diff --git a/.changeset/dirty-sheep-dress.md b/.changeset/dirty-sheep-dress.md new file mode 100644 index 000000000..16ae3306e --- /dev/null +++ b/.changeset/dirty-sheep-dress.md @@ -0,0 +1,5 @@ +--- +"@hey-api/docs": patch +--- + +docs: add format and lint migration for 0.44.0 diff --git a/.changeset/grumpy-bikes-rhyme.md b/.changeset/grumpy-bikes-rhyme.md new file mode 100644 index 000000000..222f67b32 --- /dev/null +++ b/.changeset/grumpy-bikes-rhyme.md @@ -0,0 +1,5 @@ +--- +"@hey-api/openapi-ts": minor +--- + +feat: move format and lint config options to output object diff --git a/docs/openapi-ts/configuration.md b/docs/openapi-ts/configuration.md index 4a70d9a85..6e5cdbbce 100644 --- a/docs/openapi-ts/configuration.md +++ b/docs/openapi-ts/configuration.md @@ -103,31 +103,37 @@ You might not need a `node` client. Fetch API is [experimental](https://nodejs.o ## Formatting -By default, `openapi-ts` will not automatically format your client. To enable this feature, set `format` to a valid formatter. +By default, `openapi-ts` will not automatically format your client. To enable this feature, set `output.format` to a valid formatter. ::: code-group -```js{2} [disabled] +```js{4} [disabled] export default { - format: false, input: 'path/to/openapi.json', - output: 'src/client', + output: { + format: false, + path: 'src/client', + }, } ``` -```js{2} [prettier] +```js{4} [prettier] export default { - format: 'prettier', input: 'path/to/openapi.json', - output: 'src/client', + output: { + format: 'prettier', + path: 'src/client', + }, } ``` -```js{2} [biome] +```js{4} [biome] export default { - format: 'biome', input: 'path/to/openapi.json', - output: 'src/client', + output: { + format: 'biome', + path: 'src/client', + }, } ``` @@ -137,31 +143,37 @@ You can also prevent your client from being processed by formatters by adding yo ## Linting -For performance reasons, `openapi-ts` does not automatically lint your client. To enable this feature, set `lint` to a valid linter. +For performance reasons, `openapi-ts` does not automatically lint your client. To enable this feature, set `output.lint` to a valid linter. ::: code-group -```js{3} [disabled] +```js{4} [disabled] export default { input: 'path/to/openapi.json', - lint: false, - output: 'src/client', + output: { + lint: false, + path: 'src/client', + }, } ``` -```js{3} [eslint] +```js{4} [eslint] export default { input: 'path/to/openapi.json', - lint: 'eslint', - output: 'src/client', + output: { + lint: 'eslint', + path: 'src/client', + }, } ``` -```js{3} [biome] +```js{4} [biome] export default { input: 'path/to/openapi.json', - lint: 'biome', - output: 'src/client', + output: { + lint: 'biome', + path: 'src/client', + }, } ``` diff --git a/docs/openapi-ts/migrating.md b/docs/openapi-ts/migrating.md index d8ea3b351..969a2482c 100644 --- a/docs/openapi-ts/migrating.md +++ b/docs/openapi-ts/migrating.md @@ -50,6 +50,36 @@ This config option is deprecated and will be removed in favor of [clients](./cli This config option is deprecated and will be removed in favor of [clients](./clients). +## v0.44.0 + +### Moved `format` + +This config option has been moved. You can now configure formatter using the `output.format` option. + +```js{4} +export default { + input: 'path/to/openapi.json', + output: { + format: 'prettier', + path: 'src/client', + }, +} +``` + +### Moved `lint` + +This config option has been moved. You can now configure linter using the `output.lint` option. + +```js{4} +export default { + input: 'path/to/openapi.json', + output: { + lint: 'eslint', + path: 'src/client', + }, +} +``` + ## v0.43.0 ### Removed `enums.gen.ts` diff --git a/packages/openapi-ts/bin/index.cjs b/packages/openapi-ts/bin/index.cjs index 1322769e7..33df5361a 100755 --- a/packages/openapi-ts/bin/index.cjs +++ b/packages/openapi-ts/bin/index.cjs @@ -23,12 +23,10 @@ const params = program .option('-d, --debug', 'Run in debug mode?') .option('--dry-run [value]', 'Skip writing files to disk?') .option('--exportCore [value]', 'Write core files to disk') - .option('--format [value]', 'Process output folder with formatter?') .option( '-i, --input ', 'OpenAPI specification (path, url, or string content)', ) - .option('--lint [value]', 'Process output folder with linter?') .option('--name ', 'Custom client class name') .option('-o, --output ', 'Output directory') .option('--request ', 'Path to custom request file') @@ -70,8 +68,6 @@ async function start() { userConfig = processParams(params, [ 'dryRun', 'exportCore', - 'format', - 'lint', 'schemas', 'services', 'types', diff --git a/packages/openapi-ts/src/index.ts b/packages/openapi-ts/src/index.ts index 54be3e867..82ced29a5 100644 --- a/packages/openapi-ts/src/index.ts +++ b/packages/openapi-ts/src/index.ts @@ -14,13 +14,16 @@ import { postProcessClient } from './utils/postprocess'; import { writeClient } from './utils/write/client'; type Dependencies = Record; -type PackageDependencies = { +interface PackageJson { dependencies?: Dependencies; devDependencies?: Dependencies; peerDependencies?: Dependencies; -}; +} -// Dependencies used in each client. User must have installed these to use the generated client +/** + * Dependencies used in each client. User must install these, without them + * the generated client won't work. + */ const clientDependencies: Record = { '@hey-api/client-axios': ['axios'], '@hey-api/client-fetch': [], @@ -32,24 +35,29 @@ const clientDependencies: Record = { }; type OutputProcesser = { - args: (output: string) => string[]; + args: (path: string) => string[]; command: string; condition: (dependencies: Dependencies) => boolean; name: string; }; -// Map of supported formatters -const formatters: Record, OutputProcesser> = { +/** + * Map of supported formatters + */ +const formatters: Record< + Extract, + OutputProcesser +> = { biome: { - args: (output) => ['format', '--write', output], + args: (path) => ['format', '--write', path], command: 'biome', condition: (dependencies) => Boolean(dependencies['@biomejs/biome']), name: 'Biome (Format)', }, prettier: { - args: (output) => [ + args: (path) => [ '--ignore-unknown', - output, + path, '--write', '--ignore-path', './.prettierignore', @@ -60,16 +68,21 @@ const formatters: Record, OutputProcesser> = { }, }; -// Map of supported linters -const linters: Record, OutputProcesser> = { +/** + * Map of supported linters + */ +const linters: Record< + Extract, + OutputProcesser +> = { biome: { - args: (output) => ['lint', '--apply', output], + args: (path) => ['lint', '--apply', path], command: 'biome', condition: (dependencies) => Boolean(dependencies['@biomejs/biome']), name: 'Biome (Lint)', }, eslint: { - args: (output) => [output, '--fix'], + args: (path) => [path, '--fix'], command: 'eslint', condition: (dependencies) => Boolean(dependencies.eslint), name: 'ESLint', @@ -78,18 +91,18 @@ const linters: Record, OutputProcesser> = { const processOutput = (dependencies: Dependencies) => { const config = getConfig(); - if (config.format) { - const formatter = formatters[config.format]; + if (config.output.format) { + const formatter = formatters[config.output.format]; if (formatter.condition(dependencies)) { console.log(`✨ Running ${formatter.name}`); - sync(formatter.command, formatter.args(config.output)); + sync(formatter.command, formatter.args(config.output.path)); } } - if (config.lint) { - const linter = linters[config.lint]; + if (config.output.lint) { + const linter = linters[config.output.lint]; if (linter.condition(dependencies)) { console.log(`✨ Running ${linter.name}`); - sync(linter.command, linter.args(config.output)); + sync(linter.command, linter.args(config.output.path)); } } }; @@ -142,6 +155,23 @@ const logMissingDependenciesWarning = (dependencies: Dependencies) => { } }; +const getOutput = (userConfig: UserConfig): Config['output'] => { + let output: Config['output'] = { + format: false, + lint: false, + path: '', + }; + if (typeof userConfig.output === 'string') { + output.path = userConfig.output; + } else { + output = { + ...output, + ...userConfig.output, + }; + } + return output; +}; + const getSchemas = (userConfig: UserConfig): Config['schemas'] => { let schemas: Config['schemas'] = { export: true, @@ -199,40 +229,42 @@ const getTypes = (userConfig: UserConfig): Config['types'] => { }; const getInstalledDependencies = (): Dependencies => { - const toReducedDependencies = (p: PackageDependencies): Dependencies => + const packageJsonToDependencies = (pkg: PackageJson): Dependencies => [ - p.dependencies ?? {}, - p.devDependencies ?? {}, - p.peerDependencies ?? {}, + pkg.dependencies ?? {}, + pkg.devDependencies ?? {}, + pkg.peerDependencies ?? {}, ].reduce( - (acc, deps) => ({ - ...acc, - ...deps, + (result, dependencies) => ({ + ...result, + ...dependencies, }), {}, ); let dependencies: Dependencies = {}; - // Attempt to get all globally installed pacakges. + // Attempt to get all globally installed packages. const result = sync('npm', ['list', '-g', '--json', '--depth=0']); if (!result.error) { - const globally: PackageDependencies = JSON.parse(result.stdout.toString()); + const globalDependencies: PackageJson = JSON.parse( + result.stdout.toString(), + ); dependencies = { ...dependencies, - ...toReducedDependencies(globally), + ...packageJsonToDependencies(globalDependencies), }; } // Attempt to read any dependencies installed in a local projects package.json. const pkgPath = path.resolve(process.cwd(), 'package.json'); if (existsSync(pkgPath)) { - const locally: PackageDependencies = JSON.parse( + const localDependencies: PackageJson = JSON.parse( readFileSync(pkgPath).toString(), ); dependencies = { ...dependencies, - ...toReducedDependencies(locally), + ...packageJsonToDependencies(localDependencies), }; } @@ -260,9 +292,7 @@ const initConfig = async ( debug = false, dryRun = false, exportCore = true, - format = false, input, - lint = false, name, request, useOptions = true, @@ -272,13 +302,15 @@ const initConfig = async ( console.warn('userConfig:', userConfig); } + const output = getOutput(userConfig); + if (!input) { throw new Error( '🚫 input not provided - provide path to OpenAPI specification', ); } - if (!userConfig.output) { + if (!output.path) { throw new Error( '🚫 output not provided - provide path where we should generate your client', ); @@ -291,20 +323,19 @@ const initConfig = async ( } const client = userConfig.client || inferClient(dependencies); - const output = path.resolve(process.cwd(), userConfig.output); const schemas = getSchemas(userConfig); const services = getServices(userConfig); const types = getTypes(userConfig); + output.path = path.resolve(process.cwd(), output.path); + return setConfig({ base, client, debug, dryRun, exportCore: client.startsWith('@hey-api') ? false : exportCore, - format, input, - lint, name, output, request, @@ -345,7 +376,7 @@ export async function createClient(userConfig: UserConfig): Promise { processOutput(dependencies); } - console.log('✨ Done! Your client is located in:', config.output); + console.log('✨ Done! Your client is located in:', config.output.path); return client; } diff --git a/packages/openapi-ts/src/openApi/common/parser/__tests__/operation.spec.ts b/packages/openapi-ts/src/openApi/common/parser/__tests__/operation.spec.ts index 6f7451d80..c55de64f8 100644 --- a/packages/openapi-ts/src/openApi/common/parser/__tests__/operation.spec.ts +++ b/packages/openapi-ts/src/openApi/common/parser/__tests__/operation.spec.ts @@ -13,10 +13,10 @@ describe('getOperationName', () => { debug: false, dryRun: true, exportCore: false, - format: false, input: '', - lint: false, - output: '', + output: { + path: '', + }, schemas: { export: false, }, @@ -36,10 +36,10 @@ describe('getOperationName', () => { debug: false, dryRun: true, exportCore: false, - format: false, input: '', - lint: false, - output: '', + output: { + path: '', + }, schemas: { export: false, }, diff --git a/packages/openapi-ts/src/openApi/v2/parser/__tests__/getServices.spec.ts b/packages/openapi-ts/src/openApi/v2/parser/__tests__/getServices.spec.ts index 345cacc2d..bc735f4eb 100644 --- a/packages/openapi-ts/src/openApi/v2/parser/__tests__/getServices.spec.ts +++ b/packages/openapi-ts/src/openApi/v2/parser/__tests__/getServices.spec.ts @@ -10,10 +10,10 @@ describe('getServices', () => { debug: false, dryRun: true, exportCore: true, - format: false, input: '', - lint: false, - output: '', + output: { + path: '', + }, schemas: {}, services: { operationId: false, diff --git a/packages/openapi-ts/src/openApi/v3/parser/__tests__/getServices.spec.ts b/packages/openapi-ts/src/openApi/v3/parser/__tests__/getServices.spec.ts index 92a631ef0..ad652d479 100644 --- a/packages/openapi-ts/src/openApi/v3/parser/__tests__/getServices.spec.ts +++ b/packages/openapi-ts/src/openApi/v3/parser/__tests__/getServices.spec.ts @@ -10,10 +10,10 @@ describe('getServices', () => { debug: false, dryRun: true, exportCore: true, - format: false, input: '', - lint: false, - output: '', + output: { + path: '', + }, schemas: {}, services: { operationId: true, diff --git a/packages/openapi-ts/src/types/config.ts b/packages/openapi-ts/src/types/config.ts index 15f993e2c..16551ca96 100644 --- a/packages/openapi-ts/src/types/config.ts +++ b/packages/openapi-ts/src/types/config.ts @@ -1,6 +1,7 @@ export interface UserConfig { /** * Manually set base in OpenAPI config instead of inferring from server value + * @deprecated */ base?: string; /** @@ -30,30 +31,39 @@ export interface UserConfig { * @default true */ exportCore?: boolean; - /** - * Process output folder with formatter? - * @default false - */ - format?: 'biome' | 'prettier' | false; /** * The relative location of the OpenAPI spec */ input: string | Record; - /** - * Process output folder with linter? - * @default false - */ - lint?: 'biome' | 'eslint' | false; /** * Custom client class name + * @deprecated */ name?: string; /** * The relative location of the output directory */ - output: string; + output: + | string + | { + /** + * Process output folder with formatter? + * @default false + */ + format?: 'biome' | 'prettier' | false; + /** + * Process output folder with linter? + * @default false + */ + lint?: 'biome' | 'eslint' | false; + /** + * The relative location of the output directory + */ + path: string; + }; /** * Path to custom request file + * @deprecated */ request?: string; /** @@ -144,6 +154,7 @@ export interface UserConfig { }; /** * Use options or arguments functions + * @deprecated * @default true */ useOptions?: boolean; @@ -151,9 +162,10 @@ export interface UserConfig { export type Config = Omit< Required, - 'base' | 'name' | 'request' | 'schemas' | 'services' | 'types' + 'base' | 'name' | 'output' | 'request' | 'schemas' | 'services' | 'types' > & Pick & { + output: Extract['output'], object>; schemas: Extract['schemas'], object>; services: Extract['services'], object>; types: Extract['types'], object>; diff --git a/packages/openapi-ts/src/utils/__tests__/handlebars.spec.ts b/packages/openapi-ts/src/utils/__tests__/handlebars.spec.ts index 07f337db0..e1e7f2e80 100644 --- a/packages/openapi-ts/src/utils/__tests__/handlebars.spec.ts +++ b/packages/openapi-ts/src/utils/__tests__/handlebars.spec.ts @@ -14,10 +14,11 @@ describe('registerHandlebarHelpers', () => { debug: false, dryRun: false, exportCore: true, - format: 'prettier', input: '', - lint: false, - output: '', + output: { + format: 'prettier', + path: '', + }, schemas: {}, services: {}, types: { @@ -42,10 +43,11 @@ describe('registerHandlebarTemplates', () => { debug: false, dryRun: false, exportCore: true, - format: 'prettier', input: '', - lint: false, - output: '', + output: { + format: 'prettier', + path: '', + }, schemas: {}, services: {}, types: { diff --git a/packages/openapi-ts/src/utils/write/__tests__/class.spec.ts b/packages/openapi-ts/src/utils/write/__tests__/class.spec.ts index 51dfcae98..a44b145c0 100644 --- a/packages/openapi-ts/src/utils/write/__tests__/class.spec.ts +++ b/packages/openapi-ts/src/utils/write/__tests__/class.spec.ts @@ -16,11 +16,11 @@ describe('writeClientClass', () => { debug: false, dryRun: false, exportCore: true, - format: false, input: '', - lint: false, name: 'AppClient', - output: '', + output: { + path: '', + }, schemas: {}, services: {}, types: { diff --git a/packages/openapi-ts/src/utils/write/__tests__/client.spec.ts b/packages/openapi-ts/src/utils/write/__tests__/client.spec.ts index ace9eb1d9..b09fb17d6 100644 --- a/packages/openapi-ts/src/utils/write/__tests__/client.spec.ts +++ b/packages/openapi-ts/src/utils/write/__tests__/client.spec.ts @@ -16,10 +16,11 @@ describe('writeClient', () => { debug: false, dryRun: false, exportCore: true, - format: 'prettier', input: '', - lint: false, - output: './dist', + output: { + format: 'prettier', + path: './dist', + }, schemas: {}, services: {}, types: { diff --git a/packages/openapi-ts/src/utils/write/__tests__/core.spec.ts b/packages/openapi-ts/src/utils/write/__tests__/core.spec.ts index 1f257764b..30a495580 100644 --- a/packages/openapi-ts/src/utils/write/__tests__/core.spec.ts +++ b/packages/openapi-ts/src/utils/write/__tests__/core.spec.ts @@ -29,11 +29,11 @@ describe('writeCore', () => { debug: false, dryRun: false, exportCore: true, - format: false, input: '', - lint: false, name: 'AppClient', - output: '', + output: { + path: '', + }, schemas: {}, services: {}, types: { @@ -84,11 +84,11 @@ describe('writeCore', () => { debug: false, dryRun: false, exportCore: true, - format: false, input: '', - lint: false, name: 'AppClient', - output: '', + output: { + path: '', + }, schemas: {}, services: {}, types: { @@ -122,11 +122,11 @@ describe('writeCore', () => { debug: false, dryRun: false, exportCore: true, - format: false, input: '', - lint: false, name: 'AppClient', - output: '', + output: { + path: '', + }, schemas: {}, services: {}, types: { diff --git a/packages/openapi-ts/src/utils/write/__tests__/index.spec.ts b/packages/openapi-ts/src/utils/write/__tests__/index.spec.ts index 818213b8c..0293f66cf 100644 --- a/packages/openapi-ts/src/utils/write/__tests__/index.spec.ts +++ b/packages/openapi-ts/src/utils/write/__tests__/index.spec.ts @@ -16,10 +16,10 @@ describe('processIndex', () => { debug: false, dryRun: false, exportCore: true, - format: false, input: '', - lint: false, - output: '', + output: { + path: '', + }, schemas: {}, services: {}, types: { diff --git a/packages/openapi-ts/src/utils/write/__tests__/models.spec.ts b/packages/openapi-ts/src/utils/write/__tests__/models.spec.ts index 1f9c23a65..492c7d64a 100644 --- a/packages/openapi-ts/src/utils/write/__tests__/models.spec.ts +++ b/packages/openapi-ts/src/utils/write/__tests__/models.spec.ts @@ -16,11 +16,11 @@ describe('processTypes', () => { debug: false, dryRun: false, exportCore: true, - format: false, input: '', - lint: false, name: 'AppClient', - output: '', + output: { + path: '', + }, schemas: {}, services: {}, types: { diff --git a/packages/openapi-ts/src/utils/write/__tests__/schemas.spec.ts b/packages/openapi-ts/src/utils/write/__tests__/schemas.spec.ts index 47bb5c403..29d8e872e 100644 --- a/packages/openapi-ts/src/utils/write/__tests__/schemas.spec.ts +++ b/packages/openapi-ts/src/utils/write/__tests__/schemas.spec.ts @@ -17,11 +17,11 @@ describe('processSchemas', () => { debug: false, dryRun: false, exportCore: true, - format: false, input: '', - lint: false, name: 'AppClient', - output: '', + output: { + path: '', + }, schemas: {}, services: {}, types: { diff --git a/packages/openapi-ts/src/utils/write/__tests__/services.spec.ts b/packages/openapi-ts/src/utils/write/__tests__/services.spec.ts index 28a62ee45..f13025447 100644 --- a/packages/openapi-ts/src/utils/write/__tests__/services.spec.ts +++ b/packages/openapi-ts/src/utils/write/__tests__/services.spec.ts @@ -16,10 +16,10 @@ describe('processServices', () => { debug: false, dryRun: false, exportCore: true, - format: false, input: '', - lint: false, - output: '', + output: { + path: '', + }, schemas: {}, services: {}, types: {}, diff --git a/packages/openapi-ts/src/utils/write/client.ts b/packages/openapi-ts/src/utils/write/client.ts index 72190716f..579a8ccdc 100644 --- a/packages/openapi-ts/src/utils/write/client.ts +++ b/packages/openapi-ts/src/utils/write/client.ts @@ -38,7 +38,7 @@ export const writeClient = async ( client.models = client.models.filter((model) => regexp.test(model.name)); } - const outputPath = path.resolve(config.output); + const outputPath = path.resolve(config.output.path); if (!existsSync(outputPath)) { mkdirSync(outputPath, { recursive: true }); @@ -46,25 +46,25 @@ export const writeClient = async ( const files: Record = { index: new TypeScriptFile({ - dir: config.output, + dir: config.output.path, name: 'index.ts', }), }; if (config.schemas.export) { files.schemas = new TypeScriptFile({ - dir: config.output, + dir: config.output.path, name: 'schemas.ts', }); } if (config.services.export) { files.services = new TypeScriptFile({ - dir: config.output, + dir: config.output.path, name: 'services.ts', }); } if (config.types.export) { files.types = new TypeScriptFile({ - dir: config.output, + dir: config.output.path, name: 'types.ts', }); } @@ -75,7 +75,7 @@ export const writeClient = async ( // deprecated files await writeClientClass(openApi, outputPath, client, templates); - await writeCore(path.resolve(config.output, 'core'), client, templates); + await writeCore(path.resolve(config.output.path, 'core'), client, templates); await processIndex({ files }); diff --git a/packages/openapi-ts/test/bin.spec.ts b/packages/openapi-ts/test/bin.spec.ts index 6a4ef235a..ac28bec3b 100755 --- a/packages/openapi-ts/test/bin.spec.ts +++ b/packages/openapi-ts/test/bin.spec.ts @@ -139,34 +139,6 @@ describe('bin', () => { expect(result.stderr.toString()).toBe(''); }); - it('formats output with Prettier', () => { - const result = sync('node', [ - './bin/index.cjs', - '--input', - './test/spec/v3.json', - '--output', - './test/generated/bin', - '--format', - 'prettier', - ]); - expect(result.stdout.toString()).toContain('Prettier'); - expect(result.stderr.toString()).toBe(''); - }); - - it('lints output with ESLint', () => { - const result = sync('node', [ - './bin/index.cjs', - '--input', - './test/spec/v3.json', - '--output', - './test/generated/bin', - '--lint', - 'eslint', - ]); - expect(result.stdout.toString()).toContain('ESLint'); - expect(result.stderr.toString()).toBe(''); - }); - it('throws error without parameters', () => { const result = sync('node', ['./bin/index.cjs', '--dry-run', 'true']); expect(result.stdout.toString()).toBe(''); @@ -221,10 +193,6 @@ describe('cli', () => { 'false', '--services', 'false', - '--format', - 'false', - '--lint', - 'false', '--useOptions', 'false', '--dry-run', @@ -235,8 +203,6 @@ describe('cli', () => { expect(result.stderr.toString()).toContain('exportCore: false'); expect(result.stderr.toString()).toContain('types: false'); expect(result.stderr.toString()).toContain('services: false'); - expect(result.stderr.toString()).toContain('format: false'); - expect(result.stderr.toString()).toContain('lint: false'); expect(result.stderr.toString()).toContain('schemas: false'); expect(result.stderr.toString()).toContain('useOptions: false'); }); diff --git a/packages/openapi-ts/test/sample.cjs b/packages/openapi-ts/test/sample.cjs index 8dd79a23a..5492e7558 100644 --- a/packages/openapi-ts/test/sample.cjs +++ b/packages/openapi-ts/test/sample.cjs @@ -5,7 +5,9 @@ const main = async () => { const config = { client: 'fetch', input: './test/spec/v3.json', - output: './test/generated/v3/', + output: { + path: './test/generated/v3/', + }, schemas: { // export: false, },