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
61 changes: 61 additions & 0 deletions dev-packages/e2e-tests/test-applications/node-esbuild/assert.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* Asserts that `sentryEsbuildPlugin` performs build-time instrumentation: its code transform injects
* the orchestrion "bundler ran" banner into the entry chunk. A plain build (no plugin) does not.
*
* @module
*/
import { readdirSync, readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = dirname(fileURLToPath(import.meta.url));

// A distinctive slice of the orchestrion banner that the bundler plugin's build-time code transform
// prepends to the entry chunk (see `ORCHESTRION_BUNDLER_MARKER_BANNER` in `@sentry/server-utils`).
// It is emitted only when the plugin's build-time instrumentation runs, so it tells a `plugin` build
// apart from a `plain` one. Before matching we strip block comments and whitespace, because bundlers
// format the injected banner differently — Rolldown pretty-prints it and inserts a `/* @__PURE__ */`
// annotation. The banner initializes the set with `new Set()`, hence the stripped `newSet()` form.
const BUILD_TIME_TRANSFORM_MARKER = 'g.bundler=g.bundler||newSet()';
Comment on lines +13 to +19

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

L: similar to Nico's comment - this is also duplicated in all the tests.


function bundleText(name) {
const files = [];
const walk = dir => {
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const full = join(dir, entry.name);
if (entry.isDirectory()) {
walk(full);
} else {
files.push(full);
}
}
};
walk(join(__dirname, 'dist', name));
return files
.map(f => readFileSync(f, 'utf8'))
.join('\n')
.replace(/\/\*[\s\S]*?\*\//g, '')
.replace(/\s+/g, '');
}

let failed = false;
function check(condition, message) {
// eslint-disable-next-line no-console
console.log(`${condition ? 'ok ' : 'FAIL'} - ${message}`);
if (!condition) failed = true;
}

const plain = bundleText('plain');
const plugin = bundleText('plugin');

check(!plain.includes(BUILD_TIME_TRANSFORM_MARKER), 'plain build (no plugin) does not run build-time instrumentation');
check(
plugin.includes(BUILD_TIME_TRANSFORM_MARKER),
'sentryEsbuildPlugin runs build-time instrumentation (injects the orchestrion banner)',
);

if (failed) {
process.exit(1);
}
// eslint-disable-next-line no-console
console.log('All bundle assertions passed.');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

l: this logic is duplicated for every test in this PR, should we consolidate this?

41 changes: 41 additions & 0 deletions dev-packages/e2e-tests/test-applications/node-esbuild/build.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Bundles the entrypoint with esbuild twice:
// - `plain`: no Sentry plugin.
// - `plugin`: with `sentryEsbuildPlugin` (build-time instrumentation).
// Only the `plugin` build runs the orchestrion code transform, which prepends the "bundler ran"
// banner to the entry chunk. Kept unminified so the banner keeps its identifiers (a minifier would
// rename them); assert.mjs matches it whitespace-insensitively.
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { build } from 'esbuild';
import { sentryEsbuildPlugin } from '@sentry/node/esbuild';

const __dirname = dirname(fileURLToPath(import.meta.url));

function run(name, plugins) {
return build({
entryPoints: [join(__dirname, 'src', 'entry.mjs')],
outdir: join(__dirname, 'dist', name),
bundle: true,
platform: 'node',
format: 'esm',
minify: false,
logLevel: 'silent',
plugins,
});
}

await run('plain', []);
await run(
'plugin',
// No auth/release/telemetry — we only care about the build-time transforms and defines.
[
sentryEsbuildPlugin({
telemetry: false,
sourcemaps: { disable: true },
release: { create: false, finalize: false, inject: false },
}),
],
);

// eslint-disable-next-line no-console
console.log('built plain + plugin with esbuild');
23 changes: 23 additions & 0 deletions dev-packages/e2e-tests/test-applications/node-esbuild/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "node-esbuild",
"description": "ensure the Sentry esbuild plugin performs build-time instrumentation",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"clean": "npx rimraf node_modules dist pnpm-lock.yaml",
"test:build": "pnpm install && node ./build.mjs",
"test:assert": "node ./assert.mjs"
},
"dependencies": {
"@sentry/node": "file:../../packed/sentry-node-packed.tgz",
"@sentry/server-utils": "file:../../packed/sentry-server-utils-packed.tgz",
"@sentry/bundler-plugins": "file:../../packed/sentry-bundler-plugins-packed.tgz"
},
"devDependencies": {
"esbuild": "0.28.2"
},
"volta": {
"extends": "../../package.json"
}
}

This file was deleted.

61 changes: 61 additions & 0 deletions dev-packages/e2e-tests/test-applications/node-rolldown/assert.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* Asserts that `sentryRollupPlugin` performs build-time instrumentation when bundling with Rolldown: its code transform injects
* the orchestrion "bundler ran" banner into the entry chunk. A plain build (no plugin) does not.
*
* @module
*/
import { readdirSync, readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = dirname(fileURLToPath(import.meta.url));

// A distinctive slice of the orchestrion banner that the bundler plugin's build-time code transform
// prepends to the entry chunk (see `ORCHESTRION_BUNDLER_MARKER_BANNER` in `@sentry/server-utils`).
// It is emitted only when the plugin's build-time instrumentation runs, so it tells a `plugin` build
// apart from a `plain` one. Before matching we strip block comments and whitespace, because bundlers
// format the injected banner differently — Rolldown pretty-prints it and inserts a `/* @__PURE__ */`
// annotation. The banner initializes the set with `new Set()`, hence the stripped `newSet()` form.
const BUILD_TIME_TRANSFORM_MARKER = 'g.bundler=g.bundler||newSet()';

function bundleText(name) {
const files = [];
const walk = dir => {
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const full = join(dir, entry.name);
if (entry.isDirectory()) {
walk(full);
} else {
files.push(full);
}
}
};
walk(join(__dirname, 'dist', name));
return files
.map(f => readFileSync(f, 'utf8'))
.join('\n')
.replace(/\/\*[\s\S]*?\*\//g, '')
.replace(/\s+/g, '');
}

let failed = false;
function check(condition, message) {
// eslint-disable-next-line no-console
console.log(`${condition ? 'ok ' : 'FAIL'} - ${message}`);
if (!condition) failed = true;
}

const plain = bundleText('plain');
const plugin = bundleText('plugin');

check(!plain.includes(BUILD_TIME_TRANSFORM_MARKER), 'plain build (no plugin) does not run build-time instrumentation');
check(
plugin.includes(BUILD_TIME_TRANSFORM_MARKER),
'sentryRollupPlugin runs build-time instrumentation (injects the orchestrion banner)',
);

if (failed) {
process.exit(1);
}
// eslint-disable-next-line no-console
console.log('All bundle assertions passed.');
42 changes: 42 additions & 0 deletions dev-packages/e2e-tests/test-applications/node-rolldown/build.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Bundles the entrypoint with Rolldown twice:
// - `plain`: no Sentry plugin.
// - `plugin`: with `sentryRollupPlugin` (build-time instrumentation).
// Only the `plugin` build runs the orchestrion code transform, which prepends the "bundler ran"
// banner to the entry chunk. Kept unminified so the banner keeps its identifiers (a minifier would
// rename them); assert.mjs matches it whitespace-insensitively.
// Rolldown is Rollup API-compatible, so it consumes the same `@sentry/node/rollup` plugin; it also
// resolves node modules and CommonJS natively, so no extra resolve/commonjs plugins are needed.
import { builtinModules } from 'node:module';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { rolldown } from 'rolldown';
import { sentryRollupPlugin } from '@sentry/node/rollup';

const __dirname = dirname(fileURLToPath(import.meta.url));
const external = [...builtinModules, ...builtinModules.map(m => `node:${m}`)];

async function run(name, extra) {
const bundle = await rolldown({
input: join(__dirname, 'src', 'entry.mjs'),
external,
plugins: [...extra],
onwarn: () => {},
});
await bundle.write({ dir: join(__dirname, 'dist', name), format: 'es', entryFileNames: 'main.mjs' });
await bundle.close();
}

await run('plain', []);
await run(
'plugin',
// `sentryRollupPlugin` returns an array of Rollup plugins. No auth/release/telemetry — we only care
// about the build-time transforms and defines.
sentryRollupPlugin({
telemetry: false,
sourcemaps: { disable: true },
release: { create: false, finalize: false, inject: false },
}),
);

// eslint-disable-next-line no-console
console.log('built plain + plugin with rolldown');
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "node-rolldown",
"description": "ensure the Sentry rollup plugin performs build-time instrumentation when bundling with rolldown",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"clean": "npx rimraf node_modules dist pnpm-lock.yaml",
"test:build": "pnpm install && node ./build.mjs",
"test:assert": "node ./assert.mjs"
},
"dependencies": {
"@sentry/node": "file:../../packed/sentry-node-packed.tgz",
"@sentry/server-utils": "file:../../packed/sentry-server-utils-packed.tgz",
"@sentry/bundler-plugins": "file:../../packed/sentry-bundler-plugins-packed.tgz"
},
"devDependencies": {
"rolldown": "1.2.5"
},
"volta": {
"extends": "../../package.json"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// eslint-disable-next-line no-console
console.log('this is the application');
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import * as Sentry from '@sentry/node';

Sentry.init({
traceLifecycle: 'static',
dsn: 'https://public@dsn.ingest.sentry.io/1337',
tracesSampleRate: 1,
});

await import('./app.mjs');
61 changes: 61 additions & 0 deletions dev-packages/e2e-tests/test-applications/node-rollup/assert.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* Asserts that `sentryRollupPlugin` performs build-time instrumentation: its code transform injects
* the orchestrion "bundler ran" banner into the entry chunk. A plain build (no plugin) does not.
*
* @module
*/
import { readdirSync, readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = dirname(fileURLToPath(import.meta.url));

// A distinctive slice of the orchestrion banner that the bundler plugin's build-time code transform
// prepends to the entry chunk (see `ORCHESTRION_BUNDLER_MARKER_BANNER` in `@sentry/server-utils`).
// It is emitted only when the plugin's build-time instrumentation runs, so it tells a `plugin` build
// apart from a `plain` one. Before matching we strip block comments and whitespace, because bundlers
// format the injected banner differently — Rolldown pretty-prints it and inserts a `/* @__PURE__ */`
// annotation. The banner initializes the set with `new Set()`, hence the stripped `newSet()` form.
const BUILD_TIME_TRANSFORM_MARKER = 'g.bundler=g.bundler||newSet()';

function bundleText(name) {
const files = [];
const walk = dir => {
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const full = join(dir, entry.name);
if (entry.isDirectory()) {
walk(full);
} else {
files.push(full);
}
}
};
walk(join(__dirname, 'dist', name));
return files
.map(f => readFileSync(f, 'utf8'))
.join('\n')
.replace(/\/\*[\s\S]*?\*\//g, '')
.replace(/\s+/g, '');
}

let failed = false;
function check(condition, message) {
// eslint-disable-next-line no-console
console.log(`${condition ? 'ok ' : 'FAIL'} - ${message}`);
if (!condition) failed = true;
}

const plain = bundleText('plain');
const plugin = bundleText('plugin');

check(!plain.includes(BUILD_TIME_TRANSFORM_MARKER), 'plain build (no plugin) does not run build-time instrumentation');
check(
plugin.includes(BUILD_TIME_TRANSFORM_MARKER),
'sentryRollupPlugin runs build-time instrumentation (injects the orchestrion banner)',
);

if (failed) {
process.exit(1);
}
// eslint-disable-next-line no-console
console.log('All bundle assertions passed.');
Loading
Loading