rstudio-update-nodejs

Use when updating Node.js versions in the RStudio repository, e.g. bumping to a new release. Handles both build-time Node (RSTUDIO_NODE_VERSION, used for building Electron/GWT) and installed Node (RST

SKILL.md

GitHub

Update Node.js Versions

Updates the pinned Node.js version(s) across the RStudio codebase, uploads binaries to S3, verifies the install, and opens a PR.

RStudio maintains two distinct Node.js versions:

  • Build-time Node (RSTUDIO_NODE_VERSION) — used for building Electron, GWT, and other product components. Not shipped to users.
  • Installed Node (RSTUDIO_INSTALLED_NODE_VERSION) — stripped-down node binary shipped with the product, used by GitHub Copilot and Posit Assistant.

These versions can differ. The skill handles updating either or both in a single invocation.

Arguments

The user specifies which version(s) to update:

  • Build-time only: e.g. "update build node to 22.14.0"
  • Installed only: e.g. "update installed node to 22.23.0"
  • Both: e.g. "update both node versions to 22.14.0" or "update build node to 22.14.0 and installed to 22.23.0"

Each version is a semver string like 22.14.0 (no v prefix).

Steps

1. Verify starting branch

Before doing anything else, confirm the current branch is main. This skill creates a new feature branch off the current branch, so starting off anything other than main would base the work on the wrong commit.

git branch --show-current

If the output is not main, stop immediately and warn the user that they must switch to main (and pull the latest) before re-running this skill. Do not proceed with any further steps.

2. Validate the version(s)

Confirm each provided version matches the format X.Y.Z (digits and dots only). If any version doesn't match, ask the user to correct it before proceeding.

3. Check prerequisites

Before modifying any files, verify that AWS credentials and required tools are in place. The upload script needs working AWS access, and discovering a credential problem after editing files wastes effort.

Run these checks and stop if any fail:

command -v aws >/dev/null 2>&1 && echo "aws: ok" || echo "aws: MISSING"
command -v wget >/dev/null 2>&1 && echo "wget: ok" || echo "wget: MISSING"
aws sts get-caller-identity

If aws or wget is missing, tell the user to install it. If aws sts get-caller-identity fails, tell the user to configure AWS credentials (e.g. aws sso login or aws configure sso if SSO isn't set up yet) and try again.

4. Create a branch

Use the following naming convention:

  • Build-time only: feature/update-node-build-<VERSION>
  • Installed only: feature/update-node-installed-<VERSION>
  • Both, same version: feature/update-node-<VERSION>
  • Both, different versions: feature/update-node-build-<V1>-installed-<V2>
git checkout -b <BRANCH_NAME>

5. Update version strings

Each file uses a different syntax — match the existing pattern exactly. Only update files for the version(s) being changed.

Before editing, note the current values of RSTUDIO_NODE_VERSION and RSTUDIO_INSTALLED_NODE_VERSION from cmake/globals.cmake so you can find-and-replace accurately.

5a. Build-time Node files (only if updating build)

cmake/globals.cmake — CMake cache variable:

set(RSTUDIO_NODE_VERSION "<VERSION>" CACHE INTERNAL "Node version for building")

dependencies/tools/rstudio-tools.sh — bash export with quotes:

export RSTUDIO_NODE_VERSION="<VERSION>"

dependencies/tools/rstudio-tools.cmd — Windows batch, no quotes:

set RSTUDIO_NODE_VERSION=<VERSION>

src/gwt/build.xml — XML property attribute:

<property name="node.version" value="<VERSION>"/>

src/node/desktop/envvars.sh — two hardcoded paths, one with -arm64 suffix:

NODE_PATH=$(readlink -fn ../../../dependencies/common/node/<VERSION>-arm64/bin)
NODE_PATH=$(realpath ../../../dependencies/common/node/<VERSION>/bin)

src/node/CMakeNodeTools.txt — CMake fallback default:

set(RSTUDIO_NODE_VERSION "<VERSION>")

e2e/rstudio/scripts/dev-common.ts — TypeScript constant used by the dev Playwright runners to pin node on PATH:

const PINNED_NODE_VERSION = '<VERSION>';

src/node/desktop/.vscode/settings.json — JSON string with backslash path separators:

"PATH": "${workspaceFolder}\\..\\..\\..\\dependencies\\common\\node\\<VERSION>;${env:PATH}"

5b. Installed Node files (only if updating installed)

cmake/globals.cmake — CMake cache variable:

set(RSTUDIO_INSTALLED_NODE_VERSION "<VERSION>" CACHE INTERNAL "Node version installed with product")

dependencies/tools/rstudio-tools.sh — bash export with quotes:

export RSTUDIO_INSTALLED_NODE_VERSION="<VERSION>"

dependencies/tools/rstudio-tools.cmd — Windows batch, no quotes:

set RSTUDIO_INSTALLED_NODE_VERSION=<VERSION>

dependencies/tools/upload-node.sh — bash variable with v prefix:

NODE_VERSION="v<VERSION>"

The v prefix is required here and only here. All other files use the bare version.

NEWS.md — in the ### Dependencies section, record the new version:

- Node.js <VERSION> (GitHub Copilot, Posit Assistant)

The section lists only dependencies actually updated in the current release cycle, not every dependency at its current version. So the Node.js line is usually absent and must be added — keep the list alphabetical by dependency name. If a Node.js line is already there, this cycle bumped node before; overwrite that line rather than adding a second one.

Build-time updates do NOT get a NEWS.md entry — only installed node does.

5c. Verify edits

Three files contain both versions (cmake/globals.cmake, rstudio-tools.sh, rstudio-tools.cmd). When updating both versions, take care not to cross-contaminate the two variables in these files.

After editing, review the diff:

git diff

Confirm:

  • Only the intended version strings changed
  • Syntax matches each file's format (CMake quotes, bash quotes, batch no-quotes, XML attributes, JSON strings)
  • The v prefix appears ONLY in upload-node.sh

Then search for each old version that changed, to catch any pin missing from the lists above:

git grep -nF "<OLD_VERSION>" -- . ':!version/news/os'

Use -F so the dots match literally. The archived release notes under version/news/os/ are excluded because they record what shipped. Two kinds of match are expected and stay as they are:

  • Pins for the version you did not update, when both versions started at the same value -- e.g. the RSTUDIO_INSTALLED_NODE_VERSION lines, upload-node.sh, and any NEWS.md Node.js line after a build-only update.
  • Examples rather than pins, such as the version strings in SessionNodeTools.hpp comments and SessionNodeToolsTests.cpp parser tests.

Any other remaining match is a pin this skill doesn't list: stop and report it rather than guessing.

6. Upload to S3

Upload Node.js binaries for each unique new version using dependencies/tools/upload-node.sh. This script downloads platform archives from nodejs.org and uploads them to the rstudio-buildtools S3 bucket.

The script reads its version from the hardcoded NODE_VERSION variable (set in Step 5b for installed updates). The upload covers all platforms: darwin-arm64, darwin-x64, linux-arm64, linux-x64, win-x64, win-arm64.

If updating installed (or both to the same version): upload-node.sh already has the correct version from Step 5b. Run it:

bash dependencies/tools/upload-node.sh

Confirm exit code 0.

If updating build only: upload-node.sh wasn't edited in Step 5 (it tracks the installed version by convention). Temporarily edit it to NODE_VERSION="v<BUILD_VERSION>", run it, then revert to the original installed value:

# Save the original value, set to build version, upload, restore
bash dependencies/tools/upload-node.sh
git checkout dependencies/tools/upload-node.sh

If updating both to different versions: upload-node.sh was set to the installed version in Step 5b. Upload the build version first (temporarily), then the installed version:

  1. Edit upload-node.sh to NODE_VERSION="v<BUILD_VERSION>"
  2. Run bash dependencies/tools/upload-node.sh — confirm exit code 0
  3. Edit upload-node.sh back to NODE_VERSION="v<INSTALLED_VERSION>" (its committed value)
  4. Run bash dependencies/tools/upload-node.sh — confirm exit code 0

If any upload fails, report the error and stop.

After uploading, remove the downloaded archives left in the repo root by the upload script (they match node-v* and are not gitignored):

rm -f node-v<VERSION>-*

If both versions were uploaded and they differ, remove both:

rm -f node-v<BUILD_VERSION>-* node-v<INSTALLED_VERSION>-*

7. Verify the install

Run the dependency installer to confirm the S3 binaries download and extract correctly. The script must be run from dependencies/common/ because it invokes ./install-node via a relative path. It installs into dependencies/common/node/ which is gitignored.

First, remove any cached local installs for the new version(s) so the installer is forced to download fresh from S3. The install-node script skips download when it finds an existing node/<version>/bin/node, so stale local copies would mask a broken upload.

cd dependencies/common
# Remove cached installs for the new version(s) to force a fresh download.
# Glob covers both plain and suffixed dirs (e.g. 22.14.0, 22.14.0-arm64, 22.14.0-installed, 22.14.0-arm64-installed).
rm -rf node/<VERSION>*

If both versions are being updated to different values, remove both:

rm -rf node/<BUILD_VERSION>* node/<INSTALLED_VERSION>*

Then run the installer:

bash install-npm-dependencies

Confirm exit code 0. If the install fails, report the error and stop. This also installs the new Node.js locally for development use.

If updating build, also check that the npm bundled with the new Node accepts every lockfile. The Electron package step runs npm ci with it, and a new npm major can reject a lockfile the old one accepted (npm 11 rejected the desktop lockfile with Missing: js-yaml@5.4.3). With the new Node first on PATH, from the repo root:

export PATH="$PWD/dependencies/common/node/<BUILD_VERSION>-arm64/bin:$PATH"  # drop -arm64 on Linux/Intel
for d in $(git ls-files '*package-lock.json' | xargs -n1 dirname); do
   (cd "$d" && npm ci --dry-run --ignore-scripts >/dev/null 2>&1 && echo "ok: $d" || echo "FAILED: $d")
done

If one fails, refresh it in that directory with npm install --package-lock-only --ignore-scripts. Then confirm the npm bundled with the previous Node still accepts the result; npx fetches that npm, so the previous Node doesn't need to be installed:

OLD_NPM=$(curl -fsSL https://nodejs.org/dist/index.json | jq -r '.[] | select(.version=="v<OLD_BUILD_VERSION>") | .npm')
npx --yes "npm@${OLD_NPM:?could not find the npm bundled with Node <OLD_BUILD_VERSION>}" ci --dry-run --ignore-scripts

Commit the refreshed lockfile as its own commit.

8. Commit and open a PR

Commit all modified files and open a pull request.

Commit message and PR title (varies by mode):

  • Build-time only: Update build-time Node.js to <VERSION>
  • Installed only: Update installed Node.js to <VERSION>
  • Both, same version: Update Node.js to <VERSION>
  • Both, different versions: Update Node.js (build: <V1>, installed: <V2>)

PR body should mention the version bump(s), that the S3 upload and local install were verified, and list old→new for each version that changed.