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.
Files changed (3) hide show
  1. package/README.md +111 -15
  2. package/package.json +9 -6
  3. 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 <name>
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>`: OpenAPI 파일 경로 (`.yaml`, `.yml`, `.json`)
83
- - `--output <dir>`: 생성 결과를 쓸 출력 루트 디렉터리
84
- - `--target <name>`: 생성 타깃 (`kubb`, `python`, `langgraph`)
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
- - `paths` 객체 존재 여부
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.0",
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
- "@kubb/cli": "^3.12.0",
37
- "@kubb/core": "^3.12.0",
38
- "@kubb/plugin-client": "^3.12.0",
39
- "@kubb/plugin-oas": "^3.12.0",
40
- "@kubb/plugin-ts": "^3.12.0",
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
- validateOpenApiFile(path.resolve(input))
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({ input, outputRoot: output, outputDir: kubbOutputDir, client: kubbClient })
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 key = token.slice(2)
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
- const value = args.shift()
92
- if (!value || value.startsWith('--')) {
93
- throw new Error(`Missing value for --${key}`)
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.target) options.target = []
98
- options.target.push(value)
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 runKubb({ input, outputRoot, outputDir, client }) {
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