Skip to content
49 changes: 34 additions & 15 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@
},
"dependencies": {
"bluebird": "^3.5.1",
"glob-all": "^3.1.0",
"lodash": "^4.17.10",
"semver": "^5.5.0",
"superagent": "^3.8.3",
"superagent-throttle": "^1.0.1",
"uuid": "^3.3.2"
},
"peerDependencies": {
Expand Down
118 changes: 116 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ const _ = require("lodash")
, SemVer = require("semver")
, uuid = require("uuid/v4")
, request = require("superagent")
, GitRev = require("./git-rev");
, GitRev = require("./git-rev")
, glob = require("glob-all")
, path = require("path")
, Throttle = require("superagent-throttle");

/**
* Serverless Plugin forward Lambda exceptions to Sentry (https://sentry.io)
Expand All @@ -25,6 +28,14 @@ class Sentry {
.then(this.setRelease)
.then(this.instrumentFunctions),

"before:package:createDeploymentArtifacts": () => BbPromise.bind(this)
.then(this.createSentryRelease)
.then(this.deploySentryRelease),

"after:package:createDeploymentArtifacts": () => BbPromise.bind(this)
.then(this.deploySentrySourceMaps),


"before:deploy:deploy": () => BbPromise.bind(this)
.then(this.validate),

Expand Down Expand Up @@ -156,9 +167,23 @@ class Sentry {

_resolveGitRefs(gitRev) {
return BbPromise.join(
gitRev.origin().then(str => str.match(/[:/]([^/]+\/[^/]+?)(?:\.git)?$/i)[1]).catch(_.noop),
gitRev.origin(),
gitRev.long()
)
.spread((origin, commit) => {
let repository = null;
try {
repository = origin.match(/[:/]([^/]+\/[^/]+?)(?:\.git)?$/i)[1];
if(_.includes(origin, "gitlab")) {
// gitlab uses spaces around the slashes in the repository name
repository = repository.replace(/\//g, " / ");
}
}
catch (err) {
// ignore
}
return BbPromise.join(repository, commit);
})
.spread((repository, commit) => {
_.forEach(_.get(this.sentry, "release.refs", []), ref => {
if (ref && ref.repository === "git") {
Expand Down Expand Up @@ -288,6 +313,95 @@ class Sentry {
});
}

deploySentrySourceMaps() {
if (!this.sentry.authToken || !this.sentry.release || !this.sentry.uploadSourcemaps) {
// Nothing to do
return BbPromise.resolve();
}

const organization = this.sentry.organization;
const release = this.sentry.release;
const buildDirectory = path.join(this._serverless.config.servicePath, "../");
this._serverless.cli.log(
`Sentry: Uploading source maps for "${release.version}" from directory "${buildDirectory}"...`
);

const throttle = new Throttle({
active: true, // set false to pause queue
rate: 20, // how many requests can be sent every `ratePer`
ratePer: 1000, // number of ms in which `rate` requests may be sent
concurrent: 5 // how many requests can be sent concurrently
}).on("sent", (request) => {
this._serverless.cli.log(
`Sentry: Uploading file ${request.filename} to sentry...`
);
});

const upload = filepath => BbPromise.fromCallback(cb => {

let filename = filepath.split(buildDirectory)[1];
if(filename.startsWith("node_modules")) {
filename = `/var/task/${filename}`;
}
else {
filename = `/var/${filename}`;
}

return request.post(
`https://sentry.io/api/0/organizations/${organization}/releases/${encodeURIComponent(
release.version
)}/files/`
)
.set("Authorization", `Bearer ${this.sentry.authToken}`)
.attach(
"file",
filepath,
filename
)
.use((req) => {
req.filepath = filepath;
req.filename = filename;
return req;
})
.use(throttle.plugin())
.end( (error, result) => {
if(result && result.status === 409) {
return cb(null, result);
}

if(error) {
return cb(error);
}

return cb(null, result);
});
});

const types = ["js", "js.map", "ts"];
const globs = types
.map( t => `${buildDirectory}/**/*.${t}`)
.concat(`!${buildDirectory}/node_modules`)
.concat(`!${buildDirectory}/**/*.d.ts`);

this._serverless.cli.log(
`Sentry: Uploading source maps for globs ${globs}...`
);
const files = glob.sync(globs);
const uploads = files.map( f => () => upload(f));
return BbPromise.map(uploads, u => u()).catch(err => {
if (err && err.response && err.responsetext) {
this._serverless.cli.log(
`Sentry: Received error response from Sentry:\n${err.response.text}`
);
}
return BbPromise.reject(
new this._serverless.classes.Error(
"Sentry: Error uploading source map - " + err.stack
)
);
});
}

getRandomVersion() {
return _.replace(uuid(), /-/g, "");
}
Expand Down