Bootstrap keeps yarn at its pinned version and node at its pinned major (closes #154)
check / check (push) Waiting to run
check / check (push) Waiting to run
script/bootstrap kept any yarn on PATH, so YARN_VERSION applied only where none was installed. It now keeps the yarn on PATH only when yarn --version is exactly YARN_VERSION, and otherwise installs that version. The node on PATH is used only when it has the pinned major version, as REPO_POLICIES.md asks, and is at least NODE_MIN_VERSION. After installing either, bootstrap looks the tool up through PATH again, stops, naming the path, if that one fails the same check, and prints the version it uses, as for Go. Every other host script that runs yarn now goes through the new script/yarn, which uses the first node on PATH, ~/.local/bin first, when it passes the same test, and otherwise the pinned node in nvm. Model: opus-5-5
This commit was merged in pull request #176.
This commit is contained in:
@@ -41,11 +41,13 @@ The scripts cover both the frontend and the Go backend, so `make check` fails if
|
|||||||
either one is broken. We provide:
|
either one is broken. We provide:
|
||||||
|
|
||||||
- `script/bootstrap` — install all dependencies (the pinned node via nvm unless
|
- `script/bootstrap` — install all dependencies (the pinned node via nvm unless
|
||||||
one new enough for the frontend's dependencies is installed, yarn via
|
the `node` on `PATH` has the pinned major version and is new enough for the
|
||||||
corepack, `yarn install --frozen-lockfile`, Go `GO_VERSION` unless the `go` on
|
frontend's dependencies, yarn `YARN_VERSION` via corepack, or npm where there
|
||||||
`PATH` is exactly that version, and the Go modules), linking what it installs
|
is no corepack, unless the `yarn` on `PATH` is exactly that version,
|
||||||
itself into `~/.local/bin`, which has to be on `PATH`. It installs no Go
|
`yarn install --frozen-lockfile`, Go `GO_VERSION` unless the `go` on `PATH` is
|
||||||
linter and not Docker: `make test` and `make lint` run in Docker
|
exactly that version, and the Go modules), linking what it installs itself
|
||||||
|
into `~/.local/bin`, which has to be on `PATH`. It installs no Go linter and
|
||||||
|
not Docker: `make test` and `make lint` run in Docker
|
||||||
- `script/setup` — make a fresh clone ready for development: bootstrap plus the
|
- `script/setup` — make a fresh clone ready for development: bootstrap plus the
|
||||||
git pre-commit hook
|
git pre-commit hook
|
||||||
- `script/dev` — run the Vite dev server, which proxies `/api` to a locally
|
- `script/dev` — run the Vite dev server, which proxies `/api` to a locally
|
||||||
@@ -58,6 +60,12 @@ either one is broken. We provide:
|
|||||||
defaults (see [Settings](#settings))
|
defaults (see [Settings](#settings))
|
||||||
- `script/clean` — remove what `script/build` writes
|
- `script/clean` — remove what `script/build` writes
|
||||||
- `script/projectname` — print the project name (used for the Docker image tag)
|
- `script/projectname` — print the project name (used for the Docker image tag)
|
||||||
|
- `script/yarn` — run yarn with the given arguments under the node
|
||||||
|
`script/bootstrap` would use: with `~/.local/bin` first on `PATH`, the first
|
||||||
|
`node` on that `PATH` when it has the pinned major version and is new enough
|
||||||
|
for the frontend's dependencies, and otherwise the pinned node in nvm. Every
|
||||||
|
script that runs yarn on the host runs it through this one, except
|
||||||
|
`script/bootstrap`, which picks its own node and yarn
|
||||||
- `script/test` — build the `test` phase of `Dockerfile`, writing no image: the
|
- `script/test` — build the `test` phase of `Dockerfile`, writing no image: the
|
||||||
frontend's unit tests in its `frontend-test` stage, and the backend's Go tests
|
frontend's unit tests in its `frontend-test` stage, and the backend's Go tests
|
||||||
with the race detector and coverage. The frontend's production build is left
|
with the race detector and coverage. The frontend's production build is left
|
||||||
|
|||||||
@@ -21,6 +21,20 @@ Run `make frontend-viewport-test` in CI as its own step; it is not part of
|
|||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
|
- 2026-10-19: `script/bootstrap` keeps the `yarn` on `PATH` only when
|
||||||
|
`yarn --version` is exactly `YARN_VERSION`, and otherwise installs that
|
||||||
|
version as it did when no yarn was installed
|
||||||
|
([#154](https://git.eeqj.de/sneak/netwatch/issues/154)); any yarn used to be
|
||||||
|
kept. It uses the `node` on `PATH` only when that node has the major version
|
||||||
|
of `NODE_VERSION`, as `REPO_POLICIES.md` asks, and is at least
|
||||||
|
`NODE_MIN_VERSION`; a node 24 was kept before. After installing node or yarn
|
||||||
|
it looks the tool up through `PATH` again and stops if that one fails the same
|
||||||
|
check, as it does for Go, and it prints the node and yarn versions it uses.
|
||||||
|
Every script that runs yarn on the host, except `script/bootstrap`, which
|
||||||
|
picks its own node and yarn, runs it through the new `script/yarn`. That
|
||||||
|
script puts `~/.local/bin` first on `PATH`, uses the first `node` on that
|
||||||
|
`PATH` when it passes the same test, and otherwise runs yarn under the pinned
|
||||||
|
node in nvm; `make build` ran yarn under whatever node was first on `PATH`
|
||||||
- 2026-10-19: `bin/entrypoint.sh` has each of its steps in a function, and a
|
- 2026-10-19: `bin/entrypoint.sh` has each of its steps in a function, and a
|
||||||
`main` at the end calls them in order, as the shell section of the code style
|
`main` at the end calls them in order, as the shell section of the code style
|
||||||
guide asks ([#163](https://git.eeqj.de/sneak/netwatch/issues/163)). It is
|
guide asks ([#163](https://git.eeqj.de/sneak/netwatch/issues/163)). It is
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ main() {
|
|||||||
"" | -* | *[[:space:]]*) usage ;;
|
"" | -* | *[[:space:]]*) usage ;;
|
||||||
esac
|
esac
|
||||||
cd "$ROOT"
|
cd "$ROOT"
|
||||||
yarn add --dev "$1"
|
"$ROOT/script/yarn" add --dev "$1"
|
||||||
yarn install --frozen-lockfile
|
"$ROOT/script/yarn" install --frozen-lockfile
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
+55
-14
@@ -3,11 +3,14 @@
|
|||||||
# 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. Node is
|
||||||
# used directly if it is at least NODE_MIN_VERSION; otherwise it is
|
# used directly only if it has the major version of NODE_VERSION and is
|
||||||
# installed at a pinned version via nvm (installing nvm itself first,
|
# at least NODE_MIN_VERSION; otherwise NODE_VERSION is installed via nvm
|
||||||
# from a hash-verified release archive, never curl | sh). Go, with its
|
# (installing nvm itself first, from a hash-verified release archive,
|
||||||
# gofmt, is used directly only if it is exactly GO_VERSION; otherwise
|
# never curl | sh). Yarn is used directly only if it is exactly
|
||||||
# that release is installed from its hash-verified archive.
|
# YARN_VERSION; otherwise that version is prepared through corepack,
|
||||||
|
# or installed with npm where there is no corepack.
|
||||||
|
# Go, with its gofmt, is used directly only if it is exactly GO_VERSION;
|
||||||
|
# otherwise that release is installed from its hash-verified archive.
|
||||||
#
|
#
|
||||||
# What this script installs outside the system package manager lives
|
# What this script installs outside the system package manager lives
|
||||||
# under $HOME and is linked into ~/.local/bin, where make and the git
|
# under $HOME and is linked into ~/.local/bin, where make and the git
|
||||||
@@ -142,32 +145,63 @@ ensure_nvm() {
|
|||||||
rm -rf "$tmp"
|
rm -rf "$tmp"
|
||||||
}
|
}
|
||||||
|
|
||||||
# node_ok: the node on PATH is at least NODE_MIN_VERSION. node itself
|
# node_ok: the node on PATH has the major version of NODE_VERSION and is
|
||||||
# compares the two: major, then minor, then patch.
|
# at least NODE_MIN_VERSION. Only the major is compared with the pin,
|
||||||
|
# as REPO_POLICIES.md asks. node itself compares the versions: major,
|
||||||
|
# then minor, then patch. A node that fails does not pass.
|
||||||
|
# script/bootstrap and script/yarn carry this function unchanged.
|
||||||
node_ok() {
|
node_ok() {
|
||||||
if missing node; then return 1; fi
|
if ! command -v node >/dev/null 2>&1; then return 1; fi
|
||||||
node -e '
|
node -e '
|
||||||
const have = process.versions.node.split(".").map(Number);
|
const have = process.versions.node.split(".").map(Number);
|
||||||
const want = process.argv[1].split(".").map(Number);
|
const want = process.argv[1].split(".").map(Number);
|
||||||
|
const pinned = process.argv[2].split(".").map(Number);
|
||||||
|
if (have[0] !== pinned[0]) process.exit(1);
|
||||||
for (let i = 0; i < 3; i++) {
|
for (let i = 0; i < 3; i++) {
|
||||||
if (have[i] !== want[i]) process.exit(have[i] > want[i] ? 0 : 1);
|
if (have[i] !== want[i]) process.exit(have[i] > want[i] ? 0 : 1);
|
||||||
}
|
}
|
||||||
' "$NODE_MIN_VERSION"
|
' "$NODE_MIN_VERSION" "$NODE_VERSION"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ensure_node: unless node_ok, install NODE_VERSION and link its node.
|
# ensure_node: unless node_ok, install NODE_VERSION and link its node.
|
||||||
|
# Afterwards node is looked up through PATH again and must pass node_ok.
|
||||||
ensure_node() {
|
ensure_node() {
|
||||||
if node_ok; then return 0; fi
|
if node_ok; then
|
||||||
|
echo "bootstrap: using node $(node --version)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
ensure_nvm
|
ensure_nvm
|
||||||
nvm_sh "nvm install $NODE_VERSION"
|
nvm_sh "nvm install $NODE_VERSION"
|
||||||
link_bin "$HOME/.nvm/versions/node/v$NODE_VERSION/bin/node" node
|
link_bin "$HOME/.nvm/versions/node/v$NODE_VERSION/bin/node" node
|
||||||
|
hash -r
|
||||||
|
if ! node_ok; then
|
||||||
|
echo "bootstrap: after installing node $NODE_VERSION through nvm," >&2
|
||||||
|
echo " the node on PATH, $(command -v node), is not it" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "bootstrap: installed node $(node --version)"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ensure_yarn: corepack writes its shims (pnpm and yarnpkg as well as
|
# yarn_ok: yarn --version on PATH prints exactly YARN_VERSION, compared
|
||||||
# yarn) into $TOOLCHAIN rather than next to itself, and the npm fallback
|
# over the whole version. A yarn that fails or prints anything else does
|
||||||
# installs there too; only yarn is linked.
|
# not pass.
|
||||||
|
yarn_ok() {
|
||||||
|
if missing yarn; then return 1; fi
|
||||||
|
version="$(yarn --version 2>/dev/null)" || return 1
|
||||||
|
[ "$version" = "$YARN_VERSION" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
# ensure_yarn: unless yarn_ok, prepare YARN_VERSION and link its yarn.
|
||||||
|
# corepack writes its shims (pnpm and yarnpkg as well as yarn) into
|
||||||
|
# $TOOLCHAIN rather than next to itself, and the npm fallback installs
|
||||||
|
# there too; only yarn is linked. Afterwards yarn is looked up through
|
||||||
|
# PATH again and must pass yarn_ok, so another yarn that hides the link
|
||||||
|
# stops bootstrap.
|
||||||
ensure_yarn() {
|
ensure_yarn() {
|
||||||
if ! missing yarn; then return 0; fi
|
if yarn_ok; then
|
||||||
|
echo "bootstrap: using yarn $(yarn --version)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
shims="$TOOLCHAIN/corepack-shims"
|
shims="$TOOLCHAIN/corepack-shims"
|
||||||
mkdir -p "$shims"
|
mkdir -p "$shims"
|
||||||
if ! missing corepack; then
|
if ! missing corepack; then
|
||||||
@@ -182,6 +216,13 @@ ensure_yarn() {
|
|||||||
shims="$TOOLCHAIN/npm-global/bin"
|
shims="$TOOLCHAIN/npm-global/bin"
|
||||||
fi
|
fi
|
||||||
link_bin "$shims/yarn" yarn
|
link_bin "$shims/yarn" yarn
|
||||||
|
hash -r
|
||||||
|
if ! yarn_ok; then
|
||||||
|
echo "bootstrap: after installing yarn $YARN_VERSION in $shims," >&2
|
||||||
|
echo " the yarn on PATH, $(command -v yarn), does not report it" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "bootstrap: installed yarn $(yarn --version)"
|
||||||
}
|
}
|
||||||
|
|
||||||
# go_ok: the go on PATH has its gofmt beside it (a Go release ships the
|
# go_ok: the go on PATH has its gofmt beside it (a Go release ships the
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
|||||||
|
|
||||||
main() {
|
main() {
|
||||||
cd "$ROOT"
|
cd "$ROOT"
|
||||||
yarn build
|
"$ROOT/script/yarn" build
|
||||||
|
|
||||||
# VERSION comes from the environment. Unset or empty, it is git
|
# VERSION comes from the environment. Unset or empty, it is git
|
||||||
# describe, or "dev" where there is no git or no repository history.
|
# describe, or "dev" where there is no git or no repository history.
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
|||||||
|
|
||||||
main() {
|
main() {
|
||||||
cd "$ROOT"
|
cd "$ROOT"
|
||||||
yarn dev
|
"$ROOT/script/yarn" dev
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
|||||||
|
|
||||||
main() {
|
main() {
|
||||||
cd "$ROOT"
|
cd "$ROOT"
|
||||||
yarn prettier --write '**/*.{js,css,html,md}'
|
"$ROOT/script/yarn" prettier --write '**/*.{js,css,html,md}'
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
|||||||
|
|
||||||
main() {
|
main() {
|
||||||
cd "$ROOT"
|
cd "$ROOT"
|
||||||
yarn prettier --check '**/*.{js,css,html,md}'
|
"$ROOT/script/yarn" prettier --check '**/*.{js,css,html,md}'
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
@@ -14,12 +14,14 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
|||||||
|
|
||||||
main() {
|
main() {
|
||||||
cd "$ROOT"
|
cd "$ROOT"
|
||||||
NODE_OPTIONS=--test-reporter=dot timeout 90 yarn --silent run test || {
|
NODE_OPTIONS=--test-reporter=dot \
|
||||||
|
timeout 90 "$ROOT/script/yarn" --silent run test || {
|
||||||
echo "--- Rerunning with every test listed for details ---"
|
echo "--- Rerunning with every test listed for details ---"
|
||||||
NODE_OPTIONS=--test-reporter=spec timeout 90 yarn --silent run test
|
NODE_OPTIONS=--test-reporter=spec \
|
||||||
|
timeout 90 "$ROOT/script/yarn" --silent run test
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
timeout 30 yarn build
|
timeout 30 "$ROOT/script/yarn" build
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
Executable
+55
@@ -0,0 +1,55 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# script/yarn: run yarn with the given arguments under the node
|
||||||
|
# script/bootstrap would use. Every script that runs yarn on the host
|
||||||
|
# runs it through this one, except script/bootstrap, which picks its
|
||||||
|
# own node and yarn.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||||
|
|
||||||
|
# Must match the pins in script/bootstrap.
|
||||||
|
NODE_VERSION="22.17.0"
|
||||||
|
NODE_MIN_VERSION="22.13.0"
|
||||||
|
|
||||||
|
# node_ok: the node on PATH has the major version of NODE_VERSION and is
|
||||||
|
# at least NODE_MIN_VERSION. Only the major is compared with the pin,
|
||||||
|
# as REPO_POLICIES.md asks. node itself compares the versions: major,
|
||||||
|
# then minor, then patch. A node that fails does not pass.
|
||||||
|
# script/bootstrap and script/yarn carry this function unchanged.
|
||||||
|
node_ok() {
|
||||||
|
if ! command -v node >/dev/null 2>&1; then return 1; fi
|
||||||
|
node -e '
|
||||||
|
const have = process.versions.node.split(".").map(Number);
|
||||||
|
const want = process.argv[1].split(".").map(Number);
|
||||||
|
const pinned = process.argv[2].split(".").map(Number);
|
||||||
|
if (have[0] !== pinned[0]) process.exit(1);
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
if (have[i] !== want[i]) process.exit(have[i] > want[i] ? 0 : 1);
|
||||||
|
}
|
||||||
|
' "$NODE_MIN_VERSION" "$NODE_VERSION"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run the yarn script/bootstrap installed: the yarn on PATH when the
|
||||||
|
# node on PATH passes node_ok, and otherwise yarn under the pinned node
|
||||||
|
# in nvm. nvm is a bash script, hence the subshell.
|
||||||
|
run_yarn() {
|
||||||
|
if node_ok && command -v yarn >/dev/null 2>&1; then
|
||||||
|
exec yarn "$@"
|
||||||
|
fi
|
||||||
|
if node_ok || [ ! -s "$HOME/.nvm/nvm.sh" ]; then
|
||||||
|
echo "script/yarn: 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"
|
||||||
|
# script/bootstrap links the node and yarn it installs into
|
||||||
|
# ~/.local/bin, which the shell that called it may not have on PATH.
|
||||||
|
PATH="$HOME/.local/bin:$PATH"
|
||||||
|
run_yarn "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
Reference in New Issue
Block a user