diff --git a/.circleci/config.yml b/.circleci/config.yml index a8efa3be..4054f93e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -14,11 +14,23 @@ jobs: - run: node --version - run: npm --version - run: npm install - - run: npm run format-enforce - - run: npm run test-unit + - run: npm run test-unit + - run: npm run test-int + - run: npm run test-proxy + - run: npm run test-e2e workflows: version: 2 default_workflow: jobs: - - build_and_test \ No newline at end of file + - build_and_test + nightly: + triggers: + - schedule: + cron: "0 0 * * *" + filters: + branches: + only: + - master + jobs: + - build_and_test diff --git a/.clang-format b/.clang-format index 8d1c3c31..7d6cf97e 100644 --- a/.clang-format +++ b/.clang-format @@ -1,3 +1,3 @@ Language: JavaScript BasedOnStyle: Google -ColumnLimit: 100 +ColumnLimit: 80 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7aebbac8..4b210ac9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,3 @@ -built/ node_modules/ -selenium/ -selenium_test/ -typings/ -.idea/ -npm-debug.log -.vscode +dist/ +downloads/ diff --git a/.npmignore b/.npmignore index e99ce1b0..be98b5ff 100644 --- a/.npmignore +++ b/.npmignore @@ -1,17 +1,10 @@ +.circleci/ +dist/spec/ +dist/**/*.spec-* +downloads/ lib/ -selenium/ spec/ -built/spec/ -e2e_spec/ -built/e2e_spec/ -.clang-format .gitignore -.gitattributes -.github/ -.idea/ .npmignore -circle.yml -npm-debug.log -release.md -tsconfig.json +tsconfig.json \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 7424e028..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,670 +0,0 @@ -# 12.1.9 - -Updates `adm-zip` to version `0.5.2`. - -# 12.1.8 - -Fixes for macOS ChromeDriver download. If you are using a macOS and are -running into a `WebDriver.createSession` error, run -`webdriver-manager clean` to remove the downloaded ChromeDriver -before running `webdriver-manager update`. - -- ([4f5f736](https://github.com/angular/webdriver-manager/commit/4f5f7369790c9ae604b8b51a08b645a89cc4c7b7)) - fix(chromedriver): get most recent version on x64 windows if multiple major versions exist (#473) - -- ([17db8a2](https://github.com/angular/webdriver-manager/commit/17db8a2ad5401ab68c7a400f419760edd9c3e9e3)) - fix(binaries): filter m1 mac chromedriver versions (#478) - -# 12.1.7 - -Updates to Chromedriver to the LATEST_RELEASE. This should work from here on in when Chrome is updated to a new version. - -- [64795b7](https://github.com/angular/webdriver-manager/commit/64795b753b3d00ad2d1f4ac98c531f9e147f3b3e) chore(chrome) get latest chromedriver from LATEST_RELEASE (#418) - - - changed the get latest chromedriver to use the URL getting the latest first, then downloading that specific version. - - removed unused imports. - -# 12.1.6 - -Update the max Chrome version to 76. - -# 12.1.5 - -Update the max Chrome version to 75. - -# 12.1.4 - -Fixes issues introduced by 12.1.3 (--versions.chrome flag and status command did not work). Also prevents downloading version 75 using the config.json file. - -## Bug Fix - -- ([0a6ce24](https://github.com/angular/webdriver-manager/commit/0a6ce24e73ae06319bcafa472e22a2fe99d139e1)) - fix(chromedriver): version fixes for update, status, and start (#380) - - - Set the max versioning set in config.json. This will need to be updated on -every release of chromedriver. This will "fix" chrome and chromedriver -mismatches until Chrome 75 comes out. When it does there will have to be -an update for this again. Possible future work would allow a user -to set this via flag. Example --maxVersions.chrome "74." - - Create a generic way to get a valid version for Chromedriver. If -presented with 2.x, change this to 2.x.0. If presented with a 74.x.x.x, -chop off the last set of numbers and change this to 74.x.x - - Fixes the status command during a semver check. - - Fixes the update and start command when starting a specific version of -chromedriver - -# 12.1.3 - -Fixes download issues for chromedriver version 74+. - -## Bug Fix - -- ([476c117](https://github.com/angular/webdriver-manager/commit/476c117ac10539634d1c8f8973aa94012ed017a4)) - fix(chromedriver): support downloads for chromedriver beyond 2.46 (#377) - - Versions of Chromdriver were versioned as 2.xx. We previously used to -tack on a '.0' at the end to make it a semver version. This is why it -was not downloading 74.0.3729.6. We now have to change 74.0.3729.6 to be -a semver. We will do this by grabbing 74.0.3729 with a regex. - - This should work when downloading the latest chromedriver version since -2.46.0 < 74.0.3729. If Chromedriver releases 75 but we are still on -Chrome 74, this will still break in this version of webdriver-manager. -This does not prevent latest Chromedriver and latest Chrome mismatches. -If you run into an issue where Chromedriver is mismatched with Chrome, -use the `--versions.chrome` flag to pass in the version to download. - -# 12.1.2 - -Fixes download issues for the selenium jar file. - -## Bug Fix - -- ([7dc17ef](https://github.com/angular/webdriver-manager/commit/7dc17ef36e93f71bc63475612e343ffb84efec0f)) - fix(selenium): download jar files and not zip files (#371) - - This fix is here because selenium now has .zip files in the .xml. This worked previously since - there were no .zip files and it would find a version that matched the latest jar file. - - Also do not download alpha versions since we are also not downloading beta versions of the jar - file. - - closes #370 - - -# 12.1.1 - -## Bug Fix - -- ([f17b226](https://github.com/angular/webdriver-manager/commit/f17b226342173e59b4d2fac54632185c26ca7086)) - Fix(types): Operator '==' cannot be applied to types 'string | string[]' and 'number' (#297) - - - build-enforced style changes - - add package-lock.json - -- ([7dbc1df](https://github.com/angular/webdriver-manager/commit/7dbc1dfbccc60c8836e7c1d390fd5562e0af5b9d)) - fix(clean): remove existing chrome meta files from update on clean command (#279) - - - change file name from chromedriver-response.xml to chrome-response.xml -- ([0a4c065](https://github.com/angular/webdriver-manager/commit/0a4c0658b0725154cba07ad6e7125c1dd504fa3d)) - fix(appium): change appiumPort to seleniumPort for selenium server request (#228) - -## Dependencies - -- ([6775421](https://github.com/angular/webdriver-manager/commit/6775421ea9e40db1bf547bcedcb716ba35106a80)) - deps(package): update npm audit. - - - Updates to vulnerable to zipslip. - - Update all dependencies with `npm audit fix --force`. - - closes #314 - -- ([a80ccd2](https://github.com/angular/webdriver-manager/commit/a80ccd22d494e10e8c3c6ef9af22abf38496cb14)) - deps(appium): bump up the appium version (#258) - - -# 12.0.6 - -## Bug Fix - -- ([708ade3](https://github.com/angular/webdriver-manager/commit/708ade31564ab5a48fbfcff80c37370fdc4f659a)) - fix(responses): response xml and json files (#247) - -# 12.0.5 - -## Bug Fix - -- ([242a72f](https://github.com/angular/webdriver-manager/commit/242a72ffc93037d651c9805e09b4fb30318d9f05)) - feat(start): start selenium without making web requests (#232) - -# 12.0.4 - -## Bug Fixes -- ([52d8a23](https://github.com/angular/webdriver-manager/commit/52d8a23f2d5d5021d1d9d302c492bf78a233a79d)) - fix(ignoressl): pass option to both binary and config source - - closes #207. - -- ([5af1c1c](https://github.com/angular/webdriver-manager/commit/5af1c1cdfb2d718004b02e9c0325ea6e758e78f1)) - fix(cache): change timestamp to 1 hour instead of 10 hours (#223) - - closes #221 - -# 12.0.3 - -## Bug Fixes - -- ([bb13882](https://github.com/angular/webdriver-manager/commit/bb13882f1d111fc0c16032be33a7b8dc7b1a797c)) - feat(gecko): Improve error message when Github api limit reached. (#217) - - the user. Also, API limit is reached, the error message now directly informs - when any other failure occurs the status code is reported. - This should hopefully give more info for issue #216. -- ([2cffd30](https://github.com/angular/webdriver-manager/commit/2cffd30d9ef87c5b53433f2aa73eda92b4251a76)) - fix(ignoressl): pass proxy and ignore ssl down to the binary and config source (#208) - - closes #207 and closes #221 - -# 12.0.2 - -- ([0bdf6a4](https://github.com/angular/webdriver-manager/commit/0bdf6a465ae2a4b106bb5ff948718ef4ae3f31ad)) - deps(typescript): use typescript@~2.0.0. fix any types (#203) - -# 12.0.1 - -- ([6209666](https://github.com/angular/webdriver-manager/commit/620966611f48504619a594b582060ba04a61b3a7)) - fix(gecko): add additional check for OS when getting latest (#200) - -# 12.0.0 - -### Changes to update - -This release gets the latest release for selenium standalone, chromedriver, -iedriver, and gecko driver by downloading and parsing either a json or xml file. -These json or xml files are cached in the selenium directory. This means for -users that provide an alternative cdn will also be required to provide the proper -xml or json server response to find these binaries. - -Since we are always downloading the latest, the `config.json` versions will no -longer be a place to override these. - -### Changes to start - -If a new release is out and you have old binaries, running `webdriver-manager -start` without specifying any versions should throw an error. The error will -tell the user that the binary is not present. - -### Changes to status - -Since we are downloading the latest and not maintaining a default version in -`config.json`, we are dropping the default tag. - -## Features - -- ([fe309ef](https://github.com/angular/webdriver-manager/commit/fe309ef0d85081592662164d4a24d79b0f2ed5cf)) - feat(latest): get the latest version from the cdn (#198) - - This reads the xml from the CDN to get the latest chromedriver, iedriver, - or standalone version if the version is 'latest'. If the release is from - Github, use the Github API to get the releases. Also store the downloaded - information to a cache in the output directory (default: selenium/). If - the file is older than one hour it will be rewritten. - - When getting the status, we are no longer showing the default version. - Default versions will be deprecated and will be removed from the config.json - file. - - When starting the standalone server, use the 'latest' version by default - unless specified by --versions.{binary} flag. - - Change the gulp update task to use 3.0.0-beta4 so Firefox tests will pass. - -# 11.1.1 - -## Bug Fixes - -- ([70614a2](https://github.com/angular/webdriver-manager/commit/70614a23e289088c852f5c0162a947488ffc77e0)) - fix(ie): Use 32-bit version by default for IEDriver (#181) - - closes #180 -- ([6f9a2ab](https://github.com/angular/webdriver-manager/commit/6f9a2abbf7d16f35e342f963543706ff3e1c45a1)) - fix(gecko): Respect versions.gecko in start command. (#184) - - Also bump the geckodriver version to latest. - -## Dependencies - -- ([5881c5b](https://github.com/angular/webdriver-manager/commit/5881c5bb49f330abd7804e2605df46901e87bf2a)) - deps(update): update devDependencies (#187) - - -# 11.1.0 - -- Update to set the default chrome driver version to 2.26 - -## Features - -- ([72e3d9f](https://github.com/angular/webdriver-manager/commit/72e3d9f341f1d0ba190036a72938e727d83840c7)) - feat(status): show the last downloaded version when using status (#177) - - - added a test to run update, then checks status for labels - - closes #172 - -## Bug Fixes - -- ([a3b46c7](https://github.com/angular/webdriver-manager/commit/a3b46c7a2ae59357b00fe5ce81d36964d6b0d45c)) - fix(iedriver): if downloading x64, use x64 version on start command (#173) - - - clang formatting - - closes #147 - -# 10.3 - -- Minor version update so users still on ES5/selenium 2.x can access appium/mobile fixes - See https://github.com/angular/webdriver-manager/commits/10.3.0 for details - - -# 11.0.0 - -## Breaking Change: - -- Requires node 6 since node 6 is in long term support. See (node LTS Schedule)[https://github.com/nodejs/LTS#lts-schedule]. - -## Features -- ([b5638ef](https://github.com/angular/webdriver-manager/commit/b5638ef0861843e1d42220af515adc3e03a2b65a)) - feat(update): on update, write full binary paths to file (#140) - - - Adding back in curl calls, these were removed on the new - `Downloader.getFile`. Add curl call to reflect proxies. - - - Fix output dir to read from update's options instead of Config - - - Feature will help directConnect users for Protractor. The file - will keep track of the last binary version as well as all other - binaries downloaded. - - The file will be created in the output directory. By default this is - `selenium/update-config.json`. On `clean` this file will be removed. - - ``` - webdriver-manager update --versions.chrome=2.20 --standalone=false - --gecko=false - ``` - - file created: - ``` - { - "chrome": { - "last": "/opt/src/webdriver-manager/selenium/chromedriver_2.20", - "all": ["/opt/src/webdriver-manager/selenium/chromedriver_2.20"] - } - } - ``` - - then the user wants to use 2.25: - - ``` - webdriver-manager update --versions.chrome=2.25 --standalone=false - --gecko=false - - ``` - - file created: - ``` - { - "chrome": { - "last": "/opt/src/webdriver-manager/selenium/chromedriver_2.25", - "all": ["/opt/src/webdriver-manager/selenium/chromedriver_2.20", - "/opt/src/webdriver-manager/selenium/chromedriver_2.25"] - } - } - ``` - -- ([473ab3e](https://github.com/angular/webdriver-manager/commit/473ab3e40c44468bb79e2a23d7b12753cf6e2b4d)) - feat(android): match android arch to os.arch (#164) - - The default was x86-64, but x86 cannot be emulated on ARM. This makes more sense -- ([c864c9a](https://github.com/angular/webdriver-manager/commit/c864c9af35514a4b5bf8a1d82b4339b39e5ac574)) - feat(shutdown): do not error if you try to shutdown a server which is already off (#162) - - When scripting, you might want to defensively run a `shutdown` command. If the shutdown fails - because the server is already off, you don't care. If it fails for another reason, you do care. - So I made trying to shutdown a server which is already off just a warning. I added a flag in case - you want the old behavior though. - -- ([338fffd](https://github.com/angular/webdriver-manager/commit/338fffddf68ac2767aa5c226ba5374451b9e5308)) - feat(quiet/verbose): add `--quiet` and `--verbose` flags to control the level of output (#156) - - I added the `--quiet` flag for cases like: - where currently the start --detach; ./tests.sh; webdriver-manager shutdown` - selenium server output will get mixed in with other output. - I also added the `--verbose` flag for `webdriver-manager update` in case you *really* wanted to - see all the output which gets eaten by using `--android-accept-licenses`. - -- ([91e36a3](https://github.com/angular/webdriver-manager/commit/91e36a3e56e712af2c104eafc45eeeba5997ad6a)) - feat(android on windows): Support android VMs on windows (#154) - - Closes https://github.com/angular/webdriver-manager/issues/51 - -- ([d533b03](https://github.com/angular/webdriver-manager/commit/d533b0389ac8a43b815890a644fdb9aa403ec769)) - feat(start android): extend the --detach flag to wait for appium/android (#141) - -## Bug fixes - -- ([26586f1](https://github.com/angular/webdriver-manager/commit/26586f1b341e02229d73d40827a9c1af2197ebb3)) - fix(start): wait for emulated android to really be ready before signaling (#161) - - Before, we were just waiting for the emulator to be running, rather than waiting for the OS to be - booted up and ready to instance chrome. - While I was doing that I moved some stuff into `lib/utils.ts` since I felt like too much of - `lib/cmds/start.ts` was being devoted to this one feature. - Also closes https://github.com/angular/webdriver-manager/issues/166 -- ([a7c6eb5](https://github.com/angular/webdriver-manager/commit/a7c6eb5d3d1caed2afea1ef896753d53f4ea14ed)) - fix(update/android): 2a1505f broke android -- ([3ee3e1a](https://github.com/angular/webdriver-manager/commit/3ee3e1a328087cb8c5bf869e00a325cfdeb80f6d)) - fix(fs): path.join does not handle absolute paths as desired (#152) -- ([deead0f](https://github.com/angular/webdriver-manager/commit/deead0fc55ecd00b282aedc234592181746a307c)) - fix(downloader): destroy the request after receiving the header (#144) - - Otherwise we’ll won’t terminate until the whole file was downloaded, even though we don’t need it. -- ([c16bf90](https://github.com/angular/webdriver-manager/commit/c16bf9053fc90e4b5e89ab867c514d0622ab0716)) - chore(es6): allow to use es6 promises (#160) - - - with node 6 on LTS, we can update the tsconfig to es6 - - update travis tests to use node 6 and 7 - -# 10.2.10 - -- Since 10.2.9 produced breaking changes, released as version 11.0.0 -- Version 10.2.8 is the same as 10.2.10 due to [issue #170](https://github.com/angular/webdriver-manager/issues/170). - -# 10.2.8 - -## Features - -- ([1f9713a](https://github.com/angular/webdriver-manager/commit/1f9713aff1e7d44de900ed3c74abac532d3e25ff)) - feat(start and shutdown): Added `--detach` option for `start` command and new `shutdown` command - (#130) - -- ([88cf46b](https://github.com/angular/webdriver-manager/commit/88cf46b715250559ba8a726370a83c5c2f4daed1)) - feat(version): have a way to get the package version (#136) - - closes #119 - -## Bug fixes -- ([5966b6a](https://github.com/angular/webdriver-manager/commit/5966b6ac7329878e9e16f5b1b88261c5b7f7e438)) - fix(cli): fix setting flag to false (#135) - - - This fixes `webdriver-manager update --gecko=false` - - This does not fix `webdriver-manager update --gecko=0`. Minimist interprets 0 as true. - - Add options and programs unit tests - closes #110 - -- ([35676ee](https://github.com/angular/webdriver-manager/commit/35676ee70c816d43f045fa33d02e41bf502a3a14)) - fix(gecko): follow redirects for content-length (#133) - -# 10.2.7 - -## Features - -- ([66776a0](https://github.com/angular/webdriver-manager/commit/66776a0edc97e0b2718f2fdf4eeb2c2c8b40df73)) - feat(start): add way to programmatically detect when the selenium server is running (#120) - -## Bug fixes - -- ([dc2f9f9](https://github.com/angular/webdriver-manager/commit/dc2f9f99ebd9675b02addf06732a4d8d348046bc)) - fix(cli): fix default option values, boolean and string handling (#110) (#122) - - - default option values initialize properly for `minimist` - - user-supplied boolean-type option values respected - - string-type option values are always strings - - simplify boolean-type option value access - -- ([88d6105](https://github.com/angular/webdriver-manager/commit/88d6105f538f075968c152935131bf19bf289532)) - fix(gecko): Update geckodriver to 0.11.0 and fix suffixes. (#128) - - Fixes #111 - -- ([707e015](https://github.com/angular/webdriver-manager/commit/707e015737ee3ca4b26b6d89979251f8d8c2d11d)) - fix(android): fixed four things for android: (#116) - - - Make appium default to 1.6.0 (Android N didn't work on 1.5.x) - - Make virtual devices default to `google_apis` - - Don't delete old virtual devices on update - - Update documentation - -- ([9fe4b22](https://github.com/angular/webdriver-manager/commit/9fe4b226d58fbbce2e9cf49df58f45dee7f13cf2)) - fix typo in webdriver-manager/spec/files (#125) - -# 10.2.6 - -## Features - -- ([f892ec4](https://github.com/angular/webdriver-manager/commit/f892ec41c09c210527998c966a69edc081cf418e)) - chore(chromedriver): update chromedriver version to 2.25 - -# 10.2.5 - -## Bug Fixes - -- ([b103850](https://github.com/angular/webdriver-manager/commit/b1038500466fe790cc8e3c2ff82dc3c7eb3796ba)) - fix(update): fix undefined gecko getBoolean error (#113) - - closes #107 -- ([7fbacf5](https://github.com/angular/webdriver-manager/commit/7fbacf5bc902dd3ccd1c9fbf285c8ca9a1e48ee3)) - fix(start): set the port when standalone server starts - - closes #106 - -# 10.2.4 - -## Bug Fixes - -- ([3984ea4](https://github.com/angular/webdriver-manager/commit/3984ea4e5cfd2edf0401a5e5310aecaaecb63555)) - fix(filemanager): respect proxy/ignoreSSL options in contentLength HEAD request (#101) - - -- ([946ee00](https://github.com/angular/webdriver-manager/commit/946ee005f7d316fd2d404c4bdbeae9a3802051af)) - fix(chrome_driver): use the x64 binary if chrome driver version is greater than 2.23 (#95) - - * fix(chrome_driver): use the x64 binary if chrome driver version is greater than 2.23 - * fix(chrome_driver): add semver to better determine version number - * refactor(chrome_driver): check first to see if we have valid semver or not - - closes #93 -- ([b183fad](https://github.com/angular/webdriver-manager/commit/b183fadd4ae0b47b0773d6979d090c74419ee327)) - fix(filemanager): Binaries can be downloaded from a custom CDN with alternate_cdn(#97) - - closes #96 - -## Features - -- ([5241fc1](https://github.com/angular/webdriver-manager/commit/5241fc14eaf2b5cdf4b35362f260f6973cea0b1e)) - chore(chromedriver): update chromedriver version to 2.24 (#92) - - Chromedriver < 2.24 has issues with Chrome 54+ - (https://bugs.chromium.org/p/chromedriver/issues/detail?id=1451). - -- ([61af7be](https://github.com/angular/webdriver-manager/commit/61af7be4edbaf070bdcc35dc85f11fb46ab9577e)) - feat(gecko): Add geckodriver, related config, and flags - - Users will still need 'marionette': true in their capabilities in order to use gecko driver. - -# 10.2.3 - -## Bug Fixes - -- ([fa48354](https://github.com/angular/webdriver-manager/commit/fa4835453385d4c79fcbba7bb6d408557c870bae)) - fix(downloader): fix against working proxy (#87) - -- ([d6597e8](https://github.com/angular/webdriver-manager/commit/d6597e8a06004888371cca12b8e803c7d44eaf8d)) - fix(start): add the correct flags for windows (#83) - - closes #68 -- ([c96090c](https://github.com/angular/webdriver-manager/commit/c96090c0f7cc24209b34f9634699e68669650070)) - fix(update): download standalone with proxy and ignore ssl (#81) - - closes #79 - -## Features - -- ([7ec082a](https://github.com/angular/webdriver-manager/commit/7ec082a1bcc7f262237a616ec96592c36c28b89a)) - feat(start): add a gecko driver path to the start command (#86) - -## Dependencies - -- ([fe85c94](https://github.com/angular/webdriver-manager/commit/fe85c94e8db0680be25461cd3ea1ef59fc4d8fa4)) - dep(types): update typescript, remove typings in favor of @types (#84) - -# 10.2.2 - -## Bug Fixes - -- ([236a8ec](https://github.com/angular/webdriver-manager/commit/236a8ec901133cb21247fc452d7ef7c9d5fed172)) - fix(downloader): increase timeouts and unlink sync on download errors (#75) - - closes #62 and #63 -- ([fa20ca8](https://github.com/angular/webdriver-manager/commit/fa20ca82e191b122ed49b144b8ebc53ee3b92a9d)) - fix(start): check if edge driver exists before adding to args (#73) - - closes #60 -- ([8b61b71](https://github.com/angular/webdriver-manager/commit/8b61b71410dbca6e205fbc599b954fe61a8ee937)) - fix(start): use ie32 if specified via command line (#72) - - closes #68 - -## Features - -- ([8346858](https://github.com/angular/webdriver-manager/commit/83468588fc21f7584b76a8c55afe659db045a4c9)) - feat(logging): add logging property to selenium standalone (#76) - - closes #61 -- ([18f9f1d](https://github.com/angular/webdriver-manager/commit/18f9f1dfea02cd8f5c5a2cd5f09130f0ca24f68a)) - chore(selenium): add dev/urandom to selenium start args to prevent startup delays in linux - - -# 10.2.1 - -upgrade to latest chrome driver and selenium standalone server versions - -# 10.2.0 - -- ([aa1b8b7](https://github.com/angular/webdriver-manager/commit/aa1b8b7cd9295f02b9bf69274e21eef1a7f3b7f0)) - feat(ios): iOS support (#57) - -# 10.1.0 - -## Bug Fixes - -- ([81c2aa3](https://github.com/angular/webdriver-manager/commit/81c2aa3ea6435934797b4d10c6734945484a641d)) - fix(iedriver): download url fix for iedriver (#54) - - closes #53 - -## Features - -- ([57372eb](https://github.com/angular/webdriver-manager/commit/57372ebd076f6b1ccaf41d920601e867b7b3084c)) - feat(edge): add Microsoft Edge support in CLI (#56) - - closes #55 -- ([d937245](https://github.com/angular/webdriver-manager/commit/d9372459c51a1aec553a79edaa32e497608a65de)) - feat(android): support android - -# 10.0.4 - -## Dependency Upgrades - -- ([970167a](https://github.com/angular/webdriver-manager/commit/970167a1b2db24fc8ca34db2994507ef0187ee7e)) - dep(typings): update typings (#42) - -## Bug Fixes - -- ([5073e23](https://github.com/angular/webdriver-manager/commit/5073e230574237047dd593a702f08f84907871bd)) - fix(folder): fix selenium folder location (#43) - -# 10.0.3 - -## Bug Fixes - -- ([d3724fb](https://github.com/angular/webdriver-manager/commit/d3724fbd9f6b0ceb481538f7f8f0088c8b004959)) - fix(config): simplify locating configuration file, selenium folder (#41) - - * Let the bin file decide which webdriver-manager to use - * Use the configuration file / package from the default position - * Selenium folder will always be located to webdriver-manager/selenium/ - -# 10.0.2 - -## Bug Fixes - -- ([5bca026](https://github.com/angular/webdriver-manager/commit/5bca0266118dcabf2e2782820e5c9095f6d16ed4)) - fix(config): configuration file local look up when used as a dependency (#33) - - closes #32 -- ([0cfcc88](https://github.com/angular/webdriver-manager/commit/0cfcc88f1383c400f72ea5e49f9600ff652f8214)) - fix(binary): Fix typo in fallback case when chalk isn't available. - - -# 10.0.1 - -## Bug Fixes - -- ([a6f1edd](https://github.com/angular/webdriver-manager/commit/a6f1edd782251c96d35e79a3bb78b70c2b137aa9)) - fix(global): fix finding config.json for global installs and release 10.0.1 (#23) - -# 10.0.0 - - -## Bug Fixes - -- ([70d32df](https://github.com/angular/webdriver-manager/commit/70d32df659f19510c25e97ea9a42c7f93813d448)) - fix(dir): check selenium dir and warn user that the folder does not exist (#17) - -- ([0ec1443](https://github.com/angular/webdriver-manager/commit/0ec14435379161259435edc7c766388941f1a846)) - fix(binary): file type, unzipping, and permissions - - closes #7, #16 - -- ([a073fd0](https://github.com/angular/webdriver-manager/commit/a073fd0e9d0290e52ac3a808643b069c71b196c3)) - fix(npm): use global, local, and project without env - - closes #20 - - -- ([6ccb9d8](https://github.com/angular/webdriver-manager/commit/6ccb9d8b9ac6daf79388c44e6d53f1d3d71fd3f8)) - fix(versions): versions option should stay consistent with existing webdriver-manager - - closes #6 - -- ([c34b05c](https://github.com/angular/webdriver-manager/commit/c34b05cc66849708a2fc515bc455a6a661c867d6)) - fix(bin): local, project, and global usage - -- ([4a0caf5](https://github.com/angular/webdriver-manager/commit/4a0caf5a69cacda01df87d4b4cc35092e519d267)) - fix(dep): fix dependency for chalk - -- ([15ae0e8](https://github.com/angular/webdriver-manager/commit/15ae0e815270c8af2441002492e3165edd3140df)) - fix(chmod): set permissions to 755 - -([1820fbc](https://github.com/angular/webdriver-manager/commit/1820fbc46ddc45b70911fb1678f1d99247ec7028)) - Initial commit - -## Features - -- ([374c3e7](https://github.com/angular/webdriver-manager/commit/374c3e719fce18a2f0a1b751b19bffb7d266cc69)) - feat(length): on update, check to see the file is the correct length - - closes #8 -- ([8c47291](https://github.com/angular/webdriver-manager/commit/8c472918ac73390890bbc39fcc4c7a2e86d3b262)) - feat(local): use the local version of webdriver-tool if it is installed - - closes #5 - -- ([f0622d2](https://github.com/angular/webdriver-manager/commit/f0622d2e173b68e4afcd409f9c0356c8a1c2652a)) - feat(logs): add chrome logs command line option - - - closes #11 -- ([3b30312](https://github.com/angular/webdriver-manager/commit/3b303129040b17292028452f13c73d62736f1216)) - feat(logger): update logging methods (#5) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index d50963ad..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,61 +0,0 @@ -Contributing -============ - -Questions ---------- - -Please ask support questions on [StackOverflow](http://stackoverflow.com/questions/tagged/webdriver-manager) or [Gitter](https://gitter.im/angular/webdriver-manager). - -Any questions posted to webdriver-manager's Github Issues will be closed with this note: - -Please direct general support questions like this one to an appropriate support channel, see https://github.com/angular/webdriver-manager/blob/master/CONTRIBUTING.md#questions. Thank you! - -Issues -====== - -If you have a bug or feature request, please file an issue. When submitting an issue, please include a reproducible case that we can actually run. - -Please format code and markup in your issue using [github markdown](https://help.github.com/articles/github-flavored-markdown). - - -Contributing to Source Code (Pull Requests) -=========================================== - -Loosely, follow the [Angular contribution rules](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md). - - * If your PR changes any behavior or fixes an issue, it should have an associated test. - * New features should be general and as simple as possible. - * Breaking changes should be avoided if possible. - * All pull requests require review. No PR will be submitted without a comment from a team member stating LGTM (Looks good to me). - -Webdriver-manager specific rules -------------------------- - - * JavaScript style should generally follow the [Google JS style guide](http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml). - * Wrap code at 100 chars. - * Document public methods with jsdoc. - * Be consistent with the code around you! - -Commit Messages ---------------- - -Please write meaningful commit messages - they are used to generate the changelog, so the commit message should tell a user everything they need to know about a commit. Webdriver-manager follows AngularJS's [commit message format](https://docs.google.com/a/google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#heading=h.z8a3t6ehl060). - -In summary, this style is - - (): - - - -Where `` is one of [feat, fix, docs, refactor, test, chore, deps] and -`` is a quick descriptor of the location of the change, such as cli, clientSideScripts, element. - -Testing your changes --------------------- - -Test your changes on your machine by running `npm test` to run the test suite. - -When you submit a PR, tests will also be run on the Continuous Integration environment -through Travis. If your tests fail on Travis, take a look at the logs - if the failures -are known flakes in Internet Explorer or Safari you can ignore them, but otherwise -Travis should pass. diff --git a/LICENSE b/LICENSE index 1556807c..2131a504 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ -The MIT License +MIT License -Copyright (c) 2016-2017 Google, Inc. +Copyright (c) 2018 Craig Nishina Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -9,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 8fa91dad..5f604512 100644 --- a/README.md +++ b/README.md @@ -1,76 +1,124 @@ +# webdriver-manager [![CircleCI](https://circleci.com/gh/angular/webdriver-manager/tree/replacement.svg?style=svg)](https://circleci.com/gh/angular/webdriver-manager/tree/replacement) -Webdriver Manager [![CircleCI Status](https://circleci.com/gh/angular/webdriver-manager.svg?style=shield)](https://circleci.com/gh/angular/webdriver-manager) [![Join the chat at https://gitter.im/angular/webdriver-manager](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/angular/webdriver-manager?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -================= +* [Use as a dependency](#use-as-a-dependency) +* [Use as a command line interface](#use-as-a-command-line-interface) +* [The command line interface help commands](#the-command-line-interface-help-commands) +* [Environment variables](docs/env_vars.md) -A selenium server and browser driver manager for your end to end tests. This is the same tool as `webdriver-manager` from the [Protractor](https://github.com/angular/protractor) repository. -**Note:** Version 9 and lower please reference [pose/webdriver-manager](https://github.com/pose/webdriver-manager). If there are features that existed in version 9 and lower, please open up an issue with the missing feature or a create a pull request. +## Use as a dependency -Getting Started ---------------- +To install this as a dependency: `npm install -D webdriver-manager`. The +following is an example running webdriver-manager as a dependency. +The test downloads the providers and starts the selenium server standalone as +detached. After the test, it will shutdown the selenium server standalone. -``` -npm install -g webdriver-manager -``` - -Setting up a Selenium Server ----------------------------- -Prior to starting the selenium server, download the selenium server jar and driver binaries. By default it will download the selenium server jar and chromedriver binary. - -``` -webdriver-manager update ``` +import { + Options, + setLogLevel, + shutdown, + start, + update, +} from 'webdriver-manager'; + +const options: Options = { + browserDrivers: [{ + name: 'chromedriver' // For browser drivers, we just need to use a valid + // browser driver name. Other possible values + // include 'geckodriver' and 'iedriver'. + }], + server: { + name: 'selenium', + runAsNode: true, // If we want to run as a node. By default + // running as detached will set this to true. + runAsDetach: true // To run this in detached. This returns the + // process back to the parent process. + } +}; +setLogLevel('info'); // Required if we webdriver-manager to log to + // console. Not setting this will hide the logs. + +describe('some web test', () => { + beforeAll(async () => { + await update(options); + await start(options); + }); + + it('should run some web test', async () => { + // Your async / await web test with some framework. + }); + + afterAll(async () => { + await shutdown(options); // Makes the web request to shutdown the server. + // If we do not call shutdown, the java command + // will still be running the server on port 4444. + }); +}); -Starting the Selenium Server ----------------------------- - -By default, the selenium server will run on `http://localhost:4444/wd/hub`. - - -``` -webdriver-manager start ``` -Other useful commands ---------------------- - -View different versions of server and driver files: +## Use as a command line interface ``` -webdriver-manager status +npm i -g webdriver-manager + +webdriver-manager update // Downloads the latest binaries. +webdriver-manager start // Starts the selenium server standalone. ``` -Clear out the server and driver files. If `webdriver-manager start` does not work, try to clear out the saved files. +Note: Installing globally will not work with Protractor if you are trying to +start a Selenium Standalone server with a "local" or "directConnect". It will +not work for these since Protractor is looking files downloaded locally to +the project. -``` -webdriver-manager clean -``` +## The command line interface help commands -Running / stopping server in background process (stopping is not yet supported on standalone server 3.x.x): +To get a list of commands for webdriver-manager, use the help flag. ``` -webdriver-manager start --detach -webdriver-manager shutdown +webdriver-manager --help +webdriver-manager [command] + +Commands: + webdriver-manager clean Removes downloaded files from the out_dir. + webdriver-manager shutdown Shutdown a local selenium server with GET request + webdriver-manager start Start up the selenium server. + webdriver-manager status List the current available binaries. + webdriver-manager update Install or update selected binaries. + +Options: + --version Show version number [boolean] + --help Show help [boolean] ``` -Help commands -------------- - -Wedriver-manager has a main help option: `webdriver-manager help`. There are also other built in help menus for each of the commands. So for example, if you would like to look up all the flag options you can set in `update`, you could run `webdriver-manager update help`. +To get a list of options that can be passed to the `webdriver-manager update` +command, use the help flag. -Here are a list of all the commands with help: - -``` -webdriver-manager update help -webdriver-manager start help -webdriver-manager clean help -webdriver-manager status help ``` +webdriver-manager update --help +webdriver-manager update -Other topics: --------------- - -- [mobile browser support](docs/mobile.md) -- [protractor support](docs/protractor.md) -- [set specific versions](docs/versions.md) \ No newline at end of file +Install or update selected binaries. + +Options: + --version Show version number [boolean] + --help Show help [boolean] + --out_dir Location of output. [string] + --chrome Install or update chromedriver. + [boolean] [default: true] + --gecko Install or update geckodriver.[boolean] [default: true] + --github_token Use a GitHub token to prevent rate limit issues. + [string] + --iedriver Install or update ie driver. [boolean] [default: false] + --ignore_ssl Ignore SSL certificates. [boolean] + --log_level The log level of this CLI. [string] [default: "info"] + --proxy Use a proxy server to download files. [string] + --standalone Install or update selenium server standalone. + [boolean] [default: true] + --versions.chrome The chromedriver version. [string] + --versions.gecko The geckodriver version. [string] + --versions.ie The ie driver version. [string] + --versions.standalone The selenium server standalone version. [string] +``` \ No newline at end of file diff --git a/bin/webdriver-manager b/bin/webdriver-manager old mode 100755 new mode 100644 index db376115..1af8bad0 --- a/bin/webdriver-manager +++ b/bin/webdriver-manager @@ -1,56 +1,3 @@ #!/usr/bin/env node -var path = require('path'); -var fs = require('fs'); -var cwd = process.cwd(); - -var nodeModuleName = 'webdriver-manager'; -var localInstall = path.resolve(cwd, 'node_modules', nodeModuleName); -var parentPath = path.resolve(cwd, '..'); -var dir = __dirname; -var folder = cwd.replace(parentPath, '').substring(1); - -var isProjectVersion = folder === nodeModuleName; -var isLocalVersion = false; - -var printCyan, printMagenta, printGreen; - -printCyan = printMagenta = printGreen = function(message) { - return message; -}; - -try { - var chalk = require('chalk'); - printMagenta = chalk.magenta; - printCyan = chalk.cyan; - printGreen = chalk.green; -} -catch (e) { - console.log("Error loading chalk, output will be boring."); -} - -try { - isLocalVersion = fs.statSync(localInstall).isDirectory(); -} catch(e) { -} - -// project version -if (folder === nodeModuleName) { - console.log(nodeModuleName + ': using ' + printMagenta('project version ' + - require(path.resolve('package.json')).version)); - require(path.resolve('built/lib/webdriver')); -} - -// local version -else if (isLocalVersion) { - console.log(nodeModuleName + ': using ' + printCyan('local installed version ' + - require(path.resolve(localInstall, 'package.json')).version)); - require(path.resolve(localInstall, 'built/lib/webdriver')); -} - -// global version -else { - console.log(nodeModuleName + ': using ' + printGreen('global installed version ' + - require(path.resolve(dir, '../package.json')).version)); - require(path.resolve(dir, '../built/lib/webdriver')); -} +require('../dist/lib/cli'); diff --git a/bin/webdriver-manager-replacement b/bin/webdriver-manager-replacement new file mode 100644 index 00000000..1af8bad0 --- /dev/null +++ b/bin/webdriver-manager-replacement @@ -0,0 +1,3 @@ +#!/usr/bin/env node + +require('../dist/lib/cli'); diff --git a/config.json b/config.json deleted file mode 100644 index cc7625a6..00000000 --- a/config.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "webdriverVersions": { - "selenium": "2.53.1", - "chromedriver": "2.27", - "maxChromedriver": "77", - "geckodriver": "v0.13.0", - "iedriver": "2.53.1", - "androidsdk": "24.4.1", - "appium": "1.6.5" - }, - "cdnUrls": { - "selenium": "https://selenium-release.storage.googleapis.com/", - "chromedriver": "https://chromedriver.storage.googleapis.com/", - "geckodriver": "https://github.com/mozilla/geckodriver/releases/download/", - "iedriver": "https://selenium-release.storage.googleapis.com/", - "androidsdk": "http://dl.google.com/android/" - } -} diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index efc1fb5d..00000000 --- a/docs/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Contents - -- [main page](../README.md) -- [mobile browser support](mobile.md) -- [protractor support](protractor.md) -- [set specific versions](versions.md) diff --git a/docs/env_vars.md b/docs/env_vars.md new file mode 100644 index 00000000..91cebe79 --- /dev/null +++ b/docs/env_vars.md @@ -0,0 +1,20 @@ +# Environment variables + +**`JAVA_HOME`** - If the java home variable is set as an environment variable, +use it when starting the selenium server. The java executable is assumed to be +found in `JAVA_HOME/bin/java`. + +**`NO_PROXY`** - If the no proxy environment variable exists and matches the +host name, to ignore the resolve proxy. + +**`HTTPS_PROXY`** - If the https proxy environment variable exists and the url +protocol is https, use this proxy. + +**`HTTP_PROXY`** - If the http proxy environmet variable exists and the url +protocol is http or https, use this proxy. + +**`GITHUB_TOKEN`** - A GitHub personal access token with no additional scopes +required. When created, it will just have public-access. This can be set at +[https://github.com/settings/tokens][1]. + +[1]: https://github.com/settings/tokens \ No newline at end of file diff --git a/docs/mobile.md b/docs/mobile.md deleted file mode 100644 index 64ddd36a..00000000 --- a/docs/mobile.md +++ /dev/null @@ -1,79 +0,0 @@ -Mobile Browser Support -====================== - -Support for mobile browsers is provided via [appium](https://github.com/appium/appium). If you -have used `webdriver-manager update --android` or `webdriver-manager update --ios`, when you run -`webdriver-manager start`, Appium will automatically start on the port specified by `--appium-port`. - - -Android SDK ------------ - -`webdriver-manager` will not install the android SDK by default. If you want to test on android, -run `webdriver-manager update --android`. This will download the android SDK, Appium, and set up -some virtual android devices for you to run tests against. By default, this will create only an -android device running version 24 on x86-64. If you need a different device, you must use the -`--android-api-levels` and `--android-abis` flags. So you might run a command like this: - -``` -webdriver-manager update --android --android-api-levels 24 --android-archs armeabi-v7a -``` - -Valid values for the `--android-api-levels` flag are: `24` and `25`. You *can* specify a lower -API level, but the virtual device create will not have Chrome installed. - -Valid values for the `--android-archs` flag are: - -* `x86` -* `x86_64` -* `armeabi-v7a` (or possibly some other 32-bit ARM architecture) -* `arm64-v8a` (or possibly some other 64-bit ARM architecture) -* `mips` - -Note that we always use the `google_apis/*` ABIs, since only those versions comes with chrome. So -if you specify `--android-archs x86_64`, this tool will use the ABI `google_apis/x86_64`. If you -wish to use a different platform (i.e. `android-wear`, `android-tv` or `default`), you can do so -with the `--android-platforms` flag. But only the `google_apis` version comes with Chrome. - - -As a practical matter, if you don't want to manually accept the license agreements, you can use -`--android-accept-licenses`, which will accept them on your behalf. - -Once you have installed the Android SDK with the virtual devices you need, use -`webdriver-manager start --android` to boot up Appium and begin emulating your android device(s). -By default `webdriver-manager` will emulate all available android devices. If you would rather -emulate a specific device, use `--avds`. So you might use: - -``` -webdriver-manager start --android --avds android-23-default-x86_64 -``` - -If you would prefer not to emulate any android virtual devices, use `--avds none`. - -If you need to specify the ports used by the android virtual devices, use `--avd_port`. The port -you specify will be used for the console of the first device, and the port one higher will be used -for its ADB. The second device will use the next two ports, and so on. - - -### "Welcome to Chome" UI - -Every time appium boots up chrome, a "Welcome to Chrome" dialog will pop up. It won't interfere -with your tests, since the webpage still renders underneath the dialog, and those tests access the -webpage directly. However, you won't be able to watch what's going on unless you manually click -"Accept & Continue". Also, since webdriver boots up a fresh chrome instance every time, chrome -won't remember that you've clicked "Accept & Continue" between tests. Any time you want to actually -watch the test as it's run, you'll need to click through the dialog again. For now, there is no -good way around this sadly (https://github.com/appium/appium/issues/6618). - -iOS ---------- - -When you run `webdriver-manager update --ios`, `webdriver-manager` will install Appium and check -your computer for iOS simulation capabilities. `webdriver-manager` cannot download the xcode -commandline tools for you however, nor can it agree to Apple's user agreement. The xcode -commandline tools come with several virtual devices pre-setup. If you need more, run -`xcrun simctl` for help doing that. - -Once you have installed Appium, `webdriver-manager` will launch it automatically when you run -`webdriver-manager start`. Appium will automatically handle starting iOS device emulation as -needed. diff --git a/docs/protractor.md b/docs/protractor.md deleted file mode 100644 index 0d236ea8..00000000 --- a/docs/protractor.md +++ /dev/null @@ -1,41 +0,0 @@ -# Protractor support topics - -## Supported browsers - -### Tests with Protractor test suite (v 4.0.13) - -The following are the supported browsers / drivers based on running the -Protractor test suite. Since the test suite checks only firefox and chrome, -these are the only browsers reported. - -### Current supported browsers / drivers - -| selenium standalone | firefox | chromedriver | chrome | -| ------------------- | ------- | ------------ | ------ | -| 2.53.1 | 47.0.1 | 2.26 | 54 | - - -### Investigated - -| selenium standalone | firefox 47.0.1 | firefox 49.0.1 | -| ------------------- | -------------- | -------------- | -| 2.53.1 | pass | fail | -| 3.0.0 | fail | fail | - - -## Driver providers - -There are a couple cases where Protractor launches downloaded binaries -(from webdriver-manager) without specifying which version to use: - -- local driver providers - when no `seleniumAddress`, `directConnect`, saucelabs - or browser stack configuration is set -- direct connect - when setting `directConnect` to true - -Protractor knows which downloaded binaries to use based on the -`update-config.json`. The `update-config.json` is written during the `update` -command and provides the path to the current downloaded version and a list of -all the binaries previously downloaded. - -During the launch of either local driver provider or direct connect, Protractor -will launch the last downloaded binary. diff --git a/docs/versions.md b/docs/versions.md deleted file mode 100644 index cf581386..00000000 --- a/docs/versions.md +++ /dev/null @@ -1,87 +0,0 @@ -# update command: downloading specific binaries - -The help command for update. The flags shown here are OS specific. If you are not on a Windows machine, the `--ie` flags are not shown. - -``` -webdriver-manager update help -``` - -Overview when you call `update`: - -* The output directory is created if it does not exist (`out_dir`) -* If this is the first time downloading, it will make web requests to selenium-release.storage.googleapis.com, chromedriver.storage.googleapis.com, and api.github.com. The responses will be stored in the `out_dir` as a cache. -* If this is not the first time downloading, it will look at the cached files in `out_dir` and if the file is older than an hour, it will make a request for new files. Note: api.github.com is rate limited by the ip address making the request. This is an issue for common CI systems like Travis or Circle and have an issue opened for this. -* The response will then be parsed for the url to get the latest version if no version is provided via command line. The latest version will be based on what is available for your OS type and architecture. -* Next we download the file from the url. If the file already exists (based on content length), we cancel the download. -* We uncompress zip and tar files and for linux and mac, we set the permissions. Currently, we always uncompress the files even if the uncompressed files already exist. -* Finally we write the file locations to the `update-config.json` which is also located in the `out_dir`. - -## Download defaults --chrome, --gecko, --standalone flags - -Let's say you want to download just chromedriver... - -``` -webdriver-manager update --gecko false --standalone false -``` - -Why do we set `--gecko` and `--standalone` to false? By default chromedriver, geckodriver and selenium standalone server download with no flags set. Setting `--gecko` and `--standalone` to false will prevent them from downloading and will just download chromedriver. - -## Download using --ie - -When setting the `--ie` flag, this will download IEDriver 32 bit. Why 32-bit? We prefer using the 32-bit version because the 64-bit version has been previously reported to be very slow and flaky. - -## Download a specific version - -Let's say there is an issue with the latest version of chromedriver and we need version 2.20: - -``` -webdriver-manager update --versions.chrome 2.20 -``` - -So the only thing different here is that instead of parsing for the latest version, we are parsing for version 2.20. We could then verify that it was the last item downloaded by calling `webdriver-manager status`. - -What about selenium standalone 2.53.1? This is similar but instead of the flag would be `--versions.standalone 2.53.1`. What if the selenium standalone server has a beta in the name? The idea is similar: - -``` -webdriver-manager update --versions.standalone 3.0.0-beta1 -``` - -# start command: setting specific versions - -The help command for start. - -``` -webdriver-manager start help -``` - -When starting selenium standalone server with `webdriver-manager start`, it will use the latest version that is found in the cache unless you specify the version. Why? The idea here is that if you run `webdriver-manager update` without flags, you are using the latest binary versions, so the start command would be similar `webdriver-manager start`. - -## Starting a specific version - -So let's say you downloaded chromedriver 2.20 and let's also say that the latest version is 2.29. If you have never downloaded 2.29, then chromedriver will not be associated with your selenium standalone server. In order for you to get 2.20 working: - -``` -webdriver-manager start --versions.chrome 2.20 -``` - -What about chrome version 2.20, selenium standalone server 3.0.0-beta1, and not start with gecko driver? - -``` -webdriver-manager start --versions.chrome 2.20 --versions.standalone 3.0.0-beta1 --gecko false -``` - -## Starting with --ie - -Since IEDriver is not set by default, you'll still need to pass this field if you would like to start the selenium standalone server with the IEDriver. - -``` -webdriver-manager start --ie -``` - -## Starting with --edge - -We make the assumption that you already have the EdgeDriver downloaded and installed. To get this working pass the full path to the `--edge` flag - -``` -webdriver-manager start --edge "C:\Program Files (x86)\Microsoft Web Driver\MicrosoftWebDriver.exe" -``` diff --git a/e2e_spec/android_spec.ts b/e2e_spec/android_spec.ts deleted file mode 100644 index 9a268c51..00000000 --- a/e2e_spec/android_spec.ts +++ /dev/null @@ -1,32 +0,0 @@ -import * as os from 'os'; -import * as webdriver from 'selenium-webdriver'; -import {AndroidSDK} from '../lib/binaries' - -let versions: {androidsdk: string, appium: string} = require('../config.json').webdriverVersions; - -describe('browser smoke tests', () => { - it('should be able to boot up android chrome', (done) => { - let driver = - new webdriver.Builder() - .usingServer('http://localhost:4723/wd/hub') - .withCapabilities({ - browserName: 'chrome', - platformName: 'Android', - platformVersion: - AndroidSDK.VERSIONS[parseInt(AndroidSDK.DEFAULT_API_LEVELS.split(',')[0])], - deviceName: 'Android Emulator' - }) - .build(); - driver.get('http://10.0.2.2:4723/wd/hub/status') - .then(() => { - return driver.getPageSource(); - }) - .then((source: string) => { - expect(source).toContain('"status":0'); - return driver.quit(); - }) - .then(() => { - done(); - }); - }, 10 * 60 * 1000); -}); diff --git a/e2e_spec/server_spec.ts b/e2e_spec/server_spec.ts deleted file mode 100644 index 527f6fc5..00000000 --- a/e2e_spec/server_spec.ts +++ /dev/null @@ -1,28 +0,0 @@ -import * as http from 'http'; - - -describe('sever smoke tests', () => { - it('should be able to ping selenium server', (done) => { - http.get('http://localhost:4444/wd/hub/status', (resp) => { - expect(resp.statusCode).toBe(200); - let logs = ''; - resp.on('data', (chunk) => logs += chunk); - resp.on('end', () => { - expect(logs).toContain('"ready": true'); - done() - }); - }); - }); - - it('should be able to ping appium server', (done) => { - http.get('http://localhost:4723/wd/hub/status', (resp) => { - expect(resp.statusCode).toBe(200); - let data = ''; - resp.on('data', (chunk) => data += chunk); - resp.on('end', () => { - expect(JSON.parse(data).status).toBe(0); - done() - }); - }); - }); -}); diff --git a/gulpfile.js b/gulpfile.js deleted file mode 100644 index f397bd1a..00000000 --- a/gulpfile.js +++ /dev/null @@ -1,44 +0,0 @@ -'use strict'; -const path = require('path'); -const gulp = require('gulp'); -const tsGlobs = ['lib/**/*.ts', '*spec/**/*.ts']; - -let runSpawn = (task, args, done) => { - done = done || function() {}; - const spawn = require('child_process').spawn; - let child = spawn(task, args, {stdio: 'inherit'}); - let running = false; - child.on('close', (code) => { - if (!running) { - running = true; - done(code); - } - }); - child.on('error', (err) => { - if (!running) { - console.error('gulp encountered a child error'); - running = true; - done(err || 1); - } - }); - return child; -}; - -gulp.task('copy', function() { - return gulp.src(['config.json', 'package.json']) - .pipe(gulp.dest('built/')); -}); - -gulp.task('format:enforce', () => { - const format = require('gulp-clang-format'); - const clangFormat = require('clang-format'); - return gulp.src(tsGlobs).pipe( - format.checkFormat('file', clangFormat, {verbose: true, fail: true})); -}); - -gulp.task('format', () => { - const format = require('gulp-clang-format'); - const clangFormat = require('clang-format'); - return gulp.src(tsGlobs, { base: '.' }).pipe( - format.format('file', clangFormat)).pipe(gulp.dest('.')); -}); \ No newline at end of file diff --git a/lib/binaries/android_sdk.ts b/lib/binaries/android_sdk.ts deleted file mode 100644 index 86185957..00000000 --- a/lib/binaries/android_sdk.ts +++ /dev/null @@ -1,111 +0,0 @@ -import * as path from 'path'; -import * as rimraf from 'rimraf'; - -import {Config} from '../config'; -import {spawnSync} from '../utils'; - -import {Binary, BinaryUrl, OS} from './binary'; - -function getAndroidArch(): string { - switch (Config.osArch()) { - case 'arm': - return 'armeabi-v7a'; - case 'arm64': - return 'arm64-v8a'; - case 'x86': - case 'x32': - case 'ia32': - case 'ppc': - return 'x86'; - case 'x86-64': - case 'x64': - case 'ia64': - case 'ppc64': - return 'x86_64'; - default: - return Config.osArch(); - } -} - -/** - * The android sdk binary. - */ -export class AndroidSDK extends Binary { - static os = [OS.Windows_NT, OS.Linux, OS.Darwin]; - static id = 'android'; - static versionDefault = Config.binaryVersions().android; - static isDefault = false; - static DEFAULT_API_LEVELS = '24'; - static DEFAULT_ARCHITECTURES = getAndroidArch(); - static DEFAULT_PLATFORMS = 'google_apis'; - static VERSIONS: {[api_level: number]: string} = { - // Before 24 is not supported - 24: '7.0', - 25: '7.1' - } - - constructor(alternateCDN?: string) { - super(alternateCDN || Config.cdnUrls().android); - - this.name = 'android-sdk'; - this.versionCustom = AndroidSDK.versionDefault; - } - - id(): string { - return AndroidSDK.id; - } - - prefix(): string { - return 'android-sdk_r'; - } - - suffix(): string { - if (this.ostype === 'Darwin') { - return '-macosx.zip'; - } else if (this.ostype === 'Linux') { - return '-linux.tgz'; - } else if (this.ostype === 'Windows_NT') { - return '-windows.zip'; - } - } - - getUrl(): Promise { - return Promise.resolve({url: this.cdn + this.filename(), version: this.versionCustom}); - } - - getVersionList(): Promise { - return null; - } - - url(ostype: string): string { - return this.cdn + this.filename(); - } - - zipContentName(): string { - if (this.ostype === 'Darwin') { - return this.name + '-macosx'; - } else if (this.ostype === 'Linux') { - return this.name + '-linux'; - } else if (this.ostype === 'Windows_NT') { - return this.name + '-windows'; - } - } - - executableSuffix(): string { - return ''; - } - - remove(sdkPath: string): void { - try { - let avds = require(path.resolve(sdkPath, 'available_avds.json')); - let version = path.basename(sdkPath).slice(this.prefix().length); - avds.forEach((avd: string) => { - spawnSync( - path.resolve(sdkPath, 'tools', 'android'), - ['delete', 'avd', '-n', avd + '-v' + version + '-wd-manager']); - }); - } catch (e) { - } - rimraf.sync(sdkPath); - } -} diff --git a/lib/binaries/appium.ts b/lib/binaries/appium.ts deleted file mode 100644 index 1216b5c6..00000000 --- a/lib/binaries/appium.ts +++ /dev/null @@ -1,51 +0,0 @@ -import * as path from 'path'; -import * as rimraf from 'rimraf'; - -import {Config} from '../config'; - -import {Binary, BinaryUrl, OS} from './binary'; - - -/** - * The appium binary. - */ -export class Appium extends Binary { - static os = [OS.Windows_NT, OS.Linux, OS.Darwin]; - static id = 'appium'; - static versionDefault = Config.binaryVersions().appium; - static isDefault = false; - - constructor(alternateCDN?: string) { - super(alternateCDN || Config.cdnUrls().appium); - this.name = 'appium'; - this.versionCustom = Appium.versionDefault; - } - - id(): string { - return Appium.id; - } - - prefix(): string { - return 'appium-'; - } - - suffix(): string { - return ''; - } - - executableSuffix(): string { - return ''; - } - - getUrl(version?: string): Promise { - return Promise.resolve({url: '', version: this.versionCustom}); - } - - getVersionList(): Promise { - return null; - } - - remove(sdkPath: string): void { - rimraf.sync(sdkPath); - } -} diff --git a/lib/binaries/binary.ts b/lib/binaries/binary.ts deleted file mode 100644 index 2f530433..00000000 --- a/lib/binaries/binary.ts +++ /dev/null @@ -1,119 +0,0 @@ -import * as fs from 'fs'; -import {Config} from '../config'; -import {ConfigSource} from './config_source'; - -/** - * operating system enum - */ -export enum OS { - Windows_NT, - Linux, - Darwin -} - -export interface BinaryUrl { - url: string; - version: string; -} - -/** - * Dictionary to map the binary's id to the binary object - */ -export interface BinaryMap { [id: string]: T; } - -export abstract class Binary { - static os: OS[]; - - configSource: ConfigSource; - - ostype: string = Config.osType(); - osarch: string = Config.osArch(); - - // TODO(cnishina): downloading the latest requires parsing XML or JSON that is assumed to be - // published and formatted in a specific way. If this is not available, let the user download - // the file from an alternative download URL. - alternativeDownloadUrl: string; - - cdn: string; // The url host for XML reading or the base path to the url. - - name: string; - versionDefault: string; - versionCustom: string; - - constructor(opt_alternativeCdn?: string) { - this.cdn = opt_alternativeCdn; - } - - abstract prefix(): string; - abstract suffix(): string; - - executableSuffix(): string { - if (this.ostype == 'Windows_NT') { - return '.exe'; - } else { - return ''; - } - } - - version(): string { - return this.versionCustom; - } - - filename(): string { - return this.prefix() + this.version() + this.suffix(); - } - - /** - * @param ostype The operating system. - * @returns The file name for the executable. - */ - executableFilename(): string { - return this.prefix() + this.version() + this.executableSuffix(); - } - - /** - * Gets the id of the binary. - */ - abstract id(): string; - - /** - * Gets the url to download the file set by the version. This will use the XML if available. - * If not, it will download from an existing url. - * - * @param {string} version The version we are looking for. This could also be 'latest'. - */ - getUrl(version?: string): Promise { - if (this.alternativeDownloadUrl != null) { - return Promise.resolve({url: '', version: ''}); - } else { - return this.getVersionList().then(() => { - version = version || Config.binaryVersions()[this.id()]; - return this.configSource.getUrl(version).then(binaryUrl => { - this.versionCustom = binaryUrl.version; - return {url: binaryUrl.url, version: binaryUrl.version}; - }); - }); - } - } - - /** - * Gets the list of available versions available based on the xml. If no XML exists, return an - * empty list. - */ - abstract getVersionList(): Promise; - - /** - * Delete an instance of this binary from the file system - */ - remove(filename: string): void { - fs.unlinkSync(filename); - } - - /** - * @param ostype The operating system. - * @returns The file name for the file inside the downloaded zip file - */ - zipContentName(): string { - return this.name + this.executableSuffix(); - } -} diff --git a/lib/binaries/chrome_driver.ts b/lib/binaries/chrome_driver.ts deleted file mode 100644 index 74ac0f74..00000000 --- a/lib/binaries/chrome_driver.ts +++ /dev/null @@ -1,40 +0,0 @@ -import {Config} from '../config'; - -import {Binary, BinaryUrl, OS} from './binary'; -import {ChromeXml} from './chrome_xml'; - -export class ChromeDriver extends Binary { - static id = 'chrome'; - static isDefault = true; - static os = [OS.Windows_NT, OS.Linux, OS.Darwin]; - static versionDefault = Config.binaryVersions().chrome; - - constructor(opt_alternativeCdn?: string) { - super(opt_alternativeCdn || Config.cdnUrls().chrome); - this.configSource = new ChromeXml(); - this.name = 'chromedriver'; - this.versionDefault = ChromeDriver.versionDefault; - this.versionCustom = this.versionDefault; - } - - id(): string { - return ChromeDriver.id; - } - - prefix(): string { - return 'chromedriver_'; - } - - suffix(): string { - return '.zip'; - } - - getVersionList(): Promise { - // If an alternative cdn is set, return an empty list. - if (this.alternativeDownloadUrl != null) { - Promise.resolve([]); - } else { - return this.configSource.getVersionList(); - } - } -} diff --git a/lib/binaries/chrome_xml.ts b/lib/binaries/chrome_xml.ts deleted file mode 100644 index 606425ce..00000000 --- a/lib/binaries/chrome_xml.ts +++ /dev/null @@ -1,163 +0,0 @@ -import * as semver from 'semver'; - -import {Config} from '../config'; -import {requestBody} from '../http_utils'; - -import {BinaryUrl} from './binary'; -import {XmlConfigSource} from './config_source'; - -export class ChromeXml extends XmlConfigSource { - maxVersion = Config.binaryVersions().maxChrome; - - constructor() { - super('chrome', Config.cdnUrls()['chrome']); - } - - getUrl(version: string): Promise { - if (version === 'latest') { - return this.getLatestChromeDriverVersion(); - } else { - return this.getSpecificChromeDriverVersion(version); - } - } - - /** - * Get a list of chrome drivers paths available for the configuration OS type and architecture. - */ - getVersionList(): Promise { - return this.getXml().then(xml => { - let versionPaths: string[] = []; - let osType = this.getOsTypeName(); - - for (let content of xml.ListBucketResult.Contents) { - let contentKey: string = content.Key[0]; - - if ( - // Filter for 32-bit devices, make sure x64 is not an option - (this.osarch.includes('64') || !contentKey.includes('64')) && - // Filter for x86 macs, make sure m1 is not an option - ((this.ostype === 'Darwin' && this.osarch === 'arm64') || !contentKey.includes('m1'))) { - // Filter for only the osType - if (contentKey.includes(osType)) { - versionPaths.push(contentKey); - } - } - } - return versionPaths; - }); - } - - /** - * Helper method, gets the ostype and gets the name used by the XML - */ - getOsTypeName(): string { - // Get the os type name. - if (this.ostype === 'Darwin') { - return 'mac'; - } else if (this.ostype === 'Windows_NT') { - return 'win'; - } else { - return 'linux'; - } - } - - /** - * Gets the latest item from the XML. - */ - private getLatestChromeDriverVersion(): Promise { - const latestReleaseUrl = 'https://chromedriver.storage.googleapis.com/LATEST_RELEASE'; - return requestBody(latestReleaseUrl).then(latestVersion => { - return this.getSpecificChromeDriverVersion(latestVersion); - }); - } - - /** - * Gets a specific item from the XML. - */ - private getSpecificChromeDriverVersion(inputVersion: string): Promise { - return this.getVersionList().then(list => { - const specificVersion = getValidSemver(inputVersion); - if (specificVersion === '') { - throw new Error(`version ${inputVersion} ChromeDriver does not exist`) - } - let itemFound = ''; - for (let item of list) { - // Get a semantic version. - let version = item.split('/')[0]; - if (semver.valid(version) == null) { - const lookUpVersion = getValidSemver(version); - - if (semver.valid(lookUpVersion)) { - // Check to see if the specified version matches. - if (lookUpVersion === specificVersion) { - // When item found is null, check the os arch - // 64-bit version works OR not 64-bit version and the path does not have '64' - if (itemFound == '') { - if (this.osarch === 'x64' || - (this.osarch !== 'x64' && !item.includes(this.getOsTypeName() + '64'))) { - itemFound = item; - } - if (this.osarch === 'arm64' && this.ostype === 'Darwin' && item.includes('m1')) { - itemFound = item; - } - } - // If the semantic version is the same, check os arch. - // For 64-bit systems, prefer the 64-bit version. - else if (this.osarch === 'x64') { - // No win64 version exists, so even on x64 we need to look for win32 - const osTypeNameAndArch = - this.getOsTypeName() + (this.getOsTypeName() === 'win' ? '32' : '64'); - - if (item.includes(osTypeNameAndArch)) { - itemFound = item; - } - } - } - } - } - } - if (itemFound == '') { - return {url: '', version: inputVersion}; - } else { - return {url: Config.cdnUrls().chrome + itemFound, version: inputVersion}; - } - }); - } -} - -/** - * Chromedriver is the only binary that does not conform to semantic versioning - * and either has too little number of digits or too many. To get this to be in - * semver, we will either add a '.0' at the end or chop off the last set of - * digits. This is so we can compare to find the latest and greatest. - * - * Example: - * 2.46 -> 2.46.0 - * 75.0.3770.8 -> 75.0.3770 - * - * @param version - */ -export function getValidSemver(version: string): string { - let lookUpVersion = ''; - // This supports downloading 2.46 - try { - const oldRegex = /(\d+.\d+)/g; - const exec = oldRegex.exec(version); - if (exec) { - lookUpVersion = exec[1] + '.0'; - } - } catch (_) { - // no-op: is this is not valid, do not throw here. - } - // This supports downloading 74.0.3729.6 - try { - const newRegex = /(\d+.\d+.\d+).\d+/g; - const exec = newRegex.exec(version); - if (exec) { - lookUpVersion = exec[1]; - } - } catch (_) { - // no-op: if this does not work, use the other regex pattern. - } - return lookUpVersion; -} diff --git a/lib/binaries/config_source.ts b/lib/binaries/config_source.ts deleted file mode 100644 index 092229e1..00000000 --- a/lib/binaries/config_source.ts +++ /dev/null @@ -1,214 +0,0 @@ -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; -import * as request from 'request'; -import * as url from 'url'; -import * as xml2js from 'xml2js'; - -import {Logger} from '../cli/logger'; -import {Config} from '../config'; -import {HttpUtils} from '../http_utils'; - -let logger = new Logger('config_source'); - -export abstract class ConfigSource { - ostype = Config.osType(); - osarch = Config.osArch(); - out_dir: string = Config.getSeleniumDir(); - - abstract getUrl(version: string): Promise<{url: string, version: string}>; - abstract getVersionList(): Promise; -} - -export abstract class XmlConfigSource extends ConfigSource { - constructor(public name: string, public xmlUrl: string) { - super(); - } - - protected getFileName(): string { - try { - fs.statSync(this.out_dir); - } catch (e) { - fs.mkdirSync(this.out_dir); - } - return path.resolve(this.out_dir, this.name + '-response.xml'); - } - - protected getXml(): Promise { - let fileName = this.getFileName(); - let content = this.readResponse(); - if (content) { - return Promise.resolve(content); - } else { - return this.requestXml().then(text => { - let xml = this.convertXml2js(text); - fs.writeFileSync(fileName, text); - return xml; - }); - } - } - - private readResponse(): any { - let fileName = this.getFileName(); - try { - let contents = fs.readFileSync(fileName).toString(); - let timestamp = new Date(fs.statSync(fileName).mtime).getTime(); - let size = fs.statSync(fileName).size; - let now = Date.now(); - - // On start, read the file. If not on start, check use the cache as long as the - // size > 0 and within the cache time. - // 60 minutes * 60 seconds / minute * 1000 ms / second - if (Config.runCommand === 'start' || (size > 0 && (now - (60 * 60 * 1000) < timestamp))) { - return this.convertXml2js(contents); - } else { - return null; - } - } catch (err) { - return null; - } - } - - private requestXml(): Promise { - return new Promise((resolve, reject) => { - let options = HttpUtils.initOptions(this.xmlUrl); - - let curl = this.getFileName() + ' ' + options.url; - if (HttpUtils.requestOpts.proxy) { - let pathUrl = url.parse(options.url.toString()).path; - let host = url.parse(options.url.toString()).host; - let newFileUrl = url.resolve(HttpUtils.requestOpts.proxy, pathUrl); - curl = this.getFileName() + ' \'' + newFileUrl + '\' -H \'host:' + host + '\''; - } - if (HttpUtils.requestOpts.ignoreSSL) { - curl = 'k ' + curl; - } - logger.info('curl -o' + curl); - - let req = request(options); - req.on('response', response => { - if (response.statusCode === 200) { - let output = ''; - response.on('data', (data) => { - output += data; - }); - response.on('end', () => { - resolve(output); - }); - - } else { - reject(new Error('response status code is not 200')); - } - }); - }); - } - - private convertXml2js(xml: string): any { - let retResult: any = null; - xml2js.parseString(xml, (err, result) => { - retResult = result; - }); - return retResult; - } -} - -export abstract class JsonConfigSource extends ConfigSource { - constructor(public name: string, public jsonUrl: string) { - super(); - } - - protected getFileName(): string { - try { - fs.statSync(this.out_dir); - } catch (e) { - fs.mkdirSync(this.out_dir); - } - return path.resolve(this.out_dir, this.name + '-response.json'); - } - - protected abstract getJson(): Promise; -} - -export abstract class GithubApiConfigSource extends JsonConfigSource { - constructor(name: string, url: string) { - super(name, url); - } - - /** - * This is an unauthenticated request and since Github limits the rate, we will cache this - * to a file. { timestamp: number, response: response }. We will check the timestamp and renew - * this request if the file is older than an hour. - */ - getJson(): Promise { - let fileName = this.getFileName(); - let content = this.readResponse(); - if (content) { - return Promise.resolve(JSON.parse(content)); - } else { - return this.requestJson().then(body => { - let json = JSON.parse(body); - fs.writeFileSync(fileName, JSON.stringify(json, null, ' ')); - return json; - }); - } - } - - private requestJson(): Promise { - return new Promise((resolve, reject) => { - let options = HttpUtils.initOptions(this.jsonUrl); - options = HttpUtils.optionsHeader(options, 'Host', 'api.github.com'); - options = HttpUtils.optionsHeader(options, 'User-Agent', 'request'); - - let curl = this.getFileName() + ' ' + options.url; - if (HttpUtils.requestOpts.proxy) { - let pathUrl = url.parse(options.url.toString()).path; - let host = url.parse(options.url.toString()).host; - let newFileUrl = url.resolve(HttpUtils.requestOpts.proxy, pathUrl); - curl = this.getFileName() + ' \'' + newFileUrl + '\' -H \'host:' + host + '\''; - } - if (HttpUtils.requestOpts.ignoreSSL) { - curl = 'k ' + curl; - } - logger.info('curl -o' + curl); - - let req = request(options); - req.on('response', response => { - if (response.statusCode === 200) { - let output = ''; - response.on('data', (data) => { - output += data; - }); - response.on('end', () => { - resolve(output); - }); - - } else if (response.statusCode == 403 && response.headers['x-ratelimit-remaining'] == '0') { - reject(new Error('Failed to make Github request, rate limit reached.')); - } else { - reject(new Error('response status code is not 200. It was ' + response.statusCode)); - } - }) - }); - } - - private readResponse(): any { - let fileName = this.getFileName(); - try { - let contents = fs.readFileSync(fileName).toString(); - let timestamp = new Date(fs.statSync(fileName).mtime).getTime(); - let size = fs.statSync(fileName).size; - let now = Date.now(); - - // On start, read the file. If not on start, check use the cache as long as the - // size > 0 and within the cache time. - // 60 minutes * 60 seconds / minute * 1000 ms / second - if (Config.runCommand === 'start' || (size > 0 && (now - (60 * 60 * 1000) < timestamp))) { - return contents; - } else { - return null; - } - } catch (err) { - return null; - } - } -} diff --git a/lib/binaries/gecko_driver.ts b/lib/binaries/gecko_driver.ts deleted file mode 100644 index 02e4d944..00000000 --- a/lib/binaries/gecko_driver.ts +++ /dev/null @@ -1,58 +0,0 @@ -import {Config} from '../config'; - -import {Binary, BinaryUrl, OS} from './binary'; -import {GeckoDriverGithub} from './gecko_driver_github'; - -type StringMap = { - [key: string]: string -}; -type SuffixMap = { - [key: string]: StringMap -}; - -export class GeckoDriver extends Binary { - static id = 'gecko'; - static isDefault = true; - static os = [OS.Windows_NT, OS.Linux, OS.Darwin]; - static versionDefault = Config.binaryVersions().gecko; - private static suffixes: SuffixMap = { - 'Darwin': {'x64': '-macos.tar.gz'}, - 'Linux': {'x64': '-linux64.tar.gz', 'ia32': '-linux32.tar.gz'}, - 'Windows_NT': { - 'x64': '-win64.zip', - 'ia32': '-win32.zip', - } - }; - - constructor(opt_alternativeCdn?: string) { - super(opt_alternativeCdn || Config.cdnUrls().gecko); - this.configSource = new GeckoDriverGithub(); - this.name = 'geckodriver'; - this.versionDefault = GeckoDriver.versionDefault; - this.versionCustom = this.versionDefault; - } - - id(): string { - return GeckoDriver.id; - } - - prefix(): string { - return 'geckodriver-'; - } - - suffix(): string { - if (this.ostype === 'Windows_NT') { - return '.zip'; - } else { - return '.tar.gz'; - } - } - - getVersionList(): Promise { - if (this.alternativeDownloadUrl != null) { - return Promise.resolve([]); - } else { - return this.configSource.getVersionList(); - } - } -} diff --git a/lib/binaries/gecko_driver_github.ts b/lib/binaries/gecko_driver_github.ts deleted file mode 100644 index 6ddc6eea..00000000 --- a/lib/binaries/gecko_driver_github.ts +++ /dev/null @@ -1,97 +0,0 @@ -import * as semver from 'semver'; - -import {Config} from '../config'; - -import {BinaryUrl} from './binary'; -import {GithubApiConfigSource} from './config_source'; - -export class GeckoDriverGithub extends GithubApiConfigSource { - constructor() { - super('gecko', 'https://api.github.com/repos/mozilla/geckodriver/releases'); - } - - getUrl(version: string): Promise { - if (version === 'latest') { - return this.getLatestGeckoDriverVersion(); - } else { - return this.getSpecificGeckoDrierVersion(version); - } - } - - getVersionList(): Promise { - return this.getJson().then(json => { - let versions: string[] = []; - for (let i = 0; i < json.length; i++) { - let item = json[i]; - versions.push(item.tag_name); - } - return versions; - }); - } - - getVersionsLookup(): Promise> { - return this.getJson().then(json => { - let versionsLookup: Array<{version: string, index: string}> = []; - for (let i = 0; i < json.length; i++) { - let item = json[i]; - let index = i.toString(); - versionsLookup.push({version: item.tag_name, index: index}); - } - return versionsLookup; - }); - } - - private getLatestGeckoDriverVersion(): Promise { - return this.getJson().then(json => { - return this.getVersionsLookup().then(versionsLookup => { - let latest = ''; - for (let item of versionsLookup) { - let version = item.version.replace('v', ''); - let assetsArray = json[item.index].assets; - - // check to make sure the version found has the OS - for (let asset of assetsArray) { - if ((asset.name as string).includes(this.oshelper())) { - if (latest === '') { - latest = version; - } else if (semver.lt(latest, version)) { - latest = version; - } - } - } - } - return this.getSpecificGeckoDrierVersion('v' + latest); - }); - }); - } - - private getSpecificGeckoDrierVersion(inputVersion: string): Promise { - return this.getJson().then(json => { - return this.getVersionsLookup().then(versionsLookup => { - for (let item of versionsLookup) { - // Get the asset from the matching version. - if (item.version === inputVersion) { - let assetsArray = json[item.index].assets; - for (let asset of assetsArray) { - if ((asset.name as string).includes(this.oshelper())) { - return {url: asset.browser_download_url, version: inputVersion}; - } - } - } - } - return null; - }); - }); - } - - private oshelper(): string { - // Get the os type name. - if (this.ostype === 'Darwin') { - return 'macos'; - } else if (this.ostype === 'Windows_NT') { - return this.osarch === 'x64' ? 'win64' : 'win32'; - } else { - return this.osarch === 'x64' ? 'linux64' : 'linux32'; - } - } -} diff --git a/lib/binaries/iedriver.ts b/lib/binaries/iedriver.ts deleted file mode 100644 index afc033f3..00000000 --- a/lib/binaries/iedriver.ts +++ /dev/null @@ -1,40 +0,0 @@ -import {Config} from '../config'; - -import {Binary, BinaryUrl, OS} from './binary'; -import {IEDriverXml} from './iedriver_xml'; - -export class IEDriver extends Binary { - static id = 'ie'; - static isDefault32 = false; - static isDefault64 = false; - static os = [OS.Windows_NT]; - static versionDefault = Config.binaryVersions().ie; - - constructor(opt_alternativeCdn?: string) { - super(opt_alternativeCdn || Config.cdnUrls().ie); - this.configSource = new IEDriverXml(); - this.name = 'IEDriverServer'; - this.versionDefault = IEDriver.versionDefault; - this.versionCustom = this.versionDefault; - } - - id(): string { - return IEDriver.id; - } - - prefix(): string { - return 'IEDriverServer'; - } - - suffix(): string { - return '.zip'; - } - - getVersionList(): Promise { - if (this.alternativeDownloadUrl != null) { - return Promise.resolve([]); - } else { - return this.configSource.getVersionList(); - } - } -} diff --git a/lib/binaries/iedriver_xml.ts b/lib/binaries/iedriver_xml.ts deleted file mode 100644 index 40ac3ff4..00000000 --- a/lib/binaries/iedriver_xml.ts +++ /dev/null @@ -1,76 +0,0 @@ -import * as semver from 'semver'; - -import {Config} from '../config'; - -import {BinaryUrl} from './binary'; -import {XmlConfigSource} from './config_source'; - -export class IEDriverXml extends XmlConfigSource { - constructor() { - super('iedriver', Config.cdnUrls()['ie']); - } - - getUrl(version: string): Promise { - if (version === 'latest') { - return this.getLatestIEDriverVersion(); - } else { - return this.getSpecificIEDriverVersion(version); - } - } - - getVersionList(): Promise { - return this.getXml().then(xml => { - let versionPaths: string[] = []; - - for (let content of xml.ListBucketResult.Contents) { - let contentKey: string = content.Key[0]; - - // Filter For IEDriverServer win 32. Removing option to download x64 - if (contentKey.includes('IEDriverServer_Win32_')) { - versionPaths.push(contentKey); - } - } - return versionPaths; - }); - } - - private getLatestIEDriverVersion(): Promise { - return this.getVersionList().then(list => { - let latestVersion: string = null; - let latest = ''; - for (let item of list) { - // Get a semantic version. - let version = item.split('IEDriverServer_Win32_')[1].replace('.zip', ''); - - if (latestVersion == null) { - // First time: use the version found. - latestVersion = version; - latest = item; - } else if (semver.gt(version, latestVersion)) { - // Get the latest. - latestVersion = version; - latest = item; - } - } - return {url: Config.cdnUrls().ie + latest, version: latestVersion}; - }); - } - - private getSpecificIEDriverVersion(inputVersion: string): Promise { - return this.getVersionList().then(list => { - let itemFound = ''; - - for (let item of list) { - // Get a semantic version. - let version = item.split('IEDriverServer_Win32_')[1].replace('.zip', ''); - - // Check to see if the specified version matches. - let firstPath = item.split('/')[0]; - if (version === inputVersion) { - return {url: Config.cdnUrls().ie + item, version: version}; - } - } - return {url: '', version: inputVersion}; - }); - } -} diff --git a/lib/binaries/index.ts b/lib/binaries/index.ts deleted file mode 100644 index 70c3d9b8..00000000 --- a/lib/binaries/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export * from './android_sdk'; -export * from './appium'; -export * from './binary'; -export * from './chrome_driver'; -export * from './gecko_driver'; -export * from './iedriver'; -export * from './standalone'; diff --git a/lib/binaries/standalone.ts b/lib/binaries/standalone.ts deleted file mode 100644 index 9a1ad998..00000000 --- a/lib/binaries/standalone.ts +++ /dev/null @@ -1,45 +0,0 @@ -import {Config} from '../config'; - -import {Binary, BinaryUrl, OS} from './binary'; -import {ConfigSource} from './config_source'; -import {StandaloneXml} from './standalone_xml'; - -export class Standalone extends Binary { - static id = 'standalone'; - static isDefault = true; - static os = [OS.Windows_NT, OS.Linux, OS.Darwin]; - static versionDefault = Config.binaryVersions().selenium; - - constructor(opt_alternativeCdn?: string) { - super(opt_alternativeCdn || Config.cdnUrls().selenium); - this.configSource = new StandaloneXml(); - this.name = 'selenium standalone'; - this.versionDefault = Standalone.versionDefault; - this.versionCustom = this.versionDefault; - } - - id(): string { - return Standalone.id; - } - - prefix(): string { - return 'selenium-server-standalone-'; - } - - suffix(): string { - return '.jar'; - } - - executableSuffix(): string { - return '.jar'; - } - - getVersionList(): Promise { - // If an alternative cdn is set, return an empty list. - if (this.alternativeDownloadUrl != null) { - return Promise.resolve([]); - } else { - return this.configSource.getVersionList(); - } - } -} diff --git a/lib/binaries/standalone_xml.ts b/lib/binaries/standalone_xml.ts deleted file mode 100644 index 66109ccb..00000000 --- a/lib/binaries/standalone_xml.ts +++ /dev/null @@ -1,94 +0,0 @@ -import * as semver from 'semver'; - -import {Config} from '../config'; - -import {BinaryUrl} from './binary'; -import {XmlConfigSource} from './config_source'; - -export class StandaloneXml extends XmlConfigSource { - constructor() { - super('standalone', Config.cdnUrls()['selenium']); - } - - getUrl(version: string): Promise { - if (version === 'latest') { - return this.getLatestStandaloneVersion(); - } else { - return this.getSpecificStandaloneVersion(version); - } - } - - getVersionList(): Promise { - return this.getXml().then(xml => { - let versionPaths: string[] = []; - - for (let content of xml.ListBucketResult.Contents) { - let contentKey: string = content.Key[0]; - - // Filter the selenium-server-standalone. - if (contentKey.includes('selenium-server-standalone')) { - versionPaths.push(contentKey); - } - } - return versionPaths; - }); - } - - private getLatestStandaloneVersion(): Promise { - return this.getVersionList().then(list => { - let standaloneVersion: string = null; - let latest = ''; - let latestVersion = ''; - // Use jar files that are not beta and not alpha versions. - const jarList = list.filter((i) => { - return i.endsWith('.jar') && !i.includes('beta') && !i.includes('alpha'); - }); - for (let item of jarList) { - // Get a semantic version. - let version = item.split('selenium-server-standalone-')[1].replace('.jar', ''); - if (standaloneVersion == null) { - // First time: use the version found. - standaloneVersion = version; - latest = item; - latestVersion = version; - } else if (semver.gt(version, standaloneVersion)) { - // Get the latest. - standaloneVersion = version; - latest = item; - latestVersion = version; - } - } - return {url: Config.cdnUrls().selenium + latest, version: latestVersion}; - }); - } - - private getSpecificStandaloneVersion(inputVersion: string): Promise { - return this.getVersionList().then(list => { - let itemFound = ''; - let standaloneVersion: string = null; - - for (let item of list) { - // Get a semantic version. - let version = item.split('selenium-server-standalone-')[1].replace('.jar', ''); - - // Check to see if the specified version matches. - let firstPath = item.split('/')[0]; - if (version === inputVersion) { - // Check if the beta exists that we have the right version - // Example: We will see that beta3 appears in the file and path - // 3.0-beta3/selenium-server-standalone-3.0.0-beta3.jar - // where this should not work: - // 3.0-beta2/selenium-server-standalone-3.0.0-beta3.jar - if (inputVersion.includes('beta')) { - let betaInputVersion = inputVersion.replace('.jar', '').split('beta')[1]; - if (item.split('/')[0].includes('beta' + betaInputVersion)) { - return {url: Config.cdnUrls().selenium + item, version: version}; - } - } else { - return {url: Config.cdnUrls().selenium + item, version: version}; - } - } - } - }); - } -} diff --git a/lib/cli/cli.ts b/lib/cli/cli.ts deleted file mode 100644 index a96b6678..00000000 --- a/lib/cli/cli.ts +++ /dev/null @@ -1,148 +0,0 @@ -import * as chalk from 'chalk'; -import * as path from 'path'; - -import {Config} from '../config'; - -import {MinimistArgs, Options} from './options'; -import {Program, Programs} from './programs'; - - - -/** - * The Cli contains the usage and the collection of programs. - * - * Printing help for all the programs in the following order: - * usage, commands, and options. If the options are used in multiple programs, - * it will list it once. - */ -export class Cli { - programs: Programs = {}; - usageText: string; - version: string; - - /** - * Register a program to the command line interface. - * @returns The cli for method chaining. - */ - program(prog: Program): Cli { - this.programs[prog.cmd] = prog; - return this; - } - - /** - * Add a usage for the command line interface. - * @returns The cli for method chaining. - */ - usage(usageText: string): Cli { - this.usageText = usageText; - return this; - } - - /** - * Prints help for the programs registered to the cli. - */ - printHelp(): void { - console.log('Usage: ' + this.usageText); - console.log('\nCommands:'); - let cmdDescriptionPos = this.posCmdDescription(); - for (let cmd in this.programs) { - let prog = this.programs[cmd]; - prog.printCmd(cmdDescriptionPos); - } - let descriptionPos = this.posDescription(); - let defaultPos = this.posDefault(); - let extOptions: Options = {}; - console.log('\nOptions:'); - // print all options - for (let cmd in this.programs) { - let prog = this.programs[cmd]; - prog.printOptions(descriptionPos, defaultPos, extOptions); - } - } - - /** - * For commands, gets the position where the description should start so they - * are aligned. - * @returns The position where the command description should start. - */ - posCmdDescription(): number { - let position = -1; - for (let cmd in this.programs) { - position = Math.max(position, cmd.length + 6); - } - return position; - } - - /** - * For options, gets the position where the description should start so they - * are aligned. - * @returns The position where the option description should start. - */ - posDescription(): number { - let position = -1; - for (let cmd in this.programs) { - let prog = this.programs[cmd]; - position = Math.max(position, prog.posDescription()); - } - return position; - } - - /** - * For options, get the position where the default values should start so they - * are aligned. - * @returns The position where the option default values should start. - */ - posDefault(): number { - let position = -1; - for (let cmd in this.programs) { - let prog = this.programs[cmd]; - position = Math.max(position, prog.posDefault()); - } - return position; - } - - /** - * Go through all programs and add options to the collection. - * @returns The options used in the programs. - */ - getOptions(): Options { - let allOptions: Options = {}; - for (let cmd in this.programs) { - let prog = this.programs[cmd]; - allOptions = prog.getOptions_(allOptions); - } - return allOptions; - } - - /** - * Get the options used by the programs and create the minimist options - * to ensure that minimist parses the values properly. - * @returns The options for minimist. - */ - getMinimistOptions(): Object { - let allOptions = this.getOptions(); - let minimistOptions: MinimistArgs = {}; - let minimistBoolean: string[] = []; - let minimistString: string[] = []; - let minimistNumber: string[] = []; - let minimistDefault: any = {}; - for (let opt in allOptions) { - let option = allOptions[opt]; - if (option.type === 'boolean') { - minimistBoolean.push(option.opt); - } else if (option.type === 'string') { - minimistString.push(option.opt); - } else if (option.type === 'number') { - minimistNumber.push(option.opt); - } - if (typeof option.defaultValue !== 'undefined') { - minimistDefault[option.opt] = option.defaultValue; - } - } - minimistOptions['boolean'] = minimistBoolean; - minimistOptions['string'] = minimistString; - minimistOptions['number'] = minimistNumber; - minimistOptions['default'] = minimistDefault; - return minimistOptions; - } -} diff --git a/lib/cli/index.ts b/lib/cli/index.ts index 3b0816a3..50408e33 100644 --- a/lib/cli/index.ts +++ b/lib/cli/index.ts @@ -1,4 +1,174 @@ -export * from './cli'; -export * from './options'; -export * from './programs'; -export * from './logger'; +import * as yargs from 'yargs'; +import * as clean from '../cmds/clean'; +import * as shutdown from '../cmds/shutdown'; +import * as start from '../cmds/start'; +import * as status from '../cmds/status'; +import * as update from '../cmds/update'; + +const CHROME = 'chrome'; +const chromeOption: yargs.Options = { + describe: 'Install or update chromedriver.', + default: true, + type: 'boolean' +}; +const CHROME_LOGS = 'chrome_logs'; +const chromeLogsOption: yargs.Options = { + describe: 'File path to chrome logs.', + type: 'string' +}; +const DETACH = 'detach'; +const detachOption: yargs.Options = { + describe: 'Once the selenium server is up and running, return ' + + 'control to the parent process and continue running the server ' + + 'in the background.', + default: false, + type: 'boolean' +}; +const EDGE = 'edge'; +const edgeOption: yargs.Options = { + describe: 'Use an installed Microsoft edge driver. Usually installed: ' + + '"C:\Program Files (x86)\Microsoft Web Driver\MirosoftWebDriver.exe"', + type: 'string' +}; +const GECKO = 'gecko'; +const geckoOption: yargs.Options = { + describe: 'Install or update geckodriver.', + default: true, + type: 'boolean' +}; +const GITHUB_TOKEN = 'github_token'; +const githubTokenOption: yargs.Options = { + describe: 'Use a GitHub token to prevent rate limit issues.', + type: 'string' +}; +const IEDRIVER = 'iedriver'; +const ieOption: yargs.Options = { + describe: 'Install or update ie driver.', + default: false, + type: 'boolean' +}; +const IGNORE_SSL = 'ignore_ssl'; +const ignoreSSLOption: yargs.Options = { + describe: 'Ignore SSL certificates.', + type: 'boolean' +}; +const LOG_LEVEL = 'log_level'; +const logLevelOption: yargs.Options = { + describe: 'The log level of this CLI.', + default: 'info', + type: 'string' +}; +const OUT_DIR = 'out_dir'; +const outDirOption: yargs.Options = { + describe: 'Location of output.', + type: 'string' +}; +const PROXY = 'proxy'; +const proxyOption: yargs.Options = { + describe: 'Use a proxy server to download files.', + type: 'string' +}; +const STANDALONE = 'standalone'; +const standaloneOption: yargs.Options = { + describe: 'Install or update selenium server standalone.', + default: true, + type: 'boolean' +}; +const STANDALONE_NODE = 'standalone_node'; +const standaloneNodeOption: yargs.Options = { + describe: 'Start the selenium server standalone with role set to "node".', + type: 'boolean' +}; +const VERSIONS_CHROME = 'versions.chrome'; +const versionsChromeOption: yargs.Options = { + describe: 'The chromedriver version.', + type: 'string' +}; +const VERSIONS_GECKO = 'versions.gecko'; +const versionsGeckoOption: yargs.Options = { + describe: 'The geckodriver version.', + type: 'string' +}; +const VERSIONS_IE = 'versions.ie'; +const versionsIeOption: yargs.Options = { + describe: 'The ie driver version.', + type: 'string' +}; +const VERSIONS_STANDALONE = 'versions.standalone'; +const versionsStandaloneOption: yargs.Options = { + describe: 'The selenium server standalone version.', + type: 'string' +}; + +// tslint:disable-next-line:no-unused-expression +yargs + .command( + 'clean', 'Removes downloaded files from the out_dir.', + (yargs: yargs.Argv) => { + return yargs.option(LOG_LEVEL, logLevelOption) + .option(OUT_DIR, outDirOption); + }, + (argv: yargs.Arguments) => { + clean.handler(argv); + }) + .command( + 'shutdown', 'Shutdown a local selenium server with GET request', + (yargs: yargs.Argv) => { + return yargs.option(LOG_LEVEL, logLevelOption); + }, + (argv: yargs.Arguments) => { + shutdown.handler(argv); + }) + .command( + 'start', 'Start up the selenium server.', + (yargs: yargs.Argv) => { + return yargs.option(CHROME, chromeOption) + .option(CHROME_LOGS, chromeLogsOption) + .option(DETACH, detachOption) + .option(EDGE, edgeOption) + .option(GECKO, geckoOption) + .option(IEDRIVER, ieOption) + .option(LOG_LEVEL, logLevelOption) + .option(OUT_DIR, outDirOption) + .option(STANDALONE, standaloneOption) + .option(STANDALONE_NODE, standaloneNodeOption) + .option(VERSIONS_CHROME, versionsChromeOption) + .option(VERSIONS_GECKO, versionsGeckoOption) + .option(VERSIONS_IE, versionsIeOption) + .option(VERSIONS_STANDALONE, versionsStandaloneOption); + }, + (argv: yargs.Arguments) => { + start.handler(argv); + }) + .command( + 'status', 'List the current available binaries.', + (yargs: yargs.Argv) => { + return yargs.option(LOG_LEVEL, logLevelOption) + .option(OUT_DIR, outDirOption); + }, + (argv: yargs.Arguments) => { + status.handler(argv); + }) + .command( + 'update', 'Install or update selected binaries.', + (yargs: yargs.Argv) => { + return yargs.option(OUT_DIR, outDirOption) + .option(CHROME, chromeOption) + .option(GECKO, geckoOption) + .option(GITHUB_TOKEN, githubTokenOption) + .option(IEDRIVER, ieOption) + .option(IGNORE_SSL, ignoreSSLOption) + .option(LOG_LEVEL, logLevelOption) + .option(OUT_DIR, outDirOption) + .option(PROXY, proxyOption) + .option(STANDALONE, standaloneOption) + .option(VERSIONS_CHROME, versionsChromeOption) + .option(VERSIONS_GECKO, versionsGeckoOption) + .option(VERSIONS_IE, versionsIeOption) + .option(VERSIONS_STANDALONE, versionsStandaloneOption); + }, + (argv: yargs.Arguments) => { + update.handler(argv); + }) + .help() + .argv; \ No newline at end of file diff --git a/lib/cli/logger.ts b/lib/cli/logger.ts deleted file mode 100644 index 771e425e..00000000 --- a/lib/cli/logger.ts +++ /dev/null @@ -1,268 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; - -// Will use chalk if chalk is available to add color to console logging -let chalk: any; -let printRed: Function; -let printYellow: Function; -let printGray: Function; - -try { - chalk = require('chalk'); - printRed = chalk.red; - printYellow = chalk.yellow; - printGray = chalk.gray; -} catch (e) { - printRed = printYellow = printGray = (msg: any) => { - return msg; - }; -} - -export enum LogLevel { - ERROR, - WARN, - INFO, - DEBUG -} - -export enum WriteTo { - CONSOLE, - FILE, - BOTH, - NONE -} - -let logFile = 'webdriver.log'; // the default log file name - -/** - * Logger class adds timestamp output, log levels, and identifiers to help - * when debugging. Also could write to console, file, both, or none. - */ -export class Logger { - static logLevel: LogLevel = LogLevel.INFO; - static showTimestamp: boolean = true; - static showId: boolean = true; - static writeTo: WriteTo = WriteTo.CONSOLE; - static fd: any; - static firstWrite: boolean = false; - - /** - * Set up the write location. If writing to a file, get the file descriptor. - * @param writeTo The enum for where to write the logs. - * @param opt_logFile An optional parameter to override the log file location. - */ - static setWrite(writeTo: WriteTo, opt_logFile?: string): void { - if (opt_logFile) { - logFile = opt_logFile; - } - Logger.writeTo = writeTo; - if (Logger.writeTo == WriteTo.FILE || Logger.writeTo == WriteTo.BOTH) { - Logger.fd = fs.openSync(path.resolve(logFile), 'a'); - Logger.firstWrite = false; - } - } - - /** - * Creates a logger instance with an ID for the logger. - * @constructor - */ - constructor(private id: string) {} - - /** - * Log INFO - * @param ...msgs multiple arguments to be logged. - */ - info(...msgs: any[]): void { - this.log_(LogLevel.INFO, msgs); - } - - /** - * Log DEBUG - * @param ...msgs multiple arguments to be logged. - */ - debug(...msgs: any[]): void { - this.log_(LogLevel.DEBUG, msgs); - } - - /** - * Log WARN - * @param ...msgs multiple arguments to be logged. - */ - warn(...msgs: any[]): void { - this.log_(LogLevel.WARN, msgs); - } - - /** - * Log ERROR - * @param ...msgs multiple arguments to be logged. - */ - error(...msgs: any[]): void { - this.log_(LogLevel.ERROR, msgs); - } - - /** - * For the log level set, check to see if the messages should be logged. - * @param logLevel The log level of the message. - * @param msgs The messages to be logged - */ - log_(logLevel: LogLevel, msgs: any[]): void { - switch (Logger.logLevel) { - case LogLevel.ERROR: - if (logLevel <= LogLevel.ERROR) { - this.print_(logLevel, msgs); - } - break; - case LogLevel.WARN: - if (logLevel <= LogLevel.WARN) { - this.print_(logLevel, msgs); - } - break; - case LogLevel.INFO: - if (logLevel <= LogLevel.INFO) { - this.print_(logLevel, msgs); - } - break; - case LogLevel.DEBUG: - if (logLevel <= LogLevel.DEBUG) { - this.print_(logLevel, msgs); - } - break; - default: - throw new Error('Log level undefined'); - } - } - - /** - * Format with timestamp, log level, identifier, and message and log to - * specified medium (console, file, both, none). - * @param logLevel The log level of the message. - * @param msgs The messages to be logged. - */ - print_(logLevel: LogLevel, msgs: any[]): void { - let consoleLog: string = ''; - let fileLog: string = ''; - - if (Logger.showTimestamp) { - consoleLog += Logger.timestamp_(WriteTo.CONSOLE); - fileLog += Logger.timestamp_(WriteTo.FILE); - } - consoleLog += Logger.level_(logLevel, this.id, WriteTo.CONSOLE); - fileLog += Logger.level_(logLevel, this.id, WriteTo.FILE); - if (Logger.showId) { - consoleLog += Logger.id_(logLevel, this.id, WriteTo.CONSOLE); - fileLog += Logger.id_(logLevel, this.id, WriteTo.FILE); - } - consoleLog += ' -'; - fileLog += ' - '; - - switch (Logger.writeTo) { - case WriteTo.CONSOLE: - msgs.unshift(consoleLog); - console.log.apply(console, msgs); - break; - case WriteTo.FILE: - // for the first line written to the file, add a space - if (!Logger.firstWrite) { - fs.writeSync(Logger.fd, '\n'); - Logger.firstWrite = true; - } - fileLog += ' ' + Logger.msgToFile_(msgs); - fs.writeSync(Logger.fd, fileLog + '\n'); - break; - case WriteTo.BOTH: - // for the first line written to the file, add a space - if (!Logger.firstWrite) { - fs.writeSync(Logger.fd, '\n'); - Logger.firstWrite = true; - } - fileLog += ' ' + Logger.msgToFile_(msgs); - fs.writeSync(Logger.fd, fileLog + '\n'); - msgs.unshift(consoleLog); - console.log.apply(console, msgs); - break; - case WriteTo.NONE: - break; - } - } - - /** - * Get a timestamp formatted with [hh:mm:ss] - * @param writeTo The enum for where to write the logs. - * @return The string of the formatted timestamp - */ - static timestamp_(writeTo: WriteTo): string { - let d = new Date(); - let ts = '['; - let hours = d.getHours() < 10 ? '0' + d.getHours() : d.getHours(); - let minutes = d.getMinutes() < 10 ? '0' + d.getMinutes() : d.getMinutes(); - let seconds = d.getSeconds() < 10 ? '0' + d.getSeconds() : d.getSeconds(); - if (writeTo == WriteTo.CONSOLE) { - ts += printGray(hours + ':' + minutes + ':' + seconds) + ']'; - } else { - ts += hours + ':' + minutes + ':' + seconds + ']'; - } - ts += ' '; - return ts; - } - - /** - * Get the identifier of the logger as '/' - * @param logLevel The log level of the message. - * @param writeTo The enum for where to write the logs. - * @return The string of the formatted id - */ - static id_(logLevel: LogLevel, id: string, writeTo: WriteTo): string { - let level = LogLevel[logLevel].toString(); - if (writeTo === WriteTo.FILE) { - return '/' + id; - } else if (logLevel === LogLevel.ERROR) { - return printRed('/' + id); - } else if (logLevel === LogLevel.WARN) { - return printYellow('/' + id); - } else { - return '/' + id; - } - } - - /** - * Get the log level formatted with the first letter. For info, it is I. - * @param logLevel The log level of the message. - * @param writeTo The enum for where to write the logs. - * @return The string of the formatted log level - */ - static level_(logLevel: LogLevel, id: string, writeTo: WriteTo): string { - let level = LogLevel[logLevel].toString(); - if (writeTo === WriteTo.FILE) { - return level[0]; - } else if (logLevel === LogLevel.ERROR) { - return printRed(level[0]); - } else if (logLevel === LogLevel.WARN) { - return printYellow(level[0]); - } else { - return level[0]; - } - } - - /** - * Convert the list of messages to a single string message. - * @param msgs The list of messages. - * @return The string of the formatted messages - */ - static msgToFile_(msgs: any[]): string { - let log = ''; - for (let pos = 0; pos < msgs.length; pos++) { - let msg = msgs[pos]; - let ret: any; - if (typeof msg === 'object') { - ret = JSON.stringify(msg); - } else { - ret = msg; - } - if (pos !== msgs.length - 1) { - ret += ' '; - } - log += ret; - } - return log; - } -} diff --git a/lib/cli/options.ts b/lib/cli/options.ts deleted file mode 100644 index ee8e78fb..00000000 --- a/lib/cli/options.ts +++ /dev/null @@ -1,74 +0,0 @@ -export interface MinimistArgs { [opt: string]: string[] } - -export interface Args { [opt: string]: number|string|boolean } - -export interface Options { [opt: string]: Option; } - -export class Option { - opt: string; - description: string; - type: string; - defaultValue: number|string|boolean; - value: number|string|boolean; - - constructor( - opt: string, description: string, type: string, defaultValue?: number|string|boolean) { - this.opt = opt; - this.description = description; - this.type = type; - if (defaultValue != null) { - this.defaultValue = defaultValue; - } - } - - getValue_(): number|string|boolean { - if (typeof this.value !== 'undefined') { - return this.value; - } else { - return this.defaultValue; - } - } - - getNumber(): number { - let value = this.getValue_(); - if (value != null && (typeof value === 'number' || typeof value === 'string')) { - return +value; - } else { - return null; - } - } - - getString(): string { - let value = this.getValue_(); - if (value != null) { - return '' + this.getValue_(); - } else { - return ''; - } - } - - getBoolean(): boolean { - let value = this.getValue_(); - if (value != null) { - if (typeof value === 'string') { - return !(value === '0' || value === 'false'); - } else if (typeof value === 'number') { - return value !== 0; - } else { - return value; - } - } - return false; - } -} - -export function unparseOptions(options: Options) { - var args: string[] = []; - for (let name in options) { - let value = options[name].getValue_(); - if (value !== options[name].defaultValue) { - args.push('--' + name, '' + value); - } - } - return args; -}; diff --git a/lib/cli/programs.ts b/lib/cli/programs.ts deleted file mode 100644 index 39d27ea5..00000000 --- a/lib/cli/programs.ts +++ /dev/null @@ -1,248 +0,0 @@ -import * as minimist from 'minimist'; - -import {Args, MinimistArgs, Option, Options} from './options'; - - -/** - * Dictionary that maps the command and the program. - */ -export interface Programs { [cmd: string]: Program; } - -/** - * A program has a command, a description, options, and a run method - */ -export class Program { - static MIN_SPACING: number = 4; - cmd: string; - cmdDescription: string - options: Options = {}; - runMethod: Function; - helpDescription: string; - version: string; - - /** - * Register a command and the description. - * @param cmd The command. - * @param cmdDescription The description of the command. - * @returns The program for method chaining. - */ - command(cmd: string, cmdDescription: string): Program { - this.cmd = cmd; - this.cmdDescription = cmdDescription; - return this; - } - - /** - * Register a new option. - * @param opt The option. - * @param description The description of the option. - * @param type The type of value expected: boolean, number, or string - * @param defaultValue The option's default value. - * @returns The program for method chaining. - */ - option(opt: string, description: string, type: string, opt_defaultValue?: number|string|boolean): - Program { - this.options[opt] = new Option(opt, description, type, opt_defaultValue); - return this; - } - - /** - * Adds an option to the program. - * @param option The option. - * @returns The program for method chaining. - */ - addOption(option: Option): Program { - this.options[option.opt] = option; - return this; - } - - /** - * Registers a method that will be used to run the program. - * @param runMethod The method that will be used to run the program. - * @returns The program for method chaining. - */ - action(runMethod: Function): Program { - this.runMethod = runMethod; - return this; - } - - /** - * Adds the value to the options and passes the updated options to the run - * method. - * @param args The arguments that will be parsed to run the method. - */ - run(json: JSON): Promise { - for (let opt in this.options) { - this.options[opt].value = this.getValue_(opt, json); - } - return Promise.resolve(this.runMethod(this.options)); - } - - private getValue_(key: string, json: JSON): number|boolean|string { - let keyList: string[] = key.split('.'); - let tempJson: any = json; - while (keyList.length > 0) { - let keyItem = keyList[0]; - if (tempJson[keyItem] != null) { - tempJson = tempJson[keyItem]; - keyList = keyList.slice(1); - } else { - return undefined; - } - } - return tempJson; - } - - /** - * Prints the command with the description. The description will have spaces - * between the cmd so that the starting position is "posDescription". If the - * gap between the cmd and the description is less than MIN_SPACING or - * posDescription is undefined, the spacing will be MIN_SPACING. - * - * @param opt_postDescription Starting position of the description. - */ - printCmd(opt_posDescription?: number): void { - let log = ' ' + this.cmd; - let spacing = Program.MIN_SPACING; - - if (opt_posDescription) { - let diff = opt_posDescription - log.length; - if (diff < Program.MIN_SPACING) { - spacing = Program.MIN_SPACING; - } else { - spacing = diff; - } - } - log += Array(spacing).join(' ') + this.cmdDescription; - console.log(log); - } - - /** - * Prints the options with the option descriptions and default values. - * The posDescription and posDefault is the starting position for the option - * description. If extOptions are provided, check to see if we have already - * printed those options. Also, once we print the option, add them to the extOptions. - * - * @param posDescription Position to start logging the description. - * @param posDefault Position to start logging the default value. - * @param opt_extOptions A collection of options that will be updated. - */ - printOptions(posDescription: number, posDefault: number, opt_extOptions?: Options): void { - for (let opt in this.options) { - // we have already logged it - if (opt_extOptions && opt_extOptions[opt]) { - continue; - } - - let option = this.options[opt]; - let log = ' --' + option.opt; - let spacing = Program.MIN_SPACING; - - // description - let diff = posDescription - log.length; - if (diff < Program.MIN_SPACING) { - spacing = Program.MIN_SPACING; - } else { - spacing = diff; - } - log += Array(spacing).join(' ') + option.description; - - // default value - if (option.defaultValue) { - spacing = Program.MIN_SPACING; - let diff = posDefault - log.length - 1; - if (diff <= Program.MIN_SPACING) { - spacing = Program.MIN_SPACING; - } else { - spacing = diff; - } - log += Array(spacing).join(' '); - log += '[default: ' + option.defaultValue + ']'; - } - - console.log(log); - if (opt_extOptions) { - opt_extOptions[option.opt] = option; - } - } - } - - /** - * Assuming that the this program can run by itself, to print out the program's - * help. Also assuming that the commands are called cmd-run and cmd-help. - */ - printHelp(): void { - console.log( - '\n' + - 'Usage: ' + this.cmd + ' [options]\n' + - ' ' + this.cmd + ' help\n' + - 'Description: ' + this.cmdDescription + '\n'); - console.log('Options:'); - this.printOptions(this.posDescription(), this.posDefault()); - } - - posDescription(): number { - return this.lengthOf_('opt') + 2 * Program.MIN_SPACING; - } - - posDefault(): number { - return this.posDescription() + this.lengthOf_('description') + Program.MIN_SPACING; - } - - lengthOf_(param: string): number { - let maxLength = -1; - for (let opt in this.options) { - let option = this.options[opt]; - if (param === 'description') { - maxLength = Math.max(maxLength, option.description.length); - } else if (param === 'opt') { - maxLength = Math.max(maxLength, option.opt.length); - } - } - return maxLength; - } - - /** - * Create a collection of options used by this program. - * @returns The options used in the programs. - */ - getOptions_(allOptions: Options): Options { - for (let opt in this.options) { - allOptions[opt] = this.options[opt]; - } - return allOptions; - } - - /** - * Get the options used by the program and create the minimist options - * to ensure that minimist parses the values properly. - * @returns The options for minimist. - */ - getMinimistOptions() { - let allOptions: Options = {}; - allOptions = this.getOptions_(allOptions); - let minimistOptions: MinimistArgs = {}; - let minimistBoolean: string[] = []; - let minimistString: string[] = []; - let minimistNumber: string[] = []; - let minimistDefault: any = {}; - for (let opt in allOptions) { - let option = allOptions[opt]; - if (option.type === 'boolean') { - minimistBoolean.push(option.opt); - } else if (option.type === 'string') { - minimistString.push(option.opt); - } else if (option.type === 'number') { - minimistNumber.push(option.opt); - } - if (typeof option.defaultValue !== 'undefined') { - minimistDefault[option.opt] = option.defaultValue; - } - } - minimistOptions['boolean'] = minimistBoolean; - minimistOptions['string'] = minimistString; - minimistOptions['number'] = minimistNumber; - minimistOptions['default'] = minimistDefault; - return minimistOptions; - } -} diff --git a/lib/cli_instance.ts b/lib/cli_instance.ts deleted file mode 100644 index 278ee457..00000000 --- a/lib/cli_instance.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {Cli} from './cli'; -import * as clean from './cmds/clean'; -import * as shutdown from './cmds/shutdown'; -import * as start from './cmds/start'; -import * as status from './cmds/status'; -import * as update from './cmds/update'; -import * as version from './cmds/version'; - -export let cli = new Cli() - .usage('webdriver-manager [options]') - .program(clean.program) - .program(start.program) - .program(shutdown.program) - .program(status.program) - .program(update.program) - .program(version.program); diff --git a/lib/cmds/clean.spec-e2e.ts b/lib/cmds/clean.spec-e2e.ts new file mode 100644 index 00000000..69c9e533 --- /dev/null +++ b/lib/cmds/clean.spec-e2e.ts @@ -0,0 +1,46 @@ +import * as fs from 'fs'; +import * as loglevel from 'loglevel'; +import * as os from 'os'; +import * as path from 'path'; +import * as rimraf from 'rimraf'; + +import {clean} from './clean'; +import {convertArgs2AllOptions} from './utils'; + +const log = loglevel.getLogger('webdriver-manager-test'); +log.setLevel('debug'); + +describe('using the cli', () => { + const tmpDir = path.resolve(os.tmpdir(), 'test'); + afterEach(() => { + try { + rimraf.sync(tmpDir); + } catch (err) { + } + }); + + describe('a user runs clean', () => { + it('should not log or throw errors if no folder exists', () => { + const argv = { + _: ['foobar'], + out_dir: tmpDir, + '$0': 'bin\\webdriver-manager' + }; + const options = convertArgs2AllOptions(argv); + const statusLog = clean(options); + expect(statusLog).toBe(''); + }); + + it('should not log or throw errors if empty folder exists', () => { + fs.mkdirSync(tmpDir); + const argv = { + _: ['foobar'], + out_dir: tmpDir, + '$0': 'bin\\webdriver-manager' + }; + const options = convertArgs2AllOptions(argv); + const statusLog = clean(options); + expect(statusLog).toBe(''); + }); + }); +}); \ No newline at end of file diff --git a/lib/cmds/clean.spec-int.ts b/lib/cmds/clean.spec-int.ts new file mode 100644 index 00000000..f48555c1 --- /dev/null +++ b/lib/cmds/clean.spec-int.ts @@ -0,0 +1,61 @@ +import * as fs from 'fs'; +import * as loglevel from 'loglevel'; +import * as os from 'os'; +import * as path from 'path'; +import * as rimraf from 'rimraf'; +import * as yargs from 'yargs'; + +import {clean} from './clean'; +import {convertArgs2AllOptions} from './utils'; + +const log = loglevel.getLogger('webdriver-manager-test'); +log.setLevel('debug'); + +const tmpDir = path.resolve(os.tmpdir(), 'test'); +const argv: yargs.Arguments = { + _: ['foobar'], + out_dir: tmpDir, + '$0': 'bin\\webdriver-manager' +}; +const options = convertArgs2AllOptions(argv); + +describe('clean cmd', () => { + describe('with files', () => { + beforeAll(() => { + // create the directory + try { + fs.mkdirSync(tmpDir); + } catch (err) { + } + // create files that should be deleted. + fs.closeSync(fs.openSync(path.resolve(tmpDir, 'chromedriver_2.41'), 'w')); + fs.closeSync( + fs.openSync(path.resolve(tmpDir, 'chromedriver_foo.zip'), 'w')); + fs.closeSync( + fs.openSync(path.resolve(tmpDir, 'chromedriver.config.json'), 'w')); + fs.closeSync(fs.openSync(path.resolve(tmpDir, 'chromedriver.xml'), 'w')); + }); + + afterAll(() => { + try { + rimraf.sync(tmpDir); + } catch (err) { + } + }); + + it('should remove the files', () => { + expect(fs.readdirSync(tmpDir).length).toBe(4); + const cleanList = clean(options).split('\n'); + expect(cleanList.length).toBe(4); + expect(fs.readdirSync(tmpDir).length).toBe(0); + }); + }); + + describe('with no files', () => { + it('should return nothing', () => { + spyOn(fs, 'unlinkSync'); + expect(clean(options)).toBe(''); + expect(fs.unlinkSync).not.toHaveBeenCalled(); + }); + }); +}); \ No newline at end of file diff --git a/lib/cmds/clean.ts b/lib/cmds/clean.ts index 39d5961d..c92165f5 100644 --- a/lib/cmds/clean.ts +++ b/lib/cmds/clean.ts @@ -1,40 +1,49 @@ -import * as minimist from 'minimist'; -import * as path from 'path'; +import * as loglevel from 'loglevel'; +import * as yargs from 'yargs'; +import {Options} from './options'; +import {OptionsBinary} from './options_binary'; +import {addOptionsBinary, convertArgs2AllOptions} from './utils'; -import {Options, Program} from '../cli'; -import {Config} from '../config'; -import {FileManager} from '../files'; +const log = loglevel.getLogger('webdriver-manager'); -import * as Opt from './'; -import {Opts} from './opts'; - -let prog = new Program() - .command('clean', 'removes all downloaded driver files from the out_dir') - .action(clean) - .addOption(Opts[Opt.OUT_DIR]); - -export var program = prog; +/** + * Handles removing files that were downloaded and logs the files. + * @param argv The argv from yargs. + */ +export function handler(argv: yargs.Arguments) { + log.setLevel(argv.log_level); + const options = convertArgs2AllOptions(argv); + log.info(clean(options)); +} -// stand alone runner -let argv = minimist(process.argv.slice(2), prog.getMinimistOptions()); -if (argv._[0] === 'clean-run') { - prog.run(JSON.parse(JSON.stringify(argv))); -} else if (argv._[0] === 'clean-help') { - prog.printHelp(); +/** + * Goes through all the providers and removes the downloaded files. + * @param options The constructed set of all options. + * @returns A list of deleted files. + */ +export function clean(options: Options): string { + const optionsBinary = addOptionsBinary(options); + return cleanBinary(optionsBinary); } /** - * Parses the options and cleans the output directory of binaries. - * @param: options + * Goes through all the providers and removes the downloaded files. + * @param optionsBinary The constructed set of all options with binaries. + * @returns A list of deleted files. */ -function clean(options: Options): void { - let outputDir = Config.getSeleniumDir(); - if (options[Opt.OUT_DIR].getString()) { - if (path.isAbsolute(options[Opt.OUT_DIR].getString())) { - outputDir = options[Opt.OUT_DIR].getString(); - } else { - outputDir = path.resolve(Config.getBaseDir(), options[Opt.OUT_DIR].getString()); +export function cleanBinary(optionsBinary: OptionsBinary): string { + const filesCleaned: string[] = []; + for (const browserDriver of optionsBinary.browserDrivers) { + const cleanedFiles = browserDriver.binary.cleanFiles(); + if (cleanedFiles) { + filesCleaned.push(cleanedFiles); } } - FileManager.removeExistingFiles(outputDir); -} + if (optionsBinary.server && optionsBinary.server.binary) { + const cleanedFiles = optionsBinary.server.binary.cleanFiles(); + if (cleanedFiles) { + filesCleaned.push(cleanedFiles); + } + } + return (filesCleaned.sort()).join('\n'); +} \ No newline at end of file diff --git a/lib/cmds/cmds.spec-e2e.ts b/lib/cmds/cmds.spec-e2e.ts new file mode 100644 index 00000000..decaa5fa --- /dev/null +++ b/lib/cmds/cmds.spec-e2e.ts @@ -0,0 +1,155 @@ +import * as fs from 'fs'; +import * as loglevel from 'loglevel'; +import * as os from 'os'; +import * as path from 'path'; +import * as rimraf from 'rimraf'; + +import {SeleniumServer} from '../provider/selenium_server'; + +import {clean} from './clean'; +import {startBinary} from './start'; +import {status} from './status'; +import {update} from './update'; +import {addOptionsBinary, convertArgs2AllOptions, convertArgs2Options} from './utils'; + +const log = loglevel.getLogger('webdriver-manager-test'); +log.setLevel('debug'); + +describe('using the cli', () => { + const tmpDir = path.resolve(os.tmpdir(), 'test'); + const origTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; + + beforeAll(() => { + jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; + try { + fs.mkdirSync(tmpDir); + } catch (err) { + } + }); + + afterAll(() => { + jasmine.DEFAULT_TIMEOUT_INTERVAL = origTimeout; + try { + rimraf.sync(tmpDir); + } catch (err) { + } + }); + + describe('a user runs update', () => { + it('should download the files', async () => { + const argv = { + _: ['foobar'], + chrome: true, + standalone: true, + out_dir: tmpDir, + '$0': 'bin\\webdriver-manager' + }; + const options = convertArgs2Options(argv); + await update(options); + const existFiles = fs.readdirSync(tmpDir); + expect(existFiles.length).toBe(7); + }); + }); + + describe('a user runs status', () => { + it('should get the list of versions', () => { + const argv = { + _: ['foobar'], + out_dir: tmpDir, + '$0': 'bin\\webdriver-manager' + }; + const options = convertArgs2AllOptions(argv); + const statusLog = status(options); + log.debug(statusLog); + const lines = statusLog.split('\n'); + expect(lines.length).toBe(2); + }); + }); + + describe('a user runs start', () => { + it('should start the selenium server standalone in role=node', async () => { + const argv = { + _: ['foobar'], + chrome: true, + standalone: true, + standalone_node: true, + out_dir: tmpDir, + '$0': 'bin\\webdriver-manager' + }; + const options = convertArgs2Options(argv); + const optionsBinary = addOptionsBinary(options); + // Do not await this promise to start the server since the promise is + // never resolved by waiting, it is either killed by pid or get request. + const startProcess = startBinary(optionsBinary); + + // Arbitrarily wait for the server to start. + await new Promise((resolve, _) => { + setTimeout(resolve, 3000); + }); + const seleniumServer = (optionsBinary.server.binary as SeleniumServer); + expect(seleniumServer.seleniumProcess).toBeTruthy(); + expect(seleniumServer.seleniumProcess.pid).toBeTruthy(); + + // Stop the server using the get request. + expect(seleniumServer.runAsNode).toBeTruthy(); + await seleniumServer.stopServer(); + + // Check to see that the exit code is 0. + expect(await startProcess).toBe(0); + await new Promise((resolve, _) => { + setTimeout(resolve, 5000); + }); + }); + + it('should start the selenium server standalone', async () => { + const argv = { + _: ['foobar'], + chrome: true, + standalone: true, + out_dir: tmpDir, + '$0': 'bin\\webdriver-manager' + }; + const options = convertArgs2Options(argv); + const optionsBinary = addOptionsBinary(options); + // Do not await this promise to start the server since the promise is + // never resolved by waiting, it is either killed by pid or get request. + const startProcess = startBinary(optionsBinary); + + // Arbitrarily wait for the server to start. + await new Promise((resolve, _) => { + setTimeout(resolve, 3000); + }); + const seleniumServer = (optionsBinary.server.binary as SeleniumServer); + expect(seleniumServer.seleniumProcess).toBeTruthy(); + expect(seleniumServer.seleniumProcess.pid).toBeTruthy(); + + // Stop the server using the get request. + expect(seleniumServer.runAsNode).toBeFalsy(); + await seleniumServer.stopServer(); + + // Check to see that the exit code is greater than 1. + // Observed to be 1 and sometimes 143. + expect(await startProcess).toBeGreaterThanOrEqual(1); + await new Promise((resolve, _) => { + setTimeout(resolve, 5000); + }); + }); + }); + + describe('a user runs clean', () => { + it('should remove the files', () => { + const argv = { + _: ['foobar'], + out_dir: tmpDir, + '$0': 'bin\\webdriver-manager' + }; + const options = convertArgs2AllOptions(argv); + const cleanLogs = clean(options); + log.debug(cleanLogs); + const lines = cleanLogs.split('\n'); + expect(lines.length).toBe(7); + const existFiles = fs.readdirSync(tmpDir); + expect(existFiles.length).toBe(0); + }); + }); +}); diff --git a/lib/cmds/index.ts b/lib/cmds/index.ts deleted file mode 100644 index d6ee736f..00000000 --- a/lib/cmds/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './opts'; diff --git a/lib/cmds/initialize.ts b/lib/cmds/initialize.ts deleted file mode 100644 index 2aee1dd8..00000000 --- a/lib/cmds/initialize.ts +++ /dev/null @@ -1,291 +0,0 @@ -import {ChildProcess, spawnSync} from 'child_process'; -import * as fs from 'fs'; -import * as glob from 'glob'; -import * as ini from 'ini'; -import * as path from 'path'; -import * as q from 'q'; - -import {Logger} from '../cli'; -import {Config} from '../config'; -import {spawn} from '../utils'; - - -const noop = () => {}; - -// Make a function which configures a child process to automatically respond -// to a certain question -function respondFactory(question: string, answer: string, verbose: boolean): Function { - return (child: ChildProcess) => { - (child.stdin).setDefaultEncoding('utf-8'); - child.stdout.on('data', (data: Buffer|String) => { - if (data != null) { - if (verbose) { - process.stdout.write(data as string); - } - if (data.toString().indexOf(question) != -1) { - child.stdin.write(answer + '\n'); - } - } - }); - }; -} - -// Run a command on the android SDK -function runAndroidSDKCommand( - sdkPath: string, cmd: string, args: string[], stdio?: string, - config_fun?: Function): q.Promise { - let child = spawn(path.resolve(sdkPath, 'tools', 'android'), [cmd].concat(args), stdio); - - if (config_fun) { - config_fun(child); - }; - - let deferred = q.defer() - child.on('exit', (code: number) => { - if (deferred != null) { - if (code) { - deferred.reject(code); - } else { - deferred.resolve(); - } - deferred = null; - } - }); - child.on('error', (err: Error) => { - if (deferred != null) { - deferred.reject(err); - deferred = null; - } - }); - return deferred.promise; -} - -// Download updates via the android SDK -function downloadAndroidUpdates( - sdkPath: string, targets: string[], search_all: boolean, auto_accept: boolean, - verbose: boolean): q.Promise { - return runAndroidSDKCommand( - sdkPath, 'update', - ['sdk', '-u'].concat(search_all ? ['-a'] : []).concat(['-t', targets.join(',')]), - auto_accept ? 'pipe' : 'inherit', - auto_accept ? respondFactory('Do you accept the license', 'y', verbose) : noop); -} - -// Setup hardware acceleration for x86-64 emulation -function setupHardwareAcceleration(sdkPath: string) { - // TODO(sjelin): linux setup - let toolDir = path.resolve(sdkPath, 'extras', 'intel', 'Hardware_Accelerated_Execution_Manager'); - if (Config.osType() == 'Darwin') { - console.log('Enabling hardware acceleration (requires root access)'); - // We don't need the wrapped spawnSync because we know we're on OSX - spawnSync('sudo', ['silent_install.sh'], {stdio: 'inherit', cwd: toolDir}); - } else if (Config.osType() == 'Windows_NT') { - console.log('Enabling hardware acceleration (requires admin access)'); - // We don't need the wrapped spawnSync because we know we're on windows - spawnSync('silent_install.bat', [], {stdio: 'inherit', cwd: toolDir}); - } -} - -// Get a list of all the SDK download targets for a given set of APIs, -// architectures, and platforms -function getAndroidSDKTargets( - apiLevels: string[], architectures: string[], platforms: string[], - oldAVDs: string[]): string[] { - function getSysImgTarget(architecture: string, platform: string, level: string) { - if (platform.toUpperCase() == 'DEFAULT') { - platform = 'android'; - } - return 'sys-img-' + architecture + '-' + platform + '-' + level; - } - - let targets = apiLevels - .map((level) => { - return 'android-' + level; - }) - .concat(architectures.reduce((targets, architecture) => { - return targets.concat.apply(targets, platforms.map((platform) => { - return apiLevels.map(getSysImgTarget.bind(null, architecture, platform)); - })); - }, [])); - - oldAVDs.forEach((name) => { - let avd = new AVDDescriptor(name); - if (targets.indexOf(avd.api) == -1) { - targets.push(avd.api); - } - let sysImgTarget = - getSysImgTarget(avd.architecture, avd.platform, avd.api.slice('android-'.length)); - if (targets.indexOf(sysImgTarget) == -1) { - targets.push(sysImgTarget); - } - }); - - return targets; -} - -// All the information about an android virtual device -class AVDDescriptor { - api: string; - platform: string; - architecture: string; - abi: string; - name: string; - - constructor(api: string, platform?: string, architecture?: string) { - if (platform != undefined) { - this.api = api; - this.platform = platform; - this.architecture = architecture; - this.name = [api, platform, architecture].join('-'); - } else { - this.name = api; - let nameParts = this.name.split('-'); - this.api = nameParts[0] + '-' + nameParts[1]; - if (/v[0-9]+[a-z]+/.test(nameParts[nameParts.length - 1]) && - (nameParts[nameParts.length - 2].slice(0, 3) == 'arm')) { - this.architecture = nameParts[nameParts.length - 2] + '-' + nameParts[nameParts.length - 1]; - } else { - this.architecture = nameParts[nameParts.length - 1]; - } - this.platform = this.name.slice(this.api.length + 1, -this.architecture.length - 1); - } - this.abi = - (this.platform.toUpperCase() == 'DEFAULT' ? '' : this.platform + '/') + this.architecture; - } - - avdName(version: string): string { - return this.name + '-v' + version + '-wd-manager'; - } -} - -// Gets the descriptors for all AVDs which are possible to make given the -// SDKs which were downloaded -function getAVDDescriptors(sdkPath: string): q.Promise { - let deferred = q.defer(); - // `glob` package always prefers patterns to use `/` - glob('system-images/*/*/*', {cwd: sdkPath}, (err: Error, files: string[]) => { - if (err) { - deferred.reject(err); - } else { - deferred.resolve(files.map((file: string) => { - // `file` could use `/` or `\`, so we use `path.normalize` - let info = path.normalize(file).split(path.sep).slice(-3); - return new AVDDescriptor(info[0], info[1], info[2]); - })); - } - }); - return deferred.promise; -} - -function sequentialForEach(array: T[], func: (x: T) => q.Promise): q.Promise { - let ret = q(null); - - array.forEach((x: T) => { - ret = ret.then(() => { - return func(x); - }); - }); - - return ret; -} - -// Configures the hardware.ini file for a system image of a new AVD -function configureAVDHardware(sdkPath: string, desc: AVDDescriptor): q.Promise { - let file = path.resolve( - sdkPath, 'system-images', desc.api, desc.platform, desc.architecture, 'hardware.ini'); - return q.nfcall(fs.stat, file) - .then( - (stats: fs.Stats) => { - return q.nfcall(fs.readFile, file); - }, - (err: Error) => { - return q(''); - }) - .then((contents: string|Buffer) => { - let config: any = ini.parse(contents.toString()); - config['hw.keyboard'] = 'yes'; - config['hw.battery'] = 'yes'; - config['hw.ramSize'] = 1024; - return q.nfcall(fs.writeFile, file, ini.stringify(config)); - }); -} - -// Make an android virtual device -function makeAVD( - sdkPath: string, desc: AVDDescriptor, version: string, verbose: boolean): q.Promise { - return runAndroidSDKCommand(sdkPath, 'delete', ['avd', '--name', desc.avdName(version)]) - .then(noop, noop) - .then(() => { - return runAndroidSDKCommand( - sdkPath, 'create', - ['avd', '--name', desc.avdName(version), '--target', desc.api, '--abi', desc.abi], - 'pipe', - respondFactory('Do you wish to create a custom hardware profile', 'no', verbose)); - }); -} - -// Initialize the android SDK -export function android( - sdkPath: string, apiLevels: string[], architectures: string[], platforms: string[], - acceptLicenses: boolean, version: string, oldAVDs: string[], logger: Logger, - verbose: boolean): void { - let avdDescriptors: AVDDescriptor[]; - let tools = ['platform-tool', 'tool']; - if ((Config.osType() == 'Darwin') || (Config.osType() == 'Windows_NT')) { - tools.push('extra-intel-Hardware_Accelerated_Execution_Manager'); - } - - logger.info('android-sdk: Downloading additional SDK updates'); - downloadAndroidUpdates(sdkPath, tools, false, acceptLicenses, verbose) - .then(() => { - return setupHardwareAcceleration(sdkPath); - }) - .then(() => { - logger.info( - 'android-sdk: Downloading more additional SDK updates ' + - '(this may take a while)'); - return downloadAndroidUpdates( - sdkPath, - ['build-tools-24.0.0'].concat( - getAndroidSDKTargets(apiLevels, architectures, platforms, oldAVDs)), - true, acceptLicenses, verbose); - }) - .then(() => { - return getAVDDescriptors(sdkPath); - }) - .then((descriptors: AVDDescriptor[]) => { - avdDescriptors = descriptors; - logger.info('android-sdk: Configuring virtual device hardware'); - return sequentialForEach(avdDescriptors, (descriptor: AVDDescriptor) => { - return configureAVDHardware(sdkPath, descriptor); - }); - }) - .then(() => { - return sequentialForEach(avdDescriptors, (descriptor: AVDDescriptor) => { - logger.info('android-sdk: Setting up virtual device "' + descriptor.name + '"'); - return makeAVD(sdkPath, descriptor, version, verbose); - }); - }) - .then(() => { - return q.nfcall( - fs.writeFile, path.resolve(sdkPath, 'available_avds.json'), - JSON.stringify(avdDescriptors.map((descriptor: AVDDescriptor) => { - return descriptor.name; - }))); - }) - .then(() => { - logger.info('android-sdk: Initialization complete'); - }) - .done(); -}; - -export function iOS(logger: Logger) { - if (Config.osType() != 'Darwin') { - throw new Error('Must be on a Mac to simulate iOS devices.'); - } - try { - fs.statSync('/Applications/Xcode.app'); - } catch (e) { - logger.warn('You must install the xcode commandline tools!'); - } -} diff --git a/lib/cmds/options.ts b/lib/cmds/options.ts new file mode 100644 index 00000000..0b8848ee --- /dev/null +++ b/lib/cmds/options.ts @@ -0,0 +1,48 @@ +/** + * An options object to update and start the server. + */ +export interface Options { + // A list of browser drivers. + browserDrivers?: BrowserDriver[]; + // The server. + server?: Server; + // The proxy url (must include protocol with url) + proxy?: string; + // To ignore SSL certs when making requests. + ignoreSSL?: boolean; + // The location where files should be saved. + outDir?: string; + // Use a github token for github requests. + githubToken?: string; +} + +/** + * Contains information about a browser driver. + */ +export interface BrowserDriver { + // The name of the browser driver. + name?: 'chromedriver'|'geckodriver'|'iedriver'; + // The version which does not have to follow semver. + version?: string; +} + +/** + * Contains information about the selenium server standalone. This includes + * options to start the server along with options to send to the server. + */ +export interface Server { + // The name of the server option. + name?: 'selenium'; + // The version which does not have to follow semver. + version?: string; + // Run as role = node option. + runAsNode?: boolean; + // The relative or full path to the chrome logs file. + chromeLogs?: string; + // The full path to the edge driver server. + edge?: string; + // Detach the server and return the process to the parent. + runAsDetach?: boolean; + // Port number to start the server. + port?: number; +} \ No newline at end of file diff --git a/lib/cmds/options_binary.ts b/lib/cmds/options_binary.ts new file mode 100644 index 00000000..ab549b85 --- /dev/null +++ b/lib/cmds/options_binary.ts @@ -0,0 +1,17 @@ +import {ProviderInterface} from '../provider/provider'; +import {BrowserDriver, Options, Server} from './options'; + +export interface OptionsBinary extends Options { + browserDrivers?: BrowserDriverBinary[]; + server?: ServerBinary; +} + +export interface BrowserDriverBinary extends BrowserDriver { + // The binary provider object. + binary?: ProviderInterface; +} + +export interface ServerBinary extends Server { + // The binary provider object. + binary?: ProviderInterface; +} \ No newline at end of file diff --git a/lib/cmds/opts.ts b/lib/cmds/opts.ts deleted file mode 100644 index 0e12506d..00000000 --- a/lib/cmds/opts.ts +++ /dev/null @@ -1,133 +0,0 @@ -import {AndroidSDK, Appium, ChromeDriver, GeckoDriver, IEDriver, Standalone} from '../binaries'; -import {Cli, Option, Options} from '../cli'; -import {Config} from '../config'; - -export const OUT_DIR = 'out_dir'; -export const SELENIUM_PORT = 'seleniumPort'; -export const APPIUM_PORT = 'appium-port'; -export const AVD_PORT = 'avd-port'; -export const IGNORE_SSL = 'ignore_ssl'; -export const PROXY = 'proxy'; -export const ALTERNATE_CDN = 'alternate_cdn'; -export const STANDALONE = 'standalone'; -export const CHROME = 'chrome'; -export const IE = 'ie'; -export const IE32 = 'ie32'; -export const IE64 = 'ie64'; -export const EDGE = 'edge'; -export const GECKO = 'gecko'; -export const ANDROID = 'android'; -export const IOS = 'ios'; -export const VERSIONS_CHROME = 'versions.chrome'; -export const VERSIONS_GECKO = 'versions.gecko'; -export const VERSIONS_STANDALONE = 'versions.standalone'; -export const VERSIONS_IE = 'versions.ie'; -export const VERSIONS_ANDROID = 'versions.android'; -export const VERSIONS_APPIUM = 'versions.appium'; -export const CHROME_LOGS = 'chrome_logs'; -export const LOGGING = 'logging'; -export const ANDROID_API_LEVELS = 'android-api-levels'; -export const ANDROID_ARCHITECTURES = 'android-archs'; -export const ANDROID_PLATFORMS = 'android-platorms'; -export const ANDROID_ACCEPT_LICENSES = 'android-accept-licenses'; -export const AVDS = 'avds'; -export const AVD_USE_SNAPSHOTS = 'avd-use-snapshots'; -export const STARTED_SIGNIFIER = 'started-signifier'; -export const SIGNAL_VIA_IPC = 'signal-via-ipc'; -export const DETACH = 'detach'; -export const QUIET = 'quiet'; -export const VERBOSE = 'verbose'; -export const ALREADY_OFF_ERROR = 'already-off-error'; - -/** - * The options used by the commands. - */ -var opts: Options = {}; -opts[OUT_DIR] = new Option(OUT_DIR, 'Location to output/expect', 'string', Config.getSeleniumDir()); -opts[SELENIUM_PORT] = - new Option(SELENIUM_PORT, 'Optional port for the selenium standalone server', 'string', '4444'); -opts[APPIUM_PORT] = - new Option(APPIUM_PORT, 'Optional port for the appium server', 'string', '4723'); -opts[AVD_PORT] = new Option( - AVD_PORT, 'Optional port for android virtual devices. See mobile.md for details', 'number', - 5554); -opts[IGNORE_SSL] = new Option(IGNORE_SSL, 'Ignore SSL certificates', 'boolean', false); -opts[PROXY] = new Option(PROXY, 'Proxy to use for the install or update command', 'string'); -opts[ALTERNATE_CDN] = new Option(ALTERNATE_CDN, 'Alternate CDN to binaries', 'string'); -opts[STANDALONE] = new Option( - STANDALONE, 'Install or update selenium standalone', 'boolean', Standalone.isDefault); -opts[CHROME] = - new Option(CHROME, 'Install or update chromedriver', 'boolean', ChromeDriver.isDefault); -opts[GECKO] = new Option(GECKO, 'Install or update geckodriver', 'boolean', GeckoDriver.isDefault); -opts[IE] = new Option(IE, 'Install or update 32-bit ie driver', 'boolean', IEDriver.isDefault32); -opts[IE32] = - new Option(IE32, 'Install or update 32-bit ie driver', 'boolean', IEDriver.isDefault32); -opts[IE64] = new Option( - IE64, 'Update: install or update 64-bit IE driver. Start: use installed x64 IE driver.', - 'boolean', IEDriver.isDefault64); -opts[EDGE] = new Option( - EDGE, 'Use installed Microsoft Edge driver', 'string', - 'C:\\Program Files (x86)\\Microsoft Web Driver\\MicrosoftWebDriver.exe'); -opts[ANDROID] = new Option(ANDROID, 'Update/use the android sdk', 'boolean', AndroidSDK.isDefault); -opts[IOS] = new Option(IOS, 'Update the iOS sdk', 'boolean', false); -opts[VERSIONS_CHROME] = new Option( - VERSIONS_CHROME, - 'Optional chrome driver version (use \'latest\' to get the most recent version)', 'string', - 'latest'); -opts[VERSIONS_GECKO] = - new Option(VERSIONS_GECKO, 'Optional gecko driver version', 'string', 'latest'); -opts[VERSIONS_ANDROID] = new Option( - VERSIONS_ANDROID, 'Optional android sdk version', 'string', AndroidSDK.versionDefault); -opts[VERSIONS_STANDALONE] = new Option( - VERSIONS_STANDALONE, - 'Optional seleniuim standalone server version (use \'latest\' to get the most recent version)', - 'string', 'latest'); -opts[VERSIONS_APPIUM] = - new Option(VERSIONS_APPIUM, 'Optional appium version', 'string', Appium.versionDefault); -opts[VERSIONS_IE] = new Option( - VERSIONS_IE, - 'Optional internet explorer driver version (use \'latest\' to get the most recent version)', - 'string', 'latest'); -opts[CHROME_LOGS] = new Option(CHROME_LOGS, 'File path to chrome logs', 'string', undefined); -opts[LOGGING] = new Option(LOGGING, 'File path to logging properties file', 'string', undefined); -opts[ANDROID_API_LEVELS] = new Option( - ANDROID_API_LEVELS, 'Which versions of the android API you want to emulate', 'string', - AndroidSDK.DEFAULT_API_LEVELS); -opts[ANDROID_ARCHITECTURES] = new Option( - ANDROID_ARCHITECTURES, - 'Which architectures you want to use in android emulation. By default it will try to match os.arch()', - 'string', AndroidSDK.DEFAULT_ARCHITECTURES); -opts[ANDROID_PLATFORMS] = new Option( - ANDROID_PLATFORMS, 'Which platforms you want to use in android emulation', 'string', - AndroidSDK.DEFAULT_PLATFORMS); -opts[ANDROID_ACCEPT_LICENSES] = - new Option(ANDROID_ACCEPT_LICENSES, 'Automatically accept android licenses', 'boolean', false); -opts[AVDS] = new Option( - AVDS, - 'Android virtual devices to emulate. Use "all" for emulating all possible devices, and "none" for no devices', - 'string', 'all'); -opts[AVD_USE_SNAPSHOTS] = new Option( - AVD_USE_SNAPSHOTS, - 'Rather than booting a new AVD every time, save/load snapshots of the last time it was used', - 'boolean', true); -opts[STARTED_SIGNIFIER] = new Option( - STARTED_SIGNIFIER, - 'A string to be outputted once the selenium server is up and running. Useful if you are writing a script which uses webdriver-manager.', - 'string'); -opts[SIGNAL_VIA_IPC] = new Option( - SIGNAL_VIA_IPC, - 'If you are using --' + STARTED_SIGNIFIER + - ', this flag will emit the signal string using process.send(), rather than writing it to stdout', - 'boolean', false); -opts[DETACH] = new Option( - DETACH, - 'Once the selenium server is up and running, return control to the parent process and continue running the server in the background.', - 'boolean', false); -opts[VERBOSE] = new Option(VERBOSE, 'Extra console output', 'boolean', false); -opts[QUIET] = new Option(QUIET, 'Minimal console output', 'boolean', false); -opts[ALREADY_OFF_ERROR] = new Option( - ALREADY_OFF_ERROR, - 'Normally if you try to shut down a selenium which is not running, you will get a warning. This turns it into an error', - 'boolean', false); - -export var Opts = opts; diff --git a/lib/cmds/shutdown.ts b/lib/cmds/shutdown.ts index 09965564..3963b01b 100644 --- a/lib/cmds/shutdown.ts +++ b/lib/cmds/shutdown.ts @@ -1,48 +1,44 @@ -import * as http from 'http'; -import * as minimist from 'minimist'; +import * as loglevel from 'loglevel'; +import * as yargs from 'yargs'; -import {Logger, Options, Program} from '../cli'; +import {SeleniumServer} from '../provider/selenium_server'; +import {Options} from './options'; +import {OptionsBinary} from './options_binary'; +import {addOptionsBinary} from './utils'; -import * as Opt from './'; -import {Opts} from './opts'; +const log = loglevel.getLogger('webdriver-manager'); - -let logger = new Logger('shutdown'); -let prog = new Program() - .command('shutdown', 'shut down the selenium server') - .action(shutdown) - .addOption(Opts[Opt.SELENIUM_PORT]) - .addOption(Opts[Opt.ALREADY_OFF_ERROR]); - -export var program = prog; - -// stand alone runner -let argv = minimist(process.argv.slice(2), prog.getMinimistOptions()); -if (argv._[0] === 'shutdown-run') { - prog.run(JSON.parse(JSON.stringify(argv))); -} else if (argv._[0] === 'shutdown-help') { - prog.printHelp(); +/** + * Handles making the get request to stop the selenium server standalone if the + * server has been started with role = node. + * @param argv The argv from yargs. + */ +export async function handler(argv: yargs.Arguments) { + log.setLevel(argv.log_level); + const seleniumServer = new SeleniumServer(); + seleniumServer.runAsNode = true; + const options: Options = {server: {name: 'selenium', runAsNode: true}}; + await shutdown(options); } /** - * Parses the options and starts the selenium standalone server. - * @param options + * Shutdown the selenium server with a get request. If the server is not + * started with role = node, nothing will happen. + * @param options The constructed option for the server. + * @returns A promise for the get request. */ -function shutdown(options: Options) { - logger.info('Attempting to shut down selenium nicely'); - http.get( - 'http://localhost:' + options[Opt.SELENIUM_PORT].getString() + - '/selenium-server/driver/?cmd=shutDownSeleniumServer') - .on('error', (e: NodeJS.ErrnoException) => { - if ((e.code == 'ECONNREFUSED') && (e.syscall == 'connect')) { - if (!options[Opt.ALREADY_OFF_ERROR].getBoolean()) { - logger.warn('Server does not appear to be on'); - } else { - logger.error('Server unreachable, probably not running'); - throw e; - } - } else { - throw e; - } - }); +export function shutdown(options: Options): Promise { + const optionsBinary = addOptionsBinary(options); + return shutdownBinary(optionsBinary); } + +/** + * Shutodown the selenium server with a get request. If the server is not + * started with role = node, nothing will happen. + * @param optionsBinary The constructed option for the server with binary. + * @returns A promise for the get request. + */ +export function shutdownBinary(optionsBinary: OptionsBinary): Promise { + const seleniumServer = optionsBinary.server.binary as SeleniumServer; + return seleniumServer.stopServer(); +} \ No newline at end of file diff --git a/lib/cmds/start.ts b/lib/cmds/start.ts index e395d046..0f36f97b 100644 --- a/lib/cmds/start.ts +++ b/lib/cmds/start.ts @@ -1,523 +1,77 @@ -import {ChildProcess} from 'child_process'; -import * as fs from 'fs'; -import * as http from 'http'; -import * as minimist from 'minimist'; +import * as loglevel from 'loglevel'; import * as path from 'path'; -import * as q from 'q'; +import * as yargs from 'yargs'; -import {AndroidSDK, Appium, Binary, BinaryMap, ChromeDriver, GeckoDriver, IEDriver, Standalone} from '../binaries'; -import {Logger, Options, Program, unparseOptions} from '../cli'; -import {Config} from '../config'; -import {FileManager} from '../files'; -import {adb, request, spawn} from '../utils'; +import {SeleniumServer} from '../provider/selenium_server'; +import {Options} from './options'; +import {OptionsBinary} from './options_binary'; +import {addOptionsBinary, convertArgs2Options} from './utils'; -import * as Opt from './'; -import {Opts} from './opts'; - -const commandName = 'start'; -Config.runCommand = commandName; - -let logger = new Logger('start'); -let prog = new Program() - .command(commandName, 'start up the selenium server') - .action(start) - .addOption(Opts[Opt.OUT_DIR]) - .addOption(Opts[Opt.SELENIUM_PORT]) - .addOption(Opts[Opt.APPIUM_PORT]) - .addOption(Opts[Opt.AVD_PORT]) - .addOption(Opts[Opt.VERSIONS_STANDALONE]) - .addOption(Opts[Opt.VERSIONS_CHROME]) - .addOption(Opts[Opt.VERSIONS_GECKO]) - .addOption(Opts[Opt.VERSIONS_ANDROID]) - .addOption(Opts[Opt.VERSIONS_APPIUM]) - .addOption(Opts[Opt.CHROME_LOGS]) - .addOption(Opts[Opt.LOGGING]) - .addOption(Opts[Opt.ANDROID]) - .addOption(Opts[Opt.AVDS]) - .addOption(Opts[Opt.AVD_USE_SNAPSHOTS]) - .addOption(Opts[Opt.STARTED_SIGNIFIER]) - .addOption(Opts[Opt.SIGNAL_VIA_IPC]) - .addOption(Opts[Opt.QUIET]) - .addOption(Opts[Opt.DETACH]); - -if (Config.osType() === 'Darwin') { - prog.addOption(Opts[Opt.IOS]); -} - -if (Config.osType() === 'Windows_NT') { - prog.addOption(Opts[Opt.VERSIONS_IE]).addOption(Opts[Opt.IE64]).addOption(Opts[Opt.EDGE]); -} - -export var program = prog; - -// stand alone runner -let argv = minimist(process.argv.slice(2), prog.getMinimistOptions()); -if (argv._[0] === 'start-run') { - prog.run(JSON.parse(JSON.stringify(argv))); -} else if (argv._[0] === 'start-help') { - prog.printHelp(); -} - -// Manage processes used in android emulation -let androidProcesses: ChildProcess[] = []; -let androidActiveAVDs: string[] = []; +const log = loglevel.getLogger('webdriver-manager'); /** - * Parses the options and starts the selenium standalone server. - * @param options + * Starts the selenium server standalone with browser drivers. Also handles + * the SIGINT event when the server is stopped. + * @param argv The argv from yargs. */ -function start(options: Options) { - if (options[Opt.DETACH].getBoolean()) { - return detachedRun(options); - } - - let osType = Config.osType(); - let stdio = options[Opt.QUIET].getBoolean() ? 'pipe' : 'inherit'; - let binaries = FileManager.setupBinaries(); - let seleniumPort = options[Opt.SELENIUM_PORT].getString(); - let appiumPort = options[Opt.APPIUM_PORT].getString(); - let avdPort = options[Opt.AVD_PORT].getNumber(); - let android = options[Opt.ANDROID].getBoolean(); - let outputDir = Config.getSeleniumDir(); - if (options[Opt.OUT_DIR].getString()) { - if (path.isAbsolute(options[Opt.OUT_DIR].getString())) { - outputDir = options[Opt.OUT_DIR].getString(); - } else { - outputDir = path.resolve(Config.getBaseDir(), options[Opt.OUT_DIR].getString()); - } - } - - try { - // check if folder exists - fs.statSync(outputDir).isDirectory(); - } catch (e) { - // if the folder does not exist, quit early. - logger.warn('the out_dir path ' + outputDir + ' does not exist, run webdriver-manager update'); - return; - } - - let chromeLogs: string = null; - let loggingFile: string = null; - if (options[Opt.CHROME_LOGS].getString()) { - if (path.isAbsolute(options[Opt.CHROME_LOGS].getString())) { - chromeLogs = options[Opt.CHROME_LOGS].getString(); - } else { - chromeLogs = path.resolve(Config.getBaseDir(), options[Opt.CHROME_LOGS].getString()); - } - } - binaries[Standalone.id].versionCustom = options[Opt.VERSIONS_STANDALONE].getString(); - binaries[ChromeDriver.id].versionCustom = options[Opt.VERSIONS_CHROME].getString(); - binaries[GeckoDriver.id].versionCustom = options[Opt.VERSIONS_GECKO].getString(); - if (options[Opt.VERSIONS_IE]) { - binaries[IEDriver.id].versionCustom = options[Opt.VERSIONS_IE].getString(); - } - binaries[AndroidSDK.id].versionCustom = options[Opt.VERSIONS_ANDROID].getString(); - binaries[Appium.id].versionCustom = options[Opt.VERSIONS_APPIUM].getString(); - let downloadedBinaries = FileManager.downloadedBinaries(outputDir); - - if (downloadedBinaries[Standalone.id] == null) { - logger.error( - 'Selenium Standalone is not present. Install with ' + - 'webdriver-manager update --standalone'); - process.exit(1); - } - let promises: q.IPromise[] = []; - let args: string[] = []; - if (osType === 'Linux') { - // selenium server may take a long time to start because /dev/random is BLOCKING if there is not - // enough entropy the solution is to use /dev/urandom, which is NON-BLOCKING (use /dev/./urandom - // because of a java bug) - // https://github.com/seleniumhq/selenium-google-code-issue-archive/issues/1301 - // https://bugs.openjdk.java.net/browse/JDK-6202721 - promises.push(Promise.resolve(args.push('-Djava.security.egd=file:///dev/./urandom'))); - } - if (options[Opt.LOGGING].getString()) { - if (path.isAbsolute(options[Opt.LOGGING].getString())) { - loggingFile = options[Opt.LOGGING].getString(); - } else { - loggingFile = path.resolve(Config.getBaseDir(), options[Opt.LOGGING].getString()); - } - promises.push(Promise.resolve(args.push('-Djava.util.logging.config.file=' + loggingFile))); - } - - if (downloadedBinaries[ChromeDriver.id] != null) { - let chrome: ChromeDriver = binaries[ChromeDriver.id]; - promises.push( - chrome.getUrl(chrome.versionCustom) - .then(() => { - args.push( - '-Dwebdriver.chrome.driver=' + - path.resolve(outputDir, binaries[ChromeDriver.id].executableFilename())); - if (chromeLogs != null) { - args.push('-Dwebdriver.chrome.logfile=' + chromeLogs); - } - }) - .catch(err => { - console.log(err); - })); - } - if (downloadedBinaries[GeckoDriver.id] != null) { - let gecko: GeckoDriver = binaries[GeckoDriver.id]; - promises.push(gecko.getUrl(gecko.versionCustom) - .then(() => { - args.push( - '-Dwebdriver.gecko.driver=' + - path.resolve(outputDir, binaries[GeckoDriver.id].executableFilename())); - }) - .catch(err => { - console.log(err); - })); - } - if (downloadedBinaries[IEDriver.id] != null) { - let ie: IEDriver = binaries[IEDriver.id]; - promises.push(ie.getUrl(ie.versionCustom) - .then(() => { - binaries[IEDriver.id].osarch = 'Win32'; // use Win 32 by default - if (options[Opt.IE64].getBoolean()) { - binaries[IEDriver.id].osarch = - Config.osArch(); // use the system architecture - } - args.push( - '-Dwebdriver.ie.driver=' + - path.resolve(outputDir, binaries[IEDriver.id].executableFilename())); - }) - .catch(err => { - console.log(err); - })); - } - if (options[Opt.EDGE] && options[Opt.EDGE].getString()) { - // validate that the file exists prior to adding it to args - try { - let edgeFile = options[Opt.EDGE].getString(); - if (fs.statSync(edgeFile).isFile()) { - promises.push( - Promise.resolve(args.push('-Dwebdriver.edge.driver=' + options[Opt.EDGE].getString()))); - } - } catch (err) { - // Either the default file or user specified location of the edge - // driver does not exist. - } - } - - Promise.all(promises).then(() => { - let standalone: Standalone = binaries[Standalone.id]; - standalone.getUrl(standalone.versionCustom) - .then(() => { - // starting android - if (android) { - if (downloadedBinaries[AndroidSDK.id] != null) { - let avds = options[Opt.AVDS].getString(); - startAndroid( - outputDir, binaries[AndroidSDK.id], avds.split(','), - options[Opt.AVD_USE_SNAPSHOTS].getBoolean(), avdPort, stdio); - } else { - logger.warn('Not starting android because it is not installed'); - } - } - if (downloadedBinaries[Appium.id] != null) { - startAppium(outputDir, binaries[Appium.id], binaries[AndroidSDK.id], appiumPort, stdio); - } - - args.push('-jar'); - args.push(path.resolve(outputDir, binaries[Standalone.id].filename())); - }) - .catch(err => { - console.log(err); - }) - .then(() => { - // Add the port parameter, has to declared after the jar file - if (seleniumPort) { - args.push('-port', seleniumPort); - } - - let argsToString = ''; - for (let arg in args) { - argsToString += ' ' + args[arg]; - } - logger.info('java' + argsToString); - - let seleniumProcess = spawn('java', args, stdio); - if (options[Opt.STARTED_SIGNIFIER].getString()) { - signalWhenReady( - options[Opt.STARTED_SIGNIFIER].getString(), - options[Opt.SIGNAL_VIA_IPC].getBoolean(), outputDir, seleniumPort, - downloadedBinaries[Appium.id] ? appiumPort : '', binaries[AndroidSDK.id], avdPort, - androidActiveAVDs); - } - logger.info('seleniumProcess.pid: ' + seleniumProcess.pid); - seleniumProcess.on('exit', (code: number) => { - logger.info('Selenium Standalone has exited with code ' + code); - shutdownEverything(); - process.exit(process.exitCode || code); - }); - seleniumProcess.on('error', (error: Error) => { - logger.warn('Selenium Standalone server encountered an error: ' + error); - }); - process.stdin.resume(); - process.stdin.on('data', (chunk: Buffer) => { - logger.info('Attempting to shut down selenium nicely'); - shutdownEverything(seleniumPort); - }); - process.on('SIGINT', () => { - logger.info('Staying alive until the Selenium Standalone process exits'); - shutdownEverything(seleniumPort); - }); - }); - }); -} - -function startAndroid( - outputDir: string, sdk: Binary, avds: string[], useSnapshots: boolean, port: number, - stdio: string): void { - let sdkPath = path.resolve(outputDir, sdk.executableFilename()); - if (avds[0] == 'all') { - avds = require(path.resolve(sdkPath, 'available_avds.json')); - } else if (avds[0] == 'none') { - avds.length = 0; - } - const minAVDPort = 5554; - const maxAVDPort = 5586 - 2 * avds.length; - if (avds.length && ((port < minAVDPort) || (port > maxAVDPort))) { - throw new RangeError( - 'AVD Port must be between ' + minAVDPort + ' and ' + maxAVDPort + ' to emulate ' + - avds.length + ' android devices'); - } - avds.forEach((avd: string, i: number) => { - // Credit to appium-ci, which this code was adapted from - let emuBin = 'emulator'; // TODO(sjelin): get the 64bit linux version working - let emuArgs = [ - '-avd', - avd + '-v' + sdk.versionCustom + '-wd-manager', - '-netfast', - ]; - let portArg: number = null; - if (!useSnapshots) { - emuArgs = emuArgs.concat(['-no-snapshot-load', '-no-snapshot-save']); - } - if (port) { - portArg = port + i * 2; - emuArgs = emuArgs.concat(['-port', '' + portArg]); - } - if (emuBin !== 'emulator') { - emuArgs = emuArgs.concat(['-qemu', '-enable-kvm']); - } - logger.info( - 'Starting ' + avd + ' on ' + (portArg == null ? 'default port' : 'port ' + portArg)); - let child = spawn(path.resolve(sdkPath, 'tools', emuBin), emuArgs, stdio); - child.on('error', (error: Error) => { - logger.warn(avd + ' encountered an error: ' + error); +export async function handler(argv: yargs.Arguments) { + log.setLevel(argv.log_level); + const options = convertArgs2Options(argv); + if (options.server.runAsDetach) { + await start(options); + process.exit(); + } else { + process.stdin.resume(); + process.on('SIGINT', () => { + const optionsBinary = addOptionsBinary(options); + const seleniumServer = (optionsBinary.server.binary as SeleniumServer); + process.kill(seleniumServer.seleniumProcess.pid); + process.exit(process.exitCode); }); - androidProcesses.push(child); - androidActiveAVDs.push(avd); - }); -} - -function killAndroid() { - for (var i = 0; i < androidProcesses.length; i++) { - logger.info('Shutting down ' + androidActiveAVDs[i]); - androidProcesses[i].kill(); + start(options).then(() => {}); } - androidProcesses.length = androidActiveAVDs.length = 0; } -// Manage appium process -let appiumProcess: ChildProcess; - -function startAppium( - outputDir: string, binary: Binary, androidSDK: Binary, port: string, stdio: string) { - logger.info('Starting appium server'); - if (androidSDK) { - process.env.ANDROID_HOME = path.resolve(outputDir, androidSDK.executableFilename()); - } - appiumProcess = spawn( - 'npm', ['run', 'appium'].concat(port ? ['--', '--port', port] : []), stdio, - {cwd: path.resolve(outputDir, binary.filename())}); -} - -function killAppium() { - if (appiumProcess != null) { - appiumProcess.kill(); - appiumProcess = null; - } +/** + * Goes through all the option providers and creates a set of java options + * to pass to java when starting the selenium server standalone. + * @param options The constructed options. + * @returns Promise starting the server with the resolved exit code. + */ +export function start(options: Options): Promise { + const optionsBinary = addOptionsBinary(options); + return startBinary(optionsBinary); } - -function signalWhenReady( - signal: string, viaIPC: boolean, outputDir: string, seleniumPort: string, appiumPort: string, - androidSDK: Binary, avdPort: number, avdNames: string[]) { - const maxWait = 10 * 60 * 1000; // Ten minutes - function waitFor( - getStatus: () => Promise, testStatus: (status: string) => boolean, desc?: string) { - const checkInterval = 100; - return new Promise((resolve, reject) => { - let waited = 0; - (function recursiveCheck() { - setTimeout(() => { - getStatus() - .then((status: string) => { - if (!testStatus(status)) { - return Promise.reject( - 'Invalid status' + (desc ? ' for ' + desc : '') + ': ' + status); - } - }) - .then( - () => { - resolve(); - }, - (error: any) => { - waited += checkInterval; - if (waited < maxWait) { - recursiveCheck(); - } else { - reject( - 'Timed out' + (desc ? ' wating for' + desc : '') + - '. Final rejection reason: ' + JSON.stringify(error)); - } - }); - }, checkInterval); - })(); - }); - }; - function waitForAndroid(avdPort: number, avdName: string, appiumPort: string): Promise { - let sdkPath = path.resolve(outputDir, androidSDK.executableFilename()); - logger.info('Waiting for ' + avdName + '\'s emulator to start'); - return adb(sdkPath, avdPort, 'wait-for-device', maxWait) - .then( - () => { - logger.info('Waiting for ' + avdName + '\'s OS to boot up'); - return waitFor( - () => { - return adb( - sdkPath, avdPort, 'shell', maxWait, ['getprop', 'sys.boot_completed']); - }, - (status: string) => { - return status.trim() == '1'; - }, - avdName + '\'s OS'); - }, - (error: {code: string|number, message: string}) => { - return Promise.reject( - 'Failed to wait for ' + avdName + '\'s emulator to start (' + error.code + ': ' + - error.message + ')'); - }) - .then(() => { - logger.info('Waiting for ' + avdName + ' to be ready to launch chrome'); - let version = AndroidSDK.VERSIONS[parseInt(avdName.slice('android-'.length))]; - return request('POST', appiumPort, '/wd/hub/session', maxWait, { - desiredCapabilities: { - browserName: 'chrome', - platformName: 'Android', - platformVersion: version, - deviceName: 'Android Emulator' - } - }) - .then( - (data) => { - return JSON.parse(data)['sessionId']; - }, - (error: {code: string|number, message: string}) => { - return Promise.reject( - 'Could not start chrome on ' + avdName + ' (' + error.code + ': ' + - error.message + ')'); - }); - }) - .then((sessionId: string) => { - logger.info('Shutting down dummy chrome instance for ' + avdName); - return request('DELETE', appiumPort, '/wd/hub/session/' + sessionId) - .then(() => {}, (error: {code: string|number, message: string}) => { - return Promise.reject( - 'Could not close chrome on ' + avdName + ' (' + error.code + ': ' + - error.message + ')'); - }); - }); - } - let pending = [waitFor( - () => { - return request('GET', seleniumPort, '/wd/hub/status', maxWait); - }, - (status) => { - return JSON.parse(status).status == 0; - }, - 'selenium server')]; - if (appiumPort) { - pending.push(waitFor( - () => { - return request('GET', appiumPort, '/wd/hub/status', maxWait); - }, - (status) => { - return JSON.parse(status).status == 0; - }, - 'appium server')); - } - if (androidSDK && avdPort) { - for (let i = 0; i < avdNames.length; i++) { - pending.push(waitForAndroid(avdPort + 2 * i, avdNames[i], appiumPort)); +/** + * Goes through all the option providers and creates a set of java options + * to pass to java when starting the selenium server standalone. + * @param optionsBinary The constructed options with binaries. + * @returns Promise starting the server with the resolved exit code. + */ +export function startBinary(optionsBinary: OptionsBinary): Promise { + const javaOpts: {[key: string]: string} = {}; + for (const browserDriver of optionsBinary.browserDrivers) { + if (browserDriver.binary) { + javaOpts[browserDriver.binary.seleniumFlag] = + browserDriver.binary.getBinaryPath(browserDriver.version); } } - Promise.all(pending).then( - () => { - logger.info('Everything started'); - sendStartedSignal(signal, viaIPC); - }, - (error) => { - logger.error(error); - shutdownEverything(seleniumPort); - process.exitCode = 1; - }); -} -function sendStartedSignal(signal: string, viaIPC: boolean) { - if (viaIPC) { - if (process.send) { - return process.send(signal); - } else { - logger.warn('No IPC channel, sending signal via stdout'); + if (optionsBinary.server) { + if (optionsBinary.server.chromeLogs) { + const chromeLogs = + optionsBinary.server.chromeLogs.replace('"', '').replace('\'', ''); + javaOpts['-Dwebdriver.chrome.logfile'] = path.resolve(chromeLogs); } - } - console.log(signal); -} - -function shutdownEverything(seleniumPort?: string) { - if (seleniumPort) { - http.get( - 'http://localhost:' + seleniumPort + '/selenium-server/driver/?cmd=shutDownSeleniumServer'); - } - killAndroid(); - killAppium(); -} - -function detachedRun(options: Options) { - var file = path.resolve(__dirname, '..', 'webdriver.js'); - var oldSignal = options[Opt.STARTED_SIGNIFIER].getString(); - var oldViaIPC = options[Opt.SIGNAL_VIA_IPC].getBoolean(); - options[Opt.DETACH].value = false; - options[Opt.STARTED_SIGNIFIER].value = 'server started'; - options[Opt.SIGNAL_VIA_IPC].value = true; - let args: string[] = [file, commandName].concat(unparseOptions(options)); - - var unreffed = false; - let child = spawn(process.execPath, args, ['ignore', 1, 2, 'ipc']); - - child.on('message', (message: string) => { - if (message == options[Opt.STARTED_SIGNIFIER].getString()) { - if (oldSignal) { - sendStartedSignal(oldSignal, oldViaIPC); - } - logger.info('Detached pid: ' + child.pid); - child.disconnect(); - child.unref(); - unreffed = true; + if (optionsBinary.server.edge) { + const edge = optionsBinary.server.edge.replace('"', '').replace('\'', ''); + javaOpts['-Dwebdriver.edge.driver'] = path.resolve(edge); } - }); - - child.on('exit', (code: number) => { - if (!unreffed) { - if (code == 0) { - logger.warn('Server never seemed to start, and has now exited'); - } else { - logger.error('Server never seemed to start, and has probably crashed'); - } - process.exit(code); + if (optionsBinary.server.binary) { + return (optionsBinary.server.binary as SeleniumServer) + .startServer(javaOpts, optionsBinary.server.version); } - }); -} + } + return Promise.reject('Could not start the server'); +} \ No newline at end of file diff --git a/lib/cmds/start_stop.spec-int.ts b/lib/cmds/start_stop.spec-int.ts new file mode 100644 index 00000000..4ea19a09 --- /dev/null +++ b/lib/cmds/start_stop.spec-int.ts @@ -0,0 +1,90 @@ +import * as fs from 'fs'; +import * as http from 'http'; +import * as loglevel from 'loglevel'; +import * as os from 'os'; +import * as path from 'path'; +import * as rimraf from 'rimraf'; + +import {findPort} from '../../spec/support/helpers/port_finder'; +import {ChromeDriver} from '../provider/chromedriver'; +import {SeleniumServer} from '../provider/selenium_server'; + +import {OptionsBinary} from './options_binary'; +import {shutdownBinary} from './shutdown'; +import {startBinary} from './start'; +import {updateBinary} from './update'; + +const log = loglevel.getLogger('webdriver-manager-test'); +log.setLevel('debug'); +loglevel.getLogger('webdriver-manager').setLevel('info'); +const tmpDir = path.resolve(os.tmpdir(), 'test'); +const selenium = + new SeleniumServer({outDir: tmpDir, runAsDetach: true, runAsNode: true}); + +const optionsBinary: OptionsBinary = { + outDir: tmpDir, + browserDrivers: [{binary: new ChromeDriver({outDir: tmpDir})}], + server: {binary: selenium, runAsDetach: true, runAsNode: true} +}; +let port: number; + +describe('start and stop cmd', () => { + const origTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; + + beforeAll(() => { + jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; + try { + fs.mkdirSync(tmpDir); + } catch (err) { + } + }); + + afterAll(() => { + try { + rimraf.sync(tmpDir); + } catch (err) { + } + jasmine.DEFAULT_TIMEOUT_INTERVAL = origTimeout; + }); + + describe('start', () => { + beforeAll(async () => { + await updateBinary(optionsBinary); + }); + + it('should run the detached server', async () => { + port = await findPort(4000, 5000); + optionsBinary.server.port = port; + (optionsBinary.server.binary as SeleniumServer).port = port; + await startBinary(optionsBinary); + const hubUrl = `http://localhost:${port}/wd/hub/static/resource/hub.html`; + const responseCode = new Promise((resolve, reject) => { + http.get(hubUrl, res => { + if (res.statusCode === 200) { + resolve(res.statusCode); + } else { + reject('Should be 200'); + } + }); + }); + expect(await responseCode).toBe(200); + }); + }); + + describe('stop', () => { + it('should shutdown the detached server', async () => { + const hubUrl = `http://localhost:${port}/wd/hub/static/resource/hub.html`; + await shutdownBinary(optionsBinary); + await new Promise((resolve, _) => { + setTimeout(resolve, 3000); + }); + // tslint:disable-next-line:no-any + const noResponse = new Promise((resolve, _) => { + http.get(hubUrl, _ => {}).on('error', (err) => { + resolve(err); + }); + }); + expect((await noResponse)['code']).toBe('ECONNREFUSED'); + }); + }); +}); \ No newline at end of file diff --git a/lib/cmds/status.spec-e2e.ts b/lib/cmds/status.spec-e2e.ts new file mode 100644 index 00000000..655da5d4 --- /dev/null +++ b/lib/cmds/status.spec-e2e.ts @@ -0,0 +1,47 @@ +import * as fs from 'fs'; +import * as loglevel from 'loglevel'; +import * as os from 'os'; +import * as path from 'path'; +import * as rimraf from 'rimraf'; + +import {status} from './status'; +import {convertArgs2AllOptions} from './utils'; + +const log = loglevel.getLogger('webdriver-manager-test'); +log.setLevel('debug'); + +describe('using the cli', () => { + const tmpDir = path.resolve(os.tmpdir(), 'test'); + + afterEach(() => { + try { + rimraf.sync(tmpDir); + } catch (err) { + } + }); + + describe('a user runs status', () => { + it('should log an empty string when folder does not exist', () => { + const argv = { + _: ['foobar'], + out_dir: tmpDir, + '$0': 'bin\\webdriver-manager' + }; + const options = convertArgs2AllOptions(argv); + const statusLog = status(options); + expect(statusLog).toBe(''); + }); + + it('should log an empty string when folder is empty', () => { + fs.mkdirSync(tmpDir); + const argv = { + _: ['foobar'], + out_dir: tmpDir, + '$0': 'bin\\webdriver-manager' + }; + const options = convertArgs2AllOptions(argv); + const statusLog = status(options); + expect(statusLog).toBe(''); + }); + }); +}); \ No newline at end of file diff --git a/lib/cmds/status.spec-int.ts b/lib/cmds/status.spec-int.ts new file mode 100644 index 00000000..9da1db91 --- /dev/null +++ b/lib/cmds/status.spec-int.ts @@ -0,0 +1,57 @@ +import * as fs from 'fs'; +import * as loglevel from 'loglevel'; +import * as os from 'os'; +import * as path from 'path'; +import * as rimraf from 'rimraf'; +import * as yargs from 'yargs'; + +import {statusBinary} from './status'; +import {addOptionsBinary, convertArgs2AllOptions} from './utils'; + +const log = loglevel.getLogger('webdriver-manager-test'); +log.setLevel('debug'); + +const tmpDir = path.resolve(os.tmpdir(), 'test'); +const argv: yargs.Arguments = { + _: ['foobar'], + out_dir: tmpDir, + '$0': 'bin\\webdriver-manager' +}; +const options = convertArgs2AllOptions(argv); +const optionsBinary = addOptionsBinary(options); +for (const browserDriver of optionsBinary.browserDrivers) { + browserDriver.binary.osType = 'Linux'; +} +optionsBinary.server.binary.osType = 'Linux'; + +describe('status cmd', () => { + describe('with files', () => { + beforeAll(() => { + // create the directory + try { + fs.mkdirSync(tmpDir); + } catch (err) { + } + const contents = { + last: 'chromedriver_2.41', + all: ['chromedriver_2.20', 'chromedriver_2.41'] + }; + fs.writeFileSync( + path.resolve(tmpDir, 'chromedriver.config.json'), + JSON.stringify(contents)); + }); + + afterAll(() => { + try { + rimraf.sync(tmpDir); + } catch (err) { + } + }); + + it('should get the status for chromedriver', () => { + const lines = statusBinary(optionsBinary).split('\n'); + expect(lines.length).toBe(1); + expect(lines[0]).toBe('chromedriver: 2.20, 2.41 (latest)'); + }); + }); +}); \ No newline at end of file diff --git a/lib/cmds/status.ts b/lib/cmds/status.ts index 2def1963..e1b10c3f 100644 --- a/lib/cmds/status.ts +++ b/lib/cmds/status.ts @@ -1,119 +1,49 @@ -import * as fs from 'fs'; -import * as minimist from 'minimist'; -import * as path from 'path'; -import * as semver from 'semver'; +import * as loglevel from 'loglevel'; +import * as yargs from 'yargs'; +import {Options} from './options'; +import {OptionsBinary} from './options_binary'; +import {addOptionsBinary, convertArgs2AllOptions} from './utils'; -import {AndroidSDK, Appium, ChromeDriver, GeckoDriver, IEDriver, Standalone} from '../binaries'; -import {getValidSemver} from '../binaries/chrome_xml'; -import {Logger, Options, Program} from '../cli'; -import {Config} from '../config'; -import {FileManager} from '../files'; +const log = loglevel.getLogger('webdriver-manager'); -import * as Opt from './'; -import {Opts} from './opts'; - -let logger = new Logger('status'); -let prog = new Program() - .command('status', 'list the current available drivers') - .addOption(Opts[Opt.OUT_DIR]) - .action(status); - -export var program = prog; - -// stand alone runner -let argv = minimist(process.argv.slice(2), prog.getMinimistOptions()); -if (argv._[0] === 'status-run') { - prog.run(JSON.parse(JSON.stringify(argv))); -} else if (argv._[0] === 'status-help') { - prog.printHelp(); +/** + * Displays which versions of providers that have been downloaded. + * @param argv The argv from yargs. + */ +export function handler(argv: yargs.Arguments) { + log.setLevel(argv.log_level); + const options = convertArgs2AllOptions(argv); + console.log(status(options)); } /** - * Parses the options and logs the status of the binaries downloaded. - * @param options + * Gets a list of versions for server and browser drivers. + * @param options The constructed set of all options. + * @returns A string of the versions downloaded. */ -function status(options: Options) { - let binaries = FileManager.setupBinaries(); - let outputDir = Config.getSeleniumDir(); - if (options[Opt.OUT_DIR].value) { - if (path.isAbsolute(options[Opt.OUT_DIR].getString())) { - outputDir = options[Opt.OUT_DIR].getString(); - } else { - outputDir = path.resolve(Config.getBaseDir(), options[Opt.OUT_DIR].getString()); - } - } - - try { - // check if folder exists - fs.statSync(outputDir).isDirectory(); - } catch (e) { - // if the folder does not exist, quit early. - logger.warn('the out_dir path ' + outputDir + ' does not exist'); - return; - } - - // Try to get the update-config.json. This will be used for showing the last binary downloaded. - let updateConfig: any = {}; - try { - updateConfig = - JSON.parse(fs.readFileSync(path.resolve(outputDir, 'update-config.json')).toString()) || {}; - } catch (err) { - updateConfig = {}; - } - - let downloadedBinaries = FileManager.downloadedBinaries(outputDir); - - // Log which binaries have been downloaded. - for (let bin in downloadedBinaries) { - let downloaded = downloadedBinaries[bin]; - let log = downloaded.name + ' '; - log += downloaded.versions.length == 1 ? 'version available: ' : 'versions available: '; - - // Get the "last" downloaded binary from the updateConfig. - let last: string = null; - if (downloaded.binary instanceof Appium && updateConfig[Appium.id]) { - last = updateConfig[Appium.id]['last']; - } else if (downloaded.binary instanceof AndroidSDK && updateConfig[AndroidSDK.id]) { - last = updateConfig[AndroidSDK.id]['last']; - } else if (downloaded.binary instanceof ChromeDriver && updateConfig[ChromeDriver.id]) { - last = updateConfig[ChromeDriver.id]['last']; - } else if (downloaded.binary instanceof GeckoDriver && updateConfig[GeckoDriver.id]) { - last = updateConfig[GeckoDriver.id]['last']; - } else if (downloaded.binary instanceof IEDriver && updateConfig[IEDriver.id]) { - last = updateConfig[IEDriver.id]['last']; - } else if (downloaded.binary instanceof Standalone && updateConfig[Standalone.id]) { - last = updateConfig[Standalone.id]['last']; - } +export function status(options: Options): string { + const optionsBinary = addOptionsBinary(options); + return statusBinary(optionsBinary); +} - // Sort the versions then log them: - // - last: the last binary downloaded by webdriver-manager per the update-config.json - downloaded.versions = downloaded.versions.sort((a: string, b: string): number => { - if (!semver.valid(a)) { - a = getValidSemver(a); - b = getValidSemver(b); - } - if (semver.gt(a, b)) { - return 1; - } else { - return 0; - } - }); - for (let ver in downloaded.versions) { - let version = downloaded.versions[ver]; - log += version; - if (last && last.indexOf(version) >= 0) { - log += ' [last]' - } - if (+ver != downloaded.versions.length - 1) { - log += ', '; - } +/** + * Gets a list of versions for server and browser drivers. + * @param optionsBinary The constructed set of all options with binaries. + * @returns A string of the versions downloaded. + */ +export function statusBinary(optionsBinary: OptionsBinary): string { + const binaryVersions = []; + for (const browserDriver of optionsBinary.browserDrivers) { + const status = browserDriver.binary.getStatus(); + if (status) { + binaryVersions.push(`${browserDriver.name}: ${status}`); } - logger.info(log); } - // for binaries that are available for the operating system, show them here - for (let bin in binaries) { - if (downloadedBinaries[bin] == null) { - logger.info(binaries[bin].name + ' is not present'); + if (optionsBinary.server && optionsBinary.server.binary) { + const status = optionsBinary.server.binary.getStatus(); + if (status) { + binaryVersions.push(`${optionsBinary.server.name}: ${status}`); } } -} + return (binaryVersions.sort()).join('\n'); +} \ No newline at end of file diff --git a/lib/cmds/update.spec-int.ts b/lib/cmds/update.spec-int.ts new file mode 100644 index 00000000..b9ae9e1a --- /dev/null +++ b/lib/cmds/update.spec-int.ts @@ -0,0 +1,93 @@ +import * as fs from 'fs'; +import * as loglevel from 'loglevel'; +import * as os from 'os'; +import * as path from 'path'; +import * as rimraf from 'rimraf'; + +import {ChromeDriver} from '../provider/chromedriver'; +import {GeckoDriver} from '../provider/geckodriver'; +import {IEDriver} from '../provider/iedriver'; +import {SeleniumServer} from '../provider/selenium_server'; +import {OptionsBinary} from './options_binary'; +import {updateBinary} from './update'; + +const log = loglevel.getLogger('webdriver-manager-test'); +log.setLevel('debug'); + +const tmpDir = path.resolve(os.tmpdir(), 'test'); + +describe('update cmd', () => { + const origTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; + + beforeAll(() => { + jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; + }); + + afterAll(() => { + jasmine.DEFAULT_TIMEOUT_INTERVAL = origTimeout; + }); + + beforeEach(() => { + // create the directory + try { + fs.mkdirSync(tmpDir); + } catch (err) { + } + }); + + afterEach(() => { + try { + rimraf.sync(tmpDir); + } catch (err) { + } + }); + + it('should download the chromdriver files', async () => { + const optionsBinary: OptionsBinary = { + outDir: tmpDir, + browserDrivers: [{binary: new ChromeDriver({outDir: tmpDir})}] + }; + await updateBinary(optionsBinary); + expect(fs.readdirSync(tmpDir).length).toBe(4); + }); + + it('should download the geckodriver files', async () => { + const optionsBinary: OptionsBinary = { + outDir: tmpDir, + browserDrivers: [{binary: new GeckoDriver({outDir: tmpDir})}] + }; + await updateBinary(optionsBinary); + expect(fs.readdirSync(tmpDir).length).toBe(4); + }); + + it('should download selenium server files', async () => { + const optionsBinary: OptionsBinary = { + outDir: tmpDir, + server: {binary: new SeleniumServer({outDir: tmpDir})} + }; + await updateBinary(optionsBinary); + expect(fs.readdirSync(tmpDir).length).toBe(3); + }); + + it('should download iedriver files', async () => { + const iedriver = new IEDriver({outDir: tmpDir}); + iedriver.osType = 'Windows_NT'; + const optionsBinary: + OptionsBinary = {outDir: tmpDir, browserDrivers: [{binary: iedriver}]}; + await updateBinary(optionsBinary); + expect(fs.readdirSync(tmpDir).length).toBe(4); + }); + + it('should download default files', async () => { + const optionsBinary: OptionsBinary = { + outDir: tmpDir, + browserDrivers: [ + {binary: new ChromeDriver({outDir: tmpDir})}, + {binary: new GeckoDriver({outDir: tmpDir})}, + ], + server: {binary: new SeleniumServer({outDir: tmpDir})} + }; + await updateBinary(optionsBinary); + expect(fs.readdirSync(tmpDir).length).toBe(11); + }); +}); \ No newline at end of file diff --git a/lib/cmds/update.ts b/lib/cmds/update.ts index 66763ce3..3853c828 100644 --- a/lib/cmds/update.ts +++ b/lib/cmds/update.ts @@ -1,333 +1,46 @@ -import * as AdmZip from 'adm-zip'; -import * as child_process from 'child_process'; -import * as fs from 'fs'; -import * as minimist from 'minimist'; -import * as path from 'path'; -import * as q from 'q'; -import * as rimraf from 'rimraf'; +import * as loglevel from 'loglevel'; +import * as yargs from 'yargs'; +import {Options} from './options'; +import {OptionsBinary} from './options_binary'; +import {addOptionsBinary, convertArgs2Options} from './utils'; -import {AndroidSDK, Appium, Binary, ChromeDriver, GeckoDriver, IEDriver, Standalone} from '../binaries'; -import {Logger, Options, Program} from '../cli'; -import {Config} from '../config'; -import {Downloader, FileManager} from '../files'; -import {HttpUtils} from '../http_utils'; -import {spawn} from '../utils'; - -import * as Opt from './'; -import {android as initializeAndroid, iOS as checkIOS} from './initialize'; -import {Opts} from './opts'; - -Config.runCommand = 'update'; - -let logger = new Logger('update'); -let prog = new Program() - .command('update', 'install or update selected binaries') - .action(update) - .addOption(Opts[Opt.OUT_DIR]) - .addOption(Opts[Opt.VERBOSE]) - .addOption(Opts[Opt.IGNORE_SSL]) - .addOption(Opts[Opt.PROXY]) - .addOption(Opts[Opt.ALTERNATE_CDN]) - .addOption(Opts[Opt.STANDALONE]) - .addOption(Opts[Opt.CHROME]) - .addOption(Opts[Opt.GECKO]) - .addOption(Opts[Opt.ANDROID]) - .addOption(Opts[Opt.ANDROID_API_LEVELS]) - .addOption(Opts[Opt.ANDROID_ARCHITECTURES]) - .addOption(Opts[Opt.ANDROID_PLATFORMS]) - .addOption(Opts[Opt.ANDROID_ACCEPT_LICENSES]); - -if (Config.osType() === 'Darwin') { - prog.addOption(Opts[Opt.IOS]); -} - -if (Config.osType() === 'Windows_NT') { - prog.addOption(Opts[Opt.IE]).addOption(Opts[Opt.IE32]).addOption(Opts[Opt.IE64]); -} - -prog.addOption(Opts[Opt.VERSIONS_STANDALONE]) - .addOption(Opts[Opt.VERSIONS_CHROME]) - .addOption(Opts[Opt.VERSIONS_APPIUM]) - .addOption(Opts[Opt.VERSIONS_ANDROID]) - .addOption(Opts[Opt.VERSIONS_GECKO]); - -if (Config.osType() === 'Windows_NT') { - prog.addOption(Opts[Opt.VERSIONS_IE]); -} -export let program = prog; - -// stand alone runner -let argv = minimist(process.argv.slice(2), prog.getMinimistOptions()); -if (argv._[0] === 'update-run') { - prog.run(JSON.parse(JSON.stringify(argv))); -} else if (argv._[0] === 'update-help') { - prog.printHelp(); -} - -let browserFile: BrowserFile; +const log = loglevel.getLogger('webdriver-manager'); /** - * Parses the options and downloads binaries if they do not exist. - * @param options + * Updates / downloads the providers binaries. + * @param argv The argv from yargs. */ -function update(options: Options): Promise { - let promises: q.IPromise[] = []; - let standalone = options[Opt.STANDALONE].getBoolean(); - let chrome = options[Opt.CHROME].getBoolean(); - let gecko = options[Opt.GECKO].getBoolean(); - let ie32: boolean = false; - let ie64: boolean = false; - if (options[Opt.IE]) { - ie32 = ie32 || options[Opt.IE].getBoolean(); - } - if (options[Opt.IE32]) { - ie32 = ie32 || options[Opt.IE32].getBoolean(); - } - if (options[Opt.IE64]) { - ie64 = options[Opt.IE64].getBoolean(); - } - let android: boolean = options[Opt.ANDROID].getBoolean(); - let ios: boolean = false; - if (options[Opt.IOS]) { - ios = options[Opt.IOS].getBoolean(); - } - let outputDir = options[Opt.OUT_DIR].getString(); - - try { - browserFile = - JSON.parse(fs.readFileSync(path.resolve(outputDir, 'update-config.json')).toString()); - } catch (err) { - browserFile = {}; - } - - let android_api_levels: string[] = options[Opt.ANDROID_API_LEVELS].getString().split(','); - let android_architectures: string[] = options[Opt.ANDROID_ARCHITECTURES].getString().split(','); - let android_platforms: string[] = options[Opt.ANDROID_PLATFORMS].getString().split(','); - let android_accept_licenses: boolean = options[Opt.ANDROID_ACCEPT_LICENSES].getBoolean(); - if (options[Opt.OUT_DIR].getString()) { - if (path.isAbsolute(options[Opt.OUT_DIR].getString())) { - outputDir = options[Opt.OUT_DIR].getString(); - } else { - outputDir = path.resolve(Config.getBaseDir(), options[Opt.OUT_DIR].getString()); - } - FileManager.makeOutputDirectory(outputDir); - } - let ignoreSSL = options[Opt.IGNORE_SSL].getBoolean(); - let proxy = options[Opt.PROXY].getString(); - HttpUtils.assignOptions({ignoreSSL, proxy}); - let verbose = options[Opt.VERBOSE].getBoolean(); - - // setup versions for binaries - let binaries = FileManager.setupBinaries(options[Opt.ALTERNATE_CDN].getString()); - binaries[Standalone.id].versionCustom = options[Opt.VERSIONS_STANDALONE].getString(); - binaries[ChromeDriver.id].versionCustom = options[Opt.VERSIONS_CHROME].getString(); - if (options[Opt.VERSIONS_IE]) { - binaries[IEDriver.id].versionCustom = options[Opt.VERSIONS_IE].getString(); - } - if (options[Opt.VERSIONS_GECKO]) { - binaries[GeckoDriver.id].versionCustom = options[Opt.VERSIONS_GECKO].getString(); - } - binaries[AndroidSDK.id].versionCustom = options[Opt.VERSIONS_ANDROID].getString(); - binaries[Appium.id].versionCustom = options[Opt.VERSIONS_APPIUM].getString(); - - // if the file has not been completely downloaded, download it - // else if the file has already been downloaded, unzip the file, rename it, and give it - // permissions - if (standalone) { - let binary: Standalone = binaries[Standalone.id]; - promises.push(FileManager.downloadFile(binary, outputDir) - .then((downloaded: boolean) => { - if (!downloaded) { - logger.info( - binary.name + ': file exists ' + - path.resolve(outputDir, binary.filename())); - logger.info(binary.name + ': ' + binary.filename() + ' up to date'); - } - }) - .then(() => { - updateBrowserFile(binary, outputDir); - })); - } - if (chrome) { - let binary: ChromeDriver = binaries[ChromeDriver.id]; - promises.push(updateBinary(binary, outputDir, proxy, ignoreSSL).then(() => { - return Promise.resolve(updateBrowserFile(binary, outputDir)); - })); - } - if (gecko) { - let binary: GeckoDriver = binaries[GeckoDriver.id]; - promises.push(updateBinary(binary, outputDir, proxy, ignoreSSL).then(() => { - return Promise.resolve(updateBrowserFile(binary, outputDir)); - })); - } - if (ie64) { - let binary: IEDriver = binaries[IEDriver.id]; - binary.osarch = Config.osArch(); // Win32 or x64 - promises.push(updateBinary(binary, outputDir, proxy, ignoreSSL).then(() => { - return Promise.resolve(updateBrowserFile(binary, outputDir)); - })); - } - if (ie32) { - let binary: IEDriver = binaries[IEDriver.id]; - binary.osarch = 'Win32'; - promises.push(updateBinary(binary, outputDir, proxy, ignoreSSL).then(() => { - return Promise.resolve(updateBrowserFile(binary, outputDir)); - })); - } - if (android) { - let binary = binaries[AndroidSDK.id]; - let sdk_path = path.resolve(outputDir, binary.executableFilename()); - let oldAVDList: string; - - updateBrowserFile(binary, outputDir); - promises.push(q.nfcall(fs.readFile, path.resolve(sdk_path, 'available_avds.json')) - .then( - (oldAVDs: string) => { - oldAVDList = oldAVDs; - }, - () => { - oldAVDList = '[]'; - }) - .then(() => { - return updateBinary(binary, outputDir, proxy, ignoreSSL); - }) - .then(() => { - initializeAndroid( - path.resolve(outputDir, binary.executableFilename()), - android_api_levels, android_architectures, android_platforms, - android_accept_licenses, binaries[AndroidSDK.id].versionCustom, - JSON.parse(oldAVDList), logger, verbose); - })); - } - if (ios) { - checkIOS(logger); - } - if (android || ios) { - installAppium(binaries[Appium.id], outputDir); - updateBrowserFile(binaries[Appium.id], outputDir); - } - - return Promise.all(promises).then(() => { - writeBrowserFile(outputDir); - }); +export async function handler(argv: yargs.Arguments) { + log.setLevel(argv.log_level); + const options = convertArgs2Options(argv); + await update(options); } -function updateBinary( - binary: T, outputDir: string, proxy: string, ignoreSSL: boolean): Promise { - return FileManager - .downloadFile( - binary, outputDir, - (binary: Binary, outputDir: string, fileName: string) => { - unzip(binary, outputDir, fileName); - }) - .then(downloaded => { - if (!downloaded) { - // The file did not have to download, we should unzip it. - logger.info(binary.name + ': file exists ' + path.resolve(outputDir, binary.filename())); - let fileName = binary.filename(); - unzip(binary, outputDir, fileName); - logger.info(binary.name + ': ' + binary.executableFilename() + ' up to date'); - } - }); +/** + * Updates / downloads the providers binaries. + * @param options The constructed options. + * @returns Promise when binaries are all downloaded. + */ +export function update(options: Options): Promise { + const optionsBinary = addOptionsBinary(options); + return updateBinary(optionsBinary); } -function unzip(binary: T, outputDir: string, fileName: string): void { - // remove the previously saved file and unzip it - let osType = Config.osType(); - let mv = path.resolve(outputDir, binary.executableFilename()); - try { - fs.unlinkSync(mv); - } catch (err) { - try { - rimraf.sync(mv); - } catch (err2) { - } - } - - // unzip the file - logger.info(binary.name + ': unzipping ' + fileName); - if (fileName.slice(-4) == '.zip') { - try { - let zip = new AdmZip(path.resolve(outputDir, fileName)); - zip.extractAllTo(outputDir, true); - } catch (e) { - throw new Error(`Invalid filename: ${path.resolve(outputDir, fileName)}`) - } - } else { - // We will only ever get .tar files on linux - child_process.spawnSync('tar', ['zxvf', path.resolve(outputDir, fileName), '-C', outputDir]); - } - - // rename - fs.renameSync(path.resolve(outputDir, binary.zipContentName()), mv); - - // set permissions - if (osType !== 'Windows_NT') { - logger.info(binary.name + ': setting permissions to 0755 for ' + mv); - if (binary.id() !== AndroidSDK.id) { - fs.chmodSync(mv, '0755'); - } else { - fs.chmodSync(path.resolve(mv, 'tools', 'android'), '0755'); - fs.chmodSync(path.resolve(mv, 'tools', 'emulator'), '0755'); - // TODO(sjelin): get 64 bit versions working +/** + * Updates / downloads the providers binaries. + * @param optionsBinary The constructed options with binaries. + * @returns Promise when binaries are all downloaded. + */ +export function updateBinary(optionsBinary: OptionsBinary): Promise { + const promises = []; + if (optionsBinary.browserDrivers) { + for (const provider of optionsBinary.browserDrivers) { + promises.push(provider.binary.updateBinary(provider.version)); } } -} - -function installAppium(binary: Binary, outputDir: string): void { - logger.info('appium: installing appium'); - - let folder = path.resolve(outputDir, binary.filename()); - try { - rimraf.sync(folder); - } catch (err) { - } - - fs.mkdirSync(folder); - fs.writeFileSync( - path.resolve(folder, 'package.json'), JSON.stringify({scripts: {appium: 'appium'}})); - spawn('npm', ['install', 'appium@' + binary.version()], null, {cwd: folder}); -} - -interface BinaryPath { - last?: string, all?: string[] -} - -interface BrowserFile { - chrome?: BinaryPath, standalone?: BinaryPath, gecko?: BinaryPath, iedriver?: BinaryPath -} - -function updateBrowserFile(binary: T, outputDir: string) { - let currentDownload = path.resolve(outputDir, binary.executableFilename()); - - // if browserFile[id] exists, we should update it - if ((browserFile as any)[binary.id()]) { - let binaryPath: BinaryPath = (browserFile as any)[binary.id()]; - if (binaryPath.last === currentDownload) { - return; - } else { - binaryPath.last = currentDownload; - for (let bin of binaryPath.all) { - if (bin === currentDownload) { - return; - } - } - binaryPath.all.push(currentDownload); - } - } else { - // The browserFile[id] does not exist / has not been downloaded previously. - // We should create the entry. - let binaryPath: BinaryPath = {last: currentDownload, all: [currentDownload]}; - (browserFile as any)[binary.id()] = binaryPath; + if (optionsBinary.server && optionsBinary.server.binary) { + promises.push( + optionsBinary.server.binary.updateBinary(optionsBinary.server.version)); } -} - -function writeBrowserFile(outputDir: string) { - let filePath = path.resolve(outputDir, 'update-config.json'); - fs.writeFileSync(filePath, JSON.stringify(browserFile)); -} - -// for testing -export function clearBrowserFile() { - browserFile = {}; -} + return Promise.all(promises); +} \ No newline at end of file diff --git a/lib/cmds/utils.spec-unit.ts b/lib/cmds/utils.spec-unit.ts new file mode 100644 index 00000000..96ea951e --- /dev/null +++ b/lib/cmds/utils.spec-unit.ts @@ -0,0 +1,66 @@ +import {convertArgs2AllOptions, convertArgs2Options} from './utils'; + +describe('utils', () => { + describe('convertArgs2AllOptions', () => { + it('should create all providers', () => { + const argv = { + _: ['foobar'], + proxy: 'http://some.proxy.com', + versions: {gecko: '0.16.0', chrome: '2.20'}, + out_dir: 'foobar_download', + ignore_ssl: false, + '$0': 'bin\\webdriver-manager' + }; + const options = convertArgs2AllOptions(argv); + expect(options.browserDrivers).toBeTruthy(); + expect(options.browserDrivers.length).toBe(3); + for (const provider of options.browserDrivers) { + if (provider.name === 'geckodriver') { + expect(provider.version).toBe('0.16.0'); + } + if (provider.name === 'chromedriver') { + expect(provider.version).toBe('2.20'); + } + if (provider.name === 'iedriver') { + expect(provider.version).toBeUndefined(); + } + } + expect(options.server).toBeTruthy(); + expect(options.server.name).toBe('selenium'); + expect(options.server.version).toBeUndefined(); + expect(options.proxy).toBe('http://some.proxy.com'); + expect(options.ignoreSSL).toBeFalsy(); + expect(options.outDir).toBe('foobar_download'); + }); + }); + + describe('convertArgs2Options', () => { + it('should create the default providers', () => { + const argv = { + _: ['foobar'], + chrome: true, + gecko: true, + standalone: true, + versions: {gecko: '0.16.0', chrome: '2.20'}, + out_dir: 'foobar_download', + '$0': 'bin\\webdriver-manager' + }; + const options = convertArgs2Options(argv); + expect(options.browserDrivers).toBeTruthy(); + expect(options.browserDrivers.length).toBe(2); + for (const provider of options.browserDrivers) { + if (provider.name === 'geckodriver') { + expect(provider.version).toBe('0.16.0'); + } + if (provider.name === 'chromedriver') { + expect(provider.version).toBe('2.20'); + } + } + expect(options.server).toBeTruthy(); + expect(options.server.name).toBe('selenium'); + expect(options.server.version).toBeUndefined(); + expect(options.proxy).toBeUndefined(); + expect(options.ignoreSSL).toBeUndefined(); + }); + }); +}); \ No newline at end of file diff --git a/lib/cmds/utils.ts b/lib/cmds/utils.ts new file mode 100644 index 00000000..47c4b304 --- /dev/null +++ b/lib/cmds/utils.ts @@ -0,0 +1,125 @@ +import * as yargs from 'yargs'; +import {ChromeDriver} from '../provider/chromedriver'; +import {GeckoDriver} from '../provider/geckodriver'; +import {IEDriver} from '../provider/iedriver'; +import {ProviderConfig} from '../provider/provider'; +import {SeleniumServer, SeleniumServerProviderConfig} from '../provider/selenium_server'; + +import {Options} from './options'; +import {OptionsBinary} from './options_binary'; + +/** + * Converts an options object into an options binary object. + * @param options + */ +export function addOptionsBinary(options: Options): OptionsBinary { + if (!options) { + return null; + } + const providerConfig: ProviderConfig = { + ignoreSSL: options.ignoreSSL, + outDir: options.outDir, + proxy: options.proxy + }; + + const optionsBinary: OptionsBinary = options; + if (optionsBinary.browserDrivers) { + for (const browserDriver of optionsBinary.browserDrivers) { + if (browserDriver.name === 'chromedriver') { + browserDriver.binary = new ChromeDriver(providerConfig); + } else if (browserDriver.name === 'geckodriver') { + const geckoProviderConfig = providerConfig; + geckoProviderConfig.oauthToken = optionsBinary.githubToken; + browserDriver.binary = new GeckoDriver(geckoProviderConfig); + } else if (browserDriver.name === 'iedriver') { + browserDriver.binary = new IEDriver(providerConfig); + } + } + } + if (optionsBinary.server) { + const seleniumProviderConfig: SeleniumServerProviderConfig = providerConfig; + seleniumProviderConfig.outDir = optionsBinary.outDir; + seleniumProviderConfig.port = optionsBinary.server.port; + seleniumProviderConfig.runAsDetach = optionsBinary.server.runAsDetach; + seleniumProviderConfig.runAsNode = optionsBinary.server.runAsNode; + optionsBinary.server.binary = new SeleniumServer(seleniumProviderConfig); + } + return optionsBinary; +} + +/** + * Create the options with all providers. Used for clean and status commands. + * @param argv + */ +export function convertArgs2AllOptions(argv: yargs.Arguments): Options { + let versionsChrome, versionsGecko, versionsIe, versionsStandalone = undefined; + if (argv.versions) { + versionsChrome = argv.versions.chrome as string; + versionsGecko = argv.versions.gecko as string; + versionsIe = argv.versions.ie as string; + versionsStandalone = argv.versions.standalone as string; + } + return { + browserDrivers: [ + {name: 'chromedriver', version: versionsChrome}, + {name: 'geckodriver', version: versionsGecko}, + {name: 'iedriver', version: versionsIe} + ], + server: { + name: 'selenium', + version: versionsStandalone, + runAsNode: argv.standalone_node as boolean, + runAsDetach: argv.detach as boolean, + chromeLogs: argv.chrome_logs as string, + edge: argv.edge as string, + }, + ignoreSSL: argv.ignore_ssl as boolean, + outDir: argv.out_dir as string, + proxy: argv.proxy as string, + githubToken: argv.github_token as string, + }; +} + +/** + * Create the options with providers depending on argv's. Used for update and + * start commands. + * @param argv + */ +export function convertArgs2Options(argv: yargs.Arguments): Options { + const options: Options = { + browserDrivers: [], + server: null, + ignoreSSL: argv.ignore_ssl as boolean, + outDir: argv.out_dir as string, + proxy: argv.proxy as string, + githubToken: argv.github_token as string, + }; + + let versionsChrome, versionsGecko, versionsIe, versionsStandalone = undefined; + if (argv.versions) { + versionsChrome = argv.versions.chrome as string; + versionsGecko = argv.versions.gecko as string; + versionsIe = argv.versions.ie as string; + versionsStandalone = argv.versions.standalone as string; + } + if (argv.chrome as boolean) { + options.browserDrivers.push( + {name: 'chromedriver', version: versionsChrome}); + } + if (argv.gecko as boolean) { + options.browserDrivers.push({name: 'geckodriver', version: versionsGecko}); + } + if (argv.iedriver as boolean) { + options.browserDrivers.push({name: 'iedriver', version: versionsIe}); + } + if (argv.standalone as boolean) { + options.server = {}; + options.server.name = 'selenium'; + options.server.runAsNode = argv.standalone_node as boolean; + options.server.runAsDetach = argv.detach as boolean; + options.server.version = versionsStandalone; + options.server.chromeLogs = argv.chrome_logs as string; + options.server.edge = argv.edge as string; + } + return options; +} \ No newline at end of file diff --git a/lib/cmds/version.ts b/lib/cmds/version.ts deleted file mode 100644 index 13cfa6f7..00000000 --- a/lib/cmds/version.ts +++ /dev/null @@ -1,22 +0,0 @@ -import * as minimist from 'minimist'; -import {Logger, Options, Program} from '../cli'; -import {Config} from '../config'; - -import * as Opt from './'; -import {Opts} from './opts'; - -let logger = new Logger('version'); - -let prog = new Program().command('version', 'get the current version').action(getVersion); - -export let program = prog; - -// stand alone runner -let argv = minimist(process.argv.slice(2), prog.getMinimistOptions()); -if (argv._[0] === 'version-run') { - prog.run(JSON.parse(JSON.stringify(argv))); -} - -function getVersion(): void { - logger.info('webdriver-manager', Config.getVersion()); -} diff --git a/lib/config.ts b/lib/config.ts deleted file mode 100644 index ed2b557d..00000000 --- a/lib/config.ts +++ /dev/null @@ -1,124 +0,0 @@ -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; - -import {Logger} from './cli'; - - -let logger = new Logger('config'); - -export interface ConfigFile { - [key: string]: string; - selenium?: string; - chrome?: string; - gecko?: string; - ie?: string; - android?: string; - appium?: string; - maxChrome?: string; -} - -/** - * The configuration for webdriver-manager - * - * The config.json, package.json, and selenium directory are found in the - * same location at the root directory in webdriver-manager. - * - */ -export class Config { - static runCommand: string; - - static configFile: string = 'config.json'; - static packageFile: string = 'package.json'; - static nodeModuleName = 'webdriver-manager'; - - static cwd = process.cwd(); - static localInstall: string; - static parentPath = path.resolve(Config.cwd, '..'); - static dir = __dirname; - static folder = Config.cwd.replace(Config.parentPath, '').substring(1); - - static isProjectVersion = Config.folder === Config.nodeModuleName; - static isLocalVersion = false; - - static osArch_ = os.arch(); - static osType_ = os.type(); - static noProxy_ = process.env.NO_PROXY || process.env.no_proxy; - static httpsProxy_ = process.env.HTTPS_PROXY || process.env.https_proxy; - static httpProxy_ = process.env.HTTP_PROXY || process.env.http_proxy; - - static osArch(): string { - return Config.osArch_; - } - - static osType(): string { - return Config.osType_; - } - - static noProxy(): string { - return Config.noProxy_; - } - - static httpProxy(): string { - return Config.httpProxy_; - } - - static httpsProxy(): string { - return Config.httpsProxy_; - } - - static getConfigFile_(): string { - return path.resolve(Config.dir, '..', Config.configFile); - } - - static getPackageFile_(): string { - return path.resolve(Config.dir, '..', Config.packageFile) - } - - static getSeleniumDir(): string { - return path.resolve(Config.dir, '..', '..', 'selenium/'); - } - static getBaseDir(): string { - return path.resolve(Config.dir, '..', '..'); - } - - /** - * Get the binary versions from the configuration file. - * @returns A map of the versions defined in the configuration file. - */ - static binaryVersions(): ConfigFile { - let configFile = require(Config.getConfigFile_()); - let configVersions: ConfigFile = {}; - configVersions.selenium = configFile.webdriverVersions.selenium; - configVersions.chrome = configFile.webdriverVersions.chromedriver; - configVersions.gecko = configFile.webdriverVersions.geckodriver; - configVersions.ie = configFile.webdriverVersions.iedriver; - configVersions.android = configFile.webdriverVersions.androidsdk; - configVersions.appium = configFile.webdriverVersions.appium; - configVersions.maxChrome = configFile.webdriverVersions.maxChromedriver; - return configVersions; - } - - /** - * Get the CDN urls from the configuration file. - * @returns A map of the CDN versions defined in the configuration file. - */ - static cdnUrls(): ConfigFile { - let configFile = require(Config.getConfigFile_()); - let configCdnUrls: ConfigFile = {}; - configCdnUrls.selenium = configFile.cdnUrls.selenium; - configCdnUrls.chrome = configFile.cdnUrls.chromedriver; - configCdnUrls.gecko = configFile.cdnUrls.geckodriver; - configCdnUrls.ie = configFile.cdnUrls.iedriver; - configCdnUrls.android = configFile.cdnUrls.androidsdk; - return configCdnUrls; - } - - /** - * Get the package version. - */ - static getVersion(): string { - let packageFile = require(Config.getPackageFile_()); - return packageFile.version; - } -} diff --git a/lib/files/downloaded_binary.ts b/lib/files/downloaded_binary.ts deleted file mode 100644 index 49fbad2a..00000000 --- a/lib/files/downloaded_binary.ts +++ /dev/null @@ -1,33 +0,0 @@ -import {Binary, BinaryUrl} from '../binaries'; - -/** - * The downloaded binary is the binary with the list of versions downloaded. - */ -export class DownloadedBinary extends Binary { - versions: string[] = []; - binary: Binary; - - constructor(binary: Binary) { - super(); - this.binary = binary; - this.name = binary.name; - this.versionCustom = binary.versionCustom; - } - - id(): string { - return this.binary.id(); - } - - prefix(): string { - return null; - } - suffix(): string { - return null; - } - getUrl(): Promise { - return null; - } - getVersionList(): Promise { - return null; - } -} diff --git a/lib/files/downloader.ts b/lib/files/downloader.ts deleted file mode 100644 index 1d4ab4b5..00000000 --- a/lib/files/downloader.ts +++ /dev/null @@ -1,109 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import * as q from 'q'; -import * as request from 'request'; -import * as url from 'url'; - -import {Binary} from '../binaries'; -import {Logger} from '../cli'; -import {Config} from '../config'; -import {HttpUtils} from '../http_utils'; - -let logger = new Logger('downloader'); - -/** - * The file downloader. - */ -export class Downloader { - /** - * Http get the file. Check the content length of the file before writing the file. - * If the content length does not match, remove it and download the file. - * - * @param binary The binary of interest. - * @param fileName The file name. - * @param outputDir The directory where files are downloaded and stored. - * @param contentLength The content length of the existing file. - * @param opt_proxy The proxy for downloading files. - * @param opt_callback Callback method to be executed after the file is downloaded. - * @returns Promise Resolves true = downloaded. Resolves false = not downloaded. - * Rejected with an error. - */ - static getFile( - binary: Binary, fileUrl: string, fileName: string, outputDir: string, contentLength: number, - callback?: Function): Promise { - let filePath = path.resolve(outputDir, fileName); - let file: any; - - let options = HttpUtils.initOptions(fileUrl); - - let req: request.Request = null; - let resContentLength: number; - - return new Promise((resolve, reject) => { - req = request(options); - req.on('response', response => { - if (response.statusCode === 200) { - resContentLength = +response.headers['content-length']; - if (contentLength === resContentLength) { - // if the size is the same, do not download and stop here - response.destroy(); - resolve(false); - } else { - let curl = outputDir + '/' + fileName + ' ' + options.url; - if (HttpUtils.requestOpts.proxy) { - let pathUrl = url.parse(options.url.toString()).path; - let host = url.parse(options.url.toString()).host; - let newFileUrl = url.resolve(HttpUtils.requestOpts.proxy, pathUrl); - curl = outputDir + '/' + fileName + ' \'' + newFileUrl + - '\' -H \'host:' + host + '\''; - } - if (HttpUtils.requestOpts.ignoreSSL) { - curl = 'k ' + curl; - } - logger.info('curl -o' + curl); - - // only pipe if the headers are different length - file = fs.createWriteStream(filePath); - req.pipe(file); - file.on('close', () => { - fs.stat(filePath, (error, stats) => { - if (error) { - (error as any).msg = 'Error: Got error ' + error + ' from ' + fileUrl; - return reject(error); - } - if (stats.size != resContentLength) { - (error as any).msg = 'Error: corrupt download for ' + fileName + - '. Please re-run webdriver-manager update'; - fs.unlinkSync(filePath); - reject(error); - } - if (callback) { - callback(binary, outputDir, fileName); - } - resolve(true); - }); - }); - } - - } else { - let error = new Error(); - (error as any).msg = - 'Expected response code 200, received: ' + response.statusCode; - reject(error); - } - }); - req.on('error', error => { - if ((error as any).code === 'ETIMEDOUT') { - (error as any).msg = 'Connection timeout downloading: ' + fileUrl + - '. Default timeout is 4 minutes.'; - } else if ((error as any).connect) { - (error as any).msg = 'Could not connect to the server to download: ' + fileUrl; - } - reject(error); - }); - }) - .catch(error => { - logger.error((error as any).msg || (error as any).message); - }); - } -} diff --git a/lib/files/file_manager.ts b/lib/files/file_manager.ts deleted file mode 100644 index 2770765b..00000000 --- a/lib/files/file_manager.ts +++ /dev/null @@ -1,257 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import * as q from 'q'; - -import {AndroidSDK, Appium, Binary, BinaryMap, ChromeDriver, GeckoDriver, IEDriver, OS, Standalone} from '../binaries'; -import {Logger} from '../cli'; -import {Config} from '../config'; - -import {DownloadedBinary} from './downloaded_binary'; -import {Downloader} from './downloader'; - -let logger = new Logger('file_manager'); - -/** - * The File Manager class is where the webdriver manager will compile a list of - * binaries that could be downloaded and get a list of previously downloaded - * file versions. - */ -export class FileManager { - /** - * Create a directory if it does not exist. - * @param outputDir The directory to create. - */ - static makeOutputDirectory(outputDir: string) { - try { - fs.statSync(outputDir); - } catch (e) { - logger.info('creating folder ' + outputDir); - fs.mkdirSync(outputDir); - } - } - - /** - * For the operating system, check against the list of operating systems that the - * binary is available for. - * @param osType The operating system. - * @param binary The class type to have access to the static properties. - * @returns If the binary is available for the operating system. - */ - static checkOS_(osType: string, binary: typeof Binary): boolean { - for (let os in binary.os) { - if (OS[os] == osType) { - return true; - } - } - return false; - } - - /** - * For the operating system, create a list that includes the binaries - * for selenium standalone, chrome, and internet explorer. - * @param osType The operating system. - * @param alternateCDN URL of the alternative CDN to be used instead of the default ones. - * @returns A binary map that are available for the operating system. - */ - static compileBinaries_(osType: string, alternateCDN?: string): BinaryMap { - let binaries: BinaryMap = {}; - if (FileManager.checkOS_(osType, Standalone)) { - binaries[Standalone.id] = new Standalone(alternateCDN); - } - if (FileManager.checkOS_(osType, ChromeDriver)) { - binaries[ChromeDriver.id] = new ChromeDriver(alternateCDN); - } - if (FileManager.checkOS_(osType, GeckoDriver)) { - binaries[GeckoDriver.id] = new GeckoDriver(alternateCDN); - } - if (FileManager.checkOS_(osType, IEDriver)) { - binaries[IEDriver.id] = new IEDriver(alternateCDN); - } - if (FileManager.checkOS_(osType, AndroidSDK)) { - binaries[AndroidSDK.id] = new AndroidSDK(alternateCDN); - } - if (FileManager.checkOS_(osType, Appium)) { - binaries[Appium.id] = new Appium(alternateCDN); - } - return binaries; - } - - /** - * Look up the operating system and compile a list of binaries that are available - * for the system. - * @param alternateCDN URL of the alternative CDN to be used instead of the default ones. - * @returns A binary map that is available for the operating system. - */ - static setupBinaries(alternateCDN?: string): BinaryMap { - return FileManager.compileBinaries_(Config.osType(), alternateCDN); - } - - /** - * Get the list of existing files from the output directory - * @param outputDir The directory where binaries are saved - * @returns A list of existing files. - */ - static getExistingFiles(outputDir: string): string[] { - try { - return fs.readdirSync(outputDir); - } catch (e) { - return []; - } - } - - /** - * For the binary, operating system, and system architecture, look through - * the existing files and the downloaded binary - * @param binary The binary of interest - * @param osType The operating system. - * @param existingFiles A list of existing files. - * @returns The downloaded binary with all the versions found. - */ - static downloadedVersions_( - binary: T, osType: string, arch: string, existingFiles: string[]): DownloadedBinary { - let versions: string[] = []; - for (let existPos in existingFiles) { - let existFile: string = existingFiles[existPos]; - // use only files that have a prefix and suffix that we care about - if (existFile.indexOf(binary.prefix()) === 0) { - let editExistFile = existFile.replace(binary.prefix(), ''); - // if the suffix matches the executable suffix, add it - if (binary.suffix() === binary.executableSuffix()) { - versions.push(editExistFile.replace(binary.suffix(), '')); - } - // if the suffix does not match the executable, - // the binary is something like: .exe and .zip - // TODO(cnishina): fix implementation. Suffix method is dependent on the version number - // example: chromedriver < 2.23 has a different suffix than 2.23+ (mac32.zip vs mac64.zip). - else if ( - !existFile.endsWith('.zip') && !existFile.endsWith('.tar.gz') && - existFile.indexOf(binary.suffix()) === -1) { - editExistFile = editExistFile.replace(binary.executableSuffix(), ''); - editExistFile = editExistFile.indexOf('_') === 0 ? - editExistFile.substring(1, editExistFile.length) : - editExistFile; - versions.push(editExistFile); - } - } - } - if (versions.length === 0) { - return null; - } - let downloadedBinary = new DownloadedBinary(binary); - downloadedBinary.versions = versions; - return downloadedBinary; - } - - /** - * Finds all the downloaded binary versions stored in the output directory. - * @param outputDir The directory where files are downloaded and stored. - * @returns An dictionary map of all the downloaded binaries found in the output folder. - */ - static downloadedBinaries(outputDir: string): BinaryMap { - let ostype = Config.osType(); - let arch = Config.osArch(); - let binaries = FileManager.setupBinaries(); - let existingFiles = FileManager.getExistingFiles(outputDir); - let downloaded: BinaryMap = {}; - for (let bin in binaries) { - let binary = FileManager.downloadedVersions_(binaries[bin], ostype, arch, existingFiles); - if (binary != null) { - downloaded[binary.id()] = binary; - } - } - return downloaded; - } - - /** - * Try to download the binary version. - * @param binary The binary of interest. - * @param outputDir The directory where files are downloaded and stored. - * @returns Promise resolved to true for files downloaded, resolved to false for files not - * downloaded because they exist, rejected if there is an error. - */ - static downloadFile(binary: T, outputDir: string, callback?: Function): - Promise { - return new Promise((resolve, reject) => { - let outDir = Config.getSeleniumDir(); - let downloaded: BinaryMap = FileManager.downloadedBinaries(outputDir); - let contentLength = 0; - - // Pass options down to binary to make request to get the latest version to download. - binary.getUrl(binary.version()).then(fileUrl => { - binary.versionCustom = fileUrl.version; - let filePath = path.resolve(outputDir, binary.filename()); - let fileName = binary.filename(); - - // If we have downloaded the file before, check the content length - if (downloaded[binary.id()]) { - let downloadedBinary = downloaded[binary.id()]; - let versions = downloadedBinary.versions; - let version = binary.versionCustom; - - for (let index in versions) { - let v = versions[index]; - if (v === version) { - contentLength = fs.statSync(filePath).size; - - Downloader.getFile(binary, fileUrl.url, fileName, outputDir, contentLength, callback) - .then(downloaded => { - resolve(downloaded); - }); - } - } - } - // We have not downloaded it before, or the version does not exist. Use the default content - // length of zero and download the file. - Downloader.getFile(binary, fileUrl.url, fileName, outputDir, contentLength, callback) - .then(downloaded => { - resolve(downloaded); - }); - }); - }); - } - - /** - * Removes the existing files found in the output directory that match the - * binary prefix names. - * @param outputDir The directory where files are downloaded and stored. - */ - static removeExistingFiles(outputDir: string): void { - try { - fs.statSync(outputDir); - } catch (e) { - logger.warn('path does not exist ' + outputDir); - return; - } - let existingFiles = FileManager.getExistingFiles(outputDir); - if (existingFiles.length === 0) { - logger.warn('no files found in path ' + outputDir); - return; - } - - let binaries = FileManager.setupBinaries(); - existingFiles.forEach((file) => { - for (let binPos in binaries) { - let bin: Binary = binaries[binPos]; - if (file.indexOf(bin.prefix()) !== -1) { - bin.remove(path.resolve(outputDir, file)); - logger.info('removed ' + file); - } - } - }); - - let metaFiles = [ - 'chrome-response.xml', 'gecko-response.json', 'iedriver-response.xml', - 'standalone-response.xml', 'update-config.json' - ]; - for (let metaFile of metaFiles) { - try { - let metaFilePath = path.resolve(outputDir, metaFile); - if (fs.statSync(metaFilePath)) { - fs.unlinkSync(metaFilePath); - logger.info('removed ' + metaFile); - } - } catch (e) { - } - } - } -} diff --git a/lib/files/index.ts b/lib/files/index.ts deleted file mode 100644 index 74536c2e..00000000 --- a/lib/files/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './downloaded_binary'; -export * from './downloader'; -export * from './file_manager'; diff --git a/lib/http_utils.ts b/lib/http_utils.ts deleted file mode 100644 index 30bb3383..00000000 --- a/lib/http_utils.ts +++ /dev/null @@ -1,129 +0,0 @@ -import * as request from 'request'; -import {OptionsWithUrl} from 'request'; -import * as url from 'url'; - -import {Logger} from './cli/logger'; -import {Config} from './config'; - -let logger = new Logger('http_utils'); - -export declare interface RequestOptionsValue { - proxy?: string; - ignoreSSL?: boolean; -} - -export class HttpUtils { - static requestOpts: RequestOptionsValue = {}; - static assignOptions(options: RequestOptionsValue): void { - Object.assign(HttpUtils.requestOpts, options); - } - - static initOptions(url: string, timeout?: number): OptionsWithUrl { - let options: OptionsWithUrl = { - url: url, - // default Linux can be anywhere from 20-120 seconds - // increasing this arbitrarily to 4 minutes - timeout: 240000 - }; - HttpUtils.optionsSSL(options, HttpUtils.requestOpts.ignoreSSL); - HttpUtils.optionsProxy(options, url, HttpUtils.requestOpts.proxy); - return options; - } - - static optionsSSL(options: OptionsWithUrl, opt_ignoreSSL: boolean): OptionsWithUrl { - if (opt_ignoreSSL) { - logger.info('ignoring SSL certificate'); - options.strictSSL = !opt_ignoreSSL; - (options as any).rejectUnauthorized = !opt_ignoreSSL; - } - - return options; - } - - static optionsProxy(options: OptionsWithUrl, requestUrl: string, opt_proxy: string): - OptionsWithUrl { - if (opt_proxy) { - options.proxy = HttpUtils.resolveProxy(requestUrl, opt_proxy); - if (url.parse(requestUrl).protocol === 'https:') { - options.url = requestUrl.replace('https:', 'http:'); - } - } - return options; - } - - static optionsHeader(options: OptionsWithUrl, key: string, value: string): OptionsWithUrl { - if (options.headers == null) { - options.headers = {}; - } - options.headers[key] = value; - return options; - } - - /** - * Resolves proxy based on values set - * @param fileUrl The url to download the file. - * @param opt_proxy The proxy to connect to to download files. - * @return Either undefined or the proxy. - */ - static resolveProxy(fileUrl: string, opt_proxy?: string): string { - let protocol = url.parse(fileUrl).protocol; - let hostname = url.parse(fileUrl).hostname; - - if (opt_proxy) { - return opt_proxy; - } else { - // If the NO_PROXY environment variable exists and matches the host name, - // to ignore the resolve proxy. - // the checks to see if it exists and equal to empty string is to help with testing - let noProxy: string = Config.noProxy(); - if (noProxy) { - // array of hostnames/domain names listed in the NO_PROXY environment variable - let noProxyTokens = noProxy.split(','); - // check if the fileUrl hostname part does not end with one of the - // NO_PROXY environment variable's hostnames/domain names - for (let noProxyToken of noProxyTokens) { - if (hostname.indexOf(noProxyToken) !== -1) { - return undefined; - } - } - } - - // If the HTTPS_PROXY and HTTP_PROXY environment variable is set, use that as the proxy - if (protocol === 'https:') { - return Config.httpsProxy() || Config.httpProxy(); - } else if (protocol === 'http:') { - return Config.httpProxy(); - } - } - return undefined; - } -} - -/** - * Request the body from the url. - * @param requestUrl The request url. - * @returns A promise string of the response body. - */ -export function requestBody(requestUrl: string): Promise { - const options = HttpUtils.initOptions(requestUrl); - options.followRedirect = true; - return new Promise((resolve, reject) => { - const req = request(options); - req.on('response', response => { - if (response.statusCode === 200) { - let output = ''; - response.on('data', (data) => { - output += data; - }); - response.on('end', () => { - resolve(output); - }); - } else { - reject(new Error('response status code is not 200')); - } - }); - req.on('error', error => { - reject(error); - }); - }); -} diff --git a/lib/index.ts b/lib/index.ts new file mode 100644 index 00000000..e2e8a56a --- /dev/null +++ b/lib/index.ts @@ -0,0 +1,19 @@ +// Exports when using this module as a dependency. + +// Expose the loglevel api. +import * as loglevel from 'loglevel'; +export let setLogLevel = loglevel.getLogger('webdriver-manager').setLevel; + +// Export commands used in the cli. +export {clean} from './cmds/clean'; +// Options that are used by the exported commands. +export {Options} from './cmds/options'; +export {shutdown} from './cmds/shutdown'; +export {start} from './cmds/start'; +export {status} from './cmds/status'; +export {update} from './cmds/update'; +export {ChromeDriver} from './provider/chromedriver'; +export {GeckoDriver} from './provider/geckodriver'; +export {IEDriver} from './provider/iedriver'; +export {ProviderConfig, ProviderInterface} from './provider/provider'; +export {SeleniumServer} from './provider/selenium_server'; diff --git a/lib/provider/appium.spec-int.ts b/lib/provider/appium.spec-int.ts new file mode 100644 index 00000000..ea2b6d01 --- /dev/null +++ b/lib/provider/appium.spec-int.ts @@ -0,0 +1,80 @@ +import * as childProcess from 'child_process'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import * as rimraf from 'rimraf'; + +import {spawnProcess} from '../../spec/support/helpers/test_utils'; +import {Appium} from './appium'; + +xdescribe('appium', () => { + const tmpDir = path.resolve(os.tmpdir(), 'test'); + let origTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; + describe('getVersion', () => { + let proc: childProcess.ChildProcess; + + beforeAll(() => { + jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; + }); + + afterAll(() => { + jasmine.DEFAULT_TIMEOUT_INTERVAL = origTimeout; + }); + + describe('with a http server', () => { + beforeAll(async () => { + proc = spawnProcess('node', ['dist/spec/server/http_server.js']); + console.log('http-server: ' + proc.pid); + try { + fs.mkdirSync(tmpDir); + } catch (err) { + } + await new Promise((resolve, _) => { + setTimeout(resolve, 3000); + }); + }); + + afterAll(() => { + try { + rimraf.sync(tmpDir); + } catch (err) { + } + process.kill(proc.pid); + }); + + it('should get the version from the local server', async () => { + const appium = new Appium({ + outDir: tmpDir, + requestUrl: 'http://127.0.0.1:8812/spec/support/files/appium.json' + }); + expect(await appium.getVersion()).toBe('10.11.12'); + }); + }); + }); + + describe('setup', () => { + beforeAll(() => { + origTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; + try { + fs.mkdirSync(tmpDir); + } catch (err) { + } + }); + + afterAll(() => { + try { + rimraf.sync(tmpDir); + } catch (err) { + } + jasmine.DEFAULT_TIMEOUT_INTERVAL = origTimeout; + }); + + it('should create the package.json file', async () => { + const appium = new Appium({outDir: tmpDir}); + await appium.setup('10.11.12'); + const packageJson = path.resolve(tmpDir, 'appium', 'package.json'); + expect(fs.statSync(packageJson).size).not.toBe(0); + }); + }); +}); \ No newline at end of file diff --git a/lib/provider/appium.ts b/lib/provider/appium.ts new file mode 100644 index 00000000..792deb28 --- /dev/null +++ b/lib/provider/appium.ts @@ -0,0 +1,75 @@ +import * as childProcess from 'child_process'; +import * as fs from 'fs'; +import * as loglevel from 'loglevel'; +import * as path from 'path'; +import * as rimraf from 'rimraf'; +import * as semver from 'semver'; + +import {ProviderConfig, ProviderInterface} from './provider'; +import {requestBody} from './utils/http_utils'; + +const log = loglevel.getLogger('webdriver-manager'); + +export class Appium implements ProviderInterface { + ignoreSSL: boolean; + outDir: string; + outDirAppium: string; + proxy: string; + requestUrl = 'http://registry.npmjs.org/appium'; + + constructor(providerConfig?: ProviderConfig) { + if (providerConfig) { + this.ignoreSSL = providerConfig.ignoreSSL; + if (providerConfig.outDir) { + this.outDir = providerConfig.outDir; + } + if (providerConfig.proxy) { + this.proxy = providerConfig.proxy; + } + if (providerConfig.requestUrl) { + this.requestUrl = providerConfig.requestUrl; + } + } + } + + /** + * If no valid version is provided get version from appium + */ + async getVersion(): Promise { + const body = await requestBody( + this.requestUrl, {proxy: this.proxy, ignoreSSL: this.ignoreSSL}); + return JSON.parse(body)['dist-tags']['latest']; + } + /** + * Creates appium directory and package.json file. + * @param version Optional to provide the version number or latest. + */ + async setup(version?: string): Promise { + if (!semver.valid(version)) { + version = await this.getVersion(); + } + this.outDirAppium = path.resolve(this.outDir, 'appium'); + try { + rimraf.sync(this.outDirAppium); + } catch (err) { + } + fs.mkdirSync(this.outDirAppium); + const packageJson = { + scripts: {appium: 'appium'}, + dependencies: {appium: '^' + version} + }; + fs.writeFileSync( + path.resolve(this.outDirAppium, 'package.json'), + JSON.stringify(packageJson)); + } + + /** + * Creates an appium/package.json file and installs the appium dependency. + * @param version Optional to provide the version number or latest. + */ + async updateBinary(version?: string): Promise { + log.info('appium: installing appium'); + await this.setup(version); + childProcess.execSync('npm install', {cwd: this.outDirAppium}); + } +} \ No newline at end of file diff --git a/lib/provider/chromedriver.spec-int.ts b/lib/provider/chromedriver.spec-int.ts new file mode 100644 index 00000000..a57ea5dc --- /dev/null +++ b/lib/provider/chromedriver.spec-int.ts @@ -0,0 +1,132 @@ +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import * as rimraf from 'rimraf'; + +import {checkConnectivity} from '../../spec/support/helpers/test_utils'; +import {ChromeDriver, semanticVersionParser, versionParser} from './chromedriver'; +import {convertXmlToVersionList} from './utils/cloud_storage_xml'; +import {getVersion} from './utils/version_list'; + +describe('chromedriver', () => { + const tmpDir = path.resolve(os.tmpdir(), 'test'); + + describe('class ChromeDriver', () => { + const origTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; + beforeAll(() => { + jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; + }); + + afterAll(() => { + jasmine.DEFAULT_TIMEOUT_INTERVAL = origTimeout; + }); + + describe('updateBinary', () => { + beforeEach(() => { + try { + fs.mkdirSync(tmpDir); + } catch (err) { + } + }); + + afterEach(() => { + try { + rimraf.sync(tmpDir); + } catch (err) { + } + }); + + it('should download the latest for MacOS', async () => { + if (await checkConnectivity('update binary for mac test')) { + const chromedriver = + new ChromeDriver({outDir: tmpDir, osType: 'Darwin'}); + await chromedriver.updateBinary(); + + const configFile = path.resolve(tmpDir, 'chromedriver.config.json'); + const xmlFile = path.resolve(tmpDir, 'chromedriver.xml'); + expect(fs.statSync(configFile).size).toBeTruthy(); + expect(fs.statSync(xmlFile).size).toBeTruthy(); + + const versionList = convertXmlToVersionList( + xmlFile, '.zip', versionParser, semanticVersionParser); + const versionObj = getVersion(versionList, 'mac'); + const executableFile = + path.resolve(tmpDir, 'chromedriver_' + versionObj.version); + expect(fs.statSync(executableFile).size).toBeTruthy(); + } + }); + + it('should download the latest for Windows x64', async () => { + if (await checkConnectivity('update binary for win x64 test')) { + const chromedriver = new ChromeDriver( + {outDir: tmpDir, osType: 'Windows_NT', osArch: 'x64'}); + await chromedriver.updateBinary(); + + const configFile = path.resolve(tmpDir, 'chromedriver.config.json'); + const xmlFile = path.resolve(tmpDir, 'chromedriver.xml'); + expect(fs.statSync(configFile).size).toBeTruthy(); + expect(fs.statSync(xmlFile).size).toBeTruthy(); + + const versionList = convertXmlToVersionList( + xmlFile, '.zip', versionParser, semanticVersionParser); + const versionObj = getVersion(versionList, 'win32'); + const executableFile = path.resolve( + tmpDir, 'chromedriver_' + versionObj.version + '.exe'); + expect(fs.statSync(executableFile).size).toBeTruthy(); + } + }); + + it('should download the latest for Windows x32', async () => { + if (await checkConnectivity('update binary for win x32 test')) { + const chromedriver = new ChromeDriver( + {outDir: tmpDir, osType: 'Windows_NT', osArch: 'x32'}); + await chromedriver.updateBinary(); + + const configFile = path.resolve(tmpDir, 'chromedriver.config.json'); + const xmlFile = path.resolve(tmpDir, 'chromedriver.xml'); + expect(fs.statSync(configFile).size).toBeTruthy(); + expect(fs.statSync(xmlFile).size).toBeTruthy(); + + const versionList = convertXmlToVersionList( + xmlFile, '.zip', versionParser, semanticVersionParser); + const versionObj = getVersion(versionList, 'win32'); + const executableFile = path.resolve( + tmpDir, 'chromedriver_' + versionObj.version + '.exe'); + expect(fs.statSync(executableFile).size).toBeTruthy(); + } + }); + + it('should download the latest for Linux x64', async () => { + if (await checkConnectivity('update binary for linux x64 test')) { + const chromedriver = new ChromeDriver( + {outDir: tmpDir, osType: 'Linux', osArch: 'x64'}); + await chromedriver.updateBinary(); + + const configFile = path.resolve(tmpDir, 'chromedriver.config.json'); + const xmlFile = path.resolve(tmpDir, 'chromedriver.xml'); + expect(fs.statSync(configFile).size).toBeTruthy(); + expect(fs.statSync(xmlFile).size).toBeTruthy(); + + const versionList = convertXmlToVersionList( + xmlFile, '.zip', versionParser, semanticVersionParser); + const versionObj = getVersion(versionList, 'linux64'); + const executableFile = + path.resolve(tmpDir, 'chromedriver_' + versionObj.version); + expect(fs.statSync(executableFile).size).toBeTruthy(); + } + }); + + it('should not download for Linux x32', async () => { + if (await checkConnectivity('update binary for linux x32 test')) { + const chromedriver = new ChromeDriver( + {outDir: tmpDir, osType: 'Linux', osArch: 'x32'}); + chromedriver.updateBinary() + .then(() => { + expect(false).toBeTruthy(); + }) + .catch(() => {}); + } + }); + }); + }); +}); diff --git a/lib/provider/chromedriver.spec-proxy.ts b/lib/provider/chromedriver.spec-proxy.ts new file mode 100644 index 00000000..649718dd --- /dev/null +++ b/lib/provider/chromedriver.spec-proxy.ts @@ -0,0 +1,74 @@ +import * as childProcess from 'child_process'; +import * as fs from 'fs'; +import * as loglevel from 'loglevel'; +import * as os from 'os'; +import * as path from 'path'; +import * as rimraf from 'rimraf'; + +import {proxyBaseUrl} from '../../spec/server/env'; +import {spawnProcess} from '../../spec/support/helpers/test_utils'; +import {checkConnectivity} from '../../spec/support/helpers/test_utils'; +import {ChromeDriver, semanticVersionParser, versionParser} from './chromedriver'; +import {convertXmlToVersionList} from './utils/cloud_storage_xml'; +import {getVersion} from './utils/version_list'; + +const log = loglevel.getLogger('webdriver-manager-test'); +log.setLevel('debug'); + +describe('chromedriver', () => { + const tmpDir = path.resolve(os.tmpdir(), 'test'); + + describe('class ChromeDriver', () => { + const origTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; + let proxyProc: childProcess.ChildProcess; + + describe('updateBinary', () => { + beforeEach((done) => { + jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; + proxyProc = spawnProcess('node', ['dist/spec/server/proxy_server.js']); + log.debug('proxy-server: ' + proxyProc.pid); + try { + fs.mkdirSync(tmpDir); + } catch (err) { + } + setTimeout(done, 3000); + }); + + afterEach((done) => { + process.kill(proxyProc.pid); + jasmine.DEFAULT_TIMEOUT_INTERVAL = origTimeout; + try { + rimraf.sync(tmpDir); + } catch (err) { + } + setTimeout(done, 5000); + }); + + it('should download the binary using a proxy', async (done) => { + if (!await checkConnectivity('update binary for mac test')) { + done(); + } + const chromeDriver = new ChromeDriver({ + ignoreSSL: true, + osType: 'Darwin', + osArch: 'x64', + outDir: tmpDir, + proxy: proxyBaseUrl + }); + await chromeDriver.updateBinary(); + const configFile = path.resolve(tmpDir, 'chromedriver.config.json'); + const xmlFile = path.resolve(tmpDir, 'chromedriver.xml'); + expect(fs.statSync(configFile).size).toBeTruthy(); + expect(fs.statSync(xmlFile).size).toBeTruthy(); + + const versionList = convertXmlToVersionList( + xmlFile, '.zip', versionParser, semanticVersionParser); + const versionObj = getVersion(versionList, 'mac'); + const executableFile = + path.resolve(tmpDir, 'chromedriver_' + versionObj.version); + expect(fs.statSync(executableFile).size).toBeTruthy(); + done(); + }); + }); + }); +}); \ No newline at end of file diff --git a/lib/provider/chromedriver.spec-unit.ts b/lib/provider/chromedriver.spec-unit.ts new file mode 100644 index 00000000..47e6afe7 --- /dev/null +++ b/lib/provider/chromedriver.spec-unit.ts @@ -0,0 +1,72 @@ +import * as fs from 'fs'; +import {ChromeDriver, osHelper, semanticVersionParser, versionParser,} from './chromedriver'; + +describe('chromedriver', () => { + describe('osHelper', () => { + it('should work for mac', () => { + expect(osHelper('Darwin', 'x64')).toBe('mac'); + }); + it('should work for windows', () => { + expect(osHelper('Windows_NT', 'x32')).toBe('win32'); + expect(osHelper('Windows_NT', 'x64')).toBe('win32'); + }); + it('should work for linux', () => { + expect(osHelper('Linux', 'x32')).toBeNull(); + expect(osHelper('Linux', 'x64')).toBe('linux64'); + }); + }); + + describe('verisonParser', () => { + it('should generate a semantic version', () => { + let version = versionParser('10.0/chromedriver_linux64.zip'); + expect(version).toBe('10.0'); + + version = versionParser('10.100/chromedriver_linux64.zip'); + expect(version).toBe('10.100'); + }); + }); + + describe('semanticVerisonParser', () => { + it('should generate a semantic version', () => { + let version = semanticVersionParser('10.0/chromedriver_linux64.zip'); + expect(version).toBe('10.0.0'); + + version = semanticVersionParser('10.100/chromedriver_linux64.zip'); + expect(version).toBe('10.100.0'); + }); + }); + + describe('class ChromeDriver', () => { + describe('getStatus', () => { + it('should get the status from the config file for Windows', () => { + const configCache = `{ + "last": "/path/to/chromedriver_100.1.exe", + "all": [ + "/path/to/chromedriver_90.0.exe", + "/path/to/chromedriver_99.0-beta.exe", + "/path/to/chromedriver_100.1.exe" + ] + }`; + spyOn(fs, 'readFileSync').and.returnValue(configCache); + const chromedriver = new ChromeDriver({osType: 'Windows_NT'}); + expect(chromedriver.getStatus()) + .toBe('90.0, 99.0-beta, 100.1 (latest)'); + }); + + it('should get the status from the config file for not Windows', () => { + const configCache = `{ + "last": "/path/to/chromedriver_100.1", + "all": [ + "/path/to/chromedriver_90.0", + "/path/to/chromedriver_99.0-beta", + "/path/to/chromedriver_100.1" + ] + }`; + spyOn(fs, 'readFileSync').and.returnValue(configCache); + const chromedriver = new ChromeDriver({osType: 'Darwin'}); + expect(chromedriver.getStatus()) + .toBe('90.0, 99.0-beta, 100.1 (latest)'); + }); + }); + }); +}); \ No newline at end of file diff --git a/lib/provider/chromedriver.ts b/lib/provider/chromedriver.ts new file mode 100644 index 00000000..4f69d87b --- /dev/null +++ b/lib/provider/chromedriver.ts @@ -0,0 +1,286 @@ +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; + +import {OUT_DIR, ProviderConfig, ProviderInterface} from './provider'; +import {convertXmlToVersionList, updateXml} from './utils/cloud_storage_xml'; +import {changeFilePermissions, generateConfigFile, getBinaryPathFromConfig, removeFiles, renameFileWithVersion, unzipFile, zipFileList,} from './utils/file_utils'; +import {requestBinary} from './utils/http_utils'; +import {getVersion} from './utils/version_list'; + +export class ChromeDriver implements ProviderInterface { + cacheFileName = 'chromedriver.xml'; + configFileName = 'chromedriver.config.json'; + ignoreSSL = false; + osType = os.type(); + osArch = os.arch(); + outDir = OUT_DIR; + proxy: string = null; + requestUrl = 'https://chromedriver.storage.googleapis.com/'; + seleniumFlag = '-Dwebdriver.chrome.driver'; + + constructor(providerConfig?: ProviderConfig) { + if (providerConfig) { + if (providerConfig.cacheFileName) { + this.cacheFileName = providerConfig.cacheFileName; + } + if (providerConfig.configFileName) { + this.configFileName = providerConfig.configFileName; + } + this.ignoreSSL = providerConfig.ignoreSSL; + if (providerConfig.osArch) { + this.osArch = providerConfig.osArch; + } + if (providerConfig.osType) { + this.osType = providerConfig.osType; + } + if (providerConfig.outDir) { + this.outDir = providerConfig.outDir; + } + if (providerConfig.proxy) { + this.proxy = providerConfig.proxy; + } + if (providerConfig.requestUrl) { + this.requestUrl = providerConfig.requestUrl; + } + } + } + + /** + * Should update the cache and download, find the version to download, + * then download that binary. + * @param version Optional to provide the version number or latest. + */ + async updateBinary(version?: string): Promise { + await updateXml(this.requestUrl, { + fileName: path.resolve(this.outDir, this.cacheFileName), + ignoreSSL: this.ignoreSSL, + proxy: this.proxy + }); + + const versionList = convertXmlToVersionList( + path.resolve(this.outDir, this.cacheFileName), '.zip', versionParser, + semanticVersionParser); + const versionObj = getVersion( + versionList, osHelper(this.osType, this.osArch), + formatVersion(version)); + + const chromeDriverUrl = this.requestUrl + versionObj.url; + const chromeDriverZip = path.resolve(this.outDir, versionObj.name); + + // We should check the zip file size if it exists. The size will + // be used to either make the request, or quit the request if the file + // size matches. + let fileSize = 0; + try { + fileSize = fs.statSync(chromeDriverZip).size; + } catch (err) { + } + await requestBinary(chromeDriverUrl, { + fileName: chromeDriverZip, + fileSize, + ignoreSSL: this.ignoreSSL, + proxy: this.proxy + }); + + // Unzip and rename all the files (a grand total of 1) and set the + // permissions. + const fileList = zipFileList(chromeDriverZip); + const fileItem = path.resolve(this.outDir, fileList[0]); + unzipFile(chromeDriverZip, this.outDir); + const renamedFileName = + renameFileWithVersion(fileItem, '_' + versionObj.version); + changeFilePermissions(renamedFileName, '0755', this.osType); + + generateConfigFile( + this.outDir, path.resolve(this.outDir, this.configFileName), + matchBinaries(this.osType), renamedFileName); + return Promise.resolve(); + } + + /** + * Gets the binary file path. + * @param version Optional to provide the version number or latest. + */ + getBinaryPath(version?: string): string|null { + try { + const configFilePath = path.resolve(this.outDir, this.configFileName); + return getBinaryPathFromConfig(configFilePath, version); + } catch (_) { + return null; + } + } + + /** + * Gets a comma delimited list of versions downloaded. Also has the "latest" + * downloaded noted. + */ + getStatus(): string|null { + try { + const configFilePath = path.resolve(this.outDir, this.configFileName); + const configJson = JSON.parse(fs.readFileSync(configFilePath).toString()); + const versions: string[] = []; + for (const binaryPath of configJson['all']) { + let version = ''; + let regex = /.*chromedriver_(\d+.\d+.*)/g; + if (this.osType === 'Windows_NT') { + regex = /.*chromedriver_(\d+.\d+.*).exe/g; + } + try { + const exec = regex.exec(binaryPath); + if (exec && exec[1]) { + version = exec[1]; + } + } catch (_) { + } + + if (configJson['last'] === binaryPath) { + version += ' (latest)'; + } + versions.push(version); + } + return versions.join(', '); + } catch (_) { + return null; + } + } + + /** + * Get a line delimited list of files removed. + */ + cleanFiles(): string { + return removeFiles(this.outDir, [/chromedriver.*/g]); + } +} + +/** + * Helps translate the os type and arch to the download name associated + * with composing the download link. + * @param ostype The operating stystem type. + * @param osarch The chip architecture. + * @returns The download name associated with composing the download link. + */ +export function osHelper(ostype: string, osarch: string): string { + if (ostype === 'Darwin') { + return 'mac'; + } else if (ostype === 'Windows_NT') { + if (osarch === 'x64') { + return 'win32'; + } else if (osarch === 'x32') { + return 'win32'; + } + } else if (ostype === 'Linux') { + if (osarch === 'x64') { + return 'linux64'; + } else if (osarch === 'x32') { + return null; + } + } + return null; +} + +/** + * Captures the version name which includes the semantic version and extra + * metadata. So an example for 12.34/chromedriver_linux64.zip, + * the version is 12.34. + * + * The new version is 70.0.3538.16/chromedriver_linux64.zip. This will return + * 70.0.3538.16. + * @param xmlKey The xml key including the partial url. + */ +export function versionParser(xmlKey: string) { + const newRegex = /([0-9]*\.[0-9]*\.[0-9]*\.[0-9]*)\/chromedriver_.*\.zip/g; + try { + const exec = newRegex.exec(xmlKey); + if (exec) { + return exec[1]; + } + } catch (_) { + } + const oldRegex = /([0-9]*\.[0-9]*)\/chromedriver_.*\.zip/g; + try { + const exec = oldRegex.exec(xmlKey); + if (exec) { + return exec[1]; + } + } catch (_) { + } + return null; +} + +/** + * Captures the version name which includes the semantic version and extra + * metadata. So an example for 12.34/chromedriver_linux64.zip, + * the version is 12.34.0. + * + * The new version is 70.0.3538.16/chromedriver_linux64.zip. This will return + * 70.0.3538. + * @param xmlKey The xml key including the partial url. + */ +export function semanticVersionParser(xmlKey: string): string|null { + const newRegex = /([0-9]*\.[0-9]*\.[0-9]*).[0-9]*\/chromedriver_.*\.zip/g; + try { + const exec = newRegex.exec(xmlKey); + if (exec) { + return exec[1]; + } + } catch (_) { + } + const oldRegex = /([0-9]*\.[0-9]*)\/chromedriver_.*\.zip/g; + try { + const exec = oldRegex.exec(xmlKey); + if (exec) { + return exec[1] + '.0'; + } + } catch (_) { + } + return null; +} + +/** + * Matches the installed binaries depending on the operating system. + * @param ostype The operating stystem type. + */ +export function matchBinaries(ostype: string): RegExp|null { + if (ostype === 'Darwin' || ostype === 'Linux') { + return /chromedriver_\d+.\d+.*/g; + } else if (ostype === 'Windows_NT') { + return /chromedriver_\d+.\d+.*.exe/g; + } + return null; +} + +/** + * Specifically to chromedriver, when downloading a version, the version + * you give webdriver-manager is not in the same as the output from the + * semanticVersionParser. So getting the version to the versionList will not + * be just a dictionary look up versionList[version]. The version will have + * to be formatted. + * + * Example: + * 2.44 will return with the formatted semantic version 2.44.0 + * 70.0.1.1 will return with the formatted semantic version 70.0.1 + * + * @param version The actual version. + */ +export function formatVersion(version: string): string|null { + const newRegex = /([0-9]*\.[0-9]*\.[0-9]*).[0-9]*/g; + try { + const exec = newRegex.exec(version); + if (exec) { + return exec[1]; + } + } catch (_) { + // no-op: if exec[1] is not reachable, move to the next regex. + } + const oldRegex = /([0-9]*\.[0-9]*)/g; + try { + const exec = oldRegex.exec(version); + if (exec) { + return exec[1] + '.0'; + } + } catch (_) { + // no-op: if exec[1] is not reachable, move on to return null. + } + return null; +} \ No newline at end of file diff --git a/lib/provider/geckodriver.spec-int.ts b/lib/provider/geckodriver.spec-int.ts new file mode 100644 index 00000000..c58aac12 --- /dev/null +++ b/lib/provider/geckodriver.spec-int.ts @@ -0,0 +1,135 @@ +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import * as rimraf from 'rimraf'; + +import {checkConnectivity} from '../../spec/support/helpers/test_utils'; +import {GeckoDriver} from './geckodriver'; +import {convertJsonToVersionList} from './utils/github_json'; +import {getVersion} from './utils/version_list'; + +describe('geckodriver', () => { + const tmpDir = path.resolve(os.tmpdir(), 'test'); + const origTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; + + describe('class GeckoDriver', () => { + describe('updateBinary', () => { + beforeAll(() => { + jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; + }); + + afterAll(() => { + jasmine.DEFAULT_TIMEOUT_INTERVAL = origTimeout; + }); + + beforeEach(() => { + try { + fs.mkdirSync(tmpDir); + } catch (err) { + } + }); + + afterEach(() => { + try { + rimraf.sync(tmpDir); + } catch (err) { + } + }); + + it('should download the latest for MacOS', async () => { + if (await checkConnectivity('update binary for mac test')) { + const geckodriver = + new GeckoDriver({outDir: tmpDir, osType: 'Darwin'}); + await geckodriver.updateBinary(); + + const configFile = path.resolve(tmpDir, 'geckodriver.config.json'); + const jsonFile = path.resolve(tmpDir, 'geckodriver.json'); + expect(fs.statSync(configFile).size).toBeTruthy(); + expect(fs.statSync(jsonFile).size).toBeTruthy(); + + const versionList = convertJsonToVersionList(jsonFile); + const versionObj = getVersion(versionList, 'macos'); + const executableFile = + path.resolve(tmpDir, 'geckodriver_' + versionObj.version); + expect(fs.statSync(executableFile).size).toBeTruthy(); + } + }); + + it('should download the latest for Windows x64', async () => { + if (await checkConnectivity('update binary for win64 test')) { + const geckodriver = new GeckoDriver( + {outDir: tmpDir, osType: 'Windows_NT', osArch: 'x64'}); + await geckodriver.updateBinary(); + + const configFile = path.resolve(tmpDir, 'geckodriver.config.json'); + const jsonFile = path.resolve(tmpDir, 'geckodriver.json'); + expect(fs.statSync(configFile).size).toBeTruthy(); + expect(fs.statSync(jsonFile).size).toBeTruthy(); + + const versionList = convertJsonToVersionList(jsonFile); + const versionObj = getVersion(versionList, 'win64'); + const executableFile = path.resolve( + tmpDir, 'geckodriver_' + versionObj.version + '.exe'); + expect(fs.statSync(executableFile).size).toBeTruthy(); + } + }); + + it('should download the latest for Windows x32', async () => { + if (await checkConnectivity('update binary for win32 test')) { + const geckodriver = new GeckoDriver( + {outDir: tmpDir, osType: 'Windows_NT', osArch: 'x32'}); + await geckodriver.updateBinary(); + + const configFile = path.resolve(tmpDir, 'geckodriver.config.json'); + const jsonFile = path.resolve(tmpDir, 'geckodriver.json'); + expect(fs.statSync(configFile).size).toBeTruthy(); + expect(fs.statSync(jsonFile).size).toBeTruthy(); + + const versionList = convertJsonToVersionList(jsonFile); + const versionObj = getVersion(versionList, 'win64'); + const executableFile = path.resolve( + tmpDir, 'geckodriver_' + versionObj.version + '.exe'); + expect(fs.statSync(executableFile).size).toBeTruthy(); + } + }); + + it('should download the latest for Linux x64', async () => { + if (await checkConnectivity('update binary for linux64 test')) { + const geckodriver = + new GeckoDriver({outDir: tmpDir, osType: 'Linux', osArch: 'x64'}); + await geckodriver.updateBinary(); + + const configFile = path.resolve(tmpDir, 'geckodriver.config.json'); + const jsonFile = path.resolve(tmpDir, 'geckodriver.json'); + expect(fs.statSync(configFile).size).toBeTruthy(); + expect(fs.statSync(jsonFile).size).toBeTruthy(); + + const versionList = convertJsonToVersionList(jsonFile); + const versionObj = getVersion(versionList, 'linux64'); + const executableFile = + path.resolve(tmpDir, 'geckodriver_' + versionObj.version); + expect(fs.statSync(executableFile).size).toBeTruthy(); + } + }); + + it('should download the latest for Linux x32', async () => { + if (await checkConnectivity('update binary for linux32 test')) { + const geckodriver = + new GeckoDriver({outDir: tmpDir, osType: 'Linux', osArch: 'x32'}); + await geckodriver.updateBinary(); + + const configFile = path.resolve(tmpDir, 'geckodriver.config.json'); + const jsonFile = path.resolve(tmpDir, 'geckodriver.json'); + expect(fs.statSync(configFile).size).toBeTruthy(); + expect(fs.statSync(jsonFile).size).toBeTruthy(); + + const versionList = convertJsonToVersionList(jsonFile); + const versionObj = getVersion(versionList, 'linux32'); + const executableFile = + path.resolve(tmpDir, 'geckodriver_' + versionObj.version); + expect(fs.statSync(executableFile).size).toBeTruthy(); + } + }); + }); + }); +}); \ No newline at end of file diff --git a/lib/provider/geckodriver.spec-proxy.ts b/lib/provider/geckodriver.spec-proxy.ts new file mode 100644 index 00000000..2a190513 --- /dev/null +++ b/lib/provider/geckodriver.spec-proxy.ts @@ -0,0 +1,69 @@ +import * as childProcess from 'child_process'; +import * as fs from 'fs'; +import * as loglevel from 'loglevel'; +import * as os from 'os'; +import * as path from 'path'; +import * as rimraf from 'rimraf'; + +import {proxyBaseUrl} from '../../spec/server/env'; +import {spawnProcess} from '../../spec/support/helpers/test_utils'; +import {checkConnectivity} from '../../spec/support/helpers/test_utils'; +import {GeckoDriver} from './geckodriver'; +import {convertJsonToVersionList} from './utils/github_json'; +import {getVersion} from './utils/version_list'; + +const log = loglevel.getLogger('webdriver-manager-test'); +log.setLevel('debug'); + +describe('geckodriver', () => { + const tmpDir = path.resolve(os.tmpdir(), 'test'); + + describe('class GeckoDriver', () => { + const origTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; + let proxyProc: childProcess.ChildProcess; + + describe('updateBinary', () => { + beforeEach((done) => { + jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; + proxyProc = spawnProcess('node', ['dist/spec/server/proxy_server.js']); + log.debug('proxy-server: ' + proxyProc.pid); + try { + fs.mkdirSync(tmpDir); + } catch (err) { + } + setTimeout(done, 3000); + }); + + afterEach((done) => { + process.kill(proxyProc.pid); + jasmine.DEFAULT_TIMEOUT_INTERVAL = origTimeout; + try { + rimraf.sync(tmpDir); + } catch (err) { + } + setTimeout(done, 5000); + }); + + it('should download the binary using a proxy', async (done) => { + if (!await checkConnectivity('update binary for mac test')) { + done(); + } + const geckoDriver = new GeckoDriver( + {outDir: tmpDir, osType: 'Darwin', proxy: proxyBaseUrl}); + await geckoDriver.updateBinary(); + + const configFile = path.resolve(tmpDir, 'geckodriver.config.json'); + const jsonFile = path.resolve(tmpDir, 'geckodriver.json'); + expect(fs.statSync(configFile).size).toBeTruthy(); + expect(fs.statSync(jsonFile).size).toBeTruthy(); + + const versionList = convertJsonToVersionList(jsonFile); + const versionObj = getVersion(versionList, 'macos'); + const executableFile = + path.resolve(tmpDir, 'geckodriver_' + versionObj.version); + expect(fs.statSync(executableFile).size).toBeTruthy(); + done(); + }); + }); + }); +}); \ No newline at end of file diff --git a/lib/provider/geckodriver.spec-unit.ts b/lib/provider/geckodriver.spec-unit.ts new file mode 100644 index 00000000..493853df --- /dev/null +++ b/lib/provider/geckodriver.spec-unit.ts @@ -0,0 +1,57 @@ +import * as fs from 'fs'; +import {GeckoDriver, osHelper} from './geckodriver'; + +describe('geckodriver', () => { + describe('osHelper', () => { + it('should work for mac', () => { + expect(osHelper('Darwin', 'x64')).toBe('macos'); + }); + it('should work for windows', () => { + expect(osHelper('Windows_NT', 'x32')).toBe('win32'); + expect(osHelper('Windows_NT', 'x64')).toBe('win64'); + }); + it('should work for linux', () => { + expect(osHelper('Linux', 'x32')).toBe('linux32'); + expect(osHelper('Linux', 'x64')).toBe('linux64'); + }); + it('should return null when the type / arch is not known', () => { + expect(osHelper('FooBarOS', '')).toBeNull(); + expect(osHelper('Windows_NT', 'arm')).toBeNull(); + expect(osHelper('Linux', 'arm64')).toBeNull(); + }); + }); + + describe('class GeckoDriver', () => { + describe('getStatus', () => { + it('should get the status from the config file for Windows', () => { + const configCache = `{ + "last": "/path/to/geckodriver_100.1.0.exe", + "all": [ + "/path/to/geckodriver_90.0.0.exe", + "/path/to/geckodriver_99.0.0-beta.exe", + "/path/to/geckodriver_100.1.0.exe" + ] + }`; + spyOn(fs, 'readFileSync').and.returnValue(configCache); + const geckodriver = new GeckoDriver({osType: 'Windows_NT'}); + expect(geckodriver.getStatus()) + .toBe('90.0.0, 99.0.0-beta, 100.1.0 (latest)'); + }); + + it('should get the status from the config file for not Windows', () => { + const configCache = `{ + "last": "/path/to/geckodriver_100.1.0", + "all": [ + "/path/to/geckodriver_90.0.0", + "/path/to/geckodriver_99.0.0-beta", + "/path/to/geckodriver_100.1.0" + ] + }`; + spyOn(fs, 'readFileSync').and.returnValue(configCache); + const geckodriver = new GeckoDriver({osType: 'Darwin'}); + expect(geckodriver.getStatus()) + .toBe('90.0.0, 99.0.0-beta, 100.1.0 (latest)'); + }); + }); + }); +}); diff --git a/lib/provider/geckodriver.ts b/lib/provider/geckodriver.ts new file mode 100644 index 00000000..d4bbf8c3 --- /dev/null +++ b/lib/provider/geckodriver.ts @@ -0,0 +1,212 @@ +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; + +import {OUT_DIR, ProviderConfig, ProviderInterface} from './provider'; +import {changeFilePermissions, generateConfigFile, getBinaryPathFromConfig, removeFiles, renameFileWithVersion, tarFileList, uncompressTarball, unzipFile, zipFileList,} from './utils/file_utils'; +import {convertJsonToVersionList, updateJson} from './utils/github_json'; +import {requestBinary} from './utils/http_utils'; +import {getVersion} from './utils/version_list'; + +export interface GeckoDriverProviderConfig extends ProviderConfig { + oauthToken?: string; +} + +export class GeckoDriver implements ProviderInterface { + cacheFileName = 'geckodriver.json'; + configFileName = 'geckodriver.config.json'; + ignoreSSL = false; + oauthToken: string; + osType = os.type(); + osArch = os.arch(); + outDir = OUT_DIR; + proxy: string = null; + requestUrl = 'https://api.github.com/repos/mozilla/geckodriver/releases'; + seleniumFlag = '-Dwebdriver.gecko.driver'; + + constructor(providerConfig?: GeckoDriverProviderConfig) { + if (providerConfig) { + if (providerConfig.cacheFileName) { + this.cacheFileName = providerConfig.cacheFileName; + } + if (providerConfig.configFileName) { + this.configFileName = providerConfig.configFileName; + } + this.ignoreSSL = providerConfig.ignoreSSL; + if (providerConfig.osArch) { + this.osArch = providerConfig.osArch; + } + if (providerConfig.osType) { + this.osType = providerConfig.osType; + } + if (providerConfig.outDir) { + this.outDir = providerConfig.outDir; + } + if (providerConfig.proxy) { + this.proxy = providerConfig.proxy; + } + if (providerConfig.requestUrl) { + this.requestUrl = providerConfig.requestUrl; + } + if (providerConfig.oauthToken) { + this.oauthToken = providerConfig.oauthToken; + } + } + } + + /** + * Should update the cache and download, find the version to download, + * then download that binary. + * @param version Optional to provide the version number or latest. + */ + async updateBinary(version?: string): Promise { + await updateJson( + this.requestUrl, { + fileName: path.resolve(this.outDir, this.cacheFileName), + ignoreSSL: this.ignoreSSL, + proxy: this.proxy + }, + this.oauthToken); + + const versionList = + convertJsonToVersionList(path.resolve(this.outDir, this.cacheFileName)); + const versionObj = + getVersion(versionList, osHelper(this.osType, this.osArch), version); + + const geckoDriverUrl = versionObj.url; + const geckoDriverCompressed = path.resolve(this.outDir, versionObj.name); + + // We should check the zip file size if it exists. The size will + // be used to either make the request, or quit the request if the file + // size matches. + let fileSize = 0; + try { + fileSize = fs.statSync(geckoDriverCompressed).size; + } catch (err) { + } + await requestBinary(geckoDriverUrl, { + fileName: geckoDriverCompressed, + fileSize, + ignoreSSL: this.ignoreSSL, + proxy: this.proxy + }); + + // Uncompress tarball (for linux and mac) or unzip the file for Windows. + // Rename all the files (a grand total of 1) and set the permissions. + let fileList: string[]; + if (this.osType === 'Windows_NT') { + fileList = zipFileList(geckoDriverCompressed); + } else { + fileList = await tarFileList(geckoDriverCompressed); + } + const fileItem = path.resolve(this.outDir, fileList[0]); + + if (this.osType === 'Windows_NT') { + unzipFile(geckoDriverCompressed, this.outDir); + } else { + await uncompressTarball(geckoDriverCompressed, this.outDir); + } + + const renamedFileName = + renameFileWithVersion(fileItem, '_' + versionObj.version); + + changeFilePermissions(renamedFileName, '0755', this.osType); + generateConfigFile( + this.outDir, path.resolve(this.outDir, this.configFileName), + matchBinaries(this.osType), renamedFileName); + return Promise.resolve(); + } + + /** + * Gets the binary file path. + * @param version Optional to provide the version number or latest. + */ + getBinaryPath(version?: string): string|null { + try { + const configFilePath = path.resolve(this.outDir, this.configFileName); + return getBinaryPathFromConfig(configFilePath, version); + } catch (_) { + return null; + } + } + + /** + * Gets a comma delimited list of versions downloaded. Also has the "latest" + * downloaded noted. + */ + getStatus(): string|null { + try { + const configFilePath = path.resolve(this.outDir, this.configFileName); + const configJson = JSON.parse(fs.readFileSync(configFilePath).toString()); + const versions: string[] = []; + for (const binaryPath of configJson['all']) { + let version = ''; + let regex = /.*geckodriver_(\d+.\d+.\d+.*)/g; + if (this.osType === 'Windows_NT') { + regex = /.*geckodriver_(\d+.\d+.\d+.*).exe/g; + } + try { + const exec = regex.exec(binaryPath); + if (exec && exec[1]) { + version = exec[1]; + } + } catch (_) { + } + + if (configJson['last'] === binaryPath) { + version += ' (latest)'; + } + versions.push(version); + } + return versions.join(', '); + } catch (_) { + return null; + } + } + + /** + * Get a line delimited list of files removed. + */ + cleanFiles(): string { + return removeFiles(this.outDir, [/geckodriver.*/g]); + } +} + +/** + * Helps translate the os type and arch to the download name associated + * with composing the download link. + * @param ostype The operating stystem type. + * @param osarch The chip architecture. + * @returns The download name associated with composing the download link. + */ +export function osHelper(ostype: string, osarch: string): string { + if (ostype === 'Darwin') { + return 'macos'; + } else if (ostype === 'Windows_NT') { + if (osarch === 'x64') { + return 'win64'; + } else if (osarch === 'x32') { + return 'win32'; + } + } else if (ostype === 'Linux') { + if (osarch === 'x64') { + return 'linux64'; + } else if (osarch === 'x32') { + return 'linux32'; + } + } + return null; +} + +/** + * Matches the installed binaries depending on the operating system. + * @param ostype The operating stystem type. + */ +export function matchBinaries(ostype: string): RegExp|null { + if (ostype === 'Darwin' || ostype === 'Linux') { + return /geckodriver_\d+.\d+.\d+/g; + } else if (ostype === 'Windows_NT') { + return /geckodriver_\d+.\d+.\d+.exe/g; + } + return null; +} \ No newline at end of file diff --git a/lib/provider/iedriver.spec-int.ts b/lib/provider/iedriver.spec-int.ts new file mode 100644 index 00000000..a8867841 --- /dev/null +++ b/lib/provider/iedriver.spec-int.ts @@ -0,0 +1,59 @@ +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import * as rimraf from 'rimraf'; + +import {checkConnectivity} from '../../spec/support/helpers/test_utils'; + +import {IEDriver, semanticVersionParser, versionParser} from './iedriver'; +import {convertXmlToVersionList} from './utils/cloud_storage_xml'; +import {getVersion} from './utils/version_list'; + +describe('iedriver', () => { + describe('class IE Driver', () => { + const tmpDir = path.resolve(os.tmpdir(), 'test'); + const origTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; + + beforeAll(() => { + jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; + }); + + afterAll(() => { + jasmine.DEFAULT_TIMEOUT_INTERVAL = origTimeout; + }); + + beforeEach(() => { + try { + fs.mkdirSync(tmpDir); + } catch (err) { + } + }); + + afterEach(() => { + try { + rimraf.sync(tmpDir); + } catch (err) { + } + }); + + it('should download the file', async () => { + if (await checkConnectivity('update binary for windows test')) { + const ieDriver = + new IEDriver({outDir: tmpDir, osType: 'Windows_NT', osArch: 'x64'}); + await ieDriver.updateBinary(); + + const configFile = path.resolve(tmpDir, 'iedriver.config.json'); + const xmlFile = path.resolve(tmpDir, 'iedriver.xml'); + expect(fs.statSync(configFile).size).toBeTruthy(); + expect(fs.statSync(xmlFile).size).toBeTruthy(); + + const versionList = convertXmlToVersionList( + xmlFile, 'IEDriverServer_', versionParser, semanticVersionParser); + const versionObj = getVersion(versionList, ''); + const executableFile = path.resolve( + tmpDir, 'IEDriverServer_' + versionObj.version + '.exe'); + expect(fs.statSync(executableFile).size).toBeTruthy(); + } + }); + }); +}); \ No newline at end of file diff --git a/lib/provider/iedriver.spec-unit.ts b/lib/provider/iedriver.spec-unit.ts new file mode 100644 index 00000000..ab646b30 --- /dev/null +++ b/lib/provider/iedriver.spec-unit.ts @@ -0,0 +1,23 @@ +import * as fs from 'fs'; +import {IEDriver} from './iedriver'; + +describe('iedriver', () => { + describe('class IEDriver', () => { + describe('getStatus', () => { + it('should get the status from the config file for Windows', () => { + const configCache = `{ + "last": "/path/to/IEDriverServer_100.1.0.exe", + "all": [ + "/path/to/IEDriverServer_90.0.0.exe", + "/path/to/IEDriverServer_99.0.0-beta.exe", + "/path/to/IEDriverServer_100.1.0.exe" + ] + }`; + spyOn(fs, 'readFileSync').and.returnValue(configCache); + const iedriver = new IEDriver({osType: 'Windows_NT'}); + expect(iedriver.getStatus()) + .toBe('90.0.0, 99.0.0-beta, 100.1.0 (latest)'); + }); + }); + }); +}); \ No newline at end of file diff --git a/lib/provider/iedriver.ts b/lib/provider/iedriver.ts new file mode 100644 index 00000000..2af32b20 --- /dev/null +++ b/lib/provider/iedriver.ts @@ -0,0 +1,207 @@ +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; + +import {OUT_DIR, ProviderConfig, ProviderInterface,} from './provider'; +import {convertXmlToVersionList, updateXml,} from './utils/cloud_storage_xml'; +import {generateConfigFile, getBinaryPathFromConfig, removeFiles, renameFileWithVersion, unzipFile, zipFileList,} from './utils/file_utils'; +import {requestBinary} from './utils/http_utils'; +import {getVersion} from './utils/version_list'; + +export class IEDriver implements ProviderInterface { + cacheFileName = 'iedriver.xml'; + configFileName = 'iedriver.config.json'; + ignoreSSL = false; + osType = os.type(); + osArch = os.arch(); + outDir = OUT_DIR; + proxy: string = null; + requestUrl = 'https://selenium-release.storage.googleapis.com/'; + seleniumFlag = '-Dwebdriver.ie.driver'; + + constructor(providerConfig?: ProviderConfig) { + if (providerConfig) { + if (providerConfig.cacheFileName) { + this.cacheFileName = providerConfig.cacheFileName; + } + if (providerConfig.configFileName) { + this.configFileName = providerConfig.configFileName; + } + this.ignoreSSL = providerConfig.ignoreSSL; + if (providerConfig.osArch) { + this.osArch = providerConfig.osArch; + } + if (providerConfig.osType) { + this.osType = providerConfig.osType; + } + if (providerConfig.outDir) { + this.outDir = providerConfig.outDir; + } + if (providerConfig.proxy) { + this.proxy = providerConfig.proxy; + } + if (providerConfig.requestUrl) { + this.requestUrl = providerConfig.requestUrl; + } + } + } + + /** + * Should update the cache and download, find the version to download, + * then download that binary. + * @param version Optional to provide the version number or latest. + */ + async updateBinary(version?: string): Promise { + await updateXml(this.requestUrl, { + fileName: path.resolve(this.outDir, this.cacheFileName), + ignoreSSL: this.ignoreSSL, + proxy: this.proxy + }); + const versionList = convertXmlToVersionList( + path.resolve(this.outDir, this.cacheFileName), '.zip', versionParser, + semanticVersionParser); + const versionObj = + getVersion(versionList, osHelper(this.osType, this.osArch), version); + + const chromeDriverUrl = this.requestUrl + versionObj.url; + const chromeDriverZip = path.resolve(this.outDir, versionObj.name); + + // We should check the zip file size if it exists. The size will + // be used to either make the request, or quit the request if the file + // size matches. + let fileSize = 0; + try { + fileSize = fs.statSync(chromeDriverZip).size; + } catch (err) { + } + await requestBinary(chromeDriverUrl, { + fileName: chromeDriverZip, + fileSize, + ignoreSSL: this.ignoreSSL, + proxy: this.proxy + }); + + // Unzip and rename all the files (a grand total of 1) and set the + // permissions. + const fileList = zipFileList(chromeDriverZip); + const fileItem = path.resolve(this.outDir, fileList[0]); + + unzipFile(chromeDriverZip, this.outDir); + const renamedFileName = + renameFileWithVersion(fileItem, '_' + versionObj.version); + generateConfigFile( + this.outDir, path.resolve(this.outDir, this.configFileName), + matchBinaries(this.osType), renamedFileName); + return Promise.resolve(); + } + + /** + * Gets the binary file path. + * @param version Optional to provide the version number or latest. + */ + getBinaryPath(version?: string): string|null { + try { + const configFilePath = path.resolve(this.outDir, this.configFileName); + return getBinaryPathFromConfig(configFilePath, version); + } catch (_) { + return null; + } + } + + /** + * Gets a comma delimited list of versions downloaded. Also has the "latest" + * downloaded noted. + */ + getStatus(): string|null { + try { + const configFilePath = path.resolve(this.outDir, this.configFileName); + const configJson = JSON.parse(fs.readFileSync(configFilePath).toString()); + const versions: string[] = []; + for (const binaryPath of configJson['all']) { + let version = ''; + const regex = /.*IEDriverServer_(\d+.\d+.\d+.*).exe/g; + try { + const exec = regex.exec(binaryPath); + if (exec && exec[1]) { + version = exec[1]; + } + } catch (_) { + } + + if (configJson['last'] === binaryPath) { + version += ' (latest)'; + } + versions.push(version); + } + return versions.join(', '); + } catch (_) { + return null; + } + } + + /** + * Get a line delimited list of files removed. + */ + cleanFiles(): string { + return removeFiles(this.outDir, [/IEDriverServer.*/g, /iedriver.*/g]); + } +} + +/** + * Helps translate the os type and arch to the download name associated + * with composing the download link. + * @param ostype The operating stystem type. + * @param osarch The chip architecture. + * @returns The download name associated with composing the download link. + */ +export function osHelper(ostype: string, osarch: string): string { + if (ostype === 'Windows_NT') { + if (osarch === 'x64') { + return 'Win32'; + } else if (osarch === 'x32') { + return 'Win32'; + } + } + return null; +} + +/** + * Captures the version name which includes the semantic version and extra + * metadata. So an example for 12.34/IEDriverServer_win32_12.34.56.zip, + * the version is 12.34.56. + * @param xmlKey The xml key including the partial url. + */ +export function versionParser(xmlKey: string) { + const regex = /.*\/IEDriverServer_[a-zA-Z0-9]*_([0-9]*.[0-9]*.[0-9]*).zip/g; + try { + return regex.exec(xmlKey)[1]; + } catch (_) { + return null; + } +} + +/** + * Captures the semantic version name which includes the semantic version and + * extra metadata. So an example for 12.34/IEDriverServer_win32_12.34.56.zip, + * the version is 12.34.56. + * @param xmlKey The xml key including the partial url. + */ +export function semanticVersionParser(xmlKey: string) { + const regex = /.*\/IEDriverServer_[a-zA-Z0-9]*_([0-9]*.[0-9]*.[0-9]*).zip/g; + try { + return regex.exec(xmlKey)[1]; + } catch (_) { + return null; + } +} + +/** + * Matches the installed binaries depending on the operating system. + * @param ostype The operating stystem type. + */ +export function matchBinaries(ostype: string): RegExp|null { + if (ostype === 'Windows_NT') { + return /IEDriverServer_\d+.\d+.\d+.exe/g; + } + return null; +} diff --git a/lib/provider/provider.ts b/lib/provider/provider.ts new file mode 100644 index 00000000..33779484 --- /dev/null +++ b/lib/provider/provider.ts @@ -0,0 +1,46 @@ +import * as path from 'path'; + +// Change the output directory for all providers. +// This will download to the webdriver-manager/downloads directory. +export const OUT_DIR = path.resolve(__dirname, '..', '..', '..', 'downloads'); + +/** + * The provider updateBinary interface implemented by all providers. + */ +export interface ProviderInterface { + cleanFiles?: () => string; + getBinaryPath?: (version?: string) => string | null; + getStatus?: () => string | null; + updateBinary: (version?: string) => Promise; + seleniumFlag?: string; + osType?: string; +} + +/** + * The provider configuration is passed to the Provider and can override + * the default behavior of the provider. + */ +export interface ProviderConfig { + // The request url to get the list of binaries available to download. + requestUrl?: string; + // The location of the output directory where to store the cache file, + // the config file and the binaries. + outDir?: string; + // The cache file name is just the file name and not the full path. + // The file contains the body returned from the request url. + cacheFileName?: string; + // The config file name is just the file name and not the full path. + // The file contains a json object of the list of all downloaded + // binaries and the last downloaded binary. + configFileName?: string; + // The os type of this system. + osType?: string; + // The os architecture of this system. + osArch?: string; + // The proxy requests must go through (optional). + proxy?: string; + // Set the requests to ignore SSL (optional). + ignoreSSL?: boolean; + // Catch all for other things. + [key: string]: string|boolean|number; +} diff --git a/lib/provider/selenium_server.spec-int.ts b/lib/provider/selenium_server.spec-int.ts new file mode 100644 index 00000000..576ed5f6 --- /dev/null +++ b/lib/provider/selenium_server.spec-int.ts @@ -0,0 +1,59 @@ +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import * as rimraf from 'rimraf'; + +import {checkConnectivity} from '../../spec/support/helpers/test_utils'; +import {SeleniumServer, semanticVersionParser, versionParser} from './selenium_server'; +import {convertXmlToVersionList} from './utils/cloud_storage_xml'; +import {getVersion} from './utils/version_list'; + +describe('selenium_server', () => { + describe('class Selenium Server', () => { + const tmpDir = path.resolve(os.tmpdir(), 'test'); + const origTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; + + beforeAll(() => { + jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; + }); + + afterAll(() => { + jasmine.DEFAULT_TIMEOUT_INTERVAL = origTimeout; + }); + + beforeEach(() => { + try { + fs.mkdirSync(tmpDir); + } catch (err) { + } + }); + + afterEach(() => { + try { + rimraf.sync(tmpDir); + } catch (err) { + } + }); + + it('should download the file', async () => { + if (await checkConnectivity('update binary for mac test')) { + const seleniumServer = new SeleniumServer({outDir: tmpDir}); + await seleniumServer.updateBinary(); + + const configFile = path.resolve(tmpDir, 'selenium-server.config.json'); + const xmlFile = path.resolve(tmpDir, 'selenium-server.xml'); + expect(fs.statSync(configFile).size).toBeTruthy(); + expect(fs.statSync(xmlFile).size).toBeTruthy(); + + const versionList = convertXmlToVersionList( + xmlFile, 'selenium-server-standalone', versionParser, + semanticVersionParser); + const versionObj = getVersion(versionList, ''); + const executableFile = path.resolve( + tmpDir, + 'selenium-server-standalone-' + versionObj.version + '.jar'); + expect(fs.statSync(executableFile).size).toBeTruthy(); + } + }); + }); +}); \ No newline at end of file diff --git a/lib/provider/selenium_server.spec-unit.ts b/lib/provider/selenium_server.spec-unit.ts new file mode 100644 index 00000000..0701216d --- /dev/null +++ b/lib/provider/selenium_server.spec-unit.ts @@ -0,0 +1,93 @@ +import * as fs from 'fs'; +import {SeleniumServer, semanticVersionParser, versionParser,} from './selenium_server'; + +describe('selenium_server', () => { + describe('verisonParser', () => { + it('should generate a semantic version', () => { + let version = + versionParser('10.1/selenium-server-standalone-10.1.200.jar'); + expect(version).toBe('10.1.200'); + + version = + versionParser('10.1/selenium-server-standalone-10.1.200-beta.jar'); + expect(version).toBe('10.1.200-beta'); + }); + }); + + describe('semanticVerisonParser', () => { + it('should generate a semantic version', () => { + let version = + semanticVersionParser('10.1/selenium-server-standalone-10.1.200.jar'); + expect(version).toBe('10.1.200'); + + version = semanticVersionParser( + '10.1/selenium-server-standalone-10.1.200-beta.jar'); + expect(version).toBe('10.1.200'); + }); + }); + + describe('class SeleniumServer', () => { + describe('getCmdStartServer', () => { + const configBinaries = `{ + "last": "path/to/selenium-server-3.0.jar", + "all": ["path/to/selenium-server-1.0.jar", + "path/to/selenium-server-2.0.jar", + "path/to/selenium-server-3.0.jar" + ] + }`; + const javaArgs = '-role node ' + + '-servlet org.openqa.grid.web.servlet.LifecycleServlet ' + + '-registerCycle 0 -port 4444'; + const javaArgsPort = '-port 4444'; + it('should use a selenium server with no options', () => { + spyOn(fs, 'readFileSync').and.returnValue(configBinaries); + const seleniumServer = new SeleniumServer(); + expect(seleniumServer.getCmdStartServer(null).join(' ')) + .toContain('-jar path/to/selenium-server-3.0.jar ' + javaArgsPort); + expect(seleniumServer.getCmdStartServer({}).join(' ')) + .toContain('-jar path/to/selenium-server-3.0.jar ' + javaArgsPort); + }); + + it('should use a selenium server with options', () => { + spyOn(fs, 'readFileSync').and.returnValue(configBinaries); + const seleniumServer = new SeleniumServer(); + const cmd = seleniumServer.getCmdStartServer( + {'-Dwebdriver.chrome.driver': 'path/to/chromedriver'}); + expect(cmd.join(' ')) + .toContain( + '-Dwebdriver.chrome.driver=path/to/chromedriver ' + + '-jar path/to/selenium-server-3.0.jar ' + javaArgsPort); + }); + + it('should use a selenium server with node options', () => { + spyOn(fs, 'readFileSync').and.returnValue(configBinaries); + const seleniumServer = new SeleniumServer(); + seleniumServer.runAsDetach = true; + seleniumServer.runAsNode = true; + const cmd = seleniumServer.getCmdStartServer( + {'-Dwebdriver.chrome.driver': 'path/to/chromedriver'}); + expect(cmd.join(' ')) + .toContain( + '-Dwebdriver.chrome.driver=path/to/chromedriver ' + + '-jar path/to/selenium-server-3.0.jar ' + javaArgs); + }); + }); + + describe('getStatus', () => { + it('should get the status from the config file', () => { + const configCache = `{ + "last": "/path/to/selenium-server-standalone-100.1.0.jar", + "all": [ + "/path/to/selenium-server-standalone-90.0.0.jar", + "/path/to/selenium-server-standalone-99.0.0-beta.jar", + "/path/to/selenium-server-standalone-100.1.0.jar" + ] + }`; + spyOn(fs, 'readFileSync').and.returnValue(configCache); + const seleniumServer = new SeleniumServer(); + expect(seleniumServer.getStatus()) + .toBe('90.0.0, 99.0.0-beta, 100.1.0 (latest)'); + }); + }); + }); +}); \ No newline at end of file diff --git a/lib/provider/selenium_server.ts b/lib/provider/selenium_server.ts new file mode 100644 index 00000000..0fa2f783 --- /dev/null +++ b/lib/provider/selenium_server.ts @@ -0,0 +1,335 @@ +import * as childProcess from 'child_process'; +import * as fs from 'fs'; +import * as loglevel from 'loglevel'; +import * as os from 'os'; +import * as path from 'path'; +import * as request from 'request'; + +import {OUT_DIR, ProviderConfig, ProviderInterface,} from './provider'; +import {convertXmlToVersionList, updateXml} from './utils/cloud_storage_xml'; +import {generateConfigFile, getBinaryPathFromConfig, removeFiles,} from './utils/file_utils'; +import {curlCommand, initOptions, requestBinary} from './utils/http_utils'; +import {getVersion} from './utils/version_list'; + +const log = loglevel.getLogger('webdriver-manager'); + +export interface SeleniumServerProviderConfig extends ProviderConfig { + port?: number; + runAsNode?: boolean; + runAsDetach?: boolean; +} + +export class SeleniumServer implements ProviderInterface { + cacheFileName = 'selenium-server.xml'; + configFileName = 'selenium-server.config.json'; + ignoreSSL = false; + osType = os.type(); + osArch = os.arch(); + outDir = OUT_DIR; + port = 4444; + proxy: string = null; + requestUrl = 'https://selenium-release.storage.googleapis.com/'; + seleniumProcess: childProcess.ChildProcess; + runAsNode = false; + runAsDetach = false; + + constructor(providerConfig?: SeleniumServerProviderConfig) { + if (providerConfig) { + if (providerConfig.cacheFileName) { + this.cacheFileName = providerConfig.cacheFileName; + } + if (providerConfig.configFileName) { + this.configFileName = providerConfig.configFileName; + } + this.ignoreSSL = providerConfig.ignoreSSL; + if (providerConfig.osArch) { + this.osArch = providerConfig.osArch; + } + if (providerConfig.osType) { + this.osType = providerConfig.osType; + } + if (providerConfig.outDir) { + this.outDir = providerConfig.outDir; + } + if (providerConfig.port) { + this.port = providerConfig.port; + } + if (providerConfig.proxy) { + this.proxy = providerConfig.proxy; + } + if (providerConfig.requestUrl) { + this.requestUrl = providerConfig.requestUrl; + } + if (providerConfig.runAsNode) { + this.runAsNode = providerConfig.runAsNode; + } + if (providerConfig.runAsDetach) { + this.runAsDetach = providerConfig.runAsDetach; + this.runAsNode = true; + } + } + } + + /** + * Should update the cache and download, find the version to download, + * then download that binary. + * @param version Optional to provide the version number or latest. + */ + async updateBinary(version?: string): Promise { + await updateXml(this.requestUrl, { + fileName: path.resolve(this.outDir, this.cacheFileName), + ignoreSSL: this.ignoreSSL, + proxy: this.proxy + }); + const versionList = convertXmlToVersionList( + path.resolve(this.outDir, this.cacheFileName), + 'selenium-server-standalone', versionParser, semanticVersionParser); + const versionObj = getVersion(versionList, '', version); + + const seleniumServerUrl = this.requestUrl + versionObj.url; + const seleniumServerJar = path.resolve(this.outDir, versionObj.name); + + // We should check the jar file size if it exists. The size will + // be used to either make the request, or quit the request if the file + // size matches. + let fileSize = 0; + try { + fileSize = fs.statSync(seleniumServerJar).size; + } catch (err) { + } + await requestBinary(seleniumServerUrl, { + fileName: seleniumServerJar, + fileSize, + ignoreSSL: this.ignoreSSL, + proxy: this.proxy + }); + generateConfigFile( + this.outDir, path.resolve(this.outDir, this.configFileName), + matchBinaries(), seleniumServerJar); + return Promise.resolve(); + } + + /** + * Starts selenium standalone server and handles emitted exit events. + * @param opts The options to pass to the jar file. + * @param version The optional version of the selenium jar file. + * @returns A promise so the server can run while awaiting its completion. + */ + startServer(opts: {[key: string]: string}, version?: string): + Promise { + const java = this.getJava(); + return new Promise(async (resolve, _) => { + if (this.runAsDetach) { + this.runAsNode = true; + const cmd = this.getCmdStartServer(opts, version); + log.info(`${java} ${cmd.join(' ')}`); + this.seleniumProcess = + childProcess.spawn(java, cmd, {detached: true, stdio: 'ignore'}); + log.info(`selenium process id: ${this.seleniumProcess.pid}`); + await new Promise((resolve, _) => { + setTimeout(resolve, 2000); + }); + this.seleniumProcess.unref(); + await new Promise((resolve, _) => { + setTimeout(resolve, 500); + }); + resolve(0); + } else { + const cmd = this.getCmdStartServer(opts, version); + log.info(`${java} ${cmd.join(' ')}`); + this.seleniumProcess = + childProcess.spawn(java, cmd, {stdio: 'inherit'}); + log.info(`selenium process id: ${this.seleniumProcess.pid}`); + + this.seleniumProcess.on('exit', (code: number) => { + log.info(`Selenium Standalone has exited with code: ${code}`); + resolve(code); + }); + this.seleniumProcess.on('error', (err: Error) => { + log.error(`Selenium Standalone server encountered an error: ${err}`); + }); + } + }); + } + + /** + * Get the binary file path. + * @param version Optional to provide the version number or the latest. + */ + getBinaryPath(version?: string): string|null { + try { + const configFilePath = path.resolve(this.outDir, this.configFileName); + return getBinaryPathFromConfig(configFilePath, version); + } catch (_) { + return null; + } + } + + /** + * Get the selenium server start command (not including the java command) + * @param opts The options to pass to the jar file. + * @param version The optional version of the selenium jar file. + * @returns The spawn arguments array. + */ + getCmdStartServer(opts: {[key: string]: string}, version?: string): string[] { + const jarFile = this.getBinaryPath(version); + const options: string[] = []; + if (opts) { + for (const opt of Object.keys(opts)) { + options.push(`${opt}=${opts[opt]}`); + } + } + options.push('-jar'); + options.push(jarFile); + + if (this.runAsNode) { + options.push('-role'); + options.push('node'); + + options.push('-servlet'); + options.push('org.openqa.grid.web.servlet.LifecycleServlet'); + + options.push('-registerCycle'); + options.push('0'); + } + options.push('-port'); + options.push(this.port.toString()); + + return options; + } + + /** + * Gets the java command either by the JAVA_HOME environment variable or + * just the java command. + */ + getJava(): string { + let java = 'java'; + if (process.env.JAVA_HOME) { + java = path.resolve(process.env.JAVA_HOME, 'bin', 'java'); + if (this.osType === 'Windows_NT') { + java += '.exe'; + } + } + return java; + } + + /** + * If we are running the selenium server role = node, send + * the command to stop the server via http get request. Reference: + * https://github.com/SeleniumHQ/selenium/issues/2852#issuecomment-268324091 + * + * If we are not running as the selenium server role = node, kill the + * process with pid. + * + * @param host The protocol and ip address, default http://127.0.0.1 + * @param port The port number, default 4444 + * @returns A promise of the http get request completing. + */ + stopServer(host?: string, port?: number): Promise { + if (this.runAsNode) { + if (!host) { + host = 'http://127.0.0.1'; + } + if (!port) { + port = this.port; + } + const stopUrl = + host + ':' + port + '/extra/LifecycleServlet?action=shutdown'; + const options = initOptions(stopUrl, {}); + log.info(curlCommand(options)); + return new Promise((resolve, _) => { + const req = request(options); + req.on('response', response => { + response.on('end', () => { + resolve(); + }); + }); + }); + } else if (this.seleniumProcess) { + process.kill(this.seleniumProcess.pid); + return Promise.resolve(); + } else { + return Promise.reject( + 'Could not stop the server, server is not running.'); + } + } + + /** + * Gets a comma delimited list of versions downloaded. Also has the "latest" + * downloaded noted. + */ + getStatus(): string|null { + try { + const configFilePath = path.resolve(this.outDir, this.configFileName); + const configJson = JSON.parse(fs.readFileSync(configFilePath).toString()); + const versions: string[] = []; + for (const binaryPath of configJson['all']) { + let version = ''; + const regex = /.*selenium-server-standalone-(\d+.\d+.\d+.*).jar/g; + try { + const exec = regex.exec(binaryPath); + if (exec && exec[1]) { + version = exec[1]; + } + } catch (_) { + } + + if (configJson['last'] === binaryPath) { + version += ' (latest)'; + } + versions.push(version); + } + return versions.join(', '); + } catch (_) { + return null; + } + } + + /** + * Get a line delimited list of files removed. + */ + cleanFiles(): string { + return removeFiles(this.outDir, [/selenium-server.*/g]); + } +} + +/** + * Captures the version name which includes the semantic version and extra + * metadata. So an example for 12.34/selenium-server-standalone-12.34.56.jar, + * the version is 12.34.56. For metadata, + * 12.34/selenium-server-standalone-12.34.56-beta.jar is 12.34.56-beta. + * @param xmlKey The xml key including the partial url. + */ +export function versionParser(xmlKey: string) { + // Capture the version name 12.34.56 or 12.34.56-beta + const regex = /.*selenium-server-standalone-(\d+.\d+.\d+.*).jar/g; + try { + return regex.exec(xmlKey)[1]; + } catch (_) { + return null; + } +} + +/** + * Captures the version name which includes the semantic version and extra + * metadata. So an example for 12.34/selenium-server-standalone-12.34.56.jar, + * the version is 12.34.56. For metadata, + * 12.34/selenium-server-standalone-12.34.56-beta.jar is still 12.34.56. + * @param xmlKey The xml key including the partial url. + */ +export function semanticVersionParser(xmlKey: string) { + // Only capture numbers 12.34.56 + const regex = /.*selenium-server-standalone-(\d+.\d+.\d+).*.jar/g; + try { + return regex.exec(xmlKey)[1]; + } catch (_) { + return null; + } +} + +/** + * Matches the installed binaries. + */ +export function matchBinaries(): RegExp|null { + return /selenium-server-standalone-\d+.\d+.\d+.*.jar/g; +} \ No newline at end of file diff --git a/lib/provider/utils/README.md b/lib/provider/utils/README.md new file mode 100644 index 00000000..ddb01942 --- /dev/null +++ b/lib/provider/utils/README.md @@ -0,0 +1,16 @@ +# Utils + +Developer notes for scope of each file: + +* **cloud_storage_xml** handles the Google Cloud Storage specific items like +downloading the xml and converting the xml into a version list. +* **file_utils** manages files including reading xml and json files, +uncompressing files, renaming files, and checking if we should renew the cache +of xml or json files. +* **github_json** handles the GitHub specific items like downloading the json +and converting that to a version list. Also adds GitHub specific headers to the +request including oauth token. +* **http_utils** handles requests like downloading a binary or getting the +contents of the body. +* **version_list** is a data object to help organize the versions from the +cache. The versions must be in semantic version format. \ No newline at end of file diff --git a/lib/provider/utils/cloud_storage_xml.spec-int.ts b/lib/provider/utils/cloud_storage_xml.spec-int.ts new file mode 100644 index 00000000..938bae83 --- /dev/null +++ b/lib/provider/utils/cloud_storage_xml.spec-int.ts @@ -0,0 +1,173 @@ + +import * as childProcess from 'child_process'; +import * as fs from 'fs'; +import * as loglevel from 'loglevel'; +import * as os from 'os'; +import * as path from 'path'; +import {httpBaseUrl} from '../../../spec/server/env'; +import {spawnProcess} from '../../../spec/support/helpers/test_utils'; +import {convertXmlToVersionList, updateXml} from './cloud_storage_xml'; + +const log = loglevel.getLogger('webdriver-manager-test'); +log.setLevel('debug'); + +function chromedriverVersionParser(key: string): string { + const regex = /([0-9]*.[0-9]*)\/chromedriver_.*.zip/g; + try { + return regex.exec(key)[1]; + } catch (_) { + return null; + } +} + +function chromedriverSemanticVersionParser(key: string): string { + const regex = /([0-9]*.[0-9]*)\/chromedriver_.*.zip/g; + try { + return regex.exec(key)[1] + '.0'; + } catch (_) { + return null; + } +} + +describe('cloud_storage_xml', () => { + const tmpDir = path.resolve(os.tmpdir(), 'test'); + let proc: childProcess.ChildProcess; + const origTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; + + beforeAll(() => { + jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; + }); + + afterAll(() => { + jasmine.DEFAULT_TIMEOUT_INTERVAL = origTimeout; + }); + + describe('with a http server', () => { + beforeAll(async () => { + proc = spawnProcess('node', ['dist/spec/server/http_server.js']); + log.debug('http-server: ' + proc.pid); + await new Promise((resolve, _) => { + setTimeout(resolve, 3000); + }); + }); + + afterAll(async () => { + process.kill(proc.pid); + await new Promise((resolve, _) => { + setTimeout(resolve, 5000); + }); + }); + + describe('updateXml', () => { + const fileName = path.resolve(tmpDir, 'foo.xml'); + const xmlUrl = httpBaseUrl + '/spec/support/files/foo.xml'; + + beforeAll(() => { + try { + fs.mkdirSync(tmpDir); + } catch (_) { + // If the directory already exists, we are in the desired state. + } + try { + fs.unlinkSync(fileName); + } catch (_) { + // If the file does not exist, we are in the desired state. + } + }); + + afterAll(() => { + try { + fs.unlinkSync(fileName); + fs.rmdirSync(tmpDir); + } catch (_) { + } + }); + + it('should request and write the file if it does not exist', async () => { + try { + fs.statSync(fileName); + expect('file should not exist.').toBeFalsy(); + } catch (_) { + try { + const xmlContent = await updateXml(xmlUrl, {fileName}); + expect(fs.statSync(fileName).size).toBeGreaterThan(0); + expect(xmlContent['ListBucketResult']['Contents'][0]['Key'][0]) + .toBe('2.0/foobar.zip'); + } catch (_) { + expect('thrown error from update xml.').toBeFalsy(); + } + } + }); + + it('should request and write the file if it is expired', async () => { + const mtime = Date.now() - (60 * 60 * 1000) - 5000; + + // Maintain the fs.statSync method before being spyed on. + // Spy on the fs.statSync method and return fake values. + const fsStatSync = fs.statSync; + spyOn(fs, 'statSync').and.returnValue({size: 1000, mtime}); + + try { + const xmlContent = await updateXml(xmlUrl, {fileName}); + expect(fsStatSync(fileName).size).toBeGreaterThan(0); + expect(fsStatSync(fileName).size).not.toBe(1000); + expect(xmlContent['ListBucketResult']['Contents'][0]['Key'][0]) + .toBe('2.0/foobar.zip'); + } catch (_) { + expect('debugging required').toBeFalsy(); + } + }); + + it('should read the file when it is not expired', async () => { + const initialStats = fs.statSync(fileName); + const mtime = Date.now(); + + // Maintain the fs.statSync method before being spyed on. + // Spy on the fs.statSync method and return fake values. + const fsStatSync = fs.statSync; + spyOn(fs, 'statSync').and.returnValue({size: 1000, mtime}); + + try { + const xmlContent = await updateXml(xmlUrl, {fileName}); + expect(fsStatSync(fileName).size).toBe(initialStats.size); + expect(fsStatSync(fileName).mtime.getMilliseconds()) + .toBe(initialStats.mtime.getMilliseconds()); + expect(xmlContent['ListBucketResult']['Contents'][0]['Key'][0]) + .toBe('2.0/foobar.zip'); + } catch (_) { + expect('debugging required').toBeFalsy(); + } + }); + }); + + describe('convertXmlToVersionList', () => { + const fileName = 'spec/support/files/chromedriver.xml'; + + it('should convert an xml file an object from the xml file', () => { + const versionList = convertXmlToVersionList( + fileName, '.zip', chromedriverVersionParser, + chromedriverSemanticVersionParser); + expect(Object.keys(versionList).length).toBe(3); + expect(versionList['2.0.0']).toBeTruthy(); + expect(versionList['2.10.0']).toBeTruthy(); + expect(versionList['2.20.0']).toBeTruthy(); + expect(Object.keys(versionList['2.0.0']).length).toBe(4); + expect(Object.keys(versionList['2.10.0']).length).toBe(4); + expect(Object.keys(versionList['2.20.0']).length).toBe(4); + expect(versionList['2.0.0']['chromedriver_linux32.zip']['size']) + .toBe(7262134); + expect(versionList['2.10.0']['chromedriver_linux32.zip']['size']) + .toBe(2439424); + expect(versionList['2.20.0']['chromedriver_linux32.zip']['size']) + .toBe(2612186); + }); + + it('should return a null value if the file does not exist', () => { + const versionList = convertXmlToVersionList( + 'spec/support/files/does_not_exist.xml', '.zip', + chromedriverVersionParser, chromedriverSemanticVersionParser); + expect(versionList).toBeNull(); + }); + }); + }); +}); \ No newline at end of file diff --git a/lib/provider/utils/cloud_storage_xml.spec-unit.ts b/lib/provider/utils/cloud_storage_xml.spec-unit.ts new file mode 100644 index 00000000..e943bc9d --- /dev/null +++ b/lib/provider/utils/cloud_storage_xml.spec-unit.ts @@ -0,0 +1,56 @@ +import * as fs from 'fs'; +import {convertXmlToVersionList} from './cloud_storage_xml'; + +const contents = ` + + + foobar_driver + + 2.0/foobar.zip + 10 + + + 2.1/foobar.zip + 11 + +`; + +export function versionParser(key: string): string { + const regex = /([0-9]*.[0-9]*)\/foobar.*.zip/g; + try { + return regex.exec(key)[1]; + } catch (err) { + return null; + } +} + +export function semanticVersionParser(key: string): string { + const regex = /([0-9]*.[0-9]*)\/foobar.*.zip/g; + try { + return regex.exec(key)[1] + '.0'; + } catch (err) { + return null; + } +} + +describe('cloud_storage_xml', () => { + describe('convertXmlToVersionList', () => { + it('should convert an xml file an object from the xml file', () => { + spyOn(fs, 'readFileSync').and.returnValue(contents); + const versionList = convertXmlToVersionList( + 'foobar', '.zip', versionParser, semanticVersionParser); + expect(Object.keys(versionList).length).toBe(2); + expect(versionList['2.0.0']['foobar.zip'].url).toBe('2.0/foobar.zip'); + expect(versionList['2.0.0']['foobar.zip'].size).toBe(10); + expect(versionList['2.1.0']['foobar.zip'].url).toBe('2.1/foobar.zip'); + expect(versionList['2.1.0']['foobar.zip'].size).toBe(11); + }); + + it('should return null when the method to read an xml file returns null', + () => { + const versionList = convertXmlToVersionList( + 'foo', '.zip', versionParser, semanticVersionParser); + expect(versionList).toBeNull(); + }); + }); +}); \ No newline at end of file diff --git a/lib/provider/utils/cloud_storage_xml.ts b/lib/provider/utils/cloud_storage_xml.ts new file mode 100644 index 00000000..6b980e46 --- /dev/null +++ b/lib/provider/utils/cloud_storage_xml.ts @@ -0,0 +1,64 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import * as semver from 'semver'; +import {convertXml2js, readXml} from './file_utils'; +import {isExpired} from './file_utils'; +import {HttpOptions, JsonObject, requestBody} from './http_utils'; +import {VersionList} from './version_list'; + +/** + * Read the xml file from cache. If the cache time has been exceeded or the + * file does not exist, make an http request and write it to the file. + * @param xmlUrl The xml url. + * @param httpOptions The http options for the request. + */ +export async function updateXml( + xmlUrl: string, httpOptions: HttpOptions): Promise { + if (isExpired(httpOptions.fileName)) { + const contents = await requestBody(xmlUrl, httpOptions); + const dir = path.dirname(httpOptions.fileName); + try { + fs.mkdirSync(dir); + } catch (err) { + } + fs.writeFileSync(httpOptions.fileName, contents); + return convertXml2js(contents); + } else { + return readXml(httpOptions.fileName); + } +} + +/** + * Returns a list of versions and the partial url paths. + * @param fileName the location of the xml file to read. + * @returns the version list from the xml file. + */ +export function convertXmlToVersionList( + fileName: string, matchFile: string, + versionParser: (key: string) => string | null, + semanticVersionParser: (key: string) => string): VersionList|null { + const xmlJs = readXml(fileName); + if (!xmlJs) { + return null; + } + const versionList: VersionList = {}; + for (const content of xmlJs['ListBucketResult']['Contents']) { + const key = content['Key'][0] as string; + if (key.includes(matchFile)) { + const version = versionParser(key); + if (version) { + const semanticVersion = semanticVersionParser(key); + if (!semver.valid(semanticVersion)) { + continue; + } + const name = key.split('/')[1]; + const size = +content['Size'][0]; + if (!versionList[semanticVersion]) { + versionList[semanticVersion] = {}; + } + versionList[semanticVersion][name] = {name, size, url: key, version}; + } + } + } + return versionList; +} \ No newline at end of file diff --git a/lib/provider/utils/file_utils.spec-int.ts b/lib/provider/utils/file_utils.spec-int.ts new file mode 100644 index 00000000..929827a7 --- /dev/null +++ b/lib/provider/utils/file_utils.spec-int.ts @@ -0,0 +1,190 @@ +import * as fs from 'fs'; +import * as loglevel from 'loglevel'; +import * as os from 'os'; +import * as path from 'path'; +import * as rimraf from 'rimraf'; +import {generateConfigFile, removeFiles, tarFileList, uncompressTarball, unzipFile, zipFileList} from './file_utils'; + +const log = loglevel.getLogger('webdriver-manager-test'); +log.setLevel('debug'); + +const tarballFile = path.resolve('spec/support/files/bar.tar.gz'); +const zipFile = path.resolve('spec/support/files/bar.zip'); + +describe('file_utils', () => { + describe('tarFileList', () => { + it('should have a file list', async () => { + const fileList = await tarFileList(tarballFile); + expect(fileList).toBeTruthy(); + expect(fileList.length).toBe(1); + expect(fileList[0]).toBe('bar'); + }); + + it('should return an error if the file does not exist', async () => { + try { + await tarFileList('file_does_not_exist'); + expect(false).toBeTruthy(); + } catch (err) { + expect(err).toBeTruthy(); + } + }); + }); + + describe('untarFile', () => { + let tmpDir: string; + + beforeAll(() => { + tmpDir = path.resolve(os.tmpdir(), 'test'); + try { + fs.mkdirSync(tmpDir); + } catch (err) { + } + }); + + afterAll(() => { + rimraf.sync(tmpDir); + }); + + it('should uncompress the file', async () => { + const untarFiles = await uncompressTarball(tarballFile, tmpDir); + const untarBar = path.resolve(tmpDir, 'bar'); + expect(untarFiles).toBeTruthy(); + expect(untarFiles.length).toBe(1); + expect(untarFiles[0]).toBe(untarBar); + expect(fs.statSync(untarBar).size).toBe(30); + }); + }); + + describe('zipFileList', () => { + it('should have a file list', () => { + const fileList = zipFileList(zipFile); + expect(fileList).toBeTruthy(); + expect(fileList.length).toBe(1); + expect(fileList[0]).toBe('bar'); + }); + + it('should return an error if the file does not exist', () => { + try { + zipFileList('file_does_not_exist'); + expect(false).toBeTruthy(); + } catch (err) { + expect(err).toBeTruthy(); + } + }); + }); + + describe('unzipFile', () => { + let tmpDir: string; + + beforeAll(() => { + tmpDir = path.resolve(os.tmpdir(), 'test'); + try { + fs.mkdirSync(tmpDir); + } catch (err) { + } + }); + + afterAll(() => { + rimraf.sync(tmpDir); + }); + + it('should uncompress the file', () => { + const zipFiles = unzipFile(zipFile, tmpDir); + const unzipBar = path.resolve(tmpDir, 'bar'); + expect(zipFiles).toBeTruthy(); + expect(zipFiles.length).toBe(1); + expect(zipFiles[0]).toBe(unzipBar); + expect(fs.statSync(unzipBar).size).toBe(30); + }); + }); + + describe('generateConfigFile', () => { + let tmpDir: string; + + beforeAll(() => { + tmpDir = path.resolve(os.tmpdir(), 'test'); + try { + fs.mkdirSync(tmpDir); + } catch (err) { + } + }); + + afterAll(() => { + rimraf.sync(tmpDir); + }); + + it('should write the file', () => { + // Creates empty files in the temp directory. + ['foo.zip', + 'foo_.zip', + 'foo_12.2', + 'foo_12.4', + 'foo.xml', + 'foo_.xml', + 'bar.tar.gz', + 'bar_10.1.1', + 'bar_10.1.2', + 'bar.json', + ].forEach(fileName => { + fs.closeSync(fs.openSync(path.resolve(tmpDir, fileName), 'w')); + }); + const tmpFile = path.resolve(tmpDir, 'foobar.config.json'); + const lastBinary = path.resolve(tmpDir, 'foo_12.4'); + + const fileBinaryPathRegex: RegExp = /foo_\d+.\d+/g; + generateConfigFile(tmpDir, tmpFile, fileBinaryPathRegex, lastBinary); + + const contents = fs.readFileSync(tmpFile).toString(); + const jsonContents = JSON.parse(contents); + expect(jsonContents['last']).toBe(lastBinary); + expect(jsonContents['all'].length).toBe(2); + }); + }); + + describe('removeFiles', () => { + let tmpDir: string; + + beforeEach(() => { + tmpDir = path.resolve(os.tmpdir(), 'test'); + try { + fs.mkdirSync(tmpDir); + } catch (err) { + } + }); + + afterEach(() => { + rimraf.sync(tmpDir); + }); + + it('should remove files', () => { + log.debug(tmpDir); + fs.closeSync(fs.openSync(path.resolve(tmpDir, 'bar-123'), 'w')); + fs.closeSync(fs.openSync(path.resolve(tmpDir, 'bar-456'), 'w')); + fs.closeSync(fs.openSync(path.resolve(tmpDir, 'bar-789'), 'w')); + + fs.closeSync(fs.openSync(path.resolve(tmpDir, 'baz-123'), 'w')); + fs.closeSync(fs.openSync(path.resolve(tmpDir, 'baz-456'), 'w')); + fs.closeSync(fs.openSync(path.resolve(tmpDir, 'baz-789'), 'w')); + + fs.closeSync(fs.openSync(path.resolve(tmpDir, 'foo-123'), 'w')); + fs.closeSync(fs.openSync(path.resolve(tmpDir, 'foo-456'), 'w')); + fs.closeSync(fs.openSync(path.resolve(tmpDir, 'foo-789'), 'w')); + + expect(removeFiles(tmpDir, [/bar-.*/g])) + .toBe('bar-123\nbar-456\nbar-789'); + expect(fs.readdirSync(tmpDir).length).toBe(6); + expect(removeFiles(tmpDir, [ + /foo-.*/g, /baz-.*/g + ])).toBe('baz-123\nbaz-456\nbaz-789\nfoo-123\nfoo-456\nfoo-789'); + expect(fs.readdirSync(tmpDir).length).toBe(0); + }); + + it('should not remove files if nothing is matched', () => { + fs.closeSync(fs.openSync(path.resolve(tmpDir, 'bar-123'), 'w')); + fs.closeSync(fs.openSync(path.resolve(tmpDir, 'bar-456'), 'w')); + fs.closeSync(fs.openSync(path.resolve(tmpDir, 'bar-789'), 'w')); + expect(removeFiles(tmpDir, [/zebra-.*/g])).toBe(''); + expect(fs.readdirSync(tmpDir).length).toBe(3); + }); + }); +}); \ No newline at end of file diff --git a/lib/provider/utils/file_utils.spec-unit.ts b/lib/provider/utils/file_utils.spec-unit.ts new file mode 100644 index 00000000..ebca840e --- /dev/null +++ b/lib/provider/utils/file_utils.spec-unit.ts @@ -0,0 +1,150 @@ +import * as fs from 'fs'; +import {convertXml2js, getBinaryPathFromConfig, getMatchingFiles, isExpired, readJson, readXml} from './file_utils'; +import {JsonObject} from './http_utils'; + +const xmlContents = ` + + + foobar_driver + + 2.0/foobar.zip + 10 + + + 2.1/foobar.zip + 11 + +`; + +const jsonObjectContents = `{ + "foo": "abc", + "bar": 123, + "baz": { + "num": 101, + "list": ["a", "b", "c"] + } +}`; + +const jsonArrayContents = `[{ + "foo": "abc" +}, { + "foo": "def" +}, { + "foo": "ghi" +}]`; + + +describe('file_utils', () => { + describe('isExpired', () => { + it('should return true if the file is zero', () => { + const mtime = Date.now() - 1000; + spyOn(fs, 'statSync').and.returnValue({size: 0, mtime}); + expect(isExpired('foobar.xml')).toBeTruthy(); + }); + + it('should return true if the file is zero', () => { + const mtime = Date.now() - (60 * 60 * 1000) - 5000; + spyOn(fs, 'statSync').and.returnValue({size: 1000, mtime}); + expect(isExpired('foobar.xml')).toBeTruthy(); + }); + + it('should return true if the file is zero', () => { + const mtime = Date.now() - (60 * 60 * 1000) + 5000; + spyOn(fs, 'statSync').and.returnValue({size: 1000, mtime}); + expect(isExpired('foobar.xml')).toBeFalsy(); + }); + }); + + describe('readXml', () => { + it('should read the file', () => { + spyOn(fs, 'readFileSync').and.returnValue(xmlContents); + const xmlContent = readXml('foobar'); + expect(xmlContent['ListBucketResult']['Name'][0]).toBe('foobar_driver'); + expect(xmlContent['ListBucketResult']['Contents'][0]['Key'][0]) + .toBe('2.0/foobar.zip'); + }); + + it('should get null if reading the file fails', () => { + const xmlContent = readXml('foobar'); + expect(xmlContent).toBeNull(); + }); + }); + + describe('convertXml2js', () => { + it('should convert the content to json', () => { + const xmlContent = convertXml2js(xmlContents); + expect(xmlContent['ListBucketResult']['Name'][0]).toBe('foobar_driver'); + expect(xmlContent['ListBucketResult']['Contents'][0]['Key'][0]) + .toBe('2.0/foobar.zip'); + }); + }); + + describe('readJson', () => { + it('should read the json object from file', () => { + spyOn(fs, 'readFileSync').and.returnValue(jsonObjectContents); + const jsonObj = readJson('foobar') as JsonObject; + expect(jsonObj['foo']).toBe('abc'); + expect(jsonObj['bar']).toBe(123); + expect(jsonObj['baz']['num']).toBe(101); + expect(jsonObj['baz']['list'][0]).toBe('a'); + expect(jsonObj['baz']['list'][1]).toBe('b'); + expect(jsonObj['baz']['list'][2]).toBe('c'); + }); + + it('should read the json array from file', () => { + spyOn(fs, 'readFileSync').and.returnValue(jsonArrayContents); + const jsonArray = readJson('foobar') as JsonObject[]; + expect(jsonArray.length).toBe(3); + expect(jsonArray[0]['foo']).toBe('abc'); + expect(jsonArray[1]['foo']).toBe('def'); + expect(jsonArray[2]['foo']).toBe('ghi'); + }); + + it('should get null if reading the file fails', () => { + const jsoNContent = readJson('foobar'); + expect(jsoNContent).toBeNull(); + }); + }); + + describe('getMatchingFiles', () => { + it('should find a set of matching files', () => { + const existingFiles = [ + 'foo.zip', + 'foo_.zip', + 'foo_12.2', + 'foo_12.4', + 'foo.xml', + 'foo_.xml', + 'bar.tar.gz', + 'bar_10.1.1', + 'bar_10.1.2', + 'bar.json', + ]; + const fileBinaryPathRegex: RegExp = /foo_\d+.\d+/g; + spyOn(fs, 'readdirSync').and.returnValue(existingFiles); + const matchedFiles = getMatchingFiles('/path/to', fileBinaryPathRegex); + expect(matchedFiles[0]).toContain('foo_12.2'); + expect(matchedFiles[1]).toContain('foo_12.4'); + }); + }); + + describe('getBinaryPathFromConfig', () => { + const configBinaries = `{ + "last": "foo-1.0", + "all": ["foo-1.0", "bar-1.1", "baz-1.2"] + }`; + it('should find the latest download', () => { + spyOn(fs, 'readFileSync').and.returnValue(configBinaries); + const last = getBinaryPathFromConfig('path-does-not-exist'); + expect(last).toBe('foo-1.0'); + }); + + it('should find the download from a version', () => { + spyOn(fs, 'readFileSync').and.returnValue(configBinaries); + expect(getBinaryPathFromConfig('path-does-not-exist', '1.0')) + .toBe('foo-1.0'); + expect(getBinaryPathFromConfig('path-does-not-exist', '1.2')) + .toBe('baz-1.2'); + }); + }); +}); diff --git a/lib/provider/utils/file_utils.ts b/lib/provider/utils/file_utils.ts new file mode 100644 index 00000000..0bd33304 --- /dev/null +++ b/lib/provider/utils/file_utils.ts @@ -0,0 +1,252 @@ +import * as AdmZip from 'adm-zip'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as tar from 'tar'; +import * as xml2js from 'xml2js'; +import {JsonObject} from './http_utils'; + +/** + * Check to see if the modified timestamp is expired. + * @param fileName THe xml filename. + */ +export function isExpired(fileName: string): boolean { + try { + const timestamp = new Date(fs.statSync(fileName).mtime).getTime(); + const size = fs.statSync(fileName).size; + const now = Date.now(); + + if (size > 0 && (now - (60 * 60 * 1000) < timestamp)) { + return false; + } else { + return true; + } + } catch (err) { + return true; + } +} + +/** + * Reads the json file from file. + * @param fileName The json filename to read. + * @returns + */ +export function readJson(fileName: string): JsonObject[]|JsonObject|null { + try { + const contents = fs.readFileSync(fileName).toString(); + return JSON.parse(contents); + } catch (err) { + return null; + } +} + +/** + * Reads the xml file. + * @param fileName The xml filename to read. + */ +export function readXml(fileName: string): JsonObject|null { + try { + const contents = fs.readFileSync(fileName).toString(); + return convertXml2js(contents); + } catch (err) { + return null; + } +} + +/** + * Convert the xml file to an object. + * @param content The xml contents. + */ +export function convertXml2js(content: string): JsonObject|null { + let retResult: JsonObject = null; + xml2js.parseString(content, (err, result) => { + retResult = result; + }); + return retResult; +} + +/** + * Renames a file with a semantic version. + * @param srcFileName The full path to the original file name. + * @param versionNumber The semver number. + * @returns The renamed file name. + */ +export function renameFileWithVersion( + srcFileName: string, versionNumber: string): string { + const dirName = path.dirname(srcFileName); + const extName = path.extname(srcFileName); + const baseName = path.basename(srcFileName, extName); + const dstFileName = path.resolve(dirName, baseName + versionNumber + extName); + fs.renameSync(srcFileName, dstFileName); + return dstFileName; +} + +/** + * Gets a list of files in the zip file. + * @param zipFileName The zip file. + * @returns A list of files in the zip file. + */ +export function zipFileList(zipFileName: string): string[] { + const fileList: string[] = []; + const zip = new AdmZip(zipFileName); + zip.getEntries().forEach(entry => { + fileList.push(entry.name); + }); + return fileList; +} + +/** + * Uncompress the zip file to a destination directory. + * @param zipFileName The zip file. + * @param dstDir The destination directory for the contents of the zip file. + * @returns A list of uncompressed files. + */ +export function unzipFile(zipFileName: string, dstDir: string): string[] { + const fileList: string[] = []; + const zip = new AdmZip(zipFileName); + zip.extractAllTo(dstDir, true); + for (const fileItem of zipFileList(zipFileName)) { + fileList.push(path.resolve(dstDir, fileItem)); + } + return fileList; +} + +/** + * Gets a list of files in the tarball file. + * @param tarball The tarball file. + * @returns A lsit of files in the tarball file. + */ +export function tarFileList(tarball: string): Promise { + const fileList: string[] = []; + return tar + .list({ + file: tarball, + onentry: entry => { + fileList.push(entry['path'].toString()); + } + }) + .then(() => { + return fileList; + }); +} + +/** + * Uncompress the tar file to a destination directory. + * @param tarball The tarball file. + * @param dstDir The destination directory for the contents of the zip file. + * @returns A list of uncompressed files. + */ +export async function uncompressTarball( + tarball: string, dstDir: string): Promise { + try { + fs.mkdirSync(path.resolve(dstDir)); + } catch (err) { + } + + const fileList = await tarFileList(tarball); + return tar.extract({file: tarball}).then(() => { + const dstFiles: string[] = []; + for (const fileItem of fileList) { + const dstFileName = path.resolve(dstDir, fileItem); + fs.renameSync(path.resolve(fileItem), dstFileName); + dstFiles.push(dstFileName); + } + return dstFiles; + }); +} + +/** + * Change the permissions for Linux and MacOS with chmod. + * @param fileName The full path to the filename to change permissions. + * @param mode The number to modify. + * @param osType The OS type to decide if we need to change permissions on the + * file. + */ +export function changeFilePermissions( + fileName: string, mode: string, osType: string) { + if (osType === 'Darwin' || osType === 'Linux') { + fs.chmodSync(path.resolve(fileName), mode); + } +} + +/** + * Writes a config file that matches the regex pattern. + * @param outDir The output directory. + * @param fileName The full path to the file name. + * @param fileBinaryPathRegex The regExp to match files in the outDir. + * @param lastFileBinaryPath The full path to the last binary file downloaded. + */ +export function generateConfigFile( + outDir: string, fileName: string, fileBinaryPathRegex: RegExp, + lastFileBinaryPath?: string) { + const configData: JsonObject = {}; + if (lastFileBinaryPath) { + configData['last'] = lastFileBinaryPath; + } + configData['all'] = getMatchingFiles(outDir, fileBinaryPathRegex); + fs.writeFileSync(fileName, JSON.stringify(configData)); +} + +/** + * Gets matching files form the outDir and returns it as an array. + * @param outDir The output directory. + * @param fileBinaryPathRegex The regExp to match files in the outDir. + */ +export function getMatchingFiles( + outDir: string, fileBinaryPathRegex: RegExp): string[] { + const existFiles = fs.readdirSync(outDir); + const matchingFiles: string[] = []; + for (const existFile of existFiles) { + if (existFile.match(fileBinaryPathRegex)) { + matchingFiles.push(path.resolve(outDir, existFile)); + } + } + return matchingFiles; +} + +/** + * Get the binary path from the configuration file. The configuration file + * should be formatted as { 'last': string, 'all': string[] }. In the 'all' + * array, we should match the 'version'. The version does not necessarily have + * to be a valid semantic version. + * @param cacheFilePath The cache file path. + * @param version An optional version that is not necessarily semver. + */ +export function getBinaryPathFromConfig( + cacheFilePath: string, version?: string): string|null { + const cacheJson = JSON.parse(fs.readFileSync(cacheFilePath).toString()); + let binaryPath = null; + if (!version) { + binaryPath = cacheJson['last']; + } else { + for (const cachePath of cacheJson['all']) { + if (cachePath.match(version)) { + binaryPath = cachePath; + } + } + } + return binaryPath; +} + +/** + * Removes the files that match the regular expressions and returns a string + * of removed files. + * @param outDir The output directory. + * @param fileRegexes The regExp to match files to remove in the outDir. + */ +export function removeFiles(outDir: string, fileRegexes: RegExp[]): string { + try { + const existFiles = fs.readdirSync(outDir); + const removedFiles: string[] = []; + for (const fileRegex of fileRegexes) { + for (const existFile of existFiles) { + if (existFile.match(fileRegex)) { + removedFiles.push(existFile); + fs.unlinkSync(path.resolve(outDir, existFile)); + } + } + } + return (removedFiles.sort()).join('\n'); + } catch (_) { + return null; + } +} \ No newline at end of file diff --git a/lib/provider/utils/github_json.spec-int.ts b/lib/provider/utils/github_json.spec-int.ts new file mode 100644 index 00000000..53d372cd --- /dev/null +++ b/lib/provider/utils/github_json.spec-int.ts @@ -0,0 +1,29 @@ +import * as path from 'path'; +import {checkConnectivity} from '../../../spec/support/helpers/test_utils'; +import {convertJsonToVersionList, requestRateLimit} from './github_json'; + +const fileName = path.resolve('spec/support/files/gecko.json'); + +describe('github_json', () => { + describe('requestRateLimit', () => { + it('should get rate limit assuming quota exists', async () => { + if (await checkConnectivity('rate limit test')) { + const rateLimit = await requestRateLimit(); + expect(rateLimit).toBeTruthy(); + const rateLimitObj = JSON.parse(rateLimit); + expect(rateLimitObj['resources']).toBeTruthy(); + expect(rateLimitObj['resources']['core']).toBeTruthy(); + } + }); + }); + + describe('convertJsonToVersionList', () => { + it('should convert the json', () => { + const geckoVersionList = convertJsonToVersionList(fileName); + expect(Object.keys(geckoVersionList).length).toBe(3); + expect(geckoVersionList['0.20.0']).toBeTruthy(); + expect(geckoVersionList['0.20.1']).toBeTruthy(); + expect(geckoVersionList['0.21.0']).toBeTruthy(); + }); + }); +}); \ No newline at end of file diff --git a/lib/provider/utils/github_json.spec-unit.ts b/lib/provider/utils/github_json.spec-unit.ts new file mode 100644 index 00000000..fb3767e3 --- /dev/null +++ b/lib/provider/utils/github_json.spec-unit.ts @@ -0,0 +1,34 @@ +import {hasQuota, RequestMethod} from './github_json'; + +describe('github_json', () => { + describe('hasQuota', () => { + it('should return true when there is quota', async () => { + const requestMethod: RequestMethod = + (jsonUrl: string, {}, oauthToken?: string): Promise => { + return Promise.resolve( + '{ "resources": { "core": { "remaining": 1 } } }'); + }; + const result = await hasQuota(null, requestMethod); + expect(result).toBeTruthy(); + }); + + it('should return false when there is no quota', async () => { + const requestMethod = + (jsonUrl: string, {}, oauthToken?: string): Promise => { + return Promise.resolve( + '{ "resources": { "core": { "remaining": 0 } } }'); + }; + const result = await hasQuota(null, requestMethod); + expect(result).toBeFalsy(); + }); + + it('should return false when something wrong happened', async () => { + const requestMethod = + (jsonUrl: string, {}, oauthToken?: string): Promise => { + return Promise.resolve(''); + }; + const result = await hasQuota(null, requestMethod); + expect(result).toBeFalsy(); + }); + }); +}); \ No newline at end of file diff --git a/lib/provider/utils/github_json.ts b/lib/provider/utils/github_json.ts new file mode 100644 index 00000000..111c506c --- /dev/null +++ b/lib/provider/utils/github_json.ts @@ -0,0 +1,151 @@ +import * as fs from 'fs'; +import * as loglevel from 'loglevel'; +import * as path from 'path'; + +import {isExpired, readJson} from './file_utils'; +import {HttpOptions, JsonObject, requestBody} from './http_utils'; +import {VersionList} from './version_list'; + +const log = loglevel.getLogger('webdriver-manager'); + +export interface RequestMethod { + (jsonUrl: string, httpOptions: HttpOptions, + oauthToken?: string): Promise; +} + +/** + * Read the json file from cache. If the cache time has been exceeded or the + * file does not exist, make an http request and write it to the file. + * @param jsonUrl The json url. + * @param httpOptions The http options for the request. + * @param oauthToken An optional oauth token. + */ +export async function updateJson( + jsonUrl: string, httpOptions: HttpOptions, + oauthToken?: string): Promise { + if (isExpired(httpOptions.fileName)) { + let contents: string; + + // Create the folder to store the cache. + const dir = path.dirname(httpOptions.fileName); + try { + fs.mkdirSync(dir); + } catch (err) { + } + + // Check the rate limit and if there is quota for this request. + if (await hasQuota(oauthToken)) { + contents = await requestGitHubJson(jsonUrl, httpOptions, oauthToken); + fs.writeFileSync(httpOptions.fileName, contents); + return JSON.parse(contents); + } else { + return null; + } + } else { + return readJson(httpOptions.fileName); + } +} + + +/** + * Get the GitHub rate limit with the oauth token. + * @param oauthToken An optional oauth token. + * @param requestMethod An overriding requesting method. + * @returns A promised string of the response body. + */ +export function requestRateLimit( + oauthToken?: string, requestMethod?: RequestMethod): Promise { + const rateLimitUrl = 'https://api.github.com/rate_limit'; + if (requestMethod) { + return requestMethod(rateLimitUrl, {}, oauthToken); + } else { + return requestGitHubJson(rateLimitUrl, {}, oauthToken); + } +} + +/** + * Request the GitHub json url and log the curl. + * @param jsonUrl The json url. + * @param httpOptions The http options for the request. + * @param oauthToken An optional oauth token. + * @returns A promised string of the response body. + */ +export function requestGitHubJson( + jsonUrl: string, httpOptions: HttpOptions, + oauthToken?: string): Promise { + if (!httpOptions.headers) { + httpOptions.headers = {}; + } + httpOptions.headers['User-Agent'] = 'angular/webdriver-manager'; + if (oauthToken) { + httpOptions.headers['Authorization'] = 'token ' + oauthToken; + } else if (process.env['GITHUB_TOKEN'] || process.env['github_token']) { + const token = process.env['GITHUB_TOKEN'] || process.env['github_token']; + httpOptions.headers['Authorization'] = 'token ' + token; + } + return requestBody(jsonUrl, httpOptions); +} + +/** + * Check quota for remaining GitHub requests. + * @param oauthToken An optional oauth token. + * @param requestMethod An overriding requesting method. + */ +export async function hasQuota( + oauthToken?: string, requestMethod?: RequestMethod): Promise { + try { + const requesteRateLimit = await requestRateLimit(oauthToken, requestMethod); + if (!requesteRateLimit) { + throw new Error( + 'Request encountered an error. Received null, expecting json.'); + } + const rateLimit = JSON.parse(requesteRateLimit); + if (rateLimit['resources']['core']['remaining'] === 0) { + if (oauthToken) { + log.warn('[WARN] No remaining quota for requests to GitHub.'); + } else { + log.warn( + '[WARN] Provide an oauth token. ' + + 'See https://github.com/settings/tokens'); + } + log.warn('[WARN] Stopping updates for gecko driver.'); + return false; + } + return true; + } catch (err) { + log.error('[ERROR]: ', err); + return false; + } +} + +/** + * Returns a list of versions and the partial url paths. + * @param fileName the location of the xml file to read. + * @returns the version list from the xml file. + */ +export function convertJsonToVersionList(fileName: string): VersionList|null { + const githubJson = readJson(fileName) as JsonObject[]; + if (!githubJson) { + return null; + } + const versionList: VersionList = {}; + for (const githubObj of githubJson) { + interface Asset { + name: string; + browser_download_url: string; + size: number; + } + const assets = githubObj['assets'] as JsonObject[]; + const version = (githubObj['tag_name'] as string).replace('v', ''); + versionList[version] = {}; + + for (const asset of assets) { + const name = asset['name'] as string; + const downloadUrl = asset['browser_download_url']; + const size = asset['size']; + versionList[version][name] = {name, size, url: downloadUrl, version} as + JsonObject; + } + } + return versionList; +} \ No newline at end of file diff --git a/lib/provider/utils/http_utils.spec-int.ts b/lib/provider/utils/http_utils.spec-int.ts new file mode 100644 index 00000000..6952d94b --- /dev/null +++ b/lib/provider/utils/http_utils.spec-int.ts @@ -0,0 +1,116 @@ +import * as childProcess from 'child_process'; +import * as fs from 'fs'; +import * as loglevel from 'loglevel'; +import * as os from 'os'; +import * as path from 'path'; + +import {httpBaseUrl} from '../../../spec/server/env'; +import {spawnProcess} from '../../../spec/support/helpers/test_utils'; +import {requestBinary, requestBody} from './http_utils'; + +const log = loglevel.getLogger('webdriver-manager-test'); +log.setLevel('debug'); + +const tmpDir = path.resolve(os.tmpdir(), 'test'); +const fileName = path.resolve(tmpDir, 'bar.zip'); +const binaryUrl = httpBaseUrl + '/spec/support/files/bar.zip'; +const fooJsonUrl = httpBaseUrl + '/spec/support/files/foo_json.json'; +const fooArrayUrl = httpBaseUrl + '/spec/support/files/foo_array.json'; +const fooXmlUrl = httpBaseUrl + '/spec/support/files/foo.xml'; +const barZipSize = 171; + +describe('http_utils', () => { + const origTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; + let proc: childProcess.ChildProcess; + + beforeAll(() => { + jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; + }); + + afterAll(() => { + jasmine.DEFAULT_TIMEOUT_INTERVAL = origTimeout; + }); + + describe('with a http server', () => { + beforeAll(async () => { + proc = spawnProcess('node', ['dist/spec/server/http_server.js']); + log.debug('http-server: ' + proc.pid); + await new Promise((resolve, _) => { + setTimeout(resolve, 3000); + }); + + try { + fs.mkdirSync(tmpDir); + } catch (err) { + } + try { + fs.unlinkSync(fileName); + } catch (err) { + } + }); + + afterAll(async () => { + try { + fs.unlinkSync(fileName); + fs.rmdirSync(tmpDir); + } catch (err) { + } + + process.kill(proc.pid); + await new Promise((resolve, _) => { + setTimeout(resolve, 3000); + }); + }); + + describe('requestBinary', () => { + it('should download the file if no file exists or ' + + 'the content lenght is different', + (done) => { + requestBinary(binaryUrl, {fileName, fileSize: 0}) + .then((result) => { + expect(result).toBeTruthy(); + expect(fs.statSync(fileName).size).toBe(barZipSize); + done(); + }) + .catch(err => { + done.fail(err); + }); + }); + + it('should not download the file if the file exists', (done) => { + requestBinary(binaryUrl, {fileName, fileSize: barZipSize}) + .then((result) => { + expect(result).toBeFalsy(); + expect(fs.statSync(fileName).size).toBe(barZipSize); + done(); + }) + .catch(err => { + done.fail(err); + }); + }); + }); + + describe('requestBody', () => { + it('should download a json object file', async () => { + const foo = await requestBody(fooJsonUrl, {}); + const fooJson = JSON.parse(foo); + expect(fooJson['foo']).toBe('abc'); + expect(fooJson['bar']).toBe(123); + }); + + it('should download a json array file', async () => { + const foo = await requestBody(fooArrayUrl, {}); + const fooJson = JSON.parse(foo); + expect(fooJson.length).toBe(3); + expect(fooJson[0]['foo']).toBe('abc'); + expect(fooJson[1]['foo']).toBe('def'); + expect(fooJson[2]['foo']).toBe('ghi'); + }); + + it('should get the xml file', async () => { + const text = await requestBody(fooXmlUrl, {}); + expect(text.length).toBeGreaterThan(0); + }); + }); + }); +}); \ No newline at end of file diff --git a/lib/provider/utils/http_utils.spec-proxy.ts b/lib/provider/utils/http_utils.spec-proxy.ts new file mode 100644 index 00000000..3efb2070 --- /dev/null +++ b/lib/provider/utils/http_utils.spec-proxy.ts @@ -0,0 +1,112 @@ +import * as childProcess from 'child_process'; +import * as fs from 'fs'; +import * as loglevel from 'loglevel'; +import * as os from 'os'; +import * as path from 'path'; + +import {httpBaseUrl, proxyBaseUrl} from '../../../spec/server/env'; +import {spawnProcess} from '../../../spec/support/helpers/test_utils'; + +import {requestBinary, requestBody} from './http_utils'; + +const log = loglevel.getLogger('webdriver-manager-test'); +log.setLevel('debug'); + +const tmpDir = path.resolve(os.tmpdir(), 'test'); +const fileName = path.resolve(tmpDir, 'bar.zip'); +const binaryUrl = proxyBaseUrl + '/spec/support/files/bar.zip'; +const fooJsonUrl = proxyBaseUrl + '/spec/support/files/foo_json.json'; +const fooArrayUrl = proxyBaseUrl + '/spec/support/files/foo_array.json'; +const fooXmlUrl = proxyBaseUrl + '/spec/support/files/foo.xml'; +const barZipSize = 171; +const headers = { + 'host': httpBaseUrl +}; + +describe('binary_utils', () => { + const origTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; + let httpProc: childProcess.ChildProcess; + let proxyProc: childProcess.ChildProcess; + + beforeAll((done) => { + jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; + httpProc = spawnProcess('node', ['dist/spec/server/http_server.js']); + log.debug('http-server: ' + httpProc.pid); + proxyProc = spawnProcess('node', ['dist/spec/server/proxy_server.js']); + log.debug('proxy-server: ' + proxyProc.pid); + setTimeout(done, 3000); + + try { + fs.mkdirSync(tmpDir); + } catch (err) { + } + try { + fs.unlinkSync(fileName); + } catch (err) { + } + }); + + afterAll((done) => { + try { + fs.unlinkSync(fileName); + fs.rmdirSync(tmpDir); + } catch (err) { + } + + process.kill(httpProc.pid); + process.kill(proxyProc.pid); + setTimeout(done, 5000); + jasmine.DEFAULT_TIMEOUT_INTERVAL = origTimeout; + }); + + describe('requestBinary', () => { + it('should download the file if no file exists or ' + + 'the content lenght is different', + (done) => { + requestBinary(binaryUrl, {fileName, fileSize: 0, headers}) + .then((result) => { + expect(result).toBeTruthy(); + expect(fs.statSync(fileName).size).toBe(barZipSize); + done(); + }) + .catch(err => { + done.fail(err); + }); + }); + + it('should not download the file if the file exists', (done) => { + requestBinary(binaryUrl, {fileName, fileSize: barZipSize, headers}) + .then((result) => { + expect(result).toBeFalsy(); + expect(fs.statSync(fileName).size).toBe(barZipSize); + done(); + }) + .catch(err => { + done.fail(err); + }); + }); + }); + + describe('requestBody', () => { + it('should download a json object file', async () => { + const foo = await requestBody(fooJsonUrl, {headers}); + const fooJson = JSON.parse(foo); + expect(fooJson['foo']).toBe('abc'); + expect(fooJson['bar']).toBe(123); + }); + + it('should download a json array file', async () => { + const foo = await requestBody(fooArrayUrl, {headers}); + const fooJson = JSON.parse(foo); + expect(fooJson.length).toBe(3); + expect(fooJson[0]['foo']).toBe('abc'); + expect(fooJson[1]['foo']).toBe('def'); + expect(fooJson[2]['foo']).toBe('ghi'); + }); + + it('should get the xml file', async () => { + const text = await requestBody(fooXmlUrl, {headers}); + expect(text.length).toBeGreaterThan(0); + }); + }); +}); \ No newline at end of file diff --git a/lib/provider/utils/http_utils.spec-unit.ts b/lib/provider/utils/http_utils.spec-unit.ts new file mode 100644 index 00000000..eb687ada --- /dev/null +++ b/lib/provider/utils/http_utils.spec-unit.ts @@ -0,0 +1,178 @@ +import {addHeader, initOptions, optionsProxy, optionsSSL, RequestOptionsValue, resolveProxy} from './http_utils'; + +describe('http utils', () => { + describe('initOptions', () => { + it('should create options', () => { + const requestUrl = 'http://foobar.com'; + const options = initOptions(requestUrl, {}); + expect(options['url']).toBe(requestUrl); + expect(options['timeout']).toBe(240000); + expect(options['proxy']).toBeUndefined(); + expect(options['strictSSL']).toBeUndefined(); + expect(options['rejectUnauthorized']).toBeUndefined(); + }); + + it('should create options with a proxy', () => { + const requestUrl = 'http://foobar.com'; + const proxy = 'http://baz.com'; + const options = initOptions(requestUrl, {proxy}); + expect(options['url']).toBe(requestUrl); + expect(options['timeout']).toBe(240000); + expect(options['proxy']).toBe(proxy); + expect(options['strictSSL']).toBeUndefined(); + expect(options['rejectUnauthorized']).toBeUndefined(); + }); + + it('should create options with SSL', () => { + const requestUrl = 'http://foobar.com'; + const options = initOptions(requestUrl, {ignoreSSL: true}); + expect(options['url']).toBe(requestUrl); + expect(options['timeout']).toBe(240000); + expect(options['proxy']).toBeUndefined(); + expect(options['strictSSL']).toBeFalsy(); + expect(options['rejectUnauthorized']).toBeFalsy(); + }); + + it('should create options with SSL and proxy', () => { + const requestUrl = 'http://foobar.com'; + const proxy = 'http://baz.com'; + const options = initOptions(requestUrl, {ignoreSSL: true, proxy}); + expect(options['url']).toBe(requestUrl); + expect(options['timeout']).toBe(240000); + expect(options['proxy']).toBe(proxy); + expect(options['strictSSL']).toBeFalsy(); + expect(options['rejectUnauthorized']).toBeFalsy(); + }); + }); + + describe('optionsSSL', () => { + it('should set strictSSL and rejectUnauthorized', () => { + let options: RequestOptionsValue = {url: 'http://foobar.com'}; + const ignoreSSL = true; + options = optionsSSL(options, ignoreSSL); + expect(options['strictSSL']).toBeFalsy(); + expect(options['rejectUnauthorized']).toBeFalsy(); + }); + + it('should set not set strictSSL and rejectUnauthorized', () => { + let options: RequestOptionsValue = {url: 'http://foobar.com'}; + const ignoreSSL = false; + options = optionsSSL(options, ignoreSSL); + expect(options['strictSSL']).toBeUndefined(); + expect(options['rejectUnauthorized']).toBeUndefined(); + }); + }); + + describe('optionsProxy', () => { + it('should set the proxy', () => { + const requestUrl = 'http://foobar.com'; + let options: RequestOptionsValue = {url: requestUrl}; + const proxy = 'http://baz.com'; + options = optionsProxy(options, requestUrl, proxy); + expect(options['proxy']).toBe(proxy); + }); + + it('should not set the proxy when the proxy is undefined', () => { + const requestUrl = 'http://foobar.com'; + let options: RequestOptionsValue = {url: requestUrl}; + options = optionsProxy(options, requestUrl, undefined); + expect(options['proxy']).toBeUndefined(); + }); + }); + + describe('resolveProxy', () => { + it('should return the proxy', () => { + const requestUrl = 'http://foobar.com/foo/bar/index.html'; + const proxy = 'http://baz.com'; + const resolvedProxy = resolveProxy(requestUrl, proxy); + expect(resolvedProxy).toBe(proxy); + }); + + it('should return the http proxy env for a http request url', () => { + const requestUrl = 'http://foobar.com/foo/bar/index.html'; + const proxy = 'http://baz.com'; + process.env['HTTP_PROXY'] = proxy; + expect(process.env['HTTP_PROXY']).toBe(proxy); + + const resolvedProxy = resolveProxy(requestUrl, undefined); + expect(resolvedProxy).toBe(proxy); + + delete process.env['HTTP_PROXY']; + expect(process.env['HTTP_PROXY']).toBeUndefined(); + }); + + it('should be undefined for a https proxy env for a http request', () => { + const requestUrl = 'http://foobar.com/foo/bar/index.html'; + const proxy = 'https://baz.com'; + process.env['HTTPS_PROXY'] = proxy; + expect(process.env['HTTPS_PROXY']).toBe(proxy); + + const resolvedProxy = resolveProxy(requestUrl, undefined); + expect(resolvedProxy).toBeUndefined(); + + delete process.env['HTTPS_PROXY']; + expect(process.env['HTTPS_PROXY']).toBeUndefined(); + }); + + + it('should return the https proxy env for a https request', () => { + const requestUrl = 'https://foobar.com/foo/bar/index.html'; + const proxy = 'https://baz.com'; + process.env['HTTPS_PROXY'] = proxy; + expect(process.env['HTTPS_PROXY']).toBe(proxy); + + const resolvedProxy = resolveProxy(requestUrl, undefined); + expect(resolvedProxy).toBe(proxy); + + delete process.env['HTTPS_PROXY']; + expect(process.env['HTTPS_PROXY']).toBeUndefined(); + }); + + it('should return the http proxy env for a https request', () => { + const requestUrl = 'https://foobar.com/foo/bar/index.html'; + const proxy = 'http://baz.com'; + process.env['HTTP_PROXY'] = proxy; + expect(process.env['HTTP_PROXY']).toBe(proxy); + + const resolvedProxy = resolveProxy(requestUrl, undefined); + expect(resolvedProxy).toBe(proxy); + + delete process.env['HTTP_PROXY']; + expect(process.env['HTTP_PROXY']).toBeUndefined(); + }); + }); + + describe('addHeader', () => { + let options: RequestOptionsValue; + beforeEach(() => { + options = {url: 'http://foo.bar'}; + }); + it('should create a new header if no header exists', () => { + const modifiedOptions = addHeader(options, 'foo', 'bar'); + expect(modifiedOptions.headers).toBeTruthy(); + expect(Object.keys(modifiedOptions.headers).length).toBe(1); + expect(modifiedOptions.headers['foo']).toBe('bar'); + }); + + it('should add a header to an existing header without destroying the value', + () => { + let modifiedOptions = addHeader(options, 'foo1', 'bar1'); + modifiedOptions = addHeader(options, 'foo2', 'bar2'); + expect(modifiedOptions.headers).toBeTruthy(); + expect(Object.keys(modifiedOptions.headers).length).toBe(2); + expect(modifiedOptions.headers['foo1']).toBe('bar1'); + expect(modifiedOptions.headers['foo2']).toBe('bar2'); + }); + + it('should replace the header if selecting the same header name', () => { + let modifiedOptions = addHeader(options, 'foo', 'bar'); + expect(modifiedOptions.headers).toBeTruthy(); + expect(Object.keys(modifiedOptions.headers).length).toBe(1); + expect(modifiedOptions.headers['foo']).toBe('bar'); + + modifiedOptions = addHeader(options, 'foo', 'baz'); + expect(Object.keys(modifiedOptions.headers).length).toBe(1); + expect(modifiedOptions.headers['foo']).toBe('baz'); + }); + }); +}); \ No newline at end of file diff --git a/lib/provider/utils/http_utils.ts b/lib/provider/utils/http_utils.ts new file mode 100644 index 00000000..21d8ceb3 --- /dev/null +++ b/lib/provider/utils/http_utils.ts @@ -0,0 +1,287 @@ +import * as fs from 'fs'; +import * as loglevel from 'loglevel'; +import * as path from 'path'; +import * as request from 'request'; +import * as url from 'url'; + +const log = loglevel.getLogger('webdriver-manager'); + +/** + * The request options that extend the request. This is not exported + * in preference to build an HttpOptions object with extra metadata + * and the http_utils methods will help build this object. + */ +export interface RequestOptionsValue extends request.OptionsWithUrl { + proxy?: string; + ignoreSSL?: boolean; +} + +/** + * A json object interface. + */ +export interface JsonObject { + // tslint:disable-next-line:no-any + [key: string]: any; +} + +/** + * The http option interface to build the request. + */ +export interface HttpOptions { + // The full file path. + fileName?: string; + // The file size or content length fo the file. + fileSize?: number; + // Headers to send with the request. + headers?: {[key: string]: string|number|string[]}; + // When making the request, to ignore SSL. + ignoreSSL?: boolean; + // When making the request, use the proxy url provided. + proxy?: string; +} + +/** + * Initialize the request options. + * @param requestUrl The request url. + * @param httpOptions The http options for the request. + */ +export function initOptions( + requestUrl: string, httpOptions: HttpOptions): RequestOptionsValue { + let options: RequestOptionsValue = { + url: requestUrl, + // default Linux can be anywhere from 20-120 seconds + // increasing this arbitrarily to 4 minutes + timeout: 240000 + }; + options = optionsSSL(options, httpOptions.ignoreSSL); + options = optionsProxy(options, requestUrl, httpOptions.proxy); + if (httpOptions.headers) { + for (const key of Object.keys(httpOptions.headers)) { + options = addHeader(options, key, httpOptions.headers[key]); + } + } + return options; +} + +/** + * Set ignore SSL option. + * @param options The HTTP options + * @param optIgnoreSSL The ignore SSL option. + */ +export function optionsSSL( + options: RequestOptionsValue, optIgnoreSSL: boolean): RequestOptionsValue { + if (optIgnoreSSL) { + options.strictSSL = !optIgnoreSSL; + options.rejectUnauthorized = !optIgnoreSSL; + } + return options; +} + +export function optionsProxy( + options: RequestOptionsValue, requestUrl: string, + optProxy: string): RequestOptionsValue { + if (optProxy) { + options.proxy = resolveProxy(requestUrl, optProxy); + if (url.parse(requestUrl).protocol === 'https:') { + options.url = requestUrl.replace('https:', 'http:'); + } + } + return options; +} + +/** + * Resolves proxy based on values set. + * @param requestUrl The url to download the file. + * @param optProxy The proxy to connect to to download files. + * @return Either undefined or the proxy. + */ +export function resolveProxy(requestUrl: string, optProxy: string): string { + if (optProxy) { + return optProxy; + } else { + const protocol = url.parse(requestUrl).protocol; + const hostname = url.parse(requestUrl).hostname; + // If the NO_PROXY environment variable exists and matches the host name, + // to ignore the resolve proxy. + // Check to see if it exists and equal to empty string is to help with + // testing + const noProxy: string = process.env.NO_PROXY || process.env.no_proxy; + if (noProxy) { + // array of hostnames/domain names listed in the NO_PROXY environment + // variable + const noProxyTokens = noProxy.split(','); + // check if the fileUrl hostname part does not end with one of the + // NO_PROXY environment variable's hostnames/domain names + for (const noProxyToken of noProxyTokens) { + if (hostname.indexOf(noProxyToken) !== -1) { + return undefined; + } + } + } + + // If the HTTPS_PROXY and HTTP_PROXY environment variable is set, + // use that as the proxy + const httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy; + const httpProxy = process.env.HTTP_PROXY || process.env.http_proxy; + if (protocol === 'https:') { + return httpsProxy || httpProxy; + } else if (protocol === 'http:') { + return httpProxy; + } + } + return undefined; +} + +/** + * Builds a curl command for logging purposes. + * @param requestOptions The request options. + * @param fileName The file name path. + * @returns The curl command. + */ +export function curlCommand( + requestOptions: RequestOptionsValue, fileName?: string) { + let curl = `${requestOptions.url}`; + if (requestOptions.proxy) { + const pathUrl = url.parse(requestOptions.url.toString()).path; + const host = url.parse(requestOptions.url.toString()).host; + if (requestOptions.proxy) { + const modifiedUrl = url.resolve(requestOptions.proxy, pathUrl); + curl = `"${modifiedUrl}" -H "host: ${host}"`; + } + } + if (requestOptions.headers) { + for (const headerName of Object.keys(requestOptions.headers)) { + curl += ` -H "${headerName}: ${requestOptions.headers[headerName]}"`; + } + } + if (requestOptions.ignoreSSL) { + curl = `-k ${curl}`; + } + if (fileName) { + curl = `-o ${fileName} ${curl}`; + } + return `curl ${curl}`; +} + +/** + * Add a header to the request. + * @param options The options to add a header. + * @param name The key name of the header. + * @param value The value of the header. + * @returns The modified options object. + */ +export function addHeader( + options: RequestOptionsValue, name: string, + value: string|number|string[]): RequestOptionsValue { + if (!options.headers) { + options.headers = {}; + } + options.headers[name] = value; + return options; +} + +/** + * The request to download the binary. + * @param binaryUrl The download url for the binary. + * @param httpOptions The http options for the request. + * @param isLogInfo Log info or debug + */ +export function requestBinary( + binaryUrl: string, httpOptions: HttpOptions, + isLogInfo = true): Promise { + const options = initOptions(binaryUrl, httpOptions); + options.followRedirect = false; + options.followAllRedirects = false; + if (isLogInfo) { + log.info(curlCommand(options, httpOptions.fileName)); + } else { + log.debug(curlCommand(options, httpOptions.fileName)); + } + + return new Promise((resolve, reject) => { + const req = request(options); + req.on('response', response => { + let contentLength: number; + if (response.statusCode === 200) { + // Check to see if the size is the same. + // If the file size is the same, do not download and stop here. + contentLength = +response.headers['content-length']; + if (contentLength === httpOptions.fileSize) { + response.destroy(); + resolve(false); + } else { + // Only pipe if the headers are different length. + const dir = path.dirname(httpOptions.fileName); + try { + fs.mkdirSync(dir); + } catch (err) { + } + const file = fs.createWriteStream(httpOptions.fileName); + req.pipe(file); + + file.on('close', () => { + fs.stat(httpOptions.fileName, (error, stats) => { + if (error) { + reject(error); + } + if (stats.size !== contentLength) { + fs.unlinkSync(httpOptions.fileName); + reject(error); + } + resolve(true); + }); + }); + file.on('error', (error) => { + reject(error); + }); + } + } else if (response.statusCode === 302) { + const location = response.headers['location'] as string; + if (!httpOptions.headers) { + httpOptions.headers = {}; + } + for (const header of Object.keys(response.headers)) { + httpOptions.headers[header] = response.headers[header]; + } + resolve(requestBinary(location, httpOptions, false)); + } else { + reject(new Error('response status code is not 200')); + } + }); + req.on('error', error => { + reject(error); + }); + }); +} + +/** + * Request the body from the url and log the curl. + * @param requestUrl The request url. + * @param httpOptions The http options for the request. + * @returns A promise string of the response body. + */ +export function requestBody( + requestUrl: string, httpOptions: HttpOptions): Promise { + const options = initOptions(requestUrl, httpOptions); + log.info(curlCommand(options, httpOptions.fileName)); + options.followRedirect = true; + return new Promise((resolve, reject) => { + const req = request(options); + req.on('response', response => { + if (response.statusCode === 200) { + let output = ''; + response.on('data', (data) => { + output += data; + }); + response.on('end', () => { + resolve(output); + }); + } else { + reject(new Error('response status code is not 200')); + } + }); + req.on('error', error => { + reject(error); + }); + }); +} \ No newline at end of file diff --git a/lib/provider/utils/version_list.spec-unit.ts b/lib/provider/utils/version_list.spec-unit.ts new file mode 100644 index 00000000..e07aee9e --- /dev/null +++ b/lib/provider/utils/version_list.spec-unit.ts @@ -0,0 +1,75 @@ +import {getVersionObj, getVersionObjs, VersionList} from './version_list'; + +const versionList: VersionList = { + '1.0.0': { + 'foo_mac32': {url: '1.0.0/foo_mac32', size: 10000}, + 'foo_win32': {url: '1.0.0/foo_win32', size: 10001}, + 'foo_linux64': {url: '1.0.0/foo_linux64', size: 10002} + }, + '1.0.1': { + 'foo_mac32': {url: '1.0.1/foo_mac32', size: 10100}, + 'foo_win32': {url: '1.0.1/foo_win32', size: 10101}, + 'foo_linux64': {url: '1.0.1/foo_linux64', size: 10102} + }, + '2.0.1': { + 'foo_mac32': {url: '2.0.1/foo_mac32', size: 20100}, + 'foo_win32': {url: '2.0.1/foo_win32', size: 20101}, + 'foo_linux64': {url: '2.0.1/foo_linux64', size: 20102} + }, + '3.0.1': { + 'foo_mac32': {url: '3.0.1/foo_mac32', size: 30100}, + 'foo_win32': {url: '3.0.1/foo_win32', size: 30101}, + 'foo_linux64': {url: '3.0.1/foo_linux64', size: 30102} + } +}; + +describe('version_list', () => { + describe('getVersion', () => { + it('should return the latest version when no version provided', () => { + const version = getVersionObjs(versionList); + expect(Object.keys(version).length).toBe(3); + expect(version['foo_mac32']['size']).toBe(30100); + expect(version['foo_win32']['size']).toBe(30101); + expect(version['foo_linux64']['size']).toBe(30102); + }); + + it('should return the latest version with latest option', () => { + const version = getVersionObjs(versionList, 'latest'); + expect(Object.keys(version).length).toBe(3); + expect(version['foo_mac32']['size']).toBe(30100); + expect(version['foo_win32']['size']).toBe(30101); + expect(version['foo_linux64']['size']).toBe(30102); + }); + + it('should return version 1.0.1', () => { + const version = getVersionObjs(versionList, '1.0.1'); + expect(Object.keys(version).length).toBe(3); + expect(version['foo_mac32']['size']).toBe(10100); + expect(version['foo_win32']['size']).toBe(10101); + expect(version['foo_linux64']['size']).toBe(10102); + }); + }); + + describe('getVersionObj', () => { + it('should get the partial url for mac', () => { + const versionObjMap = getVersionObjs(versionList); + const versionObj = getVersionObj(versionObjMap, 'mac32'); + expect(versionObj.url).toBe('3.0.1/foo_mac32'); + expect(versionObj.size).toBe(30100); + }); + + it('should get the partial url for windows', () => { + const versionObjMap = getVersionObjs(versionList); + const versionObj = getVersionObj(versionObjMap, 'win32'); + expect(versionObj.url).toBe('3.0.1/foo_win32'); + expect(versionObj.size).toBe(30101); + }); + + it('should get the partial url for linux', () => { + const versionObjMap = getVersionObjs(versionList); + const versionObj = getVersionObj(versionObjMap, 'linux64'); + expect(versionObj.url).toBe('3.0.1/foo_linux64'); + expect(versionObj.size).toBe(30102); + }); + }); +}); \ No newline at end of file diff --git a/lib/provider/utils/version_list.ts b/lib/provider/utils/version_list.ts new file mode 100644 index 00000000..b869e62a --- /dev/null +++ b/lib/provider/utils/version_list.ts @@ -0,0 +1,91 @@ +import * as fs from 'fs'; +import * as semver from 'semver'; + +/** + * An object of multiple versions of a binary. Each version could have + * different keys where each key represents a partial url path. Each + * partial url path could represent a combination of os architecture + * and os type. + */ +export interface VersionList { + // The forced version is the semver equivalent version of the + // actual version number. An example is 2.9 would translate into 2.9.0 + [forcedVersion: string]: { + + // The name of the binary file. + [name: string]: VersionObj; + }; +} + +/** + * Information about the binary file. + */ +export interface VersionObj { + // The file name. + name?: string; + + // The content length of the file. + size?: number; + + // The full or partial url get the binary. + url?: string; + + // The actual version number, not the forced semantic version. + version?: string; +} + +/** + * Encapsulates the getVersionObjs and getVersionObj into a single method. + * @param versionList The version list object. + * @param osMatch The OS name and architecture. + * @param version Optional field for the semver version number or latest. + * * @returns Either a VersionObj or null. + */ +export function getVersion( + versionList: VersionList, osMatch: string, version?: string): VersionObj| + null { + const versionObjs = getVersionObjs(versionList, version); + return getVersionObj(versionObjs, osMatch); +} + +/** + * Get the version obj from the version list. + * @param versionList The version list object. + * @param version Optional field for the semver version number or latest. + * @returns The object with paritial urls associated with the binary size. + */ +export function getVersionObjs( + versionList: VersionList, version?: string): {[key: string]: VersionObj} { + if (version && version !== 'latest') { + return versionList[version]; + } else { + let latestVersion = null; + for (const versionKey of Object.keys(versionList)) { + if (!latestVersion) { + latestVersion = versionKey; + } else { + if (semver.gt(versionKey, latestVersion)) { + latestVersion = versionKey; + } + } + } + return versionList[latestVersion]; + } +} + +/** + * Get the version obj from the map. + * @param versionObjs A map of partial urls to VersionObj + * @param osMatch The OS name and architecture. + * @returns Either a VersionObj or null. + */ +export function getVersionObj( + versionObjMap: {[key: string]: VersionObj}, osMatch: string): VersionObj| + null { + for (const name of Object.keys(versionObjMap)) { + if (name.includes(osMatch)) { + return versionObjMap[name]; + } + } + return null; +} \ No newline at end of file diff --git a/lib/utils.ts b/lib/utils.ts deleted file mode 100644 index 8fd10977..00000000 --- a/lib/utils.ts +++ /dev/null @@ -1,130 +0,0 @@ -import * as child_process from 'child_process'; -import * as fs from 'fs'; -import * as http from 'http'; -import * as path from 'path'; - -import {Config} from './config'; - -function spawnFactory(sync: false): - (cmd: string, args: string[], stdio?: any, opts?: child_process.SpawnOptions) => - child_process.ChildProcess; -function spawnFactory(sync: true): - (cmd: string, args: string[], stdio?: any, opts?: child_process.SpawnSyncOptions) => - child_process.SpawnSyncReturns; -function spawnFactory(sync: boolean): - (cmd: string, args: string[], stdio?: string, - opts?: child_process.SpawnOptions|child_process.SpawnSyncOptions) => - child_process.ChildProcess | child_process.SpawnSyncReturns { - return (cmd: string, args: string[], stdio?: string, - opts?: child_process.SpawnOptions|child_process.SpawnSyncOptions) => { - if ((Config.osType() === 'Windows_NT') && (cmd.slice(-4) !== '.exe')) { - if (fs.existsSync(cmd + '.exe')) { - cmd += '.exe'; - } else { - args = ['/c'].concat([cmd], args); - cmd = 'cmd'; - } - } - if (stdio) { - opts = opts || {}; - opts.stdio = stdio; - } - if (sync) { - return child_process.spawnSync(cmd, args, opts as child_process.SpawnOptions); - } else { - return child_process.spawn(cmd, args, opts as child_process.SpawnSyncOptions); - } - }; -} - -export let spawn = spawnFactory(false); -export let spawnSync = spawnFactory(true); - -export function request( - method: string, port: string, path: string, timeout?: number, data?: any): Promise { - let headers: {[key: string]: string} = {}; - let hasContent = data && ((method == 'POST') || (method == 'PUT')); - if (hasContent) { - data = data ? JSON.stringify(data) : ''; - headers['Content-Length'] = data.length; - headers['Content-Type'] = 'application/json;charset=UTF-8'; - } - return new Promise((resolve, reject) => { - let unexpectedEnd = () => { - reject({code: 'UNKNOWN', message: 'Request ended unexpectedly'}); - }; - let req = http.request( - {port: parseInt(port), method: method, path: path, headers: headers}, (res) => { - req.removeListener('end', unexpectedEnd); - if (res.statusCode !== 200) { - reject({code: res.statusCode, message: res.statusMessage}); - } else { - let buffer: (string|Buffer)[] = []; - res.on('data', buffer.push.bind(buffer)); - res.on('end', () => { - resolve(buffer.join('').replace(/\0/g, '')); - }); - } - }); - - if (timeout) { - req.setTimeout(timeout, () => { - reject({code: 'TIMEOUT', message: 'Request timed out'}); - }); - } - req.on('error', reject); - req.on('end', unexpectedEnd); - - if (hasContent) { - req.write(data as string); - } - - req.end(); - }); -} - -export function adb( - sdkPath: string, port: number, command: string, timeout: number, - args?: string[]): Promise { - return new Promise((resolve, reject) => { - let child = spawn( - path.resolve(sdkPath, 'platform-tools', 'adb'), - ['-s', 'emulator-' + port, command].concat(args || []), 'pipe'); - let done = false; - let buffer: (string|Buffer)[] = []; - child.stdout.on('data', buffer.push.bind(buffer)); - child.on('error', (err: Error) => { - if (!done) { - done = true; - reject(err); - } - }); - child.on('exit', (code: number, signal: string) => { - if (!done) { - done = true; - if (code === 0) { - resolve(buffer.join('')); - } else { - reject({ - code: code, - message: 'abd command "' + command + '" ' + - (signal ? 'received signal ' + signal : 'returned with a non-zero exit code') + - 'for emulator-' + port - }); - } - } - }); - if (timeout) { - setTimeout(() => { - if (!done) { - done = true; - child.kill(); - reject({ - code: 'TIMEOUT', - message: 'adb command "' + command + '" timed out for emulator-' + port - }); - } - }, timeout); - } - }); -} diff --git a/lib/webdriver.ts b/lib/webdriver.ts deleted file mode 100644 index 7549787f..00000000 --- a/lib/webdriver.ts +++ /dev/null @@ -1,18 +0,0 @@ -import * as minimist from 'minimist'; - -import {cli as commandline} from './cli_instance'; - -let minimistOptions = commandline.getMinimistOptions(); -let argv = minimist(process.argv.slice(2), minimistOptions); -let cmd = argv._; -if (commandline.programs[cmd[0]]) { - if (cmd[0] === 'help') { - commandline.printHelp(); - } else if (cmd[1] === 'help' || argv['help'] || argv['h']) { - commandline.programs[cmd[0]].printHelp(); - } else { - commandline.programs[cmd[0]].run(JSON.parse(JSON.stringify(argv))); - } -} else { - commandline.printHelp(); -} diff --git a/package-lock.json b/package-lock.json index bd88d205..5829bfbb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,34 +1,34 @@ { "name": "webdriver-manager", - "version": "12.1.9", + "version": "13.0.0-beta", "lockfileVersion": 1, "requires": true, "dependencies": { "@types/adm-zip": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.5.0.tgz", - "integrity": "sha512-FCJBJq9ODsQZUNURo5ILAQueuA8WJhRvuihS3ke2iI25mJlfV2LK8jG2Qj2z2AWg8U0FtWWqBHVRetceLskSaw==", + "version": "0.4.31", + "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.4.31.tgz", + "integrity": "sha1-ozdrn6j0xunAeMF20t8srreTneM=", "dev": true, "requires": { "@types/node": "*" } }, - "@types/chalk": { - "version": "0.4.31", - "resolved": "https://registry.npmjs.org/@types/chalk/-/chalk-0.4.31.tgz", - "integrity": "sha1-ox10JBprHtu5c8822XooloNKUfk=", + "@types/caseless": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.1.tgz", + "integrity": "sha512-FhlMa34NHp9K5MY1Uz8yb+ZvuX0pnvn3jScRSNAb75KHGB8d3rEU6hqMs3Z2vjuytcMfRg6c5CHMc3wtYyD2/A==", "dev": true }, "@types/events": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@types/events/-/events-1.1.0.tgz", - "integrity": "sha512-y3bR98mzYOo0pAZuiLari+cQyiKk3UXRuT45h1RjhfeCzqkjaVsfZJNaxdgtk7/3tzOm1ozLTqEqMP3VbI48jw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", + "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==", "dev": true }, "@types/form-data": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-0.0.33.tgz", - "integrity": "sha1-yayFsqX9GENbjIXZ7LUObWyJP/g=", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.1.tgz", + "integrity": "sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ==", "dev": true, "requires": { "@types/node": "*" @@ -45,500 +45,283 @@ "@types/node": "*" } }, - "@types/ini": { - "version": "1.3.29", - "resolved": "https://registry.npmjs.org/@types/ini/-/ini-1.3.29.tgz", - "integrity": "sha1-EyXpgeBH1A0TzgNZuCFHW5d0HS8=", - "dev": true + "@types/http-proxy": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.16.2.tgz", + "integrity": "sha512-GgqePmC3rlsn1nv+kx5OviPuUBU2omhnlXOaJSXFgOdsTcScNFap+OaCb2ip9Bm4m5L8EOehgT5d9M4uNB90zg==", + "dev": true, + "requires": { + "@types/events": "*", + "@types/node": "*" + } }, "@types/jasmine": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-2.8.5.tgz", - "integrity": "sha512-mkrHFZTgOXkZhau36K628iKFkjbp11t/bHCkY4Mefu4R6McMg2FD9P3naBv/0Ygyn4sz8baColJp2gdmSekgiw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.3.1.tgz", + "integrity": "sha512-JnKB+cEIFuQZXizZP6N0zxma+JlvowkjefWuL61otVmXN7Ebbs4ka3IbDVIz1pc+TCiT00q925jANz3gQJ9qXw==", "dev": true }, - "@types/minimatch": { - "version": "2.0.29", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-2.0.29.tgz", - "integrity": "sha1-UALhT3Xi1x5WQoHfBDHIwbSio2o=", + "@types/loglevel": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@types/loglevel/-/loglevel-1.5.3.tgz", + "integrity": "sha512-TzzIZihV+y9kxSg5xJMkyIkaoGkXi50isZTtGHObNHRqAAwjGNjSCNPI7AUAv0tZUKTq9f2cdkCUd/2JVZUTrA==", "dev": true }, - "@types/minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=", + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", "dev": true }, "@types/node": { - "version": "7.0.52", - "resolved": "https://registry.npmjs.org/@types/node/-/node-7.0.52.tgz", - "integrity": "sha512-jjpyQsKGsOF/wUElNjfPULk+d8PKvJOIXk3IUeBYYmNCy5dMWfrI+JiixYNw8ppKOlcRwWTXFl0B+i5oGrf95Q==", - "dev": true - }, - "@types/q": { - "version": "0.0.32", - "resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz", - "integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU=", + "version": "10.12.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.12.tgz", + "integrity": "sha512-Pr+6JRiKkfsFvmU/LK68oBRCQeEg36TyAbPhc2xpez24OOZZCuoIhWGTd39VZy6nGafSbxzGouFPTFD/rR1A0A==", "dev": true }, "@types/request": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/request/-/request-0.0.39.tgz", - "integrity": "sha1-FouWz0JTxdVNQD90b4Lueu1Hziw=", + "version": "2.48.1", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.1.tgz", + "integrity": "sha512-ZgEZ1TiD+KGA9LiAAPPJL68Id2UWfeSO62ijSXZjFJArVV+2pKcsVHmrcu+1oiE3q6eDGiFiSolRc4JHoerBBg==", "dev": true, "requires": { + "@types/caseless": "*", "@types/form-data": "*", - "@types/node": "*" + "@types/node": "*", + "@types/tough-cookie": "*" } }, "@types/rimraf": { - "version": "0.0.28", - "resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-0.0.28.tgz", - "integrity": "sha1-VWJRm8eWPKyoq/fxKMrjtZTUHQY=", - "dev": true - }, - "@types/selenium-webdriver": { - "version": "2.53.43", - "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-2.53.43.tgz", - "integrity": "sha512-UBYHWph6P3tutkbXpW6XYg9ZPbTKjw/YC2hGG1/GEvWwTbvezBUv3h+mmUFw79T3RFPnmedpiXdOBbXX+4l0jg==", - "dev": true - }, - "@types/semver": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-5.4.0.tgz", - "integrity": "sha512-PBHCvO98hNec9A491vBbh0ZNDOVxccwKL1u2pm6fs9oDgm7SEnw0lEHqHfjsYryDxnE3zaf7LvERWEXjOp1hig==", - "dev": true - }, - "@types/xml2js": { - "version": "0.0.32", - "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.0.32.tgz", - "integrity": "sha1-n36YzwrcyghaNc3q0DGbWsGcn9k=", - "dev": true - }, - "adm-zip": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz", - "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==" - }, - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" - } - }, - "ansi-colors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", - "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", - "dev": true, - "requires": { - "ansi-wrap": "^0.1.0" - } - }, - "ansi-gray": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", - "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-2.0.2.tgz", + "integrity": "sha512-Hm/bnWq0TCy7jmjeN5bKYij9vw5GrDFWME4IuxV08278NtU/VdGbzsBohcCUJ7+QMqmUq5hpRKB39HeQWJjztQ==", "dev": true, "requires": { - "ansi-wrap": "0.1.0" + "@types/glob": "*", + "@types/node": "*" } }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" - }, - "ansi-wrap": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", - "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", + "@types/semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==", "dev": true }, - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "@types/tar": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/tar/-/tar-4.0.0.tgz", + "integrity": "sha512-YybbEHNngcHlIWVCYsoj7Oo1JU9JqONuAlt1LlTH/lmL8BMhbzdFUgReY87a05rY1j8mfK47Del+TCkaLAXwLw==", "dev": true, "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" + "@types/node": "*" } }, - "append-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", - "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", + "@types/tough-cookie": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.4.tgz", + "integrity": "sha512-Set5ZdrAaKI/qHdFlVMgm/GsAv/wkXhSTuZFkJ+JI7HK+wIkIlOaUXSXieIvJ0+OvGIqtREFoE+NHJtEq0gtEw==", + "dev": true + }, + "@types/xml2js": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.3.tgz", + "integrity": "sha512-Pv2HGRE4gWLs31In7nsyXEH4uVVsd0HNV9i2dyASvtDIlOtSTr1eczPLDpdEuyv5LWH5LT20GIXwPjkshKWI1g==", "dev": true, "requires": { - "buffer-equal": "^1.0.0" + "@types/events": "*", + "@types/node": "*" } }, - "archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "@types/yargs": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-12.0.1.tgz", + "integrity": "sha512-UVjo2oH79aRNcsDlFlnQ/iJ67Jd7j6uSg7jUJP/RZ/nUjAh5ElmnwlD5K/6eGgETJUgCHkiWn91B8JjXQ6ubAw==", "dev": true }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true + "adm-zip": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.13.tgz", + "integrity": "sha512-fERNJX8sOXfel6qCBCMPvZLzENBEhZTzKqg6vrOW5pvoEaQuJhRU4ndTAh6lHOxn1I6jnz2NHra56ZODM751uw==" }, - "arr-filter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", - "integrity": "sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4=", - "dev": true, + "ajv": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.1.tgz", + "integrity": "sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww==", "requires": { - "make-iterator": "^1.0.0" + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "arr-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", - "integrity": "sha1-Onc0X/wc814qkYJWAfnljy4kysQ=", + "ansi-align": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", + "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", "dev": true, "requires": { - "make-iterator": "^1.0.0" + "string-width": "^2.0.0" } }, - "arr-union": { + "ansi-escapes": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true - }, - "array-differ": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", - "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", + "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", "dev": true }, - "array-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", - "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", - "dev": true - }, - "array-initial": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", - "integrity": "sha1-L6dLJnOTccOUe9enrcc74zSz15U=", - "dev": true, - "requires": { - "array-slice": "^1.0.0", - "is-number": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - } - } - }, - "array-last": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", - "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", - "dev": true, - "requires": { - "is-number": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - } - } + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" }, - "array-slice": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", "dev": true }, - "array-sort": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", - "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "requires": { - "default-compare": "^1.0.0", - "get-value": "^2.0.6", - "kind-of": "^5.0.2" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } + "sprintf-js": "~1.0.2" } }, - "array-union": { + "array-find-index": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "requires": { - "array-uniq": "^1.0.1" - } - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", "dev": true }, "arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true }, "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } }, "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true - }, "async": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", "dev": true }, - "async-done": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.1.tgz", - "integrity": "sha512-R1BaUeJ4PMoLNJuk+0tLJgjmEqVsdN118+Z8O+alhnQDQgy0kmD5Mqi0DNEmMx2LM0Ed5yekKu+ZXYvIHceicg==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.2", - "process-nextick-args": "^1.0.7", - "stream-exhaust": "^1.0.1" - } - }, - "async-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", - "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", - "dev": true - }, - "async-settle": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", - "integrity": "sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=", - "dev": true, - "requires": { - "async-done": "^1.2.2" - } - }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, - "atob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.1.tgz", - "integrity": "sha1-ri1acpR38onWDdf5amMUoi3Wwio=", - "dev": true - }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", - "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==" - }, - "bach": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", - "integrity": "sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA=", - "dev": true, - "requires": { - "arr-filter": "^1.1.1", - "arr-flatten": "^1.0.1", - "arr-map": "^2.0.0", - "array-each": "^1.0.0", - "array-initial": "^1.0.0", - "array-last": "^1.1.1", - "async-done": "^1.2.2", - "async-settle": "^1.0.0", - "now-and-later": "^2.0.0" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", "dev": true, "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" }, "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { - "kind-of": "^6.0.0" + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" } }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "ansi-regex": "^2.0.0" } } } }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "optional": true, "requires": { "tweetnacl": "^0.14.3" } }, - "beeper": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", - "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=", - "dev": true - }, - "binary-extensions": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", - "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", - "dev": true - }, - "brace-expansion": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", - "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "boxen": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", + "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", "dev": true, "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" + "ansi-align": "^2.0.0", + "camelcase": "^4.0.0", + "chalk": "^2.0.1", + "cli-boxes": "^1.0.0", + "string-width": "^2.0.0", + "term-size": "^1.2.0", + "widest-line": "^2.0.0" }, "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true } } }, - "buffer-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", - "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", - "dev": true - }, - "buffer-from": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.0.tgz", - "integrity": "sha512-c5mRlguI/Pe2dSZmpER62rSCu0ryKmWddzRYsuXc50U2/g8jMOulc31VZMa4mYx31U5xsmSOpDCgH88Vl9cDGQ==", - "dev": true + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } }, "builtin-modules": { "version": "1.1.1", @@ -546,27 +329,34 @@ "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", "dev": true }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "camelcase": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", + "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==" + }, + "camelcase-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", + "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", "dev": true, "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" + "camelcase": "^4.1.0", + "map-obj": "^2.0.0", + "quick-lru": "^1.0.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + } } }, - "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "capture-stack-trace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", + "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==", "dev": true }, "caseless": { @@ -575,42 +365,57 @@ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "chokidar": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", - "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.0", - "braces": "^2.3.0", - "fsevents": "^1.2.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.1", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "lodash.debounce": "^4.0.8", - "normalize-path": "^2.1.1", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.0.0", - "upath": "^1.0.5" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "chownr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==" + }, + "ci-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", + "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", + "dev": true + }, "clang-format": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/clang-format/-/clang-format-1.2.2.tgz", - "integrity": "sha512-6X9u1JBMak/9VbC0IZajEDvp19/PbjCanbRO3Z2xsluypQtbPPAGDvGGovLOWoUpXIvJH9vJExmzlqWvwItZxA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/clang-format/-/clang-format-1.2.4.tgz", + "integrity": "sha512-sw+nrGUp3hvmANd1qF8vZPuezSYQAiXgGBiEtkXTtJnnu6b00fCqkkDIsnRKrNgg4nv6NYZE92ejvOMIXZoejw==", "dev": true, "requires": { "async": "^1.5.2", @@ -618,248 +423,89 @@ "resolve": "^1.1.6" } }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } + "cli-boxes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", + "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=", + "dev": true }, - "cli-color": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.2.0.tgz", - "integrity": "sha1-OlrnT9drYmevZm5p4q+70B3vNNE=", + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", "dev": true, "requires": { - "ansi-regex": "^2.1.1", - "d": "1", - "es5-ext": "^0.10.12", - "es6-iterator": "2", - "memoizee": "^0.4.3", - "timers-ext": "0.1" + "restore-cursor": "^2.0.0" } }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "dev": true, + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", "wrap-ansi": "^2.0.0" } }, - "clone": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.3.tgz", - "integrity": "sha1-KY1+IjFmD0DAA8LtMUDezz9TCF8=", - "dev": true - }, - "clone-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", - "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", - "dev": true - }, - "clone-stats": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", - "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", - "dev": true - }, - "cloneable-readable": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.2.tgz", - "integrity": "sha512-Bq6+4t+lbM8vhTs/Bef5c5AdEMtapp/iFb6+s4/Hh9MVTt8OLKH7ZOOZSCT+Ys7hsHvqv0GuMPJ1lnQJVHvxpg==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "process-nextick-args": "^2.0.0", - "readable-stream": "^2.3.5" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" - }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true - }, - "collection-map": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", - "integrity": "sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw=", - "dev": true, - "requires": { - "arr-map": "^2.0.2", - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - } + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "color-convert": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", + "integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==", "dev": true, "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" + "color-name": "1.1.1" } }, - "color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=", "dev": true }, "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", "requires": { "delayed-stream": "~1.0.0" } }, - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", "dev": true }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "convert-source-map": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", - "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true - }, - "copy-props": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.4.tgz", - "integrity": "sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A==", + "configstore": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", + "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==", "dev": true, "requires": { - "each-props": "^1.3.0", - "is-plain-object": "^2.0.1" + "dot-prop": "^4.1.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" } }, "core-util-is": { @@ -867,13 +513,40 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, - "d": { + "create-error-class": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", + "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", + "dev": true, + "requires": { + "capture-stack-trace": "^1.0.0" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "crypto-random-string": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", - "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", + "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", + "dev": true + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", "dev": true, "requires": { - "es5-ext": "^0.10.9" + "array-find-index": "^1.0.1" } }, "dashdash": { @@ -884,16 +557,10 @@ "assert-plus": "^1.0.0" } }, - "dateformat": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", - "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=", - "dev": true - }, "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { "ms": "2.0.0" @@ -902,1598 +569,358 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true - }, - "default-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", - "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", + "decamelize-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", + "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", "dev": true, "requires": { - "kind-of": "^5.0.2" + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" }, "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", "dev": true } } }, - "default-resolution": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", - "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=", + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true }, - "define-properties": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", - "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "dot-prop": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", + "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", "dev": true, "requires": { - "foreach": "^2.0.5", - "object-keys": "^1.0.8" + "is-obj": "^1.0.0" } }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" } }, - "del": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", - "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, "requires": { - "globby": "^5.0.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "rimraf": "^2.2.8" + "is-arrayish": "^0.2.1" } }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true }, - "detect-file": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", - "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, - "diff": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/diff/-/diff-2.2.3.tgz", - "integrity": "sha1-YOr9DSjukG5Oj/ClLBIpUhAzv5k=", + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", "dev": true }, - "duplexer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", - "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", + "eventemitter3": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz", + "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==", "dev": true }, - "duplexer2": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", - "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", - "dev": true, + "execa": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", + "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", "requires": { - "readable-stream": "~1.1.9" + "cross-spawn": "^6.0.0", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" } }, - "duplexify": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.0.tgz", - "integrity": "sha512-fO3Di4tBKJpYTFHAxTU00BcfWMY9w24r/x21a6rZRbsD/ToUgGxsMbiGRmB7uVAXeGKXD9MwiLZa5E97EVgIRQ==", + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "external-editor": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", "dev": true, "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" } }, - "each-props": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", - "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", "dev": true, "requires": { - "is-plain-object": "^2.0.1", - "object.defaults": "^1.1.0" + "escape-string-regexp": "^1.0.5" } }, - "ecc-jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", - "optional": true, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "requires": { - "jsbn": "~0.1.0" + "locate-path": "^3.0.0" } }, - "end-of-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "follow-redirects": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.2.tgz", + "integrity": "sha512-kssLorP/9acIdpQ2udQVTiCS5LQmdEz9mvdIfDcl1gYX2tPKFADHSyFdvJS040XdFsPzemWtgI3q8mFVCxtX8A==", "dev": true, "requires": { - "once": "^1.4.0" + "debug": "^3.1.0" } }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, - "es5-ext": { - "version": "0.10.38", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.38.tgz", - "integrity": "sha512-jCMyePo7AXbUESwbl8Qi01VSH2piY9s/a3rSU/5w/MlTIx8HPL1xn2InGN8ejt/xulcJgnTO7vqNtOAxzYd2Kg==", - "dev": true, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "requires": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.1" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" } }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "dev": true, + "fs-minipass": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" + "minipass": "^2.2.1" } }, - "es6-symbol": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", - "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", - "dev": true, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "requires": { - "d": "1", - "es5-ext": "~0.10.14" + "assert-plus": "^1.0.0" } }, - "es6-weak-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", - "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "dev": true, "requires": { - "d": "1", - "es5-ext": "^0.10.14", - "es6-iterator": "^2.0.1", - "es6-symbol": "^3.1.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "global-dirs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", "dev": true, "requires": { - "d": "1", - "es5-ext": "~0.10.14" + "ini": "^1.3.4" } }, - "event-stream": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", + "got": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", + "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", "dev": true, "requires": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", - "pause-stream": "0.0.11", - "split": "0.3", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" + "create-error-class": "^3.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "unzip-response": "^2.0.1", + "url-parse-lax": "^1.0.0" } }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "gts": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/gts/-/gts-0.9.0.tgz", + "integrity": "sha512-Id2Vmg0xNU1FODc0AwmaFA1h0+h6V9/zBqu4NfT8FucVOVEP7pyJ16btyHfSH/UdzTCXjV1fq+fNBEgx/50EaA==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "clang-format": "1.2.3", + "diff": "^3.5.0", + "entities": "^1.1.1", + "inquirer": "^6.0.0", + "meow": "^5.0.0", + "pify": "^4.0.0", + "rimraf": "^2.6.2", + "tslint": "^5.9.1", + "update-notifier": "^2.5.0", + "write-file-atomic": "^2.3.0" }, "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "clang-format": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/clang-format/-/clang-format-1.2.3.tgz", + "integrity": "sha512-x90Hac4ERacGDcZSvHKK58Ga0STuMD+Doi5g0iG2zf7wlJef5Huvhs/3BvMRFxwRYyYSdl6mpQNrtfMxE8MQzw==", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "async": "^1.5.2", + "glob": "^7.0.0", + "resolve": "^1.1.6" } } } }, - "expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", - "dev": true, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", "requires": { - "homedir-polyfill": "^1.0.1" - } - }, - "extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" - }, - "fancy-log": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.2.tgz", - "integrity": "sha1-9BEl49hPLn2JpD0G2VjI94vha+E=", - "dev": true, - "requires": { - "ansi-gray": "^0.1.1", - "color-support": "^1.1.3", - "time-stamp": "^1.0.0" - } - }, - "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "findup-sync": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", - "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", - "dev": true, - "requires": { - "detect-file": "^1.0.0", - "is-glob": "^3.1.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "^2.1.0" - } - } - } - }, - "fined": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fined/-/fined-1.1.0.tgz", - "integrity": "sha1-s33IRLdqL15wgeiE98CuNE8VNHY=", - "dev": true, - "requires": { - "expand-tilde": "^2.0.2", - "is-plain-object": "^2.0.3", - "object.defaults": "^1.1.0", - "object.pick": "^1.2.0", - "parse-filepath": "^1.0.1" - } - }, - "flagged-respawn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.0.tgz", - "integrity": "sha1-Tnmumy6zi/hrO7Vr8+ClaqX8q9c=", - "dev": true - }, - "flush-write-stream": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", - "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.4" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true - }, - "for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", - "dev": true, - "requires": { - "for-in": "^1.0.1" - } - }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", - "dev": true - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" - }, - "form-data": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", - "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "1.0.6", - "mime-types": "^2.1.12" - } - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "requires": { - "map-cache": "^0.2.2" - } - }, - "from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", - "dev": true - }, - "fs-mkdirp-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", - "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "through2": "^2.0.3" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "fsevents": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", - "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", - "dev": true, - "optional": true, - "requires": { - "nan": "^2.12.1", - "node-pre-gyp": "^0.12.0" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "debug": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "dev": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true, - "dev": true, - "optional": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true, - "dev": true, - "optional": true - }, - "minipass": { - "version": "2.3.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.2.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "needle": { - "version": "2.3.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "debug": "^4.1.0", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.12.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "optional": true - }, - "npm-packlist": { - "version": "1.4.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "rc": { - "version": "1.2.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.6.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "dev": true, - "optional": true - }, - "semver": { - "version": "5.7.0", - "bundled": true, - "dev": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "tar": { - "version": "4.4.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.3.4", - "minizlib": "^1.1.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.2" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "wide-align": { - "version": "1.1.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "yallist": { - "version": "3.0.3", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "get-caller-file": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", - "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", - "dev": true - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dev": true, - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "^2.1.0" - } - } - } - }, - "glob-stream": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", - "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", - "dev": true, - "requires": { - "extend": "^3.0.0", - "glob": "^7.1.1", - "glob-parent": "^3.1.0", - "is-negated-glob": "^1.0.0", - "ordered-read-streams": "^1.0.0", - "pumpify": "^1.3.5", - "readable-stream": "^2.1.5", - "remove-trailing-separator": "^1.0.1", - "to-absolute-glob": "^2.0.0", - "unique-stream": "^2.0.2" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "glob-watcher": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.1.tgz", - "integrity": "sha512-fK92r2COMC199WCyGUblrZKhjra3cyVMDiypDdqg1vsSDmexnbYivK1kNR4QItiNXLKmGlqan469ks67RtNa2g==", - "dev": true, - "requires": { - "async-done": "^1.2.0", - "chokidar": "^2.0.0", - "just-debounce": "^1.0.0", - "object.defaults": "^1.1.0" - } - }, - "global-modules": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", - "dev": true, - "requires": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" - } - }, - "global-prefix": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", - "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", - "dev": true, - "requires": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" - } - }, - "globby": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", - "requires": { - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "glogg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.1.tgz", - "integrity": "sha512-ynYqXLoluBKf9XGR1gA59yEJisIL7YHEH4xr3ZziHB5/yl4qWfaK8Js9jGe6gBGCSCKVqiyO30WnRZADvemUNw==", - "dev": true, - "requires": { - "sparkles": "^1.0.0" - } - }, - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "dev": true - }, - "gulp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.0.tgz", - "integrity": "sha1-lXZsYB2t5Kd+0+eyttwDiBtZY2Y=", - "dev": true, - "requires": { - "glob-watcher": "^5.0.0", - "gulp-cli": "^2.0.0", - "undertaker": "^1.0.0", - "vinyl-fs": "^3.0.0" - }, - "dependencies": { - "gulp-cli": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.0.1.tgz", - "integrity": "sha512-RxujJJdN8/O6IW2nPugl7YazhmrIEjmiVfPKrWt68r71UCaLKS71Hp0gpKT+F6qOUFtr7KqtifDKaAJPRVvMYQ==", - "dev": true, - "requires": { - "ansi-colors": "^1.0.1", - "archy": "^1.0.0", - "array-sort": "^1.0.0", - "color-support": "^1.1.3", - "concat-stream": "^1.6.0", - "copy-props": "^2.0.1", - "fancy-log": "^1.3.2", - "gulplog": "^1.0.0", - "interpret": "^1.1.0", - "isobject": "^3.0.1", - "liftoff": "^2.5.0", - "matchdep": "^2.0.0", - "mute-stdout": "^1.0.0", - "pretty-hrtime": "^1.0.0", - "replace-homedir": "^1.0.0", - "semver-greatest-satisfied-range": "^1.1.0", - "v8flags": "^3.0.1", - "yargs": "^7.1.0" - } - } - } - }, - "gulp-clang-format": { - "version": "1.0.25", - "resolved": "https://registry.npmjs.org/gulp-clang-format/-/gulp-clang-format-1.0.25.tgz", - "integrity": "sha512-YSYk3st/ktKrBWfnDSutYZU9pLnCdaeJBTT8YguTJJLkQjSFZjBCBquecSXfoWW+NcVfGuei3N7vs7xuSR+2bg==", - "dev": true, - "requires": { - "clang-format": "^1.0.32", - "gulp-diff": "^1.0.0", - "gulp-util": "^3.0.4", - "pkginfo": "^0.3.0", - "stream-combiner2": "^1.1.1", - "stream-equal": "0.1.6", - "through2": "^0.6.3" - }, - "dependencies": { - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "through2": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", - "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", - "dev": true, - "requires": { - "readable-stream": ">=1.0.33-1 <1.1.0-0", - "xtend": ">=4.0.0 <4.1.0-0" - } - } - } - }, - "gulp-diff": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gulp-diff/-/gulp-diff-1.0.0.tgz", - "integrity": "sha1-EBsjcS3WsQe9B9BauI6jrEhf7Xc=", - "dev": true, - "requires": { - "cli-color": "^1.0.0", - "diff": "^2.0.2", - "event-stream": "^3.1.5", - "gulp-util": "^3.0.6", - "through2": "^2.0.0" - } - }, - "gulp-util": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", - "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", - "dev": true, - "requires": { - "array-differ": "^1.0.0", - "array-uniq": "^1.0.2", - "beeper": "^1.0.0", - "chalk": "^1.0.0", - "dateformat": "^2.0.0", - "fancy-log": "^1.1.0", - "gulplog": "^1.0.0", - "has-gulplog": "^0.1.0", - "lodash._reescape": "^3.0.0", - "lodash._reevaluate": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.template": "^3.0.0", - "minimist": "^1.1.0", - "multipipe": "^0.1.2", - "object-assign": "^3.0.0", - "replace-ext": "0.0.1", - "through2": "^2.0.0", - "vinyl": "^0.5.0" - }, - "dependencies": { - "object-assign": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", - "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", - "dev": true - } - } - }, - "gulplog": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", - "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", - "dev": true, - "requires": { - "glogg": "^1.0.0" - } - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" - }, - "har-validator": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", - "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", - "requires": { - "ajv": "^5.1.0", - "har-schema": "^2.0.0" + "ajv": "^6.5.5", + "har-schema": "^2.0.0" } }, "has-ansi": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "has-gulplog": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", - "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", - "dev": true, - "requires": { - "sparkles": "^1.0.0" - } - }, - "has-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", - "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", - "dev": true - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", "dev": true, "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" + "ansi-regex": "^2.0.0" }, "dependencies": { - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true } } }, - "homedir-polyfill": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz", - "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=", - "dev": true, - "requires": { - "parse-passwd": "^1.0.0" - } + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true }, "hosted-git-info": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.1.tgz", - "integrity": "sha512-Ba4+0M4YvIDUUsprMjhVTU1yN9F2/LJSAl69ZpzaLT4l4j5mwTS6jqqW9Ojvj6lKz/veqPzpJBqGbXspOb533A==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", "dev": true }, + "http-proxy": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", + "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", + "dev": true, + "requires": { + "eventemitter3": "^3.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -2504,10 +931,38 @@ "sshpk": "^1.7.0" } }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -2516,76 +971,64 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true }, "ini": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" - }, - "interpret": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", - "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", - "dev": true - }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true - }, - "is-absolute": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", - "dev": true, - "requires": { - "is-relative": "^1.0.0", - "is-windows": "^1.0.1" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, + "inquirer": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.1.tgz", + "integrity": "sha512-088kl3DRT2dLU5riVMKKr1DlImd6X7smDhpXUCkJDCKvTEJeRiXh0G132HG9u5a+6Ylw9plFRY7RuTnwohYSpg==", + "dev": true, + "requires": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.0", + "figures": "^2.0.0", + "lodash": "^4.17.10", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.1.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.0.0", + "through": "^2.3.6" }, "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "ansi-regex": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", + "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", + "dev": true + }, + "strip-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.0.0.tgz", + "integrity": "sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "ansi-regex": "^4.0.0" } } } }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==" + }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dev": true, - "requires": { - "binary-extensions": "^1.0.0" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, "is-builtin-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", @@ -2595,130 +1038,56 @@ "builtin-modules": "^1.0.0" } }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "is-ci": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", + "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", "dev": true, "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } + "ci-info": "^1.5.0" } }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, - "is-glob": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", - "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "is-installed-globally": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", + "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", "dev": true, "requires": { - "is-extglob": "^2.1.1" + "global-dirs": "^0.1.0", + "is-path-inside": "^1.0.0" } }, - "is-negated-glob": { + "is-npm": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", - "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", + "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=", "dev": true }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-path-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=" - }, - "is-path-in-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", - "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", - "requires": { - "is-path-inside": "^1.0.0" - } + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "dev": true }, "is-path-inside": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, "requires": { "path-is-inside": "^1.0.1" } }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true }, "is-promise": { "version": "2.1.0", @@ -2726,64 +1095,32 @@ "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", "dev": true }, - "is-relative": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", - "dev": true, - "requires": { - "is-unc-path": "^1.0.0" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "is-unc-path": { + "is-redirect": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", - "dev": true, - "requires": { - "unc-path-regex": "^0.1.2" - } - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", "dev": true }, - "is-valid-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", - "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", + "is-retry-allowed": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", + "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=", "dev": true }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isstream": { "version": "0.1.2", @@ -2791,27 +1128,47 @@ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "jasmine": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.9.0.tgz", - "integrity": "sha1-dlcfklyHg0CefGFTVy5aY0HPk+s=", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.3.1.tgz", + "integrity": "sha512-/vU3/H7U56XsxIXHwgEuWpCgQ0bRi2iiZeUpx7Nqo8n1TpoDHfZhkPIc7CO8I4pnMzYsi3XaSZEiy8cnTfujng==", "dev": true, "requires": { - "exit": "^0.1.2", "glob": "^7.0.6", - "jasmine-core": "~2.9.0" + "jasmine-core": "~3.3.0" } }, "jasmine-core": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.9.1.tgz", - "integrity": "sha1-trvB2OZSUNVvWIhGFwXr7uuI8i8=", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.3.0.tgz", + "integrity": "sha512-3/xSmG/d35hf80BEN66Y6g9Ca5l/Isdeg/j6zvbTYlTzeKinzmaTM4p9am5kYqOmE05D7s1t8FGjzdSnbUbceA==", + "dev": true + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", "dev": true }, + "js-yaml": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "optional": true + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true }, "json-schema": { "version": "0.2.3", @@ -2819,30 +1176,15 @@ "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" }, "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" - }, - "json-stable-stringify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "dev": true, - "requires": { - "jsonify": "~0.0.0" - } + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", - "dev": true - }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -2850,439 +1192,260 @@ "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "just-debounce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz", - "integrity": "sha1-h/zPrv/AtozRnVX2cilD+SnqNeo=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - }, - "last-run": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", - "integrity": "sha1-RblpQsF7HHnHchmCWbqUO+v4yls=", - "dev": true, - "requires": { - "default-resolution": "^2.0.0", - "es6-weak-map": "^2.0.1" - } - }, - "lazystream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", - "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", - "dev": true, - "requires": { - "readable-stream": "^2.0.5" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dev": true, - "requires": { - "invert-kv": "^1.0.0" - } - }, - "lead": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", - "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", - "dev": true, - "requires": { - "flush-write-stream": "^1.0.2" - } - }, - "liftoff": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-2.5.0.tgz", - "integrity": "sha1-IAkpG7Mc6oYbvxCnwVooyvdcMew=", - "dev": true, - "requires": { - "extend": "^3.0.0", - "findup-sync": "^2.0.0", - "fined": "^1.0.1", - "flagged-respawn": "^1.0.0", - "is-plain-object": "^2.0.4", - "object.map": "^1.0.0", - "rechoir": "^0.6.2", - "resolve": "^1.1.7" - } - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - } - }, - "lodash._basecopy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", - "dev": true - }, - "lodash._basetostring": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", - "integrity": "sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=", - "dev": true - }, - "lodash._basevalues": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", - "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=", - "dev": true - }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", - "dev": true - }, - "lodash._isiterateecall": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", - "dev": true - }, - "lodash._reescape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", - "integrity": "sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=", - "dev": true - }, - "lodash._reevaluate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", - "integrity": "sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=", - "dev": true - }, - "lodash._reinterpolate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", - "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", - "dev": true - }, - "lodash._root": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", - "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", - "dev": true - }, - "lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", - "dev": true - }, - "lodash.escape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", - "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", - "dev": true, - "requires": { - "lodash._root": "^3.0.0" + "json-schema": "0.2.3", + "verror": "1.10.0" } }, - "lodash.isarguments": { + "latest-version": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", - "dev": true - }, - "lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", - "dev": true - }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", + "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", "dev": true, "requires": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" + "package-json": "^4.0.0" } }, - "lodash.restparam": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", - "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", - "dev": true - }, - "lodash.template": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", - "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", - "dev": true, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", "requires": { - "lodash._basecopy": "^3.0.0", - "lodash._basetostring": "^3.0.0", - "lodash._basevalues": "^3.0.0", - "lodash._isiterateecall": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0", - "lodash.keys": "^3.0.0", - "lodash.restparam": "^3.0.0", - "lodash.templatesettings": "^3.0.0" + "invert-kv": "^2.0.0" } }, - "lodash.templatesettings": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", - "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", "dev": true, "requires": { - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0" + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } } }, - "lru-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", - "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", - "dev": true, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "requires": { - "es5-ext": "~0.10.2" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" } }, - "make-iterator": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", - "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "loglevel": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz", + "integrity": "sha1-4PyVEztu8nbNyIh82vJKpvFW+Po=" + }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", "dev": true, "requires": { - "kind-of": "^6.0.2" + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" } }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true - }, - "map-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", - "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", "dev": true }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", "dev": true, "requires": { - "object-visit": "^1.0.0" + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + }, + "dependencies": { + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + } } }, - "matchdep": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", - "integrity": "sha1-xvNINKDY28OzfCfui7yyfHd1WC4=", + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", "dev": true, "requires": { - "findup-sync": "^2.0.0", - "micromatch": "^3.0.4", - "resolve": "^1.4.0", - "stack-trace": "0.0.10" + "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } } }, - "memoizee": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.11.tgz", - "integrity": "sha1-vemBdmPJ5A/bKk6hw2cpYIeujI8=", - "dev": true, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", "requires": { - "d": "1", - "es5-ext": "^0.10.30", - "es6-weak-map": "^2.0.2", - "event-emitter": "^0.3.5", - "is-promise": "^2.1", - "lru-queue": "0.1", - "next-tick": "1", - "timers-ext": "^0.1.2" + "p-defer": "^1.0.0" } }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, + "map-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", + "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=", + "dev": true + }, + "mem": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.0.0.tgz", + "integrity": "sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA==", "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^1.0.0", + "p-is-promise": "^1.1.0" + } + }, + "meow": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-5.0.0.tgz", + "integrity": "sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig==", + "dev": true, + "requires": { + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", + "read-pkg-up": "^3.0.0", + "redent": "^2.0.0", + "trim-newlines": "^2.0.0", + "yargs-parser": "^10.0.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "yargs-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", + "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } + } } }, "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" }, "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "version": "2.1.21", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", + "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", "requires": { - "mime-db": "~1.33.0" + "mime-db": "~1.37.0" } }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, - "mixin-deep": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", - "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "minimist-options": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", + "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", "dev": true, "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0" } }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "multipipe": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", - "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=", - "dev": true, + "minipass": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "requires": { - "duplexer2": "0.0.2" + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" } }, - "mute-stdout": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.0.tgz", - "integrity": "sha1-WzLqB+tDyd7WEwQ0z5JvRrKn/U0=", - "dev": true - }, - "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", - "dev": true, - "optional": true + "minizlib": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", + "requires": { + "minipass": "^2.2.1" + } }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "minimist": "0.0.8" } }, - "next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", "dev": true }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + }, "normalize-package-data": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", @@ -3295,201 +1458,50 @@ "validate-npm-package-license": "^3.0.1" } }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - }, - "now-and-later": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.0.tgz", - "integrity": "sha1-vGHLtFbXnLMiB85HygUTb/Ln1u4=", - "dev": true, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "requires": { - "once": "^1.3.2" + "path-key": "^2.0.0" } }, "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "object-keys": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", - "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", - "dev": true - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "requires": { - "isobject": "^3.0.0" - } - }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, - "object.defaults": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", - "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", - "dev": true, - "requires": { - "array-each": "^1.0.1", - "array-slice": "^1.0.0", - "for-own": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "object.map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", - "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", - "dev": true, - "requires": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "object.reduce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.reduce/-/object.reduce-1.0.1.tgz", - "integrity": "sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60=", - "dev": true, - "requires": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - } + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, "requires": { "wrappy": "1" } }, - "ordered-read-streams": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", - "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", "dev": true, "requires": { - "readable-stream": "^2.0.1" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } + "mimic-fn": "^1.0.0" } }, "os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", - "dev": true, + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.0.1.tgz", + "integrity": "sha512-7g5e7dmXPtzcP4bgsZ8ixDVqA7oWYuEz4lOSujeWyliPai4gfVDiFIcwBg3aGCPnmSGfzOKTK3ccPn0CKv3DBw==", "requires": { - "lcid": "^1.0.0" + "execa": "^0.10.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" } }, "os-tmpdir": { @@ -3498,102 +1510,107 @@ "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, - "parse-filepath": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", - "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", - "dev": true, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=" + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "p-is-promise": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", + "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=" + }, + "p-limit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", + "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", "requires": { - "is-absolute": "^1.0.0", - "map-cache": "^0.2.0", - "path-root": "^0.1.1" + "p-try": "^2.0.0" } }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "requires": { - "error-ex": "^1.2.0" + "p-limit": "^2.0.0" } }, - "parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", - "dev": true - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==" }, - "path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "dev": true + "package-json": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", + "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", + "dev": true, + "requires": { + "got": "^6.7.1", + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" + } }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", "dev": true, "requires": { - "pinkie-promise": "^2.0.0" + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" } }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true }, "path-is-inside": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" - }, - "path-parse": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", "dev": true }, - "path-root": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", - "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", - "dev": true, - "requires": { - "path-root-regex": "^0.1.0" - } + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" }, - "path-root-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", - "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "pause-stream": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", - "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", "dev": true, "requires": { - "through": "~2.3" + "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } } }, "performance-now": { @@ -3602,352 +1619,253 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "requires": { - "pinkie": "^2.0.0" - } - }, - "pkginfo": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.3.1.tgz", - "integrity": "sha1-Wyn2qB9wcXFC4J52W76rl7T4HiE=", - "dev": true - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, - "pretty-hrtime": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true }, - "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", "dev": true }, - "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true }, - "pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "dev": true, - "requires": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - } + "psl": { + "version": "1.1.29", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", + "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==" }, "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - }, - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, - "read-pkg": { + "quick-lru": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "dev": true, - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - } + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", + "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=", + "dev": true }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } } }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" } }, - "readdirp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", - "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", + "read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "minimatch": "^3.0.2", - "readable-stream": "^2.0.2", - "set-immediate-shim": "^1.0.1" + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" }, "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } }, - "process-nextick-args": { + "locate-path": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "p-try": "^1.0.0" } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "p-limit": "^1.1.0" } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true } } }, - "rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", - "dev": true, - "requires": { - "resolve": "^1.1.6" - } - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, - "remove-bom-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", - "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", + "redent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", + "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", "dev": true, "requires": { - "is-buffer": "^1.1.5", - "is-utf8": "^0.2.1" + "indent-string": "^3.0.0", + "strip-indent": "^2.0.0" } }, - "remove-bom-stream": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", - "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", + "registry-auth-token": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", + "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", "dev": true, "requires": { - "remove-bom-buffer": "^3.0.0", - "safe-buffer": "^5.1.0", - "through2": "^2.0.3" + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" } }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "repeat-element": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", - "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, - "replace-ext": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", - "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", - "dev": true - }, - "replace-homedir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-1.0.0.tgz", - "integrity": "sha1-6H9tUTuSjd6AgmDBK+f+xv9ueYw=", + "registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", "dev": true, "requires": { - "homedir-polyfill": "^1.0.1", - "is-absolute": "^1.0.0", - "remove-trailing-separator": "^1.1.0" + "rc": "^1.0.1" } }, "request": { - "version": "2.87.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", - "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", "requires": { "aws-sign2": "~0.7.0", - "aws4": "^1.6.0", + "aws4": "^1.8.0", "caseless": "~0.12.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.1", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", "forever-agent": "~0.6.1", - "form-data": "~2.3.1", - "har-validator": "~5.0.3", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.17", - "oauth-sign": "~0.8.2", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", "performance-now": "^2.1.0", - "qs": "~6.5.1", - "safe-buffer": "^5.1.1", - "tough-cookie": "~2.3.3", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", "tunnel-agent": "^0.6.0", - "uuid": "^3.1.0" + "uuid": "^3.3.2" } }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, "require-main-filename": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", "dev": true }, "resolve": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", - "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", + "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", "dev": true, "requires": { "path-parse": "^1.0.5" } }, - "resolve-dir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", - "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", - "dev": true, - "requires": { - "expand-tilde": "^2.0.0", - "global-modules": "^1.0.0" - } - }, - "resolve-options": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", - "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", "dev": true, "requires": { - "value-or-function": "^3.0.0" + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" } }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true - }, "rimraf": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, "requires": { "glob": "^7.0.5" } }, - "run-sequence": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/run-sequence/-/run-sequence-1.2.2.tgz", - "integrity": "sha1-UJWgvr6YczsBQL0I3YDsAw3azes=", + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", "dev": true, "requires": { - "chalk": "*", - "gulp-util": "*" + "is-promise": "^2.1.0" } }, - "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "rxjs": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", + "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", "dev": true, "requires": { - "ret": "~0.1.10" + "tslib": "^1.9.0" } }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -3958,217 +1876,47 @@ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, - "selenium-webdriver": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.0.1.tgz", - "integrity": "sha1-ot6l2kqX9mcuiefKcnbO+jZRR6c=", - "dev": true, - "requires": { - "adm-zip": "^0.4.7", - "rimraf": "^2.5.4", - "tmp": "0.0.30", - "xml2js": "^0.4.17" - }, - "dependencies": { - "adm-zip": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", - "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", - "dev": true - } - } - }, "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" }, - "semver-greatest-satisfied-range": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", - "integrity": "sha1-E+jCZYq5aRywzXEJMkAoDTb3els=", + "semver-diff": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", + "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", "dev": true, "requires": { - "sver-compat": "^1.5.0" + "semver": "^5.0.3" } }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", - "dev": true - }, - "set-value": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", - "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "source-map-resolve": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", - "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", - "dev": true, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "requires": { - "atob": "^2.1.1", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" + "shebang-regex": "^1.0.0" } }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true - }, - "sparkles": { + "shebang-regex": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.0.tgz", - "integrity": "sha1-Gsu/tZJDbRC76PeFt8xvgoFQEsM=", - "dev": true + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "spdx-correct": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", - "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", @@ -4176,9 +1924,9 @@ } }, "spdx-exceptions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", - "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", "dev": true }, "spdx-expression-parse": { @@ -4192,349 +1940,206 @@ } }, "spdx-license-ids": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", - "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", - "dev": true - }, - "split": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", - "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", - "dev": true, - "requires": { - "through": "2" - } - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.0" - } - }, - "sshpk": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", - "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "stack-trace": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", - "dev": true - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "stream-combiner": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", - "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", - "dev": true, - "requires": { - "duplexer": "~0.1.1" - } - }, - "stream-combiner2": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", - "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", - "dev": true, - "requires": { - "duplexer2": "~0.1.0", - "readable-stream": "^2.0.2" - }, - "dependencies": { - "duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", - "dev": true, - "requires": { - "readable-stream": "^2.0.2" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.0.3", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "stream-equal": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/stream-equal/-/stream-equal-0.1.6.tgz", - "integrity": "sha1-zFIvqzhRYBLk1O5HUTsUe3I1kBk=", - "dev": true - }, - "stream-exhaust": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", - "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.2.tgz", + "integrity": "sha512-qky9CVt0lVIECkEsYbNILVnPvycuEBkXoMFLRWsREkomQLevYhtRKC+R91a5TOAQ3bCMjikRwhyaRqj1VYatYg==", "dev": true }, - "stream-shift": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", - "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, + "sshpk": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz", + "integrity": "sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA==", "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" } }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } }, "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "^3.0.0" } }, "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + }, + "strip-indent": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "requires": { - "is-utf8": "^0.2.0" - } + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true }, - "sver-compat": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", - "integrity": "sha1-PPh9/rTQe0o/FIJ7wYaz/QxkXNg=", - "dev": true, + "tar": { + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", + "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", "requires": { - "es6-iterator": "^2.0.1", - "es6-symbol": "^3.1.1" + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" } }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "through2": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", - "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "term-size": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", + "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", "dev": true, "requires": { - "readable-stream": "^2.1.5", - "xtend": "~4.0.1" + "execa": "^0.7.0" }, "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.0.3", - "util-deprecate": "~1.0.1" + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" } }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" } } } }, - "through2-filter": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-2.0.0.tgz", - "integrity": "sha1-YLxVoNrLdghdsfna6Zq0P4PWIuw=", - "dev": true, - "requires": { - "through2": "~2.0.0", - "xtend": "~4.0.0" - } - }, - "time-stamp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", - "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, - "timers-ext": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.2.tgz", - "integrity": "sha1-YcxHp2wavTGV8UUn+XjViulMUgQ=", - "dev": true, - "requires": { - "es5-ext": "~0.10.14", - "next-tick": "1" - } + "timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", + "dev": true }, "tmp": { - "version": "0.0.30", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.30.tgz", - "integrity": "sha1-ckGdSovn1s51FI/YsyTlk6cRwu0=", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.1" - } - }, - "to-absolute-glob": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", - "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, "requires": { - "is-absolute": "^1.0.0", - "is-negated-glob": "^1.0.0" + "os-tmpdir": "~1.0.2" } }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", "requires": { - "kind-of": "^3.0.2" + "psl": "^1.1.24", + "punycode": "^1.4.1" }, "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" } } }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } + "trim-newlines": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", + "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=", + "dev": true }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "dev": true }, - "to-through": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", - "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", + "tslint": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.11.0.tgz", + "integrity": "sha1-mPMMAurjzecAYgHkwzywi0hYHu0=", "dev": true, "requires": { - "through2": "^2.0.3" + "babel-code-frame": "^6.22.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^3.2.0", + "glob": "^7.1.1", + "js-yaml": "^3.7.0", + "minimatch": "^3.0.4", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.27.2" } }, - "tough-cookie": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", - "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, "requires": { - "punycode": "^1.4.1" + "tslib": "^1.8.1" } }, "tunnel-agent": { @@ -4548,198 +2153,79 @@ "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "optional": true - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, "typescript": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.3.4.tgz", - "integrity": "sha1-PTgyGCgjHkNPKHUUlZw3qCtin0I=", - "dev": true - }, - "unc-path-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.2.tgz", + "integrity": "sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg==", "dev": true }, - "undertaker": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.2.0.tgz", - "integrity": "sha1-M52kZGJS0ILcN45wgGcpl1DhG0k=", + "unique-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", + "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", "dev": true, "requires": { - "arr-flatten": "^1.0.1", - "arr-map": "^2.0.0", - "bach": "^1.0.0", - "collection-map": "^1.0.0", - "es6-weak-map": "^2.0.1", - "last-run": "^1.1.0", - "object.defaults": "^1.0.0", - "object.reduce": "^1.0.0", - "undertaker-registry": "^1.0.0" + "crypto-random-string": "^1.0.0" } }, - "undertaker-registry": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.1.tgz", - "integrity": "sha1-XkvaMI5KiirlhPm5pDWaSZglzFA=", + "unzip-response": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", + "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=", "dev": true }, - "union-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", - "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "update-notifier": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz", + "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==", "dev": true, "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^0.4.3" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "set-value": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", - "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.1", - "to-object-path": "^0.3.0" - } - } + "boxen": "^1.2.1", + "chalk": "^2.0.1", + "configstore": "^3.0.0", + "import-lazy": "^2.1.0", + "is-ci": "^1.0.10", + "is-installed-globally": "^0.1.0", + "is-npm": "^1.0.0", + "latest-version": "^3.0.0", + "semver-diff": "^2.0.0", + "xdg-basedir": "^3.0.0" } }, - "unique-stream": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.2.1.tgz", - "integrity": "sha1-WqADz76Uxf+GbE59ZouxxNuts2k=", - "dev": true, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", "requires": { - "json-stable-stringify": "^1.0.0", - "through2-filter": "^2.0.0" + "punycode": "^2.1.0" } }, - "unset-value": { + "url-parse-lax": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - } - } - }, - "upath": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", - "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", - "dev": true - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, - "use": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.0.tgz", - "integrity": "sha512-6UJEQM/L+mzC3ZJNM56Q4DFGLX/evKGRg15UJHGB9X5j5Z3AFbgZvjUh2yq/UJUY4U5dh7Fal++XbNg1uzpRAw==", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", "dev": true, "requires": { - "kind-of": "^6.0.2" + "prepend-http": "^1.0.1" } }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, "uuid": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" }, - "v8flags": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.1.tgz", - "integrity": "sha512-iw/1ViSEaff8NJ3HLyEjawk/8hjJib3E7pvG4pddVXfUg1983s3VGsiClDjhK64MQVDGqc1Q8r18S4VKQZS9EQ==", - "dev": true, - "requires": { - "homedir-polyfill": "^1.0.1" - } - }, "validate-npm-package-license": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", - "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, "requires": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, - "value-or-function": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", - "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=", - "dev": true - }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", @@ -4750,190 +2236,92 @@ "extsprintf": "^1.2.0" } }, - "vinyl": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", - "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", - "dev": true, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "requires": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", - "replace-ext": "0.0.1" + "isexe": "^2.0.0" } }, - "vinyl-fs": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", - "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + }, + "widest-line": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", + "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", "dev": true, "requires": { - "fs-mkdirp-stream": "^1.0.0", - "glob-stream": "^6.1.0", - "graceful-fs": "^4.0.0", - "is-valid-glob": "^1.0.0", - "lazystream": "^1.0.0", - "lead": "^1.0.0", - "object.assign": "^4.0.4", - "pumpify": "^1.3.5", - "readable-stream": "^2.3.3", - "remove-bom-buffer": "^3.0.0", - "remove-bom-stream": "^1.2.0", - "resolve-options": "^1.1.0", - "through2": "^2.0.0", - "to-through": "^2.0.0", - "value-or-function": "^3.0.0", - "vinyl": "^2.0.0", - "vinyl-sourcemap": "^1.1.0" + "string-width": "^2.1.1" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" }, "dependencies": { - "clone": { + "ansi-regex": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", - "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=", - "dev": true - }, - "clone-stats": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "replace-ext": { + "is-fullwidth-code-point": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", - "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "requires": { - "safe-buffer": "~5.1.0" + "number-is-nan": "^1.0.0" } }, - "vinyl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", - "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", - "dev": true, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "requires": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } - } - } - }, - "vinyl-sourcemap": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", - "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=", - "dev": true, - "requires": { - "append-buffer": "^1.0.2", - "convert-source-map": "^1.5.0", - "graceful-fs": "^4.1.6", - "normalize-path": "^2.1.1", - "now-and-later": "^2.0.0", - "remove-bom-buffer": "^3.0.0", - "vinyl": "^2.0.0" - }, - "dependencies": { - "clone": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", - "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=", - "dev": true - }, - "clone-stats": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", - "dev": true }, - "replace-ext": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", - "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", - "dev": true - }, - "vinyl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", - "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", - "dev": true, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" + "ansi-regex": "^2.0.0" } } } }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "write-file-atomic": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz", + "integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==", "dev": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" } }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "xdg-basedir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", + "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", + "dev": true }, "xml2js": { "version": "0.4.19", @@ -4945,50 +2333,46 @@ } }, "xmlbuilder": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.4.tgz", - "integrity": "sha1-UZy0ymhtAFqEINNJbz8MruzKWA8=" - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", - "dev": true + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" }, "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" }, "yargs": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", - "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", - "dev": true, + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", "requires": { - "camelcase": "^3.0.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", "get-caller-file": "^1.0.1", - "os-locale": "^1.4.0", - "read-pkg-up": "^1.0.1", + "os-locale": "^3.0.0", "require-directory": "^2.1.1", "require-main-filename": "^1.0.1", "set-blocking": "^2.0.0", - "string-width": "^1.0.2", - "which-module": "^1.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^5.0.0" + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" } }, "yargs-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", - "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", - "dev": true, + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", "requires": { - "camelcase": "^3.0.0" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" } } } diff --git a/package.json b/package.json index 08490842..e7358d32 100644 --- a/package.json +++ b/package.json @@ -1,81 +1,86 @@ { "name": "webdriver-manager", - "version": "12.1.9", - "description": "A selenium server and browser driver manager for your end to end tests.", + "version": "13.0.0-beta", + "description": "webdriver-manager", + "bin": { + "webdriver-manager": "bin/webdriver-manager" + }, + "main": "dist/lib/index.js", + "types": "dist/lib/index.d.ts", "scripts": { - "format": "gulp format", - "format-enforce": "gulp format:enforce", - "gulp": "gulp", + "check": "gts check", + "clean": "gts clean", + "compile": "npm run tsc", + "fix": "gts fix", + "gts": "gts", + "http-server": "tsc && node dist/spec/server/http_server.js", "jasmine": "jasmine", - "prepublish": "npm run format-enforce && tsc && gulp copy", - "tsc": "tsc", - "pretest": "tsc && gulp copy", - "test": "npm run test-unit && npm run test-e2e", - "test-unit": "jasmine", - "pretest-e2e:update": "node ./bin/webdriver-manager update", - "pretest-e2e:start": "node ./bin/webdriver-manager start --detach --seleniumPort 4444 --quiet", - "pretest-e2e": "npm run pretest && npm run pretest-e2e:update && npm run pretest-e2e:start", - "test-e2e": "jasmine JASMINE_CONFIG_PATH=e2e_spec/support/headless.json", - "posttest-e2e": "node ./bin/webdriver-manager shutdown" + "prepare": "npm run compile", + "pretest": "npm run compile", + "pretsc": "rm -rf dist/ && rm -rf downloads/", + "posttest": "npm run check", + "proxy-server": "tsc && node dist/spec/server/proxy_server.js", + "test": "tsc && npm run test-unit && npm run test-int && npm run test-proxy && npm run test-e2e", + "test-e2e": "tsc && jasmine JASMINE_CONFIG_PATH=spec/jasmine-e2e.json", + "test-int": "tsc && jasmine JASMINE_CONFIG_PATH=spec/jasmine-int.json", + "test-proxy": "tsc && jasmine JASMINE_CONFIG_PATH=spec/jasmine-proxy.json", + "test-unit": "tsc && jasmine JASMINE_CONFIG_PATH=spec/jasmine-unit.json", + "tslint": "tslint -c tslint.json --fix lib/**/*.ts spec/**/*.ts", + "tsc": "tsc" }, "keywords": [ - "angular", + "automation", + "browser", + "browsers", + "browser test", + "browser testing", + "chromedriver", + "geckodriver", + "iedriver", + "selenium", + "selenium-webdriver", "test", "testing", - "protractor", "webdriver", - "webdriverjs", - "selenium", - "selenium-webdriver" + "webdriverjs" ], "repository": { "type": "git", - "url": "git://github.com/angular/webdriver-manager.git" + "url": "git+https://github.com/angular/webdriver-manager.git" }, - "bin": { - "webdriver-manager": "bin/webdriver-manager" - }, - "main": "built/lib/webdriver.js", "author": "Craig Nishina ", "license": "MIT", + "bugs": { + "url": "https://github.com/angular/webdriver-manager/issues" + }, + "homepage": "https://github.com/angular/webdriver-manager#readme", "dependencies": { - "adm-zip": "^0.5.2", - "chalk": "^1.1.1", - "del": "^2.2.0", - "glob": "^7.0.3", - "ini": "^1.3.4", - "minimist": "^1.2.0", - "q": "^1.4.1", - "request": "^2.87.0", - "rimraf": "^2.5.2", - "semver": "^5.3.0", - "xml2js": "^0.4.17" + "adm-zip": "^0.4.13", + "loglevel": "^1.6.1", + "request": "^2.88.0", + "semver": "^5.6.0", + "tar": "^4.4.8", + "xml2js": "^0.4.19", + "yargs": "^12.0.5" }, "devDependencies": { - "@types/adm-zip": "^0.5.0", - "@types/chalk": "^0.4.28", - "@types/form-data": "^0.0.33", - "@types/glob": "^5.0.29", - "@types/ini": "^1.3.28", - "@types/jasmine": "^2.5.43", - "@types/minimatch": "^2.0.28", - "@types/minimist": "^1.1.28", - "@types/node": "^7.0.4", - "@types/q": "^0.0.32", - "@types/request": "^0.0.39", - "@types/rimraf": "^0.0.28", - "@types/selenium-webdriver": "^2.53.35", - "@types/semver": "^5.3.30", - "@types/xml2js": "0.0.32", - "clang-format": "^1.0.35", - "gulp": "^4.0.0", - "gulp-clang-format": "^1.0.23", - "jasmine": "^2.4.1", - "run-sequence": "^1.1.5", - "selenium-webdriver": "~3.0.1", - "typescript": "~2.3.0" - }, - "engines": { - "node": ">=6.9.x" + "@types/adm-zip": "^0.4.31", + "@types/http-proxy": "^1.16.2", + "@types/jasmine": "^3.3.1", + "@types/loglevel": "^1.5.3", + "@types/node": "^10.12.12", + "@types/request": "^2.48.1", + "@types/rimraf": "^2.0.2", + "@types/semver": "^5.5.0", + "@types/tar": "^4.0.0", + "@types/xml2js": "^0.4.3", + "@types/yargs": "^12.0.1", + "clang-format": "^1.2.4", + "gts": "^0.9.0", + "http-proxy": "^1.17.0", + "jasmine": "^3.3.1", + "rimraf": "^2.6.2", + "tslint": "^5.11.0", + "typescript": "^3.2.2" } } diff --git a/release.md b/release.md deleted file mode 100644 index 89e4b8b8..00000000 --- a/release.md +++ /dev/null @@ -1,37 +0,0 @@ -Webdriver Manager Release Checklist ------------------------------------ -Say the previous release was 0.0.J, the current release is 0.0.K, and the next release will be 0.0.L. - -- Make sure [Travis](https://travis-ci.org/angular/webdriver-manager/builds) is passing. - -- Make sure .gitignore and .npmignore are updated with any new files that need to be ignored. - -- Update package.json with a version bump. If the changes are only bug fixes, increment the patch (e.g. 0.0.5 -> 0.0.6), otherwise increment the minor version. - -- Update CHANGELOG.md - - - You can get a list of changes in the correct format by running - - ``` - git log 0.0.J..HEAD --format="- ([%h](https://github.com/angular/webdriver-manager/commit/%H)) %n%w(100,2,2)%B" > /tmp/changes.txt - ``` - - Create a new section in CHANGELOG.md and copy in features (`feat`), big dependency version updates (`deps`), bug fixes (`fix`), and breaking changes. No need to note chores or stylistic changes - the changelog should be primarily useful to someone using Protractor, not developing on it. - - - Breaking changes should be in their own section and include before/after examples of how to fix code that needs to change. - - - Make a commit with the API and package.json changes titled chore(release): version bump and changelog for 0.0.K. - - - Tag the release with `git tag 0.0.K` - - - Push to github - - - Push tags to github (`git push --tags`) - - - Verify that the changelog and tags look sane on github - - - NPM publish - - - Let people know - - Have @ProtractorTest tweet about it - - - Close the 0.0.K milestone diff --git a/spec/binaries/chrome_driver_spec.ts b/spec/binaries/chrome_driver_spec.ts deleted file mode 100644 index 740a9855..00000000 --- a/spec/binaries/chrome_driver_spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -import * as path from 'path'; -import * as rimraf from 'rimraf'; -import {ChromeDriver} from '../../lib/binaries/chrome_driver'; - -describe('chrome driver', () => { - let out_dir = path.resolve('selenium_test'); - - afterAll(() => { - rimraf.sync(out_dir); - }); - - it('should get the id', () => { - expect(new ChromeDriver().id()).toEqual('chrome'); - }); - - it('should get the url', (done) => { - let chromeDriver = new ChromeDriver(); - chromeDriver.configSource.out_dir = out_dir; - chromeDriver.configSource.osarch = 'x64'; - chromeDriver.configSource.ostype = 'Darwin'; - chromeDriver.getUrl('2.20').then(binaryUrl => { - expect(binaryUrl.url).toContain('2.20/chromedriver_mac32.zip'); - done(); - }); - }); - - it('should get the lists', (done) => { - let chromeDriver = new ChromeDriver(); - chromeDriver.configSource.out_dir = out_dir; - chromeDriver.configSource.osarch = 'x64'; - chromeDriver.configSource.ostype = 'Darwin'; - chromeDriver.getVersionList().then(list => { - for (let item of list) { - expect(item).toContain('chromedriver_mac'); - } - done(); - }); - }); -}); diff --git a/spec/binaries/chrome_xml_spec.ts b/spec/binaries/chrome_xml_spec.ts deleted file mode 100644 index ebaa2855..00000000 --- a/spec/binaries/chrome_xml_spec.ts +++ /dev/null @@ -1,93 +0,0 @@ -import * as path from 'path'; -import * as rimraf from 'rimraf'; -import {ChromeXml} from '../../lib/binaries/chrome_xml'; - -describe('chrome xml reader', () => { - let out_dir = path.resolve('selenium_test'); - - afterAll(() => { - rimraf.sync(out_dir); - }); - - it('should get a list', (done) => { - let chromeXml = new ChromeXml(); - chromeXml.out_dir = out_dir; - chromeXml.ostype = 'Darwin'; - chromeXml.osarch = 'x64'; - chromeXml.getVersionList().then(list => { - for (let item of list) { - expect(item).toContain('/chromedriver_mac'); - expect(item).not.toContain('m1'); - } - done(); - }); - }); - - it('should get the 2.27, 64-bit version (arch = x64)', (done) => { - let chromeXml = new ChromeXml(); - chromeXml.out_dir = out_dir; - chromeXml.ostype = 'Darwin'; - chromeXml.osarch = 'x64'; - chromeXml.getUrl('2.27').then(binaryUrl => { - expect(binaryUrl.url).toContain('2.27/chromedriver_mac64.zip'); - done(); - }); - }); - - it('should get the 2.27, 64-bit version (arch = x86)', (done) => { - let chromeXml = new ChromeXml(); - chromeXml.out_dir = out_dir; - chromeXml.ostype = 'Darwin'; - chromeXml.osarch = 'x86'; - chromeXml.getUrl('2.27').then(binaryUrl => { - expect(binaryUrl.url).toEqual(''); - done(); - }); - }); - - it('should get the 2.20, 32-bit version (arch = x64)', (done) => { - let chromeXml = new ChromeXml(); - chromeXml.out_dir = out_dir; - chromeXml.ostype = 'Darwin'; - chromeXml.osarch = 'x64'; - chromeXml.getUrl('2.20').then(binaryUrl => { - expect(binaryUrl.url).toContain('2.20/chromedriver_mac32.zip'); - done(); - }); - }); - - it('should get the 2.20, 32-bit version (arch = x86)', (done) => { - let chromeXml = new ChromeXml(); - chromeXml.out_dir = out_dir; - chromeXml.ostype = 'Darwin'; - chromeXml.osarch = 'x86'; - chromeXml.getUrl('2.20').then((binaryUrl) => { - expect(binaryUrl.url).toContain('2.20/chromedriver_mac32.zip'); - done(); - }); - }); - - // This test case covers a bug when all the following conditions were true. - // arch was 64 with multiple major versions available. - it('should not get the 85.0.4183.38, 32-bit version (arch = x64)', (done) => { - let chromeXml = new ChromeXml(); - chromeXml.out_dir = out_dir; - chromeXml.ostype = 'Windows_NT'; - chromeXml.osarch = 'x64'; - chromeXml.getUrl('85.0.4183.87').then((binaryUrl) => { - expect(binaryUrl.url).toContain('85.0.4183.87/chromedriver_win32.zip'); - done(); - }); - }); - - it('should get the 87.0.4280.88, 64-bit, m1 version (arch = arm64)', (done) => { - let chromeXml = new ChromeXml(); - chromeXml.out_dir = out_dir; - chromeXml.ostype = 'Darwin'; - chromeXml.osarch = 'arm64'; - chromeXml.getUrl('87.0.4280.88').then((binaryUrl) => { - expect(binaryUrl.url).toContain('87.0.4280.88/chromedriver_mac64_m1.zip'); - done(); - }); - }); -}); diff --git a/spec/binaries/config_source_spec.ts b/spec/binaries/config_source_spec.ts deleted file mode 100644 index af51af3b..00000000 --- a/spec/binaries/config_source_spec.ts +++ /dev/null @@ -1,169 +0,0 @@ -import * as fs from 'fs'; -import {GithubApiConfigSource, XmlConfigSource} from '../../lib/binaries/config_source'; -import {Config} from '../../lib/config'; - -export class XMLConfig extends XmlConfigSource { - constructor(public name: string, public xmlUrl: string) { - super(name, xmlUrl); - } - getUrl(version: string): Promise<{url: string, version: string}> { - return null; - } - getVersionList(): Promise { - return null; - } - testGetXml(): Promise { - return this.getXml(); - } -} - -export class JSONConfig extends GithubApiConfigSource { - constructor(name: string, url: string) { - super(name, url); - } - getUrl(version: string): Promise<{url: string, version: string}> { - return null; - } - getVersionList(): Promise { - return null; - } - testGetJson(): Promise { - return this.getJson(); - } -} - -describe('config', () => { - describe('xml config source', () => { - it('on start: should read the xml file and not check the timestamp', done => { - spyOn(fs, 'readFileSync').and.callFake(() => { - return ` - - - foobar-release - - 0.01/foobar.zip - - - `; - }); - spyOn(fs, 'statSync').and.callFake(() => { - return { - mtime: 0 - } - }); - Config.runCommand = 'start'; - let xmlConfig = new XMLConfig('xml', 'url'); - xmlConfig.testGetXml() - .then(xml => { - expect(xml.ListBucketResult.Name).toEqual(['foobar-release']); - done(); - }) - .catch(err => { - done.fail(err); - }); - }); - - it('on udpate: should check the timestamp, invaidate cache, and try to make a web request', - done => { - spyOn(fs, 'readFileSync').and.callFake(() => { - return 'foobar'; - }); - spyOn(fs, 'statSync').and.callFake(() => { - return { - mtime: 0 - } - }); - Config.runCommand = 'update'; - let xmlConfig = new XMLConfig('xml', 'url'); - xmlConfig.testGetXml() - .then(xml => { - // should do nothing - done.fail('this should not work'); - }) - .catch(err => { - expect(err.toString()).toContain('Invalid URI "url"'); - done(); - }); - }); - - it('on update: if the size of the file is zero, invalidate the cache', done => { - spyOn(fs, 'statSync').and.callFake(() => { - return {size: 0}; - }); - Config.runCommand = 'update'; - let xmlConfig = new XMLConfig('json', 'url'); - xmlConfig.testGetXml() - .then(xml => { - // should do nothing - done.fail('this should not work'); - }) - .catch(err => { - expect(err.toString()).toContain('Invalid URI "url"'); - done(); - }); - }); - }); - - describe('github json', () => { - it('on start: should read the json file and not check the timestamp', done => { - spyOn(fs, 'readFileSync').and.callFake(() => { - return '{ "foo": "bar" }'; - }); - spyOn(fs, 'statSync').and.callFake(() => { - return { - mtime: 0 - } - }); - Config.runCommand = 'start'; - let jsonConfig = new JSONConfig('json', 'url'); - jsonConfig.testGetJson() - .then(json => { - expect(json.foo).toEqual('bar'); - done(); - }) - .catch(err => { - done.fail(err); - }); - }); - - it('on udpate: should check the timestamp, invaidate cache, and try to make a web request', - done => { - spyOn(fs, 'readFileSync').and.callFake(() => { - return 'foobar'; - }); - spyOn(fs, 'statSync').and.callFake(() => { - return { - mtime: 0 - } - }); - Config.runCommand = 'update'; - let jsonConfig = new JSONConfig('json', 'url'); - jsonConfig.testGetJson() - .then(json => { - // should do nothing - done.fail('this should not work'); - }) - .catch(err => { - expect(err.toString()).toContain('Invalid URI "url"'); - done(); - }); - }); - - it('on update: if the size of the file is zero, invalidate the cache', done => { - spyOn(fs, 'statSync').and.callFake(() => { - return {size: 0}; - }); - Config.runCommand = 'update'; - let jsonConfig = new JSONConfig('json', 'url'); - jsonConfig.testGetJson() - .then(json => { - // should do nothing - done.fail('this should not work'); - }) - .catch(err => { - expect(err.toString()).toContain('Invalid URI "url"'); - done(); - }); - }); - }); -}); diff --git a/spec/binaries/gecko_driver_github_spec.ts b/spec/binaries/gecko_driver_github_spec.ts deleted file mode 100644 index ab9a2caf..00000000 --- a/spec/binaries/gecko_driver_github_spec.ts +++ /dev/null @@ -1,31 +0,0 @@ -import * as path from 'path'; -import * as rimraf from 'rimraf'; -import {GeckoDriverGithub} from '../../lib/binaries/gecko_driver_github'; - -describe('gecko driver github', () => { - let out_dir = path.resolve('selenium_test'); - - afterAll(() => { - rimraf.sync(out_dir); - }); - - it('should get version 0.13.0', (done) => { - let geckoDriverGithub = new GeckoDriverGithub(); - geckoDriverGithub.out_dir = out_dir; - geckoDriverGithub.getUrl('v0.13.0').then(binaryUrl => { - expect(binaryUrl.url) - .toContain( - 'https://github.com/mozilla/geckodriver/releases/download/v0.13.0/geckodriver-v'); - done(); - }); - }); - - it('should get a version list', (done) => { - let geckoDriverGithub = new GeckoDriverGithub(); - geckoDriverGithub.out_dir = out_dir; - geckoDriverGithub.getVersionList().then(list => { - expect(list.length).toBeGreaterThan(0); - done(); - }); - }); -}); diff --git a/spec/binaries/gecko_driver_spec.ts b/spec/binaries/gecko_driver_spec.ts deleted file mode 100644 index 25587e1a..00000000 --- a/spec/binaries/gecko_driver_spec.ts +++ /dev/null @@ -1,35 +0,0 @@ -import * as path from 'path'; -import * as rimraf from 'rimraf'; -import {GeckoDriver} from '../../lib/binaries/gecko_driver'; - -describe('gecko driver', () => { - let out_dir = path.resolve('selenium_test'); - - afterAll(() => { - rimraf.sync(out_dir); - }); - - it('should get id', () => { - expect(new GeckoDriver().id()).toEqual('gecko'); - }); - - it('should get url for 0.13.0', (done) => { - let geckoDriver = new GeckoDriver(); - geckoDriver.configSource.out_dir = out_dir; - geckoDriver.getUrl('v0.13.0').then(binaryUrl => { - expect(binaryUrl.url) - .toContain( - 'https://github.com/mozilla/geckodriver/releases/download/v0.13.0/geckodriver-v'); - done(); - }); - }); - - it('should get the version list', (done) => { - let geckoDriver = new GeckoDriver(); - geckoDriver.configSource.out_dir = out_dir; - geckoDriver.getVersionList().then(list => { - expect(list.length).toBeGreaterThan(0); - done(); - }); - }); -}); diff --git a/spec/binaries/iedriver_spec.ts b/spec/binaries/iedriver_spec.ts deleted file mode 100644 index badf995e..00000000 --- a/spec/binaries/iedriver_spec.ts +++ /dev/null @@ -1,26 +0,0 @@ -import * as path from 'path'; -import * as rimraf from 'rimraf'; -import {IEDriver} from '../../lib/binaries/iedriver'; - -describe('iedriver', () => { - let out_dir = path.resolve('selenium_test'); - - afterAll(() => { - rimraf.sync(out_dir); - }); - - it('should get the id', () => { - expect(new IEDriver().id()).toEqual('ie'); - }); - - it('should get version 2.53.1', (done) => { - let iedriver = new IEDriver(); - iedriver.configSource.out_dir = out_dir; - iedriver.getUrl('2.53.1').then(binaryUrl => { - expect(binaryUrl.url) - .toEqual( - 'https://selenium-release.storage.googleapis.com/2.53/IEDriverServer_Win32_2.53.1.zip'); - done(); - }); - }); -}); diff --git a/spec/binaries/iedriver_xml_spec.ts b/spec/binaries/iedriver_xml_spec.ts deleted file mode 100644 index 5f620d3e..00000000 --- a/spec/binaries/iedriver_xml_spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import * as path from 'path'; -import * as rimraf from 'rimraf'; -import {IEDriverXml} from '../../lib/binaries/iedriver_xml'; - -describe('iedriver xml', () => { - let out_dir = path.resolve('selenium_test'); - - afterAll(() => { - rimraf.sync(out_dir); - }); - - it('should get version 2.53.1', (done) => { - let iedriverXml = new IEDriverXml(); - iedriverXml.out_dir = out_dir; - iedriverXml.getUrl('2.53.1').then(binaryUrl => { - expect(binaryUrl.url) - .toEqual( - 'https://selenium-release.storage.googleapis.com/2.53/IEDriverServer_Win32_2.53.1.zip'); - done(); - }); - }); -}); diff --git a/spec/binaries/standalone_spec.ts b/spec/binaries/standalone_spec.ts deleted file mode 100644 index 6734ddcd..00000000 --- a/spec/binaries/standalone_spec.ts +++ /dev/null @@ -1,37 +0,0 @@ -import * as path from 'path'; -import * as rimraf from 'rimraf'; -import {Standalone} from '../../lib/binaries/standalone'; - -describe('standalone', () => { - let out_dir = path.resolve('selenium_test'); - - afterAll(() => { - rimraf.sync(out_dir); - }); - - it('should get the id', () => { - expect(new Standalone().id()).toEqual('standalone'); - }); - - it('should get the url', (done) => { - let standalone = new Standalone(); - standalone.configSource.out_dir = out_dir; - standalone.getUrl('2.53.1').then(binaryUrl => { - expect(binaryUrl.url).toContain('2.53/selenium-server-standalone-2.53.1.jar'); - done(); - }); - }); - - it('should get the lists', (done) => { - let standalone = new Standalone(); - standalone.configSource.out_dir = out_dir; - standalone.configSource.osarch = 'x64'; - standalone.configSource.ostype = 'Darwin'; - standalone.getVersionList().then(list => { - for (let item of list) { - expect(item).toContain('selenium-server-standalone-'); - } - done(); - }); - }); -}); diff --git a/spec/binaries/standalone_xml_spec.ts b/spec/binaries/standalone_xml_spec.ts deleted file mode 100644 index adba4095..00000000 --- a/spec/binaries/standalone_xml_spec.ts +++ /dev/null @@ -1,44 +0,0 @@ -import * as path from 'path'; -import * as rimraf from 'rimraf'; -import {StandaloneXml} from '../../lib/binaries/standalone_xml'; - -describe('standalone xml reader', () => { - let out_dir = path.resolve('selenium_test'); - - afterAll(() => { - rimraf.sync(out_dir); - }); - - it('should get a list', (done) => { - let standaloneXml = new StandaloneXml(); - standaloneXml.out_dir = out_dir; - standaloneXml.getVersionList().then(list => { - for (let item of list) { - expect(item).toContain('/selenium-server-standalone'); - } - done(); - }); - }); - - it('should get version 2.53.1', (done) => { - let standaloneXml = new StandaloneXml(); - standaloneXml.out_dir = out_dir; - standaloneXml.getUrl('2.53.1').then(binaryUrl => { - expect(binaryUrl.url) - .toBe( - 'https://selenium-release.storage.googleapis.com/2.53/selenium-server-standalone-2.53.1.jar'); - done(); - }); - }); - - it('should get version 3.0.0-beta3', (done) => { - let standaloneXml = new StandaloneXml(); - standaloneXml.out_dir = out_dir; - standaloneXml.getUrl('3.0.0-beta3').then(binaryUrl => { - expect(binaryUrl.url) - .toBe( - 'https://selenium-release.storage.googleapis.com/3.0-beta3/selenium-server-standalone-3.0.0-beta3.jar'); - done(); - }); - }); -}); diff --git a/spec/cli/options_spec.ts b/spec/cli/options_spec.ts deleted file mode 100644 index 3d8b1cb6..00000000 --- a/spec/cli/options_spec.ts +++ /dev/null @@ -1,144 +0,0 @@ -import {Option} from '../../lib/cli/options'; - - -describe('options', () => { - let option: Option; - - describe('get number', () => { - describe('for this.value not set', () => { - it('should return the default value', () => { - option = new Option('fake opt', 'fake description', 'number', 10); - expect(option.getNumber()).toEqual(10); - - option = new Option('fake opt', 'fake description', 'number', 0); - expect(option.getNumber()).toEqual(0); - - option = new Option('fake opt', 'fake description', 'number', -5); - expect(option.getNumber()).toEqual(-5); - }); - - it('should return null if the default value is not set', () => { - option = new Option('fake opt', 'fake description', 'number'); - expect(option.getNumber()).toBeNull(); - }); - }); - - describe('for this.value set', () => { - beforeEach(() => { - option = new Option('fake opt', 'fake description', 'number', -10); - }); - - it('should return the this.value when this.value is a number', () => { - option.value = 20; - expect(option.getNumber()).toEqual(20); - }); - - it('should return a number of this.value when it is a string of a number', () => { - option.value = '10'; - expect(option.getNumber()).toEqual(10); - option.value = '0'; - expect(option.getNumber()).toEqual(0); - option.value = '-5'; - expect(option.getNumber()).toEqual(-5); - }); - - it('should return null if this.value is not a string or a number', () => { - option.value = true; - expect(option.getNumber()).toBeNull(); - option.value = false; - expect(option.getNumber()).toBeNull(); - }); - - it('should return NaN if this.value is a string but is not a number', () => { - option.value = 'foobar'; - expect(option.getNumber()).toEqual(NaN); - }); - }); - }); - - describe('get boolean', () => { - describe('for this.value not set', () => { - it('should return the default value', () => { - option = new Option('fake opt', 'fake description', 'boolean', true); - expect(option.getBoolean()).toBeTruthy(); - option = new Option('fake opt', 'fake description', 'boolean', false); - expect(option.getBoolean()).not.toBeTruthy(); - }); - - it('should return false if the default value is not defined', () => { - option = new Option('fake opt', 'fake description', 'boolean'); - expect(option.getBoolean()).not.toBeTruthy(); - }); - }); - - describe('for this.value set', () => { - beforeEach(() => { - option = new Option('fake opt', 'fake description', 'boolean'); - }); - - it('should return a boolean when this.value is a string', () => { - option.value = 'true'; - expect(option.getBoolean()).toBeTruthy(); - option.value = 'false'; - expect(option.getBoolean()).not.toBeTruthy(); - }); - - it('should return a boolean of this.value when this.value is a number', () => { - option.value = 1; - expect(option.getNumber()).toBeTruthy(); - option.value = 0; - expect(option.getNumber()).not.toBeTruthy(); - }); - - it('should return the boolean of this.value when this.value is a boolean', () => { - option.value = true; - expect(option.getNumber()).toBeNull(); - option.value = false; - expect(option.getNumber()).toBeNull(); - }); - }); - }); - - describe('get string', () => { - describe('for this.value not set', () => { - it('should return the default value', () => { - option = new Option('fake opt', 'fake description', 'string', 'foobar'); - expect(option.getString()).toBe('foobar'); - option = new Option('fake opt', 'fake description', 'string', ''); - expect(option.getString()).toBe(''); - }); - - it('should return an empty string if the default value is not defined', () => { - option = new Option('fake opt', 'fake description', 'string'); - expect(option.getString()).toBe(''); - }); - }); - - describe('for this.value set', () => { - beforeEach(() => { - option = new Option('fake opt', 'fake description', 'string', 'foo'); - }); - - it('should return this.value when this.value is a string', () => { - option.value = 'bar'; - expect(option.getString()).toEqual('bar'); - option.value = ''; - expect(option.getString()).toEqual(''); - }); - - it('should return the string of this.value when this.value is a number', () => { - option.value = 0; - expect(option.getString()).toEqual('0'); - option.value = 1; - expect(option.getString()).toEqual('1'); - }); - - it('should return the string of this.value when this.value is a boolean', () => { - option.value = false; - expect(option.getString()).toEqual('false'); - option.value = true; - expect(option.getString()).toEqual('true'); - }); - }); - }); -}); diff --git a/spec/cli/programs_spec.ts b/spec/cli/programs_spec.ts deleted file mode 100644 index dffb5fdb..00000000 --- a/spec/cli/programs_spec.ts +++ /dev/null @@ -1,81 +0,0 @@ -import {Option, Options, Program} from '../../lib/cli'; - - -describe('program', () => { - let program: Program; - - beforeEach( - () => {program = new Program() - .command('fooCmd', 'fooDescription') - .addOption(new Option('fooString1', 'fooDescription', 'string', 'foo')) - .addOption(new Option('fooString1', 'fooDescription', 'string', 'foo')) - .addOption(new Option('fooBoolean1', 'fooDescription', 'boolean', false)) - .addOption(new Option('fooBoolean2', 'fooDescription', 'boolean', true)) - .addOption(new Option('fooNumber1', 'fooDescription', 'number', 1)) - .addOption(new Option('fooNumber2', 'fooDescription', 'number', 2)) - .addOption(new Option('fooNumber3', 'fooDescription', 'number', 3))}); - - it('should get minimist options', () => { - let json = JSON.parse(JSON.stringify(program.getMinimistOptions())); - expect(json.string.length).toEqual(1); - expect(json.boolean.length).toEqual(2); - expect(json.number.length).toEqual(3); - let length = 0; - for (let item in json.default) { - length++; - } - expect(length).toEqual(6); - expect(json.string[0]).toBe('fooString1'); - expect(json.boolean[0]).toBe('fooBoolean1'); - expect(json.boolean[1]).toBe('fooBoolean2'); - expect(json.number[0]).toBe('fooNumber1'); - expect(json.number[1]).toBe('fooNumber2'); - expect(json.number[2]).toBe('fooNumber3'); - }); - - it('should be able to extract the correct type and value', () => { - let testString: string; - let json = JSON.parse(JSON.stringify({ - '_': ['fooCmd'], - 'fooString1': 'bar', - 'fooBoolean1': true, - 'fooBoolean2': false, - 'fooNumber1': 10, - 'fooNumber2': 20, - 'fooNumber3': 30 - })); - let callbackTest = (options: Options) => { - expect(options['fooString1'].getString()).toEqual('bar'); - expect(options['fooBoolean1'].getBoolean()).toEqual(true); - expect(options['fooBoolean2'].getBoolean()).toEqual(false); - expect(options['fooNumber1'].getNumber()).toEqual(10); - expect(options['fooNumber2'].getNumber()).toEqual(20); - expect(options['fooNumber3'].getNumber()).toEqual(30); - }; - program.action(callbackTest); - program.run(json); - }); - - it('should be able to extract the mixed type and get the right type', () => { - let testString: string; - let json = JSON.parse(JSON.stringify({ - '_': ['fooCmd'], - 'fooString1': 1, - 'fooBoolean1': 'true', - 'fooBoolean2': 0, - 'fooNumber1': '100', - 'fooNumber2': 'foo', - 'fooNumber3': true - })); - let callbackTest = (options: Options) => { - expect(options['fooString1'].getString()).toEqual('1'); - expect(options['fooBoolean1'].getBoolean()).toEqual(true); - expect(options['fooBoolean2'].getBoolean()).toEqual(false); - expect(options['fooNumber1'].getNumber()).toEqual(100); - expect(options['fooNumber2'].getNumber()).toEqual(NaN); - expect(options['fooNumber3'].getNumber()).toEqual(null); - }; - program.action(callbackTest); - program.run(json); - }); -}); diff --git a/spec/cmds/status_spec.ts b/spec/cmds/status_spec.ts deleted file mode 100644 index 9bcba08e..00000000 --- a/spec/cmds/status_spec.ts +++ /dev/null @@ -1,84 +0,0 @@ -import * as path from 'path'; - -import {Logger, WriteTo} from '../../lib/cli/logger'; -import {program} from '../../lib/cmds/update'; -import {spawnSync} from '../../lib/utils'; - -function getVersions(line: string): string[] { - return line.split(':')[3].split(','); -} - -describe('status', () => { - Logger.writeTo = WriteTo.NONE; - let argv: any; - let tmpDir = path.resolve('selenium_test'); - - // chrome 2.20[last], 2.24 - // geckodriver {{config version}} [last] - // standalone 2.24 [last], {{config version}} - beforeAll((done) => { - argv = { - '_': ['update'], - 'gecko': 'false', - 'versions': {'chrome': '2.24', 'standalone': '2.44.0'}, - 'out_dir': tmpDir - }; - program.run(JSON.parse(JSON.stringify(argv))) - .then(() => { - argv['versions']['chrome'] = '2.20'; - program.run(JSON.parse(JSON.stringify(argv))).then(() => { - done(); - }); - }) - .catch(err => { - done.fail(); - }); - }); - - xit('should show the version number of the default and latest versions', () => { - let lines = - spawnSync( - process.execPath, - ['built/lib/webdriver.js', 'status', '--out_dir', 'selenium_test', '--gecko', 'false'], - 'pipe') - .output[1] - .toString() - .split('\n'); - let seleniumLine: string = null; - let chromeLine: string = null; - // let geckodriverLine: string = null; - let androidSdkLine: string = null; - let appiumLine: string = null; - - for (let line of lines) { - if (line.indexOf('selenium') >= 0) { - seleniumLine = line; - } else if (line.indexOf('chrome') >= 0) { - chromeLine = line; - // } else if (line.indexOf('geckodriver') >= 0) { - // geckodriverLine = line; - } else if (line.indexOf('android-sdk') >= 0) { - androidSdkLine = line; - } else if (line.indexOf('appium') >= 0) { - appiumLine = line; - } - } - expect(seleniumLine).not.toBeNull(); - expect(getVersions(seleniumLine).length).toEqual(1); - expect(getVersions(seleniumLine)[0]).toContain('2.44.0 [last]'); - - expect(chromeLine).not.toBeNull(); - expect(getVersions(chromeLine).length).toEqual(2); - expect(getVersions(chromeLine)[0]).toContain('2.20 [last]'); - expect(getVersions(chromeLine)[1]).toContain('2.24'); - - // expect(geckodriverLine).not.toBeNull(); - // expect(geckodriverLine).toContain('[last]'); - // expect(getVersions(geckodriverLine).length).toEqual(1); - - expect(androidSdkLine).not.toBeNull(); - expect(androidSdkLine).toContain('not present'); - expect(appiumLine).not.toBeNull(); - expect(appiumLine).toContain('not present'); - }); -}); diff --git a/spec/cmds/update_spec.ts b/spec/cmds/update_spec.ts deleted file mode 100644 index 6b744e60..00000000 --- a/spec/cmds/update_spec.ts +++ /dev/null @@ -1,89 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import * as rimraf from 'rimraf'; - -import {Logger, WriteTo} from '../../lib/cli/logger'; -import {clearBrowserFile, program} from '../../lib/cmds/update'; -import {Config} from '../../lib/config'; - -interface Argv { - [key: string]: any; -} -let argv: Argv = {}; - -describe('update', () => { - describe('for update-config.json', () => { - let tmpDir = ''; - beforeEach(() => { - Logger.writeTo = WriteTo.NONE; - tmpDir = path.resolve('selenium_test'); - try { - // if the folder does not exist, it will throw an error on statSync - if (fs.statSync(tmpDir).isDirectory()) { - rimraf.sync(tmpDir); - } - } catch (err) { - // do nothing, the directory does not exist - } - fs.mkdirSync(tmpDir); - }); - - afterEach(() => { - rimraf.sync(tmpDir); - clearBrowserFile(); - }); - - it('should create a file for chrome', (done) => { - Config.osType_ = 'Linux'; - Config.osArch_ = 'x64'; - argv = { - '_': ['update'], - 'versions': {'chrome': '2.20'}, - 'standalone': false, - 'gecko': false, - 'out_dir': tmpDir - }; - program.run(JSON.parse(JSON.stringify(argv))) - .then(() => { - let updateConfig = - fs.readFileSync(path.resolve(tmpDir, 'update-config.json')).toString(); - let updateObj = JSON.parse(updateConfig); - expect(updateObj['chrome']['last']).toContain('chromedriver_2.20'); - expect(updateObj['chrome']['all'].length).toEqual(1); - expect(updateObj['chrome']['last']).toEqual(updateObj['chrome']['all'][0]); - expect(updateObj['standalone']).toBeUndefined(); - expect(updateObj['ie']).toBeUndefined(); - done(); - }) - .catch((err: Error) => {done.fail()}); - }); - - xit('should create a file for standalone', (done) => { - Config.osType_ = 'Linux'; - Config.osArch_ = 'x64'; - argv = { - '_': ['update'], - 'versions': {'standalone': '2.53.1'}, - 'chrome': false, - 'gecko': false, - 'out_dir': tmpDir - }; - program.run(JSON.parse(JSON.stringify(argv))) - .then(() => { - let updateConfig = - fs.readFileSync(path.resolve(tmpDir, 'update-config.json')).toString(); - let updateObj = JSON.parse(updateConfig); - expect(updateObj['standalone']['last']).toContain('standalone-2.53.1.jar'); - expect(updateObj['standalone']['all'].length).toEqual(1); - expect(updateObj['standalone']['last']).toEqual(updateObj['standalone']['all'][0]); - expect(updateObj['chrome']).toBeUndefined(); - expect(updateObj['ie']).toBeUndefined(); - done(); - }) - .catch((err: Error) => {done.fail()}); - }); - - // TODO(cnishina): Create a test for Windows for IE driver. This will require rewriting - // how programs get configurations. - }); -}); diff --git a/spec/files/downloader_spec.ts b/spec/files/downloader_spec.ts deleted file mode 100644 index 1185073f..00000000 --- a/spec/files/downloader_spec.ts +++ /dev/null @@ -1,64 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import * as rimraf from 'rimraf'; - -import {Downloader} from '../../lib/files'; - -describe('downloader', () => { - describe('get file', () => { - let fileUrl = - 'https://selenium-release.storage.googleapis.com/3.0/selenium-server-standalone-3.0.0.jar'; - let fileName = 'foobar.jar'; - let outputDir = path.resolve('selenium_test'); - let actualContentLength = 22138949; - let contentLength: number; - - beforeEach(() => { - try { - // if the folder does not exist, it will throw an error on statSync - if (fs.statSync(outputDir).isDirectory()) { - rimraf.sync(outputDir); - } - } catch (err) { - // do nothing, the directory does not exist - } - fs.mkdirSync(outputDir); - }); - - xit('should download a file with mismatch content length', (done) => { - contentLength = 0; - Downloader.getFile(null, fileUrl, fileName, outputDir, contentLength) - .then(result => { - expect(result).toBeTruthy(); - let file = path.resolve(outputDir, fileName); - let stat = fs.statSync(file); - expect(stat.size).toEqual(actualContentLength); - rimraf.sync(file); - done(); - }) - .catch(error => { - console.log(error); - done.fail(); - }); - }); - - it('should not download a file if the content lengths match', (done) => { - contentLength = actualContentLength; - Downloader.getFile(null, fileUrl, fileName, outputDir, contentLength) - .then(result => { - expect(result).not.toBeTruthy(); - let file = path.resolve(outputDir, fileName); - try { - let access = fs.accessSync(file); - } catch (err) { - (err as any).code === 'ENOENT' - } - done(); - }) - .catch(error => { - console.log(error); - done.fail(); - }); - }); - }); -}); diff --git a/spec/files/file_manager_spec.ts b/spec/files/file_manager_spec.ts deleted file mode 100644 index 8c732b35..00000000 --- a/spec/files/file_manager_spec.ts +++ /dev/null @@ -1,268 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; - -import {AndroidSDK, Appium, Binary, BinaryMap, ChromeDriver, GeckoDriver, IEDriver, Standalone} from '../../lib/binaries'; -import {Config} from '../../lib/config'; -import {DownloadedBinary, FileManager} from '../../lib/files'; - - -describe('file manager', () => { - describe('setting up for windows', () => { - let osType = 'Windows_NT'; - - it('should find correct binaries', () => { - expect(FileManager.checkOS_(osType, ChromeDriver)).toBe(true); - expect(FileManager.checkOS_(osType, IEDriver)).toBe(true); - expect(FileManager.checkOS_(osType, Standalone)).toBe(true); - expect(FileManager.checkOS_(osType, AndroidSDK)).toBe(true); - expect(FileManager.checkOS_(osType, Appium)).toBe(true); - }); - - it('should return the binary array', () => { - let binaries = FileManager.compileBinaries_(osType); - expect(binaries[Standalone.id].name).toBe((new Standalone()).name); - expect(binaries[ChromeDriver.id].name).toBe((new ChromeDriver()).name); - expect(binaries[IEDriver.id].name).toBe((new IEDriver()).name); - expect(binaries[AndroidSDK.id].name).toBe((new AndroidSDK()).name); - expect(binaries[Appium.id].name).toBe((new Appium()).name); - }); - }); - - describe('setting up for linux', () => { - let osType = 'Linux'; - - it('should find correct binaries', () => { - expect(FileManager.checkOS_(osType, ChromeDriver)).toBe(true); - expect(FileManager.checkOS_(osType, IEDriver)).toBe(false); - expect(FileManager.checkOS_(osType, Standalone)).toBe(true); - expect(FileManager.checkOS_(osType, AndroidSDK)).toBe(true); - expect(FileManager.checkOS_(osType, Appium)).toBe(true); - }); - - it('should return the binary array', () => { - let binaries = FileManager.compileBinaries_(osType); - expect(binaries[Standalone.id].name).toBe((new Standalone()).name); - expect(binaries[ChromeDriver.id].name).toBe((new ChromeDriver()).name); - expect(binaries[AndroidSDK.id].name).toBe((new AndroidSDK()).name); - expect(binaries[Appium.id].name).toBe((new Appium()).name); - expect(binaries[IEDriver.id]).toBeUndefined(); - }); - }); - - describe('setting up for mac', () => { - let osType = 'Darwin'; - - it('should find correct binaries', () => { - expect(FileManager.checkOS_(osType, ChromeDriver)).toBe(true); - expect(FileManager.checkOS_(osType, IEDriver)).toBe(false); - expect(FileManager.checkOS_(osType, Standalone)).toBe(true); - expect(FileManager.checkOS_(osType, AndroidSDK)).toBe(true); - expect(FileManager.checkOS_(osType, Appium)).toBe(true); - }); - - it('should return the binary array', () => { - let binaries = FileManager.compileBinaries_(osType); - expect(binaries[Standalone.id].name).toBe((new Standalone()).name); - expect(binaries[ChromeDriver.id].name).toBe((new ChromeDriver()).name); - expect(binaries[IEDriver.id]).toBeUndefined(); - expect(binaries[AndroidSDK.id].name).toBe((new AndroidSDK()).name); - expect(binaries[Appium.id].name).toBe((new Appium()).name); - }); - }); - - describe('downloaded version checks', () => { - let existingFiles: string[]; - let selenium = new Standalone(); - let chrome = new ChromeDriver(); - let android = new AndroidSDK(); - let appium = new Appium(); - let ie = new IEDriver(); - let ostype: string; - let arch: string; - - function setup(osType: string): void { - ostype = osType; - arch = 'x64'; - existingFiles = [ - selenium.prefix() + '2.51.0' + selenium.executableSuffix(), - selenium.prefix() + '2.52.0' + selenium.executableSuffix() - ]; - chrome.ostype = ostype; - chrome.osarch = arch; - existingFiles.push(chrome.prefix() + '2.20' + chrome.suffix()); - existingFiles.push(chrome.prefix() + '2.20' + chrome.executableSuffix()); - existingFiles.push(chrome.prefix() + '2.21' + chrome.suffix()); - existingFiles.push(chrome.prefix() + '2.21' + chrome.executableSuffix()); - existingFiles.push(android.prefix() + '24.1.0' + android.suffix()); - existingFiles.push(android.prefix() + '24.1.0' + android.executableSuffix()); - existingFiles.push(android.prefix() + '24.1.1' + android.suffix()); - existingFiles.push(android.prefix() + '24.1.1' + android.executableSuffix()); - existingFiles.push(appium.prefix() + '1.6.0' + appium.suffix()); - if (ostype == 'Windows_NT') { - ie.ostype = ostype; - ie.osarch = arch; - existingFiles.push(ie.prefix() + '_Win32_2.51.0' + ie.suffix()); - existingFiles.push(ie.prefix() + '_Win32_2.51.0' + ie.executableSuffix()); - existingFiles.push(ie.prefix() + '_x64_2.51.0' + ie.suffix()); - existingFiles.push(ie.prefix() + '_x64_2.51.0' + ie.executableSuffix()); - existingFiles.push(ie.prefix() + '_Win32_2.52.0' + ie.suffix()); - existingFiles.push(ie.prefix() + '_Win32_2.52.0' + ie.executableSuffix()); - existingFiles.push(ie.prefix() + '_x64_2.52.0' + ie.suffix()); - existingFiles.push(ie.prefix() + '_x64_2.52.0' + ie.executableSuffix()); - } - } - - describe('versions for selenium', () => { - it('should find the correct version for windows', () => { - setup('Windows_NT'); - let downloaded = FileManager.downloadedVersions_(selenium, ostype, arch, existingFiles); - expect(downloaded.versions.length).toBe(2); - expect(downloaded.versions[0]).toBe('2.51.0'); - expect(downloaded.versions[1]).toBe('2.52.0'); - }); - it('should find the correct version for mac', () => { - setup('Darwin'); - let downloaded = FileManager.downloadedVersions_(selenium, ostype, arch, existingFiles); - expect(downloaded.versions.length).toBe(2); - expect(downloaded.versions[0]).toBe('2.51.0'); - expect(downloaded.versions[1]).toBe('2.52.0'); - }); - it('should find the correct version for mac', () => { - setup('Linux'); - let downloaded = FileManager.downloadedVersions_(selenium, ostype, arch, existingFiles); - expect(downloaded.versions.length).toBe(2); - expect(downloaded.versions[0]).toBe('2.51.0'); - expect(downloaded.versions[1]).toBe('2.52.0'); - }); - }); - - describe('versions for chrome', () => { - it('should find the correct version for windows', () => { - setup('Windows_NT'); - let downloaded = FileManager.downloadedVersions_(chrome, ostype, arch, existingFiles); - expect(downloaded.versions.length).toBe(2); - expect(downloaded.versions[0]).toBe('2.20'); - expect(downloaded.versions[1]).toBe('2.21'); - }); - it('should find the correct version for mac', () => { - setup('Darwin'); - let downloaded = FileManager.downloadedVersions_(chrome, ostype, arch, existingFiles); - expect(downloaded.versions.length).toBe(2); - expect(downloaded.versions[0]).toBe('2.20'); - expect(downloaded.versions[1]).toBe('2.21'); - }); - it('should find the correct version for linux', () => { - setup('Linux'); - let downloaded = FileManager.downloadedVersions_(chrome, ostype, arch, existingFiles); - expect(downloaded.versions.length).toBe(2); - expect(downloaded.versions[0]).toBe('2.20'); - expect(downloaded.versions[1]).toBe('2.21'); - }); - }); - - describe('versions for android', () => { - it('should find the correct version for windows', () => { - setup('Windows_NT'); - let downloaded = FileManager.downloadedVersions_(android, ostype, arch, existingFiles); - expect(downloaded.versions.length).toBe(2); - expect(downloaded.versions[0]).toBe('24.1.0'); - expect(downloaded.versions[1]).toBe('24.1.1'); - }); - it('should find the correct version for mac', () => { - setup('Darwin'); - let downloaded = FileManager.downloadedVersions_(android, ostype, arch, existingFiles); - expect(downloaded.versions.length).toBe(2); - expect(downloaded.versions[0]).toBe('24.1.0'); - expect(downloaded.versions[1]).toBe('24.1.1'); - }); - it('should find the correct version for linux', () => { - setup('Linux'); - let downloaded = FileManager.downloadedVersions_(android, ostype, arch, existingFiles); - expect(downloaded.versions.length).toBe(2); - expect(downloaded.versions[0]).toBe('24.1.0'); - expect(downloaded.versions[1]).toBe('24.1.1'); - }); - }); - - describe('versions for appium', () => { - it('should find the correct version for windows', () => { - setup('Windows_NT'); - let downloaded = FileManager.downloadedVersions_(appium, ostype, arch, existingFiles); - expect(downloaded.versions.length).toBe(1); - expect(downloaded.versions[0]).toBe('1.6.0'); - }); - it('should find the correct version for mac', () => { - setup('Darwin'); - let downloaded = FileManager.downloadedVersions_(appium, ostype, arch, existingFiles); - expect(downloaded.versions.length).toBe(1); - expect(downloaded.versions[0]).toBe('1.6.0'); - }); - it('should find the correct version for linux', () => { - setup('Linux'); - let downloaded = FileManager.downloadedVersions_(appium, ostype, arch, existingFiles); - expect(downloaded.versions.length).toBe(1); - expect(downloaded.versions[0]).toBe('1.6.0'); - }); - }); - - describe('versions for ie on windows', () => { - it('should find the correct version for windows', () => { - setup('Windows_NT'); - let downloaded = FileManager.downloadedVersions_(ie, ostype, arch, existingFiles); - expect(downloaded.versions.length).toBe(4); - expect(downloaded.versions[0]).toBe('Win32_2.51.0'); - expect(downloaded.versions[1]).toBe('x64_2.51.0'); - expect(downloaded.versions[2]).toBe('Win32_2.52.0'); - expect(downloaded.versions[3]).toBe('x64_2.52.0'); - }); - }); - }); - - describe('configuring the CDN location', () => { - describe('when no custom CDN is specified', () => { - let defaults = Config.cdnUrls(); - let binaries = FileManager.compileBinaries_('Windows_NT'); - - it('should use the default configuration for Android SDK', () => { - expect(binaries[AndroidSDK.id].cdn).toEqual(defaults[AndroidSDK.id]); - }); - - it('should use the default configuration for Appium', () => { - expect(binaries[Appium.id].cdn).toEqual(defaults[Appium.id]); - }); - - it('should use the default configuration for Chrome Driver', () => { - expect(binaries[ChromeDriver.id].cdn).toEqual(defaults[ChromeDriver.id]); - }); - - it('should use the default configuration for Gecko Driver', () => { - expect(binaries[GeckoDriver.id].cdn).toEqual(defaults[GeckoDriver.id]); - }); - - it('should use the default configuration for IE Driver', () => { - expect(binaries[IEDriver.id].cdn).toEqual(defaults[IEDriver.id]); - }); - - it('should use the default configuration for Selenium Standalone', () => { - expect(binaries[Standalone.id].cdn).toEqual(defaults['selenium']); - }); - }); - - describe('when custom CDN is specified', () => { - it('should configure the CDN for each binary', () => { - let customCDN = 'https://my.corporate.cdn/'; - let binaries = FileManager.compileBinaries_('Windows_NT', customCDN); - - forEachOf(binaries, binary => expect(binary.cdn).toEqual(customCDN, binary.name)); - }); - }); - - function forEachOf(binaries: BinaryMap, fn: (binary: T) => void) { - for (var key in binaries) { - fn(binaries[key]); - } - } - }); - - // TODO(cnishina): download binaries for each os type / arch combination -}); diff --git a/spec/http_utils_spec.ts b/spec/http_utils_spec.ts deleted file mode 100644 index b5108090..00000000 --- a/spec/http_utils_spec.ts +++ /dev/null @@ -1,76 +0,0 @@ -import {Config} from '../lib/config'; -import {HttpUtils} from '../lib/http_utils'; - -describe('http utils', () => { - let fileUrlHttp = 'http://foobar.com'; - let fileUrlHttps = 'https://foobar.com'; - let argProxy = 'http://foobar.arg'; - let envNoProxy = 'http://foobar.com'; - let envHttpProxy = 'http://foobar.env'; - let envHttpsProxy = 'https://foobar.env'; - - it('should return undefined when proxy arg is not used', () => { - let proxy = HttpUtils.resolveProxy(fileUrlHttp); - expect(proxy).toBeUndefined(); - }); - - describe('proxy arg', () => { - let opt_proxy = 'http://bar.foo'; - it('should return the proxy arg', () => { - let proxy = HttpUtils.resolveProxy(fileUrlHttp, opt_proxy); - expect(proxy).toBe(opt_proxy); - }); - - it('should always return the proxy arg with env var set', () => { - Config.httpProxy_ = envHttpProxy; - Config.httpsProxy_ = envHttpsProxy; - Config.noProxy_ = envNoProxy; - let proxy = HttpUtils.resolveProxy(fileUrlHttp, opt_proxy); - expect(proxy).toBe(opt_proxy); - }); - }); - - describe('environment variables', () => { - beforeEach(() => { - Config.httpProxy_ = undefined; - Config.httpsProxy_ = undefined; - Config.noProxy_ = undefined; - }); - - it('should return the HTTP env variable', () => { - Config.httpProxy_ = envHttpProxy; - let proxy = HttpUtils.resolveProxy(fileUrlHttp); - expect(proxy).toBe(envHttpProxy); - }); - - it('should return the HTTPS env variable for https protocol', () => { - Config.httpProxy_ = envHttpsProxy; - let proxy = HttpUtils.resolveProxy(fileUrlHttps); - expect(proxy).toBe(envHttpsProxy); - }); - - it('should return the HTTP env variable for https protocol', () => { - Config.httpProxy_ = envHttpProxy; - let proxy = HttpUtils.resolveProxy(fileUrlHttps); - expect(proxy).toBe(envHttpProxy); - }); - - describe('NO_PROXY environment variable', () => { - beforeEach(() => { - Config.noProxy_ = undefined; - }); - - it('should return undefined when the NO_PROXY matches the fileUrl', () => { - Config.noProxy_ = envNoProxy; - let proxy = HttpUtils.resolveProxy(fileUrlHttp); - expect(proxy).toBeUndefined(); - }); - - it('should return undefined when the no_proxy matches the fileUrl', () => { - Config.noProxy_ = envNoProxy; - let proxy = HttpUtils.resolveProxy(fileUrlHttp); - expect(proxy).toBeUndefined(); - }); - }); - }); -}) diff --git a/spec/support/jasmine.json b/spec/jasmine-e2e.json similarity index 64% rename from spec/support/jasmine.json rename to spec/jasmine-e2e.json index 0564d8cb..c7f76655 100644 --- a/spec/support/jasmine.json +++ b/spec/jasmine-e2e.json @@ -1,7 +1,7 @@ { - "spec_dir": "built/spec", + "spec_dir": "dist", "spec_files": [ - "**/*_spec.js" + "**/*.spec-e2e.js" ], "stopSpecOnExpectationFailure": false, "random": false diff --git a/e2e_spec/support/full.json b/spec/jasmine-int.json similarity index 62% rename from e2e_spec/support/full.json rename to spec/jasmine-int.json index bd10dc59..d7b836a9 100644 --- a/e2e_spec/support/full.json +++ b/spec/jasmine-int.json @@ -1,7 +1,7 @@ { - "spec_dir": "built/e2e_spec", + "spec_dir": "dist", "spec_files": [ - "**/*_spec.js" + "**/*.spec-int.js" ], "stopSpecOnExpectationFailure": false, "random": false diff --git a/e2e_spec/support/headless.json b/spec/jasmine-proxy.json similarity index 60% rename from e2e_spec/support/headless.json rename to spec/jasmine-proxy.json index 32c06353..70c7b733 100644 --- a/e2e_spec/support/headless.json +++ b/spec/jasmine-proxy.json @@ -1,7 +1,7 @@ { - "spec_dir": "built/e2e_spec", + "spec_dir": "dist", "spec_files": [ - "**/server_spec.js" + "**/*.spec-proxy.js" ], "stopSpecOnExpectationFailure": false, "random": false diff --git a/spec/jasmine-unit.json b/spec/jasmine-unit.json new file mode 100644 index 00000000..21ed43d9 --- /dev/null +++ b/spec/jasmine-unit.json @@ -0,0 +1,8 @@ +{ + "spec_dir": "dist", + "spec_files": [ + "**/*.spec-unit.js" + ], + "stopSpecOnExpectationFailure": false, + "random": true +} diff --git a/spec/server/env.ts b/spec/server/env.ts new file mode 100644 index 00000000..7f7b69c3 --- /dev/null +++ b/spec/server/env.ts @@ -0,0 +1,4 @@ +export let httpPort = 8812; +export let httpBaseUrl = 'http://127.0.0.1:' + httpPort; +export let proxyPort = 8814; +export let proxyBaseUrl = 'http://127.0.0.1:' + proxyPort; \ No newline at end of file diff --git a/spec/server/http_server.ts b/spec/server/http_server.ts new file mode 100644 index 00000000..257de5e3 --- /dev/null +++ b/spec/server/http_server.ts @@ -0,0 +1,50 @@ +import * as fs from 'fs'; +import * as http from 'http'; +import * as path from 'path'; +import * as url from 'url'; + +import * as env from './env'; + +const port = process.argv[2] || env.httpPort; + +http.createServer( + (request: http.IncomingMessage, response: http.ServerResponse) => { + const uri = url.parse(request.url).pathname; + let fileName = path.join(process.cwd(), uri); + + try { + if (fs.statSync(fileName).isFile() || + fs.statSync(fileName).isDirectory) { + } else { + response.writeHead(404, {'Content-Type': 'text/plain'}); + response.write('404 Not Found\n'); + response.end(); + return; + } + } catch (err) { + response.writeHead(404, {'Content-Type': 'text/plain'}); + response.write('404 Not Found\n'); + response.end(); + return; + } + + if (fs.statSync(fileName).isDirectory()) { + fileName += '/index.html'; + } + + fs.readFile( + fileName, 'binary', ((err, file) => { + if (err) { + response.writeHead(500, {'Content-Type': 'text/plain'}); + response.write(err + '\n'); + response.end(); + return; + } + + response.writeHead( + 200, {'Content-Length': fs.statSync(fileName).size}); + response.write(file, 'binary'); + response.end(); + })); + }) + .listen(port); diff --git a/spec/server/proxy_server.ts b/spec/server/proxy_server.ts new file mode 100644 index 00000000..cac79af8 --- /dev/null +++ b/spec/server/proxy_server.ts @@ -0,0 +1,27 @@ +import * as http from 'http'; +import * as httpProxy from 'http-proxy'; +import * as loglevel from 'loglevel'; +import * as env from './env'; + +const log = loglevel.getLogger('webdriver-manager-test'); + +const proxy = http.createServer((request, response) => { + let hostHeader = request.headers['host']; + log.debug( + 'request made to proxy: ' + request.url + ', ' + + 'target: ' + hostHeader); + if (hostHeader.startsWith('http://') || hostHeader.startsWith('127.0.0.1')) { + if (!hostHeader.startsWith('http://')) { + hostHeader = 'http://' + hostHeader; + } + httpProxy.createProxyServer({target: hostHeader}).web(request, response); + + } else { + if (!hostHeader.startsWith('https://')) { + hostHeader = 'https://' + hostHeader; + } + httpProxy.createProxyServer({target: hostHeader}).web(request, response); + } +}); + +proxy.listen(env.proxyPort); \ No newline at end of file diff --git a/spec/support/files/appium.json b/spec/support/files/appium.json new file mode 100644 index 00000000..77f0aad7 --- /dev/null +++ b/spec/support/files/appium.json @@ -0,0 +1,5 @@ +{ + "dist-tags": { + "latest": "10.11.12" + } +} \ No newline at end of file diff --git a/spec/support/files/bar.tar.gz b/spec/support/files/bar.tar.gz new file mode 100644 index 00000000..b8712672 Binary files /dev/null and b/spec/support/files/bar.tar.gz differ diff --git a/spec/support/files/bar.zip b/spec/support/files/bar.zip new file mode 100644 index 00000000..e734af1e Binary files /dev/null and b/spec/support/files/bar.zip differ diff --git a/spec/support/files/chromedriver.xml b/spec/support/files/chromedriver.xml new file mode 100644 index 00000000..0b9fc26c --- /dev/null +++ b/spec/support/files/chromedriver.xml @@ -0,0 +1,119 @@ + + + chromedriver + + + false + + 2.0/chromedriver_linux32.zip + 1380149859530000 + 4 + 2013-09-25T22:57:39.349Z + "c0d96102715c4916b872f91f5bf9b12c" + 7262134 + + + 2.0/chromedriver_linux64.zip + 1380149860664000 + 4 + 2013-09-25T22:57:40.449Z + "858ebaf47e13dce7600191ed59974c09" + 7433593 + + + 2.0/chromedriver_mac32.zip + 1380149857425000 + 4 + 2013-09-25T22:57:37.204Z + "efc13db5afc518000d886c2bdcb3a4bc" + 7614601 + + + 2.0/chromedriver_win32.zip + 1380149858370000 + 4 + 2013-09-25T22:57:38.165Z + "bbf8fd0fe525a06dda162619cac2b200" + 3048831 + + + 2.10/chromedriver_linux32.zip + 1398977182889000 + 3 + 2014-05-01T20:46:22.843Z + "4fecc99b066cb1a346035bf022607104" + 2439424 + + + 2.10/chromedriver_linux64.zip + 1398984058713000 + 3 + 2014-05-01T22:40:58.697Z + "058cd8b7b4b9688507701b5e648fd821" + 2301804 + + + 2.10/chromedriver_mac32.zip + 1398983790201000 + 3 + 2014-05-01T22:36:30.170Z + "fd0dafc3ada3619edda2961f2beadc5c" + 4116418 + + + 2.10/chromedriver_win32.zip + 1398985168260000 + 3 + 2014-05-01T22:59:28.242Z + "082e91e5c8994a7879710caeed62e334" + 2843903 + + + 2.10/notes.txt + 1398977183990000 + 3 + 2014-05-01T20:46:23.990Z + "27c57f1c84c22b4427f57c74f246ce1a" + 4055 + + + 2.20/chromedriver_linux32.zip + 1444349629569000 + 3 + 2015-10-09T00:13:49.568Z + "1e8cbdb84c5b70f86030297c4be3a5f9" + 2612186 + + + 2.20/chromedriver_linux64.zip + 1444346907063000 + 3 + 2015-10-08T23:28:27.062Z + "245858cc984bd946df6a1e6719c8e6f5" + 2528233 + + + 2.20/chromedriver_mac32.zip + 1444346568789000 + 3 + 2015-10-08T23:22:48.789Z + "749d4be0a317e92fd3aecaa022385439" + 3516443 + + + 2.20/chromedriver_win32.zip + 1444352264286000 + 3 + 2015-10-09T00:57:44.285Z + "028ee452b8e23890ec5ec6b0717fd295" + 2456101 + + + 2.20/notes.txt + 1444346573055000 + 3 + 2015-10-08T23:22:53.054Z + "fda30d3acf1a10aa9b193b3121787d80" + 1174 + + \ No newline at end of file diff --git a/spec/support/files/foo.xml b/spec/support/files/foo.xml new file mode 100644 index 00000000..f72c0c54 --- /dev/null +++ b/spec/support/files/foo.xml @@ -0,0 +1,10 @@ + + + foobar_driver + + 2.0/foobar.zip + + + 2.1/foobar.zip + + \ No newline at end of file diff --git a/spec/support/files/foo_array.json b/spec/support/files/foo_array.json new file mode 100644 index 00000000..f4570bd7 --- /dev/null +++ b/spec/support/files/foo_array.json @@ -0,0 +1,7 @@ +[{ + "foo": "abc" +}, { + "foo": "def" +}, { + "foo": "ghi" +}] \ No newline at end of file diff --git a/spec/support/files/foo_json.json b/spec/support/files/foo_json.json new file mode 100644 index 00000000..46e2316e --- /dev/null +++ b/spec/support/files/foo_json.json @@ -0,0 +1,8 @@ +{ + "foo": "abc", + "bar": 123, + "baz": { + "num": 101, + "list": ["a", "b", "c"] + } +} \ No newline at end of file diff --git a/spec/support/files/gecko.json b/spec/support/files/gecko.json new file mode 100644 index 00000000..b3d2659e --- /dev/null +++ b/spec/support/files/gecko.json @@ -0,0 +1,734 @@ +[ + { + "url": "https://api.github.com/repos/mozilla/geckodriver/releases/11508322", + "assets_url": "https://api.github.com/repos/mozilla/geckodriver/releases/11508322/assets", + "upload_url": "https://uploads.github.com/repos/mozilla/geckodriver/releases/11508322/assets{?name,label}", + "html_url": "https://github.com/mozilla/geckodriver/releases/tag/v0.21.0", + "id": 11508322, + "node_id": "MDc6UmVsZWFzZTExNTA4MzIy", + "tag_name": "v0.21.0", + "target_commitish": "master", + "name": "", + "draft": false, + "author": { + "login": "AutomatedTester", + "id": 128518, + "node_id": "MDQ6VXNlcjEyODUxOA==", + "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/AutomatedTester", + "html_url": "https://github.com/AutomatedTester", + "followers_url": "https://api.github.com/users/AutomatedTester/followers", + "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", + "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", + "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", + "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", + "repos_url": "https://api.github.com/users/AutomatedTester/repos", + "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", + "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", + "type": "User", + "site_admin": false + }, + "prerelease": false, + "created_at": "2018-06-15T20:46:25Z", + "published_at": "2018-06-15T20:57:11Z", + "assets": [ + { + "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/7551150", + "id": 7551150, + "node_id": "MDEyOlJlbGVhc2VBc3NldDc1NTExNTA=", + "name": "geckodriver-v0.21.0-arm7hf.tar.gz", + "label": "", + "uploader": { + "login": "AutomatedTester", + "id": 128518, + "node_id": "MDQ6VXNlcjEyODUxOA==", + "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/AutomatedTester", + "html_url": "https://github.com/AutomatedTester", + "followers_url": "https://api.github.com/users/AutomatedTester/followers", + "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", + "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", + "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", + "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", + "repos_url": "https://api.github.com/users/AutomatedTester/repos", + "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", + "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", + "type": "User", + "site_admin": false + }, + "content_type": "application/gzip", + "state": "uploaded", + "size": 3192918, + "download_count": 3888, + "created_at": "2018-06-15T20:58:12Z", + "updated_at": "2018-06-15T20:58:13Z", + "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.21.0/geckodriver-v0.21.0-arm7hf.tar.gz" + }, + { + "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/7551164", + "id": 7551164, + "node_id": "MDEyOlJlbGVhc2VBc3NldDc1NTExNjQ=", + "name": "geckodriver-v0.21.0-linux32.tar.gz", + "label": "", + "uploader": { + "login": "AutomatedTester", + "id": 128518, + "node_id": "MDQ6VXNlcjEyODUxOA==", + "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/AutomatedTester", + "html_url": "https://github.com/AutomatedTester", + "followers_url": "https://api.github.com/users/AutomatedTester/followers", + "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", + "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", + "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", + "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", + "repos_url": "https://api.github.com/users/AutomatedTester/repos", + "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", + "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", + "type": "User", + "site_admin": false + }, + "content_type": "application/gzip", + "state": "uploaded", + "size": 3204777, + "download_count": 1049, + "created_at": "2018-06-15T20:59:29Z", + "updated_at": "2018-06-15T20:59:29Z", + "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.21.0/geckodriver-v0.21.0-linux32.tar.gz" + }, + { + "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/7551149", + "id": 7551149, + "node_id": "MDEyOlJlbGVhc2VBc3NldDc1NTExNDk=", + "name": "geckodriver-v0.21.0-linux64.tar.gz", + "label": "", + "uploader": { + "login": "AutomatedTester", + "id": 128518, + "node_id": "MDQ6VXNlcjEyODUxOA==", + "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/AutomatedTester", + "html_url": "https://github.com/AutomatedTester", + "followers_url": "https://api.github.com/users/AutomatedTester/followers", + "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", + "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", + "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", + "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", + "repos_url": "https://api.github.com/users/AutomatedTester/repos", + "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", + "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", + "type": "User", + "site_admin": false + }, + "content_type": "application/gzip", + "state": "uploaded", + "size": 3154655, + "download_count": 614375, + "created_at": "2018-06-15T20:57:57Z", + "updated_at": "2018-06-15T20:57:57Z", + "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.21.0/geckodriver-v0.21.0-linux64.tar.gz" + }, + { + "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/7551144", + "id": 7551144, + "node_id": "MDEyOlJlbGVhc2VBc3NldDc1NTExNDQ=", + "name": "geckodriver-v0.21.0-macos.tar.gz", + "label": "", + "uploader": { + "login": "AutomatedTester", + "id": 128518, + "node_id": "MDQ6VXNlcjEyODUxOA==", + "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/AutomatedTester", + "html_url": "https://github.com/AutomatedTester", + "followers_url": "https://api.github.com/users/AutomatedTester/followers", + "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", + "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", + "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", + "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", + "repos_url": "https://api.github.com/users/AutomatedTester/repos", + "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", + "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", + "type": "User", + "site_admin": false + }, + "content_type": "application/gzip", + "state": "uploaded", + "size": 1874024, + "download_count": 140027, + "created_at": "2018-06-15T20:57:11Z", + "updated_at": "2018-06-15T20:57:11Z", + "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.21.0/geckodriver-v0.21.0-macos.tar.gz" + }, + { + "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/7573052", + "id": 7573052, + "node_id": "MDEyOlJlbGVhc2VBc3NldDc1NzMwNTI=", + "name": "geckodriver-v0.21.0-win32.zip", + "label": null, + "uploader": { + "login": "andreastt", + "id": 399120, + "node_id": "MDQ6VXNlcjM5OTEyMA==", + "avatar_url": "https://avatars3.githubusercontent.com/u/399120?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/andreastt", + "html_url": "https://github.com/andreastt", + "followers_url": "https://api.github.com/users/andreastt/followers", + "following_url": "https://api.github.com/users/andreastt/following{/other_user}", + "gists_url": "https://api.github.com/users/andreastt/gists{/gist_id}", + "starred_url": "https://api.github.com/users/andreastt/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/andreastt/subscriptions", + "organizations_url": "https://api.github.com/users/andreastt/orgs", + "repos_url": "https://api.github.com/users/andreastt/repos", + "events_url": "https://api.github.com/users/andreastt/events{/privacy}", + "received_events_url": "https://api.github.com/users/andreastt/received_events", + "type": "User", + "site_admin": false + }, + "content_type": "application/zip", + "state": "uploaded", + "size": 3099996, + "download_count": 19720, + "created_at": "2018-06-18T12:34:38Z", + "updated_at": "2018-06-18T12:34:40Z", + "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.21.0/geckodriver-v0.21.0-win32.zip" + }, + { + "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/7551152", + "id": 7551152, + "node_id": "MDEyOlJlbGVhc2VBc3NldDc1NTExNTI=", + "name": "geckodriver-v0.21.0-win64.zip", + "label": "", + "uploader": { + "login": "AutomatedTester", + "id": 128518, + "node_id": "MDQ6VXNlcjEyODUxOA==", + "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/AutomatedTester", + "html_url": "https://github.com/AutomatedTester", + "followers_url": "https://api.github.com/users/AutomatedTester/followers", + "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", + "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", + "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", + "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", + "repos_url": "https://api.github.com/users/AutomatedTester/repos", + "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", + "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", + "type": "User", + "site_admin": false + }, + "content_type": "application/zip", + "state": "uploaded", + "size": 3942715, + "download_count": 368746, + "created_at": "2018-06-15T20:58:31Z", + "updated_at": "2018-06-15T20:58:31Z", + "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.21.0/geckodriver-v0.21.0-win64.zip" + } + ], + "tarball_url": "https://api.github.com/repos/mozilla/geckodriver/tarball/v0.21.0", + "zipball_url": "https://api.github.com/repos/mozilla/geckodriver/zipball/v0.21.0", + "body": "Note that with this release of geckodriver the minimum recommended\r\nFirefox and Selenium versions have changed:\r\n\r\n - Firefox 57 (and greater)\r\n - Selenium 3.11 (and greater)\r\n\r\n### Added\r\n\r\n- Support for the chrome element identifier from Firefox.\r\n\r\n- The `unhandledPromptBehavior` capability now accepts `accept and\r\n notify`, `dismiss and notify`, and `ignore` options.\r\n\r\n Note that the unhandled prompt handler is not fully supported in\r\n Firefox at the time of writing.\r\n\r\n### Changed\r\n\r\n- Firefox will now be started with the `-foreground` and `-no-remote`\r\n flags if they have not already been specified by the user in\r\n `moz:firefoxOptions`.\r\n\r\n `-foreground` will ensure the application window gets focus when\r\n Firefox is started, and `-no-remote` will prevent remote commands\r\n to this instance of Firefox and also ensure we always start a new\r\n instance.\r\n\r\n- WebDriver commands that do not have a return value now correctly\r\n return `{value: null}` instead of an empty dictionary.\r\n\r\n- The HTTP server now accepts `Keep-Alive` connections.\r\n\r\n- Firefox remote protocol command mappings updated.\r\n\r\n All Marionette commands changed to make use of the `WebDriver:`\r\n prefixes introduced with Firefox 56.\r\n\r\n- Overhaul of Firefox preferences.\r\n\r\n Already deprecated preferences in Firefox versions earlier than\r\n 57 got removed.\r\n\r\n- [webdriver crate] upgraded to 0.36.0.\r\n\r\n### Fixed\r\n\r\n- Force use of IPv4 network stack.\r\n\r\n On certain system configurations, where `localhost` resolves to\r\n an IPv6 address, geckodriver would attempt to connect to Firefox\r\n on the wrong IP stack, causing the connection attempt to time out\r\n after 60 seconds. We now ensure that geckodriver uses IPv4\r\n consistently to both connect to Firefox and for allocating a free\r\n port.\r\n\r\n- geckodriver failed to locate the correct Firefox binary if it was\r\n found under a _firefox_ or _firefox-bin_ directory, depending on\r\n the system, because it thought the parent directory was the\r\n executable.\r\n\r\n- On Unix systems (macOS, Linux), geckodriver falsely reported\r\n non-executable files as valid binaries.\r\n\r\n- When stdout and stderr is redirected by geckodriver, a bug prevented\r\n the redirections from taking effect.\r\n\r\n[README]: https://github.com/mozilla/geckodriver/blob/master/README.md\r\n[Browser Toolbox]: https://developer.mozilla.org/en-US/docs/Tools/Browser_Toolbox\r\n\r\n[`CloseWindowResponse`]: https://docs.rs/webdriver/newest/webdriver/response/struct.CloseWindowResponse.html\r\n[`CookieResponse`]: https://docs.rs/webdriver/newest/webdriver/response/struct.CookieResponse.html\r\n[`DeleteSession`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.DeleteSession\r\n[`ElementClickIntercepted`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.ElementClickIntercepted\r\n[`ElementNotInteractable`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.ElementNotInteractable\r\n[`FullscreenWindow`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.FullscreenWindow\r\n[`GetNamedCookie`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.GetNamedCookie\r\n[`GetWindowRect`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.GetWindowRect\r\n[`InvalidCoordinates`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.InvalidCoordinates\r\n[`MaximizeWindow`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.MaximizeWindow\r\n[`MinimizeWindow`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.MinimizeWindow\r\n[`NewSession`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.NewSession\r\n[`NoSuchCookie`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.NoSuchCookie\r\n[`RectResponse`]: https://docs.rs/webdriver/0.27.0/webdriver/response/struct.RectResponse.html\r\n[`SendKeysParameters`]: https://docs.rs/webdriver/newest/webdriver/command/struct.SendKeysParameters.html\r\n[`SessionNotCreated`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.SessionNotCreated\r\n[`SetTimeouts`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.SetTimeouts\r\n[`SetWindowRect`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.SetWindowRect\r\n[`StaleElementReference`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.StaleElementReference\r\n[`UnableToCaptureScreen`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.UnableToCaptureScreen\r\n[`UnknownCommand`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.UnknownCommand\r\n[`UnknownError`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.UnknownError\r\n[`WindowRectParameters`]: https://docs.rs/webdriver/newest/webdriver/command/struct.WindowRectParameters.html\r\n\r\n[mozrunner crate]: https://crates.io/crates/mozrunner\r\n[webdriver crate]: https://crates.io/crates/webdriver\r\n\r\n[Actions]: https://w3c.github.io/webdriver/webdriver-spec.html#actions\r\n[Delete Session]: https://w3c.github.io/webdriver/webdriver-spec.html#delete-session\r\n[Element Click]: https://w3c.github.io/webdriver/webdriver-spec.html#element-click\r\n[Get Timeouts]: https://w3c.github.io/webdriver/webdriver-spec.html#get-timeouts\r\n[Get Window Rect]: https://w3c.github.io/webdriver/webdriver-spec.html#get-window-rect\r\n[insecure certificate]: https://w3c.github.io/webdriver/webdriver-spec.html#dfn-insecure-certificate\r\n[Minimize Window]: https://w3c.github.io/webdriver/webdriver-spec.html#minimize-window\r\n[New Session]: https://w3c.github.io/webdriver/webdriver-spec.html#new-session\r\n[Send Alert Text]: https://w3c.github.io/webdriver/webdriver-spec.html#send-alert-text\r\n[Set Timeouts]: https://w3c.github.io/webdriver/webdriver-spec.html#set-timeouts\r\n[Set Window Rect]: https://w3c.github.io/webdriver/webdriver-spec.html#set-window-rect\r\n[Status]: https://w3c.github.io/webdriver/webdriver-spec.html#status\r\n[Take Element Screenshot]: https://w3c.github.io/webdriver/webdriver-spec.html#take-element-screenshot\r\n" + }, + { + "url": "https://api.github.com/repos/mozilla/geckodriver/releases/10444528", + "assets_url": "https://api.github.com/repos/mozilla/geckodriver/releases/10444528/assets", + "upload_url": "https://uploads.github.com/repos/mozilla/geckodriver/releases/10444528/assets{?name,label}", + "html_url": "https://github.com/mozilla/geckodriver/releases/tag/v0.20.1", + "id": 10444528, + "node_id": "MDc6UmVsZWFzZTEwNDQ0NTI4", + "tag_name": "v0.20.1", + "target_commitish": "master", + "name": "", + "draft": false, + "author": { + "login": "AutomatedTester", + "id": 128518, + "node_id": "MDQ6VXNlcjEyODUxOA==", + "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/AutomatedTester", + "html_url": "https://github.com/AutomatedTester", + "followers_url": "https://api.github.com/users/AutomatedTester/followers", + "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", + "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", + "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", + "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", + "repos_url": "https://api.github.com/users/AutomatedTester/repos", + "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", + "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", + "type": "User", + "site_admin": false + }, + "prerelease": false, + "created_at": "2018-04-08T12:36:23Z", + "published_at": "2018-04-08T12:45:28Z", + "assets": [ + { + "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/6772569", + "id": 6772569, + "node_id": "MDEyOlJlbGVhc2VBc3NldDY3NzI1Njk=", + "name": "geckodriver-v0.20.1-arm7hf.tar.gz", + "label": "", + "uploader": { + "login": "AutomatedTester", + "id": 128518, + "node_id": "MDQ6VXNlcjEyODUxOA==", + "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/AutomatedTester", + "html_url": "https://github.com/AutomatedTester", + "followers_url": "https://api.github.com/users/AutomatedTester/followers", + "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", + "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", + "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", + "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", + "repos_url": "https://api.github.com/users/AutomatedTester/repos", + "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", + "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", + "type": "User", + "site_admin": false + }, + "content_type": "application/gzip", + "state": "uploaded", + "size": 2629576, + "download_count": 17878, + "created_at": "2018-04-08T12:48:57Z", + "updated_at": "2018-04-08T12:48:58Z", + "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.20.1/geckodriver-v0.20.1-arm7hf.tar.gz" + }, + { + "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/6772587", + "id": 6772587, + "node_id": "MDEyOlJlbGVhc2VBc3NldDY3NzI1ODc=", + "name": "geckodriver-v0.20.1-linux32.tar.gz", + "label": "", + "uploader": { + "login": "AutomatedTester", + "id": 128518, + "node_id": "MDQ6VXNlcjEyODUxOA==", + "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/AutomatedTester", + "html_url": "https://github.com/AutomatedTester", + "followers_url": "https://api.github.com/users/AutomatedTester/followers", + "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", + "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", + "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", + "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", + "repos_url": "https://api.github.com/users/AutomatedTester/repos", + "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", + "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", + "type": "User", + "site_admin": false + }, + "content_type": "application/gzip", + "state": "uploaded", + "size": 2700572, + "download_count": 50590, + "created_at": "2018-04-08T12:51:38Z", + "updated_at": "2018-04-08T12:51:39Z", + "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.20.1/geckodriver-v0.20.1-linux32.tar.gz" + }, + { + "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/6772557", + "id": 6772557, + "node_id": "MDEyOlJlbGVhc2VBc3NldDY3NzI1NTc=", + "name": "geckodriver-v0.20.1-linux64.tar.gz", + "label": "", + "uploader": { + "login": "AutomatedTester", + "id": 128518, + "node_id": "MDQ6VXNlcjEyODUxOA==", + "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/AutomatedTester", + "html_url": "https://github.com/AutomatedTester", + "followers_url": "https://api.github.com/users/AutomatedTester/followers", + "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", + "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", + "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", + "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", + "repos_url": "https://api.github.com/users/AutomatedTester/repos", + "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", + "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", + "type": "User", + "site_admin": false + }, + "content_type": "application/gzip", + "state": "uploaded", + "size": 2688486, + "download_count": 3278378, + "created_at": "2018-04-08T12:45:27Z", + "updated_at": "2018-04-08T12:45:28Z", + "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.20.1/geckodriver-v0.20.1-linux64.tar.gz" + }, + { + "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/6772558", + "id": 6772558, + "node_id": "MDEyOlJlbGVhc2VBc3NldDY3NzI1NTg=", + "name": "geckodriver-v0.20.1-macos.tar.gz", + "label": "", + "uploader": { + "login": "AutomatedTester", + "id": 128518, + "node_id": "MDQ6VXNlcjEyODUxOA==", + "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/AutomatedTester", + "html_url": "https://github.com/AutomatedTester", + "followers_url": "https://api.github.com/users/AutomatedTester/followers", + "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", + "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", + "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", + "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", + "repos_url": "https://api.github.com/users/AutomatedTester/repos", + "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", + "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", + "type": "User", + "site_admin": false + }, + "content_type": "application/gzip", + "state": "uploaded", + "size": 1560036, + "download_count": 840320, + "created_at": "2018-04-08T12:45:28Z", + "updated_at": "2018-04-08T12:45:28Z", + "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.20.1/geckodriver-v0.20.1-macos.tar.gz" + }, + { + "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/6772612", + "id": 6772612, + "node_id": "MDEyOlJlbGVhc2VBc3NldDY3NzI2MTI=", + "name": "geckodriver-v0.20.1-win32.zip", + "label": null, + "uploader": { + "login": "andreastt", + "id": 399120, + "node_id": "MDQ6VXNlcjM5OTEyMA==", + "avatar_url": "https://avatars3.githubusercontent.com/u/399120?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/andreastt", + "html_url": "https://github.com/andreastt", + "followers_url": "https://api.github.com/users/andreastt/followers", + "following_url": "https://api.github.com/users/andreastt/following{/other_user}", + "gists_url": "https://api.github.com/users/andreastt/gists{/gist_id}", + "starred_url": "https://api.github.com/users/andreastt/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/andreastt/subscriptions", + "organizations_url": "https://api.github.com/users/andreastt/orgs", + "repos_url": "https://api.github.com/users/andreastt/repos", + "events_url": "https://api.github.com/users/andreastt/events{/privacy}", + "received_events_url": "https://api.github.com/users/andreastt/received_events", + "type": "User", + "site_admin": false + }, + "content_type": "application/zip", + "state": "uploaded", + "size": 2483907, + "download_count": 123963, + "created_at": "2018-04-08T12:55:10Z", + "updated_at": "2018-04-08T12:55:12Z", + "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.20.1/geckodriver-v0.20.1-win32.zip" + }, + { + "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/6772583", + "id": 6772583, + "node_id": "MDEyOlJlbGVhc2VBc3NldDY3NzI1ODM=", + "name": "geckodriver-v0.20.1-win64.zip", + "label": "", + "uploader": { + "login": "AutomatedTester", + "id": 128518, + "node_id": "MDQ6VXNlcjEyODUxOA==", + "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/AutomatedTester", + "html_url": "https://github.com/AutomatedTester", + "followers_url": "https://api.github.com/users/AutomatedTester/followers", + "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", + "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", + "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", + "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", + "repos_url": "https://api.github.com/users/AutomatedTester/repos", + "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", + "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", + "type": "User", + "site_admin": false + }, + "content_type": "application/zip", + "state": "uploaded", + "size": 3102871, + "download_count": 2386311, + "created_at": "2018-04-08T12:50:12Z", + "updated_at": "2018-04-08T12:50:12Z", + "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.20.1/geckodriver-v0.20.1-win64.zip" + } + ], + "tarball_url": "https://api.github.com/repos/mozilla/geckodriver/tarball/v0.20.1", + "zipball_url": "https://api.github.com/repos/mozilla/geckodriver/zipball/v0.20.1", + "body": "### Fixed\r\n\r\n- Avoid attempting to kill Firefox process that has stopped.\r\n\r\n With the change to allow Firefox enough time to shut down in\r\n 0.20.0, geckodriver started unconditionally killing the process\r\n to reap its exit status. This caused geckodriver to inaccurately\r\n report a successful Firefox shutdown as a failure.\r\n\r\n The regression should not have caused any functional problems, but\r\n the termination cause and the exit status are now reported correctly." + }, + { + "url": "https://api.github.com/repos/mozilla/geckodriver/releases/10054801", + "assets_url": "https://api.github.com/repos/mozilla/geckodriver/releases/10054801/assets", + "upload_url": "https://uploads.github.com/repos/mozilla/geckodriver/releases/10054801/assets{?name,label}", + "html_url": "https://github.com/mozilla/geckodriver/releases/tag/v0.20.0", + "id": 10054801, + "node_id": "MDc6UmVsZWFzZTEwMDU0ODAx", + "tag_name": "v0.20.0", + "target_commitish": "master", + "name": "", + "draft": false, + "author": { + "login": "AutomatedTester", + "id": 128518, + "node_id": "MDQ6VXNlcjEyODUxOA==", + "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/AutomatedTester", + "html_url": "https://github.com/AutomatedTester", + "followers_url": "https://api.github.com/users/AutomatedTester/followers", + "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", + "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", + "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", + "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", + "repos_url": "https://api.github.com/users/AutomatedTester/repos", + "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", + "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", + "type": "User", + "site_admin": false + }, + "prerelease": false, + "created_at": "2018-03-12T23:13:32Z", + "published_at": "2018-03-12T23:21:47Z", + "assets": [ + { + "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/6489623", + "id": 6489623, + "node_id": "MDEyOlJlbGVhc2VBc3NldDY0ODk2MjM=", + "name": "geckodriver-v0.20.0-arm7hf.tar.gz", + "label": "", + "uploader": { + "login": "AutomatedTester", + "id": 128518, + "node_id": "MDQ6VXNlcjEyODUxOA==", + "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/AutomatedTester", + "html_url": "https://github.com/AutomatedTester", + "followers_url": "https://api.github.com/users/AutomatedTester/followers", + "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", + "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", + "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", + "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", + "repos_url": "https://api.github.com/users/AutomatedTester/repos", + "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", + "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", + "type": "User", + "site_admin": false + }, + "content_type": "application/gzip", + "state": "uploaded", + "size": 2849818, + "download_count": 7665, + "created_at": "2018-03-12T23:21:46Z", + "updated_at": "2018-03-12T23:21:46Z", + "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.20.0/geckodriver-v0.20.0-arm7hf.tar.gz" + }, + { + "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/6489633", + "id": 6489633, + "node_id": "MDEyOlJlbGVhc2VBc3NldDY0ODk2MzM=", + "name": "geckodriver-v0.20.0-linux32.tar.gz", + "label": "", + "uploader": { + "login": "AutomatedTester", + "id": 128518, + "node_id": "MDQ6VXNlcjEyODUxOA==", + "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/AutomatedTester", + "html_url": "https://github.com/AutomatedTester", + "followers_url": "https://api.github.com/users/AutomatedTester/followers", + "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", + "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", + "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", + "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", + "repos_url": "https://api.github.com/users/AutomatedTester/repos", + "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", + "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", + "type": "User", + "site_admin": false + }, + "content_type": "application/gzip", + "state": "uploaded", + "size": 2855060, + "download_count": 3993, + "created_at": "2018-03-12T23:23:17Z", + "updated_at": "2018-03-12T23:23:17Z", + "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.20.0/geckodriver-v0.20.0-linux32.tar.gz" + }, + { + "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/6489638", + "id": 6489638, + "node_id": "MDEyOlJlbGVhc2VBc3NldDY0ODk2Mzg=", + "name": "geckodriver-v0.20.0-linux64.tar.gz", + "label": "", + "uploader": { + "login": "AutomatedTester", + "id": 128518, + "node_id": "MDQ6VXNlcjEyODUxOA==", + "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/AutomatedTester", + "html_url": "https://github.com/AutomatedTester", + "followers_url": "https://api.github.com/users/AutomatedTester/followers", + "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", + "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", + "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", + "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", + "repos_url": "https://api.github.com/users/AutomatedTester/repos", + "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", + "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", + "type": "User", + "site_admin": false + }, + "content_type": "application/gzip", + "state": "uploaded", + "size": 2804546, + "download_count": 2816108, + "created_at": "2018-03-12T23:25:38Z", + "updated_at": "2018-03-12T23:25:38Z", + "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.20.0/geckodriver-v0.20.0-linux64.tar.gz" + }, + { + "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/6489631", + "id": 6489631, + "node_id": "MDEyOlJlbGVhc2VBc3NldDY0ODk2MzE=", + "name": "geckodriver-v0.20.0-macos.tar.gz", + "label": "", + "uploader": { + "login": "AutomatedTester", + "id": 128518, + "node_id": "MDQ6VXNlcjEyODUxOA==", + "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/AutomatedTester", + "html_url": "https://github.com/AutomatedTester", + "followers_url": "https://api.github.com/users/AutomatedTester/followers", + "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", + "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", + "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", + "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", + "repos_url": "https://api.github.com/users/AutomatedTester/repos", + "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", + "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", + "type": "User", + "site_admin": false + }, + "content_type": "application/gzip", + "state": "uploaded", + "size": 1536808, + "download_count": 673101, + "created_at": "2018-03-12T23:22:46Z", + "updated_at": "2018-03-12T23:22:47Z", + "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.20.0/geckodriver-v0.20.0-macos.tar.gz" + }, + { + "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/6496559", + "id": 6496559, + "node_id": "MDEyOlJlbGVhc2VBc3NldDY0OTY1NTk=", + "name": "geckodriver-v0.20.0-win32.zip", + "label": null, + "uploader": { + "login": "andreastt", + "id": 399120, + "node_id": "MDQ6VXNlcjM5OTEyMA==", + "avatar_url": "https://avatars3.githubusercontent.com/u/399120?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/andreastt", + "html_url": "https://github.com/andreastt", + "followers_url": "https://api.github.com/users/andreastt/followers", + "following_url": "https://api.github.com/users/andreastt/following{/other_user}", + "gists_url": "https://api.github.com/users/andreastt/gists{/gist_id}", + "starred_url": "https://api.github.com/users/andreastt/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/andreastt/subscriptions", + "organizations_url": "https://api.github.com/users/andreastt/orgs", + "repos_url": "https://api.github.com/users/andreastt/repos", + "events_url": "https://api.github.com/users/andreastt/events{/privacy}", + "received_events_url": "https://api.github.com/users/andreastt/received_events", + "type": "User", + "site_admin": false + }, + "content_type": "application/zip", + "state": "uploaded", + "size": 2510890, + "download_count": 78203, + "created_at": "2018-03-13T14:41:25Z", + "updated_at": "2018-03-13T14:41:57Z", + "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.20.0/geckodriver-v0.20.0-win32.zip" + }, + { + "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/6489635", + "id": 6489635, + "node_id": "MDEyOlJlbGVhc2VBc3NldDY0ODk2MzU=", + "name": "geckodriver-v0.20.0-win64.zip", + "label": "", + "uploader": { + "login": "AutomatedTester", + "id": 128518, + "node_id": "MDQ6VXNlcjEyODUxOA==", + "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/AutomatedTester", + "html_url": "https://github.com/AutomatedTester", + "followers_url": "https://api.github.com/users/AutomatedTester/followers", + "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", + "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", + "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", + "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", + "repos_url": "https://api.github.com/users/AutomatedTester/repos", + "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", + "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", + "type": "User", + "site_admin": false + }, + "content_type": "application/zip", + "state": "uploaded", + "size": 3401446, + "download_count": 1034210, + "created_at": "2018-03-12T23:24:13Z", + "updated_at": "2018-03-12T23:24:14Z", + "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.20.0/geckodriver-v0.20.0-win64.zip" + } + ], + "tarball_url": "https://api.github.com/repos/mozilla/geckodriver/tarball/v0.20.0", + "zipball_url": "https://api.github.com/repos/mozilla/geckodriver/zipball/v0.20.0", + "body": "### Added\r\n\r\n- New `--jsdebugger` flag to open the [Browser Toolbox] when Firefox\r\n launches. This is useful for debugging Marionette internals.\r\n\r\n- Introduced the temporary, boolean capability\r\n `moz:useNonSpecCompliantPointerOrigin` to disable the WebDriver\r\n conforming behavior of calculating the Pointer Origin.\r\n\r\n### Changed\r\n\r\n- HTTP status code for the [`StaleElementReference`] error changed\r\n from 400 (Bad Request) to 404 (Not Found).\r\n\r\n- Backtraces from geckodriver no longer substitute for missing\r\n Marionette stacktraces.\r\n\r\n- [webdriver crate] upgraded to 0.35.0.\r\n\r\n### Fixed\r\n\r\n- The Firefox process is now given ample time to shut down, allowing\r\n enough time for the Firefox shutdown hang monitor to kick in.\r\n\r\n Firefox has an integrated background monitor that observes\r\n long-running threads during shutdown. These threads will be\r\n killed after 63 seconds in the event of a hang. To allow Firefox\r\n to shut down these threads on its own, geckodriver has to wait\r\n that time and some additional seconds.\r\n\r\n- Grapheme clusters are now accepted as input for keyboard input\r\n to actions.\r\n\r\n Input to the `value` field of the `keyDown` and `keyUp` action\r\n primitives used to only accept single characters, which means\r\n geckodriver would error when a valid grapheme cluster was sent in,\r\n for example with the tamil nadu character U+0BA8 U+0BBF.\r\n\r\n Thanks to Greg Fraley for fixing this bug.\r\n\r\n- Improved error messages for malformed capability values.\r\n\r\n[README]: https://github.com/mozilla/geckodriver/blob/master/README.md\r\n[Browser Toolbox]: https://developer.mozilla.org/en-US/docs/Tools/Browser_Toolbox\r\n\r\n[`CloseWindowResponse`]: https://docs.rs/webdriver/newest/webdriver/response/struct.CloseWindowResponse.html\r\n[`CookieResponse`]: https://docs.rs/webdriver/newest/webdriver/response/struct.CookieResponse.html\r\n[`DeleteSession`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.DeleteSession\r\n[`ElementClickIntercepted`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.ElementClickIntercepted\r\n[`ElementNotInteractable`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.ElementNotInteractable\r\n[`FullscreenWindow`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.FullscreenWindow\r\n[`GetNamedCookie`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.GetNamedCookie\r\n[`GetWindowRect`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.GetWindowRect\r\n[`InvalidCoordinates`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.InvalidCoordinates\r\n[`MaximizeWindow`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.MaximizeWindow\r\n[`MinimizeWindow`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.MinimizeWindow\r\n[`NewSession`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.NewSession\r\n[`NoSuchCookie`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.NoSuchCookie\r\n[`RectResponse`]: https://docs.rs/webdriver/0.27.0/webdriver/response/struct.RectResponse.html\r\n[`SendKeysParameters`]: https://docs.rs/webdriver/newest/webdriver/command/struct.SendKeysParameters.html\r\n[`SessionNotCreated`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.SessionNotCreated\r\n[`SetTimeouts`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.SetTimeouts\r\n[`SetWindowRect`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.SetWindowRect\r\n[`StaleElementReference`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.StaleElementReference\r\n[`UnableToCaptureScreen`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.UnableToCaptureScreen\r\n[`UnknownCommand`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.UnknownCommand\r\n[`UnknownError`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.UnknownError\r\n[`WindowRectParameters`]: https://docs.rs/webdriver/newest/webdriver/command/struct.WindowRectParameters.html\r\n\r\n[mozrunner crate]: https://crates.io/crates/mozrunner\r\n[webdriver crate]: https://crates.io/crates/webdriver\r\n\r\n[Actions]: https://w3c.github.io/webdriver/webdriver-spec.html#actions\r\n[Delete Session]: https://w3c.github.io/webdriver/webdriver-spec.html#delete-session\r\n[Element Click]: https://w3c.github.io/webdriver/webdriver-spec.html#element-click\r\n[Get Timeouts]: https://w3c.github.io/webdriver/webdriver-spec.html#get-timeouts\r\n[Get Window Rect]: https://w3c.github.io/webdriver/webdriver-spec.html#get-window-rect\r\n[insecure certificate]: https://w3c.github.io/webdriver/webdriver-spec.html#dfn-insecure-certificate\r\n[Minimize Window]: https://w3c.github.io/webdriver/webdriver-spec.html#minimize-window\r\n[New Session]: https://w3c.github.io/webdriver/webdriver-spec.html#new-session\r\n[Send Alert Text]: https://w3c.github.io/webdriver/webdriver-spec.html#send-alert-text\r\n[Set Timeouts]: https://w3c.github.io/webdriver/webdriver-spec.html#set-timeouts\r\n[Set Window Rect]: https://w3c.github.io/webdriver/webdriver-spec.html#set-window-rect\r\n[Status]: https://w3c.github.io/webdriver/webdriver-spec.html#status\r\n[Take Element Screenshot]: https://w3c.github.io/webdriver/webdriver-spec.html#take-element-screenshot\r\n[WebDriver errors]: https://w3c.github.io/webdriver/webdriver-spec.html#handling-errors\r\n\r\n[Jason Juang]: https://github.com/juangj\r\n[Joshua Bruning]: https://github.com/joshbruning\r\n[Kalpesh Krishna]: https://github.com/martiansideofthemoon\r\n[Mike Pennisi]: https://github.com/jugglinmike\r\n[Sven Jost]: https://github/mythsunwind\r\n[Vlad Filippov]: https://github.com/vladikoff" + } +] \ No newline at end of file diff --git a/spec/support/helpers/port_finder.ts b/spec/support/helpers/port_finder.ts new file mode 100644 index 00000000..e462681d --- /dev/null +++ b/spec/support/helpers/port_finder.ts @@ -0,0 +1,53 @@ +// Borrowed from driver_provider/local from protractor. +import * as net from 'net'; + +/** + * Find the port number that is available for the port range provided. + * Assumes that the portRangeStart < portRangeEnd and that the portRangeEnd + * should also be checked. + * @param portRangeStart + * @param portRangeEnd + */ +export async function findPort( + portRangeStart: number, portRangeEnd: number): Promise { + // When no start is provided but an end range is provided, create + // an arbitrary start point. + if (!portRangeStart && portRangeEnd) { + portRangeStart = portRangeEnd - 1000; + } + // When no end is provided but a start range is provided, create + // an arbitrary end point. + if (portRangeStart && !portRangeEnd) { + portRangeEnd = portRangeStart + 1000; + } + // If no start and end are provided, create a range from 4000 to 5000. + if (!portRangeStart && !portRangeEnd) { + portRangeStart = 4000; + portRangeEnd = 5000; + } + + let portFound = null; + for (let port = portRangeStart; port <= portRangeEnd; port++) { + let server: net.Server; + const result = await new Promise(resolve => { + // Start a server to check if we can listen to a port. + server = net.createServer() + .listen(port) + .on('error', + () => { + // EADDRINUSE or EACCES, move on. + resolve(false); + }) + .on('listening', () => { + resolve(true); + }); + }); + if (result) { + // If the server is listening, close the server. + server.close(); + portFound = port; + break; + } + } + return portFound; +} \ No newline at end of file diff --git a/spec/support/helpers/test_utils.ts b/spec/support/helpers/test_utils.ts new file mode 100644 index 00000000..908a10a5 --- /dev/null +++ b/spec/support/helpers/test_utils.ts @@ -0,0 +1,37 @@ +import * as childProcess from 'child_process'; +import * as loglevel from 'loglevel'; +import {requestBody} from '../../../lib/provider/utils/http_utils'; + +const log = loglevel.getLogger('webdriver-manager-test'); + +/** + * A command line to run. Example 'npm start', the task='npm' and the + * opt_arg=['start'] + * @param task The task string. + * @param optArg Optional task args. + * @param optIo Optional io arg. By default, it should log to console. + * @returns The child process. + */ +export function spawnProcess(task: string, optArg?: string[], optIo?: string) { + optArg = typeof optArg !== 'undefined' ? optArg : []; + let stdio: childProcess.StdioOptions = 'inherit'; + if (optIo === 'ignore') { + stdio = 'ignore'; + } + return childProcess.spawn(task, optArg, {stdio}); +} + +/** + * Check the connectivity by making a request to https://github.com. + * If the request results in an error, return false. + */ +export function checkConnectivity(testName: string): Promise { + return requestBody('https://github.com', {}) + .then(() => { + return true; + }) + .catch(() => { + log.warn('[WARN] no connectivity. skipping test ' + testName); + return false; + }); +} \ No newline at end of file diff --git a/spec/webdriver_spec.ts b/spec/webdriver_spec.ts deleted file mode 100644 index 463a8663..00000000 --- a/spec/webdriver_spec.ts +++ /dev/null @@ -1,29 +0,0 @@ -import * as path from 'path'; -import {cli} from '../lib/cli_instance'; -import {spawnSync} from '../lib/utils'; - -describe('cli', () => { - describe('help', () => { - it('should have usage and commands', () => { - let lines = spawnSync(process.execPath, ['built/lib/webdriver.js', 'help'], 'pipe') - .output[1] - .toString() - .split('\n'); - - // very specific to make sure the - let index = 0; - expect(lines[index++].indexOf('Usage:')).toBe(0); - index++; - expect(lines[index++].indexOf('Commands:')).toBe(0); - for (let cmd in cli.programs) { - expect(lines[index++].indexOf(cmd)).toBe(2); - } - index++; - expect(lines[index++].indexOf('Options:')).toBe(0); - let options = cli.getOptions(); - for (let opt in options) { - expect(lines[index++].indexOf('--' + opt)).toBe(2); - } - }); - }); -}); diff --git a/tsconfig.json b/tsconfig.json index 96a845be..2e85db7f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,20 +1,31 @@ { "compilerOptions": { - "target": "es6", + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "declaration": true, + "forceConsistentCasingInFileNames": true, + "lib": [ "es2017" ], "module": "commonjs", "moduleResolution": "node", - "sourceMap": true, - "declaration": true, - "removeComments": false, + "noEmitOnError": true, + "noFallthroughCasesInSwitch": true, "noImplicitAny": true, - "outDir": "built/", + "noImplicitReturns": true, + "outDir": "./dist", + "pretty": true, + "removeComments": false, + "sourceMap": true, + "target": "es2017", + "typeRoots": [ + "./node_modules/@types" + ], "types": [ - "adm-zip", "chalk", "glob", "jasmine", "minimist", - "node", "q", "request", "rimraf", "semver" - ] + "jasmine", + "node" + ], }, "exclude": [ - "built", + "dist", "node_modules" ] -} +} \ No newline at end of file diff --git a/tslint.json b/tslint.json new file mode 100644 index 00000000..c06a7d7b --- /dev/null +++ b/tslint.json @@ -0,0 +1,54 @@ +{ + "rules": { + "array-type": [true, "array-simple"], + "arrow-return-shorthand": true, + "ban": [true, + {"name": "parseInt", "message": "tsstyle#type-coercion"}, + {"name": "parseFloat", "message": "tsstyle#type-coercion"}, + {"name": "Array", "message": "tsstyle#array-constructor"} + ], + "ban-types": [true, + ["Object", "Use {} instead."], + ["String", "Use 'string' instead."], + ["Number", "Use 'number' instead."], + ["Boolean", "Use 'boolean' instead."] + ], + "class-name": true, + "curly": [true, "ignore-same-line"], + "forin": true, + "interface-name": [true, "never-prefix"], + "jsdoc-format": true, + "label-position": true, + "member-access": [true, "no-public"], + "new-parens": true, + "no-angle-bracket-type-assertion": true, + "no-any": true, + "no-arg": true, + "no-conditional-assignment": true, + "no-construct": true, + "no-debugger": true, + "no-default-export": true, + "no-duplicate-variable": true, + "no-inferrable-types": true, + "no-namespace": [true, "allow-declarations"], + "no-reference": true, + "no-string-throw": true, + "no-unused-expression": true, + "no-var-keyword": true, + "object-literal-shorthand": true, + "only-arrow-functions": [true, "allow-declarations", "allow-named-functions"], + "prefer-const": true, + "radix": true, + "semicolon": [true, "always", "ignore-bound-class-methods"], + "switch-default": true, + "triple-equals": [true, "allow-null-check"], + "use-isnan": true, + "variable-name": [ + true, + "check-format", + "ban-keywords", + "allow-leading-underscore", + "allow-trailing-underscore" + ] + } +} \ No newline at end of file