Skip to content

Commit

Permalink
Merge pull request #569 from hey-api/chore/remove-client-infer
Browse files Browse the repository at this point in the history
feat: remove client inference
  • Loading branch information
mrlubos authored May 14, 2024
2 parents 7af79b5 + c37a193 commit cd0f77f
Show file tree
Hide file tree
Showing 11 changed files with 51 additions and 140 deletions.
5 changes: 5 additions & 0 deletions .changeset/chilly-masks-beg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@hey-api/openapi-ts": minor
---

feat: remove client inference
5 changes: 5 additions & 0 deletions .changeset/cold-spiders-bathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@hey-api/docs": patch
---

docs: add migration for v0.45.0
2 changes: 1 addition & 1 deletion docs/openapi-ts/clients.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ We all send HTTP requests in a slightly different way. Hey API doesn't force you

## Legacy Clients

Before standalone client packages, clients were generated using `openapi-ts`. If you want to use a client that isn't published as a standalone package, you can explicitly set the `client` config option to generate it.
Before standalone client packages, clients were generated using `openapi-ts`. If you want to generate a legacy client that isn't published as a standalone package, you can use the `client` config option.

::: code-group

Expand Down
2 changes: 1 addition & 1 deletion docs/openapi-ts/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Alternatively, you can use `openapi-ts.config.js` and configure the export state

## Clients

By default, `openapi-ts` will try to guess your client based on your project dependencies. If we don't get it right, you can specify the desired client
By default, `openapi-ts` will generate a Fetch API client. If you want a different client, you can specify it using the `client` option.

::: code-group

Expand Down
2 changes: 0 additions & 2 deletions docs/openapi-ts/get-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,8 @@ Live demo
- works with CLI, Node.js, or npx
- supports OpenAPI 2.0, 3.0, and 3.1 specifications
- supports both JSON and YAML input files
- supports external references using [json-schema-ref-parser](https://github.com/APIDevTools/json-schema-ref-parser/)
- generates TypeScript interfaces, REST clients, and JSON Schemas
- Fetch API, Axios, Angular, Node.js, and XHR clients available
- abortable requests through cancellable promise pattern

## Quick Start

Expand Down
16 changes: 15 additions & 1 deletion docs/openapi-ts/migrating.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,20 @@ 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.45.0

### Removed `client` inference

`openapi-ts` will no longer infer which client you want to generate. By default, we will create a `fetch` client. If you want a different client, you can specify it using the `client` option.

```js{2}
export default {
client: 'axios',
input: 'path/to/openapi.json',
output: 'src/client',
}
```

## v0.44.0

### Moved `format`
Expand Down Expand Up @@ -390,4 +404,4 @@ This config option has been removed. Generated types will behave the same as `us

## OpenAPI TypeScript Codegen

`openapi-ts` was originally forked from Ferdi Koomen's [openapi-typescript-codegen](https://github.com/ferdikoomen/openapi-typescript-codegen). Therefore, we want you to be able to migrate your projects. Migration should be relatively straightforward if you follow the release notes on this page. Start here and scroll up to the release you're migrating to.
`openapi-ts` was originally forked from Ferdi Koomen's [openapi-typescript-codegen](https://github.com/ferdikoomen/openapi-typescript-codegen). Therefore, we want you to be able to migrate your projects. Migration should be relatively straightforward if you follow the release notes on this page. Start on [v0.27.24](#v0-27-24) and scroll to the release you're migrating to.
9 changes: 6 additions & 3 deletions examples/openapi-ts-fetch/openapi-ts.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ import { defineConfig } from '@hey-api/openapi-ts';

export default defineConfig({
base: 'https://petstore3.swagger.io/api/v3',
format: 'prettier',
client: '@hey-api/client-fetch',
input:
'https://raw.githubusercontent.com/swagger-api/swagger-petstore/master/src/main/resources/openapi.yaml',
lint: 'eslint',
output: './src/client',
output: {
format: 'prettier',
lint: 'eslint',
path: './src/client',
},
});
2 changes: 0 additions & 2 deletions packages/openapi-ts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@ Generate TypeScript interfaces, REST clients, and JSON Schemas from OpenAPI spec
- works with CLI, Node.js, or npx
- supports OpenAPI 2.0, 3.0, and 3.1 specifications
- supports both JSON and YAML input files
- supports external references using [json-schema-ref-parser](https://github.com/APIDevTools/json-schema-ref-parser/)
- generates TypeScript interfaces, REST clients, and JSON Schemas
- Fetch API, Axios, Angular, Node.js, and XHR clients available
- abortable requests through cancellable promise pattern

## Documentation

Expand Down
144 changes: 16 additions & 128 deletions packages/openapi-ts/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { existsSync, readFileSync } from 'node:fs';
import path from 'node:path';

import { loadConfig } from 'c12';
Expand All @@ -13,31 +12,9 @@ import { registerHandlebarTemplates } from './utils/handlebars';
import { postProcessClient } from './utils/postprocess';
import { writeClient } from './utils/write/client';

type Dependencies = Record<string, unknown>;
interface PackageJson {
dependencies?: Dependencies;
devDependencies?: Dependencies;
peerDependencies?: Dependencies;
}

/**
* Dependencies used in each client. User must install these, without them
* the generated client won't work.
*/
const clientDependencies: Record<Config['client'], string[]> = {
'@hey-api/client-axios': ['axios'],
'@hey-api/client-fetch': [],
angular: ['@angular/common', '@angular/core', 'rxjs'],
axios: ['axios'],
fetch: [],
node: ['node-fetch'],
xhr: [],
};

type OutputProcesser = {
args: (path: string) => string[];
args: (path: string) => ReadonlyArray<string>;
command: string;
condition: (dependencies: Dependencies) => boolean;
name: string;
};

Expand All @@ -51,7 +28,6 @@ const formatters: Record<
biome: {
args: (path) => ['format', '--write', path],
command: 'biome',
condition: (dependencies) => Boolean(dependencies['@biomejs/biome']),
name: 'Biome (Format)',
},
prettier: {
Expand All @@ -63,7 +39,6 @@ const formatters: Record<
'./.prettierignore',
],
command: 'prettier',
condition: (dependencies) => Boolean(dependencies.prettier),
name: 'Prettier',
},
};
Expand All @@ -78,61 +53,40 @@ const linters: Record<
biome: {
args: (path) => ['lint', '--apply', path],
command: 'biome',
condition: (dependencies) => Boolean(dependencies['@biomejs/biome']),
name: 'Biome (Lint)',
},
eslint: {
args: (path) => [path, '--fix'],
command: 'eslint',
condition: (dependencies) => Boolean(dependencies.eslint),
name: 'ESLint',
},
};

const processOutput = (dependencies: Dependencies) => {
const processOutput = () => {
const config = getConfig();

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.path));
}
}
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.path));
}
const module = formatters[config.output.format];
console.log(`✨ Running ${module.name}`);
sync(module.command, module.args(config.output.path));
}
};

const inferClient = (dependencies: Dependencies): Config['client'] => {
if (dependencies['@hey-api/client-axios']) {
return '@hey-api/client-axios';
}
if (dependencies['@hey-api/client-fetch']) {
return '@hey-api/client-fetch';
}
if (dependencies.axios) {
return 'axios';
}
if (dependencies['node-fetch']) {
return 'node';
}
if (Object.keys(dependencies).some((d) => d.startsWith('@angular'))) {
return 'angular';
if (config.output.lint) {
const module = linters[config.output.lint];
console.log(`✨ Running ${module.name}`);
sync(module.command, module.args(config.output.path));
}
return 'fetch';
};

const logClientMessage = () => {
const { client } = getConfig();
switch (client) {
case 'angular':
return console.log('✨ Creating Angular client');
case '@hey-api/client-axios':
case 'axios':
return console.log('✨ Creating Axios client');
case '@hey-api/client-fetch':
case 'fetch':
return console.log('✨ Creating Fetch client');
case 'node':
Expand All @@ -142,19 +96,6 @@ const logClientMessage = () => {
}
};

const logMissingDependenciesWarning = (dependencies: Dependencies) => {
const { client } = getConfig();
const missing = clientDependencies[client].filter(
(d) => dependencies[d] === undefined,
);
if (missing.length > 0) {
console.log(
'⚠️ Dependencies used in generated client are missing: ' +
missing.join(' '),
);
}
};

const getOutput = (userConfig: UserConfig): Config['output'] => {
let output: Config['output'] = {
format: false,
Expand Down Expand Up @@ -228,53 +169,7 @@ const getTypes = (userConfig: UserConfig): Config['types'] => {
return types;
};

const getInstalledDependencies = (): Dependencies => {
const packageJsonToDependencies = (pkg: PackageJson): Dependencies =>
[
pkg.dependencies ?? {},
pkg.devDependencies ?? {},
pkg.peerDependencies ?? {},
].reduce(
(result, dependencies) => ({
...result,
...dependencies,
}),
{},
);

let dependencies: Dependencies = {};

// Attempt to get all globally installed packages.
const result = sync('npm', ['list', '-g', '--json', '--depth=0']);
if (!result.error) {
const globalDependencies: PackageJson = JSON.parse(
result.stdout.toString(),
);
dependencies = {
...dependencies,
...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 localDependencies: PackageJson = JSON.parse(
readFileSync(pkgPath).toString(),
);
dependencies = {
...dependencies,
...packageJsonToDependencies(localDependencies),
};
}

return dependencies;
};

const initConfig = async (
userConfig: UserConfig,
dependencies: Dependencies,
) => {
const initConfig = async (userConfig: UserConfig) => {
const { config: userConfigFromFile } = await loadConfig<UserConfig>({
jitiOptions: {
esmResolve: true,
Expand All @@ -289,6 +184,7 @@ const initConfig = async (

const {
base,
client = 'fetch',
debug = false,
dryRun = false,
exportCore = true,
Expand Down Expand Up @@ -322,7 +218,6 @@ const initConfig = async (
);
}

const client = userConfig.client || inferClient(dependencies);
const schemas = getSchemas(userConfig);
const services = getServices(userConfig);
const types = getTypes(userConfig);
Expand Down Expand Up @@ -353,13 +248,7 @@ const initConfig = async (
* @param userConfig {@link UserConfig} passed to the `createClient()` method
*/
export async function createClient(userConfig: UserConfig): Promise<Client> {
const dependencies = getInstalledDependencies();

if (!dependencies.typescript) {
throw new Error('🚫 dependency missing - TypeScript must be installed');
}

const config = await initConfig(userConfig, dependencies);
const config = await initConfig(userConfig);

const openApi =
typeof config.input === 'string'
Expand All @@ -371,9 +260,8 @@ export async function createClient(userConfig: UserConfig): Promise<Client> {

if (!config.dryRun) {
logClientMessage();
logMissingDependenciesWarning(dependencies);
await writeClient(openApi, client, templates);
processOutput(dependencies);
processOutput();
}

console.log('✨ Done! Your client is located in:', config.output.path);
Expand Down
2 changes: 1 addition & 1 deletion packages/openapi-ts/src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export interface UserConfig {
*/
base?: string;
/**
* The selected HTTP client (fetch, xhr, node or axios)
* HTTP client to generate
* @default 'fetch'
*/
client?:
Expand Down
2 changes: 1 addition & 1 deletion packages/openapi-ts/src/utils/getHttpRequestName.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { Config } from '../types/config';

/**
* Generate the HttpRequest filename based on the selected client
* @param client The selected HTTP client (fetch, xhr, node or axios)
* @param client HTTP client to generate
*/
export const getHttpRequestName = (client: Config['client']): string => {
switch (client) {
Expand Down

0 comments on commit cd0f77f

Please sign in to comment.