chore: re-vendor canonical files from prompts at dd4027b (closes #472)
Copies .dockerignore, .gitignore, .prettierignore, check.yml and REPO_POLICIES.md from sneak/prompts at dd4027b. The repo's own entries (dist/, release/, yarn files) are kept after the canonical content. The Dockerfile gets separate lint and test phases. Its last stage depends on both, checks the git describe version and runs make build. script/lint, test, check, cibuild and docker are the canonical models. check-censored moves into the lint phase and test-verify-build into the test phase. fmt and fmt-check fall back to the nvm-installed node. The e2e image builds are uncached. Comments that cited the old 20-second test cap now say 60. Model: opus-5-5
This commit is contained in:
+77
-7
@@ -1,7 +1,77 @@
|
||||
# .git is deliberately NOT excluded: build.js shells out to `git rev-parse` for
|
||||
# build-info stamping and the Dockerfile runs `make build`, so excluding it
|
||||
# would make every built extension report commitHash "unknown".
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
release
|
||||
# .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 repo's host-built artifacts: make build writes dist/ and make package
|
||||
# writes release/. The image builds its own.
|
||||
/dist
|
||||
/release
|
||||
|
||||
@@ -3,9 +3,6 @@ on: [push]
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
# Bounds script/cibuild, a cold-cache build included, so a hang frees
|
||||
# the shared runner. README.md "In CI" has the measured times.
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
# actions/checkout v4.2.2, 2026-02-22
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
|
||||
@@ -2,11 +2,11 @@ name: e2e
|
||||
on: [push]
|
||||
|
||||
# The browser end-to-end suites, one job per browser, deliberately kept out
|
||||
# of the check workflow: REPO_POLICIES.md caps make test at 20 seconds and
|
||||
# script/cibuild is a plain `docker build .` whose Dockerfile runs
|
||||
# make check, so folding a browser suite into either would blow that cap
|
||||
# and slow the local fast path. Before this workflow every browser-level
|
||||
# guarantee in this repo held only when a human remembered to run it.
|
||||
# of the check workflow: REPO_POLICIES.md caps make test at 60 seconds and
|
||||
# script/cibuild runs make check, so folding a browser suite into either
|
||||
# would blow that cap and slow the local fast path. Before this workflow
|
||||
# every browser-level guarantee in this repo held only when a human
|
||||
# remembered to run it.
|
||||
#
|
||||
# One job per browser rather than two steps in one job, so a Chrome failure
|
||||
# does not hide the Firefox result.
|
||||
|
||||
+31
-5
@@ -11,14 +11,40 @@ 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/
|
||||
|
||||
# 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`.
|
||||
|
||||
# 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 after these lines, 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]
|
||||
|
||||
# Build output
|
||||
dist/
|
||||
|
||||
@@ -1,4 +1,2 @@
|
||||
node_modules/
|
||||
yarn.lock
|
||||
dist/
|
||||
release/
|
||||
|
||||
+67
-28
@@ -1,41 +1,80 @@
|
||||
# Lint phase: ESLint, prettier --check, and script/check-censored. The tools
|
||||
# are invoked directly rather than through `make lint` or `script/lint`, which
|
||||
# are themselves a docker build and would recurse into a daemon that does not
|
||||
# exist in a build step.
|
||||
#
|
||||
# node:22-slim (22.x LTS), 2026-02-24
|
||||
FROM node@sha256:5373f1906319b3a1f291da5d102f4ce5c77ccbe29eb637f072b6c7b70443fc36 AS base
|
||||
FROM node@sha256:5373f1906319b3a1f291da5d102f4ce5c77ccbe29eb637f072b6c7b70443fc36 AS lint
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Marks "already inside the lint container" for script/lint, which otherwise
|
||||
# shells out to docker to build the lint stage below. Nothing outside this
|
||||
# image sets it.
|
||||
ENV AUTISTMASK_LINT_NATIVE=1
|
||||
|
||||
# script/test's default 30s bound is the host figure. In here the same suite
|
||||
# starts on a cold jest cache and shares the runner with the rest of the build,
|
||||
# so 30s is too tight — it killed a healthy suite at 30.6s on a cold CI cache.
|
||||
# 180s still catches a hang in three minutes and cannot be tripped by a suite
|
||||
# that is merely running on contended hardware.
|
||||
ENV AUTISTMASK_TEST_TIMEOUT=180
|
||||
|
||||
# script/bootstrap installs all prerequisites (make via apt here; node
|
||||
# is already in the base image, yarn comes via corepack) and runs
|
||||
# yarn install --frozen-lockfile. Dependency manifests are copied first
|
||||
# so the bootstrap layer is cached until they change.
|
||||
COPY script/ script/
|
||||
COPY package.json yarn.lock ./
|
||||
RUN script/bootstrap
|
||||
|
||||
COPY . .
|
||||
|
||||
# Lint stage — fail fast on static analysis and formatting, before the tests
|
||||
# and the build. This is also the stage script/lint builds from a host, which
|
||||
# is how linting stays on the pinned ESLint rather than the host's.
|
||||
FROM base AS lint
|
||||
RUN make lint
|
||||
RUN yarn run lint
|
||||
RUN script/check-censored
|
||||
|
||||
# Test phase, same shape and for the same reason: the jest suite (its worker
|
||||
# cap is in package.json), rerun verbose on failure, then
|
||||
# script/test-verify-build.
|
||||
#
|
||||
# node:22-slim (22.x LTS), 2026-02-24
|
||||
FROM node@sha256:5373f1906319b3a1f291da5d102f4ce5c77ccbe29eb637f072b6c7b70443fc36 AS test
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY script/ script/
|
||||
COPY package.json yarn.lock ./
|
||||
RUN script/bootstrap
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN timeout 90 yarn run test || \
|
||||
{ echo "--- Rerunning with --verbose for details ---"; \
|
||||
timeout 90 yarn run test:verbose; exit 1; }
|
||||
RUN script/test-verify-build
|
||||
|
||||
# Development environment with the extension built, and the last stage: a
|
||||
# plain `docker build .` names no target and so builds this one. Nothing is
|
||||
# wanted from the two phases above; the copies are what make BuildKit build
|
||||
# them first, so this image cannot be produced unless lint and test passed. A
|
||||
# stage appended after this one would drop all three out of a plain build.
|
||||
#
|
||||
# node:22-slim (22.x LTS), 2026-02-24
|
||||
FROM node@sha256:5373f1906319b3a1f291da5d102f4ce5c77ccbe29eb637f072b6c7b70443fc36
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Full check and build. The COPY --from is a no-op file copy whose only job is
|
||||
# to make BuildKit finish the lint stage before this one starts; without it the
|
||||
# stages run in parallel and a lint failure would not fail the build early.
|
||||
FROM base AS check
|
||||
COPY --from=lint /app/package.json /dev/null
|
||||
COPY --from=test /app/package.json /dev/null
|
||||
|
||||
RUN make check
|
||||
RUN make build
|
||||
# script/bootstrap installs all prerequisites, git included. Manifests are
|
||||
# copied first so that layer stays cached until dependencies change.
|
||||
COPY script/ script/
|
||||
COPY package.json yarn.lock ./
|
||||
RUN script/bootstrap
|
||||
# A tar-stream context keeps the sender's file owners, which git refuses.
|
||||
RUN git config --system --add safe.directory /app
|
||||
|
||||
COPY . .
|
||||
|
||||
# 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, and build.js, which stamps the
|
||||
# extension with the commit it was built from, would stamp "unknown".
|
||||
ARG VERSION
|
||||
RUN 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; \
|
||||
make build
|
||||
# A LABEL cannot run git, so it carries the build argument alone; a plain
|
||||
# `docker build .` leaves it empty.
|
||||
LABEL org.opencontainers.image.version="${VERSION}"
|
||||
|
||||
@@ -222,31 +222,32 @@ provide:
|
||||
- `script/setup` — make a fresh clone ready for development: bootstrap plus the
|
||||
git pre-commit hook
|
||||
- `script/projectname` — print the project name (used for the Docker image tag)
|
||||
- `script/test` — run the test suite (jest)
|
||||
- `script/test` — build the Dockerfile's `test` phase, uncached: the jest suite,
|
||||
stopped after 90 seconds and rerun verbose if it fails, then
|
||||
`script/test-verify-build`
|
||||
- `script/test-e2e` — run the Chrome browser end-to-end suite (docker is the
|
||||
only prerequisite: it builds a pinned image that carries the repo and a fresh
|
||||
extension build, see [End-to-End Tests](#end-to-end-tests))
|
||||
- `script/test-e2e-firefox` — run the Firefox browser end-to-end suite (same,
|
||||
against an image with a pinned Firefox and geckodriver, see
|
||||
[End-to-End Tests](#end-to-end-tests))
|
||||
- `script/lint` — run ESLint (`eslint.config.js`) and then `prettier --check`,
|
||||
failing on either. It never writes: `--fix` is not in this path, so
|
||||
`make check` stays non-mutating. Linting runs in the container — the script
|
||||
builds the Dockerfile's `lint` stage — because an ESLint result that depends
|
||||
on whichever ESLint the host happens to have is not a result. Docker is
|
||||
therefore required to lint; inside that image `AUTISTMASK_LINT_NATIVE=1` makes
|
||||
the same script lint in place instead of recursing.
|
||||
- `script/fmt` — format all files (writes)
|
||||
- `script/fmt-check` — check formatting (read-only)
|
||||
- `script/check` — run test, test-verify-build, check-censored, lint, and
|
||||
fmt-check
|
||||
- `script/lint` — build the Dockerfile's `lint` phase, uncached: ESLint
|
||||
(`eslint.config.js`), `prettier --check`, then `script/check-censored`,
|
||||
failing on any of them. It never writes: `--fix` is not in this path, so
|
||||
`make check` stays non-mutating. Linting runs only in the container, because
|
||||
an ESLint result that depends on whichever ESLint the host happens to have is
|
||||
not a result, so docker is required to lint.
|
||||
- `script/fmt` — format all files (writes), on the host
|
||||
- `script/fmt-check` — check formatting (read-only), on the host
|
||||
- `script/check` — run `script/test`, `script/lint` and `script/fmt-check`
|
||||
- `script/check-censored` — assert the competitor name RULES.md bars appears
|
||||
nowhere in the working tree or under `dist/` outside its documented
|
||||
exceptions: the pinned source reference in `script/vendor-blocklist`, the two
|
||||
provider-shim identifiers in `src/content/inpage.js`, and one ERC-20's
|
||||
on-chain name in `src/shared/tokenList.js`. Each is scoped to that path and
|
||||
fails anywhere else. Part of `make check`, which inspects `dist/` when there
|
||||
is one and says loudly when there is not; `make build` re-runs it with
|
||||
fails anywhere else. Run by the `lint` phase, so part of `make check`; it
|
||||
inspects `dist/` when there is one and says loudly when there is not, and the
|
||||
build context of that phase never has one. `make build` re-runs it with
|
||||
`--require-dist`, so a build artifact is always covered
|
||||
- `script/package` — produce the release artifacts: `make build` first, so the
|
||||
archives can only ever be made from a `dist/` that has been verified against
|
||||
@@ -281,14 +282,20 @@ provide:
|
||||
failing and a succeeding release build step, and read the `make build` and
|
||||
`make build-debug` recipes back out of `make -n` to check that they pass the
|
||||
mode as an argument on a scrubbed environment and wrap only the release path.
|
||||
Part of `make check`; it reads no build artifacts and writes nothing under
|
||||
`dist/`. The cases that depend on file permissions cannot mean anything for a
|
||||
process that is not subject to them, so the harness proves its runner against
|
||||
a mode-000 file before counting them, dropping to an unprivileged user when
|
||||
run as root; if it cannot, it skips those cases and says so in a banner rather
|
||||
than passing them.
|
||||
- `script/docker` — build the Docker image tagged via `script/projectname`
|
||||
- `script/cibuild` — CI entrypoint: plain `docker build .`
|
||||
Run by the `test` phase, so part of `make check`; it reads no build artifacts
|
||||
and writes nothing under `dist/`. The cases that depend on file permissions
|
||||
cannot mean anything for a process that is not subject to them, so the harness
|
||||
proves its runner against a mode-000 file before counting them, dropping to an
|
||||
unprivileged user when run as root; if it cannot, it skips those cases and
|
||||
says so in a banner rather than passing them.
|
||||
- `script/docker` — build the Docker image, uncached and tagged via
|
||||
`script/projectname`: the `lint` and `test` phases, then `make build` in the
|
||||
last stage. It passes the version `git describe --tags --always --dirty` gives
|
||||
on the host; without one, the build runs `git describe` on the `.git` in the
|
||||
build context, which `.dockerignore` sends without its `config`, and fails if
|
||||
git cannot read it
|
||||
- `script/cibuild` — CI entrypoint: `script/bootstrap`, `script/check`, then the
|
||||
same image build as `script/docker`
|
||||
- `script/precommit` — run by the git pre-commit hook; runs `script/check`
|
||||
- `script/install-precommit` — install the git pre-commit hook
|
||||
|
||||
@@ -617,7 +624,7 @@ Two limits are worth knowing, both real differences from the Chrome suite:
|
||||
out, but it cannot report which requests were attempted.
|
||||
|
||||
Neither `make test-e2e` nor `make test-e2e-firefox` is part of `make check` or
|
||||
`make test`. `REPO_POLICIES.md` caps `make test` at 20 seconds and a browser
|
||||
`make test`. `REPO_POLICIES.md` caps `make test` at 60 seconds and a browser
|
||||
suite does not fit; nothing in `tests/e2e/` is named `*.test.js`, so jest cannot
|
||||
pick it up either. Run them locally before changing anything under
|
||||
`src/popup/views/`.
|
||||
@@ -626,7 +633,7 @@ pick it up either. Run them locally before changing anything under
|
||||
|
||||
`.gitea/workflows/e2e.yml` runs both suites on every push, as two jobs —
|
||||
`e2e-chrome` and `e2e-firefox` — separate from the `check` workflow, so the
|
||||
20-second `make test` cap and the local fast path are untouched. Each job is a
|
||||
60-second `make test` cap and the local fast path are untouched. Each job is a
|
||||
checkout and the matching `script/` entrypoint, nothing else.
|
||||
|
||||
Docker is the only thing either job needs from the runner, and that is not an
|
||||
@@ -652,19 +659,21 @@ build fails, and when the browser fails to start; the Chrome harness aborts the
|
||||
suite outright if its network interception is not in effect.
|
||||
|
||||
Measured on this repo's runner in the green runs of early October 2026, from a
|
||||
warm docker cache to a cold one: `check` 49s to 3m37s, `e2e-chrome` 1m44s to
|
||||
4m48s, and `e2e-firefox` 31s to 4m07s. A cold cache adds three to four minutes
|
||||
to each job, spent rebuilding its image: reinstalling dependencies and, for
|
||||
`e2e-firefox`, installing Firefox, geckodriver and their system libraries. Those
|
||||
warm docker cache to a cold one: `e2e-chrome` 1m44s to 4m48s, and `e2e-firefox`
|
||||
31s to 4m07s. A cold cache adds three to four minutes to each job, spent
|
||||
rebuilding its image: reinstalling dependencies and, for `e2e-firefox`,
|
||||
installing Firefox, geckodriver and their system libraries. Both scripts now
|
||||
build their image with `--no-cache`, so every run pays the cold figure. Those
|
||||
`e2e-chrome` runs predate the cases that wait in real time for a receipt to end
|
||||
in error. `make test-e2e` now takes 3m51s locally with its image cached, so a
|
||||
cold `e2e-chrome` run comes to about seven minutes.
|
||||
in error. `make test-e2e` took 3m51s locally with its image cached, so with the
|
||||
image rebuilt every run, an `e2e-chrome` run comes to about seven minutes.
|
||||
|
||||
Every job has a `timeout-minutes` cap, so a hung build or browser ends the job
|
||||
instead of holding the shared runner: `check` 10 minutes, `e2e-firefox` 15 and
|
||||
Each e2e job has a `timeout-minutes` cap, so a hung build or browser ends the
|
||||
job instead of holding the shared runner: `e2e-firefox` 15 minutes and
|
||||
`e2e-chrome` 20, each over two and a half times the job's slowest cold run. A
|
||||
job that reaches its cap has hung; read it as a hang, not as a slow run to
|
||||
retry.
|
||||
retry. The `check` job has no cap: `.gitea/workflows/check.yml` is the canonical
|
||||
copy from `sneak/prompts`, kept byte-identical.
|
||||
|
||||
### Element id guard (part of `make check`)
|
||||
|
||||
|
||||
+355
-84
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Repository Policies
|
||||
last_modified: 2026-07-06
|
||||
last_modified: 2026-10-04
|
||||
---
|
||||
|
||||
This document covers repository structure, tooling, and workflow standards. Code
|
||||
@@ -60,17 +60,28 @@ 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 .`; 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, runs `script/bootstrap`, runs `script/check`, and builds the image
|
||||
with the version; the Gitea workflow calls it. **`script/cibuild` runs
|
||||
`script/bootstrap` first**, because the workflow checks out the repo and runs
|
||||
nothing else, while `script/fmt-check` runs the formatter on the host: on a
|
||||
pristine checkout with nothing installed the run dies there, after the
|
||||
containerised gates have passed. **The bootstrap alone is not enough**:
|
||||
`script/bootstrap` installs node and yarn under nvm and leaves neither on the
|
||||
`PATH` of the shell that called it, so a bare `yarn` still exits 127. The host
|
||||
entrypoints that need yarn — `script/fmt` and `script/fmt-check` — therefore
|
||||
source nvm for the pinned node version before invoking it, exactly as
|
||||
`script/bootstrap`'s own install step does. A runner carrying nothing but
|
||||
docker and git then gets through `script/check`. 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).
|
||||
@@ -89,87 +100,198 @@ style conventions are in separate documents:
|
||||
contributor should be able to understand the entire development workflow by
|
||||
reading the Makefile.
|
||||
|
||||
- Every repo should have a `Dockerfile`. All Dockerfiles must run `make check`
|
||||
as a build step so the build fails if the branch is not green. For non-server
|
||||
repos, the Dockerfile should bring up a development environment and run
|
||||
`make check`. For server repos, `make check` should run as an early build
|
||||
stage before the final image is assembled. Dockerfiles install development
|
||||
prerequisites by running `script/bootstrap` rather than duplicating installs
|
||||
inline; COPY `script/` and the dependency manifests (`package.json` +
|
||||
`yarn.lock`, `go.mod` + `go.sum`, etc.) before running it so the bootstrap
|
||||
layer stays cached until dependencies change.
|
||||
- Every repo should have a `Dockerfile`, and it carries the repo's gates: a
|
||||
`lint` phase and a `test` phase, with the final stage depending on both so the
|
||||
image cannot be built unless they pass. For non-server repos the final stage
|
||||
brings up a development environment; for server repos it is the runtime image.
|
||||
The gate phases and the build stage start from their pinned base images and
|
||||
install what those images lack either inline, as the canonical Go `Dockerfile`
|
||||
below does for `git`, or by running `script/bootstrap`, as the `prompts`
|
||||
repo's own `Dockerfile` does for its yarn packages. The development
|
||||
environment stage installs development prerequisites by running
|
||||
`script/bootstrap` rather than duplicating its installs inline. A stage that
|
||||
runs `script/bootstrap` COPYs `script/` and the dependency manifests
|
||||
(`package.json` + `yarn.lock`, `go.mod` + `go.sum`, etc.) before running it.
|
||||
|
||||
- **Dockerfiles must use a separate lint stage for fail-fast feedback.** Go
|
||||
repos use a multistage build where linting runs in an independent stage based
|
||||
on the `golangci/golangci-lint` image (pinned by hash). This stage runs
|
||||
`make fmt-check` and `make lint` before the full build begins. The build stage
|
||||
then declares an explicit dependency on the lint stage via
|
||||
`COPY --from=lint /src/go.sum /dev/null`, which forces BuildKit to complete
|
||||
linting before proceeding to compilation and tests. This ensures lint failures
|
||||
surface in seconds rather than minutes, without blocking on dependency
|
||||
download or compilation in the build stage.
|
||||
- **Linting and testing run in Docker, as phases of the `Dockerfile`.** There is
|
||||
no separate lint file. `script/lint` and `script/test` each build one phase
|
||||
and nothing else:
|
||||
|
||||
The standard pattern for a Go repo Dockerfile is:
|
||||
```sh
|
||||
docker build --no-cache --target lint -t "$(script/projectname)-lint" .
|
||||
docker build --no-cache --target test -t "$(script/projectname)-test" .
|
||||
```
|
||||
|
||||
**A stage that is not the last one in the file is built only when the final
|
||||
stage's chain depends on it, or when `--target` names it.** That is why the
|
||||
two gates are always invoked by name here, and why the final stage carries a
|
||||
`COPY --from=` of a harmless file from each of them: without that edge a
|
||||
plain `docker build .` builds the last stage alone and exits 0 having linted
|
||||
and tested nothing.
|
||||
|
||||
**Every `docker build` in `script/` is tagged**, here and in
|
||||
`script/cibuild` and `script/docker`. An untagged build leaves a dangling
|
||||
image behind on every invocation, on every developer host and every CI
|
||||
runner; a tagged one replaces the previous image.
|
||||
|
||||
Inside a phase the tool is invoked directly — `golangci-lint`, `go test`,
|
||||
`eslint`, `prettier` — never through `make lint` or `script/test`, which are
|
||||
themselves a `docker build` and would recurse into a daemon that does not
|
||||
exist in a build step. Formatting is the exception and stays on the host:
|
||||
`script/fmt` writes the working tree, and `script/fmt-check` is its
|
||||
read-only twin.
|
||||
|
||||
**No lint verdict may come from a host invocation of the linter.** On a
|
||||
shared host golangci-lint reads a result cache keyed on file content rather
|
||||
than location, so a second checkout of the same content is served the first
|
||||
one's findings, and a host-global lock in `$TMPDIR` makes concurrent runs
|
||||
exit non-zero with `parallel golangci-lint is running` — a status a caller
|
||||
cannot tell from real findings. Both have produced wrong verdicts in this
|
||||
org, in both directions. A container has its own cache, its own `TMPDIR` and
|
||||
a digest-pinned binary, so neither is reachable.
|
||||
|
||||
- **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
|
||||
the check `RUN` is served from cache, nothing executes, and the build still
|
||||
exits 0. Every `docker build` in `script/` therefore passes `--no-cache`:
|
||||
`script/lint`, `script/test`, `script/cibuild` and `script/docker` are the
|
||||
four, and there is no fifth — `script/check` runs the two gate phases and
|
||||
`script/fmt-check`, and builds no image of its own. A bare `docker build .` is
|
||||
not evidence that anything ran: a sub-second build reporting success is a
|
||||
cache hit, not a result. Never invalidate by pruning — `docker builder prune`
|
||||
and friends destroy a build cache shared with every other build on the host.
|
||||
When a check is added or changed, prove it works by planting a defect it must
|
||||
catch and watching the run fail on it, then revert the defect. A green run
|
||||
alone shows neither that the check ran nor that it covers what it should.
|
||||
|
||||
- **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
|
||||
hash), so lint failures surface in seconds rather than after a full compile,
|
||||
and the test phase is based on the Debian Go image. The canonical Go repo
|
||||
`Dockerfile`:
|
||||
|
||||
```dockerfile
|
||||
# Lint stage — fast feedback on formatting and lint issues
|
||||
# Lint phase
|
||||
# golangci/golangci-lint:v2.x.x, YYYY-MM-DD
|
||||
FROM golangci/golangci-lint@sha256:... AS lint
|
||||
WORKDIR /src
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
COPY . .
|
||||
RUN make fmt-check
|
||||
RUN make lint
|
||||
RUN golangci-lint run --config .golangci.yml ./...
|
||||
|
||||
# Build stage
|
||||
# golang:1.x-alpine, YYYY-MM-DD
|
||||
FROM golang@sha256:... AS builder
|
||||
# Test phase. -race needs cgo and so a C compiler, which the Debian Go
|
||||
# image ships and the alpine one does not.
|
||||
# golang:1.x, YYYY-MM-DD
|
||||
FROM golang@sha256:... AS test
|
||||
WORKDIR /src
|
||||
|
||||
# Force BuildKit to run the lint stage before proceeding
|
||||
COPY --from=lint /src/go.sum /dev/null
|
||||
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
COPY . .
|
||||
RUN make test
|
||||
RUN go test -timeout 90s -race -cover ./... || \
|
||||
{ echo "--- Rerunning with -v for details ---"; \
|
||||
go test -timeout 90s -race -v ./...; exit 1; }
|
||||
|
||||
ARG VERSION=dev
|
||||
RUN CGO_ENABLED=0 go build -trimpath \
|
||||
# Build stage. Nothing is wanted from either phase above; the copies
|
||||
# are what make BuildKit build them first, so this stage cannot run
|
||||
# unless lint and test passed.
|
||||
# golang:1.x-alpine, YYYY-MM-DD
|
||||
FROM golang@sha256:... AS builder
|
||||
COPY --from=lint /src/go.sum /dev/null
|
||||
COPY --from=test /src/go.sum /dev/null
|
||||
RUN apk add --no-cache git
|
||||
# A tar-stream context keeps the sender's file owners, which git refuses.
|
||||
RUN git config --system --add safe.directory /src
|
||||
WORKDIR /src
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
COPY . .
|
||||
|
||||
# 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 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 /app ./cmd/app/
|
||||
|
||||
# Runtime stage
|
||||
# Runtime stage, and the last one
|
||||
FROM alpine@sha256:...
|
||||
COPY --from=builder /app /usr/local/bin/app
|
||||
ENTRYPOINT ["app"]
|
||||
```
|
||||
|
||||
Key points:
|
||||
- The lint stage uses the `golangci/golangci-lint` image directly (it
|
||||
includes both Go and the linter), so there is no need to install the
|
||||
linter separately.
|
||||
- `COPY --from=lint /src/go.sum /dev/null` is a no-op file copy that creates
|
||||
a stage dependency. BuildKit runs stages in parallel by default; without
|
||||
this line, the build stage would not wait for lint to finish and a lint
|
||||
failure might not fail the overall build.
|
||||
- The lint phase uses the `golangci/golangci-lint` image directly (it has
|
||||
both Go and the linter), so nothing needs installing.
|
||||
- `COPY --from=<phase> /src/go.sum /dev/null` is a no-op copy whose only
|
||||
purpose is the ordering edge. BuildKit runs stages in parallel by default,
|
||||
and a stage nothing depends on is not built at all, so without these two
|
||||
lines a red gate would not fail the build.
|
||||
- Keep the runtime stage last, and if you add a stage after it, give it the
|
||||
same two copies. A plain `docker build .` builds the last stage's chain
|
||||
and nothing else.
|
||||
- If the project uses `//go:embed` directives that reference build artifacts
|
||||
(e.g. a web frontend compiled in a separate stage), the lint stage must
|
||||
(e.g. a web frontend compiled in a separate stage), the lint phase must
|
||||
create placeholder files so the embed directives resolve. Example:
|
||||
`RUN mkdir -p web/dist && touch web/dist/index.html web/dist/style.css`.
|
||||
The lint stage should not depend on the actual build output — it exists to
|
||||
fail fast.
|
||||
- If the project requires CGO or system libraries for linting (e.g.
|
||||
`vips-dev`), install them in the lint stage with `apk add`.
|
||||
- The build stage runs `make test` after compilation setup. Tests run in the
|
||||
build stage, not the lint stage, because they may require compiled
|
||||
artifacts or heavier dependencies.
|
||||
- If the project requires CGO or system libraries for linting, install them
|
||||
in the lint phase. The `golangci/golangci-lint` image is Debian-based and
|
||||
has no `apk`, so install with `apt-get` under the Debian package name
|
||||
(`libvips-dev`, where alpine says `vips-dev`), and delete the package
|
||||
lists in the same `RUN`, so the layer does not keep them:
|
||||
|
||||
```dockerfile
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends libvips-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
```
|
||||
|
||||
- `.dockerignore` lets `.git` into the build context. It keeps out every git
|
||||
`config` at any depth (`**/.git/config`, `**/.git/modules/**/config`): the
|
||||
repository's own, each submodule's under `.git/modules/`, and that of a
|
||||
submodule keeping its own `.git` directory. `git describe` does not need
|
||||
them, and each can hold a credential: a password in a remote URL, or the
|
||||
token the CI checkout step stores there. A submodule whose name has a
|
||||
`config` segment (`config`, `deploy/config`, `config/lib`) loses its whole
|
||||
git directory to `**/.git/modules/**/config`, and Go's version stamping
|
||||
then fails the build: give it a name without that segment
|
||||
(`git submodule add --name`). The stage that compiles has `git` (the
|
||||
Debian Go image has it; an alpine one needs `apk add --no-cache git`) and
|
||||
takes the version from the `VERSION` build argument when one is given,
|
||||
otherwise from `git describe --tags --always`. That gives the tag on a
|
||||
tagged commit; on a later commit, the tag, the number of commits since it
|
||||
and the short commit (`v1.2.3-4-gabc1234`); and the short commit when no
|
||||
tag is reachable. The stage that compiles also marks its working directory
|
||||
safe for git (`git config --system --add safe.directory /src`): a context
|
||||
sent as a tar stream keeps the sender's file owners, and git refuses a
|
||||
checkout owned by another user, so the version would come out empty.
|
||||
`ARG VERSION` has no default, and the build fails if the context carries
|
||||
`.git` and the version still comes out empty, `dev` or `unknown`. A plain
|
||||
`docker build .` with no build arguments must succeed; a Dockerfile that
|
||||
refuses an empty build argument drops that refusal and keeps the argument.
|
||||
|
||||
- Every repo should have a Gitea Actions workflow (`.gitea/workflows/`) that
|
||||
runs `script/cibuild` (which runs `docker build .`) on push. Since the
|
||||
Dockerfile already runs `make check`, a successful build implies all checks
|
||||
pass.
|
||||
runs `script/cibuild` on push, and checks out the repo as its only other step.
|
||||
That script bootstraps, runs the gate phases, and then builds the image, so a
|
||||
successful run means every check passed; a bare `docker build .` does not
|
||||
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. A separate
|
||||
workflow limited to `main` by a `branches` list under `on: push` cannot be
|
||||
checked by review: to try a change to it, add the feature branch to that list
|
||||
and push, then remove the branch from the list again before merging. Keep any
|
||||
job in it that publishes behind `if: github.ref_name == 'main'`, so the run
|
||||
from the feature branch publishes nothing.
|
||||
|
||||
- Use platform-standard formatters: `black` for Python, `prettier` for
|
||||
JS/CSS/Markdown/HTML, `go fmt` for Go. Always use default configuration with
|
||||
@@ -189,14 +311,21 @@ style conventions are in separate documents:
|
||||
module under test to verify it compiles/parses. There is no excuse for
|
||||
`make test` to be a no-op.
|
||||
|
||||
- `make test` must complete in under 20 seconds. Add a 30-second timeout in the
|
||||
Makefile.
|
||||
- `make test` must complete in under 60 seconds. That is the hard cap, and a
|
||||
suite that exceeds it fails. Under 20 seconds is the target. A suite between
|
||||
20 and 60 seconds is still green, but the overage must be filed as an
|
||||
improvement bug against that repo. Add a 90-second timeout to the test
|
||||
invocation (`go test -timeout 90s`). The backstop deliberately sits above the
|
||||
hard cap so that it catches a genuinely hung test rather than a merely slow
|
||||
one.
|
||||
|
||||
- **`make test` should use the conditional verbose rerun pattern.** Run tests
|
||||
without `-v` (verbose) first. If tests fail, automatically rerun with `-v` to
|
||||
show full output. This keeps CI logs and `docker build` output clean on
|
||||
success (just package/suite summaries) while providing full diagnostic detail
|
||||
on failure (every test case, every assertion). The general shell pattern:
|
||||
- **The test command should use the conditional verbose rerun pattern.** Run
|
||||
tests without `-v` (verbose) first. If tests fail, automatically rerun with
|
||||
`-v` to show full output. This keeps CI logs and `docker build` output clean
|
||||
on success (just package/suite summaries) while providing full diagnostic
|
||||
detail on failure (every test case, every assertion). The command lives in the
|
||||
`test` phase of the `Dockerfile`, since `script/test` builds that phase; the
|
||||
Makefile form below is the same pattern for any repo-local invocation:
|
||||
|
||||
```makefile
|
||||
test:
|
||||
@@ -209,11 +338,26 @@ style conventions are in separate documents:
|
||||
|
||||
```makefile
|
||||
test:
|
||||
@go test -timeout 30s -race -cover ./... || \
|
||||
@go test -count=1 -timeout 90s -race -cover ./... || \
|
||||
{ echo "--- Rerunning with -v for details ---"; \
|
||||
go test -timeout 30s -race -v ./...; exit 1; }
|
||||
go test -count=1 -timeout 90s -race -v ./...; exit 1; }
|
||||
```
|
||||
|
||||
`-count=1` is required on both invocations: it defeats Go's test _result_
|
||||
cache, so neither run can report a stored pass in place of running the
|
||||
tests. It leaves the build cache alone, so it costs the runtime of the suite
|
||||
and no recompilation.
|
||||
|
||||
That cache is Go's own, separate from Docker's layer cache. Go stores a
|
||||
passing result in its cache directory (`GOCACHE`), and when the same tests
|
||||
run again on unchanged code it prints that result, marked `(cached)`,
|
||||
without running them. That matters on a developer's machine, where this
|
||||
target runs and the directory lasts from one run to the next. The `test`
|
||||
phase of the `Dockerfile` needs no `-count=1`: its base image holds no
|
||||
result for this repo's tests and nothing before its `go test` step runs a
|
||||
test, so there is nothing to replay. `--no-cache` (above) is what makes that
|
||||
step run on an unchanged tree.
|
||||
|
||||
Python example:
|
||||
|
||||
```makefile
|
||||
@@ -239,10 +383,84 @@ 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
|
||||
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.gitignore` when setting up
|
||||
a new repo.
|
||||
editor files (`.swp`, `*~`), in-repo agent scratch directories (`.claude/`),
|
||||
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 `.dockerignore` and must not be transplanted into one unmodified.
|
||||
|
||||
- **`.dockerignore` does not use `.gitignore` semantics, and copying patterns
|
||||
across unmodified leaves secrets in the build context.** Docker matches with
|
||||
`moby/patternmatcher`: `filepath.Match` semantics plus a `**` extension, so
|
||||
`*` does not cross `/` and a pattern without a leading `**/` is anchored at
|
||||
the build-context root. A `.dockerignore` listing `.env`, `*.pem` and `*.key`
|
||||
therefore excludes only the copies at the repository root, while `config/.env`
|
||||
and `certs/server.key` still reach the context and can land in an image layer
|
||||
— which is more dangerous than a short file with no secret patterns at all,
|
||||
because it reads as solved and stops anyone looking. Give every
|
||||
depth-independent pattern the `**/` prefix and leave only genuinely
|
||||
root-anchored entries unprefixed: `.claude`, and the repo's own host-built
|
||||
binary, written `/myapp` and never `**/myapp`, which would also match
|
||||
`cmd/myapp/` and delete the package directory from the context. Matching is
|
||||
case-sensitive, and an ALL-CAPS twin per pattern still misses `Server.Key`, so
|
||||
secret names use character ranges — `**/*.[kK][eE][yY]`, `**/*.[pP][eE][mM]`,
|
||||
and likewise for `.envrc` and the extensionless SSH keys. Where such a pattern
|
||||
also catches something the build needs, re-include it with a negation
|
||||
(`!docs/example.env`); deleting the pattern reopens the exposure for every
|
||||
other file it covers. Fetch the standard `.dockerignore` from
|
||||
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.dockerignore` and extend
|
||||
it with the repo's own artifacts.
|
||||
|
||||
- **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 under `COPY . .` the build context
|
||||
inflates by a multiple of the repo and another session's unreviewed work can
|
||||
be copied into an image layer. In `.gitignore` the entry is `.claude/`,
|
||||
unanchored. In `.dockerignore` it is `.claude`, anchored and with **no** `**/`
|
||||
prefix, because the prefixed form would also delete any nested directory of
|
||||
that name from the build. Anchoring carries a known gap that the canonical
|
||||
`.dockerignore` states in its own comment, since consuming repos receive the
|
||||
file and not the tracker: the directory is created in the agent's working
|
||||
directory, so a repo running agents in subdirectories still ships
|
||||
`services/api/.claude/` and must add its own anchored entry there.
|
||||
|
||||
- **A plain `docker build .` of a clone stamps the version that
|
||||
`git describe --tags --always` gives**, derived from the `.git` in the build
|
||||
context as the canonical `Dockerfile` above shows. Without its failure check,
|
||||
a missing `git` or an unreadable checkout would leave `-X main.Version=` empty
|
||||
and the build would still exit 0. `script/docker` and `script/cibuild` pass
|
||||
the version they compute on the host; it takes precedence. They do this
|
||||
byte-identically across repos:
|
||||
|
||||
```sh
|
||||
# Own line: a failing command substitution inside an argument does not
|
||||
# trip `set -e`, so the inline form degrades to an empty constant.
|
||||
version="$(git describe --tags --always --dirty 2>/dev/null || true)"
|
||||
[ -n "$version" ] || version="unknown"
|
||||
docker build --no-cache \
|
||||
--build-arg VERSION="$version" \
|
||||
-t "$(script/projectname)" .
|
||||
```
|
||||
|
||||
`--always` makes an untagged repo yield an abbreviated commit hash rather
|
||||
than failing, and the `[ -n "$version" ]` line is the single place the
|
||||
fallback is applied — a live check that fires on a build from an export with
|
||||
no `.git` and on a repository with no commits yet. Do not fold it into the
|
||||
substitution as `|| echo unknown`, which makes the guard unreachable. The
|
||||
Dockerfile's side is `ARG VERSION` in the stage that compiles, declared
|
||||
there because `ARG` is stage-scoped; passing `VERSION` to a repo whose
|
||||
Dockerfile declares no such `ARG` is ignored and costs nothing, which is why
|
||||
the scripts stay byte-identical. One consequence for CI: the standard
|
||||
checkout action clones shallow and fetches no tags, so a repo that embeds a
|
||||
tag-derived version must set `fetch-depth: 0` on its checkout step.
|
||||
|
||||
- **Verify `.dockerignore` by enumerating the image, not by reading the
|
||||
patterns.** Plant files at the root _and_ at least two directories deep, build
|
||||
a probe image that does `COPY . .`, and list what actually landed
|
||||
(`docker run --rm --entrypoint find IMAGE /app`). The `transferring context`
|
||||
size is not a substitute: a nested secret is a few bytes, and BuildKit
|
||||
transfers only the delta from the previous build.
|
||||
|
||||
- **No build artifacts in version control.** Code-derived data (compiled
|
||||
bundles, minified output, generated assets) must never be committed to the
|
||||
@@ -258,9 +476,56 @@ style conventions are in separate documents:
|
||||
- Make all changes on a feature branch. You can do whatever you want on a
|
||||
feature branch.
|
||||
|
||||
- `.golangci.yml` is standardized and must _NEVER_ be modified by an agent, only
|
||||
manually by the user. Fetch from
|
||||
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.golangci.yml`.
|
||||
- `.golangci.yml` is standardized. The vendored copy in a consuming repo must
|
||||
_NEVER_ be modified by an agent: fetch it from
|
||||
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.golangci.yml` and keep it
|
||||
byte-identical, so that no repo can quietly loosen its own linting. Linter
|
||||
configuration changes are made to the canonical copy in the `prompts` repo and
|
||||
reach consuming repos by re-vendoring; an agent may open a PR against
|
||||
canonical, which only the user merges. One list is exempt from byte-identity,
|
||||
because it cannot be written once for every repo: the `deny` list of the
|
||||
`test-support` depguard rule, where a repo names its own test-support packages
|
||||
by full import path. A repo adds entries there and changes nothing else, and a
|
||||
re-vendor carries its entries forward. The canonical golangci-lint version is
|
||||
v2.14.0 (released 2026-09-24), pinned as the digest of the lint phase's base
|
||||
image
|
||||
(`golangci/golangci-lint@sha256:ad862ba6b3798cbe0fd9fd7408d498fd74fbd2623a92406b2fd3898faf0bf98f`,
|
||||
which reports `2.14.0 built with go1.27.0 from 114493f9`). A module's `go`
|
||||
directive must not name a newer Go minor version than the one golangci-lint
|
||||
was built with, or golangci-lint refuses to lint it: this release lints
|
||||
`go 1.27.1` but not `go 1.28`. That digest is the only pin, since no repo
|
||||
installs golangci-lint on the host. A repo sets the lint phase digest to the
|
||||
one named here and re-vendors `.golangci.yml` in the same commit, whichever of
|
||||
the two prompted the change: the canonical copy can name linters that an older
|
||||
golangci-lint rejects, and a newer golangci-lint can add linters that
|
||||
`default: all` switches on until the canonical copy disables them.
|
||||
|
||||
- **`script/bootstrap` installs a pinned tool by comparing versions, never by
|
||||
testing presence.** An `if ! command -v <tool>; then install; fi` guard tests
|
||||
`PATH` only, so on an already-provisioned machine the pin is inert and a
|
||||
version bump is a silent no-op — while the Dockerfile, installing into a clean
|
||||
image, gets the pinned version, so a local `make check` and `make docker` can
|
||||
disagree about what the tool even is. The canonical form:
|
||||
- compares the installed version against the pin over the **whole** version
|
||||
token; a parser that stops at the first `-` reports `2.12.2` for a host
|
||||
running `2.12.2-rc1` and skips the install;
|
||||
- treats absent, non-zero, empty or unrecognised `--version` output as a
|
||||
mismatch, so the failure direction is a redundant install and never a
|
||||
skipped one;
|
||||
- after installing, re-resolves the binary the way callers do — `hash -r`,
|
||||
then through `PATH`, not through the directory the installer wrote to —
|
||||
and fails naming the resolved path, since an install that a shadowing
|
||||
binary hides succeeds while changing nothing any caller sees;
|
||||
- is actually called, and prints the version on both success paths: a
|
||||
function defined and never invoked has the same exit status and the same
|
||||
empty output as one that worked.
|
||||
|
||||
Keep it POSIX sh: no arrays, no `[[`, no `grep -P`.
|
||||
|
||||
A Go tool a repo needs on the host is installed with `go install` pinned to
|
||||
a commit hash (`go install <package>@<commit hash>`). It is never tracked as
|
||||
a `go.mod` tool dependency or through a `tools.go` file, either of which
|
||||
pulls the tool's own dependencies into the repo's `go.mod` and `go.sum`.
|
||||
|
||||
- When pinning images or packages by hash, add a comment above the reference
|
||||
with the version and date (YYYY-MM-DD).
|
||||
@@ -374,12 +639,14 @@ style conventions are in separate documents:
|
||||
settings.
|
||||
|
||||
- Avoid putting files in the repo root unless necessary. Root should contain
|
||||
only project-level config files (`README.md`, `Makefile`, `Dockerfile`,
|
||||
`LICENSE`, `.gitignore`, `.editorconfig`, `REPO_POLICIES.md`, and
|
||||
language-specific config). Everything else goes in a subdirectory. Canonical
|
||||
subdirectory names:
|
||||
only project-level config files (`README.md`, `AGENTS.md`, `Makefile`,
|
||||
`Dockerfile`, `LICENSE`, `.gitignore`, `.editorconfig`, `REPO_POLICIES.md`,
|
||||
and language-specific config). Everything else goes in a subdirectory.
|
||||
Canonical subdirectory names:
|
||||
- `bin/` — executable scripts and tools
|
||||
- `cmd/` — Go command entrypoints
|
||||
- `cmd/` — Go command entrypoints; thin only: one `main.go` per binary whose
|
||||
body is a single call into `internal/` or `pkg/`, no project logic in
|
||||
`cmd/`
|
||||
- `configs/` — configuration templates and examples
|
||||
- `deploy/` — deployment manifests (k8s, compose, terraform)
|
||||
- `docs/` — documentation and markdown (README.md stays in root)
|
||||
@@ -406,3 +673,7 @@ style conventions are in separate documents:
|
||||
- Go: `go.mod`, `go.sum`, `.golangci.yml`
|
||||
- JS: `package.json`, `yarn.lock`, `.prettierrc`, `.prettierignore`
|
||||
- Python: `pyproject.toml`
|
||||
|
||||
- Guidance for coding agents lives in one `AGENTS.md` at the repository root. It
|
||||
is never committed under a file or directory named after one agent tool, such
|
||||
as `CLAUDE.md` or `.claude/`, and never split into separate memory files.
|
||||
|
||||
@@ -45,6 +45,18 @@ but the review is broader than any of them.
|
||||
|
||||
# Completed Steps
|
||||
|
||||
- 2026-10-06: The canonical files are re-vendored from `sneak/prompts` at
|
||||
`dd4027b` ([#472](https://git.eeqj.de/sneak/AutistMask/issues/472)). The
|
||||
`Dockerfile` has separate `lint` and `test` phases, and its last stage depends
|
||||
on both before it runs `make build`. `script/lint` and `script/test` each
|
||||
build one phase, uncached. `script/check-censored` runs in the `lint` phase
|
||||
and `script/test-verify-build` in the `test` phase. `script/check` runs the
|
||||
two phases and `script/fmt-check`. `script/cibuild` bootstraps, runs
|
||||
`script/check`, then builds the image uncached. With no version passed, the
|
||||
image build takes one from `git describe` on the `.git` in the build context,
|
||||
which `.dockerignore` now sends without its `config`. The vendored `check.yml`
|
||||
has no `timeout-minutes`, so the `check` job's cap is gone.
|
||||
|
||||
- 2026-10-05: `make build` no longer prints the Node `DEP0205`
|
||||
`module.register()` deprecation warning
|
||||
([#355](https://git.eeqj.de/sneak/AutistMask/issues/355)). The call came from
|
||||
|
||||
@@ -40,7 +40,7 @@ const AUDITED_MODULE = "src/shared/constants.js";
|
||||
// the build, whether or not a text matcher would have recognized it. A
|
||||
// background entry point the table does not name fails as well, so a second
|
||||
// worker is protected by default rather than by someone remembering this file.
|
||||
// Dockerfile:42 runs `make build`, so it is enforced in CI.
|
||||
// The Dockerfile's last stage runs `make build`, so it is enforced in CI.
|
||||
|
||||
// The build receipt: every file this build emits, with its sha256 and whether
|
||||
// it is one of the audited bundles. script/verify-build is handed this and
|
||||
|
||||
+3
-3
@@ -16,9 +16,9 @@ so the "release commit" throughout is the `main` commit the milestone PR merged.
|
||||
## Procedure
|
||||
|
||||
1. **Confirm `main` is green in CI.** The `check` workflow
|
||||
(`.gitea/workflows/check.yml`) runs `script/cibuild`, i.e. `docker build .`,
|
||||
and the `Dockerfile` runs `make check` as a build step, so a green `check`
|
||||
run is a green `make check`. Find the run for the exact release commit on the
|
||||
(`.gitea/workflows/check.yml`) runs `script/cibuild`, which runs
|
||||
`script/check` and then builds the image uncached, so a green `check` run is
|
||||
a green `make check`. Find the run for the exact release commit on the
|
||||
tracker's Actions view. _Check:_ that commit's `check` run succeeded; running
|
||||
`make check` on a clean checkout of the commit reproduces it and exits 0.
|
||||
|
||||
|
||||
+4
-4
@@ -1,14 +1,14 @@
|
||||
#!/bin/sh
|
||||
# script/check: run all checks (test, test-verify-build, lint, fmt-check).
|
||||
# Our own extension to scripts-to-rule-them-all. Must not modify any files.
|
||||
# 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/test-verify-build"
|
||||
"$SCRIPT_DIR/check-censored"
|
||||
"$SCRIPT_DIR/lint"
|
||||
"$SCRIPT_DIR/fmt-check"
|
||||
}
|
||||
|
||||
+19
-4
@@ -1,13 +1,28 @@
|
||||
#!/bin/sh
|
||||
# script/cibuild: run the CI build. The Dockerfile runs make check, so
|
||||
# a successful build implies all checks pass.
|
||||
# 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.
|
||||
# --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
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
|
||||
ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)"
|
||||
|
||||
main() {
|
||||
cd "$ROOT"
|
||||
docker build .
|
||||
"$SCRIPT_DIR/bootstrap"
|
||||
"$SCRIPT_DIR/check"
|
||||
# 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"
|
||||
docker build --no-cache \
|
||||
--build-arg VERSION="$version" \
|
||||
-t "$("$SCRIPT_DIR/projectname")" .
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
+11
-1
@@ -1,6 +1,8 @@
|
||||
#!/bin/sh
|
||||
# script/docker: build the Docker image tagged with the project name.
|
||||
# Identical in all repos; the tag comes from script/projectname.
|
||||
# --no-cache because the gate phases the final stage depends on are RUN
|
||||
# steps, and a cached one is a check that did not run.
|
||||
set -eu
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
|
||||
@@ -8,7 +10,15 @@ ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)"
|
||||
|
||||
main() {
|
||||
cd "$ROOT"
|
||||
docker build -t "$("$SCRIPT_DIR/projectname")" .
|
||||
# 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"
|
||||
docker build --no-cache \
|
||||
--build-arg VERSION="$version" \
|
||||
-t "$("$SCRIPT_DIR/projectname")" .
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
+20
-1
@@ -4,10 +4,29 @@ set -eu
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||
|
||||
# Must match the pin in script/bootstrap.
|
||||
NODE_VERSION="22.17.0"
|
||||
|
||||
# script/bootstrap installs node and yarn under nvm and leaves neither
|
||||
# on the PATH of the shell that called it, so resolve the pinned
|
||||
# toolchain here the way bootstrap's own install step does. nvm is a
|
||||
# bash script, hence the subshell.
|
||||
run_yarn() {
|
||||
if command -v yarn >/dev/null 2>&1; then
|
||||
exec yarn "$@"
|
||||
fi
|
||||
if [ ! -s "$HOME/.nvm/nvm.sh" ]; then
|
||||
echo "fmt: no yarn; run script/bootstrap first" >&2
|
||||
exit 1
|
||||
fi
|
||||
exec bash -c '. "$HOME/.nvm/nvm.sh" && nvm use "$1" >/dev/null &&
|
||||
shift && exec yarn "$@"' bash "$NODE_VERSION" "$@"
|
||||
}
|
||||
|
||||
main() {
|
||||
cd "$ROOT"
|
||||
echo "Formatting..."
|
||||
yarn run fmt 2>&1
|
||||
run_yarn run fmt
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
+20
-1
@@ -5,10 +5,29 @@ set -eu
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||
|
||||
# Must match the pin in script/bootstrap.
|
||||
NODE_VERSION="22.17.0"
|
||||
|
||||
# script/bootstrap installs node and yarn under nvm and leaves neither
|
||||
# on the PATH of the shell that called it, so resolve the pinned
|
||||
# toolchain here the way bootstrap's own install step does. nvm is a
|
||||
# bash script, hence the subshell.
|
||||
run_yarn() {
|
||||
if command -v yarn >/dev/null 2>&1; then
|
||||
exec yarn "$@"
|
||||
fi
|
||||
if [ ! -s "$HOME/.nvm/nvm.sh" ]; then
|
||||
echo "fmt-check: no yarn; run script/bootstrap first" >&2
|
||||
exit 1
|
||||
fi
|
||||
exec bash -c '. "$HOME/.nvm/nvm.sh" && nvm use "$1" >/dev/null &&
|
||||
shift && exec yarn "$@"' bash "$NODE_VERSION" "$@"
|
||||
}
|
||||
|
||||
main() {
|
||||
cd "$ROOT"
|
||||
echo "Checking formatting..."
|
||||
yarn run fmt-check 2>&1
|
||||
run_yarn run fmt-check
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
+13
-41
@@ -1,51 +1,23 @@
|
||||
#!/bin/sh
|
||||
# script/lint: run the linter (eslint, then prettier --check).
|
||||
# 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.
|
||||
#
|
||||
# Linting is containerized. ESLint results depend on the ESLint version, and
|
||||
# the pinned one is the one in the image; a host's own install must not be
|
||||
# able to decide whether this repo is green. From a host this therefore builds
|
||||
# the Dockerfile's `lint` stage, which runs this same script inside the image.
|
||||
#
|
||||
# AUTISTMASK_LINT_NATIVE is set only in that image (see the Dockerfile) and is
|
||||
# what stops the recursion, so `make check` inside the CI build lints in place
|
||||
# instead of trying to reach a docker daemon it does not have.
|
||||
# The phase is not the last stage in the file, so it is built only when
|
||||
# --target names it. --no-cache because a cached lint layer is a lint
|
||||
# that did not run. The tag makes each build replace the previous image
|
||||
# instead of leaving a dangling one behind.
|
||||
set -eu
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
|
||||
ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)"
|
||||
|
||||
main() {
|
||||
cd "$ROOT"
|
||||
|
||||
case "${AUTISTMASK_LINT_NATIVE:-}" in
|
||||
1)
|
||||
echo "Linting..."
|
||||
yarn run lint 2>&1
|
||||
return 0
|
||||
;;
|
||||
"") ;;
|
||||
*)
|
||||
# Set but not recognized: say so rather than silently taking the
|
||||
# docker path, which would look like the variable had no effect.
|
||||
echo "lint: AUTISTMASK_LINT_NATIVE is set to" \
|
||||
"'${AUTISTMASK_LINT_NATIVE}'; the only recognized value is 1" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
echo "lint: docker is required; linting does not run on the host" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Linting in the pinned container..."
|
||||
# --progress=plain: the default progress renderer collapses the lint
|
||||
# output on success, and a lint run whose output cannot be seen is not
|
||||
# evidence that it ran.
|
||||
#
|
||||
# --output=type=cacheonly: the exit status is the whole result; exporting
|
||||
# an image afterwards costs about ten times the lint itself.
|
||||
docker build --progress=plain --target lint \
|
||||
--output=type=cacheonly . 2>&1
|
||||
docker build --no-cache \
|
||||
--target lint \
|
||||
-t "$("$SCRIPT_DIR/projectname")-lint" .
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
+10
-43
@@ -1,52 +1,19 @@
|
||||
#!/bin/sh
|
||||
# script/test: run the test suite.
|
||||
#
|
||||
# jest runs three worker processes (package.json), not one per CPU core: on a
|
||||
# many-core shared host one per core took gigabytes of RAM per run.
|
||||
#
|
||||
# The timeout bounds a hung suite; it is not a performance budget. On the busy
|
||||
# shared build host the suite takes 8-13s with three workers, inside
|
||||
# REPO_POLICIES' 20s budget. Inside the image the same suite also pays a cold
|
||||
# jest cache and shares the runner with the rest of the build, which is not what
|
||||
# that budget describes, so the Dockerfile raises the bound through
|
||||
# AUTISTMASK_TEST_TIMEOUT. A cap a healthy suite can trip on a cold cache
|
||||
# produces a red that means nothing, and teaches "just run it again".
|
||||
# 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, --no-cache because a cached test layer is a test that did not
|
||||
# run, and a tag so each build replaces the previous image.
|
||||
set -eu
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||
TIMEOUT="${AUTISTMASK_TEST_TIMEOUT:-30}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
|
||||
ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)"
|
||||
|
||||
main() {
|
||||
cd "$ROOT"
|
||||
echo "Running tests (timeout ${TIMEOUT}s)..."
|
||||
|
||||
status=0
|
||||
timeout "$TIMEOUT" yarn run test 2>&1 || status=$?
|
||||
[ "$status" -eq 0 ] && return 0
|
||||
|
||||
# 124 is timeout(1) killing the suite. Say so: a kill is not a failed
|
||||
# assertion, and the verbose rerun would only spend the same wall clock
|
||||
# to be killed again.
|
||||
if [ "$status" -eq 124 ]; then
|
||||
echo "tests: TIMED OUT after ${TIMEOUT}s (no assertion failed)" >&2
|
||||
echo "tests: raise AUTISTMASK_TEST_TIMEOUT if the suite is healthy" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 125 is timeout(1) itself failing, which here means AUTISTMASK_TEST_TIMEOUT
|
||||
# is not a duration it accepts. The suite never ran, so it neither timed out
|
||||
# nor failed, and the verbose rerun would only reprint the same complaint.
|
||||
if [ "$status" -eq 125 ]; then
|
||||
echo "tests: DID NOT RUN: timeout(1) rejected AUTISTMASK_TEST_TIMEOUT=\"${TIMEOUT}\"" >&2
|
||||
echo "tests: set it to a duration such as 30 or 180 (see timeout(1))" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "--- Rerunning with --verbose for details ---"
|
||||
timeout "$TIMEOUT" yarn run test:verbose 2>&1 || true
|
||||
# Always fail: the first run already proved the tests are broken, so a
|
||||
# flaky pass on the rerun must not turn the build green.
|
||||
exit 1
|
||||
docker build --no-cache \
|
||||
--target test \
|
||||
-t "$("$SCRIPT_DIR/projectname")-test" .
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
+5
-2
@@ -4,7 +4,7 @@
|
||||
# scripts-to-rule-them-all.
|
||||
#
|
||||
# Deliberately NOT called by script/check or script/test: REPO_POLICIES.md
|
||||
# caps make test at 20 seconds and a browser suite does not fit. Run it
|
||||
# caps make test at 60 seconds and a browser suite does not fit. Run it
|
||||
# yourself before touching popup views. ESLint's no-undef now catches a
|
||||
# used-but-not-imported identifier in make check, but only this suite sees
|
||||
# what a view actually does when it runs.
|
||||
@@ -45,7 +45,10 @@ main() {
|
||||
trap 'cleanup; exit 130' INT TERM
|
||||
|
||||
echo "Building the Chrome e2e image (extension included)..."
|
||||
docker build --iidfile "$IIDFILE" -t "$IMAGE" -f tests/e2e/Dockerfile .
|
||||
# --no-cache: the image build runs make build and its checks, and a
|
||||
# cached layer is a check that did not run.
|
||||
docker build --no-cache --iidfile "$IIDFILE" -t "$IMAGE" \
|
||||
-f tests/e2e/Dockerfile .
|
||||
|
||||
echo "Running e2e suite in the pinned Playwright container..."
|
||||
# The image is run by ID, not by tag: where two clones of this repo run
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# script/test-e2e. Our own extension to scripts-to-rule-them-all.
|
||||
#
|
||||
# Deliberately NOT called by script/check or script/test, for the same
|
||||
# reason as the Chrome suite: REPO_POLICIES.md caps make test at 20 seconds
|
||||
# reason as the Chrome suite: REPO_POLICIES.md caps make test at 60 seconds
|
||||
# and a browser suite does not fit. .gitea/workflows/e2e.yml also runs it
|
||||
# on every push, in a job separate from check.
|
||||
#
|
||||
@@ -44,7 +44,9 @@ main() {
|
||||
trap 'cleanup; exit 130' INT TERM
|
||||
|
||||
echo "Building the pinned Firefox e2e image (extension included)..."
|
||||
docker build --iidfile "$IIDFILE" -t "$IMAGE" \
|
||||
# --no-cache: the image build runs make build and its checks, and a
|
||||
# cached layer is a check that did not run.
|
||||
docker build --no-cache --iidfile "$IIDFILE" -t "$IMAGE" \
|
||||
-f tests/e2e/firefox/Dockerfile .
|
||||
|
||||
echo "Running the Firefox e2e suite..."
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
//
|
||||
// The browser half of the same claim — that a real Chrome renders that
|
||||
// string as text and puts no iframe in the popup DOM — is in
|
||||
// tests/e2e/run.js. This half runs inside the 20-second make test cap.
|
||||
// tests/e2e/run.js. This half runs inside the 60-second make test cap.
|
||||
|
||||
"use strict";
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ describe("the flash line the message is shown in", () => {
|
||||
// "a rejected dust threshold shifts no layout (#233)" and "an over-long
|
||||
// flash message keeps to one line (#252)" in tests/e2e/run.js, run by
|
||||
// make test-e2e. They are not in make check because REPO_POLICIES.md
|
||||
// caps make test at 20 seconds and a browser suite does not fit.
|
||||
// caps make test at 60 seconds and a browser suite does not fit.
|
||||
test("reserves its height in the markup", () => {
|
||||
const flashLine = POPUP_HTML.match(
|
||||
/<div\s+id="flash-msg"\s+class="([^"]*)"/,
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
// node tests/e2e/firefox/run.js [dist/firefox]
|
||||
//
|
||||
// Deliberately not part of script/check, and deliberately not named
|
||||
// *.test.js: REPO_POLICIES.md caps make test at 20 seconds and a browser
|
||||
// *.test.js: REPO_POLICIES.md caps make test at 60 seconds and a browser
|
||||
// suite does not fit.
|
||||
//
|
||||
// This shares no driver layer with the Chrome suite in tests/e2e/, and the
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
//
|
||||
// This runs inside the pinned Playwright container; see script/test-e2e.
|
||||
// It is deliberately NOT part of make check — REPO_POLICIES.md caps
|
||||
// make test at 20 seconds and a browser suite does not fit.
|
||||
// make test at 60 seconds and a browser suite does not fit.
|
||||
|
||||
"use strict";
|
||||
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
//
|
||||
// A plain runner rather than jest on purpose: jest's default testMatch
|
||||
// would pull these files into script/test, and browser tests do not fit
|
||||
// inside the 20-second cap REPO_POLICIES.md puts on make test. Nothing
|
||||
// inside the 60-second cap REPO_POLICIES.md puts on make test. Nothing
|
||||
// here is named *.test.js for the same reason.
|
||||
|
||||
"use strict";
|
||||
|
||||
+2
-2
@@ -12,8 +12,8 @@
|
||||
// interactive parameters, which the module hardcodes. The parameters are not
|
||||
// weakened or overridden anywhere in this file — they are pinned by the "key
|
||||
// derivation cost" tests, since they are the vault's only defence against an
|
||||
// offline attack on a stolen blob. The suite is kept inside script/test's
|
||||
// 30-second budget by sharing one encrypted fixture across the tamper cases
|
||||
// offline attack on a stolen blob. The suite is kept inside the 60-second
|
||||
// make test cap by sharing one encrypted fixture across the tamper cases
|
||||
// instead of re-encrypting per test.
|
||||
|
||||
const sodium = require("libsodium-wrappers-sumo");
|
||||
|
||||
Reference in New Issue
Block a user