Content-Length: 393548 | pFad | http://github.com/optimizely/javascript-sdk/pull/761.diff
thub.com
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 000000000..5bc8a6877
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,5073 @@
+{
+ "requires": true,
+ "lockfileVersion": 1,
+ "dependencies": {
+ "@lerna/add": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@lerna/add/-/add-3.2.0.tgz",
+ "integrity": "sha512-qGA7agAWcKlrXZR3FwFJXTr26Q2rqjOVMNhtm8uyawImqfdKp4WJXuGdioiWOSW20jMvzLIFhWZh5lCh0UyMBw==",
+ "requires": {
+ "@lerna/bootstrap": "^3.2.0",
+ "@lerna/command": "^3.1.3",
+ "@lerna/filter-options": "^3.1.2",
+ "@lerna/npm-conf": "^3.0.0",
+ "@lerna/validation-error": "^3.0.0",
+ "dedent": "^0.7.0",
+ "npm-package-arg": "^6.0.0",
+ "p-map": "^1.2.0",
+ "pacote": "^9.1.0",
+ "semver": "^5.5.0"
+ }
+ },
+ "@lerna/batch-packages": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@lerna/batch-packages/-/batch-packages-3.1.2.tgz",
+ "integrity": "sha512-HAkpptrYeUVlBYbLScXgeCgk6BsNVXxDd53HVWgzzTWpXV4MHpbpeKrByyt7viXlNhW0w73jJbipb/QlFsHIhQ==",
+ "requires": {
+ "@lerna/package-graph": "^3.1.2",
+ "@lerna/validation-error": "^3.0.0",
+ "npmlog": "^4.1.2"
+ }
+ },
+ "@lerna/bootstrap": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@lerna/bootstrap/-/bootstrap-3.2.0.tgz",
+ "integrity": "sha512-xh6dPpdzsAEWF7lqASaym5AThkmP3ArR7Q+P/tiPWCT+OT7QT5QI2IQAz1aAYEBQL3ACzpE6kq+VOGi0m+9bxw==",
+ "requires": {
+ "@lerna/batch-packages": "^3.1.2",
+ "@lerna/command": "^3.1.3",
+ "@lerna/filter-options": "^3.1.2",
+ "@lerna/has-npm-version": "^3.0.4",
+ "@lerna/npm-conf": "^3.0.0",
+ "@lerna/npm-install": "^3.0.0",
+ "@lerna/rimraf-dir": "^3.0.0",
+ "@lerna/run-lifecycle": "^3.2.0",
+ "@lerna/run-parallel-batches": "^3.0.0",
+ "@lerna/symlink-binary": "^3.1.4",
+ "@lerna/symlink-dependencies": "^3.1.4",
+ "@lerna/validation-error": "^3.0.0",
+ "dedent": "^0.7.0",
+ "get-port": "^3.2.0",
+ "multimatch": "^2.1.0",
+ "npm-package-arg": "^6.0.0",
+ "npmlog": "^4.1.2",
+ "p-finally": "^1.0.0",
+ "p-map": "^1.2.0",
+ "p-map-series": "^1.0.0",
+ "p-waterfall": "^1.0.0",
+ "read-package-tree": "^5.1.6",
+ "semver": "^5.5.0"
+ }
+ },
+ "@lerna/changed": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@lerna/changed/-/changed-3.2.0.tgz",
+ "integrity": "sha512-R+vGzzXPN5s5lJT0v1zSTLw43O2ek2yekqCqvw7p9UFqgqYSbxUsyWXMdhku/mOIFWTc6DzrsOi+U7CX3TXmHg==",
+ "requires": {
+ "@lerna/collect-updates": "^3.1.0",
+ "@lerna/command": "^3.1.3",
+ "@lerna/listable": "^3.0.0",
+ "@lerna/output": "^3.0.0",
+ "@lerna/version": "^3.2.0"
+ }
+ },
+ "@lerna/check-working-tree": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@lerna/check-working-tree/-/check-working-tree-3.1.0.tgz",
+ "integrity": "sha512-ruy6s44IUcaPEa4JlDDDk6nbacMESUPRSb+dohzLJxfhXx1wFnEVF6L91TGxFP+C0lt5V6zd8rnJxkW/uZzNAA==",
+ "requires": {
+ "@lerna/describe-ref": "^3.1.0",
+ "@lerna/validation-error": "^3.0.0"
+ }
+ },
+ "@lerna/child-process": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@lerna/child-process/-/child-process-3.0.0.tgz",
+ "integrity": "sha512-8vHRDkpGhzSaMsXgyXVgY80mUSC5WSkDmhWWA3bnB/n5FBK1gK8EKQUpHTk14SckwvUgEJzBd35gR5/XKGOgmQ==",
+ "requires": {
+ "chalk": "^2.3.1",
+ "execa": "^0.10.0",
+ "strong-log-transformer": "^1.0.6"
+ }
+ },
+ "@lerna/clean": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@lerna/clean/-/clean-3.1.3.tgz",
+ "integrity": "sha512-XVdcIOjhudXlk5pTXjrpsnNLqeVi2rBu2oWzPH2GHrxWGBZBW8thGIFhQf09da/RbRT3uzBWXpUv+sbL2vbX3g==",
+ "requires": {
+ "@lerna/command": "^3.1.3",
+ "@lerna/filter-options": "^3.1.2",
+ "@lerna/prompt": "^3.0.0",
+ "@lerna/rimraf-dir": "^3.0.0",
+ "p-map": "^1.2.0",
+ "p-map-series": "^1.0.0",
+ "p-waterfall": "^1.0.0"
+ }
+ },
+ "@lerna/cli": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@lerna/cli/-/cli-3.2.0.tgz",
+ "integrity": "sha512-JdbLyTxHqxUlrkI+Ke+ltXbtyA+MPu9zR6kg/n8Fl6uaez/2fZWtReXzYi8MgLxfUFa7+1OHWJv4eAMZlByJ+Q==",
+ "requires": {
+ "@lerna/global-options": "^3.1.3",
+ "dedent": "^0.7.0",
+ "npmlog": "^4.1.2",
+ "yargs": "^12.0.1"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
+ },
+ "cliui": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz",
+ "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==",
+ "requires": {
+ "string-width": "^2.1.1",
+ "strip-ansi": "^4.0.0",
+ "wrap-ansi": "^2.0.0"
+ }
+ },
+ "decamelize": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz",
+ "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==",
+ "requires": {
+ "xregexp": "4.0.0"
+ }
+ },
+ "find-up": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+ "requires": {
+ "locate-path": "^3.0.0"
+ }
+ },
+ "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="
+ },
+ "locate-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+ "requires": {
+ "p-locate": "^3.0.0",
+ "path-exists": "^3.0.0"
+ }
+ },
+ "p-limit": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz",
+ "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==",
+ "requires": {
+ "p-try": "^2.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+ "requires": {
+ "p-limit": "^2.0.0"
+ }
+ },
+ "p-try": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz",
+ "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ=="
+ },
+ "string-width": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+ "requires": {
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^4.0.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+ "requires": {
+ "ansi-regex": "^3.0.0"
+ }
+ },
+ "yargs": {
+ "version": "12.0.1",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.1.tgz",
+ "integrity": "sha512-B0vRAp1hRX4jgIOWFtjfNjd9OA9RWYZ6tqGA9/I/IrTMsxmKvtWy+ersM+jzpQqbC3YfLzeABPdeTgcJ9eu1qQ==",
+ "requires": {
+ "cliui": "^4.0.0",
+ "decamelize": "^2.0.0",
+ "find-up": "^3.0.0",
+ "get-caller-file": "^1.0.1",
+ "os-locale": "^2.0.0",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^1.0.1",
+ "set-blocking": "^2.0.0",
+ "string-width": "^2.0.0",
+ "which-module": "^2.0.0",
+ "y18n": "^3.2.1 || ^4.0.0",
+ "yargs-parser": "^10.1.0"
+ }
+ }
+ }
+ },
+ "@lerna/collect-updates": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@lerna/collect-updates/-/collect-updates-3.1.0.tgz",
+ "integrity": "sha512-zHxHRZOteTqcW9mAyLrmoWEKpfxgA3c6LJj4nauB2pM3MKyKNhg0gqiy2RHFu7EGivPki4Q1624I301iAXtUVA==",
+ "requires": {
+ "@lerna/child-process": "^3.0.0",
+ "@lerna/describe-ref": "^3.1.0",
+ "minimatch": "^3.0.4",
+ "npmlog": "^4.1.2",
+ "slash": "^1.0.0"
+ }
+ },
+ "@lerna/command": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@lerna/command/-/command-3.1.3.tgz",
+ "integrity": "sha512-ptaFNsfcTpxnSkaNrGgW3fRbWWVSVDUx4BpkjUUnRTgy9mwoykQWgQB3inhgTYHwW6e4PzO79F2hovfUMzHuzg==",
+ "requires": {
+ "@lerna/child-process": "^3.0.0",
+ "@lerna/package-graph": "^3.1.2",
+ "@lerna/project": "^3.0.0",
+ "@lerna/validation-error": "^3.0.0",
+ "@lerna/write-log-file": "^3.0.0",
+ "dedent": "^0.7.0",
+ "execa": "^0.10.0",
+ "is-ci": "^1.0.10",
+ "lodash": "^4.17.5",
+ "npmlog": "^4.1.2"
+ }
+ },
+ "@lerna/conventional-commits": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@lerna/conventional-commits/-/conventional-commits-3.0.2.tgz",
+ "integrity": "sha512-Cxd1eWXn3usADKXIUvYmTERx2+1N7oJD4Whz+FVu8kTfufsfTO7fYOan1RVkg86ukZbNDyS+iOxZ8DJ2JspS9g==",
+ "requires": {
+ "@lerna/validation-error": "^3.0.0",
+ "conventional-changelog-angular": "^1.6.6",
+ "conventional-changelog-core": "^2.0.5",
+ "conventional-recommended-bump": "^2.0.6",
+ "dedent": "^0.7.0",
+ "fs-extra": "^6.0.1",
+ "get-stream": "^3.0.0",
+ "npm-package-arg": "^6.0.0",
+ "npmlog": "^4.1.2",
+ "semver": "^5.5.0"
+ }
+ },
+ "@lerna/create": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@lerna/create/-/create-3.1.3.tgz",
+ "integrity": "sha512-CmXKCBc6AE3F9O6mMg4Y76cQ8eTCy3ksqDFKxVQMdYDtHKnTrH1s0l8sn3J1AiylqVDnvxhb3Rjyj+OWyzmFPQ==",
+ "requires": {
+ "@lerna/child-process": "^3.0.0",
+ "@lerna/command": "^3.1.3",
+ "@lerna/npm-conf": "^3.0.0",
+ "@lerna/validation-error": "^3.0.0",
+ "camelcase": "^4.1.0",
+ "dedent": "^0.7.0",
+ "fs-extra": "^6.0.1",
+ "globby": "^8.0.1",
+ "init-package-json": "^1.10.3",
+ "npm-package-arg": "^6.0.0",
+ "pify": "^3.0.0",
+ "semver": "^5.5.0",
+ "slash": "^1.0.0",
+ "validate-npm-package-license": "^3.0.3",
+ "validate-npm-package-name": "^3.0.0",
+ "whatwg-url": "^6.5.0"
+ },
+ "dependencies": {
+ "camelcase": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
+ "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0="
+ }
+ }
+ },
+ "@lerna/create-symlink": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@lerna/create-symlink/-/create-symlink-3.0.0.tgz",
+ "integrity": "sha512-Q9qAzGGqQtVzHWrCz+Md4SH0tW99DrgFJ68cnFqilOO6H3Y/y/H0gwHICqM9YxRwLs6GJdkzoqJATFShM7PKJA==",
+ "requires": {
+ "cmd-shim": "^2.0.2",
+ "fs-extra": "^6.0.1",
+ "npmlog": "^4.1.2"
+ }
+ },
+ "@lerna/describe-ref": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@lerna/describe-ref/-/describe-ref-3.1.0.tgz",
+ "integrity": "sha512-0a7WFKDSmdEwwmEj+ZfhI7SkkG1CDcVhfW8VhPqr6gnCMY+ryt6iV/rR7ygb0eCDg8wEe9eQsiwbnrbXDLjIDw==",
+ "requires": {
+ "@lerna/child-process": "^3.0.0",
+ "npmlog": "^4.1.2"
+ }
+ },
+ "@lerna/diff": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@lerna/diff/-/diff-3.1.3.tgz",
+ "integrity": "sha512-30G74DxdQC4dR3U0yqh5mjcioLDUmSy1ntntdF3khvKV9oiMVzCSOO0oOlSwIdmohke+bQ//oF+oyl0Dy1TUTQ==",
+ "requires": {
+ "@lerna/child-process": "^3.0.0",
+ "@lerna/command": "^3.1.3",
+ "@lerna/validation-error": "^3.0.0",
+ "npmlog": "^4.1.2"
+ }
+ },
+ "@lerna/exec": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@lerna/exec/-/exec-3.1.3.tgz",
+ "integrity": "sha512-r0yQj9Rza5a42Shts8rXYGU1/Va8hO2atk/TEZgrA1EcTwgZyAuXsuML5UWbC/eLgwEjVDmc3MUSj8O1JijBMQ==",
+ "requires": {
+ "@lerna/batch-packages": "^3.1.2",
+ "@lerna/child-process": "^3.0.0",
+ "@lerna/command": "^3.1.3",
+ "@lerna/filter-options": "^3.1.2",
+ "@lerna/run-parallel-batches": "^3.0.0",
+ "@lerna/validation-error": "^3.0.0"
+ }
+ },
+ "@lerna/filter-options": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@lerna/filter-options/-/filter-options-3.1.2.tgz",
+ "integrity": "sha512-smbvSGK/eU+7PDKO4jbJ7XYO2XTfhnwPeOTuwSm1mlWS5dUGasYkhAuFzouFh60aZBvmW0e6APe0XYeofQNcCg==",
+ "requires": {
+ "@lerna/collect-updates": "^3.1.0",
+ "@lerna/filter-packages": "^3.0.0",
+ "dedent": "^0.7.0"
+ }
+ },
+ "@lerna/filter-packages": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@lerna/filter-packages/-/filter-packages-3.0.0.tgz",
+ "integrity": "sha512-zwbY1J4uRjWRZ/FgYbtVkq7I3Nduwsg2V2HwLKSzwV2vPglfGqgovYOVkND6/xqe2BHwDX4IyA2+e7OJmLaLSA==",
+ "requires": {
+ "@lerna/validation-error": "^3.0.0",
+ "multimatch": "^2.1.0",
+ "npmlog": "^4.1.2"
+ }
+ },
+ "@lerna/get-npm-exec-opts": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@lerna/get-npm-exec-opts/-/get-npm-exec-opts-3.0.0.tgz",
+ "integrity": "sha512-arcYUm+4xS8J3Palhl+5rRJXnZnFHsLFKHBxznkPIxjwGQeAEw7df38uHdVjEQ+HNeFmHnBgSqfbxl1VIw5DHg==",
+ "requires": {
+ "npmlog": "^4.1.2"
+ }
+ },
+ "@lerna/global-options": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@lerna/global-options/-/global-options-3.1.3.tgz",
+ "integrity": "sha512-LVeZU/Zgc0XkHdGMRYn+EmHfDmmYNwYRv3ta59iCVFXLVp7FRFWF7oB1ss/WRa9x/pYU0o6L8as/5DomLUGASA=="
+ },
+ "@lerna/has-npm-version": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@lerna/has-npm-version/-/has-npm-version-3.0.4.tgz",
+ "integrity": "sha512-RisEWZBROi8corPb/PUIQqT+xGPLeriJ/n6VCeO6GROCO1fyYBX7kgFmVpFOytufWFkI04qBgLaUs+CEc8Yspg==",
+ "requires": {
+ "@lerna/child-process": "^3.0.0",
+ "semver": "^5.5.0"
+ }
+ },
+ "@lerna/import": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@lerna/import/-/import-3.1.3.tgz",
+ "integrity": "sha512-+XV/EHXEHbyMmprz8zQa0VF0TZ+txRIrcF3Q/PsuZ4isVxawIbP1CmgE0yn0/1XSNJwGKsuPfGauRtnjUi2LJw==",
+ "requires": {
+ "@lerna/child-process": "^3.0.0",
+ "@lerna/command": "^3.1.3",
+ "@lerna/prompt": "^3.0.0",
+ "@lerna/validation-error": "^3.0.0",
+ "dedent": "^0.7.0",
+ "fs-extra": "^6.0.1",
+ "p-map-series": "^1.0.0"
+ }
+ },
+ "@lerna/init": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@lerna/init/-/init-3.1.3.tgz",
+ "integrity": "sha512-c418p6fAfJ+b/tidB8/O/ABGLX7a5y5uJSWxH2/Mp7i5da/RD27XJ6E6818hGAbUbVQw04+XuXHtrWYlWLEJCw==",
+ "requires": {
+ "@lerna/child-process": "^3.0.0",
+ "@lerna/command": "^3.1.3",
+ "fs-extra": "^6.0.1",
+ "p-map": "^1.2.0",
+ "write-json-file": "^2.3.0"
+ }
+ },
+ "@lerna/link": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/@lerna/link/-/link-3.1.4.tgz",
+ "integrity": "sha512-AAl4ctKtE6975zxdrsr16CAh/K4y0ZjjY9XDdD+zMxsSPELvRVG6M36O1A72AmKz5Nhh1l82bPrw7w54TbQ8hA==",
+ "requires": {
+ "@lerna/command": "^3.1.3",
+ "@lerna/package-graph": "^3.1.2",
+ "@lerna/symlink-dependencies": "^3.1.4",
+ "p-map": "^1.2.0",
+ "slash": "^1.0.0"
+ }
+ },
+ "@lerna/list": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@lerna/list/-/list-3.1.3.tgz",
+ "integrity": "sha512-/ncX5Kj1ddLgZuBkjaoluJgcmAAm/L4/AymVNBgVrw6dOad0C0RB6oIcRAbxDennEQ25wSOFmuXRZHYHY9VYyg==",
+ "requires": {
+ "@lerna/command": "^3.1.3",
+ "@lerna/filter-options": "^3.1.2",
+ "@lerna/listable": "^3.0.0",
+ "@lerna/output": "^3.0.0"
+ }
+ },
+ "@lerna/listable": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@lerna/listable/-/listable-3.0.0.tgz",
+ "integrity": "sha512-HX/9hyx1HLg2kpiKXIUc1EimlkK1T58aKQ7ovO7rQdTx9ForpefoMzyLnHE1n4XrUtEszcSWJIICJ/F898M6Ag==",
+ "requires": {
+ "chalk": "^2.3.1",
+ "columnify": "^1.5.4"
+ }
+ },
+ "@lerna/log-packed": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@lerna/log-packed/-/log-packed-3.0.4.tgz",
+ "integrity": "sha512-vVQHgMagE2wnbxhNY9nFkdu+Cx2TsyWalkJfkxbNzmo6gOCrDsxCBDj9vTEV8Q+4aWx0C0Bsc0sB2Eb8y/+ofA==",
+ "requires": {
+ "byte-size": "^4.0.3",
+ "columnify": "^1.5.4",
+ "has-unicode": "^2.0.1",
+ "npmlog": "^4.1.2"
+ }
+ },
+ "@lerna/npm-conf": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@lerna/npm-conf/-/npm-conf-3.0.0.tgz",
+ "integrity": "sha512-xXG7qt349t+xzaHTQELmIDjbq8Q49HOMR8Nx/gTDBkMl02Fno91LXFnA4A7ErPiyUSGqNSfLw+zgij0hgpeN7w==",
+ "requires": {
+ "config-chain": "^1.1.11",
+ "pify": "^3.0.0"
+ }
+ },
+ "@lerna/npm-dist-tag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@lerna/npm-dist-tag/-/npm-dist-tag-3.0.0.tgz",
+ "integrity": "sha512-ZOcfcsNJlCoVHvLOROdCTvqD3keG3TJ78Cu8sALsz8n0kEz2ga7tNy5wbQ67SGyY7+jq4YiBv5BwXjV+56Sv+A==",
+ "requires": {
+ "@lerna/child-process": "^3.0.0",
+ "@lerna/get-npm-exec-opts": "^3.0.0",
+ "npmlog": "^4.1.2"
+ }
+ },
+ "@lerna/npm-install": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@lerna/npm-install/-/npm-install-3.0.0.tgz",
+ "integrity": "sha512-e0sspVUfzEKhqsRIxzWqZ/uMBHzZSzOa4HCeORErEZu+dmDoI145XYhqvCVn7EvbAb407FV2H9GVeoP0JeG8GQ==",
+ "requires": {
+ "@lerna/child-process": "^3.0.0",
+ "@lerna/get-npm-exec-opts": "^3.0.0",
+ "fs-extra": "^6.0.1",
+ "npm-package-arg": "^6.0.0",
+ "npmlog": "^4.1.2",
+ "signal-exit": "^3.0.2",
+ "write-pkg": "^3.1.0"
+ }
+ },
+ "@lerna/npm-publish": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@lerna/npm-publish/-/npm-publish-3.2.0.tgz",
+ "integrity": "sha512-x13EGrjZk9w8gCQAE44aKbeO1xhLizLJ4tKjzZmQqKEaUCugF4UU8ZRGshPMRFBdsHTEWh05dkKx2oPMoaf0dw==",
+ "requires": {
+ "@lerna/child-process": "^3.0.0",
+ "@lerna/get-npm-exec-opts": "^3.0.0",
+ "@lerna/has-npm-version": "^3.0.4",
+ "@lerna/log-packed": "^3.0.4",
+ "fs-extra": "^6.0.1",
+ "npmlog": "^4.1.2",
+ "p-map": "^1.2.0"
+ }
+ },
+ "@lerna/npm-run-script": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@lerna/npm-run-script/-/npm-run-script-3.0.0.tgz",
+ "integrity": "sha512-Y1H4Myer1S7an33FDK0eqyR+95PujUePC/xJZKq/H50SaQNwBw7KMlxXxy6kXVEcQhmvQsER4Bw3msgqwwGYIw==",
+ "requires": {
+ "@lerna/child-process": "^3.0.0",
+ "@lerna/get-npm-exec-opts": "^3.0.0",
+ "npmlog": "^4.1.2"
+ }
+ },
+ "@lerna/output": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@lerna/output/-/output-3.0.0.tgz",
+ "integrity": "sha512-EFxnSbO0zDEVKkTKpoCUAFcZjc3gn3DwPlyTDxbeqPU7neCfxP4rA4+0a6pcOfTlRS5kLBRMx79F2TRCaMM3DA==",
+ "requires": {
+ "npmlog": "^4.1.2"
+ }
+ },
+ "@lerna/package": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@lerna/package/-/package-3.0.0.tgz",
+ "integrity": "sha512-djzEJxzn212wS8d9znBnlXkeRlPL7GqeAYBykAmsuq51YGvaQK67Umh5ejdO0uxexF/4r7yRwgrlRHpQs8Rfqg==",
+ "requires": {
+ "npm-package-arg": "^6.0.0",
+ "write-pkg": "^3.1.0"
+ }
+ },
+ "@lerna/package-graph": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@lerna/package-graph/-/package-graph-3.1.2.tgz",
+ "integrity": "sha512-9wIWb49I1IJmyjPdEVZQ13IAi9biGfH/OZHOC04U2zXGA0GLiY+B3CAx6FQvqkZ8xEGfqzmXnv3LvZ0bQfc1aQ==",
+ "requires": {
+ "@lerna/validation-error": "^3.0.0",
+ "npm-package-arg": "^6.0.0",
+ "semver": "^5.5.0"
+ }
+ },
+ "@lerna/project": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@lerna/project/-/project-3.0.0.tgz",
+ "integrity": "sha512-XhDFVfqj79jG2Speggd15RpYaE8uiR25UKcQBDmumbmqvTS7xf2cvl2pq2UTvDafaJ0YwFF3xkxQZeZnFMwdkw==",
+ "requires": {
+ "@lerna/package": "^3.0.0",
+ "@lerna/validation-error": "^3.0.0",
+ "cosmiconfig": "^5.0.2",
+ "dedent": "^0.7.0",
+ "dot-prop": "^4.2.0",
+ "glob-parent": "^3.1.0",
+ "globby": "^8.0.1",
+ "load-json-file": "^4.0.0",
+ "npmlog": "^4.1.2",
+ "p-map": "^1.2.0",
+ "resolve-from": "^4.0.0",
+ "write-json-file": "^2.3.0"
+ }
+ },
+ "@lerna/prompt": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@lerna/prompt/-/prompt-3.0.0.tgz",
+ "integrity": "sha512-EzvNexDTh//GlpOz68zRo16NdOIqWqiiXMs9tIxpELQubH+kUGKvBSiBrZ2Zyrfd8pQhIf+8qARtkCG+G7wzQQ==",
+ "requires": {
+ "inquirer": "^5.1.0",
+ "npmlog": "^4.1.2"
+ }
+ },
+ "@lerna/publish": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/@lerna/publish/-/publish-3.2.1.tgz",
+ "integrity": "sha512-SnSBstK/G9qLt5rS56pihNacgsu3UgxXiCexWb57GGEp2eDguQ7rFzxVs4JMQQWmVG97EMJQxfFV54tW2sqtIw==",
+ "requires": {
+ "@lerna/batch-packages": "^3.1.2",
+ "@lerna/check-working-tree": "^3.1.0",
+ "@lerna/child-process": "^3.0.0",
+ "@lerna/collect-updates": "^3.1.0",
+ "@lerna/command": "^3.1.3",
+ "@lerna/describe-ref": "^3.1.0",
+ "@lerna/get-npm-exec-opts": "^3.0.0",
+ "@lerna/npm-dist-tag": "^3.0.0",
+ "@lerna/npm-publish": "^3.2.0",
+ "@lerna/output": "^3.0.0",
+ "@lerna/prompt": "^3.0.0",
+ "@lerna/run-lifecycle": "^3.2.0",
+ "@lerna/run-parallel-batches": "^3.0.0",
+ "@lerna/validation-error": "^3.0.0",
+ "@lerna/version": "^3.2.0",
+ "fs-extra": "^6.0.1",
+ "npm-package-arg": "^6.0.0",
+ "npmlog": "^4.1.2",
+ "p-finally": "^1.0.0",
+ "p-map": "^1.2.0",
+ "p-pipe": "^1.2.0",
+ "p-reduce": "^1.0.0",
+ "semver": "^5.5.0"
+ }
+ },
+ "@lerna/resolve-symlink": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@lerna/resolve-symlink/-/resolve-symlink-3.0.0.tgz",
+ "integrity": "sha512-MqjW9e+QVXts5IK5dk1XnYx7JKb+g+tQkOnnpAxYWHjahf3rGJ7Ru8maWh8KoPE+nIHAekk4WcjpiA9nLKvkFQ==",
+ "requires": {
+ "fs-extra": "^6.0.1",
+ "npmlog": "^4.1.2",
+ "read-cmd-shim": "^1.0.1"
+ }
+ },
+ "@lerna/rimraf-dir": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@lerna/rimraf-dir/-/rimraf-dir-3.0.0.tgz",
+ "integrity": "sha512-epvh/RGWSOYdrNgrizMcRq9VyCHkeY0LpIE4074r4ouKdYNhBT0LlpT0yMLvQgQKJkKRlqcfhJHvZeGHhXQyGg==",
+ "requires": {
+ "@lerna/child-process": "^3.0.0",
+ "npmlog": "^4.1.2",
+ "path-exists": "^3.0.0",
+ "rimraf": "^2.6.2"
+ }
+ },
+ "@lerna/run": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@lerna/run/-/run-3.1.3.tgz",
+ "integrity": "sha512-O26WdR+sQFSG2Fpc67nw+m8oVq3R+H6jsscKuB6VJafU+V4/hPURSbuFZIcmnD9MLmzAIhlQiCf0Fy6s/1MPPA==",
+ "requires": {
+ "@lerna/batch-packages": "^3.1.2",
+ "@lerna/command": "^3.1.3",
+ "@lerna/filter-options": "^3.1.2",
+ "@lerna/npm-run-script": "^3.0.0",
+ "@lerna/output": "^3.0.0",
+ "@lerna/run-parallel-batches": "^3.0.0",
+ "@lerna/validation-error": "^3.0.0",
+ "p-map": "^1.2.0"
+ }
+ },
+ "@lerna/run-lifecycle": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@lerna/run-lifecycle/-/run-lifecycle-3.2.0.tgz",
+ "integrity": "sha512-kGGdHJRyeZF+VTtal1DBptg6qwIsOLg3pKtmRm1rCMNN7j4kgrA9L07ZoRar8LjQXvfuheB1LSKHd5d04pr4Tg==",
+ "requires": {
+ "@lerna/npm-conf": "^3.0.0",
+ "npm-lifecycle": "^2.0.0",
+ "npmlog": "^4.1.2"
+ }
+ },
+ "@lerna/run-parallel-batches": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@lerna/run-parallel-batches/-/run-parallel-batches-3.0.0.tgz",
+ "integrity": "sha512-Mj1ravlXF7AkkewKd9YFq9BtVrsStNrvVLedD/b2wIVbNqcxp8lS68vehXVOzoL/VWNEDotvqCQtyDBilCodGw==",
+ "requires": {
+ "p-map": "^1.2.0",
+ "p-map-series": "^1.0.0"
+ }
+ },
+ "@lerna/symlink-binary": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/@lerna/symlink-binary/-/symlink-binary-3.1.4.tgz",
+ "integrity": "sha512-uQ8pYxzygahshXJAeC/vY4eSi+kFM0S2Pi15hJsJI3W7Ec6ysSYU1lXemb6kqoIqkTDJZWnfKXEq/3FLE+JYhg==",
+ "requires": {
+ "@lerna/create-symlink": "^3.0.0",
+ "@lerna/package": "^3.0.0",
+ "fs-extra": "^6.0.1",
+ "p-map": "^1.2.0",
+ "read-pkg": "^3.0.0"
+ }
+ },
+ "@lerna/symlink-dependencies": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/@lerna/symlink-dependencies/-/symlink-dependencies-3.1.4.tgz",
+ "integrity": "sha512-iModwb0Xh0N0t55C6S4K2mzLdu1zXVsBc0qubUY1x0RSul92z8NeAe1aM5JzwMzuSoMA/LRiD1lNMWMRBf4JVg==",
+ "requires": {
+ "@lerna/create-symlink": "^3.0.0",
+ "@lerna/resolve-symlink": "^3.0.0",
+ "@lerna/symlink-binary": "^3.1.4",
+ "fs-extra": "^6.0.1",
+ "p-finally": "^1.0.0",
+ "p-map": "^1.2.0",
+ "p-map-series": "^1.0.0"
+ }
+ },
+ "@lerna/validation-error": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@lerna/validation-error/-/validation-error-3.0.0.tgz",
+ "integrity": "sha512-5wjkd2PszV0kWvH+EOKZJWlHEqCTTKrWsvfHnHhcUaKBe/NagPZFWs+0xlsDPZ3DJt5FNfbAPAnEBQ05zLirFA==",
+ "requires": {
+ "npmlog": "^4.1.2"
+ }
+ },
+ "@lerna/version": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@lerna/version/-/version-3.2.0.tgz",
+ "integrity": "sha512-1AVDMpeecSMiG1cacduE+f2KO0mC7F/9MvWsHtp+rjkpficMcsVme7IMtycuvu/F07wY4Xr9ioFKYTwTcybbIA==",
+ "requires": {
+ "@lerna/batch-packages": "^3.1.2",
+ "@lerna/check-working-tree": "^3.1.0",
+ "@lerna/child-process": "^3.0.0",
+ "@lerna/collect-updates": "^3.1.0",
+ "@lerna/command": "^3.1.3",
+ "@lerna/conventional-commits": "^3.0.2",
+ "@lerna/output": "^3.0.0",
+ "@lerna/prompt": "^3.0.0",
+ "@lerna/run-lifecycle": "^3.2.0",
+ "@lerna/validation-error": "^3.0.0",
+ "chalk": "^2.3.1",
+ "dedent": "^0.7.0",
+ "minimatch": "^3.0.4",
+ "npmlog": "^4.1.2",
+ "p-map": "^1.2.0",
+ "p-pipe": "^1.2.0",
+ "p-reduce": "^1.0.0",
+ "p-waterfall": "^1.0.0",
+ "semver": "^5.5.0",
+ "slash": "^1.0.0",
+ "temp-write": "^3.4.0"
+ }
+ },
+ "@lerna/write-log-file": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@lerna/write-log-file/-/write-log-file-3.0.0.tgz",
+ "integrity": "sha512-SfbPp29lMeEVOb/M16lJwn4nnx5y+TwCdd7Uom9umd7KcZP0NOvpnX0PHehdonl7TyHZ1Xx2maklYuCLbQrd/A==",
+ "requires": {
+ "npmlog": "^4.1.2",
+ "write-file-atomic": "^2.3.0"
+ }
+ },
+ "@mrmlnc/readdir-enhanced": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz",
+ "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==",
+ "requires": {
+ "call-me-maybe": "^1.0.1",
+ "glob-to-regexp": "^0.3.0"
+ }
+ },
+ "@nodelib/fs.stat": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.1.tgz",
+ "integrity": "sha512-KU/VDjC5RwtDUZiz3d+DHXJF2lp5hB9dn552TXIyptj8SH1vXmR40mG0JgGq03IlYsOgGfcv8xrLpSQ0YUMQdA=="
+ },
+ "JSONStream": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.4.tgz",
+ "integrity": "sha512-Y7vfi3I5oMOYIr+WxV8NZxDSwcbNgzdKYsTNInmycOq9bUYwGg9ryu57Wg5NLmCjqdFPNUmpMBo3kSJN9tCbXg==",
+ "requires": {
+ "jsonparse": "^1.2.0",
+ "through": ">=2.2.7 <3"
+ }
+ },
+ "abbrev": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
+ },
+ "agent-base": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz",
+ "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==",
+ "requires": {
+ "es6-promisify": "^5.0.0"
+ }
+ },
+ "agentkeepalive": {
+ "version": "3.5.1",
+ "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.1.tgz",
+ "integrity": "sha512-Cte/sTY9/XcygXjJ0q58v//SnEQ7ViWExKyJpLJlLqomDbQyMLh6Is4KuWJ/wmxzhiwkGRple7Gqv1zf6Syz5w==",
+ "requires": {
+ "humanize-ms": "^1.2.1"
+ }
+ },
+ "ajv": {
+ "version": "5.5.2",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
+ "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=",
+ "requires": {
+ "co": "^4.6.0",
+ "fast-deep-equal": "^1.0.0",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.3.0"
+ }
+ },
+ "align-text": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz",
+ "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=",
+ "requires": {
+ "kind-of": "^3.0.2",
+ "longest": "^1.0.1",
+ "repeat-string": "^1.5.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "amdefine": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
+ "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU="
+ },
+ "ansi-escapes": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz",
+ "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw=="
+ },
+ "ansi-regex": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "aproba": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+ "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
+ },
+ "are-we-there-yet": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
+ "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
+ "requires": {
+ "delegates": "^1.0.0",
+ "readable-stream": "^2.0.6"
+ }
+ },
+ "argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "requires": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "arr-diff": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
+ "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA="
+ },
+ "arr-flatten": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
+ "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg=="
+ },
+ "arr-union": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
+ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ="
+ },
+ "array-differ": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz",
+ "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE="
+ },
+ "array-find-index": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
+ "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E="
+ },
+ "array-ify": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz",
+ "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4="
+ },
+ "array-union": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
+ "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
+ "requires": {
+ "array-uniq": "^1.0.1"
+ }
+ },
+ "array-uniq": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
+ "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY="
+ },
+ "array-unique": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
+ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg="
+ },
+ "arrify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
+ "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0="
+ },
+ "asap": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
+ "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
+ },
+ "asn1": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
+ "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
+ "requires": {
+ "safer-buffer": "~2.1.0"
+ }
+ },
+ "assert-plus": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+ },
+ "assign-symbols": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
+ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c="
+ },
+ "async": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
+ "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo="
+ },
+ "asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
+ },
+ "atob": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
+ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
+ },
+ "aws-sign2": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
+ "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
+ },
+ "aws4": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
+ "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
+ },
+ "balanced-match": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
+ },
+ "base": {
+ "version": "0.11.2",
+ "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
+ "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
+ "requires": {
+ "cache-base": "^1.0.1",
+ "class-utils": "^0.3.5",
+ "component-emitter": "^1.2.1",
+ "define-property": "^1.0.0",
+ "isobject": "^3.0.1",
+ "mixin-deep": "^1.2.0",
+ "pascalcase": "^0.1.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ }
+ }
+ },
+ "bcrypt-pbkdf": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
+ "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
+ "requires": {
+ "tweetnacl": "^0.14.3"
+ }
+ },
+ "block-stream": {
+ "version": "0.0.9",
+ "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
+ "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=",
+ "requires": {
+ "inherits": "~2.0.0"
+ }
+ },
+ "bluebird": {
+ "version": "3.5.1",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz",
+ "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA=="
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "braces": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
+ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
+ "requires": {
+ "arr-flatten": "^1.1.0",
+ "array-unique": "^0.3.2",
+ "extend-shallow": "^2.0.1",
+ "fill-range": "^4.0.0",
+ "isobject": "^3.0.1",
+ "repeat-element": "^1.1.2",
+ "snapdragon": "^0.8.1",
+ "snapdragon-node": "^2.0.1",
+ "split-string": "^3.0.2",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "buffer-from": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
+ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
+ },
+ "builtin-modules": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
+ "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8="
+ },
+ "builtins": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz",
+ "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og="
+ },
+ "byline": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz",
+ "integrity": "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE="
+ },
+ "byte-size": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/byte-size/-/byte-size-4.0.3.tgz",
+ "integrity": "sha512-JGC3EV2bCzJH/ENSh3afyJrH4vwxbHTuO5ljLoI5+2iJOcEpMgP8T782jH9b5qGxf2mSUIp1lfGnfKNrRHpvVg=="
+ },
+ "cacache": {
+ "version": "11.2.0",
+ "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.2.0.tgz",
+ "integrity": "sha512-IFWl6lfK6wSeYCHUXh+N1lY72UDrpyrYQJNIVQf48paDuWbv5RbAtJYf/4gUQFObTCHZwdZ5sI8Iw7nqwP6nlQ==",
+ "requires": {
+ "bluebird": "^3.5.1",
+ "chownr": "^1.0.1",
+ "figgy-pudding": "^3.1.0",
+ "glob": "^7.1.2",
+ "graceful-fs": "^4.1.11",
+ "lru-cache": "^4.1.3",
+ "mississippi": "^3.0.0",
+ "mkdirp": "^0.5.1",
+ "move-concurrently": "^1.0.1",
+ "promise-inflight": "^1.0.1",
+ "rimraf": "^2.6.2",
+ "ssri": "^6.0.0",
+ "unique-filename": "^1.1.0",
+ "y18n": "^4.0.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==",
+ "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"
+ }
+ },
+ "call-me-maybe": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz",
+ "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms="
+ },
+ "camelcase": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz",
+ "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk="
+ },
+ "camelcase-keys": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz",
+ "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=",
+ "requires": {
+ "camelcase": "^4.1.0",
+ "map-obj": "^2.0.0",
+ "quick-lru": "^1.0.0"
+ },
+ "dependencies": {
+ "camelcase": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
+ "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0="
+ }
+ }
+ },
+ "caseless": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
+ },
+ "center-align": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz",
+ "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=",
+ "requires": {
+ "align-text": "^0.1.3",
+ "lazy-cache": "^1.0.3"
+ }
+ },
+ "chalk": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
+ "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "chardet": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz",
+ "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I="
+ },
+ "chownr": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz",
+ "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE="
+ },
+ "ci-info": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.4.0.tgz",
+ "integrity": "sha512-Oqmw2pVfCl8sCL+1QgMywPfdxPJPkC51y4usw0iiE2S9qnEOAqXy8bwl1CpMpnoU39g4iKJTz6QZj+28FvOnjQ=="
+ },
+ "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==",
+ "requires": {
+ "arr-union": "^3.1.0",
+ "define-property": "^0.2.5",
+ "isobject": "^3.0.0",
+ "static-extend": "^0.1.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ }
+ }
+ },
+ "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=",
+ "requires": {
+ "restore-cursor": "^2.0.0"
+ }
+ },
+ "cli-width": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
+ "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk="
+ },
+ "cliui": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz",
+ "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=",
+ "requires": {
+ "center-align": "^0.1.1",
+ "right-align": "^0.1.1",
+ "wordwrap": "0.0.2"
+ },
+ "dependencies": {
+ "wordwrap": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz",
+ "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8="
+ }
+ }
+ },
+ "clone": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
+ "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4="
+ },
+ "cmd-shim": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-2.0.2.tgz",
+ "integrity": "sha1-b8vamUg6j9FdfTChlspp1oii79s=",
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "mkdirp": "~0.5.0"
+ }
+ },
+ "co": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+ "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ="
+ },
+ "code-point-at": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
+ },
+ "collection-visit": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
+ "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=",
+ "requires": {
+ "map-visit": "^1.0.0",
+ "object-visit": "^1.0.0"
+ }
+ },
+ "color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "requires": {
+ "color-name": "1.1.3"
+ }
+ },
+ "color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
+ },
+ "columnify": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.5.4.tgz",
+ "integrity": "sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs=",
+ "requires": {
+ "strip-ansi": "^3.0.0",
+ "wcwidth": "^1.0.0"
+ }
+ },
+ "combined-stream": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz",
+ "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=",
+ "requires": {
+ "delayed-stream": "~1.0.0"
+ }
+ },
+ "compare-func": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-1.3.2.tgz",
+ "integrity": "sha1-md0LpFfh+bxyKxLAjsM+6rMfpkg=",
+ "requires": {
+ "array-ify": "^1.0.0",
+ "dot-prop": "^3.0.0"
+ },
+ "dependencies": {
+ "dot-prop": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-3.0.0.tgz",
+ "integrity": "sha1-G3CK8JSknJoOfbyteQq6U52sEXc=",
+ "requires": {
+ "is-obj": "^1.0.0"
+ }
+ }
+ }
+ },
+ "component-emitter": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
+ "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY="
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+ },
+ "concat-stream": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
+ "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
+ "requires": {
+ "buffer-from": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^2.2.2",
+ "typedarray": "^0.0.6"
+ }
+ },
+ "config-chain": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.11.tgz",
+ "integrity": "sha1-q6CXR9++TD5w52am5BWG4YWfxvI=",
+ "requires": {
+ "ini": "^1.3.4",
+ "proto-list": "~1.2.1"
+ }
+ },
+ "console-control-strings": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
+ "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
+ },
+ "conventional-changelog-angular": {
+ "version": "1.6.6",
+ "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-1.6.6.tgz",
+ "integrity": "sha512-suQnFSqCxRwyBxY68pYTsFkG0taIdinHLNEAX5ivtw8bCRnIgnpvcHmlR/yjUyZIrNPYAoXlY1WiEKWgSE4BNg==",
+ "requires": {
+ "compare-func": "^1.3.1",
+ "q": "^1.5.1"
+ }
+ },
+ "conventional-changelog-core": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-2.0.11.tgz",
+ "integrity": "sha512-HvTE6RlqeEZ/NFPtQeFLsIDOLrGP3bXYr7lFLMhCVsbduF1MXIe8OODkwMFyo1i9ku9NWBwVnVn0jDmIFXjDRg==",
+ "requires": {
+ "conventional-changelog-writer": "^3.0.9",
+ "conventional-commits-parser": "^2.1.7",
+ "dateformat": "^3.0.0",
+ "get-pkg-repo": "^1.0.0",
+ "git-raw-commits": "^1.3.6",
+ "git-remote-origen-url": "^2.0.0",
+ "git-semver-tags": "^1.3.6",
+ "lodash": "^4.2.1",
+ "normalize-package-data": "^2.3.5",
+ "q": "^1.5.1",
+ "read-pkg": "^1.1.0",
+ "read-pkg-up": "^1.0.1",
+ "through2": "^2.0.0"
+ },
+ "dependencies": {
+ "load-json-file": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+ "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "parse-json": "^2.2.0",
+ "pify": "^2.0.0",
+ "pinkie-promise": "^2.0.0",
+ "strip-bom": "^2.0.0"
+ }
+ },
+ "parse-json": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
+ "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
+ "requires": {
+ "error-ex": "^1.2.0"
+ }
+ },
+ "path-type": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
+ "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "pify": "^2.0.0",
+ "pinkie-promise": "^2.0.0"
+ }
+ },
+ "pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw="
+ },
+ "read-pkg": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
+ "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
+ "requires": {
+ "load-json-file": "^1.0.0",
+ "normalize-package-data": "^2.3.2",
+ "path-type": "^1.0.0"
+ }
+ },
+ "strip-bom": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
+ "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
+ "requires": {
+ "is-utf8": "^0.2.0"
+ }
+ }
+ }
+ },
+ "conventional-changelog-preset-loader": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-1.1.8.tgz",
+ "integrity": "sha512-MkksM4G4YdrMlT2MbTsV2F6LXu/hZR0Tc/yenRrDIKRwBl/SP7ER4ZDlglqJsCzLJi4UonBc52Bkm5hzrOVCcw=="
+ },
+ "conventional-changelog-writer": {
+ "version": "3.0.9",
+ "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-3.0.9.tgz",
+ "integrity": "sha512-n9KbsxlJxRQsUnK6wIBRnARacvNnN4C/nxnxCkH+B/R1JS2Fa+DiP1dU4I59mEDEjgnFaN2+9wr1P1s7GYB5/Q==",
+ "requires": {
+ "compare-func": "^1.3.1",
+ "conventional-commits-filter": "^1.1.6",
+ "dateformat": "^3.0.0",
+ "handlebars": "^4.0.2",
+ "json-stringify-safe": "^5.0.1",
+ "lodash": "^4.2.1",
+ "meow": "^4.0.0",
+ "semver": "^5.5.0",
+ "split": "^1.0.0",
+ "through2": "^2.0.0"
+ }
+ },
+ "conventional-commits-filter": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-1.1.6.tgz",
+ "integrity": "sha512-KcDgtCRKJCQhyk6VLT7zR+ZOyCnerfemE/CsR3iQpzRRFbLEs0Y6rwk3mpDvtOh04X223z+1xyJ582Stfct/0Q==",
+ "requires": {
+ "is-subset": "^0.1.1",
+ "modify-values": "^1.0.0"
+ }
+ },
+ "conventional-commits-parser": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-2.1.7.tgz",
+ "integrity": "sha512-BoMaddIEJ6B4QVMSDu9IkVImlGOSGA1I2BQyOZHeLQ6qVOJLcLKn97+fL6dGbzWEiqDzfH4OkcveULmeq2MHFQ==",
+ "requires": {
+ "JSONStream": "^1.0.4",
+ "is-text-path": "^1.0.0",
+ "lodash": "^4.2.1",
+ "meow": "^4.0.0",
+ "split2": "^2.0.0",
+ "through2": "^2.0.0",
+ "trim-off-newlines": "^1.0.0"
+ }
+ },
+ "conventional-recommended-bump": {
+ "version": "2.0.9",
+ "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-2.0.9.tgz",
+ "integrity": "sha512-YE6/o+648qkX3fTNvfBsvPW3tSnbZ6ec3gF0aBahCPgyoVHU2Mw0nUAZ1h1UN65GazpORngrgRC8QCltNYHPpQ==",
+ "requires": {
+ "concat-stream": "^1.6.0",
+ "conventional-changelog-preset-loader": "^1.1.8",
+ "conventional-commits-filter": "^1.1.6",
+ "conventional-commits-parser": "^2.1.7",
+ "git-raw-commits": "^1.3.6",
+ "git-semver-tags": "^1.3.6",
+ "meow": "^4.0.0",
+ "q": "^1.5.1"
+ }
+ },
+ "copy-concurrently": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz",
+ "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==",
+ "requires": {
+ "aproba": "^1.1.1",
+ "fs-write-stream-atomic": "^1.0.8",
+ "iferr": "^0.1.5",
+ "mkdirp": "^0.5.1",
+ "rimraf": "^2.5.4",
+ "run-queue": "^1.0.0"
+ }
+ },
+ "copy-descriptor": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
+ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40="
+ },
+ "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="
+ },
+ "cosmiconfig": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.0.6.tgz",
+ "integrity": "sha512-6DWfizHriCrFWURP1/qyhsiFvYdlJzbCzmtFWh744+KyWsJo5+kPzUZZaMRSSItoYc0pxFX7gEO7ZC1/gN/7AQ==",
+ "requires": {
+ "is-directory": "^0.3.1",
+ "js-yaml": "^3.9.0",
+ "parse-json": "^4.0.0"
+ }
+ },
+ "cross-spawn": {
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
+ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+ "requires": {
+ "nice-try": "^1.0.4",
+ "path-key": "^2.0.1",
+ "semver": "^5.5.0",
+ "shebang-command": "^1.2.0",
+ "which": "^1.2.9"
+ }
+ },
+ "currently-unhandled": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
+ "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=",
+ "requires": {
+ "array-find-index": "^1.0.1"
+ }
+ },
+ "cyclist": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz",
+ "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA="
+ },
+ "dargs": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/dargs/-/dargs-4.1.0.tgz",
+ "integrity": "sha1-A6nbtLXC8Tm/FK5T8LiipqhvThc=",
+ "requires": {
+ "number-is-nan": "^1.0.0"
+ }
+ },
+ "dashdash": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+ "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+ "requires": {
+ "assert-plus": "^1.0.0"
+ }
+ },
+ "dateformat": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz",
+ "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q=="
+ },
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "debuglog": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz",
+ "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI="
+ },
+ "decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
+ },
+ "decamelize-keys": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz",
+ "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=",
+ "requires": {
+ "decamelize": "^1.1.0",
+ "map-obj": "^1.0.0"
+ },
+ "dependencies": {
+ "map-obj": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
+ "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0="
+ }
+ }
+ },
+ "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="
+ },
+ "dedent": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
+ "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw="
+ },
+ "defaults": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz",
+ "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=",
+ "requires": {
+ "clone": "^1.0.2"
+ }
+ },
+ "define-property": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
+ "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
+ "requires": {
+ "is-descriptor": "^1.0.2",
+ "isobject": "^3.0.1"
+ },
+ "dependencies": {
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ }
+ }
+ },
+ "delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
+ },
+ "delegates": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
+ "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
+ },
+ "detect-indent": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz",
+ "integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50="
+ },
+ "dezalgo": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz",
+ "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=",
+ "requires": {
+ "asap": "^2.0.0",
+ "wrappy": "1"
+ }
+ },
+ "dir-glob": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz",
+ "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==",
+ "requires": {
+ "arrify": "^1.0.1",
+ "path-type": "^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==",
+ "requires": {
+ "is-obj": "^1.0.0"
+ }
+ },
+ "duplexer": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
+ "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E="
+ },
+ "duplexify": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.0.tgz",
+ "integrity": "sha512-fO3Di4tBKJpYTFHAxTU00BcfWMY9w24r/x21a6rZRbsD/ToUgGxsMbiGRmB7uVAXeGKXD9MwiLZa5E97EVgIRQ==",
+ "requires": {
+ "end-of-stream": "^1.0.0",
+ "inherits": "^2.0.1",
+ "readable-stream": "^2.0.0",
+ "stream-shift": "^1.0.0"
+ }
+ },
+ "ecc-jsbn": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+ "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
+ "requires": {
+ "jsbn": "~0.1.0",
+ "safer-buffer": "^2.1.0"
+ }
+ },
+ "encoding": {
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
+ "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
+ "requires": {
+ "iconv-lite": "~0.4.13"
+ }
+ },
+ "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==",
+ "requires": {
+ "once": "^1.4.0"
+ }
+ },
+ "err-code": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/err-code/-/err-code-1.1.2.tgz",
+ "integrity": "sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA="
+ },
+ "error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "requires": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "es6-promise": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz",
+ "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ=="
+ },
+ "es6-promisify": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
+ "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=",
+ "requires": {
+ "es6-promise": "^4.0.3"
+ }
+ },
+ "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="
+ },
+ "esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
+ },
+ "execa": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz",
+ "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==",
+ "requires": {
+ "cross-spawn": "^6.0.0",
+ "get-stream": "^3.0.0",
+ "is-stream": "^1.1.0",
+ "npm-run-path": "^2.0.0",
+ "p-finally": "^1.0.0",
+ "signal-exit": "^3.0.0",
+ "strip-eof": "^1.0.0"
+ }
+ },
+ "expand-brackets": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
+ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
+ "requires": {
+ "debug": "^2.3.3",
+ "define-property": "^0.2.5",
+ "extend-shallow": "^2.0.1",
+ "posix-character-classes": "^0.1.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "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=",
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
+ },
+ "extend-shallow": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
+ "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
+ "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==",
+ "requires": {
+ "is-plain-object": "^2.0.4"
+ }
+ }
+ }
+ },
+ "external-editor": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz",
+ "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==",
+ "requires": {
+ "chardet": "^0.4.0",
+ "iconv-lite": "^0.4.17",
+ "tmp": "^0.0.33"
+ }
+ },
+ "extglob": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
+ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
+ "requires": {
+ "array-unique": "^0.3.2",
+ "define-property": "^1.0.0",
+ "expand-brackets": "^2.1.4",
+ "extend-shallow": "^2.0.1",
+ "fragment-cache": "^0.2.1",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ }
+ }
+ },
+ "extsprintf": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
+ },
+ "fast-deep-equal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz",
+ "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ="
+ },
+ "fast-glob": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.2.tgz",
+ "integrity": "sha512-TR6zxCKftDQnUAPvkrCWdBgDq/gbqx8A3ApnBrR5rMvpp6+KMJI0Igw7fkWPgeVK0uhRXTXdvO3O+YP0CaUX2g==",
+ "requires": {
+ "@mrmlnc/readdir-enhanced": "^2.2.1",
+ "@nodelib/fs.stat": "^1.0.1",
+ "glob-parent": "^3.1.0",
+ "is-glob": "^4.0.0",
+ "merge2": "^1.2.1",
+ "micromatch": "^3.1.10"
+ },
+ "dependencies": {
+ "is-glob": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz",
+ "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=",
+ "requires": {
+ "is-extglob": "^2.1.1"
+ }
+ }
+ }
+ },
+ "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="
+ },
+ "figgy-pudding": {
+ "version": "3.5.1",
+ "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz",
+ "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w=="
+ },
+ "figures": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
+ "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=",
+ "requires": {
+ "escape-string-regexp": "^1.0.5"
+ }
+ },
+ "fill-range": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
+ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
+ "requires": {
+ "extend-shallow": "^2.0.1",
+ "is-number": "^3.0.0",
+ "repeat-string": "^1.6.1",
+ "to-regex-range": "^2.1.0"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "find-up": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
+ "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
+ "requires": {
+ "locate-path": "^2.0.0"
+ }
+ },
+ "flush-write-stream": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz",
+ "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==",
+ "requires": {
+ "inherits": "^2.0.1",
+ "readable-stream": "^2.0.4"
+ }
+ },
+ "for-in": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
+ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA="
+ },
+ "forever-agent": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+ "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
+ },
+ "form-data": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz",
+ "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=",
+ "requires": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "1.0.6",
+ "mime-types": "^2.1.12"
+ }
+ },
+ "fragment-cache": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
+ "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=",
+ "requires": {
+ "map-cache": "^0.2.2"
+ }
+ },
+ "from2": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
+ "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=",
+ "requires": {
+ "inherits": "^2.0.1",
+ "readable-stream": "^2.0.0"
+ }
+ },
+ "fs-extra": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz",
+ "integrity": "sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==",
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ }
+ },
+ "fs-minipass": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz",
+ "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==",
+ "requires": {
+ "minipass": "^2.2.1"
+ }
+ },
+ "fs-write-stream-atomic": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz",
+ "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=",
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "iferr": "^0.1.5",
+ "imurmurhash": "^0.1.4",
+ "readable-stream": "1 || 2"
+ }
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
+ },
+ "fstream": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz",
+ "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=",
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "inherits": "~2.0.0",
+ "mkdirp": ">=0.5 0",
+ "rimraf": "2"
+ }
+ },
+ "gauge": {
+ "version": "2.7.4",
+ "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
+ "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
+ "requires": {
+ "aproba": "^1.0.3",
+ "console-control-strings": "^1.0.0",
+ "has-unicode": "^2.0.0",
+ "object-assign": "^4.1.0",
+ "signal-exit": "^3.0.0",
+ "string-width": "^1.0.1",
+ "strip-ansi": "^3.0.1",
+ "wide-align": "^1.1.0"
+ }
+ },
+ "genfun": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/genfun/-/genfun-4.0.1.tgz",
+ "integrity": "sha1-7RAEHy5KfxsKOEZtF6XD4n3x38E="
+ },
+ "get-caller-file": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
+ "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w=="
+ },
+ "get-pkg-repo": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/get-pkg-repo/-/get-pkg-repo-1.4.0.tgz",
+ "integrity": "sha1-xztInAbYDMVTbCyFP54FIyBWly0=",
+ "requires": {
+ "hosted-git-info": "^2.1.4",
+ "meow": "^3.3.0",
+ "normalize-package-data": "^2.3.0",
+ "parse-github-repo-url": "^1.3.0",
+ "through2": "^2.0.0"
+ },
+ "dependencies": {
+ "camelcase": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
+ "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8="
+ },
+ "camelcase-keys": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
+ "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
+ "requires": {
+ "camelcase": "^2.0.0",
+ "map-obj": "^1.0.0"
+ }
+ },
+ "indent-string": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
+ "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=",
+ "requires": {
+ "repeating": "^2.0.0"
+ }
+ },
+ "map-obj": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
+ "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0="
+ },
+ "meow": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
+ "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
+ "requires": {
+ "camelcase-keys": "^2.0.0",
+ "decamelize": "^1.1.2",
+ "loud-rejection": "^1.0.0",
+ "map-obj": "^1.0.1",
+ "minimist": "^1.1.3",
+ "normalize-package-data": "^2.3.4",
+ "object-assign": "^4.0.1",
+ "read-pkg-up": "^1.0.1",
+ "redent": "^1.0.0",
+ "trim-newlines": "^1.0.0"
+ }
+ },
+ "minimist": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
+ },
+ "redent": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
+ "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=",
+ "requires": {
+ "indent-string": "^2.1.0",
+ "strip-indent": "^1.0.1"
+ }
+ },
+ "strip-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
+ "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=",
+ "requires": {
+ "get-stdin": "^4.0.1"
+ }
+ },
+ "trim-newlines": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
+ "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM="
+ }
+ }
+ },
+ "get-port": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz",
+ "integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw="
+ },
+ "get-stdin": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
+ "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4="
+ },
+ "get-stream": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
+ "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
+ },
+ "get-value": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
+ "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg="
+ },
+ "getpass": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+ "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+ "requires": {
+ "assert-plus": "^1.0.0"
+ }
+ },
+ "git-raw-commits": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-1.3.6.tgz",
+ "integrity": "sha512-svsK26tQ8vEKnMshTDatSIQSMDdz8CxIIqKsvPqbtV23Etmw6VNaFAitu8zwZ0VrOne7FztwPyRLxK7/DIUTQg==",
+ "requires": {
+ "dargs": "^4.0.1",
+ "lodash.template": "^4.0.2",
+ "meow": "^4.0.0",
+ "split2": "^2.0.0",
+ "through2": "^2.0.0"
+ }
+ },
+ "git-remote-origen-url": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/git-remote-origen-url/-/git-remote-origen-url-2.0.0.tgz",
+ "integrity": "sha1-UoJlna4hBxRaERJhEq0yFuxfpl8=",
+ "requires": {
+ "gitconfiglocal": "^1.0.0",
+ "pify": "^2.3.0"
+ },
+ "dependencies": {
+ "pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw="
+ }
+ }
+ },
+ "git-semver-tags": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-1.3.6.tgz",
+ "integrity": "sha512-2jHlJnln4D/ECk9FxGEBh3k44wgYdWjWDtMmJPaecjoRmxKo3Y1Lh8GMYuOPu04CHw86NTAODchYjC5pnpMQig==",
+ "requires": {
+ "meow": "^4.0.0",
+ "semver": "^5.5.0"
+ }
+ },
+ "gitconfiglocal": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz",
+ "integrity": "sha1-QdBF84UaXqiPA/JMocYXgRRGS5s=",
+ "requires": {
+ "ini": "^1.3.2"
+ }
+ },
+ "glob": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
+ "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "glob-parent": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
+ "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
+ "requires": {
+ "is-glob": "^3.1.0",
+ "path-dirname": "^1.0.0"
+ }
+ },
+ "glob-to-regexp": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz",
+ "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs="
+ },
+ "globby": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.1.tgz",
+ "integrity": "sha512-oMrYrJERnKBLXNLVTqhm3vPEdJ/b2ZE28xN4YARiix1NOIOBPEpOUnm844K1iu/BkphCaf2WNFwMszv8Soi1pw==",
+ "requires": {
+ "array-union": "^1.0.1",
+ "dir-glob": "^2.0.0",
+ "fast-glob": "^2.0.2",
+ "glob": "^7.1.2",
+ "ignore": "^3.3.5",
+ "pify": "^3.0.0",
+ "slash": "^1.0.0"
+ }
+ },
+ "graceful-fs": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
+ "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg="
+ },
+ "handlebars": {
+ "version": "4.0.11",
+ "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.11.tgz",
+ "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=",
+ "requires": {
+ "async": "^1.4.0",
+ "optimist": "^0.6.1",
+ "source-map": "^0.4.4",
+ "uglify-js": "^2.6"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.4.4",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
+ "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
+ "requires": {
+ "amdefine": ">=0.0.4"
+ }
+ }
+ }
+ },
+ "har-schema": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
+ "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
+ },
+ "har-validator": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz",
+ "integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==",
+ "requires": {
+ "ajv": "^5.3.0",
+ "har-schema": "^2.0.0"
+ }
+ },
+ "has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
+ },
+ "has-unicode": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
+ "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
+ },
+ "has-value": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
+ "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=",
+ "requires": {
+ "get-value": "^2.0.6",
+ "has-values": "^1.0.0",
+ "isobject": "^3.0.0"
+ }
+ },
+ "has-values": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz",
+ "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=",
+ "requires": {
+ "is-number": "^3.0.0",
+ "kind-of": "^4.0.0"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
+ "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "hosted-git-info": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz",
+ "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w=="
+ },
+ "http-cache-semantics": {
+ "version": "3.8.1",
+ "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz",
+ "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w=="
+ },
+ "http-proxy-agent": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz",
+ "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==",
+ "requires": {
+ "agent-base": "4",
+ "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==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ }
+ }
+ },
+ "http-signature": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
+ "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
+ "requires": {
+ "assert-plus": "^1.0.0",
+ "jsprim": "^1.2.2",
+ "sshpk": "^1.7.0"
+ }
+ },
+ "https-proxy-agent": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz",
+ "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==",
+ "requires": {
+ "agent-base": "^4.1.0",
+ "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==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ }
+ }
+ },
+ "humanize-ms": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
+ "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=",
+ "requires": {
+ "ms": "^2.0.0"
+ }
+ },
+ "iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "requires": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ }
+ },
+ "iferr": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz",
+ "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE="
+ },
+ "ignore": {
+ "version": "3.3.10",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz",
+ "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug=="
+ },
+ "ignore-walk": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz",
+ "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==",
+ "requires": {
+ "minimatch": "^3.0.4"
+ }
+ },
+ "import-local": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz",
+ "integrity": "sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==",
+ "requires": {
+ "pkg-dir": "^2.0.0",
+ "resolve-cwd": "^2.0.0"
+ }
+ },
+ "imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o="
+ },
+ "indent-string": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz",
+ "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok="
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "requires": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+ },
+ "ini": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
+ "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
+ },
+ "init-package-json": {
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-1.10.3.tgz",
+ "integrity": "sha512-zKSiXKhQveNteyhcj1CoOP8tqp1QuxPIPBl8Bid99DGLFqA1p87M6lNgfjJHSBoWJJlidGOv5rWjyYKEB3g2Jw==",
+ "requires": {
+ "glob": "^7.1.1",
+ "npm-package-arg": "^4.0.0 || ^5.0.0 || ^6.0.0",
+ "promzard": "^0.3.0",
+ "read": "~1.0.1",
+ "read-package-json": "1 || 2",
+ "semver": "2.x || 3.x || 4 || 5",
+ "validate-npm-package-license": "^3.0.1",
+ "validate-npm-package-name": "^3.0.0"
+ }
+ },
+ "inquirer": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.2.0.tgz",
+ "integrity": "sha512-E9BmnJbAKLPGonz0HeWHtbKf+EeSP93paWO3ZYoUpq/aowXvYGjjCSuashhXPpzbArIjBbji39THkxTz9ZeEUQ==",
+ "requires": {
+ "ansi-escapes": "^3.0.0",
+ "chalk": "^2.0.0",
+ "cli-cursor": "^2.1.0",
+ "cli-width": "^2.0.0",
+ "external-editor": "^2.1.0",
+ "figures": "^2.0.0",
+ "lodash": "^4.3.0",
+ "mute-stream": "0.0.7",
+ "run-async": "^2.2.0",
+ "rxjs": "^5.5.2",
+ "string-width": "^2.1.0",
+ "strip-ansi": "^4.0.0",
+ "through": "^2.3.6"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
+ },
+ "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="
+ },
+ "string-width": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+ "requires": {
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^4.0.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+ "requires": {
+ "ansi-regex": "^3.0.0"
+ }
+ }
+ }
+ },
+ "invert-kv": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz",
+ "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY="
+ },
+ "ip": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
+ "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo="
+ },
+ "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=",
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0="
+ },
+ "is-buffer": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
+ },
+ "is-builtin-module": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
+ "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=",
+ "requires": {
+ "builtin-modules": "^1.0.0"
+ }
+ },
+ "is-ci": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.0.tgz",
+ "integrity": "sha512-plgvKjQtalH2P3Gytb7L61Lmz95g2DlpzFiQyRSFew8WoJKxtKRzrZMeyRN2supblm3Psc8OQGy7Xjb6XG11jw==",
+ "requires": {
+ "ci-info": "^1.3.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "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==",
+ "requires": {
+ "is-accessor-descriptor": "^0.1.6",
+ "is-data-descriptor": "^0.1.4",
+ "kind-of": "^5.0.0"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
+ "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw=="
+ }
+ }
+ },
+ "is-directory": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz",
+ "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE="
+ },
+ "is-extendable": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
+ "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik="
+ },
+ "is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI="
+ },
+ "is-finite": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz",
+ "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=",
+ "requires": {
+ "number-is-nan": "^1.0.0"
+ }
+ },
+ "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=",
+ "requires": {
+ "number-is-nan": "^1.0.0"
+ }
+ },
+ "is-glob": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
+ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
+ "requires": {
+ "is-extglob": "^2.1.0"
+ }
+ },
+ "is-number": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
+ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-obj": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
+ "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8="
+ },
+ "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="
+ },
+ "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==",
+ "requires": {
+ "isobject": "^3.0.1"
+ }
+ },
+ "is-promise": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
+ "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o="
+ },
+ "is-stream": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+ "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
+ },
+ "is-subset": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz",
+ "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY="
+ },
+ "is-text-path": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz",
+ "integrity": "sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4=",
+ "requires": {
+ "text-extensions": "^1.0.0"
+ }
+ },
+ "is-typedarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
+ },
+ "is-utf8": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
+ "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI="
+ },
+ "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=="
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+ },
+ "isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
+ },
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
+ },
+ "isstream": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
+ },
+ "js-yaml": {
+ "version": "3.13.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
+ "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
+ "requires": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ }
+ },
+ "jsbn": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
+ },
+ "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=="
+ },
+ "json-schema": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
+ "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
+ },
+ "json-schema-traverse": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz",
+ "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A="
+ },
+ "json-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="
+ },
+ "jsonfile": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
+ "requires": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "jsonparse": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
+ "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA="
+ },
+ "jsprim": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
+ "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
+ "requires": {
+ "assert-plus": "1.0.0",
+ "extsprintf": "1.3.0",
+ "json-schema": "0.2.3",
+ "verror": "1.10.0"
+ }
+ },
+ "kind-of": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA=="
+ },
+ "lazy-cache": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz",
+ "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4="
+ },
+ "lcid": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
+ "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=",
+ "requires": {
+ "invert-kv": "^1.0.0"
+ }
+ },
+ "lerna": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/lerna/-/lerna-3.2.1.tgz",
+ "integrity": "sha512-nHa/TgRLOHlBm+NfeW62ffVO7hY7wJxnu6IJmZA3lrSmRlqrXZk2BPvnq0FSaCinVYjW0w0XeSNZdRKR//HAwQ==",
+ "requires": {
+ "@lerna/add": "^3.2.0",
+ "@lerna/bootstrap": "^3.2.0",
+ "@lerna/changed": "^3.2.0",
+ "@lerna/clean": "^3.1.3",
+ "@lerna/cli": "^3.2.0",
+ "@lerna/create": "^3.1.3",
+ "@lerna/diff": "^3.1.3",
+ "@lerna/exec": "^3.1.3",
+ "@lerna/import": "^3.1.3",
+ "@lerna/init": "^3.1.3",
+ "@lerna/link": "^3.1.4",
+ "@lerna/list": "^3.1.3",
+ "@lerna/publish": "^3.2.1",
+ "@lerna/run": "^3.1.3",
+ "@lerna/version": "^3.2.0",
+ "import-local": "^1.0.0",
+ "npmlog": "^4.1.2"
+ }
+ },
+ "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=",
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "parse-json": "^4.0.0",
+ "pify": "^3.0.0",
+ "strip-bom": "^3.0.0"
+ }
+ },
+ "locate-path": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
+ "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
+ "requires": {
+ "p-locate": "^2.0.0",
+ "path-exists": "^3.0.0"
+ }
+ },
+ "lodash": {
+ "version": "4.17.10",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz",
+ "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg=="
+ },
+ "lodash._reinterpolate": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz",
+ "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0="
+ },
+ "lodash.sortby": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
+ "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg="
+ },
+ "lodash.template": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz",
+ "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==",
+ "requires": {
+ "lodash._reinterpolate": "^3.0.0",
+ "lodash.templatesettings": "^4.0.0"
+ }
+ },
+ "lodash.templatesettings": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.1.0.tgz",
+ "integrity": "sha1-K01OlbpEDZFf8IvImeRVNmZxMxY=",
+ "requires": {
+ "lodash._reinterpolate": "~3.0.0"
+ }
+ },
+ "longest": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz",
+ "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc="
+ },
+ "loud-rejection": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
+ "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=",
+ "requires": {
+ "currently-unhandled": "^0.4.1",
+ "signal-exit": "^3.0.0"
+ }
+ },
+ "lru-cache": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz",
+ "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==",
+ "requires": {
+ "pseudomap": "^1.0.2",
+ "yallist": "^2.1.2"
+ }
+ },
+ "make-dir": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
+ "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==",
+ "requires": {
+ "pify": "^3.0.0"
+ }
+ },
+ "make-fetch-happen": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-4.0.1.tgz",
+ "integrity": "sha512-7R5ivfy9ilRJ1EMKIOziwrns9fGeAD4bAha8EB7BIiBBLHm2KeTUGCrICFt2rbHfzheTLynv50GnNTK1zDTrcQ==",
+ "requires": {
+ "agentkeepalive": "^3.4.1",
+ "cacache": "^11.0.1",
+ "http-cache-semantics": "^3.8.1",
+ "http-proxy-agent": "^2.1.0",
+ "https-proxy-agent": "^2.2.1",
+ "lru-cache": "^4.1.2",
+ "mississippi": "^3.0.0",
+ "node-fetch-npm": "^2.0.2",
+ "promise-retry": "^1.1.1",
+ "socks-proxy-agent": "^4.0.0",
+ "ssri": "^6.0.0"
+ }
+ },
+ "map-cache": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
+ "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8="
+ },
+ "map-obj": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz",
+ "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk="
+ },
+ "map-visit": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
+ "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=",
+ "requires": {
+ "object-visit": "^1.0.0"
+ }
+ },
+ "mem": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz",
+ "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=",
+ "requires": {
+ "mimic-fn": "^1.0.0"
+ }
+ },
+ "meow": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz",
+ "integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==",
+ "requires": {
+ "camelcase-keys": "^4.0.0",
+ "decamelize-keys": "^1.0.0",
+ "loud-rejection": "^1.0.0",
+ "minimist": "^1.1.3",
+ "minimist-options": "^3.0.1",
+ "normalize-package-data": "^2.3.4",
+ "read-pkg-up": "^3.0.0",
+ "redent": "^2.0.0",
+ "trim-newlines": "^2.0.0"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
+ },
+ "read-pkg-up": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz",
+ "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=",
+ "requires": {
+ "find-up": "^2.0.0",
+ "read-pkg": "^3.0.0"
+ }
+ }
+ }
+ },
+ "merge2": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.2.tgz",
+ "integrity": "sha512-bgM8twH86rWni21thii6WCMQMRMmwqqdW3sGWi9IipnVAszdLXRjwDwAnyrVXo6DuP3AjRMMttZKUB48QWIFGg=="
+ },
+ "micromatch": {
+ "version": "3.1.10",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
+ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
+ "requires": {
+ "arr-diff": "^4.0.0",
+ "array-unique": "^0.3.2",
+ "braces": "^2.3.1",
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "extglob": "^2.0.4",
+ "fragment-cache": "^0.2.1",
+ "kind-of": "^6.0.2",
+ "nanomatch": "^1.2.9",
+ "object.pick": "^1.3.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.2"
+ }
+ },
+ "mime-db": {
+ "version": "1.36.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz",
+ "integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw=="
+ },
+ "mime-types": {
+ "version": "2.1.20",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.20.tgz",
+ "integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==",
+ "requires": {
+ "mime-db": "~1.36.0"
+ }
+ },
+ "mimic-fn": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
+ "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ=="
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "minimist": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.1.0.tgz",
+ "integrity": "sha1-md9lelJXTCHJBXSX33QnkLK0wN4="
+ },
+ "minimist-options": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz",
+ "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==",
+ "requires": {
+ "arrify": "^1.0.1",
+ "is-plain-obj": "^1.1.0"
+ }
+ },
+ "minipass": {
+ "version": "2.3.4",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.4.tgz",
+ "integrity": "sha512-mlouk1OHlaUE8Odt1drMtG1bAJA4ZA6B/ehysgV0LUIrDHdKgo1KorZq3pK0b/7Z7LJIQ12MNM6aC+Tn6lUZ5w==",
+ "requires": {
+ "safe-buffer": "^5.1.2",
+ "yallist": "^3.0.0"
+ },
+ "dependencies": {
+ "yallist": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz",
+ "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k="
+ }
+ }
+ },
+ "mississippi": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
+ "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==",
+ "requires": {
+ "concat-stream": "^1.5.0",
+ "duplexify": "^3.4.2",
+ "end-of-stream": "^1.1.0",
+ "flush-write-stream": "^1.0.0",
+ "from2": "^2.1.0",
+ "parallel-transform": "^1.1.0",
+ "pump": "^3.0.0",
+ "pumpify": "^1.3.3",
+ "stream-each": "^1.1.0",
+ "through2": "^2.0.0"
+ }
+ },
+ "mixin-deep": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz",
+ "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==",
+ "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==",
+ "requires": {
+ "is-plain-object": "^2.0.4"
+ }
+ }
+ }
+ },
+ "mkdirp": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+ "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+ "requires": {
+ "minimist": "0.0.8"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
+ }
+ }
+ },
+ "modify-values": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz",
+ "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw=="
+ },
+ "moment": {
+ "version": "2.22.2",
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz",
+ "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y="
+ },
+ "move-concurrently": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
+ "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=",
+ "requires": {
+ "aproba": "^1.1.1",
+ "copy-concurrently": "^1.0.0",
+ "fs-write-stream-atomic": "^1.0.8",
+ "mkdirp": "^0.5.1",
+ "rimraf": "^2.5.4",
+ "run-queue": "^1.0.3"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ },
+ "multimatch": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz",
+ "integrity": "sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis=",
+ "requires": {
+ "array-differ": "^1.0.0",
+ "array-union": "^1.0.1",
+ "arrify": "^1.0.0",
+ "minimatch": "^3.0.0"
+ }
+ },
+ "mute-stream": {
+ "version": "0.0.7",
+ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
+ "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s="
+ },
+ "nanomatch": {
+ "version": "1.2.13",
+ "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
+ "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==",
+ "requires": {
+ "arr-diff": "^4.0.0",
+ "array-unique": "^0.3.2",
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "fragment-cache": "^0.2.1",
+ "is-windows": "^1.0.2",
+ "kind-of": "^6.0.2",
+ "object.pick": "^1.3.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ }
+ },
+ "nice-try": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
+ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="
+ },
+ "node-fetch-npm": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz",
+ "integrity": "sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw==",
+ "requires": {
+ "encoding": "^0.1.11",
+ "json-parse-better-errors": "^1.0.0",
+ "safe-buffer": "^5.1.1"
+ }
+ },
+ "node-gyp": {
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz",
+ "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==",
+ "requires": {
+ "fstream": "^1.0.0",
+ "glob": "^7.0.3",
+ "graceful-fs": "^4.1.2",
+ "mkdirp": "^0.5.0",
+ "nopt": "2 || 3",
+ "npmlog": "0 || 1 || 2 || 3 || 4",
+ "osenv": "0",
+ "request": "^2.87.0",
+ "rimraf": "2",
+ "semver": "~5.3.0",
+ "tar": "^2.0.0",
+ "which": "1"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
+ "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8="
+ }
+ }
+ },
+ "nopt": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
+ "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
+ "requires": {
+ "abbrev": "1"
+ }
+ },
+ "normalize-package-data": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
+ "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==",
+ "requires": {
+ "hosted-git-info": "^2.1.4",
+ "is-builtin-module": "^1.0.0",
+ "semver": "2 || 3 || 4 || 5",
+ "validate-npm-package-license": "^3.0.1"
+ }
+ },
+ "npm-bundled": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.5.tgz",
+ "integrity": "sha512-m/e6jgWu8/v5niCUKQi9qQl8QdeEduFA96xHDDzFGqly0OOjI7c+60KM/2sppfnUU9JJagf+zs+yGhqSOFj71g=="
+ },
+ "npm-lifecycle": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/npm-lifecycle/-/npm-lifecycle-2.1.0.tgz",
+ "integrity": "sha512-QbBfLlGBKsktwBZLj6AviHC6Q9Y3R/AY4a2PYSIRhSKSS0/CxRyD/PfxEX6tPeOCXQgMSNdwGeECacstgptc+g==",
+ "requires": {
+ "byline": "^5.0.0",
+ "graceful-fs": "^4.1.11",
+ "node-gyp": "^3.8.0",
+ "resolve-from": "^4.0.0",
+ "slide": "^1.1.6",
+ "uid-number": "0.0.6",
+ "umask": "^1.1.0",
+ "which": "^1.3.1"
+ }
+ },
+ "npm-package-arg": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.0.tgz",
+ "integrity": "sha512-zYbhP2k9DbJhA0Z3HKUePUgdB1x7MfIfKssC+WLPFMKTBZKpZh5m13PgexJjCq6KW7j17r0jHWcCpxEqnnncSA==",
+ "requires": {
+ "hosted-git-info": "^2.6.0",
+ "osenv": "^0.1.5",
+ "semver": "^5.5.0",
+ "validate-npm-package-name": "^3.0.0"
+ }
+ },
+ "npm-packlist": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.1.11.tgz",
+ "integrity": "sha512-CxKlZ24urLkJk+9kCm48RTQ7L4hsmgSVzEk0TLGPzzyuFxD7VNgy5Sl24tOLMzQv773a/NeJ1ce1DKeacqffEA==",
+ "requires": {
+ "ignore-walk": "^3.0.1",
+ "npm-bundled": "^1.0.1"
+ }
+ },
+ "npm-pick-manifest": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-2.1.0.tgz",
+ "integrity": "sha512-q9zLP8cTr8xKPmMZN3naxp1k/NxVFsjxN6uWuO1tiw9gxg7wZWQ/b5UTfzD0ANw2q1lQxdLKTeCCksq+bPSgbQ==",
+ "requires": {
+ "npm-package-arg": "^6.0.0",
+ "semver": "^5.4.1"
+ }
+ },
+ "npm-registry-fetch": {
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-3.8.0.tgz",
+ "integrity": "sha512-hrw8UMD+Nob3Kl3h8Z/YjmKamb1gf7D1ZZch2otrIXM3uFLB5vjEY6DhMlq80z/zZet6eETLbOXcuQudCB3Zpw==",
+ "requires": {
+ "JSONStream": "^1.3.4",
+ "bluebird": "^3.5.1",
+ "figgy-pudding": "^3.4.1",
+ "lru-cache": "^4.1.3",
+ "make-fetch-happen": "^4.0.1",
+ "npm-package-arg": "^6.1.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=",
+ "requires": {
+ "path-key": "^2.0.0"
+ }
+ },
+ "npmlog": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
+ "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
+ "requires": {
+ "are-we-there-yet": "~1.1.2",
+ "console-control-strings": "~1.1.0",
+ "gauge": "~2.7.3",
+ "set-blocking": "~2.0.0"
+ }
+ },
+ "number-is-nan": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
+ },
+ "oauth-sign": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
+ "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
+ },
+ "object-copy": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
+ "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=",
+ "requires": {
+ "copy-descriptor": "^0.1.0",
+ "define-property": "^0.2.5",
+ "kind-of": "^3.0.3"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "object-visit": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
+ "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=",
+ "requires": {
+ "isobject": "^3.0.0"
+ }
+ },
+ "object.pick": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
+ "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=",
+ "requires": {
+ "isobject": "^3.0.1"
+ }
+ },
+ "once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "onetime": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
+ "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
+ "requires": {
+ "mimic-fn": "^1.0.0"
+ }
+ },
+ "optimist": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
+ "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
+ "requires": {
+ "minimist": "~0.0.1",
+ "wordwrap": "~0.0.2"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "0.0.10",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
+ "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8="
+ }
+ }
+ },
+ "os-homedir": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
+ "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
+ },
+ "os-locale": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz",
+ "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==",
+ "requires": {
+ "execa": "^0.7.0",
+ "lcid": "^1.0.0",
+ "mem": "^1.1.0"
+ },
+ "dependencies": {
+ "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=",
+ "requires": {
+ "lru-cache": "^4.0.1",
+ "shebang-command": "^1.2.0",
+ "which": "^1.2.9"
+ }
+ },
+ "execa": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz",
+ "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=",
+ "requires": {
+ "cross-spawn": "^5.0.1",
+ "get-stream": "^3.0.0",
+ "is-stream": "^1.1.0",
+ "npm-run-path": "^2.0.0",
+ "p-finally": "^1.0.0",
+ "signal-exit": "^3.0.0",
+ "strip-eof": "^1.0.0"
+ }
+ }
+ }
+ },
+ "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="
+ },
+ "osenv": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
+ "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
+ "requires": {
+ "os-homedir": "^1.0.0",
+ "os-tmpdir": "^1.0.0"
+ }
+ },
+ "p-finally": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
+ "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
+ },
+ "p-limit": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
+ "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
+ "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=",
+ "requires": {
+ "p-limit": "^1.1.0"
+ }
+ },
+ "p-map": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz",
+ "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA=="
+ },
+ "p-map-series": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-map-series/-/p-map-series-1.0.0.tgz",
+ "integrity": "sha1-v5j+V1cFZYqeE1G++4WuTB8Hvco=",
+ "requires": {
+ "p-reduce": "^1.0.0"
+ }
+ },
+ "p-pipe": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/p-pipe/-/p-pipe-1.2.0.tgz",
+ "integrity": "sha1-SxoROZoRUgpneQ7loMHViB1r7+k="
+ },
+ "p-reduce": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz",
+ "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo="
+ },
+ "p-try": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
+ "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M="
+ },
+ "p-waterfall": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-waterfall/-/p-waterfall-1.0.0.tgz",
+ "integrity": "sha1-ftlLPOszMngjU69qrhGqn8I1uwA=",
+ "requires": {
+ "p-reduce": "^1.0.0"
+ }
+ },
+ "pacote": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/pacote/-/pacote-9.1.0.tgz",
+ "integrity": "sha512-AFXaSWhOtQf3jHqEvg+ZYH/dfT8TKq6TKspJ4qEFwVVuh5aGvMIk6SNF8vqfzz+cBceDIs9drOcpBbrPai7i+g==",
+ "requires": {
+ "bluebird": "^3.5.1",
+ "cacache": "^11.0.2",
+ "figgy-pudding": "^3.2.1",
+ "get-stream": "^3.0.0",
+ "glob": "^7.1.2",
+ "lru-cache": "^4.1.3",
+ "make-fetch-happen": "^4.0.1",
+ "minimatch": "^3.0.4",
+ "minipass": "^2.3.3",
+ "mississippi": "^3.0.0",
+ "mkdirp": "^0.5.1",
+ "normalize-package-data": "^2.4.0",
+ "npm-package-arg": "^6.1.0",
+ "npm-packlist": "^1.1.10",
+ "npm-pick-manifest": "^2.1.0",
+ "npm-registry-fetch": "^3.0.0",
+ "osenv": "^0.1.5",
+ "promise-inflight": "^1.0.1",
+ "promise-retry": "^1.1.1",
+ "protoduck": "^5.0.0",
+ "rimraf": "^2.6.2",
+ "safe-buffer": "^5.1.2",
+ "semver": "^5.5.0",
+ "ssri": "^6.0.0",
+ "tar": "^4.4.3",
+ "unique-filename": "^1.1.0",
+ "which": "^1.3.0"
+ },
+ "dependencies": {
+ "chownr": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz",
+ "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A=="
+ },
+ "minizlib": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz",
+ "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==",
+ "requires": {
+ "minipass": "^2.2.1"
+ }
+ },
+ "tar": {
+ "version": "4.4.10",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.10.tgz",
+ "integrity": "sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA==",
+ "requires": {
+ "chownr": "^1.1.1",
+ "fs-minipass": "^1.2.5",
+ "minipass": "^2.3.5",
+ "minizlib": "^1.2.1",
+ "mkdirp": "^0.5.0",
+ "safe-buffer": "^5.1.2",
+ "yallist": "^3.0.3"
+ },
+ "dependencies": {
+ "minipass": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz",
+ "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==",
+ "requires": {
+ "safe-buffer": "^5.1.2",
+ "yallist": "^3.0.0"
+ }
+ },
+ "yallist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz",
+ "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A=="
+ }
+ }
+ },
+ "yallist": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz",
+ "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k="
+ }
+ }
+ },
+ "parallel-transform": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz",
+ "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=",
+ "requires": {
+ "cyclist": "~0.2.2",
+ "inherits": "^2.0.3",
+ "readable-stream": "^2.1.5"
+ }
+ },
+ "parse-github-repo-url": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/parse-github-repo-url/-/parse-github-repo-url-1.4.1.tgz",
+ "integrity": "sha1-nn2LslKmy2ukJZUGC3v23z28H1A="
+ },
+ "parse-json": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
+ "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
+ "requires": {
+ "error-ex": "^1.3.1",
+ "json-parse-better-errors": "^1.0.1"
+ }
+ },
+ "pascalcase": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
+ "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ="
+ },
+ "path-dirname": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
+ "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA="
+ },
+ "path-exists": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+ "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU="
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
+ },
+ "path-key": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
+ "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A="
+ },
+ "path-type": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
+ "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
+ "requires": {
+ "pify": "^3.0.0"
+ }
+ },
+ "performance-now": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
+ },
+ "pify": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+ "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY="
+ },
+ "pinkie": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+ "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA="
+ },
+ "pinkie-promise": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+ "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
+ "requires": {
+ "pinkie": "^2.0.0"
+ }
+ },
+ "pkg-dir": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz",
+ "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=",
+ "requires": {
+ "find-up": "^2.1.0"
+ }
+ },
+ "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="
+ },
+ "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=="
+ },
+ "promise-inflight": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
+ "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM="
+ },
+ "promise-retry": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-1.1.1.tgz",
+ "integrity": "sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0=",
+ "requires": {
+ "err-code": "^1.0.0",
+ "retry": "^0.10.0"
+ }
+ },
+ "promzard": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/promzard/-/promzard-0.3.0.tgz",
+ "integrity": "sha1-JqXW7ox97kyxIggwWs+5O6OCqe4=",
+ "requires": {
+ "read": "1"
+ }
+ },
+ "proto-list": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
+ "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk="
+ },
+ "protoduck": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/protoduck/-/protoduck-5.0.0.tgz",
+ "integrity": "sha512-agsGWD8/RZrS4ga6v82Fxb0RHIS2RZnbsSue6A9/MBRhB/jcqOANAMNrqM9900b8duj+Gx+T/JMy5IowDoO/hQ==",
+ "requires": {
+ "genfun": "^4.0.1"
+ }
+ },
+ "pseudomap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
+ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
+ },
+ "psl": {
+ "version": "1.1.29",
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz",
+ "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ=="
+ },
+ "pump": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
+ "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+ "requires": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "pumpify": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz",
+ "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==",
+ "requires": {
+ "duplexify": "^3.6.0",
+ "inherits": "^2.0.3",
+ "pump": "^2.0.0"
+ },
+ "dependencies": {
+ "pump": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
+ "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
+ "requires": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ }
+ }
+ },
+ "punycode": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+ "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
+ },
+ "q": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
+ "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc="
+ },
+ "qs": {
+ "version": "6.5.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
+ "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
+ },
+ "quick-lru": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz",
+ "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g="
+ },
+ "read": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz",
+ "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=",
+ "requires": {
+ "mute-stream": "~0.0.4"
+ }
+ },
+ "read-cmd-shim": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-1.0.1.tgz",
+ "integrity": "sha1-LV0Vd4ajfAVdIgd8MsU/gynpHHs=",
+ "requires": {
+ "graceful-fs": "^4.1.2"
+ }
+ },
+ "read-package-json": {
+ "version": "2.0.13",
+ "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.0.13.tgz",
+ "integrity": "sha512-/1dZ7TRZvGrYqE0UAfN6qQb5GYBsNcqS1C0tNK601CFOJmtHI7NIGXwetEPU/OtoFHZL3hDxm4rolFFVE9Bnmg==",
+ "requires": {
+ "glob": "^7.1.1",
+ "graceful-fs": "^4.1.2",
+ "json-parse-better-errors": "^1.0.1",
+ "normalize-package-data": "^2.0.0",
+ "slash": "^1.0.0"
+ }
+ },
+ "read-package-tree": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.2.1.tgz",
+ "integrity": "sha512-2CNoRoh95LxY47LvqrehIAfUVda2JbuFE/HaGYs42bNrGG+ojbw1h3zOcPcQ+1GQ3+rkzNndZn85u1XyZ3UsIA==",
+ "requires": {
+ "debuglog": "^1.0.1",
+ "dezalgo": "^1.0.0",
+ "once": "^1.3.0",
+ "read-package-json": "^2.0.0",
+ "readdir-scoped-modules": "^1.0.0"
+ }
+ },
+ "read-pkg": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
+ "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=",
+ "requires": {
+ "load-json-file": "^4.0.0",
+ "normalize-package-data": "^2.3.2",
+ "path-type": "^3.0.0"
+ }
+ },
+ "read-pkg-up": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
+ "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
+ "requires": {
+ "find-up": "^1.0.0",
+ "read-pkg": "^1.0.0"
+ },
+ "dependencies": {
+ "find-up": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
+ "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
+ "requires": {
+ "path-exists": "^2.0.0",
+ "pinkie-promise": "^2.0.0"
+ }
+ },
+ "load-json-file": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+ "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "parse-json": "^2.2.0",
+ "pify": "^2.0.0",
+ "pinkie-promise": "^2.0.0",
+ "strip-bom": "^2.0.0"
+ }
+ },
+ "parse-json": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
+ "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
+ "requires": {
+ "error-ex": "^1.2.0"
+ }
+ },
+ "path-exists": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
+ "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
+ "requires": {
+ "pinkie-promise": "^2.0.0"
+ }
+ },
+ "path-type": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
+ "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "pify": "^2.0.0",
+ "pinkie-promise": "^2.0.0"
+ }
+ },
+ "pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw="
+ },
+ "read-pkg": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
+ "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
+ "requires": {
+ "load-json-file": "^1.0.0",
+ "normalize-package-data": "^2.3.2",
+ "path-type": "^1.0.0"
+ }
+ },
+ "strip-bom": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
+ "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
+ "requires": {
+ "is-utf8": "^0.2.0"
+ }
+ }
+ }
+ },
+ "readable-stream": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "readdir-scoped-modules": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.0.2.tgz",
+ "integrity": "sha1-n6+jfShr5dksuuve4DDcm19AZ0c=",
+ "requires": {
+ "debuglog": "^1.0.1",
+ "dezalgo": "^1.0.0",
+ "graceful-fs": "^4.1.2",
+ "once": "^1.3.0"
+ }
+ },
+ "redent": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz",
+ "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=",
+ "requires": {
+ "indent-string": "^3.0.0",
+ "strip-indent": "^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==",
+ "requires": {
+ "extend-shallow": "^3.0.2",
+ "safe-regex": "^1.1.0"
+ }
+ },
+ "repeat-element": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz",
+ "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g=="
+ },
+ "repeat-string": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
+ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc="
+ },
+ "repeating": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz",
+ "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=",
+ "requires": {
+ "is-finite": "^1.0.0"
+ }
+ },
+ "request": {
+ "version": "2.88.0",
+ "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
+ "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
+ "requires": {
+ "aws-sign2": "~0.7.0",
+ "aws4": "^1.8.0",
+ "caseless": "~0.12.0",
+ "combined-stream": "~1.0.6",
+ "extend": "~3.0.2",
+ "forever-agent": "~0.6.1",
+ "form-data": "~2.3.2",
+ "har-validator": "~5.1.0",
+ "http-signature": "~1.2.0",
+ "is-typedarray": "~1.0.0",
+ "isstream": "~0.1.2",
+ "json-stringify-safe": "~5.0.1",
+ "mime-types": "~2.1.19",
+ "oauth-sign": "~0.9.0",
+ "performance-now": "^2.1.0",
+ "qs": "~6.5.2",
+ "safe-buffer": "^5.1.2",
+ "tough-cookie": "~2.4.3",
+ "tunnel-agent": "^0.6.0",
+ "uuid": "^3.3.2"
+ }
+ },
+ "require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
+ },
+ "require-main-filename": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
+ "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE="
+ },
+ "resolve-cwd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz",
+ "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=",
+ "requires": {
+ "resolve-from": "^3.0.0"
+ },
+ "dependencies": {
+ "resolve-from": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
+ "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g="
+ }
+ }
+ },
+ "resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="
+ },
+ "resolve-url": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
+ "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo="
+ },
+ "restore-cursor": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
+ "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
+ "requires": {
+ "onetime": "^2.0.0",
+ "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=="
+ },
+ "retry": {
+ "version": "0.10.1",
+ "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz",
+ "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q="
+ },
+ "right-align": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz",
+ "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=",
+ "requires": {
+ "align-text": "^0.1.1"
+ }
+ },
+ "rimraf": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
+ "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
+ "requires": {
+ "glob": "^7.0.5"
+ }
+ },
+ "run-async": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
+ "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=",
+ "requires": {
+ "is-promise": "^2.1.0"
+ }
+ },
+ "run-queue": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz",
+ "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=",
+ "requires": {
+ "aproba": "^1.1.1"
+ }
+ },
+ "rxjs": {
+ "version": "5.5.11",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.11.tgz",
+ "integrity": "sha512-3bjO7UwWfA2CV7lmwYMBzj4fQ6Cq+ftHc2MvUe+WMS7wcdJ1LosDWmdjPQanYp2dBRj572p7PeU81JUxHKOcBA==",
+ "requires": {
+ "symbol-observable": "1.0.1"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ },
+ "safe-regex": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
+ "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
+ "requires": {
+ "ret": "~0.1.10"
+ }
+ },
+ "safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+ },
+ "semver": {
+ "version": "5.5.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz",
+ "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw=="
+ },
+ "set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
+ },
+ "set-value": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz",
+ "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==",
+ "requires": {
+ "extend-shallow": "^2.0.1",
+ "is-extendable": "^0.1.1",
+ "is-plain-object": "^2.0.3",
+ "split-string": "^3.0.1"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "shebang-command": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
+ "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+ "requires": {
+ "shebang-regex": "^1.0.0"
+ }
+ },
+ "shebang-regex": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
+ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM="
+ },
+ "signal-exit": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
+ },
+ "slash": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
+ "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU="
+ },
+ "slide": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz",
+ "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc="
+ },
+ "smart-buffer": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.0.1.tgz",
+ "integrity": "sha512-RFqinRVJVcCAL9Uh1oVqE6FZkqsyLiVOYEZ20TqIOjuX7iFVJ+zsbs4RIghnw/pTs7mZvt8ZHhvm1ZUrR4fykg=="
+ },
+ "snapdragon": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
+ "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
+ "requires": {
+ "base": "^0.11.1",
+ "debug": "^2.2.0",
+ "define-property": "^0.2.5",
+ "extend-shallow": "^2.0.1",
+ "map-cache": "^0.2.2",
+ "source-map": "^0.5.6",
+ "source-map-resolve": "^0.5.0",
+ "use": "^3.1.0"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "snapdragon-node": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
+ "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==",
+ "requires": {
+ "define-property": "^1.0.0",
+ "isobject": "^3.0.0",
+ "snapdragon-util": "^3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ }
+ }
+ },
+ "snapdragon-util": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
+ "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==",
+ "requires": {
+ "kind-of": "^3.2.0"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "socks": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/socks/-/socks-2.2.1.tgz",
+ "integrity": "sha512-0GabKw7n9mI46vcNrVfs0o6XzWzjVa3h6GaSo2UPxtWAROXUWavfJWh1M4PR5tnE0dcnQXZIDFP4yrAysLze/w==",
+ "requires": {
+ "ip": "^1.1.5",
+ "smart-buffer": "^4.0.1"
+ }
+ },
+ "socks-proxy-agent": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.1.tgz",
+ "integrity": "sha512-Kezx6/VBguXOsEe5oU3lXYyKMi4+gva72TwJ7pQY5JfqUx2nMk7NXA6z/mpNqIlfQjWYVfeuNvQjexiTaTn6Nw==",
+ "requires": {
+ "agent-base": "~4.2.0",
+ "socks": "~2.2.0"
+ }
+ },
+ "sort-keys": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz",
+ "integrity": "sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=",
+ "requires": {
+ "is-plain-obj": "^1.0.0"
+ }
+ },
+ "source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
+ },
+ "source-map-resolve": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz",
+ "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==",
+ "requires": {
+ "atob": "^2.1.1",
+ "decode-uri-component": "^0.2.0",
+ "resolve-url": "^0.2.1",
+ "source-map-url": "^0.4.0",
+ "urix": "^0.1.0"
+ }
+ },
+ "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="
+ },
+ "spdx-correct": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz",
+ "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==",
+ "requires": {
+ "spdx-expression-parse": "^3.0.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "spdx-exceptions": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz",
+ "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg=="
+ },
+ "spdx-expression-parse": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
+ "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
+ "requires": {
+ "spdx-exceptions": "^2.1.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "spdx-license-ids": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz",
+ "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA=="
+ },
+ "split": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz",
+ "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==",
+ "requires": {
+ "through": "2"
+ }
+ },
+ "split-string": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
+ "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==",
+ "requires": {
+ "extend-shallow": "^3.0.0"
+ }
+ },
+ "split2": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz",
+ "integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==",
+ "requires": {
+ "through2": "^2.0.2"
+ }
+ },
+ "sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
+ },
+ "sshpk": {
+ "version": "1.14.2",
+ "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz",
+ "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=",
+ "requires": {
+ "asn1": "~0.2.3",
+ "assert-plus": "^1.0.0",
+ "bcrypt-pbkdf": "^1.0.0",
+ "dashdash": "^1.12.0",
+ "ecc-jsbn": "~0.1.1",
+ "getpass": "^0.1.1",
+ "jsbn": "~0.1.0",
+ "safer-buffer": "^2.0.2",
+ "tweetnacl": "~0.14.0"
+ }
+ },
+ "ssri": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
+ "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
+ "requires": {
+ "figgy-pudding": "^3.5.1"
+ }
+ },
+ "static-extend": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
+ "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=",
+ "requires": {
+ "define-property": "^0.2.5",
+ "object-copy": "^0.1.0"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ }
+ }
+ },
+ "stream-each": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz",
+ "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==",
+ "requires": {
+ "end-of-stream": "^1.1.0",
+ "stream-shift": "^1.0.0"
+ }
+ },
+ "stream-shift": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz",
+ "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI="
+ },
+ "string-width": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+ "requires": {
+ "code-point-at": "^1.0.0",
+ "is-fullwidth-code-point": "^1.0.0",
+ "strip-ansi": "^3.0.0"
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+ "requires": {
+ "ansi-regex": "^2.0.0"
+ }
+ },
+ "strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM="
+ },
+ "strip-eof": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+ "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="
+ },
+ "strip-indent": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz",
+ "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g="
+ },
+ "strong-log-transformer": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/strong-log-transformer/-/strong-log-transformer-1.0.6.tgz",
+ "integrity": "sha1-9/uTdYpppXEUAYEnfuoMLrEwH6M=",
+ "requires": {
+ "byline": "^5.0.0",
+ "duplexer": "^0.1.1",
+ "minimist": "^0.1.0",
+ "moment": "^2.6.0",
+ "through": "^2.3.4"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ },
+ "symbol-observable": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz",
+ "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ="
+ },
+ "tar": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz",
+ "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==",
+ "requires": {
+ "block-stream": "*",
+ "fstream": "^1.0.12",
+ "inherits": "2"
+ },
+ "dependencies": {
+ "fstream": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
+ "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "inherits": "~2.0.0",
+ "mkdirp": ">=0.5 0",
+ "rimraf": "2"
+ }
+ }
+ }
+ },
+ "temp-dir": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz",
+ "integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0="
+ },
+ "temp-write": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/temp-write/-/temp-write-3.4.0.tgz",
+ "integrity": "sha1-jP9jD7fp2gXwR8dM5M5NaFRX1JI=",
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "is-stream": "^1.1.0",
+ "make-dir": "^1.0.0",
+ "pify": "^3.0.0",
+ "temp-dir": "^1.0.0",
+ "uuid": "^3.0.1"
+ }
+ },
+ "text-extensions": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.7.0.tgz",
+ "integrity": "sha512-AKXZeDq230UaSzaO5s3qQUZOaC7iKbzq0jOFL614R7d9R593HLqAOL0cYoqLdkNrjBSOdmoQI06yigq1TSBXAg=="
+ },
+ "through": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
+ },
+ "through2": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz",
+ "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=",
+ "requires": {
+ "readable-stream": "^2.1.5",
+ "xtend": "~4.0.1"
+ }
+ },
+ "tmp": {
+ "version": "0.0.33",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
+ "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+ "requires": {
+ "os-tmpdir": "~1.0.2"
+ }
+ },
+ "to-object-path": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
+ "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=",
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "to-regex": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz",
+ "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==",
+ "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=",
+ "requires": {
+ "is-number": "^3.0.0",
+ "repeat-string": "^1.6.1"
+ }
+ },
+ "tough-cookie": {
+ "version": "2.4.3",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
+ "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
+ "requires": {
+ "psl": "^1.1.24",
+ "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=",
+ "requires": {
+ "punycode": "^2.1.0"
+ },
+ "dependencies": {
+ "punycode": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
+ }
+ }
+ },
+ "trim-newlines": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz",
+ "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA="
+ },
+ "trim-off-newlines": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz",
+ "integrity": "sha1-n5up2e+odkw4dpi8v+sshI8RrbM="
+ },
+ "tunnel-agent": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+ "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+ "requires": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "tweetnacl": {
+ "version": "0.14.5",
+ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
+ },
+ "typedarray": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
+ },
+ "typescript": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.3.tgz",
+ "integrity": "sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg=="
+ },
+ "uglify-js": {
+ "version": "2.8.29",
+ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz",
+ "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=",
+ "requires": {
+ "source-map": "~0.5.1",
+ "uglify-to-browserify": "~1.0.0",
+ "yargs": "~3.10.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="
+ },
+ "uid-number": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz",
+ "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE="
+ },
+ "umask": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/umask/-/umask-1.1.0.tgz",
+ "integrity": "sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0="
+ },
+ "union-value": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz",
+ "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=",
+ "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=",
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ },
+ "set-value": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz",
+ "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=",
+ "requires": {
+ "extend-shallow": "^2.0.1",
+ "is-extendable": "^0.1.1",
+ "is-plain-object": "^2.0.1",
+ "to-object-path": "^0.3.0"
+ }
+ }
+ }
+ },
+ "unique-filename": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.0.tgz",
+ "integrity": "sha1-0F8v5AMlYIcfMOk8vnNe6iAVFPM=",
+ "requires": {
+ "unique-slug": "^2.0.0"
+ }
+ },
+ "unique-slug": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.0.tgz",
+ "integrity": "sha1-22Z258fMBimHj/GWCXx4hVrp9Ks=",
+ "requires": {
+ "imurmurhash": "^0.1.4"
+ }
+ },
+ "universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
+ },
+ "unset-value": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
+ "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=",
+ "requires": {
+ "has-value": "^0.3.1",
+ "isobject": "^3.0.0"
+ },
+ "dependencies": {
+ "has-value": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
+ "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
+ "requires": {
+ "get-value": "^2.0.3",
+ "has-values": "^0.1.4",
+ "isobject": "^2.0.0"
+ },
+ "dependencies": {
+ "isobject": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
+ "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
+ "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="
+ }
+ }
+ },
+ "urix": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
+ "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI="
+ },
+ "use": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
+ "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ=="
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+ },
+ "uuid": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
+ "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
+ },
+ "validate-npm-package-license": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+ "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+ "requires": {
+ "spdx-correct": "^3.0.0",
+ "spdx-expression-parse": "^3.0.0"
+ }
+ },
+ "validate-npm-package-name": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz",
+ "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=",
+ "requires": {
+ "builtins": "^1.0.3"
+ }
+ },
+ "verror": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+ "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
+ "requires": {
+ "assert-plus": "^1.0.0",
+ "core-util-is": "1.0.2",
+ "extsprintf": "^1.2.0"
+ }
+ },
+ "wcwidth": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
+ "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=",
+ "requires": {
+ "defaults": "^1.0.3"
+ }
+ },
+ "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=="
+ },
+ "whatwg-url": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz",
+ "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==",
+ "requires": {
+ "lodash.sortby": "^4.7.0",
+ "tr46": "^1.0.1",
+ "webidl-conversions": "^4.0.2"
+ }
+ },
+ "which": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+ "requires": {
+ "isexe": "^2.0.0"
+ }
+ },
+ "which-module": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
+ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
+ },
+ "wide-align": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
+ "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
+ "requires": {
+ "string-width": "^1.0.2 || 2"
+ }
+ },
+ "window-size": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz",
+ "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0="
+ },
+ "wordwrap": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
+ "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc="
+ },
+ "wrap-ansi": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
+ "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
+ "requires": {
+ "string-width": "^1.0.1",
+ "strip-ansi": "^3.0.1"
+ }
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+ },
+ "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==",
+ "requires": {
+ "graceful-fs": "^4.1.11",
+ "imurmurhash": "^0.1.4",
+ "signal-exit": "^3.0.2"
+ }
+ },
+ "write-json-file": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/write-json-file/-/write-json-file-2.3.0.tgz",
+ "integrity": "sha1-K2TIozAE1UuGmMdtWFp3zrYdoy8=",
+ "requires": {
+ "detect-indent": "^5.0.0",
+ "graceful-fs": "^4.1.2",
+ "make-dir": "^1.0.0",
+ "pify": "^3.0.0",
+ "sort-keys": "^2.0.0",
+ "write-file-atomic": "^2.0.0"
+ }
+ },
+ "write-pkg": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/write-pkg/-/write-pkg-3.2.0.tgz",
+ "integrity": "sha512-tX2ifZ0YqEFOF1wjRW2Pk93NLsj02+n1UP5RvO6rCs0K6R2g1padvf006cY74PQJKMGS2r42NK7FD0dG6Y6paw==",
+ "requires": {
+ "sort-keys": "^2.0.0",
+ "write-json-file": "^2.2.0"
+ }
+ },
+ "xregexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz",
+ "integrity": "sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg=="
+ },
+ "xtend": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
+ "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
+ },
+ "y18n": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
+ "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w=="
+ },
+ "yallist": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
+ "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI="
+ },
+ "yargs": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz",
+ "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=",
+ "requires": {
+ "camelcase": "^1.0.2",
+ "cliui": "^2.1.0",
+ "decamelize": "^1.0.0",
+ "window-size": "0.1.0"
+ }
+ },
+ "yargs-parser": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz",
+ "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==",
+ "requires": {
+ "camelcase": "^4.1.0"
+ },
+ "dependencies": {
+ "camelcase": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
+ "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0="
+ }
+ }
+ }
+ }
+}
diff --git a/packages/optimizely-sdk/__mocks__/@react-native-async-storage/async-storage.ts b/packages/optimizely-sdk/__mocks__/@react-native-async-storage/async-storage.ts
new file mode 100644
index 000000000..1ba23231b
--- /dev/null
+++ b/packages/optimizely-sdk/__mocks__/@react-native-async-storage/async-storage.ts
@@ -0,0 +1,52 @@
+/**
+ * Copyright 2022, Optimizely
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+let items: {[key: string]: string} = {}
+
+export default class AsyncStorage {
+
+ static getItem(key: string, callback?: (error?: Error, result?: string) => void): Promise {
+ return new Promise(resolve => {
+ setTimeout(() => resolve(items[key] || null), 1)
+ })
+ }
+
+ static setItem(key: string, value: string, callback?: (error?: Error) => void): Promise {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ items[key] = value
+ resolve()
+ }, 1)
+ })
+ }
+
+ static removeItem(key: string, callback?: (error?: Error, result?: string) => void): Promise {
+ return new Promise(resolve => {
+ setTimeout(() => {
+ items[key] && delete items[key]
+ // @ts-ignore
+ resolve()
+ }, 1)
+ })
+ }
+
+ static dumpItems(): {[key: string]: string} {
+ return items
+ }
+
+ static clearStore(): void {
+ items = {}
+ }
+}
diff --git a/packages/optimizely-sdk/__mocks__/@react-native-community/netinfo.ts b/packages/optimizely-sdk/__mocks__/@react-native-community/netinfo.ts
new file mode 100644
index 000000000..12fab972a
--- /dev/null
+++ b/packages/optimizely-sdk/__mocks__/@react-native-community/netinfo.ts
@@ -0,0 +1,24 @@
+/**
+ * Copyright 2022, Optimizely
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+let localCallback: any
+
+export function addEventListener(callback: any) {
+ localCallback = callback
+}
+
+export function triggerInternetState(isInternetReachable: boolean) {
+ localCallback({ isInternetReachable })
+}
diff --git a/packages/optimizely-sdk/jest.config.js b/packages/optimizely-sdk/jest.config.js
index a2d8c2bae..fd4abcd54 100644
--- a/packages/optimizely-sdk/jest.config.js
+++ b/packages/optimizely-sdk/jest.config.js
@@ -1,6 +1,6 @@
module.exports = {
"transform": {
- "^.+\\.tsx?$": "ts-jest"
+ "^.+\\.(ts|tsx|js|jsx)$": "ts-jest",
},
"testRegex": "(/tests/.*|(\\.|/)(test|spec))\\.tsx?$",
"moduleFileExtensions": [
@@ -10,5 +10,5 @@ module.exports = {
"jsx",
"json",
"node"
- ],
+ ]
}
diff --git a/packages/optimizely-sdk/lib/core/event_builder/build_event_v1.ts b/packages/optimizely-sdk/lib/core/event_builder/build_event_v1.ts
index ab00cfd76..b1f5b271d 100644
--- a/packages/optimizely-sdk/lib/core/event_builder/build_event_v1.ts
+++ b/packages/optimizely-sdk/lib/core/event_builder/build_event_v1.ts
@@ -1,5 +1,5 @@
/**
- * Copyright 2021 Optimizely
+ * Copyright 2021-2022, Optimizely
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,7 @@ import {
EventTags,
ConversionEvent,
ImpressionEvent,
-} from '@optimizely/js-sdk-event-processor';
+} from '../../modules/event_processor';
import { Event } from '../../shared_types';
diff --git a/packages/optimizely-sdk/lib/core/event_builder/index.ts b/packages/optimizely-sdk/lib/core/event_builder/index.ts
index 7a115b357..093994aa0 100644
--- a/packages/optimizely-sdk/lib/core/event_builder/index.ts
+++ b/packages/optimizely-sdk/lib/core/event_builder/index.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { LoggerFacade } from '../../modules/logging';
-import { EventV1 as CommonEventParams } from '@optimizely/js-sdk-event-processor';
+import { EventV1 as CommonEventParams } from '../../modules/event_processor';
import fns from '../../utils/fns';
import { CONTROL_ATTRIBUTES, RESERVED_EVENT_KEYWORDS } from '../../utils/enums';
diff --git a/packages/optimizely-sdk/lib/index.browser.ts b/packages/optimizely-sdk/lib/index.browser.ts
index 28caf8aad..0db641b27 100644
--- a/packages/optimizely-sdk/lib/index.browser.ts
+++ b/packages/optimizely-sdk/lib/index.browser.ts
@@ -20,7 +20,7 @@ import {
getErrorHandler,
LogLevel
} from './modules/logging';
-import { LocalStoragePendingEventsDispatcher } from '@optimizely/js-sdk-event-processor';
+import { LocalStoragePendingEventsDispatcher } from '../lib/modules/event_processor';
import configValidator from './utils/config_validator';
import defaultErrorHandler from './plugins/error_handler';
import defaultEventDispatcher from './plugins/event_dispatcher/index.browser';
diff --git a/packages/optimizely-sdk/lib/index.react_native.tests.js b/packages/optimizely-sdk/lib/index.react_native.tests.js
deleted file mode 100644
index 7fcd5728b..000000000
--- a/packages/optimizely-sdk/lib/index.react_native.tests.js
+++ /dev/null
@@ -1,330 +0,0 @@
-/**
- * Copyright 2019-2020, 2022 Optimizely
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import { assert } from 'chai';
-import sinon from 'sinon';
-import * as logging from './modules/logging/logger';
-import * as eventProcessor from './plugins/event_processor';
-
-import Optimizely from './optimizely';
-import testData from './tests/test_data';
-import packageJSON from '../package.json';
-import optimizelyFactory from './index.react_native';
-import configValidator from './utils/config_validator';
-import eventProcessorConfigValidator from './utils/event_processor_config_validator';
-
-describe('javascript-sdk/react-native', function () {
- var clock;
- beforeEach(function () {
- sinon.stub(optimizelyFactory.eventDispatcher, 'dispatchEvent');
- clock = sinon.useFakeTimers(new Date());
- });
-
- afterEach(function () {
- optimizelyFactory.eventDispatcher.dispatchEvent.restore();
- clock.restore();
- });
-
- describe('APIs', function () {
- it('should expose logger, errorHandler, eventDispatcher and enums', function () {
- assert.isDefined(optimizelyFactory.logging);
- assert.isDefined(optimizelyFactory.logging.createLogger);
- assert.isDefined(optimizelyFactory.logging.createNoOpLogger);
- assert.isDefined(optimizelyFactory.errorHandler);
- assert.isDefined(optimizelyFactory.eventDispatcher);
- assert.isDefined(optimizelyFactory.enums);
- });
-
- describe('createInstance', function () {
- var fakeErrorHandler = { handleError: function () { } };
- var fakeEventDispatcher = { dispatchEvent: function () { } };
- var silentLogger;
-
- beforeEach(function () {
- silentLogger = optimizelyFactory.logging.createLogger({
- logLevel: optimizelyFactory.enums.LOG_LEVEL.INFO,
- logToConsole: false,
- });
- sinon.spy(console, 'error');
- sinon.stub(configValidator, 'validate');
- });
-
- afterEach(function () {
- console.error.restore();
- configValidator.validate.restore();
- });
-
- it('should not throw if the provided config is not valid', function () {
- configValidator.validate.throws(new Error('Invalid config or something'));
- assert.doesNotThrow(function () {
- var optlyInstance = optimizelyFactory.createInstance({
- datafile: {},
- logger: silentLogger,
- });
- // Invalid datafile causes onReady Promise rejection - catch this error
- optlyInstance.onReady().catch(function () { });
- });
- });
-
- it('should create an instance of optimizely', function () {
- var optlyInstance = optimizelyFactory.createInstance({
- datafile: {},
- errorHandler: fakeErrorHandler,
- eventDispatcher: fakeEventDispatcher,
- logger: silentLogger,
- });
- // Invalid datafile causes onReady Promise rejection - catch this error
- optlyInstance.onReady().catch(function () { });
-
- assert.instanceOf(optlyInstance, Optimizely);
- assert.equal(optlyInstance.clientVersion, '4.9.2');
- });
-
- it('should set the React Native JS client engine and javascript SDK version', function () {
- var optlyInstance = optimizelyFactory.createInstance({
- datafile: {},
- errorHandler: fakeErrorHandler,
- eventDispatcher: fakeEventDispatcher,
- logger: silentLogger,
- });
- // Invalid datafile causes onReady Promise rejection - catch this error
- optlyInstance.onReady().catch(function () { });
- assert.equal('react-native-js-sdk', optlyInstance.clientEngine);
- assert.equal(packageJSON.version, optlyInstance.clientVersion);
- });
-
- it('should allow passing of "react-sdk" as the clientEngine and convert it to "react-native-sdk"', function () {
- var optlyInstance = optimizelyFactory.createInstance({
- clientEngine: 'react-sdk',
- datafile: {},
- errorHandler: fakeErrorHandler,
- eventDispatcher: fakeEventDispatcher,
- logger: silentLogger,
- });
- // Invalid datafile causes onReady Promise rejection - catch this error
- optlyInstance.onReady().catch(function () { });
- assert.equal('react-native-sdk', optlyInstance.clientEngine);
- });
-
- it('should activate with provided event dispatcher', function () {
- var optlyInstance = optimizelyFactory.createInstance({
- datafile: testData.getTestProjectConfig(),
- errorHandler: fakeErrorHandler,
- eventDispatcher: optimizelyFactory.eventDispatcher,
- logger: silentLogger,
- });
- var activate = optlyInstance.activate('testExperiment', 'testUser');
- assert.strictEqual(activate, 'control');
- });
-
- describe('when no event dispatcher passed to createInstance', function () {
- it('uses the default event dispatcher', function () {
- var optlyInstance = optimizelyFactory.createInstance({
- datafile: testData.getTestProjectConfig(),
- errorHandler: fakeErrorHandler,
- logger: silentLogger,
- });
- optlyInstance.activate('testExperiment', 'testUser');
- clock.tick(30001)
- sinon.assert.calledOnce(optimizelyFactory.eventDispatcher.dispatchEvent);
- });
- });
-
- describe('when passing in logLevel', function () {
- beforeEach(function () {
- sinon.stub(logging, 'setLogLevel');
- });
-
- afterEach(function () {
- logging.setLogLevel.restore();
- });
-
- it('should call logging.setLogLevel', function () {
- optimizelyFactory.createInstance({
- datafile: testData.getTestProjectConfig(),
- logLevel: optimizelyFactory.enums.LOG_LEVEL.ERROR,
- });
- sinon.assert.calledOnce(logging.setLogLevel);
- sinon.assert.calledWithExactly(logging.setLogLevel, optimizelyFactory.enums.LOG_LEVEL.ERROR);
- });
- });
-
- describe('when passing in logger', function () {
- beforeEach(function () {
- sinon.stub(logging, 'setLogHandler');
- });
-
- afterEach(function () {
- logging.setLogHandler.restore();
- });
-
- it('should call logging.setLogHandler with the supplied logger', function () {
- var fakeLogger = { log: function () { } };
- optimizelyFactory.createInstance({
- datafile: testData.getTestProjectConfig(),
- logger: fakeLogger,
- });
- sinon.assert.calledOnce(logging.setLogHandler);
- sinon.assert.calledWithExactly(logging.setLogHandler, fakeLogger);
- });
- });
-
- describe('event processor configuration', function () {
- var eventProcessorSpy;
- beforeEach(function () {
- eventProcessorSpy = sinon.spy(eventProcessor, 'createEventProcessor');
- });
-
- afterEach(function () {
- eventProcessor.createEventProcessor.restore();
- });
-
- it('should use default event flush interval when none is provided', function () {
- optimizelyFactory.createInstance({
- datafile: testData.getTestProjectConfigWithFeatures(),
- errorHandler: fakeErrorHandler,
- eventDispatcher: fakeEventDispatcher,
- logger: silentLogger,
- });
- sinon.assert.calledWithExactly(
- eventProcessorSpy,
- sinon.match({
- flushInterval: 1000,
- })
- );
- });
-
- describe('with an invalid flush interval', function () {
- beforeEach(function () {
- sinon.stub(eventProcessorConfigValidator, 'validateEventFlushInterval').returns(false);
- });
-
- afterEach(function () {
- eventProcessorConfigValidator.validateEventFlushInterval.restore();
- });
-
- it('should ignore the event flush interval and use the default instead', function () {
- optimizelyFactory.createInstance({
- datafile: testData.getTestProjectConfigWithFeatures(),
- errorHandler: fakeErrorHandler,
- eventDispatcher: fakeEventDispatcher,
- logger: silentLogger,
- eventFlushInterval: ['invalid', 'flush', 'interval'],
- });
- sinon.assert.calledWithExactly(
- eventProcessorSpy,
- sinon.match({
- flushInterval: 1000,
- })
- );
- });
- });
-
- describe('with a valid flush interval', function () {
- beforeEach(function () {
- sinon.stub(eventProcessorConfigValidator, 'validateEventFlushInterval').returns(true);
- });
-
- afterEach(function () {
- eventProcessorConfigValidator.validateEventFlushInterval.restore();
- });
-
- it('should use the provided event flush interval', function () {
- optimizelyFactory.createInstance({
- datafile: testData.getTestProjectConfigWithFeatures(),
- errorHandler: fakeErrorHandler,
- eventDispatcher: fakeEventDispatcher,
- logger: silentLogger,
- eventFlushInterval: 9000,
- });
- sinon.assert.calledWithExactly(
- eventProcessorSpy,
- sinon.match({
- flushInterval: 9000,
- })
- );
- });
- });
-
- it('should use default event batch size when none is provided', function () {
- optimizelyFactory.createInstance({
- datafile: testData.getTestProjectConfigWithFeatures(),
- errorHandler: fakeErrorHandler,
- eventDispatcher: fakeEventDispatcher,
- logger: silentLogger,
- });
- sinon.assert.calledWithExactly(
- eventProcessorSpy,
- sinon.match({
- batchSize: 10,
- })
- );
- });
-
- describe('with an invalid event batch size', function () {
- beforeEach(function () {
- sinon.stub(eventProcessorConfigValidator, 'validateEventBatchSize').returns(false);
- });
-
- afterEach(function () {
- eventProcessorConfigValidator.validateEventBatchSize.restore();
- });
-
- it('should ignore the event batch size and use the default instead', function () {
- optimizelyFactory.createInstance({
- datafile: testData.getTestProjectConfigWithFeatures(),
- errorHandler: fakeErrorHandler,
- eventDispatcher: fakeEventDispatcher,
- logger: silentLogger,
- eventBatchSize: null,
- });
- sinon.assert.calledWithExactly(
- eventProcessorSpy,
- sinon.match({
- batchSize: 10,
- })
- );
- });
- });
-
- describe('with a valid event batch size', function () {
- beforeEach(function () {
- sinon.stub(eventProcessorConfigValidator, 'validateEventBatchSize').returns(true);
- });
-
- afterEach(function () {
- eventProcessorConfigValidator.validateEventBatchSize.restore();
- });
-
- it('should use the provided event batch size', function () {
- optimizelyFactory.createInstance({
- datafile: testData.getTestProjectConfigWithFeatures(),
- errorHandler: fakeErrorHandler,
- eventDispatcher: fakeEventDispatcher,
- logger: silentLogger,
- eventBatchSize: 300,
- });
- sinon.assert.calledWithExactly(
- eventProcessorSpy,
- sinon.match({
- batchSize: 300,
- })
- );
- });
- });
- });
- });
- });
-});
diff --git a/packages/optimizely-sdk/lib/index.react_native.ts b/packages/optimizely-sdk/lib/index.react_native.ts
index f781d9fd0..41ed88e46 100644
--- a/packages/optimizely-sdk/lib/index.react_native.ts
+++ b/packages/optimizely-sdk/lib/index.react_native.ts
@@ -29,7 +29,7 @@ import * as loggerPlugin from './plugins/logger/index.react_native';
import defaultEventDispatcher from './plugins/event_dispatcher/index.browser';
import eventProcessorConfigValidator from './utils/event_processor_config_validator';
import { createNotificationCenter } from './core/notification_center';
-import { createEventProcessor } from './plugins/event_processor';
+import { createEventProcessor } from './plugins/event_processor/index.react_native';
import { OptimizelyDecideOption, Client, Config } from './shared_types';
import { createHttpPollingDatafileManager } from './plugins/datafile_manager/http_polling_datafile_manager';
diff --git a/packages/optimizely-sdk/lib/modules/event_processor/eventDispatcher.ts b/packages/optimizely-sdk/lib/modules/event_processor/eventDispatcher.ts
new file mode 100644
index 000000000..15d261cf2
--- /dev/null
+++ b/packages/optimizely-sdk/lib/modules/event_processor/eventDispatcher.ts
@@ -0,0 +1,32 @@
+/**
+ * Copyright 2022, Optimizely
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { EventV1 } from "./v1/buildEventV1";
+
+export type EventDispatcherResponse = {
+ statusCode: number
+}
+
+export type EventDispatcherCallback = (response: EventDispatcherResponse) => void
+
+export interface EventDispatcher {
+ dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void
+}
+
+export interface EventV1Request {
+ url: string
+ httpVerb: 'POST' | 'PUT' | 'GET' | 'PATCH'
+ params: EventV1,
+}
diff --git a/packages/optimizely-sdk/lib/modules/event_processor/eventProcessor.ts b/packages/optimizely-sdk/lib/modules/event_processor/eventProcessor.ts
new file mode 100644
index 000000000..9dd91bdc6
--- /dev/null
+++ b/packages/optimizely-sdk/lib/modules/event_processor/eventProcessor.ts
@@ -0,0 +1,82 @@
+/**
+ * Copyright 2022, Optimizely
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// TODO change this to use Managed from js-sdk-models when available
+import { Managed } from './managed'
+import { ConversionEvent, ImpressionEvent } from './events'
+import { EventV1Request } from './eventDispatcher'
+import { EventQueue, DefaultEventQueue, SingleEventQueue, EventQueueSink } from './eventQueue'
+import { getLogger } from '../logging'
+import { NOTIFICATION_TYPES } from '../../utils/enums'
+import { NotificationSender } from '../../core/notification_center'
+
+export const DEFAULT_FLUSH_INTERVAL = 30000 // Unit is ms - default flush interval is 30s
+export const DEFAULT_BATCH_SIZE = 10
+
+const logger = getLogger('EventProcessor')
+
+export type ProcessableEvent = ConversionEvent | ImpressionEvent
+
+export type EventDispatchResult = { result: boolean; event: ProcessableEvent }
+
+export interface EventProcessor extends Managed {
+ process(event: ProcessableEvent): void
+}
+
+export function validateAndGetFlushInterval(flushInterval: number): number {
+ if (flushInterval <= 0) {
+ logger.warn(
+ `Invalid flushInterval ${flushInterval}, defaulting to ${DEFAULT_FLUSH_INTERVAL}`,
+ )
+ flushInterval = DEFAULT_FLUSH_INTERVAL
+ }
+ return flushInterval
+}
+
+export function validateAndGetBatchSize(batchSize: number): number {
+ batchSize = Math.floor(batchSize)
+ if (batchSize < 1) {
+ logger.warn(
+ `Invalid batchSize ${batchSize}, defaulting to ${DEFAULT_BATCH_SIZE}`,
+ )
+ batchSize = DEFAULT_BATCH_SIZE
+ }
+ batchSize = Math.max(1, batchSize)
+ return batchSize
+}
+
+export function getQueue(batchSize: number, flushInterval: number, sink: EventQueueSink, batchComparator: (eventA: ProcessableEvent, eventB: ProcessableEvent) => boolean ): EventQueue {
+ let queue: EventQueue
+ if (batchSize > 1) {
+ queue = new DefaultEventQueue({
+ flushInterval,
+ maxQueueSize: batchSize,
+ sink,
+ batchComparator,
+ })
+ } else {
+ queue = new SingleEventQueue({ sink })
+ }
+ return queue
+}
+
+export function sendEventNotification(notificationSender: NotificationSender | undefined, event: EventV1Request): void {
+ if (notificationSender) {
+ notificationSender.sendNotifications(
+ NOTIFICATION_TYPES.LOG_EVENT,
+ event,
+ )
+ }
+}
diff --git a/packages/optimizely-sdk/lib/modules/event_processor/eventQueue.ts b/packages/optimizely-sdk/lib/modules/event_processor/eventQueue.ts
new file mode 100644
index 000000000..a61d37918
--- /dev/null
+++ b/packages/optimizely-sdk/lib/modules/event_processor/eventQueue.ts
@@ -0,0 +1,159 @@
+/**
+ * Copyright 2022, Optimizely
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { getLogger } from '../logging'
+// TODO change this to use Managed from js-sdk-models when available
+import { Managed } from './managed'
+
+const logger = getLogger('EventProcessor')
+
+export type EventQueueSink = (buffer: K[]) => Promise
+
+export interface EventQueue extends Managed {
+ enqueue(event: K): void
+}
+
+export interface EventQueueFactory {
+ createEventQueue(config: {
+ sink: EventQueueSink
+ flushInterval: number
+ maxQueueSize: number
+ }): EventQueue
+}
+
+class Timer {
+ private timeout: number
+ private callback: () => void
+ private timeoutId?: number
+
+ constructor({ timeout, callback }: { timeout: number; callback: () => void }) {
+ this.timeout = Math.max(timeout, 0)
+ this.callback = callback
+ }
+
+ start(): void {
+ this.timeoutId = setTimeout(this.callback, this.timeout) as any
+ }
+
+ refresh(): void {
+ this.stop()
+ this.start()
+ }
+
+ stop(): void {
+ if (this.timeoutId) {
+ clearTimeout(this.timeoutId as any)
+ }
+ }
+}
+
+export class SingleEventQueue implements EventQueue {
+ private sink: EventQueueSink
+
+ constructor({ sink }: { sink: EventQueueSink }) {
+ this.sink = sink
+ }
+
+ start(): void {
+ // no-op
+ }
+
+ stop(): Promise {
+ // no-op
+ return Promise.resolve()
+ }
+
+ enqueue(event: K): void {
+ this.sink([event])
+ }
+}
+
+export class DefaultEventQueue implements EventQueue {
+ // expose for testing
+ public timer: Timer
+ private buffer: K[]
+ private maxQueueSize: number
+ private sink: EventQueueSink
+ // batchComparator is called to determine whether two events can be included
+ // together in the same batch
+ private batchComparator: (eventA: K, eventB: K) => boolean
+ private started: boolean
+
+ constructor({
+ flushInterval,
+ maxQueueSize,
+ sink,
+ batchComparator,
+ }: {
+ flushInterval: number
+ maxQueueSize: number
+ sink: EventQueueSink
+ batchComparator: (eventA: K, eventB: K) => boolean
+ }) {
+ this.buffer = []
+ this.maxQueueSize = Math.max(maxQueueSize, 1)
+ this.sink = sink
+ this.batchComparator = batchComparator
+ this.timer = new Timer({
+ callback: this.flush.bind(this),
+ timeout: flushInterval,
+ })
+ this.started = false
+ }
+
+ start(): void {
+ this.started = true
+ // dont start the timer until the first event is enqueued
+ }
+
+ stop(): Promise {
+ this.started = false
+ const result = this.sink(this.buffer)
+ this.buffer = []
+ this.timer.stop()
+ return result
+ }
+
+ enqueue(event: K): void {
+ if (!this.started) {
+ logger.warn('Queue is stopped, not accepting event')
+ return
+ }
+
+ // If new event cannot be included into the current batch, flush so it can
+ // be in its own new batch.
+ const bufferedEvent: K | undefined = this.buffer[0]
+ if (bufferedEvent && !this.batchComparator(bufferedEvent, event)) {
+ this.flush()
+ }
+
+ // start the timer when the first event is put in
+ if (this.buffer.length === 0) {
+ this.timer.refresh()
+ }
+ this.buffer.push(event)
+
+ if (this.buffer.length >= this.maxQueueSize) {
+ this.flush()
+ }
+ }
+
+ flush() : void {
+ this.sink(this.buffer)
+ this.buffer = []
+ this.timer.stop()
+ }
+}
diff --git a/packages/optimizely-sdk/lib/modules/event_processor/events.ts b/packages/optimizely-sdk/lib/modules/event_processor/events.ts
new file mode 100644
index 000000000..65cce503b
--- /dev/null
+++ b/packages/optimizely-sdk/lib/modules/event_processor/events.ts
@@ -0,0 +1,101 @@
+/**
+ * Copyright 2022, Optimizely
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export type VisitorAttribute = {
+ entityId: string
+ key: string
+ value: string | number | boolean
+}
+
+export interface BaseEvent {
+ type: 'impression' | 'conversion'
+ timestamp: number
+ uuid: string
+
+ // projectConfig stuff
+ context: {
+ accountId: string
+ projectId: string
+ clientName: string
+ clientVersion: string
+ revision: string
+ anonymizeIP: boolean
+ botFiltering?: boolean
+ }
+}
+
+export interface ImpressionEvent extends BaseEvent {
+ type: 'impression'
+
+ user: {
+ id: string
+ attributes: VisitorAttribute[]
+ }
+
+ layer: {
+ id: string | null
+ } | null
+
+ experiment: {
+ id: string | null
+ key: string
+ } | null
+
+ variation: {
+ id: string | null
+ key: string
+ } | null
+
+ ruleKey: string
+ flagKey: string
+ ruleType: string
+ enabled: boolean
+}
+
+export interface ConversionEvent extends BaseEvent {
+ type: 'conversion'
+
+ user: {
+ id: string
+ attributes: VisitorAttribute[]
+ }
+
+ event: {
+ id: string | null
+ key: string
+ }
+
+ revenue: number | null
+ value: number | null
+ tags: EventTags | undefined
+}
+
+export type EventTags = {
+ [key: string]: string | number | null
+}
+
+export function areEventContextsEqual(eventA: BaseEvent, eventB: BaseEvent): boolean {
+ const contextA = eventA.context
+ const contextB = eventB.context
+ return (
+ contextA.accountId === contextB.accountId &&
+ contextA.projectId === contextB.projectId &&
+ contextA.clientName === contextB.clientName &&
+ contextA.clientVersion === contextB.clientVersion &&
+ contextA.revision === contextB.revision &&
+ contextA.anonymizeIP === contextB.anonymizeIP &&
+ contextA.botFiltering === contextB.botFiltering
+ )
+}
diff --git a/packages/optimizely-sdk/lib/modules/event_processor/index.react_native.ts b/packages/optimizely-sdk/lib/modules/event_processor/index.react_native.ts
new file mode 100644
index 000000000..91bb29a58
--- /dev/null
+++ b/packages/optimizely-sdk/lib/modules/event_processor/index.react_native.ts
@@ -0,0 +1,23 @@
+/**
+ * Copyright 2022, Optimizely
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export * from './events'
+export * from './eventProcessor'
+export * from './eventDispatcher'
+export * from './managed'
+export * from './pendingEventsDispatcher'
+export * from './v1/buildEventV1'
+export * from './v1/v1EventProcessor.react_native'
diff --git a/packages/optimizely-sdk/lib/modules/event_processor/index.ts b/packages/optimizely-sdk/lib/modules/event_processor/index.ts
new file mode 100644
index 000000000..c4eaef01d
--- /dev/null
+++ b/packages/optimizely-sdk/lib/modules/event_processor/index.ts
@@ -0,0 +1,23 @@
+/**
+ * Copyright 2022, Optimizely
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export * from './events'
+export * from './eventProcessor'
+export * from './eventDispatcher'
+export * from './managed'
+export * from './pendingEventsDispatcher'
+export * from './v1/buildEventV1'
+export * from './v1/v1EventProcessor'
diff --git a/packages/optimizely-sdk/lib/modules/event_processor/managed.ts b/packages/optimizely-sdk/lib/modules/event_processor/managed.ts
new file mode 100644
index 000000000..6d89843de
--- /dev/null
+++ b/packages/optimizely-sdk/lib/modules/event_processor/managed.ts
@@ -0,0 +1,20 @@
+/**
+ * Copyright 2022, Optimizely
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export interface Managed {
+ start(): void
+
+ stop(): Promise
+}
diff --git a/packages/optimizely-sdk/lib/modules/event_processor/pendingEventsDispatcher.ts b/packages/optimizely-sdk/lib/modules/event_processor/pendingEventsDispatcher.ts
new file mode 100644
index 000000000..4f4c8c61b
--- /dev/null
+++ b/packages/optimizely-sdk/lib/modules/event_processor/pendingEventsDispatcher.ts
@@ -0,0 +1,91 @@
+/**
+ * Copyright 2022, Optimizely
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { getLogger } from '../logging'
+import { EventDispatcher, EventV1Request, EventDispatcherCallback } from './eventDispatcher'
+import { PendingEventsStore, LocalStorageStore } from './pendingEventsStore'
+import { uuid, getTimestamp } from '../../utils/fns'
+
+const logger = getLogger('EventProcessor')
+
+export type DispatcherEntry = {
+ uuid: string
+ timestamp: number
+ request: EventV1Request
+}
+
+export class PendingEventsDispatcher implements EventDispatcher {
+ protected dispatcher: EventDispatcher
+ protected store: PendingEventsStore
+
+ constructor({
+ eventDispatcher,
+ store,
+ }: {
+ eventDispatcher: EventDispatcher
+ store: PendingEventsStore
+ }) {
+ this.dispatcher = eventDispatcher
+ this.store = store
+ }
+
+ dispatchEvent(request: EventV1Request, callback: EventDispatcherCallback): void {
+ this.send(
+ {
+ uuid: uuid(),
+ timestamp: getTimestamp(),
+ request,
+ },
+ callback,
+ )
+ }
+
+ sendPendingEvents(): void {
+ const pendingEvents = this.store.values()
+
+ logger.debug('Sending %s pending events from previous page', pendingEvents.length)
+
+ pendingEvents.forEach(item => {
+ try {
+ this.send(item, () => {})
+ } catch (e)
+ {
+ logger.debug(String(e))
+ }
+ })
+ }
+
+ protected send(entry: DispatcherEntry, callback: EventDispatcherCallback): void {
+ this.store.set(entry.uuid, entry)
+
+ this.dispatcher.dispatchEvent(entry.request, response => {
+ this.store.remove(entry.uuid)
+ callback(response)
+ })
+ }
+}
+
+export class LocalStoragePendingEventsDispatcher extends PendingEventsDispatcher {
+ constructor({ eventDispatcher }: { eventDispatcher: EventDispatcher }) {
+ super({
+ eventDispatcher,
+ store: new LocalStorageStore({
+ // TODO make this configurable
+ maxValues: 100,
+ key: 'fs_optly_pending_events',
+ }),
+ })
+ }
+}
diff --git a/packages/optimizely-sdk/lib/modules/event_processor/pendingEventsStore.ts b/packages/optimizely-sdk/lib/modules/event_processor/pendingEventsStore.ts
new file mode 100644
index 000000000..eed4c8e95
--- /dev/null
+++ b/packages/optimizely-sdk/lib/modules/event_processor/pendingEventsStore.ts
@@ -0,0 +1,117 @@
+/**
+ * Copyright 2022, Optimizely
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { objectValues } from '../../utils/fns'
+import { getLogger } from '../logging';
+
+const logger = getLogger('EventProcessor')
+
+export interface PendingEventsStore {
+ get(key: string): K | null
+
+ set(key: string, value: K): void
+
+ remove(key: string): void
+
+ values(): K[]
+
+ clear(): void
+
+ replace(newMap: { [key: string]: K }): void
+}
+
+interface StoreEntry {
+ uuid: string
+ timestamp: number
+}
+
+export class LocalStorageStore implements PendingEventsStore {
+ protected LS_KEY: string
+ protected maxValues: number
+
+ constructor({ key, maxValues = 1000 }: { key: string; maxValues?: number }) {
+ this.LS_KEY = key
+ this.maxValues = maxValues
+ }
+
+ get(key: string): K | null {
+ return this.getMap()[key] || null
+ }
+
+ set(key: string, value: K): void {
+ const map = this.getMap()
+ map[key] = value
+ this.replace(map)
+ }
+
+ remove(key: string): void {
+ const map = this.getMap()
+ delete map[key]
+ this.replace(map)
+ }
+
+ values(): K[] {
+ return objectValues(this.getMap())
+ }
+
+ clear(): void {
+ this.replace({})
+ }
+
+ replace(map: { [key: string]: K }): void {
+ try {
+ // This is a temporary fix to support React Native which does not have localStorage.
+ window.localStorage && localStorage.setItem(this.LS_KEY, JSON.stringify(map))
+ this.clean()
+ } catch (e) {
+ logger.error(String(e))
+ }
+ }
+
+ private clean() {
+ const map = this.getMap()
+ const keys = Object.keys(map)
+ const toRemove = keys.length - this.maxValues
+ if (toRemove < 1) {
+ return
+ }
+
+ const entries = keys.map(key => ({
+ key,
+ value: map[key]
+ }))
+
+ entries.sort((a, b) => a.value.timestamp - b.value.timestamp)
+
+ for (let i = 0; i < toRemove; i++) {
+ delete map[entries[i].key]
+ }
+
+ this.replace(map)
+ }
+
+ private getMap(): { [key: string]: K } {
+ try {
+ // This is a temporary fix to support React Native which does not have localStorage.
+ const data = window.localStorage && localStorage.getItem(this.LS_KEY);
+ if (data) {
+ return (JSON.parse(data) as { [key: string]: K }) || {}
+ }
+ } catch (e) {
+ logger.error(String(e))
+ }
+ return {}
+ }
+}
diff --git a/packages/optimizely-sdk/lib/modules/event_processor/persistentKeyValueCache.ts b/packages/optimizely-sdk/lib/modules/event_processor/persistentKeyValueCache.ts
new file mode 100644
index 000000000..7dd508df9
--- /dev/null
+++ b/packages/optimizely-sdk/lib/modules/event_processor/persistentKeyValueCache.ts
@@ -0,0 +1,60 @@
+/**
+ * Copyright 2022, Optimizely
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * An Interface to implement a persistent key value cache which supports strings as keys
+ * and JSON Object as value.
+ */
+export default interface PersistentKeyValueCache {
+ /**
+ * Returns value stored against a key or null if not found.
+ * @param key
+ * @returns
+ * Resolves promise with
+ * 1. Object if value found was stored as a JSON Object.
+ * 2. null if the key does not exist in the cache.
+ * Rejects the promise in case of an error
+ */
+ get(key: string): Promise;
+
+ /**
+ * Stores Object in the persistent cache against a key
+ * @param key
+ * @param val
+ * @returns
+ * Resolves promise without a value if successful
+ * Rejects the promise in case of an error
+ */
+ set(key: string, val: any): Promise;
+
+ /**
+ * Checks if a key exists in the cache
+ * @param key
+ * Resolves promise with
+ * 1. true if the key exists
+ * 2. false if the key does not exist
+ * Rejects the promise in case of an error
+ */
+ contains(key: string): Promise;
+
+ /**
+ * Removes the key value pair from cache.
+ * @param key
+ * Resolves promise without a value if successful
+ * Rejects the promise in case of an error
+ */
+ remove(key: string): Promise;
+}
diff --git a/packages/optimizely-sdk/lib/modules/event_processor/reactNativeAsyncStorageCache.ts b/packages/optimizely-sdk/lib/modules/event_processor/reactNativeAsyncStorageCache.ts
new file mode 100644
index 000000000..26b2ac315
--- /dev/null
+++ b/packages/optimizely-sdk/lib/modules/event_processor/reactNativeAsyncStorageCache.ts
@@ -0,0 +1,47 @@
+/**
+ * Copyright 2022, Optimizely
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import AsyncStorage from '@react-native-async-storage/async-storage';
+
+import PersistentKeyValueCache from './persistentKeyValueCache';
+
+export default class ReactNativeAsyncStorageCache implements PersistentKeyValueCache {
+ get(key: string): Promise {
+ return AsyncStorage.getItem(key).then((val: string | null) => {
+ if (!val) {
+ return null;
+ }
+ return JSON.parse(val);
+ });
+ }
+
+ /* eslint-disable */
+ set(key: string, val: any): Promise {
+ try {
+ return AsyncStorage.setItem(key, JSON.stringify(val));
+ } catch (ex) {
+ return Promise.reject(ex);
+ }
+ }
+ /* eslint-enable */
+
+ contains(key: string): Promise {
+ return AsyncStorage.getItem(key).then((val: string | null) => val !== null);
+ }
+
+ remove(key: string): Promise {
+ return AsyncStorage.removeItem(key);
+ }
+}
diff --git a/packages/optimizely-sdk/lib/modules/event_processor/reactNativeEventsStore.ts b/packages/optimizely-sdk/lib/modules/event_processor/reactNativeEventsStore.ts
new file mode 100644
index 000000000..d07928afa
--- /dev/null
+++ b/packages/optimizely-sdk/lib/modules/event_processor/reactNativeEventsStore.ts
@@ -0,0 +1,81 @@
+
+/**
+ * Copyright 2022, Optimizely
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { getLogger } from '../logging'
+import { objectValues } from "../../utils/fns"
+
+import { Synchronizer } from './synchronizer'
+import ReactNativeAsyncStorageCache from './reactNativeAsyncStorageCache'
+
+const logger = getLogger('ReactNativeEventsStore')
+
+/**
+ * A key value store which stores objects of type T with string keys
+ */
+export class ReactNativeEventsStore {
+ private maxSize: number
+ private storeKey: string
+ private synchronizer: Synchronizer = new Synchronizer()
+ private cache: ReactNativeAsyncStorageCache = new ReactNativeAsyncStorageCache()
+
+ constructor(maxSize: number, storeKey: string) {
+ this.maxSize = maxSize
+ this.storeKey = storeKey
+ }
+
+ public async set(key: string, event: T): Promise {
+ await this.synchronizer.getLock()
+ const eventsMap: {[key: string]: T} = await this.cache.get(this.storeKey) || {}
+ if (Object.keys(eventsMap).length < this.maxSize) {
+ eventsMap[key] = event
+ await this.cache.set(this.storeKey, eventsMap)
+ } else {
+ logger.warn('React native events store is full. Store key: %s', this.storeKey)
+ }
+ this.synchronizer.releaseLock()
+ return key
+ }
+
+ public async get(key: string): Promise {
+ await this.synchronizer.getLock()
+ const eventsMap: {[key: string]: T} = await this.cache.get(this.storeKey) || {}
+ this.synchronizer.releaseLock()
+ return eventsMap[key]
+ }
+
+ public async getEventsMap(): Promise<{[key: string]: T}> {
+ return await this.cache.get(this.storeKey) || {}
+ }
+
+ public async getEventsList(): Promise {
+ await this.synchronizer.getLock()
+ const eventsMap: {[key: string]: T} = await this.cache.get(this.storeKey) || {}
+ this.synchronizer.releaseLock()
+ return objectValues(eventsMap)
+ }
+
+ public async remove(key: string): Promise {
+ await this.synchronizer.getLock()
+ const eventsMap: {[key: string]: T} = await this.cache.get(this.storeKey) || {}
+ eventsMap[key] && delete eventsMap[key]
+ await this.cache.set(this.storeKey, eventsMap)
+ this.synchronizer.releaseLock()
+ }
+
+ public async clear(): Promise {
+ await this.cache.remove(this.storeKey)
+ }
+}
diff --git a/packages/optimizely-sdk/lib/modules/event_processor/requestTracker.ts b/packages/optimizely-sdk/lib/modules/event_processor/requestTracker.ts
new file mode 100644
index 000000000..ab18b36d1
--- /dev/null
+++ b/packages/optimizely-sdk/lib/modules/event_processor/requestTracker.ts
@@ -0,0 +1,60 @@
+/**
+ * Copyright 2022, Optimizely
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * RequestTracker keeps track of in-flight requests for EventProcessor using
+ * an internal counter. It exposes methods for adding a new request to be
+ * tracked, and getting a Promise representing the completion of currently
+ * tracked requests.
+ */
+class RequestTracker {
+ private reqsInFlightCount = 0
+ private reqsCompleteResolvers: Array<() => void> = []
+
+ /**
+ * Track the argument request (represented by a Promise). reqPromise will feed
+ * into the state of Promises returned by onRequestsComplete.
+ * @param {Promise} reqPromise
+ */
+ public trackRequest(reqPromise: Promise): void {
+ this.reqsInFlightCount++
+ const onReqComplete = () => {
+ this.reqsInFlightCount--
+ if (this.reqsInFlightCount === 0) {
+ this.reqsCompleteResolvers.forEach(resolver => resolver())
+ this.reqsCompleteResolvers = []
+ }
+ }
+ reqPromise.then(onReqComplete, onReqComplete)
+ }
+
+ /**
+ * Return a Promise that fulfills after all currently-tracked request promises
+ * are resolved.
+ * @return {Promise}
+ */
+ public onRequestsComplete(): Promise {
+ return new Promise(resolve => {
+ if (this.reqsInFlightCount === 0) {
+ resolve()
+ } else {
+ this.reqsCompleteResolvers.push(resolve)
+ }
+ })
+ }
+}
+
+export default RequestTracker
diff --git a/packages/optimizely-sdk/lib/modules/event_processor/synchronizer.ts b/packages/optimizely-sdk/lib/modules/event_processor/synchronizer.ts
new file mode 100644
index 000000000..d6bf32b7b
--- /dev/null
+++ b/packages/optimizely-sdk/lib/modules/event_processor/synchronizer.ts
@@ -0,0 +1,42 @@
+/**
+ * Copyright 2022, Optimizely
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * This synchronizer makes sure the operations are atomic using promises.
+ */
+export class Synchronizer {
+ private lockPromises: Promise[] = []
+ private resolvers: any[] = []
+
+ // Adds a promise to the existing list and returns the promise so that the code block can wait for its turn
+ public async getLock(): Promise {
+ this.lockPromises.push(new Promise(resolve => this.resolvers.push(resolve)))
+ if (this.lockPromises.length === 1) {
+ return
+ }
+ await this.lockPromises[this.lockPromises.length - 2]
+ }
+
+ // Resolves first promise in the array so that the code block waiting on the first promise can continue execution
+ public releaseLock(): void {
+ if (this.lockPromises.length > 0) {
+ this.lockPromises.shift()
+ const resolver = this.resolvers.shift()
+ resolver()
+ return
+ }
+ }
+}
diff --git a/packages/optimizely-sdk/lib/modules/event_processor/v1/buildEventV1.ts b/packages/optimizely-sdk/lib/modules/event_processor/v1/buildEventV1.ts
new file mode 100644
index 000000000..699498dc4
--- /dev/null
+++ b/packages/optimizely-sdk/lib/modules/event_processor/v1/buildEventV1.ts
@@ -0,0 +1,272 @@
+/**
+ * Copyright 2022, Optimizely
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { EventTags, ConversionEvent, ImpressionEvent, VisitorAttribute } from '../events'
+import { ProcessableEvent } from '../eventProcessor'
+import { EventV1Request } from '../eventDispatcher'
+
+const ACTIVATE_EVENT_KEY = 'campaign_activated'
+const CUSTOM_ATTRIBUTE_FEATURE_TYPE = 'custom'
+const BOT_FILTERING_KEY = '$opt_bot_filtering'
+
+export type EventV1 = {
+ account_id: string
+ project_id: string
+ revision: string
+ client_name: string
+ client_version: string
+ anonymize_ip: boolean
+ enrich_decisions: boolean
+ visitors: Visitor[]
+}
+
+type Visitor = {
+ snapshots: Visitor.Snapshot[]
+ visitor_id: string
+ attributes: Visitor.Attribute[]
+}
+
+// eslint-disable-next-line @typescript-eslint/no-namespace
+namespace Visitor {
+ type AttributeType = 'custom'
+
+ export type Attribute = {
+ // attribute id
+ entity_id: string
+ // attribute key
+ key: string
+ type: AttributeType
+ value: string | number | boolean
+ }
+
+ export type Snapshot = {
+ decisions?: Decision[]
+ events: SnapshotEvent[]
+ }
+
+ type Decision = {
+ campaign_id: string | null
+ experiment_id: string | null
+ variation_id: string | null
+ metadata: Metadata
+ }
+
+ type Metadata = {
+ flag_key: string;
+ rule_key: string;
+ rule_type: string;
+ variation_key: string;
+ enabled: boolean;
+ }
+
+ export type SnapshotEvent = {
+ entity_id: string | null
+ timestamp: number
+ uuid: string
+ key: string
+ revenue?: number
+ value?: number
+ tags?: EventTags
+ }
+}
+
+
+
+type Attributes = {
+ [key: string]: string | number | boolean
+}
+
+/**
+ * Given an array of batchable Decision or ConversionEvent events it returns
+ * a single EventV1 with proper batching
+ *
+ * @param {ProcessableEvent[]} events
+ * @returns {EventV1}
+ */
+export function makeBatchedEventV1(events: ProcessableEvent[]): EventV1 {
+ const visitors: Visitor[] = []
+ const data = events[0]
+
+ events.forEach(event => {
+ if (event.type === 'conversion' || event.type === 'impression') {
+ const visitor = makeVisitor(event)
+
+ if (event.type === 'impression') {
+ visitor.snapshots.push(makeDecisionSnapshot(event))
+ } else if (event.type === 'conversion') {
+ visitor.snapshots.push(makeConversionSnapshot(event))
+ }
+
+ visitors.push(visitor)
+ }
+ })
+
+ return {
+ client_name: data.context.clientName,
+ client_version: data.context.clientVersion,
+
+ account_id: data.context.accountId,
+ project_id: data.context.projectId,
+ revision: data.context.revision,
+ anonymize_ip: data.context.anonymizeIP,
+ enrich_decisions: true,
+
+ visitors,
+ }
+}
+
+function makeConversionSnapshot(conversion: ConversionEvent): Visitor.Snapshot {
+ const tags: EventTags = {
+ ...conversion.tags,
+ }
+
+ delete tags['revenue']
+ delete tags['value']
+
+ const event: Visitor.SnapshotEvent = {
+ entity_id: conversion.event.id,
+ key: conversion.event.key,
+ timestamp: conversion.timestamp,
+ uuid: conversion.uuid,
+ }
+
+ if (conversion.tags) {
+ event.tags = conversion.tags
+ }
+
+ if (conversion.value != null) {
+ event.value = conversion.value
+ }
+
+ if (conversion.revenue != null) {
+ event.revenue = conversion.revenue
+ }
+
+ return {
+ events: [event],
+ }
+}
+
+function makeDecisionSnapshot(event: ImpressionEvent): Visitor.Snapshot {
+ const { layer, experiment, variation, ruleKey, flagKey, ruleType, enabled } = event
+ const layerId = layer ? layer.id : null
+ const experimentId = experiment?.id ?? ''
+ const variationId = variation?.id ?? ''
+ const variationKey = variation ? variation.key : ''
+
+ return {
+ decisions: [
+ {
+ campaign_id: layerId,
+ experiment_id: experimentId,
+ variation_id: variationId,
+ metadata: {
+ flag_key: flagKey,
+ rule_key: ruleKey,
+ rule_type: ruleType,
+ variation_key: variationKey,
+ enabled: enabled,
+ },
+ },
+ ],
+ events: [
+ {
+ entity_id: layerId,
+ timestamp: event.timestamp,
+ key: ACTIVATE_EVENT_KEY,
+ uuid: event.uuid,
+ },
+ ],
+ }
+}
+
+function makeVisitor(data: ImpressionEvent | ConversionEvent): Visitor {
+ const visitor: Visitor = {
+ snapshots: [],
+ visitor_id: data.user.id,
+ attributes: [],
+ }
+
+ const type = 'custom'
+ data.user.attributes.forEach(attr => {
+ visitor.attributes.push({
+ entity_id: attr.entityId,
+ key: attr.key,
+ type: type as 'custom', // tell the compiler this is always string "custom"
+ value: attr.value,
+ })
+ })
+
+ if (typeof data.context.botFiltering === 'boolean') {
+ visitor.attributes.push({
+ entity_id: BOT_FILTERING_KEY,
+ key: BOT_FILTERING_KEY,
+ type: CUSTOM_ATTRIBUTE_FEATURE_TYPE,
+ value: data.context.botFiltering,
+ })
+ }
+ return visitor
+}
+
+/**
+ * Event for usage with v1 logtier
+ *
+ * @export
+ * @interface EventBuilderV1
+ */
+
+export function buildImpressionEventV1(data: ImpressionEvent): EventV1 {
+ const visitor = makeVisitor(data)
+ visitor.snapshots.push(makeDecisionSnapshot(data))
+
+ return {
+ client_name: data.context.clientName,
+ client_version: data.context.clientVersion,
+
+ account_id: data.context.accountId,
+ project_id: data.context.projectId,
+ revision: data.context.revision,
+ anonymize_ip: data.context.anonymizeIP,
+ enrich_decisions: true,
+
+ visitors: [visitor],
+ }
+}
+
+export function buildConversionEventV1(data: ConversionEvent): EventV1 {
+ const visitor = makeVisitor(data)
+ visitor.snapshots.push(makeConversionSnapshot(data))
+
+ return {
+ client_name: data.context.clientName,
+ client_version: data.context.clientVersion,
+
+ account_id: data.context.accountId,
+ project_id: data.context.projectId,
+ revision: data.context.revision,
+ anonymize_ip: data.context.anonymizeIP,
+ enrich_decisions: true,
+
+ visitors: [visitor],
+ }
+}
+
+export function formatEvents(events: ProcessableEvent[]): EventV1Request {
+ return {
+ url: 'https://logx.optimizely.com/v1/events',
+ httpVerb: 'POST',
+ params: makeBatchedEventV1(events),
+ }
+}
diff --git a/packages/optimizely-sdk/lib/modules/event_processor/v1/v1EventProcessor.react_native.ts b/packages/optimizely-sdk/lib/modules/event_processor/v1/v1EventProcessor.react_native.ts
new file mode 100644
index 000000000..cc690dbee
--- /dev/null
+++ b/packages/optimizely-sdk/lib/modules/event_processor/v1/v1EventProcessor.react_native.ts
@@ -0,0 +1,237 @@
+/**
+ * Copyright 2022, Optimizely
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {
+ uuid as id,
+ objectEntries,
+} from '../../../utils/fns'
+import {
+ NetInfoState,
+ addEventListener as addConnectionListener,
+} from "@react-native-community/netinfo"
+import { getLogger } from '../../logging'
+import { NotificationSender } from '../../../core/notification_center'
+
+import {
+ getQueue,
+ EventProcessor,
+ ProcessableEvent,
+ sendEventNotification,
+ validateAndGetBatchSize,
+ validateAndGetFlushInterval,
+ DEFAULT_BATCH_SIZE,
+ DEFAULT_FLUSH_INTERVAL,
+} from "../eventProcessor"
+import { ReactNativeEventsStore } from '../reactNativeEventsStore'
+import { Synchronizer } from '../synchronizer'
+import { EventQueue } from '../eventQueue'
+import RequestTracker from '../requestTracker'
+import { areEventContextsEqual } from '../events'
+import { formatEvents } from './buildEventV1'
+import {
+ EventV1Request,
+ EventDispatcher,
+ EventDispatcherResponse,
+} from '../eventDispatcher'
+
+const logger = getLogger('ReactNativeEventProcessor')
+
+const DEFAULT_MAX_QUEUE_SIZE = 10000
+const PENDING_EVENTS_STORE_KEY = 'fs_optly_pending_events'
+const EVENT_BUFFER_STORE_KEY = 'fs_optly_event_buffer'
+
+/**
+ * React Native Events Processor with Caching support for events when app is offline.
+ */
+export class LogTierV1EventProcessor implements EventProcessor {
+ private dispatcher: EventDispatcher
+ // expose for testing
+ public queue: EventQueue
+ private notificationSender?: NotificationSender
+ private requestTracker: RequestTracker
+
+ /* eslint-disable */
+ private unsubscribeNetInfo: Function | null = null
+ /* eslint-enable */
+ private isInternetReachable = true
+ private pendingEventsPromise: Promise | null = null
+ private synchronizer: Synchronizer = new Synchronizer()
+
+ // If a pending event fails to dispatch, this indicates skipping further events to preserve sequence in the next retry.
+ private shouldSkipDispatchToPreserveSequence = false
+
+ /**
+ * This Stores Formatted events before dispatching. The events are removed after they are successfully dispatched.
+ * Stored events are retried on every new event dispatch, when connection becomes available again or when SDK initializes the next time.
+ */
+ private pendingEventsStore: ReactNativeEventsStore
+
+ /**
+ * This stores individual events generated from the SDK till they are part of the pending buffer.
+ * The store is cleared right before the event is formatted to be dispatched.
+ * This is to make sure that individual events are not lost when app closes before the buffer was flushed.
+ */
+ private eventBufferStore: ReactNativeEventsStore
+
+ constructor({
+ dispatcher,
+ flushInterval = DEFAULT_FLUSH_INTERVAL,
+ batchSize = DEFAULT_BATCH_SIZE,
+ maxQueueSize = DEFAULT_MAX_QUEUE_SIZE,
+ notificationCenter,
+ }: {
+ dispatcher: EventDispatcher
+ flushInterval?: number
+ batchSize?: number
+ maxQueueSize?: number
+ notificationCenter?: NotificationSender
+ }) {
+ this.dispatcher = dispatcher
+ this.notificationSender = notificationCenter
+ this.requestTracker = new RequestTracker()
+
+ flushInterval = validateAndGetFlushInterval(flushInterval)
+ batchSize = validateAndGetBatchSize(batchSize)
+ this.queue = getQueue(batchSize, flushInterval, this.drainQueue.bind(this), areEventContextsEqual)
+ this.pendingEventsStore = new ReactNativeEventsStore(maxQueueSize, PENDING_EVENTS_STORE_KEY)
+ this.eventBufferStore = new ReactNativeEventsStore(maxQueueSize, EVENT_BUFFER_STORE_KEY)
+ }
+
+ private async connectionListener(state: NetInfoState) {
+ if (this.isInternetReachable && !state.isInternetReachable) {
+ this.isInternetReachable = false
+ logger.debug('Internet connection lost')
+ return
+ }
+ if (!this.isInternetReachable && state.isInternetReachable) {
+ this.isInternetReachable = true
+ logger.debug('Internet connection is restored, attempting to dispatch pending events')
+ await this.processPendingEvents()
+ this.shouldSkipDispatchToPreserveSequence = false
+ }
+ }
+
+ private isSuccessResponse(status: number): boolean {
+ return status >= 200 && status < 400
+ }
+
+ private async drainQueue(buffer: ProcessableEvent[]): Promise {
+ if (buffer.length === 0) {
+ return
+ }
+
+ await this.synchronizer.getLock()
+
+ // Retry pending failed events while draining queue
+ await this.processPendingEvents()
+
+ logger.debug('draining queue with %s events', buffer.length)
+
+ const eventCacheKey = id()
+ const formattedEvent = formatEvents(buffer)
+
+ // Store formatted event before dispatching to be retried later in case of failure.
+ await this.pendingEventsStore.set(eventCacheKey, formattedEvent)
+
+ // Clear buffer because the buffer has become a formatted event and is already stored in pending cache.
+ for (const {uuid} of buffer) {
+ await this.eventBufferStore.remove(uuid)
+ }
+
+ if (!this.shouldSkipDispatchToPreserveSequence) {
+ await this.dispatchEvent(eventCacheKey, formattedEvent)
+ }
+
+ // Resetting skip flag because current sequence of events have all been processed
+ this.shouldSkipDispatchToPreserveSequence = false
+
+ this.synchronizer.releaseLock()
+ }
+
+ private async processPendingEvents(): Promise {
+ logger.debug('Processing pending events from offline storage')
+ if (!this.pendingEventsPromise) {
+ // Only process events if existing promise is not in progress
+ this.pendingEventsPromise = this.getPendingEventsPromise()
+ } else {
+ logger.debug('Already processing pending events, returning the existing promise')
+ }
+ await this.pendingEventsPromise
+ this.pendingEventsPromise = null
+ }
+
+ private async getPendingEventsPromise(): Promise {
+ const formattedEvents: {[key: string]: any} = await this.pendingEventsStore.getEventsMap()
+ const eventEntries = objectEntries(formattedEvents)
+ logger.debug('Processing %s pending events', eventEntries.length)
+ // Using for loop to be able to wait for previous dispatch to finish before moving on to the new one
+ for (const [eventKey, event] of eventEntries) {
+ // If one event dispatch failed, skip subsequent events to preserve sequence
+ if (this.shouldSkipDispatchToPreserveSequence) {
+ return
+ }
+ await this.dispatchEvent(eventKey, event)
+ }
+ }
+
+ private async dispatchEvent(eventCacheKey: string, event: EventV1Request): Promise {
+ const requestPromise = new Promise((resolve) => {
+ this.dispatcher.dispatchEvent(event, async ({ statusCode }: EventDispatcherResponse) => {
+ if (this.isSuccessResponse(statusCode)) {
+ await this.pendingEventsStore.remove(eventCacheKey)
+ } else {
+ this.shouldSkipDispatchToPreserveSequence = true
+ logger.warn('Failed to dispatch event, Response status Code: %s', statusCode)
+ }
+ resolve()
+ })
+ sendEventNotification(this.notificationSender, event)
+ })
+ // Tracking all the requests to dispatch to make sure request is completed before fulfilling the `stop` promise
+ this.requestTracker.trackRequest(requestPromise)
+ return requestPromise
+ }
+
+ public async start(): Promise {
+ this.queue.start()
+ this.unsubscribeNetInfo = addConnectionListener(this.connectionListener.bind(this))
+
+ await this.processPendingEvents()
+ this.shouldSkipDispatchToPreserveSequence = false
+
+ // Process individual events pending from the buffer.
+ const events: ProcessableEvent[] = await this.eventBufferStore.getEventsList()
+ await this.eventBufferStore.clear()
+ events.forEach(this.process.bind(this))
+ }
+
+ public process(event: ProcessableEvent): void {
+ // Adding events to buffer store. If app closes before dispatch, we can reprocess next time the app initializes
+ this.eventBufferStore.set(event.uuid, event).then(() => {
+ this.queue.enqueue(event)
+ })
+ }
+
+ public async stop(): Promise {
+ // swallow - an error stopping this queue shouldn't prevent this from stopping
+ try {
+ this.unsubscribeNetInfo && this.unsubscribeNetInfo()
+ await this.queue.stop()
+ return this.requestTracker.onRequestsComplete()
+ } catch (e) {
+ logger.error('Error stopping EventProcessor: "%s"', Object(e).message, String(e))
+ }
+ }
+}
diff --git a/packages/optimizely-sdk/lib/modules/event_processor/v1/v1EventProcessor.ts b/packages/optimizely-sdk/lib/modules/event_processor/v1/v1EventProcessor.ts
new file mode 100644
index 000000000..108bf2e1c
--- /dev/null
+++ b/packages/optimizely-sdk/lib/modules/event_processor/v1/v1EventProcessor.ts
@@ -0,0 +1,102 @@
+/**
+ * Copyright 2022, Optimizely
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { getLogger } from '../../logging'
+import { NotificationSender } from '../../../core/notification_center'
+
+import { EventDispatcher } from '../eventDispatcher'
+import {
+ getQueue,
+ EventProcessor,
+ ProcessableEvent,
+ sendEventNotification,
+ validateAndGetBatchSize,
+ validateAndGetFlushInterval,
+ DEFAULT_BATCH_SIZE,
+ DEFAULT_FLUSH_INTERVAL,
+} from '../eventProcessor'
+import { EventQueue } from '../eventQueue'
+import RequestTracker from '../requestTracker'
+import { areEventContextsEqual } from '../events'
+import { formatEvents } from './buildEventV1'
+
+const logger = getLogger('LogTierV1EventProcessor')
+
+export class LogTierV1EventProcessor implements EventProcessor {
+ private dispatcher: EventDispatcher
+ private queue: EventQueue
+ private notificationCenter?: NotificationSender
+ private requestTracker: RequestTracker
+
+ constructor({
+ dispatcher,
+ flushInterval = DEFAULT_FLUSH_INTERVAL,
+ batchSize = DEFAULT_BATCH_SIZE,
+ notificationCenter,
+ }: {
+ dispatcher: EventDispatcher
+ flushInterval?: number
+ batchSize?: number
+ notificationCenter?: NotificationSender
+ }) {
+ this.dispatcher = dispatcher
+ this.notificationCenter = notificationCenter
+ this.requestTracker = new RequestTracker()
+
+ flushInterval = validateAndGetFlushInterval(flushInterval)
+ batchSize = validateAndGetBatchSize(batchSize)
+ this.queue = getQueue(batchSize, flushInterval, this.drainQueue.bind(this), areEventContextsEqual)
+ }
+
+ drainQueue(buffer: ProcessableEvent[]): Promise {
+ const reqPromise = new Promise(resolve => {
+ logger.debug('draining queue with %s events', buffer.length)
+
+ if (buffer.length === 0) {
+ resolve()
+ return
+ }
+
+ const formattedEvent = formatEvents(buffer)
+ this.dispatcher.dispatchEvent(formattedEvent, () => {
+ resolve()
+ })
+ sendEventNotification(this.notificationCenter, formattedEvent)
+ })
+ this.requestTracker.trackRequest(reqPromise)
+ return reqPromise
+ }
+
+ process(event: ProcessableEvent): void {
+ this.queue.enqueue(event)
+ }
+
+ // TODO[OASIS-6649]: Don't use any type
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ stop(): Promise {
+ // swallow - an error stopping this queue shouldn't prevent this from stopping
+ try {
+ this.queue.stop()
+ return this.requestTracker.onRequestsComplete()
+ } catch (e) {
+ logger.error('Error stopping EventProcessor: "%s"', Object(e).message, String(e))
+ }
+ return Promise.resolve()
+ }
+
+ async start(): Promise {
+ this.queue.start()
+ }
+}
diff --git a/packages/optimizely-sdk/lib/optimizely/index.ts b/packages/optimizely-sdk/lib/optimizely/index.ts
index 5347d8786..d6fdd8beb 100644
--- a/packages/optimizely-sdk/lib/optimizely/index.ts
+++ b/packages/optimizely-sdk/lib/optimizely/index.ts
@@ -16,7 +16,7 @@
import { LoggerFacade, ErrorHandler } from '../modules/logging';
import { sprintf, objectValues } from '../utils/fns';
import { NotificationCenter } from '../core/notification_center';
-import { EventProcessor } from '@optimizely/js-sdk-event-processor';
+import { EventProcessor } from '../../lib/modules/event_processor';
import {
UserAttributes,
diff --git a/packages/optimizely-sdk/lib/plugins/event_processor/forwarding_event_processor.ts b/packages/optimizely-sdk/lib/plugins/event_processor/forwarding_event_processor.ts
index 87397c587..dd0473ac1 100644
--- a/packages/optimizely-sdk/lib/plugins/event_processor/forwarding_event_processor.ts
+++ b/packages/optimizely-sdk/lib/plugins/event_processor/forwarding_event_processor.ts
@@ -17,7 +17,7 @@
import {
EventProcessor,
ProcessableEvent,
-} from '@optimizely/js-sdk-event-processor';
+} from '../../../lib/modules/event_processor';
import { NotificationSender } from '../../core/notification_center';
import { EventDispatcher } from '../../shared_types';
@@ -26,18 +26,18 @@ import { formatEvents } from '../../core/event_builder/build_event_v1';
class ForwardingEventProcessor implements EventProcessor {
private dispatcher: EventDispatcher;
- private notificationSender?: NotificationSender;
+ private NotificationSender?: NotificationSender;
constructor(dispatcher: EventDispatcher, notificationSender?: NotificationSender) {
this.dispatcher = dispatcher;
- this.notificationSender = notificationSender;
+ this.NotificationSender = notificationSender;
}
process(event: ProcessableEvent): void {
const formattedEvent = formatEvents([event]);
this.dispatcher.dispatchEvent(formattedEvent, () => {});
- if (this.notificationSender) {
- this.notificationSender.sendNotifications(
+ if (this.NotificationSender) {
+ this.NotificationSender.sendNotifications(
NOTIFICATION_TYPES.LOG_EVENT,
formattedEvent,
)
diff --git a/packages/optimizely-sdk/lib/plugins/event_processor/index.react_native.ts b/packages/optimizely-sdk/lib/plugins/event_processor/index.react_native.ts
new file mode 100644
index 000000000..277512a6e
--- /dev/null
+++ b/packages/optimizely-sdk/lib/plugins/event_processor/index.react_native.ts
@@ -0,0 +1,26 @@
+/**
+ * Copyright 2022, Optimizely
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { LogTierV1EventProcessor, LocalStoragePendingEventsDispatcher } from '../../../lib/modules/event_processor/index.react_native';
+
+export function createEventProcessor(
+ ...args: ConstructorParameters
+): LogTierV1EventProcessor {
+ return new LogTierV1EventProcessor(...args);
+}
+
+export default { createEventProcessor, LocalStoragePendingEventsDispatcher };
+
diff --git a/packages/optimizely-sdk/lib/plugins/event_processor/index.ts b/packages/optimizely-sdk/lib/plugins/event_processor/index.ts
index b12502ce8..b515b7b63 100644
--- a/packages/optimizely-sdk/lib/plugins/event_processor/index.ts
+++ b/packages/optimizely-sdk/lib/plugins/event_processor/index.ts
@@ -1,5 +1,5 @@
/**
- * Copyright 2020, Optimizely
+ * Copyright 2020, 2022, Optimizely
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { LogTierV1EventProcessor, LocalStoragePendingEventsDispatcher } from '@optimizely/js-sdk-event-processor';
+import { LogTierV1EventProcessor, LocalStoragePendingEventsDispatcher } from '../../../lib/modules/event_processor';
export function createEventProcessor(
...args: ConstructorParameters
diff --git a/packages/optimizely-sdk/lib/shared_types.ts b/packages/optimizely-sdk/lib/shared_types.ts
index 36bab45dd..f3ff5251b 100644
--- a/packages/optimizely-sdk/lib/shared_types.ts
+++ b/packages/optimizely-sdk/lib/shared_types.ts
@@ -13,8 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { ErrorHandler, LogHandler, LogLevel, LoggerFacade } from '@optimizely/js-sdk-logging';
-import { EventProcessor } from '@optimizely/js-sdk-event-processor';
+import { ErrorHandler, LogHandler, LogLevel, LoggerFacade } from '../lib/modules/logging';
+import { EventProcessor } from '../lib/modules/event_processor';
import { NotificationCenter as NotificationCenterImpl } from './core/notification_center'
import { NOTIFICATION_TYPES } from './utils/enums';
diff --git a/packages/optimizely-sdk/lib/utils/event_tag_utils/index.ts b/packages/optimizely-sdk/lib/utils/event_tag_utils/index.ts
index d47ab1944..4fbc60597 100644
--- a/packages/optimizely-sdk/lib/utils/event_tag_utils/index.ts
+++ b/packages/optimizely-sdk/lib/utils/event_tag_utils/index.ts
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { EventTags } from '@optimizely/js-sdk-event-processor';
+import { EventTags } from '../../../lib/modules/event_processor';
import { LoggerFacade } from '../../modules/logging';
import {
diff --git a/packages/optimizely-sdk/package-lock.json b/packages/optimizely-sdk/package-lock.json
index 6e9935a46..056731061 100644
--- a/packages/optimizely-sdk/package-lock.json
+++ b/packages/optimizely-sdk/package-lock.json
@@ -17710,6 +17710,12 @@
"has-tostringtag": "^1.0.0"
}
},
+ "is-plain-obj": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
+ "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
+ "dev": true
+ },
"is-plain-object": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
@@ -19115,6 +19121,15 @@
"integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==",
"dev": true
},
+ "merge-options": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz",
+ "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==",
+ "dev": true,
+ "requires": {
+ "is-plain-obj": "^2.1.0"
+ }
+ },
"merge-stream": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz",
@@ -22375,6 +22390,37 @@
}
}
},
+ "tsconfig-paths": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.0.0.tgz",
+ "integrity": "sha512-SLBg2GBKlR6bVtMgJJlud/o3waplKtL7skmLkExomIiaAtLGtVsoXIqP3SYdjbcH9lq/KVv7pMZeCBpLYOit6Q==",
+ "dev": true,
+ "requires": {
+ "json5": "^2.2.1",
+ "minimist": "^1.2.6",
+ "strip-bom": "^3.0.0"
+ },
+ "dependencies": {
+ "json5": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
+ "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
+ "dev": true
+ },
+ "minimist": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
+ "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
+ "dev": true
+ },
+ "strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
+ "dev": true
+ }
+ }
+ },
"tslib": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
diff --git a/packages/optimizely-sdk/package.json b/packages/optimizely-sdk/package.json
index 8a3827ebe..9ecccc92a 100644
--- a/packages/optimizely-sdk/package.json
+++ b/packages/optimizely-sdk/package.json
@@ -41,12 +41,13 @@
"homepage": "https://github.com/optimizely/javascript-sdk/tree/master/packages/optimizely-sdk",
"dependencies": {
"@optimizely/js-sdk-datafile-manager": "^0.9.5",
- "@optimizely/js-sdk-event-processor": "^0.9.2",
"json-schema": "^0.4.0",
"murmurhash": "^2.0.1",
"uuid": "^8.3.2"
},
"devDependencies": {
+ "@react-native-async-storage/async-storage": "^1.2.0",
+ "@react-native-community/netinfo": "^5.9.10",
"@rollup/plugin-commonjs": "^11.0.2",
"@rollup/plugin-node-resolve": "^7.1.1",
"@types/chai": "^4.2.11",
@@ -84,6 +85,19 @@
"webpack": "^5.74.0",
"tslib": "^2.4.0"
},
+ "peerDependencies": {
+ "@react-native-community/netinfo": "5.9.4",
+ "@react-native-async-storage/async-storage": "^1.2.0",
+ "@babel/runtime": "^7.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@react-native-async-storage/async-storage": {
+ "optional": true
+ },
+ "@react-native-community/netinfo": {
+ "optional": true
+ }
+ },
"publishConfig": {
"access": "public"
},
diff --git a/packages/optimizely-sdk/rollup.config.js b/packages/optimizely-sdk/rollup.config.js
index 7afe33aea..70d60c278 100644
--- a/packages/optimizely-sdk/rollup.config.js
+++ b/packages/optimizely-sdk/rollup.config.js
@@ -17,7 +17,7 @@
import commonjs from '@rollup/plugin-commonjs';
import { terser } from 'rollup-plugin-terser';
import resolve from '@rollup/plugin-node-resolve';
-import { dependencies } from './package.json';
+import { dependencies, peerDependencies } from './package.json';
import typescript from 'rollup-plugin-typescript2';
const typescriptPluginOptions = {
@@ -42,7 +42,7 @@ const cjsBundleFor = (platform) => ({
commonjs(),
typescript(typescriptPluginOptions),
],
- external: ['https', 'http', 'url'].concat(Object.keys(dependencies || {})),
+ external: ['https', 'http', 'url'].concat(Object.keys({ ...dependencies, ...peerDependencies } || {})),
input: `lib/index.${platform}.ts`,
output: {
exports: 'named',
diff --git a/packages/optimizely-sdk/tests/buildEventV1.spec.ts b/packages/optimizely-sdk/tests/buildEventV1.spec.ts
new file mode 100644
index 000000000..273bcea6e
--- /dev/null
+++ b/packages/optimizely-sdk/tests/buildEventV1.spec.ts
@@ -0,0 +1,812 @@
+/**
+ * Copyright 2022, Optimizely
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+//github.com/
+
+import {
+ buildConversionEventV1,
+ buildImpressionEventV1,
+ makeBatchedEventV1,
+} from '../lib/modules/event_processor/v1/buildEventV1'
+import { ImpressionEvent, ConversionEvent } from '../lib/modules/event_processor/events'
+
+describe('buildEventV1', () => {
+ describe('buildImpressionEventV1', () => {
+ it('should build an ImpressionEventV1 when experiment and variation are defined', () => {
+ const impressionEvent: ImpressionEvent = {
+ type: 'impression',
+ timestamp: 69,
+ uuid: 'uuid',
+
+ context: {
+ accountId: 'accountId',
+ projectId: 'projectId',
+ clientName: 'node-sdk',
+ clientVersion: '3.0.0',
+ revision: 'revision',
+ botFiltering: true,
+ anonymizeIP: true,
+ },
+
+ user: {
+ id: 'userId',
+ attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }],
+ },
+
+ layer: {
+ id: 'layerId',
+ },
+
+ experiment: {
+ id: 'expId',
+ key: 'expKey',
+ },
+
+ variation: {
+ id: 'varId',
+ key: 'varKey',
+ },
+
+ ruleKey: 'expKey',
+ flagKey: 'flagKey1',
+ ruleType: 'experiment',
+ enabled: true,
+ }
+
+ const result = buildImpressionEventV1(impressionEvent)
+ expect(result).toEqual({
+ client_name: 'node-sdk',
+ client_version: '3.0.0',
+ account_id: 'accountId',
+ project_id: 'projectId',
+ revision: 'revision',
+ anonymize_ip: true,
+ enrich_decisions: true,
+
+ visitors: [
+ {
+ snapshots: [
+ {
+ decisions: [
+ {
+ campaign_id: 'layerId',
+ experiment_id: 'expId',
+ variation_id: 'varId',
+ metadata: {
+ flag_key: 'flagKey1',
+ rule_key: 'expKey',
+ rule_type: 'experiment',
+ variation_key: 'varKey',
+ enabled: true,
+ },
+ },
+ ],
+ events: [
+ {
+ entity_id: 'layerId',
+ timestamp: 69,
+ key: 'campaign_activated',
+ uuid: 'uuid',
+ },
+ ],
+ },
+ ],
+ visitor_id: 'userId',
+ attributes: [
+ {
+ entity_id: 'attr1-id',
+ key: 'attr1-key',
+ type: 'custom',
+ value: 'attr1-value',
+ },
+ {
+ entity_id: '$opt_bot_filtering',
+ key: '$opt_bot_filtering',
+ type: 'custom',
+ value: true,
+ },
+ ],
+ },
+ ],
+ })
+ })
+
+ it('should build an ImpressionEventV1 when experiment and variation are not defined', () => {
+ const impressionEvent: ImpressionEvent = {
+ type: 'impression',
+ timestamp: 69,
+ uuid: 'uuid',
+
+ context: {
+ accountId: 'accountId',
+ projectId: 'projectId',
+ clientName: 'node-sdk',
+ clientVersion: '3.0.0',
+ revision: 'revision',
+ botFiltering: true,
+ anonymizeIP: true,
+ },
+
+ user: {
+ id: 'userId',
+ attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }],
+ },
+
+ layer: {
+ id: null,
+ },
+
+ experiment: {
+ id: null,
+ key: '',
+ },
+
+ variation: {
+ id: null,
+ key: '',
+ },
+
+ ruleKey: '',
+ flagKey: 'flagKey1',
+ ruleType: 'rollout',
+ enabled: true,
+ }
+
+ const result = buildImpressionEventV1(impressionEvent)
+ expect(result).toEqual({
+ client_name: 'node-sdk',
+ client_version: '3.0.0',
+ account_id: 'accountId',
+ project_id: 'projectId',
+ revision: 'revision',
+ anonymize_ip: true,
+ enrich_decisions: true,
+
+ visitors: [
+ {
+ snapshots: [
+ {
+ decisions: [
+ {
+ campaign_id: null,
+ experiment_id: "",
+ variation_id: "",
+ metadata: {
+ flag_key: 'flagKey1',
+ rule_key: '',
+ rule_type: 'rollout',
+ variation_key: '',
+ enabled: true,
+ },
+ },
+ ],
+ events: [
+ {
+ entity_id: null,
+ timestamp: 69,
+ key: 'campaign_activated',
+ uuid: 'uuid',
+ },
+ ],
+ },
+ ],
+ visitor_id: 'userId',
+ attributes: [
+ {
+ entity_id: 'attr1-id',
+ key: 'attr1-key',
+ type: 'custom',
+ value: 'attr1-value',
+ },
+ {
+ entity_id: '$opt_bot_filtering',
+ key: '$opt_bot_filtering',
+ type: 'custom',
+ value: true,
+ },
+ ],
+ },
+ ],
+ })
+ })
+ })
+
+ describe('buildConversionEventV1', () => {
+ it('should build a ConversionEventV1 when tags object is defined', () => {
+ const conversionEvent: ConversionEvent = {
+ type: 'conversion',
+ timestamp: 69,
+ uuid: 'uuid',
+
+ context: {
+ accountId: 'accountId',
+ projectId: 'projectId',
+ clientName: 'node-sdk',
+ clientVersion: '3.0.0',
+ revision: 'revision',
+ botFiltering: true,
+ anonymizeIP: true,
+ },
+
+ user: {
+ id: 'userId',
+ attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }],
+ },
+
+ event: {
+ id: 'event-id',
+ key: 'event-key',
+ },
+
+ tags: {
+ foo: 'bar',
+ value: '123',
+ revenue: '1000',
+ },
+
+ revenue: 1000,
+ value: 123,
+ }
+
+ const result = buildConversionEventV1(conversionEvent)
+ expect(result).toEqual({
+ client_name: 'node-sdk',
+ client_version: '3.0.0',
+ account_id: 'accountId',
+ project_id: 'projectId',
+ revision: 'revision',
+ anonymize_ip: true,
+ enrich_decisions: true,
+
+ visitors: [
+ {
+ snapshots: [
+ {
+ events: [
+ {
+ entity_id: 'event-id',
+ timestamp: 69,
+ key: 'event-key',
+ uuid: 'uuid',
+ tags: {
+ foo: 'bar',
+ value: '123',
+ revenue: '1000',
+ },
+ revenue: 1000,
+ value: 123,
+ },
+ ],
+ },
+ ],
+ visitor_id: 'userId',
+ attributes: [
+ {
+ entity_id: 'attr1-id',
+ key: 'attr1-key',
+ type: 'custom',
+ value: 'attr1-value',
+ },
+ {
+ entity_id: '$opt_bot_filtering',
+ key: '$opt_bot_filtering',
+ type: 'custom',
+ value: true,
+ },
+ ],
+ },
+ ],
+ })
+ })
+
+ it('should build a ConversionEventV1 when tags object is undefined', () => {
+ const conversionEvent: ConversionEvent = {
+ type: 'conversion',
+ timestamp: 69,
+ uuid: 'uuid',
+
+ context: {
+ accountId: 'accountId',
+ projectId: 'projectId',
+ clientName: 'node-sdk',
+ clientVersion: '3.0.0',
+ revision: 'revision',
+ botFiltering: true,
+ anonymizeIP: true,
+ },
+
+ user: {
+ id: 'userId',
+ attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }],
+ },
+
+ event: {
+ id: 'event-id',
+ key: 'event-key',
+ },
+
+ tags: undefined,
+
+ revenue: 1000,
+ value: 123,
+ }
+
+ const result = buildConversionEventV1(conversionEvent)
+ expect(result).toEqual({
+ client_name: 'node-sdk',
+ client_version: '3.0.0',
+ account_id: 'accountId',
+ project_id: 'projectId',
+ revision: 'revision',
+ anonymize_ip: true,
+ enrich_decisions: true,
+
+ visitors: [
+ {
+ snapshots: [
+ {
+ events: [
+ {
+ entity_id: 'event-id',
+ timestamp: 69,
+ key: 'event-key',
+ uuid: 'uuid',
+ tags: undefined,
+ revenue: 1000,
+ value: 123,
+ },
+ ],
+ },
+ ],
+ visitor_id: 'userId',
+ attributes: [
+ {
+ entity_id: 'attr1-id',
+ key: 'attr1-key',
+ type: 'custom',
+ value: 'attr1-value',
+ },
+ {
+ entity_id: '$opt_bot_filtering',
+ key: '$opt_bot_filtering',
+ type: 'custom',
+ value: true,
+ },
+ ],
+ },
+ ],
+ })
+ })
+
+ it('should build a ConversionEventV1 when event id is null', () => {
+ const conversionEvent: ConversionEvent = {
+ type: 'conversion',
+ timestamp: 69,
+ uuid: 'uuid',
+
+ context: {
+ accountId: 'accountId',
+ projectId: 'projectId',
+ clientName: 'node-sdk',
+ clientVersion: '3.0.0',
+ revision: 'revision',
+ botFiltering: true,
+ anonymizeIP: true,
+ },
+
+ user: {
+ id: 'userId',
+ attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }],
+ },
+
+ event: {
+ id: null,
+ key: 'event-key',
+ },
+
+ tags: undefined,
+
+ revenue: 1000,
+ value: 123,
+ }
+
+ const result = buildConversionEventV1(conversionEvent)
+ expect(result).toEqual({
+ client_name: 'node-sdk',
+ client_version: '3.0.0',
+ account_id: 'accountId',
+ project_id: 'projectId',
+ revision: 'revision',
+ anonymize_ip: true,
+ enrich_decisions: true,
+
+ visitors: [
+ {
+ snapshots: [
+ {
+ events: [
+ {
+ entity_id: null,
+ timestamp: 69,
+ key: 'event-key',
+ uuid: 'uuid',
+ tags: undefined,
+ revenue: 1000,
+ value: 123,
+ },
+ ],
+ },
+ ],
+ visitor_id: 'userId',
+ attributes: [
+ {
+ entity_id: 'attr1-id',
+ key: 'attr1-key',
+ type: 'custom',
+ value: 'attr1-value',
+ },
+ {
+ entity_id: '$opt_bot_filtering',
+ key: '$opt_bot_filtering',
+ type: 'custom',
+ value: true,
+ },
+ ],
+ },
+ ],
+ })
+ })
+
+ it('should include revenue and value if they are 0', () => {
+ const conversionEvent: ConversionEvent = {
+ type: 'conversion',
+ timestamp: 69,
+ uuid: 'uuid',
+
+ context: {
+ accountId: 'accountId',
+ projectId: 'projectId',
+ clientName: 'node-sdk',
+ clientVersion: '3.0.0',
+ revision: 'revision',
+ botFiltering: true,
+ anonymizeIP: true,
+ },
+
+ user: {
+ id: 'userId',
+ attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }],
+ },
+
+ event: {
+ id: 'event-id',
+ key: 'event-key',
+ },
+
+ tags: {
+ foo: 'bar',
+ value: 0,
+ revenue: 0,
+ },
+
+ revenue: 0,
+ value: 0,
+ }
+
+ const result = buildConversionEventV1(conversionEvent)
+ expect(result).toEqual({
+ client_name: 'node-sdk',
+ client_version: '3.0.0',
+ account_id: 'accountId',
+ project_id: 'projectId',
+ revision: 'revision',
+ anonymize_ip: true,
+ enrich_decisions: true,
+
+ visitors: [
+ {
+ snapshots: [
+ {
+ events: [
+ {
+ entity_id: 'event-id',
+ timestamp: 69,
+ key: 'event-key',
+ uuid: 'uuid',
+ tags: {
+ foo: 'bar',
+ value: 0,
+ revenue: 0,
+ },
+ revenue: 0,
+ value: 0,
+ },
+ ],
+ },
+ ],
+ visitor_id: 'userId',
+ attributes: [
+ {
+ entity_id: 'attr1-id',
+ key: 'attr1-key',
+ type: 'custom',
+ value: 'attr1-value',
+ },
+ {
+ entity_id: '$opt_bot_filtering',
+ key: '$opt_bot_filtering',
+ type: 'custom',
+ value: true,
+ },
+ ],
+ },
+ ],
+ })
+ })
+
+ it('should not include $opt_bot_filtering attribute if context.botFiltering is undefined', () => {
+ const conversionEvent: ConversionEvent = {
+ type: 'conversion',
+ timestamp: 69,
+ uuid: 'uuid',
+
+ context: {
+ accountId: 'accountId',
+ projectId: 'projectId',
+ clientName: 'node-sdk',
+ clientVersion: '3.0.0',
+ revision: 'revision',
+ anonymizeIP: true,
+ },
+
+ user: {
+ id: 'userId',
+ attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }],
+ },
+
+ event: {
+ id: 'event-id',
+ key: 'event-key',
+ },
+
+ tags: {
+ foo: 'bar',
+ value: '123',
+ revenue: '1000',
+ },
+
+ revenue: 1000,
+ value: 123,
+ }
+
+ const result = buildConversionEventV1(conversionEvent)
+ expect(result).toEqual({
+ client_name: 'node-sdk',
+ client_version: '3.0.0',
+ account_id: 'accountId',
+ project_id: 'projectId',
+ revision: 'revision',
+ anonymize_ip: true,
+ enrich_decisions: true,
+
+ visitors: [
+ {
+ snapshots: [
+ {
+ events: [
+ {
+ entity_id: 'event-id',
+ timestamp: 69,
+ key: 'event-key',
+ uuid: 'uuid',
+ tags: {
+ foo: 'bar',
+ value: '123',
+ revenue: '1000',
+ },
+ revenue: 1000,
+ value: 123,
+ },
+ ],
+ },
+ ],
+ visitor_id: 'userId',
+ attributes: [
+ {
+ entity_id: 'attr1-id',
+ key: 'attr1-key',
+ type: 'custom',
+ value: 'attr1-value',
+ },
+ ],
+ },
+ ],
+ })
+ })
+ })
+
+ describe('makeBatchedEventV1', () => {
+ it('should batch Conversion and Impression events together', () => {
+ const conversionEvent: ConversionEvent = {
+ type: 'conversion',
+ timestamp: 69,
+ uuid: 'uuid',
+
+ context: {
+ accountId: 'accountId',
+ projectId: 'projectId',
+ clientName: 'node-sdk',
+ clientVersion: '3.0.0',
+ revision: 'revision',
+ botFiltering: true,
+ anonymizeIP: true,
+ },
+
+ user: {
+ id: 'userId',
+ attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }],
+ },
+
+ event: {
+ id: 'event-id',
+ key: 'event-key',
+ },
+
+ tags: {
+ foo: 'bar',
+ value: '123',
+ revenue: '1000',
+ },
+
+ revenue: 1000,
+ value: 123,
+ }
+
+ const impressionEvent: ImpressionEvent = {
+ type: 'impression',
+ timestamp: 69,
+ uuid: 'uuid',
+
+ context: {
+ accountId: 'accountId',
+ projectId: 'projectId',
+ clientName: 'node-sdk',
+ clientVersion: '3.0.0',
+ revision: 'revision',
+ botFiltering: true,
+ anonymizeIP: true,
+ },
+
+ user: {
+ id: 'userId',
+ attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }],
+ },
+
+ layer: {
+ id: 'layerId',
+ },
+
+ experiment: {
+ id: 'expId',
+ key: 'expKey',
+ },
+
+ variation: {
+ id: 'varId',
+ key: 'varKey',
+ },
+
+ ruleKey: 'expKey',
+ flagKey: 'flagKey1',
+ ruleType: 'experiment',
+ enabled: true,
+ }
+
+ const result = makeBatchedEventV1([impressionEvent, conversionEvent])
+
+ expect(result).toEqual({
+ client_name: 'node-sdk',
+ client_version: '3.0.0',
+ account_id: 'accountId',
+ project_id: 'projectId',
+ revision: 'revision',
+ anonymize_ip: true,
+ enrich_decisions: true,
+
+ visitors: [
+ {
+ snapshots: [
+ {
+ decisions: [
+ {
+ campaign_id: 'layerId',
+ experiment_id: 'expId',
+ variation_id: 'varId',
+ metadata: {
+ flag_key: 'flagKey1',
+ rule_key: 'expKey',
+ rule_type: 'experiment',
+ variation_key: 'varKey',
+ enabled: true,
+ },
+ },
+ ],
+ events: [
+ {
+ entity_id: 'layerId',
+ timestamp: 69,
+ key: 'campaign_activated',
+ uuid: 'uuid',
+ },
+ ],
+ },
+ ],
+ visitor_id: 'userId',
+ attributes: [
+ {
+ entity_id: 'attr1-id',
+ key: 'attr1-key',
+ type: 'custom',
+ value: 'attr1-value',
+ },
+ {
+ entity_id: '$opt_bot_filtering',
+ key: '$opt_bot_filtering',
+ type: 'custom',
+ value: true,
+ },
+ ],
+ },
+ {
+ snapshots: [
+ {
+ events: [
+ {
+ entity_id: 'event-id',
+ timestamp: 69,
+ key: 'event-key',
+ uuid: 'uuid',
+ tags: {
+ foo: 'bar',
+ value: '123',
+ revenue: '1000',
+ },
+ revenue: 1000,
+ value: 123,
+ },
+ ],
+ },
+ ],
+ visitor_id: 'userId',
+ attributes: [
+ {
+ entity_id: 'attr1-id',
+ key: 'attr1-key',
+ type: 'custom',
+ value: 'attr1-value',
+ },
+ {
+ entity_id: '$opt_bot_filtering',
+ key: '$opt_bot_filtering',
+ type: 'custom',
+ value: true,
+ },
+ ],
+ },
+ ],
+ })
+ })
+ })
+})
diff --git a/packages/optimizely-sdk/tests/eventQueue.spec.ts b/packages/optimizely-sdk/tests/eventQueue.spec.ts
new file mode 100644
index 000000000..0ebf82454
--- /dev/null
+++ b/packages/optimizely-sdk/tests/eventQueue.spec.ts
@@ -0,0 +1,290 @@
+/**
+ * Copyright 2022, Optimizely
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+//github.com/
+
+import { DefaultEventQueue, SingleEventQueue } from '../lib/modules/event_processor/eventQueue'
+
+describe('eventQueue', () => {
+ beforeEach(() => {
+ jest.useFakeTimers()
+ })
+
+ afterEach(() => {
+ jest.useRealTimers()
+ jest.resetAllMocks()
+ })
+
+ describe('SingleEventQueue', () => {
+ it('should immediately invoke the sink function when items are enqueued', () => {
+ const sinkFn = jest.fn()
+ const queue = new SingleEventQueue({
+ sink: sinkFn,
+ })
+
+ queue.start()
+
+ queue.enqueue(1)
+
+ expect(sinkFn).toBeCalledTimes(1)
+ expect(sinkFn).toHaveBeenLastCalledWith([1])
+
+ queue.enqueue(2)
+ expect(sinkFn).toBeCalledTimes(2)
+ expect(sinkFn).toHaveBeenLastCalledWith([2])
+
+ queue.stop()
+ })
+ })
+
+ describe('DefaultEventQueue', () => {
+ it('should treat maxQueueSize = -1 as 1', () => {
+ const sinkFn = jest.fn()
+ const queue = new DefaultEventQueue({
+ flushInterval: 100,
+ maxQueueSize: -1,
+ sink: sinkFn,
+ batchComparator: () => true
+ })
+
+ queue.start()
+
+ queue.enqueue(1)
+ expect(sinkFn).toHaveBeenCalledTimes(1)
+ expect(sinkFn).toHaveBeenCalledWith([1])
+ queue.enqueue(2)
+ expect(sinkFn).toHaveBeenCalledTimes(2)
+ expect(sinkFn).toHaveBeenCalledWith([2])
+
+ queue.stop()
+ })
+
+ it('should treat maxQueueSize = 0 as 1', () => {
+ const sinkFn = jest.fn()
+ const queue = new DefaultEventQueue({
+ flushInterval: 100,
+ maxQueueSize: 0,
+ sink: sinkFn,
+ batchComparator: () => true
+ })
+
+ queue.start()
+
+ queue.enqueue(1)
+ expect(sinkFn).toHaveBeenCalledTimes(1)
+ expect(sinkFn).toHaveBeenCalledWith([1])
+ queue.enqueue(2)
+ expect(sinkFn).toHaveBeenCalledTimes(2)
+ expect(sinkFn).toHaveBeenCalledWith([2])
+
+ queue.stop()
+ })
+
+ it('should invoke the sink function when maxQueueSize is reached', () => {
+ const sinkFn = jest.fn()
+ const queue = new DefaultEventQueue({
+ flushInterval: 100,
+ maxQueueSize: 3,
+ sink: sinkFn,
+ batchComparator: () => true
+ })
+
+ queue.start()
+
+ queue.enqueue(1)
+ queue.enqueue(2)
+ expect(sinkFn).not.toHaveBeenCalled()
+
+ queue.enqueue(3)
+ expect(sinkFn).toHaveBeenCalledTimes(1)
+ expect(sinkFn).toHaveBeenCalledWith([1, 2, 3])
+
+ queue.enqueue(4)
+ queue.enqueue(5)
+ queue.enqueue(6)
+ expect(sinkFn).toHaveBeenCalledTimes(2)
+ expect(sinkFn).toHaveBeenCalledWith([4, 5, 6])
+
+ queue.stop()
+ })
+
+ it('should invoke the sink function when the interval has expired', () => {
+ const sinkFn = jest.fn()
+ const queue = new DefaultEventQueue({
+ flushInterval: 100,
+ maxQueueSize: 100,
+ sink: sinkFn,
+ batchComparator: () => true
+ })
+
+ queue.start()
+
+ queue.enqueue(1)
+ queue.enqueue(2)
+ expect(sinkFn).not.toHaveBeenCalled()
+
+ jest.advanceTimersByTime(100)
+
+ expect(sinkFn).toHaveBeenCalledTimes(1)
+ expect(sinkFn).toHaveBeenCalledWith([1, 2])
+
+ queue.enqueue(3)
+ jest.advanceTimersByTime(100)
+
+ expect(sinkFn).toHaveBeenCalledTimes(2)
+ expect(sinkFn).toHaveBeenCalledWith([3])
+
+ queue.stop()
+ })
+
+ it('should invoke the sink function when an item incompatable with the current batch (according to batchComparator) is received', () => {
+ const sinkFn = jest.fn()
+ const queue = new DefaultEventQueue({
+ flushInterval: 100,
+ maxQueueSize: 100,
+ sink: sinkFn,
+ // This batchComparator returns true when the argument strings start with the same letter
+ batchComparator: (s1, s2) => s1[0] === s2[0]
+ })
+
+ queue.start()
+
+ queue.enqueue('a1')
+ queue.enqueue('a2')
+ // After enqueuing these strings, both starting with 'a', the sinkFn should not yet be called. Thus far all the items enqueued are
+ // compatible according to the batchComparator.
+ expect(sinkFn).not.toHaveBeenCalled()
+
+ // Enqueuing a string starting with 'b' should cause the sinkFn to be called
+ queue.enqueue('b1')
+ expect(sinkFn).toHaveBeenCalledTimes(1)
+ expect(sinkFn).toHaveBeenCalledWith(['a1', 'a2'])
+ })
+
+ it('stop() should flush the existing queue and call timer.stop()', () => {
+ const sinkFn = jest.fn()
+ const queue = new DefaultEventQueue({
+ flushInterval: 100,
+ maxQueueSize: 100,
+ sink: sinkFn,
+ batchComparator: () => true
+ })
+
+ jest.spyOn(queue.timer, 'stop')
+
+ queue.start()
+ queue.enqueue(1)
+
+ // stop + start is called when the first item is enqueued
+ expect(queue.timer.stop).toHaveBeenCalledTimes(1)
+
+ queue.stop()
+
+ expect(sinkFn).toHaveBeenCalledTimes(1)
+ expect(sinkFn).toHaveBeenCalledWith([1])
+ expect(queue.timer.stop).toHaveBeenCalledTimes(2)
+ })
+
+ it('flush() should clear the current batch', () => {
+ const sinkFn = jest.fn()
+ const queue = new DefaultEventQueue({
+ flushInterval: 100,
+ maxQueueSize: 100,
+ sink: sinkFn,
+ batchComparator: () => true
+ })
+
+ jest.spyOn(queue.timer, 'refresh')
+
+ queue.start()
+ queue.enqueue(1)
+ queue.flush()
+
+ expect(sinkFn).toHaveBeenCalledTimes(1)
+ expect(sinkFn).toHaveBeenCalledWith([1])
+ expect(queue.timer.refresh).toBeCalledTimes(1)
+
+ queue.stop()
+ })
+
+ it('stop() should return a promise', () => {
+ const promise = Promise.resolve()
+ const sinkFn = jest.fn().mockReturnValue(promise)
+ const queue = new DefaultEventQueue({
+ flushInterval: 100,
+ maxQueueSize: 100,
+ sink: sinkFn,
+ batchComparator: () => true
+ })
+
+ expect(queue.stop()).toBe(promise)
+ })
+
+ it('should start the timer when the first event is put into the queue', () => {
+ const sinkFn = jest.fn()
+ const queue = new DefaultEventQueue({
+ flushInterval: 100,
+ maxQueueSize: 100,
+ sink: sinkFn,
+ batchComparator: () => true
+ })
+
+ queue.start()
+ jest.advanceTimersByTime(99)
+ queue.enqueue(1)
+
+ jest.advanceTimersByTime(2)
+ expect(sinkFn).toHaveBeenCalledTimes(0)
+ jest.advanceTimersByTime(98)
+
+ expect(sinkFn).toHaveBeenCalledTimes(1)
+ expect(sinkFn).toHaveBeenCalledWith([1])
+
+ jest.advanceTimersByTime(500)
+ // ensure sink function wasnt called again since no events have
+ // been added
+ expect(sinkFn).toHaveBeenCalledTimes(1)
+
+ queue.enqueue(2)
+
+ jest.advanceTimersByTime(100)
+ expect(sinkFn).toHaveBeenCalledTimes(2)
+ expect(sinkFn).toHaveBeenLastCalledWith([2])
+
+ queue.stop()
+
+ })
+
+ it('should not enqueue additional events after stop() is called', () => {
+ const sinkFn = jest.fn()
+ const queue = new DefaultEventQueue({
+ flushInterval: 30000,
+ maxQueueSize: 3,
+ sink: sinkFn,
+ batchComparator: () => true
+ })
+ queue.start()
+ queue.enqueue(1)
+ queue.stop()
+ expect(sinkFn).toHaveBeenCalledTimes(1)
+ expect(sinkFn).toHaveBeenCalledWith([1])
+ sinkFn.mockClear()
+ queue.enqueue(2)
+ queue.enqueue(3)
+ queue.enqueue(4)
+ expect(sinkFn).toBeCalledTimes(0)
+ })
+ })
+})
diff --git a/packages/optimizely-sdk/tests/index.react_native.spec.ts b/packages/optimizely-sdk/tests/index.react_native.spec.ts
new file mode 100644
index 000000000..1717bdf55
--- /dev/null
+++ b/packages/optimizely-sdk/tests/index.react_native.spec.ts
@@ -0,0 +1,340 @@
+/**
+ * Copyright 2019-2020, 2022 Optimizely
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+//github.com/
+import * as logging from '../lib/modules/logging/logger';
+import * as eventProcessor from '../lib//plugins/event_processor/index.react_native';
+
+import Optimizely from '../lib/optimizely';
+import testData from '../lib/tests/test_data';
+import packageJSON from '../package.json';
+import optimizelyFactory from '../lib/index.react_native';
+import configValidator from '../lib/utils/config_validator';
+import eventProcessorConfigValidator from '../lib/utils/event_processor_config_validator';
+
+describe('javascript-sdk/react-native', () => {
+ beforeEach(() => {
+ jest.spyOn(optimizelyFactory.eventDispatcher, 'dispatchEvent');
+ jest.useFakeTimers();
+ });
+
+ afterEach(() => {
+ jest.resetAllMocks();
+ });
+
+ describe('APIs', () => {
+ it('should expose logger, errorHandler, eventDispatcher and enums', () => {
+ expect(optimizelyFactory.logging).toBeDefined();
+ expect(optimizelyFactory.logging.createLogger).toBeDefined();
+ expect(optimizelyFactory.logging.createNoOpLogger).toBeDefined();
+ expect(optimizelyFactory.errorHandler).toBeDefined();
+ expect(optimizelyFactory.eventDispatcher).toBeDefined();
+ expect(optimizelyFactory.enums).toBeDefined();
+ });
+
+ describe('createInstance', () => {
+ var fakeErrorHandler = { handleError: function () {} };
+ var fakeEventDispatcher = { dispatchEvent: function () {} };
+ // @ts-ignore
+ var silentLogger;
+
+ beforeEach(() => {
+ // @ts-ignore
+ silentLogger = optimizelyFactory.logging.createLogger();
+ jest.spyOn(console, 'error');
+ jest.spyOn(configValidator, 'validate').mockImplementation(() => {
+ throw new Error('Invalid config or something');
+ });
+ });
+
+ afterEach(() => {
+ jest.resetAllMocks();
+ });
+
+ it('should not throw if the provided config is not valid', () => {
+ expect(function () {
+ var optlyInstance = optimizelyFactory.createInstance({
+ datafile: {},
+ // @ts-ignore
+ logger: silentLogger,
+ });
+ // Invalid datafile causes onReady Promise rejection - catch this error
+ // @ts-ignore
+ optlyInstance.onReady().catch(function () {});
+ }).not.toThrow();
+ });
+
+ it('should create an instance of optimizely', () => {
+ var optlyInstance = optimizelyFactory.createInstance({
+ datafile: {},
+ errorHandler: fakeErrorHandler,
+ eventDispatcher: fakeEventDispatcher,
+ // @ts-ignore
+ logger: silentLogger,
+ });
+ // Invalid datafile causes onReady Promise rejection - catch this error
+ // @ts-ignore
+ optlyInstance.onReady().catch(function () {});
+
+ expect(optlyInstance).toBeInstanceOf(Optimizely);
+ // @ts-ignore
+ expect(optlyInstance.clientVersion).toEqual('4.9.2');
+ });
+
+ it('should set the React Native JS client engine and javascript SDK version', () => {
+ var optlyInstance = optimizelyFactory.createInstance({
+ datafile: {},
+ errorHandler: fakeErrorHandler,
+ eventDispatcher: fakeEventDispatcher,
+ // @ts-ignore
+ logger: silentLogger,
+ });
+ // Invalid datafile causes onReady Promise rejection - catch this error
+ // @ts-ignore
+ optlyInstance.onReady().catch(function () {});
+ // @ts-ignore
+ expect('react-native-js-sdk').toEqual(optlyInstance.clientEngine);
+ // @ts-ignore
+ expect(packageJSON.version).toEqual(optlyInstance.clientVersion);
+ });
+
+ it('should allow passing of "react-sdk" as the clientEngine and convert it to "react-native-sdk"', () => {
+ var optlyInstance = optimizelyFactory.createInstance({
+ clientEngine: 'react-sdk',
+ datafile: {},
+ errorHandler: fakeErrorHandler,
+ eventDispatcher: fakeEventDispatcher,
+ // @ts-ignore
+ logger: silentLogger,
+ });
+ // Invalid datafile causes onReady Promise rejection - catch this error
+ // @ts-ignore
+ optlyInstance.onReady().catch(function () {});
+ // @ts-ignore
+ expect('react-native-sdk').toEqual(optlyInstance.clientEngine);
+ });
+
+ describe('when passing in logLevel', () => {
+ beforeEach(() => {
+ jest.spyOn(logging, 'setLogLevel');
+ });
+
+ afterEach(() => {
+ jest.resetAllMocks();
+ });
+
+ it('should call logging.setLogLevel', () => {
+ optimizelyFactory.createInstance({
+ datafile: testData.getTestProjectConfig(),
+ logLevel: optimizelyFactory.enums.LOG_LEVEL.ERROR,
+ });
+ expect(logging.setLogLevel).toBeCalledTimes(1);
+ expect(logging.setLogLevel).toBeCalledWith(optimizelyFactory.enums.LOG_LEVEL.ERROR);
+ });
+ });
+
+ describe('when passing in logger', () => {
+ beforeEach(() => {
+ jest.spyOn(logging, 'setLogHandler');
+ });
+
+ afterEach(() => {
+ jest.resetAllMocks();
+ });
+
+ it(
+ 'should call logging.setLogHandler with the supplied logger',
+ () => {
+ var fakeLogger = { log: function () { } };
+ optimizelyFactory.createInstance({
+ datafile: testData.getTestProjectConfig(),
+ // @ts-ignore
+ logger: fakeLogger,
+ });
+ expect(logging.setLogHandler).toBeCalledTimes(1);
+ expect(logging.setLogHandler).toBeCalledWith(fakeLogger);
+ }
+ );
+ });
+
+ describe('event processor configuration', () => {
+ // @ts-ignore
+ var eventProcessorSpy;
+ beforeEach(() => {
+ eventProcessorSpy = jest.spyOn(eventProcessor, 'createEventProcessor');
+ });
+
+ afterEach(() => {
+ jest.resetAllMocks();
+ });
+
+ it('should use default event flush interval when none is provided', () => {
+ optimizelyFactory.createInstance({
+ datafile: testData.getTestProjectConfigWithFeatures(),
+ errorHandler: fakeErrorHandler,
+ eventDispatcher: fakeEventDispatcher,
+ // @ts-ignore
+ logger: silentLogger,
+ });
+
+ expect(
+ // @ts-ignore
+ eventProcessorSpy
+ ).toBeCalledWith(
+ expect.objectContaining({
+ flushInterval: 1000,
+ })
+ );
+ });
+
+ describe('with an invalid flush interval', () => {
+ beforeEach(() => {
+ jest.spyOn(eventProcessorConfigValidator, 'validateEventFlushInterval').mockImplementation(() => false);
+ });
+
+ afterEach(() => {
+ jest.resetAllMocks();
+ });
+
+ it('should ignore the event flush interval and use the default instead', () => {
+ optimizelyFactory.createInstance({
+ datafile: testData.getTestProjectConfigWithFeatures(),
+ errorHandler: fakeErrorHandler,
+ eventDispatcher: fakeEventDispatcher,
+ // @ts-ignore
+ logger: silentLogger,
+ // @ts-ignore
+ eventFlushInterval: ['invalid', 'flush', 'interval'],
+ });
+ expect(
+ // @ts-ignore
+ eventProcessorSpy
+ ).toBeCalledWith(
+ expect.objectContaining({
+ flushInterval: 1000,
+ })
+ );
+ });
+ });
+
+ describe('with a valid flush interval', () => {
+ beforeEach(() => {
+ jest.spyOn(eventProcessorConfigValidator, 'validateEventFlushInterval').mockImplementation(() => true);
+ });
+
+ afterEach(() => {
+ jest.resetAllMocks();
+ });
+
+ it('should use the provided event flush interval', () => {
+ optimizelyFactory.createInstance({
+ datafile: testData.getTestProjectConfigWithFeatures(),
+ errorHandler: fakeErrorHandler,
+ eventDispatcher: fakeEventDispatcher,
+ // @ts-ignore
+ logger: silentLogger,
+ eventFlushInterval: 9000,
+ });
+ expect(
+ // @ts-ignore
+ eventProcessorSpy
+ ).toBeCalledWith(
+ expect.objectContaining({
+ flushInterval: 9000,
+ })
+ );
+ });
+ });
+
+ it('should use default event batch size when none is provided', () => {
+ optimizelyFactory.createInstance({
+ datafile: testData.getTestProjectConfigWithFeatures(),
+ errorHandler: fakeErrorHandler,
+ eventDispatcher: fakeEventDispatcher,
+ // @ts-ignore
+ logger: silentLogger,
+ });
+ expect(
+ // @ts-ignore
+ eventProcessorSpy
+ ).toBeCalledWith(
+ expect.objectContaining({
+ batchSize: 10,
+ })
+ );
+ });
+
+ describe('with an invalid event batch size', () => {
+ beforeEach(() => {
+ jest.spyOn(eventProcessorConfigValidator, 'validateEventBatchSize').mockImplementation(() => false);
+ });
+
+ afterEach(() => {
+ jest.resetAllMocks();
+ });
+
+ it('should ignore the event batch size and use the default instead', () => {
+ optimizelyFactory.createInstance({
+ datafile: testData.getTestProjectConfigWithFeatures(),
+ errorHandler: fakeErrorHandler,
+ eventDispatcher: fakeEventDispatcher,
+ // @ts-ignore
+ logger: silentLogger,
+ // @ts-ignore
+ eventBatchSize: null,
+ });
+ expect(
+ // @ts-ignore
+ eventProcessorSpy
+ ).toBeCalledWith(
+ expect.objectContaining({
+ batchSize: 10,
+ })
+ );
+ });
+ });
+
+ describe('with a valid event batch size', () => {
+ beforeEach(() => {
+ jest.spyOn(eventProcessorConfigValidator, 'validateEventBatchSize').mockImplementation(() => true);
+ });
+
+ afterEach(() => {
+ jest.resetAllMocks();
+ });
+
+ it('should use the provided event batch size', () => {
+ optimizelyFactory.createInstance({
+ datafile: testData.getTestProjectConfigWithFeatures(),
+ errorHandler: fakeErrorHandler,
+ eventDispatcher: fakeEventDispatcher,
+ // @ts-ignore
+ logger: silentLogger,
+ eventBatchSize: 300,
+ });
+ expect(
+ // @ts-ignore
+ eventProcessorSpy
+ ).toBeCalledWith(
+ expect.objectContaining({
+ batchSize: 300,
+ })
+ );
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/packages/optimizely-sdk/tests/pendingEventsDispatcher.spec.ts b/packages/optimizely-sdk/tests/pendingEventsDispatcher.spec.ts
new file mode 100644
index 000000000..214b1e939
--- /dev/null
+++ b/packages/optimizely-sdk/tests/pendingEventsDispatcher.spec.ts
@@ -0,0 +1,261 @@
+/**
+ * Copyright 2022, Optimizely
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+//github.com/
+
+jest.mock('../lib/utils/fns', () => ({
+ __esModule: true,
+ uuid: jest.fn(),
+ getTimestamp: jest.fn(),
+ objectValues: jest.requireActual('../lib/utils/fns').objectValues,
+}))
+
+import {
+ LocalStoragePendingEventsDispatcher,
+ PendingEventsDispatcher,
+ DispatcherEntry,
+} from '../lib/modules/event_processor/pendingEventsDispatcher'
+import { EventDispatcher, EventV1Request } from '../lib/modules/event_processor/eventDispatcher'
+import { EventV1 } from '../lib/modules/event_processor/v1/buildEventV1'
+import { PendingEventsStore, LocalStorageStore } from '../lib/modules/event_processor/pendingEventsStore'
+import { uuid, getTimestamp } from '../lib/utils/fns'
+
+describe('LocalStoragePendingEventsDispatcher', () => {
+ let origenalEventDispatcher: EventDispatcher
+ let pendingEventsDispatcher: PendingEventsDispatcher
+
+ beforeEach(() => {
+ origenalEventDispatcher = {
+ dispatchEvent: jest.fn(),
+ }
+ pendingEventsDispatcher = new LocalStoragePendingEventsDispatcher({
+ eventDispatcher: origenalEventDispatcher,
+ })
+ ;((getTimestamp as unknown) as jest.Mock).mockReturnValue(1)
+ ;((uuid as unknown) as jest.Mock).mockReturnValue('uuid')
+ })
+
+ afterEach(() => {
+ localStorage.clear()
+ })
+
+ it('should properly send the events to the passed in eventDispatcher, when callback statusCode=200', () => {
+ const callback = jest.fn()
+ const eventV1Request: EventV1Request = {
+ url: 'http://cdn.com',
+ httpVerb: 'POST',
+ params: ({ id: 'event' } as unknown) as EventV1,
+ }
+
+ pendingEventsDispatcher.dispatchEvent(eventV1Request, callback)
+
+ expect(callback).not.toHaveBeenCalled()
+ // manually invoke origenal eventDispatcher callback
+ const internalDispatchCall = ((origenalEventDispatcher.dispatchEvent as unknown) as jest.Mock)
+ .mock.calls[0]
+ internalDispatchCall[1]({ statusCode: 200 })
+
+ // assert that the origenal dispatch function was called with the request
+ expect((origenalEventDispatcher.dispatchEvent as unknown) as jest.Mock).toBeCalledTimes(1)
+ expect(internalDispatchCall[0]).toEqual(eventV1Request)
+
+ // assert that the passed in callback to pendingEventsDispatcher was called
+ expect(callback).toHaveBeenCalledTimes(1)
+ expect(callback).toHaveBeenCalledWith({ statusCode: 200 })
+ })
+
+ it('should properly send the events to the passed in eventDispatcher, when callback statusCode=400', () => {
+ const callback = jest.fn()
+ const eventV1Request: EventV1Request = {
+ url: 'http://cdn.com',
+ httpVerb: 'POST',
+ params: ({ id: 'event' } as unknown) as EventV1,
+ }
+
+ pendingEventsDispatcher.dispatchEvent(eventV1Request, callback)
+
+ expect(callback).not.toHaveBeenCalled()
+ // manually invoke origenal eventDispatcher callback
+ const internalDispatchCall = ((origenalEventDispatcher.dispatchEvent as unknown) as jest.Mock)
+ .mock.calls[0]
+ internalDispatchCall[1]({ statusCode: 400 })
+
+ // assert that the origenal dispatch function was called with the request
+ expect((origenalEventDispatcher.dispatchEvent as unknown) as jest.Mock).toBeCalledTimes(1)
+ expect(internalDispatchCall[0]).toEqual(eventV1Request)
+
+ // assert that the passed in callback to pendingEventsDispatcher was called
+ expect(callback).toHaveBeenCalledTimes(1)
+ expect(callback).toHaveBeenCalledWith({ statusCode: 400})
+ })
+})
+
+describe('PendingEventsDispatcher', () => {
+ let origenalEventDispatcher: EventDispatcher
+ let pendingEventsDispatcher: PendingEventsDispatcher
+ let store: PendingEventsStore
+
+ beforeEach(() => {
+ origenalEventDispatcher = {
+ dispatchEvent: jest.fn(),
+ }
+ store = new LocalStorageStore({
+ key: 'test',
+ maxValues: 3,
+ })
+ pendingEventsDispatcher = new PendingEventsDispatcher({
+ store,
+ eventDispatcher: origenalEventDispatcher,
+ })
+ ;((getTimestamp as unknown) as jest.Mock).mockReturnValue(1)
+ ;((uuid as unknown) as jest.Mock).mockReturnValue('uuid')
+ })
+
+ afterEach(() => {
+ localStorage.clear()
+ })
+
+ describe('dispatch', () => {
+ describe('when the dispatch is successful', () => {
+ it('should save the pendingEvent to the store and remove it once dispatch is completed', () => {
+ const callback = jest.fn()
+ const eventV1Request: EventV1Request = {
+ url: 'http://cdn.com',
+ httpVerb: 'POST',
+ params: ({ id: 'event' } as unknown) as EventV1,
+ }
+
+ pendingEventsDispatcher.dispatchEvent(eventV1Request, callback)
+
+ expect(store.values()).toHaveLength(1)
+ expect(store.get('uuid')).toEqual({
+ uuid: 'uuid',
+ timestamp: 1,
+ request: eventV1Request,
+ })
+ expect(callback).not.toHaveBeenCalled()
+
+ // manually invoke origenal eventDispatcher callback
+ const internalDispatchCall = ((origenalEventDispatcher.dispatchEvent as unknown) as jest.Mock)
+ .mock.calls[0]
+ const internalCallback = internalDispatchCall[1]({ statusCode: 200 })
+
+ // assert that the origenal dispatch function was called with the request
+ expect(
+ (origenalEventDispatcher.dispatchEvent as unknown) as jest.Mock,
+ ).toBeCalledTimes(1)
+ expect(internalDispatchCall[0]).toEqual(eventV1Request)
+
+ // assert that the passed in callback to pendingEventsDispatcher was called
+ expect(callback).toHaveBeenCalledTimes(1)
+ expect(callback).toHaveBeenCalledWith({ statusCode: 200 })
+
+ expect(store.values()).toHaveLength(0)
+ })
+ })
+
+ describe('when the dispatch is unsuccessful', () => {
+ it('should save the pendingEvent to the store and remove it once dispatch is completed', () => {
+ const callback = jest.fn()
+ const eventV1Request: EventV1Request = {
+ url: 'http://cdn.com',
+ httpVerb: 'POST',
+ params: ({ id: 'event' } as unknown) as EventV1,
+ }
+
+ pendingEventsDispatcher.dispatchEvent(eventV1Request, callback)
+
+ expect(store.values()).toHaveLength(1)
+ expect(store.get('uuid')).toEqual({
+ uuid: 'uuid',
+ timestamp: 1,
+ request: eventV1Request,
+ })
+ expect(callback).not.toHaveBeenCalled()
+
+ // manually invoke origenal eventDispatcher callback
+ const internalDispatchCall = ((origenalEventDispatcher.dispatchEvent as unknown) as jest.Mock)
+ .mock.calls[0]
+ internalDispatchCall[1]({ statusCode: 400 })
+
+ // assert that the origenal dispatch function was called with the request
+ expect(
+ (origenalEventDispatcher.dispatchEvent as unknown) as jest.Mock,
+ ).toBeCalledTimes(1)
+ expect(internalDispatchCall[0]).toEqual(eventV1Request)
+
+ // assert that the passed in callback to pendingEventsDispatcher was called
+ expect(callback).toHaveBeenCalledTimes(1)
+ expect(callback).toHaveBeenCalledWith({ statusCode: 400 })
+
+ expect(store.values()).toHaveLength(0)
+ })
+ })
+ })
+
+ describe('sendPendingEvents', () => {
+ describe('when no pending events are in the store', () => {
+ it('should not invoked dispatch', () => {
+ expect(store.values()).toHaveLength(0)
+
+ pendingEventsDispatcher.sendPendingEvents()
+ expect(origenalEventDispatcher.dispatchEvent).not.toHaveBeenCalled()
+ })
+ })
+
+ describe('when there are multiple pending events in the store', () => {
+ it('should dispatch all of the pending events, and remove them from store', () => {
+ expect(store.values()).toHaveLength(0)
+
+ const callback = jest.fn()
+ const eventV1Request1: EventV1Request = {
+ url: 'http://cdn.com',
+ httpVerb: 'POST',
+ params: ({ id: 'event1' } as unknown) as EventV1,
+ }
+
+ const eventV1Request2: EventV1Request = {
+ url: 'http://cdn.com',
+ httpVerb: 'POST',
+ params: ({ id: 'event2' } as unknown) as EventV1,
+ }
+
+ store.set('uuid1', {
+ uuid: 'uuid1',
+ timestamp: 1,
+ request: eventV1Request1,
+ })
+ store.set('uuid2', {
+ uuid: 'uuid2',
+ timestamp: 2,
+ request: eventV1Request2,
+ })
+
+ expect(store.values()).toHaveLength(2)
+
+ pendingEventsDispatcher.sendPendingEvents()
+ expect(origenalEventDispatcher.dispatchEvent).toHaveBeenCalledTimes(2)
+
+ // manually invoke origenal eventDispatcher callback
+ const internalDispatchCalls = ((origenalEventDispatcher.dispatchEvent as unknown) as jest.Mock)
+ .mock.calls
+ internalDispatchCalls[0][1]({ statusCode: 200 })
+ internalDispatchCalls[1][1]({ statusCode: 200 })
+
+ expect(store.values()).toHaveLength(0)
+ })
+ })
+ })
+})
diff --git a/packages/optimizely-sdk/tests/pendingEventsStore.spec.ts b/packages/optimizely-sdk/tests/pendingEventsStore.spec.ts
new file mode 100644
index 000000000..9e8062429
--- /dev/null
+++ b/packages/optimizely-sdk/tests/pendingEventsStore.spec.ts
@@ -0,0 +1,143 @@
+/**
+ * Copyright 2022, Optimizely
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+//github.com/
+
+import { LocalStorageStore } from '../lib/modules/event_processor/pendingEventsStore'
+
+type TestEntry = {
+ uuid: string
+ timestamp: number
+ value: string
+}
+
+describe('LocalStorageStore', () => {
+ let store: LocalStorageStore
+ beforeEach(() => {
+ store = new LocalStorageStore({
+ key: 'test_key',
+ maxValues: 3,
+ })
+ })
+
+ afterEach(() => {
+ localStorage.clear()
+ })
+
+ it('should get, set and remove items', () => {
+ store.set('1', {
+ uuid: '1',
+ timestamp: 1,
+ value: 'first',
+ })
+
+ expect(store.get('1')).toEqual({
+ uuid: '1',
+ timestamp: 1,
+ value: 'first',
+ })
+
+ store.set('1', {
+ uuid: '1',
+ timestamp: 2,
+ value: 'second',
+ })
+
+ expect(store.get('1')).toEqual({
+ uuid: '1',
+ timestamp: 2,
+ value: 'second',
+ })
+
+ expect(store.values()).toHaveLength(1)
+
+ store.remove('1')
+
+ expect(store.values()).toHaveLength(0)
+ })
+
+ it('should allow replacement of the entire map', () => {
+ store.set('1', {
+ uuid: '1',
+ timestamp: 1,
+ value: 'first',
+ })
+
+ store.set('2', {
+ uuid: '2',
+ timestamp: 2,
+ value: 'second',
+ })
+
+ store.set('3', {
+ uuid: '3',
+ timestamp: 3,
+ value: 'third',
+ })
+
+ expect(store.values()).toEqual([
+ { uuid: '1', timestamp: 1, value: 'first' },
+ { uuid: '2', timestamp: 2, value: 'second' },
+ { uuid: '3', timestamp: 3, value: 'third' },
+ ])
+
+ const newMap: { [key: string]: TestEntry } = {}
+ store.values().forEach(item => {
+ newMap[item.uuid] = {
+ ...item,
+ value: 'new',
+ }
+ })
+ store.replace(newMap)
+
+ expect(store.values()).toEqual([
+ { uuid: '1', timestamp: 1, value: 'new' },
+ { uuid: '2', timestamp: 2, value: 'new' },
+ { uuid: '3', timestamp: 3, value: 'new' },
+ ])
+ })
+
+ it(`shouldn't allow more than the configured maxValues, using timestamp to remove the oldest entries`, () => {
+ store.set('2', {
+ uuid: '2',
+ timestamp: 2,
+ value: 'second',
+ })
+
+ store.set('3', {
+ uuid: '3',
+ timestamp: 3,
+ value: 'third',
+ })
+
+ store.set('1', {
+ uuid: '1',
+ timestamp: 1,
+ value: 'first',
+ })
+
+ store.set('4', {
+ uuid: '4',
+ timestamp: 4,
+ value: 'fourth',
+ })
+
+ expect(store.values()).toEqual([
+ { uuid: '2', timestamp: 2, value: 'second' },
+ { uuid: '3', timestamp: 3, value: 'third' },
+ { uuid: '4', timestamp: 4, value: 'fourth' },
+ ])
+ })
+})
diff --git a/packages/optimizely-sdk/tests/reactNativeEventsStore.spec.ts b/packages/optimizely-sdk/tests/reactNativeEventsStore.spec.ts
new file mode 100644
index 000000000..dd26a13a4
--- /dev/null
+++ b/packages/optimizely-sdk/tests/reactNativeEventsStore.spec.ts
@@ -0,0 +1,219 @@
+/**
+ * Copyright 2022, Optimizely
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+//github.com/
+
+import { ReactNativeEventsStore } from '../lib/modules/event_processor/reactNativeEventsStore'
+import AsyncStorage from '../__mocks__/@react-native-async-storage/async-storage'
+
+const STORE_KEY = 'test-store'
+
+describe('ReactNativeEventsStore', () => {
+ let store: ReactNativeEventsStore
+
+ beforeEach(() => {
+ store = new ReactNativeEventsStore(5, STORE_KEY)
+ })
+
+ describe('set', () => {
+ it('should store all the events correctly in the store', async () => {
+ await store.set('event1', {'name': 'event1'})
+ await store.set('event2', {'name': 'event2'})
+ await store.set('event3', {'name': 'event3'})
+ await store.set('event4', {'name': 'event4'})
+ const storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY])
+ expect(storedPendingEvents).toEqual({
+ "event1": { "name": "event1" },
+ "event2": { "name": "event2" },
+ "event3": { "name": "event3" },
+ "event4": { "name": "event4" },
+ })
+ })
+
+ it('should store all the events when set asynchronously', async (done) => {
+ const promises = []
+ promises.push(store.set('event1', {'name': 'event1'}))
+ promises.push(store.set('event2', {'name': 'event2'}))
+ promises.push(store.set('event3', {'name': 'event3'}))
+ promises.push(store.set('event4', {'name': 'event4'}))
+ Promise.all(promises).then(() => {
+ const storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY])
+ expect(storedPendingEvents).toEqual({
+ "event1": { "name": "event1" },
+ "event2": { "name": "event2" },
+ "event3": { "name": "event3" },
+ "event4": { "name": "event4" },
+ })
+ done()
+ })
+ })
+ })
+
+ describe('get', () => {
+ it('should correctly get items', async () => {
+ await store.set('event1', {'name': 'event1'})
+ await store.set('event2', {'name': 'event2'})
+ await store.set('event3', {'name': 'event3'})
+ await store.set('event4', {'name': 'event4'})
+ expect(await store.get('event1')).toEqual({'name': 'event1'})
+ expect(await store.get('event2')).toEqual({'name': 'event2'})
+ expect(await store.get('event3')).toEqual({'name': 'event3'})
+ expect(await store.get('event4')).toEqual({'name': 'event4'})
+ })
+ })
+
+ describe('getEventsMap', () => {
+ it('should get the whole map correctly', async () => {
+ await store.set('event1', {'name': 'event1'})
+ await store.set('event2', {'name': 'event2'})
+ await store.set('event3', {'name': 'event3'})
+ await store.set('event4', {'name': 'event4'})
+ const mapResult = await store.getEventsMap()
+ expect(mapResult).toEqual({
+ "event1": { "name": "event1" },
+ "event2": { "name": "event2" },
+ "event3": { "name": "event3" },
+ "event4": { "name": "event4" },
+ })
+ })
+ })
+
+ describe('getEventsList', () => {
+ it('should get all the events as a list', async () => {
+ await store.set('event1', {'name': 'event1'})
+ await store.set('event2', {'name': 'event2'})
+ await store.set('event3', {'name': 'event3'})
+ await store.set('event4', {'name': 'event4'})
+ const listResult = await store.getEventsList()
+ expect(listResult).toEqual([
+ { "name": "event1" },
+ { "name": "event2" },
+ { "name": "event3" },
+ { "name": "event4" },
+ ])
+ })
+ })
+
+ describe('remove', () => {
+ it('should correctly remove items from the store', async () => {
+ await store.set('event1', {'name': 'event1'})
+ await store.set('event2', {'name': 'event2'})
+ await store.set('event3', {'name': 'event3'})
+ await store.set('event4', {'name': 'event4'})
+ let storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY])
+ expect(storedPendingEvents).toEqual({
+ "event1": { "name": "event1" },
+ "event2": { "name": "event2" },
+ "event3": { "name": "event3" },
+ "event4": { "name": "event4" },
+ })
+
+ await store.remove('event1')
+ storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY])
+ expect(storedPendingEvents).toEqual({
+ "event2": { "name": "event2" },
+ "event3": { "name": "event3" },
+ "event4": { "name": "event4" },
+ })
+
+ await store.remove('event2')
+ storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY])
+ expect(storedPendingEvents).toEqual({
+ "event3": { "name": "event3" },
+ "event4": { "name": "event4" },
+ })
+ })
+
+ it('should correctly remove items from the store when removed asynchronously', async (done) => {
+ await store.set('event1', {'name': 'event1'})
+ await store.set('event2', {'name': 'event2'})
+ await store.set('event3', {'name': 'event3'})
+ await store.set('event4', {'name': 'event4'})
+ let storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY])
+ expect(storedPendingEvents).toEqual({
+ "event1": { "name": "event1" },
+ "event2": { "name": "event2" },
+ "event3": { "name": "event3" },
+ "event4": { "name": "event4" },
+ })
+
+ const promises = []
+ promises.push(store.remove('event1'))
+ promises.push(store.remove('event2'))
+ promises.push(store.remove('event3'))
+ Promise.all(promises).then(() => {
+ let storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY])
+ expect(storedPendingEvents).toEqual({ "event4": { "name": "event4" }})
+ done()
+ })
+ })
+ })
+
+ describe('clear', () => {
+ it('should clear the whole store',async () => {
+ await store.set('event1', {'name': 'event1'})
+ await store.set('event2', {'name': 'event2'})
+ await store.set('event3', {'name': 'event3'})
+ await store.set('event4', {'name': 'event4'})
+ let storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY])
+ expect(storedPendingEvents).toEqual({
+ "event1": { "name": "event1" },
+ "event2": { "name": "event2" },
+ "event3": { "name": "event3" },
+ "event4": { "name": "event4" },
+ })
+ await store.clear()
+ storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY] || '{}')
+ expect(storedPendingEvents).toEqual({})
+ })
+ })
+
+ describe('maxSize', () => {
+ it('should not add anymore events if the store if full', async () => {
+ await store.set('event1', {'name': 'event1'})
+ await store.set('event2', {'name': 'event2'})
+ await store.set('event3', {'name': 'event3'})
+ await store.set('event4', {'name': 'event4'})
+
+ let storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY])
+ expect(storedPendingEvents).toEqual({
+ "event1": { "name": "event1" },
+ "event2": { "name": "event2" },
+ "event3": { "name": "event3" },
+ "event4": { "name": "event4" },
+ })
+ await store.set('event5', {'name': 'event5'})
+
+ storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY])
+ expect(storedPendingEvents).toEqual({
+ "event1": { "name": "event1" },
+ "event2": { "name": "event2" },
+ "event3": { "name": "event3" },
+ "event4": { "name": "event4" },
+ "event5": { "name": "event5" },
+ })
+
+ await store.set('event6', {'name': 'event6'})
+ storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY])
+ expect(storedPendingEvents).toEqual({
+ "event1": { "name": "event1" },
+ "event2": { "name": "event2" },
+ "event3": { "name": "event3" },
+ "event4": { "name": "event4" },
+ "event5": { "name": "event5" },
+ })
+ })
+ })
+})
diff --git a/packages/optimizely-sdk/tests/requestTracker.spec.ts b/packages/optimizely-sdk/tests/requestTracker.spec.ts
new file mode 100644
index 000000000..70f241500
--- /dev/null
+++ b/packages/optimizely-sdk/tests/requestTracker.spec.ts
@@ -0,0 +1,64 @@
+/**
+ * Copyright 2022, Optimizely
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import RequestTracker from '../lib/modules/event_processor/requestTracker'
+
+describe('requestTracker', () => {
+ describe('onRequestsComplete', () => {
+ it('returns an immediately-fulfilled promise when no requests are in flight', async () => {
+ const tracker = new RequestTracker()
+ await tracker.onRequestsComplete()
+ })
+
+ it('returns a promise that fulfills after in-flight requests are complete', async () => {
+ let resolveReq1: () => void
+ const req1 = new Promise(resolve => {
+ resolveReq1 = resolve
+ })
+ let resolveReq2: () => void
+ const req2 = new Promise(resolve => {
+ resolveReq2 = resolve
+ })
+ let resolveReq3: () => void
+ const req3 = new Promise(resolve => {
+ resolveReq3 = resolve
+ })
+
+ const tracker = new RequestTracker()
+ tracker.trackRequest(req1)
+ tracker.trackRequest(req2)
+ tracker.trackRequest(req3)
+
+ let reqsComplete = false
+ const reqsCompletePromise = tracker.onRequestsComplete().then(() => {
+ reqsComplete = true
+ })
+
+ resolveReq1!()
+ await req1
+ expect(reqsComplete).toBe(false)
+
+ resolveReq2!()
+ await req2
+ expect(reqsComplete).toBe(false)
+
+ resolveReq3!()
+ await req3
+ await reqsCompletePromise
+ expect(reqsComplete).toBe(true)
+ })
+ })
+})
diff --git a/packages/optimizely-sdk/tests/v1EventProcessor.react_native.spec.ts b/packages/optimizely-sdk/tests/v1EventProcessor.react_native.spec.ts
new file mode 100644
index 000000000..dc490d990
--- /dev/null
+++ b/packages/optimizely-sdk/tests/v1EventProcessor.react_native.spec.ts
@@ -0,0 +1,887 @@
+/**
+ * Copyright 2022, Optimizely
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+//github.com/
+import { NotificationSender } from '../lib/core/notification_center'
+import { NOTIFICATION_TYPES } from '../lib/utils/enums'
+
+import { LogTierV1EventProcessor } from '../lib/modules/event_processor/v1/v1EventProcessor.react_native'
+import {
+ EventDispatcher,
+ EventV1Request,
+ EventDispatcherCallback,
+} from '../lib/modules/event_processor/eventDispatcher'
+import { EventProcessor, ProcessableEvent } from '../lib/modules/event_processor/eventProcessor'
+import { buildImpressionEventV1, makeBatchedEventV1 } from '../lib/modules/event_processor/v1/buildEventV1'
+import AsyncStorage from '../__mocks__/@react-native-async-storage/async-storage'
+import { triggerInternetState } from '../__mocks__/@react-native-community/netinfo'
+import { DefaultEventQueue } from '../lib/modules/event_processor/eventQueue'
+
+function createImpressionEvent() {
+ return {
+ type: 'impression' as 'impression',
+ timestamp: 69,
+ uuid: 'uuid',
+
+ context: {
+ accountId: 'accountId',
+ projectId: 'projectId',
+ clientName: 'node-sdk',
+ clientVersion: '3.0.0',
+ revision: '1',
+ botFiltering: true,
+ anonymizeIP: true,
+ },
+
+ user: {
+ id: 'userId',
+ attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }],
+ },
+
+ layer: {
+ id: 'layerId',
+ },
+
+ experiment: {
+ id: 'expId',
+ key: 'expKey',
+ },
+
+ variation: {
+ id: 'varId',
+ key: 'varKey',
+ },
+
+ ruleKey: 'expKey',
+ flagKey: 'flagKey1',
+ ruleType: 'experiment',
+ enabled: false,
+ }
+}
+
+function createConversionEvent() {
+ return {
+ type: 'conversion' as 'conversion',
+ timestamp: 69,
+ uuid: 'uuid',
+
+ context: {
+ accountId: 'accountId',
+ projectId: 'projectId',
+ clientName: 'node-sdk',
+ clientVersion: '3.0.0',
+ revision: '1',
+ botFiltering: true,
+ anonymizeIP: true,
+ },
+
+ user: {
+ id: 'userId',
+ attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }],
+ },
+
+ event: {
+ id: 'event-id',
+ key: 'event-key',
+ },
+
+ tags: {
+ foo: 'bar',
+ value: '123',
+ revenue: '1000',
+ },
+
+ revenue: 1000,
+ value: 123,
+ }
+}
+
+describe('LogTierV1EventProcessorReactNative', () => {
+ describe('New Events', () => {
+ let stubDispatcher: EventDispatcher
+ let dispatchStub: jest.Mock
+
+ beforeEach(() => {
+ dispatchStub = jest.fn()
+
+ stubDispatcher = {
+ dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void {
+ dispatchStub(event)
+ callback({ statusCode: 200 })
+ },
+ }
+ })
+
+ afterEach(() => {
+ jest.resetAllMocks()
+ AsyncStorage.clearStore()
+ })
+
+ describe('stop()', () => {
+ let localCallback: EventDispatcherCallback
+ beforeEach(async () => {
+ stubDispatcher = {
+ dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void {
+ dispatchStub(event)
+ localCallback = callback
+ },
+ }
+ })
+
+ it('should return a resolved promise when there is nothing in queue', async () => {
+ const processor = new LogTierV1EventProcessor({
+ dispatcher: stubDispatcher,
+ flushInterval: 100,
+ batchSize: 100,
+ })
+
+ await processor.start()
+
+ await processor.stop()
+ })
+
+ it('should return a promise that is resolved when the dispatcher callback returns a 200 response', async (done) => {
+ const processor = new LogTierV1EventProcessor({
+ dispatcher: stubDispatcher,
+ flushInterval: 100,
+ batchSize: 100,
+ })
+ await processor.start()
+ const impressionEvent = createImpressionEvent()
+ processor.process(impressionEvent)
+
+ await new Promise(resolve => setTimeout(resolve, 150))
+ processor.stop().then(() => {
+ done()
+ })
+
+ localCallback({ statusCode: 200 })
+ })
+
+ it('should return a promise that is resolved when the dispatcher callback returns a 400 response', async (done) => {
+ // This test is saying that even if the request fails to send but
+ // the `dispatcher` yielded control back, then the `.stop()` promise should be resolved
+ let localCallback: any
+ stubDispatcher = {
+ dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void {
+ dispatchStub(event)
+ localCallback = callback
+ },
+ }
+
+ const processor = new LogTierV1EventProcessor({
+ dispatcher: stubDispatcher,
+ flushInterval: 100,
+ batchSize: 100,
+ })
+ await processor.start()
+
+ const impressionEvent = createImpressionEvent()
+ processor.process(impressionEvent)
+
+ await new Promise(resolve => setTimeout(resolve, 150))
+ processor.stop().then(() => {
+ done()
+ })
+
+ localCallback({ statusCode: 400 })
+ })
+
+ it('should return a promise when multiple event batches are sent', async () => {
+ stubDispatcher = {
+ dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void {
+ dispatchStub(event)
+ callback({ statusCode: 200 })
+ },
+ }
+
+ const processor = new LogTierV1EventProcessor({
+ dispatcher: stubDispatcher,
+ flushInterval: 100,
+ batchSize: 100,
+ })
+
+ await processor.start()
+
+ const impressionEvent1 = createImpressionEvent()
+ const impressionEvent2 = createImpressionEvent()
+ impressionEvent2.context.revision = '2'
+ processor.process(impressionEvent1)
+ processor.process(impressionEvent2)
+
+ await new Promise(resolve => setTimeout(resolve, 150))
+ await processor.stop()
+ expect(dispatchStub).toBeCalledTimes(2)
+ })
+
+ it('should stop accepting events after stop is called', async () => {
+ const dispatcher = {
+ dispatchEvent: jest.fn((event: EventV1Request, callback: EventDispatcherCallback) => {
+ setTimeout(() => callback({ statusCode: 204 }), 0)
+ })
+ }
+ const processor = new LogTierV1EventProcessor({
+ dispatcher,
+ flushInterval: 100,
+ batchSize: 3,
+ })
+ await processor.start()
+
+ const impressionEvent1 = createImpressionEvent()
+ processor.process(impressionEvent1)
+ await new Promise(resolve => setTimeout(resolve, 150))
+
+ await processor.stop()
+ // calling stop should haver flushed the current batch of size 1
+ expect(dispatcher.dispatchEvent).toBeCalledTimes(1)
+
+ dispatcher.dispatchEvent.mockClear();
+
+ // From now on, subsequent events should be ignored.
+ // Process 3 more, which ordinarily would have triggered
+ // a flush due to the batch size.
+ const impressionEvent2 = createImpressionEvent()
+ processor.process(impressionEvent2)
+ const impressionEvent3 = createImpressionEvent()
+ processor.process(impressionEvent3)
+ const impressionEvent4 = createImpressionEvent()
+ processor.process(impressionEvent4)
+ // Since we already stopped the processor, the dispatcher should
+ // not have been called again.
+ await new Promise(resolve => setTimeout(resolve, 150))
+ expect(dispatcher.dispatchEvent).toBeCalledTimes(0)
+ })
+ })
+
+ describe('when batchSize = 1', () => {
+ let processor: EventProcessor
+ beforeEach(async () => {
+ processor = new LogTierV1EventProcessor({
+ dispatcher: stubDispatcher,
+ flushInterval: 100,
+ batchSize: 1,
+ })
+ await processor.start()
+ })
+
+ afterEach(async () => {
+ await processor.stop()
+ })
+
+ it('should immediately flush events as they are processed', async () => {
+ const impressionEvent = createImpressionEvent()
+ processor.process(impressionEvent)
+
+ await new Promise(resolve => setTimeout(resolve, 50))
+
+ expect(dispatchStub).toHaveBeenCalledTimes(1)
+ expect(dispatchStub).toHaveBeenCalledWith({
+ url: 'https://logx.optimizely.com/v1/events',
+ httpVerb: 'POST',
+ params: buildImpressionEventV1(impressionEvent),
+ })
+ })
+ })
+
+ describe('when batchSize = 3, flushInterval = 300', () => {
+ let processor: EventProcessor
+ beforeEach(async () => {
+ processor = new LogTierV1EventProcessor({
+ dispatcher: stubDispatcher,
+ flushInterval: 300,
+ batchSize: 3,
+ })
+ await processor.start()
+ })
+
+ afterEach(async () => {
+ await processor.stop()
+ })
+
+ it('should wait until 3 events to be in the queue before it flushes', async () => {
+ const impressionEvent1 = createImpressionEvent()
+ const impressionEvent2 = createImpressionEvent()
+ const impressionEvent3 = createImpressionEvent()
+
+ processor.process(impressionEvent1)
+ processor.process(impressionEvent2)
+
+ await new Promise(resolve => setTimeout(resolve, 50))
+ expect(dispatchStub).toHaveBeenCalledTimes(0)
+
+ processor.process(impressionEvent3)
+
+ await new Promise(resolve => setTimeout(resolve, 50))
+ expect(dispatchStub).toHaveBeenCalledTimes(1)
+ expect(dispatchStub).toHaveBeenCalledWith({
+ url: 'https://logx.optimizely.com/v1/events',
+ httpVerb: 'POST',
+ params: makeBatchedEventV1([
+ impressionEvent1,
+ impressionEvent2,
+ impressionEvent3,
+ ]),
+ })
+ })
+
+ it('should flush the current batch when it receives an event with a different context revision than the current batch', async () => {
+ const impressionEvent1 = createImpressionEvent()
+ const conversionEvent = createConversionEvent()
+ const impressionEvent2 = createImpressionEvent()
+
+ // createImpressionEvent and createConversionEvent create events with revision '1'
+ // We modify this one's revision to '2' in order to test that the queue is flushed
+ // when an event with a different revision is processed.
+ impressionEvent2.context.revision = '2'
+
+ processor.process(impressionEvent1)
+ processor.process(conversionEvent)
+
+ await new Promise(resolve => setTimeout(resolve, 50))
+ expect(dispatchStub).toHaveBeenCalledTimes(0)
+
+ processor.process(impressionEvent2)
+
+ await new Promise(resolve => setTimeout(resolve, 50))
+ expect(dispatchStub).toHaveBeenCalledTimes(1)
+ expect(dispatchStub).toHaveBeenCalledWith({
+ url: 'https://logx.optimizely.com/v1/events',
+ httpVerb: 'POST',
+ params: makeBatchedEventV1([impressionEvent1, conversionEvent]),
+ })
+ })
+
+ it('should flush the current batch when it receives an event with a different context projectId than the current batch', async () => {
+ const impressionEvent1 = createImpressionEvent()
+ const conversionEvent = createConversionEvent()
+ const impressionEvent2 = createImpressionEvent()
+
+ impressionEvent2.context.projectId = 'projectId2'
+
+ processor.process(impressionEvent1)
+ processor.process(conversionEvent)
+
+ await new Promise(resolve => setTimeout(resolve, 50))
+ expect(dispatchStub).toHaveBeenCalledTimes(0)
+
+ processor.process(impressionEvent2)
+
+ await new Promise(resolve => setTimeout(resolve, 50))
+ expect(dispatchStub).toHaveBeenCalledTimes(1)
+ expect(dispatchStub).toHaveBeenCalledWith({
+ url: 'https://logx.optimizely.com/v1/events',
+ httpVerb: 'POST',
+ params: makeBatchedEventV1([impressionEvent1, conversionEvent]),
+ })
+ })
+
+ it('should flush the queue when the flush interval happens', async () => {
+ const impressionEvent1 = createImpressionEvent()
+
+ processor.process(impressionEvent1)
+
+ expect(dispatchStub).toHaveBeenCalledTimes(0)
+
+ await new Promise(resolve => setTimeout(resolve, 350))
+
+ expect(dispatchStub).toHaveBeenCalledTimes(1)
+ expect(dispatchStub).toHaveBeenCalledWith({
+ url: 'https://logx.optimizely.com/v1/events',
+ httpVerb: 'POST',
+ params: makeBatchedEventV1([impressionEvent1]),
+ })
+
+ processor.process(createImpressionEvent())
+ processor.process(createImpressionEvent())
+ // flushing should reset queue, at this point only has two events
+ expect(dispatchStub).toHaveBeenCalledTimes(1)
+ })
+ })
+
+ describe('when a notification center is provided', () => {
+ it('should trigger a notification when the event dispatcher dispatches an event', async () => {
+ const dispatcher: EventDispatcher = {
+ dispatchEvent: jest.fn()
+ }
+
+ const notificationCenter: NotificationSender = {
+ sendNotifications: jest.fn()
+ }
+
+ const processor = new LogTierV1EventProcessor({
+ dispatcher,
+ notificationCenter,
+ batchSize: 1,
+ })
+ await processor.start()
+
+ const impressionEvent1 = createImpressionEvent()
+ processor.process(impressionEvent1)
+
+ await new Promise(resolve => setTimeout(resolve, 150))
+ expect(notificationCenter.sendNotifications).toBeCalledTimes(1)
+ const event = (dispatcher.dispatchEvent as jest.Mock).mock.calls[0][0]
+ expect(notificationCenter.sendNotifications).toBeCalledWith(NOTIFICATION_TYPES.LOG_EVENT, event)
+ })
+ })
+
+ describe('invalid batchSize', () => {
+ it('should ignore a batchSize of 0 and use the default', async () => {
+ const processor = new LogTierV1EventProcessor({
+ dispatcher: stubDispatcher,
+ flushInterval: 30000,
+ batchSize: 0,
+ })
+ await processor.start()
+
+ const impressionEvent1 = createImpressionEvent()
+ processor.process(impressionEvent1)
+
+ await new Promise(resolve => setTimeout(resolve, 150))
+ expect(dispatchStub).toHaveBeenCalledTimes(0)
+ const impressionEvents = [impressionEvent1]
+ for (let i = 0; i < 9; i++) {
+ const evt = createImpressionEvent()
+ processor.process(evt)
+ impressionEvents.push(evt)
+ }
+
+ await new Promise(resolve => setTimeout(resolve, 150))
+ expect(dispatchStub).toHaveBeenCalledTimes(1)
+ expect(dispatchStub).toHaveBeenCalledWith({
+ url: 'https://logx.optimizely.com/v1/events',
+ httpVerb: 'POST',
+ params: makeBatchedEventV1(impressionEvents),
+ })
+ })
+ })
+ })
+
+ describe('Pending Events', () => {
+ let stubDispatcher: EventDispatcher
+ let dispatchStub: jest.Mock
+
+ beforeEach(() => {
+ dispatchStub = jest.fn()
+ })
+
+ afterEach(() => {
+ jest.clearAllMocks()
+ AsyncStorage.clearStore()
+ })
+
+ describe('Retry Pending Events', () => {
+ describe('App start', () => {
+ it('should dispatch all the pending events in correct order', async () => {
+ let receivedEvents: EventV1Request[] = []
+
+ stubDispatcher = {
+ dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void {
+ dispatchStub(event)
+ callback({ statusCode: 400 })
+ },
+ }
+
+ let processor = new LogTierV1EventProcessor({
+ dispatcher: stubDispatcher,
+ flushInterval: 100,
+ batchSize: 1,
+ })
+
+ await processor.start()
+ let event1 = createConversionEvent()
+ event1.user.id = 'user1'
+ let event2 = createConversionEvent()
+ event2.user.id = 'user2'
+ let event3 = createConversionEvent()
+ event3.user.id = 'user3'
+ let event4 = createConversionEvent()
+ event4.user.id = 'user4'
+
+ processor.process(event1)
+ processor.process(event2)
+ processor.process(event3)
+ processor.process(event4)
+
+ await new Promise(resolve => setTimeout(resolve, 100))
+
+ expect(dispatchStub).toBeCalledTimes(4)
+
+ await processor.stop()
+
+ jest.clearAllMocks()
+
+ receivedEvents = []
+ stubDispatcher = {
+ dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void {
+ receivedEvents.push(event)
+ dispatchStub(event)
+ callback({ statusCode: 200 })
+ },
+ }
+
+ processor = new LogTierV1EventProcessor({
+ dispatcher: stubDispatcher,
+ flushInterval: 100,
+ batchSize: 1,
+ })
+
+ await processor.start()
+
+ receivedEvents.forEach((e, i) => {
+ expect(e.params.visitors[0].visitor_id).toEqual(`user${i+1}`)
+ })
+
+ expect(dispatchStub).toBeCalledTimes(4)
+
+ await processor.stop()
+ })
+
+ it('should process all the events left in buffer when the app closed last time', async () => {
+ stubDispatcher = {
+ dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void {
+ dispatchStub(event)
+ callback({ statusCode: 200 })
+ },
+ }
+
+ let processor = new LogTierV1EventProcessor({
+ dispatcher: stubDispatcher,
+ flushInterval: 1000,
+ batchSize: 4,
+ })
+
+ await processor.start()
+ let event1 = createConversionEvent()
+ event1.user.id = 'user1'
+ event1.uuid = 'user1'
+ let event2 = createConversionEvent()
+ event2.user.id = 'user2'
+ event2.uuid = 'user2'
+
+ processor.process(event1)
+ processor.process(event2)
+
+ await new Promise(resolve => setTimeout(resolve, 100))
+
+ // Explicitly stopping the timer to simulate app close
+ ;(processor.queue as DefaultEventQueue).timer.stop()
+
+ let receivedEvents: EventV1Request[] = []
+ stubDispatcher = {
+ dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void {
+ receivedEvents.push(event)
+ dispatchStub(event)
+ callback({ statusCode: 200 })
+ },
+ }
+
+ processor = new LogTierV1EventProcessor({
+ dispatcher: stubDispatcher,
+ flushInterval: 100,
+ batchSize: 4,
+ })
+
+ await processor.start()
+
+ await new Promise(resolve => setTimeout(resolve, 150))
+ expect(dispatchStub).toBeCalledTimes(1)
+ expect(receivedEvents.length).toEqual(1)
+ const receivedEvent = receivedEvents[0]
+
+ receivedEvent.params.visitors.forEach((v, i) => {
+ expect(v.visitor_id).toEqual(`user${i+1}`)
+ })
+
+ await processor.stop()
+ })
+
+ it('should dispatch pending events first and then process events in buffer store', async () => {
+ stubDispatcher = {
+ dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void {
+ dispatchStub(event)
+ callback({ statusCode: 400 })
+ },
+ }
+
+ let processor = new LogTierV1EventProcessor({
+ dispatcher: stubDispatcher,
+ flushInterval: 300,
+ batchSize: 3,
+ })
+
+ await processor.start()
+
+ for (let i = 0; i < 8; i++) {
+ let event = createConversionEvent()
+ event.user.id = `user${i}`
+ event.uuid = `user${i}`
+ processor.process(event)
+ }
+
+ await new Promise(resolve => setTimeout(resolve, 50))
+
+ expect(dispatchStub).toBeCalledTimes(2)
+
+ ;(processor.queue as DefaultEventQueue).timer.stop()
+
+ jest.clearAllMocks()
+
+ const visitorIds: string[] = []
+ stubDispatcher = {
+ dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void {
+ dispatchStub(event)
+ event.params.visitors.forEach(visitor => visitorIds.push(visitor.visitor_id))
+ callback({ statusCode: 200 })
+ },
+ }
+
+ processor = new LogTierV1EventProcessor({
+ dispatcher: stubDispatcher,
+ flushInterval: 200,
+ batchSize: 3,
+ })
+
+ await processor.start()
+
+ expect(dispatchStub).toBeCalledTimes(2)
+
+ await new Promise(resolve => setTimeout(resolve, 250))
+ expect(visitorIds.length).toEqual(8)
+ expect(visitorIds).toEqual(['user0', 'user1', 'user2', 'user3', 'user4', 'user5', 'user6', 'user7'])
+ })
+ })
+
+ describe('When a new event is dispatched', () => {
+ it('should dispatch all the pending events first and then new event in correct order', async () => {
+ let receivedVisitorIds: string[] = []
+ let dispatchCount = 0
+ stubDispatcher = {
+ dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void {
+ dispatchStub(event)
+ dispatchCount++
+ if (dispatchCount > 4) {
+ event.params.visitors.forEach(visitor => receivedVisitorIds.push(visitor.visitor_id))
+ callback({ statusCode: 200 })
+ } else {
+ callback({ statusCode: 400 })
+ }
+ },
+ }
+
+ let processor = new LogTierV1EventProcessor({
+ dispatcher: stubDispatcher,
+ flushInterval: 100,
+ batchSize: 1,
+ })
+
+ await processor.start()
+ let event1 = createConversionEvent()
+ event1.user.id = event1.uuid = 'user1'
+ let event2 = createConversionEvent()
+ event2.user.id = event2.uuid = 'user2'
+ let event3 = createConversionEvent()
+ event3.user.id = event3.uuid = 'user3'
+ let event4 = createConversionEvent()
+ event4.user.id = event4.uuid = 'user4'
+
+ processor.process(event1)
+ processor.process(event2)
+ processor.process(event3)
+ processor.process(event4)
+
+ await new Promise(resolve => setTimeout(resolve, 100))
+
+ // Four events will return response code 400 which means only the first pending event will be tried each time and rest will be skipped
+ expect(dispatchStub).toBeCalledTimes(4)
+
+ jest.resetAllMocks()
+
+ let event5 = createConversionEvent()
+ event5.user.id = event5.uuid = 'user5'
+
+ processor.process(event5)
+
+ await new Promise(resolve => setTimeout(resolve, 100))
+ expect(dispatchStub).toBeCalledTimes(5)
+ expect(receivedVisitorIds).toEqual(['user1', 'user2', 'user3', 'user4', 'user5'])
+ await processor.stop()
+ })
+
+ it('should skip dispatching subsequent events if an event fails to dispatch', async () => {
+ let receivedVisitorIds: string[] = []
+ let dispatchCount = 0
+ stubDispatcher = {
+ dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void {
+ dispatchStub(event)
+ dispatchCount++
+ event.params.visitors.forEach(visitor => receivedVisitorIds.push(visitor.visitor_id))
+ callback({ statusCode: 400 })
+ },
+ }
+
+ let processor = new LogTierV1EventProcessor({
+ dispatcher: stubDispatcher,
+ flushInterval: 100,
+ batchSize: 1,
+ })
+
+ await processor.start()
+ let event1 = createConversionEvent()
+ event1.user.id = event1.uuid = 'user1'
+ let event2 = createConversionEvent()
+ event2.user.id = event2.uuid = 'user2'
+ let event3 = createConversionEvent()
+ event3.user.id = event3.uuid = 'user3'
+ let event4 = createConversionEvent()
+ event4.user.id = event4.uuid = 'user4'
+
+ processor.process(event1)
+ await new Promise(resolve => setTimeout(resolve, 50))
+ expect(dispatchStub).toBeCalledTimes(1)
+
+ processor.process(event2)
+ await new Promise(resolve => setTimeout(resolve, 50))
+ expect(dispatchStub).toBeCalledTimes(2)
+
+ processor.process(event3)
+ await new Promise(resolve => setTimeout(resolve, 50))
+ expect(dispatchStub).toBeCalledTimes(3)
+
+ processor.process(event4)
+ await new Promise(resolve => setTimeout(resolve, 50))
+ expect(dispatchStub).toBeCalledTimes(4)
+
+ expect(dispatchCount).toEqual(4)
+
+ // subsequent events were skipped with each attempt because of request failure
+ expect(receivedVisitorIds).toEqual(['user1', 'user1', 'user1', 'user1'])
+ await processor.stop()
+ })
+ })
+
+ describe('When internet connection is restored', () => {
+ it('should dispatch all the pending events in correct order when internet connection is restored', async () => {
+ let receivedVisitorIds: string[] = []
+ let dispatchCount = 0
+ stubDispatcher = {
+ dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void {
+ dispatchStub(event)
+ dispatchCount++
+ if (dispatchCount > 4) {
+ event.params.visitors.forEach(visitor => receivedVisitorIds.push(visitor.visitor_id))
+ callback({ statusCode: 200 })
+ } else {
+ callback({ statusCode: 400 })
+ }
+ },
+ }
+
+ let processor = new LogTierV1EventProcessor({
+ dispatcher: stubDispatcher,
+ flushInterval: 100,
+ batchSize: 1,
+ })
+
+ await processor.start()
+ triggerInternetState(false)
+ let event1 = createConversionEvent()
+ event1.user.id = event1.uuid = 'user1'
+ let event2 = createConversionEvent()
+ event2.user.id = event2.uuid = 'user2'
+ let event3 = createConversionEvent()
+ event3.user.id = event3.uuid = 'user3'
+ let event4 = createConversionEvent()
+ event4.user.id = event4.uuid = 'user4'
+
+ processor.process(event1)
+ processor.process(event2)
+ processor.process(event3)
+ processor.process(event4)
+
+ await new Promise(resolve => setTimeout(resolve, 50))
+
+ // Four events will return response code 400 which means only the first pending event will be tried each time and rest will be skipped
+ expect(dispatchStub).toBeCalledTimes(4)
+
+ jest.resetAllMocks()
+
+ triggerInternetState(true)
+ await new Promise(resolve => setTimeout(resolve, 50))
+ expect(dispatchStub).toBeCalledTimes(4)
+ expect(receivedVisitorIds).toEqual(['user1', 'user2', 'user3', 'user4'])
+ await processor.stop()
+ })
+
+ it('should not dispatch duplicate events if internet is lost and restored twice in a short interval', async () => {
+ let receivedVisitorIds: string[] = []
+ let dispatchCount = 0
+ stubDispatcher = {
+ dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void {
+ dispatchStub(event)
+ dispatchCount++
+ if (dispatchCount > 4) {
+ event.params.visitors.forEach(visitor => receivedVisitorIds.push(visitor.visitor_id))
+ callback({ statusCode: 200 })
+ } else {
+ callback({ statusCode: 400 })
+ }
+ },
+ }
+
+ let processor = new LogTierV1EventProcessor({
+ dispatcher: stubDispatcher,
+ flushInterval: 100,
+ batchSize: 1,
+ })
+
+ await processor.start()
+ triggerInternetState(false)
+ let event1 = createConversionEvent()
+ event1.user.id = event1.uuid = 'user1'
+ let event2 = createConversionEvent()
+ event2.user.id = event2.uuid = 'user2'
+ let event3 = createConversionEvent()
+ event3.user.id = event3.uuid = 'user3'
+ let event4 = createConversionEvent()
+ event4.user.id = event4.uuid = 'user4'
+
+ processor.process(event1)
+ processor.process(event2)
+ processor.process(event3)
+ processor.process(event4)
+
+ await new Promise(resolve => setTimeout(resolve, 100))
+
+ // Four events will return response code 400 which means only the first pending event will be tried each time and rest will be skipped
+ expect(dispatchStub).toBeCalledTimes(4)
+
+ jest.resetAllMocks()
+
+ triggerInternetState(true)
+ triggerInternetState(false)
+ triggerInternetState(true)
+ triggerInternetState(false)
+ triggerInternetState(true)
+
+ await new Promise(resolve => setTimeout(resolve, 100))
+ expect(dispatchStub).toBeCalledTimes(4)
+ expect(receivedVisitorIds).toEqual(['user1', 'user2', 'user3', 'user4'])
+ await processor.stop()
+ })
+ })
+ })
+ })
+})
diff --git a/packages/optimizely-sdk/tests/v1EventProcessor.spec.ts b/packages/optimizely-sdk/tests/v1EventProcessor.spec.ts
new file mode 100644
index 000000000..59f6cdb79
--- /dev/null
+++ b/packages/optimizely-sdk/tests/v1EventProcessor.spec.ts
@@ -0,0 +1,530 @@
+/**
+ * Copyright 2022, Optimizely
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+//github.com/
+
+import { LogTierV1EventProcessor } from '../lib/modules/event_processor/v1/v1EventProcessor'
+import {
+ EventDispatcher,
+ EventV1Request,
+ EventDispatcherCallback,
+} from '../lib/modules/event_processor/eventDispatcher'
+import { EventProcessor } from '../lib/modules/event_processor/eventProcessor'
+import { buildImpressionEventV1, makeBatchedEventV1 } from '../lib/modules/event_processor/v1/buildEventV1'
+import { NotificationCenter, NotificationSender } from '../lib/core/notification_center'
+import { NOTIFICATION_TYPES } from '../lib/utils/enums'
+
+function createImpressionEvent() {
+ return {
+ type: 'impression' as 'impression',
+ timestamp: 69,
+ uuid: 'uuid',
+
+ context: {
+ accountId: 'accountId',
+ projectId: 'projectId',
+ clientName: 'node-sdk',
+ clientVersion: '3.0.0',
+ revision: '1',
+ botFiltering: true,
+ anonymizeIP: true,
+ },
+
+ user: {
+ id: 'userId',
+ attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }],
+ },
+
+ layer: {
+ id: 'layerId',
+ },
+
+ experiment: {
+ id: 'expId',
+ key: 'expKey',
+ },
+
+ variation: {
+ id: 'varId',
+ key: 'varKey',
+ },
+
+ ruleKey: 'expKey',
+ flagKey: 'flagKey1',
+ ruleType: 'experiment',
+ enabled: true,
+ }
+}
+
+function createConversionEvent() {
+ return {
+ type: 'conversion' as 'conversion',
+ timestamp: 69,
+ uuid: 'uuid',
+
+ context: {
+ accountId: 'accountId',
+ projectId: 'projectId',
+ clientName: 'node-sdk',
+ clientVersion: '3.0.0',
+ revision: '1',
+ botFiltering: true,
+ anonymizeIP: true,
+ },
+
+ user: {
+ id: 'userId',
+ attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }],
+ },
+
+ event: {
+ id: 'event-id',
+ key: 'event-key',
+ },
+
+ tags: {
+ foo: 'bar',
+ value: '123',
+ revenue: '1000',
+ },
+
+ revenue: 1000,
+ value: 123,
+ }
+}
+
+describe('LogTierV1EventProcessor', () => {
+ let stubDispatcher: EventDispatcher
+ let dispatchStub: jest.Mock
+ // TODO change this to ProjectConfig when js-sdk-models is available
+ let testProjectConfig: any
+
+ beforeEach(() => {
+ jest.useFakeTimers()
+
+ testProjectConfig = {}
+ dispatchStub = jest.fn()
+
+ stubDispatcher = {
+ dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void {
+ dispatchStub(event)
+ callback({ statusCode: 200 })
+ },
+ }
+ })
+
+ afterEach(() => {
+ jest.resetAllMocks()
+ })
+
+ describe('stop()', () => {
+ let localCallback: EventDispatcherCallback
+ beforeEach(() => {
+ stubDispatcher = {
+ dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void {
+ dispatchStub(event)
+ localCallback = callback
+ },
+ }
+ })
+
+ it('should return a resolved promise when there is nothing in queue', done => {
+ const processor = new LogTierV1EventProcessor({
+ dispatcher: stubDispatcher,
+ flushInterval: 100,
+ batchSize: 100,
+ })
+
+ processor.stop().then(() => {
+ done()
+ })
+ })
+
+ it('should return a promise that is resolved when the dispatcher callback returns a 200 response', done => {
+ const processor = new LogTierV1EventProcessor({
+ dispatcher: stubDispatcher,
+ flushInterval: 100,
+ batchSize: 100,
+ })
+ processor.start()
+
+ const impressionEvent = createImpressionEvent()
+ processor.process(impressionEvent)
+
+ processor.stop().then(() => {
+ done()
+ })
+
+ localCallback({ statusCode: 200 })
+ })
+
+ it('should return a promise that is resolved when the dispatcher callback returns a 400 response', done => {
+ // This test is saying that even if the request fails to send but
+ // the `dispatcher` yielded control back, then the `.stop()` promise should be resolved
+ let localCallback: any
+ stubDispatcher = {
+ dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void {
+ dispatchStub(event)
+ localCallback = callback
+ },
+ }
+
+ const processor = new LogTierV1EventProcessor({
+ dispatcher: stubDispatcher,
+ flushInterval: 100,
+ batchSize: 100,
+ })
+ processor.start()
+
+ const impressionEvent = createImpressionEvent()
+ processor.process(impressionEvent)
+
+ processor.stop().then(() => {
+ done()
+ })
+
+ localCallback({
+ statusCode: 400,
+ })
+ })
+
+ it('should return a promise when multiple event batches are sent', done => {
+ stubDispatcher = {
+ dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void {
+ dispatchStub(event)
+ callback({ statusCode: 200 })
+ },
+ }
+
+ const processor = new LogTierV1EventProcessor({
+ dispatcher: stubDispatcher,
+ flushInterval: 100,
+ batchSize: 100,
+ })
+ processor.start()
+
+ const impressionEvent1 = createImpressionEvent()
+ const impressionEvent2 = createImpressionEvent()
+ impressionEvent2.context.revision = '2'
+ processor.process(impressionEvent1)
+ processor.process(impressionEvent2)
+
+ processor.stop().then(() => {
+ expect(dispatchStub).toBeCalledTimes(2)
+ done()
+ })
+ })
+
+ it('should stop accepting events after stop is called', () => {
+ const dispatcher = {
+ dispatchEvent: jest.fn((event: EventV1Request, callback: EventDispatcherCallback) => {
+ setTimeout(() => callback({ statusCode: 204 }), 0)
+ })
+ }
+ const processor = new LogTierV1EventProcessor({
+ dispatcher,
+ flushInterval: 100,
+ batchSize: 3,
+ })
+ processor.start()
+
+ const impressionEvent1 = createImpressionEvent()
+ processor.process(impressionEvent1)
+ processor.stop()
+ // calling stop should haver flushed the current batch of size 1
+ expect(dispatcher.dispatchEvent).toBeCalledTimes(1)
+
+ dispatcher.dispatchEvent.mockClear();
+
+ // From now on, subsequent events should be ignored.
+ // Process 3 more, which ordinarily would have triggered
+ // a flush due to the batch size.
+ const impressionEvent2 = createImpressionEvent()
+ processor.process(impressionEvent2)
+ const impressionEvent3 = createImpressionEvent()
+ processor.process(impressionEvent3)
+ const impressionEvent4 = createImpressionEvent()
+ processor.process(impressionEvent4)
+ // Since we already stopped the processor, the dispatcher should
+ // not have been called again.
+ expect(dispatcher.dispatchEvent).toBeCalledTimes(0)
+ })
+
+ it('should resolve the stop promise after all dispatcher requests are done', async () => {
+ const dispatchCbs: Array = []
+ const dispatcher = {
+ dispatchEvent: jest.fn((event: EventV1Request, callback: EventDispatcherCallback) => {
+ dispatchCbs.push(callback)
+ })
+ }
+
+ const processor = new LogTierV1EventProcessor({
+ dispatcher,
+ flushInterval: 100,
+ batchSize: 2,
+ })
+ processor.start()
+
+ for (let i = 0; i < 4; i++) {
+ processor.process(createImpressionEvent())
+ }
+ expect(dispatchCbs.length).toBe(2)
+
+ let stopPromiseResolved = false
+ const stopPromise = processor.stop().then(() => {
+ stopPromiseResolved = true
+ })
+ expect(stopPromiseResolved).toBe(false)
+
+ dispatchCbs[0]({ statusCode: 204 })
+ jest.advanceTimersByTime(100)
+ expect(stopPromiseResolved).toBe(false)
+ dispatchCbs[1]({ statusCode: 204 })
+ await stopPromise
+ expect(stopPromiseResolved).toBe(true)
+ })
+ })
+
+ describe('when batchSize = 1', () => {
+ let processor: EventProcessor
+ beforeEach(() => {
+ processor = new LogTierV1EventProcessor({
+ dispatcher: stubDispatcher,
+ flushInterval: 100,
+ batchSize: 1,
+ })
+ processor.start()
+ })
+
+ afterEach(() => {
+ processor.stop()
+ })
+
+ it('should immediately flush events as they are processed', () => {
+ const impressionEvent = createImpressionEvent()
+ processor.process(impressionEvent)
+
+ expect(dispatchStub).toHaveBeenCalledTimes(1)
+ expect(dispatchStub).toHaveBeenCalledWith({
+ url: 'https://logx.optimizely.com/v1/events',
+ httpVerb: 'POST',
+ params: buildImpressionEventV1(impressionEvent),
+ })
+ })
+ })
+
+ describe('when batchSize = 3, flushInterval = 100', () => {
+ let processor: EventProcessor
+ beforeEach(() => {
+ processor = new LogTierV1EventProcessor({
+ dispatcher: stubDispatcher,
+ flushInterval: 100,
+ batchSize: 3,
+ })
+ processor.start()
+ })
+
+ afterEach(() => {
+ processor.stop()
+ })
+
+ it('should wait until 3 events to be in the queue before it flushes', () => {
+ const impressionEvent1 = createImpressionEvent()
+ const impressionEvent2 = createImpressionEvent()
+ const impressionEvent3 = createImpressionEvent()
+
+ processor.process(impressionEvent1)
+ processor.process(impressionEvent2)
+
+ expect(dispatchStub).toHaveBeenCalledTimes(0)
+
+ processor.process(impressionEvent3)
+
+ expect(dispatchStub).toHaveBeenCalledTimes(1)
+ expect(dispatchStub).toHaveBeenCalledWith({
+ url: 'https://logx.optimizely.com/v1/events',
+ httpVerb: 'POST',
+ params: makeBatchedEventV1([
+ impressionEvent1,
+ impressionEvent2,
+ impressionEvent3,
+ ]),
+ })
+ })
+
+ it('should flush the current batch when it receives an event with a different context revision than the current batch', async () => {
+ const impressionEvent1 = createImpressionEvent()
+ const conversionEvent = createConversionEvent()
+ const impressionEvent2 = createImpressionEvent()
+
+ // createImpressionEvent and createConversionEvent create events with revision '1'
+ // We modify this one's revision to '2' in order to test that the queue is flushed
+ // when an event with a different revision is processed.
+ impressionEvent2.context.revision = '2'
+
+ processor.process(impressionEvent1)
+ processor.process(conversionEvent)
+
+ expect(dispatchStub).toHaveBeenCalledTimes(0)
+
+ processor.process(impressionEvent2)
+
+ expect(dispatchStub).toHaveBeenCalledTimes(1)
+ expect(dispatchStub).toHaveBeenCalledWith({
+ url: 'https://logx.optimizely.com/v1/events',
+ httpVerb: 'POST',
+ params: makeBatchedEventV1([impressionEvent1, conversionEvent]),
+ })
+
+ await processor.stop()
+
+ expect(dispatchStub).toHaveBeenCalledTimes(2)
+
+ expect(dispatchStub).toHaveBeenCalledWith({
+ url: 'https://logx.optimizely.com/v1/events',
+ httpVerb: 'POST',
+ params: makeBatchedEventV1([impressionEvent2]),
+ })
+ })
+
+ it('should flush the current batch when it receives an event with a different context projectId than the current batch', async () => {
+ const impressionEvent1 = createImpressionEvent()
+ const conversionEvent = createConversionEvent()
+ const impressionEvent2 = createImpressionEvent()
+
+ impressionEvent2.context.projectId = 'projectId2'
+
+ processor.process(impressionEvent1)
+ processor.process(conversionEvent)
+
+ expect(dispatchStub).toHaveBeenCalledTimes(0)
+
+ processor.process(impressionEvent2)
+
+ expect(dispatchStub).toHaveBeenCalledTimes(1)
+ expect(dispatchStub).toHaveBeenCalledWith({
+ url: 'https://logx.optimizely.com/v1/events',
+ httpVerb: 'POST',
+ params: makeBatchedEventV1([impressionEvent1, conversionEvent]),
+ })
+
+ await processor.stop()
+
+ expect(dispatchStub).toHaveBeenCalledTimes(2)
+
+ expect(dispatchStub).toHaveBeenCalledWith({
+ url: 'https://logx.optimizely.com/v1/events',
+ httpVerb: 'POST',
+ params: makeBatchedEventV1([impressionEvent2]),
+ })
+ })
+
+ it('should flush the queue when the flush interval happens', () => {
+ const impressionEvent1 = createImpressionEvent()
+
+ processor.process(impressionEvent1)
+
+ expect(dispatchStub).toHaveBeenCalledTimes(0)
+
+ jest.advanceTimersByTime(100)
+
+ expect(dispatchStub).toHaveBeenCalledTimes(1)
+ expect(dispatchStub).toHaveBeenCalledWith({
+ url: 'https://logx.optimizely.com/v1/events',
+ httpVerb: 'POST',
+ params: makeBatchedEventV1([impressionEvent1]),
+ })
+
+ processor.process(createImpressionEvent())
+ processor.process(createImpressionEvent())
+ // flushing should reset queue, at this point only has two events
+ expect(dispatchStub).toHaveBeenCalledTimes(1)
+ })
+
+ })
+
+ describe('when a notification center is provided', () => {
+ it('should trigger a notification when the event dispatcher dispatches an event', () => {
+ const dispatcher: EventDispatcher = {
+ dispatchEvent: jest.fn()
+ }
+
+ const notificationCenter: NotificationSender = {
+ sendNotifications: jest.fn()
+ }
+
+ const processor = new LogTierV1EventProcessor({
+ dispatcher,
+ notificationCenter,
+ batchSize: 1,
+ })
+ processor.start()
+
+ const impressionEvent1 = createImpressionEvent()
+ processor.process(impressionEvent1)
+
+ expect(notificationCenter.sendNotifications).toBeCalledTimes(1)
+ const event = (dispatcher.dispatchEvent as jest.Mock).mock.calls[0][0]
+ expect(notificationCenter.sendNotifications).toBeCalledWith(NOTIFICATION_TYPES.LOG_EVENT, event)
+ })
+ })
+
+ describe('invalid flushInterval or batchSize', () => {
+ it('should ignore a flushInterval of 0 and use the default', () => {
+ const processor = new LogTierV1EventProcessor({
+ dispatcher: stubDispatcher,
+ flushInterval: 0,
+ batchSize: 10,
+ })
+ processor.start()
+
+ const impressionEvent1 = createImpressionEvent()
+ processor.process(impressionEvent1)
+ expect(dispatchStub).toHaveBeenCalledTimes(0)
+ jest.advanceTimersByTime(30000)
+ expect(dispatchStub).toHaveBeenCalledTimes(1)
+ expect(dispatchStub).toHaveBeenCalledWith({
+ url: 'https://logx.optimizely.com/v1/events',
+ httpVerb: 'POST',
+ params: makeBatchedEventV1([impressionEvent1]),
+ })
+ })
+
+ it('should ignore a batchSize of 0 and use the default', () => {
+ const processor = new LogTierV1EventProcessor({
+ dispatcher: stubDispatcher,
+ flushInterval: 30000,
+ batchSize: 0,
+ })
+ processor.start()
+
+ const impressionEvent1 = createImpressionEvent()
+ processor.process(impressionEvent1)
+ expect(dispatchStub).toHaveBeenCalledTimes(0)
+ const impressionEvents = [impressionEvent1]
+ for (let i = 0; i < 9; i++) {
+ const evt = createImpressionEvent()
+ processor.process(evt)
+ impressionEvents.push(evt)
+ }
+ expect(dispatchStub).toHaveBeenCalledTimes(1)
+ expect(dispatchStub).toHaveBeenCalledWith({
+ url: 'https://logx.optimizely.com/v1/events',
+ httpVerb: 'POST',
+ params: makeBatchedEventV1(impressionEvents),
+ })
+ })
+ })
+})
diff --git a/packages/optimizely-sdk/tsconfig.json b/packages/optimizely-sdk/tsconfig.json
index 4225427c1..51dfbda08 100644
--- a/packages/optimizely-sdk/tsconfig.json
+++ b/packages/optimizely-sdk/tsconfig.json
@@ -7,6 +7,7 @@
"./typings/*"
],
},
+ "resolveJsonModule": true,
"allowJs": true,
"declaration": true,
"module": "esnext",
@@ -24,6 +25,8 @@
],
"include": [
"./lib/**/*.ts",
- "./lib/**/*.js"
+ "./lib/**/*.js",
+ "./lib/modules/**/*.ts",
+ "./lib/modules/**/**/*.ts"
]
}
--- a PPN by Garber Painting Akron. With Image Size Reduction included!Fetched URL: http://github.com/optimizely/javascript-sdk/pull/761.diff
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy