From 4b35df7a8cb64cd04d6cd0433d30fa5637266a1c Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Fri, 31 May 2024 15:40:34 +0200 Subject: [PATCH 1/2] feat: Add `applicationKey` option to identify application code from within the SDK --- packages/bundler-plugin-core/src/index.ts | 18 +++++++-- .../src/options-mapping.ts | 1 + packages/bundler-plugin-core/src/types.ts | 6 +++ .../application-key-injection/input/bundle.js | 3 ++ .../metadata-injection.test.ts | 38 +++++++++++++++++++ .../application-key-injection/setup.ts | 15 ++++++++ 6 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 packages/integration-tests/fixtures/application-key-injection/input/bundle.js create mode 100644 packages/integration-tests/fixtures/application-key-injection/metadata-injection.test.ts create mode 100644 packages/integration-tests/fixtures/application-key-injection/setup.ts diff --git a/packages/bundler-plugin-core/src/index.ts b/packages/bundler-plugin-core/src/index.ts index 8799f1d8..61177b8c 100644 --- a/packages/bundler-plugin-core/src/index.ts +++ b/packages/bundler-plugin-core/src/index.ts @@ -224,8 +224,18 @@ export function sentryUnpluginFactory({ plugins.push(releaseInjectionPlugin(injectionCode)); } - if (options.moduleMetadata) { - let metadata: Record; + if (options.moduleMetadata || options.applicationKey) { + let metadata: Record = {}; + + if (options.applicationKey) { + // We use different keys so that if code receives multiple bundling passes. + // It is a bit unfortunate that we have to inject the metadata snippet at the top, which means that if we didn't + // have this mechanism, the first bundling pass would always "win". If this weren't the case we would be fine with + // the last pass winning but the first pass winning is very bad, because it would prevent any sort of overriding. + // We can simply use the `_sentryBundlerPluginAppKey:` to filter for app keys in the SDK. + metadata[`_sentryBundlerPluginAppKey:${options.applicationKey}`] = true; + } + if (typeof options.moduleMetadata === "function") { const args = { org: options.org, @@ -233,10 +243,10 @@ export function sentryUnpluginFactory({ release: options.release.name, }; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - metadata = options.moduleMetadata(args); + metadata = { ...metadata, ...options.moduleMetadata(args) }; } else { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - metadata = options.moduleMetadata; + metadata = { ...metadata, ...options.moduleMetadata }; } const injectionCode = generateModuleMetadataInjectorCode(metadata); diff --git a/packages/bundler-plugin-core/src/options-mapping.ts b/packages/bundler-plugin-core/src/options-mapping.ts index 86f4e3c4..3a141378 100644 --- a/packages/bundler-plugin-core/src/options-mapping.ts +++ b/packages/bundler-plugin-core/src/options-mapping.ts @@ -34,6 +34,7 @@ export function normalizeUserOptions(userOptions: UserOptions) { metaFramework: userOptions._metaOptions?.telemetry?.metaFramework, }, }, + applicationKey: userOptions.applicationKey, moduleMetadata: userOptions.moduleMetadata || userOptions._experiments?.moduleMetadata, _experiments: userOptions._experiments ?? {}, }; diff --git a/packages/bundler-plugin-core/src/types.ts b/packages/bundler-plugin-core/src/types.ts index 05cc6f88..3cac126d 100644 --- a/packages/bundler-plugin-core/src/types.ts +++ b/packages/bundler-plugin-core/src/types.ts @@ -312,6 +312,12 @@ export interface Options { // eslint-disable-next-line @typescript-eslint/no-explicit-any moduleMetadata?: ModuleMetadata | ModuleMetadataCallback; + /** + * A key which will embedded in all the bundled files. The SDK will be able to use the key to apply filtering + * rules, for example using the `thirdPartyErrorFilterIntegration`. + */ + applicationKey?: string; + /** * Options that are considered experimental and subject to change. * diff --git a/packages/integration-tests/fixtures/application-key-injection/input/bundle.js b/packages/integration-tests/fixtures/application-key-injection/input/bundle.js new file mode 100644 index 00000000..bf138963 --- /dev/null +++ b/packages/integration-tests/fixtures/application-key-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/application-key-injection/metadata-injection.test.ts b/packages/integration-tests/fixtures/application-key-injection/metadata-injection.test.ts new file mode 100644 index 00000000..85fc09de --- /dev/null +++ b/packages/integration-tests/fixtures/application-key-injection/metadata-injection.test.ts @@ -0,0 +1,38 @@ +/* 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([{ ["_sentryBundlerPluginAppKey:my-app"]: true }]); +} + +describe("appKey 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")); + }); + + test("esbuild bundle", () => { + checkBundle(path.join(__dirname, "out", "esbuild", "bundle.js")); + }); + + test("rollup bundle", () => { + checkBundle(path.join(__dirname, "out", "rollup", "bundle.js")); + }); + + test("vite bundle", () => { + checkBundle(path.join(__dirname, "out", "vite", "bundle.js")); + }); +}); diff --git a/packages/integration-tests/fixtures/application-key-injection/setup.ts b/packages/integration-tests/fixtures/application-key-injection/setup.ts new file mode 100644 index 00000000..a745efa2 --- /dev/null +++ b/packages/integration-tests/fixtures/application-key-injection/setup.ts @@ -0,0 +1,15 @@ +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, + { + applicationKey: "my-app", + }, + ["webpack4", "webpack5", "esbuild", "rollup", "vite"] +); From 793d3905156098c5fe7aeac34d4652badb1d3254 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Fri, 31 May 2024 16:24:21 +0200 Subject: [PATCH 2/2] turn comment to proper english --- packages/bundler-plugin-core/src/index.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/bundler-plugin-core/src/index.ts b/packages/bundler-plugin-core/src/index.ts index 61177b8c..a04b03fa 100644 --- a/packages/bundler-plugin-core/src/index.ts +++ b/packages/bundler-plugin-core/src/index.ts @@ -228,10 +228,11 @@ export function sentryUnpluginFactory({ let metadata: Record = {}; if (options.applicationKey) { - // We use different keys so that if code receives multiple bundling passes. - // It is a bit unfortunate that we have to inject the metadata snippet at the top, which means that if we didn't - // have this mechanism, the first bundling pass would always "win". If this weren't the case we would be fine with - // the last pass winning but the first pass winning is very bad, because it would prevent any sort of overriding. + // We use different keys so that if user-code receives multiple bundling passes, we will store the application keys of all the passes. + // It is a bit unfortunate that we have to inject the metadata snippet at the top, because after multiple + // injections, the first injection will always "win" because it comes last in the code. We would generally be + // fine with making the last bundling pass win. But because it cannot win, we have to use a workaround of storing + // the app keys in different object keys. // We can simply use the `_sentryBundlerPluginAppKey:` to filter for app keys in the SDK. metadata[`_sentryBundlerPluginAppKey:${options.applicationKey}`] = true; }