Skip to content

Commit ca1fd10

Browse files
authored
Pawel/signed url (#10)
1 parent a5307b7 commit ca1fd10

File tree

11 files changed

+452
-9
lines changed

11 files changed

+452
-9
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ Options:
3131
--url-prefix Base part of the stack trace URLs [string] [required]
3232
--code-version Code version string must match value in the Rollbar item
3333
[string] [required]
34+
--next Next version. Zip all the source map files and upload as one
35+
file [boolean]
3436
-D, --dry-run Scan and validate source maps without uploading [boolean]
3537
```
3638

@@ -52,10 +54,17 @@ and it must exactly match the URLs in the error stack frames. See `minified_url`
5254
error payload, which is usually set in the config options for Rollbar.js.
5355
See [Source Maps](https://docs.rollbar.com/docs/source-maps) for more information.
5456

57+
`--next`: This is an optional parameter triggering next version. When specified, all source map files
58+
are compressed and uploaded as one zip file directly.
59+
5560
Example:
5661
```
5762
rollbar-cli upload-sourcemaps ./dist --access-token 638d... --url-prefix 'http://example.com/' --code-version 123.456
5863
```
64+
or
65+
```
66+
rollbar-cli upload-sourcemaps ./dist --access-token 638d... --url-prefix 'http://example.com/' --code-version 123.456 --signed-url
67+
```
5968

6069
### notify-deploy
6170
Notify deploy to Rollbar.

package.json

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66
"rollbar-cli": "bin/rollbar"
77
},
88
"scripts": {
9-
"lint": "./node_modules/.bin/eslint . --ext .js",
9+
"lint": "eslint . --ext .js",
1010
"test": "nyc --reporter=html --reporter=text mocha './test/{,!(fixtures)/**/}*test.js'"
1111
},
1212
"dependencies": {
13+
"adm-zip": "^0.5.2",
1314
"axios": "^0.19.2",
1415
"chalk": "^4.1.0",
1516
"form-data": "^3.0.0",
@@ -23,5 +24,20 @@
2324
"mocha": "^7.2.0",
2425
"nyc": "^15.1.0",
2526
"sinon": "^9.0.3"
26-
}
27+
},
28+
"description": "![build](https://github.com/rollbar/rollbar-cli/workflows/Node.js%20CI/badge.svg)",
29+
"main": "index.js",
30+
"directories": {
31+
"test": "test"
32+
},
33+
"repository": {
34+
"type": "git",
35+
"url": "git+https://github.com/rollbar/rollbar-cli.git"
36+
},
37+
"keywords": [],
38+
"author": "",
39+
"bugs": {
40+
"url": "https://github.com/rollbar/rollbar-cli/issues"
41+
},
42+
"homepage": "https://github.com/rollbar/rollbar-cli#readme"
2743
}

src/common/rollbar-api.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ class RollbarAPI {
3636
return this.processResponse(resp);
3737
}
3838

39+
async sigendURLsourcemaps(request) {
40+
41+
const resp = await this.axios.post(
42+
'/signed_url/sourcemap_bundle', { version: request.version , prefix_url: request.baseUrl}
43+
);
44+
return this.processSignedURLResponse(resp);
45+
}
46+
3947
async sourcemaps(request) {
4048
output.verbose('', 'minified_url: ' + request.minified_url);
4149

@@ -70,6 +78,11 @@ class RollbarAPI {
7078
return form;
7179
}
7280

81+
processSignedURLResponse(resp) {
82+
output.verbose('', 'response:', resp.data, resp.status, resp.statusText);
83+
return resp.data;
84+
}
85+
7386
processResponse(resp) {
7487
output.verbose('', 'response:', resp.data, resp.status, resp.statusText);
7588
if (resp.status === 200) {

src/sourcemaps/command.js

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
const Scanner = require('./scanner');
44
const Uploader = require('./uploader');
5+
const SignedUrlUploader = require('./signed-url-uploader');
6+
const Requester = require('./requester');
57
const Output = require('../common/output.js');
68

79
exports.command = 'upload-sourcemaps <path> [options]'
@@ -28,6 +30,12 @@ exports.builder = function (yargs) {
2830
type: 'string',
2931
demandOption: true
3032
})
33+
.option('next', {
34+
describe: 'Next version. Zip all the source map files and upload as one file',
35+
requiresArg: false,
36+
type: 'boolean',
37+
demandOption: false
38+
})
3139
.option('D', {
3240
alias: 'dry-run',
3341
describe: 'Scan and validate source maps without uploading',
@@ -50,13 +58,30 @@ exports.handler = async function (argv) {
5058

5159
await scanner.scan();
5260

53-
const uploader = new Uploader({
54-
accessToken: argv['access-token'],
55-
baseUrl: argv['url-prefix'],
56-
codeVersion: argv['code-version']
57-
})
61+
if (argv['next']) {
62+
const requester = new Requester({
63+
accessToken: argv['access-token'],
64+
baseUrl: argv['url-prefix'],
65+
codeVersion: argv['code-version'],
66+
dryRun: argv['dry-run']
67+
});
68+
69+
await requester.requestSignedUrl();
70+
const signedUrlUploader = new SignedUrlUploader(requester);
71+
if (requester.data && requester.data['err'] === 0) {
72+
requester.setProjectID();
73+
requester.createManifestData();
74+
await signedUrlUploader.upload(argv['dry-run'], scanner.files, requester.data['result']['signed_url']);
75+
}
76+
} else {
77+
const uploader = new Uploader({
78+
accessToken: argv['access-token'],
79+
baseUrl: argv['url-prefix'],
80+
codeVersion: argv['code-version']
81+
});
5882

59-
uploader.mapFiles(scanner.files);
83+
uploader.mapFiles(scanner.files);
6084

61-
await uploader.upload(argv['dry-run']);
85+
await uploader.upload(argv['dry-run']);
86+
}
6287
}

src/sourcemaps/requester.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
'use strict';
2+
3+
const RollbarAPI = require('../common/rollbar-api');
4+
5+
class Requester {
6+
constructor(options) {
7+
this.data = null;
8+
this.rollbarAPI = new RollbarAPI(options.accessToken);
9+
this.baseUrl = options.baseUrl;
10+
this.version = options.codeVersion;
11+
this.projectID = 0;
12+
this.dryRun = options.dryRun;
13+
this.manifestData = '';
14+
}
15+
16+
async requestSignedUrl() {
17+
if (this.dryRun) {
18+
// TODO: Maybe more can be done here, but the important part is just to
19+
// return without sending. The bulk of validation is done earlier
20+
// in the scanning phase.
21+
return this;
22+
}
23+
24+
try {
25+
const data = await this.rollbarAPI.sigendURLsourcemaps(this.buildRequest());
26+
27+
this.data = data;
28+
if (data && data['err'] === 0) {
29+
output.success('', 'Requested for signed URL successfully');
30+
} else {
31+
output.error('Error', data.message);
32+
}
33+
34+
} catch (e) {
35+
output.error('Error', e.message);
36+
}
37+
}
38+
39+
buildRequest() {
40+
return {
41+
version: this.version,
42+
baseUrl: this.baseUrl,
43+
};
44+
}
45+
46+
setProjectID() {
47+
this.projectID = this.data['result']['project_id'];
48+
}
49+
50+
createManifestData() {
51+
const data = {
52+
projectID: this.projectID,
53+
version: this.version,
54+
baseUrl: this.baseUrl,
55+
};
56+
57+
const strData = JSON.stringify(data, null, 2);
58+
this.manifestData = strData;
59+
}
60+
}
61+
62+
module.exports = Requester;

src/sourcemaps/scanner.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ class Scanner {
4343
output.status('', mapPath);
4444
file.mapPathName = mapPath;
4545
file.sourceMappingURL = true;
46+
file.mappedFile = path.join(this.targetPath, mapPath);
47+
4648
} else {
4749
output.warn('', 'map not found');
4850
}

src/sourcemaps/signed-url-uploader.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
'use strict';
2+
3+
const AdmZip = require('adm-zip');
4+
const axios = require('axios');
5+
const zipFile = new AdmZip();
6+
7+
class SignedUrlUploader {
8+
constructor(requester) {
9+
this.zippedMapFile = '';
10+
this.files = [];
11+
this.requester = requester;
12+
this.zipBuffer = Buffer;
13+
14+
}
15+
16+
mapFiles(files) {
17+
this.files = files;
18+
19+
return this;
20+
}
21+
22+
zipFiles() {
23+
24+
try {
25+
zipFile.addFile('manifest.json', this.requester.manifestData);
26+
} catch (e) {
27+
output.status('Error', e.message);
28+
}
29+
for (const file of this.files) {
30+
try {
31+
if (file.validated) {
32+
zipFile.addLocalFile(file.mappedFile);
33+
}
34+
} catch(e) {
35+
output.status('Error', e.message);
36+
}
37+
}
38+
try {
39+
this.zipBuffer = zipFile.toBuffer();
40+
if (this.zipBuffer.length != 0) {
41+
output.success('', 'Zipped all the source map files successfully');
42+
} else {
43+
output.error('', 'Zip was unsuccessful');
44+
}
45+
} catch(e) {
46+
output.status('Error', e.message);
47+
}
48+
}
49+
50+
async upload(dryRun, files, signedUrl) {
51+
if (dryRun) {
52+
// TODO: Maybe more can be done here, but the important part is just to
53+
// return without sending. The bulk of validation is done earlier
54+
// in the scanning phase.
55+
return this.files;
56+
}
57+
58+
try {
59+
this.mapFiles(files);
60+
this.zipFiles();
61+
62+
const resp = await axios.put(signedUrl, this.zipBuffer, {
63+
headers: {
64+
'Content-Type': 'application/octet-stream',
65+
},
66+
});
67+
if (resp.status === 200) {
68+
output.status('Success', 'Uploaded zip file successfully');
69+
} else {
70+
output.status('Error', 'Could not upload the zip file');
71+
}
72+
73+
} catch (e) {
74+
output.status('Error', e.message);
75+
}
76+
}
77+
}
78+
79+
module.exports = SignedUrlUploader;

test/common/rollbar-api.test.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,41 @@ describe('.sourcemaps()', function() {
3535
this.currentTest.stub.restore();
3636
});
3737

38+
it('should send well formed request for signed URL', async function() {
39+
const rollbarAPI = this.test.rollbarAPI;
40+
const stub = this.test.stub;
41+
42+
stub.resolves({
43+
status: 200,
44+
statusText: 'Success',
45+
data: { err: 0, result: { uuid: 'd4c7acef55bf4c9ea95e4fe9428a8287'}}
46+
});
47+
48+
const request = {
49+
version: '123',
50+
prefix_url: 'https://example.com/',
51+
source_map: '{ \
52+
"version" : 3, \
53+
"file": "out.js", \
54+
"sourceRoot": "", \
55+
"sources": ["foo.js", "bar.js"], \
56+
"sourcesContent": [null, null], \
57+
"names": ["src", "maps", "are", "fun"], \
58+
"mappings": "A,AAAB;;ABCDE;" \
59+
}',
60+
sources: []
61+
};
62+
63+
const response = await rollbarAPI.sigendURLsourcemaps(request);
64+
65+
expect(response).to.be.not.null;
66+
expect(stub.calledOnce).to.be.true;
67+
68+
const body = stub.getCall(0).args;
69+
expect(body[0]).to.equal('/signed_url/sourcemap_bundle');
70+
expect(body[1]).to.be.a('Object');
71+
});
72+
3873
it('should send well formed request', async function() {
3974
const rollbarAPI = this.test.rollbarAPI;
4075
const stub = this.test.stub;

test/sourcemaps.test.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,18 @@ describe('rollbar-cli upload-sourcemaps', function() {
2828
expect(lines[i]).to.have.string('[Found ]');
2929
}
3030
});
31+
32+
it('uploads react project via signed URL', function() {
33+
this.timeout(5000);
34+
35+
const stdout = execSync('./bin/rollbar upload-sourcemaps ./test/fixtures/builds/react16/build --access-token 1234 --url-prefix "http://localhost:3000/" --code-version react16 -D --signed-url');
36+
37+
const lines = stdout.toString().split('\n');
38+
39+
expect(lines.length).to.equal(14);
40+
41+
for(let i; i < lines.length; i+=2) {
42+
expect(lines[i]).to.have.string('[Found ]');
43+
}
44+
});
3145
});

0 commit comments

Comments
 (0)