1 | 'use strict';
|
2 |
|
3 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
|
4 |
|
5 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
|
6 |
|
7 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
8 |
|
9 | var http = require('http');
|
10 | var https = require('https');
|
11 | var utils = require('./utils');
|
12 | var Environment = require('./environment');
|
13 | var UserAgent = require('./user-agent');
|
14 | var retry = require('bluebird-retry');
|
15 | var requestPromise = require('request-promise');
|
16 | var PromisePool = require('es6-promise-pool');
|
17 | var regeneratorRuntime = require('regenerator-runtime');
|
18 | var fs = require('fs');
|
19 |
|
20 | var JSON_API_CONTENT_TYPE = 'application/vnd.api+json';
|
21 | var CONCURRENCY = 2;
|
22 |
|
23 | var Resource = function () {
|
24 | function Resource(options) {
|
25 | _classCallCheck(this, Resource);
|
26 |
|
27 | if (!options.resourceUrl) {
|
28 | throw new Error('"resourceUrl" arg is required to create a Resource.');
|
29 | }
|
30 | if (!options.sha && !options.content) {
|
31 | throw new Error('Either "sha" or "content" is required to create a Resource.');
|
32 | }
|
33 | if (/\s/.test(options.resourceUrl)) {
|
34 | throw new Error('"resourceUrl" arg includes whitespace. It needs to be encoded.');
|
35 | }
|
36 | this.resourceUrl = options.resourceUrl;
|
37 | this.content = options.content;
|
38 | this.sha = options.sha || utils.sha256hash(options.content);
|
39 | this.mimetype = options.mimetype;
|
40 | this.isRoot = options.isRoot;
|
41 |
|
42 |
|
43 |
|
44 | this.localPath = options.localPath;
|
45 | }
|
46 |
|
47 | _createClass(Resource, [{
|
48 | key: 'serialize',
|
49 | value: function serialize() {
|
50 | return {
|
51 | type: 'resources',
|
52 | id: this.sha,
|
53 | attributes: {
|
54 | 'resource-url': this.resourceUrl,
|
55 | mimetype: this.mimetype || null,
|
56 | 'is-root': this.isRoot || null
|
57 | }
|
58 | };
|
59 | }
|
60 | }]);
|
61 |
|
62 | return Resource;
|
63 | }();
|
64 |
|
65 | var PercyClient = function () {
|
66 | function PercyClient(options) {
|
67 | _classCallCheck(this, PercyClient);
|
68 |
|
69 | options = options || {};
|
70 | this.token = options.token;
|
71 | this.apiUrl = options.apiUrl || 'https://percy.io/api/v1';
|
72 | this.environment = options.environment || new Environment(process.env);
|
73 | this._httpClient = requestPromise;
|
74 | this._httpModule = this.apiUrl.indexOf('http://') === 0 ? http : https;
|
75 |
|
76 | this._httpAgent = new this._httpModule.Agent({
|
77 | maxSockets: 5,
|
78 | keepAlive: true
|
79 | });
|
80 | this._clientInfo = options.clientInfo;
|
81 | this._environmentInfo = options.environmentInfo;
|
82 | this._sdkClientInfo = null;
|
83 | this._sdkEnvironmentInfo = null;
|
84 | }
|
85 |
|
86 | _createClass(PercyClient, [{
|
87 | key: '_headers',
|
88 | value: function _headers(headers) {
|
89 | return _extends({
|
90 | Authorization: 'Token token=' + this.token,
|
91 | 'User-Agent': new UserAgent(this).toString()
|
92 | }, headers);
|
93 | }
|
94 | }, {
|
95 | key: '_httpGet',
|
96 | value: function _httpGet(uri) {
|
97 | var requestOptions = {
|
98 | method: 'GET',
|
99 | uri: uri,
|
100 | headers: this._headers(),
|
101 | json: true,
|
102 | resolveWithFullResponse: true,
|
103 | agent: this._httpAgent
|
104 | };
|
105 |
|
106 | return retry(this._httpClient, {
|
107 | context: this,
|
108 | args: [uri, requestOptions],
|
109 | interval: 50,
|
110 | max_tries: 5,
|
111 | throw_original: true,
|
112 | predicate: function predicate(err) {
|
113 | return err.statusCode >= 500 && err.statusCode < 600;
|
114 | }
|
115 | });
|
116 | }
|
117 | }, {
|
118 | key: '_httpPost',
|
119 | value: function _httpPost(uri, data) {
|
120 | var requestOptions = {
|
121 | method: 'POST',
|
122 | uri: uri,
|
123 | body: data,
|
124 | headers: this._headers({ 'Content-Type': JSON_API_CONTENT_TYPE }),
|
125 | json: true,
|
126 | resolveWithFullResponse: true,
|
127 | agent: this._httpAgent
|
128 | };
|
129 |
|
130 | return retry(this._httpClient, {
|
131 | context: this,
|
132 | args: [uri, requestOptions],
|
133 | interval: 50,
|
134 | max_tries: 5,
|
135 | throw_original: true,
|
136 | predicate: function predicate(err) {
|
137 | return err.statusCode >= 500 && err.statusCode < 600;
|
138 | }
|
139 | });
|
140 | }
|
141 | }, {
|
142 | key: 'createBuild',
|
143 | value: function createBuild(options) {
|
144 | var parallelNonce = this.environment.parallelNonce;
|
145 | var parallelTotalShards = this.environment.parallelTotalShards;
|
146 |
|
147 |
|
148 | if (!parallelNonce || !parallelTotalShards) {
|
149 | parallelNonce = null;
|
150 | parallelTotalShards = null;
|
151 | }
|
152 |
|
153 | options = options || {};
|
154 |
|
155 | var commitData = options['commitData'] || this.environment.commitData;
|
156 |
|
157 | var data = {
|
158 | data: {
|
159 | type: 'builds',
|
160 | attributes: {
|
161 | branch: commitData.branch,
|
162 | 'target-branch': this.environment.targetBranch,
|
163 | 'target-commit-sha': this.environment.targetCommitSha,
|
164 | 'commit-sha': commitData.sha,
|
165 | 'commit-committed-at': commitData.committedAt,
|
166 | 'commit-author-name': commitData.authorName,
|
167 | 'commit-author-email': commitData.authorEmail,
|
168 | 'commit-committer-name': commitData.committerName,
|
169 | 'commit-committer-email': commitData.committerEmail,
|
170 | 'commit-message': commitData.message,
|
171 | 'pull-request-number': this.environment.pullRequestNumber,
|
172 | 'parallel-nonce': parallelNonce,
|
173 | 'parallel-total-shards': parallelTotalShards
|
174 | }
|
175 | }
|
176 | };
|
177 |
|
178 | if (options.resources) {
|
179 | data['data']['relationships'] = {
|
180 | resources: {
|
181 | data: options.resources.map(function (resource) {
|
182 | return resource.serialize();
|
183 | })
|
184 | }
|
185 | };
|
186 | }
|
187 |
|
188 | return this._httpPost(this.apiUrl + '/builds/', data);
|
189 | }
|
190 |
|
191 |
|
192 |
|
193 | }, {
|
194 | key: 'getBuild',
|
195 | value: function getBuild(buildId) {
|
196 | return this._httpGet(this.apiUrl + '/builds/' + buildId);
|
197 | }
|
198 |
|
199 |
|
200 |
|
201 | }, {
|
202 | key: 'getBuilds',
|
203 | value: function getBuilds(project, filter) {
|
204 | filter = filter || {};
|
205 | var queryString = Object.keys(filter).map(function (key) {
|
206 | return 'filter[' + key + ']=' + filter[key];
|
207 | }).join('&');
|
208 |
|
209 | if (queryString.length > 0) {
|
210 | queryString = '?' + queryString;
|
211 | }
|
212 |
|
213 | return this._httpGet(this.apiUrl + '/projects/' + project + '/builds' + queryString);
|
214 | }
|
215 | }, {
|
216 | key: 'makeResource',
|
217 | value: function makeResource(options) {
|
218 | return new Resource(options);
|
219 | }
|
220 |
|
221 |
|
222 |
|
223 | }, {
|
224 | key: 'gatherBuildResources',
|
225 | value: function gatherBuildResources(rootDir, options) {
|
226 | return utils.gatherBuildResources(this, rootDir, options);
|
227 | }
|
228 | }, {
|
229 | key: 'uploadResource',
|
230 | value: function uploadResource(buildId, content) {
|
231 | var sha = utils.sha256hash(content);
|
232 | var data = {
|
233 | data: {
|
234 | type: 'resources',
|
235 | id: sha,
|
236 | attributes: {
|
237 | 'base64-content': utils.base64encode(content)
|
238 | }
|
239 | }
|
240 | };
|
241 |
|
242 | return this._httpPost(this.apiUrl + '/builds/' + buildId + '/resources/', data);
|
243 | }
|
244 | }, {
|
245 | key: 'uploadResources',
|
246 | value: function uploadResources(buildId, resources) {
|
247 | var _marked = regeneratorRuntime.mark(generatePromises);
|
248 |
|
249 | var _this = this;
|
250 | function generatePromises() {
|
251 | var _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, resource, content;
|
252 |
|
253 | return regeneratorRuntime.wrap(function generatePromises$(_context) {
|
254 | while (1) {
|
255 | switch (_context.prev = _context.next) {
|
256 | case 0:
|
257 | _iteratorNormalCompletion = true;
|
258 | _didIteratorError = false;
|
259 | _iteratorError = undefined;
|
260 | _context.prev = 3;
|
261 | _iterator = resources[Symbol.iterator]();
|
262 |
|
263 | case 5:
|
264 | if (_iteratorNormalCompletion = (_step = _iterator.next()).done) {
|
265 | _context.next = 13;
|
266 | break;
|
267 | }
|
268 |
|
269 | resource = _step.value;
|
270 | content = resource.localPath ? fs.readFileSync(resource.localPath) : resource.content;
|
271 | _context.next = 10;
|
272 | return _this.uploadResource(buildId, content);
|
273 |
|
274 | case 10:
|
275 | _iteratorNormalCompletion = true;
|
276 | _context.next = 5;
|
277 | break;
|
278 |
|
279 | case 13:
|
280 | _context.next = 19;
|
281 | break;
|
282 |
|
283 | case 15:
|
284 | _context.prev = 15;
|
285 | _context.t0 = _context['catch'](3);
|
286 | _didIteratorError = true;
|
287 | _iteratorError = _context.t0;
|
288 |
|
289 | case 19:
|
290 | _context.prev = 19;
|
291 | _context.prev = 20;
|
292 |
|
293 | if (!_iteratorNormalCompletion && _iterator.return) {
|
294 | _iterator.return();
|
295 | }
|
296 |
|
297 | case 22:
|
298 | _context.prev = 22;
|
299 |
|
300 | if (!_didIteratorError) {
|
301 | _context.next = 25;
|
302 | break;
|
303 | }
|
304 |
|
305 | throw _iteratorError;
|
306 |
|
307 | case 25:
|
308 | return _context.finish(22);
|
309 |
|
310 | case 26:
|
311 | return _context.finish(19);
|
312 |
|
313 | case 27:
|
314 | case 'end':
|
315 | return _context.stop();
|
316 | }
|
317 | }
|
318 | }, _marked, this, [[3, 15, 19, 27], [20,, 22, 26]]);
|
319 | }
|
320 |
|
321 | var pool = new PromisePool(generatePromises(), CONCURRENCY);
|
322 | return pool.start();
|
323 | }
|
324 | }, {
|
325 | key: 'uploadMissingResources',
|
326 | value: function uploadMissingResources(buildId, response, resources) {
|
327 | var missingResourceShas = utils.getMissingResources(response);
|
328 | if (!missingResourceShas.length) {
|
329 | return Promise.resolve();
|
330 | }
|
331 |
|
332 | var resourcesBySha = resources.reduce(function (map, resource) {
|
333 | map[resource.sha] = resource;
|
334 | return map;
|
335 | }, {});
|
336 | var missingResources = missingResourceShas.map(function (resource) {
|
337 | return resourcesBySha[resource.id];
|
338 | });
|
339 |
|
340 | return this.uploadResources(buildId, missingResources);
|
341 | }
|
342 | }, {
|
343 | key: 'createSnapshot',
|
344 | value: function createSnapshot(buildId, resources, options) {
|
345 | options = options || {};
|
346 | resources = resources || [];
|
347 |
|
348 | var data = {
|
349 | data: {
|
350 | type: 'snapshots',
|
351 | attributes: {
|
352 | name: options.name || null,
|
353 | 'enable-javascript': options.enableJavaScript || null,
|
354 | widths: options.widths || null,
|
355 | 'minimum-height': options.minimumHeight || null
|
356 | },
|
357 | relationships: {
|
358 | resources: {
|
359 | data: resources.map(function (resource) {
|
360 | return resource.serialize();
|
361 | })
|
362 | }
|
363 | }
|
364 | }
|
365 | };
|
366 |
|
367 | this._sdkClientInfo = options.clientInfo;
|
368 | this._sdkEnvironmentInfo = options.environmentInfo;
|
369 | return this._httpPost(this.apiUrl + '/builds/' + buildId + '/snapshots/', data);
|
370 | }
|
371 | }, {
|
372 | key: 'finalizeSnapshot',
|
373 | value: function finalizeSnapshot(snapshotId) {
|
374 | return this._httpPost(this.apiUrl + '/snapshots/' + snapshotId + '/finalize', {});
|
375 | }
|
376 | }, {
|
377 | key: 'finalizeBuild',
|
378 | value: function finalizeBuild(buildId, options) {
|
379 | options = options || {};
|
380 | var allShards = options.allShards || false;
|
381 | var query = allShards ? '?all-shards=true' : '';
|
382 | return this._httpPost(this.apiUrl + '/builds/' + buildId + '/finalize' + query, {});
|
383 | }
|
384 | }]);
|
385 |
|
386 | return PercyClient;
|
387 | }();
|
388 |
|
389 | module.exports = PercyClient; |
\ | No newline at end of file |