openapi-gen-typescript
Advanced tools
Comparing version 0.1.0 to 0.1.2
@@ -1,11 +0,4 @@ | ||
import { OpenAPI, OpenAPIV3 } from "openapi-types"; | ||
import { OpenAPI, OpenAPIV3 } from 'openapi-types'; | ||
import { ETemplateCode } from './constants'; | ||
import OperationObject = OpenAPIV3.OperationObject; | ||
declare enum ETemplateCode { | ||
RequestQueryCode = "requestQueryCode", | ||
RequestHeaderCode = "requestHeaderCode", | ||
RequestCookieCode = "requestCookieCode", | ||
RequestBodyCode = "requestBodyCode", | ||
ResponsesCode = "responsesCode", | ||
RequestFuncTypeCode = "requestFuncTypeCode" | ||
} | ||
declare type PostScriptReturnType = { | ||
@@ -12,0 +5,0 @@ [key in ETemplateCode]: string; |
@@ -34,11 +34,3 @@ "use strict"; | ||
const util_1 = require("./util"); | ||
var ETemplateCode; | ||
(function (ETemplateCode) { | ||
ETemplateCode["RequestQueryCode"] = "requestQueryCode"; | ||
ETemplateCode["RequestHeaderCode"] = "requestHeaderCode"; | ||
ETemplateCode["RequestCookieCode"] = "requestCookieCode"; | ||
ETemplateCode["RequestBodyCode"] = "requestBodyCode"; | ||
ETemplateCode["ResponsesCode"] = "responsesCode"; | ||
ETemplateCode["RequestFuncTypeCode"] = "requestFuncTypeCode"; | ||
})(ETemplateCode || (ETemplateCode = {})); | ||
const constants_1 = require("./constants"); | ||
function getCamelcase(urlPath, options) { | ||
@@ -61,6 +53,6 @@ return camelcase(urlPath.split('/').join('_'), options); | ||
} | ||
const bodyCode = (yield Promise.all(Object.keys(parameters).map((parameterName) => { | ||
const bodyCode = yield Promise.all(Object.keys(parameters).map(parameterName => { | ||
return getCodeFromParameter(parameters[parameterName], parameterName); | ||
}))).join('\n'); | ||
return `${exportKey ? 'export' : ''} interface ${name} {\n${bodyCode}\n}`; | ||
})); | ||
return `${exportKey ? 'export' : ''} interface ${name} {\n${bodyCode.join('\n')}\n}`; | ||
}); | ||
@@ -73,36 +65,63 @@ } | ||
} | ||
return (yield Promise.all(Object.keys(content).map((mediaType, index) => __awaiter(this, void 0, void 0, function* () { | ||
const contentCode = yield Promise.all(Object.keys(content).map((mediaType, index) => __awaiter(this, void 0, void 0, function* () { | ||
const responseTypeName = `${typeNamePrefix}${index > 0 ? getCamelcase(mediaType, { pascalCase: true }) : ''}`; | ||
responseTypeNames.push(responseTypeName); | ||
return `export type ${responseTypeName} = ${transform_1.transform(content[mediaType].schema)}`; | ||
})))).join('\n'); | ||
let jsonSchema = transform_1.transform(content[mediaType].schema); | ||
if (jsonSchema.includes('[]')) { | ||
jsonSchema = jsonSchema.replace(/[\(\)\[\]]+/g, ''); | ||
responseTypeNames.push(`${responseTypeName}[]`); | ||
} | ||
else { | ||
responseTypeNames.push(responseTypeName); | ||
} | ||
return `export type ${responseTypeName} = ${jsonSchema}`; | ||
}))); | ||
return contentCode.join('\n'); | ||
}); | ||
} | ||
function getTagWithPaths(allTags, pathsMap) { | ||
const commonTag = { | ||
name: 'common', | ||
description: 'common tag', | ||
}; | ||
const customTags = allTags ? allTags.concat([commonTag]) : [commonTag]; | ||
const commonfilterPaths = Object.keys(pathsMap).filter(namespaceName => { | ||
let filter = true; | ||
customTags.forEach(currTag => { | ||
if (pathsMap[namespaceName].tags.includes(currTag.name)) { | ||
filter = false; | ||
} | ||
}); | ||
return filter; | ||
}); | ||
return customTags.map(currTag => { | ||
const pathsInCurrTag = {}; | ||
let filterPaths = Object.keys(pathsMap).filter(namespaceName => pathsMap[namespaceName].tags.includes(currTag.name)); | ||
if (currTag.name === 'common') { | ||
filterPaths = filterPaths.concat(commonfilterPaths); | ||
} | ||
filterPaths.map(namespaceName => { | ||
pathsInCurrTag[namespaceName] = pathsMap[namespaceName]; | ||
}); | ||
return Object.assign(Object.assign({}, currTag), { pathsInCurrTag }); | ||
}); | ||
} | ||
function gen(options) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const { url, path: filePath, version, object, fetchModuleFile = `${__dirname}/defaultFetch.ts`, outputDir, pascalCase = true, } = options; | ||
const fetchModuleImportCode = `import fetchImpl from '${path.relative(outputDir, fetchModuleFile).replace(/\.ts$/, '')}';\n`; | ||
let openApiData; | ||
if (url) { | ||
if (url || filePath) { | ||
const { dereference, parse } = swaggerParser; | ||
const params = url || filePath; | ||
if (version === '2') { | ||
const openapi = yield swagger2openapi.convertUrl(url, { | ||
const { convertUrl, convertFile } = swagger2openapi; | ||
const openapiConvert = url ? convertUrl : convertFile; | ||
const openapi = yield openapiConvert(params, { | ||
patch: true, | ||
}); | ||
openApiData = openapi.openapi || (yield swaggerParser.dereference(openapi.openapi)); | ||
openApiData = openapi.openapi || (yield dereference(openapi.openapi)); | ||
} | ||
else { | ||
openApiData = (yield swaggerParser.parse(url)); | ||
openApiData = (yield parse(params)); | ||
} | ||
} | ||
else if (filePath) { | ||
if (version === '2') { | ||
const openapi = yield swagger2openapi.convertFile(filePath, { | ||
patch: true, | ||
}); | ||
openApiData = openapi.openapi || (yield swaggerParser.dereference(openapi.openapi)); | ||
} | ||
else { | ||
openApiData = (yield swaggerParser.parse(filePath)); | ||
} | ||
} | ||
else if (!object) { | ||
@@ -118,6 +137,7 @@ throw 'option: url or object must be specified one'; | ||
} | ||
let schemasCode = ''; | ||
const schemasTypesCode = []; | ||
const schemasClassCode = []; | ||
const { schemas } = openApiData.components || {}; | ||
if (schemas) { | ||
schemasCode = (yield Promise.all(Object.keys(schemas).map((schemaKey) => __awaiter(this, void 0, void 0, function* () { | ||
Object.keys(schemas).forEach(schemaKey => { | ||
const schemaObject = schemas[schemaKey]; | ||
@@ -127,33 +147,24 @@ if (pascalCase) { | ||
} | ||
return `export type ${schemaKey} = ${transform_1.transform(schemaObject)}`; | ||
})))).join('\n'); | ||
const transformObject = transform_1.transform(schemaObject); | ||
schemasTypesCode.push(`export type ${schemaKey} = ${transformObject}`); | ||
schemasClassCode.push(`export class ${schemaKey} ${transformObject.replace(/[()]/g, '')}\n`); | ||
}); | ||
} | ||
const { paths } = openApiData; | ||
const methods = ['get', 'post', 'options', 'put', 'delete', 'patch', 'head']; | ||
const pathsCode = (yield Promise.all(Object.keys(paths) | ||
.map((urlPath) => __awaiter(this, void 0, void 0, function* () { | ||
const { paths, tags: allTags } = openApiData; | ||
const pathsCode = []; | ||
const pathsMap = {}; | ||
yield Promise.all(Object.keys(paths).map((urlPath) => __awaiter(this, void 0, void 0, function* () { | ||
const pathsObject = paths[urlPath]; | ||
return (yield Promise.all(methods.filter(method => !!pathsObject[method]) | ||
.map((method) => __awaiter(this, void 0, void 0, function* () { | ||
const filterMethods = constants_1.AllMethods.filter(method => !!pathsObject[method]); | ||
const pathsTypesCode = []; | ||
yield Promise.all(filterMethods.map((method) => __awaiter(this, void 0, void 0, function* () { | ||
const objectElement = pathsObject[method]; | ||
const { operationId, parameters = [], requestBody = {}, responses, } = objectElement; | ||
let namespaceName = operationId || `${method.toLowerCase()}${getCamelcase(urlPath, { pascalCase: true })}`; | ||
namespaceName = camelcase(namespaceName.replace(/[^a-zA-Z0-9_]/g, ""), { pascalCase: true }); | ||
const responseTypeNames = []; | ||
const responsesCode = (yield Promise.all(Object.keys(responses) | ||
.filter(key => key !== 'default') | ||
.map((statusCode) => __awaiter(this, void 0, void 0, function* () { | ||
const responsesObjectElement = responses[statusCode]; | ||
const { $ref, content, description } = responsesObjectElement; | ||
if ($ref) { | ||
// TODO | ||
return ''; | ||
} | ||
else { | ||
// response | ||
const typeNamePrefix = `Response${camelcase(statusCode, { pascalCase: true })}`; | ||
const responseCode = yield getCodeFromContent(content, typeNamePrefix, description, responseTypeNames); | ||
return responseCode; | ||
} | ||
})))).join('\n'); | ||
const { operationId, parameters = [], requestBody = {}, responses, summary, tags, } = objectElement; | ||
let namespaceName = operationId || | ||
`${method.toLowerCase()}${getCamelcase(urlPath, { | ||
pascalCase: true, | ||
})}`; | ||
namespaceName = camelcase(namespaceName.replace(/[^a-zA-Z0-9_]/g, ''), { | ||
pascalCase: true, | ||
}); | ||
// request parameter | ||
@@ -163,3 +174,3 @@ const requestHeaders = {}; | ||
const requestQuery = {}; | ||
parameters.forEach((parameter) => { | ||
parameters.forEach(parameter => { | ||
const _a = parameter, { in: keyIn, name } = _a, otherParams = __rest(_a, ["in", "name"]); | ||
@@ -174,3 +185,3 @@ switch (keyIn) { | ||
case 'header': | ||
if (["CONTENT-TYPE", "COOKIE"].indexOf(name.toUpperCase()) === -1) { | ||
if (['CONTENT-TYPE', 'COOKIE'].indexOf(name.toUpperCase()) === -1) { | ||
requestHeaders[name] = otherParams; | ||
@@ -184,15 +195,36 @@ } | ||
const requestCookieCode = yield getCodeFromParameters(requestCookies, 'Cookie', true); | ||
const { content, required: requestBodyRequired, description: requestBodyDescription } = requestBody; | ||
// request body | ||
const { content, required: requestBodyRequired, description: requestBodyDescription, } = requestBody; | ||
const requestBodyTypeNames = []; | ||
const requestBodyCode = yield getCodeFromContent(content, `Body`, requestBodyDescription, requestBodyTypeNames); | ||
// response | ||
const responseTypeNames = []; | ||
const responsesArr = Object.keys(responses); | ||
const responsesCode = (yield Promise.all(responsesArr.map((statusCode) => __awaiter(this, void 0, void 0, function* () { | ||
const responsesObjectElement = responses[statusCode]; | ||
const { $ref, content, description } = responsesObjectElement; | ||
if ($ref) { | ||
// TODO | ||
return []; | ||
} | ||
else { | ||
// response | ||
const typeNamePrefix = `Response${camelcase(statusCode, { | ||
pascalCase: true, | ||
})}`; | ||
const responseCode = yield getCodeFromContent(content, typeNamePrefix, description, responseTypeNames); | ||
return responseCode; | ||
} | ||
})))).join('\n'); | ||
const requestFuncTypeCode = ` | ||
export async function request(options: { | ||
query: Query; | ||
body${requestBodyRequired ? '' : '?'}: ${requestBodyTypeNames.length > 0 ? requestBodyTypeNames.join('|') : 'any'}; | ||
headers?: RequestHeader; | ||
cookie?: Cookie; | ||
}, otherOptions?: any): Promise<{ body: ${responseTypeNames.length > 0 ? responseTypeNames.join('|') : 'any'} }> { | ||
return fetchImpl({...options, ...otherOptions, url: '${baseUrl}${urlPath}', method: '${method.toLowerCase()}'}); | ||
} | ||
`; | ||
export const request = async (options: { | ||
query: Query; | ||
body${requestBodyRequired ? '' : '?'}: ${requestBodyTypeNames.length > 0 ? requestBodyTypeNames.join('|') : 'any'}; | ||
headers?: RequestHeader; | ||
cookie?: Cookie; | ||
}, otherOptions?: any): Promise<{ body: ${responseTypeNames.length > 0 ? responseTypeNames.join('|') : 'any'} }> => { | ||
return fetchImpl({...options, ...otherOptions, url: '${baseUrl}${urlPath}', method: '${method.toLowerCase()}'}); | ||
}; | ||
`; | ||
const requestUrl = `export const url = \`${baseUrl}${urlPath}\``; | ||
let exportObj = { | ||
@@ -205,2 +237,3 @@ requestQueryCode, | ||
requestFuncTypeCode, | ||
requestUrl, | ||
}; | ||
@@ -211,29 +244,78 @@ if (options.handlePostScript) { | ||
} | ||
const sortList = ['requestQueryCode', 'requestHeaderCode', 'requestCookieCode', 'requestBodyCode', 'responsesCode', 'requestFuncTypeCode']; | ||
const exportArr = []; | ||
sortList.forEach(item => { | ||
constants_1.SortList.forEach(item => { | ||
exportArr.push(exportObj[item]); | ||
}); | ||
Object.keys(exportObj).forEach(item => { | ||
if (!sortList.includes(item)) { | ||
if (!constants_1.SortList.includes(item)) { | ||
exportArr.unshift(exportObj[item]); | ||
} | ||
}); | ||
return `export namespace ${namespaceName} {\n${exportArr.join('\n')} \n}`; | ||
})))) | ||
.join('\n'); | ||
})))) | ||
.join('\n'); | ||
const code = util_1.format([ | ||
`/* tslint:disable */ | ||
/** | ||
* This file was automatically generated by openapi-gen-typescript. | ||
* DO NOT MODIFY IT BY HAND. | ||
*/`, | ||
fetchModuleImportCode, | ||
`export namespace components { export namespace schemas { ${schemasCode} } } `, | ||
`export namespace Api { ${pathsCode} } `, | ||
].join('\n')); | ||
const pathsTypesArr = exportArr.map(exp => { | ||
return exp | ||
.replace(/export const request = async/, 'export type request =') | ||
.replace(/: Promise<([^>]+)>((\s|\S)+)/g, '=> Promise<$1>;'); | ||
}); | ||
pathsTypesCode.push(`export namespace ${namespaceName} {\n${pathsTypesArr.join('\n')}\n}`); | ||
const generateClassArr = exportArr.map(exp => { | ||
return exp | ||
.replace(/ interface | type = /g, ' class ') | ||
.replace(/ type ([^=]+) = components.([a-zA-Z.]+)[;{}]?/g, ' class $1 extends $2 {}'); | ||
}); | ||
pathsMap[namespaceName] = { | ||
summary, | ||
tags: tags || [], | ||
code: generateClassArr.join('\n'), | ||
}; | ||
}))); | ||
pathsCode.push(pathsTypesCode.join('\n')); | ||
}))); | ||
yield util_1.deleteFolderRecursive(outputDir); | ||
// generate code | ||
yield mkdirp(outputDir); | ||
fs.writeFileSync(`${outputDir}/index.ts`, code); | ||
const tagWithPaths = getTagWithPaths(allTags, pathsMap); | ||
yield Promise.all(tagWithPaths.map((currTag) => __awaiter(this, void 0, void 0, function* () { | ||
const currMap = currTag.pathsInCurrTag; | ||
if (Object.keys(currMap).length > 0) { | ||
const currTagNameDir = `${outputDir}/${currTag.name}`; | ||
yield mkdirp(currTagNameDir); | ||
const tagIndex = []; | ||
Object.keys(currMap).map((namespaceName) => { | ||
const { summary, code } = currMap[namespaceName]; | ||
const pathCode = [ | ||
`/** | ||
* @namespace ${namespaceName} | ||
* @summary ${summary} | ||
*/\n`, | ||
`import fetchImpl from '${path | ||
.relative(currTagNameDir, fetchModuleFile) | ||
.replace(/\.ts$/, '')}';`, | ||
`import * as schemas from '../schemas';\n`, | ||
code, | ||
].join('\n'); | ||
tagIndex.push(`export * as ${namespaceName} from './${namespaceName}';`); | ||
fs.writeFileSync(`${currTagNameDir}/${namespaceName}.ts`, util_1.format(pathCode)); | ||
}); | ||
const tagCode = [ | ||
`/** | ||
* @description ${currTag.description} | ||
*/\n`, | ||
...tagIndex, | ||
].join('\n'); | ||
fs.writeFileSync(`${currTagNameDir}/index.ts`, util_1.format(tagCode)); | ||
} | ||
}))); | ||
const typesCode = [ | ||
constants_1.NotModifyCode, | ||
`import fetchImpl from '${path.relative(outputDir, fetchModuleFile).replace(/\.ts$/, '')}';`, | ||
`export namespace components { export namespace schemas { ${schemasTypesCode.join('\n')} } } `, | ||
`export namespace Api { ${pathsCode.join('\n')} } `, | ||
].join('\n'); | ||
const schemasCode = [ | ||
constants_1.NotModifyCode, | ||
`import { components } from './index';\n`, | ||
schemasClassCode.join('\n'), | ||
].join('\n'); | ||
fs.writeFileSync(`${outputDir}/index.ts`, util_1.format(typesCode)); | ||
fs.writeFileSync(`${outputDir}/schemas.ts`, util_1.format(schemasCode)); | ||
console.info(`Generate code successful in directory: ${outputDir}`); | ||
@@ -240,0 +322,0 @@ }); |
import { Options } from 'prettier'; | ||
export declare const DEFAULT_OPTIONS: Options; | ||
export declare function format(code: string, options?: Options): string; | ||
/** | ||
* | ||
* @param {*} filePath | ||
*/ | ||
export declare function deleteFolderRecursive(filePath: string): void; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.format = exports.DEFAULT_OPTIONS = void 0; | ||
exports.deleteFolderRecursive = exports.format = exports.DEFAULT_OPTIONS = void 0; | ||
const prettier_1 = require("prettier"); | ||
const fs = require("fs"); | ||
const path = require("path"); | ||
exports.DEFAULT_OPTIONS = { | ||
@@ -12,3 +14,3 @@ bracketSpacing: false, | ||
trailingComma: 'none', | ||
useTabs: false | ||
useTabs: false, | ||
}; | ||
@@ -19,1 +21,39 @@ function format(code, options = exports.DEFAULT_OPTIONS) { | ||
exports.format = format; | ||
/** | ||
* | ||
* @param {*} filePath | ||
*/ | ||
function deleteFolderRecursive(filePath) { | ||
let files = []; | ||
/** | ||
* 判断给定的路径是否存在 | ||
*/ | ||
if (fs.existsSync(filePath)) { | ||
/** | ||
* 返回文件和子目录的数组 | ||
*/ | ||
files = fs.readdirSync(filePath); | ||
files.forEach(function (file, index) { | ||
const curPath = path.join(filePath, file); | ||
console.log(curPath); | ||
/** | ||
* fs.statSync同步读取文件夹文件,如果是文件夹,在重复触发函数 | ||
*/ | ||
if (fs.statSync(curPath).isDirectory()) { | ||
// recurse | ||
deleteFolderRecursive(curPath); | ||
} | ||
else { | ||
fs.unlinkSync(curPath); | ||
} | ||
}); | ||
/** | ||
* 清除文件夹 | ||
*/ | ||
fs.rmdirSync(filePath); | ||
} | ||
else { | ||
// console.log('给定的路径不存在,请给出正确的路径'); | ||
} | ||
} | ||
exports.deleteFolderRecursive = deleteFolderRecursive; |
{ | ||
"name": "openapi-gen-typescript", | ||
"version": "0.1.0", | ||
"version": "0.1.2", | ||
"main": "dist/index.js", | ||
@@ -19,11 +19,12 @@ "types": "dist/index.d.ts", | ||
"ts-node": "^9.0.0", | ||
"tslint": "^6.1.3", | ||
"typescript": "^4.0.3" | ||
}, | ||
"dependencies": { | ||
"openapi-types": "^7.0.1", | ||
"@apidevtools/swagger-parser": "^10.0.2", | ||
"camelcase": "^6.1.0", | ||
"mkdirp": "^1.0.4", | ||
"openapi-types": "^7.0.1", | ||
"swagger2openapi": "^7.0.3" | ||
} | ||
} | ||
} |
@@ -19,2 +19,3 @@ # openapi-gen-typescript | ||
| url | The url of fetch openapi or swagger data | | ||
| path | The filePath of fetch openapi or swagger data | | ||
| version | The version of Swagger or OpenApi, example: `2`, `3` | | ||
@@ -21,0 +22,0 @@ | outputDir | Dir of output files | |
396
src/index.ts
@@ -7,8 +7,9 @@ // @ts-ignore | ||
import * as camelcase from 'camelcase'; | ||
import { Options } from "camelcase"; | ||
import { Options } from 'camelcase'; | ||
import * as fs from 'fs'; | ||
import * as path from "path"; | ||
import { transform } from "./schemaToTypes/transform"; | ||
import { format } from "./util"; | ||
import { IJsonSchema, OpenAPI, OpenAPIV3 } from "openapi-types"; | ||
import * as path from 'path'; | ||
import { transform } from './schemaToTypes/transform'; | ||
import { format, deleteFolderRecursive } from './util'; | ||
import { IJsonSchema, OpenAPI, OpenAPIV3 } from 'openapi-types'; | ||
import { AllMethods, SortList, NotModifyCode, ETemplateCode } from './constants'; | ||
import ParameterBaseObject = OpenAPIV3.ParameterBaseObject; | ||
@@ -25,19 +26,12 @@ import MediaTypeObject = OpenAPIV3.MediaTypeObject; | ||
[media: string]: MediaTypeObject; | ||
} | ||
}; | ||
enum ETemplateCode { | ||
RequestQueryCode = 'requestQueryCode', | ||
RequestHeaderCode = 'requestHeaderCode', | ||
RequestCookieCode = 'requestCookieCode', | ||
RequestBodyCode = 'requestBodyCode', | ||
ResponsesCode = 'responsesCode', | ||
RequestFuncTypeCode = 'requestFuncTypeCode', | ||
} | ||
type PostScriptReturnType = | ||
| { | ||
[key in ETemplateCode]: string; | ||
} | ||
| { | ||
[key: string]: string; | ||
}; | ||
type PostScriptReturnType = { | ||
[key in ETemplateCode]: string; | ||
} | { | ||
[key: string]: string; | ||
} | ||
function getCamelcase(urlPath: string, options?: Options): string { | ||
@@ -53,3 +47,3 @@ return camelcase(urlPath.split('/').join('_'), options); | ||
if (description) { | ||
code += `/* ${description} */\n` | ||
code += `/* ${description} */\n`; | ||
} | ||
@@ -62,8 +56,18 @@ | ||
interface ParameterMap { | ||
interface IParameterMap { | ||
[name: string]: ParameterBaseObject; | ||
} | ||
interface IPathMapContent { | ||
summary: string | undefined; | ||
tags: string[]; | ||
code: string; | ||
} | ||
interface IPathMap { | ||
[key: string]: IPathMapContent; | ||
} | ||
async function getCodeFromParameters( | ||
parameters: ParameterMap | undefined, | ||
parameters: IParameterMap | undefined, | ||
name: string, | ||
@@ -76,6 +80,8 @@ exportKey: boolean = false, | ||
const bodyCode = (await Promise.all(Object.keys(parameters).map((parameterName) => { | ||
return getCodeFromParameter(parameters[parameterName], parameterName); | ||
}))).join('\n'); | ||
return `${exportKey ? 'export' : ''} interface ${name} {\n${bodyCode}\n}`; | ||
const bodyCode = await Promise.all( | ||
Object.keys(parameters).map(parameterName => { | ||
return getCodeFromParameter(parameters[parameterName], parameterName); | ||
}), | ||
); | ||
return `${exportKey ? 'export' : ''} interface ${name} {\n${bodyCode.join('\n')}\n}`; | ||
} | ||
@@ -93,9 +99,53 @@ | ||
return (await Promise.all(Object.keys(content).map(async (mediaType, index) => { | ||
const responseTypeName = `${typeNamePrefix}${index > 0 ? getCamelcase(mediaType, { pascalCase: true }) : ''}`; | ||
responseTypeNames.push(responseTypeName); | ||
return `export type ${responseTypeName} = ${transform((content[mediaType] as MediaTypeObject).schema as IJsonSchema)}` | ||
}))).join('\n'); | ||
const contentCode = await Promise.all( | ||
Object.keys(content).map(async (mediaType, index) => { | ||
const responseTypeName = `${typeNamePrefix}${ | ||
index > 0 ? getCamelcase(mediaType, { pascalCase: true }) : '' | ||
}`; | ||
let jsonSchema = transform((content[mediaType] as MediaTypeObject).schema as IJsonSchema); | ||
if (jsonSchema.includes('[]')) { | ||
jsonSchema = jsonSchema.replace(/[\(\)\[\]]+/g, ''); | ||
responseTypeNames.push(`${responseTypeName}[]`); | ||
} else { | ||
responseTypeNames.push(responseTypeName); | ||
} | ||
return `export type ${responseTypeName} = ${jsonSchema}`; | ||
}), | ||
); | ||
return contentCode.join('\n'); | ||
} | ||
function getTagWithPaths(allTags: OpenAPIV3.TagObject[] | undefined, pathsMap: IPathMap) { | ||
const commonTag: OpenAPIV3.TagObject = { | ||
name: 'common', | ||
description: 'common tag', | ||
}; | ||
const customTags = allTags ? allTags.concat([commonTag]) : [commonTag]; | ||
const commonfilterPaths = Object.keys(pathsMap).filter(namespaceName => { | ||
let filter = true; | ||
customTags.forEach(currTag => { | ||
if (pathsMap[namespaceName].tags.includes(currTag.name)) { | ||
filter = false; | ||
} | ||
}); | ||
return filter; | ||
}); | ||
return customTags.map(currTag => { | ||
const pathsInCurrTag: { [key: string]: any } = {}; | ||
let filterPaths = Object.keys(pathsMap).filter(namespaceName => | ||
pathsMap[namespaceName].tags.includes(currTag.name), | ||
); | ||
if (currTag.name === 'common') { | ||
filterPaths = filterPaths.concat(commonfilterPaths); | ||
} | ||
filterPaths.map(namespaceName => { | ||
pathsInCurrTag[namespaceName] = pathsMap[namespaceName]; | ||
}); | ||
return { | ||
...currTag, | ||
pathsInCurrTag, | ||
}; | ||
}); | ||
} | ||
export async function gen(options: { | ||
@@ -123,25 +173,18 @@ url?: string; | ||
const fetchModuleImportCode = `import fetchImpl from '${path.relative(outputDir, fetchModuleFile).replace(/\.ts$/, '')}';\n`; | ||
let openApiData: OpenAPIV3.Document; | ||
if (url) { | ||
if (url || filePath) { | ||
const { dereference, parse } = swaggerParser; | ||
const params: any = url || filePath; | ||
if (version === '2') { | ||
const openapi = await swagger2openapi.convertUrl(url, { | ||
const { convertUrl, convertFile } = swagger2openapi; | ||
const openapiConvert = url ? convertUrl : convertFile; | ||
const openapi = await openapiConvert(params, { | ||
patch: true, | ||
}); | ||
openApiData = openapi.openapi || await swaggerParser.dereference(openapi.openapi); | ||
openApiData = openapi.openapi || (await dereference(openapi.openapi)); | ||
} else { | ||
openApiData = await swaggerParser.parse(url) as OpenAPIV3.Document; | ||
openApiData = (await parse(params)) as OpenAPIV3.Document; | ||
} | ||
} else if (filePath) { | ||
if (version === '2') { | ||
const openapi = await swagger2openapi.convertFile(filePath, { | ||
patch: true, | ||
}); | ||
openApiData = openapi.openapi || await swaggerParser.dereference(openapi.openapi); | ||
} else { | ||
openApiData = await swaggerParser.parse(filePath) as OpenAPIV3.Document; | ||
} | ||
} else if (!object) { | ||
throw 'option: url or object must be specified one' | ||
throw 'option: url or object must be specified one'; | ||
} else { | ||
@@ -156,6 +199,7 @@ openApiData = object as OpenAPIV3.Document; | ||
let schemasCode: string = ''; | ||
const schemasTypesCode: string[] = []; | ||
const schemasClassCode: string[] = []; | ||
const { schemas } = openApiData.components || {}; | ||
if (schemas) { | ||
schemasCode = (await Promise.all(Object.keys(schemas).map(async (schemaKey) => { | ||
Object.keys(schemas).forEach(schemaKey => { | ||
const schemaObject = schemas[schemaKey] as IJsonSchema; | ||
@@ -165,13 +209,19 @@ if (pascalCase) { | ||
} | ||
return `export type ${schemaKey} = ${transform(schemaObject)}`; | ||
}))).join('\n'); | ||
const transformObject = transform(schemaObject); | ||
schemasTypesCode.push(`export type ${schemaKey} = ${transformObject}`); | ||
schemasClassCode.push(`export class ${schemaKey} ${transformObject.replace(/[()]/g, '')}\n`); | ||
}); | ||
} | ||
const { paths } = openApiData; | ||
const methods = ['get', 'post', 'options', 'put', 'delete', 'patch', 'head']; | ||
const pathsCode = (await Promise.all(Object.keys(paths) | ||
.map(async (urlPath) => { | ||
const { paths, tags: allTags } = openApiData; | ||
const pathsCode: string[] = []; | ||
const pathsMap: IPathMap = {}; | ||
await Promise.all( | ||
Object.keys(paths).map(async urlPath => { | ||
const pathsObject: PathItemObject = paths[urlPath]; | ||
return (await Promise.all(methods.filter(method => !!(pathsObject as any)[method]) | ||
.map(async (method) => { | ||
const filterMethods = AllMethods.filter(method => !!(pathsObject as any)[method]); | ||
const pathsTypesCode: string[] = []; | ||
await Promise.all( | ||
filterMethods.map(async method => { | ||
const objectElement: OperationObject = (pathsObject as any)[method] as OperationObject; | ||
@@ -183,35 +233,20 @@ const { | ||
responses, | ||
summary, | ||
tags, | ||
} = objectElement; | ||
let namespaceName = operationId || `${method.toLowerCase()}${getCamelcase(urlPath, { pascalCase: true })}`; | ||
namespaceName = camelcase(namespaceName.replace(/[^a-zA-Z0-9_]/g, ""), { pascalCase: true }); | ||
const responseTypeNames: string[] = []; | ||
const responsesCode: string = (await Promise.all(Object.keys(responses as Object) | ||
.filter(key => key !== 'default') | ||
.map(async (statusCode) => { | ||
const responsesObjectElement: ResponseObject & ReferenceObject = (responses as any)[statusCode]; | ||
const { $ref, content, description } = responsesObjectElement; | ||
let namespaceName = | ||
operationId || | ||
`${method.toLowerCase()}${getCamelcase(urlPath, { | ||
pascalCase: true, | ||
})}`; | ||
namespaceName = camelcase(namespaceName.replace(/[^a-zA-Z0-9_]/g, ''), { | ||
pascalCase: true, | ||
}); | ||
if ($ref) { | ||
// TODO | ||
return ''; | ||
} else { | ||
// response | ||
const typeNamePrefix = `Response${camelcase(statusCode, { pascalCase: true })}`; | ||
const responseCode = await getCodeFromContent( | ||
content as ContentObject, | ||
typeNamePrefix, | ||
description, | ||
responseTypeNames, | ||
); | ||
return responseCode; | ||
} | ||
}))).join('\n'); | ||
// request parameter | ||
const requestHeaders: ParameterMap = {}; | ||
const requestCookies: ParameterMap = {}; | ||
const requestQuery: ParameterMap = {}; | ||
parameters.forEach((parameter) => { | ||
const requestHeaders: IParameterMap = {}; | ||
const requestCookies: IParameterMap = {}; | ||
const requestQuery: IParameterMap = {}; | ||
parameters.forEach(parameter => { | ||
const { in: keyIn, name, ...otherParams } = parameter as ParameterObject; | ||
@@ -226,3 +261,3 @@ switch (keyIn) { | ||
case 'header': | ||
if (["CONTENT-TYPE", "COOKIE"].indexOf(name.toUpperCase()) === -1) { | ||
if (['CONTENT-TYPE', 'COOKIE'].indexOf(name.toUpperCase()) === -1) { | ||
requestHeaders[name] = otherParams; | ||
@@ -233,22 +268,73 @@ } | ||
}); | ||
const requestHeaderCode = await getCodeFromParameters(requestHeaders, 'RequestHeader', true); | ||
const requestHeaderCode = await getCodeFromParameters( | ||
requestHeaders, | ||
'RequestHeader', | ||
true, | ||
); | ||
const requestQueryCode = await getCodeFromParameters(requestQuery, 'Query', true); | ||
const requestCookieCode = await getCodeFromParameters(requestCookies, 'Cookie', true); | ||
const { content, required: requestBodyRequired, description: requestBodyDescription } = (requestBody as RequestBodyObject); | ||
// request body | ||
const { | ||
content, | ||
required: requestBodyRequired, | ||
description: requestBodyDescription, | ||
} = requestBody as RequestBodyObject; | ||
const requestBodyTypeNames: string[] = []; | ||
const requestBodyCode = await getCodeFromContent(content, `Body`, requestBodyDescription, requestBodyTypeNames); | ||
const requestBodyCode = await getCodeFromContent( | ||
content, | ||
`Body`, | ||
requestBodyDescription, | ||
requestBodyTypeNames, | ||
); | ||
// response | ||
const responseTypeNames: string[] = []; | ||
const responsesArr = Object.keys(responses as Object); | ||
const responsesCode = ( | ||
await Promise.all( | ||
responsesArr.map(async statusCode => { | ||
const responsesObjectElement: ResponseObject & ReferenceObject = (responses as any)[ | ||
statusCode | ||
]; | ||
const { $ref, content, description } = responsesObjectElement; | ||
if ($ref) { | ||
// TODO | ||
return []; | ||
} else { | ||
// response | ||
const typeNamePrefix = `Response${camelcase(statusCode, { | ||
pascalCase: true, | ||
})}`; | ||
const responseCode = await getCodeFromContent( | ||
content as ContentObject, | ||
typeNamePrefix, | ||
description, | ||
responseTypeNames, | ||
); | ||
return responseCode; | ||
} | ||
}), | ||
) | ||
).join('\n'); | ||
const requestFuncTypeCode = ` | ||
export async function request(options: { | ||
query: Query; | ||
body${requestBodyRequired ? '' : '?'}: ${requestBodyTypeNames.length > 0 ? requestBodyTypeNames.join('|') : 'any'}; | ||
headers?: RequestHeader; | ||
cookie?: Cookie; | ||
}, otherOptions?: any): Promise<{ body: ${responseTypeNames.length > 0 ? responseTypeNames.join('|') : 'any'} }> { | ||
return fetchImpl({...options, ...otherOptions, url: '${baseUrl}${urlPath}', method: '${method.toLowerCase()}'}); | ||
} | ||
`; | ||
export const request = async (options: { | ||
query: Query; | ||
body${requestBodyRequired ? '' : '?'}: ${ | ||
requestBodyTypeNames.length > 0 ? requestBodyTypeNames.join('|') : 'any' | ||
}; | ||
headers?: RequestHeader; | ||
cookie?: Cookie; | ||
}, otherOptions?: any): Promise<{ body: ${ | ||
responseTypeNames.length > 0 ? responseTypeNames.join('|') : 'any' | ||
} }> => { | ||
return fetchImpl({...options, ...otherOptions, url: '${baseUrl}${urlPath}', method: '${method.toLowerCase()}'}); | ||
}; | ||
`; | ||
const requestUrl = `export const url = \`${baseUrl}${urlPath}\``; | ||
@@ -262,3 +348,4 @@ let exportObj: { [key: string]: string } = { | ||
requestFuncTypeCode, | ||
} | ||
requestUrl, | ||
}; | ||
@@ -271,37 +358,98 @@ if (options.handlePostScript) { | ||
const sortList = ['requestQueryCode', 'requestHeaderCode', 'requestCookieCode', 'requestBodyCode', 'responsesCode', 'requestFuncTypeCode']; | ||
const exportArr: string[] = []; | ||
sortList.forEach(item => { | ||
SortList.forEach(item => { | ||
exportArr.push(exportObj[item]); | ||
}) | ||
}); | ||
Object.keys(exportObj).forEach(item => { | ||
if (!sortList.includes(item)) { | ||
if (!SortList.includes(item)) { | ||
exportArr.unshift(exportObj[item]); | ||
} | ||
}) | ||
}); | ||
return `export namespace ${namespaceName} {\n${exportArr.join('\n') | ||
} \n}`; | ||
}))) | ||
.join('\n'); | ||
}))) | ||
.join('\n'); | ||
const code = format([ | ||
`/* tslint:disable */ | ||
/** | ||
* This file was automatically generated by openapi-gen-typescript. | ||
* DO NOT MODIFY IT BY HAND. | ||
*/`, | ||
fetchModuleImportCode, | ||
`export namespace components { export namespace schemas { ${schemasCode} } } `, | ||
`export namespace Api { ${pathsCode} } `, | ||
].join('\n')); | ||
const pathsTypesArr = exportArr.map(exp => { | ||
return exp | ||
.replace(/export const request = async/, 'export type request =') | ||
.replace(/: Promise<([^>]+)>((\s|\S)+)/g, '=> Promise<$1>;'); | ||
}); | ||
pathsTypesCode.push( | ||
`export namespace ${namespaceName} {\n${pathsTypesArr.join('\n')}\n}`, | ||
); | ||
const generateClassArr = exportArr.map(exp => { | ||
return exp | ||
.replace(/ interface | type = /g, ' class ') | ||
.replace(/ type ([^=]+) = components.([a-zA-Z.]+)[;{}]?/g, ' class $1 extends $2 {}'); | ||
}); | ||
pathsMap[namespaceName] = { | ||
summary, | ||
tags: tags || [], | ||
code: generateClassArr.join('\n'), | ||
}; | ||
}), | ||
); | ||
pathsCode.push(pathsTypesCode.join('\n')); | ||
}), | ||
); | ||
await deleteFolderRecursive(outputDir); | ||
// generate code | ||
await mkdirp(outputDir); | ||
fs.writeFileSync(`${outputDir}/index.ts`, code); | ||
const tagWithPaths = getTagWithPaths(allTags, pathsMap); | ||
await Promise.all( | ||
tagWithPaths.map(async currTag => { | ||
const currMap = currTag.pathsInCurrTag; | ||
if (Object.keys(currMap).length > 0) { | ||
const currTagNameDir = `${outputDir}/${currTag.name}`; | ||
await mkdirp(currTagNameDir); | ||
const tagIndex: string[] = []; | ||
Object.keys(currMap).map((namespaceName: string) => { | ||
const { summary, code } = currMap[namespaceName]; | ||
const pathCode = [ | ||
`/** | ||
* @namespace ${namespaceName} | ||
* @summary ${summary} | ||
*/\n`, | ||
`import fetchImpl from '${path | ||
.relative(currTagNameDir, fetchModuleFile) | ||
.replace(/\.ts$/, '')}';`, | ||
`import * as schemas from '../schemas';\n`, | ||
code, | ||
].join('\n'); | ||
tagIndex.push(`export * as ${namespaceName} from './${namespaceName}';`); | ||
fs.writeFileSync(`${currTagNameDir}/${namespaceName}.ts`, format(pathCode)); | ||
}); | ||
const tagCode = [ | ||
`/** | ||
* @description ${currTag.description} | ||
*/\n`, | ||
...tagIndex, | ||
].join('\n'); | ||
fs.writeFileSync(`${currTagNameDir}/index.ts`, format(tagCode)); | ||
} | ||
}), | ||
); | ||
const typesCode = [ | ||
NotModifyCode, | ||
`import fetchImpl from '${path.relative(outputDir, fetchModuleFile).replace(/\.ts$/, '')}';`, | ||
`export namespace components { export namespace schemas { ${schemasTypesCode.join('\n')} } } `, | ||
`export namespace Api { ${pathsCode.join('\n')} } `, | ||
].join('\n'); | ||
const schemasCode = [ | ||
NotModifyCode, | ||
`import { components } from './index';\n`, | ||
schemasClassCode.join('\n'), | ||
].join('\n'); | ||
fs.writeFileSync(`${outputDir}/index.ts`, format(typesCode)); | ||
fs.writeFileSync(`${outputDir}/schemas.ts`, format(schemasCode)); | ||
console.info(`Generate code successful in directory: ${outputDir}`); | ||
} |
@@ -1,2 +0,4 @@ | ||
import { format as prettify, Options } from 'prettier' | ||
import { format as prettify, Options } from 'prettier'; | ||
import * as fs from 'fs'; | ||
import * as path from 'path'; | ||
@@ -10,7 +12,43 @@ export const DEFAULT_OPTIONS: Options = { | ||
trailingComma: 'none', | ||
useTabs: false | ||
useTabs: false, | ||
}; | ||
export function format(code: string, options: Options = DEFAULT_OPTIONS): string { | ||
return prettify(code, { parser: 'typescript', ...options }) | ||
return prettify(code, { parser: 'typescript', ...options }); | ||
} | ||
/** | ||
* | ||
* @param {*} filePath | ||
*/ | ||
export function deleteFolderRecursive(filePath: string) { | ||
let files = []; | ||
/** | ||
* 判断给定的路径是否存在 | ||
*/ | ||
if (fs.existsSync(filePath)) { | ||
/** | ||
* 返回文件和子目录的数组 | ||
*/ | ||
files = fs.readdirSync(filePath); | ||
files.forEach(function (file, index) { | ||
const curPath = path.join(filePath, file); | ||
console.log(curPath); | ||
/** | ||
* fs.statSync同步读取文件夹文件,如果是文件夹,在重复触发函数 | ||
*/ | ||
if (fs.statSync(curPath).isDirectory()) { | ||
// recurse | ||
deleteFolderRecursive(curPath); | ||
} else { | ||
fs.unlinkSync(curPath); | ||
} | ||
}); | ||
/** | ||
* 清除文件夹 | ||
*/ | ||
fs.rmdirSync(filePath); | ||
} else { | ||
// console.log('给定的路径不存在,请给出正确的路径'); | ||
} | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
63898
25
1368
26
7
3