Skip to content

Commit e4c3846

Browse files
authored
Merge branch 'master' into allowIgnoringOperationId
2 parents af4dfb7 + 8b7e5f6 commit e4c3846

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1423
-293
lines changed

.github/workflows/unittest.yml

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: unittest
2+
3+
on: [pull_request]
4+
5+
jobs:
6+
test:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- name: Checkout
10+
uses: actions/[email protected]
11+
12+
- name: Setup Node environment
13+
uses: actions/[email protected]
14+
with:
15+
node-version: 20
16+
17+
- name: Cache Modules
18+
uses: actions/cache@v4
19+
with:
20+
path: "**/node_modules"
21+
key: ${{ runner.os }}-modules-${{ hashFiles('**/package-lock.json') }}
22+
23+
- name: Install dependencies
24+
run: npm install
25+
26+
- name: Build library
27+
run: npm run release
28+
29+
- name: Run unit tests
30+
run: npm run test
31+
32+
# - name: Run e2e tests
33+
# run: npm run test:e2e
34+
35+
# - name: Submit to Codecov
36+
# run: npm run codecov
37+

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ test/e2e/generated
1515
samples/generated
1616
samples/swagger-codegen-cli-v2.jar
1717
samples/swagger-codegen-cli-v3.jar
18+
.env

README.md

+6-30
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,17 @@
11
# OpenAPI Typescript Codegen
22

3-
[![NPM][npm-image]][npm-url]
4-
[![License][license-image]][license-url]
5-
[![Coverage][coverage-image]][coverage-url]
6-
[![Coverage][coverage-image]][coverage-url]
7-
[![Downloads][downloads-image]][downloads-url]
8-
[![Build][build-image]][build-url]
9-
103
> Node.js library that generates Typescript clients based on the OpenAPI specification.
114
5+
This Repo is a fork of the original codebase that was created to support OpenAPI spec v3.1. The original motivation was to be able to support schema generated by [FastAPI](https://fastapi.tiangolo.com/) versions 0.100 and above.
6+
7+
128
## Why?
139
- Frontend ❤️ OpenAPI, but we do not want to use JAVA codegen in our builds
1410
- Quick, lightweight, robust and framework-agnostic 🚀
1511
- Supports generation of TypeScript clients
1612
- Supports generations of Fetch, Node-Fetch, Axios, Angular and XHR http clients
1713
- Supports OpenAPI specification v2.0 and v3.0
14+
- Partial support of OpenAPI specification v3.1
1815
- Supports JSON and YAML files for input
1916
- Supports generation through CLI, Node.js and NPX
2017
- Supports tsc and @babel/plugin-transform-typescript
@@ -43,8 +40,8 @@ $ openapi --help
4340
--useOptions Use options instead of arguments
4441
--useUnionTypes Use union types instead of enums
4542
--exportCore <value> Write core files to disk (default: true)
46-
--exportServices <value> Write services to disk (default: true)
47-
--exportModels <value> Write models to disk (default: true)
43+
--exportServices <value> Write services to disk [true, false, regexp] (default: true)
44+
--exportModels <value> Write models to disk [true, false, regexp] (default: true)
4845
--exportSchemas <value> Write schemas to disk (default: false)
4946
--indent <value> Indentation options [4, 2, tab] (default: "4")
5047
--postfixServices Service name postfix (default: "Service")
@@ -62,24 +59,3 @@ Documentation
6259

6360
The main documentation can be found in the [openapi-typescript-codegen/wiki](https://github.com/ferdikoomen/openapi-typescript-codegen/wiki)
6461

65-
Sponsors
66-
===
67-
68-
If you or your company use the OpenAPI Typescript Codegen, please consider supporting me. By sponsoring I can free up time to give this project some love! Details can be found here: https://github.com/sponsors/ferdikoomen
69-
70-
If you're from an enterprise looking for a fully managed SDK generation, please consider our sponsor:
71-
72-
<a href="https://speakeasyapi.dev/?utm_source=ferdi+repo&utm_medium=github+sponsorship">
73-
<img alt="speakeasy" src="https://storage.googleapis.com/speakeasy-design-assets/ferdi-sponsorship.png" width="640"/>
74-
</a>
75-
76-
[npm-url]: https://npmjs.org/package/openapi-typescript-codegen
77-
[npm-image]: https://img.shields.io/npm/v/openapi-typescript-codegen.svg
78-
[license-url]: LICENSE
79-
[license-image]: http://img.shields.io/npm/l/openapi-typescript-codegen.svg
80-
[coverage-url]: https://codecov.io/gh/ferdikoomen/openapi-typescript-codegen
81-
[coverage-image]: https://img.shields.io/codecov/c/github/ferdikoomen/openapi-typescript-codegen.svg
82-
[downloads-url]: http://npm-stat.com/charts.html?package=openapi-typescript-codegen
83-
[downloads-image]: http://img.shields.io/npm/dm/openapi-typescript-codegen.svg
84-
[build-url]: https://circleci.com/gh/ferdikoomen/openapi-typescript-codegen/tree/master
85-
[build-image]: https://circleci.com/gh/ferdikoomen/openapi-typescript-codegen/tree/master.svg?style=svg

bin/index.js

+12-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const params = program
1616
.option('--name <value>', 'Custom client class name')
1717
.option('--useOptions', 'Use options instead of arguments')
1818
.option('--useUnionTypes', 'Use union types instead of enums')
19+
.option('--autoformat', 'Process generated files with autoformatter', false)
1920
.option('--exportCore <value>', 'Write core files to disk', true)
2021
.option('--exportServices <value>', 'Write services to disk', true)
2122
.option('--exportModels <value>', 'Write models to disk', true)
@@ -30,6 +31,14 @@ const params = program
3031

3132
const OpenAPI = require(path.resolve(__dirname, '../dist/index.js'));
3233

34+
const parseBooleanOrString = value => {
35+
try {
36+
return JSON.parse(value) === true;
37+
} catch (error) {
38+
return value;
39+
}
40+
};
41+
3342
if (OpenAPI) {
3443
OpenAPI.generate({
3544
input: params.input,
@@ -38,9 +47,10 @@ if (OpenAPI) {
3847
clientName: params.name,
3948
useOptions: params.useOptions,
4049
useUnionTypes: params.useUnionTypes,
50+
autoformat: JSON.parse(params.autoformat) === true,
4151
exportCore: JSON.parse(params.exportCore) === true,
42-
exportServices: JSON.parse(params.exportServices) === true,
43-
exportModels: JSON.parse(params.exportModels) === true,
52+
exportServices: parseBooleanOrString(params.exportServices),
53+
exportModels: parseBooleanOrString(params.exportModels),
4454
exportSchemas: JSON.parse(params.exportSchemas) === true,
4555
useOperationId: JSON.parse(params.useOperationId) === true,
4656
indent: params.indent,

bin/index.spec.js

+29
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,35 @@ describe('bin', () => {
4343
expect(result.stderr.toString()).toBe('');
4444
});
4545

46+
it('it should support regexp params', async () => {
47+
const result = crossSpawn.sync('node', [
48+
'./bin/index.js',
49+
'--input',
50+
'./test/spec/v3.json',
51+
'--output',
52+
'./test/generated/bin',
53+
'--exportServices',
54+
'^(Simple|Types)',
55+
'--exportModels',
56+
'^(Simple|Types)',
57+
]);
58+
expect(result.stdout.toString()).toBe('');
59+
expect(result.stderr.toString()).toBe('');
60+
});
61+
62+
it('should autoformat with Prettier', async () => {
63+
const result = crossSpawn.sync('node', [
64+
'./bin/index.js',
65+
'--input',
66+
'./test/spec/v3.json',
67+
'--output',
68+
'./test/generated/bin',
69+
'--autoformat',
70+
]);
71+
expect(result.stdout.toString()).toBe('');
72+
expect(result.stderr.toString()).toBe('');
73+
});
74+
4675
it('it should throw error without params', async () => {
4776
const result = crossSpawn.sync('node', ['./bin/index.js']);
4877
expect(result.stdout.toString()).toBe('');

package-lock.json

+4-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
{
2-
"name": "openapi-typescript-codegen",
3-
"version": "0.27.0",
2+
"name": "@nicolas-chaulet/openapi-typescript-codegen",
3+
"version": "0.27.8",
44
"description": "Library that generates Typescript clients based on the OpenAPI specification.",
55
"author": "Ferdi Koomen",
6-
"homepage": "https://github.com/ferdikoomen/openapi-typescript-codegen",
6+
"homepage": "https://github.com/CanoaPBC/openapi-typescript-codegen",
77
"repository": {
88
"type": "git",
9-
"url": "git+https://github.com/ferdikoomen/openapi-typescript-codegen.git"
9+
"url": "git+https://github.com/CanoaPBC/openapi-typescript-codegen.git"
1010
},
1111
"bugs": {
1212
"url": "https://github.com/ferdikoomen/openapi-typescript-codegen/issues"

src/client/interfaces/Model.d.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,17 @@ import type { Schema } from './Schema';
33

44
export interface Model extends Schema {
55
name: string;
6-
export: 'reference' | 'generic' | 'enum' | 'array' | 'dictionary' | 'interface' | 'one-of' | 'any-of' | 'all-of';
6+
export:
7+
| 'reference'
8+
| 'generic'
9+
| 'enum'
10+
| 'array'
11+
| 'dictionary'
12+
| 'interface'
13+
| 'one-of'
14+
| 'any-of'
15+
| 'all-of'
16+
| 'const';
717
type: string;
818
base: string;
919
template: string | null;
+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import type { Model } from './Model';
22

33
export interface ModelComposition {
4-
type: 'one-of' | 'any-of' | 'all-of';
5-
imports: string[];
64
enums: Model[];
5+
export: 'one-of' | 'any-of' | 'all-of';
6+
imports: string[];
77
properties: Model[];
88
}

src/index.ts

+17-26
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ export type Options = {
1919
clientName?: string;
2020
useOptions?: boolean;
2121
useUnionTypes?: boolean;
22+
autoformat?: boolean;
2223
exportCore?: boolean;
23-
exportServices?: boolean;
24-
exportModels?: boolean;
24+
exportServices?: boolean | string;
25+
exportModels?: boolean | string;
2526
exportSchemas?: boolean;
2627
useOperationId?: boolean;
2728
indent?: Indent;
@@ -59,6 +60,7 @@ export const generate = async ({
5960
clientName,
6061
useOptions = false,
6162
useUnionTypes = false,
63+
autoformat = false,
6264
exportCore = true,
6365
exportServices = true,
6466
exportModels = true,
@@ -78,42 +80,32 @@ export const generate = async ({
7880
useOptions,
7981
});
8082

83+
let parser: typeof parseV2 | typeof parseV3;
84+
8185
switch (openApiVersion) {
8286
case OpenApiVersion.V2: {
83-
const client = parseV2(openApi, useOperationId);
84-
const clientFinal = postProcessClient(client);
85-
if (!write) break;
86-
await writeClient(
87-
clientFinal,
88-
templates,
89-
output,
90-
httpClient,
91-
useOptions,
92-
useUnionTypes,
93-
exportCore,
94-
exportServices,
95-
exportModels,
96-
exportSchemas,
97-
indent,
98-
postfixServices,
99-
postfixModels,
100-
clientName,
101-
request
102-
);
87+
parser = parseV2;
10388
break;
10489
}
10590

10691
case OpenApiVersion.V3: {
107-
const client = parseV3(openApi, useOperationId);
108-
const clientFinal = postProcessClient(client);
109-
if (!write) break;
92+
parser = parseV3;
93+
break;
94+
}
95+
}
96+
97+
if (parser) {
98+
const client = parser(openApi, useOperationId);
99+
const clientFinal = postProcessClient(client);
100+
if (write) {
110101
await writeClient(
111102
clientFinal,
112103
templates,
113104
output,
114105
httpClient,
115106
useOptions,
116107
useUnionTypes,
108+
autoformat,
117109
exportCore,
118110
exportServices,
119111
exportModels,
@@ -124,7 +116,6 @@ export const generate = async ({
124116
clientName,
125117
request
126118
);
127-
break;
128119
}
129120
}
130121
};

src/openApi/v2/parser/escapeName.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import validTypescriptIdentifierRegex from '../../../utils/validTypescriptIdentifierRegex';
2+
13
export const escapeName = (value: string): string => {
24
if (value || value === '') {
3-
const validName = /^[a-zA-Z_$][\w$]+$/g.test(value);
5+
const validName = validTypescriptIdentifierRegex.test(value);
46
if (!validName) {
57
return `'${value}'`;
68
}

src/openApi/v2/parser/getEnum.ts

+2-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Enum } from '../../../client/interfaces/Enum';
2+
import sanitizeEnumName from '../../../utils/sanitizeEnumName';
23

34
export const getEnum = (values?: (string | number)[]): Enum[] => {
45
if (Array.isArray(values)) {
@@ -19,11 +20,7 @@ export const getEnum = (values?: (string | number)[]): Enum[] => {
1920
};
2021
}
2122
return {
22-
name: String(value)
23-
.replace(/\W+/g, '_')
24-
.replace(/^(\d+)/g, '_$1')
25-
.replace(/([a-z])([A-Z]+)/g, '$1_$2')
26-
.toUpperCase(),
23+
name: sanitizeEnumName(String(value)),
2724
value: `'${value.replace(/'/g, "\\'")}'`,
2825
type: 'string',
2926
description: null,

src/openApi/v2/parser/getModel.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export const getModel = (
112112

113113
if (definition.allOf?.length) {
114114
const composition = getModelComposition(openApi, definition, definition.allOf, 'all-of', getModel);
115-
model.export = composition.type;
115+
model.export = composition.export;
116116
model.imports.push(...composition.imports);
117117
model.properties.push(...composition.properties);
118118
model.enums.push(...composition.enums);

src/openApi/v2/parser/getModelComposition.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ export const getModelComposition = (
1717
getModel: GetModelFn
1818
): ModelComposition => {
1919
const composition: ModelComposition = {
20-
type,
21-
imports: [],
2220
enums: [],
21+
export: type,
22+
imports: [],
2323
properties: [],
2424
};
2525

src/openApi/v2/parser/getOperationName.ts

+3-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import camelCase from 'camelcase';
22

3+
import sanitizeOperationName from '../../../utils/sanitizeOperationName';
4+
35
/**
46
* Convert the input value to a correct operation (method) classname.
57
* This will use the operation ID - if available - and otherwise fallback
@@ -12,12 +14,7 @@ export const getOperationName = (
1214
operationId?: string
1315
): string => {
1416
if (useOperationId && operationId) {
15-
return camelCase(
16-
operationId
17-
.replace(/^[^a-zA-Z]+/g, '')
18-
.replace(/[^\w\-]+/g, '-')
19-
.trim()
20-
);
17+
return camelCase(sanitizeOperationName(operationId).trim());
2118
}
2219

2320
const urlWithoutPlaceholders = url

0 commit comments

Comments
 (0)