Vendor pinned prettier/format toolchain from prompts scaffold (closes #203) #204

Merged
clawbot merged 1 commits from issue-203-vendor-prettier-toolchain into next2 2026-09-22 14:43:08 +02:00
9 changed files with 277 additions and 136 deletions
+3
View File
@@ -1,2 +1,5 @@
node_modules/
yarn.lock
# Vendored, minified third-party bundles must never be reformatted. # Vendored, minified third-party bundles must never be reformatted.
*.min.js *.min.js
+4
View File
@@ -0,0 +1,4 @@
{
"tabWidth": 4,
"proseWrap": "always"
}
+18 -12
View File
@@ -1,6 +1,8 @@
# Go HTTP Server Conventions # Go HTTP Server Conventions
This document defines the architectural patterns, design decisions, and conventions for building Go HTTP servers. All new projects must follow these standards. This document defines the architectural patterns, design decisions, and
conventions for building Go HTTP servers. All new projects must follow these
standards.
## Table of Contents ## Table of Contents
@@ -26,7 +28,7 @@ This document defines the architectural patterns, design decisions, and conventi
These libraries are **mandatory** for all new projects: These libraries are **mandatory** for all new projects:
| Purpose | Library | Import Path | | Purpose | Library | Import Path |
|---------|---------|-------------| | -------------------- | --------------- | ------------------------------------- |
| Dependency Injection | Uber fx | `go.uber.org/fx` | | Dependency Injection | Uber fx | `go.uber.org/fx` |
| HTTP Router | go-chi | `github.com/go-chi/chi` | | HTTP Router | go-chi | `github.com/go-chi/chi` |
| Logging | slog (stdlib) | `log/slog` | | Logging | slog (stdlib) | `log/slog` |
@@ -85,7 +87,8 @@ project-root/
### Key Principles ### Key Principles
- **`cmd/{appname}/`**: Only the entry point. Minimal logic, just bootstrapping. - **`cmd/{appname}/`**: Only the entry point. Minimal logic, just bootstrapping.
- **`internal/`**: All application packages. Not importable by external projects. - **`internal/`**: All application packages. Not importable by external
projects.
- **One package per concern**: config, database, handlers, middleware, etc. - **One package per concern**: config, database, handlers, middleware, etc.
- **Flat handler files**: One file per handler or logical group of handlers. - **Flat handler files**: One file per handler or logical group of handlers.
@@ -190,7 +193,8 @@ Providers are resolved automatically by fx, but conceptually follow this order:
2. `logger.New` - Logger (depends on Globals) 2. `logger.New` - Logger (depends on Globals)
3. `config.New` - Configuration (depends on Globals, Logger) 3. `config.New` - Configuration (depends on Globals, Logger)
4. `database.New` - Database (depends on Logger, Config) 4. `database.New` - Database (depends on Logger, Config)
5. `healthcheck.New` - Health check (depends on Globals, Config, Logger, Database) 5. `healthcheck.New` - Health check (depends on Globals, Config, Logger,
Database)
6. `middleware.New` - Middleware (depends on Logger, Globals, Config) 6. `middleware.New` - Middleware (depends on Logger, Globals, Config)
7. `handlers.New` - Handlers (depends on Logger, Globals, Database, Healthcheck) 7. `handlers.New` - Handlers (depends on Logger, Globals, Database, Healthcheck)
8. `server.New` - Server (depends on all above) 8. `server.New` - Server (depends on all above)
@@ -453,7 +457,8 @@ func New(lc fx.Lifecycle, params HandlersParams) (*Handlers, error) {
### Closure-Based Handler Pattern ### Closure-Based Handler Pattern
All handlers return `http.HandlerFunc` using the closure pattern. This allows initialization logic to run once when the handler is created: All handlers return `http.HandlerFunc` using the closure pattern. This allows
initialization logic to run once when the handler is created:
```go ```go
// internal/handlers/index.go // internal/handlers/index.go
@@ -510,7 +515,8 @@ func (s *Handlers) decodeJSON(w http.ResponseWriter, r *http.Request, v interfac
### Handler Naming Convention ### Handler Naming Convention
- `HandleIndex()` - Main page - `HandleIndex()` - Main page
- `HandleLoginGET()` / `HandleLoginPOST()` - Form handlers with HTTP method suffix - `HandleLoginGET()` / `HandleLoginPOST()` - Form handlers with HTTP method
suffix
- `HandleNow()` - API endpoints - `HandleNow()` - API endpoints
- `HandleHealthCheck()` - System endpoints - `HandleHealthCheck()` - System endpoints
@@ -733,7 +739,8 @@ func New(lc fx.Lifecycle, params ConfigParams) (*Config, error) {
1. **Environment variables** (highest priority via `AutomaticEnv()`) 1. **Environment variables** (highest priority via `AutomaticEnv()`)
2. **`.env` file** (loaded via `godotenv/autoload` import) 2. **`.env` file** (loaded via `godotenv/autoload` import)
3. **Config files**: `/etc/{appname}/{appname}.yaml`, `~/.config/{appname}/{appname}.yaml` 3. **Config files**: `/etc/{appname}/{appname}.yaml`,
`~/.config/{appname}/{appname}.yaml`
4. **Defaults** (lowest priority) 4. **Defaults** (lowest priority)
### Environment Loading ### Environment Loading
@@ -1005,6 +1012,7 @@ var Static embed.FS
``` ```
Directory structure: Directory structure:
``` ```
static/ static/
├── static.go ├── static.go
@@ -1045,15 +1053,13 @@ Templates use Go's template composition:
```html ```html
<!-- index.html --> <!-- index.html -->
{{ template "htmlheader.html" . }} {{ template "htmlheader.html" . }} {{ template "navbar.html" . }}
{{ template "navbar.html" . }}
<main> <main>
<!-- Page content --> <!-- Page content -->
</main> </main>
{{ template "pagefooter.html" . }} {{ template "pagefooter.html" . }} {{ template "htmlfooter.html" . }}
{{ template "htmlfooter.html" . }}
``` ```
### Static Asset References ### Static Asset References
@@ -1215,7 +1221,7 @@ if viper.GetString("METRICS_USERNAME") != "" {
### Environment Variables Summary ### Environment Variables Summary
| Variable | Description | Default | | Variable | Description | Default |
|----------|-------------|---------| | ------------------ | -------------------------------- | ------- |
| `PORT` | HTTP listen port | 8080 | | `PORT` | HTTP listen port | 8080 |
| `DEBUG` | Enable debug logging | false | | `DEBUG` | Enable debug logging | false |
| `DBURL` | Database connection URL | "" | | `DBURL` | Database connection URL | "" |
+29 -20
View File
@@ -1,12 +1,14 @@
# µPaaS by [@sneak](https://sneak.berlin) # µPaaS by [@sneak](https://sneak.berlin)
A simple self-hosted PaaS that auto-deploys Docker containers from Git repositories via webhooks from Gitea, GitHub, or GitLab. A simple self-hosted PaaS that auto-deploys Docker containers from Git
repositories via webhooks from Gitea, GitHub, or GitLab.
## Features ## Features
- Single admin user with argon2id password hashing - Single admin user with argon2id password hashing
- Per-app SSH keypairs for read-only deploy keys - Per-app SSH keypairs for read-only deploy keys
- Per-app UUID-based webhook URLs with auto-detection of Gitea, GitHub, and GitLab - Per-app UUID-based webhook URLs with auto-detection of Gitea, GitHub, and
GitLab
- Branch filtering - only deploy on configured branch changes - Branch filtering - only deploy on configured branch changes
- Environment variables, labels, and volume mounts per app - Environment variables, labels, and volume mounts per app
- CPU and memory resource limits per app - CPU and memory resource limits per app
@@ -95,9 +97,12 @@ chi Router ──► Middleware Stack ──► Handler
### Key Patterns ### Key Patterns
- **Closure-based handlers**: Handlers return `http.HandlerFunc` allowing one-time initialization - **Closure-based handlers**: Handlers return `http.HandlerFunc` allowing
- **Active Record models**: Models encapsulate database operations (`Save()`, `Delete()`, `Reload()`) one-time initialization
- **Async deployments**: Webhook triggers deploy via goroutine with `context.WithoutCancel()` - **Active Record models**: Models encapsulate database operations (`Save()`,
`Delete()`, `Reload()`)
- **Async deployments**: Webhook triggers deploy via goroutine with
`context.WithoutCancel()`
- **Embedded assets**: Templates and static files embedded via `//go:embed` - **Embedded assets**: Templates and static files embedded via `//go:embed`
## Entrypoints ## Entrypoints
@@ -105,12 +110,12 @@ chi Router ──► Middleware Stack ──► Handler
This repository adheres to the This repository adheres to the
[Scripts to Rule Them All](https://github.com/github/scripts-to-rule-them-all) [Scripts to Rule Them All](https://github.com/github/scripts-to-rule-them-all)
standard: normalized scripts in `script/` are the entrypoints for the standard: normalized scripts in `script/` are the entrypoints for the
development workflow, and the Makefile targets are thin shims that call development workflow, and the Makefile targets are thin shims that call them. We
them. We provide: provide:
- `script/bootstrap` — install all dependencies (idempotent) - `script/bootstrap` — install all dependencies (idempotent)
- `script/setup` — make a fresh clone ready for development - `script/setup` — make a fresh clone ready for development (bootstrap, then
(bootstrap, then install-precommit) install-precommit)
- `script/projectname` — output the project name ("upaas") - `script/projectname` — output the project name ("upaas")
- `script/test` — run the test suite - `script/test` — run the test suite
- `script/lint` — run golangci-lint - `script/lint` — run golangci-lint
@@ -118,12 +123,12 @@ them. We provide:
- `script/fmt-check` — check formatting (read-only) - `script/fmt-check` — check formatting (read-only)
- `script/check` — run test, lint, and fmt-check - `script/check` — run test, lint, and fmt-check
- `script/docker` — build the Docker image tagged via `script/projectname` - `script/docker` — build the Docker image tagged via `script/projectname`
- `script/cibuild` — CI entrypoint: `docker build .` (the Dockerfile - `script/cibuild` — CI entrypoint: `docker build .` (the Dockerfile runs the
runs the checks, so a green build implies a green repo) checks, so a green build implies a green repo)
- `script/precommit` — pre-commit checks (`go mod tidy` guard, then - `script/precommit` — pre-commit checks (`go mod tidy` guard, then
`script/check`) `script/check`)
- `script/install-precommit` — install the git pre-commit hook that - `script/install-precommit` — install the git pre-commit hook that runs
runs `script/precommit` `script/precommit`
## Development ## Development
@@ -174,6 +179,7 @@ git commit -m "Your message"
``` ```
The Docker build runs `make check` and will fail if: The Docker build runs `make check` and will fail if:
- Code is not formatted - Code is not formatted
- Linting errors exist - Linting errors exist
- Tests fail - Tests fail
@@ -186,10 +192,10 @@ This ensures the main branch always contains clean, tested, working code.
Environment variables: Environment variables:
| Variable | Description | Default | | Variable | Description | Default |
|----------|-------------|---------| | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
| `PORT` | HTTP listen port | 8080 | | `PORT` | HTTP listen port | 8080 |
| `UPAAS_DATA_DIR` | Data directory for SQLite and keys | `./data` (local dev only — use absolute path for Docker) | | `UPAAS_DATA_DIR` | Data directory for SQLite and keys | `./data` (local dev only — use absolute path for Docker) |
| `UPAAS_HOST_DATA_DIR` | Host path for DATA_DIR (when running in container) | *(none — must be set to an absolute path)* | | `UPAAS_HOST_DATA_DIR` | Host path for DATA_DIR (when running in container) | _(none — must be set to an absolute path)_ |
| `UPAAS_DOCKER_HOST` | Docker socket path | unix:///var/run/docker.sock | | `UPAAS_DOCKER_HOST` | Docker socket path | unix:///var/run/docker.sock |
| `UPAAS_PLAINTEXT_HTTP` | Set when µPaaS is reached over plain HTTP (no TLS-terminating proxy in front) so CSRF origin checks use `http://`. Leave unset behind a TLS-terminating reverse proxy. | false | | `UPAAS_PLAINTEXT_HTTP` | Set when µPaaS is reached over plain HTTP (no TLS-terminating proxy in front) so CSRF origin checks use `http://`. Leave unset behind a TLS-terminating reverse proxy. | false |
| `DEBUG` | Enable debug logging | false | | `DEBUG` | Enable debug logging | false |
@@ -238,14 +244,17 @@ services:
# - METRICS_PASSWORD=secret # - METRICS_PASSWORD=secret
``` ```
**Important**: You **must** set `HOST_DATA_DIR` to an **absolute path** on the host before running **Important**: You **must** set `HOST_DATA_DIR` to an **absolute path** on the
`docker compose up`. This value is bind-mounted into the container and passed as `UPAAS_HOST_DATA_DIR` host before running `docker compose up`. This value is bind-mounted into the
so that Docker bind mounts during builds resolve correctly. Relative paths (e.g. `./data`) will break container and passed as `UPAAS_HOST_DATA_DIR` so that Docker bind mounts during
container builds because the Docker daemon resolves paths relative to the host, not the container. builds resolve correctly. Relative paths (e.g. `./data`) will break container
builds because the Docker daemon resolves paths relative to the host, not the
container.
Example: `HOST_DATA_DIR=/srv/upaas/data docker compose up -d` Example: `HOST_DATA_DIR=/srv/upaas/data docker compose up -d`
Session secrets are automatically generated on first startup and persisted to `$UPAAS_DATA_DIR/session.key`. Session secrets are automatically generated on first startup and persisted to
`$UPAAS_DATA_DIR/session.key`.
## License ## License
+55 -50
View File
@@ -1,70 +1,75 @@
# Workflow # Workflow
* branch (from `main`) - branch (from `main`)
* do the work in Next Step - do the work in Next Step
* move Next Step to the top of Completed Steps - move Next Step to the top of Completed Steps
* move the top item of Future Steps into Next Step - move the top item of Future Steps into Next Step
* commit (`TODO.md` changes in the same commit as the work) - commit (`TODO.md` changes in the same commit as the work)
* merge to `main` if the branch is not protected, otherwise open a PR - merge to `main` if the branch is not protected, otherwise open a PR
* push - push
# Status # Status
1.0+. Tagged 1.0.0 on 2026-02-26; 8 commits on main since. `make check` 1.0+. Tagged 1.0.0 on 2026-02-26; 8 commits on main since. `make check` is green
is green as of the golangci-lint v2.12.2 update. as of the golangci-lint v2.12.2 update.
# Next Step # Next Step
Confirm `.gitea/workflows/check.yml` gates merges on `make check` so Confirm `.gitea/workflows/check.yml` gates merges on `make check` so main cannot
main cannot regress. regress.
# Completed Steps # Completed Steps
- 2026-09-22: Vendored the canonical prettier/format toolchain from the
`sneak/prompts` scaffold: added `.prettierrc` (tabWidth 4, proseWrap always),
pinned `package.json` + `yarn.lock` (prettier 3.8.1), taught
`script/bootstrap` to install a pinned node/yarn via a hash-verified nvm
archive, and switched `script/fmt` to the pinned prettier reading
`.prettierrc` (no inline flags) over `static/js/*.js` and `**/*.md`. Reflowed
all markdown to house style; `alpine.min.js` stays byte-identical (#203).
- 2026-09-22: Fixed the flaky `t.TempDir` cleanup race in - 2026-09-22: Fixed the flaky `t.TempDir` cleanup race in
`internal/service/webhook` by tracking the async deployment goroutine `internal/service/webhook` by tracking the async deployment goroutine in a
in a `sync.WaitGroup` and exposing `WaitForDeployments`; tests now `sync.WaitGroup` and exposing `WaitForDeployments`; tests now synchronize on
synchronize on completion instead of sleeping (#198). completion instead of sleeping (#198).
- 2026-09-22: Linting now runs only in Docker. Added `Dockerfile.lint` - 2026-09-22: Linting now runs only in Docker. Added `Dockerfile.lint` (pinned
(pinned golangci-lint v2.12.2, cache-busted via a `GATE_RUN` build arg golangci-lint v2.12.2, cache-busted via a `GATE_RUN` build arg so the linter
so the linter always executes), reduced `script/lint` to building it, always executes), reduced `script/lint` to building it, dropped the
dropped the golangci-lint install from `script/bootstrap`, and switched golangci-lint install from `script/bootstrap`, and switched the `Dockerfile`
the `Dockerfile` lint stage to invoke `golangci-lint` directly instead lint stage to invoke `golangci-lint` directly instead of `make lint` to avoid
of `make lint` to avoid docker-in-docker (#188). docker-in-docker (#188).
- 2026-09-22: Added `.prettierignore` so `make fmt` no longer rewrites - 2026-09-22: Added `.prettierignore` so `make fmt` no longer rewrites the
the vendored `static/js/alpine.min.js` bundle (#185). vendored `static/js/alpine.min.js` bundle (#185).
- 2026-09-22: Fixed the gosec G703 path-traversal finding in the deploy - 2026-09-22: Fixed the gosec G703 path-traversal finding in the deploy log
log download handler by verifying the resolved path stays within the download handler by verifying the resolved path stays within the deploy log
deploy log directory before serving, returning 404 on escape (#177). directory before serving, returning 404 on escape (#177).
- 2026-09-22: `script/bootstrap` now installs a pinned `goimports` - 2026-09-22: `script/bootstrap` now installs a pinned `goimports`
(`golang.org/x/tools` v0.49.0) into `/usr/local/bin`, so `make fmt` (`golang.org/x/tools` v0.49.0) into `/usr/local/bin`, so `make fmt` succeeds
succeeds on a fresh machine after `make bootstrap` (#184). on a fresh machine after `make bootstrap` (#184).
- 2026-09-09: Fixed four deployability blockers found by QA: CSRF origin - 2026-09-09: Fixed four deployability blockers found by QA: CSRF origin check
check over plain HTTP (`UPAAS_PLAINTEXT_HTTP`, #189), pulling the git over plain HTTP (`UPAAS_PLAINTEXT_HTTP`, #189), pulling the git image when
image when absent (#190), the env-var editor CSRF token lookup (#191), absent (#190), the env-var editor CSRF token lookup (#191), and the
and the port-mapping delete form's CSRF field (#192). port-mapping delete form's CSRF field (#192).
- 2026-08-07: Updated golangci-lint to v2.12.2 (canonical - 2026-08-07: Updated golangci-lint to v2.12.2 (canonical `.golangci.yml`,
`.golangci.yml`, `Dockerfile` lint stage pin, `script/bootstrap` `Dockerfile` lint stage pin, `script/bootstrap` release-archive pins) and
release-archive pins) and fixed all resulting lint findings (noctx, fixed all resulting lint findings (noctx, gosec, goconst, lll, dupl,
gosec, goconst, lll, dupl, nolintlint); `make check` green. nolintlint); `make check` green.
- 2026-07-07 Adopted scripts-to-rule-them-all: `script/` entrypoints, - 2026-07-07 Adopted scripts-to-rule-them-all: `script/` entrypoints, Makefile
Makefile shims, README Entrypoints section shims, README Entrypoints section
- 2026-03-11: Monolithic env var editing with bulk save (#158). - 2026-03-11: Monolithic env var editing with bulk save (#158).
- 2026-03-10: Webhook event history UI page (#164); added missing - 2026-03-10: Webhook event history UI page (#164); added missing Makefile
Makefile docker and hooks targets plus test timeout (#159); docker and hooks targets plus test timeout (#159); notification settings
notification settings passed from create form (#160). passed from create form (#160).
- 2026-03-03: REPO_POLICIES compliance file set added (#155). - 2026-03-03: REPO_POLICIES compliance file set added (#155).
- 2026-03-01: Module path changed to sneak.berlin/go/upaas (#143); - 2026-03-01: Module path changed to sneak.berlin/go/upaas (#143); Dockerfile
Dockerfile split into lint and build stages with forced lint split into lint and build stages with forced lint execution (#152, #154).
execution (#152, #154).
- 2026-02-26: 1.0.0 tagged; dashboard CSRFField crash fixed (#146). - 2026-02-26: 1.0.0 tagged; dashboard CSRFField crash fixed (#146).
- 1.0 audit bug fixes (#120-#125): deferred rollback on commit error, - 1.0 audit bug fixes (#120-#125): deferred rollback on commit error, deployment
deployment log size cap, error path rendering, docker-compose bind log size cap, error path rendering, docker-compose bind mount, domain type
mount, domain type refactor. refactor.
- CI simplified to docker build only (#130). - CI simplified to docker build only (#130).
- 2025-12-29 onward: core PaaS built out: deploys with real-time build - 2025-12-29 onward: core PaaS built out: deploys with real-time build log
log streaming, container start/stop/restart and logs, TCP/UDP port streaming, container start/stop/restart and logs, TCP/UDP port mapping,
mapping, Alpine.js UI, Slack notifications, ULID app IDs, session Alpine.js UI, Slack notifications, ULID app IDs, session handling.
handling.
# Future Steps # Future Steps
+5
View File
@@ -0,0 +1,5 @@
{
"devDependencies": {
"prettier": "3.8.1"
}
}
+80 -2
View File
@@ -5,8 +5,12 @@
# or apk (detected in that order); assumes NOTHING is present (not git, # or apk (detected in that order); assumes NOTHING is present (not git,
# make, or go). goimports is installed with `go install` at a pinned # make, or go). goimports is installed with `go install` at a pinned
# version (integrity via the Go module checksum database) into # version (integrity via the Go module checksum database) into
# /usr/local/bin so it is on PATH. The linter is not installed here: it # /usr/local/bin so it is on PATH. Node is used directly if installed;
# runs only in Docker via script/lint, so docker is its sole prerequisite. # otherwise it is installed at a pinned version via nvm (installing nvm
# itself first, from a hash-verified release archive, never curl | sh),
# then the pinned prettier from yarn.lock. The linter is not installed
# here: it runs only in Docker via script/lint, so docker is its sole
# prerequisite.
set -eu set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
@@ -15,6 +19,12 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
# golang.org/x/tools goimports, 2026-08-13. v0.49.0 requires Go 1.25 (matches # golang.org/x/tools goimports, 2026-08-13. v0.49.0 requires Go 1.25 (matches
# go.mod); v0.50.0 needs Go 1.26. Integrity via the Go module checksum database. # go.mod); v0.50.0 needs Go 1.26. Integrity via the Go module checksum database.
GOIMPORTS_VERSION="v0.49.0" GOIMPORTS_VERSION="v0.49.0"
# Node/yarn toolchain, 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="" PKGMGR=""
SUDO="" SUDO=""
@@ -56,6 +66,21 @@ missing() {
! command -v "$1" >/dev/null 2>&1 ! command -v "$1" >/dev/null 2>&1
} }
# 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
}
# goimports is not packaged uniformly across nix/apt/brew/apk, so install it # goimports is not packaged uniformly across nix/apt/brew/apk, so install it
# with `go install` at a pinned version and place the binary in /usr/local/bin # with `go install` at a pinned version and place the binary in /usr/local/bin
# so it is on PATH regardless of shell config. Requires go, which main # so it is on PATH regardless of shell config. Requires go, which main
@@ -69,6 +94,54 @@ ensure_goimports() {
rm -rf "$tmp" rm -rf "$tmp"
} }
# 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"
}
ensure_node() {
if ! missing node; then return 0; fi
ensure_nvm
nvm_sh "nvm install $NODE_VERSION"
}
ensure_yarn() {
if ! missing yarn; then return 0; fi
if ! missing corepack; then
corepack enable
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
npm install -g "yarn@$YARN_VERSION"
fi
}
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() { main() {
cd "$ROOT" cd "$ROOT"
@@ -80,6 +153,11 @@ main() {
if missing go; then pkg_install go golang go go; fi if missing go; then pkg_install go golang go go; fi
ensure_goimports ensure_goimports
# Node toolchain and pinned prettier
ensure_node
ensure_yarn
install_js_deps
# The linter runs only in Docker (script/lint). Warn, don't fail: the # The linter runs only in Docker (script/lint). Warn, don't fail: the
# rest of the repo works without it. # rest of the repo works without it.
if missing docker; then if missing docker; then
+24 -1
View File
@@ -4,11 +4,34 @@ set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" 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
yarn "$@"
return
fi
if [ ! -s "$HOME/.nvm/nvm.sh" ]; then
echo "fmt: no yarn; run script/bootstrap first" >&2
exit 1
fi
bash -c '. "$HOME/.nvm/nvm.sh" && nvm use "$1" >/dev/null &&
shift && exec yarn "$@"' bash "$NODE_VERSION" "$@"
}
main() { main() {
cd "$ROOT" cd "$ROOT"
gofmt -s -w . gofmt -s -w .
goimports -w . goimports -w .
npx prettier --write --tab-width 4 static/js/*.js # Pinned prettier reads settings from .prettierrc (tabWidth 4,
# proseWrap always); .prettierignore keeps alpine.min.js untouched.
# Globs are quoted so prettier expands them, not the shell.
run_yarn run prettier --write 'static/js/*.js' '**/*.md'
} }
main "$@" main "$@"
+8
View File
@@ -0,0 +1,8 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
prettier@3.8.1:
version "3.8.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.1.tgz#edf48977cf991558f4fcbd8a3ba6015ba2a3a173"
integrity sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==