├── .github └── FUNDING.yml ├── .gitignore ├── .npmrc ├── LICENSE ├── README.md ├── bin └── cli.js ├── images └── donate-badge.png ├── package-lock.json ├── package.json ├── src ├── cli │ ├── cli.ts │ └── tasks │ │ ├── extract.task.ts │ │ └── task.interface.ts ├── compilers │ ├── compiler.factory.ts │ ├── compiler.interface.ts │ ├── json.compiler.ts │ ├── namespaced-json.compiler.ts │ └── po.compiler.ts ├── index.ts ├── parsers │ ├── directive.parser.ts │ ├── marker.parser.ts │ ├── parser.interface.ts │ ├── pipe.parser.ts │ └── service.parser.ts ├── post-processors │ ├── key-as-default-value.post-processor.ts │ ├── null-as-default-value.post-processor.ts │ ├── post-processor.interface.ts │ ├── purge-obsolete-keys.post-processor.ts │ ├── sort-by-key.post-processor.ts │ └── string-as-default-value.post-processor.ts └── utils │ ├── ast-helpers.ts │ ├── donate.ts │ ├── fs-helpers.ts │ ├── translation.collection.ts │ └── utils.ts ├── tests ├── compilers │ ├── namespaced-json.compiler.spec.ts │ └── po.compiler.spec.ts ├── parsers │ ├── directive.parser.spec.ts │ ├── marker.parser.spec.ts │ ├── pipe.parser.spec.ts │ ├── service.parser.spec.ts │ └── utils.spec.ts ├── post-processors │ ├── key-as-default-value.post-processor.spec.ts │ ├── null-as-default-value.post-processor.spec.ts │ ├── purge-obsolete-keys.post-processor.spec.ts │ ├── sort-by-key.post-processor.spec.ts │ └── string-as-default-value.post-processor.spec.ts └── utils │ └── translation.collection.spec.ts ├── tsconfig.json ├── tslint.commit.json └── tslint.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: biesbjerg 2 | custom: https://donate.biesbjerg.com 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE 2 | .vscode 3 | .idea 4 | 5 | # Logs and other files 6 | npm-debug.log* 7 | .DS_Store 8 | 9 | # Compiled files 10 | dist 11 | src/**/*.js 12 | tests/**/*.js 13 | 14 | # Extracted strings 15 | strings.json 16 | strings.pot 17 | 18 | # Dependency directory 19 | node_modules 20 | 21 | # Source maps for JS builds 22 | *.js.map 23 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Kim Biesbjerg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | If this tool saves you time, please consider making a donation towards the continued maintainence and development: https://donate.biesbjerg.com 2 | 3 | [![Donate](images/donate-badge.png)](https://donate.biesbjerg.com) 4 | 5 | # ngx-translate-extract 6 | Extract translatable (ngx-translate) strings and save as a JSON or Gettext pot file. 7 | Merges with existing strings if the output file already exists. 8 | 9 | ## Install 10 | Install the package in your project: 11 | 12 | `npm install @biesbjerg/ngx-translate-extract --save-dev` 13 | 14 | Add a script to your project's `package.json`: 15 | ```json 16 | ... 17 | "scripts": { 18 | "i18n:init": "ngx-translate-extract --input ./src --output ./src/assets/i18n/template.json --key-as-default-value --replace --format json", 19 | "i18n:extract": "ngx-translate-extract --input ./src --output ./src/assets/i18n/{en,da,de,fi,nb,nl,sv}.json --clean --format json" 20 | } 21 | ... 22 | ``` 23 | You can now run `npm run i18n:extract` and it will extract strings from your project. 24 | 25 | ## Usage 26 | 27 | **Extract from dir and save to file** 28 | 29 | `ngx-translate-extract --input ./src --output ./src/assets/i18n/strings.json` 30 | 31 | **Extract from multiple dirs** 32 | 33 | `ngx-translate-extract --input ./src-a ./src-b --output ./src/assets/i18n/strings.json` 34 | 35 | 36 | **Extract and save to multiple files using path expansion** 37 | 38 | `ngx-translate-extract --input ./src --output ./src/i18n/{da,en}.json` 39 | 40 | ### JSON indentation 41 | Tabs are used by default for indentation when saving extracted strings in json formats: 42 | 43 | If you want to use spaces instead, you can do the following: 44 | 45 | `ngx-translate-extract --input ./src --output ./src/i18n/en.json --format-indentation ' '` 46 | 47 | ### Marker function 48 | If you want to extract strings that are not passed directly to `TranslateService`'s `get()`/`instant()`/`stream()` methods, you can wrap them in a marker function to let `ngx-translate-extract` know you want to extract them. 49 | 50 | Install marker function: 51 | `npm install @biesbjerg/ngx-translate-extract-marker` 52 | 53 | ```ts 54 | import { marker } from '@biesbjerg/ngx-translate-extract-marker'; 55 | 56 | marker('Extract me'); 57 | ``` 58 | 59 | You can alias the marker function if needed: 60 | 61 | ```ts 62 | import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; 63 | 64 | _('Extract me'); 65 | ``` 66 | 67 | _Note: `ngx-translate-extract` will automatically detect the import name_ 68 | 69 | ### Commandline arguments 70 | ``` 71 | Usage: 72 | ngx-translate-extract [options] 73 | 74 | Output 75 | --format, -f Format [string] [choices: "json", "namespaced-json", "pot"] [default: "json"] 76 | --format-indentation, --fi Format indentation (JSON/Namedspaced JSON) [string] [default: " "] 77 | --sort, -s Sort strings in alphabetical order [boolean] 78 | --clean, -c Remove obsolete strings after merge [boolean] 79 | --replace, -r Replace the contents of output file if it exists (Merges by default) [boolean] 80 | 81 | Extracted key value (defaults to empty string) 82 | --key-as-default-value, -k Use key as default value [boolean] 83 | --null-as-default-value, -n Use null as default value [boolean] 84 | --string-as-default-value, -d Use string as default value [string] 85 | 86 | Options: 87 | --version, -v Show version number [boolean] 88 | --help, -h Show help [boolean] 89 | --input, -i Paths you would like to extract strings from. You can use path expansion, glob patterns and 90 | multiple paths [array] [required] [default: ["/Users/kim/apps/ngx-translate-extract"]] 91 | --output, -o Paths where you would like to save extracted strings. You can use path expansion, glob 92 | patterns and multiple paths [array] [required] 93 | 94 | Examples: 95 | ngx-translate-extract -i ./src-a/ -i ./src-b/ -o strings.json Extract (ts, html) from multiple paths 96 | ngx-translate-extract -i './{src-a,src-b}/' -o strings.json Extract (ts, html) from multiple paths using brace 97 | expansion 98 | ngx-translate-extract -i ./src/ -o ./i18n/da.json -o ./i18n/en.json Extract (ts, html) and save to da.json and en.json 99 | ngx-translate-extract -i ./src/ -o './i18n/{en,da}.json' Extract (ts, html) and save to da.json and en.json 100 | using brace expansion 101 | ngx-translate-extract -i './src/**/*.{ts,tsx,html}' -o strings.json Extract from ts, tsx and html 102 | ngx-translate-extract -i './src/**/!(*.spec).{ts,html}' -o Extract from ts, html, excluding files with ".spec" 103 | strings.json 104 | ``` 105 | 106 | ## Note for GetText users 107 | 108 | Please pay attention of which version of `gettext-parser` you actually use in your project. For instance, `gettext-parser:1.2.2` does not support HTML tags in translation keys. 109 | -------------------------------------------------------------------------------- /bin/cli.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | require('../dist/cli/cli'); 4 | -------------------------------------------------------------------------------- /images/donate-badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biesbjerg/ngx-translate-extract/82eb652e4bfec73f60f06cbc5ed4ddf8179f58f7/images/donate-badge.png -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@biesbjerg/ngx-translate-extract", 3 | "version": "7.0.3", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@angular/compiler": { 8 | "version": "11.2.9", 9 | "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-11.2.9.tgz", 10 | "integrity": "sha512-EelccCc6Xgrz6UzoJU5EqrRc0CGDHS82cheiBP3lSDfjG6borD9al4DN5lfphp+FCmTpLY33wQ3jqhPwC02FMQ==", 11 | "dev": true, 12 | "requires": { 13 | "tslib": "^2.0.0" 14 | }, 15 | "dependencies": { 16 | "tslib": { 17 | "version": "2.2.0", 18 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", 19 | "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", 20 | "dev": true 21 | } 22 | } 23 | }, 24 | "@babel/code-frame": { 25 | "version": "7.10.4", 26 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", 27 | "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", 28 | "dev": true, 29 | "requires": { 30 | "@babel/highlight": "^7.10.4" 31 | } 32 | }, 33 | "@babel/helper-validator-identifier": { 34 | "version": "7.10.4", 35 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", 36 | "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", 37 | "dev": true 38 | }, 39 | "@babel/highlight": { 40 | "version": "7.10.4", 41 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", 42 | "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", 43 | "dev": true, 44 | "requires": { 45 | "@babel/helper-validator-identifier": "^7.10.4", 46 | "chalk": "^2.0.0", 47 | "js-tokens": "^4.0.0" 48 | }, 49 | "dependencies": { 50 | "ansi-styles": { 51 | "version": "3.2.1", 52 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 53 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 54 | "dev": true, 55 | "requires": { 56 | "color-convert": "^1.9.0" 57 | } 58 | }, 59 | "chalk": { 60 | "version": "2.4.2", 61 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 62 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 63 | "dev": true, 64 | "requires": { 65 | "ansi-styles": "^3.2.1", 66 | "escape-string-regexp": "^1.0.5", 67 | "supports-color": "^5.3.0" 68 | } 69 | }, 70 | "color-convert": { 71 | "version": "1.9.3", 72 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 73 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 74 | "dev": true, 75 | "requires": { 76 | "color-name": "1.1.3" 77 | } 78 | }, 79 | "color-name": { 80 | "version": "1.1.3", 81 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 82 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 83 | "dev": true 84 | }, 85 | "has-flag": { 86 | "version": "3.0.0", 87 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 88 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 89 | "dev": true 90 | }, 91 | "supports-color": { 92 | "version": "5.5.0", 93 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 94 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 95 | "dev": true, 96 | "requires": { 97 | "has-flag": "^3.0.0" 98 | } 99 | } 100 | } 101 | }, 102 | "@phenomnomnominal/tsquery": { 103 | "version": "4.1.1", 104 | "resolved": "https://registry.npmjs.org/@phenomnomnominal/tsquery/-/tsquery-4.1.1.tgz", 105 | "integrity": "sha512-jjMmK1tnZbm1Jq5a7fBliM4gQwjxMU7TFoRNwIyzwlO+eHPRCFv/Nv+H/Gi1jc3WR7QURG8D5d0Tn12YGrUqBQ==", 106 | "requires": { 107 | "esquery": "^1.0.1" 108 | } 109 | }, 110 | "@types/braces": { 111 | "version": "3.0.0", 112 | "resolved": "https://registry.npmjs.org/@types/braces/-/braces-3.0.0.tgz", 113 | "integrity": "sha512-TbH79tcyi9FHwbyboOKeRachRq63mSuWYXOflsNO9ZyE5ClQ/JaozNKl+aWUq87qPNsXasXxi2AbgfwIJ+8GQw==", 114 | "dev": true 115 | }, 116 | "@types/chai": { 117 | "version": "4.2.16", 118 | "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.16.tgz", 119 | "integrity": "sha512-vI5iOAsez9+roLS3M3+Xx7w+WRuDtSmF8bQkrbcIJ2sC1PcDgVoA0WGpa+bIrJ+y8zqY2oi//fUctkxtIcXJCw==", 120 | "dev": true 121 | }, 122 | "@types/color-name": { 123 | "version": "1.1.1", 124 | "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", 125 | "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" 126 | }, 127 | "@types/flat": { 128 | "version": "5.0.1", 129 | "resolved": "https://registry.npmjs.org/@types/flat/-/flat-5.0.1.tgz", 130 | "integrity": "sha512-ykRODHi9G9exJdTZvQggsqCUtB7jqiwLHcXCjNMb7zgWx6Lc2bydIUYBG1+It6VXZVFaeROv6HqPjDCAsoPG3w==", 131 | "dev": true 132 | }, 133 | "@types/gettext-parser": { 134 | "version": "4.0.0", 135 | "resolved": "https://registry.npmjs.org/@types/gettext-parser/-/gettext-parser-4.0.0.tgz", 136 | "integrity": "sha512-I/wvhr+l5M7IwBF1ADBfNQ6qGfUg85UTjj/AZWn09Y+POqyLe1cfbdJSMWzCobiJ3EJNY23MQCbP6jxQT81OTQ==", 137 | "dev": true, 138 | "requires": { 139 | "@types/node": "*" 140 | } 141 | }, 142 | "@types/glob": { 143 | "version": "7.1.3", 144 | "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", 145 | "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", 146 | "dev": true, 147 | "requires": { 148 | "@types/minimatch": "*", 149 | "@types/node": "*" 150 | } 151 | }, 152 | "@types/minimatch": { 153 | "version": "3.0.3", 154 | "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", 155 | "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", 156 | "dev": true 157 | }, 158 | "@types/mkdirp": { 159 | "version": "1.0.1", 160 | "resolved": "https://registry.npmjs.org/@types/mkdirp/-/mkdirp-1.0.1.tgz", 161 | "integrity": "sha512-HkGSK7CGAXncr8Qn/0VqNtExEE+PHMWb+qlR1faHMao7ng6P3tAaoWWBMdva0gL5h4zprjIO89GJOLXsMcDm1Q==", 162 | "dev": true, 163 | "requires": { 164 | "@types/node": "*" 165 | } 166 | }, 167 | "@types/mocha": { 168 | "version": "8.2.2", 169 | "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.2.tgz", 170 | "integrity": "sha512-Lwh0lzzqT5Pqh6z61P3c3P5nm6fzQK/MMHl9UKeneAeInVflBSz1O2EkX6gM6xfJd7FBXBY5purtLx7fUiZ7Hw==", 171 | "dev": true 172 | }, 173 | "@types/node": { 174 | "version": "14.14.37", 175 | "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.37.tgz", 176 | "integrity": "sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw==", 177 | "dev": true 178 | }, 179 | "@types/parse-json": { 180 | "version": "4.0.0", 181 | "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", 182 | "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", 183 | "dev": true 184 | }, 185 | "@types/yargs": { 186 | "version": "16.0.1", 187 | "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.1.tgz", 188 | "integrity": "sha512-x4HABGLyzr5hKUzBC9dvjciOTm11WVH1NWonNjGgxapnTHu5SWUqyqn0zQ6Re0yQU0lsQ6ztLCoMAKDGZflyxA==", 189 | "dev": true, 190 | "requires": { 191 | "@types/yargs-parser": "*" 192 | } 193 | }, 194 | "@types/yargs-parser": { 195 | "version": "20.2.0", 196 | "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.0.tgz", 197 | "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==", 198 | "dev": true 199 | }, 200 | "@ungap/promise-all-settled": { 201 | "version": "1.1.2", 202 | "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", 203 | "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", 204 | "dev": true 205 | }, 206 | "aggregate-error": { 207 | "version": "3.1.0", 208 | "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", 209 | "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", 210 | "dev": true, 211 | "requires": { 212 | "clean-stack": "^2.0.0", 213 | "indent-string": "^4.0.0" 214 | } 215 | }, 216 | "ansi-align": { 217 | "version": "3.0.0", 218 | "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", 219 | "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", 220 | "requires": { 221 | "string-width": "^3.0.0" 222 | }, 223 | "dependencies": { 224 | "string-width": { 225 | "version": "3.1.0", 226 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", 227 | "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", 228 | "requires": { 229 | "emoji-regex": "^7.0.1", 230 | "is-fullwidth-code-point": "^2.0.0", 231 | "strip-ansi": "^5.1.0" 232 | } 233 | } 234 | } 235 | }, 236 | "ansi-colors": { 237 | "version": "4.1.1", 238 | "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", 239 | "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", 240 | "dev": true 241 | }, 242 | "ansi-escapes": { 243 | "version": "4.3.1", 244 | "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", 245 | "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", 246 | "requires": { 247 | "type-fest": "^0.11.0" 248 | }, 249 | "dependencies": { 250 | "type-fest": { 251 | "version": "0.11.0", 252 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", 253 | "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==" 254 | } 255 | } 256 | }, 257 | "ansi-regex": { 258 | "version": "4.1.0", 259 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", 260 | "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" 261 | }, 262 | "ansi-styles": { 263 | "version": "4.2.1", 264 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", 265 | "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", 266 | "requires": { 267 | "@types/color-name": "^1.1.1", 268 | "color-convert": "^2.0.1" 269 | } 270 | }, 271 | "anymatch": { 272 | "version": "3.1.2", 273 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", 274 | "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", 275 | "dev": true, 276 | "requires": { 277 | "normalize-path": "^3.0.0", 278 | "picomatch": "^2.0.4" 279 | } 280 | }, 281 | "arg": { 282 | "version": "4.1.3", 283 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 284 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", 285 | "dev": true 286 | }, 287 | "argparse": { 288 | "version": "1.0.10", 289 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 290 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 291 | "dev": true, 292 | "requires": { 293 | "sprintf-js": "~1.0.2" 294 | } 295 | }, 296 | "assertion-error": { 297 | "version": "1.1.0", 298 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", 299 | "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", 300 | "dev": true 301 | }, 302 | "astral-regex": { 303 | "version": "2.0.0", 304 | "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", 305 | "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", 306 | "dev": true 307 | }, 308 | "balanced-match": { 309 | "version": "1.0.0", 310 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 311 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 312 | }, 313 | "binary-extensions": { 314 | "version": "2.2.0", 315 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", 316 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", 317 | "dev": true 318 | }, 319 | "boxen": { 320 | "version": "5.0.1", 321 | "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.0.1.tgz", 322 | "integrity": "sha512-49VBlw+PrWEF51aCmy7QIteYPIFZxSpvqBdP/2itCPPlJ49kj9zg/XPRFrdkne2W+CfwXUls8exMvu1RysZpKA==", 323 | "requires": { 324 | "ansi-align": "^3.0.0", 325 | "camelcase": "^6.2.0", 326 | "chalk": "^4.1.0", 327 | "cli-boxes": "^2.2.1", 328 | "string-width": "^4.2.0", 329 | "type-fest": "^0.20.2", 330 | "widest-line": "^3.1.0", 331 | "wrap-ansi": "^7.0.0" 332 | }, 333 | "dependencies": { 334 | "camelcase": { 335 | "version": "6.2.0", 336 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", 337 | "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==" 338 | } 339 | } 340 | }, 341 | "brace-expansion": { 342 | "version": "1.1.11", 343 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 344 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 345 | "requires": { 346 | "balanced-match": "^1.0.0", 347 | "concat-map": "0.0.1" 348 | } 349 | }, 350 | "braces": { 351 | "version": "3.0.2", 352 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 353 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 354 | "dev": true, 355 | "requires": { 356 | "fill-range": "^7.0.1" 357 | } 358 | }, 359 | "browser-stdout": { 360 | "version": "1.3.1", 361 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", 362 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", 363 | "dev": true 364 | }, 365 | "buffer-from": { 366 | "version": "1.1.1", 367 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", 368 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", 369 | "dev": true 370 | }, 371 | "builtin-modules": { 372 | "version": "1.1.1", 373 | "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", 374 | "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", 375 | "dev": true 376 | }, 377 | "callsites": { 378 | "version": "3.1.0", 379 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", 380 | "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", 381 | "dev": true 382 | }, 383 | "camelcase": { 384 | "version": "5.3.1", 385 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", 386 | "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", 387 | "dev": true 388 | }, 389 | "chai": { 390 | "version": "4.3.4", 391 | "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", 392 | "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", 393 | "dev": true, 394 | "requires": { 395 | "assertion-error": "^1.1.0", 396 | "check-error": "^1.0.2", 397 | "deep-eql": "^3.0.1", 398 | "get-func-name": "^2.0.0", 399 | "pathval": "^1.1.1", 400 | "type-detect": "^4.0.5" 401 | } 402 | }, 403 | "chalk": { 404 | "version": "4.1.0", 405 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", 406 | "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", 407 | "requires": { 408 | "ansi-styles": "^4.1.0", 409 | "supports-color": "^7.1.0" 410 | } 411 | }, 412 | "check-error": { 413 | "version": "1.0.2", 414 | "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", 415 | "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", 416 | "dev": true 417 | }, 418 | "chokidar": { 419 | "version": "3.5.1", 420 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", 421 | "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", 422 | "dev": true, 423 | "requires": { 424 | "anymatch": "~3.1.1", 425 | "braces": "~3.0.2", 426 | "fsevents": "~2.3.1", 427 | "glob-parent": "~5.1.0", 428 | "is-binary-path": "~2.1.0", 429 | "is-glob": "~4.0.1", 430 | "normalize-path": "~3.0.0", 431 | "readdirp": "~3.5.0" 432 | } 433 | }, 434 | "clean-stack": { 435 | "version": "2.2.0", 436 | "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", 437 | "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", 438 | "dev": true 439 | }, 440 | "cli-boxes": { 441 | "version": "2.2.1", 442 | "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", 443 | "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==" 444 | }, 445 | "cli-cursor": { 446 | "version": "3.1.0", 447 | "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", 448 | "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", 449 | "dev": true, 450 | "requires": { 451 | "restore-cursor": "^3.1.0" 452 | } 453 | }, 454 | "cli-truncate": { 455 | "version": "2.1.0", 456 | "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", 457 | "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", 458 | "dev": true, 459 | "requires": { 460 | "slice-ansi": "^3.0.0", 461 | "string-width": "^4.2.0" 462 | } 463 | }, 464 | "cliui": { 465 | "version": "7.0.4", 466 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", 467 | "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", 468 | "requires": { 469 | "string-width": "^4.2.0", 470 | "strip-ansi": "^6.0.0", 471 | "wrap-ansi": "^7.0.0" 472 | }, 473 | "dependencies": { 474 | "ansi-regex": { 475 | "version": "5.0.0", 476 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", 477 | "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" 478 | }, 479 | "strip-ansi": { 480 | "version": "6.0.0", 481 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", 482 | "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", 483 | "requires": { 484 | "ansi-regex": "^5.0.0" 485 | } 486 | } 487 | } 488 | }, 489 | "color-convert": { 490 | "version": "2.0.1", 491 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 492 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 493 | "requires": { 494 | "color-name": "~1.1.4" 495 | } 496 | }, 497 | "color-name": { 498 | "version": "1.1.4", 499 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 500 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" 501 | }, 502 | "colorette": { 503 | "version": "1.2.2", 504 | "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", 505 | "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==" 506 | }, 507 | "commander": { 508 | "version": "6.2.1", 509 | "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", 510 | "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", 511 | "dev": true 512 | }, 513 | "concat-map": { 514 | "version": "0.0.1", 515 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 516 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 517 | }, 518 | "content-type": { 519 | "version": "1.0.4", 520 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 521 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 522 | }, 523 | "cosmiconfig": { 524 | "version": "7.0.0", 525 | "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", 526 | "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", 527 | "dev": true, 528 | "requires": { 529 | "@types/parse-json": "^4.0.0", 530 | "import-fresh": "^3.2.1", 531 | "parse-json": "^5.0.0", 532 | "path-type": "^4.0.0", 533 | "yaml": "^1.10.0" 534 | } 535 | }, 536 | "create-require": { 537 | "version": "1.1.1", 538 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 539 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", 540 | "dev": true 541 | }, 542 | "cross-spawn": { 543 | "version": "7.0.3", 544 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", 545 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", 546 | "dev": true, 547 | "requires": { 548 | "path-key": "^3.1.0", 549 | "shebang-command": "^2.0.0", 550 | "which": "^2.0.1" 551 | } 552 | }, 553 | "debug": { 554 | "version": "4.3.1", 555 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", 556 | "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", 557 | "dev": true, 558 | "requires": { 559 | "ms": "2.1.2" 560 | } 561 | }, 562 | "decamelize": { 563 | "version": "4.0.0", 564 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", 565 | "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", 566 | "dev": true 567 | }, 568 | "dedent": { 569 | "version": "0.7.0", 570 | "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", 571 | "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", 572 | "dev": true 573 | }, 574 | "deep-eql": { 575 | "version": "3.0.1", 576 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", 577 | "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", 578 | "dev": true, 579 | "requires": { 580 | "type-detect": "^4.0.0" 581 | } 582 | }, 583 | "diff": { 584 | "version": "4.0.2", 585 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 586 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 587 | "dev": true 588 | }, 589 | "doctrine": { 590 | "version": "0.7.2", 591 | "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-0.7.2.tgz", 592 | "integrity": "sha1-fLhgNZujvpDgQLJrcpzkv6ZUxSM=", 593 | "dev": true, 594 | "requires": { 595 | "esutils": "^1.1.6", 596 | "isarray": "0.0.1" 597 | }, 598 | "dependencies": { 599 | "esutils": { 600 | "version": "1.1.6", 601 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.1.6.tgz", 602 | "integrity": "sha1-wBzKqa5LiXxtDD4hCuUvPHqEQ3U=", 603 | "dev": true 604 | } 605 | } 606 | }, 607 | "emoji-regex": { 608 | "version": "7.0.3", 609 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", 610 | "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" 611 | }, 612 | "encoding": { 613 | "version": "0.1.13", 614 | "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", 615 | "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", 616 | "requires": { 617 | "iconv-lite": "^0.6.2" 618 | } 619 | }, 620 | "end-of-stream": { 621 | "version": "1.4.4", 622 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", 623 | "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", 624 | "dev": true, 625 | "requires": { 626 | "once": "^1.4.0" 627 | } 628 | }, 629 | "enquirer": { 630 | "version": "2.3.6", 631 | "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", 632 | "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", 633 | "dev": true, 634 | "requires": { 635 | "ansi-colors": "^4.1.1" 636 | } 637 | }, 638 | "error-ex": { 639 | "version": "1.3.2", 640 | "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", 641 | "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", 642 | "dev": true, 643 | "requires": { 644 | "is-arrayish": "^0.2.1" 645 | } 646 | }, 647 | "escalade": { 648 | "version": "3.1.1", 649 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", 650 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" 651 | }, 652 | "escape-string-regexp": { 653 | "version": "1.0.5", 654 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 655 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 656 | "dev": true 657 | }, 658 | "esprima": { 659 | "version": "4.0.1", 660 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 661 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", 662 | "dev": true 663 | }, 664 | "esquery": { 665 | "version": "1.3.1", 666 | "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", 667 | "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", 668 | "requires": { 669 | "estraverse": "^5.1.0" 670 | } 671 | }, 672 | "estraverse": { 673 | "version": "5.2.0", 674 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", 675 | "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==" 676 | }, 677 | "execa": { 678 | "version": "4.1.0", 679 | "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", 680 | "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", 681 | "dev": true, 682 | "requires": { 683 | "cross-spawn": "^7.0.0", 684 | "get-stream": "^5.0.0", 685 | "human-signals": "^1.1.1", 686 | "is-stream": "^2.0.0", 687 | "merge-stream": "^2.0.0", 688 | "npm-run-path": "^4.0.0", 689 | "onetime": "^5.1.0", 690 | "signal-exit": "^3.0.2", 691 | "strip-final-newline": "^2.0.0" 692 | } 693 | }, 694 | "figures": { 695 | "version": "3.2.0", 696 | "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", 697 | "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", 698 | "dev": true, 699 | "requires": { 700 | "escape-string-regexp": "^1.0.5" 701 | } 702 | }, 703 | "fill-range": { 704 | "version": "7.0.1", 705 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 706 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 707 | "dev": true, 708 | "requires": { 709 | "to-regex-range": "^5.0.1" 710 | } 711 | }, 712 | "find-up": { 713 | "version": "5.0.0", 714 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", 715 | "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", 716 | "dev": true, 717 | "requires": { 718 | "locate-path": "^6.0.0", 719 | "path-exists": "^4.0.0" 720 | } 721 | }, 722 | "flat": { 723 | "version": "5.0.2", 724 | "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", 725 | "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==" 726 | }, 727 | "fs.realpath": { 728 | "version": "1.0.0", 729 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 730 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 731 | }, 732 | "fsevents": { 733 | "version": "2.3.2", 734 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 735 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 736 | "dev": true, 737 | "optional": true 738 | }, 739 | "get-caller-file": { 740 | "version": "2.0.5", 741 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 742 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" 743 | }, 744 | "get-func-name": { 745 | "version": "2.0.0", 746 | "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", 747 | "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", 748 | "dev": true 749 | }, 750 | "get-own-enumerable-property-symbols": { 751 | "version": "3.0.2", 752 | "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", 753 | "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", 754 | "dev": true 755 | }, 756 | "get-stream": { 757 | "version": "5.2.0", 758 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", 759 | "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", 760 | "dev": true, 761 | "requires": { 762 | "pump": "^3.0.0" 763 | } 764 | }, 765 | "gettext-parser": { 766 | "version": "4.0.4", 767 | "resolved": "https://registry.npmjs.org/gettext-parser/-/gettext-parser-4.0.4.tgz", 768 | "integrity": "sha512-VDZEeOIYd0veZXt5iAn0SS3I0Fz14fJw+59avRNa7VIslEDriRLxcfrBd/xeQyOcm6nyS4uuufxm2iw88qirAg==", 769 | "requires": { 770 | "content-type": "^1.0.4", 771 | "encoding": "^0.1.13", 772 | "readable-stream": "^3.6.0", 773 | "safe-buffer": "^5.2.1" 774 | } 775 | }, 776 | "glob": { 777 | "version": "7.1.6", 778 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 779 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 780 | "requires": { 781 | "fs.realpath": "^1.0.0", 782 | "inflight": "^1.0.4", 783 | "inherits": "2", 784 | "minimatch": "^3.0.4", 785 | "once": "^1.3.0", 786 | "path-is-absolute": "^1.0.0" 787 | } 788 | }, 789 | "glob-parent": { 790 | "version": "5.1.2", 791 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 792 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 793 | "dev": true, 794 | "requires": { 795 | "is-glob": "^4.0.1" 796 | } 797 | }, 798 | "growl": { 799 | "version": "1.10.5", 800 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", 801 | "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", 802 | "dev": true 803 | }, 804 | "has-flag": { 805 | "version": "4.0.0", 806 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 807 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" 808 | }, 809 | "he": { 810 | "version": "1.2.0", 811 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", 812 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", 813 | "dev": true 814 | }, 815 | "human-signals": { 816 | "version": "1.1.1", 817 | "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", 818 | "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", 819 | "dev": true 820 | }, 821 | "husky": { 822 | "version": "6.0.0", 823 | "resolved": "https://registry.npmjs.org/husky/-/husky-6.0.0.tgz", 824 | "integrity": "sha512-SQS2gDTB7tBN486QSoKPKQItZw97BMOd+Kdb6ghfpBc0yXyzrddI0oDV5MkDAbuB4X2mO3/nj60TRMcYxwzZeQ==", 825 | "dev": true 826 | }, 827 | "iconv-lite": { 828 | "version": "0.6.2", 829 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz", 830 | "integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==", 831 | "requires": { 832 | "safer-buffer": ">= 2.1.2 < 3.0.0" 833 | } 834 | }, 835 | "import-fresh": { 836 | "version": "3.3.0", 837 | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", 838 | "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", 839 | "dev": true, 840 | "requires": { 841 | "parent-module": "^1.0.0", 842 | "resolve-from": "^4.0.0" 843 | } 844 | }, 845 | "indent-string": { 846 | "version": "4.0.0", 847 | "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", 848 | "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", 849 | "dev": true 850 | }, 851 | "inflight": { 852 | "version": "1.0.6", 853 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 854 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 855 | "requires": { 856 | "once": "^1.3.0", 857 | "wrappy": "1" 858 | } 859 | }, 860 | "inherits": { 861 | "version": "2.0.4", 862 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 863 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 864 | }, 865 | "is-arrayish": { 866 | "version": "0.2.1", 867 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", 868 | "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", 869 | "dev": true 870 | }, 871 | "is-binary-path": { 872 | "version": "2.1.0", 873 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 874 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 875 | "dev": true, 876 | "requires": { 877 | "binary-extensions": "^2.0.0" 878 | } 879 | }, 880 | "is-extglob": { 881 | "version": "2.1.1", 882 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 883 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", 884 | "dev": true 885 | }, 886 | "is-fullwidth-code-point": { 887 | "version": "2.0.0", 888 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", 889 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" 890 | }, 891 | "is-glob": { 892 | "version": "4.0.1", 893 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", 894 | "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", 895 | "dev": true, 896 | "requires": { 897 | "is-extglob": "^2.1.1" 898 | } 899 | }, 900 | "is-number": { 901 | "version": "7.0.0", 902 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 903 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 904 | "dev": true 905 | }, 906 | "is-obj": { 907 | "version": "1.0.1", 908 | "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", 909 | "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", 910 | "dev": true 911 | }, 912 | "is-plain-obj": { 913 | "version": "2.1.0", 914 | "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", 915 | "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", 916 | "dev": true 917 | }, 918 | "is-regexp": { 919 | "version": "1.0.0", 920 | "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", 921 | "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=", 922 | "dev": true 923 | }, 924 | "is-stream": { 925 | "version": "2.0.0", 926 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", 927 | "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", 928 | "dev": true 929 | }, 930 | "is-unicode-supported": { 931 | "version": "0.1.0", 932 | "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", 933 | "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", 934 | "dev": true 935 | }, 936 | "isarray": { 937 | "version": "0.0.1", 938 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 939 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", 940 | "dev": true 941 | }, 942 | "isexe": { 943 | "version": "2.0.0", 944 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 945 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", 946 | "dev": true 947 | }, 948 | "js-tokens": { 949 | "version": "4.0.0", 950 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 951 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 952 | "dev": true 953 | }, 954 | "js-yaml": { 955 | "version": "3.14.0", 956 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", 957 | "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", 958 | "dev": true, 959 | "requires": { 960 | "argparse": "^1.0.7", 961 | "esprima": "^4.0.0" 962 | } 963 | }, 964 | "json-parse-even-better-errors": { 965 | "version": "2.3.1", 966 | "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", 967 | "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", 968 | "dev": true 969 | }, 970 | "lines-and-columns": { 971 | "version": "1.1.6", 972 | "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", 973 | "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", 974 | "dev": true 975 | }, 976 | "lint-staged": { 977 | "version": "10.5.4", 978 | "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.5.4.tgz", 979 | "integrity": "sha512-EechC3DdFic/TdOPgj/RB3FicqE6932LTHCUm0Y2fsD9KGlLB+RwJl2q1IYBIvEsKzDOgn0D4gll+YxG5RsrKg==", 980 | "dev": true, 981 | "requires": { 982 | "chalk": "^4.1.0", 983 | "cli-truncate": "^2.1.0", 984 | "commander": "^6.2.0", 985 | "cosmiconfig": "^7.0.0", 986 | "debug": "^4.2.0", 987 | "dedent": "^0.7.0", 988 | "enquirer": "^2.3.6", 989 | "execa": "^4.1.0", 990 | "listr2": "^3.2.2", 991 | "log-symbols": "^4.0.0", 992 | "micromatch": "^4.0.2", 993 | "normalize-path": "^3.0.0", 994 | "please-upgrade-node": "^3.2.0", 995 | "string-argv": "0.3.1", 996 | "stringify-object": "^3.3.0" 997 | }, 998 | "dependencies": { 999 | "chalk": { 1000 | "version": "4.1.0", 1001 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", 1002 | "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", 1003 | "dev": true, 1004 | "requires": { 1005 | "ansi-styles": "^4.1.0", 1006 | "supports-color": "^7.1.0" 1007 | } 1008 | } 1009 | } 1010 | }, 1011 | "listr2": { 1012 | "version": "3.6.2", 1013 | "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.6.2.tgz", 1014 | "integrity": "sha512-B2vlu7Zx/2OAMVUovJ7Tv1kQ2v2oXd0nZKzkSAcRCej269d8gkS/gupDEdNl23KQ3ZjVD8hQmifrrBFbx8F9LA==", 1015 | "dev": true, 1016 | "requires": { 1017 | "chalk": "^4.1.0", 1018 | "cli-truncate": "^2.1.0", 1019 | "figures": "^3.2.0", 1020 | "indent-string": "^4.0.0", 1021 | "log-update": "^4.0.0", 1022 | "p-map": "^4.0.0", 1023 | "rxjs": "^6.6.7", 1024 | "through": "^2.3.8", 1025 | "wrap-ansi": "^7.0.0" 1026 | }, 1027 | "dependencies": { 1028 | "chalk": { 1029 | "version": "4.1.0", 1030 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", 1031 | "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", 1032 | "dev": true, 1033 | "requires": { 1034 | "ansi-styles": "^4.1.0", 1035 | "supports-color": "^7.1.0" 1036 | } 1037 | } 1038 | } 1039 | }, 1040 | "locate-path": { 1041 | "version": "6.0.0", 1042 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", 1043 | "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", 1044 | "dev": true, 1045 | "requires": { 1046 | "p-locate": "^5.0.0" 1047 | } 1048 | }, 1049 | "log-symbols": { 1050 | "version": "4.1.0", 1051 | "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", 1052 | "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", 1053 | "dev": true, 1054 | "requires": { 1055 | "chalk": "^4.1.0", 1056 | "is-unicode-supported": "^0.1.0" 1057 | }, 1058 | "dependencies": { 1059 | "chalk": { 1060 | "version": "4.1.0", 1061 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", 1062 | "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", 1063 | "dev": true, 1064 | "requires": { 1065 | "ansi-styles": "^4.1.0", 1066 | "supports-color": "^7.1.0" 1067 | } 1068 | } 1069 | } 1070 | }, 1071 | "log-update": { 1072 | "version": "4.0.0", 1073 | "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", 1074 | "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", 1075 | "dev": true, 1076 | "requires": { 1077 | "ansi-escapes": "^4.3.0", 1078 | "cli-cursor": "^3.1.0", 1079 | "slice-ansi": "^4.0.0", 1080 | "wrap-ansi": "^6.2.0" 1081 | }, 1082 | "dependencies": { 1083 | "ansi-regex": { 1084 | "version": "5.0.0", 1085 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", 1086 | "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", 1087 | "dev": true 1088 | }, 1089 | "is-fullwidth-code-point": { 1090 | "version": "3.0.0", 1091 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 1092 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 1093 | "dev": true 1094 | }, 1095 | "slice-ansi": { 1096 | "version": "4.0.0", 1097 | "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", 1098 | "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", 1099 | "dev": true, 1100 | "requires": { 1101 | "ansi-styles": "^4.0.0", 1102 | "astral-regex": "^2.0.0", 1103 | "is-fullwidth-code-point": "^3.0.0" 1104 | } 1105 | }, 1106 | "strip-ansi": { 1107 | "version": "6.0.0", 1108 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", 1109 | "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", 1110 | "dev": true, 1111 | "requires": { 1112 | "ansi-regex": "^5.0.0" 1113 | } 1114 | }, 1115 | "wrap-ansi": { 1116 | "version": "6.2.0", 1117 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", 1118 | "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", 1119 | "dev": true, 1120 | "requires": { 1121 | "ansi-styles": "^4.0.0", 1122 | "string-width": "^4.1.0", 1123 | "strip-ansi": "^6.0.0" 1124 | } 1125 | } 1126 | } 1127 | }, 1128 | "make-error": { 1129 | "version": "1.3.6", 1130 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 1131 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", 1132 | "dev": true 1133 | }, 1134 | "merge-stream": { 1135 | "version": "2.0.0", 1136 | "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", 1137 | "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", 1138 | "dev": true 1139 | }, 1140 | "micromatch": { 1141 | "version": "4.0.4", 1142 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", 1143 | "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", 1144 | "dev": true, 1145 | "requires": { 1146 | "braces": "^3.0.1", 1147 | "picomatch": "^2.2.3" 1148 | } 1149 | }, 1150 | "mimic-fn": { 1151 | "version": "2.1.0", 1152 | "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", 1153 | "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", 1154 | "dev": true 1155 | }, 1156 | "minimatch": { 1157 | "version": "3.0.4", 1158 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 1159 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 1160 | "requires": { 1161 | "brace-expansion": "^1.1.7" 1162 | } 1163 | }, 1164 | "minimist": { 1165 | "version": "1.2.5", 1166 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 1167 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", 1168 | "dev": true 1169 | }, 1170 | "mkdirp": { 1171 | "version": "1.0.4", 1172 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", 1173 | "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" 1174 | }, 1175 | "mocha": { 1176 | "version": "8.3.2", 1177 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.3.2.tgz", 1178 | "integrity": "sha512-UdmISwr/5w+uXLPKspgoV7/RXZwKRTiTjJ2/AC5ZiEztIoOYdfKb19+9jNmEInzx5pBsCyJQzarAxqIGBNYJhg==", 1179 | "dev": true, 1180 | "requires": { 1181 | "@ungap/promise-all-settled": "1.1.2", 1182 | "ansi-colors": "4.1.1", 1183 | "browser-stdout": "1.3.1", 1184 | "chokidar": "3.5.1", 1185 | "debug": "4.3.1", 1186 | "diff": "5.0.0", 1187 | "escape-string-regexp": "4.0.0", 1188 | "find-up": "5.0.0", 1189 | "glob": "7.1.6", 1190 | "growl": "1.10.5", 1191 | "he": "1.2.0", 1192 | "js-yaml": "4.0.0", 1193 | "log-symbols": "4.0.0", 1194 | "minimatch": "3.0.4", 1195 | "ms": "2.1.3", 1196 | "nanoid": "3.1.20", 1197 | "serialize-javascript": "5.0.1", 1198 | "strip-json-comments": "3.1.1", 1199 | "supports-color": "8.1.1", 1200 | "which": "2.0.2", 1201 | "wide-align": "1.1.3", 1202 | "workerpool": "6.1.0", 1203 | "yargs": "16.2.0", 1204 | "yargs-parser": "20.2.4", 1205 | "yargs-unparser": "2.0.0" 1206 | }, 1207 | "dependencies": { 1208 | "argparse": { 1209 | "version": "2.0.1", 1210 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 1211 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 1212 | "dev": true 1213 | }, 1214 | "chalk": { 1215 | "version": "4.1.0", 1216 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", 1217 | "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", 1218 | "dev": true, 1219 | "requires": { 1220 | "ansi-styles": "^4.1.0", 1221 | "supports-color": "^7.1.0" 1222 | }, 1223 | "dependencies": { 1224 | "supports-color": { 1225 | "version": "7.2.0", 1226 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 1227 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 1228 | "dev": true, 1229 | "requires": { 1230 | "has-flag": "^4.0.0" 1231 | } 1232 | } 1233 | } 1234 | }, 1235 | "diff": { 1236 | "version": "5.0.0", 1237 | "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", 1238 | "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", 1239 | "dev": true 1240 | }, 1241 | "escape-string-regexp": { 1242 | "version": "4.0.0", 1243 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 1244 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 1245 | "dev": true 1246 | }, 1247 | "js-yaml": { 1248 | "version": "4.0.0", 1249 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", 1250 | "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", 1251 | "dev": true, 1252 | "requires": { 1253 | "argparse": "^2.0.1" 1254 | } 1255 | }, 1256 | "log-symbols": { 1257 | "version": "4.0.0", 1258 | "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", 1259 | "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", 1260 | "dev": true, 1261 | "requires": { 1262 | "chalk": "^4.0.0" 1263 | } 1264 | }, 1265 | "ms": { 1266 | "version": "2.1.3", 1267 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1268 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 1269 | "dev": true 1270 | }, 1271 | "supports-color": { 1272 | "version": "8.1.1", 1273 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", 1274 | "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", 1275 | "dev": true, 1276 | "requires": { 1277 | "has-flag": "^4.0.0" 1278 | } 1279 | } 1280 | } 1281 | }, 1282 | "ms": { 1283 | "version": "2.1.2", 1284 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1285 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 1286 | "dev": true 1287 | }, 1288 | "nanoid": { 1289 | "version": "3.1.20", 1290 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", 1291 | "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==", 1292 | "dev": true 1293 | }, 1294 | "normalize-path": { 1295 | "version": "3.0.0", 1296 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 1297 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 1298 | "dev": true 1299 | }, 1300 | "npm-run-path": { 1301 | "version": "4.0.1", 1302 | "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", 1303 | "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", 1304 | "dev": true, 1305 | "requires": { 1306 | "path-key": "^3.0.0" 1307 | } 1308 | }, 1309 | "once": { 1310 | "version": "1.4.0", 1311 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1312 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 1313 | "requires": { 1314 | "wrappy": "1" 1315 | } 1316 | }, 1317 | "onetime": { 1318 | "version": "5.1.2", 1319 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", 1320 | "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", 1321 | "dev": true, 1322 | "requires": { 1323 | "mimic-fn": "^2.1.0" 1324 | } 1325 | }, 1326 | "p-limit": { 1327 | "version": "3.1.0", 1328 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", 1329 | "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", 1330 | "dev": true, 1331 | "requires": { 1332 | "yocto-queue": "^0.1.0" 1333 | } 1334 | }, 1335 | "p-locate": { 1336 | "version": "5.0.0", 1337 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", 1338 | "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", 1339 | "dev": true, 1340 | "requires": { 1341 | "p-limit": "^3.0.2" 1342 | } 1343 | }, 1344 | "p-map": { 1345 | "version": "4.0.0", 1346 | "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", 1347 | "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", 1348 | "dev": true, 1349 | "requires": { 1350 | "aggregate-error": "^3.0.0" 1351 | } 1352 | }, 1353 | "p-try": { 1354 | "version": "2.2.0", 1355 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", 1356 | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", 1357 | "dev": true 1358 | }, 1359 | "parent-module": { 1360 | "version": "1.0.1", 1361 | "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", 1362 | "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 1363 | "dev": true, 1364 | "requires": { 1365 | "callsites": "^3.0.0" 1366 | } 1367 | }, 1368 | "parse-json": { 1369 | "version": "5.2.0", 1370 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", 1371 | "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", 1372 | "dev": true, 1373 | "requires": { 1374 | "@babel/code-frame": "^7.0.0", 1375 | "error-ex": "^1.3.1", 1376 | "json-parse-even-better-errors": "^2.3.0", 1377 | "lines-and-columns": "^1.1.6" 1378 | } 1379 | }, 1380 | "path": { 1381 | "version": "0.12.7", 1382 | "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", 1383 | "integrity": "sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=", 1384 | "requires": { 1385 | "process": "^0.11.1", 1386 | "util": "^0.10.3" 1387 | } 1388 | }, 1389 | "path-exists": { 1390 | "version": "4.0.0", 1391 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 1392 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 1393 | "dev": true 1394 | }, 1395 | "path-is-absolute": { 1396 | "version": "1.0.1", 1397 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1398 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 1399 | }, 1400 | "path-key": { 1401 | "version": "3.1.1", 1402 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 1403 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 1404 | "dev": true 1405 | }, 1406 | "path-parse": { 1407 | "version": "1.0.6", 1408 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 1409 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", 1410 | "dev": true 1411 | }, 1412 | "path-type": { 1413 | "version": "4.0.0", 1414 | "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", 1415 | "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", 1416 | "dev": true 1417 | }, 1418 | "pathval": { 1419 | "version": "1.1.1", 1420 | "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", 1421 | "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", 1422 | "dev": true 1423 | }, 1424 | "picomatch": { 1425 | "version": "2.2.3", 1426 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz", 1427 | "integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==", 1428 | "dev": true 1429 | }, 1430 | "please-upgrade-node": { 1431 | "version": "3.2.0", 1432 | "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", 1433 | "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", 1434 | "dev": true, 1435 | "requires": { 1436 | "semver-compare": "^1.0.0" 1437 | } 1438 | }, 1439 | "prettier": { 1440 | "version": "2.2.1", 1441 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz", 1442 | "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==", 1443 | "dev": true 1444 | }, 1445 | "process": { 1446 | "version": "0.11.10", 1447 | "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", 1448 | "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" 1449 | }, 1450 | "pump": { 1451 | "version": "3.0.0", 1452 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", 1453 | "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", 1454 | "dev": true, 1455 | "requires": { 1456 | "end-of-stream": "^1.1.0", 1457 | "once": "^1.3.1" 1458 | } 1459 | }, 1460 | "randombytes": { 1461 | "version": "2.1.0", 1462 | "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", 1463 | "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", 1464 | "dev": true, 1465 | "requires": { 1466 | "safe-buffer": "^5.1.0" 1467 | } 1468 | }, 1469 | "readable-stream": { 1470 | "version": "3.6.0", 1471 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 1472 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 1473 | "requires": { 1474 | "inherits": "^2.0.3", 1475 | "string_decoder": "^1.1.1", 1476 | "util-deprecate": "^1.0.1" 1477 | } 1478 | }, 1479 | "readdirp": { 1480 | "version": "3.5.0", 1481 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", 1482 | "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", 1483 | "dev": true, 1484 | "requires": { 1485 | "picomatch": "^2.2.1" 1486 | } 1487 | }, 1488 | "require-directory": { 1489 | "version": "2.1.1", 1490 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 1491 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" 1492 | }, 1493 | "require-main-filename": { 1494 | "version": "2.0.0", 1495 | "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", 1496 | "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", 1497 | "dev": true 1498 | }, 1499 | "resolve": { 1500 | "version": "1.17.0", 1501 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", 1502 | "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", 1503 | "dev": true, 1504 | "requires": { 1505 | "path-parse": "^1.0.6" 1506 | } 1507 | }, 1508 | "resolve-from": { 1509 | "version": "4.0.0", 1510 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", 1511 | "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", 1512 | "dev": true 1513 | }, 1514 | "restore-cursor": { 1515 | "version": "3.1.0", 1516 | "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", 1517 | "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", 1518 | "dev": true, 1519 | "requires": { 1520 | "onetime": "^5.1.0", 1521 | "signal-exit": "^3.0.2" 1522 | } 1523 | }, 1524 | "rimraf": { 1525 | "version": "3.0.2", 1526 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 1527 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 1528 | "dev": true, 1529 | "requires": { 1530 | "glob": "^7.1.3" 1531 | } 1532 | }, 1533 | "rxjs": { 1534 | "version": "6.6.7", 1535 | "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", 1536 | "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", 1537 | "dev": true, 1538 | "requires": { 1539 | "tslib": "^1.9.0" 1540 | } 1541 | }, 1542 | "safe-buffer": { 1543 | "version": "5.2.1", 1544 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1545 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 1546 | }, 1547 | "safer-buffer": { 1548 | "version": "2.1.2", 1549 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1550 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 1551 | }, 1552 | "semver": { 1553 | "version": "5.7.1", 1554 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 1555 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", 1556 | "dev": true 1557 | }, 1558 | "semver-compare": { 1559 | "version": "1.0.0", 1560 | "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", 1561 | "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", 1562 | "dev": true 1563 | }, 1564 | "serialize-javascript": { 1565 | "version": "5.0.1", 1566 | "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", 1567 | "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", 1568 | "dev": true, 1569 | "requires": { 1570 | "randombytes": "^2.1.0" 1571 | } 1572 | }, 1573 | "set-blocking": { 1574 | "version": "2.0.0", 1575 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 1576 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", 1577 | "dev": true 1578 | }, 1579 | "shebang-command": { 1580 | "version": "2.0.0", 1581 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 1582 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 1583 | "dev": true, 1584 | "requires": { 1585 | "shebang-regex": "^3.0.0" 1586 | } 1587 | }, 1588 | "shebang-regex": { 1589 | "version": "3.0.0", 1590 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 1591 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 1592 | "dev": true 1593 | }, 1594 | "signal-exit": { 1595 | "version": "3.0.3", 1596 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", 1597 | "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", 1598 | "dev": true 1599 | }, 1600 | "slice-ansi": { 1601 | "version": "3.0.0", 1602 | "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", 1603 | "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", 1604 | "dev": true, 1605 | "requires": { 1606 | "ansi-styles": "^4.0.0", 1607 | "astral-regex": "^2.0.0", 1608 | "is-fullwidth-code-point": "^3.0.0" 1609 | }, 1610 | "dependencies": { 1611 | "is-fullwidth-code-point": { 1612 | "version": "3.0.0", 1613 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 1614 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 1615 | "dev": true 1616 | } 1617 | } 1618 | }, 1619 | "source-map": { 1620 | "version": "0.6.1", 1621 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 1622 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 1623 | "dev": true 1624 | }, 1625 | "source-map-support": { 1626 | "version": "0.5.19", 1627 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", 1628 | "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", 1629 | "dev": true, 1630 | "requires": { 1631 | "buffer-from": "^1.0.0", 1632 | "source-map": "^0.6.0" 1633 | } 1634 | }, 1635 | "sprintf-js": { 1636 | "version": "1.0.3", 1637 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 1638 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", 1639 | "dev": true 1640 | }, 1641 | "string-argv": { 1642 | "version": "0.3.1", 1643 | "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", 1644 | "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", 1645 | "dev": true 1646 | }, 1647 | "string-width": { 1648 | "version": "4.2.0", 1649 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", 1650 | "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", 1651 | "requires": { 1652 | "emoji-regex": "^8.0.0", 1653 | "is-fullwidth-code-point": "^3.0.0", 1654 | "strip-ansi": "^6.0.0" 1655 | }, 1656 | "dependencies": { 1657 | "ansi-regex": { 1658 | "version": "5.0.0", 1659 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", 1660 | "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" 1661 | }, 1662 | "emoji-regex": { 1663 | "version": "8.0.0", 1664 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 1665 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" 1666 | }, 1667 | "is-fullwidth-code-point": { 1668 | "version": "3.0.0", 1669 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 1670 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" 1671 | }, 1672 | "strip-ansi": { 1673 | "version": "6.0.0", 1674 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", 1675 | "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", 1676 | "requires": { 1677 | "ansi-regex": "^5.0.0" 1678 | } 1679 | } 1680 | } 1681 | }, 1682 | "string_decoder": { 1683 | "version": "1.3.0", 1684 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 1685 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 1686 | "requires": { 1687 | "safe-buffer": "~5.2.0" 1688 | } 1689 | }, 1690 | "stringify-object": { 1691 | "version": "3.3.0", 1692 | "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", 1693 | "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", 1694 | "dev": true, 1695 | "requires": { 1696 | "get-own-enumerable-property-symbols": "^3.0.0", 1697 | "is-obj": "^1.0.1", 1698 | "is-regexp": "^1.0.0" 1699 | } 1700 | }, 1701 | "strip-ansi": { 1702 | "version": "5.2.0", 1703 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", 1704 | "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", 1705 | "requires": { 1706 | "ansi-regex": "^4.1.0" 1707 | } 1708 | }, 1709 | "strip-final-newline": { 1710 | "version": "2.0.0", 1711 | "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", 1712 | "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", 1713 | "dev": true 1714 | }, 1715 | "strip-json-comments": { 1716 | "version": "3.1.1", 1717 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 1718 | "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 1719 | "dev": true 1720 | }, 1721 | "supports-color": { 1722 | "version": "7.1.0", 1723 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", 1724 | "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", 1725 | "requires": { 1726 | "has-flag": "^4.0.0" 1727 | } 1728 | }, 1729 | "supports-hyperlinks": { 1730 | "version": "2.1.0", 1731 | "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz", 1732 | "integrity": "sha512-zoE5/e+dnEijk6ASB6/qrK+oYdm2do1hjoLWrqUC/8WEIW1gbxFcKuBof7sW8ArN6e+AYvsE8HBGiVRWL/F5CA==", 1733 | "requires": { 1734 | "has-flag": "^4.0.0", 1735 | "supports-color": "^7.0.0" 1736 | } 1737 | }, 1738 | "terminal-link": { 1739 | "version": "2.1.1", 1740 | "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", 1741 | "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", 1742 | "requires": { 1743 | "ansi-escapes": "^4.2.1", 1744 | "supports-hyperlinks": "^2.0.0" 1745 | } 1746 | }, 1747 | "through": { 1748 | "version": "2.3.8", 1749 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 1750 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", 1751 | "dev": true 1752 | }, 1753 | "to-regex-range": { 1754 | "version": "5.0.1", 1755 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1756 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1757 | "dev": true, 1758 | "requires": { 1759 | "is-number": "^7.0.0" 1760 | } 1761 | }, 1762 | "ts-node": { 1763 | "version": "9.1.1", 1764 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", 1765 | "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", 1766 | "dev": true, 1767 | "requires": { 1768 | "arg": "^4.1.0", 1769 | "create-require": "^1.1.0", 1770 | "diff": "^4.0.1", 1771 | "make-error": "^1.1.1", 1772 | "source-map-support": "^0.5.17", 1773 | "yn": "3.1.1" 1774 | } 1775 | }, 1776 | "tslib": { 1777 | "version": "1.13.0", 1778 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", 1779 | "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==", 1780 | "dev": true 1781 | }, 1782 | "tslint": { 1783 | "version": "6.1.3", 1784 | "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", 1785 | "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", 1786 | "dev": true, 1787 | "requires": { 1788 | "@babel/code-frame": "^7.0.0", 1789 | "builtin-modules": "^1.1.1", 1790 | "chalk": "^2.3.0", 1791 | "commander": "^2.12.1", 1792 | "diff": "^4.0.1", 1793 | "glob": "^7.1.1", 1794 | "js-yaml": "^3.13.1", 1795 | "minimatch": "^3.0.4", 1796 | "mkdirp": "^0.5.3", 1797 | "resolve": "^1.3.2", 1798 | "semver": "^5.3.0", 1799 | "tslib": "^1.13.0", 1800 | "tsutils": "^2.29.0" 1801 | }, 1802 | "dependencies": { 1803 | "ansi-styles": { 1804 | "version": "3.2.1", 1805 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 1806 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 1807 | "dev": true, 1808 | "requires": { 1809 | "color-convert": "^1.9.0" 1810 | } 1811 | }, 1812 | "chalk": { 1813 | "version": "2.4.2", 1814 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 1815 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 1816 | "dev": true, 1817 | "requires": { 1818 | "ansi-styles": "^3.2.1", 1819 | "escape-string-regexp": "^1.0.5", 1820 | "supports-color": "^5.3.0" 1821 | } 1822 | }, 1823 | "color-convert": { 1824 | "version": "1.9.3", 1825 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 1826 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 1827 | "dev": true, 1828 | "requires": { 1829 | "color-name": "1.1.3" 1830 | } 1831 | }, 1832 | "color-name": { 1833 | "version": "1.1.3", 1834 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 1835 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 1836 | "dev": true 1837 | }, 1838 | "commander": { 1839 | "version": "2.20.3", 1840 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 1841 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", 1842 | "dev": true 1843 | }, 1844 | "has-flag": { 1845 | "version": "3.0.0", 1846 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 1847 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 1848 | "dev": true 1849 | }, 1850 | "mkdirp": { 1851 | "version": "0.5.5", 1852 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", 1853 | "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", 1854 | "dev": true, 1855 | "requires": { 1856 | "minimist": "^1.2.5" 1857 | } 1858 | }, 1859 | "supports-color": { 1860 | "version": "5.5.0", 1861 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 1862 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 1863 | "dev": true, 1864 | "requires": { 1865 | "has-flag": "^3.0.0" 1866 | } 1867 | } 1868 | } 1869 | }, 1870 | "tslint-config-prettier": { 1871 | "version": "1.18.0", 1872 | "resolved": "https://registry.npmjs.org/tslint-config-prettier/-/tslint-config-prettier-1.18.0.tgz", 1873 | "integrity": "sha512-xPw9PgNPLG3iKRxmK7DWr+Ea/SzrvfHtjFt5LBl61gk2UBG/DB9kCXRjv+xyIU1rUtnayLeMUVJBcMX8Z17nDg==", 1874 | "dev": true 1875 | }, 1876 | "tslint-eslint-rules": { 1877 | "version": "5.4.0", 1878 | "resolved": "https://registry.npmjs.org/tslint-eslint-rules/-/tslint-eslint-rules-5.4.0.tgz", 1879 | "integrity": "sha512-WlSXE+J2vY/VPgIcqQuijMQiel+UtmXS+4nvK4ZzlDiqBfXse8FAvkNnTcYhnQyOTW5KFM+uRRGXxYhFpuBc6w==", 1880 | "dev": true, 1881 | "requires": { 1882 | "doctrine": "0.7.2", 1883 | "tslib": "1.9.0", 1884 | "tsutils": "^3.0.0" 1885 | }, 1886 | "dependencies": { 1887 | "tslib": { 1888 | "version": "1.9.0", 1889 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.0.tgz", 1890 | "integrity": "sha512-f/qGG2tUkrISBlQZEjEqoZ3B2+npJjIf04H1wuAv9iA8i04Icp+61KRXxFdha22670NJopsZCIjhC3SnjPRKrQ==", 1891 | "dev": true 1892 | }, 1893 | "tsutils": { 1894 | "version": "3.17.1", 1895 | "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", 1896 | "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", 1897 | "dev": true, 1898 | "requires": { 1899 | "tslib": "^1.8.1" 1900 | } 1901 | } 1902 | } 1903 | }, 1904 | "tslint-etc": { 1905 | "version": "1.13.9", 1906 | "resolved": "https://registry.npmjs.org/tslint-etc/-/tslint-etc-1.13.9.tgz", 1907 | "integrity": "sha512-plelxI+RH0w1irVPxOX7REqnwaAM1FJcgoe12UU1Ft3zIvGx9VX4BNf9jRLAt2wd00TJPpCb0ACumaDoCXp7hA==", 1908 | "dev": true, 1909 | "requires": { 1910 | "@phenomnomnominal/tsquery": "^4.0.0", 1911 | "tslib": "^2.0.0", 1912 | "tsutils": "^3.0.0", 1913 | "tsutils-etc": "^1.0.0" 1914 | }, 1915 | "dependencies": { 1916 | "tslib": { 1917 | "version": "2.2.0", 1918 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", 1919 | "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", 1920 | "dev": true 1921 | }, 1922 | "tsutils": { 1923 | "version": "3.21.0", 1924 | "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", 1925 | "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", 1926 | "dev": true, 1927 | "requires": { 1928 | "tslib": "^1.8.1" 1929 | }, 1930 | "dependencies": { 1931 | "tslib": { 1932 | "version": "1.14.1", 1933 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", 1934 | "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", 1935 | "dev": true 1936 | } 1937 | } 1938 | } 1939 | } 1940 | }, 1941 | "tsutils": { 1942 | "version": "2.29.0", 1943 | "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", 1944 | "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", 1945 | "dev": true, 1946 | "requires": { 1947 | "tslib": "^1.8.1" 1948 | } 1949 | }, 1950 | "tsutils-etc": { 1951 | "version": "1.3.2", 1952 | "resolved": "https://registry.npmjs.org/tsutils-etc/-/tsutils-etc-1.3.2.tgz", 1953 | "integrity": "sha512-hAQoELuJxKiJ7nISOuTFI+OKcwtbBTfQh47DquqG4R0bDvhfK6vXvykoKTNpBBS1n67vGvGPqEtnR9zE6dfWdg==", 1954 | "dev": true, 1955 | "requires": { 1956 | "@types/yargs": "^15.0.5", 1957 | "yargs": "^15.4.1" 1958 | }, 1959 | "dependencies": { 1960 | "@types/yargs": { 1961 | "version": "15.0.13", 1962 | "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz", 1963 | "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==", 1964 | "dev": true, 1965 | "requires": { 1966 | "@types/yargs-parser": "*" 1967 | } 1968 | }, 1969 | "ansi-regex": { 1970 | "version": "5.0.0", 1971 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", 1972 | "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", 1973 | "dev": true 1974 | }, 1975 | "cliui": { 1976 | "version": "6.0.0", 1977 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", 1978 | "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", 1979 | "dev": true, 1980 | "requires": { 1981 | "string-width": "^4.2.0", 1982 | "strip-ansi": "^6.0.0", 1983 | "wrap-ansi": "^6.2.0" 1984 | } 1985 | }, 1986 | "decamelize": { 1987 | "version": "1.2.0", 1988 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", 1989 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", 1990 | "dev": true 1991 | }, 1992 | "find-up": { 1993 | "version": "4.1.0", 1994 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", 1995 | "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", 1996 | "dev": true, 1997 | "requires": { 1998 | "locate-path": "^5.0.0", 1999 | "path-exists": "^4.0.0" 2000 | } 2001 | }, 2002 | "locate-path": { 2003 | "version": "5.0.0", 2004 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", 2005 | "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", 2006 | "dev": true, 2007 | "requires": { 2008 | "p-locate": "^4.1.0" 2009 | } 2010 | }, 2011 | "p-limit": { 2012 | "version": "2.3.0", 2013 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", 2014 | "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", 2015 | "dev": true, 2016 | "requires": { 2017 | "p-try": "^2.0.0" 2018 | } 2019 | }, 2020 | "p-locate": { 2021 | "version": "4.1.0", 2022 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", 2023 | "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", 2024 | "dev": true, 2025 | "requires": { 2026 | "p-limit": "^2.2.0" 2027 | } 2028 | }, 2029 | "strip-ansi": { 2030 | "version": "6.0.0", 2031 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", 2032 | "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", 2033 | "dev": true, 2034 | "requires": { 2035 | "ansi-regex": "^5.0.0" 2036 | } 2037 | }, 2038 | "wrap-ansi": { 2039 | "version": "6.2.0", 2040 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", 2041 | "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", 2042 | "dev": true, 2043 | "requires": { 2044 | "ansi-styles": "^4.0.0", 2045 | "string-width": "^4.1.0", 2046 | "strip-ansi": "^6.0.0" 2047 | } 2048 | }, 2049 | "y18n": { 2050 | "version": "4.0.3", 2051 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", 2052 | "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", 2053 | "dev": true 2054 | }, 2055 | "yargs": { 2056 | "version": "15.4.1", 2057 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", 2058 | "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", 2059 | "dev": true, 2060 | "requires": { 2061 | "cliui": "^6.0.0", 2062 | "decamelize": "^1.2.0", 2063 | "find-up": "^4.1.0", 2064 | "get-caller-file": "^2.0.1", 2065 | "require-directory": "^2.1.1", 2066 | "require-main-filename": "^2.0.0", 2067 | "set-blocking": "^2.0.0", 2068 | "string-width": "^4.2.0", 2069 | "which-module": "^2.0.0", 2070 | "y18n": "^4.0.0", 2071 | "yargs-parser": "^18.1.2" 2072 | } 2073 | }, 2074 | "yargs-parser": { 2075 | "version": "18.1.3", 2076 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", 2077 | "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", 2078 | "dev": true, 2079 | "requires": { 2080 | "camelcase": "^5.0.0", 2081 | "decamelize": "^1.2.0" 2082 | } 2083 | } 2084 | } 2085 | }, 2086 | "type-detect": { 2087 | "version": "4.0.8", 2088 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", 2089 | "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", 2090 | "dev": true 2091 | }, 2092 | "type-fest": { 2093 | "version": "0.20.2", 2094 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", 2095 | "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" 2096 | }, 2097 | "typescript": { 2098 | "version": "4.2.4", 2099 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz", 2100 | "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==", 2101 | "dev": true 2102 | }, 2103 | "util": { 2104 | "version": "0.10.4", 2105 | "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", 2106 | "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", 2107 | "requires": { 2108 | "inherits": "2.0.3" 2109 | }, 2110 | "dependencies": { 2111 | "inherits": { 2112 | "version": "2.0.3", 2113 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 2114 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 2115 | } 2116 | } 2117 | }, 2118 | "util-deprecate": { 2119 | "version": "1.0.2", 2120 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 2121 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 2122 | }, 2123 | "which": { 2124 | "version": "2.0.2", 2125 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 2126 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 2127 | "dev": true, 2128 | "requires": { 2129 | "isexe": "^2.0.0" 2130 | } 2131 | }, 2132 | "which-module": { 2133 | "version": "2.0.0", 2134 | "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", 2135 | "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", 2136 | "dev": true 2137 | }, 2138 | "wide-align": { 2139 | "version": "1.1.3", 2140 | "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", 2141 | "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", 2142 | "dev": true, 2143 | "requires": { 2144 | "string-width": "^1.0.2 || 2" 2145 | }, 2146 | "dependencies": { 2147 | "ansi-regex": { 2148 | "version": "3.0.0", 2149 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", 2150 | "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", 2151 | "dev": true 2152 | }, 2153 | "string-width": { 2154 | "version": "2.1.1", 2155 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", 2156 | "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", 2157 | "dev": true, 2158 | "requires": { 2159 | "is-fullwidth-code-point": "^2.0.0", 2160 | "strip-ansi": "^4.0.0" 2161 | } 2162 | }, 2163 | "strip-ansi": { 2164 | "version": "4.0.0", 2165 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", 2166 | "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", 2167 | "dev": true, 2168 | "requires": { 2169 | "ansi-regex": "^3.0.0" 2170 | } 2171 | } 2172 | } 2173 | }, 2174 | "widest-line": { 2175 | "version": "3.1.0", 2176 | "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", 2177 | "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", 2178 | "requires": { 2179 | "string-width": "^4.0.0" 2180 | } 2181 | }, 2182 | "workerpool": { 2183 | "version": "6.1.0", 2184 | "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", 2185 | "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==", 2186 | "dev": true 2187 | }, 2188 | "wrap-ansi": { 2189 | "version": "7.0.0", 2190 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 2191 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 2192 | "requires": { 2193 | "ansi-styles": "^4.0.0", 2194 | "string-width": "^4.1.0", 2195 | "strip-ansi": "^6.0.0" 2196 | }, 2197 | "dependencies": { 2198 | "ansi-regex": { 2199 | "version": "5.0.0", 2200 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", 2201 | "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" 2202 | }, 2203 | "strip-ansi": { 2204 | "version": "6.0.0", 2205 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", 2206 | "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", 2207 | "requires": { 2208 | "ansi-regex": "^5.0.0" 2209 | } 2210 | } 2211 | } 2212 | }, 2213 | "wrappy": { 2214 | "version": "1.0.2", 2215 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 2216 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 2217 | }, 2218 | "y18n": { 2219 | "version": "5.0.8", 2220 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", 2221 | "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" 2222 | }, 2223 | "yaml": { 2224 | "version": "1.10.2", 2225 | "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", 2226 | "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", 2227 | "dev": true 2228 | }, 2229 | "yargs": { 2230 | "version": "16.2.0", 2231 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", 2232 | "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", 2233 | "requires": { 2234 | "cliui": "^7.0.2", 2235 | "escalade": "^3.1.1", 2236 | "get-caller-file": "^2.0.5", 2237 | "require-directory": "^2.1.1", 2238 | "string-width": "^4.2.0", 2239 | "y18n": "^5.0.5", 2240 | "yargs-parser": "^20.2.2" 2241 | } 2242 | }, 2243 | "yargs-parser": { 2244 | "version": "20.2.4", 2245 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", 2246 | "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==" 2247 | }, 2248 | "yargs-unparser": { 2249 | "version": "2.0.0", 2250 | "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", 2251 | "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", 2252 | "dev": true, 2253 | "requires": { 2254 | "camelcase": "^6.0.0", 2255 | "decamelize": "^4.0.0", 2256 | "flat": "^5.0.2", 2257 | "is-plain-obj": "^2.1.0" 2258 | }, 2259 | "dependencies": { 2260 | "camelcase": { 2261 | "version": "6.2.0", 2262 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", 2263 | "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", 2264 | "dev": true 2265 | } 2266 | } 2267 | }, 2268 | "yn": { 2269 | "version": "3.1.1", 2270 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 2271 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", 2272 | "dev": true 2273 | }, 2274 | "yocto-queue": { 2275 | "version": "0.1.0", 2276 | "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", 2277 | "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", 2278 | "dev": true 2279 | } 2280 | } 2281 | } 2282 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@biesbjerg/ngx-translate-extract", 3 | "version": "7.0.4", 4 | "description": "Extract strings from projects using ngx-translate", 5 | "main": "dist/index.js", 6 | "typings": "dist/index.d.ts", 7 | "files": [ 8 | "bin/", 9 | "dist/" 10 | ], 11 | "bin": { 12 | "ngx-translate-extract": "bin/cli.js" 13 | }, 14 | "scripts": { 15 | "build": "npm run clean && tsc", 16 | "watch": "npm run clean && tsc --watch", 17 | "clean": "rimraf ./dist", 18 | "lint": "tslint --force './src/**/*.ts'", 19 | "test": "mocha -r ts-node/register tests/**/*.spec.ts" 20 | }, 21 | "husky": { 22 | "hooks": { 23 | "pre-commit": "lint-staged && npm test" 24 | } 25 | }, 26 | "prettier": { 27 | "trailingComma": "none", 28 | "printWidth": 145, 29 | "useTabs": true, 30 | "singleQuote": true 31 | }, 32 | "lint-staged": { 33 | "{src,tests}/**/*.{ts}": [ 34 | "tslint --project tsconfig.json -c tslint.commit.json --fix", 35 | "prettier --write" 36 | ] 37 | }, 38 | "repository": { 39 | "type": "git", 40 | "url": "https://github.com/biesbjerg/ngx-translate-extract.git" 41 | }, 42 | "keywords": [ 43 | "angular", 44 | "ionic", 45 | "ngx-translate", 46 | "extract", 47 | "extractor", 48 | "translate", 49 | "translation", 50 | "i18n", 51 | "gettext" 52 | ], 53 | "author": "Kim Biesbjerg ", 54 | "license": "MIT", 55 | "bugs": { 56 | "url": "https://github.com/biesbjerg/ngx-translate-extract/issues" 57 | }, 58 | "homepage": "https://github.com/biesbjerg/ngx-translate-extract", 59 | "engines": { 60 | "node": ">=11.15.0" 61 | }, 62 | "config": {}, 63 | "devDependencies": { 64 | "@angular/compiler": "^11.2.9", 65 | "@types/braces": "^3.0.0", 66 | "@types/chai": "^4.2.16", 67 | "@types/flat": "^5.0.1", 68 | "@types/gettext-parser": "4.0.0", 69 | "@types/glob": "^7.1.3", 70 | "@types/mkdirp": "^1.0.1", 71 | "@types/mocha": "^8.2.2", 72 | "@types/node": "^14.14.37", 73 | "@types/yargs": "^16.0.1", 74 | "braces": "^3.0.2", 75 | "chai": "^4.3.4", 76 | "husky": "^6.0.0", 77 | "lint-staged": "^10.5.4", 78 | "mocha": "^8.3.2", 79 | "prettier": "^2.2.1", 80 | "rimraf": "^3.0.2", 81 | "ts-node": "^9.1.1", 82 | "tslint": "^6.1.3", 83 | "tslint-config-prettier": "^1.18.0", 84 | "tslint-eslint-rules": "^5.4.0", 85 | "tslint-etc": "^1.13.9", 86 | "typescript": "^4.2.4" 87 | }, 88 | "peerDependencies": { 89 | "@angular/compiler": ">=8.0.0", 90 | "typescript": ">=3.0.0" 91 | }, 92 | "dependencies": { 93 | "@phenomnomnominal/tsquery": "^4.1.1", 94 | "boxen": "^5.0.1", 95 | "colorette": "^1.2.2", 96 | "flat": "^5.0.2", 97 | "gettext-parser": "^4.0.4", 98 | "glob": "^7.1.6", 99 | "mkdirp": "^1.0.4", 100 | "path": "^0.12.7", 101 | "terminal-link": "^2.1.1", 102 | "yargs": "^16.2.0" 103 | } 104 | } -------------------------------------------------------------------------------- /src/cli/cli.ts: -------------------------------------------------------------------------------- 1 | import * as yargs from 'yargs'; 2 | import { red, green } from 'colorette'; 3 | 4 | import { ExtractTask } from './tasks/extract.task'; 5 | import { ParserInterface } from '../parsers/parser.interface'; 6 | import { PipeParser } from '../parsers/pipe.parser'; 7 | import { DirectiveParser } from '../parsers/directive.parser'; 8 | import { ServiceParser } from '../parsers/service.parser'; 9 | import { MarkerParser } from '../parsers/marker.parser'; 10 | import { PostProcessorInterface } from '../post-processors/post-processor.interface'; 11 | import { SortByKeyPostProcessor } from '../post-processors/sort-by-key.post-processor'; 12 | import { KeyAsDefaultValuePostProcessor } from '../post-processors/key-as-default-value.post-processor'; 13 | import { NullAsDefaultValuePostProcessor } from '../post-processors/null-as-default-value.post-processor'; 14 | import { StringAsDefaultValuePostProcessor } from '../post-processors/string-as-default-value.post-processor'; 15 | import { PurgeObsoleteKeysPostProcessor } from '../post-processors/purge-obsolete-keys.post-processor'; 16 | import { CompilerInterface } from '../compilers/compiler.interface'; 17 | import { CompilerFactory } from '../compilers/compiler.factory'; 18 | import { normalizePaths } from '../utils/fs-helpers'; 19 | import { donateMessage } from '../utils/donate'; 20 | 21 | // First parsing pass to be able to access pattern argument for use input/output arguments 22 | const y = yargs.option('patterns', { 23 | alias: 'p', 24 | describe: 'Default patterns', 25 | type: 'array', 26 | default: ['/**/*.html', '/**/*.ts'], 27 | hidden: true 28 | }); 29 | 30 | const parsed = y.parse(); 31 | 32 | export const cli = y 33 | .usage('Extract strings from files for translation.\nUsage: $0 [options]') 34 | .version(require(__dirname + '/../../package.json').version) 35 | .alias('version', 'v') 36 | .help('help') 37 | .alias('help', 'h') 38 | .option('input', { 39 | alias: 'i', 40 | describe: 'Paths you would like to extract strings from. You can use path expansion, glob patterns and multiple paths', 41 | default: [process.env.PWD], 42 | type: 'array', 43 | normalize: true, 44 | required: true 45 | }) 46 | .coerce('input', (input: string[]) => { 47 | const paths = normalizePaths(input, parsed.patterns); 48 | return paths; 49 | }) 50 | .option('output', { 51 | alias: 'o', 52 | describe: 'Paths where you would like to save extracted strings. You can use path expansion, glob patterns and multiple paths', 53 | type: 'array', 54 | normalize: true, 55 | required: true 56 | }) 57 | .coerce('output', (output: string[]) => { 58 | const paths = normalizePaths(output, parsed.patterns); 59 | return paths; 60 | }) 61 | .option('format', { 62 | alias: 'f', 63 | describe: 'Format', 64 | default: 'json', 65 | type: 'string', 66 | choices: ['json', 'namespaced-json', 'pot'] 67 | }) 68 | .option('format-indentation', { 69 | alias: 'fi', 70 | describe: 'Format indentation (JSON/Namedspaced JSON)', 71 | default: '\t', 72 | type: 'string' 73 | }) 74 | .option('replace', { 75 | alias: 'r', 76 | describe: 'Replace the contents of output file if it exists (Merges by default)', 77 | type: 'boolean' 78 | }) 79 | .option('sort', { 80 | alias: 's', 81 | describe: 'Sort strings in alphabetical order', 82 | type: 'boolean' 83 | }) 84 | .option('clean', { 85 | alias: 'c', 86 | describe: 'Remove obsolete strings after merge', 87 | type: 'boolean' 88 | }) 89 | .option('key-as-default-value', { 90 | alias: 'k', 91 | describe: 'Use key as default value', 92 | type: 'boolean', 93 | conflicts: ['null-as-default-value', 'string-as-default-value'] 94 | }) 95 | .option('null-as-default-value', { 96 | alias: 'n', 97 | describe: 'Use null as default value', 98 | type: 'boolean', 99 | conflicts: ['key-as-default-value', 'string-as-default-value'] 100 | }) 101 | .option('string-as-default-value', { 102 | alias: 'd', 103 | describe: 'Use string as default value', 104 | type: 'string', 105 | conflicts: ['null-as-default-value', 'key-as-default-value'] 106 | }) 107 | .group(['format', 'format-indentation', 'sort', 'clean', 'replace'], 'Output') 108 | .group(['key-as-default-value', 'null-as-default-value', 'string-as-default-value'], 'Extracted key value (defaults to empty string)') 109 | .conflicts('key-as-default-value', 'null-as-default-value') 110 | .example(`$0 -i ./src-a/ -i ./src-b/ -o strings.json`, 'Extract (ts, html) from multiple paths') 111 | .example(`$0 -i './{src-a,src-b}/' -o strings.json`, 'Extract (ts, html) from multiple paths using brace expansion') 112 | .example(`$0 -i ./src/ -o ./i18n/da.json -o ./i18n/en.json`, 'Extract (ts, html) and save to da.json and en.json') 113 | .example(`$0 -i ./src/ -o './i18n/{en,da}.json'`, 'Extract (ts, html) and save to da.json and en.json using brace expansion') 114 | .example(`$0 -i './src/**/*.{ts,tsx,html}' -o strings.json`, 'Extract from ts, tsx and html') 115 | .example(`$0 -i './src/**/!(*.spec).{ts,html}' -o strings.json`, 'Extract from ts, html, excluding files with ".spec" in filename') 116 | .wrap(110) 117 | .exitProcess(true) 118 | .parse(process.argv); 119 | 120 | const extractTask = new ExtractTask(cli.input, cli.output, { 121 | replace: cli.replace 122 | }); 123 | 124 | // Parsers 125 | const parsers: ParserInterface[] = [new PipeParser(), new DirectiveParser(), new ServiceParser(), new MarkerParser()]; 126 | extractTask.setParsers(parsers); 127 | 128 | // Post processors 129 | const postProcessors: PostProcessorInterface[] = []; 130 | if (cli.clean) { 131 | postProcessors.push(new PurgeObsoleteKeysPostProcessor()); 132 | } 133 | if (cli.keyAsDefaultValue) { 134 | postProcessors.push(new KeyAsDefaultValuePostProcessor()); 135 | } else if (cli.nullAsDefaultValue) { 136 | postProcessors.push(new NullAsDefaultValuePostProcessor()); 137 | } else if (cli.stringAsDefaultValue) { 138 | postProcessors.push(new StringAsDefaultValuePostProcessor({ defaultValue: cli.stringAsDefaultValue as string })); 139 | } 140 | 141 | if (cli.sort) { 142 | postProcessors.push(new SortByKeyPostProcessor()); 143 | } 144 | extractTask.setPostProcessors(postProcessors); 145 | 146 | // Compiler 147 | const compiler: CompilerInterface = CompilerFactory.create(cli.format, { 148 | indentation: cli.formatIndentation 149 | }); 150 | extractTask.setCompiler(compiler); 151 | 152 | // Run task 153 | try { 154 | extractTask.execute(); 155 | console.log(green('\nDone.\n')); 156 | console.log(donateMessage); 157 | process.exit(0); 158 | } catch (e) { 159 | console.log(red(`\nAn error occurred: ${e}\n`)); 160 | process.exit(1); 161 | } 162 | -------------------------------------------------------------------------------- /src/cli/tasks/extract.task.ts: -------------------------------------------------------------------------------- 1 | import { TranslationCollection } from '../../utils/translation.collection'; 2 | import { TaskInterface } from './task.interface'; 3 | import { ParserInterface } from '../../parsers/parser.interface'; 4 | import { PostProcessorInterface } from '../../post-processors/post-processor.interface'; 5 | import { CompilerInterface } from '../../compilers/compiler.interface'; 6 | 7 | import { cyan, green, bold, dim, red } from 'colorette'; 8 | import * as glob from 'glob'; 9 | import * as fs from 'fs'; 10 | import * as path from 'path'; 11 | import * as mkdirp from 'mkdirp'; 12 | 13 | export interface ExtractTaskOptionsInterface { 14 | replace?: boolean; 15 | } 16 | 17 | export class ExtractTask implements TaskInterface { 18 | protected options: ExtractTaskOptionsInterface = { 19 | replace: false 20 | }; 21 | 22 | protected parsers: ParserInterface[] = []; 23 | protected postProcessors: PostProcessorInterface[] = []; 24 | protected compiler: CompilerInterface; 25 | 26 | public constructor(protected inputs: string[], protected outputs: string[], options?: ExtractTaskOptionsInterface) { 27 | this.inputs = inputs.map((input) => path.resolve(input)); 28 | this.outputs = outputs.map((output) => path.resolve(output)); 29 | this.options = { ...this.options, ...options }; 30 | } 31 | 32 | public execute(): void { 33 | if (!this.compiler) { 34 | throw new Error('No compiler configured'); 35 | } 36 | 37 | this.printEnabledParsers(); 38 | this.printEnabledPostProcessors(); 39 | this.printEnabledCompiler(); 40 | 41 | this.out(bold('Extracting:')); 42 | const extracted = this.extract(); 43 | this.out(green(`\nFound %d strings.\n`), extracted.count()); 44 | 45 | this.out(bold('Saving:')); 46 | 47 | this.outputs.forEach((output) => { 48 | let dir: string = output; 49 | let filename: string = `strings.${this.compiler.extension}`; 50 | if (!fs.existsSync(output) || !fs.statSync(output).isDirectory()) { 51 | dir = path.dirname(output); 52 | filename = path.basename(output); 53 | } 54 | 55 | const outputPath: string = path.join(dir, filename); 56 | 57 | let existing: TranslationCollection = new TranslationCollection(); 58 | if (!this.options.replace && fs.existsSync(outputPath)) { 59 | try { 60 | existing = this.compiler.parse(fs.readFileSync(outputPath, 'utf-8')); 61 | } catch (e) { 62 | this.out(`%s %s`, dim(`- ${outputPath}`), red(`[ERROR]`)); 63 | throw e; 64 | } 65 | } 66 | 67 | // merge extracted strings with existing 68 | const draft = extracted.union(existing); 69 | 70 | // Run collection through post processors 71 | const final = this.process(draft, extracted, existing); 72 | 73 | // Save 74 | try { 75 | let event = 'CREATED'; 76 | if (fs.existsSync(outputPath)) { 77 | this.options.replace ? (event = 'REPLACED') : (event = 'MERGED'); 78 | } 79 | this.save(outputPath, final); 80 | this.out(`%s %s`, dim(`- ${outputPath}`), green(`[${event}]`)); 81 | } catch (e) { 82 | this.out(`%s %s`, dim(`- ${outputPath}`), red(`[ERROR]`)); 83 | throw e; 84 | } 85 | }); 86 | } 87 | 88 | public setParsers(parsers: ParserInterface[]): this { 89 | this.parsers = parsers; 90 | return this; 91 | } 92 | 93 | public setPostProcessors(postProcessors: PostProcessorInterface[]): this { 94 | this.postProcessors = postProcessors; 95 | return this; 96 | } 97 | 98 | public setCompiler(compiler: CompilerInterface): this { 99 | this.compiler = compiler; 100 | return this; 101 | } 102 | 103 | /** 104 | * Extract strings from specified input dirs using configured parsers 105 | */ 106 | protected extract(): TranslationCollection { 107 | let collection: TranslationCollection = new TranslationCollection(); 108 | this.inputs.forEach((pattern) => { 109 | this.getFiles(pattern).forEach((filePath) => { 110 | this.out(dim('- %s'), filePath); 111 | const contents: string = fs.readFileSync(filePath, 'utf-8'); 112 | this.parsers.forEach((parser) => { 113 | const extracted = parser.extract(contents, filePath); 114 | if (extracted instanceof TranslationCollection) { 115 | collection = collection.union(extracted); 116 | } 117 | }); 118 | }); 119 | }); 120 | return collection; 121 | } 122 | 123 | /** 124 | * Run strings through configured post processors 125 | */ 126 | protected process(draft: TranslationCollection, extracted: TranslationCollection, existing: TranslationCollection): TranslationCollection { 127 | this.postProcessors.forEach((postProcessor) => { 128 | draft = postProcessor.process(draft, extracted, existing); 129 | }); 130 | return draft; 131 | } 132 | 133 | /** 134 | * Compile and save translations 135 | * @param collection 136 | */ 137 | protected save(output: string, collection: TranslationCollection): void { 138 | const dir = path.dirname(output); 139 | if (!fs.existsSync(dir)) { 140 | mkdirp.sync(dir); 141 | } 142 | fs.writeFileSync(output, this.compiler.compile(collection)); 143 | } 144 | 145 | /** 146 | * Get all files matching pattern 147 | */ 148 | protected getFiles(pattern: string): string[] { 149 | return glob.sync(pattern).filter((filePath) => fs.statSync(filePath).isFile()); 150 | } 151 | 152 | protected out(...args: any[]): void { 153 | console.log.apply(this, arguments); 154 | } 155 | 156 | protected printEnabledParsers(): void { 157 | this.out(cyan('Enabled parsers:')); 158 | if (this.parsers.length) { 159 | this.out(cyan(dim(this.parsers.map((parser) => `- ${parser.constructor.name}`).join('\n')))); 160 | } else { 161 | this.out(cyan(dim('(none)'))); 162 | } 163 | this.out(); 164 | } 165 | 166 | protected printEnabledPostProcessors(): void { 167 | this.out(cyan('Enabled post processors:')); 168 | if (this.postProcessors.length) { 169 | this.out(cyan(dim(this.postProcessors.map((postProcessor) => `- ${postProcessor.constructor.name}`).join('\n')))); 170 | } else { 171 | this.out(cyan(dim('(none)'))); 172 | } 173 | this.out(); 174 | } 175 | 176 | protected printEnabledCompiler(): void { 177 | this.out(cyan('Compiler:')); 178 | this.out(cyan(dim(`- ${this.compiler.constructor.name}`))); 179 | this.out(); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/cli/tasks/task.interface.ts: -------------------------------------------------------------------------------- 1 | export interface TaskInterface { 2 | execute(): void; 3 | } 4 | -------------------------------------------------------------------------------- /src/compilers/compiler.factory.ts: -------------------------------------------------------------------------------- 1 | import { CompilerInterface } from '../compilers/compiler.interface'; 2 | import { JsonCompiler } from '../compilers/json.compiler'; 3 | import { NamespacedJsonCompiler } from '../compilers/namespaced-json.compiler'; 4 | import { PoCompiler } from '../compilers/po.compiler'; 5 | 6 | export class CompilerFactory { 7 | public static create(format: string, options?: {}): CompilerInterface { 8 | switch (format) { 9 | case 'pot': 10 | return new PoCompiler(options); 11 | case 'json': 12 | return new JsonCompiler(options); 13 | case 'namespaced-json': 14 | return new NamespacedJsonCompiler(options); 15 | default: 16 | throw new Error(`Unknown format: ${format}`); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/compilers/compiler.interface.ts: -------------------------------------------------------------------------------- 1 | import { TranslationCollection } from '../utils/translation.collection'; 2 | 3 | export interface CompilerInterface { 4 | extension: string; 5 | 6 | compile(collection: TranslationCollection): string; 7 | 8 | parse(contents: string): TranslationCollection; 9 | } 10 | -------------------------------------------------------------------------------- /src/compilers/json.compiler.ts: -------------------------------------------------------------------------------- 1 | import { CompilerInterface } from './compiler.interface'; 2 | import { TranslationCollection } from '../utils/translation.collection'; 3 | import { stripBOM } from '../utils/utils'; 4 | 5 | import { flatten } from 'flat'; 6 | 7 | export class JsonCompiler implements CompilerInterface { 8 | public indentation: string = '\t'; 9 | 10 | public extension: string = 'json'; 11 | 12 | public constructor(options?: any) { 13 | if (options && typeof options.indentation !== 'undefined') { 14 | this.indentation = options.indentation; 15 | } 16 | } 17 | 18 | public compile(collection: TranslationCollection): string { 19 | return JSON.stringify(collection.values, null, this.indentation); 20 | } 21 | 22 | public parse(contents: string): TranslationCollection { 23 | let values: any = JSON.parse(stripBOM(contents)); 24 | if (this.isNamespacedJsonFormat(values)) { 25 | values = flatten(values); 26 | } 27 | return new TranslationCollection(values); 28 | } 29 | 30 | protected isNamespacedJsonFormat(values: any): boolean { 31 | return Object.keys(values).some((key) => typeof values[key] === 'object'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/compilers/namespaced-json.compiler.ts: -------------------------------------------------------------------------------- 1 | import { CompilerInterface } from './compiler.interface'; 2 | import { TranslationCollection } from '../utils/translation.collection'; 3 | import { stripBOM } from '../utils/utils'; 4 | 5 | import { flatten, unflatten } from 'flat'; 6 | 7 | export class NamespacedJsonCompiler implements CompilerInterface { 8 | public indentation: string = '\t'; 9 | 10 | public extension = 'json'; 11 | 12 | public constructor(options?: any) { 13 | if (options && typeof options.indentation !== 'undefined') { 14 | this.indentation = options.indentation; 15 | } 16 | } 17 | 18 | public compile(collection: TranslationCollection): string { 19 | const values: {} = unflatten(collection.values, { 20 | object: true 21 | }); 22 | return JSON.stringify(values, null, this.indentation); 23 | } 24 | 25 | public parse(contents: string): TranslationCollection { 26 | const values: {} = flatten(JSON.parse(stripBOM(contents))); 27 | return new TranslationCollection(values); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/compilers/po.compiler.ts: -------------------------------------------------------------------------------- 1 | import { CompilerInterface } from './compiler.interface'; 2 | import { TranslationCollection, TranslationType } from '../utils/translation.collection'; 3 | 4 | import { po } from 'gettext-parser'; 5 | 6 | export class PoCompiler implements CompilerInterface { 7 | public extension: string = 'po'; 8 | 9 | /** 10 | * Translation domain 11 | */ 12 | public domain: string = ''; 13 | 14 | public constructor(options?: any) {} 15 | 16 | public compile(collection: TranslationCollection): string { 17 | const data = { 18 | charset: 'utf-8', 19 | headers: { 20 | 'mime-version': '1.0', 21 | 'content-type': 'text/plain; charset=utf-8', 22 | 'content-transfer-encoding': '8bit' 23 | }, 24 | translations: { 25 | [this.domain]: Object.keys(collection.values).reduce((translations, key) => { 26 | return { 27 | ...translations, 28 | [key]: { 29 | msgid: key, 30 | msgstr: collection.get(key) 31 | } 32 | }; 33 | }, {} as any) 34 | } 35 | }; 36 | 37 | return po.compile(data).toString('utf8'); 38 | } 39 | 40 | public parse(contents: string): TranslationCollection { 41 | const collection = new TranslationCollection(); 42 | 43 | const parsedPo = po.parse(contents, 'utf8'); 44 | 45 | if (!parsedPo.translations.hasOwnProperty(this.domain)) { 46 | return collection; 47 | } 48 | 49 | const values = Object.keys(parsedPo.translations[this.domain]) 50 | .filter((key) => key.length > 0) 51 | .reduce((result, key) => { 52 | return { 53 | ...result, 54 | [key]: parsedPo.translations[this.domain][key].msgstr.pop() 55 | }; 56 | }, {} as TranslationType); 57 | 58 | return new TranslationCollection(values); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './utils/translation.collection'; 2 | export * from './utils/utils'; 3 | 4 | export * from './cli/cli'; 5 | export * from './cli/tasks/task.interface'; 6 | export * from './cli/tasks/extract.task'; 7 | 8 | export * from './parsers/parser.interface'; 9 | export * from './parsers/directive.parser'; 10 | export * from './parsers/pipe.parser'; 11 | export * from './parsers/service.parser'; 12 | export * from './parsers/marker.parser'; 13 | 14 | export * from './compilers/compiler.interface'; 15 | export * from './compilers/compiler.factory'; 16 | export * from './compilers/json.compiler'; 17 | export * from './compilers/namespaced-json.compiler'; 18 | export * from './compilers/po.compiler'; 19 | 20 | export * from './post-processors/post-processor.interface'; 21 | export * from './post-processors/key-as-default-value.post-processor'; 22 | export * from './post-processors/purge-obsolete-keys.post-processor'; 23 | export * from './post-processors/sort-by-key.post-processor'; 24 | -------------------------------------------------------------------------------- /src/parsers/directive.parser.ts: -------------------------------------------------------------------------------- 1 | import { 2 | parseTemplate, 3 | TmplAstNode as Node, 4 | TmplAstElement as Element, 5 | TmplAstText as Text, 6 | TmplAstTemplate as Template, 7 | TmplAstTextAttribute as TextAttribute, 8 | TmplAstBoundAttribute as BoundAttribute, 9 | AST, 10 | ASTWithSource, 11 | LiteralPrimitive, 12 | Conditional, 13 | Binary, 14 | BindingPipe, 15 | Interpolation, 16 | LiteralArray, 17 | LiteralMap 18 | } from '@angular/compiler'; 19 | 20 | import { ParserInterface } from './parser.interface'; 21 | import { TranslationCollection } from '../utils/translation.collection'; 22 | import { isPathAngularComponent, extractComponentInlineTemplate } from '../utils/utils'; 23 | 24 | const TRANSLATE_ATTR_NAME = 'translate'; 25 | type ElementLike = Element | Template; 26 | 27 | export class DirectiveParser implements ParserInterface { 28 | public extract(source: string, filePath: string): TranslationCollection | null { 29 | let collection: TranslationCollection = new TranslationCollection(); 30 | 31 | if (filePath && isPathAngularComponent(filePath)) { 32 | source = extractComponentInlineTemplate(source); 33 | } 34 | const nodes: Node[] = this.parseTemplate(source, filePath); 35 | const elements: ElementLike[] = this.getElementsWithTranslateAttribute(nodes); 36 | 37 | elements.forEach((element) => { 38 | const attribute = this.getAttribute(element, TRANSLATE_ATTR_NAME); 39 | if (attribute?.value) { 40 | collection = collection.add(attribute.value); 41 | return; 42 | } 43 | 44 | const boundAttribute = this.getBoundAttribute(element, TRANSLATE_ATTR_NAME); 45 | if (boundAttribute?.value) { 46 | this.getLiteralPrimitives(boundAttribute.value).forEach((literalPrimitive) => { 47 | collection = collection.add(literalPrimitive.value); 48 | }); 49 | return; 50 | } 51 | 52 | const textNodes = this.getTextNodes(element); 53 | textNodes.forEach((textNode) => { 54 | collection = collection.add(textNode.value.trim()); 55 | }); 56 | }); 57 | return collection; 58 | } 59 | 60 | /** 61 | * Find all ElementLike nodes with a translate attribute 62 | * @param nodes 63 | */ 64 | protected getElementsWithTranslateAttribute(nodes: Node[]): ElementLike[] { 65 | let elements: ElementLike[] = []; 66 | nodes.filter(this.isElementLike).forEach((element) => { 67 | if (this.hasAttribute(element, TRANSLATE_ATTR_NAME)) { 68 | elements = [...elements, element]; 69 | } 70 | if (this.hasBoundAttribute(element, TRANSLATE_ATTR_NAME)) { 71 | elements = [...elements, element]; 72 | } 73 | const childElements = this.getElementsWithTranslateAttribute(element.children); 74 | if (childElements.length) { 75 | elements = [...elements, ...childElements]; 76 | } 77 | }); 78 | return elements; 79 | } 80 | 81 | /** 82 | * Get direct child nodes of type Text 83 | * @param element 84 | */ 85 | protected getTextNodes(element: ElementLike): Text[] { 86 | return element.children.filter(this.isText); 87 | } 88 | 89 | /** 90 | * Check if attribute is present on element 91 | * @param element 92 | */ 93 | protected hasAttribute(element: ElementLike, name: string): boolean { 94 | return this.getAttribute(element, name) !== undefined; 95 | } 96 | 97 | /** 98 | * Get attribute value if present on element 99 | * @param element 100 | */ 101 | protected getAttribute(element: ElementLike, name: string): TextAttribute { 102 | return element.attributes.find((attribute) => attribute.name === name); 103 | } 104 | 105 | /** 106 | * Check if bound attribute is present on element 107 | * @param element 108 | * @param name 109 | */ 110 | protected hasBoundAttribute(element: ElementLike, name: string): boolean { 111 | return this.getBoundAttribute(element, name) !== undefined; 112 | } 113 | 114 | /** 115 | * Get bound attribute if present on element 116 | * @param element 117 | * @param name 118 | */ 119 | protected getBoundAttribute(element: ElementLike, name: string): BoundAttribute { 120 | return element.inputs.find((input) => input.name === name); 121 | } 122 | 123 | /** 124 | * Get literal primitives from expression 125 | * @param exp 126 | */ 127 | protected getLiteralPrimitives(exp: AST): LiteralPrimitive[] { 128 | if (exp instanceof LiteralPrimitive) { 129 | return [exp]; 130 | } 131 | 132 | let visit: AST[] = []; 133 | if (exp instanceof Interpolation) { 134 | visit = exp.expressions; 135 | } else if (exp instanceof LiteralArray) { 136 | visit = exp.expressions; 137 | } else if (exp instanceof LiteralMap) { 138 | visit = exp.values; 139 | } else if (exp instanceof BindingPipe) { 140 | visit = [exp.exp]; 141 | } else if (exp instanceof Conditional) { 142 | visit = [exp.trueExp, exp.falseExp]; 143 | } else if (exp instanceof Binary) { 144 | visit = [exp.left, exp.right]; 145 | } else if (exp instanceof ASTWithSource) { 146 | visit = [exp.ast]; 147 | } 148 | 149 | let results: LiteralPrimitive[] = []; 150 | visit.forEach((child) => { 151 | results = [...results, ...this.getLiteralPrimitives(child)]; 152 | }); 153 | return results; 154 | } 155 | 156 | /** 157 | * Check if node type is ElementLike 158 | * @param node 159 | */ 160 | protected isElementLike(node: Node): node is ElementLike { 161 | return node instanceof Element || node instanceof Template; 162 | } 163 | 164 | /** 165 | * Check if node type is Text 166 | * @param node 167 | */ 168 | protected isText(node: Node): node is Text { 169 | return node instanceof Text; 170 | } 171 | 172 | /** 173 | * Parse a template into nodes 174 | * @param template 175 | * @param path 176 | */ 177 | protected parseTemplate(template: string, path: string): Node[] { 178 | return parseTemplate(template, path).nodes; 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/parsers/marker.parser.ts: -------------------------------------------------------------------------------- 1 | import { tsquery } from '@phenomnomnominal/tsquery'; 2 | 3 | import { ParserInterface } from './parser.interface'; 4 | import { TranslationCollection } from '../utils/translation.collection'; 5 | import { getNamedImportAlias, findFunctionCallExpressions, getStringsFromExpression } from '../utils/ast-helpers'; 6 | 7 | const MARKER_MODULE_NAME = '@biesbjerg/ngx-translate-extract-marker'; 8 | const MARKER_IMPORT_NAME = 'marker'; 9 | 10 | export class MarkerParser implements ParserInterface { 11 | public extract(source: string, filePath: string): TranslationCollection | null { 12 | const sourceFile = tsquery.ast(source, filePath); 13 | 14 | const markerImportName = getNamedImportAlias(sourceFile, MARKER_MODULE_NAME, MARKER_IMPORT_NAME); 15 | if (!markerImportName) { 16 | return null; 17 | } 18 | 19 | let collection: TranslationCollection = new TranslationCollection(); 20 | 21 | const callExpressions = findFunctionCallExpressions(sourceFile, markerImportName); 22 | callExpressions.forEach((callExpression) => { 23 | const [firstArg] = callExpression.arguments; 24 | if (!firstArg) { 25 | return; 26 | } 27 | const strings = getStringsFromExpression(firstArg); 28 | collection = collection.addKeys(strings); 29 | }); 30 | return collection; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/parsers/parser.interface.ts: -------------------------------------------------------------------------------- 1 | import { TranslationCollection } from '../utils/translation.collection'; 2 | 3 | export interface ParserInterface { 4 | extract(source: string, filePath: string): TranslationCollection | null; 5 | } 6 | -------------------------------------------------------------------------------- /src/parsers/pipe.parser.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AST, 3 | TmplAstNode, 4 | parseTemplate, 5 | BindingPipe, 6 | LiteralPrimitive, 7 | Conditional, 8 | TmplAstTextAttribute, 9 | Binary, 10 | LiteralMap, 11 | LiteralArray, 12 | Interpolation, 13 | MethodCall 14 | } from '@angular/compiler'; 15 | 16 | import { ParserInterface } from './parser.interface'; 17 | import { TranslationCollection } from '../utils/translation.collection'; 18 | import { isPathAngularComponent, extractComponentInlineTemplate } from '../utils/utils'; 19 | 20 | const TRANSLATE_PIPE_NAME = 'translate'; 21 | 22 | export class PipeParser implements ParserInterface { 23 | public extract(source: string, filePath: string): TranslationCollection | null { 24 | if (filePath && isPathAngularComponent(filePath)) { 25 | source = extractComponentInlineTemplate(source); 26 | } 27 | 28 | let collection: TranslationCollection = new TranslationCollection(); 29 | const nodes: TmplAstNode[] = this.parseTemplate(source, filePath); 30 | const pipes: BindingPipe[] = nodes.map((node) => this.findPipesInNode(node)).flat(); 31 | pipes.forEach((pipe) => { 32 | this.parseTranslationKeysFromPipe(pipe).forEach((key: string) => { 33 | collection = collection.add(key); 34 | }); 35 | }); 36 | return collection; 37 | } 38 | 39 | protected findPipesInNode(node: any): BindingPipe[] { 40 | let ret: BindingPipe[] = []; 41 | 42 | if (node?.children) { 43 | ret = node.children.reduce( 44 | (result: BindingPipe[], childNode: TmplAstNode) => { 45 | const children = this.findPipesInNode(childNode); 46 | return result.concat(children); 47 | }, 48 | [ret] 49 | ); 50 | } 51 | 52 | if (node?.value?.ast) { 53 | ret.push(...this.getTranslatablesFromAst(node.value.ast)); 54 | } 55 | 56 | if (node?.attributes) { 57 | const translateableAttributes = node.attributes.filter((attr: TmplAstTextAttribute) => { 58 | return attr.name === TRANSLATE_PIPE_NAME; 59 | }); 60 | ret = [...ret, ...translateableAttributes]; 61 | } 62 | 63 | if (node?.inputs) { 64 | node.inputs.forEach((input: any) => { 65 | // 66 | if (input?.value?.ast) { 67 | ret.push(...this.getTranslatablesFromAst(input.value.ast)); 68 | } 69 | }); 70 | } 71 | 72 | return ret; 73 | } 74 | 75 | protected parseTranslationKeysFromPipe(pipeContent: BindingPipe | LiteralPrimitive | Conditional): string[] { 76 | const ret: string[] = []; 77 | if (pipeContent instanceof LiteralPrimitive) { 78 | ret.push(pipeContent.value); 79 | } else if (pipeContent instanceof Conditional) { 80 | const trueExp: LiteralPrimitive | Conditional = pipeContent.trueExp as any; 81 | ret.push(...this.parseTranslationKeysFromPipe(trueExp)); 82 | const falseExp: LiteralPrimitive | Conditional = pipeContent.falseExp as any; 83 | ret.push(...this.parseTranslationKeysFromPipe(falseExp)); 84 | } else if (pipeContent instanceof BindingPipe) { 85 | ret.push(...this.parseTranslationKeysFromPipe(pipeContent.exp as any)); 86 | } 87 | return ret; 88 | } 89 | 90 | protected getTranslatablesFromAst(ast: AST): BindingPipe[] { 91 | // the entire expression is the translate pipe, e.g.: 92 | // - 'foo' | translate 93 | // - (condition ? 'foo' : 'bar') | translate 94 | if (this.expressionIsOrHasBindingPipe(ast)) { 95 | return [ast]; 96 | } 97 | 98 | // angular double curly bracket interpolation, e.g.: 99 | // - {{ expressions }} 100 | if (ast instanceof Interpolation) { 101 | return this.getTranslatablesFromAsts(ast.expressions); 102 | } 103 | 104 | // ternary operator, e.g.: 105 | // - condition ? null : ('foo' | translate) 106 | // - condition ? ('foo' | translate) : null 107 | if (ast instanceof Conditional) { 108 | return this.getTranslatablesFromAsts([ast.trueExp, ast.falseExp]); 109 | } 110 | 111 | // string concatenation, e.g.: 112 | // - 'foo' + 'bar' + ('baz' | translate) 113 | if (ast instanceof Binary) { 114 | return this.getTranslatablesFromAsts([ast.left, ast.right]); 115 | } 116 | 117 | // a pipe on the outer expression, but not the translate pipe - ignore the pipe, visit the expression, e.g.: 118 | // - { foo: 'Hello' | translate } | json 119 | if (ast instanceof BindingPipe) { 120 | return this.getTranslatablesFromAst(ast.exp); 121 | } 122 | 123 | // object - ignore the keys, visit all values, e.g.: 124 | // - { key1: 'value1' | translate, key2: 'value2' | translate } 125 | if (ast instanceof LiteralMap) { 126 | return this.getTranslatablesFromAsts(ast.values); 127 | } 128 | 129 | // array - visit all its values, e.g.: 130 | // - [ 'value1' | translate, 'value2' | translate ] 131 | if (ast instanceof LiteralArray) { 132 | return this.getTranslatablesFromAsts(ast.expressions); 133 | } 134 | 135 | if (ast instanceof MethodCall) { 136 | return this.getTranslatablesFromAsts(ast.args); 137 | } 138 | 139 | return []; 140 | } 141 | 142 | protected getTranslatablesFromAsts(asts: AST[]): BindingPipe[] { 143 | return this.flatten(asts.map((ast) => this.getTranslatablesFromAst(ast))); 144 | } 145 | 146 | protected flatten(array: T[][]): T[] { 147 | return [].concat(...array); 148 | } 149 | 150 | protected expressionIsOrHasBindingPipe(exp: any): exp is BindingPipe { 151 | if (exp.name && exp.name === TRANSLATE_PIPE_NAME) { 152 | return true; 153 | } 154 | if (exp.exp && exp.exp instanceof BindingPipe) { 155 | return this.expressionIsOrHasBindingPipe(exp.exp); 156 | } 157 | return false; 158 | } 159 | 160 | protected parseTemplate(template: string, path: string): TmplAstNode[] { 161 | return parseTemplate(template, path).nodes; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/parsers/service.parser.ts: -------------------------------------------------------------------------------- 1 | import { ClassDeclaration, CallExpression } from 'typescript'; 2 | import { tsquery } from '@phenomnomnominal/tsquery'; 3 | 4 | import { ParserInterface } from './parser.interface'; 5 | import { TranslationCollection } from '../utils/translation.collection'; 6 | import { 7 | findClassDeclarations, 8 | findClassPropertyByType, 9 | findPropertyCallExpressions, 10 | findMethodCallExpressions, 11 | getStringsFromExpression, 12 | findMethodParameterByType, 13 | findConstructorDeclaration 14 | } from '../utils/ast-helpers'; 15 | 16 | const TRANSLATE_SERVICE_TYPE_REFERENCE = 'TranslateService'; 17 | const TRANSLATE_SERVICE_METHOD_NAMES = ['get', 'instant', 'stream']; 18 | 19 | export class ServiceParser implements ParserInterface { 20 | public extract(source: string, filePath: string): TranslationCollection | null { 21 | const sourceFile = tsquery.ast(source, filePath); 22 | 23 | const classDeclarations = findClassDeclarations(sourceFile); 24 | if (!classDeclarations) { 25 | return null; 26 | } 27 | 28 | let collection: TranslationCollection = new TranslationCollection(); 29 | 30 | classDeclarations.forEach((classDeclaration) => { 31 | const callExpressions = [ 32 | ...this.findConstructorParamCallExpressions(classDeclaration), 33 | ...this.findPropertyCallExpressions(classDeclaration) 34 | ]; 35 | 36 | callExpressions.forEach((callExpression) => { 37 | const [firstArg] = callExpression.arguments; 38 | if (!firstArg) { 39 | return; 40 | } 41 | const strings = getStringsFromExpression(firstArg); 42 | collection = collection.addKeys(strings); 43 | }); 44 | }); 45 | return collection; 46 | } 47 | 48 | protected findConstructorParamCallExpressions(classDeclaration: ClassDeclaration): CallExpression[] { 49 | const constructorDeclaration = findConstructorDeclaration(classDeclaration); 50 | if (!constructorDeclaration) { 51 | return []; 52 | } 53 | const paramName = findMethodParameterByType(constructorDeclaration, TRANSLATE_SERVICE_TYPE_REFERENCE); 54 | return findMethodCallExpressions(constructorDeclaration, paramName, TRANSLATE_SERVICE_METHOD_NAMES); 55 | } 56 | 57 | protected findPropertyCallExpressions(classDeclaration: ClassDeclaration): CallExpression[] { 58 | const propName: string = findClassPropertyByType(classDeclaration, TRANSLATE_SERVICE_TYPE_REFERENCE); 59 | if (!propName) { 60 | return []; 61 | } 62 | return findPropertyCallExpressions(classDeclaration, propName, TRANSLATE_SERVICE_METHOD_NAMES); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/post-processors/key-as-default-value.post-processor.ts: -------------------------------------------------------------------------------- 1 | import { TranslationCollection } from '../utils/translation.collection'; 2 | import { PostProcessorInterface } from './post-processor.interface'; 3 | 4 | export class KeyAsDefaultValuePostProcessor implements PostProcessorInterface { 5 | public name: string = 'KeyAsDefaultValue'; 6 | 7 | public process(draft: TranslationCollection, extracted: TranslationCollection, existing: TranslationCollection): TranslationCollection { 8 | return draft.map((key, val) => (val === '' ? key : val)); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/post-processors/null-as-default-value.post-processor.ts: -------------------------------------------------------------------------------- 1 | import { TranslationCollection } from '../utils/translation.collection'; 2 | import { PostProcessorInterface } from './post-processor.interface'; 3 | 4 | export class NullAsDefaultValuePostProcessor implements PostProcessorInterface { 5 | public name: string = 'NullAsDefaultValue'; 6 | 7 | public process(draft: TranslationCollection, extracted: TranslationCollection, existing: TranslationCollection): TranslationCollection { 8 | return draft.map((key, val) => (existing.get(key) === undefined ? null : val)); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/post-processors/post-processor.interface.ts: -------------------------------------------------------------------------------- 1 | import { TranslationCollection } from '../utils/translation.collection'; 2 | 3 | export interface PostProcessorInterface { 4 | name: string; 5 | 6 | process(draft: TranslationCollection, extracted: TranslationCollection, existing: TranslationCollection): TranslationCollection; 7 | } 8 | -------------------------------------------------------------------------------- /src/post-processors/purge-obsolete-keys.post-processor.ts: -------------------------------------------------------------------------------- 1 | import { TranslationCollection } from '../utils/translation.collection'; 2 | import { PostProcessorInterface } from './post-processor.interface'; 3 | 4 | export class PurgeObsoleteKeysPostProcessor implements PostProcessorInterface { 5 | public name: string = 'PurgeObsoleteKeys'; 6 | 7 | public process(draft: TranslationCollection, extracted: TranslationCollection, existing: TranslationCollection): TranslationCollection { 8 | return draft.intersect(extracted); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/post-processors/sort-by-key.post-processor.ts: -------------------------------------------------------------------------------- 1 | import { TranslationCollection } from '../utils/translation.collection'; 2 | import { PostProcessorInterface } from './post-processor.interface'; 3 | 4 | export class SortByKeyPostProcessor implements PostProcessorInterface { 5 | public name: string = 'SortByKey'; 6 | 7 | public process(draft: TranslationCollection, extracted: TranslationCollection, existing: TranslationCollection): TranslationCollection { 8 | return draft.sort(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/post-processors/string-as-default-value.post-processor.ts: -------------------------------------------------------------------------------- 1 | import { TranslationCollection } from '../utils/translation.collection'; 2 | import { PostProcessorInterface } from './post-processor.interface'; 3 | 4 | interface Options { 5 | defaultValue: string; 6 | } 7 | 8 | export class StringAsDefaultValuePostProcessor implements PostProcessorInterface { 9 | public name: string = 'StringAsDefaultValue'; 10 | 11 | public constructor(protected options: Options) {} 12 | 13 | public process(draft: TranslationCollection, extracted: TranslationCollection, existing: TranslationCollection): TranslationCollection { 14 | return draft.map((key, val) => (existing.get(key) === undefined ? this.options.defaultValue : val)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/ast-helpers.ts: -------------------------------------------------------------------------------- 1 | import { tsquery } from '@phenomnomnominal/tsquery'; 2 | import { 3 | SyntaxKind, 4 | Node, 5 | NamedImports, 6 | Identifier, 7 | ClassDeclaration, 8 | ConstructorDeclaration, 9 | isStringLiteralLike, 10 | isArrayLiteralExpression, 11 | CallExpression, 12 | Expression, 13 | isBinaryExpression, 14 | isConditionalExpression, 15 | PropertyAccessExpression 16 | } from 'typescript'; 17 | 18 | export function getNamedImports(node: Node, moduleName: string): NamedImports[] { 19 | const query = `ImportDeclaration[moduleSpecifier.text="${moduleName}"] NamedImports`; 20 | return tsquery(node, query); 21 | } 22 | 23 | export function getNamedImportAlias(node: Node, moduleName: string, importName: string): string | null { 24 | const [namedImportNode] = getNamedImports(node, moduleName); 25 | if (!namedImportNode) { 26 | return null; 27 | } 28 | 29 | const query = `ImportSpecifier:has(Identifier[name="${importName}"]) > Identifier`; 30 | const identifiers = tsquery(namedImportNode, query); 31 | if (identifiers.length === 1) { 32 | return identifiers[0].text; 33 | } 34 | if (identifiers.length > 1) { 35 | return identifiers[identifiers.length - 1].text; 36 | } 37 | return null; 38 | } 39 | 40 | export function findClassDeclarations(node: Node): ClassDeclaration[] { 41 | const query = 'ClassDeclaration'; 42 | return tsquery(node, query); 43 | } 44 | 45 | export function findClassPropertyByType(node: ClassDeclaration, type: string): string | null { 46 | return findClassPropertyConstructorParameterByType(node, type) || findClassPropertyDeclarationByType(node, type); 47 | } 48 | 49 | export function findConstructorDeclaration(node: ClassDeclaration): ConstructorDeclaration { 50 | const query = `Constructor`; 51 | const [result] = tsquery(node, query); 52 | return result; 53 | } 54 | 55 | export function findMethodParameterByType(node: Node, type: string): string | null { 56 | const query = `Parameter:has(TypeReference > Identifier[name="${type}"]) > Identifier`; 57 | const [result] = tsquery(node, query); 58 | if (result) { 59 | return result.text; 60 | } 61 | return null; 62 | } 63 | 64 | export function findMethodCallExpressions(node: Node, propName: string, fnName: string | string[]): CallExpression[] { 65 | if (Array.isArray(fnName)) { 66 | fnName = fnName.join('|'); 67 | } 68 | const query = `CallExpression > PropertyAccessExpression:has(Identifier[name=/^(${fnName})$/]):has(PropertyAccessExpression:has(Identifier[name="${propName}"]):not(:has(ThisKeyword)))`; 69 | const nodes = tsquery(node, query).map((n) => n.parent as CallExpression); 70 | return nodes; 71 | } 72 | 73 | export function findClassPropertyConstructorParameterByType(node: ClassDeclaration, type: string): string | null { 74 | const query = `Constructor Parameter:has(TypeReference > Identifier[name="${type}"]):has(PublicKeyword,ProtectedKeyword,PrivateKeyword) > Identifier`; 75 | const [result] = tsquery(node, query); 76 | if (result) { 77 | return result.text; 78 | } 79 | return null; 80 | } 81 | 82 | export function findClassPropertyDeclarationByType(node: ClassDeclaration, type: string): string | null { 83 | const query = `PropertyDeclaration:has(TypeReference > Identifier[name="${type}"]) > Identifier`; 84 | const [result] = tsquery(node, query); 85 | if (result) { 86 | return result.text; 87 | } 88 | return null; 89 | } 90 | 91 | export function findFunctionCallExpressions(node: Node, fnName: string | string[]): CallExpression[] { 92 | if (Array.isArray(fnName)) { 93 | fnName = fnName.join('|'); 94 | } 95 | const query = `CallExpression:has(Identifier[name="${fnName}"]):not(:has(PropertyAccessExpression))`; 96 | const nodes = tsquery(node, query); 97 | return nodes; 98 | } 99 | 100 | export function findPropertyCallExpressions(node: Node, prop: string, fnName: string | string[]): CallExpression[] { 101 | if (Array.isArray(fnName)) { 102 | fnName = fnName.join('|'); 103 | } 104 | const query = `CallExpression > PropertyAccessExpression:has(Identifier[name=/^(${fnName})$/]):has(PropertyAccessExpression:has(Identifier[name="${prop}"]):has(ThisKeyword))`; 105 | const nodes = tsquery(node, query).map((n) => n.parent as CallExpression); 106 | return nodes; 107 | } 108 | 109 | export function getStringsFromExpression(expression: Expression): string[] { 110 | if (isStringLiteralLike(expression)) { 111 | return [expression.text]; 112 | } 113 | 114 | if (isArrayLiteralExpression(expression)) { 115 | return expression.elements.reduce((result: string[], element: Expression) => { 116 | const strings = getStringsFromExpression(element); 117 | return [...result, ...strings]; 118 | }, []); 119 | } 120 | 121 | if (isBinaryExpression(expression)) { 122 | const [left] = getStringsFromExpression(expression.left); 123 | const [right] = getStringsFromExpression(expression.right); 124 | 125 | if (expression.operatorToken.kind === SyntaxKind.PlusToken) { 126 | if (typeof left === 'string' && typeof right === 'string') { 127 | return [left + right]; 128 | } 129 | } 130 | 131 | if (expression.operatorToken.kind === SyntaxKind.BarBarToken) { 132 | const result = []; 133 | if (typeof left === 'string') { 134 | result.push(left); 135 | } 136 | if (typeof right === 'string') { 137 | result.push(right); 138 | } 139 | return result; 140 | } 141 | } 142 | 143 | if (isConditionalExpression(expression)) { 144 | const [whenTrue] = getStringsFromExpression(expression.whenTrue); 145 | const [whenFalse] = getStringsFromExpression(expression.whenFalse); 146 | 147 | const result = []; 148 | if (typeof whenTrue === 'string') { 149 | result.push(whenTrue); 150 | } 151 | if (typeof whenFalse === 'string') { 152 | result.push(whenFalse); 153 | } 154 | return result; 155 | } 156 | return []; 157 | } 158 | -------------------------------------------------------------------------------- /src/utils/donate.ts: -------------------------------------------------------------------------------- 1 | import { yellow } from 'colorette'; 2 | import * as boxen from 'boxen'; 3 | import * as terminalLink from 'terminal-link'; 4 | 5 | const url = 'https://donate.biesbjerg.com'; 6 | const link = terminalLink(url, url); 7 | const message = ` 8 | If this tool saves you or your company time, please consider making a 9 | donation to support my work and the continued maintainence and development: 10 | 11 | ${yellow(link)}`; 12 | 13 | export const donateMessage = boxen(message.trim(), { 14 | padding: 1, 15 | margin: 0, 16 | dimBorder: true 17 | }); 18 | -------------------------------------------------------------------------------- /src/utils/fs-helpers.ts: -------------------------------------------------------------------------------- 1 | import * as os from 'os'; 2 | import * as fs from 'fs'; 3 | import * as braces from 'braces'; 4 | 5 | declare module 'braces' { 6 | interface Options { 7 | keepEscaping?: boolean; // Workaround for option not present in @types/braces 3.0.0 8 | } 9 | } 10 | 11 | export function normalizeHomeDir(path: string): string { 12 | if (path.substring(0, 1) === '~') { 13 | return `${os.homedir()}/${path.substring(1)}`; 14 | } 15 | return path; 16 | } 17 | 18 | export function expandPattern(pattern: string): string[] { 19 | return braces(pattern, { expand: true, keepEscaping: true }); 20 | } 21 | 22 | export function normalizePaths(patterns: string[], defaultPatterns: string[] = []): string[] { 23 | return patterns 24 | .map((pattern) => 25 | expandPattern(pattern) 26 | .map((path) => { 27 | path = normalizeHomeDir(path); 28 | if (fs.existsSync(path) && fs.statSync(path).isDirectory()) { 29 | return defaultPatterns.map((defaultPattern) => path + defaultPattern); 30 | } 31 | return path; 32 | }) 33 | .flat() 34 | ) 35 | .flat(); 36 | } 37 | -------------------------------------------------------------------------------- /src/utils/translation.collection.ts: -------------------------------------------------------------------------------- 1 | export interface TranslationType { 2 | [key: string]: string; 3 | } 4 | 5 | export class TranslationCollection { 6 | public values: TranslationType = {}; 7 | 8 | public constructor(values: TranslationType = {}) { 9 | this.values = values; 10 | } 11 | 12 | public add(key: string, val: string = ''): TranslationCollection { 13 | return new TranslationCollection({ ...this.values, [key]: val }); 14 | } 15 | 16 | public addKeys(keys: string[]): TranslationCollection { 17 | const values = keys.reduce((results, key) => { 18 | return { ...results, [key]: '' }; 19 | }, {} as TranslationType); 20 | return new TranslationCollection({ ...this.values, ...values }); 21 | } 22 | 23 | public remove(key: string): TranslationCollection { 24 | return this.filter((k) => key !== k); 25 | } 26 | 27 | public forEach(callback: (key?: string, val?: string) => void): TranslationCollection { 28 | Object.keys(this.values).forEach((key) => callback.call(this, key, this.values[key])); 29 | return this; 30 | } 31 | 32 | public filter(callback: (key?: string, val?: string) => boolean): TranslationCollection { 33 | const values: TranslationType = {}; 34 | this.forEach((key, val) => { 35 | if (callback.call(this, key, val)) { 36 | values[key] = val; 37 | } 38 | }); 39 | return new TranslationCollection(values); 40 | } 41 | 42 | public map(callback: (key?: string, val?: string) => string): TranslationCollection { 43 | const values: TranslationType = {}; 44 | this.forEach((key, val) => { 45 | values[key] = callback.call(this, key, val); 46 | }); 47 | return new TranslationCollection(values); 48 | } 49 | 50 | public union(collection: TranslationCollection): TranslationCollection { 51 | return new TranslationCollection({ ...this.values, ...collection.values }); 52 | } 53 | 54 | public intersect(collection: TranslationCollection): TranslationCollection { 55 | const values: TranslationType = {}; 56 | this.filter((key) => collection.has(key)).forEach((key, val) => { 57 | values[key] = val; 58 | }); 59 | 60 | return new TranslationCollection(values); 61 | } 62 | 63 | public has(key: string): boolean { 64 | return this.values.hasOwnProperty(key); 65 | } 66 | 67 | public get(key: string): string { 68 | return this.values[key]; 69 | } 70 | 71 | public keys(): string[] { 72 | return Object.keys(this.values); 73 | } 74 | 75 | public count(): number { 76 | return Object.keys(this.values).length; 77 | } 78 | 79 | public isEmpty(): boolean { 80 | return Object.keys(this.values).length === 0; 81 | } 82 | 83 | public sort(compareFn?: (a: string, b: string) => number): TranslationCollection { 84 | const values: TranslationType = {}; 85 | this.keys() 86 | .sort(compareFn) 87 | .forEach((key) => { 88 | values[key] = this.get(key); 89 | }); 90 | 91 | return new TranslationCollection(values); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Assumes file is an Angular component if type is javascript/typescript 3 | */ 4 | export function isPathAngularComponent(path: string): boolean { 5 | return /\.ts|js$/i.test(path); 6 | } 7 | 8 | /** 9 | * Extract inline template from a component 10 | */ 11 | export function extractComponentInlineTemplate(contents: string): string { 12 | const regExp: RegExp = /template\s*:\s*(["'`])([^\1]*?)\1/; 13 | const match = regExp.exec(contents); 14 | if (match !== null) { 15 | return match[2]; 16 | } 17 | return ''; 18 | } 19 | 20 | export function stripBOM(contents: string): string { 21 | return contents.trim(); 22 | } 23 | -------------------------------------------------------------------------------- /tests/compilers/namespaced-json.compiler.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import { TranslationCollection } from '../../src/utils/translation.collection'; 4 | import { NamespacedJsonCompiler } from '../../src/compilers/namespaced-json.compiler'; 5 | 6 | describe('NamespacedJsonCompiler', () => { 7 | let compiler: NamespacedJsonCompiler; 8 | 9 | beforeEach(() => { 10 | compiler = new NamespacedJsonCompiler(); 11 | }); 12 | 13 | it('should flatten keys on parse', () => { 14 | const contents = ` 15 | { 16 | "NAMESPACE": { 17 | "KEY": { 18 | "FIRST_KEY": "", 19 | "SECOND_KEY": "VALUE" 20 | } 21 | } 22 | } 23 | `; 24 | const collection: TranslationCollection = compiler.parse(contents); 25 | expect(collection.values).to.deep.equal({ 26 | 'NAMESPACE.KEY.FIRST_KEY': '', 27 | 'NAMESPACE.KEY.SECOND_KEY': 'VALUE' 28 | }); 29 | }); 30 | 31 | it('should unflatten keys on compile', () => { 32 | const collection = new TranslationCollection({ 33 | 'NAMESPACE.KEY.FIRST_KEY': '', 34 | 'NAMESPACE.KEY.SECOND_KEY': 'VALUE' 35 | }); 36 | const result: string = compiler.compile(collection); 37 | expect(result).to.equal('{\n\t"NAMESPACE": {\n\t\t"KEY": {\n\t\t\t"FIRST_KEY": "",\n\t\t\t"SECOND_KEY": "VALUE"\n\t\t}\n\t}\n}'); 38 | }); 39 | 40 | it('should preserve numeric values on compile', () => { 41 | const collection = new TranslationCollection({ 42 | 'option.0': '', 43 | 'option.1': '', 44 | 'option.2': '' 45 | }); 46 | const result: string = compiler.compile(collection); 47 | expect(result).to.equal('{\n\t"option": {\n\t\t"0": "",\n\t\t"1": "",\n\t\t"2": ""\n\t}\n}'); 48 | }); 49 | 50 | it('should use custom indentation chars', () => { 51 | const collection = new TranslationCollection({ 52 | 'NAMESPACE.KEY.FIRST_KEY': '', 53 | 'NAMESPACE.KEY.SECOND_KEY': 'VALUE' 54 | }); 55 | const customCompiler = new NamespacedJsonCompiler({ 56 | indentation: ' ' 57 | }); 58 | const result: string = customCompiler.compile(collection); 59 | expect(result).to.equal('{\n "NAMESPACE": {\n "KEY": {\n "FIRST_KEY": "",\n "SECOND_KEY": "VALUE"\n }\n }\n}'); 60 | }); 61 | 62 | it('should not reorder keys when compiled', () => { 63 | const collection = new TranslationCollection({ 64 | BROWSE: '', 65 | LOGIN: '' 66 | }); 67 | const result: string = compiler.compile(collection); 68 | expect(result).to.equal('{\n\t"BROWSE": "",\n\t"LOGIN": ""\n}'); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /tests/compilers/po.compiler.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import { TranslationCollection } from '../../src/utils/translation.collection'; 4 | import { PoCompiler } from '../../src/compilers/po.compiler'; 5 | 6 | describe('PoCompiler', () => { 7 | let compiler: PoCompiler; 8 | 9 | beforeEach(() => { 10 | compiler = new PoCompiler(); 11 | }); 12 | 13 | it('should still include html ', () => { 14 | const collection = new TranslationCollection({ 15 | 'A test': 'Un test', 16 | 'With a lot of html included': 'Avec beaucoup d\'html inclus' 17 | }); 18 | const result: Buffer = Buffer.from(compiler.compile(collection)); 19 | expect(result.toString('utf8')).to.equal('msgid ""\nmsgstr ""\n"mime-version: 1.0\\n"\n"Content-Type: text/plain; charset=utf-8\\n"\n"Content-Transfer-Encoding: 8bit\\n"\n\nmsgid "A test"\nmsgstr "Un test"\n\nmsgid "With a lot of html included"\nmsgstr "Avec beaucoup d\'html inclus"'); 20 | }); 21 | }); 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/parsers/directive.parser.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import { DirectiveParser } from '../../src/parsers/directive.parser'; 4 | 5 | describe('DirectiveParser', () => { 6 | const templateFilename: string = 'test.template.html'; 7 | const componentFilename: string = 'test.component.ts'; 8 | 9 | let parser: DirectiveParser; 10 | 11 | beforeEach(() => { 12 | parser = new DirectiveParser(); 13 | }); 14 | 15 | 16 | it('should extract keys when using literal map in bound attribute', () => { 17 | const contents = `
`; 18 | const keys = parser.extract(contents, templateFilename).keys(); 19 | expect(keys).to.deep.equal(['value1', 'value2']); 20 | }); 21 | 22 | it('should extract keys when using literal arrays in bound attribute', () => { 23 | const contents = `
`; 24 | const keys = parser.extract(contents, templateFilename).keys(); 25 | expect(keys).to.deep.equal(['value1', 'value2']); 26 | }); 27 | 28 | it('should extract keys when using binding pipe in bound attribute', () => { 29 | const contents = `
`; 30 | const keys = parser.extract(contents, templateFilename).keys(); 31 | expect(keys).to.deep.equal(['KEY1']); 32 | }); 33 | 34 | it('should extract keys when using binary expression in bound attribute', () => { 35 | const contents = `
`; 36 | const keys = parser.extract(contents, templateFilename).keys(); 37 | expect(keys).to.deep.equal(['KEY1']); 38 | }); 39 | 40 | it('should extract keys when using literal primitive in bound attribute', () => { 41 | const contents = `
`; 42 | const keys = parser.extract(contents, templateFilename).keys(); 43 | expect(keys).to.deep.equal(['KEY1']); 44 | }); 45 | 46 | it('should extract keys when using conditional in bound attribute', () => { 47 | const contents = `
`; 48 | const keys = parser.extract(contents, templateFilename).keys(); 49 | expect(keys).to.deep.equal(['KEY1', 'KEY2']); 50 | }); 51 | 52 | it('should extract keys when using nested conditionals in bound attribute', () => { 53 | const contents = `
`; 54 | const keys = parser.extract(contents, templateFilename).keys(); 55 | expect(keys).to.deep.equal(['Sunny and warm', 'Sunny but cold', 'Not sunny']); 56 | }); 57 | 58 | it('should extract keys when using interpolation', () => { 59 | const contents = `
`; 60 | const keys = parser.extract(contents, templateFilename).keys(); 61 | expect(keys).to.deep.equal(['KEY1', 'KEY3']); 62 | }); 63 | 64 | it('should extract keys keeping proper whitespace', () => { 65 | const contents = ` 66 |
67 | Wubba 68 | Lubba 69 | Dub Dub 70 |
71 | `; 72 | const keys = parser.extract(contents, templateFilename).keys(); 73 | expect(keys).to.deep.equal(['Wubba Lubba Dub Dub']); 74 | }); 75 | 76 | it('should use element contents as key when no translate attribute value is present', () => { 77 | const contents = '
Hello World
'; 78 | const keys = parser.extract(contents, templateFilename).keys(); 79 | expect(keys).to.deep.equal(['Hello World']); 80 | }); 81 | 82 | it('should use translate attribute value as key when present', () => { 83 | const contents = '
Hello World
'; 84 | const keys = parser.extract(contents, templateFilename).keys(); 85 | expect(keys).to.deep.equal(['MY_KEY']); 86 | }); 87 | 88 | it('should extract keys from child elements when translate attribute is present', () => { 89 | const contents = `
Hello World
`; 90 | const keys = parser.extract(contents, templateFilename).keys(); 91 | expect(keys).to.deep.equal(['Hello', 'World']); 92 | }); 93 | 94 | it('should not extract keys from child elements when translate attribute is not present', () => { 95 | const contents = `
Hello World
`; 96 | const keys = parser.extract(contents, templateFilename).keys(); 97 | expect(keys).to.deep.equal(['Hello']); 98 | }); 99 | 100 | it('should extract and parse inline template', () => { 101 | const contents = ` 102 | @Component({ 103 | selector: 'test', 104 | template: '

Hello World

' 105 | }) 106 | export class TestComponent { } 107 | `; 108 | const keys = parser.extract(contents, componentFilename).keys(); 109 | expect(keys).to.deep.equal(['Hello World']); 110 | }); 111 | 112 | it('should extract contents when no translate attribute value is provided', () => { 113 | const contents = '
Hello World
'; 114 | const keys = parser.extract(contents, templateFilename).keys(); 115 | expect(keys).to.deep.equal(['Hello World']); 116 | }); 117 | 118 | it('should extract translate attribute value if provided', () => { 119 | const contents = '
Hello World
'; 120 | const keys = parser.extract(contents, templateFilename).keys(); 121 | expect(keys).to.deep.equal(['KEY']); 122 | }); 123 | 124 | it('should not extract translate pipe in html tag', () => { 125 | const contents = `

{{ 'Audiobooks for personal development' | translate }}

`; 126 | const collection = parser.extract(contents, templateFilename); 127 | expect(collection.values).to.deep.equal({}); 128 | }); 129 | 130 | it('should extract contents from custom elements', () => { 131 | const contents = `Hello World`; 132 | const keys = parser.extract(contents, templateFilename).keys(); 133 | expect(keys).to.deep.equal(['Hello World']); 134 | }); 135 | 136 | it('should extract from template without leading/trailing whitespace', () => { 137 | const contents = ` 138 |
There 139 | are currently no students in this class. The good news is, adding students is really easy! Just use the options 140 | at the top. 141 |
142 | `; 143 | const keys = parser.extract(contents, templateFilename).keys(); 144 | expect(keys).to.deep.equal([ 145 | 'There are currently no students in this class. The good news is, adding students is really easy! Just use the options at the top.' 146 | ]); 147 | }); 148 | 149 | it('should extract keys from element without leading/trailing whitespace', () => { 150 | const contents = ` 151 |
152 | this is an example 153 | of a long label 154 |
155 | 156 |
157 |

158 | this is an example 159 | of another a long label 160 |

161 |
162 | `; 163 | const keys = parser.extract(contents, templateFilename).keys(); 164 | expect(keys).to.deep.equal(['this is an example of a long label', 'this is an example of another a long label']); 165 | }); 166 | 167 | it('should collapse excessive whitespace', () => { 168 | const contents = '

this is an example

'; 169 | const keys = parser.extract(contents, templateFilename).keys(); 170 | expect(keys).to.deep.equal(['this is an example']); 171 | }); 172 | 173 | }); 174 | -------------------------------------------------------------------------------- /tests/parsers/marker.parser.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import { MarkerParser } from '../../src/parsers/marker.parser'; 4 | 5 | describe('MarkerParser', () => { 6 | const componentFilename: string = 'test.component.ts'; 7 | 8 | let parser: MarkerParser; 9 | 10 | beforeEach(() => { 11 | parser = new MarkerParser(); 12 | }); 13 | 14 | it('should extract strings using marker function', () => { 15 | const contents = ` 16 | import { marker } from '@biesbjerg/ngx-translate-extract-marker'; 17 | marker('Hello world'); 18 | marker(['I', 'am', 'extracted']); 19 | otherFunction('But I am not'); 20 | marker(message || 'binary expression'); 21 | marker(message ? message : 'conditional operator'); 22 | marker('FOO.bar'); 23 | `; 24 | const keys = parser.extract(contents, componentFilename).keys(); 25 | expect(keys).to.deep.equal(['Hello world', 'I', 'am', 'extracted', 'binary expression', 'conditional operator', 'FOO.bar']); 26 | }); 27 | 28 | it('should extract split strings', () => { 29 | const contents = ` 30 | import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; 31 | _('Hello ' + 'world'); 32 | _('This is a ' + 'very ' + 'very ' + 'very ' + 'very ' + 'long line.'); 33 | _('Mix ' + \`of \` + 'different ' + \`types\`); 34 | `; 35 | const keys = parser.extract(contents, componentFilename).keys(); 36 | expect(keys).to.deep.equal(['Hello world', 'This is a very very very very long line.', 'Mix of different types']); 37 | }); 38 | 39 | it('should extract split strings while keeping html tags', () => { 40 | const contents = ` 41 | import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; 42 | _('Hello ' + 'world'); 43 | _('This is a ' + 'very ' + 'very ' + 'very ' + 'very ' + 'long line.'); 44 | _('Mix ' + \`of \` + 'different ' + \`types\`); 45 | `; 46 | const keys = parser.extract(contents, componentFilename).keys(); 47 | expect(keys).to.deep.equal(['Hello world', 'This is a very very very very long line.', 'Mix of different types']); 48 | }); 49 | 50 | it('should extract the strings', () => { 51 | const contents = ` 52 | import { marker } from '@biesbjerg/ngx-translate-extract-marker'; 53 | 54 | export class AppModule { 55 | constructor() { 56 | marker('DYNAMIC_TRAD.val1'); 57 | marker('DYNAMIC_TRAD.val2'); 58 | } 59 | } 60 | `; 61 | const keys = parser.extract(contents, componentFilename).keys(); 62 | expect(keys).to.deep.equal(['DYNAMIC_TRAD.val1', 'DYNAMIC_TRAD.val2']); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /tests/parsers/pipe.parser.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import { PipeParser } from '../../src/parsers/pipe.parser'; 4 | 5 | describe('PipeParser', () => { 6 | const templateFilename: string = 'test.template.html'; 7 | 8 | let parser: PipeParser; 9 | 10 | beforeEach(() => { 11 | parser = new PipeParser(); 12 | }); 13 | 14 | it('should only extract string using pipe', () => { 15 | const contents = ``; 16 | const keys = parser.extract(contents, templateFilename).keys(); 17 | expect(keys).to.deep.equal(['SomeKey_NotWorking']); 18 | }); 19 | 20 | it('should extract string using pipe, but between quotes only', () => { 21 | const contents = ``; 22 | const keys = parser.extract(contents, templateFilename).keys(); 23 | expect(keys).to.deep.equal(['user.settings.form.phone.placeholder']); 24 | }); 25 | 26 | it('should extract interpolated strings using translate pipe', () => { 27 | const contents = `Hello {{ 'World' | translate }}`; 28 | const keys = parser.extract(contents, templateFilename).keys(); 29 | expect(keys).to.deep.equal(['World']); 30 | }); 31 | 32 | it('should extract interpolated strings when translate pipe is used before other pipes', () => { 33 | const contents = `Hello {{ 'World' | translate | upper }}`; 34 | const keys = parser.extract(contents, templateFilename).keys(); 35 | expect(keys).to.deep.equal(['World']); 36 | }); 37 | 38 | it('should extract interpolated strings when translate pipe is used after other pipes', () => { 39 | const contents = `Hello {{ 'World' | upper | translate }}`; 40 | const keys = parser.extract(contents, templateFilename).keys(); 41 | expect(keys).to.deep.equal(['World']); 42 | }); 43 | 44 | it('should extract strings from ternary operators inside interpolations', () => { 45 | const contents = `{{ (condition ? 'Hello' : 'World') | translate }}`; 46 | const keys = parser.extract(contents, templateFilename).keys(); 47 | expect(keys).to.deep.equal(['Hello', 'World']); 48 | }); 49 | 50 | it('should extract strings from ternary operators right expression', () => { 51 | const contents = `{{ condition ? null : ('World' | translate) }}`; 52 | const keys = parser.extract(contents, templateFilename).keys(); 53 | expect(keys).to.deep.equal(['World']); 54 | }); 55 | 56 | it('should extract strings from ternary operators inside attribute bindings', () => { 57 | const contents = ``; 58 | const keys = parser.extract(contents, templateFilename).keys(); 59 | expect(keys).to.deep.equal(['World']); 60 | }); 61 | 62 | it('should extract strings from ternary operators left expression', () => { 63 | const contents = `{{ condition ? ('World' | translate) : null }}`; 64 | const keys = parser.extract(contents, templateFilename).keys(); 65 | expect(keys).to.deep.equal(['World']); 66 | }); 67 | 68 | it('should extract strings inside string concatenation', () => { 69 | const contents = `{{ 'a' + ('Hello' | translate) + 'b' + 'c' + ('World' | translate) + 'd' }}`; 70 | const keys = parser.extract(contents, templateFilename).keys(); 71 | expect(keys).to.deep.equal(['Hello', 'World']); 72 | }); 73 | 74 | it('should extract strings from object', () => { 75 | const contents = `{{ { foo: 'Hello' | translate, bar: ['World' | translate], deep: { nested: { baz: 'Yes' | translate } } } | json }}`; 76 | const keys = parser.extract(contents, templateFilename).keys(); 77 | expect(keys).to.deep.equal(['Hello', 'World', 'Yes']); 78 | }); 79 | 80 | it('should extract strings from ternary operators inside attribute bindings', () => { 81 | const contents = ``; 82 | const keys = parser.extract(contents, templateFilename).keys(); 83 | expect(keys).to.deep.equal(['Hello', 'World']); 84 | }); 85 | 86 | it('should extract strings from nested expressions', () => { 87 | const contents = ``; 88 | const keys = parser.extract(contents, templateFilename).keys(); 89 | expect(keys).to.deep.equal(['Hello', 'World']); 90 | }); 91 | 92 | it('should extract strings from nested ternary operators ', () => { 93 | const contents = `

{{ (condition ? 'Hello' : anotherCondition ? 'Nested' : 'World' ) | translate }}

`; 94 | const keys = parser.extract(contents, templateFilename).keys(); 95 | expect(keys).to.deep.equal(['Hello', 'Nested', 'World']); 96 | }); 97 | 98 | it('should extract strings from ternary operators inside attribute interpolations', () => { 99 | const contents = ``; 100 | const keys = parser.extract(contents, templateFilename).keys(); 101 | expect(keys).to.deep.equal(['Hello', 'World']); 102 | }); 103 | 104 | it('should extract strings with escaped quotes', () => { 105 | const contents = `Hello {{ 'World\\'s largest potato' | translate }}`; 106 | const keys = parser.extract(contents, templateFilename).keys(); 107 | expect(keys).to.deep.equal([`World's largest potato`]); 108 | }); 109 | 110 | it('should extract strings with multiple escaped quotes', () => { 111 | const contents = `{{ 'C\\'est ok. C\\'est ok' | translate }}`; 112 | const keys = parser.extract(contents, templateFilename).keys(); 113 | expect(keys).to.deep.equal([`C'est ok. C'est ok`]); 114 | }); 115 | 116 | it('should extract interpolated strings using translate pipe in attributes', () => { 117 | const contents = ``; 118 | const keys = parser.extract(contents, templateFilename).keys(); 119 | expect(keys).to.deep.equal(['Hello World']); 120 | }); 121 | 122 | it('should extract bound strings using translate pipe in attributes', () => { 123 | const contents = ``; 124 | const keys = parser.extract(contents, templateFilename).keys(); 125 | expect(keys).to.deep.equal(['Hello World']); 126 | }); 127 | 128 | it('should extract multiple entries from nodes', () => { 129 | const contents = ` 130 | 131 | 132 | {{ 'Info' | translate }} 133 | 134 | 135 | 136 | 137 | 138 | 139 | {{ 'Loading...' | translate }} 140 | 141 | 142 | 143 | `; 144 | const keys = parser.extract(contents, templateFilename).keys(); 145 | expect(keys).to.deep.equal(['Info', 'Loading...']); 146 | }); 147 | 148 | it('should extract strings on same line', () => { 149 | const contents = ``; 150 | const keys = parser.extract(contents, templateFilename).keys(); 151 | expect(keys).to.deep.equal(['Hello', 'World']); 152 | }); 153 | 154 | it('should extract strings from this template', () => { 155 | const contents = ` 156 | 157 | 158 | 159 | 160 | 161 | 162 |

163 | {{ error }} 164 |

165 |
166 |
167 |
168 | 169 |
170 | `; 171 | const keys = parser.extract(contents, templateFilename).keys(); 172 | expect(keys).to.deep.equal(['Name', 'Create account']); 173 | }); 174 | 175 | it('should not extract variables', () => { 176 | const contents = '

{{ message | translate }}

'; 177 | const keys = parser.extract(contents, templateFilename).keys(); 178 | expect(keys).to.deep.equal([]); 179 | }); 180 | 181 | it('should be able to extract without html', () => { 182 | const contents = `{{ 'message' | translate }}`; 183 | const keys = parser.extract(contents, templateFilename).keys(); 184 | expect(keys).to.deep.equal(['message']); 185 | }); 186 | 187 | it('should ignore calculated values', () => { 188 | const contents = `{{ 'SOURCES.' + source.name + '.NAME_PLURAL' | translate }}`; 189 | const keys = parser.extract(contents, templateFilename).keys(); 190 | expect(keys).to.deep.equal([]); 191 | }); 192 | 193 | it('should not extract pipe argument', () => { 194 | const contents = `{{ value | valueToTranslationKey: 'argument' | translate }}`; 195 | const keys = parser.extract(contents, templateFilename).keys(); 196 | expect(keys).to.deep.equal([]); 197 | }); 198 | 199 | it('should extract strings from piped arguments inside a function calls on templates', () => { 200 | const contents = `{{ callMe('Hello' | translate, 'World' | translate ) }}`; 201 | const keys = parser.extract(contents, templateFilename).keys(); 202 | expect(keys).to.deep.equal([`Hello`, `World`]); 203 | }); 204 | }); 205 | -------------------------------------------------------------------------------- /tests/parsers/service.parser.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import { ServiceParser } from '../../src/parsers/service.parser'; 4 | 5 | describe('ServiceParser', () => { 6 | const componentFilename: string = 'test.component.ts'; 7 | 8 | let parser: ServiceParser; 9 | 10 | beforeEach(() => { 11 | parser = new ServiceParser(); 12 | }); 13 | 14 | it('should extract strings when TranslateService is accessed directly via constructor parameter', () => { 15 | const contents = ` 16 | @Component({ }) 17 | export class MyComponent { 18 | public constructor(protected translateService: TranslateService) { 19 | translateService.get('It works!'); 20 | } 21 | `; 22 | const keys = parser.extract(contents, componentFilename).keys(); 23 | expect(keys).to.deep.equal(['It works!']); 24 | }); 25 | 26 | it('should support extracting binary expressions', () => { 27 | const contents = ` 28 | @Component({ }) 29 | export class AppComponent { 30 | public constructor(protected _translateService: TranslateService) { } 31 | public test() { 32 | const message = 'The Message'; 33 | this._translateService.get(message || 'Fallback message'); 34 | } 35 | `; 36 | const keys = parser.extract(contents, componentFilename).keys(); 37 | expect(keys).to.deep.equal(['Fallback message']); 38 | }); 39 | 40 | it('should support conditional operator', () => { 41 | const contents = ` 42 | @Component({ }) 43 | export class AppComponent { 44 | public constructor(protected _translateService: TranslateService) { } 45 | public test() { 46 | const message = 'The Message'; 47 | this._translateService.get(message ? message : 'Fallback message'); 48 | } 49 | `; 50 | const keys = parser.extract(contents, componentFilename).keys(); 51 | expect(keys).to.deep.equal(['Fallback message']); 52 | }); 53 | 54 | it("should extract strings in TranslateService's get() method", () => { 55 | const contents = ` 56 | @Component({ }) 57 | export class AppComponent { 58 | public constructor(protected _translateService: TranslateService) { } 59 | public test() { 60 | this._translateService.get('Hello World'); 61 | } 62 | `; 63 | const keys = parser.extract(contents, componentFilename).keys(); 64 | expect(keys).to.deep.equal(['Hello World']); 65 | }); 66 | 67 | it("should extract strings in TranslateService's instant() method", () => { 68 | const contents = ` 69 | @Component({ }) 70 | export class AppComponent { 71 | public constructor(protected _translateService: TranslateService) { } 72 | public test() { 73 | this._translateService.instant('Hello World'); 74 | } 75 | `; 76 | const keys = parser.extract(contents, componentFilename).keys(); 77 | expect(keys).to.deep.equal(['Hello World']); 78 | }); 79 | 80 | it("should extract strings in TranslateService's stream() method", () => { 81 | const contents = ` 82 | @Component({ }) 83 | export class AppComponent { 84 | public constructor(protected _translateService: TranslateService) { } 85 | public test() { 86 | this._translateService.stream('Hello World'); 87 | } 88 | `; 89 | const keys = parser.extract(contents, componentFilename).keys(); 90 | expect(keys).to.deep.equal(['Hello World']); 91 | }); 92 | 93 | it("should extract array of strings in TranslateService's get() method", () => { 94 | const contents = ` 95 | @Component({ }) 96 | export class AppComponent { 97 | public constructor(protected _translateService: TranslateService) { } 98 | public test() { 99 | this._translateService.get(['Hello', 'World']); 100 | } 101 | `; 102 | const keys = parser.extract(contents, componentFilename).keys(); 103 | expect(keys).to.deep.equal(['Hello', 'World']); 104 | }); 105 | 106 | it("should extract array of strings in TranslateService's instant() method", () => { 107 | const contents = ` 108 | @Component({ }) 109 | export class AppComponent { 110 | public constructor(protected _translateService: TranslateService) { } 111 | public test() { 112 | this._translateService.instant(['Hello', 'World']); 113 | } 114 | `; 115 | const key = parser.extract(contents, componentFilename).keys(); 116 | expect(key).to.deep.equal(['Hello', 'World']); 117 | }); 118 | 119 | it("should extract array of strings in TranslateService's stream() method", () => { 120 | const contents = ` 121 | @Component({ }) 122 | export class AppComponent { 123 | public constructor(protected _translateService: TranslateService) { } 124 | public test() { 125 | this._translateService.stream(['Hello', 'World']); 126 | } 127 | `; 128 | const key = parser.extract(contents, componentFilename).keys(); 129 | expect(key).to.deep.equal(['Hello', 'World']); 130 | }); 131 | 132 | it('should extract string arrays encapsulated in backticks', () => { 133 | const contents = ` 134 | @Component({ }) 135 | export class AppComponent { 136 | public constructor(protected _translateService: TranslateService) { } 137 | public test() { 138 | this._translateService.get([\`Hello\`, \`World\`]); 139 | } 140 | `; 141 | const keys = parser.extract(contents, componentFilename).keys(); 142 | expect(keys).to.deep.equal(['Hello', 'World']); 143 | }); 144 | 145 | it('should not extract strings in get()/instant()/stream() methods of other services', () => { 146 | const contents = ` 147 | @Component({ }) 148 | export class AppComponent { 149 | public constructor( 150 | protected _translateService: TranslateService, 151 | protected _otherService: OtherService 152 | ) { } 153 | public test() { 154 | this._otherService.get('Hello World'); 155 | this._otherService.instant('Hi there'); 156 | this._otherService.stream('Hi there'); 157 | } 158 | `; 159 | const keys = parser.extract(contents, componentFilename).keys(); 160 | expect(keys).to.deep.equal([]); 161 | }); 162 | 163 | it('should extract strings with liberal spacing', () => { 164 | const contents = ` 165 | @Component({ }) 166 | export class AppComponent { 167 | public constructor( 168 | protected _translateService: TranslateService, 169 | protected _otherService: OtherService 170 | ) { } 171 | public test() { 172 | this._translateService.instant('Hello'); 173 | this._translateService.get ( 'World' ); 174 | this._translateService.instant ( ['How'] ); 175 | this._translateService.get([ 'Are' ]); 176 | this._translateService.get([ 'You' , 'Today' ]); 177 | } 178 | `; 179 | const keys = parser.extract(contents, componentFilename).keys(); 180 | expect(keys).to.deep.equal(['Hello', 'World', 'How', 'Are', 'You', 'Today']); 181 | }); 182 | 183 | it('should not extract string when not accessing property', () => { 184 | const contents = ` 185 | @Component({ }) 186 | export class AppComponent { 187 | public constructor(protected trans: TranslateService) { } 188 | public test() { 189 | trans.get("You are expected at {{time}}", {time: moment.format('H:mm')}).subscribe(); 190 | } 191 | } 192 | `; 193 | const keys = parser.extract(contents, componentFilename).keys(); 194 | expect(keys).to.deep.equal([]); 195 | }); 196 | 197 | it('should extract string with params on same line', () => { 198 | const contents = ` 199 | @Component({ }) 200 | export class AppComponent { 201 | public constructor(protected _translateService: TranslateService) { } 202 | public test() { 203 | this._translateService.get('You are expected at {{time}}', {time: moment.format('H:mm')}); 204 | } 205 | } 206 | `; 207 | const keys = parser.extract(contents, componentFilename).keys(); 208 | expect(keys).to.deep.equal(['You are expected at {{time}}']); 209 | }); 210 | 211 | it('should not crash when constructor parameter has no type', () => { 212 | const contents = ` 213 | @Component({ }) 214 | export class AppComponent { 215 | public constructor(protected _translateService) { } 216 | public test() { 217 | this._translateService.instant('Hello World'); 218 | } 219 | `; 220 | const keys = parser.extract(contents, componentFilename).keys(); 221 | expect(keys).to.deep.equal([]); 222 | }); 223 | 224 | it('should not extract variables', () => { 225 | const contents = ` 226 | @Component({ }) 227 | export class AppComponent { 228 | public constructor(protected translateService: TranslateService) { } 229 | public test() { 230 | this.translateService.get(["yes", variable]).then(translations => { 231 | console.log(translations[variable]); 232 | }); 233 | } 234 | } 235 | `; 236 | const keys = parser.extract(contents, componentFilename).keys(); 237 | expect(keys).to.deep.equal(['yes']); 238 | }); 239 | 240 | it('should extract strings from all classes in the file', () => { 241 | const contents = ` 242 | import { Injectable } from '@angular/core'; 243 | import { TranslateService } from '@ngx-translate/core'; 244 | export class Stuff { 245 | thing: string; 246 | translate: any; 247 | constructor(thing: string) { 248 | this.translate.get('Not me'); 249 | this.thing = thing; 250 | } 251 | } 252 | @Injectable() 253 | export class MyComponent { 254 | constructor(public translate: TranslateService) { 255 | this.translate.instant("Extract me!"); 256 | } 257 | } 258 | export class OtherClass { 259 | constructor(thing: string, _translate: TranslateService) { 260 | this._translate.get("Do not extract me"); 261 | } 262 | } 263 | @Injectable() 264 | export class AuthService { 265 | constructor(public translate: TranslateService) { 266 | this.translate.instant("Hello!"); 267 | } 268 | } 269 | `; 270 | const keys = parser.extract(contents, componentFilename).keys(); 271 | expect(keys).to.deep.equal(['Extract me!', 'Hello!']); 272 | }); 273 | 274 | it('should extract strings when TranslateService is declared as a property', () => { 275 | const contents = ` 276 | export class MyComponent { 277 | protected translateService: TranslateService; 278 | public constructor() { 279 | this.translateService = new TranslateService(); 280 | } 281 | public test() { 282 | this.translateService.instant('Hello World'); 283 | } 284 | `; 285 | const keys = parser.extract(contents, componentFilename).keys(); 286 | expect(keys).to.deep.equal(['Hello World']); 287 | }); 288 | 289 | it('should extract strings passed to TranslateServices methods only', () => { 290 | const contents = ` 291 | export class AppComponent implements OnInit { 292 | constructor(protected config: Config, protected translateService: TranslateService) {} 293 | 294 | public ngOnInit(): void { 295 | this.localizeBackButton(); 296 | } 297 | 298 | protected localizeBackButton(): void { 299 | this.translateService.onLangChange.subscribe((event: LangChangeEvent) => { 300 | this.config.set('backButtonText', this.translateService.instant('Back')); 301 | }); 302 | } 303 | } 304 | `; 305 | const keys = parser.extract(contents, componentFilename).keys(); 306 | expect(keys).to.deep.equal(['Back']); 307 | }); 308 | }); 309 | -------------------------------------------------------------------------------- /tests/parsers/utils.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import { isPathAngularComponent, extractComponentInlineTemplate } from '../../src/utils/utils'; 4 | 5 | describe('Utils', () => { 6 | it('should recognize js extension as angular component', () => { 7 | const result = isPathAngularComponent('test.js'); 8 | expect(result).to.equal(true); 9 | }); 10 | 11 | it('should recognize ts extension as angular component', () => { 12 | const result = isPathAngularComponent('test.ts'); 13 | expect(result).to.equal(true); 14 | }); 15 | 16 | it('should not recognize html extension as angular component', () => { 17 | const result = isPathAngularComponent('test.html'); 18 | expect(result).to.equal(false); 19 | }); 20 | 21 | it('should extract inline template', () => { 22 | const contents = ` 23 | @Component({ 24 | selector: 'test', 25 | template: '

Hello World

' 26 | }) 27 | export class TestComponent { } 28 | `; 29 | const template = extractComponentInlineTemplate(contents); 30 | expect(template).to.equal('

Hello World

'); 31 | }); 32 | 33 | it('should extract inline template without html', () => { 34 | const contents = ` 35 | @Component({ 36 | selector: 'test', 37 | template: '{{ "Hello World" | translate }}' 38 | }) 39 | export class TestComponent { } 40 | `; 41 | const template = extractComponentInlineTemplate(contents); 42 | expect(template).to.equal('{{ "Hello World" | translate }}'); 43 | }); 44 | 45 | it('should extract inline template spanning multiple lines', () => { 46 | const contents = ` 47 | @Component({ 48 | selector: 'test', 49 | template: ' 50 |

51 | Hello World 52 |

53 | ', 54 | styles: [' 55 | p { 56 | color: red; 57 | } 58 | '] 59 | }) 60 | export class TestComponent { } 61 | `; 62 | const template = extractComponentInlineTemplate(contents); 63 | expect(template).to.equal('\n\t\t\t\t\t

\n\t\t\t\t\t\tHello World\n\t\t\t\t\t

\n\t\t\t\t'); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /tests/post-processors/key-as-default-value.post-processor.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import { PostProcessorInterface } from '../../src/post-processors/post-processor.interface'; 4 | import { KeyAsDefaultValuePostProcessor } from '../../src/post-processors/key-as-default-value.post-processor'; 5 | import { TranslationCollection } from '../../src/utils/translation.collection'; 6 | 7 | describe('KeyAsDefaultValuePostProcessor', () => { 8 | let processor: PostProcessorInterface; 9 | 10 | beforeEach(() => { 11 | processor = new KeyAsDefaultValuePostProcessor(); 12 | }); 13 | 14 | it('should use key as default value', () => { 15 | const collection = new TranslationCollection({ 16 | 'I have no value': '', 17 | 'I am already translated': 'Jeg er allerede oversat', 18 | 'Use this key as value as well': '' 19 | }); 20 | const extracted = new TranslationCollection(); 21 | const existing = new TranslationCollection(); 22 | 23 | expect(processor.process(collection, extracted, existing).values).to.deep.equal({ 24 | 'I have no value': 'I have no value', 25 | 'I am already translated': 'Jeg er allerede oversat', 26 | 'Use this key as value as well': 'Use this key as value as well' 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /tests/post-processors/null-as-default-value.post-processor.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import { PostProcessorInterface } from '../../src/post-processors/post-processor.interface'; 4 | import { NullAsDefaultValuePostProcessor } from '../../src/post-processors/null-as-default-value.post-processor'; 5 | import { TranslationCollection } from '../../src/utils/translation.collection'; 6 | 7 | describe('NullAsDefaultValuePostProcessor', () => { 8 | let processor: PostProcessorInterface; 9 | 10 | beforeEach(() => { 11 | processor = new NullAsDefaultValuePostProcessor(); 12 | }); 13 | 14 | it('should use null as default value', () => { 15 | const draft = new TranslationCollection({ 'String A': '' }); 16 | const extracted = new TranslationCollection({ 'String A': '' }); 17 | const existing = new TranslationCollection(); 18 | expect(processor.process(draft, extracted, existing).values).to.deep.equal({ 19 | 'String A': null 20 | }); 21 | }); 22 | 23 | it('should keep existing value even if it is an empty string', () => { 24 | const draft = new TranslationCollection({ 'String A': '' }); 25 | const extracted = new TranslationCollection({ 'String A': '' }); 26 | const existing = new TranslationCollection({ 'String A': '' }); 27 | expect(processor.process(draft, extracted, existing).values).to.deep.equal({ 28 | 'String A': '' 29 | }); 30 | }); 31 | 32 | it('should keep existing value', () => { 33 | const draft = new TranslationCollection({ 'String A': 'Streng A' }); 34 | const extracted = new TranslationCollection({ 'String A': 'Streng A' }); 35 | const existing = new TranslationCollection({ 'String A': 'Streng A' }); 36 | expect(processor.process(draft, extracted, existing).values).to.deep.equal({ 37 | 'String A': 'Streng A' 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /tests/post-processors/purge-obsolete-keys.post-processor.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import { PostProcessorInterface } from '../../src/post-processors/post-processor.interface'; 4 | import { PurgeObsoleteKeysPostProcessor } from '../../src/post-processors/purge-obsolete-keys.post-processor'; 5 | import { TranslationCollection } from '../../src/utils/translation.collection'; 6 | 7 | describe('PurgeObsoleteKeysPostProcessor', () => { 8 | let postProcessor: PostProcessorInterface; 9 | 10 | beforeEach(() => { 11 | postProcessor = new PurgeObsoleteKeysPostProcessor(); 12 | }); 13 | 14 | it('should purge obsolete keys', () => { 15 | const draft = new TranslationCollection({ 16 | 'I am completely new': '', 17 | 'I already exist': '', 18 | 'I already exist but was not present in extract': '' 19 | }); 20 | const extracted = new TranslationCollection({ 21 | 'I am completely new': '', 22 | 'I already exist': '' 23 | }); 24 | const existing = new TranslationCollection({ 25 | 'I already exist': '', 26 | 'I already exist but was not present in extract': '' 27 | }); 28 | 29 | expect(postProcessor.process(draft, extracted, existing).values).to.deep.equal({ 30 | 'I am completely new': '', 31 | 'I already exist': '' 32 | }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /tests/post-processors/sort-by-key.post-processor.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import { PostProcessorInterface } from '../../src/post-processors/post-processor.interface'; 4 | import { SortByKeyPostProcessor } from '../../src/post-processors/sort-by-key.post-processor'; 5 | import { TranslationCollection } from '../../src/utils/translation.collection'; 6 | 7 | describe('SortByKeyPostProcessor', () => { 8 | let processor: PostProcessorInterface; 9 | 10 | beforeEach(() => { 11 | processor = new SortByKeyPostProcessor(); 12 | }); 13 | 14 | it('should sort keys alphanumerically', () => { 15 | const collection = new TranslationCollection({ 16 | z: 'last value', 17 | a: 'a value', 18 | '9': 'a numeric key', 19 | b: 'another value' 20 | }); 21 | const extracted = new TranslationCollection(); 22 | const existing = new TranslationCollection(); 23 | 24 | expect(processor.process(collection, extracted, existing).values).to.deep.equal({ 25 | '9': 'a numeric key', 26 | a: 'a value', 27 | b: 'another value', 28 | z: 'last value' 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /tests/post-processors/string-as-default-value.post-processor.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import { PostProcessorInterface } from '../../src/post-processors/post-processor.interface'; 4 | import { StringAsDefaultValuePostProcessor } from '../../src/post-processors/string-as-default-value.post-processor'; 5 | import { TranslationCollection } from '../../src/utils/translation.collection'; 6 | 7 | describe('StringAsDefaultValuePostProcessor', () => { 8 | let processor: PostProcessorInterface; 9 | 10 | beforeEach(() => { 11 | processor = new StringAsDefaultValuePostProcessor({ defaultValue: 'default' }); 12 | }); 13 | 14 | it('should use string as default value', () => { 15 | const draft = new TranslationCollection({ 'String A': '' }); 16 | const extracted = new TranslationCollection({ 'String A': '' }); 17 | const existing = new TranslationCollection(); 18 | expect(processor.process(draft, extracted, existing).values).to.deep.equal({ 19 | 'String A': 'default' 20 | }); 21 | }); 22 | 23 | it('should keep existing value even if it is an empty string', () => { 24 | const draft = new TranslationCollection({ 'String A': '' }); 25 | const extracted = new TranslationCollection({ 'String A': '' }); 26 | const existing = new TranslationCollection({ 'String A': '' }); 27 | expect(processor.process(draft, extracted, existing).values).to.deep.equal({ 28 | 'String A': '' 29 | }); 30 | }); 31 | 32 | it('should keep existing value', () => { 33 | const draft = new TranslationCollection({ 'String A': 'Streng A' }); 34 | const extracted = new TranslationCollection({ 'String A': 'Streng A' }); 35 | const existing = new TranslationCollection({ 'String A': 'Streng A' }); 36 | expect(processor.process(draft, extracted, existing).values).to.deep.equal({ 37 | 'String A': 'Streng A' 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /tests/utils/translation.collection.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import { TranslationCollection } from '../../src/utils/translation.collection'; 4 | 5 | describe('StringCollection', () => { 6 | let collection: TranslationCollection; 7 | 8 | beforeEach(() => { 9 | collection = new TranslationCollection(); 10 | }); 11 | 12 | it('should initialize with key/value pairs', () => { 13 | collection = new TranslationCollection({ key1: 'val1', key2: 'val2' }); 14 | expect(collection.values).to.deep.equal({ key1: 'val1', key2: 'val2' }); 15 | }); 16 | 17 | it('should add key with value', () => { 18 | const newCollection = collection.add('theKey', 'theVal'); 19 | expect(newCollection.get('theKey')).to.equal('theVal'); 20 | }); 21 | 22 | it('should add key with default value', () => { 23 | collection = collection.add('theKey'); 24 | expect(collection.get('theKey')).to.equal(''); 25 | }); 26 | 27 | it('should not mutate collection when adding key', () => { 28 | collection.add('theKey', 'theVal'); 29 | expect(collection.has('theKey')).to.equal(false); 30 | }); 31 | 32 | it('should add array of keys with default value', () => { 33 | collection = collection.addKeys(['key1', 'key2']); 34 | expect(collection.values).to.deep.equal({ key1: '', key2: '' }); 35 | }); 36 | 37 | it('should return true when collection has key', () => { 38 | collection = collection.add('key'); 39 | expect(collection.has('key')).to.equal(true); 40 | }); 41 | 42 | it('should return false when collection does not have key', () => { 43 | expect(collection.has('key')).to.equal(false); 44 | }); 45 | 46 | it('should remove key', () => { 47 | collection = new TranslationCollection({ removeThisKey: '' }); 48 | collection = collection.remove('removeThisKey'); 49 | expect(collection.has('removeThisKey')).to.equal(false); 50 | }); 51 | 52 | it('should not mutate collection when removing key', () => { 53 | collection = new TranslationCollection({ removeThisKey: '' }); 54 | collection.remove('removeThisKey'); 55 | expect(collection.has('removeThisKey')).to.equal(true); 56 | }); 57 | 58 | it('should return number of keys', () => { 59 | collection = collection.addKeys(['key1', 'key2', 'key3']); 60 | expect(collection.count()).to.equal(3); 61 | }); 62 | 63 | it('should merge with other collection', () => { 64 | collection = collection.add('oldKey', 'oldVal'); 65 | const newCollection = new TranslationCollection({ newKey: 'newVal' }); 66 | expect(collection.union(newCollection).values).to.deep.equal({ 67 | oldKey: 'oldVal', 68 | newKey: 'newVal' 69 | }); 70 | }); 71 | 72 | it('should intersect with passed collection', () => { 73 | collection = collection.addKeys(['red', 'green', 'blue']); 74 | const newCollection = new TranslationCollection({ red: '', blue: '' }); 75 | expect(collection.intersect(newCollection).values).to.deep.equal({ red: '', blue: '' }); 76 | }); 77 | 78 | it('should intersect with passed collection and keep original values', () => { 79 | collection = new TranslationCollection({ red: 'rød', green: 'grøn', blue: 'blå' }); 80 | const newCollection = new TranslationCollection({ red: 'no value', blue: 'also no value' }); 81 | expect(collection.intersect(newCollection).values).to.deep.equal({ red: 'rød', blue: 'blå' }); 82 | }); 83 | 84 | it('should sort keys alphabetically', () => { 85 | collection = new TranslationCollection({ red: 'rød', green: 'grøn', blue: 'blå' }); 86 | collection = collection.sort(); 87 | expect(collection.keys()).deep.equal(['blue', 'green', 'red']); 88 | }); 89 | 90 | it('should map values', () => { 91 | collection = new TranslationCollection({ red: 'rød', green: 'grøn', blue: 'blå' }); 92 | collection = collection.map((key, val) => 'mapped value'); 93 | expect(collection.values).to.deep.equal({ 94 | red: 'mapped value', 95 | green: 'mapped value', 96 | blue: 'mapped value' 97 | }); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "noImplicitAny": true, 5 | "noImplicitReturns": true, 6 | "noImplicitThis": true, 7 | "noImplicitUseStrict": true, 8 | "removeComments": true, 9 | "declaration": true, 10 | "target": "es2019", 11 | "lib": [ 12 | "es2019" 13 | ], 14 | "module": "commonjs", 15 | "outDir": "dist", 16 | "sourceMap": true, 17 | "typeRoots" : [ 18 | "node_modules/@types" 19 | ] 20 | }, 21 | "include": [ 22 | "src/**/*.ts" 23 | ], 24 | "exclude": [] 25 | } 26 | -------------------------------------------------------------------------------- /tslint.commit.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["./tslint.json", "tslint-etc"], 3 | "jsRules": {}, 4 | "rules": { 5 | "ordered-imports": false, 6 | "no-unused-declaration": true 7 | } 8 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint-config-prettier" 5 | ], 6 | "rules": { 7 | "arrow-return-shorthand": true, 8 | "callable-types": true, 9 | "class-name": true, 10 | "comment-format": [ 11 | true, 12 | "check-space" 13 | ], 14 | "curly": true, 15 | "deprecation": { 16 | "severity": "warn" 17 | }, 18 | "eofline": true, 19 | "forin": true, 20 | "import-spacing": true, 21 | "indent": [ 22 | true, 23 | "tabs" 24 | ], 25 | "interface-over-type-literal": true, 26 | "label-position": true, 27 | "max-line-length": [ 28 | true, 29 | 220 30 | ], 31 | "member-access": false, 32 | "member-ordering": [ 33 | true, 34 | { 35 | "order": [ 36 | "static-field", 37 | "instance-field", 38 | "static-method", 39 | "instance-method" 40 | ] 41 | } 42 | ], 43 | "no-arg": true, 44 | "no-bitwise": true, 45 | "no-console": [ 46 | true, 47 | "debug", 48 | "info", 49 | "time", 50 | "timeEnd", 51 | "trace" 52 | ], 53 | "no-construct": true, 54 | "no-debugger": true, 55 | "no-duplicate-super": true, 56 | "no-empty": false, 57 | "no-empty-interface": true, 58 | "no-eval": true, 59 | "no-inferrable-types": [ 60 | false, 61 | "ignore-params" 62 | ], 63 | "no-misused-new": true, 64 | "no-non-null-assertion": true, 65 | "no-shadowed-variable": true, 66 | "no-string-literal": false, 67 | "no-string-throw": true, 68 | "no-switch-case-fall-through": true, 69 | "no-trailing-whitespace": true, 70 | "no-unnecessary-initializer": true, 71 | "no-unused-expression": true, 72 | "no-var-keyword": true, 73 | "object-literal-sort-keys": false, 74 | "one-line": [ 75 | true, 76 | "check-open-brace", 77 | "check-catch", 78 | "check-else", 79 | "check-whitespace" 80 | ], 81 | "prefer-const": true, 82 | "radix": true, 83 | "semicolon": [ 84 | true, 85 | "always" 86 | ], 87 | "triple-equals": [ 88 | true, 89 | "allow-null-check" 90 | ], 91 | "typedef-whitespace": [ 92 | true, 93 | { 94 | "call-signature": "nospace", 95 | "index-signature": "nospace", 96 | "parameter": "nospace", 97 | "property-declaration": "nospace", 98 | "variable-declaration": "nospace" 99 | } 100 | ], 101 | "unified-signatures": true, 102 | "variable-name": [ 103 | true, 104 | "ban-keywords", 105 | "allow-pascal-case", 106 | "check-format" 107 | ], 108 | "whitespace": [ 109 | true, 110 | "check-branch", 111 | "check-decl", 112 | "check-operator", 113 | "check-separator", 114 | "check-type" 115 | ], 116 | "quotemark": [true, "single"] 117 | } 118 | } --------------------------------------------------------------------------------