mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-23 13:03:50 +00:00
227 lines
7.4 KiB
TypeScript
227 lines
7.4 KiB
TypeScript
import type { TSType, TSTypeLiteral } from '@babel/types';
|
|
import { parse } from '@babel/parser';
|
|
import traverse, { NodePath } from '@babel/traverse';
|
|
import * as fs from 'fs';
|
|
import type { ApiMetaData, ApiType, itemType } from './type';
|
|
|
|
function getMetadata(path: NodePath): ApiMetaData | undefined {
|
|
const metadata = {
|
|
name: '',
|
|
author: '',
|
|
version: '',
|
|
method: ''
|
|
};
|
|
if (
|
|
path.isExportNamedDeclaration() && // get metadata
|
|
path.node.declaration?.type === 'VariableDeclaration' &&
|
|
path.node.declaration.declarations[0]?.id.type === 'Identifier' &&
|
|
path.node.declaration.declarations[0].id.name === 'ApiMetadata' &&
|
|
path.node.declaration.declarations[0].init?.type === 'ObjectExpression'
|
|
) {
|
|
path.node.declaration.declarations[0].init.properties.forEach((item) => {
|
|
if (item.type === 'ObjectProperty') {
|
|
const key = item.key.type === 'Identifier' ? item.key.name : item.key.type;
|
|
if (key === 'name') {
|
|
metadata.name = item.value.type === 'StringLiteral' ? item.value.value : item.value.type;
|
|
}
|
|
if (key === 'author') {
|
|
metadata.author =
|
|
item.value.type === 'StringLiteral' ? item.value.value : item.value.type;
|
|
}
|
|
if (key === 'version') {
|
|
metadata.version =
|
|
item.value.type === 'StringLiteral' ? item.value.value : item.value.type;
|
|
} else if (key === 'method') {
|
|
metadata.method =
|
|
item.value.type === 'StringLiteral' ? item.value.value : item.value.type;
|
|
metadata.method = metadata.method.toUpperCase();
|
|
}
|
|
}
|
|
});
|
|
if (metadata.name && metadata.author && metadata.version) {
|
|
return metadata;
|
|
}
|
|
}
|
|
}
|
|
|
|
function getDescription(path: NodePath) {
|
|
if (path.isFunctionDeclaration() && path.node.id?.name === 'handler') {
|
|
const comments = path.node.leadingComments?.map((item) => item.value.trim()).join('\n');
|
|
return comments;
|
|
}
|
|
}
|
|
|
|
type ApiDataType = {
|
|
type?: 'query' | 'body' | 'response';
|
|
comment?: string;
|
|
items?: itemType[];
|
|
dataType?: string;
|
|
};
|
|
|
|
function parseType(type?: TSType): string {
|
|
if (!type) {
|
|
return '';
|
|
}
|
|
if (type.type === 'TSTypeReference') {
|
|
return type.typeName.type === 'Identifier' ? type.typeName.name : type.typeName.type;
|
|
} else if (type.type === 'TSArrayType') {
|
|
return `${parseType(type.elementType)}[]`;
|
|
} else if (type.type === 'TSUnionType') {
|
|
return type.types.map((item) => parseType(item)).join(' | ');
|
|
} else if (type.type === 'TSIntersectionType') {
|
|
return type.types.map((item) => parseType(item)).join(' & ');
|
|
} else if (type.type === 'TSLiteralType') {
|
|
return type.literal.type === 'StringLiteral' ? type.literal.value : type.literal.type;
|
|
// } else if (type.type === 'TSTypeLiteral') {
|
|
// return parseTypeLiteral(type);
|
|
} else if (type.type === 'TSStringKeyword') {
|
|
return 'string';
|
|
} else if (type.type === 'TSNumberKeyword') {
|
|
return 'number';
|
|
} else if (type.type === 'TSBooleanKeyword') {
|
|
return 'boolean';
|
|
} else {
|
|
return type.type;
|
|
}
|
|
}
|
|
|
|
function parseTypeLiteral(type: TSTypeLiteral): itemType[] {
|
|
const items: itemType[] = [];
|
|
type.members.forEach((item) => {
|
|
if (item.type === 'TSPropertySignature') {
|
|
const key = item.key.type === 'Identifier' ? item.key.name : item.key.type;
|
|
const value = parseType(item.typeAnnotation?.typeAnnotation);
|
|
const comments = [
|
|
item.leadingComments?.map((item) => item.value.trim()).join('\n'),
|
|
item.trailingComments?.map((item) => item.value.trim()).join('\n')
|
|
].join('\n');
|
|
const required = item.optional ? false : true;
|
|
items.push({
|
|
type: value,
|
|
comment: comments,
|
|
key,
|
|
required
|
|
});
|
|
}
|
|
});
|
|
return items;
|
|
}
|
|
|
|
function getData(path: NodePath): ApiDataType | undefined {
|
|
const type: ApiDataType = {};
|
|
if (path.isExportNamedDeclaration()) {
|
|
const comments = [
|
|
path.node.leadingComments?.map((item) => item.value.trim()).join('\n'),
|
|
path.node.trailingComments?.map((item) => item.value.trim()).join('\n')
|
|
].join('\n');
|
|
if (comments) {
|
|
type.comment = comments;
|
|
}
|
|
if (path.node.declaration?.type === 'TSTypeAliasDeclaration') {
|
|
if (path.node.declaration.id.type === 'Identifier') {
|
|
if (path.node.declaration.id.name.endsWith('Query')) {
|
|
type.type = 'query';
|
|
const queryType = path.node.declaration.typeAnnotation;
|
|
if (queryType) {
|
|
if (queryType.type === 'TSTypeLiteral') {
|
|
type.items = parseTypeLiteral(queryType);
|
|
} else {
|
|
type.dataType = parseType(queryType);
|
|
}
|
|
}
|
|
} else if (path.node.declaration.id.name.endsWith('Body')) {
|
|
type.type = 'body';
|
|
if (path.node.declaration.typeAnnotation) {
|
|
if (path.node.declaration.typeAnnotation.type === 'TSTypeLiteral') {
|
|
type.items = parseTypeLiteral(path.node.declaration.typeAnnotation);
|
|
} else {
|
|
type.dataType = parseType(path.node.declaration.typeAnnotation);
|
|
}
|
|
}
|
|
} else if (path.node.declaration.id.name.endsWith('Response')) {
|
|
type.type = 'response';
|
|
if (path.node.declaration.typeAnnotation) {
|
|
if (path.node.declaration.typeAnnotation.type === 'TSTypeLiteral') {
|
|
type.items = parseTypeLiteral(path.node.declaration.typeAnnotation);
|
|
} else {
|
|
type.dataType = parseType(path.node.declaration.typeAnnotation);
|
|
}
|
|
}
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function parseCode(code: string): ApiType {
|
|
const ast = parse(code, {
|
|
sourceType: 'module',
|
|
plugins: ['typescript', 'jsx']
|
|
});
|
|
|
|
const api = <ApiType>{};
|
|
|
|
traverse(ast, {
|
|
enter(path) {
|
|
const metadata = getMetadata(path);
|
|
const description = getDescription(path);
|
|
const data = getData(path);
|
|
if (metadata) {
|
|
api.name = metadata.name;
|
|
api.author = metadata.author;
|
|
api.version = metadata.version;
|
|
}
|
|
if (description) {
|
|
api.description = description;
|
|
}
|
|
if (data) {
|
|
if (data.type === 'query') {
|
|
api.query = data.items ?? {
|
|
type: data.dataType ?? '',
|
|
comment: data.comment ?? ''
|
|
};
|
|
} else if (data.type === 'body') {
|
|
api.body = data.items ?? {
|
|
type: data.dataType ?? '',
|
|
comment: data.comment ?? ''
|
|
};
|
|
} else if (data.type === 'response') {
|
|
api.response = data.items ?? {
|
|
type: data.dataType ?? '',
|
|
comment: data.comment ?? ''
|
|
};
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
return api;
|
|
}
|
|
|
|
function getMethod(api: ApiType): 'GET' | 'POST' {
|
|
if (api.query && !(Array.isArray(api.query) && api.query.length === 0)) {
|
|
return 'GET';
|
|
} else if (api.body && !(Array.isArray(api.body) && api.body.length === 0)) {
|
|
return 'POST';
|
|
} else {
|
|
return 'GET';
|
|
}
|
|
}
|
|
|
|
export function parseAPI({ path, rootPath }: { path: string; rootPath: string }): ApiType {
|
|
const code = fs.readFileSync(path, 'utf-8');
|
|
const authApiKey = code.includes('authApiKey: true');
|
|
const authToken = code.includes('authToken: true');
|
|
const api = parseCode(code);
|
|
api.authorization = authApiKey ? 'apikey' : authToken ? 'token' : undefined;
|
|
api.url = path.replace('.ts', '').replace(rootPath, '');
|
|
api.path = path;
|
|
if (api.method === undefined) {
|
|
api.method = getMethod(api);
|
|
}
|
|
return api;
|
|
}
|