Skip to content

ref(core): Clean up @sentry/bundler-plugin-core package #716

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Apr 2, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import { normalizeUserOptions, validateOptions } from "./options-mapping";
import { createLogger } from "./sentry/logger";
import { createLogger } from "./logger";
import {
allowedToSendTelemetry,
createSentryInstance,
Expand All @@ -27,9 +27,23 @@ import { dynamicSamplingContextToSentryBaggageHeader } from "@sentry/utils";

export type SentryBuildPluginManager = ReturnType<typeof createSentryBuildPluginManager>;

/**
* Creates a build plugin manager that exposes primitives for everything that a Sentry JavaScript SDK or build tooling may do during a build.
*
* The build plugin manager's behavior strongly depends on the options that are passed in.
*/
export function createSentryBuildPluginManager(
userOptions: Options,
bundlerPluginMetaContext: { buildTool: string; loggerPrefix: string }
bundlerPluginMetaContext: {
/**
* E.g. `webpack` or `nextjs` or `turbopack`
*/
buildTool: string;
/**
* E.g. `[sentry-webpack-plugin]` or `[@sentry/nextjs]`
*/
loggerPrefix: string;
}
) {
const logger = createLogger({
prefix: bundlerPluginMetaContext.loggerPrefix,
Expand Down Expand Up @@ -241,11 +255,35 @@ export function createSentryBuildPluginManager(
}

return {
/**
* A logger instance that takes the options passed to the build plugin manager into account. (for silencing and log level etc.)
*/
logger,

/**
* Options after normalization. Includes things like the inferred release name.
*/
normalizedOptions: options,

/**
* Magic strings and their replacement values that can be used for bundle size optimizations. This already takes
* into account the options passed to the build plugin manager.
*/
bundleSizeOptimizationReplacementValues,

/**
* Metadata that should be injected into bundles if possible. Takes into account options passed to the build plugin manager.
*/
// See `generateModuleMetadataInjectorCode` for how this should be used exactly
bundleMetadata,

/**
* Contains utility functions for emitting telemetry via the build plugin manager.
*/
telemetry: {
/**
* Emits a `Sentry Bundler Plugin execution` signal.
*/
async emitBundlerPluginExecutionSignal() {
if (await shouldSendTelemetry) {
logger.info(
Expand All @@ -258,6 +296,16 @@ export function createSentryBuildPluginManager(
}
},
},

/**
* Will potentially create a release based on the build plugin manager options.
*
* Also
* - finalizes the release
* - sets commits
* - uploads legacy sourcemaps
* - adds deploy information
*/
async createRelease() {
if (!options.release.name) {
logger.debug(
Expand Down Expand Up @@ -367,6 +415,10 @@ export function createSentryBuildPluginManager(
freeWriteBundleInvocationDependencyOnSourcemapFiles();
}
},

/**
* Uploads sourcemaps using the "Debug ID" method. This function takes a list of build artifact paths that will be uploaded
*/
async uploadSourcemaps(buildArtifactPaths: string[]) {
if (options.sourcemaps?.disable) {
logger.debug(
Expand Down Expand Up @@ -547,6 +599,10 @@ export function createSentryBuildPluginManager(
}
);
},

/**
* Will delete artifacts based on the passed `sourcemaps.filesToDeleteAfterUpload` option.
*/
async deleteArtifacts() {
try {
const filesToDelete = await options.sourcemaps?.filesToDeleteAfterUpload;
Expand Down
4 changes: 2 additions & 2 deletions packages/bundler-plugin-core/src/debug-id-upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import fs from "fs";
import path from "path";
import * as util from "util";
import { promisify } from "util";
import { SentryBuildPluginManager } from "./api-primitives";
import { Logger } from "./sentry/logger";
import { SentryBuildPluginManager } from "./build-plugin-manager";
import { Logger } from "./logger";

interface RewriteSourcesHook {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down
80 changes: 35 additions & 45 deletions packages/bundler-plugin-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,9 @@ import { glob } from "glob";
import MagicString from "magic-string";
import * as path from "path";
import { createUnplugin, TransformResult, UnpluginOptions } from "unplugin";
import { createSentryBuildPluginManager } from "./api-primitives";
import { createSentryBuildPluginManager } from "./build-plugin-manager";
import { createDebugIdUploadFunction } from "./debug-id-upload";
import { releaseManagementPlugin } from "./plugins/release-management";
import { fileDeletionPlugin } from "./plugins/sourcemap-deletion";
import { telemetryPlugin } from "./plugins/telemetry";
import { Logger } from "./sentry/logger";
import { Logger } from "./logger";
import { Options, SentrySDKBuildFlags } from "./types";
import {
generateGlobalInjectorCode,
Expand Down Expand Up @@ -40,30 +37,7 @@ interface SentryUnpluginFactoryOptions {
}

/**
* The sentry bundler plugin concerns itself with two things:
* - Release injection
* - Sourcemaps upload
*
* Release injection:
* Per default the sentry bundler plugin will inject a global `SENTRY_RELEASE` into each JavaScript/TypeScript module
* that is part of the bundle. On a technical level this is done by appending an import (`import "sentry-release-injector";`)
* to all entrypoint files of the user code (see `transformInclude` and `transform` hooks). This import is then resolved
* by the sentry plugin to a virtual module that sets the global variable (see `resolveId` and `load` hooks).
* If a user wants to inject the release into a particular set of modules they can use the `releaseInjectionTargets` option.
*
* Source maps upload:
*
* The sentry bundler plugin will also take care of uploading source maps to Sentry. This
* is all done in the `writeBundle` hook. In this hook the sentry plugin will execute the
* release creation pipeline:
*
* 1. Create a new release
* 2. Upload sourcemaps based on `include` and source-map-specific options
* 3. Associate a range of commits with the release (if `setCommits` is specified)
* 4. Finalize the release (unless `finalize` is disabled)
* 5. Add deploy information to the release (if `deploy` is specified)
*
* This release creation pipeline relies on Sentry CLI to execute the different steps.
* Creates an unplugin instance used to create Sentry plugins for Vite, Rollup, esbuild, and Webpack.
*/
export function sentryUnpluginFactory({
releaseInjectionPlugin,
Expand Down Expand Up @@ -103,11 +77,13 @@ export function sentryUnpluginFactory({

const plugins: UnpluginOptions[] = [];

plugins.push(
telemetryPlugin({
sentryBuildPluginManager,
})
);
// Add plugin to emit a telemetry signal when the build starts
plugins.push({
name: "sentry-telemetry-plugin",
async buildStart() {
await sentryBuildPluginManager.telemetry.emitBundlerPluginExecutionSignal();
},
});

if (Object.keys(bundleSizeOptimizationReplacementValues).length > 0) {
plugins.push(bundleSizeOptimizationsPlugin(bundleSizeOptimizationReplacementValues));
Expand Down Expand Up @@ -136,11 +112,19 @@ export function sentryUnpluginFactory({
plugins.push(moduleMetadataInjectionPlugin(injectionCode));
}

plugins.push(
releaseManagementPlugin({
sentryBuildPluginManager,
})
);
// Add plugin to create and finalize releases, and also take care of adding commits and legacy sourcemaps
const freeGlobalDependencyOnBuildArtifacts =
sentryBuildPluginManager.createDependencyOnBuildArtifacts();
plugins.push({
name: "sentry-release-management-plugin",
async writeBundle() {
try {
await sentryBuildPluginManager.createRelease();
} finally {
freeGlobalDependencyOnBuildArtifacts();
}
},
});

if (!options.sourcemaps?.disable) {
plugins.push(debugIdInjectionPlugin(logger));
Expand Down Expand Up @@ -180,16 +164,22 @@ export function sentryUnpluginFactory({
}
}

plugins.push(
fileDeletionPlugin({
sentryBuildPluginManager,
})
);
// Add plugin to delete unwanted artifacts like source maps after the uploads have completed
plugins.push({
name: "sentry-file-deletion-plugin",
async writeBundle() {
await sentryBuildPluginManager.deleteArtifacts();
},
});

return plugins;
});
}

/**
* @deprecated
*/
// TODO(v4): Don't export this from the package
export function getBuildInformation() {
const packageJson = getPackageJson();

Expand Down Expand Up @@ -458,6 +448,6 @@ export function getDebugIdSnippet(debugId: string): string {
return `;{try{let e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="${debugId}",e._sentryDebugIdIdentifier="sentry-dbid-${debugId}")}catch(e){}};`;
}

export type { Logger } from "./sentry/logger";
export type { Logger } from "./logger";
export type { Options, SentrySDKBuildFlags } from "./types";
export { replaceBooleanFlagsInCode, stringToUUID } from "./utils";
2 changes: 1 addition & 1 deletion packages/bundler-plugin-core/src/options-mapping.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Logger } from "./sentry/logger";
import { Logger } from "./logger";
import { Options as UserOptions, SetCommitsOptions } from "./types";
import { determineReleaseName } from "./utils";

Expand Down
28 changes: 0 additions & 28 deletions packages/bundler-plugin-core/src/plugins/release-management.ts

This file was deleted.

17 changes: 0 additions & 17 deletions packages/bundler-plugin-core/src/plugins/sourcemap-deletion.ts

This file was deleted.

17 changes: 0 additions & 17 deletions packages/bundler-plugin-core/src/plugins/telemetry.ts

This file was deleted.

14 changes: 9 additions & 5 deletions packages/bundler-plugin-core/src/sentry/telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const stackParser = createStackParser(nodeStackLineParser());
export function createSentryInstance(
options: NormalizedOptions,
shouldSendTelemetry: Promise<boolean>,
bundler: string
buildTool: string
): { sentryScope: Scope; sentryClient: Client } {
const clientOptions: ServerRuntimeClientOptions = {
platform: "node",
Expand Down Expand Up @@ -55,12 +55,16 @@ export function createSentryInstance(
const scope = new Scope();
scope.setClient(client);

setTelemetryDataOnScope(options, scope, bundler);
setTelemetryDataOnScope(options, scope, buildTool);

return { sentryScope: scope, sentryClient: client };
}

export function setTelemetryDataOnScope(options: NormalizedOptions, scope: Scope, bundler: string) {
export function setTelemetryDataOnScope(
options: NormalizedOptions,
scope: Scope,
buildTool: string
) {
const { org, project, release, errorHandler, sourcemaps, reactComponentAnnotation } = options;

scope.setTag("upload-legacy-sourcemaps", !!release.uploadLegacySourcemaps);
Expand Down Expand Up @@ -103,7 +107,7 @@ export function setTelemetryDataOnScope(options: NormalizedOptions, scope: Scope
scope.setTags({
organization: org,
project,
bundler,
bundler: buildTool,
});

scope.setUser({ id: org });
Expand Down Expand Up @@ -134,7 +138,7 @@ export async function allowedToSendTelemetry(options: NormalizedOptions): Promis
let cliInfo;
try {
// Makes a call to SentryCLI to get the Sentry server URL the CLI uses.
// We need to check and decide to use telemetry based on the CLI's respone to this call
// We need to check and decide to use telemetry based on the CLI's response to this call
// because only at this time we checked a possibly existing .sentryclirc file. This file
// could point to another URL than the default URL.
cliInfo = await cli.execute(["info"], false);
Expand Down
2 changes: 1 addition & 1 deletion packages/bundler-plugin-core/test/sentry/logger.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createLogger } from "../../src/sentry/logger";
import { createLogger } from "../../src/logger";

describe("Logger", () => {
const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(() => undefined);
Expand Down
18 changes: 10 additions & 8 deletions packages/rollup-plugin/test/public-api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ describe("sentryRollupPlugin", () => {

const pluginNames = plugins.map((plugin) => plugin.name);

expect(pluginNames).toEqual([
"sentry-telemetry-plugin",
"sentry-rollup-release-injection-plugin",
"sentry-release-management-plugin",
"sentry-rollup-debug-id-injection-plugin",
"sentry-rollup-debug-id-upload-plugin",
"sentry-file-deletion-plugin",
]);
expect(pluginNames).toEqual(
expect.arrayContaining([
"sentry-telemetry-plugin",
"sentry-rollup-release-injection-plugin",
"sentry-release-management-plugin",
"sentry-rollup-debug-id-injection-plugin",
"sentry-rollup-debug-id-upload-plugin",
"sentry-file-deletion-plugin",
])
);
});
});
Loading