Skip to content
Open
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
2 changes: 1 addition & 1 deletion .claude/CLOUD.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ These cannot live in the repo and must be set once per environment:

## Notes

- Node/pnpm versions come from `package.json` (Volta locally). In the cloud we use the sandbox Node runtime, and the setup script installs the pinned pnpm version when the sandbox has none.
- Node.js/pnpm versions come from `package.json` (`devEngines.runtime` and `packageManager`, managed by pnpm locally). In the cloud we use the sandbox Node.js runtime, and the setup script installs the pinned pnpm version when the sandbox has none.
- The full production `pnpm run build` is not run at startup; `build:dev` (transpile + types) is enough for editing and running unit tests. Run `pnpm run build` manually if you need bundles.
8 changes: 4 additions & 4 deletions .cursor/environment.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"name": "Sentry JavaScript SDK Development",
"install": "curl https://get.volta.sh | bash && export VOLTA_HOME=\"$HOME/.volta\" && export PATH=\"$VOLTA_HOME/bin:$PATH\" && export VOLTA_FEATURE_PNPM=1 && pnpm install",
"start": "export VOLTA_HOME=\"$HOME/.volta\" && export PATH=\"$VOLTA_HOME/bin:$PATH\" && export VOLTA_FEATURE_PNPM=1",
"install": "curl -fsSL https://get.pnpm.io/install.sh | sh - && export PNPM_HOME=\"$HOME/.local/share/pnpm\" && export PATH=\"$PNPM_HOME/bin:$PATH\" && pnpm runtime set node 24 -g -y && pnpm install",
"start": "export PNPM_HOME=\"$HOME/.local/share/pnpm\" && export PATH=\"$PNPM_HOME/bin:$PATH\"",
"terminals": [
{
"name": "Development",
"command": "export VOLTA_HOME=\"$HOME/.volta\" && export PATH=\"$VOLTA_HOME/bin:$PATH\" && export VOLTA_FEATURE_PNPM=1 && echo 'Volta setup complete. Node version:' && node --version && echo 'pnpm version:' && pnpm --version",
"description": "Main development terminal with Volta environment configured"
"command": "export PNPM_HOME=\"$HOME/.local/share/pnpm\" && export PATH=\"$PNPM_HOME/bin:$PATH\" && echo 'pnpm setup complete. Node version:' && node --version && echo 'pnpm version:' && pnpm --version",
"description": "Main development terminal with pnpm-managed node"
}
]
}
12 changes: 11 additions & 1 deletion .github/actions/prepare-e2e-test/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ inputs:
runs:
using: 'composite'
steps:
# Uses the repo's root node pin for the steps before the app is copied (installing pnpm, `test:prepare`,
# `test:validate`, `ci:copy-to-temp`). The app's own node version is set up after the copy below.
- name: Set up Node
uses: actions/setup-node@v7
with:
node-version-file: 'dev-packages/e2e-tests/test-applications/${{ inputs.test-application }}/package.json'
node-version-file: 'package.json'
- name: Set up Bun
if:
inputs.test-application == 'node-exports-test-app' || contains(inputs.test-application, 'bun') || inputs.runtime
Expand Down Expand Up @@ -102,6 +104,14 @@ runs:
shell: bash
working-directory: dev-packages/e2e-tests

# Reads the node version that `ci:copy-to-temp` wrote into the copied app's `devEngines.runtime`: the app's own pin
# if it has one, otherwise the one inherited from the repo root. setup-node can't look up parent directories, so the
# temp copy has to carry the resolved version.
- name: Set up Node for the E2E app
uses: actions/setup-node@v7
with:
node-version-file: '${{ runner.temp }}/test-application/package.json'

- name: Add pnpm overrides
run:
pnpm run ci:pnpm-overrides ${{ runner.temp }}/test-application ${{ github.workspace
Expand Down
12 changes: 11 additions & 1 deletion .github/workflows/canary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,12 @@ jobs:
ref: ${{ env.HEAD_COMMIT }}
- name: Install pnpm
uses: ./.github/actions/install-pnpm
# Uses the repo's root node pin for the steps before the app is copied (`test:prepare`, `test:validate`,
# `ci:copy-to-temp`). The app's own node version is set up after the copy below.
- name: Set up Node
uses: actions/setup-node@v7
with:
node-version-file: 'dev-packages/e2e-tests/test-applications/${{ matrix.test-application }}/package.json'
node-version-file: 'package.json'

- name: Restore canary cache
uses: actions/cache/restore@v6.1.0
Expand All @@ -147,6 +149,14 @@ jobs:
}}/test-application
working-directory: dev-packages/e2e-tests

# Reads the node version that `ci:copy-to-temp` wrote into the copied app's `devEngines.runtime`: the app's own pin
# if it has one, otherwise the one inherited from the repo root. setup-node can't look up parent directories, so
# the temp copy has to carry the resolved version.
- name: Set up Node for the E2E app
uses: actions/setup-node@v7
with:
node-version-file: '${{ runner.temp }}/test-application/package.json'

- name: Add pnpm overrides
run:
pnpm run ci:pnpm-overrides ${{ runner.temp }}/test-application ${{ github.workspace
Expand Down
5 changes: 2 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ Monorepo with 40+ packages in `@sentry/*`, managed with pnpm workspaces and Nx.

## Setup

- [Volta](https://volta.sh/) for Node.js/pnpm version management
- Requires `VOLTA_FEATURE_PNPM=1`
- pnpm manages Node.js and pnpm versions (see `CONTRIBUTING.md`): Node.js in `devEngines.runtime`, pnpm in `packageManager`
- After cloning: `pnpm install && pnpm run build`
- Never change Volta, pnpm, or package manager versions unless explicitly asked
- Never change Node.js, pnpm, or package manager versions unless explicitly asked

### Code Intelligence

Expand Down
53 changes: 41 additions & 12 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,30 @@ Developer Documentation.

## Setting up an Environment

We use [Volta](https://volta.sh/) to ensure we use consistent versions of node and pnpm. Volta manages pnpm only when
[pnpm support](https://docs.volta.sh/advanced/pnpm) is enabled, so add `export VOLTA_FEATURE_PNPM=1` to your shell
profile (for example `~/.zshrc`).
`sentry-javascript` is a monorepo containing several packages, managed with pnpm workspaces and Nx. pnpm installs the
Node.js and pnpm versions the repo pins.

`sentry-javascript` is a monorepo containing several packages, managed with pnpm workspaces and Nx. To get started,
install all dependencies, and then perform an initial build, so TypeScript can read all of the linked type definitions.
1. Install pnpm 12 or newer, see the [pnpm installation docs](https://pnpm.io/installation).
2. Install a default Node.js version through pnpm:

```
$ pnpm install
$ pnpm run build
```
```sh
pnpm runtime set node 24 -g
Comment thread
sentry-junior[bot] marked this conversation as resolved.
```

If this fails with `ERR_PNPM_GLOBAL_BIN_DIR_NOT_IN_PATH` (for example because pnpm came from npm or Homebrew instead
of the install script), run [`pnpm setup`](https://pnpm.io/cli/setup) to add pnpm's global bin directory to your
`PATH`, restart your shell, and try again.

With that, the repo is fully set up and you are ready to run all commands.
3. Install all dependencies, and then perform an initial build, so TypeScript can read all of the linked type
definitions:

```sh
pnpm install
pnpm run build
```

With that, the repo is fully set up and you are ready to run all commands. If you used Volta before, or `node --version`
doesn't match the repo, see [Node.js and pnpm Versions](#nodejs-and-pnpm-versions).

## Building Packages

Expand Down Expand Up @@ -79,8 +90,8 @@ E2E tests verify SDK behavior in real-world framework scenarios.
### Prerequisites

1. **Docker**: Required for some E2E tests
2. **Volta**: The E2E tests run each app on the node version that it pins. Without Volta, they use your current node
version.
2. **pnpm-managed Node.js** (see [Setting up an Environment](#setting-up-an-environment)): The E2E tests run each app on
the Node.js version that it pins. Without pnpm's `node` shim, they use your current Node.js version.

### Step-by-Step Instructions

Expand Down Expand Up @@ -190,3 +201,21 @@ See [PR Reviews](./docs/pr-reviews.md).
_These steps are only relevant to Sentry employees when preparing and publishing a new SDK release._

[See the docs for publishing a release](./docs/publishing-a-release.md)

## Node.js and pnpm Versions

- The root `package.json` pins Node.js in `devEngines.runtime` and pnpm in `packageManager`. CI reads the same fields.
- pnpm's `node` command runs the Node.js version of the nearest `package.json` that pins one
([project-aware global bins](https://pnpm.io/global-packages#project-aware-global-bins)). Packages and E2E test apps
inherit the root version, and can override it with their own `devEngines.runtime`.

If `node --version` or `pnpm --version` doesn't match the repo, run `which node pnpm`. Both should be in
`$PNPM_HOME/bin`. If not, another tool (Volta, nvm, fnm, corepack, Homebrew Node.js) comes first in your `PATH`. Remove it,
or move `$PNPM_HOME/bin` before it.

### Migrating from Volta

The repo no longer has a `volta` field, so Volta runs its default Node.js instead of the version the repo pins.
[Uninstall Volta](https://docs.volta.sh/advanced/uninstall) and follow the
[setup](#setting-up-an-environment). If other projects still need Volta, keep it, but put `$PNPM_HOME/bin` before
`~/.volta/bin` in your `PATH`, and use `volta run node` in those projects.
3 changes: 0 additions & 3 deletions dev-packages/browser-integration-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,5 @@
"@types/node": "^20.19.0",
"eslint-plugin-regexp": "^3.1.0",
"glob": "^13.0.6"
},
"volta": {
"extends": "../../package.json"
}
}
3 changes: 0 additions & 3 deletions dev-packages/bun-integration-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,5 @@
"@types/pg": "^8.20.0",
"bun-types": "^1.2.9",
"vitest": "^4.1.11"
},
"volta": {
"extends": "../../package.json"
}
}
3 changes: 0 additions & 3 deletions dev-packages/bundler-plugin-integration-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,5 @@
"sentry": "^0.45.0",
"typescript": "~6.0.3",
"vitest": "^4.1.11"
},
"volta": {
"extends": "../../package.json"
}
}
3 changes: 0 additions & 3 deletions dev-packages/bundler-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,5 @@
"vitest": "^4.1.11",
"webpack": "^5.0.0"
},
"volta": {
"extends": "../../package.json"
},
"type": "module"
}
3 changes: 0 additions & 3 deletions dev-packages/clear-cache-gh-action/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,5 @@
},
"devDependencies": {
"eslint-plugin-regexp": "^3.1.0"
},
"volta": {
"extends": "../../package.json"
}
}
3 changes: 0 additions & 3 deletions dev-packages/cloudflare-integration-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,5 @@
"vite": "^8.3.1",
"vitest": "^4.1.11",
"wrangler": "4.86.0"
},
"volta": {
"extends": "../../package.json"
}
}
3 changes: 0 additions & 3 deletions dev-packages/deno-integration-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,5 @@
"@sentry-internal/node-integration-tests": "11.9.0",
"@sentry/conventions": "^0.27.0",
"vitest": "^4.1.11"
},
"volta": {
"extends": "../../package.json"
}
}
6 changes: 6 additions & 0 deletions dev-packages/e2e-tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,12 @@ Apps use the pnpm version of `packageManager` in the root `package.json`. To use
`package.json` (for example `"packageManager": "pnpm@10.34.6"`), and use the settings of that version in its
`pnpm-workspace.yaml`. pnpm then switches to that version, locally and in CI. The lowest supported version is pnpm 10.

Apps use the Node.js version of `devEngines.runtime` in the root `package.json`. To use another version, set
`devEngines.runtime` in the app's `package.json` (for example `{ "name": "node", "version": "24.15.0", "onFail": "warn" }`).
Because the apps run in a temporary directory without the repo's `package.json` files above it, the copy step writes the
app's Node.js and pnpm versions into the copied `package.json`. pnpm's `node` and `actions/setup-node` in CI read them
from there.

### Test apps that deploy a real Cloudflare Worker

Apps like `cloudflare-workers-send-to-sentry` deploy the built app as a real Worker and check what arrives in Sentry:
Expand Down
59 changes: 42 additions & 17 deletions dev-packages/e2e-tests/lib/copyToTemp.mjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
// @ts-check
/* eslint-disable no-console */
import { readFileSync, writeFileSync } from 'node:fs';
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
import { cp } from 'node:fs/promises';
import { dirname, isAbsolute, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = resolve(__dirname, '../../..');

/** @typedef {{ name: string; version: string; onFail?: string }} RuntimeEntry */

/**
* @param {string} originalPath
Expand All @@ -16,15 +19,16 @@ export async function copyToTemp(originalPath, tmpDirPath) {
// copy files to tmp dir
await cp(originalPath, tmpDirPath, { recursive: true });

fixPackageJson(tmpDirPath);
fixPackageJson(originalPath, tmpDirPath);
}

/**
* @param {string} originalPath
* @param {string} cwd
*/
function fixPackageJson(cwd) {
function fixPackageJson(originalPath, cwd) {
const packageJsonPath = join(cwd, 'package.json');
/** @type {{ dependencies?: Record<string, string>; devDependencies?: Record<string, string>; volta?: Record<string, unknown> }} */
/** @type {{ dependencies?: Record<string, string>; devDependencies?: Record<string, string>; devEngines?: { runtime?: RuntimeEntry | RuntimeEntry[] } & Record<string, unknown>; packageManager?: string }} */
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));

// 1. Fix file dependencies
Expand All @@ -35,25 +39,46 @@ function fixPackageJson(cwd) {
fixFileLinkDependencies(packageJson.devDependencies);
}

// 2. Fix volta extends
if (!packageJson.volta) {
throw new Error("No volta config found, please add one to the test app's package.json!");
// 2. Pin node and pnpm. The tmp dir has no parent package.json to inherit them from.
const { nodeRuntime, packageManager } = resolveToolPins(originalPath);
if (!nodeRuntime || !packageManager) {
throw new Error(`Could not resolve the node and pnpm versions for ${originalPath}.`);
}

if (typeof packageJson.volta.extends === 'string') {
const extendsPath = packageJson.volta.extends;
// We add a virtual dir to ensure that the relative depth is consistent
// dirPath is relative to ./../test-applications/xxx
const newPath = resolve(__dirname, 'virtual-dir/', extendsPath);
packageJson.volta.extends = newPath;
console.log(`Fixed volta.extends to ${newPath}`);
} else {
console.log('No volta.extends found');
const ownRuntimes = [packageJson.devEngines?.runtime].flat().filter(runtime => runtime !== undefined);
if (!ownRuntimes.some(runtime => runtime.name === 'node')) {
const runtime = ownRuntimes.length ? [...ownRuntimes, nodeRuntime] : nodeRuntime;
packageJson.devEngines = { ...packageJson.devEngines, runtime };
}
packageJson.packageManager = packageManager;
console.log(`Pinned node ${nodeRuntime.version} and ${packageManager}`);

writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
}

/**
* The nearest `devEngines.runtime` node entry and `packageManager` between the app and the repo root.
* @param {string} appPath
* @returns {{ nodeRuntime?: RuntimeEntry; packageManager?: string }}
*/
function resolveToolPins(appPath) {
/** @type {RuntimeEntry | undefined} */
let nodeRuntime;
/** @type {string | undefined} */
let packageManager;

for (let dir = resolve(appPath); ; dir = dirname(dir)) {
const packageJsonPath = join(dir, 'package.json');
if (existsSync(packageJsonPath)) {
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
nodeRuntime ??= [packageJson.devEngines?.runtime].flat().find(runtime => runtime?.name === 'node');
packageManager ??= packageJson.packageManager;
}
if (dir === REPO_ROOT || dir === dirname(dir)) {
return { nodeRuntime, packageManager };
}
}
}

/**
* Exported for pnpmOverrides as well
* @param {Record<string, string>} dependencyObj
Expand Down
3 changes: 0 additions & 3 deletions dev-packages/e2e-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,5 @@
"devDependencies": {
"eslint-plugin-regexp": "^3.1.0",
"rimraf": "^6.1.3"
},
"volta": {
"extends": "../../package.json"
}
}
Loading
Loading