@sentry/webpack-plugin 1.20.0 → 2.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.js DELETED
@@ -1,596 +0,0 @@
1
- const SentryCli = require('@sentry/cli');
2
- const fs = require('fs');
3
- const path = require('path');
4
- const util = require('util');
5
- const { RawSource } = require('webpack-sources');
6
- const pjson = require('../package.json');
7
-
8
- const SENTRY_LOADER = path.resolve(__dirname, 'sentry.loader.js');
9
- const SENTRY_MODULE = path.resolve(__dirname, 'sentry-webpack.module.js');
10
-
11
- // Set the User-Agent string.
12
- process.env.SENTRY_PIPELINE = `webpack-plugin/${pjson.version}`;
13
-
14
- /**
15
- * Helper function that ensures an object key is defined. This mutates target!
16
- *
17
- * @param {object} target The target object
18
- * @param {string} key The object key
19
- * @param {function} factory A function that creates the new element
20
- * @returns {any} The existing or created element.
21
- */
22
- function ensure(target, key, factory) {
23
- // eslint-disable-next-line no-param-reassign
24
- target[key] = typeof target[key] !== 'undefined' ? target[key] : factory();
25
- return target[key];
26
- }
27
-
28
- /** Deep copy of a given input */
29
- function sillyClone(input) {
30
- try {
31
- return JSON.parse(JSON.stringify(input));
32
- } catch (oO) {
33
- return undefined;
34
- }
35
- }
36
-
37
- /** Diffs two arrays */
38
- function diffArray(prev, next) {
39
- // eslint-disable-next-line no-param-reassign
40
- prev = Array.isArray(prev) ? prev : [prev];
41
- // eslint-disable-next-line no-param-reassign
42
- next = Array.isArray(next) ? next : [next];
43
-
44
- return {
45
- removed: prev.filter(x => !next.includes(x)),
46
- added: next.filter(x => !prev.includes(x)),
47
- };
48
- }
49
-
50
- /** Extracts loader's name independently of Webpack's version */
51
- function getLoaderName(entry) {
52
- return (
53
- entry.loader ||
54
- (entry.use && entry.use[0] && entry.use[0].loader) ||
55
- '<unknown loader>'
56
- );
57
- }
58
-
59
- /**
60
- * Wraps the given value in an array if it is not already an array itself.
61
- * Ignores `undefined` and `null`, returning them as is.
62
- *
63
- * @param {any} value Either an array or a value that should be wrapped in an array
64
- * @returns {array} The resulting array, or the original value if it's null/undefined
65
- */
66
- function toArray(value) {
67
- if (Array.isArray(value) || value === null || value === undefined) {
68
- return value;
69
- }
70
-
71
- return [value];
72
- }
73
-
74
- /** Backwards compatible version of `compiler.plugin.afterEmit.tapAsync()`. */
75
- function attachAfterEmitHook(compiler, callback) {
76
- if (compiler.hooks && compiler.hooks.afterEmit) {
77
- compiler.hooks.afterEmit.tapAsync('SentryCliPlugin', callback);
78
- } else {
79
- compiler.plugin('after-emit', callback);
80
- }
81
- }
82
-
83
- function attachAfterCodeGenerationHook(compiler, options) {
84
- // This is only a problem for folks on webpack 3 and below
85
- if (!compiler.hooks || !compiler.hooks.make) {
86
- return;
87
- }
88
-
89
- const moduleFederationPlugin =
90
- compiler.options &&
91
- compiler.options.plugins &&
92
- compiler.options.plugins.find(
93
- x => x.constructor.name === 'ModuleFederationPlugin'
94
- );
95
-
96
- if (!moduleFederationPlugin) {
97
- return;
98
- }
99
-
100
- compiler.hooks.make.tapAsync('SentryCliPlugin', (compilation, cb) => {
101
- options.releasePromise.then(version => {
102
- compilation.hooks.afterCodeGeneration.tap('SentryCliPlugin', () => {
103
- compilation.modules.forEach(module => {
104
- // eslint-disable-next-line no-underscore-dangle
105
- if (module._name !== moduleFederationPlugin._options.name) {
106
- return;
107
- }
108
-
109
- const sourceMap = compilation.codeGenerationResults.get(module)
110
- .sources;
111
- const rawSource = sourceMap.get('javascript');
112
-
113
- if (rawSource) {
114
- sourceMap.set(
115
- 'javascript',
116
- new RawSource(
117
- `${rawSource.source()}
118
- (function (){
119
- var globalThis = (typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {});
120
- globalThis.SENTRY_RELEASES = globalThis.SENTRY_RELEASES || {};
121
- globalThis.SENTRY_RELEASES["${options.project}@${
122
- options.org
123
- }"] = {"id":"${version}"};
124
- })();`
125
- )
126
- );
127
- }
128
- });
129
- });
130
- cb();
131
- });
132
- });
133
- }
134
-
135
- class SentryCliPlugin {
136
- constructor(options = {}) {
137
- const defaults = {
138
- finalize: true,
139
- rewrite: true,
140
- };
141
-
142
- this.options = Object.assign({}, defaults, options);
143
-
144
- // the webpack plugin has looser type requirements than `@sentry/cli` -
145
- // ensure `include` and `ignore` options are in the right format
146
- if (options.include) {
147
- this.options.include = toArray(options.include);
148
- this.options.include.forEach(includeEntry => {
149
- if (
150
- typeof includeEntry === 'object' &&
151
- includeEntry.ignore !== undefined
152
- ) {
153
- // eslint-disable-next-line no-param-reassign
154
- includeEntry.ignore = toArray(includeEntry.ignore);
155
- }
156
- });
157
- }
158
-
159
- if (options.ignore) this.options.ignore = toArray(options.ignore);
160
-
161
- this.cli = this.getSentryCli();
162
- this.release = this.getReleasePromise();
163
- }
164
-
165
- /**
166
- * Pretty-prints debug information
167
- *
168
- * @param {string} label Label to be printed as a prefix for the data
169
- * @param {any} data Input to be pretty-printed
170
- */
171
- outputDebug(label, data) {
172
- if (this.isSilent()) {
173
- return;
174
- }
175
- if (data !== undefined) {
176
- // eslint-disable-next-line no-console
177
- console.log(
178
- `[Sentry Webpack Plugin] ${label} ${util.inspect(
179
- data,
180
- false,
181
- null,
182
- true
183
- )}`
184
- );
185
- } else {
186
- // eslint-disable-next-line no-console
187
- console.log(`[Sentry Webpack Plugin] ${label}`);
188
- }
189
- }
190
-
191
- /** Returns whether this plugin should emit any data to stdout. */
192
- isSilent() {
193
- return this.options.silent === true;
194
- }
195
-
196
- /** Returns whether this plugin is in dryRun mode. */
197
- isDryRun() {
198
- return this.options.dryRun === true;
199
- }
200
-
201
- static cliBinaryExists() {
202
- return fs.existsSync(SentryCli.getPath());
203
- }
204
-
205
- /** Creates a new Sentry CLI instance. */
206
- getSentryCli() {
207
- const cli = new SentryCli(this.options.configFile, {
208
- silent: this.isSilent(),
209
- org: this.options.org,
210
- project: this.options.project,
211
- authToken: this.options.authToken,
212
- url: this.options.url,
213
- vcsRemote: this.options.vcsRemote,
214
- });
215
-
216
- if (this.isDryRun()) {
217
- this.outputDebug('DRY Run Mode');
218
-
219
- return {
220
- releases: {
221
- proposeVersion: () =>
222
- cli.releases.proposeVersion().then(version => {
223
- this.outputDebug('Proposed version:\n', version);
224
- return version;
225
- }),
226
- new: release => {
227
- this.outputDebug('Creating new release:\n', release);
228
- return Promise.resolve(release);
229
- },
230
- uploadSourceMaps: (release, config) => {
231
- this.outputDebug('Calling upload-sourcemaps with:\n', config);
232
- return Promise.resolve(release, config);
233
- },
234
- finalize: release => {
235
- this.outputDebug('Finalizing release:\n', release);
236
- return Promise.resolve(release);
237
- },
238
- setCommits: (release, config) => {
239
- this.outputDebug('Calling set-commits with:\n', config);
240
- return Promise.resolve(release, config);
241
- },
242
- newDeploy: (release, config) => {
243
- this.outputDebug('Calling deploy with:\n', config);
244
- return Promise.resolve(release, config);
245
- },
246
- },
247
- };
248
- }
249
-
250
- return cli;
251
- }
252
-
253
- /**
254
- * Returns a Promise that will solve to the configured release.
255
- *
256
- * If no release is specified, it uses Sentry CLI to propose a version.
257
- * The release string is always trimmed.
258
- * Returns undefined if proposeVersion failed.
259
- */
260
- getReleasePromise() {
261
- const userSpecifiedRelease =
262
- this.options.release || process.env.SENTRY_RELEASE;
263
- return (userSpecifiedRelease
264
- ? Promise.resolve(userSpecifiedRelease)
265
- : this.cli.releases.proposeVersion()
266
- )
267
- .then(version => `${version}`.trim())
268
- .catch(() => undefined);
269
- }
270
-
271
- /** Checks if the given named entry point should be handled. */
272
- shouldInjectEntry(key) {
273
- const { entries } = this.options;
274
- if (entries == null) {
275
- return true;
276
- }
277
-
278
- if (typeof entries === 'function') {
279
- return entries(key);
280
- }
281
-
282
- if (entries instanceof RegExp) {
283
- return entries.test(key);
284
- }
285
-
286
- if (Array.isArray(entries)) {
287
- return entries.includes(key);
288
- }
289
-
290
- throw new Error(
291
- 'Invalid `entries` option: Must be an array, RegExp or function'
292
- );
293
- }
294
-
295
- /** Injects the release string into the given entry point. */
296
- injectEntry(entry, sentryModule) {
297
- if (!entry) {
298
- return sentryModule;
299
- }
300
-
301
- /**
302
- * in:
303
- * entry: 'index.js'
304
- * out:
305
- * entry: ['sentry-webpack.module.js', 'index.js']
306
- */
307
- if (typeof entry === 'string') {
308
- return [sentryModule, entry];
309
- }
310
-
311
- /**
312
- * in:
313
- * entry: ['index.js', 'header.js', 'footer.js']
314
- * out:
315
- * entry: ['sentry-webpack.module.js', 'index.js', 'header.js', 'footer.js']
316
- */
317
- if (Array.isArray(entry)) {
318
- return [sentryModule].concat(entry);
319
- }
320
-
321
- /**
322
- * in:
323
- * entry: () => 'index.js'
324
- * entry: () => ['index.js']
325
- * out:
326
- * entry: ['sentry-webpack.module.js', 'index.js']
327
- * entry: ['sentry-webpack.module.js', 'index.js']
328
- */
329
- if (typeof entry === 'function') {
330
- return () =>
331
- Promise.resolve(entry()).then(resolvedEntry =>
332
- this.injectEntry(resolvedEntry, sentryModule)
333
- );
334
- }
335
-
336
- /**
337
- * in:
338
- * entry: {
339
- * home: './home.js',
340
- * about: ['./about.js'],
341
- * contact: () => './contact.js',
342
- * login: {
343
- * import: './login.js',
344
- * },
345
- * logout: {
346
- * import: ['./logout.js']
347
- * }
348
- * }
349
- * out:
350
- * entry: {
351
- * home: ['sentry-webpack.module.js', './home.js'],
352
- * about: ['sentry-webpack.module.js', './about.js'],
353
- * contact: ['sentry-webpack.module.js', './contact.js'],
354
- * login: {
355
- * import: ['sentry-webpack.module.js', './login.js']
356
- * },
357
- * logout: {
358
- * import: ['sentry-webpack.module.js', './logout.js']
359
- * }
360
- * }
361
- */
362
- const modifiedEntry = { ...entry };
363
- Object.keys(modifiedEntry)
364
- .filter(key => this.shouldInjectEntry(key))
365
- .forEach(key => {
366
- if (entry[key] && entry[key].import) {
367
- modifiedEntry[key].import = this.injectEntry(
368
- entry[key].import,
369
- sentryModule
370
- );
371
- } else {
372
- modifiedEntry[key] = this.injectEntry(entry[key], sentryModule);
373
- }
374
- });
375
- return modifiedEntry;
376
- }
377
-
378
- /** Webpack 2: Adds a new loader for the release module. */
379
- injectLoader(loaders) {
380
- const loader = {
381
- test: /sentry-webpack\.module\.js$/,
382
- loader: SENTRY_LOADER,
383
- options: {
384
- releasePromise: this.release,
385
- org: this.options.org || process.env.SENTRY_ORG,
386
- project: this.options.project || process.env.SENTRY_PROJECT,
387
- },
388
- };
389
-
390
- return (loaders || []).concat([loader]);
391
- }
392
-
393
- /** Webpack 3+: Injects a new rule for the release module. */
394
- injectRule(rules) {
395
- const rule = {
396
- test: /sentry-webpack\.module\.js$/,
397
- use: [
398
- {
399
- loader: SENTRY_LOADER,
400
- options: {
401
- // We check for `process.env.SENTRY_RELEASE` earlier, which is why
402
- // it's not used here the way `process.env.SENTRY_ORG` and
403
- // `process.env.SENTRY_PROJECT` are
404
- releasePromise: this.release,
405
- org: this.options.org || process.env.SENTRY_ORG,
406
- project: this.options.project || process.env.SENTRY_PROJECT,
407
- },
408
- },
409
- ],
410
- };
411
-
412
- return (rules || []).concat([rule]);
413
- }
414
-
415
- /** Injects the release entry points and rules into the given options. */
416
- injectRelease(compilerOptions) {
417
- const options = compilerOptions;
418
- options.entry = this.injectEntry(options.entry, SENTRY_MODULE);
419
- if (options.module.loaders) {
420
- // Handle old `options.module.loaders` syntax
421
- options.module.loaders = this.injectLoader(options.module.loaders);
422
- } else {
423
- options.module.rules = this.injectRule(options.module.rules);
424
- }
425
- }
426
-
427
- /** injectRelease with printable debug info */
428
- injectReleaseWithDebug(compilerOptions) {
429
- const input = {
430
- loaders: sillyClone(
431
- compilerOptions.module.loaders || compilerOptions.module.rules
432
- ).map(getLoaderName),
433
- entry: sillyClone(compilerOptions.entry),
434
- };
435
-
436
- this.injectRelease(compilerOptions);
437
-
438
- const output = {
439
- loaders: sillyClone(
440
- compilerOptions.module.loaders || compilerOptions.module.rules
441
- ).map(getLoaderName),
442
- entry: sillyClone(compilerOptions.entry),
443
- };
444
-
445
- const loaders = diffArray(input.loaders, output.loaders);
446
- const entry = diffArray(input.entry, output.entry);
447
-
448
- this.outputDebug('DEBUG: Injecting release code');
449
- this.outputDebug('DEBUG: Loaders:\n', output.loaders);
450
- this.outputDebug('DEBUG: Added loaders:\n', loaders.added);
451
- this.outputDebug('DEBUG: Removed loaders:\n', loaders.removed);
452
- this.outputDebug('DEBUG: Entry:\n', output.entry);
453
- this.outputDebug('DEBUG: Added entry:\n', entry.added);
454
- this.outputDebug('DEBUG: Removed entry:\n', entry.removed);
455
- }
456
-
457
- /** Creates and finalizes a release on Sentry. */
458
- finalizeRelease(compilation) {
459
- const {
460
- include,
461
- errorHandler = (_, invokeErr) => {
462
- invokeErr();
463
- },
464
- } = this.options;
465
-
466
- let release;
467
- return this.release
468
- .then(proposedVersion => {
469
- release = proposedVersion;
470
-
471
- if (!include) {
472
- throw new Error(`\`include\` option is required`);
473
- }
474
-
475
- if (!release) {
476
- throw new Error(
477
- `Unable to determine version. Make sure to include \`release\` option or use the environment that supports auto-detection https://docs.sentry.io/cli/releases/#creating-releases`
478
- );
479
- }
480
-
481
- return this.cli.releases.new(release);
482
- })
483
- .then(() => {
484
- if (this.options.cleanArtifacts) {
485
- return this.cli.releases.execute(
486
- ['releases', 'files', release, 'delete', '--all'],
487
- true
488
- );
489
- }
490
- return undefined;
491
- })
492
- .then(() => this.cli.releases.uploadSourceMaps(release, this.options))
493
- .then(() => {
494
- const {
495
- commit,
496
- previousCommit,
497
- repo,
498
- auto,
499
- ignoreMissing,
500
- ignoreEmpty,
501
- } = this.options.setCommits || this.options;
502
-
503
- if (auto || (repo && commit)) {
504
- return this.cli.releases.setCommits(release, {
505
- commit,
506
- previousCommit,
507
- repo,
508
- auto,
509
- ignoreMissing,
510
- ignoreEmpty,
511
- });
512
- }
513
- return undefined;
514
- })
515
- .then(() => {
516
- if (this.options.finalize) {
517
- return this.cli.releases.finalize(release);
518
- }
519
- return undefined;
520
- })
521
- .then(() => {
522
- const { env, started, finished, time, name, url } =
523
- this.options.deploy || {};
524
-
525
- if (env) {
526
- return this.cli.releases.newDeploy(release, {
527
- env,
528
- started,
529
- finished,
530
- time,
531
- name,
532
- url,
533
- });
534
- }
535
- return undefined;
536
- })
537
- .catch(err => {
538
- errorHandler(
539
- err,
540
- () =>
541
- compilation.errors.push(
542
- new Error(`Sentry CLI Plugin: ${err.message}`)
543
- ),
544
- compilation
545
- );
546
- });
547
- }
548
-
549
- /** Webpack lifecycle hook to update compiler options. */
550
- apply(compiler) {
551
- /**
552
- * Determines whether plugin should be applied not more than once during whole webpack run.
553
- * Useful when the process is performing multiple builds using the same config.
554
- * It cannot be stored on the instance, as every run is creating a new one.
555
- */
556
- if (this.options.runOnce && module.alreadyRun) {
557
- if (this.options.debug) {
558
- this.outputDebug(
559
- '`runOnce` option set and plugin already ran. Skipping release.'
560
- );
561
- }
562
- return;
563
- }
564
- module.alreadyRun = true;
565
-
566
- const compilerOptions = compiler.options || {};
567
- ensure(compilerOptions, 'module', Object);
568
-
569
- if (this.options.debug) {
570
- this.injectReleaseWithDebug(compilerOptions);
571
- } else {
572
- this.injectRelease(compilerOptions);
573
- }
574
-
575
- attachAfterCodeGenerationHook(compiler, {
576
- // We check for `process.env.SENTRY_RELEASE` earlier, which is why it's
577
- // not used here the way `process.env.SENTRY_ORG` and
578
- // `process.env.SENTRY_PROJECT` are
579
- releasePromise: this.release,
580
- org: this.options.org || process.env.SENTRY_ORG,
581
- project: this.options.project || process.env.SENTRY_PROJECT,
582
- });
583
-
584
- attachAfterEmitHook(compiler, (compilation, cb) => {
585
- if (!this.options.include || !this.options.include.length) {
586
- ensure(compilerOptions, 'output', Object);
587
- if (compilerOptions.output.path) {
588
- this.options.include = [compilerOptions.output.path];
589
- }
590
- }
591
- this.finalizeRelease(compilation).then(() => cb());
592
- });
593
- }
594
- }
595
-
596
- module.exports.default = SentryCliPlugin;
@@ -1 +0,0 @@
1
- // This will be replaced
@@ -1,15 +0,0 @@
1
- module.exports = function sentryLoader(content, map, meta) {
2
- const { releasePromise, org, project } = this.query;
3
- const callback = this.async();
4
- releasePromise.then(version => {
5
- let sentryRelease = `var _global = (typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}); _global.SENTRY_RELEASE={id:"${version}"};`;
6
- if (project) {
7
- const key = org ? `${project}@${org}` : project;
8
- sentryRelease += `
9
- _global.SENTRY_RELEASES=_global.SENTRY_RELEASES || {};
10
- _global.SENTRY_RELEASES["${key}"]={id:"${version}"};
11
- `;
12
- }
13
- callback(null, sentryRelease, map, meta);
14
- });
15
- };