UNPKG

@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
'use strict'; 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); } });