diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8ae1a7e --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/metrics/appsody_reports +/metrics/node_modules +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md index c5bff4d..052f5b1 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,33 @@ # utils + +## metrics Repository to store various utilities related to Appsody project + + +### Data Pulling scripts + +In order to execute any of these scripts, you'll need to first export your GitHub API Key to an environment variable called `GITHUB_API_KEY`. + + - run_all.sh + + Usage: + - Can be passed a date in the form of `YYY-MM-DD`. This date would have to correspond to a folder in `metrics/appsody_reports/` which contains data from a previous run, to provide the total amount downloads (dockerhub & GitHub), or gained/lost (stars/watchers/forks) since that date. + + - For example: `sh run_all.sh 2020-02-10` would give the results of the current most up-to-date information, as well as a data compared to the results on the 10th of February 2020. + + Runs all of the `.js` scripts in this folder consecutively, then runs `../data_pulling_utils/json_report_to_csv.py` which creates a `.csv` file for any comparisons again'st the given date (if the `.json` files from that date are found). + + - appsody_dockerhub.js + + Gets the pull count for each of the repositories on [appsody's dockerhub](https://hub.docker.com/u/appsody), and when the repository was last updated. + + - appsody_releases.js + + Gets the the download count for each of the binaries for every release of appsody from [the GitHub releases](https://github.com/appsody/appsody/releases). + + - appsody_stars_watchers_forks.js + + Gets the number of stars, watchers, and forks for the [appsody/stacks repository](https://github.com/appsody/stacks) and the [appsody/appsody repository](https://github.com/appsody/appsody). + + +All of the results are stored in an `appsody_reports/DATE/` folder. diff --git a/metrics/data_pulling_scripts/appsody_dockerhub.js b/metrics/data_pulling_scripts/appsody_dockerhub.js new file mode 100644 index 0000000..d803066 --- /dev/null +++ b/metrics/data_pulling_scripts/appsody_dockerhub.js @@ -0,0 +1,43 @@ +const axios = require('axios'); +const tools = require('../data_pulling_utils/data_pulling_tools'); + +const args = process.argv.slice(2) + +retrieveData(); + + async function retrieveData() { + + + const AuthStr = `token ${process.env.GITHUB_API_KEY}` + const URL = 'https://hub.docker.com/v2/repositories/appsody/?page=1&page_size=100'; + + axios.get(URL, { headers: { Authorization: AuthStr } }) + .then(response => { + + dockerHubResultsArray = []; + for (let item of response.data.results) { + + const date = new Date(item.last_updated) + var readableTimestamp = tools.addZeroPrefix(date.getDate()) + "-" + tools.addZeroPrefix((date.getMonth() +1)) + "-" + tools.addZeroPrefix(date.getFullYear()); + + const single = { + "name": item.name, + "pull_count": item.pull_count, + "last_updated": readableTimestamp + } + dockerHubResultsArray.push(single); + } + + tools.createLogFile("dockerhub_appsody.json", dockerHubResultsArray, function(err) { + console.log(err); + }); + + if(args[0] != undefined) { + tools.createComparisonFile("dockerhub_appsody", dockerHubResultsArray, "name", args[0]); + } + + }) + .catch((error) => { + console.log('error ' + error); + }); + } \ No newline at end of file diff --git a/metrics/data_pulling_scripts/appsody_releases.js b/metrics/data_pulling_scripts/appsody_releases.js new file mode 100644 index 0000000..0dbeeaa --- /dev/null +++ b/metrics/data_pulling_scripts/appsody_releases.js @@ -0,0 +1,44 @@ +const axios = require('axios'); +const tools = require('../data_pulling_utils/data_pulling_tools'); + +const args = process.argv.slice(2) + +retrieveData(); + + async function retrieveData() { + + + const AuthStr = `token ${process.env.GITHUB_API_KEY}` + const URL = 'https://api.github.com/repos/appsody/appsody/releases'; + + axios.get(URL, { headers: { Authorization: AuthStr } }) + .then(response => { + releaseResultsArray = []; + for (let item of response.data) { + + let name = item.tag_name + + for (let asset of item.assets) { + const single = { + "release": name, + "cli_binary": asset.name, + "download_count": asset.download_count + } + + releaseResultsArray.push(single); + } + } + + tools.createLogFile("releases_appsody.json", releaseResultsArray, function(err) { + console.log(err); + }); + + if(args[0] != undefined) { + tools.createComparisonFile("releases_appsody", releaseResultsArray, "cli_binary", args[0]); + } + + }) + .catch((error) => { + console.log('error ' + error); + }); + } \ No newline at end of file diff --git a/metrics/data_pulling_scripts/appsody_stars_watchers_forks.js b/metrics/data_pulling_scripts/appsody_stars_watchers_forks.js new file mode 100644 index 0000000..1b8e351 --- /dev/null +++ b/metrics/data_pulling_scripts/appsody_stars_watchers_forks.js @@ -0,0 +1,79 @@ +const { graphql } = require('@octokit/graphql') +const tools = require('../data_pulling_utils/data_pulling_tools'); + +const args = process.argv.slice(2) + +async function asyncForEach(array, callback) { + for (let index = 0; index < array.length; index++) { + await callback(array[index], index, array); + } +} + +const queryTotals = async (repo, org) => { + const graphqlWithAuth = graphql.defaults({ + headers: { + authorization: `token ${process.env.GITHUB_API_KEY}` + } + }) + let data + try { + data = await graphqlWithAuth(` + { + repository(name: "${repo}", owner: "${org}") { + forks { + totalCount + } + stargazers { + totalCount + } + watchers { + totalCount + } + } + }`) + } catch (err) { + if (err.name === 'HttpError') { + setTimeout(() => { + queryTotals(repo, org) + }, 10000) + } else { + throw (err) + } + } finally { + + const single = { + "repo": repo, + "stars": data.repository.stargazers.totalCount, + "watchers": data.repository.watchers.totalCount, + "forks": data.repository.forks.totalCount, + } + return single; + } +} + +var queries = [ + ['appsody', 'stacks'], + ['appsody', 'appsody'] +]; +results = []; + +const callQueries = async () => { + + const start = async () => { + await asyncForEach(queries, async (q) => { + let res = await (queryTotals(q[1], q[0])); + results.push(res) + }); + + tools.createLogFile(`stars_watchers_forks.json`, results, function(err) { + console.log(err); + }); + + if(args[0] != undefined) { + tools.createComparisonFile("stars_watchers_forks", results, "repo", args[0]); + } + } + start(); +} + +callQueries(); \ No newline at end of file diff --git a/metrics/data_pulling_scripts/run_all.sh b/metrics/data_pulling_scripts/run_all.sh new file mode 100644 index 0000000..fa7fa8a --- /dev/null +++ b/metrics/data_pulling_scripts/run_all.sh @@ -0,0 +1,14 @@ +COMPARISON=$1 + +for entry in ./*.js +do + echo "$entry" + if [ -z "$COMPARISON" ] + then + `node $entry` + else + echo "Comparison $COMPARISON was given" + `node $entry $COMPARISON` + fi +done +`python ../data_pulling_utils/json_report_to_csv.py ../appsody_reports/` \ No newline at end of file diff --git a/metrics/data_pulling_utils/data_pulling_tools.js b/metrics/data_pulling_utils/data_pulling_tools.js new file mode 100644 index 0000000..5b0a4bd --- /dev/null +++ b/metrics/data_pulling_utils/data_pulling_tools.js @@ -0,0 +1,164 @@ +const fs = require('fs'); +const moment = require('moment'); + +const folderPath = "../appsody_reports/" + +module.exports = { + createLogFile: function(extension, array, cb) { + + ensurePathExists(folderPath, 0744, function(err) { + if (err) { + cb(err) + } + }); + + d = new Date().toISOString().slice(0, 10); + + var filePath = `${folderPath}${d}/` + ensurePathExists(filePath, 0744, function(err) { + if (err) { + cb(err) + } else { + var filename = `${filePath}${extension}` + + writeFile(filename, array, function(err) { + if (err) { + cb(err) + } + }); + } + }) + }, + addZeroPrefix: function(num) { + if (num.toString().length < 2) { + return `0${num}`; + } else { + return num; + } + }, + createComparisonFile: function(filename, newRes, id, previous) { + oldResPath = `../appsody_reports/${previous}/${filename}.json`; + fs.access(oldResPath, fs.F_OK, (err) => { + if (err) { + console.error("No file found to make comparison.") + return + } + + let rawdata = fs.readFileSync(oldResPath); + let oldRes = JSON.parse(rawdata); + + var comparison = compareToLast(oldRes, newRes, id); + this.createLogFile(`${filename}_comparison.json`, comparison, function(err) { + console.log(err); + }); + }) + } +}; + +function ensurePathExists(path, mask, cb) { + if (typeof mask == 'function') { + cb = mask; + mask = 0777; + } + fs.mkdir(path, mask, function(err) { + if (err) { + if (err.code == 'EEXIST') { + cb(null); + } else { + cb(err); + } + } else { + cb(null); + } + }); +} + +function writeFile(filename, array, cb) { + fs.writeFile(filename, JSON.stringify(array, null, 4), 'utf8', (err) => { + if (err) { + console.log("An error occured while writing JSON Object to File."); + cb(err); + } else { + cb(null); + } + }); +} + +function compareToLast(oldRes, newRes, id) { + comparisons = []; + + for (let i = 0; i < newRes.length; i++) { + let single = {}; + switch (id) { + case "repo": + + var noMatch = true; + for (let j = 0; j < oldRes.length; j++) { + if (newRes[i].repo === oldRes[j].repo) { + noMatch = false; + + single = { + "repo": newRes[i].repo, + "stars": newRes[i].stars - oldRes[j].stars, + "watchers": newRes[i].watchers - oldRes[j].watchers, + "forks": newRes[i].forks - oldRes[j].forks + } + } + } + if (noMatch) { + single = newRes[i]; + } + comparisons.push(single); + break; + case "name": + var noMatch = true; + for (let j = 0; j < oldRes.length; j++) { + if (newRes[i].name === oldRes[j].name) { + noMatch = false; + + single = { + "name": newRes[i].name, + "pull_count": newRes[i].pull_count - oldRes[j].pull_count, + "last_updated": newRes[i].last_updated + } + } + } + if (noMatch) { + + single = { + "name": newRes[i].name, + "pull_count": newRes[i].pull_count, + "last_updated": newRes[i].last_updated + } + } + + comparisons.push(single); + break; + + case "cli_binary": + var noMatch = true; + for (let j = 0; j < oldRes.length; j++) { + if (newRes[i].cli_binary === oldRes[j].cli_binary && newRes[i].release === oldRes[j].release) { + noMatch = false; + + single = { + "release": newRes[i].release, + "cli_binary": newRes[i].cli_binary, + "download_count": newRes[i].download_count - oldRes[j].download_count + } + } + } + if (noMatch) { + single = { + "release": newRes[i].release, + "cli_binary": newRes[i].cli_binary, + "download_count": newRes[i].download_count + } + } + + comparisons.push(single); + break; + } + } + return comparisons; +} \ No newline at end of file diff --git a/metrics/data_pulling_utils/json_report_to_csv.py b/metrics/data_pulling_utils/json_report_to_csv.py new file mode 100644 index 0000000..978ba8c --- /dev/null +++ b/metrics/data_pulling_utils/json_report_to_csv.py @@ -0,0 +1,34 @@ +from os import walk, path, remove + +from datetime import date + +import csv, json, sys, logging + + +if sys.argv[1] is not None: + + today = date.today().isoformat() + targetFolder = sys.argv[1] + today + "/" + + f = [] + for (dirpath, dirnames, filenames) in walk(targetFolder): + f.extend(filenames) + break + + for file in f: + if ".csv" not in file: + fileInput = targetFolder + file + fileOutput = fileInput[:-5] + ".csv" + + inputFile = open(fileInput) + outputFile = open(fileOutput, 'w') + data = json.load(inputFile) + inputFile.close() + + output = csv.writer(outputFile) + + output.writerow(data[0].keys()) # header row + + for row in data: + output.writerow(row.values()) + output.writerow("") \ No newline at end of file diff --git a/metrics/package-lock.json b/metrics/package-lock.json new file mode 100644 index 0000000..43f3947 --- /dev/null +++ b/metrics/package-lock.json @@ -0,0 +1,289 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "@octokit/endpoint": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-5.5.1.tgz", + "integrity": "sha512-nBFhRUb5YzVTCX/iAK1MgQ4uWo89Gu0TH00qQHoYRCsE12dWcG1OiLd7v2EIo2+tpUKPMOQ62QFy9hy9Vg2ULg==", + "requires": { + "@octokit/types": "^2.0.0", + "is-plain-object": "^3.0.0", + "universal-user-agent": "^4.0.0" + } + }, + "@octokit/graphql": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.3.1.tgz", + "integrity": "sha512-hCdTjfvrK+ilU2keAdqNBWOk+gm1kai1ZcdjRfB30oA3/T6n53UVJb7w0L5cR3/rhU91xT3HSqCd+qbvH06yxA==", + "requires": { + "@octokit/request": "^5.3.0", + "@octokit/types": "^2.0.0", + "universal-user-agent": "^4.0.0" + } + }, + "@octokit/request": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.3.1.tgz", + "integrity": "sha512-5/X0AL1ZgoU32fAepTfEoggFinO3rxsMLtzhlUX+RctLrusn/CApJuGFCd0v7GMFhF+8UiCsTTfsu7Fh1HnEJg==", + "requires": { + "@octokit/endpoint": "^5.5.0", + "@octokit/request-error": "^1.0.1", + "@octokit/types": "^2.0.0", + "deprecation": "^2.0.0", + "is-plain-object": "^3.0.0", + "node-fetch": "^2.3.0", + "once": "^1.4.0", + "universal-user-agent": "^4.0.0" + } + }, + "@octokit/request-error": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-1.2.0.tgz", + "integrity": "sha512-DNBhROBYjjV/I9n7A8kVkmQNkqFAMem90dSxqvPq57e2hBr7mNTX98y3R2zDpqMQHVRpBDjsvsfIGgBzy+4PAg==", + "requires": { + "@octokit/types": "^2.0.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "@octokit/types": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.1.1.tgz", + "integrity": "sha512-89LOYH+d/vsbDX785NOfLxTW88GjNd0lWRz1DVPVsZgg9Yett5O+3MOvwo7iHgvUwbFz0mf/yPIjBkUbs4kxoQ==", + "requires": { + "@types/node": ">= 8" + } + }, + "@types/node": { + "version": "13.5.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.5.2.tgz", + "integrity": "sha512-Fr6a47c84PRLfd7M7u3/hEknyUdQrrBA6VoPmkze0tcflhU5UnpWEX2kn12ktA/lb+MNHSqFlSiPHIHsaErTPA==" + }, + "axios": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", + "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "requires": { + "follow-redirects": "1.5.10" + } + }, + "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" + } + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.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" + } + }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + } + }, + "fs": { + "version": "0.0.1-security", + "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", + "integrity": "sha1-invTcYa23d84E/I4WLV+yq9eQdQ=" + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "requires": { + "pump": "^3.0.0" + } + }, + "is-plain-object": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.0.tgz", + "integrity": "sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg==", + "requires": { + "isobject": "^4.0.0" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "isobject": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", + "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==" + }, + "macos-release": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.3.0.tgz", + "integrity": "sha512-OHhSbtcviqMPt7yfw5ef5aghS2jzFVKEFyCJndQt2YpSQ9qRVSEv2axSJI1paVThEu+FFGs584h/1YhxjVqajA==" + }, + "moment": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + }, + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + }, + "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": { + "path-key": "^2.0.0" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "os-name": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-name/-/os-name-3.1.0.tgz", + "integrity": "sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg==", + "requires": { + "macos-release": "^2.2.0", + "windows-release": "^3.1.0" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "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=" + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + }, + "universal-user-agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-4.0.0.tgz", + "integrity": "sha512-eM8knLpev67iBDizr/YtqkJsF3GK8gzDc6st/WKzrTuPtcsOKW/0IdL4cnMBsU69pOx0otavLWBDGTwg+dB0aA==", + "requires": { + "os-name": "^3.1.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + }, + "windows-release": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-3.2.0.tgz", + "integrity": "sha512-QTlz2hKLrdqukrsapKsINzqMgOUpQW268eJ0OaOpJN32h272waxR9fkB9VoWRtK7uKHG5EHJcTXQBD8XZVJkFA==", + "requires": { + "execa": "^1.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + } + } +}