Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tidy-eyes-flash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hey-api/openapi-ts': minor
---

feat: add input.include option
4 changes: 2 additions & 2 deletions docs/.vitepress/config/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,10 @@ export default defineConfig({
},
{
items: [
{ link: '/about', text: 'Philosophy' },
{ link: '/openapi-ts/migrating', text: 'Migrating' },
{ link: '/license', text: 'License' },
{ link: '/about', text: 'Philosophy' },
{ link: '/contributing', text: 'Contributing' },
{ link: '/openapi-ts/migrating', text: 'Migrating' },
],
text: '@hey-api',
},
Expand Down
47 changes: 47 additions & 0 deletions docs/openapi-ts/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,28 @@ Input is the first thing you must define. It can be a local path, remote URL, or
We use [`@apidevtools/json-schema-ref-parser`](https://github.com/APIDevTools/json-schema-ref-parser) to resolve file locations. Please note that accessing a HTTPS URL on localhost has a known [workaround](https://github.com/hey-api/openapi-ts/issues/276).
:::

## Filters

::: warning
Filters work only with the [experimental parser](#parser) which is currently an opt-in feature.
:::

If you work with large specifications and want to generate output from their subset, set `input.include` to a valid regular expression string.

```js
export default {
client: '@hey-api/client-fetch',
experimentalParser: true, // [!code ++]
input: {
include: '^(#/components/schemas/foo|#/paths/api/v1/foo/get)$', // [!code ++]
path: 'path/to/openapi.json',
},
output: 'src/client',
};
```

The configuration above will process only the schema named `foo` and `GET` operation for the `/api/v1/foo` path.

## Output

Output is the next thing to define. It can be either a string pointing to the destination folder or a configuration object containing the destination folder path and optional settings (these are described below).
Expand Down Expand Up @@ -180,6 +202,31 @@ Plugins are responsible for generating artifacts from your input. By default, He

You can learn more on the [Output](/openapi-ts/output) page.

## Parser

If you're using OpenAPI 3.0 or newer, we encourage you to try out the experimental parser. In the future it will become the default parser, but until it's been tested it's an opt-in feature. To try it out, set the `experimentalParser` flag in your configuration to `true`.

::: code-group

```js [config]
export default {
client: '@hey-api/client-fetch',
experimentalParser: true, // [!code ++]
input: 'path/to/openapi.json',
output: 'src/client',
};
```

```sh [cli]
npx @hey-api/openapi-ts -i path/to/openapi.json -o src/client -c @hey-api/client-fetch -e
```

:::

The experimental parser produces a cleaner output while being faster than the legacy parser. It also supports features such as [Filters](#filters) and more will be added in the future.

The legacy parser will remain enabled for the [legacy clients](/openapi-ts/clients/legacy) regardless of the `experimentalParser` flag value. However, it's unlikely to receive any further updates.

## Config API

You can view the complete list of options in the [UserConfig](https://github.com/hey-api/openapi-ts/blob/main/packages/openapi-ts/src/types/config.ts) interface.
Expand Down
32 changes: 32 additions & 0 deletions docs/openapi-ts/migrating.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,38 @@ This config option is deprecated and will be removed in favor of [clients](./cli

This config option is deprecated and will be removed.

## v0.55.0

This release adds the ability to filter your OpenAPI specification before it's processed. This feature will be useful if you are working with a large specification and are interested in generating output only from a small subset.

This feature is available only in the experimental parser. In the future, this will become the default parser. To opt-in to the experimental parser, set the `experimentalParser` flag in your configuration to `true`.

### Deprecated `include` in `@hey-api/types`

This config option is deprecated and will be removed when the experimental parser becomes the default.

### Deprecated `filter` in `@hey-api/services`

This config option is deprecated and will be removed when the experimental parser becomes the default.

### Added `input.include` option

This config option can be used to replace the deprecated options. It accepts a regular expression string matching against references within the bundled specification.

```js
export default {
client: '@hey-api/client-fetch',
experimentalParser: true,
input: {
include: '^(#/components/schemas/foo|#/paths/api/v1/foo/get)$', // [!code ++]
path: 'path/to/openapi.json',
},
output: 'src/client',
};
```

The configuration above will process only the schema named `foo` and `GET` operation for the `/api/v1/foo` path.

## v0.54.0

This release makes plugins first-class citizens. In order to achieve that, the following breaking changes were introduced.
Expand Down
2 changes: 0 additions & 2 deletions docs/openapi-ts/output.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ description: Learn about files generated with @hey-api/openapi-ts.
Learn about files generated with `@hey-api/openapi-ts`.

::: tip

Your actual output depends on your Hey API configuration. It may contain a different number of files and their contents might differ.

:::

## Overview
Expand Down
4 changes: 3 additions & 1 deletion packages/openapi-ts/src/generate/__tests__/class.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ describe('generateLegacyClientClass', () => {
dryRun: false,
experimentalParser: false,
exportCore: true,
input: '',
input: {
path: '',
},
name: 'AppClient',
output: {
path: '',
Expand Down
12 changes: 9 additions & 3 deletions packages/openapi-ts/src/generate/__tests__/core.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ describe('generateLegacyCore', () => {
dryRun: false,
experimentalParser: false,
exportCore: true,
input: '',
input: {
path: '',
},
name: 'AppClient',
output: {
path: '',
Expand Down Expand Up @@ -106,7 +108,9 @@ describe('generateLegacyCore', () => {
dryRun: false,
experimentalParser: false,
exportCore: true,
input: '',
input: {
path: '',
},
name: 'AppClient',
output: {
path: '',
Expand Down Expand Up @@ -162,7 +166,9 @@ describe('generateLegacyCore', () => {
dryRun: false,
experimentalParser: false,
exportCore: true,
input: '',
input: {
path: '',
},
name: 'AppClient',
output: {
path: '',
Expand Down
4 changes: 3 additions & 1 deletion packages/openapi-ts/src/generate/__tests__/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ describe('generateIndexFile', () => {
dryRun: false,
experimentalParser: false,
exportCore: true,
input: '',
input: {
path: '',
},
output: {
path: '',
},
Expand Down
4 changes: 3 additions & 1 deletion packages/openapi-ts/src/generate/__tests__/output.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ describe('generateLegacyOutput', () => {
dryRun: false,
experimentalParser: false,
exportCore: true,
input: '',
input: {
path: '',
},
output: {
format: 'prettier',
path: './dist',
Expand Down
8 changes: 5 additions & 3 deletions packages/openapi-ts/src/generate/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ export const generateLegacyOutput = async ({
templates,
}: {
client: Client;
openApi: OpenApi;
openApi: unknown;
templates: Templates;
}): Promise<void> => {
const config = getConfig();

const spec = openApi as OpenApi;

// TODO: parser - move to config.input
if (client) {
if (
Expand All @@ -55,7 +57,7 @@ export const generateLegacyOutput = async ({
}

// deprecated files
await generateLegacyClientClass(openApi, outputPath, client, templates);
await generateLegacyClientClass(spec, outputPath, client, templates);
await generateLegacyCore(
path.resolve(config.output.path, 'core'),
client,
Expand All @@ -78,7 +80,7 @@ export const generateLegacyOutput = async ({
plugin._handlerLegacy({
client,
files,
openApi,
openApi: spec,
plugin: plugin as never,
});
}
Expand Down
57 changes: 43 additions & 14 deletions packages/openapi-ts/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { existsSync } from 'node:fs';
import path from 'node:path';

import $RefParser from '@apidevtools/json-schema-ref-parser';
import { loadConfig } from 'c12';
import { sync } from 'cross-spawn';

Expand All @@ -23,7 +25,6 @@
legacyNameFromConfig,
setConfig,
} from './utils/config';
import { getOpenApiSpec } from './utils/getOpenApiSpec';
import { registerHandlebarTemplates } from './utils/handlebars';
import { Performance, PerformanceReport } from './utils/performance';
import { postProcessClient } from './utils/postprocess';
Expand Down Expand Up @@ -125,6 +126,26 @@
return client;
};

const getInput = (userConfig: ClientConfig): Config['input'] => {
let input: Config['input'] = {
path: '',
};
if (typeof userConfig.input === 'string') {
input.path = userConfig.input;
} else if (userConfig.input && userConfig.input.path) {
input = {
...input,
...userConfig.input,
};
} else {
input = {
...input,
path: userConfig.input,
};
}

Check warning on line 145 in packages/openapi-ts/src/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/index.ts#L136-L145

Added lines #L136 - L145 were not covered by tests
return input;
};

const getOutput = (userConfig: ClientConfig): Config['output'] => {
let output: Config['output'] = {
format: false,
Expand Down Expand Up @@ -221,6 +242,19 @@
};
};

const getSpec = async ({ config }: { config: Config }) => {
let spec: unknown = config.input.path;

if (typeof config.input.path === 'string') {
const absolutePathOrUrl = existsSync(config.input.path)
? path.resolve(config.input.path)
: config.input.path;
spec = await $RefParser.bundle(absolutePathOrUrl, absolutePathOrUrl, {});
}

return spec;
};

const initConfigs = async (userConfig: UserConfig): Promise<Config[]> => {
let configurationFile: string | undefined = undefined;
if (userConfig.configFile) {
Expand Down Expand Up @@ -250,7 +284,6 @@
dryRun = false,
exportCore = true,
experimentalParser = false,
input,
name,
request,
useOptions = true,
Expand All @@ -260,9 +293,10 @@
console.warn('userConfig:', userConfig);
}

const input = getInput(userConfig);
const output = getOutput(userConfig);

if (!input) {
if (!input.path) {
throw new Error(
'🚫 missing input - which OpenAPI specification should we use to generate your client?',
);
Expand Down Expand Up @@ -332,14 +366,9 @@
Performance.end('handlebars');

const pCreateClient = (config: Config) => async () => {
Performance.start('openapi');
const openApi =
typeof config.input === 'string'
? await getOpenApiSpec(config.input)
: (config.input as unknown as Awaited<
ReturnType<typeof getOpenApiSpec>
>);
Performance.end('openapi');
Performance.start('spec');
const spec = await getSpec({ config });
Performance.end('spec');

let client: Client | undefined;
let context: IRContext | undefined;
Expand All @@ -363,14 +392,14 @@
context = parseExperimental({
config,
parserConfig,
spec: openApi,
spec,
});
}

// fallback to legacy parser
if (!context) {
const parsed = parseLegacy({
openApi,
openApi: spec,
parserConfig,
});
client = postProcessClient(parsed);
Expand All @@ -383,7 +412,7 @@
if (context) {
await generateOutput({ context });
} else if (client) {
await generateLegacyOutput({ client, openApi, templates });
await generateLegacyOutput({ client, openApi: spec, templates });
}
Performance.end('generator');

Expand Down
Loading