From 3909b4efa5cf435713a134067cbf5421193857f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Fri, 9 Mar 2018 14:53:28 +0200 Subject: [PATCH 01/50] Fix the division between dependencies and devDependencies in package.json #115 --- package.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index d41067a..f2178da 100644 --- a/package.json +++ b/package.json @@ -7,23 +7,23 @@ "url": "https://github.com/mozilla/shield-studies-addon-utils/issues" }, "dependencies": { - "assert": "^1.4.1", - "fs-extra": "^4.0.0", - "fx-runner": "^1.0.7", - "geckodriver": "^1.8.0", - "mocha": "^3.4.2", "path": "^0.12.7", - "selenium-webdriver": "^3.4.0" + "shield-study-schemas": "^0.8.3" }, "devDependencies": { "ajv": "^4.11.2", + "assert": "^1.4.1", "doctoc": "^1.3.1", "eslint": "^4.0.0", "eslint-plugin-json": "^1.2.0", "eslint-plugin-mozilla": "^0.4.0", "fixpack": "^2.3.1", + "fs-extra": "^4.0.0", + "fx-runner": "^1.0.7", + "geckodriver": "^1.8.0", + "mocha": "^3.4.2", "prettier": "^1.11.0", - "shield-study-schemas": "^0.8.3", + "selenium-webdriver": "^3.4.0", "webpack": "^2.6.1" }, "files": [ From ea27ed28e6432a11ab56835296c73573bb5a19c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Sat, 10 Mar 2018 10:43:08 +0200 Subject: [PATCH 02/50] Removed path as dependency (since it is a core package, we should not use a specific version from npm) --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index f2178da..229e68d 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,6 @@ "url": "https://github.com/mozilla/shield-studies-addon-utils/issues" }, "dependencies": { - "path": "^0.12.7", "shield-study-schemas": "^0.8.3" }, "devDependencies": { From c7777fe414658fa6a93555e05cd4559b47a0d0ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Tue, 3 Apr 2018 07:48:47 +0300 Subject: [PATCH 03/50] Updated package-lock.json --- package-lock.json | 630 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 541 insertions(+), 89 deletions(-) diff --git a/package-lock.json b/package-lock.json index 935845d..c26cfb0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "shield-studies-addon-utils", - "version": "4.0.0", + "version": "4.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -47,7 +47,8 @@ "adm-zip": { "version": "0.4.7", "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.7.tgz", - "integrity": "sha1-hgbCy/HEJs6MjsABdER/1Jtur8E=" + "integrity": "sha1-hgbCy/HEJs6MjsABdER/1Jtur8E=", + "dev": true }, "ajv": { "version": "4.11.8", @@ -94,6 +95,15 @@ "repeat-string": "1.6.1" } }, + "anchor-markdown-header": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/anchor-markdown-header/-/anchor-markdown-header-0.5.7.tgz", + "integrity": "sha1-BFBj125qH5zTJ6V6ASaqD97Dcac=", + "dev": true, + "requires": { + "emoji-regex": "6.1.3" + } + }, "ansi-escapes": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-2.0.0.tgz", @@ -149,17 +159,20 @@ "array-filter": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz", - "integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=" + "integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=", + "dev": true }, "array-map": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz", - "integrity": "sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=" + "integrity": "sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=", + "dev": true }, "array-reduce": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/array-reduce/-/array-reduce-0.0.0.tgz", - "integrity": "sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=" + "integrity": "sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=", + "dev": true }, "array-union": { "version": "1.0.2", @@ -203,6 +216,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", + "dev": true, "requires": { "util": "0.10.3" } @@ -233,10 +247,17 @@ "js-tokens": "3.0.2" } }, + "bail": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.2.tgz", + "integrity": "sha1-99bBcxYwqfnw1NNe0fli4gdKF2Q=", + "dev": true + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true }, "base64-js": { "version": "1.2.1", @@ -260,6 +281,7 @@ "version": "0.0.9", "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "dev": true, "requires": { "inherits": "2.0.3" } @@ -267,7 +289,8 @@ "bluebird": { "version": "3.4.6", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.6.tgz", - "integrity": "sha1-AdqNgh2HgT0ViWfnQ9X+bGLPjA8=" + "integrity": "sha1-AdqNgh2HgT0ViWfnQ9X+bGLPjA8=", + "dev": true }, "bn.js": { "version": "4.11.7", @@ -275,10 +298,17 @@ "integrity": "sha512-LxFiV5mefv0ley0SzqkOPR1bC4EbpPx8LkOz5vMe/Yi15t5hzwgO/G+tc7wOtL4PZTYjwHu8JnEiSLumuSjSfA==", "dev": true }, + "boundary": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/boundary/-/boundary-1.0.1.tgz", + "integrity": "sha1-TWfcJgLAzBbdm85+v4fpSCkPWBI=", + "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=", + "dev": true, "requires": { "balanced-match": "1.0.0", "concat-map": "0.0.1" @@ -304,7 +334,8 @@ "browser-stdout": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", - "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=" + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "dev": true }, "browserify-aes": { "version": "1.0.6", @@ -428,7 +459,14 @@ "capture-stack-trace": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz", - "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=" + "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=", + "dev": true + }, + "ccount": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.0.2.tgz", + "integrity": "sha1-U7ai+BW7d7nChx97mnLDol8djok=", + "dev": true }, "center-align": { "version": "0.1.3", @@ -453,6 +491,30 @@ "supports-color": "2.0.0" } }, + "character-entities": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.1.tgz", + "integrity": "sha1-92hxvl72bdt/j440eOzDdMJ9bco=", + "dev": true + }, + "character-entities-html4": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-1.1.1.tgz", + "integrity": "sha1-NZoqSg9+KdPcKsmb2+Ie45Q46lA=", + "dev": true + }, + "character-entities-legacy": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.1.tgz", + "integrity": "sha1-9Ad53xoQGHK7UQo9KV4fzPFHIC8=", + "dev": true + }, + "character-reference-invalid": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.1.tgz", + "integrity": "sha1-lCg191Dk7GGjCOYMLvjMEBEgLvw=", + "dev": true + }, "chokidar": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", @@ -542,6 +604,12 @@ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true }, + "collapse-white-space": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.3.tgz", + "integrity": "sha1-S5BvZw5aljqHt2sOFolkM0G2Ajw=", + "dev": true + }, "color-convert": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz", @@ -566,17 +634,20 @@ "commander": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", - "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==" + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", + "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=" + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true }, "concat-stream": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", + "dev": true, "requires": { "inherits": "2.0.3", "readable-stream": "2.3.3", @@ -601,7 +672,8 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true }, "create-ecdh": { "version": "4.0.0", @@ -617,6 +689,7 @@ "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" } @@ -735,7 +808,8 @@ "diff": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", - "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=" + "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", + "dev": true }, "diffie-hellman": { "version": "5.0.2", @@ -748,6 +822,48 @@ "randombytes": "2.0.5" } }, + "doctoc": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/doctoc/-/doctoc-1.3.1.tgz", + "integrity": "sha1-8BLjYD4xViVMLvIqyIxxkPVUJro=", + "dev": true, + "requires": { + "anchor-markdown-header": "0.5.7", + "htmlparser2": "3.9.2", + "markdown-to-ast": "3.4.0", + "minimist": "1.2.0", + "underscore": "1.8.3", + "update-section": "0.3.3" + }, + "dependencies": { + "entities": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", + "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=", + "dev": true + }, + "htmlparser2": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", + "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=", + "dev": true, + "requires": { + "domelementtype": "1.3.0", + "domhandler": "2.3.0", + "domutils": "1.5.1", + "entities": "1.1.1", + "inherits": "2.0.3", + "readable-stream": "2.3.3" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, "doctrine": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz", @@ -817,6 +933,7 @@ "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.3.3" } @@ -836,6 +953,12 @@ "minimalistic-crypto-utils": "1.0.1" } }, + "emoji-regex": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.1.3.tgz", + "integrity": "sha1-7HmjlpsC0uzytyJUJ5v5m8eoOTI=", + "dev": true + }, "emojis-list": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", @@ -873,6 +996,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "dev": true, "requires": { "is-arrayish": "0.2.1" } @@ -950,7 +1074,8 @@ "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=" + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true }, "escope": { "version": "3.6.0", @@ -1148,6 +1273,12 @@ "fill-range": "2.2.3" } }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", + "dev": true + }, "extend-object": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/extend-object/-/extend-object-1.0.0.tgz", @@ -1277,6 +1408,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.0.tgz", "integrity": "sha1-QU+0yi0hcLoAFBWdOorsMwNBjZ4=", + "dev": true, "requires": { "graceful-fs": "4.1.11", "jsonfile": "3.0.1", @@ -1286,7 +1418,8 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true }, "fsevents": { "version": "1.1.2", @@ -2057,14 +2190,6 @@ } } }, - "string_decoder": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "5.0.1" - } - }, "string-width": { "version": "1.0.2", "bundled": true, @@ -2075,6 +2200,14 @@ "strip-ansi": "3.0.1" } }, + "string_decoder": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "5.0.1" + } + }, "stringstream": { "version": "0.0.5", "bundled": true, @@ -2191,6 +2324,7 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", + "dev": true, "requires": { "graceful-fs": "4.1.11", "inherits": "2.0.3", @@ -2198,10 +2332,17 @@ "rimraf": "2.6.1" } }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, "fx-runner": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/fx-runner/-/fx-runner-1.0.7.tgz", "integrity": "sha1-wQLP3yEjTC29FnI9zLEYb5nAtkE=", + "dev": true, "requires": { "commander": "2.9.0", "lodash": "3.10.1", @@ -2216,6 +2357,7 @@ "version": "2.9.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "dev": true, "requires": { "graceful-readlink": "1.0.1" } @@ -2223,7 +2365,8 @@ "lodash": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", + "dev": true } } }, @@ -2231,6 +2374,7 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-1.8.0.tgz", "integrity": "sha1-ef7fk3B3w0uu5ZGim7zxtgLS1hk=", + "dev": true, "requires": { "adm-zip": "0.4.7", "bluebird": "3.4.6", @@ -2248,6 +2392,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, "requires": { "fs.realpath": "1.0.0", "inflight": "1.0.6", @@ -2300,6 +2445,7 @@ "version": "5.6.0", "resolved": "https://registry.npmjs.org/got/-/got-5.6.0.tgz", "integrity": "sha1-ux1+4WO3gIK7yOuDbz85UATqb78=", + "dev": true, "requires": { "create-error-class": "3.0.2", "duplexer2": "0.1.4", @@ -2322,17 +2468,29 @@ "graceful-fs": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true }, "graceful-readlink": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", - "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=" + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", + "dev": true }, "growl": { "version": "1.9.2", "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", - "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=" + "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", + "dev": true + }, + "has": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", + "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", + "dev": true, + "requires": { + "function-bind": "1.1.1" + } }, "has-ansi": { "version": "2.0.0", @@ -2464,6 +2622,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, "requires": { "once": "1.4.0", "wrappy": "1.0.2" @@ -2472,7 +2631,8 @@ "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.4", @@ -2570,14 +2730,32 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz", "integrity": "sha1-hHSREZ/MtftDYhfMc39/qtUPYD8=", + "dev": true, "requires": { "is-relative": "0.1.3" } }, + "is-alphabetical": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.1.tgz", + "integrity": "sha1-x3B5zJHU76x3W+EDS/LSQ/lebwg=", + "dev": true + }, + "is-alphanumerical": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.1.tgz", + "integrity": "sha1-37SqTRCF4zvbYcLe6cgOnGwZ9Ts=", + "dev": true, + "requires": { + "is-alphabetical": "1.0.1", + "is-decimal": "1.0.1" + } + }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true }, "is-binary-path": { "version": "1.0.1", @@ -2603,6 +2781,12 @@ "builtin-modules": "1.1.1" } }, + "is-decimal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.1.tgz", + "integrity": "sha1-9ftqlJlq2ejjdh+/vQkfH8qMToI=", + "dev": true + }, "is-dotfile": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", @@ -2645,6 +2829,12 @@ "is-extglob": "1.0.0" } }, + "is-hexadecimal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.1.tgz", + "integrity": "sha1-bghLvJIGH7sJcexYts5tQE4k2mk=", + "dev": true + }, "is-number": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", @@ -2681,7 +2871,8 @@ "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=" + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true }, "is-posix-bracket": { "version": "0.1.1", @@ -2704,12 +2895,14 @@ "is-redirect": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", - "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=" + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", + "dev": true }, "is-relative": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz", - "integrity": "sha1-kF/uiuhvRbPsYUvDwVyGnfCHboI=" + "integrity": "sha1-kF/uiuhvRbPsYUvDwVyGnfCHboI=", + "dev": true }, "is-resolvable": { "version": "1.0.0", @@ -2723,12 +2916,14 @@ "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=" + "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=", + "dev": true }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true }, "is-utf8": { "version": "0.2.1", @@ -2739,12 +2934,14 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true }, "isexe": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/isexe/-/isexe-1.1.2.tgz", - "integrity": "sha1-NvPiLmB1CSD15yQaR2qMakInWtA=" + "integrity": "sha1-NvPiLmB1CSD15yQaR2qMakInWtA=", + "dev": true }, "isobject": { "version": "2.1.0", @@ -2831,7 +3028,8 @@ "json3": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", - "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=" + "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", + "dev": true }, "json5": { "version": "0.5.1", @@ -2843,6 +3041,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz", "integrity": "sha1-pezG9l9T9mLEQVx2daAzHQmS7GY=", + "dev": true, "requires": { "graceful-fs": "4.1.11" } @@ -2850,7 +3049,8 @@ "jsonify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true }, "kind-of": { "version": "3.2.2", @@ -2920,12 +3120,14 @@ "lodash": { "version": "4.17.4", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", + "dev": true }, "lodash._baseassign": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", + "dev": true, "requires": { "lodash._basecopy": "3.0.1", "lodash.keys": "3.1.2" @@ -2934,27 +3136,32 @@ "lodash._basecopy": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=" + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", + "dev": true }, "lodash._basecreate": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", - "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=" + "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", + "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=" + "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=" + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", + "dev": true }, "lodash.create": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", + "dev": true, "requires": { "lodash._baseassign": "3.2.0", "lodash._basecreate": "3.0.3", @@ -2964,17 +3171,20 @@ "lodash.isarguments": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" + "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=" + "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=", + "dev": true, "requires": { "lodash._getnative": "3.9.1", "lodash.isarguments": "3.1.0", @@ -2987,10 +3197,35 @@ "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", "dev": true }, + "longest-streak": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-1.0.0.tgz", + "integrity": "sha1-0GWXxNTDG1LMsfXY+P5xSOr9aWU=", + "dev": true + }, "lowercase-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", - "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=" + "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=", + "dev": true + }, + "markdown-table": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-0.4.0.tgz", + "integrity": "sha1-iQwsGzv+g/sA5BKbjkz+ZFJw+dE=", + "dev": true + }, + "markdown-to-ast": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/markdown-to-ast/-/markdown-to-ast-3.4.0.tgz", + "integrity": "sha1-Diy6gTkLBUmpFT7DsNkVthwWS+c=", + "dev": true, + "requires": { + "debug": "2.6.8", + "remark": "5.1.0", + "structured-source": "3.0.2", + "traverse": "0.6.6" + } }, "memory-fs": { "version": "0.4.1", @@ -3055,6 +3290,7 @@ "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.8" } @@ -3062,12 +3298,14 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, "requires": { "minimist": "0.0.8" } @@ -3076,6 +3314,7 @@ "version": "3.4.2", "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.4.2.tgz", "integrity": "sha1-0O9NMyEm2/GNDWQMmzgt1IvpdZQ=", + "dev": true, "requires": { "browser-stdout": "1.3.0", "commander": "2.9.0", @@ -3102,6 +3341,7 @@ "version": "2.6.0", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.0.tgz", "integrity": "sha1-vFlryr52F/Edn6FTYe3tVgi4SZs=", + "dev": true, "requires": { "ms": "0.7.2" } @@ -3127,7 +3367,8 @@ "ms": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", - "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=" + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "dev": true }, "supports-color": { "version": "3.1.2", @@ -3142,7 +3383,8 @@ "mout": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/mout/-/mout-0.11.1.tgz", - "integrity": "sha1-ujYR318OWx/7/QEWa48C0fX6K5k=" + "integrity": "sha1-ujYR318OWx/7/QEWa48C0fX6K5k=", + "dev": true }, "ms": { "version": "2.0.0", @@ -3211,7 +3453,8 @@ "node-status-codes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz", - "integrity": "sha1-WuVUHQJGRdMqWPzdyc7s6nrjrC8=" + "integrity": "sha1-WuVUHQJGRdMqWPzdyc7s6nrjrC8=", + "dev": true }, "normalize-package-data": { "version": "2.4.0", @@ -3243,7 +3486,8 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true }, "object.omit": { "version": "2.0.1", @@ -3259,6 +3503,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, "requires": { "wrappy": "1.0.2" } @@ -3304,12 +3549,14 @@ "os-shim": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz", - "integrity": "sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc=" + "integrity": "sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc=", + "dev": true }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true }, "pako": { "version": "0.2.9", @@ -3330,6 +3577,20 @@ "pbkdf2": "3.0.12" } }, + "parse-entities": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-1.1.1.tgz", + "integrity": "sha1-gRLYhHExnyerrk1klksSL+ThuJA=", + "dev": true, + "requires": { + "character-entities": "1.2.1", + "character-entities-legacy": "1.1.1", + "character-reference-invalid": "1.1.1", + "is-alphanumerical": "1.0.1", + "is-decimal": "1.0.1", + "is-hexadecimal": "1.0.1" + } + }, "parse-glob": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", @@ -3346,19 +3607,11 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, "requires": { "error-ex": "1.3.1" } }, - "path": { - "version": "0.12.7", - "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", - "integrity": "sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=", - "requires": { - "process": "0.11.10", - "util": "0.10.3" - } - }, "path-browserify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", @@ -3377,7 +3630,8 @@ "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", @@ -3418,12 +3672,14 @@ "pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true }, "pinkie-promise": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, "requires": { "pinkie": "2.0.4" } @@ -3443,7 +3699,8 @@ "prepend-http": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", + "dev": true }, "preserve": { "version": "0.2.0", @@ -3451,15 +3708,23 @@ "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", "dev": true }, + "prettier": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.11.1.tgz", + "integrity": "sha512-T/KD65Ot0PB97xTrG8afQ46x3oiVhnfGjGESSI9NWYcG92+OUPZKkwHqGWXH2t9jK1crnQjubECW0FuOth+hxw==", + "dev": true + }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "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=" + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true }, "progress": { "version": "2.0.0", @@ -3578,6 +3843,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/read-all-stream/-/read-all-stream-3.1.0.tgz", "integrity": "sha1-NcPhd/IHjveJ7kv6+kNzB06u9Po=", + "dev": true, "requires": { "pinkie-promise": "2.0.1", "readable-stream": "2.3.3" @@ -3608,6 +3874,7 @@ "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.2", "inherits": "2.0.3", @@ -3640,6 +3907,50 @@ "is-primitive": "2.0.0" } }, + "remark": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/remark/-/remark-5.1.0.tgz", + "integrity": "sha1-y0Y709vLS5l5STXu4c9x16jjBow=", + "dev": true, + "requires": { + "remark-parse": "1.1.0", + "remark-stringify": "1.1.0", + "unified": "4.2.1" + } + }, + "remark-parse": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-1.1.0.tgz", + "integrity": "sha1-w8oQ+ajaBGFcKPCapOMEUQUm7CE=", + "dev": true, + "requires": { + "collapse-white-space": "1.0.3", + "extend": "3.0.1", + "parse-entities": "1.1.1", + "repeat-string": "1.6.1", + "trim": "0.0.1", + "trim-trailing-lines": "1.1.0", + "unherit": "1.1.0", + "unist-util-remove-position": "1.1.1", + "vfile-location": "2.0.2" + } + }, + "remark-stringify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-1.1.0.tgz", + "integrity": "sha1-pxBeJbnuK/mkm3XSxCPxGwauIJI=", + "dev": true, + "requires": { + "ccount": "1.0.2", + "extend": "3.0.1", + "longest-streak": "1.0.0", + "markdown-table": "0.4.0", + "parse-entities": "1.1.1", + "repeat-string": "1.6.1", + "stringify-entities": "1.3.1", + "unherit": "1.1.0" + } + }, "remove-trailing-separator": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.0.2.tgz", @@ -3709,6 +4020,7 @@ "version": "2.6.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=", + "dev": true, "requires": { "glob": "7.1.2" } @@ -3750,17 +4062,20 @@ "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "dev": true }, "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true }, "selenium-webdriver": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.4.0.tgz", "integrity": "sha1-FR90RSlNpqZsScwwB0eioX5TxSo=", + "dev": true, "requires": { "adm-zip": "0.4.7", "rimraf": "2.6.1", @@ -3772,6 +4087,7 @@ "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.2" } @@ -3815,6 +4131,7 @@ "version": "1.6.1", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz", "integrity": "sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c=", + "dev": true, "requires": { "array-filter": "0.0.1", "array-map": "0.0.0", @@ -3831,8 +4148,7 @@ "shield-study-schemas": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/shield-study-schemas/-/shield-study-schemas-0.8.3.tgz", - "integrity": "sha1-2yBiSamGlJkVnThHxxiYV1lfgM0=", - "dev": true + "integrity": "sha1-2yBiSamGlJkVnThHxxiYV1lfgM0=" }, "signal-exit": { "version": "3.0.2", @@ -3862,6 +4178,7 @@ "version": "1.0.15", "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz", "integrity": "sha1-sAeZVX63+wyDdsKdROih6mfldHY=", + "dev": true, "requires": { "concat-stream": "1.6.0", "os-shim": "0.1.3" @@ -3917,14 +4234,6 @@ "xtend": "4.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==", - "requires": { - "safe-buffer": "5.1.1" - } - }, "string-width": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.0.tgz", @@ -3952,6 +4261,27 @@ } } }, + "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.1" + } + }, + "stringify-entities": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-1.3.1.tgz", + "integrity": "sha1-sVDsLXKsTBtfMktR+2soyc3/BYw=", + "dev": true, + "requires": { + "character-entities-html4": "1.1.1", + "character-entities-legacy": "1.1.1", + "is-alphanumerical": "1.0.1", + "is-hexadecimal": "1.0.1" + } + }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -3976,6 +4306,15 @@ "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true }, + "structured-source": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/structured-source/-/structured-source-3.0.2.tgz", + "integrity": "sha1-3YAkJeD1PcSm56yjdSkBoczaevU=", + "dev": true, + "requires": { + "boundary": "1.0.1" + } + }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", @@ -4014,6 +4353,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "dev": true, "requires": { "block-stream": "0.0.9", "fstream": "1.0.11", @@ -4024,6 +4364,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/tar.gz/-/tar.gz-1.0.5.tgz", "integrity": "sha1-4a2n5F7yJBtLHuWBI8j0C108G8Q=", + "dev": true, "requires": { "bluebird": "2.11.0", "commander": "2.11.0", @@ -4035,7 +4376,8 @@ "bluebird": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz", - "integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=" + "integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=", + "dev": true } } }, @@ -4054,7 +4396,8 @@ "timed-out": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-2.0.0.tgz", - "integrity": "sha1-84sK6B03R9YoAB9B2vxlKs5nHAo=" + "integrity": "sha1-84sK6B03R9YoAB9B2vxlKs5nHAo=", + "dev": true }, "timers-browserify": { "version": "2.0.2", @@ -4080,6 +4423,30 @@ "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", "dev": true }, + "traverse": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", + "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=", + "dev": true + }, + "trim": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", + "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=", + "dev": true + }, + "trim-trailing-lines": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.0.tgz", + "integrity": "sha1-eu+7eAjfnWafbaLkOMrIxGradoQ=", + "dev": true + }, + "trough": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.1.tgz", + "integrity": "sha1-qf2LA5Swro//guBjOgo2zK1bX4Y=", + "dev": true + }, "tryit": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", @@ -4104,7 +4471,8 @@ "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true }, "uglify-js": { "version": "2.8.29", @@ -4138,15 +4506,77 @@ "dev": true, "optional": true }, + "underscore": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=", + "dev": true + }, + "unherit": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.0.tgz", + "integrity": "sha1-a5qu379z3xdWrZ4xbdmBiFhAzX0=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "xtend": "4.0.1" + } + }, + "unified": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/unified/-/unified-4.2.1.tgz", + "integrity": "sha1-dv9Dqo2kMPbn5KVchOusKtLPzS4=", + "dev": true, + "requires": { + "bail": "1.0.2", + "extend": "3.0.1", + "has": "1.0.1", + "once": "1.4.0", + "trough": "1.0.1", + "vfile": "1.4.0" + } + }, + "unist-util-is": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-2.1.1.tgz", + "integrity": "sha1-DDEmKeP5YMZukx6BLT2A53AQlHs=", + "dev": true + }, + "unist-util-remove-position": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-1.1.1.tgz", + "integrity": "sha1-WoXBVV/BugwQG4ZwfRXlD6TIcbs=", + "dev": true, + "requires": { + "unist-util-visit": "1.3.0" + } + }, + "unist-util-visit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.3.0.tgz", + "integrity": "sha512-9ntYcxPFtl44gnwXrQKZ5bMqXMY0ZHzUpqMFiU4zcc8mmf/jzYm8GhYgezuUlX4cJIM1zIDYaO6fG/fI+L6iiQ==", + "dev": true, + "requires": { + "unist-util-is": "2.1.1" + } + }, "universalify": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.0.tgz", - "integrity": "sha1-nrHEZR3rzGcMyU8adXYjMruWd3g=" + "integrity": "sha1-nrHEZR3rzGcMyU8adXYjMruWd3g=", + "dev": true }, "unzip-response": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-1.0.2.tgz", - "integrity": "sha1-uYTwh3/AqJwsdzzB73tbIytbBv4=" + "integrity": "sha1-uYTwh3/AqJwsdzzB73tbIytbBv4=", + "dev": true + }, + "update-section": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/update-section/-/update-section-0.3.3.tgz", + "integrity": "sha1-RY8Xgg03gg3GDiC4bZQ5GwASMVg=", + "dev": true }, "url": { "version": "0.11.0", @@ -4170,6 +4600,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "dev": true, "requires": { "prepend-http": "1.0.4" } @@ -4178,6 +4609,7 @@ "version": "0.10.3", "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, "requires": { "inherits": "2.0.1" }, @@ -4185,14 +4617,16 @@ "inherits": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true } } }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true }, "validate-npm-package-license": { "version": "3.0.1", @@ -4204,6 +4638,18 @@ "spdx-expression-parse": "1.0.4" } }, + "vfile": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-1.4.0.tgz", + "integrity": "sha1-wP1vpIT43r23cfaMMe112I2pf+c=", + "dev": true + }, + "vfile-location": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-2.0.2.tgz", + "integrity": "sha1-02dcWch3SY5JK0dW/2Xkrxp1IlU=", + "dev": true + }, "vm-browserify": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", @@ -4289,12 +4735,14 @@ "when": { "version": "3.7.7", "resolved": "https://registry.npmjs.org/when/-/when-3.7.7.tgz", - "integrity": "sha1-q6A/w7tzbWyIsJHQE9io5ZDYRxg=" + "integrity": "sha1-q6A/w7tzbWyIsJHQE9io5ZDYRxg=", + "dev": true }, "which": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/which/-/which-1.2.4.tgz", "integrity": "sha1-FVf5YIBgTlsRs1meufRbUKnv1yI=", + "dev": true, "requires": { "is-absolute": "0.1.7", "isexe": "1.1.2" @@ -4315,7 +4763,8 @@ "winreg": { "version": "0.0.12", "resolved": "https://registry.npmjs.org/winreg/-/winreg-0.0.12.tgz", - "integrity": "sha1-BxBVVLoanQiXklHRKUdb/64wBrc=" + "integrity": "sha1-BxBVVLoanQiXklHRKUdb/64wBrc=", + "dev": true }, "wordwrap": { "version": "1.0.0", @@ -4358,7 +4807,8 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true }, "write": { "version": "0.2.1", @@ -4373,6 +4823,7 @@ "version": "0.4.17", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.17.tgz", "integrity": "sha1-F76T6q4/O3eTWceVtBlwWogX6Gg=", + "dev": true, "requires": { "sax": "1.2.4", "xmlbuilder": "4.2.1" @@ -4382,6 +4833,7 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-4.2.1.tgz", "integrity": "sha1-qlijBBoGb5DqoWwvU4n/GfP0YaU=", + "dev": true, "requires": { "lodash": "4.17.4" } From 3e1f54587217a58f611fba8a1047307187217ac1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Sat, 10 Mar 2018 11:23:33 +0200 Subject: [PATCH 04/50] Npm run format (prettier + eslint --fix + fixpack) --- .eslintignore | 7 +++++++ package.json | 3 +++ 2 files changed, 10 insertions(+) diff --git a/.eslintignore b/.eslintignore index e69de29..8d6919b 100644 --- a/.eslintignore +++ b/.eslintignore @@ -0,0 +1,7 @@ +# do not lint/format generated artifacts +dist/ +package-lock.json +# makes sure that eslintrc.js gets linted/formatted +!.eslintrc.js +# don't lint/format package.json since npm install formats it differently by default +package.json diff --git a/package.json b/package.json index 229e68d..aab0d75 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,9 @@ "build-test-addon-xpi": "./bin/make_xpi.sh", "dist": "webpack", "eslint": "eslint src --ext jsm --ext js --ext json", + "eslint-fix": "npm run eslint -- --fix", + "format": "prettier '**/*.{css,js,jsm,json,md}' --trailing-comma=all --ignore-path=.eslintignore --write", + "postformat": "npm run eslint-fix && fixpack", "predist": "npm run eslint", "prepack": "fixpack && npm run dist", "pretest": "npm run dist && npm run build-test-addon-xpi", From af564c7be516c1e3b24185fe384ee5e4cf6636b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Sat, 10 Mar 2018 11:29:26 +0200 Subject: [PATCH 05/50] Results of npm run format --- .eslintrc.js | 18 +- package.json | 4 +- shield-study-helper-addon/addon/bootstrap.js | 41 +- .../addon/webextension/.eslintrc.json | 20 +- .../addon/webextension/qa.js | 20 +- shield-study-helper-addon/run-firefox.js | 27 +- src/StudyUtils.in.jsm | 606 +++++++++--------- src/schema.studySetup.json | 35 +- src/schema.webExtensionMsg.json | 11 +- src/schema.weightedVariations.json | 5 +- src/schemas.js | 12 +- test-addon/bootstrap.js | 6 +- test-addon/utils.jsm | 15 +- test/shield_utils_test.js | 203 ++++-- test/utils.js | 32 +- webpack.config.js | 14 +- 16 files changed, 575 insertions(+), 494 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index c108f12..1b71f9e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -2,23 +2,17 @@ module.exports = { env: { - "node": true + node: true, }, - extends: [ - "eslint:recommended", - "plugin:mozilla/recommended", - ], + extends: ["eslint:recommended", "plugin:mozilla/recommended"], - plugins: [ - "mozilla", - "json" - ], + plugins: ["mozilla", "json"], rules: { "babel/new-cap": "off", "comma-dangle": ["error", "always-multiline"], - "eqeqeq": "error", - "indent": ["warn", 2, {SwitchCase: 1}], + eqeqeq: "error", + indent: ["warn", 2, { SwitchCase: 1 }], "mozilla/no-aArgs": "warn", "mozilla/balanced-listeners": 0, "no-console": "warn", @@ -26,7 +20,7 @@ module.exports = { "no-shadow": ["error"], "no-unused-vars": "error", "prefer-const": "warn", - "semi": ["error", "always"], + semi: ["error", "always"], "require-jsdoc": "warn", "valid-jsdoc": "warn", "max-len": ["warn", 80], diff --git a/package.json b/package.json index aab0d75..18a5261 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "scripts": { "build-test-addon-xpi": "./bin/make_xpi.sh", "dist": "webpack", + "docformat": "doctoc --title '**Contents**' docs/*.md && prettier '**/*.md' --write", "eslint": "eslint src --ext jsm --ext js --ext json", "eslint-fix": "npm run eslint -- --fix", "format": "prettier '**/*.{css,js,jsm,json,md}' --trailing-comma=all --ignore-path=.eslintignore --write", @@ -53,7 +54,6 @@ "predist": "npm run eslint", "prepack": "fixpack && npm run dist", "pretest": "npm run dist && npm run build-test-addon-xpi", - "test": "export FIREFOX_BINARY=firefox && XPI_NAME=test-addon/test-addon.xpi mocha test", - "docformat": "doctoc --title '**Contents**' docs/*.md && prettier '**/*.md' --write" + "test": "export FIREFOX_BINARY=firefox && XPI_NAME=test-addon/test-addon.xpi mocha test" } } diff --git a/shield-study-helper-addon/addon/bootstrap.js b/shield-study-helper-addon/addon/bootstrap.js index 7db1e9b..efcf49a 100644 --- a/shield-study-helper-addon/addon/bootstrap.js +++ b/shield-study-helper-addon/addon/bootstrap.js @@ -3,15 +3,15 @@ /* global __SCRIPT_URI_SPEC__, Feature, studyUtils, config */ /* eslint no-unused-vars: ["error", { "varsIgnorePattern": "(startup|shutdown|install|uninstall)" }]*/ -async function getTelemetryPings (options) { +async function getTelemetryPings(options) { // type is String or Array - const {type, n, timestamp, headersOnly} = options; + const { type, n, timestamp, headersOnly } = options; Components.utils.import("resource://gre/modules/TelemetryArchive.jsm"); // {type, id, timestampCreated} let pings = await TelemetryArchive.promiseArchivedPingList(); if (type) { if (!(type instanceof Array)) { - type = [type]; // Array-ify if it's a string + type = [type]; // Array-ify if it's a string } } if (type) pings = pings.filter(p => type.includes(p.type)); @@ -19,23 +19,26 @@ async function getTelemetryPings (options) { pings.sort((a, b) => b.timestampCreated - a.timestampCreated); if (n) pings = pings.slice(0, n); - const pingData = headersOnly ? pings : pings.map(ping => TelemetryArchive.promiseArchivedPingById(ping.id)); - return Promise.all(pingData) + const pingData = headersOnly + ? pings + : pings.map(ping => TelemetryArchive.promiseArchivedPingById(ping.id)); + return Promise.all(pingData); } -async function pingsReport () { +async function pingsReport() { async function getPings() { const ar = ["shield-study", "shield-study-addon"]; - return getTelemetryPings({type: ["shield-study", "shield-study-addon"]}); + return getTelemetryPings({ type: ["shield-study", "shield-study-addon"] }); } const pings = (await getPings()).reverse(); if (pings.length == 0) { - return {"report": "No pings found"} + return { report: "No pings found" }; } const p0 = pings[0].payload; // print common fields - const report = ` + const report = + ` // common fields branch ${p0.branch} // should describe Question text @@ -43,20 +46,23 @@ study_name ${p0.study_name} addon_version ${p0.addon_version} version ${p0.version} -` + pings.map((p,i)=>`${i} ${p.creationDate} ${p.payload.type} -${JSON.stringify(p.payload.data,null,2)} +` + + pings + .map( + (p, i) => `${i} ${p.creationDate} ${p.payload.type} +${JSON.stringify(p.payload.data, null, 2)} -`).join('\n'); +`, + ) + .join("\n"); - return {"report": report}; + return { report: report }; //pings.forEach(p=>{ // console.log(p.creationDate, p.payload.type); // console.log(JSON.stringify(p.payload.data,null,2)) //}) } - - async function listenFromWebExtension(msg, sender, sendResponse) { //await pingsReport(); console.log(`got ${msg}`); @@ -76,12 +82,11 @@ async function listenFromWebExtension(msg, sender, sendResponse) { return false; } - async function startup(addonData, reason) { - console.log('starting up debugger') + console.log("starting up debugger"); const webExtension = addonData.webExtension; webExtension.startup().then(api => { - const {browser} = api; + const { browser } = api; // messages intended for shieldn: {shield:true,msg=[info|endStudy|telemetry],data=data} browser.runtime.onMessage.addListener(listenFromWebExtension); // other message handlers from your addon, if any diff --git a/shield-study-helper-addon/addon/webextension/.eslintrc.json b/shield-study-helper-addon/addon/webextension/.eslintrc.json index 1d71c50..5c7cc9e 100644 --- a/shield-study-helper-addon/addon/webextension/.eslintrc.json +++ b/shield-study-helper-addon/addon/webextension/.eslintrc.json @@ -1,13 +1,11 @@ { - "env": { - "browser": true, - "es6": true, - "webextensions": true - }, - "extends": [ - "eslint:recommended" - ], - "rules": { - "no-console": "warn" - } + "env": { + "browser": true, + "es6": true, + "webextensions": true + }, + "extends": ["eslint:recommended"], + "rules": { + "no-console": "warn" + } } diff --git a/shield-study-helper-addon/addon/webextension/qa.js b/shield-study-helper-addon/addon/webextension/qa.js index ef036a1..d9f3142 100644 --- a/shield-study-helper-addon/addon/webextension/qa.js +++ b/shield-study-helper-addon/addon/webextension/qa.js @@ -7,26 +7,25 @@ has a bunch of pings and stuff `; -function printReport (text) { +function printReport(text) { console.log(`about to replace: ${text}`); - document.querySelector('#timestamp').textContent=`${new Date()}`; - document.querySelector('#qa').textContent=text; + document.querySelector("#timestamp").textContent = `${new Date()}`; + document.querySelector("#qa").textContent = text; } - -async function tryReportFromFirefox () { - console.log(`has browser runtime? ${browser.runtime}`) +async function tryReportFromFirefox() { + console.log(`has browser runtime? ${browser.runtime}`); if (browser.runtime) { - const reply = await browser.runtime.sendMessage("qa-report") + const reply = await browser.runtime.sendMessage("qa-report"); console.log("got reply!", reply); if (reply) { printReport(reply.report); //console.log("response from legacy add-on: " + reply.content); - }; + } } } -function startup () { +function startup() { printReport(PLACEHOLDER); console.log("asking firefox"); tryReportFromFirefox(); @@ -39,5 +38,4 @@ page starts up. - once it arrives, insert it. */ - -document.addEventListener('DOMContentLoaded', startup); +document.addEventListener("DOMContentLoaded", startup); diff --git a/shield-study-helper-addon/run-firefox.js b/shield-study-helper-addon/run-firefox.js index c784775..0307240 100644 --- a/shield-study-helper-addon/run-firefox.js +++ b/shield-study-helper-addon/run-firefox.js @@ -10,7 +10,6 @@ console.log("Starting up firefox"); - require("geckodriver"); const firefox = require("selenium-webdriver/firefox"); const cmd = require("selenium-webdriver/lib/command"); @@ -60,11 +59,11 @@ async function promiseActualBinary(binary) { } } -promiseSetupDriver = async() => { +promiseSetupDriver = async () => { const profile = new firefox.Profile(); // TODO, allow 'actually send telemetry' here. - Object.keys(FIREFOX_PREFERENCES).forEach((key) => { + Object.keys(FIREFOX_PREFERENCES).forEach(key => { profile.setPreference(key, FIREFOX_PREFERENCES[key]); }); @@ -76,7 +75,9 @@ promiseSetupDriver = async() => { .forBrowser("firefox") .setFirefoxOptions(options); - const binaryLocation = await promiseActualBinary(process.env.FIREFOX_BINARY || "nightly"); + const binaryLocation = await promiseActualBinary( + process.env.FIREFOX_BINARY || "nightly", + ); await options.setBinary(new firefox.Binary(binaryLocation)); const driver = await builder.build(); // Firefox will be started up by now @@ -85,21 +86,28 @@ promiseSetupDriver = async() => { return driver; }; -installAddon = async(driver, fileLocation) => { +installAddon = async (driver, fileLocation) => { // references: // https://bugzilla.mozilla.org/show_bug.cgi?id=1298025 // https://github.com/mozilla/geckodriver/releases/tag/v0.17.0 const executor = driver.getExecutor(); - executor.defineCommand("installAddon", "POST", "/session/:sessionId/moz/addon/install"); + executor.defineCommand( + "installAddon", + "POST", + "/session/:sessionId/moz/addon/install", + ); const installCmd = new cmd.Command("installAddon"); const session = await driver.getSession(); - installCmd.setParameters({ sessionId: session.getId(), path: fileLocation, temporary: true }); + installCmd.setParameters({ + sessionId: session.getId(), + path: fileLocation, + temporary: true, + }); return executor.execute(installCmd); }; - -(async() => { +(async () => { try { const driver = await promiseSetupDriver(); @@ -114,7 +122,6 @@ installAddon = async(driver, fileLocation) => { // navigate to a regular page driver.setContext(Context.CONTENT); driver.get("about:debugging"); - } catch (e) { console.error(e); // eslint-disable-line no-console } diff --git a/src/StudyUtils.in.jsm b/src/StudyUtils.in.jsm index f4a5e88..af2e835 100644 --- a/src/StudyUtils.in.jsm +++ b/src/StudyUtils.in.jsm @@ -10,7 +10,6 @@ * config data passed in is valid per the studySetup schema. */ - /* * TODO glind survey / urls & query args * TODO glind publish as v4 @@ -20,7 +19,7 @@ const EXPORTED_SYMBOLS = ["studyUtils"]; const UTILS_VERSION = require("../package.json").version; const PACKET_VERSION = 3; -const {utils: Cu} = Components; +const { utils: Cu } = Components; Cu.import("resource://gre/modules/AddonManager.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); @@ -30,10 +29,14 @@ let log; // telemetry utils const CID = Cu.import("resource://gre/modules/ClientID.jsm", null); -const { TelemetryController } - = Cu.import("resource://gre/modules/TelemetryController.jsm", null); -const { TelemetryEnvironment } - = Cu.import("resource://gre/modules/TelemetryEnvironment.jsm", null); +const { TelemetryController } = Cu.import( + "resource://gre/modules/TelemetryController.jsm", + null, +); +const { TelemetryEnvironment } = Cu.import( + "resource://gre/modules/TelemetryEnvironment.jsm", + null, +); /* * Set-up JSON schema validation @@ -61,25 +64,27 @@ const ajv = new Ajv(); var jsonschema = { /** - * Validates input data based on a specified schema - * @param {Object} data - The data to be validated - * @param {Object} schema - The schema to validate against - * @returns {boolean} - Will return true if the data is valid - */ + * Validates input data based on a specified schema + * @param {Object} data - The data to be validated + * @param {Object} schema - The schema to validate against + * @returns {boolean} - Will return true if the data is valid + */ validate(data, schema) { var valid = ajv.validate(schema, data); - return {valid, errors: ajv.errors || []}; + return { valid, errors: ajv.errors || [] }; }, /** - * Validates input data based on a specified schema - * @param {Object} data - The data to be validated - * @param {Object} schema - The schema to validate against - * @throws Will throw an error if the data is not valid - * @returns {boolean} - Will return true if the data is valid - */ + * Validates input data based on a specified schema + * @param {Object} data - The data to be validated + * @param {Object} schema - The schema to validate against + * @throws Will throw an error if the data is not valid + * @returns {boolean} - Will return true if the data is valid + */ validateOrThrow(data, schema) { const valid = ajv.validate(schema, data); - if (!valid) { throw new Error(JSON.stringify((ajv.errors))); } + if (!valid) { + throw new Error(JSON.stringify(ajv.errors)); + } return true; }, }; @@ -89,7 +94,7 @@ var jsonschema = { * Probably deeper than we need. Unlike the shallow merge with the * spread operator (const c = {...a, ...b}), this function can be configured * to copy non-enumerable properties, symbols, and property descriptors. - * + * * Merges all the properties of all arguments into first argument. If two or * more argument objects have own properties with the same name, the property * is overridden, with precedence from right to left, implying, that properties @@ -118,29 +123,32 @@ function merge(source) { nonEnumerables: true, }; /** - * Gets object's own property symbols and/or names, including non-enumerables - * by default - * @param {Object} object - the object for which to get own property symbols - * and names - * @param {Object} options - object indicating what kinds of properties to - * merge - * @param {boolean} options.name - True if function should return object's own - * property names - * @param {boolean} options.symbols - True if function should return object's - * own property symbols - * @param {boolean} options.nonEnumerables - True if function should return - * object's non-enumerable own property names - * @returns {string[]|symbol[]} - An array of own property names and/or symbols - * for object - */ + * Gets object's own property symbols and/or names, including non-enumerables + * by default + * @param {Object} object - the object for which to get own property symbols + * and names + * @param {Object} options - object indicating what kinds of properties to + * merge + * @param {boolean} options.name - True if function should return object's own + * property names + * @param {boolean} options.symbols - True if function should return + * object's own property symbols + * @param {boolean} options.nonEnumerables - True if function should return + * object's non-enumerable own property names + * @returns {string[]|symbol[]} - An array of own property names and/or + * symbols for object + */ function getOwnPropertyIdentifiers(object, options = optionsDefault) { - const symbols = !options.symbols ? [] : - Object.getOwnPropertySymbols(object); + const symbols = !options.symbols + ? [] + : Object.getOwnPropertySymbols(object); // eslint-disable-next-line - const names = !options.names ? [] : - options.nonEnumerables ? Object.getOwnPropertyNames(object) : - Object.keys(object); + const names = !options.names + ? [] + : options.nonEnumerables + ? Object.getOwnPropertyNames(object) + : Object.keys(object); return [...names, ...symbols]; } /* @@ -154,21 +162,23 @@ function merge(source) { * converted to `true` where `null` and `undefined` becames `false`. Therefore * the `filter` method will keep only objects that are defined and not null. */ - Array.slice(arguments, 1).filter(Boolean).forEach((properties) => { - getOwnPropertyIdentifiers(properties).forEach((name) => { - descriptor[name] = Object.getOwnPropertyDescriptor(properties, name); + Array.slice(arguments, 1) + .filter(Boolean) + .forEach(properties => { + getOwnPropertyIdentifiers(properties).forEach(name => { + descriptor[name] = Object.getOwnPropertyDescriptor(properties, name); + }); }); - }); return Object.defineProperties(source, descriptor); } /** -* Appends a query string to a url. -* @param {string} url - a base url to append; must be static (data) or external -* @param {Object} args - query arguments, one or more object literal used to -* build a query string -* @returns {string} - an absolute url appended with a query string -*/ + * Appends a query string to a url. + * @param {string} url - a base url to append; must be static (data) or external + * @param {Object} args - query arguments, one or more object literal used to + * build a query string + * @returns {string} - an absolute url appended with a query string + */ function mergeQueryArgs(url, ...args) { // currently left to right // TODO, glind, decide order of merge here @@ -181,7 +191,7 @@ function mergeQueryArgs(url, ...args) { const merged = merge({}, ...args); // Set each search parameter in "merged" to its value in the query string, // building up the query string one search parameter at a time. - Object.keys(merged).forEach((k) => { + Object.keys(merged).forEach(k => { // k, the search parameter (ex: fxVersion) // q.get(k), returns the value of k, in query string, q (ex: 57.0.1a) log.debug(q.get(k), k, merged[k]); @@ -195,13 +205,13 @@ function mergeQueryArgs(url, ...args) { // sampling utils /** -* @async -* Converts a string into its sha256 hexadecimal representation. -* Note: This is ultimately used to make a hash of the user's telemetry clientID -* and the study name. -* @param {string} message - The message to convert. -* @returns {string} - a hexadecimal, 256-bit hash -*/ + * @async + * Converts a string into its sha256 hexadecimal representation. + * Note: This is ultimately used to make a hash of the user's telemetry clientID + * and the study name. + * @param {string} message - The message to convert. + * @returns {string} - a hexadecimal, 256-bit hash + */ async function sha256(message) { // encode as UTF-8 const msgBuffer = new TextEncoder("utf-8").encode(message); @@ -210,41 +220,42 @@ async function sha256(message) { // convert ArrayBuffer to Array const hashArray = Array.from(new Uint8Array(hashBuffer)); // convert bytes to hex string - const hashHex - = hashArray.map(b => ("00" + b.toString(16)).slice(-2)).join(""); + const hashHex = hashArray + .map(b => ("00" + b.toString(16)).slice(-2)) + .join(""); return hashHex; } /** -* Converts an array of length N into a cumulative sum array of length N, -* where n_i = sum(array.slice(0,i)) i.e. each element is the sum of all -* elements up to and including that element -* This is ultimately used for turning sample weights (AKA weightedVariations) -* into right hand limits (>= X) to deterministically select which variation -* a user receives. -* @example [.25,.3,.45] => [.25,.55,1.0]; if a user's sample weight were .25, -* they would fall into the left-most bucket -* @param {Number[]} arr - An array of sample weights (0 <= sample weight < 1) -* @returns {Number[]} - A cumulative sum array of sample weights -* (0 <= sample weight <= 1) -*/ + * Converts an array of length N into a cumulative sum array of length N, + * where n_i = sum(array.slice(0,i)) i.e. each element is the sum of all + * elements up to and including that element + * This is ultimately used for turning sample weights (AKA weightedVariations) + * into right hand limits (>= X) to deterministically select which variation + * a user receives. + * @example [.25,.3,.45] => [.25,.55,1.0]; if a user's sample weight were .25, + * they would fall into the left-most bucket + * @param {Number[]} arr - An array of sample weights (0 <= sample weight < 1) + * @returns {Number[]} - A cumulative sum array of sample weights + * (0 <= sample weight <= 1) + */ function cumsum(arr) { return arr.reduce(function(r, c, i) { r.push((r[i - 1] || 0) + c); return r; - }, [] ); + }, []); } /** -* Given sample weights (weightedVariations) and a particular position -* (fraction), return a variation. If no fraction given, return a variation -* at random fraction proportional to the weightVariations object -* @param {Object[]} weightedVariations - the array of branch name:weight pairs -* used to randomly assign the user to a branch -* @param {Number} fraction - a number (0 <= fraction < 1) -* @returns {Object} - the variation object in weightedVariations for the given -* fraction -*/ + * Given sample weights (weightedVariations) and a particular position + * (fraction), return a variation. If no fraction given, return a variation + * at random fraction proportional to the weightVariations object + * @param {Object[]} weightedVariations - the array of branch name:weight pairs + * used to randomly assign the user to a branch + * @param {Number} fraction - a number (0 <= fraction < 1) + * @returns {Object} - the variation object in weightedVariations for the given + * fraction + */ function chooseWeighted(weightedVariations, fraction = Math.random()) { /* weightedVaiations, list of: @@ -267,87 +278,89 @@ function chooseWeighted(weightedVariations, fraction = Math.random()) { } /** -* @async -* Converts a string into a fraction (0 <= fraction < 1) based on the first -* X bits of its sha256 hexadecimal representation -* Note: Salting (adding the study name to the telemetry clientID) ensures -* that the same user gets a different bucket/hash for each study. -* Hashing of the salted string ensures uniform hashing; i.e. that every -* bucket/variation gets filled. -* @param {string} saltedString - a salted string used to create a hash for -* the user -* @param {Number} bits - The first number of bits to use in the sha256 hex -* representation -* @returns {Number} - a fraction (0 <= fraction < 1) -*/ + * @async + * Converts a string into a fraction (0 <= fraction < 1) based on the first + * X bits of its sha256 hexadecimal representation + * Note: Salting (adding the study name to the telemetry clientID) ensures + * that the same user gets a different bucket/hash for each study. + * Hashing of the salted string ensures uniform hashing; i.e. that every + * bucket/variation gets filled. + * @param {string} saltedString - a salted string used to create a hash for + * the user + * @param {Number} bits - The first number of bits to use in the sha256 hex + * representation + * @returns {Number} - a fraction (0 <= fraction < 1) + */ async function hashFraction(saltedString, bits = 12) { const hash = await sha256(saltedString); return parseInt(hash.substr(0, bits), 16) / Math.pow(16, bits); } /** -* Class representing utilities for shield studies. -*/ + * Class representing utilities for shield studies. + */ class StudyUtils { /** - * Create a StudyUtils instance. - */ + * Create a StudyUtils instance. + */ constructor() { /* * TODO glind Answer: no. see if you can merge the construtor and setup * and export the class, rather than a singleton */ /** - * Handles a message received by the webExtension, sending a response back. - * @param {Object} webExtensionMsg object, see its schema - * @param {boolean} webExtensionMsg.shield - Whether or not the message - * is a shield message (intended for StudyUtils) - * @param {string} webExtensionMsg.msg - StudyUtils method to be called - *from the webExtension - * @param {*} webExtensionMsg.data - Data sent from webExtension - * @param {Object} sender - Details about the message sender, see - * runtime.onMessage MDN docs - * @param {responseCallback} sendResponse - The callback to send a response - * back to the webExtension - * @returns {boolean|undefined} - true if the message has been processed - * (shield message) or ignored (non-shield message) - */ - this.respondToWebExtensionMessage - = function({shield, msg, data}, sender, sendResponse) { - // @TODO glind: make sure we're using the webExtensionMsg schema - if (!shield) return true; - const allowedMethods = ["endStudy", "telemetry", "info"]; - if (!allowedMethods.includes(msg)) { - const errStr1 = "respondToWebExtensionMessage:"; - const errStr2 = "is not in allowed studyUtils methods:"; - throw new Error(`${errStr1} "${msg}" ${errStr2} ${allowedMethods}`); - } - /* + * Handles a message received by the webExtension, sending a response back. + * @param {Object} webExtensionMsg object, see its schema + * @param {boolean} webExtensionMsg.shield - Whether or not the message + * is a shield message (intended for StudyUtils) + * @param {string} webExtensionMsg.msg - StudyUtils method to be called + *from the webExtension + * @param {*} webExtensionMsg.data - Data sent from webExtension + * @param {Object} sender - Details about the message sender, see + * runtime.onMessage MDN docs + * @param {responseCallback} sendResponse - The callback to send a response + * back to the webExtension + * @returns {boolean|undefined} - true if the message has been processed + * (shield message) or ignored (non-shield message) + */ + this.respondToWebExtensionMessage = function( + { shield, msg, data }, + sender, + sendResponse, + ) { + // @TODO glind: make sure we're using the webExtensionMsg schema + if (!shield) return true; + const allowedMethods = ["endStudy", "telemetry", "info"]; + if (!allowedMethods.includes(msg)) { + const errStr1 = "respondToWebExtensionMessage:"; + const errStr2 = "is not in allowed studyUtils methods:"; + throw new Error(`${errStr1} "${msg}" ${errStr2} ${allowedMethods}`); + } + /* * handle async * Execute the StudyUtils method requested by the webExtension * then send the webExtension a response with their return value */ - Promise.resolve(this[msg](data)).then( - function(ans) { - log.debug("respondingTo", msg, ans); - sendResponse(ans); - }, - // function error eventually - ); - return true; - /* Ensure this method is bound to the instance of studyUtils, see + Promise.resolve(this[msg](data)).then( + function(ans) { + log.debug("respondingTo", msg, ans); + sendResponse(ans); + }, + // function error eventually + ); + return true; + /* Ensure this method is bound to the instance of studyUtils, see * callsite in bootstrap.js * TODO glind: bdanforth's claim: making this function a StudyUtils * method would also do this. */ - }.bind(this); + }.bind(this); /* * Expose sampling methods onto the exported studyUtils singleton, for use * by any Components.utils-importing module */ this.sample = { - sha256, cumsum, chooseWeighted, @@ -363,21 +376,22 @@ class StudyUtils { } /** - * Checks if the StudyUtils.setup method has been called - * @param {string} name - the name of a StudyUtils method - * @returns {void} - */ + * Checks if the StudyUtils.setup method has been called + * @param {string} name - the name of a StudyUtils method + * @returns {void} + */ throwIfNotSetup(name = "unknown") { - if (!this._isSetup) throw new Error( - name + ": this method can't be used until `setup` is called" - ); + if (!this._isSetup) + throw new Error( + name + ": this method can't be used until `setup` is called", + ); } /** - * Validates the studySetup object passed in from the addon. - * @param {Object} config - the studySetup object, see schema.studySetup.json - * @returns {StudyUtils} - the StudyUtils class instance - */ + * Validates the studySetup object passed in from the addon. + * @param {Object} config - the studySetup object, see schema.studySetup.json + * @returns {StudyUtils} - the StudyUtils class instance + */ setup(config) { log = createLog("shield-study-utils", config.log.studyUtils.level); @@ -390,9 +404,9 @@ class StudyUtils { } /** - * Resets the state of the study. Suggested use is for testing. - * @returns {void} - */ + * Resets the state of the study. Suggested use is for testing. + * @returns {void} + */ reset() { this.config = {}; delete this._variation; @@ -400,13 +414,13 @@ class StudyUtils { } /** - * @async - * Opens a new tab that loads a page with the specified URL. - * @param {string} url - the url of a page - * @param {Object} params - optional, see - * https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/Method/addTab - * @returns {void} - */ + * @async + * Opens a new tab that loads a page with the specified URL. + * @param {string} url - the url of a page + * @param {Object} params - optional, see + * https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/Method/addTab + * @returns {void} + */ async openTab(url, params = {}) { this.throwIfNotSetup("openTab"); log.debug(url, params); @@ -419,15 +433,16 @@ class StudyUtils { */ await new Promise(resolve => setTimeout(resolve, 30000)); } - Services.wm.getMostRecentWindow("navigator:browser") + Services.wm + .getMostRecentWindow("navigator:browser") .gBrowser.addTab(url, params); } /** - * @async - * Gets the telemetry client ID for the user. - * @returns {string} - the telemetry client ID - */ + * @async + * Gets the telemetry client ID for the user. + * @returns {string} - the telemetry client ID + */ async getTelemetryId() { const id = TelemetryController.clientID; /* istanbul ignore next */ @@ -438,10 +453,10 @@ class StudyUtils { } /** - * Sets the variation for the StudyUtils instance. - * @param {Object} variation - the study variation for this user - * @returns {StudyUtils} - the StudyUtils class instance - */ + * Sets the variation for the StudyUtils instance. + * @param {Object} variation - the study variation for this user + * @returns {StudyUtils} - the StudyUtils class instance + */ setVariation(variation) { this.throwIfNotSetup("setVariation"); this._variation = variation; @@ -449,22 +464,22 @@ class StudyUtils { } /** - * Gets the variation for the StudyUtils instance. - * @returns {Object} - the study variation for this user - */ + * Gets the variation for the StudyUtils instance. + * @returns {Object} - the study variation for this user + */ getVariation() { this.throwIfNotSetup("getvariation"); return this._variation; } /** - * @async - * Deterministically selects and returns the study variation for the user. - * @param {Object[]} weightedVariations - see schema.weightedVariations.json - * @param {Number} rng - randomly generated number (0 <= rng < 1) of the form - * returned by Math.random; can be set explicitly for testing - * @returns {Object} - the study variation for this user - */ + * @async + * Deterministically selects and returns the study variation for the user. + * @param {Object[]} weightedVariations - see schema.weightedVariations.json + * @param {Number} rng - randomly generated number (0 <= rng < 1) of the form + * returned by Math.random; can be set explicitly for testing + * @returns {Object} - the study variation for this user + */ async deterministicVariation(weightedVariations, rng = null) { // hash the studyName and telemetryId to get the same branch every time. this.throwIfNotSetup("deterministicVariation needs studyName"); @@ -479,18 +494,18 @@ class StudyUtils { } /** - * Gets the Shield recipe client ID. - * @returns {string} - the Shield recipe client ID. - */ + * Gets the Shield recipe client ID. + * @returns {string} - the Shield recipe client ID. + */ getShieldId() { const key = "extensions.shield-recipe-client.user_id"; return Services.prefs.getCharPref(key, ""); } /** - * Packages information about the study into an object. - * @returns {Object} - study information, see schema.studySetup.json - */ + * Packages information about the study into an object. + * @returns {Object} - study information, see schema.studySetup.json + */ info() { log.debug("getting info"); this.throwIfNotSetup("info"); @@ -503,9 +518,9 @@ class StudyUtils { } /** - * Get the telemetry configuration for the study. - * @returns {Object} - the telemetry cofiguration, see schema.studySetup.json - */ + * Get the telemetry configuration for the study. + * @returns {Object} - the telemetry cofiguration, see schema.studySetup.json + */ // TODO glind, maybe this is getter / setter? get telemetryConfig() { this.throwIfNotSetup("telemetryConfig"); @@ -513,57 +528,57 @@ class StudyUtils { } /** - * Sends an 'enter' telemetry ping for the study; should be called on addon - * startup for the reason ADDON_INSTALL. For more on study states like 'enter' - * see ABOUT.md at github.com/mozilla/shield-studies-addon-template - * @returns {void} - */ + * Sends an 'enter' telemetry ping for the study; should be called on addon + * startup for the reason ADDON_INSTALL. For more on study states like 'enter' + * see ABOUT.md at github.com/mozilla/shield-studies-addon-template + * @returns {void} + */ firstSeen() { log.debug(`firstSeen`); this.throwIfNotSetup("firstSeen uses telemetry."); - this._telemetry({study_state: "enter"}, "shield-study"); + this._telemetry({ study_state: "enter" }, "shield-study"); } /** - * Marks the study's telemetry pings as being part of this experimental - * cohort in a way that downstream data pipeline tools (like ExperimentsViewer) - * can use it. - * @returns {void} - */ + * Marks the study's telemetry pings as being part of this experimental + * cohort in a way that downstream data pipeline tools + * (like ExperimentsViewer) can use it. + * @returns {void} + */ setActive() { this.throwIfNotSetup("setActive uses telemetry."); const info = this.info(); log.debug( "marking TelemetryEnvironment", info.studyName, - info.variation.name + info.variation.name, ); TelemetryEnvironment.setExperimentActive( info.studyName, - info.variation.name + info.variation.name, ); } /** - * Removes the study from the active list of telemetry experiments - * @returns {void} - */ + * Removes the study from the active list of telemetry experiments + * @returns {void} + */ unsetActive() { this.throwIfNotSetup("unsetActive uses telemetry."); const info = this.info(); log.debug( "unmarking TelemetryEnvironment", info.studyName, - info.variation.name + info.variation.name, ); TelemetryEnvironment.setExperimentInactive(info.studyName); } /** - * Uninstalls the shield study addon, given its addon id. - * @param {string} id - the addon id - * @returns {void} - */ + * Uninstalls the shield study addon, given its addon id. + * @param {string} id - the addon id + * @returns {void} + */ uninstall(id) { if (!id) id = this.info().addon.id; if (!id) { @@ -574,37 +589,37 @@ class StudyUtils { } /** - * @async - * Adds the study to the active list of telemetry experiments and sends the - * "installed" telemetry ping if applicable - * @param {string} reason - The reason the addon has started up - * @returns {void} - */ - async startup({reason}) { + * @async + * Adds the study to the active list of telemetry experiments and sends the + * "installed" telemetry ping if applicable + * @param {string} reason - The reason the addon has started up + * @returns {void} + */ + async startup({ reason }) { this.throwIfNotSetup("startup"); log.debug(`startup ${reason}`); this.setActive(); if (reason === REASONS.ADDON_INSTALL) { - this._telemetry({study_state: "installed"}, "shield-study"); + this._telemetry({ study_state: "installed" }, "shield-study"); } } /** - * @async - * Ends the study: - * - Removes the study from the active list of telemetry experiments - * - Opens a new tab at a specified URL, if present (e.g. for a survey) - * - Sends a telemetry ping about the nature of the ending - * (positive, neutral, negative) - * - Sends an exit telemetry ping - * @param {Object} param - A details object describing why the study is ending - * @param {string} param.reason - The reason the study is ending, see - * schema.studySetup.json - * @param {string} param.fullname - optional, the full name of the study - * state, see schema.studySetup.json - * @returns {void} - */ - async endStudy({reason, fullname}) { + * @async + * Ends the study: + * - Removes the study from the active list of telemetry experiments + * - Opens a new tab at a specified URL, if present (e.g. for a survey) + * - Sends a telemetry ping about the nature of the ending + * (positive, neutral, negative) + * - Sends an exit telemetry ping + * @param {Object} param - A details object describing why the study is ending + * @param {string} param.reason - The reason the study is ending, see + * schema.studySetup.json + * @param {string} param.fullname - optional, the full name of the study + * state, see schema.studySetup.json + * @returns {void} + */ + async endStudy({ reason, fullname }) { this.throwIfNotSetup("endStudy"); if (this._isEnding) { log.debug("endStudy, already ending!"); @@ -623,7 +638,7 @@ class StudyUtils { if (ending) { // baseUrl: needs to be appended with query arguments before use, // exactUrl: used as is - const {baseUrl, exactUrl} = ending; + const { baseUrl, exactUrl } = ending; if (exactUrl) { this.openTab(exactUrl); } else if (baseUrl) { @@ -642,7 +657,7 @@ class StudyUtils { case "ended-positive": case "ended-neutral": case "ended-negative": - this._telemetry({study_state: reason, fullname}, "shield-study"); + this._telemetry({ study_state: reason, fullname }, "shield-study"); break; default: this._telemetry( @@ -650,21 +665,21 @@ class StudyUtils { study_state: "ended-neutral", study_state_fullname: reason, }, - "shield-study" + "shield-study", ); - // unless we know better TODO grl + // unless we know better TODO grl } // these are all exits - this._telemetry({study_state: "exit"}, "shield-study"); + this._telemetry({ study_state: "exit" }, "shield-study"); this.uninstall(); // TODO glind. should be controllable by arg? } /** - * @async - * Builds an object whose properties are query arguments that can be - * appended to a study ending url - * @returns {Object} - the query arguments for the study - */ + * @async + * Builds an object whose properties are query arguments that can be + * appended to a study ending url + * @returns {Object} - the query arguments for the study + */ async endingQueryArgs() { // TODO glind, make this back breaking! this.throwIfNotSetup("endingQueryArgs"); @@ -684,29 +699,29 @@ class StudyUtils { } /** - * @async - * Validates and submits telemetry pings from StudyUtils. - * @param {Object} data - the data to send as part of the telemetry packet - * @param {string} bucket - the type of telemetry packet to be sent - * @returns {Promise|boolean} - A promise that resolves with the ping id - * once the ping is stored or sent, or false if - * - there is a validation error, - * - the packet is of type "shield-study-error" - * - the study's telemetryConfig.send is set to false - */ + * @async + * Validates and submits telemetry pings from StudyUtils. + * @param {Object} data - the data to send as part of the telemetry packet + * @param {string} bucket - the type of telemetry packet to be sent + * @returns {Promise|boolean} - A promise that resolves with the ping id + * once the ping is stored or sent, or false if + * - there is a validation error, + * - the packet is of type "shield-study-error" + * - the study's telemetryConfig.send is set to false + */ async _telemetry(data, bucket = "shield-study-addon") { log.debug(`telemetry in: ${bucket} ${JSON.stringify(data)}`); this.throwIfNotSetup("_telemetry"); const info = this.info(); const payload = { - version: PACKET_VERSION, - study_name: info.studyName, - branch: info.variation.name, - addon_version: info.addon.version, + version: PACKET_VERSION, + study_name: info.studyName, + branch: info.variation.name, + addon_version: info.addon.version, shield_version: UTILS_VERSION, - type: bucket, + type: bucket, data, - testing: !this.telemetryConfig.removeTestingFlag, + testing: !this.telemetryConfig.removeTestingFlag, }; let validation; @@ -727,10 +742,10 @@ class StudyUtils { */ if (validation.errors.length) { const errorReport = { - "error_id": "jsonschema-validation", - "error_source": "addon", - "severity": "fatal", - "message": JSON.stringify(validation.errors), + error_id: "jsonschema-validation", + error_source: "addon", + severity: "fatal", + message: JSON.stringify(validation.errors), }; if (bucket === "shield-study-error") { // log: if it's a warn or error, it breaks jpm test @@ -743,7 +758,7 @@ class StudyUtils { log.debug(`telemetry: ${JSON.stringify(payload)}`); // FIXME marcrowo: addClientId makes the ping not appear in test? // seems like a problem with Telemetry, not the shield-study-utils library - const telOptions = {addClientId: true, addEnvironment: true}; + const telOptions = { addClientId: true, addEnvironment: true }; if (!this.telemetryConfig.send) { log.debug("NOT sending. `telemetryConfig.send` is false"); return false; @@ -752,12 +767,12 @@ class StudyUtils { } /** - * @async - * Validates and submits telemetry pings from the addon; mostly from - * webExtension messages. - * @param {Object} data - the data to send as part of the telemetry packet - * @returns {Promise|boolean} - see StudyUtils._telemetry - */ + * @async + * Validates and submits telemetry pings from the addon; mostly from + * webExtension messages. + * @param {Object} data - the data to send as part of the telemetry packet + * @returns {Promise|boolean} - see StudyUtils._telemetry + */ async telemetry(data) { log.debug(`telemetry ${JSON.stringify(data)}`); const toSubmit = { @@ -768,38 +783,37 @@ class StudyUtils { } /** - * Submits error report telemetry pings. - * @param {Object} errorReport - the error report, see StudyUtils._telemetry - * @returns {Promise|boolean} - see StudyUtils._telemetry - */ + * Submits error report telemetry pings. + * @param {Object} errorReport - the error report, see StudyUtils._telemetry + * @returns {Promise|boolean} - see StudyUtils._telemetry + */ telemetryError(errorReport) { return this._telemetry(errorReport, "shield-study-error"); } /** - * Sets the logging level. This is can be called from the addon, even after the - * log has been created. - * @param {string} descriptor - the Log level (e.g. "trace", "error", ...) - * @returns {void} - */ + * Sets the logging level. This is can be called from the addon, even + * after the log has been created. + * @param {string} descriptor - the Log level (e.g. "trace", "error", ...) + * @returns {void} + */ setLoggingLevel(descriptor) { log.level = Log.Level[descriptor]; } - } /** -* Creates a log for debugging. -* Note: Log.jsm is used over Console.log/warn/error because: -* - Console has limited log levels -* - Console is not pref-controllable. Log can be turned on and off using -* config.log (see ./addon/Config.jsm in -* github.com/mozilla/shield-study-addon-template) -* - Console can create linting errors and warnings. -* @param {string} name - the name of the Logger instance -* @param {string} levelWord - the Log level (e.g. "trace", "error", ...) -* @returns {Object} - the Logger instance, see gre/modules/Log.jsm -*/ + * Creates a log for debugging. + * Note: Log.jsm is used over Console.log/warn/error because: + * - Console has limited log levels + * - Console is not pref-controllable. Log can be turned on and off using + * config.log (see ./addon/Config.jsm in + * github.com/mozilla/shield-study-addon-template) + * - Console can create linting errors and warnings. + * @param {string} name - the name of the Logger instance + * @param {string} levelWord - the Log level (e.g. "trace", "error", ...) + * @returns {Object} - the Logger instance, see gre/modules/Log.jsm + */ function createLog(name, levelWord) { Cu.import("resource://gre/modules/Log.jsm"); var L = Log.repository.getLogger(name); @@ -812,16 +826,18 @@ function createLog(name, levelWord) { // addon state change reasons const REASONS = { - APP_STARTUP: 1, // The application is starting up. - APP_SHUTDOWN: 2, // The application is shutting down. - ADDON_ENABLE: 3, // The add-on is being enabled. - ADDON_DISABLE: 4, // The add-on is being disabled. (Also sent at uninstall) - ADDON_INSTALL: 5, // The add-on is being installed. - ADDON_UNINSTALL: 6, // The add-on is being uninstalled. - ADDON_UPGRADE: 7, // The add-on is being upgraded. - ADDON_DOWNGRADE: 8, // The add-on is being downgraded. + APP_STARTUP: 1, // The application is starting up. + APP_SHUTDOWN: 2, // The application is shutting down. + ADDON_ENABLE: 3, // The add-on is being enabled. + ADDON_DISABLE: 4, // The add-on is being disabled. (Also sent at uninstall) + ADDON_INSTALL: 5, // The add-on is being installed. + ADDON_UNINSTALL: 6, // The add-on is being uninstalled. + ADDON_UPGRADE: 7, // The add-on is being upgraded. + ADDON_DOWNGRADE: 8, // The add-on is being downgraded. }; -for (const r in REASONS) { REASONS[REASONS[r]] = r; } +for (const r in REASONS) { + REASONS[REASONS[r]] = r; +} // Actually create the singleton. var studyUtils = new StudyUtils(); diff --git a/src/schema.studySetup.json b/src/schema.studySetup.json index a061b96..ef4616a 100644 --- a/src/schema.studySetup.json +++ b/src/schema.studySetup.json @@ -1,10 +1,10 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "title": "study setup", + "title": "study setup", "type": "object", "definitions": { "idString": { - "description": "between 1,100 chars, no spaces, unicode ok.", + "description": "between 1,100 chars, no spaces, unicode ok.", "type": "string", "pattern": "^\\S+$", "minLength": 1, @@ -27,7 +27,8 @@ }, "study_state_fullname": { "type": "string", - "description": "Second part of name of state, if any. Study-specific for study-defined endings." + "description": + "Second part of name of state, if any. Study-specific for study-defined endings." }, "url": { "type": "string" @@ -60,23 +61,13 @@ }, "onInvalid": { "type": "string", - "enum": [ - "throw", - "log" - ] + "enum": ["throw", "log"] } }, - "required": [ - "removeTestingFlag", - "send" - ] + "required": ["removeTestingFlag", "send"] } }, - "required": [ - "studyName", - "endings", - "telemetry" - ] + "required": ["studyName", "endings", "telemetry"] }, "addon": { "type": "object", @@ -90,14 +81,8 @@ "description": "Semantic version of the addon." } }, - "required": [ - "id", - "version" - ] + "required": ["id", "version"] } }, - "required": [ - "study", - "addon" - ] -} \ No newline at end of file + "required": ["study", "addon"] +} diff --git a/src/schema.webExtensionMsg.json b/src/schema.webExtensionMsg.json index 5c5814f..c5df40b 100644 --- a/src/schema.webExtensionMsg.json +++ b/src/schema.webExtensionMsg.json @@ -8,18 +8,11 @@ }, "msg": { "type": "string", - "enum": [ - "endStudy", - "telemetry", - "info" - ] + "enum": ["endStudy", "telemetry", "info"] }, "data": { "type": "object" } }, - "required": [ - "shield", - "msg" - ] + "required": ["shield", "msg"] } diff --git a/src/schema.weightedVariations.json b/src/schema.weightedVariations.json index f6b7cd5..3c7c809 100644 --- a/src/schema.weightedVariations.json +++ b/src/schema.weightedVariations.json @@ -9,10 +9,7 @@ "type": "number", "minimum": 0 }, - "required": [ - "name", - "weight" - ] + "required": ["name", "weight"] } }, "items": { diff --git a/src/schemas.js b/src/schemas.js index 27ecb3d..17dd1b6 100644 --- a/src/schemas.js +++ b/src/schemas.js @@ -1,8 +1,8 @@ module.exports = { - "shield-study": require("shield-study-schemas/schemas-client/shield-study.schema.json"), - "shield-study-addon": require("shield-study-schemas/schemas-client/shield-study-addon.schema.json"), - "shield-study-error": require("shield-study-schemas/schemas-client/shield-study-error.schema.json"), - "studySetup": require("./schema.studySetup.json"), - "webExtensionMsg": require("./schema.webExtensionMsg.json"), - "weightedVariations": require("./schema.weightedVariations.json"), + "shield-study": require("shield-study-schemas/schemas-client/shield-study.schema.json"), // eslint-disable-line max-len + "shield-study-addon": require("shield-study-schemas/schemas-client/shield-study-addon.schema.json"), // eslint-disable-line max-len + "shield-study-error": require("shield-study-schemas/schemas-client/shield-study-error.schema.json"), // eslint-disable-line max-len + studySetup: require("./schema.studySetup.json"), + webExtensionMsg: require("./schema.webExtensionMsg.json"), + weightedVariations: require("./schema.weightedVariations.json"), }; diff --git a/test-addon/bootstrap.js b/test-addon/bootstrap.js index 4bccfe5..fb3845d 100644 --- a/test-addon/bootstrap.js +++ b/test-addon/bootstrap.js @@ -1,6 +1,10 @@ const { classes: Cc, interfaces: Ci, utils: Cu } = Components; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "studyUtils", "resource://test-addon/StudyUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter( + this, + "studyUtils", + "resource://test-addon/StudyUtils.jsm", +); this.install = function(data, reason) {}; diff --git a/test-addon/utils.jsm b/test-addon/utils.jsm index 1472083..1468391 100644 --- a/test-addon/utils.jsm +++ b/test-addon/utils.jsm @@ -1,6 +1,9 @@ /* eslint no-unused-vars: "off" */ -const { studyUtils } = Components.utils.import("resource://test-addon/StudyUtils.jsm", {}); +const { studyUtils } = Components.utils.import( + "resource://test-addon/StudyUtils.jsm", + {}, +); Components.utils.import("resource://gre/modules/TelemetryArchive.jsm"); Components.utils.import("resource://gre/modules/Console.jsm"); @@ -11,8 +14,8 @@ function fakeSetup() { study: { studyName: "shield-utils-test", endings: { - "expired": { - "baseUrl": "http://www.example.com/?reason=expired", + expired: { + baseUrl: "http://www.example.com/?reason=expired", }, }, telemetry: { send: true, removeTestingFlag: false }, @@ -23,7 +26,7 @@ function fakeSetup() { level: "Warn", }, }, - addon: {id: "1", version: "1"}, + addon: { id: "1", version: "1" }, }); studyUtils.setVariation({ name: "puppers", weight: "2" }); } @@ -36,6 +39,8 @@ async function getMostRecentPingsByType(type) { const filteredPings = pings.filter(p => p.type === type); filteredPings.sort((a, b) => b.timestampCreated - a.timestampCreated); - const pingData = filteredPings.map(ping => TelemetryArchive.promiseArchivedPingById(ping.id)); + const pingData = filteredPings.map(ping => + TelemetryArchive.promiseArchivedPingById(ping.id), + ); return Promise.all(pingData); } diff --git a/test/shield_utils_test.js b/test/shield_utils_test.js index d54b2ec..41ea5b4 100644 --- a/test/shield_utils_test.js +++ b/test/shield_utils_test.js @@ -14,7 +14,7 @@ describe("Shield Study Utils Functional Tests", function() { let driver; - before(async() => { + before(async () => { driver = await utils.promiseSetupDriver(); // install the addon (note: returns addon id) await utils.installAddon(driver); @@ -22,56 +22,84 @@ describe("Shield Study Utils Functional Tests", function() { after(() => driver.quit()); - it("should return the correct variation", async() => { - const variation = await driver.executeAsyncScript(async(callback) => { - const { studyUtils } = Components.utils.import("resource://test-addon/StudyUtils.jsm", {}); + it("should return the correct variation", async () => { + const variation = await driver.executeAsyncScript(async callback => { + const { studyUtils } = Components.utils.import( + "resource://test-addon/StudyUtils.jsm", + {}, + ); // TODO move this to a Config.jsm file const studyConfig = { studyName: "shieldStudyUtilsTest", - "weightedVariations": [ - {"name": "control", - "weight": 1}, - {"name": "kittens", - "weight": 1.5}, - {"name": "puppers", - "weight": 2}, // we want more puppers in our sample + weightedVariations: [ + { + name: "control", + weight: 1, + }, + { + name: "kittens", + weight: 1.5, + }, + { + name: "puppers", + weight: 2, + }, // we want more puppers in our sample ], }; const sample = studyUtils.sample; const hashFraction = await sample.hashFraction("test"); - const chosenVariation = await sample.chooseWeighted(studyConfig.weightedVariations, hashFraction); + const chosenVariation = await sample.chooseWeighted( + studyConfig.weightedVariations, + hashFraction, + ); callback(chosenVariation); }); assert(variation.name === "puppers"); }); - it("telemetry should be working", async() => { - const shieldTelemetryPing = await driver.executeAsyncScript(async(callback) => { - const { fakeSetup, getMostRecentPingsByType } = Components.utils.import("resource://test-addon/utils.jsm", {}); - const { studyUtils } = Components.utils.import("resource://test-addon/StudyUtils.jsm", {}); - Components.utils.import("resource://gre/modules/TelemetryArchive.jsm"); + it("telemetry should be working", async () => { + const shieldTelemetryPing = await driver.executeAsyncScript( + async callback => { + const { fakeSetup, getMostRecentPingsByType } = Components.utils.import( + "resource://test-addon/utils.jsm", + {}, + ); + const { studyUtils } = Components.utils.import( + "resource://test-addon/StudyUtils.jsm", + {}, + ); + Components.utils.import("resource://gre/modules/TelemetryArchive.jsm"); - fakeSetup(); + fakeSetup(); - await studyUtils.telemetry({ "foo": "bar" }); + await studyUtils.telemetry({ foo: "bar" }); - // TODO Fix this hackiness; caused by addClientId option in submitExternalPing - // The ping seems to be sending (appears in about:telemetry) but does not appear - // in the pings array - await new Promise(resolve => setTimeout(resolve, 1000)); + // TODO Fix this hackiness; caused by addClientId option in submitExternalPing + // The ping seems to be sending (appears in about:telemetry) but does not appear + // in the pings array + await new Promise(resolve => setTimeout(resolve, 1000)); - const shieldPings = await getMostRecentPingsByType("shield-study-addon"); - callback(shieldPings[0]); - }); + const shieldPings = await getMostRecentPingsByType( + "shield-study-addon", + ); + callback(shieldPings[0]); + }, + ); assert(shieldTelemetryPing.payload.data.attributes.foo === "bar"); }); - describe("test the library's \"startup\" process", function() { - it("should send the correct ping on first seen", async() => { - const firstSeenPing = await driver.executeAsyncScript(async(callback) => { - const { fakeSetup, getMostRecentPingsByType } = Components.utils.import("resource://test-addon/utils.jsm", {}); - const { studyUtils } = Components.utils.import("resource://test-addon/StudyUtils.jsm", {}); + describe('test the library\'s "startup" process', function() { + it("should send the correct ping on first seen", async () => { + const firstSeenPing = await driver.executeAsyncScript(async callback => { + const { fakeSetup, getMostRecentPingsByType } = Components.utils.import( + "resource://test-addon/utils.jsm", + {}, + ); + const { studyUtils } = Components.utils.import( + "resource://test-addon/StudyUtils.jsm", + {}, + ); Components.utils.import("resource://gre/modules/TelemetryArchive.jsm"); fakeSetup(); @@ -84,30 +112,46 @@ describe("Shield Study Utils Functional Tests", function() { assert(firstSeenPing.payload.data.study_state === "enter"); }); - it("should set the experiment to active in Telemetry", async() => { - const activeExperiments = await driver.executeAsyncScript(async(callback) => { - const { fakeSetup } = Components.utils.import("resource://test-addon/utils.jsm", {}); - const { studyUtils } = Components.utils.import("resource://test-addon/StudyUtils.jsm", {}); - Components.utils.import("resource://gre/modules/TelemetryEnvironment.jsm"); - - fakeSetup(); - - studyUtils.setActive(); - - callback(TelemetryEnvironment.getActiveExperiments()); - }); + it("should set the experiment to active in Telemetry", async () => { + const activeExperiments = await driver.executeAsyncScript( + async callback => { + const { fakeSetup } = Components.utils.import( + "resource://test-addon/utils.jsm", + {}, + ); + const { studyUtils } = Components.utils.import( + "resource://test-addon/StudyUtils.jsm", + {}, + ); + Components.utils.import( + "resource://gre/modules/TelemetryEnvironment.jsm", + ); + + fakeSetup(); + + studyUtils.setActive(); + + callback(TelemetryEnvironment.getActiveExperiments()); + }, + ); assert(activeExperiments.hasOwnProperty("shield-utils-test")); }); - it("should send the correct telemetry ping on first install", async() => { - const installedPing = await driver.executeAsyncScript(async(callback) => { - const { fakeSetup, getMostRecentPingsByType } = Components.utils.import("resource://test-addon/utils.jsm", {}); - const { studyUtils } = Components.utils.import("resource://test-addon/StudyUtils.jsm", {}); + it("should send the correct telemetry ping on first install", async () => { + const installedPing = await driver.executeAsyncScript(async callback => { + const { fakeSetup, getMostRecentPingsByType } = Components.utils.import( + "resource://test-addon/utils.jsm", + {}, + ); + const { studyUtils } = Components.utils.import( + "resource://test-addon/StudyUtils.jsm", + {}, + ); Components.utils.import("resource://gre/modules/TelemetryArchive.jsm"); fakeSetup(); - await studyUtils.startup({reason: 5}); // ADDON_INSTALL = 5 + await studyUtils.startup({ reason: 5 }); // ADDON_INSTALL = 5 const studyPings = await getMostRecentPingsByType("shield-study"); callback(studyPings[0]); @@ -117,37 +161,50 @@ describe("Shield Study Utils Functional Tests", function() { }); describe("test the library's endStudy() function", function() { - before(async() => { - await driver.executeAsyncScript(async(callback) => { - const { fakeSetup } = Components.utils.import("resource://test-addon/utils.jsm", {}); - const { studyUtils } = Components.utils.import("resource://test-addon/StudyUtils.jsm", {}); + before(async () => { + await driver.executeAsyncScript(async callback => { + const { fakeSetup } = Components.utils.import( + "resource://test-addon/utils.jsm", + {}, + ); + const { studyUtils } = Components.utils.import( + "resource://test-addon/StudyUtils.jsm", + {}, + ); fakeSetup(); // TODO add tests for other reasons (?) - await studyUtils.endStudy({reason: "expired", fullname: "TEST_FULLNAME"}); + await studyUtils.endStudy({ + reason: "expired", + fullname: "TEST_FULLNAME", + }); callback(); }); }); - it("should set the experiment as inactive", async() => { - const activeExperiments = await driver.executeAsyncScript(async(callback) => { - Components.utils.import("resource://gre/modules/TelemetryEnvironment.jsm"); - callback(TelemetryEnvironment.getActiveExperiments()); - }); + it("should set the experiment as inactive", async () => { + const activeExperiments = await driver.executeAsyncScript( + async callback => { + Components.utils.import( + "resource://gre/modules/TelemetryEnvironment.jsm", + ); + callback(TelemetryEnvironment.getActiveExperiments()); + }, + ); assert(!activeExperiments.hasOwnProperty("shield-utils-test")); }); describe("test the opening of a URL at the end of the study", function() { - it("should open a new tab", async() => { - const newTabOpened = await driver.wait(async() => { + it("should open a new tab", async () => { + const newTabOpened = await driver.wait(async () => { const handles = await driver.getAllWindowHandles(); return handles.length === 2; // opened a new tab }, 3000); assert(newTabOpened); }); - it("should open a new tab to the correct URL", async() => { + it("should open a new tab to the correct URL", async () => { const currentHandle = await driver.getWindowHandle(); driver.setContext(Context.CONTENT); // Find the new window handle. @@ -158,27 +215,35 @@ describe("Shield Study Utils Functional Tests", function() { newWindowHandle = handle; } } - const correctURLOpened = await driver.wait(async() => { + const correctURLOpened = await driver.wait(async () => { await driver.switchTo().window(newWindowHandle); const currentURL = await driver.getCurrentUrl(); - return currentURL.startsWith("http://www.example.com/?reason=expired"); + return currentURL.startsWith( + "http://www.example.com/?reason=expired", + ); }); assert(correctURLOpened); }); }); - it("should send the correct reason telemetry", async() => { - const pings = await driver.executeAsyncScript(async(callback) => { - const { getMostRecentPingsByType } = Components.utils.import("resource://test-addon/utils.jsm", {}); + it("should send the correct reason telemetry", async () => { + const pings = await driver.executeAsyncScript(async callback => { + const { getMostRecentPingsByType } = Components.utils.import( + "resource://test-addon/utils.jsm", + {}, + ); const studyPings = await getMostRecentPingsByType("shield-study"); callback(studyPings[1]); // ping before the most recent ping }); assert(pings.payload.data.study_state === "expired"); }); - it("should send the uninstall telemetry", async() => { - const pings = await driver.executeAsyncScript(async(callback) => { - const { getMostRecentPingsByType } = Components.utils.import("resource://test-addon/utils.jsm", {}); + it("should send the uninstall telemetry", async () => { + const pings = await driver.executeAsyncScript(async callback => { + const { getMostRecentPingsByType } = Components.utils.import( + "resource://test-addon/utils.jsm", + {}, + ); const studyPings = await getMostRecentPingsByType("shield-study"); callback(studyPings[0]); }); diff --git a/test/utils.js b/test/utils.js index f06a237..8722d83 100644 --- a/test/utils.js +++ b/test/utils.js @@ -27,7 +27,7 @@ const FIREFOX_PREFERENCES = { "devtools.chrome.enabled": true, "devtools.debugger.remote-enabled": true, "devtools.debugger.prompt-connection": false, - "general.warnOnAboutConfig": false + "general.warnOnAboutConfig": false, }; // useful if we need to test on a specific version of Firefox @@ -44,10 +44,10 @@ async function promiseActualBinary(binary) { } } -module.exports.promiseSetupDriver = async() => { +module.exports.promiseSetupDriver = async () => { const profile = new firefox.Profile(); - Object.keys(FIREFOX_PREFERENCES).forEach((key) => { + Object.keys(FIREFOX_PREFERENCES).forEach(key => { profile.setPreference(key, FIREFOX_PREFERENCES[key]); }); @@ -58,30 +58,44 @@ module.exports.promiseSetupDriver = async() => { .forBrowser("firefox") .setFirefoxOptions(options); - const binaryLocation = await promiseActualBinary(process.env.FIREFOX_BINARY || "firefox"); + const binaryLocation = await promiseActualBinary( + process.env.FIREFOX_BINARY || "firefox", + ); await options.setBinary(new firefox.Binary(binaryLocation)); const driver = await builder.build(); driver.setContext(Context.CHROME); return driver; }; -module.exports.installAddon = async(driver) => { +module.exports.installAddon = async driver => { // references: // https://bugzilla.mozilla.org/show_bug.cgi?id=1298025 // https://github.com/mozilla/geckodriver/releases/tag/v0.17.0 const fileLocation = path.join(process.cwd(), process.env.XPI_NAME); const executor = driver.getExecutor(); - executor.defineCommand("installAddon", "POST", "/session/:sessionId/moz/addon/install"); + executor.defineCommand( + "installAddon", + "POST", + "/session/:sessionId/moz/addon/install", + ); const installCmd = new cmd.Command("installAddon"); const session = await driver.getSession(); - installCmd.setParameters({ sessionId: session.getId(), path: fileLocation, temporary: true }); + installCmd.setParameters({ + sessionId: session.getId(), + path: fileLocation, + temporary: true, + }); return executor.execute(installCmd); }; -module.exports.uninstallAddon = async(driver, id) => { +module.exports.uninstallAddon = async (driver, id) => { const executor = driver.getExecutor(); - executor.defineCommand("uninstallAddon", "POST", "/session/:sessionId/moz/addon/uninstall"); + executor.defineCommand( + "uninstallAddon", + "POST", + "/session/:sessionId/moz/addon/uninstall", + ); const uninstallCmd = new cmd.Command("uninstallAddon"); const session = await driver.getSession(); diff --git a/webpack.config.js b/webpack.config.js index c888164..ccc134a 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,11 +1,11 @@ -var path = require('path'); -const webpack = require('webpack'); //to access built-in plugins +var path = require("path"); +const webpack = require("webpack"); //to access built-in plugins module.exports = { - entry: './src/StudyUtils.in.jsm', + entry: "./src/StudyUtils.in.jsm", output: { - path: path.resolve(__dirname, 'dist'), - filename: 'StudyUtils.jsm', - libraryTarget: 'this' // Possible value - amd, commonjs, commonjs2, commonjs-module, this, var - } + path: path.resolve(__dirname, "dist"), + filename: "StudyUtils.jsm", + libraryTarget: "this", // Possible value - amd, commonjs, commonjs2, commonjs-module, this, var + }, }; From 08ce53e9ab27f8de0ba191cee5f62824ff4b393b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Sat, 24 Mar 2018 00:42:12 +0200 Subject: [PATCH 06/50] Version bump to 5.0.0-alpha1 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 18a5261..6f8b9d4 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "shield-studies-addon-utils", "description": "Utilities for building Shield-Study Mozilla Firefox addons.", - "version": "4.1.0", - "author": "Gregg Lind ", + "version": "5.0.0-alpha1", + "author": "Mozilla", "bugs": { "url": "https://github.com/mozilla/shield-studies-addon-utils/issues" }, From 10b430fd285019b579442eb5b58a3638d73f1418 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Sat, 24 Mar 2018 00:51:14 +0200 Subject: [PATCH 07/50] Imported js that should be part of study utils rather than the template repo --- src/shieldUtils/api.js | 122 +++++++++++++++++ src/shieldUtils/jsm/StudyUtilsBootstrap.jsm | 141 ++++++++++++++++++++ src/shieldUtils/schema.json | 47 +++++++ 3 files changed, 310 insertions(+) create mode 100644 src/shieldUtils/api.js create mode 100644 src/shieldUtils/jsm/StudyUtilsBootstrap.jsm create mode 100644 src/shieldUtils/schema.json diff --git a/src/shieldUtils/api.js b/src/shieldUtils/api.js new file mode 100644 index 0000000..6794cd1 --- /dev/null +++ b/src/shieldUtils/api.js @@ -0,0 +1,122 @@ +/* global ExtensionAPI */ + +this.shieldUtils = class extends ExtensionAPI { + /** + * We don't need to override the constructor for other + * reasons than to clarify the class member "extension" + * being of type Extension + * + * @param extension Extension + */ + constructor(extension) { + super(extension); + /** + * @type Extension + */ + this.extension = extension; + } + + /** + * Extension Shutdown + * APIs that allocate any resources (e.g., adding elements to the browser’s user interface, + * setting up internal event listeners, etc.) must free these resources when the extension + * for which they are allocated is shut down. + */ + onShutdown(shutdownReason) { + console.log("onShutdown", shutdownReason); + // TODO: debootstrap study + } + + getAPI(context) { + const { config } = ChromeUtils.import( + context.extension.getURL("privileged/Config.jsm"), + ); + + // TODO: Select classes to import based on config (Pioneer or not) + const { studyUtils } = ChromeUtils.import( + context.extension.getURL("privileged/shieldUtils/jsm/StudyUtils.jsm"), + ); + const studyUtilsBootstrap = ChromeUtils.import( + context.extension.getURL( + "privileged/shieldUtils/jsm/StudyUtilsBootstrap.jsm", + ), + ); + // const { PioneerUtils } = ChromeUtils.import(context.extension.getURL("privileged/shieldUtils/jsm/PioneerUtils.jsm")); + // const { PioneerUtilsBootstrap } = ChromeUtils.import(context.extension.getURL("privileged/shieldUtils/jsm/PioneerUtilsBootstrap.jsm")); + const bootstrap = studyUtilsBootstrap.Bootstrap(config, studyUtils); + + /* + console.log("config", config); + console.log("studyUtils", studyUtils); + console.log("studyUtilsBootstrap", studyUtilsBootstrap); + console.log("bootstrap", bootstrap); + console.log("this.extension", this.extension); + */ + + const { extension } = this; + + // console.log("PioneerUtils", PioneerUtils); + + return { + shieldUtils: { + /** + * ensure we have configured shieldUtils and are supposed to run our feature + * @returns {Promise} + */ + async bootstrapStudy() { + await bootstrap.startup(extension); + }, + + /** + * current studyUtils configuration, including 'variation' + * @returns {Promise} + */ + async info() { + return studyUtils.info(); + }, + + /** + * + * `telemetry` + * + * - check all pings for validity as "shield-study-addon" pings + * - send a 'shield-study-addon' packet + * + * Good practice: send all Telemetry from one function for easier + * logging, debugging, validation + * + * Note: keys, values must be strings to fulfill the `shield-study-addon` + * ping-type validation. This allows `payload.data.attributes` to store + * correctly at Parquet at s.t.m.o. + * + * Bold claim: catching errors here + * + */ + async telemetry(data) { + function throwIfInvalid(obj) { + // Check: all keys and values must be strings, + for (const k in obj) { + if (typeof k !== "string") + throw new Error(`key ${k} not a string`); + if (typeof obj[k] !== "string") + throw new Error(`value ${k} ${obj[k]} not a string`); + } + return true; + } + + throwIfInvalid(data); + studyUtils.telemetry(data); + }, + + /** + * for ending a study + * @param data + * @returns {Promise} + */ + async endStudy(data) { + studyUtils.endStudy(data); + }, + }, + }; + } +}; diff --git a/src/shieldUtils/jsm/StudyUtilsBootstrap.jsm b/src/shieldUtils/jsm/StudyUtilsBootstrap.jsm new file mode 100644 index 0000000..83c4e8f --- /dev/null +++ b/src/shieldUtils/jsm/StudyUtilsBootstrap.jsm @@ -0,0 +1,141 @@ +"use strict"; + +const EXPORTED_SYMBOLS = ["Bootstrap"]; + +const { utils: Cu } = Components; +Cu.import("resource://gre/modules/Services.jsm"); + +this.Bootstrap = function(config, studyUtils) { + return { + /** + * Use console as our logger until there is a log() method in studyUtils that we can rely on + */ + log: console, + + /** + * + * @param manifest + * @param reason + * @returns {Promise} + */ + async startup(extension) { + const { manifest, startupReason } = extension; + + this.log.debug("startup", startupReason); + + const addonId = manifest.applications.gecko.id; + const addonVersion = manifest.version; + this.initStudyUtils(addonId, addonVersion); + + // choose and set variation + await this.selectVariation(); + + // Check if the user is eligible to run this study using the |isEligible| + // function when the study is initialized + if ( + startupReason === "ADDON_INSTALL" || + startupReason === "ADDON_UPGRADE" + ) { + // telemetry "enter" ONCE + studyUtils.firstSeen(); + const eligible = await config.isEligible(); + if (!eligible) { + this.log.debug("User is ineligible, ending study."); + // 1. uses config.endings.ineligible.url if any, + // 2. sends UT for "ineligible" + // 3. then uninstalls addon + await studyUtils.endStudy({ reason: "ineligible" }); + return; + } + } + + const expired = await config.hasExpired(); + if (expired) { + await studyUtils.endStudy({ reason: "expired" }); + return; + } + + /* + * Adds the study to the active list of telemetry experiments, + * and sends the "installed" telemetry ping if applicable + */ + await studyUtils.startup({ startupReason }); + + // log what the study variation and other info is. + this.log.debug(`info ${JSON.stringify(studyUtils.info())}`); + }, + + initStudyUtils(id, version) { + // validate study config + studyUtils.setup({ ...config, addon: { id, version } }); + // TODO bdanforth: patch studyUtils to setLoggingLevel as part of setup method + studyUtils.setLoggingLevel(config.log.studyUtils.level); + }, + + // choose the variation for this particular user, then set it. + async selectVariation() { + const variation = + this.getVariationFromPref(config.weightedVariations) || + (await studyUtils.deterministicVariation(config.weightedVariations)); + studyUtils.setVariation(variation); + this.log.debug(`studyUtils has config and variation.name: ${ + variation.name + }. + Ready to send telemetry`); + return variation; + }, + + // helper to let Dev or QA set the variation name + getVariationFromPref(weightedVariations) { + const name = Services.prefs.getCharPref( + config.variationOverridePreference, + "", + ); + if (name !== "") { + const variation = weightedVariations.filter(x => x.name === name)[0]; + if (!variation) { + throw new Error(`about:config => ${ + config.variationOverridePreference + } set to ${name}, + but no variation with that name exists.`); + } + return variation; + } + return name; + }, + + /** + * Shutdown needs to distinguish between USER-DISABLE and other + * times that `endStudy` is called. + * + * studyUtils._isEnding means this is a '2nd shutdown'. + */ + async shutdown(addonData, reason) { + this.log.debug("shutdown", studyUtils.REASONS[reason] || reason); + + const isUninstall = + reason === studyUtils.REASONS.ADDON_UNINSTALL || + reason === studyUtils.REASONS.ADDON_DISABLE; + if (isUninstall) { + this.log.debug("uninstall or disable"); + } + + if (isUninstall && !studyUtils._isEnding) { + // we are the first 'uninstall' requestor => must be user action. + this.log.debug("probably: user requested shutdown"); + studyUtils.endStudy({ reason: "user-disable" }); + } + + // normal shutdown, or 2nd uninstall request + }, + + uninstall(addonData, reason) { + this.log.debug("uninstall", reason); + }, + + install(addonData, reason) { + this.log.debug("install", reason); + // handle ADDON_UPGRADE (if needful) here + }, + }; +}; diff --git a/src/shieldUtils/schema.json b/src/shieldUtils/schema.json new file mode 100644 index 0000000..a14cb3b --- /dev/null +++ b/src/shieldUtils/schema.json @@ -0,0 +1,47 @@ +[ + { + "namespace": "shieldUtils", + "description": "shieldUtils", + "functions": [ + { + "name": "bootstrapStudy", + "type": "function", + "description": "TODO", + "async": true, + "parameters": [] + }, + { + "name": "info", + "type": "function", + "description": "TODO", + "async": true, + "parameters": [] + }, + { + "name": "telemetry", + "type": "function", + "description": "TODO", + "async": true, + "parameters": [ + { + "name": "data", + "type": "any" + } + ] + }, + { + "name": "endStudy", + "type": "function", + "description": "TODO", + "async": true, + "parameters": [ + { + "optional": true, + "name": "data", + "type": "any" + } + ] + } + ] + } +] From f882fe96c32c9b99c9d37aa385b3fad9c5a90746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Sat, 24 Mar 2018 16:19:52 +0200 Subject: [PATCH 08/50] Using the same eslint configuration as the template (from https://github.com/mozilla/shield-studies-addon-template/issues/61) --- .eslintrc.js | 45 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 1b71f9e..618576a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,27 +1,52 @@ +/* eslint-env node */ + "use strict"; +// All Mozilla specific rules and environments at: +// http://firefox-source-docs.mozilla.org/tools/lint/linters/eslint-plugin-mozilla.html + module.exports = { env: { - node: true, + es6: true, }, - extends: ["eslint:recommended", "plugin:mozilla/recommended"], - - plugins: ["mozilla", "json"], - + extends: [ + "eslint:recommended", + // list of rules at: https://dxr.mozilla.org/mozilla-central/source/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/recommended.js + "plugin:mozilla/recommended", + ], + overrides: [ + { + files: "src/**", + env: { + browser: true, + webextensions: true, + }, + }, + ], + parserOptions: { + ecmaVersion: 8, + sourceType: "module", + ecmaFeatures: { + jsx: false, + experimentalObjectRestSpread: true, + }, + }, + plugins: ["json", "mozilla"], + root: true, rules: { "babel/new-cap": "off", + "mozilla/no-aArgs": "warn", + "mozilla/balanced-listeners": "off", "comma-dangle": ["error", "always-multiline"], eqeqeq: "error", indent: ["warn", 2, { SwitchCase: 1 }], - "mozilla/no-aArgs": "warn", - "mozilla/balanced-listeners": 0, "no-console": "warn", - "no-debugger": "warn", - "no-shadow": ["error"], + "no-var": "error", + "no-shadow": "error", "no-unused-vars": "error", "prefer-const": "warn", + "prefer-spread": "error", semi: ["error", "always"], - "require-jsdoc": "warn", "valid-jsdoc": "warn", "max-len": ["warn", 80], }, From f718c539536f82cb7d233822f8527d5fa7b511c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Sat, 24 Mar 2018 16:20:52 +0200 Subject: [PATCH 09/50] Npm run format + linting --- src/StudyUtils.in.jsm | 12 +++++++----- src/schemas.js | 2 ++ src/shieldUtils/api.js | 21 +++++++++++++-------- webpack.config.js | 4 ++-- 4 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/StudyUtils.in.jsm b/src/StudyUtils.in.jsm index af2e835..7c7fccd 100644 --- a/src/StudyUtils.in.jsm +++ b/src/StudyUtils.in.jsm @@ -1,3 +1,5 @@ +/* eslint-env commonjs */ + "use strict"; /* @@ -62,7 +64,7 @@ const schemas = require("./schemas.js"); const Ajv = require("ajv/dist/ajv.min.js"); const ajv = new Ajv(); -var jsonschema = { +const jsonschema = { /** * Validates input data based on a specified schema * @param {Object} data - The data to be validated @@ -70,7 +72,7 @@ var jsonschema = { * @returns {boolean} - Will return true if the data is valid */ validate(data, schema) { - var valid = ajv.validate(schema, data); + const valid = ajv.validate(schema, data); return { valid, errors: ajv.errors || [] }; }, /** @@ -266,7 +268,7 @@ function chooseWeighted(weightedVariations, fraction = Math.random()) { */ jsonschema.validateOrThrow(weightedVariations, schemas.weightedVariations); - var weights = weightedVariations.map(x => x.weight || 1); + const weights = weightedVariations.map(x => x.weight || 1); const partial = cumsum(weights); const total = weights.reduce((a, b) => a + b); for (let ii = 0; ii < weightedVariations.length; ii++) { @@ -816,7 +818,7 @@ class StudyUtils { */ function createLog(name, levelWord) { Cu.import("resource://gre/modules/Log.jsm"); - var L = Log.repository.getLogger(name); + const L = Log.repository.getLogger(name); L.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter())); // should be a config / pref L.level = Log.Level[levelWord] || Log.Level.Debug; @@ -840,7 +842,7 @@ for (const r in REASONS) { } // Actually create the singleton. -var studyUtils = new StudyUtils(); +const studyUtils = new StudyUtils(); // to make this work with webpack! this.EXPORTED_SYMBOLS = EXPORTED_SYMBOLS; diff --git a/src/schemas.js b/src/schemas.js index 17dd1b6..8ea22b3 100644 --- a/src/schemas.js +++ b/src/schemas.js @@ -1,3 +1,5 @@ +/* eslint-env commonjs */ + module.exports = { "shield-study": require("shield-study-schemas/schemas-client/shield-study.schema.json"), // eslint-disable-line max-len "shield-study-addon": require("shield-study-schemas/schemas-client/shield-study-addon.schema.json"), // eslint-disable-line max-len diff --git a/src/shieldUtils/api.js b/src/shieldUtils/api.js index 6794cd1..b2aa3a2 100644 --- a/src/shieldUtils/api.js +++ b/src/shieldUtils/api.js @@ -18,9 +18,10 @@ this.shieldUtils = class extends ExtensionAPI { /** * Extension Shutdown - * APIs that allocate any resources (e.g., adding elements to the browser’s user interface, - * setting up internal event listeners, etc.) must free these resources when the extension - * for which they are allocated is shut down. + * APIs that allocate any resources (e.g., adding elements to the browser’s + * user interface, setting up internal event listeners, etc.) must free + * these resources when the extension for which they are allocated is + * shut down. */ onShutdown(shutdownReason) { console.log("onShutdown", shutdownReason); @@ -41,8 +42,10 @@ this.shieldUtils = class extends ExtensionAPI { "privileged/shieldUtils/jsm/StudyUtilsBootstrap.jsm", ), ); - // const { PioneerUtils } = ChromeUtils.import(context.extension.getURL("privileged/shieldUtils/jsm/PioneerUtils.jsm")); - // const { PioneerUtilsBootstrap } = ChromeUtils.import(context.extension.getURL("privileged/shieldUtils/jsm/PioneerUtilsBootstrap.jsm")); + // const { PioneerUtils } = ChromeUtils.import(context.extension + // .getURL("privileged/shieldUtils/jsm/PioneerUtils.jsm")); + // const { PioneerUtilsBootstrap } = ChromeUtils.import(context.extension + // .getURL("privileged/shieldUtils/jsm/PioneerUtilsBootstrap.jsm")); const bootstrap = studyUtilsBootstrap.Bootstrap(config, studyUtils); /* @@ -60,7 +63,8 @@ this.shieldUtils = class extends ExtensionAPI { return { shieldUtils: { /** - * ensure we have configured shieldUtils and are supposed to run our feature + * ensure we have configured shieldUtils + * and are supposed to run our feature * @returns {Promise} */ async bootstrapStudy() { @@ -85,8 +89,9 @@ this.shieldUtils = class extends ExtensionAPI { * Good practice: send all Telemetry from one function for easier * logging, debugging, validation * - * Note: keys, values must be strings to fulfill the `shield-study-addon` - * ping-type validation. This allows `payload.data.attributes` to store + * Note: keys, values must be strings to fulfill the + * `shield-study-addon` ping-type validation. + * This allows `payload.data.attributes` to store * correctly at Parquet at s.t.m.o. * * Bold claim: catching errors here diff --git a/webpack.config.js b/webpack.config.js index ccc134a..2ac037c 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,5 +1,5 @@ -var path = require("path"); -const webpack = require("webpack"); //to access built-in plugins +/* eslint-env node */ +const path = require("path"); module.exports = { entry: "./src/StudyUtils.in.jsm", From efae1b0796909907d20fcebf95900d887abb2dd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Sat, 24 Mar 2018 16:32:48 +0200 Subject: [PATCH 10/50] Moved make_xpi script to test-addon folder (since it it only used to make an xpi for the test-addon) --- package.json | 2 +- {bin => test-addon/bin}/make_xpi.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename {bin => test-addon/bin}/make_xpi.sh (96%) diff --git a/package.json b/package.json index 6f8b9d4..0735b99 100644 --- a/package.json +++ b/package.json @@ -44,8 +44,8 @@ "url": "git+https://github.com/mozilla/shield-studies-addon-utils.git" }, "scripts": { - "build-test-addon-xpi": "./bin/make_xpi.sh", "dist": "webpack", + "build-test-addon-xpi": "./test-addon/bin/make_xpi.sh", "docformat": "doctoc --title '**Contents**' docs/*.md && prettier '**/*.md' --write", "eslint": "eslint src --ext jsm --ext js --ext json", "eslint-fix": "npm run eslint -- --fix", diff --git a/bin/make_xpi.sh b/test-addon/bin/make_xpi.sh similarity index 96% rename from bin/make_xpi.sh rename to test-addon/bin/make_xpi.sh index 40a5e15..db322de 100755 --- a/bin/make_xpi.sh +++ b/test-addon/bin/make_xpi.sh @@ -3,7 +3,7 @@ cp dist/StudyUtils.jsm test-addon/ set -eu -BASE_DIR="$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")""/test-addon" +BASE_DIR="$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")""" TMP_DIR=$(mktemp -d) DEST="${TMP_DIR}/test-addon" mkdir -p $DEST From 7c0a77cf5acab4480723d1b5333e474389979f79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Sat, 24 Mar 2018 16:35:04 +0200 Subject: [PATCH 11/50] Inactivated functional tests for now (will be restored after refactoring is complete) --- test/shield_utils_test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/shield_utils_test.js b/test/shield_utils_test.js index 41ea5b4..3639752 100644 --- a/test/shield_utils_test.js +++ b/test/shield_utils_test.js @@ -8,6 +8,7 @@ const Context = firefox.Context; // TODO create new profile per test? // then we can test with a clean profile every time +/* describe("Shield Study Utils Functional Tests", function() { // This gives Firefox time to start, and us a bit longer during some of the tests. this.timeout(15000); @@ -251,3 +252,4 @@ describe("Shield Study Utils Functional Tests", function() { }); }); }); +*/ From 0a473eed8912a3125a92d4897fb7d933fa6581f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Sat, 24 Mar 2018 16:35:53 +0200 Subject: [PATCH 12/50] Not: Using add-on in package description --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0735b99..28ff409 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "shield-studies-addon-utils", - "description": "Utilities for building Shield-Study Mozilla Firefox addons.", + "description": "Utilities for building Shield-Study Mozilla Firefox add-ons.", "version": "5.0.0-alpha1", "author": "Mozilla", "bugs": { From 1ac3e3f2675e097a524cb3bda739e583b03284cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Sat, 24 Mar 2018 16:57:57 +0200 Subject: [PATCH 13/50] Cleaned up npm run build / lint commands --- package.json | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 28ff409..f53e6e8 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "fx-runner": "^1.0.7", "geckodriver": "^1.8.0", "mocha": "^3.4.2", + "npm-run-all": "^4.1.2", "prettier": "^1.11.0", "selenium-webdriver": "^3.4.0", "webpack": "^2.6.1" @@ -44,16 +45,20 @@ "url": "git+https://github.com/mozilla/shield-studies-addon-utils.git" }, "scripts": { - "dist": "webpack", + "build": "webpack", "build-test-addon-xpi": "./test-addon/bin/make_xpi.sh", "docformat": "doctoc --title '**Contents**' docs/*.md && prettier '**/*.md' --write", "eslint": "eslint src --ext jsm --ext js --ext json", "eslint-fix": "npm run eslint -- --fix", "format": "prettier '**/*.{css,js,jsm,json,md}' --trailing-comma=all --ignore-path=.eslintignore --write", + "lint": "npm-run-all lint:*", + "lint:eslint": "npm run eslint", + "lint:fixpack": "fixpack", "postformat": "npm run eslint-fix && fixpack", - "predist": "npm run eslint", - "prepack": "fixpack && npm run dist", - "pretest": "npm run dist && npm run build-test-addon-xpi", - "test": "export FIREFOX_BINARY=firefox && XPI_NAME=test-addon/test-addon.xpi mocha test" + "prebuild": "npm run lint", + "prepack": "fixpack && npm run build", + "prepare": "npm run test", + "pretest": "npm run build && npm run build-test-addon-xpi", + "test": "export FIREFOX_BINARY=firefox && XPI_NAME=test-addon/test-addon.xpi mocha test --bail" } } From f33f31201ad79fee444ed7969cd075563a122573 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Sat, 24 Mar 2018 17:19:35 +0200 Subject: [PATCH 14/50] Build produces a web extensions api js file instead of ShieldUtils.jsm + readme updated to reflect this + files moved to their proper location --- README.md | 19 ++++++++++++++++--- src/shieldUtils/schema.json => schema.json | 0 .../jsm => }/StudyUtilsBootstrap.jsm | 0 src/{shieldUtils/api.js => index.js} | 0 webpack.config.js | 4 ++-- 5 files changed, 18 insertions(+), 5 deletions(-) rename src/shieldUtils/schema.json => schema.json (100%) rename src/{shieldUtils/jsm => }/StudyUtilsBootstrap.jsm (100%) rename src/{shieldUtils/api.js => index.js} (100%) diff --git a/README.md b/README.md index de6c45a..681e2fb 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Build Status](https://travis-ci.org/mozilla/shield-studies-addon-utils.svg?branch=master)](https://travis-ci.org/mozilla/shield-studies-addon-utils) -A Firefox JavaScript module to be bundled with shield study add-ons (as `StudyUtils.jsm`). Provides these capabilities: +A Firefox WebExtension Experiments API to be bundled with shield study add-ons (`browser.shieldUtils.*`). Provides these capabilities: 1. **Suggest variation for a client** (Deterministically! i.e. based on a hash of non-PII user info, they will always get assigned to the same branch every time the study launches) 2. **Report study lifecycle data** using Telemetry @@ -29,13 +29,26 @@ Allows add-on developers to build [Shield Study](https://wiki.mozilla.org/Firefo Check out [mozilla/shield-studies-addon-template/](https://github.com/mozilla/shield-studies-addon-template/) to get started with an example study where shield-studies-addon-utils is already installed and configured. -## Installing the `StudyUtils.jsm` in your add-on +## Installing the utils in your add-on ``` npm install --save-dev shield-studies-addon-utils ``` -Copy `dist/StudyUtils.jsm` to your `addon` source directory, where it will be zipped up. +Copy `dist/api.js` and `schema.json` to your add-on's source directory under `privileged/shieldUtils`, then add-the following to your add-on's manifest.json: + +``` + "experiment_apis": { + "shieldUtils": { + "schema": "./privileged/shieldUtils/schema.json", + "parent": { + "scopes": ["addon_parent"], + "script": "./privileged/shieldUtils/api.js", + "paths": [["shieldUtils"]] + } + } + }, +``` ## Engineering and Process diff --git a/src/shieldUtils/schema.json b/schema.json similarity index 100% rename from src/shieldUtils/schema.json rename to schema.json diff --git a/src/shieldUtils/jsm/StudyUtilsBootstrap.jsm b/src/StudyUtilsBootstrap.jsm similarity index 100% rename from src/shieldUtils/jsm/StudyUtilsBootstrap.jsm rename to src/StudyUtilsBootstrap.jsm diff --git a/src/shieldUtils/api.js b/src/index.js similarity index 100% rename from src/shieldUtils/api.js rename to src/index.js diff --git a/webpack.config.js b/webpack.config.js index 2ac037c..2356f9b 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -2,10 +2,10 @@ const path = require("path"); module.exports = { - entry: "./src/StudyUtils.in.jsm", + entry: "./src/index.js", output: { path: path.resolve(__dirname, "dist"), - filename: "StudyUtils.jsm", + filename: "api.js", libraryTarget: "this", // Possible value - amd, commonjs, commonjs2, commonjs-module, this, var }, }; From efdbf5c3e9a14f4bfe957fc5bca04f5c1c228427 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Sat, 24 Mar 2018 17:51:07 +0200 Subject: [PATCH 15/50] Package.json nits --- package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index f53e6e8..67ef93b 100644 --- a/package.json +++ b/package.json @@ -39,10 +39,10 @@ "shield-study" ], "license": "MPL-2.0", - "main": "lib/index.js", + "main": "src/index.js", "repository": { "type": "git", - "url": "git+https://github.com/mozilla/shield-studies-addon-utils.git" + "url": "git://github.com/mozilla/shield-studies-addon-utils.git" }, "scripts": { "build": "webpack", @@ -57,7 +57,6 @@ "postformat": "npm run eslint-fix && fixpack", "prebuild": "npm run lint", "prepack": "fixpack && npm run build", - "prepare": "npm run test", "pretest": "npm run build && npm run build-test-addon-xpi", "test": "export FIREFOX_BINARY=firefox && XPI_NAME=test-addon/test-addon.xpi mocha test --bail" } From d72e3ce6567c7b4ab8d5a15d335dcf7cf4289078 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Sat, 24 Mar 2018 18:26:07 +0200 Subject: [PATCH 16/50] Smaller test clean-up --- package.json | 2 +- test/{ => functional}/shield_utils_test.js | 0 test/{ => functional}/utils.js | 16 +++++++++++----- 3 files changed, 12 insertions(+), 6 deletions(-) rename test/{ => functional}/shield_utils_test.js (100%) rename test/{ => functional}/utils.js (91%) diff --git a/package.json b/package.json index 67ef93b..f56d82b 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,6 @@ "prebuild": "npm run lint", "prepack": "fixpack && npm run build", "pretest": "npm run build && npm run build-test-addon-xpi", - "test": "export FIREFOX_BINARY=firefox && XPI_NAME=test-addon/test-addon.xpi mocha test --bail" + "test": "export FIREFOX_BINARY=${FIREFOX_BINARY:-nightly} && XPI_NAME=test-addon/test-addon.xpi mocha test/functional/ --bail" } } diff --git a/test/shield_utils_test.js b/test/functional/shield_utils_test.js similarity index 100% rename from test/shield_utils_test.js rename to test/functional/shield_utils_test.js diff --git a/test/utils.js b/test/functional/utils.js similarity index 91% rename from test/utils.js rename to test/functional/utils.js index 8722d83..a63d9a4 100644 --- a/test/utils.js +++ b/test/functional/utils.js @@ -1,14 +1,15 @@ /* eslint-env node */ +/* eslint no-console:off */ + // The geckodriver package downloads and installs geckodriver for us. // We use it by requiring it. require("geckodriver"); -// const assert = require("assert"); const cmd = require("selenium-webdriver/lib/command"); const firefox = require("selenium-webdriver/firefox"); -const Fs = require("fs-extra"); +const webdriver = require("selenium-webdriver"); const FxRunnerUtils = require("fx-runner/lib/utils"); +const Fs = require("fs-extra"); const path = require("path"); -const webdriver = require("selenium-webdriver"); // const By = webdriver.By; const Context = firefox.Context; @@ -33,7 +34,8 @@ const FIREFOX_PREFERENCES = { // useful if we need to test on a specific version of Firefox async function promiseActualBinary(binary) { try { - const normalizedBinary = await FxRunnerUtils.normalizeBinary(binary); + let normalizedBinary = await FxRunnerUtils.normalizeBinary(binary); + normalizedBinary = path.resolve(normalizedBinary); await Fs.stat(normalizedBinary); return normalizedBinary; } catch (ex) { @@ -44,7 +46,10 @@ async function promiseActualBinary(binary) { } } -module.exports.promiseSetupDriver = async () => { +/** + * Uses process.env.FIREFOX_BINARY + */ +module.exports.promiseSetupDriver = async() => { const profile = new firefox.Profile(); Object.keys(FIREFOX_PREFERENCES).forEach(key => { @@ -63,6 +68,7 @@ module.exports.promiseSetupDriver = async () => { ); await options.setBinary(new firefox.Binary(binaryLocation)); const driver = await builder.build(); + // Firefox will be started up by now driver.setContext(Context.CHROME); return driver; }; From 96188af21fe1946ea5d85652f805b9dd1f6b3b3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Sat, 24 Mar 2018 19:36:02 +0200 Subject: [PATCH 17/50] Legacy test add-on removed, webextension-based test add-on inserted --- package.json | 10 +- test-addon/.gitignore | 1 - test-addon/README.md | 3 - test-addon/bin/make_xpi.sh | 26 -- test-addon/bootstrap.js | 15 - test-addon/build-includes.txt | 5 - test-addon/chrome.manifest | 1 - test-addon/install.rdf | 22 -- test-addon/src/background.js | 44 +++ test-addon/src/icons/LICENSE | 1 + test-addon/src/icons/shield-icon.svg | 281 ++++++++++++++++++ test-addon/src/manifest.json | 33 ++ test-addon/src/privileged/.gitignore | 1 + test-addon/src/privileged/Config.jsm | 123 ++++++++ .../test}/functional/shield_utils_test.js | 0 {test => test-addon/test}/functional/utils.js | 22 +- test-addon/utils.jsm | 46 --- test-addon/web-ext-config.js | 19 ++ 18 files changed, 527 insertions(+), 126 deletions(-) delete mode 100644 test-addon/.gitignore delete mode 100644 test-addon/README.md delete mode 100755 test-addon/bin/make_xpi.sh delete mode 100644 test-addon/bootstrap.js delete mode 100644 test-addon/build-includes.txt delete mode 100644 test-addon/chrome.manifest delete mode 100644 test-addon/install.rdf create mode 100644 test-addon/src/background.js create mode 100644 test-addon/src/icons/LICENSE create mode 100644 test-addon/src/icons/shield-icon.svg create mode 100644 test-addon/src/manifest.json create mode 100644 test-addon/src/privileged/.gitignore create mode 100644 test-addon/src/privileged/Config.jsm rename {test => test-addon/test}/functional/shield_utils_test.js (100%) rename {test => test-addon/test}/functional/utils.js (83%) delete mode 100644 test-addon/utils.jsm create mode 100644 test-addon/web-ext-config.js diff --git a/package.json b/package.json index f56d82b..0e9c795 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "npm-run-all": "^4.1.2", "prettier": "^1.11.0", "selenium-webdriver": "^3.4.0", + "web-ext": "^2.5.0", "webpack": "^2.6.1" }, "files": [ @@ -46,7 +47,6 @@ }, "scripts": { "build": "webpack", - "build-test-addon-xpi": "./test-addon/bin/make_xpi.sh", "docformat": "doctoc --title '**Contents**' docs/*.md && prettier '**/*.md' --write", "eslint": "eslint src --ext jsm --ext js --ext json", "eslint-fix": "npm run eslint -- --fix", @@ -57,7 +57,11 @@ "postformat": "npm run eslint-fix && fixpack", "prebuild": "npm run lint", "prepack": "fixpack && npm run build", - "pretest": "npm run build && npm run build-test-addon-xpi", - "test": "export FIREFOX_BINARY=${FIREFOX_BINARY:-nightly} && XPI_NAME=test-addon/test-addon.xpi mocha test/functional/ --bail" + "pretest": "npm run build && npm run test-addon:bundle-utils && npm run test-addon:build", + "pretest-addon:run": "npm run pretest", + "test": "FIREFOX_BINARY=${FIREFOX_BINARY:-firefox} XPI_NAME=test-addon/dist/shield_utils_test_add-on-1.0.0.zip mocha test-addon/test/functional/ --bail", + "test-addon:build": "cd test-addon && web-ext build", + "test-addon:bundle-utils": "mkdir -p test-addon/src/privileged/shieldUtils/jsm && cp src/StudyUtilsBootstrap.jsm test-addon/src/privileged/shieldUtils/jsm/StudyUtilsBootstrap.jsm && cp dist/api.js test-addon/src/privileged/shieldUtils/api.js && cp schema.json test-addon/src/privileged/shieldUtils/schema.json", + "test-addon:run": "cd test-addon && web-ext run --no-reload" } } diff --git a/test-addon/.gitignore b/test-addon/.gitignore deleted file mode 100644 index 5561a3d..0000000 --- a/test-addon/.gitignore +++ /dev/null @@ -1 +0,0 @@ -StudyUtils.jsm diff --git a/test-addon/README.md b/test-addon/README.md deleted file mode 100644 index 40af997..0000000 --- a/test-addon/README.md +++ /dev/null @@ -1,3 +0,0 @@ -This is an example add-on that is used to test the shield-study-utils library. - -We include the library by obtaining a copy of dist/StudyUtils.jsm (created by `npm run dist` = `webpack`). diff --git a/test-addon/bin/make_xpi.sh b/test-addon/bin/make_xpi.sh deleted file mode 100755 index db322de..0000000 --- a/test-addon/bin/make_xpi.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash -cp dist/StudyUtils.jsm test-addon/ - -set -eu - -BASE_DIR="$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")""" -TMP_DIR=$(mktemp -d) -DEST="${TMP_DIR}/test-addon" -mkdir -p $DEST - -# deletes the temp directory -function cleanup { - rm -rf "$TMP_DIR" -} -trap cleanup EXIT - -while read -r LINE || [[ -n "${LINE}" ]]; do - mkdir -p "$(dirname "${DEST}/${LINE}")" - cp -r "${BASE_DIR}/${LINE}" "$(dirname "${DEST}/${LINE}")" -done < "${BASE_DIR}/build-includes.txt" - -pushd $DEST -zip -r test-addon.xpi * -mv test-addon.xpi $BASE_DIR -echo "created at: ${BASE_DIR}/test-addon.xpi" -popd diff --git a/test-addon/bootstrap.js b/test-addon/bootstrap.js deleted file mode 100644 index fb3845d..0000000 --- a/test-addon/bootstrap.js +++ /dev/null @@ -1,15 +0,0 @@ -const { classes: Cc, interfaces: Ci, utils: Cu } = Components; -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter( - this, - "studyUtils", - "resource://test-addon/StudyUtils.jsm", -); - -this.install = function(data, reason) {}; - -this.startup = async function(data, reason) {}; - -this.shutdown = function(data, reason) {}; - -this.uninstall = function(data, reason) {}; diff --git a/test-addon/build-includes.txt b/test-addon/build-includes.txt deleted file mode 100644 index 667c77d..0000000 --- a/test-addon/build-includes.txt +++ /dev/null @@ -1,5 +0,0 @@ -bootstrap.js -chrome.manifest -install.rdf -StudyUtils.jsm -utils.jsm diff --git a/test-addon/chrome.manifest b/test-addon/chrome.manifest deleted file mode 100644 index 9721aaa..0000000 --- a/test-addon/chrome.manifest +++ /dev/null @@ -1 +0,0 @@ -resource test-addon . diff --git a/test-addon/install.rdf b/test-addon/install.rdf deleted file mode 100644 index 41781f8..0000000 --- a/test-addon/install.rdf +++ /dev/null @@ -1,22 +0,0 @@ - - - - - shield-study-utils-test@mozilla.org - 2 - true - false - 0.1.0 - Test Add-on for shield-study-utils - Add-on whose sole purpose of existence is to test the shield-study-utils library. - true - - - - {ec8030f7-c20a-464f-9b0e-13a3a9e97384} - 54 - * - - - - diff --git a/test-addon/src/background.js b/test-addon/src/background.js new file mode 100644 index 0000000..6277b24 --- /dev/null +++ b/test-addon/src/background.js @@ -0,0 +1,44 @@ +/* eslint no-console:off */ + +"use strict"; + +/** + * CONFIGURE and INSTRUMENT the BrowserAction button for a specific variation + * + * 1. Request 'info' from the shieldUtils + * 2. We only care about the `variation` key. + * 3. initialize the feature, using our specific variation + */ +async function runOnce() { + // ensure we have configured shieldUtils and are supposed to run our feature + await browser.shieldUtils.bootstrapStudy(); + // get study variation + const { variation } = await browser.shieldUtils.info(); +} + +/** + * Fired when a profile that has this extension installed first starts up. + * This event is not fired when a private browsing/incognito profile is started. + */ +function handleStartup() { + console.log("handleStartup", arguments); +} + +browser.runtime.onStartup.addListener(handleStartup); + +/** + * Fired when the extension is first installed, when the extension is updated + * to a new version, and when the browser is updated to a new version. + * @param details + */ +function handleInstalled(details) { + console.log("handleInstalled", details.reason, details); +} + +browser.runtime.onInstalled.addListener(handleInstalled); + +// todo: on shutdown +// Run shutdown-related non-priviliged code + +// actually start +runOnce(); diff --git a/test-addon/src/icons/LICENSE b/test-addon/src/icons/LICENSE new file mode 100644 index 0000000..fcd8e26 --- /dev/null +++ b/test-addon/src/icons/LICENSE @@ -0,0 +1 @@ +All icons are public domain and came from https://openclipart.org/. diff --git a/test-addon/src/icons/shield-icon.svg b/test-addon/src/icons/shield-icon.svg new file mode 100644 index 0000000..3ad3e5f --- /dev/null +++ b/test-addon/src/icons/shield-icon.svg @@ -0,0 +1,281 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + Openclipart + + + Shield Icon + 2010-09-19T03:15:30 + Here is an icon depicting a shield bearing a star. Use it to depict security, strength etc. Easily modifiable to incorporate a different emblem such as a sword. Enjoy! + https://openclipart.org/detail/85567/shield-icon-by-marricklip14 + + + marricklip14 + + + + + Emblem + Icon Star + Security + Shield + Strength + + + + + + + + + + + diff --git a/test-addon/src/manifest.json b/test-addon/src/manifest.json new file mode 100644 index 0000000..3bf3d53 --- /dev/null +++ b/test-addon/src/manifest.json @@ -0,0 +1,33 @@ +{ + "name": "Shield Utils Test Add-on", + "description": "Used for functional testing of shield-studies-addon-utils", + "version": "1.0.0", + "manifest_version": 2, + "applications": { + "gecko": { + "id": "shield-utils-test-addon@shield.mozilla.org", + "strict_min_version": "59.0" + } + }, + "experiment_apis": { + "shieldUtils": { + "schema": "./privileged/shieldUtils/schema.json", + "parent": { + "scopes": ["addon_parent"], + "script": "./privileged/shieldUtils/api.js", + "paths": [["shieldUtils"]] + } + } + }, + "background": { + "scripts": ["background.js"] + }, + "icons": { + "48": "icons/shield-icon.svg" + }, + "browser_action": { + "browser_style": true, + "default_icon": "icons/shield-icon.svg", + "default_title": "Test" + } +} diff --git a/test-addon/src/privileged/.gitignore b/test-addon/src/privileged/.gitignore new file mode 100644 index 0000000..be98b2c --- /dev/null +++ b/test-addon/src/privileged/.gitignore @@ -0,0 +1 @@ +shieldUtils/ diff --git a/test-addon/src/privileged/Config.jsm b/test-addon/src/privileged/Config.jsm new file mode 100644 index 0000000..4403291 --- /dev/null +++ b/test-addon/src/privileged/Config.jsm @@ -0,0 +1,123 @@ +"use strict"; + +/* to use: + +- Recall this file has chrome privileges +- Cu.import in this file will work for any 'general firefox things' (Services,etc) + but NOT for addon-specific libs +*/ + +/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "(config|EXPORTED_SYMBOLS)" }]*/ +const EXPORTED_SYMBOLS = ["config"]; + +// Anything else than "var" here will result in config being undefined when included in other js modules +// prettier-ignore +var config = { // eslint-disable-line no-var + // required STUDY key + study: { + /** Required for studyUtils.setup(): + * + * - studyName + * - endings: + * - map of endingName: configuration + * - telemetry + * - boolean send + * - boolean removeTestingFlag + * + * All other keys are optional. + */ + + // will be used activeExperiments tagging + studyName: "shield-utils-test", + + /** **endings** + * - keys indicate the 'endStudy' even that opens these. + * - urls should be static (data) or external, because they have to + * survive uninstall + * - If there is no key for an endStudy reason, no url will open. + * - usually surveys, orientations, explanations + */ + endings: { + /** standard endings */ + "user-disable": { + baseUrl: "http://www.example.com/?reason=user-disable", + }, + ineligible: { + baseUrl: "http://www.example.com/?reason=ineligible", + }, + expired: { + baseUrl: "http://www.example.com/?reason=expired", + }, + /** User defined endings */ + "used-often": { + baseUrl: "http://www.example.com/?reason=used-often", + study_state: "ended-positive", // neutral is default + }, + "a-non-url-opening-ending": { + study_state: "ended-neutral", + baseUrl: null, + }, + "introduction-leave-study": { + study_state: "ended-negative", + baseUrl: "http://www.example.com/?reason=introduction-leave-study", + }, + }, + telemetry: { + send: true, // assumed false. Actually send pings? + removeTestingFlag: false, // Marks pings to be discarded, set true for to have the pings processed in the pipeline + // TODO "onInvalid": "throw" // invalid packet for schema? throw||log + }, + }, + + // required LOG key + log: { + // Fatal: 70, Error: 60, Warn: 50, Info: 40, Config: 30, Debug: 20, Trace: 10, All: -1, + bootstrap: { + // Console.jsm uses "debug", whereas Log.jsm uses "Debug", *sigh* + level: "debug", + }, + studyUtils: { + level: "Trace", + }, + }, + + // Will run only during first install attempt + async isEligible() { + // get whatever prefs, addons, telemetry, anything! + // Cu.import can see 'firefox things', but not package things. + return true; + }, + + // Expiration checks should be implemented in a very reliable way by + // the add-on since Normandy does not handle study expiration in a reliable manner + async hasExpired() { + return false; + }, + + /* Button study branches and sample weights + - test kittens vs. puppies if we can only have one. + - downweight lizards. Lizards is a 'poison' branch, meant to + help control for novelty effect + */ + weightedVariations: [ + { + name: "kittens", + weight: 1.5, + }, + { + name: "puppers", + weight: 1.5, + }, + { + name: "lizard", + weight: 1, + }, // we want more puppers in our sample + ], + + /** + * Change this preference to be able to test the add-on behavior in different study + * variations/branches (or leave it unset to use the automatic assigning + * of a study variation/branch from weightedVariations in Config.jsm) + */ + variationOverridePreference: "extensions.button_icon_preference.variation", +}; diff --git a/test/functional/shield_utils_test.js b/test-addon/test/functional/shield_utils_test.js similarity index 100% rename from test/functional/shield_utils_test.js rename to test-addon/test/functional/shield_utils_test.js diff --git a/test/functional/utils.js b/test-addon/test/functional/utils.js similarity index 83% rename from test/functional/utils.js rename to test-addon/test/functional/utils.js index a63d9a4..08eac29 100644 --- a/test/functional/utils.js +++ b/test-addon/test/functional/utils.js @@ -23,12 +23,25 @@ const FIREFOX_PREFERENCES = { "browser.tabs.remote.autostart": true, "browser.tabs.remote.autostart.1": true, "browser.tabs.remote.autostart.2": true, - // These are good to have set up if you're debugging tests with the browser - // toolbox. + + // Improve debugging using `browser toolbox`. "devtools.chrome.enabled": true, "devtools.debugger.remote-enabled": true, "devtools.debugger.prompt-connection": false, + + // Removing warning for `about:config` "general.warnOnAboutConfig": false, + + // Force variation for testing + "extensions.button_icon_preference.variation": "puppers", + + /** WARNING: gecko webdriver sets many additional prefs at: + * https://dxr.mozilla.org/mozilla-central/source/testing/geckodriver/src/prefs.rs + * + * In, particular, this DISABLES actual telemetry uploading + * ("toolkit.telemetry.server", Pref::new("https://%(server)s/dummy/telemetry/")), + * + */ }; // useful if we need to test on a specific version of Firefox @@ -92,10 +105,11 @@ module.exports.installAddon = async driver => { path: fileLocation, temporary: true, }); - return executor.execute(installCmd); + await executor.execute(installCmd); + console.log(`Add-on at ${fileLocation} installed`); }; -module.exports.uninstallAddon = async (driver, id) => { +module.exports.uninstallAddon = async(driver, id) => { const executor = driver.getExecutor(); executor.defineCommand( "uninstallAddon", diff --git a/test-addon/utils.jsm b/test-addon/utils.jsm deleted file mode 100644 index 1468391..0000000 --- a/test-addon/utils.jsm +++ /dev/null @@ -1,46 +0,0 @@ -/* eslint no-unused-vars: "off" */ - -const { studyUtils } = Components.utils.import( - "resource://test-addon/StudyUtils.jsm", - {}, -); -Components.utils.import("resource://gre/modules/TelemetryArchive.jsm"); -Components.utils.import("resource://gre/modules/Console.jsm"); - -var EXPORTED_SYMBOLS = ["fakeSetup", "getMostRecentPingsByType"]; - -function fakeSetup() { - studyUtils.setup({ - study: { - studyName: "shield-utils-test", - endings: { - expired: { - baseUrl: "http://www.example.com/?reason=expired", - }, - }, - telemetry: { send: true, removeTestingFlag: false }, - }, - log: { - // Fatal: 70, Error: 60, Warn: 50, Info: 40, Config: 30, Debug: 20, Trace: 10, All: -1, - studyUtils: { - level: "Warn", - }, - }, - addon: { id: "1", version: "1" }, - }); - studyUtils.setVariation({ name: "puppers", weight: "2" }); -} - -// Returns array of pings of type `type` in sorted order by timestamp -// first element is most recent ping -async function getMostRecentPingsByType(type) { - const pings = await TelemetryArchive.promiseArchivedPingList(); - - const filteredPings = pings.filter(p => p.type === type); - filteredPings.sort((a, b) => b.timestampCreated - a.timestampCreated); - - const pingData = filteredPings.map(ping => - TelemetryArchive.promiseArchivedPingById(ping.id), - ); - return Promise.all(pingData); -} diff --git a/test-addon/web-ext-config.js b/test-addon/web-ext-config.js new file mode 100644 index 0000000..c6511df --- /dev/null +++ b/test-addon/web-ext-config.js @@ -0,0 +1,19 @@ +/* eslint-env node */ + +const defaultConfig = { + // Global options: + sourceDir: "./src/", + artifactsDir: "./dist/", + ignoreFiles: [".DS_Store"], + // Command options: + build: { + overwriteDest: true, + }, + run: { + firefox: "nightly", + browserConsole: true, + startUrl: ["about:debugging"], + }, +}; + +module.exports = defaultConfig; From 6ca70e2943239dde1146010b57e9db9bec8d48bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Sat, 24 Mar 2018 19:43:49 +0200 Subject: [PATCH 18/50] Importing study utils and study utils bootstrap jsm modules via webpack require instead of ChromeUtils.import(context.extension.getURL(...)) --- src/StudyUtils.in.jsm | 4 ++++ src/StudyUtilsBootstrap.jsm | 3 ++- src/index.js | 18 ++++++------------ 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/StudyUtils.in.jsm b/src/StudyUtils.in.jsm index 7c7fccd..21ee4a5 100644 --- a/src/StudyUtils.in.jsm +++ b/src/StudyUtils.in.jsm @@ -27,6 +27,10 @@ Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.importGlobalProperties(["URL", "crypto", "URLSearchParams"]); +// Steal globals only available in JSM scope (and not Sandbox one) +// Example globals: ChromeUtils, HeapSnapshot, XMLHttpRequest, atob, btoa, TextEncoder, TextDecoder +const { TextEncoder } = Cu.getGlobalForObject(Services); + let log; // telemetry utils diff --git a/src/StudyUtilsBootstrap.jsm b/src/StudyUtilsBootstrap.jsm index 83c4e8f..0ff6f4a 100644 --- a/src/StudyUtilsBootstrap.jsm +++ b/src/StudyUtilsBootstrap.jsm @@ -67,7 +67,8 @@ this.Bootstrap = function(config, studyUtils) { initStudyUtils(id, version) { // validate study config - studyUtils.setup({ ...config, addon: { id, version } }); + config.addon = { id, version }; + studyUtils.setup(config); // TODO bdanforth: patch studyUtils to setLoggingLevel as part of setup method studyUtils.setLoggingLevel(config.log.studyUtils.level); }, diff --git a/src/index.js b/src/index.js index b2aa3a2..d23ea92 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,5 @@ +/* eslint-env commonjs */ + /* global ExtensionAPI */ this.shieldUtils = class extends ExtensionAPI { @@ -34,18 +36,10 @@ this.shieldUtils = class extends ExtensionAPI { ); // TODO: Select classes to import based on config (Pioneer or not) - const { studyUtils } = ChromeUtils.import( - context.extension.getURL("privileged/shieldUtils/jsm/StudyUtils.jsm"), - ); - const studyUtilsBootstrap = ChromeUtils.import( - context.extension.getURL( - "privileged/shieldUtils/jsm/StudyUtilsBootstrap.jsm", - ), - ); - // const { PioneerUtils } = ChromeUtils.import(context.extension - // .getURL("privileged/shieldUtils/jsm/PioneerUtils.jsm")); - // const { PioneerUtilsBootstrap } = ChromeUtils.import(context.extension - // .getURL("privileged/shieldUtils/jsm/PioneerUtilsBootstrap.jsm")); + const { studyUtils } = require("./StudyUtils.in.jsm"); + const studyUtilsBootstrap = require("./StudyUtilsBootstrap.jsm"); + // const { PioneerUtils } = require("pioneer-utils/PioneerUtils.jsm"); + // const pioneerUtilsBootstrap = require("./PioneerUtilsBootstrap.jsm"); const bootstrap = studyUtilsBootstrap.Bootstrap(config, studyUtils); /* From 7e3ebab79c410df14dab37802e1a4b705fb240cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Sat, 24 Mar 2018 19:49:05 +0200 Subject: [PATCH 19/50] Minor clean-up in src/index.js --- src/index.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/index.js b/src/index.js index d23ea92..258b9ed 100644 --- a/src/index.js +++ b/src/index.js @@ -35,25 +35,14 @@ this.shieldUtils = class extends ExtensionAPI { context.extension.getURL("privileged/Config.jsm"), ); - // TODO: Select classes to import based on config (Pioneer or not) const { studyUtils } = require("./StudyUtils.in.jsm"); const studyUtilsBootstrap = require("./StudyUtilsBootstrap.jsm"); // const { PioneerUtils } = require("pioneer-utils/PioneerUtils.jsm"); // const pioneerUtilsBootstrap = require("./PioneerUtilsBootstrap.jsm"); const bootstrap = studyUtilsBootstrap.Bootstrap(config, studyUtils); - /* - console.log("config", config); - console.log("studyUtils", studyUtils); - console.log("studyUtilsBootstrap", studyUtilsBootstrap); - console.log("bootstrap", bootstrap); - console.log("this.extension", this.extension); - */ - const { extension } = this; - // console.log("PioneerUtils", PioneerUtils); - return { shieldUtils: { /** From c7c1faa6a220f3891e884e21ef2dbed77d98c4b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Mon, 26 Mar 2018 14:50:30 +0300 Subject: [PATCH 20/50] Smaller clean-up in background.js --- test-addon/src/background.js | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/test-addon/src/background.js b/test-addon/src/background.js index 6277b24..f6d3cab 100644 --- a/test-addon/src/background.js +++ b/test-addon/src/background.js @@ -2,17 +2,10 @@ "use strict"; -/** - * CONFIGURE and INSTRUMENT the BrowserAction button for a specific variation - * - * 1. Request 'info' from the shieldUtils - * 2. We only care about the `variation` key. - * 3. initialize the feature, using our specific variation - */ async function runOnce() { - // ensure we have configured shieldUtils and are supposed to run our feature - await browser.shieldUtils.bootstrapStudy(); - // get study variation + // Ensure we have configured shieldUtils and are supposed to run our feature + await browser.shieldUtils.bootstrapStudy(studyConfig); + // Get study variation const { variation } = await browser.shieldUtils.info(); } From 879dbd0d7adb4ca853db3a9b68f83c187b5cef5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Mon, 26 Mar 2018 14:53:23 +0300 Subject: [PATCH 21/50] Renamed config to studyConfig --- src/StudyUtilsBootstrap.jsm | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/StudyUtilsBootstrap.jsm b/src/StudyUtilsBootstrap.jsm index 0ff6f4a..2e4f078 100644 --- a/src/StudyUtilsBootstrap.jsm +++ b/src/StudyUtilsBootstrap.jsm @@ -5,7 +5,7 @@ const EXPORTED_SYMBOLS = ["Bootstrap"]; const { utils: Cu } = Components; Cu.import("resource://gre/modules/Services.jsm"); -this.Bootstrap = function(config, studyUtils) { +this.Bootstrap = function(studyConfig, studyUtils) { return { /** * Use console as our logger until there is a log() method in studyUtils that we can rely on @@ -38,10 +38,10 @@ this.Bootstrap = function(config, studyUtils) { ) { // telemetry "enter" ONCE studyUtils.firstSeen(); - const eligible = await config.isEligible(); + const eligible = await studyConfig.isEligible(); if (!eligible) { this.log.debug("User is ineligible, ending study."); - // 1. uses config.endings.ineligible.url if any, + // 1. uses studyConfig.endings.ineligible.url if any, // 2. sends UT for "ineligible" // 3. then uninstalls addon await studyUtils.endStudy({ reason: "ineligible" }); @@ -49,7 +49,7 @@ this.Bootstrap = function(config, studyUtils) { } } - const expired = await config.hasExpired(); + const expired = await studyConfig.hasExpired(); if (expired) { await studyUtils.endStudy({ reason: "expired" }); return; @@ -67,19 +67,19 @@ this.Bootstrap = function(config, studyUtils) { initStudyUtils(id, version) { // validate study config - config.addon = { id, version }; - studyUtils.setup(config); + studyConfig.addon = { id, version }; + studyUtils.setup(studyConfig); // TODO bdanforth: patch studyUtils to setLoggingLevel as part of setup method - studyUtils.setLoggingLevel(config.log.studyUtils.level); + studyUtils.setLoggingLevel(studyConfig.log.studyUtils.level); }, // choose the variation for this particular user, then set it. async selectVariation() { const variation = - this.getVariationFromPref(config.weightedVariations) || - (await studyUtils.deterministicVariation(config.weightedVariations)); + this.getVariationFromPref(studyConfig.weightedVariations) || + (await studyUtils.deterministicVariation(studyConfig.weightedVariations)); studyUtils.setVariation(variation); - this.log.debug(`studyUtils has config and variation.name: ${ + this.log.debug(`studyUtils has studyConfig and variation.name: ${ variation.name }. Ready to send telemetry`); @@ -89,14 +89,14 @@ this.Bootstrap = function(config, studyUtils) { // helper to let Dev or QA set the variation name getVariationFromPref(weightedVariations) { const name = Services.prefs.getCharPref( - config.variationOverridePreference, + studyConfig.variationOverridePreference, "", ); if (name !== "") { const variation = weightedVariations.filter(x => x.name === name)[0]; if (!variation) { throw new Error(`about:config => ${ - config.variationOverridePreference + studyConfig.variationOverridePreference } set to ${name}, but no variation with that name exists.`); } From 2e2fc1fa4cdb6e42e4c4adc40ab1812d199f0d03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Mon, 26 Mar 2018 14:57:58 +0300 Subject: [PATCH 22/50] Moved study config from privileged scope to web extension background scope --- schema.json | 7 ++++- src/StudyUtilsBootstrap.jsm | 6 ++-- src/index.js | 11 ++++--- test-addon/src/background.js | 23 ++++++++++++++ test-addon/src/manifest.json | 2 +- .../{privileged/Config.jsm => studyConfig.js} | 30 +------------------ 6 files changed, 38 insertions(+), 41 deletions(-) rename test-addon/src/{privileged/Config.jsm => studyConfig.js} (74%) diff --git a/schema.json b/schema.json index a14cb3b..a7df523 100644 --- a/schema.json +++ b/schema.json @@ -8,7 +8,12 @@ "type": "function", "description": "TODO", "async": true, - "parameters": [] + "parameters": [ + { + "name": "studyConfig", + "type": "any" + } + ] }, { "name": "info", diff --git a/src/StudyUtilsBootstrap.jsm b/src/StudyUtilsBootstrap.jsm index 2e4f078..703477d 100644 --- a/src/StudyUtilsBootstrap.jsm +++ b/src/StudyUtilsBootstrap.jsm @@ -38,8 +38,7 @@ this.Bootstrap = function(studyConfig, studyUtils) { ) { // telemetry "enter" ONCE studyUtils.firstSeen(); - const eligible = await studyConfig.isEligible(); - if (!eligible) { + if (!studyConfig.eligible) { this.log.debug("User is ineligible, ending study."); // 1. uses studyConfig.endings.ineligible.url if any, // 2. sends UT for "ineligible" @@ -49,8 +48,7 @@ this.Bootstrap = function(studyConfig, studyUtils) { } } - const expired = await studyConfig.hasExpired(); - if (expired) { + if (studyConfig.expired) { await studyUtils.endStudy({ reason: "expired" }); return; } diff --git a/src/index.js b/src/index.js index 258b9ed..bfab469 100644 --- a/src/index.js +++ b/src/index.js @@ -31,15 +31,10 @@ this.shieldUtils = class extends ExtensionAPI { } getAPI(context) { - const { config } = ChromeUtils.import( - context.extension.getURL("privileged/Config.jsm"), - ); - const { studyUtils } = require("./StudyUtils.in.jsm"); const studyUtilsBootstrap = require("./StudyUtilsBootstrap.jsm"); // const { PioneerUtils } = require("pioneer-utils/PioneerUtils.jsm"); // const pioneerUtilsBootstrap = require("./PioneerUtilsBootstrap.jsm"); - const bootstrap = studyUtilsBootstrap.Bootstrap(config, studyUtils); const { extension } = this; @@ -50,7 +45,11 @@ this.shieldUtils = class extends ExtensionAPI { * and are supposed to run our feature * @returns {Promise} */ - async bootstrapStudy() { + async bootstrapStudy(studyConfig) { + const bootstrap = studyUtilsBootstrap.Bootstrap( + studyConfig, + studyUtils, + ); await bootstrap.startup(extension); }, diff --git a/test-addon/src/background.js b/test-addon/src/background.js index f6d3cab..dc14237 100644 --- a/test-addon/src/background.js +++ b/test-addon/src/background.js @@ -1,12 +1,35 @@ /* eslint no-console:off */ +/* global studyConfig */ "use strict"; +class Study { + constructor(variation) {} + + // Will run only during first install attempt + static async isEligible() { + // get whatever prefs, addons, telemetry, anything! + // Cu.import can see 'firefox things', but not package things. + return true; + } + + // Expiration checks should be implemented in a very reliable way by + // the add-on since Normandy does not handle study expiration in a reliable manner + static async hasExpired() { + return false; + } +} + async function runOnce() { + // Set dynamic study configuration flags + studyConfig.eligible = await Study.isEligible(); + studyConfig.expired = await Study.hasExpired(); // Ensure we have configured shieldUtils and are supposed to run our feature await browser.shieldUtils.bootstrapStudy(studyConfig); // Get study variation const { variation } = await browser.shieldUtils.info(); + // Initiate the study + new Study(variation); } /** diff --git a/test-addon/src/manifest.json b/test-addon/src/manifest.json index 3bf3d53..f8db89d 100644 --- a/test-addon/src/manifest.json +++ b/test-addon/src/manifest.json @@ -20,7 +20,7 @@ } }, "background": { - "scripts": ["background.js"] + "scripts": ["studyConfig.js", "background.js"] }, "icons": { "48": "icons/shield-icon.svg" diff --git a/test-addon/src/privileged/Config.jsm b/test-addon/src/studyConfig.js similarity index 74% rename from test-addon/src/privileged/Config.jsm rename to test-addon/src/studyConfig.js index 4403291..bbc9de9 100644 --- a/test-addon/src/privileged/Config.jsm +++ b/test-addon/src/studyConfig.js @@ -1,19 +1,4 @@ -"use strict"; - -/* to use: - -- Recall this file has chrome privileges -- Cu.import in this file will work for any 'general firefox things' (Services,etc) - but NOT for addon-specific libs -*/ - -/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "(config|EXPORTED_SYMBOLS)" }]*/ -const EXPORTED_SYMBOLS = ["config"]; - -// Anything else than "var" here will result in config being undefined when included in other js modules -// prettier-ignore -var config = { // eslint-disable-line no-var - // required STUDY key +const studyConfig = { study: { /** Required for studyUtils.setup(): * @@ -81,19 +66,6 @@ var config = { // eslint-disable-line no-var }, }, - // Will run only during first install attempt - async isEligible() { - // get whatever prefs, addons, telemetry, anything! - // Cu.import can see 'firefox things', but not package things. - return true; - }, - - // Expiration checks should be implemented in a very reliable way by - // the add-on since Normandy does not handle study expiration in a reliable manner - async hasExpired() { - return false; - }, - /* Button study branches and sample weights - test kittens vs. puppies if we can only have one. - downweight lizards. Lizards is a 'poison' branch, meant to From 087bf7c032361495d24b7fd9715b29b0d230e6c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Mon, 26 Mar 2018 14:58:32 +0300 Subject: [PATCH 23/50] Formatting --- src/StudyUtilsBootstrap.jsm | 4 +++- test-addon/test/functional/utils.js | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/StudyUtilsBootstrap.jsm b/src/StudyUtilsBootstrap.jsm index 703477d..257aeec 100644 --- a/src/StudyUtilsBootstrap.jsm +++ b/src/StudyUtilsBootstrap.jsm @@ -75,7 +75,9 @@ this.Bootstrap = function(studyConfig, studyUtils) { async selectVariation() { const variation = this.getVariationFromPref(studyConfig.weightedVariations) || - (await studyUtils.deterministicVariation(studyConfig.weightedVariations)); + (await studyUtils.deterministicVariation( + studyConfig.weightedVariations, + )); studyUtils.setVariation(variation); this.log.debug(`studyUtils has studyConfig and variation.name: ${ variation.name diff --git a/test-addon/test/functional/utils.js b/test-addon/test/functional/utils.js index 08eac29..89adf57 100644 --- a/test-addon/test/functional/utils.js +++ b/test-addon/test/functional/utils.js @@ -62,7 +62,7 @@ async function promiseActualBinary(binary) { /** * Uses process.env.FIREFOX_BINARY */ -module.exports.promiseSetupDriver = async() => { +module.exports.promiseSetupDriver = async () => { const profile = new firefox.Profile(); Object.keys(FIREFOX_PREFERENCES).forEach(key => { @@ -109,7 +109,7 @@ module.exports.installAddon = async driver => { console.log(`Add-on at ${fileLocation} installed`); }; -module.exports.uninstallAddon = async(driver, id) => { +module.exports.uninstallAddon = async (driver, id) => { const executor = driver.getExecutor(); executor.defineCommand( "uninstallAddon", From aa3b8911af51619f81b65f72e0b28cfeb889e20d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Mon, 26 Mar 2018 15:28:15 +0300 Subject: [PATCH 24/50] Renamed study util js files to match variable names --- src/index.js | 6 +++--- src/{StudyUtils.in.jsm => studyUtils.js} | 0 src/{StudyUtilsBootstrap.jsm => studyUtilsBootstrap.js} | 0 3 files changed, 3 insertions(+), 3 deletions(-) rename src/{StudyUtils.in.jsm => studyUtils.js} (100%) rename src/{StudyUtilsBootstrap.jsm => studyUtilsBootstrap.js} (100%) diff --git a/src/index.js b/src/index.js index bfab469..8098222 100644 --- a/src/index.js +++ b/src/index.js @@ -31,10 +31,10 @@ this.shieldUtils = class extends ExtensionAPI { } getAPI(context) { - const { studyUtils } = require("./StudyUtils.in.jsm"); - const studyUtilsBootstrap = require("./StudyUtilsBootstrap.jsm"); + const { studyUtils } = require("./studyUtils.js"); + const studyUtilsBootstrap = require("./studyUtilsBootstrap.js"); // const { PioneerUtils } = require("pioneer-utils/PioneerUtils.jsm"); - // const pioneerUtilsBootstrap = require("./PioneerUtilsBootstrap.jsm"); + // const pioneerUtilsBootstrap = require("./pioneerUtilsBootstrap.js"); const { extension } = this; diff --git a/src/StudyUtils.in.jsm b/src/studyUtils.js similarity index 100% rename from src/StudyUtils.in.jsm rename to src/studyUtils.js diff --git a/src/StudyUtilsBootstrap.jsm b/src/studyUtilsBootstrap.js similarity index 100% rename from src/StudyUtilsBootstrap.jsm rename to src/studyUtilsBootstrap.js From f4e6c85540262f47344c62a7655e918816a529b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Mon, 26 Mar 2018 15:29:03 +0300 Subject: [PATCH 25/50] Bundle command to reflect that it is no longer necessary to bundle any jsm file --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0e9c795..2e4122e 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "pretest-addon:run": "npm run pretest", "test": "FIREFOX_BINARY=${FIREFOX_BINARY:-firefox} XPI_NAME=test-addon/dist/shield_utils_test_add-on-1.0.0.zip mocha test-addon/test/functional/ --bail", "test-addon:build": "cd test-addon && web-ext build", - "test-addon:bundle-utils": "mkdir -p test-addon/src/privileged/shieldUtils/jsm && cp src/StudyUtilsBootstrap.jsm test-addon/src/privileged/shieldUtils/jsm/StudyUtilsBootstrap.jsm && cp dist/api.js test-addon/src/privileged/shieldUtils/api.js && cp schema.json test-addon/src/privileged/shieldUtils/schema.json", + "test-addon:bundle-utils": "mkdir -p test-addon/src/privileged/shieldUtils && cp dist/api.js test-addon/src/privileged/shieldUtils/api.js && cp schema.json test-addon/src/privileged/shieldUtils/schema.json", "test-addon:run": "cd test-addon && web-ext run --no-reload" } } From c67c13a3b13c5c452608cdfd67e3788176e9535e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Mon, 26 Mar 2018 15:29:42 +0300 Subject: [PATCH 26/50] Moved schema-related files to own folder --- src/{schemas.js => schemas/index.js} | 0 src/{ => schemas}/schema.studySetup.json | 0 src/{ => schemas}/schema.webExtensionMsg.json | 0 src/{ => schemas}/schema.weightedVariations.json | 0 src/studyUtils.js | 2 +- 5 files changed, 1 insertion(+), 1 deletion(-) rename src/{schemas.js => schemas/index.js} (100%) rename src/{ => schemas}/schema.studySetup.json (100%) rename src/{ => schemas}/schema.webExtensionMsg.json (100%) rename src/{ => schemas}/schema.weightedVariations.json (100%) diff --git a/src/schemas.js b/src/schemas/index.js similarity index 100% rename from src/schemas.js rename to src/schemas/index.js diff --git a/src/schema.studySetup.json b/src/schemas/schema.studySetup.json similarity index 100% rename from src/schema.studySetup.json rename to src/schemas/schema.studySetup.json diff --git a/src/schema.webExtensionMsg.json b/src/schemas/schema.webExtensionMsg.json similarity index 100% rename from src/schema.webExtensionMsg.json rename to src/schemas/schema.webExtensionMsg.json diff --git a/src/schema.weightedVariations.json b/src/schemas/schema.weightedVariations.json similarity index 100% rename from src/schema.weightedVariations.json rename to src/schemas/schema.weightedVariations.json diff --git a/src/studyUtils.js b/src/studyUtils.js index 21ee4a5..0eb1645 100644 --- a/src/studyUtils.js +++ b/src/studyUtils.js @@ -64,7 +64,7 @@ const { TelemetryEnvironment } = Cu.import( * StudyUtils.respondToWebExtensionMessage method * - "studySetup": the options object passed into the StudyUtils.setup method */ -const schemas = require("./schemas.js"); +const schemas = require("./schemas/index.js"); const Ajv = require("ajv/dist/ajv.min.js"); const ajv = new Ajv(); From 05b05068cf87b913bf736ee4654328652a134dc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Mon, 26 Mar 2018 15:33:01 +0300 Subject: [PATCH 27/50] Synchronized names for studySetup variables (was called studySteup, config and studyConfig previously) --- src/studyUtils.js | 26 +++++++++---------- src/studyUtilsBootstrap.js | 24 ++++++++--------- test-addon/src/background.js | 8 +++--- test-addon/src/manifest.json | 2 +- .../src/{studyConfig.js => studySetup.js} | 2 +- 5 files changed, 31 insertions(+), 31 deletions(-) rename test-addon/src/{studyConfig.js => studySetup.js} (99%) diff --git a/src/studyUtils.js b/src/studyUtils.js index 0eb1645..2b2acb7 100644 --- a/src/studyUtils.js +++ b/src/studyUtils.js @@ -9,7 +9,7 @@ * Note: There are a number of methods that won't work if the * setup method has not executed (they perform a check with the * `throwIfNotSetup` method). The setup method ensures that the -* config data passed in is valid per the studySetup schema. +* studySetup data passed in is valid per the studySetup schema. */ /* @@ -395,16 +395,16 @@ class StudyUtils { /** * Validates the studySetup object passed in from the addon. - * @param {Object} config - the studySetup object, see schema.studySetup.json + * @param {Object} studySetup - the studySetup object, see schema.studySetup.json * @returns {StudyUtils} - the StudyUtils class instance */ - setup(config) { - log = createLog("shield-study-utils", config.log.studyUtils.level); + setup(studySetup) { + log = createLog("shield-study-utils", studySetup.log.studyUtils.level); log.debug("setting up!"); - jsonschema.validateOrThrow(config, schemas.studySetup); + jsonschema.validateOrThrow(studySetup, schemas.studySetup); - this.config = config; + this.studySetup = studySetup; this._isSetup = true; return this; } @@ -414,7 +414,7 @@ class StudyUtils { * @returns {void} */ reset() { - this.config = {}; + this.studySetup = {}; delete this._variation; this._isSetup = false; } @@ -493,7 +493,7 @@ class StudyUtils { let fraction = rng; if (fraction === null) { const clientId = await this.getTelemetryId(); - const studyName = this.config.study.studyName; + const studyName = this.studySetup.study.studyName; fraction = await this.sample.hashFraction(studyName + clientId, 12); } return this.sample.chooseWeighted(weightedVariations, fraction); @@ -516,8 +516,8 @@ class StudyUtils { log.debug("getting info"); this.throwIfNotSetup("info"); return { - studyName: this.config.study.studyName, - addon: this.config.addon, + studyName: this.studySetup.study.studyName, + addon: this.studySetup.addon, variation: this.getVariation(), shieldId: this.getShieldId(), }; @@ -530,7 +530,7 @@ class StudyUtils { // TODO glind, maybe this is getter / setter? get telemetryConfig() { this.throwIfNotSetup("telemetryConfig"); - return this.config.study.telemetry; + return this.studySetup.study.telemetry; } /** @@ -640,7 +640,7 @@ class StudyUtils { * Check if the study ending shows the user a page in a new tab * (ex: survey, explanation, etc.) */ - const ending = this.config.study.endings[reason]; + const ending = this.studySetup.study.endings[reason]; if (ending) { // baseUrl: needs to be appended with query arguments before use, // exactUrl: used as is @@ -813,7 +813,7 @@ class StudyUtils { * Note: Log.jsm is used over Console.log/warn/error because: * - Console has limited log levels * - Console is not pref-controllable. Log can be turned on and off using - * config.log (see ./addon/Config.jsm in + * studySetup.log (see ./addon/Config.jsm in * github.com/mozilla/shield-study-addon-template) * - Console can create linting errors and warnings. * @param {string} name - the name of the Logger instance diff --git a/src/studyUtilsBootstrap.js b/src/studyUtilsBootstrap.js index 257aeec..a0e4ac5 100644 --- a/src/studyUtilsBootstrap.js +++ b/src/studyUtilsBootstrap.js @@ -5,7 +5,7 @@ const EXPORTED_SYMBOLS = ["Bootstrap"]; const { utils: Cu } = Components; Cu.import("resource://gre/modules/Services.jsm"); -this.Bootstrap = function(studyConfig, studyUtils) { +this.Bootstrap = function(studySetup, studyUtils) { return { /** * Use console as our logger until there is a log() method in studyUtils that we can rely on @@ -38,9 +38,9 @@ this.Bootstrap = function(studyConfig, studyUtils) { ) { // telemetry "enter" ONCE studyUtils.firstSeen(); - if (!studyConfig.eligible) { + if (!studySetup.eligible) { this.log.debug("User is ineligible, ending study."); - // 1. uses studyConfig.endings.ineligible.url if any, + // 1. uses studySetup.endings.ineligible.url if any, // 2. sends UT for "ineligible" // 3. then uninstalls addon await studyUtils.endStudy({ reason: "ineligible" }); @@ -48,7 +48,7 @@ this.Bootstrap = function(studyConfig, studyUtils) { } } - if (studyConfig.expired) { + if (studySetup.expired) { await studyUtils.endStudy({ reason: "expired" }); return; } @@ -65,21 +65,21 @@ this.Bootstrap = function(studyConfig, studyUtils) { initStudyUtils(id, version) { // validate study config - studyConfig.addon = { id, version }; - studyUtils.setup(studyConfig); + studySetup.addon = { id, version }; + studyUtils.setup(studySetup); // TODO bdanforth: patch studyUtils to setLoggingLevel as part of setup method - studyUtils.setLoggingLevel(studyConfig.log.studyUtils.level); + studyUtils.setLoggingLevel(studySetup.log.studyUtils.level); }, // choose the variation for this particular user, then set it. async selectVariation() { const variation = - this.getVariationFromPref(studyConfig.weightedVariations) || + this.getVariationFromPref(studySetup.weightedVariations) || (await studyUtils.deterministicVariation( - studyConfig.weightedVariations, + studySetup.weightedVariations, )); studyUtils.setVariation(variation); - this.log.debug(`studyUtils has studyConfig and variation.name: ${ + this.log.debug(`studyUtils has studySetup and variation.name: ${ variation.name }. Ready to send telemetry`); @@ -89,14 +89,14 @@ this.Bootstrap = function(studyConfig, studyUtils) { // helper to let Dev or QA set the variation name getVariationFromPref(weightedVariations) { const name = Services.prefs.getCharPref( - studyConfig.variationOverridePreference, + studySetup.variationOverridePreference, "", ); if (name !== "") { const variation = weightedVariations.filter(x => x.name === name)[0]; if (!variation) { throw new Error(`about:config => ${ - studyConfig.variationOverridePreference + studySetup.variationOverridePreference } set to ${name}, but no variation with that name exists.`); } diff --git a/test-addon/src/background.js b/test-addon/src/background.js index dc14237..7f98802 100644 --- a/test-addon/src/background.js +++ b/test-addon/src/background.js @@ -1,5 +1,5 @@ /* eslint no-console:off */ -/* global studyConfig */ +/* global studySetup */ "use strict"; @@ -22,10 +22,10 @@ class Study { async function runOnce() { // Set dynamic study configuration flags - studyConfig.eligible = await Study.isEligible(); - studyConfig.expired = await Study.hasExpired(); + studySetup.eligible = await Study.isEligible(); + studySetup.expired = await Study.hasExpired(); // Ensure we have configured shieldUtils and are supposed to run our feature - await browser.shieldUtils.bootstrapStudy(studyConfig); + await browser.shieldUtils.bootstrapStudy(studySetup); // Get study variation const { variation } = await browser.shieldUtils.info(); // Initiate the study diff --git a/test-addon/src/manifest.json b/test-addon/src/manifest.json index f8db89d..a538dba 100644 --- a/test-addon/src/manifest.json +++ b/test-addon/src/manifest.json @@ -20,7 +20,7 @@ } }, "background": { - "scripts": ["studyConfig.js", "background.js"] + "scripts": ["studySetup.js", "background.js"] }, "icons": { "48": "icons/shield-icon.svg" diff --git a/test-addon/src/studyConfig.js b/test-addon/src/studySetup.js similarity index 99% rename from test-addon/src/studyConfig.js rename to test-addon/src/studySetup.js index bbc9de9..0efb776 100644 --- a/test-addon/src/studyConfig.js +++ b/test-addon/src/studySetup.js @@ -1,4 +1,4 @@ -const studyConfig = { +const studySetup = { study: { /** Required for studyUtils.setup(): * From 10dea4756cb0099de23d626dc89a574119be4169 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Mon, 26 Mar 2018 15:45:01 +0300 Subject: [PATCH 28/50] Moved web extension api related parts to a separate folder --- package.json | 2 +- schema.json => webExtensionApi/schema.json | 0 {src => webExtensionApi/src}/index.js | 0 {src => webExtensionApi/src}/schemas/index.js | 0 {src => webExtensionApi/src}/schemas/schema.studySetup.json | 0 .../src}/schemas/schema.webExtensionMsg.json | 0 .../src}/schemas/schema.weightedVariations.json | 0 {src => webExtensionApi/src}/studyUtils.js | 2 +- {src => webExtensionApi/src}/studyUtilsBootstrap.js | 0 webpack.config.js | 4 ++-- 10 files changed, 4 insertions(+), 4 deletions(-) rename schema.json => webExtensionApi/schema.json (100%) rename {src => webExtensionApi/src}/index.js (100%) rename {src => webExtensionApi/src}/schemas/index.js (100%) rename {src => webExtensionApi/src}/schemas/schema.studySetup.json (100%) rename {src => webExtensionApi/src}/schemas/schema.webExtensionMsg.json (100%) rename {src => webExtensionApi/src}/schemas/schema.weightedVariations.json (100%) rename {src => webExtensionApi/src}/studyUtils.js (99%) rename {src => webExtensionApi/src}/studyUtilsBootstrap.js (100%) diff --git a/package.json b/package.json index 2e4122e..12a2f31 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "pretest-addon:run": "npm run pretest", "test": "FIREFOX_BINARY=${FIREFOX_BINARY:-firefox} XPI_NAME=test-addon/dist/shield_utils_test_add-on-1.0.0.zip mocha test-addon/test/functional/ --bail", "test-addon:build": "cd test-addon && web-ext build", - "test-addon:bundle-utils": "mkdir -p test-addon/src/privileged/shieldUtils && cp dist/api.js test-addon/src/privileged/shieldUtils/api.js && cp schema.json test-addon/src/privileged/shieldUtils/schema.json", + "test-addon:bundle-utils": "mkdir -p test-addon/src/privileged/shieldUtils && cp webExtensionApi/dist/api.js test-addon/src/privileged/shieldUtils/api.js && cp webExtensionApi/schema.json test-addon/src/privileged/shieldUtils/schema.json", "test-addon:run": "cd test-addon && web-ext run --no-reload" } } diff --git a/schema.json b/webExtensionApi/schema.json similarity index 100% rename from schema.json rename to webExtensionApi/schema.json diff --git a/src/index.js b/webExtensionApi/src/index.js similarity index 100% rename from src/index.js rename to webExtensionApi/src/index.js diff --git a/src/schemas/index.js b/webExtensionApi/src/schemas/index.js similarity index 100% rename from src/schemas/index.js rename to webExtensionApi/src/schemas/index.js diff --git a/src/schemas/schema.studySetup.json b/webExtensionApi/src/schemas/schema.studySetup.json similarity index 100% rename from src/schemas/schema.studySetup.json rename to webExtensionApi/src/schemas/schema.studySetup.json diff --git a/src/schemas/schema.webExtensionMsg.json b/webExtensionApi/src/schemas/schema.webExtensionMsg.json similarity index 100% rename from src/schemas/schema.webExtensionMsg.json rename to webExtensionApi/src/schemas/schema.webExtensionMsg.json diff --git a/src/schemas/schema.weightedVariations.json b/webExtensionApi/src/schemas/schema.weightedVariations.json similarity index 100% rename from src/schemas/schema.weightedVariations.json rename to webExtensionApi/src/schemas/schema.weightedVariations.json diff --git a/src/studyUtils.js b/webExtensionApi/src/studyUtils.js similarity index 99% rename from src/studyUtils.js rename to webExtensionApi/src/studyUtils.js index 2b2acb7..7cdfe6d 100644 --- a/src/studyUtils.js +++ b/webExtensionApi/src/studyUtils.js @@ -18,7 +18,7 @@ */ const EXPORTED_SYMBOLS = ["studyUtils"]; -const UTILS_VERSION = require("../package.json").version; +const UTILS_VERSION = require("../../package.json").version; const PACKET_VERSION = 3; const { utils: Cu } = Components; diff --git a/src/studyUtilsBootstrap.js b/webExtensionApi/src/studyUtilsBootstrap.js similarity index 100% rename from src/studyUtilsBootstrap.js rename to webExtensionApi/src/studyUtilsBootstrap.js diff --git a/webpack.config.js b/webpack.config.js index 2356f9b..0197be5 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -2,9 +2,9 @@ const path = require("path"); module.exports = { - entry: "./src/index.js", + entry: "./webExtensionApi/src/index.js", output: { - path: path.resolve(__dirname, "dist"), + path: path.resolve(__dirname, "webExtensionApi/dist"), filename: "api.js", libraryTarget: "this", // Possible value - amd, commonjs, commonjs2, commonjs-module, this, var }, From d9b5ae40bfb072d4212b3a4b1f8bf5a2ba703024 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Mon, 26 Mar 2018 16:11:08 +0300 Subject: [PATCH 29/50] Moved out jsonschema + sampling to separate files. Changed exposed "sample" to "sampling" and in general structured sampling.js in a comparable way to pioneer-utils sampling.ts --- webExtensionApi/src/jsonschema.js | 31 +++++++ webExtensionApi/src/sampling.js | 105 ++++++++++++++++++++++ webExtensionApi/src/studyUtils.js | 143 ++---------------------------- 3 files changed, 144 insertions(+), 135 deletions(-) create mode 100644 webExtensionApi/src/jsonschema.js create mode 100644 webExtensionApi/src/sampling.js diff --git a/webExtensionApi/src/jsonschema.js b/webExtensionApi/src/jsonschema.js new file mode 100644 index 0000000..45b44a9 --- /dev/null +++ b/webExtensionApi/src/jsonschema.js @@ -0,0 +1,31 @@ +const Ajv = require("ajv/dist/ajv.min.js"); +const ajv = new Ajv(); + +const jsonschema = { + /** + * Validates input data based on a specified schema + * @param {Object} data - The data to be validated + * @param {Object} schema - The schema to validate against + * @returns {boolean} - Will return true if the data is valid + */ + validate(data, schema) { + const valid = ajv.validate(schema, data); + return { valid, errors: ajv.errors || [] }; + }, + /** + * Validates input data based on a specified schema + * @param {Object} data - The data to be validated + * @param {Object} schema - The schema to validate against + * @throws Will throw an error if the data is not valid + * @returns {boolean} - Will return true if the data is valid + */ + validateOrThrow(data, schema) { + const valid = ajv.validate(schema, data); + if (!valid) { + throw new Error(JSON.stringify(ajv.errors)); + } + return true; + }, +}; + +export default jsonschema; diff --git a/webExtensionApi/src/sampling.js b/webExtensionApi/src/sampling.js new file mode 100644 index 0000000..027ab19 --- /dev/null +++ b/webExtensionApi/src/sampling.js @@ -0,0 +1,105 @@ +import jsonschema from "./jsonschema"; +import schemas from "./schemas"; + +const { utils: Cu } = Components; +Cu.import("resource://gre/modules/Services.jsm"); +const { TextEncoder } = Cu.getGlobalForObject(Services); + +/** + * Given sample weights (weightedVariations) and a particular position + * (fraction), return a variation. If no fraction given, return a variation + * at random fraction proportional to the weightVariations object + * @param {Object[]} weightedVariations - the array of branch name:weight pairs + * used to randomly assign the user to a branch + * @param {Number} fraction - a number (0 <= fraction < 1) + * @returns {Object} - the variation object in weightedVariations for the given + * fraction + */ +export function chooseWeighted(weightedVariations, fraction = Math.random()) { + /* + weightedVaiations, list of: + { + name: string of any length + weight: float >= 0 + } + */ + jsonschema.validateOrThrow(weightedVariations, schemas.weightedVariations); + + const weights = weightedVariations.map(x => x.weight || 1); + const partial = cumsum(weights); + const total = weights.reduce((a, b) => a + b); + for (let ii = 0; ii < weightedVariations.length; ii++) { + if (fraction <= partial[ii] / total) { + return weightedVariations[ii]; + } + } + return null; +} + +/** + * @async + * Converts a string into a fraction (0 <= fraction < 1) based on the first + * X bits of its sha256 hexadecimal representation + * Note: Salting (adding the study name to the telemetry clientID) ensures + * that the same user gets a different bucket/hash for each study. + * Hashing of the salted string ensures uniform hashing; i.e. that every + * bucket/variation gets filled. + * @param {string} saltedString - a salted string used to create a hash for + * the user + * @param {Number} bits - The first number of bits to use in the sha256 hex + * representation + * @returns {Number} - a fraction (0 <= fraction < 1) + */ +export async function hashFraction(saltedString, bits = 12) { + const hash = await sha256(saltedString); + return parseInt(hash.substr(0, bits), 16) / Math.pow(16, bits); +} + +/** + * @async + * Converts a string into its sha256 hexadecimal representation. + * Note: This is ultimately used to make a hash of the user's telemetry clientID + * and the study name. + * @param {string} message - The message to convert. + * @returns {string} - a hexadecimal, 256-bit hash + */ +export async function sha256(message) { + // encode as UTF-8 + const msgBuffer = new TextEncoder("utf-8").encode(message); + // hash the message + const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer); + // convert ArrayBuffer to Array + const hashArray = Array.from(new Uint8Array(hashBuffer)); + // convert bytes to hex string + const hashHex = hashArray + .map(b => ("00" + b.toString(16)).slice(-2)) + .join(""); + return hashHex; +} + +/** + * Converts an array of length N into a cumulative sum array of length N, + * where n_i = sum(array.slice(0,i)) i.e. each element is the sum of all + * elements up to and including that element + * This is ultimately used for turning sample weights (AKA weightedVariations) + * into right hand limits (>= X) to deterministically select which variation + * a user receives. + * @example [.25,.3,.45] => [.25,.55,1.0]; if a user's sample weight were .25, + * they would fall into the left-most bucket + * @param {Number[]} arr - An array of sample weights (0 <= sample weight < 1) + * @returns {Number[]} - A cumulative sum array of sample weights + * (0 <= sample weight <= 1) + */ +export function cumsum(arr) { + return arr.reduce(function(r, c, i) { + r.push((r[i - 1] || 0) + c); + return r; + }, []); +} + +export default { + chooseWeighted, + cumsum, + hashFraction, + sha256, +}; diff --git a/webExtensionApi/src/studyUtils.js b/webExtensionApi/src/studyUtils.js index 7cdfe6d..f906e7d 100644 --- a/webExtensionApi/src/studyUtils.js +++ b/webExtensionApi/src/studyUtils.js @@ -2,6 +2,8 @@ "use strict"; +import sampling from "./sampling"; + /* * For an overview of what this module does, see ABOUT.md at * github.com/mozilla/shield-studies-addon-template @@ -27,10 +29,6 @@ Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.importGlobalProperties(["URL", "crypto", "URLSearchParams"]); -// Steal globals only available in JSM scope (and not Sandbox one) -// Example globals: ChromeUtils, HeapSnapshot, XMLHttpRequest, atob, btoa, TextEncoder, TextDecoder -const { TextEncoder } = Cu.getGlobalForObject(Services); - let log; // telemetry utils @@ -64,36 +62,8 @@ const { TelemetryEnvironment } = Cu.import( * StudyUtils.respondToWebExtensionMessage method * - "studySetup": the options object passed into the StudyUtils.setup method */ -const schemas = require("./schemas/index.js"); -const Ajv = require("ajv/dist/ajv.min.js"); -const ajv = new Ajv(); - -const jsonschema = { - /** - * Validates input data based on a specified schema - * @param {Object} data - The data to be validated - * @param {Object} schema - The schema to validate against - * @returns {boolean} - Will return true if the data is valid - */ - validate(data, schema) { - const valid = ajv.validate(schema, data); - return { valid, errors: ajv.errors || [] }; - }, - /** - * Validates input data based on a specified schema - * @param {Object} data - The data to be validated - * @param {Object} schema - The schema to validate against - * @throws Will throw an error if the data is not valid - * @returns {boolean} - Will return true if the data is valid - */ - validateOrThrow(data, schema) { - const valid = ajv.validate(schema, data); - if (!valid) { - throw new Error(JSON.stringify(ajv.errors)); - } - return true; - }, -}; +import schemas from "./schemas"; +import jsonschema from "./jsonschema"; /** * Note: This is the deep merge from the addon-sdk (sdk/util/object.js). @@ -209,99 +179,6 @@ function mergeQueryArgs(url, ...args) { return U.toString(); } -// sampling utils -/** - * @async - * Converts a string into its sha256 hexadecimal representation. - * Note: This is ultimately used to make a hash of the user's telemetry clientID - * and the study name. - * @param {string} message - The message to convert. - * @returns {string} - a hexadecimal, 256-bit hash - */ -async function sha256(message) { - // encode as UTF-8 - const msgBuffer = new TextEncoder("utf-8").encode(message); - // hash the message - const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer); - // convert ArrayBuffer to Array - const hashArray = Array.from(new Uint8Array(hashBuffer)); - // convert bytes to hex string - const hashHex = hashArray - .map(b => ("00" + b.toString(16)).slice(-2)) - .join(""); - return hashHex; -} - -/** - * Converts an array of length N into a cumulative sum array of length N, - * where n_i = sum(array.slice(0,i)) i.e. each element is the sum of all - * elements up to and including that element - * This is ultimately used for turning sample weights (AKA weightedVariations) - * into right hand limits (>= X) to deterministically select which variation - * a user receives. - * @example [.25,.3,.45] => [.25,.55,1.0]; if a user's sample weight were .25, - * they would fall into the left-most bucket - * @param {Number[]} arr - An array of sample weights (0 <= sample weight < 1) - * @returns {Number[]} - A cumulative sum array of sample weights - * (0 <= sample weight <= 1) - */ -function cumsum(arr) { - return arr.reduce(function(r, c, i) { - r.push((r[i - 1] || 0) + c); - return r; - }, []); -} - -/** - * Given sample weights (weightedVariations) and a particular position - * (fraction), return a variation. If no fraction given, return a variation - * at random fraction proportional to the weightVariations object - * @param {Object[]} weightedVariations - the array of branch name:weight pairs - * used to randomly assign the user to a branch - * @param {Number} fraction - a number (0 <= fraction < 1) - * @returns {Object} - the variation object in weightedVariations for the given - * fraction - */ -function chooseWeighted(weightedVariations, fraction = Math.random()) { - /* - weightedVaiations, list of: - { - name: string of any length - weight: float >= 0 - } - */ - jsonschema.validateOrThrow(weightedVariations, schemas.weightedVariations); - - const weights = weightedVariations.map(x => x.weight || 1); - const partial = cumsum(weights); - const total = weights.reduce((a, b) => a + b); - for (let ii = 0; ii < weightedVariations.length; ii++) { - if (fraction <= partial[ii] / total) { - return weightedVariations[ii]; - } - } - return null; -} - -/** - * @async - * Converts a string into a fraction (0 <= fraction < 1) based on the first - * X bits of its sha256 hexadecimal representation - * Note: Salting (adding the study name to the telemetry clientID) ensures - * that the same user gets a different bucket/hash for each study. - * Hashing of the salted string ensures uniform hashing; i.e. that every - * bucket/variation gets filled. - * @param {string} saltedString - a salted string used to create a hash for - * the user - * @param {Number} bits - The first number of bits to use in the sha256 hex - * representation - * @returns {Number} - a fraction (0 <= fraction < 1) - */ -async function hashFraction(saltedString, bits = 12) { - const hash = await sha256(saltedString); - return parseInt(hash.substr(0, bits), 16) / Math.pow(16, bits); -} - /** * Class representing utilities for shield studies. */ @@ -366,12 +243,8 @@ class StudyUtils { * Expose sampling methods onto the exported studyUtils singleton, for use * by any Components.utils-importing module */ - this.sample = { - sha256, - cumsum, - chooseWeighted, - hashFraction, - }; + this.sampling = sampling; + // expose schemas this.schemas = schemas; @@ -494,9 +367,9 @@ class StudyUtils { if (fraction === null) { const clientId = await this.getTelemetryId(); const studyName = this.studySetup.study.studyName; - fraction = await this.sample.hashFraction(studyName + clientId, 12); + fraction = await this.sampling.hashFraction(studyName + clientId, 12); } - return this.sample.chooseWeighted(weightedVariations, fraction); + return this.sampling.chooseWeighted(weightedVariations, fraction); } /** From 533291f212577956b66e8194070f7b0983f1027c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Mon, 26 Mar 2018 16:55:34 +0300 Subject: [PATCH 30/50] Made room for more web extension apis --- {webExtensionApi => webExtensionApis/shieldUtils}/schema.json | 0 {webExtensionApi => webExtensionApis/shieldUtils}/src/index.js | 0 .../shieldUtils}/src/jsonschema.js | 0 {webExtensionApi => webExtensionApis/shieldUtils}/src/sampling.js | 0 .../shieldUtils}/src/schemas/index.js | 0 .../shieldUtils}/src/schemas/schema.studySetup.json | 0 .../shieldUtils}/src/schemas/schema.webExtensionMsg.json | 0 .../shieldUtils}/src/schemas/schema.weightedVariations.json | 0 .../shieldUtils}/src/studyUtils.js | 0 .../shieldUtils}/src/studyUtilsBootstrap.js | 0 10 files changed, 0 insertions(+), 0 deletions(-) rename {webExtensionApi => webExtensionApis/shieldUtils}/schema.json (100%) rename {webExtensionApi => webExtensionApis/shieldUtils}/src/index.js (100%) rename {webExtensionApi => webExtensionApis/shieldUtils}/src/jsonschema.js (100%) rename {webExtensionApi => webExtensionApis/shieldUtils}/src/sampling.js (100%) rename {webExtensionApi => webExtensionApis/shieldUtils}/src/schemas/index.js (100%) rename {webExtensionApi => webExtensionApis/shieldUtils}/src/schemas/schema.studySetup.json (100%) rename {webExtensionApi => webExtensionApis/shieldUtils}/src/schemas/schema.webExtensionMsg.json (100%) rename {webExtensionApi => webExtensionApis/shieldUtils}/src/schemas/schema.weightedVariations.json (100%) rename {webExtensionApi => webExtensionApis/shieldUtils}/src/studyUtils.js (100%) rename {webExtensionApi => webExtensionApis/shieldUtils}/src/studyUtilsBootstrap.js (100%) diff --git a/webExtensionApi/schema.json b/webExtensionApis/shieldUtils/schema.json similarity index 100% rename from webExtensionApi/schema.json rename to webExtensionApis/shieldUtils/schema.json diff --git a/webExtensionApi/src/index.js b/webExtensionApis/shieldUtils/src/index.js similarity index 100% rename from webExtensionApi/src/index.js rename to webExtensionApis/shieldUtils/src/index.js diff --git a/webExtensionApi/src/jsonschema.js b/webExtensionApis/shieldUtils/src/jsonschema.js similarity index 100% rename from webExtensionApi/src/jsonschema.js rename to webExtensionApis/shieldUtils/src/jsonschema.js diff --git a/webExtensionApi/src/sampling.js b/webExtensionApis/shieldUtils/src/sampling.js similarity index 100% rename from webExtensionApi/src/sampling.js rename to webExtensionApis/shieldUtils/src/sampling.js diff --git a/webExtensionApi/src/schemas/index.js b/webExtensionApis/shieldUtils/src/schemas/index.js similarity index 100% rename from webExtensionApi/src/schemas/index.js rename to webExtensionApis/shieldUtils/src/schemas/index.js diff --git a/webExtensionApi/src/schemas/schema.studySetup.json b/webExtensionApis/shieldUtils/src/schemas/schema.studySetup.json similarity index 100% rename from webExtensionApi/src/schemas/schema.studySetup.json rename to webExtensionApis/shieldUtils/src/schemas/schema.studySetup.json diff --git a/webExtensionApi/src/schemas/schema.webExtensionMsg.json b/webExtensionApis/shieldUtils/src/schemas/schema.webExtensionMsg.json similarity index 100% rename from webExtensionApi/src/schemas/schema.webExtensionMsg.json rename to webExtensionApis/shieldUtils/src/schemas/schema.webExtensionMsg.json diff --git a/webExtensionApi/src/schemas/schema.weightedVariations.json b/webExtensionApis/shieldUtils/src/schemas/schema.weightedVariations.json similarity index 100% rename from webExtensionApi/src/schemas/schema.weightedVariations.json rename to webExtensionApis/shieldUtils/src/schemas/schema.weightedVariations.json diff --git a/webExtensionApi/src/studyUtils.js b/webExtensionApis/shieldUtils/src/studyUtils.js similarity index 100% rename from webExtensionApi/src/studyUtils.js rename to webExtensionApis/shieldUtils/src/studyUtils.js diff --git a/webExtensionApi/src/studyUtilsBootstrap.js b/webExtensionApis/shieldUtils/src/studyUtilsBootstrap.js similarity index 100% rename from webExtensionApi/src/studyUtilsBootstrap.js rename to webExtensionApis/shieldUtils/src/studyUtilsBootstrap.js From 243b4404a8aeea5e9e023a09de6cc386bd8d0c2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Mon, 26 Mar 2018 16:58:19 +0300 Subject: [PATCH 31/50] Added the prefs api from https://github.com/gregglind/shield-study-webextension-experiment-template/commit/ecf81ea772911be1f9bc7d007d05a5d8b0bc698e --- test-addon/src/background.js | 4 +++ test-addon/src/manifest.json | 8 +++++ webExtensionApis/prefs/api.js | 50 ++++++++++++++++++++++++++++++ webExtensionApis/prefs/schema.json | 37 ++++++++++++++++++++++ 4 files changed, 99 insertions(+) create mode 100644 webExtensionApis/prefs/api.js create mode 100644 webExtensionApis/prefs/schema.json diff --git a/test-addon/src/background.js b/test-addon/src/background.js index 7f98802..cccee42 100644 --- a/test-addon/src/background.js +++ b/test-addon/src/background.js @@ -21,7 +21,11 @@ class Study { } async function runOnce() { + + //browser.prefs.get('my.favorite.pref'); + // Set dynamic study configuration flags + // TODO studySetup.eligible = await Study.isEligible(); studySetup.expired = await Study.hasExpired(); // Ensure we have configured shieldUtils and are supposed to run our feature diff --git a/test-addon/src/manifest.json b/test-addon/src/manifest.json index a538dba..78d0c1e 100644 --- a/test-addon/src/manifest.json +++ b/test-addon/src/manifest.json @@ -10,6 +10,14 @@ } }, "experiment_apis": { + "prefs": { + "schema": "./privileged/prefs/schema.json", + "parent": { + "scopes": ["addon_parent"], + "script": "./privileged/prefs/api.js", + "paths": [["prefs"]] + } + }, "shieldUtils": { "schema": "./privileged/shieldUtils/schema.json", "parent": { diff --git a/webExtensionApis/prefs/api.js b/webExtensionApis/prefs/api.js new file mode 100644 index 0000000..ea29465 --- /dev/null +++ b/webExtensionApis/prefs/api.js @@ -0,0 +1,50 @@ +"use strict"; + +ChromeUtils.import("resource://gre/modules/Services.jsm"); + +const BASE_PREF = "extensions.original-bootstrap-addon-id."; + +function get(key, type = "char") { + key = BASE_PREF + key; + + switch (type) { + case "char": + return Services.prefs.getCharPref(key); + case "bool": + return Services.prefs.getBoolPref(key); + case "int": + return Services.prefs.getIntPref(key); + } + + throw new Error(`Unknown type: ${type}`); +} + +function set(key, type, value) { + key = BASE_PREF + key; + + switch (type) { + case "char": + return Services.prefs.setCharPref(key, value); + case "bool": + return Services.prefs.setBoolPref(key, value); + case "int": + return Services.prefs.setIntPref(key, value); + } + throw new Error(`Unknown type: ${type}`); +} + +/* https://firefox-source-docs.mozilla.org/toolkit/components/extensions/webextensions/functions.html */ +this.prefs = class extends ExtensionAPI { + getAPI(context) { + return { + prefs: { + async get(prefName) { + return "getting"; + }, + async set(prefName, value) { + return "set"; + } + } + }; + } +} diff --git a/webExtensionApis/prefs/schema.json b/webExtensionApis/prefs/schema.json new file mode 100644 index 0000000..fa2208d --- /dev/null +++ b/webExtensionApis/prefs/schema.json @@ -0,0 +1,37 @@ +[ + { + "namespace": "prefs", + "description": "Pref get, set, watch, unwatch(?)", + "functions": [ + { + "name": "get", + "type": "function", + "description": "Returns the value of a pref", + "async": true, + "parameters": [ + { + "name": "prefName", + "type": "string" + } + ] + }, + { + "name": "set", + "type": "function", + "description": "Sets the value of a pref", + "async": true, + "parameters": [ + { + "name": "prefName", + "type": "string" + }, + { + "name": "value", + "type": "any" + } + + ] + } + ] + } +] From 228266b353ceffa0ec396e4ae56b2096c7560737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Mon, 26 Mar 2018 17:13:26 +0300 Subject: [PATCH 32/50] Restored build + moved webpack to where it is used + made a bash script in the add-on determine which files to bundle into the add-on --- .gitignore | 1 - package.json | 4 ++-- .../bin/bundle-shield-studies-addon-utils.sh | 18 ++++++++++++++++++ test-addon/dist/.gitignore | 2 ++ webExtensionApis/shieldUtils/.gitignore | 1 + webExtensionApis/shieldUtils/src/studyUtils.js | 2 +- .../shieldUtils/webpack.config.js | 4 ++-- 7 files changed, 26 insertions(+), 6 deletions(-) create mode 100755 test-addon/bin/bundle-shield-studies-addon-utils.sh create mode 100644 test-addon/dist/.gitignore create mode 100644 webExtensionApis/shieldUtils/.gitignore rename webpack.config.js => webExtensionApis/shieldUtils/webpack.config.js (67%) diff --git a/.gitignore b/.gitignore index 10856d8..b75b390 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,3 @@ node_modules *.xpi *.tgz *.*.swp -dist/ diff --git a/package.json b/package.json index 12a2f31..92b5032 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "url": "git://github.com/mozilla/shield-studies-addon-utils.git" }, "scripts": { - "build": "webpack", + "build": "cd webExtensionApis/shieldUtils && webpack", "docformat": "doctoc --title '**Contents**' docs/*.md && prettier '**/*.md' --write", "eslint": "eslint src --ext jsm --ext js --ext json", "eslint-fix": "npm run eslint -- --fix", @@ -61,7 +61,7 @@ "pretest-addon:run": "npm run pretest", "test": "FIREFOX_BINARY=${FIREFOX_BINARY:-firefox} XPI_NAME=test-addon/dist/shield_utils_test_add-on-1.0.0.zip mocha test-addon/test/functional/ --bail", "test-addon:build": "cd test-addon && web-ext build", - "test-addon:bundle-utils": "mkdir -p test-addon/src/privileged/shieldUtils && cp webExtensionApi/dist/api.js test-addon/src/privileged/shieldUtils/api.js && cp webExtensionApi/schema.json test-addon/src/privileged/shieldUtils/schema.json", + "test-addon:bundle-utils": "test-addon/bin/bundle-shield-studies-addon-utils.sh", "test-addon:run": "cd test-addon && web-ext run --no-reload" } } diff --git a/test-addon/bin/bundle-shield-studies-addon-utils.sh b/test-addon/bin/bundle-shield-studies-addon-utils.sh new file mode 100755 index 0000000..dd3c041 --- /dev/null +++ b/test-addon/bin/bundle-shield-studies-addon-utils.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +# fail on any error +set -o errexit + +# always run from the repository root directory +script_path=`dirname $0` +cd "$script_path/../../" + +# bundle the shieldUtils web extension experiment +mkdir -p test-addon/src/privileged/shieldUtils +cp webExtensionApis/shieldUtils/api.js test-addon/src/privileged/shieldUtils/api.js +cp webExtensionApis/shieldUtils/schema.json test-addon/src/privileged/shieldUtils/schema.json + +# bundle the prefs web extension experiment +mkdir -p test-addon/src/privileged/prefs +cp webExtensionApis/prefs/api.js test-addon/src/privileged/prefs/api.js +cp webExtensionApis/prefs/schema.json test-addon/src/privileged/prefs/schema.json diff --git a/test-addon/dist/.gitignore b/test-addon/dist/.gitignore new file mode 100644 index 0000000..aca2417 --- /dev/null +++ b/test-addon/dist/.gitignore @@ -0,0 +1,2 @@ +* +!*.gitignore diff --git a/webExtensionApis/shieldUtils/.gitignore b/webExtensionApis/shieldUtils/.gitignore new file mode 100644 index 0000000..2e3f83a --- /dev/null +++ b/webExtensionApis/shieldUtils/.gitignore @@ -0,0 +1 @@ +/api.js diff --git a/webExtensionApis/shieldUtils/src/studyUtils.js b/webExtensionApis/shieldUtils/src/studyUtils.js index f906e7d..d6860cc 100644 --- a/webExtensionApis/shieldUtils/src/studyUtils.js +++ b/webExtensionApis/shieldUtils/src/studyUtils.js @@ -20,7 +20,7 @@ import sampling from "./sampling"; */ const EXPORTED_SYMBOLS = ["studyUtils"]; -const UTILS_VERSION = require("../../package.json").version; +const UTILS_VERSION = require("../../../package.json").version; const PACKET_VERSION = 3; const { utils: Cu } = Components; diff --git a/webpack.config.js b/webExtensionApis/shieldUtils/webpack.config.js similarity index 67% rename from webpack.config.js rename to webExtensionApis/shieldUtils/webpack.config.js index 0197be5..5c6ea0e 100644 --- a/webpack.config.js +++ b/webExtensionApis/shieldUtils/webpack.config.js @@ -2,9 +2,9 @@ const path = require("path"); module.exports = { - entry: "./webExtensionApi/src/index.js", + entry: "./src/index.js", output: { - path: path.resolve(__dirname, "webExtensionApi/dist"), + path: path.resolve(__dirname), filename: "api.js", libraryTarget: "this", // Possible value - amd, commonjs, commonjs2, commonjs-module, this, var }, From c6c8f753001f4b4f307b628debfc546d35903e94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Mon, 26 Mar 2018 17:16:41 +0300 Subject: [PATCH 33/50] Formatting --- test-addon/src/background.js | 1 - webExtensionApis/prefs/api.js | 6 +++--- webExtensionApis/prefs/schema.json | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/test-addon/src/background.js b/test-addon/src/background.js index cccee42..99b310a 100644 --- a/test-addon/src/background.js +++ b/test-addon/src/background.js @@ -21,7 +21,6 @@ class Study { } async function runOnce() { - //browser.prefs.get('my.favorite.pref'); // Set dynamic study configuration flags diff --git a/webExtensionApis/prefs/api.js b/webExtensionApis/prefs/api.js index ea29465..6b62101 100644 --- a/webExtensionApis/prefs/api.js +++ b/webExtensionApis/prefs/api.js @@ -43,8 +43,8 @@ this.prefs = class extends ExtensionAPI { }, async set(prefName, value) { return "set"; - } - } + }, + }, }; } -} +}; diff --git a/webExtensionApis/prefs/schema.json b/webExtensionApis/prefs/schema.json index fa2208d..605c449 100644 --- a/webExtensionApis/prefs/schema.json +++ b/webExtensionApis/prefs/schema.json @@ -29,7 +29,6 @@ "name": "value", "type": "any" } - ] } ] From 317657bcfe8a5aa187686d021b3928a4a34d0306 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Tue, 27 Mar 2018 16:18:38 +0300 Subject: [PATCH 34/50] Added the test utils from the template repo --- testUtils/exampleStudyTestUtils.js | 447 +++++++++++++++++++++++++++++ 1 file changed, 447 insertions(+) create mode 100644 testUtils/exampleStudyTestUtils.js diff --git a/testUtils/exampleStudyTestUtils.js b/testUtils/exampleStudyTestUtils.js new file mode 100644 index 0000000..8f9197e --- /dev/null +++ b/testUtils/exampleStudyTestUtils.js @@ -0,0 +1,447 @@ +/* eslint-env node */ +/* eslint no-console:off */ + +// The geckodriver package downloads and installs geckodriver for us. +// We use it by requiring it. +require("geckodriver"); +const cmd = require("selenium-webdriver/lib/command"); +const firefox = require("selenium-webdriver/firefox"); +const webdriver = require("selenium-webdriver"); +const FxRunnerUtils = require("fx-runner/lib/utils"); +const Fs = require("fs-extra"); +const path = require("path"); + +const By = webdriver.By; +const Context = firefox.Context; +const until = webdriver.until; + +// Note: Geckodriver already has quite a good set of default preferences +// for disabling various items. +// https://github.com/mozilla/geckodriver/blob/master/src/marionette.rs +const FIREFOX_PREFERENCES = { + // Ensure e10s is turned on. + "browser.tabs.remote.autostart": true, + "browser.tabs.remote.autostart.1": true, + "browser.tabs.remote.autostart.2": true, + + // Improve debugging using `browser toolbox`. + "devtools.chrome.enabled": true, + "devtools.debugger.remote-enabled": true, + "devtools.debugger.prompt-connection": false, + + // Removing warning for `about:config` + "general.warnOnAboutConfig": false, + + // Force variation for testing + "extensions.button_icon_preference.variation": "kittens", + + /** WARNING: gecko webdriver sets many additional prefs at: + * https://dxr.mozilla.org/mozilla-central/source/testing/geckodriver/src/prefs.rs + * + * In, particular, this DISABLES actual telemetry uploading + * ("toolkit.telemetry.server", Pref::new("https://%(server)s/dummy/telemetry/")), + * + */ +}; + +// useful if we need to test on a specific version of Firefox +async function promiseActualBinary(binary) { + try { + let normalizedBinary = await FxRunnerUtils.normalizeBinary(binary); + normalizedBinary = path.resolve(normalizedBinary); + await Fs.stat(normalizedBinary); + return normalizedBinary; + } catch (ex) { + if (ex.code === "ENOENT") { + throw new Error(`Could not find ${binary}`); + } + throw ex; + } +} + +/** + * Uses process.env.FIREFOX_BINARY + */ +module.exports.promiseSetupDriver = async () => { + const profile = new firefox.Profile(); + + // TODO, allow 'actually send telemetry' here. + Object.keys(FIREFOX_PREFERENCES).forEach(key => { + profile.setPreference(key, FIREFOX_PREFERENCES[key]); + }); + + // TODO glind, allow config to re-use profile + const options = new firefox.Options(); + options.setProfile(profile); + + const builder = new webdriver.Builder() + .forBrowser("firefox") + .setFirefoxOptions(options); + + const binaryLocation = await promiseActualBinary( + process.env.FIREFOX_BINARY || "nightly", + ); + await options.setBinary(new firefox.Binary(binaryLocation)); + const driver = await builder.build(); + // Firefox will be started up by now + driver.setContext(Context.CHROME); + return driver; +}; + +/* let's actually just make this a constant */ +const MODIFIER_KEY = (function getModifierKey() { + const modifierKey = + process.platform === "darwin" + ? webdriver.Key.COMMAND + : webdriver.Key.CONTROL; + return modifierKey; +})(); + +module.exports.MODIFIER_KEY = MODIFIER_KEY; + +// TODO glind general wrapper for 'async with callback'? + +/* Firefox UI helper functions */ + +// such as: "social-share-button" +module.exports.addButtonFromCustomizePanel = async (driver, buttonId) => + driver.executeAsyncScript(callback => { + // see https://dxr.mozilla.org/mozilla-central/rev/211d4dd61025c0a40caea7a54c9066e051bdde8c/browser/base/content/browser-social.js#193 + Components.utils.import("resource:///modules/CustomizableUI.jsm"); + CustomizableUI.addWidgetToArea(buttonId, CustomizableUI.AREA_NAVBAR); + callback(); + }); + +module.exports.removeButtonFromNavbar = async (driver, buttonId) => { + driver.setContext(Context.CONTENT); + try { + await driver.executeAsyncScript(callback => { + Components.utils.import("resource:///modules/CustomizableUI.jsm"); + CustomizableUI.removeWidgetFromArea(buttonId); + callback(); + }); + + // TODO glind fix this, I think this is supposed to prove it's dead. + const button = await module.exports.promiseAddonButton(driver); + return button === null; + } catch (e) { + if (e.name === "TimeoutError") { + return false; + } + throw e; + } +}; + +const promiseManifest = async () => { + const manifestJson = await Fs.readFile( + path.resolve("src/manifest.json"), + "utf8", + ); + return JSON.parse(manifestJson); +}; +module.exports.promiseManifest = promiseManifest; + +/** + * The widget id is used to identify add-on specific chrome elements. Examples: + * - Browser action - {addonWidgetId}-browser-action + * - Page action - {addonWidgetId}-page-action + * Search for makeWidgetId(extension.id) in the Firefox source code for more examples. + * @returns {Promise<*>} + */ +const addonWidgetId = async () => { + /** + * From firefox/browser/components/extensions/ExtensionPopups.jsm + */ + function makeWidgetId(id) { + id = id.toLowerCase(); + // FIXME: This allows for collisions. + return id.replace(/[^a-z0-9_-]/g, "_"); + } + + const manifest = await promiseManifest(); + return makeWidgetId(manifest.applications.gecko.id); +}; +module.exports.addonWidgetId = addonWidgetId; + +module.exports.installAddon = async (driver, fileLocation) => { + // references: + // https://bugzilla.mozilla.org/show_bug.cgi?id=1298025 + // https://github.com/mozilla/geckodriver/releases/tag/v0.17.0 + fileLocation = + fileLocation || path.join(process.cwd(), process.env.ADDON_ZIP); + + const executor = driver.getExecutor(); + executor.defineCommand( + "installAddon", + "POST", + "/session/:sessionId/moz/addon/install", + ); + const installCmd = new cmd.Command("installAddon"); + + const session = await driver.getSession(); + installCmd.setParameters({ + sessionId: session.getId(), + path: fileLocation, + temporary: true, + }); + await executor.execute(installCmd); + console.log(`Add-on at ${fileLocation} installed`); +}; + +module.exports.uninstallAddon = async (driver, id) => { + const executor = driver.getExecutor(); + executor.defineCommand( + "uninstallAddon", + "POST", + "/session/:sessionId/moz/addon/uninstall", + ); + const uninstallCmd = new cmd.Command("uninstallAddon"); + + const session = await driver.getSession(); + uninstallCmd.setParameters({ sessionId: session.getId(), id }); + await executor.execute(uninstallCmd); +}; + +/* this is NOT WORKING FOR UNKNOWN HARD TO EXLAIN REASONS +=> Uncaught WebDriverError: InternalError: too much recursion +module.exports.allAddons = async(driver) => { + // callback is how you get the return back from the script + return driver.executeAsyncScript(async(callback,) => { + Components.utils.import("resource://gre/modules/AddonManager.jsm"); + const L = await AddonManager.getAllAddons(); + callback(await L); + }); +}; +*/ + +/** Returns array of pings of type `type` in reverse sorted order by timestamp + * first element is most recent ping + * + * as seen in shield-study-addon-util's `utils.jsm` + * options + * - type: string or array of ping types + * - n: positive integer. at most n pings. + * - timestamp: only pings after this timestamp. + * - headersOnly: boolean, just the 'headers' for the pings, not the full bodies. + */ +const getTelemetryPings = async (driver, passedOptions) => { + // callback is how you get the return back from the script + return driver.executeAsyncScript(async (options, callback) => { + let { type } = options; + const { n, timestamp, headersOnly } = options; + Components.utils.import("resource://gre/modules/TelemetryArchive.jsm"); + // {type, id, timestampCreated} + let pings = await TelemetryArchive.promiseArchivedPingList(); + if (type) { + if (!(type instanceof Array)) { + type = [type]; // Array-ify if it's a string + } + } + if (type) pings = pings.filter(p => type.includes(p.type)); + + if (timestamp) pings = pings.filter(p => p.timestampCreated > timestamp); + + pings.sort((a, b) => b.timestampCreated - a.timestampCreated); + if (n) pings = pings.slice(0, n); + const pingData = headersOnly + ? pings + : pings.map(ping => TelemetryArchive.promiseArchivedPingById(ping.id)); + + callback(await Promise.all(pingData)); + }, passedOptions); +}; +module.exports.getTelemetryPings = getTelemetryPings; + +const getShieldPingsAfterTimestamp = async (driver, ts) => { + return getTelemetryPings(driver, { + type: ["shield-study", "shield-study-addon"], + timestamp: ts, + }); +}; +module.exports.getShieldPingsAfterTimestamp = getShieldPingsAfterTimestamp; + +module.exports.summarizePings = pings => { + return pings.map(p => [p.payload.type, p.payload.data]); +}; + +module.exports.pingsReport = async pings => { + if (pings.length === 0) { + return { report: "No pings found" }; + } + const p0 = pings[0].payload; + // print common fields + const report = + ` +// common fields + +branch ${p0.branch} +study_name ${p0.study_name} +addon_version ${p0.addon_version} +version ${p0.version} + +` + + pings + .map( + (p, i) => `${i} ${p.creationDate} ${p.payload.type} +${JSON.stringify(p.payload.data, null, 2)} + +`, + ) + .join("\n"); + + return { report }; +}; + +// TODO glind, this interface feels janky +// this feels like it wants to be $ like. +// not obvious right now, moving on! +class getChromeElementBy { + static async _get1(driver, method, selector) { + driver.setContext(Context.CHROME); + try { + return await driver.wait( + until.elementLocated(By[method](selector)), + 1000, + ); + } catch (e) { + // if there an error, the button was not found + console.error(e); + return null; + } + } + static async id(driver, id) { + return this._get1(driver, "id", id); + } + + static async className(driver, className) { + return this._get1(driver, "className", className); + } + + static async tagName(driver, tagName) { + return this._get1(driver, "tagName", tagName); + } +} +module.exports.getChromeElementBy = getChromeElementBy; + +module.exports.promiseUrlBar = driver => { + driver.setContext(Context.CHROME); + return driver.wait(until.elementLocated(By.id("urlbar")), 1000); +}; + +module.exports.takeScreenshot = async ( + driver, + filepath = "./screenshot.png", +) => { + try { + const data = await driver.takeScreenshot(); + return await Fs.outputFile(filepath, data, "base64"); + } catch (screenshotError) { + throw screenshotError; + } +}; + +module.exports.gotoURL = async (driver, url) => { + // navigate to a regular page + driver.setContext(Context.CONTENT); + await driver.get(url); + driver.setContext(Context.CHROME); +}; + +class SearchError extends Error { + constructor(condition) { + const message = `Could not find ping satisfying condition: ${condition.toString()}`; + super(message); + this.message = message; + this.name = "SearchError"; + } +} + +module.exports.searchTelemetry = (conditionArray, telemetryArray) => { + const resultingPings = []; + for (const condition of conditionArray) { + const index = telemetryArray.findIndex(ping => condition(ping)); + if (index === -1) { + throw new SearchError(condition); + } + resultingPings.push(telemetryArray[index]); + } + return resultingPings; +}; + +// TODO glind, specific to share-button-study but useful to demo patterns. +// TODO glind, generalize, document, or destroy + +// module.exports.copyUrlBar = async(driver) => { +// const urlBar = await getChromeElementBy.id(driver,'urlbar'); +// const urlBar = await module.exports.promiseUrlBar(driver); +// await urlBar.sendKeys(webdriver.Key.chord(MODIFIER_KEY, "A")); +// await urlBar.sendKeys(webdriver.Key.chord(MODIFIER_KEY, "C")); +// }; + +// module.exports.testAnimation = async(driver) => { +// const button = await module.exports.promiseAddonButton(driver); +// if (button === null) { return { hasClass: false, hasColor: false }; } +// +// const buttonClassString = await button.getAttribute("class"); +// const buttonColor = await button.getCssValue("background-color"); +// +// const hasClass = buttonClassString.split(" ").includes("social-share-button-on"); +// const hasColor = buttonColor.includes("43, 153, 255"); +// return { hasClass, hasColor }; +// }; + +// module.exports.waitForClassAdded = async(driver) => { +// try { +// const animationTest = await driver.wait(async() => { +// const { hasClass } = await module.exports.testAnimation(driver); +// return hasClass; +// }, 1000); +// return animationTest; +// } catch (e) { +// if (e.name === "TimeoutError") { return null; } +// throw (e); +// } +// }; +// +// module.exports.waitForAnimationEnd = async(driver) => { +// try { +// return await driver.wait(async() => { +// const { hasClass, hasColor } = await module.exports.testAnimation(driver); +// return !hasClass && !hasColor; +// }, 1000); +// } catch (e) { +// if (e.name === "TimeoutError") { return null; } +// throw (e); +// } +// }; + +// module.exports.testPanel = async(driver, panelId) => { +// driver.setContext(Context.CHROME); +// try { // if we can't find the panel, return false +// return await driver.wait(async() => { +// // need to execute JS, since state is not an HTML attribute, it's a property +// const panelState = await driver.executeAsyncScript((panelIdArg, callback) => { +// const shareButtonPanel = window.document.getElementById(panelIdArg); +// if (shareButtonPanel === null) { +// callback(null); +// } else { +// const state = shareButtonPanel.state; +// callback(state); +// } +// }, panelId); +// return panelState === "open"; +// }, 1000); +// } catch (e) { +// if (e.name === "TimeoutError") { return null; } +// throw e; +// } +// }; + +// module.exports.closePanel = async(driver, target = null) => { +// if (target !== null) { +// target.sendKeys(webdriver.Key.ESCAPE); +// } else { +// const urlbar = await module.exports.promiseUrlBar(driver); +// await urlbar.sendKeys(webdriver.Key.ESCAPE); +// } +// }; From 6758125a885b68d6ed45b6c63d1aaa588c63e53c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Tue, 27 Mar 2018 17:07:04 +0300 Subject: [PATCH 35/50] Added test for: should be able to access shieldUtils WebExtensions API from the extension page for tests --- test-addon/src/background.js | 13 +++- .../src/extension-page-for-tests/index.html | 1 + .../test/functional/shield_utils_test.js | 26 ++++++- test-addon/test/functional/utils.js | 7 ++ testUtils/basics.js | 73 +++++++++++++++++++ 5 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 test-addon/src/extension-page-for-tests/index.html create mode 100644 testUtils/basics.js diff --git a/test-addon/src/background.js b/test-addon/src/background.js index 99b310a..2661dfa 100644 --- a/test-addon/src/background.js +++ b/test-addon/src/background.js @@ -60,4 +60,15 @@ browser.runtime.onInstalled.addListener(handleInstalled); // Run shutdown-related non-priviliged code // actually start -runOnce(); +//runOnce(); + +// run things here via tests instead +// required for right context to be available to selenium + +const createData = { + type: "detached_panel", + url: "extension-page-for-tests/index.html", + width: 500, + height: 500, +}; +const creating = browser.windows.create(createData); diff --git a/test-addon/src/extension-page-for-tests/index.html b/test-addon/src/extension-page-for-tests/index.html new file mode 100644 index 0000000..7fa58a6 --- /dev/null +++ b/test-addon/src/extension-page-for-tests/index.html @@ -0,0 +1 @@ +extension page diff --git a/test-addon/test/functional/shield_utils_test.js b/test-addon/test/functional/shield_utils_test.js index 3639752..e3202fa 100644 --- a/test-addon/test/functional/shield_utils_test.js +++ b/test-addon/test/functional/shield_utils_test.js @@ -8,8 +8,7 @@ const Context = firefox.Context; // TODO create new profile per test? // then we can test with a clean profile every time -/* -describe("Shield Study Utils Functional Tests", function() { +describe("Shield Study Add-on Utils Functional Tests", function() { // This gives Firefox time to start, and us a bit longer during some of the tests. this.timeout(15000); @@ -23,6 +22,27 @@ describe("Shield Study Utils Functional Tests", function() { after(() => driver.quit()); + it("should be able to access window.browser from the extension page for tests", async () => { + const hasAccessToWebExtensionApi = await utils.executeAsyncScriptInExtensionPageForTests( + driver, + async callback => { + callback(typeof browser === "object"); + }, + ); + assert(hasAccessToWebExtensionApi); + }); + + it("should be able to access shieldUtils WebExtensions API from the extension page for tests", async () => { + const hasAccessToShieldUtilsWebExtensionApi = await utils.executeAsyncScriptInExtensionPageForTests( + driver, + async callback => { + callback(browser && typeof browser.shieldUtils === "object"); + }, + ); + assert(hasAccessToShieldUtilsWebExtensionApi); + }); + + /* it("should return the correct variation", async () => { const variation = await driver.executeAsyncScript(async callback => { const { studyUtils } = Components.utils.import( @@ -251,5 +271,5 @@ describe("Shield Study Utils Functional Tests", function() { assert(pings.payload.data.study_state === "exit"); }); }); + */ }); -*/ diff --git a/test-addon/test/functional/utils.js b/test-addon/test/functional/utils.js index 89adf57..1dbbb8f 100644 --- a/test-addon/test/functional/utils.js +++ b/test-addon/test/functional/utils.js @@ -15,6 +15,11 @@ const path = require("path"); const Context = firefox.Context; // const until = webdriver.until; +// Re-usable shield-related test methods +const { + executeAsyncScriptInExtensionPageForTests, +} = require("../../../testUtils/basics"); + // Note: Geckodriver already has quite a good set of default preferences // for disabling various items. // https://github.com/mozilla/geckodriver/blob/master/src/marionette.rs @@ -122,3 +127,5 @@ module.exports.uninstallAddon = async (driver, id) => { uninstallCmd.setParameters({ sessionId: session.getId(), id }); await executor.execute(uninstallCmd); }; + +module.exports.executeAsyncScriptInExtensionPageForTests = executeAsyncScriptInExtensionPageForTests; diff --git a/testUtils/basics.js b/testUtils/basics.js new file mode 100644 index 0000000..ba89d8c --- /dev/null +++ b/testUtils/basics.js @@ -0,0 +1,73 @@ +/* eslint-env node */ + +const firefox = require("selenium-webdriver/firefox"); +const Context = firefox.Context; + +/** + * The tests rely on the add-on's background script opening up + * an extension page in a new window/tab. + * + * Extension pages get access to all the same privileged + * WebExtension APIs as the background scripts, allowing us + * to run tests directly against those APIs. + * + * This sets the expected path of this extension page so that + * we can check if we are in the right context to run tests. + * + * TODO: Find a cleaner way to accomplish this + * @type {string} + */ +const extensionPagePath = "/extension-page-for-tests/index.html"; + +module.exports.executeAsyncScriptInExtensionPageForTests = async(driver, + callable,) => { + + driver.setContext(Context.CONTENT); + + const checkIfCurrentlyInExtensionPageWindow = async() => { + let currentUrl = await driver.getCurrentUrl(); + return currentUrl.indexOf(extensionPagePath) > 0; + }; + + const isCurrentlyInExtensionPageWindow = await checkIfCurrentlyInExtensionPageWindow(); + + // We may still be loading firefox / the add-on + // wait for the extension page window to be available + if (!isCurrentlyInExtensionPageWindow) { + + await driver.wait( + async function() { + const handles = await driver.getAllWindowHandles(); + return handles.length === 2; + }, + 9000, + "Should have opened a popup", + ); + + const handles = await driver.getAllWindowHandles(); + const currentHandle = await driver.getWindowHandle(); + + // Find the new window handle. + let newWindowHandle = null; + for (const handle of handles) { + if (handle !== currentHandle) { + newWindowHandle = handle; + } + } + + // Switch to the extension page popup + await driver.switchTo().window(newWindowHandle); + + // Check the tab has loaded the right page. + // We use driver.wait to wait for the page to be loaded, since we + // are not able to easily use the load listeners built into selenium. + await driver.wait( + checkIfCurrentlyInExtensionPageWindow, + 10000, + "Should have loaded the extension page for tests", + ); + + } + + return await driver.executeAsyncScript(callable); +}; From 4f79edaec62b63ebf4672ee195dbd980046db64f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Tue, 27 Mar 2018 17:08:39 +0300 Subject: [PATCH 36/50] Test add-on ignores APIs that are bundled from shield-studies-addon-utils --- test-addon/src/privileged/.gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test-addon/src/privileged/.gitignore b/test-addon/src/privileged/.gitignore index be98b2c..04390e0 100644 --- a/test-addon/src/privileged/.gitignore +++ b/test-addon/src/privileged/.gitignore @@ -1 +1,3 @@ +# ignore APIs that are bundled from shield-studies-addon-utils shieldUtils/ +prefs/ From 7d04c3cfc4a43e1b45ca5da98f28c0e153421574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Tue, 27 Mar 2018 21:06:59 +0300 Subject: [PATCH 37/50] Formatting --- testUtils/basics.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/testUtils/basics.js b/testUtils/basics.js index ba89d8c..b89aecf 100644 --- a/testUtils/basics.js +++ b/testUtils/basics.js @@ -19,22 +19,22 @@ const Context = firefox.Context; */ const extensionPagePath = "/extension-page-for-tests/index.html"; -module.exports.executeAsyncScriptInExtensionPageForTests = async(driver, - callable,) => { - +module.exports.executeAsyncScriptInExtensionPageForTests = async ( + driver, + callable, +) => { driver.setContext(Context.CONTENT); - const checkIfCurrentlyInExtensionPageWindow = async() => { + const checkIfCurrentlyInExtensionPageWindow = async () => { let currentUrl = await driver.getCurrentUrl(); return currentUrl.indexOf(extensionPagePath) > 0; }; const isCurrentlyInExtensionPageWindow = await checkIfCurrentlyInExtensionPageWindow(); - // We may still be loading firefox / the add-on - // wait for the extension page window to be available + // Wait for the extension page window to be available + // (we may still be loading firefox/the add-on) if (!isCurrentlyInExtensionPageWindow) { - await driver.wait( async function() { const handles = await driver.getAllWindowHandles(); @@ -66,7 +66,6 @@ module.exports.executeAsyncScriptInExtensionPageForTests = async(driver, 10000, "Should have loaded the extension page for tests", ); - } return await driver.executeAsyncScript(callable); From 98182dc20e42263e7fb5d5efcff4da67ef7dbd10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Wed, 28 Mar 2018 10:58:21 +0300 Subject: [PATCH 38/50] Synced example study test utils and test-addon test utils + split test utils into separate files based on type of test util method --- package.json | 2 +- .../test/functional/shield_utils_test.js | 14 +- test-addon/test/functional/utils.js | 116 +---- testUtils/basics.js | 72 --- testUtils/exampleStudyTestUtils.js | 447 ------------------ testUtils/executeJs.js | 91 ++++ testUtils/nav.js | 13 + testUtils/pings.js | 101 ++++ testUtils/setup.js | 94 ++++ testUtils/ui.js | 121 +++++ testUtils/wip.js | 98 ++++ 11 files changed, 543 insertions(+), 626 deletions(-) delete mode 100644 testUtils/basics.js delete mode 100644 testUtils/exampleStudyTestUtils.js create mode 100644 testUtils/executeJs.js create mode 100644 testUtils/nav.js create mode 100644 testUtils/pings.js create mode 100644 testUtils/setup.js create mode 100644 testUtils/ui.js create mode 100644 testUtils/wip.js diff --git a/package.json b/package.json index 92b5032..3c09054 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "prepack": "fixpack && npm run build", "pretest": "npm run build && npm run test-addon:bundle-utils && npm run test-addon:build", "pretest-addon:run": "npm run pretest", - "test": "FIREFOX_BINARY=${FIREFOX_BINARY:-firefox} XPI_NAME=test-addon/dist/shield_utils_test_add-on-1.0.0.zip mocha test-addon/test/functional/ --bail", + "test": "FIREFOX_BINARY=${FIREFOX_BINARY:-firefox} ADDON_ZIP=test-addon/dist/shield_utils_test_add-on-1.0.0.zip mocha test-addon/test/functional/ --bail", "test-addon:build": "cd test-addon && web-ext build", "test-addon:bundle-utils": "test-addon/bin/bundle-shield-studies-addon-utils.sh", "test-addon:run": "cd test-addon && web-ext run --no-reload" diff --git a/test-addon/test/functional/shield_utils_test.js b/test-addon/test/functional/shield_utils_test.js index e3202fa..33afdb9 100644 --- a/test-addon/test/functional/shield_utils_test.js +++ b/test-addon/test/functional/shield_utils_test.js @@ -14,16 +14,16 @@ describe("Shield Study Add-on Utils Functional Tests", function() { let driver; - before(async () => { - driver = await utils.promiseSetupDriver(); + before(async() => { + driver = await utils.setup.promiseSetupDriver(utils.FIREFOX_PREFERENCES); // install the addon (note: returns addon id) - await utils.installAddon(driver); + await utils.setup.installAddon(driver); }); after(() => driver.quit()); - it("should be able to access window.browser from the extension page for tests", async () => { - const hasAccessToWebExtensionApi = await utils.executeAsyncScriptInExtensionPageForTests( + it("should be able to access window.browser from the extension page for tests", async() => { + const hasAccessToWebExtensionApi = await utils.executeJs.executeAsyncScriptInExtensionPageForTests( driver, async callback => { callback(typeof browser === "object"); @@ -32,8 +32,8 @@ describe("Shield Study Add-on Utils Functional Tests", function() { assert(hasAccessToWebExtensionApi); }); - it("should be able to access shieldUtils WebExtensions API from the extension page for tests", async () => { - const hasAccessToShieldUtilsWebExtensionApi = await utils.executeAsyncScriptInExtensionPageForTests( + it("should be able to access shieldUtils WebExtensions API from the extension page for tests", async() => { + const hasAccessToShieldUtilsWebExtensionApi = await utils.executeJs.executeAsyncScriptInExtensionPageForTests( driver, async callback => { callback(browser && typeof browser.shieldUtils === "object"); diff --git a/test-addon/test/functional/utils.js b/test-addon/test/functional/utils.js index 1dbbb8f..4f0b6cf 100644 --- a/test-addon/test/functional/utils.js +++ b/test-addon/test/functional/utils.js @@ -1,28 +1,10 @@ /* eslint-env node */ -/* eslint no-console:off */ // The geckodriver package downloads and installs geckodriver for us. // We use it by requiring it. require("geckodriver"); -const cmd = require("selenium-webdriver/lib/command"); -const firefox = require("selenium-webdriver/firefox"); -const webdriver = require("selenium-webdriver"); -const FxRunnerUtils = require("fx-runner/lib/utils"); -const Fs = require("fs-extra"); -const path = require("path"); -// const By = webdriver.By; -const Context = firefox.Context; -// const until = webdriver.until; - -// Re-usable shield-related test methods -const { - executeAsyncScriptInExtensionPageForTests, -} = require("../../../testUtils/basics"); - -// Note: Geckodriver already has quite a good set of default preferences -// for disabling various items. -// https://github.com/mozilla/geckodriver/blob/master/src/marionette.rs +// Preferences set during testing const FIREFOX_PREFERENCES = { // Ensure e10s is turned on. "browser.tabs.remote.autostart": true, @@ -40,7 +22,7 @@ const FIREFOX_PREFERENCES = { // Force variation for testing "extensions.button_icon_preference.variation": "puppers", - /** WARNING: gecko webdriver sets many additional prefs at: + /** WARNING: Geckodriver sets many additional prefs at: * https://dxr.mozilla.org/mozilla-central/source/testing/geckodriver/src/prefs.rs * * In, particular, this DISABLES actual telemetry uploading @@ -49,83 +31,19 @@ const FIREFOX_PREFERENCES = { */ }; -// useful if we need to test on a specific version of Firefox -async function promiseActualBinary(binary) { - try { - let normalizedBinary = await FxRunnerUtils.normalizeBinary(binary); - normalizedBinary = path.resolve(normalizedBinary); - await Fs.stat(normalizedBinary); - return normalizedBinary; - } catch (ex) { - if (ex.code === "ENOENT") { - throw new Error(`Could not find ${binary}`); - } - throw ex; - } -} - -/** - * Uses process.env.FIREFOX_BINARY - */ -module.exports.promiseSetupDriver = async () => { - const profile = new firefox.Profile(); - - Object.keys(FIREFOX_PREFERENCES).forEach(key => { - profile.setPreference(key, FIREFOX_PREFERENCES[key]); - }); - - const options = new firefox.Options(); - options.setProfile(profile); - - const builder = new webdriver.Builder() - .forBrowser("firefox") - .setFirefoxOptions(options); - - const binaryLocation = await promiseActualBinary( - process.env.FIREFOX_BINARY || "firefox", - ); - await options.setBinary(new firefox.Binary(binaryLocation)); - const driver = await builder.build(); - // Firefox will be started up by now - driver.setContext(Context.CHROME); - return driver; -}; - -module.exports.installAddon = async driver => { - // references: - // https://bugzilla.mozilla.org/show_bug.cgi?id=1298025 - // https://github.com/mozilla/geckodriver/releases/tag/v0.17.0 - const fileLocation = path.join(process.cwd(), process.env.XPI_NAME); - const executor = driver.getExecutor(); - executor.defineCommand( - "installAddon", - "POST", - "/session/:sessionId/moz/addon/install", - ); - const installCmd = new cmd.Command("installAddon"); - - const session = await driver.getSession(); - installCmd.setParameters({ - sessionId: session.getId(), - path: fileLocation, - temporary: true, - }); - await executor.execute(installCmd); - console.log(`Add-on at ${fileLocation} installed`); +// Re-usable test methods from shield-studies-addon-utils +const { executeJs } = require("../../../testUtils/executeJs"); +const { nav } = require("../../../testUtils/nav"); +const { pings } = require("../../../testUtils/pings"); +const { setup } = require("../../../testUtils/setup"); +const { ui } = require("../../../testUtils/ui"); + +// What we expose to our add-on-specific tests +module.exports = { + FIREFOX_PREFERENCES, + executeJs, + nav, + pings, + setup, + ui, }; - -module.exports.uninstallAddon = async (driver, id) => { - const executor = driver.getExecutor(); - executor.defineCommand( - "uninstallAddon", - "POST", - "/session/:sessionId/moz/addon/uninstall", - ); - const uninstallCmd = new cmd.Command("uninstallAddon"); - - const session = await driver.getSession(); - uninstallCmd.setParameters({ sessionId: session.getId(), id }); - await executor.execute(uninstallCmd); -}; - -module.exports.executeAsyncScriptInExtensionPageForTests = executeAsyncScriptInExtensionPageForTests; diff --git a/testUtils/basics.js b/testUtils/basics.js deleted file mode 100644 index b89aecf..0000000 --- a/testUtils/basics.js +++ /dev/null @@ -1,72 +0,0 @@ -/* eslint-env node */ - -const firefox = require("selenium-webdriver/firefox"); -const Context = firefox.Context; - -/** - * The tests rely on the add-on's background script opening up - * an extension page in a new window/tab. - * - * Extension pages get access to all the same privileged - * WebExtension APIs as the background scripts, allowing us - * to run tests directly against those APIs. - * - * This sets the expected path of this extension page so that - * we can check if we are in the right context to run tests. - * - * TODO: Find a cleaner way to accomplish this - * @type {string} - */ -const extensionPagePath = "/extension-page-for-tests/index.html"; - -module.exports.executeAsyncScriptInExtensionPageForTests = async ( - driver, - callable, -) => { - driver.setContext(Context.CONTENT); - - const checkIfCurrentlyInExtensionPageWindow = async () => { - let currentUrl = await driver.getCurrentUrl(); - return currentUrl.indexOf(extensionPagePath) > 0; - }; - - const isCurrentlyInExtensionPageWindow = await checkIfCurrentlyInExtensionPageWindow(); - - // Wait for the extension page window to be available - // (we may still be loading firefox/the add-on) - if (!isCurrentlyInExtensionPageWindow) { - await driver.wait( - async function() { - const handles = await driver.getAllWindowHandles(); - return handles.length === 2; - }, - 9000, - "Should have opened a popup", - ); - - const handles = await driver.getAllWindowHandles(); - const currentHandle = await driver.getWindowHandle(); - - // Find the new window handle. - let newWindowHandle = null; - for (const handle of handles) { - if (handle !== currentHandle) { - newWindowHandle = handle; - } - } - - // Switch to the extension page popup - await driver.switchTo().window(newWindowHandle); - - // Check the tab has loaded the right page. - // We use driver.wait to wait for the page to be loaded, since we - // are not able to easily use the load listeners built into selenium. - await driver.wait( - checkIfCurrentlyInExtensionPageWindow, - 10000, - "Should have loaded the extension page for tests", - ); - } - - return await driver.executeAsyncScript(callable); -}; diff --git a/testUtils/exampleStudyTestUtils.js b/testUtils/exampleStudyTestUtils.js deleted file mode 100644 index 8f9197e..0000000 --- a/testUtils/exampleStudyTestUtils.js +++ /dev/null @@ -1,447 +0,0 @@ -/* eslint-env node */ -/* eslint no-console:off */ - -// The geckodriver package downloads and installs geckodriver for us. -// We use it by requiring it. -require("geckodriver"); -const cmd = require("selenium-webdriver/lib/command"); -const firefox = require("selenium-webdriver/firefox"); -const webdriver = require("selenium-webdriver"); -const FxRunnerUtils = require("fx-runner/lib/utils"); -const Fs = require("fs-extra"); -const path = require("path"); - -const By = webdriver.By; -const Context = firefox.Context; -const until = webdriver.until; - -// Note: Geckodriver already has quite a good set of default preferences -// for disabling various items. -// https://github.com/mozilla/geckodriver/blob/master/src/marionette.rs -const FIREFOX_PREFERENCES = { - // Ensure e10s is turned on. - "browser.tabs.remote.autostart": true, - "browser.tabs.remote.autostart.1": true, - "browser.tabs.remote.autostart.2": true, - - // Improve debugging using `browser toolbox`. - "devtools.chrome.enabled": true, - "devtools.debugger.remote-enabled": true, - "devtools.debugger.prompt-connection": false, - - // Removing warning for `about:config` - "general.warnOnAboutConfig": false, - - // Force variation for testing - "extensions.button_icon_preference.variation": "kittens", - - /** WARNING: gecko webdriver sets many additional prefs at: - * https://dxr.mozilla.org/mozilla-central/source/testing/geckodriver/src/prefs.rs - * - * In, particular, this DISABLES actual telemetry uploading - * ("toolkit.telemetry.server", Pref::new("https://%(server)s/dummy/telemetry/")), - * - */ -}; - -// useful if we need to test on a specific version of Firefox -async function promiseActualBinary(binary) { - try { - let normalizedBinary = await FxRunnerUtils.normalizeBinary(binary); - normalizedBinary = path.resolve(normalizedBinary); - await Fs.stat(normalizedBinary); - return normalizedBinary; - } catch (ex) { - if (ex.code === "ENOENT") { - throw new Error(`Could not find ${binary}`); - } - throw ex; - } -} - -/** - * Uses process.env.FIREFOX_BINARY - */ -module.exports.promiseSetupDriver = async () => { - const profile = new firefox.Profile(); - - // TODO, allow 'actually send telemetry' here. - Object.keys(FIREFOX_PREFERENCES).forEach(key => { - profile.setPreference(key, FIREFOX_PREFERENCES[key]); - }); - - // TODO glind, allow config to re-use profile - const options = new firefox.Options(); - options.setProfile(profile); - - const builder = new webdriver.Builder() - .forBrowser("firefox") - .setFirefoxOptions(options); - - const binaryLocation = await promiseActualBinary( - process.env.FIREFOX_BINARY || "nightly", - ); - await options.setBinary(new firefox.Binary(binaryLocation)); - const driver = await builder.build(); - // Firefox will be started up by now - driver.setContext(Context.CHROME); - return driver; -}; - -/* let's actually just make this a constant */ -const MODIFIER_KEY = (function getModifierKey() { - const modifierKey = - process.platform === "darwin" - ? webdriver.Key.COMMAND - : webdriver.Key.CONTROL; - return modifierKey; -})(); - -module.exports.MODIFIER_KEY = MODIFIER_KEY; - -// TODO glind general wrapper for 'async with callback'? - -/* Firefox UI helper functions */ - -// such as: "social-share-button" -module.exports.addButtonFromCustomizePanel = async (driver, buttonId) => - driver.executeAsyncScript(callback => { - // see https://dxr.mozilla.org/mozilla-central/rev/211d4dd61025c0a40caea7a54c9066e051bdde8c/browser/base/content/browser-social.js#193 - Components.utils.import("resource:///modules/CustomizableUI.jsm"); - CustomizableUI.addWidgetToArea(buttonId, CustomizableUI.AREA_NAVBAR); - callback(); - }); - -module.exports.removeButtonFromNavbar = async (driver, buttonId) => { - driver.setContext(Context.CONTENT); - try { - await driver.executeAsyncScript(callback => { - Components.utils.import("resource:///modules/CustomizableUI.jsm"); - CustomizableUI.removeWidgetFromArea(buttonId); - callback(); - }); - - // TODO glind fix this, I think this is supposed to prove it's dead. - const button = await module.exports.promiseAddonButton(driver); - return button === null; - } catch (e) { - if (e.name === "TimeoutError") { - return false; - } - throw e; - } -}; - -const promiseManifest = async () => { - const manifestJson = await Fs.readFile( - path.resolve("src/manifest.json"), - "utf8", - ); - return JSON.parse(manifestJson); -}; -module.exports.promiseManifest = promiseManifest; - -/** - * The widget id is used to identify add-on specific chrome elements. Examples: - * - Browser action - {addonWidgetId}-browser-action - * - Page action - {addonWidgetId}-page-action - * Search for makeWidgetId(extension.id) in the Firefox source code for more examples. - * @returns {Promise<*>} - */ -const addonWidgetId = async () => { - /** - * From firefox/browser/components/extensions/ExtensionPopups.jsm - */ - function makeWidgetId(id) { - id = id.toLowerCase(); - // FIXME: This allows for collisions. - return id.replace(/[^a-z0-9_-]/g, "_"); - } - - const manifest = await promiseManifest(); - return makeWidgetId(manifest.applications.gecko.id); -}; -module.exports.addonWidgetId = addonWidgetId; - -module.exports.installAddon = async (driver, fileLocation) => { - // references: - // https://bugzilla.mozilla.org/show_bug.cgi?id=1298025 - // https://github.com/mozilla/geckodriver/releases/tag/v0.17.0 - fileLocation = - fileLocation || path.join(process.cwd(), process.env.ADDON_ZIP); - - const executor = driver.getExecutor(); - executor.defineCommand( - "installAddon", - "POST", - "/session/:sessionId/moz/addon/install", - ); - const installCmd = new cmd.Command("installAddon"); - - const session = await driver.getSession(); - installCmd.setParameters({ - sessionId: session.getId(), - path: fileLocation, - temporary: true, - }); - await executor.execute(installCmd); - console.log(`Add-on at ${fileLocation} installed`); -}; - -module.exports.uninstallAddon = async (driver, id) => { - const executor = driver.getExecutor(); - executor.defineCommand( - "uninstallAddon", - "POST", - "/session/:sessionId/moz/addon/uninstall", - ); - const uninstallCmd = new cmd.Command("uninstallAddon"); - - const session = await driver.getSession(); - uninstallCmd.setParameters({ sessionId: session.getId(), id }); - await executor.execute(uninstallCmd); -}; - -/* this is NOT WORKING FOR UNKNOWN HARD TO EXLAIN REASONS -=> Uncaught WebDriverError: InternalError: too much recursion -module.exports.allAddons = async(driver) => { - // callback is how you get the return back from the script - return driver.executeAsyncScript(async(callback,) => { - Components.utils.import("resource://gre/modules/AddonManager.jsm"); - const L = await AddonManager.getAllAddons(); - callback(await L); - }); -}; -*/ - -/** Returns array of pings of type `type` in reverse sorted order by timestamp - * first element is most recent ping - * - * as seen in shield-study-addon-util's `utils.jsm` - * options - * - type: string or array of ping types - * - n: positive integer. at most n pings. - * - timestamp: only pings after this timestamp. - * - headersOnly: boolean, just the 'headers' for the pings, not the full bodies. - */ -const getTelemetryPings = async (driver, passedOptions) => { - // callback is how you get the return back from the script - return driver.executeAsyncScript(async (options, callback) => { - let { type } = options; - const { n, timestamp, headersOnly } = options; - Components.utils.import("resource://gre/modules/TelemetryArchive.jsm"); - // {type, id, timestampCreated} - let pings = await TelemetryArchive.promiseArchivedPingList(); - if (type) { - if (!(type instanceof Array)) { - type = [type]; // Array-ify if it's a string - } - } - if (type) pings = pings.filter(p => type.includes(p.type)); - - if (timestamp) pings = pings.filter(p => p.timestampCreated > timestamp); - - pings.sort((a, b) => b.timestampCreated - a.timestampCreated); - if (n) pings = pings.slice(0, n); - const pingData = headersOnly - ? pings - : pings.map(ping => TelemetryArchive.promiseArchivedPingById(ping.id)); - - callback(await Promise.all(pingData)); - }, passedOptions); -}; -module.exports.getTelemetryPings = getTelemetryPings; - -const getShieldPingsAfterTimestamp = async (driver, ts) => { - return getTelemetryPings(driver, { - type: ["shield-study", "shield-study-addon"], - timestamp: ts, - }); -}; -module.exports.getShieldPingsAfterTimestamp = getShieldPingsAfterTimestamp; - -module.exports.summarizePings = pings => { - return pings.map(p => [p.payload.type, p.payload.data]); -}; - -module.exports.pingsReport = async pings => { - if (pings.length === 0) { - return { report: "No pings found" }; - } - const p0 = pings[0].payload; - // print common fields - const report = - ` -// common fields - -branch ${p0.branch} -study_name ${p0.study_name} -addon_version ${p0.addon_version} -version ${p0.version} - -` + - pings - .map( - (p, i) => `${i} ${p.creationDate} ${p.payload.type} -${JSON.stringify(p.payload.data, null, 2)} - -`, - ) - .join("\n"); - - return { report }; -}; - -// TODO glind, this interface feels janky -// this feels like it wants to be $ like. -// not obvious right now, moving on! -class getChromeElementBy { - static async _get1(driver, method, selector) { - driver.setContext(Context.CHROME); - try { - return await driver.wait( - until.elementLocated(By[method](selector)), - 1000, - ); - } catch (e) { - // if there an error, the button was not found - console.error(e); - return null; - } - } - static async id(driver, id) { - return this._get1(driver, "id", id); - } - - static async className(driver, className) { - return this._get1(driver, "className", className); - } - - static async tagName(driver, tagName) { - return this._get1(driver, "tagName", tagName); - } -} -module.exports.getChromeElementBy = getChromeElementBy; - -module.exports.promiseUrlBar = driver => { - driver.setContext(Context.CHROME); - return driver.wait(until.elementLocated(By.id("urlbar")), 1000); -}; - -module.exports.takeScreenshot = async ( - driver, - filepath = "./screenshot.png", -) => { - try { - const data = await driver.takeScreenshot(); - return await Fs.outputFile(filepath, data, "base64"); - } catch (screenshotError) { - throw screenshotError; - } -}; - -module.exports.gotoURL = async (driver, url) => { - // navigate to a regular page - driver.setContext(Context.CONTENT); - await driver.get(url); - driver.setContext(Context.CHROME); -}; - -class SearchError extends Error { - constructor(condition) { - const message = `Could not find ping satisfying condition: ${condition.toString()}`; - super(message); - this.message = message; - this.name = "SearchError"; - } -} - -module.exports.searchTelemetry = (conditionArray, telemetryArray) => { - const resultingPings = []; - for (const condition of conditionArray) { - const index = telemetryArray.findIndex(ping => condition(ping)); - if (index === -1) { - throw new SearchError(condition); - } - resultingPings.push(telemetryArray[index]); - } - return resultingPings; -}; - -// TODO glind, specific to share-button-study but useful to demo patterns. -// TODO glind, generalize, document, or destroy - -// module.exports.copyUrlBar = async(driver) => { -// const urlBar = await getChromeElementBy.id(driver,'urlbar'); -// const urlBar = await module.exports.promiseUrlBar(driver); -// await urlBar.sendKeys(webdriver.Key.chord(MODIFIER_KEY, "A")); -// await urlBar.sendKeys(webdriver.Key.chord(MODIFIER_KEY, "C")); -// }; - -// module.exports.testAnimation = async(driver) => { -// const button = await module.exports.promiseAddonButton(driver); -// if (button === null) { return { hasClass: false, hasColor: false }; } -// -// const buttonClassString = await button.getAttribute("class"); -// const buttonColor = await button.getCssValue("background-color"); -// -// const hasClass = buttonClassString.split(" ").includes("social-share-button-on"); -// const hasColor = buttonColor.includes("43, 153, 255"); -// return { hasClass, hasColor }; -// }; - -// module.exports.waitForClassAdded = async(driver) => { -// try { -// const animationTest = await driver.wait(async() => { -// const { hasClass } = await module.exports.testAnimation(driver); -// return hasClass; -// }, 1000); -// return animationTest; -// } catch (e) { -// if (e.name === "TimeoutError") { return null; } -// throw (e); -// } -// }; -// -// module.exports.waitForAnimationEnd = async(driver) => { -// try { -// return await driver.wait(async() => { -// const { hasClass, hasColor } = await module.exports.testAnimation(driver); -// return !hasClass && !hasColor; -// }, 1000); -// } catch (e) { -// if (e.name === "TimeoutError") { return null; } -// throw (e); -// } -// }; - -// module.exports.testPanel = async(driver, panelId) => { -// driver.setContext(Context.CHROME); -// try { // if we can't find the panel, return false -// return await driver.wait(async() => { -// // need to execute JS, since state is not an HTML attribute, it's a property -// const panelState = await driver.executeAsyncScript((panelIdArg, callback) => { -// const shareButtonPanel = window.document.getElementById(panelIdArg); -// if (shareButtonPanel === null) { -// callback(null); -// } else { -// const state = shareButtonPanel.state; -// callback(state); -// } -// }, panelId); -// return panelState === "open"; -// }, 1000); -// } catch (e) { -// if (e.name === "TimeoutError") { return null; } -// throw e; -// } -// }; - -// module.exports.closePanel = async(driver, target = null) => { -// if (target !== null) { -// target.sendKeys(webdriver.Key.ESCAPE); -// } else { -// const urlbar = await module.exports.promiseUrlBar(driver); -// await urlbar.sendKeys(webdriver.Key.ESCAPE); -// } -// }; diff --git a/testUtils/executeJs.js b/testUtils/executeJs.js new file mode 100644 index 0000000..1e56acd --- /dev/null +++ b/testUtils/executeJs.js @@ -0,0 +1,91 @@ +/* eslint-env node */ + +const firefox = require("selenium-webdriver/firefox"); +const Context = firefox.Context; + +/** + * The tests rely on the add-on's background script opening up + * an extension page in a new window/tab. + * + * In background.js, make it possible for your tests to execute + * the following at some point: + * + * const createData = { + * type: "detached_panel", + * url: "extension-page-for-tests/index.html", + * width: 500, + * height: 500, + * }; + * browser.windows.create(createData); + * + * Extension pages get access to all the same privileged + * WebExtension APIs as the background scripts, allowing us + * to run tests directly against those APIs. + * + * This variable sets the expected path of this extension page so + * that we can check if we are in the right context to run tests. + * + * TODO: Find a cleaner way to accomplish this + * @type {string} + */ +const extensionPagePath = "/extension-page-for-tests/index.html"; + +module.exports.executeJs = { + /** + * Executes JavaScript with access to all the same privileged + * WebExtension APIs as the background scripts, allowing us + * to run tests directly against those APIs. + * + * @param driver + * @param callable + * @returns {Promise<*>} + */ + executeAsyncScriptInExtensionPageForTests: async (driver, callable) => { + driver.setContext(Context.CONTENT); + + const checkIfCurrentlyInExtensionPageWindow = async () => { + let currentUrl = await driver.getCurrentUrl(); + return currentUrl.indexOf(extensionPagePath) > 0; + }; + + const isCurrentlyInExtensionPageWindow = await checkIfCurrentlyInExtensionPageWindow(); + + // Wait for the extension page window to be available + // (we may still be loading firefox/the add-on) + if (!isCurrentlyInExtensionPageWindow) { + await driver.wait( + async function() { + const handles = await driver.getAllWindowHandles(); + return handles.length === 2; + }, + 9000, + "Should have opened a popup", + ); + + const handles = await driver.getAllWindowHandles(); + const currentHandle = await driver.getWindowHandle(); + + // Find the new window handle. + let newWindowHandle = null; + for (const handle of handles) { + if (handle !== currentHandle) { + newWindowHandle = handle; + } + } + + // Switch to the extension page popup + await driver.switchTo().window(newWindowHandle); + + // Check the tab has loaded the right page. + // We use driver.wait to wait for the page to be loaded, since we + // are not able to easily use the load listeners built into selenium. + await driver.wait( + checkIfCurrentlyInExtensionPageWindow, + 10000, + "Should have loaded the extension page for tests", + ); + } + + return await driver.executeAsyncScript(callable); + }, +}; diff --git a/testUtils/nav.js b/testUtils/nav.js new file mode 100644 index 0000000..75a7a40 --- /dev/null +++ b/testUtils/nav.js @@ -0,0 +1,13 @@ +/* eslint-env node */ + +const firefox = require("selenium-webdriver/firefox"); +const Context = firefox.Context; + +module.exports.nav = { + gotoURL: async (driver, url) => { + // navigate to a regular page + driver.setContext(Context.CONTENT); + await driver.get(url); + driver.setContext(Context.CHROME); + }, +}; diff --git a/testUtils/pings.js b/testUtils/pings.js new file mode 100644 index 0000000..68290eb --- /dev/null +++ b/testUtils/pings.js @@ -0,0 +1,101 @@ +/* eslint-env node */ + +module.exports.pings = { + /** Returns array of pings of type `type` in reverse sorted order by timestamp + * first element is most recent ping + * + * as seen in shield-study-addon-util's `utils.jsm` + * options + * - type: string or array of ping types + * - n: positive integer. at most n pings. + * - timestamp: only pings after this timestamp. + * - headersOnly: boolean, just the 'headers' for the pings, not the full bodies. + */ + getTelemetryPings: async(driver, passedOptions) => { + // callback is how you get the return back from the script + return driver.executeAsyncScript(async(options, callback) => { + let { type } = options; + const { n, timestamp, headersOnly } = options; + Components.utils.import("resource://gre/modules/TelemetryArchive.jsm"); + // {type, id, timestampCreated} + let pings = await TelemetryArchive.promiseArchivedPingList(); + if (type) { + if (!(type instanceof Array)) { + type = [type]; // Array-ify if it's a string + } + } + if (type) pings = pings.filter(p => type.includes(p.type)); + + if (timestamp) pings = pings.filter(p => p.timestampCreated > timestamp); + + pings.sort((a, b) => b.timestampCreated - a.timestampCreated); + if (n) pings = pings.slice(0, n); + const pingData = headersOnly + ? pings + : pings.map(ping => TelemetryArchive.promiseArchivedPingById(ping.id)); + + callback(await Promise.all(pingData)); + }, passedOptions); + }, + + getShieldPingsAfterTimestamp: async(driver, ts) => { + return getTelemetryPings(driver, { + type: ["shield-study", "shield-study-addon"], + timestamp: ts, + }); + }, + + summarizePings: pings => { + return pings.map(p => [p.payload.type, p.payload.data]); + }, + + pingsReport: async pings => { + if (pings.length === 0) { + return { report: "No pings found" }; + } + const p0 = pings[0].payload; + // print common fields + const report = + ` +// common fields + +branch ${p0.branch} +study_name ${p0.study_name} +addon_version ${p0.addon_version} +version ${p0.version} + +` + + pings + .map( + (p, i) => `${i} ${p.creationDate} ${p.payload.type} +${JSON.stringify(p.payload.data, null, 2)} + +`, + ) + .join("\n"); + + return { report }; + }, + + searchTelemetry: (conditionArray, telemetryArray) => { + const resultingPings = []; + for (const condition of conditionArray) { + const index = telemetryArray.findIndex(ping => condition(ping)); + if (index === -1) { + throw new this.SearchError(condition); + } + resultingPings.push(telemetryArray[index]); + } + return resultingPings; + }, + + SearchError: class extends Error { + constructor(condition) { + const message = `Could not find ping satisfying condition: ${condition.toString()}`; + super(message); + this.message = message; + this.name = "SearchError"; + } + }, + +}; diff --git a/testUtils/setup.js b/testUtils/setup.js new file mode 100644 index 0000000..44948d3 --- /dev/null +++ b/testUtils/setup.js @@ -0,0 +1,94 @@ +/* eslint-env node */ + +const cmd = require("selenium-webdriver/lib/command"); +const firefox = require("selenium-webdriver/firefox"); +const webdriver = require("selenium-webdriver"); +const FxRunnerUtils = require("fx-runner/lib/utils"); +const Fs = require("fs-extra"); +const path = require("path"); +const Context = firefox.Context; + +// useful if we need to test on a specific version of Firefox +async function promiseActualBinary(binary) { + try { + let normalizedBinary = await FxRunnerUtils.normalizeBinary(binary); + normalizedBinary = path.resolve(normalizedBinary); + await Fs.stat(normalizedBinary); + return normalizedBinary; + } catch (ex) { + if (ex.code === "ENOENT") { + throw new Error(`Could not find ${binary}`); + } + throw ex; + } +} + +module.exports.setup = { + /** + * Uses process.env.FIREFOX_BINARY + */ + promiseSetupDriver: async FIREFOX_PREFERENCES => { + const profile = new firefox.Profile(); + + // TODO, allow 'actually send telemetry' here. + Object.keys(FIREFOX_PREFERENCES).forEach(key => { + profile.setPreference(key, FIREFOX_PREFERENCES[key]); + }); + + // TODO glind, allow config to re-use profile + const options = new firefox.Options(); + options.setProfile(profile); + + const builder = new webdriver.Builder() + .forBrowser("firefox") + .setFirefoxOptions(options); + + const binaryLocation = await promiseActualBinary( + process.env.FIREFOX_BINARY || "firefox", + ); + await options.setBinary(new firefox.Binary(binaryLocation)); + const driver = await builder.build(); + // Firefox will be started up by now + driver.setContext(Context.CHROME); + return driver; + }, + + installAddon: async (driver, fileLocation) => { + // references: + // https://bugzilla.mozilla.org/show_bug.cgi?id=1298025 + // https://github.com/mozilla/geckodriver/releases/tag/v0.17.0 + fileLocation = + fileLocation || path.join(process.cwd(), process.env.ADDON_ZIP); + + const executor = driver.getExecutor(); + executor.defineCommand( + "installAddon", + "POST", + "/session/:sessionId/moz/addon/install", + ); + const installCmd = new cmd.Command("installAddon"); + + const session = await driver.getSession(); + installCmd.setParameters({ + sessionId: session.getId(), + path: fileLocation, + temporary: true, + }); + await executor.execute(installCmd); + console.log(`Add-on at ${fileLocation} installed`); + }, + + uninstallAddon: async (driver, id) => { + const executor = driver.getExecutor(); + executor.defineCommand( + "uninstallAddon", + "POST", + "/session/:sessionId/moz/addon/uninstall", + ); + const uninstallCmd = new cmd.Command("uninstallAddon"); + + const session = await driver.getSession(); + uninstallCmd.setParameters({ sessionId: session.getId(), id }); + await executor.execute(uninstallCmd); + }, +}; diff --git a/testUtils/ui.js b/testUtils/ui.js new file mode 100644 index 0000000..f4ac318 --- /dev/null +++ b/testUtils/ui.js @@ -0,0 +1,121 @@ +/* eslint-env node */ + +const webdriver = require("selenium-webdriver"); +const firefox = require("selenium-webdriver/firefox"); +const Context = firefox.Context; +const until = webdriver.until; + +/* Firefox UI testing helper functions */ +module.exports.ui = { + + promiseManifest: async () => { + const manifestJson = await Fs.readFile( + path.resolve("src/manifest.json"), + "utf8", + ); + return JSON.parse(manifestJson); + }, + + /** + * The widget id is used to identify add-on specific chrome elements. Examples: + * - Browser action - {addonWidgetId}-browser-action + * - Page action - {addonWidgetId}-page-action + * Search for makeWidgetId(extension.id) in the Firefox source code for more examples. + * @returns {Promise<*>} + */ + addonWidgetId: async () => { + /** + * From firefox/browser/components/extensions/ExtensionPopups.jsm + */ + function makeWidgetId(id) { + id = id.toLowerCase(); + // FIXME: This allows for collisions. + return id.replace(/[^a-z0-9_-]/g, "_"); + } + + const manifest = await promiseManifest(); + return makeWidgetId(manifest.applications.gecko.id); + }, + + takeScreenshot: async (driver, filepath = "./screenshot.png") => { + try { + const data = await driver.takeScreenshot(); + return await Fs.outputFile(filepath, data, "base64"); + } catch (screenshotError) { + throw screenshotError; + } + }, + + MODIFIER_KEY: (function getModifierKey() { + const modifierKey = + process.platform === "darwin" + ? webdriver.Key.COMMAND + : webdriver.Key.CONTROL; + return modifierKey; + })(), + + // TODO glind, this interface feels janky + // this feels like it wants to be $ like. + // not obvious right now, moving on! + getChromeElementBy: class { + static async _get1(driver, method, selector) { + driver.setContext(Context.CHROME); + try { + return await driver.wait( + until.elementLocated(By[method](selector)), + 1000, + ); + } catch (e) { + // if there an error, the button was not found + console.error(e); + return null; + } + } + + static async id(driver, id) { + return this._get1(driver, "id", id); + } + + static async className(driver, className) { + return this._get1(driver, "className", className); + } + + static async tagName(driver, tagName) { + return this._get1(driver, "tagName", tagName); + } + }, + + promiseUrlBar: driver => { + driver.setContext(Context.CHROME); + return driver.wait(until.elementLocated(By.id("urlbar")), 1000); + }, + + // such as: "social-share-button" + addButtonFromCustomizePanel: async (driver, buttonId) => + driver.executeAsyncScript(callback => { + // see https://dxr.mozilla.org/mozilla-central/rev/211d4dd61025c0a40caea7a54c9066e051bdde8c/browser/base/content/browser-social.js#193 + Components.utils.import("resource:///modules/CustomizableUI.jsm"); + CustomizableUI.addWidgetToArea(buttonId, CustomizableUI.AREA_NAVBAR); + callback(); + }), + + removeButtonFromNavbar: async (driver, buttonId) => { + driver.setContext(Context.CONTENT); + try { + await driver.executeAsyncScript(callback => { + Components.utils.import("resource:///modules/CustomizableUI.jsm"); + CustomizableUI.removeWidgetFromArea(buttonId); + callback(); + }); + + // TODO glind fix this, I think this is supposed to prove it's dead. + const button = await this.promiseAddonButton(driver); + return button === null; + } catch (e) { + if (e.name === "TimeoutError") { + return false; + } + throw e; + } + }, +}; diff --git a/testUtils/wip.js b/testUtils/wip.js new file mode 100644 index 0000000..5ccdcca --- /dev/null +++ b/testUtils/wip.js @@ -0,0 +1,98 @@ +/* eslint-env node */ +/* eslint no-console:off */ + +const firefox = require("selenium-webdriver/firefox"); +const Context = firefox.Context; +const until = webdriver.until; + +// TODO glind general wrapper for 'async with callback'? + +/* this is NOT WORKING FOR UNKNOWN HARD TO EXLAIN REASONS +=> Uncaught WebDriverError: InternalError: too much recursion +module.exports.allAddons = async(driver) => { + // callback is how you get the return back from the script + return driver.executeAsyncScript(async(callback,) => { + Components.utils.import("resource://gre/modules/AddonManager.jsm"); + const L = await AddonManager.getAllAddons(); + callback(await L); + }); +}; +*/ + +// TODO glind, specific to share-button-study but useful to demo patterns. +// TODO glind, generalize, document, or destroy + +// module.exports.copyUrlBar = async(driver) => { +// const urlBar = await getChromeElementBy.id(driver,'urlbar'); +// const urlBar = await module.exports.promiseUrlBar(driver); +// await urlBar.sendKeys(webdriver.Key.chord(MODIFIER_KEY, "A")); +// await urlBar.sendKeys(webdriver.Key.chord(MODIFIER_KEY, "C")); +// }; + +// module.exports.testAnimation = async(driver) => { +// const button = await module.exports.promiseAddonButton(driver); +// if (button === null) { return { hasClass: false, hasColor: false }; } +// +// const buttonClassString = await button.getAttribute("class"); +// const buttonColor = await button.getCssValue("background-color"); +// +// const hasClass = buttonClassString.split(" ").includes("social-share-button-on"); +// const hasColor = buttonColor.includes("43, 153, 255"); +// return { hasClass, hasColor }; +// }; + +// module.exports.waitForClassAdded = async(driver) => { +// try { +// const animationTest = await driver.wait(async() => { +// const { hasClass } = await module.exports.testAnimation(driver); +// return hasClass; +// }, 1000); +// return animationTest; +// } catch (e) { +// if (e.name === "TimeoutError") { return null; } +// throw (e); +// } +// }; +// +// module.exports.waitForAnimationEnd = async(driver) => { +// try { +// return await driver.wait(async() => { +// const { hasClass, hasColor } = await module.exports.testAnimation(driver); +// return !hasClass && !hasColor; +// }, 1000); +// } catch (e) { +// if (e.name === "TimeoutError") { return null; } +// throw (e); +// } +// }; + +// module.exports.testPanel = async(driver, panelId) => { +// driver.setContext(Context.CHROME); +// try { // if we can't find the panel, return false +// return await driver.wait(async() => { +// // need to execute JS, since state is not an HTML attribute, it's a property +// const panelState = await driver.executeAsyncScript((panelIdArg, callback) => { +// const shareButtonPanel = window.document.getElementById(panelIdArg); +// if (shareButtonPanel === null) { +// callback(null); +// } else { +// const state = shareButtonPanel.state; +// callback(state); +// } +// }, panelId); +// return panelState === "open"; +// }, 1000); +// } catch (e) { +// if (e.name === "TimeoutError") { return null; } +// throw e; +// } +// }; + +// module.exports.closePanel = async(driver, target = null) => { +// if (target !== null) { +// target.sendKeys(webdriver.Key.ESCAPE); +// } else { +// const urlbar = await module.exports.promiseUrlBar(driver); +// await urlbar.sendKeys(webdriver.Key.ESCAPE); +// } +// }; From ff31a488a4bbdc2035a75d8686476eef779ed935 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Thu, 29 Mar 2018 10:01:27 +0300 Subject: [PATCH 39/50] Formatting + a minor grammar nit --- test-addon/test/functional/shield_utils_test.js | 8 ++++---- testUtils/pings.js | 7 +++---- testUtils/ui.js | 1 - 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/test-addon/test/functional/shield_utils_test.js b/test-addon/test/functional/shield_utils_test.js index 33afdb9..781c79e 100644 --- a/test-addon/test/functional/shield_utils_test.js +++ b/test-addon/test/functional/shield_utils_test.js @@ -14,7 +14,7 @@ describe("Shield Study Add-on Utils Functional Tests", function() { let driver; - before(async() => { + before(async () => { driver = await utils.setup.promiseSetupDriver(utils.FIREFOX_PREFERENCES); // install the addon (note: returns addon id) await utils.setup.installAddon(driver); @@ -22,7 +22,7 @@ describe("Shield Study Add-on Utils Functional Tests", function() { after(() => driver.quit()); - it("should be able to access window.browser from the extension page for tests", async() => { + it("should be able to access window.browser from the extension page for tests", async () => { const hasAccessToWebExtensionApi = await utils.executeJs.executeAsyncScriptInExtensionPageForTests( driver, async callback => { @@ -32,7 +32,7 @@ describe("Shield Study Add-on Utils Functional Tests", function() { assert(hasAccessToWebExtensionApi); }); - it("should be able to access shieldUtils WebExtensions API from the extension page for tests", async() => { + it("should be able to access shieldUtils WebExtensions API from the extension page for tests", async () => { const hasAccessToShieldUtilsWebExtensionApi = await utils.executeJs.executeAsyncScriptInExtensionPageForTests( driver, async callback => { @@ -216,7 +216,7 @@ describe("Shield Study Add-on Utils Functional Tests", function() { assert(!activeExperiments.hasOwnProperty("shield-utils-test")); }); - describe("test the opening of a URL at the end of the study", function() { + describe("test the opening of an URL at the end of the study", function() { it("should open a new tab", async () => { const newTabOpened = await driver.wait(async () => { const handles = await driver.getAllWindowHandles(); diff --git a/testUtils/pings.js b/testUtils/pings.js index 68290eb..52d095f 100644 --- a/testUtils/pings.js +++ b/testUtils/pings.js @@ -11,9 +11,9 @@ module.exports.pings = { * - timestamp: only pings after this timestamp. * - headersOnly: boolean, just the 'headers' for the pings, not the full bodies. */ - getTelemetryPings: async(driver, passedOptions) => { + getTelemetryPings: async (driver, passedOptions) => { // callback is how you get the return back from the script - return driver.executeAsyncScript(async(options, callback) => { + return driver.executeAsyncScript(async (options, callback) => { let { type } = options; const { n, timestamp, headersOnly } = options; Components.utils.import("resource://gre/modules/TelemetryArchive.jsm"); @@ -38,7 +38,7 @@ module.exports.pings = { }, passedOptions); }, - getShieldPingsAfterTimestamp: async(driver, ts) => { + getShieldPingsAfterTimestamp: async (driver, ts) => { return getTelemetryPings(driver, { type: ["shield-study", "shield-study-addon"], timestamp: ts, @@ -97,5 +97,4 @@ ${JSON.stringify(p.payload.data, null, 2)} this.name = "SearchError"; } }, - }; diff --git a/testUtils/ui.js b/testUtils/ui.js index f4ac318..e397c0b 100644 --- a/testUtils/ui.js +++ b/testUtils/ui.js @@ -7,7 +7,6 @@ const until = webdriver.until; /* Firefox UI testing helper functions */ module.exports.ui = { - promiseManifest: async () => { const manifestJson = await Fs.readFile( path.resolve("src/manifest.json"), From 4c6242c15996d75a0107803c43073bc844badeef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Thu, 29 Mar 2018 10:03:20 +0300 Subject: [PATCH 40/50] Cleaned up background script, clarified extension page and let execution of background script logic be initiated by messages --- test-addon/src/background.js | 76 ++++++++++--------- .../src/extension-page-for-tests/index.html | 32 +++++++- .../src/extension-page-for-tests/page.js | 9 +++ 3 files changed, 82 insertions(+), 35 deletions(-) create mode 100644 test-addon/src/extension-page-for-tests/page.js diff --git a/test-addon/src/background.js b/test-addon/src/background.js index 2661dfa..6d1eb1a 100644 --- a/test-addon/src/background.js +++ b/test-addon/src/background.js @@ -7,9 +7,10 @@ class Study { constructor(variation) {} // Will run only during first install attempt + // Use web extension experiments to get whatever prefs, add-ons, + // telemetry, anything necessary for the check static async isEligible() { - // get whatever prefs, addons, telemetry, anything! - // Cu.import can see 'firefox things', but not package things. + //browser.prefs.get('my.favorite.pref'); return true; } @@ -20,51 +21,58 @@ class Study { } } -async function runOnce() { - //browser.prefs.get('my.favorite.pref'); - - // Set dynamic study configuration flags - // TODO - studySetup.eligible = await Study.isEligible(); - studySetup.expired = await Study.hasExpired(); - // Ensure we have configured shieldUtils and are supposed to run our feature - await browser.shieldUtils.bootstrapStudy(studySetup); - // Get study variation - const { variation } = await browser.shieldUtils.info(); - // Initiate the study - new Study(variation); -} - -/** - * Fired when a profile that has this extension installed first starts up. - * This event is not fired when a private browsing/incognito profile is started. - */ -function handleStartup() { - console.log("handleStartup", arguments); -} - -browser.runtime.onStartup.addListener(handleStartup); - /** * Fired when the extension is first installed, when the extension is updated * to a new version, and when the browser is updated to a new version. * @param details */ function handleInstalled(details) { - console.log("handleInstalled", details.reason, details); + console.log( + "The 'handleInstalled' event was fired.", + details.reason, + details, + ); } -browser.runtime.onInstalled.addListener(handleInstalled); +/** + * Fired when a profile that has this extension installed first starts up. + * This event is not fired when a private browsing/incognito profile is started. + */ +async function handleStartup() { + console.log("The 'handleStartup' event was fired.", arguments); +} // todo: on shutdown -// Run shutdown-related non-priviliged code +// Run shutdown-related non-privileged code -// actually start -//runOnce(); +browser.runtime.onStartup.addListener(handleStartup); +browser.runtime.onInstalled.addListener(handleInstalled); + +async function initiateStudy() { + // Set dynamic study configuration flags + studySetup.eligible = await Study.isEligible(); + studySetup.expired = await Study.hasExpired(); + // Ensure we have configured shieldUtils and are supposed to run our feature + await browser.shieldUtils.bootstrapStudy(studySetup); + // Get study variation + const { variation } = await browser.shieldUtils.info(); + // Initiate the study + new Study(variation); +} -// run things here via tests instead -// required for right context to be available to selenium +// Since this is a test-addon, we don't initiate any code directly, but wait +// for events sent by tests. This allows us to control and test the execution +// properly. +browser.runtime.onMessage.addListener((request, sender, sendResponse) => { + console.log("request", request); + if (request === "test:initiateStudy") { + initiateStudy(); + } +}); +// The tests that probe the web extensions APIs directly rely on an extension +// page opening up in a new window/tab. +// For more information, see shield-studies-addon-utils/testUtils/executeJs.js const createData = { type: "detached_panel", url: "extension-page-for-tests/index.html", diff --git a/test-addon/src/extension-page-for-tests/index.html b/test-addon/src/extension-page-for-tests/index.html index 7fa58a6..22c39b4 100644 --- a/test-addon/src/extension-page-for-tests/index.html +++ b/test-addon/src/extension-page-for-tests/index.html @@ -1 +1,31 @@ -extension page + + + + + + + + + +

Shield Study Utils Test Add-on

+

This is an extension page for shield-studies-addon-utils/test-addon

+ +

This add-on initiates no background logic on it's own, so that the tests + can test each lifecycle event in isolation.

+ +

For manual testing, use the buttons below to run the code + corresponding to each lifecycle event.

+ +

+ +

+ + + + diff --git a/test-addon/src/extension-page-for-tests/page.js b/test-addon/src/extension-page-for-tests/page.js new file mode 100644 index 0000000..3e27ec9 --- /dev/null +++ b/test-addon/src/extension-page-for-tests/page.js @@ -0,0 +1,9 @@ +document.addEventListener("click", async e => { + function handleError(error) { + console.error(error); + } + + if (e.target.id === "initiateStudy-button") { + await browser.runtime.sendMessage("test:initiateStudy"); + } +}); From 7da6b5eca686a31e4573d2a9d608ae0b2f967d8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Thu, 29 Mar 2018 15:10:16 +0300 Subject: [PATCH 41/50] Moved test-addon into example folder and shield-study-helper-addon into misc folder --- .../bin/bundle-shield-studies-addon-utils.sh | 0 {test-addon => examples/test-addon}/dist/.gitignore | 0 .../test-addon}/src/background.js | 0 .../src/extension-page-for-tests/index.html | 0 .../src/extension-page-for-tests/page.js | 0 .../test-addon}/src/icons/LICENSE | 0 .../test-addon}/src/icons/shield-icon.svg | 0 .../test-addon}/src/manifest.json | 0 .../test-addon}/src/privileged/.gitignore | 0 .../test-addon}/src/studySetup.js | 0 .../test/functional/shield_utils_test.js | 0 .../test-addon}/test/functional/utils.js | 0 .../test-addon}/web-ext-config.js | 0 .../shield-study-helper-addon}/addon/bootstrap.js | 0 .../addon/chrome.manifest | 0 .../shield-study-helper-addon}/addon/install.rdf | 0 .../addon/webextension/.eslintrc.json | 0 .../addon/webextension/icon.png | Bin .../addon/webextension/manifest.json | 0 .../addon/webextension/qa.html | 0 .../addon/webextension/qa.js | 0 .../shield-study-helper-addon}/build.sh | 0 .../shield-study-helper-addon}/package-lock.json | 0 .../shield-study-helper-addon}/package.json | 0 .../shield-study-helper-addon}/run-firefox.js | 0 25 files changed, 0 insertions(+), 0 deletions(-) rename {test-addon => examples/test-addon}/bin/bundle-shield-studies-addon-utils.sh (100%) rename {test-addon => examples/test-addon}/dist/.gitignore (100%) rename {test-addon => examples/test-addon}/src/background.js (100%) rename {test-addon => examples/test-addon}/src/extension-page-for-tests/index.html (100%) rename {test-addon => examples/test-addon}/src/extension-page-for-tests/page.js (100%) rename {test-addon => examples/test-addon}/src/icons/LICENSE (100%) rename {test-addon => examples/test-addon}/src/icons/shield-icon.svg (100%) rename {test-addon => examples/test-addon}/src/manifest.json (100%) rename {test-addon => examples/test-addon}/src/privileged/.gitignore (100%) rename {test-addon => examples/test-addon}/src/studySetup.js (100%) rename {test-addon => examples/test-addon}/test/functional/shield_utils_test.js (100%) rename {test-addon => examples/test-addon}/test/functional/utils.js (100%) rename {test-addon => examples/test-addon}/web-ext-config.js (100%) rename {shield-study-helper-addon => misc/shield-study-helper-addon}/addon/bootstrap.js (100%) rename {shield-study-helper-addon => misc/shield-study-helper-addon}/addon/chrome.manifest (100%) rename {shield-study-helper-addon => misc/shield-study-helper-addon}/addon/install.rdf (100%) rename {shield-study-helper-addon => misc/shield-study-helper-addon}/addon/webextension/.eslintrc.json (100%) rename {shield-study-helper-addon => misc/shield-study-helper-addon}/addon/webextension/icon.png (100%) rename {shield-study-helper-addon => misc/shield-study-helper-addon}/addon/webextension/manifest.json (100%) rename {shield-study-helper-addon => misc/shield-study-helper-addon}/addon/webextension/qa.html (100%) rename {shield-study-helper-addon => misc/shield-study-helper-addon}/addon/webextension/qa.js (100%) rename {shield-study-helper-addon => misc/shield-study-helper-addon}/build.sh (100%) rename {shield-study-helper-addon => misc/shield-study-helper-addon}/package-lock.json (100%) rename {shield-study-helper-addon => misc/shield-study-helper-addon}/package.json (100%) rename {shield-study-helper-addon => misc/shield-study-helper-addon}/run-firefox.js (100%) diff --git a/test-addon/bin/bundle-shield-studies-addon-utils.sh b/examples/test-addon/bin/bundle-shield-studies-addon-utils.sh similarity index 100% rename from test-addon/bin/bundle-shield-studies-addon-utils.sh rename to examples/test-addon/bin/bundle-shield-studies-addon-utils.sh diff --git a/test-addon/dist/.gitignore b/examples/test-addon/dist/.gitignore similarity index 100% rename from test-addon/dist/.gitignore rename to examples/test-addon/dist/.gitignore diff --git a/test-addon/src/background.js b/examples/test-addon/src/background.js similarity index 100% rename from test-addon/src/background.js rename to examples/test-addon/src/background.js diff --git a/test-addon/src/extension-page-for-tests/index.html b/examples/test-addon/src/extension-page-for-tests/index.html similarity index 100% rename from test-addon/src/extension-page-for-tests/index.html rename to examples/test-addon/src/extension-page-for-tests/index.html diff --git a/test-addon/src/extension-page-for-tests/page.js b/examples/test-addon/src/extension-page-for-tests/page.js similarity index 100% rename from test-addon/src/extension-page-for-tests/page.js rename to examples/test-addon/src/extension-page-for-tests/page.js diff --git a/test-addon/src/icons/LICENSE b/examples/test-addon/src/icons/LICENSE similarity index 100% rename from test-addon/src/icons/LICENSE rename to examples/test-addon/src/icons/LICENSE diff --git a/test-addon/src/icons/shield-icon.svg b/examples/test-addon/src/icons/shield-icon.svg similarity index 100% rename from test-addon/src/icons/shield-icon.svg rename to examples/test-addon/src/icons/shield-icon.svg diff --git a/test-addon/src/manifest.json b/examples/test-addon/src/manifest.json similarity index 100% rename from test-addon/src/manifest.json rename to examples/test-addon/src/manifest.json diff --git a/test-addon/src/privileged/.gitignore b/examples/test-addon/src/privileged/.gitignore similarity index 100% rename from test-addon/src/privileged/.gitignore rename to examples/test-addon/src/privileged/.gitignore diff --git a/test-addon/src/studySetup.js b/examples/test-addon/src/studySetup.js similarity index 100% rename from test-addon/src/studySetup.js rename to examples/test-addon/src/studySetup.js diff --git a/test-addon/test/functional/shield_utils_test.js b/examples/test-addon/test/functional/shield_utils_test.js similarity index 100% rename from test-addon/test/functional/shield_utils_test.js rename to examples/test-addon/test/functional/shield_utils_test.js diff --git a/test-addon/test/functional/utils.js b/examples/test-addon/test/functional/utils.js similarity index 100% rename from test-addon/test/functional/utils.js rename to examples/test-addon/test/functional/utils.js diff --git a/test-addon/web-ext-config.js b/examples/test-addon/web-ext-config.js similarity index 100% rename from test-addon/web-ext-config.js rename to examples/test-addon/web-ext-config.js diff --git a/shield-study-helper-addon/addon/bootstrap.js b/misc/shield-study-helper-addon/addon/bootstrap.js similarity index 100% rename from shield-study-helper-addon/addon/bootstrap.js rename to misc/shield-study-helper-addon/addon/bootstrap.js diff --git a/shield-study-helper-addon/addon/chrome.manifest b/misc/shield-study-helper-addon/addon/chrome.manifest similarity index 100% rename from shield-study-helper-addon/addon/chrome.manifest rename to misc/shield-study-helper-addon/addon/chrome.manifest diff --git a/shield-study-helper-addon/addon/install.rdf b/misc/shield-study-helper-addon/addon/install.rdf similarity index 100% rename from shield-study-helper-addon/addon/install.rdf rename to misc/shield-study-helper-addon/addon/install.rdf diff --git a/shield-study-helper-addon/addon/webextension/.eslintrc.json b/misc/shield-study-helper-addon/addon/webextension/.eslintrc.json similarity index 100% rename from shield-study-helper-addon/addon/webextension/.eslintrc.json rename to misc/shield-study-helper-addon/addon/webextension/.eslintrc.json diff --git a/shield-study-helper-addon/addon/webextension/icon.png b/misc/shield-study-helper-addon/addon/webextension/icon.png similarity index 100% rename from shield-study-helper-addon/addon/webextension/icon.png rename to misc/shield-study-helper-addon/addon/webextension/icon.png diff --git a/shield-study-helper-addon/addon/webextension/manifest.json b/misc/shield-study-helper-addon/addon/webextension/manifest.json similarity index 100% rename from shield-study-helper-addon/addon/webextension/manifest.json rename to misc/shield-study-helper-addon/addon/webextension/manifest.json diff --git a/shield-study-helper-addon/addon/webextension/qa.html b/misc/shield-study-helper-addon/addon/webextension/qa.html similarity index 100% rename from shield-study-helper-addon/addon/webextension/qa.html rename to misc/shield-study-helper-addon/addon/webextension/qa.html diff --git a/shield-study-helper-addon/addon/webextension/qa.js b/misc/shield-study-helper-addon/addon/webextension/qa.js similarity index 100% rename from shield-study-helper-addon/addon/webextension/qa.js rename to misc/shield-study-helper-addon/addon/webextension/qa.js diff --git a/shield-study-helper-addon/build.sh b/misc/shield-study-helper-addon/build.sh similarity index 100% rename from shield-study-helper-addon/build.sh rename to misc/shield-study-helper-addon/build.sh diff --git a/shield-study-helper-addon/package-lock.json b/misc/shield-study-helper-addon/package-lock.json similarity index 100% rename from shield-study-helper-addon/package-lock.json rename to misc/shield-study-helper-addon/package-lock.json diff --git a/shield-study-helper-addon/package.json b/misc/shield-study-helper-addon/package.json similarity index 100% rename from shield-study-helper-addon/package.json rename to misc/shield-study-helper-addon/package.json diff --git a/shield-study-helper-addon/run-firefox.js b/misc/shield-study-helper-addon/run-firefox.js similarity index 100% rename from shield-study-helper-addon/run-firefox.js rename to misc/shield-study-helper-addon/run-firefox.js From 4b23202ce7b81f79a94de7117eac3e0b8611bb7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Thu, 29 Mar 2018 16:07:13 +0300 Subject: [PATCH 42/50] Updated the readme to reflect version 5 --- README.md | 94 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 56 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 681e2fb..7c799f2 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,17 @@ | [Add-on template](https://github.com/mozilla/shield-studies-addon-template/) | [Engineering hints](#engineering-and-process) | [More documentation](./docs/) | [Shield - Mozilla Wiki](https://wiki.mozilla.org/Firefox/Shield) | | ---------------------------------------------------------------------------- | --------------------------------------------- | ----------------------------- | ---------------------------------------------------------------- | - # Shield Studies Add-on Utils [![Build Status](https://travis-ci.org/mozilla/shield-studies-addon-utils.svg?branch=master)](https://travis-ci.org/mozilla/shield-studies-addon-utils) -A Firefox WebExtension Experiments API to be bundled with shield study add-ons (`browser.shieldUtils.*`). Provides these capabilities: - -1. **Suggest variation for a client** (Deterministically! i.e. based on a hash of non-PII user info, they will always get assigned to the same branch every time the study launches) -2. **Report study lifecycle data** using Telemetry -3. **Report feature interaction and success data** using Telemetry -4. **Registers/uregisters the study as an active experiment** (By annotating the Telemetry Environment, marking the user as special in the `main` ping). -5. **Validates schema for study config** -6. **Handles study endings** (endStudy method bundles lots of tasks in one, including appending survey URLs specified in Config.jsm with query strings/sending the user to a survey and uninstalling the add-on) - -The pings end up in the `shield-study` and `shield-study-addon` Telemetry buckets for faster analysis. - -Allows add-on developers to build [Shield Study](https://wiki.mozilla.org/Firefox/Shield/Shield_Studies) ([Normandy](https://wiki.mozilla.org/Firefox/Shield#Normandy_-_User_Profile_Matching_and_Recipe_Deployment)) compatible add-ons without having to think very much. +APIs and tooling that allows add-on developers to build [Shield/Pioneer](https://wiki.mozilla.org/Firefox/Shield/Shield_Studies) ([Normandy](https://wiki.mozilla.org/Firefox/Shield#Normandy_-_User_Profile_Matching_and_Recipe_Deployment)) study add-ons efficiently. -## What You are Building +## Overview -* You are building a [legacy add-on](https://developer.mozilla.org/Add-ons/Legacy_add_ons). To deploy these after 57, you will need the magic special signing. -* Shield study add-ons can not be based on Web Extensions [yet](https://github.com/mozilla/shield-studies-addon-utils/issues/45). -* Jetpack / addon-sdk is not at all supported since v4 of this utils library. +* `webExtensionApis` - Firefox WebExtension Experiments APIs providing capabilities for study add-ons that are yet not available in the built-in WebExtension APIs +* `testUtils` - Test utils (helper classes to write functional/unit tests for your study add-on) +* `examples` - Tested and verified example add-ons using the WebExtension Experiments APIs and test utils ## Get started @@ -35,48 +23,78 @@ Check out [mozilla/shield-studies-addon-template/](https://github.com/mozilla/sh npm install --save-dev shield-studies-addon-utils ``` -Copy `dist/api.js` and `schema.json` to your add-on's source directory under `privileged/shieldUtils`, then add-the following to your add-on's manifest.json: +## Engineering and Process + +* [Shield article on Mozilla Wiki](https://wiki.mozilla.org/Firefox/Shield) +* [Shield Studies article on Mozilla Wiki](https://wiki.mozilla.org/Firefox/Shield/Shield_Studies) +* [mozilla/shield-studies-addon-template/](https://github.com/mozilla/shield-studies-addon-template/) +* [Current work-in-progress docs and launch process](https://github.com/mozilla/shield-studies-addon-utils/issues/93) +* [Long, rambling engineering docs](./docs/engineering.md) +* Come to slack: #shield + +## WebExtension APIs + +### `browser.study.*` + +Provides these capabilities: + +1. **Suggest variation for a client** (Deterministically! i.e. based on a hash of non-PII user info, they will always get assigned to the same branch every time the study launches) +2. **Report study lifecycle data** using Telemetry +3. **Report feature interaction and success data** using Telemetry +4. **Registers/unregisters the study as an active experiment** (By annotating the Telemetry Environment, marking the user as special in the `main` ping). +5. **Validates schema for study config** +6. **Handles study endings** (endStudy method bundles lots of tasks in one, including appending survey URLs specified in Config.jsm with query strings/sending the user to a survey and uninstalling the add-on) + +To use, copy `webExtensionApis/study/api.js` and `webExtensionApis/study/schema.json` to your add-on's source directory under `privileged/study`, then add-the following to your add-on's manifest.json: ``` "experiment_apis": { - "shieldUtils": { - "schema": "./privileged/shieldUtils/schema.json", + "study": { + "schema": "./privileged/study/schema.json", "parent": { "scopes": ["addon_parent"], - "script": "./privileged/shieldUtils/api.js", - "paths": [["shieldUtils"]] + "script": "./privileged/study/api.js", + "paths": [["study"]] } } }, ``` -## Engineering and Process +#### Data processing pipelines -* [Shield article on Mozilla Wiki](https://wiki.mozilla.org/Firefox/Shield) -* [Shield Studies article on Mozilla Wiki](https://wiki.mozilla.org/Firefox/Shield/Shield_Studies) -* [mozilla/shield-studies-addon-template/](https://github.com/mozilla/shield-studies-addon-template/) -* [Current work-in-progress docs and launch process](https://github.com/mozilla/shield-studies-addon-utils/issues/93) -* [Long, rambling engineering docs](./docs/engineering.md) -* Come to slack: #shield +Depending on which data processing pipeline the study add-on is configured to use, the pings end up in different destinations: + +* `parquet` - The pings end up in the `shield-study` and `shield-study-addon` Telemetry buckets for faster analysis. +* `pioneer` - The pings are encrypted and end up in the Pioneer processing pipeline +* `custom-telemetry-events` - The pings end up in the ordinary destination for custom telemetry events + +To use, copy and adjust the files as per the `study` API above. + +### `browser.prefs.*` + +Allows your web extension add-on to set and read preferences. + +## What You are Building + +* You are building . To deploy these after 57, you will need the magic special signing. +* Shield study add-ons can not be based on Web Extensions [yet](https://github.com/mozilla/shield-studies-addon-utils/issues/45). ## Gotchas, Opinions, Side Effects, and Misfeatures 1. No handling of 'timers'. No saved state at all (including the variation name), unless you handle it yourself. - 2. No 'running' pings in v4 (yet). - 3. User disable also uninstalls (and cleans up). ## Development on the Utils -* open an issue -* hack and file a PR +* Open an issue +* Hack and file a PR ## History of major versions -* v5: (In development) API exposed as a Web Extension Experiment +* v5: (In development) API exposed as a Web Extension Experiment. Minimal viable add-on example added. Test coverage improved. Test utils added. * v4.1: Improved utils for common cases -* v4: First `.jsm` release. Uses packet format for PACKET version 3. +* v4: First `.jsm` release for shipping studies as [legacy add-ons](https://developer.mozilla.org/Add-ons/Legacy_add_ons). Used packet format for PACKET version 3. (Jetpack / addon-sdk is not at all supported since v4 of this utils library) * v3: Attempt to formalize on `shield-study` PACKET version 3. Jetpack based. Prototype used for `raymak/page-reload`. All work abandoned, and no formal npm release in this series. Work done at `v3-shield-packet-format` branch. LAST JETPACK (addon-sdk) RELEASE. * v2: Code refactor to es6 `class` with event models. Added cli tooling. Packet format is still arbitrary and per-study. Jetpack based. Last used in studies in Q2 2017. * v1: Initial work and thinking. Telemetry packets are rather arbitrary. Jetpack based. @@ -85,6 +103,6 @@ Copy `dist/api.js` and `schema.json` to your add-on's source directory under `pr Repositories that should not be used as templates for new studies: - - The incubation repo for the updated structure and contents of this repo, ported to the official template in late 2017. - - A repository that was created in 2017 to help new Shield/Pioneer engineers to quickly get up and running with a Shield add-on, built upon an older and much more verbose add-on template. It's documentation has been ported to the official template repo. - - Despite its name, this repo is for static amo consent pages and does not contain any template for Shield studies +* - The incubation repo for the updated structure and contents of the template repo, ported to the official template in late 2017. +* - A repository that was created in 2017 to help new Shield/Pioneer engineers to quickly get up and running with a Shield add-on, built upon an older and much more verbose add-on template. It's documentation has been ported to the official template repo. +* - Despite its name, this repo is for static amo consent pages and does not contain any template for Shield studies From 6adda9285e531184b65522a900c2859e31f0c486 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Thu, 29 Mar 2018 16:12:45 +0300 Subject: [PATCH 43/50] Corrected npm install command in readme (should be installed as runtime dependency since the experimental APIs are used at runtime --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7c799f2..3063e9b 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Check out [mozilla/shield-studies-addon-template/](https://github.com/mozilla/sh ## Installing the utils in your add-on ``` -npm install --save-dev shield-studies-addon-utils +npm install --save shield-studies-addon-utils ``` ## Engineering and Process From cb990d2806d8791bb41d9bc4fd42afb7aad6ab0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Mon, 2 Apr 2018 23:05:16 +0300 Subject: [PATCH 44/50] Changed webextension api namespace from shieldUtils to study --- .../test-addon/bin/bundle-shield-studies-addon-utils.sh | 8 ++++---- examples/test-addon/src/background.js | 6 +++--- examples/test-addon/src/manifest.json | 8 ++++---- examples/test-addon/src/privileged/.gitignore | 2 +- examples/test-addon/test/functional/shield_utils_test.js | 4 ++-- package.json | 2 +- webExtensionApis/{shieldUtils => study}/.gitignore | 0 webExtensionApis/{shieldUtils => study}/schema.json | 4 ++-- webExtensionApis/{shieldUtils => study}/src/index.js | 6 +++--- webExtensionApis/{shieldUtils => study}/src/jsonschema.js | 0 webExtensionApis/{shieldUtils => study}/src/sampling.js | 0 .../{shieldUtils => study}/src/schemas/index.js | 0 .../src/schemas/schema.studySetup.json | 0 .../src/schemas/schema.webExtensionMsg.json | 0 .../src/schemas/schema.weightedVariations.json | 0 webExtensionApis/{shieldUtils => study}/src/studyUtils.js | 0 .../{shieldUtils => study}/src/studyUtilsBootstrap.js | 0 webExtensionApis/{shieldUtils => study}/webpack.config.js | 0 18 files changed, 20 insertions(+), 20 deletions(-) rename webExtensionApis/{shieldUtils => study}/.gitignore (100%) rename webExtensionApis/{shieldUtils => study}/schema.json (93%) rename webExtensionApis/{shieldUtils => study}/src/index.js (96%) rename webExtensionApis/{shieldUtils => study}/src/jsonschema.js (100%) rename webExtensionApis/{shieldUtils => study}/src/sampling.js (100%) rename webExtensionApis/{shieldUtils => study}/src/schemas/index.js (100%) rename webExtensionApis/{shieldUtils => study}/src/schemas/schema.studySetup.json (100%) rename webExtensionApis/{shieldUtils => study}/src/schemas/schema.webExtensionMsg.json (100%) rename webExtensionApis/{shieldUtils => study}/src/schemas/schema.weightedVariations.json (100%) rename webExtensionApis/{shieldUtils => study}/src/studyUtils.js (100%) rename webExtensionApis/{shieldUtils => study}/src/studyUtilsBootstrap.js (100%) rename webExtensionApis/{shieldUtils => study}/webpack.config.js (100%) diff --git a/examples/test-addon/bin/bundle-shield-studies-addon-utils.sh b/examples/test-addon/bin/bundle-shield-studies-addon-utils.sh index dd3c041..9ea9201 100755 --- a/examples/test-addon/bin/bundle-shield-studies-addon-utils.sh +++ b/examples/test-addon/bin/bundle-shield-studies-addon-utils.sh @@ -7,10 +7,10 @@ set -o errexit script_path=`dirname $0` cd "$script_path/../../" -# bundle the shieldUtils web extension experiment -mkdir -p test-addon/src/privileged/shieldUtils -cp webExtensionApis/shieldUtils/api.js test-addon/src/privileged/shieldUtils/api.js -cp webExtensionApis/shieldUtils/schema.json test-addon/src/privileged/shieldUtils/schema.json +# bundle the study web extension experiment +mkdir -p test-addon/src/privileged/study +cp webExtensionApis/study/api.js test-addon/src/privileged/study/api.js +cp webExtensionApis/study/schema.json test-addon/src/privileged/study/schema.json # bundle the prefs web extension experiment mkdir -p test-addon/src/privileged/prefs diff --git a/examples/test-addon/src/background.js b/examples/test-addon/src/background.js index 6d1eb1a..a77cf69 100644 --- a/examples/test-addon/src/background.js +++ b/examples/test-addon/src/background.js @@ -52,10 +52,10 @@ async function initiateStudy() { // Set dynamic study configuration flags studySetup.eligible = await Study.isEligible(); studySetup.expired = await Study.hasExpired(); - // Ensure we have configured shieldUtils and are supposed to run our feature - await browser.shieldUtils.bootstrapStudy(studySetup); + // Ensure we have configured study and are supposed to run our feature + await browser.study.bootstrapStudy(studySetup); // Get study variation - const { variation } = await browser.shieldUtils.info(); + const { variation } = await browser.study.info(); // Initiate the study new Study(variation); } diff --git a/examples/test-addon/src/manifest.json b/examples/test-addon/src/manifest.json index 78d0c1e..7fb2753 100644 --- a/examples/test-addon/src/manifest.json +++ b/examples/test-addon/src/manifest.json @@ -18,12 +18,12 @@ "paths": [["prefs"]] } }, - "shieldUtils": { - "schema": "./privileged/shieldUtils/schema.json", + "study": { + "schema": "./privileged/study/schema.json", "parent": { "scopes": ["addon_parent"], - "script": "./privileged/shieldUtils/api.js", - "paths": [["shieldUtils"]] + "script": "./privileged/study/api.js", + "paths": [["study"]] } } }, diff --git a/examples/test-addon/src/privileged/.gitignore b/examples/test-addon/src/privileged/.gitignore index 04390e0..3c31506 100644 --- a/examples/test-addon/src/privileged/.gitignore +++ b/examples/test-addon/src/privileged/.gitignore @@ -1,3 +1,3 @@ # ignore APIs that are bundled from shield-studies-addon-utils -shieldUtils/ +study/ prefs/ diff --git a/examples/test-addon/test/functional/shield_utils_test.js b/examples/test-addon/test/functional/shield_utils_test.js index 781c79e..c9d7e59 100644 --- a/examples/test-addon/test/functional/shield_utils_test.js +++ b/examples/test-addon/test/functional/shield_utils_test.js @@ -32,11 +32,11 @@ describe("Shield Study Add-on Utils Functional Tests", function() { assert(hasAccessToWebExtensionApi); }); - it("should be able to access shieldUtils WebExtensions API from the extension page for tests", async () => { + it("should be able to access study WebExtensions API from the extension page for tests", async () => { const hasAccessToShieldUtilsWebExtensionApi = await utils.executeJs.executeAsyncScriptInExtensionPageForTests( driver, async callback => { - callback(browser && typeof browser.shieldUtils === "object"); + callback(browser && typeof browser.study === "object"); }, ); assert(hasAccessToShieldUtilsWebExtensionApi); diff --git a/package.json b/package.json index 3c09054..538ba04 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "url": "git://github.com/mozilla/shield-studies-addon-utils.git" }, "scripts": { - "build": "cd webExtensionApis/shieldUtils && webpack", + "build": "cd webExtensionApis/study && webpack", "docformat": "doctoc --title '**Contents**' docs/*.md && prettier '**/*.md' --write", "eslint": "eslint src --ext jsm --ext js --ext json", "eslint-fix": "npm run eslint -- --fix", diff --git a/webExtensionApis/shieldUtils/.gitignore b/webExtensionApis/study/.gitignore similarity index 100% rename from webExtensionApis/shieldUtils/.gitignore rename to webExtensionApis/study/.gitignore diff --git a/webExtensionApis/shieldUtils/schema.json b/webExtensionApis/study/schema.json similarity index 93% rename from webExtensionApis/shieldUtils/schema.json rename to webExtensionApis/study/schema.json index a7df523..2149c00 100644 --- a/webExtensionApis/shieldUtils/schema.json +++ b/webExtensionApis/study/schema.json @@ -1,7 +1,7 @@ [ { - "namespace": "shieldUtils", - "description": "shieldUtils", + "namespace": "study", + "description": "study", "functions": [ { "name": "bootstrapStudy", diff --git a/webExtensionApis/shieldUtils/src/index.js b/webExtensionApis/study/src/index.js similarity index 96% rename from webExtensionApis/shieldUtils/src/index.js rename to webExtensionApis/study/src/index.js index 8098222..4a5b4cf 100644 --- a/webExtensionApis/shieldUtils/src/index.js +++ b/webExtensionApis/study/src/index.js @@ -2,7 +2,7 @@ /* global ExtensionAPI */ -this.shieldUtils = class extends ExtensionAPI { +this.study = class extends ExtensionAPI { /** * We don't need to override the constructor for other * reasons than to clarify the class member "extension" @@ -39,9 +39,9 @@ this.shieldUtils = class extends ExtensionAPI { const { extension } = this; return { - shieldUtils: { + study: { /** - * ensure we have configured shieldUtils + * ensure we have configured study * and are supposed to run our feature * @returns {Promise} */ diff --git a/webExtensionApis/shieldUtils/src/jsonschema.js b/webExtensionApis/study/src/jsonschema.js similarity index 100% rename from webExtensionApis/shieldUtils/src/jsonschema.js rename to webExtensionApis/study/src/jsonschema.js diff --git a/webExtensionApis/shieldUtils/src/sampling.js b/webExtensionApis/study/src/sampling.js similarity index 100% rename from webExtensionApis/shieldUtils/src/sampling.js rename to webExtensionApis/study/src/sampling.js diff --git a/webExtensionApis/shieldUtils/src/schemas/index.js b/webExtensionApis/study/src/schemas/index.js similarity index 100% rename from webExtensionApis/shieldUtils/src/schemas/index.js rename to webExtensionApis/study/src/schemas/index.js diff --git a/webExtensionApis/shieldUtils/src/schemas/schema.studySetup.json b/webExtensionApis/study/src/schemas/schema.studySetup.json similarity index 100% rename from webExtensionApis/shieldUtils/src/schemas/schema.studySetup.json rename to webExtensionApis/study/src/schemas/schema.studySetup.json diff --git a/webExtensionApis/shieldUtils/src/schemas/schema.webExtensionMsg.json b/webExtensionApis/study/src/schemas/schema.webExtensionMsg.json similarity index 100% rename from webExtensionApis/shieldUtils/src/schemas/schema.webExtensionMsg.json rename to webExtensionApis/study/src/schemas/schema.webExtensionMsg.json diff --git a/webExtensionApis/shieldUtils/src/schemas/schema.weightedVariations.json b/webExtensionApis/study/src/schemas/schema.weightedVariations.json similarity index 100% rename from webExtensionApis/shieldUtils/src/schemas/schema.weightedVariations.json rename to webExtensionApis/study/src/schemas/schema.weightedVariations.json diff --git a/webExtensionApis/shieldUtils/src/studyUtils.js b/webExtensionApis/study/src/studyUtils.js similarity index 100% rename from webExtensionApis/shieldUtils/src/studyUtils.js rename to webExtensionApis/study/src/studyUtils.js diff --git a/webExtensionApis/shieldUtils/src/studyUtilsBootstrap.js b/webExtensionApis/study/src/studyUtilsBootstrap.js similarity index 100% rename from webExtensionApis/shieldUtils/src/studyUtilsBootstrap.js rename to webExtensionApis/study/src/studyUtilsBootstrap.js diff --git a/webExtensionApis/shieldUtils/webpack.config.js b/webExtensionApis/study/webpack.config.js similarity index 100% rename from webExtensionApis/shieldUtils/webpack.config.js rename to webExtensionApis/study/webpack.config.js From 4e01d6ed2e994a1c13f2e29454a3ee1447633801 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Tue, 3 Apr 2018 00:01:50 +0300 Subject: [PATCH 45/50] Smaller improvements --- .circleci/config.yml | 2 +- Dockerfile | 2 +- README.md | 2 +- webExtensionApis/prefs/api.js | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b433f85..476b7cc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -20,7 +20,7 @@ jobs: - run: name: webpack and eslint - command: npm run dist + command: npm run build - run: name: Build .XPI diff --git a/Dockerfile b/Dockerfile index 86a8aeb..4c8425d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,4 +8,4 @@ RUN apt-get update -y && \ apt-get install -y zip firefox xvfb nodejs xsel git ssh openbox && \ npm install -g npm@5.3.0 -ENV PATH="/shield-study-utils/node_modules/.bin:$PATH" +ENV PATH="/shield-study-addon-utils/node_modules/.bin:$PATH" diff --git a/README.md b/README.md index 3063e9b..6f0d1a6 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ To use, copy `webExtensionApis/study/api.js` and `webExtensionApis/study/schema. Depending on which data processing pipeline the study add-on is configured to use, the pings end up in different destinations: -* `parquet` - The pings end up in the `shield-study` and `shield-study-addon` Telemetry buckets for faster analysis. +* `shield-parquet` - The pings end up in the `shield-study` and `shield-study-addon` Telemetry buckets for faster analysis. * `pioneer` - The pings are encrypted and end up in the Pioneer processing pipeline * `custom-telemetry-events` - The pings end up in the ordinary destination for custom telemetry events diff --git a/webExtensionApis/prefs/api.js b/webExtensionApis/prefs/api.js index 6b62101..aef5915 100644 --- a/webExtensionApis/prefs/api.js +++ b/webExtensionApis/prefs/api.js @@ -2,10 +2,10 @@ ChromeUtils.import("resource://gre/modules/Services.jsm"); -const BASE_PREF = "extensions.original-bootstrap-addon-id."; +const PREFERENCES_PREFIX = ""; function get(key, type = "char") { - key = BASE_PREF + key; + key = PREFERENCES_PREFIX + key; switch (type) { case "char": @@ -20,7 +20,7 @@ function get(key, type = "char") { } function set(key, type, value) { - key = BASE_PREF + key; + key = PREFERENCES_PREFIX + key; switch (type) { case "char": From 6c69c0930fa756e575863afc800ba9b2e6f562d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Tue, 3 Apr 2018 11:18:01 +0300 Subject: [PATCH 46/50] Updated package-lock.json --- package-lock.json | 7668 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 7108 insertions(+), 560 deletions(-) diff --git a/package-lock.json b/package-lock.json index c26cfb0..d90dc94 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,9 +1,55 @@ { "name": "shield-studies-addon-utils", - "version": "4.1.0", + "version": "5.0.0-alpha1", "lockfileVersion": 1, "requires": true, "dependencies": { + "@cliqz-oss/firefox-client": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@cliqz-oss/firefox-client/-/firefox-client-0.3.1.tgz", + "integrity": "sha512-RO+Tops/wGnBzWoZYkCraqyh2JqOejqJq5/a4b54HhmjTNSKdUPwAOK17EGg/zPb0nWqkuB7QyZsI9bo+ev8Kw==", + "dev": true, + "requires": { + "colors": "0.5.1", + "js-select": "0.6.0" + }, + "dependencies": { + "colors": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz", + "integrity": "sha1-fQAj6usVTo7p/Oddy5I9DtFmd3Q=", + "dev": true + } + } + }, + "@cliqz-oss/node-firefox-connect": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@cliqz-oss/node-firefox-connect/-/node-firefox-connect-1.2.1.tgz", + "integrity": "sha512-O/IyiB5pfztCdmxQZg0/xeq5w+YiP3gtJz8d4We2EpLPKzbDVjOrtfLKYgVfm6Ya6mbvDge1uLkSRwaoVCWKnA==", + "dev": true, + "requires": { + "@cliqz-oss/firefox-client": "0.3.1", + "es6-promise": "2.3.0" + } + }, + "@types/node": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.1.tgz", + "integrity": "sha512-xwlHq5DXQFRpe+u6hmmNkzYk/3oxxqDp71a/AJMupOQYmxyaBetqrVMqdNlSQfbg7XTJYD8vARjf3Op06OzdtQ==", + "dev": true + }, + "JSONSelect": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/JSONSelect/-/JSONSelect-0.2.1.tgz", + "integrity": "sha1-QVQYpSbTP+MddLTe+jyDbUhewgM=", + "dev": true + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, "acorn": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.1.1.tgz", @@ -44,6 +90,441 @@ } } }, + "adbkit": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/adbkit/-/adbkit-2.11.0.tgz", + "integrity": "sha512-j2vUhEeZmCiqBP+p77CpPWQTcT20rOmSmRHFUTZUwUpxzeCd3fXop4NAGYztSY9/FNU4bT/qqvYQ4EZKuCXhfA==", + "dev": true, + "requires": { + "adbkit-logcat": "1.1.0", + "adbkit-monkey": "1.0.1", + "bluebird": "2.9.34", + "commander": "2.11.0", + "debug": "2.6.8", + "node-forge": "0.7.5", + "split": "0.3.3" + }, + "dependencies": { + "bluebird": { + "version": "2.9.34", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.9.34.tgz", + "integrity": "sha1-L3tOyAIWMoqf3evfacjUlC/v99g=", + "dev": true + } + } + }, + "adbkit-logcat": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/adbkit-logcat/-/adbkit-logcat-1.1.0.tgz", + "integrity": "sha1-Adf5sM75CTowvLOwB+//MBUIli8=", + "dev": true + }, + "adbkit-monkey": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/adbkit-monkey/-/adbkit-monkey-1.0.1.tgz", + "integrity": "sha1-8pG+cBou/FZ6Y/x6pq/N7TFDC+E=", + "dev": true, + "requires": { + "async": "0.2.10" + }, + "dependencies": { + "async": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=", + "dev": true + } + } + }, + "addons-linter": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/addons-linter/-/addons-linter-0.41.0.tgz", + "integrity": "sha512-jrN8OC6HKWv6nadJYOWH/ISiuJrTzBp0Jonfg7xUBPTSo6Lxo/EWBv1s3mSGTErzhiODDAFFsP6QUr76QwfcWA==", + "dev": true, + "requires": { + "ajv": "6.3.0", + "ajv-merge-patch": "3.0.0", + "babel-register": "6.26.0", + "chalk": "2.3.2", + "cheerio": "1.0.0-rc.2", + "columnify": "1.5.4", + "common-tags": "1.7.2", + "crx-parser": "0.1.2", + "deepmerge": "2.1.0", + "dispensary": "0.16.0", + "doctoc": "1.3.1", + "es6-promisify": "5.0.0", + "eslint": "4.19.0", + "eslint-plugin-no-unsafe-innerhtml": "1.0.16", + "esprima": "3.1.3", + "first-chunk-stream": "2.0.0", + "fluent-syntax": "0.6.6", + "glob": "7.1.2", + "is-mergeable-object": "1.1.0", + "jed": "1.1.1", + "os-locale": "2.1.0", + "pino": "4.14.0", + "po2json": "0.4.5", + "postcss": "6.0.19", + "probe-image-size": "4.0.0", + "relaxed-json": "1.0.1", + "semver": "5.5.0", + "shelljs": "0.8.1", + "snyk": "1.70.3", + "source-map-support": "0.5.4", + "strip-bom-stream": "3.0.0", + "tosource": "1.0.0", + "upath": "1.0.4", + "whatwg-url": "6.3.0", + "xmldom": "0.1.27", + "yargs": "11.0.0", + "yauzl": "2.9.1" + }, + "dependencies": { + "acorn": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.5.3.tgz", + "integrity": "sha512-jd5MkIUlbbmb07nXH0DT3y7rDVtkzDi4XZOUVWAer8ajmF/DTSSbl5oNFyDOl/OXA33Bl79+ypHhl2pN20VeOQ==", + "dev": true + }, + "ajv": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.3.0.tgz", + "integrity": "sha1-FlCkERTvAFdMrBC4Ay2PTBSBLac=", + "dev": true, + "requires": { + "fast-deep-equal": "1.0.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" + } + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "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" + } + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "cliui": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.0.0.tgz", + "integrity": "sha512-nY3W5Gu2racvdDk//ELReY+dHjb9PlIcVDFXP72nVIhq2Gy3LuVXYwJoPVudwQnv1shtohpgkdCKT2YaKY0CKw==", + "dev": true, + "requires": { + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "wrap-ansi": "2.1.0" + } + }, + "debug": { + "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" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "2.0.2" + } + }, + "eslint": { + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.0.tgz", + "integrity": "sha512-r83L5CuqaocDvfwdojbz68b6tCUk8KJkqfppO+gmSAQqYCzTr0bCSMu6A6yFCLKG65j5eKcKUw4Cw4Yl4gfWkg==", + "dev": true, + "requires": { + "ajv": "5.5.2", + "babel-code-frame": "6.22.0", + "chalk": "2.3.2", + "concat-stream": "1.6.0", + "cross-spawn": "5.1.0", + "debug": "3.1.0", + "doctrine": "2.1.0", + "eslint-scope": "3.7.1", + "eslint-visitor-keys": "1.0.0", + "espree": "3.5.4", + "esquery": "1.0.0", + "esutils": "2.0.2", + "file-entry-cache": "2.0.0", + "functional-red-black-tree": "1.0.1", + "glob": "7.1.2", + "globals": "11.4.0", + "ignore": "3.3.3", + "imurmurhash": "0.1.4", + "inquirer": "3.2.0", + "is-resolvable": "1.0.0", + "js-yaml": "3.11.0", + "json-stable-stringify-without-jsonify": "1.0.1", + "levn": "0.3.0", + "lodash": "4.17.4", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "natural-compare": "1.4.0", + "optionator": "0.8.2", + "path-is-inside": "1.0.2", + "pluralize": "7.0.0", + "progress": "2.0.0", + "regexpp": "1.1.0", + "require-uncached": "1.0.3", + "semver": "5.5.0", + "strip-ansi": "4.0.0", + "strip-json-comments": "2.0.1", + "table": "4.0.2", + "text-table": "0.2.0" + }, + "dependencies": { + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.0.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" + } + } + } + }, + "espree": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "dev": true, + "requires": { + "acorn": "5.5.3", + "acorn-jsx": "3.0.1" + } + }, + "esprima": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", + "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" + } + }, + "globals": { + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.4.0.tgz", + "integrity": "sha512-Dyzmifil8n/TmSqYDEXbm+C8yitzJQqQIlJQLNRMwa+BOUJpRC19pyVeN12JAjt61xonvXjtff+hJruTRXn5HA==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "js-yaml": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.11.0.tgz", + "integrity": "sha512-saJstZWv7oNeOyBh3+Dx1qWzhW0+e6/8eDzo7p5rDFqxntSztloLtuKu+Ejhtq82jsilwOIZYsCz+lIjthg1Hw==", + "dev": true, + "requires": { + "argparse": "1.0.9", + "esprima": "4.0.0" + }, + "dependencies": { + "esprima": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", + "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "dev": true + } + } + }, + "os-locale": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", + "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "dev": true, + "requires": { + "execa": "0.7.0", + "lcid": "1.0.0", + "mem": "1.1.0" + } + }, + "pluralize": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", + "dev": true + }, + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "dev": true + }, + "shelljs": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.1.tgz", + "integrity": "sha512-YA/iYtZpzFe5HyWVGrb02FjPxc4EMCfpoU/Phg9fQoyMC72u9598OUBrsU8IrtwAKG0tO8IYaqbaLIw+k3IRGA==", + "dev": true, + "requires": { + "glob": "7.1.2", + "interpret": "1.0.3", + "rechoir": "0.6.2" + } + }, + "slice-ansi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.4.tgz", + "integrity": "sha512-PETSPG6BjY1AHs2t64vS2aqAgu6dMIMXJULWFBGbh2Gr8nVLbCFDo6i/RMMvviIQ2h1Z8+5gQhVKSn2je9nmdg==", + "dev": true, + "requires": { + "source-map": "0.6.1" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + }, + "table": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", + "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "dev": true, + "requires": { + "ajv": "5.5.2", + "ajv-keywords": "2.1.1", + "chalk": "2.3.2", + "lodash": "4.17.4", + "slice-ansi": "1.0.0", + "string-width": "2.1.1" + }, + "dependencies": { + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.0.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" + } + } + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "yargs": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.0.0.tgz", + "integrity": "sha512-Rjp+lMYQOWtgqojx1dEWorjCofi1YN7AoFvYV7b1gx/7dAAeuI4kN5SZiEvr0ZmsZTOpDRcCqrpI10L31tFkBw==", + "dev": true, + "requires": { + "cliui": "4.0.0", + "decamelize": "1.2.0", + "find-up": "2.1.0", + "get-caller-file": "1.0.2", + "os-locale": "2.1.0", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "2.1.1", + "which-module": "2.0.0", + "y18n": "3.2.1", + "yargs-parser": "9.0.2" + } + }, + "yargs-parser": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", + "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", + "dev": true, + "requires": { + "camelcase": "4.1.0" + } + } + } + }, "adm-zip": { "version": "0.4.7", "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.7.tgz", @@ -60,6 +541,22 @@ "json-stable-stringify": "1.0.1" } }, + "ajv-keywords": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", + "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", + "dev": true + }, + "ajv-merge-patch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ajv-merge-patch/-/ajv-merge-patch-3.0.0.tgz", + "integrity": "sha1-dvBx45H0Gf6f4/6n6SChrYJLK2E=", + "dev": true, + "requires": { + "fast-json-patch": "1.2.2", + "json-merge-patch": "0.2.3" + } + }, "alce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/alce/-/alce-1.0.0.tgz", @@ -104,6 +601,15 @@ "emoji-regex": "6.1.3" } }, + "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": { + "string-width": "2.1.0" + } + }, "ansi-escapes": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-2.0.0.tgz", @@ -122,6 +628,18 @@ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", "dev": true }, + "ansicolors": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", + "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=", + "dev": true + }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", + "dev": true + }, "anymatch": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.0.tgz", @@ -132,6 +650,42 @@ "micromatch": "2.3.11" } }, + "archiver": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-2.1.1.tgz", + "integrity": "sha1-/2YrSnggFJSj7lRNOjP+dJZQnrw=", + "dev": true, + "requires": { + "archiver-utils": "1.3.0", + "async": "2.5.0", + "buffer-crc32": "0.2.13", + "glob": "7.1.2", + "lodash": "4.17.4", + "readable-stream": "2.3.3", + "tar-stream": "1.5.5", + "zip-stream": "1.2.0" + } + }, + "archiver-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-1.3.0.tgz", + "integrity": "sha1-5QtMCccL89aA4y/xt5lOn52JUXQ=", + "dev": true, + "requires": { + "glob": "7.1.2", + "graceful-fs": "4.1.11", + "lazystream": "1.0.0", + "lodash": "4.17.4", + "normalize-path": "2.1.1", + "readable-stream": "2.3.3" + } + }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, "argparse": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", @@ -156,12 +710,24 @@ "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", "dev": true }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, "array-filter": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz", "integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=", "dev": true }, + "array-from": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", + "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", + "dev": true + }, "array-map": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz", @@ -201,6 +767,18 @@ "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", + "dev": true + }, + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", + "dev": true + }, "asn1.js": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.9.1.tgz", @@ -221,6 +799,18 @@ "util": "0.10.3" } }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "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": "2.5.0", "resolved": "https://registry.npmjs.org/async/-/async-2.5.0.tgz", @@ -236,6 +826,30 @@ "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", "dev": true }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "atob": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.0.tgz", + "integrity": "sha512-SuiKH8vbsOyCALjA/+EINmt/Kdl+TQPrtFgW7XZZcwtryFu9e5kQoX3bjCW6mIvGH1fbeAZZuvwGR5IlBRznGw==", + "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=", + "dev": true + }, + "aws4": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", + "dev": true + }, "babel-code-frame": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz", @@ -247,6 +861,205 @@ "js-tokens": "3.0.2" } }, + "babel-core": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.0.tgz", + "integrity": "sha1-rzL3izGm/O8RnIew/Y2XU/A6C7g=", + "dev": true, + "requires": { + "babel-code-frame": "6.26.0", + "babel-generator": "6.26.1", + "babel-helpers": "6.24.1", + "babel-messages": "6.23.0", + "babel-register": "6.26.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "convert-source-map": "1.5.1", + "debug": "2.6.8", + "json5": "0.5.1", + "lodash": "4.17.4", + "minimatch": "3.0.4", + "path-is-absolute": "1.0.1", + "private": "0.1.8", + "slash": "1.0.0", + "source-map": "0.5.6" + }, + "dependencies": { + "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": { + "chalk": "1.1.3", + "esutils": "2.0.2", + "js-tokens": "3.0.2" + } + } + } + }, + "babel-generator": { + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", + "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", + "dev": true, + "requires": { + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "detect-indent": "4.0.0", + "jsesc": "1.3.0", + "lodash": "4.17.4", + "source-map": "0.5.7", + "trim-right": "1.0.1" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "babel-helpers": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", + "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" + } + }, + "babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-polyfill": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", + "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "core-js": "2.5.4", + "regenerator-runtime": "0.10.5" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", + "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=", + "dev": true + } + } + }, + "babel-register": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", + "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", + "dev": true, + "requires": { + "babel-core": "6.26.0", + "babel-runtime": "6.26.0", + "core-js": "2.5.4", + "home-or-tmp": "2.0.0", + "lodash": "4.17.4", + "mkdirp": "0.5.1", + "source-map-support": "0.4.18" + }, + "dependencies": { + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "requires": { + "source-map": "0.5.6" + } + } + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.4", + "regenerator-runtime": "0.11.1" + } + }, + "babel-template": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "lodash": "4.17.4" + } + }, + "babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "dev": true, + "requires": { + "babel-code-frame": "6.26.0", + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "debug": "2.6.8", + "globals": "9.18.0", + "invariant": "2.2.4", + "lodash": "4.17.4" + }, + "dependencies": { + "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": { + "chalk": "1.1.3", + "esutils": "2.0.2", + "js-tokens": "3.0.2" + } + } + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "esutils": "2.0.2", + "lodash": "4.17.4", + "to-fast-properties": "1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true + }, "bail": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.2.tgz", @@ -259,12 +1072,60 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "1.0.1", + "class-utils": "0.3.6", + "component-emitter": "1.2.1", + "define-property": "1.0.0", + "isobject": "3.0.1", + "mixin-deep": "1.3.1", + "pascalcase": "0.1.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.2" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, "base64-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz", "integrity": "sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw==", "dev": true }, + "base64url": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz", + "integrity": "sha1-6sFuA+oUOO/5Qj1puqNiYu0fcLs=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "dev": true, + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, "big.js": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.1.3.tgz", @@ -277,6 +1138,39 @@ "integrity": "sha1-SOyNFt9Dd+rl+liEaCSAr02Vx3Q=", "dev": true }, + "bl": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", + "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", + "dev": true, + "requires": { + "readable-stream": "2.3.5", + "safe-buffer": "5.1.1" + }, + "dependencies": { + "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.5", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.5.tgz", + "integrity": "sha512-tK0yDhrkygt/knjowCUiWP9YdV7c5R+8cR0r/kt9ZhBU906Fs6RpQJCEilamRJj1Nx2rWI6LkW9gKqjTkshhEw==", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + } + } + }, "block-stream": { "version": "0.0.9", "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", @@ -298,12 +1192,81 @@ "integrity": "sha512-LxFiV5mefv0ley0SzqkOPR1bC4EbpPx8LkOz5vMe/Yi15t5hzwgO/G+tc7wOtL4PZTYjwHu8JnEiSLumuSjSfA==", "dev": true }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, + "boom": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", + "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", + "dev": true, + "requires": { + "hoek": "4.2.1" + } + }, + "bops": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/bops/-/bops-0.1.1.tgz", + "integrity": "sha1-Bi4CqNqoAfoQ8uXb5nQM/4Af4X4=", + "dev": true, + "requires": { + "base64-js": "0.0.2", + "to-utf8": "0.0.1" + }, + "dependencies": { + "base64-js": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.2.tgz", + "integrity": "sha1-Ak8Pcq+iW3X5wO5zzU9V7Bvtl4Q=", + "dev": true + } + } + }, "boundary": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/boundary/-/boundary-1.0.1.tgz", "integrity": "sha1-TWfcJgLAzBbdm85+v4fpSCkPWBI=", "dev": true }, + "boxen": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-0.3.1.tgz", + "integrity": "sha1-p9iYJDrmIvertrtgTXQKdsalRhs=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "filled-array": "1.1.0", + "object-assign": "4.1.1", + "repeating": "2.0.1", + "string-width": "1.0.2", + "widest-line": "1.0.0" + }, + "dependencies": { + "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.1" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + } + } + }, "brace-expansion": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", @@ -417,6 +1380,18 @@ "isarray": "1.0.0" } }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=", + "dev": true + }, "buffer-xor": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", @@ -435,6 +1410,43 @@ "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", "dev": true }, + "bunyan": { + "version": "1.8.12", + "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.12.tgz", + "integrity": "sha1-8VDw9nSKvdcq6uhPBEA74u8RN5c=", + "dev": true, + "requires": { + "dtrace-provider": "0.8.6", + "moment": "2.22.0", + "mv": "2.1.1", + "safe-json-stringify": "1.1.0" + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "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" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, "caller-path": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", @@ -462,6 +1474,12 @@ "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=", "dev": true }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, "ccount": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.0.2.tgz", @@ -515,6 +1533,42 @@ "integrity": "sha1-lCg191Dk7GGjCOYMLvjMEBEgLvw=", "dev": true }, + "cheerio": { + "version": "1.0.0-rc.2", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.2.tgz", + "integrity": "sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs=", + "dev": true, + "requires": { + "css-select": "1.2.0", + "dom-serializer": "0.1.0", + "entities": "1.1.1", + "htmlparser2": "3.9.2", + "lodash": "4.17.4", + "parse5": "3.0.3" + }, + "dependencies": { + "entities": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", + "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=", + "dev": true + }, + "htmlparser2": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", + "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=", + "dev": true, + "requires": { + "domelementtype": "1.3.0", + "domhandler": "2.3.0", + "domutils": "1.5.1", + "entities": "1.1.1", + "inherits": "2.0.3", + "readable-stream": "2.3.3" + } + } + } + }, "chokidar": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", @@ -548,20 +1602,112 @@ "integrity": "sha1-vos2rvzN6LPKeqLWr8B6NyQsDS0=", "dev": true }, - "cli": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz", - "integrity": "sha1-IoF1NPJL+klQw01TLUjsvGIbjBQ=", + "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": { - "exit": "0.1.2", - "glob": "7.1.2" - } - }, - "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=", + "arr-union": "3.1.0", + "define-property": "0.2.5", + "isobject": "3.0.1", + "static-extend": "0.1.2" + }, + "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.6" + } + }, + "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.2.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-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.2.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==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "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 + } + } + }, + "cli": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz", + "integrity": "sha1-IoF1NPJL+klQw01TLUjsvGIbjBQ=", + "dev": true, + "requires": { + "exit": "0.1.2", + "glob": "7.1.2" + } + }, + "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-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": { "restore-cursor": "2.0.0" @@ -573,6 +1719,143 @@ "integrity": "sha1-sjTKIJsp72b8UY2bmNWEewDt8Ao=", "dev": true }, + "clite": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/clite/-/clite-0.3.0.tgz", + "integrity": "sha1-5/y8jMW9Pn+LhO1I2xLpR0zHNEE=", + "dev": true, + "requires": { + "abbrev": "1.1.1", + "debug": "2.6.8", + "es6-promise": "3.3.1", + "lodash.defaults": "4.2.0", + "lodash.defaultsdeep": "4.6.0", + "lodash.mergewith": "4.6.1", + "then-fs": "2.0.0", + "update-notifier": "0.6.3", + "yargs": "4.8.1" + }, + "dependencies": { + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wrap-ansi": "2.1.0" + } + }, + "configstore": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-2.1.0.tgz", + "integrity": "sha1-c3o6cDbpiGECqmCZ5HuzOrGroaE=", + "dev": true, + "requires": { + "dot-prop": "3.0.0", + "graceful-fs": "4.1.11", + "mkdirp": "0.5.1", + "object-assign": "4.1.1", + "os-tmpdir": "1.0.2", + "osenv": "0.1.5", + "uuid": "2.0.3", + "write-file-atomic": "1.3.4", + "xdg-basedir": "2.0.0" + } + }, + "es6-promise": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", + "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=", + "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.1" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "update-notifier": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-0.6.3.tgz", + "integrity": "sha1-d23sjaoT6WKjQeih2YNUMGtnrgg=", + "dev": true, + "requires": { + "boxen": "0.3.1", + "chalk": "1.1.3", + "configstore": "2.1.0", + "is-npm": "1.0.0", + "latest-version": "2.0.0", + "semver-diff": "2.1.0" + } + }, + "uuid": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", + "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=", + "dev": true + }, + "window-size": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.2.0.tgz", + "integrity": "sha1-tDFbtCFKPXBY6+7okuE/ok2YsHU=", + "dev": true + }, + "yargs": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-4.8.1.tgz", + "integrity": "sha1-wMQpJMpKqmsObaFznfshZDn53cA=", + "dev": true, + "requires": { + "cliui": "3.2.0", + "decamelize": "1.2.0", + "get-caller-file": "1.0.2", + "lodash.assign": "4.2.0", + "os-locale": "1.4.0", + "read-pkg-up": "1.0.1", + "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", + "window-size": "0.2.0", + "y18n": "3.2.1", + "yargs-parser": "2.4.1" + } + }, + "yargs-parser": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-2.4.1.tgz", + "integrity": "sha1-hVaN488VD/SfpRgl8DqMiA3cxcQ=", + "dev": true, + "requires": { + "camelcase": "3.0.0", + "lodash.assign": "4.2.0" + } + } + } + }, "cliui": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", @@ -592,6 +1875,35 @@ } } }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true + }, + "clone-deep": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.3.0.tgz", + "integrity": "sha1-NIxhrpzb4O3+BT2R/0zFIdeQ7eg=", + "dev": true, + "requires": { + "for-own": "1.0.0", + "is-plain-object": "2.0.4", + "kind-of": "3.2.2", + "shallow-clone": "0.1.2" + }, + "dependencies": { + "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.2" + } + } + } + }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -610,6 +1922,16 @@ "integrity": "sha1-S5BvZw5aljqHt2sOFolkM0G2Ajw=", "dev": true }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "1.0.0", + "object-visit": "1.0.1" + } + }, "color-convert": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz", @@ -631,12 +1953,58 @@ "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", "dev": true }, + "columnify": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.5.4.tgz", + "integrity": "sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs=", + "dev": true, + "requires": { + "strip-ansi": "3.0.1", + "wcwidth": "1.0.1" + } + }, + "combined-stream": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "dev": true, + "requires": { + "delayed-stream": "1.0.0" + } + }, "commander": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", "dev": true }, + "common-tags": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.7.2.tgz", + "integrity": "sha512-joj9ZlUOjCrwdbmiLqafeUSgkUM74NqhLsZtSqDmhKudaIY197zTrb8JMl31fMnCUuxwFT23eC/oWvrZzDLRJQ==", + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + } + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "compress-commons": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-1.2.2.tgz", + "integrity": "sha1-UkqfEJA/OoEzibAiXSfEi7dRiQ8=", + "dev": true, + "requires": { + "buffer-crc32": "0.2.13", + "crc32-stream": "2.0.0", + "normalize-path": "2.1.1", + "readable-stream": "2.3.3" + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -654,6 +2022,30 @@ "typedarray": "0.0.6" } }, + "configstore": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-1.4.0.tgz", + "integrity": "sha1-w1eB0FAdJowlxUuLF/YkDopPsCE=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "mkdirp": "0.5.1", + "object-assign": "4.1.1", + "os-tmpdir": "1.0.2", + "osenv": "0.1.5", + "uuid": "2.0.3", + "write-file-atomic": "1.3.4", + "xdg-basedir": "2.0.0" + }, + "dependencies": { + "uuid": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", + "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=", + "dev": true + } + } + }, "console-browserify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", @@ -669,12 +2061,46 @@ "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", "dev": true }, + "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=", + "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 + }, + "core-js": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.4.tgz", + "integrity": "sha1-8si/GB8qgLkvNgEhQpzmOi8K6uA=", + "dev": true + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, + "crc": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.5.0.tgz", + "integrity": "sha1-mLi6fUiWZbo5efWbITgTdBAaGWQ=", + "dev": true + }, + "crc32-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-2.0.0.tgz", + "integrity": "sha1-483TtN8xaN10494/u8t7KX/pCPQ=", + "dev": true, + "requires": { + "crc": "3.5.0", + "readable-stream": "2.3.3" + } + }, "create-ecdh": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.0.tgz", @@ -720,6 +2146,60 @@ "sha.js": "2.4.8" } }, + "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": { + "lru-cache": "4.1.2", + "shebang-command": "1.2.0", + "which": "1.3.0" + }, + "dependencies": { + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "which": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", + "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", + "dev": true, + "requires": { + "isexe": "2.0.0" + } + } + } + }, + "crx-parser": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/crx-parser/-/crx-parser-0.1.2.tgz", + "integrity": "sha1-fu7tnt3JXiLBiTguNGJARKiaWm0=", + "dev": true + }, + "cryptiles": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", + "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", + "dev": true, + "requires": { + "boom": "5.2.0" + }, + "dependencies": { + "boom": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", + "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", + "dev": true, + "requires": { + "hoek": "4.2.1" + } + } + } + }, "crypto-browserify": { "version": "3.11.1", "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.11.1.tgz", @@ -738,13 +2218,46 @@ "randombytes": "2.0.5" } }, - "d": { + "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 + }, + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", "dev": true, "requires": { - "es5-ext": "0.10.24" + "boolbase": "1.0.0", + "css-what": "2.1.0", + "domutils": "1.5.1", + "nth-check": "1.0.1" + } + }, + "css-what": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz", + "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=", + "dev": true + }, + "d": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", + "dev": true, + "requires": { + "es5-ext": "0.10.24" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "1.0.0" } }, "date-now": { @@ -753,6 +2266,12 @@ "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", "dev": true }, + "debounce": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.1.0.tgz", + "integrity": "sha512-ZQVKfRVlwRfD150ndzEK8M90ABT+Y/JQKs4Y7U4MXdpuoUkkrr4DwKbVux3YjylA5bUMUj0Nc3pMxPJX6N2QQQ==", + "dev": true + }, "debug": { "version": "2.6.8", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", @@ -768,6 +2287,18 @@ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, + "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 + }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", + "dev": true + }, "deep-extend": { "version": "0.2.11", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.2.11.tgz", @@ -780,6 +2311,55 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "deepcopy": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/deepcopy/-/deepcopy-0.6.3.tgz", + "integrity": "sha1-Y0eA8vhlardxr4+oQx7RzO5Vx7A=", + "dev": true + }, + "deepmerge": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.1.0.tgz", + "integrity": "sha512-Q89Z26KAfA3lpPGhbF6XMfYAm3jIV3avViy6KOJ2JLzFbeWHOvPQUu5aSJIWXap3gDZC2y1eF5HXEPI2wGqgvw==", + "dev": true + }, + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "dev": true, + "requires": { + "clone": "1.0.4" + } + }, + "define-properties": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", + "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", + "dev": true, + "requires": { + "foreach": "2.0.5", + "object-keys": "1.0.11" + } + }, + "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, + "requires": { + "is-descriptor": "1.0.2", + "isobject": "3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, "del": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", @@ -795,6 +2375,12 @@ "rimraf": "2.6.1" } }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, "des.js": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", @@ -805,6 +2391,15 @@ "minimalistic-assert": "1.0.0" } }, + "detect-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", + "dev": true, + "requires": { + "repeating": "2.0.1" + } + }, "diff": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", @@ -822,6 +2417,154 @@ "randombytes": "2.0.5" } }, + "dispensary": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/dispensary/-/dispensary-0.16.0.tgz", + "integrity": "sha1-cXPygoOAE148jrn2Fxn6A4wM0TM=", + "dev": true, + "requires": { + "array-from": "2.1.1", + "async": "2.6.0", + "natural-compare-lite": "1.4.0", + "pino": "4.14.0", + "request": "2.85.0", + "semver": "5.5.0", + "sha.js": "2.4.8", + "source-map-support": "0.5.4", + "yargs": "11.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "async": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", + "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", + "dev": true, + "requires": { + "lodash": "4.17.4" + } + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "cliui": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.0.0.tgz", + "integrity": "sha512-nY3W5Gu2racvdDk//ELReY+dHjb9PlIcVDFXP72nVIhq2Gy3LuVXYwJoPVudwQnv1shtohpgkdCKT2YaKY0CKw==", + "dev": true, + "requires": { + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "wrap-ansi": "2.1.0" + }, + "dependencies": { + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + } + } + } + }, + "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" + } + }, + "os-locale": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", + "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "dev": true, + "requires": { + "execa": "0.7.0", + "lcid": "1.0.0", + "mem": "1.1.0" + } + }, + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.4.tgz", + "integrity": "sha512-PETSPG6BjY1AHs2t64vS2aqAgu6dMIMXJULWFBGbh2Gr8nVLbCFDo6i/RMMvviIQ2h1Z8+5gQhVKSn2je9nmdg==", + "dev": true, + "requires": { + "source-map": "0.6.1" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "yargs": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.0.0.tgz", + "integrity": "sha512-Rjp+lMYQOWtgqojx1dEWorjCofi1YN7AoFvYV7b1gx/7dAAeuI4kN5SZiEvr0ZmsZTOpDRcCqrpI10L31tFkBw==", + "dev": true, + "requires": { + "cliui": "4.0.0", + "decamelize": "1.2.0", + "find-up": "2.1.0", + "get-caller-file": "1.0.2", + "os-locale": "2.1.0", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "2.1.0", + "which-module": "2.0.0", + "y18n": "3.2.1", + "yargs-parser": "9.0.2" + } + }, + "yargs-parser": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", + "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", + "dev": true, + "requires": { + "camelcase": "4.1.0" + } + } + } + }, "doctoc": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/doctoc/-/doctoc-1.3.1.tgz", @@ -929,6 +2672,31 @@ "domelementtype": "1.3.0" } }, + "dot-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-3.0.0.tgz", + "integrity": "sha1-G3CK8JSknJoOfbyteQq6U52sEXc=", + "dev": true, + "requires": { + "is-obj": "1.0.1" + } + }, + "dtrace-provider": { + "version": "0.8.6", + "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.6.tgz", + "integrity": "sha1-QooiOv4DQl0s1tY0f99AxmkDVj0=", + "dev": true, + "optional": true, + "requires": { + "nan": "2.6.2" + } + }, + "duplexer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", + "dev": true + }, "duplexer2": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", @@ -938,6 +2706,44 @@ "readable-stream": "2.3.3" } }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true + }, + "duplexify": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.5.4.tgz", + "integrity": "sha512-JzYSLYMhoVVBe8+mbHQ4KgpvHpm0DZpJuL8PY93Vyv1fW7jYJ90LoXa1di/CVbJM+TgMs91rbDapE/RNIfnJsA==", + "dev": true, + "requires": { + "end-of-stream": "1.4.1", + "inherits": "2.0.3", + "readable-stream": "2.3.3", + "stream-shift": "1.0.0" + } + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "dev": true, + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "ecdsa-sig-formatter": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz", + "integrity": "sha1-S8kmJ07Dtau1AW5+HWCSGsJisqE=", + "dev": true, + "requires": { + "base64url": "2.0.0", + "safe-buffer": "5.1.1" + } + }, "elliptic": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", @@ -953,6 +2759,12 @@ "minimalistic-crypto-utils": "1.0.1" } }, + "email-validator": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/email-validator/-/email-validator-1.1.1.tgz", + "integrity": "sha512-vkcJJZEb7JXDY883Nx1Lkmb6noM3j1SfSt8L9tVFhZPnPQiFq+Nkd5evc77+tRVS4ChTUSr34voThsglI/ja/A==", + "dev": true + }, "emoji-regex": { "version": "6.1.3", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.1.3.tgz", @@ -965,6 +2777,24 @@ "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", "dev": true }, + "encoding": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", + "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", + "dev": true, + "requires": { + "iconv-lite": "0.4.18" + } + }, + "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==", + "dev": true, + "requires": { + "once": "1.4.0" + } + }, "enhanced-resolve": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.3.0.tgz", @@ -1001,6 +2831,30 @@ "is-arrayish": "0.2.1" } }, + "es-abstract": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.11.0.tgz", + "integrity": "sha512-ZnQrE/lXTTQ39ulXZ+J1DTFazV9qBy61x2bY071B+qGco8Z8q1QddsLdt/EF8Ai9hcWH72dWS0kFqXLxOxqslA==", + "dev": true, + "requires": { + "es-to-primitive": "1.1.1", + "function-bind": "1.1.1", + "has": "1.0.1", + "is-callable": "1.1.3", + "is-regex": "1.0.4" + } + }, + "es-to-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", + "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", + "dev": true, + "requires": { + "is-callable": "1.1.3", + "is-date-object": "1.0.1", + "is-symbol": "1.0.1" + } + }, "es5-ext": { "version": "0.10.24", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.24.tgz", @@ -1011,6 +2865,12 @@ "es6-symbol": "3.1.1" } }, + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, "es6-iterator": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.1.tgz", @@ -1036,6 +2896,29 @@ "event-emitter": "0.3.5" } }, + "es6-promise": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-2.3.0.tgz", + "integrity": "sha1-lu258v2wGZWCKyY92KratnSBgbw=", + "dev": true + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, + "requires": { + "es6-promise": "4.2.4" + }, + "dependencies": { + "es6-promise": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", + "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==", + "dev": true + } + } + }, "es6-set": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", @@ -1167,61 +3050,300 @@ "sax": "1.2.4" } }, - "eslint-scope": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", - "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", - "dev": true, - "requires": { - "esrecurse": "4.2.0", - "estraverse": "4.2.0" - } - }, - "espree": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.4.3.tgz", - "integrity": "sha1-KRC1zNSc6JPC//+qtP2LOjG4I3Q=", - "dev": true, - "requires": { - "acorn": "5.1.1", - "acorn-jsx": "3.0.1" - } - }, - "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", - "dev": true - }, - "esquery": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz", - "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=", - "dev": true, - "requires": { - "estraverse": "4.2.0" - } - }, - "esrecurse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", - "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", + "eslint-plugin-no-unsafe-innerhtml": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-unsafe-innerhtml/-/eslint-plugin-no-unsafe-innerhtml-1.0.16.tgz", + "integrity": "sha1-fQKHjI6b95FriINtWsEitC8VGTI=", "dev": true, "requires": { - "estraverse": "4.2.0", - "object-assign": "4.1.1" - } - }, - "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true - }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "eslint": "3.19.0" + }, + "dependencies": { + "ajv-keywords": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", + "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", + "dev": true + }, + "ansi-escapes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", + "dev": true + }, + "cli-cursor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "dev": true, + "requires": { + "restore-cursor": "1.0.1" + } + }, + "eslint": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz", + "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=", + "dev": true, + "requires": { + "babel-code-frame": "6.22.0", + "chalk": "1.1.3", + "concat-stream": "1.6.0", + "debug": "2.6.8", + "doctrine": "2.0.0", + "escope": "3.6.0", + "espree": "3.4.3", + "esquery": "1.0.0", + "estraverse": "4.2.0", + "esutils": "2.0.2", + "file-entry-cache": "2.0.0", + "glob": "7.1.2", + "globals": "9.18.0", + "ignore": "3.3.3", + "imurmurhash": "0.1.4", + "inquirer": "0.12.0", + "is-my-json-valid": "2.17.2", + "is-resolvable": "1.0.0", + "js-yaml": "3.9.0", + "json-stable-stringify": "1.0.1", + "levn": "0.3.0", + "lodash": "4.17.4", + "mkdirp": "0.5.1", + "natural-compare": "1.4.0", + "optionator": "0.8.2", + "path-is-inside": "1.0.2", + "pluralize": "1.2.1", + "progress": "1.1.8", + "require-uncached": "1.0.3", + "shelljs": "0.7.8", + "strip-bom": "3.0.0", + "strip-json-comments": "2.0.1", + "table": "3.8.3", + "text-table": "0.2.0", + "user-home": "2.0.0" + } + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "1.0.5", + "object-assign": "4.1.1" + } + }, + "inquirer": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", + "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", + "dev": true, + "requires": { + "ansi-escapes": "1.4.0", + "ansi-regex": "2.1.1", + "chalk": "1.1.3", + "cli-cursor": "1.0.2", + "cli-width": "2.1.0", + "figures": "1.7.0", + "lodash": "4.17.4", + "readline2": "1.0.1", + "run-async": "0.1.0", + "rx-lite": "3.1.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "through": "2.3.8" + } + }, + "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.1" + } + }, + "onetime": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "dev": true + }, + "pluralize": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", + "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", + "dev": true + }, + "progress": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", + "dev": true + }, + "restore-cursor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "dev": true, + "requires": { + "exit-hook": "1.1.1", + "onetime": "1.1.0" + } + }, + "run-async": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", + "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", + "dev": true, + "requires": { + "once": "1.4.0" + } + }, + "rx-lite": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", + "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=", + "dev": true + }, + "shelljs": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", + "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", + "dev": true, + "requires": { + "glob": "7.1.2", + "interpret": "1.0.3", + "rechoir": "0.6.2" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "table": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz", + "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", + "dev": true, + "requires": { + "ajv": "4.11.8", + "ajv-keywords": "1.5.1", + "chalk": "1.1.3", + "lodash": "4.17.4", + "slice-ansi": "0.0.4", + "string-width": "2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "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=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + } + } + } + } + }, + "eslint-scope": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", + "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", + "dev": true, + "requires": { + "esrecurse": "4.2.0", + "estraverse": "4.2.0" + } + }, + "eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "dev": true + }, + "espree": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.4.3.tgz", + "integrity": "sha1-KRC1zNSc6JPC//+qtP2LOjG4I3Q=", + "dev": true, + "requires": { + "acorn": "5.1.1", + "acorn-jsx": "3.0.1" + } + }, + "esprima": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", + "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "dev": true + }, + "esquery": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz", + "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=", + "dev": true, + "requires": { + "estraverse": "4.2.0" + } + }, + "esrecurse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", + "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", + "dev": true, + "requires": { + "estraverse": "4.2.0", + "object-assign": "4.1.1" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", "dev": true }, "event-emitter": { @@ -1234,6 +3356,27 @@ "es5-ext": "0.10.24" } }, + "event-stream": { + "version": "3.3.4", + "resolved": "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", + "dev": true, + "requires": { + "duplexer": "0.1.1", + "from": "0.1.7", + "map-stream": "0.1.0", + "pause-stream": "0.0.11", + "split": "0.3.3", + "stream-combiner": "0.0.4", + "through": "2.3.8" + } + }, + "event-to-promise": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/event-to-promise/-/event-to-promise-0.8.0.tgz", + "integrity": "sha1-S4TxF3K28l93Uvx02XFTGsb1tiY=", + "dev": true + }, "events": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", @@ -1249,12 +3392,33 @@ "create-hash": "1.1.3" } }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "dev": true, + "requires": { + "cross-spawn": "5.1.0", + "get-stream": "3.0.0", + "is-stream": "1.1.0", + "npm-run-path": "2.0.2", + "p-finally": "1.0.0", + "signal-exit": "3.0.2", + "strip-eof": "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 }, + "exit-hook": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", + "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", + "dev": true + }, "expand-brackets": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", @@ -1285,6 +3449,27 @@ "integrity": "sha1-QlFPhAFdE1bK9Rh5ad+yvBvaCCM=", "dev": true }, + "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" + } + } + } + }, "external-editor": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.0.4.tgz", @@ -1305,18 +3490,57 @@ "is-extglob": "1.0.0" } }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, "fast-deep-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=", "dev": true }, + "fast-json-parse": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fast-json-parse/-/fast-json-parse-1.0.3.tgz", + "integrity": "sha512-FRWsaZRWEJ1ESVNbDWmsAlqDk96gPQezzLghafp5J4GUKjbCz3OkAHuZs5TuPEtkbVQERysLp9xv6c24fBm8Aw==", + "dev": true + }, + "fast-json-patch": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-1.2.2.tgz", + "integrity": "sha1-03fZfGkR290qHIC/rNoEik+Du/k=", + "dev": true + }, + "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=", + "dev": true + }, "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fast-safe-stringify": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-1.2.3.tgz", + "integrity": "sha512-QJYT/i0QYoiZBQ71ivxdyTqkwKkQ0oxACXHYxH2zYHJEgzi2LsbjgvtzTbLi1SZcF190Db2YP7I7eTsU2egOlw==", + "dev": true + }, + "fd-slicer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", + "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", + "dev": true, + "requires": { + "pend": "1.2.0" + } + }, "figures": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", @@ -1355,6 +3579,12 @@ "repeat-string": "1.6.1" } }, + "filled-array": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filled-array/-/filled-array-1.1.0.tgz", + "integrity": "sha1-w8T2xmO5I0WamqKZEtLQMfFQf4Q=", + "dev": true + }, "find-up": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", @@ -1365,6 +3595,62 @@ "pinkie-promise": "2.0.1" } }, + "firefox-profile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/firefox-profile/-/firefox-profile-1.1.0.tgz", + "integrity": "sha512-wUIE4QeAjwoHvFbomWmXgKyYtV4/oZxDcJG4znxtGGa/0BhKkd3HzeOf3tAsMWPq1ExARZxCRRiNw1BL3FuPqA==", + "dev": true, + "requires": { + "adm-zip": "0.4.7", + "archiver": "2.1.1", + "async": "2.5.0", + "fs-extra": "4.0.3", + "ini": "1.3.4", + "jetpack-id": "1.0.0", + "lazystream": "1.0.0", + "lodash": "4.17.4", + "minimist": "1.2.0", + "uuid": "3.2.1", + "xml2js": "0.4.17" + }, + "dependencies": { + "fs-extra": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", + "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "jsonfile": "4.0.0", + "universalify": "0.1.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "first-chunk-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-2.0.0.tgz", + "integrity": "sha1-G97NuOCDwGZLkZRVgVd6Q6nzHXA=", + "dev": true, + "requires": { + "readable-stream": "2.3.3" + } + }, "fixpack": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/fixpack/-/fixpack-2.3.1.tgz", @@ -1389,6 +3675,18 @@ "write": "0.2.1" } }, + "flatstr": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/flatstr/-/flatstr-1.0.5.tgz", + "integrity": "sha1-W0UbCMvUji6sVKK74L9GFlqhS+M=", + "dev": true + }, + "fluent-syntax": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/fluent-syntax/-/fluent-syntax-0.6.6.tgz", + "integrity": "sha512-8iuHPWpn8pPt7/GOBcoDu+x34PkKOH7D0xIjq4C0K2tmf2Bo9bSYVlCj1kvv8g51xKInCyKYg6q5wb6cKZjdUA==", + "dev": true + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -1404,14 +3702,52 @@ "for-in": "1.0.2" } }, - "fs-extra": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.0.tgz", - "integrity": "sha1-QU+0yi0hcLoAFBWdOorsMwNBjZ4=", + "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=", + "dev": true + }, + "form-data": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", + "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "jsonfile": "3.0.1", + "asynckit": "0.4.0", + "combined-stream": "1.0.6", + "mime-types": "2.1.18" + } + }, + "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-extra": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.0.tgz", + "integrity": "sha1-QU+0yi0hcLoAFBWdOorsMwNBjZ4=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "jsonfile": "3.0.1", "universalify": "0.1.0" } }, @@ -2338,6 +4674,12 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, "fx-runner": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/fx-runner/-/fx-runner-1.0.7.tgz", @@ -2382,12 +4724,81 @@ "tar.gz": "1.0.5" } }, + "generate-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", + "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=", + "dev": true + }, + "generate-object-property": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", + "dev": true, + "requires": { + "is-property": "1.0.2" + } + }, "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-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "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=", + "dev": true, + "requires": { + "assert-plus": "1.0.0" + } + }, + "gettext-parser": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/gettext-parser/-/gettext-parser-1.1.0.tgz", + "integrity": "sha1-LFpmONiTk0ubVQN9CtgstwBLJnk=", + "dev": true, + "requires": { + "encoding": "0.1.12" + } + }, + "git-rev-sync": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/git-rev-sync/-/git-rev-sync-1.9.1.tgz", + "integrity": "sha1-oMLj3TkqvPa3aWLif8dfsyI0Sc4=", + "dev": true, + "requires": { + "escape-string-regexp": "1.0.5", + "graceful-fs": "4.1.11", + "shelljs": "0.7.7" + }, + "dependencies": { + "shelljs": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.7.tgz", + "integrity": "sha1-svXHfvlxSPS09uImguELuoZnz/E=", + "dev": true, + "requires": { + "glob": "7.1.2", + "interpret": "1.0.3", + "rechoir": "0.6.2" + } + } + } + }, "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", @@ -2421,6 +4832,15 @@ "is-glob": "2.0.1" } }, + "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": { + "ini": "1.3.4" + } + }, "globals": { "version": "9.18.0", "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", @@ -2477,12 +4897,57 @@ "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", "dev": true }, + "graphlib": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.5.tgz", + "integrity": "sha512-XvtbqCcw+EM5SqQrIetIKKD+uZVNQtDPD1goIg7K73RuRZtVI5rYMdcCVSHm/AS1sCBZ7vt0p5WgXouucHQaOA==", + "dev": true, + "requires": { + "lodash": "4.17.4" + } + }, "growl": { "version": "1.9.2", "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", "dev": true }, + "growly": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", + "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "dev": true, + "requires": { + "ajv": "5.5.2", + "har-schema": "2.0.0" + }, + "dependencies": { + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.0.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" + } + } + } + }, "has": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", @@ -2501,12 +4966,95 @@ "ansi-regex": "2.1.1" } }, + "has-color": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", + "integrity": "sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=", + "dev": true + }, "has-flag": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", "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.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "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=", + "dev": true, + "requires": { + "is-number": "3.0.0", + "kind-of": "4.0.0" + }, + "dependencies": { + "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.2.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" + } + } + } + }, + "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" + } + } + } + }, + "hasbin": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/hasbin/-/hasbin-1.2.3.tgz", + "integrity": "sha1-eMWSaJPIAhXCtWiuH9P8q3omlrA=", + "dev": true, + "requires": { + "async": "1.5.2" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + } + } + }, "hash-base": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz", @@ -2526,6 +5074,18 @@ "minimalistic-assert": "1.0.0" } }, + "hawk": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", + "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", + "dev": true, + "requires": { + "boom": "4.3.1", + "cryptiles": "3.1.2", + "hoek": "4.2.1", + "sntp": "2.1.0" + } + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -2537,6 +5097,22 @@ "minimalistic-crypto-utils": "1.0.1" } }, + "hoek": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", + "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==", + "dev": true + }, + "home-or-tmp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", + "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", + "dev": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, "hosted-git-info": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", @@ -2582,6 +5158,17 @@ } } }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.14.1" + } + }, "https-browserify": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-0.0.1.tgz", @@ -2606,6 +5193,12 @@ "integrity": "sha1-QyNS5XrM2HqzEQ6C0/6g5HgSFW0=", "dev": true }, + "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", @@ -2618,6 +5211,12 @@ "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", "dev": true }, + "infinity-agent": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/infinity-agent/-/infinity-agent-2.0.3.tgz", + "integrity": "sha1-ReDi/3qesDCyfWK3SzdEt6esQhY=", + "dev": true + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -2720,6 +5319,15 @@ "integrity": "sha1-y8NcYu7uc/Gat7EKgBURQBr8D5A=", "dev": true }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "1.3.1" + } + }, "invert-kv": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", @@ -2735,6 +5343,23 @@ "is-relative": "0.1.3" } }, + "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.2" + }, + "dependencies": { + "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 + } + } + }, "is-alphabetical": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.1.tgz", @@ -2781,12 +5406,60 @@ "builtin-modules": "1.1.1" } }, + "is-callable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz", + "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=", + "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==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + }, + "dependencies": { + "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 + } + } + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, "is-decimal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.1.tgz", "integrity": "sha1-9ftqlJlq2ejjdh+/vQkfH8qMToI=", "dev": true }, + "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" + }, + "dependencies": { + "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 + } + } + }, "is-dotfile": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", @@ -2814,6 +5487,15 @@ "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", "dev": true }, + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", @@ -2835,6 +5517,47 @@ "integrity": "sha1-bghLvJIGH7sJcexYts5tQE4k2mk=", "dev": true }, + "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": { + "global-dirs": "0.1.1", + "is-path-inside": "1.0.0" + } + }, + "is-mergeable-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-mergeable-object/-/is-mergeable-object-1.1.0.tgz", + "integrity": "sha512-JfyDDwUdtS4yHCgUpxOyKB9dnfZ0gecufxB0eytX6BmSXSE+8dbxDGt+V7CNRIRJ9sYFV/WQt2KJG6hNob2sBw==", + "dev": true + }, + "is-my-ip-valid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", + "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", + "dev": true + }, + "is-my-json-valid": { + "version": "2.17.2", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz", + "integrity": "sha512-IBhBslgngMQN8DDSppmgDv7RNrlFotuuDsKcrCP3+HbFaVivIBU7u9oiiErw8sH4ynx3+gOGQ3q2otkgiSi6kg==", + "dev": true, + "requires": { + "generate-function": "2.0.0", + "generate-object-property": "1.2.0", + "is-my-ip-valid": "1.0.0", + "jsonpointer": "4.0.1", + "xtend": "4.0.1" + } + }, + "is-npm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", + "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=", + "dev": true + }, "is-number": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", @@ -2844,6 +5567,29 @@ "kind-of": "3.2.2" } }, + "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-odd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-2.0.0.tgz", + "integrity": "sha512-OTiixgpZAT1M4NHgS5IguFp/Vz2VI3U7Goh4/HA1adtwyLtSBrxYlcSYkhpAE07s4fKEcjrFxyvtQBND4vFQyQ==", + "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 + } + } + }, "is-path-cwd": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", @@ -2874,14 +5620,31 @@ "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", "dev": true }, - "is-posix-bracket": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", - "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", - "dev": true - }, - "is-primitive": { - "version": "2.0.0", + "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" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", + "dev": true + }, + "is-primitive": { + "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", "dev": true @@ -2892,12 +5655,27 @@ "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", "dev": true }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", + "dev": true + }, "is-redirect": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", "dev": true }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "1.0.1" + } + }, "is-relative": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz", @@ -2925,18 +5703,42 @@ "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", "dev": true }, + "is-symbol": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", + "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, "is-utf8": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", "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 + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, + "isemail": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-1.2.0.tgz", + "integrity": "sha1-vgPfjMPineTSxd9lASY/H6RZXpo=", + "dev": true + }, "isexe": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/isexe/-/isexe-1.1.2.tgz", @@ -2952,6 +5754,62 @@ "isarray": "1.0.0" } }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "jed": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/jed/-/jed-1.1.1.tgz", + "integrity": "sha1-elSbvZ/+FYWwzQoZHiAwVb7ldLQ=", + "dev": true + }, + "jetpack-id": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jetpack-id/-/jetpack-id-1.0.0.tgz", + "integrity": "sha1-LPn7rkbYB0/Ba33gBxyO/rykc6Y=", + "dev": true + }, + "joi": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/joi/-/joi-6.10.1.tgz", + "integrity": "sha1-TVDDGAeRIgAP5fFq8f+OGRe3fgY=", + "dev": true, + "requires": { + "hoek": "2.16.3", + "isemail": "1.2.0", + "moment": "2.22.0", + "topo": "1.1.0" + }, + "dependencies": { + "hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", + "dev": true + } + } + }, + "js-select": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/js-select/-/js-select-0.6.0.tgz", + "integrity": "sha1-woTiKCTVknrsli3N8kcXSu+w0ZA=", + "dev": true, + "requires": { + "JSONSelect": "0.2.1", + "traverse": "0.4.6" + }, + "dependencies": { + "traverse": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.4.6.tgz", + "integrity": "sha1-0EsigOTHkqWBVCnve4tgxkyczDQ=", + "dev": true + } + } + }, "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", @@ -2968,12 +5826,25 @@ "esprima": "4.0.0" } }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true, + "optional": true + }, "jschardet": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/jschardet/-/jschardet-1.4.2.tgz", "integrity": "sha1-KqEH8UKvQSHRRWWdRPUIMJYeaZo=", "dev": true }, + "jsesc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", + "dev": true + }, "jshint": { "version": "2.9.5", "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.9.5.tgz", @@ -3010,6 +5881,27 @@ "integrity": "sha1-i6oTZaYy9Yo8RtIBdfxgAsluN94=", "dev": true }, + "json-merge-patch": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-merge-patch/-/json-merge-patch-0.2.3.tgz", + "integrity": "sha1-+ixrWvh9p3uuKWalidUuI+2B/kA=", + "dev": true, + "requires": { + "deep-equal": "1.0.1" + } + }, + "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", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, "json-schema-traverse": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", @@ -3025,6 +5917,18 @@ "jsonify": "0.0.0" } }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "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=", + "dev": true + }, "json3": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", @@ -3052,6 +5956,85 @@ "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", "dev": true }, + "jsonpointer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", + "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", + "dev": true + }, + "jsonwebtoken": { + "version": "7.1.9", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-7.1.9.tgz", + "integrity": "sha1-hHgE5SWL7FqUmajcSl56O64I1Yo=", + "dev": true, + "requires": { + "joi": "6.10.1", + "jws": "3.1.4", + "lodash.once": "4.1.1", + "ms": "0.7.3", + "xtend": "4.0.1" + }, + "dependencies": { + "ms": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.3.tgz", + "integrity": "sha1-cIFVpeROM/X9D8U+gdDUCpG+H/8=", + "dev": true + } + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "jszip": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-2.6.1.tgz", + "integrity": "sha1-uI86ey5noqBIFSmCx6N1bZxIKPA=", + "dev": true, + "requires": { + "pako": "1.0.6" + }, + "dependencies": { + "pako": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", + "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==", + "dev": true + } + } + }, + "jwa": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.5.tgz", + "integrity": "sha1-oFUs4CIHQs1S4VN3SjKQXDDnVuU=", + "dev": true, + "requires": { + "base64url": "2.0.0", + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.9", + "safe-buffer": "5.1.1" + } + }, + "jws": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.4.tgz", + "integrity": "sha1-+ei5M46KhHJ31kRLFGT2GIDgUKI=", + "dev": true, + "requires": { + "base64url": "2.0.0", + "jwa": "1.1.5", + "safe-buffer": "5.1.1" + } + }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", @@ -3061,12 +6044,30 @@ "is-buffer": "1.1.5" } }, + "latest-version": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-2.0.0.tgz", + "integrity": "sha1-VvjWE5YghHuAF/jx9NeOIRMkFos=", + "dev": true, + "requires": { + "package-json": "2.4.0" + } + }, "lazy-cache": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", "dev": true }, + "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.3.3" + } + }, "lcid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", @@ -3117,6 +6118,24 @@ "object-assign": "4.1.1" } }, + "locate-path": { + "version": "2.0.0", + "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" + }, + "dependencies": { + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + } + } + }, "lodash": { "version": "4.17.4", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", @@ -3157,6 +6176,18 @@ "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", "dev": true }, + "lodash.assign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=", + "dev": true + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, "lodash.create": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", @@ -3168,6 +6199,18 @@ "lodash._isiterateecall": "3.0.9" } }, + "lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=", + "dev": true + }, + "lodash.defaultsdeep": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.0.tgz", + "integrity": "sha1-vsECT4WxvZbL6kBbI8FK1kQ6b4E=", + "dev": true + }, "lodash.isarguments": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", @@ -3191,6 +6234,24 @@ "lodash.isarray": "3.0.4" } }, + "lodash.mergewith": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz", + "integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==", + "dev": true + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=", + "dev": true + }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", + "dev": true + }, "longest": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", @@ -3203,12 +6264,69 @@ "integrity": "sha1-0GWXxNTDG1LMsfXY+P5xSOr9aWU=", "dev": true }, + "loose-envify": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", + "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", + "dev": true, + "requires": { + "js-tokens": "3.0.2" + } + }, "lowercase-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=", "dev": true }, + "lru-cache": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.2.tgz", + "integrity": "sha512-wgeVXhrDwAWnIF/yZARsFnMBtdFXOg1b8RIrhilp+0iDYN4mdQcNZElDZ0e4B64BhaxeQ5zN7PMyvu7we1kPeQ==", + "dev": true, + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.1.2" + } + }, + "make-dir": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.2.0.tgz", + "integrity": "sha512-aNUAa4UMg/UougV25bbrU4ZaaKNjJ/3/xnvg/twpmKROPdKZPZ9wGgI0opdZzO8q/zUFawoUuixuOv33eZ61Iw==", + "dev": true, + "requires": { + "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 + } + } + }, + "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=", + "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=", + "dev": true, + "requires": { + "object-visit": "1.0.1" + } + }, "markdown-table": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-0.4.0.tgz", @@ -3227,6 +6345,15 @@ "traverse": "0.6.6" } }, + "mem": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", + "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", + "dev": true, + "requires": { + "mimic-fn": "1.1.0" + } + }, "memory-fs": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", @@ -3237,6 +6364,12 @@ "readable-stream": "2.3.3" } }, + "memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", + "dev": true + }, "micromatch": { "version": "2.3.11", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", @@ -3268,6 +6401,21 @@ "brorand": "1.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==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "mime-db": "1.33.0" + } + }, "mimic-fn": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.1.0.tgz", @@ -3301,6 +6449,45 @@ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, + "mixin-deep": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "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" + } + } + } + }, + "mixin-object": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", + "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", + "dev": true, + "requires": { + "for-in": "0.1.8", + "is-extendable": "0.1.1" + }, + "dependencies": { + "for-in": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", + "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=", + "dev": true + } + } + }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", @@ -3333,6 +6520,7 @@ "version": "2.9.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "dev": true, "requires": { "graceful-readlink": "1.0.1" } @@ -3350,6 +6538,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", + "dev": true, "requires": { "fs.realpath": "1.0.0", "inflight": "1.0.6", @@ -3362,7 +6551,8 @@ "has-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true }, "ms": { "version": "0.7.2", @@ -3374,12 +6564,19 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", + "dev": true, "requires": { "has-flag": "1.0.0" } } } }, + "moment": { + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.0.tgz", + "integrity": "sha512-1muXCh8jb1N/gHRbn9VDUBr0GYb8A/aVcHlII9QSB68a50spqEVLIGN6KVmCOnSvJrUhC0edGgKU5ofnGXdYdg==", + "dev": true + }, "mout": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/mout/-/mout-0.11.1.tgz", @@ -3398,23 +6595,200 @@ "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", "dev": true }, - "nan": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.6.2.tgz", - "integrity": "sha1-5P805slf37WuzAjeZZb0NgWn20U=", + "mv": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", + "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", "dev": true, - "optional": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "node-libs-browser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.0.0.tgz", - "integrity": "sha1-o6WeyXAkmFtG6Vg3lkb5bEthZkY=", + "optional": true, + "requires": { + "mkdirp": "0.5.1", + "ncp": "2.0.0", + "rimraf": "2.4.5" + }, + "dependencies": { + "glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", + "dev": true, + "optional": true, + "requires": { + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "rimraf": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", + "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", + "dev": true, + "optional": true, + "requires": { + "glob": "6.0.4" + } + } + } + }, + "mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "requires": { + "any-promise": "1.3.0", + "object-assign": "4.1.1", + "thenify-all": "1.6.0" + } + }, + "nan": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.6.2.tgz", + "integrity": "sha1-5P805slf37WuzAjeZZb0NgWn20U=", + "dev": true, + "optional": true + }, + "nanomatch": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz", + "integrity": "sha512-n8R9bS8yQ6eSXaV6jHUpKzD8gLsin02w1HSFiegwrs9E098Ylhw5jdyKPaYqvHknHaSCKTPp7C8dGCQ0q9koXA==", + "dev": true, + "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-odd": "2.0.0", + "is-windows": "1.0.2", + "kind-of": "6.0.2", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "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 + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "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 + } + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha1-F7CVgZiJef3a/gIB6TG6kzyWy7Q=", + "dev": true + }, + "nconf": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/nconf/-/nconf-0.7.2.tgz", + "integrity": "sha1-oF/fItwBw3jdXE3yfy3JC5qouwA=", + "dev": true, + "requires": { + "async": "0.9.2", + "ini": "1.3.4", + "yargs": "3.15.0" + }, + "dependencies": { + "async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", + "dev": true + }, + "window-size": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz", + "integrity": "sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY=", + "dev": true + }, + "yargs": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.15.0.tgz", + "integrity": "sha1-PZRG7yH7N5GzmFaQZi5LloPH8YE=", + "dev": true, + "requires": { + "camelcase": "1.2.1", + "cliui": "2.1.0", + "decamelize": "1.2.0", + "window-size": "0.1.4" + } + } + } + }, + "ncp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", + "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", + "dev": true, + "optional": true + }, + "needle": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.2.0.tgz", + "integrity": "sha512-eFagy6c+TYayorXw/qtAdSvaUpEbBsDwDyxYFgLZ0lTojfH7K+OdBqAF7TAFwDokJaGpubpSGG0wO3iC0XPi8w==", + "dev": true, + "requires": { + "debug": "2.6.8", + "iconv-lite": "0.4.18", + "sax": "1.2.4" + } + }, + "neo-async": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.5.0.tgz", + "integrity": "sha512-nJmSswG4As/MkRq7QZFuH/sf/yuv8ODdMZrY4Bedjp77a5MK4A6s7YbBB64c9u79EBUOfXUXBvArmvzTD0X+6g==", + "dev": true + }, + "nested-error-stacks": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-1.0.2.tgz", + "integrity": "sha1-GfYZWRUZ8JZ2mlupqG5u7sgjw88=", + "dev": true, + "requires": { + "inherits": "2.0.3" + } + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, + "node-forge": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.5.tgz", + "integrity": "sha512-MmbQJ2MTESTjt3Gi/3yG1wGpIMhUfcIypUCGtTizFR9IiccFwxSpfp0vtIZlkFclEqERemxfnSdZEMR9VqqEFQ==", + "dev": true + }, + "node-libs-browser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.0.0.tgz", + "integrity": "sha1-o6WeyXAkmFtG6Vg3lkb5bEthZkY=", "dev": true, "requires": { "assert": "1.4.1", @@ -3450,12 +6824,88 @@ } } }, + "node-notifier": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.2.1.tgz", + "integrity": "sha512-MIBs+AAd6dJ2SklbbE8RUDRlIVhU8MaNLh1A9SUZDUHPiZkWLFde6UNwG41yQHZEToHgJMXqyVZ9UcS/ReOVTg==", + "dev": true, + "requires": { + "growly": "1.3.0", + "semver": "5.5.0", + "shellwords": "0.1.1", + "which": "1.3.0" + }, + "dependencies": { + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "dev": true + }, + "which": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", + "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", + "dev": true, + "requires": { + "isexe": "2.0.0" + } + } + } + }, "node-status-codes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz", "integrity": "sha1-WuVUHQJGRdMqWPzdyc7s6nrjrC8=", "dev": true }, + "nomnom": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.8.1.tgz", + "integrity": "sha1-IVH3Ikcrp55Qp2/BJbuMjy5Nwqc=", + "dev": true, + "requires": { + "chalk": "0.4.0", + "underscore": "1.6.0" + }, + "dependencies": { + "ansi-styles": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz", + "integrity": "sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg=", + "dev": true + }, + "chalk": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz", + "integrity": "sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=", + "dev": true, + "requires": { + "ansi-styles": "1.0.0", + "has-color": "0.1.7", + "strip-ansi": "0.1.1" + } + }, + "strip-ansi": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz", + "integrity": "sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=", + "dev": true + }, + "underscore": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", + "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=", + "dev": true + } + } + }, "normalize-package-data": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", @@ -3477,33 +6927,264 @@ "remove-trailing-separator": "1.0.2" } }, + "npm-run-all": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.2.tgz", + "integrity": "sha512-Z2aRlajMK4SQ8u19ZA75NZZu7wupfCNQWdYosIi8S6FgBdGf/8Y6Hgyjdc8zU2cYmIRVCx1nM80tJPkdEd+UYg==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "chalk": "2.3.2", + "cross-spawn": "5.1.0", + "memorystream": "0.3.1", + "minimatch": "3.0.4", + "ps-tree": "1.1.0", + "read-pkg": "3.0.0", + "shell-quote": "1.6.1", + "string.prototype.padend": "3.0.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" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.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 + }, + "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": { + "graceful-fs": "4.1.11", + "parse-json": "4.0.0", + "pify": "3.0.0", + "strip-bom": "3.0.0" + } + }, + "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": { + "error-ex": "1.3.1", + "json-parse-better-errors": "1.0.2" + } + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "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": { + "load-json-file": "4.0.0", + "normalize-package-data": "2.4.0", + "path-type": "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 + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "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=", + "dev": true, + "requires": { + "path-key": "2.0.1" + } + }, + "nth-check": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", + "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", + "dev": true, + "requires": { + "boolbase": "1.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 }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "dev": true + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true }, - "object.omit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "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": { - "for-own": "0.1.5", - "is-extendable": "0.1.1" + "copy-descriptor": "0.1.1", + "define-property": "0.2.5", + "kind-of": "3.2.2" + }, + "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.6" + } + }, + "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.2.2" + } + }, + "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.2.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.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 + } + } + } } }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, + "object-keys": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", + "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=", + "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.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "dev": true, + "requires": { + "for-own": "0.1.5", + "is-extendable": "0.1.1" + } + }, + "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" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, "requires": { "wrappy": "1.0.2" } @@ -3517,6 +7198,12 @@ "mimic-fn": "1.1.0" } }, + "open": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/open/-/open-0.0.5.tgz", + "integrity": "sha1-QsPhjslUZra/DcQvOilFw/DK2Pw=", + "dev": true + }, "optionator": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", @@ -3537,6 +7224,12 @@ "integrity": "sha1-Y/xMzuXS13Y9Jrv4YBB45sLgBE8=", "dev": true }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, "os-locale": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", @@ -3546,6 +7239,16 @@ "lcid": "1.0.0" } }, + "os-name": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/os-name/-/os-name-1.0.3.tgz", + "integrity": "sha1-GzefZINa98Wn9JizV8uVIVwVnt8=", + "dev": true, + "requires": { + "osx-release": "1.1.0", + "win-release": "1.1.1" + } + }, "os-shim": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz", @@ -3558,6 +7261,75 @@ "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, + "osx-release": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/osx-release/-/osx-release-1.1.0.tgz", + "integrity": "sha1-8heRGigTaUmvG/kwiyQeJzfTzWw=", + "dev": true, + "requires": { + "minimist": "1.2.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-limit": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz", + "integrity": "sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==", + "dev": true, + "requires": { + "p-try": "1.0.0" + } + }, + "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": { + "p-limit": "1.2.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 + }, + "package-json": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-2.4.0.tgz", + "integrity": "sha1-DRW9Z9HLvduyyiIv8u24a8sxqLs=", + "dev": true, + "requires": { + "got": "5.6.0", + "registry-auth-token": "3.3.2", + "registry-url": "3.1.0", + "semver": "5.3.0" + } + }, "pako": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", @@ -3612,12 +7384,33 @@ "error-ex": "1.3.1" } }, + "parse5": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", + "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", + "dev": true, + "requires": { + "@types/node": "9.6.1" + } + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, "path-browserify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", "dev": true }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, "path-exists": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", @@ -3639,6 +7432,18 @@ "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", "dev": true }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", + "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", + "dev": true + }, "path-type": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", @@ -3650,6 +7455,15 @@ "pinkie-promise": "2.0.1" } }, + "pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", + "dev": true, + "requires": { + "through": "2.3.8" + } + }, "pbkdf2": { "version": "3.0.12", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.12.tgz", @@ -3663,6 +7477,18 @@ "sha.js": "2.4.8" } }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -3684,12 +7510,141 @@ "pinkie": "2.0.4" } }, + "pino": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-4.14.0.tgz", + "integrity": "sha512-nuPTxdy3OHmsdZmi8hzZYlW0m2+8HLUYM8euys6OOKVGBRF8TY7uFGvQZMLkwNgG+Zw+pNaGUw7OIMa76Ok2eg==", + "dev": true, + "requires": { + "chalk": "2.3.2", + "fast-json-parse": "1.0.3", + "fast-safe-stringify": "1.2.3", + "flatstr": "1.0.5", + "pino-std-serializers": "1.2.0", + "pump": "3.0.0", + "quick-format-unescaped": "1.1.2", + "split2": "2.2.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" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.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 + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "pino-std-serializers": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-1.2.0.tgz", + "integrity": "sha512-6LQcMXLHlT+PKMFkY6WpPmQvkFIecngF7WJPiMi5PfCGcScikvB3kt+Q5+zLtxw0dxPqXHd9XczPx/HwsNmOIA==", + "dev": true + }, "pluralize": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-4.0.0.tgz", "integrity": "sha1-WbcIwcAZCi9pLxx2GMRGsFL9F2I=", "dev": true }, + "po2json": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/po2json/-/po2json-0.4.5.tgz", + "integrity": "sha1-R7spUtoy1Yob4vJWpZjuvAt0URg=", + "dev": true, + "requires": { + "gettext-parser": "1.1.0", + "nomnom": "1.8.1" + } + }, + "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 + }, + "postcss": { + "version": "6.0.19", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.19.tgz", + "integrity": "sha512-f13HRz0HtVwVaEuW6J6cOUCBLFtymhgyLPV7t4QEk2UD3twRI9IluDcQNdzQdBpiixkXj2OmzejhhTbSbDxNTg==", + "dev": true, + "requires": { + "chalk": "2.3.2", + "source-map": "0.6.1", + "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" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.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 + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -3714,6 +7669,26 @@ "integrity": "sha512-T/KD65Ot0PB97xTrG8afQ46x3oiVhnfGjGESSI9NWYcG92+OUPZKkwHqGWXH2t9jK1crnQjubECW0FuOth+hxw==", "dev": true }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true + }, + "probe-image-size": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/probe-image-size/-/probe-image-size-4.0.0.tgz", + "integrity": "sha512-nm7RvWUxps+2+jZKNLkd04mNapXNariS6G5WIEVzvAqjx7EUuKcY1Dp3e6oUK7GLwzJ+3gbSbPLFAASHFQrPcQ==", + "dev": true, + "requires": { + "any-promise": "1.3.0", + "deepmerge": "2.1.0", + "inherits": "2.0.3", + "next-tick": "1.0.0", + "request": "2.85.0", + "stream-parser": "0.3.1" + } + }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -3732,12 +7707,42 @@ "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", "dev": true }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dev": true, + "requires": { + "asap": "2.0.6" + } + }, + "proxy-from-env": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", + "integrity": "sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4=", + "dev": true + }, "prr": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/prr/-/prr-0.0.0.tgz", "integrity": "sha1-GoS4WQgyVQFBGFPQCB7j+obikmo=", "dev": true }, + "ps-tree": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.1.0.tgz", + "integrity": "sha1-tCGyQUDWID8e08dplrRCewjowBQ=", + "dev": true, + "requires": { + "event-stream": "3.3.4" + } + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, "public-encrypt": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.0.tgz", @@ -3751,12 +7756,28 @@ "randombytes": "2.0.5" } }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "1.4.1", + "once": "1.4.0" + } + }, "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", "dev": true }, + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", + "dev": true + }, "querystring": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", @@ -3769,6 +7790,15 @@ "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", "dev": true }, + "quick-format-unescaped": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-1.1.2.tgz", + "integrity": "sha1-DKWB3jF0vs7yWsPC6JVjQjgdtpg=", + "dev": true, + "requires": { + "fast-safe-stringify": "1.2.3" + } + }, "randomatic": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", @@ -3897,31 +7927,180 @@ "set-immediate-shim": "1.0.1" } }, - "regex-cache": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.3.tgz", - "integrity": "sha1-mxpsNdTQ3871cRrmUejp09cRQUU=", + "readline2": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", + "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", "dev": true, "requires": { - "is-equal-shallow": "0.1.3", - "is-primitive": "2.0.0" + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "mute-stream": "0.0.5" + }, + "dependencies": { + "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.1" + } + }, + "mute-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", + "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", + "dev": true + } } }, - "remark": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/remark/-/remark-5.1.0.tgz", - "integrity": "sha1-y0Y709vLS5l5STXu4c9x16jjBow=", + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", "dev": true, "requires": { - "remark-parse": "1.1.0", - "remark-stringify": "1.1.0", - "unified": "4.2.1" + "resolve": "1.6.0" } }, - "remark-parse": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-1.1.0.tgz", - "integrity": "sha1-w8oQ+ajaBGFcKPCapOMEUQUm7CE=", + "recursive-readdir": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz", + "integrity": "sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg==", + "dev": true, + "requires": { + "minimatch": "3.0.4" + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + }, + "regex-cache": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.3.tgz", + "integrity": "sha1-mxpsNdTQ3871cRrmUejp09cRQUU=", + "dev": true, + "requires": { + "is-equal-shallow": "0.1.3", + "is-primitive": "2.0.0" + } + }, + "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" + } + }, + "regexpp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", + "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==", + "dev": true + }, + "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": { + "rc": "1.2.6", + "safe-buffer": "5.1.1" + }, + "dependencies": { + "deep-extend": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", + "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=", + "dev": true + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "rc": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.6.tgz", + "integrity": "sha1-6xiYnG1PTxYsOZ953dKfODVWgJI=", + "dev": true, + "requires": { + "deep-extend": "0.4.2", + "ini": "1.3.4", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + } + } + } + }, + "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": { + "rc": "1.2.6" + }, + "dependencies": { + "deep-extend": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", + "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=", + "dev": true + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "rc": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.6.tgz", + "integrity": "sha1-6xiYnG1PTxYsOZ953dKfODVWgJI=", + "dev": true, + "requires": { + "deep-extend": "0.4.2", + "ini": "1.3.4", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + } + } + } + }, + "relaxed-json": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/relaxed-json/-/relaxed-json-1.0.1.tgz", + "integrity": "sha1-fI1KovCVcEzQIOMugJm8rhA/C9Q=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "commander": "2.11.0" + } + }, + "remark": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/remark/-/remark-5.1.0.tgz", + "integrity": "sha1-y0Y709vLS5l5STXu4c9x16jjBow=", + "dev": true, + "requires": { + "remark-parse": "1.1.0", + "remark-stringify": "1.1.0", + "unified": "4.2.1" + } + }, + "remark-parse": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-1.1.0.tgz", + "integrity": "sha1-w8oQ+ajaBGFcKPCapOMEUQUm7CE=", "dev": true, "requires": { "collapse-white-space": "1.0.3", @@ -3969,6 +8148,45 @@ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", "dev": true }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "1.0.2" + } + }, + "request": { + "version": "2.85.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.85.0.tgz", + "integrity": "sha512-8H7Ehijd4js+s6wuVPLjwORxD4zeuyjYugprdOXlPSqaApmL/QOy+EB/beICHVCHkGMKNh5rvihb5ov+IDw4mg==", + "dev": true, + "requires": { + "aws-sign2": "0.7.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.6", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.3.2", + "har-validator": "5.0.3", + "hawk": "6.0.2", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.18", + "oauth-sign": "0.8.2", + "performance-now": "2.1.0", + "qs": "6.5.1", + "safe-buffer": "5.1.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.4", + "tunnel-agent": "0.6.0", + "uuid": "3.2.1" + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -3991,12 +8209,27 @@ "resolve-from": "1.0.1" } }, + "resolve": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.6.0.tgz", + "integrity": "sha512-mw7JQNu5ExIkcw4LPih0owX/TZXjD/ZUF/ZQ/pDnkw3ZKhDcZZw5klmBlj6gVMwjQ3Pz5Jgu7F3d0jcDVuEWdw==", + "dev": true, + "requires": { + "path-parse": "1.0.5" + } + }, "resolve-from": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", "dev": true }, + "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 + }, "restore-cursor": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", @@ -4007,6 +8240,12 @@ "signal-exit": "3.0.2" } }, + "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 + }, "right-align": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", @@ -4044,6 +8283,12 @@ "is-promise": "2.1.0" } }, + "rx": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz", + "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=", + "dev": true + }, "rx-lite": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", @@ -4065,6 +8310,22 @@ "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", "dev": true }, + "safe-json-stringify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.1.0.tgz", + "integrity": "sha512-EzBtUaFH9bHYPc69wqjp0efJI/DPNHdFbGE3uIMn4sVbO0zx8vZ8cG4WKxQfOpUOKsQyGBiT2mTqnCw+6nLswA==", + "dev": true, + "optional": true + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "0.1.15" + } + }, "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", @@ -4100,6 +8361,15 @@ "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", "dev": true }, + "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": { + "semver": "5.3.0" + } + }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -4112,6 +8382,29 @@ "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.4", + "split-string": "3.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.1" + } + } + } + }, "setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -4127,6 +8420,50 @@ "inherits": "2.0.3" } }, + "shallow-clone": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", + "integrity": "sha1-WQnodLp3EG1zrEFM/sH/yofZcGA=", + "dev": true, + "requires": { + "is-extendable": "0.1.1", + "kind-of": "2.0.1", + "lazy-cache": "0.2.7", + "mixin-object": "2.0.1" + }, + "dependencies": { + "kind-of": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", + "integrity": "sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU=", + "dev": true, + "requires": { + "is-buffer": "1.1.5" + } + }, + "lazy-cache": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", + "integrity": "sha1-f+3fLctu23fRHvHRF6tf/fCrG2U=", + "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=", + "dev": true, + "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=", + "dev": true + }, "shell-quote": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz", @@ -4145,531 +8482,2604 @@ "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=", "dev": true }, + "shellwords": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", + "dev": true + }, "shield-study-schemas": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/shield-study-schemas/-/shield-study-schemas-0.8.3.tgz", "integrity": "sha1-2yBiSamGlJkVnThHxxiYV1lfgM0=" }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true - }, - "slice-ansi": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", - "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", - "dev": true - }, - "source-list-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.0.tgz", - "integrity": "sha512-I2UmuJSRr/T8jisiROLU3A3ltr+swpniSmNPI4Ml3ZCX6tVnDsuZzK7F2hl5jTqbZBWCEKlj5HRQiPExXLgE8A==", - "dev": true - }, - "source-map": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", - "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", - "dev": true - }, - "spawn-sync": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz", - "integrity": "sha1-sAeZVX63+wyDdsKdROih6mfldHY=", - "dev": true, - "requires": { - "concat-stream": "1.6.0", - "os-shim": "0.1.3" - } - }, - "spdx-correct": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", - "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", - "dev": true, - "requires": { - "spdx-license-ids": "1.2.2" - } - }, - "spdx-expression-parse": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", - "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=", - "dev": true - }, - "spdx-license-ids": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", - "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=", - "dev": true - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "stream-browserify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", - "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.3" - } - }, - "stream-http": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.7.2.tgz", - "integrity": "sha512-c0yTD2rbQzXtSsFSVhtpvY/vS6u066PcXOX9kBB3mSO76RiUQzL340uJkGBWnlBg4/HZzqiUXtaVA7wcRcJgEw==", - "dev": true, - "requires": { - "builtin-status-codes": "3.0.0", - "inherits": "2.0.3", - "readable-stream": "2.3.3", - "to-arraybuffer": "1.0.1", - "xtend": "4.0.1" - } - }, - "string-width": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.0.tgz", - "integrity": "sha1-AwZkVh/BRslCPsfZeP4kV0N/5tA=", - "dev": true, - "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" + "sign-addon": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/sign-addon/-/sign-addon-0.3.0.tgz", + "integrity": "sha512-xc9beD51s4OUaBIstN9xXfZMzEVqi9iYXbrOTDivxb3Z6+nSw/X4q6tXqqJ+QP0kyMzQxLTagnRroC5wWVH5mQ==", + "dev": true, + "requires": { + "babel-polyfill": "6.16.0", + "deepcopy": "0.6.3", + "es6-error": "4.0.0", + "es6-promisify": "5.0.0", + "jsonwebtoken": "7.1.9", + "mz": "2.5.0", + "request": "2.79.0", + "source-map-support": "0.4.6", + "stream-to-promise": "2.2.0", + "when": "3.7.7" }, "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "assert-plus": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", "dev": true }, - "strip-ansi": { + "aws-sign2": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", + "dev": true + }, + "babel-polyfill": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.16.0.tgz", + "integrity": "sha1-LUUCHfh+JqN0ttTRqcZZZNF/JCI=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "core-js": "2.5.4", + "regenerator-runtime": "0.9.6" + } + }, + "boom": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "dev": true, + "requires": { + "hoek": "2.16.3" + } + }, + "caseless": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=", + "dev": true + }, + "cryptiles": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "dev": true, + "requires": { + "boom": "2.10.1" + } + }, + "es6-error": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.0.0.tgz", + "integrity": "sha1-8JTHBB9mJZm7EnINoFnWucf/D0A=", + "dev": true + }, + "form-data": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", "dev": true, "requires": { - "ansi-regex": "3.0.0" + "asynckit": "0.4.0", + "combined-stream": "1.0.6", + "mime-types": "2.1.18" + } + }, + "har-validator": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", + "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "commander": "2.11.0", + "is-my-json-valid": "2.17.2", + "pinkie-promise": "2.0.1" + } + }, + "hawk": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "dev": true, + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + } + }, + "hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", + "dev": true + }, + "http-signature": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "dev": true, + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.4.1", + "sshpk": "1.14.1" + } + }, + "mz": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.5.0.tgz", + "integrity": "sha1-KFkCXfA9RrV7sxcXSxlkd85kzsE=", + "dev": true, + "requires": { + "any-promise": "1.3.0", + "object-assign": "4.1.1", + "thenify-all": "1.6.0" + } + }, + "qs": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.3.2.tgz", + "integrity": "sha1-51vV9uJoEioqDgvaYwslUMFmUCw=", + "dev": true + }, + "regenerator-runtime": { + "version": "0.9.6", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.9.6.tgz", + "integrity": "sha1-0z65XQ0gAaS+OWWXB8UbDLcc4Ck=", + "dev": true + }, + "request": { + "version": "2.79.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.79.0.tgz", + "integrity": "sha1-Tf5b9r6LjNw3/Pk+BLZVd3InEN4=", + "dev": true, + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.6.0", + "caseless": "0.11.0", + "combined-stream": "1.0.6", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.1.4", + "har-validator": "2.0.6", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.18", + "oauth-sign": "0.8.2", + "qs": "6.3.2", + "stringstream": "0.0.5", + "tough-cookie": "2.3.4", + "tunnel-agent": "0.4.3", + "uuid": "3.2.1" + } + }, + "sntp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "dev": true, + "requires": { + "hoek": "2.16.3" } + }, + "source-map-support": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.6.tgz", + "integrity": "sha1-MlUqpktFg5KoXqs7C17mFScWeus=", + "dev": true, + "requires": { + "source-map": "0.5.6" + } + }, + "tunnel-agent": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", + "dev": true } } }, - "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.1" - } + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true }, - "stringify-entities": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-1.3.1.tgz", - "integrity": "sha1-sVDsLXKsTBtfMktR+2soyc3/BYw=", + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true + }, + "slice-ansi": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", + "dev": true + }, + "slide": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", + "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=", + "dev": true + }, + "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": { - "character-entities-html4": "1.1.1", - "character-entities-legacy": "1.1.1", - "is-alphanumerical": "1.0.1", - "is-hexadecimal": "1.0.1" + "base": "0.11.2", + "debug": "2.6.8", + "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.1", + "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.6" + } + }, + "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.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.2.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-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.2.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==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "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 + } } }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "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": { - "ansi-regex": "2.1.1" + "define-property": "1.0.0", + "isobject": "3.0.1", + "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.2" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } } }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "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": { - "is-utf8": "0.2.1" + "kind-of": "3.2.2" } }, - "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 - }, - "structured-source": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/structured-source/-/structured-source-3.0.2.tgz", - "integrity": "sha1-3YAkJeD1PcSm56yjdSkBoczaevU=", + "sntp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", + "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", "dev": true, "requires": { - "boundary": "1.0.1" + "hoek": "4.2.1" } }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - }, - "table": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.1.tgz", - "integrity": "sha1-qBFsEz+sLGH0pCCrbN9cTWHw5DU=", + "snyk": { + "version": "1.70.3", + "resolved": "https://registry.npmjs.org/snyk/-/snyk-1.70.3.tgz", + "integrity": "sha1-U8mJSdJ79XkF7re+7ZTTYKJP/1U=", "dev": true, "requires": { - "ajv": "4.11.8", - "ajv-keywords": "1.5.1", + "abbrev": "1.1.1", + "ansi-escapes": "1.4.0", "chalk": "1.1.3", - "lodash": "4.17.4", - "slice-ansi": "0.0.4", - "string-width": "2.1.0" + "configstore": "1.4.0", + "debug": "3.1.0", + "es6-promise": "3.3.1", + "hasbin": "1.2.3", + "inquirer": "1.0.3", + "needle": "2.2.0", + "open": "0.0.5", + "os-name": "1.0.3", + "proxy-from-env": "1.0.0", + "recursive-readdir": "2.2.2", + "semver": "5.3.0", + "snyk-config": "1.0.1", + "snyk-go-plugin": "1.4.5", + "snyk-gradle-plugin": "1.2.0", + "snyk-module": "1.8.1", + "snyk-mvn-plugin": "1.1.1", + "snyk-nuget-plugin": "1.3.9", + "snyk-php-plugin": "1.3.2", + "snyk-policy": "1.10.2", + "snyk-python-plugin": "1.5.7", + "snyk-resolve": "1.0.0", + "snyk-resolve-deps": "1.7.0", + "snyk-sbt-plugin": "1.2.5", + "snyk-tree": "1.0.0", + "snyk-try-require": "1.2.0", + "tempfile": "1.1.1", + "then-fs": "2.0.0", + "undefsafe": "0.0.3", + "update-notifier": "0.5.0", + "url": "0.11.0", + "uuid": "3.2.1" }, "dependencies": { - "ajv-keywords": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", - "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", + "ansi-escapes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", + "dev": true + }, + "cli-cursor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "dev": true, + "requires": { + "restore-cursor": "1.0.1" + } + }, + "debug": { + "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" + } + }, + "es6-promise": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", + "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=", + "dev": true + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "1.0.5", + "object-assign": "4.1.1" + } + }, + "got": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/got/-/got-3.3.1.tgz", + "integrity": "sha1-5dDtSvVfw+701WAHdp2YGSvLLso=", + "dev": true, + "requires": { + "duplexify": "3.5.4", + "infinity-agent": "2.0.3", + "is-redirect": "1.0.0", + "is-stream": "1.1.0", + "lowercase-keys": "1.0.0", + "nested-error-stacks": "1.0.2", + "object-assign": "3.0.0", + "prepend-http": "1.0.4", + "read-all-stream": "3.1.0", + "timed-out": "2.0.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 + } + } + }, + "inquirer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-1.0.3.tgz", + "integrity": "sha1-6+OglIVxvMRszMvi+bzsJR6YS9A=", + "dev": true, + "requires": { + "ansi-escapes": "1.4.0", + "chalk": "1.1.3", + "cli-cursor": "1.0.2", + "cli-width": "2.1.0", + "figures": "1.7.0", + "lodash": "4.17.4", + "mute-stream": "0.0.6", + "pinkie-promise": "2.0.1", + "run-async": "2.3.0", + "rx": "4.1.0", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "through": "2.3.8" + } + }, + "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.1" + } + }, + "latest-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-1.0.1.tgz", + "integrity": "sha1-cs/Ebj6NG+ZR4eu1Tqn26pbzdLs=", + "dev": true, + "requires": { + "package-json": "1.2.0" + } + }, + "mute-stream": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.6.tgz", + "integrity": "sha1-SJYrGeFp/R38JAs/HnMXYnu8R9s=", "dev": true + }, + "onetime": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "dev": true + }, + "package-json": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-1.2.0.tgz", + "integrity": "sha1-yOysCUInzfdqMWh07QXifMk5oOA=", + "dev": true, + "requires": { + "got": "3.3.1", + "registry-url": "3.1.0" + } + }, + "repeating": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz", + "integrity": "sha1-PUEUIYh3U3SU+X93+Xhfq4EPpKw=", + "dev": true, + "requires": { + "is-finite": "1.0.2" + } + }, + "restore-cursor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "dev": true, + "requires": { + "exit-hook": "1.1.1", + "onetime": "1.1.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "update-notifier": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-0.5.0.tgz", + "integrity": "sha1-B7XcIGazYnqztPUwEw9+3doHpMw=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "configstore": "1.4.0", + "is-npm": "1.0.0", + "latest-version": "1.0.1", + "repeating": "1.1.3", + "semver-diff": "2.1.0", + "string-length": "1.0.1" + } } } }, - "tapable": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.6.tgz", - "integrity": "sha1-IGvo4YiGC1FEJTdebxrom/sB/Y0=", - "dev": true + "snyk-config": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/snyk-config/-/snyk-config-1.0.1.tgz", + "integrity": "sha1-8nrsJJiyQCescZIUAmUhWRERUI8=", + "dev": true, + "requires": { + "debug": "2.6.8", + "nconf": "0.7.2", + "path-is-absolute": "1.0.1" + } }, - "tar": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", - "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "snyk-go-plugin": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/snyk-go-plugin/-/snyk-go-plugin-1.4.5.tgz", + "integrity": "sha512-uuPXt/NDROmG/pnQveOdur/ToG3h4W64F8r+3L7ZCMPikkRkieoCMGpfMYhEgG+oMlO1bzAsf+YGvMfY0o96Kg==", "dev": true, "requires": { - "block-stream": "0.0.9", - "fstream": "1.0.11", - "inherits": "2.0.3" + "graphlib": "2.1.5", + "toml": "2.3.3" } }, - "tar.gz": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tar.gz/-/tar.gz-1.0.5.tgz", - "integrity": "sha1-4a2n5F7yJBtLHuWBI8j0C108G8Q=", + "snyk-gradle-plugin": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/snyk-gradle-plugin/-/snyk-gradle-plugin-1.2.0.tgz", + "integrity": "sha512-FucMRR+Rc6LBaSIYxiBl+jvb7R00SgA0QfMT+RGxLIZlDk1lagvA/jIkv+mRadwHVSV/ShIFSZLmS7agfPclVg==", "dev": true, "requires": { - "bluebird": "2.11.0", - "commander": "2.11.0", - "fstream": "1.0.11", - "mout": "0.11.1", - "tar": "2.2.1" + "clone-deep": "0.3.0" + } + }, + "snyk-module": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/snyk-module/-/snyk-module-1.8.1.tgz", + "integrity": "sha1-MdUID7HA39b6hWfdNKUj/QK/H8o=", + "dev": true, + "requires": { + "debug": "2.6.8", + "hosted-git-info": "2.5.0" + } + }, + "snyk-mvn-plugin": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/snyk-mvn-plugin/-/snyk-mvn-plugin-1.1.1.tgz", + "integrity": "sha512-CkOAkOYVpEXm/c0peKNpEhbSIqb6SxNM28L5Rt5XZOkZ00Ud3uhz26+AicZVgvhe3in8A2CzOIAPyMUL2ueW4A==", + "dev": true + }, + "snyk-nuget-plugin": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/snyk-nuget-plugin/-/snyk-nuget-plugin-1.3.9.tgz", + "integrity": "sha512-F38Amr8AxbalFfUmjLM+57P2Gq2vUh9dWsP7oE2DPXO/f7tW00jwyWhJ5D39Zx+elBoXDxWYvAp14IJnxV18Ag==", + "dev": true, + "requires": { + "debug": "3.1.0", + "es6-promise": "4.2.4", + "xml2js": "0.4.17", + "zip": "1.2.0" }, "dependencies": { - "bluebird": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz", - "integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=", + "debug": { + "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" + } + }, + "es6-promise": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", + "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==", "dev": true } } }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true + "snyk-php-plugin": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/snyk-php-plugin/-/snyk-php-plugin-1.3.2.tgz", + "integrity": "sha512-EVN5ilP2PJ5EEBWUvSjzI1kHTRyJxqCQXm5Bb2Kkl4z1cNCFO9ScxjwUDO7cJmQCDQUhHGflDd611ToWmlEYnQ==", + "dev": true, + "requires": { + "debug": "3.1.0" + }, + "dependencies": { + "debug": { + "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" + } + } + } }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true + "snyk-policy": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/snyk-policy/-/snyk-policy-1.10.2.tgz", + "integrity": "sha1-Knvw8Hx7gRud2pPPm7sQ3Jkt17w=", + "dev": true, + "requires": { + "debug": "2.6.8", + "email-validator": "1.1.1", + "es6-promise": "3.3.1", + "js-yaml": "3.9.0", + "lodash.clonedeep": "4.5.0", + "semver": "5.3.0", + "snyk-module": "1.8.1", + "snyk-resolve": "1.0.0", + "snyk-try-require": "1.2.0", + "then-fs": "2.0.0" + }, + "dependencies": { + "es6-promise": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", + "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=", + "dev": true + } + } }, - "timed-out": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-2.0.0.tgz", - "integrity": "sha1-84sK6B03R9YoAB9B2vxlKs5nHAo=", + "snyk-python-plugin": { + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/snyk-python-plugin/-/snyk-python-plugin-1.5.7.tgz", + "integrity": "sha512-+BJ0ORdZSt62fIxDKkOi7TmKC9sFE9B4QXpoZV7Y0OtScEN99HYT7h4Jh3Hs8c9Vu40UmGfLQG1r78PoqxmM3Q==", "dev": true }, - "timers-browserify": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.2.tgz", - "integrity": "sha1-q0iDz1l9zVCvIRNJoA+8pWrIa4Y=", + "snyk-resolve": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/snyk-resolve/-/snyk-resolve-1.0.0.tgz", + "integrity": "sha1-u+kZbTf1fDklHmvnXM3Vsgl+maI=", "dev": true, "requires": { - "setimmediate": "1.0.5" + "debug": "2.6.8", + "then-fs": "2.0.0" } }, - "tmp": { - "version": "0.0.31", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.31.tgz", - "integrity": "sha1-jzirlDjhcxXl29izZX6L+yd65Kc=", + "snyk-resolve-deps": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/snyk-resolve-deps/-/snyk-resolve-deps-1.7.0.tgz", + "integrity": "sha1-E3Q6BYQ33/iQuq9DfDM8lmp0PLY=", "dev": true, "requires": { - "os-tmpdir": "1.0.2" + "abbrev": "1.1.1", + "ansicolors": "0.3.2", + "clite": "0.3.0", + "debug": "2.6.8", + "es6-promise": "3.3.1", + "lodash": "4.17.4", + "lru-cache": "4.1.2", + "minimist": "1.2.0", + "semver": "5.3.0", + "snyk-module": "1.8.1", + "snyk-resolve": "1.0.0", + "snyk-tree": "1.0.0", + "snyk-try-require": "1.2.0", + "then-fs": "2.0.0" + }, + "dependencies": { + "es6-promise": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", + "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=", + "dev": true + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } } }, - "to-arraybuffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", - "dev": true - }, - "traverse": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", - "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=", - "dev": true + "snyk-sbt-plugin": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/snyk-sbt-plugin/-/snyk-sbt-plugin-1.2.5.tgz", + "integrity": "sha512-6D981zAdFYatBLNwp7J5Vl5wZFieBlwKj1Ans9uZ5BZZfg4mjIX/62tfADmJEbHijvnN+i7N8cNQRvVOyLo2UA==", + "dev": true, + "requires": { + "debug": "2.6.8" + } }, - "trim": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", - "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=", - "dev": true + "snyk-tree": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/snyk-tree/-/snyk-tree-1.0.0.tgz", + "integrity": "sha1-D7cxdtvzLngvGRAClBYESPkRHMg=", + "dev": true, + "requires": { + "archy": "1.0.0" + } }, - "trim-trailing-lines": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.0.tgz", - "integrity": "sha1-eu+7eAjfnWafbaLkOMrIxGradoQ=", - "dev": true + "snyk-try-require": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/snyk-try-require/-/snyk-try-require-1.2.0.tgz", + "integrity": "sha1-MPwrEcBwZFke41eAyCa+kTEvIUQ=", + "dev": true, + "requires": { + "debug": "2.6.8", + "es6-promise": "3.3.1", + "lodash.clonedeep": "4.5.0", + "lru-cache": "4.1.2", + "then-fs": "2.0.0" + }, + "dependencies": { + "es6-promise": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", + "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=", + "dev": true + } + } }, - "trough": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.1.tgz", - "integrity": "sha1-qf2LA5Swro//guBjOgo2zK1bX4Y=", + "source-list-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.0.tgz", + "integrity": "sha512-I2UmuJSRr/T8jisiROLU3A3ltr+swpniSmNPI4Ml3ZCX6tVnDsuZzK7F2hl5jTqbZBWCEKlj5HRQiPExXLgE8A==", "dev": true }, - "tryit": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", - "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=", + "source-map": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", "dev": true }, - "tty-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", - "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "source-map-resolve": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.1.tgz", + "integrity": "sha512-0KW2wvzfxm8NCTb30z0LMNyPqWCdDGE2viwzUaucqJdkTRXtZiSY3I+2A6nVAjmdOy0I4gU8DwnVVGsk9jvP2A==", + "dev": true, + "requires": { + "atob": "2.1.0", + "decode-uri-component": "0.2.0", + "resolve-url": "0.2.1", + "source-map-url": "0.4.0", + "urix": "0.1.0" + } + }, + "source-map-support": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.3.tgz", + "integrity": "sha512-eKkTgWYeBOQqFGXRfKabMFdnWepo51vWqEdoeikaEPFiJC7MCU5j2h4+6Q8npkZTeLGbSyecZvRxiSoWl3rh+w==", + "dev": true, + "requires": { + "source-map": "0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "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 }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "spawn-sync": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz", + "integrity": "sha1-sAeZVX63+wyDdsKdROih6mfldHY=", "dev": true, "requires": { - "prelude-ls": "1.1.2" + "concat-stream": "1.6.0", + "os-shim": "0.1.3" } }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "spdx-correct": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", + "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", + "dev": true, + "requires": { + "spdx-license-ids": "1.2.2" + } + }, + "spdx-expression-parse": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", + "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=", "dev": true }, - "uglify-js": { - "version": "2.8.29", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "spdx-license-ids": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", + "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=", + "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": { - "source-map": "0.5.6", - "uglify-to-browserify": "1.0.2", - "yargs": "3.10.0" + "through": "2.3.8" + } + }, + "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.2" + } + }, + "split2": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz", + "integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==", + "dev": true, + "requires": { + "through2": "2.0.3" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", + "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", + "dev": true, + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + } + }, + "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": { - "yargs": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "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.6" + } + }, + "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.2.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-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.2.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==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "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 + } + } + }, + "stream-browserify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", + "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.3" + } + }, + "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-http": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.7.2.tgz", + "integrity": "sha512-c0yTD2rbQzXtSsFSVhtpvY/vS6u066PcXOX9kBB3mSO76RiUQzL340uJkGBWnlBg4/HZzqiUXtaVA7wcRcJgEw==", + "dev": true, + "requires": { + "builtin-status-codes": "3.0.0", + "inherits": "2.0.3", + "readable-stream": "2.3.3", + "to-arraybuffer": "1.0.1", + "xtend": "4.0.1" + } + }, + "stream-parser": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/stream-parser/-/stream-parser-0.3.1.tgz", + "integrity": "sha1-FhhUhpRCACGhGC/wrxkRwSl2F3M=", + "dev": true, + "requires": { + "debug": "2.6.8" + } + }, + "stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "dev": true + }, + "stream-to-array": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/stream-to-array/-/stream-to-array-2.3.0.tgz", + "integrity": "sha1-u/azn19D7DC8cbq8s3VXrOzzQ1M=", + "dev": true, + "requires": { + "any-promise": "1.3.0" + } + }, + "stream-to-promise": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/stream-to-promise/-/stream-to-promise-2.2.0.tgz", + "integrity": "sha1-se2y4cjLESidG1A8CNPyrvUeZQ8=", + "dev": true, + "requires": { + "any-promise": "1.3.0", + "end-of-stream": "1.1.0", + "stream-to-array": "2.3.0" + }, + "dependencies": { + "end-of-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.1.0.tgz", + "integrity": "sha1-6TUyWLqpEIll78QcsO+K3i88+wc=", + "dev": true, + "requires": { + "once": "1.3.3" + } + }, + "once": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", + "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + } + } + }, + "string-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz", + "integrity": "sha1-VpcPscOFWOnnC3KL894mmsRa36w=", + "dev": true, + "requires": { + "strip-ansi": "3.0.1" + } + }, + "string-width": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.0.tgz", + "integrity": "sha1-AwZkVh/BRslCPsfZeP4kV0N/5tA=", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + } + } + }, + "string.prototype.padend": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.0.0.tgz", + "integrity": "sha1-86rvfBcZ8XDF6rHDK/eA2W4h8vA=", + "dev": true, + "requires": { + "define-properties": "1.1.2", + "es-abstract": "1.11.0", + "function-bind": "1.1.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.1" + } + }, + "stringify-entities": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-1.3.1.tgz", + "integrity": "sha1-sVDsLXKsTBtfMktR+2soyc3/BYw=", + "dev": true, + "requires": { + "character-entities-html4": "1.1.1", + "character-entities-legacy": "1.1.1", + "is-alphanumerical": "1.0.1", + "is-hexadecimal": "1.0.1" + } + }, + "stringstream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", + "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=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-bom": { + "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.1" + } + }, + "strip-bom-buf": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-buf/-/strip-bom-buf-1.0.0.tgz", + "integrity": "sha1-HLRar1dTD0yvhsf3UXnSyaUd1XI=", + "dev": true, + "requires": { + "is-utf8": "0.2.1" + } + }, + "strip-bom-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-3.0.0.tgz", + "integrity": "sha1-lWvMXYRDD2klapDtgjdlzYWOFZw=", + "dev": true, + "requires": { + "first-chunk-stream": "2.0.0", + "strip-bom-buf": "1.0.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "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 + }, + "structured-source": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/structured-source/-/structured-source-3.0.2.tgz", + "integrity": "sha1-3YAkJeD1PcSm56yjdSkBoczaevU=", + "dev": true, + "requires": { + "boundary": "1.0.1" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "table": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.1.tgz", + "integrity": "sha1-qBFsEz+sLGH0pCCrbN9cTWHw5DU=", + "dev": true, + "requires": { + "ajv": "4.11.8", + "ajv-keywords": "1.5.1", + "chalk": "1.1.3", + "lodash": "4.17.4", + "slice-ansi": "0.0.4", + "string-width": "2.1.0" + }, + "dependencies": { + "ajv-keywords": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", + "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", + "dev": true + } + } + }, + "tapable": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.6.tgz", + "integrity": "sha1-IGvo4YiGC1FEJTdebxrom/sB/Y0=", + "dev": true + }, + "tar": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "dev": true, + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" + } + }, + "tar-stream": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.5.5.tgz", + "integrity": "sha512-mQdgLPc/Vjfr3VWqWbfxW8yQNiJCbAZ+Gf6GDu1Cy0bdb33ofyiNGBtAY96jHFhDuivCwgW1H9DgTON+INiXgg==", + "dev": true, + "requires": { + "bl": "1.2.2", + "end-of-stream": "1.4.1", + "readable-stream": "2.3.3", + "xtend": "4.0.1" + } + }, + "tar.gz": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tar.gz/-/tar.gz-1.0.5.tgz", + "integrity": "sha1-4a2n5F7yJBtLHuWBI8j0C108G8Q=", + "dev": true, + "requires": { + "bluebird": "2.11.0", + "commander": "2.11.0", + "fstream": "1.0.11", + "mout": "0.11.1", + "tar": "2.2.1" + }, + "dependencies": { + "bluebird": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz", + "integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=", + "dev": true + } + } + }, + "tempfile": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tempfile/-/tempfile-1.1.1.tgz", + "integrity": "sha1-W8xOrsxKsscH2LwR2ZzMmiyyh/I=", + "dev": true, + "requires": { + "os-tmpdir": "1.0.2", + "uuid": "2.0.3" + }, + "dependencies": { + "uuid": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", + "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=", + "dev": true + } + } + }, + "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": { + "execa": "0.7.0" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "then-fs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/then-fs/-/then-fs-2.0.0.tgz", + "integrity": "sha1-cveS3Z0xcFqRrhnr/Piz+WjIHaI=", + "dev": true, + "requires": { + "promise": "7.3.1" + } + }, + "thenify": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz", + "integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=", + "dev": true, + "requires": { + "any-promise": "1.3.0" + } + }, + "thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", + "dev": true, + "requires": { + "thenify": "3.3.0" + } + }, + "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=", + "dev": true, + "requires": { + "readable-stream": "2.3.3", + "xtend": "4.0.1" + } + }, + "timed-out": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-2.0.0.tgz", + "integrity": "sha1-84sK6B03R9YoAB9B2vxlKs5nHAo=", + "dev": true + }, + "timers-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.2.tgz", + "integrity": "sha1-q0iDz1l9zVCvIRNJoA+8pWrIa4Y=", + "dev": true, + "requires": { + "setimmediate": "1.0.5" + } + }, + "tmp": { + "version": "0.0.31", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.31.tgz", + "integrity": "sha1-jzirlDjhcxXl29izZX6L+yd65Kc=", + "dev": true, + "requires": { + "os-tmpdir": "1.0.2" + } + }, + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "dev": true + }, + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true + }, + "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, + "requires": { + "kind-of": "3.2.2" + } + }, + "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" + } + }, + "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" + }, + "dependencies": { + "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.2.2" + } + } + } + }, + "to-utf8": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/to-utf8/-/to-utf8-0.0.1.tgz", + "integrity": "sha1-0Xrqcv8vujm55DYBvns/9y4ImFI=", + "dev": true + }, + "toml": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/toml/-/toml-2.3.3.tgz", + "integrity": "sha512-O7L5hhSQHxuufWUdcTRPfuTh3phKfAZ/dqfxZFoxPCj2RYmpaSGLEIs016FCXItQwNr08yefUB5TSjzRYnajTA==", + "dev": true + }, + "topo": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/topo/-/topo-1.1.0.tgz", + "integrity": "sha1-6ddRYV0buH3IZdsYL6HKCl71NtU=", + "dev": true, + "requires": { + "hoek": "2.16.3" + }, + "dependencies": { + "hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", + "dev": true + } + } + }, + "tosource": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tosource/-/tosource-1.0.0.tgz", + "integrity": "sha1-QtiN0RZhi88A1hBt1URvNCeQL/E=", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "punycode": "1.4.1" + } + }, + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "dev": true, + "requires": { + "punycode": "2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz", + "integrity": "sha1-X4Y+3Im5bbCQdLrXlHvwkFbKTn0=", + "dev": true + } + } + }, + "traverse": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", + "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=", + "dev": true + }, + "trim": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", + "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=", + "dev": true + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "trim-trailing-lines": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.0.tgz", + "integrity": "sha1-eu+7eAjfnWafbaLkOMrIxGradoQ=", + "dev": true + }, + "trough": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.1.tgz", + "integrity": "sha1-qf2LA5Swro//guBjOgo2zK1bX4Y=", + "dev": true + }, + "tryit": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", + "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=", + "dev": true + }, + "tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true, + "optional": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "dev": true, + "requires": { + "source-map": "0.5.6", + "uglify-to-browserify": "1.0.2", + "yargs": "3.10.0" + }, + "dependencies": { + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dev": true, + "requires": { + "camelcase": "1.2.1", + "cliui": "2.1.0", + "decamelize": "1.2.0", + "window-size": "0.1.0" + } + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "dev": true, + "optional": true + }, + "undefsafe": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-0.0.3.tgz", + "integrity": "sha1-7Mo6A+VrmvFzhbqsgSrIO5lKli8=", + "dev": true + }, + "underscore": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=", + "dev": true + }, + "unherit": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.0.tgz", + "integrity": "sha1-a5qu379z3xdWrZ4xbdmBiFhAzX0=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "xtend": "4.0.1" + } + }, + "unified": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/unified/-/unified-4.2.1.tgz", + "integrity": "sha1-dv9Dqo2kMPbn5KVchOusKtLPzS4=", + "dev": true, + "requires": { + "bail": "1.0.2", + "extend": "3.0.1", + "has": "1.0.1", + "once": "1.4.0", + "trough": "1.0.1", + "vfile": "1.4.0" + } + }, + "union-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", + "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "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.1" + } + }, + "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.4", + "to-object-path": "0.3.0" + } + } + } + }, + "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": { + "crypto-random-string": "1.0.0" + } + }, + "unist-util-is": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-2.1.1.tgz", + "integrity": "sha1-DDEmKeP5YMZukx6BLT2A53AQlHs=", + "dev": true + }, + "unist-util-remove-position": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-1.1.1.tgz", + "integrity": "sha1-WoXBVV/BugwQG4ZwfRXlD6TIcbs=", + "dev": true, + "requires": { + "unist-util-visit": "1.3.0" + } + }, + "unist-util-visit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.3.0.tgz", + "integrity": "sha512-9ntYcxPFtl44gnwXrQKZ5bMqXMY0ZHzUpqMFiU4zcc8mmf/jzYm8GhYgezuUlX4cJIM1zIDYaO6fG/fI+L6iiQ==", + "dev": true, + "requires": { + "unist-util-is": "2.1.1" + } + }, + "universalify": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.0.tgz", + "integrity": "sha1-nrHEZR3rzGcMyU8adXYjMruWd3g=", + "dev": true + }, + "unset-value": { + "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.1" + }, + "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.6", + "has-values": "0.1.4", + "isobject": "2.1.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 + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "unzip-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-1.0.2.tgz", + "integrity": "sha1-uYTwh3/AqJwsdzzB73tbIytbBv4=", + "dev": true + }, + "upath": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.0.4.tgz", + "integrity": "sha512-d4SJySNBXDaQp+DPrziv3xGS6w3d2Xt69FijJr86zMPBy23JEloMCEOUBBzuN7xCtjLCnmB9tI/z7SBCahHBOw==", + "dev": true + }, + "update-notifier": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.3.0.tgz", + "integrity": "sha1-TognpruRUUCrCTVZ1wFOPruDdFE=", + "dev": true, + "requires": { + "boxen": "1.3.0", + "chalk": "2.3.2", + "configstore": "3.1.2", + "import-lazy": "2.1.0", + "is-installed-globally": "0.1.0", + "is-npm": "1.0.0", + "latest-version": "3.1.0", + "semver-diff": "2.1.0", + "xdg-basedir": "3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "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" + } + }, + "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": { + "ansi-align": "2.0.0", + "camelcase": "4.1.0", + "chalk": "2.3.2", + "cli-boxes": "1.0.0", + "string-width": "2.1.0", + "term-size": "1.2.0", + "widest-line": "2.0.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 + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "configstore": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", + "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==", + "dev": true, + "requires": { + "dot-prop": "4.2.0", + "graceful-fs": "4.1.11", + "make-dir": "1.2.0", + "unique-string": "1.0.0", + "write-file-atomic": "2.3.0", + "xdg-basedir": "3.0.0" + } + }, + "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": { + "is-obj": "1.0.1" + } + }, + "got": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", + "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", + "dev": true, + "requires": { + "create-error-class": "3.0.2", + "duplexer3": "0.1.4", + "get-stream": "3.0.0", + "is-redirect": "1.0.0", + "is-retry-allowed": "1.1.0", + "is-stream": "1.1.0", + "lowercase-keys": "1.0.0", + "safe-buffer": "5.1.1", + "timed-out": "4.0.1", + "unzip-response": "2.0.1", + "url-parse-lax": "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 + }, + "latest-version": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", + "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", + "dev": true, + "requires": { + "package-json": "4.0.1" + } + }, + "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.3.2", + "registry-url": "3.1.0", + "semver": "5.3.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + }, + "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 + }, + "unzip-response": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", + "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=", + "dev": true + }, + "widest-line": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.0.tgz", + "integrity": "sha1-AUKk6KJD+IgsAjOqDgKBqnYVInM=", + "dev": true, + "requires": { + "string-width": "2.1.1" + }, + "dependencies": { + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + } + } + } + }, + "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": { + "graceful-fs": "4.1.11", + "imurmurhash": "0.1.4", + "signal-exit": "3.0.2" + } + }, + "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 + } + } + }, + "update-section": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/update-section/-/update-section-0.3.3.tgz", + "integrity": "sha1-RY8Xgg03gg3GDiC4bZQ5GwASMVg=", + "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 + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, + "url-parse-lax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "dev": true, + "requires": { + "prepend-http": "1.0.4" + } + }, + "use": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.0.tgz", + "integrity": "sha512-6UJEQM/L+mzC3ZJNM56Q4DFGLX/evKGRg15UJHGB9X5j5Z3AFbgZvjUh2yq/UJUY4U5dh7Fal++XbNg1uzpRAw==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + }, + "dependencies": { + "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 + } + } + }, + "user-home": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", + "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", + "dev": true, + "requires": { + "os-homedir": "1.0.2" + } + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "requires": { + "inherits": "2.0.1" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + } + } + }, + "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.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", + "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", + "dev": true, + "requires": { + "spdx-correct": "1.0.2", + "spdx-expression-parse": "1.0.4" + } + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "1.3.0" + } + }, + "vfile": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-1.4.0.tgz", + "integrity": "sha1-wP1vpIT43r23cfaMMe112I2pf+c=", + "dev": true + }, + "vfile-location": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-2.0.2.tgz", + "integrity": "sha1-02dcWch3SY5JK0dW/2Xkrxp1IlU=", + "dev": true + }, + "vm-browserify": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", + "dev": true, + "requires": { + "indexof": "0.0.1" + } + }, + "watchpack": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.3.1.tgz", + "integrity": "sha1-fYaTkHsozmAT5/NhCqKhrPB9rYc=", + "dev": true, + "requires": { + "async": "2.5.0", + "chokidar": "1.7.0", + "graceful-fs": "4.1.11" + } + }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "dev": true, + "requires": { + "defaults": "1.0.3" + } + }, + "web-ext": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/web-ext/-/web-ext-2.6.0.tgz", + "integrity": "sha1-UsB0Ej5jdqT7Zz5WXDPdAncU6bA=", + "dev": true, + "requires": { + "@cliqz-oss/firefox-client": "0.3.1", + "@cliqz-oss/node-firefox-connect": "1.2.1", + "adbkit": "2.11.0", + "addons-linter": "0.41.0", + "babel-polyfill": "6.26.0", + "babel-runtime": "6.26.0", + "bunyan": "1.8.12", + "camelcase": "4.1.0", + "debounce": "1.1.0", + "decamelize": "2.0.0", + "es6-error": "4.1.1", + "es6-promisify": "5.0.0", + "event-to-promise": "0.8.0", + "firefox-profile": "1.1.0", + "fx-runner": "1.0.8", + "git-rev-sync": "1.9.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "mz": "2.7.0", + "node-notifier": "5.2.1", + "open": "0.0.5", + "parse-json": "4.0.0", + "regenerator-runtime": "0.11.1", + "require-uncached": "1.0.3", + "sign-addon": "0.3.0", + "source-map-support": "0.5.3", + "stream-to-promise": "2.2.0", + "strip-json-comments": "2.0.1", + "tmp": "0.0.33", + "update-notifier": "2.3.0", + "watchpack": "1.5.0", + "yargs": "6.6.0", + "zip-dir": "1.0.2" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "3.1.10", + "normalize-path": "2.1.1" + } + }, + "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 + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.1.tgz", + "integrity": "sha512-SO5lYHA3vO6gz66erVvedSCkp7AKWdv6VcQ2N4ysXfPxdAlxAMMAdwegGGcv1Bqwm7naF1hNdk5d6AAIEHV2nQ==", + "dev": true, + "requires": { + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "define-property": "1.0.0", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "kind-of": "6.0.2", + "repeat-element": "1.1.2", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "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.2" + } + }, + "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.1" + } + } + } + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "chokidar": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.3.tgz", + "integrity": "sha512-zW8iXYZtXMx4kux/nuZVXjkLP+CyIK5Al5FHnj1OgTKGZfp4Oy6/ymtMSKFv3GD8DviEmUPmJg9eFdJ/JzudMg==", + "dev": true, + "requires": { + "anymatch": "2.0.0", + "async-each": "1.0.1", + "braces": "2.3.1", + "fsevents": "1.1.2", + "glob-parent": "3.1.0", + "inherits": "2.0.3", + "is-binary-path": "1.0.1", + "is-glob": "4.0.0", + "normalize-path": "2.1.1", + "path-is-absolute": "1.0.1", + "readdirp": "2.1.0", + "upath": "1.0.4" + } + }, + "commander": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "dev": true, + "requires": { + "graceful-readlink": "1.0.1" + } + }, + "decamelize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz", + "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==", + "dev": true, + "requires": { + "xregexp": "4.0.0" + } + }, + "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.6.8", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "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.6" + } + }, + "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.1" + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "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 + } + } + }, + "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.2", + "snapdragon": "0.8.2", + "to-regex": "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.2" + } + }, + "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.1" + } + } + } + }, + "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.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.1" + } + } + } + }, + "fx-runner": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/fx-runner/-/fx-runner-1.0.8.tgz", + "integrity": "sha1-XO07BKjVHWNN4g0UgPDcXdgyXew=", + "dev": true, + "requires": { + "commander": "2.9.0", + "lodash": "3.10.1", + "shell-quote": "1.6.1", + "spawn-sync": "1.0.15", + "when": "3.7.7", + "which": "1.2.4", + "winreg": "0.0.12" + } + }, + "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.2" + }, + "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.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.2.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-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.2.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-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-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "dev": true, + "requires": { + "is-extglob": "2.1.1" + } + }, + "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.2.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" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "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 + }, + "lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "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.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + } + }, + "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": { + "error-ex": "1.3.1", + "json-parse-better-errors": "1.0.2" + } + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "1.0.2" + } + }, + "watchpack": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.5.0.tgz", + "integrity": "sha512-RSlipNQB1u48cq0wH/BNfCu1tD/cJ8ydFIkNYhp9o+3d+8unClkIovpW5qpFPgmL9OE48wfAnlZydXByWP82AA==", "dev": true, "requires": { - "camelcase": "1.2.1", - "cliui": "2.1.0", - "decamelize": "1.2.0", - "window-size": "0.1.0" + "chokidar": "2.0.3", + "graceful-fs": "4.1.11", + "neo-async": "2.5.0" } } } }, - "uglify-to-browserify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", - "dev": true, - "optional": true - }, - "underscore": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", - "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=", - "dev": true - }, - "unherit": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.0.tgz", - "integrity": "sha1-a5qu379z3xdWrZ4xbdmBiFhAzX0=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "xtend": "4.0.1" - } - }, - "unified": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/unified/-/unified-4.2.1.tgz", - "integrity": "sha1-dv9Dqo2kMPbn5KVchOusKtLPzS4=", - "dev": true, - "requires": { - "bail": "1.0.2", - "extend": "3.0.1", - "has": "1.0.1", - "once": "1.4.0", - "trough": "1.0.1", - "vfile": "1.4.0" - } - }, - "unist-util-is": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-2.1.1.tgz", - "integrity": "sha1-DDEmKeP5YMZukx6BLT2A53AQlHs=", - "dev": true - }, - "unist-util-remove-position": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-1.1.1.tgz", - "integrity": "sha1-WoXBVV/BugwQG4ZwfRXlD6TIcbs=", - "dev": true, - "requires": { - "unist-util-visit": "1.3.0" - } - }, - "unist-util-visit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.3.0.tgz", - "integrity": "sha512-9ntYcxPFtl44gnwXrQKZ5bMqXMY0ZHzUpqMFiU4zcc8mmf/jzYm8GhYgezuUlX4cJIM1zIDYaO6fG/fI+L6iiQ==", - "dev": true, - "requires": { - "unist-util-is": "2.1.1" - } - }, - "universalify": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.0.tgz", - "integrity": "sha1-nrHEZR3rzGcMyU8adXYjMruWd3g=", - "dev": true - }, - "unzip-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-1.0.2.tgz", - "integrity": "sha1-uYTwh3/AqJwsdzzB73tbIytbBv4=", - "dev": true - }, - "update-section": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/update-section/-/update-section-0.3.3.tgz", - "integrity": "sha1-RY8Xgg03gg3GDiC4bZQ5GwASMVg=", - "dev": true - }, - "url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "dev": true, - "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - }, - "dependencies": { - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "dev": true - } - } - }, - "url-parse-lax": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", - "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", - "dev": true, - "requires": { - "prepend-http": "1.0.4" - } - }, - "util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", - "dev": true, - "requires": { - "inherits": "2.0.1" - }, - "dependencies": { - "inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", - "dev": true - } - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "validate-npm-package-license": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", - "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", - "dev": true, - "requires": { - "spdx-correct": "1.0.2", - "spdx-expression-parse": "1.0.4" - } - }, - "vfile": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-1.4.0.tgz", - "integrity": "sha1-wP1vpIT43r23cfaMMe112I2pf+c=", - "dev": true - }, - "vfile-location": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-2.0.2.tgz", - "integrity": "sha1-02dcWch3SY5JK0dW/2Xkrxp1IlU=", + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", "dev": true }, - "vm-browserify": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", - "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", - "dev": true, - "requires": { - "indexof": "0.0.1" - } - }, - "watchpack": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.3.1.tgz", - "integrity": "sha1-fYaTkHsozmAT5/NhCqKhrPB9rYc=", - "dev": true, - "requires": { - "async": "2.5.0", - "chokidar": "1.7.0", - "graceful-fs": "4.1.11" - } - }, "webpack": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-2.7.0.tgz", @@ -4732,6 +11142,17 @@ "source-map": "0.5.6" } }, + "whatwg-url": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.3.0.tgz", + "integrity": "sha512-rM+hE5iYKGPAOu05mIdJR47pYSR2vDzfrTEFRc/S8D3L60yW8BuXmUJ7Kog7x/DrokFN7JNaHKadpzjouKRRAw==", + "dev": true, + "requires": { + "lodash.sortby": "4.7.0", + "tr46": "1.0.1", + "webidl-conversions": "4.0.2" + } + }, "when": { "version": "3.7.7", "resolved": "https://registry.npmjs.org/when/-/when-3.7.7.tgz", @@ -4754,6 +11175,46 @@ "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", "dev": true }, + "widest-line": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-1.0.0.tgz", + "integrity": "sha1-DAnIXCqUaD0Nfq+O4JfVZL8OEFw=", + "dev": true, + "requires": { + "string-width": "1.0.2" + }, + "dependencies": { + "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.1" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + } + } + }, + "win-release": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/win-release/-/win-release-1.1.1.tgz", + "integrity": "sha1-X6VeAr58qTTt/BJmVjLoSbcuUgk=", + "dev": true, + "requires": { + "semver": "5.3.0" + } + }, "window-size": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", @@ -4819,6 +11280,26 @@ "mkdirp": "0.5.1" } }, + "write-file-atomic": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.4.tgz", + "integrity": "sha1-+Aek8LHZ6ROuekgRLmzDrxmRtF8=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "imurmurhash": "0.1.4", + "slide": "1.1.6" + } + }, + "xdg-basedir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-2.0.0.tgz", + "integrity": "sha1-7byQPMOF/ARSPZZqM1UEtVBNG9I=", + "dev": true, + "requires": { + "os-homedir": "1.0.2" + } + }, "xml2js": { "version": "0.4.17", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.17.tgz", @@ -4838,6 +11319,18 @@ "lodash": "4.17.4" } }, + "xmldom": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz", + "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=", + "dev": true + }, + "xregexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz", + "integrity": "sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==", + "dev": true + }, "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", @@ -4850,6 +11343,12 @@ "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", "dev": true }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, "yargs": { "version": "6.6.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz", @@ -4926,6 +11425,55 @@ "dev": true } } + }, + "yauzl": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.9.1.tgz", + "integrity": "sha1-qBmB6nCleUYTOIPwKcWCGok1mn8=", + "dev": true, + "requires": { + "buffer-crc32": "0.2.13", + "fd-slicer": "1.0.1" + } + }, + "zip": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/zip/-/zip-1.2.0.tgz", + "integrity": "sha1-rQrUImUwm+QutW/IYZThfCTmapw=", + "dev": true, + "requires": { + "bops": "0.1.1" + } + }, + "zip-dir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/zip-dir/-/zip-dir-1.0.2.tgz", + "integrity": "sha1-JT+QeurWKiGs2HIdi4gDKyQRwFE=", + "dev": true, + "requires": { + "async": "1.5.2", + "jszip": "2.6.1" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + } + } + }, + "zip-stream": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-1.2.0.tgz", + "integrity": "sha1-qLxF9MG0lpnGuQGYuqyqzbzUugQ=", + "dev": true, + "requires": { + "archiver-utils": "1.3.0", + "compress-commons": "1.2.2", + "lodash": "4.17.4", + "readable-stream": "2.3.3" + } } } } From ffd015ae511972cf6d0dd2e1dd8dfd7dc9491219 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Tue, 3 Apr 2018 11:27:47 +0300 Subject: [PATCH 47/50] Restored npm run test after test add-on path change --- .../bin/bundle-shield-studies-addon-utils.sh | 18 +++++++++++------- examples/test-addon/test/functional/utils.js | 10 +++++----- package.json | 8 ++++---- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/examples/test-addon/bin/bundle-shield-studies-addon-utils.sh b/examples/test-addon/bin/bundle-shield-studies-addon-utils.sh index 9ea9201..33d78fb 100755 --- a/examples/test-addon/bin/bundle-shield-studies-addon-utils.sh +++ b/examples/test-addon/bin/bundle-shield-studies-addon-utils.sh @@ -5,14 +5,18 @@ set -o errexit # always run from the repository root directory script_path=`dirname $0` -cd "$script_path/../../" +cd "$script_path/../../../" + +# paths +WEBEXTAPIS_PATH="webExtensionApis" +ADDON_SRC_PATH="examples/test-addon/src" # bundle the study web extension experiment -mkdir -p test-addon/src/privileged/study -cp webExtensionApis/study/api.js test-addon/src/privileged/study/api.js -cp webExtensionApis/study/schema.json test-addon/src/privileged/study/schema.json +mkdir -p $ADDON_SRC_PATH/privileged/study +cp $WEBEXTAPIS_PATH/study/api.js $ADDON_SRC_PATH/privileged/study/api.js +cp $WEBEXTAPIS_PATH/study/schema.json $ADDON_SRC_PATH/privileged/study/schema.json # bundle the prefs web extension experiment -mkdir -p test-addon/src/privileged/prefs -cp webExtensionApis/prefs/api.js test-addon/src/privileged/prefs/api.js -cp webExtensionApis/prefs/schema.json test-addon/src/privileged/prefs/schema.json +mkdir -p $ADDON_SRC_PATH/privileged/prefs +cp $WEBEXTAPIS_PATH/prefs/api.js $ADDON_SRC_PATH/privileged/prefs/api.js +cp $WEBEXTAPIS_PATH/prefs/schema.json $ADDON_SRC_PATH/privileged/prefs/schema.json diff --git a/examples/test-addon/test/functional/utils.js b/examples/test-addon/test/functional/utils.js index 4f0b6cf..41a8e68 100644 --- a/examples/test-addon/test/functional/utils.js +++ b/examples/test-addon/test/functional/utils.js @@ -32,11 +32,11 @@ const FIREFOX_PREFERENCES = { }; // Re-usable test methods from shield-studies-addon-utils -const { executeJs } = require("../../../testUtils/executeJs"); -const { nav } = require("../../../testUtils/nav"); -const { pings } = require("../../../testUtils/pings"); -const { setup } = require("../../../testUtils/setup"); -const { ui } = require("../../../testUtils/ui"); +const { executeJs } = require("../../../../testUtils/executeJs"); +const { nav } = require("../../../../testUtils/nav"); +const { pings } = require("../../../../testUtils/pings"); +const { setup } = require("../../../../testUtils/setup"); +const { ui } = require("../../../../testUtils/ui"); // What we expose to our add-on-specific tests module.exports = { diff --git a/package.json b/package.json index 538ba04..f77aa61 100644 --- a/package.json +++ b/package.json @@ -59,9 +59,9 @@ "prepack": "fixpack && npm run build", "pretest": "npm run build && npm run test-addon:bundle-utils && npm run test-addon:build", "pretest-addon:run": "npm run pretest", - "test": "FIREFOX_BINARY=${FIREFOX_BINARY:-firefox} ADDON_ZIP=test-addon/dist/shield_utils_test_add-on-1.0.0.zip mocha test-addon/test/functional/ --bail", - "test-addon:build": "cd test-addon && web-ext build", - "test-addon:bundle-utils": "test-addon/bin/bundle-shield-studies-addon-utils.sh", - "test-addon:run": "cd test-addon && web-ext run --no-reload" + "test": "FIREFOX_BINARY=${FIREFOX_BINARY:-firefox} ADDON_ZIP=examples/test-addon/dist/shield_utils_test_add-on-1.0.0.zip mocha examples/test-addon/test/functional/ --bail", + "test-addon:build": "cd examples/test-addon && web-ext build", + "test-addon:bundle-utils": "examples/test-addon/bin/bundle-shield-studies-addon-utils.sh", + "test-addon:run": "cd examples/test-addon && web-ext run --no-reload" } } From fd82f9209dee90fe41f557023c94b4c3c0c8af04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Tue, 3 Apr 2018 13:17:10 +0300 Subject: [PATCH 48/50] Smaller method and variable refactoring --- examples/test-addon/src/background.js | 2 +- webExtensionApis/study/schema.json | 4 ++-- webExtensionApis/study/src/index.js | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/test-addon/src/background.js b/examples/test-addon/src/background.js index a77cf69..040b3d2 100644 --- a/examples/test-addon/src/background.js +++ b/examples/test-addon/src/background.js @@ -53,7 +53,7 @@ async function initiateStudy() { studySetup.eligible = await Study.isEligible(); studySetup.expired = await Study.hasExpired(); // Ensure we have configured study and are supposed to run our feature - await browser.study.bootstrapStudy(studySetup); + await browser.study.configure(studySetup); // Get study variation const { variation } = await browser.study.info(); // Initiate the study diff --git a/webExtensionApis/study/schema.json b/webExtensionApis/study/schema.json index 2149c00..eb0160f 100644 --- a/webExtensionApis/study/schema.json +++ b/webExtensionApis/study/schema.json @@ -4,13 +4,13 @@ "description": "study", "functions": [ { - "name": "bootstrapStudy", + "name": "configure", "type": "function", "description": "TODO", "async": true, "parameters": [ { - "name": "studyConfig", + "name": "studySetup", "type": "any" } ] diff --git a/webExtensionApis/study/src/index.js b/webExtensionApis/study/src/index.js index 4a5b4cf..c8d883c 100644 --- a/webExtensionApis/study/src/index.js +++ b/webExtensionApis/study/src/index.js @@ -45,9 +45,9 @@ this.study = class extends ExtensionAPI { * and are supposed to run our feature * @returns {Promise} */ - async bootstrapStudy(studyConfig) { + async configure(studySetup) { const bootstrap = studyUtilsBootstrap.Bootstrap( - studyConfig, + studySetup, studyUtils, ); await bootstrap.startup(extension); From 41f06798ce92d4eefc51523b193ed696cc6a65b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Tue, 3 Apr 2018 17:14:47 +0300 Subject: [PATCH 49/50] =?UTF-8?q?Added=20browser.study.deterministicVariat?= =?UTF-8?q?ion=20and=20browser.study.startup=20+=20restored=20some=20initi?= =?UTF-8?q?al=20tests:=20=20=20=20=20=E2=9C=93=20should=20be=20able=20to?= =?UTF-8?q?=20access=20window.browser=20from=20the=20extension=20page=20fo?= =?UTF-8?q?r=20tests=20(1450ms)=20=20=20=20=20=E2=9C=93=20should=20be=20ab?= =?UTF-8?q?le=20to=20access=20study=20WebExtensions=20API=20from=20the=20e?= =?UTF-8?q?xtension=20page=20for=20tests=20(67ms)=20=20=20=20=20=E2=9C=93?= =?UTF-8?q?=20should=20return=20the=20correct=20variation=20based=20on=20s?= =?UTF-8?q?pecific=20weightedVariations=20(165ms)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + examples/test-addon/src/background.js | 6 ++- examples/test-addon/src/studySetup.js | 12 ----- .../test/functional/shield_utils_test.js | 45 +++++++++---------- webExtensionApis/study/schema.json | 24 ++++++++++ webExtensionApis/study/src/index.js | 21 +++++++-- .../study/src/schemas/schema.studySetup.json | 18 +++++++- webExtensionApis/study/src/studyUtils.js | 16 +++---- .../study/src/studyUtilsBootstrap.js | 14 +++--- 9 files changed, 99 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index 6f0d1a6..2638281 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ | [Add-on template](https://github.com/mozilla/shield-studies-addon-template/) | [Engineering hints](#engineering-and-process) | [More documentation](./docs/) | [Shield - Mozilla Wiki](https://wiki.mozilla.org/Firefox/Shield) | | ---------------------------------------------------------------------------- | --------------------------------------------- | ----------------------------- | ---------------------------------------------------------------- | + # Shield Studies Add-on Utils [![Build Status](https://travis-ci.org/mozilla/shield-studies-addon-utils.svg?branch=master)](https://travis-ci.org/mozilla/shield-studies-addon-utils) diff --git a/examples/test-addon/src/background.js b/examples/test-addon/src/background.js index 040b3d2..2ff5f72 100644 --- a/examples/test-addon/src/background.js +++ b/examples/test-addon/src/background.js @@ -54,9 +54,11 @@ async function initiateStudy() { studySetup.expired = await Study.hasExpired(); // Ensure we have configured study and are supposed to run our feature await browser.study.configure(studySetup); - // Get study variation + // Run the startup study checks + await browser.study.startup(); + // Read the active study variation const { variation } = await browser.study.info(); - // Initiate the study + // Initiate our study-specific background logic new Study(variation); } diff --git a/examples/test-addon/src/studySetup.js b/examples/test-addon/src/studySetup.js index 0efb776..b000565 100644 --- a/examples/test-addon/src/studySetup.js +++ b/examples/test-addon/src/studySetup.js @@ -54,18 +54,6 @@ const studySetup = { }, }, - // required LOG key - log: { - // Fatal: 70, Error: 60, Warn: 50, Info: 40, Config: 30, Debug: 20, Trace: 10, All: -1, - bootstrap: { - // Console.jsm uses "debug", whereas Log.jsm uses "Debug", *sigh* - level: "debug", - }, - studyUtils: { - level: "Trace", - }, - }, - /* Button study branches and sample weights - test kittens vs. puppies if we can only have one. - downweight lizards. Lizards is a 'poison' branch, meant to diff --git a/examples/test-addon/test/functional/shield_utils_test.js b/examples/test-addon/test/functional/shield_utils_test.js index c9d7e59..ed16e75 100644 --- a/examples/test-addon/test/functional/shield_utils_test.js +++ b/examples/test-addon/test/functional/shield_utils_test.js @@ -20,6 +20,8 @@ describe("Shield Study Add-on Utils Functional Tests", function() { await utils.setup.installAddon(driver); }); + // hint: skipping driver.quit() may be useful when debugging failed tests, + // leaving the browser open allowing inspection of the ui and browser logs after(() => driver.quit()); it("should be able to access window.browser from the extension page for tests", async () => { @@ -42,17 +44,11 @@ describe("Shield Study Add-on Utils Functional Tests", function() { assert(hasAccessToShieldUtilsWebExtensionApi); }); - /* - it("should return the correct variation", async () => { - const variation = await driver.executeAsyncScript(async callback => { - const { studyUtils } = Components.utils.import( - "resource://test-addon/StudyUtils.jsm", - {}, - ); - // TODO move this to a Config.jsm file - const studyConfig = { - studyName: "shieldStudyUtilsTest", - weightedVariations: [ + it("should return the correct variation based on specific weightedVariations", async () => { + const chosenVariation = await utils.executeJs.executeAsyncScriptInExtensionPageForTests( + driver, + async callback => { + const weightedVariations = [ { name: "control", weight: 1, @@ -64,19 +60,19 @@ describe("Shield Study Add-on Utils Functional Tests", function() { { name: "puppers", weight: 2, - }, // we want more puppers in our sample - ], - }; - - const sample = studyUtils.sample; - const hashFraction = await sample.hashFraction("test"); - const chosenVariation = await sample.chooseWeighted( - studyConfig.weightedVariations, - hashFraction, - ); - callback(chosenVariation); - }); - assert(variation.name === "puppers"); + }, + ]; + + const fraction = 0.3; + const variation = await browser.study.deterministicVariation( + weightedVariations, + fraction, + ); + + callback(variation); + }, + ); + assert(chosenVariation.name === "kittens"); }); it("telemetry should be working", async () => { @@ -271,5 +267,4 @@ describe("Shield Study Add-on Utils Functional Tests", function() { assert(pings.payload.data.study_state === "exit"); }); }); - */ }); diff --git a/webExtensionApis/study/schema.json b/webExtensionApis/study/schema.json index eb0160f..1807617 100644 --- a/webExtensionApis/study/schema.json +++ b/webExtensionApis/study/schema.json @@ -15,6 +15,30 @@ } ] }, + { + "name": "deterministicVariation", + "type": "function", + "description": "TODO", + "async": true, + "parameters": [ + { + "name": "weightedVariations", + "type": "any" + }, + { + "optional": true, + "name": "fraction", + "type": "any" + } + ] + }, + { + "name": "startup", + "type": "function", + "description": "TODO", + "async": true, + "parameters": [] + }, { "name": "info", "type": "function", diff --git a/webExtensionApis/study/src/index.js b/webExtensionApis/study/src/index.js index c8d883c..53ddf6b 100644 --- a/webExtensionApis/study/src/index.js +++ b/webExtensionApis/study/src/index.js @@ -36,6 +36,8 @@ this.study = class extends ExtensionAPI { // const { PioneerUtils } = require("pioneer-utils/PioneerUtils.jsm"); // const pioneerUtilsBootstrap = require("./pioneerUtilsBootstrap.js"); + let bootstrap; + const { extension } = this; return { @@ -46,13 +48,24 @@ this.study = class extends ExtensionAPI { * @returns {Promise} */ async configure(studySetup) { - const bootstrap = studyUtilsBootstrap.Bootstrap( - studySetup, - studyUtils, - ); + bootstrap = studyUtilsBootstrap.Bootstrap(studySetup, studyUtils); + await bootstrap.configure(extension); + }, + + async startup() { await bootstrap.startup(extension); }, + async deterministicVariation(weightedVariations, fraction) { + if (typeof fraction === "string") { + fraction = parseFloat(fraction); + } + return await studyUtils.deterministicVariation( + weightedVariations, + fraction, + ); + }, + /** * current studyUtils configuration, including 'variation' * @returns {Promise} diff --git a/webExtensionApis/study/src/schemas/schema.studySetup.json b/webExtensionApis/study/src/schemas/schema.studySetup.json index ef4616a..cb90023 100644 --- a/webExtensionApis/study/src/schemas/schema.studySetup.json +++ b/webExtensionApis/study/src/schemas/schema.studySetup.json @@ -34,6 +34,16 @@ "type": "string" } } + }, + "variation": { + "name": { + "type": "string" + }, + "weight": { + "type": "number", + "minimum": 0 + }, + "required": ["name", "weight"] } }, "properties": { @@ -69,6 +79,12 @@ }, "required": ["studyName", "endings", "telemetry"] }, + "weightedVariations": { + "items": { + "$ref": "#/definitions/variation" + }, + "type": "array" + }, "addon": { "type": "object", "properties": { @@ -84,5 +100,5 @@ "required": ["id", "version"] } }, - "required": ["study", "addon"] + "required": ["study", "weightedVariations", "addon"] } diff --git a/webExtensionApis/study/src/studyUtils.js b/webExtensionApis/study/src/studyUtils.js index d6860cc..d5fc9ba 100644 --- a/webExtensionApis/study/src/studyUtils.js +++ b/webExtensionApis/study/src/studyUtils.js @@ -30,6 +30,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.importGlobalProperties(["URL", "crypto", "URLSearchParams"]); let log; +const studyUtilsLoggingLevel = "Trace"; // Fatal: 70, Error: 60, Warn: 50, Info: 40, Config: 30, Debug: 20, Trace: 10, All: -1, // telemetry utils const CID = Cu.import("resource://gre/modules/ClientID.jsm", null); @@ -272,11 +273,9 @@ class StudyUtils { * @returns {StudyUtils} - the StudyUtils class instance */ setup(studySetup) { - log = createLog("shield-study-utils", studySetup.log.studyUtils.level); - + log = createLog("shield-study-utils", studyUtilsLoggingLevel); log.debug("setting up!"); jsonschema.validateOrThrow(studySetup, schemas.studySetup); - this.studySetup = studySetup; this._isSetup = true; return this; @@ -355,16 +354,15 @@ class StudyUtils { * @async * Deterministically selects and returns the study variation for the user. * @param {Object[]} weightedVariations - see schema.weightedVariations.json - * @param {Number} rng - randomly generated number (0 <= rng < 1) of the form - * returned by Math.random; can be set explicitly for testing + * @param {Number} fraction - a number (0 <= fraction < 1); can be set explicitly for testing * @returns {Object} - the study variation for this user */ - async deterministicVariation(weightedVariations, rng = null) { - // hash the studyName and telemetryId to get the same branch every time. - this.throwIfNotSetup("deterministicVariation needs studyName"); + async deterministicVariation(weightedVariations, fraction = null) { + console.log("deterministicVariation", arguments); // this is the standard arm choosing method - let fraction = rng; if (fraction === null) { + // hash the studyName and telemetryId to get the same branch every time. + this.throwIfNotSetup("deterministicVariation needs studyName"); const clientId = await this.getTelemetryId(); const studyName = this.studySetup.study.studyName; fraction = await this.sampling.hashFraction(studyName + clientId, 12); diff --git a/webExtensionApis/study/src/studyUtilsBootstrap.js b/webExtensionApis/study/src/studyUtilsBootstrap.js index a0e4ac5..ff419f6 100644 --- a/webExtensionApis/study/src/studyUtilsBootstrap.js +++ b/webExtensionApis/study/src/studyUtilsBootstrap.js @@ -18,10 +18,8 @@ this.Bootstrap = function(studySetup, studyUtils) { * @param reason * @returns {Promise} */ - async startup(extension) { - const { manifest, startupReason } = extension; - - this.log.debug("startup", startupReason); + async configure(extension) { + const { manifest } = extension; const addonId = manifest.applications.gecko.id; const addonVersion = manifest.version; @@ -29,6 +27,12 @@ this.Bootstrap = function(studySetup, studyUtils) { // choose and set variation await this.selectVariation(); + }, + + async startup(extension) { + const { startupReason } = extension; + + this.log.debug("startup", startupReason); // Check if the user is eligible to run this study using the |isEligible| // function when the study is initialized @@ -68,7 +72,7 @@ this.Bootstrap = function(studySetup, studyUtils) { studySetup.addon = { id, version }; studyUtils.setup(studySetup); // TODO bdanforth: patch studyUtils to setLoggingLevel as part of setup method - studyUtils.setLoggingLevel(studySetup.log.studyUtils.level); + //studyUtils.setLoggingLevel(...); }, // choose the variation for this particular user, then set it. From beda8c26f665f516480aab7738f3d6ab94ac069c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Wollse=CC=81n?= Date: Tue, 3 Apr 2018 17:15:13 +0300 Subject: [PATCH 50/50] Removed webExtensionMsg schema since no longer relevant --- webExtensionApis/study/src/schemas/index.js | 1 - .../src/schemas/schema.webExtensionMsg.json | 18 ------------------ 2 files changed, 19 deletions(-) delete mode 100644 webExtensionApis/study/src/schemas/schema.webExtensionMsg.json diff --git a/webExtensionApis/study/src/schemas/index.js b/webExtensionApis/study/src/schemas/index.js index 8ea22b3..9ab0a98 100644 --- a/webExtensionApis/study/src/schemas/index.js +++ b/webExtensionApis/study/src/schemas/index.js @@ -5,6 +5,5 @@ module.exports = { "shield-study-addon": require("shield-study-schemas/schemas-client/shield-study-addon.schema.json"), // eslint-disable-line max-len "shield-study-error": require("shield-study-schemas/schemas-client/shield-study-error.schema.json"), // eslint-disable-line max-len studySetup: require("./schema.studySetup.json"), - webExtensionMsg: require("./schema.webExtensionMsg.json"), weightedVariations: require("./schema.weightedVariations.json"), }; diff --git a/webExtensionApis/study/src/schemas/schema.webExtensionMsg.json b/webExtensionApis/study/src/schemas/schema.webExtensionMsg.json deleted file mode 100644 index c5df40b..0000000 --- a/webExtensionApis/study/src/schemas/schema.webExtensionMsg.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "webExtension browserMessage", - "type": "object", - "properties": { - "shield": { - "type": "boolean" - }, - "msg": { - "type": "string", - "enum": ["endStudy", "telemetry", "info"] - }, - "data": { - "type": "object" - } - }, - "required": ["shield", "msg"] -}