diff --git a/.eslintignore b/.eslintignore index 19aad5c..c16bd21 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,5 +4,5 @@ dist/ lib/ node_modules/ src/Semver.ts -src/Sha1.ts +src/Hash.ts src/lib.*.d.ts \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 190fbba..27b00c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@types/chai": "4.3.4", "@types/mocha": "^10.0.1", "@types/node": "^18.11.18", + "@types/tunnel": "^0.0.5", "@typescript-eslint/eslint-plugin": "^5.53.0", "@typescript-eslint/parser": "^5.53.0", "chai": "^4.3.7", @@ -23,285 +24,194 @@ "eslint-plugin-import": "^2.27.5", "mocha": "^10.2.0", "moq.ts": "^7.4.1", - "nyc": "^15.1.0", + "nyc": "^14.1.1", "source-map-support": "^0.5.21", "ts-node": "^10.9.1", + "tunnel": "^0.0.6", "typescript": "^4.0.2" } }, "node_modules/@babel/code-frame": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", - "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "dependencies": { - "@babel/highlight": "^7.16.0" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/compat-data": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.0.tgz", - "integrity": "sha512-DGjt2QZse5SGd9nfOSqO4WLJ8NN/oHkijbXbPrxuoJO3oIPJL3TciZs9FX+cOHNiY9E9l0opL8g7BmLe3T+9ew==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.0.tgz", - "integrity": "sha512-mYZEvshBRHGsIAiyH5PzCFTCfbWfoYbO/jcSdXQSUQu1/pW0xDZAUP7KEc32heqWTAfAHhV9j1vH8Sav7l+JNQ==", + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.16.0", - "@babel/generator": "^7.16.0", - "@babel/helper-compilation-targets": "^7.16.0", - "@babel/helper-module-transforms": "^7.16.0", - "@babel/helpers": "^7.16.0", - "@babel/parser": "^7.16.0", - "@babel/template": "^7.16.0", - "@babel/traverse": "^7.16.0", - "@babel/types": "^7.16.0", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" + "color-convert": "^1.9.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, - "node_modules/@babel/generator": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.0.tgz", - "integrity": "sha512-RR8hUCfRQn9j9RPKEVXo9LiwoxLPYn6hNZlvUOR8tSnaxlD0p0+la00ZP9/SnRt6HchKr+X0fO2r8vrETiJGew==", + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "dependencies": { - "@babel/types": "^7.16.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" }, "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/generator/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.0.tgz", - "integrity": "sha512-S7iaOT1SYlqK0sQaCi21RX4+13hmdmnxIEAnQUB/eh7GeAnRjOUgTYpLkUOiRXzD+yog1JxP0qyAQZ7ZxVxLVg==", + "node_modules/@babel/code-frame/node_modules/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==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.16.0", - "@babel/helper-validator-option": "^7.14.5", - "browserslist": "^4.16.6", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "color-name": "1.1.3" } }, - "node_modules/@babel/helper-function-name": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz", - "integrity": "sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog==", - "dev": true, - "dependencies": { - "@babel/helper-get-function-arity": "^7.16.0", - "@babel/template": "^7.16.0", - "@babel/types": "^7.16.0" - }, - "engines": { - "node": ">=6.9.0" - } + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true }, - "node_modules/@babel/helper-get-function-arity": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz", - "integrity": "sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ==", + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, - "dependencies": { - "@babel/types": "^7.16.0" - }, "engines": { - "node": ">=6.9.0" + "node": ">=0.8.0" } }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz", - "integrity": "sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg==", + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, - "dependencies": { - "@babel/types": "^7.16.0" - }, "engines": { - "node": ">=6.9.0" + "node": ">=4" } }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.0.tgz", - "integrity": "sha512-bsjlBFPuWT6IWhl28EdrQ+gTvSvj5tqVP5Xeftp07SEuz5pLnsXZuDkDD3Rfcxy0IsHmbZ+7B2/9SHzxO0T+sQ==", + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "dependencies": { - "@babel/types": "^7.16.0" + "has-flag": "^3.0.0" }, "engines": { - "node": ">=6.9.0" + "node": ">=4" } }, - "node_modules/@babel/helper-module-imports": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz", - "integrity": "sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg==", + "node_modules/@babel/generator": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "dependencies": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.23.0", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.0.tgz", - "integrity": "sha512-My4cr9ATcaBbmaEa8M0dZNA74cfI6gitvUAskgDtAFmAqyFKDSHQo5YstxPbN+lzHl2D9l/YOEFqb2mtUh4gfA==", + "node_modules/@babel/generator/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", "dev": true, "dependencies": { - "@babel/helper-module-imports": "^7.16.0", - "@babel/helper-replace-supers": "^7.16.0", - "@babel/helper-simple-access": "^7.16.0", - "@babel/helper-split-export-declaration": "^7.16.0", - "@babel/helper-validator-identifier": "^7.15.7", - "@babel/template": "^7.16.0", - "@babel/traverse": "^7.16.0", - "@babel/types": "^7.16.0" - }, - "engines": { - "node": ">=6.9.0" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.0.tgz", - "integrity": "sha512-SuI467Gi2V8fkofm2JPnZzB/SUuXoJA5zXe/xzyPP2M04686RzFKFHPK6HDVN6JvWBIEW8tt9hPR7fXdn2Lgpw==", + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, - "dependencies": { - "@babel/types": "^7.16.0" - }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.0.tgz", - "integrity": "sha512-TQxuQfSCdoha7cpRNJvfaYxxxzmbxXw/+6cS7V02eeDYyhxderSoMVALvwupA54/pZcOTtVeJ0xccp1nGWladA==", + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.16.0", - "@babel/helper-optimise-call-expression": "^7.16.0", - "@babel/traverse": "^7.16.0", - "@babel/types": "^7.16.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-simple-access": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.0.tgz", - "integrity": "sha512-o1rjBT/gppAqKsYfUdfHq5Rk03lMQrkPHG1OWzHWpLgVXRH4HnMM9Et9CVdIqwkCQlobnGHEJMsgWP/jE1zUiw==", + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "dependencies": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz", - "integrity": "sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "dependencies": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", - "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", + "node_modules/@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helpers": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.0.tgz", - "integrity": "sha512-dVRM0StFMdKlkt7cVcGgwD8UMaBfWJHl3A83Yfs8GQ3MO0LHIIIMvK7Fa0RGOGUQ10qikLaX6D7o5htcQWgTMQ==", + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, - "dependencies": { - "@babel/template": "^7.16.0", - "@babel/traverse": "^7.16.0", - "@babel/types": "^7.16.0" - }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", - "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.15.7", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -346,13 +256,13 @@ "node_modules/@babel/highlight/node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, "node_modules/@babel/highlight/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, "engines": { "node": ">=0.8.0" @@ -361,7 +271,7 @@ "node_modules/@babel/highlight/node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, "engines": { "node": ">=4" @@ -380,9 +290,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.16.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.2.tgz", - "integrity": "sha512-RUVpT0G2h6rOZwqLDTrKk7ksNv7YpAilTnYe1/Q+eDjxEceRMKVWbCsX7t8h6C1qCFi/1Y8WZjcEPBAFG27GPw==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -392,32 +302,33 @@ } }, "node_modules/@babel/template": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.0.tgz", - "integrity": "sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.16.0", - "@babel/parser": "^7.16.0", - "@babel/types": "^7.16.0" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.0.tgz", - "integrity": "sha512-qQ84jIs1aRQxaGaxSysII9TuDaguZ5yVrEuC0BN2vcPlalwfLovVmCjbFDPECPXcYM/wLvNFfp8uDOliLxIoUQ==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.16.0", - "@babel/generator": "^7.16.0", - "@babel/helper-function-name": "^7.16.0", - "@babel/helper-hoist-variables": "^7.16.0", - "@babel/helper-split-export-declaration": "^7.16.0", - "@babel/parser": "^7.16.0", - "@babel/types": "^7.16.0", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -426,12 +337,13 @@ } }, "node_modules/@babel/types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz", - "integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.15.7", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -533,103 +445,18 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", "dev": true, "dependencies": { - "p-limit": "^2.2.0" + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" }, "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "engines": { - "node": ">=8" + "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -641,6 +468,15 @@ "node": ">=6.0.0" } }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", @@ -752,6 +588,15 @@ "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", "dev": true }, + "node_modules/@types/tunnel": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@types/tunnel/-/tunnel-0.0.5.tgz", + "integrity": "sha512-XGvAonhX1CeULtu4BTzy5nrsOWbxE2SzOBO1c+T16y4gyNnL7EPX7O5cjISdL2hSxXcuTXVieLJa+rSsKxGp+g==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.53.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.53.0.tgz", @@ -1015,19 +860,6 @@ "node": ">=0.4.0" } }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -1069,21 +901,21 @@ } }, "node_modules/append-transform": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", - "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", + "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", "dev": true, "dependencies": { - "default-require-extensions": "^3.0.0" + "default-require-extensions": "^2.0.0" }, "engines": { - "node": ">=8" + "node": ">=4" } }, "node_modules/archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", "dev": true }, "node_modules/arg": { @@ -1217,29 +1049,6 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, - "node_modules/browserslist": { - "version": "4.17.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.6.tgz", - "integrity": "sha512-uPgz3vyRTlEiCv4ee9KlsKgo2V6qPk7Jsn0KAn2OBqbqKo3iNcPEC1Ti6J4dwnz+aIRfEEEuOzC9IBk8tXUomw==", - "dev": true, - "dependencies": { - "caniuse-lite": "^1.0.30001274", - "electron-to-chromium": "^1.3.886", - "escalade": "^3.1.1", - "node-releases": "^2.0.1", - "picocolors": "^1.0.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } - }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -1247,18 +1056,18 @@ "dev": true }, "node_modules/caching-transform": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", - "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-3.0.2.tgz", + "integrity": "sha512-Mtgcv3lh3U0zRii/6qVgQODdPA4G3zhG+jtbCWj39RXuUFTMzH0vcdMtaJS1jPowd+It2Pqr6y3NJMQqOqCE2w==", "dev": true, "dependencies": { - "hasha": "^5.0.0", - "make-dir": "^3.0.0", - "package-hash": "^4.0.0", - "write-file-atomic": "^3.0.0" + "hasha": "^3.0.0", + "make-dir": "^2.0.0", + "package-hash": "^3.0.0", + "write-file-atomic": "^2.4.2" }, "engines": { - "node": ">=8" + "node": ">=6" } }, "node_modules/call-bind": { @@ -1292,16 +1101,6 @@ "node": ">=6" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001277", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001277.tgz", - "integrity": "sha512-J2WtYj2Pl6LBEG214XmbGw1gzZEsYuinQFPqYtpZDB3/vm49qNlrcbJrTMkHKmdRDdmXYwkG0tgOBJsuI+J12Q==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } - }, "node_modules/chai": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", @@ -1357,11 +1156,34 @@ "node": "*" } }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "node_modules/cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "dependencies": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, "engines": { "node": ">=6" } @@ -1387,7 +1209,7 @@ "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", "dev": true }, "node_modules/concat-map": { @@ -1397,12 +1219,25 @@ "dev": true }, "node_modules/convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/cp-file": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-6.2.0.tgz", + "integrity": "sha512-fmvV4caBnofhPe8kOcitBwSn2f39QLjnAnGq3gO9dfd75mUytzKNZB1hde6QHunW2Rt+OwuBOMc3i1tNElbszA==", "dev": true, "dependencies": { - "safe-buffer": "~5.1.1" + "graceful-fs": "^4.1.2", + "make-dir": "^2.0.0", + "nested-error-stacks": "^2.0.0", + "pify": "^4.0.1", + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": ">=6" } }, "node_modules/create-require": { @@ -1463,7 +1298,7 @@ "node_modules/decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -1488,15 +1323,15 @@ "dev": true }, "node_modules/default-require-extensions": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", - "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", + "integrity": "sha512-B0n2zDIXpzLzKeoEozorDSa1cHc1t0NjmxP0zuAxbizNU2MBqYJJKYXrrFdKuQliojXynrxgd7l4ahfg/+aA5g==", "dev": true, "dependencies": { - "strip-bom": "^4.0.0" + "strip-bom": "^3.0.0" }, "engines": { - "node": ">=8" + "node": ">=4" } }, "node_modules/define-properties": { @@ -1548,18 +1383,21 @@ "node": ">=6.0.0" } }, - "node_modules/electron-to-chromium": { - "version": "1.3.889", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.889.tgz", - "integrity": "sha512-suEUoPTD1mExjL9TdmH7cvEiWJVM2oEiAi+Y1p0QKxI2HcRlT44qDTP2c1aZmVwRemIPYOpxmV7CxQCOWcm4XQ==", - "dev": true - }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/es-abstract": { "version": "1.21.1", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.1.tgz", @@ -2094,20 +1932,17 @@ } }, "node_modules/find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", "dev": true, "dependencies": { "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + "node": ">=6" } }, "node_modules/find-up": { @@ -2164,37 +1999,52 @@ } }, "node_modules/foreground-child": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", - "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-1.5.6.tgz", + "integrity": "sha512-3TOY+4TKV0Ml83PXJQY+JFQaHNV38lzQDIzzXYg1kWdBLenGgoZhAs0CKgzI31vi2pWEpQMq/Yi4bpKwCPkw7g==", "dev": true, "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8.0.0" + "cross-spawn": "^4", + "signal-exit": "^3.0.0" } }, - "node_modules/fromentries": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", - "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "node_modules/foreground-child/node_modules/cross-spawn": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", + "integrity": "sha512-yAXz/pA1tD8Gtg2S98Ekf/sewp3Lcp3YoFKJ4Hkp5h5yLWnKVTDU0kwjKJ8NDCYcfTLfyGkzTikst+jWypT1iA==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "dependencies": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + }, + "node_modules/foreground-child/node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/foreground-child/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/foreground-child/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true }, "node_modules/fs.realpath": { "version": "1.0.0", @@ -2235,13 +2085,13 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, "engines": { - "node": ">=6.9.0" + "node": "6.* || 8.* || >= 10.*" } }, "node_modules/get-func-name": { @@ -2267,15 +2117,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/get-symbol-description": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", @@ -2381,9 +2222,9 @@ } }, "node_modules/graceful-fs": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, "node_modules/grapheme-splitter": { @@ -2474,19 +2315,15 @@ } }, "node_modules/hasha": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", - "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-3.0.0.tgz", + "integrity": "sha512-w0Kz8lJFBoyaurBiNrIvxPqr/gJ6fOfSkpAPOepN3oECqGJag37xPbOv57izi/KP8auHgNYxn5fXtAb+1LsJ6w==", "dev": true, "dependencies": { - "is-stream": "^2.0.0", - "type-fest": "^0.8.0" + "is-stream": "^1.0.1" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4" } }, "node_modules/he": { @@ -2498,6 +2335,12 @@ "he": "bin/he" } }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -2529,15 +2372,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-fresh/node_modules/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==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -2547,15 +2381,6 @@ "node": ">=0.8.19" } }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -2600,6 +2425,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, "node_modules/is-bigint": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", @@ -2676,6 +2507,15 @@ "node": ">=0.10.0" } }, + "node_modules/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": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -2771,15 +2611,12 @@ } }, "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", "dev": true, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" } }, "node_modules/is-string": { @@ -2831,12 +2668,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -2861,15 +2692,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2877,119 +2699,117 @@ "dev": true }, "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", "dev": true, "engines": { - "node": ">=8" + "node": ">=6" } }, "node_modules/istanbul-lib-hook": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", - "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz", + "integrity": "sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA==", "dev": true, "dependencies": { - "append-transform": "^2.0.0" + "append-transform": "^1.0.0" }, "engines": { - "node": ">=8" + "node": ">=6" } }, "node_modules/istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", + "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", "dev": true, "dependencies": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" + "@babel/generator": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0", + "istanbul-lib-coverage": "^2.0.5", + "semver": "^6.0.0" }, "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/istanbul-lib-processinfo": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz", - "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==", + "node_modules/istanbul-lib-report": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", + "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", "dev": true, "dependencies": { - "archy": "^1.0.0", - "cross-spawn": "^7.0.0", - "istanbul-lib-coverage": "^3.0.0-alpha.1", - "make-dir": "^3.0.0", - "p-map": "^3.0.0", - "rimraf": "^3.0.0", - "uuid": "^3.3.3" + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "supports-color": "^6.1.0" }, "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/istanbul-lib-report": { + "node_modules/istanbul-lib-report/node_modules/has-flag": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=8" + "node": ">=4" } }, "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "has-flag": "^3.0.0" }, "engines": { - "node": ">=8" + "node": ">=6" } }, "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", + "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", "dev": true, "dependencies": { "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "rimraf": "^2.6.3", "source-map": "^0.6.1" }, "engines": { - "node": ">=10" + "node": ">=6" } }, - "node_modules/istanbul-lib-source-maps/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "node_modules/istanbul-lib-source-maps/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "dev": true, - "engines": { - "node": ">=0.10.0" + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" } }, "node_modules/istanbul-reports": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.5.tgz", - "integrity": "sha512-5+19PlhnGabNWB7kOFnuxT8H3T/iIyQzIbQMxXsURmmvKg86P2sbkrGOT77VnHw0Qr0gc2XzRaRfMZYYbSQCJQ==", + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.7.tgz", + "integrity": "sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg==", "dev": true, "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" + "html-escaper": "^2.0.0" }, "engines": { - "node": ">=8" + "node": ">=6" } }, "node_modules/js-sdsl": { @@ -3032,6 +2852,12 @@ "node": ">=4" } }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -3044,18 +2870,6 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -3069,6 +2883,30 @@ "node": ">= 0.8.0" } }, + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/load-json-file/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -3087,7 +2925,7 @@ "node_modules/lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", "dev": true }, "node_modules/lodash.merge": { @@ -3134,18 +2972,25 @@ } }, "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", "dev": true, "dependencies": { - "semver": "^6.0.0" + "pify": "^4.0.1", + "semver": "^5.6.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" } }, "node_modules/make-error": { @@ -3154,6 +2999,15 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, + "node_modules/merge-source-map": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", + "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", + "dev": true, + "dependencies": { + "source-map": "^0.6.1" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3197,6 +3051,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/mocha": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", @@ -3329,15 +3195,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/mocha/node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, "node_modules/mocha/node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -3513,23 +3370,32 @@ "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", "dev": true }, - "node_modules/node-preload": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", - "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "node_modules/nested-error-stacks": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.1.tgz", + "integrity": "sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw==", + "dev": true + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "dependencies": { - "process-on-spawn": "^1.0.0" - }, - "engines": { - "node": ">=8" + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" } }, - "node_modules/node-releases": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", - "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", - "dev": true + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } }, "node_modules/normalize-path": { "version": "3.0.0", @@ -3541,98 +3407,89 @@ } }, "node_modules/nyc": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", - "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", - "dev": true, - "dependencies": { - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "caching-transform": "^4.0.0", - "convert-source-map": "^1.7.0", - "decamelize": "^1.2.0", - "find-cache-dir": "^3.2.0", - "find-up": "^4.1.0", - "foreground-child": "^2.0.0", - "get-package-type": "^0.1.0", - "glob": "^7.1.6", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-hook": "^3.0.0", - "istanbul-lib-instrument": "^4.0.0", - "istanbul-lib-processinfo": "^2.0.2", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "make-dir": "^3.0.0", - "node-preload": "^0.2.1", - "p-map": "^3.0.0", - "process-on-spawn": "^1.0.0", - "resolve-from": "^5.0.0", - "rimraf": "^3.0.0", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-14.1.1.tgz", + "integrity": "sha512-OI0vm6ZGUnoGZv/tLdZ2esSVzDwUC88SNs+6JoSOMVxA+gKMB8Tk7jBwgemLx4O40lhhvZCVw1C+OYLOBOPXWw==", + "dev": true, + "dependencies": { + "archy": "^1.0.0", + "caching-transform": "^3.0.2", + "convert-source-map": "^1.6.0", + "cp-file": "^6.2.0", + "find-cache-dir": "^2.1.0", + "find-up": "^3.0.0", + "foreground-child": "^1.5.6", + "glob": "^7.1.3", + "istanbul-lib-coverage": "^2.0.5", + "istanbul-lib-hook": "^2.0.7", + "istanbul-lib-instrument": "^3.3.0", + "istanbul-lib-report": "^2.0.8", + "istanbul-lib-source-maps": "^3.0.6", + "istanbul-reports": "^2.2.4", + "js-yaml": "^3.13.1", + "make-dir": "^2.1.0", + "merge-source-map": "^1.1.0", + "resolve-from": "^4.0.0", + "rimraf": "^2.6.3", "signal-exit": "^3.0.2", - "spawn-wrap": "^2.0.0", - "test-exclude": "^6.0.0", - "yargs": "^15.0.2" + "spawn-wrap": "^1.4.2", + "test-exclude": "^5.2.3", + "uuid": "^3.3.2", + "yargs": "^13.2.2", + "yargs-parser": "^13.0.0" }, "bin": { "nyc": "bin/nyc.js" }, "engines": { - "node": ">=8.9" + "node": ">=6" } }, - "node_modules/nyc/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "node_modules/nyc/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" + "sprintf-js": "~1.0.2" } }, "node_modules/nyc/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "locate-path": "^3.0.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": ">=6" } }, - "node_modules/nyc/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/nyc/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, - "engines": { - "node": ">=8" + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, "node_modules/nyc/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "dependencies": { - "p-locate": "^4.1.0" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" }, "engines": { - "node": ">=8" + "node": ">=6" } }, "node_modules/nyc/node_modules/p-limit": { @@ -3651,96 +3508,36 @@ } }, "node_modules/nyc/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "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==", "dev": true, "dependencies": { - "p-limit": "^2.2.0" + "p-limit": "^2.0.0" }, "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/nyc/node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true + "node_modules/nyc/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "engines": { + "node": ">=4" + } }, - "node_modules/nyc/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/nyc/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "dev": true, "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "node_modules/nyc/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "node_modules/nyc/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "glob": "^7.1.3" }, - "engines": { - "node": ">=6" + "bin": { + "rimraf": "bin.js" } }, "node_modules/object-inspect": { @@ -3822,6 +3619,15 @@ "node": ">= 0.8.0" } }, + "node_modules/os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -3852,18 +3658,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -3874,18 +3668,18 @@ } }, "node_modules/package-hash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", - "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-3.0.0.tgz", + "integrity": "sha512-lOtmukMDVvtkL84rJHI7dpTYq+0rli8N2wlnqUcBuDWCfVhRUfOmnR9SsoHFMLpACvEV60dX7rd0rFaYDZI+FA==", "dev": true, "dependencies": { "graceful-fs": "^4.1.15", - "hasha": "^5.0.0", + "hasha": "^3.0.0", "lodash.flattendeep": "^4.4.0", "release-zalgo": "^1.0.0" }, "engines": { - "node": ">=8" + "node": ">=6" } }, "node_modules/parent-module": { @@ -3900,6 +3694,19 @@ "node": ">=6" } }, + "node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3951,12 +3758,6 @@ "node": "*" } }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -3969,41 +3770,50 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", "dev": true, "dependencies": { - "find-up": "^4.0.0" + "find-up": "^3.0.0" }, "engines": { - "node": ">=8" + "node": ">=6" } }, "node_modules/pkg-dir/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "locate-path": "^3.0.0" }, "engines": { - "node": ">=8" + "node": ">=6" } }, "node_modules/pkg-dir/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "dependencies": { - "p-locate": "^4.1.0" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" }, "engines": { - "node": ">=8" + "node": ">=6" } }, "node_modules/pkg-dir/node_modules/p-limit": { @@ -4022,15 +3832,24 @@ } }, "node_modules/pkg-dir/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "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==", "dev": true, "dependencies": { - "p-limit": "^2.2.0" + "p-limit": "^2.0.0" }, "engines": { - "node": ">=8" + "node": ">=6" + } + }, + "node_modules/pkg-dir/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "engines": { + "node": ">=4" } }, "node_modules/prelude-ls": { @@ -4042,17 +3861,11 @@ "node": ">= 0.8.0" } }, - "node_modules/process-on-spawn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", - "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", - "dev": true, - "dependencies": { - "fromentries": "^1.2.0" - }, - "engines": { - "node": ">=8" - } + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "dev": true }, "node_modules/punycode": { "version": "2.3.0", @@ -4092,6 +3905,115 @@ "safe-buffer": "^5.1.0" } }, + "node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", + "dev": true, + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg-up": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", + "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "dev": true, + "dependencies": { + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/read-pkg-up/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/read-pkg-up/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/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==", + "dev": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/read-pkg-up/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg/node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/regexp.prototype.flags": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", @@ -4124,7 +4046,7 @@ "node_modules/release-zalgo": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", "dev": true, "dependencies": { "es6-error": "^4.0.1" @@ -4142,6 +4064,12 @@ "node": ">=0.10.0" } }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, "node_modules/resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -4160,12 +4088,12 @@ } }, "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, "engines": { - "node": ">=8" + "node": ">=4" } }, "node_modules/reusify": { @@ -4257,7 +4185,7 @@ "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true }, "node_modules/shebang-command": { @@ -4296,9 +4224,9 @@ } }, "node_modules/signal-exit": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz", - "integrity": "sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, "node_modules/slash": { @@ -4310,6 +4238,15 @@ "node": ">=8" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", @@ -4320,85 +4257,170 @@ "source-map": "^0.6.0" } }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/spawn-wrap": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", - "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-1.4.3.tgz", + "integrity": "sha512-IgB8md0QW/+tWqcavuFgKYR/qIRvJkRLPJDFaoXtLLUaVcCDK0+HeFTkmQHj3eprcYhc+gOl0aEA1w7qZlYezw==", "dev": true, "dependencies": { - "foreground-child": "^2.0.0", - "is-windows": "^1.0.2", - "make-dir": "^3.0.0", - "rimraf": "^3.0.0", + "foreground-child": "^1.5.6", + "mkdirp": "^0.5.0", + "os-homedir": "^1.0.1", + "rimraf": "^2.6.2", "signal-exit": "^3.0.2", - "which": "^2.0.1" - }, - "engines": { - "node": ">=8" + "which": "^1.3.0" } }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", - "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "node_modules/spawn-wrap/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "glob": "^7.1.3" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "bin": { + "rimraf": "bin.js" } }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", - "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "node_modules/spawn-wrap/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "isexe": "^2.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "bin": { + "which": "bin/which" } }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", "dev": true, "dependencies": { - "ansi-regex": "^5.0.1" + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.16", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz", + "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==", + "dev": true + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/string-width/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" }, "engines": { "node": ">=8" } }, "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, "engines": { - "node": ">=8" + "node": ">=4" } }, "node_modules/strip-json-comments": { @@ -4441,17 +4463,18 @@ } }, "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz", + "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==", "dev": true, "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" + "glob": "^7.1.3", + "minimatch": "^3.0.4", + "read-pkg-up": "^4.0.0", + "require-main-filename": "^2.0.0" }, "engines": { - "node": ">=8" + "node": ">=6" } }, "node_modules/text-table": { @@ -4463,7 +4486,7 @@ "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", "dev": true, "engines": { "node": ">=4" @@ -4557,15 +4580,6 @@ "json5": "lib/cli.js" } }, - "node_modules/tsconfig-paths/node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/tslib": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", @@ -4592,6 +4606,15 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "dev": true, + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -4613,15 +4636,6 @@ "node": ">=4" } }, - "node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/typed-array-length": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", @@ -4636,15 +4650,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, "node_modules/typescript": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz", @@ -4698,6 +4703,16 @@ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true }, + "node_modules/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==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -4729,6 +4744,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "dev": true + }, "node_modules/which-typed-array": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", @@ -4764,6 +4785,68 @@ "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", "dev": true }, + "node_modules/wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/wrap-ansi/node_modules/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==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -4771,23 +4854,56 @@ "dev": true }, "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", "dev": true, "dependencies": { + "graceful-fs": "^4.1.11", "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" + "signal-exit": "^3.0.2" } }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "dependencies": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "node_modules/yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, "node_modules/yargs-unparser": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", @@ -4827,6 +4943,67 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yargs/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs/node_modules/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==", + "dev": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", @@ -4851,215 +5028,151 @@ }, "dependencies": { "@babel/code-frame": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", - "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==", - "dev": true, - "requires": { - "@babel/highlight": "^7.16.0" - } - }, - "@babel/compat-data": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.0.tgz", - "integrity": "sha512-DGjt2QZse5SGd9nfOSqO4WLJ8NN/oHkijbXbPrxuoJO3oIPJL3TciZs9FX+cOHNiY9E9l0opL8g7BmLe3T+9ew==", - "dev": true - }, - "@babel/core": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.0.tgz", - "integrity": "sha512-mYZEvshBRHGsIAiyH5PzCFTCfbWfoYbO/jcSdXQSUQu1/pW0xDZAUP7KEc32heqWTAfAHhV9j1vH8Sav7l+JNQ==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "requires": { - "@babel/code-frame": "^7.16.0", - "@babel/generator": "^7.16.0", - "@babel/helper-compilation-targets": "^7.16.0", - "@babel/helper-module-transforms": "^7.16.0", - "@babel/helpers": "^7.16.0", - "@babel/parser": "^7.16.0", - "@babel/template": "^7.16.0", - "@babel/traverse": "^7.16.0", - "@babel/types": "^7.16.0", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" }, "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.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==", + "dev": true, + "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": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, "@babel/generator": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.0.tgz", - "integrity": "sha512-RR8hUCfRQn9j9RPKEVXo9LiwoxLPYn6hNZlvUOR8tSnaxlD0p0+la00ZP9/SnRt6HchKr+X0fO2r8vrETiJGew==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "requires": { - "@babel/types": "^7.16.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" + "@babel/types": "^7.23.0", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" }, "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true + "@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } } } }, - "@babel/helper-compilation-targets": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.0.tgz", - "integrity": "sha512-S7iaOT1SYlqK0sQaCi21RX4+13hmdmnxIEAnQUB/eh7GeAnRjOUgTYpLkUOiRXzD+yog1JxP0qyAQZ7ZxVxLVg==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.16.0", - "@babel/helper-validator-option": "^7.14.5", - "browserslist": "^4.16.6", - "semver": "^6.3.0" - } + "@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true }, "@babel/helper-function-name": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz", - "integrity": "sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.16.0", - "@babel/template": "^7.16.0", - "@babel/types": "^7.16.0" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz", - "integrity": "sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "requires": { - "@babel/types": "^7.16.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" } }, "@babel/helper-hoist-variables": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz", - "integrity": "sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg==", - "dev": true, - "requires": { - "@babel/types": "^7.16.0" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.0.tgz", - "integrity": "sha512-bsjlBFPuWT6IWhl28EdrQ+gTvSvj5tqVP5Xeftp07SEuz5pLnsXZuDkDD3Rfcxy0IsHmbZ+7B2/9SHzxO0T+sQ==", - "dev": true, - "requires": { - "@babel/types": "^7.16.0" - } - }, - "@babel/helper-module-imports": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz", - "integrity": "sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "requires": { - "@babel/types": "^7.16.0" - } - }, - "@babel/helper-module-transforms": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.0.tgz", - "integrity": "sha512-My4cr9ATcaBbmaEa8M0dZNA74cfI6gitvUAskgDtAFmAqyFKDSHQo5YstxPbN+lzHl2D9l/YOEFqb2mtUh4gfA==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.16.0", - "@babel/helper-replace-supers": "^7.16.0", - "@babel/helper-simple-access": "^7.16.0", - "@babel/helper-split-export-declaration": "^7.16.0", - "@babel/helper-validator-identifier": "^7.15.7", - "@babel/template": "^7.16.0", - "@babel/traverse": "^7.16.0", - "@babel/types": "^7.16.0" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.0.tgz", - "integrity": "sha512-SuI467Gi2V8fkofm2JPnZzB/SUuXoJA5zXe/xzyPP2M04686RzFKFHPK6HDVN6JvWBIEW8tt9hPR7fXdn2Lgpw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.0" - } - }, - "@babel/helper-replace-supers": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.0.tgz", - "integrity": "sha512-TQxuQfSCdoha7cpRNJvfaYxxxzmbxXw/+6cS7V02eeDYyhxderSoMVALvwupA54/pZcOTtVeJ0xccp1nGWladA==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.16.0", - "@babel/helper-optimise-call-expression": "^7.16.0", - "@babel/traverse": "^7.16.0", - "@babel/types": "^7.16.0" - } - }, - "@babel/helper-simple-access": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.0.tgz", - "integrity": "sha512-o1rjBT/gppAqKsYfUdfHq5Rk03lMQrkPHG1OWzHWpLgVXRH4HnMM9Et9CVdIqwkCQlobnGHEJMsgWP/jE1zUiw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.22.5" } }, "@babel/helper-split-export-declaration": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz", - "integrity": "sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "requires": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.22.5" } }, - "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true }, - "@babel/helper-validator-option": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", - "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", + "@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true }, - "@babel/helpers": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.0.tgz", - "integrity": "sha512-dVRM0StFMdKlkt7cVcGgwD8UMaBfWJHl3A83Yfs8GQ3MO0LHIIIMvK7Fa0RGOGUQ10qikLaX6D7o5htcQWgTMQ==", - "dev": true, - "requires": { - "@babel/template": "^7.16.0", - "@babel/traverse": "^7.16.0", - "@babel/types": "^7.16.0" - } - }, "@babel/highlight": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", - "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.15.7", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "dependencies": { @@ -5095,19 +5208,19 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true }, "supports-color": { @@ -5122,46 +5235,48 @@ } }, "@babel/parser": { - "version": "7.16.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.2.tgz", - "integrity": "sha512-RUVpT0G2h6rOZwqLDTrKk7ksNv7YpAilTnYe1/Q+eDjxEceRMKVWbCsX7t8h6C1qCFi/1Y8WZjcEPBAFG27GPw==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true }, "@babel/template": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.0.tgz", - "integrity": "sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "requires": { - "@babel/code-frame": "^7.16.0", - "@babel/parser": "^7.16.0", - "@babel/types": "^7.16.0" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" } }, "@babel/traverse": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.0.tgz", - "integrity": "sha512-qQ84jIs1aRQxaGaxSysII9TuDaguZ5yVrEuC0BN2vcPlalwfLovVmCjbFDPECPXcYM/wLvNFfp8uDOliLxIoUQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.0", - "@babel/generator": "^7.16.0", - "@babel/helper-function-name": "^7.16.0", - "@babel/helper-hoist-variables": "^7.16.0", - "@babel/helper-split-export-declaration": "^7.16.0", - "@babel/parser": "^7.16.0", - "@babel/types": "^7.16.0", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz", - "integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.15.7", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" } }, @@ -5231,82 +5346,16 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, - "@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", "dev": true, "requires": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "dependencies": { - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - } - } - }, - "@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } }, "@jridgewell/resolve-uri": { "version": "3.1.0", @@ -5314,6 +5363,12 @@ "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", "dev": true }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true + }, "@jridgewell/sourcemap-codec": { "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", @@ -5416,6 +5471,15 @@ "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", "dev": true }, + "@types/tunnel": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@types/tunnel/-/tunnel-0.0.5.tgz", + "integrity": "sha512-XGvAonhX1CeULtu4BTzy5nrsOWbxE2SzOBO1c+T16y4gyNnL7EPX7O5cjISdL2hSxXcuTXVieLJa+rSsKxGp+g==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@typescript-eslint/eslint-plugin": { "version": "5.53.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.53.0.tgz", @@ -5567,16 +5631,6 @@ "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", "dev": true }, - "aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } - }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -5605,18 +5659,18 @@ } }, "append-transform": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", - "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", + "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", "dev": true, "requires": { - "default-require-extensions": "^3.0.0" + "default-require-extensions": "^2.0.0" } }, "archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", "dev": true }, "arg": { @@ -5717,19 +5771,6 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, - "browserslist": { - "version": "4.17.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.6.tgz", - "integrity": "sha512-uPgz3vyRTlEiCv4ee9KlsKgo2V6qPk7Jsn0KAn2OBqbqKo3iNcPEC1Ti6J4dwnz+aIRfEEEuOzC9IBk8tXUomw==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001274", - "electron-to-chromium": "^1.3.886", - "escalade": "^3.1.1", - "node-releases": "^2.0.1", - "picocolors": "^1.0.0" - } - }, "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -5737,15 +5778,15 @@ "dev": true }, "caching-transform": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", - "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-3.0.2.tgz", + "integrity": "sha512-Mtgcv3lh3U0zRii/6qVgQODdPA4G3zhG+jtbCWj39RXuUFTMzH0vcdMtaJS1jPowd+It2Pqr6y3NJMQqOqCE2w==", "dev": true, "requires": { - "hasha": "^5.0.0", - "make-dir": "^3.0.0", - "package-hash": "^4.0.0", - "write-file-atomic": "^3.0.0" + "hasha": "^3.0.0", + "make-dir": "^2.0.0", + "package-hash": "^3.0.0", + "write-file-atomic": "^2.4.2" } }, "call-bind": { @@ -5770,12 +5811,6 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, - "caniuse-lite": { - "version": "1.0.30001277", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001277.tgz", - "integrity": "sha512-J2WtYj2Pl6LBEG214XmbGw1gzZEsYuinQFPqYtpZDB3/vm49qNlrcbJrTMkHKmdRDdmXYwkG0tgOBJsuI+J12Q==", - "dev": true - }, "chai": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", @@ -5818,11 +5853,33 @@ "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", "dev": true }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } }, "color-convert": { "version": "2.0.1", @@ -5842,7 +5899,7 @@ "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", "dev": true }, "concat-map": { @@ -5852,12 +5909,22 @@ "dev": true }, "convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "cp-file": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-6.2.0.tgz", + "integrity": "sha512-fmvV4caBnofhPe8kOcitBwSn2f39QLjnAnGq3gO9dfd75mUytzKNZB1hde6QHunW2Rt+OwuBOMc3i1tNElbszA==", "dev": true, "requires": { - "safe-buffer": "~5.1.1" + "graceful-fs": "^4.1.2", + "make-dir": "^2.0.0", + "nested-error-stacks": "^2.0.0", + "pify": "^4.0.1", + "safe-buffer": "^5.0.1" } }, "create-require": { @@ -5898,7 +5965,7 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "dev": true }, "deep-eql": { @@ -5917,12 +5984,12 @@ "dev": true }, "default-require-extensions": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", - "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", + "integrity": "sha512-B0n2zDIXpzLzKeoEozorDSa1cHc1t0NjmxP0zuAxbizNU2MBqYJJKYXrrFdKuQliojXynrxgd7l4ahfg/+aA5g==", "dev": true, "requires": { - "strip-bom": "^4.0.0" + "strip-bom": "^3.0.0" } }, "define-properties": { @@ -5959,18 +6026,21 @@ "esutils": "^2.0.2" } }, - "electron-to-chromium": { - "version": "1.3.889", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.889.tgz", - "integrity": "sha512-suEUoPTD1mExjL9TdmH7cvEiWJVM2oEiAi+Y1p0QKxI2HcRlT44qDTP2c1aZmVwRemIPYOpxmV7CxQCOWcm4XQ==", - "dev": true - }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, "es-abstract": { "version": "1.21.1", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.1.tgz", @@ -6392,14 +6462,14 @@ } }, "find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", "dev": true, "requires": { "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" } }, "find-up": { @@ -6444,21 +6514,52 @@ } }, "foreground-child": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", - "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-1.5.6.tgz", + "integrity": "sha512-3TOY+4TKV0Ml83PXJQY+JFQaHNV38lzQDIzzXYg1kWdBLenGgoZhAs0CKgzI31vi2pWEpQMq/Yi4bpKwCPkw7g==", "dev": true, "requires": { - "cross-spawn": "^7.0.0", - "signal-exit": "^3.0.2" + "cross-spawn": "^4", + "signal-exit": "^3.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", + "integrity": "sha512-yAXz/pA1tD8Gtg2S98Ekf/sewp3Lcp3YoFKJ4Hkp5h5yLWnKVTDU0kwjKJ8NDCYcfTLfyGkzTikst+jWypT1iA==", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true + } } }, - "fromentries": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", - "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", - "dev": true - }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -6489,10 +6590,10 @@ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, "get-func-name": { @@ -6512,12 +6613,6 @@ "has-symbols": "^1.0.3" } }, - "get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true - }, "get-symbol-description": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", @@ -6590,9 +6685,9 @@ } }, "graceful-fs": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, "grapheme-splitter": { @@ -6653,13 +6748,12 @@ } }, "hasha": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", - "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-3.0.0.tgz", + "integrity": "sha512-w0Kz8lJFBoyaurBiNrIvxPqr/gJ6fOfSkpAPOepN3oECqGJag37xPbOv57izi/KP8auHgNYxn5fXtAb+1LsJ6w==", "dev": true, "requires": { - "is-stream": "^2.0.0", - "type-fest": "^0.8.0" + "is-stream": "^1.0.1" } }, "he": { @@ -6668,6 +6762,12 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, "html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -6688,14 +6788,6 @@ "requires": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" - }, - "dependencies": { - "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==", - "dev": true - } } }, "imurmurhash": { @@ -6704,12 +6796,6 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -6748,6 +6834,12 @@ "is-typed-array": "^1.1.10" } }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, "is-bigint": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", @@ -6797,6 +6889,12 @@ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true + }, "is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -6859,9 +6957,9 @@ } }, "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", "dev": true }, "is-string": { @@ -6895,12 +6993,6 @@ "has-tostringtag": "^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=", - "dev": true - }, "is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -6916,12 +7008,6 @@ "call-bind": "^1.0.2" } }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true - }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -6929,96 +7015,94 @@ "dev": true }, "istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", "dev": true }, "istanbul-lib-hook": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", - "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz", + "integrity": "sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA==", "dev": true, "requires": { - "append-transform": "^2.0.0" + "append-transform": "^1.0.0" } }, "istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, - "requires": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - } - }, - "istanbul-lib-processinfo": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz", - "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", + "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", "dev": true, "requires": { - "archy": "^1.0.0", - "cross-spawn": "^7.0.0", - "istanbul-lib-coverage": "^3.0.0-alpha.1", - "make-dir": "^3.0.0", - "p-map": "^3.0.0", - "rimraf": "^3.0.0", - "uuid": "^3.3.3" + "@babel/generator": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0", + "istanbul-lib-coverage": "^2.0.5", + "semver": "^6.0.0" } }, "istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", + "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", "dev": true, "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "supports-color": "^6.1.0" }, "dependencies": { + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", "dev": true, "requires": { - "has-flag": "^4.0.0" + "has-flag": "^3.0.0" } } } }, "istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", + "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", "dev": true, "requires": { "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "rimraf": "^2.6.3", "source-map": "^0.6.1" }, "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } } } }, "istanbul-reports": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.5.tgz", - "integrity": "sha512-5+19PlhnGabNWB7kOFnuxT8H3T/iIyQzIbQMxXsURmmvKg86P2sbkrGOT77VnHw0Qr0gc2XzRaRfMZYYbSQCJQ==", + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.7.tgz", + "integrity": "sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg==", "dev": true, "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" + "html-escaper": "^2.0.0" } }, "js-sdsl": { @@ -7048,6 +7132,12 @@ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -7060,12 +7150,6 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, - "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true - }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -7076,6 +7160,26 @@ "type-check": "~0.4.0" } }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true + } + } + }, "locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -7088,7 +7192,7 @@ "lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", "dev": true }, "lodash.merge": { @@ -7126,12 +7230,21 @@ } }, "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", "dev": true, "requires": { - "semver": "^6.0.0" + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "dependencies": { + "semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true + } } }, "make-error": { @@ -7140,6 +7253,15 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, + "merge-source-map": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", + "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", + "dev": true, + "requires": { + "source-map": "^0.6.1" + } + }, "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -7171,6 +7293,15 @@ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "requires": { + "minimist": "^1.2.6" + } + }, "mocha": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", @@ -7265,12 +7396,6 @@ "dev": true, "optional": true }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, "glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -7411,21 +7536,32 @@ "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", "dev": true }, - "node-preload": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", - "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "nested-error-stacks": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.1.tgz", + "integrity": "sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw==", + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "requires": { - "process-on-spawn": "^1.0.0" + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true + } } }, - "node-releases": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", - "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", - "dev": true - }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -7433,80 +7569,74 @@ "dev": true }, "nyc": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", - "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", - "dev": true, - "requires": { - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "caching-transform": "^4.0.0", - "convert-source-map": "^1.7.0", - "decamelize": "^1.2.0", - "find-cache-dir": "^3.2.0", - "find-up": "^4.1.0", - "foreground-child": "^2.0.0", - "get-package-type": "^0.1.0", - "glob": "^7.1.6", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-hook": "^3.0.0", - "istanbul-lib-instrument": "^4.0.0", - "istanbul-lib-processinfo": "^2.0.2", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "make-dir": "^3.0.0", - "node-preload": "^0.2.1", - "p-map": "^3.0.0", - "process-on-spawn": "^1.0.0", - "resolve-from": "^5.0.0", - "rimraf": "^3.0.0", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-14.1.1.tgz", + "integrity": "sha512-OI0vm6ZGUnoGZv/tLdZ2esSVzDwUC88SNs+6JoSOMVxA+gKMB8Tk7jBwgemLx4O40lhhvZCVw1C+OYLOBOPXWw==", + "dev": true, + "requires": { + "archy": "^1.0.0", + "caching-transform": "^3.0.2", + "convert-source-map": "^1.6.0", + "cp-file": "^6.2.0", + "find-cache-dir": "^2.1.0", + "find-up": "^3.0.0", + "foreground-child": "^1.5.6", + "glob": "^7.1.3", + "istanbul-lib-coverage": "^2.0.5", + "istanbul-lib-hook": "^2.0.7", + "istanbul-lib-instrument": "^3.3.0", + "istanbul-lib-report": "^2.0.8", + "istanbul-lib-source-maps": "^3.0.6", + "istanbul-reports": "^2.2.4", + "js-yaml": "^3.13.1", + "make-dir": "^2.1.0", + "merge-source-map": "^1.1.0", + "resolve-from": "^4.0.0", + "rimraf": "^2.6.3", "signal-exit": "^3.0.2", - "spawn-wrap": "^2.0.0", - "test-exclude": "^6.0.0", - "yargs": "^15.0.2" + "spawn-wrap": "^1.4.2", + "test-exclude": "^5.2.3", + "uuid": "^3.3.2", + "yargs": "^13.2.2", + "yargs-parser": "^13.0.0" }, "dependencies": { - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" + "sprintf-js": "~1.0.2" } }, "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "locate-path": "^3.0.0" } }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } }, "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "p-locate": "^4.1.0" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" } }, "p-limit": { @@ -7519,81 +7649,27 @@ } }, "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "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==", "dev": true, "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "p-limit": "^2.0.0" } }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", "dev": true }, - "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "dev": true, "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "glob": "^7.1.3" } } } @@ -7656,6 +7732,12 @@ "word-wrap": "^1.2.3" } }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", + "dev": true + }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -7674,15 +7756,6 @@ "p-limit": "^3.0.2" } }, - "p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -7690,13 +7763,13 @@ "dev": true }, "package-hash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", - "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-3.0.0.tgz", + "integrity": "sha512-lOtmukMDVvtkL84rJHI7dpTYq+0rli8N2wlnqUcBuDWCfVhRUfOmnR9SsoHFMLpACvEV60dX7rd0rFaYDZI+FA==", "dev": true, "requires": { "graceful-fs": "^4.1.15", - "hasha": "^5.0.0", + "hasha": "^3.0.0", "lodash.flattendeep": "^4.4.0", "release-zalgo": "^1.0.0" } @@ -7710,6 +7783,16 @@ "callsites": "^3.0.0" } }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -7746,44 +7829,44 @@ "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, "picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", "dev": true, "requires": { - "find-up": "^4.0.0" + "find-up": "^3.0.0" }, "dependencies": { "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "locate-path": "^3.0.0" } }, "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "p-locate": "^4.1.0" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" } }, "p-limit": { @@ -7796,13 +7879,19 @@ } }, "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "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==", "dev": true, "requires": { - "p-limit": "^2.2.0" + "p-limit": "^2.0.0" } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true } } }, @@ -7812,14 +7901,11 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, - "process-on-spawn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", - "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", - "dev": true, - "requires": { - "fromentries": "^1.2.0" - } + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "dev": true }, "punycode": { "version": "2.3.0", @@ -7842,6 +7928,89 @@ "safe-buffer": "^5.1.0" } }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "dependencies": { + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true + } + } + }, + "read-pkg-up": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", + "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "dev": true, + "requires": { + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "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==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true + } + } + }, "regexp.prototype.flags": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", @@ -7862,7 +8031,7 @@ "release-zalgo": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", "dev": true, "requires": { "es6-error": "^4.0.1" @@ -7874,6 +8043,12 @@ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, "resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -7886,9 +8061,9 @@ } }, "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, "reusify": { @@ -7950,7 +8125,7 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true }, "shebang-command": { @@ -7980,9 +8155,9 @@ } }, "signal-exit": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz", - "integrity": "sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, "slash": { @@ -7991,6 +8166,12 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, "source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", @@ -7999,36 +8180,114 @@ "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" + } + }, + "spawn-wrap": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-1.4.3.tgz", + "integrity": "sha512-IgB8md0QW/+tWqcavuFgKYR/qIRvJkRLPJDFaoXtLLUaVcCDK0+HeFTkmQHj3eprcYhc+gOl0aEA1w7qZlYezw==", + "dev": true, + "requires": { + "foreground-child": "^1.5.6", + "mkdirp": "^0.5.0", + "os-homedir": "^1.0.1", + "rimraf": "^2.6.2", + "signal-exit": "^3.0.2", + "which": "^1.3.0" }, "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } } } }, - "spawn-wrap": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", - "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", "dev": true, "requires": { - "foreground-child": "^2.0.0", - "is-windows": "^1.0.2", - "make-dir": "^3.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "which": "^2.0.1" + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" } }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.16", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz", + "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==", + "dev": true + }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, "string.prototype.trimend": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", @@ -8061,9 +8320,9 @@ } }, "strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true }, "strip-json-comments": { @@ -8088,14 +8347,15 @@ "dev": true }, "test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz", + "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==", "dev": true, "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" + "glob": "^7.1.3", + "minimatch": "^3.0.4", + "read-pkg-up": "^4.0.0", + "require-main-filename": "^2.0.0" } }, "text-table": { @@ -8107,7 +8367,7 @@ "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", "dev": true }, "to-regex-range": { @@ -8168,12 +8428,6 @@ "requires": { "minimist": "^1.2.0" } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true } } }, @@ -8199,6 +8453,12 @@ } } }, + "tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "dev": true + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -8214,12 +8474,6 @@ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - }, "typed-array-length": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", @@ -8231,15 +8485,6 @@ "is-typed-array": "^1.1.9" } }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "requires": { - "is-typedarray": "^1.0.0" - } - }, "typescript": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz", @@ -8279,6 +8524,16 @@ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true }, + "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==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -8301,6 +8556,12 @@ "is-symbol": "^1.0.3" } }, + "which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "dev": true + }, "which-typed-array": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", @@ -8327,6 +8588,58 @@ "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", "dev": true }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "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==", + "dev": true, + "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": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -8334,23 +8647,101 @@ "dev": true }, "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", "dev": true, "requires": { + "graceful-fs": "^4.1.11", "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" + "signal-exit": "^3.0.2" } }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "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==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true + } + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, "yargs-unparser": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", diff --git a/package.json b/package.json index 1968628..77452c6 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "types": "lib/index.d.ts", "module": "lib/esm/index.js", "scripts": { - "coverage": "nyc npm run test", + "coverage": "cross-env nyc npm run test", "build": "tsc -p tsconfig.build.cjs.json && tsc -p tsconfig.build.esm.json", "prepare": "npm run build", "test": "cross-env TS_NODE_PROJECT=./tsconfig.mocha.json node --expose-gc node_modules/mocha/bin/_mocha --require ts-node/register 'test/**/*.ts' --exit --timeout 30000", @@ -37,6 +37,7 @@ "@types/chai": "4.3.4", "@types/mocha": "^10.0.1", "@types/node": "^18.11.18", + "@types/tunnel": "^0.0.5", "@typescript-eslint/eslint-plugin": "^5.53.0", "@typescript-eslint/parser": "^5.53.0", "chai": "^4.3.7", @@ -45,9 +46,10 @@ "eslint-plugin-import": "^2.27.5", "mocha": "^10.2.0", "moq.ts": "^7.4.1", - "nyc": "^15.1.0", + "nyc": "^14.1.1", "source-map-support": "^0.5.21", "ts-node": "^10.9.1", + "tunnel": "^0.0.6", "typescript": "^4.0.2" }, "repository": { @@ -68,8 +70,8 @@ "src" ], "exclude": [ - "src/Semver.ts", - "src/Sha1.ts" + "src/Hash.ts", + "src/Semver.ts" ] }, "sideEffects": false diff --git a/samples/deno-sandbox/import_map.json b/samples/deno-sandbox/import_map.json index 3bd8ae3..3bbe107 100644 --- a/samples/deno-sandbox/import_map.json +++ b/samples/deno-sandbox/import_map.json @@ -12,8 +12,10 @@ "../../src/ConfigFetcher": "../../src/ConfigFetcher.ts", "../../src/ConfigServiceBase": "../../src/ConfigServiceBase.ts", "../../src/DefaultEventEmitter": "../../src/DefaultEventEmitter.ts", + "../../src/EvaluateLogBuilder": "../../src/EvaluateLogBuilder.ts", "../../src/EventEmitter": "../../src/EventEmitter.ts", "../../src/FlagOverrides": "../../src/FlagOverrides.ts", + "../../src/Hash": "../../src/Hash.ts", "../../src/Hooks": "../../src/Hooks.ts", "../../src/LazyLoadConfigService": "../../src/LazyLoadConfigService.ts", "../../src/ManualPollConfigService": "../../src/ManualPollConfigService.ts", @@ -21,7 +23,7 @@ "../../src/ProjectConfig": "../../src/ProjectConfig.ts", "../../src/RolloutEvaluator": "../../src/RolloutEvaluator.ts", "../../src/Semver": "../../src/Semver.ts", - "../../src/Sha1": "../../src/Sha1.ts", + "../../src/User": "../../src/User.ts", "../../src/Utils": "../../src/Utils.ts" } } diff --git a/samples/deno-sandbox/sample.json b/samples/deno-sandbox/sample.json index 95004fe..72df15d 100644 --- a/samples/deno-sandbox/sample.json +++ b/samples/deno-sandbox/sample.json @@ -1 +1,119 @@ -{"p":{"u":"https://cdn-global.configcat.com","r":0},"f":{"isAwesomeFeatureEnabled":{"v":true,"i":"ca36009d","t":0,"p":[{"o":0,"v":true,"p":0,"i":"ca36009d"},{"o":1,"v":false,"p":100,"i":"2bdcee75"}],"r":[{"o":0,"a":"Identifier","t":0,"c":"gdf","v":false,"i":"2bdcee75"},{"o":1,"a":"Identifier","t":0,"c":"dfgdf","v":false,"i":"2bdcee75"}]},"isPOCFeatureEnabled":{"v":false,"i":"430bded3","t":0,"p":[],"r":[{"o":0,"a":"Email","t":2,"c":"@something.com","v":false,"i":"430bded3"},{"o":1,"a":"Email","t":2,"c":"@example.com","v":true,"i":"9f21c24c"}]}}} \ No newline at end of file +{ + "p": { + "u": "https://cdn-global.configcat.com", + "r": 0, + "s": "unEpMhsXD/Zv9MH0gkBqS0Hr97LCGUKpHHOhEfzTcY4=" + }, + "f": { + "isAwesomeFeatureEnabled": { + "t": 0, + "r": [ + { + "c": [ + { + "u": { + "a": "Identifier", + "c": 0, + "l": [ + "gdf" + ] + } + } + ], + "s": { + "v": { + "b": false + }, + "i": "2bdcee75" + } + }, + { + "c": [ + { + "u": { + "a": "Identifier", + "c": 0, + "l": [ + "dfgdf" + ] + } + } + ], + "s": { + "v": { + "b": false + }, + "i": "2bdcee75" + } + } + ], + "p": [ + { + "p": 0, + "v": { + "b": true + }, + "i": "ca36009d" + }, + { + "p": 100, + "v": { + "b": false + }, + "i": "2bdcee75" + } + ], + "v": { + "b": true + }, + "i": "ca36009d" + }, + "isPOCFeatureEnabled": { + "t": 0, + "r": [ + { + "c": [ + { + "u": { + "a": "Email", + "c": 2, + "l": [ + "@something.com" + ] + } + } + ], + "s": { + "v": { + "b": false + }, + "i": "430bded3" + } + }, + { + "c": [ + { + "u": { + "a": "Email", + "c": 2, + "l": [ + "@example.com" + ] + } + } + ], + "s": { + "v": { + "b": true + }, + "i": "9f21c24c" + } + } + ], + "v": { + "b": false + }, + "i": "430bded3" + } + } +} diff --git a/src/ConfigCatCache.ts b/src/ConfigCatCache.ts index 47720a5..6c661db 100644 --- a/src/ConfigCatCache.ts +++ b/src/ConfigCatCache.ts @@ -74,7 +74,7 @@ export class ExternalConfigCache implements IConfigCache { } private updateCachedConfig(externalSerializedConfig: string | null | undefined): void { - if (externalSerializedConfig === null || externalSerializedConfig === void 0 || externalSerializedConfig === this.cachedSerializedConfig) { + if (externalSerializedConfig == null || externalSerializedConfig === this.cachedSerializedConfig) { return; } diff --git a/src/ConfigCatClient.ts b/src/ConfigCatClient.ts index e3304fa..86d6aec 100644 --- a/src/ConfigCatClient.ts +++ b/src/ConfigCatClient.ts @@ -12,10 +12,11 @@ import type { HookEvents, Hooks, IProvidesHooks } from "./Hooks"; import { LazyLoadConfigService } from "./LazyLoadConfigService"; import { ManualPollConfigService } from "./ManualPollConfigService"; import { getWeakRefStub, isWeakRefAvailable } from "./Polyfills"; -import type { IConfig, ProjectConfig, RolloutPercentageItem, RolloutRule, Setting, SettingValue } from "./ProjectConfig"; -import type { IEvaluationDetails, IRolloutEvaluator, SettingTypeOf, User } from "./RolloutEvaluator"; +import type { IConfig, PercentageOption, ProjectConfig, Setting, SettingValue } from "./ProjectConfig"; +import type { IEvaluationDetails, IRolloutEvaluator, SettingTypeOf } from "./RolloutEvaluator"; import { RolloutEvaluator, checkSettingsAvailable, evaluate, evaluateAll, evaluationDetailsFromDefaultValue, getTimestampAsDate, isAllowedValue } from "./RolloutEvaluator"; -import { errorToString } from "./Utils"; +import type { User } from "./User"; +import { errorToString, isArray, throwError } from "./Utils"; /** ConfigCat SDK client. */ export interface IConfigCatClient extends IProvidesHooks { @@ -242,18 +243,23 @@ export class ConfigCatClient implements IConfigCatClient { private static get instanceCache() { return clientInstanceCache; } static get(sdkKey: string, pollingMode: TMode, options: OptionsForPollingMode | undefined | null, configCatKernel: IConfigCatKernel): IConfigCatClient { + const invalidSdkKeyError = "Invalid 'sdkKey' value"; if (!sdkKey) { - throw new Error("Invalid 'sdkKey' value"); + throw new Error(invalidSdkKeyError); } const optionsClass = pollingMode === PollingMode.AutoPoll ? AutoPollOptions : pollingMode === PollingMode.ManualPoll ? ManualPollOptions : pollingMode === PollingMode.LazyLoad ? LazyLoadOptions : - (() => { throw new Error("Invalid 'pollingMode' value"); })(); + throwError(new Error("Invalid 'pollingMode' value")); const actualOptions = new optionsClass(sdkKey, configCatKernel.sdkType, configCatKernel.sdkVersion, options, configCatKernel.defaultCacheFactory, configCatKernel.eventEmitterFactory); + if (actualOptions.flagOverrides?.behaviour !== OverrideBehaviour.LocalOnly && !isValidSdkKey(sdkKey, actualOptions.baseUrlOverriden)) { + throw new Error(invalidSdkKeyError); + } + const [instance, instanceAlreadyCreated] = clientInstanceCache.getOrCreate(actualOptions, configCatKernel); if (instanceAlreadyCreated && options) { @@ -298,7 +304,7 @@ export class ConfigCatClient implements IConfigCatClient { options instanceof AutoPollOptions ? new AutoPollConfigService(configCatKernel.configFetcher, options) : options instanceof ManualPollOptions ? new ManualPollConfigService(configCatKernel.configFetcher, options) : options instanceof LazyLoadOptions ? new LazyLoadConfigService(configCatKernel.configFetcher, options) : - (() => { throw new Error("Invalid 'options' value"); })(); + throwError(new Error("Invalid 'options' value")); } else { this.hooks.emit("clientReady", ClientCacheState.HasLocalOverrideFlagDataOnly); @@ -493,22 +499,30 @@ export class ConfigCatClient implements IConfigCatClient { return new SettingKeyValue(settingKey, setting.value); } - const rolloutRules = settings[settingKey].targetingRules; - if (rolloutRules && rolloutRules.length > 0) { - for (let i = 0; i < rolloutRules.length; i++) { - const rolloutRule: RolloutRule = rolloutRules[i]; - if (variationId === rolloutRule.variationId) { - return new SettingKeyValue(settingKey, rolloutRule.value); + const targetingRules = settings[settingKey].targetingRules; + if (targetingRules && targetingRules.length > 0) { + for (let i = 0; i < targetingRules.length; i++) { + const then = targetingRules[i].then; + if (isArray(then)) { + for (let j = 0; j < then.length; j++) { + const percentageOption: PercentageOption = then[j]; + if (variationId === percentageOption.variationId) { + return new SettingKeyValue(settingKey, percentageOption.value); + } + } + } + else if (variationId === then.variationId) { + return new SettingKeyValue(settingKey, then.value); } } } - const percentageItems = settings[settingKey].percentageOptions; - if (percentageItems && percentageItems.length > 0) { - for (let i = 0; i < percentageItems.length; i++) { - const percentageItem: RolloutPercentageItem = percentageItems[i]; - if (variationId === percentageItem.variationId) { - return new SettingKeyValue(settingKey, percentageItem.value); + const percentageOptions = settings[settingKey].percentageOptions; + if (percentageOptions && percentageOptions.length > 0) { + for (let i = 0; i < percentageOptions.length; i++) { + const percentageOption: PercentageOption = percentageOptions[i]; + if (variationId === percentageOption.variationId) { + return new SettingKeyValue(settingKey, percentageOption.value); } } } @@ -751,6 +765,23 @@ export class SettingKeyValue { public settingValue: TValue) { } } +function isValidSdkKey(sdkKey: string, customBaseUrl: boolean) { + const proxyPrefix = "configcat-proxy/"; + + // NOTE: String.prototype.startsWith was introduced after ES5. We'd rather work around it instead of polyfilling it. + if (customBaseUrl && sdkKey.length > proxyPrefix.length && sdkKey.lastIndexOf(proxyPrefix, 0) === 0) { + return true; + } + + const components = sdkKey.split("/"); + const keyLength = 22; + switch (components.length) { + case 2: return components[0].length === keyLength && components[1].length === keyLength; + case 3: return components[0] === "configcat-sdk-1" && components[1].length === keyLength && components[2].length === keyLength; + default: return false; + } +} + function validateKey(key: string): void { if (!key) { throw new Error("Invalid 'key' value"); @@ -758,7 +789,7 @@ function validateKey(key: string): void { } function ensureAllowedDefaultValue(value: SettingValue): void { - if (!isAllowedValue(value)) { + if (value != null && !isAllowedValue(value)) { throw new TypeError("The default value must be boolean, number, string, null or undefined."); } } diff --git a/src/ConfigCatClientOptions.ts b/src/ConfigCatClientOptions.ts index 867d6a9..00537fc 100644 --- a/src/ConfigCatClientOptions.ts +++ b/src/ConfigCatClientOptions.ts @@ -6,12 +6,12 @@ import { DefaultEventEmitter } from "./DefaultEventEmitter"; import type { IEventEmitter } from "./EventEmitter"; import { NullEventEmitter } from "./EventEmitter"; import type { FlagOverrides } from "./FlagOverrides"; +import { sha1 } from "./Hash"; import type { HookEvents, IProvidesHooks, SafeHooksWrapper } from "./Hooks"; import { Hooks } from "./Hooks"; import { getWeakRefStub, isWeakRefAvailable } from "./Polyfills"; import { ProjectConfig } from "./ProjectConfig"; -import type { User } from "./RolloutEvaluator"; -import { sha1 } from "./Sha1"; +import type { User } from "./User"; /** Specifies the supported polling modes. */ export enum PollingMode { @@ -130,7 +130,7 @@ export type OptionsForPollingMode = export abstract class OptionsBase { - private static readonly configFileName = "config_v5.json"; + private static readonly configFileName = "config_v6.json"; logger: LoggerWrapper; @@ -266,11 +266,11 @@ export class AutoPollOptions extends OptionsBase { if (options) { - if (options.pollIntervalSeconds !== void 0 && options.pollIntervalSeconds !== null) { + if (options.pollIntervalSeconds != null) { this.pollIntervalSeconds = options.pollIntervalSeconds; } - if (options.maxInitWaitTimeSeconds !== void 0 && options.maxInitWaitTimeSeconds !== null) { + if (options.maxInitWaitTimeSeconds != null) { this.maxInitWaitTimeSeconds = options.maxInitWaitTimeSeconds; } } @@ -309,7 +309,7 @@ export class LazyLoadOptions extends OptionsBase { super(apiKey, sdkType + "/l-" + sdkVersion, options, defaultCacheFactory, eventEmitterFactory); if (options) { - if (options.cacheTimeToLiveSeconds !== void 0 && options.cacheTimeToLiveSeconds !== null) { + if (options.cacheTimeToLiveSeconds != null) { this.cacheTimeToLiveSeconds = options.cacheTimeToLiveSeconds; } } diff --git a/src/ConfigCatLogger.ts b/src/ConfigCatLogger.ts index a450118..aa23a4a 100644 --- a/src/ConfigCatLogger.ts +++ b/src/ConfigCatLogger.ts @@ -84,13 +84,13 @@ export class LoggerWrapper implements IConfigCatLogger { private readonly hooks?: SafeHooksWrapper) { } - private isLogLevelEnabled(logLevel: LogLevel): boolean { + isEnabled(logLevel: LogLevel): boolean { return this.level >= logLevel; } /** @inheritdoc */ log(level: LogLevel, eventId: LogEventId, message: LogMessage, exception?: any): LogMessage { - if (this.isLogLevelEnabled(level)) { + if (this.isEnabled(level)) { this.logger.log(level, eventId, message, exception); } @@ -259,7 +259,7 @@ export class LoggerWrapper implements IConfigCatLogger { ); } - targetingIsNotPossible(key: string): LogMessage { + userObjectIsMissing(key: string): LogMessage { return this.log( LogLevel.Warn, 3001, FormattableLogMessage.from( @@ -275,6 +275,43 @@ export class LoggerWrapper implements IConfigCatLogger { ); } + userObjectAttributeIsMissingPercentage(key: string, attributeName: string): LogMessage { + return this.log( + LogLevel.Warn, 3003, + FormattableLogMessage.from( + "KEY", "ATTRIBUTE_NAME", "ATTRIBUTE_NAME" + )`Cannot evaluate % options for setting '${key}' (the User.${attributeName} attribute is missing). You should set the User.${attributeName} attribute in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/` + ); + } + + userObjectAttributeIsMissingCondition(condition: string, key: string, attributeName: string): LogMessage { + return this.log( + LogLevel.Warn, 3003, + FormattableLogMessage.from( + "CONDITION", "KEY", "ATTRIBUTE_NAME", "ATTRIBUTE_NAME" + )`Cannot evaluate condition (${condition}) for setting '${key}' (the User.${attributeName} attribute is missing). You should set the User.${attributeName} attribute in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/` + ); + } + + userObjectAttributeIsInvalid(condition: string, key: string, reason: string, attributeName: string): LogMessage { + return this.log( + LogLevel.Warn, 3004, + FormattableLogMessage.from( + "CONDITION", "KEY", "REASON", "ATTRIBUTE_NAME" + )`Cannot evaluate condition (${condition}) for setting '${key}' (${reason}). Please check the User.${attributeName} attribute and make sure that its value corresponds to the comparison operator.` + ); + } + + userObjectAttributeIsAutoConverted(condition: string, key: string, attributeName: string, attributeValue: string): LogMessage { + return this.log( + LogLevel.Warn, + 3005, + FormattableLogMessage.from( + "CONDITION", "KEY", "ATTRIBUTE_NAME", "ATTRIBUTE_VALUE" + )`Evaluation of condition (${condition}) for setting '${key}' may not produce the expected result (the User.${attributeName} attribute is not a string value, thus it was automatically converted to the string value '${attributeValue}'). Please make sure that using a non-string value was intended.` + ); + } + configServiceCannotInitiateHttpCalls(): LogMessage { return this.log( LogLevel.Warn, 3200, @@ -304,7 +341,7 @@ export class LoggerWrapper implements IConfigCatLogger { /* Common info messages (5000-5999) */ - settingEvaluated(evaluateLog: object): LogMessage { + settingEvaluated(evaluateLog: string): LogMessage { return this.log( LogLevel.Info, 5000, FormattableLogMessage.from( diff --git a/src/EvaluateLogBuilder.ts b/src/EvaluateLogBuilder.ts new file mode 100644 index 0000000..7d3df36 --- /dev/null +++ b/src/EvaluateLogBuilder.ts @@ -0,0 +1,249 @@ +import type { PrerequisiteFlagCondition, SegmentCondition, SettingValue, TargetingRule, UserCondition, UserConditionUnion } from "./ProjectConfig"; +import { PrerequisiteFlagComparator, SegmentComparator, UserComparator } from "./ProjectConfig"; +import { isAllowedValue } from "./RolloutEvaluator"; +import { formatStringList, isArray } from "./Utils"; + +const invalidValuePlaceholder = ""; +const invalidNamePlaceholder = ""; +const invalidOperatorPlaceholder = ""; +const invalidReferencePlaceholder = ""; + +const stringListMaxLength = 10; + +export class EvaluateLogBuilder { + private log = ""; + private indent = ""; + + resetIndent(): this { + this.indent = ""; + return this; + } + + increaseIndent(): this { + this.indent += " "; + return this; + } + + decreaseIndent(): this { + this.indent = this.indent.slice(0, -2); + return this; + } + + newLine(text?: string): this { + this.log += "\n" + this.indent + (text ?? ""); + return this; + } + + append(text: string): this { + this.log += text; + return this; + } + + toString(): string { + return this.log; + } + + appendEvaluationResult(isMatch: boolean): this { + return this.append(`${isMatch}`); + } + + private appendUserConditionCore(comparisonAttribute: string, comparator: UserComparator, comparisonValue?: unknown) { + return this.append(`User.${comparisonAttribute} ${formatUserComparator(comparator)} '${comparisonValue ?? invalidValuePlaceholder}'`); + } + + private appendUserConditionString(comparisonAttribute: string, comparator: UserComparator, comparisonValue: string, isSensitive: boolean) { + return this.appendUserConditionCore(comparisonAttribute, comparator, !isSensitive ? comparisonValue : ""); + } + + private appendUserConditionStringList(comparisonAttribute: string, comparator: UserComparator, comparisonValue: ReadonlyArray, isSensitive: boolean): this { + if (comparisonValue == null) { + return this.appendUserConditionCore(comparisonAttribute, comparator); + } + + const valueText = "value", valuesText = "values"; + + const comparatorFormatted = formatUserComparator(comparator); + if (isSensitive) { + return this.append(`User.${comparisonAttribute} ${comparatorFormatted} [<${comparisonValue.length} hashed ${comparisonValue.length === 1 ? valueText : valuesText}>]`); + } + else { + const comparisonValueFormatted = formatStringList(comparisonValue, stringListMaxLength, count => `, ... <${count} more ${count === 1 ? valueText : valuesText}>`); + + return this.append(`User.${comparisonAttribute} ${comparatorFormatted} [${comparisonValueFormatted}]`); + } + } + + private appendUserConditionNumber(comparisonAttribute: string, comparator: UserComparator, comparisonValue: number, isDateTime?: boolean) { + if (comparisonValue == null) { + return this.appendUserConditionCore(comparisonAttribute, comparator); + } + + const comparatorFormatted = formatUserComparator(comparator); + let date: Date; + return isDateTime && !isNaN((date = new Date(comparisonValue * 1000)) as unknown as number) // see https://stackoverflow.com/a/1353711/8656352 + ? this.append(`User.${comparisonAttribute} ${comparatorFormatted} '${comparisonValue}' (${date.toISOString()} UTC)`) + : this.append(`User.${comparisonAttribute} ${comparatorFormatted} '${comparisonValue}'`); + } + + appendUserCondition(condition: UserConditionUnion): this { + const { comparisonAttribute, comparator } = condition; + switch (condition.comparator) { + case UserComparator.IsOneOf: + case UserComparator.IsNotOneOf: + case UserComparator.ContainsAnyOf: + case UserComparator.NotContainsAnyOf: + case UserComparator.SemVerIsOneOf: + case UserComparator.SemVerIsNotOneOf: + case UserComparator.TextStartsWithAnyOf: + case UserComparator.TextNotStartsWithAnyOf: + case UserComparator.TextEndsWithAnyOf: + case UserComparator.TextNotEndsWithAnyOf: + case UserComparator.ArrayContainsAnyOf: + case UserComparator.ArrayNotContainsAnyOf: + return this.appendUserConditionStringList(comparisonAttribute, comparator, condition.comparisonValue, false); + + case UserComparator.SemVerLess: + case UserComparator.SemVerLessOrEquals: + case UserComparator.SemVerGreater: + case UserComparator.SemVerGreaterOrEquals: + case UserComparator.TextEquals: + case UserComparator.TextNotEquals: + return this.appendUserConditionString(comparisonAttribute, comparator, condition.comparisonValue, false); + + case UserComparator.NumberEquals: + case UserComparator.NumberNotEquals: + case UserComparator.NumberLess: + case UserComparator.NumberLessOrEquals: + case UserComparator.NumberGreater: + case UserComparator.NumberGreaterOrEquals: + return this.appendUserConditionNumber(comparisonAttribute, comparator, condition.comparisonValue); + + case UserComparator.SensitiveIsOneOf: + case UserComparator.SensitiveIsNotOneOf: + case UserComparator.SensitiveTextStartsWithAnyOf: + case UserComparator.SensitiveTextNotStartsWithAnyOf: + case UserComparator.SensitiveTextEndsWithAnyOf: + case UserComparator.SensitiveTextNotEndsWithAnyOf: + case UserComparator.SensitiveArrayContainsAnyOf: + case UserComparator.SensitiveArrayNotContainsAnyOf: + return this.appendUserConditionStringList(comparisonAttribute, comparator, condition.comparisonValue, true); + + case UserComparator.DateTimeBefore: + case UserComparator.DateTimeAfter: + return this.appendUserConditionNumber(comparisonAttribute, comparator, condition.comparisonValue, true); + + case UserComparator.SensitiveTextEquals: + case UserComparator.SensitiveTextNotEquals: + return this.appendUserConditionString(comparisonAttribute, comparator, condition.comparisonValue, true); + + default: + return this.appendUserConditionCore(comparisonAttribute, comparator, (condition as UserCondition).comparisonValue); + } + } + + appendPrerequisiteFlagCondition(condition: PrerequisiteFlagCondition): this { + const prerequisiteFlagKey = condition.prerequisiteFlagKey; + const comparator = condition.comparator; + const comparisonValue = condition.comparisonValue; + + return this.append(`Flag '${prerequisiteFlagKey}' ${formatPrerequisiteFlagComparator(comparator)} '${valueToString(comparisonValue)}'`); + } + + appendSegmentCondition(condition: SegmentCondition): this { + const segment = condition.segment; + const comparator = condition.comparator; + + const segmentName = segment?.name ?? + (segment == null ? invalidReferencePlaceholder : invalidNamePlaceholder); + + return this.append(`User ${formatSegmentComparator(comparator)} '${segmentName}'`); + } + + appendConditionConsequence(isMatch: boolean): this { + this.append(" => ").appendEvaluationResult(isMatch); + return isMatch ? this : this.append(", skipping the remaining AND conditions"); + } + + appendTargetingRuleThenPart(targetingRule: TargetingRule, newLine: boolean): this { + (newLine ? this.newLine() : this.append(" ")) + .append("THEN"); + + const then = targetingRule.then; + return this.append(!isArray(then) ? ` '${valueToString(then.value)}'` : " % options"); + } + + appendTargetingRuleConsequence(targetingRule: TargetingRule, isMatchOrError: boolean | string, newLine: boolean): this { + this.increaseIndent(); + + this.appendTargetingRuleThenPart(targetingRule, newLine) + .append(" => ").append(isMatchOrError === true ? "MATCH, applying rule" : isMatchOrError === false ? "no match" : isMatchOrError); + + return this.decreaseIndent(); + } +} + +export function formatUserComparator(comparator: UserComparator): string { + switch (comparator) { + case UserComparator.IsOneOf: + case UserComparator.SensitiveIsOneOf: + case UserComparator.SemVerIsOneOf: return "IS ONE OF"; + case UserComparator.IsNotOneOf: + case UserComparator.SensitiveIsNotOneOf: + case UserComparator.SemVerIsNotOneOf: return "IS NOT ONE OF"; + case UserComparator.ContainsAnyOf: return "CONTAINS ANY OF"; + case UserComparator.NotContainsAnyOf: return "NOT CONTAINS ANY OF"; + case UserComparator.SemVerLess: + case UserComparator.NumberLess: return "<"; + case UserComparator.SemVerLessOrEquals: + case UserComparator.NumberLessOrEquals: return "<="; + case UserComparator.SemVerGreater: + case UserComparator.NumberGreater: return ">"; + case UserComparator.SemVerGreaterOrEquals: + case UserComparator.NumberGreaterOrEquals: return ">="; + case UserComparator.NumberEquals: return "="; + case UserComparator.NumberNotEquals: return "!="; + case UserComparator.DateTimeBefore: return "BEFORE"; + case UserComparator.DateTimeAfter: return "AFTER"; + case UserComparator.TextEquals: + case UserComparator.SensitiveTextEquals: return "EQUALS"; + case UserComparator.TextNotEquals: + case UserComparator.SensitiveTextNotEquals: return "NOT EQUALS"; + case UserComparator.TextStartsWithAnyOf: + case UserComparator.SensitiveTextStartsWithAnyOf: return "STARTS WITH ANY OF"; + case UserComparator.TextNotStartsWithAnyOf: + case UserComparator.SensitiveTextNotStartsWithAnyOf: return "NOT STARTS WITH ANY OF"; + case UserComparator.TextEndsWithAnyOf: + case UserComparator.SensitiveTextEndsWithAnyOf: return "ENDS WITH ANY OF"; + case UserComparator.TextNotEndsWithAnyOf: + case UserComparator.SensitiveTextNotEndsWithAnyOf: return "NOT ENDS WITH ANY OF"; + case UserComparator.ArrayContainsAnyOf: + case UserComparator.SensitiveArrayContainsAnyOf: return "ARRAY CONTAINS ANY OF"; + case UserComparator.ArrayNotContainsAnyOf: + case UserComparator.SensitiveArrayNotContainsAnyOf: return "ARRAY NOT CONTAINS ANY OF"; + default: return invalidOperatorPlaceholder; + } +} + +export function formatUserCondition(condition: UserConditionUnion): string { + return new EvaluateLogBuilder().appendUserCondition(condition).toString(); +} + +export function formatPrerequisiteFlagComparator(comparator: PrerequisiteFlagComparator): string { + switch (comparator) { + case PrerequisiteFlagComparator.Equals: return "EQUALS"; + case PrerequisiteFlagComparator.NotEquals: return "NOT EQUALS"; + default: return invalidOperatorPlaceholder; + } +} + +export function formatSegmentComparator(comparator: SegmentComparator): string { + switch (comparator) { + case SegmentComparator.IsIn: return "IS IN SEGMENT"; + case SegmentComparator.IsNotIn: return "IS NOT IN SEGMENT"; + default: return invalidOperatorPlaceholder; + } +} + +export function valueToString(value: NonNullable): string { + return isAllowedValue(value) ? value.toString() : invalidValuePlaceholder; +} diff --git a/src/Hash.ts b/src/Hash.ts new file mode 100644 index 0000000..0fab6c8 --- /dev/null +++ b/src/Hash.ts @@ -0,0 +1,196 @@ +import { utf8Encode } from "./Utils"; + +export function sha1(msg: string) { + function rotate_left(n: number, s: number) { + var t4 = ( n<>>(32-s)); + return t4; + }; + var blockstart; + var i, j; + var W = new Array(80); + var H0 = 0x67452301; + var H1 = 0xEFCDAB89; + var H2 = 0x98BADCFE; + var H3 = 0x10325476; + var H4 = 0xC3D2E1F0; + var A, B, C, D, E; + var temp; + msg = utf8Encode(msg); + var msg_len = msg.length; + var word_array = new Array(); + for( i=0; i>>29 ); + word_array.push( (msg_len<<3)&0x0ffffffff ); + for ( blockstart=0; blockstart>> amount) | (value << (32 - amount)); + }; + + const lengthProperty = "length"; + var mathPow = Math.pow; + var maxWord = mathPow(2, 32); + var i, j; // Used as a counter across the whole file + + var precomputedData = sha256 as { h?: number[], k?: number[] }; + var hash = precomputedData.h!; + var k = precomputedData.k; + if (!k) { + // Initial hash value: first 32 bits of the fractional parts of the square roots of the first 8 primes + // (we actually calculate the first 64, but extra values are just ignored) + hash = []; + // Round constants: first 32 bits of the fractional parts of the cube roots of the first 64 primes + k = []; + + var isComposite: {[n: number]: number | undefined } = {}; + for (var candidate = 2, primeCounter = 0; primeCounter < 64; candidate++) { + if (!isComposite[candidate]) { + for (i = 0; i < 313; i += candidate) { + isComposite[i] = candidate; + } + hash[primeCounter] = (mathPow(candidate, .5) * maxWord) | 0; + k[primeCounter++] = (mathPow(candidate, 1 / 3) * maxWord) | 0; + } + } + + precomputedData.h = hash = hash.slice(0, 8); + precomputedData.k = k; + } + + var asciiBitLength = msgUtf8[lengthProperty] * 8; + msgUtf8 += '\x80' // Append 茋' bit (plus zero padding) + + var words: number[] = []; + while (msgUtf8[lengthProperty] % 64 - 56) msgUtf8 += '\x00' // More zero padding + for (i = 0; i < msgUtf8[lengthProperty]; i++) { + j = msgUtf8.charCodeAt(i); + words[i >> 2] |= j << ((3 - i) % 4) * 8; + } + words[words[lengthProperty]] = ((asciiBitLength / maxWord) | 0); + words[words[lengthProperty]] = (asciiBitLength) + + // process each chunk + for (j = 0; j < words[lengthProperty];) { + var w = words.slice(j, j += 16); // The message is expanded into 64 words as part of the iteration + var oldHash = hash; + // This is now the undefinedworking hash", often labelled as variables a...g + // (we have to truncate as well, otherwise extra entries at the end accumulate + hash = hash.slice(0, 8); + + for (i = 0; i < 64; i++) { + // Expand the message into 64 words + // Used below if + var w15 = w[i - 15], w2 = w[i - 2]; + + // Iterate + var a = hash[0], e = hash[4]; + var temp1 = hash[7] + + (rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25)) // S1 + + ((e & hash[5]) ^ ((~e) & hash[6])) // ch + + k[i] + // Expand the message schedule if needed + + (w[i] = (i < 16) ? w[i] : ( + w[i - 16] + + (rightRotate(w15, 7) ^ rightRotate(w15, 18) ^ (w15 >>> 3)) // s0 + + w[i - 7] + + (rightRotate(w2, 17) ^ rightRotate(w2, 19) ^ (w2 >>> 10)) // s1 + ) | 0 + ); + // This is only used once, so *could* be moved below, but it only saves 4 bytes and makes things unreadble + var temp2 = (rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22)) // S0 + + ((a & hash[1]) ^ (a & hash[2]) ^ (hash[1] & hash[2])); // maj + + hash = [(temp1 + temp2) | 0].concat(hash); // We don't bother trimming off the extra ones, they're harmless as long as we're truncating when we do the slice() + hash[4] = (hash[4] + temp1) | 0; + } + + for (i = 0; i < 8; i++) { + hash[i] = (hash[i] + oldHash[i]) | 0; + } + } + + return toHexString(hash, 8); +} + +function toHexString(int32Array: number[], count?: number) { + const hexDigits = "0123456789abcdef"; + var result = ""; + count ??= int32Array.length; + for (let i = 0; i < count; i++) { + for (let j = 3; j >= 0; j--) { + const b = (int32Array[i] >> (j << 3)) & 0xFF; + result += hexDigits[b >> 4]; + result += hexDigits[b & 0xF]; + } + } + return result; +} \ No newline at end of file diff --git a/src/ProjectConfig.ts b/src/ProjectConfig.ts index 7f9b065..8af4627 100644 --- a/src/ProjectConfig.ts +++ b/src/ProjectConfig.ts @@ -1,3 +1,5 @@ +import type { WellKnownUserObjectAttribute } from "./User"; + export class ProjectConfig { static readonly serializationFormatVersion = "v2"; @@ -80,23 +82,30 @@ export class ProjectConfig { } } -/** ConfigCat config. */ +/** Details of a ConfigCat config. */ export interface IConfig { - /** Settings by key. */ - readonly settings: Readonly<{ [key: string]: ISetting }>; + /** The salt that was used to hash sensitive comparison values. */ + readonly salt: string | undefined; + /** The array of segments. */ + readonly segments: ReadonlyArray; + /** The key-value map of settings. */ + readonly settings: Readonly<{ [key: string]: ISettingUnion }>; } export class Config implements IConfig { - readonly settings: Readonly<{ [key: string]: Setting }>; - readonly preferences?: Preferences; + readonly preferences: Preferences | undefined; + readonly segments: ReadonlyArray; + readonly settings: Readonly<{ [key: string]: SettingUnion }>; constructor(json: any) { - this.settings = json.f - ? Object.fromEntries(Object.entries(json.f).map(([key, value]) => { return [key, new Setting(value)]; })) + this.preferences = json.p != null ? new Preferences(json.p) : void 0; + this.segments = json.s?.map((item: any) => new Segment(item)) ?? []; + this.settings = json.f != null + ? Object.fromEntries(Object.entries(json.f).map(([key, value]) => [key, new Setting(value, this) as SettingUnion])) : {}; - - this.preferences = json.p ? new Preferences(json.p) : void 0; } + + get salt(): string | undefined { return this.preferences?.salt; } } export enum RedirectMode { @@ -106,12 +115,32 @@ export enum RedirectMode { } export class Preferences { - readonly baseUrl?: string; - readonly redirectMode?: RedirectMode; + readonly baseUrl: string | undefined; + readonly redirectMode: RedirectMode | undefined; + readonly salt: string | undefined; constructor(json: any) { this.baseUrl = json.u; this.redirectMode = json.r; + this.salt = json.s; + } +} + +/** Describes a segment. */ +export interface ISegment { + /** The name of the segment. */ + readonly name: string; + /** The array of segment rule conditions (where there is a logical AND relation between the items). */ + readonly conditions: ReadonlyArray; +} + +export class Segment implements ISegment { + readonly name: string; + readonly conditions: ReadonlyArray; + + constructor(json: any) { + this.name = json.n; + this.conditions = json.r?.map((item: any) => new UserCondition(item)) ?? []; } } @@ -127,143 +156,334 @@ export enum SettingType { Double = 3, } -export type SettingValue = boolean | number | string | null | undefined; +export type SettingTypeMap = { + [SettingType.Boolean]: boolean; + [SettingType.String]: string; + [SettingType.Int]: number; + [SettingType.Double]: number; +}; + +export type SettingValue = SettingTypeMap[SettingType] | null | undefined; export type VariationIdValue = string | null | undefined; -/** Feature flag or setting. */ -export interface ISetting { - /** The (fallback) value of the setting. */ - readonly value: NonNullable; - /** Setting type. */ - readonly type: SettingType; - /** Array of percentage options. */ - readonly percentageOptions: ReadonlyArray; - /** Array of targeting rules. */ - readonly targetingRules: ReadonlyArray; +/** A model object which contains a setting value along with related data. */ +export interface ISettingValueContainer = NonNullable> { + /** Setting value. */ + readonly value: TValue; /** Variation ID. */ readonly variationId?: NonNullable; } -export class Setting implements ISetting { - readonly value: NonNullable; - readonly type: SettingType; - readonly percentageOptions: ReadonlyArray; - readonly targetingRules: ReadonlyArray; +export class SettingValueContainer = NonNullable> implements ISettingValueContainer { + readonly value: TValue; readonly variationId?: NonNullable; - constructor(json: any) { - this.value = json.v; - this.type = json.t; - this.percentageOptions = json.p?.map((item: any) => new RolloutPercentageItem(item)) ?? []; - this.targetingRules = json.r?.map((item: any) => new RolloutRule(item)) ?? []; + constructor(json: any, hasUnwrappedValue = false) { + this.value = !hasUnwrappedValue ? unwrapSettingValue(json.v) : json.v; this.variationId = json.i; } +} + +// NOTE: The TS compiler can't do type narrowing on constrained generic type parameters (see https://stackoverflow.com/a/68898908/8656352). +// So, to make type narrowing work, we derive a discriminated union type containing all the possible substitutions of the type parameter +// (see also https://github.com/Microsoft/TypeScript/issues/27272#issuecomment-423630799). +export type ISettingUnion = { [K in SettingType]: ISetting }[SettingType]; + +/** Feature flag or setting. */ +export interface ISetting extends ISettingValueContainer { + /** Setting type. */ + readonly type: T; + /** The User Object attribute which serves as the basis of percentage options evaluation. */ + readonly percentageOptionsAttribute: string; + /** The array of targeting rules (where there is a logical OR relation between the items). */ + readonly targetingRules: ReadonlyArray; + /** The array of percentage options. */ + readonly percentageOptions: ReadonlyArray; +} + +export type SettingUnion = { [K in SettingType]: Setting }[SettingType]; + +export class Setting extends SettingValueContainer implements ISetting { + readonly type: T; + readonly percentageOptionsAttribute: string; + readonly targetingRules: ReadonlyArray>; + readonly percentageOptions: ReadonlyArray>; + readonly configJsonSalt: string; + + constructor(json: any, config?: Config) { + super(json, json.t < 0); + this.type = json.t; + const identifierAttribute: WellKnownUserObjectAttribute = "Identifier"; + this.percentageOptionsAttribute = json.a ?? identifierAttribute; + this.targetingRules = json.r?.map((item: any) => new TargetingRule(item, config!)) ?? []; + this.percentageOptions = json.p?.map((item: any) => new PercentageOption(item)) ?? []; + this.configJsonSalt = config?.salt ?? ""; + } static fromValue(value: NonNullable): Setting { return new Setting({ - t: -1, // this is not a defined SettingType value, we only use it internally (will never exposed it to the consumer) + t: -1, // this is not a defined SettingType value, we only use it internally (will never expose it to the consumer) v: value, }); } } -/** Targeting rule comparison operator. */ -export enum Comparator { - /** Does the comparison value interpreted as a comma-separated list of strings contain the comparison attribute? */ - In = 0, - /** Does the comparison value interpreted as a comma-separated list of strings not contain the comparison attribute? */ - NotIn = 1, - /** Is the comparison value contained by the comparison attribute as a substring? */ - Contains = 2, - /** Is the comparison value not contained by the comparison attribute as a substring? */ - NotContains = 3, - /** Does the comparison value interpreted as a comma-separated list of semantic versions contain the comparison attribute? */ - SemVerIn = 4, - /** Does the comparison value interpreted as a comma-separated list of semantic versions not contain the comparison attribute? */ - SemVerNotIn = 5, - /** Is the comparison value interpreted as a semantic version less than the comparison attribute? */ - SemVerLessThan = 6, - /** Is the comparison value interpreted as a semantic version less than or equal to the comparison attribute? */ - SemVerLessThanEqual = 7, - /** Is the comparison value interpreted as a semantic version greater than the comparison attribute? */ - SemVerGreaterThan = 8, - /** Is the comparison value interpreted as a semantic version greater than or equal to the comparison attribute? */ - SemVerGreaterThanEqual = 9, - /** Is the comparison value interpreted as a number equal to the comparison attribute? */ - NumberEqual = 10, - /** Is the comparison value interpreted as a number not equal to the comparison attribute? */ - NumberNotEqual = 11, - /** Is the comparison value interpreted as a number less than the comparison attribute? */ - NumberLessThan = 12, - /** Is the comparison value interpreted as a number less than or equal to the comparison attribute? */ - NumberLessThanEqual = 13, - /** Is the comparison value interpreted as a number greater than the comparison attribute? */ - NumberGreaterThan = 14, - /** Is the comparison value interpreted as a number greater than or equal to the comparison attribute? */ - NumberGreaterThanEqual = 15, - /** Does the comparison value interpreted as a comma-separated list of hashes of strings contain the hash of the comparison attribute? */ - SensitiveOneOf = 16, - /** Does the comparison value interpreted as a comma-separated list of hashes of strings not contain the hash of the comparison attribute? */ - SensitiveNotOneOf = 17 +/** Describes a targeting rule. */ +export interface ITargetingRule = NonNullable> { + /** The array of conditions that are combined with the AND logical operator. (The IF part of the targeting rule.) */ + readonly conditions: ReadonlyArray; + /** The simple value or the array of percentage options associated with the targeting rule. (The THEN part of the targeting rule.) */ + readonly then: ISettingValueContainer | ReadonlyArray>; } -/** Targeting rule. */ -export interface ITargetingRule { - /** A numeric value which determines the order of evaluation. */ - readonly order: number; - /** The attribute that the targeting rule is based on. Can be "User ID", "Email", "Country" or any custom attribute. */ +export class TargetingRule = NonNullable> implements ITargetingRule { + readonly conditions: ReadonlyArray; + readonly then: SettingValueContainer | ReadonlyArray>; + + constructor(json: any, config: Config) { + this.conditions = json.c?.map((item: any) => + item.u != null ? new UserCondition(item.u) : + item.p != null ? new PrerequisiteFlagCondition(item.p) : + item.s != null ? new SegmentCondition(item.s, config) : + void 0) ?? []; + this.then = json.p != null + ? json.p.map((item: any) => new PercentageOption(item)) + : new SettingValueContainer(json.s); + } +} + +/** Represents a percentage option. */ +export interface IPercentageOption = NonNullable> extends ISettingValueContainer { + /** A number between 0 and 100 that represents a randomly allocated fraction of the users. */ + readonly percentage: number; +} + +export class PercentageOption = NonNullable> extends SettingValueContainer implements IPercentageOption { + readonly percentage: number; + + constructor(json: any) { + super(json); + this.percentage = json.p; + } +} + +export type ConditionTypeMap = { + ["UserCondition"]: IUserConditionUnion; + ["PrerequisiteFlagCondition"]: IPrerequisiteFlagCondition; + ["SegmentCondition"]: ISegmentCondition; +} + +export type IConditionUnion = ConditionTypeMap[keyof ConditionTypeMap]; + +/** Represents a condition. */ +export interface ICondition { + /** The type of the condition. */ + readonly type: T; +} + +export type ConditionUnion = UserConditionUnion | PrerequisiteFlagCondition | SegmentCondition; + +/** User Object attribute comparison operator used during the evaluation process. */ +export enum UserComparator { + /** IS ONE OF (cleartext) - It matches when the comparison attribute is equal to any of the comparison values. */ + IsOneOf = 0, + /** IS NOT ONE OF (cleartext) - It matches when the comparison attribute is not equal to any of the comparison values. */ + IsNotOneOf = 1, + /** CONTAINS ANY OF (cleartext) - It matches when the comparison attribute contains any comparison values as a substring. */ + ContainsAnyOf = 2, + /** NOT CONTAINS ANY OF (cleartext) - It matches when the comparison attribute does not contain any comparison values as a substring. */ + NotContainsAnyOf = 3, + /** IS ONE OF (semver) - It matches when the comparison attribute interpreted as a semantic version is equal to any of the comparison values. */ + SemVerIsOneOf = 4, + /** IS NOT ONE OF (semver) - It matches when the comparison attribute interpreted as a semantic version is not equal to any of the comparison values. */ + SemVerIsNotOneOf = 5, + /** < (semver) - It matches when the comparison attribute interpreted as a semantic version is less than the comparison value. */ + SemVerLess = 6, + /** <= (semver) - It matches when the comparison attribute interpreted as a semantic version is less than or equal to the comparison value. */ + SemVerLessOrEquals = 7, + /** > (semver) - It matches when the comparison attribute interpreted as a semantic version is greater than the comparison value. */ + SemVerGreater = 8, + /** >= (semver) - It matches when the comparison attribute interpreted as a semantic version is greater than or equal to the comparison value. */ + SemVerGreaterOrEquals = 9, + /** = (number) - It matches when the comparison attribute interpreted as a decimal number is equal to the comparison value. */ + NumberEquals = 10, + /** != (number) - It matches when the comparison attribute interpreted as a decimal number is not equal to the comparison value. */ + NumberNotEquals = 11, + /** < (number) - It matches when the comparison attribute interpreted as a decimal number is less than the comparison value. */ + NumberLess = 12, + /** <= (number) - It matches when the comparison attribute interpreted as a decimal number is less than or equal to the comparison value. */ + NumberLessOrEquals = 13, + /** > (number) - It matches when the comparison attribute interpreted as a decimal number is greater than the comparison value. */ + NumberGreater = 14, + /** >= (number) - It matches when the comparison attribute interpreted as a decimal number is greater than or equal to the comparison value. */ + NumberGreaterOrEquals = 15, + /** IS ONE OF (hashed) - It matches when the comparison attribute is equal to any of the comparison values (where the comparison is performed using the salted SHA256 hashes of the values). */ + SensitiveIsOneOf = 16, + /** IS NOT ONE OF (hashed) - It matches when the comparison attribute is not equal to any of the comparison values (where the comparison is performed using the salted SHA256 hashes of the values). */ + SensitiveIsNotOneOf = 17, + /** BEFORE (UTC datetime) - It matches when the comparison attribute interpreted as the seconds elapsed since Unix Epoch is less than the comparison value. */ + DateTimeBefore = 18, + /** AFTER (UTC datetime) - It matches when the comparison attribute interpreted as the seconds elapsed since Unix Epoch is greater than the comparison value. */ + DateTimeAfter = 19, + /** EQUALS (hashed) - It matches when the comparison attribute is equal to the comparison value (where the comparison is performed using the salted SHA256 hashes of the values). */ + SensitiveTextEquals = 20, + /** NOT EQUALS (hashed) - It matches when the comparison attribute is not equal to the comparison value (where the comparison is performed using the salted SHA256 hashes of the values). */ + SensitiveTextNotEquals = 21, + /** STARTS WITH ANY OF (hashed) - It matches when the comparison attribute starts with any of the comparison values (where the comparison is performed using the salted SHA256 hashes of the values). */ + SensitiveTextStartsWithAnyOf = 22, + /** NOT STARTS WITH ANY OF (hashed) - It matches when the comparison attribute does not start with any of the comparison values (where the comparison is performed using the salted SHA256 hashes of the values). */ + SensitiveTextNotStartsWithAnyOf = 23, + /** ENDS WITH ANY OF (hashed) - It matches when the comparison attribute ends with any of the comparison values (where the comparison is performed using the salted SHA256 hashes of the values). */ + SensitiveTextEndsWithAnyOf = 24, + /** NOT ENDS WITH ANY OF (hashed) - It matches when the comparison attribute does not end with any of the comparison values (where the comparison is performed using the salted SHA256 hashes of the values). */ + SensitiveTextNotEndsWithAnyOf = 25, + /** ARRAY CONTAINS ANY OF (hashed) - It matches when the comparison attribute interpreted as a comma-separated list contains any of the comparison values (where the comparison is performed using the salted SHA256 hashes of the values). */ + SensitiveArrayContainsAnyOf = 26, + /** ARRAY NOT CONTAINS ANY OF (hashed) - It matches when the comparison attribute interpreted as a comma-separated list does not contain any of the comparison values (where the comparison is performed using the salted SHA256 hashes of the values). */ + SensitiveArrayNotContainsAnyOf = 27, + /** EQUALS (cleartext) - It matches when the comparison attribute is equal to the comparison value. */ + TextEquals = 28, + /** NOT EQUALS (cleartext) - It matches when the comparison attribute is not equal to the comparison value. */ + TextNotEquals = 29, + /** STARTS WITH ANY OF (cleartext) - It matches when the comparison attribute starts with any of the comparison values. */ + TextStartsWithAnyOf = 30, + /** NOT STARTS WITH ANY OF (cleartext) - It matches when the comparison attribute does not start with any of the comparison values. */ + TextNotStartsWithAnyOf = 31, + /** ENDS WITH ANY OF (cleartext) - It matches when the comparison attribute ends with any of the comparison values. */ + TextEndsWithAnyOf = 32, + /** NOT ENDS WITH ANY OF (cleartext) - It matches when the comparison attribute does not end with any of the comparison values. */ + TextNotEndsWithAnyOf = 33, + /** ARRAY CONTAINS ANY OF (cleartext) - It matches when the comparison attribute interpreted as a comma-separated list contains any of the comparison values. */ + ArrayContainsAnyOf = 34, + /** ARRAY NOT CONTAINS ANY OF (cleartext) - It matches when the comparison attribute interpreted as a comma-separated list does not contain any of the comparison values. */ + ArrayNotContainsAnyOf = 35, +} + +export type UserConditionComparisonValueTypeMap = { + [UserComparator.IsOneOf]: Readonly; + [UserComparator.IsNotOneOf]: Readonly; + [UserComparator.ContainsAnyOf]: Readonly; + [UserComparator.NotContainsAnyOf]: Readonly; + [UserComparator.SemVerIsOneOf]: Readonly; + [UserComparator.SemVerIsNotOneOf]: Readonly; + [UserComparator.SemVerLess]: string; + [UserComparator.SemVerLessOrEquals]: string; + [UserComparator.SemVerGreater]: string; + [UserComparator.SemVerGreaterOrEquals]: string; + [UserComparator.NumberEquals]: number; + [UserComparator.NumberNotEquals]: number; + [UserComparator.NumberLess]: number; + [UserComparator.NumberLessOrEquals]: number; + [UserComparator.NumberGreater]: number; + [UserComparator.NumberGreaterOrEquals]: number; + [UserComparator.SensitiveIsOneOf]: Readonly; + [UserComparator.SensitiveIsNotOneOf]: Readonly; + [UserComparator.DateTimeBefore]: number; + [UserComparator.DateTimeAfter]: number; + [UserComparator.SensitiveTextEquals]: string; + [UserComparator.SensitiveTextNotEquals]: string; + [UserComparator.SensitiveTextStartsWithAnyOf]: Readonly; + [UserComparator.SensitiveTextNotStartsWithAnyOf]: Readonly; + [UserComparator.SensitiveTextEndsWithAnyOf]: Readonly; + [UserComparator.SensitiveTextNotEndsWithAnyOf]: Readonly; + [UserComparator.SensitiveArrayContainsAnyOf]: Readonly; + [UserComparator.SensitiveArrayNotContainsAnyOf]: Readonly; + [UserComparator.TextEquals]: string; + [UserComparator.TextNotEquals]: string; + [UserComparator.TextStartsWithAnyOf]: Readonly; + [UserComparator.TextNotStartsWithAnyOf]: Readonly; + [UserComparator.TextEndsWithAnyOf]: Readonly; + [UserComparator.TextNotEndsWithAnyOf]: Readonly; + [UserComparator.ArrayContainsAnyOf]: Readonly; + [UserComparator.ArrayNotContainsAnyOf]: Readonly; +} + +export type IUserConditionUnion = { [K in UserComparator]: IUserCondition }[UserComparator]; + +/** Describes a condition that is based on a User Object attribute. */ +export interface IUserCondition extends ICondition<"UserCondition"> { + /** The User Object attribute that the condition is based on. Can be "Identifier", "Email", "Country" or any custom attribute. */ readonly comparisonAttribute: string; - /** The comparison operator. Defines the connection between the attribute and the value. */ - readonly comparator: Comparator; - /** The value that the attribute is compared to. Can be a string, a number, a semantic version or a comma-separated list, depending on the comparator. */ - readonly comparisonValue: string; - /** The value associated with the targeting rule. */ - readonly value: NonNullable; - /** Variation ID. */ - readonly variationId?: NonNullable; + /** The operator which defines the relation between the comparison attribute and the comparison value. */ + readonly comparator: TComparator; + /** The value that the User Object attribute is compared to. */ + readonly comparisonValue: UserConditionComparisonValueTypeMap[TComparator]; } -export class RolloutRule implements ITargetingRule { - readonly order: number; +export type UserConditionUnion = { [K in UserComparator]: UserCondition }[UserComparator]; + +export class UserCondition implements IUserCondition { + readonly type = "UserCondition"; readonly comparisonAttribute: string; - readonly comparator: Comparator; - readonly comparisonValue: string; - readonly value: NonNullable; - readonly variationId?: NonNullable; + readonly comparator: TComparator; + readonly comparisonValue: UserConditionComparisonValueTypeMap[TComparator]; constructor(json: any) { - this.order = json.o; this.comparisonAttribute = json.a; - this.comparator = json.t; - this.comparisonValue = json.c; - this.value = json.v; - this.variationId = json.i; + this.comparator = json.c; + this.comparisonValue = json.s ?? json.d ?? json.l; } } -/** Percentage option. */ -export interface IPercentageOption { - /** A numeric value which determines the order of evaluation. */ - readonly order: number; - /** A number between 0 and 100 that represents a randomly allocated fraction of the users. */ - readonly percentage: number; - /** The value associated with the percentage option. */ - readonly value: NonNullable; - /** Variation ID. */ - readonly variationId?: NonNullable; +/** Prerequisite flag comparison operator used during the evaluation process. */ +export enum PrerequisiteFlagComparator { + /** EQUALS - It matches when the evaluated value of the specified prerequisite flag is equal to the comparison value. */ + Equals = 0, + /** NOT EQUALS - It matches when the evaluated value of the specified prerequisite flag is not equal to the comparison value. */ + NotEquals = 1 } -export class RolloutPercentageItem implements IPercentageOption { - readonly order: number; - readonly percentage: number; - readonly value: NonNullable; - readonly variationId?: NonNullable; +/** Describes a condition that is based on a prerequisite flag. */ +export interface IPrerequisiteFlagCondition extends ICondition<"PrerequisiteFlagCondition"> { + /** The key of the prerequisite flag that the condition is based on. */ + readonly prerequisiteFlagKey: string; + /** The operator which defines the relation between the evaluated value of the prerequisite flag and the comparison value. */ + readonly comparator: PrerequisiteFlagComparator; + /** The value that the evaluated value of the prerequisite flag is compared to. */ + readonly comparisonValue: NonNullable; +} + +export class PrerequisiteFlagCondition implements IPrerequisiteFlagCondition { + readonly type = "PrerequisiteFlagCondition"; + readonly prerequisiteFlagKey: string; + readonly comparator: PrerequisiteFlagComparator; + readonly comparisonValue: NonNullable; constructor(json: any) { - this.order = json.o; - this.percentage = json.p; - this.value = json.v; - this.variationId = json.i; + this.prerequisiteFlagKey = json.f; + this.comparator = json.c; + this.comparisonValue = unwrapSettingValue(json.v); } } + +/** Segment comparison operator used during the evaluation process. */ +export enum SegmentComparator { + /** IS IN SEGMENT - It matches when the conditions of the specified segment are evaluated to true. */ + IsIn, + /** IS NOT IN SEGMENT - It matches when the conditions of the specified segment are evaluated to false. */ + IsNotIn, +} + +/** Describes a condition that is based on a segment. */ +export interface ISegmentCondition extends ICondition<"SegmentCondition"> { + /** The segment that the condition is based on. */ + readonly segment: ISegment; + /** The operator which defines the expected result of the evaluation of the segment. */ + readonly comparator: SegmentComparator; +} + +export class SegmentCondition implements ISegmentCondition { + readonly type = "SegmentCondition"; + readonly segment: Segment; + readonly comparator: SegmentComparator; + + constructor(json: any, config: Config) { + this.segment = config.segments[json.s]; + this.comparator = json.c; + } +} + +function unwrapSettingValue(json: any): NonNullable { + return json.b ?? json.s ?? json.i ?? json.d; +} diff --git a/src/RolloutEvaluator.ts b/src/RolloutEvaluator.ts index 54ea9cf..498d439 100644 --- a/src/RolloutEvaluator.ts +++ b/src/RolloutEvaluator.ts @@ -1,562 +1,843 @@ import type { LoggerWrapper } from "./ConfigCatLogger"; -import type { IPercentageOption, ITargetingRule, ProjectConfig, RolloutPercentageItem, RolloutRule, Setting, SettingValue, VariationIdValue } from "./ProjectConfig"; -import { Comparator } from "./ProjectConfig"; -import * as semver from "./Semver"; -import { sha1 } from "./Sha1"; -import { errorToString } from "./Utils"; +import { LogLevel } from "./ConfigCatLogger"; +import { EvaluateLogBuilder, formatSegmentComparator, formatUserCondition, valueToString } from "./EvaluateLogBuilder"; +import { sha1, sha256 } from "./Hash"; +import type { ConditionUnion, IPercentageOption, ITargetingRule, PercentageOption, PrerequisiteFlagCondition, ProjectConfig, SegmentCondition, Setting, SettingValue, SettingValueContainer, TargetingRule, UserConditionUnion, VariationIdValue } from "./ProjectConfig"; +import { PrerequisiteFlagComparator, SegmentComparator, SettingType, UserComparator } from "./ProjectConfig"; +import type { ISemVer } from "./Semver"; +import { parse as parseSemVer } from "./Semver"; +import type { User, UserAttributeValue } from "./User"; +import { getUserAttributes } from "./User"; +import { errorToString, formatStringList, isArray, parseFloatStrict, utf8Encode } from "./Utils"; + +export class EvaluateContext { + private $userAttributes?: { [key: string]: UserAttributeValue } | null; + get userAttributes(): { [key: string]: UserAttributeValue } | null { + const attributes = this.$userAttributes; + return attributes !== void 0 ? attributes : (this.$userAttributes = this.user ? getUserAttributes(this.user) : null); + } -export type SettingTypeOf = - T extends boolean ? boolean : - T extends number ? number : - T extends string ? string : - T extends null ? boolean | number | string | null : - T extends undefined ? boolean | number | string | undefined : - any; + private $visitedFlags?: string[]; + get visitedFlags(): string[] { return this.$visitedFlags ??= []; } -export interface IRolloutEvaluator { - evaluate(setting: Setting, key: string, defaultValue: SettingValue, user: User | undefined, remoteConfig: ProjectConfig | null): IEvaluateResult; -} + isMissingUserObjectLogged?: boolean; + isMissingUserObjectAttributeLogged?: boolean; -/** The evaluated value and additional information about the evaluation of a feature flag or setting. */ -export interface IEvaluationDetails { - /** Key of the feature flag or setting. */ - key: string; + logBuilder?: EvaluateLogBuilder; // initialized by RolloutEvaluator.evaluate - /** Evaluated value of the feature or setting flag. */ - value: TValue; + constructor( + readonly key: string, + readonly setting: Setting, + readonly user: User | undefined, + readonly settings: Readonly<{ [name: string]: Setting }>, + ) { + } - /** Variation ID of the feature or setting flag (if available). */ - variationId?: VariationIdValue; + static forPrerequisiteFlag(key: string, setting: Setting, dependentFlagContext: EvaluateContext): EvaluateContext { + const context = new EvaluateContext(key, setting, dependentFlagContext.user, dependentFlagContext.settings); + context.$userAttributes = dependentFlagContext.userAttributes; + context.$visitedFlags = dependentFlagContext.visitedFlags; // crucial to use the computed property here to make sure the list is created! + context.logBuilder = dependentFlagContext.logBuilder; + return context; + } +} - /** Time of last successful config download (if there has been a successful download already). */ - fetchTime?: Date; +export interface IEvaluateResult { + selectedValue: SettingValueContainer; + matchedTargetingRule?: TargetingRule; + matchedPercentageOption?: PercentageOption; +} - /** The User object used for the evaluation (if available). */ - user?: User; +export interface IRolloutEvaluator { + evaluate(defaultValue: SettingValue, context: EvaluateContext): IEvaluateResult; +} - /** - * Indicates whether the default value passed to the setting evaluation methods like `IConfigCatClient.getValueAsync`, `IConfigCatClient.getValueDetailsAsync`, etc. - * is used as the result of the evaluation. - */ - isDefaultValue: boolean; +const targetingRuleIgnoredMessage = "The current targeting rule is ignored and the evaluation continues with the next rule."; - /** Error message in case evaluation failed. */ - errorMessage?: string; +const missingUserObjectError = "cannot evaluate, User Object is missing"; +const missingUserAttributeError = (attributeName: string) => `cannot evaluate, the User.${attributeName} attribute is missing`; +const invalidUserAttributeError = (attributeName: string, reason: string) => `cannot evaluate, the User.${attributeName} attribute is invalid (${reason})`; - /** The exception object related to the error in case evaluation failed (if any). */ - errorException?: any; +export class RolloutEvaluator implements IRolloutEvaluator { + constructor(private readonly logger: LoggerWrapper) { + } - /** The targeting rule which was used to select the evaluated value (if any). */ - matchedEvaluationRule?: ITargetingRule; + evaluate(defaultValue: SettingValue, context: EvaluateContext): IEvaluateResult { + this.logger.debug("RolloutEvaluator.evaluate() called."); - /** The percentage option which was used to select the evaluated value (if any). */ - matchedEvaluationPercentageRule?: IPercentageOption; -} + let logBuilder = context.logBuilder; -/** User Object. Contains user attributes which are used for evaluating targeting rules and percentage options. */ -export class User { + // Building the evaluation log is expensive, so let's not do it if it wouldn't be logged anyway. + if (this.logger.isEnabled(LogLevel.Info)) { + context.logBuilder = logBuilder = new EvaluateLogBuilder(); - constructor(identifier: string, email?: string, country?: string, custom?: { [key: string]: string }) { - this.identifier = identifier; - this.email = email; - this.country = country; - this.custom = custom || {}; - } + logBuilder.append(`Evaluating '${context.key}'`); - /** The unique identifier of the user or session (e.g. email address, primary key, session ID, etc.) */ - identifier: string; + if (context.userAttributes) { + logBuilder.append(` for User '${JSON.stringify(context.userAttributes)}'`); + } - /** Email address of the user. */ - email?: string; + logBuilder.increaseIndent(); + } - /** Country of the user. */ - country?: string; + let returnValue: SettingValue; + try { + let result: IEvaluateResult, isValidReturnValue: boolean; + + if (defaultValue != null) { + // NOTE: We've already checked earlier in the call chain that the defaultValue is of an allowed type (see also ensureAllowedDefaultValue). + + const settingType = context.setting.type; + // A negative setting type indicates a setting which comes from a flag override (see also Setting.fromValue). + if (settingType >= 0 && !isCompatibleValue(defaultValue, settingType)) { + throw new TypeError( + "The type of a setting must match the type of the specified default value. " + + `Setting's type was ${SettingType[settingType]} but the default value's type was ${typeof defaultValue}. ` + + `Please use a default value which corresponds to the setting type ${SettingType[settingType]}. ` + + "Learn more: https://configcat.com/docs/sdk-reference/js/#setting-type-mapping"); + } - /** Custom attributes of the user for advanced targeting rule definitions (e.g. user role, subscription type, etc.) */ - custom?: { [key: string]: string } = {}; -} + result = this.evaluateSetting(context); + returnValue = result.selectedValue.value; -export class RolloutEvaluator implements IRolloutEvaluator { + // When a default value other than null or undefined is specified, the return value must have the same type as the default value + // so that the consistency between TS (compile-time) and JS (run-time) return value types is maintained. + isValidReturnValue = typeof returnValue === typeof defaultValue; + } + else { + result = this.evaluateSetting(context); + returnValue = result.selectedValue.value; - private readonly logger: LoggerWrapper; + // When the specified default value is null or undefined, the return value can be of whatever allowed type (boolean, string, number). + isValidReturnValue = isAllowedValue(returnValue); + } - constructor(logger: LoggerWrapper) { + if (!isValidReturnValue) { + handleInvalidReturnValue(returnValue); + } - this.logger = logger; + return result; + } + catch (err) { + logBuilder?.resetIndent().increaseIndent(); + + returnValue = defaultValue; + throw err; + } + finally { + if (logBuilder) { + logBuilder.newLine(`Returning '${returnValue}'.`) + .decreaseIndent(); + this.logger.settingEvaluated(logBuilder.toString()); + } + } } - evaluate(setting: Setting, key: string, defaultValue: SettingValue, user: User | undefined, remoteConfig: ProjectConfig | null): IEvaluateResult { - this.logger.debug("RolloutEvaluator.Evaluate() called."); + private evaluateSetting(context: EvaluateContext): IEvaluateResult { + let evaluateResult: IEvaluateResult | undefined; - // A negative setting type indicates a flag override (see also Setting.fromValue) - if (setting.type < 0 && !isAllowedValue(setting.value)) { - throw new TypeError( - setting.value === null ? "Setting value is null." : - setting.value === void 0 ? "Setting value is undefined." : - `Setting value '${setting.value}' is of an unsupported type (${typeof setting.value}).`); + const targetingRules = context.setting.targetingRules; + if (targetingRules.length > 0 && (evaluateResult = this.evaluateTargetingRules(targetingRules, context))) { + return evaluateResult; } - const eLog: EvaluateLogger = new EvaluateLogger(); + const percentageOptions = context.setting.percentageOptions; + if (percentageOptions.length > 0 && (evaluateResult = this.evaluatePercentageOptions(percentageOptions, void 0, context))) { + return evaluateResult; + } - eLog.user = user; - eLog.keyName = key; - eLog.returnValue = defaultValue; + return { selectedValue: context.setting }; + } - let result: IEvaluateResult | null; + private evaluateTargetingRules(targetingRules: ReadonlyArray, context: EvaluateContext): IEvaluateResult | undefined { + const logBuilder = context.logBuilder; - try { - if (user) { - // evaluate comparison-based rules + logBuilder?.newLine("Evaluating targeting rules and applying the first match if any:"); - result = this.evaluateRules(setting.targetingRules, user, eLog); - if (result !== null) { - eLog.returnValue = result.value; + for (let i = 0; i < targetingRules.length; i++) { + const targetingRule = targetingRules[i]; + const conditions = targetingRule.conditions; - return result; - } + const isMatchOrError = this.evaluateConditions(conditions, targetingRule, context.key, context); - // evaluate percentage-based rules + if (isMatchOrError !== true) { + if (isEvaluationError(isMatchOrError)) { + logBuilder?.increaseIndent() + .newLine(targetingRuleIgnoredMessage) + .decreaseIndent(); + } + continue; + } - result = this.evaluatePercentageRules(setting.percentageOptions, key, user); + if (!isArray(targetingRule.then)) { + return { selectedValue: targetingRule.then, matchedTargetingRule: targetingRule }; + } - if (setting.percentageOptions && setting.percentageOptions.length > 0) { - eLog.opAppendLine("Evaluating % options => " + (!result ? "user not targeted" : "user targeted")); - } + const percentageOptions = targetingRule.then; - if (result !== null) { - eLog.returnValue = result.value; + logBuilder?.increaseIndent(); - return result; - } - } - else { - if ((setting.targetingRules && setting.targetingRules.length > 0) - || (setting.percentageOptions && setting.percentageOptions.length > 0)) { - this.logger.targetingIsNotPossible(key); - } + const evaluateResult = this.evaluatePercentageOptions(percentageOptions, targetingRule, context); + if (evaluateResult) { + logBuilder?.decreaseIndent(); + return evaluateResult; } - // regular evaluate - result = { - value: setting.value, - variationId: setting.variationId - }; - eLog.returnValue = result.value; - - return result; - } - finally { - this.logger.settingEvaluated(eLog); + logBuilder?.newLine(targetingRuleIgnoredMessage) + .decreaseIndent(); } } - private evaluateRules(rolloutRules: ReadonlyArray, user: User, eLog: EvaluateLogger): IEvaluateResult | null { + private evaluatePercentageOptions(percentageOptions: ReadonlyArray, targetingRule: TargetingRule | undefined, context: EvaluateContext): IEvaluateResult | undefined { + const logBuilder = context.logBuilder; - this.logger.debug("RolloutEvaluator.EvaluateRules() called."); + if (!context.userAttributes) { + logBuilder?.newLine("Skipping % options because the User Object is missing."); - if (rolloutRules && rolloutRules.length > 0) { - - for (let i = 0; i < rolloutRules.length; i++) { - - const rule: RolloutRule = rolloutRules[i]; - - const comparisonAttribute = this.getUserAttribute(user, rule.comparisonAttribute); - - const comparator: number = rule.comparator; + if (!context.isMissingUserObjectLogged) { + this.logger.userObjectIsMissing(context.key); + context.isMissingUserObjectLogged = true; + } - const comparisonValue: string = rule.comparisonValue; + return; + } - let log: string = "Evaluating rule: '" + comparisonAttribute + "' " + this.ruleToString(comparator) + " '" + comparisonValue + "' => "; + const percentageOptionsAttributeName = context.setting.percentageOptionsAttribute; + const percentageOptionsAttributeValue = context.userAttributes[percentageOptionsAttributeName]; + if (percentageOptionsAttributeValue == null) { + logBuilder?.newLine(`Skipping % options because the User.${percentageOptionsAttributeName} attribute is missing.`); - if (!comparisonAttribute) { - log += "NO MATCH (Attribute is not defined on the user object)"; - eLog.opAppendLine(log); - continue; - } + if (!context.isMissingUserObjectAttributeLogged) { + this.logger.userObjectAttributeIsMissingPercentage(context.key, percentageOptionsAttributeName); + context.isMissingUserObjectAttributeLogged = true; + } - const result: IEvaluateResult = { - value: rule.value, - variationId: rule.variationId, - matchedTargetingRule: rule - }; + return; + } - switch (comparator) { - case Comparator.In: + logBuilder?.newLine(`Evaluating % options based on the User.${percentageOptionsAttributeName} attribute:`); - const cvs: string[] = comparisonValue.split(","); + const sha1Hash = sha1(context.key + userAttributeValueToString(percentageOptionsAttributeValue)); + const hashValue = parseInt(sha1Hash.substring(0, 7), 16) % 100; - for (let ci = 0; ci < cvs.length; ci++) { + logBuilder?.newLine(`- Computing hash in the [0..99] range from User.${percentageOptionsAttributeName} => ${hashValue} (this value is sticky and consistent across all SDKs)`); - if (cvs[ci].trim() === comparisonAttribute) { - log += "MATCH"; - eLog.opAppendLine(log); + let bucket = 0; + for (let i = 0; i < percentageOptions.length; i++) { + const percentageOption = percentageOptions[i]; - return result; - } - } + bucket += percentageOption.percentage; - log += "no match"; + if (hashValue >= bucket) { + continue; + } - break; + logBuilder?.newLine(`- Hash value ${hashValue} selects % option ${i + 1} (${percentageOption.percentage}%), '${valueToString(percentageOption.value)}'.`); - case Comparator.NotIn: + return { selectedValue: percentageOption, matchedTargetingRule: targetingRule, matchedPercentageOption: percentageOption }; + } - if (!comparisonValue.split(",").some(e => { - if (e.trim() === comparisonAttribute) { - return true; - } + throw new Error("Sum of percentage option percentages are less than 100."); + } - return false; - })) { - log += "MATCH"; - eLog.opAppendLine(log); + private evaluateConditions(conditions: ReadonlyArray, targetingRule: TargetingRule | undefined, contextSalt: string, context: EvaluateContext): boolean | string { + // The result of a condition evaluation is either match (true) / no match (false) or an error (string). + let result: boolean | string = true; - return result; - } + const logBuilder = context.logBuilder; + let newLineBeforeThen = false; - log += "no match"; + logBuilder?.newLine("- "); - break; + for (let i = 0; i < conditions.length; i++) { + const condition = conditions[i]; - case Comparator.Contains: + if (logBuilder) { + if (!i) { + logBuilder.append("IF ") + .increaseIndent(); + } + else { + logBuilder.increaseIndent() + .newLine("AND "); + } + } - if (comparisonAttribute.indexOf(comparisonValue) !== -1) { - log += "MATCH"; - eLog.opAppendLine(log); + switch (condition.type) { + case "UserCondition": + result = this.evaluateUserCondition(condition, contextSalt, context); + newLineBeforeThen = conditions.length > 1; + break; - return result; - } + case "PrerequisiteFlagCondition": + result = this.evaluatePrerequisiteFlagCondition(condition, context); + newLineBeforeThen = true; + break; - log += "no match"; + case "SegmentCondition": + result = this.evaluateSegmentCondition(condition, context); + newLineBeforeThen = !isEvaluationError(result) || result !== missingUserObjectError || conditions.length > 1; + break; - break; + default: + throw new Error(); // execution should never get here + } - case Comparator.NotContains: + const isMatch = result === true; - if (comparisonAttribute.indexOf(comparisonValue) === -1) { - log += "MATCH"; - eLog.opAppendLine(log); + if (logBuilder) { + if (!targetingRule || conditions.length > 1) { + logBuilder.appendConditionConsequence(isMatch); + } - return result; - } + logBuilder.decreaseIndent(); + } - log += "no match"; + if (!isMatch) { + break; + } + } - break; + if (targetingRule) { + logBuilder?.appendTargetingRuleConsequence(targetingRule, result, newLineBeforeThen); + } - case Comparator.SemVerIn: - case Comparator.SemVerNotIn: - case Comparator.SemVerLessThan: - case Comparator.SemVerLessThanEqual: - case Comparator.SemVerGreaterThan: - case Comparator.SemVerGreaterThanEqual: + return result; + } - if (this.evaluateSemver(comparisonAttribute, comparisonValue, comparator)) { - log += "MATCH"; - eLog.opAppendLine(log); + private evaluateUserCondition(condition: UserConditionUnion, contextSalt: string, context: EvaluateContext): boolean | string { + const logBuilder = context.logBuilder; + logBuilder?.appendUserCondition(condition); - return result; - } + if (!context.userAttributes) { + if (!context.isMissingUserObjectLogged) { + this.logger.userObjectIsMissing(context.key); + context.isMissingUserObjectLogged = true; + } - log += "no match"; + return missingUserObjectError; + } - break; + const userAttributeName = condition.comparisonAttribute; + const userAttributeValue = context.userAttributes[userAttributeName]; + if (userAttributeValue == null || userAttributeValue === "") { // besides null and undefined, empty string is considered missing value as well + this.logger.userObjectAttributeIsMissingCondition(formatUserCondition(condition), context.key, userAttributeName); + return missingUserAttributeError(userAttributeName); + } - case Comparator.NumberEqual: - case Comparator.NumberNotEqual: - case Comparator.NumberLessThan: - case Comparator.NumberLessThanEqual: - case Comparator.NumberGreaterThan: - case Comparator.NumberGreaterThanEqual: + let text: string, versionOrError: ISemVer | string, numberOrError: number | string, arrayOrError: ReadonlyArray | string; + switch (condition.comparator) { + case UserComparator.TextEquals: + case UserComparator.TextNotEquals: + text = getUserAttributeValueAsText(userAttributeName, userAttributeValue, condition, context.key, this.logger); + return this.evaluateTextEquals(text, condition.comparisonValue, condition.comparator === UserComparator.TextNotEquals); + + case UserComparator.SensitiveTextEquals: + case UserComparator.SensitiveTextNotEquals: + text = getUserAttributeValueAsText(userAttributeName, userAttributeValue, condition, context.key, this.logger); + return this.evaluateSensitiveTextEquals(text, condition.comparisonValue, + context.setting.configJsonSalt, contextSalt, condition.comparator === UserComparator.SensitiveTextNotEquals); + + case UserComparator.IsOneOf: + case UserComparator.IsNotOneOf: + text = getUserAttributeValueAsText(userAttributeName, userAttributeValue, condition, context.key, this.logger); + return this.evaluateIsOneOf(text, condition.comparisonValue, condition.comparator === UserComparator.IsNotOneOf); + + case UserComparator.SensitiveIsOneOf: + case UserComparator.SensitiveIsNotOneOf: + text = getUserAttributeValueAsText(userAttributeName, userAttributeValue, condition, context.key, this.logger); + return this.evaluateSensitiveIsOneOf(text, condition.comparisonValue, + context.setting.configJsonSalt, contextSalt, condition.comparator === UserComparator.SensitiveIsNotOneOf); + + case UserComparator.TextStartsWithAnyOf: + case UserComparator.TextNotStartsWithAnyOf: + text = getUserAttributeValueAsText(userAttributeName, userAttributeValue, condition, context.key, this.logger); + return this.evaluateTextSliceEqualsAnyOf(text, condition.comparisonValue, true, condition.comparator === UserComparator.TextNotStartsWithAnyOf); + + case UserComparator.SensitiveTextStartsWithAnyOf: + case UserComparator.SensitiveTextNotStartsWithAnyOf: + text = getUserAttributeValueAsText(userAttributeName, userAttributeValue, condition, context.key, this.logger); + return this.evaluateSensitiveTextSliceEqualsAnyOf(text, condition.comparisonValue, + context.setting.configJsonSalt, contextSalt, true, condition.comparator === UserComparator.SensitiveTextNotStartsWithAnyOf); + + case UserComparator.TextEndsWithAnyOf: + case UserComparator.TextNotEndsWithAnyOf: + text = getUserAttributeValueAsText(userAttributeName, userAttributeValue, condition, context.key, this.logger); + return this.evaluateTextSliceEqualsAnyOf(text, condition.comparisonValue, false, condition.comparator === UserComparator.TextNotEndsWithAnyOf); + + case UserComparator.SensitiveTextEndsWithAnyOf: + case UserComparator.SensitiveTextNotEndsWithAnyOf: + text = getUserAttributeValueAsText(userAttributeName, userAttributeValue, condition, context.key, this.logger); + return this.evaluateSensitiveTextSliceEqualsAnyOf(text, condition.comparisonValue, + context.setting.configJsonSalt, contextSalt, false, condition.comparator === UserComparator.SensitiveTextNotEndsWithAnyOf); + + case UserComparator.ContainsAnyOf: + case UserComparator.NotContainsAnyOf: + text = getUserAttributeValueAsText(userAttributeName, userAttributeValue, condition, context.key, this.logger); + return this.evaluateContainsAnyOf(text, condition.comparisonValue, condition.comparator === UserComparator.NotContainsAnyOf); + + case UserComparator.SemVerIsOneOf: + case UserComparator.SemVerIsNotOneOf: + versionOrError = getUserAttributeValueAsSemVer(userAttributeName, userAttributeValue, condition, context.key, this.logger); + return typeof versionOrError !== "string" + ? this.evaluateSemVerIsOneOf(versionOrError, condition.comparisonValue, condition.comparator === UserComparator.SemVerIsNotOneOf) + : versionOrError; + + case UserComparator.SemVerLess: + case UserComparator.SemVerLessOrEquals: + case UserComparator.SemVerGreater: + case UserComparator.SemVerGreaterOrEquals: + versionOrError = getUserAttributeValueAsSemVer(userAttributeName, userAttributeValue, condition, context.key, this.logger); + return typeof versionOrError !== "string" + ? this.evaluateSemVerRelation(versionOrError, condition.comparator, condition.comparisonValue) + : versionOrError; + + case UserComparator.NumberEquals: + case UserComparator.NumberNotEquals: + case UserComparator.NumberLess: + case UserComparator.NumberLessOrEquals: + case UserComparator.NumberGreater: + case UserComparator.NumberGreaterOrEquals: + numberOrError = getUserAttributeValueAsNumber(userAttributeName, userAttributeValue, condition, context.key, this.logger); + return typeof numberOrError !== "string" + ? this.evaluateNumberRelation(numberOrError, condition.comparator, condition.comparisonValue) + : numberOrError; + + case UserComparator.DateTimeBefore: + case UserComparator.DateTimeAfter: + numberOrError = getUserAttributeValueAsUnixTimeSeconds(userAttributeName, userAttributeValue, condition, context.key, this.logger); + return typeof numberOrError !== "string" + ? this.evaluateDateTimeRelation(numberOrError, condition.comparisonValue, condition.comparator === UserComparator.DateTimeBefore) + : numberOrError; + + case UserComparator.ArrayContainsAnyOf: + case UserComparator.ArrayNotContainsAnyOf: + arrayOrError = getUserAttributeValueAsStringArray(userAttributeName, userAttributeValue, condition, context.key, this.logger); + return typeof arrayOrError !== "string" + ? this.evaluateArrayContainsAnyOf(arrayOrError, condition.comparisonValue, condition.comparator === UserComparator.ArrayNotContainsAnyOf) + : arrayOrError; + + case UserComparator.SensitiveArrayContainsAnyOf: + case UserComparator.SensitiveArrayNotContainsAnyOf: + arrayOrError = getUserAttributeValueAsStringArray(userAttributeName, userAttributeValue, condition, context.key, this.logger); + return typeof arrayOrError !== "string" + ? this.evaluateSensitiveArrayContainsAnyOf(arrayOrError, condition.comparisonValue, + context.setting.configJsonSalt, contextSalt, condition.comparator === UserComparator.SensitiveArrayNotContainsAnyOf) + : arrayOrError; - if (this.evaluateNumber(comparisonAttribute, comparisonValue, comparator)) { - log += "MATCH"; - eLog.opAppendLine(log); + default: + throw new Error(); // execution should never get here (unless there is an error in the config JSON) + } + } - return result; - } + private evaluateTextEquals(text: string, comparisonValue: string, negate: boolean): boolean { + return (text === comparisonValue) !== negate; + } - log += "no match"; + private evaluateSensitiveTextEquals(text: string, comparisonValue: string, configJsonSalt: string, contextSalt: string, negate: boolean): boolean { + const hash = hashComparisonValue(text, configJsonSalt, contextSalt); + return (hash === comparisonValue) !== negate; + } - break; - case Comparator.SensitiveOneOf: { - const values: string[] = comparisonValue.split(","); - const hashedComparisonAttribute: string = sha1(comparisonAttribute); + private evaluateIsOneOf(text: string, comparisonValues: ReadonlyArray, negate: boolean): boolean { + // NOTE: Array.prototype.indexOf uses strict equality. + const isMatch = comparisonValues.indexOf(text) >= 0; + return isMatch !== negate; + } - for (let ci = 0; ci < values.length; ci++) { + private evaluateSensitiveIsOneOf(text: string, comparisonValues: ReadonlyArray, configJsonSalt: string, contextSalt: string, negate: boolean): boolean { + const hash = hashComparisonValue(text, configJsonSalt, contextSalt); + // NOTE: Array.prototype.indexOf uses strict equality. + const isMatch = comparisonValues.indexOf(hash) >= 0; + return isMatch !== negate; + } - if (values[ci].trim() === hashedComparisonAttribute) { - log += "MATCH"; - eLog.opAppendLine(log); + private evaluateTextSliceEqualsAnyOf(text: string, comparisonValues: ReadonlyArray, startsWith: boolean, negate: boolean): boolean { + for (let i = 0; i < comparisonValues.length; i++) { + const item = comparisonValues[i]; - return result; - } - } + if (text.length < item.length) { + continue; + } - log += "no match"; + // NOTE: String.prototype.startsWith/endsWith were introduced after ES5. We'd rather work around them instead of polyfilling them. + const isMatch = (startsWith ? text.lastIndexOf(item, 0) : text.indexOf(item, text.length - item.length)) >= 0; + if (isMatch) { + return !negate; + } + } - break; - } + return negate; + } - case Comparator.SensitiveNotOneOf: { - const hashedComparisonAttribute: string = sha1(comparisonAttribute); + private evaluateSensitiveTextSliceEqualsAnyOf(text: string, comparisonValues: ReadonlyArray, configJsonSalt: string, contextSalt: string, startsWith: boolean, negate: boolean): boolean { + const textUtf8 = utf8Encode(text); - if (!comparisonValue.split(",").some(e => { - if (e.trim() === hashedComparisonAttribute) { - return true; - } + for (let i = 0; i < comparisonValues.length; i++) { + const item = comparisonValues[i]; - return false; - })) { - log += "MATCH"; - eLog.opAppendLine(log); + const index = item.indexOf("_"); + const sliceLength = parseInt(item.slice(0, index)); - return result; - } + if (textUtf8.length < sliceLength) { + continue; + } - log += "no match"; + const sliceUtf8 = startsWith ? textUtf8.slice(0, sliceLength) : textUtf8.slice(textUtf8.length - sliceLength); + const hash = hashComparisonValueSlice(sliceUtf8, configJsonSalt, contextSalt); - break; - } + const isMatch = hash === item.slice(index + 1); + if (isMatch) { + return !negate; + } + } - default: - break; - } + return negate; + } - eLog.opAppendLine(log); + private evaluateContainsAnyOf(text: string, comparisonValues: ReadonlyArray, negate: boolean): boolean { + for (let i = 0; i < comparisonValues.length; i++) { + if (text.indexOf(comparisonValues[i]) >= 0) { + return !negate; } } - return null; + return negate; } - private evaluatePercentageRules(rolloutPercentageItems: ReadonlyArray, key: string, user: User): IEvaluateResult | null { - this.logger.debug("RolloutEvaluator.EvaluateVariations() called."); - if (rolloutPercentageItems && rolloutPercentageItems.length > 0) { + private evaluateSemVerIsOneOf(version: ISemVer, comparisonValues: ReadonlyArray, negate: boolean): boolean { + let result = false; - const hashCandidate: string = key + ((user.identifier === null || user.identifier === void 0) ? "" : user.identifier); - const hashValue: any = sha1(hashCandidate).substring(0, 7); - const hashScale: number = parseInt(hashValue, 16) % 100; - let bucket = 0; + for (let i = 0; i < comparisonValues.length; i++) { + const item = comparisonValues[i]; - for (let i = 0; i < rolloutPercentageItems.length; i++) { - const percentageRule: RolloutPercentageItem = rolloutPercentageItems[i]; - bucket += +percentageRule.percentage; + // NOTE: Previous versions of the evaluation algorithm ignore empty comparison values. + // We keep this behavior for backward compatibility. + if (!item.length) { + continue; + } - if (hashScale < bucket) { - return { - value: percentageRule.value, - variationId: percentageRule.variationId, - matchedPercentageOption: percentageRule - }; - } + const version2 = parseSemVer(item.trim()); + if (!version2) { + // NOTE: Previous versions of the evaluation algorithm ignored invalid comparison values. + // We keep this behavior for backward compatibility. + return false; + } + + if (!result && version.compare(version2) === 0) { + // NOTE: Previous versions of the evaluation algorithm require that + // none of the comparison values are empty or invalid, that is, we can't stop when finding a match. + // We keep this behavior for backward compatibility. + result = true; } } - return null; + return result !== negate; } - private evaluateNumber(v1: string, v2: string, comparator: Comparator): boolean { - this.logger.debug("RolloutEvaluator.EvaluateNumber() called."); - - let n1: number, n2: number; + private evaluateSemVerRelation(version: ISemVer, + comparator: UserComparator.SemVerLess | UserComparator.SemVerLessOrEquals | UserComparator.SemVerGreater | UserComparator.SemVerGreaterOrEquals, + comparisonValue: string): boolean { - if (v1 && !Number.isNaN(Number.parseFloat(v1.replace(",", ".")))) { - n1 = Number.parseFloat(v1.replace(",", ".")); - } - else { + const version2 = parseSemVer(comparisonValue.trim()); + if (!version2) { return false; } - if (v2 && !Number.isNaN(Number.parseFloat(v2.replace(",", ".")))) { - n2 = Number.parseFloat(v2.replace(",", ".")); - } - else { - return false; + const comparisonResult = version.compare(version2); + switch (comparator) { + case UserComparator.SemVerLess: return comparisonResult < 0; + case UserComparator.SemVerLessOrEquals: return comparisonResult <= 0; + case UserComparator.SemVerGreater: return comparisonResult > 0; + case UserComparator.SemVerGreaterOrEquals: return comparisonResult >= 0; } + } + private evaluateNumberRelation(number: number, + comparator: UserComparator.NumberEquals | UserComparator.NumberNotEquals | UserComparator.NumberLess | UserComparator.NumberLessOrEquals | UserComparator.NumberGreater | UserComparator.NumberGreaterOrEquals, + comparisonValue: number): boolean { switch (comparator) { - case Comparator.NumberEqual: - return n1 === n2; - case Comparator.NumberNotEqual: - return n1 !== n2; - case Comparator.NumberLessThan: - return n1 < n2; - case Comparator.NumberLessThanEqual: - return n1 <= n2; - case Comparator.NumberGreaterThan: - return n1 > n2; - case Comparator.NumberGreaterThanEqual: - return n1 >= n2; - default: - break; + case UserComparator.NumberEquals: return number === comparisonValue; + case UserComparator.NumberNotEquals: return number !== comparisonValue; + case UserComparator.NumberLess: return number < comparisonValue; + case UserComparator.NumberLessOrEquals: return number <= comparisonValue; + case UserComparator.NumberGreater: return number > comparisonValue; + case UserComparator.NumberGreaterOrEquals: return number >= comparisonValue; } + } - return false; + private evaluateDateTimeRelation(number: number, comparisonValue: number, before: boolean): boolean { + return before ? number < comparisonValue : number > comparisonValue; } - private evaluateSemver(v1: string, v2: string, comparator: Comparator): boolean { - this.logger.debug("RolloutEvaluator.EvaluateSemver() called."); - if (semver.valid(v1) == null || v2 === void 0) { - return false; + private evaluateArrayContainsAnyOf(array: ReadonlyArray, comparisonValues: ReadonlyArray, negate: boolean): boolean { + for (let i = 0; i < array.length; i++) { + // NOTE: Array.prototype.indexOf uses strict equality. + const isMatch = comparisonValues.indexOf(array[i]) >= 0; + if (isMatch) { + return !negate; + } } - v2 = v2.trim(); - - switch (comparator) { - case Comparator.SemVerIn: - const sv: string[] = v2.split(","); - let found = false; - for (let ci = 0; ci < sv.length; ci++) { - - if (!sv[ci] || sv[ci].trim() === "") { - continue; - } - - if (semver.valid(sv[ci].trim()) == null) { - return false; - } - - if (!found) { - found = semver.looseeq(v1, sv[ci].trim()); - } - } - - return found; + return negate; + } - case Comparator.SemVerNotIn: - return !v2.split(",").some(e => { + private evaluateSensitiveArrayContainsAnyOf(array: ReadonlyArray, comparisonValues: ReadonlyArray, configJsonSalt: string, contextSalt: string, negate: boolean): boolean { + for (let i = 0; i < array.length; i++) { + const hash = hashComparisonValue(array[i], configJsonSalt, contextSalt); + // NOTE: Array.prototype.indexOf uses strict equality. + const isMatch = comparisonValues.indexOf(hash) >= 0; + if (isMatch) { + return !negate; + } + } - if (!e || e.trim() === "") { - return false; - } + return negate; + } - e = semver.valid(e.trim()); + private evaluatePrerequisiteFlagCondition(condition: PrerequisiteFlagCondition, context: EvaluateContext): boolean | string { + const logBuilder = context.logBuilder; + logBuilder?.appendPrerequisiteFlagCondition(condition); - if (e == null) { - return false; - } + const prerequisiteFlagKey = condition.prerequisiteFlagKey; + const prerequisiteFlag = context.settings[prerequisiteFlagKey]; - return semver.eq(v1, e); - }); + context.visitedFlags.push(context.key); - case Comparator.SemVerLessThan: + if (context.visitedFlags.indexOf(prerequisiteFlagKey) >= 0) { + context.visitedFlags.push(prerequisiteFlagKey); + const dependencyCycle = formatStringList(context.visitedFlags, void 0, void 0, " -> "); + throw new Error(`Circular dependency detected between the following depending flags: ${dependencyCycle}.`); + } - if (semver.valid(v2) == null) { - return false; - } + const prerequisiteFlagContext = EvaluateContext.forPrerequisiteFlag(prerequisiteFlagKey, prerequisiteFlag, context); - return semver.lt(v1, v2); - case Comparator.SemVerLessThanEqual: + logBuilder?.newLine("(") + .increaseIndent() + .newLine(`Evaluating prerequisite flag '${prerequisiteFlagKey}':`); - if (semver.valid(v2) == null) { - return false; - } + const prerequisiteFlagEvaluateResult = this.evaluateSetting(prerequisiteFlagContext); - return semver.lte(v1, v2); - case Comparator.SemVerGreaterThan: + context.visitedFlags.pop(); - if (semver.valid(v2) == null) { - return false; - } + const prerequisiteFlagValue = prerequisiteFlagEvaluateResult.selectedValue.value; + if (typeof prerequisiteFlagValue !== typeof condition.comparisonValue) { + if (isAllowedValue(prerequisiteFlagValue)) { + throw new Error(`Type mismatch between comparison value '${condition.comparisonValue}' and prerequisite flag '${prerequisiteFlagKey}'.`); + } + else { + handleInvalidReturnValue(prerequisiteFlagValue); + } + } - return semver.gt(v1, v2); - case Comparator.SemVerGreaterThanEqual: + let result: boolean; - if (semver.valid(v2) == null) { - return false; - } - return semver.gte(v1, v2); - default: + switch (condition.comparator) { + case PrerequisiteFlagComparator.Equals: + result = prerequisiteFlagValue === condition.comparisonValue; break; + case PrerequisiteFlagComparator.NotEquals: + result = prerequisiteFlagValue !== condition.comparisonValue; + break; + default: + throw new Error(); // execution should never get here (unless there is an error in the config JSON) } - return false; + logBuilder?.newLine(`Prerequisite flag evaluation result: '${valueToString(prerequisiteFlagValue)}'.`) + .newLine("Condition (") + .appendPrerequisiteFlagCondition(condition) + .append(") evaluates to ").appendEvaluationResult(result).append(".") + .decreaseIndent() + .newLine(")"); + + return result; } - private getUserAttribute(user: User, attribute: string): string | undefined { - switch (attribute) { - case "Identifier": - return user.identifier; - case "Email": - return user.email; - case "Country": - return user.country; - default: - return (user.custom || {})[attribute]; - } - } - - private ruleToString(rule: Comparator): string { - switch (rule) { - case Comparator.In: - return "IS ONE OF"; - case Comparator.NotIn: - return "IS NOT ONE OF"; - case Comparator.Contains: - return "CONTAINS"; - case Comparator.NotContains: - return "DOES NOT CONTAIN"; - case Comparator.SemVerIn: - return "IS ONE OF (SemVer)"; - case Comparator.SemVerNotIn: - return "IS NOT ONE OF (SemVer)"; - case Comparator.SemVerLessThan: - return "< (SemVer)"; - case Comparator.SemVerLessThanEqual: - return "<= (SemVer)"; - case Comparator.SemVerGreaterThan: - return "> (SemVer)"; - case Comparator.SemVerGreaterThanEqual: - return ">= (SemVer)"; - case Comparator.NumberEqual: - return "= (Number)"; - case Comparator.NumberNotEqual: - return "!= (Number)"; - case Comparator.NumberLessThan: - return "< (Number)"; - case Comparator.NumberLessThanEqual: - return "<= (Number)"; - case Comparator.NumberGreaterThan: - return "> (Number)"; - case Comparator.NumberGreaterThanEqual: - return ">= (Number)"; - case Comparator.SensitiveOneOf: - return "IS ONE OF (Sensitive)"; - case Comparator.SensitiveNotOneOf: - return "IS NOT ONE OF (Sensitive)"; - default: - return rule + ""; + private evaluateSegmentCondition(condition: SegmentCondition, context: EvaluateContext): boolean | string { + const logBuilder = context.logBuilder; + logBuilder?.appendSegmentCondition(condition); + + if (!context.userAttributes) { + if (!context.isMissingUserObjectLogged) { + this.logger.userObjectIsMissing(context.key); + context.isMissingUserObjectLogged = true; + } + + return missingUserObjectError; + } + + const segment = condition.segment; + + logBuilder?.newLine("(") + .increaseIndent() + .newLine(`Evaluating segment '${segment.name}':`); + + const segmentResult = this.evaluateConditions(segment.conditions, void 0, segment.name, context); + let result = segmentResult; + + if (!isEvaluationError(result)) { + switch (condition.comparator) { + case SegmentComparator.IsIn: + break; + case SegmentComparator.IsNotIn: + result = !result; + break; + default: + throw new Error(); // execution should never get here (unless there is an error in the config JSON) + } } + + if (logBuilder) { + logBuilder.newLine("Segment evaluation result: "); + (!isEvaluationError(result) + ? logBuilder.append(`User ${formatSegmentComparator(segmentResult ? SegmentComparator.IsIn : SegmentComparator.IsNotIn)}`) + : logBuilder.append(result)) + .append("."); + + logBuilder.newLine("Condition (").appendSegmentCondition(condition).append(")"); + (!isEvaluationError(result) + ? logBuilder.append(" evaluates to ").appendEvaluationResult(result) + : logBuilder.append(" failed to evaluate")) + .append("."); + + logBuilder + .decreaseIndent() + .newLine(")"); + } + + return result; } } -export interface IEvaluateResult { - value: SettingValue; - variationId?: string; - matchedTargetingRule?: RolloutRule; - matchedPercentageOption?: RolloutPercentageItem; +function isEvaluationError(isMatchOrError: boolean | string): isMatchOrError is string { + return typeof isMatchOrError === "string"; } -class EvaluateLogger { - user!: User | undefined; +export function isStringArray(value: unknown): value is string[] { + return isArray(value) && !value.some(item => typeof item !== "string"); +} - keyName!: string; +function hashComparisonValue(value: string, configJsonSalt: string, contextSalt: string) { + return hashComparisonValueSlice(utf8Encode(value), configJsonSalt, contextSalt); +} - returnValue!: any; +function hashComparisonValueSlice(sliceUtf8: string, configJsonSalt: string, contextSalt: string) { + return sha256(sliceUtf8 + utf8Encode(configJsonSalt) + utf8Encode(contextSalt)); +} - operations = ""; +function userAttributeValueToString(userAttributeValue: UserAttributeValue) { + return typeof userAttributeValue === "string" ? userAttributeValue : + userAttributeValue instanceof Date ? (userAttributeValue.getTime() / 1000) + "" : + isStringArray(userAttributeValue) ? JSON.stringify(userAttributeValue) : + userAttributeValue + ""; +} - opAppendLine(s: string): void { - this.operations += " " + s + "\n"; +function getUserAttributeValueAsText(attributeName: string, attributeValue: UserAttributeValue, condition: UserConditionUnion, key: string, logger: LoggerWrapper): string { + if (typeof attributeValue === "string") { + return attributeValue; } - toString(): string { - return "Evaluate '" + this.keyName + "'" - + "\n User : " + JSON.stringify(this.user) - + "\n" + this.operations - + " Returning value : " + this.returnValue; + attributeValue = userAttributeValueToString(attributeValue); + logger.userObjectAttributeIsAutoConverted(formatUserCondition(condition), key, attributeName, attributeValue); + return attributeValue; +} + +function getUserAttributeValueAsSemVer(attributeName: string, attributeValue: UserAttributeValue, condition: UserConditionUnion, key: string, logger: LoggerWrapper): ISemVer | string { + let version: ISemVer | null; + if (typeof attributeValue === "string" && (version = parseSemVer(attributeValue.trim()))) { + return version; } + return handleInvalidUserAttribute(logger, condition, key, attributeName, `'${attributeValue}' is not a valid semantic version`); +} + +function getUserAttributeValueAsNumber(attributeName: string, attributeValue: UserAttributeValue, condition: UserConditionUnion, key: string, logger: LoggerWrapper): number | string { + if (typeof attributeValue === "number") { + return attributeValue; + } + let number: number; + if (typeof attributeValue === "string" + && (!isNaN(number = parseFloatStrict(attributeValue.replace(",", "."))) || attributeValue === "NaN")) { + return number; + } + return handleInvalidUserAttribute(logger, condition, key, attributeName, `'${attributeValue}' is not a valid decimal number`); +} + +function getUserAttributeValueAsUnixTimeSeconds(attributeName: string, attributeValue: UserAttributeValue, condition: UserConditionUnion, key: string, logger: LoggerWrapper): number | string { + if (attributeValue instanceof Date) { + return attributeValue.getTime() / 1000; + } + if (typeof attributeValue === "number") { + return attributeValue; + } + let number: number; + if (typeof attributeValue === "string" + && (!isNaN(number = parseFloatStrict(attributeValue.replace(",", "."))) || attributeValue === "NaN")) { + return number; + } + return handleInvalidUserAttribute(logger, condition, key, attributeName, `'${attributeValue}' is not a valid Unix timestamp (number of seconds elapsed since Unix epoch)`); +} + +function getUserAttributeValueAsStringArray(attributeName: string, attributeValue: UserAttributeValue, condition: UserConditionUnion, key: string, logger: LoggerWrapper): ReadonlyArray | string { + let stringArray = attributeValue; + if (typeof stringArray === "string") { + try { stringArray = JSON.parse(stringArray); } + catch (err) { /* intentional no-op */ } + } + if (isStringArray(stringArray)) { + return stringArray; + } + return handleInvalidUserAttribute(logger, condition, key, attributeName, `'${attributeValue}' is not a valid string array`); +} + +function handleInvalidUserAttribute(logger: LoggerWrapper, condition: UserConditionUnion, key: string, attributeName: string, reason: string) { + logger.userObjectAttributeIsInvalid(formatUserCondition(condition), key, reason, attributeName); + return invalidUserAttributeError(attributeName, reason); +} + +/* Evaluation details */ + +export type SettingTypeOf = + T extends boolean ? boolean : + T extends number ? number : + T extends string ? string : + T extends null ? boolean | number | string | null : + T extends undefined ? boolean | number | string | undefined : + any; + +/** The evaluated value and additional information about the evaluation of a feature flag or setting. */ +export interface IEvaluationDetails { + /** Key of the feature flag or setting. */ + key: string; + + /** Evaluated value of the feature or setting flag. */ + value: TValue; + + /** Variation ID of the feature or setting flag (if available). */ + variationId?: VariationIdValue; + + /** Time of last successful config download (if there has been a successful download already). */ + fetchTime?: Date; + + /** The User object used for the evaluation (if available). */ + user?: User; + + /** + * Indicates whether the default value passed to the setting evaluation methods like `IConfigCatClient.getValueAsync`, `IConfigCatClient.getValueDetailsAsync`, etc. + * is used as the result of the evaluation. + */ + isDefaultValue: boolean; + + /** Error message in case evaluation failed. */ + errorMessage?: string; + + /** The exception object related to the error in case evaluation failed (if any). */ + errorException?: any; + + /** The targeting rule which was used to select the evaluated value (if any). */ + matchedTargetingRule?: ITargetingRule; + + /** The percentage option which was used to select the evaluated value (if any). */ + matchedPercentageOption?: IPercentageOption; } /* Helper functions */ @@ -564,13 +845,13 @@ class EvaluateLogger { function evaluationDetailsFromEvaluateResult(key: string, evaluateResult: IEvaluateResult, fetchTime?: Date, user?: User): IEvaluationDetails> { return { key, - value: evaluateResult.value as SettingTypeOf, - variationId: evaluateResult.variationId, + value: evaluateResult.selectedValue.value as SettingTypeOf, + variationId: evaluateResult.selectedValue.variationId, fetchTime, user, isDefaultValue: false, - matchedEvaluationRule: evaluateResult.matchedTargetingRule, - matchedEvaluationPercentageRule: evaluateResult.matchedPercentageOption, + matchedTargetingRule: evaluateResult.matchedTargetingRule, + matchedPercentageOption: evaluateResult.matchedPercentageOption, }; } @@ -586,7 +867,7 @@ export function evaluationDetailsFromDefaultValue(key: s }; } -export function evaluate(evaluator: IRolloutEvaluator, settings: { [name: string]: Setting } | null, key: string, defaultValue: T, +export function evaluate(evaluator: IRolloutEvaluator, settings: Readonly<{ [name: string]: Setting }> | null, key: string, defaultValue: T, user: User | undefined, remoteConfig: ProjectConfig | null, logger: LoggerWrapper): IEvaluationDetails> { let errorMessage: string; @@ -597,20 +878,16 @@ export function evaluate(evaluator: IRolloutEvaluator, s const setting = settings[key]; if (!setting) { - errorMessage = logger.settingEvaluationFailedDueToMissingKey(key, "defaultValue", defaultValue, keysToString(settings)).toString(); + errorMessage = logger.settingEvaluationFailedDueToMissingKey(key, "defaultValue", defaultValue, formatStringList(Object.keys(settings))).toString(); return evaluationDetailsFromDefaultValue(key, defaultValue, getTimestampAsDate(remoteConfig), user, errorMessage); } - const evaluateResult = evaluator.evaluate(setting, key, defaultValue, user, remoteConfig); - - if (defaultValue !== null && defaultValue !== void 0 && typeof defaultValue !== typeof evaluateResult.value) { - throw new TypeError(`The type of a setting must match the type of the given default value.\nThe setting's type was ${typeof defaultValue}, the given default value's type was ${typeof evaluateResult.value}.\nPlease pass a corresponding default value type.`); - } + const evaluateResult = evaluator.evaluate(defaultValue, new EvaluateContext(key, setting, user, settings)); return evaluationDetailsFromEvaluateResult(key, evaluateResult, getTimestampAsDate(remoteConfig), user); } -export function evaluateAll(evaluator: IRolloutEvaluator, settings: { [name: string]: Setting } | null, +export function evaluateAll(evaluator: IRolloutEvaluator, settings: Readonly<{ [name: string]: Setting }> | null, user: User | undefined, remoteConfig: ProjectConfig | null, logger: LoggerWrapper, defaultReturnValue: string): [IEvaluationDetails[], any[] | undefined] { let errors: any[] | undefined; @@ -624,7 +901,7 @@ export function evaluateAll(evaluator: IRolloutEvaluator, settings: { [name: str for (const [key, setting] of Object.entries(settings)) { let evaluationDetails: IEvaluationDetails; try { - const evaluateResult = evaluator.evaluate(setting, key, null, user, remoteConfig); + const evaluateResult = evaluator.evaluate(null, new EvaluateContext(key, setting, user, settings)); evaluationDetails = evaluationDetailsFromEvaluateResult(key, evaluateResult, getTimestampAsDate(remoteConfig), user); } catch (err) { @@ -639,7 +916,7 @@ export function evaluateAll(evaluator: IRolloutEvaluator, settings: { [name: str return [evaluationDetailsArray, errors]; } -export function checkSettingsAvailable(settings: { [name: string]: Setting } | null, logger: LoggerWrapper, defaultReturnValue: string): settings is { [name: string]: Setting } { +export function checkSettingsAvailable(settings: Readonly<{ [name: string]: Setting }> | null, logger: LoggerWrapper, defaultReturnValue: string): settings is Readonly<{ [name: string]: Setting }> { if (!settings) { logger.configJsonIsNotPresent(defaultReturnValue); return false; @@ -648,18 +925,27 @@ export function checkSettingsAvailable(settings: { [name: string]: Setting } | n return true; } -export function isAllowedValue(value: SettingValue): boolean { - return value === null - || value === void 0 - || typeof value === "boolean" - || typeof value === "number" - || typeof value === "string"; +export function isAllowedValue(value: unknown): value is NonNullable { + return typeof value === "boolean" || typeof value === "string" || typeof value === "number"; } -export function getTimestampAsDate(projectConfig: ProjectConfig | null): Date | undefined { - return projectConfig ? new Date(projectConfig.timestamp) : void 0; +function isCompatibleValue(value: SettingValue, settingType: SettingType): boolean { + switch (settingType) { + case SettingType.Boolean: return typeof value === "boolean"; + case SettingType.String: return typeof value === "string"; + case SettingType.Int: + case SettingType.Double: return typeof value === "number"; + default: return false; + } } -function keysToString(settings: { [name: string]: Setting }) { - return Object.keys(settings).map(key => `'${key}'`).join(", "); +function handleInvalidReturnValue(value: unknown): never { + throw new TypeError( + value === null ? "Setting value is null." : + value === void 0 ? "Setting value is undefined." : + `Setting value '${value}' is of an unsupported type (${typeof value}).`); +} + +export function getTimestampAsDate(projectConfig: ProjectConfig | null): Date | undefined { + return projectConfig ? new Date(projectConfig.timestamp) : void 0; } diff --git a/src/Semver.ts b/src/Semver.ts index f376c8f..dc92292 100644 --- a/src/Semver.ts +++ b/src/Semver.ts @@ -69,7 +69,17 @@ createToken('LOOSEPLAIN', `[v=\\s]*${src[t['MAINVERSIONLOOSE']] src[t['BUILD']]}?`); createToken('LOOSE', `^${src[t['LOOSEPLAIN']]}$`); -class SemVer { +export interface ISemVer { + version: string; + major: number; + minor: number; + patch: number; + prerelease?: ReadonlyArray; + build: ReadonlyArray; + compare(other: ISemVer): number; +} + +class SemVer implements ISemVer { loose: any; includePrerelease: any; version: any; @@ -360,7 +370,7 @@ class SemVer { } } -const parse = (version: any, options: any) => { +export const parse = (version: string | ISemVer, options?: boolean | { loose?: boolean, includePrerelease?: boolean }): ISemVer | null => { if (!options || typeof options !== 'object') { options = { loose: !!options, @@ -391,17 +401,3 @@ const parse = (version: any, options: any) => { return null } } - -const compare = (a: any, b: any, loose: any) => - new SemVer(a, loose).compare(new SemVer(b, loose)); - -export const valid = (version: any) => { - const v = parse(version, false) - return v ? v.version : null -}; -export const looseeq = (a: any, b: any) => compare(a, b, true) === 0; -export const eq = (a: any, b: any) => compare(a, b, false) === 0; -export const lt = (a: any, b: any) => compare(a, b, false) < 0; -export const lte = (a: any, b: any) => compare(a, b, false) <= 0; -export const gt = (a: any, b: any) => compare(a, b, false) > 0; -export const gte = (a: any, b: any) => compare(a, b, false) >= 0; diff --git a/src/Sha1.ts b/src/Sha1.ts deleted file mode 100644 index b295ab4..0000000 --- a/src/Sha1.ts +++ /dev/null @@ -1,118 +0,0 @@ -export function sha1 (msg: any) { - function rotate_left(n:any,s:any) { - var t4 = ( n<>>(32-s)); - return t4; - }; - function cvt_hex(val:any) { - var str=""; - var v; - for(var i=7; i>=0; i-- ) { - v = (val>>>(i*4))&0x0f; - str += v.toString(16); - } - return str; - }; - function Utf8Encode(string: any) { - string = string.replace(/\r\n/g,"\n"); - var utftext = ""; - for (var n = 0; n < string.length; n++) { - var c = string.charCodeAt(n); - if (c < 128) { - utftext += String.fromCharCode(c); - } - else if((c > 127) && (c < 2048)) { - utftext += String.fromCharCode((c >> 6) | 192); - utftext += String.fromCharCode((c & 63) | 128); - } - else { - utftext += String.fromCharCode((c >> 12) | 224); - utftext += String.fromCharCode(((c >> 6) & 63) | 128); - utftext += String.fromCharCode((c & 63) | 128); - } - } - return utftext; - }; - var blockstart; - var i, j; - var W = new Array(80); - var H0 = 0x67452301; - var H1 = 0xEFCDAB89; - var H2 = 0x98BADCFE; - var H3 = 0x10325476; - var H4 = 0xC3D2E1F0; - var A, B, C, D, E; - var temp; - msg = Utf8Encode(msg); - var msg_len = msg.length; - var word_array = new Array(); - for( i=0; i>>29 ); - word_array.push( (msg_len<<3)&0x0ffffffff ); - for ( blockstart=0; blockstart; + +/** + * User Object. Contains user attributes which are used for evaluating targeting rules and percentage options. + * @remarks + * Please note that the `User` class is not designed to be used as a DTO (data transfer object). + * (Since the type of the `custom` property is polymorphic, it's not guaranteed that deserializing a serialized instance produces an instance with an identical or even valid data content.) + **/ +export class User { + constructor( + /** The unique identifier of the user or session (e.g. email address, primary key, session ID, etc.) */ + public identifier: string, + /** Email address of the user. */ + public email?: string, + /** Country of the user. */ + public country?: string, + /** + * Custom attributes of the user for advanced targeting rule definitions (e.g. user role, subscription type, etc.) + * @remarks + * The set of allowed attribute values depends on the comparison type of the condition which references the User Object attribute. + * `string` values are supported by all comparison types (in some cases they need to be provided in a specific format though). + * Some of the comparison types work with other types of values, as described below. + * + * Text-based comparisons (EQUALS, IS ONE OF, etc.)
+ * * accept `string` values,
+ * * all other values are automatically converted to string (a warning will be logged but evaluation will continue as normal). + * + * SemVer-based comparisons (IS ONE OF, <, >=, etc.)
+ * * accept `string` values containing a properly formatted, valid semver value,
+ * * all other values are considered invalid (a warning will be logged and the currently evaluated targeting rule will be skipped). + * + * Number-based comparisons (=, <, >=, etc.)
+ * * accept `number` values,
+ * * accept `string` values containing a properly formatted, valid `number` value,
+ * * all other values are considered invalid (a warning will be logged and the currently evaluated targeting rule will be skipped). + * + * Date time-based comparisons (BEFORE / AFTER)
+ * * accept `Date` values, which are automatically converted to a second-based Unix timestamp,
+ * * accept `number` values representing a second-based Unix timestamp,
+ * * accept `string` values containing a properly formatted, valid `number` value,
+ * * all other values are considered invalid (a warning will be logged and the currently evaluated targeting rule will be skipped). + * + * String array-based comparisons (ARRAY CONTAINS ANY OF / ARRAY NOT CONTAINS ANY OF)
+ * * accept arrays of `string`,
+ * * accept `string` values containing a valid JSON string which can be deserialized to an array of `string`,
+ * * all other values are considered invalid (a warning will be logged and the currently evaluated targeting rule will be skipped). + * + * In case a non-string attribute value needs to be converted to `string` during evaluation, it will always be done using the same format + * which is accepted by the comparisons. + **/ + public custom: { [key: string]: UserAttributeValue } = {} + ) { + } +} + +// NOTE: This could be an instance method of the User class, however formerly we suggested `const user = { ... }`-style initialization in the SDK docs, +// which would lead to "...is not a function" errors if we called functions on instances created that way as those don't have the correct prototype. +export function getUserAttributes(user: User): { [key: string]: UserAttributeValue } { + + const result: { [key: string]: UserAttributeValue } = {}; + + const identifierAttribute: WellKnownUserObjectAttribute = "Identifier"; + const emailAttribute: WellKnownUserObjectAttribute = "Email"; + const countryAttribute: WellKnownUserObjectAttribute = "Country"; + + result[identifierAttribute] = user.identifier ?? ""; + + if (user.email != null) { + result[emailAttribute] = user.email; + } + + if (user.country != null) { + result[countryAttribute] = user.country; + } + + if (user.custom != null) { + const wellKnownAttributes: string[] = [identifierAttribute, emailAttribute, countryAttribute]; + for (const [attributeName, attributeValue] of Object.entries(user.custom)) { + if (attributeValue != null && wellKnownAttributes.indexOf(attributeName) < 0) { + result[attributeName] = attributeValue; + } + } + } + + return result; +} diff --git a/src/Utils.ts b/src/Utils.ts index bdbb1f6..7264ec0 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -13,7 +13,95 @@ export function errorToString(err: any, includeStackTrace = false): string { : err + ""; } +export function throwError(err: any): never { + throw err; +} + +export function isArray(value: unknown): value is readonly unknown[] { + // See also: https://github.com/microsoft/TypeScript/issues/17002#issuecomment-1477626624 + return Array.isArray(value); +} + export function isPromiseLike(obj: unknown): obj is PromiseLike { // See also: https://stackoverflow.com/a/27746324/8656352 return typeof (obj as PromiseLike)?.then === "function"; } + +export function formatStringList(items: ReadonlyArray, maxLength = 0, getOmittedItemsText?: (count: number) => string, separator = ", "): string { + const length = items.length; + if (!length) { + return ""; + } + + let appendix = ""; + + if (maxLength > 0 && length > maxLength) { + items = items.slice(0, maxLength); + if (getOmittedItemsText) { + appendix = getOmittedItemsText?.(length - maxLength); + } + } + + return "'" + items.join("'" + separator + "'") + "'" + appendix; +} + +export function utf8Encode(text: string): string { + function codePointAt(text: string, index: number): number { + const ch = text.charCodeAt(index); + if (0xD800 <= ch && ch < 0xDC00) { // is high surrogate? + const nextCh = text.charCodeAt(index + 1); + if (0xDC00 <= nextCh && nextCh <= 0xDFFF) { // is low surrogate? + return (ch << 10) + nextCh - 0x35FDC00; + } + } + return ch; + } + + let utf8text = "", chunkStart = 0; + const fromCharCode = String.fromCharCode; + + let i; + for (i = 0; i < text.length; i++) { + const cp = codePointAt(text, i); + if (cp <= 0x7F) { + continue; + } + + // See also: https://stackoverflow.com/a/6240184/8656352 + + utf8text += text.slice(chunkStart, i); + if (cp <= 0x7FF) { + utf8text += fromCharCode(0xC0 | (cp >> 6)); + utf8text += fromCharCode(0x80 | (cp & 0x3F)); + } + else if (cp <= 0xFFFF) { + utf8text += fromCharCode(0xE0 | (cp >> 12)); + utf8text += fromCharCode(0x80 | ((cp >> 6) & 0x3F)); + utf8text += fromCharCode(0x80 | (cp & 0x3F)); + } + else { + utf8text += fromCharCode(0xF0 | (cp >> 18)); + utf8text += fromCharCode(0x80 | ((cp >> 12) & 0x3F)); + utf8text += fromCharCode(0x80 | ((cp >> 6) & 0x3F)); + utf8text += fromCharCode(0x80 | (cp & 0x3F)); + ++i; + } + chunkStart = i + 1; + } + + return utf8text += text.slice(chunkStart, i); +} + +export function parseFloatStrict(value: unknown): number { + // NOTE: parseFloat is too forgiving, it allows leading/trailing whitespace and ignores invalid characters after the number. + + if (typeof value === "number") { + return value; + } + + if (typeof value !== "string" || !value.length || /^\s*$|^\s*0[^\d.e]/.test(value)) { + return NaN; + } + + return +value; +} diff --git a/src/index.ts b/src/index.ts index 90933c1..d05a41c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -82,9 +82,12 @@ export { FormattableLogMessage } from "./ConfigCatLogger"; export type { IConfigCatCache } from "./ConfigCatCache"; -export type { IConfig, ISetting, ITargetingRule, IPercentageOption, SettingValue, VariationIdValue } from "./ProjectConfig"; +export type { + IConfig, ISegment, SettingTypeMap, SettingValue, VariationIdValue, ISettingValueContainer, ISettingUnion, ISetting, ITargetingRule, IPercentageOption, + ConditionTypeMap, IConditionUnion, ICondition, UserConditionComparisonValueTypeMap, IUserConditionUnion, IUserCondition, IPrerequisiteFlagCondition, ISegmentCondition +} from "./ProjectConfig"; -export { SettingType, Comparator } from "./ProjectConfig"; +export { SettingType, UserComparator, PrerequisiteFlagComparator, SegmentComparator } from "./ProjectConfig"; export type { IConfigCatClient }; @@ -94,7 +97,9 @@ export { SettingKeyValue } from "./ConfigCatClient"; export type { IEvaluationDetails, SettingTypeOf } from "./RolloutEvaluator"; -export { User } from "./RolloutEvaluator"; +export type { UserAttributeValue } from "./User"; + +export { User } from "./User"; export { OverrideBehaviour } from "./FlagOverrides"; diff --git a/test/ConfigCatCacheTests.ts b/test/ConfigCatCacheTests.ts index 05bb6ae..49ea6cb 100644 --- a/test/ConfigCatCacheTests.ts +++ b/test/ConfigCatCacheTests.ts @@ -64,12 +64,12 @@ describe("ConfigCatCache", () => { assert.equal(ProjectConfig.empty, cachedConfig); - assert.equal(1, logger.messages.filter(([level, eventId, _, err]) => + assert.equal(1, logger.events.filter(([level, eventId, _, err]) => level === LogLevel.Error && eventId === 2200 && (err as Error).message === "Operation failed :(").length); // 2. Set should overwrite the local cache and log the error. - logger.messages.length = 0; + logger.events.length = 0; const configJson = "{\"p\": {\"u\": \"http://example.com\", \"r\": 0}}"; const config = new ProjectConfig(configJson, new Config(configJson), ProjectConfig.generateTimestamp(), "\"ETAG\""); @@ -78,24 +78,24 @@ describe("ConfigCatCache", () => { assert.equal(config, configCache["cachedConfig"]); - assert.equal(1, logger.messages.filter(([level, eventId, _, err]) => + assert.equal(1, logger.events.filter(([level, eventId, _, err]) => level === LogLevel.Error && eventId === 2201 && (err as Error).message === "Operation failed :(").length); // 3. Get should log the error and return the local cache which was set previously. - logger.messages.length = 0; + logger.events.length = 0; cachedConfig = await configCache.get(cacheKey); assert.equal(config, cachedConfig); - assert.equal(1, logger.messages.filter(([level, eventId, _, err]) => + assert.equal(1, logger.events.filter(([level, eventId, _, err]) => level === LogLevel.Error && eventId === 2200 && (err as Error).message === "Operation failed :(").length); }); for (const [sdkKey, expectedCacheKey] of [ - ["test1", "147c5b4c2b2d7c77e1605b1a4309f0ea6684a0c6"], - ["test2", "c09513b1756de9e4bc48815ec7a142b2441ed4d5"], + ["test1", "7f845c43ecc95e202b91e271435935e6d1391e5d"], + ["test2", "a78b7e323ef543a272c74540387566a22415148a"], ]) { it(`Cache key generation should be platform independent - ${sdkKey}`, () => { const options = new ManualPollOptions(sdkKey, "common", "1.0.0"); diff --git a/test/ConfigCatClientOptionsTests.ts b/test/ConfigCatClientOptionsTests.ts index a87608f..8a131f8 100644 --- a/test/ConfigCatClientOptionsTests.ts +++ b/test/ConfigCatClientOptionsTests.ts @@ -27,7 +27,7 @@ describe("Options", () => { assert.equal("APIKEY", options.apiKey); assert.equal(30000, options.requestTimeoutMs); - assert.equal("https://cdn-global.configcat.com/configuration-files/APIKEY/config_v5.json?sdk=common/m-1.0.0", options.getUrl()); + assert.equal("https://cdn-global.configcat.com/configuration-files/APIKEY/config_v6.json?sdk=common/m-1.0.0", options.getUrl()); }); it("ManualPollOptions initialization With parameters works", () => { @@ -46,7 +46,7 @@ describe("Options", () => { assert.equal(fakeLogger, options.logger["logger"]); assert.equal("APIKEY", options.apiKey); assert.equal(10, options.requestTimeoutMs); - assert.equal("https://cdn-global.configcat.com/configuration-files/APIKEY/config_v5.json?sdk=common/m-1.0.0", options.getUrl()); + assert.equal("https://cdn-global.configcat.com/configuration-files/APIKEY/config_v6.json?sdk=common/m-1.0.0", options.getUrl()); assert.equal("http://fake-proxy.com:8080", options.proxy); }); @@ -55,8 +55,8 @@ describe("Options", () => { const options: ManualPollOptions = new ManualPollOptions("APIKEY", "common", "1.0.0", { baseUrl: "https://mycdn.example.org" }, null); assert.isDefined(options); - assert.equal("https://mycdn.example.org/configuration-files/APIKEY/config_v5.json?sdk=common/m-1.0.0", options.getUrl()); - assert.notEqual("https://cdn-global.configcat.com/configuration-files/APIKEY/config_v5.json?sdk=common/m-1.0.0", options.getUrl()); + assert.equal("https://mycdn.example.org/configuration-files/APIKEY/config_v6.json?sdk=common/m-1.0.0", options.getUrl()); + assert.notEqual("https://cdn-global.configcat.com/configuration-files/APIKEY/config_v6.json?sdk=common/m-1.0.0", options.getUrl()); }); it("AutoPollOptions initialization With -1 requestTimeoutMs ShouldThrowError", () => { @@ -71,7 +71,7 @@ describe("Options", () => { assert.isTrue(options.logger instanceof LoggerWrapper); assert.isTrue(options.logger["logger"] instanceof ConfigCatConsoleLogger); assert.equal("APIKEY", options.apiKey); - assert.equal("https://cdn-global.configcat.com/configuration-files/APIKEY/config_v5.json?sdk=common/a-1.0.0", options.getUrl()); + assert.equal("https://cdn-global.configcat.com/configuration-files/APIKEY/config_v6.json?sdk=common/a-1.0.0", options.getUrl()); assert.equal(60, options.pollIntervalSeconds); assert.equal(30000, options.requestTimeoutMs); assert.isDefined(options.cache); @@ -93,7 +93,7 @@ describe("Options", () => { assert.isDefined(options); assert.equal(fakeLogger, options.logger["logger"]); assert.equal("APIKEY", options.apiKey); - assert.equal("https://cdn-global.configcat.com/configuration-files/APIKEY/config_v5.json?sdk=common/a-1.0.0", options.getUrl()); + assert.equal("https://cdn-global.configcat.com/configuration-files/APIKEY/config_v6.json?sdk=common/a-1.0.0", options.getUrl()); assert.equal(59, options.pollIntervalSeconds); assert.equal(20, options.requestTimeoutMs); assert.equal("http://fake-proxy.com:8080", options.proxy); @@ -156,8 +156,8 @@ describe("Options", () => { const options: AutoPollOptions = new AutoPollOptions("APIKEY", "common", "1.0.0", { baseUrl: "https://mycdn.example.org" }, null); assert.isDefined(options); - assert.equal("https://mycdn.example.org/configuration-files/APIKEY/config_v5.json?sdk=common/a-1.0.0", options.getUrl()); - assert.notEqual("https://cdn-global.configcat.com/configuration-files/APIKEY/config_v5.json?sdk=common/a-1.0.0", options.getUrl()); + assert.equal("https://mycdn.example.org/configuration-files/APIKEY/config_v6.json?sdk=common/a-1.0.0", options.getUrl()); + assert.notEqual("https://cdn-global.configcat.com/configuration-files/APIKEY/config_v6.json?sdk=common/a-1.0.0", options.getUrl()); }); it("AutoPollOptions initialization With NaN 'maxInitWaitTimeSeconds' ShouldThrowError", () => { @@ -212,7 +212,7 @@ describe("Options", () => { const options: LazyLoadOptions = new LazyLoadOptions("APIKEY", "common", "1.0.0", null, null); assert.isDefined(options); assert.equal("APIKEY", options.apiKey); - assert.equal("https://cdn-global.configcat.com/configuration-files/APIKEY/config_v5.json?sdk=common/l-1.0.0", options.getUrl()); + assert.equal("https://cdn-global.configcat.com/configuration-files/APIKEY/config_v6.json?sdk=common/l-1.0.0", options.getUrl()); assert.equal(60, options.cacheTimeToLiveSeconds); assert.equal(30000, options.requestTimeoutMs); }); @@ -232,7 +232,7 @@ describe("Options", () => { assert.isDefined(options); assert.equal(fakeLogger, options.logger["logger"]); assert.equal("APIKEY", options.apiKey); - assert.equal("https://cdn-global.configcat.com/configuration-files/APIKEY/config_v5.json?sdk=common/l-1.0.0", options.getUrl()); + assert.equal("https://cdn-global.configcat.com/configuration-files/APIKEY/config_v6.json?sdk=common/l-1.0.0", options.getUrl()); assert.equal(59, options.cacheTimeToLiveSeconds); assert.equal(20, options.requestTimeoutMs); assert.equal("http://fake-proxy.com:8080", options.proxy); @@ -262,8 +262,8 @@ describe("Options", () => { const options: LazyLoadOptions = new LazyLoadOptions("APIKEY", "common", "1.0.0", { baseUrl: "https://mycdn.example.org" }, null); assert.isDefined(options); - assert.equal("https://mycdn.example.org/configuration-files/APIKEY/config_v5.json?sdk=common/l-1.0.0", options.getUrl()); - assert.notEqual("https://cdn-global.configcat.com/configuration-files/APIKEY/config_v5.json?sdk=common/l-1.0.0", options.getUrl()); + assert.equal("https://mycdn.example.org/configuration-files/APIKEY/config_v6.json?sdk=common/l-1.0.0", options.getUrl()); + assert.notEqual("https://cdn-global.configcat.com/configuration-files/APIKEY/config_v6.json?sdk=common/l-1.0.0", options.getUrl()); }); it("Options initialization With 'defaultCache' Should set option cache to passed instance", () => { diff --git a/test/ConfigCatClientTests.ts b/test/ConfigCatClientTests.ts index 09ec8d7..c19ce1c 100644 --- a/test/ConfigCatClientTests.ts +++ b/test/ConfigCatClientTests.ts @@ -11,14 +11,50 @@ import { MapOverrideDataSource, OverrideBehaviour } from "../src/FlagOverrides"; import { ClientReadyState, IProvidesHooks } from "../src/Hooks"; import { LazyLoadConfigService } from "../src/LazyLoadConfigService"; import { isWeakRefAvailable, setupPolyfills } from "../src/Polyfills"; -import { Config, IConfig, ProjectConfig, Setting } from "../src/ProjectConfig"; -import { IEvaluateResult, IEvaluationDetails, IRolloutEvaluator, User } from "../src/RolloutEvaluator"; +import { SettingValueContainer } from "../src/ProjectConfig"; +import { Config, IConfig, ProjectConfig, SettingValue } from "../src/ProjectConfig"; +import { EvaluateContext, IEvaluateResult, IEvaluationDetails, IRolloutEvaluator } from "../src/RolloutEvaluator"; +import { User } from "../src/User"; import { delay } from "../src/Utils"; import "./helpers/ConfigCatClientCacheExtensions"; -import { FakeCache, FakeConfigCatKernel, FakeConfigFetcher, FakeConfigFetcherBase, FakeConfigFetcherWithAlwaysVariableEtag, FakeConfigFetcherWithNullNewConfig, FakeConfigFetcherWithPercantageRules, FakeConfigFetcherWithRules, FakeConfigFetcherWithTwoCaseSensitiveKeys, FakeConfigFetcherWithTwoKeys, FakeConfigFetcherWithTwoKeysAndRules, FakeExternalAsyncCache, FakeExternalCache, FakeExternalCacheWithInitialData, FakeLogger } from "./helpers/fakes"; +import { FakeCache, FakeConfigCatKernel, FakeConfigFetcher, FakeConfigFetcherBase, FakeConfigFetcherWithAlwaysVariableEtag, FakeConfigFetcherWithNullNewConfig, FakeConfigFetcherWithPercentageOptions, FakeConfigFetcherWithRules, FakeConfigFetcherWithTwoCaseSensitiveKeys, FakeConfigFetcherWithTwoKeys, FakeConfigFetcherWithTwoKeysAndRules, FakeExternalAsyncCache, FakeExternalCache, FakeExternalCacheWithInitialData, FakeLogger } from "./helpers/fakes"; import { allowEventLoop } from "./helpers/utils"; describe("ConfigCatClient", () => { + for (const [sdkKey, customBaseUrl, isValid] of <[string, boolean, boolean][]>[ + ["sdk-key-90123456789012", false, false], + ["sdk-key-9012345678901/1234567890123456789012", false, false], + ["sdk-key-90123456789012/123456789012345678901", false, false], + ["sdk-key-90123456789012/12345678901234567890123", false, false], + ["sdk-key-901234567890123/1234567890123456789012", false, false], + ["sdk-key-90123456789012/1234567890123456789012", false, true], + ["configcat-sdk-1/sdk-key-90123456789012", false, false], + ["configcat-sdk-1/sdk-key-9012345678901/1234567890123456789012", false, false], + ["configcat-sdk-1/sdk-key-90123456789012/123456789012345678901", false, false], + ["configcat-sdk-1/sdk-key-90123456789012/12345678901234567890123", false, false], + ["configcat-sdk-1/sdk-key-901234567890123/1234567890123456789012", false, false], + ["configcat-sdk-1/sdk-key-90123456789012/1234567890123456789012", false, true], + ["configcat-sdk-2/sdk-key-90123456789012/1234567890123456789012", false, false], + ["configcat-proxy/", false, false], + ["configcat-proxy/", true, false], + ["configcat-proxy/sdk-key-90123456789012", false, false], + ["configcat-proxy/sdk-key-90123456789012", true, true], + ]) { + it(`SDK key format should be validated - sdkKey: ${sdkKey} | customBaseUrl: ${customBaseUrl}`, () => { + const options: IManualPollOptions = customBaseUrl ? { baseUrl: "https://my-configcat-proxy" } : {}; + const configCatKernel: FakeConfigCatKernel = { configFetcher: new FakeConfigFetcher(), sdkType: "common", sdkVersion: "1.0.0" }; + + if (isValid) { + ConfigCatClient.get(sdkKey, PollingMode.ManualPoll, options, configCatKernel).dispose(); + } + else { + assert.throws(() => { + ConfigCatClient.get(sdkKey, PollingMode.ManualPoll, options, configCatKernel).dispose(); + }, "Invalid 'sdkKey' value"); + } + }); + } + it("Initialization With AutoPollOptions should create an instance, getValueAsync works", async () => { const configCatKernel: FakeConfigCatKernel = { configFetcher: new FakeConfigFetcher(), sdkType: "common", sdkVersion: "1.0.0" }; const options: AutoPollOptions = new AutoPollOptions("APIKEY", "common", "1.0.0", { logger: null }, null); @@ -175,8 +211,8 @@ describe("ConfigCatClient", () => { assert.strictEqual(user, actual.user); assert.isDefined(actual.errorMessage); assert.isUndefined(actual.errorException); - assert.isUndefined(actual.matchedEvaluationRule); - assert.isUndefined(actual.matchedEvaluationPercentageRule); + assert.isUndefined(actual.matchedTargetingRule); + assert.isUndefined(actual.matchedPercentageOption); assert.equal(1, flagEvaluatedEvents.length); assert.strictEqual(actual, flagEvaluatedEvents[0]); @@ -216,8 +252,8 @@ describe("ConfigCatClient", () => { assert.strictEqual(user, actual.user); assert.isUndefined(actual.errorMessage); assert.isUndefined(actual.errorException); - assert.isUndefined(actual.matchedEvaluationRule); - assert.isUndefined(actual.matchedEvaluationPercentageRule); + assert.isUndefined(actual.matchedTargetingRule); + assert.isUndefined(actual.matchedPercentageOption); assert.equal(1, flagEvaluatedEvents.length); assert.strictEqual(actual, flagEvaluatedEvents[0]); @@ -258,10 +294,10 @@ describe("ConfigCatClient", () => { assert.strictEqual(user, actual.user); assert.isUndefined(actual.errorMessage); assert.isUndefined(actual.errorException); - assert.isDefined(actual.matchedEvaluationRule); - assert.strictEqual(actual.value, actual.matchedEvaluationRule?.value); - assert.strictEqual(actual.variationId, actual.matchedEvaluationRule?.variationId); - assert.isUndefined(actual.matchedEvaluationPercentageRule); + assert.isDefined(actual.matchedTargetingRule); + assert.strictEqual(actual.value, (actual.matchedTargetingRule?.then as SettingValueContainer).value); + assert.strictEqual(actual.variationId, (actual.matchedTargetingRule?.then as SettingValueContainer).variationId); + assert.isUndefined(actual.matchedPercentageOption); assert.equal(1, flagEvaluatedEvents.length); assert.strictEqual(actual, flagEvaluatedEvents[0]); @@ -275,7 +311,7 @@ describe("ConfigCatClient", () => { const defaultValue = "N/A"; const timestamp = new Date().getTime(); - const configFetcherClass = FakeConfigFetcherWithPercantageRules; + const configFetcherClass = FakeConfigFetcherWithPercentageOptions; const cachedPc = new ProjectConfig(configFetcherClass.configJson, new Config(JSON.parse(configFetcherClass.configJson)), timestamp, "etag"); const configCache = new FakeCache(cachedPc); const configCatKernel: FakeConfigCatKernel = { configFetcher: new configFetcherClass(), sdkType: "common", sdkVersion: "1.0.0" }; @@ -301,10 +337,10 @@ describe("ConfigCatClient", () => { assert.strictEqual(user, actual.user); assert.isUndefined(actual.errorMessage); assert.isUndefined(actual.errorException); - assert.isUndefined(actual.matchedEvaluationRule); - assert.isDefined(actual.matchedEvaluationPercentageRule); - assert.strictEqual(actual.value, actual.matchedEvaluationPercentageRule?.value); - assert.strictEqual(actual.variationId, actual.matchedEvaluationPercentageRule?.variationId); + assert.isUndefined(actual.matchedTargetingRule); + assert.isDefined(actual.matchedPercentageOption); + assert.strictEqual(actual.value, actual.matchedPercentageOption?.value); + assert.strictEqual(actual.variationId, actual.matchedPercentageOption?.variationId); assert.equal(1, flagEvaluatedEvents.length); assert.strictEqual(actual, flagEvaluatedEvents[0]); @@ -327,7 +363,7 @@ describe("ConfigCatClient", () => { const err = new Error("Something went wrong."); client["evaluator"] = new class implements IRolloutEvaluator { - evaluate(setting: Setting, key: string, defaultValue: any, user: User | undefined, remoteConfig: ProjectConfig | null, defaultVariationId?: any): IEvaluateResult { + evaluate(defaultValue: SettingValue, context: EvaluateContext): IEvaluateResult { throw err; } }; @@ -353,8 +389,8 @@ describe("ConfigCatClient", () => { assert.strictEqual(user, actual.user); assert.isDefined(actual.errorMessage); assert.strictEqual(err, actual.errorException); - assert.isUndefined(actual.matchedEvaluationRule); - assert.isUndefined(actual.matchedEvaluationPercentageRule); + assert.isUndefined(actual.matchedTargetingRule); + assert.isUndefined(actual.matchedPercentageOption); assert.equal(1, flagEvaluatedEvents.length); assert.strictEqual(actual, flagEvaluatedEvents[0]); @@ -405,8 +441,8 @@ describe("ConfigCatClient", () => { assert.strictEqual(user, actualDetails.user); assert.isUndefined(actualDetails.errorMessage); assert.isUndefined(actualDetails.errorException); - assert.isUndefined(actualDetails.matchedEvaluationRule); - assert.isUndefined(actualDetails.matchedEvaluationPercentageRule); + assert.isUndefined(actualDetails.matchedTargetingRule); + assert.isUndefined(actualDetails.matchedPercentageOption); const flagEvaluatedDetails = flagEvaluatedEvents.find(details => details.key === key)!; @@ -430,7 +466,7 @@ describe("ConfigCatClient", () => { const err = new Error("Something went wrong."); client["evaluator"] = new class implements IRolloutEvaluator { - evaluate(setting: Setting, key: string, defaultValue: any, user: User | undefined, remoteConfig: ProjectConfig | null, defaultVariationId?: any): IEvaluateResult { + evaluate(defaultValue: SettingValue, context: EvaluateContext): IEvaluateResult { throw err; } }; @@ -458,8 +494,8 @@ describe("ConfigCatClient", () => { assert.strictEqual(user, actualDetails.user); assert.isDefined(actualDetails.errorMessage); assert.strictEqual(err, actualDetails.errorException); - assert.isUndefined(actualDetails.matchedEvaluationRule); - assert.isUndefined(actualDetails.matchedEvaluationPercentageRule); + assert.isUndefined(actualDetails.matchedTargetingRule); + assert.isUndefined(actualDetails.matchedPercentageOption); const flagEvaluatedDetails = flagEvaluatedEvents.find(details => details.key === key)!; @@ -609,7 +645,12 @@ describe("ConfigCatClient", () => { assert.isAtMost(elapsedMilliseconds, (maxInitWaitTimeSeconds * 1000) + 50); // 50 ms for tolerance assert.equal(state, ClientReadyState.NoFlagData); - assert.equal(client.snapshot().getValue("debug", false), false); + + const snapshot = client.snapshot(); + assert.equal(snapshot.getValue("debug", false), false); + const evaluationDetails = snapshot.getValueDetails("debug", false); + assert.isTrue(evaluationDetails.isDefaultValue); + assert.equal(evaluationDetails.value, false); }); it("AutoPoll - should wait for maxInitWaitTimeSeconds and return cached", async () => { @@ -635,7 +676,12 @@ describe("ConfigCatClient", () => { assert.isAtMost(elapsedMilliseconds, (maxInitWaitTimeSeconds * 1000) + 50); // 50 ms for tolerance assert.equal(state, ClientReadyState.HasCachedFlagDataOnly); - assert.equal(client.snapshot().getValue("debug", false), true); + + const snapshot = client.snapshot(); + assert.equal(snapshot.getValue("debug", false), true); + const evaluationDetails = snapshot.getValueDetails("debug", false); + assert.isFalse(evaluationDetails.isDefaultValue); + assert.equal(evaluationDetails.value, true); }); it("LazyLoad - return cached", async () => { @@ -843,7 +889,7 @@ describe("ConfigCatClient", () => { it("Initialization With AutoPollOptions with expired cache - getValue should take care of maxInitWaitTimeSeconds", done => { const configFetcher = new FakeConfigFetcher(500); - const configJson = "{\"f\": { \"debug\": { \"v\": false, \"i\": \"abcdefgh\", \"t\": 0, \"p\": [], \"r\": [] } } }"; + const configJson = "{\"f\": { \"debug\": { \"v\": { \"b\": false }, \"i\": \"abcdefgh\", \"t\": 0, \"p\": [], \"r\": [] } } }"; const configCache = new FakeCache(new ProjectConfig(configJson, new Config(JSON.parse(configJson)), new Date().getTime() - 10000000, "etag2")); const configCatKernel: FakeConfigCatKernel = { configFetcher, defaultCacheFactory: () => configCache, sdkType: "common", sdkVersion: "1.0.0" }; const options: AutoPollOptions = new AutoPollOptions("APIKEY", "common", "1.0.0", { maxInitWaitTimeSeconds: 10 }); @@ -857,7 +903,7 @@ describe("ConfigCatClient", () => { it("Initialization With AutoPollOptions with expired cache - getValueAsync should take care of maxInitWaitTimeSeconds", async () => { const configFetcher = new FakeConfigFetcher(500); - const configJson = "{\"f\": { \"debug\": { \"v\": false, \"i\": \"abcdefgh\", \"t\": 0, \"p\": [], \"r\": [] } } }"; + const configJson = "{\"f\": { \"debug\": { \"v\": { \"b\": false }, \"i\": \"abcdefgh\", \"t\": 0, \"p\": [], \"r\": [] } } }"; const configCache = new FakeCache(new ProjectConfig(configJson, new Config(JSON.parse(configJson)), new Date().getTime() - 10000000, "etag2")); const configCatKernel: FakeConfigCatKernel = { configFetcher, defaultCacheFactory: () => configCache, sdkType: "common", sdkVersion: "1.0.0" }; const options: AutoPollOptions = new AutoPollOptions("APIKEY", "common", "1.0.0", { maxInitWaitTimeSeconds: 10 }); @@ -885,7 +931,7 @@ describe("ConfigCatClient", () => { it(`get() should return cached instance ${passOptionsToSecondGet ? "with" : "without"} warning`, done => { // Arrange - const sdkKey = "test"; + const sdkKey = "test-67890123456789012/1234567890123456789012"; const logger = new FakeLogger(LogLevel.Debug); @@ -895,11 +941,11 @@ describe("ConfigCatClient", () => { // Act const client1 = ConfigCatClient.get(sdkKey, PollingMode.ManualPoll, options, configCatKernel); - const messages1 = [...logger.messages]; + const logEvents1 = [...logger.events]; logger.reset(); const client2 = ConfigCatClient.get(sdkKey, PollingMode.ManualPoll, passOptionsToSecondGet ? options : null, configCatKernel); - const messages2 = [...logger.messages]; + const logEvents2 = [...logger.events]; const instanceCount = ConfigCatClient["instanceCache"].getAliveCount(); @@ -910,13 +956,13 @@ describe("ConfigCatClient", () => { assert.equal(1, instanceCount); assert.strictEqual(client1, client2); - assert.isEmpty(messages1.filter(([, , msg]) => msg.indexOf("the specified options are ignored") >= 0)); + assert.isEmpty(logEvents1.filter(([, , msg]) => msg.toString().indexOf("the specified options are ignored") >= 0)); if (passOptionsToSecondGet) { - assert.isNotEmpty(messages2.filter(([, , msg]) => msg.indexOf("the specified options are ignored") >= 0)); + assert.isNotEmpty(logEvents2.filter(([, , msg]) => msg.toString().indexOf("the specified options are ignored") >= 0)); } else { - assert.isEmpty(messages2.filter(([, , msg]) => msg.indexOf("the specified options are ignored") >= 0)); + assert.isEmpty(logEvents2.filter(([, , msg]) => msg.toString().indexOf("the specified options are ignored") >= 0)); } done(); @@ -926,7 +972,7 @@ describe("ConfigCatClient", () => { it("dispose() should remove cached instance", done => { // Arrange - const sdkKey = "test"; + const sdkKey = "test-67890123456789012/1234567890123456789012"; const configCatKernel: FakeConfigCatKernel = { configFetcher: new FakeConfigFetcher(), sdkType: "common", sdkVersion: "1.0.0" }; @@ -951,7 +997,7 @@ describe("ConfigCatClient", () => { it("dispose() should remove current cached instance only", done => { // Arrange - const sdkKey = "test"; + const sdkKey = "test-67890123456789012/1234567890123456789012"; const configCatKernel: FakeConfigCatKernel = { configFetcher: new FakeConfigFetcher(), sdkType: "common", sdkVersion: "1.0.0" }; @@ -991,7 +1037,7 @@ describe("ConfigCatClient", () => { it("disposeAll() should remove all cached instances", done => { // Arrange - const sdkKey1 = "test1", sdkKey2 = "test2"; + const sdkKey1 = "test1-7890123456789012/1234567890123456789012", sdkKey2 = "test2-7890123456789012/1234567890123456789012"; const configCatKernel: FakeConfigCatKernel = { configFetcher: new FakeConfigFetcher(), sdkType: "common", sdkVersion: "1.0.0" }; @@ -1025,7 +1071,7 @@ describe("ConfigCatClient", () => { } const isFinalizationRegistryAvailable = typeof FinalizationRegistry !== "undefined"; - const sdkKey1 = "test1", sdkKey2 = "test2"; + const sdkKey1 = "test1-7890123456789012/1234567890123456789012", sdkKey2 = "test2-7890123456789012/1234567890123456789012"; const logger = new FakeLogger(LogLevel.Debug); const configCatKernel: FakeConfigCatKernel = { configFetcher: new FakeConfigFetcher(), sdkType: "common", sdkVersion: "1.0.0" }; @@ -1058,7 +1104,7 @@ describe("ConfigCatClient", () => { if (isFinalizationRegistryAvailable) { assert.equal(0, instanceCount2); - assert.equal(2, logger.messages.filter(([, , msg]) => msg.indexOf("finalize() called") >= 0).length); + assert.equal(2, logger.events.filter(([, , msg]) => msg.toString().indexOf("finalize() called") >= 0).length); } else { // When finalization is not available, Auto Polling won't be stopped. @@ -1074,7 +1120,7 @@ describe("ConfigCatClient", () => { this.skip(); } - const sdkKey1 = "test1"; + const sdkKey1 = "test1-7890123456789012/1234567890123456789012"; const configCatKernel: FakeConfigCatKernel = { configFetcher: new FakeConfigFetcher(), sdkType: "common", sdkVersion: "1.0.0" }; diff --git a/test/ConfigServiceBaseTests.ts b/test/ConfigServiceBaseTests.ts index df8f2d7..6e55f66 100644 --- a/test/ConfigServiceBaseTests.ts +++ b/test/ConfigServiceBaseTests.ts @@ -740,7 +740,7 @@ describe("ConfigServiceBaseTests", () => { }); function createProjectConfig(eTag = "etag"): ProjectConfig { - const configJson = "{\"f\": { \"debug\": { \"v\": true, \"i\": \"abcdefgh\", \"t\": 0, \"p\": [], \"r\": [] } } }"; + const configJson = "{\"f\": { \"debug\": { \"v\": { \"b\": true }, \"i\": \"abcdefgh\", \"t\": 0, \"p\": [], \"r\": [] } } }"; return new ProjectConfig( configJson, new Config(JSON.parse(configJson)), diff --git a/test/ConfigV2EvaluationTests.ts b/test/ConfigV2EvaluationTests.ts new file mode 100644 index 0000000..331e6bf --- /dev/null +++ b/test/ConfigV2EvaluationTests.ts @@ -0,0 +1,295 @@ +import { assert } from "chai"; +import "mocha"; +import { FlagOverrides, IManualPollOptions, MapOverrideDataSource, OverrideBehaviour, SettingValue, User, UserAttributeValue } from "../src"; +import { LogLevel, LoggerWrapper } from "../src/ConfigCatLogger"; +import { RolloutEvaluator, evaluate, isAllowedValue } from "../src/RolloutEvaluator"; +import { errorToString } from "../src/Utils"; +import { CdnConfigLocation, LocalFileConfigLocation } from "./helpers/ConfigLocation"; +import { FakeLogger } from "./helpers/fakes"; +import { HttpConfigFetcher } from "./helpers/HttpConfigFetcher"; +import { createClientWithManualPoll, sdkType, sdkVersion } from "./helpers/utils"; + +describe("Setting evaluation (config v2)", () => { + + for (const [key, dependencyCycle] of <[string, string][]>[ + ["key1", "'key1' -> 'key1'"], + ["key2", "'key2' -> 'key3' -> 'key2'"], + ["key4", "'key4' -> 'key3' -> 'key2' -> 'key3'"], + ]) { + it("Prerequisite flag circular dependency detection", async () => { + const configLocation = new LocalFileConfigLocation("test", "data", "test_circulardependency_v6.json"); + const config = await configLocation.fetchConfigAsync(); + + const fakeLogger = new FakeLogger(); + const logger = new LoggerWrapper(fakeLogger); + const evaluator = new RolloutEvaluator(logger); + + try { + evaluate(evaluator, config.settings, key, null, void 0, null, logger); + assert.fail("evaluate should throw."); + } + catch (err) { + const errMsg = errorToString(err); + assert.include(errMsg, "Circular dependency detected"); + assert.include(errMsg, dependencyCycle); + } + }); + } + + for (const [key, prerequisiteFlagKey, prerequisiteFlagValue, expectedValue] of <[string, string, unknown, string | null][]>[ + ["stringDependsOnBool", "mainBoolFlag", true, "Dog"], + ["stringDependsOnBool", "mainBoolFlag", false, "Cat"], + ["stringDependsOnBool", "mainBoolFlag", "1", null], + ["stringDependsOnBool", "mainBoolFlag", 1, null], + ["stringDependsOnBool", "mainBoolFlag", [true], null], + ["stringDependsOnBool", "mainBoolFlag", null, null], + ["stringDependsOnBool", "mainBoolFlag", void 0, null], + ["stringDependsOnString", "mainStringFlag", "private", "Dog"], + ["stringDependsOnString", "mainStringFlag", "Private", "Cat"], + ["stringDependsOnString", "mainStringFlag", true, null], + ["stringDependsOnString", "mainStringFlag", 1, null], + ["stringDependsOnString", "mainStringFlag", ["private"], null], + ["stringDependsOnString", "mainStringFlag", null, null], + ["stringDependsOnString", "mainStringFlag", void 0, null], + ["stringDependsOnInt", "mainIntFlag", 2, "Dog"], + ["stringDependsOnInt", "mainIntFlag", 1, "Cat"], + ["stringDependsOnInt", "mainIntFlag", "2", null], + ["stringDependsOnInt", "mainIntFlag", true, null], + ["stringDependsOnInt", "mainIntFlag", [2], null], + ["stringDependsOnInt", "mainIntFlag", null, null], + ["stringDependsOnInt", "mainIntFlag", void 0, null], + ["stringDependsOnDouble", "mainDoubleFlag", 0.1, "Dog"], + ["stringDependsOnDouble", "mainDoubleFlag", 0.11, "Cat"], + ["stringDependsOnDouble", "mainDoubleFlag", "0.1", null], + ["stringDependsOnDouble", "mainDoubleFlag", true, null], + ["stringDependsOnDouble", "mainDoubleFlag", [0.1], null], + ["stringDependsOnDouble", "mainDoubleFlag", null, null], + ["stringDependsOnDouble", "mainDoubleFlag", void 0, null], + ]) { + it(`Prerequisite flag comparison value type mismatch - key: ${key} | prerequisiteFlagKey: ${prerequisiteFlagKey} | prerequisiteFlagValue: ${prerequisiteFlagValue}`, async () => { + const overrideMap: { [name: string]: NonNullable } = { + [prerequisiteFlagKey]: prerequisiteFlagValue as unknown as NonNullable + }; + + const fakeLogger = new FakeLogger(); + const options: IManualPollOptions = { + flagOverrides: new FlagOverrides(new MapOverrideDataSource(overrideMap), OverrideBehaviour.LocalOverRemote), + logger: fakeLogger + }; + + // https://app.configcat.com/v2/e7a75611-4256-49a5-9320-ce158755e3ba/08dbc325-7f69-4fd4-8af4-cf9f24ec8ac9/08dbc325-9b74-45cb-86d0-4d61c25af1aa/08dbc325-9ebd-4587-8171-88f76a3004cb + const client = createClientWithManualPoll("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/JoGwdqJZQ0K2xDy7LnbyOg", + { sdkType, sdkVersion, configFetcher: new HttpConfigFetcher() }, options); + + try { + await client.forceRefreshAsync(); + const actualValue = await client.getValueAsync(key, null); + + assert.strictEqual(expectedValue, actualValue); + + if (actualValue == null) { + const errors = fakeLogger.events.filter(([level]) => level === LogLevel.Error); + assert.strictEqual(1, errors.length); + const [, eventId, , err] = errors[0]; + assert.strictEqual(1002, eventId); + + const errMsg = errorToString(err); + if (prerequisiteFlagValue === null) { + assert.include(errMsg, "Setting value is null"); + } + else if (prerequisiteFlagValue === void 0) { + assert.include(errMsg, "Setting value is undefined"); + } + else if (!isAllowedValue(prerequisiteFlagValue)) { + assert.match(errMsg, /Setting value '[^']+' is of an unsupported type/); + } + else { + assert.match(errMsg, /Type mismatch between comparison value '[^']+' and prerequisite flag '[^']+'/); + } + } + } + finally { + client.dispose(); + } + }); + } + + for (const [key, userId, email, overrideBehaviour, expectedValue] of <[string, string, string, OverrideBehaviour, string][]>[ + ["stringDependsOnString", "1", "john@sensitivecompany.com", null, "Dog"], + ["stringDependsOnString", "1", "john@sensitivecompany.com", OverrideBehaviour.RemoteOverLocal, "Dog"], + ["stringDependsOnString", "1", "john@sensitivecompany.com", OverrideBehaviour.LocalOverRemote, "Dog"], + ["stringDependsOnString", "1", "john@sensitivecompany.com", OverrideBehaviour.LocalOnly, null], + ["stringDependsOnString", "2", "john@notsensitivecompany.com", null, "Cat"], + ["stringDependsOnString", "2", "john@notsensitivecompany.com", OverrideBehaviour.RemoteOverLocal, "Cat"], + ["stringDependsOnString", "2", "john@notsensitivecompany.com", OverrideBehaviour.LocalOverRemote, "Dog"], + ["stringDependsOnString", "2", "john@notsensitivecompany.com", OverrideBehaviour.LocalOnly, null], + ["stringDependsOnInt", "1", "john@sensitivecompany.com", null, "Dog"], + ["stringDependsOnInt", "1", "john@sensitivecompany.com", OverrideBehaviour.RemoteOverLocal, "Dog"], + ["stringDependsOnInt", "1", "john@sensitivecompany.com", OverrideBehaviour.LocalOverRemote, "Falcon"], + ["stringDependsOnInt", "1", "john@sensitivecompany.com", OverrideBehaviour.LocalOnly, "Falcon"], + ["stringDependsOnInt", "2", "john@notsensitivecompany.com", null, "Cat"], + ["stringDependsOnInt", "2", "john@notsensitivecompany.com", OverrideBehaviour.RemoteOverLocal, "Cat"], + ["stringDependsOnInt", "2", "john@notsensitivecompany.com", OverrideBehaviour.LocalOverRemote, "Falcon"], + ["stringDependsOnInt", "2", "john@notsensitivecompany.com", OverrideBehaviour.LocalOnly, "Falcon"], + ]) { + it(`Prerequisite flag override - key: ${key} | userId: ${userId} | email: ${email} | overrideBehavior: ${overrideBehaviour}`, async () => { + const overrideMap: { [name: string]: NonNullable } = { + ["mainStringFlag"]: "private", // to check the case where a prerequisite flag is overridden (dependent flag: 'stringDependsOnString') + ["stringDependsOnInt"]: "Falcon" // to check the case where a dependent flag is overridden (prerequisite flag: 'mainIntFlag') + }; + + const options: IManualPollOptions = { + flagOverrides: new FlagOverrides(new MapOverrideDataSource(overrideMap), overrideBehaviour) + }; + + // https://app.configcat.com/v2/e7a75611-4256-49a5-9320-ce158755e3ba/08dbc325-7f69-4fd4-8af4-cf9f24ec8ac9/08dbc325-9b74-45cb-86d0-4d61c25af1aa/08dbc325-9ebd-4587-8171-88f76a3004cb + const client = createClientWithManualPoll("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/JoGwdqJZQ0K2xDy7LnbyOg", + { sdkType, sdkVersion, configFetcher: new HttpConfigFetcher() }, options); + + try { + await client.forceRefreshAsync(); + const actualValue = await client.getValueAsync(key, null, new User(userId, email)); + + assert.strictEqual(expectedValue, actualValue); + } + finally { + client.dispose(); + } + }); + } + + // https://app.configcat.com/v2/e7a75611-4256-49a5-9320-ce158755e3ba/08dbc325-7f69-4fd4-8af4-cf9f24ec8ac9/08dbc325-9e4e-4f59-86b2-5da50924b6ca/08dbc325-9ebd-4587-8171-88f76a3004cb + for (const [sdkKey, key, userId, email, percentageBase, expectedReturnValue, expectedIsExpectedMatchedTargetingRuleSet, expectedIsExpectedMatchedPercentageOptionSet] of + <[string, string, string | null, string | null, string | null, string, boolean, boolean][]>[ + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/P4e3fAz_1ky2-Zg2e4cbkw", "stringMatchedTargetingRuleAndOrPercentageOption", null, null, null, "Cat", false, false], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/P4e3fAz_1ky2-Zg2e4cbkw", "stringMatchedTargetingRuleAndOrPercentageOption", "12345", null, null, "Cat", false, false], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/P4e3fAz_1ky2-Zg2e4cbkw", "stringMatchedTargetingRuleAndOrPercentageOption", "12345", "a@example.com", null, "Dog", true, false], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/P4e3fAz_1ky2-Zg2e4cbkw", "stringMatchedTargetingRuleAndOrPercentageOption", "12345", "a@configcat.com", null, "Cat", false, false], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/P4e3fAz_1ky2-Zg2e4cbkw", "stringMatchedTargetingRuleAndOrPercentageOption", "12345", "a@configcat.com", "", "Frog", true, true], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/P4e3fAz_1ky2-Zg2e4cbkw", "stringMatchedTargetingRuleAndOrPercentageOption", "12345", "a@configcat.com", "US", "Fish", true, true], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/P4e3fAz_1ky2-Zg2e4cbkw", "stringMatchedTargetingRuleAndOrPercentageOption", "12345", "b@configcat.com", null, "Cat", false, false], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/P4e3fAz_1ky2-Zg2e4cbkw", "stringMatchedTargetingRuleAndOrPercentageOption", "12345", "b@configcat.com", "", "Falcon", false, true], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/P4e3fAz_1ky2-Zg2e4cbkw", "stringMatchedTargetingRuleAndOrPercentageOption", "12345", "b@configcat.com", "US", "Spider", false, true], + ]) { + it(`IEvaluationDetails.matchedTargetingRule/matchedTargetingOption - sdkKey: ${sdkKey} | key: ${key} | userId: ${userId} | email: ${email} | percentageBase: ${percentageBase}`, async () => { + const configLocation = new CdnConfigLocation(sdkKey); + const config = await configLocation.fetchConfigAsync(); + + const fakeLogger = new FakeLogger(); + const logger = new LoggerWrapper(fakeLogger); + const evaluator = new RolloutEvaluator(logger); + + const user = userId != null ? new User(userId, email!, void 0, { ["PercentageBase"]: percentageBase! }) : null; + + const evaluationDetails = evaluate(evaluator, config.settings, key, null, user!, null, logger); + + assert.strictEqual(evaluationDetails.value, expectedReturnValue); + assert.strictEqual(evaluationDetails.matchedTargetingRule !== void 0, expectedIsExpectedMatchedTargetingRuleSet); + assert.strictEqual(evaluationDetails.matchedPercentageOption !== void 0, expectedIsExpectedMatchedPercentageOptionSet); + }); + } + + it("User Object attribute conversions - text comparisons", async () => { + const configLocation = new CdnConfigLocation("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ"); + const config = await configLocation.fetchConfigCachedAsync(); + + const fakeLogger = new FakeLogger(); + const logger = new LoggerWrapper(fakeLogger); + const evaluator = new RolloutEvaluator(logger); + + const customAttributeName = "Custom1", customAttributeValue = 42; + const user = new User("12345", void 0, void 0, { [customAttributeName]: customAttributeValue }); + + const key = "boolTextEqualsNumber"; + const evaluationDetails = evaluate(evaluator, config.settings, key, null, user!, null, logger); + + assert.strictEqual(evaluationDetails.value, true); + + const warnings = fakeLogger.events.filter(([level]) => level === LogLevel.Warn); + assert.strictEqual(warnings.length, 1); + + const [, eventId, message] = warnings[0]; + assert.strictEqual(eventId, 3005); + const expectedAttributeValueText = customAttributeValue + ""; + assert.strictEqual(message.toString(), `Evaluation of condition (User.${customAttributeName} EQUALS '${expectedAttributeValueText}') for setting '${key}' may not produce the expected result (the User.${customAttributeName} attribute is not a string value, thus it was automatically converted to the string value '${expectedAttributeValueText}'). Please make sure that using a non-string value was intended.`); + }); + + for (const [sdkKey, key, userId, customAttributeName, customAttributeValue, expectedReturnValue] of + <[string, string, string, string, UserAttributeValue, string][]>[ + // SemVer-based comparisons + ["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/iV8vH2MBakKxkFZylxHmTg", "lessThanWithPercentage", "12345", "Custom1", "0.0", "20%"], + ["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/iV8vH2MBakKxkFZylxHmTg", "lessThanWithPercentage", "12345", "Custom1", "0.9.9", "< 1.0.0"], + ["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/iV8vH2MBakKxkFZylxHmTg", "lessThanWithPercentage", "12345", "Custom1", "1.0.0", "20%"], + ["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/iV8vH2MBakKxkFZylxHmTg", "lessThanWithPercentage", "12345", "Custom1", "1.1", "20%"], + ["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/iV8vH2MBakKxkFZylxHmTg", "lessThanWithPercentage", "12345", "Custom1", 0, "20%"], + ["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/iV8vH2MBakKxkFZylxHmTg", "lessThanWithPercentage", "12345", "Custom1", 0.9, "20%"], + ["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/iV8vH2MBakKxkFZylxHmTg", "lessThanWithPercentage", "12345", "Custom1", 2, "20%"], + // Number-based comparisons + ["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", -Infinity, "<2.1"], + ["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", -1, "<2.1"], + ["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", 2, "<2.1"], + ["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", 2.1, "<=2,1"], + ["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", 3, "<>4.2"], + ["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", 5, ">=5"], + ["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", Infinity, ">5"], + ["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", NaN, "<>4.2"], + ["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "-Infinity", "<2.1"], + ["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "-1", "<2.1"], + ["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "2", "<2.1"], + ["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "2.1", "<=2,1"], + ["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "2,1", "<=2,1"], + ["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "3", "<>4.2"], + ["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "5", ">=5"], + ["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "Infinity", ">5"], + ["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "NaN", "<>4.2"], + ["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "NaNa", "80%"], + // Date time-based comparisons + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", new Date("2023-03-31T23:59:59.9990000Z"), false], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", new Date("2023-04-01T01:59:59.9990000+02:00"), false], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", new Date("2023-04-01T00:00:00.0010000Z"), true], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", new Date("2023-04-01T02:00:00.0010000+02:00"), true], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", new Date("2023-04-30T23:59:59.9990000Z"), true], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", new Date("2023-05-01T01:59:59.9990000+02:00"), true], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", new Date("2023-05-01T00:00:00.0010000Z"), false], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", new Date("2023-05-01T02:00:00.0010000+02:00"), false], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", -Infinity, false], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", 1680307199.999, false], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", 1680307200.001, true], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", 1682899199.999, true], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", 1682899200.001, false], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", Infinity, false], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", NaN, false], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", 1680307199, false], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", 1680307201, true], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", 1682899199, true], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", 1682899201, false], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", "-Infinity", false], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", "1680307199.999", false], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", "1680307200.001", true], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", "1682899199.999", true], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", "1682899200.001", false], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", "+Infinity", false], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", "NaN", false], + // String array-based comparisons + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "stringArrayContainsAnyOfDogDefaultCat", "12345", "Custom1", ["x", "read"], "Dog"], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "stringArrayContainsAnyOfDogDefaultCat", "12345", "Custom1", ["x", "Read"], "Cat"], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "stringArrayContainsAnyOfDogDefaultCat", "12345", "Custom1", "[\"x\", \"read\"]", "Dog"], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "stringArrayContainsAnyOfDogDefaultCat", "12345", "Custom1", "[\"x\", \"Read\"]", "Cat"], + ["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "stringArrayContainsAnyOfDogDefaultCat", "12345", "Custom1", "x, read", "Cat"], + ]) { + it(`User Object attribute conversions - non-text comparisons - sdkKey: ${sdkKey} | key: ${key} | userId: ${userId} | customAttributeName: ${customAttributeName} | customAttributeValue: ${customAttributeValue}`, async () => { + const configLocation = new CdnConfigLocation(sdkKey); + const config = await configLocation.fetchConfigCachedAsync(); + + const fakeLogger = new FakeLogger(); + const logger = new LoggerWrapper(fakeLogger); + const evaluator = new RolloutEvaluator(logger); + + const user = new User(userId, void 0, void 0, { [customAttributeName]: customAttributeValue }); + + const evaluationDetails = evaluate(evaluator, config.settings, key, null, user!, null, logger); + + assert.strictEqual(evaluationDetails.value, expectedReturnValue); + }); + } +}); diff --git a/test/DataGovernanceTests.ts b/test/DataGovernanceTests.ts index 13811ec..487aa5a 100644 --- a/test/DataGovernanceTests.ts +++ b/test/DataGovernanceTests.ts @@ -10,7 +10,7 @@ const globalUrl = "https://cdn-global.configcat.com"; const euOnlyUrl = "https://cdn-eu.configcat.com"; const customUrl = "https://cdn-custom.configcat.com"; const forcedUrl = "https://cdn-forced.configcat.com"; -const testObject = { test: "test" }; +const testObject = { test: { t: 0, v: { b: true } } }; describe("DataGovernance", () => { @@ -303,7 +303,7 @@ export class FakeConfigServiceBase extends ConfigServiceBase { } private getUrl(baseUrl: string) { - return baseUrl + "/configuration-files/API_KEY/config_v5.json?sdk=" + this.options.clientVersion; + return baseUrl + "/configuration-files/API_KEY/config_v6.json?sdk=" + this.options.clientVersion; } getCacheState(cachedConfig: ProjectConfig): ClientReadyState { diff --git a/test/EvaluationLogTests.ts b/test/EvaluationLogTests.ts new file mode 100644 index 0000000..750f589 --- /dev/null +++ b/test/EvaluationLogTests.ts @@ -0,0 +1,135 @@ +import { assert } from "chai"; +import * as fs from "fs"; +import * as path from "path"; +import * as util from "util"; +import "mocha"; +import { User } from "../src"; +import { LogLevel, LoggerWrapper } from "../src/ConfigCatLogger"; +import { SettingValue } from "../src/ProjectConfig"; +import { RolloutEvaluator, evaluate } from "../src/RolloutEvaluator"; +import { WellKnownUserObjectAttribute } from "../src/User"; +import { errorToString } from "../src/Utils"; +import { CdnConfigLocation, ConfigLocation, LocalFileConfigLocation } from "./helpers/ConfigLocation"; +import { FakeLogger } from "./helpers/fakes"; +import { normalizeLineEndings } from "./helpers/utils"; + +const testDataBasePath = path.join("test", "data", "evaluationlog"); + +type TestSet = { + sdkKey: string; + baseUrl?: string; + jsonOverride?: string; + tests?: ReadonlyArray; +} + +type TestCase = { + key: string; + defaultValue: SettingValue; + returnValue: NonNullable; + expectedLog: string; + user?: Readonly<{ [key: string]: string }>; +} + +describe("Evaluation log", () => { + describeTestSet("simple_value"); + describeTestSet("1_targeting_rule"); + describeTestSet("2_targeting_rules"); + describeTestSet("options_based_on_user_id"); + describeTestSet("options_based_on_custom_attr"); + describeTestSet("options_after_targeting_rule"); + describeTestSet("options_within_targeting_rule"); + describeTestSet("and_rules"); + describeTestSet("segment"); + describeTestSet("prerequisite_flag"); + describeTestSet("comparators"); + describeTestSet("epoch_date_validation"); + describeTestSet("number_validation"); + describeTestSet("semver_validation"); + describeTestSet("list_truncation"); +}); + +function describeTestSet(testSetName: string) { + for (const [configLocation, testCase] of getTestCases(testSetName)) { + const userJson = JSON.stringify(testCase.user ?? null).replace(/"/g, "'"); + it(`${testSetName} - ${configLocation} | ${testCase.key} | ${testCase.defaultValue} | ${userJson}`, () => runTest(testSetName, configLocation, testCase)); + } +} + +function* getTestCases(testSetName: string): Generator<[ConfigLocation, TestCase], void, undefined> { + const data = fs.readFileSync(path.join(testDataBasePath, testSetName + ".json"), "utf8"); + const testSet: TestSet = JSON.parse(data); + + const configLocation = testSet.sdkKey + ? new CdnConfigLocation(testSet.sdkKey, testSet.baseUrl) + : new LocalFileConfigLocation(testDataBasePath, "_overrides", testSet.jsonOverride!); + + for (const testCase of testSet.tests ?? []) { + yield [configLocation, testCase]; + } +} + +function createUser(userRaw?: Readonly<{ [key: string]: string }>): User | undefined { + if (!userRaw) { + return; + } + + const identifierAttribute: WellKnownUserObjectAttribute = "Identifier"; + const emailAttribute: WellKnownUserObjectAttribute = "Email"; + const countryAttribute: WellKnownUserObjectAttribute = "Country"; + + const user = new User(userRaw[identifierAttribute]); + + const email = userRaw[emailAttribute]; + if (email) { + user.email = email; + } + + const country = userRaw[countryAttribute]; + if (country) { + user.country = country; + } + + const wellKnownAttributes: string[] = [identifierAttribute, emailAttribute, countryAttribute]; + for (const attributeName of Object.keys(userRaw)) { + if (wellKnownAttributes.indexOf(attributeName) < 0) { + user.custom[attributeName] = userRaw[attributeName]; + } + } + + return user; +} + +function formatLogEvent(event: FakeLogger["events"][0]) { + const [level, eventId, message, exception] = event; + + const levelString = + level === LogLevel.Debug ? "DEBUG" : + level === LogLevel.Info ? "INFO" : + level === LogLevel.Warn ? "WARNING" : + level === LogLevel.Error ? "ERROR" : + LogLevel[level].toUpperCase().padStart(5); + + const exceptionString = exception !== void 0 ? "\n" + errorToString(exception, true) : ""; + + return `${levelString} [${eventId}] ${message}${exceptionString}`; +} + +async function runTest(testSetName: string, configLocation: ConfigLocation, testCase: TestCase) { + const config = await configLocation.fetchConfigCachedAsync(); + + const fakeLogger = new FakeLogger(); + const logger = new LoggerWrapper(fakeLogger); + const evaluator = new RolloutEvaluator(logger); + + const user = createUser(testCase.user); + const evaluationDetails = evaluate(evaluator, config.settings, testCase.key, testCase.defaultValue, user, null, logger); + const actualReturnValue = evaluationDetails.value; + + assert.strictEqual(actualReturnValue, testCase.returnValue); + + const expectedLogFilePath = path.join(testDataBasePath, testSetName, testCase.expectedLog); + const expectedLogText = normalizeLineEndings(await util.promisify(fs.readFile)(expectedLogFilePath, "utf8")).replace(/(\r|\n)*$/, ""); + const actualLogText = fakeLogger.events.map(e => formatLogEvent(e)).join("\n"); + + assert.strictEqual(actualLogText, expectedLogText); +} diff --git a/test/HashTests.ts b/test/HashTests.ts new file mode 100644 index 0000000..02e196a --- /dev/null +++ b/test/HashTests.ts @@ -0,0 +1,34 @@ +import { assert } from "chai"; +import "mocha"; +import { sha1, sha256 } from "../src/Hash"; +import { utf8Encode } from "../src/Utils"; + +describe("Hash functions", () => { + for (const [input, expectedOutput] of [ + ["", "da39a3ee5e6b4b0d3255bfef95601890afd80709"], + ["abc", "a9993e364706816aba3e25717850c26c9cd0d89d"], + ["<谩rv铆zt疟r艖 t眉k枚rf煤r贸g茅p | 脕RV脥ZT虐R艕 T脺K脰RF脷R脫G脡P>", "1be0a50a536668a6ff6c9650e13c2c09f011f3d8"], + ["\u{0} \u{7F}\n\u{80} \u{7FF}\n\u{800} \u{FFFF}\n\u{10000} \u{10FFFF}\r\n", "b7cd3a8fe37c968f87d9d2eb581a42c94952a890"], + ]) { + it(`sha1 should work - input: '${input}'`, () => { + let actualOutput = sha1(input); + assert.equal(actualOutput, expectedOutput); + actualOutput = sha1(input); + assert.equal(actualOutput, expectedOutput); + }); + } + + for (const [input, expectedOutput] of [ + ["", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"], + ["abc", "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"], + ["<谩rv铆zt疟r艖 t眉k枚rf煤r贸g茅p | 脕RV脥ZT虐R艕 T脺K脰RF脷R脫G脡P>", "8a396060d6d54c97112c2d420144d528f4b1d824c66a83b54070b0f0bb8cafa7"], + ["\u{0} \u{7F}\n\u{80} \u{7FF}\n\u{800} \u{FFFF}\n\u{10000} \u{10FFFF}\r\n", "306979857214e80b4eee7caf751d38c7491147fa62ed2980f4fd6d7398479b9b"], + ]) { + it(`sha256 should work - input: '${input}'`, () => { + let actualOutput = sha256(utf8Encode(input)); + assert.equal(actualOutput, expectedOutput); + actualOutput = sha256(utf8Encode(input)); + assert.equal(actualOutput, expectedOutput); + }); + } +}); diff --git a/test/IndexTests.ts b/test/IndexTests.ts index 7fe7ea6..d84a036 100644 --- a/test/IndexTests.ts +++ b/test/IndexTests.ts @@ -10,7 +10,7 @@ describe("ConfigCatClient index (main)", () => { it("getClient ShouldCreateInstance - AutoPoll", () => { const configCatKernel: FakeConfigCatKernel = { configFetcher: new FakeConfigFetcher(), sdkType: "common", sdkVersion: "1.0.0" }; - const client: IConfigCatClient = configcatClient.getClient("APIKEY", PollingMode.AutoPoll, void 0, configCatKernel); + const client: IConfigCatClient = configcatClient.getClient("SDKKEY-890123456789012/1234567890123456789012", PollingMode.AutoPoll, void 0, configCatKernel); try { assert.isDefined(client); @@ -23,7 +23,7 @@ describe("ConfigCatClient index (main)", () => { it("getClient ShouldCreateInstance - LazyLoad", () => { const configCatKernel: FakeConfigCatKernel = { configFetcher: new FakeConfigFetcher(), sdkType: "common", sdkVersion: "1.0.0" }; - const client: IConfigCatClient = configcatClient.getClient("APIKEY", PollingMode.LazyLoad, void 0, configCatKernel); + const client: IConfigCatClient = configcatClient.getClient("SDKKEY-890123456789012/1234567890123456789012", PollingMode.LazyLoad, void 0, configCatKernel); try { assert.isDefined(client); @@ -36,7 +36,7 @@ describe("ConfigCatClient index (main)", () => { it("getClient ShouldCreateInstance - ManualPoll", () => { const configCatKernel: FakeConfigCatKernel = { configFetcher: new FakeConfigFetcher(), sdkType: "common", sdkVersion: "1.0.0" }; - const client: IConfigCatClient = configcatClient.getClient("APIKEY", PollingMode.ManualPoll, void 0, configCatKernel); + const client: IConfigCatClient = configcatClient.getClient("SDKKEY-890123456789012/1234567890123456789012", PollingMode.ManualPoll, void 0, configCatKernel); try { assert.isDefined(client); diff --git a/test/MatrixTests.ts b/test/MatrixTests.ts index 49a59c9..460c2c6 100644 --- a/test/MatrixTests.ts +++ b/test/MatrixTests.ts @@ -1,119 +1,160 @@ import { assert } from "chai"; import * as fs from "fs"; +import * as path from "path"; import "mocha"; -import { EOL } from "os"; -import { ConfigCatConsoleLogger, LogLevel } from "../src/ConfigCatLogger"; -import { User } from "../src/RolloutEvaluator"; -import { FakeConfigCatKernel, FakeConfigFetcherBase } from "./helpers/fakes"; -import { createClientWithManualPoll } from "./helpers/utils"; +import { LogLevel, SettingType, SettingValue, User } from "../src"; +import { createConsoleLogger } from "../src"; +import { LoggerWrapper } from "../src/ConfigCatLogger"; +import { RolloutEvaluator, evaluate } from "../src/RolloutEvaluator"; +import { getUserAttributes } from "../src/User"; +import { CdnConfigLocation, ConfigLocation } from "./helpers/ConfigLocation"; -describe("MatrixTests", () => { +const testDataBasePath = path.join("test", "data"); - it("GetValue basic operators", async () => { - await Helper.runMatrixTest("test/data/sample_v5.json", "test/data/testmatrix.csv"); - }); +type MatrixTestCase = [key: string, user: User | undefined, userAttributesJson: string, expected: string]; - it("GetValue numeric operators", async () => { - await Helper.runMatrixTest("test/data/sample_number_v5.json", "test/data/testmatrix_number.csv"); - }); +const logger = new LoggerWrapper(createConsoleLogger(LogLevel.Error)); +const evaluator = new RolloutEvaluator(logger); - it("GetValue semver operators", async () => { - await Helper.runMatrixTest("test/data/sample_semantic_v5.json", "test/data/testmatrix_semantic.csv"); - }); +describe("MatrixTests (config v1)", () => { + // https://app.configcat.com/08d5a03c-feb7-af1e-a1fa-40b3329f8bed/08d62463-86ec-8fde-f5b5-1c5c426fc830/244cf8b0-f604-11e8-b543-f23c917f9d8d + describeMatrixTest("Basic operators", new CdnConfigLocation("PKDVCLf-Hq-h-kCzMp-L7Q/psuH7BGHoUmdONrzzUOY7A"), "testmatrix.csv", evaluator); - it("GetValue semver operators", async () => { - await Helper.runMatrixTest("test/data/sample_semantic_2_v5.json", "test/data/testmatrix_semantic_2.csv"); - }); + // https://app.configcat.com/08d5a03c-feb7-af1e-a1fa-40b3329f8bed/08d747f0-5986-c2ef-eef3-ec778e32e10a/244cf8b0-f604-11e8-b543-f23c917f9d8d + describeMatrixTest("Numeric operators", new CdnConfigLocation("PKDVCLf-Hq-h-kCzMp-L7Q/uGyK3q9_ckmdxRyI7vjwCw"), "testmatrix_number.csv", evaluator); - it("GetValue sensitive operators", async () => { - await Helper.runMatrixTest("test/data/sample_sensitive_v5.json", "test/data/testmatrix_sensitive.csv"); - }); + // https://app.configcat.com/08d5a03c-feb7-af1e-a1fa-40b3329f8bed/08d9f207-6883-43e5-868c-cbf677af3fe6/244cf8b0-f604-11e8-b543-f23c917f9d8d + describeMatrixTest("Segments v1", new CdnConfigLocation("PKDVCLf-Hq-h-kCzMp-L7Q/LcYz135LE0qbcacz2mgXnA"), "testmatrix_segments_old.csv", evaluator); - class Helper { + // https://app.configcat.com/08d5a03c-feb7-af1e-a1fa-40b3329f8bed/08d745f1-f315-7daf-d163-5541d3786e6f/244cf8b0-f604-11e8-b543-f23c917f9d8d + describeMatrixTest("SemVer operators", new CdnConfigLocation("PKDVCLf-Hq-h-kCzMp-L7Q/BAr3KgLTP0ObzKnBTo5nhA"), "testmatrix_semantic.csv", evaluator); - static createUser(row: string, headers: string[]): User | undefined { + // https://app.configcat.com/08d5a03c-feb7-af1e-a1fa-40b3329f8bed/08d77fa1-a796-85f9-df0c-57c448eb9934/244cf8b0-f604-11e8-b543-f23c917f9d8d + describeMatrixTest("SemVer operators 2", new CdnConfigLocation("PKDVCLf-Hq-h-kCzMp-L7Q/q6jMCFIp-EmuAfnmZhPY7w"), "testmatrix_semantic_2.csv", evaluator); - const column: string[] = row.split(";"); - const USERNULL = "##null##"; + // https://app.configcat.com/08d5a03c-feb7-af1e-a1fa-40b3329f8bed/08d7b724-9285-f4a7-9fcd-00f64f1e83d5/244cf8b0-f604-11e8-b543-f23c917f9d8d + describeMatrixTest("Sensitive text operators", new CdnConfigLocation("PKDVCLf-Hq-h-kCzMp-L7Q/qX3TP2dTj06ZpCCT1h_SPA"), "testmatrix_sensitive.csv", evaluator); - if (column[0] === USERNULL) { - return; - } + // https://app.configcat.com/08d5a03c-feb7-af1e-a1fa-40b3329f8bed/08d774b9-3d05-0027-d5f4-3e76c3dba752/244cf8b0-f604-11e8-b543-f23c917f9d8d + describeMatrixTest("Variation ID", new CdnConfigLocation("PKDVCLf-Hq-h-kCzMp-L7Q/nQ5qkhRAUEa6beEyyrVLBA"), "testmatrix_variationid.csv", evaluator, runVariationIdMatrixTest); +}); - const result: User = new User(column[0]); +describe("MatrixTests (config v2)", () => { + // https://app.configcat.com/v2/e7a75611-4256-49a5-9320-ce158755e3ba/08d5a03c-feb7-af1e-a1fa-40b3329f8bed/08dbc4dc-1927-4d6b-8fb9-b1472564e2d3/244cf8b0-f604-11e8-b543-f23c917f9d8d + describeMatrixTest("Basic operators", new CdnConfigLocation("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/AG6C1ngVb0CvM07un6JisQ"), "testmatrix.csv", evaluator); - if (column[1] !== USERNULL) { - result.email = column[1]; - } + // https://app.configcat.com/v2/e7a75611-4256-49a5-9320-ce158755e3ba/08d5a03c-feb7-af1e-a1fa-40b3329f8bed/08dbc4dc-0fa3-48d0-8de8-9de55b67fb8b/244cf8b0-f604-11e8-b543-f23c917f9d8d + describeMatrixTest("Numeric operators", new CdnConfigLocation("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw"), "testmatrix_number.csv", evaluator); - if (column[2] !== USERNULL) { - result.country = column[2]; - } + // https://app.configcat.com/v2/e7a75611-4256-49a5-9320-ce158755e3ba/08d5a03c-feb7-af1e-a1fa-40b3329f8bed/08dbd6ca-a85f-4ed0-888a-2da18def92b5/244cf8b0-f604-11e8-b543-f23c917f9d8d + describeMatrixTest("Segments v1", new CdnConfigLocation("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/y_ZB7o-Xb0Swxth-ZlMSeA"), "testmatrix_segments_old.csv", evaluator); - if (column[3] !== USERNULL) { - result.custom = result.custom || {}; - result.custom[headers[3]] = column[3]; - } + // https://app.configcat.com/v2/e7a75611-4256-49a5-9320-ce158755e3ba/08d5a03c-feb7-af1e-a1fa-40b3329f8bed/08dbc4dc-278c-4f83-8d36-db73ad6e2a3a/244cf8b0-f604-11e8-b543-f23c917f9d8d + describeMatrixTest("SemVer operators", new CdnConfigLocation("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/iV8vH2MBakKxkFZylxHmTg"), "testmatrix_semantic.csv", evaluator); - return result; - } + // https://app.configcat.com/v2/e7a75611-4256-49a5-9320-ce158755e3ba/08d5a03c-feb7-af1e-a1fa-40b3329f8bed/08dbc4dc-2b2b-451e-8359-abdef494c2a2/244cf8b0-f604-11e8-b543-f23c917f9d8d + describeMatrixTest("SemVer operators 2", new CdnConfigLocation("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/U8nt3zEhDEO5S2ulubCopA"), "testmatrix_semantic_2.csv", evaluator); - static getTypedValue(value: string, header: string): string | boolean | number { + // https://app.configcat.com/v2/e7a75611-4256-49a5-9320-ce158755e3ba/08d5a03c-feb7-af1e-a1fa-40b3329f8bed/08dbc4dc-2d62-4e1b-884b-6aa237b34764/244cf8b0-f604-11e8-b543-f23c917f9d8d + describeMatrixTest("Sensitive text operators", new CdnConfigLocation("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/-0YmVOUNgEGKkgRF-rU65g"), "testmatrix_sensitive.csv", evaluator); - if (header.substring(0, "bool".length) === "bool") { - return value.toLowerCase() === "true"; - } + //https://app.configcat.com/v2/e7a75611-4256-49a5-9320-ce158755e3ba/08d5a03c-feb7-af1e-a1fa-40b3329f8bed/08dbc4dc-30c6-4969-8e4c-03f6a8764199/244cf8b0-f604-11e8-b543-f23c917f9d8d + describeMatrixTest("Variation ID", new CdnConfigLocation("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/spQnkRTIPEWVivZkWM84lQ"), "testmatrix_variationid.csv", evaluator, runVariationIdMatrixTest); - if (header.substring(0, "double".length) === "double") { - return +value; - } + // https://app.configcat.com/v2/e7a75611-4256-49a5-9320-ce158755e3ba/08dbc325-7f69-4fd4-8af4-cf9f24ec8ac9/08dbc325-9d5e-4988-891c-fd4a45790bd1/08dbc325-9ebd-4587-8171-88f76a3004cb + describeMatrixTest("AND conditions", new CdnConfigLocation("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/ByMO9yZNn02kXcm72lnY1A"), "testmatrix_and_or.csv", evaluator); - if (header.substring(0, "integer".length) === "integer") { - return +value; - } + // https://app.configcat.com/v2/e7a75611-4256-49a5-9320-ce158755e3ba/08dbc325-7f69-4fd4-8af4-cf9f24ec8ac9/08dbc325-9a6b-4947-84e2-91529248278a/08dbc325-9ebd-4587-8171-88f76a3004cb + describeMatrixTest("Comparators v2", new CdnConfigLocation("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ"), "testmatrix_comparators_v6.csv", evaluator); - return value; - } + // https://app.configcat.com/v2/e7a75611-4256-49a5-9320-ce158755e3ba/08dbc325-7f69-4fd4-8af4-cf9f24ec8ac9/08dbc325-9b74-45cb-86d0-4d61c25af1aa/08dbc325-9ebd-4587-8171-88f76a3004cb + describeMatrixTest("Prerequisite flags", new CdnConfigLocation("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/JoGwdqJZQ0K2xDy7LnbyOg"), "testmatrix_prerequisite_flag.csv", evaluator); - static async runMatrixTest(sampleFilePath: string, matrixFilePath: string): Promise { + // https://app.configcat.com/v2/e7a75611-4256-49a5-9320-ce158755e3ba/08dbc325-7f69-4fd4-8af4-cf9f24ec8ac9/08dbc325-9cfb-486f-8906-72a57c693615/08dbc325-9ebd-4587-8171-88f76a3004cb + describeMatrixTest("Segments v2", new CdnConfigLocation("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/h99HYXWWNE2bH8eWyLAVMA"), "testmatrix_segments.csv", evaluator); - const SAMPLE: string = fs.readFileSync(sampleFilePath, "utf8"); - const configCatKernel: FakeConfigCatKernel = { configFetcher: new FakeConfigFetcherBase(SAMPLE), sdkType: "common", sdkVersion: "1.0.0" }; - const client = createClientWithManualPoll("SDKKEY", configCatKernel, { - logger: new ConfigCatConsoleLogger(LogLevel.Off) - }); + // https://app.configcat.com/v2/e7a75611-4256-49a5-9320-ce158755e3ba/08dbc325-7f69-4fd4-8af4-cf9f24ec8ac9/08dbd63c-9774-49d6-8187-5f2aab7bd606/08dbc325-9ebd-4587-8171-88f76a3004cb + describeMatrixTest("Unicode texts", new CdnConfigLocation("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/Da6w8dBbmUeMUBhh0iEeQQ"), "testmatrix_unicode.csv", evaluator); +}); + +function describeMatrixTest(title: string, configLocation: ConfigLocation, matrixFilePath: string, evaluator: RolloutEvaluator, + runner: (configLocation: ConfigLocation, key: string, user: User | undefined, expected: string, evaluator: RolloutEvaluator) => void = runMatrixTest) { - await client.forceRefreshAsync(); + for (const [key, user, userAttributesJson, expected] of getMatrixTestCases(matrixFilePath)) { + it(`${title} - ${configLocation} | ${key} | ${userAttributesJson}`, () => runner(configLocation, key, user, expected, evaluator)); + } +} - const data = fs.readFileSync(matrixFilePath, "utf8"); +function* getMatrixTestCases(matrixFilePath: string): Generator { + const data = fs.readFileSync(path.join(testDataBasePath, matrixFilePath), "utf8"); - const lines: string[] = data.toString().split(EOL); - const header: string[] = lines.shift()?.split(";") ?? []; + const lines: string[] = data.toString().split(/\r\n?|\n/); + const header: string[] = lines.shift()?.split(";") ?? []; - for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) { - const line = lines[lineIndex]; + for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) { + const line = lines[lineIndex]; + if (!line.trim()) { + continue; + } + const columns = line.split(";"); - if (!line) { - return; - } + const user = createUser(columns, header); + const userAttributesJson = JSON.stringify(user ? getUserAttributes(user) : null).replace(/"/g, "'"); - const user = Helper.createUser(line, header); + for (let i = 4; i < header.length; i++) { + const key = header[i]; + const expected = columns[i]; + yield [key, user, userAttributesJson, expected]; + } + } +} - for (let i = 4; i < header.length; i++) { +function createUser(columns: ReadonlyArray, headers: string[]): User | undefined { + const USERNULL = "##null##"; - const key: string = header[i]; - const actual: any = await client.getValueAsync(key, null, user); - const expected: any = Helper.getTypedValue(line.split(";")[i], key); + const [identifier, email, country, custom] = columns; - if (actual !== expected) { + if (identifier === USERNULL) { + return; + } - const l = `Matrix test failed in line ${lineIndex + 1}.\n User: ${JSON.stringify(user)},\n Key: ${key},\n Actual: ${actual}, Expected: ${expected}`; - console.log(l); - } + const result: User = new User(identifier); - assert.strictEqual(actual, expected); - } - } - } + if (email && email !== USERNULL) { + result.email = email; } -}); + + if (country && country !== USERNULL) { + result.country = country; + } + + if (custom && custom !== USERNULL) { + result.custom[headers[3]] = custom; + } + + return result; +} + +function getTypedValue(value: string, settingType: SettingType): NonNullable { + switch (settingType) { + case SettingType.Boolean: return value.toLowerCase() === "true"; + case SettingType.Int: + case SettingType.Double: return +value; + default: return value; + } +} + +async function runMatrixTest(configLocation: ConfigLocation, key: string, user: User | undefined, expected: string, evaluator: RolloutEvaluator) { + const config = await configLocation.fetchConfigCachedAsync(); + const setting = config.settings[key]; + + const actual = evaluate(evaluator, config.settings, key, null, user, null, evaluator["logger"]).value; + assert.strictEqual(actual, getTypedValue(expected, setting.type)); +} + +async function runVariationIdMatrixTest(configLocation: ConfigLocation, key: string, user: User | undefined, expected: string, evaluator: RolloutEvaluator) { + const config = await configLocation.fetchConfigCachedAsync(); + + const actual = evaluate(evaluator, config.settings, key, null, user, null, evaluator["logger"]).variationId; + assert.strictEqual(actual, expected); +} diff --git a/test/OverrideTests.ts b/test/OverrideTests.ts index 05b6f5d..386929b 100644 --- a/test/OverrideTests.ts +++ b/test/OverrideTests.ts @@ -5,6 +5,7 @@ import { ConfigCatClient, IConfigCatClient, IConfigCatKernel } from "../src/Conf import { AutoPollOptions, ManualPollOptions } from "../src/ConfigCatClientOptions"; import { MapOverrideDataSource, OverrideBehaviour } from "../src/FlagOverrides"; import { SettingValue } from "../src/ProjectConfig"; +import { isAllowedValue } from "../src/RolloutEvaluator"; import { FakeConfigCatKernel, FakeConfigFetcherBase, FakeConfigFetcherWithNullNewConfig } from "./helpers/fakes"; describe("Local Overrides", () => { @@ -58,7 +59,7 @@ describe("Local Overrides", () => { it("Values from map - RemoteOverLocal", async () => { const configCatKernel: FakeConfigCatKernel = { - configFetcher: new FakeConfigFetcherBase("{\"f\": { \"fakeKey\": { \"v\": false, \"p\": [], \"r\": [] } } }"), + configFetcher: new FakeConfigFetcherBase('{"f":{"fakeKey":{"t":0,"v":{"b":false}}}}'), sdkType: "common", sdkVersion: "1.0.0" }; @@ -107,7 +108,7 @@ describe("Local Overrides", () => { dataSource["double_setting"] = 3.14; dataSource["string-setting"] = "test"; const configCatKernel: FakeConfigCatKernel = { - configFetcher: new FakeConfigFetcherBase("{\"f\": { \"fakeKey\": { \"v\": false, \"p\": [], \"r\": [] } } }"), + configFetcher: new FakeConfigFetcherBase('{"f":{"fakeKey":{"t":0,"v":{"b":false}}}}'), sdkType: "common", sdkVersion: "1.0.0" }; @@ -200,9 +201,7 @@ describe("Local Overrides", () => { const expectedEvaluatedValues: SettingKeyValue[] = [{ settingKey: key, - settingValue: typeof overrideValue === "boolean" || typeof overrideValue === "string" || typeof overrideValue === "number" || overrideValue === null || overrideValue === void 0 - ? overrideValue - : null + settingValue: isAllowedValue(overrideValue) ? overrideValue : null }]; assert.deepEqual(expectedEvaluatedValues, actualEvaluatedValues); }); diff --git a/test/UserTests.ts b/test/UserTests.ts new file mode 100644 index 0000000..d3d1eae --- /dev/null +++ b/test/UserTests.ts @@ -0,0 +1,158 @@ +import { assert } from "chai"; +import "mocha"; +import { LogLevel, LoggerWrapper } from "../src/ConfigCatLogger"; +import { User } from "../src/index"; +import { Config } from "../src/ProjectConfig"; +import { RolloutEvaluator, evaluate } from "../src/RolloutEvaluator"; +import { WellKnownUserObjectAttribute, getUserAttributes } from "../src/User"; +import { parseFloatStrict } from "../src/Utils"; +import { FakeLogger } from "./helpers/fakes"; + +const identifierAttribute: WellKnownUserObjectAttribute = "Identifier"; +const emailAttribute: WellKnownUserObjectAttribute = "Email"; +const countryAttribute: WellKnownUserObjectAttribute = "Country"; + +function createUser(attributeName: string, attributeValue: string) { + const user = new User(""); + switch (attributeName) { + case identifierAttribute: + user.identifier = attributeValue; + break; + case emailAttribute: + user.email = attributeValue; + break; + case countryAttribute: + user.country = attributeValue; + break; + default: + user.custom[attributeName] = attributeValue; + } + return user; +} + +describe("User Object", () => { + + for (const [attributeName, attributeValue, expectedValue] of <[string, string, string][]>[ + [identifierAttribute, void 0, ""], + [identifierAttribute, null, ""], + [identifierAttribute, "", ""], + [identifierAttribute, "id", "id"], + [identifierAttribute, "\t", "\t"], + [identifierAttribute, "\u1F600", "\u1F600"], + [emailAttribute, void 0, void 0], + [emailAttribute, null, void 0], + [emailAttribute, "", ""], + [emailAttribute, "a@example.com", "a@example.com"], + [countryAttribute, void 0, void 0], + [countryAttribute, null, void 0], + [countryAttribute, "", ""], + [countryAttribute, "US", "US"], + ["Custom1", void 0, void 0], + ["Custom1", null, void 0], + ["Custom1", "", ""], + ["Custom1", "3.14", "3.14"], + ]) { + it(`Create user - should set attribute value - ${attributeName}: ${attributeValue}`, () => { + const user = createUser(attributeName, attributeValue); + + assert.strictEqual(getUserAttributes(user)[attributeName], expectedValue); + }); + } + + it("Create User with id, email and country - all attributes should contain passed values", () => { + // Arrange + + const user = new User("id", "id@example.com", "US"); + + // Act + + const actualAttributes = getUserAttributes(user); + + // Assert + + assert.strictEqual(actualAttributes[identifierAttribute], "id"); + assert.strictEqual(actualAttributes[emailAttribute], "id@example.com"); + assert.strictEqual(actualAttributes[countryAttribute], "US"); + + assert.equal(3, Object.keys(actualAttributes).length); + }); + + it("Use well-known attributes as custom properties - should not append all attributes", () => { + // Arrange + + const user = new User("id", "id@example.com", "US", { + myCustomAttribute: "myCustomAttributeValue", + [identifierAttribute]: "myIdentifier", + [countryAttribute]: "United States", + [emailAttribute]: "otherEmail@example.com" + }); + + // Act + + const actualAttributes = getUserAttributes(user); + + // Assert + + assert.strictEqual(actualAttributes[identifierAttribute], "id"); + assert.strictEqual(actualAttributes[emailAttribute], "id@example.com"); + assert.strictEqual(actualAttributes[countryAttribute], "US"); + assert.strictEqual(actualAttributes["myCustomAttribute"], "myCustomAttributeValue"); + + assert.equal(4, Object.keys(actualAttributes).length); + }); + + for (const [attributeName, attributeValue] of <[string, string][]>[ + ["identifier", "myId"], + ["IDENTIFIER", "myId"], + ["email", "theBoss@example.com"], + ["EMAIL", "theBoss@example.com"], + ["eMail", "theBoss@example.com"], + ["country", "myHome"], + ["COUNTRY", "myHome"], + ]) { + it(`Use well-known attributes as custom properties with different casings - should append all attributes - attributeName: ${attributeName} | attributeValue: ${attributeValue}`, () => { + // Arrange + + const user = new User("id", "id@example.com", "US", { + [attributeName]: attributeValue, + }); + + // Act + + const actualAttributes = getUserAttributes(user); + + // Assert + + assert.strictEqual(actualAttributes[attributeName], attributeValue); + + assert.equal(4, Object.keys(actualAttributes).length); + }); + } + + it("Non-standard instantiation should work", () => { + // Arrange + + const user = { + identifier: "id", + email: "id@example.com", + country: "US", + custom: { + myCustomAttribute: "myCustomAttributeValue" + } + }; + + // Act + + const actualAttributes = getUserAttributes(user); + + // Assert + + assert.strictEqual(actualAttributes[identifierAttribute], "id"); + assert.strictEqual(actualAttributes[emailAttribute], "id@example.com"); + assert.strictEqual(actualAttributes[countryAttribute], "US"); + assert.strictEqual(actualAttributes["myCustomAttribute"], "myCustomAttributeValue"); + + assert.equal(4, Object.keys(actualAttributes).length); + }); + +}); diff --git a/test/UtilsTests.ts b/test/UtilsTests.ts new file mode 100644 index 0000000..b9eb44c --- /dev/null +++ b/test/UtilsTests.ts @@ -0,0 +1,76 @@ +import { assert } from "chai"; +import "mocha"; +import { formatStringList, parseFloatStrict, utf8Encode } from "../src/Utils"; + +describe("Utils", () => { + + for (const [input, expectedOutput] of <[string, number[]][]>[ + ["", []], + [" ", [0x20]], + ["", [0x3C, 0x61, 0x20, 0x7E, 0x20, 0xC2, 0x85, 0x20, 0xDD, 0xAB, 0x20, 0xE0, 0xA0, 0x92, 0x20, 0xEF, 0xBF, 0xBE, 0x20, 0xF0, 0x9F, 0x92, 0xA9, 0x3E]], + ]) { + it(`utf8encode - input: ${input}`, () => { + const actualOutput = utf8Encode(input); + assert.deepEqual([...actualOutput].map(ch => ch.charCodeAt(0)), expectedOutput); + }); + } + + for (const [input, expectedOutput] of <[string, number][]>[ + ["", NaN], + [" ", NaN], + ["NaN", NaN], + ["Infinity", Infinity], + ["-Infinity", -Infinity], + ["1", 1], + ["1 ", 1], + [" 1", 1], + [" 1 ", 1], + ["0x1", NaN], + [" 0x1", NaN], + ["+0x1", NaN], + ["-0x1", NaN], + ["1f", NaN], + ["1e", NaN], + ["0+", NaN], + ["0-", NaN], + ["2023.11.13", NaN], + ["0", 0], + ["-0", 0], + ["+0", 0], + ["1234567890", 1234567890], + ["1234567890.0", 1234567890], + ["1234567890e0", 1234567890], + [".1234567890", 0.1234567890], + ["+0.123e-3", 0.000123], + ["-0.123e+3", -123], + ]) { + it(`parseFloatStrict - input: ${input}`, () => { + const actualOutput = parseFloatStrict(input); + assert.isNumber(actualOutput); + if (isNaN(expectedOutput)) { + assert.isNaN(actualOutput); + } + else { + assert.strictEqual(actualOutput, expectedOutput); + } + }); + } + + for (const [items, maxLength, addOmittedItemsText, separator, expectedOutput] of <[string[], number, boolean, string | undefined, string][]>[ + [[], 0, false, void 0, ""], + [[], 1, true, void 0, ""], + [["a"], 0, false, void 0, "'a'"], + [["a"], 1, true, void 0, "'a'"], + [["a"], 1, true, " -> ", "'a'"], + [["a", "b", "c"], 0, false, void 0, "'a', 'b', 'c'"], + [["a", "b", "c"], 3, false, void 0, "'a', 'b', 'c'"], + [["a", "b", "c"], 2, false, void 0, "'a', 'b'"], + [["a", "b", "c"], 2, true, void 0, "'a', 'b', ...1 item(s) omitted"], + [["a", "b", "c"], 0, true, " -> ", "'a' -> 'b' -> 'c'"], + ]) { + it(`formatStringList - items: ${items} | maxLength: ${maxLength} | addOmittedItemsText: ${addOmittedItemsText}`, () => { + const actualOutput = formatStringList(items, maxLength, addOmittedItemsText ? count => `, ...${count} item(s) omitted` : void 0, separator); + assert.strictEqual(actualOutput, expectedOutput); + }); + } +}); diff --git a/test/VariationIdMatrixTests.ts b/test/VariationIdMatrixTests.ts deleted file mode 100644 index 32aefa5..0000000 --- a/test/VariationIdMatrixTests.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { assert } from "chai"; -import * as fs from "fs"; -import "mocha"; -import { EOL } from "os"; -import { ConfigCatClient, IConfigCatClient } from "../src/ConfigCatClient"; -import { AutoPollOptions } from "../src/ConfigCatClientOptions"; -import { User } from "../src/RolloutEvaluator"; -import { FakeConfigCatKernel, FakeConfigFetcherBase } from "./helpers/fakes"; - -describe("MatrixTests", () => { - - const variationIdV5: string = fs.readFileSync("test/data/sample_variationid_v5.json", "utf8"); - - it("GetVariationId", async () => { - - const configCatKernel: FakeConfigCatKernel = { - configFetcher: new FakeConfigFetcherBase(variationIdV5), - sdkType: "common", - sdkVersion: "1.0.0" - }; - const options: AutoPollOptions = new AutoPollOptions("APIKEY", "common", "1.0.0", { logger: null }, null); - const client: IConfigCatClient = new ConfigCatClient(options, configCatKernel); - - let header: string[] | null = null; - let rowNo = 1; - - const data = fs.readFileSync("test/data/testmatrix_variationId.csv", "utf8"); - - const lines: string[] = data.toString().split(EOL); - for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) { - const line = lines[lineIndex]; - - if (header) { - - if (!line) { - return; - } - - const user = Helper.createUser(line, header); - const splittedLine = line.split(";"); - for (let i = 4; i < header.length; i++) { - - const key: string = header[i]; - const expected = splittedLine[i]; - const actual = (await client.getValueDetailsAsync(key, null, user)).variationId; - - if (actual !== expected) { - - // tslint:disable-next-line:max-line-length - const l: string = rowNo + "." + " User - " + user + "(" + key + ") " + actual + " === " + expected + " = " + (actual === expected); - - console.log(l); - } - - // assert - assert.strictEqual(actual, expected); - } - } - else { - header = line.split(";"); - } - - rowNo++; - } - }); - - class Helper { - - static createUser(row: string, headers: string[]): User | undefined { - - const up: string[] = row.split(";"); - - if (up[0] === "##null##") { - return; - } - - const result: User = new User(up[0]); - - if (up[1] !== "##null##") { - result.email = up[1]; - } - - if (up[2] !== "##null##") { - result.country = up[2]; - } - - if (up[3] !== "##null##") { - result.custom = result.custom || {}; - result.custom[headers[3]] = up[3]; - } - - return result; - } - - static getTypedValue(value: string, header: string): string | boolean | number { - - if (header.substring(0, "bool".length) === "bool") { - return value.toLowerCase() === "true"; - } - - if (header.substring(0, "double".length) === "double") { - return +value; - } - - if (header.substring(0, "integer".length) === "integer") { - return +value; - } - - return value; - } - } -}); diff --git a/test/VariationIdTests.ts b/test/VariationIdTests.ts index 2b72c7c..9fbac74 100644 --- a/test/VariationIdTests.ts +++ b/test/VariationIdTests.ts @@ -2,7 +2,7 @@ import { assert } from "chai"; import "mocha"; import { ConfigCatClient, IConfigCatClient } from "../src/ConfigCatClient"; import { AutoPollOptions } from "../src/ConfigCatClientOptions"; -import { FakeConfigCatKernel, FakeConfigFetcherWithNullNewConfig, FakeConfigFetcherWithTwoKeysAndRules } from "./helpers/fakes"; +import { FakeConfigCatKernel, FakeConfigFetcherWithNullNewConfig, FakeConfigFetcherWithPercentageOptionsWithinTargetingRule, FakeConfigFetcherWithTwoKeysAndRules } from "./helpers/fakes"; describe("ConfigCatClient", () => { it("getKeyAndValueAsync() works with default", async () => { @@ -33,7 +33,7 @@ describe("ConfigCatClient", () => { assert.equal(result?.settingValue, "value"); }); - it("getKeyAndValueAsync() works with percentage rules", async () => { + it("getKeyAndValueAsync() works with percentage options", async () => { const configCatKernel: FakeConfigCatKernel = { configFetcher: new FakeConfigFetcherWithTwoKeysAndRules(), sdkType: "common", @@ -47,6 +47,20 @@ describe("ConfigCatClient", () => { assert.equal(result?.settingValue, "value2"); }); + it("getKeyAndValueAsync() works with percentage options within targeting rule", async () => { + const configCatKernel: FakeConfigCatKernel = { + configFetcher: new FakeConfigFetcherWithPercentageOptionsWithinTargetingRule(), + sdkType: "common", + sdkVersion: "1.0.0" + }; + const options: AutoPollOptions = new AutoPollOptions("APIKEY", "common", "1.0.0", { logger: null }, null); + const client: IConfigCatClient = new ConfigCatClient(options, configCatKernel); + + const result = await client.getKeyAndValueAsync("622f5d07"); + assert.equal(result?.settingKey, "debug"); + assert.equal(result?.settingValue, "value2"); + }); + it("getKeyAndValueAsync() with null config", async () => { const configCatKernel: FakeConfigCatKernel = { configFetcher: new FakeConfigFetcherWithNullNewConfig(), diff --git a/test/data/evaluationlog/1_targeting_rule.json b/test/data/evaluationlog/1_targeting_rule.json new file mode 100644 index 0000000..596bd2b --- /dev/null +++ b/test/data/evaluationlog/1_targeting_rule.json @@ -0,0 +1,41 @@ +{ + "configUrl": "https://app.configcat.com/08d5a03c-feb7-af1e-a1fa-40b3329f8bed/08d62463-86ec-8fde-f5b5-1c5c426fc830/244cf8b0-f604-11e8-b543-f23c917f9d8d", + "sdkKey": "PKDVCLf-Hq-h-kCzMp-L7Q/psuH7BGHoUmdONrzzUOY7A", + "tests": [ + { + "key": "stringContainsDogDefaultCat", + "defaultValue": "default", + "returnValue": "Cat", + "expectedLog": "1_rule_no_user.txt" + }, + { + "key": "stringContainsDogDefaultCat", + "defaultValue": "default", + "user": { + "Identifier": "12345" + }, + "returnValue": "Cat", + "expectedLog": "1_rule_no_targeted_attribute.txt" + }, + { + "key": "stringContainsDogDefaultCat", + "defaultValue": "default", + "user": { + "Identifier": "12345", + "Email": "joe@example.com" + }, + "returnValue": "Cat", + "expectedLog": "1_rule_not_matching_targeted_attribute.txt" + }, + { + "key": "stringContainsDogDefaultCat", + "defaultValue": "default", + "user": { + "Identifier": "12345", + "Email": "joe@configcat.com" + }, + "returnValue": "Dog", + "expectedLog": "1_rule_matching_targeted_attribute.txt" + } + ] +} diff --git a/test/data/evaluationlog/1_targeting_rule/1_rule_matching_targeted_attribute.txt b/test/data/evaluationlog/1_targeting_rule/1_rule_matching_targeted_attribute.txt new file mode 100644 index 0000000..f05c6f6 --- /dev/null +++ b/test/data/evaluationlog/1_targeting_rule/1_rule_matching_targeted_attribute.txt @@ -0,0 +1,4 @@ +INFO [5000] Evaluating 'stringContainsDogDefaultCat' for User '{"Identifier":"12345","Email":"joe@configcat.com"}' + Evaluating targeting rules and applying the first match if any: + - IF User.Email CONTAINS ANY OF ['@configcat.com'] THEN 'Dog' => MATCH, applying rule + Returning 'Dog'. diff --git a/test/data/evaluationlog/1_targeting_rule/1_rule_no_targeted_attribute.txt b/test/data/evaluationlog/1_targeting_rule/1_rule_no_targeted_attribute.txt new file mode 100644 index 0000000..80702e9 --- /dev/null +++ b/test/data/evaluationlog/1_targeting_rule/1_rule_no_targeted_attribute.txt @@ -0,0 +1,6 @@ +WARNING [3003] Cannot evaluate condition (User.Email CONTAINS ANY OF ['@configcat.com']) for setting 'stringContainsDogDefaultCat' (the User.Email attribute is missing). You should set the User.Email attribute in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ +INFO [5000] Evaluating 'stringContainsDogDefaultCat' for User '{"Identifier":"12345"}' + Evaluating targeting rules and applying the first match if any: + - IF User.Email CONTAINS ANY OF ['@configcat.com'] THEN 'Dog' => cannot evaluate, the User.Email attribute is missing + The current targeting rule is ignored and the evaluation continues with the next rule. + Returning 'Cat'. diff --git a/test/data/evaluationlog/1_targeting_rule/1_rule_no_user.txt b/test/data/evaluationlog/1_targeting_rule/1_rule_no_user.txt new file mode 100644 index 0000000..231ac7d --- /dev/null +++ b/test/data/evaluationlog/1_targeting_rule/1_rule_no_user.txt @@ -0,0 +1,6 @@ +WARNING [3001] Cannot evaluate targeting rules and % options for setting 'stringContainsDogDefaultCat' (User Object is missing). You should pass a User Object to the evaluation methods like `getValueAsync()` in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ +INFO [5000] Evaluating 'stringContainsDogDefaultCat' + Evaluating targeting rules and applying the first match if any: + - IF User.Email CONTAINS ANY OF ['@configcat.com'] THEN 'Dog' => cannot evaluate, User Object is missing + The current targeting rule is ignored and the evaluation continues with the next rule. + Returning 'Cat'. diff --git a/test/data/evaluationlog/1_targeting_rule/1_rule_not_matching_targeted_attribute.txt b/test/data/evaluationlog/1_targeting_rule/1_rule_not_matching_targeted_attribute.txt new file mode 100644 index 0000000..49d1252 --- /dev/null +++ b/test/data/evaluationlog/1_targeting_rule/1_rule_not_matching_targeted_attribute.txt @@ -0,0 +1,4 @@ +INFO [5000] Evaluating 'stringContainsDogDefaultCat' for User '{"Identifier":"12345","Email":"joe@example.com"}' + Evaluating targeting rules and applying the first match if any: + - IF User.Email CONTAINS ANY OF ['@configcat.com'] THEN 'Dog' => no match + Returning 'Cat'. diff --git a/test/data/evaluationlog/2_targeting_rules.json b/test/data/evaluationlog/2_targeting_rules.json new file mode 100644 index 0000000..5cf8a3c --- /dev/null +++ b/test/data/evaluationlog/2_targeting_rules.json @@ -0,0 +1,41 @@ +{ + "configUrl": "https://app.configcat.com/08d5a03c-feb7-af1e-a1fa-40b3329f8bed/08d62463-86ec-8fde-f5b5-1c5c426fc830/244cf8b0-f604-11e8-b543-f23c917f9d8d", + "sdkKey": "PKDVCLf-Hq-h-kCzMp-L7Q/psuH7BGHoUmdONrzzUOY7A", + "tests": [ + { + "key": "stringIsInDogDefaultCat", + "defaultValue": "default", + "returnValue": "Cat", + "expectedLog": "2_rules_no_user.txt" + }, + { + "key": "stringIsInDogDefaultCat", + "defaultValue": "default", + "user": { + "Identifier": "12345" + }, + "returnValue": "Cat", + "expectedLog": "2_rules_no_targeted_attribute.txt" + }, + { + "key": "stringIsInDogDefaultCat", + "defaultValue": "default", + "user": { + "Identifier": "12345", + "Custom1": "user" + }, + "returnValue": "Cat", + "expectedLog": "2_rules_not_matching_targeted_attribute.txt" + }, + { + "key": "stringIsInDogDefaultCat", + "defaultValue": "default", + "user": { + "Identifier": "12345", + "Custom1": "admin" + }, + "returnValue": "Dog", + "expectedLog": "2_rules_matching_targeted_attribute.txt" + } + ] +} diff --git a/test/data/evaluationlog/2_targeting_rules/2_rules_matching_targeted_attribute.txt b/test/data/evaluationlog/2_targeting_rules/2_rules_matching_targeted_attribute.txt new file mode 100644 index 0000000..d124a4f --- /dev/null +++ b/test/data/evaluationlog/2_targeting_rules/2_rules_matching_targeted_attribute.txt @@ -0,0 +1,7 @@ +WARNING [3003] Cannot evaluate condition (User.Email IS ONE OF ['a@configcat.com', 'b@configcat.com']) for setting 'stringIsInDogDefaultCat' (the User.Email attribute is missing). You should set the User.Email attribute in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ +INFO [5000] Evaluating 'stringIsInDogDefaultCat' for User '{"Identifier":"12345","Custom1":"admin"}' + Evaluating targeting rules and applying the first match if any: + - IF User.Email IS ONE OF ['a@configcat.com', 'b@configcat.com'] THEN 'Dog' => cannot evaluate, the User.Email attribute is missing + The current targeting rule is ignored and the evaluation continues with the next rule. + - IF User.Custom1 IS ONE OF ['admin'] THEN 'Dog' => MATCH, applying rule + Returning 'Dog'. diff --git a/test/data/evaluationlog/2_targeting_rules/2_rules_no_targeted_attribute.txt b/test/data/evaluationlog/2_targeting_rules/2_rules_no_targeted_attribute.txt new file mode 100644 index 0000000..0e02076 --- /dev/null +++ b/test/data/evaluationlog/2_targeting_rules/2_rules_no_targeted_attribute.txt @@ -0,0 +1,9 @@ +WARNING [3003] Cannot evaluate condition (User.Email IS ONE OF ['a@configcat.com', 'b@configcat.com']) for setting 'stringIsInDogDefaultCat' (the User.Email attribute is missing). You should set the User.Email attribute in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ +WARNING [3003] Cannot evaluate condition (User.Custom1 IS ONE OF ['admin']) for setting 'stringIsInDogDefaultCat' (the User.Custom1 attribute is missing). You should set the User.Custom1 attribute in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ +INFO [5000] Evaluating 'stringIsInDogDefaultCat' for User '{"Identifier":"12345"}' + Evaluating targeting rules and applying the first match if any: + - IF User.Email IS ONE OF ['a@configcat.com', 'b@configcat.com'] THEN 'Dog' => cannot evaluate, the User.Email attribute is missing + The current targeting rule is ignored and the evaluation continues with the next rule. + - IF User.Custom1 IS ONE OF ['admin'] THEN 'Dog' => cannot evaluate, the User.Custom1 attribute is missing + The current targeting rule is ignored and the evaluation continues with the next rule. + Returning 'Cat'. diff --git a/test/data/evaluationlog/2_targeting_rules/2_rules_no_user.txt b/test/data/evaluationlog/2_targeting_rules/2_rules_no_user.txt new file mode 100644 index 0000000..73f33c7 --- /dev/null +++ b/test/data/evaluationlog/2_targeting_rules/2_rules_no_user.txt @@ -0,0 +1,8 @@ +WARNING [3001] Cannot evaluate targeting rules and % options for setting 'stringIsInDogDefaultCat' (User Object is missing). You should pass a User Object to the evaluation methods like `getValueAsync()` in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ +INFO [5000] Evaluating 'stringIsInDogDefaultCat' + Evaluating targeting rules and applying the first match if any: + - IF User.Email IS ONE OF ['a@configcat.com', 'b@configcat.com'] THEN 'Dog' => cannot evaluate, User Object is missing + The current targeting rule is ignored and the evaluation continues with the next rule. + - IF User.Custom1 IS ONE OF ['admin'] THEN 'Dog' => cannot evaluate, User Object is missing + The current targeting rule is ignored and the evaluation continues with the next rule. + Returning 'Cat'. diff --git a/test/data/evaluationlog/2_targeting_rules/2_rules_not_matching_targeted_attribute.txt b/test/data/evaluationlog/2_targeting_rules/2_rules_not_matching_targeted_attribute.txt new file mode 100644 index 0000000..72217b2 --- /dev/null +++ b/test/data/evaluationlog/2_targeting_rules/2_rules_not_matching_targeted_attribute.txt @@ -0,0 +1,7 @@ +WARNING [3003] Cannot evaluate condition (User.Email IS ONE OF ['a@configcat.com', 'b@configcat.com']) for setting 'stringIsInDogDefaultCat' (the User.Email attribute is missing). You should set the User.Email attribute in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ +INFO [5000] Evaluating 'stringIsInDogDefaultCat' for User '{"Identifier":"12345","Custom1":"user"}' + Evaluating targeting rules and applying the first match if any: + - IF User.Email IS ONE OF ['a@configcat.com', 'b@configcat.com'] THEN 'Dog' => cannot evaluate, the User.Email attribute is missing + The current targeting rule is ignored and the evaluation continues with the next rule. + - IF User.Custom1 IS ONE OF ['admin'] THEN 'Dog' => no match + Returning 'Cat'. diff --git a/test/data/evaluationlog/_overrides/test_list_truncation.json b/test/data/evaluationlog/_overrides/test_list_truncation.json new file mode 100644 index 0000000..6fdde45 --- /dev/null +++ b/test/data/evaluationlog/_overrides/test_list_truncation.json @@ -0,0 +1,83 @@ +{ + "p": { + "u": "https://cdn-global.configcat.com", + "r": 0, + "s": "test-salt" + }, + "f": { + "booleanKey1": { + "t": 0, + "v": { + "b": false + }, + "r": [ + { + "c": [ + { + "u": { + "a": "Identifier", + "c": 2, + "l": [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10" + ] + } + }, + { + "u": { + "a": "Identifier", + "c": 2, + "l": [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11" + ] + } + }, + { + "u": { + "a": "Identifier", + "c": 2, + "l": [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12" + ] + } + } + ], + "s": { + "v": { + "b": true + } + } + } + ] + } + } +} diff --git a/test/data/evaluationlog/and_rules.json b/test/data/evaluationlog/and_rules.json new file mode 100644 index 0000000..c6ed879 --- /dev/null +++ b/test/data/evaluationlog/and_rules.json @@ -0,0 +1,22 @@ +{ + "configUrl": "https://app.configcat.com/v2/e7a75611-4256-49a5-9320-ce158755e3ba/08dbc325-7f69-4fd4-8af4-cf9f24ec8ac9/08dbc325-9d5e-4988-891c-fd4a45790bd1/08dbc325-9ebd-4587-8171-88f76a3004cb", + "sdkKey": "configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/ByMO9yZNn02kXcm72lnY1A", + "tests": [ + { + "key": "emailAnd", + "defaultValue": "default", + "returnValue": "Cat", + "expectedLog": "and_rules_no_user.txt" + }, + { + "key": "emailAnd", + "defaultValue": "default", + "user": { + "Identifier": "12345", + "Email": "jane@configcat.com" + }, + "returnValue": "Cat", + "expectedLog": "and_rules_user.txt" + } + ] +} diff --git a/test/data/evaluationlog/and_rules/and_rules_no_user.txt b/test/data/evaluationlog/and_rules/and_rules_no_user.txt new file mode 100644 index 0000000..654d079 --- /dev/null +++ b/test/data/evaluationlog/and_rules/and_rules_no_user.txt @@ -0,0 +1,7 @@ +WARNING [3001] Cannot evaluate targeting rules and % options for setting 'emailAnd' (User Object is missing). You should pass a User Object to the evaluation methods like `getValueAsync()` in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ +INFO [5000] Evaluating 'emailAnd' + Evaluating targeting rules and applying the first match if any: + - IF User.Email STARTS WITH ANY OF [<1 hashed value>] => false, skipping the remaining AND conditions + THEN 'Dog' => cannot evaluate, User Object is missing + The current targeting rule is ignored and the evaluation continues with the next rule. + Returning 'Cat'. diff --git a/test/data/evaluationlog/and_rules/and_rules_user.txt b/test/data/evaluationlog/and_rules/and_rules_user.txt new file mode 100644 index 0000000..92c59ce --- /dev/null +++ b/test/data/evaluationlog/and_rules/and_rules_user.txt @@ -0,0 +1,7 @@ +INFO [5000] Evaluating 'emailAnd' for User '{"Identifier":"12345","Email":"jane@configcat.com"}' + Evaluating targeting rules and applying the first match if any: + - IF User.Email STARTS WITH ANY OF [<1 hashed value>] => true + AND User.Email CONTAINS ANY OF ['@'] => true + AND User.Email ENDS WITH ANY OF [<1 hashed value>] => false, skipping the remaining AND conditions + THEN 'Dog' => no match + Returning 'Cat'. diff --git a/test/data/evaluationlog/comparators.json b/test/data/evaluationlog/comparators.json new file mode 100644 index 0000000..5d5631e --- /dev/null +++ b/test/data/evaluationlog/comparators.json @@ -0,0 +1,20 @@ +{ + "configUrl": "https://app.configcat.com/v2/e7a75611-4256-49a5-9320-ce158755e3ba/08dbc325-7f69-4fd4-8af4-cf9f24ec8ac9/08dbc325-9a6b-4947-84e2-91529248278a/08dbc325-9ebd-4587-8171-88f76a3004cb", + "sdkKey": "configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", + "tests": [ + { + "key": "allinone", + "defaultValue": "", + "user": { + "Identifier": "12345", + "Email": "joe@example.com", + "Country": "[\"USA\"]", + "Version": "1.0.0", + "Number": "1.0", + "Date": "1693497500" + }, + "returnValue": "default", + "expectedLog": "allinone.txt" + } + ] +} diff --git a/test/data/evaluationlog/comparators/allinone.txt b/test/data/evaluationlog/comparators/allinone.txt new file mode 100644 index 0000000..84e9b32 --- /dev/null +++ b/test/data/evaluationlog/comparators/allinone.txt @@ -0,0 +1,57 @@ +INFO [5000] Evaluating 'allinone' for User '{"Identifier":"12345","Email":"joe@example.com","Country":"[\"USA\"]","Version":"1.0.0","Number":"1.0","Date":"1693497500"}' + Evaluating targeting rules and applying the first match if any: + - IF User.Email EQUALS '' => true + AND User.Email NOT EQUALS '' => false, skipping the remaining AND conditions + THEN '1h' => no match + - IF User.Email EQUALS 'joe@example.com' => true + AND User.Email NOT EQUALS 'joe@example.com' => false, skipping the remaining AND conditions + THEN '1c' => no match + - IF User.Email IS ONE OF [<1 hashed value>] => true + AND User.Email IS NOT ONE OF [<1 hashed value>] => false, skipping the remaining AND conditions + THEN '2h' => no match + - IF User.Email IS ONE OF ['joe@example.com'] => true + AND User.Email IS NOT ONE OF ['joe@example.com'] => false, skipping the remaining AND conditions + THEN '2c' => no match + - IF User.Email STARTS WITH ANY OF [<1 hashed value>] => true + AND User.Email NOT STARTS WITH ANY OF [<1 hashed value>] => false, skipping the remaining AND conditions + THEN '3h' => no match + - IF User.Email STARTS WITH ANY OF ['joe@'] => true + AND User.Email NOT STARTS WITH ANY OF ['joe@'] => false, skipping the remaining AND conditions + THEN '3c' => no match + - IF User.Email ENDS WITH ANY OF [<1 hashed value>] => true + AND User.Email NOT ENDS WITH ANY OF [<1 hashed value>] => false, skipping the remaining AND conditions + THEN '4h' => no match + - IF User.Email ENDS WITH ANY OF ['@example.com'] => true + AND User.Email NOT ENDS WITH ANY OF ['@example.com'] => false, skipping the remaining AND conditions + THEN '4c' => no match + - IF User.Email CONTAINS ANY OF ['e@e'] => true + AND User.Email NOT CONTAINS ANY OF ['e@e'] => false, skipping the remaining AND conditions + THEN '5' => no match + - IF User.Version IS ONE OF ['1.0.0'] => true + AND User.Version IS NOT ONE OF ['1.0.0'] => false, skipping the remaining AND conditions + THEN '6' => no match + - IF User.Version < '1.0.1' => true + AND User.Version >= '1.0.1' => false, skipping the remaining AND conditions + THEN '7' => no match + - IF User.Version > '0.9.9' => true + AND User.Version <= '0.9.9' => false, skipping the remaining AND conditions + THEN '8' => no match + - IF User.Number = '1' => true + AND User.Number != '1' => false, skipping the remaining AND conditions + THEN '9' => no match + - IF User.Number < '1.1' => true + AND User.Number >= '1.1' => false, skipping the remaining AND conditions + THEN '10' => no match + - IF User.Number > '0.9' => true + AND User.Number <= '0.9' => false, skipping the remaining AND conditions + THEN '11' => no match + - IF User.Date BEFORE '1693497600' (2023-08-31T16:00:00.000Z UTC) => true + AND User.Date AFTER '1693497600' (2023-08-31T16:00:00.000Z UTC) => false, skipping the remaining AND conditions + THEN '12' => no match + - IF User.Country ARRAY CONTAINS ANY OF [<1 hashed value>] => true + AND User.Country ARRAY NOT CONTAINS ANY OF [<1 hashed value>] => false, skipping the remaining AND conditions + THEN '13h' => no match + - IF User.Country ARRAY CONTAINS ANY OF ['USA'] => true + AND User.Country ARRAY NOT CONTAINS ANY OF ['USA'] => false, skipping the remaining AND conditions + THEN '13c' => no match + Returning 'default'. diff --git a/test/data/evaluationlog/epoch_date_validation.json b/test/data/evaluationlog/epoch_date_validation.json new file mode 100644 index 0000000..e916d21 --- /dev/null +++ b/test/data/evaluationlog/epoch_date_validation.json @@ -0,0 +1,16 @@ +{ + "configUrl": "https://app.configcat.com/v2/e7a75611-4256-49a5-9320-ce158755e3ba/08dbc325-7f69-4fd4-8af4-cf9f24ec8ac9/08dbc325-9a6b-4947-84e2-91529248278a/08dbc325-9ebd-4587-8171-88f76a3004cb", + "sdkKey": "configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", + "tests": [ + { + "key": "boolTrueIn202304", + "defaultValue": true, + "returnValue": false, + "expectedLog": "date_error.txt", + "user": { + "Identifier": "12345", + "Custom1": "2023.04.10" + } + } + ] +} diff --git a/test/data/evaluationlog/epoch_date_validation/date_error.txt b/test/data/evaluationlog/epoch_date_validation/date_error.txt new file mode 100644 index 0000000..610b8f5 --- /dev/null +++ b/test/data/evaluationlog/epoch_date_validation/date_error.txt @@ -0,0 +1,7 @@ +WARNING [3004] Cannot evaluate condition (User.Custom1 AFTER '1680307200' (2023-04-01T00:00:00.000Z UTC)) for setting 'boolTrueIn202304' ('2023.04.10' is not a valid Unix timestamp (number of seconds elapsed since Unix epoch)). Please check the User.Custom1 attribute and make sure that its value corresponds to the comparison operator. +INFO [5000] Evaluating 'boolTrueIn202304' for User '{"Identifier":"12345","Custom1":"2023.04.10"}' + Evaluating targeting rules and applying the first match if any: + - IF User.Custom1 AFTER '1680307200' (2023-04-01T00:00:00.000Z UTC) => false, skipping the remaining AND conditions + THEN 'true' => cannot evaluate, the User.Custom1 attribute is invalid ('2023.04.10' is not a valid Unix timestamp (number of seconds elapsed since Unix epoch)) + The current targeting rule is ignored and the evaluation continues with the next rule. + Returning 'false'. diff --git a/test/data/evaluationlog/list_truncation.json b/test/data/evaluationlog/list_truncation.json new file mode 100644 index 0000000..64e9426 --- /dev/null +++ b/test/data/evaluationlog/list_truncation.json @@ -0,0 +1,14 @@ +{ + "jsonOverride": "test_list_truncation.json", + "tests": [ + { + "key": "booleanKey1", + "defaultValue": false, + "user": { + "Identifier": "12" + }, + "returnValue": true, + "expectedLog": "list_truncation.txt" + } + ] +} diff --git a/test/data/evaluationlog/list_truncation/list_truncation.txt b/test/data/evaluationlog/list_truncation/list_truncation.txt new file mode 100644 index 0000000..10a0195 --- /dev/null +++ b/test/data/evaluationlog/list_truncation/list_truncation.txt @@ -0,0 +1,7 @@ +INFO [5000] Evaluating 'booleanKey1' for User '{"Identifier":"12"}' + Evaluating targeting rules and applying the first match if any: + - IF User.Identifier CONTAINS ANY OF ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'] => true + AND User.Identifier CONTAINS ANY OF ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', ... <1 more value>] => true + AND User.Identifier CONTAINS ANY OF ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', ... <2 more values>] => true + THEN 'true' => MATCH, applying rule + Returning 'true'. diff --git a/test/data/evaluationlog/number_validation.json b/test/data/evaluationlog/number_validation.json new file mode 100644 index 0000000..640cf3d --- /dev/null +++ b/test/data/evaluationlog/number_validation.json @@ -0,0 +1,16 @@ +{ + "configUrl": "https://app.configcat.com/08d5a03c-feb7-af1e-a1fa-40b3329f8bed/08d747f0-5986-c2ef-eef3-ec778e32e10a/244cf8b0-f604-11e8-b543-f23c917f9d8d", + "sdkKey": "PKDVCLf-Hq-h-kCzMp-L7Q/uGyK3q9_ckmdxRyI7vjwCw", + "tests": [ + { + "key": "number", + "defaultValue": "default", + "returnValue": "Default", + "expectedLog": "number_error.txt", + "user": { + "Identifier": "12345", + "Custom1": "not_a_number" + } + } + ] +} diff --git a/test/data/evaluationlog/number_validation/number_error.txt b/test/data/evaluationlog/number_validation/number_error.txt new file mode 100644 index 0000000..f936809 --- /dev/null +++ b/test/data/evaluationlog/number_validation/number_error.txt @@ -0,0 +1,6 @@ +WARNING [3004] Cannot evaluate condition (User.Custom1 != '5') for setting 'number' ('not_a_number' is not a valid decimal number). Please check the User.Custom1 attribute and make sure that its value corresponds to the comparison operator. +INFO [5000] Evaluating 'number' for User '{"Identifier":"12345","Custom1":"not_a_number"}' + Evaluating targeting rules and applying the first match if any: + - IF User.Custom1 != '5' THEN '<>5' => cannot evaluate, the User.Custom1 attribute is invalid ('not_a_number' is not a valid decimal number) + The current targeting rule is ignored and the evaluation continues with the next rule. + Returning 'Default'. diff --git a/test/data/evaluationlog/options_after_targeting_rule.json b/test/data/evaluationlog/options_after_targeting_rule.json new file mode 100644 index 0000000..803840e --- /dev/null +++ b/test/data/evaluationlog/options_after_targeting_rule.json @@ -0,0 +1,41 @@ +{ + "configUrl": "https://app.configcat.com/08d5a03c-feb7-af1e-a1fa-40b3329f8bed/08d62463-86ec-8fde-f5b5-1c5c426fc830/244cf8b0-f604-11e8-b543-f23c917f9d8d", + "sdkKey": "PKDVCLf-Hq-h-kCzMp-L7Q/psuH7BGHoUmdONrzzUOY7A", + "tests": [ + { + "key": "integer25One25Two25Three25FourAdvancedRules", + "defaultValue": 42, + "returnValue": -1, + "expectedLog": "options_after_targeting_rule_no_user.txt" + }, + { + "key": "integer25One25Two25Three25FourAdvancedRules", + "defaultValue": 42, + "user": { + "Identifier": "12345" + }, + "returnValue": 2, + "expectedLog": "options_after_targeting_rule_no_targeted_attribute.txt" + }, + { + "key": "integer25One25Two25Three25FourAdvancedRules", + "defaultValue": 42, + "user": { + "Identifier": "12345", + "Email": "joe@example.com" + }, + "returnValue": 2, + "expectedLog": "options_after_targeting_rule_not_matching_targeted_attribute.txt" + }, + { + "key": "integer25One25Two25Three25FourAdvancedRules", + "defaultValue": 42, + "user": { + "Identifier": "12345", + "Email": "joe@configcat.com" + }, + "returnValue": 5, + "expectedLog": "options_after_targeting_rule_matching_targeted_attribute.txt" + } + ] +} diff --git a/test/data/evaluationlog/options_after_targeting_rule/options_after_targeting_rule_matching_targeted_attribute.txt b/test/data/evaluationlog/options_after_targeting_rule/options_after_targeting_rule_matching_targeted_attribute.txt new file mode 100644 index 0000000..6815fa3 --- /dev/null +++ b/test/data/evaluationlog/options_after_targeting_rule/options_after_targeting_rule_matching_targeted_attribute.txt @@ -0,0 +1,4 @@ +INFO [5000] Evaluating 'integer25One25Two25Three25FourAdvancedRules' for User '{"Identifier":"12345","Email":"joe@configcat.com"}' + Evaluating targeting rules and applying the first match if any: + - IF User.Email CONTAINS ANY OF ['@configcat.com'] THEN '5' => MATCH, applying rule + Returning '5'. diff --git a/test/data/evaluationlog/options_after_targeting_rule/options_after_targeting_rule_no_targeted_attribute.txt b/test/data/evaluationlog/options_after_targeting_rule/options_after_targeting_rule_no_targeted_attribute.txt new file mode 100644 index 0000000..8e6facb --- /dev/null +++ b/test/data/evaluationlog/options_after_targeting_rule/options_after_targeting_rule_no_targeted_attribute.txt @@ -0,0 +1,9 @@ +WARNING [3003] Cannot evaluate condition (User.Email CONTAINS ANY OF ['@configcat.com']) for setting 'integer25One25Two25Three25FourAdvancedRules' (the User.Email attribute is missing). You should set the User.Email attribute in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ +INFO [5000] Evaluating 'integer25One25Two25Three25FourAdvancedRules' for User '{"Identifier":"12345"}' + Evaluating targeting rules and applying the first match if any: + - IF User.Email CONTAINS ANY OF ['@configcat.com'] THEN '5' => cannot evaluate, the User.Email attribute is missing + The current targeting rule is ignored and the evaluation continues with the next rule. + Evaluating % options based on the User.Identifier attribute: + - Computing hash in the [0..99] range from User.Identifier => 25 (this value is sticky and consistent across all SDKs) + - Hash value 25 selects % option 2 (25%), '2'. + Returning '2'. diff --git a/test/data/evaluationlog/options_after_targeting_rule/options_after_targeting_rule_no_user.txt b/test/data/evaluationlog/options_after_targeting_rule/options_after_targeting_rule_no_user.txt new file mode 100644 index 0000000..674200d --- /dev/null +++ b/test/data/evaluationlog/options_after_targeting_rule/options_after_targeting_rule_no_user.txt @@ -0,0 +1,7 @@ +WARNING [3001] Cannot evaluate targeting rules and % options for setting 'integer25One25Two25Three25FourAdvancedRules' (User Object is missing). You should pass a User Object to the evaluation methods like `getValueAsync()` in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ +INFO [5000] Evaluating 'integer25One25Two25Three25FourAdvancedRules' + Evaluating targeting rules and applying the first match if any: + - IF User.Email CONTAINS ANY OF ['@configcat.com'] THEN '5' => cannot evaluate, User Object is missing + The current targeting rule is ignored and the evaluation continues with the next rule. + Skipping % options because the User Object is missing. + Returning '-1'. diff --git a/test/data/evaluationlog/options_after_targeting_rule/options_after_targeting_rule_not_matching_targeted_attribute.txt b/test/data/evaluationlog/options_after_targeting_rule/options_after_targeting_rule_not_matching_targeted_attribute.txt new file mode 100644 index 0000000..c412e5a --- /dev/null +++ b/test/data/evaluationlog/options_after_targeting_rule/options_after_targeting_rule_not_matching_targeted_attribute.txt @@ -0,0 +1,7 @@ +INFO [5000] Evaluating 'integer25One25Two25Three25FourAdvancedRules' for User '{"Identifier":"12345","Email":"joe@example.com"}' + Evaluating targeting rules and applying the first match if any: + - IF User.Email CONTAINS ANY OF ['@configcat.com'] THEN '5' => no match + Evaluating % options based on the User.Identifier attribute: + - Computing hash in the [0..99] range from User.Identifier => 25 (this value is sticky and consistent across all SDKs) + - Hash value 25 selects % option 2 (25%), '2'. + Returning '2'. diff --git a/test/data/evaluationlog/options_based_on_custom_attr.json b/test/data/evaluationlog/options_based_on_custom_attr.json new file mode 100644 index 0000000..5f8d1c6 --- /dev/null +++ b/test/data/evaluationlog/options_based_on_custom_attr.json @@ -0,0 +1,31 @@ +{ + "configUrl": "https://app.configcat.com/v2/e7a75611-4256-49a5-9320-ce158755e3ba/08dbc325-7f69-4fd4-8af4-cf9f24ec8ac9/08dbc325-9e4e-4f59-86b2-5da50924b6ca/08dbc325-9ebd-4587-8171-88f76a3004cb", + "sdkKey": "configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/P4e3fAz_1ky2-Zg2e4cbkw", + "tests": [ + { + "key": "string75Cat0Dog25Falcon0HorseCustomAttr", + "defaultValue": "default", + "returnValue": "Chicken", + "expectedLog": "options_custom_attribute_no_user.txt" + }, + { + "key": "string75Cat0Dog25Falcon0HorseCustomAttr", + "defaultValue": "default", + "user": { + "Identifier": "12345" + }, + "returnValue": "Chicken", + "expectedLog": "no_options_custom_attribute.txt" + }, + { + "key": "string75Cat0Dog25Falcon0HorseCustomAttr", + "defaultValue": "default", + "user": { + "Identifier": "12345", + "Country": "US" + }, + "returnValue": "Cat", + "expectedLog": "matching_options_custom_attribute.txt" + } + ] +} diff --git a/test/data/evaluationlog/options_based_on_custom_attr/matching_options_custom_attribute.txt b/test/data/evaluationlog/options_based_on_custom_attr/matching_options_custom_attribute.txt new file mode 100644 index 0000000..2621086 --- /dev/null +++ b/test/data/evaluationlog/options_based_on_custom_attr/matching_options_custom_attribute.txt @@ -0,0 +1,5 @@ +INFO [5000] Evaluating 'string75Cat0Dog25Falcon0HorseCustomAttr' for User '{"Identifier":"12345","Country":"US"}' + Evaluating % options based on the User.Country attribute: + - Computing hash in the [0..99] range from User.Country => 70 (this value is sticky and consistent across all SDKs) + - Hash value 70 selects % option 1 (75%), 'Cat'. + Returning 'Cat'. diff --git a/test/data/evaluationlog/options_based_on_custom_attr/no_options_custom_attribute.txt b/test/data/evaluationlog/options_based_on_custom_attr/no_options_custom_attribute.txt new file mode 100644 index 0000000..c92c5bc --- /dev/null +++ b/test/data/evaluationlog/options_based_on_custom_attr/no_options_custom_attribute.txt @@ -0,0 +1,4 @@ +WARNING [3003] Cannot evaluate % options for setting 'string75Cat0Dog25Falcon0HorseCustomAttr' (the User.Country attribute is missing). You should set the User.Country attribute in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ +INFO [5000] Evaluating 'string75Cat0Dog25Falcon0HorseCustomAttr' for User '{"Identifier":"12345"}' + Skipping % options because the User.Country attribute is missing. + Returning 'Chicken'. diff --git a/test/data/evaluationlog/options_based_on_custom_attr/options_custom_attribute_no_user.txt b/test/data/evaluationlog/options_based_on_custom_attr/options_custom_attribute_no_user.txt new file mode 100644 index 0000000..da00a4e --- /dev/null +++ b/test/data/evaluationlog/options_based_on_custom_attr/options_custom_attribute_no_user.txt @@ -0,0 +1,4 @@ +WARNING [3001] Cannot evaluate targeting rules and % options for setting 'string75Cat0Dog25Falcon0HorseCustomAttr' (User Object is missing). You should pass a User Object to the evaluation methods like `getValueAsync()` in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ +INFO [5000] Evaluating 'string75Cat0Dog25Falcon0HorseCustomAttr' + Skipping % options because the User Object is missing. + Returning 'Chicken'. diff --git a/test/data/evaluationlog/options_based_on_user_id.json b/test/data/evaluationlog/options_based_on_user_id.json new file mode 100644 index 0000000..442f575 --- /dev/null +++ b/test/data/evaluationlog/options_based_on_user_id.json @@ -0,0 +1,21 @@ +{ + "configUrl": "https://app.configcat.com/08d5a03c-feb7-af1e-a1fa-40b3329f8bed/08d62463-86ec-8fde-f5b5-1c5c426fc830/244cf8b0-f604-11e8-b543-f23c917f9d8d", + "sdkKey": "PKDVCLf-Hq-h-kCzMp-L7Q/psuH7BGHoUmdONrzzUOY7A", + "tests": [ + { + "key": "string75Cat0Dog25Falcon0Horse", + "defaultValue": "default", + "returnValue": "Chicken", + "expectedLog": "options_user_attribute_no_user.txt" + }, + { + "key": "string75Cat0Dog25Falcon0Horse", + "defaultValue": "default", + "user": { + "Identifier": "12345" + }, + "returnValue": "Cat", + "expectedLog": "options_user_attribute_user.txt" + } + ] +} diff --git a/test/data/evaluationlog/options_based_on_user_id/options_user_attribute_no_user.txt b/test/data/evaluationlog/options_based_on_user_id/options_user_attribute_no_user.txt new file mode 100644 index 0000000..fca2dbd --- /dev/null +++ b/test/data/evaluationlog/options_based_on_user_id/options_user_attribute_no_user.txt @@ -0,0 +1,4 @@ +WARNING [3001] Cannot evaluate targeting rules and % options for setting 'string75Cat0Dog25Falcon0Horse' (User Object is missing). You should pass a User Object to the evaluation methods like `getValueAsync()` in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ +INFO [5000] Evaluating 'string75Cat0Dog25Falcon0Horse' + Skipping % options because the User Object is missing. + Returning 'Chicken'. diff --git a/test/data/evaluationlog/options_based_on_user_id/options_user_attribute_user.txt b/test/data/evaluationlog/options_based_on_user_id/options_user_attribute_user.txt new file mode 100644 index 0000000..dac8dd6 --- /dev/null +++ b/test/data/evaluationlog/options_based_on_user_id/options_user_attribute_user.txt @@ -0,0 +1,5 @@ +INFO [5000] Evaluating 'string75Cat0Dog25Falcon0Horse' for User '{"Identifier":"12345"}' + Evaluating % options based on the User.Identifier attribute: + - Computing hash in the [0..99] range from User.Identifier => 21 (this value is sticky and consistent across all SDKs) + - Hash value 21 selects % option 1 (75%), 'Cat'. + Returning 'Cat'. diff --git a/test/data/evaluationlog/options_within_targeting_rule.json b/test/data/evaluationlog/options_within_targeting_rule.json new file mode 100644 index 0000000..4c6c533 --- /dev/null +++ b/test/data/evaluationlog/options_within_targeting_rule.json @@ -0,0 +1,52 @@ +{ + "configUrl": "https://app.configcat.com/v2/e7a75611-4256-49a5-9320-ce158755e3ba/08dbc325-7f69-4fd4-8af4-cf9f24ec8ac9/08dbc325-9e4e-4f59-86b2-5da50924b6ca/08dbc325-9ebd-4587-8171-88f76a3004cb", + "sdkKey": "configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/P4e3fAz_1ky2-Zg2e4cbkw", + "tests": [ + { + "key": "stringContainsString75Cat0Dog25Falcon0HorseDefaultCat", + "defaultValue": "default", + "returnValue": "Cat", + "expectedLog": "options_within_targeting_rule_no_user.txt" + }, + { + "key": "stringContainsString75Cat0Dog25Falcon0HorseDefaultCat", + "defaultValue": "default", + "user": { + "Identifier": "12345" + }, + "returnValue": "Cat", + "expectedLog": "options_within_targeting_rule_no_targeted_attribute.txt" + }, + { + "key": "stringContainsString75Cat0Dog25Falcon0HorseDefaultCat", + "defaultValue": "default", + "user": { + "Identifier": "12345", + "Email": "joe@example.com" + }, + "returnValue": "Cat", + "expectedLog": "options_within_targeting_rule_not_matching_targeted_attribute.txt" + }, + { + "key": "stringContainsString75Cat0Dog25Falcon0HorseDefaultCat", + "defaultValue": "default", + "user": { + "Identifier": "12345", + "Email": "joe@configcat.com" + }, + "returnValue": "Cat", + "expectedLog": "options_within_targeting_rule_matching_targeted_attribute_no_options_attribute.txt" + }, + { + "key": "stringContainsString75Cat0Dog25Falcon0HorseDefaultCat", + "defaultValue": "default", + "user": { + "Identifier": "12345", + "Email": "joe@configcat.com", + "Country": "US" + }, + "returnValue": "Cat", + "expectedLog": "options_within_targeting_rule_matching_targeted_attribute_options_attribute.txt" + } + ] +} diff --git a/test/data/evaluationlog/options_within_targeting_rule/options_within_targeting_rule_matching_targeted_attribute_no_options_attribute.txt b/test/data/evaluationlog/options_within_targeting_rule/options_within_targeting_rule_matching_targeted_attribute_no_options_attribute.txt new file mode 100644 index 0000000..db721f5 --- /dev/null +++ b/test/data/evaluationlog/options_within_targeting_rule/options_within_targeting_rule_matching_targeted_attribute_no_options_attribute.txt @@ -0,0 +1,7 @@ +WARNING [3003] Cannot evaluate % options for setting 'stringContainsString75Cat0Dog25Falcon0HorseDefaultCat' (the User.Country attribute is missing). You should set the User.Country attribute in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ +INFO [5000] Evaluating 'stringContainsString75Cat0Dog25Falcon0HorseDefaultCat' for User '{"Identifier":"12345","Email":"joe@configcat.com"}' + Evaluating targeting rules and applying the first match if any: + - IF User.Email CONTAINS ANY OF ['@configcat.com'] THEN % options => MATCH, applying rule + Skipping % options because the User.Country attribute is missing. + The current targeting rule is ignored and the evaluation continues with the next rule. + Returning 'Cat'. diff --git a/test/data/evaluationlog/options_within_targeting_rule/options_within_targeting_rule_matching_targeted_attribute_options_attribute.txt b/test/data/evaluationlog/options_within_targeting_rule/options_within_targeting_rule_matching_targeted_attribute_options_attribute.txt new file mode 100644 index 0000000..8129521 --- /dev/null +++ b/test/data/evaluationlog/options_within_targeting_rule/options_within_targeting_rule_matching_targeted_attribute_options_attribute.txt @@ -0,0 +1,7 @@ +INFO [5000] Evaluating 'stringContainsString75Cat0Dog25Falcon0HorseDefaultCat' for User '{"Identifier":"12345","Email":"joe@configcat.com","Country":"US"}' + Evaluating targeting rules and applying the first match if any: + - IF User.Email CONTAINS ANY OF ['@configcat.com'] THEN % options => MATCH, applying rule + Evaluating % options based on the User.Country attribute: + - Computing hash in the [0..99] range from User.Country => 63 (this value is sticky and consistent across all SDKs) + - Hash value 63 selects % option 1 (75%), 'Cat'. + Returning 'Cat'. diff --git a/test/data/evaluationlog/options_within_targeting_rule/options_within_targeting_rule_no_targeted_attribute.txt b/test/data/evaluationlog/options_within_targeting_rule/options_within_targeting_rule_no_targeted_attribute.txt new file mode 100644 index 0000000..74f812f --- /dev/null +++ b/test/data/evaluationlog/options_within_targeting_rule/options_within_targeting_rule_no_targeted_attribute.txt @@ -0,0 +1,6 @@ +WARNING [3003] Cannot evaluate condition (User.Email CONTAINS ANY OF ['@configcat.com']) for setting 'stringContainsString75Cat0Dog25Falcon0HorseDefaultCat' (the User.Email attribute is missing). You should set the User.Email attribute in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ +INFO [5000] Evaluating 'stringContainsString75Cat0Dog25Falcon0HorseDefaultCat' for User '{"Identifier":"12345"}' + Evaluating targeting rules and applying the first match if any: + - IF User.Email CONTAINS ANY OF ['@configcat.com'] THEN % options => cannot evaluate, the User.Email attribute is missing + The current targeting rule is ignored and the evaluation continues with the next rule. + Returning 'Cat'. diff --git a/test/data/evaluationlog/options_within_targeting_rule/options_within_targeting_rule_no_user.txt b/test/data/evaluationlog/options_within_targeting_rule/options_within_targeting_rule_no_user.txt new file mode 100644 index 0000000..17db876 --- /dev/null +++ b/test/data/evaluationlog/options_within_targeting_rule/options_within_targeting_rule_no_user.txt @@ -0,0 +1,6 @@ +WARNING [3001] Cannot evaluate targeting rules and % options for setting 'stringContainsString75Cat0Dog25Falcon0HorseDefaultCat' (User Object is missing). You should pass a User Object to the evaluation methods like `getValueAsync()` in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ +INFO [5000] Evaluating 'stringContainsString75Cat0Dog25Falcon0HorseDefaultCat' + Evaluating targeting rules and applying the first match if any: + - IF User.Email CONTAINS ANY OF ['@configcat.com'] THEN % options => cannot evaluate, User Object is missing + The current targeting rule is ignored and the evaluation continues with the next rule. + Returning 'Cat'. diff --git a/test/data/evaluationlog/options_within_targeting_rule/options_within_targeting_rule_not_matching_targeted_attribute.txt b/test/data/evaluationlog/options_within_targeting_rule/options_within_targeting_rule_not_matching_targeted_attribute.txt new file mode 100644 index 0000000..dd6032e --- /dev/null +++ b/test/data/evaluationlog/options_within_targeting_rule/options_within_targeting_rule_not_matching_targeted_attribute.txt @@ -0,0 +1,4 @@ +INFO [5000] Evaluating 'stringContainsString75Cat0Dog25Falcon0HorseDefaultCat' for User '{"Identifier":"12345","Email":"joe@example.com"}' + Evaluating targeting rules and applying the first match if any: + - IF User.Email CONTAINS ANY OF ['@configcat.com'] THEN % options => no match + Returning 'Cat'. diff --git a/test/data/evaluationlog/prerequisite_flag.json b/test/data/evaluationlog/prerequisite_flag.json new file mode 100644 index 0000000..674e2d3 --- /dev/null +++ b/test/data/evaluationlog/prerequisite_flag.json @@ -0,0 +1,35 @@ +{ + "configUrl": "https://app.configcat.com/v2/e7a75611-4256-49a5-9320-ce158755e3ba/08dbc325-7f69-4fd4-8af4-cf9f24ec8ac9/08dbc325-9d5e-4988-891c-fd4a45790bd1/08dbc325-9ebd-4587-8171-88f76a3004cb", + "sdkKey": "configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/ByMO9yZNn02kXcm72lnY1A", + "tests": [ + { + "key": "dependentFeatureWithUserCondition", + "defaultValue": "default", + "returnValue": "Chicken", + "expectedLog": "prerequisite_flag_no_user_needed_by_dep.txt" + }, + { + "key": "dependentFeature", + "defaultValue": "default", + "returnValue": "Chicken", + "expectedLog": "prerequisite_flag_no_user_needed_by_prereq.txt" + }, + { + "key": "dependentFeatureWithUserCondition2", + "defaultValue": "default", + "returnValue": "Frog", + "expectedLog": "prerequisite_flag_no_user_needed_by_both.txt" + }, + { + "key": "dependentFeature", + "defaultValue": "default", + "user": { + "Identifier": "12345", + "Email": "kate@configcat.com", + "Country": "USA" + }, + "returnValue": "Horse", + "expectedLog": "prerequisite_flag.txt" + } + ] +} diff --git a/test/data/evaluationlog/prerequisite_flag/prerequisite_flag.txt b/test/data/evaluationlog/prerequisite_flag/prerequisite_flag.txt new file mode 100644 index 0000000..1d9022b --- /dev/null +++ b/test/data/evaluationlog/prerequisite_flag/prerequisite_flag.txt @@ -0,0 +1,32 @@ +INFO [5000] Evaluating 'dependentFeature' for User '{"Identifier":"12345","Email":"kate@configcat.com","Country":"USA"}' + Evaluating targeting rules and applying the first match if any: + - IF Flag 'mainFeature' EQUALS 'target' + ( + Evaluating prerequisite flag 'mainFeature': + Evaluating targeting rules and applying the first match if any: + - IF User.Email ENDS WITH ANY OF [<1 hashed value>] => false, skipping the remaining AND conditions + THEN 'private' => no match + - IF User.Country IS ONE OF [<1 hashed value>] => true + AND User IS NOT IN SEGMENT 'Beta Users' + ( + Evaluating segment 'Beta Users': + - IF User.Email IS ONE OF [<2 hashed values>] => false, skipping the remaining AND conditions + Segment evaluation result: User IS NOT IN SEGMENT. + Condition (User IS NOT IN SEGMENT 'Beta Users') evaluates to true. + ) => true + AND User IS NOT IN SEGMENT 'Developers' + ( + Evaluating segment 'Developers': + - IF User.Email IS ONE OF [<2 hashed values>] => false, skipping the remaining AND conditions + Segment evaluation result: User IS NOT IN SEGMENT. + Condition (User IS NOT IN SEGMENT 'Developers') evaluates to true. + ) => true + THEN 'target' => MATCH, applying rule + Prerequisite flag evaluation result: 'target'. + Condition (Flag 'mainFeature' EQUALS 'target') evaluates to true. + ) + THEN % options => MATCH, applying rule + Evaluating % options based on the User.Identifier attribute: + - Computing hash in the [0..99] range from User.Identifier => 78 (this value is sticky and consistent across all SDKs) + - Hash value 78 selects % option 4 (25%), 'Horse'. + Returning 'Horse'. diff --git a/test/data/evaluationlog/prerequisite_flag/prerequisite_flag_no_user_needed_by_both.txt b/test/data/evaluationlog/prerequisite_flag/prerequisite_flag_no_user_needed_by_both.txt new file mode 100644 index 0000000..d08702c --- /dev/null +++ b/test/data/evaluationlog/prerequisite_flag/prerequisite_flag_no_user_needed_by_both.txt @@ -0,0 +1,38 @@ +WARNING [3001] Cannot evaluate targeting rules and % options for setting 'dependentFeatureWithUserCondition2' (User Object is missing). You should pass a User Object to the evaluation methods like `getValueAsync()` in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ +WARNING [3001] Cannot evaluate targeting rules and % options for setting 'mainFeature' (User Object is missing). You should pass a User Object to the evaluation methods like `getValueAsync()` in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ +WARNING [3001] Cannot evaluate targeting rules and % options for setting 'mainFeature' (User Object is missing). You should pass a User Object to the evaluation methods like `getValueAsync()` in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ +INFO [5000] Evaluating 'dependentFeatureWithUserCondition2' + Evaluating targeting rules and applying the first match if any: + - IF User.Email IS ONE OF [<2 hashed values>] THEN 'Dog' => cannot evaluate, User Object is missing + The current targeting rule is ignored and the evaluation continues with the next rule. + - IF Flag 'mainFeature' EQUALS 'public' + ( + Evaluating prerequisite flag 'mainFeature': + Evaluating targeting rules and applying the first match if any: + - IF User.Email ENDS WITH ANY OF [<1 hashed value>] => false, skipping the remaining AND conditions + THEN 'private' => cannot evaluate, User Object is missing + The current targeting rule is ignored and the evaluation continues with the next rule. + - IF User.Country IS ONE OF [<1 hashed value>] => false, skipping the remaining AND conditions + THEN 'target' => cannot evaluate, User Object is missing + The current targeting rule is ignored and the evaluation continues with the next rule. + Prerequisite flag evaluation result: 'public'. + Condition (Flag 'mainFeature' EQUALS 'public') evaluates to true. + ) + THEN % options => MATCH, applying rule + Skipping % options because the User Object is missing. + The current targeting rule is ignored and the evaluation continues with the next rule. + - IF Flag 'mainFeature' EQUALS 'public' + ( + Evaluating prerequisite flag 'mainFeature': + Evaluating targeting rules and applying the first match if any: + - IF User.Email ENDS WITH ANY OF [<1 hashed value>] => false, skipping the remaining AND conditions + THEN 'private' => cannot evaluate, User Object is missing + The current targeting rule is ignored and the evaluation continues with the next rule. + - IF User.Country IS ONE OF [<1 hashed value>] => false, skipping the remaining AND conditions + THEN 'target' => cannot evaluate, User Object is missing + The current targeting rule is ignored and the evaluation continues with the next rule. + Prerequisite flag evaluation result: 'public'. + Condition (Flag 'mainFeature' EQUALS 'public') evaluates to true. + ) + THEN 'Frog' => MATCH, applying rule + Returning 'Frog'. diff --git a/test/data/evaluationlog/prerequisite_flag/prerequisite_flag_no_user_needed_by_dep.txt b/test/data/evaluationlog/prerequisite_flag/prerequisite_flag_no_user_needed_by_dep.txt new file mode 100644 index 0000000..af2f442 --- /dev/null +++ b/test/data/evaluationlog/prerequisite_flag/prerequisite_flag_no_user_needed_by_dep.txt @@ -0,0 +1,15 @@ +WARNING [3001] Cannot evaluate targeting rules and % options for setting 'dependentFeatureWithUserCondition' (User Object is missing). You should pass a User Object to the evaluation methods like `getValueAsync()` in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ +INFO [5000] Evaluating 'dependentFeatureWithUserCondition' + Evaluating targeting rules and applying the first match if any: + - IF User.Email IS ONE OF [<2 hashed values>] THEN 'Dog' => cannot evaluate, User Object is missing + The current targeting rule is ignored and the evaluation continues with the next rule. + - IF Flag 'mainFeatureWithoutUserCondition' EQUALS 'true' + ( + Evaluating prerequisite flag 'mainFeatureWithoutUserCondition': + Prerequisite flag evaluation result: 'true'. + Condition (Flag 'mainFeatureWithoutUserCondition' EQUALS 'true') evaluates to true. + ) + THEN % options => MATCH, applying rule + Skipping % options because the User Object is missing. + The current targeting rule is ignored and the evaluation continues with the next rule. + Returning 'Chicken'. diff --git a/test/data/evaluationlog/prerequisite_flag/prerequisite_flag_no_user_needed_by_prereq.txt b/test/data/evaluationlog/prerequisite_flag/prerequisite_flag_no_user_needed_by_prereq.txt new file mode 100644 index 0000000..8c2c4c4 --- /dev/null +++ b/test/data/evaluationlog/prerequisite_flag/prerequisite_flag_no_user_needed_by_prereq.txt @@ -0,0 +1,18 @@ +WARNING [3001] Cannot evaluate targeting rules and % options for setting 'mainFeature' (User Object is missing). You should pass a User Object to the evaluation methods like `getValueAsync()` in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ +INFO [5000] Evaluating 'dependentFeature' + Evaluating targeting rules and applying the first match if any: + - IF Flag 'mainFeature' EQUALS 'target' + ( + Evaluating prerequisite flag 'mainFeature': + Evaluating targeting rules and applying the first match if any: + - IF User.Email ENDS WITH ANY OF [<1 hashed value>] => false, skipping the remaining AND conditions + THEN 'private' => cannot evaluate, User Object is missing + The current targeting rule is ignored and the evaluation continues with the next rule. + - IF User.Country IS ONE OF [<1 hashed value>] => false, skipping the remaining AND conditions + THEN 'target' => cannot evaluate, User Object is missing + The current targeting rule is ignored and the evaluation continues with the next rule. + Prerequisite flag evaluation result: 'public'. + Condition (Flag 'mainFeature' EQUALS 'target') evaluates to false. + ) + THEN % options => no match + Returning 'Chicken'. diff --git a/test/data/evaluationlog/segment.json b/test/data/evaluationlog/segment.json new file mode 100644 index 0000000..41744c2 --- /dev/null +++ b/test/data/evaluationlog/segment.json @@ -0,0 +1,41 @@ +{ + "configUrl": "https://app.configcat.com/08d5a03c-feb7-af1e-a1fa-40b3329f8bed/08d9f207-6883-43e5-868c-cbf677af3fe6/244cf8b0-f604-11e8-b543-f23c917f9d8d", + "sdkKey": "PKDVCLf-Hq-h-kCzMp-L7Q/LcYz135LE0qbcacz2mgXnA", + "tests": [ + { + "key": "featureWithSegmentTargeting", + "defaultValue": false, + "returnValue": false, + "expectedLog": "segment_no_user.txt" + }, + { + "key": "featureWithNegatedSegmentTargetingCleartext", + "defaultValue": false, + "user": { + "Identifier": "12345" + }, + "returnValue": false, + "expectedLog": "segment_no_targeted_attribute.txt" + }, + { + "key": "featureWithSegmentTargeting", + "defaultValue": false, + "user": { + "Identifier": "12345", + "Email": "jane@example.com" + }, + "returnValue": true, + "expectedLog": "segment_matching.txt" + }, + { + "key": "featureWithNegatedSegmentTargeting", + "defaultValue": false, + "user": { + "Identifier": "12345", + "Email": "jane@example.com" + }, + "returnValue": false, + "expectedLog": "segment_no_matching.txt" + } + ] +} diff --git a/test/data/evaluationlog/segment/segment_matching.txt b/test/data/evaluationlog/segment/segment_matching.txt new file mode 100644 index 0000000..9065aae --- /dev/null +++ b/test/data/evaluationlog/segment/segment_matching.txt @@ -0,0 +1,11 @@ +INFO [5000] Evaluating 'featureWithSegmentTargeting' for User '{"Identifier":"12345","Email":"jane@example.com"}' + Evaluating targeting rules and applying the first match if any: + - IF User IS IN SEGMENT 'Beta users' + ( + Evaluating segment 'Beta users': + - IF User.Email IS ONE OF [<2 hashed values>] => true + Segment evaluation result: User IS IN SEGMENT. + Condition (User IS IN SEGMENT 'Beta users') evaluates to true. + ) + THEN 'true' => MATCH, applying rule + Returning 'true'. diff --git a/test/data/evaluationlog/segment/segment_no_matching.txt b/test/data/evaluationlog/segment/segment_no_matching.txt new file mode 100644 index 0000000..0d04d83 --- /dev/null +++ b/test/data/evaluationlog/segment/segment_no_matching.txt @@ -0,0 +1,11 @@ +INFO [5000] Evaluating 'featureWithNegatedSegmentTargeting' for User '{"Identifier":"12345","Email":"jane@example.com"}' + Evaluating targeting rules and applying the first match if any: + - IF User IS NOT IN SEGMENT 'Beta users' + ( + Evaluating segment 'Beta users': + - IF User.Email IS ONE OF [<2 hashed values>] => true + Segment evaluation result: User IS IN SEGMENT. + Condition (User IS NOT IN SEGMENT 'Beta users') evaluates to false. + ) + THEN 'true' => no match + Returning 'false'. diff --git a/test/data/evaluationlog/segment/segment_no_targeted_attribute.txt b/test/data/evaluationlog/segment/segment_no_targeted_attribute.txt new file mode 100644 index 0000000..6c7cf7e --- /dev/null +++ b/test/data/evaluationlog/segment/segment_no_targeted_attribute.txt @@ -0,0 +1,13 @@ +WARNING [3003] Cannot evaluate condition (User.Email IS ONE OF ['jane@example.com', 'john@example.com']) for setting 'featureWithNegatedSegmentTargetingCleartext' (the User.Email attribute is missing). You should set the User.Email attribute in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ +INFO [5000] Evaluating 'featureWithNegatedSegmentTargetingCleartext' for User '{"Identifier":"12345"}' + Evaluating targeting rules and applying the first match if any: + - IF User IS NOT IN SEGMENT 'Beta users (cleartext)' + ( + Evaluating segment 'Beta users (cleartext)': + - IF User.Email IS ONE OF ['jane@example.com', 'john@example.com'] => false, skipping the remaining AND conditions + Segment evaluation result: cannot evaluate, the User.Email attribute is missing. + Condition (User IS NOT IN SEGMENT 'Beta users (cleartext)') failed to evaluate. + ) + THEN 'true' => cannot evaluate, the User.Email attribute is missing + The current targeting rule is ignored and the evaluation continues with the next rule. + Returning 'false'. diff --git a/test/data/evaluationlog/segment/segment_no_user.txt b/test/data/evaluationlog/segment/segment_no_user.txt new file mode 100644 index 0000000..ad626fd --- /dev/null +++ b/test/data/evaluationlog/segment/segment_no_user.txt @@ -0,0 +1,6 @@ +WARNING [3001] Cannot evaluate targeting rules and % options for setting 'featureWithSegmentTargeting' (User Object is missing). You should pass a User Object to the evaluation methods like `getValueAsync()` in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ +INFO [5000] Evaluating 'featureWithSegmentTargeting' + Evaluating targeting rules and applying the first match if any: + - IF User IS IN SEGMENT 'Beta users' THEN 'true' => cannot evaluate, User Object is missing + The current targeting rule is ignored and the evaluation continues with the next rule. + Returning 'false'. diff --git a/test/data/evaluationlog/semver_validation.json b/test/data/evaluationlog/semver_validation.json new file mode 100644 index 0000000..3a14fc6 --- /dev/null +++ b/test/data/evaluationlog/semver_validation.json @@ -0,0 +1,26 @@ +{ + "configUrl": "https://app.configcat.com/08d5a03c-feb7-af1e-a1fa-40b3329f8bed/08d745f1-f315-7daf-d163-5541d3786e6f/244cf8b0-f604-11e8-b543-f23c917f9d8d", + "sdkKey": "PKDVCLf-Hq-h-kCzMp-L7Q/BAr3KgLTP0ObzKnBTo5nhA", + "tests": [ + { + "key": "isNotOneOf", + "defaultValue": "default", + "returnValue": "Default", + "expectedLog": "semver_error.txt", + "user": { + "Identifier": "12345", + "Custom1": "wrong_semver" + } + }, + { + "key": "relations", + "defaultValue": "default", + "returnValue": "Default", + "expectedLog": "semver_relations_error.txt", + "user": { + "Identifier": "12345", + "Custom1": "wrong_semver" + } + } + ] +} diff --git a/test/data/evaluationlog/semver_validation/semver_error.txt b/test/data/evaluationlog/semver_validation/semver_error.txt new file mode 100644 index 0000000..e14cc95 --- /dev/null +++ b/test/data/evaluationlog/semver_validation/semver_error.txt @@ -0,0 +1,9 @@ +WARNING [3004] Cannot evaluate condition (User.Custom1 IS NOT ONE OF ['1.0.0', '1.0.1', '2.0.0', '2.0.1', '2.0.2', '']) for setting 'isNotOneOf' ('wrong_semver' is not a valid semantic version). Please check the User.Custom1 attribute and make sure that its value corresponds to the comparison operator. +WARNING [3004] Cannot evaluate condition (User.Custom1 IS NOT ONE OF ['1.0.0', '3.0.1']) for setting 'isNotOneOf' ('wrong_semver' is not a valid semantic version). Please check the User.Custom1 attribute and make sure that its value corresponds to the comparison operator. +INFO [5000] Evaluating 'isNotOneOf' for User '{"Identifier":"12345","Custom1":"wrong_semver"}' + Evaluating targeting rules and applying the first match if any: + - IF User.Custom1 IS NOT ONE OF ['1.0.0', '1.0.1', '2.0.0', '2.0.1', '2.0.2', ''] THEN 'Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, )' => cannot evaluate, the User.Custom1 attribute is invalid ('wrong_semver' is not a valid semantic version) + The current targeting rule is ignored and the evaluation continues with the next rule. + - IF User.Custom1 IS NOT ONE OF ['1.0.0', '3.0.1'] THEN 'Is not one of (1.0.0, 3.0.1)' => cannot evaluate, the User.Custom1 attribute is invalid ('wrong_semver' is not a valid semantic version) + The current targeting rule is ignored and the evaluation continues with the next rule. + Returning 'Default'. diff --git a/test/data/evaluationlog/semver_validation/semver_relations_error.txt b/test/data/evaluationlog/semver_validation/semver_relations_error.txt new file mode 100644 index 0000000..8198c85 --- /dev/null +++ b/test/data/evaluationlog/semver_validation/semver_relations_error.txt @@ -0,0 +1,18 @@ +WARNING [3004] Cannot evaluate condition (User.Custom1 < '1.0.0,') for setting 'relations' ('wrong_semver' is not a valid semantic version). Please check the User.Custom1 attribute and make sure that its value corresponds to the comparison operator. +WARNING [3004] Cannot evaluate condition (User.Custom1 < '1.0.0') for setting 'relations' ('wrong_semver' is not a valid semantic version). Please check the User.Custom1 attribute and make sure that its value corresponds to the comparison operator. +WARNING [3004] Cannot evaluate condition (User.Custom1 <= '1.0.0') for setting 'relations' ('wrong_semver' is not a valid semantic version). Please check the User.Custom1 attribute and make sure that its value corresponds to the comparison operator. +WARNING [3004] Cannot evaluate condition (User.Custom1 > '2.0.0') for setting 'relations' ('wrong_semver' is not a valid semantic version). Please check the User.Custom1 attribute and make sure that its value corresponds to the comparison operator. +WARNING [3004] Cannot evaluate condition (User.Custom1 >= '2.0.0') for setting 'relations' ('wrong_semver' is not a valid semantic version). Please check the User.Custom1 attribute and make sure that its value corresponds to the comparison operator. +INFO [5000] Evaluating 'relations' for User '{"Identifier":"12345","Custom1":"wrong_semver"}' + Evaluating targeting rules and applying the first match if any: + - IF User.Custom1 < '1.0.0,' THEN '<1.0.0,' => cannot evaluate, the User.Custom1 attribute is invalid ('wrong_semver' is not a valid semantic version) + The current targeting rule is ignored and the evaluation continues with the next rule. + - IF User.Custom1 < '1.0.0' THEN '< 1.0.0' => cannot evaluate, the User.Custom1 attribute is invalid ('wrong_semver' is not a valid semantic version) + The current targeting rule is ignored and the evaluation continues with the next rule. + - IF User.Custom1 <= '1.0.0' THEN '<=1.0.0' => cannot evaluate, the User.Custom1 attribute is invalid ('wrong_semver' is not a valid semantic version) + The current targeting rule is ignored and the evaluation continues with the next rule. + - IF User.Custom1 > '2.0.0' THEN '>2.0.0' => cannot evaluate, the User.Custom1 attribute is invalid ('wrong_semver' is not a valid semantic version) + The current targeting rule is ignored and the evaluation continues with the next rule. + - IF User.Custom1 >= '2.0.0' THEN '>=2.0.0' => cannot evaluate, the User.Custom1 attribute is invalid ('wrong_semver' is not a valid semantic version) + The current targeting rule is ignored and the evaluation continues with the next rule. + Returning 'Default'. diff --git a/test/data/evaluationlog/simple_value.json b/test/data/evaluationlog/simple_value.json new file mode 100644 index 0000000..070d6f5 --- /dev/null +++ b/test/data/evaluationlog/simple_value.json @@ -0,0 +1,37 @@ +{ + "configUrl": "https://app.configcat.com/08d5a03c-feb7-af1e-a1fa-40b3329f8bed/08d62463-86ec-8fde-f5b5-1c5c426fc830/244cf8b0-f604-11e8-b543-f23c917f9d8d", + "sdkKey": "PKDVCLf-Hq-h-kCzMp-L7Q/psuH7BGHoUmdONrzzUOY7A", + "tests": [ + { + "key": "boolDefaultFalse", + "defaultValue": true, + "returnValue": false, + "expectedLog": "off_flag.txt" + }, + { + "key": "boolDefaultTrue", + "defaultValue": false, + "returnValue": true, + "expectedLog": "on_flag.txt" + }, + { + "key": "stringDefaultCat", + "defaultValue": "Default", + "returnValue": "Cat", + "expectedLog": "text_setting.txt" + }, + { + "key": "integerDefaultOne", + "defaultValue": 0, + "returnValue": 1, + "expectedLog": "int_setting.txt" + }, + { + "testName": "double_setting", + "key": "doubleDefaultPi", + "defaultValue": 0.0, + "returnValue": 3.1415, + "expectedLog": "double_setting.txt" + } + ] +} diff --git a/test/data/evaluationlog/simple_value/double_setting.txt b/test/data/evaluationlog/simple_value/double_setting.txt new file mode 100644 index 0000000..4a632f7 --- /dev/null +++ b/test/data/evaluationlog/simple_value/double_setting.txt @@ -0,0 +1,2 @@ +INFO [5000] Evaluating 'doubleDefaultPi' + Returning '3.1415'. diff --git a/test/data/evaluationlog/simple_value/int_setting.txt b/test/data/evaluationlog/simple_value/int_setting.txt new file mode 100644 index 0000000..1361843 --- /dev/null +++ b/test/data/evaluationlog/simple_value/int_setting.txt @@ -0,0 +1,2 @@ +INFO [5000] Evaluating 'integerDefaultOne' + Returning '1'. diff --git a/test/data/evaluationlog/simple_value/off_flag.txt b/test/data/evaluationlog/simple_value/off_flag.txt new file mode 100644 index 0000000..4580685 --- /dev/null +++ b/test/data/evaluationlog/simple_value/off_flag.txt @@ -0,0 +1,2 @@ +INFO [5000] Evaluating 'boolDefaultFalse' + Returning 'false'. diff --git a/test/data/evaluationlog/simple_value/on_flag.txt b/test/data/evaluationlog/simple_value/on_flag.txt new file mode 100644 index 0000000..274c990 --- /dev/null +++ b/test/data/evaluationlog/simple_value/on_flag.txt @@ -0,0 +1,2 @@ +INFO [5000] Evaluating 'boolDefaultTrue' + Returning 'true'. diff --git a/test/data/evaluationlog/simple_value/text_setting.txt b/test/data/evaluationlog/simple_value/text_setting.txt new file mode 100644 index 0000000..831d7c6 --- /dev/null +++ b/test/data/evaluationlog/simple_value/text_setting.txt @@ -0,0 +1,2 @@ +INFO [5000] Evaluating 'stringDefaultCat' + Returning 'Cat'. diff --git a/test/data/sample_number_v5.json b/test/data/sample_number_v5.json deleted file mode 100644 index 89d8355..0000000 --- a/test/data/sample_number_v5.json +++ /dev/null @@ -1 +0,0 @@ -{"f":{"numberWithPercentage":{"v":"Default","t":1,"i":"642bbb26","p":[{"o":0,"v":"80%","p":80,"i":"ad5f05a7"},{"o":1,"v":"20%","p":20,"i":"786b696f"}],"r":[{"o":0,"a":"Custom1","t":10,"c":"sajt","v":"=sajt","i":"216987c8"},{"o":1,"a":"Custom1","t":12,"c":"2.1","v":"<2.1","i":"a900bc23"},{"o":2,"a":"Custom1","t":13,"c":"2,1","v":"<=2,1","i":"2c85f73d"},{"o":3,"a":"Custom1","t":10,"c":"3.5","v":"=3.5","i":"ae86baf5"},{"o":4,"a":"Custom1","t":14,"c":"5","v":">5","i":"c6924001"},{"o":5,"a":"Custom1","t":15,"c":"5","v":">=5","i":"8090543a"},{"o":6,"a":"Custom1","t":11,"c":"4.2","v":"<>4.2","i":"2691fade"}]},"number":{"v":"Default","t":1,"i":"5ced27a9","p":[],"r":[{"o":0,"a":"Custom1","t":11,"c":"5","v":"<>5","i":"a41938c5"}]}}} \ No newline at end of file diff --git a/test/data/sample_semantic_2_v5.json b/test/data/sample_semantic_2_v5.json deleted file mode 100644 index e5feb98..0000000 --- a/test/data/sample_semantic_2_v5.json +++ /dev/null @@ -1 +0,0 @@ -{"f":{"precedenceTests":{"v":"DEFAULT-FROM-CC-APP","t":1,"i":"53940653","p":[],"r":[{"o":0,"a":"AppVersion","t":6,"c":"1.9.1-2","v":"< 1.9.1-2","i":"92a04969"},{"o":1,"a":"AppVersion","t":6,"c":"1.9.1-10","v":"< 1.9.1-10","i":"c651eba2"},{"o":2,"a":"AppVersion","t":6,"c":"1.9.1-10a","v":"< 1.9.1-10a","i":"237dedc5"},{"o":3,"a":"AppVersion","t":6,"c":"1.9.1-1a","v":"< 1.9.1-1a","i":"154a319b"},{"o":4,"a":"AppVersion","t":6,"c":"1.9.1-alpha","v":"< 1.9.1-alpha","i":"33f59c5e"},{"o":5,"a":"AppVersion","t":6,"c":"1.9.99-alpha","v":"< 1.9.99-alpha","i":"9b6c24f1"},{"o":6,"a":"AppVersion","t":4,"c":"1.9.99-alpha","v":"= 1.9.99-alpha","i":"c08a99de"},{"o":7,"a":"AppVersion","t":6,"c":"1.9.99-beta","v":"< 1.9.99-beta","i":"4c9d7eb1"},{"o":8,"a":"AppVersion","t":6,"c":"1.9.99-rc","v":"< 1.9.99-rc","i":"e5aa7655"},{"o":9,"a":"AppVersion","t":6,"c":"1.9.99-rc.1","v":"< 1.9.99-rc.1","i":"c9075e5b"},{"o":10,"a":"AppVersion","t":6,"c":"1.9.99-rc.2","v":"< 1.9.99-rc.2","i":"97465d24"},{"o":11,"a":"AppVersion","t":6,"c":"1.9.99-rc.20","v":"< 1.9.99-rc.20","i":"32d20254"},{"o":12,"a":"AppVersion","t":6,"c":"1.9.99-rc.20a","v":"< 1.9.99-rc.20a","i":"c4843bfb"},{"o":13,"a":"AppVersion","t":6,"c":"1.9.99-rc.2a","v":"< 1.9.99-rc.2a","i":"11b96c5a"},{"o":14,"a":"AppVersion","t":6,"c":"1.9.99","v":"< 1.9.99","i":"dc5a0ed1"},{"o":15,"a":"AppVersion","t":6,"c":"1.9.100","v":"< 1.9.100","i":"8ce0bff8"},{"o":16,"a":"AppVersion","t":6,"c":"1.10.0-alpha","v":"< 1.10.0-alpha","i":"9ff0cadc"},{"o":17,"a":"AppVersion","t":7,"c":"1.10.0-alpha","v":"<= 1.10.0-alpha","i":"7a24a0f6"},{"o":18,"a":"AppVersion","t":6,"c":"1.10.0","v":"< 1.10.0","i":"03a85e10"},{"o":19,"a":"AppVersion","t":7,"c":"1.10.0","v":"<= 1.10.0","i":"b37d5427"},{"o":20,"a":"AppVersion","t":7,"c":"1.10.1","v":"<= 1.10.1","i":"b402f112"},{"o":21,"a":"AppVersion","t":7,"c":"1.10.3","v":"<= 1.10.3","i":"da563c51"},{"o":22,"a":"AppVersion","t":6,"c":"2.0.0","v":"< 2.0.0","i":"c64645a1"},{"o":23,"a":"AppVersion","t":4,"c":"2.0.0","v":"= 2.0.0","i":"b0008e97"},{"o":24,"a":"AppVersion","t":4,"c":"3.0.0+build3","v":"= 3.0.0+build3","i":"67ceff4e"},{"o":25,"a":"AppVersion","t":4,"c":"4.0.0+001","v":"= 4.0.0+001","i":"da6dd7ab"},{"o":26,"a":"AppVersion","t":4,"c":"5.0.0+20130313144700","v":"= 5.0.0+20130313144700","i":"673b3fd5"},{"o":27,"a":"AppVersion","t":4,"c":"6.0.0+exp.sha.5114f85","v":"= 6.0.0+exp.sha.5114f85","i":"e3bcafe6"},{"o":28,"a":"AppVersion","t":4,"c":"7.0.0-patch","v":"= 7.0.0-patch","i":"04e2949b"},{"o":29,"a":"AppVersion","t":4,"c":"8.0.0-patch+anothermetadata","v":"= 8.0.0-patch+anothermetadata","i":"505e8efa"},{"o":30,"a":"AppVersion","t":4,"c":"9.0.0-patch+metadata","v":"= 9.0.0-patch+metadata","i":"ca4c9dcc"},{"o":31,"a":"AppVersion","t":8,"c":"103.0.0","v":"> 103.0.0","i":"9428e733"},{"o":32,"a":"AppVersion","t":9,"c":"103.0.0","v":">= 103.0.0","i":"c448abb8"},{"o":33,"a":"AppVersion","t":9,"c":"101.0.0","v":">= 101.0.0","i":"9980c03a"},{"o":34,"a":"AppVersion","t":8,"c":"90.103.0","v":"> 90.103.0","i":"04259f0b"},{"o":35,"a":"AppVersion","t":9,"c":"90.103.0","v":">= 90.103.0","i":"4817782c"},{"o":36,"a":"AppVersion","t":9,"c":"90.101.0","v":">= 90.101.0","i":"2e9be278"},{"o":37,"a":"AppVersion","t":8,"c":"80.0.103","v":"> 80.0.103","i":"d7058d3e"},{"o":38,"a":"AppVersion","t":9,"c":"80.0.103","v":">= 80.0.103","i":"0da87e6b"},{"o":39,"a":"AppVersion","t":9,"c":"80.0.101","v":">= 80.0.101","i":"8e71aa24"},{"o":40,"a":"AppVersion","t":9,"c":"73.0.0-beta.2","v":">= 73.0.0-beta.2","i":"26a443e3"},{"o":41,"a":"AppVersion","t":8,"c":"72.0.0-beta.2","v":"> 72.0.0-beta.2","i":"0705710a"},{"o":42,"a":"AppVersion","t":8,"c":"72.0.0-beta.1","v":"> 72.0.0-beta.1","i":"7d6cf793"},{"o":43,"a":"AppVersion","t":8,"c":"72.0.0-beta","v":"> 72.0.0-beta","i":"f9ef6e83"},{"o":44,"a":"AppVersion","t":8,"c":"72.0.0-alpha","v":"> 72.0.0-alpha","i":"cf17c939"},{"o":45,"a":"AppVersion","t":8,"c":"72.0.0-1a","v":"> 72.0.0-1a","i":"650640fd"},{"o":46,"a":"AppVersion","t":8,"c":"72.0.0-10a","v":"> 72.0.0-10a","i":"508dd0b2"},{"o":47,"a":"AppVersion","t":8,"c":"72.0.0-2","v":"> 72.0.0-2","i":"142e6d61"},{"o":48,"a":"AppVersion","t":8,"c":"72.0.0-1","v":"> 72.0.0-1","i":"d969006a"},{"o":49,"a":"AppVersion","t":9,"c":"71.0.0+anothermetadata","v":">= 71.0.0+anothermetadata","i":"6f74dc87"},{"o":50,"a":"AppVersion","t":9,"c":"71.0.0-patch3+anothermetadata","v":">= 71.0.0-patch3+anothermetadata","i":"8061734b"},{"o":51,"a":"AppVersion","t":9,"c":"71.0.0-patch2","v":">= 71.0.0-patch2","i":"0615c726"},{"o":52,"a":"AppVersion","t":9,"c":"71.0.0-patch1+metadata","v":">= 71.0.0-patch1+metadata","i":"910b79b5"},{"o":53,"a":"AppVersion","t":9,"c":"60.73.0-beta.2","v":">= 60.73.0-beta.2","i":"32e2a4ea"},{"o":54,"a":"AppVersion","t":8,"c":"60.72.0-beta.2","v":"> 60.72.0-beta.2","i":"9017539e"},{"o":55,"a":"AppVersion","t":8,"c":"60.72.0-beta.1","v":"> 60.72.0-beta.1","i":"74de4704"},{"o":56,"a":"AppVersion","t":8,"c":"60.72.0-beta","v":"> 60.72.0-beta","i":"b61af046"},{"o":57,"a":"AppVersion","t":8,"c":"60.72.0-alpha","v":"> 60.72.0-alpha","i":"419eb18d"},{"o":58,"a":"AppVersion","t":8,"c":"60.72.0-1a","v":"> 60.72.0-1a","i":"7574c707"},{"o":59,"a":"AppVersion","t":8,"c":"60.72.0-10a","v":"> 60.72.0-10a","i":"5b3949e6"},{"o":60,"a":"AppVersion","t":8,"c":"60.72.0-2","v":"> 60.72.0-2","i":"9ff17692"},{"o":61,"a":"AppVersion","t":8,"c":"60.72.0-1","v":"> 60.72.0-1","i":"3027451d"},{"o":62,"a":"AppVersion","t":9,"c":"60.71.0+anothermetadata","v":">= 60.71.0+anothermetadata","i":"613d3642"},{"o":63,"a":"AppVersion","t":9,"c":"60.71.0-patch3+anothermetadata","v":">= 60.71.0-patch3+anothermetadata","i":"e45ffb06"},{"o":64,"a":"AppVersion","t":9,"c":"60.71.0-patch2","v":">= 60.71.0-patch2","i":"db50de0a"},{"o":65,"a":"AppVersion","t":9,"c":"60.71.0-patch1+metadata","v":">= 60.71.0-patch1+metadata","i":"5f9acaf7"},{"o":66,"a":"AppVersion","t":9,"c":"50.60.73-beta.2","v":">= 50.60.73-beta.2","i":"701ac6b2"},{"o":67,"a":"AppVersion","t":8,"c":"50.60.72-beta.2","v":"> 50.60.72-beta.2","i":"da09daf8"},{"o":68,"a":"AppVersion","t":8,"c":"50.60.72-beta.1","v":"> 50.60.72-beta.1","i":"8f7e54d5"},{"o":69,"a":"AppVersion","t":8,"c":"50.60.72-beta","v":"> 50.60.72-beta","i":"93e245a5"},{"o":70,"a":"AppVersion","t":8,"c":"50.60.72-alpha","v":"> 50.60.72-alpha","i":"356c8279"},{"o":71,"a":"AppVersion","t":8,"c":"50.60.72-1a","v":"> 50.60.72-1a","i":"6131df16"},{"o":72,"a":"AppVersion","t":8,"c":"50.60.72-10a","v":"> 50.60.72-10a","i":"3f1a3aa4"},{"o":73,"a":"AppVersion","t":8,"c":"50.60.72-2","v":"> 50.60.72-2","i":"7534cc57"},{"o":74,"a":"AppVersion","t":8,"c":"50.60.72-1","v":"> 50.60.72-1","i":"24d6f0ab"},{"o":75,"a":"AppVersion","t":9,"c":"50.60.71+anothermetadata","v":">= 50.60.71+anothermetadata","i":"fdd36d82"},{"o":76,"a":"AppVersion","t":9,"c":"50.60.71-patch3+anothermetadata","v":">= 50.60.71-patch3+anothermetadata","i":"709780e6"},{"o":77,"a":"AppVersion","t":9,"c":"50.60.71-patch2","v":">= 50.60.71-patch2","i":"7649322d"},{"o":78,"a":"AppVersion","t":9,"c":"50.60.71-patch1+metadata","v":">= 50.60.71-patch1+metadata","i":"25d5c70d"},{"o":79,"a":"AppVersion","t":9,"c":"40.0.0-patch","v":">= 40.0.0-patch","i":"271370ff"},{"o":80,"a":"AppVersion","t":9,"c":"30.0.0-alpha","v":">= 30.0.0-alpha","i":"af29c39d"}]}}} \ No newline at end of file diff --git a/test/data/sample_semantic_v5.json b/test/data/sample_semantic_v5.json deleted file mode 100644 index 89bbbb4..0000000 --- a/test/data/sample_semantic_v5.json +++ /dev/null @@ -1 +0,0 @@ -{"f":{"isOneOf":{"v":"Default","t":1,"i":"c4ec4d53","p":[],"r":[{"o":0,"a":"Custom1","t":4,"c":"1.0.0, 2","v":"Is one of (1.0.0, 2)","i":"1e934047"},{"o":1,"a":"Custom1","t":4,"c":"1.0.0","v":"Is one of (1.0.0)","i":"44342254"},{"o":2,"a":"Custom1","t":4,"c":" , 2.0.1, 2.0.2, ","v":"Is one of ( , 2.0.1, 2.0.2, )","i":"90e3ef46"},{"o":3,"a":"Custom1","t":4,"c":"3......","v":"Is one of (3......)","i":"59523971"},{"o":4,"a":"Custom1","t":4,"c":"3....","v":"Is one of (3...)","i":"2de217a1"},{"o":5,"a":"Custom1","t":4,"c":"3..0","v":"Is one of (3..0)","i":"bf943c79"},{"o":6,"a":"Custom1","t":4,"c":"3.0","v":"Is one of (3.0)","i":"3a6a8077"},{"o":7,"a":"Custom1","t":4,"c":"3.0.","v":"Is one of (3.0.)","i":"44f25fed"},{"o":8,"a":"Custom1","t":4,"c":"3.0.0","v":"Is one of (3.0.0)","i":"e77f5306"}]},"isOneOfWithPercentage":{"v":"Default","t":1,"i":"a94ff896","p":[{"o":0,"v":"20%","p":20,"i":"e25dba31"},{"o":1,"v":"80%","p":80,"i":"8c70c181"}],"r":[{"o":0,"a":"Custom1","t":4,"c":"1.0.0","v":"is one of (1.0.0)","i":"0ac4afc1"}]},"isNotOneOf":{"v":"Default","t":1,"i":"f79b763d","p":[],"r":[{"o":0,"a":"Custom1","t":5,"c":"1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, ","v":"Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, )","i":"a8d5f278"},{"o":1,"a":"Custom1","t":5,"c":"1.0.0, 3.0.1","v":"Is not one of (1.0.0, 3.0.1)","i":"54ac757f"}]},"isNotOneOfWithPercentage":{"v":"Default","t":1,"i":"b9614bad","p":[{"o":0,"v":"20%","p":20,"i":"68f652f0"},{"o":1,"v":"80%","p":80,"i":"b8d926e0"}],"r":[{"o":0,"a":"Custom1","t":5,"c":"1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, ","v":"Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, )","i":"9bf9e66f"},{"o":1,"a":"Custom1","t":5,"c":"1.0.0, 3.0.1","v":"Is not one of (1.0.0, 3.0.1)","i":"bfc1a544"}]},"lessThanWithPercentage":{"v":"Default","t":1,"i":"0081c525","p":[{"o":0,"v":"20%","p":20,"i":"3b1fde2a"},{"o":1,"v":"80%","p":80,"i":"42e92759"}],"r":[{"o":0,"a":"Custom1","t":6,"c":" 1.0.0 ","v":"< 1.0.0","i":"0c27d053"}]},"relations":{"v":"Default","t":1,"i":"c6155773","p":[],"r":[{"o":0,"a":"Custom1","t":6,"c":"1.0.0,","v":"<1.0.0,","i":"21b31b61"},{"o":1,"a":"Custom1","t":6,"c":"1.0.0","v":"< 1.0.0","i":"db3ddb7d"},{"o":2,"a":"Custom1","t":7,"c":"1.0.0","v":"<=1.0.0","i":"aa2c7493"},{"o":3,"a":"Custom1","t":8,"c":"2.0.0","v":">2.0.0","i":"5e47a1ea"},{"o":4,"a":"Custom1","t":9,"c":"2.0.0","v":">=2.0.0","i":"99482756"}]}}} \ No newline at end of file diff --git a/test/data/sample_sensitive_v5.json b/test/data/sample_sensitive_v5.json deleted file mode 100644 index fee0c3e..0000000 --- a/test/data/sample_sensitive_v5.json +++ /dev/null @@ -1 +0,0 @@ -{"f":{"isNotOneOfSensitive":{"v":"ToAll","t":1,"i":"97bd663d","p":[],"r":[{"o":0,"a":"Identifier","t":17,"c":"68d93aa74a0aa1664f65ad6c0515f24769b15c84,8409e4e5d27a1465165012b03b2606f0e5b08250","v":"Kigyo","i":"4e4356b4"},{"o":1,"a":"Email","t":17,"c":"2e1c7263a639cf2719f585dfa0be3953c13dd36f,532df0aa59af3cf1d3d876316225e987e63bf8a6","v":"Angolna","i":"d75ea4a4"},{"o":2,"a":"Country","t":17,"c":"707fe00aa123eb0be5010f1d3065c2b6d7934ca4,ff95dc990b9440c8ff18edd8592bf43915e510b9,e2ff49d5209adefb1d572ca4ca42701ac5b167ad","v":"Ireland","i":"e8826a82"}]},"isOneOfSensitive":{"v":"ToAll","t":1,"i":"71a78b2a","p":[],"r":[{"o":0,"a":"Email","t":16,"c":"532df0aa59af3cf1d3d876316225e987e63bf8a6","v":"Macska","i":"b1dc4d99"},{"o":1,"a":"Identifier","t":16,"c":"cc1a672b80f85ec48aa620a588864285e2b04a45,68d93aa74a0aa1664f65ad6c0515f24769b15c84","v":"Allat","i":"fb7be8fd"},{"o":2,"a":"Country","t":16,"c":"707fe00aa123eb0be5010f1d3065c2b6d7934ca4,ff95dc990b9440c8ff18edd8592bf43915e510b9,e2ff49d5209adefb1d572ca4ca42701ac5b167ad","v":"Britt","i":"f1b9ed25"}]}}} \ No newline at end of file diff --git a/test/data/sample_v5.json b/test/data/sample_v5.json deleted file mode 100644 index 673dbc2..0000000 --- a/test/data/sample_v5.json +++ /dev/null @@ -1 +0,0 @@ -{"p":{"u":"https://cdn-global.configcat.com","r":0},"f":{"stringDefaultCat":{"v":"Cat","t":1,"i":"7a0be518","p":[],"r":[]},"stringIsInDogDefaultCat":{"v":"Cat","t":1,"i":"83372510","p":[],"r":[{"o":0,"a":"Email","t":0,"c":"a@configcat.com, b@configcat.com","v":"Dog","i":"5b64d9b4"},{"o":1,"a":"Custom1","t":0,"c":"admin","v":"Dog","i":"5b64d9b4"}]},"stringIsNotInDogDefaultCat":{"v":"Cat","t":1,"i":"2459598d","p":[],"r":[{"o":0,"a":"Email","t":1,"c":"a@configcat.com,b@configcat.com","v":"Dog","i":"6ada5ff2"}]},"stringContainsDogDefaultCat":{"v":"Cat","t":1,"i":"ce564c3a","p":[],"r":[{"o":0,"a":"Email","t":2,"c":"@configcat.com","v":"Dog","i":"d0cd8f06"},{"o":1,"a":"Email","t":2,"c":"+dog@example.com","v":"Dog","i":"d0cd8f06"}]},"stringNotContainsDogDefaultCat":{"v":"Cat","t":1,"i":"44ab483a","p":[],"r":[{"o":0,"a":"Email","t":3,"c":"@configcat.com","v":"Dog","i":"f7f8f43d"},{"o":1,"a":"Email","t":3,"c":"+demo@example.com","v":"Cat","i":"f7f8f43d"}]},"string25Cat25Dog25Falcon25Horse":{"v":"Chicken","t":1,"i":"2588a3e6","p":[{"o":0,"v":"Cat","p":25,"i":"d227b334"},{"o":1,"v":"Dog","p":25,"i":"622f5d07"},{"o":2,"v":"Falcon","p":25,"i":"0ff32bab"},{"o":3,"v":"Horse","p":25,"i":"6c597441"}],"r":[]},"string75Cat0Dog25Falcon0Horse":{"v":"Chicken","t":1,"i":"aa65b5ce","p":[{"o":0,"v":"Cat","p":75,"i":"93f5a1c0"},{"o":1,"v":"Dog","p":0,"i":"b8f49554"},{"o":2,"v":"Falcon","p":25,"i":"7beaf504"},{"o":3,"v":"Horse","p":0,"i":"30ee31af"}],"r":[]},"string25Cat25Dog25Falcon25HorseAdvancedRules":{"v":"Chicken","t":1,"i":"8250ef5a","p":[{"o":0,"v":"Cat","p":25,"i":"83461b47"},{"o":1,"v":"Dog","p":25,"i":"4f026fbc"},{"o":2,"v":"Falcon","p":25,"i":"392a4d59"},{"o":3,"v":"Horse","p":25,"i":"bb66b1f3"}],"r":[{"o":0,"a":"Country","t":0,"c":"Hungary, United Kingdom","v":"Dolphin","i":"3accb1d0"},{"o":1,"a":"Custom1","t":2,"c":"admi","v":"Lion","i":"e95ebf10"},{"o":2,"a":"Email","t":2,"c":"@configcat.com","v":"Kitten","i":"88243650"}]},"boolDefaultTrue":{"v":true,"i":"09513143","t":0,"p":[],"r":[]},"boolDefaultFalse":{"v":false,"i":"489a16d2","t":0,"p":[],"r":[]},"bool30TrueAdvancedRules":{"v":true,"i":"607147d5","t":0,"p":[{"o":0,"v":true,"p":30,"i":"607147d5"},{"o":1,"v":false,"p":70,"i":"385d9803"}],"r":[{"o":0,"a":"Email","t":0,"c":"a@configcat.com, b@configcat.com","v":false,"i":"385d9803"},{"o":1,"a":"Country","t":2,"c":"United","v":false,"i":"385d9803"}]},"integer25One25Two25Three25FourAdvancedRules":{"v":-1,"i":"ce3c4f5a","t":2,"p":[{"o":0,"v":1,"p":25,"i":"11634414"},{"o":1,"v":2,"p":25,"i":"5530655d"},{"o":2,"v":3,"p":25,"i":"2ad19a52"},{"o":3,"v":4,"p":25,"i":"41b30851"}],"r":[{"o":0,"a":"Email","t":2,"c":"@configcat.com","v":5,"i":"58136ba2"}]},"integerDefaultOne":{"v":1,"i":"faadbf54","t":2,"p":[],"r":[]},"doubleDefaultPi":{"v":3.1415,"i":"5af8acc7","t":3,"p":[],"r":[]},"double25Pi25E25Gr25Zero":{"v":-1,"i":"9503a1de","t":3,"p":[{"o":0,"v":3.1415,"p":25,"i":"6d75b4d3"},{"o":1,"v":2.7182,"p":25,"i":"183ee713"},{"o":2,"v":1.61803,"p":25,"i":"01eb6326"},{"o":3,"v":0,"p":25,"i":"64c434ff"}],"r":[{"o":0,"a":"Email","t":2,"c":"@configcat.com","v":5.561,"i":"3f7826de"}]},"keySampleText":{"v":"Cat","t":1,"i":"69ef126c","p":[{"o":0,"v":"Falcon","p":50,"i":"baff2362"},{"o":1,"v":"Horse","p":50,"i":"dab78ba5"}],"r":[{"o":0,"a":"Country","t":0,"c":"Hungary,Bahamas","v":"Dog","i":"9fa0e57e"},{"o":1,"a":"SubscriptionType","t":0,"c":"unlimited","v":"Lion","i":"2be6b03f"}]}}} \ No newline at end of file diff --git a/test/data/sample_variationid_v5.json b/test/data/sample_variationid_v5.json deleted file mode 100644 index 2b29969..0000000 --- a/test/data/sample_variationid_v5.json +++ /dev/null @@ -1 +0,0 @@ -{"f":{"boolean":{"v":false,"i":"a0e56eda","t":0,"p":[{"o":0,"v":true,"p":50,"i":"67787ae4"},{"o":1,"v":false,"p":50,"i":"a0e56eda"}],"r":[{"o":0,"a":"Email","t":2,"c":"@configcat.com","v":true,"i":"67787ae4"}]},"text":{"v":"c","t":1,"i":"3f05be89","p":[{"o":0,"v":"a","p":50,"i":"30ba32b9"},{"o":1,"v":"b","p":50,"i":"cf19e913"}],"r":[{"o":0,"a":"Email","t":2,"c":"@configcat.com","v":"true","i":"9bdc6a1f"},{"o":1,"a":"Email","t":2,"c":"@test.com","v":"false","i":"65310deb"}]},"whole":{"v":999999,"i":"cf2e9162","t":2,"p":[{"o":0,"v":0,"p":50,"i":"ec14f6a9"},{"o":1,"v":-1,"p":50,"i":"61a5a033"}],"r":[{"o":0,"a":"Email","t":2,"c":"@configcat.com","v":1,"i":"ab30533b"}]},"decimal":{"v":0.0,"i":"63612d39","t":3,"p":[{"o":0,"v":1.0,"p":50,"i":"d0dbc27f"},{"o":1,"v":2.0,"p":50,"i":"8155ad7b"}],"r":[{"o":0,"a":"Email","t":2,"c":"@configcat.com","v":-2147483647.2147484,"i":"8f9559cf"},{"o":1,"a":"Email","t":0,"c":"a@test.com","v":0.12345678912345678,"i":"d66c5781"},{"o":2,"a":"Email","t":0,"c":"b@test.com","v":0.12345678912,"i":"d66c5781"}]}}} \ No newline at end of file diff --git a/test/data/test_circulardependency_v6.json b/test/data/test_circulardependency_v6.json new file mode 100644 index 0000000..a8a9e17 --- /dev/null +++ b/test/data/test_circulardependency_v6.json @@ -0,0 +1,80 @@ +{ + "p": { + "u": "https://cdn-global.configcat.com", + "r": 0 + }, + "f": { + "key1": { + "t": 1, + "v": { "s": "key1-value" }, + "r": [ + { + "c": [ + { + "p": { + "f": "key1", + "c": 0, + "v": { "s": "key1-prereq" } + } + } + ], + "s": { "v": { "s": "key1-prereq" } } + } + ] + }, + "key2": { + "t": 1, + "v": { "s": "key2-value" }, + "r": [ + { + "c": [ + { + "p": { + "f": "key3", + "c": 0, + "v": { "s": "key3-prereq" } + } + } + ], + "s": { "v": { "s": "key2-prereq" } } + } + ] + }, + "key3": { + "t": 1, + "v": { "s": "key3-value" }, + "r": [ + { + "c": [ + { + "p": { + "f": "key2", + "c": 0, + "v": { "s": "key2-prereq" } + } + } + ], + "s": { "v": { "s": "key3-prereq" } } + } + ] + }, + "key4": { + "t": 1, + "v": { "s": "key4-value" }, + "r": [ + { + "c": [ + { + "p": { + "f": "key3", + "c": 0, + "v": { "s": "key3-prereq" } + } + } + ], + "s": { "v": { "s": "key4-prereq" } } + } + ] + } + } +} diff --git a/test/data/testmatrix.csv b/test/data/testmatrix.csv index 7f26e33..6de7454 100644 --- a/test/data/testmatrix.csv +++ b/test/data/testmatrix.csv @@ -1,5 +1,6 @@ Identifier;Email;Country;Custom1;bool30TrueAdvancedRules;boolDefaultFalse;boolDefaultTrue;double25Pi25E25Gr25Zero;doubleDefaultPi;integer25One25Two25Three25FourAdvancedRules;integerDefaultOne;string25Cat25Dog25Falcon25Horse;string25Cat25Dog25Falcon25HorseAdvancedRules;string75Cat0Dog25Falcon0Horse;stringContainsDogDefaultCat;stringDefaultCat;stringIsInDogDefaultCat;stringIsNotInDogDefaultCat;stringNotContainsDogDefaultCat -##null##;;;;True;False;True;-1.0;3.1415;-1;1;Chicken;Chicken;Chicken;Cat;Cat;Cat;Cat;Cat +##null##;;;;True;False;True;-1;3.1415;-1;1;Chicken;Chicken;Chicken;Cat;Cat;Cat;Cat;Cat +;;;;False;False;True;2.7182;3.1415;4;1;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Cat a@configcat.com;a@configcat.com;Hungary;admin;False;False;True;5.561;3.1415;5;1;Cat;Dolphin;Cat;Dog;Cat;Dog;Cat;Cat b@configcat.com;b@configcat.com;Hungary;;False;False;True;5.561;3.1415;5;1;Falcon;Dolphin;Cat;Dog;Cat;Dog;Cat;Cat c@configcat.com;c@configcat.com;United Kingdom;admin;False;False;True;5.561;3.1415;5;1;Dog;Dolphin;Falcon;Dog;Cat;Dog;Dog;Cat @@ -11,9 +12,9 @@ h@configcat.com;h@configcat.com;;;False;False;True;5.561;3.1415;5;1;Cat;Kitten;C i@configcat.com;i@configcat.com;;admin;True;False;True;5.561;3.1415;5;1;Cat;Lion;Falcon;Dog;Cat;Dog;Dog;Cat j@configcat.com;j@configcat.com;;;False;False;True;5.561;3.1415;5;1;Cat;Kitten;Falcon;Dog;Cat;Cat;Dog;Cat stern@msn.com;stern@msn.com;##null##;##null##;False;False;True;1.61803;3.1415;1;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Dog;Dog -sarahs@yahoo.com;sarahs@yahoo.com;##null##;##null##;True;False;True;0.0;3.1415;4;1;Horse;Falcon;Cat;Cat;Cat;Cat;Dog;Dog +sarahs@yahoo.com;sarahs@yahoo.com;##null##;##null##;True;False;True;0;3.1415;4;1;Horse;Falcon;Cat;Cat;Cat;Cat;Dog;Dog luebke@hotmail.com;luebke@hotmail.com;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Falcon;Cat;Cat;Cat;Cat;Cat;Dog;Dog -padme@icloud.com;padme@icloud.com;##null##;##null##;True;False;True;0.0;3.1415;3;1;Cat;Cat;Cat;Cat;Cat;Cat;Dog;Dog +padme@icloud.com;padme@icloud.com;##null##;##null##;True;False;True;0;3.1415;3;1;Cat;Cat;Cat;Cat;Cat;Cat;Dog;Dog claypool@aol.com;claypool@aol.com;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Dog;Dog bogjobber@verizon.net;bogjobber@verizon.net;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Dog;Horse;Falcon;Cat;Cat;Cat;Dog;Dog cliffordj@aol.com;cliffordj@aol.com;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Horse;Horse;Cat;Cat;Cat;Cat;Dog;Dog @@ -28,57 +29,57 @@ josem@icloud.com;josem@icloud.com;##null##;##null##;False;False;True;2.7182;3.14 hedwig@outlook.com;hedwig@outlook.com;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Horse;Horse;Falcon;Cat;Cat;Cat;Dog;Dog camenisch@yahoo.com;camenisch@yahoo.com;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Horse;Falcon;Cat;Cat;Cat;Cat;Dog;Dog ccohen@comcast.net;ccohen@comcast.net;##null##;##null##;True;False;True;3.1415;3.1415;4;1;Cat;Cat;Cat;Cat;Cat;Cat;Dog;Dog -techie@att.net;techie@att.net;##null##;##null##;False;False;True;0.0;3.1415;4;1;Horse;Falcon;Cat;Cat;Cat;Cat;Dog;Dog +techie@att.net;techie@att.net;##null##;##null##;False;False;True;0;3.1415;4;1;Horse;Falcon;Cat;Cat;Cat;Cat;Dog;Dog damian@gmail.com;damian@gmail.com;##null##;##null##;True;False;True;3.1415;3.1415;2;1;Horse;Horse;Cat;Cat;Cat;Cat;Dog;Dog -psharpe@comcast.net;psharpe@comcast.net;##null##;##null##;False;False;True;0.0;3.1415;2;1;Horse;Falcon;Cat;Cat;Cat;Cat;Dog;Dog +psharpe@comcast.net;psharpe@comcast.net;##null##;##null##;False;False;True;0;3.1415;2;1;Horse;Falcon;Cat;Cat;Cat;Cat;Dog;Dog ebassi@me.com;ebassi@me.com;##null##;##null##;True;False;True;3.1415;3.1415;3;1;Horse;Horse;Falcon;Cat;Cat;Cat;Dog;Dog curly@aol.com;curly@aol.com;##null##;##null##;True;False;True;3.1415;3.1415;1;1;Cat;Horse;Cat;Cat;Cat;Cat;Dog;Dog rddesign@optonline.net;rddesign@optonline.net;##null##;##null##;True;False;True;3.1415;3.1415;4;1;Falcon;Horse;Falcon;Cat;Cat;Cat;Dog;Dog boftx@gmail.com;boftx@gmail.com;##null##;##null##;False;False;True;2.7182;3.1415;3;1;Falcon;Horse;Falcon;Cat;Cat;Cat;Dog;Dog eegsa@yahoo.ca;eegsa@yahoo.ca;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Cat;Horse;Cat;Cat;Cat;Cat;Dog;Dog ganter@gmail.com;ganter@gmail.com;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Falcon;Cat;Falcon;Cat;Cat;Cat;Dog;Dog -mleary@att.net;mleary@att.net;##null##;##null##;False;False;True;0.0;3.1415;2;1;Falcon;Horse;Cat;Cat;Cat;Cat;Dog;Dog +mleary@att.net;mleary@att.net;##null##;##null##;False;False;True;0;3.1415;2;1;Falcon;Horse;Cat;Cat;Cat;Cat;Dog;Dog kassiesa@icloud.com;kassiesa@icloud.com;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Horse;Falcon;Cat;Cat;Cat;Cat;Dog;Dog -peterhoeg@outlook.com;peterhoeg@outlook.com;##null##;##null##;False;False;True;0.0;3.1415;4;1;Dog;Cat;Cat;Cat;Cat;Cat;Dog;Dog +peterhoeg@outlook.com;peterhoeg@outlook.com;##null##;##null##;False;False;True;0;3.1415;4;1;Dog;Cat;Cat;Cat;Cat;Cat;Dog;Dog mhanoh@yahoo.ca;mhanoh@yahoo.ca;##null##;##null##;True;False;True;2.7182;3.1415;4;1;Horse;Falcon;Cat;Cat;Cat;Cat;Dog;Dog -henkp@yahoo.com;henkp@yahoo.com;##null##;##null##;False;False;True;0.0;3.1415;1;1;Horse;Horse;Falcon;Cat;Cat;Cat;Dog;Dog -krueger@sbcglobal.net;krueger@sbcglobal.net;##null##;##null##;False;False;True;0.0;3.1415;4;1;Dog;Horse;Cat;Cat;Cat;Cat;Dog;Dog +henkp@yahoo.com;henkp@yahoo.com;##null##;##null##;False;False;True;0;3.1415;1;1;Horse;Horse;Falcon;Cat;Cat;Cat;Dog;Dog +krueger@sbcglobal.net;krueger@sbcglobal.net;##null##;##null##;False;False;True;0;3.1415;4;1;Dog;Horse;Cat;Cat;Cat;Cat;Dog;Dog barjam@yahoo.com;barjam@yahoo.com;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Horse;Horse;Falcon;Cat;Cat;Cat;Dog;Dog mirod@msn.com;mirod@msn.com;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Cat;Falcon;Falcon;Cat;Cat;Cat;Dog;Dog -marioph@yahoo.com;marioph@yahoo.com;##null##;##null##;False;False;True;0.0;3.1415;2;1;Cat;Dog;Cat;Cat;Cat;Cat;Dog;Dog +marioph@yahoo.com;marioph@yahoo.com;##null##;##null##;False;False;True;0;3.1415;2;1;Cat;Dog;Cat;Cat;Cat;Cat;Dog;Dog niknejad@optonline.net;niknejad@optonline.net;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Falcon;Cat;Cat;Cat;Cat;Cat;Dog;Dog bwcarty@sbcglobal.net;bwcarty@sbcglobal.net;##null##;##null##;True;False;True;3.1415;3.1415;4;1;Dog;Horse;Falcon;Cat;Cat;Cat;Dog;Dog mcast@aol.com;mcast@aol.com;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Falcon;Falcon;Falcon;Cat;Cat;Cat;Dog;Dog -portscan@msn.com;portscan@msn.com;##null##;##null##;False;False;True;0.0;3.1415;4;1;Falcon;Horse;Cat;Cat;Cat;Cat;Dog;Dog +portscan@msn.com;portscan@msn.com;##null##;##null##;False;False;True;0;3.1415;4;1;Falcon;Horse;Cat;Cat;Cat;Cat;Dog;Dog pereinar@yahoo.ca;pereinar@yahoo.ca;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Horse;Falcon;Cat;Cat;Cat;Cat;Dog;Dog floxy@verizon.net;floxy@verizon.net;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Falcon;Horse;Cat;Cat;Cat;Cat;Dog;Dog -mhassel@comcast.net;mhassel@comcast.net;##null##;##null##;False;False;True;0.0;3.1415;3;1;Cat;Dog;Cat;Cat;Cat;Cat;Dog;Dog +mhassel@comcast.net;mhassel@comcast.net;##null##;##null##;False;False;True;0;3.1415;3;1;Cat;Dog;Cat;Cat;Cat;Cat;Dog;Dog mgemmons@optonline.net;mgemmons@optonline.net;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Dog;Cat;Falcon;Cat;Cat;Cat;Dog;Dog -luvirini@mac.com;luvirini@mac.com;##null##;##null##;False;False;True;0.0;3.1415;4;1;Dog;Horse;Falcon;Cat;Cat;Cat;Dog;Dog +luvirini@mac.com;luvirini@mac.com;##null##;##null##;False;False;True;0;3.1415;4;1;Dog;Horse;Falcon;Cat;Cat;Cat;Dog;Dog gslondon@gmail.com;gslondon@gmail.com;##null##;##null##;True;False;True;3.1415;3.1415;2;1;Cat;Horse;Cat;Cat;Cat;Cat;Dog;Dog lamky@comcast.net;lamky@comcast.net;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Falcon;Cat;Falcon;Cat;Cat;Cat;Dog;Dog -lipeng@aol.com;lipeng@aol.com;##null##;##null##;False;False;True;0.0;3.1415;4;1;Cat;Horse;Cat;Cat;Cat;Cat;Dog;Dog +lipeng@aol.com;lipeng@aol.com;##null##;##null##;False;False;True;0;3.1415;4;1;Cat;Horse;Cat;Cat;Cat;Cat;Dog;Dog keiji@mac.com;keiji@mac.com;##null##;##null##;False;False;True;3.1415;3.1415;3;1;Dog;Dog;Falcon;Cat;Cat;Cat;Dog;Dog gumpish@verizon.net;gumpish@verizon.net;##null##;##null##;True;False;True;2.7182;3.1415;2;1;Horse;Dog;Cat;Cat;Cat;Cat;Dog;Dog tromey@hotmail.com;tromey@hotmail.com;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Falcon;Cat;Cat;Cat;Cat;Cat;Dog;Dog miyop@aol.com;miyop@aol.com;##null##;##null##;True;False;True;3.1415;3.1415;1;1;Cat;Falcon;Cat;Cat;Cat;Cat;Dog;Dog natepuri@me.com;natepuri@me.com;##null##;##null##;True;False;True;2.7182;3.1415;3;1;Horse;Falcon;Cat;Cat;Cat;Cat;Dog;Dog sbmrjbr@outlook.com;sbmrjbr@outlook.com;##null##;##null##;True;False;True;3.1415;3.1415;4;1;Horse;Dog;Falcon;Cat;Cat;Cat;Dog;Dog -hahiss@gmail.com;hahiss@gmail.com;##null##;##null##;False;False;True;0.0;3.1415;1;1;Horse;Horse;Cat;Cat;Cat;Cat;Dog;Dog -gmcgath@yahoo.ca;gmcgath@yahoo.ca;##null##;##null##;True;False;True;0.0;3.1415;1;1;Falcon;Dog;Cat;Cat;Cat;Cat;Dog;Dog -zavadsky@msn.com;zavadsky@msn.com;##null##;##null##;True;False;True;0.0;3.1415;3;1;Cat;Falcon;Falcon;Cat;Cat;Cat;Dog;Dog -munson@gmail.com;munson@gmail.com;##null##;##null##;False;False;True;0.0;3.1415;2;1;Falcon;Horse;Cat;Cat;Cat;Cat;Dog;Dog -jfriedl@yahoo.com;jfriedl@yahoo.com;##null##;##null##;False;False;True;0.0;3.1415;4;1;Horse;Falcon;Cat;Cat;Cat;Cat;Dog;Dog +hahiss@gmail.com;hahiss@gmail.com;##null##;##null##;False;False;True;0;3.1415;1;1;Horse;Horse;Cat;Cat;Cat;Cat;Dog;Dog +gmcgath@yahoo.ca;gmcgath@yahoo.ca;##null##;##null##;True;False;True;0;3.1415;1;1;Falcon;Dog;Cat;Cat;Cat;Cat;Dog;Dog +zavadsky@msn.com;zavadsky@msn.com;##null##;##null##;True;False;True;0;3.1415;3;1;Cat;Falcon;Falcon;Cat;Cat;Cat;Dog;Dog +munson@gmail.com;munson@gmail.com;##null##;##null##;False;False;True;0;3.1415;2;1;Falcon;Horse;Cat;Cat;Cat;Cat;Dog;Dog +jfriedl@yahoo.com;jfriedl@yahoo.com;##null##;##null##;False;False;True;0;3.1415;4;1;Horse;Falcon;Cat;Cat;Cat;Cat;Dog;Dog lushe@yahoo.ca;lushe@yahoo.ca;##null##;##null##;True;False;True;2.7182;3.1415;2;1;Cat;Cat;Falcon;Cat;Cat;Cat;Dog;Dog skythe@gmail.com;skythe@gmail.com;##null##;##null##;True;False;True;2.7182;3.1415;2;1;Horse;Horse;Falcon;Cat;Cat;Cat;Dog;Dog -lipeng@aol.com;lipeng@aol.com;##null##;##null##;False;False;True;0.0;3.1415;4;1;Cat;Horse;Cat;Cat;Cat;Cat;Dog;Dog +lipeng@aol.com;lipeng@aol.com;##null##;##null##;False;False;True;0;3.1415;4;1;Cat;Horse;Cat;Cat;Cat;Cat;Dog;Dog jigsaw@me.com;jigsaw@me.com;##null##;##null##;False;False;True;1.61803;3.1415;1;1;Falcon;Horse;Cat;Cat;Cat;Cat;Dog;Dog schwaang@gmail.com;schwaang@gmail.com;##null##;##null##;True;False;True;1.61803;3.1415;4;1;Horse;Dog;Cat;Cat;Cat;Cat;Dog;Dog eurohack@verizon.net;eurohack@verizon.net;##null##;##null##;False;False;True;3.1415;3.1415;3;1;Falcon;Dog;Cat;Cat;Cat;Cat;Dog;Dog janneh@icloud.com;janneh@icloud.com;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Dog;Horse;Falcon;Cat;Cat;Cat;Dog;Dog -frederic@me.com;frederic@me.com;##null##;##null##;False;False;True;0.0;3.1415;4;1;Dog;Falcon;Cat;Cat;Cat;Cat;Dog;Dog +frederic@me.com;frederic@me.com;##null##;##null##;False;False;True;0;3.1415;4;1;Dog;Falcon;Cat;Cat;Cat;Cat;Dog;Dog facet@optonline.net;facet@optonline.net;##null##;##null##;False;False;True;3.1415;3.1415;4;1;Cat;Cat;Cat;Cat;Cat;Cat;Dog;Dog -uncle@aol.com;uncle@aol.com;##null##;##null##;False;False;True;0.0;3.1415;3;1;Horse;Horse;Falcon;Cat;Cat;Cat;Dog;Dog +uncle@aol.com;uncle@aol.com;##null##;##null##;False;False;True;0;3.1415;3;1;Horse;Horse;Falcon;Cat;Cat;Cat;Dog;Dog wilsonpm@comcast.net;wilsonpm@comcast.net;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Dog;Dog;Cat;Cat;Cat;Cat;Dog;Dog garland@optonline.net;garland@optonline.net;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Cat;Cat;Cat;Cat;Cat;Cat;Dog;Dog srour@yahoo.com;srour@yahoo.com;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Dog;Dog;Cat;Cat;Cat;Cat;Dog;Dog @@ -90,9 +91,9 @@ bester@mac.com;bester@mac.com;##null##;##null##;True;False;True;1.61803;3.1415;1 kildjean@verizon.net;kildjean@verizon.net;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Cat;Horse;Falcon;Cat;Cat;Cat;Dog;Dog arandal@comcast.net;arandal@comcast.net;##null##;##null##;False;False;True;2.7182;3.1415;3;1;Falcon;Cat;Cat;Cat;Cat;Cat;Dog;Dog bartlett@yahoo.com;bartlett@yahoo.com;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Dog;Falcon;Cat;Cat;Cat;Cat;Dog;Dog -zyghom@icloud.com;zyghom@icloud.com;##null##;##null##;False;False;True;0.0;3.1415;3;1;Falcon;Horse;Cat;Cat;Cat;Cat;Dog;Dog +zyghom@icloud.com;zyghom@icloud.com;##null##;##null##;False;False;True;0;3.1415;3;1;Falcon;Horse;Cat;Cat;Cat;Cat;Dog;Dog valdez@mac.com;valdez@mac.com;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Cat;Falcon;Falcon;Cat;Cat;Cat;Dog;Dog -scato@yahoo.com;scato@yahoo.com;##null##;##null##;False;False;True;0.0;3.1415;4;1;Horse;Cat;Cat;Cat;Cat;Cat;Dog;Dog +scato@yahoo.com;scato@yahoo.com;##null##;##null##;False;False;True;0;3.1415;4;1;Horse;Cat;Cat;Cat;Cat;Cat;Dog;Dog sinkou@live.com;sinkou@live.com;##null##;##null##;True;False;True;2.7182;3.1415;1;1;Dog;Falcon;Falcon;Cat;Cat;Cat;Dog;Dog evilopie@comcast.net;evilopie@comcast.net;##null##;##null##;True;False;True;2.7182;3.1415;1;1;Horse;Dog;Cat;Cat;Cat;Cat;Dog;Dog ducasse@gmail.com;ducasse@gmail.com;##null##;##null##;True;False;True;3.1415;3.1415;3;1;Cat;Dog;Cat;Cat;Cat;Cat;Dog;Dog @@ -100,30 +101,30 @@ sthomas@sbcglobal.net;sthomas@sbcglobal.net;##null##;##null##;False;False;True;1 plover@msn.com;plover@msn.com;##null##;##null##;True;False;True;3.1415;3.1415;2;1;Cat;Falcon;Cat;Cat;Cat;Cat;Dog;Dog mavilar@yahoo.com;mavilar@yahoo.com;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Horse;Falcon;Falcon;Cat;Cat;Cat;Dog;Dog josephw@msn.com;josephw@msn.com;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Falcon;Horse;Cat;Cat;Cat;Cat;Dog;Dog -qmacro@yahoo.com;qmacro@yahoo.com;##null##;##null##;True;False;True;0.0;3.1415;1;1;Cat;Falcon;Cat;Cat;Cat;Cat;Dog;Dog +qmacro@yahoo.com;qmacro@yahoo.com;##null##;##null##;True;False;True;0;3.1415;1;1;Cat;Falcon;Cat;Cat;Cat;Cat;Dog;Dog munson@mac.com;munson@mac.com;##null##;##null##;True;False;True;3.1415;3.1415;3;1;Cat;Cat;Falcon;Cat;Cat;Cat;Dog;Dog paulv@mac.com;paulv@mac.com;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Falcon;Falcon;Falcon;Cat;Cat;Cat;Dog;Dog dogdude@hotmail.com;dogdude@hotmail.com;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Cat;Dog;Falcon;Cat;Cat;Cat;Dog;Dog -symbolic@yahoo.ca;symbolic@yahoo.ca;##null##;##null##;False;False;True;0.0;3.1415;2;1;Falcon;Dog;Cat;Cat;Cat;Cat;Dog;Dog +symbolic@yahoo.ca;symbolic@yahoo.ca;##null##;##null##;False;False;True;0;3.1415;2;1;Falcon;Dog;Cat;Cat;Cat;Cat;Dog;Dog carcus@yahoo.com;carcus@yahoo.com;##null##;##null##;True;False;True;2.7182;3.1415;3;1;Cat;Cat;Cat;Cat;Cat;Cat;Dog;Dog sblack@me.com;sblack@me.com;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Dog;Dog;Falcon;Cat;Cat;Cat;Dog;Dog richard@gmail.com;richard@gmail.com;##null##;##null##;True;False;True;2.7182;3.1415;3;1;Falcon;Dog;Cat;Cat;Cat;Cat;Dog;Dog tbusch@yahoo.ca;tbusch@yahoo.ca;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Dog;Dog;Cat;Cat;Cat;Cat;Dog;Dog gtaylor@aol.com;gtaylor@aol.com;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Dog;Dog burniske@att.net;burniske@att.net;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Cat;Horse;Cat;Cat;Cat;Cat;Dog;Dog -bebing@me.com;bebing@me.com;##null##;##null##;False;False;True;0.0;3.1415;1;1;Falcon;Cat;Cat;Cat;Cat;Cat;Dog;Dog +bebing@me.com;bebing@me.com;##null##;##null##;False;False;True;0;3.1415;1;1;Falcon;Cat;Cat;Cat;Cat;Cat;Dog;Dog joglo@gmail.com;joglo@gmail.com;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Cat;Cat;Cat;Cat;Cat;Cat;Dog;Dog chrwin@sbcglobal.net;chrwin@sbcglobal.net;##null##;##null##;True;False;True;3.1415;3.1415;3;1;Horse;Falcon;Cat;Cat;Cat;Cat;Dog;Dog chaikin@yahoo.com;chaikin@yahoo.com;##null##;##null##;True;False;True;3.1415;3.1415;4;1;Cat;Cat;Cat;Cat;Cat;Cat;Dog;Dog -jigsaw@verizon.net;jigsaw@verizon.net;##null##;##null##;True;False;True;0.0;3.1415;3;1;Cat;Cat;Cat;Cat;Cat;Cat;Dog;Dog +jigsaw@verizon.net;jigsaw@verizon.net;##null##;##null##;True;False;True;0;3.1415;3;1;Cat;Cat;Cat;Cat;Cat;Cat;Dog;Dog wbarker@yahoo.ca;wbarker@yahoo.ca;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Horse;Falcon;Cat;Cat;Cat;Cat;Dog;Dog ganter@verizon.net;ganter@verizon.net;##null##;##null##;True;False;True;2.7182;3.1415;2;1;Dog;Cat;Cat;Cat;Cat;Cat;Dog;Dog -eegsa@att.net;eegsa@att.net;##null##;##null##;False;False;True;0.0;3.1415;3;1;Falcon;Cat;Cat;Cat;Cat;Cat;Dog;Dog +eegsa@att.net;eegsa@att.net;##null##;##null##;False;False;True;0;3.1415;3;1;Falcon;Cat;Cat;Cat;Cat;Cat;Dog;Dog sethbrown@hotmail.com;sethbrown@hotmail.com;##null##;##null##;True;False;True;1.61803;3.1415;4;1;Dog;Horse;Cat;Cat;Cat;Cat;Dog;Dog solomon@me.com;solomon@me.com;##null##;##null##;True;False;True;3.1415;3.1415;2;1;Cat;Dog;Cat;Cat;Cat;Cat;Dog;Dog tellis@yahoo.ca;tellis@yahoo.ca;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Dog;Dog jshirley@optonline.net;jshirley@optonline.net;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Dog;Dog -tattooman@verizon.net;tattooman@verizon.net;##null##;##null##;False;False;True;0.0;3.1415;1;1;Horse;Dog;Cat;Cat;Cat;Cat;Dog;Dog +tattooman@verizon.net;tattooman@verizon.net;##null##;##null##;False;False;True;0;3.1415;1;1;Horse;Dog;Cat;Cat;Cat;Cat;Dog;Dog bescoto@yahoo.com;bescoto@yahoo.com;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Horse;Dog;Cat;Cat;Cat;Cat;Dog;Dog hstiles@comcast.net;hstiles@comcast.net;##null##;##null##;True;False;True;2.7182;3.1415;2;1;Dog;Cat;Cat;Cat;Cat;Cat;Dog;Dog gumpish@optonline.net;gumpish@optonline.net;##null##;##null##;True;False;True;2.7182;3.1415;3;1;Horse;Cat;Cat;Cat;Cat;Cat;Dog;Dog @@ -138,12 +139,12 @@ shrapnull@att.net;shrapnull@att.net;##null##;##null##;True;False;True;2.7182;3.1 lcheng@comcast.net;lcheng@comcast.net;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Dog;Falcon;Cat;Cat;Cat;Cat;Dog;Dog cyrus@msn.com;cyrus@msn.com;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Dog;Dog suresh@yahoo.ca;suresh@yahoo.ca;##null##;##null##;True;False;True;1.61803;3.1415;1;1;Cat;Horse;Cat;Cat;Cat;Cat;Dog;Dog -elflord@yahoo.ca;##null##;##null##;##null##;False;False;True;0.0;3.1415;4;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat -sassen@verizon.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +elflord@yahoo.ca;##null##;##null##;##null##;False;False;True;0;3.1415;4;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat +sassen@verizon.net;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat dbindel@live.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Horse;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat -morain@hotmail.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat +morain@hotmail.com;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat timtroyr@outlook.com;##null##;##null##;##null##;True;False;True;3.1415;3.1415;3;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat -esbeck@live.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;1;1;Cat;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat +esbeck@live.com;##null##;##null##;##null##;True;False;True;0;3.1415;1;1;Cat;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat ilyaz@hotmail.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;4;1;Horse;Cat;Falcon;Cat;Cat;Cat;Cat;Cat grinder@icloud.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat trieuvan@gmail.com;##null##;##null##;##null##;True;False;True;1.61803;3.1415;1;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat @@ -153,7 +154,7 @@ nichoj@outlook.com;##null##;##null##;##null##;True;False;True;1.61803;3.1415;2;1 sopwith@outlook.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;1;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat odlyzko@yahoo.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat warrior@optonline.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat -budinger@msn.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;1;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +budinger@msn.com;##null##;##null##;##null##;False;False;True;0;3.1415;1;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat lstein@comcast.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat kmiller@gmail.com;##null##;##null##;##null##;True;False;True;1.61803;3.1415;4;1;Cat;Cat;Falcon;Cat;Cat;Cat;Cat;Cat british@msn.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Cat;Horse;Falcon;Cat;Cat;Cat;Cat;Cat @@ -161,19 +162,19 @@ webinc@gmail.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;1;1; kohlis@aol.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;1;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat matthijs@outlook.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat mmccool@me.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;3;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat -ribet@hotmail.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;4;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat -wildfire@me.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Dog;Horse;Falcon;Cat;Cat;Cat;Cat;Cat +ribet@hotmail.com;##null##;##null##;##null##;True;False;True;0;3.1415;4;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat +wildfire@me.com;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Dog;Horse;Falcon;Cat;Cat;Cat;Cat;Cat makarow@gmail.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat garland@hotmail.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Cat;Cat;Falcon;Cat;Cat;Cat;Cat;Cat -kjohnson@outlook.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Horse;Cat;Falcon;Cat;Cat;Cat;Cat;Cat +kjohnson@outlook.com;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Horse;Cat;Falcon;Cat;Cat;Cat;Cat;Cat oneiros@sbcglobal.net;##null##;##null##;##null##;True;False;True;1.61803;3.1415;3;1;Cat;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat -jaxweb@gmail.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;1;1;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Cat -raides@msn.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;3;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat +jaxweb@gmail.com;##null##;##null##;##null##;True;False;True;0;3.1415;1;1;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Cat +raides@msn.com;##null##;##null##;##null##;True;False;True;0;3.1415;3;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat cantu@comcast.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;4;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat msherr@comcast.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -dwsauder@aol.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;1;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat +dwsauder@aol.com;##null##;##null##;##null##;True;False;True;0;3.1415;1;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat comdig@gmail.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;3;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat -esokullu@yahoo.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;4;1;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Cat +esokullu@yahoo.com;##null##;##null##;##null##;False;False;True;0;3.1415;4;1;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Cat kjetilk@aol.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;4;1;Falcon;Horse;Falcon;Cat;Cat;Cat;Cat;Cat boomzilla@icloud.com;##null##;##null##;##null##;True;False;True;3.1415;3.1415;1;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat cvrcek@outlook.com;##null##;##null##;##null##;True;False;True;1.61803;3.1415;1;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat @@ -185,76 +186,76 @@ weidai@hotmail.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;2; dpitts@live.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat bebing@optonline.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;1;1;Dog;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat wikinerd@yahoo.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat -pfitza@yahoo.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat -policies@me.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +pfitza@yahoo.com;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat +policies@me.com;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat niknejad@me.com;##null##;##null##;##null##;True;False;True;1.61803;3.1415;4;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat aukjan@hotmail.com;##null##;##null##;##null##;True;False;True;1.61803;3.1415;3;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat dleconte@sbcglobal.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Cat;Dog;Falcon;Cat;Cat;Cat;Cat;Cat -noahb@aol.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;4;1;Dog;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +noahb@aol.com;##null##;##null##;##null##;True;False;True;0;3.1415;4;1;Dog;Falcon;Cat;Cat;Cat;Cat;Cat;Cat bdbrown@aol.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;1;1;Horse;Horse;Falcon;Cat;Cat;Cat;Cat;Cat -adillon@att.net;##null##;##null##;##null##;True;False;True;0.0;3.1415;3;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat +adillon@att.net;##null##;##null##;##null##;True;False;True;0;3.1415;3;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat eegsa@me.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Dog;Dog;Falcon;Cat;Cat;Cat;Cat;Cat chunzi@hotmail.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;3;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat privcan@optonline.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat mglee@hotmail.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat leocharre@me.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Cat;Dog;Falcon;Cat;Cat;Cat;Cat;Cat dwendlan@verizon.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -lpalmer@hotmail.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;1;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +lpalmer@hotmail.com;##null##;##null##;##null##;False;False;True;0;3.1415;1;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat emcleod@msn.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;3;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat breegster@sbcglobal.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;4;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat mwandel@comcast.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;3;1;Dog;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -stewwy@me.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;2;1;Dog;Horse;Falcon;Cat;Cat;Cat;Cat;Cat +stewwy@me.com;##null##;##null##;##null##;True;False;True;0;3.1415;2;1;Dog;Horse;Falcon;Cat;Cat;Cat;Cat;Cat drolsky@live.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;1;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat -lukka@live.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;1;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat +lukka@live.com;##null##;##null##;##null##;True;False;True;0;3.1415;1;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat geekgrl@me.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;3;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat epeeist@me.com;##null##;##null##;##null##;True;False;True;1.61803;3.1415;1;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat papathan@verizon.net;##null##;##null##;##null##;True;False;True;1.61803;3.1415;1;1;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Cat singh@optonline.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat -njpayne@aol.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;4;1;Dog;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +njpayne@aol.com;##null##;##null##;##null##;False;False;True;0;3.1415;4;1;Dog;Falcon;Cat;Cat;Cat;Cat;Cat;Cat willg@comcast.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;3;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat jimmichie@icloud.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;3;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat frosal@aol.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;3;1;Cat;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat dunstan@yahoo.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat parasite@yahoo.ca;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat -firstpr@msn.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;2;1;Cat;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat +firstpr@msn.com;##null##;##null##;##null##;True;False;True;0;3.1415;2;1;Cat;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat konit@icloud.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat amaranth@msn.com;##null##;##null##;##null##;True;False;True;3.1415;3.1415;1;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat mcsporran@msn.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;4;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat -gommix@yahoo.ca;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat -dprice@verizon.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;4;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat +gommix@yahoo.ca;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat +dprice@verizon.net;##null##;##null##;##null##;False;False;True;0;3.1415;4;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat lcheng@me.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;3;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat -dwendlan@optonline.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;1;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat -miami@hotmail.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;4;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +dwendlan@optonline.net;##null##;##null##;##null##;False;False;True;0;3.1415;1;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat +miami@hotmail.com;##null##;##null##;##null##;False;False;True;0;3.1415;4;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat ajlitt@hotmail.com;##null##;##null##;##null##;True;False;True;1.61803;3.1415;2;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat grdschl@mac.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat andersbr@att.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Falcon;Cat;Falcon;Cat;Cat;Cat;Cat;Cat nacho@yahoo.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat hoangle@msn.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;2;1;Dog;Falcon;Cat;Cat;Cat;Cat;Cat;Cat jbuchana@gmail.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat -knorr@sbcglobal.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;1;1;Horse;Dog;Falcon;Cat;Cat;Cat;Cat;Cat -saridder@gmail.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;1;1;Horse;Horse;Falcon;Cat;Cat;Cat;Cat;Cat -scotfl@outlook.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;1;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat -skoch@yahoo.ca;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +knorr@sbcglobal.net;##null##;##null##;##null##;False;False;True;0;3.1415;1;1;Horse;Dog;Falcon;Cat;Cat;Cat;Cat;Cat +saridder@gmail.com;##null##;##null##;##null##;True;False;True;0;3.1415;1;1;Horse;Horse;Falcon;Cat;Cat;Cat;Cat;Cat +scotfl@outlook.com;##null##;##null##;##null##;False;False;True;0;3.1415;1;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat +skoch@yahoo.ca;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat luebke@verizon.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat -bsikdar@live.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;3;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat +bsikdar@live.com;##null##;##null##;##null##;True;False;True;0;3.1415;3;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat ryanvm@yahoo.ca;##null##;##null##;##null##;True;False;True;1.61803;3.1415;4;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat dburrows@gmail.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat -seebs@hotmail.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat +seebs@hotmail.com;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat mgemmons@optonline.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Dog;Cat;Falcon;Cat;Cat;Cat;Cat;Cat kobayasi@att.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat marcs@sbcglobal.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Cat;Horse;Falcon;Cat;Cat;Cat;Cat;Cat -netsfr@att.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat -martink@me.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;4;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +netsfr@att.net;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat +martink@me.com;##null##;##null##;##null##;False;False;True;0;3.1415;4;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat bflong@verizon.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat bhima@outlook.com;##null##;##null##;##null##;True;False;True;3.1415;3.1415;4;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat oster@att.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat -teverett@yahoo.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat +teverett@yahoo.com;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat kannan@optonline.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat yzheng@verizon.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat msusa@gmail.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat -hmbrand@gmail.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;4;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +hmbrand@gmail.com;##null##;##null##;##null##;False;False;True;0;3.1415;4;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat falcao@gmail.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat -uraeus@live.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Dog;Dog;Falcon;Cat;Cat;Cat;Cat;Cat +uraeus@live.com;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Dog;Dog;Falcon;Cat;Cat;Cat;Cat;Cat dunstan@msn.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat oracle@yahoo.ca;##null##;##null##;##null##;True;False;True;3.1415;3.1415;2;1;Cat;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat tbeck@gmail.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat @@ -264,25 +265,25 @@ yenya@msn.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Dog; bjoern@icloud.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Dog;Falcon;Cat;Cat;Cat;Cat;Cat;Cat benanov@aol.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;3;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat preneel@outlook.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;3;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat -punkis@sbcglobal.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;1;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -rwelty@comcast.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat +punkis@sbcglobal.net;##null##;##null##;##null##;False;False;True;0;3.1415;1;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +rwelty@comcast.net;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat penna@me.com;##null##;##null##;##null##;True;False;True;3.1415;3.1415;1;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat baveja@msn.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Dog;Falcon;Cat;Cat;Cat;Cat;Cat;Cat louise@verizon.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Cat;Dog;Falcon;Cat;Cat;Cat;Cat;Cat arachne@icloud.com;##null##;##null##;##null##;True;False;True;1.61803;3.1415;1;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat hahiss@msn.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat wayward@optonline.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -pajas@sbcglobal.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Cat;Horse;Falcon;Cat;Cat;Cat;Cat;Cat -intlprog@comcast.net;##null##;##null##;##null##;True;False;True;0.0;3.1415;2;1;Horse;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat +pajas@sbcglobal.net;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Cat;Horse;Falcon;Cat;Cat;Cat;Cat;Cat +intlprog@comcast.net;##null##;##null##;##null##;True;False;True;0;3.1415;2;1;Horse;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat hermanab@sbcglobal.net;##null##;##null##;##null##;True;False;True;2.7182;3.1415;2;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat presoff@msn.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;1;1;Dog;Falcon;Cat;Cat;Cat;Cat;Cat;Cat trygstad@mac.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat -denton@optonline.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat -skythe@live.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;1;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat -hmbrand@gmail.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;4;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +denton@optonline.net;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat +skythe@live.com;##null##;##null##;##null##;False;False;True;0;3.1415;1;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat +hmbrand@gmail.com;##null##;##null##;##null##;False;False;True;0;3.1415;4;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat lushe@sbcglobal.net;##null##;##null##;##null##;True;False;True;1.61803;3.1415;2;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat -magusnet@outlook.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;1;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat -ullman@optonline.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;4;1;Cat;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat +magusnet@outlook.com;##null##;##null##;##null##;False;False;True;0;3.1415;1;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat +ullman@optonline.net;##null##;##null##;##null##;False;False;True;0;3.1415;4;1;Cat;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat jyoliver@optonline.net;##null##;##null##;##null##;True;False;True;2.7182;3.1415;2;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat flavell@icloud.com;##null##;##null##;##null##;True;False;True;3.1415;3.1415;1;1;Falcon;Cat;Falcon;Cat;Cat;Cat;Cat;Cat ianbuck@att.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;4;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat @@ -291,11 +292,11 @@ gommix@sbcglobal.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;4 rnelson@att.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;1;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat crusader@gmail.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat rddesign@sbcglobal.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat -nanop@mac.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat +nanop@mac.com;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat ngedmond@live.com;##null##;##null##;##null##;True;False;True;3.1415;3.1415;1;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat munjal@live.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;3;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat unreal@aol.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;4;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat -jemarch@sbcglobal.net;##null##;##null##;##null##;True;False;True;0.0;3.1415;2;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat +jemarch@sbcglobal.net;##null##;##null##;##null##;True;False;True;0;3.1415;2;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat shawnce@sbcglobal.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;1;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat nweaver@yahoo.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat british@verizon.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat @@ -309,10 +310,10 @@ mrobshaw@optonline.net;##null##;##null##;##null##;True;False;True;1.61803;3.1415 denton@yahoo.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat konst@mac.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat louise@aol.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;2;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat -wetter@gmail.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat +wetter@gmail.com;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat kohlis@att.net;##null##;##null##;##null##;True;False;True;3.1415;3.1415;4;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat monkeydo@aol.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;3;1;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Cat -melnik@yahoo.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +melnik@yahoo.com;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat munge@verizon.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;4;1;Dog;Falcon;Cat;Cat;Cat;Cat;Cat;Cat stefano@live.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat giafly@verizon.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat @@ -323,8 +324,8 @@ firstpr@yahoo.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1; nichoj@comcast.net;##null##;##null##;##null##;True;False;True;3.1415;3.1415;1;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat aibrahim@optonline.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Cat;Cat;Falcon;Cat;Cat;Cat;Cat;Cat inico@aol.com;##null##;##null##;##null##;True;False;True;1.61803;3.1415;4;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat -ribet@sbcglobal.net;##null##;##null##;##null##;True;False;True;0.0;3.1415;1;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -ajlitt@mac.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat +ribet@sbcglobal.net;##null##;##null##;##null##;True;False;True;0;3.1415;1;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +ajlitt@mac.com;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat errxn@me.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat lstein@icloud.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat mgemmons@icloud.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Horse;Horse;Falcon;Cat;Cat;Cat;Cat;Cat @@ -339,17 +340,17 @@ dhwon@yahoo.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Do mstrout@msn.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Cat;Cat;Falcon;Cat;Cat;Cat;Cat;Cat manuals@me.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;1;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat andrewik@me.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;3;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat -hahsler@icloud.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;4;1;Dog;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat +hahsler@icloud.com;##null##;##null##;##null##;True;False;True;0;3.1415;4;1;Dog;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat miami@verizon.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;3;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat facet@me.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;3;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat -zeitlin@outlook.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;4;1;Falcon;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat -lamprecht@aol.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat +zeitlin@outlook.com;##null##;##null##;##null##;False;False;True;0;3.1415;4;1;Falcon;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat +lamprecht@aol.com;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat johnh@mac.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;4;1;Dog;Cat;Falcon;Cat;Cat;Cat;Cat;Cat -mrsam@yahoo.ca;##null##;##null##;##null##;False;False;True;0.0;3.1415;4;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +mrsam@yahoo.ca;##null##;##null##;##null##;False;False;True;0;3.1415;4;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat lipeng@outlook.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat dsowsy@icloud.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat philen@icloud.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat -kjohnson@gmail.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +kjohnson@gmail.com;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat nelson@hotmail.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;3;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat syncnine@comcast.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat pgottsch@hotmail.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;4;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat @@ -359,16 +360,16 @@ mrdvt@outlook.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1; cfhsoft@outlook.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat kodeman@yahoo.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat mbrown@comcast.net;##null##;##null##;##null##;True;False;True;2.7182;3.1415;1;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat -jaxweb@hotmail.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat +jaxweb@hotmail.com;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat symbolic@icloud.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat denism@att.net;##null##;##null##;##null##;True;False;True;2.7182;3.1415;2;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat hager@mac.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Dog;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -zavadsky@yahoo.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;2;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat +zavadsky@yahoo.com;##null##;##null##;##null##;True;False;True;0;3.1415;2;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat mugwump@hotmail.com;##null##;##null##;##null##;True;False;True;3.1415;3.1415;4;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat geekgrl@comcast.net;##null##;##null##;##null##;True;False;True;1.61803;3.1415;1;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat -dprice@me.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Falcon;Horse;Falcon;Cat;Cat;Cat;Cat;Cat -petersko@yahoo.ca;##null##;##null##;##null##;False;False;True;0.0;3.1415;4;1;Falcon;Horse;Falcon;Cat;Cat;Cat;Cat;Cat -netsfr@aol.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;2;1;Dog;Horse;Falcon;Cat;Cat;Cat;Cat;Cat +dprice@me.com;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Falcon;Horse;Falcon;Cat;Cat;Cat;Cat;Cat +petersko@yahoo.ca;##null##;##null##;##null##;False;False;True;0;3.1415;4;1;Falcon;Horse;Falcon;Cat;Cat;Cat;Cat;Cat +netsfr@aol.com;##null##;##null##;##null##;True;False;True;0;3.1415;2;1;Dog;Horse;Falcon;Cat;Cat;Cat;Cat;Cat tbmaddux@aol.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;1;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat meder@att.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat benits@live.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat @@ -394,12 +395,12 @@ wayward@yahoo.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;4;1; amaranth@me.com;##null##;##null##;##null##;True;False;True;3.1415;3.1415;2;1;Falcon;Dog;Falcon;Cat;Cat;Cat;Cat;Cat garland@yahoo.com;##null##;##null##;##null##;True;False;True;3.1415;3.1415;3;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat rfisher@live.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;4;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat -stern@verizon.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Dog;Dog;Falcon;Cat;Cat;Cat;Cat;Cat +stern@verizon.net;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Dog;Dog;Falcon;Cat;Cat;Cat;Cat;Cat mavilar@yahoo.ca;##null##;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat rfisher@sbcglobal.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Horse;Dog;Falcon;Cat;Cat;Cat;Cat;Cat tarreau@att.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat -koudas@sbcglobal.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;4;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat -bwcarty@mac.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;2;1;Cat;Horse;Falcon;Cat;Cat;Cat;Cat;Cat +koudas@sbcglobal.net;##null##;##null##;##null##;False;False;True;0;3.1415;4;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat +bwcarty@mac.com;##null##;##null##;##null##;True;False;True;0;3.1415;2;1;Cat;Horse;Falcon;Cat;Cat;Cat;Cat;Cat jeteve@verizon.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Falcon;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat pmint@comcast.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat barlow@icloud.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;4;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat @@ -414,24 +415,24 @@ clkao@outlook.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;4;1; noahb@yahoo.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat ducasse@comcast.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat jrkorson@live.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;3;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat -hmbrand@gmail.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;4;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +hmbrand@gmail.com;##null##;##null##;##null##;False;False;True;0;3.1415;4;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat corrada@yahoo.ca;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Horse;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat tmaek@aol.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;4;1;Horse;Cat;Falcon;Cat;Cat;Cat;Cat;Cat richard@mac.com;##null##;##null##;##null##;True;False;True;1.61803;3.1415;1;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat pkplex@comcast.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -kwilliams@icloud.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat +kwilliams@icloud.com;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat mcrawfor@yahoo.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;3;1;Dog;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat webteam@outlook.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat leakin@hotmail.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;4;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat ebassi@optonline.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;3;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -njpayne@msn.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;4;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat +njpayne@msn.com;##null##;##null##;##null##;False;False;True;0;3.1415;4;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat konst@live.com;##null##;##null##;##null##;True;False;True;3.1415;3.1415;2;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat godeke@me.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -godeke@yahoo.ca;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat +godeke@yahoo.ca;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat glenz@gmail.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat fallorn@comcast.net;##null##;##null##;##null##;True;False;True;2.7182;3.1415;2;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat nacho@comcast.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;3;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat -dkeeler@hotmail.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Dog;Horse;Falcon;Cat;Cat;Cat;Cat;Cat +dkeeler@hotmail.com;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Dog;Horse;Falcon;Cat;Cat;Cat;Cat;Cat adhere@live.com;##null##;##null##;##null##;True;False;True;1.61803;3.1415;3;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat gfody@gmail.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;3;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat codex@verizon.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;3;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat @@ -443,37 +444,37 @@ miami@msn.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Cat; kewley@icloud.com;##null##;##null##;##null##;True;False;True;3.1415;3.1415;2;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat dkeeler@outlook.com;##null##;##null##;##null##;True;False;True;3.1415;3.1415;1;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat galbra@gmail.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;4;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat -mastinfo@yahoo.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat -kempsonc@sbcglobal.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;1;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat +mastinfo@yahoo.com;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat +kempsonc@sbcglobal.net;##null##;##null##;##null##;False;False;True;0;3.1415;1;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat andale@mac.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Dog;Cat;Falcon;Cat;Cat;Cat;Cat;Cat airship@sbcglobal.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Dog;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat xtang@live.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat jhardin@yahoo.ca;##null##;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat frederic@sbcglobal.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat -matsn@yahoo.ca;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Falcon;Horse;Falcon;Cat;Cat;Cat;Cat;Cat +matsn@yahoo.ca;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Falcon;Horse;Falcon;Cat;Cat;Cat;Cat;Cat pereinar@optonline.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat salesgeek@verizon.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat tezbo@aol.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;4;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat keijser@icloud.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;1;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat chaki@yahoo.ca;##null##;##null##;##null##;False;False;True;1.61803;3.1415;1;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat -wetter@msn.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat +wetter@msn.com;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat delpino@icloud.com;##null##;##null##;##null##;True;False;True;3.1415;3.1415;1;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat -thassine@att.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Cat +thassine@att.net;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Cat hoangle@hotmail.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;3;1;Cat;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat bester@gmail.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat jdhedden@yahoo.ca;##null##;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Falcon;Dog;Falcon;Cat;Cat;Cat;Cat;Cat killmenow@msn.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Dog;Cat;Falcon;Cat;Cat;Cat;Cat;Cat retoh@mac.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat -goresky@yahoo.ca;##null##;##null##;##null##;True;False;True;0.0;3.1415;3;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -microfab@att.net;##null##;##null##;##null##;True;False;True;0.0;3.1415;1;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat -pfitza@aol.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat +goresky@yahoo.ca;##null##;##null##;##null##;True;False;True;0;3.1415;3;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +microfab@att.net;##null##;##null##;##null##;True;False;True;0;3.1415;1;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat +pfitza@aol.com;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat onestab@hotmail.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Falcon;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat aracne@me.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat sherzodr@yahoo.ca;##null##;##null##;##null##;True;False;True;1.61803;3.1415;1;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat feamster@verizon.net;##null##;##null##;##null##;True;False;True;3.1415;3.1415;4;1;Dog;Dog;Falcon;Cat;Cat;Cat;Cat;Cat hyper@yahoo.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;3;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat jmgomez@me.com;##null##;##null##;##null##;True;False;True;3.1415;3.1415;2;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat -fwitness@outlook.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;1;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +fwitness@outlook.com;##null##;##null##;##null##;False;False;True;0;3.1415;1;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat storerm@comcast.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Dog;Horse;Falcon;Cat;Cat;Cat;Cat;Cat improv@yahoo.ca;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Dog;Dog;Falcon;Cat;Cat;Cat;Cat;Cat arnold@comcast.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;3;1;Falcon;Cat;Falcon;Cat;Cat;Cat;Cat;Cat @@ -483,11 +484,11 @@ rfisher@verizon.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;4; dwsauder@icloud.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat alastair@gmail.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;3;1;Horse;Horse;Falcon;Cat;Cat;Cat;Cat;Cat multiplx@hotmail.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat -caidaperl@icloud.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Cat;Cat;Falcon;Cat;Cat;Cat;Cat;Cat +caidaperl@icloud.com;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Cat;Cat;Falcon;Cat;Cat;Cat;Cat;Cat nacho@yahoo.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat keutzer@icloud.com;##null##;##null##;##null##;True;False;True;3.1415;3.1415;1;1;Dog;Falcon;Cat;Cat;Cat;Cat;Cat;Cat lbaxter@icloud.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat -hachi@live.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;2;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat +hachi@live.com;##null##;##null##;##null##;True;False;True;0;3.1415;2;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat mfburgo@msn.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Cat gfody@yahoo.ca;##null##;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Horse;Cat;Falcon;Cat;Cat;Cat;Cat;Cat jaxweb@yahoo.ca;##null##;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat @@ -499,25 +500,25 @@ lstein@verizon.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3; conteb@msn.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;3;1;Dog;Horse;Falcon;Cat;Cat;Cat;Cat;Cat wildixon@att.net;##null##;##null##;##null##;True;False;True;1.61803;3.1415;1;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat oechslin@hotmail.com;##null##;##null##;##null##;True;False;True;1.61803;3.1415;2;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat -metzzo@msn.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Cat +metzzo@msn.com;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Cat kosact@live.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -kodeman@optonline.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat +kodeman@optonline.net;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat ebassi@hotmail.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Cat pgolle@optonline.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat jdhildeb@mac.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;3;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat garyjb@optonline.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat gslondon@me.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;4;1;Dog;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -maratb@msn.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat -marnanel@optonline.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +maratb@msn.com;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat +marnanel@optonline.net;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat rgiersig@live.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;3;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat gozer@msn.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat valdez@hotmail.com;##null##;##null##;##null##;True;False;True;3.1415;3.1415;4;1;Horse;Dog;Falcon;Cat;Cat;Cat;Cat;Cat mnemonic@yahoo.ca;##null##;##null##;##null##;True;False;True;2.7182;3.1415;4;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat paina@outlook.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat -syncnine@aol.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;1;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat +syncnine@aol.com;##null##;##null##;##null##;True;False;True;0;3.1415;1;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat melnik@gmail.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;4;1;Horse;Horse;Falcon;Cat;Cat;Cat;Cat;Cat jaesenj@sbcglobal.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -sekiya@me.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat +sekiya@me.com;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat tbusch@aol.com;##null##;##null##;##null##;True;False;True;1.61803;3.1415;1;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat whimsy@gmail.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Dog;Dog;Falcon;Cat;Cat;Cat;Cat;Cat firstpr@aol.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;4;1;Cat;Horse;Falcon;Cat;Cat;Cat;Cat;Cat @@ -528,22 +529,22 @@ bcevc@gmail.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;1;1;C sethbrown@me.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat mcmillan@aol.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat raines@optonline.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;1;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat -psharpe@comcast.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -bachmann@gmail.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;3;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat +psharpe@comcast.net;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +bachmann@gmail.com;##null##;##null##;##null##;True;False;True;0;3.1415;3;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat leslie@att.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat hager@att.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -ismail@mac.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;1;1;Cat;Dog;Falcon;Cat;Cat;Cat;Cat;Cat +ismail@mac.com;##null##;##null##;##null##;False;False;True;0;3.1415;1;1;Cat;Dog;Falcon;Cat;Cat;Cat;Cat;Cat nacho@aol.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat kohlis@yahoo.ca;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat lahvak@hotmail.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Falcon;Dog;Falcon;Cat;Cat;Cat;Cat;Cat gozer@mac.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Cat willg@icloud.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;3;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat zavadsky@yahoo.ca;##null##;##null##;##null##;False;False;True;2.7182;3.1415;3;1;Horse;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat -steve@me.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;4;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat +steve@me.com;##null##;##null##;##null##;False;False;True;0;3.1415;4;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat ccohen@icloud.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat msusa@mac.com;##null##;##null##;##null##;True;False;True;1.61803;3.1415;1;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat jsmith@att.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat -jshearer@outlook.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Cat;Horse;Falcon;Cat;Cat;Cat;Cat;Cat +jshearer@outlook.com;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Cat;Horse;Falcon;Cat;Cat;Cat;Cat;Cat pgottsch@hotmail.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;4;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat majordick@gmail.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;4;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat sjava@icloud.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat @@ -555,32 +556,32 @@ portscan@sbcglobal.net;##null##;##null##;##null##;False;False;True;1.61803;3.141 morain@comcast.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat gozer@mac.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Cat akoblin@icloud.com;##null##;##null##;##null##;True;False;True;3.1415;3.1415;3;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat -mhassel@comcast.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat +mhassel@comcast.net;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat spadkins@sbcglobal.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat rohitm@yahoo.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;4;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat mwandel@yahoo.com;##null##;##null##;##null##;True;False;True;3.1415;3.1415;4;1;Dog;Dog;Falcon;Cat;Cat;Cat;Cat;Cat warrior@me.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;4;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat -jipsen@aol.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;3;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat +jipsen@aol.com;##null##;##null##;##null##;True;False;True;0;3.1415;3;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat bancboy@mac.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat corrada@yahoo.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;1;1;Horse;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat wojciech@gmail.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat marcs@verizon.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat -atmarks@me.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;1;1;Cat;Horse;Falcon;Cat;Cat;Cat;Cat;Cat +atmarks@me.com;##null##;##null##;##null##;True;False;True;0;3.1415;1;1;Cat;Horse;Falcon;Cat;Cat;Cat;Cat;Cat quinn@verizon.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Dog;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -dkeeler@aol.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;2;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -pizza@yahoo.ca;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat +dkeeler@aol.com;##null##;##null##;##null##;True;False;True;0;3.1415;2;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +pizza@yahoo.ca;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat empathy@mac.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;1;1;Horse;Dog;Falcon;Cat;Cat;Cat;Cat;Cat dmouse@aol.com;##null##;##null##;##null##;True;False;True;1.61803;3.1415;4;1;Cat;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat -dinther@comcast.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;4;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat +dinther@comcast.net;##null##;##null##;##null##;False;False;True;0;3.1415;4;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat pappp@aol.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;2;1;Falcon;Cat;Falcon;Cat;Cat;Cat;Cat;Cat dougj@outlook.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;3;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat cfhsoft@msn.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;3;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat maratb@sbcglobal.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;3;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat stewwy@verizon.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Dog;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat -sravani@hotmail.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Falcon;Cat;Falcon;Cat;Cat;Cat;Cat;Cat +sravani@hotmail.com;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Falcon;Cat;Falcon;Cat;Cat;Cat;Cat;Cat tmaek@msn.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;4;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat juliano@yahoo.ca;##null##;##null##;##null##;False;False;True;1.61803;3.1415;1;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat -mcsporran@optonline.net;##null##;##null##;##null##;True;False;True;0.0;3.1415;2;1;Cat;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat +mcsporran@optonline.net;##null##;##null##;##null##;True;False;True;0;3.1415;2;1;Cat;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat mgemmons@yahoo.ca;##null##;##null##;##null##;True;False;True;1.61803;3.1415;1;1;Horse;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat seasweb@att.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat lushe@yahoo.ca;##null##;##null##;##null##;True;False;True;2.7182;3.1415;2;1;Cat;Cat;Falcon;Cat;Cat;Cat;Cat;Cat @@ -591,34 +592,34 @@ eegsa@msn.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Cat; rhavyn@hotmail.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Falcon;Cat;Falcon;Cat;Cat;Cat;Cat;Cat cremonini@me.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;3;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat boftx@me.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Dog;Cat;Falcon;Cat;Cat;Cat;Cat;Cat -smartfart@outlook.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;4;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat -uncled@outlook.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Dog;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +smartfart@outlook.com;##null##;##null##;##null##;True;False;True;0;3.1415;4;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat +uncled@outlook.com;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Dog;Falcon;Cat;Cat;Cat;Cat;Cat;Cat quantaman@aol.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;2;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat heidrich@live.com;##null##;##null##;##null##;True;False;True;1.61803;3.1415;3;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat moinefou@yahoo.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;3;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat -ilial@mac.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat +ilial@mac.com;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat fraser@verizon.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;1;1;Dog;Dog;Falcon;Cat;Cat;Cat;Cat;Cat -csilvers@me.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat -csilvers@mac.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;4;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat -kalpol@sbcglobal.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat +csilvers@me.com;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat +csilvers@mac.com;##null##;##null##;##null##;False;False;True;0;3.1415;4;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat +kalpol@sbcglobal.net;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat punkis@yahoo.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -nacho@msn.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat +nacho@msn.com;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat mcsporran@sbcglobal.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;3;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat jaarnial@hotmail.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat gboss@optonline.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;4;1;Dog;Cat;Falcon;Cat;Cat;Cat;Cat;Cat henkp@msn.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat philb@mac.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat -hllam@yahoo.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;3;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat +hllam@yahoo.com;##null##;##null##;##null##;True;False;True;0;3.1415;3;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat roamer@yahoo.ca;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat geekgrl@comcast.net;##null##;##null##;##null##;True;False;True;1.61803;3.1415;1;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat cantu@outlook.com;##null##;##null##;##null##;True;False;True;1.61803;3.1415;2;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat firstpr@outlook.com;##null##;##null##;##null##;True;False;True;3.1415;3.1415;1;1;Cat;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat hmbrand@aol.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;4;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat arandal@icloud.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;4;1;Dog;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -jaarnial@live.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;1;1;Horse;Dog;Falcon;Cat;Cat;Cat;Cat;Cat +jaarnial@live.com;##null##;##null##;##null##;False;False;True;0;3.1415;1;1;Horse;Dog;Falcon;Cat;Cat;Cat;Cat;Cat hoyer@me.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;1;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat mmccool@att.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;1;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat -smcnabb@att.net;##null##;##null##;##null##;True;False;True;0.0;3.1415;4;1;Cat;Horse;Falcon;Cat;Cat;Cat;Cat;Cat +smcnabb@att.net;##null##;##null##;##null##;True;False;True;0;3.1415;4;1;Cat;Horse;Falcon;Cat;Cat;Cat;Cat;Cat pakaste@yahoo.ca;##null##;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat panolex@mac.com;##null##;##null##;##null##;True;False;True;3.1415;3.1415;4;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat hikoza@att.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat @@ -632,64 +633,64 @@ hstiles@comcast.net;##null##;##null##;##null##;True;False;True;2.7182;3.1415;2;1 lushe@icloud.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat devphil@hotmail.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;4;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat dowdy@comcast.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat -arachne@verizon.net;##null##;##null##;##null##;True;False;True;0.0;3.1415;3;1;Cat;Cat;Falcon;Cat;Cat;Cat;Cat;Cat +arachne@verizon.net;##null##;##null##;##null##;True;False;True;0;3.1415;3;1;Cat;Cat;Falcon;Cat;Cat;Cat;Cat;Cat donev@icloud.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Dog;Falcon;Cat;Cat;Cat;Cat;Cat;Cat bowmanbs@hotmail.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat roesch@mac.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Dog;Horse;Falcon;Cat;Cat;Cat;Cat;Cat lridener@aol.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat hmbrand@comcast.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat sopwith@hotmail.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;4;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat -vsprintf@msn.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;1;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat +vsprintf@msn.com;##null##;##null##;##null##;False;False;True;0;3.1415;1;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat dwsauder@icloud.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat -symbolic@aol.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;2;1;Cat;Cat;Falcon;Cat;Cat;Cat;Cat;Cat +symbolic@aol.com;##null##;##null##;##null##;True;False;True;0;3.1415;2;1;Cat;Cat;Falcon;Cat;Cat;Cat;Cat;Cat gbacon@live.com;##null##;##null##;##null##;True;False;True;1.61803;3.1415;2;1;Dog;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -hillct@verizon.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;4;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat -earmstro@att.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;1;1;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Cat +hillct@verizon.net;##null##;##null##;##null##;False;False;True;0;3.1415;4;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat +earmstro@att.net;##null##;##null##;##null##;False;False;True;0;3.1415;1;1;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Cat studyabr@outlook.com;##null##;##null##;##null##;True;False;True;1.61803;3.1415;4;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat shawnce@yahoo.ca;##null##;##null##;##null##;True;False;True;2.7182;3.1415;2;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat boser@yahoo.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat gknauss@yahoo.ca;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat marcs@att.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;3;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat -bruck@icloud.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Falcon;Horse;Falcon;Cat;Cat;Cat;Cat;Cat +bruck@icloud.com;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Falcon;Horse;Falcon;Cat;Cat;Cat;Cat;Cat comdig@comcast.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Falcon;Horse;Falcon;Cat;Cat;Cat;Cat;Cat floxy@yahoo.com;##null##;##null##;##null##;True;False;True;3.1415;3.1415;3;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat tmccarth@icloud.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Cat;Cat;Falcon;Cat;Cat;Cat;Cat;Cat darin@me.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;3;1;Dog;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -mcraigw@msn.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat -fhirsch@outlook.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;1;1;Falcon;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat +mcraigw@msn.com;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat +fhirsch@outlook.com;##null##;##null##;##null##;False;False;True;0;3.1415;1;1;Falcon;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat unreal@verizon.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Cat;Horse;Falcon;Cat;Cat;Cat;Cat;Cat crypt@comcast.net;##null##;##null##;##null##;True;False;True;1.61803;3.1415;2;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat pakaste@sbcglobal.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat denism@att.net;##null##;##null##;##null##;True;False;True;2.7182;3.1415;2;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat staffelb@aol.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat -jonas@outlook.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;2;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat +jonas@outlook.com;##null##;##null##;##null##;True;False;True;0;3.1415;2;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat staikos@live.com;##null##;##null##;##null##;True;False;True;3.1415;3.1415;2;1;Horse;Cat;Falcon;Cat;Cat;Cat;Cat;Cat mfburgo@me.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Dog;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -psichel@sbcglobal.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Cat;Cat;Falcon;Cat;Cat;Cat;Cat;Cat +psichel@sbcglobal.net;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Cat;Cat;Falcon;Cat;Cat;Cat;Cat;Cat brainless@live.com;##null##;##null##;##null##;True;False;True;3.1415;3.1415;4;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat jmgomez@comcast.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat wsnyder@icloud.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -carcus@yahoo.ca;##null##;##null##;##null##;True;False;True;0.0;3.1415;2;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat +carcus@yahoo.ca;##null##;##null##;##null##;True;False;True;0;3.1415;2;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat dmouse@outlook.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;3;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat graham@mac.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat murdocj@comcast.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Falcon;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat reziac@att.net;##null##;##null##;##null##;True;False;True;1.61803;3.1415;4;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat -caronni@sbcglobal.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat +caronni@sbcglobal.net;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat hoyer@verizon.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat -amcuri@verizon.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat +amcuri@verizon.net;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat mstrout@live.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -osrin@verizon.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;4;1;Falcon;Dog;Falcon;Cat;Cat;Cat;Cat;Cat +osrin@verizon.net;##null##;##null##;##null##;False;False;True;0;3.1415;4;1;Falcon;Dog;Falcon;Cat;Cat;Cat;Cat;Cat geeber@hotmail.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;3;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat konit@aol.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;2;1;Falcon;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat mxiao@att.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat -ryanshaw@hotmail.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;1;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat +ryanshaw@hotmail.com;##null##;##null##;##null##;False;False;True;0;3.1415;1;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat bowmanbs@aol.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;1;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -yamla@hotmail.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;4;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat +yamla@hotmail.com;##null##;##null##;##null##;False;False;True;0;3.1415;4;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat ardagna@sbcglobal.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Falcon;Horse;Falcon;Cat;Cat;Cat;Cat;Cat -darin@outlook.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;4;1;Cat;Horse;Falcon;Cat;Cat;Cat;Cat;Cat -jmorris@me.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;3;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat -valdez@att.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;4;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat -haddawy@msn.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;1;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat +darin@outlook.com;##null##;##null##;##null##;False;False;True;0;3.1415;4;1;Cat;Horse;Falcon;Cat;Cat;Cat;Cat;Cat +jmorris@me.com;##null##;##null##;##null##;True;False;True;0;3.1415;3;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat +valdez@att.net;##null##;##null##;##null##;False;False;True;0;3.1415;4;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat +haddawy@msn.com;##null##;##null##;##null##;False;False;True;0;3.1415;1;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat biglou@sbcglobal.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Dog;Falcon;Cat;Cat;Cat;Cat;Cat;Cat pplinux@icloud.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat afeldspar@optonline.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;3;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat @@ -697,10 +698,10 @@ campbell@optonline.net;##null##;##null##;##null##;True;False;True;1.61803;3.1415 akoblin@outlook.com;##null##;##null##;##null##;True;False;True;3.1415;3.1415;4;1;Falcon;Cat;Falcon;Cat;Cat;Cat;Cat;Cat nwiger@yahoo.ca;##null##;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat stinson@sbcglobal.net;##null##;##null##;##null##;True;False;True;2.7182;3.1415;1;1;Dog;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat -daveed@me.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat +daveed@me.com;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat arachne@outlook.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat augusto@live.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;3;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -xtang@me.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat +xtang@me.com;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat itstatus@outlook.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat ebassi@optonline.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;3;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat kspiteri@yahoo.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;3;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat @@ -713,69 +714,69 @@ sisyphus@verizon.net;##null##;##null##;##null##;True;False;True;2.7182;3.1415;4; jandrese@live.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;3;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat jamuir@comcast.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Falcon;Dog;Falcon;Cat;Cat;Cat;Cat;Cat dobey@yahoo.ca;##null##;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat -rande@live.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;4;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat +rande@live.com;##null##;##null##;##null##;True;False;True;0;3.1415;4;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat dkasak@yahoo.com;##null##;##null##;##null##;True;False;True;1.61803;3.1415;2;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat greear@msn.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;1;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat thaljef@verizon.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;3;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat -sjmuir@gmail.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;1;1;Dog;Cat;Falcon;Cat;Cat;Cat;Cat;Cat +sjmuir@gmail.com;##null##;##null##;##null##;False;False;True;0;3.1415;1;1;Dog;Cat;Falcon;Cat;Cat;Cat;Cat;Cat joehall@sbcglobal.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;3;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat kronvold@optonline.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat jmorris@outlook.com;##null##;##null##;##null##;True;False;True;1.61803;3.1415;4;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat ajohnson@yahoo.ca;##null##;##null##;##null##;True;False;True;1.61803;3.1415;2;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat russotto@mac.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat pgolle@msn.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Dog;Dog;Falcon;Cat;Cat;Cat;Cat;Cat -mrdvt@aol.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat -starstuff@icloud.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Horse;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat +mrdvt@aol.com;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat +starstuff@icloud.com;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Horse;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat jesse@live.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;1;1;Falcon;Cat;Falcon;Cat;Cat;Cat;Cat;Cat luebke@outlook.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat emmanuel@optonline.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Dog;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -imightb@msn.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;4;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat +imightb@msn.com;##null##;##null##;##null##;True;False;True;0;3.1415;4;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat wbarker@sbcglobal.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat luvirini@hotmail.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;3;1;Falcon;Dog;Falcon;Cat;Cat;Cat;Cat;Cat ylchang@yahoo.com;##null##;##null##;##null##;True;False;True;1.61803;3.1415;1;1;Dog;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -elflord@icloud.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;3;1;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Cat +elflord@icloud.com;##null##;##null##;##null##;True;False;True;0;3.1415;3;1;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Cat scottzed@yahoo.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Falcon;Cat;Falcon;Cat;Cat;Cat;Cat;Cat -mcraigw@yahoo.ca;##null##;##null##;##null##;True;False;True;0.0;3.1415;4;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat +mcraigw@yahoo.ca;##null##;##null##;##null##;True;False;True;0;3.1415;4;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat nacho@icloud.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;2;1;Horse;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat rwelty@yahoo.ca;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat subir@aol.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat tbusch@yahoo.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;2;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat -rupak@yahoo.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;4;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat +rupak@yahoo.com;##null##;##null##;##null##;False;False;True;0;3.1415;4;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat giafly@aol.com;##null##;##null##;##null##;True;False;True;1.61803;3.1415;1;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -british@outlook.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;1;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +british@outlook.com;##null##;##null##;##null##;True;False;True;0;3.1415;1;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat hllam@icloud.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat fatelk@att.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat parsimony@verizon.net;##null##;##null##;##null##;True;False;True;2.7182;3.1415;2;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat tbeck@yahoo.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat citizenl@optonline.net;##null##;##null##;##null##;True;False;True;3.1415;3.1415;2;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat jimxugle@aol.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Cat;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat -starstuff@sbcglobal.net;##null##;##null##;##null##;True;False;True;0.0;3.1415;1;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -bader@icloud.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;2;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat +starstuff@sbcglobal.net;##null##;##null##;##null##;True;False;True;0;3.1415;1;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +bader@icloud.com;##null##;##null##;##null##;True;False;True;0;3.1415;2;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat starstuff@comcast.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat -satishr@yahoo.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;1;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +satishr@yahoo.com;##null##;##null##;##null##;True;False;True;0;3.1415;1;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat ilikered@gmail.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;3;1;Dog;Horse;Falcon;Cat;Cat;Cat;Cat;Cat -bader@mac.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;4;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat -yruan@msn.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +bader@mac.com;##null##;##null##;##null##;False;False;True;0;3.1415;4;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat +yruan@msn.com;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat skoch@outlook.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;1;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat bader@att.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat smallpaul@sbcglobal.net;##null##;##null##;##null##;True;False;True;1.61803;3.1415;3;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat -stern@verizon.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Dog;Dog;Falcon;Cat;Cat;Cat;Cat;Cat +stern@verizon.net;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Dog;Dog;Falcon;Cat;Cat;Cat;Cat;Cat feamster@outlook.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;2;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat mcnihil@sbcglobal.net;##null##;##null##;##null##;True;False;True;2.7182;3.1415;4;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat -firstpr@msn.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;2;1;Cat;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat -bmorrow@yahoo.ca;##null##;##null##;##null##;False;False;True;0.0;3.1415;1;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat +firstpr@msn.com;##null##;##null##;##null##;True;False;True;0;3.1415;2;1;Cat;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat +bmorrow@yahoo.ca;##null##;##null##;##null##;False;False;True;0;3.1415;1;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat laird@att.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Cat;Horse;Falcon;Cat;Cat;Cat;Cat;Cat -ingolfke@msn.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;4;1;Dog;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -irving@mac.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat +ingolfke@msn.com;##null##;##null##;##null##;True;False;True;0;3.1415;4;1;Dog;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +irving@mac.com;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat monopole@me.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Horse;Horse;Falcon;Cat;Cat;Cat;Cat;Cat -keiji@msn.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;1;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat -wortmanj@gmail.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Dog;Dog;Falcon;Cat;Cat;Cat;Cat;Cat +keiji@msn.com;##null##;##null##;##null##;False;False;True;0;3.1415;1;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat +wortmanj@gmail.com;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Dog;Dog;Falcon;Cat;Cat;Cat;Cat;Cat keijser@me.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;4;1;Cat;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat aschmitz@yahoo.ca;##null##;##null##;##null##;False;False;True;3.1415;3.1415;3;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat -tangsh@mac.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;1;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +tangsh@mac.com;##null##;##null##;##null##;True;False;True;0;3.1415;1;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat jdray@aol.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;1;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat -kewley@yahoo.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;1;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat -policies@verizon.net;##null##;##null##;##null##;True;False;True;0.0;3.1415;4;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat +kewley@yahoo.com;##null##;##null##;##null##;True;False;True;0;3.1415;1;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat +policies@verizon.net;##null##;##null##;##null##;True;False;True;0;3.1415;4;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat wayward@outlook.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;3;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat errxn@icloud.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat fglock@icloud.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat @@ -788,14 +789,14 @@ dmiller@hotmail.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;2; choset@live.com;##null##;##null##;##null##;True;False;True;1.61803;3.1415;3;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat dbrobins@aol.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;2;1;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Cat pizza@optonline.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat -noahb@att.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Dog;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +noahb@att.net;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Dog;Falcon;Cat;Cat;Cat;Cat;Cat;Cat jespley@yahoo.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;3;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat -jfriedl@hotmail.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;1;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat +jfriedl@hotmail.com;##null##;##null##;##null##;False;False;True;0;3.1415;1;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat arebenti@sbcglobal.net;##null##;##null##;##null##;True;False;True;2.7182;3.1415;4;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat eidac@yahoo.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Horse;Horse;Falcon;Cat;Cat;Cat;Cat;Cat jipsen@yahoo.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Horse;Horse;Falcon;Cat;Cat;Cat;Cat;Cat malin@me.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;3;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat -jramio@optonline.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;4;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat +jramio@optonline.net;##null##;##null##;##null##;False;False;True;0;3.1415;4;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat jsnover@outlook.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;1;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat martyloo@gmail.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;1;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat danny@yahoo.ca;##null##;##null##;##null##;False;False;True;2.7182;3.1415;3;1;Dog;Falcon;Cat;Cat;Cat;Cat;Cat;Cat @@ -815,37 +816,37 @@ knorr@hotmail.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;1;1; kawasaki@aol.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat ducasse@verizon.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Falcon;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat satishr@icloud.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;2;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat -drewf@comcast.net;##null##;##null##;##null##;True;False;True;0.0;3.1415;2;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat +drewf@comcast.net;##null##;##null##;##null##;True;False;True;0;3.1415;2;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat martyloo@yahoo.ca;##null##;##null##;##null##;True;False;True;2.7182;3.1415;4;1;Dog;Horse;Falcon;Cat;Cat;Cat;Cat;Cat lstein@live.com;##null##;##null##;##null##;True;False;True;3.1415;3.1415;3;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat nighthawk@me.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat debest@sbcglobal.net;##null##;##null##;##null##;True;False;True;2.7182;3.1415;3;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat -cyrus@yahoo.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +cyrus@yahoo.com;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat dogdude@att.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat yruan@optonline.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;1;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat bmidd@live.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat policies@att.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat -treit@att.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Cat;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat +treit@att.net;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Cat;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat animats@msn.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;4;1;Dog;Falcon;Cat;Cat;Cat;Cat;Cat;Cat kawasaki@sbcglobal.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -jramio@yahoo.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;4;1;Horse;Dog;Falcon;Cat;Cat;Cat;Cat;Cat +jramio@yahoo.com;##null##;##null##;##null##;True;False;True;0;3.1415;4;1;Horse;Dog;Falcon;Cat;Cat;Cat;Cat;Cat josephw@me.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;3;1;Cat;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat -rgarcia@me.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;1;1;Horse;Dog;Falcon;Cat;Cat;Cat;Cat;Cat +rgarcia@me.com;##null##;##null##;##null##;True;False;True;0;3.1415;1;1;Horse;Dog;Falcon;Cat;Cat;Cat;Cat;Cat ryanvm@gmail.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat rnewman@me.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;2;1;Horse;Horse;Falcon;Cat;Cat;Cat;Cat;Cat yangyan@mac.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Falcon;Cat;Falcon;Cat;Cat;Cat;Cat;Cat tubesteak@optonline.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Cat;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat munjal@sbcglobal.net;##null##;##null##;##null##;True;False;True;2.7182;3.1415;4;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat pgolle@live.com;##null##;##null##;##null##;True;False;True;1.61803;3.1415;2;1;Cat;Cat;Falcon;Cat;Cat;Cat;Cat;Cat -milton@icloud.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;4;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat +milton@icloud.com;##null##;##null##;##null##;False;False;True;0;3.1415;4;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat harryh@live.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat -howler@yahoo.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +howler@yahoo.com;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat drewf@verizon.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat cantu@optonline.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat leslie@optonline.net;##null##;##null##;##null##;True;False;True;3.1415;3.1415;2;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat mfleming@sbcglobal.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat nelson@att.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat -valdez@yahoo.ca;##null##;##null##;##null##;False;False;True;0.0;3.1415;1;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat +valdez@yahoo.ca;##null##;##null##;##null##;False;False;True;0;3.1415;1;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat jsmith@msn.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;3;1;Dog;Falcon;Cat;Cat;Cat;Cat;Cat;Cat djpig@mac.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat bader@hotmail.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Horse;Cat;Falcon;Cat;Cat;Cat;Cat;Cat @@ -854,24 +855,24 @@ dawnsong@msn.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;1;1; mcrawfor@optonline.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat pthomsen@icloud.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;1;1;Cat;Horse;Falcon;Cat;Cat;Cat;Cat;Cat raides@mac.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -ahuillet@icloud.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat -kostas@aol.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat -lridener@att.net;##null##;##null##;##null##;True;False;True;0.0;3.1415;3;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat +ahuillet@icloud.com;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat +kostas@aol.com;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat +lridener@att.net;##null##;##null##;##null##;True;False;True;0;3.1415;3;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat maneesh@outlook.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;3;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat sartak@yahoo.ca;##null##;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Horse;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat -rohitm@comcast.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat +rohitm@comcast.net;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat heidrich@mac.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;3;1;Falcon;Cat;Falcon;Cat;Cat;Cat;Cat;Cat koudas@comcast.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;3;1;Falcon;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat okroeger@me.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat -cgcra@me.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;4;1;Falcon;Cat;Falcon;Cat;Cat;Cat;Cat;Cat +cgcra@me.com;##null##;##null##;##null##;False;False;True;0;3.1415;4;1;Falcon;Cat;Falcon;Cat;Cat;Cat;Cat;Cat janusfury@yahoo.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;3;1;Dog;Horse;Falcon;Cat;Cat;Cat;Cat;Cat seurat@comcast.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat dhrakar@mac.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;4;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat delpino@icloud.com;##null##;##null##;##null##;True;False;True;3.1415;3.1415;1;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat bebing@msn.com;##null##;##null##;##null##;True;False;True;1.61803;3.1415;1;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat keiji@gmail.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Dog;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -okroeger@hotmail.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;1;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat -gward@yahoo.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;4;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +okroeger@hotmail.com;##null##;##null##;##null##;True;False;True;0;3.1415;1;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat +gward@yahoo.com;##null##;##null##;##null##;False;False;True;0;3.1415;4;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat jusdisgi@att.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat sakusha@comcast.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat rande@sbcglobal.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;3;1;Cat;Cat;Falcon;Cat;Cat;Cat;Cat;Cat @@ -881,14 +882,14 @@ marcs@yahoo.ca;##null##;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Hor juerd@mac.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat sethbrown@comcast.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat kdawson@verizon.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;4;1;Cat;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat -mfburgo@aol.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat +mfburgo@aol.com;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat crandall@hotmail.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;2;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat schwaang@msn.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;1;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -mrsam@icloud.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;4;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +mrsam@icloud.com;##null##;##null##;##null##;False;False;True;0;3.1415;4;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat szymansk@sbcglobal.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;4;1;Dog;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat epeeist@att.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat miyop@sbcglobal.net;##null##;##null##;##null##;True;False;True;3.1415;3.1415;1;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat -wainwrig@me.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat +wainwrig@me.com;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat pereinar@icloud.com;##null##;##null##;##null##;True;False;True;3.1415;3.1415;2;1;Falcon;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat killmenow@mac.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;4;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat dsowsy@mac.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;1;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat @@ -898,8 +899,8 @@ mkearl@hotmail.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;2;1; jandrese@live.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;3;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat openldap@msn.com;##null##;##null##;##null##;True;False;True;3.1415;3.1415;3;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat world@hotmail.com;##null##;##null##;##null##;True;False;True;1.61803;3.1415;1;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat -adamk@att.net;##null##;##null##;##null##;True;False;True;0.0;3.1415;3;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat -pdbaby@att.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +adamk@att.net;##null##;##null##;##null##;True;False;True;0;3.1415;3;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat +pdbaby@att.net;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat hellfire@comcast.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat firstpr@optonline.net;##null##;##null##;##null##;True;False;True;3.1415;3.1415;2;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat kenja@optonline.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat @@ -907,14 +908,14 @@ leslie@gmail.com;##null##;##null##;##null##;True;False;True;1.61803;3.1415;4;1;F bogjobber@optonline.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat hauma@verizon.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Dog;Cat;Falcon;Cat;Cat;Cat;Cat;Cat hoangle@mac.com;##null##;##null##;##null##;True;False;True;1.61803;3.1415;4;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat -nimaclea@msn.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat +nimaclea@msn.com;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat fraterk@icloud.com;##null##;##null##;##null##;True;False;True;1.61803;3.1415;4;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat ninenine@icloud.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat dogdude@att.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -campware@att.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;4;1;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Cat +campware@att.net;##null##;##null##;##null##;False;False;True;0;3.1415;4;1;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Cat amimojo@comcast.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat -karasik@hotmail.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat -yenya@hotmail.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Horse;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat +karasik@hotmail.com;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat +yenya@hotmail.com;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Horse;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat stevelim@gmail.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;3;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat dvdotnet@att.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat bonmots@verizon.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat @@ -924,8 +925,8 @@ alfred@verizon.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;1; multiplx@optonline.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;3;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat tjensen@optonline.net;##null##;##null##;##null##;True;False;True;1.61803;3.1415;3;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat dmath@yahoo.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;3;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat -kostas@yahoo.ca;##null##;##null##;##null##;False;False;True;0.0;3.1415;1;1;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Cat -carmena@gmail.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;4;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat +kostas@yahoo.ca;##null##;##null##;##null##;False;False;True;0;3.1415;1;1;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Cat +carmena@gmail.com;##null##;##null##;##null##;False;False;True;0;3.1415;4;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat terjesa@yahoo.ca;##null##;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat tjensen@sbcglobal.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Dog;Dog;Falcon;Cat;Cat;Cat;Cat;Cat schwaang@aol.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Dog;Horse;Falcon;Cat;Cat;Cat;Cat;Cat @@ -942,14 +943,14 @@ lpalmer@att.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Ca dgatwood@aol.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat jtorkbob@att.net;##null##;##null##;##null##;True;False;True;1.61803;3.1415;2;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat rfoley@comcast.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Dog;Cat;Falcon;Cat;Cat;Cat;Cat;Cat -andale@comcast.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;4;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat +andale@comcast.net;##null##;##null##;##null##;False;False;True;0;3.1415;4;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat mlewan@yahoo.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -ianbuck@yahoo.ca;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat -syrinx@live.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;1;1;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Cat +ianbuck@yahoo.ca;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Falcon;Horse;Cat;Cat;Cat;Cat;Cat;Cat +syrinx@live.com;##null##;##null##;##null##;False;False;True;0;3.1415;1;1;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Cat imightb@live.com;##null##;##null##;##null##;True;False;True;3.1415;3.1415;4;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat gozer@icloud.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat -gozer@outlook.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -shawnce@gmail.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;4;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +gozer@outlook.com;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +shawnce@gmail.com;##null##;##null##;##null##;True;False;True;0;3.1415;4;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat lauronen@att.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;3;1;Cat;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat raines@gmail.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;2;1;Falcon;Horse;Falcon;Cat;Cat;Cat;Cat;Cat jfriedl@icloud.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat;Cat @@ -959,25 +960,25 @@ thurston@verizon.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415; flaviog@aol.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Falcon;Cat;Falcon;Cat;Cat;Cat;Cat;Cat mnemonic@me.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;1;1;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Cat privcan@gmail.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Falcon;Cat;Falcon;Cat;Cat;Cat;Cat;Cat -mobileip@verizon.net;##null##;##null##;##null##;True;False;True;0.0;3.1415;3;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat +mobileip@verizon.net;##null##;##null##;##null##;True;False;True;0;3.1415;3;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat gbacon@yahoo.ca;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat -caronni@optonline.net;##null##;##null##;##null##;True;False;True;0.0;3.1415;1;1;Dog;Horse;Falcon;Cat;Cat;Cat;Cat;Cat +caronni@optonline.net;##null##;##null##;##null##;True;False;True;0;3.1415;1;1;Dog;Horse;Falcon;Cat;Cat;Cat;Cat;Cat tbeck@verizon.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat keijser@verizon.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat scotfl@verizon.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Cat ryanshaw@sbcglobal.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;1;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat -eimear@att.net;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat +eimear@att.net;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat noticias@comcast.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat -leocharre@yahoo.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;1;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat +leocharre@yahoo.com;##null##;##null##;##null##;True;False;True;0;3.1415;1;1;Horse;Dog;Cat;Cat;Cat;Cat;Cat;Cat killmenow@yahoo.ca;##null##;##null##;##null##;True;False;True;3.1415;3.1415;2;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat petersen@gmail.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Cat bdthomas@yahoo.ca;##null##;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat mavilar@msn.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat isaacson@msn.com;##null##;##null##;##null##;False;False;True;3.1415;3.1415;3;1;Horse;Horse;Cat;Cat;Cat;Cat;Cat;Cat -miyop@msn.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat +miyop@msn.com;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat bwcarty@hotmail.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;1;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat warrior@mac.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;1;1;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Cat -magusnet@hotmail.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;3;1;Cat;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat +magusnet@hotmail.com;##null##;##null##;##null##;False;False;True;0;3.1415;3;1;Cat;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat crowemojo@verizon.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Falcon;Cat;Falcon;Cat;Cat;Cat;Cat;Cat crypt@optonline.net;##null##;##null##;##null##;True;False;True;1.61803;3.1415;1;1;Cat;Dog;Falcon;Cat;Cat;Cat;Cat;Cat kempsonc@mac.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Falcon;Cat;Falcon;Cat;Cat;Cat;Cat;Cat @@ -986,28 +987,27 @@ noahb@yahoo.ca;##null##;##null##;##null##;False;False;True;2.7182;3.1415;3;1;Fal symbolic@mac.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;3;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat wsnyder@yahoo.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;1;1;Dog;Falcon;Cat;Cat;Cat;Cat;Cat;Cat pdbaby@yahoo.ca;##null##;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Cat;Horse;Falcon;Cat;Cat;Cat;Cat;Cat -jfriedl@yahoo.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;4;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat +jfriedl@yahoo.com;##null##;##null##;##null##;False;False;True;0;3.1415;4;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat openldap@gmail.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Falcon;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat jwarren@optonline.net;##null##;##null##;##null##;True;False;True;2.7182;3.1415;1;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat dsugal@verizon.net;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat kayvonf@aol.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Cat;Horse;Cat;Cat;Cat;Cat;Cat;Cat -nasarius@mac.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat +nasarius@mac.com;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Dog;Cat;Cat;Cat;Cat;Cat;Cat;Cat bolow@mac.com;##null##;##null##;##null##;True;False;True;1.61803;3.1415;2;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat tbmaddux@hotmail.com;##null##;##null##;##null##;True;False;True;2.7182;3.1415;2;1;Horse;Falcon;Cat;Cat;Cat;Cat;Cat;Cat maradine@aol.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat breegster@gmail.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;3;1;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat breegster@sbcglobal.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;4;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat openldap@gmail.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;4;1;Falcon;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat -jshirley@gmail.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;2;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat +jshirley@gmail.com;##null##;##null##;##null##;False;False;True;0;3.1415;2;1;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Cat tfinniga@msn.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;4;1;Dog;Horse;Cat;Cat;Cat;Cat;Cat;Cat delpino@mac.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;3;1;Falcon;Cat;Cat;Cat;Cat;Cat;Cat;Cat stecoop@live.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;2;1;Falcon;Dog;Cat;Cat;Cat;Cat;Cat;Cat jnolan@comcast.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;3;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat -jhardin@yahoo.com;##null##;##null##;##null##;True;False;True;0.0;3.1415;1;1;Dog;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat +jhardin@yahoo.com;##null##;##null##;##null##;True;False;True;0;3.1415;1;1;Dog;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat teverett@sbcglobal.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;2;1;Horse;Horse;Falcon;Cat;Cat;Cat;Cat;Cat wsnyder@comcast.net;##null##;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Dog;Falcon;Cat;Cat;Cat;Cat;Cat;Cat treeves@msn.com;##null##;##null##;##null##;False;False;True;1.61803;3.1415;2;1;Cat;Falcon;Cat;Cat;Cat;Cat;Cat;Cat -garland@outlook.com;##null##;##null##;##null##;False;False;True;0.0;3.1415;1;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat +garland@outlook.com;##null##;##null##;##null##;False;False;True;0;3.1415;1;1;Horse;Cat;Cat;Cat;Cat;Cat;Cat;Cat ullman@comcast.net;##null##;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Horse;Falcon;Falcon;Cat;Cat;Cat;Cat;Cat sumdumass@outlook.com;##null##;##null##;##null##;False;False;True;2.7182;3.1415;1;1;Dog;Horse;Falcon;Cat;Cat;Cat;Cat;Cat -cat+dog@example.com;cat+dog@example.com;##null##;##null##;False;False;True;3.1415;3.1415;1;1;Dog;Cat;Falcon;Dog;Cat;Cat;Dog;Dog diff --git a/test/data/testmatrix_and_or.csv b/test/data/testmatrix_and_or.csv new file mode 100644 index 0000000..5a149f4 --- /dev/null +++ b/test/data/testmatrix_and_or.csv @@ -0,0 +1,15 @@ +Identifier;Email;Country;Custom1;mainFeature;dependentFeature;emailAnd;emailOr +##null##;;;;public;Chicken;Cat;Cat +;;;;public;Chicken;Cat;Cat +jane@example.com;jane@example.com;##null##;##null##;public;Chicken;Cat;Jane +john@example.com;john@example.com;##null##;##null##;public;Chicken;Cat;John +a@example.com;a@example.com;USA;##null##;target;Cat;Cat;Cat +mark@example.com;mark@example.com;USA;##null##;target;Dog;Cat;Mark +nora@example.com;nora@example.com;USA;##null##;target;Falcon;Cat;Cat +stern@msn.com;stern@msn.com;USA;##null##;target;Horse;Cat;Cat +jane@sensitivecompany.com;jane@sensitivecompany.com;England;##null##;private;Chicken;Dog;Jane +anna@sensitivecompany.com;anna@sensitivecompany.com;France;##null##;private;Chicken;Cat;Cat +jane@sensitivecompany.com;jane@sensitivecompany.com;england;##null##;public;Chicken;Dog;Jane +jane;jane;##null##;##null##;public;Chicken;Cat;Cat +@sensitivecompany.com;@sensitivecompany.com;##null##;##null##;public;Chicken;Cat;Cat +jane.sensitivecompany.com;jane.sensitivecompany.com;##null##;##null##;public;Chicken;Cat;Cat diff --git a/test/data/testmatrix_comparators_v6.csv b/test/data/testmatrix_comparators_v6.csv new file mode 100644 index 0000000..d53efb5 --- /dev/null +++ b/test/data/testmatrix_comparators_v6.csv @@ -0,0 +1,24 @@ +Identifier;Email;Country;Custom1;boolTrueIn202304;stringEqualsDogDefaultCat;stringEqualsCleartextDogDefaultCat;stringDoseNotEqualDogDefaultCat;stringNotEqualsCleartextDogDefaultCat;stringStartsWithDogDefaultCat;stringNotStartsWithDogDefaultCat;stringEndsWithDogDefaultCat;stringNotEndsWithDogDefaultCat;arrayContainsDogDefaultCat;arrayDoesNotContainDogDefaultCat;arrayContainsCaseCheckDogDefaultCat;arrayDoesNotContainCaseCheckDogDefaultCat;customPercentageAttribute;missingPercentageAttribute;countryPercentageAttribute;stringContainsAnyOfDogDefaultCat;stringNotContainsAnyOfDogDefaultCat;stringStartsWithAnyOfDogDefaultCat;stringStartsWithAnyOfCleartextDogDefaultCat;stringNotStartsWithAnyOfDogDefaultCat;stringNotStartsWithAnyOfCleartextDogDefaultCat;stringEndsWithAnyOfDogDefaultCat;stringEndsWithAnyOfCleartextDogDefaultCat;stringNotEndsWithAnyOfDogDefaultCat;stringNotEndsWithAnyOfCleartextDogDefaultCat;stringArrayContainsAnyOfDogDefaultCat;stringArrayContainsAnyOfCleartextDogDefaultCat;stringArrayNotContainsAnyOfDogDefaultCat;stringArrayNotContainsAnyOfCleartextDogDefaultCat +##null##;;;;False;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Chicken;Chicken;Chicken;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat +;;;;False;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Chicken;Chicken;Chicken;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat;Cat +a@configcat.com;a@configcat.com;##null##;##null##;False;Dog;Dog;Dog;Dog;Dog;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Chicken;NotFound;Chicken;Cat;Dog;Dog;Dog;Cat;Cat;Cat;Cat;Dog;Dog;Cat;Cat;Cat;Cat +b@configcat.com;b@configcat.com;Hungary;0;False;Cat;Cat;Cat;Cat;Cat;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Horse;NotFound;Falcon;Cat;Dog;Dog;Dog;Cat;Cat;Cat;Cat;Dog;Dog;Cat;Cat;Cat;Cat +c@configcat.com;c@configcat.com;United Kingdom;1680307199.9;False;Cat;Cat;Dog;Dog;Cat;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Falcon;NotFound;Falcon;Cat;Dog;Cat;Cat;Dog;Dog;Cat;Cat;Dog;Dog;Cat;Cat;Cat;Cat +anna@configcat.com;anna@configcat.com;Hungary;1681118000.56;True;Cat;Cat;Dog;Dog;Dog;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Falcon;NotFound;Falcon;Cat;Dog;Cat;Cat;Dog;Dog;Cat;Cat;Dog;Dog;Cat;Cat;Cat;Cat +bogjobber@verizon.net;bogjobber@verizon.net;##null##;1682899200.1;False;Cat;Cat;Dog;Dog;Cat;Dog;Cat;Dog;Cat;Cat;Cat;Cat;Horse;Chicken;Chicken;Dog;Cat;Cat;Cat;Dog;Dog;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Cat +cliffordj@aol.com;cliffordj@aol.com;Austria;1682999200;False;Cat;Cat;Dog;Dog;Cat;Dog;Cat;Dog;Cat;Cat;Cat;Cat;Falcon;Chicken;Falcon;Dog;Cat;Cat;Cat;Dog;Dog;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Cat +reader@configcat.com;reader@configcat.com;Bahamas;read,execute;False;Cat;Cat;Dog;Dog;Cat;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Falcon;NotFound;Falcon;Cat;Dog;Cat;Cat;Dog;Dog;Cat;Cat;Dog;Dog;Cat;Cat;Cat;Cat +writer@configcat.com;writer@configcat.com;Belgium;write, execute;False;Cat;Cat;Dog;Dog;Cat;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Horse;NotFound;Horse;Cat;Dog;Cat;Cat;Dog;Dog;Cat;Cat;Dog;Dog;Cat;Cat;Cat;Cat +reader@configcat.com;reader@configcat.com;Canada;execute, Read;False;Cat;Cat;Dog;Dog;Cat;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Horse;NotFound;Horse;Cat;Dog;Cat;Cat;Dog;Dog;Cat;Cat;Dog;Dog;Cat;Cat;Cat;Cat +writer@configcat.com;writer@configcat.com;China;Write;False;Cat;Cat;Dog;Dog;Cat;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Falcon;NotFound;Horse;Cat;Dog;Cat;Cat;Dog;Dog;Cat;Cat;Dog;Dog;Cat;Cat;Cat;Cat +admin@configcat.com;admin@configcat.com;France;read, write,execute;False;Cat;Cat;Dog;Dog;Dog;Cat;Dog;Cat;Cat;Cat;Cat;Cat;Falcon;NotFound;Horse;Cat;Dog;Cat;Cat;Dog;Dog;Cat;Cat;Dog;Dog;Cat;Cat;Cat;Cat +user@configcat.com;user@configcat.com;Greece;,execute;False;Cat;Cat;Dog;Dog;Cat;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Falcon;NotFound;Horse;Cat;Dog;Cat;Cat;Dog;Dog;Cat;Cat;Dog;Dog;Cat;Cat;Cat;Cat +reader@configcat.com;reader@configcat.com;Bahamas;["read","execute"];False;Cat;Cat;Dog;Dog;Cat;Dog;Dog;Cat;Dog;Dog;Cat;Dog;Falcon;NotFound;Falcon;Cat;Dog;Cat;Cat;Dog;Dog;Cat;Cat;Dog;Dog;Dog;Dog;Cat;Cat +writer@configcat.com;writer@configcat.com;Belgium;["write", "execute"];False;Cat;Cat;Dog;Dog;Cat;Dog;Dog;Cat;Cat;Cat;Cat;Dog;Falcon;NotFound;Horse;Cat;Dog;Cat;Cat;Dog;Dog;Cat;Cat;Dog;Dog;Dog;Dog;Cat;Cat +reader@configcat.com;reader@configcat.com;Canada;["execute", "Read"];False;Cat;Cat;Dog;Dog;Cat;Dog;Dog;Cat;Cat;Dog;Dog;Dog;Horse;NotFound;Horse;Cat;Dog;Cat;Cat;Dog;Dog;Cat;Cat;Dog;Dog;Dog;Dog;Cat;Cat +writer@configcat.com;writer@configcat.com;China;["Write"];False;Cat;Cat;Dog;Dog;Cat;Dog;Dog;Cat;Cat;Dog;Cat;Cat;Horse;NotFound;Horse;Cat;Dog;Cat;Cat;Dog;Dog;Cat;Cat;Dog;Dog;Cat;Cat;Dog;Dog +admin@configcat.com;admin@configcat.com;France;["read", "write","execute"];False;Cat;Cat;Dog;Dog;Dog;Cat;Dog;Cat;Dog;Cat;Cat;Dog;Falcon;NotFound;Horse;Cat;Dog;Cat;Cat;Dog;Dog;Cat;Cat;Dog;Dog;Dog;Dog;Cat;Cat +admin@configcat.com;admin@configcat.com;France;["Read", "Write", "execute"];False;Cat;Cat;Dog;Dog;Dog;Cat;Dog;Cat;Cat;Dog;Dog;Cat;Horse;NotFound;Horse;Cat;Dog;Cat;Cat;Dog;Dog;Cat;Cat;Dog;Dog;Dog;Dog;Cat;Cat +admin@configcat.com;admin@configcat.com;France;["Read", "Write", "eXecute"];False;Cat;Cat;Dog;Dog;Dog;Cat;Dog;Cat;Cat;Dog;Dog;Cat;Falcon;NotFound;Horse;Cat;Dog;Cat;Cat;Dog;Dog;Cat;Cat;Dog;Dog;Cat;Cat;Dog;Dog +user@configcat.com;user@configcat.com;Greece;["","execute"];False;Cat;Cat;Dog;Dog;Cat;Dog;Dog;Cat;Cat;Dog;Cat;Dog;Horse;NotFound;Horse;Cat;Dog;Cat;Cat;Dog;Dog;Cat;Cat;Dog;Dog;Dog;Dog;Cat;Cat +user@configcat.com;user@configcat.com;Monaco;,null, ,,nil, None;False;Cat;Cat;Dog;Dog;Cat;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Falcon;NotFound;Horse;Cat;Dog;Cat;Cat;Dog;Dog;Cat;Cat;Dog;Dog;Cat;Cat;Cat;Cat diff --git a/test/data/testmatrix_prerequisite_flag.csv b/test/data/testmatrix_prerequisite_flag.csv new file mode 100644 index 0000000..dcf68f4 --- /dev/null +++ b/test/data/testmatrix_prerequisite_flag.csv @@ -0,0 +1,5 @@ +Identifier;Email;Country;Custom1;mainBoolFlag;mainStringFlag;mainIntFlag;mainDoubleFlag;stringDependsOnBool;stringDependsOnString;stringDependsOnStringCaseCheck;stringDependsOnInt;stringDependsOnDouble;stringDependsOnDoubleIntValue;boolDependsOnBool;intDependsOnBool;doubleDependsOnBool;boolDependsOnBoolDependsOnBool;mainBoolFlagEmpty;stringDependsOnEmptyBool;stringInverseDependsOnEmptyBool;mainBoolFlagInverse;boolDependsOnBoolInverse +##null##;;;;True;public;42;3.14;Dog;Cat;Cat;Cat;Cat;Cat;True;1;1.1;False;True;EmptyOn;EmptyOn;False;True +;;;;True;public;42;3.14;Dog;Cat;Cat;Cat;Cat;Cat;True;1;1.1;False;True;EmptyOn;EmptyOn;False;True +john@sensitivecompany.com;john@sensitivecompany.com;##null##;##null##;False;private;2;0.1;Cat;Dog;Cat;Dog;Dog;Cat;False;42;3.14;True;True;EmptyOn;EmptyOn;True;False +jane@example.com;jane@example.com;##null##;##null##;True;public;42;3.14;Dog;Cat;Cat;Cat;Cat;Cat;True;1;1.1;False;True;EmptyOn;EmptyOn;False;True diff --git a/test/data/testmatrix_segments.csv b/test/data/testmatrix_segments.csv new file mode 100644 index 0000000..b59ba3a --- /dev/null +++ b/test/data/testmatrix_segments.csv @@ -0,0 +1,6 @@ +Identifier;Email;Country;Custom1;developerAndBetaUserSegment;developerAndBetaUserCleartextSegment;notDeveloperAndNotBetaUserSegment;notDeveloperAndNotBetaUserCleartextSegment +##null##;;;;False;False;False;False +;;;;False;False;False;False +john@example.com;john@example.com;##null##;##null##;False;False;False;False +jane@example.com;jane@example.com;##null##;##null##;False;False;False;False +kate@example.com;kate@example.com;##null##;##null##;True;True;True;True diff --git a/test/data/testmatrix_segments_old.csv b/test/data/testmatrix_segments_old.csv new file mode 100644 index 0000000..9fc605e --- /dev/null +++ b/test/data/testmatrix_segments_old.csv @@ -0,0 +1,6 @@ +Identifier;Email;Country;Custom1;featureWithSegmentTargeting;featureWithSegmentTargetingCleartext;featureWithNegatedSegmentTargeting;featureWithNegatedSegmentTargetingCleartext;featureWithSegmentTargetingInverse;featureWithSegmentTargetingInverseCleartext;featureWithNegatedSegmentTargetingInverse;featureWithNegatedSegmentTargetingInverseCleartext +##null##;;;;False;False;False;False;False;False;False;False +;;;;False;False;False;False;False;False;False;False +john@example.com;john@example.com;##null##;##null##;True;True;False;False;False;False;True;True +jane@example.com;jane@example.com;##null##;##null##;True;True;False;False;False;False;True;True +kate@example.com;kate@example.com;##null##;##null##;False;False;True;True;True;True;False;False diff --git a/test/data/testmatrix_semantic_2.csv b/test/data/testmatrix_semantic_2.csv index e0da30c..449f863 100644 --- a/test/data/testmatrix_semantic_2.csv +++ b/test/data/testmatrix_semantic_2.csv @@ -92,4 +92,4 @@ dontcare;;;50.60.71-patch2+metadata;>= 50.60.71-patch2 dontcare;;;50.60.71-patch1;>= 50.60.71-patch1+metadata dontcare;;;50.60.71-patch1+anothermetadata;>= 50.60.71-patch1+metadata dontcare;;;40.0.0-patch;>= 40.0.0-patch -dontcare;;;30.0.0-beta;>= 30.0.0-alpha \ No newline at end of file +dontcare;;;30.0.0-beta;>= 30.0.0-alpha diff --git a/test/data/testmatrix_unicode.csv b/test/data/testmatrix_unicode.csv new file mode 100644 index 0000000..e5b01de --- /dev/null +++ b/test/data/testmatrix_unicode.csv @@ -0,0 +1,14 @@ +Identifier;Email;Country;馃唭馃叴馃唶馃唭;boolTextEqualsHashed;boolTextEqualsCleartext;boolTextNotEqualsHashed;boolTextNotEqualsCleartext;boolIsOneOfHashed;boolIsOneOfCleartext;boolIsNotOneOfHashed;boolIsNotOneOfCleartext;boolStartsWithHashed;boolStartsWithCleartext;boolNotStartsWithHashed;boolNotStartsWithCleartext;boolEndsWithHashed;boolEndsWithCleartext;boolNotEndsWithHashed;boolNotEndsWithCleartext;boolContainsCleartext;boolNotContainsCleartext;boolArrayContainsHashed;boolArrayContainsCleartext;boolArrayNotContainsHashed;boolArrayNotContainsCleartext +1;;;蕜菬占茍蕪 榷蓻蛹榷;True;True;False;False;False;False;True;True;False;False;True;True;False;False;True;True;False;True;False;False;False;False +1;;;蕜a占茍蕪 榷蓻蛹榷;False;False;True;True;False;False;True;True;False;False;True;True;False;False;True;True;False;True;False;False;False;False +1;;;脕RV脥ZT虐R艕 t眉k枚rf煤r贸g茅p;False;False;True;True;True;True;False;False;True;True;False;False;True;True;False;False;True;False;False;False;False;False +1;;;谩rv铆zt疟r艖 t眉k枚rf煤r贸g茅p;False;False;True;True;False;False;True;True;False;False;True;True;True;True;False;False;True;False;False;False;False;False +1;;;脕RV脥ZT虐R艕 T脺K脰RF脷R脫G脡P;False;False;True;True;False;False;True;True;True;True;False;False;False;False;True;True;True;False;False;False;False;False +1;;;谩rv铆zt疟r艖 T脺K脰RF脷R脫G脡P;False;False;True;True;False;False;True;True;False;False;True;True;False;False;True;True;False;True;False;False;False;False +1;;;u饾枔饾枎饾枅饾枖饾枆e;False;False;True;True;True;True;False;False;True;True;False;False;True;True;False;False;True;False;False;False;False;False +;;;饾枤饾枔饾枎饾枅饾枖饾枆e;False;False;True;True;False;False;True;True;False;False;True;True;True;True;False;False;True;False;False;False;False;False +;;;u饾枔饾枎饾枅饾枖饾枆饾枈;False;False;True;True;False;False;True;True;True;True;False;False;False;False;True;True;True;False;False;False;False;False +;;;饾枤饾枔饾枎饾枅饾枖饾枆饾枈;False;False;True;True;False;False;True;True;False;False;True;True;False;False;True;True;False;True;False;False;False;False +1;;;["脕RV脥ZT虐R艕 t眉k枚rf煤r贸g茅p", "unicode"];False;False;True;True;False;False;True;True;False;False;True;True;False;False;True;True;True;False;True;True;False;False +1;;;["脕RV脥ZT虐R艕", "t眉k枚rf煤r贸g茅p", "u饾枔饾枎饾枅饾枖饾枆e"];False;False;True;True;False;False;True;True;False;False;True;True;False;False;True;True;True;False;True;True;False;False +1;;;["脕RV脥ZT虐R艕", "t眉k枚rf煤r贸g茅p", "unicode"];False;False;True;True;False;False;True;True;False;False;True;True;False;False;True;True;True;False;False;False;True;True diff --git a/test/data/testmatrix_variationId.csv b/test/data/testmatrix_variationid.csv similarity index 100% rename from test/data/testmatrix_variationId.csv rename to test/data/testmatrix_variationid.csv diff --git a/test/helpers/ConfigLocation.ts b/test/helpers/ConfigLocation.ts new file mode 100644 index 0000000..920ebba --- /dev/null +++ b/test/helpers/ConfigLocation.ts @@ -0,0 +1,83 @@ +import * as fs from "fs"; +import * as path from "path"; +import * as util from "util"; +import { ManualPollOptions } from "../../src/ConfigCatClientOptions"; +import { ManualPollConfigService } from "../../src/ManualPollConfigService"; +import { Config } from "../../src/ProjectConfig"; +import { HttpConfigFetcher } from "./HttpConfigFetcher"; +import { sdkType, sdkVersion } from "./utils"; + +const configCache: { [location: string]: Promise } = {}; + +export abstract class ConfigLocation { + abstract getRealLocation(): string; + + abstract fetchConfigAsync(): Promise; + + fetchConfigCachedAsync(): Promise { + const location = this.getRealLocation(); + let configPromise = configCache[location]; + if (!configPromise) { + configCache[location] = configPromise = this.fetchConfigAsync(); + } + return configPromise; + } +} + +export class CdnConfigLocation extends ConfigLocation { + private $options?: ManualPollOptions; + get options(): ManualPollOptions { + return this.$options ??= new ManualPollOptions(this.sdkKey, sdkType, sdkVersion, { + baseUrl: this.baseUrl ?? "https://cdn-eu.configcat.com" + }); + } + + constructor( + readonly sdkKey: string, + readonly baseUrl?: string, + ) { + super(); + } + + getRealLocation(): string { + const url = this.options.getUrl(); + const index = url.lastIndexOf("?"); + return index >= 0 ? url.slice(0, index) : url; + } + + async fetchConfigAsync(): Promise { + const configFetcher = new HttpConfigFetcher(); + const configService = new ManualPollConfigService(configFetcher, this.options); + + const [fetchResult, projectConfig] = await configService.refreshConfigAsync(); + if (!fetchResult.isSuccess) { + throw new Error("Could not fetch config from CDN: " + fetchResult.errorMessage); + } + return projectConfig.config!; + } + + toString(): string { + return this.sdkKey + (this.baseUrl ? ` (${this.baseUrl})` : ""); + } +} + +export class LocalFileConfigLocation extends ConfigLocation { + filePath: string; + + constructor(...paths: ReadonlyArray) { + super(); + this.filePath = path.join(...paths); + } + + getRealLocation(): string { return this.filePath; } + + async fetchConfigAsync(): Promise { + const configJson = await util.promisify(fs.readFile)(this.filePath, "utf8"); + const parsedObject = JSON.parse(configJson); + return new Config(parsedObject); + } + + toString(): string { + return this.getRealLocation(); + } +} diff --git a/test/helpers/HttpConfigFetcher.ts b/test/helpers/HttpConfigFetcher.ts new file mode 100644 index 0000000..efbbb8a --- /dev/null +++ b/test/helpers/HttpConfigFetcher.ts @@ -0,0 +1,99 @@ +import * as http from "http"; +import * as https from "https"; +import * as tunnel from "tunnel"; +import { URL } from "url"; +import { FetchError, FormattableLogMessage, IConfigFetcher, LogLevel } from "../../src"; +import type { IFetchResponse, OptionsBase } from "../../src"; + +export class HttpConfigFetcher implements IConfigFetcher { + private handleResponse(response: http.IncomingMessage, resolve: (value: IFetchResponse) => void, reject: (reason?: any) => void) { + try { + const { statusCode, statusMessage: reasonPhrase } = response as { statusCode: number; statusMessage: string }; + + if (statusCode === 200) { + const eTag = response.headers["etag"]; + const chunks: any[] = []; + response + .on("data", chunk => chunks.push(chunk)) + .on("end", () => { + try { + resolve({ statusCode, reasonPhrase, eTag, body: Buffer.concat(chunks).toString() }); + } + catch (err) { + reject(err); + } + }) + .on("error", err => reject(new FetchError("failure", err))); + } + else { + // Consume response data to free up memory + response.resume(); + + resolve({ statusCode, reasonPhrase }); + } + } + catch (err) { + reject(err); + } + } + + fetchLogic(options: OptionsBase, lastEtag: string): Promise { + return new Promise((resolve, reject) => { + try { + options.logger.debug("HttpConfigFetcher.fetchLogic() called."); + const baseUrl = options.getUrl(); + const isBaseUrlSecure = baseUrl.startsWith("https"); + let agent: any; + if (options.proxy) { + try { + const proxy: URL = new URL(options.proxy); + let agentFactory: any; + if (proxy.protocol === "https:") { + agentFactory = isBaseUrlSecure ? tunnel.httpsOverHttps : tunnel.httpOverHttps; + } + else { + agentFactory = isBaseUrlSecure ? tunnel.httpsOverHttp : tunnel.httpOverHttp; + } + agent = agentFactory({ + proxy: { + host: proxy.hostname, + port: proxy.port, + proxyAuth: (proxy.username && proxy.password) ? `${proxy.username}:${proxy.password}` : null + } + }); + } + catch (err) { + options.logger.log(LogLevel.Error, 0, FormattableLogMessage.from("PROXY")`Failed to parse \`options.proxy\`: '${options.proxy}'.`, err); + } + } + + const requestOptions = { + agent, + headers: { + "User-Agent": options.clientVersion, + "If-None-Match": lastEtag ?? null + }, + timeout: options.requestTimeoutMs, + }; + options.logger.debug(JSON.stringify(requestOptions)); + + const request = (isBaseUrlSecure ? https : http).get(baseUrl, requestOptions, response => this.handleResponse(response, resolve, reject)) + .on("timeout", () => { + try { + request.destroy(); + } + finally { + reject(new FetchError("timeout", options.requestTimeoutMs)); + } + }) + .on("error", err => reject(new FetchError("failure", err))) + .end(); + } + catch (err) { + reject(err); + } + }); + } +} + +export default IConfigFetcher; diff --git a/test/helpers/fakes.ts b/test/helpers/fakes.ts index bd2a158..201da74 100644 --- a/test/helpers/fakes.ts +++ b/test/helpers/fakes.ts @@ -8,14 +8,14 @@ import { ProjectConfig } from "../../src/ProjectConfig"; import { delay } from "../../src/Utils"; export class FakeLogger implements IConfigCatLogger { - messages: [LogLevel, LogEventId, string, any?][] = []; + events: [LogLevel, LogEventId, LogMessage, any?][] = []; constructor(public level = LogLevel.Info) { } - reset(): void { this.messages.splice(0); } + reset(): void { this.events.splice(0); } log(level: LogLevel, eventId: number, message: LogMessage, exception?: any): void { - this.messages.push([level, eventId, message.toString(), exception]); + this.events.push([level, eventId, message, exception]); } } @@ -88,7 +88,7 @@ export class FakeExternalCacheWithInitialData implements IConfigCatCache { throw new Error("Method not implemented."); } get(key: string): string | Promise | null | undefined { - const cachedJson = '{"f": { "debug": { "v": true, "i": "abcdefgh", "t": 0, "p": [], "r": [] } } }'; + const cachedJson = '{"f":{"debug":{"t":0,"v":{"b":true},"i":"abcdefgh"}}}'; const config = new ProjectConfig(cachedJson, JSON.parse(cachedJson), (new Date().getTime()) - this.expirationDelta, "\"ETAG\""); return ProjectConfig.serialize(config); } @@ -132,7 +132,7 @@ export class FakeConfigFetcherBase implements IConfigFetcher { export class FakeConfigFetcher extends FakeConfigFetcherBase { static get configJson(): string { - return '{"f": { "debug": { "v": true, "i": "abcdefgh", "t": 0, "p": [], "r": [] } } }'; + return '{"f":{"debug":{"t":0,"v":{"b":true},"i":"abcdefgh"}}}'; } ["constructor"]!: typeof FakeConfigFetcher; @@ -146,25 +146,31 @@ export class FakeConfigFetcher extends FakeConfigFetcherBase { export class FakeConfigFetcherWithTwoKeys extends FakeConfigFetcher { static get configJson(): string { - return '{"f": { "debug": { "v": true, "i": "abcdefgh", "t": 0, "p": [], "r": [] }, "debug2": { "v": true, "i": "12345678", "t": 0, "p": [], "r": [] } } }'; + return '{"f":{"debug":{"t":0,"v":{"b":true},"i":"abcdefgh"},"debug2":{"t":0,"v":{"b":true},"i":"12345678"}}}'; } } export class FakeConfigFetcherWithTwoCaseSensitiveKeys extends FakeConfigFetcher { static get configJson(): string { - return '{"f": { "debug": { "v": "debug", "i": "abcdefgh", "t": 1, "p": [], "r": [{ "o":0, "a":"CUSTOM", "t":0, "c":"c", "v":"UPPER-VALUE", "i":"6ada5ff2"}, { "o":1, "a":"custom", "t":0, "c":"c", "v":"lower-value", "i":"6ada5ff2"}] }, "DEBUG": { "v": "DEBUG", "i": "12345678", "t": 1, "p": [], "r": [] } } }'; + return '{"f":{"DEBUG":{"t":1,"v":{"s":"DEBUG"},"i":"12345678"},"debug":{"t":1,"r":[{"c":[{"u":{"a":"CUSTOM","c":0,"l":["c"]}}],"s":{"v":{"s":"UPPER-VALUE"},"i":"6ada5ff2"}},{"c":[{"u":{"a":"custom","c":0,"l":["c"]}}],"s":{"v":{"s":"lower-value"},"i":"6ada5ff2"}}],"v":{"s":"debug"},"i":"abcdefgh"}}}'; } } export class FakeConfigFetcherWithTwoKeysAndRules extends FakeConfigFetcher { static get configJson(): string { - return '{"f": { "debug": { "v": "def", "i": "abcdefgh", "t": 1, "p": [], "r": [{ "o":0, "a":"a", "t":1, "c":"abcd", "v":"value", "i":"6ada5ff2"}] }, "debug2": { "v": "def", "i": "12345678", "t": 1, "p": [{"o":0, "v":"value1", "p":50, "i":"d227b334" }, { "o":1, "v":"value2", "p":50, "i":"622f5d07" }], "r": [] } } }'; + return '{"f":{"debug":{"t":1,"r":[{"c":[{"u":{"a":"a","c":1,"l":["abcd"]}}],"s":{"v":{"s":"value"},"i":"6ada5ff2"}}],"v":{"s":"def"},"i":"abcdefgh"},"debug2":{"t":1,"p":[{"p":50,"v":{"s":"value1"},"i":"d227b334"},{"p":50,"v":{"s":"value2"},"i":"622f5d07"}],"v":{"s":"def"},"i":"12345678"}}}'; + } +} + +export class FakeConfigFetcherWithPercentageOptionsWithinTargetingRule extends FakeConfigFetcher { + static get configJson(): string { + return '{"f":{"debug":{"t":1,"r":[{"c":[{"u":{"a":"a","c":1,"l":["abcd"]}}],"s":{"v":{"s":"value"},"i":"6ada5ff2"}},{"c":[{"u":{"a":"a","c":0,"l":["abcd"]}}],"p":[{"p":50,"v":{"s":"value1"},"i":"d227b334"},{"p":50,"v":{"s":"value2"},"i":"622f5d07"}]}],"v":{"s":"def"},"i":"abcdefgh"}}}'; } } export class FakeConfigFetcherWithRules extends FakeConfigFetcher { static get configJson(): string { - return '{"f": { "debug": { "v": "defaultValue", "i": "defaultVariationId", "t": 0, "p": [], "r": [{ "o":0, "a":"eyeColor", "t":0, "c":"red", "v":"redValue", "i":"redVariationId"}, { "o":1, "a":"eyeColor", "t":0, "c":"blue", "v":"blueValue", "i":"blueVariationId"}] } } }'; + return '{"f":{"debug":{"t":1,"r":[{"c":[{"u":{"a":"eyeColor","c":0,"l":["red"]}}],"s":{"v":{"s":"redValue"},"i":"redVariationId"}},{"c":[{"u":{"a":"eyeColor","c":0,"l":["blue"]}}],"s":{"v":{"s":"blueValue"},"i":"blueVariationId"}}],"v":{"s":"defaultValue"},"i":"defaultVariationId"}}}'; } } @@ -176,7 +182,7 @@ export class FakeConfigFetcherWithNullNewConfig extends FakeConfigFetcherBase { export class FakeConfigFetcherWithAlwaysVariableEtag extends FakeConfigFetcher { static get configJson(): string { - return '{ "f": { "debug": { "v": true, "i": "abcdefgh", "t": 0, "p": [], "r": [] } }}'; + return '{"f":{"debug":{"t":0,"v":{"b":true},"i":"abcdefgh"}}}'; } getEtag(): string { @@ -184,8 +190,8 @@ export class FakeConfigFetcherWithAlwaysVariableEtag extends FakeConfigFetcher { } } -export class FakeConfigFetcherWithPercantageRules extends FakeConfigFetcher { +export class FakeConfigFetcherWithPercentageOptions extends FakeConfigFetcher { static get configJson(): string { - return '{"f":{"string25Cat25Dog25Falcon25Horse":{"v":"Chicken","i":"ChickenVariationId","t":1,"p":[{"o":0,"v":"Cat","p":25,"i":"CatVariationId"},{"o":1,"v":"Dog","p":25,"i":"DogVariationId"},{"o":2,"v":"Falcon","p":25,"i":"FalconVariationId"},{"o":3,"v":"Horse","p":25,"i":"HorseVariationId"}],"r":[]}}}'; + return '{"f":{"string25Cat25Dog25Falcon25Horse":{"t":1,"p":[{"p":25,"v":{"s":"Cat"},"i":"CatVariationId"},{"p":25,"v":{"s":"Dog"},"i":"DogVariationId"},{"p":25,"v":{"s":"Falcon"},"i":"FalconVariationId"},{"p":25,"v":{"s":"Horse"},"i":"HorseVariationId"}],"v":{"s":"Chicken"},"i":"ChickenVariationId"}}}'; } } diff --git a/test/helpers/utils.ts b/test/helpers/utils.ts index e1a9a82..bf2c355 100644 --- a/test/helpers/utils.ts +++ b/test/helpers/utils.ts @@ -2,10 +2,7 @@ import { IAutoPollOptions, IConfigCatKernel, ILazyLoadingOptions, IManualPollOpt import { ConfigCatClient, IConfigCatClient } from "../../src/ConfigCatClient"; import { AutoPollOptions, LazyLoadOptions, ManualPollOptions } from "../../src/ConfigCatClientOptions"; -// See https://stackoverflow.com/a/72237137/8656352 -export function allowEventLoop(waitMs = 0): Promise { - return new Promise(resolve => setTimeout(resolve, waitMs)); -} +export const sdkType = "ConfigCat-JS-Common", sdkVersion = "0.0.0-test"; export function createClientWithAutoPoll(apiKey: string, configCatKernel: IConfigCatKernel, options?: IAutoPollOptions): IConfigCatClient { return new ConfigCatClient(new AutoPollOptions(apiKey, configCatKernel.sdkType, configCatKernel.sdkVersion, options, configCatKernel.defaultCacheFactory, configCatKernel.eventEmitterFactory), configCatKernel); @@ -18,3 +15,17 @@ export function createClientWithManualPoll(apiKey: string, configCatKernel: ICon export function createClientWithLazyLoad(apiKey: string, configCatKernel: IConfigCatKernel, options?: ILazyLoadingOptions): IConfigCatClient { return new ConfigCatClient(new LazyLoadOptions(apiKey, configCatKernel.sdkType, configCatKernel.sdkVersion, options, configCatKernel.defaultCacheFactory, configCatKernel.eventEmitterFactory), configCatKernel); } + +// See https://stackoverflow.com/a/72237137/8656352 +export function allowEventLoop(waitMs = 0): Promise { + return new Promise(resolve => setTimeout(resolve, waitMs)); +} + +export function normalizeLineEndings(text: string, eol = "\n"): string { + return text.replace(/\r\n?|\n/g, eol); +} + +export function escapeRegExp(text: string): string { + // See also: https://tc39.es/ecma262/#prod-SyntaxCharacter + return text.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&"); +} diff --git a/tsconfig.mocha.json b/tsconfig.mocha.json index 2e33b91..1ca807c 100644 --- a/tsconfig.mocha.json +++ b/tsconfig.mocha.json @@ -1,7 +1,7 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "target": "ES5", + "target": "ES2018", "module": "CommonJS", "downlevelIteration": true, },