feat: resolve a program's pinned node version for build + runtime
Frontend builds triggered from the castle web app run inside the castle-api systemd service, whose PATH omits nvm's versioned node dir — so `pnpm build` died with `node: not found` even though it worked from an interactive shell. Introduce a per-program node convention: a program declares its version the ecosystem-standard way (.node-version / .nvmrc / package.json engines.node), and castle_core.toolchains.resolve_node_bin() maps it to a concrete nvm bin dir (CASTLE_NODE_VERSIONS_DIR, default ~/.nvm/versions/node; newest match wins). The same resolver feeds both sites that run a program's node: - build time: stacks._build_env() prepends the pinned node for the dev-verb subprocess (keyed on the source dir), so `castle program build` uses the program's node regardless of caller. - run time: deploy._build_deployed() stores it in Deployment.path_prepend, which the systemd generator puts ahead of the default unit PATH — so a `launcher: node` service runs its program's node. A pinned-but-uninstalled version fails loud with an `nvm install` hint instead of a cryptic `node: not found`. Unpinned → no injection (no guessing). Also: the systemd generator now honors an explicit PATH in defaults.env as a full override instead of clobbering it with a trailing Environment=PATH line (systemd's last-assignment-wins rule had silently defeated the documented escape hatch). Pins app/.node-version and documents the convention in the react-vite stack. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -59,6 +59,7 @@ from castle_core.registry import (
|
||||
load_registry,
|
||||
save_registry,
|
||||
)
|
||||
from castle_core.toolchains import ToolchainError, resolve_node_bin
|
||||
|
||||
SYSTEMD_USER_DIR = Path.home() / ".config" / "systemd" / "user"
|
||||
|
||||
@@ -695,12 +696,26 @@ def _build_deployed(
|
||||
)
|
||||
stop_cmd = _build_stop_cmd(name, run, source_dir)
|
||||
|
||||
# A program that pins a node version (.node-version/.nvmrc/engines) → that node's
|
||||
# bin dir on the unit PATH, so a `launcher: node` service runs the program's node
|
||||
# (the default tool PATH omits nvm's versioned dirs). Harmless for non-node
|
||||
# programs (no pin → no prepend). Fail-soft: a missing pinned version is a warning,
|
||||
# not an aborted apply — it surfaces again, loudly, when the build/verb runs.
|
||||
path_prepend: list[str] = []
|
||||
try:
|
||||
node_bin = resolve_node_bin(source_dir)
|
||||
if node_bin is not None:
|
||||
path_prepend = [str(node_bin)]
|
||||
except ToolchainError as e:
|
||||
messages.append(f"⚠ {name}: {e}")
|
||||
|
||||
return Deployment(
|
||||
manager="systemd",
|
||||
launcher=run.launcher,
|
||||
run_cmd=run_cmd,
|
||||
stop_cmd=stop_cmd,
|
||||
env=env,
|
||||
path_prepend=path_prepend,
|
||||
secret_env_keys=sorted(secret_env),
|
||||
description=description,
|
||||
kind=kind,
|
||||
|
||||
Reference in New Issue
Block a user