@penkov/swagger-code-gen 1.12.0 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/method.js +33 -4
- package/dist/renderer.js +3 -3
- package/dist/templates/index.ejs +7 -2
- package/dist/templates/method.ejs +3 -3
- package/jest.config.cjs +18 -0
- package/package.json +7 -3
- package/tsconfig.jest.json +15 -0
- package/AGENTS.md +0 -34
package/dist/method.js
CHANGED
|
@@ -7,6 +7,12 @@ export const SHARED_BODIES_PREFIX = '#/components/requestBodies/';
|
|
|
7
7
|
const sortByIn = HashMap.of(['path', 0], ['query', 1], ['header', 2], ['cookie', 3], ['body', 4]);
|
|
8
8
|
export const supportedBodyMimeTypes = HashMap.of(['application/json', 'Json'], ['application/x-www-form-urlencoded', 'Form'], ['multipart/form-data', 'File'], ['application/octet-stream', 'Binary']);
|
|
9
9
|
export class Method {
|
|
10
|
+
static parseModeByMimeType(mimeType) {
|
|
11
|
+
if (mimeType.startsWith('text/') || mimeType.includes('xml')) {
|
|
12
|
+
return 'text';
|
|
13
|
+
}
|
|
14
|
+
return 'json';
|
|
15
|
+
}
|
|
10
16
|
constructor(path, method, def, schemasTypes, options, pool) {
|
|
11
17
|
this.path = path;
|
|
12
18
|
this.method = method;
|
|
@@ -132,10 +138,27 @@ export class Method {
|
|
|
132
138
|
.getOrElseValue({});
|
|
133
139
|
const mimeTypes = option(respDef.content)
|
|
134
140
|
.map(content => Collection.from(Object.keys(content)).toMap(mimeType => [mimeType, content[mimeType]])).getOrElseValue(HashMap.empty);
|
|
135
|
-
|
|
136
|
-
.
|
|
137
|
-
.
|
|
141
|
+
const responseMimeType = mimeTypes.get('application/json')
|
|
142
|
+
.map(_ => 'application/json')
|
|
143
|
+
.orElse(() => mimeTypes.keySet.headOption)
|
|
144
|
+
.getOrElseValue('application/json');
|
|
145
|
+
const responseParseMode = Method.parseModeByMimeType(responseMimeType);
|
|
146
|
+
this.response = mimeTypes.get(responseMimeType)
|
|
147
|
+
.filter(p => option(p.schema).isDefined || responseParseMode === 'text')
|
|
138
148
|
.map(p => {
|
|
149
|
+
if (responseParseMode === 'text') {
|
|
150
|
+
const r = Property.fromDefinition('', '', { type: 'string' }, schemasTypes, options).copy({
|
|
151
|
+
nullable: false,
|
|
152
|
+
required: true,
|
|
153
|
+
});
|
|
154
|
+
return {
|
|
155
|
+
asProperty: r,
|
|
156
|
+
responseType: 'string',
|
|
157
|
+
description: respDef.description,
|
|
158
|
+
mimeType: responseMimeType,
|
|
159
|
+
parseMode: responseParseMode,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
139
162
|
if (p.schema.type === 'object' && p.schema['properties'] && Object.keys(p.schema['properties']).length > 0) {
|
|
140
163
|
const inPlaceObject = NameUtils.normaliseClassname(def.operationId + 'Response$' + method);
|
|
141
164
|
const r = Property.fromDefinition(inPlaceObject, '', {
|
|
@@ -149,7 +172,9 @@ export class Method {
|
|
|
149
172
|
asProperty: r,
|
|
150
173
|
responseType: inPlaceObject,
|
|
151
174
|
description: respDef.description,
|
|
152
|
-
inPlace: p.schema
|
|
175
|
+
inPlace: p.schema,
|
|
176
|
+
mimeType: responseMimeType,
|
|
177
|
+
parseMode: responseParseMode,
|
|
153
178
|
};
|
|
154
179
|
}
|
|
155
180
|
else {
|
|
@@ -161,12 +186,16 @@ export class Method {
|
|
|
161
186
|
asProperty: r,
|
|
162
187
|
responseType: r.jsType,
|
|
163
188
|
description: respDef.description,
|
|
189
|
+
mimeType: responseMimeType,
|
|
190
|
+
parseMode: responseParseMode,
|
|
164
191
|
};
|
|
165
192
|
}
|
|
166
193
|
})
|
|
167
194
|
.getOrElseValue(({
|
|
168
195
|
asProperty: Property.fromDefinition('', 'UNKNOWN', { type: 'any' }, schemasTypes, options),
|
|
169
196
|
responseType: 'any',
|
|
197
|
+
mimeType: responseMimeType,
|
|
198
|
+
parseMode: responseParseMode,
|
|
170
199
|
}));
|
|
171
200
|
this.wrapParamsInObject = this.parameters.size > 2 || (this.body.nonEmpty) && this.parameters.nonEmpty;
|
|
172
201
|
}
|
package/dist/renderer.js
CHANGED
|
@@ -3,11 +3,11 @@ import * as fs from 'fs';
|
|
|
3
3
|
import path, { dirname } from 'path';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import * as scatsLib from 'scats';
|
|
6
|
-
const
|
|
7
|
-
const
|
|
6
|
+
const currentFilename = typeof __filename !== 'undefined' ? __filename : fileURLToPath(import.meta.url);
|
|
7
|
+
const currentDirname = typeof __dirname !== 'undefined' ? __dirname : dirname(currentFilename);
|
|
8
8
|
export class Renderer {
|
|
9
9
|
async renderToFile(schemas, methods, enableScats, targetNode, file) {
|
|
10
|
-
const view = await ejs.renderFile(path.resolve(
|
|
10
|
+
const view = await ejs.renderFile(path.resolve(currentDirname, 'templates/index.ejs'), {
|
|
11
11
|
scatsLib: scatsLib,
|
|
12
12
|
schemas: schemas,
|
|
13
13
|
methods: methods,
|
package/dist/templates/index.ejs
CHANGED
|
@@ -86,11 +86,17 @@ function objectToFormWwwEncoded(o: Record<string, any>): string {
|
|
|
86
86
|
.join('&');
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
type ResponseParseMode = 'json' | 'text';
|
|
90
|
+
|
|
91
|
+
async function requestImpl<T>(request: Request, requestOptions: RequestOptions, parseMode: ResponseParseMode = 'json'): Promise<T> {
|
|
90
92
|
const preProcessed = requestOptions.preProcessRequest ? await requestOptions.preProcessRequest(request) : request;
|
|
91
93
|
const resp = await fetch(preProcessed);
|
|
92
94
|
const postProcessed = requestOptions.postProcessResponse ? await requestOptions.postProcessResponse(preProcessed, resp) : resp;
|
|
93
95
|
if (postProcessed.ok) {
|
|
96
|
+
if (parseMode === 'text') {
|
|
97
|
+
return (await postProcessed.text()) as T;
|
|
98
|
+
}
|
|
99
|
+
|
|
94
100
|
let json: any = null;
|
|
95
101
|
if (postProcessed.headers.has('content-length')) {
|
|
96
102
|
const ctLent = postProcessed.headers.get('content-length');
|
|
@@ -140,4 +146,3 @@ methods.foreach(method => {
|
|
|
140
146
|
|
|
141
147
|
<% } %>
|
|
142
148
|
|
|
143
|
-
|
|
@@ -46,7 +46,7 @@ export async function <%= method.endpointName %><%= body.map(b => b.suffix).getO
|
|
|
46
46
|
<%_ } -%>
|
|
47
47
|
<%_ }) -%>
|
|
48
48
|
<%_ method.parameters.filter(x => x.in === 'query' && !x.required).foreach(p => { -%>
|
|
49
|
-
if (
|
|
49
|
+
if (<%= method.wrapParamsInObject ? 'params.' : '' %><%= p.uniqueName %> !== undefined) {
|
|
50
50
|
<%_ if (p.isArray) { -%>
|
|
51
51
|
<%= method.wrapParamsInObject ? 'params.' : '' %><%= p.uniqueName %>.forEach(p => {
|
|
52
52
|
queryParams.push(`<%= p.name %>=${p}`);
|
|
@@ -76,7 +76,7 @@ export async function <%= method.endpointName %><%= body.map(b => b.suffix).getO
|
|
|
76
76
|
|
|
77
77
|
const headers: HeadersInit = {
|
|
78
78
|
...requestOptions.headers || {},
|
|
79
|
-
'Accept': '
|
|
79
|
+
'Accept': '<%= method.response.mimeType %>',
|
|
80
80
|
<%_ if (body.nonEmpty && body.get.mimeType !== 'multipart/form-data') {%>
|
|
81
81
|
'Content-Type': '<%= body.get.mimeType %>',
|
|
82
82
|
<%_ } -%>
|
|
@@ -97,5 +97,5 @@ export async function <%= method.endpointName %><%= body.map(b => b.suffix).getO
|
|
|
97
97
|
signal: requestOptions.signal,
|
|
98
98
|
headers: headers
|
|
99
99
|
});
|
|
100
|
-
return requestImpl<<%- method.response.responseType %>>(request, requestOptions);
|
|
100
|
+
return requestImpl<<%- method.response.responseType %>>(request, requestOptions, '<%= method.response.parseMode %>');
|
|
101
101
|
}
|
package/jest.config.cjs
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** @type {import('jest').Config} */
|
|
2
|
+
module.exports = {
|
|
3
|
+
preset: 'ts-jest/presets/default-esm',
|
|
4
|
+
testEnvironment: 'node',
|
|
5
|
+
extensionsToTreatAsEsm: ['.ts'],
|
|
6
|
+
transform: {
|
|
7
|
+
'^.+\\.(ts|tsx)$': [
|
|
8
|
+
'ts-jest',
|
|
9
|
+
{
|
|
10
|
+
useESM: true,
|
|
11
|
+
tsconfig: './tsconfig.jest.json'
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
},
|
|
15
|
+
moduleNameMapper: {
|
|
16
|
+
'^(\\.{1,2}/.*)\\.js$': '$1'
|
|
17
|
+
}
|
|
18
|
+
};
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@penkov/swagger-code-gen",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
|
-
"generate-client": "
|
|
6
|
+
"generate-client": "dist/cli.mjs"
|
|
7
7
|
},
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/papirosko/swagger-code-gen"
|
|
10
|
+
"url": "git+https://github.com/papirosko/swagger-code-gen.git"
|
|
11
11
|
},
|
|
12
12
|
"keywords": [
|
|
13
13
|
"swagger",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"homepage": "https://github.com/papirosko/swagger-code-gen#readme",
|
|
24
24
|
"scripts": {
|
|
25
|
+
"test": "NODE_OPTIONS=--experimental-vm-modules jest",
|
|
25
26
|
"test:petstore": "node --loader ts-node/esm ./src/cli.mjs --enableScats --targetNode --url https://petstore3.swagger.io/api/v3/openapi.json tmp/petstore.ts",
|
|
26
27
|
"clean": "rimraf dist",
|
|
27
28
|
"lint": "eslint \"{src,test}/**/*.ts\" --fix",
|
|
@@ -39,10 +40,13 @@
|
|
|
39
40
|
},
|
|
40
41
|
"devDependencies": {
|
|
41
42
|
"@types/ejs": "^3.1.1",
|
|
43
|
+
"@types/jest": "^29.5.12",
|
|
42
44
|
"@typescript-eslint/eslint-plugin": "^5.10.2",
|
|
43
45
|
"@typescript-eslint/parser": "^5.10.2",
|
|
44
46
|
"eslint": "^7.30.0",
|
|
47
|
+
"jest": "^29.7.0",
|
|
45
48
|
"rimraf": "^3.0.2",
|
|
49
|
+
"ts-jest": "^29.2.5",
|
|
46
50
|
"ts-node": "^10.9.2",
|
|
47
51
|
"typescript": "^4.9.3"
|
|
48
52
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"module": "ES2020",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"target": "ES2020",
|
|
7
|
+
"isolatedModules": true,
|
|
8
|
+
"types": ["jest"]
|
|
9
|
+
},
|
|
10
|
+
"include": [
|
|
11
|
+
"src/**/*.ts",
|
|
12
|
+
"src/**/*.mjs",
|
|
13
|
+
"test/**/*.ts"
|
|
14
|
+
]
|
|
15
|
+
}
|
package/AGENTS.md
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# Repository Guidelines
|
|
2
|
-
|
|
3
|
-
## Project Structure & Module Organization
|
|
4
|
-
- Core TypeScript sources live in `src/` (CLI in `src/cli.mjs`, generators in `src/openapi.ts`, `src/renderer.ts`, helpers in `src/name.utils.ts`, etc.).
|
|
5
|
-
- Handlebars/EJS-style templates for emitted clients are under `src/templates/`; they are copied to `dist/templates/` during build.
|
|
6
|
-
- Built artifacts output to `dist/` (ESM). Keep `dist/` out of PR diffs unless you're publishing.
|
|
7
|
-
- Temporary outputs from local runs can go in `tmp/`; avoid committing generated clients.
|
|
8
|
-
|
|
9
|
-
## Build, Test, and Development Commands
|
|
10
|
-
- `npm run lint` — ESLint over `src/**/*.ts`; fixes simple style issues.
|
|
11
|
-
- `npm run build` — Type-checks with `tsc`, lints, cleans `dist/`, copies templates, and makes the CLI executable.
|
|
12
|
-
- `npm run test:petstore` — Integration sanity check that generates a client from the public Petstore spec into `tmp/petstore.ts`.
|
|
13
|
-
- Local usage example while developing: `node --loader ts-node/esm ./src/cli.mjs --url <openapi.json> --targetNode tmp/client.ts`.
|
|
14
|
-
|
|
15
|
-
## Coding Style & Naming Conventions
|
|
16
|
-
- TypeScript, ESM (`"type": "module"`). Prefer `export`/`import` syntax and avoid CommonJS.
|
|
17
|
-
- Indentation: 2 spaces; keep lines concise and avoid trailing whitespace.
|
|
18
|
-
- Naming: PascalCase for types/interfaces/classes; camelCase for functions/variables; kebab-case for filenames (`method.ts`, `components-parse.ts`).
|
|
19
|
-
- Keep pure generation logic inside `src/` modules; CLI wiring belongs in `src/cli.mjs`.
|
|
20
|
-
- Run `npm run lint` before pushing; configure editors to respect the repo’s ESLint/TypeScript settings (`tsconfig.json`, `tsconfig-eslint.json`).
|
|
21
|
-
|
|
22
|
-
## Testing Guidelines
|
|
23
|
-
- Primary check is code generation parity; use `npm run test:petstore` after generator changes to ensure emitted code compiles and fetch imports are wired when `--targetNode` is used.
|
|
24
|
-
- When adding new parsing/rendering logic, add a minimal fixture OpenAPI snippet under `tmp/` (ignored) and exercise `src/cli.mjs` with `ts-node` to confirm types render as expected.
|
|
25
|
-
- Prefer assertions in future tests under `test/` with `ts-node` if you add unit coverage; mirror file names of the modules under test.
|
|
26
|
-
|
|
27
|
-
## Commit & Pull Request Guidelines
|
|
28
|
-
- Follow existing history: `release: x.y.z` for version bumps; otherwise use `type: short-description` (e.g., `fix: handle nullable refs`, `feat: add tag filter`).
|
|
29
|
-
- Keep PRs small and scoped (parser vs renderer vs CLI). Describe the OpenAPI inputs you validated against and attach generated output snippets when relevant.
|
|
30
|
-
- Link issues when applicable; include reproduction steps for bugs and the exact CLI command used.
|
|
31
|
-
|
|
32
|
-
## Security & Configuration Tips
|
|
33
|
-
- Treat remote specs as untrusted; prefer HTTPS URLs and avoid embedding secrets in command examples.
|
|
34
|
-
- Generated clients may import `node-fetch` when `--targetNode` is passed; ensure the consumer installs it if targeting Node.
|