├── .gitignore
├── .travis.yml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── docs
└── protractor.md
├── index.js
├── package.json
├── src
└── percy-node-client.js
├── test
├── jasmine.json
├── mock-project
│ └── assets
│ │ └── styles.css
└── percy-node-client-integration.spec.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore files generated by IDE software.
2 | .idea
3 |
4 | node_modules
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "7.10.1"
4 | cache: yarn
5 | script:
6 | - npm run test
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | We'd love to accept your patches and contributions to this project. There are
4 | just a few small guidelines you need to follow.
5 |
6 | ## Contributor License Agreement
7 |
8 | Contributions to this project must be accompanied by a Contributor License
9 | Agreement. You (or your employer) retain the copyright to your contribution,
10 | this simply gives us permission to use and redistribute your contributions as
11 | part of the project. Head over to to see
12 | your current agreements on file or to sign a new one.
13 |
14 | You generally only need to submit a CLA once, so if you've already submitted one
15 | (even if it was for a different project), you probably don't need to do it
16 | again.
17 |
18 | ## Code reviews
19 |
20 | All submissions, including submissions by project members, require review. We
21 | use GitHub pull requests for this purpose. Consult
22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
23 | information on using pull requests.
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016 Perceptual Inc.
2 | Copyright 2017 Google Inc.
3 |
4 | The MIT License (MIT)
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/google/percy-node)
2 |
3 | # Important notice of archive
4 |
5 | Hi there friend. The team at Google that originally started this package is no longer using Percy and thus it will no longer be maintained. For that reason, we'll be moving this to the archive. If you'd like to take ownership of it, feel free to fork it.
6 |
7 | # percy-node
8 |
9 | This is a wrapper of [percy-js](https://github.com/percy/percy-js) that simplifies the API so it can be used for automated visual regression testing in node environments such as testing an express app, an Angular app, or AngularJS app.
10 |
11 | For more general information about Percy, visit [Percy's homepage](https://percy.io/)
12 |
13 | This is not an official Google product.
14 |
15 | ## How to use
16 |
17 | Percy-node is an installable [npm package](https://www.npmjs.com/package/percy-node).
18 |
19 | ```
20 | npm install percy-node --save-dev
21 | ```
22 |
23 | ## Feature
24 |
25 | Percy-node provides an optional feature to return Percy build results. Passing 'true' to finalizeBuild() to enable that feature. You also need a token with read access, please reach out to the Percy team for that.
26 |
27 | ```
28 | const percyNodeClient = require('percy-node');
29 | percyNodeClient.finalizeBuild(true);
30 | ```
31 |
32 | ## Motivation
33 | This package was originally created specifically to allow testing of Express AngularJS apps tested with Jasmine, Karma, and Protractor. However, it is written in a general enough way that it could be used in another node based testing environment.
34 |
35 |
36 | ## How to use with Protractor
37 | See the [Protractor guide](/docs/protractor.md)
38 |
39 | ## Contributing
40 | See [CONTRIBUTING.md](/CONTRIBUTING.md)
41 |
--------------------------------------------------------------------------------
/docs/protractor.md:
--------------------------------------------------------------------------------
1 | # How to use percy-node with Protractor
2 |
3 | This guide details how to use `percy-node` with [Protractor](http://www.protractortest.org/#/) which is ideal for end to end tests for AngularJS apps or Angular (2+) apps.
4 |
5 | **Note:** We haven't actually verified this works with Angular2+ yet so if you are able to get it working, please let us know. We welcome contributions to this documentation.
6 |
7 | - [Setup](#setup)
8 | - [Configuration](#config)
9 | - [Directories and Breakpoints](#constants)
10 | - [onPrepare](#onprepare)
11 | - [onComplete](#oncomplete)
12 | - [Test helpers](#helpers)
13 | - [Test specs](#specs)
14 |
15 | ## Setup
16 |
17 | Install the percy-node package:
18 |
19 | ```
20 | npm install percy-node --save-dev
21 | ```
22 |
23 | Setup your environment variables `PERCY_TOKEN` and `PERCY_PROJECT` per the [instructions on the Percy documentation](https://percy.io/docs).
24 |
25 | ## Configuration
26 |
27 | We assume you've already successfully gotten protractor to launch your server and execute some tests against your app. If you haven't gotten this far yet, please read the [Protractor tutorial](http://www.protractortest.org/#/tutorial)
28 |
29 | ### Directories and Breakpoints
30 |
31 | At the top of your protractor configuration file (e.g. `conf.js`), declare the constants `ASSET_DIRS`, `PATHS_TO_REPLACE`, and `BREAKPOINT_WIDTH`.
32 |
33 | `conf.js`
34 | ```javascript
35 | const ASSET_DIRS = [
36 | 'mysite/assets/**',
37 | 'mysite/css/**'
38 | ];
39 |
40 | const PATHS_TO_REPLACE = [
41 | process.cwd() + '/mysite/',
42 | ];
43 |
44 | const BREAKPOINT_WIDTH = {
45 | 'x-small': 320,
46 | small: 600,
47 | medium: 1024,
48 | large: 1440,
49 | };
50 |
51 | exports.config = {
52 | ...
53 | ```
54 |
55 | #### `ASSET_DIRS`
56 |
57 | In this constant, define paths for your static assets such as images, compiled
58 | css, and optionally your compiled js. These paths should be relative starting
59 | from the directory where protractor will be executed (typically starting from
60 | the root of your project). It supports globs.
61 |
62 | `percy-node` will create a manifest of all of these assets, create hashes from
63 | the file contents, and send those hashes to the percy server. The percy service
64 | will respond with a list of asset hashes it does not yet have cached. Then
65 | `percy-node` will upload those assets to the percy service before beginning
66 | any of the test specs. A list of uploaded assets will be displayed in the
67 | console.
68 |
69 |
70 | #### `PATHS_TO_REPLACE`
71 |
72 | These are path partials to be removed from the `ASSET_DIRS` paths to create the
73 | urls. For example if you have `/root/path/to/project/mysite/assets/ponies.jpg`
74 | and it's accessible on your webserver at `localhost:8000/assets/ponies.jpg`,
75 | then you'll want to remove everything up to and including `/mysite/`.
76 |
77 | #### `BREAKPOINT_WIDTH`
78 |
79 | This is an object where the key is the name of your breakpoint and the value is
80 | the pixel width of the breakpoint. You will refer to these breakpoints by name
81 | in your snapshots later.
82 |
83 | ### Define `onPrepare`
84 |
85 | Again, in your protractor configuration file (e.g `conf.js`), import the
86 | `percy-node` package and add or modify your `onPrepare` method as follows.
87 |
88 | `conf.js`
89 |
90 | ```javascript
91 | const percyNodeClient = require('percy-node');
92 |
93 |
94 | exports.config = {
95 | ...
96 |
97 | // onPrepare waits for the returned promise to resolve before beginning
98 | // the tests. So make sure you either return the promise from
99 | // percyNodeClient.setup() or create your own custom promise.
100 | onPrepare: function() {
101 | // Add code to start your server here.
102 |
103 | // Return promise so Protractor wont begin until percyNodeClient setup
104 | // promise resolves.
105 | return percyNodeClient.setup(ASSET_DIRS, PATHS_TO_REPLACE,
106 | BREAKPOINT_WIDTH);
107 |
108 | },
109 | };
110 | ```
111 |
112 |
113 | Here's another example for the `onPrepare` method with a custom promise and
114 | express server.
115 |
116 | `conf.js`
117 |
118 | ```javascript
119 |
120 | const server = require('./server.js'); // Express server.
121 | const percyNodeClient = require('percy-node');
122 |
123 | ...
124 |
125 | exports.config = {
126 | ...
127 | onPrepare: function() {
128 | return new Promise((resolve) => {
129 | server.start(9000).then((url) => {
130 | browser.params.testUrl = url;
131 | percyNodeClient.setup(ASSET_DIRS, PATHS_TO_REPLACE,
132 | BREAKPOINT_WIDTH).then(resolve);
133 | });
134 | });
135 |
136 | },
137 | };
138 | ```
139 |
140 | ### Define `onComplete`
141 |
142 | Protractor will execute the `onComplete` method after all test specs have been
143 | completed.
144 |
145 | Continuing in your protractor configuration file, we'll add an `onComplete`
146 | property to the configuration to tell percy to finalize the build.
147 |
148 | `conf.js`
149 |
150 | ```javascript
151 | exports.config = {
152 | ...
153 | onComplete: function() {
154 | // Add code to stop your server here.
155 |
156 | return percyNodeClient.finalizeBuild();
157 | }
158 | };
159 | ```
160 |
161 | If you're using a node express server, here's how this might look:
162 |
163 | `conf.js`
164 |
165 | ```javascript
166 |
167 | const server = require('./server.js'); // Express server.
168 |
169 | ...
170 |
171 | exports.config = {
172 | ...
173 | onComplete: function() {
174 | server.stop();
175 | return percyNodeClient.finalizeBuild();
176 | }
177 | };
178 | ```
179 |
180 | ## Create snapshot helper
181 |
182 | After completing the configurations above, you'll need to define a helper method
183 | to reuse in your test specs. Here's one you can start with but feel free to
184 | customize it to your needs. This one grabs the entire html document to send to
185 | percy.
186 |
187 | `percy-helpers.js`
188 |
189 | ```javascript
190 |
191 | /**
192 | * Takes a snapshot of the current dom and passes it to percy to be processed.
193 | *
194 | * @param {string} name The unique name to give this snapshot which appears in
195 | * the percy UI.
196 | * @param {Array} breakpoints The names of which breakpoints you want
197 | * percy to take snapshots in.
198 | * @param {*} browser Reference to selenium webdriver browser.
199 | * @param {!Function} done The jasmine callback to call when the snapshot
200 | * process completes.
201 | * @param {string=} opt_selector A jquery like selector to use.
202 | * Uses 'html' by default.
203 | */
204 | function snapshot(name, breakpoints, browser, done, opt_selector = 'html') {
205 | browser.executeScript('return arguments[0].outerHTML;',
206 | $(opt_selector)).then((content) => {
207 | percyNodeClient.snapshot(name, content, breakpoints);
208 | done();
209 | });
210 | }
211 |
212 | module.exports = {snapshot};
213 | ```
214 |
215 |
216 | ## Add test specs
217 |
218 | Now you can write your jasmine test specs. With this example, you can only have one snapshot per `it()` block unless you setup a promise pool or promise chain.
219 |
220 | `homepage.spec.js`
221 |
222 | ```javascript
223 | const percy = require('../path/to/percy-helpers');
224 |
225 | describe(function('Homepage') {
226 | // Note that the function is accepting a `done` parameter.
227 | it('should look awesome-o', (done) => {
228 | // Navigate to the page you want snapshots of.
229 | browser.get('http://localhost:9000/home');
230 |
231 | percy.snapshot('homepage',
232 | ['small', 'medium'], browser, done);
233 | });
234 | });
235 | ```
236 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc.
3 | *
4 | * Use of this source code is governed by a MIT-style
5 | * license that can be found in the LICENSE file or at
6 | * https://opensource.org/licenses/MIT.
7 | */
8 |
9 | const percyNodeClient = require('./src/percy-node-client');
10 |
11 | module.exports = percyNodeClient;
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "percy-node",
3 | "version": "0.2.1",
4 | "description": "A node based javascript client for testing apps with node and percy. https://percy.io/",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "jasmine JASMINE_CONFIG_PATH=test/jasmine.json"
8 | },
9 | "repository": "https://github.com/google/percy-node.git",
10 | "keywords": [
11 | "percy",
12 | "percy.io",
13 | "node",
14 | "protractor",
15 | "angularjs",
16 | "angular",
17 | "visual regression testing"
18 | ],
19 | "engines": {
20 | "node": ">=7.8"
21 | },
22 | "author": "Susie Sahim ",
23 | "license": "MIT",
24 | "dependencies": {
25 | "crypto": "0.0.3",
26 | "es6-promise-pool": "^2.4.6",
27 | "globby": "^6.1.0",
28 | "percy-client": "^2.6.0",
29 | "walk": "^2.3.9"
30 | },
31 | "devDependencies": {
32 | "jasmine": "^2.7.0",
33 | "nock": "^9.0.14"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/percy-node-client.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc.
3 | *
4 | * Use of this source code is governed by a MIT-style
5 | * license that can be found in the LICENSE file or at
6 | * https://opensource.org/licenses/MIT.
7 | *
8 | *
9 | * @fileoverview Node utilities for working with the percy client.
10 | * https://github.com/percy/percy-js
11 | *
12 | * Based on the ember percy utilities:
13 | * https://github.com/percy/ember-percy/blob/fe475031437fb7da33edb256d812164dfbddc94b/index.js
14 | *
15 | * Example usage:
16 | * Configure protractor configuration as follows:
17 | *
18 | * protractor.karma.conf.js
19 | * const percyNodeClient = require('./percy-node-client.js');
20 | *
21 | * exports.config = {
22 | * ...
23 | * onPrepare: function() {
24 | * // You may have to write a custom promise to combine this with a
25 | * // server setup step. See below for param documentation.
26 | * return percyUtil.setup(...);
27 | * },
28 | * onComplete: function() {
29 | * return percyUtil.finalizeBuild();
30 | * },
31 | * };
32 | *
33 | *
34 | * Overall flow of how to use these utilities and what they do.
35 | *
36 | * setup()
37 | * Do this once before tests begin in the protractor `onPrepare` phase.
38 | *
39 | * snapshot()
40 | * In each spec, call snapshot() which will create a snapshot of the html in
41 | * the page and pass it to percyClient.
42 | *
43 | * finalizeBuild()
44 | * Runs in protractor onComplete phase after all test specs are finished by
45 | * sending all of the snapshots together to percy api for processing.
46 | *
47 | *
48 | * Note: This client does not return any sort of status as to whether the
49 | * snapshots match or not. You have to check the percy dashboard to see if
50 | * snapshots differ.
51 | *
52 | * Here's a gist showing how you can check if diffs pass and return error exit
53 | * code if it fails. But this API is not advertised by Percy publicly so
54 | * support for it is limited.
55 | * https://gist.github.com/rdy/cb67b9c5403817b7dea3efcfc67429c7
56 | */
57 |
58 | const crypto = require('crypto');
59 | const walk = require('walk');
60 | const fs = require('fs');
61 | const PercyClient = require('percy-client');
62 | const PromisePool = require('es6-promise-pool');
63 | const Environment = require('percy-client/dist/environment');
64 | const globby = require('globby');
65 |
66 | const MAX_FILE_SIZE_BYTES = 15728640; // 15MB.
67 |
68 |
69 | var percyClient, percyBuildPromise;
70 | var isPercyEnabled = true;
71 | var isDebugEnabled = false;
72 |
73 |
74 | /**
75 | * An array of promises for when snapshots have finished uploading.
76 | * @type {Array}
77 | */
78 | const snapshotResourceUploadPromises = [];
79 |
80 |
81 | /**
82 | * A list of resources that percy does not yet have uploaded.
83 | * @type {Array}
84 | */
85 | const buildResourceUploadPromises = [];
86 |
87 |
88 | /**
89 | * A key value pairing where the key is the name of the breakpoint and the value
90 | * is the pixel width of the breakpoint.
91 | * @type {Object}
92 | */
93 | var registeredBreakpoints = {};
94 |
95 |
96 | /**
97 | * Maximum trys to retrieve the build infomation from the Percy server.
98 | * @const {number}
99 | */
100 | const MAX_RETRIES_WHEN_PROCESSING = 1000;
101 |
102 |
103 | /**
104 | * After app is ready, create a percy build and upload assets. Call this only
105 | * once per protractor test run (so in the karma onPrepare phase).
106 | * Call this in the protractor onPrepare() phase. It should be called only
107 | * once to setup the percy client to be shared across all specs.
108 | *
109 | * It will:
110 | * - create percy client
111 | * - create manifest of asset (css, images) and send it to percy to determine
112 | * which are missing from percy's server.
113 | * - upload missing assets to percy.
114 | *
115 | * @param {Array} buildDirs The directories where assets are stored.
116 | * @param {Array} rootDirs The directory for the root of the app.
117 | * This is used to change a local path to a url path.
118 | * @param {Object} breakpointsConfig A key value pairing where
119 | * the key is the name of the breakpoint and the value is the pixel width of
120 | * the breakpoint. E.g.
121 | * {
122 | * small: 320,
123 | * medium: 768,
124 | * large: 1024,
125 | * }
126 | * @param {boolean=} opt_isDebugEnabled If debug mode is enabled.
127 | * @return {Promise} Resolves when all assets have been uploaded to percy.
128 | */
129 | async function setup(buildDirs, rootDirs, breakpointsConfig,
130 | opt_isDebugEnabled = false) {
131 | registeredBreakpoints = breakpointsConfig;
132 | isDebugEnabled = opt_isDebugEnabled;
133 | const environment = new Environment(process.env);
134 | logger.log(`[percy] Setting up project "${process.env.PERCY_PROJECT}"`);
135 | percyClient = new PercyClient({
136 | // The token has write access only by default. Please reach out to the Percy team
137 | // for a token with read access if you want to get build results from the server.
138 | token: process.env.PERCY_TOKEN,
139 | clientInfo: process.env.PERCY_PROJECT,
140 | environment: environment,
141 | // Not sure if we actually have to populate this with anything.
142 | environmentInfo: '',
143 | });
144 | const resourceManifestDict = gatherBuildResources(
145 | percyClient, buildDirs, rootDirs);
146 |
147 | // Convert resources from dict to array. Still may need dict later.
148 | const resourceManifestArr = [];
149 | Object.keys(resourceManifestDict).forEach(function(key) {
150 | resourceManifestArr.push(resourceManifestDict[key]);
151 | });
152 |
153 | logDebug('Resource manifest', resourceManifestArr);
154 |
155 | // Initialize the percy client and a new build.
156 | // environment.repo is defined by the PERCY_PROJECT environment variable.
157 | let repo = environment.repo;
158 | // This tells the percy server about all of our build assets. The promise
159 | // returns a build response which notifies us if any of the assets are not
160 | // yet uploaded to the server.
161 | // Return a promise and only resolve when all build resources are uploaded,
162 | // which ensures that the output build dir is still available to be read from
163 | // before deleted.
164 | try {
165 | // Assign to module variable so we can chain off of it elsewhere.
166 | percyBuildPromise = percyClient.createBuild(repo,
167 | {resources: resourceManifestArr});
168 | const buildResponse = await percyBuildPromise;
169 |
170 | // Here we process the response to check if percy says it's missing
171 | // any of the assets. For the ones it's missing, we upload them.
172 | var percyBuildData = buildResponse.body.data;
173 | //console.log('PERCY BUILD RESPONSE', buildResponse.body);
174 | logger.log('\n[percy] Build created:',
175 | percyBuildData.attributes['web-url']);
176 |
177 | // Upload all missing build resources.
178 | var missingResources = parseMissingResources(buildResponse);
179 | logDebug('Missing resources', missingResources);
180 | if (missingResources && missingResources.length > 0) {
181 | await uploadMissingResources(percyBuildData.id, missingResources,
182 | resourceManifestDict);
183 | }
184 | } catch (err) {
185 | handlePercyFailure(err);
186 | }
187 | }
188 |
189 |
190 | /**
191 | * Creates a dom snapshot and adds it to the percy client.
192 | * It will:
193 | * - create config for snapshot widths.
194 | * - call percySnapshot() passing widths and html content.
195 | * - trigger uploading snapshots to percy if it doesn't already have them.
196 | *
197 | * @param {string} name The name to use for this snapshot.
198 | * E.g. 'carousel-simple'
199 | * @param {string} content The html content as a string to take a snapshot of.
200 | * @param {Array=} opt_breakpoints A list of breakpoint names.
201 | * @param {boolean=} opt_enableJs Whether or not to enable javascript.
202 | */
203 | function snapshot(name, content, opt_breakpoints, opt_enableJs) {
204 | const enableJs = opt_enableJs || false;
205 | const defaultBreakpointNames = Object.keys(
206 | registeredBreakpoints);
207 | // Transform the `breakpoints` array of named breakpoints into an array of
208 | // integer widths, mapped by the breakpoints config.
209 | /** @type {Array} */
210 | var breakpointNamesList = opt_breakpoints || defaultBreakpointNames;
211 |
212 | const widths = getWidthsFromBreakpointNames(breakpointNamesList);
213 |
214 | // Add a new promise to the list of resource uploads so that finalize_build
215 | // can wait on resource uploads. We MUST do this immediately here with a
216 | // custom promise, not wait for the nested `uploadResource()` promise below,
217 | // to avoid creating a race condition where the uploads array may be missing
218 | // some possible upload promises.
219 | //
220 | // Nasty way to get a reference to the `resolve` method so that we can
221 | // manually resolve this promise below.
222 | // http://stackoverflow.com/a/26150465/128597
223 | var resolveAfterHtmlResourceUploaded;
224 | var htmlResourceUploadedPromise = new Promise(function(resolve) {
225 | resolveAfterHtmlResourceUploaded = resolve;
226 | });
227 | snapshotResourceUploadPromises.push(htmlResourceUploadedPromise);
228 |
229 | percyBuildPromise.then(function(buildResponse) {
230 | const percyBuildData = buildResponse.body.data;
231 |
232 | // Construct the root resource and create the snapshot.
233 | var htmlResource = percyClient.makeResource({
234 | resourceUrl: '/',
235 | content: content,
236 | isRoot: true,
237 | mimetype: 'text/html',
238 | });
239 |
240 | var snapshotPromise = percyClient.createSnapshot(
241 | percyBuildData.id,
242 | [htmlResource],
243 | {
244 | name: name,
245 | widths: widths,
246 | enableJavaScript: enableJs,
247 | });
248 |
249 | // Upload missing resources (just the root resource HTML in this case).
250 | snapshotPromise.then((response) => {
251 | var snapshotId = response.body.data.id;
252 |
253 | var missingResources = parseMissingResources(response);
254 | logDebug('Missing snapshot resources', missingResources);
255 | uploadHtml(percyBuildData.id, snapshotId, htmlResource, missingResources,
256 | resolveAfterHtmlResourceUploaded);
257 | }, (error) => {
258 | // TODO: Exit with error exit code? May not want to silently let this
259 | // pass. Need to discuss as a team our strategy for handling failures with
260 | // percy and determine how reliable percy is.
261 | if (error.statusCode && error.statusCode == 400) {
262 | console.warn(
263 | '[percy][WARNING] Bad request error, skipping snapshot: ' + name
264 | );
265 | console.warn(error.toString());
266 | // Skip this snapshot, resolve on error to unblock the finalization
267 | // promise chain.
268 | resolveAfterHtmlResourceUploaded();
269 | } else {
270 | handlePercyFailure(error);
271 | }
272 | });
273 | });
274 | }
275 |
276 |
277 | /**
278 | * Sent another request to the Percy server if the number of tries does not exceed the limit.
279 | * @param {string} buildId Percy Build ID.
280 | * @param {number} numRetries The number of get build requests to the server.
281 | * @param {Function} resolve Promise resolve function.
282 | */
283 | function retry(buildId, numRetries, resolve) {
284 | if (numRetries < MAX_RETRIES_WHEN_PROCESSING) {
285 | // Retry with recursion with retries incremented
286 | return setTimeout(checkBuildStatus, 1000, buildId, numRetries + 1, resolve);
287 | } else {
288 | handleError('Retries exceeded. Exiting.');
289 | }
290 | }
291 |
292 |
293 | /**
294 | * Retrieve the build information from the Percy server, send another request to the server
295 | * if the build state is processing or pending. Once the build is finished, check for diffs and
296 | * display errors if there are diffs.
297 | * @param {string} buildId Percy Build ID.
298 | * @param {number} numRetries The number of get build requests to the server.
299 | * @param {Function} resolve Promise resolve function.
300 | */
301 | async function checkBuildStatus(buildId, numRetries, resolve) {
302 | const response = await percyClient.getBuild(buildId);
303 | const {body: {data: {attributes}}} = response;
304 | const {state} = attributes;
305 | if (state == 'processing' || state == 'pending') {
306 | retry(buildId, numRetries, resolve);
307 | } else if (state == 'finished'){
308 | // Unreviewed diffs are the diffs which have not been approved in the percy UI
309 | const totalUnreviewed = attributes['total-snapshots-unreviewed'];
310 | if (totalUnreviewed) {
311 | const url = attributes['web-url'];
312 | handleError('percy', `unreviewed diffs found: ${totalUnreviewed}. Check ${url}`);
313 | } else {
314 | logger.log('Hooray! The build is successful with no unreviewed diffs. \\o/');
315 | }
316 | resolve();
317 | } else if (state == 'failed') {
318 | handleError('percy', `build failed: ${attributes['failure-reason']}`);
319 | resolve();
320 | }
321 | }
322 |
323 |
324 | /**
325 | * Return a promise that gets build information.
326 | * @param {string} buildId Percy Build ID.
327 | * @return {Promise} Promise object gets resolved when build is finished.
328 | */
329 | function getBuildPromise(buildId) {
330 | return new Promise(function(resolve, reject) {
331 | checkBuildStatus(buildId, 0, resolve);
332 | });
333 | }
334 |
335 |
336 | /**
337 | * Finalizes the request to be sent to Percy api which includes all the assets,
338 | * snapshots, etc.
339 | * Return this in karma onComplete() phase after all test specs have been run.
340 | * @param {boolean} getDiffs Set to true to request the build results from the server
341 | * after the diffs are generated. This feature will slow down the build process. To use
342 | * this feature, please reach out to the Percy team to get a token with read access.
343 | * @return {Promise}
344 | */
345 | async function finalizeBuild(getDiffs = false) {
346 | logger.log('[percy] Finalizing build...');
347 |
348 | try {
349 | // These promises need to be processed sequentially, not concurrently.
350 | const {body: {data: percyBuildData}} = await percyBuildPromise;
351 | // We also need to wait until all snapshot resources have been uploaded.
352 | // We do NOT need to wait until the snapshot itself has been finalized, just
353 | // until resources are uploaded.
354 | await Promise.all(snapshotResourceUploadPromises);
355 | // Finalize the build.
356 | await percyClient.finalizeBuild(percyBuildData.id);
357 | // Avoid trying to add snapshots to an already-finalized build. This might
358 | // happen when running tests locally and the browser gets refreshed after
359 | // the end of a test run. Generally, this is not a problem because tests
360 | // only run in CI and only once.
361 | isPercyEnabled = false;
362 |
363 | // Attempt to make our logging come last, giving time for test output to finish.
364 | var url = percyBuildData.attributes['web-url'];
365 | process.nextTick(function() {
366 | logger.log('[percy] Visual diffs are now processing:', url);
367 | });
368 |
369 | if (getDiffs) {
370 | await getBuildPromise(percyBuildData.id);
371 | }
372 |
373 | } catch (err) {
374 | handlePercyFailure(err);
375 | }
376 | }
377 |
378 |
379 | /**
380 | * Reads the filesystem for assets and assembles an object to be handed to percy
381 | * so it can upload the assets.
382 | * Synchronously walk the build directory, read each file and calculate its
383 | * SHA 256 hash, and create a mapping of hashes to Resource objects.
384 | * @param {PercyClient} percyClient
385 | * @param {Array} buildDirs the directory to look in for assets.
386 | * @param {Array} rootDirs The directory for the root of the app.
387 | * This is used to change a local path to a url path.
388 | * @return {Object}
389 | * {@see https://github.com/percy/percy-js/blob/master/src/main.js#L9}
390 | */
391 | function gatherBuildResources(percyClient, buildDirs, rootDirs) {
392 | const hashToResource = {};
393 | const paths = globby.sync(buildDirs, {absolute: true, nodir: true});
394 |
395 | let absolutePath, resourceUrl, content, sha;
396 | for (let i = 0; i < paths.length; i++) {
397 | absolutePath = paths[i];
398 | resourceUrl = absolutePath;
399 | rootDirs.forEach((rootDir) => {
400 | resourceUrl = resourceUrl.replace(rootDir, '');
401 | });
402 | if (resourceUrl.charAt(0) !== '/') resourceUrl = '/' + resourceUrl;
403 | // Skip large files.
404 | if (fs.statSync(absolutePath)['size'] > MAX_FILE_SIZE_BYTES) {
405 | console.warn('\n[percy][WARNING] Skipping large build resource: ',
406 | resourceUrl);
407 | continue;
408 | }
409 |
410 | content = fs.readFileSync(absolutePath);
411 | sha = crypto.createHash('sha256').update(content).digest('hex');
412 |
413 | hashToResource[sha] = percyClient.makeResource({
414 | resourceUrl: encodeURI(resourceUrl),
415 | sha: sha,
416 | localPath: absolutePath,
417 | });
418 | }
419 |
420 | return hashToResource;
421 | }
422 |
423 |
424 | /**
425 | * Uploads to percy any assets (such as css, images, js, etc) that it doesn't
426 | * already have cached on its servers.
427 | * @param {number} buildId
428 | * @param {Array<{id: number}>} missingResources
429 | * @param {Object} resourceManifestDict
430 | */
431 | async function uploadMissingResources(
432 | buildId, missingResources, resourceManifestDict) {
433 | var missingResourcesIndex = 0;
434 | var promiseGenerator = function() {
435 | var missingResource = missingResources[missingResourcesIndex];
436 | missingResourcesIndex++;
437 |
438 | if (missingResource) {
439 | var resource = resourceManifestDict[missingResource.id];
440 | var content = fs.readFileSync(resource.localPath);
441 |
442 | // Start the build resource upload and add it to a collection we can
443 | // block on later because build resources must be fully uploaded before
444 | // snapshots are finalized.
445 | var promise = percyClient.uploadResource(buildId, content);
446 | promise.then((response) => {
447 | logger.log(
448 | `[percy] Uploaded new build resource: ${resource.resourceUrl}`);
449 | }, handlePercyFailure);
450 | buildResourceUploadPromises.push(promise);
451 |
452 | return promise;
453 | } else {
454 | // Trigger the pool to end.
455 | return null;
456 | }
457 | };
458 |
459 | // We do this in a promise pool for two reasons: 1) to limit the number of
460 | // files that are held in memory concurrently, and 2) without a pool, all
461 | // upload promises are created at the same time and request-promise timeout
462 | // settings begin immediately, which timeboxes ALL uploads to finish within
463 | // one timeout period. With a pool, we defer creation of the upload promises,
464 | // which makes timeouts apply more individually.
465 | var concurrency = 2;
466 | var pool = new PromisePool(promiseGenerator, concurrency);
467 |
468 | // Wait for all build resource uploads before we allow the addon build step to
469 | // complete. If an upload failed, resolve anyway to unblock the building
470 | // process.
471 | await pool.start();
472 | }
473 |
474 |
475 | /**
476 | * Uploads the html snapshot to percy if it doesn't already have it stored.
477 | * @param {number} buildId
478 | * @param {number} snapshotId
479 | * @param {PercyClient.Resource} htmlResource
480 | * @param {Array<{id: number}>} missingResources
481 | * @param {Function} callback
482 | *
483 | */
484 | function uploadHtml(buildId, snapshotId, htmlResource, missingResources,
485 | callback) {
486 | if (missingResources.length > 0) {
487 | // We assume there is only one missing resource here and it is the root
488 | // resource. All other resources should be build resources.
489 | percyClient.uploadResource(buildId, htmlResource.content)
490 | .then(function() {
491 | callback();
492 |
493 | // After we're sure all build resources are uploaded, finalize the
494 | // snapshot.
495 | Promise.all(buildResourceUploadPromises).then(function() {
496 | logDebug('Snapshot id', snapshotId);
497 | percyClient.finalizeSnapshot(snapshotId);
498 | });
499 | });
500 | } else {
501 | // No missing resources, we can immediately finalize the snapshot after
502 | // build resources.
503 | Promise.all(buildResourceUploadPromises).then(function() {
504 | percyClient.finalizeSnapshot(snapshotId);
505 | });
506 |
507 | // No resources to upload, so resolve immediately.
508 | callback();
509 | }
510 | }
511 |
512 |
513 | /**
514 | * Takes a list of breakpoint names and gets the corresponding widths from
515 | * the registered breakpoints.
516 | * @param {Array} breakpointNamesList
517 | * @return {Array}
518 | */
519 | function getWidthsFromBreakpointNames(breakpointNamesList) {
520 | const widths = [];
521 | for (var i in breakpointNamesList) {
522 | if (breakpointNamesList.hasOwnProperty(i)) {
523 | var breakpointName = breakpointNamesList[i];
524 | var breakpointWidth =
525 | registeredBreakpoints[breakpointName];
526 |
527 | if (!parseInt(breakpointWidth)) {
528 | console.error(`[percy] Breakpoint name "${breakpointName}"
529 | is not defined in Percy config.`);
530 | }
531 | // Avoid duplicate widths.
532 | if (widths.indexOf(breakpointWidth) === -1) {
533 | widths.push(breakpointWidth);
534 | }
535 | }
536 | }
537 | return widths;
538 | }
539 |
540 |
541 | /**
542 | * Checks the percy response to see if it says it's missing any of the resources
543 | * in our registry so that they can be uploaded.
544 | * @param {Object} response
545 | * @return {Array<{id: string}>}
546 | */
547 | function parseMissingResources(response) {
548 | return response.body.data &&
549 | response.body.data.relationships &&
550 | response.body.data.relationships['missing-resources'] &&
551 | response.body.data.relationships['missing-resources'].data || [];
552 | }
553 |
554 | /**
555 | * Displays the error in the console and exits with a non-zero exit code to
556 | * trigger a failed build message in CI.
557 | * @param {string} error
558 | */
559 | function handlePercyFailure(error) {
560 | isPercyEnabled = false;
561 | console.error(
562 | `[percy][ERROR] API call failed, Percy has been disabled
563 | for this build. ${error.toString()}`);
564 | process.exit(2);
565 | }
566 |
567 | /**
568 | * Logs debug information to the console.
569 | * @param {Array} args
570 | */
571 | function logDebug(...args) {
572 | if (isDebugEnabled) {
573 | console.log('[percy] DEBUG', ...args);
574 | }
575 | }
576 |
577 |
578 | /**
579 | * Separate logging so we can more easily spy/mock logging.
580 | * @param args
581 | */
582 | const logger = {
583 | log: function(...args) {
584 | console.log(...args);
585 | },
586 | error: function(...args) {
587 | console.error(...args);
588 | }
589 | };
590 |
591 |
592 | /**
593 | * Print error message and exit.
594 | * @param args
595 | */
596 | function handleError(...args) {
597 | logger.error(...args);
598 | process.exit(2);
599 | }
600 |
601 |
602 | /** @type {Object} */
603 | module.exports = {
604 | setup, snapshot, finalizeBuild, logger
605 | };
606 |
--------------------------------------------------------------------------------
/test/jasmine.json:
--------------------------------------------------------------------------------
1 | {
2 | "spec_dir": "test",
3 | "spec_files": [
4 | "**/*spec.js"
5 | ],
6 | "helpers": [
7 | "helpers/**/*.js"
8 | ],
9 | "stopSpecOnExpectationFailure": false,
10 | "random": false
11 | }
12 |
--------------------------------------------------------------------------------
/test/mock-project/assets/styles.css:
--------------------------------------------------------------------------------
1 | .bogus {
2 | color: red;
3 | }
4 |
--------------------------------------------------------------------------------
/test/percy-node-client-integration.spec.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc.
3 | *
4 | * Use of this source code is governed by a MIT-style
5 | * license that can be found in the LICENSE file or at
6 | * https://opensource.org/licenses/MIT.
7 | *
8 | * Integration test. This test ensures the percy-node-client.js is working
9 | * properly with the percy-js package.
10 | */
11 |
12 | const path = require('path');
13 | const percyNodeClient = require(path.join(__dirname, '..', 'src',
14 | 'percy-node-client'));
15 | const nock = require('nock');
16 |
17 |
18 | describe('percyNodeClient', function() {
19 | let setupPromise;
20 | let nockRequests = {};
21 | // Change to true for helpful debugging.
22 | const enableDebugMode = false;
23 |
24 | const exitFunc = process.exit;
25 |
26 | // Directory of assets to upload to percy (e.g. images, css)
27 | const BUILD_DIRS = ['test/mock-project/assets/**'];
28 | // Paths that shouldn't be part of the final url path.
29 | // E.g.
30 | // including `/full/path/to/site/` will convert
31 | // /full/path/to/site/assets/images/foo.jpg --> /assets/images/foo.jpg
32 | const PATHS_TO_REPLACE = [
33 | process.cwd() + 'test/mock-project',
34 | ];
35 |
36 | const BREAKPOINT_CONFIG = {
37 | small: 600,
38 | large: 1440,
39 | };
40 |
41 | const API_URLS = {
42 | CREATE_BUILD: '/api/v1/projects/foo/bar/builds/',
43 | UPLOAD_RESOURCE: '/api/v1/builds/123/resources/',
44 | CREATE_SNAPSHOT: '/api/v1/builds/123/snapshots/',
45 | FINALIZE_SNAPSHOT: '/api/v1/snapshots/snapshot1/finalize',
46 | FINALIZE_BUILD: '/api/v1/builds/123/finalize',
47 | GET_BUILD: '/api/v1/builds/123',
48 | };
49 |
50 | const BUTTON_SNAPSHOT = `
51 |
52 |
53 |
54 | `;
55 | // Hash for /assets/styles.css
56 | const STYLES_ASSET_HASH = '34dcd364992c6d3620b8d9db413a0b6fc0bd536cb9911e3f434969988f216b54';
57 | const BUTTONS_HTML_HASH = '57090fe7d6aa4e8cd1abcda7f8c73f8e885a8290b0da3944b5ca13e841653ce2';
58 |
59 | // TODO: Move mock to separate file.
60 | const BUILD_RESPONSE_MOCK = {
61 | data: {
62 | id: '123', // Unique build id for this build.
63 | relationships: {
64 | 'missing-resources': {
65 | // An array of resources the percy api says it doesn't yet have.
66 | data: [
67 | {
68 | id: STYLES_ASSET_HASH
69 | },
70 | ]
71 | }
72 | },
73 | attributes: {
74 | 'web-url': 'https://percy.io/foo/bar/builds/123',
75 | }
76 | }
77 | };
78 |
79 | const SUCCESS_RESPONSE_MOCK = {success: true};
80 |
81 | const CREATE_SNAPSHOT_RESPONSE_MOCK = {
82 | data: {
83 | id: 'snapshot1',
84 | relationships: {
85 | 'missing-resources': {
86 | // An array of resources the percy api says it doesn't yet have.
87 | data: [
88 | {
89 | // Hash for buttons.
90 | id: 'TODO'
91 | },
92 | ]
93 | }
94 | },
95 | }
96 | };
97 |
98 | const BUILD_RESULTS_RESPONSE_NO_DIFF_MOCK = {
99 | data: {
100 | id: '123', // Unique build id for this build.
101 | attributes: {
102 | 'state': 'finished',
103 | 'total-comparisons-diff': 0,
104 | 'total-snapshots-unreviewed': 0,
105 | 'web-url': 'https://percy.io/foo/bar/builds/123',
106 | }
107 | }
108 | };
109 |
110 | const BUILD_RESULTS_RESPONSE_HAS_DIFF_MOCK = {
111 | data: {
112 | id: '123', // Unique build id for this build.
113 | attributes: {
114 | 'state': 'finished',
115 | 'total-comparisons-diff': 5,
116 | 'total-snapshots-unreviewed': 3,
117 | 'web-url': 'https://percy.io/foo/bar/builds/123',
118 | }
119 | }
120 | };
121 |
122 | const BUILD_RESULTS_RESPONSE_HAS_NO_UNREVIEWED_DIFF_MOCK = {
123 | data: {
124 | id: '123', // Unique build id for this build.
125 | attributes: {
126 | 'state': 'finished',
127 | 'total-comparisons-diff': 5,
128 | 'total-snapshots-unreviewed': 0,
129 | 'web-url': 'https://percy.io/foo/bar/builds/123',
130 | }
131 | }
132 | };
133 |
134 | const BUILD_RESULTS_RESPONSE_FAILED_MOCK = {
135 | data: {
136 | id: '123', // Unique build id for this build.
137 | attributes: {
138 | 'state': 'failed',
139 | 'failure-reason': 'missing_resources',
140 | 'web-url': 'https://percy.io/foo/bar/builds/123',
141 | }
142 | }
143 | };
144 |
145 | beforeEach(function() {
146 | // Mock process environment variables.
147 | process.env.PERCY_TOKEN = 'abcxyz';
148 | process.env.PERCY_PROJECT = 'foo/bar';
149 | process.env.PERCY_BRANCH = 'foo-branch';
150 |
151 | spyOn(percyNodeClient.logger, 'log');
152 | spyOn(percyNodeClient.logger, 'error');
153 | spyOn(process, 'exit');
154 | });
155 |
156 | describe('when percy is missing assets', function() {
157 | beforeEach(function() {
158 |
159 | // Mock the initial build post request.
160 | nockRequests.buildsRequest = nock('https://percy.io')
161 | // Not asserting parameters of this post request as it's handled
162 | // entirely by logic in percy-js
163 | .post(API_URLS.CREATE_BUILD)
164 | .reply(201, BUILD_RESPONSE_MOCK);
165 |
166 | nockRequests.uploadCss = nock('https://percy.io')
167 | .post(API_URLS.UPLOAD_RESOURCE, {
168 | data: {
169 | type: 'resources',
170 | id: STYLES_ASSET_HASH,
171 | attributes: {
172 | 'base64-content': 'LmJvZ3VzIHsKICAgIGNvbG9yOiByZWQ7Cn0K'
173 | }
174 | }
175 | })
176 | .reply(201, SUCCESS_RESPONSE_MOCK);
177 | nockRequests.uploadHtmlSnapshot = nock('https://percy.io')
178 | .post(API_URLS.UPLOAD_RESOURCE, {
179 | data: {
180 | id: BUTTONS_HTML_HASH,
181 | attributes: {
182 | 'base64-content': 'CiAgICA8Ym9keT4KICAgICAgPGJ1dHRvbiBjbGFzcz0iYm9ndXMiPlJlZDwvYnV0dG9uPgogICAgPC9ib2R5PgogIA==' }
183 | }
184 | })
185 | .reply(201, SUCCESS_RESPONSE_MOCK);
186 | nockRequests.createSnapshot = nock('https://percy.io')
187 | .post(API_URLS.CREATE_SNAPSHOT, {
188 | data: {
189 | attributes: {
190 | name: 'buttons',
191 | widths: [600, 1440]
192 | },
193 | relationships: {
194 | resources: {
195 | data: [{id: BUTTONS_HTML_HASH}]
196 | }
197 | }
198 | }
199 | })
200 | .reply(201, CREATE_SNAPSHOT_RESPONSE_MOCK);
201 | nockRequests.finalizeSnapshot = nock('https://percy.io')
202 | .post(API_URLS.FINALIZE_SNAPSHOT)
203 | .reply(201, SUCCESS_RESPONSE_MOCK);
204 | nockRequests.finalizeBuild = nock('https://percy.io')
205 | .post(API_URLS.FINALIZE_BUILD)
206 | .reply(201, SUCCESS_RESPONSE_MOCK);
207 |
208 | setupPromise = percyNodeClient.setup(
209 | BUILD_DIRS, PATHS_TO_REPLACE, BREAKPOINT_CONFIG,
210 | enableDebugMode);
211 | });
212 |
213 | afterEach(() => {
214 | process.exit = exitFunc;
215 | nock.cleanAll();
216 | });
217 |
218 | it('should create a build', (done) => {
219 | setupPromise.then(() => {
220 | expect(nockRequests.buildsRequest.isDone()).toBe(true);
221 | expect(percyNodeClient.logger.log.calls.argsFor(0)[0])
222 | .toBe('[percy] Setting up project "foo/bar"');
223 | expect(percyNodeClient.logger.log.calls.argsFor(1)[0])
224 | .toBe('\n[percy] Build created:');
225 | expect(percyNodeClient.logger.log.calls.argsFor(1)[1])
226 | .toBe('https://percy.io/foo/bar/builds/123');
227 | done();
228 | });
229 | });
230 |
231 | it('should upload missing assets', (done) => {
232 | setupPromise.then(() => {
233 | expect(nockRequests.uploadCss.isDone()).toBe(true);
234 | expect(percyNodeClient.logger.log.calls.argsFor(2)[0])
235 | .toContain('[percy] Uploaded new build resource:');
236 | expect(percyNodeClient.logger.log.calls.argsFor(2)[0])
237 | .toContain('/assets/styles.css');
238 | done();
239 | });
240 | });
241 |
242 | it('should upload missing snapshots', (done) => {
243 | setupPromise.then(() => {
244 | percyNodeClient.snapshot('buttons', BUTTON_SNAPSHOT,
245 | ['small', 'large']);
246 | percyNodeClient.finalizeBuild().then(() => {
247 | expect(nockRequests.uploadHtmlSnapshot.isDone()).toBe(true);
248 | expect(nockRequests.finalizeSnapshot.isDone()).toBe(true);
249 | done();
250 | });
251 | });
252 | });
253 |
254 | it('should finalize the build', (done) => {
255 | setupPromise.then(() => {
256 | percyNodeClient.snapshot('buttons', BUTTON_SNAPSHOT,
257 | ['small', 'large']);
258 | percyNodeClient.finalizeBuild().then(() => {
259 | expect(nockRequests.finalizeBuild.isDone()).toBe(true);
260 |
261 | expect(percyNodeClient.logger.log.calls.argsFor(3)[0])
262 | .toBe('[percy] Finalizing build...');
263 |
264 | // Have to delay here because of process.nextTick in finalizeBuild.
265 | setTimeout(() => {
266 | expect(percyNodeClient.logger.log.calls.argsFor(4)[0])
267 | .toBe('[percy] Visual diffs are now processing:');
268 | expect(percyNodeClient.logger.log.calls.argsFor(4)[1])
269 | .toBe('https://percy.io/foo/bar/builds/123');
270 | done();
271 | }, 10);
272 | });
273 | });
274 | });
275 |
276 | it('should get the build successful result without diffs', (done) => {
277 | nockRequests.getBuild = nock('https://percy.io')
278 | .get(API_URLS.GET_BUILD)
279 | .reply(201, BUILD_RESULTS_RESPONSE_NO_DIFF_MOCK);
280 | setupPromise.then(() => {
281 | percyNodeClient.finalizeBuild(true).then(() => {
282 | expect(nockRequests.getBuild.isDone()).toBe(true);
283 | expect(percyNodeClient.logger.log.calls.argsFor(5)[0])
284 | .toBe('Hooray! The build is successful with no unreviewed diffs. \\o/');
285 | done();
286 | });
287 | });
288 | });
289 |
290 | it('should get the build successful result with diffs', (done) => {
291 | nockRequests.getBuild = nock('https://percy.io')
292 | .get(API_URLS.GET_BUILD)
293 | .reply(201, BUILD_RESULTS_RESPONSE_HAS_DIFF_MOCK);
294 | setupPromise.then(() => {
295 | percyNodeClient.finalizeBuild(true).then(() => {
296 | expect(nockRequests.getBuild.isDone()).toBe(true);
297 | expect(percyNodeClient.logger.error.calls.argsFor(0).join(' '))
298 | .toBe('percy unreviewed diffs found: 3. Check https://percy.io/foo/bar/builds/123');
299 | expect(process.exit).toHaveBeenCalled();
300 | done();
301 | });
302 | });
303 | });
304 |
305 | it('should get the build successful result with no unreviewed diffs', (done) => {
306 | nockRequests.getBuild = nock('https://percy.io')
307 | .get(API_URLS.GET_BUILD)
308 | .reply(201, BUILD_RESULTS_RESPONSE_HAS_NO_UNREVIEWED_DIFF_MOCK);
309 | setupPromise.then(() => {
310 | percyNodeClient.finalizeBuild(true).then(() => {
311 | expect(nockRequests.getBuild.isDone()).toBe(true);
312 | expect(percyNodeClient.logger.log.calls.argsFor(5)[0])
313 | .toBe('Hooray! The build is successful with no unreviewed diffs. \\o/');
314 | done();
315 | });
316 | });
317 | });
318 |
319 | it('should get the build failed result', (done) => {
320 | nockRequests.getBuild = nock('https://percy.io')
321 | .get(API_URLS.GET_BUILD)
322 | .reply(201, BUILD_RESULTS_RESPONSE_FAILED_MOCK);
323 | setupPromise.then(() => {
324 | percyNodeClient.finalizeBuild(true).then(() => {
325 | expect(nockRequests.getBuild.isDone()).toBe(true);
326 | expect(percyNodeClient.logger.error.calls.argsFor(0).join(' '))
327 | .toBe('percy build failed: missing_resources');
328 | expect(process.exit).toHaveBeenCalled();
329 | done();
330 | });
331 | });
332 | });
333 | });
334 | });
335 |
--------------------------------------------------------------------------------
/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | ajv@^4.9.1:
6 | version "4.11.8"
7 | resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536"
8 | dependencies:
9 | co "^4.6.0"
10 | json-stable-stringify "^1.0.1"
11 |
12 | ansi-escapes@^1.0.0:
13 | version "1.4.0"
14 | resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e"
15 |
16 | ansi-regex@^2.0.0:
17 | version "2.1.1"
18 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
19 |
20 | ansi-regex@^3.0.0:
21 | version "3.0.0"
22 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
23 |
24 | ansi-styles@^2.2.1:
25 | version "2.2.1"
26 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
27 |
28 | ansi-styles@^3.2.0:
29 | version "3.2.0"
30 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88"
31 | dependencies:
32 | color-convert "^1.9.0"
33 |
34 | app-root-path@^2.0.0:
35 | version "2.0.1"
36 | resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.0.1.tgz#cd62dcf8e4fd5a417efc664d2e5b10653c651b46"
37 |
38 | argparse@^1.0.7:
39 | version "1.0.10"
40 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
41 | dependencies:
42 | sprintf-js "~1.0.2"
43 |
44 | array-union@^1.0.1:
45 | version "1.0.2"
46 | resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39"
47 | dependencies:
48 | array-uniq "^1.0.1"
49 |
50 | array-uniq@^1.0.1:
51 | version "1.0.3"
52 | resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
53 |
54 | asn1@~0.2.3:
55 | version "0.2.4"
56 | resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
57 | dependencies:
58 | safer-buffer "~2.1.0"
59 |
60 | assert-plus@1.0.0, assert-plus@^1.0.0:
61 | version "1.0.0"
62 | resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
63 |
64 | assert-plus@^0.2.0:
65 | version "0.2.0"
66 | resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234"
67 |
68 | assertion-error@^1.0.1:
69 | version "1.0.2"
70 | resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.2.tgz#13ca515d86206da0bac66e834dd397d87581094c"
71 |
72 | asynckit@^0.4.0:
73 | version "0.4.0"
74 | resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
75 |
76 | aws-sign2@~0.6.0:
77 | version "0.6.0"
78 | resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f"
79 |
80 | aws4@^1.2.1:
81 | version "1.6.0"
82 | resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
83 |
84 | balanced-match@^1.0.0:
85 | version "1.0.0"
86 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
87 |
88 | base64-js@^1.1.2:
89 | version "1.2.1"
90 | resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886"
91 |
92 | bcrypt-pbkdf@^1.0.0:
93 | version "1.0.2"
94 | resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
95 | dependencies:
96 | tweetnacl "^0.14.3"
97 |
98 | bluebird-retry@^0.11.0:
99 | version "0.11.0"
100 | resolved "https://registry.yarnpkg.com/bluebird-retry/-/bluebird-retry-0.11.0.tgz#1289ab22cbbc3a02587baad35595351dd0c1c047"
101 |
102 | bluebird@^3.5.0:
103 | version "3.5.0"
104 | resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c"
105 |
106 | bluebird@^3.5.1:
107 | version "3.5.1"
108 | resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9"
109 |
110 | boom@2.x.x:
111 | version "2.10.1"
112 | resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f"
113 | dependencies:
114 | hoek "2.x.x"
115 |
116 | brace-expansion@^1.1.7:
117 | version "1.1.8"
118 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292"
119 | dependencies:
120 | balanced-match "^1.0.0"
121 | concat-map "0.0.1"
122 |
123 | caseless@~0.12.0:
124 | version "0.12.0"
125 | resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
126 |
127 | "chai@>=1.9.2 <4.0.0":
128 | version "3.5.0"
129 | resolved "https://registry.yarnpkg.com/chai/-/chai-3.5.0.tgz#4d02637b067fe958bdbfdd3a40ec56fef7373247"
130 | dependencies:
131 | assertion-error "^1.0.1"
132 | deep-eql "^0.1.3"
133 | type-detect "^1.0.0"
134 |
135 | chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
136 | version "1.1.3"
137 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
138 | dependencies:
139 | ansi-styles "^2.2.1"
140 | escape-string-regexp "^1.0.2"
141 | has-ansi "^2.0.0"
142 | strip-ansi "^3.0.0"
143 | supports-color "^2.0.0"
144 |
145 | chalk@^2.0.1, chalk@^2.1.0:
146 | version "2.3.1"
147 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.1.tgz#523fe2678aec7b04e8041909292fe8b17059b796"
148 | dependencies:
149 | ansi-styles "^3.2.0"
150 | escape-string-regexp "^1.0.5"
151 | supports-color "^5.2.0"
152 |
153 | cli-cursor@^1.0.2:
154 | version "1.0.2"
155 | resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987"
156 | dependencies:
157 | restore-cursor "^1.0.1"
158 |
159 | cli-spinners@^0.1.2:
160 | version "0.1.2"
161 | resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-0.1.2.tgz#bb764d88e185fb9e1e6a2a1f19772318f605e31c"
162 |
163 | cli-truncate@^0.2.1:
164 | version "0.2.1"
165 | resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-0.2.1.tgz#9f15cfbb0705005369216c626ac7d05ab90dd574"
166 | dependencies:
167 | slice-ansi "0.0.4"
168 | string-width "^1.0.1"
169 |
170 | co@^4.6.0:
171 | version "4.6.0"
172 | resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
173 |
174 | code-point-at@^1.0.0:
175 | version "1.1.0"
176 | resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
177 |
178 | color-convert@^1.9.0:
179 | version "1.9.1"
180 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed"
181 | dependencies:
182 | color-name "^1.1.1"
183 |
184 | color-name@^1.1.1:
185 | version "1.1.3"
186 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
187 |
188 | combined-stream@^1.0.5, combined-stream@~1.0.5:
189 | version "1.0.5"
190 | resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009"
191 | dependencies:
192 | delayed-stream "~1.0.0"
193 |
194 | commander@^2.11.0, commander@^2.9.0:
195 | version "2.14.1"
196 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.14.1.tgz#2235123e37af8ca3c65df45b026dbd357b01b9aa"
197 |
198 | concat-map@0.0.1:
199 | version "0.0.1"
200 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
201 |
202 | core-util-is@1.0.2:
203 | version "1.0.2"
204 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
205 |
206 | cosmiconfig@^1.1.0:
207 | version "1.1.0"
208 | resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-1.1.0.tgz#0dea0f9804efdfb929fbb1b188e25553ea053d37"
209 | dependencies:
210 | graceful-fs "^4.1.2"
211 | js-yaml "^3.4.3"
212 | minimist "^1.2.0"
213 | object-assign "^4.0.1"
214 | os-homedir "^1.0.1"
215 | parse-json "^2.2.0"
216 | pinkie-promise "^2.0.0"
217 | require-from-string "^1.1.0"
218 |
219 | cross-spawn@^5.0.1:
220 | version "5.1.0"
221 | resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
222 | dependencies:
223 | lru-cache "^4.0.1"
224 | shebang-command "^1.2.0"
225 | which "^1.2.9"
226 |
227 | cryptiles@2.x.x:
228 | version "2.0.5"
229 | resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8"
230 | dependencies:
231 | boom "2.x.x"
232 |
233 | crypto@0.0.3:
234 | version "0.0.3"
235 | resolved "https://registry.yarnpkg.com/crypto/-/crypto-0.0.3.tgz#470a81b86be4c5ee17acc8207a1f5315ae20dbb0"
236 |
237 | dashdash@^1.12.0:
238 | version "1.14.1"
239 | resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
240 | dependencies:
241 | assert-plus "^1.0.0"
242 |
243 | date-fns@^1.27.2:
244 | version "1.29.0"
245 | resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.29.0.tgz#12e609cdcb935127311d04d33334e2960a2a54e6"
246 |
247 | debug@^2.2.0:
248 | version "2.6.9"
249 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
250 | dependencies:
251 | ms "2.0.0"
252 |
253 | deep-eql@^0.1.3:
254 | version "0.1.3"
255 | resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-0.1.3.tgz#ef558acab8de25206cd713906d74e56930eb69f2"
256 | dependencies:
257 | type-detect "0.1.1"
258 |
259 | deep-equal@^1.0.0:
260 | version "1.0.1"
261 | resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
262 |
263 | delayed-stream@~1.0.0:
264 | version "1.0.0"
265 | resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
266 |
267 | ecc-jsbn@~0.1.1:
268 | version "0.1.2"
269 | resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
270 | dependencies:
271 | jsbn "~0.1.0"
272 | safer-buffer "^2.1.0"
273 |
274 | elegant-spinner@^1.0.1:
275 | version "1.0.1"
276 | resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e"
277 |
278 | error-ex@^1.2.0:
279 | version "1.3.1"
280 | resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc"
281 | dependencies:
282 | is-arrayish "^0.2.1"
283 |
284 | es6-promise-pool@^2.4.6, es6-promise-pool@^2.5.0:
285 | version "2.5.0"
286 | resolved "https://registry.yarnpkg.com/es6-promise-pool/-/es6-promise-pool-2.5.0.tgz#147c612b36b47f105027f9d2bf54a598a99d9ccb"
287 |
288 | escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
289 | version "1.0.5"
290 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
291 |
292 | esprima@^4.0.0:
293 | version "4.0.1"
294 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
295 |
296 | execa@^0.8.0:
297 | version "0.8.0"
298 | resolved "https://registry.yarnpkg.com/execa/-/execa-0.8.0.tgz#d8d76bbc1b55217ed190fd6dd49d3c774ecfc8da"
299 | dependencies:
300 | cross-spawn "^5.0.1"
301 | get-stream "^3.0.0"
302 | is-stream "^1.1.0"
303 | npm-run-path "^2.0.0"
304 | p-finally "^1.0.0"
305 | signal-exit "^3.0.0"
306 | strip-eof "^1.0.0"
307 |
308 | exit-hook@^1.0.0:
309 | version "1.1.1"
310 | resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8"
311 |
312 | exit@^0.1.2:
313 | version "0.1.2"
314 | resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
315 |
316 | extend@~3.0.0:
317 | version "3.0.2"
318 | resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
319 |
320 | extsprintf@1.3.0, extsprintf@^1.2.0:
321 | version "1.3.0"
322 | resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
323 |
324 | figures@^1.7.0:
325 | version "1.7.0"
326 | resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
327 | dependencies:
328 | escape-string-regexp "^1.0.5"
329 | object-assign "^4.1.0"
330 |
331 | foreachasync@^3.0.0:
332 | version "3.0.0"
333 | resolved "https://registry.yarnpkg.com/foreachasync/-/foreachasync-3.0.0.tgz#5502987dc8714be3392097f32e0071c9dee07cf6"
334 |
335 | forever-agent@~0.6.1:
336 | version "0.6.1"
337 | resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
338 |
339 | form-data@~2.1.1:
340 | version "2.1.4"
341 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1"
342 | dependencies:
343 | asynckit "^0.4.0"
344 | combined-stream "^1.0.5"
345 | mime-types "^2.1.12"
346 |
347 | fs.realpath@^1.0.0:
348 | version "1.0.0"
349 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
350 |
351 | get-own-enumerable-property-symbols@^2.0.1:
352 | version "2.0.1"
353 | resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-2.0.1.tgz#5c4ad87f2834c4b9b4e84549dc1e0650fb38c24b"
354 |
355 | get-stream@^3.0.0:
356 | version "3.0.0"
357 | resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
358 |
359 | getpass@^0.1.1:
360 | version "0.1.7"
361 | resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
362 | dependencies:
363 | assert-plus "^1.0.0"
364 |
365 | glob@^7.0.3, glob@^7.0.6:
366 | version "7.1.2"
367 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
368 | dependencies:
369 | fs.realpath "^1.0.0"
370 | inflight "^1.0.4"
371 | inherits "2"
372 | minimatch "^3.0.4"
373 | once "^1.3.0"
374 | path-is-absolute "^1.0.0"
375 |
376 | globby@^6.1.0:
377 | version "6.1.0"
378 | resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c"
379 | dependencies:
380 | array-union "^1.0.1"
381 | glob "^7.0.3"
382 | object-assign "^4.0.1"
383 | pify "^2.0.0"
384 | pinkie-promise "^2.0.0"
385 |
386 | graceful-fs@^4.1.2:
387 | version "4.1.11"
388 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
389 |
390 | har-schema@^1.0.5:
391 | version "1.0.5"
392 | resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e"
393 |
394 | har-validator@~4.2.1:
395 | version "4.2.1"
396 | resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a"
397 | dependencies:
398 | ajv "^4.9.1"
399 | har-schema "^1.0.5"
400 |
401 | has-ansi@^2.0.0:
402 | version "2.0.0"
403 | resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
404 | dependencies:
405 | ansi-regex "^2.0.0"
406 |
407 | has-flag@^3.0.0:
408 | version "3.0.0"
409 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
410 |
411 | hawk@~3.1.3:
412 | version "3.1.3"
413 | resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4"
414 | dependencies:
415 | boom "2.x.x"
416 | cryptiles "2.x.x"
417 | hoek "2.x.x"
418 | sntp "1.x.x"
419 |
420 | hoek@2.x.x:
421 | version "2.16.3"
422 | resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
423 |
424 | http-signature@~1.1.0:
425 | version "1.1.1"
426 | resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf"
427 | dependencies:
428 | assert-plus "^0.2.0"
429 | jsprim "^1.2.2"
430 | sshpk "^1.7.0"
431 |
432 | indent-string@^2.1.0:
433 | version "2.1.0"
434 | resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80"
435 | dependencies:
436 | repeating "^2.0.0"
437 |
438 | indent-string@^3.0.0:
439 | version "3.2.0"
440 | resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289"
441 |
442 | inflight@^1.0.4:
443 | version "1.0.6"
444 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
445 | dependencies:
446 | once "^1.3.0"
447 | wrappy "1"
448 |
449 | inherits@2:
450 | version "2.0.3"
451 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
452 |
453 | is-arrayish@^0.2.1:
454 | version "0.2.1"
455 | resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
456 |
457 | is-extglob@^2.1.1:
458 | version "2.1.1"
459 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
460 |
461 | is-finite@^1.0.0:
462 | version "1.0.2"
463 | resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa"
464 | dependencies:
465 | number-is-nan "^1.0.0"
466 |
467 | is-fullwidth-code-point@^1.0.0:
468 | version "1.0.0"
469 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
470 | dependencies:
471 | number-is-nan "^1.0.0"
472 |
473 | is-glob@^4.0.0:
474 | version "4.0.0"
475 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0"
476 | dependencies:
477 | is-extglob "^2.1.1"
478 |
479 | is-obj@^1.0.1:
480 | version "1.0.1"
481 | resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
482 |
483 | is-promise@^2.1.0:
484 | version "2.1.0"
485 | resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa"
486 |
487 | is-regexp@^1.0.0:
488 | version "1.0.0"
489 | resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069"
490 |
491 | is-stream@^1.1.0:
492 | version "1.1.0"
493 | resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
494 |
495 | is-typedarray@~1.0.0:
496 | version "1.0.0"
497 | resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
498 |
499 | isexe@^2.0.0:
500 | version "2.0.0"
501 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
502 |
503 | isstream@~0.1.2:
504 | version "0.1.2"
505 | resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
506 |
507 | jasmine-core@~2.7.0:
508 | version "2.7.0"
509 | resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.7.0.tgz#50ff8c4f92d8ef5c0b2c1b846dd263ed85152091"
510 |
511 | jasmine@^2.7.0:
512 | version "2.7.0"
513 | resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-2.7.0.tgz#5cf0bb4e594b4600bb4235560366212ac5aea1b2"
514 | dependencies:
515 | exit "^0.1.2"
516 | glob "^7.0.6"
517 | jasmine-core "~2.7.0"
518 |
519 | jest-get-type@^21.2.0:
520 | version "21.2.0"
521 | resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-21.2.0.tgz#f6376ab9db4b60d81e39f30749c6c466f40d4a23"
522 |
523 | jest-validate@^21.1.0:
524 | version "21.2.1"
525 | resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-21.2.1.tgz#cc0cbca653cd54937ba4f2a111796774530dd3c7"
526 | dependencies:
527 | chalk "^2.0.1"
528 | jest-get-type "^21.2.0"
529 | leven "^2.1.0"
530 | pretty-format "^21.2.1"
531 |
532 | js-yaml@^3.4.3:
533 | version "3.14.1"
534 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537"
535 | dependencies:
536 | argparse "^1.0.7"
537 | esprima "^4.0.0"
538 |
539 | jsbn@~0.1.0:
540 | version "0.1.1"
541 | resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
542 |
543 | json-schema@0.2.3:
544 | version "0.2.3"
545 | resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
546 |
547 | json-stable-stringify@^1.0.1:
548 | version "1.0.1"
549 | resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af"
550 | dependencies:
551 | jsonify "~0.0.0"
552 |
553 | json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1:
554 | version "5.0.1"
555 | resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
556 |
557 | jsonify@~0.0.0:
558 | version "0.0.0"
559 | resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
560 |
561 | jsprim@^1.2.2:
562 | version "1.4.1"
563 | resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
564 | dependencies:
565 | assert-plus "1.0.0"
566 | extsprintf "1.3.0"
567 | json-schema "0.2.3"
568 | verror "1.10.0"
569 |
570 | jssha@^2.1.0:
571 | version "2.3.1"
572 | resolved "https://registry.yarnpkg.com/jssha/-/jssha-2.3.1.tgz#147b2125369035ca4b2f7d210dc539f009b3de9a"
573 |
574 | leven@^2.1.0:
575 | version "2.1.0"
576 | resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580"
577 |
578 | lint-staged@^4.1.3:
579 | version "4.3.0"
580 | resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-4.3.0.tgz#ed0779ad9a42c0dc62bb3244e522870b41125879"
581 | dependencies:
582 | app-root-path "^2.0.0"
583 | chalk "^2.1.0"
584 | commander "^2.11.0"
585 | cosmiconfig "^1.1.0"
586 | execa "^0.8.0"
587 | is-glob "^4.0.0"
588 | jest-validate "^21.1.0"
589 | listr "^0.12.0"
590 | lodash "^4.17.4"
591 | log-symbols "^2.0.0"
592 | minimatch "^3.0.0"
593 | npm-which "^3.0.1"
594 | p-map "^1.1.1"
595 | staged-git-files "0.0.4"
596 | stringify-object "^3.2.0"
597 |
598 | listr-silent-renderer@^1.1.1:
599 | version "1.1.1"
600 | resolved "https://registry.yarnpkg.com/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz#924b5a3757153770bf1a8e3fbf74b8bbf3f9242e"
601 |
602 | listr-update-renderer@^0.2.0:
603 | version "0.2.0"
604 | resolved "https://registry.yarnpkg.com/listr-update-renderer/-/listr-update-renderer-0.2.0.tgz#ca80e1779b4e70266807e8eed1ad6abe398550f9"
605 | dependencies:
606 | chalk "^1.1.3"
607 | cli-truncate "^0.2.1"
608 | elegant-spinner "^1.0.1"
609 | figures "^1.7.0"
610 | indent-string "^3.0.0"
611 | log-symbols "^1.0.2"
612 | log-update "^1.0.2"
613 | strip-ansi "^3.0.1"
614 |
615 | listr-verbose-renderer@^0.4.0:
616 | version "0.4.1"
617 | resolved "https://registry.yarnpkg.com/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz#8206f4cf6d52ddc5827e5fd14989e0e965933a35"
618 | dependencies:
619 | chalk "^1.1.3"
620 | cli-cursor "^1.0.2"
621 | date-fns "^1.27.2"
622 | figures "^1.7.0"
623 |
624 | listr@^0.12.0:
625 | version "0.12.0"
626 | resolved "https://registry.yarnpkg.com/listr/-/listr-0.12.0.tgz#6bce2c0f5603fa49580ea17cd6a00cc0e5fa451a"
627 | dependencies:
628 | chalk "^1.1.3"
629 | cli-truncate "^0.2.1"
630 | figures "^1.7.0"
631 | indent-string "^2.1.0"
632 | is-promise "^2.1.0"
633 | is-stream "^1.1.0"
634 | listr-silent-renderer "^1.1.1"
635 | listr-update-renderer "^0.2.0"
636 | listr-verbose-renderer "^0.4.0"
637 | log-symbols "^1.0.2"
638 | log-update "^1.0.2"
639 | ora "^0.2.3"
640 | p-map "^1.1.1"
641 | rxjs "^5.0.0-beta.11"
642 | stream-to-observable "^0.1.0"
643 | strip-ansi "^3.0.1"
644 |
645 | lodash@^4.13.1, lodash@^4.17.4, lodash@~4.17.2:
646 | version "4.17.21"
647 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
648 |
649 | log-symbols@^1.0.2:
650 | version "1.0.2"
651 | resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18"
652 | dependencies:
653 | chalk "^1.0.0"
654 |
655 | log-symbols@^2.0.0:
656 | version "2.2.0"
657 | resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a"
658 | dependencies:
659 | chalk "^2.0.1"
660 |
661 | log-update@^1.0.2:
662 | version "1.0.2"
663 | resolved "https://registry.yarnpkg.com/log-update/-/log-update-1.0.2.tgz#19929f64c4093d2d2e7075a1dad8af59c296b8d1"
664 | dependencies:
665 | ansi-escapes "^1.0.0"
666 | cli-cursor "^1.0.2"
667 |
668 | lru-cache@^4.0.1:
669 | version "4.1.1"
670 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55"
671 | dependencies:
672 | pseudomap "^1.0.2"
673 | yallist "^2.1.2"
674 |
675 | mime-db@~1.29.0:
676 | version "1.29.0"
677 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.29.0.tgz#48d26d235589651704ac5916ca06001914266878"
678 |
679 | mime-types@^2.1.12, mime-types@~2.1.7:
680 | version "2.1.16"
681 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.16.tgz#2b858a52e5ecd516db897ac2be87487830698e23"
682 | dependencies:
683 | mime-db "~1.29.0"
684 |
685 | minimatch@^3.0.0, minimatch@^3.0.4:
686 | version "3.0.4"
687 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
688 | dependencies:
689 | brace-expansion "^1.1.7"
690 |
691 | minimist@0.0.8:
692 | version "0.0.8"
693 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
694 |
695 | minimist@^1.2.0:
696 | version "1.2.0"
697 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
698 |
699 | mkdirp@^0.5.0:
700 | version "0.5.1"
701 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
702 | dependencies:
703 | minimist "0.0.8"
704 |
705 | ms@2.0.0:
706 | version "2.0.0"
707 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
708 |
709 | nock@^9.0.14:
710 | version "9.0.14"
711 | resolved "https://registry.yarnpkg.com/nock/-/nock-9.0.14.tgz#2211550253173ce298bcd89fca825e83813ca72b"
712 | dependencies:
713 | chai ">=1.9.2 <4.0.0"
714 | debug "^2.2.0"
715 | deep-equal "^1.0.0"
716 | json-stringify-safe "^5.0.1"
717 | lodash "~4.17.2"
718 | mkdirp "^0.5.0"
719 | propagate "0.4.0"
720 | qs "^6.0.2"
721 | semver "^5.3.0"
722 |
723 | npm-path@^2.0.2:
724 | version "2.0.4"
725 | resolved "https://registry.yarnpkg.com/npm-path/-/npm-path-2.0.4.tgz#c641347a5ff9d6a09e4d9bce5580c4f505278e64"
726 | dependencies:
727 | which "^1.2.10"
728 |
729 | npm-run-path@^2.0.0:
730 | version "2.0.2"
731 | resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
732 | dependencies:
733 | path-key "^2.0.0"
734 |
735 | npm-which@^3.0.1:
736 | version "3.0.1"
737 | resolved "https://registry.yarnpkg.com/npm-which/-/npm-which-3.0.1.tgz#9225f26ec3a285c209cae67c3b11a6b4ab7140aa"
738 | dependencies:
739 | commander "^2.9.0"
740 | npm-path "^2.0.2"
741 | which "^1.2.10"
742 |
743 | number-is-nan@^1.0.0:
744 | version "1.0.1"
745 | resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
746 |
747 | oauth-sign@~0.8.1:
748 | version "0.8.2"
749 | resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
750 |
751 | object-assign@^4.0.1, object-assign@^4.1.0:
752 | version "4.1.1"
753 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
754 |
755 | once@^1.3.0:
756 | version "1.4.0"
757 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
758 | dependencies:
759 | wrappy "1"
760 |
761 | onetime@^1.0.0:
762 | version "1.1.0"
763 | resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789"
764 |
765 | ora@^0.2.3:
766 | version "0.2.3"
767 | resolved "https://registry.yarnpkg.com/ora/-/ora-0.2.3.tgz#37527d220adcd53c39b73571d754156d5db657a4"
768 | dependencies:
769 | chalk "^1.1.1"
770 | cli-cursor "^1.0.2"
771 | cli-spinners "^0.1.2"
772 | object-assign "^4.0.1"
773 |
774 | os-homedir@^1.0.1:
775 | version "1.0.2"
776 | resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
777 |
778 | p-finally@^1.0.0:
779 | version "1.0.0"
780 | resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
781 |
782 | p-map@^1.1.1:
783 | version "1.2.0"
784 | resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b"
785 |
786 | parse-json@^2.2.0:
787 | version "2.2.0"
788 | resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9"
789 | dependencies:
790 | error-ex "^1.2.0"
791 |
792 | path-is-absolute@^1.0.0:
793 | version "1.0.1"
794 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
795 |
796 | path-key@^2.0.0:
797 | version "2.0.1"
798 | resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
799 |
800 | percy-client@^2.6.0:
801 | version "2.6.0"
802 | resolved "https://registry.yarnpkg.com/percy-client/-/percy-client-2.6.0.tgz#df63a63256ba0e428b6a22ee5fc4dec482ab94a2"
803 | dependencies:
804 | base64-js "^1.1.2"
805 | bluebird "^3.5.1"
806 | bluebird-retry "^0.11.0"
807 | es6-promise-pool "^2.5.0"
808 | jssha "^2.1.0"
809 | regenerator-runtime "^0.11.0"
810 | request "^2.40.0"
811 | request-promise "^4.1.1"
812 | optionalDependencies:
813 | lint-staged "^4.1.3"
814 |
815 | performance-now@^0.2.0:
816 | version "0.2.0"
817 | resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5"
818 |
819 | pify@^2.0.0:
820 | version "2.3.0"
821 | resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
822 |
823 | pinkie-promise@^2.0.0:
824 | version "2.0.1"
825 | resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
826 | dependencies:
827 | pinkie "^2.0.0"
828 |
829 | pinkie@^2.0.0:
830 | version "2.0.4"
831 | resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
832 |
833 | pretty-format@^21.2.1:
834 | version "21.2.1"
835 | resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-21.2.1.tgz#ae5407f3cf21066cd011aa1ba5fce7b6a2eddb36"
836 | dependencies:
837 | ansi-regex "^3.0.0"
838 | ansi-styles "^3.2.0"
839 |
840 | propagate@0.4.0:
841 | version "0.4.0"
842 | resolved "https://registry.yarnpkg.com/propagate/-/propagate-0.4.0.tgz#f3fcca0a6fe06736a7ba572966069617c130b481"
843 |
844 | pseudomap@^1.0.2:
845 | version "1.0.2"
846 | resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
847 |
848 | punycode@^1.4.1:
849 | version "1.4.1"
850 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
851 |
852 | qs@^6.0.2:
853 | version "6.5.0"
854 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.0.tgz#8d04954d364def3efc55b5a0793e1e2c8b1e6e49"
855 |
856 | qs@~6.4.0:
857 | version "6.4.0"
858 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
859 |
860 | regenerator-runtime@^0.11.0:
861 | version "0.11.1"
862 | resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
863 |
864 | repeating@^2.0.0:
865 | version "2.0.1"
866 | resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda"
867 | dependencies:
868 | is-finite "^1.0.0"
869 |
870 | request-promise-core@1.1.1:
871 | version "1.1.1"
872 | resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.1.tgz#3eee00b2c5aa83239cfb04c5700da36f81cd08b6"
873 | dependencies:
874 | lodash "^4.13.1"
875 |
876 | request-promise@^4.1.1:
877 | version "4.2.1"
878 | resolved "https://registry.yarnpkg.com/request-promise/-/request-promise-4.2.1.tgz#7eec56c89317a822cbfea99b039ce543c2e15f67"
879 | dependencies:
880 | bluebird "^3.5.0"
881 | request-promise-core "1.1.1"
882 | stealthy-require "^1.1.0"
883 | tough-cookie ">=2.3.0"
884 |
885 | request@^2.40.0:
886 | version "2.81.0"
887 | resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0"
888 | dependencies:
889 | aws-sign2 "~0.6.0"
890 | aws4 "^1.2.1"
891 | caseless "~0.12.0"
892 | combined-stream "~1.0.5"
893 | extend "~3.0.0"
894 | forever-agent "~0.6.1"
895 | form-data "~2.1.1"
896 | har-validator "~4.2.1"
897 | hawk "~3.1.3"
898 | http-signature "~1.1.0"
899 | is-typedarray "~1.0.0"
900 | isstream "~0.1.2"
901 | json-stringify-safe "~5.0.1"
902 | mime-types "~2.1.7"
903 | oauth-sign "~0.8.1"
904 | performance-now "^0.2.0"
905 | qs "~6.4.0"
906 | safe-buffer "^5.0.1"
907 | stringstream "~0.0.4"
908 | tough-cookie "~2.3.0"
909 | tunnel-agent "^0.6.0"
910 | uuid "^3.0.0"
911 |
912 | require-from-string@^1.1.0:
913 | version "1.2.1"
914 | resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-1.2.1.tgz#529c9ccef27380adfec9a2f965b649bbee636418"
915 |
916 | restore-cursor@^1.0.1:
917 | version "1.0.1"
918 | resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541"
919 | dependencies:
920 | exit-hook "^1.0.0"
921 | onetime "^1.0.0"
922 |
923 | rxjs@^5.0.0-beta.11:
924 | version "5.5.6"
925 | resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.6.tgz#e31fb96d6fd2ff1fd84bcea8ae9c02d007179c02"
926 | dependencies:
927 | symbol-observable "1.0.1"
928 |
929 | safe-buffer@^5.0.1:
930 | version "5.1.1"
931 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
932 |
933 | safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
934 | version "2.1.2"
935 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
936 |
937 | semver@^5.3.0:
938 | version "5.4.1"
939 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e"
940 |
941 | shebang-command@^1.2.0:
942 | version "1.2.0"
943 | resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
944 | dependencies:
945 | shebang-regex "^1.0.0"
946 |
947 | shebang-regex@^1.0.0:
948 | version "1.0.0"
949 | resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
950 |
951 | signal-exit@^3.0.0:
952 | version "3.0.2"
953 | resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
954 |
955 | slice-ansi@0.0.4:
956 | version "0.0.4"
957 | resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35"
958 |
959 | sntp@1.x.x:
960 | version "1.0.9"
961 | resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198"
962 | dependencies:
963 | hoek "2.x.x"
964 |
965 | sprintf-js@~1.0.2:
966 | version "1.0.3"
967 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
968 |
969 | sshpk@^1.7.0:
970 | version "1.16.1"
971 | resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"
972 | dependencies:
973 | asn1 "~0.2.3"
974 | assert-plus "^1.0.0"
975 | bcrypt-pbkdf "^1.0.0"
976 | dashdash "^1.12.0"
977 | ecc-jsbn "~0.1.1"
978 | getpass "^0.1.1"
979 | jsbn "~0.1.0"
980 | safer-buffer "^2.0.2"
981 | tweetnacl "~0.14.0"
982 |
983 | staged-git-files@0.0.4:
984 | version "0.0.4"
985 | resolved "https://registry.yarnpkg.com/staged-git-files/-/staged-git-files-0.0.4.tgz#d797e1b551ca7a639dec0237dc6eb4bb9be17d35"
986 |
987 | stealthy-require@^1.1.0:
988 | version "1.1.1"
989 | resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b"
990 |
991 | stream-to-observable@^0.1.0:
992 | version "0.1.0"
993 | resolved "https://registry.yarnpkg.com/stream-to-observable/-/stream-to-observable-0.1.0.tgz#45bf1d9f2d7dc09bed81f1c307c430e68b84cffe"
994 |
995 | string-width@^1.0.1:
996 | version "1.0.2"
997 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
998 | dependencies:
999 | code-point-at "^1.0.0"
1000 | is-fullwidth-code-point "^1.0.0"
1001 | strip-ansi "^3.0.0"
1002 |
1003 | stringify-object@^3.2.0:
1004 | version "3.2.2"
1005 | resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.2.2.tgz#9853052e5a88fb605a44cd27445aa257ad7ffbcd"
1006 | dependencies:
1007 | get-own-enumerable-property-symbols "^2.0.1"
1008 | is-obj "^1.0.1"
1009 | is-regexp "^1.0.0"
1010 |
1011 | stringstream@~0.0.4:
1012 | version "0.0.6"
1013 | resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.6.tgz#7880225b0d4ad10e30927d167a1d6f2fd3b33a72"
1014 |
1015 | strip-ansi@^3.0.0, strip-ansi@^3.0.1:
1016 | version "3.0.1"
1017 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
1018 | dependencies:
1019 | ansi-regex "^2.0.0"
1020 |
1021 | strip-eof@^1.0.0:
1022 | version "1.0.0"
1023 | resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
1024 |
1025 | supports-color@^2.0.0:
1026 | version "2.0.0"
1027 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
1028 |
1029 | supports-color@^5.2.0:
1030 | version "5.2.0"
1031 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.2.0.tgz#b0d5333b1184dd3666cbe5aa0b45c5ac7ac17a4a"
1032 | dependencies:
1033 | has-flag "^3.0.0"
1034 |
1035 | symbol-observable@1.0.1:
1036 | version "1.0.1"
1037 | resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4"
1038 |
1039 | tough-cookie@>=2.3.0, tough-cookie@~2.3.0:
1040 | version "2.3.4"
1041 | resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655"
1042 | dependencies:
1043 | punycode "^1.4.1"
1044 |
1045 | tunnel-agent@^0.6.0:
1046 | version "0.6.0"
1047 | resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
1048 | dependencies:
1049 | safe-buffer "^5.0.1"
1050 |
1051 | tweetnacl@^0.14.3, tweetnacl@~0.14.0:
1052 | version "0.14.5"
1053 | resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
1054 |
1055 | type-detect@0.1.1:
1056 | version "0.1.1"
1057 | resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-0.1.1.tgz#0ba5ec2a885640e470ea4e8505971900dac58822"
1058 |
1059 | type-detect@^1.0.0:
1060 | version "1.0.0"
1061 | resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-1.0.0.tgz#762217cc06db258ec48908a1298e8b95121e8ea2"
1062 |
1063 | uuid@^3.0.0:
1064 | version "3.1.0"
1065 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04"
1066 |
1067 | verror@1.10.0:
1068 | version "1.10.0"
1069 | resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
1070 | dependencies:
1071 | assert-plus "^1.0.0"
1072 | core-util-is "1.0.2"
1073 | extsprintf "^1.2.0"
1074 |
1075 | walk@^2.3.9:
1076 | version "2.3.9"
1077 | resolved "https://registry.yarnpkg.com/walk/-/walk-2.3.9.tgz#31b4db6678f2ae01c39ea9fb8725a9031e558a7b"
1078 | dependencies:
1079 | foreachasync "^3.0.0"
1080 |
1081 | which@^1.2.10, which@^1.2.9:
1082 | version "1.3.0"
1083 | resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a"
1084 | dependencies:
1085 | isexe "^2.0.0"
1086 |
1087 | wrappy@1:
1088 | version "1.0.2"
1089 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
1090 |
1091 | yallist@^2.1.2:
1092 | version "2.1.2"
1093 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
1094 |
--------------------------------------------------------------------------------