├── .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 | [![Build Status](https://travis-ci.org/google/percy-node.svg?branch=master)](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 | --------------------------------------------------------------------------------