openapi-multitarget-codegen 0.1.0 → 0.1.2
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/README.md +111 -15
- package/package.json +9 -6
- package/src/cli.mjs +140 -14
package/README.md
CHANGED
|
@@ -36,6 +36,26 @@ pnpm add -D openapi-multitarget-codegen
|
|
|
36
36
|
npm install -g openapi-multitarget-codegen
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
### 실행 방법
|
|
40
|
+
|
|
41
|
+
- dev dependency로 설치했다면 다음 형태로 실행하세요.
|
|
42
|
+
- `npx openapi-multitarget-codegen ...`
|
|
43
|
+
- `pnpm exec openapi-multitarget-codegen ...`
|
|
44
|
+
- 전역 설치한 경우에만 `openapi-multitarget-codegen ...` 명령을 직접 사용할 수 있습니다.
|
|
45
|
+
|
|
46
|
+
### 처음 사용해보기
|
|
47
|
+
|
|
48
|
+
1. 스펙 최소 구조를 확인합니다.
|
|
49
|
+
2. 단일 타깃으로 먼저 생성해 봅니다.
|
|
50
|
+
3. 필요한 경우 여러 타깃을 한 번에 생성합니다.
|
|
51
|
+
4. Kubb 고급 설정이 필요할 때만 `--kubb-config` 모드로 넘어갑니다.
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npx openapi-multitarget-codegen check-spec --input ./specs/openapi.yaml
|
|
55
|
+
npx openapi-multitarget-codegen generate --input ./specs/openapi.yaml --output ./generated --target kubb
|
|
56
|
+
npx openapi-multitarget-codegen generate --input ./specs/openapi.yaml --output ./generated --target kubb --target python --target langgraph
|
|
57
|
+
```
|
|
58
|
+
|
|
39
59
|
---
|
|
40
60
|
|
|
41
61
|
## 문서
|
|
@@ -74,16 +94,23 @@ OpenAPI operation을 기준으로 LangGraph/LangChain StructuredTool 초안 코
|
|
|
74
94
|
## CLI 사용법
|
|
75
95
|
|
|
76
96
|
```bash
|
|
77
|
-
openapi-multitarget-codegen generate --input <spec> --output <dir> --target <
|
|
97
|
+
openapi-multitarget-codegen generate --input <spec> --output <dir> --target <kubb|python|langgraph> [--target ...]
|
|
98
|
+
openapi-multitarget-codegen check-spec --input <spec>
|
|
78
99
|
```
|
|
79
100
|
|
|
80
101
|
### 공통 옵션
|
|
81
102
|
|
|
82
|
-
- `--input <path
|
|
83
|
-
- `--output <dir
|
|
84
|
-
- `--target <name
|
|
103
|
+
- `--input <path>` (필수): OpenAPI 파일 경로 (`.yaml`, `.yml`, `.json`)
|
|
104
|
+
- `--output <dir>` (필수, `generate` 전용): 생성 결과를 쓸 출력 루트 디렉터리
|
|
105
|
+
- `--target <name>` (필수, `generate` 전용): 생성 타깃 (`kubb`, `python`, `langgraph`)
|
|
85
106
|
- 여러 개를 생성하려면 `--target`을 반복해서 넘깁니다.
|
|
86
|
-
- `--clean
|
|
107
|
+
- `--clean` (`generate` 전용): 출력 루트를 먼저 비웁니다.
|
|
108
|
+
|
|
109
|
+
### `check-spec` 범위
|
|
110
|
+
|
|
111
|
+
- `check-spec`은 파일 존재 여부와 기본 구조(`openapi`, `paths`) 확인에 더해, OpenAPI 문서를 실제로 parse/validate 합니다.
|
|
112
|
+
- unresolved `$ref`, 잘못된 스키마 구조, 문서 수준의 OpenAPI 오류를 조기에 잡는 용도입니다.
|
|
113
|
+
- 다만 **target별 생성기 호환성까지 전부 보장하는 것은 아닙니다.** 즉, `kubb`, `python`, `langgraph` 각각의 세부 제약은 실제 generate 단계에서 추가 확인이 필요할 수 있습니다.
|
|
87
114
|
|
|
88
115
|
### 추가 옵션
|
|
89
116
|
|
|
@@ -92,6 +119,13 @@ openapi-multitarget-codegen generate --input <spec> --output <dir> --target <nam
|
|
|
92
119
|
- `--kubb-output-dir <path>`
|
|
93
120
|
- 기본값: `<output>/kubb`
|
|
94
121
|
- 상대 경로는 `--output` 기준으로 해석됩니다.
|
|
122
|
+
- `--kubb-config`와 함께 사용할 수 없습니다.
|
|
123
|
+
- `--kubb-format <mode>`
|
|
124
|
+
- 기본값: `false`
|
|
125
|
+
- preset 모드에서만 사용합니다.
|
|
126
|
+
- 허용값: `auto`, `prettier`, `biome`, `oxfmt`, `false`
|
|
127
|
+
- 기본값 `false`는 Kubb 생성은 유지하되 formatter 미설치로 인한 혼란스러운 메시지를 피하기 위한 설정입니다.
|
|
128
|
+
- `--kubb-config`와 함께 사용할 수 없습니다.
|
|
95
129
|
- `--python-output-file <path>`
|
|
96
130
|
- 기본값: `<output>/python/models.py`
|
|
97
131
|
- 상대 경로는 `--output` 기준으로 해석됩니다.
|
|
@@ -102,6 +136,46 @@ openapi-multitarget-codegen generate --input <spec> --output <dir> --target <nam
|
|
|
102
136
|
- 기본값: `langgraph_tools.py`
|
|
103
137
|
- `--kubb-client <client>`
|
|
104
138
|
- 기본값: `fetch`
|
|
139
|
+
- `--kubb-config`와 함께 사용할 수 없습니다.
|
|
140
|
+
- `--kubb-config <path>`
|
|
141
|
+
- Kubb native config 파일을 그대로 사용합니다.
|
|
142
|
+
- 이 모드에서는 해당 config 파일이 **Kubb 생성에 관한 입력/출력/플러그인 설정의 source of truth**가 됩니다.
|
|
143
|
+
- 다만 이 래퍼 CLI 차원에서는 `--input`, `--output`을 계속 요구합니다.
|
|
144
|
+
- `--input`은 사전 검증에 사용되고, `--output`은 `--clean` 처리 기준 디렉터리입니다.
|
|
145
|
+
- 이 패키지는 내장 preset을 만들지 않고 `kubb generate --config ...`를 pass-through 합니다.
|
|
146
|
+
- `--kubb-arg <value>`
|
|
147
|
+
- 반복 가능 옵션입니다.
|
|
148
|
+
- `--kubb-config`와 함께만 사용할 수 있습니다.
|
|
149
|
+
- 전달한 값은 `kubb generate` 뒤에 그대로 추가됩니다.
|
|
150
|
+
- 값이 `--watch`, `--logLevel`처럼 `--`로 시작하면 `--kubb-arg=--watch`처럼 `=` 형식으로 전달하세요.
|
|
151
|
+
|
|
152
|
+
> 주의: `--kubb-output-dir`, `--python-output-file`, `--langgraph-output-dir`에 주는 상대 경로는 현재 작업 디렉터리가 아니라 `--output` 디렉터리 기준으로 해석됩니다.
|
|
153
|
+
|
|
154
|
+
### Kubb 사용 정책
|
|
155
|
+
|
|
156
|
+
이 CLI의 Kubb 지원은 두 가지 모드가 있습니다.
|
|
157
|
+
|
|
158
|
+
1. **기본 preset 모드**
|
|
159
|
+
- 이 패키지에 포함된 기본 Kubb 구성으로 생성합니다.
|
|
160
|
+
- 기본 formatter 설정은 `false`입니다.
|
|
161
|
+
- formatter를 켜고 싶다면 `--kubb-format auto` 또는 `--kubb-format prettier` 같은 식으로 명시합니다.
|
|
162
|
+
- 현재 포함된 핵심 의존성:
|
|
163
|
+
- `@kubb/cli`
|
|
164
|
+
- `@kubb/core`
|
|
165
|
+
- `@kubb/plugin-oas`
|
|
166
|
+
- `@kubb/plugin-ts`
|
|
167
|
+
- `@kubb/plugin-client`
|
|
168
|
+
2. **native config pass-through 모드**
|
|
169
|
+
- `--kubb-config`를 주면 사용자의 기존 Kubb config를 그대로 사용합니다.
|
|
170
|
+
- Kubb의 실제 입력/출력/플러그인/formatter 설정은 config 파일 내용을 따릅니다.
|
|
171
|
+
- 다만 이 래퍼 CLI는 `--input`, `--output`을 계속 요구합니다.
|
|
172
|
+
- `--input`은 OpenAPI 파일 사전 검증에 사용되고, `--output`은 `--clean` 기준 루트입니다.
|
|
173
|
+
- 이 모드에서는 `--kubb-output-dir`, `--kubb-client`, `--kubb-format`을 함께 사용할 수 없습니다.
|
|
174
|
+
- 이 경우 추가 Kubb 플러그인은 소비자 프로젝트에서 직접 설치/관리하는 것을 권장합니다.
|
|
175
|
+
|
|
176
|
+
즉, 이 패키지는 **기본 preset에 필요한 핵심 Kubb 플러그인만 포함**하고, 나머지 Kubb 생태계는 **기존 Kubb CLI/config를 그대로 통과시키는 방향**을 따릅니다.
|
|
177
|
+
|
|
178
|
+
> 주의: `--clean`은 항상 `--output` 루트를 먼저 삭제합니다. `--kubb-config` 모드에서는 실제 Kubb 출력 위치가 config 내부 설정과 다를 수 있으므로 신중하게 사용하세요.
|
|
105
179
|
|
|
106
180
|
---
|
|
107
181
|
|
|
@@ -211,7 +285,7 @@ components:
|
|
|
211
285
|
### Kubb만 생성
|
|
212
286
|
|
|
213
287
|
```bash
|
|
214
|
-
openapi-multitarget-codegen generate \
|
|
288
|
+
npx openapi-multitarget-codegen generate \
|
|
215
289
|
--input ./specs/openapi.yaml \
|
|
216
290
|
--output ./generated \
|
|
217
291
|
--target kubb
|
|
@@ -220,23 +294,40 @@ openapi-multitarget-codegen generate \
|
|
|
220
294
|
예시 출력 위치 (`--output ./generated`를 준 경우):
|
|
221
295
|
- `./generated/kubb/...`
|
|
222
296
|
|
|
297
|
+
### 기존 Kubb config 그대로 사용
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
npx openapi-multitarget-codegen generate \
|
|
301
|
+
--input ./specs/openapi.yaml \
|
|
302
|
+
--output ./generated \
|
|
303
|
+
--target kubb \
|
|
304
|
+
--kubb-config ./kubb.config.ts \
|
|
305
|
+
--kubb-arg=--logLevel \
|
|
306
|
+
--kubb-arg=info
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
이 모드에서는 `kubb generate --config ./kubb.config.ts --logLevel info` 형태로 pass-through 됩니다.
|
|
310
|
+
추가 Kubb 플러그인이 필요하면 해당 플러그인은 소비자 프로젝트에서 직접 설치하면 됩니다.
|
|
311
|
+
`--input`, `--output`은 이 래퍼 CLI의 사전 검증 및 `--clean` 기준을 위해 계속 필요하지만, Kubb config 내부의 실제 input/output 설정을 덮어쓰지는 않습니다.
|
|
312
|
+
|
|
223
313
|
### Python 타입만 생성
|
|
224
314
|
|
|
225
315
|
```bash
|
|
226
|
-
openapi-multitarget-codegen generate \
|
|
316
|
+
npx openapi-multitarget-codegen generate \
|
|
227
317
|
--input ./specs/openapi.yaml \
|
|
228
318
|
--output ./generated \
|
|
229
|
-
--target python
|
|
230
|
-
--python-model-type pydantic_v2.BaseModel
|
|
319
|
+
--target python
|
|
231
320
|
```
|
|
232
321
|
|
|
322
|
+
기본 모델 타입은 `pydantic_v2.BaseModel`이므로 기본값을 그대로 사용할 때는 `--python-model-type`을 생략해도 됩니다.
|
|
323
|
+
|
|
233
324
|
예시 출력 위치 (`--output ./generated`를 준 경우):
|
|
234
325
|
- `./generated/python/models.py`
|
|
235
326
|
|
|
236
327
|
### 출력 경로까지 세분화해서 생성
|
|
237
328
|
|
|
238
329
|
```bash
|
|
239
|
-
openapi-multitarget-codegen generate \
|
|
330
|
+
npx openapi-multitarget-codegen generate \
|
|
240
331
|
--input ./specs/openapi.yaml \
|
|
241
332
|
--output ./generated \
|
|
242
333
|
--target kubb \
|
|
@@ -253,10 +344,12 @@ openapi-multitarget-codegen generate \
|
|
|
253
344
|
- `./generated/sdk/python/api_models.py`
|
|
254
345
|
- `./generated/agent/tools/catalog_tools.py`
|
|
255
346
|
|
|
347
|
+
주의: 위 상대 경로들은 현재 작업 디렉터리가 아니라 `--output ./generated` 기준으로 해석됩니다.
|
|
348
|
+
|
|
256
349
|
### LangGraph 코드만 생성
|
|
257
350
|
|
|
258
351
|
```bash
|
|
259
|
-
openapi-multitarget-codegen generate \
|
|
352
|
+
npx openapi-multitarget-codegen generate \
|
|
260
353
|
--input ./specs/openapi.yaml \
|
|
261
354
|
--output ./generated \
|
|
262
355
|
--target langgraph
|
|
@@ -268,7 +361,7 @@ openapi-multitarget-codegen generate \
|
|
|
268
361
|
### 여러 타깃 동시 생성
|
|
269
362
|
|
|
270
363
|
```bash
|
|
271
|
-
openapi-multitarget-codegen generate \
|
|
364
|
+
npx openapi-multitarget-codegen generate \
|
|
272
365
|
--input ./specs/openapi.yaml \
|
|
273
366
|
--output ./generated \
|
|
274
367
|
--target kubb \
|
|
@@ -287,13 +380,16 @@ openapi-multitarget-codegen generate \
|
|
|
287
380
|
## 스펙 유효성 확인
|
|
288
381
|
|
|
289
382
|
```bash
|
|
290
|
-
openapi-multitarget-codegen check-spec --input ./specs/openapi.yaml
|
|
383
|
+
npx openapi-multitarget-codegen check-spec --input ./specs/openapi.yaml
|
|
291
384
|
```
|
|
292
385
|
|
|
293
386
|
이 명령은 다음을 검사합니다.
|
|
294
387
|
- 파일 존재 여부
|
|
295
|
-
- `openapi`
|
|
296
|
-
-
|
|
388
|
+
- 기본 구조(`openapi`, `paths`) 존재 여부
|
|
389
|
+
- OpenAPI 문서 parse/validate
|
|
390
|
+
- unresolved `$ref`, 잘못된 스키마 구조 등 문서 수준 오류
|
|
391
|
+
|
|
392
|
+
다만 이 단계가 `kubb`, `python`, `langgraph` 각 타깃 생성기의 세부 호환성까지 모두 보장하는 것은 아닙니다. 타깃별 제약은 실제 `generate` 단계에서 추가 확인이 필요할 수 있습니다.
|
|
297
393
|
|
|
298
394
|
---
|
|
299
395
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openapi-multitarget-codegen",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenAPI-based multi-target code generation CLI for Kubb, Python types, and LangGraph tools",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"clean": "rimraf .tmp-output",
|
|
29
|
+
"test": "node --test tests/*.test.mjs",
|
|
29
30
|
"check:example-spec": "node ./src/cli.mjs check-spec --input ./openapi/openapi.example.yaml",
|
|
30
31
|
"generate:example:kubb": "node ./src/cli.mjs generate --input ./openapi/openapi.example.yaml --output ./.tmp-output --target kubb --clean",
|
|
31
32
|
"generate:example:python": "node ./src/cli.mjs generate --input ./openapi/openapi.example.yaml --output ./.tmp-output --target python --clean",
|
|
@@ -33,14 +34,16 @@
|
|
|
33
34
|
"generate:example:all": "node ./src/cli.mjs generate --input ./openapi/openapi.example.yaml --output ./.tmp-output --target kubb --target python --target langgraph --clean"
|
|
34
35
|
},
|
|
35
36
|
"dependencies": {
|
|
36
|
-
"@
|
|
37
|
-
"@kubb/
|
|
38
|
-
"@kubb/
|
|
39
|
-
"@kubb/plugin-
|
|
40
|
-
"@kubb/plugin-
|
|
37
|
+
"@apidevtools/swagger-parser": "^12.1.0",
|
|
38
|
+
"@kubb/cli": "^4.37.2",
|
|
39
|
+
"@kubb/core": "^4.37.2",
|
|
40
|
+
"@kubb/plugin-client": "^4.37.2",
|
|
41
|
+
"@kubb/plugin-oas": "^4.37.2",
|
|
42
|
+
"@kubb/plugin-ts": "^4.37.2",
|
|
41
43
|
"js-yaml": "^4.1.0"
|
|
42
44
|
},
|
|
43
45
|
"devDependencies": {
|
|
46
|
+
"prettier": "^3.8.2",
|
|
44
47
|
"rimraf": "^6.0.1"
|
|
45
48
|
}
|
|
46
49
|
}
|
package/src/cli.mjs
CHANGED
|
@@ -9,10 +9,11 @@ import { fileURLToPath } from 'node:url'
|
|
|
9
9
|
import yaml from 'js-yaml'
|
|
10
10
|
|
|
11
11
|
const require = createRequire(import.meta.url)
|
|
12
|
+
const SwaggerParser = require('@apidevtools/swagger-parser')
|
|
12
13
|
const CLI_FILE = fileURLToPath(import.meta.url)
|
|
13
14
|
const PACKAGE_ROOT = path.resolve(path.dirname(CLI_FILE), '..')
|
|
14
15
|
|
|
15
|
-
function main() {
|
|
16
|
+
async function main() {
|
|
16
17
|
const { command, options } = parseArgs(process.argv.slice(2))
|
|
17
18
|
|
|
18
19
|
try {
|
|
@@ -23,7 +24,7 @@ function main() {
|
|
|
23
24
|
|
|
24
25
|
if (command === 'check-spec') {
|
|
25
26
|
const input = requireOption(options, 'input')
|
|
26
|
-
|
|
27
|
+
await validateOpenApiSpec(path.resolve(input))
|
|
27
28
|
console.log(`✓ valid OpenAPI spec: ${path.resolve(input)}`)
|
|
28
29
|
process.exit(0)
|
|
29
30
|
}
|
|
@@ -32,17 +33,23 @@ function main() {
|
|
|
32
33
|
throw new Error(`Unsupported command: ${command}`)
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
const input = path.resolve(requireOption(options, 'input'))
|
|
36
|
-
const output = path.resolve(requireOption(options, 'output'))
|
|
37
36
|
const targets = normalizeTargets(options.target)
|
|
38
37
|
const pythonModelType = options['python-model-type'] || 'pydantic_v2.BaseModel'
|
|
39
38
|
const kubbOutputDir = options['kubb-output-dir']
|
|
39
|
+
const kubbConfigPath = options['kubb-config']
|
|
40
|
+
const kubbArgs = Array.isArray(options['kubb-arg']) ? options['kubb-arg'] : []
|
|
41
|
+
const kubbFormat = normalizeKubbFormat(options['kubb-format'])
|
|
40
42
|
const pythonOutputFile = options['python-output-file']
|
|
41
43
|
const langgraphOutputDir = options['langgraph-output-dir']
|
|
42
44
|
const langgraphFileName = options['langgraph-file'] || 'langgraph_tools.py'
|
|
43
45
|
const kubbClient = options['kubb-client'] || 'fetch'
|
|
44
46
|
const clean = Boolean(options.clean)
|
|
45
47
|
|
|
48
|
+
validateKubbOptionCompatibility({ options, targets, kubbConfigPath, kubbArgs })
|
|
49
|
+
|
|
50
|
+
const input = path.resolve(requireOption(options, 'input'))
|
|
51
|
+
const output = path.resolve(requireOption(options, 'output'))
|
|
52
|
+
|
|
46
53
|
validateOpenApiFile(input)
|
|
47
54
|
|
|
48
55
|
if (clean) {
|
|
@@ -55,7 +62,15 @@ function main() {
|
|
|
55
62
|
|
|
56
63
|
for (const target of targets) {
|
|
57
64
|
if (target === 'kubb') {
|
|
58
|
-
runKubb({
|
|
65
|
+
runKubb({
|
|
66
|
+
input,
|
|
67
|
+
outputRoot: output,
|
|
68
|
+
outputDir: kubbOutputDir,
|
|
69
|
+
client: kubbClient,
|
|
70
|
+
configPath: kubbConfigPath,
|
|
71
|
+
extraArgs: kubbArgs,
|
|
72
|
+
format: kubbFormat,
|
|
73
|
+
})
|
|
59
74
|
} else if (target === 'python') {
|
|
60
75
|
runPython({ input, outputRoot: output, outputFile: pythonOutputFile, modelType: pythonModelType })
|
|
61
76
|
} else if (target === 'langgraph') {
|
|
@@ -82,20 +97,29 @@ function parseArgs(argv) {
|
|
|
82
97
|
continue
|
|
83
98
|
}
|
|
84
99
|
|
|
85
|
-
const
|
|
100
|
+
const inlineEqualsIndex = token.indexOf('=')
|
|
101
|
+
const key = inlineEqualsIndex >= 0 ? token.slice(2, inlineEqualsIndex) : token.slice(2)
|
|
86
102
|
if (key === 'help' || key === 'clean') {
|
|
87
103
|
options[key] = true
|
|
88
104
|
continue
|
|
89
105
|
}
|
|
90
106
|
|
|
91
|
-
|
|
92
|
-
if (
|
|
93
|
-
|
|
107
|
+
let value
|
|
108
|
+
if (inlineEqualsIndex >= 0) {
|
|
109
|
+
value = token.slice(inlineEqualsIndex + 1)
|
|
110
|
+
if (!value) {
|
|
111
|
+
throw new Error(`Missing value for --${key}`)
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
value = args.shift()
|
|
115
|
+
if (!value || value.startsWith('--')) {
|
|
116
|
+
throw new Error(`Missing value for --${key}`)
|
|
117
|
+
}
|
|
94
118
|
}
|
|
95
119
|
|
|
96
|
-
if (key === 'target') {
|
|
97
|
-
if (!options
|
|
98
|
-
options.
|
|
120
|
+
if (key === 'target' || key === 'kubb-arg') {
|
|
121
|
+
if (!options[key]) options[key] = []
|
|
122
|
+
options[key].push(value)
|
|
99
123
|
} else {
|
|
100
124
|
options[key] = value
|
|
101
125
|
}
|
|
@@ -112,6 +136,20 @@ function normalizeTargets(targetValue) {
|
|
|
112
136
|
return values
|
|
113
137
|
}
|
|
114
138
|
|
|
139
|
+
function normalizeKubbFormat(value) {
|
|
140
|
+
if (!value) {
|
|
141
|
+
return false
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const normalized = String(value).toLowerCase()
|
|
145
|
+
const allowed = new Set(['auto', 'prettier', 'biome', 'oxfmt', 'false'])
|
|
146
|
+
if (!allowed.has(normalized)) {
|
|
147
|
+
throw new Error("--kubb-format must be one of: auto, prettier, biome, oxfmt, false")
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return normalized === 'false' ? false : normalized
|
|
151
|
+
}
|
|
152
|
+
|
|
115
153
|
function requireOption(options, key) {
|
|
116
154
|
const value = options[key]
|
|
117
155
|
if (!value) {
|
|
@@ -125,6 +163,10 @@ function validateOpenApiFile(specPath) {
|
|
|
125
163
|
throw new Error(`OpenAPI spec not found: ${specPath}`)
|
|
126
164
|
}
|
|
127
165
|
|
|
166
|
+
return loadOpenApiFile(specPath)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function loadOpenApiFile(specPath) {
|
|
128
170
|
const text = fs.readFileSync(specPath, 'utf8')
|
|
129
171
|
let data
|
|
130
172
|
try {
|
|
@@ -146,7 +188,52 @@ function validateOpenApiFile(specPath) {
|
|
|
146
188
|
return data
|
|
147
189
|
}
|
|
148
190
|
|
|
149
|
-
function
|
|
191
|
+
async function validateOpenApiSpec(specPath) {
|
|
192
|
+
validateOpenApiFile(specPath)
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
await SwaggerParser.validate(specPath)
|
|
196
|
+
} catch (error) {
|
|
197
|
+
const details = []
|
|
198
|
+
const primaryMessage = error?.message || 'OpenAPI validation failed'
|
|
199
|
+
details.push(primaryMessage)
|
|
200
|
+
|
|
201
|
+
const parserDetails = Array.isArray(error?.details) ? error.details : []
|
|
202
|
+
for (const detail of parserDetails.slice(0, 5)) {
|
|
203
|
+
const pathText = detail?.path ? `${detail.path}: ` : ''
|
|
204
|
+
const messageText = detail?.message || detail?.toString?.() || ''
|
|
205
|
+
if (messageText) {
|
|
206
|
+
details.push(`${pathText}${messageText}`)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
throw new Error(`OpenAPI validation failed:\n- ${details.join('\n- ')}`)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function runKubb({ input, outputRoot, outputDir, client, configPath, extraArgs = [], format }) {
|
|
215
|
+
if (configPath) {
|
|
216
|
+
const resolvedConfigPath = path.resolve(configPath)
|
|
217
|
+
if (!fs.existsSync(resolvedConfigPath)) {
|
|
218
|
+
throw new Error(`Kubb config not found: ${resolvedConfigPath}`)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const result = spawnSync(
|
|
222
|
+
process.execPath,
|
|
223
|
+
[resolveBinScript('@kubb/cli'), 'generate', '--config', resolvedConfigPath, ...extraArgs],
|
|
224
|
+
{
|
|
225
|
+
stdio: 'inherit',
|
|
226
|
+
},
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
if (result.status !== 0) {
|
|
230
|
+
throw new Error('Kubb generation failed')
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
console.log(`✓ generated kubb output via config -> ${resolvedConfigPath}`)
|
|
234
|
+
return
|
|
235
|
+
}
|
|
236
|
+
|
|
150
237
|
const kubbOutputDir = resolveOutputPath(outputRoot, outputDir, path.join('kubb'))
|
|
151
238
|
fs.mkdirSync(kubbOutputDir, { recursive: true })
|
|
152
239
|
|
|
@@ -168,6 +255,7 @@ export default defineConfig({
|
|
|
168
255
|
output: {
|
|
169
256
|
path: ${JSON.stringify(kubbOutputDir)},
|
|
170
257
|
clean: true,
|
|
258
|
+
format: ${format === false ? 'false' : JSON.stringify(format)},
|
|
171
259
|
},
|
|
172
260
|
plugins: [
|
|
173
261
|
pluginOas(),
|
|
@@ -568,6 +656,37 @@ function resolveOutputPath(outputRoot, candidatePath, defaultRelativePath) {
|
|
|
568
656
|
: path.resolve(outputRoot, candidatePath)
|
|
569
657
|
}
|
|
570
658
|
|
|
659
|
+
function validateKubbOptionCompatibility({ options, targets, kubbConfigPath, kubbArgs }) {
|
|
660
|
+
if (!targets.includes('kubb')) {
|
|
661
|
+
if (kubbConfigPath) {
|
|
662
|
+
throw new Error('--kubb-config requires --target kubb')
|
|
663
|
+
}
|
|
664
|
+
if (kubbArgs.length > 0) {
|
|
665
|
+
throw new Error('--kubb-arg requires --target kubb')
|
|
666
|
+
}
|
|
667
|
+
return
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
if (kubbArgs.length > 0 && !kubbConfigPath) {
|
|
671
|
+
throw new Error('--kubb-arg requires --kubb-config')
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
if (!kubbConfigPath) {
|
|
675
|
+
return
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
const conflicting = [
|
|
679
|
+
['kubb-client', options['kubb-client']],
|
|
680
|
+
['kubb-output-dir', options['kubb-output-dir']],
|
|
681
|
+
['kubb-format', options['kubb-format']],
|
|
682
|
+
].filter(([, value]) => value)
|
|
683
|
+
|
|
684
|
+
if (conflicting.length > 0) {
|
|
685
|
+
const names = conflicting.map(([name]) => `--${name}`).join(', ')
|
|
686
|
+
throw new Error(`${names} cannot be used together with --kubb-config`)
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
571
690
|
function toPythonLiteral(value, indent = 0) {
|
|
572
691
|
const spaces = ' '.repeat(indent)
|
|
573
692
|
const childSpaces = ' '.repeat(indent + 1)
|
|
@@ -717,17 +836,24 @@ Usage:
|
|
|
717
836
|
openapi-multitarget-codegen check-spec --input <spec>
|
|
718
837
|
|
|
719
838
|
Options:
|
|
720
|
-
--input <path> OpenAPI file path (.yaml or .json)
|
|
839
|
+
--input <path> OpenAPI file path (.yaml, .yml, or .json)
|
|
721
840
|
--output <dir> Output root directory
|
|
722
841
|
--target <name> Generation target (repeatable)
|
|
723
842
|
--python-model-type <type> Python output model type (default: pydantic_v2.BaseModel)
|
|
724
843
|
--kubb-output-dir <path> Kubb output directory (default: <output>/kubb)
|
|
844
|
+
--kubb-config <path> Use an existing Kubb config file as the source of truth
|
|
845
|
+
--kubb-arg <value> Pass an extra argument to 'kubb generate' (repeatable, requires --kubb-config)
|
|
846
|
+
--kubb-format <mode> Kubb formatter for preset mode (default: false; auto|prettier|biome|oxfmt|false)
|
|
725
847
|
--python-output-file <path> Python output file (default: <output>/python/models.py)
|
|
726
848
|
--langgraph-output-dir <path> LangGraph output directory (default: <output>/langgraph)
|
|
727
849
|
--langgraph-file <name> LangGraph output filename (default: langgraph_tools.py)
|
|
728
850
|
--kubb-client <client> Kubb client type (default: fetch)
|
|
729
851
|
--clean Remove output root before generation
|
|
730
852
|
--help Show help
|
|
853
|
+
|
|
854
|
+
Examples:
|
|
855
|
+
openapi-multitarget-codegen generate --input ./openapi/openapi.example.yaml --output ./.tmp-output --target kubb --clean
|
|
856
|
+
openapi-multitarget-codegen generate --input ./openapi/openapi.example.yaml --output ./.tmp-output --target kubb --kubb-config ./kubb.config.ts --kubb-arg=--watch
|
|
731
857
|
`)
|
|
732
858
|
}
|
|
733
859
|
|