@honeybadger-io/webpack
Advanced tools
Comparing version 1.2.0 to 1.3.0-beta.0
@@ -9,2 +9,22 @@ # Changelog | ||
## [1.3.0-beta.0] - 2021-03-22 | ||
### Added | ||
- Add retry functionality for fetch requests via | ||
[fetch-retry](https://github.com/vercel/fetch-retry) | ||
- Add a retry option that defaults to 3, with a max number of retries | ||
of 10. | ||
- Add a warning if no assets will be uploaded. Uses console.info instead | ||
of process.stdout.write. | ||
- Add a configurable `endpoint` to the constructor, defaults to | ||
`https://api.honeybadger.io/v1/source_maps` | ||
- Add a check for auxiliary files for Webpack 5 compatibility | ||
- Add Webpack 5 compatibility | ||
- Make Webpack 4+ a peerDependency | ||
### Fixed | ||
- fetch separates response errors from network errors. | ||
400+ status codes are treated separately from actual network errors. | ||
- Attempt to reduce `ECONNRESET` and `SOCKETTIMEOUT` errors by | ||
using `fetch-retry` | ||
## [1.2.0] - 2019-12-18 | ||
@@ -11,0 +31,0 @@ ### Changed |
@@ -6,8 +6,10 @@ "use strict"; | ||
}); | ||
exports.REQUIRED_FIELDS = exports.ENDPOINT = exports.PLUGIN_NAME = void 0; | ||
exports.REQUIRED_FIELDS = exports.MAX_RETRIES = exports.ENDPOINT = exports.PLUGIN_NAME = void 0; | ||
var PLUGIN_NAME = 'HoneybadgerSourceMapPlugin'; | ||
exports.PLUGIN_NAME = PLUGIN_NAME; | ||
var ENDPOINT = 'https://api.honeybadger.io/v1/source_maps '; | ||
var ENDPOINT = 'https://api.honeybadger.io/v1/source_maps'; | ||
exports.ENDPOINT = ENDPOINT; | ||
var MAX_RETRIES = 10; | ||
exports.MAX_RETRIES = MAX_RETRIES; | ||
var REQUIRED_FIELDS = ['apiKey', 'assetsUrl']; | ||
exports.REQUIRED_FIELDS = REQUIRED_FIELDS; |
@@ -5,4 +5,8 @@ "use strict"; | ||
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); | ||
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); | ||
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); | ||
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); | ||
@@ -12,6 +16,10 @@ | ||
var _async = _interopRequireDefault(require("async")); | ||
var _fs = require("fs"); | ||
var _request = _interopRequireDefault(require("request")); | ||
var _path = require("path"); | ||
var _nodeFetch = _interopRequireDefault(require("node-fetch")); | ||
var _fetchRetry = _interopRequireDefault(require("@vercel/fetch-retry")); | ||
var _verror = _interopRequireDefault(require("verror")); | ||
@@ -23,2 +31,4 @@ | ||
var _formData = _interopRequireDefault(require("form-data")); | ||
var _helpers = require("./helpers"); | ||
@@ -28,17 +38,22 @@ | ||
var HoneybadgerSourceMapPlugin = | ||
/*#__PURE__*/ | ||
function () { | ||
var fetch = (0, _fetchRetry["default"])(_nodeFetch["default"]); | ||
var HoneybadgerSourceMapPlugin = /*#__PURE__*/function () { | ||
function HoneybadgerSourceMapPlugin(_ref) { | ||
var apiKey = _ref.apiKey, | ||
assetsUrl = _ref.assetsUrl, | ||
_ref$endpoint = _ref.endpoint, | ||
endpoint = _ref$endpoint === void 0 ? _constants.ENDPOINT : _ref$endpoint, | ||
_ref$revision = _ref.revision, | ||
revision = _ref$revision === void 0 ? "master" : _ref$revision, | ||
revision = _ref$revision === void 0 ? 'master' : _ref$revision, | ||
_ref$silent = _ref.silent, | ||
silent = _ref$silent === void 0 ? false : _ref$silent, | ||
_ref$ignoreErrors = _ref.ignoreErrors, | ||
ignoreErrors = _ref$ignoreErrors === void 0 ? false : _ref$ignoreErrors; | ||
ignoreErrors = _ref$ignoreErrors === void 0 ? false : _ref$ignoreErrors, | ||
_ref$retries = _ref.retries, | ||
retries = _ref$retries === void 0 ? 3 : _ref$retries; | ||
(0, _classCallCheck2["default"])(this, HoneybadgerSourceMapPlugin); | ||
this.apiKey = apiKey; | ||
this.assetsUrl = assetsUrl; | ||
this.endpoint = endpoint; | ||
this.revision = revision; | ||
@@ -48,57 +63,83 @@ this.silent = silent; | ||
this.emittedAssets = new Map(); | ||
this.retries = retries; | ||
if (this.retries > _constants.MAX_RETRIES) { | ||
this.retries = 10; | ||
} | ||
} | ||
(0, _createClass2["default"])(HoneybadgerSourceMapPlugin, [{ | ||
key: "assetEmitted", | ||
value: function assetEmitted(file, content, done) { | ||
this.emittedAssets.set(file, content); | ||
done(); | ||
} | ||
}, { | ||
key: "afterEmit", | ||
value: function afterEmit(compilation, done) { | ||
var _this = this; | ||
value: function () { | ||
var _afterEmit = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee(compilation) { | ||
var errors, _compilation$errors, _compilation$errors2, _compilation$warnings; | ||
var errors = (0, _helpers.validateOptions)(this); | ||
return _regenerator["default"].wrap(function _callee$(_context) { | ||
while (1) { | ||
switch (_context.prev = _context.next) { | ||
case 0: | ||
errors = (0, _helpers.validateOptions)(this); | ||
if (errors) { | ||
var _compilation$errors; | ||
if (!errors) { | ||
_context.next = 4; | ||
break; | ||
} | ||
(_compilation$errors = compilation.errors).push.apply(_compilation$errors, (0, _toConsumableArray2["default"])((0, _helpers.handleError)(errors))); | ||
(_compilation$errors = compilation.errors).push.apply(_compilation$errors, (0, _toConsumableArray2["default"])((0, _helpers.handleError)(errors))); | ||
return done(); | ||
} | ||
return _context.abrupt("return"); | ||
this.uploadSourceMaps(compilation, function (err) { | ||
if (err) { | ||
if (!_this.ignoreErrors) { | ||
var _compilation$errors2; | ||
case 4: | ||
_context.prev = 4; | ||
_context.next = 7; | ||
return this.uploadSourceMaps(compilation); | ||
(_compilation$errors2 = compilation.errors).push.apply(_compilation$errors2, (0, _toConsumableArray2["default"])((0, _helpers.handleError)(err))); | ||
} else if (!_this.silent) { | ||
var _compilation$warnings; | ||
case 7: | ||
_context.next = 12; | ||
break; | ||
(_compilation$warnings = compilation.warnings).push.apply(_compilation$warnings, (0, _toConsumableArray2["default"])((0, _helpers.handleError)(err))); | ||
case 9: | ||
_context.prev = 9; | ||
_context.t0 = _context["catch"](4); | ||
if (!this.ignoreErrors) { | ||
(_compilation$errors2 = compilation.errors).push.apply(_compilation$errors2, (0, _toConsumableArray2["default"])((0, _helpers.handleError)(_context.t0))); | ||
} else if (!this.silent) { | ||
(_compilation$warnings = compilation.warnings).push.apply(_compilation$warnings, (0, _toConsumableArray2["default"])((0, _helpers.handleError)(_context.t0))); | ||
} | ||
case 12: | ||
case "end": | ||
return _context.stop(); | ||
} | ||
} | ||
} | ||
}, _callee, this, [[4, 9]]); | ||
})); | ||
_this.emittedAssets.clear(); | ||
function afterEmit(_x) { | ||
return _afterEmit.apply(this, arguments); | ||
} | ||
done(); | ||
}); | ||
} | ||
return afterEmit; | ||
}() | ||
}, { | ||
key: "apply", | ||
value: function apply(compiler) { | ||
if (compiler.hooks) { | ||
compiler.hooks.afterEmit.tapAsync(_constants.PLUGIN_NAME, this.afterEmit.bind(this)); | ||
compiler.hooks.afterEmit.tapPromise(_constants.PLUGIN_NAME, this.afterEmit.bind(this)); | ||
} // eslint-disable-next-line class-methods-use-this | ||
if (compiler.hooks.assetEmitted) { | ||
compiler.hooks.assetEmitted.tapAsync(_constants.PLUGIN_NAME, this.assetEmitted.bind(this)); | ||
} | ||
} else { | ||
compiler.plugin('after-emit', this.afterEmit.bind(this)); | ||
} | ||
}, { | ||
key: "getAssetPath", | ||
value: function getAssetPath(compilation, name) { | ||
return (0, _path.join)(compilation.getPath(compilation.compiler.outputPath), name.split('?')[0]); | ||
} | ||
}, { | ||
key: "getSource", | ||
value: function getSource(compilation, name) { | ||
var path = this.getAssetPath(compilation, name); | ||
return _fs.promises.readFile(path, { | ||
encoding: 'utf-8' | ||
}); | ||
} | ||
}, { | ||
key: "getAssets", | ||
@@ -110,7 +151,8 @@ value: function getAssets(compilation) { | ||
return (0, _lodash2["default"])(chunks, function (result, chunk) { | ||
var chunkName = chunk.names[0]; | ||
var sourceFile = (0, _lodash["default"])(chunk.files, function (file) { | ||
return /\.js$/.test(file); | ||
}); | ||
var sourceMap = (0, _lodash["default"])(chunk.files, function (file) { | ||
}); // Webpack 4 using chunk.files, Webpack 5 uses chunk.auxiliaryFiles | ||
// https://webpack.js.org/blog/2020-10-10-webpack-5-release/#stats | ||
var sourceMap = (chunk.auxiliaryFiles || chunk.files).find(function (file) { | ||
return /\.js\.map$/.test(file); | ||
@@ -130,65 +172,156 @@ }); | ||
}, { | ||
key: "getUrlToAsset", | ||
value: function getUrlToAsset(sourceFile) { | ||
if (typeof sourceFile === 'string') { | ||
var sep = this.assetsUrl.endsWith('/') ? '' : '/'; | ||
return "".concat(this.assetsUrl).concat(sep).concat(sourceFile); | ||
} | ||
return this.assetsUrl(sourceFile); | ||
} | ||
}, { | ||
key: "uploadSourceMap", | ||
value: function uploadSourceMap(compilation, _ref2, done) { | ||
var _this2 = this; | ||
value: function () { | ||
var _uploadSourceMap = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee2(compilation, _ref2) { | ||
var sourceFile, sourceMap, errorMessage, sourceMapSource, sourceFileSource, form, res, details, body; | ||
return _regenerator["default"].wrap(function _callee2$(_context2) { | ||
while (1) { | ||
switch (_context2.prev = _context2.next) { | ||
case 0: | ||
sourceFile = _ref2.sourceFile, sourceMap = _ref2.sourceMap; | ||
errorMessage = "failed to upload ".concat(sourceMap, " to Honeybadger API"); | ||
_context2.prev = 2; | ||
_context2.next = 5; | ||
return this.getSource(compilation, sourceMap); | ||
var sourceFile = _ref2.sourceFile, | ||
sourceMap = _ref2.sourceMap; | ||
case 5: | ||
sourceMapSource = _context2.sent; | ||
_context2.next = 8; | ||
return this.getSource(compilation, sourceFile); | ||
var req = _request["default"].post(_constants.ENDPOINT, function (err, res, body) { | ||
if (!err && res.statusCode === 201) { | ||
if (!_this2.silent) { | ||
console.info("Uploaded ".concat(sourceMap, " to Honeybadger API")); // eslint-disable-line no-console | ||
} | ||
case 8: | ||
sourceFileSource = _context2.sent; | ||
_context2.next = 14; | ||
break; | ||
return done(); | ||
} | ||
case 11: | ||
_context2.prev = 11; | ||
_context2.t0 = _context2["catch"](2); | ||
throw new _verror["default"](_context2.t0, _context2.t0.message); | ||
var errMessage = "failed to upload ".concat(sourceMap, " to Honeybadger API"); | ||
case 14: | ||
form = new _formData["default"](); | ||
form.append('api_key', this.apiKey); | ||
form.append('minified_url', this.getUrlToAsset(sourceFile)); | ||
form.append('minified_file', sourceFileSource, { | ||
filename: sourceFile, | ||
contentType: 'application/javascript' | ||
}); | ||
form.append('source_map', sourceMapSource, { | ||
filename: sourceMap, | ||
contentType: 'application/octet-stream' | ||
}); | ||
form.append('revision', this.revision); | ||
_context2.prev = 20; | ||
_context2.next = 23; | ||
return fetch(this.endpoint, { | ||
method: 'POST', | ||
body: form, | ||
redirect: 'follow', | ||
opts: { | ||
retries: this.retries, | ||
// Max timeout between retries, in milliseconds | ||
maxTimeout: 1000 | ||
} | ||
}); | ||
if (err) { | ||
return done(new _verror["default"](err, errMessage)); | ||
} | ||
case 23: | ||
res = _context2.sent; | ||
_context2.next = 29; | ||
break; | ||
var result; | ||
case 26: | ||
_context2.prev = 26; | ||
_context2.t1 = _context2["catch"](20); | ||
throw new _verror["default"](_context2.t1, errorMessage); | ||
try { | ||
var _JSON$parse = JSON.parse(body), | ||
error = _JSON$parse.error; | ||
case 29: | ||
if (res.ok) { | ||
_context2.next = 41; | ||
break; | ||
} | ||
result = new Error(error ? "".concat(errMessage, ": ").concat(error) : errMessage); | ||
} catch (parseErr) { | ||
result = new _verror["default"](parseErr, errMessage); | ||
} | ||
_context2.prev = 30; | ||
_context2.next = 33; | ||
return res.json(); | ||
return done(result); | ||
}); | ||
case 33: | ||
body = _context2.sent; | ||
var form = req.form(); | ||
form.append('api_key', this.apiKey); | ||
form.append('minified_url', "".concat(this.assetsUrl.toString().replace(/^\//, ''), "/").concat(sourceFile.replace(/^\//, ''))); | ||
form.append('minified_file', this.emittedAssets.get(sourceFile) || compilation.assets[sourceFile].source(), { | ||
filename: sourceFile, | ||
contentType: 'application/javascript' | ||
}); | ||
form.append('source_map', this.emittedAssets.get(sourceMap) || compilation.assets[sourceMap].source(), { | ||
filename: sourceMap, | ||
contentType: 'application/octet-stream' | ||
}); | ||
form.append('revision', this.revision); | ||
} | ||
if (body && body.error) { | ||
details = body.error; | ||
} else { | ||
details = "".concat(res.status, " - ").concat(res.statusText); | ||
} | ||
_context2.next = 40; | ||
break; | ||
case 37: | ||
_context2.prev = 37; | ||
_context2.t2 = _context2["catch"](30); | ||
details = "".concat(res.status, " - ").concat(res.statusText); | ||
case 40: | ||
throw new Error("".concat(errorMessage, ": ").concat(details)); | ||
case 41: | ||
// Success | ||
if (!this.silent) { | ||
// eslint-disable-next-line no-console | ||
console.info("Uploaded ".concat(sourceMap, " to Honeybadger API")); | ||
} | ||
case 42: | ||
case "end": | ||
return _context2.stop(); | ||
} | ||
} | ||
}, _callee2, this, [[2, 11], [20, 26], [30, 37]]); | ||
})); | ||
function uploadSourceMap(_x2, _x3) { | ||
return _uploadSourceMap.apply(this, arguments); | ||
} | ||
return uploadSourceMap; | ||
}() | ||
}, { | ||
key: "uploadSourceMaps", | ||
value: function uploadSourceMaps(compilation, done) { | ||
value: function uploadSourceMaps(compilation) { | ||
var _this = this; | ||
var assets = this.getAssets(compilation); | ||
var upload = this.uploadSourceMap.bind(this, compilation); | ||
_async["default"].each(assets, upload, function (err, results) { | ||
if (err) { | ||
return done(err); | ||
if (assets.length <= 0) { | ||
// We should probably tell people they're not uploading assets. | ||
// this is also an open issue on Rollbar sourcemap plugin | ||
// https://github.com/thredup/rollbar-sourcemap-webpack-plugin/issues/39 | ||
if (!this.silent) { | ||
console.info(this.noAssetsFoundMessage); | ||
} | ||
return done(null, results); | ||
}); | ||
return; | ||
} | ||
console.info('\n'); | ||
return Promise.all(assets.map(function (asset) { | ||
return _this.uploadSourceMap(compilation, asset); | ||
})); | ||
} | ||
}, { | ||
key: "noAssetsFoundMessage", | ||
get: function get() { | ||
return '\nHoneybadger could not find any sourcemaps. Nothing will be uploaded.'; | ||
} | ||
}]); | ||
@@ -195,0 +328,0 @@ return HoneybadgerSourceMapPlugin; |
{ | ||
"name": "@honeybadger-io/webpack", | ||
"version": "1.2.0", | ||
"version": "1.3.0-beta.0", | ||
"description": "Webpack plugin to upload source maps to Honeybadger's API - http://docs.honeybadger.io/guides/source-maps.html", | ||
@@ -12,7 +12,9 @@ "main": "./dist/HoneybadgerSourceMapPlugin.js", | ||
"prepublishOnly": "npm run clean && npm run build && npm run test", | ||
"test": "nyc mocha --require @babel/register", | ||
"test": "mocha --require @babel/register", | ||
"test:watch": "npm test -- -w", | ||
"preversion": "npm test", | ||
"version": "scripts/update-versions.sh", | ||
"postversion": "git push && git push --tags" | ||
"postversion": "git push && git push --tags", | ||
"lint": "standard ./{src,test}/**/*.js", | ||
"fix": "standard --fix ./{src,test}/**/*.js" | ||
}, | ||
@@ -38,26 +40,37 @@ "repository": { | ||
"devDependencies": { | ||
"@babel/cli": "^7.7.5", | ||
"@babel/core": "^7.7.5", | ||
"@babel/plugin-transform-runtime": "^7.7.6", | ||
"@babel/preset-env": "^7.7.6", | ||
"@babel/register": "^7.7.4", | ||
"cross-env": "^6.0.3", | ||
"@babel/cli": "^7.13.10", | ||
"@babel/core": "^7.13.10", | ||
"@babel/plugin-transform-runtime": "^7.13.10", | ||
"@babel/preset-env": "^7.13.10", | ||
"@babel/register": "^7.13.8", | ||
"chai": "^4.3.4", | ||
"cross-env": "^7.0.0", | ||
"debug": "^4.1.0", | ||
"expect": "^1.20.1", | ||
"lodash": "^4.17.15", | ||
"mocha": "^6.2.2", | ||
"nock": "^11.7.0", | ||
"nyc": "^14.1.0", | ||
"rimraf": "^3.0.0", | ||
"husky": "^5.1.3", | ||
"lodash": "^4.17.21", | ||
"mocha": "^8.3.1", | ||
"nock": "^13.0.11", | ||
"rimraf": "^3.0.2", | ||
"sinon": "^9.2.4", | ||
"standard": "^16.0.3", | ||
"watch": "^1.0.2" | ||
}, | ||
"dependencies": { | ||
"@babel/runtime": "^7.7.6", | ||
"async": "^3.0.0", | ||
"@babel/runtime": "^7.13.10", | ||
"@vercel/fetch-retry": "^5.0.3", | ||
"form-data": "^4.0.0", | ||
"lodash.find": "^4.3.0", | ||
"lodash.foreach": "^4.2.0", | ||
"lodash.reduce": "^4.3.0", | ||
"request": "^2.87.0", | ||
"node-fetch": "^2.6.1", | ||
"verror": "^1.6.1" | ||
}, | ||
"peerDependencies": { | ||
"webpack": ">= 4.0.0" | ||
}, | ||
"husky": { | ||
"hooks": { | ||
"pre-commit": "npm run fix" | ||
} | ||
} | ||
} |
@@ -34,2 +34,7 @@ # Honeybadger's Webpack Source Map Plugin | ||
<dt><code>endpoint</code> (optional — default: "https://api.honeybadger.io/v1/source_maps")</dt> | ||
<dd>Where to upload your sourcemaps to. Perhaps you have a self hosted | ||
sourcemap server you would like to upload your sourcemaps to instead | ||
of honeybadger.</dd> | ||
<dt><code>revision</code> (optional — default: "master")</dt> | ||
@@ -43,2 +48,9 @@ <dd>The deploy revision (i.e. commit sha) that your source map applies to. This could also be a code version. For best results, set it to something unique every time your code changes.</dd> | ||
<dd>If true, webpack compilation errors are treated as warnings.</dd> | ||
<dt><code>retries</code> (optional — default: 3, max: 10)</dt> | ||
<dd>This package implements fetch retry functionality via | ||
https://github.com/vercel/fetch-retry </br> | ||
Retrying helps fix issues like `ECONNRESET` and `SOCKETTIMEOUT` | ||
errors and retries on 429 and 500 errors as well. | ||
</dd> | ||
</dl> | ||
@@ -87,2 +99,6 @@ | ||
## Changelog | ||
See https://github.com/honeybadger-io/honeybadger-webpack/blob/master/CHANGELOG.md | ||
## Contributing | ||
@@ -110,3 +126,2 @@ | ||
## License | ||
@@ -113,0 +128,0 @@ |
export const PLUGIN_NAME = 'HoneybadgerSourceMapPlugin' | ||
export const ENDPOINT = 'https://api.honeybadger.io/v1/source_maps '; | ||
export const ENDPOINT = 'https://api.honeybadger.io/v1/source_maps' | ||
export const MAX_RETRIES = 10 | ||
export const REQUIRED_FIELDS = [ | ||
'apiKey', | ||
'assetsUrl', | ||
]; | ||
'assetsUrl' | ||
] |
@@ -1,17 +0,17 @@ | ||
import VError from 'verror'; | ||
import { REQUIRED_FIELDS } from './constants'; | ||
import VError from 'verror' | ||
import { REQUIRED_FIELDS } from './constants' | ||
export function handleError(err, prefix = 'HoneybadgerSourceMapPlugin') { | ||
export function handleError (err, prefix = 'HoneybadgerSourceMapPlugin') { | ||
if (!err) { | ||
return []; | ||
return [] | ||
} | ||
const errors = [].concat(err); | ||
return errors.map(e => new VError(e, prefix)); | ||
const errors = [].concat(err) | ||
return errors.map(e => new VError(e, prefix)) | ||
} | ||
export function validateOptions(ref) { | ||
export function validateOptions (ref) { | ||
const errors = REQUIRED_FIELDS.reduce((result, field) => { | ||
if (ref && ref[field]) { | ||
return result; | ||
return result | ||
} | ||
@@ -22,6 +22,6 @@ | ||
new Error(`required field, '${field}', is missing.`) | ||
]; | ||
}, []); | ||
] | ||
}, []) | ||
return errors.length ? errors : null; | ||
return errors.length ? errors : null | ||
} |
@@ -1,73 +0,89 @@ | ||
import async from 'async'; | ||
import request from 'request'; | ||
import VError from 'verror'; | ||
import find from 'lodash.find'; | ||
import reduce from 'lodash.reduce'; | ||
import { handleError, validateOptions } from './helpers'; | ||
import { ENDPOINT, PLUGIN_NAME } from './constants'; | ||
import { promises as fs } from 'fs' | ||
import { join } from 'path' | ||
import nodeFetch from 'node-fetch' | ||
import fetchRetry from '@vercel/fetch-retry' | ||
import VError from 'verror' | ||
import find from 'lodash.find' | ||
import reduce from 'lodash.reduce' | ||
import FormData from 'form-data' | ||
import { handleError, validateOptions } from './helpers' | ||
import { ENDPOINT, PLUGIN_NAME, MAX_RETRIES } from './constants' | ||
const fetch = fetchRetry(nodeFetch) | ||
class HoneybadgerSourceMapPlugin { | ||
constructor({ | ||
constructor ({ | ||
apiKey, | ||
assetsUrl, | ||
revision = "master", | ||
endpoint = ENDPOINT, | ||
revision = 'master', | ||
silent = false, | ||
ignoreErrors = false | ||
ignoreErrors = false, | ||
retries = 3 | ||
}) { | ||
this.apiKey = apiKey; | ||
this.assetsUrl = assetsUrl; | ||
this.revision = revision; | ||
this.silent = silent; | ||
this.ignoreErrors = ignoreErrors; | ||
this.emittedAssets = new Map(); | ||
} | ||
this.apiKey = apiKey | ||
this.assetsUrl = assetsUrl | ||
this.endpoint = endpoint | ||
this.revision = revision | ||
this.silent = silent | ||
this.ignoreErrors = ignoreErrors | ||
this.emittedAssets = new Map() | ||
assetEmitted(file, content, done) { | ||
this.emittedAssets.set(file, content); | ||
done(); | ||
this.retries = retries | ||
if (this.retries > MAX_RETRIES) { | ||
this.retries = 10 | ||
} | ||
} | ||
afterEmit(compilation, done) { | ||
const errors = validateOptions(this); | ||
async afterEmit (compilation) { | ||
const errors = validateOptions(this) | ||
if (errors) { | ||
compilation.errors.push(...handleError(errors)); | ||
return done(); | ||
compilation.errors.push(...handleError(errors)) | ||
return | ||
} | ||
this.uploadSourceMaps(compilation, (err) => { | ||
if (err) { | ||
if (!this.ignoreErrors) { | ||
compilation.errors.push(...handleError(err)); | ||
} else if (!this.silent) { | ||
compilation.warnings.push(...handleError(err)); | ||
} | ||
try { | ||
await this.uploadSourceMaps(compilation) | ||
} catch (err) { | ||
if (!this.ignoreErrors) { | ||
compilation.errors.push(...handleError(err)) | ||
} else if (!this.silent) { | ||
compilation.warnings.push(...handleError(err)) | ||
} | ||
this.emittedAssets.clear(); | ||
done(); | ||
}); | ||
} | ||
} | ||
apply(compiler) { | ||
if (compiler.hooks) { | ||
compiler.hooks.afterEmit.tapAsync(PLUGIN_NAME, this.afterEmit.bind(this)); | ||
if (compiler.hooks.assetEmitted) { | ||
compiler.hooks.assetEmitted.tapAsync(PLUGIN_NAME, this.assetEmitted.bind(this)); | ||
} | ||
} else { | ||
compiler.plugin('after-emit', this.afterEmit.bind(this)); | ||
} | ||
apply (compiler) { | ||
compiler.hooks.afterEmit.tapPromise(PLUGIN_NAME, this.afterEmit.bind(this)) | ||
} | ||
getAssets(compilation) { | ||
const { chunks } = compilation.getStats().toJson(); | ||
// eslint-disable-next-line class-methods-use-this | ||
getAssetPath (compilation, name) { | ||
return join( | ||
compilation.getPath(compilation.compiler.outputPath), | ||
name.split('?')[0] | ||
) | ||
} | ||
getSource (compilation, name) { | ||
const path = this.getAssetPath(compilation, name) | ||
return fs.readFile(path, { encoding: 'utf-8' }) | ||
} | ||
getAssets (compilation) { | ||
const { chunks } = compilation.getStats().toJson() | ||
return reduce(chunks, (result, chunk) => { | ||
const chunkName = chunk.names[0]; | ||
const sourceFile = find(chunk.files, file => /\.js$/.test(file)) | ||
const sourceFile = find(chunk.files, file => /\.js$/.test(file)); | ||
const sourceMap = find(chunk.files, file => /\.js\.map$/.test(file)); | ||
// Webpack 4 using chunk.files, Webpack 5 uses chunk.auxiliaryFiles | ||
// https://webpack.js.org/blog/2020-10-10-webpack-5-release/#stats | ||
const sourceMap = (chunk.auxiliaryFiles || chunk.files).find(file => | ||
/\.js\.map$/.test(file) | ||
) | ||
if (!sourceFile || !sourceMap) { | ||
return result; | ||
return result | ||
} | ||
@@ -78,59 +94,109 @@ | ||
{ sourceFile, sourceMap } | ||
]; | ||
}, []); | ||
] | ||
}, []) | ||
} | ||
uploadSourceMap(compilation, { sourceFile, sourceMap }, done) { | ||
const req = request.post(ENDPOINT, (err, res, body) => { | ||
if (!err && res.statusCode === 201) { | ||
if (!this.silent) { | ||
console.info(`Uploaded ${sourceMap} to Honeybadger API`); // eslint-disable-line no-console | ||
} | ||
return done(); | ||
} | ||
getUrlToAsset (sourceFile) { | ||
if (typeof sourceFile === 'string') { | ||
const sep = this.assetsUrl.endsWith('/') ? '' : '/' | ||
return `${this.assetsUrl}${sep}${sourceFile}` | ||
} | ||
return this.assetsUrl(sourceFile) | ||
} | ||
const errMessage = `failed to upload ${sourceMap} to Honeybadger API`; | ||
if (err) { | ||
return done(new VError(err, errMessage)); | ||
} | ||
async uploadSourceMap (compilation, { sourceFile, sourceMap }) { | ||
const errorMessage = `failed to upload ${sourceMap} to Honeybadger API` | ||
let result; | ||
let sourceMapSource | ||
let sourceFileSource | ||
try { | ||
sourceMapSource = await this.getSource(compilation, sourceMap) | ||
sourceFileSource = await this.getSource(compilation, sourceFile) | ||
} catch (err) { | ||
throw new VError(err, err.message) | ||
} | ||
const form = new FormData() | ||
form.append('api_key', this.apiKey) | ||
form.append('minified_url', this.getUrlToAsset(sourceFile)) | ||
form.append('minified_file', sourceFileSource, { | ||
filename: sourceFile, | ||
contentType: 'application/javascript' | ||
}) | ||
form.append('source_map', sourceMapSource, { | ||
filename: sourceMap, | ||
contentType: 'application/octet-stream' | ||
}) | ||
form.append('revision', this.revision) | ||
let res | ||
try { | ||
res = await fetch(this.endpoint, { | ||
method: 'POST', | ||
body: form, | ||
redirect: 'follow', | ||
opts: { | ||
retries: this.retries, | ||
// Max timeout between retries, in milliseconds | ||
maxTimeout: 1000 | ||
} | ||
}) | ||
} catch (err) { | ||
// network / operational errors. Does not include 404 / 500 errors | ||
throw new VError(err, errorMessage) | ||
} | ||
// >= 400 responses | ||
if (!res.ok) { | ||
// Attempt to parse error details from response | ||
let details | ||
try { | ||
const { error } = JSON.parse(body); | ||
result = new Error(error ? `${errMessage}: ${error}` : errMessage); | ||
const body = await res.json() | ||
if (body && body.error) { | ||
details = body.error | ||
} else { | ||
details = `${res.status} - ${res.statusText}` | ||
} | ||
} catch (parseErr) { | ||
result = new VError(parseErr, errMessage); | ||
details = `${res.status} - ${res.statusText}` | ||
} | ||
return done(result); | ||
}); | ||
throw new Error(`${errorMessage}: ${details}`) | ||
} | ||
const form = req.form(); | ||
form.append('api_key', this.apiKey); | ||
form.append('minified_url', `${this.assetsUrl.toString().replace(/^\//, '')}/${sourceFile.replace(/^\//, '')}`); | ||
form.append('minified_file', (this.emittedAssets.get(sourceFile) || compilation.assets[sourceFile].source()), { | ||
filename: sourceFile, | ||
contentType: 'application/javascript' | ||
}); | ||
form.append('source_map', (this.emittedAssets.get(sourceMap) || compilation.assets[sourceMap].source()), { | ||
filename: sourceMap, | ||
contentType: 'application/octet-stream' | ||
}); | ||
form.append('revision', this.revision); | ||
// Success | ||
if (!this.silent) { | ||
// eslint-disable-next-line no-console | ||
console.info(`Uploaded ${sourceMap} to Honeybadger API`) | ||
} | ||
} | ||
uploadSourceMaps(compilation, done) { | ||
const assets = this.getAssets(compilation); | ||
const upload = this.uploadSourceMap.bind(this, compilation); | ||
uploadSourceMaps (compilation) { | ||
const assets = this.getAssets(compilation) | ||
async.each(assets, upload, (err, results) => { | ||
if (err) { | ||
return done(err); | ||
if (assets.length <= 0) { | ||
// We should probably tell people they're not uploading assets. | ||
// this is also an open issue on Rollbar sourcemap plugin | ||
// https://github.com/thredup/rollbar-sourcemap-webpack-plugin/issues/39 | ||
if (!this.silent) { | ||
console.info(this.noAssetsFoundMessage) | ||
} | ||
return done(null, results); | ||
}); | ||
return | ||
} | ||
console.info('\n') | ||
return Promise.all( | ||
assets.map(asset => this.uploadSourceMap(compilation, asset)) | ||
) | ||
} | ||
get noAssetsFoundMessage () { | ||
return '\nHoneybadger could not find any sourcemaps. Nothing will be uploaded.' | ||
} | ||
} | ||
module.exports = HoneybadgerSourceMapPlugin; | ||
module.exports = HoneybadgerSourceMapPlugin |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
30496
501
128
9
16
1
2
3
+ Added@vercel/fetch-retry@^5.0.3
+ Addedform-data@^4.0.0
+ Addednode-fetch@^2.6.1
+ Added@jridgewell/gen-mapping@0.3.8(transitive)
+ Added@jridgewell/resolve-uri@3.1.2(transitive)
+ Added@jridgewell/set-array@1.2.1(transitive)
+ Added@jridgewell/source-map@0.3.6(transitive)
+ Added@jridgewell/sourcemap-codec@1.5.0(transitive)
+ Added@jridgewell/trace-mapping@0.3.25(transitive)
+ Added@types/eslint@9.6.1(transitive)
+ Added@types/eslint-scope@3.7.7(transitive)
+ Added@types/estree@1.0.6(transitive)
+ Added@types/json-schema@7.0.15(transitive)
+ Added@types/node@22.10.2(transitive)
+ Added@vercel/fetch-retry@5.1.3(transitive)
+ Added@webassemblyjs/ast@1.14.1(transitive)
+ Added@webassemblyjs/floating-point-hex-parser@1.13.2(transitive)
+ Added@webassemblyjs/helper-api-error@1.13.2(transitive)
+ Added@webassemblyjs/helper-buffer@1.14.1(transitive)
+ Added@webassemblyjs/helper-numbers@1.13.2(transitive)
+ Added@webassemblyjs/helper-wasm-bytecode@1.13.2(transitive)
+ Added@webassemblyjs/helper-wasm-section@1.14.1(transitive)
+ Added@webassemblyjs/ieee754@1.13.2(transitive)
+ Added@webassemblyjs/leb128@1.13.2(transitive)
+ Added@webassemblyjs/utf8@1.13.2(transitive)
+ Added@webassemblyjs/wasm-edit@1.14.1(transitive)
+ Added@webassemblyjs/wasm-gen@1.14.1(transitive)
+ Added@webassemblyjs/wasm-opt@1.14.1(transitive)
+ Added@webassemblyjs/wasm-parser@1.14.1(transitive)
+ Added@webassemblyjs/wast-printer@1.14.1(transitive)
+ Added@xtuc/ieee754@1.2.0(transitive)
+ Added@xtuc/long@4.2.2(transitive)
+ Addedacorn@8.14.0(transitive)
+ Addedajv@8.17.1(transitive)
+ Addedajv-formats@2.1.1(transitive)
+ Addedajv-keywords@3.5.25.1.0(transitive)
+ Addedasync-retry@1.3.3(transitive)
+ Addedbrowserslist@4.24.3(transitive)
+ Addedbuffer-from@1.1.2(transitive)
+ Addedcaniuse-lite@1.0.30001688(transitive)
+ Addedchrome-trace-event@1.0.4(transitive)
+ Addedcommander@2.20.3(transitive)
+ Addeddebug@4.4.0(transitive)
+ Addedelectron-to-chromium@1.5.73(transitive)
+ Addedenhanced-resolve@5.17.1(transitive)
+ Addedes-module-lexer@1.5.4(transitive)
+ Addedescalade@3.2.0(transitive)
+ Addedeslint-scope@5.1.1(transitive)
+ Addedesrecurse@4.3.0(transitive)
+ Addedestraverse@4.3.05.3.0(transitive)
+ Addedevents@3.3.0(transitive)
+ Addedextsprintf@1.4.1(transitive)
+ Addedfast-uri@3.0.3(transitive)
+ Addedform-data@4.0.1(transitive)
+ Addedglob-to-regexp@0.4.1(transitive)
+ Addedgraceful-fs@4.2.11(transitive)
+ Addedhas-flag@4.0.0(transitive)
+ Addedjest-worker@27.5.1(transitive)
+ Addedjson-parse-even-better-errors@2.3.1(transitive)
+ Addedjson-schema-traverse@1.0.0(transitive)
+ Addedloader-runner@4.3.0(transitive)
+ Addedmerge-stream@2.0.0(transitive)
+ Addedms@2.1.3(transitive)
+ Addedneo-async@2.6.2(transitive)
+ Addednode-fetch@2.7.0(transitive)
+ Addednode-releases@2.0.19(transitive)
+ Addedpicocolors@1.1.1(transitive)
+ Addedrandombytes@2.1.0(transitive)
+ Addedrequire-from-string@2.0.2(transitive)
+ Addedretry@0.13.1(transitive)
+ Addedschema-utils@3.3.04.3.0(transitive)
+ Addedserialize-javascript@6.0.2(transitive)
+ Addedsource-map@0.6.1(transitive)
+ Addedsource-map-support@0.5.21(transitive)
+ Addedsupports-color@8.1.1(transitive)
+ Addedtapable@2.2.1(transitive)
+ Addedterser@5.37.0(transitive)
+ Addedterser-webpack-plugin@5.3.11(transitive)
+ Addedtr46@0.0.3(transitive)
+ Addedundici-types@6.20.0(transitive)
+ Addedupdate-browserslist-db@1.1.1(transitive)
+ Addedwatchpack@2.4.2(transitive)
+ Addedwebidl-conversions@3.0.1(transitive)
+ Addedwebpack@5.97.1(transitive)
+ Addedwebpack-sources@3.2.3(transitive)
+ Addedwhatwg-url@5.0.0(transitive)
- Removedasync@^3.0.0
- Removedrequest@^2.87.0
- Removedasn1@0.2.6(transitive)
- Removedasync@3.2.6(transitive)
- Removedaws-sign2@0.7.0(transitive)
- Removedaws4@1.13.2(transitive)
- Removedbcrypt-pbkdf@1.0.2(transitive)
- Removedcaseless@0.12.0(transitive)
- Removeddashdash@1.14.1(transitive)
- Removedecc-jsbn@0.1.2(transitive)
- Removedextend@3.0.2(transitive)
- Removedextsprintf@1.3.0(transitive)
- Removedforever-agent@0.6.1(transitive)
- Removedform-data@2.3.3(transitive)
- Removedgetpass@0.1.7(transitive)
- Removedhar-schema@2.0.0(transitive)
- Removedhar-validator@5.1.5(transitive)
- Removedhttp-signature@1.2.0(transitive)
- Removedis-typedarray@1.0.0(transitive)
- Removedisstream@0.1.2(transitive)
- Removedjsbn@0.1.1(transitive)
- Removedjson-schema@0.4.0(transitive)
- Removedjson-stringify-safe@5.0.1(transitive)
- Removedjsprim@1.4.2(transitive)
- Removedoauth-sign@0.9.0(transitive)
- Removedperformance-now@2.1.0(transitive)
- Removedpsl@1.15.0(transitive)
- Removedqs@6.5.3(transitive)
- Removedrequest@2.88.2(transitive)
- Removedsafer-buffer@2.1.2(transitive)
- Removedsshpk@1.18.0(transitive)
- Removedtough-cookie@2.5.0(transitive)
- Removedtunnel-agent@0.6.0(transitive)
- Removedtweetnacl@0.14.5(transitive)
- Removeduuid@3.4.0(transitive)
- Removedverror@1.10.0(transitive)
Updated@babel/runtime@^7.13.10