diff --git a/README.md b/README.md index 824c675e3..53819c880 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,9 @@ [![Code Climate][climate-image]][climate-url] [![Downloads][downloads-image]][downloads-url] +## It's a fork. Original repository: +[Link](https://github.com/ferdikoomen/openapi-typescript-codegen) + > Node.js library that generates Typescript clients based on the OpenAPI specification. ## Why? @@ -20,6 +23,7 @@ - Supports JSON and YAML files for input - Supports generation through CLI, Node.js and NPX - Supports tsc and @babel/plugin-transform-typescript +- Supports custom httpClients with methods GET, POST, PUT, DELETE ## Install @@ -37,21 +41,23 @@ $ openapi --help Usage: openapi [options] Options: - -V, --version output the version number - -i, --input OpenAPI specification, can be a path, url or string content (required) - -o, --output Output directory (required) - -c, --client HTTP client to generate [fetch, xhr, node] (default: "fetch") - --useOptions Use options instead of arguments - --useUnionTypes Use union types instead of enums - --exportCore Write core files to disk (default: true) - --exportServices Write services to disk (default: true) - --exportModels Write models to disk (default: true) - --exportSchemas Write schemas to disk (default: false) + -V, --version output the version number + -i, --input OpenAPI specification, can be a path, url or string content (required) + -o, --output Output directory (required) + -c, --client HTTP client to generate [fetch, xhr, node, httpClient] (default: "fetch") + --httpClientLibrary Library for httpClient with methods GET, POST, PUT, DELETE + --useOptions Use options instead of arguments + --useUnionTypes Use union types instead of enums + --exportCore Write core files to disk (default: true) + --exportServices Write services to disk (default: true) + --exportModels Write models to disk (default: true) + --exportSchemas Write schemas to disk (default: false) Examples $ openapi --input ./spec.json $ openapi --input ./spec.json --output ./dist $ openapi --input ./spec.json --output ./dist --client xhr + $ openapi --input ./spec.json --output ./dist --client httpClient --httpClientLibrary @myapp/XHR/HttpClient ``` diff --git a/bin/index.js b/bin/index.js index 99605b784..3b0dcbe24 100755 --- a/bin/index.js +++ b/bin/index.js @@ -12,7 +12,8 @@ program .version(pkg.version) .requiredOption('-i, --input ', 'OpenAPI specification, can be a path, url or string content (required)') .requiredOption('-o, --output ', 'Output directory (required)') - .option('-c, --client ', 'HTTP client to generate [fetch, xhr, node]', 'fetch') + .option('-c, --client ', 'HTTP client to generate [fetch, xhr, node, httpClient]', 'fetch') + .option('--httpClientLibrary ', 'Library for httpClient with methods GET, POST, PUT, DELETE', '') .option('--useOptions', 'Use options instead of arguments') .option('--useUnionTypes', 'Use union types instead of enums') .option('--exportCore ', 'Write core files to disk', true) @@ -28,6 +29,7 @@ if (OpenAPI) { input: program.input, output: program.output, httpClient: program.client, + httpClientLibrary: program.httpClientLibrary, useOptions: program.useOptions, useUnionTypes: program.useUnionTypes, exportCore: JSON.parse(program.exportCore) === true, diff --git a/package.json b/package.json index 8e866949c..1db19092e 100644 --- a/package.json +++ b/package.json @@ -1,103 +1,101 @@ { - "name": "openapi-typescript-codegen", - "version": "0.7.0", - "description": "Library that generates Typescript clients based on the OpenAPI specification.", - "author": "Ferdi Koomen", - "homepage": "https://github.com/ferdikoomen/openapi-typescript-codegen", - "repository": { - "type": "git", - "url": "git+https://github.com/ferdikoomen/openapi-typescript-codegen.git" - }, - "bugs": { - "url": "https://github.com/ferdikoomen/openapi-typescript-codegen/issues" - }, - "license": "MIT", - "keywords": [ - "openapi", - "swagger", - "codegen", - "generator", - "client", - "typescript", - "yaml", - "json", - "fetch", - "xhr", - "node" - ], - "maintainers": [ - { - "name": "Ferdi Koomen", - "email": "info@madebyferdi.com" - } - ], - "main": "dist/index.js", - "module": "dist/index.js", - "types": "types/index.d.ts", - "bin": { - "openapi": "bin/index.js" - }, - "files": [ - "bin/index.js", - "dist/index.js", - "types/index.d.ts" - ], - "scripts": { - "clean": "rimraf ./dist ./test/generated ./test/e2e/generated ./samples/generated ./coverage ./node_modules/.cache", - "build": "rollup --config --environment NODE_ENV:development", - "build:watch": "rollup --config --environment NODE_ENV:development --watch", - "release": "rollup --config --environment NODE_ENV:production", - "run": "node ./test/index.js", - "test": "jest --selectProjects UNIT", - "test:update": "jest --selectProjects UNIT --updateSnapshot", - "test:watch": "jest --selectProjects UNIT --watch", - "test:coverage": "jest --selectProjects UNIT --coverage", - "test:e2e": "jest --selectProjects E2E --runInBand", - "eslint": "eslint \"./src/**/*.ts\" \"./bin/index.js\" \"./types/index.d.ts\"", - "eslint:fix": "eslint \"./src/**/*.ts\" \"./bin/index.js\" \"./types/index.d.ts\" --fix", - "prettier": "prettier \"./src/**/*.ts\" \"./bin/index.js\" \"./types/index.d.ts\" --check", - "prettier:fix": "prettier \"./src/**/*.ts\" \"./bin/index.js\" \"./types/index.d.ts\" --write", - "prepublish": "yarn run clean && yarn run release", - "codecov": "codecov --token=66c30c23-8954-4892-bef9-fbaed0a2e42b" - }, - "dependencies": { - "camelcase": "^6.2.0", - "commander": "^6.2.0", - "handlebars": "^4.7.6", - "js-yaml": "^3.14.0", - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - }, - "devDependencies": { - "@babel/cli": "7.12.8", - "@babel/core": "7.12.9", - "@babel/preset-env": "7.12.1", - "@babel/preset-typescript": "7.12.7", - "@rollup/plugin-commonjs": "16.0.0", - "@rollup/plugin-node-resolve": "10.0.0", - "@types/express": "4.17.9", - "@types/jest": "26.0.15", - "@types/js-yaml": "3.12.5", - "@types/node": "14.14.10", - "@types/node-fetch": "2.5.7", - "@typescript-eslint/eslint-plugin": "4.8.1", - "@typescript-eslint/parser": "4.8.2", - "codecov": "3.8.1", - "eslint": "7.14.0", - "eslint-config-prettier": "6.15.0", - "eslint-plugin-prettier": "3.1.4", - "eslint-plugin-simple-import-sort": "6.0.1", - "express": "4.17.1", - "form-data": "3.0.0", - "glob": "7.1.6", - "jest": "26.6.3", - "jest-cli": "26.6.3", - "node-fetch": "2.6.1", - "prettier": "2.2.1", - "puppeteer": "5.4.1", - "rollup": "2.34.0", - "rollup-plugin-terser": "7.0.2", - "rollup-plugin-typescript2": "0.29.0", - "typescript": "4.1.2" - } + "name": "@sundved/openapi-typescript-codegen", + "version": "0.0.7", + "description": "Fork of library that generates Typescript clients based on the OpenAPI specification.", + "author": "Sundved", + "homepage": "https://github.com/Sundved/openapi-typescript-codegen", + "repository": { + "type": "git", + "url": "git+https://github.com/Sundved/openapi-typescript-codegen.git" + }, + "bugs": { + "url": "https://github.com/Sundved/openapi-typescript-codegen/issues" + }, + "license": "MIT", + "keywords": [ + "openapi", + "swagger", + "codegen", + "generator", + "client", + "typescript", + "yaml", + "json", + "fetch", + "xhr", + "node" + ], + "maintainers": [ + "Sundved " + ], + "main": "dist/index.js", + "module": "dist/index.js", + "types": "types/index.d.ts", + "bin": { + "openapi": "bin/index.js" + }, + "files": [ + "bin/index.js", + "dist/index.js", + "types/index.d.ts" + ], + "scripts": { + "clean": "rimraf ./dist ./test/generated ./test/e2e/generated ./samples/generated ./coverage ./node_modules/.cache", + "build": "rollup --config --environment NODE_ENV:development", + "build:watch": "rollup --config --environment NODE_ENV:development --watch", + "release": "rollup --config --environment NODE_ENV:production", + "run": "node ./test/index.js", + "test": "jest --selectProjects UNIT", + "test:update": "jest --selectProjects UNIT --updateSnapshot", + "test:watch": "jest --selectProjects UNIT --watch", + "test:coverage": "jest --selectProjects UNIT --coverage", + "test:e2e": "jest --selectProjects E2E --runInBand", + "eslint": "eslint \"./src/**/*.ts\" \"./bin/index.js\" \"./types/index.d.ts\"", + "eslint:fix": "eslint \"./src/**/*.ts\" \"./bin/index.js\" \"./types/index.d.ts\" --fix", + "prettier": "prettier \"./src/**/*.ts\" \"./bin/index.js\" \"./types/index.d.ts\" --check", + "prettier:fix": "prettier \"./src/**/*.ts\" \"./bin/index.js\" \"./types/index.d.ts\" --write", + "prepublish": "yarn run clean && yarn run release" + }, + "dependencies": { + "camelcase": "^6.2.0", + "commander": "^6.2.0", + "handlebars": "^4.7.6", + "js-yaml": "^3.14.0", + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "devDependencies": { + "@babel/cli": "7.12.8", + "@babel/core": "7.12.9", + "@babel/preset-env": "7.12.1", + "@babel/preset-typescript": "7.12.7", + "@rollup/plugin-commonjs": "16.0.0", + "@rollup/plugin-node-resolve": "10.0.0", + "@types/express": "4.17.9", + "@types/jest": "26.0.15", + "@types/js-yaml": "3.12.5", + "@types/node": "14.14.10", + "@types/node-fetch": "2.5.7", + "@typescript-eslint/eslint-plugin": "4.8.1", + "@typescript-eslint/parser": "4.8.2", + "eslint": "7.14.0", + "eslint-config-prettier": "6.15.0", + "eslint-plugin-prettier": "3.1.4", + "eslint-plugin-simple-import-sort": "6.0.1", + "express": "4.17.1", + "form-data": "3.0.0", + "glob": "7.1.6", + "jest": "26.6.3", + "jest-cli": "26.6.3", + "node-fetch": "2.6.1", + "prettier": "2.2.1", + "puppeteer": "5.4.1", + "rollup": "2.34.0", + "rollup-plugin-terser": "7.0.2", + "rollup-plugin-typescript2": "0.29.0", + "typescript": "4.1.2" + }, + "directories": { + "test": "test" + } } diff --git a/src/index.ts b/src/index.ts index c098715c4..f47a33e2c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,6 +11,7 @@ export enum HttpClient { FETCH = 'fetch', XHR = 'xhr', NODE = 'node', + HTTP_CLIENT = 'httpClient', } export type Options = { @@ -23,6 +24,7 @@ export type Options = { exportServices?: boolean; exportModels?: boolean; exportSchemas?: boolean; + httpClientLibrary?: string; write?: boolean; }; @@ -39,6 +41,7 @@ export type Options = { * @param exportServices: Generate services * @param exportModels: Generate models * @param exportSchemas: Generate schemas + * @param httpClientLibrary Library for httpClient * @param write Write the files to disk (true or false) */ export async function generate({ @@ -51,6 +54,7 @@ export async function generate({ exportServices = true, exportModels = true, exportSchemas = false, + httpClientLibrary = '', write = true, }: Options): Promise { const openApi = isString(input) ? await getOpenApiSpec(input) : input; @@ -62,7 +66,7 @@ export async function generate({ const client = parseV2(openApi); const clientFinal = postProcessClient(client); if (!write) break; - await writeClient(clientFinal, templates, output, httpClient, useOptions, useUnionTypes, exportCore, exportServices, exportModels, exportSchemas); + await writeClient(clientFinal, templates, output, httpClient, useOptions, useUnionTypes, exportCore, exportServices, exportModels, exportSchemas, httpClientLibrary); break; } @@ -70,7 +74,7 @@ export async function generate({ const client = parseV3(openApi); const clientFinal = postProcessClient(client); if (!write) break; - await writeClient(clientFinal, templates, output, httpClient, useOptions, useUnionTypes, exportCore, exportServices, exportModels, exportSchemas); + await writeClient(clientFinal, templates, output, httpClient, useOptions, useUnionTypes, exportCore, exportServices, exportModels, exportSchemas, httpClientLibrary); break; } } diff --git a/src/templates/core/ApiRequestOptions.hbs b/src/templates/core/ApiRequestOptions.hbs index dc33aac89..fe2f6863f 100644 --- a/src/templates/core/ApiRequestOptions.hbs +++ b/src/templates/core/ApiRequestOptions.hbs @@ -9,5 +9,5 @@ export type ApiRequestOptions = { readonly formData?: Record; readonly body?: any; readonly responseHeader?: string; - readonly errors?: Record; + readonly errors?: Record; } diff --git a/src/templates/core/functions/getUrl.hbs b/src/templates/core/functions/getUrl.hbs index be040bbb6..0cb791a86 100644 --- a/src/templates/core/functions/getUrl.hbs +++ b/src/templates/core/functions/getUrl.hbs @@ -3,7 +3,7 @@ function getUrl(options: ApiRequestOptions): string { const url = `${OpenAPI.BASE}${path}`; if (options.query) { - return `${url}${getQueryString(options.query)}`; + return `${url}${getQueryString({...options.query, ts: new Date().getTime()})}`; } return url; } diff --git a/src/templates/core/httpClient/getRequestBody.hbs b/src/templates/core/httpClient/getRequestBody.hbs new file mode 100644 index 000000000..09df2d0be --- /dev/null +++ b/src/templates/core/httpClient/getRequestBody.hbs @@ -0,0 +1,13 @@ +function getRequestBody(options: ApiRequestOptions): BodyInit | undefined { + if (options.formData) { + return getFormData(options.formData); + } + if (options.body) { + if (isString(options.body) || isBlob(options.body)) { + return options.body; + } else { + return JSON.stringify(options.body); + } + } + return undefined; +} diff --git a/src/templates/core/httpClient/request.hbs b/src/templates/core/httpClient/request.hbs new file mode 100644 index 000000000..c3ef82bd2 --- /dev/null +++ b/src/templates/core/httpClient/request.hbs @@ -0,0 +1,40 @@ +{{>header}} + +import { DELETE, GET, POST, PUT } from '{{{httpClientLibrary}}}'; +import type { ApiRequestOptions } from './ApiRequestOptions'; +import { OpenAPI } from './OpenAPI'; + +{{>functions/isDefined}} + + +{{>functions/isBlob}} + + +{{>functions/isString}} + + +{{>functions/getFormData}} + + +{{>functions/getQueryString}} + + +{{>functions/getUrl}} + + +{{>httpClient/getRequestBody}} + + +{{>httpClient/sendRequest}} + + +/** + * Request using XHR client + * @param options The request options from the the service + * @result ApiResult + */ +export function request(options: ApiRequestOptions): Promise { + const url = getUrl(options); + + return sendRequest(options, url); +} diff --git a/src/templates/core/httpClient/sendRequest.hbs b/src/templates/core/httpClient/sendRequest.hbs new file mode 100644 index 000000000..9311346c0 --- /dev/null +++ b/src/templates/core/httpClient/sendRequest.hbs @@ -0,0 +1,19 @@ +async function sendRequest(options: ApiRequestOptions, url: string): Promise { + let body; + switch (options.method) { + case 'GET': + body = await GET(url); + break; + case 'POST': + body = await POST(url, getRequestBody(options)); + break; + case 'PUT': + body = await PUT(url, getRequestBody(options)); + break; + case 'DELETE': + body = await DELETE(url, getRequestBody(options)); + break; + } + + return {body}; +} diff --git a/src/templates/core/request.hbs b/src/templates/core/request.hbs index 2a32097e5..0f7770acd 100644 --- a/src/templates/core/request.hbs +++ b/src/templates/core/request.hbs @@ -1,3 +1,4 @@ {{#equals @root.httpClient 'fetch'}}{{>fetch/request}}{{/equals}} {{#equals @root.httpClient 'xhr'}}{{>xhr/request}}{{/equals}} {{#equals @root.httpClient 'node'}}{{>node/request}}{{/equals}} +{{#equals @root.httpClient 'httpClient'}}{{>httpClient/request}}{{/equals}} diff --git a/src/utils/registerHandlebarTemplates.ts b/src/utils/registerHandlebarTemplates.ts index 6772eebd5..70fc2b7f1 100644 --- a/src/utils/registerHandlebarTemplates.ts +++ b/src/utils/registerHandlebarTemplates.ts @@ -20,6 +20,9 @@ import functionIsString from '../templates/core/functions/isString.hbs'; import functionIsStringWithValue from '../templates/core/functions/isStringWithValue.hbs'; import functionIsSuccess from '../templates/core/functions/isSuccess.hbs'; import functionResolve from '../templates/core/functions/resolve.hbs'; +import httpClientGetRequestBody from '../templates/core/httpClient/getRequestBody.hbs'; +import httpClientRequest from '../templates/core/httpClient/request.hbs'; +import httpClientSendRequest from '../templates/core/httpClient/sendRequest.hbs'; import nodeGetHeaders from '../templates/core/node/getHeaders.hbs'; import nodeGetRequestBody from '../templates/core/node/getRequestBody.hbs'; import nodeGetResponseBody from '../templates/core/node/getResponseBody.hbs'; @@ -165,6 +168,11 @@ export function registerHandlebarTemplates(): Templates { Handlebars.registerPartial('xhr/sendRequest', Handlebars.template(xhrSendRequest)); Handlebars.registerPartial('xhr/request', Handlebars.template(xhrRequest)); + // Specific files for the httpClient client implementation + Handlebars.registerPartial('httpClient/getRequestBody', Handlebars.template(httpClientGetRequestBody)); + Handlebars.registerPartial('httpClient/sendRequest', Handlebars.template(httpClientSendRequest)); + Handlebars.registerPartial('httpClient/request', Handlebars.template(httpClientRequest)); + // Specific files for the node client implementation Handlebars.registerPartial('node/getHeaders', Handlebars.template(nodeGetHeaders)); Handlebars.registerPartial('node/getRequestBody', Handlebars.template(nodeGetRequestBody)); diff --git a/src/utils/writeClient.ts b/src/utils/writeClient.ts index 4c4cb3c25..eda177452 100644 --- a/src/utils/writeClient.ts +++ b/src/utils/writeClient.ts @@ -23,6 +23,7 @@ import { writeClientServices } from './writeClientServices'; * @param exportServices: Generate services * @param exportModels: Generate models * @param exportSchemas: Generate schemas + * @param httpClientLibrary: Library for httpClient */ export async function writeClient( client: Client, @@ -34,7 +35,8 @@ export async function writeClient( exportCore: boolean, exportServices: boolean, exportModels: boolean, - exportSchemas: boolean + exportSchemas: boolean, + httpClientLibrary: string ): Promise { const outputPath = path.resolve(process.cwd(), output); const outputPathCore = path.resolve(outputPath, 'core'); @@ -49,7 +51,7 @@ export async function writeClient( if (exportCore) { await rmdir(outputPathCore); await mkdir(outputPathCore); - await writeClientCore(client, templates, outputPathCore, httpClient); + await writeClientCore(client, templates, outputPathCore, httpClient, httpClientLibrary); } if (exportServices) { diff --git a/src/utils/writeClientCore.ts b/src/utils/writeClientCore.ts index 1022d6d6d..4b46bbc04 100644 --- a/src/utils/writeClientCore.ts +++ b/src/utils/writeClientCore.ts @@ -11,10 +11,12 @@ import { Templates } from './registerHandlebarTemplates'; * @param templates The loaded handlebar templates * @param outputPath Directory to write the generated files to * @param httpClient The selected httpClient (fetch, xhr or node) + * @param httpClientLibrary Library for httpClient */ -export async function writeClientCore(client: Client, templates: Templates, outputPath: string, httpClient: HttpClient): Promise { +export async function writeClientCore(client: Client, templates: Templates, outputPath: string, httpClient: HttpClient, httpClientLibrary: string): Promise { const context = { httpClient, + httpClientLibrary, server: client.server, version: client.version, }; diff --git a/test/__snapshots__/index.spec.js.snap b/test/__snapshots__/index.spec.js.snap index 5f230fc2f..b8ad72bba 100644 --- a/test/__snapshots__/index.spec.js.snap +++ b/test/__snapshots__/index.spec.js.snap @@ -131,7 +131,7 @@ function getUrl(options: ApiRequestOptions): string { const url = \`\${OpenAPI.BASE}\${path}\`; if (options.query) { - return \`\${url}\${getQueryString(options.query)}\`; + return \`\${url}\${getQueryString({...options.query, ts: new Date().getTime()})}\`; } return url; } @@ -284,6 +284,7 @@ export async function request(options: ApiRequestOptions): Promise { return result; } + " `; @@ -2374,7 +2375,7 @@ function getUrl(options: ApiRequestOptions): string { const url = \`\${OpenAPI.BASE}\${path}\`; if (options.query) { - return \`\${url}\${getQueryString(options.query)}\`; + return \`\${url}\${getQueryString({...options.query, ts: new Date().getTime()})}\`; } return url; } @@ -2527,6 +2528,7 @@ export async function request(options: ApiRequestOptions): Promise { return result; } + " `;