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
4 changes: 4 additions & 0 deletions .craft.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ targets:
- name: npm
id: '@sentry/server-utils'
includeNames: /^sentry-server-utils-\d.*\.tgz$/
# Depends on @sentry/server-utils (for the shared instrumentation config); publish after it.
- name: npm
id: '@sentry/server-runtime-injection'
includeNames: /^sentry-server-runtime-injection-\d.*\.tgz$/
## 1.3 Browser Utils package
- name: npm
id: '@sentry/browser-utils'
Expand Down
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
# Node/server runtimes and related packages
# TEMP: whole JS SDK team reviews orchestrion work; revert to team-javascript-sdks-server after
/packages/node/ @getsentry/team-javascript-sdks
/packages/server-runtime-injection/ @getsentry/team-javascript-sdks
/packages/server-utils/ @getsentry/team-javascript-sdks
/packages/node-native/ @getsentry/team-javascript-sdks-server
/packages/profiling-node/ @getsentry/team-javascript-sdks-server
Expand Down
2 changes: 1 addition & 1 deletion .size-limit.js
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ module.exports = [
},
{
name: '@sentry/node/import (ESM hook with diagnostics-channel injection)',
path: ['packages/server-utils/build/esm/orchestrion/runtime/hook.js', 'packages/node/build/import-hook.mjs'],
path: ['packages/server-runtime-injection/build/esm/hook.js', 'packages/node/build/import-hook.mjs'],
ignore: [...builtinModules, ...nodePrefixedBuiltinModules],
gzip: true,
limit: '91 KB',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,14 @@ export class LocalLambdaStack extends Stack {
const packageLockPath = path.join(lambdaPath, 'package-lock.json');
const nodeModulesPath = path.join(lambdaPath, 'node_modules');

// `dir` is the package directory under `packages/`; `name` is the published
// npm name (most are `@sentry/<dir>`, but `server-utils` is `@sentry-internal`).
// `dir` is the package directory under `packages/`; `name` is the published npm name.
const packagesToLink: Array<{ dir: string; name: string }> = [
{ dir: 'aws-serverless', name: '@sentry/aws-serverless' },
{ dir: 'node', name: '@sentry/node' },
{ dir: 'core', name: '@sentry/core' },
{ dir: 'opentelemetry', name: '@sentry/opentelemetry' },
{ dir: 'server-utils', name: '@sentry/server-utils' },
{ dir: 'server-runtime-injection', name: '@sentry/server-runtime-injection' },
{ dir: 'bundler-plugins', name: '@sentry/bundler-plugins' },
];
const dependencies: Record<string, string> = {};
Expand Down
2 changes: 1 addition & 1 deletion dev-packages/rollup-utils/code/importHookTemplate.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
import '@sentry/server-utils/orchestrion/import-hook';
import '@sentry/server-runtime-injection/import-hook';
8 changes: 4 additions & 4 deletions dev-packages/rollup-utils/npmHelpers.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,9 @@ export function makeNPMConfigVariants(baseConfig, options = {}) {
/**
* Emits the `@sentry/<framework>/import` entry (`build/import-hook.mjs`) as part of the rollup build,
* used as `node --import @sentry/<framework>/import app.js`. The generated hook imports
* `@sentry/server-utils/orchestrion/import-hook`, which registers the orchestrion
* diagnostics-channel injection, so the consuming package must declare `@sentry/server-utils` as a
* dependency.
* `@sentry/server-runtime-injection/import-hook`, which registers the orchestrion
* diagnostics-channel injection, so the consuming package must declare
* `@sentry/server-runtime-injection` as a dependency.
*
* @param {string} outputFolder Build output folder.
*/
Expand All @@ -209,7 +209,7 @@ export function makeOrchestrionLoader(outputFolder) {
);
}

const requiredDep = '@sentry/server-utils';
const requiredDep = '@sentry/server-runtime-injection';
Comment thread
cursor[bot] marked this conversation as resolved.
const foundRequiredDep =
Object.keys(packageDotJSON.dependencies ?? {}).some(key => {
return key === requiredDep;
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
"packages/replay-internal",
"packages/replay-canvas",
"packages/replay-worker",
"packages/server-runtime-injection",
"packages/server-utils",
"packages/solid",
"packages/solidstart",
Expand Down
1 change: 1 addition & 0 deletions packages/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"@sentry/core": "10.67.0",
"@sentry/conventions": "^0.20.0",
"@sentry/node": "10.67.0",
"@sentry/server-runtime-injection": "10.67.0",
"@sentry/server-utils": "10.67.0",
"@sentry/bundler-plugins": "10.67.0"
},
Expand Down
1 change: 1 addition & 0 deletions packages/aws-serverless/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"@sentry/conventions": "^0.20.0",
"@sentry/core": "10.67.0",
"@sentry/node": "10.67.0",
"@sentry/server-runtime-injection": "10.67.0",
"@sentry/server-utils": "10.67.0",
"@types/aws-lambda": "^8.10.161"
},
Expand Down
1 change: 0 additions & 1 deletion packages/bun/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
"access": "public"
},
"dependencies": {
"@apm-js-collab/code-transformer-bundler-plugins": "^0.7.4",
"@sentry/core": "10.67.0",
"@sentry/conventions": "^0.20.0",
"@sentry/node": "10.67.0",
Expand Down
121 changes: 2 additions & 119 deletions packages/bun/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,125 +11,8 @@
* });
* ```
*
* This is BUILD-ONLY. Runtime instrumentation (`bun run`) is intentionally not
* offered: a module returned by a runtime `onLoad` plugin in Bun loses its
* CommonJS named exports.
*
* When https://github.com/oven-sh/bun/pull/31770 lands, we can revisit.
*
* Until then, Bun apps must bundle to get build-time instrumentation. In dev
* (ie, `bun run`) there is simply no instrumentation, which is clearer than
* partial/inconsistent coverage.
*
* Shipped as both ESM and CJS (via the `@sentry/bun/plugin` subpath) so a user's
* `bun build` script can be authored in either module system. It's a plain
* library import here (not a `--import`/`--preload` hook), so CJS is fine; Bun
* resolves the underlying ESM-only transformer in either module system.
* This is BUILD-ONLY. Runtime instrumentation (`bun run`) is currently not supported.
*
* @module
*/

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type UnknownPlugin = any;

// `@apm-js-collab/code-transformer-bundler-plugins/bun` is published ESM-only
// (no `require` arm, unlike its `/vite` entry). The ESM build imports it; the
// CJS build requires it. Bun resolves correctly for ESM modules in either
// module system.
import codeTransformer from '@apm-js-collab/code-transformer-bundler-plugins/bun';
import {
INSTRUMENTED_MODULE_NAMES,
moduleInjectedTransforms,
ORCHESTRION_BUNDLER_MARKER_BANNER,
SENTRY_INSTRUMENTATIONS,
withoutInstrumentedExternals,
} from '@sentry/server-utils/orchestrion/config';

// Minimal shape of Bun's `PluginBuilder` that we touch. Typed locally instead
// of depending on `bun-types`, which would pull Bun's globals.
interface BunPluginBuilder {
config?: { banner?: string; external?: string[]; packages?: 'bundle' | 'external' };
}

/**
* Returns the Sentry code-transform plugin for Bun's bundler, configured
* with the central `SENTRY_INSTRUMENTATIONS`. The plugin injects
* `diagnostics_channel.tracingChannel` calls into the instrumented libraries as
* `bun build` bundles them — plus, via the module-injected transform, the
* snippet that records each module on `globalThis.__SENTRY_ORCHESTRION__` when
* it is evaluated — and injects the marker banner so `bundler` is set (to an
* empty `Set`) from boot, which is what gates the SDK's channel-integration
* setup at `init()`.
*
* Pass the result to `Bun.build({ plugins: [...] })`.
*
* @example
* ```ts
* import { sentryBunPlugin } from '@sentry/bun/plugin';
* await Bun.build({ entrypoints: ['./app.ts'], plugins: [sentryBunPlugin()] });
* ```
*/
export function sentryBunPlugin(): UnknownPlugin {
// Typed upstream as an esbuild `Plugin`, but Bun passes its own
// `PluginBuilder` (which has the `onLoad` the transform uses) to `setup`.
// Cast to the Bun-compatible shape so we can forward Bun's builder to its
// `setup`.
const transformer = codeTransformer({
instrumentations: SENTRY_INSTRUMENTATIONS,
customTransforms: moduleInjectedTransforms(),
}) as unknown as {
setup: (build: BunPluginBuilder) => void;
};

return {
name: 'sentry-orchestrion',
setup(build: BunPluginBuilder): void {
// Inject the marker banner via Bun's native `banner` config (unlike the
// upstream `injectDiagnostics` path, it needs no `outdir`). `config` is
// the `Bun.build` config and is present when this plugin is passed to
// `Bun.build({ plugins: [...] })`.
if (build.config) {
const existing = build.config.banner ?? '';
build.config.banner = existing
? `${existing}\n${ORCHESTRION_BUNDLER_MARKER_BANNER}`
: ORCHESTRION_BUNDLER_MARKER_BANNER;

// Force-bundle every instrumented package. An externalized dependency
// is resolved from `node_modules` at runtime and never passes throug
// the transform's `onLoad`, so its diagnostics_channel calls would
// be silently never injected. Bun has no runtime fallback here, so
// bundling is the only injection path.
build.config.external = withoutInstrumentedExternals(build.config.external);

// A blanket externalization strategy like `packages: 'external'` or
// `'*'` in `external` externalizes instrumented packages too, and
// `withoutInstrumentedExternals` only strips exact names/subpaths (not
// these), so those packages ship un-transformed with no runtime
// fallback. Forcing them back in via `onResolve` is not an option: Bun
// ignores `{ external: false }` against a blanket strategy, and
// returning a resolved `path` corrupts the package's ESM/CJS interop.
// So warn instead. This runs in the user's build script, where the
// Sentry debug logger isn't enabled, and `console` is the thing to use.
const blanketExternal =
build.config.packages === 'external'
? "packages: 'external'"
: build.config.external?.includes('*')
? "'*' in external"
: undefined;
if (blanketExternal) {
// eslint-disable-next-line no-console
console.warn(
`[Sentry] This Bun build externalizes all dependencies (${blanketExternal}), so Sentry ` +
'cannot instrument bundled libraries. Instrumentation will be missing for any of ' +
`these packages your app uses: ${INSTRUMENTED_MODULE_NAMES.join(', ')}. To instrument them, ` +
'externalize only the specific packages you need external instead of all of them.',
);
}
}

// Delegate to the upstream code-transformer, which registers the `onLoad`
// hook that does the actual channel injection.
transformer.setup(build);
},
};
}
export { sentryOrchestrionPlugin as sentryBunPlugin } from '@sentry/server-utils/orchestrion/bun';
Comment thread
cursor[bot] marked this conversation as resolved.
2 changes: 1 addition & 1 deletion packages/core/src/utils/worldwide.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export type InternalGlobal = {
/**
* Set once `registerDiagnosticsChannelInjection()` has run but could not
* install the runtime module hooks — most commonly because
* `@sentry/server-utils` was bundled into the app (which strips its vendored
* `@sentry/server-runtime-injection` was bundled into the app (which strips its vendored
* code transformer) or the Node runtime lacks the required module-hook API.
* Dedupes the one-time warning and short-circuits repeat calls.
*/
Expand Down
1 change: 1 addition & 0 deletions packages/deno/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"@opentelemetry/api": "^1.9.1",
"@sentry/conventions": "^0.20.0",
"@sentry/core": "10.67.0",
"@sentry/server-runtime-injection": "10.67.0",
"@sentry/server-utils": "10.67.0"
},
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/deno/src/import.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@
*
* @module
*/
import '@sentry/server-utils/orchestrion/import-hook';
import '@sentry/server-runtime-injection/import-hook';
1 change: 1 addition & 0 deletions packages/google-cloud-serverless/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"@sentry/conventions": "^0.20.0",
"@sentry/core": "10.67.0",
"@sentry/node": "10.67.0",
"@sentry/server-runtime-injection": "10.67.0",
"@sentry/server-utils": "10.67.0"
},
"devDependencies": {
Expand Down
1 change: 1 addition & 0 deletions packages/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
"@sentry/node": "10.67.0",
"@sentry/opentelemetry": "10.67.0",
"@sentry/react": "10.67.0",
"@sentry/server-runtime-injection": "10.67.0",
"@sentry/server-utils": "10.67.0",
"@sentry/vercel-edge": "10.67.0",
"rollup": "^4.60.3",
Expand Down
18 changes: 9 additions & 9 deletions packages/nextjs/scripts/buildRollup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,28 +24,28 @@ fs.readdirSync(esmTemplateDir).forEach(templateFile =>
);

// Generate the orchestrion runtime forwarders (see `src/config/diagnosticsChannelInjection.ts`)
// from `@sentry/server-utils`' own exports map, so a new subpath there is forwarded automatically.
// Only `require`-able entries get one, since the emitted external is a `require()`. Written as
// plain CJS, not built by rollup: they are loaded by specifier, never bundled.
const SERVER_UTILS = '@sentry/server-utils';
// from `@sentry/server-runtime-injection`' own exports map, so a new subpath there is forwarded
// automatically. Only `require`-able entries get one, since the emitted external is a `require()`.
// Written as plain CJS, not built by rollup: they are loaded by specifier, never bundled.
const RUNTIME_INJECTION = '@sentry/server-runtime-injection';
const orchestrionRuntimeBuildDir = 'build/orchestrion-runtime';

const serverUtilsExports = (
JSON.parse(fs.readFileSync(require.resolve(`${SERVER_UTILS}/package.json`), 'utf8')) as {
const runtimeInjectionExports = (
JSON.parse(fs.readFileSync(require.resolve(`${RUNTIME_INJECTION}/package.json`), 'utf8')) as {
exports: Record<string, { require?: string } | string>;
}
).exports;

for (const [key, conditions] of Object.entries(serverUtilsExports)) {
for (const [key, conditions] of Object.entries(runtimeInjectionExports)) {
if (key === './package.json' || typeof conditions === 'string' || !conditions.require) {
continue;
}

// '.' → 'index', './orchestrion/register' → 'orchestrion/register'
// '.' → 'index', './register' → 'register'
const forwarderPath = path.join(orchestrionRuntimeBuildDir, `${key === '.' ? 'index' : key.slice(2)}.js`);
fs.mkdirSync(path.dirname(forwarderPath), { recursive: true });
fs.writeFileSync(
forwarderPath,
`// Generated by scripts/buildRollup.ts — do not edit.\nmodule.exports = require('${SERVER_UTILS}${key.slice(1)}');\n`,
`// Generated by scripts/buildRollup.ts — do not edit.\nmodule.exports = require('${RUNTIME_INJECTION}${key.slice(1)}');\n`,
);
}
39 changes: 30 additions & 9 deletions packages/nextjs/src/config/diagnosticsChannelInjection.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { resolveOrchestrionRuntimeRequest } from '@sentry/server-utils/orchestrion/webpack';
import { createRequire } from 'node:module';

/**
* Instrumented packages verified (via e2e) to bundle correctly, removed from Sentry's own
Expand All @@ -9,17 +9,38 @@ import { resolveOrchestrionRuntimeRequest } from '@sentry/server-utils/orchestri
export const BUNDLE_SAFE_INSTRUMENTED_PACKAGES = ['ioredis'];

/**
* `@sentry/server-utils` (where `register.ts` and the bundled orchestrion runtime ship) must stay
* external: `register.ts` passes its own `__filename`/`import.meta.url` as the `parentURL` for
* `Module.register('@sentry/server-utils/orchestrion/hook.mjs', …)`, so that self-reference only
* `@sentry/server-runtime-injection` (where `register.ts` and the bundled orchestrion runtime ship)
* must stay external: `register.ts` passes its own `__filename`/`import.meta.url` as the `parentURL`
* for `Module.register('@sentry/server-runtime-injection/hook', …)`, so that self-reference only
* resolves while the code still lives at its real `node_modules` location. Bundled into an app
* server chunk instead, the specifier would have to resolve from the chunk's output location,
* which fails under isolated installs (pnpm) where the package is a transitive dependency.
*
* (The `@apm-js-collab/*` packages no longer appear here: they are bundled into
* `@sentry/server-utils`' build, so no import of them exists at runtime.)
* `@sentry/server-utils` (the barrel + bundler plugins) is NOT here — it is meant to be bundled; the
* build-time snippet's `@sentry/server-utils` import is handled separately by the code-transform.
*/
export const ORCHESTRION_RUNTIME_EXTERNAL_PACKAGES = ['@sentry/server-utils'];
export const ORCHESTRION_RUNTIME_EXTERNAL_PACKAGES = ['@sentry/server-runtime-injection'];

// `require` anchored at THIS package (`@sentry/nextjs`), which depends on
// `@sentry/server-runtime-injection` — so the resolvability check below works even under isolated
// installs (pnpm), where a resolver anchored at `@sentry/server-utils` could not see it.
let nextjsRequire: NodeJS.Require;
/*! rollup-include-cjs-only */
nextjsRequire = createRequire(__filename);
/*! rollup-include-cjs-only-end */
/*! rollup-include-esm-only */
nextjsRequire = createRequire(import.meta.url);
/*! rollup-include-esm-only-end */

/** Whether `request` resolves as a `require`-able module (skips ESM-only subpaths like `/hook`). */
function isRequireResolvable(request: string): boolean {
try {
nextjsRequire.resolve(request);
return true;
} catch {
return false;
}
}

/** Remove the given packages from a `serverExternalPackages` list. */
export function filterInstrumentedExternals(externals: string[], packagesToBundle: string[]): string[] {
Expand All @@ -28,7 +49,7 @@ export function filterInstrumentedExternals(externals: string[], packagesToBundl
}

/**
* Where the generated forwarders live — one CJS one-liner per `@sentry/server-utils` entrypoint
* Where the generated forwarders live — one CJS one-liner per `@sentry/server-runtime-injection` entrypoint
* (see `scripts/buildRollup.ts`). Forwarding through `@sentry/nextjs`, always a direct dependency,
* is what makes the emitted specifier both resolvable from `.next/server/**` and relocation-safe.
*/
Expand Down Expand Up @@ -67,7 +88,7 @@ export async function externalizeOrchestrionRuntimePackages({
}

// Not `require`-able (ESM-only subpath, or a typo): webpack reports it better than we can.
if (!resolveOrchestrionRuntimeRequest(request)) {
if (!isRequireResolvable(request)) {
return undefined;
}

Expand Down
Loading
Loading