From 1dfff391403a0fbd12272e20e9cac5b7bf37c163 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Sun, 8 Feb 2026 00:28:59 +0530 Subject: [PATCH] feat: add node wrapper script and update Makefile for frontend tooling --- Makefile | 41 +++++++----- scripts/with_node.sh | 155 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 180 insertions(+), 16 deletions(-) create mode 100644 scripts/with_node.sh diff --git a/Makefile b/Makefile index da1a24d1..6441b6d6 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,8 @@ SHELL := /usr/bin/env bash BACKEND_DIR := backend FRONTEND_DIR := frontend +NODE_WRAP := bash scripts/with_node.sh + .PHONY: help help: ## Show available targets @grep -E '^[a-zA-Z0-9_.-]+:.*?## ' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*## "}; {printf " %-26s %s\n", $$1, $$2}' @@ -13,13 +15,20 @@ help: ## Show available targets .PHONY: setup setup: backend-sync frontend-sync ## Install/sync backend + frontend deps +.PHONY: all +all: setup format check ## Run everything (deps + format + CI-equivalent checks) + .PHONY: backend-sync backend-sync: ## uv sync backend deps (includes dev extra) cd $(BACKEND_DIR) && uv sync --extra dev +.PHONY: frontend-tooling +frontend-tooling: ## Verify frontend toolchain (node + npm) + @$(NODE_WRAP) --check + .PHONY: frontend-sync -frontend-sync: ## npm install frontend deps - cd $(FRONTEND_DIR) && npm install +frontend-sync: frontend-tooling ## npm install frontend deps + $(NODE_WRAP) --cwd $(FRONTEND_DIR) npm install .PHONY: format format: backend-format frontend-format ## Format backend + frontend @@ -30,8 +39,8 @@ backend-format: ## Format backend (isort + black) cd $(BACKEND_DIR) && uv run black . .PHONY: frontend-format -frontend-format: ## Format frontend (prettier) - cd $(FRONTEND_DIR) && npx prettier --write "src/**/*.{ts,tsx,js,jsx,json,css,md}" "*.{ts,js,json,md,mdx}" +frontend-format: frontend-tooling ## Format frontend (prettier) + $(NODE_WRAP) --cwd $(FRONTEND_DIR) npx prettier --write "src/**/*.{ts,tsx,js,jsx,json,css,md}" "*.{ts,js,json,md,mdx}" .PHONY: format-check format-check: backend-format-check frontend-format-check ## Check formatting (no changes) @@ -42,8 +51,8 @@ backend-format-check: ## Check backend formatting (isort + black) cd $(BACKEND_DIR) && uv run black . --check --diff .PHONY: frontend-format-check -frontend-format-check: ## Check frontend formatting (prettier) - cd $(FRONTEND_DIR) && npx prettier --check "src/**/*.{ts,tsx,js,jsx,json,css,md}" "*.{ts,js,json,md,mdx}" +frontend-format-check: frontend-tooling ## Check frontend formatting (prettier) + $(NODE_WRAP) --cwd $(FRONTEND_DIR) npx prettier --check "src/**/*.{ts,tsx,js,jsx,json,css,md}" "*.{ts,js,json,md,mdx}" .PHONY: lint lint: backend-lint frontend-lint ## Lint backend + frontend @@ -53,8 +62,8 @@ backend-lint: ## Lint backend (flake8) cd $(BACKEND_DIR) && uv run flake8 --config .flake8 .PHONY: frontend-lint -frontend-lint: ## Lint frontend (eslint) - cd $(FRONTEND_DIR) && npm run lint +frontend-lint: frontend-tooling ## Lint frontend (eslint) + $(NODE_WRAP) --cwd $(FRONTEND_DIR) npm run lint .PHONY: typecheck typecheck: backend-typecheck frontend-typecheck ## Typecheck backend + frontend @@ -64,8 +73,8 @@ backend-typecheck: ## Typecheck backend (mypy --strict) cd $(BACKEND_DIR) && uv run mypy .PHONY: frontend-typecheck -frontend-typecheck: ## Typecheck frontend (tsc) - cd $(FRONTEND_DIR) && npx tsc -p tsconfig.json --noEmit +frontend-typecheck: frontend-tooling ## Typecheck frontend (tsc) + $(NODE_WRAP) --cwd $(FRONTEND_DIR) npx tsc -p tsconfig.json --noEmit .PHONY: test test: backend-test frontend-test ## Run tests @@ -88,8 +97,8 @@ backend-coverage: ## Backend tests with coverage gate (scoped 100% stmt+branch o --cov-fail-under=100 .PHONY: frontend-test -frontend-test: ## Frontend tests (vitest) - cd $(FRONTEND_DIR) && npm run test +frontend-test: frontend-tooling ## Frontend tests (vitest) + $(NODE_WRAP) --cwd $(FRONTEND_DIR) npm run test .PHONY: backend-migrate backend-migrate: ## Apply backend DB migrations (alembic upgrade head) @@ -99,12 +108,12 @@ backend-migrate: ## Apply backend DB migrations (alembic upgrade head) build: frontend-build ## Build artifacts .PHONY: frontend-build -frontend-build: ## Build frontend (next build) - cd $(FRONTEND_DIR) && npm run build +frontend-build: frontend-tooling ## Build frontend (next build) + $(NODE_WRAP) --cwd $(FRONTEND_DIR) npm run build .PHONY: api-gen -api-gen: ## Regenerate TS API client (requires backend running at 127.0.0.1:8000) - cd $(FRONTEND_DIR) && npm run api:gen +api-gen: frontend-tooling ## Regenerate TS API client (requires backend running at 127.0.0.1:8000) + $(NODE_WRAP) --cwd $(FRONTEND_DIR) npm run api:gen .PHONY: backend-templates-sync backend-templates-sync: ## Sync templates to existing gateway agents (usage: make backend-templates-sync GATEWAY_ID= SYNC_ARGS="--reset-sessions") diff --git a/scripts/with_node.sh b/scripts/with_node.sh new file mode 100644 index 00000000..4974d23e --- /dev/null +++ b/scripts/with_node.sh @@ -0,0 +1,155 @@ +#!/usr/bin/env bash + +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: + with_node.sh [--check] [--cwd DIR] [--] [args...] + +Ensures node/npm/npx are available (optionally via nvm) before running a command. + +Options: + --check Only verify node/npm/npx are available (no command required). + --cwd DIR Change to DIR before running. + -h, --help Show help. +EOF +} + +CHECK_ONLY="false" +CWD="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --check) + CHECK_ONLY="true" + shift + ;; + --cwd) + CWD="${2:-}" + shift 2 + ;; + --) + shift + break + ;; + -h|--help) + usage + exit 0 + ;; + *) + break + ;; + esac +done + +if [[ -n "$CWD" ]]; then + : # handled after we resolve repo root from this script's location +fi + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" +REPO_ROOT="$(cd -- "$SCRIPT_DIR/.." && pwd -P)" + +if [[ -n "$CWD" ]]; then + cd "$CWD" +fi + +read_nvmrc() { + local path="$1" + if [[ -f "$path" ]]; then + command tr -d ' \t\r\n' <"$path" || true + fi +} + +version_greater() { + # Returns 0 (true) if $1 > $2 for simple semver-ish values like "v22.21.1". + local v1="${1#v}" + local v2="${2#v}" + local a1 b1 c1 a2 b2 c2 + IFS=. read -r a1 b1 c1 <<<"$v1" + IFS=. read -r a2 b2 c2 <<<"$v2" + a1="${a1:-0}"; b1="${b1:-0}"; c1="${c1:-0}" + a2="${a2:-0}"; b2="${b2:-0}"; c2="${c2:-0}" + if ((a1 != a2)); then ((a1 > a2)); return; fi + if ((b1 != b2)); then ((b1 > b2)); return; fi + ((c1 > c2)) +} + +bootstrap_nvm_if_needed() { + if command -v node >/dev/null 2>&1 && command -v npm >/dev/null 2>&1 && command -v npx >/dev/null 2>&1; then + return 0 + fi + + local nvm_dir="${NVM_DIR:-$HOME/.nvm}" + if [[ ! -s "$nvm_dir/nvm.sh" ]]; then + return 0 + fi + + # nvm is not guaranteed to be safe under `set -u`. + set +u + # shellcheck disable=SC1090 + . "$nvm_dir/nvm.sh" + + local version="" + version="$(read_nvmrc "$REPO_ROOT/.nvmrc")" + if [[ -z "$version" ]]; then + version="$(read_nvmrc "$REPO_ROOT/frontend/.nvmrc")" + fi + + if [[ -n "$version" ]]; then + nvm use --silent "$version" >/dev/null 2>&1 || true + else + # Prefer a user-defined nvm default, otherwise pick the newest installed version. + nvm use --silent default >/dev/null 2>&1 || true + if ! command -v node >/dev/null 2>&1; then + local versions_dir="$nvm_dir/versions/node" + if [[ -d "$versions_dir" ]]; then + local latest="" + local candidate="" + for candidate in "$versions_dir"/*; do + [[ -d "$candidate" ]] || continue + candidate="$(basename "$candidate")" + [[ "$candidate" =~ ^v?[0-9]+(\\.[0-9]+){0,2}$ ]] || continue + if [[ -z "$latest" ]] || version_greater "$candidate" "$latest"; then + latest="$candidate" + fi + done + [[ -n "$latest" ]] && nvm use --silent "$latest" >/dev/null 2>&1 || true + fi + fi + fi + + set -u +} + +bootstrap_nvm_if_needed + +if ! command -v node >/dev/null 2>&1; then + echo "ERROR: node is required to run frontend targets." >&2 + echo "Install Node.js or make it available via nvm (set NVM_DIR and ensure \$NVM_DIR/nvm.sh exists)." >&2 + echo "Tip: add a project .nvmrc or set an nvm default alias (e.g. 'nvm alias default ')." >&2 + exit 127 +fi + +if ! command -v npm >/dev/null 2>&1; then + echo "ERROR: npm is required to run frontend targets." >&2 + echo "Install Node.js (includes npm/npx) or ensure your nvm-selected Node provides npm." >&2 + exit 127 +fi + +if ! command -v npx >/dev/null 2>&1; then + echo "ERROR: npx is required to run frontend targets (usually installed with npm)." >&2 + echo "Install Node.js (includes npm/npx) or ensure your npm install includes npx." >&2 + exit 127 +fi + +if [[ "$CHECK_ONLY" == "true" ]]; then + exit 0 +fi + +if [[ $# -lt 1 ]]; then + usage >&2 + exit 2 +fi + +exec "$@"