├── .gitignore ├── .npmignore ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src ├── databases │ ├── base.ts │ ├── indexeddb.ts │ ├── loki.ts │ └── memory.ts ├── index.ts ├── util.ts └── uuid.ts ├── test ├── databases │ └── database.spec.ts ├── index.spec.ts └── uuid.spec.ts ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | out/ 61 | 62 | .vscode/ 63 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | tsconfig.json 3 | tslint.json 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | # Sync to `process.versions.node` in the Electron dev console 4 | # Currently: Electron 2.0 5 | node_js: "8.9.3" 6 | 7 | before_script: 8 | - export DISPLAY=:99.0; sh -e /etc/init.d/xvfb start 9 | 10 | notifications: 11 | email: 12 | on_success: never 13 | on_failure: change 14 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [atom@github.com](mailto:atom@github.com). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [https://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: https://contributor-covenant.org 46 | [version]: https://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to github-telemetry 2 | 3 | :+1: :tada: :sparkling_heart: Thanks for your interest! :sparkling_heart: :tada: :+1: 4 | 5 | +Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [project's open source license](LICENSE). 6 | 7 | The following is a set of guidelines for contributing to github-telemtry. These are just guidelines, not rules. Use your best judgment, and 8 | feel free to propose changes to this document in a pull request. 9 | 10 | ## Code of Conduct 11 | 12 | This project and everyone participating in it is governed by the [Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to [atom@github.com](mailto:atom@github.com). 13 | 14 | ## What should I know before I get started? 15 | `telemetry` is hosted in the Atom org, but the intention is that it could be used by other GitHub tools. This is why it's a npm module and not simply an Atom package. 16 | 17 | That said, Atom is the first user of `telemetry`. If you are looking to contribute to where `telemetry` is called from Atom, please see the [Atom contributing guide](https://github.com/atom/atom/blob/master/CONTRIBUTING.md). 18 | 19 | ## How Can I Contribute? 20 | 21 | ### Reporting Bugs 22 | 23 | Before creating bug reports, please check existing issues as you might find out that you don't need to create one. When you are creating a bug report, please include as many details as possible. Fill out [the required template](ISSUE_TEMPLATE.md); the information it asks for helps us resolve issues faster. 24 | 25 | > **Note:** If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one. 26 | 27 | ## Submitting a pull request 28 | 29 | Here are a few things you can do that will increase the likelihood of your pull request being accepted: 30 | 31 | - Write tests. 32 | - Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests. 33 | - Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). 34 | 35 | ## Resources 36 | 37 | - [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) 38 | - [Using Pull Requests](https://help.github.com/articles/about-pull-requests/) 39 | - [GitHub Help](https://help.github.com) 40 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | ### Prerequisites 8 | 9 | * [ ] Put an X between the brackets on this line if you have done all of the following: 10 | * Checked that your issue isn't already filed: https://github.com/atom/telemetry/issues 11 | 12 | ### Description 13 | 14 | [Description of the issue] 15 | 16 | ### Steps to Reproduce 17 | 18 | 1. [First Step] 19 | 2. [Second Step] 20 | 3. [and so on...] 21 | 22 | **Expected behavior:** [What you expect to happen] 23 | 24 | **Actual behavior:** [What actually happens] 25 | 26 | **Reproduces how often:** [What percentage of the time does it reproduce?] 27 | 28 | ### Versions 29 | 30 | Please include the version of `github-telemetry`, as well as the version of the editor you're using. Please include the OS and what version of the OS you're running. 31 | 32 | ### Additional Information 33 | 34 | Any additional information, configuration or data that might be necessary to reproduce the issue. 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) GitHub, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ##### Atom and all repositories under Atom will be archived on December 15, 2022. Learn more in our [official announcement](https://github.blog/2022-06-08-sunsetting-atom/) 2 | [![Build Status](https://travis-ci.com/atom/telemetry.svg?token=RwrCnzpsZN5oEq5S5p7V&branch=master)](https://travis-ci.com/atom/telemetry) 3 | 4 | `telemetry-github` is an open source module written in TypeScript. It sends usage metrics to GitHub's internal analytics pipeline. 5 | 6 | This app can be used from any GitHub client application that speaks JavaScript and has access to localStorage. 7 | 8 | We've open-sourced this library because we care about privacy and want to be transparent about how we collect metrics. 9 | 10 | ## Getting started 11 | 12 | to install package and dependencies: 13 | `npm install telemetry-github` 14 | 15 | to run tests: 16 | `npm test` 17 | 18 | to run lint: 19 | `npm run-script lint` 20 | 21 | to compile typescript: 22 | `tsc` 23 | 24 | The client api is experimental and subject to change. 25 | but for right now it looks something like this: 26 | ``` 27 | import StatsStore from "telemetry-github"; 28 | 29 | // make a store 30 | const store = new StatsStore("atom", "1.24.1", false, getAccessToken, options); 31 | 32 | // record a usage event 33 | store.incrementCounter("commit"); 34 | 35 | // record a change in user consent to record metrics 36 | store.setOptOut(true); 37 | 38 | ``` 39 | 40 | Please note that there are several methods of the `StatsStore` class that are public for unit testing purposes only. The methods describe below are the ones that clients should care about. 41 | 42 | ### Counters vs. custom events 43 | 44 | There are some event types that are common across all client apps: usage events, ping events, and opt in / out events. `telemetry` encapsulates as much complexity around these as possible so clients don't have to deal with it. 45 | 46 | Counters are a great fit for understanding the number of times a certain action happened. For example, how many times per day do users click a particular button? 47 | 48 | However, apps might want to collect more complex metrics with arbitrary metadata. For example, Atom currently collects "file open" events, which preserve the grammar (aka language) of the opened file. For those use cases, the `addCustomEvent` function is your friend. `addCustomEvent` takes any object and stuffs it in the database, giving clients the flexibility to define their own data destiny. The events are sent to the metrics back end along with the daily payload. 49 | 50 | Events must include a type, which is the first argument to `addCustomEvent`. A timestamp is added for you in ISO-8601 format. 51 | 52 | ``` 53 | const event = { grammar: "javascript" }; 54 | await store.addCustomEvent("open", event); 55 | 56 | // { "date": "2018-06-14T21:01:33.602Z", "eventType": "open", "grammar": "javascript" } 57 | ``` 58 | 59 | ### Timers 60 | 61 | You can use the `addTimer` API to send latency metrics. While of course you could use `addCustomEvent` to record latency metrics, using this endpoint allows us to have a consistent event format across apps. 62 | 63 | ``` 64 | const eventType = "appStartup"; 65 | const loadTimeInMilliseconds = 42; 66 | const metadata = {spam: "ham"}; 67 | // metadata is optional 68 | store.addTiming(eventType, loadTimeInMilliseconds, metadata); 69 | ``` 70 | 71 | ### Options 72 | 73 | You can pass additional options to `telemetry` via its constructor: 74 | 75 | ```js 76 | // The following are the default values 77 | const options = { 78 | reportingFrequency: 86400, // How often do we want to send metrics. 79 | logInDevMode: false, // Whether it should send metrics when isDevMode is true. 80 | verboseMode: false, // Whether it should log the requests in the console. 81 | }; 82 | 83 | const store = new StatsStore("atom", "1.24.1", false, getAccessToken, options); 84 | ``` 85 | 86 | All the option parameters are optional. 87 | 88 | ## Publishing a new release 89 | 90 | Follow [these instructions](https://docs.npmjs.com/getting-started/publishing-npm-packages) for releasing a new version with npm. In order for client apps to use a new version, bump the version of `telemetry-github` in the `package.json` file, and then run `npm install` again. 91 | 92 | ## License 93 | 94 | 95 | [MIT](https://github.com/atom/telemetry/blob/master/LICENSE) 96 | 97 | When using any GitHub logos, be sure to follow the [GitHub logo guidelines](https://github.com/logos). 98 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "telemetry-github", 3 | "version": "0.1.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@babel/code-frame": { 8 | "version": "7.0.0", 9 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", 10 | "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", 11 | "dev": true, 12 | "requires": { 13 | "@babel/highlight": "^7.0.0" 14 | } 15 | }, 16 | "@babel/highlight": { 17 | "version": "7.0.0", 18 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", 19 | "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", 20 | "dev": true, 21 | "requires": { 22 | "chalk": "^2.0.0", 23 | "esutils": "^2.0.2", 24 | "js-tokens": "^4.0.0" 25 | } 26 | }, 27 | "@sinonjs/formatio": { 28 | "version": "2.0.0", 29 | "resolved": "http://registry.npmjs.org/@sinonjs/formatio/-/formatio-2.0.0.tgz", 30 | "integrity": "sha512-ls6CAMA6/5gG+O/IdsBcblvnd8qcO/l1TYoNeAzp3wcISOxlPXQEus0mLcdwazEkWjaBdaJ3TaxmNgCLWwvWzg==", 31 | "dev": true, 32 | "requires": { 33 | "samsam": "1.3.0" 34 | } 35 | }, 36 | "@types/chai": { 37 | "version": "4.1.3", 38 | "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.3.tgz", 39 | "integrity": "sha512-f5dXGzOJycyzSMdaXVhiBhauL4dYydXwVpavfQ1mVCaGjR56a9QfklXObUxlIY9bGTmCPHEEZ04I16BZ/8w5ww==", 40 | "dev": true 41 | }, 42 | "@types/chai-as-promised": { 43 | "version": "7.1.0", 44 | "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.0.tgz", 45 | "integrity": "sha512-MFiW54UOSt+f2bRw8J7LgQeIvE/9b4oGvwU7XW30S9QGAiHGnU/fmiOprsyMkdmH2rl8xSPc0/yrQw8juXU6bQ==", 46 | "dev": true, 47 | "requires": { 48 | "@types/chai": "*" 49 | } 50 | }, 51 | "@types/lokijs": { 52 | "version": "1.5.2", 53 | "resolved": "https://registry.npmjs.org/@types/lokijs/-/lokijs-1.5.2.tgz", 54 | "integrity": "sha512-ZF14v1P1Bjbw8VJRu+p4WS9V926CAOjWF4yq23QmSBWRPe0/GXlUKzSxjP1fi/xi8nrq6zr9ECo8Z/8KsRqroQ==", 55 | "dev": true 56 | }, 57 | "@types/mocha": { 58 | "version": "5.2.0", 59 | "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.0.tgz", 60 | "integrity": "sha512-YeDiSEzznwZwwp766SJ6QlrTyBYUGPSIwmREHVTmktUYiT/WADdWtpt9iH0KuUSf8lZLdI4lP0X6PBzPo5//JQ==", 61 | "dev": true 62 | }, 63 | "@types/node": { 64 | "version": "10.0.3", 65 | "resolved": "https://registry.npmjs.org/@types/node/-/node-10.0.3.tgz", 66 | "integrity": "sha512-J7nx6JzxmtT4zyvfLipYL7jNaxvlCWpyG7JhhCQ4fQyG+AGfovAHoYR55TFx+X8akfkUJYpt5JG6GPeFMjZaCQ==", 67 | "dev": true 68 | }, 69 | "@types/sinon": { 70 | "version": "4.3.1", 71 | "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-4.3.1.tgz", 72 | "integrity": "sha512-DK4YtH30I67k4klURIBS4VAe1aBISfS9lgNlHFkibSmKem2tLQc5VkKoJreT3dCJAd+xRyCS8bx1o97iq3yUVg==", 73 | "dev": true 74 | }, 75 | "@types/uuid": { 76 | "version": "3.4.3", 77 | "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.3.tgz", 78 | "integrity": "sha512-5fRLCYhLtDb3hMWqQyH10qtF+Ud2JnNCXTCZ+9ktNdCcgslcuXkDTkFcJNk++MT29yDntDnlF1+jD+uVGumsbw==", 79 | "dev": true, 80 | "requires": { 81 | "@types/node": "*" 82 | } 83 | }, 84 | "ajv": { 85 | "version": "6.10.0", 86 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", 87 | "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", 88 | "dev": true, 89 | "requires": { 90 | "fast-deep-equal": "^2.0.1", 91 | "fast-json-stable-stringify": "^2.0.0", 92 | "json-schema-traverse": "^0.4.1", 93 | "uri-js": "^4.2.2" 94 | } 95 | }, 96 | "ansi-colors": { 97 | "version": "3.2.4", 98 | "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", 99 | "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", 100 | "dev": true 101 | }, 102 | "ansi-regex": { 103 | "version": "3.0.0", 104 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", 105 | "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", 106 | "dev": true 107 | }, 108 | "ansi-styles": { 109 | "version": "3.2.1", 110 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 111 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 112 | "dev": true, 113 | "requires": { 114 | "color-convert": "^1.9.0" 115 | } 116 | }, 117 | "argparse": { 118 | "version": "1.0.10", 119 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 120 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 121 | "dev": true, 122 | "requires": { 123 | "sprintf-js": "~1.0.2" 124 | } 125 | }, 126 | "array-find-index": { 127 | "version": "1.0.2", 128 | "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", 129 | "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", 130 | "dev": true 131 | }, 132 | "arrify": { 133 | "version": "1.0.1", 134 | "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", 135 | "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", 136 | "dev": true 137 | }, 138 | "asn1": { 139 | "version": "0.2.4", 140 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", 141 | "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", 142 | "dev": true, 143 | "requires": { 144 | "safer-buffer": "~2.1.0" 145 | } 146 | }, 147 | "assert-plus": { 148 | "version": "1.0.0", 149 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 150 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", 151 | "dev": true 152 | }, 153 | "assertion-error": { 154 | "version": "1.1.0", 155 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", 156 | "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", 157 | "dev": true 158 | }, 159 | "asynckit": { 160 | "version": "0.4.0", 161 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 162 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", 163 | "dev": true 164 | }, 165 | "aws-sign2": { 166 | "version": "0.7.0", 167 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 168 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", 169 | "dev": true 170 | }, 171 | "aws4": { 172 | "version": "1.8.0", 173 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", 174 | "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", 175 | "dev": true 176 | }, 177 | "balanced-match": { 178 | "version": "1.0.0", 179 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 180 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 181 | "dev": true 182 | }, 183 | "bcrypt-pbkdf": { 184 | "version": "1.0.2", 185 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", 186 | "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", 187 | "dev": true, 188 | "requires": { 189 | "tweetnacl": "^0.14.3" 190 | } 191 | }, 192 | "brace-expansion": { 193 | "version": "1.1.11", 194 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 195 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 196 | "dev": true, 197 | "requires": { 198 | "balanced-match": "^1.0.0", 199 | "concat-map": "0.0.1" 200 | } 201 | }, 202 | "browser-stdout": { 203 | "version": "1.3.1", 204 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", 205 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", 206 | "dev": true 207 | }, 208 | "buffer-from": { 209 | "version": "1.0.0", 210 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz", 211 | "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==", 212 | "dev": true 213 | }, 214 | "builtin-modules": { 215 | "version": "1.1.1", 216 | "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", 217 | "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", 218 | "dev": true 219 | }, 220 | "camelcase": { 221 | "version": "5.3.1", 222 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", 223 | "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", 224 | "dev": true 225 | }, 226 | "camelcase-keys": { 227 | "version": "2.1.0", 228 | "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", 229 | "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", 230 | "dev": true, 231 | "requires": { 232 | "camelcase": "^2.0.0", 233 | "map-obj": "^1.0.0" 234 | }, 235 | "dependencies": { 236 | "camelcase": { 237 | "version": "2.1.1", 238 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", 239 | "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", 240 | "dev": true 241 | } 242 | } 243 | }, 244 | "caseless": { 245 | "version": "0.12.0", 246 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 247 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", 248 | "dev": true 249 | }, 250 | "chai": { 251 | "version": "4.1.2", 252 | "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", 253 | "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", 254 | "dev": true, 255 | "requires": { 256 | "assertion-error": "^1.0.1", 257 | "check-error": "^1.0.1", 258 | "deep-eql": "^3.0.0", 259 | "get-func-name": "^2.0.0", 260 | "pathval": "^1.0.0", 261 | "type-detect": "^4.0.0" 262 | } 263 | }, 264 | "chai-as-promised": { 265 | "version": "7.1.1", 266 | "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", 267 | "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", 268 | "dev": true, 269 | "requires": { 270 | "check-error": "^1.0.2" 271 | } 272 | }, 273 | "chalk": { 274 | "version": "2.4.1", 275 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", 276 | "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", 277 | "dev": true, 278 | "requires": { 279 | "ansi-styles": "^3.2.1", 280 | "escape-string-regexp": "^1.0.5", 281 | "supports-color": "^5.3.0" 282 | }, 283 | "dependencies": { 284 | "has-flag": { 285 | "version": "3.0.0", 286 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 287 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 288 | "dev": true 289 | }, 290 | "supports-color": { 291 | "version": "5.4.0", 292 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", 293 | "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", 294 | "dev": true, 295 | "requires": { 296 | "has-flag": "^3.0.0" 297 | } 298 | } 299 | } 300 | }, 301 | "check-error": { 302 | "version": "1.0.2", 303 | "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", 304 | "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", 305 | "dev": true 306 | }, 307 | "cliui": { 308 | "version": "4.1.0", 309 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", 310 | "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", 311 | "dev": true, 312 | "requires": { 313 | "string-width": "^2.1.1", 314 | "strip-ansi": "^4.0.0", 315 | "wrap-ansi": "^2.0.0" 316 | } 317 | }, 318 | "code-point-at": { 319 | "version": "1.1.0", 320 | "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", 321 | "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", 322 | "dev": true 323 | }, 324 | "color-convert": { 325 | "version": "1.9.1", 326 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", 327 | "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", 328 | "dev": true, 329 | "requires": { 330 | "color-name": "^1.1.1" 331 | } 332 | }, 333 | "color-name": { 334 | "version": "1.1.3", 335 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 336 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 337 | "dev": true 338 | }, 339 | "combined-stream": { 340 | "version": "1.0.8", 341 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 342 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 343 | "dev": true, 344 | "requires": { 345 | "delayed-stream": "~1.0.0" 346 | } 347 | }, 348 | "commander": { 349 | "version": "2.11.0", 350 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", 351 | "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", 352 | "dev": true 353 | }, 354 | "concat-map": { 355 | "version": "0.0.1", 356 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 357 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 358 | "dev": true 359 | }, 360 | "concat-stream": { 361 | "version": "1.6.2", 362 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", 363 | "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", 364 | "dev": true, 365 | "requires": { 366 | "buffer-from": "^1.0.0", 367 | "inherits": "^2.0.3", 368 | "readable-stream": "^2.2.2", 369 | "typedarray": "^0.0.6" 370 | }, 371 | "dependencies": { 372 | "isarray": { 373 | "version": "1.0.0", 374 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 375 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", 376 | "dev": true 377 | }, 378 | "readable-stream": { 379 | "version": "2.3.6", 380 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", 381 | "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", 382 | "dev": true, 383 | "requires": { 384 | "core-util-is": "~1.0.0", 385 | "inherits": "~2.0.3", 386 | "isarray": "~1.0.0", 387 | "process-nextick-args": "~2.0.0", 388 | "safe-buffer": "~5.1.1", 389 | "string_decoder": "~1.1.1", 390 | "util-deprecate": "~1.0.1" 391 | } 392 | }, 393 | "string_decoder": { 394 | "version": "1.1.1", 395 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 396 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 397 | "dev": true, 398 | "requires": { 399 | "safe-buffer": "~5.1.0" 400 | } 401 | } 402 | } 403 | }, 404 | "core-util-is": { 405 | "version": "1.0.2", 406 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 407 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", 408 | "dev": true 409 | }, 410 | "cross-spawn": { 411 | "version": "6.0.5", 412 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", 413 | "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", 414 | "dev": true, 415 | "requires": { 416 | "nice-try": "^1.0.4", 417 | "path-key": "^2.0.1", 418 | "semver": "^5.5.0", 419 | "shebang-command": "^1.2.0", 420 | "which": "^1.2.9" 421 | } 422 | }, 423 | "currently-unhandled": { 424 | "version": "0.4.1", 425 | "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", 426 | "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", 427 | "dev": true, 428 | "requires": { 429 | "array-find-index": "^1.0.1" 430 | } 431 | }, 432 | "dashdash": { 433 | "version": "1.14.1", 434 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 435 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 436 | "dev": true, 437 | "requires": { 438 | "assert-plus": "^1.0.0" 439 | } 440 | }, 441 | "debug": { 442 | "version": "3.1.0", 443 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 444 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 445 | "dev": true, 446 | "requires": { 447 | "ms": "2.0.0" 448 | } 449 | }, 450 | "decamelize": { 451 | "version": "1.2.0", 452 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", 453 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", 454 | "dev": true 455 | }, 456 | "deep-eql": { 457 | "version": "3.0.1", 458 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", 459 | "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", 460 | "dev": true, 461 | "requires": { 462 | "type-detect": "^4.0.0" 463 | } 464 | }, 465 | "deep-extend": { 466 | "version": "0.6.0", 467 | "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", 468 | "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", 469 | "dev": true 470 | }, 471 | "define-properties": { 472 | "version": "1.1.3", 473 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", 474 | "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", 475 | "dev": true, 476 | "requires": { 477 | "object-keys": "^1.0.12" 478 | } 479 | }, 480 | "delayed-stream": { 481 | "version": "1.0.0", 482 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 483 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", 484 | "dev": true 485 | }, 486 | "diff": { 487 | "version": "3.5.0", 488 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", 489 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", 490 | "dev": true 491 | }, 492 | "ecc-jsbn": { 493 | "version": "0.1.2", 494 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", 495 | "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", 496 | "dev": true, 497 | "requires": { 498 | "jsbn": "~0.1.0", 499 | "safer-buffer": "^2.1.0" 500 | } 501 | }, 502 | "electron": { 503 | "version": "5.0.2", 504 | "resolved": "https://registry.npmjs.org/electron/-/electron-5.0.2.tgz", 505 | "integrity": "sha512-bUHKQhyuOen/q8iHTkrnzqB9CAwBDI+vHbeu21kpq2bqAD+t25yfrmUEcYHaPL4fZOAhk6nnRqskF6/Xd+aZxg==", 506 | "dev": true, 507 | "requires": { 508 | "@types/node": "^10.12.18", 509 | "electron-download": "^4.1.0", 510 | "extract-zip": "^1.0.3" 511 | }, 512 | "dependencies": { 513 | "@types/node": { 514 | "version": "10.14.7", 515 | "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.7.tgz", 516 | "integrity": "sha512-on4MmIDgHXiuJDELPk1NFaKVUxxCFr37tm8E9yN6rAiF5Pzp/9bBfBHkoexqRiY+hk/Z04EJU9kKEb59YqJ82A==", 517 | "dev": true 518 | } 519 | } 520 | }, 521 | "electron-download": { 522 | "version": "4.1.1", 523 | "resolved": "https://registry.npmjs.org/electron-download/-/electron-download-4.1.1.tgz", 524 | "integrity": "sha512-FjEWG9Jb/ppK/2zToP+U5dds114fM1ZOJqMAR4aXXL5CvyPE9fiqBK/9YcwC9poIFQTEJk/EM/zyRwziziRZrg==", 525 | "dev": true, 526 | "requires": { 527 | "debug": "^3.0.0", 528 | "env-paths": "^1.0.0", 529 | "fs-extra": "^4.0.1", 530 | "minimist": "^1.2.0", 531 | "nugget": "^2.0.1", 532 | "path-exists": "^3.0.0", 533 | "rc": "^1.2.1", 534 | "semver": "^5.4.1", 535 | "sumchecker": "^2.0.2" 536 | }, 537 | "dependencies": { 538 | "fs-extra": { 539 | "version": "4.0.3", 540 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", 541 | "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", 542 | "dev": true, 543 | "requires": { 544 | "graceful-fs": "^4.1.2", 545 | "jsonfile": "^4.0.0", 546 | "universalify": "^0.1.0" 547 | } 548 | }, 549 | "minimist": { 550 | "version": "1.2.0", 551 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 552 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", 553 | "dev": true 554 | } 555 | } 556 | }, 557 | "electron-mocha": { 558 | "version": "8.0.2", 559 | "resolved": "https://registry.npmjs.org/electron-mocha/-/electron-mocha-8.0.2.tgz", 560 | "integrity": "sha512-IlqYToYJjo8vuqbN3SJoDPvNcPGsG1nvOth3UzHGnd3SRVl5cLuSFJWxYJK7hP5ZZQiMFwmJ1J0ldaPBlZuYZA==", 561 | "dev": true, 562 | "requires": { 563 | "ansi-colors": "^3.2.4", 564 | "electron-window": "^0.8.0", 565 | "fs-extra": "^7.0.1", 566 | "log-symbols": "^2.2.0", 567 | "mocha": "^6.1.2", 568 | "which": "^1.3.1", 569 | "yargs": "^13.2.2" 570 | }, 571 | "dependencies": { 572 | "ansi-regex": { 573 | "version": "4.1.0", 574 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", 575 | "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", 576 | "dev": true 577 | }, 578 | "debug": { 579 | "version": "3.2.6", 580 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", 581 | "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", 582 | "dev": true, 583 | "requires": { 584 | "ms": "^2.1.1" 585 | } 586 | }, 587 | "glob": { 588 | "version": "7.1.3", 589 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", 590 | "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", 591 | "dev": true, 592 | "requires": { 593 | "fs.realpath": "^1.0.0", 594 | "inflight": "^1.0.4", 595 | "inherits": "2", 596 | "minimatch": "^3.0.4", 597 | "once": "^1.3.0", 598 | "path-is-absolute": "^1.0.0" 599 | } 600 | }, 601 | "growl": { 602 | "version": "1.10.5", 603 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", 604 | "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", 605 | "dev": true 606 | }, 607 | "has-flag": { 608 | "version": "3.0.0", 609 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 610 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 611 | "dev": true 612 | }, 613 | "he": { 614 | "version": "1.2.0", 615 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", 616 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", 617 | "dev": true 618 | }, 619 | "mocha": { 620 | "version": "6.1.4", 621 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.1.4.tgz", 622 | "integrity": "sha512-PN8CIy4RXsIoxoFJzS4QNnCH4psUCPWc4/rPrst/ecSJJbLBkubMiyGCP2Kj/9YnWbotFqAoeXyXMucj7gwCFg==", 623 | "dev": true, 624 | "requires": { 625 | "ansi-colors": "3.2.3", 626 | "browser-stdout": "1.3.1", 627 | "debug": "3.2.6", 628 | "diff": "3.5.0", 629 | "escape-string-regexp": "1.0.5", 630 | "find-up": "3.0.0", 631 | "glob": "7.1.3", 632 | "growl": "1.10.5", 633 | "he": "1.2.0", 634 | "js-yaml": "3.13.1", 635 | "log-symbols": "2.2.0", 636 | "minimatch": "3.0.4", 637 | "mkdirp": "0.5.1", 638 | "ms": "2.1.1", 639 | "node-environment-flags": "1.0.5", 640 | "object.assign": "4.1.0", 641 | "strip-json-comments": "2.0.1", 642 | "supports-color": "6.0.0", 643 | "which": "1.3.1", 644 | "wide-align": "1.1.3", 645 | "yargs": "13.2.2", 646 | "yargs-parser": "13.0.0", 647 | "yargs-unparser": "1.5.0" 648 | }, 649 | "dependencies": { 650 | "ansi-colors": { 651 | "version": "3.2.3", 652 | "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", 653 | "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", 654 | "dev": true 655 | }, 656 | "yargs": { 657 | "version": "13.2.2", 658 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz", 659 | "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==", 660 | "dev": true, 661 | "requires": { 662 | "cliui": "^4.0.0", 663 | "find-up": "^3.0.0", 664 | "get-caller-file": "^2.0.1", 665 | "os-locale": "^3.1.0", 666 | "require-directory": "^2.1.1", 667 | "require-main-filename": "^2.0.0", 668 | "set-blocking": "^2.0.0", 669 | "string-width": "^3.0.0", 670 | "which-module": "^2.0.0", 671 | "y18n": "^4.0.0", 672 | "yargs-parser": "^13.0.0" 673 | } 674 | } 675 | } 676 | }, 677 | "ms": { 678 | "version": "2.1.1", 679 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 680 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", 681 | "dev": true 682 | }, 683 | "string-width": { 684 | "version": "3.1.0", 685 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", 686 | "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", 687 | "dev": true, 688 | "requires": { 689 | "emoji-regex": "^7.0.1", 690 | "is-fullwidth-code-point": "^2.0.0", 691 | "strip-ansi": "^5.1.0" 692 | } 693 | }, 694 | "strip-ansi": { 695 | "version": "5.2.0", 696 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", 697 | "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", 698 | "dev": true, 699 | "requires": { 700 | "ansi-regex": "^4.1.0" 701 | } 702 | }, 703 | "supports-color": { 704 | "version": "6.0.0", 705 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", 706 | "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", 707 | "dev": true, 708 | "requires": { 709 | "has-flag": "^3.0.0" 710 | } 711 | } 712 | } 713 | }, 714 | "electron-window": { 715 | "version": "0.8.1", 716 | "resolved": "https://registry.npmjs.org/electron-window/-/electron-window-0.8.1.tgz", 717 | "integrity": "sha1-FsoYfrSHCwZ5J0/IKZxZYOarLF4=", 718 | "dev": true, 719 | "requires": { 720 | "is-electron-renderer": "^2.0.0" 721 | } 722 | }, 723 | "emoji-regex": { 724 | "version": "7.0.3", 725 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", 726 | "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", 727 | "dev": true 728 | }, 729 | "end-of-stream": { 730 | "version": "1.4.1", 731 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", 732 | "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", 733 | "dev": true, 734 | "requires": { 735 | "once": "^1.4.0" 736 | } 737 | }, 738 | "env-paths": { 739 | "version": "1.0.0", 740 | "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-1.0.0.tgz", 741 | "integrity": "sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA=", 742 | "dev": true 743 | }, 744 | "error-ex": { 745 | "version": "1.3.2", 746 | "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", 747 | "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", 748 | "dev": true, 749 | "requires": { 750 | "is-arrayish": "^0.2.1" 751 | } 752 | }, 753 | "es-abstract": { 754 | "version": "1.13.0", 755 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", 756 | "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", 757 | "dev": true, 758 | "requires": { 759 | "es-to-primitive": "^1.2.0", 760 | "function-bind": "^1.1.1", 761 | "has": "^1.0.3", 762 | "is-callable": "^1.1.4", 763 | "is-regex": "^1.0.4", 764 | "object-keys": "^1.0.12" 765 | } 766 | }, 767 | "es-to-primitive": { 768 | "version": "1.2.0", 769 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", 770 | "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", 771 | "dev": true, 772 | "requires": { 773 | "is-callable": "^1.1.4", 774 | "is-date-object": "^1.0.1", 775 | "is-symbol": "^1.0.2" 776 | } 777 | }, 778 | "escape-string-regexp": { 779 | "version": "1.0.5", 780 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 781 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 782 | "dev": true 783 | }, 784 | "esprima": { 785 | "version": "4.0.1", 786 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 787 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", 788 | "dev": true 789 | }, 790 | "esutils": { 791 | "version": "2.0.2", 792 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", 793 | "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", 794 | "dev": true 795 | }, 796 | "execa": { 797 | "version": "1.0.0", 798 | "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", 799 | "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", 800 | "dev": true, 801 | "requires": { 802 | "cross-spawn": "^6.0.0", 803 | "get-stream": "^4.0.0", 804 | "is-stream": "^1.1.0", 805 | "npm-run-path": "^2.0.0", 806 | "p-finally": "^1.0.0", 807 | "signal-exit": "^3.0.0", 808 | "strip-eof": "^1.0.0" 809 | } 810 | }, 811 | "extend": { 812 | "version": "3.0.2", 813 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 814 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", 815 | "dev": true 816 | }, 817 | "extract-zip": { 818 | "version": "1.6.7", 819 | "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", 820 | "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=", 821 | "dev": true, 822 | "requires": { 823 | "concat-stream": "1.6.2", 824 | "debug": "2.6.9", 825 | "mkdirp": "0.5.1", 826 | "yauzl": "2.4.1" 827 | }, 828 | "dependencies": { 829 | "debug": { 830 | "version": "2.6.9", 831 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 832 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 833 | "dev": true, 834 | "requires": { 835 | "ms": "2.0.0" 836 | } 837 | } 838 | } 839 | }, 840 | "extsprintf": { 841 | "version": "1.3.0", 842 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 843 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", 844 | "dev": true 845 | }, 846 | "fast-deep-equal": { 847 | "version": "2.0.1", 848 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", 849 | "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", 850 | "dev": true 851 | }, 852 | "fast-json-stable-stringify": { 853 | "version": "2.0.0", 854 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", 855 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", 856 | "dev": true 857 | }, 858 | "fd-slicer": { 859 | "version": "1.0.1", 860 | "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", 861 | "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", 862 | "dev": true, 863 | "requires": { 864 | "pend": "~1.2.0" 865 | } 866 | }, 867 | "find-up": { 868 | "version": "3.0.0", 869 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", 870 | "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", 871 | "dev": true, 872 | "requires": { 873 | "locate-path": "^3.0.0" 874 | } 875 | }, 876 | "flat": { 877 | "version": "4.1.0", 878 | "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", 879 | "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", 880 | "dev": true, 881 | "requires": { 882 | "is-buffer": "~2.0.3" 883 | } 884 | }, 885 | "forever-agent": { 886 | "version": "0.6.1", 887 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 888 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", 889 | "dev": true 890 | }, 891 | "form-data": { 892 | "version": "2.3.3", 893 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", 894 | "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", 895 | "dev": true, 896 | "requires": { 897 | "asynckit": "^0.4.0", 898 | "combined-stream": "^1.0.6", 899 | "mime-types": "^2.1.12" 900 | } 901 | }, 902 | "fs-extra": { 903 | "version": "7.0.1", 904 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", 905 | "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", 906 | "dev": true, 907 | "requires": { 908 | "graceful-fs": "^4.1.2", 909 | "jsonfile": "^4.0.0", 910 | "universalify": "^0.1.0" 911 | } 912 | }, 913 | "fs.realpath": { 914 | "version": "1.0.0", 915 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 916 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 917 | "dev": true 918 | }, 919 | "function-bind": { 920 | "version": "1.1.1", 921 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 922 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 923 | "dev": true 924 | }, 925 | "get-caller-file": { 926 | "version": "2.0.5", 927 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 928 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", 929 | "dev": true 930 | }, 931 | "get-func-name": { 932 | "version": "2.0.0", 933 | "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", 934 | "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", 935 | "dev": true 936 | }, 937 | "get-stdin": { 938 | "version": "4.0.1", 939 | "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", 940 | "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", 941 | "dev": true 942 | }, 943 | "get-stream": { 944 | "version": "4.1.0", 945 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", 946 | "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", 947 | "dev": true, 948 | "requires": { 949 | "pump": "^3.0.0" 950 | } 951 | }, 952 | "getpass": { 953 | "version": "0.1.7", 954 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 955 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 956 | "dev": true, 957 | "requires": { 958 | "assert-plus": "^1.0.0" 959 | } 960 | }, 961 | "glob": { 962 | "version": "7.1.2", 963 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 964 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 965 | "dev": true, 966 | "requires": { 967 | "fs.realpath": "^1.0.0", 968 | "inflight": "^1.0.4", 969 | "inherits": "2", 970 | "minimatch": "^3.0.4", 971 | "once": "^1.3.0", 972 | "path-is-absolute": "^1.0.0" 973 | } 974 | }, 975 | "graceful-fs": { 976 | "version": "4.1.15", 977 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", 978 | "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", 979 | "dev": true 980 | }, 981 | "growl": { 982 | "version": "1.10.3", 983 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", 984 | "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", 985 | "dev": true 986 | }, 987 | "har-schema": { 988 | "version": "2.0.0", 989 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", 990 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", 991 | "dev": true 992 | }, 993 | "har-validator": { 994 | "version": "5.1.3", 995 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", 996 | "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", 997 | "dev": true, 998 | "requires": { 999 | "ajv": "^6.5.5", 1000 | "har-schema": "^2.0.0" 1001 | } 1002 | }, 1003 | "has": { 1004 | "version": "1.0.3", 1005 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 1006 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 1007 | "dev": true, 1008 | "requires": { 1009 | "function-bind": "^1.1.1" 1010 | } 1011 | }, 1012 | "has-flag": { 1013 | "version": "2.0.0", 1014 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", 1015 | "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", 1016 | "dev": true 1017 | }, 1018 | "has-symbols": { 1019 | "version": "1.0.0", 1020 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", 1021 | "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", 1022 | "dev": true 1023 | }, 1024 | "he": { 1025 | "version": "1.1.1", 1026 | "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", 1027 | "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", 1028 | "dev": true 1029 | }, 1030 | "hosted-git-info": { 1031 | "version": "2.7.1", 1032 | "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", 1033 | "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", 1034 | "dev": true 1035 | }, 1036 | "http-signature": { 1037 | "version": "1.2.0", 1038 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 1039 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", 1040 | "dev": true, 1041 | "requires": { 1042 | "assert-plus": "^1.0.0", 1043 | "jsprim": "^1.2.2", 1044 | "sshpk": "^1.7.0" 1045 | } 1046 | }, 1047 | "idb": { 1048 | "version": "4.0.3", 1049 | "resolved": "https://registry.npmjs.org/idb/-/idb-4.0.3.tgz", 1050 | "integrity": "sha512-moRlNNe0Gsvp4jAwz5cJ7scjyNTVA/cESKGCobULaljfaKZ970y8NDNCseHdMY+YxNXH58Z1V+7tTyf0GZyKqw==" 1051 | }, 1052 | "indent-string": { 1053 | "version": "2.1.0", 1054 | "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", 1055 | "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", 1056 | "dev": true, 1057 | "requires": { 1058 | "repeating": "^2.0.0" 1059 | } 1060 | }, 1061 | "inflight": { 1062 | "version": "1.0.6", 1063 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 1064 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 1065 | "dev": true, 1066 | "requires": { 1067 | "once": "^1.3.0", 1068 | "wrappy": "1" 1069 | } 1070 | }, 1071 | "inherits": { 1072 | "version": "2.0.3", 1073 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 1074 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 1075 | "dev": true 1076 | }, 1077 | "ini": { 1078 | "version": "1.3.5", 1079 | "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", 1080 | "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", 1081 | "dev": true 1082 | }, 1083 | "invert-kv": { 1084 | "version": "2.0.0", 1085 | "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", 1086 | "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", 1087 | "dev": true 1088 | }, 1089 | "is-arrayish": { 1090 | "version": "0.2.1", 1091 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", 1092 | "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", 1093 | "dev": true 1094 | }, 1095 | "is-buffer": { 1096 | "version": "2.0.3", 1097 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", 1098 | "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==", 1099 | "dev": true 1100 | }, 1101 | "is-callable": { 1102 | "version": "1.1.4", 1103 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", 1104 | "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", 1105 | "dev": true 1106 | }, 1107 | "is-date-object": { 1108 | "version": "1.0.1", 1109 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", 1110 | "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", 1111 | "dev": true 1112 | }, 1113 | "is-electron-renderer": { 1114 | "version": "2.0.1", 1115 | "resolved": "https://registry.npmjs.org/is-electron-renderer/-/is-electron-renderer-2.0.1.tgz", 1116 | "integrity": "sha1-pGnQVvl1aXxYyYxgI+sKp5r4laI=", 1117 | "dev": true 1118 | }, 1119 | "is-finite": { 1120 | "version": "1.0.2", 1121 | "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", 1122 | "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", 1123 | "dev": true, 1124 | "requires": { 1125 | "number-is-nan": "^1.0.0" 1126 | } 1127 | }, 1128 | "is-fullwidth-code-point": { 1129 | "version": "2.0.0", 1130 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", 1131 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", 1132 | "dev": true 1133 | }, 1134 | "is-regex": { 1135 | "version": "1.0.4", 1136 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", 1137 | "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", 1138 | "dev": true, 1139 | "requires": { 1140 | "has": "^1.0.1" 1141 | } 1142 | }, 1143 | "is-stream": { 1144 | "version": "1.1.0", 1145 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", 1146 | "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", 1147 | "dev": true 1148 | }, 1149 | "is-symbol": { 1150 | "version": "1.0.2", 1151 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", 1152 | "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", 1153 | "dev": true, 1154 | "requires": { 1155 | "has-symbols": "^1.0.0" 1156 | } 1157 | }, 1158 | "is-typedarray": { 1159 | "version": "1.0.0", 1160 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 1161 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", 1162 | "dev": true 1163 | }, 1164 | "is-utf8": { 1165 | "version": "0.2.1", 1166 | "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", 1167 | "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", 1168 | "dev": true 1169 | }, 1170 | "isarray": { 1171 | "version": "0.0.1", 1172 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 1173 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", 1174 | "dev": true 1175 | }, 1176 | "isexe": { 1177 | "version": "2.0.0", 1178 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 1179 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", 1180 | "dev": true 1181 | }, 1182 | "isstream": { 1183 | "version": "0.1.2", 1184 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 1185 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", 1186 | "dev": true 1187 | }, 1188 | "js-tokens": { 1189 | "version": "4.0.0", 1190 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 1191 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 1192 | "dev": true 1193 | }, 1194 | "js-yaml": { 1195 | "version": "3.13.1", 1196 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", 1197 | "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", 1198 | "dev": true, 1199 | "requires": { 1200 | "argparse": "^1.0.7", 1201 | "esprima": "^4.0.0" 1202 | } 1203 | }, 1204 | "jsbn": { 1205 | "version": "0.1.1", 1206 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 1207 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", 1208 | "dev": true 1209 | }, 1210 | "json-schema": { 1211 | "version": "0.2.3", 1212 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", 1213 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", 1214 | "dev": true 1215 | }, 1216 | "json-schema-traverse": { 1217 | "version": "0.4.1", 1218 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 1219 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 1220 | "dev": true 1221 | }, 1222 | "json-stringify-safe": { 1223 | "version": "5.0.1", 1224 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 1225 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", 1226 | "dev": true 1227 | }, 1228 | "jsonfile": { 1229 | "version": "4.0.0", 1230 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", 1231 | "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", 1232 | "dev": true, 1233 | "requires": { 1234 | "graceful-fs": "^4.1.6" 1235 | } 1236 | }, 1237 | "jsprim": { 1238 | "version": "1.4.1", 1239 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", 1240 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", 1241 | "dev": true, 1242 | "requires": { 1243 | "assert-plus": "1.0.0", 1244 | "extsprintf": "1.3.0", 1245 | "json-schema": "0.2.3", 1246 | "verror": "1.10.0" 1247 | } 1248 | }, 1249 | "just-extend": { 1250 | "version": "1.1.27", 1251 | "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-1.1.27.tgz", 1252 | "integrity": "sha512-mJVp13Ix6gFo3SBAy9U/kL+oeZqzlYYYLQBwXVBlVzIsZwBqGREnOro24oC/8s8aox+rJhtZ2DiQof++IrkA+g==", 1253 | "dev": true 1254 | }, 1255 | "lcid": { 1256 | "version": "2.0.0", 1257 | "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", 1258 | "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", 1259 | "dev": true, 1260 | "requires": { 1261 | "invert-kv": "^2.0.0" 1262 | } 1263 | }, 1264 | "load-json-file": { 1265 | "version": "1.1.0", 1266 | "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", 1267 | "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", 1268 | "dev": true, 1269 | "requires": { 1270 | "graceful-fs": "^4.1.2", 1271 | "parse-json": "^2.2.0", 1272 | "pify": "^2.0.0", 1273 | "pinkie-promise": "^2.0.0", 1274 | "strip-bom": "^2.0.0" 1275 | } 1276 | }, 1277 | "locate-path": { 1278 | "version": "3.0.0", 1279 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", 1280 | "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", 1281 | "dev": true, 1282 | "requires": { 1283 | "p-locate": "^3.0.0", 1284 | "path-exists": "^3.0.0" 1285 | } 1286 | }, 1287 | "lodash": { 1288 | "version": "4.17.11", 1289 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", 1290 | "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", 1291 | "dev": true 1292 | }, 1293 | "lodash.get": { 1294 | "version": "4.4.2", 1295 | "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", 1296 | "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", 1297 | "dev": true 1298 | }, 1299 | "log-symbols": { 1300 | "version": "2.2.0", 1301 | "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", 1302 | "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", 1303 | "dev": true, 1304 | "requires": { 1305 | "chalk": "^2.0.1" 1306 | } 1307 | }, 1308 | "lokijs": { 1309 | "version": "1.5.4", 1310 | "resolved": "https://registry.npmjs.org/lokijs/-/lokijs-1.5.4.tgz", 1311 | "integrity": "sha1-RiJZXGvxdykwZjqzrVDR9im2zDA=" 1312 | }, 1313 | "lolex": { 1314 | "version": "2.3.2", 1315 | "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.3.2.tgz", 1316 | "integrity": "sha512-A5pN2tkFj7H0dGIAM6MFvHKMJcPnjZsOMvR7ujCjfgW5TbV6H9vb1PgxLtHvjqNZTHsUolz+6/WEO0N1xNx2ng==", 1317 | "dev": true 1318 | }, 1319 | "loud-rejection": { 1320 | "version": "1.6.0", 1321 | "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", 1322 | "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", 1323 | "dev": true, 1324 | "requires": { 1325 | "currently-unhandled": "^0.4.1", 1326 | "signal-exit": "^3.0.0" 1327 | } 1328 | }, 1329 | "make-error": { 1330 | "version": "1.3.4", 1331 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.4.tgz", 1332 | "integrity": "sha512-0Dab5btKVPhibSalc9QGXb559ED7G7iLjFXBaj9Wq8O3vorueR5K5jaE3hkG6ZQINyhA/JgG6Qk4qdFQjsYV6g==", 1333 | "dev": true 1334 | }, 1335 | "map-age-cleaner": { 1336 | "version": "0.1.3", 1337 | "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", 1338 | "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", 1339 | "dev": true, 1340 | "requires": { 1341 | "p-defer": "^1.0.0" 1342 | } 1343 | }, 1344 | "map-obj": { 1345 | "version": "1.0.1", 1346 | "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", 1347 | "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", 1348 | "dev": true 1349 | }, 1350 | "mem": { 1351 | "version": "4.3.0", 1352 | "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", 1353 | "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", 1354 | "dev": true, 1355 | "requires": { 1356 | "map-age-cleaner": "^0.1.1", 1357 | "mimic-fn": "^2.0.0", 1358 | "p-is-promise": "^2.0.0" 1359 | } 1360 | }, 1361 | "meow": { 1362 | "version": "3.7.0", 1363 | "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", 1364 | "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", 1365 | "dev": true, 1366 | "requires": { 1367 | "camelcase-keys": "^2.0.0", 1368 | "decamelize": "^1.1.2", 1369 | "loud-rejection": "^1.0.0", 1370 | "map-obj": "^1.0.1", 1371 | "minimist": "^1.1.3", 1372 | "normalize-package-data": "^2.3.4", 1373 | "object-assign": "^4.0.1", 1374 | "read-pkg-up": "^1.0.1", 1375 | "redent": "^1.0.0", 1376 | "trim-newlines": "^1.0.0" 1377 | }, 1378 | "dependencies": { 1379 | "minimist": { 1380 | "version": "1.2.0", 1381 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 1382 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", 1383 | "dev": true 1384 | } 1385 | } 1386 | }, 1387 | "mime-db": { 1388 | "version": "1.40.0", 1389 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", 1390 | "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", 1391 | "dev": true 1392 | }, 1393 | "mime-types": { 1394 | "version": "2.1.24", 1395 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", 1396 | "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", 1397 | "dev": true, 1398 | "requires": { 1399 | "mime-db": "1.40.0" 1400 | } 1401 | }, 1402 | "mimic-fn": { 1403 | "version": "2.1.0", 1404 | "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", 1405 | "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", 1406 | "dev": true 1407 | }, 1408 | "minimatch": { 1409 | "version": "3.0.4", 1410 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 1411 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 1412 | "dev": true, 1413 | "requires": { 1414 | "brace-expansion": "^1.1.7" 1415 | } 1416 | }, 1417 | "minimist": { 1418 | "version": "0.0.8", 1419 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 1420 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 1421 | "dev": true 1422 | }, 1423 | "mkdirp": { 1424 | "version": "0.5.1", 1425 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 1426 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 1427 | "dev": true, 1428 | "requires": { 1429 | "minimist": "0.0.8" 1430 | } 1431 | }, 1432 | "mocha": { 1433 | "version": "5.1.1", 1434 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.1.1.tgz", 1435 | "integrity": "sha512-kKKs/H1KrMMQIEsWNxGmb4/BGsmj0dkeyotEvbrAuQ01FcWRLssUNXCEUZk6SZtyJBi6EE7SL0zDDtItw1rGhw==", 1436 | "dev": true, 1437 | "requires": { 1438 | "browser-stdout": "1.3.1", 1439 | "commander": "2.11.0", 1440 | "debug": "3.1.0", 1441 | "diff": "3.5.0", 1442 | "escape-string-regexp": "1.0.5", 1443 | "glob": "7.1.2", 1444 | "growl": "1.10.3", 1445 | "he": "1.1.1", 1446 | "minimatch": "3.0.4", 1447 | "mkdirp": "0.5.1", 1448 | "supports-color": "4.4.0" 1449 | } 1450 | }, 1451 | "ms": { 1452 | "version": "2.0.0", 1453 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1454 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 1455 | "dev": true 1456 | }, 1457 | "nice-try": { 1458 | "version": "1.0.5", 1459 | "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", 1460 | "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", 1461 | "dev": true 1462 | }, 1463 | "nise": { 1464 | "version": "1.3.3", 1465 | "resolved": "https://registry.npmjs.org/nise/-/nise-1.3.3.tgz", 1466 | "integrity": "sha512-v1J/FLUB9PfGqZLGDBhQqODkbLotP0WtLo9R4EJY2PPu5f5Xg4o0rA8FDlmrjFSv9vBBKcfnOSpfYYuu5RTHqg==", 1467 | "dev": true, 1468 | "requires": { 1469 | "@sinonjs/formatio": "^2.0.0", 1470 | "just-extend": "^1.1.27", 1471 | "lolex": "^2.3.2", 1472 | "path-to-regexp": "^1.7.0", 1473 | "text-encoding": "^0.6.4" 1474 | } 1475 | }, 1476 | "node-environment-flags": { 1477 | "version": "1.0.5", 1478 | "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", 1479 | "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", 1480 | "dev": true, 1481 | "requires": { 1482 | "object.getownpropertydescriptors": "^2.0.3", 1483 | "semver": "^5.7.0" 1484 | } 1485 | }, 1486 | "normalize-package-data": { 1487 | "version": "2.5.0", 1488 | "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", 1489 | "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", 1490 | "dev": true, 1491 | "requires": { 1492 | "hosted-git-info": "^2.1.4", 1493 | "resolve": "^1.10.0", 1494 | "semver": "2 || 3 || 4 || 5", 1495 | "validate-npm-package-license": "^3.0.1" 1496 | } 1497 | }, 1498 | "npm-run-path": { 1499 | "version": "2.0.2", 1500 | "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", 1501 | "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", 1502 | "dev": true, 1503 | "requires": { 1504 | "path-key": "^2.0.0" 1505 | } 1506 | }, 1507 | "nugget": { 1508 | "version": "2.0.1", 1509 | "resolved": "https://registry.npmjs.org/nugget/-/nugget-2.0.1.tgz", 1510 | "integrity": "sha1-IBCVpIfhrTYIGzQy+jytpPjQcbA=", 1511 | "dev": true, 1512 | "requires": { 1513 | "debug": "^2.1.3", 1514 | "minimist": "^1.1.0", 1515 | "pretty-bytes": "^1.0.2", 1516 | "progress-stream": "^1.1.0", 1517 | "request": "^2.45.0", 1518 | "single-line-log": "^1.1.2", 1519 | "throttleit": "0.0.2" 1520 | }, 1521 | "dependencies": { 1522 | "debug": { 1523 | "version": "2.6.9", 1524 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 1525 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 1526 | "dev": true, 1527 | "requires": { 1528 | "ms": "2.0.0" 1529 | } 1530 | }, 1531 | "minimist": { 1532 | "version": "1.2.0", 1533 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 1534 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", 1535 | "dev": true 1536 | } 1537 | } 1538 | }, 1539 | "number-is-nan": { 1540 | "version": "1.0.1", 1541 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", 1542 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", 1543 | "dev": true 1544 | }, 1545 | "oauth-sign": { 1546 | "version": "0.9.0", 1547 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", 1548 | "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", 1549 | "dev": true 1550 | }, 1551 | "object-assign": { 1552 | "version": "4.1.1", 1553 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 1554 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", 1555 | "dev": true 1556 | }, 1557 | "object-keys": { 1558 | "version": "1.1.1", 1559 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", 1560 | "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", 1561 | "dev": true 1562 | }, 1563 | "object.assign": { 1564 | "version": "4.1.0", 1565 | "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", 1566 | "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", 1567 | "dev": true, 1568 | "requires": { 1569 | "define-properties": "^1.1.2", 1570 | "function-bind": "^1.1.1", 1571 | "has-symbols": "^1.0.0", 1572 | "object-keys": "^1.0.11" 1573 | } 1574 | }, 1575 | "object.getownpropertydescriptors": { 1576 | "version": "2.0.3", 1577 | "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", 1578 | "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", 1579 | "dev": true, 1580 | "requires": { 1581 | "define-properties": "^1.1.2", 1582 | "es-abstract": "^1.5.1" 1583 | } 1584 | }, 1585 | "once": { 1586 | "version": "1.4.0", 1587 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1588 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 1589 | "dev": true, 1590 | "requires": { 1591 | "wrappy": "1" 1592 | } 1593 | }, 1594 | "os-locale": { 1595 | "version": "3.1.0", 1596 | "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", 1597 | "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", 1598 | "dev": true, 1599 | "requires": { 1600 | "execa": "^1.0.0", 1601 | "lcid": "^2.0.0", 1602 | "mem": "^4.0.0" 1603 | } 1604 | }, 1605 | "p-defer": { 1606 | "version": "1.0.0", 1607 | "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", 1608 | "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", 1609 | "dev": true 1610 | }, 1611 | "p-finally": { 1612 | "version": "1.0.0", 1613 | "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", 1614 | "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", 1615 | "dev": true 1616 | }, 1617 | "p-is-promise": { 1618 | "version": "2.1.0", 1619 | "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", 1620 | "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", 1621 | "dev": true 1622 | }, 1623 | "p-limit": { 1624 | "version": "2.2.0", 1625 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", 1626 | "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", 1627 | "dev": true, 1628 | "requires": { 1629 | "p-try": "^2.0.0" 1630 | } 1631 | }, 1632 | "p-locate": { 1633 | "version": "3.0.0", 1634 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", 1635 | "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", 1636 | "dev": true, 1637 | "requires": { 1638 | "p-limit": "^2.0.0" 1639 | } 1640 | }, 1641 | "p-try": { 1642 | "version": "2.2.0", 1643 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", 1644 | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", 1645 | "dev": true 1646 | }, 1647 | "parse-json": { 1648 | "version": "2.2.0", 1649 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", 1650 | "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", 1651 | "dev": true, 1652 | "requires": { 1653 | "error-ex": "^1.2.0" 1654 | } 1655 | }, 1656 | "path-exists": { 1657 | "version": "3.0.0", 1658 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", 1659 | "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", 1660 | "dev": true 1661 | }, 1662 | "path-is-absolute": { 1663 | "version": "1.0.1", 1664 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1665 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 1666 | "dev": true 1667 | }, 1668 | "path-key": { 1669 | "version": "2.0.1", 1670 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", 1671 | "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", 1672 | "dev": true 1673 | }, 1674 | "path-parse": { 1675 | "version": "1.0.6", 1676 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 1677 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", 1678 | "dev": true 1679 | }, 1680 | "path-to-regexp": { 1681 | "version": "1.7.0", 1682 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", 1683 | "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", 1684 | "dev": true, 1685 | "requires": { 1686 | "isarray": "0.0.1" 1687 | } 1688 | }, 1689 | "path-type": { 1690 | "version": "1.1.0", 1691 | "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", 1692 | "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", 1693 | "dev": true, 1694 | "requires": { 1695 | "graceful-fs": "^4.1.2", 1696 | "pify": "^2.0.0", 1697 | "pinkie-promise": "^2.0.0" 1698 | } 1699 | }, 1700 | "pathval": { 1701 | "version": "1.1.0", 1702 | "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", 1703 | "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", 1704 | "dev": true 1705 | }, 1706 | "pend": { 1707 | "version": "1.2.0", 1708 | "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", 1709 | "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", 1710 | "dev": true 1711 | }, 1712 | "performance-now": { 1713 | "version": "2.1.0", 1714 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 1715 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", 1716 | "dev": true 1717 | }, 1718 | "pify": { 1719 | "version": "2.3.0", 1720 | "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", 1721 | "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", 1722 | "dev": true 1723 | }, 1724 | "pinkie": { 1725 | "version": "2.0.4", 1726 | "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", 1727 | "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", 1728 | "dev": true 1729 | }, 1730 | "pinkie-promise": { 1731 | "version": "2.0.1", 1732 | "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", 1733 | "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", 1734 | "dev": true, 1735 | "requires": { 1736 | "pinkie": "^2.0.0" 1737 | } 1738 | }, 1739 | "pretty-bytes": { 1740 | "version": "1.0.4", 1741 | "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-1.0.4.tgz", 1742 | "integrity": "sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ=", 1743 | "dev": true, 1744 | "requires": { 1745 | "get-stdin": "^4.0.1", 1746 | "meow": "^3.1.0" 1747 | } 1748 | }, 1749 | "process-nextick-args": { 1750 | "version": "2.0.0", 1751 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", 1752 | "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", 1753 | "dev": true 1754 | }, 1755 | "progress-stream": { 1756 | "version": "1.2.0", 1757 | "resolved": "https://registry.npmjs.org/progress-stream/-/progress-stream-1.2.0.tgz", 1758 | "integrity": "sha1-LNPP6jO6OonJwSHsM0er6asSX3c=", 1759 | "dev": true, 1760 | "requires": { 1761 | "speedometer": "~0.1.2", 1762 | "through2": "~0.2.3" 1763 | } 1764 | }, 1765 | "psl": { 1766 | "version": "1.1.32", 1767 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.32.tgz", 1768 | "integrity": "sha512-MHACAkHpihU/REGGPLj4sEfc/XKW2bheigvHO1dUqjaKigMp1C8+WLQYRGgeKFMsw5PMfegZcaN8IDXK/cD0+g==", 1769 | "dev": true 1770 | }, 1771 | "pump": { 1772 | "version": "3.0.0", 1773 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", 1774 | "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", 1775 | "dev": true, 1776 | "requires": { 1777 | "end-of-stream": "^1.1.0", 1778 | "once": "^1.3.1" 1779 | } 1780 | }, 1781 | "punycode": { 1782 | "version": "2.1.1", 1783 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 1784 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", 1785 | "dev": true 1786 | }, 1787 | "qs": { 1788 | "version": "6.5.2", 1789 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", 1790 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", 1791 | "dev": true 1792 | }, 1793 | "rc": { 1794 | "version": "1.2.8", 1795 | "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", 1796 | "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", 1797 | "dev": true, 1798 | "requires": { 1799 | "deep-extend": "^0.6.0", 1800 | "ini": "~1.3.0", 1801 | "minimist": "^1.2.0", 1802 | "strip-json-comments": "~2.0.1" 1803 | }, 1804 | "dependencies": { 1805 | "minimist": { 1806 | "version": "1.2.0", 1807 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 1808 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", 1809 | "dev": true 1810 | } 1811 | } 1812 | }, 1813 | "read-pkg": { 1814 | "version": "1.1.0", 1815 | "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", 1816 | "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", 1817 | "dev": true, 1818 | "requires": { 1819 | "load-json-file": "^1.0.0", 1820 | "normalize-package-data": "^2.3.2", 1821 | "path-type": "^1.0.0" 1822 | } 1823 | }, 1824 | "read-pkg-up": { 1825 | "version": "1.0.1", 1826 | "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", 1827 | "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", 1828 | "dev": true, 1829 | "requires": { 1830 | "find-up": "^1.0.0", 1831 | "read-pkg": "^1.0.0" 1832 | }, 1833 | "dependencies": { 1834 | "find-up": { 1835 | "version": "1.1.2", 1836 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", 1837 | "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", 1838 | "dev": true, 1839 | "requires": { 1840 | "path-exists": "^2.0.0", 1841 | "pinkie-promise": "^2.0.0" 1842 | } 1843 | }, 1844 | "path-exists": { 1845 | "version": "2.1.0", 1846 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", 1847 | "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", 1848 | "dev": true, 1849 | "requires": { 1850 | "pinkie-promise": "^2.0.0" 1851 | } 1852 | } 1853 | } 1854 | }, 1855 | "readable-stream": { 1856 | "version": "1.1.14", 1857 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", 1858 | "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", 1859 | "dev": true, 1860 | "requires": { 1861 | "core-util-is": "~1.0.0", 1862 | "inherits": "~2.0.1", 1863 | "isarray": "0.0.1", 1864 | "string_decoder": "~0.10.x" 1865 | } 1866 | }, 1867 | "redent": { 1868 | "version": "1.0.0", 1869 | "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", 1870 | "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", 1871 | "dev": true, 1872 | "requires": { 1873 | "indent-string": "^2.1.0", 1874 | "strip-indent": "^1.0.1" 1875 | } 1876 | }, 1877 | "repeating": { 1878 | "version": "2.0.1", 1879 | "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", 1880 | "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", 1881 | "dev": true, 1882 | "requires": { 1883 | "is-finite": "^1.0.0" 1884 | } 1885 | }, 1886 | "request": { 1887 | "version": "2.88.0", 1888 | "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", 1889 | "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", 1890 | "dev": true, 1891 | "requires": { 1892 | "aws-sign2": "~0.7.0", 1893 | "aws4": "^1.8.0", 1894 | "caseless": "~0.12.0", 1895 | "combined-stream": "~1.0.6", 1896 | "extend": "~3.0.2", 1897 | "forever-agent": "~0.6.1", 1898 | "form-data": "~2.3.2", 1899 | "har-validator": "~5.1.0", 1900 | "http-signature": "~1.2.0", 1901 | "is-typedarray": "~1.0.0", 1902 | "isstream": "~0.1.2", 1903 | "json-stringify-safe": "~5.0.1", 1904 | "mime-types": "~2.1.19", 1905 | "oauth-sign": "~0.9.0", 1906 | "performance-now": "^2.1.0", 1907 | "qs": "~6.5.2", 1908 | "safe-buffer": "^5.1.2", 1909 | "tough-cookie": "~2.4.3", 1910 | "tunnel-agent": "^0.6.0", 1911 | "uuid": "^3.3.2" 1912 | }, 1913 | "dependencies": { 1914 | "uuid": { 1915 | "version": "3.3.2", 1916 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", 1917 | "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", 1918 | "dev": true 1919 | } 1920 | } 1921 | }, 1922 | "require-directory": { 1923 | "version": "2.1.1", 1924 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 1925 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", 1926 | "dev": true 1927 | }, 1928 | "require-main-filename": { 1929 | "version": "2.0.0", 1930 | "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", 1931 | "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", 1932 | "dev": true 1933 | }, 1934 | "resolve": { 1935 | "version": "1.11.0", 1936 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.0.tgz", 1937 | "integrity": "sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw==", 1938 | "dev": true, 1939 | "requires": { 1940 | "path-parse": "^1.0.6" 1941 | } 1942 | }, 1943 | "safe-buffer": { 1944 | "version": "5.1.2", 1945 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 1946 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 1947 | "dev": true 1948 | }, 1949 | "safer-buffer": { 1950 | "version": "2.1.2", 1951 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1952 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 1953 | "dev": true 1954 | }, 1955 | "samsam": { 1956 | "version": "1.3.0", 1957 | "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", 1958 | "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", 1959 | "dev": true 1960 | }, 1961 | "semver": { 1962 | "version": "5.7.0", 1963 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", 1964 | "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", 1965 | "dev": true 1966 | }, 1967 | "set-blocking": { 1968 | "version": "2.0.0", 1969 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 1970 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", 1971 | "dev": true 1972 | }, 1973 | "shebang-command": { 1974 | "version": "1.2.0", 1975 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", 1976 | "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", 1977 | "dev": true, 1978 | "requires": { 1979 | "shebang-regex": "^1.0.0" 1980 | } 1981 | }, 1982 | "shebang-regex": { 1983 | "version": "1.0.0", 1984 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", 1985 | "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", 1986 | "dev": true 1987 | }, 1988 | "signal-exit": { 1989 | "version": "3.0.2", 1990 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", 1991 | "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", 1992 | "dev": true 1993 | }, 1994 | "single-line-log": { 1995 | "version": "1.1.2", 1996 | "resolved": "https://registry.npmjs.org/single-line-log/-/single-line-log-1.1.2.tgz", 1997 | "integrity": "sha1-wvg/Jzo+GhbtsJlWYdoO1e8DM2Q=", 1998 | "dev": true, 1999 | "requires": { 2000 | "string-width": "^1.0.1" 2001 | }, 2002 | "dependencies": { 2003 | "ansi-regex": { 2004 | "version": "2.1.1", 2005 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 2006 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", 2007 | "dev": true 2008 | }, 2009 | "is-fullwidth-code-point": { 2010 | "version": "1.0.0", 2011 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", 2012 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", 2013 | "dev": true, 2014 | "requires": { 2015 | "number-is-nan": "^1.0.0" 2016 | } 2017 | }, 2018 | "string-width": { 2019 | "version": "1.0.2", 2020 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", 2021 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", 2022 | "dev": true, 2023 | "requires": { 2024 | "code-point-at": "^1.0.0", 2025 | "is-fullwidth-code-point": "^1.0.0", 2026 | "strip-ansi": "^3.0.0" 2027 | } 2028 | }, 2029 | "strip-ansi": { 2030 | "version": "3.0.1", 2031 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 2032 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 2033 | "dev": true, 2034 | "requires": { 2035 | "ansi-regex": "^2.0.0" 2036 | } 2037 | } 2038 | } 2039 | }, 2040 | "sinon": { 2041 | "version": "5.0.3", 2042 | "resolved": "https://registry.npmjs.org/sinon/-/sinon-5.0.3.tgz", 2043 | "integrity": "sha512-kzBkET1Hf0r0J4uVnlicuAEiq9nnhPrEHZWS0mds+5EaB9rA0XoliIkLaqkBNU9lwPuJACo/velUQQOmTRJtUw==", 2044 | "dev": true, 2045 | "requires": { 2046 | "@sinonjs/formatio": "^2.0.0", 2047 | "diff": "^3.1.0", 2048 | "lodash.get": "^4.4.2", 2049 | "lolex": "^2.2.0", 2050 | "nise": "^1.2.0", 2051 | "supports-color": "^5.1.0", 2052 | "type-detect": "^4.0.5" 2053 | }, 2054 | "dependencies": { 2055 | "has-flag": { 2056 | "version": "3.0.0", 2057 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 2058 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 2059 | "dev": true 2060 | }, 2061 | "supports-color": { 2062 | "version": "5.4.0", 2063 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", 2064 | "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", 2065 | "dev": true, 2066 | "requires": { 2067 | "has-flag": "^3.0.0" 2068 | } 2069 | } 2070 | } 2071 | }, 2072 | "source-map": { 2073 | "version": "0.6.1", 2074 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 2075 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 2076 | "dev": true 2077 | }, 2078 | "source-map-support": { 2079 | "version": "0.5.5", 2080 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.5.tgz", 2081 | "integrity": "sha512-mR7/Nd5l1z6g99010shcXJiNEaf3fEtmLhRB/sBcQVJGodcHCULPp2y4Sfa43Kv2zq7T+Izmfp/WHCR6dYkQCA==", 2082 | "dev": true, 2083 | "requires": { 2084 | "buffer-from": "^1.0.0", 2085 | "source-map": "^0.6.0" 2086 | } 2087 | }, 2088 | "spdx-correct": { 2089 | "version": "3.1.0", 2090 | "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", 2091 | "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", 2092 | "dev": true, 2093 | "requires": { 2094 | "spdx-expression-parse": "^3.0.0", 2095 | "spdx-license-ids": "^3.0.0" 2096 | } 2097 | }, 2098 | "spdx-exceptions": { 2099 | "version": "2.2.0", 2100 | "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", 2101 | "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", 2102 | "dev": true 2103 | }, 2104 | "spdx-expression-parse": { 2105 | "version": "3.0.0", 2106 | "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", 2107 | "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", 2108 | "dev": true, 2109 | "requires": { 2110 | "spdx-exceptions": "^2.1.0", 2111 | "spdx-license-ids": "^3.0.0" 2112 | } 2113 | }, 2114 | "spdx-license-ids": { 2115 | "version": "3.0.4", 2116 | "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", 2117 | "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==", 2118 | "dev": true 2119 | }, 2120 | "speedometer": { 2121 | "version": "0.1.4", 2122 | "resolved": "https://registry.npmjs.org/speedometer/-/speedometer-0.1.4.tgz", 2123 | "integrity": "sha1-mHbb0qFp0xFUAtSObqYynIgWpQ0=", 2124 | "dev": true 2125 | }, 2126 | "sprintf-js": { 2127 | "version": "1.0.3", 2128 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 2129 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", 2130 | "dev": true 2131 | }, 2132 | "sshpk": { 2133 | "version": "1.16.1", 2134 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", 2135 | "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", 2136 | "dev": true, 2137 | "requires": { 2138 | "asn1": "~0.2.3", 2139 | "assert-plus": "^1.0.0", 2140 | "bcrypt-pbkdf": "^1.0.0", 2141 | "dashdash": "^1.12.0", 2142 | "ecc-jsbn": "~0.1.1", 2143 | "getpass": "^0.1.1", 2144 | "jsbn": "~0.1.0", 2145 | "safer-buffer": "^2.0.2", 2146 | "tweetnacl": "~0.14.0" 2147 | } 2148 | }, 2149 | "string-width": { 2150 | "version": "2.1.1", 2151 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", 2152 | "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", 2153 | "dev": true, 2154 | "requires": { 2155 | "is-fullwidth-code-point": "^2.0.0", 2156 | "strip-ansi": "^4.0.0" 2157 | } 2158 | }, 2159 | "string_decoder": { 2160 | "version": "0.10.31", 2161 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", 2162 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", 2163 | "dev": true 2164 | }, 2165 | "strip-ansi": { 2166 | "version": "4.0.0", 2167 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", 2168 | "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", 2169 | "dev": true, 2170 | "requires": { 2171 | "ansi-regex": "^3.0.0" 2172 | } 2173 | }, 2174 | "strip-bom": { 2175 | "version": "2.0.0", 2176 | "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", 2177 | "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", 2178 | "dev": true, 2179 | "requires": { 2180 | "is-utf8": "^0.2.0" 2181 | } 2182 | }, 2183 | "strip-eof": { 2184 | "version": "1.0.0", 2185 | "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", 2186 | "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", 2187 | "dev": true 2188 | }, 2189 | "strip-indent": { 2190 | "version": "1.0.1", 2191 | "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", 2192 | "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", 2193 | "dev": true, 2194 | "requires": { 2195 | "get-stdin": "^4.0.1" 2196 | } 2197 | }, 2198 | "strip-json-comments": { 2199 | "version": "2.0.1", 2200 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", 2201 | "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", 2202 | "dev": true 2203 | }, 2204 | "sumchecker": { 2205 | "version": "2.0.2", 2206 | "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-2.0.2.tgz", 2207 | "integrity": "sha1-D0LBDl0F2l1C7qPlbDOZo31sWz4=", 2208 | "dev": true, 2209 | "requires": { 2210 | "debug": "^2.2.0" 2211 | }, 2212 | "dependencies": { 2213 | "debug": { 2214 | "version": "2.6.9", 2215 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 2216 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 2217 | "dev": true, 2218 | "requires": { 2219 | "ms": "2.0.0" 2220 | } 2221 | } 2222 | } 2223 | }, 2224 | "supports-color": { 2225 | "version": "4.4.0", 2226 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", 2227 | "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", 2228 | "dev": true, 2229 | "requires": { 2230 | "has-flag": "^2.0.0" 2231 | } 2232 | }, 2233 | "text-encoding": { 2234 | "version": "0.6.4", 2235 | "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", 2236 | "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", 2237 | "dev": true 2238 | }, 2239 | "throttleit": { 2240 | "version": "0.0.2", 2241 | "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-0.0.2.tgz", 2242 | "integrity": "sha1-z+34jmDADdlpe2H90qg0OptoDq8=", 2243 | "dev": true 2244 | }, 2245 | "through2": { 2246 | "version": "0.2.3", 2247 | "resolved": "https://registry.npmjs.org/through2/-/through2-0.2.3.tgz", 2248 | "integrity": "sha1-6zKE2k6jEbbMis42U3SKUqvyWj8=", 2249 | "dev": true, 2250 | "requires": { 2251 | "readable-stream": "~1.1.9", 2252 | "xtend": "~2.1.1" 2253 | } 2254 | }, 2255 | "tough-cookie": { 2256 | "version": "2.4.3", 2257 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", 2258 | "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", 2259 | "dev": true, 2260 | "requires": { 2261 | "psl": "^1.1.24", 2262 | "punycode": "^1.4.1" 2263 | }, 2264 | "dependencies": { 2265 | "punycode": { 2266 | "version": "1.4.1", 2267 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", 2268 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", 2269 | "dev": true 2270 | } 2271 | } 2272 | }, 2273 | "trim-newlines": { 2274 | "version": "1.0.0", 2275 | "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", 2276 | "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", 2277 | "dev": true 2278 | }, 2279 | "ts-node": { 2280 | "version": "6.0.2", 2281 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-6.0.2.tgz", 2282 | "integrity": "sha512-H/KWK27B3JJAc5WFOBBUxN638DukbV8PptdQgiHWPO2SGDVJzuVOl8Ye0XJ5+FiZIdFtgUuGOJRV4c/XBQ5dBg==", 2283 | "dev": true, 2284 | "requires": { 2285 | "arrify": "^1.0.0", 2286 | "chalk": "^2.3.0", 2287 | "diff": "^3.1.0", 2288 | "make-error": "^1.1.1", 2289 | "minimist": "^1.2.0", 2290 | "mkdirp": "^0.5.1", 2291 | "source-map-support": "^0.5.3", 2292 | "yn": "^2.0.0" 2293 | }, 2294 | "dependencies": { 2295 | "minimist": { 2296 | "version": "1.2.0", 2297 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 2298 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", 2299 | "dev": true 2300 | } 2301 | } 2302 | }, 2303 | "tslib": { 2304 | "version": "1.9.3", 2305 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", 2306 | "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", 2307 | "dev": true 2308 | }, 2309 | "tslint": { 2310 | "version": "5.16.0", 2311 | "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.16.0.tgz", 2312 | "integrity": "sha512-UxG2yNxJ5pgGwmMzPMYh/CCnCnh0HfPgtlVRDs1ykZklufFBL1ZoTlWFRz2NQjcoEiDoRp+JyT0lhBbbH/obyA==", 2313 | "dev": true, 2314 | "requires": { 2315 | "@babel/code-frame": "^7.0.0", 2316 | "builtin-modules": "^1.1.1", 2317 | "chalk": "^2.3.0", 2318 | "commander": "^2.12.1", 2319 | "diff": "^3.2.0", 2320 | "glob": "^7.1.1", 2321 | "js-yaml": "^3.13.0", 2322 | "minimatch": "^3.0.4", 2323 | "mkdirp": "^0.5.1", 2324 | "resolve": "^1.3.2", 2325 | "semver": "^5.3.0", 2326 | "tslib": "^1.8.0", 2327 | "tsutils": "^2.29.0" 2328 | }, 2329 | "dependencies": { 2330 | "commander": { 2331 | "version": "2.20.0", 2332 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", 2333 | "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", 2334 | "dev": true 2335 | } 2336 | } 2337 | }, 2338 | "tsutils": { 2339 | "version": "2.29.0", 2340 | "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", 2341 | "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", 2342 | "dev": true, 2343 | "requires": { 2344 | "tslib": "^1.8.1" 2345 | } 2346 | }, 2347 | "tunnel-agent": { 2348 | "version": "0.6.0", 2349 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 2350 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", 2351 | "dev": true, 2352 | "requires": { 2353 | "safe-buffer": "^5.0.1" 2354 | } 2355 | }, 2356 | "tweetnacl": { 2357 | "version": "0.14.5", 2358 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 2359 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", 2360 | "dev": true 2361 | }, 2362 | "type-detect": { 2363 | "version": "4.0.8", 2364 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", 2365 | "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", 2366 | "dev": true 2367 | }, 2368 | "typedarray": { 2369 | "version": "0.0.6", 2370 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", 2371 | "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", 2372 | "dev": true 2373 | }, 2374 | "typescript": { 2375 | "version": "3.4.5", 2376 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.5.tgz", 2377 | "integrity": "sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw==", 2378 | "dev": true 2379 | }, 2380 | "universalify": { 2381 | "version": "0.1.2", 2382 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", 2383 | "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", 2384 | "dev": true 2385 | }, 2386 | "uri-js": { 2387 | "version": "4.2.2", 2388 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", 2389 | "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", 2390 | "dev": true, 2391 | "requires": { 2392 | "punycode": "^2.1.0" 2393 | } 2394 | }, 2395 | "util-deprecate": { 2396 | "version": "1.0.2", 2397 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 2398 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", 2399 | "dev": true 2400 | }, 2401 | "uuid": { 2402 | "version": "3.2.1", 2403 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", 2404 | "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" 2405 | }, 2406 | "validate-npm-package-license": { 2407 | "version": "3.0.4", 2408 | "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", 2409 | "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", 2410 | "dev": true, 2411 | "requires": { 2412 | "spdx-correct": "^3.0.0", 2413 | "spdx-expression-parse": "^3.0.0" 2414 | } 2415 | }, 2416 | "verror": { 2417 | "version": "1.10.0", 2418 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 2419 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", 2420 | "dev": true, 2421 | "requires": { 2422 | "assert-plus": "^1.0.0", 2423 | "core-util-is": "1.0.2", 2424 | "extsprintf": "^1.2.0" 2425 | } 2426 | }, 2427 | "which": { 2428 | "version": "1.3.1", 2429 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", 2430 | "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", 2431 | "dev": true, 2432 | "requires": { 2433 | "isexe": "^2.0.0" 2434 | } 2435 | }, 2436 | "which-module": { 2437 | "version": "2.0.0", 2438 | "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", 2439 | "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", 2440 | "dev": true 2441 | }, 2442 | "wide-align": { 2443 | "version": "1.1.3", 2444 | "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", 2445 | "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", 2446 | "dev": true, 2447 | "requires": { 2448 | "string-width": "^1.0.2 || 2" 2449 | } 2450 | }, 2451 | "wrap-ansi": { 2452 | "version": "2.1.0", 2453 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", 2454 | "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", 2455 | "dev": true, 2456 | "requires": { 2457 | "string-width": "^1.0.1", 2458 | "strip-ansi": "^3.0.1" 2459 | }, 2460 | "dependencies": { 2461 | "ansi-regex": { 2462 | "version": "2.1.1", 2463 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 2464 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", 2465 | "dev": true 2466 | }, 2467 | "is-fullwidth-code-point": { 2468 | "version": "1.0.0", 2469 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", 2470 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", 2471 | "dev": true, 2472 | "requires": { 2473 | "number-is-nan": "^1.0.0" 2474 | } 2475 | }, 2476 | "string-width": { 2477 | "version": "1.0.2", 2478 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", 2479 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", 2480 | "dev": true, 2481 | "requires": { 2482 | "code-point-at": "^1.0.0", 2483 | "is-fullwidth-code-point": "^1.0.0", 2484 | "strip-ansi": "^3.0.0" 2485 | } 2486 | }, 2487 | "strip-ansi": { 2488 | "version": "3.0.1", 2489 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 2490 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 2491 | "dev": true, 2492 | "requires": { 2493 | "ansi-regex": "^2.0.0" 2494 | } 2495 | } 2496 | } 2497 | }, 2498 | "wrappy": { 2499 | "version": "1.0.2", 2500 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 2501 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 2502 | "dev": true 2503 | }, 2504 | "xtend": { 2505 | "version": "2.1.2", 2506 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", 2507 | "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", 2508 | "dev": true, 2509 | "requires": { 2510 | "object-keys": "~0.4.0" 2511 | }, 2512 | "dependencies": { 2513 | "object-keys": { 2514 | "version": "0.4.0", 2515 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", 2516 | "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", 2517 | "dev": true 2518 | } 2519 | } 2520 | }, 2521 | "y18n": { 2522 | "version": "4.0.0", 2523 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", 2524 | "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", 2525 | "dev": true 2526 | }, 2527 | "yargs": { 2528 | "version": "13.2.4", 2529 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.4.tgz", 2530 | "integrity": "sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==", 2531 | "dev": true, 2532 | "requires": { 2533 | "cliui": "^5.0.0", 2534 | "find-up": "^3.0.0", 2535 | "get-caller-file": "^2.0.1", 2536 | "os-locale": "^3.1.0", 2537 | "require-directory": "^2.1.1", 2538 | "require-main-filename": "^2.0.0", 2539 | "set-blocking": "^2.0.0", 2540 | "string-width": "^3.0.0", 2541 | "which-module": "^2.0.0", 2542 | "y18n": "^4.0.0", 2543 | "yargs-parser": "^13.1.0" 2544 | }, 2545 | "dependencies": { 2546 | "ansi-regex": { 2547 | "version": "4.1.0", 2548 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", 2549 | "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", 2550 | "dev": true 2551 | }, 2552 | "cliui": { 2553 | "version": "5.0.0", 2554 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", 2555 | "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", 2556 | "dev": true, 2557 | "requires": { 2558 | "string-width": "^3.1.0", 2559 | "strip-ansi": "^5.2.0", 2560 | "wrap-ansi": "^5.1.0" 2561 | } 2562 | }, 2563 | "string-width": { 2564 | "version": "3.1.0", 2565 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", 2566 | "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", 2567 | "dev": true, 2568 | "requires": { 2569 | "emoji-regex": "^7.0.1", 2570 | "is-fullwidth-code-point": "^2.0.0", 2571 | "strip-ansi": "^5.1.0" 2572 | } 2573 | }, 2574 | "strip-ansi": { 2575 | "version": "5.2.0", 2576 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", 2577 | "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", 2578 | "dev": true, 2579 | "requires": { 2580 | "ansi-regex": "^4.1.0" 2581 | } 2582 | }, 2583 | "wrap-ansi": { 2584 | "version": "5.1.0", 2585 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", 2586 | "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", 2587 | "dev": true, 2588 | "requires": { 2589 | "ansi-styles": "^3.2.0", 2590 | "string-width": "^3.0.0", 2591 | "strip-ansi": "^5.0.0" 2592 | } 2593 | }, 2594 | "yargs-parser": { 2595 | "version": "13.1.0", 2596 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.0.tgz", 2597 | "integrity": "sha512-Yq+32PrijHRri0vVKQEm+ys8mbqWjLiwQkMFNXEENutzLPP0bE4Lcd4iA3OQY5HF+GD3xXxf0MEHb8E4/SA3AA==", 2598 | "dev": true, 2599 | "requires": { 2600 | "camelcase": "^5.0.0", 2601 | "decamelize": "^1.2.0" 2602 | } 2603 | } 2604 | } 2605 | }, 2606 | "yargs-parser": { 2607 | "version": "13.0.0", 2608 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.0.0.tgz", 2609 | "integrity": "sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==", 2610 | "dev": true, 2611 | "requires": { 2612 | "camelcase": "^5.0.0", 2613 | "decamelize": "^1.2.0" 2614 | } 2615 | }, 2616 | "yargs-unparser": { 2617 | "version": "1.5.0", 2618 | "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.5.0.tgz", 2619 | "integrity": "sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw==", 2620 | "dev": true, 2621 | "requires": { 2622 | "flat": "^4.1.0", 2623 | "lodash": "^4.17.11", 2624 | "yargs": "^12.0.5" 2625 | }, 2626 | "dependencies": { 2627 | "get-caller-file": { 2628 | "version": "1.0.3", 2629 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", 2630 | "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", 2631 | "dev": true 2632 | }, 2633 | "require-main-filename": { 2634 | "version": "1.0.1", 2635 | "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", 2636 | "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", 2637 | "dev": true 2638 | }, 2639 | "yargs": { 2640 | "version": "12.0.5", 2641 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", 2642 | "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", 2643 | "dev": true, 2644 | "requires": { 2645 | "cliui": "^4.0.0", 2646 | "decamelize": "^1.2.0", 2647 | "find-up": "^3.0.0", 2648 | "get-caller-file": "^1.0.1", 2649 | "os-locale": "^3.0.0", 2650 | "require-directory": "^2.1.1", 2651 | "require-main-filename": "^1.0.1", 2652 | "set-blocking": "^2.0.0", 2653 | "string-width": "^2.0.0", 2654 | "which-module": "^2.0.0", 2655 | "y18n": "^3.2.1 || ^4.0.0", 2656 | "yargs-parser": "^11.1.1" 2657 | } 2658 | }, 2659 | "yargs-parser": { 2660 | "version": "11.1.1", 2661 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", 2662 | "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", 2663 | "dev": true, 2664 | "requires": { 2665 | "camelcase": "^5.0.0", 2666 | "decamelize": "^1.2.0" 2667 | } 2668 | } 2669 | } 2670 | }, 2671 | "yauzl": { 2672 | "version": "2.4.1", 2673 | "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", 2674 | "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", 2675 | "dev": true, 2676 | "requires": { 2677 | "fd-slicer": "~1.0.1" 2678 | } 2679 | }, 2680 | "yn": { 2681 | "version": "2.0.0", 2682 | "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", 2683 | "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", 2684 | "dev": true 2685 | } 2686 | } 2687 | } 2688 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "telemetry-github", 3 | "version": "0.1.1", 4 | "description": "telemetry for GitHub client apps", 5 | "main": "./out/index.js", 6 | "scripts": { 7 | "mocha": "electron-mocha --renderer --require ts-node/register 'test/**/*.spec.ts'", 8 | "test": "npm run mocha && npm run lint", 9 | "lint": "tslint -c tslint.json 'index/**/*.ts' 'test/**/*.ts'", 10 | "prepare": "tsc" 11 | }, 12 | "author": { 13 | "name": "GitHub, Inc.", 14 | "email": "atom@github.com" 15 | }, 16 | "license": "MIT", 17 | "dependencies": { 18 | "idb": "^4.0.3", 19 | "lokijs": "^1.5.4", 20 | "uuid": "^3.2.1" 21 | }, 22 | "devDependencies": { 23 | "@types/chai": "^4.1.3", 24 | "@types/chai-as-promised": "^7.1.0", 25 | "@types/lokijs": "^1.5.2", 26 | "@types/mocha": "^5.2.0", 27 | "@types/sinon": "^4.3.1", 28 | "@types/uuid": "^3.4.3", 29 | "chai": "^4.1.2", 30 | "chai-as-promised": "^7.1.1", 31 | "electron": "^5.0.2", 32 | "electron-mocha": "^8.0.2", 33 | "mocha": "^5.1.1", 34 | "sinon": "^5.0.3", 35 | "ts-node": "^6.0.2", 36 | "tslint": "^5.16.0", 37 | "typescript": "^3.4.5" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/databases/base.ts: -------------------------------------------------------------------------------- 1 | export interface BaseDatabase { 2 | addCustomEvent(eventType: string, customEvent: object): Promise; 3 | 4 | incrementCounter(counterName: string): Promise; 5 | 6 | addTiming( 7 | eventType: string, 8 | durationInMilliseconds: number, 9 | metadata?: object, 10 | ): Promise; 11 | 12 | clearData(): Promise; 13 | 14 | getTimings(): Promise; 15 | 16 | getCustomEvents(): Promise; 17 | 18 | getCounters(): Promise; 19 | } 20 | 21 | export interface Counters { 22 | [name: string]: number; 23 | } 24 | 25 | export interface TimingEvent { 26 | date: string; 27 | eventType: string; 28 | durationInMilliseconds: number; 29 | metadata: object; 30 | } 31 | 32 | export interface CustomEvent { 33 | date: string; 34 | eventType: string; 35 | } 36 | -------------------------------------------------------------------------------- /src/databases/indexeddb.ts: -------------------------------------------------------------------------------- 1 | import {BaseDatabase, Counters, TimingEvent, CustomEvent} from "./base"; 2 | import {getISODate} from "../util"; 3 | import MemoryDatabase from "./memory"; 4 | import { openDB, IDBPDatabase } from "idb"; 5 | 6 | export default class IndexedDBDatabase implements BaseDatabase { 7 | private dbPromise: Promise | MemoryDatabase>; 22 | 23 | public constructor() { 24 | this.dbPromise = openDB("atom-telemetry-store", 1, { 25 | upgrade(db) { 26 | db.createObjectStore("counters", { keyPath: "name" }); 27 | db.createObjectStore("customEvents", { autoIncrement : true }); 28 | db.createObjectStore("timingEvents", { autoIncrement : true }); 29 | }, 30 | }).catch(() => { 31 | console.warn( 32 | "Could not open IndexedDB database to store telemetry events. This session events won't be recorded." 33 | ); 34 | return new MemoryDatabase(); 35 | }); 36 | } 37 | 38 | public async addCustomEvent(eventType: string, customEvent: object) { 39 | const db = await this.dbPromise; 40 | if (db instanceof MemoryDatabase) { 41 | db.addCustomEvent(eventType, customEvent); 42 | return; 43 | } 44 | 45 | const eventToInsert = { 46 | ...customEvent, 47 | date: getISODate(), 48 | eventType, 49 | }; 50 | 51 | db.put("customEvents", eventToInsert); 52 | } 53 | 54 | public async incrementCounter(counterName: string) { 55 | const db = await this.dbPromise; 56 | if (db instanceof MemoryDatabase) { 57 | db.incrementCounter(counterName); 58 | return; 59 | } 60 | 61 | const tx = db.transaction("counters", "readwrite"); 62 | const entry = await tx.store.get(counterName); 63 | const currentValue = entry ? entry.value : 0; 64 | 65 | await tx.store.put({name: counterName, value: currentValue + 1}); 66 | 67 | await tx.done; 68 | } 69 | 70 | public async addTiming(eventType: string, durationInMilliseconds: number, metadata: object = {}) { 71 | const db = await this.dbPromise; 72 | if (db instanceof MemoryDatabase) { 73 | db.addTiming(eventType, durationInMilliseconds, metadata); 74 | return; 75 | } 76 | 77 | const timingData = { 78 | eventType, 79 | durationInMilliseconds, 80 | metadata, 81 | date: getISODate(), 82 | }; 83 | 84 | db.put("timingEvents", timingData); 85 | } 86 | 87 | public async clearData() { 88 | const db = await this.dbPromise; 89 | if (db instanceof MemoryDatabase) { 90 | db.clearData(); 91 | return; 92 | } 93 | 94 | const tx = db.transaction( 95 | ["counters", "customEvents", "timingEvents"], 96 | "readwrite", 97 | ); 98 | 99 | await tx.objectStore("counters").clear(); 100 | await tx.objectStore("customEvents").clear(); 101 | await tx.objectStore("timingEvents").clear(); 102 | 103 | await tx.done; 104 | } 105 | 106 | public async getTimings(): Promise { 107 | const db = await this.dbPromise; 108 | if (db instanceof MemoryDatabase) { 109 | return db.getTimings(); 110 | } 111 | 112 | return db.getAll("timingEvents"); 113 | } 114 | 115 | public async getCustomEvents(): Promise { 116 | const db = await this.dbPromise; 117 | if (db instanceof MemoryDatabase) { 118 | return db.getCustomEvents(); 119 | } 120 | 121 | return db.getAll("customEvents"); 122 | } 123 | 124 | /** 125 | * Get all counters. 126 | * Returns something like { commits: 7, coAuthoredCommits: 8 }. 127 | */ 128 | public async getCounters(): Promise { 129 | const db = await this.dbPromise; 130 | if (db instanceof MemoryDatabase) { 131 | return db.getCounters(); 132 | } 133 | 134 | const counters: Counters = Object.create(null); 135 | 136 | const entries = await db.getAll("counters"); 137 | 138 | for (const { name, value } of entries) { 139 | counters[name] = value; 140 | } 141 | 142 | return counters; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/databases/loki.ts: -------------------------------------------------------------------------------- 1 | import * as loki from "lokijs"; 2 | import {BaseDatabase, Counters, TimingEvent, CustomEvent} from "./base"; 3 | import {getISODate} from "../util"; 4 | 5 | export default class LokiDatabase implements BaseDatabase { 6 | 7 | /** 8 | * Counters which can be incremented. 9 | * Most commonly used for usage stats that don't need 10 | * additional metadata. 11 | */ 12 | private counters: Collection<{ 13 | name: string; 14 | count: number; 15 | }>; 16 | 17 | /** 18 | * Events are used to record application metrics that need additional metadata 19 | * For example, file open events where you'd want to track the language of the file. 20 | * Events are an object, to give clients maximal flexibility. Date is automatically added 21 | * for you in ISO-8601 format. 22 | */ 23 | private customEvents: Collection; 24 | 25 | /** 26 | * Timing is used to record application metrics that deal with latency. 27 | */ 28 | private timings: Collection; 29 | 30 | public constructor() { 31 | const db = new loki("stats-database"); 32 | this.counters = db.addCollection("counters"); 33 | this.customEvents = db.addCollection("customEvents"); 34 | this.timings = db.addCollection("timing"); 35 | } 36 | 37 | public async addCustomEvent(eventType: string, customEvent: object) { 38 | const eventToInsert: CustomEvent = { 39 | ...customEvent, 40 | date: getISODate(), 41 | eventType, 42 | }; 43 | 44 | this.customEvents.insert(eventToInsert); 45 | } 46 | 47 | public async incrementCounter(counterName: string) { 48 | const existing = await this.getUnformattedCounter(counterName); 49 | if (existing) { 50 | existing.count += 1; 51 | this.counters.update(existing); 52 | } else { 53 | this.counters.insert({ name: counterName, count: 1}); 54 | } 55 | } 56 | 57 | public async addTiming(eventType: string, durationInMilliseconds: number, metadata: object = {}) { 58 | const timingData = { eventType, durationInMilliseconds, metadata, date: getISODate() }; 59 | this.timings.insert(timingData); 60 | } 61 | 62 | /** Clears all values that exist in the database. 63 | * returns nothing. 64 | */ 65 | public async clearData() { 66 | await this.counters.clear(); 67 | await this.customEvents.clear(); 68 | await this.timings.clear(); 69 | } 70 | 71 | public async getTimings(): Promise { 72 | const timings = await this.timings.find(); 73 | timings.forEach((timing) => { 74 | delete timing.$loki; 75 | delete timing.meta; 76 | }); 77 | 78 | return timings; 79 | } 80 | 81 | public async getCustomEvents(): Promise { 82 | const events = await this.customEvents.find(); 83 | events.forEach((event) => { 84 | // honey badger don't care about lokijis meta data. 85 | delete event.$loki; 86 | delete event.meta; 87 | }); 88 | return events; 89 | } 90 | 91 | /** Get all counters. 92 | * This method strips the lokijs metadata, which external 93 | * callers shouldn't care about. 94 | * Returns something like { commits: 7, coAuthoredCommits: 8 }. 95 | */ 96 | public async getCounters(): Promise { 97 | const counters: Counters = {}; 98 | this.counters.find().forEach((counter) => { 99 | counters[counter.name] = counter.count; 100 | }); 101 | 102 | return counters; 103 | } 104 | 105 | /** Get a single counter. 106 | * Don't strip lokijs metadata, because if we want to update existing 107 | * items we need to pass that shizz back in. 108 | * Returns something like: 109 | * [ { name: 'coAuthoredCommits',count: 1, meta: { revision: 0, created: 1526592157642, version: 0 },'$loki': 1 } ] 110 | */ 111 | private async getUnformattedCounter(counterName: string) { 112 | const existing = await this.counters.find({ name: counterName }); 113 | 114 | if (existing.length > 1) { 115 | // we should never get into this situation because if we are using the lokijs 116 | // api properly it should overwrite existing items with the same name. 117 | // but I've seen things (in prod) you people wouldn't believe. 118 | // Attack ships on fire off the shoulder of Orion. 119 | // Cosmic rays flipping bits and influencing the outcome of elections. 120 | // So throw an error just in case. 121 | throw new Error("multiple counters with the same name"); 122 | } else if (existing.length < 1) { 123 | return null; 124 | } else { 125 | return existing[0]; 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/databases/memory.ts: -------------------------------------------------------------------------------- 1 | import {BaseDatabase, Counters, TimingEvent, CustomEvent} from "./base"; 2 | import {getISODate} from "../util"; 3 | 4 | export default class MemoryDatabase implements BaseDatabase { 5 | private counters: Counters; 6 | private customEvents: CustomEvent[]; 7 | private timingEvents: TimingEvent[]; 8 | 9 | public constructor() { 10 | this.counters = Object.create(null); 11 | this.customEvents = []; 12 | this.timingEvents = []; 13 | } 14 | 15 | public async addCustomEvent(eventType: string, customEvent: object) { 16 | const eventToInsert = { 17 | ...customEvent, 18 | date: getISODate(), 19 | eventType, 20 | }; 21 | 22 | this.customEvents.push(eventToInsert); 23 | } 24 | 25 | public async incrementCounter(counterName: string) { 26 | this.counters[counterName] = (this.counters[counterName] || 0) + 1; 27 | } 28 | 29 | public async addTiming(eventType: string, durationInMilliseconds: number, metadata: object = {}) { 30 | const timingData = { 31 | eventType, 32 | durationInMilliseconds, 33 | metadata, 34 | date: getISODate(), 35 | }; 36 | 37 | this.timingEvents.push(timingData); 38 | } 39 | 40 | public async clearData() { 41 | this.counters = Object.create(null); 42 | this.customEvents = []; 43 | this.timingEvents = []; 44 | } 45 | 46 | public async getTimings(): Promise { 47 | return this.timingEvents; 48 | } 49 | 50 | public async getCustomEvents(): Promise { 51 | return this.customEvents; 52 | } 53 | 54 | /** 55 | * Get all counters. 56 | * Returns something like { commits: 7, coAuthoredCommits: 8 }. 57 | */ 58 | public async getCounters(): Promise { 59 | return this.counters; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { getGUID } from "./uuid"; 2 | import {BaseDatabase, TimingEvent, CustomEvent, Counters} from "./databases/base"; 3 | import IndexedDBDatabase from "./databases/indexeddb"; 4 | import {getISODate} from "./util"; 5 | 6 | // if you're running a local instance of central, use 7 | // "http://localhost:4000/api/usage/" instead. 8 | const baseUsageApi = "https://central.github.com/api/usage/"; 9 | 10 | export const LastDailyStatsReportKey = "last-daily-stats-report"; 11 | 12 | /** The localStorage key for whether the user has opted out. */ 13 | export const StatsOptOutKey = "stats-opt-out"; 14 | 15 | /** The localStorage key that indicates that we're sending the stats to the server. */ 16 | export const SendingStatsKey = "metrics:stats-being-sent"; 17 | 18 | /** Have we successfully sent the stats opt-in? */ 19 | export const HasSentOptInPingKey = "has-sent-stats-opt-in-ping"; 20 | 21 | /** milliseconds in a minute (for readability, dawg) */ 22 | const minutes = 60 * 1000; 23 | 24 | /** milliseconds in an hour (for readability, dawg) */ 25 | const hours = 60 * minutes; 26 | 27 | /** How often do we want to check to report stats (also the delay until the first report). */ 28 | const MaxReportingFrequency = 10 * minutes; 29 | 30 | /** How often daily stats should be submitted (i.e., 24 hours). */ 31 | export const DailyStatsReportIntervalInMs = hours * 24; 32 | 33 | /** After this amount of time we'll. */ 34 | export const TimeoutForReportingLock = hours * 2; 35 | 36 | interface Dimensions { 37 | /** The app version. */ 38 | readonly appVersion: string; 39 | 40 | /** the platform */ 41 | readonly platform: string; 42 | 43 | /** The install ID. */ 44 | readonly guid: string; 45 | 46 | /** The date the metrics were sent, in ISO-8601 format */ 47 | readonly date: string; 48 | 49 | readonly eventType: "usage"; 50 | 51 | readonly language: string; 52 | 53 | readonly gitHubUser: string | null; 54 | } 55 | 56 | export interface Metrics { 57 | dimensions: Dimensions; 58 | // object with the value for each counter name. 59 | measures: Counters; 60 | 61 | // array of custom events that can be defined by the client 62 | customEvents: CustomEvent[]; 63 | 64 | // array of timing events 65 | timings: TimingEvent[]; 66 | } 67 | 68 | /** The goal is for this package to be app-agnostic so we can add 69 | * other editors in the future. 70 | */ 71 | export enum AppName { 72 | Atom = "atom", 73 | } 74 | 75 | export class StatsStore { 76 | 77 | private timer: NodeJS.Timer; 78 | 79 | /** Has the user opted out of stats reporting? */ 80 | private optOut: boolean; 81 | 82 | /** api for calling central with our stats */ 83 | private appUrl: string; 84 | 85 | /** which version are we running, dawg */ 86 | private version: string; 87 | 88 | /** is electron app running in development mode? 89 | * There isn't currently a consistent way of programmatically determining if an app 90 | * is in dev mode that works in Desktop, Atom, and vscode. 91 | * Todo: use Electron's new api to determine whether we are in dev mode, once 92 | * all the clients using `telemetry` are on. Electron versions that support this api. 93 | * https://github.com/electron/electron/issues/7714 94 | */ 95 | private isDevMode: boolean; 96 | 97 | /** Instance of a class thats stores metrics so they can be stored across sessions */ 98 | private database: BaseDatabase; 99 | 100 | /** function for getting GitHub access token if one exists. 101 | * We don't want to store the token, due to security concerns, and also 102 | * because the token might expire. 103 | */ 104 | private getAccessToken: () => string; 105 | 106 | private gitHubUser: string | null; 107 | 108 | /** How often daily stats should be submitted (in ms). */ 109 | private reportingFrequency: number; 110 | 111 | /** If true, it'll print every metrics request on the console. */ 112 | private verboseMode: boolean; 113 | 114 | public constructor( 115 | appName: AppName, 116 | version: string, 117 | isDevMode: boolean, 118 | getAccessToken = () => "", 119 | options: { 120 | logInDevMode?: boolean, 121 | reportingFrequency?: number, 122 | verboseMode?: boolean, 123 | } = {}, 124 | ) { 125 | this.database = new IndexedDBDatabase(); 126 | this.version = version; 127 | this.appUrl = baseUsageApi + appName; 128 | const optOutValue = localStorage.getItem(StatsOptOutKey); 129 | 130 | this.isDevMode = !options.logInDevMode && isDevMode; 131 | this.getAccessToken = getAccessToken; 132 | this.gitHubUser = null; 133 | 134 | this.reportingFrequency = options.reportingFrequency || exports.DailyStatsReportIntervalInMs; 135 | 136 | // We set verbose mode when logging in Dev mode to prevent users from forgetting to turn 137 | // off the logging in dev mode. 138 | this.verboseMode = (!!options.logInDevMode && isDevMode) || !!options.verboseMode; 139 | this.timer = this.getTimer(Math.min(this.reportingFrequency / 6, MaxReportingFrequency)); 140 | 141 | if (optOutValue) { 142 | this.optOut = !!parseInt(optOutValue, 10); 143 | 144 | // If the user has set an opt out value but we haven't sent the ping yet, 145 | // give it a shot now. 146 | if (!localStorage.getItem(HasSentOptInPingKey)) { 147 | this.sendOptInStatusPing(!this.optOut); 148 | } 149 | } else { 150 | this.optOut = false; 151 | } 152 | } 153 | 154 | public end() { 155 | clearInterval(this.timer); 156 | } 157 | 158 | public setGitHubUser(gitHubUser: string) { 159 | this.gitHubUser = gitHubUser; 160 | } 161 | 162 | /** Set whether the user has opted out of stats reporting. */ 163 | public async setOptOut(optOut: boolean): Promise { 164 | const changed = this.optOut !== optOut; 165 | 166 | this.optOut = optOut; 167 | 168 | localStorage.setItem(StatsOptOutKey, optOut ? "1" : "0"); 169 | 170 | if (changed) { 171 | await this.sendOptInStatusPing(!optOut); 172 | } 173 | } 174 | 175 | public async reportStats() { 176 | if (this.optOut || this.isDevMode) { 177 | return; 178 | } 179 | 180 | // If multiple instances of `telemetry` are being run from different 181 | // renderer process, we want to avoid two instances to send the same 182 | // stats. 183 | // We use a timed mutex so if for some reason the lock is not released 184 | // after reporting the metrics the metrics can still be sent after some 185 | // timeout. 186 | // Remember to not perform async operations between the `localStorage.setItem()` 187 | // and the `localStorage.getItem()` calls. This way we can take advantage of the 188 | // LocalStorage mutex on Chrome: https://www.w3.org/TR/webstorage/#threads 189 | if (!this.isDateBefore( 190 | localStorage.getItem(SendingStatsKey), 191 | TimeoutForReportingLock, 192 | )) { 193 | return; 194 | } 195 | localStorage.setItem(SendingStatsKey, Date.now().toString()); 196 | 197 | try { 198 | const stats = await this.getDailyStats(); 199 | const response = await this.post(stats); 200 | 201 | if (response.status !== 200) { 202 | throw new Error(`Stats reporting failure: ${response.status})`); 203 | } else { 204 | await localStorage.setItem(LastDailyStatsReportKey, Date.now().toString()); 205 | await this.database.clearData(); 206 | console.log("stats successfully reported"); 207 | } 208 | } catch (err) { 209 | // todo (tt, 5/2018): would be good to log these errors to Haystack/Datadog 210 | // so we have some kind of visibility into how often things are failing. 211 | console.log(err); 212 | } finally { 213 | // Delete the "mutex" used to ensure that stats are not sent at the same time by 214 | // two different processes. 215 | localStorage.removeItem(SendingStatsKey); 216 | } 217 | } 218 | 219 | public async clearData() { 220 | await this.database.clearData(); 221 | } 222 | 223 | /* send a ping to indicate that the user has changed their opt-in preferences. 224 | * public for testing purposes only. 225 | */ 226 | public async sendOptInStatusPing(optIn: boolean): Promise { 227 | if (this.isDevMode) { 228 | return; 229 | } 230 | const direction = optIn ? "in" : "out"; 231 | try { 232 | const response = await this.post({ 233 | eventType: "ping", 234 | dimensions: { 235 | optIn, 236 | }, 237 | }); 238 | if (response.status !== 200) { 239 | throw new Error(`Error sending opt in ping: ${response.status}`); 240 | } 241 | localStorage.setItem(HasSentOptInPingKey, "1"); 242 | 243 | console.log(`Opt ${direction} reported.`); 244 | } catch (err) { 245 | // todo (tt, 5/2018): would be good to log these errors to Haystack/Datadog 246 | // so we have some kind of visibility into how often things are failing. 247 | console.log(`Error reporting opt ${direction}`, err); 248 | } 249 | } 250 | 251 | // public for testing purposes only 252 | public async getDailyStats(): Promise { 253 | return { 254 | measures: await this.database.getCounters(), 255 | customEvents: await this.database.getCustomEvents(), 256 | timings: await this.database.getTimings(), 257 | dimensions: { 258 | appVersion: this.version, 259 | platform: process.platform, 260 | guid: getGUID(), 261 | eventType: "usage", 262 | date: getISODate(), 263 | language: process.env.LANG || "", 264 | gitHubUser: this.gitHubUser, 265 | }, 266 | }; 267 | } 268 | 269 | public async addCustomEvent(eventType: string, event: object) { 270 | await this.database.addCustomEvent(eventType, event); 271 | } 272 | 273 | /** 274 | * Add timing data to the stats store, to be sent with the daily metrics requests. 275 | */ 276 | public async addTiming(eventType: string, durationInMilliseconds: number, metadata = {}) { 277 | // don't increment in dev mode because localStorage 278 | // is shared across dev and non dev windows and there's 279 | // no way to keep dev and non-dev metrics separate. 280 | // don't increment if the user has opted out, because 281 | // we want to respect user privacy. 282 | if (this.isDevMode || this.optOut) { 283 | return; 284 | } 285 | await this.database.addTiming(eventType, durationInMilliseconds, metadata); 286 | } 287 | 288 | /** 289 | * Increment a counter. This is used to track usage statistics. 290 | */ 291 | public async incrementCounter(counterName: string) { 292 | // don't increment in dev mode because localStorage 293 | // is shared across dev and non dev windows and there's 294 | // no way to keep dev and non-dev metrics separate. 295 | // don't increment if the user has opted out, because 296 | // we want to respect user privacy. 297 | if (this.isDevMode || this.optOut) { 298 | return; 299 | } 300 | await this.database.incrementCounter(counterName); 301 | } 302 | 303 | /** Post some data to our stats endpoint. 304 | * This is public for testing purposes only. 305 | */ 306 | public async post(body: object): Promise { 307 | if (this.verboseMode) { 308 | console.log("Sending metrics", body); 309 | } 310 | 311 | const requestHeaders: {[name: string]: string} = { "Content-Type": "application/json" }; 312 | const token = this.getAccessToken(); 313 | if (token) { 314 | requestHeaders.Authorization = `token ${token}`; 315 | } 316 | const options = { 317 | method: "POST", 318 | headers: requestHeaders, 319 | body: JSON.stringify(body), 320 | }; 321 | 322 | return this.fetch(this.appUrl, options); 323 | } 324 | 325 | /** Exists to enable us to mock fetch in tests 326 | * This is public for testing purposes only. 327 | */ 328 | public async fetch(url: string, options: object): Promise { 329 | return fetch(url, options); 330 | } 331 | 332 | /** Should the app report its daily stats? 333 | * Public for testing purposes only. 334 | */ 335 | public hasReportingIntervalElapsed(): boolean { 336 | return this.isDateBefore( 337 | localStorage.getItem(LastDailyStatsReportKey), 338 | this.reportingFrequency, 339 | ); 340 | } 341 | 342 | /** Set a timer so we can report the stats when the time comes. */ 343 | private getTimer(loopInterval: number): NodeJS.Timer { 344 | // todo (tt, 5/2018): maybe we shouldn't even set up the timer 345 | // in dev mode or if the user has opted out. 346 | const timer = setInterval(() => { 347 | if (this.hasReportingIntervalElapsed()) { 348 | this.reportStats(); 349 | } 350 | }, loopInterval); 351 | 352 | if (timer.unref !== undefined) { 353 | // make sure we don't block node from exiting in tests 354 | // https://stackoverflow.com/questions/48172363/mocha-test-suite-never-ends-when-setinterval-running 355 | timer.unref(); 356 | } 357 | return timer; 358 | } 359 | 360 | /** 361 | * Helper method that returns whether the difference between the current date and the specified date is 362 | * less than the specified amount of milliseconds. 363 | * 364 | * The pass date should be a string representing the number of milliseconds elapsed since 365 | * January 1, 1970 00:00:00 UTC. 366 | */ 367 | private isDateBefore(dateAsString: string | null, numMilliseconds: number): boolean { 368 | if (!dateAsString || dateAsString.length === 0) { 369 | return true; 370 | } 371 | 372 | const date = parseInt(dateAsString, 10); 373 | 374 | if (isNaN(date)) { 375 | return true; 376 | } 377 | 378 | return (Date.now() - date) > numMilliseconds; 379 | } 380 | } 381 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | export function getISODate() { 2 | return new Date().toISOString(); 3 | } 4 | -------------------------------------------------------------------------------- /src/uuid.ts: -------------------------------------------------------------------------------- 1 | import { randomBytes } from "crypto"; 2 | const uuidv4 = require("uuid/v4"); 3 | 4 | /** 5 | * Wrapper function over uuid's v4 method that attempts to source 6 | * entropy using Node's randomBytes 7 | */ 8 | function uuid(): string { 9 | return uuidv4({random: randomBytes(16)}); 10 | } 11 | 12 | /** The localStorage key for the stats GUID. */ 13 | export const StatsGUIDKey = "stats-guid"; 14 | 15 | let cachedGUID: string | null = null; 16 | 17 | /** Get the stats GUID. */ 18 | export function getGUID(): string { 19 | if (!cachedGUID) { 20 | let GUID = localStorage.getItem(StatsGUIDKey); 21 | if (!GUID) { 22 | GUID = uuid(); 23 | localStorage.setItem(StatsGUIDKey, GUID); 24 | } 25 | 26 | cachedGUID = GUID; 27 | } 28 | return cachedGUID; 29 | } 30 | -------------------------------------------------------------------------------- /test/databases/database.spec.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | import {BaseDatabase} from "../../src/databases/base"; 3 | import LokiDatabase from "../../src/databases/loki"; 4 | import IndexedDBDatabase from "../../src/databases/indexeddb"; 5 | import MemoryDatabase from "../../src/databases/memory"; 6 | 7 | const grammar = "javascript"; 8 | const openEventType = "open"; 9 | const openEvent = { grammar, eventType: openEventType}; 10 | 11 | const deprecateEventType = "deprecate"; 12 | const message = "oh noes"; 13 | const deprecateEvent = { message, eventType: deprecateEventType }; 14 | 15 | for (const DatabaseImpl of [LokiDatabase, IndexedDBDatabase, MemoryDatabase]) { 16 | describe(`database - ${DatabaseImpl.name}`, async function() { 17 | const counterName = "commits"; 18 | let database: BaseDatabase; 19 | 20 | beforeEach(async function() { 21 | database = new DatabaseImpl(); 22 | }); 23 | 24 | afterEach(async function() { 25 | await database.clearData(); 26 | }); 27 | 28 | // inserting into lokijs mutates the passed-in object 29 | // so that you can't insert the same object twice. 30 | // in real life this isn't a problem because you'd be passing in a new 31 | // object every time, but it makes test fixtures annoying. 32 | // So just make a new object in every test when you're inserting and move on with your life. 33 | describe("custom event methods", async function() { 34 | it("adds and gets a single event", async function() { 35 | await database.addCustomEvent(openEventType, { grammar }); 36 | const rawEvents = await database.getCustomEvents(); 37 | const events = rawEvents.map(({date, ...rest}) => rest); 38 | 39 | assert.deepEqual(events[0], openEvent); 40 | }); 41 | it("adds multiple events", async function() { 42 | await database.addCustomEvent(openEventType, { grammar }); 43 | await database.addCustomEvent(deprecateEventType, { message }); 44 | const rawEvents = await database.getCustomEvents(); 45 | const events = rawEvents.map(({date, ...rest}) => rest); 46 | 47 | assert.deepEqual(events, [openEvent, deprecateEvent]); 48 | }); 49 | }); 50 | describe("timing methods", async function() { 51 | it("adds and gets a single timer", async function() { 52 | const eventType = "load"; 53 | const durationInMilliseconds = 100; 54 | const metadata = { meta: "data" }; 55 | await database.addTiming(eventType, durationInMilliseconds, metadata); 56 | 57 | const [{date, ...timingEvent}] = await database.getTimings(); 58 | 59 | assert.deepEqual(timingEvent, { eventType, durationInMilliseconds, metadata }); 60 | assert.isString(date); 61 | }); 62 | it("adds and gets multiple timers", async function() { 63 | const eventType = "load"; 64 | const durationInMilliseconds1 = 100; 65 | const durationInMilliseconds2 = 200; 66 | const metadata = { meta: "data" }; 67 | await database.addTiming(eventType, durationInMilliseconds1, metadata); 68 | await database.addTiming(eventType, durationInMilliseconds2, metadata); 69 | 70 | const rawTimings = await database.getTimings(); 71 | 72 | // strip the dates from the timing events to make easier assertions. 73 | const timings = rawTimings.map(({date, ...rest}) => rest); 74 | 75 | assert.deepEqual( 76 | timings[0], 77 | { eventType, durationInMilliseconds: durationInMilliseconds1, metadata }, 78 | ); 79 | assert.deepEqual( 80 | timings[1], 81 | { eventType, durationInMilliseconds: durationInMilliseconds2, metadata }, 82 | ); 83 | }); 84 | }); 85 | describe("incrementCounter", async function() { 86 | it("adds a new counter if it does not exist", async function() { 87 | const counter = await database.getCounters(); 88 | assert.deepEqual(counter, {}); 89 | await database.incrementCounter(counterName); 90 | const incrementedCounter = await database.getCounters(); 91 | assert.deepEqual(incrementedCounter, {[counterName]: 1}); 92 | }); 93 | it("increments an existing counter", async function() { 94 | await database.incrementCounter(counterName); 95 | const counter = await database.getCounters(); 96 | assert.deepEqual(counter, {[counterName]: 1}); 97 | await database.incrementCounter(counterName); 98 | const incrementedCounter = await database.getCounters(); 99 | assert.deepEqual(incrementedCounter, { [counterName]: 2}); 100 | }); 101 | }); 102 | describe("getCounters", async function() { 103 | it("gets an empty object if counters do not exist", async function() { 104 | const counter = await database.getCounters(); 105 | assert.deepEqual(counter, {}); 106 | }); 107 | it("gets a single counter if it exists", async function() { 108 | await database.incrementCounter(counterName); 109 | const counter = await database.getCounters(); 110 | assert.deepEqual(counter, { [counterName]: 1 }); 111 | }); 112 | it("gets all counters that exist", async function() { 113 | await database.incrementCounter(counterName); 114 | await database.incrementCounter("foo"); 115 | const measures = await database.getCounters(); 116 | assert.deepEqual(measures, { [counterName]: 1, foo: 1}); 117 | }); 118 | }); 119 | describe("clearData", async function() { 120 | it("clears db containing single counter", async function() { 121 | await database.incrementCounter(counterName); 122 | await database.clearData(); 123 | const counters = await database.getCounters(); 124 | assert.deepEqual(counters, {}); 125 | }); 126 | it("clears db containing multiple counters", async function() { 127 | await database.incrementCounter(counterName); 128 | await database.incrementCounter(counterName); 129 | await database.incrementCounter("foo"); 130 | await database.clearData(); 131 | const counters = await database.getCounters(); 132 | assert.deepEqual(counters, {}); 133 | }); 134 | it("clears db containing single customEvent", async function() { 135 | await database.addCustomEvent(openEventType, { grammar }); 136 | 137 | await database.clearData(); 138 | const events = await database.getCustomEvents(); 139 | 140 | assert.deepEqual(events, []); 141 | }); 142 | it("clears db containing multiple customEvents", async function() { 143 | await database.addCustomEvent(openEventType, { grammar}); 144 | await database.addCustomEvent(deprecateEventType, { message }); 145 | 146 | await database.clearData(); 147 | const events = await database.getCustomEvents(); 148 | 149 | assert.deepEqual(events, []); 150 | }); 151 | it("clears db containing timing", async function() { 152 | await database.addTiming("load", 100); 153 | 154 | await database.clearData(); 155 | const timings = await database.getTimings(); 156 | 157 | assert.deepEqual(timings, []); 158 | }); 159 | it("clearing an empty db does not throw an error", async function() { 160 | const counters = await database.getCounters(); 161 | assert.deepEqual(counters, {}); 162 | 163 | const events = await database.getCustomEvents(); 164 | assert.deepEqual(events, []); 165 | 166 | await database.clearData(); 167 | }); 168 | }); 169 | }); 170 | } 171 | -------------------------------------------------------------------------------- /test/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, assert } from "chai"; 2 | import { AppName, DailyStatsReportIntervalInMs, HasSentOptInPingKey, 3 | LastDailyStatsReportKey, Metrics, StatsOptOutKey, StatsStore } from "../src/index"; 4 | import * as sinon from "sinon"; 5 | import * as chai from "chai"; 6 | import * as chaiAsPromised from "chai-as-promised"; 7 | import { getGUID } from "../src/uuid"; 8 | 9 | const ReportingLoopIntervalInMs = DailyStatsReportIntervalInMs / 6; 10 | 11 | chai.use(chaiAsPromised); 12 | 13 | const ACCESS_TOKEN = "SUPER_AWESOME_ACCESS_TOKEN"; 14 | 15 | const getAccessToken = () => { 16 | return ACCESS_TOKEN; 17 | }; 18 | 19 | describe("StatsStore", function() { 20 | const version = "1.2.3"; 21 | let store: StatsStore; 22 | let postStub: sinon.SinonStub; 23 | let shouldReportStub: sinon.SinonStub; 24 | const pingEvent = { eventType: "ping", dimensions: {optIn: false} }; 25 | 26 | beforeEach(function() { 27 | store = new StatsStore(AppName.Atom, version, false, getAccessToken); 28 | postStub = sinon.stub(store, "post"); 29 | }); 30 | afterEach(async function() { 31 | store.end(); 32 | localStorage.clear(); 33 | await store.clearData(); 34 | }); 35 | describe("constructor", function() { 36 | let clock: sinon.SinonFakeTimers; 37 | beforeEach(function() { 38 | clock = sinon.useFakeTimers(); 39 | postStub.resolves({ status: 200 }); 40 | }); 41 | afterEach(function() { 42 | clock.restore(); 43 | }); 44 | it("reports stats when hasReportingIntervalElapsed returns true", function() { 45 | shouldReportStub = sinon.stub(store, "hasReportingIntervalElapsed").callsFake(() => true); 46 | setTimeout(() => { 47 | sinon.assert.called(postStub); 48 | }, ReportingLoopIntervalInMs + 100); 49 | }); 50 | it("does not report stats when shouldReportDailyStats returns false", function() { 51 | shouldReportStub = sinon.stub(store, "hasReportingIntervalElapsed").callsFake(() => false); 52 | postStub.resolves({ status: 200 }); 53 | setTimeout(() => { 54 | sinon.assert.notCalled(postStub); 55 | }, ReportingLoopIntervalInMs + 100); 56 | }); 57 | }); 58 | describe("reportStats", async function() { 59 | let fakeEvent: Metrics; 60 | 61 | beforeEach(async function() { 62 | await store.incrementCounter("commit"); 63 | fakeEvent = await store.getDailyStats(); 64 | }); 65 | 66 | it("handles success case", async function() { 67 | postStub.resolves({ status: 200 }); 68 | await store.reportStats(); 69 | const event: Metrics = postStub.getCalls()[0].args[0]; 70 | 71 | // Remove the date from both events since it can be different. 72 | fakeEvent.dimensions = {...fakeEvent.dimensions, date: "mock"}; 73 | event.dimensions = {...event.dimensions, date: "mock"}; 74 | 75 | assert.deepEqual(event, fakeEvent); 76 | 77 | // counters should be cleared in success case 78 | const counters = (await store.getDailyStats()).measures; 79 | assert.deepEqual(counters, {}); 80 | }); 81 | it("handles failure case", async function() { 82 | postStub.resolves({ status: 500 }); 83 | await store.reportStats(); 84 | 85 | const event: Metrics = postStub.getCalls()[0].args[0]; 86 | 87 | // Remove the date from both events since it can be different. 88 | fakeEvent.dimensions = {...fakeEvent.dimensions, date: "mock"}; 89 | event.dimensions = {...event.dimensions, date: "mock"}; 90 | 91 | assert.deepEqual(event, fakeEvent); 92 | 93 | // counters should not be cleared if we fail to send daily stats 94 | const counters = (await store.getDailyStats()).measures; 95 | assert.deepEqual(counters, fakeEvent.measures); 96 | }); 97 | it("does not report stats when app is in dev mode", async function() { 98 | const storeInDevMode = new StatsStore(AppName.Atom, version, true, getAccessToken); 99 | postStub = sinon.stub(storeInDevMode, "post").resolves( { status: 200 }); 100 | await storeInDevMode.reportStats(); 101 | sinon.assert.notCalled(postStub); 102 | 103 | storeInDevMode.end(); 104 | }); 105 | it("sends a single ping event instead of reporting stats if a user has opted out", async function() { 106 | postStub.resolves({ status: 200 }); 107 | store.setOptOut(true); 108 | await store.reportStats(); 109 | await store.reportStats(); 110 | sinon.assert.calledWith(postStub, pingEvent); 111 | 112 | // event should only be sent the first time even though we call report stats 113 | sinon.assert.callCount(postStub, 1); 114 | }); 115 | 116 | it("does not report stats twice when called concurrently", async function() { 117 | const store1 = new StatsStore(AppName.Atom, version, false, getAccessToken); 118 | const store2 = new StatsStore(AppName.Atom, version, false, getAccessToken); 119 | 120 | const stubFetch = sinon.stub(window, "fetch"); 121 | stubFetch.returns(Promise.resolve(new Response(new ReadableStream(), { 122 | status: 200, 123 | headers: { "Content-type": "application/json" }, 124 | }))); 125 | 126 | await Promise.all([ 127 | store1.reportStats(), 128 | store2.reportStats(), 129 | ]); 130 | sinon.assert.calledOnce(stubFetch); 131 | 132 | store1.end(); 133 | store2.end(); 134 | }); 135 | }); 136 | describe("addTimer", async function() { 137 | it("does not add timer in dev mode", async function() { 138 | const storeInDevMode = new StatsStore(AppName.Atom, version, true, getAccessToken); 139 | await storeInDevMode.addTiming("load", 100); 140 | const stats = await storeInDevMode.getDailyStats(); 141 | assert.deepEqual(stats.timings, []); 142 | 143 | storeInDevMode.end(); 144 | }); 145 | it("does not add timer if user has opted out", async function() { 146 | store.setOptOut(true); 147 | await store.addTiming("load", 100); 148 | const stats = await store.getDailyStats(); 149 | assert.deepEqual(stats.timings, []); 150 | }); 151 | it("does add timer if user has opted in and not in dev mode", async function() { 152 | await store.addTiming("load", 100); 153 | const stats = await store.getDailyStats(); 154 | assert.deepEqual(stats.timings.length, 1); 155 | }); 156 | }); 157 | describe("incrementCounter", async function() { 158 | const counterName = "commits"; 159 | it("does not increment counter in dev mode", async function() { 160 | const storeInDevMode = new StatsStore(AppName.Atom, version, true, getAccessToken); 161 | await storeInDevMode.incrementCounter(counterName); 162 | const stats = await storeInDevMode.getDailyStats(); 163 | assert.deepEqual(stats.measures, {}); 164 | 165 | storeInDevMode.end(); 166 | }); 167 | it("does not increment counter if user has opted out", async function() { 168 | store.setOptOut(true); 169 | await store.incrementCounter(counterName); 170 | const stats = await store.getDailyStats(); 171 | assert.deepEqual(stats.measures, {}); 172 | }); 173 | it("does increment counter if non dev and user has opted in", async function() { 174 | await store.incrementCounter(counterName); 175 | const stats = await store.getDailyStats(); 176 | assert.deepEqual(stats.measures, {[counterName]: 1}); 177 | }); 178 | }); 179 | describe("post", async function() { 180 | it("sends the auth header if one exists", async function() { 181 | const storeWithToken = new StatsStore(AppName.Atom, version, false, getAccessToken); 182 | const fetch: sinon.SinonStub = sinon.stub(storeWithToken, "fetch").resolves({ status: 200 }); 183 | await storeWithToken.reportStats(); 184 | assert.deepEqual(fetch.args[0][1].headers, { 185 | "Content-Type": "application/json", 186 | "Authorization": `token ${ACCESS_TOKEN}`, 187 | }); 188 | 189 | storeWithToken.end(); 190 | }); 191 | it("does not send the auth header if the auth header is falsy", async function() { 192 | const storeWithFalseHeader = new StatsStore(AppName.Atom, version, false, () => ""); 193 | const fetch: sinon.SinonStub = sinon.stub(storeWithFalseHeader, "fetch").resolves({ status: 200 }); 194 | await storeWithFalseHeader.reportStats(); 195 | assert.deepEqual(fetch.args[0][1].headers, { 196 | "Content-Type": "application/json", 197 | }); 198 | 199 | storeWithFalseHeader.end(); 200 | }); 201 | it("logs a message in the console when in verbose mode", async function() { 202 | const consoleLogStub = sinon.stub(console, "log"); 203 | 204 | const storeVerbose = new StatsStore(AppName.Atom, version, false, getAccessToken, {verboseMode: true}); 205 | const fetch: sinon.SinonStub = sinon.stub(storeVerbose, "fetch").resolves({ status: 200 }); 206 | await storeVerbose.reportStats(); 207 | assert.deepEqual(fetch.args[0][1].headers, { 208 | "Content-Type": "application/json", 209 | "Authorization": `token ${ACCESS_TOKEN}`, 210 | }); 211 | sinon.assert.calledWith(consoleLogStub, "Sending metrics"); 212 | consoleLogStub.restore(); 213 | 214 | storeVerbose.end(); 215 | }); 216 | it("logs a message in the console when in dev mode and dev logging is enabled", async function() { 217 | const consoleLogStub = sinon.stub(console, "log"); 218 | 219 | const storeLogging = new StatsStore(AppName.Atom, version, true, getAccessToken, {logInDevMode: true}); 220 | const fetch: sinon.SinonStub = sinon.stub(storeLogging, "fetch").resolves({ status: 200 }); 221 | await storeLogging.reportStats(); 222 | assert.deepEqual(fetch.args[0][1].headers, { 223 | "Content-Type": "application/json", 224 | "Authorization": `token ${ACCESS_TOKEN}`, 225 | }); 226 | sinon.assert.calledWith(consoleLogStub, "Sending metrics"); 227 | consoleLogStub.restore(); 228 | storeLogging.end(); 229 | }); 230 | }); 231 | describe("setOptOut", async function() { 232 | it("sets the opt out preferences in local storage", async function() { 233 | assert.notOk(localStorage.getItem(StatsOptOutKey)); 234 | await store.setOptOut(true); 235 | assert.ok(localStorage.getItem(StatsOptOutKey)); 236 | }); 237 | it("does not send ping in dev mode", async function() { 238 | const storeInDevMode = new StatsStore(AppName.Atom, version, true, getAccessToken); 239 | await storeInDevMode.setOptOut(true); 240 | sinon.assert.notCalled(postStub); 241 | 242 | storeInDevMode.end(); 243 | }); 244 | it("sends one status ping when status is changed", async function() { 245 | const sendPingStub = sinon.stub(store, "sendOptInStatusPing").resolves(true); 246 | await store.setOptOut(true); 247 | sinon.assert.calledWith(sendPingStub, false); 248 | 249 | sendPingStub.reset(); 250 | await store.setOptOut(true); 251 | sinon.assert.notCalled(sendPingStub); 252 | 253 | await store.setOptOut(false); 254 | sinon.assert.calledWith(sendPingStub, true); 255 | }); 256 | }); 257 | describe("hasReportingIntervalElapsed", function() { 258 | it("returns false if not enough time has elapsed since last report", function() { 259 | localStorage.setItem(LastDailyStatsReportKey, (Date.now()).toString()); 260 | assert.isFalse(store.hasReportingIntervalElapsed()); 261 | }); 262 | it("returns true if enough time has elapsed since last report", function() { 263 | localStorage.setItem(LastDailyStatsReportKey, (Date.now() - DailyStatsReportIntervalInMs - 1).toString()); 264 | assert.isTrue(store.hasReportingIntervalElapsed()); 265 | }); 266 | 267 | it("returns true if enough time has elapsed since last report for a custom interval", function() { 268 | const reportingFrequency = 100; 269 | const storeWithFrequency = new StatsStore(AppName.Atom, version, false, getAccessToken, {reportingFrequency}); 270 | 271 | localStorage.setItem(LastDailyStatsReportKey, (Date.now() - reportingFrequency + 1).toString()); 272 | assert.isFalse(storeWithFrequency.hasReportingIntervalElapsed()); 273 | 274 | localStorage.setItem(LastDailyStatsReportKey, (Date.now() - reportingFrequency - 1).toString()); 275 | assert.isTrue(storeWithFrequency.hasReportingIntervalElapsed()); 276 | 277 | storeWithFrequency.end(); 278 | }); 279 | }); 280 | describe("sendOptInStatusPing", async function() { 281 | it("handles success", async function() { 282 | postStub.resolves({status: 200}); 283 | await store.sendOptInStatusPing(false); 284 | 285 | sinon.assert.calledWith(postStub, pingEvent); 286 | 287 | assert.strictEqual(localStorage.getItem(HasSentOptInPingKey), "1"); 288 | }); 289 | it("handles error", async function() { 290 | postStub.resolves({ status: 500 }); 291 | await store.sendOptInStatusPing(false); 292 | 293 | sinon.assert.calledWith(postStub, pingEvent); 294 | 295 | assert.strictEqual(localStorage.getItem(HasSentOptInPingKey), null); 296 | }); 297 | }); 298 | describe("getDailyStats", async function() { 299 | it("event has all the fields we expect", async function() { 300 | const gitHubUser = "annthurium"; 301 | store.setGitHubUser(gitHubUser); 302 | 303 | const counter1 = "commits"; 304 | const counter2 = "openGitPane"; 305 | await store.incrementCounter(counter1); 306 | await store.incrementCounter(counter2); 307 | await store.incrementCounter(counter2); 308 | 309 | await store.addCustomEvent("open", { grammar: "javascript" }); 310 | await store.addCustomEvent("deprecate", { message: "oh noes" }); 311 | 312 | const timingEventName = "load"; 313 | const loadTimeInMs1 = 100; 314 | const loadTimeInMs2 = 200; 315 | const metadata = { meta: "data"}; 316 | await store.addTiming(timingEventName, loadTimeInMs1, metadata); 317 | await store.addTiming(timingEventName, loadTimeInMs2, metadata); 318 | 319 | const event = await store.getDailyStats(); 320 | 321 | const dimensions = event.dimensions; 322 | expect(dimensions.appVersion).to.eq(version); 323 | expect(dimensions.platform).to.eq(process.platform); 324 | expect(dimensions.eventType).to.eq("usage"); 325 | expect(dimensions.guid).to.eq(getGUID()); 326 | expect(dimensions.language).to.eq(process.env.LANG); 327 | expect(dimensions.gitHubUser).to.eq(gitHubUser); 328 | 329 | const counters = event.measures; 330 | expect(counters).to.deep.include({ [counter1]: 1}); 331 | expect(counters).to.deep.include({ [counter2]: 2}); 332 | 333 | const customEvents = event.customEvents; 334 | expect(customEvents.length).to.eq(2); 335 | 336 | const timings = event.timings; 337 | expect(timings.length).to.eq(2); 338 | }); 339 | it("handles null gitHubUser", async function() { 340 | const event = await store.getDailyStats(); 341 | expect(event.dimensions.gitHubUser).to.be.null; 342 | }); 343 | }); 344 | }); 345 | -------------------------------------------------------------------------------- /test/uuid.spec.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | import { getGUID, StatsGUIDKey } from "../src/uuid"; 3 | 4 | describe("uuid", () => { 5 | describe("getGUID", function() { 6 | it("uses cached GUID if one exists already", function() { 7 | const GUID = getGUID(); 8 | const GUID2 = getGUID(); 9 | assert.deepEqual(GUID, GUID2); 10 | }); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "moduleResolution": "node", 5 | "target": "es2017", 6 | "lib": ["es2015", "dom"], 7 | "noFallthroughCasesInSwitch": true, 8 | "noImplicitAny": true, 9 | "noImplicitReturns": true, 10 | "noUnusedLocals": false, 11 | "sourceMap": true, 12 | "strict": true, 13 | "strictNullChecks": true, 14 | "outDir": "./out/" 15 | }, 16 | "include": ["./src/**/*"], 17 | "exclude": [ 18 | "node_modules", 19 | "dist", 20 | "out", 21 | ], 22 | "compileOnSave": true 23 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "jsRules": {}, 7 | "rules": { 8 | "interface-name": false, 9 | "object-literal-sort-keys": false, 10 | "only-arrow-functions": false, 11 | "no-console": false, 12 | "no-unused-expression": false, 13 | "no-var-requires": false, 14 | "ordered-imports": false 15 | }, 16 | "rulesDirectory": [] 17 | } 18 | --------------------------------------------------------------------------------