diff --git a/README.md b/README.md index b851329a8..9b4694ea3 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,9 @@ $ openapi --help --postfixServices Service name postfix (default: "Service") --postfixModels Model name postfix --request Path to custom request file + --serviceTemplate Path to custom service handlebars template to generate the service files + --clientTemplate Path to custom client handlebars template to generate the client file + --indexTemplate Path to custom index handlebars template to generate the index file -h, --help display help for command Examples diff --git a/bin/index.js b/bin/index.js index 32f2fecbc..f9bc86902 100755 --- a/bin/index.js +++ b/bin/index.js @@ -19,11 +19,16 @@ const params = program .option('--exportCore ', 'Write core files to disk', true) .option('--exportServices ', 'Write services to disk', true) .option('--exportModels ', 'Write models to disk', true) + .option('--exportClient ', 'Write main Client file to disk', true) + .option('--exportIndex ', 'Write Index to disk', true) .option('--exportSchemas ', 'Write schemas to disk', false) .option('--indent ', 'Indentation options [4, 2, tabs]', '4') .option('--postfixServices ', 'Service name postfix', 'Service') .option('--postfixModels ', 'Model name postfix') .option('--request ', 'Path to custom request file') + .option('--serviceTemplate ', 'Path to custom service handlebars template to generate the service files') + .option('--clientTemplate ', 'Path to custom client handlebars template to generate the client file') + .option('--indexTemplate ', 'Path to custom index handlebars template to generate the index file') .parse(process.argv) .opts(); @@ -40,11 +45,16 @@ if (OpenAPI) { exportCore: JSON.parse(params.exportCore) === true, exportServices: JSON.parse(params.exportServices) === true, exportModels: JSON.parse(params.exportModels) === true, + exportClient: JSON.parse(params.exportClient) === true, + exportIndex: JSON.parse(params.exportIndex) === true, exportSchemas: JSON.parse(params.exportSchemas) === true, indent: params.indent, postfixServices: params.postfixServices, postfixModels: params.postfixModels, request: params.request, + serviceTemplate: params.serviceTemplate, + clientTemplate: params.clientTemplate, + indexTemplate: params.indexTemplate, }) .then(() => { process.exit(0); diff --git a/package.json b/package.json index 47033bf3b..56b88d9be 100644 --- a/package.json +++ b/package.json @@ -121,7 +121,7 @@ "typescript": "4.9.5", "zone.js": "0.13.0" }, - "overrides" : { + "overrides": { "node-fetch": "2.6.9", "rollup": "3.20.2", "typescript": "4.9.5" diff --git a/src/index.ts b/src/index.ts index e63919085..66d33e2f8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,9 @@ import { isString } from './utils/isString'; import { postProcessClient } from './utils/postProcessClient'; import { registerHandlebarTemplates } from './utils/registerHandlebarTemplates'; import { writeClient } from './utils/writeClient'; +import { writeClientClassCustomTemplate } from './utils/writeClientCustomTemplate/clientClass'; +import { writeClientIndexCustomTemplate } from './utils/writeClientCustomTemplate/index'; +import { writeClientServicesCustomTemplate } from './utils/writeClientCustomTemplate/services'; export { HttpClient } from './HttpClient'; export { Indent } from './Indent'; @@ -22,11 +25,16 @@ export type Options = { exportCore?: boolean; exportServices?: boolean; exportModels?: boolean; + exportClient?: boolean; + exportIndex?: boolean; exportSchemas?: boolean; indent?: Indent; postfixServices?: string; postfixModels?: string; request?: string; + serviceTemplate?: string; + clientTemplate?: string; + indexTemplate?: string; write?: boolean; }; @@ -60,11 +68,16 @@ export const generate = async ({ exportCore = true, exportServices = true, exportModels = true, + exportClient = true, + exportIndex = true, exportSchemas = false, indent = Indent.SPACE_4, postfixServices = 'Service', postfixModels = '', request, + serviceTemplate, + clientTemplate, + indexTemplate, write = true, }: Options): Promise => { const openApi = isString(input) ? await getOpenApiSpec(input) : input; @@ -75,10 +88,11 @@ export const generate = async ({ useOptions, }); + let clientFinal; switch (openApiVersion) { case OpenApiVersion.V2: { const client = parseV2(openApi); - const clientFinal = postProcessClient(client); + clientFinal = postProcessClient(client); if (!write) break; await writeClient( clientFinal, @@ -90,6 +104,8 @@ export const generate = async ({ exportCore, exportServices, exportModels, + exportClient, + exportIndex, exportSchemas, indent, postfixServices, @@ -102,7 +118,7 @@ export const generate = async ({ case OpenApiVersion.V3: { const client = parseV3(openApi); - const clientFinal = postProcessClient(client); + clientFinal = postProcessClient(client); if (!write) break; await writeClient( clientFinal, @@ -114,6 +130,8 @@ export const generate = async ({ exportCore, exportServices, exportModels, + exportClient, + exportIndex, exportSchemas, indent, postfixServices, @@ -124,6 +142,55 @@ export const generate = async ({ break; } } + + if (serviceTemplate) + await writeClientServicesCustomTemplate( + clientFinal, + output, + httpClient, + useOptions, + useUnionTypes, + indent, + postfixServices, + postfixModels, + serviceTemplate, + exportClient, + exportModels, + exportSchemas, + clientName + ); + + if (clientTemplate) + await writeClientClassCustomTemplate( + clientFinal, + output, + httpClient, + useOptions, + useUnionTypes, + indent, + postfixServices, + clientTemplate, + clientName + ); + + if (indexTemplate) + await writeClientIndexCustomTemplate( + clientFinal, + output, + httpClient, + useOptions, + useUnionTypes, + indent, + postfixServices, + postfixModels, + indexTemplate, + exportCore, + exportServices, + exportModels, + exportSchemas, + exportClient, + clientName + ); }; export default { diff --git a/src/utils/registerHandlebarHelpers.ts b/src/utils/registerHandlebarHelpers.ts index 88f47c19b..841c2e63d 100644 --- a/src/utils/registerHandlebarHelpers.ts +++ b/src/utils/registerHandlebarHelpers.ts @@ -1,5 +1,5 @@ import camelCase from 'camelcase'; -import Handlebars from 'handlebars/runtime'; +import HandlebarsRuntime from 'handlebars/runtime'; import { EOL } from 'os'; import type { Enum } from '../client/interfaces/Enum'; @@ -11,7 +11,10 @@ export const registerHandlebarHelpers = (root: { httpClient: HttpClient; useOptions: boolean; useUnionTypes: boolean; + handlebars?: typeof HandlebarsRuntime; }): void => { + const Handlebars = root.handlebars || HandlebarsRuntime; + Handlebars.registerHelper('ifdef', function (this: any, ...args): string { const options = args.pop(); if (!args.every(value => !value)) { diff --git a/src/utils/registerHandlebarTemplates.ts b/src/utils/registerHandlebarTemplates.ts index bf77cbdc1..e6e883ab9 100644 --- a/src/utils/registerHandlebarTemplates.ts +++ b/src/utils/registerHandlebarTemplates.ts @@ -1,4 +1,4 @@ -import Handlebars from 'handlebars/runtime'; +import HandlebarsRuntime from 'handlebars/runtime'; import { HttpClient } from '../HttpClient'; import templateClient from '../templates/client.hbs'; @@ -113,7 +113,10 @@ export const registerHandlebarTemplates = (root: { httpClient: HttpClient; useOptions: boolean; useUnionTypes: boolean; + handlebars?: typeof HandlebarsRuntime; }): Templates => { + const Handlebars = root.handlebars || HandlebarsRuntime; + registerHandlebarHelpers(root); // Main templates (entry points for the files we write to disk) diff --git a/src/utils/writeClient.spec.ts b/src/utils/writeClient.spec.ts index 3c06a95a5..bbf32df10 100644 --- a/src/utils/writeClient.spec.ts +++ b/src/utils/writeClient.spec.ts @@ -47,6 +47,8 @@ describe('writeClient', () => { true, true, true, + true, + true, Indent.SPACE_4, 'Service', 'AppClient' diff --git a/src/utils/writeClient.ts b/src/utils/writeClient.ts index cea2f3d88..1f57b10d1 100644 --- a/src/utils/writeClient.ts +++ b/src/utils/writeClient.ts @@ -43,6 +43,8 @@ export const writeClient = async ( exportCore: boolean, exportServices: boolean, exportModels: boolean, + exportClient: boolean, + exportIndex: boolean, exportSchemas: boolean, indent: Indent, postfixServices: string, @@ -94,12 +96,12 @@ export const writeClient = async ( await writeClientModels(client.models, templates, outputPathModels, httpClient, useUnionTypes, indent); } - if (isDefined(clientName)) { + if (isDefined(clientName) && exportClient) { await mkdir(outputPath); await writeClientClass(client, templates, outputPath, httpClient, clientName, indent, postfixServices); } - if (exportCore || exportServices || exportSchemas || exportModels) { + if ((exportCore || exportServices || exportSchemas || exportModels) && exportIndex) { await mkdir(outputPath); await writeClientIndex( client, diff --git a/src/utils/writeClientCustomTemplate/clientClass.ts b/src/utils/writeClientCustomTemplate/clientClass.ts new file mode 100644 index 000000000..5b7188961 --- /dev/null +++ b/src/utils/writeClientCustomTemplate/clientClass.ts @@ -0,0 +1,54 @@ +import { readFile, remove } from 'fs-extra'; +import Handlebars from 'handlebars'; +import { resolve } from 'path'; + +import { Client } from '../../client/interfaces/Client'; +import { HttpClient } from '../../HttpClient'; +import { Indent } from '../../Indent'; +import { writeFile } from '../fileSystem'; +import { formatCode as f } from '../formatCode'; +import { formatIndentation as i } from '../formatIndentation'; +import { getHttpRequestName } from '../getHttpRequestName.js'; +import { registerHandlebarTemplates } from '../registerHandlebarTemplates'; +import { sortModelsByName } from '../sortModelsByName.js'; +import { sortServicesByName } from '../sortServicesByName.js'; + +export const writeClientClassCustomTemplate = async ( + client: Client, + outputPath: string, + httpClient: HttpClient, + useOptions: boolean, + useUnionTypes: boolean, + indent: Indent, + postfix: string, + templatePath: string, + clientName?: string +) => { + registerHandlebarTemplates({ + httpClient, + useUnionTypes, + useOptions, + handlebars: Handlebars, // since we're not using precompiled templates, we need a different object here + }); + Handlebars.registerHelper('capitalize', str => { + return str.charAt(0).toUpperCase() + str.slice(1); + }); + + const clientClassTemplate = Handlebars.compile(await readFile(templatePath, 'utf8')); + + const clientClassFile = resolve(outputPath, `${clientName}.ts`); + await remove(clientClassFile); + + const templateResult = clientClassTemplate({ + clientName, + httpClient, + postfix, + server: client.server, + version: client.version, + models: sortModelsByName(client.models), + services: sortServicesByName(client.services), + httpRequest: getHttpRequestName(httpClient), + }); + + await writeFile(resolve(outputPath, `${clientName}.ts`), i(f(templateResult), indent)); +}; diff --git a/src/utils/writeClientCustomTemplate/index.ts b/src/utils/writeClientCustomTemplate/index.ts new file mode 100644 index 000000000..3a18a5639 --- /dev/null +++ b/src/utils/writeClientCustomTemplate/index.ts @@ -0,0 +1,65 @@ +import { readFile, remove } from 'fs-extra'; +import Handlebars from 'handlebars'; +import { resolve } from 'path'; + +import { Client } from '../../client/interfaces/Client'; +import { HttpClient } from '../../HttpClient'; +import { Indent } from '../../Indent'; +import { writeFile } from '../fileSystem'; +import { formatCode as f } from '../formatCode'; +import { formatIndentation as i } from '../formatIndentation'; +import { isDefined } from '../isDefined.js'; +import { registerHandlebarTemplates } from '../registerHandlebarTemplates'; +import { sortModelsByName } from '../sortModelsByName.js'; +import { sortServicesByName } from '../sortServicesByName.js'; + +export const writeClientIndexCustomTemplate = async ( + client: Client, + outputPath: string, + httpClient: HttpClient, + useOptions: boolean, + useUnionTypes: boolean, + indent: Indent, + postfixServices: string, + postfixModels: string, + templatePath: string, + exportCore: boolean, + exportServices: boolean, + exportModels: boolean, + exportSchemas: boolean, + exportClient: boolean, + clientName?: string +) => { + registerHandlebarTemplates({ + httpClient, + useUnionTypes, + useOptions, + handlebars: Handlebars, // since we're not using precompiled templates, we need a different object here + }); + Handlebars.registerHelper('capitalize', str => { + return str.charAt(0).toUpperCase() + str.slice(1); + }); + + const indexTemplate = Handlebars.compile(await readFile(templatePath, 'utf8')); + + const dir = resolve(outputPath, 'index.ts'); + await remove(dir); + + const templateResult = indexTemplate({ + serviceBaseUrl: client.server, + exportCore, + exportServices, + exportModels, + exportSchemas, + useUnionTypes, + postfixServices, + postfixModels, + clientName, + server: client.server, + version: client.version, + models: sortModelsByName(client.models), + services: sortServicesByName(client.services), + exportClient: isDefined(clientName) && exportClient, + }); + await writeFile(resolve(outputPath, 'index.ts'), i(f(templateResult), indent)); +}; diff --git a/src/utils/writeClientCustomTemplate/services.ts b/src/utils/writeClientCustomTemplate/services.ts new file mode 100644 index 000000000..441ba8f06 --- /dev/null +++ b/src/utils/writeClientCustomTemplate/services.ts @@ -0,0 +1,61 @@ +import { mkdir, readFile, remove } from 'fs-extra'; +import Handlebars from 'handlebars'; +import { resolve } from 'path'; + +import { Client } from '../../client/interfaces/Client'; +import { HttpClient } from '../../HttpClient'; +import { Indent } from '../../Indent'; +import { writeFile } from '../fileSystem'; +import { formatCode as f } from '../formatCode'; +import { formatIndentation as i } from '../formatIndentation'; +import { isDefined } from '../isDefined.js'; +import { registerHandlebarTemplates } from '../registerHandlebarTemplates'; + +export const writeClientServicesCustomTemplate = async ( + client: Client, + outputPath: string, + httpClient: HttpClient, + useOptions: boolean, + useUnionTypes: boolean, + indent: Indent, + postfixServices: string, + postfixModels: string, + templatePath: string, + exportClient: boolean, + exportModels: boolean, + exportSchemas: boolean, + clientName?: string +) => { + registerHandlebarTemplates({ + httpClient, + useUnionTypes, + useOptions, + handlebars: Handlebars, // since we're not using precompiled templates, we need a different object here + }); + Handlebars.registerHelper('capitalize', str => { + return str.charAt(0).toUpperCase() + str.slice(1); + }); + + const serviceTemplate = Handlebars.compile(await readFile(templatePath, 'utf8')); + + const servicesDir = resolve(outputPath, 'services'); + await remove(servicesDir); + await mkdir(servicesDir); + + for (const service of client.services) { + const file = resolve(outputPath, `services/${service.name}${postfixServices}.ts`); + const templateResult = serviceTemplate({ + ...service, + serviceBaseUrl: client.server, + httpClient, + useUnionTypes, + useOptions, + postfixServices, + postfixModels, + exportClient: isDefined(clientName) && exportClient, + exportModels, + exportSchemas, + }); + await writeFile(file, i(f(templateResult), indent)); + } +}; diff --git a/types/index.d.ts b/types/index.d.ts index e2b5247e0..0aecb81cb 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -22,11 +22,16 @@ export type Options = { exportCore?: boolean; exportServices?: boolean; exportModels?: boolean; + exportClient?: boolean; + exportIndex?: boolean; exportSchemas?: boolean; indent?: Indent | '4' | '2' | 'tab'; postfixServices?: string; postfixModels?: string; request?: string; + serviceTemplate?: string; + clientTemplate?: string; + indexTemplate?: string; write?: boolean; };