@strapi/typescript-utils 4.3.0-beta.1 → 4.3.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,81 @@
1
+ 'use strict';
2
+
3
+ jest.mock('../../../generators/schemas/imports', () => ({ addImport: jest.fn() }));
4
+
5
+ const consoleWarnMock = jest.spyOn(console, 'warn').mockImplementation();
6
+
7
+ const ts = require('typescript');
8
+
9
+ const { getAttributeType } = require('../../../generators/schemas/attributes');
10
+ const { addImport } = require('../../../generators/schemas/imports');
11
+
12
+ describe('Attributes', () => {
13
+ afterEach(() => {
14
+ jest.resetAllMocks();
15
+ });
16
+
17
+ // TODO
18
+ // describe('Attribute to Property Signature', () => {});
19
+
20
+ // TODO
21
+ // describe('Mappers', () => {});
22
+
23
+ describe('Get Attribute Type', () => {
24
+ test('If the attribute type is not valid then log an error and exit early without importing the type', () => {
25
+ const typeNode = getAttributeType('foo', { type: 'invalid', uid: 'api::foo.foo' });
26
+
27
+ expect(typeNode).toBeNull();
28
+ expect(consoleWarnMock).toHaveBeenCalledWith(
29
+ '"foo" attribute from "undefined" has an invalid type: "invalid"'
30
+ );
31
+ expect(addImport).not.toHaveBeenCalled();
32
+ });
33
+
34
+ test('Return a basic type node without generic type parameter', () => {
35
+ const typeNode = getAttributeType('foo', { type: 'string' });
36
+
37
+ expect(ts.isTypeNode(typeNode)).toBeTruthy();
38
+
39
+ expect(typeNode.kind).toBe(ts.SyntaxKind.TypeReference);
40
+ expect(typeNode.typeName.escapedText).toBe('StringAttribute');
41
+ expect(typeNode.typeArguments).toBeUndefined();
42
+
43
+ expect(consoleWarnMock).not.toHaveBeenCalled();
44
+ expect(addImport).toHaveBeenCalledWith('StringAttribute');
45
+ });
46
+
47
+ describe('Complex types (with generic type parameters)', () => {
48
+ const defaultAssertions = (typeNode, typeName) => {
49
+ expect(ts.isTypeNode(typeNode)).toBeTruthy();
50
+
51
+ expect(typeNode.kind).toBe(ts.SyntaxKind.TypeReference);
52
+ expect(typeNode.typeName.escapedText).toBe(typeName);
53
+
54
+ expect(consoleWarnMock).not.toHaveBeenCalled();
55
+ expect(addImport).toHaveBeenCalledWith(typeName);
56
+ };
57
+
58
+ test('Enumeration', () => {
59
+ const attribute = { type: 'enumeration', enum: ['a', 'b', 'c'] };
60
+ const typeNode = getAttributeType('foo', attribute);
61
+
62
+ defaultAssertions(typeNode, 'EnumerationAttribute');
63
+
64
+ expect(typeNode.typeArguments).toHaveLength(1);
65
+ expect(typeNode.typeArguments[0].kind).toBe(ts.SyntaxKind.TupleType);
66
+
67
+ const tupleElements = typeNode.typeArguments[0].elements;
68
+
69
+ attribute.enum.forEach((value, index) => {
70
+ const element = tupleElements[index];
71
+
72
+ expect(element.kind).toBe(ts.SyntaxKind.StringLiteral);
73
+ expect(element.text).toBe(value);
74
+ });
75
+ });
76
+ });
77
+ });
78
+
79
+ // TODO
80
+ // describe('Get Attribute Modifiers', () => {});
81
+ });
@@ -0,0 +1,54 @@
1
+ 'use strict';
2
+
3
+ const ts = require('typescript');
4
+
5
+ const {
6
+ addImport,
7
+ generateImportDefinition,
8
+ getImports,
9
+ } = require('../../../generators/schemas/imports');
10
+
11
+ describe('Imports', () => {
12
+ test('When first loaded, the list of imports should be empty', () => {
13
+ expect(getImports()).toHaveLength(0);
14
+ });
15
+
16
+ test('Can add new imports to the list', () => {
17
+ addImport('foo');
18
+ addImport('bar');
19
+
20
+ expect(getImports()).toHaveLength(2);
21
+ });
22
+
23
+ test('When adding an already registered import, ignore it', () => {
24
+ addImport('foo');
25
+
26
+ expect(getImports()).toHaveLength(2);
27
+ });
28
+
29
+ test('Generate an import type definition containing the registered import', () => {
30
+ const def = generateImportDefinition();
31
+
32
+ expect(def.kind).toBe(ts.SyntaxKind.ImportDeclaration);
33
+
34
+ // Module specifier
35
+ expect(def.moduleSpecifier.kind).toBe(ts.SyntaxKind.StringLiteral);
36
+ expect(def.moduleSpecifier.text).toBe('@strapi/strapi');
37
+
38
+ // Import clause (should be named imports)
39
+ expect(def.importClause.kind).toBe(ts.SyntaxKind.ImportClause);
40
+
41
+ const { elements } = def.importClause.namedBindings;
42
+
43
+ expect(elements).toHaveLength(2);
44
+
45
+ // Import clauses
46
+ getImports().forEach((namedImport, index) => {
47
+ const element = elements[index];
48
+
49
+ expect(element.kind).toBe(ts.SyntaxKind.ImportSpecifier);
50
+ expect(element.name.kind).toBe(ts.SyntaxKind.Identifier);
51
+ expect(element.name.escapedText).toBe(namedImport);
52
+ });
53
+ });
54
+ });
@@ -0,0 +1,362 @@
1
+ 'use strict';
2
+
3
+ const ts = require('typescript');
4
+ const { factory } = require('typescript');
5
+
6
+ const {
7
+ getAllStrapiSchemas,
8
+ getDefinitionAttributesCount,
9
+ getSchemaExtendsTypeName,
10
+ getSchemaInterfaceName,
11
+ getSchemaModelType,
12
+ getTypeNode,
13
+ toTypeLiteral,
14
+ } = require('../../../generators/schemas/utils');
15
+
16
+ describe('Utils', () => {
17
+ describe('Get All Strapi Schemas', () => {
18
+ test('Get both components and content types', () => {
19
+ const strapi = {
20
+ contentTypes: {
21
+ ctA: {},
22
+ ctB: {},
23
+ },
24
+ components: {
25
+ comp1: {},
26
+ comp2: {},
27
+ comp3: {},
28
+ },
29
+ };
30
+
31
+ const schemas = getAllStrapiSchemas(strapi);
32
+
33
+ expect(schemas).toMatchObject({ ctA: {}, ctB: {}, comp1: {}, comp2: {}, comp3: {} });
34
+ });
35
+
36
+ test('Get only components if there is no content type', () => {
37
+ const strapi = {
38
+ contentTypes: {},
39
+
40
+ components: {
41
+ comp1: {},
42
+ comp2: {},
43
+ comp3: {},
44
+ },
45
+ };
46
+
47
+ const schemas = getAllStrapiSchemas(strapi);
48
+
49
+ expect(schemas).toMatchObject({ comp1: {}, comp2: {}, comp3: {} });
50
+ });
51
+
52
+ test('Get only content types if there is no component', () => {
53
+ const strapi = {
54
+ contentTypes: {
55
+ ctA: {},
56
+ ctB: {},
57
+ },
58
+
59
+ components: {},
60
+ };
61
+
62
+ const schemas = getAllStrapiSchemas(strapi);
63
+
64
+ expect(schemas).toMatchObject({ ctA: {}, ctB: {} });
65
+ });
66
+ });
67
+
68
+ describe('Get Definition Attributes Count', () => {
69
+ const createMainNode = (members = []) => {
70
+ return factory.createInterfaceDeclaration(
71
+ undefined,
72
+ undefined,
73
+ factory.createIdentifier('Foo'),
74
+ undefined,
75
+ undefined,
76
+ members
77
+ );
78
+ };
79
+
80
+ const createPropertyDeclaration = (name, type) => {
81
+ return factory.createPropertyDeclaration(
82
+ undefined,
83
+ undefined,
84
+ factory.createIdentifier(name),
85
+ undefined,
86
+ type
87
+ );
88
+ };
89
+
90
+ test('Returns null if there are no members in the parent node', () => {
91
+ const mainNode = createMainNode();
92
+
93
+ const count = getDefinitionAttributesCount(mainNode);
94
+
95
+ expect(count).toBeNull();
96
+ });
97
+
98
+ test('Returns null if there are members in the parent node, but none named "attributes"', () => {
99
+ const mainNode = createMainNode([
100
+ createPropertyDeclaration(
101
+ 'bar',
102
+ factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)
103
+ ),
104
+ createPropertyDeclaration(
105
+ 'foobar',
106
+ factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword)
107
+ ),
108
+ ]);
109
+
110
+ const count = getDefinitionAttributesCount(mainNode);
111
+
112
+ expect(count).toBeNull();
113
+ });
114
+
115
+ test('Returns the number of attributes if the property is present', () => {
116
+ const mainNode = createMainNode([
117
+ createPropertyDeclaration(
118
+ 'bar',
119
+ factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)
120
+ ),
121
+ createPropertyDeclaration(
122
+ 'attributes',
123
+ factory.createTypeLiteralNode([
124
+ createPropertyDeclaration(
125
+ 'a',
126
+ factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)
127
+ ),
128
+ createPropertyDeclaration(
129
+ 'b',
130
+ factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword)
131
+ ),
132
+ createPropertyDeclaration(
133
+ 'c',
134
+ factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword)
135
+ ),
136
+ ])
137
+ ),
138
+ createPropertyDeclaration(
139
+ 'foobar',
140
+ factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword)
141
+ ),
142
+ ]);
143
+
144
+ const count = getDefinitionAttributesCount(mainNode);
145
+
146
+ expect(count).toBe(3);
147
+ });
148
+
149
+ test("Returns 0 if the attributes node is present but don't have any members", () => {
150
+ const mainNode = createMainNode([
151
+ createPropertyDeclaration('attributes', factory.createTypeLiteralNode()),
152
+ createPropertyDeclaration(
153
+ 'foobar',
154
+ factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword)
155
+ ),
156
+ ]);
157
+
158
+ const count = getDefinitionAttributesCount(mainNode);
159
+
160
+ expect(count).toBe(0);
161
+ });
162
+ });
163
+
164
+ describe('Get Schema Model Type', () => {
165
+ test.each([
166
+ [{ modelType: 'component', kind: null }, 'component'],
167
+ [{ modelType: 'contentType', kind: 'singleType' }, 'singleType'],
168
+ [{ modelType: 'contentType', kind: 'collectionType' }, 'collectionType'],
169
+ [{ modelType: 'invalidType', kind: 'foo' }, null],
170
+ ])('%p to be evaluated to %p', (schema, expected) => {
171
+ expect(getSchemaModelType(schema)).toBe(expected);
172
+ });
173
+ });
174
+
175
+ describe('Get Schema Extends Type Name', () => {
176
+ test.each([
177
+ [{ modelType: 'component', kind: null }, 'ComponentSchema'],
178
+ [{ modelType: 'contentType', kind: 'singleType' }, 'SingleTypeSchema'],
179
+ [{ modelType: 'contentType', kind: 'collectionType' }, 'CollectionTypeSchema'],
180
+ [{ modelType: 'invalidType', kind: 'foo' }, 'Schema'],
181
+ ])("Expect %p to generate %p as the base type for a schema's interface", (schema, expected) => {
182
+ expect(getSchemaExtendsTypeName(schema)).toBe(expected);
183
+ });
184
+ });
185
+
186
+ describe('Get Schema Interface Name', () => {
187
+ test.each([
188
+ ['api::foo.foo', 'ApiFooFoo'],
189
+ ['plugin::bar.foo', 'PluginBarFoo'],
190
+ ['default.dish', 'DefaultDish'],
191
+ ])('Should transform UID (%p) to interface name (%p)', (uid, interfaceName) => {
192
+ expect(getSchemaInterfaceName(uid)).toBe(interfaceName);
193
+ });
194
+ });
195
+
196
+ describe('Get Type Node', () => {
197
+ test('Create a valid type reference node based on the given generic parameters', () => {
198
+ const node = getTypeNode('FooBar', [
199
+ factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
200
+ factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword),
201
+ ]);
202
+
203
+ expect(node.typeArguments).toHaveLength(2);
204
+
205
+ expect(node.typeArguments[0].kind).toBe(ts.SyntaxKind.StringKeyword);
206
+ expect(node.typeArguments[1].kind).toBe(ts.SyntaxKind.NumberKeyword);
207
+ });
208
+
209
+ test('Create a valid empty type reference node', () => {
210
+ const node = getTypeNode('FooBar');
211
+
212
+ expect(node.typeArguments).toBeUndefined();
213
+ });
214
+ });
215
+
216
+ describe('To Type Literal', () => {
217
+ test('String', () => {
218
+ const node = toTypeLiteral('foo');
219
+
220
+ expect(node.kind).toBe(ts.SyntaxKind.StringLiteral);
221
+ expect(node.text).toBe('foo');
222
+ });
223
+
224
+ test('Number', () => {
225
+ const node = toTypeLiteral(42);
226
+
227
+ expect(node.kind).toBe(ts.SyntaxKind.FirstLiteralToken);
228
+ expect(node.text).toBe('42');
229
+ });
230
+
231
+ test('Boolean', () => {
232
+ const trueNode = toTypeLiteral(true);
233
+ const falseNode = toTypeLiteral(false);
234
+
235
+ expect(trueNode.kind).toBe(ts.SyntaxKind.TrueKeyword);
236
+ expect(falseNode.kind).toBe(ts.SyntaxKind.FalseKeyword);
237
+ });
238
+
239
+ test('undefined', () => {
240
+ const node = toTypeLiteral(undefined);
241
+
242
+ expect(node.kind).toBe(ts.SyntaxKind.LiteralType);
243
+ expect(node.literal).toBe(ts.SyntaxKind.UndefinedKeyword);
244
+ });
245
+
246
+ test('null', () => {
247
+ const node = toTypeLiteral(null);
248
+
249
+ expect(node.kind).toBe(ts.SyntaxKind.LiteralType);
250
+ expect(node.literal).toBe(ts.SyntaxKind.NullKeyword);
251
+ });
252
+
253
+ test('Array (empty)', () => {
254
+ const node = toTypeLiteral([]);
255
+
256
+ expect(node.kind).toBe(ts.SyntaxKind.TupleType);
257
+ expect(node.elements).toHaveLength(0);
258
+ });
259
+
260
+ test('Array (with elements)', () => {
261
+ const node = toTypeLiteral(['foo', 2]);
262
+
263
+ expect(node.kind).toBe(ts.SyntaxKind.TupleType);
264
+ expect(node.elements).toHaveLength(2);
265
+
266
+ expect(node.elements[0].kind).toBe(ts.SyntaxKind.StringLiteral);
267
+ expect(node.elements[0].text).toBe('foo');
268
+
269
+ expect(node.elements[1].kind).toBe(ts.SyntaxKind.FirstLiteralToken);
270
+ expect(node.elements[1].text).toBe('2');
271
+ });
272
+
273
+ test('Array (nested)', () => {
274
+ const node = toTypeLiteral(['foo', ['bar', 'foobar']]);
275
+
276
+ expect(node.kind).toBe(ts.SyntaxKind.TupleType);
277
+ expect(node.elements).toHaveLength(2);
278
+
279
+ expect(node.elements[0].kind).toBe(ts.SyntaxKind.StringLiteral);
280
+ expect(node.elements[0].text).toBe('foo');
281
+
282
+ expect(node.elements[1].kind).toBe(ts.SyntaxKind.TupleType);
283
+ expect(node.elements[1].elements).toHaveLength(2);
284
+
285
+ expect(node.elements[1].elements[0].kind).toBe(ts.SyntaxKind.StringLiteral);
286
+ expect(node.elements[1].elements[0].text).toBe('bar');
287
+
288
+ expect(node.elements[1].elements[1].kind).toBe(ts.SyntaxKind.StringLiteral);
289
+ expect(node.elements[1].elements[1].text).toBe('foobar');
290
+ });
291
+
292
+ test('Array (with object)', () => {
293
+ const node = toTypeLiteral([{ foo: 'bar', bar: true }]);
294
+
295
+ expect(node.kind).toBe(ts.SyntaxKind.TupleType);
296
+ expect(node.elements).toHaveLength(1);
297
+
298
+ const objectNode = node.elements[0];
299
+
300
+ expect(objectNode.kind).toBe(ts.SyntaxKind.TypeLiteral);
301
+ expect(objectNode.members).toHaveLength(2);
302
+
303
+ expect(objectNode.members[0].kind).toBe(ts.SyntaxKind.PropertyDeclaration);
304
+ expect(objectNode.members[0].name.escapedText).toBe('foo');
305
+ expect(objectNode.members[0].type.kind).toBe(ts.SyntaxKind.StringLiteral);
306
+ expect(objectNode.members[0].type.text).toBe('bar');
307
+
308
+ expect(objectNode.members[1].kind).toBe(ts.SyntaxKind.PropertyDeclaration);
309
+ expect(objectNode.members[1].name.escapedText).toBe('bar');
310
+ expect(objectNode.members[1].type.kind).toBe(ts.SyntaxKind.TrueKeyword);
311
+ });
312
+
313
+ test('Object', () => {
314
+ const node = toTypeLiteral({ foo: ['bar', true, 2], bar: null });
315
+
316
+ expect(node.kind).toBe(ts.SyntaxKind.TypeLiteral);
317
+ expect(node.members).toHaveLength(2);
318
+
319
+ const [firstMember, secondMember] = node.members;
320
+
321
+ expect(firstMember.kind).toBe(ts.SyntaxKind.PropertyDeclaration);
322
+ expect(firstMember.name.escapedText).toBe('foo');
323
+ expect(firstMember.type.kind).toBe(ts.SyntaxKind.TupleType);
324
+ expect(firstMember.type.elements).toHaveLength(3);
325
+ expect(firstMember.type.elements[0].kind).toBe(ts.SyntaxKind.StringLiteral);
326
+ expect(firstMember.type.elements[1].kind).toBe(ts.SyntaxKind.TrueKeyword);
327
+ expect(firstMember.type.elements[2].kind).toBe(ts.SyntaxKind.FirstLiteralToken);
328
+
329
+ expect(secondMember.kind).toBe(ts.SyntaxKind.PropertyDeclaration);
330
+ expect(secondMember.name.escapedText).toBe('bar');
331
+ expect(secondMember.type.kind).toBe(ts.SyntaxKind.LiteralType);
332
+ expect(secondMember.type.literal).toBe(ts.SyntaxKind.NullKeyword);
333
+ });
334
+
335
+ test('Object with complex keys', () => {
336
+ const node = toTypeLiteral({ 'foo-bar': 'foobar', foo: 'bar' });
337
+
338
+ expect(node.kind).toBe(ts.SyntaxKind.TypeLiteral);
339
+ expect(node.members).toHaveLength(2);
340
+
341
+ const [firstMember, secondMember] = node.members;
342
+
343
+ expect(firstMember.kind).toBe(ts.SyntaxKind.PropertyDeclaration);
344
+ expect(firstMember.name.kind).toBe(ts.SyntaxKind.StringLiteral);
345
+ expect(firstMember.name.text).toBe('foo-bar');
346
+ expect(firstMember.type.kind).toBe(ts.SyntaxKind.StringLiteral);
347
+ expect(firstMember.type.text).toBe('foobar');
348
+
349
+ expect(secondMember.kind).toBe(ts.SyntaxKind.PropertyDeclaration);
350
+ expect(secondMember.name.kind).toBe(ts.SyntaxKind.Identifier);
351
+ expect(secondMember.name.escapedText).toBe('foo');
352
+ expect(secondMember.type.kind).toBe(ts.SyntaxKind.StringLiteral);
353
+ expect(secondMember.type.text).toBe('bar');
354
+ });
355
+
356
+ test('Invalid data type supplied (function)', () => {
357
+ expect(() => toTypeLiteral(() => {})).toThrowError(
358
+ 'Cannot convert to object literal. Unknown type "function"'
359
+ );
360
+ });
361
+ });
362
+ });
@@ -0,0 +1,7 @@
1
+ 'use strict';
2
+
3
+ const generateSchemasDefinitions = require('./schemas');
4
+
5
+ module.exports = {
6
+ generateSchemasDefinitions,
7
+ };
@@ -0,0 +1,285 @@
1
+ 'use strict';
2
+
3
+ const ts = require('typescript');
4
+ const { factory } = require('typescript');
5
+ const _ = require('lodash/fp');
6
+
7
+ const { addImport } = require('./imports');
8
+ const { getTypeNode, toTypeLiteral } = require('./utils');
9
+
10
+ /**
11
+ * Generate a property signature node for a given attribute
12
+ *
13
+ * @param {object} schema
14
+ * @param {string} attributeName
15
+ * @param {object} attribute
16
+ * @returns {object}
17
+ */
18
+ const attributeToPropertySignature = (schema, attributeName, attribute) => {
19
+ const baseType = getAttributeType(attributeName, attribute, schema.uid);
20
+
21
+ if (baseType === null) {
22
+ return null;
23
+ }
24
+
25
+ const modifiers = getAttributeModifiers(attributeName, attribute);
26
+
27
+ const nodes = [baseType, ...modifiers];
28
+
29
+ return factory.createPropertySignature(
30
+ undefined,
31
+ factory.createIdentifier(attributeName),
32
+ undefined,
33
+ factory.createIntersectionTypeNode(nodes)
34
+ );
35
+ };
36
+
37
+ /**
38
+ * Create the base type node for a given attribute
39
+ *
40
+ * @param {string} attributeName
41
+ * @param {object} attribute
42
+ * @param {string} uid
43
+ * @returns {object}
44
+ */
45
+ const getAttributeType = (attributeName, attribute, uid) => {
46
+ if (!Object.keys(mappers).includes(attribute.type)) {
47
+ console.warn(
48
+ `"${attributeName}" attribute from "${uid}" has an invalid type: "${attribute.type}"`
49
+ );
50
+
51
+ return null;
52
+ }
53
+
54
+ const [attributeType, typeParams] = mappers[attribute.type]({ uid, attribute, attributeName });
55
+
56
+ addImport(attributeType);
57
+
58
+ return getTypeNode(attributeType, typeParams);
59
+ };
60
+
61
+ /**
62
+ * Collect every modifier node from an attribute
63
+ *
64
+ * @param {string} _attributeName
65
+ * @param {object} attribute
66
+ * @returns {object[]}
67
+ */
68
+ const getAttributeModifiers = (_attributeName, attribute) => {
69
+ const modifiers = [];
70
+
71
+ // Required
72
+ if (attribute.required) {
73
+ addImport('RequiredAttribute');
74
+
75
+ modifiers.push(factory.createTypeReferenceNode(factory.createIdentifier('RequiredAttribute')));
76
+ }
77
+
78
+ // Private
79
+ if (attribute.private) {
80
+ addImport('PrivateAttribute');
81
+
82
+ modifiers.push(factory.createTypeReferenceNode(factory.createIdentifier('PrivateAttribute')));
83
+ }
84
+
85
+ // Unique
86
+ if (attribute.unique) {
87
+ addImport('UniqueAttribute');
88
+
89
+ modifiers.push(factory.createTypeReferenceNode(factory.createIdentifier('UniqueAttribute')));
90
+ }
91
+
92
+ // Configurable
93
+ if (attribute.configurable) {
94
+ addImport('ConfigurableAttribute');
95
+
96
+ modifiers.push(
97
+ factory.createTypeReferenceNode(factory.createIdentifier('ConfigurableAttribute'))
98
+ );
99
+ }
100
+
101
+ // Plugin Options
102
+ if (!_.isEmpty(attribute.pluginOptions)) {
103
+ addImport('SetPluginOptions');
104
+
105
+ modifiers.push(
106
+ factory.createTypeReferenceNode(
107
+ factory.createIdentifier('SetPluginOptions'),
108
+ // Transform the pluginOptions object into an object literal expression
109
+ [toTypeLiteral(attribute.pluginOptions)]
110
+ )
111
+ );
112
+ }
113
+
114
+ // Min / Max
115
+ if (!_.isNil(attribute.min) || !_.isNil(attribute.max)) {
116
+ addImport('SetMinMax');
117
+
118
+ const minMaxProperties = _.pick(['min', 'max'], attribute);
119
+
120
+ modifiers.push(
121
+ factory.createTypeReferenceNode(factory.createIdentifier('SetMinMax'), [
122
+ toTypeLiteral(minMaxProperties),
123
+ ])
124
+ );
125
+ }
126
+
127
+ // Min length / Max length
128
+ if (!_.isNil(attribute.minLength) || !_.isNil(attribute.maxLength)) {
129
+ addImport('SetMinMaxLength');
130
+
131
+ const minMaxProperties = _.pick(['minLength', 'maxLength'], attribute);
132
+
133
+ modifiers.push(
134
+ factory.createTypeReferenceNode(factory.createIdentifier('SetMinMaxLength'), [
135
+ toTypeLiteral(minMaxProperties),
136
+ ])
137
+ );
138
+ }
139
+
140
+ // Default
141
+ if (!_.isNil(attribute.default)) {
142
+ addImport('DefaultTo');
143
+
144
+ const defaultLiteral = toTypeLiteral(attribute.default);
145
+
146
+ modifiers.push(
147
+ factory.createTypeReferenceNode(factory.createIdentifier('DefaultTo'), [defaultLiteral])
148
+ );
149
+ }
150
+
151
+ return modifiers;
152
+ };
153
+
154
+ const mappers = {
155
+ string() {
156
+ return ['StringAttribute'];
157
+ },
158
+ text() {
159
+ return ['TextAttribute'];
160
+ },
161
+ richtext() {
162
+ return ['RichTextAttribute'];
163
+ },
164
+ password() {
165
+ return ['PasswordAttribute'];
166
+ },
167
+ email() {
168
+ return ['EmailAttribute'];
169
+ },
170
+ date() {
171
+ return ['DateAttribute'];
172
+ },
173
+ time() {
174
+ return ['TimeAttribute'];
175
+ },
176
+ datetime() {
177
+ return ['DateTimeAttribute'];
178
+ },
179
+ timestamp() {
180
+ return ['TimestampAttribute'];
181
+ },
182
+ integer() {
183
+ return ['IntegerAttribute'];
184
+ },
185
+ biginteger() {
186
+ return ['BigIntegerAttribute'];
187
+ },
188
+ float() {
189
+ return ['FloatAttribute'];
190
+ },
191
+ decimal() {
192
+ return ['DecimalAttribute'];
193
+ },
194
+ uid({ attribute, uid }) {
195
+ const { targetField, options } = attribute;
196
+
197
+ // If there are no params to compute, then return the attribute type alone
198
+ if (targetField === undefined && options === undefined) {
199
+ return ['UIDAttribute'];
200
+ }
201
+
202
+ const params = [];
203
+
204
+ // If the targetField property is defined, then reference it,
205
+ // otherwise, put `undefined` keyword type nodes as placeholders
206
+ const targetFieldParams = _.isUndefined(targetField)
207
+ ? [
208
+ factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword),
209
+ factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword),
210
+ ]
211
+ : [factory.createStringLiteral(uid), factory.createStringLiteral(targetField)];
212
+
213
+ params.push(...targetFieldParams);
214
+
215
+ // If the options property is defined, transform it to
216
+ // a type literral node and add it to the params list
217
+ if (_.isObject(options)) {
218
+ params.push(toTypeLiteral(options));
219
+ }
220
+
221
+ return ['UIDAttribute', params];
222
+ },
223
+ enumeration({ attribute }) {
224
+ const { enum: enumValues } = attribute;
225
+
226
+ if (Array.isArray(enumValues)) {
227
+ return ['EnumerationAttribute', [toTypeLiteral(enumValues)]];
228
+ }
229
+
230
+ return ['EnumerationAttribute'];
231
+ },
232
+ boolean() {
233
+ return ['BooleanAttribute'];
234
+ },
235
+ json() {
236
+ return ['JSONAttribute'];
237
+ },
238
+ media() {
239
+ return ['MediaAttribute'];
240
+ },
241
+ relation({ uid, attribute }) {
242
+ const { relation, target } = attribute;
243
+
244
+ const isMorphRelation = relation.toLowerCase().includes('morph');
245
+
246
+ if (isMorphRelation) {
247
+ return [
248
+ 'RelationAttribute',
249
+ [factory.createStringLiteral(uid, true), factory.createStringLiteral(relation, true)],
250
+ ];
251
+ }
252
+
253
+ return [
254
+ 'RelationAttribute',
255
+ [
256
+ factory.createStringLiteral(uid, true),
257
+ factory.createStringLiteral(relation, true),
258
+ factory.createStringLiteral(target, true),
259
+ ],
260
+ ];
261
+ },
262
+ component({ attribute }) {
263
+ const target = attribute.component;
264
+ const params = [factory.createStringLiteral(target, true)];
265
+
266
+ if (attribute.repeatable) {
267
+ params.push(factory.createTrue());
268
+ }
269
+
270
+ return ['ComponentAttribute', params];
271
+ },
272
+ dynamiczone({ attribute }) {
273
+ const componentsParam = factory.createTupleTypeNode(
274
+ attribute.components.map(component => factory.createStringLiteral(component))
275
+ );
276
+
277
+ return ['DynamicZoneAttribute', [componentsParam]];
278
+ },
279
+ };
280
+
281
+ module.exports = attributeToPropertySignature;
282
+
283
+ module.exports.mappers = mappers;
284
+ module.exports.getAttributeType = getAttributeType;
285
+ module.exports.getAttributeModifiers = getAttributeModifiers;
@@ -0,0 +1,68 @@
1
+ 'use strict';
2
+
3
+ const ts = require('typescript');
4
+ const { factory } = require('typescript');
5
+
6
+ const { getSchemaInterfaceName } = require('./utils');
7
+
8
+ /**
9
+ * Generate the global module augmentation block
10
+ *
11
+ * @param {Array<{ schema: object; definition: ts.TypeNode }>} schemasDefinitions
12
+ * @returns {ts.ModuleDeclaration}
13
+ */
14
+ const generateGlobalDefinition = (schemasDefinitions = []) => {
15
+ const properties = schemasDefinitions.map(schemaDefinitionToPropertySignature);
16
+
17
+ return factory.createModuleDeclaration(
18
+ undefined,
19
+ [factory.createModifier(ts.SyntaxKind.DeclareKeyword)],
20
+ factory.createIdentifier('global'),
21
+ factory.createModuleBlock([
22
+ factory.createModuleDeclaration(
23
+ undefined,
24
+ undefined,
25
+ factory.createIdentifier('Strapi'),
26
+ factory.createModuleBlock([
27
+ factory.createInterfaceDeclaration(
28
+ undefined,
29
+ undefined,
30
+ factory.createIdentifier('Schemas'),
31
+ undefined,
32
+ undefined,
33
+ properties
34
+ ),
35
+ ]),
36
+ ts.NodeFlags.Namespace |
37
+ ts.NodeFlags.ExportContext |
38
+ ts.NodeFlags.Ambient |
39
+ ts.NodeFlags.ContextFlags
40
+ ),
41
+ ]),
42
+ ts.NodeFlags.ExportContext |
43
+ ts.NodeFlags.GlobalAugmentation |
44
+ ts.NodeFlags.Ambient |
45
+ ts.NodeFlags.ContextFlags
46
+ );
47
+ };
48
+
49
+ /**
50
+ *
51
+ * @param {object} schemaDefinition
52
+ * @param {ts.InterfaceDeclaration} schemaDefinition.definition
53
+ * @param {object} schemaDefinition.schema
54
+ */
55
+ const schemaDefinitionToPropertySignature = ({ schema }) => {
56
+ const { uid } = schema;
57
+
58
+ const interfaceTypeName = getSchemaInterfaceName(uid);
59
+
60
+ return factory.createPropertySignature(
61
+ undefined,
62
+ factory.createStringLiteral(uid, true),
63
+ undefined,
64
+ factory.createTypeReferenceNode(factory.createIdentifier(interfaceTypeName))
65
+ );
66
+ };
67
+
68
+ module.exports = { generateGlobalDefinition };
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ const { factory } = require('typescript');
4
+
5
+ const imports = [];
6
+
7
+ module.exports = {
8
+ getImports() {
9
+ return imports;
10
+ },
11
+
12
+ addImport(type) {
13
+ const hasType = imports.includes(type);
14
+
15
+ if (!hasType) {
16
+ imports.push(type);
17
+ }
18
+ },
19
+
20
+ generateImportDefinition() {
21
+ const formattedImports = imports.map(key =>
22
+ factory.createImportSpecifier(false, undefined, factory.createIdentifier(key))
23
+ );
24
+
25
+ return factory.createImportDeclaration(
26
+ undefined,
27
+ undefined,
28
+ factory.createImportClause(false, undefined, factory.createNamedImports(formattedImports)),
29
+ factory.createStringLiteral('@strapi/strapi'),
30
+ undefined
31
+ );
32
+ },
33
+ };
@@ -0,0 +1,177 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+
5
+ const ts = require('typescript');
6
+ const { factory } = require('typescript');
7
+
8
+ const fp = require('lodash/fp');
9
+ const fse = require('fs-extra');
10
+ const prettier = require('prettier');
11
+ const chalk = require('chalk');
12
+ const CLITable = require('cli-table3');
13
+
14
+ const { generateImportDefinition } = require('./imports');
15
+ const { generateSchemaDefinition } = require('./schema');
16
+ const { generateGlobalDefinition } = require('./global');
17
+ const {
18
+ getAllStrapiSchemas,
19
+ getSchemaInterfaceName,
20
+ getSchemaModelType,
21
+ getDefinitionAttributesCount,
22
+ } = require('./utils');
23
+
24
+ const DEFAULT_OUT_FILENAME = 'schemas.d.ts';
25
+
26
+ /**
27
+ * Generate type definitions for Strapi schemas
28
+ *
29
+ * @param {object} options
30
+ * @param {Strapi} options.strapi
31
+ * @param {{ distDir: string; appDir: string; }} options.dirs
32
+ * @param {string} [options.outDir]
33
+ * @param {string} [options.file]
34
+ * @param {boolean} [options.verbose]
35
+ */
36
+ const generateSchemasDefinitions = async (options = {}) => {
37
+ const { strapi, outDir = process.cwd(), file = DEFAULT_OUT_FILENAME, verbose = false } = options;
38
+
39
+ const schemas = getAllStrapiSchemas(strapi);
40
+
41
+ const schemasDefinitions = Object.values(schemas).map(schema => ({
42
+ schema,
43
+ definition: generateSchemaDefinition(schema),
44
+ }));
45
+
46
+ const formattedSchemasDefinitions = schemasDefinitions.reduce((acc, def) => {
47
+ acc.push(
48
+ // Definition
49
+ def.definition,
50
+
51
+ // Add a newline between each interface declaration
52
+ factory.createIdentifier('\n')
53
+ );
54
+
55
+ return acc;
56
+ }, []);
57
+
58
+ const allDefinitions = [
59
+ // Imports
60
+ generateImportDefinition(),
61
+
62
+ // Add a newline after the import statement
63
+ factory.createIdentifier('\n'),
64
+
65
+ // Schemas
66
+ ...formattedSchemasDefinitions,
67
+
68
+ // Global
69
+ generateGlobalDefinition(schemasDefinitions),
70
+ ];
71
+
72
+ const output = emitDefinitions(allDefinitions);
73
+ const formattedOutput = await format(output);
74
+
75
+ const definitionFilepath = await saveDefinitionToFileSystem(outDir, file, formattedOutput);
76
+
77
+ if (verbose) {
78
+ logDebugInformation(schemasDefinitions, { filepath: definitionFilepath });
79
+ }
80
+ };
81
+
82
+ const emitDefinitions = definitions => {
83
+ const nodeArray = factory.createNodeArray(definitions);
84
+
85
+ const sourceFile = ts.createSourceFile(
86
+ 'placeholder.ts',
87
+ '',
88
+ ts.ScriptTarget.ESNext,
89
+ true,
90
+ ts.ScriptKind.TS
91
+ );
92
+
93
+ const printer = ts.createPrinter({ newLine: true, omitTrailingSemicolon: true });
94
+
95
+ return printer.printList(ts.ListFormat.MultiLine, nodeArray, sourceFile);
96
+ };
97
+
98
+ const saveDefinitionToFileSystem = async (dir, file, content) => {
99
+ const filepath = path.join(dir, file);
100
+
101
+ await fse.writeFile(filepath, content);
102
+
103
+ return filepath;
104
+ };
105
+
106
+ /**
107
+ * Format the given definitions.
108
+ * Uses the existing config if one is defined in the project.
109
+ *
110
+ * @param {string} content
111
+ * @returns {string}
112
+ */
113
+ const format = async content => {
114
+ const configFile = await prettier.resolveConfigFile();
115
+ const config = configFile
116
+ ? await prettier.resolveConfig(configFile)
117
+ : // Default config
118
+ {
119
+ singleQuote: true,
120
+ useTabs: false,
121
+ tabWidth: 2,
122
+ };
123
+
124
+ Object.assign(config, { parser: 'typescript' });
125
+
126
+ return prettier.format(content, config);
127
+ };
128
+
129
+ const logDebugInformation = (definitions, options = {}) => {
130
+ const { filepath } = options;
131
+
132
+ const table = new CLITable({
133
+ head: [
134
+ chalk.bold(chalk.green('Model Type')),
135
+ chalk.bold(chalk.blue('UID')),
136
+ chalk.bold(chalk.blue('Type')),
137
+ chalk.bold(chalk.gray('Attributes Count')),
138
+ ],
139
+ colAligns: ['center', 'left', 'left', 'center'],
140
+ });
141
+
142
+ const sortedDefinitions = definitions.map(def => ({
143
+ ...def,
144
+ attributesCount: getDefinitionAttributesCount(def.definition),
145
+ }));
146
+
147
+ for (const { schema, attributesCount } of sortedDefinitions) {
148
+ const modelType = fp.upperFirst(getSchemaModelType(schema));
149
+ const interfaceType = getSchemaInterfaceName(schema.uid);
150
+
151
+ table.push([
152
+ chalk.greenBright(modelType),
153
+ chalk.blue(schema.uid),
154
+ chalk.blue(interfaceType),
155
+ chalk.grey(fp.isNil(attributesCount) ? 'N/A' : attributesCount),
156
+ ]);
157
+ }
158
+
159
+ // Table
160
+ console.log(table.toString());
161
+
162
+ // Metrics
163
+ console.log(
164
+ chalk.greenBright(
165
+ `Generated ${definitions.length} type definition for your Strapi application's schemas.`
166
+ )
167
+ );
168
+
169
+ // Filepath
170
+ const relativePath = path.relative(process.cwd(), filepath);
171
+
172
+ console.log(
173
+ chalk.grey(`The definitions file has been generated here: ${chalk.bold(relativePath)}`)
174
+ );
175
+ };
176
+
177
+ module.exports = generateSchemasDefinitions;
@@ -0,0 +1,87 @@
1
+ 'use strict';
2
+
3
+ const ts = require('typescript');
4
+ const { factory } = require('typescript');
5
+ const { isEmpty } = require('lodash/fp');
6
+
7
+ const { getSchemaExtendsTypeName, getSchemaInterfaceName, toTypeLiteral } = require('./utils');
8
+ const attributeToPropertySignature = require('./attributes');
9
+ const { addImport } = require('./imports');
10
+
11
+ /**
12
+ * Generate an interface declaration for a given schema
13
+ *
14
+ * @param {object} schema
15
+ * @returns {ts.InterfaceDeclaration}
16
+ */
17
+ const generateSchemaDefinition = schema => {
18
+ const { uid } = schema;
19
+
20
+ // Resolve the different interface names needed to declare the schema's interface
21
+ const interfaceName = getSchemaInterfaceName(uid);
22
+ const parentType = getSchemaExtendsTypeName(schema);
23
+
24
+ // Make sure the extended interface are imported
25
+ addImport(parentType);
26
+
27
+ // Properties whose values can be mapped to a literal type expression
28
+ const literalPropertiesDefinitions = ['info', 'options', 'pluginOptions']
29
+ // Ignore non-existent or empty declarations
30
+ .filter(key => !isEmpty(schema[key]))
31
+ // Generate literal definition for each property
32
+ .map(generatePropertyLiteralDefinitionFactory(schema));
33
+
34
+ // Generate the `attributes` literal type definition
35
+ const attributesProp = generateAttributePropertySignature(schema);
36
+
37
+ // Merge every schema's definition in a single list
38
+ const schemaProperties = [...literalPropertiesDefinitions, attributesProp];
39
+
40
+ // Generate the schema's interface declaration
41
+ const schemaType = factory.createInterfaceDeclaration(
42
+ undefined,
43
+ [factory.createModifier(ts.SyntaxKind.ExportKeyword)],
44
+ factory.createIdentifier(interfaceName),
45
+ undefined,
46
+ [
47
+ factory.createHeritageClause(ts.SyntaxKind.ExtendsKeyword, [
48
+ factory.createIdentifier(parentType),
49
+ ]),
50
+ ],
51
+ schemaProperties
52
+ );
53
+
54
+ return schemaType;
55
+ };
56
+
57
+ /**
58
+ * Generate a property signature for the schema's `attributes` field
59
+ *
60
+ * @param {object} schema
61
+ * @returns {ts.PropertySignature}
62
+ */
63
+ const generateAttributePropertySignature = schema => {
64
+ const { attributes } = schema;
65
+
66
+ const properties = Object.entries(attributes).map(([attributeName, attribute]) => {
67
+ return attributeToPropertySignature(schema, attributeName, attribute);
68
+ });
69
+
70
+ return factory.createPropertySignature(
71
+ undefined,
72
+ factory.createIdentifier('attributes'),
73
+ undefined,
74
+ factory.createTypeLiteralNode(properties)
75
+ );
76
+ };
77
+
78
+ const generatePropertyLiteralDefinitionFactory = schema => key => {
79
+ return factory.createPropertySignature(
80
+ undefined,
81
+ factory.createIdentifier(key),
82
+ undefined,
83
+ toTypeLiteral(schema[key])
84
+ );
85
+ };
86
+
87
+ module.exports = { generateSchemaDefinition };
@@ -0,0 +1,155 @@
1
+ 'use strict';
2
+
3
+ const ts = require('typescript');
4
+ const { factory } = require('typescript');
5
+ const {
6
+ pipe,
7
+ replace,
8
+ camelCase,
9
+ upperFirst,
10
+ isUndefined,
11
+ isNull,
12
+ isString,
13
+ isNumber,
14
+ isArray,
15
+ isBoolean,
16
+ propEq,
17
+ } = require('lodash/fp');
18
+
19
+ /**
20
+ * Get all components and content-types in a Strapi application
21
+ *
22
+ * @param {Strapi} strapi
23
+ * @returns {object}
24
+ */
25
+ const getAllStrapiSchemas = strapi => ({ ...strapi.contentTypes, ...strapi.components });
26
+
27
+ /**
28
+ * Extract a valid interface name from a schema uid
29
+ *
30
+ * @param {string} uid
31
+ * @returns {string}
32
+ */
33
+ const getSchemaInterfaceName = pipe(replace(/(:.)/, ' '), camelCase, upperFirst);
34
+
35
+ /**
36
+ * Get the parent type name to extend based on the schema's nature
37
+ *
38
+ * @param {object} schema
39
+ * @returns {string}
40
+ */
41
+ const getSchemaExtendsTypeName = schema => {
42
+ const base = getSchemaModelType(schema);
43
+
44
+ return upperFirst(base) + 'Schema';
45
+ };
46
+
47
+ const getSchemaModelType = schema => {
48
+ const { modelType, kind } = schema;
49
+
50
+ // Components
51
+ if (modelType === 'component') {
52
+ return 'component';
53
+ }
54
+
55
+ // Content-Types
56
+ else if (modelType === 'contentType') {
57
+ return kind;
58
+ }
59
+
60
+ return null;
61
+ };
62
+
63
+ /**
64
+ * Get a type node based on a type and its params
65
+ *
66
+ * @param {string} typeName
67
+ * @param {ts.TypeNode[]} [params]
68
+ * @returns
69
+ */
70
+ const getTypeNode = (typeName, params = []) => {
71
+ return factory.createTypeReferenceNode(factory.createIdentifier(typeName), params);
72
+ };
73
+
74
+ /**
75
+ * Transform a regular JavaScript object or scalar value into a literal expression
76
+ * @param data
77
+ * @returns {ts.TypeNode}
78
+ */
79
+ const toTypeLiteral = data => {
80
+ if (isUndefined(data)) {
81
+ return factory.createLiteralTypeNode(ts.SyntaxKind.UndefinedKeyword);
82
+ }
83
+
84
+ if (isNull(data)) {
85
+ return factory.createLiteralTypeNode(ts.SyntaxKind.NullKeyword);
86
+ }
87
+
88
+ if (isString(data)) {
89
+ return factory.createStringLiteral(data, true);
90
+ }
91
+
92
+ if (isNumber(data)) {
93
+ return factory.createNumericLiteral(data);
94
+ }
95
+
96
+ if (isBoolean(data)) {
97
+ return data ? factory.createTrue() : factory.createFalse();
98
+ }
99
+
100
+ if (isArray(data)) {
101
+ return factory.createTupleTypeNode(data.map(item => toTypeLiteral(item)));
102
+ }
103
+
104
+ if (typeof data !== 'object') {
105
+ throw new Error(`Cannot convert to object literal. Unknown type "${typeof data}"`);
106
+ }
107
+
108
+ const entries = Object.entries(data);
109
+
110
+ const props = entries.reduce((acc, [key, value]) => {
111
+ // Handle keys such as content-type-builder & co.
112
+ const identifier = key.includes('-')
113
+ ? factory.createStringLiteral(key, true)
114
+ : factory.createIdentifier(key);
115
+
116
+ return [
117
+ ...acc,
118
+ factory.createPropertyDeclaration(
119
+ undefined,
120
+ undefined,
121
+ identifier,
122
+ undefined,
123
+ toTypeLiteral(value)
124
+ ),
125
+ ];
126
+ }, []);
127
+
128
+ return factory.createTypeLiteralNode(props);
129
+ };
130
+
131
+ /**
132
+ * Get the number of attributes generated for a given schema definition
133
+ *
134
+ * @param {ts.TypeNode} definition
135
+ * @returns {number | null}
136
+ */
137
+ const getDefinitionAttributesCount = definition => {
138
+ const attributesNode = definition.members.find(propEq('name.escapedText', 'attributes'));
139
+
140
+ if (!attributesNode) {
141
+ return null;
142
+ }
143
+
144
+ return attributesNode.type.members.length;
145
+ };
146
+
147
+ module.exports = {
148
+ getAllStrapiSchemas,
149
+ getSchemaInterfaceName,
150
+ getSchemaExtendsTypeName,
151
+ getSchemaModelType,
152
+ getDefinitionAttributesCount,
153
+ getTypeNode,
154
+ toTypeLiteral,
155
+ };
package/lib/index.js CHANGED
@@ -4,11 +4,13 @@ const compile = require('./compile');
4
4
  const compilers = require('./compilers');
5
5
  const admin = require('./admin');
6
6
  const utils = require('./utils');
7
+ const generators = require('./generators');
7
8
 
8
9
  module.exports = {
9
10
  compile,
10
11
  compilers,
11
12
  admin,
13
+ generators,
12
14
 
13
15
  ...utils,
14
16
  };
@@ -8,7 +8,7 @@ const DEFAULT_TS_CONFIG_FILENAME = 'tsconfig.json';
8
8
  * Gets the outDir value from config file (tsconfig)
9
9
  * @param {string} dir
10
10
  * @param {string | undefined} configFilename
11
- * @returns {string | undefined}
11
+ * @returns {Promise<string | undefined>}
12
12
  */
13
13
  module.exports = async (dir, configFilename = DEFAULT_TS_CONFIG_FILENAME) => {
14
14
  return (await isUsingTypescript(dir))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strapi/typescript-utils",
3
- "version": "4.3.0-beta.1",
3
+ "version": "4.3.0-beta.2",
4
4
  "description": "Typescript support for Strapi",
5
5
  "keywords": [
6
6
  "strapi",
@@ -24,13 +24,16 @@
24
24
  "lib": "./lib"
25
25
  },
26
26
  "dependencies": {
27
+ "chalk": "4.1.2",
28
+ "cli-table3": "0.6.2",
27
29
  "fs-extra": "10.0.1",
28
30
  "lodash": "4.17.21",
31
+ "prettier": "2.7.1",
29
32
  "typescript": "4.6.2"
30
33
  },
31
34
  "engines": {
32
35
  "node": ">=12.22.0 <=16.x.x",
33
36
  "npm": ">=6.0.0"
34
37
  },
35
- "gitHead": "9d6555398960c39159d66bb4eea3bcb0362e37e3"
38
+ "gitHead": "42aba356ad1b0751584d3b375e83baa4a2c18f65"
36
39
  }