@clean-js/api-gen
Version:
[docs](https://lulusir.github.io/clean-js/api-gen/usage) [中文文档](https://github.com/lulusir/clean-js-api-gen/blob/main/README-zh.md)
419 lines (412 loc) • 14.7 kB
JavaScript
;
var jiti = require('jiti');
var path = require('path');
var changeCase = require('change-case');
var tsMorph = require('ts-morph');
var fs = require('fs-extra');
var jsonSchemaToTypescript = require('json-schema-to-typescript');
var mkdirp = require('mkdirp');
var jsonSchemaToZod = require('json-schema-to-zod');
class Config {
url = '';
type = 'axios';
outDir = 'clean-js';
diff = true;
zod = false;
mock = false;
loadRuntime() {
const rootDir = process.cwd();
const require = jiti(rootDir, { interopDefault: true, esmResolve: true });
const runtimeConfig = require('./clean.config');
Object.assign(this, runtimeConfig);
}
loadConfig(opt) {
Object.assign(this, opt);
}
getOutPath() {
return path.join(process.cwd(), this.outDir);
}
getServicePath(serviceName = 'http') {
return path.join(this.getOutPath(), `./${serviceName}.service.ts`);
}
getMockPath(serviceName = 'http') {
return path.join(this.getOutPath(), `./${serviceName}.mock.ts`);
}
getAstCachePath() {
return path.join(this.getOutPath(), '.ast.cache.json');
}
getLogPath() {
return path.join(this.getOutPath(), './log');
}
}
const config = new Config();
function safeName(name) {
const washName = name.replace(/[«»<>:\\|?*".]/g, '');
let modeName = changeCase.pascalCase(washName);
return modeName;
}
function isSimpleType(schemaType) {
return ['number', 'string', 'boolean', 'null'].includes(schemaType);
}
const jsTypeMap = {
integer: 'number',
number: 'number',
string: 'string',
boolean: 'boolean',
object: 'object',
null: 'null',
array: 'array',
undefined: 'undefined',
file: 'File',
};
function schemaTypeToJsType(type) {
if (type === undefined) {
return jsTypeMap.undefined;
}
return jsTypeMap[type];
}
class Writer {
static async schemaToRenameInterface(schema, name) {
const title = name;
const s = {
...schema,
title,
};
const code = await jsonSchemaToTypescript.compile(s, title, {
bannerComment: '',
additionalProperties: false,
});
return code;
}
static getSourceFile() {
const tplPaths = {
axios: 'axios.tpl.ts',
umi3: 'umi3.tpl.ts',
};
const tplCode = fs.readFileSync(path.join(__dirname, '../template/', tplPaths[config.type || 'axios']), 'utf-8');
const p = new tsMorph.Project({});
const s = p.createSourceFile(config.getServicePath(), tplCode, {
overwrite: true,
});
return s;
}
static cleanOut() {
return fs.rm(config.getOutPath(), { recursive: true }).catch((err) => {
console.log(err);
});
}
static async writeOutFolder() {
if (fs.existsSync(config.getOutPath())) {
await mkdirp(config.getOutPath(), {});
}
}
static async writeFile(filePath, content) {
const fileName = path.basename(filePath);
const folder = path.dirname(filePath);
try {
await mkdirp(folder, {});
fs.writeFileSync(path.join(folder, fileName), content);
}
catch (err) {
console.log(err);
}
}
}
class RequestGeneratorSub {
ast;
constructor(ast) {
this.ast = ast;
}
getSourceFile() {
const p = new tsMorph.Project({});
const s = p.createSourceFile('');
return s;
}
async paint() {
return await this.paintRequestsOneFile(this.ast.requests);
}
async paintRequestsOneFile(requests) {
const sf = this.getSourceFile();
if (requests.length) {
await Promise.all(requests.map(async (s) => {
const { pathAlias, queryAlias, bodyAlias, response200Alias } = await this.processPrams(sf, s);
this.generateFunc(s, sf, bodyAlias, queryAlias, pathAlias, response200Alias);
}));
}
return sf.getText();
}
async processPrams(sf, s) {
let response200Alias = null;
if (s.responses?.length) {
const res200 = s.responses.filter((v) => v.status === 200)[0];
response200Alias = await this.writeSchema('N' + s.id + '.' + 'Res', sf, res200?.schema, 'Response');
}
const pathAlias = [];
await Promise.all(Object.entries(s?.pathParams || {})?.map(async ([name, schema]) => {
if (schema) {
const alias = await this.writeSchema('N' + s.id + '.' + 'Path', sf, schema, safeName(name), name);
pathAlias.push({
name,
alias,
});
}
}));
const queryAlias = [];
await Promise.all(Object.entries(s?.queryParams || {})?.map(async ([name, schema]) => {
if (schema) {
const alias = await this.writeSchema('N' + s.id + '.' + 'Query', sf, schema, safeName(name), name);
queryAlias.push({
name,
alias,
});
}
}));
let bodyAlias = null;
if (s.bodyParams) {
if (s.bodyParams.type === 'json') {
const a = await this.writeSchema('N' + s.id + '.' + 'Body', sf, s.bodyParams.schema, 'Body');
bodyAlias = {
...a,
type: 'json',
};
}
if (s.bodyParams.type === 'formData') {
const a = await this.writeSchema('N' + s.id + '.' + 'Body', sf, s.bodyParams.schema, 'BodyFile');
bodyAlias = {
...a,
type: 'formData',
};
}
}
return {
bodyAlias,
queryAlias,
pathAlias,
response200Alias,
};
}
async generateFunc(s, sf, bodyAlias, queryAlias, pathAlias, response200Alias) {
let parameter = [];
if (bodyAlias || queryAlias.length || pathAlias.length) {
parameter = [
{
name: 'parameter',
type: (writer) => {
writer.block(() => {
if (bodyAlias) {
writer.write(`body: ${bodyAlias.alias},`).endsWith(',');
}
if (queryAlias.length) {
writer
.write('params: ')
.block(() => {
queryAlias.forEach((v) => {
writer.write(`'${v.name}'${v.alias.required ? ':' : '?:'} ${v.alias.alias},`);
});
})
.endsWith(',');
}
if (pathAlias.length) {
writer
.write('path: ')
.block(() => {
pathAlias.forEach((v) => {
writer.write(`'${v.name}': ${v.alias.alias},`);
});
})
.endsWith(',');
}
});
},
},
];
}
if (config.type === 'axios') {
parameter.push({
name: 'config',
hasQuestionToken: true,
type: 'AxiosRequestConfig',
});
const fn = sf?.addFunction({
isExported: true,
name: s.id,
parameters: parameter,
statements: (writer) => {
if (config.zod) {
writer.write(`const s = ${jsonSchemaToZod.parseSchema(response200Alias?.schema?.schema)};`);
}
writer.write('return Req.request');
if (response200Alias) {
writer.write(`<${response200Alias.alias}>`);
}
writer.write('(');
writer
.block(() => {
if (pathAlias.length) {
writer.writeLine(`url: replaceUrlPath('${s.url}', parameter?.path),`);
}
else {
writer.writeLine(`url: '${s.url}',`);
}
writer.writeLine(`method: '${s.method}',`);
if (queryAlias.length) {
writer.writeLine(`params: parameter.params,`);
}
if (bodyAlias) {
if (bodyAlias.type === 'json') {
writer.writeLine(`data: parameter.body,`);
}
if (bodyAlias.type === 'formData') {
writer.writeLine(`data: handleFormData(parameter.body),`);
writer.writeLine(`headers: {
'Content-Type': 'multipart/form-data'
},`);
}
}
writer.writeLine('...config');
})
.write(')');
if (config.zod) {
writer.write(`.then(res => {
if (verifyZod && s) {
verifyZod(s, res.data, '${s.url}')
}
return res
})`);
}
writer.write(';');
},
});
if (s.description) {
fn.addJsDoc({
description: s.description,
});
}
}
else if (config.type === 'umi3') {
parameter.push({
name: 'config',
hasQuestionToken: true,
type: 'RequestUmiOptions',
});
const fn = sf?.addFunction({
isExported: true,
name: s.id,
parameters: parameter,
statements: (writer) => {
if (config.zod) {
writer.write(`const s = ${jsonSchemaToZod.parseSchema(response200Alias?.schema?.schema)};`);
}
writer.write('return Req.request');
if (response200Alias) {
writer.write(`<${response200Alias.alias}>`);
}
if (pathAlias.length) {
writer.write(`( replaceUrlPath('${s.url}', parameter?.path),`);
}
else {
writer.writeLine(`('${s.url}',`);
}
writer
.block(() => {
writer.writeLine(`method: '${s.method}',`);
if (queryAlias.length) {
writer.writeLine(`params: parameter.params,`);
}
if (bodyAlias) {
if (bodyAlias.type === 'json') {
writer.writeLine(`data: parameter.body,`);
}
if (bodyAlias.type === 'formData') {
writer.writeLine(`data: handleFormData(parameter.body),`);
writer.writeLine(`requestType: 'form',`);
}
}
writer.writeLine('...config');
})
.write(')');
if (config.zod) {
writer.write(`.then(res => {
if (verifyZod && s) {
verifyZod(s, res, '${s.url}')
}
return res
})`);
}
writer.write(';');
},
});
if (s.description) {
fn.addJsDoc({
description: s.description,
});
}
}
}
async writeSchema(namespace, sf, schema, newSchemaName, oldSchemaName) {
let alias = 'any';
let required = true;
let ns = namespace;
let subNs = '';
if (ns.includes('.')) {
[ns, subNs] = namespace.split('.');
}
if (schema) {
alias = newSchemaName;
const type = schemaTypeToJsType(schema.schema?.type);
if (isSimpleType(type)) {
alias = type;
if (oldSchemaName) {
if (!schema.schema.required?.includes(oldSchemaName)) {
required = false;
}
}
}
else {
const code = await Writer.schemaToRenameInterface(schema.schema, alias);
let module = sf.getModule(ns) ||
sf.addModule({
name: ns,
isExported: true,
});
module.setDeclarationKind(tsMorph.ModuleDeclarationKind.Namespace);
if (subNs) {
let subModule = module.getModule(subNs) ||
module.addModule({
name: subNs,
isExported: true,
});
subModule.insertText(subModule.getEnd() - 1, code);
}
else {
module.insertText(module.getEnd() - 1, code);
}
}
}
if (alias === newSchemaName) {
let t = ns;
if (subNs) {
t += '.' + subNs;
}
alias = t + '.' + alias;
}
return {
alias,
required,
schema: schema,
};
}
}
process.on('message', async (data) => {
if (data === 'done') {
process.exit();
}
else {
console.log('Core processing');
const value = JSON.parse(data);
config.loadConfig(value.config);
const g = new RequestGeneratorSub(value.ast);
const code = await g.paint();
process?.send?.(code);
}
});