From 7bf35d8010fdc1c54757c49536259b721bc51c55 Mon Sep 17 00:00:00 2001 From: frami Date: Mon, 25 May 2026 23:53:40 -0300 Subject: [PATCH] feat: shared --- src/node/NOTES.md | 6 ++++- src/node/README.md | 8 ++++-- src/node/devcontainer-feature.json | 2 +- src/node/install.sh | 41 ++++++++++++++++++------------ 4 files changed, 37 insertions(+), 20 deletions(-) diff --git a/src/node/NOTES.md b/src/node/NOTES.md index e43f79c..0fbc080 100644 --- a/src/node/NOTES.md +++ b/src/node/NOTES.md @@ -7,7 +7,11 @@ Debian/Ubuntu-based images. The feature does **not** call `apt-get` itself — i - **Node.js** is the official Linux prebuilt tarball from `https://nodejs.org/dist/`, extracted into `/usr/local` with `tar --strip-components=1 --exclude=CHANGELOG.md --exclude=LICENSE --exclude=README.md` so that `node`, `npm`, and `npx` land directly under `/usr/local/bin` — already on every shell's `PATH`. No NVM, no NodeSource apt repo, no shell-rc edits. - **Version resolution** is done against the official `https://nodejs.org/dist/index.json` index. Accepted forms for the `version` option: `lts`, `latest`/`current`, `lts/` (e.g. `lts/krypton`), `` (e.g. `24`), `.`, or an exact version with or without a leading `v`. - **npm** is pinned with `npm install -g npm@` into the Node tarball's `/usr/local` prefix, overwriting `/usr/local/bin/npm`. The default `latest` always installs the newest published npm. -- **pnpm** is installed with `npm install -g pnpm@` (no `get.pnpm.io` script, no Corepack indirection). It lands in `NPM_CONFIG_PREFIX=/usr/local/share/npm-global` — a dedicated dir for npm globals so user-installed packages don't mix with the Node tarball's `/usr/local/bin` — and is symlinked at `/usr/local/bin/pnpm` so it's reachable even before `/etc/profile.d/node.sh` is sourced. The install script writes `/etc/profile.d/node.sh` exporting `NPM_CONFIG_PREFIX`, `PNPM_HOME=/usr/local/share/pnpm` (where pnpm itself stores globally-installed packages on later `pnpm add -g …`), and prepending both `${NPM_CONFIG_PREFIX}/bin` and `${PNPM_HOME}` to `PATH`. `/etc/profile` is sourced by login shells, and `common-utils:2` (a prerequisite) also wires `/etc/bash.bashrc` to source `/etc/profile.d/*.sh`, so the binaries are reachable from both login and interactive non-login shells. +- **pnpm** is installed with `npm install -g pnpm@` (no `get.pnpm.io` script, no Corepack indirection). It lands in `NPM_CONFIG_PREFIX` — a dedicated dir for npm globals so user-installed packages don't mix with the Node tarball's `/usr/local/bin` — and is symlinked at `/usr/local/bin/pnpm` so it's reachable even before `/etc/profile.d/node.sh` is sourced. The install script writes `/etc/profile.d/node.sh` exporting `NPM_CONFIG_PREFIX`, `PNPM_HOME` (where pnpm itself stores globally-installed packages on later `pnpm add -g …`), and prepending both `${NPM_CONFIG_PREFIX}/bin` and `${PNPM_HOME}` to `PATH`. `/etc/profile` is sourced by login shells, and `common-utils:2` (a prerequisite) also wires `/etc/bash.bashrc` to source `/etc/profile.d/*.sh`, so the binaries are reachable from both login and interactive non-login shells. +- **Location of `NPM_CONFIG_PREFIX` / `PNPM_HOME`** depends on whether a non-root remote user is configured: + - **Non-root `remoteUser`** (e.g. `vscode`): `${_REMOTE_USER_HOME}/.local/share/npm-global` and `${_REMOTE_USER_HOME}/.local/share/pnpm`. Keeping them under the user's `HOME` lets non-root `pnpm install` write the SQLite store index (WAL files) without "readonly database" errors, and survives layer caching in a user-owned tree. + - **No remote user or `root`**: falls back to `/usr/local/share/npm-global` and `/usr/local/share/pnpm`. + Both paths are baked into `/etc/profile.d/node.sh` at install time and chowned to `_REMOTE_USER` when set. ## Feature ordering diff --git a/src/node/README.md b/src/node/README.md index 5aebcf5..be20bb6 100644 --- a/src/node/README.md +++ b/src/node/README.md @@ -17,7 +17,7 @@ Installs Node.js from the official nodejs.org prebuilt binaries, with optional n |-----|-----|-----|-----| | version | Node.js version. 'lts' (default), 'latest'/'current', an lts codename ('lts/krypton'), a major ('24'), a major.minor ('24.16'), or an exact version ('24.16.0' / 'v24.16.0'). | string | lts | | npmVersion | npm version (passed to 'npm install -g npm@'). Use 'latest' for the newest, or pin (e.g. '11.15.0'). | string | latest | -| pnpmVersion | pnpm version (passed to 'npm install -g pnpm@'). Installed into NPM_CONFIG_PREFIX=/usr/local/share/npm-global, which is added to PATH via /etc/profile.d/node.sh. Use 'latest' for the newest, or pin (e.g. '11.2.2'). | string | latest | +| pnpmVersion | pnpm version (passed to 'npm install -g pnpm@'). Installed into NPM_CONFIG_PREFIX (defaults to `${_REMOTE_USER_HOME}/.local/share/npm-global` when a non-root remote user is configured, otherwise `/usr/local/share/npm-global`), which is added to PATH via /etc/profile.d/node.sh. Use 'latest' for the newest, or pin (e.g. '11.2.2'). | string | latest | ## OS support @@ -28,7 +28,11 @@ Debian/Ubuntu-based images. The feature does **not** call `apt-get` itself — i - **Node.js** is the official Linux prebuilt tarball from `https://nodejs.org/dist/`, extracted into `/usr/local` with `tar --strip-components=1 --exclude=CHANGELOG.md --exclude=LICENSE --exclude=README.md` so that `node`, `npm`, and `npx` land directly under `/usr/local/bin` — already on every shell's `PATH`. No NVM, no NodeSource apt repo, no shell-rc edits. - **Version resolution** is done against the official `https://nodejs.org/dist/index.json` index. Accepted forms for the `version` option: `lts`, `latest`/`current`, `lts/` (e.g. `lts/krypton`), `` (e.g. `24`), `.`, or an exact version with or without a leading `v`. - **npm** is pinned with `npm install -g npm@` into the Node tarball's `/usr/local` prefix, overwriting `/usr/local/bin/npm`. The default `latest` always installs the newest published npm. -- **pnpm** is installed with `npm install -g pnpm@` (no `get.pnpm.io` script, no Corepack indirection). It lands in `NPM_CONFIG_PREFIX=/usr/local/share/npm-global` — a dedicated dir for npm globals so user-installed packages don't mix with the Node tarball's `/usr/local/bin` — and is symlinked at `/usr/local/bin/pnpm` so it's reachable even before `/etc/profile.d/node.sh` is sourced. The install script writes `/etc/profile.d/node.sh` exporting `NPM_CONFIG_PREFIX`, `PNPM_HOME=/usr/local/share/pnpm` (where pnpm itself stores globally-installed packages on later `pnpm add -g …`), and prepending both `${NPM_CONFIG_PREFIX}/bin` and `${PNPM_HOME}` to `PATH`. `/etc/profile` is sourced by login shells, and `common-utils:2` (a prerequisite) also wires `/etc/bash.bashrc` to source `/etc/profile.d/*.sh`, so the binaries are reachable from both login and interactive non-login shells. +- **pnpm** is installed with `npm install -g pnpm@` (no `get.pnpm.io` script, no Corepack indirection). It lands in `NPM_CONFIG_PREFIX` — a dedicated dir for npm globals so user-installed packages don't mix with the Node tarball's `/usr/local/bin` — and is symlinked at `/usr/local/bin/pnpm` so it's reachable even before `/etc/profile.d/node.sh` is sourced. The install script writes `/etc/profile.d/node.sh` exporting `NPM_CONFIG_PREFIX`, `PNPM_HOME` (where pnpm itself stores globally-installed packages on later `pnpm add -g …`), and prepending both `${NPM_CONFIG_PREFIX}/bin` and `${PNPM_HOME}` to `PATH`. `/etc/profile` is sourced by login shells, and `common-utils:2` (a prerequisite) also wires `/etc/bash.bashrc` to source `/etc/profile.d/*.sh`, so the binaries are reachable from both login and interactive non-login shells. +- **Location of `NPM_CONFIG_PREFIX` / `PNPM_HOME`** depends on whether a non-root remote user is configured: + - **Non-root `remoteUser`** (e.g. `vscode`): `${_REMOTE_USER_HOME}/.local/share/npm-global` and `${_REMOTE_USER_HOME}/.local/share/pnpm`. Keeping them under the user's `HOME` lets non-root `pnpm install` write the SQLite store index (WAL files) without "readonly database" errors, and survives layer caching in a user-owned tree. + - **No remote user or `root`**: falls back to `/usr/local/share/npm-global` and `/usr/local/share/pnpm`. + Both paths are baked into `/etc/profile.d/node.sh` at install time and chowned to `_REMOTE_USER` when set. ## Feature ordering diff --git a/src/node/devcontainer-feature.json b/src/node/devcontainer-feature.json index 663b6f5..4e91344 100644 --- a/src/node/devcontainer-feature.json +++ b/src/node/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "node", - "version": "1.0.1", + "version": "1.0.2", "name": "Node.js", "description": "Installs Node.js from the official nodejs.org prebuilt binaries, with optional npm and pnpm pinning. Assumes 'ghcr.io/devcontainers/features/common-utils:2' has run.", "documentationURL": "https://github.com/Synergy-Shock/devcontainer-features/tree/main/src/node", diff --git a/src/node/install.sh b/src/node/install.sh index 893e84e..15bf69f 100755 --- a/src/node/install.sh +++ b/src/node/install.sh @@ -130,13 +130,26 @@ npm --version # ------------------------------------------------------------------ # Install pnpm via npm. From here on, npm globals live in a dedicated -# directory exposed on PATH via containerEnv, keeping user-installed -# globals separate from the Node tarball's /usr/local/bin. +# directory exposed on PATH via /etc/profile.d/node.sh, keeping +# user-installed globals separate from the Node tarball's /usr/local/bin. +# +# When a non-root remote user is configured, the dirs live under their HOME +# (XDG-style) so non-root `pnpm install` can write the SQLite store index +# (WAL files) without "readonly database". Otherwise they land system-wide +# under /usr/local/share. # ------------------------------------------------------------------ -export NPM_CONFIG_PREFIX="/usr/local/share/npm-global" -mkdir -p "${NPM_CONFIG_PREFIX}/bin" +if [ -n "${_REMOTE_USER:-}" ] && [ "${_REMOTE_USER}" != "root" ]; then + REMOTE_USER_HOME="${_REMOTE_USER_HOME:-/home/${_REMOTE_USER}}" + NPM_CONFIG_PREFIX="${REMOTE_USER_HOME}/.local/share/npm-global" + PNPM_HOME="${REMOTE_USER_HOME}/.local/share/pnpm" +else + NPM_CONFIG_PREFIX="/usr/local/share/npm-global" + PNPM_HOME="/usr/local/share/pnpm" +fi +export NPM_CONFIG_PREFIX PNPM_HOME +mkdir -p "${NPM_CONFIG_PREFIX}/bin" "${PNPM_HOME}" -echo "==> Installing pnpm@${PNPM_VERSION_OPT} via npm..." +echo "==> Installing pnpm@${PNPM_VERSION_OPT} via npm into ${NPM_CONFIG_PREFIX}..." # Accept both '10.0.0' and 'v10.0.0' — npm semver wants no leading 'v'. PNPM_SPEC="${PNPM_VERSION_OPT#v}" npm install -g --no-fund --no-audit "pnpm@${PNPM_SPEC}" @@ -146,11 +159,6 @@ npm install -g --no-fund --no-audit "pnpm@${PNPM_SPEC}" # symlink that the Node tarball ships). ln -sf "${NPM_CONFIG_PREFIX}/bin/pnpm" /usr/local/bin/pnpm -# PNPM_HOME is where pnpm stores globally-installed packages (populated -# when users later run `pnpm add -g …`). containerEnv puts it on PATH. -export PNPM_HOME="/usr/local/share/pnpm" -mkdir -p "${PNPM_HOME}" - # Make pnpm reachable for the verification call below; persisted to future # shells via /etc/profile.d/node.sh, written next. export PATH="${NPM_CONFIG_PREFIX}/bin:${PNPM_HOME}:${PATH}" @@ -158,18 +166,19 @@ pnpm --version # Hand the global npm/pnpm trees to the remote user so non-root pnpm operations # can write SQLite state (store index, WAL files) without "readonly database". -chown -R "${_REMOTE_USER}:${_REMOTE_USER}" "${NPM_CONFIG_PREFIX}" -chown -R "${_REMOTE_USER}:${_REMOTE_USER}" "${PNPM_HOME}" +if [ -n "${_REMOTE_USER:-}" ] && [ "${_REMOTE_USER}" != "root" ]; then + chown -R "${_REMOTE_USER}:${_REMOTE_USER}" "${NPM_CONFIG_PREFIX}" "${PNPM_HOME}" +fi # ------------------------------------------------------------------ # Persist env vars and PATH additions for future shells. # /etc/profile is sourced by login shells; common-utils:2 (a prerequisite) # also wires /etc/bash.bashrc to source /etc/profile.d/*.sh. # ------------------------------------------------------------------ -cat > /etc/profile.d/node.sh <<'EOF' -export NPM_CONFIG_PREFIX="/usr/local/share/npm-global" -export PNPM_HOME="/usr/local/share/pnpm" -export PATH="${NPM_CONFIG_PREFIX}/bin:${PNPM_HOME}:${PATH}" +cat > /etc/profile.d/node.sh <