Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/sourcemaps #1

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
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