diff --git a/index.d.ts b/index.d.ts index 056b7276..430a490c 100644 --- a/index.d.ts +++ b/index.d.ts @@ -105,6 +105,13 @@ export interface SentryCliPluginOptions { */ finalize?: boolean; + /** + * Determines whether plugin should be applied not more than once during whole webpack run. + * Useful when the process is performing multiple builds using the same config. + * Defaults to `false`. + */ + runOnce?: boolean; + /** * Attempts a dry run (useful for dev environments). */ diff --git a/src/__tests__/index.spec.js b/src/__tests__/index.spec.js index b61558c6..b46a2f88 100644 --- a/src/__tests__/index.spec.js +++ b/src/__tests__/index.spec.js @@ -15,18 +15,17 @@ const mockCli = { const SentryCliMock = jest.fn((configFile, options) => mockCli); const SentryCli = jest.mock('@sentry/cli', () => SentryCliMock); -const SentryCliPlugin = require('../..'); - -afterEach(() => { - jest.clearAllMocks(); -}); +let SentryCliPlugin = require('../..'); const defaults = { - debug: false, finalize: true, rewrite: true, }; +beforeEach(() => { + jest.clearAllMocks(); +}); + describe('constructor', () => { test('uses defaults without options', () => { const sentryCliPlugin = new SentryCliPlugin(); @@ -287,6 +286,27 @@ describe('afterEmitHook', () => { done(); }); }); + + test('does not skip the release if `runOnce` option is not set and its called more than once', () => { + const sentryCliPlugin = new SentryCliPlugin(); + sentryCliPlugin.apply(compiler); + expect(compiler.hooks.afterEmit.tapAsync).toHaveBeenCalledTimes(1); + sentryCliPlugin.apply(compiler); + expect(compiler.hooks.afterEmit.tapAsync).toHaveBeenCalledTimes(2); + }); + + test('skips the release if `runOnce` option is set and its called more than once', () => { + // Reset the state of a module to verify `runOnce` behavior + jest.resetModules(); + SentryCliPlugin = require('../..'); + const sentryCliPlugin = new SentryCliPlugin({ + runOnce: true, + }); + sentryCliPlugin.apply(compiler); + expect(compiler.hooks.afterEmit.tapAsync).toHaveBeenCalledTimes(1); + sentryCliPlugin.apply(compiler); + expect(compiler.hooks.afterEmit.tapAsync).toHaveBeenCalledTimes(1); + }); }); describe('module rule overrides', () => { diff --git a/src/index.js b/src/index.js index 5df5551a..1a1ea326 100644 --- a/src/index.js +++ b/src/index.js @@ -72,7 +72,6 @@ function attachAfterEmitHook(compiler, callback) { class SentryCliPlugin { constructor(options = {}) { const defaults = { - debug: false, finalize: true, rewrite: true, }; @@ -452,6 +451,21 @@ class SentryCliPlugin { /** Webpack lifecycle hook to update compiler options. */ apply(compiler) { + /** + * Determines whether plugin should be applied not more than once during whole webpack run. + * Useful when the process is performing multiple builds using the same config. + * It cannot be stored on the instance, as every run is creating a new one. + */ + if (this.options.runOnce && module.alreadyRun) { + if (this.options.debug) { + this.outputDebug( + '`runOnce` option set and plugin already ran. Skipping release.' + ); + } + return; + } + module.alreadyRun = true; + const compilerOptions = compiler.options || {}; ensure(compilerOptions, 'module', Object);