Files
attrsum/script/bootstrap
T
clawbot 8976a64832
check / check (push) Successful in 2m19s
Format Markdown with prettier in make fmt and fmt-check (closes #23)
`make fmt` now runs prettier over the Markdown files after the Go
formatters, and `make fmt-check` fails when prettier would change one.
`package.json`, `yarn.lock`, `.prettierrc` and `.prettierignore` are the
`sneak/prompts` copies at `dd4027b`, except for `"private": true` in
`package.json` in place of the licence field. Both scripts find yarn the
way that repo's scripts do. `script/bootstrap` installs prettier with
`yarn install --frozen-lockfile`. When the yarn those scripts would run
is not 1.22.22, it installs yarn through corepack under node 22.17.0:
the node on `PATH` if it has that version, otherwise one installed
through nvm from a hash-checked archive. `README.md` and `TODO.md` are
reformatted once.

Model: opus-5-5
2026-10-06 11:43:51 +02:00

220 lines
6.7 KiB
Bash
Executable File

#!/bin/sh
# script/bootstrap: install all dependencies needed to build and develop
# this repo. Idempotent: every install is guarded by a check so already
# installed tools are skipped. Base tooling comes from nix, apt, brew,
# or apk (detected in that order); assumes nothing is present.
# goimports is installed via `go install` at a pinned commit (never
# "latest"), unless the installed one already has the pinned version.
# The linter is not installed: it runs only as the lint phase of the
# Dockerfile. yarn is installed via corepack unless the yarn that
# script/fmt runs already has the pinned version. It is installed under
# the node on PATH if that has the pinned version; otherwise the pinned
# node is installed via nvm (installing nvm itself first, from a
# hash-verified release archive, never curl | sh).
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
# Pinned versions, 2026-10-05
# GOIMPORTS_REF is the commit tagged GOIMPORTS_VERSION.
GOIMPORTS_VERSION="v0.42.0"
GOIMPORTS_REF="golang.org/x/tools/cmd/goimports@009367f5c17a8d4c45a961a3a509277190a9a6f0"
# Pinned versions, 2026-07-06
NODE_VERSION="22.17.0"
NVM_VERSION="0.40.3"
# sha256 of https://github.com/nvm-sh/nvm/archive/refs/tags/v0.40.3.tar.gz
NVM_SHA256="5f4d6aaa04a177dc93c985e31dbc411ab6b8c6e1e21d8015dbc1372625fcd1d0"
YARN_VERSION="1.22.22"
PKGMGR=""
SUDO=""
APT_UPDATED=""
detect_pkgmgr() {
[ -n "$PKGMGR" ] && return 0
if command -v nix-env >/dev/null 2>&1; then
PKGMGR="nix"
elif command -v apt-get >/dev/null 2>&1; then
PKGMGR="apt"
elif command -v brew >/dev/null 2>&1; then
PKGMGR="brew"
elif command -v apk >/dev/null 2>&1; then
PKGMGR="apk"
else
echo "bootstrap: no supported package manager (nix, apt, brew, apk)" >&2
exit 1
fi
if [ "$PKGMGR" = "apt" ]; then
export DEBIAN_FRONTEND=noninteractive
if [ "$(id -u)" != "0" ]; then
SUDO="sudo"
fi
fi
}
# pkg_install <nix-attr> <apt-pkg> <brew-formula> <apk-pkg>
pkg_install() {
detect_pkgmgr
case "$PKGMGR" in
nix) nix-env -iA "nixpkgs.$1" ;;
apt)
if [ -z "$APT_UPDATED" ]; then
$SUDO env DEBIAN_FRONTEND=noninteractive apt-get update
APT_UPDATED=1
fi
$SUDO env DEBIAN_FRONTEND=noninteractive apt-get install -y "$2"
;;
brew) brew install "$3" ;;
apk) apk add --no-cache "$4" ;;
esac
}
missing() {
! command -v "$1" >/dev/null 2>&1
}
# Print the version the goimports on PATH was built from, or nothing.
# goimports has no version flag; `go version -m` reads the binary's
# build info, whose "mod" line names the module and its version.
goimports_version() {
if missing goimports; then return 0; fi
go version -m "$(command -v goimports)" 2>/dev/null |
awk '$1 == "mod" { print $3 }'
}
ensure_goimports() {
if [ "$(goimports_version)" = "$GOIMPORTS_VERSION" ]; then
echo "goimports $GOIMPORTS_VERSION already installed"
return 0
fi
go install "$GOIMPORTS_REF"
hash -r
if [ "$(goimports_version)" != "$GOIMPORTS_VERSION" ]; then
echo "bootstrap: goimports on PATH is not $GOIMPORTS_VERSION:" \
"$(command -v goimports)" >&2
exit 1
fi
echo "goimports $GOIMPORTS_VERSION installed"
}
# verify_sha256 <file> <expected-hash>
verify_sha256() {
if command -v sha256sum >/dev/null 2>&1; then
actual="$(sha256sum "$1" | cut -d' ' -f1)"
else
actual="$(shasum -a 256 "$1" | cut -d' ' -f1)"
fi
if [ "$actual" != "$2" ]; then
echo "bootstrap: sha256 mismatch for $1" >&2
echo " expected: $2" >&2
echo " actual: $actual" >&2
exit 1
fi
}
# nvm is a bash script; run a command in a bash with nvm loaded
nvm_sh() {
bash -c ". \"\$HOME/.nvm/nvm.sh\" && $*"
}
ensure_nvm() {
[ -s "$HOME/.nvm/nvm.sh" ] && return 0
# nvm prerequisites; nvm itself requires bash
if missing bash; then pkg_install bash bash bash bash; fi
if missing curl; then pkg_install curl curl curl curl; fi
if missing git; then pkg_install git git git git; fi
tmp="$(mktemp -d)"
curl -fsSL -o "$tmp/nvm.tar.gz" \
"https://github.com/nvm-sh/nvm/archive/refs/tags/v${NVM_VERSION}.tar.gz"
verify_sha256 "$tmp/nvm.tar.gz" "$NVM_SHA256"
mkdir -p "$HOME/.nvm"
tar -xzf "$tmp/nvm.tar.gz" -C "$HOME/.nvm" --strip-components=1
rm -rf "$tmp"
}
# Print the version of the node on PATH, such as v22.17.0, or nothing.
node_version() {
if missing node; then return 0; fi
node --version 2>/dev/null
}
ensure_node() {
if [ "$(node_version)" = "v$NODE_VERSION" ]; then
echo "node $NODE_VERSION already installed"
return 0
fi
ensure_nvm
nvm_sh "nvm install $NODE_VERSION"
echo "node $NODE_VERSION installed via nvm"
}
# Print the version of the yarn that script/fmt and script/fmt-check
# run, or nothing: the yarn on PATH, else the one under the pinned node
# in nvm.
yarn_version() {
if ! missing yarn; then
yarn --version 2>/dev/null
elif [ -s "$HOME/.nvm/nvm.sh" ]; then
nvm_sh "nvm use $NODE_VERSION >/dev/null && yarn --version" \
2>/dev/null
fi
}
# A yarn that already has the pinned version is used with the node that
# runs it. Otherwise yarn is installed via corepack under the pinned
# node.
ensure_yarn() {
if [ "$(yarn_version)" = "$YARN_VERSION" ]; then
echo "yarn $YARN_VERSION already installed"
return 0
fi
ensure_node
if [ "$(node_version)" = "v$NODE_VERSION" ]; then
corepack enable
corepack prepare "yarn@$YARN_VERSION" --activate
else
nvm_sh "nvm use $NODE_VERSION >/dev/null && corepack enable && \
corepack prepare yarn@$YARN_VERSION --activate"
fi
hash -r
if [ "$(yarn_version)" != "$YARN_VERSION" ]; then
echo "bootstrap: the yarn script/fmt runs is not $YARN_VERSION:" \
"$(command -v yarn || echo "none on PATH")" >&2
exit 1
fi
echo "yarn $YARN_VERSION installed"
}
install_js_deps() {
if missing yarn && [ -s "$HOME/.nvm/nvm.sh" ]; then
nvm_sh "nvm use $NODE_VERSION >/dev/null && cd \"$ROOT\" && \
yarn install --frozen-lockfile"
else
yarn install --frozen-lockfile
fi
}
main() {
cd "$ROOT"
if missing git; then pkg_install git git git git; fi
if missing make; then pkg_install gnumake make make make; fi
if missing go; then pkg_install go golang go go; fi
# go install writes to Go's bin directory, which need not be on PATH.
gobin="$(go env GOBIN)"
[ -n "$gobin" ] || gobin="$(go env GOPATH)/bin"
PATH="$gobin:$PATH"
ensure_goimports
go mod download
ensure_yarn
install_js_deps
echo "bootstrap complete"
}
main "$@"