Install the pinned node when the installed one is another major version (closes #118)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
script/bootstrap used whatever node was installed, so on the CI runner image, which ships node 24, the pinned yarn 1.22.22 ran under node 24 and printed the deprecation warning DEP0169 on every bootstrap. It now uses the installed node only when its major version is the pinned one, and otherwise installs the pinned node with nvm, as it already did when node was missing, with yarn and the packages under it. Only the major is compared because the Dockerfile stages use a node 22 alpine image that nvm cannot replace. script/fmt and script/fmt-check run yarn under nvm's pinned node when nvm has it installed, and otherwise the yarn on PATH. Model: opus-5-5
This commit is contained in:
@@ -21,6 +21,17 @@ fmt-check, and commit.
|
|||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
|
- 2026-10-08: The canonical `script/bootstrap` now uses the installed node only
|
||||||
|
when its major version is the pinned one (issue 118). Otherwise, as when node
|
||||||
|
is missing, it installs the pinned node under nvm and installs yarn and the
|
||||||
|
packages under it. The CI runner image ships node 24, under which the pinned
|
||||||
|
yarn 1.22.22 printed the deprecation warning DEP0169 on every bootstrap. Only
|
||||||
|
the major is compared because the `Dockerfile` stages start from a node 22
|
||||||
|
alpine image whose exact version is not the pin, and nvm cannot install a
|
||||||
|
prebuilt node on alpine. `script/fmt` and `script/fmt-check` now run yarn
|
||||||
|
under nvm's pinned node when nvm has it installed, and otherwise the `yarn` on
|
||||||
|
`PATH`. `REPO_POLICIES.md` and both checklists say so. Not yet tried on the
|
||||||
|
shared runner. Repositories pick this up on their next re-vendor.
|
||||||
- 2026-10-07: The `script/cibuild` item in `NEW_REPO_CHECKLIST.md` now matches
|
- 2026-10-07: The `script/cibuild` item in `NEW_REPO_CHECKLIST.md` now matches
|
||||||
the canonical `script/cibuild` (issue 125). Its image build carries the tag,
|
the canonical `script/cibuild` (issue 125). Its image build carries the tag,
|
||||||
`-t "$tag"`, and the item says that `$version` comes from
|
`-t "$tag"`, and the item says that `$version` comes from
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: Existing Repo Checklist
|
title: Existing Repo Checklist
|
||||||
last_modified: 2026-10-07
|
last_modified: 2026-10-08
|
||||||
---
|
---
|
||||||
|
|
||||||
Use this checklist when beginning work in a repo that may not yet conform to our
|
Use this checklist when beginning work in a repo that may not yet conform to our
|
||||||
@@ -149,11 +149,11 @@ with your task.
|
|||||||
the image with `--no-cache`. Without the bootstrap the CI run dies in
|
the image with `--no-cache`. Without the bootstrap the CI run dies in
|
||||||
`script/fmt-check`, which runs the formatter on the host and finds nothing
|
`script/fmt-check`, which runs the formatter on the host and finds nothing
|
||||||
installed.
|
installed.
|
||||||
- [ ] `script/fmt` and `script/fmt-check` source nvm for the pinned node version
|
- [ ] `script/fmt` and `script/fmt-check` run `yarn` under nvm's pinned node
|
||||||
before invoking `yarn`, as `script/bootstrap`'s own install step does.
|
when nvm has that version installed, and otherwise the `yarn` on `PATH`.
|
||||||
`script/bootstrap` leaves the node and yarn it installs off the `PATH` of
|
`script/bootstrap` leaves the node and yarn it installs under nvm off the
|
||||||
the shell that called it, so a bare `yarn` exits 127 on a runner carrying
|
`PATH` of the shell that called it, so a bare `yarn` exits 127 on a runner
|
||||||
nothing but docker and git.
|
carrying nothing but docker and git, or runs under another node.
|
||||||
- [ ] `script/bootstrap` installs no linter of its own — delete the block, its
|
- [ ] `script/bootstrap` installs no linter of its own — delete the block, its
|
||||||
version variables and its call site. A JS repo's `yarn install` stays; it
|
version variables and its call site. A JS repo's `yarn install` stays; it
|
||||||
brings a linter along with every other dependency, and no verdict is taken
|
brings a linter along with every other dependency, and no verdict is taken
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: New Repo Checklist
|
title: New Repo Checklist
|
||||||
last_modified: 2026-10-07
|
last_modified: 2026-10-08
|
||||||
---
|
---
|
||||||
|
|
||||||
Use this checklist when creating a new repository from scratch. Follow the steps
|
Use this checklist when creating a new repository from scratch. Follow the steps
|
||||||
@@ -136,8 +136,9 @@ are thin shims calling them. Model scripts:
|
|||||||
alpine images without bash
|
alpine images without bash
|
||||||
- [ ] `script/bootstrap` / `make bootstrap` — installs all dependencies,
|
- [ ] `script/bootstrap` / `make bootstrap` — installs all dependencies,
|
||||||
idempotently, assuming nothing (pkg manager detection nix/apt/brew/apk;
|
idempotently, assuming nothing (pkg manager detection nix/apt/brew/apk;
|
||||||
node used if present, else pinned version via nvm from a hash-verified
|
node used if its major version is the pinned one, else pinned version via
|
||||||
archive; pinned yarn via corepack); a non-server repo's development
|
nvm from a hash-verified archive, with yarn and the packages installed
|
||||||
|
under it; pinned yarn via corepack); a non-server repo's development
|
||||||
environment stage runs it instead of inline installs; a gate phase or the
|
environment stage runs it instead of inline installs; a gate phase or the
|
||||||
build stage installs what its base image lacks either inline or by running
|
build stage installs what its base image lacks either inline or by running
|
||||||
it
|
it
|
||||||
@@ -170,11 +171,11 @@ are thin shims calling them. Model scripts:
|
|||||||
on its own line before the build. The bootstrap is required: CI checks out
|
on its own line before the build. The bootstrap is required: CI checks out
|
||||||
and runs this alone, and `script/fmt-check` runs the formatter on the
|
and runs this alone, and `script/fmt-check` runs the formatter on the
|
||||||
host.
|
host.
|
||||||
- [ ] `script/fmt` and `script/fmt-check` source nvm for the pinned node version
|
- [ ] `script/fmt` and `script/fmt-check` run `yarn` under nvm's pinned node
|
||||||
before invoking `yarn`, as `script/bootstrap`'s own install step does.
|
when nvm has that version installed, and otherwise the `yarn` on `PATH`.
|
||||||
`script/bootstrap` leaves the node and yarn it installs off the `PATH` of
|
`script/bootstrap` leaves the node and yarn it installs under nvm off the
|
||||||
the shell that called it, so a bare `yarn` exits 127 on a runner carrying
|
`PATH` of the shell that called it, so a bare `yarn` exits 127 on a runner
|
||||||
nothing but docker and git.
|
carrying nothing but docker and git, or runs under another node.
|
||||||
- [ ] No `docker build` in `script/` leaves a dangling image behind:
|
- [ ] No `docker build` in `script/` leaves a dangling image behind:
|
||||||
`script/lint` and `script/test` write no image, and `script/docker` and
|
`script/lint` and `script/test` write no image, and `script/docker` and
|
||||||
`script/cibuild` tag theirs
|
`script/cibuild` tag theirs
|
||||||
|
|||||||
+30
-26
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: Repository Policies
|
title: Repository Policies
|
||||||
last_modified: 2026-10-07
|
last_modified: 2026-10-08
|
||||||
---
|
---
|
||||||
|
|
||||||
This document covers repository structure, tooling, and workflow standards. Code
|
This document covers repository structure, tooling, and workflow standards. Code
|
||||||
@@ -54,34 +54,38 @@ style conventions are in separate documents:
|
|||||||
`cibuild`. `script/bootstrap` installs all dependencies idempotently and
|
`cibuild`. `script/bootstrap` installs all dependencies idempotently and
|
||||||
assumes nothing is present: base tools come from nix, apt, brew, or apk
|
assumes nothing is present: base tools come from nix, apt, brew, or apk
|
||||||
(detected in that order; apt runs noninteractive). For node it uses the
|
(detected in that order; apt runs noninteractive). For node it uses the
|
||||||
installed node if present; otherwise it installs a PINNED node version via
|
installed node only when its major version is the pinned one; otherwise (node
|
||||||
nvm, first installing nvm itself if missing — from a hash-verified GitHub
|
missing, or another major, such as the node 24 the CI runner image ships) it
|
||||||
release archive (never `curl | sh`), with bash installed as an explicit
|
installs the PINNED node version via nvm and installs yarn and the packages
|
||||||
prerequisite since nvm requires bash. yarn is then pinned via
|
under it. nvm itself is installed first if missing, from a hash-verified
|
||||||
`corepack prepare yarn@<version> --activate`. Never install "latest" or "lts";
|
GitHub release archive (never `curl | sh`), with bash installed as an explicit
|
||||||
always exact versions. `script/cibuild` runs the CI build: it changes to the
|
prerequisite since nvm requires bash. Only the major version is compared
|
||||||
repo root, runs `script/bootstrap`, runs `script/check`, and builds the image
|
because the `Dockerfile` stages start from a node image whose exact version is
|
||||||
with the version; the Gitea workflow calls it. **`script/cibuild` runs
|
not the pin, and nvm cannot install a prebuilt node on alpine. yarn is 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, 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
|
`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
|
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
|
pristine checkout with nothing installed the run dies there, after the
|
||||||
containerised gates have passed. **The bootstrap alone is not enough**:
|
containerised gates have passed. **The bootstrap alone is not enough**: when
|
||||||
`script/bootstrap` installs node and yarn under nvm and leaves neither on the
|
`script/bootstrap` installs node and yarn under nvm it leaves neither on the
|
||||||
`PATH` of the shell that called it, so a bare `yarn` still exits 127. The host
|
`PATH` of the shell that called it, so a bare `yarn` either exits 127 or runs
|
||||||
entrypoints that need yarn — `script/fmt` and `script/fmt-check` — therefore
|
under another node. The host entrypoints that need yarn — `script/fmt` and
|
||||||
source nvm for the pinned node version before invoking it, exactly as
|
`script/fmt-check` — therefore run it under nvm's pinned node when nvm has
|
||||||
`script/bootstrap`'s own install step does. A runner carrying nothing but
|
that version installed, and otherwise run the `yarn` on `PATH`. A runner
|
||||||
docker and git then gets through `script/check`. Four further scripts are our
|
carrying nothing but docker and git then gets through `script/check`. Four
|
||||||
own extensions to the standard: `script/check` runs `script/test`,
|
further scripts are our own extensions to the standard: `script/check` runs
|
||||||
`script/lint` and `script/fmt-check`; `script/precommit` is what the git
|
`script/test`, `script/lint` and `script/fmt-check`; `script/precommit` is
|
||||||
pre-commit hook runs, and it calls `script/check`; `script/install-precommit`
|
what the git pre-commit hook runs, and it calls `script/check`;
|
||||||
installs the git pre-commit hook (the `make hooks` target shims to it); and
|
`script/install-precommit` installs the git pre-commit hook (the `make hooks`
|
||||||
`script/projectname` (literally that filename) simply outputs the project's
|
target shims to it); and `script/projectname` (literally that filename) simply
|
||||||
name. Scripts that need the name call `script/projectname` — e.g.
|
outputs the project's name. Scripts that need the name call
|
||||||
`script/docker` assembles its image tag from it — so those scripts stay
|
`script/projectname` — e.g. `script/docker` assembles its image tag from it —
|
||||||
byte-identical across all repos. Repo-type-specific pre-commit extras (e.g.
|
so those scripts stay byte-identical across all repos. Repo-type-specific
|
||||||
`go mod tidy` verification in Go repos) belong in `script/precommit`, not in
|
pre-commit extras (e.g. `go mod tidy` verification in Go repos) belong in
|
||||||
the hook itself. Model scripts are at
|
`script/precommit`, not in the hook itself. Model scripts are at
|
||||||
`https://git.eeqj.de/sneak/prompts/raw/branch/main/script/<name>`. The README
|
`https://git.eeqj.de/sneak/prompts/raw/branch/main/script/<name>`. The README
|
||||||
must document the provided scripts in an **Entrypoints** section (see the
|
must document the provided scripts in an **Entrypoints** section (see the
|
||||||
README requirements below).
|
README requirements below).
|
||||||
|
|||||||
+27
-11
@@ -2,10 +2,12 @@
|
|||||||
# script/bootstrap: install all dependencies needed to build and develop
|
# script/bootstrap: install all dependencies needed to build and develop
|
||||||
# this repo. Idempotent: every install is guarded by a check so already
|
# this repo. Idempotent: every install is guarded by a check so already
|
||||||
# installed tools are skipped. Base tooling comes from nix, apt, brew,
|
# installed tools are skipped. Base tooling comes from nix, apt, brew,
|
||||||
# or apk (detected in that order); assumes nothing is present. Node is
|
# or apk (detected in that order); assumes nothing is present. The
|
||||||
# used directly if installed; otherwise it is installed at a pinned
|
# installed node is used only when its major version is the pinned one;
|
||||||
# version via nvm (installing nvm itself first, from a hash-verified
|
# otherwise (node missing, or another major) the pinned version is
|
||||||
# release archive, never curl | sh).
|
# installed via nvm (installing nvm itself first, from a hash-verified
|
||||||
|
# release archive, never curl | sh), and yarn and the packages are
|
||||||
|
# installed under it.
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||||
@@ -100,31 +102,45 @@ ensure_nvm() {
|
|||||||
rm -rf "$tmp"
|
rm -rf "$tmp"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# True when the node on PATH has the pinned major version. Only the
|
||||||
|
# major is compared: the Dockerfile stages start from a node 22 alpine
|
||||||
|
# image whose exact version is not the pin, and nvm cannot install a
|
||||||
|
# prebuilt node on alpine. Another major is not used: the pinned yarn 1
|
||||||
|
# prints a deprecation warning under node 24.
|
||||||
|
node_is_pinned_major() {
|
||||||
|
if missing node; then return 1; fi
|
||||||
|
installed="$(node --version)"
|
||||||
|
installed="${installed#v}"
|
||||||
|
[ "${installed%%.*}" = "${NODE_VERSION%%.*}" ]
|
||||||
|
}
|
||||||
|
|
||||||
ensure_node() {
|
ensure_node() {
|
||||||
if ! missing node; then return 0; fi
|
if node_is_pinned_major; then return 0; fi
|
||||||
ensure_nvm
|
ensure_nvm
|
||||||
nvm_sh "nvm install $NODE_VERSION"
|
nvm_sh "nvm install $NODE_VERSION"
|
||||||
}
|
}
|
||||||
|
|
||||||
ensure_yarn() {
|
ensure_yarn() {
|
||||||
|
if ! node_is_pinned_major; then
|
||||||
|
nvm_sh "nvm use $NODE_VERSION >/dev/null && corepack enable && \
|
||||||
|
corepack prepare yarn@$YARN_VERSION --activate"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
if ! missing yarn; then return 0; fi
|
if ! missing yarn; then return 0; fi
|
||||||
if ! missing corepack; then
|
if ! missing corepack; then
|
||||||
corepack enable
|
corepack enable
|
||||||
corepack prepare "yarn@$YARN_VERSION" --activate
|
corepack prepare "yarn@$YARN_VERSION" --activate
|
||||||
elif [ -s "$HOME/.nvm/nvm.sh" ]; then
|
|
||||||
nvm_sh "nvm use $NODE_VERSION >/dev/null && corepack enable && \
|
|
||||||
corepack prepare yarn@$YARN_VERSION --activate"
|
|
||||||
else
|
else
|
||||||
npm install -g "yarn@$YARN_VERSION"
|
npm install -g "yarn@$YARN_VERSION"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
install_js_deps() {
|
install_js_deps() {
|
||||||
if missing yarn && [ -s "$HOME/.nvm/nvm.sh" ]; then
|
if node_is_pinned_major; then
|
||||||
|
yarn install --frozen-lockfile
|
||||||
|
else
|
||||||
nvm_sh "nvm use $NODE_VERSION >/dev/null && cd \"$ROOT\" && \
|
nvm_sh "nvm use $NODE_VERSION >/dev/null && cd \"$ROOT\" && \
|
||||||
yarn install --frozen-lockfile"
|
yarn install --frozen-lockfile"
|
||||||
else
|
|
||||||
yarn install --frozen-lockfile
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-9
@@ -7,20 +7,21 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
|||||||
# Must match the pin in script/bootstrap.
|
# Must match the pin in script/bootstrap.
|
||||||
NODE_VERSION="22.17.0"
|
NODE_VERSION="22.17.0"
|
||||||
|
|
||||||
# script/bootstrap installs node and yarn under nvm and leaves neither
|
# When the installed node is not the pinned major version,
|
||||||
# on the PATH of the shell that called it, so resolve the pinned
|
# script/bootstrap installs the pinned node and yarn under nvm and
|
||||||
# toolchain here the way bootstrap's own install step does. nvm is a
|
# leaves neither on the PATH of the shell that called it. So yarn runs
|
||||||
# bash script, hence the subshell.
|
# under nvm's pinned node when nvm has it installed, and otherwise is
|
||||||
|
# the yarn on PATH. nvm is a bash script, hence the subshell.
|
||||||
run_yarn() {
|
run_yarn() {
|
||||||
if command -v yarn >/dev/null 2>&1; then
|
if [ -d "$HOME/.nvm/versions/node/v$NODE_VERSION" ]; then
|
||||||
exec yarn "$@"
|
exec bash -c '. "$HOME/.nvm/nvm.sh" && nvm use "$1" >/dev/null &&
|
||||||
|
shift && exec yarn "$@"' bash "$NODE_VERSION" "$@"
|
||||||
fi
|
fi
|
||||||
if [ ! -s "$HOME/.nvm/nvm.sh" ]; then
|
if ! command -v yarn >/dev/null 2>&1; then
|
||||||
echo "fmt: no yarn; run script/bootstrap first" >&2
|
echo "fmt: no yarn; run script/bootstrap first" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
exec bash -c '. "$HOME/.nvm/nvm.sh" && nvm use "$1" >/dev/null &&
|
exec yarn "$@"
|
||||||
shift && exec yarn "$@"' bash "$NODE_VERSION" "$@"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
|
|||||||
+10
-9
@@ -7,20 +7,21 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
|||||||
# Must match the pin in script/bootstrap.
|
# Must match the pin in script/bootstrap.
|
||||||
NODE_VERSION="22.17.0"
|
NODE_VERSION="22.17.0"
|
||||||
|
|
||||||
# script/bootstrap installs node and yarn under nvm and leaves neither
|
# When the installed node is not the pinned major version,
|
||||||
# on the PATH of the shell that called it, so resolve the pinned
|
# script/bootstrap installs the pinned node and yarn under nvm and
|
||||||
# toolchain here the way bootstrap's own install step does. nvm is a
|
# leaves neither on the PATH of the shell that called it. So yarn runs
|
||||||
# bash script, hence the subshell.
|
# under nvm's pinned node when nvm has it installed, and otherwise is
|
||||||
|
# the yarn on PATH. nvm is a bash script, hence the subshell.
|
||||||
run_yarn() {
|
run_yarn() {
|
||||||
if command -v yarn >/dev/null 2>&1; then
|
if [ -d "$HOME/.nvm/versions/node/v$NODE_VERSION" ]; then
|
||||||
exec yarn "$@"
|
exec bash -c '. "$HOME/.nvm/nvm.sh" && nvm use "$1" >/dev/null &&
|
||||||
|
shift && exec yarn "$@"' bash "$NODE_VERSION" "$@"
|
||||||
fi
|
fi
|
||||||
if [ ! -s "$HOME/.nvm/nvm.sh" ]; then
|
if ! command -v yarn >/dev/null 2>&1; then
|
||||||
echo "fmt-check: no yarn; run script/bootstrap first" >&2
|
echo "fmt-check: no yarn; run script/bootstrap first" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
exec bash -c '. "$HOME/.nvm/nvm.sh" && nvm use "$1" >/dev/null &&
|
exec yarn "$@"
|
||||||
shift && exec yarn "$@"' bash "$NODE_VERSION" "$@"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
|
|||||||
Reference in New Issue
Block a user