Files
compiler-explorer/Makefile
Matt Godbolt (bot acct) eee2e8bfd9 Improve node discovery: version-manager aware, single source of truth (#8845)
## What

Reworks the bespoke `etc/scripts/find-node` and its `Makefile`
interaction so node discovery works out of the box on Linux and macOS,
is optionally friendly to version managers, and stays sympathetic to
people who just use system node.

### Before
- Only ever looked at `PATH`, plus a vestigial
`/opt/compiler-explorer/node` hardcode (an affordance for one dev's
local setup, unused by the live site).
- Duplicated the required node version in three places (`find-node` had
`22` twice; `.node-version`; `package.json` `engines`).
- Silently fell back *past* an unusable `NODE_DIR` to whatever else it
found.
- nvm users got whatever default was active, not the version this repo
pins.
- Nagged on any version that wasn't exactly `22.x`, even newer ones.

### After — clear, permissive resolution order
1. **`$NODE_DIR/bin/node`** — explicit override, now authoritative. If
it's set but missing/too old you get a clear error instead of a silent
fallback.
2. **`node`/`nodejs` on `PATH`** — covers system node *and* any version
manager with shell integration (fnm, asdf, nodenv, volta, an active
nvm). Zero extra steps on a well-configured machine.
3. **Manager rescue** — only when PATH node is absent or below the
minimum: source `nvm` and ask for the pinned version (the one manager
that's a shell function rather than a PATH binary), then try
`fnm`/`nodenv`/`asdf`. Each branch is inert unless that tool is
installed, so nobody is pushed onto a manager.

Other changes:
- **Single source of truth**: the minimum major now comes solely from
`.node-version` (the same file the managers read). Newer majors are
always fine — the "only tested against v22.x" warning is dropped.
- **`Makefile`**: `.node-bin` now also depends on `.node-version`, so
bumping the pin re-resolves node instead of serving a stale cache. The
lazy-dotfile pattern (so failures abort `make` and `make help` pays
nothing) is kept.
- **`README`**: documents the PATH-first + manager-rescue behaviour.

## Testing
- `shellcheck` clean.
- Verified: PATH discovery, valid/bogus `NODE_DIR`, nvm rescue (shadowed
`node`+`nodejs` with v18 shims → skipped them and resolved v22.22 via
nvm), too-old fallthrough, no-node/no-manager failure message, and `make
info` end-to-end.
- `fnm` branch is logically straightforward but was not exercised (no
fnm on the test box) — worth a smoke test if you have one.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: mattgodbolt-molty <mattgodbolt-molty@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Matt Godbolt <matt@godbolt.org>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-02 19:50:58 +01:00

127 lines
4.7 KiB
Makefile

default: run
help: # with thanks to Ben Rady
@grep -E '^[0-9a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
# If you see "node-not-found" then you need to depend on node-installed.
NODE:=node-not-found
NPM:=npm-not-found
NODE_MODULES:=./node_modules/.npm-updated
NODE_ARGS?=
TS_NODE_ARGS:=--no-warnings=ExperimentalWarning --import=tsx
# All file paths that are not .ts files that should be watched for changes if launched with watch.
FILEWATCHER_ARGS:=--watch --include "etc/config/*"
# find-node caches its result in .node-bin.
# Doing it this way instead of NODE:=$(shell etc/scripts/find-node) means
# if it fails, it stops the make process. As best I can tell there's no
# way to get make to fail if a sub-shell command fails.
# Depending on .node-version means bumping the pinned version re-resolves node;
# if you switch node behind make's back, `rm .node-bin` (or `make clean`).
.node-bin: etc/scripts/find-node .node-version
@etc/scripts/find-node .node-bin
# All targets that need node must depend on this to ensure the NODE variable
# is appropriately set, and that PATH is updated.
.PHONY: node-installed
node-installed: .node-bin
@$(eval NODE:=$(shell cat .node-bin))
@$(eval NPM:=$(shell dirname $(shell cat .node-bin))/npm)
@$(eval PATH=$(shell dirname $(realpath $(NODE))):$(PATH))
.PHONY: info
info: node-installed ## print out some useful variables
@echo Using node from $(NODE)
@echo Using npm from $(NPM)
@echo PATH is $(PATH)
# disassemblers are needed for local deploys: #4225
.PHONY: scripts
scripts:
mkdir -p out/dist/etc/scripts/disasms
rsync -r -u etc/scripts/disasms/* out/dist/etc/scripts/disasms
.PHONY: prereqs
prereqs: $(NODE_MODULES)
$(NODE_MODULES): package.json package-lock.json | node-installed
$(NPM) clean-install $(NPM_FLAGS)
@rm -rf node_modules/.cache/esm/*
@touch $@
.PHONY: lint
lint: $(NODE_MODULES) ## Checks if the source currently matches code conventions
$(NPM) run ts-check
$(NPM) run lint-check
.PHONY: lint-fix
lint-fix: $(NODE_MODULES) ## Checks if everything matches code conventions & fixes those which are trivial to do so
$(NPM) run lint
.PHONY: test
test: $(NODE_MODULES) ## Runs the tests
$(NPM) run test
@echo Tests pass
.PHONY: test-min
test-min: $(NODE_MODULES) ## Runs the minimal tests
$(NPM) run test-min
@echo Tests pass
.PHONY: check
check: $(NODE_MODULES) lint test ## Runs all checks required before committing (fixing trivial things automatically)
.PHONY: check-frontend-imports
check-frontend-imports: node-installed ## Check that frontend doesn't import from backend
@$(NODE) ./etc/scripts/check-frontend-imports.js
.PHONY: check-license-headers
check-license-headers: node-installed ## Check that every source file has the license banner
@$(NODE) ./etc/scripts/check-license-headers.js
.PHONY: pre-commit
pre-commit: $(NODE_MODULES) test-min lint check-frontend-imports check-license-headers
.PHONY: clean
clean: ## Cleans up everything
rm -rf node_modules .*-updated .*-bin out
.PHONY: prebuild
prebuild: prereqs scripts
$(NPM) run webpack
$(NPM) run ts-compile
.PHONY: run-only
run-only: node-installed ## Runs the site like it runs in production without building it
env NODE_ENV=production $(NODE) $(NODE_ARGS) ./out/dist/app.js --static ./out/webpack/static $(EXTRA_ARGS)
.PHONY: run
run: ## Runs the site like it runs in production
$(MAKE) prebuild
$(MAKE) run-only
.PHONY: dev
dev: prereqs ## Runs the site as a developer; including live reload support and installation of git hooks
NODE_OPTIONS="$(TS_NODE_ARGS) $(NODE_ARGS)" ./node_modules/.bin/tsx watch $(FILEWATCHER_ARGS) ./app.ts $(EXTRA_ARGS)
.PHONY: gpu-dev
gpu-dev: prereqs ## Runs the site as a developer; including live reload support and installation of git hooks
NODE_OPTIONS="$(TS_NODE_ARGS) $(NODE_ARGS)" ./node_modules/.bin/tsx watch $(FILEWATCHER_ARGS) ./app.ts --env gpu $(EXTRA_ARGS)
.PHONY: debug
debug: prereqs ## Runs the site as a developer with full debugging; including live reload support and installation of git hooks
NODE_OPTIONS="$(TS_NODE_ARGS) $(NODE_ARGS) --inspect 9229" ./node_modules/.bin/tsx watch $(FILEWATCHER_ARGS) ./app.ts --debug $(EXTRA_ARGS)
.PHONY:
asm-docs:
$(MAKE) -C etc/scripts/docenizers || ( \
echo "==============================================================================="; \
echo "One of the docenizers failed to run. The dependencies are managed by uv and"; \
echo "should be automatically installed. If you see this error, please check that:"; \
echo " 1. uv is available (it will be auto-installed if not)"; \
echo " 2. npm install has been run for the TypeScript docenizers"; \
echo "==============================================================================="; \
exit 1 \
)