@strapi/typescript-utils 0.0.0-d0fee44d6f → 0.0.0-d36609ff26fb4e00a41a7e2b657f16c7466203fe
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.
- package/lib/__tests__/generators/schemas/attributes.test.js +57 -2
- package/lib/__tests__/generators/schemas/global.test.js +1 -1
- package/lib/admin/create-tsconfig-file.js +1 -1
- package/lib/compilers/watch.js +4 -4
- package/lib/generators/schemas/attributes.js +39 -146
- package/lib/generators/schemas/global.js +21 -19
- package/lib/generators/schemas/imports.js +1 -1
- package/lib/generators/schemas/index.js +63 -63
- package/lib/generators/schemas/mappers.js +131 -0
- package/lib/generators/schemas/schema.js +32 -32
- package/lib/generators/schemas/utils.js +18 -18
- package/lib/utils/get-config-path.js +3 -1
- package/lib/utils/report-diagnostics.js +1 -1
- package/lib/utils/resolve-config-options.js +1 -1
- package/lib/utils/resolve-outdir.js +1 -0
- package/package.json +3 -3
|
@@ -23,11 +23,11 @@ describe('Attributes', () => {
|
|
|
23
23
|
const schema = { uid: 'api::foo.foo' };
|
|
24
24
|
const attributeName = 'foo';
|
|
25
25
|
|
|
26
|
-
const toPropertySignature = attribute => {
|
|
26
|
+
const toPropertySignature = (attribute) => {
|
|
27
27
|
return attributeToPropertySignature(schema, attributeName, attribute);
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
-
const defaultAssertion = node => {
|
|
30
|
+
const defaultAssertion = (node) => {
|
|
31
31
|
expect(node.kind).toBe(ts.SyntaxKind.PropertySignature);
|
|
32
32
|
expect(node.name.escapedText).toBe(attributeName);
|
|
33
33
|
expect(node.type.kind).toBe(ts.SyntaxKind.IntersectionType);
|
|
@@ -438,6 +438,61 @@ describe('Attributes', () => {
|
|
|
438
438
|
});
|
|
439
439
|
});
|
|
440
440
|
|
|
441
|
+
describe('Custom field', () => {
|
|
442
|
+
test('No custom field', () => {
|
|
443
|
+
const attribute = {};
|
|
444
|
+
const modifiers = getAttributeModifiers(attribute);
|
|
445
|
+
|
|
446
|
+
expect(modifiers).toHaveLength(0);
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
test('Basic custom field', () => {
|
|
450
|
+
const attribute = {
|
|
451
|
+
type: 'string',
|
|
452
|
+
customField: 'plugin::color-picker.color',
|
|
453
|
+
};
|
|
454
|
+
const modifiers = getAttributeModifiers(attribute);
|
|
455
|
+
|
|
456
|
+
expect(modifiers).toHaveLength(1);
|
|
457
|
+
expect(modifiers[0].kind).toBe(ts.SyntaxKind.TypeReference);
|
|
458
|
+
expect(modifiers[0].typeName.escapedText).toBe('CustomField');
|
|
459
|
+
expect(modifiers[0].typeArguments).toHaveLength(1);
|
|
460
|
+
expect(modifiers[0].typeArguments[0].kind).toBe(ts.SyntaxKind.StringLiteral);
|
|
461
|
+
expect(modifiers[0].typeArguments[0].text).toBe('plugin::color-picker.color');
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
test('Advanced custom field', () => {
|
|
465
|
+
const attribute = {
|
|
466
|
+
type: 'string',
|
|
467
|
+
customField: 'plugin::color-picker.color',
|
|
468
|
+
options: {
|
|
469
|
+
format: 'hex',
|
|
470
|
+
},
|
|
471
|
+
};
|
|
472
|
+
const modifiers = getAttributeModifiers(attribute);
|
|
473
|
+
|
|
474
|
+
expect(modifiers).toHaveLength(1);
|
|
475
|
+
expect(modifiers[0].kind).toBe(ts.SyntaxKind.TypeReference);
|
|
476
|
+
expect(modifiers[0].typeName.escapedText).toBe('CustomField');
|
|
477
|
+
expect(modifiers[0].typeArguments).toHaveLength(2);
|
|
478
|
+
expect(modifiers[0].typeArguments[0].kind).toBe(ts.SyntaxKind.StringLiteral);
|
|
479
|
+
expect(modifiers[0].typeArguments[0].text).toBe('plugin::color-picker.color');
|
|
480
|
+
expect(modifiers[0].typeArguments[1].kind).toBe(ts.SyntaxKind.TypeLiteral);
|
|
481
|
+
expect(modifiers[0].typeArguments[1].members).toHaveLength(1);
|
|
482
|
+
expect(modifiers[0].typeArguments[1].members[0].kind).toBe(
|
|
483
|
+
ts.SyntaxKind.PropertyDeclaration
|
|
484
|
+
);
|
|
485
|
+
expect(modifiers[0].typeArguments[1].members[0].name.escapedText).toBe('format');
|
|
486
|
+
expect(modifiers[0].typeArguments[1].members[0].kind).toBe(
|
|
487
|
+
ts.SyntaxKind.PropertyDeclaration
|
|
488
|
+
);
|
|
489
|
+
expect(modifiers[0].typeArguments[1].members[0].type.kind).toBe(
|
|
490
|
+
ts.SyntaxKind.StringLiteral
|
|
491
|
+
);
|
|
492
|
+
expect(modifiers[0].typeArguments[1].members[0].type.text).toBe('hex');
|
|
493
|
+
});
|
|
494
|
+
});
|
|
495
|
+
|
|
441
496
|
describe('Plugin Options', () => {
|
|
442
497
|
test('No plugin options', () => {
|
|
443
498
|
const attribute = {};
|
|
@@ -17,7 +17,7 @@ describe('Global', () => {
|
|
|
17
17
|
jest.resetAllMocks();
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
-
const assertGlobalNodeStructure = node => {
|
|
20
|
+
const assertGlobalNodeStructure = (node) => {
|
|
21
21
|
// "declare global"
|
|
22
22
|
expect(node.kind).toBe(ts.SyntaxKind.ModuleDeclaration);
|
|
23
23
|
expect(node.modifiers).toHaveLength(1);
|
package/lib/compilers/watch.js
CHANGED
|
@@ -10,7 +10,7 @@ const resolveConfigOptions = require('../utils/resolve-config-options');
|
|
|
10
10
|
* Prints a diagnostic every time the watch status changes.
|
|
11
11
|
* This is mainly for messages like "Starting compilation" or "Compilation completed".
|
|
12
12
|
*/
|
|
13
|
-
const reportWatchStatusChanged = diagnostic => {
|
|
13
|
+
const reportWatchStatusChanged = (diagnostic) => {
|
|
14
14
|
console.info(ts.formatDiagnostic(diagnostic, formatHost));
|
|
15
15
|
};
|
|
16
16
|
|
|
@@ -18,9 +18,9 @@ module.exports = {
|
|
|
18
18
|
run(configPath) {
|
|
19
19
|
const createProgram = ts.createSemanticDiagnosticsBuilderProgram;
|
|
20
20
|
|
|
21
|
-
const { fileNames, options, projectReferences, watchOptions } =
|
|
22
|
-
configPath
|
|
23
|
-
|
|
21
|
+
const { fileNames, options, projectReferences, watchOptions } =
|
|
22
|
+
resolveConfigOptions(configPath);
|
|
23
|
+
|
|
24
24
|
const host = ts.createWatchCompilerHost(
|
|
25
25
|
fileNames,
|
|
26
26
|
options,
|
|
@@ -1,38 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const ts = require('typescript');
|
|
4
3
|
const { factory } = require('typescript');
|
|
5
4
|
const _ = require('lodash/fp');
|
|
6
5
|
|
|
7
6
|
const { addImport } = require('./imports');
|
|
8
7
|
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(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
|
-
};
|
|
8
|
+
const mappers = require('./mappers');
|
|
36
9
|
|
|
37
10
|
/**
|
|
38
11
|
* Create the base type node for a given attribute
|
|
@@ -64,7 +37,7 @@ const getAttributeType = (attributeName, attribute, uid) => {
|
|
|
64
37
|
* @param {object} attribute
|
|
65
38
|
* @returns {object[]}
|
|
66
39
|
*/
|
|
67
|
-
const getAttributeModifiers = attribute => {
|
|
40
|
+
const getAttributeModifiers = (attribute) => {
|
|
68
41
|
const modifiers = [];
|
|
69
42
|
|
|
70
43
|
// Required
|
|
@@ -97,6 +70,22 @@ const getAttributeModifiers = attribute => {
|
|
|
97
70
|
);
|
|
98
71
|
}
|
|
99
72
|
|
|
73
|
+
// Custom field
|
|
74
|
+
if (attribute.customField) {
|
|
75
|
+
addImport('CustomField');
|
|
76
|
+
|
|
77
|
+
const customFieldUid = factory.createStringLiteral(attribute.customField);
|
|
78
|
+
const typeArguments = [customFieldUid];
|
|
79
|
+
|
|
80
|
+
if (attribute.options) {
|
|
81
|
+
typeArguments.push(toTypeLiteral(attribute.options));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
modifiers.push(
|
|
85
|
+
factory.createTypeReferenceNode(factory.createIdentifier('CustomField'), typeArguments)
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
100
89
|
// Plugin Options
|
|
101
90
|
if (!_.isEmpty(attribute.pluginOptions)) {
|
|
102
91
|
addImport('SetPluginOptions');
|
|
@@ -151,127 +140,31 @@ const getAttributeModifiers = attribute => {
|
|
|
151
140
|
return modifiers;
|
|
152
141
|
};
|
|
153
142
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
}
|
|
143
|
+
/**
|
|
144
|
+
* Generate a property signature node for a given attribute
|
|
145
|
+
*
|
|
146
|
+
* @param {object} schema
|
|
147
|
+
* @param {string} attributeName
|
|
148
|
+
* @param {object} attribute
|
|
149
|
+
* @returns {object}
|
|
150
|
+
*/
|
|
151
|
+
const attributeToPropertySignature = (schema, attributeName, attribute) => {
|
|
152
|
+
const baseType = getAttributeType(attributeName, attribute, schema.uid);
|
|
220
153
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const { enum: enumValues } = attribute;
|
|
225
|
-
|
|
226
|
-
return ['EnumerationAttribute', [toTypeLiteral(enumValues)]];
|
|
227
|
-
},
|
|
228
|
-
boolean() {
|
|
229
|
-
return ['BooleanAttribute'];
|
|
230
|
-
},
|
|
231
|
-
json() {
|
|
232
|
-
return ['JSONAttribute'];
|
|
233
|
-
},
|
|
234
|
-
media() {
|
|
235
|
-
return ['MediaAttribute'];
|
|
236
|
-
},
|
|
237
|
-
relation({ uid, attribute }) {
|
|
238
|
-
const { relation, target } = attribute;
|
|
239
|
-
|
|
240
|
-
const isMorphRelation = relation.toLowerCase().includes('morph');
|
|
241
|
-
|
|
242
|
-
if (isMorphRelation) {
|
|
243
|
-
return [
|
|
244
|
-
'RelationAttribute',
|
|
245
|
-
[factory.createStringLiteral(uid, true), factory.createStringLiteral(relation, true)],
|
|
246
|
-
];
|
|
247
|
-
}
|
|
154
|
+
if (baseType === null) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
248
157
|
|
|
249
|
-
|
|
250
|
-
'RelationAttribute',
|
|
251
|
-
[
|
|
252
|
-
factory.createStringLiteral(uid, true),
|
|
253
|
-
factory.createStringLiteral(relation, true),
|
|
254
|
-
factory.createStringLiteral(target, true),
|
|
255
|
-
],
|
|
256
|
-
];
|
|
257
|
-
},
|
|
258
|
-
component({ attribute }) {
|
|
259
|
-
const target = attribute.component;
|
|
260
|
-
const params = [factory.createStringLiteral(target, true)];
|
|
261
|
-
|
|
262
|
-
if (attribute.repeatable) {
|
|
263
|
-
params.push(factory.createTrue());
|
|
264
|
-
}
|
|
158
|
+
const modifiers = getAttributeModifiers(attribute);
|
|
265
159
|
|
|
266
|
-
|
|
267
|
-
},
|
|
268
|
-
dynamiczone({ attribute }) {
|
|
269
|
-
const componentsParam = factory.createTupleTypeNode(
|
|
270
|
-
attribute.components.map(component => factory.createStringLiteral(component))
|
|
271
|
-
);
|
|
160
|
+
const nodes = [baseType, ...modifiers];
|
|
272
161
|
|
|
273
|
-
|
|
274
|
-
|
|
162
|
+
return factory.createPropertySignature(
|
|
163
|
+
undefined,
|
|
164
|
+
factory.createIdentifier(attributeName),
|
|
165
|
+
undefined,
|
|
166
|
+
factory.createIntersectionTypeNode(nodes)
|
|
167
|
+
);
|
|
275
168
|
};
|
|
276
169
|
|
|
277
170
|
module.exports = attributeToPropertySignature;
|
|
@@ -1,10 +1,31 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
/* eslint-disable no-bitwise */
|
|
4
|
+
|
|
3
5
|
const ts = require('typescript');
|
|
4
6
|
const { factory } = require('typescript');
|
|
5
7
|
|
|
6
8
|
const { getSchemaInterfaceName } = require('./utils');
|
|
7
9
|
|
|
10
|
+
/**
|
|
11
|
+
*
|
|
12
|
+
* @param {object} schemaDefinition
|
|
13
|
+
* @param {ts.InterfaceDeclaration} schemaDefinition.definition
|
|
14
|
+
* @param {object} schemaDefinition.schema
|
|
15
|
+
*/
|
|
16
|
+
const schemaDefinitionToPropertySignature = ({ schema }) => {
|
|
17
|
+
const { uid } = schema;
|
|
18
|
+
|
|
19
|
+
const interfaceTypeName = getSchemaInterfaceName(uid);
|
|
20
|
+
|
|
21
|
+
return factory.createPropertySignature(
|
|
22
|
+
undefined,
|
|
23
|
+
factory.createStringLiteral(uid, true),
|
|
24
|
+
undefined,
|
|
25
|
+
factory.createTypeReferenceNode(factory.createIdentifier(interfaceTypeName))
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
|
|
8
29
|
/**
|
|
9
30
|
* Generate the global module augmentation block
|
|
10
31
|
*
|
|
@@ -46,23 +67,4 @@ const generateGlobalDefinition = (schemasDefinitions = []) => {
|
|
|
46
67
|
);
|
|
47
68
|
};
|
|
48
69
|
|
|
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
70
|
module.exports = { generateGlobalDefinition };
|
|
@@ -23,67 +23,7 @@ const {
|
|
|
23
23
|
|
|
24
24
|
const DEFAULT_OUT_FILENAME = 'schemas.d.ts';
|
|
25
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 {
|
|
38
|
-
strapi,
|
|
39
|
-
outDir = process.cwd(),
|
|
40
|
-
file = DEFAULT_OUT_FILENAME,
|
|
41
|
-
verbose = false,
|
|
42
|
-
silent = false,
|
|
43
|
-
} = options;
|
|
44
|
-
|
|
45
|
-
const schemas = getAllStrapiSchemas(strapi);
|
|
46
|
-
|
|
47
|
-
const schemasDefinitions = Object.values(schemas).map(schema => ({
|
|
48
|
-
schema,
|
|
49
|
-
definition: generateSchemaDefinition(schema),
|
|
50
|
-
}));
|
|
51
|
-
|
|
52
|
-
const formattedSchemasDefinitions = schemasDefinitions.reduce((acc, def) => {
|
|
53
|
-
acc.push(
|
|
54
|
-
// Definition
|
|
55
|
-
def.definition,
|
|
56
|
-
|
|
57
|
-
// Add a newline between each interface declaration
|
|
58
|
-
factory.createIdentifier('\n')
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
return acc;
|
|
62
|
-
}, []);
|
|
63
|
-
|
|
64
|
-
const allDefinitions = [
|
|
65
|
-
// Imports
|
|
66
|
-
generateImportDefinition(),
|
|
67
|
-
|
|
68
|
-
// Add a newline after the import statement
|
|
69
|
-
factory.createIdentifier('\n'),
|
|
70
|
-
|
|
71
|
-
// Schemas
|
|
72
|
-
...formattedSchemasDefinitions,
|
|
73
|
-
|
|
74
|
-
// Global
|
|
75
|
-
generateGlobalDefinition(schemasDefinitions),
|
|
76
|
-
];
|
|
77
|
-
|
|
78
|
-
const output = emitDefinitions(allDefinitions);
|
|
79
|
-
const formattedOutput = await format(output);
|
|
80
|
-
|
|
81
|
-
const definitionFilepath = await saveDefinitionToFileSystem(outDir, file, formattedOutput);
|
|
82
|
-
|
|
83
|
-
logDebugInformation(schemasDefinitions, { filepath: definitionFilepath, verbose, silent });
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
const emitDefinitions = definitions => {
|
|
26
|
+
const emitDefinitions = (definitions) => {
|
|
87
27
|
const nodeArray = factory.createNodeArray(definitions);
|
|
88
28
|
|
|
89
29
|
const sourceFile = ts.createSourceFile(
|
|
@@ -114,7 +54,7 @@ const saveDefinitionToFileSystem = async (dir, file, content) => {
|
|
|
114
54
|
* @param {string} content
|
|
115
55
|
* @returns {Promise<string>}
|
|
116
56
|
*/
|
|
117
|
-
const format = async content => {
|
|
57
|
+
const format = async (content) => {
|
|
118
58
|
const configFile = await prettier.resolveConfigFile();
|
|
119
59
|
const config = configFile
|
|
120
60
|
? await prettier.resolveConfig(configFile)
|
|
@@ -144,7 +84,7 @@ const logDebugInformation = (definitions, options = {}) => {
|
|
|
144
84
|
colAligns: ['center', 'left', 'left', 'center'],
|
|
145
85
|
});
|
|
146
86
|
|
|
147
|
-
const sortedDefinitions = definitions.map(def => ({
|
|
87
|
+
const sortedDefinitions = definitions.map((def) => ({
|
|
148
88
|
...def,
|
|
149
89
|
attributesCount: getDefinitionAttributesCount(def.definition),
|
|
150
90
|
}));
|
|
@@ -182,4 +122,64 @@ const logDebugInformation = (definitions, options = {}) => {
|
|
|
182
122
|
}
|
|
183
123
|
};
|
|
184
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Generate type definitions for Strapi schemas
|
|
127
|
+
*
|
|
128
|
+
* @param {object} options
|
|
129
|
+
* @param {Strapi} options.strapi
|
|
130
|
+
* @param {{ distDir: string; appDir: string; }} options.dirs
|
|
131
|
+
* @param {string} [options.outDir]
|
|
132
|
+
* @param {string} [options.file]
|
|
133
|
+
* @param {boolean} [options.verbose]
|
|
134
|
+
*/
|
|
135
|
+
const generateSchemasDefinitions = async (options = {}) => {
|
|
136
|
+
const {
|
|
137
|
+
strapi,
|
|
138
|
+
outDir = process.cwd(),
|
|
139
|
+
file = DEFAULT_OUT_FILENAME,
|
|
140
|
+
verbose = false,
|
|
141
|
+
silent = false,
|
|
142
|
+
} = options;
|
|
143
|
+
|
|
144
|
+
const schemas = getAllStrapiSchemas(strapi);
|
|
145
|
+
|
|
146
|
+
const schemasDefinitions = Object.values(schemas).map((schema) => ({
|
|
147
|
+
schema,
|
|
148
|
+
definition: generateSchemaDefinition(schema),
|
|
149
|
+
}));
|
|
150
|
+
|
|
151
|
+
const formattedSchemasDefinitions = schemasDefinitions.reduce((acc, def) => {
|
|
152
|
+
acc.push(
|
|
153
|
+
// Definition
|
|
154
|
+
def.definition,
|
|
155
|
+
|
|
156
|
+
// Add a newline between each interface declaration
|
|
157
|
+
factory.createIdentifier('\n')
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
return acc;
|
|
161
|
+
}, []);
|
|
162
|
+
|
|
163
|
+
const allDefinitions = [
|
|
164
|
+
// Imports
|
|
165
|
+
generateImportDefinition(),
|
|
166
|
+
|
|
167
|
+
// Add a newline after the import statement
|
|
168
|
+
factory.createIdentifier('\n'),
|
|
169
|
+
|
|
170
|
+
// Schemas
|
|
171
|
+
...formattedSchemasDefinitions,
|
|
172
|
+
|
|
173
|
+
// Global
|
|
174
|
+
generateGlobalDefinition(schemasDefinitions),
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
const output = emitDefinitions(allDefinitions);
|
|
178
|
+
const formattedOutput = await format(output);
|
|
179
|
+
|
|
180
|
+
const definitionFilepath = await saveDefinitionToFileSystem(outDir, file, formattedOutput);
|
|
181
|
+
|
|
182
|
+
logDebugInformation(schemasDefinitions, { filepath: definitionFilepath, verbose, silent });
|
|
183
|
+
};
|
|
184
|
+
|
|
185
185
|
module.exports = generateSchemasDefinitions;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const ts = require('typescript');
|
|
4
|
+
const _ = require('lodash/fp');
|
|
5
|
+
|
|
6
|
+
const { toTypeLiteral } = require('./utils');
|
|
7
|
+
|
|
8
|
+
const { factory } = ts;
|
|
9
|
+
|
|
10
|
+
module.exports = {
|
|
11
|
+
string() {
|
|
12
|
+
return ['StringAttribute'];
|
|
13
|
+
},
|
|
14
|
+
text() {
|
|
15
|
+
return ['TextAttribute'];
|
|
16
|
+
},
|
|
17
|
+
richtext() {
|
|
18
|
+
return ['RichTextAttribute'];
|
|
19
|
+
},
|
|
20
|
+
password() {
|
|
21
|
+
return ['PasswordAttribute'];
|
|
22
|
+
},
|
|
23
|
+
email() {
|
|
24
|
+
return ['EmailAttribute'];
|
|
25
|
+
},
|
|
26
|
+
date() {
|
|
27
|
+
return ['DateAttribute'];
|
|
28
|
+
},
|
|
29
|
+
time() {
|
|
30
|
+
return ['TimeAttribute'];
|
|
31
|
+
},
|
|
32
|
+
datetime() {
|
|
33
|
+
return ['DateTimeAttribute'];
|
|
34
|
+
},
|
|
35
|
+
timestamp() {
|
|
36
|
+
return ['TimestampAttribute'];
|
|
37
|
+
},
|
|
38
|
+
integer() {
|
|
39
|
+
return ['IntegerAttribute'];
|
|
40
|
+
},
|
|
41
|
+
biginteger() {
|
|
42
|
+
return ['BigIntegerAttribute'];
|
|
43
|
+
},
|
|
44
|
+
float() {
|
|
45
|
+
return ['FloatAttribute'];
|
|
46
|
+
},
|
|
47
|
+
decimal() {
|
|
48
|
+
return ['DecimalAttribute'];
|
|
49
|
+
},
|
|
50
|
+
uid({ attribute, uid }) {
|
|
51
|
+
const { targetField, options } = attribute;
|
|
52
|
+
|
|
53
|
+
// If there are no params to compute, then return the attribute type alone
|
|
54
|
+
if (targetField === undefined && options === undefined) {
|
|
55
|
+
return ['UIDAttribute'];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const params = [];
|
|
59
|
+
|
|
60
|
+
// If the targetField property is defined, then reference it,
|
|
61
|
+
// otherwise, put `undefined` keyword type nodes as placeholders
|
|
62
|
+
const targetFieldParams = _.isUndefined(targetField)
|
|
63
|
+
? [
|
|
64
|
+
factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword),
|
|
65
|
+
factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword),
|
|
66
|
+
]
|
|
67
|
+
: [factory.createStringLiteral(uid), factory.createStringLiteral(targetField)];
|
|
68
|
+
|
|
69
|
+
params.push(...targetFieldParams);
|
|
70
|
+
|
|
71
|
+
// If the options property is defined, transform it to
|
|
72
|
+
// a type literral node and add it to the params list
|
|
73
|
+
if (_.isObject(options)) {
|
|
74
|
+
params.push(toTypeLiteral(options));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return ['UIDAttribute', params];
|
|
78
|
+
},
|
|
79
|
+
enumeration({ attribute }) {
|
|
80
|
+
const { enum: enumValues } = attribute;
|
|
81
|
+
|
|
82
|
+
return ['EnumerationAttribute', [toTypeLiteral(enumValues)]];
|
|
83
|
+
},
|
|
84
|
+
boolean() {
|
|
85
|
+
return ['BooleanAttribute'];
|
|
86
|
+
},
|
|
87
|
+
json() {
|
|
88
|
+
return ['JSONAttribute'];
|
|
89
|
+
},
|
|
90
|
+
media() {
|
|
91
|
+
return ['MediaAttribute'];
|
|
92
|
+
},
|
|
93
|
+
relation({ uid, attribute }) {
|
|
94
|
+
const { relation, target } = attribute;
|
|
95
|
+
|
|
96
|
+
const isMorphRelation = relation.toLowerCase().includes('morph');
|
|
97
|
+
|
|
98
|
+
if (isMorphRelation) {
|
|
99
|
+
return [
|
|
100
|
+
'RelationAttribute',
|
|
101
|
+
[factory.createStringLiteral(uid, true), factory.createStringLiteral(relation, true)],
|
|
102
|
+
];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return [
|
|
106
|
+
'RelationAttribute',
|
|
107
|
+
[
|
|
108
|
+
factory.createStringLiteral(uid, true),
|
|
109
|
+
factory.createStringLiteral(relation, true),
|
|
110
|
+
factory.createStringLiteral(target, true),
|
|
111
|
+
],
|
|
112
|
+
];
|
|
113
|
+
},
|
|
114
|
+
component({ attribute }) {
|
|
115
|
+
const target = attribute.component;
|
|
116
|
+
const params = [factory.createStringLiteral(target, true)];
|
|
117
|
+
|
|
118
|
+
if (attribute.repeatable) {
|
|
119
|
+
params.push(factory.createTrue());
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return ['ComponentAttribute', params];
|
|
123
|
+
},
|
|
124
|
+
dynamiczone({ attribute }) {
|
|
125
|
+
const componentsParam = factory.createTupleTypeNode(
|
|
126
|
+
attribute.components.map((component) => factory.createStringLiteral(component))
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
return ['DynamicZoneAttribute', [componentsParam]];
|
|
130
|
+
},
|
|
131
|
+
};
|
|
@@ -8,13 +8,43 @@ const { getSchemaExtendsTypeName, getSchemaInterfaceName, toTypeLiteral } = requ
|
|
|
8
8
|
const attributeToPropertySignature = require('./attributes');
|
|
9
9
|
const { addImport } = require('./imports');
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Generate a property signature for the schema's `attributes` field
|
|
13
|
+
*
|
|
14
|
+
* @param {object} schema
|
|
15
|
+
* @returns {ts.PropertySignature}
|
|
16
|
+
*/
|
|
17
|
+
const generateAttributePropertySignature = (schema) => {
|
|
18
|
+
const { attributes } = schema;
|
|
19
|
+
|
|
20
|
+
const properties = Object.entries(attributes).map(([attributeName, attribute]) => {
|
|
21
|
+
return attributeToPropertySignature(schema, attributeName, attribute);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
return factory.createPropertySignature(
|
|
25
|
+
undefined,
|
|
26
|
+
factory.createIdentifier('attributes'),
|
|
27
|
+
undefined,
|
|
28
|
+
factory.createTypeLiteralNode(properties)
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const generatePropertyLiteralDefinitionFactory = (schema) => (key) => {
|
|
33
|
+
return factory.createPropertySignature(
|
|
34
|
+
undefined,
|
|
35
|
+
factory.createIdentifier(key),
|
|
36
|
+
undefined,
|
|
37
|
+
toTypeLiteral(schema[key])
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
11
41
|
/**
|
|
12
42
|
* Generate an interface declaration for a given schema
|
|
13
43
|
*
|
|
14
44
|
* @param {object} schema
|
|
15
45
|
* @returns {ts.InterfaceDeclaration}
|
|
16
46
|
*/
|
|
17
|
-
const generateSchemaDefinition = schema => {
|
|
47
|
+
const generateSchemaDefinition = (schema) => {
|
|
18
48
|
const { uid } = schema;
|
|
19
49
|
|
|
20
50
|
// Resolve the different interface names needed to declare the schema's interface
|
|
@@ -27,7 +57,7 @@ const generateSchemaDefinition = schema => {
|
|
|
27
57
|
// Properties whose values can be mapped to a literal type expression
|
|
28
58
|
const literalPropertiesDefinitions = ['info', 'options', 'pluginOptions']
|
|
29
59
|
// Ignore non-existent or empty declarations
|
|
30
|
-
.filter(key => !isEmpty(schema[key]))
|
|
60
|
+
.filter((key) => !isEmpty(schema[key]))
|
|
31
61
|
// Generate literal definition for each property
|
|
32
62
|
.map(generatePropertyLiteralDefinitionFactory(schema));
|
|
33
63
|
|
|
@@ -54,34 +84,4 @@ const generateSchemaDefinition = schema => {
|
|
|
54
84
|
return schemaType;
|
|
55
85
|
};
|
|
56
86
|
|
|
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
87
|
module.exports = { generateSchemaDefinition };
|
|
@@ -23,7 +23,7 @@ const {
|
|
|
23
23
|
* @param {Strapi} strapi
|
|
24
24
|
* @returns {object}
|
|
25
25
|
*/
|
|
26
|
-
const getAllStrapiSchemas = strapi => ({ ...strapi.contentTypes, ...strapi.components });
|
|
26
|
+
const getAllStrapiSchemas = (strapi) => ({ ...strapi.contentTypes, ...strapi.components });
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* Extract a valid interface name from a schema uid
|
|
@@ -33,19 +33,7 @@ const getAllStrapiSchemas = strapi => ({ ...strapi.contentTypes, ...strapi.compo
|
|
|
33
33
|
*/
|
|
34
34
|
const getSchemaInterfaceName = pipe(replace(/(:.)/, ' '), camelCase, upperFirst);
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
* Get the parent type name to extend based on the schema's nature
|
|
38
|
-
*
|
|
39
|
-
* @param {object} schema
|
|
40
|
-
* @returns {string}
|
|
41
|
-
*/
|
|
42
|
-
const getSchemaExtendsTypeName = schema => {
|
|
43
|
-
const base = getSchemaModelType(schema);
|
|
44
|
-
|
|
45
|
-
return upperFirst(base) + 'Schema';
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
const getSchemaModelType = schema => {
|
|
36
|
+
const getSchemaModelType = (schema) => {
|
|
49
37
|
const { modelType, kind } = schema;
|
|
50
38
|
|
|
51
39
|
// Components
|
|
@@ -54,13 +42,25 @@ const getSchemaModelType = schema => {
|
|
|
54
42
|
}
|
|
55
43
|
|
|
56
44
|
// Content-Types
|
|
57
|
-
|
|
45
|
+
if (modelType === 'contentType') {
|
|
58
46
|
return kind;
|
|
59
47
|
}
|
|
60
48
|
|
|
61
49
|
return null;
|
|
62
50
|
};
|
|
63
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Get the parent type name to extend based on the schema's nature
|
|
54
|
+
*
|
|
55
|
+
* @param {object} schema
|
|
56
|
+
* @returns {string}
|
|
57
|
+
*/
|
|
58
|
+
const getSchemaExtendsTypeName = (schema) => {
|
|
59
|
+
const base = getSchemaModelType(schema);
|
|
60
|
+
|
|
61
|
+
return `${upperFirst(base)}Schema`;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
64
|
/**
|
|
65
65
|
* Get a type node based on a type and its params
|
|
66
66
|
*
|
|
@@ -77,7 +77,7 @@ const getTypeNode = (typeName, params = []) => {
|
|
|
77
77
|
* @param data
|
|
78
78
|
* @returns {ts.TypeNode}
|
|
79
79
|
*/
|
|
80
|
-
const toTypeLiteral = data => {
|
|
80
|
+
const toTypeLiteral = (data) => {
|
|
81
81
|
if (isUndefined(data)) {
|
|
82
82
|
return factory.createLiteralTypeNode(ts.SyntaxKind.UndefinedKeyword);
|
|
83
83
|
}
|
|
@@ -99,7 +99,7 @@ const toTypeLiteral = data => {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
if (isArray(data)) {
|
|
102
|
-
return factory.createTupleTypeNode(data.map(item => toTypeLiteral(item)));
|
|
102
|
+
return factory.createTupleTypeNode(data.map((item) => toTypeLiteral(item)));
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
if (isDate(data)) {
|
|
@@ -139,7 +139,7 @@ const toTypeLiteral = data => {
|
|
|
139
139
|
* @param {ts.TypeNode} definition
|
|
140
140
|
* @returns {number | null}
|
|
141
141
|
*/
|
|
142
|
-
const getDefinitionAttributesCount = definition => {
|
|
142
|
+
const getDefinitionAttributesCount = (definition) => {
|
|
143
143
|
const attributesNode = definition.members.find(propEq('name.escapedText', 'attributes'));
|
|
144
144
|
|
|
145
145
|
if (!attributesNode) {
|
|
@@ -17,7 +17,9 @@ const DEFAULT_TS_CONFIG_FILENAME = 'tsconfig.json';
|
|
|
17
17
|
*/
|
|
18
18
|
module.exports = (dir, { filename = DEFAULT_TS_CONFIG_FILENAME, ancestorsLookup = false } = {}) => {
|
|
19
19
|
const dirAbsolutePath = path.resolve(dir);
|
|
20
|
-
|
|
20
|
+
let configFilePath = ts.findConfigFile(dirAbsolutePath, ts.sys.fileExists, filename);
|
|
21
|
+
|
|
22
|
+
if (configFilePath) configFilePath = path.resolve(configFilePath);
|
|
21
23
|
|
|
22
24
|
if (!configFilePath || ancestorsLookup) {
|
|
23
25
|
return configFilePath;
|
|
@@ -8,7 +8,7 @@ const formatHost = require('./format-host');
|
|
|
8
8
|
* Report one or several diagnostic to the console
|
|
9
9
|
* @param {ts.Diagnostic[] | ts.Diagnostic} diagnostics
|
|
10
10
|
*/
|
|
11
|
-
module.exports = diagnostics => {
|
|
11
|
+
module.exports = (diagnostics) => {
|
|
12
12
|
const formattedDiagnostics = ts.formatDiagnosticsWithColorAndContext(
|
|
13
13
|
Array.isArray(diagnostics) ? diagnostics : [diagnostics],
|
|
14
14
|
formatHost
|
|
@@ -4,7 +4,7 @@ const ts = require('typescript');
|
|
|
4
4
|
|
|
5
5
|
const logDiagnostics = require('./report-diagnostics');
|
|
6
6
|
|
|
7
|
-
module.exports = configPath => {
|
|
7
|
+
module.exports = (configPath) => {
|
|
8
8
|
// Parse the tsconfig.json file and resolve every file name & compiler options
|
|
9
9
|
const { errors, ...configOptions } = ts.getParsedCommandLineOfConfigFile(
|
|
10
10
|
configPath,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strapi/typescript-utils",
|
|
3
|
-
"version": "0.0.0-
|
|
3
|
+
"version": "0.0.0-d36609ff26fb4e00a41a7e2b657f16c7466203fe",
|
|
4
4
|
"description": "Typescript support for Strapi",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"strapi",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"typescript": "4.6.2"
|
|
33
33
|
},
|
|
34
34
|
"engines": {
|
|
35
|
-
"node": ">=
|
|
35
|
+
"node": ">=14.19.1 <=18.x.x",
|
|
36
36
|
"npm": ">=6.0.0"
|
|
37
37
|
},
|
|
38
|
-
"gitHead": "
|
|
38
|
+
"gitHead": "d36609ff26fb4e00a41a7e2b657f16c7466203fe"
|
|
39
39
|
}
|