3 Commits
Author SHA1 Message Date
clawbot 9389bf0172 Commit yarn 4 in the tree and install with --immutable (closes #149)
check / check (push) Successful in 16s
Follows sneak's ruling at
#149 (comment). This
repository now carries yarn 4.18.1 under .yarn/releases. The
distribution's yarn only starts that file, and that file does the work.
yarn.lock is converted once, and script/bootstrap installs with
--immutable instead of stopping on yarn's major version. Node is the
distribution's newest Node package: nodejs-current on Alpine and
nodejs_latest on nix. Every Dockerfile stage that needs yarn also copies
.yarnrc.yml and .yarn/releases/. The policies and both checklists say
so, with the steps for converting and for a new repository.

Model: opus-5-5
2026-11-04 21:32:55 +01:00
clawbot 3066aae835 Allow go test on one package while iterating (closes #51)
check / check (push) Canceled after 0s
Follows sneak's ruling at
#51 (comment). An agent
whose work is confined to one package may run `go test` directly on that
package's suite, or on individual tests in it, while iterating. `go build`,
`go vet` and the linter still go through make targets. The full `make check`
is still required before every push. The `Makefile` gains nothing.

The rule on Docker now says the lint and test gates run there, because a run
while iterating is not a gate.

Model: opus-5-5
2026-11-04 19:32:37 +01:00
clawbot 86c7617f86 Install node, yarn and python from the distribution (closes #146)
check / check (push) Canceled after 0s
Follows sneak's ruling at
#146 (comment). Runtimes
and the installer tools bundled with python come from the distribution,
with no version and no hash. Third-party packages stay pinned by hash.

script/bootstrap drops nvm and corepack. It installs node, yarn and the
node packages only when yarn.lock is in the tree, and stops there when
yarn's major version is not 1. This repository's Dockerfile starts from
Alpine 3.24. The Python example starts from debian:trixie-slim, keeps
pipenv at $HOME/.pipenv on a host and in an image, and sets
PIP_ONLY_BINARY=:all:, so pip downloads no build tools without a hash
check.

Model: opus-5-5
2026-11-04 09:55:26 +01:00
15 changed files with 1225 additions and 83 deletions
+6
View File
@@ -35,6 +35,12 @@
# `services/api/.claude/` and must add its own anchored entry. # `services/api/.claude/` and must add its own anchored entry.
.claude .claude
# yarn's install state and cache stay out, and its release file stays
# in. Docker obeys the last matching line, so these two lines sit above
# the secret patterns, which must win under .yarn/releases/.
.yarn/*
!.yarn/releases
# Environment files. `*.env` covers bare `.env` and the `prod.env` # Environment files. `*.env` covers bare `.env` and the `prod.env`
# convention. Re-include a committed template with a negation if the # convention. Re-include a committed template with a negation if the
# build needs one: `!docs/example.env`. # build needs one: `!docs/example.env`.
+3
View File
@@ -2,3 +2,6 @@
# both sides instead of conflicting. Git never reports a conflict here: read # both sides instead of conflicting. Git never reports a conflict here: read
# the merged entries after every merge or rebase. # the merged entries after every merge or rebase.
TODO.md merge=union TODO.md merge=union
# yarn's release file is one opaque file in diffs and merges.
.yarn/releases/** binary
+5 -3
View File
@@ -19,10 +19,12 @@ Thumbs.db
# Node # Node
node_modules/ node_modules/
.yarn/*
!.yarn/releases
# Secrets. Unanchored like every entry above, so each matches at every # Secrets. Unanchored, so each matches at every depth. Matching is
# depth. Matching is case-sensitive on Linux, so names use character # case-sensitive on Linux, so names use character ranges rather than a
# ranges rather than a lowercase form that misses `Server.Key`. # lowercase form that misses `Server.Key`.
# Environment files. `*.env` covers bare `.env` and the `prod.env` # Environment files. `*.env` covers bare `.env` and the `prod.env`
# convention. Only the templates `example.env` and `sample.env` are # convention. Only the templates `example.env` and `sample.env` are
+1
View File
@@ -1,2 +1,3 @@
node_modules/ node_modules/
.yarn/
yarn.lock yarn.lock
+1000
View File
File diff suppressed because one or more lines are too long
+5
View File
@@ -0,0 +1,5 @@
enableTelemetry: false
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.18.1.cjs
+12 -7
View File
@@ -6,17 +6,20 @@
# system packages. script/ passes a new value on every build. That value # system packages. script/ passes a new value on every build. That value
# reruns every RUN step below the ARG line, and the install step keeps # reruns every RUN step below the ARG line, and the install step keeps
# its layer. node and yarn come from the distribution, which is trusted, # its layer. node and yarn come from the distribution, which is trusted,
# so they need no version and no hash. # so they need no version and no hash. That yarn only starts the release
# file in .yarn/releases/, so every stage copies that file and
# .yarnrc.yml before it runs script/bootstrap.
# #
# alpine 3.24.3, 2026-11-04 # alpine 3.24.3, 2026-11-04
FROM alpine@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a AS lint FROM alpine@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a AS lint
RUN apk add --no-cache git make nodejs yarn RUN apk add --no-cache git make nodejs-current yarn
ARG CACHEBUST ARG CACHEBUST
WORKDIR /app WORKDIR /app
COPY script/ script/ COPY script/ script/
COPY package.json yarn.lock ./ COPY package.json yarn.lock .yarnrc.yml ./
COPY .yarn/releases/ .yarn/releases/
RUN script/bootstrap RUN script/bootstrap
COPY . . COPY . .
@@ -27,13 +30,14 @@ RUN yarn run prettier --check '**/*.md' --tab-width 4 --prose-wrap always
# #
# alpine 3.24.3, 2026-11-04 # alpine 3.24.3, 2026-11-04
FROM alpine@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a AS test FROM alpine@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a AS test
RUN apk add --no-cache git make nodejs yarn RUN apk add --no-cache git make nodejs-current yarn
ARG CACHEBUST ARG CACHEBUST
WORKDIR /app WORKDIR /app
COPY script/ script/ COPY script/ script/
COPY package.json yarn.lock ./ COPY package.json yarn.lock .yarnrc.yml ./
COPY .yarn/releases/ .yarn/releases/
RUN script/bootstrap RUN script/bootstrap
COPY . . COPY . .
@@ -48,7 +52,7 @@ RUN echo "No tests defined."
# #
# alpine 3.24.3, 2026-11-04 # alpine 3.24.3, 2026-11-04
FROM alpine@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a FROM alpine@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a
RUN apk add --no-cache git make nodejs yarn RUN apk add --no-cache git make nodejs-current yarn
ARG CACHEBUST ARG CACHEBUST
WORKDIR /app WORKDIR /app
@@ -59,7 +63,8 @@ COPY --from=test /app/package.json /dev/null
# script/bootstrap finds the system packages above and installs the node # script/bootstrap finds the system packages above and installs the node
# packages. # packages.
COPY script/ script/ COPY script/ script/
COPY package.json yarn.lock ./ COPY package.json yarn.lock .yarnrc.yml ./
COPY .yarn/releases/ .yarn/releases/
RUN script/bootstrap RUN script/bootstrap
COPY . . COPY . .
+1 -1
View File
@@ -111,7 +111,7 @@ development workflow, and the Makefile targets are thin shims that call them.
The scripts are POSIX sh (not bash) so they run in minimal containers such as The scripts are POSIX sh (not bash) so they run in minimal containers such as
alpine. We provide: alpine. We provide:
- `script/bootstrap` — install all dependencies (yarn install) - `script/bootstrap` — install all dependencies (`yarn install --immutable`)
- `script/setup` — set up the repo for development after a fresh clone: runs - `script/setup` — set up the repo for development after a fresh clone: runs
`script/bootstrap`, then `script/install-precommit` `script/bootstrap`, then `script/install-precommit`
- `script/projectname` — output the project name (our own extension); used by - `script/projectname` — output the project name (our own extension); used by
+22
View File
@@ -21,6 +21,28 @@ fmt-check, and commit.
# Completed Steps # Completed Steps
- 2026-11-04: Each repository now commits yarn 4 and installs with
`yarn install --immutable` (https://git.eeqj.de/sneak/prompts/issues/149), on
sneak's ruling at
https://git.eeqj.de/sneak/prompts/issues/149#issuecomment-167558. Product
repositories convert when they next have work. These changed:
- This repository carries `.yarn/releases/yarn-4.18.1.cjs`, and its
`yarn.lock` is in yarn 4's format.
- `script/bootstrap` no longer stops on yarn's major version.
- Node comes from the distribution's newest Node package, which on Alpine
3.24 is `nodejs-current`.
- A `Dockerfile` stage that needs yarn also copies `.yarnrc.yml` and
`.yarn/releases/`, and may start from any distribution that packages node
and yarn.
- Both checklists give the steps, and the ignore files keep yarn's install
state out of git and the build context.
- 2026-11-04: An agent whose work is confined to one package may run `go test`
directly on that package's suite, or on individual tests in it, while
iterating (https://git.eeqj.de/sneak/prompts/issues/51), on sneak's ruling at
https://git.eeqj.de/sneak/prompts/issues/51#issuecomment-167600. The full
`make check` is still required before every push, and the `Makefile` gains
nothing. The rule on Docker now names the lint and test gates, because a run
while iterating is not one.
- 2026-11-04: node, yarn, npm, python, pip and venv now come from the - 2026-11-04: node, yarn, npm, python, pip and venv now come from the
distribution with no version and no hash distribution with no version and no hash
(https://git.eeqj.de/sneak/prompts/issues/146), on sneak's ruling at (https://git.eeqj.de/sneak/prompts/issues/146), on sneak's ruling at
+2 -1
View File
@@ -2,5 +2,6 @@
"private": true, "private": true,
"devDependencies": { "devDependencies": {
"prettier": "3.8.1" "prettier": "3.8.1"
} },
"packageManager": "yarn@4.18.1"
} }
+45 -11
View File
@@ -164,12 +164,39 @@ with your task.
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.golangci.yml` and, `https://git.eeqj.de/sneak/prompts/raw/branch/main/.golangci.yml` and,
in the same commit, set the lint phase digest to the one named in the in the same commit, set the lint phase digest to the one named in the
`.golangci.yml` paragraph of `REPO_POLICIES.md`) `.golangci.yml` paragraph of `REPO_POLICIES.md`)
- [ ] JS: `package.json`, `yarn.lock`, `.prettierrc`, `.prettierignore` - [ ] JS: `package.json`, `yarn.lock`, `.yarnrc.yml`, `.yarn/releases/`,
(fetch from `.gitattributes`, `.prettierrc`, `.prettierignore` (fetch the last two
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.prettierrc` and from `https://git.eeqj.de/sneak/prompts/raw/branch/main/.prettierrc`
and
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.prettierignore`). `https://git.eeqj.de/sneak/prompts/raw/branch/main/.prettierignore`).
Make the first `yarn.lock` with yarn 1, from a distribution that - [ ] Any repository with `yarn.lock`: that file is in yarn 4's format. If
packages it, such as Alpine (`apk add nodejs yarn`). its second line says `yarn lockfile v1`, convert it with the steps
below. Do them in one commit with the re-vendored scripts, because
yarn 1 ignores `--immutable` and exits 0 on a tree with no
`.yarnrc.yml`. Run them with any distribution's node and yarn, and use
`yarnpkg` on Debian and Ubuntu.
1. Read the version from `yarnPath` in
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.yarnrc.yml`.
2. Run `yarn set version <version>`. It writes
`.yarn/releases/yarn-<version>.cjs`, `yarnPath` in `.yarnrc.yml`, and
`packageManager` in `package.json`.
3. Add `nodeLinker: node-modules` and `enableTelemetry: false` to
`.yarnrc.yml`.
4. Run `yarn install` once. It converts `yarn.lock`, and each package
keeps its version and gains a `checksum`.
5. Delete `approvedGitRepositories`, `enableScripts` and
`npmMinimalAgeGate` from `.yarnrc.yml`. The conversion wrote them.
They allow install scripts, any git repository and packages of any
age, which yarn otherwise refuses.
6. In `package.json`, name any package that must run its install script,
as `"dependenciesMeta": { "<name>": { "built": true } }`, and run
`yarn install` again.
7. Add `.yarn/*` and `!.yarn/releases` above the secret patterns in
`.gitignore` and `.dockerignore`. Docker obeys the last matching
line, and the secret patterns must win. Add `.yarn/` to
`.prettierignore`, and `.yarn/releases/** binary` to
`.gitattributes`.
8. Run `yarn install --immutable`. It must pass and change nothing.
- [ ] Python: `pyproject.toml` - [ ] Python: `pyproject.toml`
- [ ] Docs/writing: `.prettierrc`, `.prettierignore` (same URLs as above) - [ ] Docs/writing: `.prettierrc`, `.prettierignore` (same URLs as above)
@@ -206,11 +233,14 @@ with your task.
installed. installed.
- [ ] `script/bootstrap` installs node, yarn and the node packages only when - [ ] `script/bootstrap` installs node, yarn and the node packages only when
`yarn.lock` is in the tree. node and yarn come from the distribution when `yarn.lock` is in the tree. node and yarn come from the distribution when
they are missing. With `yarn.lock` in the tree, `script/bootstrap` stops they are missing. It installs the node packages with
with a one-line message when yarn's major version is not 1. `yarn install --immutable`. The distribution's yarn only starts the
- [ ] A `Dockerfile` stage copies `yarn.lock` before it runs `script/bootstrap` release file that `yarnPath` in `.yarnrc.yml` names, and that file does
only if that stage needs yarn. A stage that needs yarn starts from an the work.
image whose distribution packages yarn 1, such as Alpine. - [ ] A `Dockerfile` stage that needs yarn may start from any distribution that
packages node and yarn. It copies `package.json`, `yarn.lock`,
`.yarnrc.yml` and `.yarn/releases/` before it runs `script/bootstrap`. A
stage that needs no yarn copies none of them.
- [ ] `script/bootstrap`, `script/fmt` and `script/fmt-check` run yarn by - [ ] `script/bootstrap`, `script/fmt` and `script/fmt-check` run yarn by
whichever name is present: `yarn`, or `yarnpkg` on Debian and Ubuntu. With whichever name is present: `yarn`, or `yarnpkg` on Debian and Ubuntu. With
neither name on `PATH`, `script/fmt` and `script/fmt-check` print one line neither name on `PATH`, `script/fmt` and `script/fmt-check` print one line
@@ -247,7 +277,11 @@ with your task.
this exception this exception
- [ ] node, yarn, npm, python, pip and venv come from the distribution wherever - [ ] node, yarn, npm, python, pip and venv come from the distribution wherever
possible, never from nvm, pyenv, corepack or `npm install -g yarn`. If the possible, never from nvm, pyenv, corepack or `npm install -g yarn`. If the
distribution's version is too old, use a newer distribution distribution's version is too old, use a newer distribution. node means
the distribution's newest Node package, such as `nodejs-current` on Alpine
3.24
- [ ] yarn's release file is in git under `.yarn/releases/`, written once by
`yarn set version` with an exact version (repositories with `yarn.lock`)
- [ ] Using `yarn`, not `npm` (JS projects) - [ ] Using `yarn`, not `npm` (JS projects)
# Directory Structure # Directory Structure
+28 -9
View File
@@ -179,9 +179,22 @@ Template files can be fetched from:
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.golangci.yml` and, `https://git.eeqj.de/sneak/prompts/raw/branch/main/.golangci.yml` and,
in the same commit, set the lint phase digest to the one named in the in the same commit, set the lint phase digest to the one named in the
`.golangci.yml` paragraph of `REPO_POLICIES.md`) `.golangci.yml` paragraph of `REPO_POLICIES.md`)
- [ ] JS: `yarn init`, `yarn add --dev prettier`. `script/bootstrap` - [ ] JS, or any repository that runs prettier through yarn: make the first
installs no yarn until `yarn.lock` exists, so use yarn 1 from a `yarn.lock` with the steps below. `script/bootstrap` installs no yarn
distribution that packages it, such as Alpine (`apk add nodejs yarn`). until `yarn.lock` exists, so run them with any distribution's node and
yarn, such as Alpine's (`apk add nodejs-current yarn`). Use `yarnpkg`
on Debian and Ubuntu.
1. Read the version from `yarnPath` in
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.yarnrc.yml`.
2. Run `yarn set version <version>`. It writes
`.yarn/releases/yarn-<version>.cjs`, `.yarnrc.yml` and
`package.json`.
3. Add `nodeLinker: node-modules` and `enableTelemetry: false` to
`.yarnrc.yml`.
4. Add `"private": true` to `package.json`.
5. Run `yarn add --dev prettier`. It writes `yarn.lock`.
6. Add `.yarn/releases/** binary` to `.gitattributes`.
7. Run `yarn install --immutable`. It must pass and change nothing.
- [ ] Python: `pyproject.toml` - [ ] Python: `pyproject.toml`
## Configure script/ Entrypoints and Makefile ## Configure script/ Entrypoints and Makefile
@@ -198,15 +211,17 @@ are thin shims calling them. Model scripts:
- It installs node, yarn and the node packages only when `yarn.lock` is in - It installs node, yarn and the node packages only when `yarn.lock` is in
the tree. node and yarn come from that package manager when they are the tree. node and yarn come from that package manager when they are
missing. missing.
- With `yarn.lock` in the tree, it stops with a one-line message when yarn's - It installs the node packages with `yarn install --immutable`. The
major version is not 1. distribution's yarn only starts the release file that `yarnPath` in
`.yarnrc.yml` names, and that file does the work.
- A non-server repo's development environment stage runs it, with only - A non-server repo's development environment stage runs it, with only
system packages installed inline above it. system packages installed inline above it.
- A gate phase or the build stage installs what its base image lacks, either - A gate phase or the build stage installs what its base image lacks, either
inline or by running it. inline or by running it.
- [ ] A `Dockerfile` stage copies `yarn.lock` before it runs `script/bootstrap` - [ ] A `Dockerfile` stage that needs yarn may start from any distribution that
only if that stage needs yarn. A stage that needs yarn starts from an packages node and yarn. It copies `package.json`, `yarn.lock`,
image whose distribution packages yarn 1, such as Alpine. `.yarnrc.yml` and `.yarn/releases/` before it runs `script/bootstrap`. A
stage that needs no yarn copies none of them.
- [ ] `script/setup` / `make setup` — readies a fresh clone: runs `bootstrap`, - [ ] `script/setup` / `make setup` — readies a fresh clone: runs `bootstrap`,
then `install-precommit`, plus repo-specific init then `install-precommit`, plus repo-specific init
- [ ] `script/test` / `make test` — - [ ] `script/test` / `make test` —
@@ -278,7 +293,11 @@ are thin shims calling them. Model scripts:
that a `Dockerfile` adds itself does not get this exception that a `Dockerfile` adds itself does not get this exception
- [ ] node, yarn, npm, python, pip and venv come from the distribution wherever - [ ] node, yarn, npm, python, pip and venv come from the distribution wherever
possible, never from nvm, pyenv, corepack or `npm install -g yarn`. If the possible, never from nvm, pyenv, corepack or `npm install -g yarn`. If the
distribution's version is too old, use a newer distribution distribution's version is too old, use a newer distribution. node means
the distribution's newest Node package, such as `nodejs-current` on Alpine
3.24
- [ ] yarn's release file is in git under `.yarn/releases/`, written once by
`yarn set version` with an exact version (repositories with `yarn.lock`)
- [ ] No unnecessary files in repo root - [ ] No unnecessary files in repo root
- [ ] All dates written as YYYY-MM-DD - [ ] All dates written as YYYY-MM-DD
+68 -32
View File
@@ -35,7 +35,7 @@ style conventions are in separate documents:
- Docker: `@sha256:...` - Docker: `@sha256:...`
- Go: the module hash in `go.sum` - Go: the module hash in `go.sum`
- Python: the package hash in `Pipfile.lock` - Python: the package hash in `Pipfile.lock`
- npm: the integrity hash in the lockfile - npm: the `checksum` in `yarn.lock`
- GitHub Actions: `@<commit-sha>` - GitHub Actions: `@<commit-sha>`
This also means never `curl | bash` to install a tool such as rustup. This also means never `curl | bash` to install a tool such as rustup.
@@ -56,7 +56,8 @@ style conventions are in separate documents:
- `apk add` - `apk add`
These come from the distribution wherever possible: These come from the distribution wherever possible:
- node - node, meaning the distribution's newest Node package, such as
`nodejs-current` on Alpine 3.24
- yarn - yarn
- npm - npm
- python - python
@@ -69,6 +70,9 @@ style conventions are in separate documents:
- corepack - corepack
- `npm install -g yarn` - `npm install -g yarn`
yarn's release file is third-party code, and git pins it. `yarn set version`
writes it to `.yarn/releases/` once, given an exact version.
These are not trusted, so everything from them stays pinned by hash: These are not trusted, so everything from them stays pinned by hash:
- Docker Hub - Docker Hub
- PyPI - PyPI
@@ -123,9 +127,32 @@ style conventions are in separate documents:
`script/bootstrap` installs node, yarn and the node packages only when `script/bootstrap` installs node, yarn and the node packages only when
`yarn.lock` is in the tree. With no `yarn.lock`, it installs no node and no `yarn.lock` is in the tree. With no `yarn.lock`, it installs no node and no
yarn, and it does not check yarn's version. Make a repository's first yarn. It installs the node packages with `yarn install --immutable`. That
`yarn.lock` with yarn 1, from a distribution that packages it, such as install fails when a package's `checksum` is wrong, or when `package.json`
Alpine (`apk add nodejs yarn`). is out of step with `yarn.lock`.
Each repository commits the yarn that runs. `yarnPath` in `.yarnrc.yml`
points at the release file in `.yarn/releases/`. The distribution's yarn
only starts that file, and that file does the work. Alpine packages yarn 1,
and Debian 13 packages yarn 4.1. The same committed yarn does the work on
both. Corepack never runs, and no yarn is downloaded.
`.yarnrc.yml` holds these settings:
- `yarnPath`
- `nodeLinker: node-modules`
- `enableTelemetry: false`, because yarn otherwise sends usage reports from
any host that is not CI
By default, yarn runs no dependency's install script. A dependency that must
run one is named in `package.json`, as
`"dependenciesMeta": { "<name>": { "built": true } }`.
`.gitattributes` carries `.yarn/releases/** binary`, so diffs and merges
treat the release file as one opaque file.
yarn 1 ignores `--immutable` and exits 0, so on a tree with no `.yarnrc.yml`
the install is not frozen. A repository therefore re-vendors
`script/bootstrap` and converts its `yarn.lock` in one commit.
Debian and Ubuntu name the yarn command `yarnpkg`. `script/bootstrap`, Debian and Ubuntu name the yarn command `yarnpkg`. `script/bootstrap`,
`script/fmt` and `script/fmt-check` each carry the same small function that `script/fmt` and `script/fmt-check` each carry the same small function that
@@ -133,18 +160,18 @@ style conventions are in separate documents:
and `script/fmt-check` print one line saying to run `script/bootstrap`, and and `script/fmt-check` print one line saying to run `script/bootstrap`, and
exit 1. exit 1.
With `yarn.lock` in the tree, `script/bootstrap` stops with a one-line A `Dockerfile` stage that needs yarn may start from any distribution that
message when yarn's major version is not 1, before it installs any node packages node and yarn. It copies all of these before it runs
packages. Debian 13 and Ubuntu 26.04 package yarn 4. With `script/bootstrap`:
`--frozen-lockfile`, yarn 4 refuses a yarn 1 `yarn.lock`. Without that flag, - `package.json`
it rewrites the file in its own format, with yarn 4 hashes in place of the - `yarn.lock`
yarn 1 ones. A host whose distribution packages yarn 4 moves to one that - `.yarnrc.yml`
packages yarn 1, such as Alpine or Ubuntu 24.04. - `.yarn/releases/`
A `Dockerfile` stage copies `yarn.lock` before it runs `script/bootstrap` With only `package.json` and `yarn.lock`, the install fails. Alpine's yarn 1
only if that stage needs yarn. A stage that needs yarn starts from an image then says to enable corepack; never do that, and copy the missing files
whose distribution packages yarn 1, such as Alpine. The Python and Go stages instead. The Python and Go stages below need no yarn and copy no
below need no yarn and copy no `yarn.lock`, so they never reach the stop. `yarn.lock`, so `script/bootstrap` installs no node and no yarn there.
`script/cibuild` runs the CI build, and the Gitea workflow calls it. The `script/cibuild` runs the CI build, and the Gitea workflow calls it. The
script changes to the repo root, runs `script/bootstrap`, runs script changes to the repo root, runs `script/bootstrap`, runs
@@ -176,6 +203,12 @@ style conventions are in separate documents:
instead of invoking the underlying tools directly. The Makefile is the single instead of invoking the underlying tools directly. The Makefile is the single
source of truth for how these operations are run. source of truth for how these operations are run.
An agent whose work is confined to one package may run `go test` directly on
that package's suite, or on individual tests in it, while iterating. This is
the only exception to this rule. `go build`, `go vet` and the linter still
go through make targets. A run while iterating is not a gate, so the full
`make check` is still required before every push.
- The Makefile is authoritative documentation for how the repo is used. Beyond - The Makefile is authoritative documentation for how the repo is used. Beyond
the required targets above, it should have targets for every common operation: the required targets above, it should have targets for every common operation:
- running a local development server (`make run`, `make dev`) - running a local development server (`make run`, `make dev`)
@@ -204,12 +237,13 @@ style conventions are in separate documents:
running `script/bootstrap`. Only system packages may be installed inline running `script/bootstrap`. Only system packages may be installed inline
above it, so that check builds keep them; the `prompts` repo's own above it, so that check builds keep them; the `prompts` repo's own
`Dockerfile` does this. A stage that runs `script/bootstrap` COPYs `script/` `Dockerfile` does this. A stage that runs `script/bootstrap` COPYs `script/`
and the dependency manifests it needs (`package.json` + `yarn.lock`, and the dependency manifests it needs before running it. These are examples:
`go.mod` + `go.sum`, etc.) before running it. - `package.json`, `yarn.lock`, `.yarnrc.yml` and `.yarn/releases/`
- `go.mod` and `go.sum`
- **Linting and testing run in Docker, as phases of the `Dockerfile`.** There is - **The lint and test gates run in Docker, as phases of the `Dockerfile`.**
no separate lint file. `script/lint` and `script/test` each build one phase There is no separate lint file. `script/lint` and `script/test` each build one
and nothing else: phase and nothing else:
```sh ```sh
docker build --build-arg CACHEBUST="$(date +%s)-$$" \ docker build --build-arg CACHEBUST="$(date +%s)-$$" \
@@ -765,6 +799,7 @@ style conventions are in separate documents:
- editor files (`.swp`, `*~`) - editor files (`.swp`, `*~`)
- in-repo agent scratch directories (`.claude/`) - in-repo agent scratch directories (`.claude/`)
- `node_modules/` - `node_modules/`
- `.yarn/*`, with `!.yarn/releases` so that git keeps yarn's release file
- the repo's own build outputs - the repo's own build outputs
Fetch the standard `.gitignore` from Fetch the standard `.gitignore` from
@@ -786,16 +821,16 @@ style conventions are in separate documents:
Such a `.dockerignore` is more dangerous than a short file with no secret Such a `.dockerignore` is more dangerous than a short file with no secret
patterns at all, because it reads as solved and stops anyone looking. Give patterns at all, because it reads as solved and stops anyone looking. Give
every depth-independent pattern the `**/` prefix, and leave only genuinely every depth-independent pattern the `**/` prefix, and leave only genuinely
root-anchored entries unprefixed. Those are `.claude`, and the repo's own root-anchored entries unprefixed. Those are `.claude`, `.yarn/*`,
host-built binary. The binary is written `/myapp` and never `**/myapp`, which `!.yarn/releases`, and the repo's own host-built binary. The binary is written
would also match `cmd/myapp/` and delete the package directory from the `/myapp` and never `**/myapp`, which would also match `cmd/myapp/` and delete
context. Matching is case-sensitive, and an all-caps copy of each pattern the package directory from the context. Matching is case-sensitive, and an
still misses `Server.Key`. So secret names use character ranges: all-caps copy of each pattern still misses `Server.Key`. So secret names use
`**/*.[kK][eE][yY]`, `**/*.[pP][eE][mM]`, and likewise for `.envrc` and the character ranges: `**/*.[kK][eE][yY]`, `**/*.[pP][eE][mM]`, and likewise for
extensionless SSH keys. Where such a pattern also catches something the build `.envrc` and the extensionless SSH keys. Where such a pattern also catches
needs, re-include it with a negation (`!docs/example.env`). Deleting the something the build needs, re-include it with a negation
pattern reopens the exposure for every other file it covers. Fetch the (`!docs/example.env`). Deleting the pattern reopens the exposure for every
standard `.dockerignore` from other file it covers. Fetch the standard `.dockerignore` from
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.dockerignore` and extend `https://git.eeqj.de/sneak/prompts/raw/branch/main/.dockerignore` and extend
it with the repo's own artifacts. it with the repo's own artifacts.
@@ -1099,7 +1134,8 @@ style conventions are in separate documents:
- `Dockerfile`, `.dockerignore` - `Dockerfile`, `.dockerignore`
- `.gitea/workflows/check.yml` - `.gitea/workflows/check.yml`
- Go: `go.mod`, `go.sum`, `.golangci.yml` - Go: `go.mod`, `go.sum`, `.golangci.yml`
- JS: `package.json`, `yarn.lock`, `.prettierrc`, `.prettierignore` - JS: `package.json`, `yarn.lock`, `.yarnrc.yml`, `.yarn/releases/`,
`.gitattributes`, `.prettierrc`, `.prettierignore`
- Python: `pyproject.toml` - Python: `pyproject.toml`
- Guidance for coding agents lives in one `AGENTS.md` at the repository root. It - Guidance for coding agents lives in one `AGENTS.md` at the repository root. It
+6 -13
View File
@@ -4,8 +4,10 @@
# installed tools are skipped. Base tools, node and yarn come from nix, # installed tools are skipped. Base tools, node and yarn come from nix,
# apt, brew, or apk (detected in that order), with no version and no # apt, brew, or apk (detected in that order), with no version and no
# hash, because the distribution is trusted. It installs node, yarn and # hash, because the distribution is trusted. It installs node, yarn and
# the node packages only when yarn.lock is in the tree. Assumes nothing # the node packages only when yarn.lock is in the tree. The
# is present. # distribution's yarn only starts the release file that yarnPath in
# .yarnrc.yml names, and that file does the work. So git pins the yarn
# that runs. Assumes nothing is present.
set -eu set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
@@ -78,21 +80,12 @@ main() {
# A Dockerfile stage that needs no yarn copies no yarn.lock. # A Dockerfile stage that needs no yarn copies no yarn.lock.
if [ -f yarn.lock ]; then if [ -f yarn.lock ]; then
if missing node; then pkg_install nodejs nodejs node nodejs; fi if missing node; then pkg_install nodejs_latest nodejs node nodejs-current; fi
if missing yarn && missing yarnpkg; then if missing yarn && missing yarnpkg; then
pkg_install yarn yarnpkg yarn yarn pkg_install yarn yarnpkg yarn yarn
fi fi
# Debian 13 and Ubuntu 26.04 package yarn 4, which refuses a yarn 1 run_yarn install --immutable
# yarn.lock, or rewrites it in its own format.
yarn_version="$(run_yarn --version)"
if [ "${yarn_version%%.*}" != "1" ]; then
echo "bootstrap: yarn $yarn_version refuses this yarn 1 yarn.lock;" \
"use a distribution that packages yarn 1" >&2
exit 1
fi
run_yarn install --frozen-lockfile
fi fi
echo "bootstrap complete" echo "bootstrap complete"
+21 -6
View File
@@ -1,8 +1,23 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. # This file is generated by running "yarn install" inside your project.
# yarn lockfile v1 # Manual changes might be lost - proceed with caution!
__metadata:
version: 10
cacheKey: 10c0
prettier@3.8.1: "prettier@npm:3.8.1":
version "3.8.1" version: 3.8.1
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.1.tgz#edf48977cf991558f4fcbd8a3ba6015ba2a3a173" resolution: "prettier@npm:3.8.1"
integrity sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg== bin:
prettier: bin/prettier.cjs
checksum: 10c0/33169b594009e48f570471271be7eac7cdcf88a209eed39ac3b8d6d78984039bfa9132f82b7e6ba3b06711f3bfe0222a62a1bfb87c43f50c25a83df1b78a2c42
languageName: node
linkType: hard
"root-workspace-0b6124@workspace:.":
version: 0.0.0-use.local
resolution: "root-workspace-0b6124@workspace:."
dependencies:
prettier: "npm:3.8.1"
languageName: unknown
linkType: soft