├── .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 |
5 |
6 |
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 |
--------------------------------------------------------------------------------