Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/node/NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<codename>` (e.g. `lts/krypton`), `<major>` (e.g. `24`), `<major>.<minor>`, or an exact version with or without a leading `v`.
- **npm** is pinned with `npm install -g npm@<version>` 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@<version>` (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@<version>` (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

Expand Down
8 changes: 6 additions & 2 deletions src/node/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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@<value>'). Use 'latest' for the newest, or pin (e.g. '11.15.0'). | string | latest |
| pnpmVersion | pnpm version (passed to 'npm install -g pnpm@<value>'). 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@<value>'). 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

Expand All @@ -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/<codename>` (e.g. `lts/krypton`), `<major>` (e.g. `24`), `<major>.<minor>`, or an exact version with or without a leading `v`.
- **npm** is pinned with `npm install -g npm@<version>` 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@<version>` (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@<version>` (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

Expand Down
2 changes: 1 addition & 1 deletion src/node/devcontainer-feature.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
41 changes: 25 additions & 16 deletions src/node/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand All @@ -146,30 +159,26 @@ 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}"
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 <<EOF
export NPM_CONFIG_PREFIX="${NPM_CONFIG_PREFIX}"
export PNPM_HOME="${PNPM_HOME}"
export PATH="\${NPM_CONFIG_PREFIX}/bin:\${PNPM_HOME}:\${PATH}"
EOF
chmod 0644 /etc/profile.d/node.sh

Expand Down