├── .editorconfig ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── bin └── run.js ├── index.d.ts ├── package-lock.json ├── package.json ├── src ├── .stsconfig.js ├── cli.ts ├── importer.ts ├── models │ └── strapi-model.ts ├── processor.ts ├── test.ts ├── test │ ├── api │ │ ├── testobject │ │ │ └── models │ │ │ │ └── testobject.settings.json │ │ └── testobjectrelation │ │ │ └── models │ │ │ └── testobjectrelation.settings.json │ ├── components │ │ └── content │ │ │ ├── another.json │ │ │ ├── camel-case.json │ │ │ ├── complex.json │ │ │ └── simple.json │ ├── strapi │ │ ├── strapi-plugin-navigation │ │ │ ├── navigation.settings.json │ │ │ └── navigationItem.settings.json │ │ ├── strapi-plugin-upload │ │ │ └── models │ │ │ │ └── File.settings.json │ │ └── strapi-plugin-users-permissions │ │ │ └── models │ │ │ ├── Permission.settings.json │ │ │ ├── Role.settings.json │ │ │ └── User.settings.json │ ├── test1.assert.ts │ ├── test1.config.js │ ├── test2.assert.ts │ ├── test2.config.js │ ├── test3.assert.ts │ └── test3.config.js └── ts-exporter.ts ├── tsconfig.json └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = false 10 | 11 | # 2 space indentation 12 | [**.*] 13 | indent_style = space 14 | indent_size = 2 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # This repo 64 | tmp 65 | dist 66 | 67 | dist/ 68 | 69 | src/test/out*/ 70 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | src 3 | node_modules 4 | tmp 5 | .editorconfig 6 | tslint.json 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Erik Vullings 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 | # Strapi-to-TypeScript 2 | 3 | 4 | NPM version 5 | NPM download 6 | contributors 7 | 8 | 9 | 10 | THIS PROJECT IS NOT MAINTAINED ANYMORE. Don't hesitate to fork it. 11 | 12 | THIS PROJECT DOESN'T SUPPORT VERSION 4 OR LATER OF STRAPI. (see [PR#49](https://github.com/erikvullings/strapi-to-typescript/issues/49)) 13 | 14 | ## 15 | 16 | Convert the Strapi models to TypeScript interfaces by processing each of the `./api/**/models/*.settings.json` recursively. 17 | 18 | ## Install and Run 19 | 20 | ```shell 21 | npm install -g strapi-to-typescript 22 | 23 | sts path/to/strapi/api/ -o path/to/your/types/dir/ 24 | 25 | # see all doc 26 | sts -h 27 | 28 | # external conf. see: strapi-to-typescript/index.d.ts for format 29 | sts -c .stsconfig.js 30 | ``` 31 | 32 | ## Command line option 33 | 34 | `sts input -g components -o output ...` 35 | 36 | ### required 37 | * **input** 38 | Strapi folder(s)/file(s) with models *.settings.json 39 | You may define multiple inputs. In case your API models have relations to other plugins like 'users-permissions'. 40 | `sts path/to/strapi/api/ path/to/strapi/plugins/users-permissions/models -o path/to/your/types/dir/` 41 | * Order matters, if you have two models with the same name, the last one is used. 42 | * Add '!' to exclude folder or subfolder, ex: `!path/to/strapi/plugins_excluded`. 43 | 44 | * **-g components** 45 | Strapi folder(s) with components models 46 | 47 | ### optional 48 | * **-o output** 49 | Output folder 50 | * **-n nested** 51 | Put all interfaces in a nested tree instead of directly under the output folder 52 | * **-u collectionCanBeUndefined** 53 | By default, all collection can not be undefined. You can turn this off, so only unrequired collections may be undefined. 54 | * **-e Enumeration** 55 | You may generate **enumeration** or **string literal** 56 | Example: 57 | ```typescript 58 | // enumeration (with -e option) 59 | export interface IOrder { 60 | payment: IOrderPayment; 61 | } 62 | export enum IOrderPayment { 63 | card = "card", 64 | check = "check", 65 | } 66 | // OR string literal types (by default) 67 | export interface IOrder { 68 | payment: "card" | "check"; 69 | } 70 | ``` 71 | 72 | * **-c Advanced configuration** 73 | path to configuration file 74 | 75 | # Advanced configuration 76 | 77 | .stsconfig 78 | ```javascript 79 | 80 | /** 81 | * @type {import('strapi-to-typescript')} 82 | */ 83 | const config = { 84 | 85 | //required 86 | input: [ 87 | 'api', 88 | './node_modules/strapi-plugin-users-permissions/models/', 89 | './node_modules/strapi-plugin-upload/models/', 90 | './extensions/users-permissions/models/' 91 | ], 92 | components: './components/', 93 | output: './sts/', 94 | 95 | // optional 96 | enum: true, 97 | nested: false, 98 | excludeField: (interfaceName, fieldName) => fieldName === 'hide_field', 99 | addField: (interfaceName) => [{ name: "created_by", type: "string" }], 100 | 101 | // optional, builtin function used if undefined return 102 | fieldType: (fieldType, fieldName, interfaceName) => { if(fieldType == 'datetime') return 'string' }, 103 | fieldName: (fieldName) => fieldName.replace('_', ''), 104 | interfaceName: (name) => `X${name}`, 105 | enumName: (name, interfaceName) => `Enum${interfaceName}${name}`, 106 | importAsType: (interfaceName) => interfaceName === 'MyInterfaceThatWantsToImportAsTypes' /* or just true */, 107 | outputFileName: (interfaceName, filename) => interfaceName; 108 | } 109 | module.exports = config; 110 | ``` 111 | 112 | package.json 113 | ```json 114 | { 115 | "//" : "...", 116 | 117 | "scripts": { 118 | "sts": "sts -c .stsconfig" 119 | }, 120 | 121 | "///" : "..." 122 | } 123 | ``` 124 | 125 | ## Issue 126 | 127 | If you want to create an issue. First of all, be nice. Take the time to explain and format your post. 128 | 129 | The better solution to explain your issue (and for me, to fix it) is to create a pull request with your data: 130 | 131 | 1. fork this repo with the button "fork" on github website. wait a minute. 132 | 1. git clone your master branch from the newly created repository. 133 | 1. `yarn install` or `npm install` 134 | 1. add your api in `src/test/api` `src/test/components` (if necessary) 135 | 1. add your test: 136 | 1. `src/test/test.config.js` copy an other test and modify `output` conf 137 | 1. `src/test/test.assert.ts` copy another assert and modify the import accordingly to your conf `output` 138 | 1. run your test with `./node_modules/.bin/ts-node src/test.ts test` or run all test `yarn test` 139 | 1. create pull request on this repo 140 | 141 | ## Explanation 142 | 143 | The input folder is recursively processed and each model file is read. When done, the expected output folder is generated, and finally, the Strapi models are converted to TypeScript. 144 | 145 | ## Build 146 | 147 | ```sh 148 | npm install && npm run build 149 | # output files generated in dist folder 150 | ``` -------------------------------------------------------------------------------- /bin/run.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | var cli = require('../dist/cli'); 4 | cli -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | export interface ICommandOptions { 2 | /** Strapi folder(s)/file(s) with models (*.settings.json) */ 3 | input: string[]; 4 | /** Strapi folder(s) with components models (*.json) */ 5 | components: string; 6 | /** @deprecated use components */ 7 | inputGroup: string; 8 | /** Output folder */ 9 | output: string; 10 | /** Put all interfaces in a nested tree instead of directly under the output folder */ 11 | nested: boolean; 12 | /** Generate enumeration */ 13 | enum: boolean; 14 | /** By default, all collection can not be undefined. You can turn this off, so only unrequired collections may be undefined. */ 15 | collectionCanBeUndefined: boolean 16 | 17 | /** configuration file*/ 18 | config: string; 19 | /** Display help output */ 20 | help: boolean; 21 | } 22 | 23 | export interface IConfigOptions extends ICommandOptions { 24 | /** 25 | * Model Strapi attributes type and name => type of field. 26 | * (use default, if empty return) 27 | * example: 28 | * (fieldType) => { if(fieldType == 'datetime') return 'string'} 29 | */ 30 | fieldType: (fieldType: string, fieldName: string, interfaceName: string) => string | undefined; 31 | /** @deprecated use fieldType */ 32 | type: (fieldType: string, fieldName: string, interfaceName: string) => string | undefined; 33 | 34 | /** 35 | * Model Strapi attributes name => name of field. 36 | * (use default, if empty return) 37 | * example: 38 | * (fieldName) => fieldName.replace('_', '') 39 | */ 40 | fieldName: (fieldName: string, interfaceName: string) => string | undefined; 41 | 42 | /** 43 | * Model Strapi info.name => name of typescript interface. 44 | * (use default, if empty return) 45 | * example: 46 | * (name) => name.charAt(0).toUpperCase() + name.slice(1) 47 | */ 48 | interfaceName: (name: string, filename: string) => string | undefined; 49 | 50 | /** 51 | * Model Strapi attributes name. 52 | * (use default, if empty return) 53 | * example: 54 | * (name) => 'Enum' + name.charAt(0).toUpperCase() + name.slice(1) 55 | */ 56 | enumName: (name: string, interfaceName: string) => string | undefined; 57 | 58 | /** 59 | * outputfile .ts path. 60 | */ 61 | outputFileName: (interfaceName: string, filename: string) => string | undefined; 62 | 63 | /** 64 | * Exclude field on typescript interface. 65 | * example: 66 | * (interfaceName, fieldName) => fieldName === 'hide_field' 67 | */ 68 | excludeField: (interfaceName: string, fieldName: string) => boolean | undefined; 69 | 70 | /** 71 | * Use `import type` in import statements in generated files. 72 | * () 73 | * example: 74 | * (interfaceName) => true 75 | * (interfaceName) => interfaceName === 'MyInterface' 76 | */ 77 | importAsType: (interfaceName: string) => boolean | undefined; 78 | 79 | /** 80 | * add your fields on typescript interface. 81 | * example: 82 | * () => [{ name: "created_by", type: "string" }, { name: "updated_by", type: "string" }] 83 | */ 84 | addField: (interfaceName: string) => { name: string, type: string }[] 85 | } 86 | 87 | declare module 'strapi-to-typescript' { 88 | export = IConfigOptions 89 | } 90 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "strapi-to-typescript", 3 | "version": "2.0.14", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "strapi-to-typescript", 9 | "version": "2.0.14", 10 | "license": "MIT", 11 | "dependencies": { 12 | "command-line-args": "^5.1.1", 13 | "command-line-usage": "^5.0.5" 14 | }, 15 | "bin": { 16 | "sts": "bin/run.js" 17 | }, 18 | "devDependencies": { 19 | "@types/command-line-args": "^5.0.0", 20 | "@types/command-line-usage": "^5.0.1", 21 | "@types/node": "^14.14.10", 22 | "@types/pluralize": "^0.0.29", 23 | "ts-node": "^9.0.0", 24 | "typescript": "^4.1.2" 25 | } 26 | }, 27 | "node_modules/@types/command-line-args": { 28 | "version": "5.2.0", 29 | "resolved": "https://registry.npmjs.org/@types/command-line-args/-/command-line-args-5.2.0.tgz", 30 | "integrity": "sha512-UuKzKpJJ/Ief6ufIaIzr3A/0XnluX7RvFgwkV89Yzvm77wCh1kFaFmqN8XEnGcN62EuHdedQjEMb8mYxFLGPyA==", 31 | "dev": true 32 | }, 33 | "node_modules/@types/command-line-usage": { 34 | "version": "5.0.2", 35 | "resolved": "https://registry.npmjs.org/@types/command-line-usage/-/command-line-usage-5.0.2.tgz", 36 | "integrity": "sha512-n7RlEEJ+4x4TS7ZQddTmNSxP+zziEG0TNsMfiRIxcIVXt71ENJ9ojeXmGO3wPoTdn7pJcU2xc3CJYMktNT6DPg==", 37 | "dev": true 38 | }, 39 | "node_modules/@types/node": { 40 | "version": "14.18.0", 41 | "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.0.tgz", 42 | "integrity": "sha512-0GeIl2kmVMXEnx8tg1SlG6Gg8vkqirrW752KqolYo1PHevhhZN3bhJ67qHj+bQaINhX0Ra3TlWwRvMCd9iEfNQ==", 43 | "dev": true 44 | }, 45 | "node_modules/@types/pluralize": { 46 | "version": "0.0.29", 47 | "resolved": "https://registry.npmjs.org/@types/pluralize/-/pluralize-0.0.29.tgz", 48 | "integrity": "sha512-BYOID+l2Aco2nBik+iYS4SZX0Lf20KPILP5RGmM1IgzdwNdTs0eebiFriOPcej1sX9mLnSoiNte5zcFxssgpGA==", 49 | "dev": true 50 | }, 51 | "node_modules/ansi-styles": { 52 | "version": "3.2.1", 53 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 54 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 55 | "dependencies": { 56 | "color-convert": "^1.9.0" 57 | }, 58 | "engines": { 59 | "node": ">=4" 60 | } 61 | }, 62 | "node_modules/arg": { 63 | "version": "4.1.3", 64 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 65 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", 66 | "dev": true 67 | }, 68 | "node_modules/array-back": { 69 | "version": "3.1.0", 70 | "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", 71 | "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", 72 | "engines": { 73 | "node": ">=6" 74 | } 75 | }, 76 | "node_modules/buffer-from": { 77 | "version": "1.1.2", 78 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", 79 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", 80 | "dev": true 81 | }, 82 | "node_modules/chalk": { 83 | "version": "2.4.2", 84 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 85 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 86 | "dependencies": { 87 | "ansi-styles": "^3.2.1", 88 | "escape-string-regexp": "^1.0.5", 89 | "supports-color": "^5.3.0" 90 | }, 91 | "engines": { 92 | "node": ">=4" 93 | } 94 | }, 95 | "node_modules/color-convert": { 96 | "version": "1.9.3", 97 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 98 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 99 | "dependencies": { 100 | "color-name": "1.1.3" 101 | } 102 | }, 103 | "node_modules/color-name": { 104 | "version": "1.1.3", 105 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 106 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" 107 | }, 108 | "node_modules/command-line-args": { 109 | "version": "5.2.0", 110 | "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.0.tgz", 111 | "integrity": "sha512-4zqtU1hYsSJzcJBOcNZIbW5Fbk9BkjCp1pZVhQKoRaWL5J7N4XphDLwo8aWwdQpTugxwu+jf9u2ZhkXiqp5Z6A==", 112 | "dependencies": { 113 | "array-back": "^3.1.0", 114 | "find-replace": "^3.0.0", 115 | "lodash.camelcase": "^4.3.0", 116 | "typical": "^4.0.0" 117 | }, 118 | "engines": { 119 | "node": ">=4.0.0" 120 | } 121 | }, 122 | "node_modules/command-line-usage": { 123 | "version": "5.0.5", 124 | "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-5.0.5.tgz", 125 | "integrity": "sha512-d8NrGylA5oCXSbGoKz05FkehDAzSmIm4K03S5VDh4d5lZAtTWfc3D1RuETtuQCn8129nYfJfDdF7P/lwcz1BlA==", 126 | "dependencies": { 127 | "array-back": "^2.0.0", 128 | "chalk": "^2.4.1", 129 | "table-layout": "^0.4.3", 130 | "typical": "^2.6.1" 131 | }, 132 | "engines": { 133 | "node": ">=4.0.0" 134 | } 135 | }, 136 | "node_modules/command-line-usage/node_modules/array-back": { 137 | "version": "2.0.0", 138 | "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz", 139 | "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==", 140 | "dependencies": { 141 | "typical": "^2.6.1" 142 | }, 143 | "engines": { 144 | "node": ">=4" 145 | } 146 | }, 147 | "node_modules/command-line-usage/node_modules/typical": { 148 | "version": "2.6.1", 149 | "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz", 150 | "integrity": "sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0=" 151 | }, 152 | "node_modules/create-require": { 153 | "version": "1.1.1", 154 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 155 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", 156 | "dev": true 157 | }, 158 | "node_modules/deep-extend": { 159 | "version": "0.6.0", 160 | "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", 161 | "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", 162 | "engines": { 163 | "node": ">=4.0.0" 164 | } 165 | }, 166 | "node_modules/diff": { 167 | "version": "4.0.2", 168 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 169 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 170 | "dev": true, 171 | "engines": { 172 | "node": ">=0.3.1" 173 | } 174 | }, 175 | "node_modules/escape-string-regexp": { 176 | "version": "1.0.5", 177 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 178 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 179 | "engines": { 180 | "node": ">=0.8.0" 181 | } 182 | }, 183 | "node_modules/find-replace": { 184 | "version": "3.0.0", 185 | "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", 186 | "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", 187 | "dependencies": { 188 | "array-back": "^3.0.1" 189 | }, 190 | "engines": { 191 | "node": ">=4.0.0" 192 | } 193 | }, 194 | "node_modules/has-flag": { 195 | "version": "3.0.0", 196 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 197 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 198 | "engines": { 199 | "node": ">=4" 200 | } 201 | }, 202 | "node_modules/lodash.camelcase": { 203 | "version": "4.3.0", 204 | "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", 205 | "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" 206 | }, 207 | "node_modules/lodash.padend": { 208 | "version": "4.6.1", 209 | "resolved": "https://registry.npmjs.org/lodash.padend/-/lodash.padend-4.6.1.tgz", 210 | "integrity": "sha1-U8y6BH0G4VjTEfRdpiX05J5vFm4=" 211 | }, 212 | "node_modules/make-error": { 213 | "version": "1.3.6", 214 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 215 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", 216 | "dev": true 217 | }, 218 | "node_modules/reduce-flatten": { 219 | "version": "1.0.1", 220 | "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-1.0.1.tgz", 221 | "integrity": "sha1-JYx479FT3fk8tWEjf2EYTzaW4yc=", 222 | "engines": { 223 | "node": ">=0.10.0" 224 | } 225 | }, 226 | "node_modules/source-map": { 227 | "version": "0.6.1", 228 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 229 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 230 | "dev": true, 231 | "engines": { 232 | "node": ">=0.10.0" 233 | } 234 | }, 235 | "node_modules/source-map-support": { 236 | "version": "0.5.21", 237 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", 238 | "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", 239 | "dev": true, 240 | "dependencies": { 241 | "buffer-from": "^1.0.0", 242 | "source-map": "^0.6.0" 243 | } 244 | }, 245 | "node_modules/supports-color": { 246 | "version": "5.5.0", 247 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 248 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 249 | "dependencies": { 250 | "has-flag": "^3.0.0" 251 | }, 252 | "engines": { 253 | "node": ">=4" 254 | } 255 | }, 256 | "node_modules/table-layout": { 257 | "version": "0.4.5", 258 | "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-0.4.5.tgz", 259 | "integrity": "sha512-zTvf0mcggrGeTe/2jJ6ECkJHAQPIYEwDoqsiqBjI24mvRmQbInK5jq33fyypaCBxX08hMkfmdOqj6haT33EqWw==", 260 | "dependencies": { 261 | "array-back": "^2.0.0", 262 | "deep-extend": "~0.6.0", 263 | "lodash.padend": "^4.6.1", 264 | "typical": "^2.6.1", 265 | "wordwrapjs": "^3.0.0" 266 | }, 267 | "engines": { 268 | "node": ">=4.0.0" 269 | } 270 | }, 271 | "node_modules/table-layout/node_modules/array-back": { 272 | "version": "2.0.0", 273 | "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz", 274 | "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==", 275 | "dependencies": { 276 | "typical": "^2.6.1" 277 | }, 278 | "engines": { 279 | "node": ">=4" 280 | } 281 | }, 282 | "node_modules/table-layout/node_modules/typical": { 283 | "version": "2.6.1", 284 | "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz", 285 | "integrity": "sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0=" 286 | }, 287 | "node_modules/ts-node": { 288 | "version": "9.1.1", 289 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", 290 | "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", 291 | "dev": true, 292 | "dependencies": { 293 | "arg": "^4.1.0", 294 | "create-require": "^1.1.0", 295 | "diff": "^4.0.1", 296 | "make-error": "^1.1.1", 297 | "source-map-support": "^0.5.17", 298 | "yn": "3.1.1" 299 | }, 300 | "bin": { 301 | "ts-node": "dist/bin.js", 302 | "ts-node-script": "dist/bin-script.js", 303 | "ts-node-transpile-only": "dist/bin-transpile.js", 304 | "ts-script": "dist/bin-script-deprecated.js" 305 | }, 306 | "engines": { 307 | "node": ">=10.0.0" 308 | }, 309 | "peerDependencies": { 310 | "typescript": ">=2.7" 311 | } 312 | }, 313 | "node_modules/typescript": { 314 | "version": "4.5.3", 315 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.3.tgz", 316 | "integrity": "sha512-eVYaEHALSt+s9LbvgEv4Ef+Tdq7hBiIZgii12xXJnukryt3pMgJf6aKhoCZ3FWQsu6sydEnkg11fYXLzhLBjeQ==", 317 | "dev": true, 318 | "bin": { 319 | "tsc": "bin/tsc", 320 | "tsserver": "bin/tsserver" 321 | }, 322 | "engines": { 323 | "node": ">=4.2.0" 324 | } 325 | }, 326 | "node_modules/typical": { 327 | "version": "4.0.0", 328 | "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", 329 | "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", 330 | "engines": { 331 | "node": ">=8" 332 | } 333 | }, 334 | "node_modules/wordwrapjs": { 335 | "version": "3.0.0", 336 | "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-3.0.0.tgz", 337 | "integrity": "sha512-mO8XtqyPvykVCsrwj5MlOVWvSnCdT+C+QVbm6blradR7JExAhbkZ7hZ9A+9NUtwzSqrlUo9a67ws0EiILrvRpw==", 338 | "dependencies": { 339 | "reduce-flatten": "^1.0.1", 340 | "typical": "^2.6.1" 341 | }, 342 | "engines": { 343 | "node": ">=4.0.0" 344 | } 345 | }, 346 | "node_modules/wordwrapjs/node_modules/typical": { 347 | "version": "2.6.1", 348 | "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz", 349 | "integrity": "sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0=" 350 | }, 351 | "node_modules/yn": { 352 | "version": "3.1.1", 353 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 354 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", 355 | "dev": true, 356 | "engines": { 357 | "node": ">=6" 358 | } 359 | } 360 | }, 361 | "dependencies": { 362 | "@types/command-line-args": { 363 | "version": "5.2.0", 364 | "resolved": "https://registry.npmjs.org/@types/command-line-args/-/command-line-args-5.2.0.tgz", 365 | "integrity": "sha512-UuKzKpJJ/Ief6ufIaIzr3A/0XnluX7RvFgwkV89Yzvm77wCh1kFaFmqN8XEnGcN62EuHdedQjEMb8mYxFLGPyA==", 366 | "dev": true 367 | }, 368 | "@types/command-line-usage": { 369 | "version": "5.0.2", 370 | "resolved": "https://registry.npmjs.org/@types/command-line-usage/-/command-line-usage-5.0.2.tgz", 371 | "integrity": "sha512-n7RlEEJ+4x4TS7ZQddTmNSxP+zziEG0TNsMfiRIxcIVXt71ENJ9ojeXmGO3wPoTdn7pJcU2xc3CJYMktNT6DPg==", 372 | "dev": true 373 | }, 374 | "@types/node": { 375 | "version": "14.18.0", 376 | "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.0.tgz", 377 | "integrity": "sha512-0GeIl2kmVMXEnx8tg1SlG6Gg8vkqirrW752KqolYo1PHevhhZN3bhJ67qHj+bQaINhX0Ra3TlWwRvMCd9iEfNQ==", 378 | "dev": true 379 | }, 380 | "@types/pluralize": { 381 | "version": "0.0.29", 382 | "resolved": "https://registry.npmjs.org/@types/pluralize/-/pluralize-0.0.29.tgz", 383 | "integrity": "sha512-BYOID+l2Aco2nBik+iYS4SZX0Lf20KPILP5RGmM1IgzdwNdTs0eebiFriOPcej1sX9mLnSoiNte5zcFxssgpGA==", 384 | "dev": true 385 | }, 386 | "ansi-styles": { 387 | "version": "3.2.1", 388 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 389 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 390 | "requires": { 391 | "color-convert": "^1.9.0" 392 | } 393 | }, 394 | "arg": { 395 | "version": "4.1.3", 396 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 397 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", 398 | "dev": true 399 | }, 400 | "array-back": { 401 | "version": "3.1.0", 402 | "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", 403 | "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==" 404 | }, 405 | "buffer-from": { 406 | "version": "1.1.2", 407 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", 408 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", 409 | "dev": true 410 | }, 411 | "chalk": { 412 | "version": "2.4.2", 413 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 414 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 415 | "requires": { 416 | "ansi-styles": "^3.2.1", 417 | "escape-string-regexp": "^1.0.5", 418 | "supports-color": "^5.3.0" 419 | } 420 | }, 421 | "color-convert": { 422 | "version": "1.9.3", 423 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 424 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 425 | "requires": { 426 | "color-name": "1.1.3" 427 | } 428 | }, 429 | "color-name": { 430 | "version": "1.1.3", 431 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 432 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" 433 | }, 434 | "command-line-args": { 435 | "version": "5.2.0", 436 | "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.0.tgz", 437 | "integrity": "sha512-4zqtU1hYsSJzcJBOcNZIbW5Fbk9BkjCp1pZVhQKoRaWL5J7N4XphDLwo8aWwdQpTugxwu+jf9u2ZhkXiqp5Z6A==", 438 | "requires": { 439 | "array-back": "^3.1.0", 440 | "find-replace": "^3.0.0", 441 | "lodash.camelcase": "^4.3.0", 442 | "typical": "^4.0.0" 443 | } 444 | }, 445 | "command-line-usage": { 446 | "version": "5.0.5", 447 | "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-5.0.5.tgz", 448 | "integrity": "sha512-d8NrGylA5oCXSbGoKz05FkehDAzSmIm4K03S5VDh4d5lZAtTWfc3D1RuETtuQCn8129nYfJfDdF7P/lwcz1BlA==", 449 | "requires": { 450 | "array-back": "^2.0.0", 451 | "chalk": "^2.4.1", 452 | "table-layout": "^0.4.3", 453 | "typical": "^2.6.1" 454 | }, 455 | "dependencies": { 456 | "array-back": { 457 | "version": "2.0.0", 458 | "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz", 459 | "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==", 460 | "requires": { 461 | "typical": "^2.6.1" 462 | } 463 | }, 464 | "typical": { 465 | "version": "2.6.1", 466 | "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz", 467 | "integrity": "sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0=" 468 | } 469 | } 470 | }, 471 | "create-require": { 472 | "version": "1.1.1", 473 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 474 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", 475 | "dev": true 476 | }, 477 | "deep-extend": { 478 | "version": "0.6.0", 479 | "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", 480 | "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" 481 | }, 482 | "diff": { 483 | "version": "4.0.2", 484 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 485 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 486 | "dev": true 487 | }, 488 | "escape-string-regexp": { 489 | "version": "1.0.5", 490 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 491 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" 492 | }, 493 | "find-replace": { 494 | "version": "3.0.0", 495 | "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", 496 | "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", 497 | "requires": { 498 | "array-back": "^3.0.1" 499 | } 500 | }, 501 | "has-flag": { 502 | "version": "3.0.0", 503 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 504 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" 505 | }, 506 | "lodash.camelcase": { 507 | "version": "4.3.0", 508 | "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", 509 | "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" 510 | }, 511 | "lodash.padend": { 512 | "version": "4.6.1", 513 | "resolved": "https://registry.npmjs.org/lodash.padend/-/lodash.padend-4.6.1.tgz", 514 | "integrity": "sha1-U8y6BH0G4VjTEfRdpiX05J5vFm4=" 515 | }, 516 | "make-error": { 517 | "version": "1.3.6", 518 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 519 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", 520 | "dev": true 521 | }, 522 | "reduce-flatten": { 523 | "version": "1.0.1", 524 | "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-1.0.1.tgz", 525 | "integrity": "sha1-JYx479FT3fk8tWEjf2EYTzaW4yc=" 526 | }, 527 | "source-map": { 528 | "version": "0.6.1", 529 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 530 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 531 | "dev": true 532 | }, 533 | "source-map-support": { 534 | "version": "0.5.21", 535 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", 536 | "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", 537 | "dev": true, 538 | "requires": { 539 | "buffer-from": "^1.0.0", 540 | "source-map": "^0.6.0" 541 | } 542 | }, 543 | "supports-color": { 544 | "version": "5.5.0", 545 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 546 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 547 | "requires": { 548 | "has-flag": "^3.0.0" 549 | } 550 | }, 551 | "table-layout": { 552 | "version": "0.4.5", 553 | "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-0.4.5.tgz", 554 | "integrity": "sha512-zTvf0mcggrGeTe/2jJ6ECkJHAQPIYEwDoqsiqBjI24mvRmQbInK5jq33fyypaCBxX08hMkfmdOqj6haT33EqWw==", 555 | "requires": { 556 | "array-back": "^2.0.0", 557 | "deep-extend": "~0.6.0", 558 | "lodash.padend": "^4.6.1", 559 | "typical": "^2.6.1", 560 | "wordwrapjs": "^3.0.0" 561 | }, 562 | "dependencies": { 563 | "array-back": { 564 | "version": "2.0.0", 565 | "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz", 566 | "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==", 567 | "requires": { 568 | "typical": "^2.6.1" 569 | } 570 | }, 571 | "typical": { 572 | "version": "2.6.1", 573 | "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz", 574 | "integrity": "sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0=" 575 | } 576 | } 577 | }, 578 | "ts-node": { 579 | "version": "9.1.1", 580 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", 581 | "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", 582 | "dev": true, 583 | "requires": { 584 | "arg": "^4.1.0", 585 | "create-require": "^1.1.0", 586 | "diff": "^4.0.1", 587 | "make-error": "^1.1.1", 588 | "source-map-support": "^0.5.17", 589 | "yn": "3.1.1" 590 | } 591 | }, 592 | "typescript": { 593 | "version": "4.5.3", 594 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.3.tgz", 595 | "integrity": "sha512-eVYaEHALSt+s9LbvgEv4Ef+Tdq7hBiIZgii12xXJnukryt3pMgJf6aKhoCZ3FWQsu6sydEnkg11fYXLzhLBjeQ==", 596 | "dev": true 597 | }, 598 | "typical": { 599 | "version": "4.0.0", 600 | "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", 601 | "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==" 602 | }, 603 | "wordwrapjs": { 604 | "version": "3.0.0", 605 | "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-3.0.0.tgz", 606 | "integrity": "sha512-mO8XtqyPvykVCsrwj5MlOVWvSnCdT+C+QVbm6blradR7JExAhbkZ7hZ9A+9NUtwzSqrlUo9a67ws0EiILrvRpw==", 607 | "requires": { 608 | "reduce-flatten": "^1.0.1", 609 | "typical": "^2.6.1" 610 | }, 611 | "dependencies": { 612 | "typical": { 613 | "version": "2.6.1", 614 | "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz", 615 | "integrity": "sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0=" 616 | } 617 | } 618 | }, 619 | "yn": { 620 | "version": "3.1.1", 621 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 622 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", 623 | "dev": true 624 | } 625 | } 626 | } 627 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "strapi-to-typescript", 3 | "version": "2.0.14", 4 | "description": "Convert the strapi models to typescript interfaces.", 5 | "main": "index.js", 6 | "bin": { 7 | "sts": "./bin/run.js" 8 | }, 9 | "scripts": { 10 | "start": "tsc -w", 11 | "prebuild": "rm -rf ./dist", 12 | "build": "tsc && cp ./src/.stsconfig.js ./dist", 13 | "patch-release": "npm version patch && npm publish && git push --follow-tags", 14 | "minor-release": "npm version minor && npm publish && git push --follow-tags", 15 | "pretest": "rm -rf ./src/test/out*", 16 | "test": "ts-node src/test.ts", 17 | "test12": "ts-node src/test.ts test1 test2", 18 | "prepublishOnly": "npm run build && npm run test" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/erikvullings/strapi-to-typescript.git" 23 | }, 24 | "keywords": [ 25 | "Strapi", 26 | "TypeScript", 27 | "model", 28 | "generator" 29 | ], 30 | "author": "Erik Vullings (http://www.tno.nl)", 31 | "contributors": [ 32 | "Anthony Perron (http://anthonyperron.fr)" 33 | ], 34 | "license": "MIT", 35 | "bugs": { 36 | "url": "https://github.com/erikvullings/strapi-to-typescript/issues" 37 | }, 38 | "homepage": "https://github.com/erikvullings/strapi-to-typescript#readme", 39 | "dependencies": { 40 | "command-line-args": "^5.1.1", 41 | "command-line-usage": "^5.0.5" 42 | }, 43 | "devDependencies": { 44 | "@types/command-line-args": "^5.0.0", 45 | "@types/command-line-usage": "^5.0.1", 46 | "@types/node": "^14.14.10", 47 | "@types/pluralize": "^0.0.29", 48 | "ts-node": "^9.0.0", 49 | "typescript": "^4.1.2" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/.stsconfig.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import('strapi-to-typescript')} 3 | */ 4 | const config = { 5 | 6 | }; 7 | module.exports = config; -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | const npmPackage = require('../package.json'); 2 | import commandLineUsage from 'command-line-usage'; 3 | import commandLineArgs from 'command-line-args'; 4 | import { exec } from './processor'; 5 | import { ICommandOptions, IConfigOptions } from '..'; 6 | import { resolve } from 'path' 7 | 8 | 9 | function examplePath() { 10 | const pathToEx = `${__dirname}/.stsconfig.js` 11 | if (pathToEx.startsWith(process.cwd())) return pathToEx.replace(process.cwd(), '.') 12 | return pathToEx; 13 | } 14 | 15 | export class CommandLineInterface { 16 | public static optionDefinitions: commandLineUsage.OptionDefinition[] = [ 17 | { 18 | name: 'help', 19 | alias: 'h', 20 | type: Boolean, 21 | description: 'Show help text.', 22 | }, 23 | { 24 | name: 'input', 25 | alias: 'i', 26 | type: String, 27 | multiple: true, 28 | typeLabel: '{underline String}', 29 | defaultOption: true, 30 | description: 'Input folder with the Strapi models (*.settings.json)', 31 | }, 32 | { 33 | name: 'components', 34 | alias: 'g', 35 | type: String, 36 | typeLabel: '{underline String}', 37 | description: 'Input folder with the Strapi components (*.json)' 38 | }, 39 | { 40 | name: 'inputGroup', 41 | type: String, 42 | typeLabel: '{underline String}', 43 | description: 'Deprecated. use: -g --components' 44 | }, 45 | { 46 | name: 'output', 47 | alias: 'o', 48 | type: String, 49 | typeLabel: '{underline String}', 50 | defaultValue: '.', 51 | description: 'Output folder with the TypeScript models. (default: current directory)', 52 | }, 53 | { 54 | name: 'config', 55 | alias: 'c', 56 | type: String, 57 | typeLabel: '{underline String}', 58 | description: 'Advanced configuration file', 59 | }, 60 | { 61 | name: 'nested', 62 | alias: 'n', 63 | type: Boolean, 64 | defaultValue: false, 65 | description: 'add each interface in its own folder.', 66 | }, 67 | { 68 | name: 'enum', 69 | alias: 'e', 70 | type: Boolean, 71 | defaultValue: false, 72 | description: 'Enumeration is generate, else string literal types is used', 73 | }, 74 | { 75 | name: 'collectionCanBeUndefined', 76 | alias: 'u', 77 | type: Boolean, 78 | defaultValue: false, 79 | description: 'collection can be undefined/optional', 80 | }, 81 | ]; 82 | 83 | public static sections = [ 84 | { 85 | header: `${npmPackage.name.toUpperCase()}, v${npmPackage.version}`, 86 | content: `${npmPackage.license} license. 87 | 88 | ${npmPackage.description} 89 | 90 | Usage: sts ([OPTION]...) [INPUT FOLDER]... 91 | `, 92 | }, 93 | { 94 | header: 'Options', 95 | optionList: CommandLineInterface.optionDefinitions, 96 | }, 97 | { 98 | header: 'Examples', 99 | content: [ 100 | { 101 | desc: '01. Convert the Strapi API folder and write the results to current folder.', 102 | example: '$ sts ./api', 103 | }, 104 | { 105 | desc: '02. Convert the Strapi API folder and write the results to output folder.', 106 | example: '$ sts ./api -o ./sts', 107 | }, 108 | { 109 | desc: '03. Convert the Strapi API folder with components and write the results to output folder.', 110 | example: '$ sts ./api -g ./components -o ./sts', 111 | }, 112 | { 113 | desc: '04. Add each interface to its own folder.', 114 | example: '$ sts ./api -o ./sts -n', 115 | }, 116 | { 117 | desc: '05. Define multiple input folders.', 118 | example: '$ sts ./api ./node_modules/strapi-plugin-users-permissions/models/ ./node_modules/strapi-plugin-upload/models/', 119 | }, 120 | { 121 | desc: `06. Use advanced configuration. See example: ${examplePath()}`, 122 | example: '$ sts -c ./stsconfig.js', 123 | } 124 | ], 125 | }, 126 | ]; 127 | } 128 | 129 | 130 | const options = commandLineArgs(CommandLineInterface.optionDefinitions) as ICommandOptions; 131 | 132 | const usage = commandLineUsage(CommandLineInterface.sections); 133 | 134 | const log = console.log; 135 | const warn = (...x: any[]) => { 136 | log(usage); 137 | console.warn('\x1b[31m%s\x1b[0m', ...x); 138 | process.exit(1); 139 | } 140 | 141 | if (options.help) { 142 | log(usage); 143 | process.exit(0); 144 | } else { 145 | 146 | // if arg config file, merge command line options with config file options 147 | const mergedOptions: IConfigOptions = (options.config) ? { 148 | ...options, 149 | ...require(resolve(process.cwd(), options.config)) 150 | } : options; 151 | 152 | if (!mergedOptions.input) { 153 | warn('need input folder'); 154 | } else if ('inputGroup' in mergedOptions && !mergedOptions.inputGroup) { 155 | warn('option -g need argument'); 156 | } else { 157 | exec(mergedOptions); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/importer.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | import { IStrapiModel } from './models/strapi-model'; 4 | 5 | /** 6 | * Recursively walk a directory asynchronously and obtain all file names (with full path). 7 | * 8 | * @param dir Folder name you want to recursively process 9 | * @param done Callback function to return the results when the processing is done. Returns all files with full path. 10 | * @param filter Optional filter to specify which files to include, e.g. for json files: (f: string) => /.json$/.test(f) 11 | */ 12 | const walk = ( 13 | dir: string, 14 | done: (err: Error | null, files?: string[]) => void, 15 | filter?: (f: string) => boolean 16 | ) => { 17 | let foundFiles: string[] = []; 18 | fs.readdir(dir, (err: NodeJS.ErrnoException | null, list: string[]) => { 19 | if (err) { 20 | return done(err); 21 | } 22 | let pending = list.length; 23 | if (!pending) { 24 | return done(null, foundFiles); 25 | } 26 | list.forEach((file: string) => { 27 | file = path.resolve(dir, file); 28 | // tslint:disable-next-line:variable-name 29 | fs.stat(file, (_err2, stat) => { 30 | if (stat && stat.isDirectory()) { 31 | walk( 32 | file, 33 | // tslint:disable-next-line:variable-name 34 | (_err3, res) => { 35 | if (res) { 36 | foundFiles = foundFiles.concat(res); 37 | } 38 | if (!--pending) { 39 | done(null, foundFiles); 40 | } 41 | }, 42 | filter 43 | ); 44 | } else { 45 | if (typeof filter === 'undefined' || (filter && filter(file))) { 46 | foundFiles.push(file); 47 | } 48 | if (!--pending) { 49 | done(null, foundFiles); 50 | } 51 | } 52 | }); 53 | }); 54 | }); 55 | }; 56 | 57 | export const findFiles = (dir: string, ext: RegExp = /.settings.json$/, exclude: string[] = []) => 58 | new Promise((resolve, reject) => { 59 | walk( 60 | dir, 61 | (err, files) => { 62 | if (err) { 63 | reject(err); 64 | } else if (files) { 65 | resolve(files); 66 | } 67 | }, 68 | (f: string) => ext.test(f) && !exclude.map(f => path.resolve(f)).find(x => f.startsWith(x)) 69 | ); 70 | }); 71 | 72 | 73 | /** 74 | * Wrapper around "findFiles". 75 | * 76 | */ 77 | export async function findFilesFromMultipleDirectories(...files: string[]): Promise { 78 | const exclude = files.filter(f => f.startsWith("!")).map(f => f.replace(/^!/, '')) 79 | const inputs = [... new Set(files.filter(f => !f.startsWith("!")))] 80 | 81 | var actions = inputs.map(i => fs.statSync(i).isFile() ? [i] : findFiles(i, /.settings.json$/, exclude)); // run the function over all items 82 | 83 | // we now have a promises array and we want to wait for it 84 | 85 | var results = await Promise.all(actions); // pass array of promises 86 | 87 | // flatten 88 | return (new Array()).concat.apply([], results) 89 | } 90 | 91 | /* 92 | */ 93 | export const importFiles = (files: string[], results: IStrapiModel[] = [], merge: Partial = {}) => 94 | new Promise((resolve, reject) => { 95 | 96 | let pending = files.length; 97 | if (files.length === 0) resolve(results); 98 | files.forEach(f => { 99 | 100 | try { 101 | const data = fs.readFileSync(f, { encoding: 'utf8' }); 102 | 103 | pending--; 104 | 105 | let strapiModel = Object.assign(JSON.parse(data), { _filename: f, ...merge }) 106 | 107 | if(strapiModel.info && !strapiModel.info.name && strapiModel.info.displayName) 108 | strapiModel.info.name = strapiModel.info.displayName; 109 | 110 | if (strapiModel.info && strapiModel.info.name) { 111 | 112 | let sameNameIndex = results.map(s => s.info.name).indexOf(strapiModel.info.name); 113 | if (sameNameIndex === -1) { 114 | results.push(strapiModel); 115 | } else { 116 | console.warn(`Already have model '${strapiModel.info.name}' => skip ${results[sameNameIndex]._filename} use ${strapiModel._filename}`) 117 | results[sameNameIndex] = strapiModel; 118 | } 119 | } else { 120 | results.push(strapiModel); 121 | } 122 | 123 | if (pending === 0) { 124 | resolve(results); 125 | } 126 | } catch (err) { 127 | reject(err); 128 | } 129 | }) 130 | }); 131 | -------------------------------------------------------------------------------- /src/models/strapi-model.ts: -------------------------------------------------------------------------------- 1 | export type StrapiType = 'string' | 'number' | 'boolean' | 'text' | 'date' | 'email' | 'component' | 'enumeration' | 'dynamiczone'; 2 | 3 | export interface IStrapiModelAttribute { 4 | unique?: boolean; 5 | required?: boolean; 6 | type?: StrapiType; 7 | default?: string | number | boolean; 8 | dominant?: boolean; 9 | collection?: string; 10 | model?: string; 11 | via?: string; 12 | plugin?: string; 13 | enum?: string[]; 14 | component?: string; 15 | components?: string[]; 16 | repeatable?: boolean; 17 | } 18 | 19 | export interface IStrapiModel { 20 | /** Not from Strapi, but is the filename on disk */ 21 | _filename: string; 22 | _isComponent?: boolean; 23 | 24 | connection: string; 25 | collectionName: string; 26 | info: { 27 | name: string; 28 | description: string; 29 | icon?: string; 30 | }; 31 | options?: { 32 | timestamps: boolean; 33 | }; 34 | attributes: { [attr: string]: IStrapiModelAttribute }; 35 | } 36 | -------------------------------------------------------------------------------- /src/processor.ts: -------------------------------------------------------------------------------- 1 | import { convert } from './ts-exporter'; 2 | import { IConfigOptions } from '..'; 3 | import { findFilesFromMultipleDirectories, importFiles, findFiles } from './importer'; 4 | 5 | const log = console.log; 6 | const logError = console.error; 7 | 8 | export const exec = async (options: IConfigOptions) => { 9 | try { 10 | // find *.settings.json 11 | let strapiModels = await importFiles(await findFilesFromMultipleDirectories(...options.input)); 12 | 13 | if (options.inputGroup) console.log("option '--inputGroup' is deprecated use '--components'."); 14 | if (options.components || options.inputGroup ) 15 | strapiModels = await importFiles(await findFiles(options.components || options.inputGroup, /.json/), strapiModels, { _isComponent: true }); 16 | 17 | // build and write .ts 18 | const count = await convert(strapiModels, options); 19 | 20 | log(`Generated ${count} interfaces.`); 21 | } catch (e) { 22 | logError(e) 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | import * as child_process from 'child_process'; 2 | import * as fs from 'fs'; 3 | 4 | async function execCmd(cmd: string) { 5 | return new Promise((r, e) => { 6 | child_process.exec(cmd, (error, stdout, stderr) => { 7 | if (error || stderr) { 8 | console.error(`error: ${stderr || error && error.message}`); 9 | e(cmd) 10 | } 11 | console.log(`$ ${cmd}\n${stdout}`); 12 | r(null) 13 | }) 14 | }) 15 | } 16 | 17 | async function runTest(file: string) { 18 | try { 19 | const testConf = require(`./test/${file}.config.js`) 20 | if (testConf.output && (testConf.output as string).startsWith('src/test')) { 21 | await execCmd(`rm -rf ./${testConf.output}`) 22 | } else { 23 | console.error(`output of test '${file}' is wrong: ${testConf.output}`) 24 | process.exit(1) 25 | } 26 | await execCmd(`./node_modules/.bin/ts-node src/cli.ts -c ./src/test/${file}.config.js`); 27 | await execCmd(`./node_modules/.bin/ts-node src/test/${file}.assert.ts`) 28 | } catch (e) { 29 | console.error(e); 30 | process.exit(1); 31 | } 32 | } 33 | 34 | async function runTestSuite(files: string[]) { 35 | for (let file of files) { 36 | await runTest(file); 37 | } 38 | } 39 | 40 | if (process.argv.length > 2) 41 | runTestSuite(process.argv.slice(2)) 42 | else 43 | runTestSuite( 44 | fs.readdirSync('./src/test') 45 | .filter(f => /.config.js$/.test(f)) 46 | .map(f => f.replace('.config.js', '')) 47 | ); 48 | 49 | -------------------------------------------------------------------------------- /src/test/api/testobject/models/testobject.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "collectionName": "testobjects", 4 | "info": { 5 | "name": "testobject" 6 | }, 7 | "options": { 8 | "increments": true, 9 | "timestamps": true 10 | }, 11 | "attributes": { 12 | "string_optional_field": { 13 | "type": "string", 14 | "required": false 15 | }, 16 | "short_text_field": { 17 | "type": "string", 18 | "required": true 19 | }, 20 | "long_text_field": { 21 | "type": "string", 22 | "required": true 23 | }, 24 | "richtext_field": { 25 | "type": "richtext", 26 | "required": true 27 | }, 28 | "integer_field": { 29 | "type": "integer", 30 | "required": true 31 | }, 32 | "big_integer_field": { 33 | "type": "biginteger", 34 | "required": true 35 | }, 36 | "truncated_float_field": { 37 | "type": "decimal", 38 | "required": true 39 | }, 40 | "float_field": { 41 | "type": "float", 42 | "required": true 43 | }, 44 | "date_field": { 45 | "type": "date", 46 | "required": true 47 | }, 48 | "datetime_field": { 49 | "type": "datetime", 50 | "required": true 51 | }, 52 | "time_field": { 53 | "type": "time", 54 | "required": true 55 | }, 56 | "boolean_field": { 57 | "type": "boolean", 58 | "required": true 59 | }, 60 | "email_field": { 61 | "type": "email", 62 | "required": true 63 | }, 64 | "password_field": { 65 | "type": "password", 66 | "required": true 67 | }, 68 | "enum_field": { 69 | "type": "enumeration", 70 | "required": true, 71 | "enum": ["enum1", "enum2"] 72 | }, 73 | "mulitple_media_field": { 74 | "collection": "file", 75 | "via": "related", 76 | "allowedTypes": [ 77 | "images", "files", "videos" 78 | ], 79 | "plugin": "upload", 80 | "required": true 81 | }, 82 | "single_media_field": { 83 | "model": "file", 84 | "via": "related", 85 | "allowedTypes": [ 86 | "images", "files", "videos" 87 | ], 88 | "plugin": "upload", 89 | "required": false 90 | }, 91 | "json_field": { 92 | "type": "json", 93 | "required": true 94 | 95 | }, 96 | "uid_field": { 97 | "type": "uid", 98 | "required": true 99 | }, 100 | "component_complex_repeatable": { 101 | "component": "content.complex", 102 | "repeatable": true, 103 | "type": "component", 104 | "required": true 105 | }, 106 | "component_complex_optional": { 107 | "component": "content.complex", 108 | "repeatable": false, 109 | "type": "component", 110 | "required": false 111 | }, 112 | "component_complex": { 113 | "component": "content.complex", 114 | "repeatable": false, 115 | "type": "component", 116 | "required": true 117 | }, 118 | "dynamiczone": { 119 | "type": "dynamiczone", 120 | "components": [ 121 | "content.complex", 122 | "content.simple", 123 | "content.camel-case", 124 | "content.another" 125 | ] 126 | }, 127 | "testobjectrelation": { 128 | "via": "testobjects_one_many", 129 | "model": "testobjectrelation" 130 | }, 131 | "testobjectrelations": { 132 | "via": "testobjects_many_many", 133 | "collection": "testobjectrelation" 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/test/api/testobjectrelation/models/testobjectrelation.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "collectionName": "testobjectrelations", 4 | "info": { 5 | "name": "testobjectrelation" 6 | }, 7 | "options": { 8 | "increments": true, 9 | "timestamps": true 10 | }, 11 | "attributes": { 12 | "testobject_one_way": { 13 | "model": "testobject" 14 | }, 15 | "testobject_one_one": { 16 | "model": "testobject", 17 | "via": "testobjectrelation" 18 | }, 19 | "testobjects_one_many": { 20 | "collection": "testobject", 21 | "via": "testobjectrelation" 22 | }, 23 | "testobject_many_one": { 24 | "model": "testobject", 25 | "via": "testobjectrelations" 26 | }, 27 | "testobjects_many_many": { 28 | "collection": "testobject", 29 | "via": "testobjectrelations", 30 | "dominant": true 31 | }, 32 | "testobject_dependonitself": { 33 | "model": "testobjectrelation" 34 | }, 35 | "testobjects_poly": { 36 | "collection": "testobject" 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/test/components/content/another.json: -------------------------------------------------------------------------------- 1 | { 2 | "collectionName": "components_content_anothers", 3 | "info": { 4 | "name": "Just a Complete Other Name", 5 | "icon": "angle-right" 6 | }, 7 | "options": {}, 8 | "attributes": { 9 | "reference": { 10 | "model": "testobject" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/test/components/content/camel-case.json: -------------------------------------------------------------------------------- 1 | { 2 | "collectionName": "components_content_with_dashs", 3 | "info": { 4 | "name": "WithDash", 5 | "icon": "angle-right" 6 | }, 7 | "options": {}, 8 | "attributes": { 9 | "reference": { 10 | "model": "testobject" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/test/components/content/complex.json: -------------------------------------------------------------------------------- 1 | { 2 | "collectionName": "components_content_complexs", 3 | "info": { 4 | "name": "complex", 5 | "icon": "angle-right" 6 | }, 7 | "options": {}, 8 | "attributes": { 9 | "variant": { 10 | "type": "enumeration", 11 | "enum": [ 12 | "a", 13 | "b" 14 | ] 15 | }, 16 | "key": { 17 | "type": "string", 18 | "regex": "\\w" 19 | }, 20 | "single": { 21 | "type": "component", 22 | "repeatable": false, 23 | "component": "content.complex" 24 | }, 25 | "repeatable": { 26 | "type": "component", 27 | "repeatable": true, 28 | "component": "content.complex" 29 | }, 30 | "dynamic": { 31 | "type": "dynamiczone", 32 | "components": [ 33 | "content.complex", 34 | "content.simple" 35 | ] 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/components/content/simple.json: -------------------------------------------------------------------------------- 1 | { 2 | "collectionName": "components_content_simples", 3 | "info": { 4 | "name": "simple", 5 | "icon": "angle-right" 6 | }, 7 | "options": {}, 8 | "attributes": { 9 | "text": { 10 | "type": "text" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/test/strapi/strapi-plugin-navigation/navigation.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "collectionName": "navigations", 3 | "info": { 4 | "name": "navigation", 5 | "description": "Navigation container" 6 | }, 7 | "options": { 8 | "increments": true, 9 | "comment": "" 10 | }, 11 | "pluginOptions": { 12 | "content-manager": { 13 | "visible": false 14 | }, 15 | "content-type-builder": { 16 | "visible": false 17 | } 18 | }, 19 | "attributes": { 20 | "name": { 21 | "type": "text", 22 | "configurable": false, 23 | "required": true 24 | }, 25 | "slug": { 26 | "type": "uid", 27 | "target": "name", 28 | "configurable": false, 29 | "required": true 30 | }, 31 | "visible": { 32 | "type": "boolean", 33 | "default": false, 34 | "configurable": false 35 | }, 36 | "items": { 37 | "collection": "navigationitem", 38 | "plugin": "navigation", 39 | "via": "master", 40 | "configurable": false 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/strapi/strapi-plugin-navigation/navigationItem.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "collectionName": "navigations_items", 3 | "info": { 4 | "name": "navigationItem", 5 | "description": "" 6 | }, 7 | "options": { 8 | "increments": true, 9 | "timestamps": true, 10 | "comment": "Navigation Item" 11 | }, 12 | "pluginOptions": { 13 | "content-manager": { 14 | "visible": false 15 | }, 16 | "content-type-builder": { 17 | "visible": false 18 | } 19 | }, 20 | "attributes": { 21 | "title": { 22 | "type": "text", 23 | "configurable": false, 24 | "required": true 25 | }, 26 | "type": { 27 | "type": "enumeration", 28 | "enum": ["INTERNAL", "EXTERNAL"], 29 | "default": "INTERNAL", 30 | "configurable": false 31 | }, 32 | "path": { 33 | "type": "text", 34 | "targetField": "title", 35 | "configurable": false 36 | }, 37 | "externalPath": { 38 | "type": "text", 39 | "configurable": false 40 | }, 41 | "uiRouterKey": { 42 | "type": "string", 43 | "configurable": false 44 | }, 45 | "menuAttached": { 46 | "type": "boolean", 47 | "default": false, 48 | "configurable": false 49 | }, 50 | "order": { 51 | "type": "integer", 52 | "default": 0, 53 | "configurable": false 54 | }, 55 | "related": { 56 | "collection": "*", 57 | "filter": "field", 58 | "configurable": false 59 | }, 60 | "parent": { 61 | "columnName": "parent", 62 | "model": "navigationItem", 63 | "plugin": "navigation", 64 | "configurable": false, 65 | "default": null 66 | }, 67 | "master": { 68 | "columnName": "master", 69 | "model": "navigation", 70 | "plugin": "navigation", 71 | "configurable": false 72 | }, 73 | "audience": { 74 | "collection": "audience", 75 | "plugin": "navigation" 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/test/strapi/strapi-plugin-upload/models/File.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "name": "file", 4 | "description": "" 5 | }, 6 | "options": { 7 | "timestamps": true 8 | }, 9 | "attributes": { 10 | "name": { 11 | "type": "string", 12 | "configurable": false, 13 | "required": true 14 | }, 15 | "alternativeText": { 16 | "type": "string", 17 | "configurable": false 18 | }, 19 | "caption": { 20 | "type": "string", 21 | "configurable": false 22 | }, 23 | "width": { 24 | "type": "integer", 25 | "configurable": false 26 | }, 27 | "height": { 28 | "type": "integer", 29 | "configurable": false 30 | }, 31 | "formats": { 32 | "type": "json", 33 | "configurable": false 34 | }, 35 | "hash": { 36 | "type": "string", 37 | "configurable": false, 38 | "required": true 39 | }, 40 | "ext": { 41 | "type": "string", 42 | "configurable": false 43 | }, 44 | "mime": { 45 | "type": "string", 46 | "configurable": false, 47 | "required": true 48 | }, 49 | "size": { 50 | "type": "decimal", 51 | "configurable": false, 52 | "required": true 53 | }, 54 | "url": { 55 | "type": "string", 56 | "configurable": false, 57 | "required": true 58 | }, 59 | "previewUrl": { 60 | "type": "string", 61 | "configurable": false 62 | }, 63 | "provider": { 64 | "type": "string", 65 | "configurable": false, 66 | "required": true 67 | }, 68 | "provider_metadata": { 69 | "type": "json", 70 | "configurable": false 71 | }, 72 | "related": { 73 | "collection": "*", 74 | "filter": "field", 75 | "configurable": false 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/test/strapi/strapi-plugin-users-permissions/models/Permission.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "name": "permission", 4 | "description": "" 5 | }, 6 | "attributes": { 7 | "type": { 8 | "type": "string", 9 | "required": true, 10 | "configurable": false 11 | }, 12 | "controller": { 13 | "type": "string", 14 | "required": true, 15 | "configurable": false 16 | }, 17 | "action": { 18 | "type": "string", 19 | "required": true, 20 | "configurable": false 21 | }, 22 | "enabled": { 23 | "type": "boolean", 24 | "required": true, 25 | "configurable": false 26 | }, 27 | "policy": { 28 | "type": "string", 29 | "configurable": false 30 | }, 31 | "role": { 32 | "model": "role", 33 | "via": "permissions", 34 | "plugin": "users-permissions", 35 | "configurable": false 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/test/strapi/strapi-plugin-users-permissions/models/Role.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "name": "role", 4 | "description": "" 5 | }, 6 | "attributes": { 7 | "name": { 8 | "type": "string", 9 | "minLength": 3, 10 | "required": true, 11 | "configurable": false 12 | }, 13 | "description": { 14 | "type": "string", 15 | "configurable": false 16 | }, 17 | "type": { 18 | "type": "string", 19 | "unique": true, 20 | "configurable": false 21 | }, 22 | "permissions": { 23 | "collection": "permission", 24 | "via": "role", 25 | "plugin": "users-permissions", 26 | "configurable": false, 27 | "isVirtual": true 28 | }, 29 | "users": { 30 | "collection": "user", 31 | "via": "role", 32 | "configurable": false, 33 | "plugin": "users-permissions" 34 | } 35 | }, 36 | "collectionName": "users-permissions_role" 37 | } 38 | -------------------------------------------------------------------------------- /src/test/strapi/strapi-plugin-users-permissions/models/User.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "collectionName": "users-permissions_user", 3 | "info": { 4 | "name": "user", 5 | "description": "" 6 | }, 7 | "options": { 8 | "timestamps": true 9 | }, 10 | "attributes": { 11 | "username": { 12 | "type": "string", 13 | "minLength": 3, 14 | "unique": true, 15 | "configurable": false, 16 | "required": true 17 | }, 18 | "email": { 19 | "type": "email", 20 | "minLength": 6, 21 | "configurable": false, 22 | "required": true 23 | }, 24 | "provider": { 25 | "type": "string", 26 | "configurable": false 27 | }, 28 | "password": { 29 | "type": "password", 30 | "minLength": 6, 31 | "configurable": false, 32 | "private": true 33 | }, 34 | "resetPasswordToken": { 35 | "type": "string", 36 | "configurable": false, 37 | "private": true 38 | }, 39 | "confirmed": { 40 | "type": "boolean", 41 | "default": false, 42 | "configurable": false 43 | }, 44 | "blocked": { 45 | "type": "boolean", 46 | "default": false, 47 | "configurable": false 48 | }, 49 | "role": { 50 | "model": "role", 51 | "via": "users", 52 | "plugin": "users-permissions", 53 | "configurable": false 54 | }, 55 | "managees": { 56 | "plugin": "users-permissions", 57 | "collection": "user", 58 | "via": "managers", 59 | "dominant": true 60 | }, 61 | "managers": { 62 | "plugin": "users-permissions", 63 | "collection": "user", 64 | "via": "managees" 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/test1.assert.ts: -------------------------------------------------------------------------------- 1 | import { ITestobject, ITestobjectrelation, EnumITestobjectenum_field } from './out1'; 2 | import { IFile } from './out1/file' 3 | import { EnumIComplexvariant, IComplex } from "./out1/content/complex"; 4 | import { ISimple } from "./out1/content/simple"; 5 | import { IWithDash } from "./out1/content/camel-case"; 6 | import { IJustACompleteOtherName } from "./out1/content/another"; 7 | 8 | 9 | class IComplexImpl implements IComplex { 10 | id: string; 11 | variant?: EnumIComplexvariant; 12 | key?: string; 13 | single?: IComplex; 14 | repeatable: IComplex[]; 15 | dynamic: ( 16 | | ({ __component: 'content.complex' } & IComplex) 17 | | ({ __component: 'content.simple' } & ISimple) 18 | )[]; 19 | 20 | constructor(){ 21 | this.id = "id"; 22 | this.repeatable = []; 23 | this.dynamic = []; 24 | } 25 | } 26 | 27 | // implementation of Itestobject test required and type fields 28 | class ItestobjectImpl implements ITestobject { 29 | id: string; 30 | 31 | string_optional_field?: string; 32 | short_text_field: string; 33 | long_text_field: string; 34 | richtext_field: string; 35 | integer_field: number; 36 | big_integer_field: number; 37 | truncated_float_field: number; 38 | float_field: number; 39 | date_field: Date; 40 | datetime_field: any; 41 | time_field: any; 42 | boolean_field: boolean; 43 | email_field: string; 44 | password_field: string; 45 | enum_field: EnumITestobjectenum_field; 46 | mulitple_media_field: any[]; 47 | single_media_field?: IFile; 48 | json_field: { [key: string]: any; }; 49 | uid_field: any; 50 | created_by: string; 51 | component_complex: IComplex; 52 | component_complex_optional?: IComplex; 53 | component_complex_repeatable:IComplex[]; 54 | dynamiczone: ( 55 | | ({ __component: 'content.complex' } & IComplex) 56 | | ({ __component: 'content.simple' } & ISimple) 57 | | ({ __component: 'content.camel-case' } & IWithDash) 58 | | ({ __component: 'content.another' } & IJustACompleteOtherName) 59 | )[]; 60 | 61 | testobjectrelation?: ITestobjectrelation; 62 | testobjectrelations: ITestobjectrelation[]; 63 | 64 | constructor() { 65 | this.id = "id" 66 | this.short_text_field = "short_text"; 67 | this.long_text_field = "long_text_field"; 68 | this.richtext_field = "richtext_field"; 69 | this.integer_field = 1; 70 | this.big_integer_field = 2; 71 | this.truncated_float_field = 3; 72 | this.float_field = 4; 73 | this.date_field = new Date(); 74 | this.boolean_field = true; 75 | this.email_field = "email_field"; 76 | this.password_field = "password_field"; 77 | this.enum_field = EnumITestobjectenum_field.enum1; 78 | this.json_field = {}; 79 | this.created_by = "created_by"; 80 | 81 | this.testobjectrelations = []; 82 | this.dynamiczone = []; 83 | 84 | // TOFII => any field 85 | 86 | this.mulitple_media_field = []; 87 | this.datetime_field = {}; 88 | this.time_field = {}; 89 | this.uid_field = {}; 90 | 91 | this.component_complex = new IComplexImpl(); 92 | this.component_complex_repeatable = []; 93 | } 94 | } 95 | 96 | class ItestobjectrelationImpl implements ITestobjectrelation { 97 | id: string; 98 | 99 | testobject_one_way?: ITestobject; 100 | testobject_one_one?: ITestobject; 101 | testobjects_one_many: ITestobject[]; 102 | testobject_many_one?: ITestobject; 103 | testobjects_many_many: ITestobject[]; 104 | testobjects_poly: ITestobject[]; 105 | 106 | constructor(testobject: ITestobject) { 107 | this.id = "id"; 108 | this.testobjects_one_many = [testobject]; 109 | this.testobjects_many_many = [testobject]; 110 | this.testobjects_poly = [testobject]; 111 | } 112 | } 113 | 114 | // test optional fields 115 | const testobject = new ItestobjectImpl(); 116 | testobject.string_optional_field = "stringfieldoptional"; 117 | 118 | const testobjectrelation = new ItestobjectrelationImpl(testobject); 119 | testobjectrelation.testobject_one_way = testobject; 120 | testobjectrelation.testobject_one_one = testobject; 121 | testobjectrelation.testobject_many_one = testobject; -------------------------------------------------------------------------------- /src/test/test1.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import('../../index').IConfigOptions} 3 | */ 4 | const config = { 5 | input: [ 6 | 'src/test/api/', 7 | 'src/test/strapi/', 8 | '!src/test/strapi/strapi-plugin-users-permissions' 9 | ], 10 | components: 'src/test/components/content/', 11 | output: 'src/test/out1/', 12 | enum: true, 13 | nested: false, 14 | fieldType: (fieldType) => { if(fieldType === 'datetime') return 'string'}, 15 | // fieldName: (fieldName) => fieldName.replace('_', '-'), 16 | // interfaceName: name => `X${name}`, 17 | enumName: (name, interfaceName) =>`Enum${interfaceName}${name}`, 18 | excludeField: (interfaceName, fieldName) => fieldName === 'email_field', 19 | addField: (interfaceName) => { 20 | if(interfaceName === 'Xtestobject') return [ 21 | { 22 | name: "created_by", 23 | type: "string" 24 | } 25 | ] 26 | }, 27 | importAsType: (interfaceName) => interfaceName !== 'Xuser' 28 | } 29 | module.exports = config; 30 | -------------------------------------------------------------------------------- /src/test/test2.assert.ts: -------------------------------------------------------------------------------- 1 | import { ITestobject, ITestobjectrelation, EnumITestobjectenum_field } from './out2'; 2 | import { IFile } from './out2/file/file'; 3 | import { IComplex, EnumIComplexvariant } from "./out2/content/complex"; 4 | import { ISimple } from "./out2/content/simple"; 5 | import { IWithDash } from "./out2/content/camel-case"; 6 | import { IJustACompleteOtherName } from "./out2/content/another"; 7 | 8 | class IComplexImpl implements IComplex { 9 | id: string; 10 | variant?: EnumIComplexvariant; 11 | key?: string; 12 | single?: IComplex; 13 | repeatable: IComplex[]; 14 | dynamic: ( 15 | | ({ __component: 'content.complex' } & IComplex) 16 | | ({ __component: 'content.simple' } & ISimple) 17 | )[]; 18 | 19 | constructor(){ 20 | this.id = "id"; 21 | this.repeatable = []; 22 | this.dynamic = []; 23 | } 24 | } 25 | 26 | // implementation of Itestobject test required and type fields 27 | class ItestobjectImpl implements ITestobject { 28 | id: string; 29 | 30 | string_optional_field?: string; 31 | short_text_field: string; 32 | long_text_field: string; 33 | richtext_field: string; 34 | integer_field: number; 35 | big_integer_field: number; 36 | truncated_float_field: number; 37 | float_field: number; 38 | date_field: Date; 39 | datetime_field: any; 40 | time_field: any; 41 | boolean_field: boolean; 42 | email_field: string; 43 | password_field: string; 44 | enum_field: EnumITestobjectenum_field; 45 | mulitple_media_field: any[]; 46 | single_media_field?: IFile; 47 | json_field: { [key: string]: any; }; 48 | uid_field: any; 49 | created_by: string; 50 | component_complex: IComplex; 51 | component_complex_optional?: IComplex; 52 | component_complex_repeatable:IComplex[]; 53 | dynamiczone: ( 54 | | ({ __component: 'content.complex' } & IComplex) 55 | | ({ __component: 'content.simple' } & ISimple) 56 | | ({ __component: 'content.camel-case' } & IWithDash) 57 | | ({ __component: 'content.another' } & IJustACompleteOtherName) 58 | )[]; 59 | 60 | testobjectrelation?: ITestobjectrelation; 61 | testobjectrelations: ITestobjectrelation[]; 62 | 63 | constructor() { 64 | this.id = "id" 65 | this.short_text_field = "short_text"; 66 | this.long_text_field = "long_text_field"; 67 | this.richtext_field = "richtext_field"; 68 | this.integer_field = 1; 69 | this.big_integer_field = 2; 70 | this.truncated_float_field = 3; 71 | this.float_field = 4; 72 | this.date_field = new Date(); 73 | this.boolean_field = true; 74 | this.email_field = "email_field"; 75 | this.password_field = "password_field"; 76 | this.enum_field = EnumITestobjectenum_field.enum1; 77 | this.json_field = {}; 78 | this.created_by = "created_by"; 79 | 80 | this.testobjectrelations = []; 81 | this.dynamiczone = []; 82 | 83 | // TOFII => any field 84 | 85 | this.mulitple_media_field = []; 86 | this.datetime_field = {}; 87 | this.time_field = {}; 88 | this.uid_field = {}; 89 | 90 | this.component_complex = new IComplexImpl(); 91 | this.component_complex_repeatable = []; 92 | } 93 | } 94 | 95 | class ItestobjectrelationImpl implements ITestobjectrelation { 96 | id: string; 97 | 98 | testobject_one_way?: ITestobject; 99 | testobject_one_one?: ITestobject; 100 | testobjects_one_many: ITestobject[]; 101 | testobject_many_one?: ITestobject; 102 | testobjects_many_many: ITestobject[]; 103 | testobjects_poly: ITestobject[]; 104 | 105 | constructor(testobject: ITestobject) { 106 | this.id = "id"; 107 | this.testobjects_one_many = [testobject]; 108 | this.testobjects_many_many = [testobject]; 109 | this.testobjects_poly = [testobject]; 110 | } 111 | } 112 | 113 | // test optional fields 114 | const testobject = new ItestobjectImpl(); 115 | testobject.string_optional_field = "stringfieldoptional"; 116 | 117 | const testobjectrelation = new ItestobjectrelationImpl(testobject); 118 | testobjectrelation.testobject_one_way = testobject; 119 | testobjectrelation.testobject_one_one = testobject; 120 | testobjectrelation.testobject_many_one = testobject; -------------------------------------------------------------------------------- /src/test/test2.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import('../../index').IConfigOptions} 3 | */ 4 | const config = { 5 | input: [ 6 | 'src/test/api/', 7 | 'src/test/strapi/', 8 | 9 | ], 10 | components: 'src/test/components/content/', 11 | output: 'src/test/out2/', 12 | enum: true, 13 | nested: true, 14 | fieldType: (fieldType) => { if(fieldType === 'datetime') return 'string'}, 15 | // fieldName: (fieldName) => fieldName.replace('_', '-'), 16 | // interfaceName: name => `X${name}`, 17 | enumName: (name, interfaceName) =>`Enum${interfaceName}${name}`, 18 | excludeField: (interfaceName, fieldName) => fieldName === 'email_field', 19 | addField: (interfaceName) => { 20 | if(interfaceName === 'Xtestobject') return [ 21 | { 22 | name: "created_by", 23 | type: "string" 24 | } 25 | ] 26 | }, 27 | importAsType: (interfaceName) => interfaceName !== 'Xuser' 28 | } 29 | module.exports = config; 30 | -------------------------------------------------------------------------------- /src/test/test3.assert.ts: -------------------------------------------------------------------------------- 1 | import { Xtestobject, Xtestobjectrelation, EnumXtestobjectenum_field } from './out3'; 2 | import { Xfile } from './out3/Xfile'; 3 | import { Xcomplex, EnumXcomplexvariant } from "./out3/content/Xcomplex"; 4 | import { Xsimple } from "./out3/content/Xsimple"; 5 | import { XWithDash } from "./out3/content/XWithDash"; 6 | import { XJustaCompleteOtherName } from "./out3/content/XJustaCompleteOtherName"; 7 | 8 | class XcomplexImpl implements Xcomplex { 9 | id: string; 10 | variant?: EnumXcomplexvariant; 11 | key?: string; 12 | single?: Xcomplex; 13 | repeatable: Xcomplex[]; 14 | dynamic: ( 15 | | ({ __component: 'content.complex' } & Xcomplex) 16 | | ({ __component: 'content.simple' } & Xsimple) 17 | )[]; 18 | 19 | constructor(){ 20 | this.id = "id"; 21 | this.repeatable = []; 22 | this.dynamic = []; 23 | } 24 | } 25 | 26 | // implementation of Itestobject test required and type fields 27 | class ItestobjectImpl implements Xtestobject { 28 | id: string; 29 | 30 | string_optional_field?: string; 31 | short_text_field: string; 32 | long_text_field: string; 33 | richtext_field: string; 34 | integer_field: number; 35 | big_integer_field: number; 36 | truncated_float_field: number; 37 | float_field: number; 38 | date_field: Date; 39 | datetime_field: any; 40 | time_field: any; 41 | boolean_field: boolean; 42 | email_field: string; 43 | password_field: string; 44 | enum_field: EnumXtestobjectenum_field; 45 | mulitple_media_field: any[]; 46 | single_media_field?: Xfile; 47 | json_field: { [key: string]: any; }; 48 | uid_field: any; 49 | created_by: string; 50 | component_complex: Xcomplex; 51 | component_complex_optional?: Xcomplex; 52 | component_complex_repeatable:Xcomplex[]; 53 | dynamiczone: ( 54 | | ({ __component: 'content.complex' } & Xcomplex) 55 | | ({ __component: 'content.simple' } & Xsimple) 56 | | ({ __component: 'content.camel-case' } & XWithDash) 57 | | ({ __component: 'content.another' } & XJustaCompleteOtherName) 58 | )[]; 59 | 60 | testobjectrelation?: Xtestobjectrelation; 61 | testobjectrelations: Xtestobjectrelation[]; 62 | 63 | constructor() { 64 | this.id = "id" 65 | this.short_text_field = "short_text"; 66 | this.long_text_field = "long_text_field"; 67 | this.richtext_field = "richtext_field"; 68 | this.integer_field = 1; 69 | this.big_integer_field = 2; 70 | this.truncated_float_field = 3; 71 | this.float_field = 4; 72 | this.date_field = new Date(); 73 | this.boolean_field = true; 74 | this.email_field = "email_field"; 75 | this.password_field = "password_field"; 76 | this.enum_field = EnumXtestobjectenum_field.enum1; 77 | this.json_field = {}; 78 | this.created_by = "created_by"; 79 | 80 | this.testobjectrelations = []; 81 | this.dynamiczone = []; 82 | 83 | // TOFII => any field 84 | 85 | this.mulitple_media_field = []; 86 | this.datetime_field = {}; 87 | this.time_field = {}; 88 | this.uid_field = {}; 89 | 90 | this.component_complex = new XcomplexImpl(); 91 | this.component_complex_repeatable = []; 92 | } 93 | } 94 | 95 | class ItestobjectrelationImpl implements Xtestobjectrelation { 96 | id: string; 97 | 98 | testobject_one_way?: Xtestobject; 99 | testobject_one_one?: Xtestobject; 100 | testobjects_one_many: Xtestobject[]; 101 | testobject_many_one?: Xtestobject; 102 | testobjects_many_many: Xtestobject[]; 103 | testobjects_poly: Xtestobject[]; 104 | 105 | constructor(testobject: Xtestobject) { 106 | this.id = "id"; 107 | this.testobjects_one_many = [testobject]; 108 | this.testobjects_many_many = [testobject]; 109 | this.testobjects_poly = [testobject]; 110 | } 111 | } 112 | 113 | // test optional fields 114 | const testobject = new ItestobjectImpl(); 115 | testobject.string_optional_field = "stringfieldoptional"; 116 | 117 | const testobjectrelation = new ItestobjectrelationImpl(testobject); 118 | testobjectrelation.testobject_one_way = testobject; 119 | testobjectrelation.testobject_one_one = testobject; 120 | testobjectrelation.testobject_many_one = testobject; -------------------------------------------------------------------------------- /src/test/test3.config.js: -------------------------------------------------------------------------------- 1 | const { interfaceName } = require('./test1.config'); 2 | const path = require('path'); 3 | /** 4 | * @type {import('../../index').IConfigOptions} 5 | */ 6 | const config = { 7 | input: [ 8 | 'src/test/api/', 9 | 'src/test/strapi/strapi-plugin-upload/models/File.settings.json', 10 | 'src/test/strapi/strapi-plugin-users-permissions/models/User.settings.json' 11 | ], 12 | inputGroup: 'src/test/components/content/', 13 | output: 'src/test/out3/', 14 | enum: true, 15 | fieldType: (fieldType) => { if (fieldType === 'datetime') return 'string' }, 16 | // fieldName: (fieldName) => fieldName.replace('_', '-'), 17 | interfaceName: name => `X${name}`.replace(/ /g, ''), 18 | outputFileName: (interfaceName, filename) => filename.indexOf(`test${path.sep}components${path.sep}content${path.sep}`) !== -1 ? `content${path.sep}${interfaceName}` : interfaceName, 19 | enumName: (name, interfaceName) => `Enum${interfaceName}${name}`, 20 | excludeField: (interfaceName, fieldName) => fieldName === 'email_field', 21 | addField: (interfaceName) => { 22 | if (interfaceName === 'Xtestobject') return [{ 23 | name: "created_by", 24 | type: "string" 25 | }] 26 | }, 27 | importAsType: (interfaceName) => interfaceName !== 'Xuser' 28 | } 29 | module.exports = config; -------------------------------------------------------------------------------- /src/ts-exporter.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | import { IStrapiModel, IStrapiModelAttribute } from './models/strapi-model'; 4 | import { IConfigOptions } from '..'; 5 | 6 | interface IStrapiModelExtended extends IStrapiModel { 7 | // use to output filename 8 | ouputFile: string; 9 | // interface name 10 | interfaceName: string; 11 | // model name extract from *.settings.json filename. Use to link model. 12 | modelName: string; 13 | } 14 | 15 | const util = { 16 | 17 | // InterfaceName 18 | defaultToInterfaceName: (name: string) => name ? `I${name.replace(/^./, (str: string) => str.toUpperCase()).replace(/[ ]+./g, (str: string) => str.trimLeft().toUpperCase()).replace(/\//g, '')}` : 'any', 19 | overrideToInterfaceName: undefined as IConfigOptions['interfaceName'] | undefined, 20 | toInterfaceName(name: string, filename: string) { 21 | return util.overrideToInterfaceName ? util.overrideToInterfaceName(name, filename) || util.defaultToInterfaceName(name) : this.defaultToInterfaceName(name); 22 | }, 23 | 24 | // EnumName 25 | defaultToEnumName: (name: string, interfaceName: string) => name ? `${interfaceName}${name.replace(/^./, (str: string) => str.toUpperCase())}` : 'any', 26 | overrideToEnumName: undefined as IConfigOptions['enumName'] | undefined, 27 | toEnumName(name: string, interfaceName: string) { 28 | return this.overrideToEnumName ? this.overrideToEnumName(name, interfaceName) || this.defaultToEnumName(name, interfaceName) : this.defaultToEnumName(name, interfaceName); 29 | }, 30 | 31 | // OutputFileName 32 | defaultOutputFileName: (modelName: string, isComponent?: boolean, configNested?: boolean) => isComponent ? 33 | modelName.replace('.', path.sep) : 34 | configNested ? modelName.toLowerCase() + path.sep + modelName.toLowerCase() : modelName.toLowerCase(), 35 | overrideOutputFileName: undefined as IConfigOptions['outputFileName'] | undefined, 36 | toOutputFileName(modelName: string, isComponent: boolean | undefined, configNested: boolean | undefined, interfaceName: string, filename: string) { 37 | return this.overrideOutputFileName ? this.overrideOutputFileName(interfaceName, filename) || this.defaultOutputFileName(modelName, isComponent, configNested) : this.defaultOutputFileName(modelName, isComponent, configNested); 38 | }, 39 | 40 | /** 41 | * Convert a Strapi type to a TypeScript type. 42 | * 43 | * @param interfaceName name of current interface 44 | * @param fieldName name of the field 45 | * @param model Strapi type 46 | * @param enumm Use Enum type (or string literal types) 47 | */ 48 | defaultToPropertyType: (interfaceName: string, fieldName: string, model: IStrapiModelAttribute, enumm: boolean) => { 49 | const pt = model.type ? model.type.toLowerCase() : 'any'; 50 | switch (pt) { 51 | case 'text': 52 | case 'richtext': 53 | case 'email': 54 | case 'password': 55 | case 'uid': 56 | case 'time': 57 | return 'string'; 58 | case 'enumeration': 59 | if (enumm) { 60 | return model.enum ? util.toEnumName(fieldName, interfaceName) : 'string'; 61 | } else { 62 | return model.enum ? `"${model.enum.join(`" | "`)}"` : 'string'; 63 | } 64 | case 'date': 65 | case 'datetime': 66 | case 'timestamp': 67 | return 'Date'; 68 | case 'media': 69 | return 'Blob'; 70 | case 'json': 71 | return '{ [key: string]: any }'; 72 | case 'dynamiczone': 73 | return 'any[]' 74 | case 'decimal': 75 | case 'float': 76 | case 'biginteger': 77 | case 'integer': 78 | return 'number'; 79 | case 'string': 80 | case 'number': 81 | case 'boolean': 82 | default: 83 | return pt; 84 | } 85 | }, 86 | overrideToPropertyType: undefined as IConfigOptions['fieldType'] | undefined, 87 | toPropertyType(interfaceName: string, fieldName: string, model: IStrapiModelAttribute, enumm: boolean) { 88 | return this.overrideToPropertyType ? this.overrideToPropertyType(`${model.type}`, fieldName, interfaceName) || this.defaultToPropertyType(interfaceName, fieldName, model, enumm) : this.defaultToPropertyType(interfaceName, fieldName, model, enumm); 89 | }, 90 | 91 | // PropertyName 92 | defaultToPropertyName: (fieldName: string) => fieldName, 93 | overrideToPropertyName: undefined as IConfigOptions['fieldName'] | undefined, 94 | toPropertyName(fieldName: string, interfaceName: string) { 95 | return this.overrideToPropertyName ? this.overrideToPropertyName(fieldName, interfaceName) || this.defaultToPropertyName(fieldName) : this.defaultToPropertyName(fieldName); 96 | }, 97 | 98 | 99 | excludeField: undefined as IConfigOptions['excludeField'] | undefined, 100 | 101 | addField: undefined as IConfigOptions['addField'] | undefined, 102 | } 103 | 104 | const findModel = (structure: IStrapiModelExtended[], name: string): IStrapiModelExtended | undefined => { 105 | return structure.filter((s) => s.modelName === name.toLowerCase()).shift(); 106 | }; 107 | 108 | class Converter { 109 | 110 | strapiModels: IStrapiModelExtended[] = []; 111 | 112 | constructor(strapiModelsParse: IStrapiModel[], private config: IConfigOptions) { 113 | 114 | if (!fs.existsSync(config.output)) fs.mkdirSync(config.output); 115 | 116 | if (config.enumName && typeof config.enumName === 'function') util.overrideToEnumName = config.enumName; 117 | if (config.interfaceName && typeof config.interfaceName === 'function') util.overrideToInterfaceName = config.interfaceName; 118 | if (config.fieldType && typeof config.fieldType === 'function') util.overrideToPropertyType = config.fieldType; 119 | else if (config.type && typeof config.type === 'function') { 120 | console.warn("option 'type' is depreated. use 'fieldType'"); 121 | util.overrideToPropertyType = config.type; 122 | } 123 | if (config.excludeField && typeof config.excludeField === 'function') util.excludeField = config.excludeField; 124 | if (config.addField && typeof config.addField === 'function') util.addField = config.addField; 125 | if (config.fieldName && typeof config.fieldName === 'function') util.overrideToPropertyName = config.fieldName; 126 | if (config.outputFileName && typeof config.outputFileName === 'function') util.overrideOutputFileName = config.outputFileName; 127 | 128 | this.strapiModels = strapiModelsParse.map((m): IStrapiModelExtended => { 129 | 130 | const modelName = m._isComponent ? 131 | path.dirname(m._filename).split(path.sep).pop() + '.' + path.basename(m._filename, '.json') 132 | : path.basename(m._filename, '.settings.json'); 133 | const interfaceName = util.toInterfaceName(m.info.name, m._filename); 134 | const ouputFile = util.toOutputFileName(modelName, m._isComponent, config.nested, interfaceName, m._filename) 135 | return { 136 | ...m, 137 | interfaceName, 138 | modelName: modelName.toLowerCase(), 139 | ouputFile 140 | } 141 | }) 142 | 143 | } 144 | 145 | async run() { 146 | return new Promise((resolve, reject) => { 147 | 148 | // Write index.ts 149 | const outputFile = path.resolve(this.config.output, 'index.ts'); 150 | 151 | const output = this.strapiModels 152 | .map(s => `export * from './${s.ouputFile.replace('\\', '/')}';`) 153 | .sort() 154 | .join('\n'); 155 | fs.writeFileSync(outputFile, output + '\n'); 156 | 157 | // Write each interfaces 158 | let count = this.strapiModels.length; 159 | this.strapiModels.forEach(g => { 160 | const folder = path.resolve(this.config.output, g.ouputFile); 161 | if (!fs.existsSync(path.dirname(folder))) fs.mkdirSync(path.dirname(folder)); 162 | fs.writeFile(`${folder}.ts`, this.strapiModelToInterface(g), { encoding: 'utf8' }, (err) => { 163 | count--; 164 | if (err) reject(err); 165 | if (count === 0) resolve(this.strapiModels.length); 166 | }); 167 | }); 168 | }) 169 | } 170 | 171 | strapiModelToInterface(m: IStrapiModelExtended) { 172 | const result: string[] = []; 173 | 174 | result.push(...this.strapiModelExtractImports(m)); 175 | if (result.length > 0) result.push('') 176 | 177 | result.push('/**'); 178 | result.push(` * Model definition for ${m.info.name}`); 179 | result.push(' */'); 180 | result.push(`export interface ${m.interfaceName} {`); 181 | 182 | result.push(` ${this.strapiModelAttributeToProperty(m.interfaceName, 'id', { 183 | type: 'string', 184 | required: true 185 | })}`); 186 | 187 | if (m.attributes) for (const aName in m.attributes) { 188 | if ((util.excludeField && util.excludeField(m.interfaceName, aName)) || !m.attributes.hasOwnProperty(aName)) continue; 189 | result.push(` ${this.strapiModelAttributeToProperty(m.interfaceName, aName, m.attributes[aName])}`); 190 | } 191 | 192 | if (util.addField) { 193 | let addFields = util.addField(m.interfaceName); 194 | if (addFields && Array.isArray(addFields)) for (let f of addFields) { 195 | result.push(` ${f.name}: ${f.type};`) 196 | } 197 | } 198 | 199 | result.push('}'); 200 | 201 | if (this.config.enum) result.push('', ...this.strapiModelAttributeToEnum(m.interfaceName, m.attributes)); 202 | 203 | return result.join('\n'); 204 | }; 205 | 206 | /** 207 | * Find all required models and import them. 208 | * 209 | * @param m Strapi model to examine 210 | * @param structure Overall output structure 211 | */ 212 | strapiModelExtractImports(m: IStrapiModelExtended) { 213 | const toImportDefinition = (name: string) => { 214 | const found = findModel(this.strapiModels, name); 215 | const toFolder = (f: IStrapiModelExtended) => { 216 | let rel = path.normalize(path.relative(path.dirname(m.ouputFile), path.dirname(f.ouputFile))); 217 | rel = path.normalize(rel + path.sep + path.basename(f.ouputFile)) 218 | if (!rel.startsWith('..')) rel = '.' + path.sep + rel; 219 | return rel.replace('\\', '/').replace('\\', '/'); 220 | } 221 | return found ? `import ${(this.config.importAsType && this.config.importAsType(m.interfaceName) ? 'type ' : '')}{ ${found.interfaceName} } from '${toFolder(found)}';` : ''; 222 | }; 223 | 224 | const imports: string[] = []; 225 | if (m.attributes) for (const aName in m.attributes) { 226 | 227 | if (!m.attributes.hasOwnProperty(aName)) continue; 228 | 229 | const a = m.attributes[aName]; 230 | if ((a.collection || a.model || a.component || '').toLowerCase() === m.modelName) continue; 231 | 232 | const proposedImport = toImportDefinition(a.collection || a.model || a.component || '') 233 | if (proposedImport) imports.push(proposedImport); 234 | 235 | imports.push(...(a.components || []) 236 | .filter(c => c !== m.modelName) 237 | .map(toImportDefinition)); 238 | } 239 | 240 | return imports 241 | .filter((value, index, arr) => arr.indexOf(value) === index) // is unique 242 | .sort() 243 | }; 244 | 245 | /** 246 | * Convert a Strapi Attribute to a TypeScript property. 247 | * 248 | * @param interfaceName name of current interface 249 | * @param name Name of the property 250 | * @param a Attributes of the property 251 | * @param structure Overall output structure 252 | * @param enumm Use Enum type (or string literal types) 253 | */ 254 | strapiModelAttributeToProperty( 255 | interfaceName: string, 256 | name: string, 257 | a: IStrapiModelAttribute 258 | ) { 259 | const findModelName = (n: string) => { 260 | const result = findModel(this.strapiModels, n); 261 | if (!result && n !== '*') console.debug(`type '${n}' unknown on ${interfaceName}[${name}] => fallback to 'any'. Add in the input arguments the folder that contains *.settings.json with info.name === '${n}'`) 262 | return result ? result.interfaceName : 'any'; 263 | }; 264 | const buildDynamicZoneComponent = (n: string) => { 265 | const result = findModel(this.strapiModels, n); 266 | if (!result && n !== '*') console.debug(`type '${n}' unknown on ${interfaceName}[${name}] => fallback to 'any'. Add in the input arguments the folder that contains *.settings.json with info.name === '${n}'`) 267 | return result ? ` | ({ __component: '${result.modelName}' } & ${result.interfaceName})\n` : 'any'; 268 | }; 269 | 270 | const required = !a.required && !(!this.config.collectionCanBeUndefined && (a.collection || a.repeatable)) && a.type !== 'dynamiczone' ? '?' : ''; 271 | const collection = a.collection || a.repeatable ? '[]' : ''; 272 | 273 | let propType = 'unknown'; 274 | if (a.collection) { 275 | propType = findModelName(a.collection); 276 | } else if (a.component) { 277 | propType = findModelName(a.component); 278 | } else if (a.model) { 279 | propType = findModelName(a.model); 280 | } else if (a.type === "dynamiczone") { 281 | propType = `(\n${a.components!.map(buildDynamicZoneComponent).join('')} )[]` 282 | } else if (a.type) { 283 | propType = util.toPropertyType(interfaceName, name, a, this.config.enum) 284 | } 285 | 286 | return `${util.toPropertyName(name, interfaceName)}${required}: ${propType}${collection};`; 287 | }; 288 | 289 | /** 290 | * Convert all Strapi Enum to TypeScript Enumeration. 291 | * 292 | * @param interfaceName name of current interface 293 | * @param a Attributes 294 | */ 295 | strapiModelAttributeToEnum(interfaceName: string, attributes: { [attr: string]: IStrapiModelAttribute }): string[] { 296 | const enums: string[] = [] 297 | for (const aName in attributes) { 298 | if (!attributes.hasOwnProperty(aName)) continue; 299 | if (attributes[aName].type === 'enumeration') { 300 | enums.push(`export enum ${util.toEnumName(aName, interfaceName)} {`); 301 | attributes[aName].enum!.forEach(e => { 302 | enums.push(` ${e} = "${e}",`); 303 | }) 304 | enums.push(`}\n`); 305 | } 306 | } 307 | return enums 308 | } 309 | 310 | } 311 | 312 | /** 313 | * Export a StrapiModel to a TypeScript interface 314 | */ 315 | export const convert = async (strapiModels: IStrapiModel[], config: IConfigOptions) => { 316 | return new Converter(strapiModels, config).run() 317 | } 318 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ 5 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 6 | // "lib": [], /* Specify library files to be included in the compilation. */ 7 | // "allowJs": true, /* Allow javascript files to be compiled. */ 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 10 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 11 | "sourceMap": true, /* Generates corresponding '.map' file. */ 12 | // "outFile": "./", /* Concatenate and emit output to single file. */ 13 | "outDir": "./dist", /* Redirect output structure to the directory. */ 14 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 15 | // "removeComments": true, /* Do not emit comments to output. */ 16 | // "noEmit": true, /* Do not emit outputs. */ 17 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 18 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 19 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 20 | 21 | /* Strict Type-Checking Options */ 22 | "strict": true, /* Enable all strict type-checking options. */ 23 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 24 | // "strictNullChecks": true, /* Enable strict null checks. */ 25 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 26 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 27 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 28 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 29 | 30 | /* Additional Checks */ 31 | "noUnusedLocals": true, /* Report errors on unused locals. */ 32 | "noUnusedParameters": true, /* Report errors on unused parameters. */ 33 | "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 34 | "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 35 | 36 | /* Module Resolution Options */ 37 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 38 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 39 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 40 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 41 | // "typeRoots": [], /* List of folders to include type definitions from. */ 42 | // "types": [], /* Type declaration files to be included in compilation. */ 43 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 44 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 45 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 46 | 47 | /* Source Map Options */ 48 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 49 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ 50 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 51 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 52 | 53 | /* Experimental Options */ 54 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 55 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 56 | }, 57 | "include": [ 58 | "./src/**/*.ts" 59 | ], 60 | "exclude": [ 61 | "./tmp", 62 | "./src/test*" 63 | ] 64 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:recommended"], 4 | "jsRules": {}, 5 | "rules": { 6 | "quotemark": [true, "single"], 7 | "trailing-comma": [ 8 | true, 9 | { 10 | "multiline": { 11 | "objects": "always", 12 | "arrays": "always", 13 | "functions": "never", 14 | "typeLiterals": "ignore" 15 | }, 16 | "esSpecCompliant": true 17 | } 18 | ], 19 | "object-literal-sort-keys": false, 20 | "ordered-imports": false 21 | }, 22 | "rulesDirectory": [] 23 | } 24 | --------------------------------------------------------------------------------