From 63fc67330d7b4ef6a40c258954aa58dde451cd67 Mon Sep 17 00:00:00 2001 From: Zakhar Dolozhevskiy Date: Mon, 2 Nov 2020 13:37:55 +0200 Subject: [PATCH 1/4] feat: custom templates implementation DAS-224 --- .prettierrc.json | 2 +- bin/index.js | 2 + package.json | 16 ++- rollup.config.js | 20 +-- src/index.ts | 31 ++++- src/utils/registerHandlebarHelpers.ts | 2 +- src/utils/registerHandlebarPartials.ts | 66 ++++++++++ src/utils/registerHandlebarTemplates.ts | 163 ++++-------------------- src/utils/resolveHandlebarTemplate.ts | 11 ++ yarn.lock | 97 +++++++++++++- 10 files changed, 246 insertions(+), 164 deletions(-) create mode 100644 src/utils/registerHandlebarPartials.ts create mode 100644 src/utils/resolveHandlebarTemplate.ts diff --git a/.prettierrc.json b/.prettierrc.json index d1580670d..d7d68f56b 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -3,6 +3,6 @@ "singleQuote": true, "trailingComma": "es5", "arrowParens": "avoid", - "printWidth": 200, + "printWidth": 120, "tabWidth": 4 } diff --git a/bin/index.js b/bin/index.js index 99605b784..353771246 100755 --- a/bin/index.js +++ b/bin/index.js @@ -13,6 +13,7 @@ program .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('-t --templates ', 'Path to custom templates directory') .option('--useOptions', 'Use options instead of arguments') .option('--useUnionTypes', 'Use union types instead of enums') .option('--exportCore ', 'Write core files to disk', true) @@ -34,6 +35,7 @@ if (OpenAPI) { exportServices: JSON.parse(program.exportServices) === true, exportModels: JSON.parse(program.exportModels) === true, exportSchemas: JSON.parse(program.exportSchemas) === true, + customTemplatesPath: program.templates, }) .then(() => { process.exit(0); diff --git a/package.json b/package.json index 677ba698a..f385c689c 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,15 @@ { - "name": "openapi-typescript-codegen", + "name": "dh-api-client-codegen", "version": "0.5.4", "description": "NodeJS library that generates Typescript or Javascript clients based on the OpenAPI specification.", - "author": "Ferdi Koomen", - "homepage": "https://github.com/ferdikoomen/openapi-typescript-codegen", + "author": "DoclerLabs", + "homepage": "https://github.com/DoclerLabs/openapi-typescript-codegen", "repository": { "type": "git", - "url": "git+https://github.com/ferdikoomen/openapi-typescript-codegen.git" + "url": "git+https://github.com/DoclerLabs/openapi-typescript-codegen.git" }, "bugs": { - "url": "https://github.com/ferdikoomen/openapi-typescript-codegen/issues" + "url": "https://github.com/DoclerLabs/openapi-typescript-codegen/issues" }, "license": "MIT", "keywords": [ @@ -28,8 +28,8 @@ ], "maintainers": [ { - "name": "Ferdi Koomen", - "email": "info@madebyferdi.com" + "name": "DoclerLabs", + "email": "info@DoclerLabs.com" } ], "main": "dist/index.js", @@ -64,6 +64,7 @@ "dependencies": { "camelcase": "6.1.0", "commander": "6.2.0", + "fs-extra": "^9.0.1", "handlebars": "4.7.6", "js-yaml": "3.14.0", "mkdirp": "1.0.4", @@ -98,6 +99,7 @@ "prettier": "2.1.2", "puppeteer": "5.4.1", "rollup": "2.32.1", + "rollup-plugin-copy": "^3.3.0", "rollup-plugin-terser": "7.0.2", "rollup-plugin-typescript2": "0.28.0", "typescript": "4.0.5" diff --git a/rollup.config.js b/rollup.config.js index 973c75daa..832ab2539 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,5 +1,6 @@ 'use strict'; +const copy = require('rollup-plugin-copy'); const commonjs = require('@rollup/plugin-commonjs'); const { nodeResolve } = require('@rollup/plugin-node-resolve'); const { terser } = require('rollup-plugin-terser'); @@ -23,7 +24,7 @@ const handlebarsPlugin = () => ({ } return null; }, - load: (file) => { + load: file => { if (path.extname(file) === '.hbs') { const template = fs.readFileSync(file, 'utf8').toString().trim(); const templateSpec = handlebars.precompile(template, { @@ -39,7 +40,7 @@ const handlebarsPlugin = () => ({ return `export default ${templateSpec};`; } return null; - } + }, }); const getPlugins = () => { @@ -48,12 +49,13 @@ const getPlugins = () => { typescript(), nodeResolve(), commonjs(), - ] + copy({ targets: [{ src: 'src/templates/', dest: 'dist' }] }), + ]; if (process.env.NODE_ENV === 'development') { return plugins; } return [...plugins, terser()]; -} +}; module.exports = { input: './src/index.ts', @@ -61,14 +63,6 @@ module.exports = { file: './dist/index.js', format: 'cjs', }, - external: [ - 'fs', - 'os', - 'util', - 'http', - 'https', - 'handlebars/runtime', - ...external, - ], + external: ['fs', 'os', 'util', 'http', 'https', 'handlebars/runtime', ...external], plugins: getPlugins(), }; diff --git a/src/index.ts b/src/index.ts index cc20b3c8a..164546aff 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,6 +24,7 @@ export interface Options { exportModels?: boolean; exportSchemas?: boolean; write?: boolean; + customTemplatesPath?: string; } /** @@ -40,6 +41,7 @@ export interface Options { * @param exportModels: Generate models * @param exportSchemas: Generate schemas * @param write Write the files to disk (true or false) + * @param customTemplatesPath: Provide custom templates to override default set */ export async function generate({ input, @@ -52,17 +54,29 @@ export async function generate({ exportModels = true, exportSchemas = false, write = true, + customTemplatesPath, }: Options): Promise { const openApi = isString(input) ? await getOpenApiSpec(input) : input; const openApiVersion = getOpenApiVersion(openApi); - const templates = registerHandlebarTemplates(); + const templates = registerHandlebarTemplates(customTemplatesPath); switch (openApiVersion) { case OpenApiVersion.V2: { 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 + ); break; } @@ -70,7 +84,18 @@ 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 + ); break; } } diff --git a/src/utils/registerHandlebarHelpers.ts b/src/utils/registerHandlebarHelpers.ts index 991e4c7d5..53a706870 100644 --- a/src/utils/registerHandlebarHelpers.ts +++ b/src/utils/registerHandlebarHelpers.ts @@ -1,6 +1,6 @@ // @ts-nocheck -import * as Handlebars from 'handlebars/runtime'; +import * as Handlebars from 'handlebars'; export function registerHandlebarHelpers(): void { Handlebars.registerHelper('equals', function (a: string, b: string, options: Handlebars.HelperOptions): string { diff --git a/src/utils/registerHandlebarPartials.ts b/src/utils/registerHandlebarPartials.ts new file mode 100644 index 000000000..0395e96df --- /dev/null +++ b/src/utils/registerHandlebarPartials.ts @@ -0,0 +1,66 @@ +import * as Handlebars from 'handlebars'; +import { resolveHandlebarTemplate } from './resolveHandlebarTemplate'; + +export const registerHandlebarPartials = () => { + // Partials for the generations of the models, services, etc. + Handlebars.registerPartial('exportEnum', resolveHandlebarTemplate('partials/exportEnum')); + Handlebars.registerPartial('exportInterface', resolveHandlebarTemplate('partials/exportInterface')); + Handlebars.registerPartial('exportType', resolveHandlebarTemplate('partials/exportType')); + Handlebars.registerPartial('extends', resolveHandlebarTemplate('partials/extends')); + Handlebars.registerPartial('header', resolveHandlebarTemplate('partials/header')); + Handlebars.registerPartial('isNullable', resolveHandlebarTemplate('partials/isNullable')); + Handlebars.registerPartial('isReadOnly', resolveHandlebarTemplate('partials/isReadOnly')); + Handlebars.registerPartial('isRequired', resolveHandlebarTemplate('partials/isRequired')); + Handlebars.registerPartial('parameters', resolveHandlebarTemplate('partials/parameters')); + Handlebars.registerPartial('result', resolveHandlebarTemplate('partials/result')); + Handlebars.registerPartial('schema', resolveHandlebarTemplate('partials/schema')); + Handlebars.registerPartial('schemaArray', resolveHandlebarTemplate('partials/schemaArray')); + Handlebars.registerPartial('schemaDictionary', resolveHandlebarTemplate('partials/schemaDictionary')); + Handlebars.registerPartial('schemaEnum', resolveHandlebarTemplate('partials/schemaEnum')); + Handlebars.registerPartial('schemaGeneric', resolveHandlebarTemplate('partials/schemaGeneric')); + Handlebars.registerPartial('schemaInterface', resolveHandlebarTemplate('partials/schemaInterface')); + Handlebars.registerPartial('type', resolveHandlebarTemplate('partials/type')); + Handlebars.registerPartial('typeArray', resolveHandlebarTemplate('partials/typeArray')); + Handlebars.registerPartial('typeDictionary', resolveHandlebarTemplate('partials/typeDictionary')); + Handlebars.registerPartial('typeEnum', resolveHandlebarTemplate('partials/typeEnum')); + Handlebars.registerPartial('typeGeneric', resolveHandlebarTemplate('partials/typeGeneric')); + Handlebars.registerPartial('typeInterface', resolveHandlebarTemplate('partials/typeInterface')); + Handlebars.registerPartial('typeReference', resolveHandlebarTemplate('partials/typeReference')); + Handlebars.registerPartial('base', resolveHandlebarTemplate('partials/base')); + + // Generic functions used in 'request' file @see src/templates/core/request.hbs for more info + Handlebars.registerPartial('functions/catchErrors', resolveHandlebarTemplate('core/functions/catchErrors')); + Handlebars.registerPartial('functions/getFormData', resolveHandlebarTemplate('core/functions/getFormData')); + Handlebars.registerPartial('functions/getToken', resolveHandlebarTemplate('core/functions/getToken')); + Handlebars.registerPartial('functions/getQueryString', resolveHandlebarTemplate('core/functions/getQueryString')); + Handlebars.registerPartial('functions/getUrl', resolveHandlebarTemplate('core/functions/getUrl')); + Handlebars.registerPartial('functions/isBinary', resolveHandlebarTemplate('core/functions/isBinary')); + Handlebars.registerPartial('functions/isBlob', resolveHandlebarTemplate('core/functions/isBlob')); + Handlebars.registerPartial('functions/isDefined', resolveHandlebarTemplate('core/functions/isDefined')); + Handlebars.registerPartial('functions/isString', resolveHandlebarTemplate('core/functions/isString')); + Handlebars.registerPartial('functions/isSuccess', resolveHandlebarTemplate('core/functions/isSuccess')); + + // Specific files for the fetch client implementation + Handlebars.registerPartial('fetch/getHeaders', resolveHandlebarTemplate('core/fetch/getHeaders')); + Handlebars.registerPartial('fetch/getRequestBody', resolveHandlebarTemplate('core/fetch/getRequestBody')); + Handlebars.registerPartial('fetch/getResponseBody', resolveHandlebarTemplate('core/fetch/getResponseBody')); + Handlebars.registerPartial('fetch/getResponseHeader', resolveHandlebarTemplate('core/fetch/getResponseHeader')); + Handlebars.registerPartial('fetch/sendRequest', resolveHandlebarTemplate('core/fetch/sendRequest')); + Handlebars.registerPartial('fetch/request', resolveHandlebarTemplate('core/fetch/request')); + + // Specific files for the xhr client implementation + Handlebars.registerPartial('xhr/getHeaders', resolveHandlebarTemplate('core/xhr/getHeaders')); + Handlebars.registerPartial('xhr/getRequestBody', resolveHandlebarTemplate('core/xhr/getRequestBody')); + Handlebars.registerPartial('xhr/getResponseBody', resolveHandlebarTemplate('core/xhr/getResponseBody')); + Handlebars.registerPartial('xhr/getResponseHeader', resolveHandlebarTemplate('core/xhr/getResponseHeader')); + Handlebars.registerPartial('xhr/sendRequest', resolveHandlebarTemplate('core/xhr/sendRequest')); + Handlebars.registerPartial('xhr/request', resolveHandlebarTemplate('core/xhr/request')); + + // Specific files for the node client implementation + Handlebars.registerPartial('node/getHeaders', resolveHandlebarTemplate('core/node/getHeaders')); + Handlebars.registerPartial('node/getRequestBody', resolveHandlebarTemplate('core/node/getRequestBody')); + Handlebars.registerPartial('node/getResponseBody', resolveHandlebarTemplate('core/node/getResponseBody')); + Handlebars.registerPartial('node/getResponseHeader', resolveHandlebarTemplate('core/node/getResponseHeader')); + Handlebars.registerPartial('node/sendRequest', resolveHandlebarTemplate('core/node/sendRequest')); + Handlebars.registerPartial('node/request', resolveHandlebarTemplate('core/node/request')); +}; diff --git a/src/utils/registerHandlebarTemplates.ts b/src/utils/registerHandlebarTemplates.ts index e555eb1bb..48af137c3 100644 --- a/src/utils/registerHandlebarTemplates.ts +++ b/src/utils/registerHandlebarTemplates.ts @@ -1,67 +1,9 @@ -import * as Handlebars from 'handlebars/runtime'; - -import templateCoreApiError from '../templates/core/ApiError.hbs'; -import templateCoreApiRequestOptions from '../templates/core/ApiRequestOptions.hbs'; -import templateCoreApiResult from '../templates/core/ApiResult.hbs'; -import fetchGetHeaders from '../templates/core/fetch/getHeaders.hbs'; -import fetchGetRequestBody from '../templates/core/fetch/getRequestBody.hbs'; -import fetchGetResponseBody from '../templates/core/fetch/getResponseBody.hbs'; -import fetchGetResponseHeader from '../templates/core/fetch/getResponseHeader.hbs'; -import fetchRequest from '../templates/core/fetch/request.hbs'; -import fetchSendRequest from '../templates/core/fetch/sendRequest.hbs'; -import functionCatchErrors from '../templates/core/functions/catchErrors.hbs'; -import functionGetFormData from '../templates/core/functions/getFormData.hbs'; -import functionGetQueryString from '../templates/core/functions/getQueryString.hbs'; -import functionGetToken from '../templates/core/functions/getToken.hbs'; -import functionGetUrl from '../templates/core/functions/getUrl.hbs'; -import functionIsBinary from '../templates/core/functions/isBinary.hbs'; -import functionIsBlob from '../templates/core/functions/isBlob.hbs'; -import functionIsDefined from '../templates/core/functions/isDefined.hbs'; -import functionIsString from '../templates/core/functions/isString.hbs'; -import functionIsSuccess from '../templates/core/functions/isSuccess.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'; -import nodeGetResponseHeader from '../templates/core/node/getResponseHeader.hbs'; -import nodeRequest from '../templates/core/node/request.hbs'; -import nodeSendRequest from '../templates/core/node/sendRequest.hbs'; -import templateCoreSettings from '../templates/core/OpenAPI.hbs'; -import templateCoreRequest from '../templates/core/request.hbs'; -import xhrGetHeaders from '../templates/core/xhr/getHeaders.hbs'; -import xhrGetRequestBody from '../templates/core/xhr/getRequestBody.hbs'; -import xhrGetResponseBody from '../templates/core/xhr/getResponseBody.hbs'; -import xhrGetResponseHeader from '../templates/core/xhr/getResponseHeader.hbs'; -import xhrRequest from '../templates/core/xhr/request.hbs'; -import xhrSendRequest from '../templates/core/xhr/sendRequest.hbs'; -import templateExportModel from '../templates/exportModel.hbs'; -import templateExportSchema from '../templates/exportSchema.hbs'; -import templateExportService from '../templates/exportService.hbs'; -import templateIndex from '../templates/index.hbs'; -import partialBase from '../templates/partials/base.hbs'; -import partialExportEnum from '../templates/partials/exportEnum.hbs'; -import partialExportInterface from '../templates/partials/exportInterface.hbs'; -import partialExportType from '../templates/partials/exportType.hbs'; -import partialExtends from '../templates/partials/extends.hbs'; -import partialHeader from '../templates/partials/header.hbs'; -import partialIsNullable from '../templates/partials/isNullable.hbs'; -import partialIsReadOnly from '../templates/partials/isReadOnly.hbs'; -import partialIsRequired from '../templates/partials/isRequired.hbs'; -import partialParameters from '../templates/partials/parameters.hbs'; -import partialResult from '../templates/partials/result.hbs'; -import partialSchema from '../templates/partials/schema.hbs'; -import partialSchemaArray from '../templates/partials/schemaArray.hbs'; -import partialSchemaDictionary from '../templates/partials/schemaDictionary.hbs'; -import partialSchemaEnum from '../templates/partials/schemaEnum.hbs'; -import partialSchemaGeneric from '../templates/partials/schemaGeneric.hbs'; -import partialSchemaInterface from '../templates/partials/schemaInterface.hbs'; -import partialType from '../templates/partials/type.hbs'; -import partialTypeArray from '../templates/partials/typeArray.hbs'; -import partialTypeDictionary from '../templates/partials/typeDictionary.hbs'; -import partialTypeEnum from '../templates/partials/typeEnum.hbs'; -import partialTypeGeneric from '../templates/partials/typeGeneric.hbs'; -import partialTypeInterface from '../templates/partials/typeInterface.hbs'; -import partialTypeReference from '../templates/partials/typeReference.hbs'; +import * as fse from 'fs-extra'; +import * as path from 'path'; +import * as Handlebars from 'handlebars'; import { registerHandlebarHelpers } from './registerHandlebarHelpers'; +import { registerHandlebarPartials } from './registerHandlebarPartials'; +import { resolveHandlebarTemplate } from './resolveHandlebarTemplate'; export interface Templates { index: Handlebars.TemplateDelegate; @@ -80,90 +22,41 @@ export interface Templates { } /** - * Read all the Handlebar templates that we need and return on wrapper object + * Read all default and custom Handlebar templates that we need and return on wrapper object * so we can easily access the templates in out generator / write functions. */ -export function registerHandlebarTemplates(): Templates { +export function registerHandlebarTemplates(customTemplates?: string): Templates { + const templatesDefault = path.resolve(__dirname, 'templates'); + const templatesRuntime = path.resolve(__dirname, 'runtime' + process.pid); + // Take default templates to temporary runtime folder + fse.copySync(templatesDefault, templatesRuntime); + + if (customTemplates) { + // Merge custom templates provided with override of default set + fse.copySync(path.resolve(customTemplates), templatesRuntime, { overwrite: true }); + } + registerHandlebarHelpers(); + registerHandlebarPartials(); // Main templates (entry points for the files we write to disk) const templates: Templates = { - index: Handlebars.template(templateIndex), + index: resolveHandlebarTemplate('index'), exports: { - model: Handlebars.template(templateExportModel), - schema: Handlebars.template(templateExportSchema), - service: Handlebars.template(templateExportService), + model: resolveHandlebarTemplate('exportModel'), + schema: resolveHandlebarTemplate('exportSchema'), + service: resolveHandlebarTemplate('exportService'), }, core: { - settings: Handlebars.template(templateCoreSettings), - apiError: Handlebars.template(templateCoreApiError), - apiRequestOptions: Handlebars.template(templateCoreApiRequestOptions), - apiResult: Handlebars.template(templateCoreApiResult), - request: Handlebars.template(templateCoreRequest), + settings: resolveHandlebarTemplate('core/OpenAPI'), + apiError: resolveHandlebarTemplate('core/ApiError'), + apiRequestOptions: resolveHandlebarTemplate('core/ApiRequestOptions'), + apiResult: resolveHandlebarTemplate('core/ApiResult'), + request: resolveHandlebarTemplate('core/request'), }, }; - // Partials for the generations of the models, services, etc. - Handlebars.registerPartial('exportEnum', Handlebars.template(partialExportEnum)); - Handlebars.registerPartial('exportInterface', Handlebars.template(partialExportInterface)); - Handlebars.registerPartial('exportType', Handlebars.template(partialExportType)); - Handlebars.registerPartial('extends', Handlebars.template(partialExtends)); - Handlebars.registerPartial('header', Handlebars.template(partialHeader)); - Handlebars.registerPartial('isNullable', Handlebars.template(partialIsNullable)); - Handlebars.registerPartial('isReadOnly', Handlebars.template(partialIsReadOnly)); - Handlebars.registerPartial('isRequired', Handlebars.template(partialIsRequired)); - Handlebars.registerPartial('parameters', Handlebars.template(partialParameters)); - Handlebars.registerPartial('result', Handlebars.template(partialResult)); - Handlebars.registerPartial('schema', Handlebars.template(partialSchema)); - Handlebars.registerPartial('schemaArray', Handlebars.template(partialSchemaArray)); - Handlebars.registerPartial('schemaDictionary', Handlebars.template(partialSchemaDictionary)); - Handlebars.registerPartial('schemaEnum', Handlebars.template(partialSchemaEnum)); - Handlebars.registerPartial('schemaGeneric', Handlebars.template(partialSchemaGeneric)); - Handlebars.registerPartial('schemaInterface', Handlebars.template(partialSchemaInterface)); - Handlebars.registerPartial('type', Handlebars.template(partialType)); - Handlebars.registerPartial('typeArray', Handlebars.template(partialTypeArray)); - Handlebars.registerPartial('typeDictionary', Handlebars.template(partialTypeDictionary)); - Handlebars.registerPartial('typeEnum', Handlebars.template(partialTypeEnum)); - Handlebars.registerPartial('typeGeneric', Handlebars.template(partialTypeGeneric)); - Handlebars.registerPartial('typeInterface', Handlebars.template(partialTypeInterface)); - Handlebars.registerPartial('typeReference', Handlebars.template(partialTypeReference)); - Handlebars.registerPartial('base', Handlebars.template(partialBase)); - - // Generic functions used in 'request' file @see src/templates/core/request.hbs for more info - Handlebars.registerPartial('functions/catchErrors', Handlebars.template(functionCatchErrors)); - Handlebars.registerPartial('functions/getFormData', Handlebars.template(functionGetFormData)); - Handlebars.registerPartial('functions/getToken', Handlebars.template(functionGetToken)); - Handlebars.registerPartial('functions/getQueryString', Handlebars.template(functionGetQueryString)); - Handlebars.registerPartial('functions/getUrl', Handlebars.template(functionGetUrl)); - Handlebars.registerPartial('functions/isBinary', Handlebars.template(functionIsBinary)); - Handlebars.registerPartial('functions/isBlob', Handlebars.template(functionIsBlob)); - Handlebars.registerPartial('functions/isDefined', Handlebars.template(functionIsDefined)); - Handlebars.registerPartial('functions/isString', Handlebars.template(functionIsString)); - Handlebars.registerPartial('functions/isSuccess', Handlebars.template(functionIsSuccess)); - - // Specific files for the fetch client implementation - Handlebars.registerPartial('fetch/getHeaders', Handlebars.template(fetchGetHeaders)); - Handlebars.registerPartial('fetch/getRequestBody', Handlebars.template(fetchGetRequestBody)); - Handlebars.registerPartial('fetch/getResponseBody', Handlebars.template(fetchGetResponseBody)); - Handlebars.registerPartial('fetch/getResponseHeader', Handlebars.template(fetchGetResponseHeader)); - Handlebars.registerPartial('fetch/sendRequest', Handlebars.template(fetchSendRequest)); - Handlebars.registerPartial('fetch/request', Handlebars.template(fetchRequest)); - - // Specific files for the xhr client implementation - Handlebars.registerPartial('xhr/getHeaders', Handlebars.template(xhrGetHeaders)); - Handlebars.registerPartial('xhr/getRequestBody', Handlebars.template(xhrGetRequestBody)); - Handlebars.registerPartial('xhr/getResponseBody', Handlebars.template(xhrGetResponseBody)); - Handlebars.registerPartial('xhr/getResponseHeader', Handlebars.template(xhrGetResponseHeader)); - Handlebars.registerPartial('xhr/sendRequest', Handlebars.template(xhrSendRequest)); - Handlebars.registerPartial('xhr/request', Handlebars.template(xhrRequest)); - - // Specific files for the node client implementation - Handlebars.registerPartial('node/getHeaders', Handlebars.template(nodeGetHeaders)); - Handlebars.registerPartial('node/getRequestBody', Handlebars.template(nodeGetRequestBody)); - Handlebars.registerPartial('node/getResponseBody', Handlebars.template(nodeGetResponseBody)); - Handlebars.registerPartial('node/getResponseHeader', Handlebars.template(nodeGetResponseHeader)); - Handlebars.registerPartial('node/sendRequest', Handlebars.template(nodeSendRequest)); - Handlebars.registerPartial('node/request', Handlebars.template(nodeRequest)); + fse.remove(templatesRuntime); return templates; } diff --git a/src/utils/resolveHandlebarTemplate.ts b/src/utils/resolveHandlebarTemplate.ts new file mode 100644 index 000000000..a0dd9736a --- /dev/null +++ b/src/utils/resolveHandlebarTemplate.ts @@ -0,0 +1,11 @@ +import * as fse from 'fs-extra'; +import * as path from 'path'; +import * as Handlebars from 'handlebars'; + +export const resolveHandlebarTemplate = (template: string) => + Handlebars.compile( + fse + .readFileSync(path.resolve(__dirname, 'runtime' + process.pid, template + '.hbs'), 'utf8') + .toString() + .trim() + ); diff --git a/yarn.lock b/yarn.lock index 101fd1118..192e80a41 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1253,6 +1253,21 @@ "@types/qs" "*" "@types/serve-static" "*" +"@types/fs-extra@^8.0.1": + version "8.1.1" + resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-8.1.1.tgz#1e49f22d09aa46e19b51c0b013cb63d0d923a068" + integrity sha512-TcUlBem321DFQzBNuz8p0CLLKp0VvF/XH9E4KHNmgwyp4E3AfgI5cjiIVZWlbfThBop2qxFIh4+LeY6hVWWZ2w== + dependencies: + "@types/node" "*" + +"@types/glob@^7.1.1": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" + integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w== + dependencies: + "@types/minimatch" "*" + "@types/node" "*" + "@types/graceful-fs@^4.1.2": version "4.1.4" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.4.tgz#4ff9f641a7c6d1a3508ff88bc3141b152772e753" @@ -1302,6 +1317,11 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.3.tgz#c893b73721db73699943bfc3653b1deb7faa4a3a" integrity sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q== +"@types/minimatch@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" + integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== + "@types/node-fetch@2.5.7": version "2.5.7" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.7.tgz#20a2afffa882ab04d44ca786449a276f9f6bbf3c" @@ -1633,6 +1653,11 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + atob@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" @@ -2071,6 +2096,11 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +colorette@^1.1.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b" + integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw== + combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -2727,7 +2757,7 @@ fast-diff@^1.1.2: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== -fast-glob@^3.1.1: +fast-glob@^3.0.3, fast-glob@^3.1.1: version "3.2.4" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3" integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ== @@ -2893,7 +2923,7 @@ fs-constants@^1.0.0: resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== -fs-extra@8.1.0: +fs-extra@8.1.0, fs-extra@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== @@ -2902,6 +2932,16 @@ fs-extra@8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" +fs-extra@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" + integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^1.0.0" + fs-readdir-recursive@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27" @@ -3029,6 +3069,20 @@ globals@^12.1.0: dependencies: type-fest "^0.8.1" +globby@10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/globby/-/globby-10.0.1.tgz#4782c34cb75dd683351335c5829cc3420e606b22" + integrity sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A== + dependencies: + "@types/glob" "^7.1.1" + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.0.3" + glob "^7.1.3" + ignore "^5.1.1" + merge2 "^1.2.3" + slash "^3.0.0" + globby@^11.0.1: version "11.0.1" resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357" @@ -3231,7 +3285,7 @@ ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== -ignore@^5.1.4: +ignore@^5.1.1, ignore@^5.1.4: version "5.1.8" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== @@ -3444,6 +3498,11 @@ is-plain-object@^2.0.3, is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" +is-plain-object@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-3.0.1.tgz#662d92d24c0aa4302407b0d45d21f2251c85f85b" + integrity sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g== + is-potential-custom-element-name@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz#0c52e54bcca391bb2c494b21e8626d7336c6e397" @@ -4022,6 +4081,15 @@ jsonfile@^4.0.0: optionalDependencies: graceful-fs "^4.1.6" +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -4160,7 +4228,7 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -merge2@^1.3.0: +merge2@^1.2.3, merge2@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== @@ -5009,6 +5077,17 @@ rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: dependencies: glob "^7.1.3" +rollup-plugin-copy@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/rollup-plugin-copy/-/rollup-plugin-copy-3.3.0.tgz#5ba230047f86b9f703a29288f242948a5580e7b9" + integrity sha512-euDjCUSBXZa06nqnwCNADbkAcYDfzwowfZQkto9K/TFhiH+QG7I4PUsEMwM9tDgomGWJc//z7KLW8t+tZwxADA== + dependencies: + "@types/fs-extra" "^8.0.1" + colorette "^1.1.0" + fs-extra "^8.1.0" + globby "10.0.1" + is-plain-object "^3.0.0" + rollup-plugin-terser@7.0.2: version "7.0.2" resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz#e8fbba4869981b2dc35ae7e8a502d5c6c04d324d" @@ -5778,6 +5857,16 @@ universalify@^0.1.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== +universalify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d" + integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug== + +universalify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" From 190377a407b4fa7838624f64b6b5389d7772cc88 Mon Sep 17 00:00:00 2001 From: Zakhar Dolozhevskiy Date: Mon, 2 Nov 2020 15:01:58 +0200 Subject: [PATCH 2/4] fix: broken tests --- src/index.spec.ts | 8 ++++++++ src/utils/registerHandlebarHelpers.spec.ts | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/index.spec.ts b/src/index.spec.ts index d1170357a..ea6a0699a 100644 --- a/src/index.spec.ts +++ b/src/index.spec.ts @@ -1,6 +1,14 @@ +import * as fse from 'fs-extra'; +import * as path from 'path'; import * as OpenAPI from './index'; describe('index', () => { + beforeAll(() => + fse.symlinkSync(path.resolve(__dirname, 'templates'), path.resolve(__dirname, 'utils/templates'), 'dir') + ); + + afterAll(() => fse.unlinkSync(path.resolve(__dirname, 'utils/templates'))); + it('parses v2 without issues', async () => { await OpenAPI.generate({ input: './test/spec/v2.json', diff --git a/src/utils/registerHandlebarHelpers.spec.ts b/src/utils/registerHandlebarHelpers.spec.ts index e7b22ee8b..f7590bb93 100644 --- a/src/utils/registerHandlebarHelpers.spec.ts +++ b/src/utils/registerHandlebarHelpers.spec.ts @@ -1,4 +1,4 @@ -import * as Handlebars from 'handlebars/runtime'; +import * as Handlebars from 'handlebars'; import { registerHandlebarHelpers } from './registerHandlebarHelpers'; From 8e717d95afb392195661e224c753eb1b49dd443f Mon Sep 17 00:00:00 2001 From: Zakhar Dolozhevskiy Date: Mon, 2 Nov 2020 15:13:11 +0200 Subject: [PATCH 3/4] fix: mock templates folder in test env ( we need to mimic templates structure as we have it in dist folder ) --- src/index.spec.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/index.spec.ts b/src/index.spec.ts index ea6a0699a..427876b8f 100644 --- a/src/index.spec.ts +++ b/src/index.spec.ts @@ -2,13 +2,15 @@ import * as fse from 'fs-extra'; import * as path from 'path'; import * as OpenAPI from './index'; -describe('index', () => { - beforeAll(() => - fse.symlinkSync(path.resolve(__dirname, 'templates'), path.resolve(__dirname, 'utils/templates'), 'dir') - ); +beforeAll(() => { + fse.symlinkSync(path.resolve(__dirname, 'templates'), path.resolve(__dirname, 'utils/templates'), 'dir'); +}); - afterAll(() => fse.unlinkSync(path.resolve(__dirname, 'utils/templates'))); +afterAll(() => { + fse.unlinkSync(path.resolve(__dirname, 'utils/templates')); +}); +describe('index', () => { it('parses v2 without issues', async () => { await OpenAPI.generate({ input: './test/spec/v2.json', From 3aef1ad304ba228fb61c0315db2eb773f614d126 Mon Sep 17 00:00:00 2001 From: Zakhar Dolozhevskiy Date: Mon, 2 Nov 2020 16:12:44 +0200 Subject: [PATCH 4/4] test: add test case for template customisation --- src/index.spec.ts | 9 ++++ test/templates/exportService.hbs | 86 ++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 test/templates/exportService.hbs diff --git a/src/index.spec.ts b/src/index.spec.ts index 427876b8f..654ce8920 100644 --- a/src/index.spec.ts +++ b/src/index.spec.ts @@ -42,4 +42,13 @@ describe('index', () => { write: false, }); }); + + it('apply templates customisation without issues', async () => { + await OpenAPI.generate({ + input: './test/spec/v3.json', + output: './generated/v3/', + customTemplatesPath: './test/templates', + write: false, + }); + }); }); diff --git a/test/templates/exportService.hbs b/test/templates/exportService.hbs new file mode 100644 index 000000000..10cf87f77 --- /dev/null +++ b/test/templates/exportService.hbs @@ -0,0 +1,86 @@ +{{>header}} + +{{#if imports}} +{{#each imports}} +import type { {{{this}}} } from '../models/{{{this}}}'; +{{/each}} +{{/if}} +import { request as __request } from '../core/request'; +{{#if @root.useVersion}} +import { OpenAPI } from '../core/OpenAPI'; +{{/if}} + +export class {{{name}}} { + + {{#each operations}} + /** + {{#if deprecated}} + * @deprecated + {{/if}} + {{#if summary}} + * {{{summary}}} + {{/if}} + {{#if description}} + * {{{description}}} + {{/if}} + {{#if parameters}} + {{#each parameters}} + * @param {{{name}}} {{{description}}} + {{/each}} + {{/if}} + {{#each results}} + * @result {{{type}}} {{{description}}} + {{/each}} + * @throws ApiError + */ + public static async {{{name}}}({{>parameters}}): Promise<{{>result}}> { + const result = await __request({ + method: '{{{method}}}', + path: `{{{path}}}`, + {{#if parametersCookie}} + cookies: { + {{#each parametersCookie}} + '{{{prop}}}': {{{name}}}, + {{/each}} + }, + {{/if}} + {{#if parametersHeader}} + headers: { + {{#each parametersHeader}} + '{{{prop}}}': {{{name}}}, + {{/each}} + }, + {{/if}} + {{#if parametersQuery}} + query: { + {{#each parametersQuery}} + '{{{prop}}}': {{{name}}}, + {{/each}} + }, + {{/if}} + {{#if parametersForm}} + formData: { + {{#each parametersForm}} + '{{{prop}}}': {{{name}}}, + {{/each}} + }, + {{/if}} + {{#if parametersBody}} + body: {{{parametersBody.name}}}, + {{/if}} + {{#if responseHeader}} + responseHeader: '{{{responseHeader}}}', + {{/if}} + {{#if errors}} + errors: { + {{#each errors}} + {{{code}}}: `{{{description}}}`, + {{/each}} + }, + {{/if}} + }); + return result.body; + } + + {{/each}} +}