diff --git a/src/__tests__/index.spec.js b/src/__tests__/index.spec.js index e78c61d6..aefecc83 100644 --- a/src/__tests__/index.spec.js +++ b/src/__tests__/index.spec.js @@ -82,7 +82,15 @@ describe('CLI configuration', () => { test('only creates a single CLI instance', () => { const sentryCliPlugin = new SentryCliPlugin({}); - sentryCliPlugin.apply({ hooks: { afterEmit: { tapAsync: jest.fn() } } }); + sentryCliPlugin.apply({ + hooks: { + afterEmit: { tapAsync: jest.fn() }, + make: { tapAsync: jest.fn() }, + }, + options: { + plugins: [], + }, + }); expect(SentryCliMock.mock.instances.length).toBe(1); }); }); @@ -91,20 +99,36 @@ describe('afterEmitHook', () => { let compiler; let compilation; let compilationDoneCallback; + let makeCallback; beforeEach(() => { compiler = { + options: { + plugins: [], + }, hooks: { afterEmit: { tapAsync: jest.fn((name, callback) => callback(compilation, compilationDoneCallback) ), }, + make: { + tapAsync: jest.fn((name, callback) => + callback(compilation, makeCallback) + ), + }, }, }; - compilation = { errors: [], hash: 'someHash' }; + compilation = { + errors: [], + hash: 'someHash', + hooks: { + afterCodeGeneration: { tap: jest.fn() }, + }, + }; compilationDoneCallback = jest.fn(); + makeCallback = jest.fn(); }); test('calls `hooks.afterEmit.tapAsync()`', () => { @@ -121,7 +145,7 @@ describe('afterEmitHook', () => { const sentryCliPlugin = new SentryCliPlugin(); // Simulate Webpack <= 2 - compiler = { plugin: jest.fn() }; + compiler = { plugin: jest.fn(), options: { plugins: [] } }; sentryCliPlugin.apply(compiler); expect(compiler.plugin).toHaveBeenCalledWith( @@ -343,7 +367,10 @@ describe('module rule overrides', () => { beforeEach(() => { sentryCliPlugin = new SentryCliPlugin({ release: '42', include: 'src' }); compiler = { - hooks: { afterEmit: { tapAsync: jest.fn() } }, + hooks: { + afterEmit: { tapAsync: jest.fn() }, + make: { tapAsync: jest.fn() }, + }, options: { module: {} }, }; }); @@ -402,7 +429,10 @@ describe('entry point overrides', () => { beforeEach(() => { sentryCliPlugin = new SentryCliPlugin({ release: '42', include: 'src' }); compiler = { - hooks: { afterEmit: { tapAsync: jest.fn() } }, + hooks: { + afterEmit: { tapAsync: jest.fn() }, + make: { tapAsync: jest.fn() }, + }, options: { module: { rules: [] } }, }; }); diff --git a/src/index.js b/src/index.js index 9cfeedd9..1c283a81 100644 --- a/src/index.js +++ b/src/index.js @@ -69,6 +69,52 @@ function attachAfterEmitHook(compiler, callback) { } } +function attachAfterCodeGenerationHook(compiler, options) { + if (!compiler.hooks || !compiler.hooks.make) { + return; + } + const moduleFederationPlugin = + compiler.options && + compiler.options.plugins && + compiler.options.plugins.find( + x => x.constructor.name === 'ModuleFederationPlugin' + ); + + if (!moduleFederationPlugin) { + return; + } + + const { RawSource } = require('webpack-sources'); + + compiler.hooks.make.tapAsync('SentryCliPlugin', (compilation, cb) => { + options.releasePromise.then(version => { + compilation.hooks.afterCodeGeneration.tap('SentryCliPlugin', () => { + compilation.modules.forEach(module => { + // eslint-disable-next-line no-underscore-dangle + if (module._name !== moduleFederationPlugin._options.name) return; + const sourceMap = compilation.codeGenerationResults.get(module) + .sources; + const rawSource = sourceMap.get('javascript'); + sourceMap.set( + 'javascript', + new RawSource( + `${rawSource.source()} +(function (){ +var globalThis = (typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}); +globalThis.SENTRY_RELEASES = globalThis.SENTRY_RELEASES || {}; +globalThis.SENTRY_RELEASES["${options.project}@${ + options.org + }"] = {"id":"${version}"}; +})();` + ) + ); + }); + }); + cb(); + }); + }); +} + class SentryCliPlugin { constructor(options = {}) { const defaults = { @@ -299,6 +345,8 @@ class SentryCliPlugin { loader: SENTRY_LOADER, options: { releasePromise: this.release, + org: this.options.org || process.env.SENTRY_ORG, + project: this.options.project || process.env.SENTRY_PROJECT, }, }; @@ -314,6 +362,8 @@ class SentryCliPlugin { loader: SENTRY_LOADER, options: { releasePromise: this.release, + org: this.options.org || process.env.SENTRY_ORG, + project: this.options.project || process.env.SENTRY_PROJECT, }, }, ], @@ -482,6 +532,12 @@ class SentryCliPlugin { this.injectRelease(compilerOptions); } + attachAfterCodeGenerationHook(compiler, { + releasePromise: this.release, + org: this.options.org || process.env.SENTRY_ORG, + project: this.options.project || process.env.SENTRY_PROJECT, + }); + attachAfterEmitHook(compiler, (compilation, cb) => { if (!this.options.include || !this.options.include.length) { ensure(compilerOptions, 'output', Object); diff --git a/src/sentry.loader.js b/src/sentry.loader.js index 95f2634c..3a153be0 100644 --- a/src/sentry.loader.js +++ b/src/sentry.loader.js @@ -1,8 +1,15 @@ module.exports = function sentryLoader(content, map, meta) { - const { releasePromise } = this.query; + const { releasePromise, org, project } = this.query; const callback = this.async(); releasePromise.then(version => { - const sentryRelease = `(typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}).SENTRY_RELEASE={id:"${version}"};`; + let sentryRelease = `const _global = (typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}); _global.SENTRY_RELEASE={id:"${version}"};`; + if (project) { + const key = org ? `${project}@${org}` : project; + sentryRelease += ` + _global.SENTRY_RELEASES=_global.SENTRY_RELEASES||{}; + _global.SENTRY_RELEASES["${key}"]={id:"${version}"}; + `; + } callback(null, sentryRelease, map, meta); }); }; diff --git a/yarn.lock b/yarn.lock index 541ba9b5..ba468276 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4785,7 +4785,7 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^3.1.0" -source-list-map@^2.0.0: +source-list-map@^2.0.0, source-list-map@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== @@ -5458,6 +5458,14 @@ webpack-sources@^1.4.0, webpack-sources@^1.4.1: source-list-map "^2.0.0" source-map "~0.6.1" +webpack-sources@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-2.3.0.tgz#9ed2de69b25143a4c18847586ad9eccb19278cfa" + integrity sha512-WyOdtwSvOML1kbgtXbTDnEW0jkJ7hZr/bDByIwszhWd/4XX1A3XMkrbFMsuH4+/MfLlZCUzlAdg4r7jaGKEIgQ== + dependencies: + source-list-map "^2.0.1" + source-map "^0.6.1" + webpack@^4.39.3: version "4.46.0" resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.46.0.tgz#bf9b4404ea20a073605e0a011d188d77cb6ad542"