From 6ef0bfc6ae0410d87794d0e029f7f23f43f65cc9 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Wed, 28 Jun 2023 11:38:35 +0200 Subject: [PATCH 1/6] feat: Add module metadata injection --- packages/bundler-plugin-core/src/index.ts | 24 +++++++++++++++++++++++ packages/bundler-plugin-core/src/types.ts | 17 ++++++++++++++++ packages/bundler-plugin-core/src/utils.ts | 18 +++++++++++++++++ packages/webpack-plugin/src/index.ts | 22 +++++++++++++++++++++ 4 files changed, 81 insertions(+) diff --git a/packages/bundler-plugin-core/src/index.ts b/packages/bundler-plugin-core/src/index.ts index 0132b73e..516e6b5e 100644 --- a/packages/bundler-plugin-core/src/index.ts +++ b/packages/bundler-plugin-core/src/index.ts @@ -12,6 +12,7 @@ import { allowedToSendTelemetry, createSentryInstance } from "./sentry/telemetry import { Options } from "./types"; import { generateGlobalInjectorCode, + generateModuleMetadataInjectorCode, getDependencies, getPackageJson, parseMajorVersion, @@ -21,6 +22,7 @@ import { interface SentryUnpluginFactoryOptions { releaseInjectionPlugin: (injectionCode: string) => UnpluginOptions; + moduleMetadataInjectionPlugin?: (injectionCode: string) => UnpluginOptions; debugIdInjectionPlugin: () => UnpluginOptions; debugIdUploadPlugin: (upload: (buildArtifacts: string[]) => Promise) => UnpluginOptions; } @@ -54,6 +56,7 @@ interface SentryUnpluginFactoryOptions { */ export function sentryUnpluginFactory({ releaseInjectionPlugin, + moduleMetadataInjectionPlugin, debugIdInjectionPlugin, debugIdUploadPlugin, }: SentryUnpluginFactoryOptions) { @@ -159,6 +162,27 @@ export function sentryUnpluginFactory({ plugins.push(releaseInjectionPlugin(injectionCode)); } + if (moduleMetadataInjectionPlugin && options._experiments.moduleMetadata) { + let metadata: object; + if (typeof options._experiments.moduleMetadata === "function") { + const args = { + org: options.org, + project: options.project, + release: options.release.name, + dist: options.release.dist, + }; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + metadata = options._experiments.moduleMetadata(args); + } else { + metadata = options._experiments.moduleMetadata; + } + + const injectionCode = generateModuleMetadataInjectorCode(metadata); + plugins.push(moduleMetadataInjectionPlugin(injectionCode)); + } else if (options._experiments.moduleMetadata) { + logger.warn("'moduleMetadata' is currently only supported by '@sentry/webpack-plugin'"); + } + if (!options.release.name) { logger.warn( "No release name provided. Will not create release. Please set the `release.name` option to identifiy your release." diff --git a/packages/bundler-plugin-core/src/types.ts b/packages/bundler-plugin-core/src/types.ts index 9235d28f..1c9ad5c6 100644 --- a/packages/bundler-plugin-core/src/types.ts +++ b/packages/bundler-plugin-core/src/types.ts @@ -241,9 +241,26 @@ export interface Options { * Defaults to `false`. */ injectBuildInformation?: boolean; + + /** + * Metadata associated with this module. + * The metadata is serialized and can be looked up at runtime by URL. + * + * Note: This option is currently only supported by `@sentry/webpack-plugin`. + */ + moduleMetadata?: object | ModuleMetadataCallback; }; } +export interface ModuleMetadataCallbackArgs { + org?: string; + project?: string; + release?: string; + dist?: string; +} + +export type ModuleMetadataCallback = (args: ModuleMetadataCallbackArgs) => object; + export type IncludeEntry = { /** * One or more paths to scan for files to upload. diff --git a/packages/bundler-plugin-core/src/utils.ts b/packages/bundler-plugin-core/src/utils.ts index cdafa84d..e6b0cc6e 100644 --- a/packages/bundler-plugin-core/src/utils.ts +++ b/packages/bundler-plugin-core/src/utils.ts @@ -264,6 +264,24 @@ export function generateGlobalInjectorCode({ return code; } +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function generateModuleMetadataInjectorCode(metadata: any) { + // The code below is mostly ternary operators because it saves bundle size. + // The checks are to support as many environments as possible. (Node.js, Browser, webworkers, etc.) + return ` + var _global2 = + typeof window !== 'undefined' ? + window : + typeof global !== 'undefined' ? + global : + typeof self !== 'undefined' ? + self : + {}; + + _global2.__MODULE_METADATA__ = _global2.__MODULE_METADATA__ || {}; + _global2.__MODULE_METADATA__[new Error().stack] = ${JSON.stringify(metadata)};`; +} + function getBuildInformation() { const packageJson = getPackageJson(); diff --git a/packages/webpack-plugin/src/index.ts b/packages/webpack-plugin/src/index.ts index 36b2ddf3..5e98be0f 100644 --- a/packages/webpack-plugin/src/index.ts +++ b/packages/webpack-plugin/src/index.ts @@ -84,8 +84,30 @@ function webpackDebugIdUploadPlugin( }; } +function webpackModuleMetadataInjectionPlugin(injectionCode: string): UnpluginOptions { + return { + name: "sentry-webpack-module-metadata-injection-plugin", + webpack(compiler) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore webpack version compatibility shenanigans + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + const BannerPlugin = compiler?.webpack?.BannerPlugin || webback4or5?.BannerPlugin; + compiler.options.plugins = compiler.options.plugins || []; + compiler.options.plugins.push( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call + new BannerPlugin({ + raw: true, + include: /\.(js|ts|jsx|tsx|mjs|cjs)$/, + banner: injectionCode, + }) + ); + }, + }; +} + const sentryUnplugin = sentryUnpluginFactory({ releaseInjectionPlugin: webpackReleaseInjectionPlugin, + moduleMetadataInjectionPlugin: webpackModuleMetadataInjectionPlugin, debugIdInjectionPlugin: webpackDebugIdInjectionPlugin, debugIdUploadPlugin: webpackDebugIdUploadPlugin, }); From 9dbe23ba42b312f6f7ff9c43bf14a056a3df4028 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Thu, 29 Jun 2023 21:01:20 +0200 Subject: [PATCH 2/6] Add e2e test --- .../e2e-tests/scenarios/basic-upload/setup.ts | 3 +- .../scenarios/module-metadata/config.ts | 13 ++ .../scenarios/module-metadata/input/index.js | 2 + .../module-metadata/module-metadata.test.ts | 21 ++ .../scenarios/module-metadata/setup.ts | 10 + packages/e2e-tests/utils/bundlers.ts | 2 + .../e2e-tests/utils/create-cjs-bundles.ts | 221 +++++++++--------- 7 files changed, 166 insertions(+), 106 deletions(-) create mode 100644 packages/e2e-tests/scenarios/module-metadata/config.ts create mode 100644 packages/e2e-tests/scenarios/module-metadata/input/index.js create mode 100644 packages/e2e-tests/scenarios/module-metadata/module-metadata.test.ts create mode 100644 packages/e2e-tests/scenarios/module-metadata/setup.ts diff --git a/packages/e2e-tests/scenarios/basic-upload/setup.ts b/packages/e2e-tests/scenarios/basic-upload/setup.ts index f6c4a4f4..45a961cf 100644 --- a/packages/e2e-tests/scenarios/basic-upload/setup.ts +++ b/packages/e2e-tests/scenarios/basic-upload/setup.ts @@ -3,13 +3,14 @@ import * as path from "path"; import { pluginConfig } from "./config"; import { deleteAllReleases } from "../../utils/releases"; import { createCjsBundles } from "../../utils/create-cjs-bundles"; +import { BUNDLERS } from "../../utils/bundlers"; deleteAllReleases(pluginConfig.release?.name || "") .then(() => { const entryPointPath = path.resolve(__dirname, "input", "index.js"); const outputDir = path.resolve(__dirname, "out"); - createCjsBundles({ index: entryPointPath }, outputDir, pluginConfig); + createCjsBundles(BUNDLERS, { index: entryPointPath }, outputDir, pluginConfig); }) .catch(() => { console.error("Could not delete release!"); diff --git a/packages/e2e-tests/scenarios/module-metadata/config.ts b/packages/e2e-tests/scenarios/module-metadata/config.ts new file mode 100644 index 00000000..1ee5aff4 --- /dev/null +++ b/packages/e2e-tests/scenarios/module-metadata/config.ts @@ -0,0 +1,13 @@ +import { Options } from "@sentry/bundler-plugin-core"; + +/** + * The Sentry bundler plugin config object used for this test + */ +export const pluginConfig: Options = { + release: { + name: "basic-upload", + }, + _experiments: { + moduleMetadata: { team: "frontend" }, + }, +}; diff --git a/packages/e2e-tests/scenarios/module-metadata/input/index.js b/packages/e2e-tests/scenarios/module-metadata/input/index.js new file mode 100644 index 00000000..e5c46bbd --- /dev/null +++ b/packages/e2e-tests/scenarios/module-metadata/input/index.js @@ -0,0 +1,2 @@ +// Simply output the metadata to the console so it can be checked in a test +console.log(JSON.stringify(global.__MODULE_METADATA__)); diff --git a/packages/e2e-tests/scenarios/module-metadata/module-metadata.test.ts b/packages/e2e-tests/scenarios/module-metadata/module-metadata.test.ts new file mode 100644 index 00000000..588f9e67 --- /dev/null +++ b/packages/e2e-tests/scenarios/module-metadata/module-metadata.test.ts @@ -0,0 +1,21 @@ +import * as path from "path"; +import { execSync } from "child_process"; + +import { MODULE_META_BUNDLERS } from "../../utils/bundlers"; + +describe("Module metadata injection", () => { + it.each(MODULE_META_BUNDLERS)("Metadata is injected", (bundler) => { + // Run the bundle and parse the JSON output + const bundle = path.join(__dirname, "out", bundler, "index.js"); + const output = execSync(`node ${bundle}`).toString(); + const map = JSON.parse(output) as Record; + + // There should be only one key in the map + expect(Object.keys(map)).toHaveLength(1); + + // and that key should have the correct metadata + for (const key in map) { + expect(map[key]).toEqual({ team: "frontend" }); + } + }); +}); diff --git a/packages/e2e-tests/scenarios/module-metadata/setup.ts b/packages/e2e-tests/scenarios/module-metadata/setup.ts new file mode 100644 index 00000000..aac4bb4e --- /dev/null +++ b/packages/e2e-tests/scenarios/module-metadata/setup.ts @@ -0,0 +1,10 @@ +import * as path from "path"; + +import { pluginConfig } from "./config"; +import { createCjsBundles } from "../../utils/create-cjs-bundles"; +import { MODULE_META_BUNDLERS } from "../../utils/bundlers"; + +const entryPointPath = path.resolve(__dirname, "input", "index.js"); +const outputDir = path.resolve(__dirname, "out"); + +createCjsBundles(MODULE_META_BUNDLERS, { index: entryPointPath }, outputDir, pluginConfig); diff --git a/packages/e2e-tests/utils/bundlers.ts b/packages/e2e-tests/utils/bundlers.ts index 358ebbb1..57d5436d 100644 --- a/packages/e2e-tests/utils/bundlers.ts +++ b/packages/e2e-tests/utils/bundlers.ts @@ -1 +1,3 @@ export const BUNDLERS = ["rollup", "vite", "esbuild", "webpack4", "webpack5"]; +// Bundlers that support injecting module metadata +export const MODULE_META_BUNDLERS = ["webpack4", "webpack5"]; diff --git a/packages/e2e-tests/utils/create-cjs-bundles.ts b/packages/e2e-tests/utils/create-cjs-bundles.ts index ebd55151..235450bb 100644 --- a/packages/e2e-tests/utils/create-cjs-bundles.ts +++ b/packages/e2e-tests/utils/create-cjs-bundles.ts @@ -12,6 +12,7 @@ import { sentryEsbuildPlugin } from "@sentry/esbuild-plugin"; import { sentryRollupPlugin } from "@sentry/rollup-plugin"; export function createCjsBundles( + bundlers: string[], entrypoints: { [name: string]: string }, outFolder: string, sentryUnpluginOptions: Options @@ -21,134 +22,144 @@ export function createCjsBundles( return; } - void vite.build({ - clearScreen: false, - build: { - sourcemap: true, - outDir: path.join(outFolder, "vite"), - rollupOptions: { - input: entrypoints, - output: { - format: "cjs", - entryFileNames: "[name].js", + if (bundlers.includes("vite")) { + void vite.build({ + clearScreen: false, + build: { + sourcemap: true, + outDir: path.join(outFolder, "vite"), + rollupOptions: { + input: entrypoints, + output: { + format: "cjs", + entryFileNames: "[name].js", + }, }, }, - }, - plugins: [ - sentryVitePlugin({ - ...sentryUnpluginOptions, - release: { - name: `${sentryUnpluginOptions.release.name!}-vite`, - uploadLegacySourcemaps: `${ - sentryUnpluginOptions.release.uploadLegacySourcemaps as string - }/vite`, - }, - }), - ], - }); - - void rollup - .rollup({ - input: entrypoints, plugins: [ - sentryRollupPlugin({ + sentryVitePlugin({ ...sentryUnpluginOptions, release: { - name: `${sentryUnpluginOptions.release.name!}-rollup`, + name: `${sentryUnpluginOptions.release.name!}-vite`, uploadLegacySourcemaps: `${ sentryUnpluginOptions.release.uploadLegacySourcemaps as string - }/rollup`, + }/vite`, }, }), ], - }) - .then((bundle) => - bundle.write({ - sourcemap: true, - dir: path.join(outFolder, "rollup"), - format: "cjs", - exports: "named", - }) - ); + }); + } - void esbuild.build({ - entryPoints: entrypoints, - outdir: path.join(outFolder, "esbuild"), - sourcemap: true, - plugins: [ - sentryEsbuildPlugin({ - ...sentryUnpluginOptions, - release: { - name: `${sentryUnpluginOptions.release.name!}-esbuild`, - uploadLegacySourcemaps: `${ - sentryUnpluginOptions.release.uploadLegacySourcemaps as string - }/esbuild`, - }, - }), - ], - minify: true, - bundle: true, - format: "cjs", - }); + if (bundlers.includes("rollup")) { + void rollup + .rollup({ + input: entrypoints, + plugins: [ + sentryRollupPlugin({ + ...sentryUnpluginOptions, + release: { + name: `${sentryUnpluginOptions.release.name!}-rollup`, + uploadLegacySourcemaps: `${ + sentryUnpluginOptions.release.uploadLegacySourcemaps as string + }/rollup`, + }, + }), + ], + }) + .then((bundle) => + bundle.write({ + sourcemap: true, + dir: path.join(outFolder, "rollup"), + format: "cjs", + exports: "named", + }) + ); + } - webpack4( - { - devtool: "source-map", - mode: "production", - entry: entrypoints, - cache: false, - output: { - path: path.join(outFolder, "webpack4"), - libraryTarget: "commonjs", - }, - target: "node", // needed for webpack 4 so we can access node api + if (bundlers.includes("esbuild")) { + void esbuild.build({ + entryPoints: entrypoints, + outdir: path.join(outFolder, "esbuild"), + sourcemap: true, plugins: [ - sentryWebpackPlugin({ + sentryEsbuildPlugin({ ...sentryUnpluginOptions, release: { - name: `${sentryUnpluginOptions.release.name!}-webpack4`, + name: `${sentryUnpluginOptions.release.name!}-esbuild`, uploadLegacySourcemaps: `${ sentryUnpluginOptions.release.uploadLegacySourcemaps as string - }/webpack4`, + }/esbuild`, }, }), ], - }, - (err) => { - if (err) { - throw err; - } - } - ); + minify: true, + bundle: true, + format: "cjs", + }); + } - webpack5( - { - devtool: "source-map", - cache: false, - entry: entrypoints, - output: { - path: path.join(outFolder, "webpack5"), - library: { - type: "commonjs", + if (bundlers.includes("webpack4")) { + webpack4( + { + devtool: "source-map", + mode: "production", + entry: entrypoints, + cache: false, + output: { + path: path.join(outFolder, "webpack4"), + libraryTarget: "commonjs", }, + target: "node", // needed for webpack 4 so we can access node api + plugins: [ + sentryWebpackPlugin({ + ...sentryUnpluginOptions, + release: { + name: `${sentryUnpluginOptions.release.name!}-webpack4`, + uploadLegacySourcemaps: `${ + sentryUnpluginOptions.release.uploadLegacySourcemaps as string + }/webpack4`, + }, + }), + ], }, - mode: "production", - plugins: [ - sentryWebpackPlugin({ - ...sentryUnpluginOptions, - release: { - name: `${sentryUnpluginOptions.release.name!}-webpack5`, - uploadLegacySourcemaps: `${ - sentryUnpluginOptions.release.uploadLegacySourcemaps as string - }/webpack5`, + (err) => { + if (err) { + throw err; + } + } + ); + } + + if (bundlers.includes("webpack5")) { + webpack5( + { + devtool: "source-map", + cache: false, + entry: entrypoints, + output: { + path: path.join(outFolder, "webpack5"), + library: { + type: "commonjs", }, - }), - ], - }, - (err) => { - if (err) { - throw err; + }, + mode: "production", + plugins: [ + sentryWebpackPlugin({ + ...sentryUnpluginOptions, + release: { + name: `${sentryUnpluginOptions.release.name!}-webpack5`, + uploadLegacySourcemaps: `${ + sentryUnpluginOptions.release.uploadLegacySourcemaps as string + }/webpack5`, + }, + }), + ], + }, + (err) => { + if (err) { + throw err; + } } - } - ); + ); + } } From e22f755ad6d4193026bf5bc826ccf5e1601fb066 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Thu, 29 Jun 2023 21:02:29 +0200 Subject: [PATCH 3/6] Remove `dist` --- packages/bundler-plugin-core/src/index.ts | 1 - packages/bundler-plugin-core/src/types.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/bundler-plugin-core/src/index.ts b/packages/bundler-plugin-core/src/index.ts index 4854b909..96163e7c 100644 --- a/packages/bundler-plugin-core/src/index.ts +++ b/packages/bundler-plugin-core/src/index.ts @@ -183,7 +183,6 @@ export function sentryUnpluginFactory({ org: options.org, project: options.project, release: options.release.name, - dist: options.release.dist, }; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment metadata = options._experiments.moduleMetadata(args); diff --git a/packages/bundler-plugin-core/src/types.ts b/packages/bundler-plugin-core/src/types.ts index fec8cb60..396a2be8 100644 --- a/packages/bundler-plugin-core/src/types.ts +++ b/packages/bundler-plugin-core/src/types.ts @@ -256,7 +256,6 @@ export interface ModuleMetadataCallbackArgs { org?: string; project?: string; release?: string; - dist?: string; } export type ModuleMetadataCallback = (args: ModuleMetadataCallbackArgs) => object; From 094ba700b330f8eee6c5c1c2cf7af62a9d25bf5b Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Mon, 3 Jul 2023 14:49:08 +0200 Subject: [PATCH 4/6] Revert "Add e2e test" This reverts commit 9dbe23ba42b312f6f7ff9c43bf14a056a3df4028. --- .../e2e-tests/scenarios/basic-upload/setup.ts | 3 +- .../scenarios/module-metadata/config.ts | 13 -- .../scenarios/module-metadata/input/index.js | 2 - .../module-metadata/module-metadata.test.ts | 21 -- .../scenarios/module-metadata/setup.ts | 10 - packages/e2e-tests/utils/bundlers.ts | 2 - .../e2e-tests/utils/create-cjs-bundles.ts | 221 +++++++++--------- 7 files changed, 106 insertions(+), 166 deletions(-) delete mode 100644 packages/e2e-tests/scenarios/module-metadata/config.ts delete mode 100644 packages/e2e-tests/scenarios/module-metadata/input/index.js delete mode 100644 packages/e2e-tests/scenarios/module-metadata/module-metadata.test.ts delete mode 100644 packages/e2e-tests/scenarios/module-metadata/setup.ts diff --git a/packages/e2e-tests/scenarios/basic-upload/setup.ts b/packages/e2e-tests/scenarios/basic-upload/setup.ts index 45a961cf..f6c4a4f4 100644 --- a/packages/e2e-tests/scenarios/basic-upload/setup.ts +++ b/packages/e2e-tests/scenarios/basic-upload/setup.ts @@ -3,14 +3,13 @@ import * as path from "path"; import { pluginConfig } from "./config"; import { deleteAllReleases } from "../../utils/releases"; import { createCjsBundles } from "../../utils/create-cjs-bundles"; -import { BUNDLERS } from "../../utils/bundlers"; deleteAllReleases(pluginConfig.release?.name || "") .then(() => { const entryPointPath = path.resolve(__dirname, "input", "index.js"); const outputDir = path.resolve(__dirname, "out"); - createCjsBundles(BUNDLERS, { index: entryPointPath }, outputDir, pluginConfig); + createCjsBundles({ index: entryPointPath }, outputDir, pluginConfig); }) .catch(() => { console.error("Could not delete release!"); diff --git a/packages/e2e-tests/scenarios/module-metadata/config.ts b/packages/e2e-tests/scenarios/module-metadata/config.ts deleted file mode 100644 index 1ee5aff4..00000000 --- a/packages/e2e-tests/scenarios/module-metadata/config.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Options } from "@sentry/bundler-plugin-core"; - -/** - * The Sentry bundler plugin config object used for this test - */ -export const pluginConfig: Options = { - release: { - name: "basic-upload", - }, - _experiments: { - moduleMetadata: { team: "frontend" }, - }, -}; diff --git a/packages/e2e-tests/scenarios/module-metadata/input/index.js b/packages/e2e-tests/scenarios/module-metadata/input/index.js deleted file mode 100644 index e5c46bbd..00000000 --- a/packages/e2e-tests/scenarios/module-metadata/input/index.js +++ /dev/null @@ -1,2 +0,0 @@ -// Simply output the metadata to the console so it can be checked in a test -console.log(JSON.stringify(global.__MODULE_METADATA__)); diff --git a/packages/e2e-tests/scenarios/module-metadata/module-metadata.test.ts b/packages/e2e-tests/scenarios/module-metadata/module-metadata.test.ts deleted file mode 100644 index 588f9e67..00000000 --- a/packages/e2e-tests/scenarios/module-metadata/module-metadata.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import * as path from "path"; -import { execSync } from "child_process"; - -import { MODULE_META_BUNDLERS } from "../../utils/bundlers"; - -describe("Module metadata injection", () => { - it.each(MODULE_META_BUNDLERS)("Metadata is injected", (bundler) => { - // Run the bundle and parse the JSON output - const bundle = path.join(__dirname, "out", bundler, "index.js"); - const output = execSync(`node ${bundle}`).toString(); - const map = JSON.parse(output) as Record; - - // There should be only one key in the map - expect(Object.keys(map)).toHaveLength(1); - - // and that key should have the correct metadata - for (const key in map) { - expect(map[key]).toEqual({ team: "frontend" }); - } - }); -}); diff --git a/packages/e2e-tests/scenarios/module-metadata/setup.ts b/packages/e2e-tests/scenarios/module-metadata/setup.ts deleted file mode 100644 index aac4bb4e..00000000 --- a/packages/e2e-tests/scenarios/module-metadata/setup.ts +++ /dev/null @@ -1,10 +0,0 @@ -import * as path from "path"; - -import { pluginConfig } from "./config"; -import { createCjsBundles } from "../../utils/create-cjs-bundles"; -import { MODULE_META_BUNDLERS } from "../../utils/bundlers"; - -const entryPointPath = path.resolve(__dirname, "input", "index.js"); -const outputDir = path.resolve(__dirname, "out"); - -createCjsBundles(MODULE_META_BUNDLERS, { index: entryPointPath }, outputDir, pluginConfig); diff --git a/packages/e2e-tests/utils/bundlers.ts b/packages/e2e-tests/utils/bundlers.ts index 57d5436d..358ebbb1 100644 --- a/packages/e2e-tests/utils/bundlers.ts +++ b/packages/e2e-tests/utils/bundlers.ts @@ -1,3 +1 @@ export const BUNDLERS = ["rollup", "vite", "esbuild", "webpack4", "webpack5"]; -// Bundlers that support injecting module metadata -export const MODULE_META_BUNDLERS = ["webpack4", "webpack5"]; diff --git a/packages/e2e-tests/utils/create-cjs-bundles.ts b/packages/e2e-tests/utils/create-cjs-bundles.ts index 235450bb..ebd55151 100644 --- a/packages/e2e-tests/utils/create-cjs-bundles.ts +++ b/packages/e2e-tests/utils/create-cjs-bundles.ts @@ -12,7 +12,6 @@ import { sentryEsbuildPlugin } from "@sentry/esbuild-plugin"; import { sentryRollupPlugin } from "@sentry/rollup-plugin"; export function createCjsBundles( - bundlers: string[], entrypoints: { [name: string]: string }, outFolder: string, sentryUnpluginOptions: Options @@ -22,144 +21,134 @@ export function createCjsBundles( return; } - if (bundlers.includes("vite")) { - void vite.build({ - clearScreen: false, - build: { - sourcemap: true, - outDir: path.join(outFolder, "vite"), - rollupOptions: { - input: entrypoints, - output: { - format: "cjs", - entryFileNames: "[name].js", - }, + void vite.build({ + clearScreen: false, + build: { + sourcemap: true, + outDir: path.join(outFolder, "vite"), + rollupOptions: { + input: entrypoints, + output: { + format: "cjs", + entryFileNames: "[name].js", }, }, + }, + plugins: [ + sentryVitePlugin({ + ...sentryUnpluginOptions, + release: { + name: `${sentryUnpluginOptions.release.name!}-vite`, + uploadLegacySourcemaps: `${ + sentryUnpluginOptions.release.uploadLegacySourcemaps as string + }/vite`, + }, + }), + ], + }); + + void rollup + .rollup({ + input: entrypoints, plugins: [ - sentryVitePlugin({ + sentryRollupPlugin({ ...sentryUnpluginOptions, release: { - name: `${sentryUnpluginOptions.release.name!}-vite`, + name: `${sentryUnpluginOptions.release.name!}-rollup`, uploadLegacySourcemaps: `${ sentryUnpluginOptions.release.uploadLegacySourcemaps as string - }/vite`, + }/rollup`, }, }), ], - }); - } - - if (bundlers.includes("rollup")) { - void rollup - .rollup({ - input: entrypoints, - plugins: [ - sentryRollupPlugin({ - ...sentryUnpluginOptions, - release: { - name: `${sentryUnpluginOptions.release.name!}-rollup`, - uploadLegacySourcemaps: `${ - sentryUnpluginOptions.release.uploadLegacySourcemaps as string - }/rollup`, - }, - }), - ], + }) + .then((bundle) => + bundle.write({ + sourcemap: true, + dir: path.join(outFolder, "rollup"), + format: "cjs", + exports: "named", }) - .then((bundle) => - bundle.write({ - sourcemap: true, - dir: path.join(outFolder, "rollup"), - format: "cjs", - exports: "named", - }) - ); - } + ); - if (bundlers.includes("esbuild")) { - void esbuild.build({ - entryPoints: entrypoints, - outdir: path.join(outFolder, "esbuild"), - sourcemap: true, + void esbuild.build({ + entryPoints: entrypoints, + outdir: path.join(outFolder, "esbuild"), + sourcemap: true, + plugins: [ + sentryEsbuildPlugin({ + ...sentryUnpluginOptions, + release: { + name: `${sentryUnpluginOptions.release.name!}-esbuild`, + uploadLegacySourcemaps: `${ + sentryUnpluginOptions.release.uploadLegacySourcemaps as string + }/esbuild`, + }, + }), + ], + minify: true, + bundle: true, + format: "cjs", + }); + + webpack4( + { + devtool: "source-map", + mode: "production", + entry: entrypoints, + cache: false, + output: { + path: path.join(outFolder, "webpack4"), + libraryTarget: "commonjs", + }, + target: "node", // needed for webpack 4 so we can access node api plugins: [ - sentryEsbuildPlugin({ + sentryWebpackPlugin({ ...sentryUnpluginOptions, release: { - name: `${sentryUnpluginOptions.release.name!}-esbuild`, + name: `${sentryUnpluginOptions.release.name!}-webpack4`, uploadLegacySourcemaps: `${ sentryUnpluginOptions.release.uploadLegacySourcemaps as string - }/esbuild`, + }/webpack4`, }, }), ], - minify: true, - bundle: true, - format: "cjs", - }); - } - - if (bundlers.includes("webpack4")) { - webpack4( - { - devtool: "source-map", - mode: "production", - entry: entrypoints, - cache: false, - output: { - path: path.join(outFolder, "webpack4"), - libraryTarget: "commonjs", - }, - target: "node", // needed for webpack 4 so we can access node api - plugins: [ - sentryWebpackPlugin({ - ...sentryUnpluginOptions, - release: { - name: `${sentryUnpluginOptions.release.name!}-webpack4`, - uploadLegacySourcemaps: `${ - sentryUnpluginOptions.release.uploadLegacySourcemaps as string - }/webpack4`, - }, - }), - ], - }, - (err) => { - if (err) { - throw err; - } + }, + (err) => { + if (err) { + throw err; } - ); - } + } + ); - if (bundlers.includes("webpack5")) { - webpack5( - { - devtool: "source-map", - cache: false, - entry: entrypoints, - output: { - path: path.join(outFolder, "webpack5"), - library: { - type: "commonjs", - }, + webpack5( + { + devtool: "source-map", + cache: false, + entry: entrypoints, + output: { + path: path.join(outFolder, "webpack5"), + library: { + type: "commonjs", }, - mode: "production", - plugins: [ - sentryWebpackPlugin({ - ...sentryUnpluginOptions, - release: { - name: `${sentryUnpluginOptions.release.name!}-webpack5`, - uploadLegacySourcemaps: `${ - sentryUnpluginOptions.release.uploadLegacySourcemaps as string - }/webpack5`, - }, - }), - ], }, - (err) => { - if (err) { - throw err; - } + mode: "production", + plugins: [ + sentryWebpackPlugin({ + ...sentryUnpluginOptions, + release: { + name: `${sentryUnpluginOptions.release.name!}-webpack5`, + uploadLegacySourcemaps: `${ + sentryUnpluginOptions.release.uploadLegacySourcemaps as string + }/webpack5`, + }, + }), + ], + }, + (err) => { + if (err) { + throw err; } - ); - } + } + ); } From 215cb0bd2689c97644ba04e7fef288bd5da7081e Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Mon, 3 Jul 2023 15:19:56 +0200 Subject: [PATCH 5/6] PR review changes --- packages/bundler-plugin-core/src/types.ts | 11 ++++++++++- packages/bundler-plugin-core/src/utils.ts | 4 ++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/bundler-plugin-core/src/types.ts b/packages/bundler-plugin-core/src/types.ts index 396a2be8..b8bd1f04 100644 --- a/packages/bundler-plugin-core/src/types.ts +++ b/packages/bundler-plugin-core/src/types.ts @@ -244,11 +244,20 @@ export interface Options { /** * Metadata associated with this module. + * * The metadata is serialized and can be looked up at runtime by URL. * + * Metadata can either be passed directly or alternatively a callback can be provided that will be + * called with the following arguments: + * - `org`: The organization slug. + * - `project`: The project slug. + * - `release`: The release name. + * + * * Note: This option is currently only supported by `@sentry/webpack-plugin`. */ - moduleMetadata?: object | ModuleMetadataCallback; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + moduleMetadata?: any | ModuleMetadataCallback; }; } diff --git a/packages/bundler-plugin-core/src/utils.ts b/packages/bundler-plugin-core/src/utils.ts index 4e82d186..479e470c 100644 --- a/packages/bundler-plugin-core/src/utils.ts +++ b/packages/bundler-plugin-core/src/utils.ts @@ -278,8 +278,8 @@ export function generateModuleMetadataInjectorCode(metadata: any) { self : {}; - _global2.__MODULE_METADATA__ = _global2.__MODULE_METADATA__ || {}; - _global2.__MODULE_METADATA__[new Error().stack] = ${JSON.stringify(metadata)};`; + _global2._sentryModuleMetadata = _global2._sentryModuleMetadata || {}; + _global2._sentryModuleMetadata[new Error().stack] = ${JSON.stringify(metadata)};`; } function getBuildInformation() { From 703cbdd48fc87a5441afb2972283e0903b6553f8 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Mon, 3 Jul 2023 15:54:59 +0200 Subject: [PATCH 6/6] Add integration test --- packages/bundler-plugin-core/src/index.ts | 3 +- packages/bundler-plugin-core/src/types.ts | 2 +- .../metadata-injection/input/bundle.js | 3 + .../metadata-injection.test.ts | 26 +++++ .../fixtures/metadata-injection/setup.ts | 17 +++ .../utils/create-cjs-bundles.ts | 108 ++++++++++-------- 6 files changed, 107 insertions(+), 52 deletions(-) create mode 100644 packages/integration-tests/fixtures/metadata-injection/input/bundle.js create mode 100644 packages/integration-tests/fixtures/metadata-injection/metadata-injection.test.ts create mode 100644 packages/integration-tests/fixtures/metadata-injection/setup.ts diff --git a/packages/bundler-plugin-core/src/index.ts b/packages/bundler-plugin-core/src/index.ts index 96163e7c..7873f943 100644 --- a/packages/bundler-plugin-core/src/index.ts +++ b/packages/bundler-plugin-core/src/index.ts @@ -184,9 +184,10 @@ export function sentryUnpluginFactory({ project: options.project, release: options.release.name, }; - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call metadata = options._experiments.moduleMetadata(args); } else { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment metadata = options._experiments.moduleMetadata; } diff --git a/packages/bundler-plugin-core/src/types.ts b/packages/bundler-plugin-core/src/types.ts index b8bd1f04..339dc32a 100644 --- a/packages/bundler-plugin-core/src/types.ts +++ b/packages/bundler-plugin-core/src/types.ts @@ -245,7 +245,7 @@ export interface Options { /** * Metadata associated with this module. * - * The metadata is serialized and can be looked up at runtime by URL. + * The metadata is serialized and can be looked up at runtime by filename. * * Metadata can either be passed directly or alternatively a callback can be provided that will be * called with the following arguments: diff --git a/packages/integration-tests/fixtures/metadata-injection/input/bundle.js b/packages/integration-tests/fixtures/metadata-injection/input/bundle.js new file mode 100644 index 00000000..bf138963 --- /dev/null +++ b/packages/integration-tests/fixtures/metadata-injection/input/bundle.js @@ -0,0 +1,3 @@ +// Simply output the metadata to the console so it can be checked in a test +// eslint-disable-next-line no-console +console.log(JSON.stringify(global._sentryModuleMetadata)); diff --git a/packages/integration-tests/fixtures/metadata-injection/metadata-injection.test.ts b/packages/integration-tests/fixtures/metadata-injection/metadata-injection.test.ts new file mode 100644 index 00000000..83640d1b --- /dev/null +++ b/packages/integration-tests/fixtures/metadata-injection/metadata-injection.test.ts @@ -0,0 +1,26 @@ +/* eslint-disable jest/no-standalone-expect */ +/* eslint-disable jest/expect-expect */ +import { execSync } from "child_process"; +import path from "path"; +import { testIfNodeMajorVersionIsLessThan18 } from "../../utils/testIf"; + +function checkBundle(bundlePath: string): void { + const output = execSync(`node ${bundlePath}`, { encoding: "utf-8" }); + + const map = JSON.parse(output) as Record; + + // There should be only one key in the map + expect(Object.values(map)).toHaveLength(1); + // The value should be the expected metadata + expect(Object.values(map)).toEqual([{ team: "frontend" }]); +} + +describe("metadata injection", () => { + testIfNodeMajorVersionIsLessThan18("webpack 4 bundle", () => { + checkBundle(path.join(__dirname, "out", "webpack4", "bundle.js")); + }); + + test("webpack 5 bundle", () => { + checkBundle(path.join(__dirname, "out", "webpack5", "bundle.js")); + }); +}); diff --git a/packages/integration-tests/fixtures/metadata-injection/setup.ts b/packages/integration-tests/fixtures/metadata-injection/setup.ts new file mode 100644 index 00000000..1a7fc221 --- /dev/null +++ b/packages/integration-tests/fixtures/metadata-injection/setup.ts @@ -0,0 +1,17 @@ +import * as path from "path"; +import { createCjsBundles } from "../../utils/create-cjs-bundles"; + +const outputDir = path.resolve(__dirname, "out"); + +createCjsBundles( + { + bundle: path.resolve(__dirname, "input", "bundle.js"), + }, + outputDir, + { + _experiments: { + moduleMetadata: { team: "frontend" }, + }, + }, + ["webpack4", "webpack5"] +); diff --git a/packages/integration-tests/utils/create-cjs-bundles.ts b/packages/integration-tests/utils/create-cjs-bundles.ts index 5f85d8ec..1569b3ea 100644 --- a/packages/integration-tests/utils/create-cjs-bundles.ts +++ b/packages/integration-tests/utils/create-cjs-bundles.ts @@ -16,47 +16,53 @@ const nodejsMajorversion = process.version.split(".")[0]!.slice(1); export function createCjsBundles( entrypoints: { [name: string]: string }, outFolder: string, - sentryUnpluginOptions: Options + sentryUnpluginOptions: Options, + plugins: string[] = [] ): void { - void vite.build({ - clearScreen: false, - build: { - outDir: path.join(outFolder, "vite"), - rollupOptions: { - input: entrypoints, - output: { - format: "cjs", - entryFileNames: "[name].js", + if (plugins.length === 0 || plugins.includes("vite")) { + void vite.build({ + clearScreen: false, + build: { + outDir: path.join(outFolder, "vite"), + rollupOptions: { + input: entrypoints, + output: { + format: "cjs", + entryFileNames: "[name].js", + }, }, }, - }, - plugins: [sentryVitePlugin(sentryUnpluginOptions)], - }); - - void rollup - .rollup({ - input: entrypoints, - plugins: [sentryRollupPlugin(sentryUnpluginOptions)], - }) - .then((bundle) => - bundle.write({ - dir: path.join(outFolder, "rollup"), - format: "cjs", - exports: "named", + plugins: [sentryVitePlugin(sentryUnpluginOptions)], + }); + } + if (plugins.length === 0 || plugins.includes("rollup")) { + void rollup + .rollup({ + input: entrypoints, + plugins: [sentryRollupPlugin(sentryUnpluginOptions)], }) - ); + .then((bundle) => + bundle.write({ + dir: path.join(outFolder, "rollup"), + format: "cjs", + exports: "named", + }) + ); + } - void esbuild.build({ - entryPoints: entrypoints, - outdir: path.join(outFolder, "esbuild"), - plugins: [sentryEsbuildPlugin(sentryUnpluginOptions)], - minify: true, - bundle: true, - format: "cjs", - }); + if (plugins.length === 0 || plugins.includes("esbuild")) { + void esbuild.build({ + entryPoints: entrypoints, + outdir: path.join(outFolder, "esbuild"), + plugins: [sentryEsbuildPlugin(sentryUnpluginOptions)], + minify: true, + bundle: true, + format: "cjs", + }); + } // Webpack 4 doesn't work on Node.js versions >= 18 - if (parseInt(nodejsMajorversion) < 18) { + if (parseInt(nodejsMajorversion) < 18 && (plugins.length === 0 || plugins.includes("webpack4"))) { webpack4( { mode: "production", @@ -77,23 +83,25 @@ export function createCjsBundles( ); } - webpack5( - { - cache: false, - entry: entrypoints, - output: { - path: path.join(outFolder, "webpack5"), - library: { - type: "commonjs", + if (plugins.length === 0 || plugins.includes("webpack5")) { + webpack5( + { + cache: false, + entry: entrypoints, + output: { + path: path.join(outFolder, "webpack5"), + library: { + type: "commonjs", + }, }, + mode: "production", + plugins: [sentryWebpackPlugin(sentryUnpluginOptions)], }, - mode: "production", - plugins: [sentryWebpackPlugin(sentryUnpluginOptions)], - }, - (err) => { - if (err) { - throw err; + (err) => { + if (err) { + throw err; + } } - } - ); + ); + } }