diff --git a/packages/bundler-plugin-core/src/api-primitives.ts b/packages/bundler-plugin-core/src/build-plugin-manager.ts similarity index 92% rename from packages/bundler-plugin-core/src/api-primitives.ts rename to packages/bundler-plugin-core/src/build-plugin-manager.ts index bbe7cff3..4bc85e4f 100644 --- a/packages/bundler-plugin-core/src/api-primitives.ts +++ b/packages/bundler-plugin-core/src/build-plugin-manager.ts @@ -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, @@ -27,9 +27,23 @@ import { dynamicSamplingContextToSentryBaggageHeader } from "@sentry/utils"; export type SentryBuildPluginManager = ReturnType; +/** + * 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, @@ -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( @@ -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( @@ -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( @@ -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; diff --git a/packages/bundler-plugin-core/src/debug-id-upload.ts b/packages/bundler-plugin-core/src/debug-id-upload.ts index c894acf1..b9bba5a3 100644 --- a/packages/bundler-plugin-core/src/debug-id-upload.ts +++ b/packages/bundler-plugin-core/src/debug-id-upload.ts @@ -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 diff --git a/packages/bundler-plugin-core/src/index.ts b/packages/bundler-plugin-core/src/index.ts index e8a14a3e..f500195a 100644 --- a/packages/bundler-plugin-core/src/index.ts +++ b/packages/bundler-plugin-core/src/index.ts @@ -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, @@ -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, @@ -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)); @@ -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)); @@ -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(); @@ -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"; diff --git a/packages/bundler-plugin-core/src/sentry/logger.ts b/packages/bundler-plugin-core/src/logger.ts similarity index 100% rename from packages/bundler-plugin-core/src/sentry/logger.ts rename to packages/bundler-plugin-core/src/logger.ts diff --git a/packages/bundler-plugin-core/src/options-mapping.ts b/packages/bundler-plugin-core/src/options-mapping.ts index 83bf11cb..55998ce0 100644 --- a/packages/bundler-plugin-core/src/options-mapping.ts +++ b/packages/bundler-plugin-core/src/options-mapping.ts @@ -1,4 +1,4 @@ -import { Logger } from "./sentry/logger"; +import { Logger } from "./logger"; import { Options as UserOptions, SetCommitsOptions } from "./types"; import { determineReleaseName } from "./utils"; diff --git a/packages/bundler-plugin-core/src/plugins/release-management.ts b/packages/bundler-plugin-core/src/plugins/release-management.ts deleted file mode 100644 index 14fafb7b..00000000 --- a/packages/bundler-plugin-core/src/plugins/release-management.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { UnpluginOptions } from "unplugin"; -import { SentryBuildPluginManager } from "../api-primitives"; - -interface ReleaseManagementPluginOptions { - sentryBuildPluginManager: SentryBuildPluginManager; -} - -/** - * Creates a plugin that creates releases, sets commits, deploys and finalizes releases. - * - * Additionally, if legacy upload options are set, it uploads source maps in the legacy (non-debugId) way. - */ -export function releaseManagementPlugin({ - sentryBuildPluginManager, -}: ReleaseManagementPluginOptions): UnpluginOptions { - const freeGlobalDependencyOnBuildArtifacts = - sentryBuildPluginManager.createDependencyOnBuildArtifacts(); - return { - name: "sentry-release-management-plugin", - async writeBundle() { - try { - await sentryBuildPluginManager.createRelease(); - } finally { - freeGlobalDependencyOnBuildArtifacts(); - } - }, - }; -} diff --git a/packages/bundler-plugin-core/src/plugins/sourcemap-deletion.ts b/packages/bundler-plugin-core/src/plugins/sourcemap-deletion.ts deleted file mode 100644 index 8a34a8da..00000000 --- a/packages/bundler-plugin-core/src/plugins/sourcemap-deletion.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { UnpluginOptions } from "unplugin"; -import { SentryBuildPluginManager } from "../api-primitives"; - -interface FileDeletionPlugin { - sentryBuildPluginManager: SentryBuildPluginManager; -} - -export function fileDeletionPlugin({ - sentryBuildPluginManager, -}: FileDeletionPlugin): UnpluginOptions { - return { - name: "sentry-file-deletion-plugin", - async writeBundle() { - await sentryBuildPluginManager.deleteArtifacts(); - }, - }; -} diff --git a/packages/bundler-plugin-core/src/plugins/telemetry.ts b/packages/bundler-plugin-core/src/plugins/telemetry.ts deleted file mode 100644 index c3201d28..00000000 --- a/packages/bundler-plugin-core/src/plugins/telemetry.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { UnpluginOptions } from "unplugin"; -import { SentryBuildPluginManager } from "../api-primitives"; - -interface TelemetryPluginOptions { - sentryBuildPluginManager: SentryBuildPluginManager; -} - -export function telemetryPlugin({ - sentryBuildPluginManager, -}: TelemetryPluginOptions): UnpluginOptions { - return { - name: "sentry-telemetry-plugin", - async buildStart() { - await sentryBuildPluginManager.telemetry.emitBundlerPluginExecutionSignal(); - }, - }; -} diff --git a/packages/bundler-plugin-core/src/sentry/telemetry.ts b/packages/bundler-plugin-core/src/sentry/telemetry.ts index 81b10c36..3c183ad8 100644 --- a/packages/bundler-plugin-core/src/sentry/telemetry.ts +++ b/packages/bundler-plugin-core/src/sentry/telemetry.ts @@ -13,7 +13,7 @@ const stackParser = createStackParser(nodeStackLineParser()); export function createSentryInstance( options: NormalizedOptions, shouldSendTelemetry: Promise, - bundler: string + buildTool: string ): { sentryScope: Scope; sentryClient: Client } { const clientOptions: ServerRuntimeClientOptions = { platform: "node", @@ -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); @@ -103,7 +107,7 @@ export function setTelemetryDataOnScope(options: NormalizedOptions, scope: Scope scope.setTags({ organization: org, project, - bundler, + bundler: buildTool, }); scope.setUser({ id: org }); @@ -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); diff --git a/packages/bundler-plugin-core/test/sentry/logger.test.ts b/packages/bundler-plugin-core/test/sentry/logger.test.ts index f16d5c89..d63d3bcd 100644 --- a/packages/bundler-plugin-core/test/sentry/logger.test.ts +++ b/packages/bundler-plugin-core/test/sentry/logger.test.ts @@ -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); diff --git a/packages/rollup-plugin/test/public-api.test.ts b/packages/rollup-plugin/test/public-api.test.ts index 3539c887..8a36177c 100644 --- a/packages/rollup-plugin/test/public-api.test.ts +++ b/packages/rollup-plugin/test/public-api.test.ts @@ -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", + ]) + ); }); }); diff --git a/packages/vite-plugin/test/public-api.test.ts b/packages/vite-plugin/test/public-api.test.ts index 5413ddf8..d610eb10 100644 --- a/packages/vite-plugin/test/public-api.test.ts +++ b/packages/vite-plugin/test/public-api.test.ts @@ -22,13 +22,15 @@ describe("sentryVitePlugin", () => { const pluginNames = plugins.map((plugin) => plugin.name); - expect(pluginNames).toEqual([ - "sentry-telemetry-plugin", - "sentry-vite-release-injection-plugin", - "sentry-release-management-plugin", - "sentry-vite-debug-id-injection-plugin", - "sentry-vite-debug-id-upload-plugin", - "sentry-file-deletion-plugin", - ]); + expect(pluginNames).toEqual( + expect.arrayContaining([ + "sentry-telemetry-plugin", + "sentry-vite-release-injection-plugin", + "sentry-release-management-plugin", + "sentry-vite-debug-id-injection-plugin", + "sentry-vite-debug-id-upload-plugin", + "sentry-file-deletion-plugin", + ]) + ); }); });