amis/scripts/build-schemas.ts

320 lines
8.2 KiB
TypeScript
Raw Normal View History

2020-09-08 17:39:14 +08:00
/**
* @file json-schemas
*/
import fs = require('fs');
import path = require('path');
import tsj = require('ts-json-schema-generator');
import mkdirp = require('mkdirp');
2020-09-10 23:31:27 +08:00
import {
DiagnosticError,
2020-09-16 00:41:21 +08:00
IntersectionTypeFormatter,
ObjectTypeFormatter,
SubTypeFormatter,
2020-09-10 23:31:27 +08:00
UnknownNodeError,
2020-09-16 00:41:21 +08:00
UnknownTypeError,
IntersectionNodeParser,
2020-09-16 13:20:10 +08:00
SubNodeParser,
Schema,
uniqueArray
2020-09-10 23:31:27 +08:00
} from 'ts-json-schema-generator';
2020-09-16 00:41:21 +08:00
import {IntersectionTypeFormatter as MyIntersectionTypeFormatter} from './TypeFormatter/IntersectionTypeFormatter';
import {IntersectionNodeParser as MyIntersectionNodeParser} from './NodeParser/IntersectionNodeParser';
2020-09-08 17:39:14 +08:00
/**
*
*/
async function main() {
2022-06-01 15:06:00 +08:00
const dir = path.join(__dirname, '../packages/amis/src');
const outDir = path.join(__dirname, '../packages/amis/');
const tsConfig = path.join(
__dirname,
'../packages/amis/tsconfig-for-declaration.json'
);
2020-09-08 17:39:14 +08:00
const config = {
2020-09-09 10:58:37 +08:00
path: path.join(dir, 'Schema.ts'),
2020-09-08 17:39:14 +08:00
tsconfig: tsConfig,
2022-06-01 15:06:00 +08:00
type: 'RootSchema',
skipTypeCheck: true
2020-09-08 17:39:14 +08:00
};
2020-09-09 10:58:37 +08:00
const generator = tsj.createGenerator(config);
2020-09-16 00:41:21 +08:00
hackIt(generator);
2020-09-08 17:39:14 +08:00
const schema = generator.createSchema(config.type);
2020-09-16 13:20:10 +08:00
fixSchema(schema);
2020-09-08 17:39:14 +08:00
const outputFile = path.join(outDir, 'schema.json');
mkdirp(path.dirname(outputFile));
fs.writeFileSync(outputFile, JSON.stringify(schema, null, 2));
}
2020-09-16 13:20:10 +08:00
function fixSchema(schema: Schema) {
const keys = Object.keys(schema.definitions!);
const list: Array<{
patternProperties: any;
referenceKey: string;
definationKey: string;
}> = [];
keys.forEach(definationKey => {
const definition: any = schema.definitions![definationKey];
if (
Array.isArray(definition.allOf) &&
definition.allOf.length &&
definition.allOf[0].patternProperties &&
/^#\/definitions\/(.*?)$/.test(definition.allOf[0].$ref || '')
) {
const referenceKey = RegExp.$1;
if (schema.definitions![referenceKey]) {
list.push({
patternProperties: definition.allOf[0].patternProperties,
referenceKey,
definationKey
});
definition.allOf[0].$ref =
'#/definitions/' + referenceKey + definationKey;
}
}
});
copyAnyOf(schema, list);
2020-10-21 23:51:00 +08:00
convertAnyOfItemToConditionalItem(schema, ['SchemaObject', 'ActionSchema']);
2020-10-21 23:51:00 +08:00
schema.definitions!['UnkownSchema'] = {
type: 'object',
description: '不能识别渲染器类型,无法提供提示信息。'
};
2020-09-16 13:20:10 +08:00
}
function copyAnyOf(
schema: Schema,
list: Array<{
patternProperties: any;
referenceKey: string;
definationKey: string;
}>
) {
list.forEach(({referenceKey, definationKey, patternProperties}) => {
const definition: any = schema.definitions![referenceKey];
if (Array.isArray(definition.anyOf)) {
const anyOf = definition.anyOf.map((item: any) => {
2020-09-16 19:33:47 +08:00
if (item.properties) {
const keys = Object.keys(patternProperties)[0];
const extenedPatternProperties = {
[`^(${uniqueArray(
Object.keys(item.properties).concat(
keys.substring(2, keys.length - 2).split('|')
)
).join('|')})$`]: {}
};
return {
...item,
additionalProperties: false,
patternProperties: extenedPatternProperties
};
} else if (!/^#\/definitions\/(.*?)$/.test(item.$ref || '')) {
2020-09-16 13:20:10 +08:00
return item;
}
const baseKey = RegExp.$1;
if (!baseKey || !schema.definitions![baseKey]) {
return item;
}
const baseDefinition: any = schema.definitions![baseKey]!;
if (!baseDefinition) {
return item;
}
2020-09-16 19:33:47 +08:00
if (
Array.isArray(baseDefinition.anyOf) &&
baseDefinition.anyOf.length
) {
if (baseKey === 'ButtonControlSchema') {
copyAnyOf(schema, [
{
referenceKey: baseKey,
definationKey,
patternProperties
}
]);
return {
$ref: '#/definitions/' + baseKey + definationKey
};
} else {
return item;
}
2020-09-16 13:20:10 +08:00
} else if (!baseDefinition.properties) {
return item;
}
const keys = Object.keys(patternProperties)[0];
const extenedPatternProperties = {
[`^(${uniqueArray(
Object.keys(baseDefinition.properties).concat(
keys.substring(2, keys.length - 2).split('|')
)
).join('|')})$`]: {}
};
return {
...item,
additionalProperties: false,
patternProperties: extenedPatternProperties
};
});
schema.definitions![`${referenceKey}${definationKey}`] = {
anyOf: anyOf
};
}
});
}
2020-09-16 00:41:21 +08:00
function hackIt(generator: any) {
const circularReferenceTypeFormatter = generator.typeFormatter;
const typeFormatters =
circularReferenceTypeFormatter.childTypeFormatter.typeFormatters;
replaceTypeFormatter(
typeFormatters,
IntersectionTypeFormatter,
new MyIntersectionTypeFormatter(circularReferenceTypeFormatter)
);
const chainNodeParser = generator.nodeParser.childNodeParser;
const typeChecker = generator.program.getTypeChecker();
replaceNodeParser(
chainNodeParser.nodeParsers,
IntersectionNodeParser,
2020-11-30 16:53:04 +08:00
new MyIntersectionNodeParser(typeChecker as any, chainNodeParser) as any
2020-09-16 00:41:21 +08:00
);
}
function replaceTypeFormatter(
typeFormatters: Array<SubTypeFormatter>,
Klass: any,
replaceWith: SubTypeFormatter
) {
const idx = typeFormatters.findIndex(item => item instanceof Klass);
if (~idx) {
typeFormatters.splice(idx, 1, replaceWith);
}
}
function replaceNodeParser(
nodeParsers: Array<SubNodeParser>,
Klass: any,
replaceWith: SubNodeParser
) {
const idx = nodeParsers.findIndex(item => item instanceof Klass);
if (~idx) {
nodeParsers.splice(idx, 1, replaceWith);
}
}
function convertAnyOfItemToConditionalItem(schema: any, list: Array<string>) {
list.forEach(key => {
if (!Array.isArray(schema.definitions[key]?.anyOf)) {
return;
}
const list: Array<any> = schema.definitions[key].anyOf.concat();
const allOf: Array<any> = [];
while (list.length) {
const item: any = list.shift()!;
if (item.$ref) {
const src = schema.definitions[item.$ref.replace('#/definitions/', '')];
if (!src) {
console.log(`${item.$ref} not found`);
return;
}
if (Array.isArray(src.anyOf)) {
list.unshift.apply(list, src.anyOf);
continue;
}
allOf.push({
if: {
properties: pickConstProperties(src)
},
then: item
});
} else {
allOf.push({
if: {
properties: pickConstProperties(item)
},
then: item
});
}
}
schema.definitions[key] = {
allOf
};
});
}
function pickConstProperties(schema: any) {
const keys = Object.keys(schema.properties);
const filtedProperties: any = {};
keys.forEach((key: string) => {
if (
schema.properties[key]?.const ||
(schema.properties[key]?.enum && /type|mode/i.test(key))
) {
filtedProperties[key] = schema.properties[key];
}
});
return filtedProperties;
}
2020-09-09 10:58:37 +08:00
main().catch(e => {
if (e instanceof DiagnosticError) {
console.log('Ts 错误\n');
e.getDiagnostics().forEach(diagnostic => {
const poistion = diagnostic.file!.getLineAndCharacterOfPosition(
diagnostic.start!
);
console.log(
`\x1b[36m${diagnostic.file!.fileName}:${poistion.line + 1}:${
poistion.character + 1
}\x1b[0m - \x1b[31merror\x1b[0m\n`
);
console.log(diagnostic.messageText);
console.log('\n');
});
2020-09-09 13:56:33 +08:00
} else if (e instanceof UnknownNodeError) {
let node = e.getNode();
const sourceFile = node.getSourceFile();
const position = sourceFile.getLineAndCharacterOfPosition(node.pos);
2020-09-09 13:56:33 +08:00
console.log(
`\x1b[36m${sourceFile.fileName}:${position.line + 1}:${
position.character + 1
}\x1b[0m - \x1b[31m类型不支持转 JSON Schema\x1b[0m\n`
);
2020-09-10 23:31:27 +08:00
} else if (e instanceof UnknownTypeError) {
console.log(`类型不支持`, e);
} else {
console.error(e);
2020-09-09 10:58:37 +08:00
}
});