openapi-multitarget-codegen 0.1.0 → 0.1.1

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 +97 -13
  2. package/package.json +7 -6
  3. package/src/cli.mjs +91 -12
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,22 @@ 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 스키마 검증이 아니라, 파일 존재 여부와 기본 구조(`openapi`, `paths`)를 확인하는 최소 검사입니다.
112
+ - 즉, 정식 lint/validation을 대체하지는 않습니다.
87
113
 
88
114
  ### 추가 옵션
89
115
 
@@ -92,6 +118,7 @@ openapi-multitarget-codegen generate --input <spec> --output <dir> --target <nam
92
118
  - `--kubb-output-dir <path>`
93
119
  - 기본값: `<output>/kubb`
94
120
  - 상대 경로는 `--output` 기준으로 해석됩니다.
121
+ - `--kubb-config`와 함께 사용할 수 없습니다.
95
122
  - `--python-output-file <path>`
96
123
  - 기본값: `<output>/python/models.py`
97
124
  - 상대 경로는 `--output` 기준으로 해석됩니다.
@@ -102,6 +129,44 @@ openapi-multitarget-codegen generate --input <spec> --output <dir> --target <nam
102
129
  - 기본값: `langgraph_tools.py`
103
130
  - `--kubb-client <client>`
104
131
  - 기본값: `fetch`
132
+ - `--kubb-config`와 함께 사용할 수 없습니다.
133
+ - `--kubb-config <path>`
134
+ - Kubb native config 파일을 그대로 사용합니다.
135
+ - 이 모드에서는 해당 config 파일이 **Kubb 생성에 관한 입력/출력/플러그인 설정의 source of truth**가 됩니다.
136
+ - 다만 이 래퍼 CLI 차원에서는 `--input`, `--output`을 계속 요구합니다.
137
+ - `--input`은 사전 검증에 사용되고, `--output`은 `--clean` 처리 기준 디렉터리입니다.
138
+ - 이 패키지는 내장 preset을 만들지 않고 `kubb generate --config ...`를 pass-through 합니다.
139
+ - `--kubb-arg <value>`
140
+ - 반복 가능 옵션입니다.
141
+ - `--kubb-config`와 함께만 사용할 수 있습니다.
142
+ - 전달한 값은 `kubb generate` 뒤에 그대로 추가됩니다.
143
+ - 값이 `--watch`, `--logLevel`처럼 `--`로 시작하면 `--kubb-arg=--watch`처럼 `=` 형식으로 전달하세요.
144
+
145
+ > 주의: `--kubb-output-dir`, `--python-output-file`, `--langgraph-output-dir`에 주는 상대 경로는 현재 작업 디렉터리가 아니라 `--output` 디렉터리 기준으로 해석됩니다.
146
+
147
+ ### Kubb 사용 정책
148
+
149
+ 이 CLI의 Kubb 지원은 두 가지 모드가 있습니다.
150
+
151
+ 1. **기본 preset 모드**
152
+ - 이 패키지에 포함된 기본 Kubb 구성으로 생성합니다.
153
+ - 현재 포함된 핵심 의존성:
154
+ - `@kubb/cli`
155
+ - `@kubb/core`
156
+ - `@kubb/plugin-oas`
157
+ - `@kubb/plugin-ts`
158
+ - `@kubb/plugin-client`
159
+ 2. **native config pass-through 모드**
160
+ - `--kubb-config`를 주면 사용자의 기존 Kubb config를 그대로 사용합니다.
161
+ - Kubb의 실제 입력/출력/플러그인 설정은 config 파일 내용을 따릅니다.
162
+ - 다만 이 래퍼 CLI는 `--input`, `--output`을 계속 요구합니다.
163
+ - `--input`은 OpenAPI 파일 사전 검증에 사용되고, `--output`은 `--clean` 기준 루트입니다.
164
+ - 이 모드에서는 `--kubb-output-dir`, `--kubb-client`를 함께 사용할 수 없습니다.
165
+ - 이 경우 추가 Kubb 플러그인은 소비자 프로젝트에서 직접 설치/관리하는 것을 권장합니다.
166
+
167
+ 즉, 이 패키지는 **기본 preset에 필요한 핵심 Kubb 플러그인만 포함**하고, 나머지 Kubb 생태계는 **기존 Kubb CLI/config를 그대로 통과시키는 방향**을 따릅니다.
168
+
169
+ > 주의: `--clean`은 항상 `--output` 루트를 먼저 삭제합니다. `--kubb-config` 모드에서는 실제 Kubb 출력 위치가 config 내부 설정과 다를 수 있으므로 신중하게 사용하세요.
105
170
 
106
171
  ---
107
172
 
@@ -211,7 +276,7 @@ components:
211
276
  ### Kubb만 생성
212
277
 
213
278
  ```bash
214
- openapi-multitarget-codegen generate \
279
+ npx openapi-multitarget-codegen generate \
215
280
  --input ./specs/openapi.yaml \
216
281
  --output ./generated \
217
282
  --target kubb
@@ -220,23 +285,40 @@ openapi-multitarget-codegen generate \
220
285
  예시 출력 위치 (`--output ./generated`를 준 경우):
221
286
  - `./generated/kubb/...`
222
287
 
288
+ ### 기존 Kubb config 그대로 사용
289
+
290
+ ```bash
291
+ npx openapi-multitarget-codegen generate \
292
+ --input ./specs/openapi.yaml \
293
+ --output ./generated \
294
+ --target kubb \
295
+ --kubb-config ./kubb.config.ts \
296
+ --kubb-arg=--logLevel \
297
+ --kubb-arg=info
298
+ ```
299
+
300
+ 이 모드에서는 `kubb generate --config ./kubb.config.ts --logLevel info` 형태로 pass-through 됩니다.
301
+ 추가 Kubb 플러그인이 필요하면 해당 플러그인은 소비자 프로젝트에서 직접 설치하면 됩니다.
302
+ `--input`, `--output`은 이 래퍼 CLI의 사전 검증 및 `--clean` 기준을 위해 계속 필요하지만, Kubb config 내부의 실제 input/output 설정을 덮어쓰지는 않습니다.
303
+
223
304
  ### Python 타입만 생성
224
305
 
225
306
  ```bash
226
- openapi-multitarget-codegen generate \
307
+ npx openapi-multitarget-codegen generate \
227
308
  --input ./specs/openapi.yaml \
228
309
  --output ./generated \
229
- --target python \
230
- --python-model-type pydantic_v2.BaseModel
310
+ --target python
231
311
  ```
232
312
 
313
+ 기본 모델 타입은 `pydantic_v2.BaseModel`이므로 기본값을 그대로 사용할 때는 `--python-model-type`을 생략해도 됩니다.
314
+
233
315
  예시 출력 위치 (`--output ./generated`를 준 경우):
234
316
  - `./generated/python/models.py`
235
317
 
236
318
  ### 출력 경로까지 세분화해서 생성
237
319
 
238
320
  ```bash
239
- openapi-multitarget-codegen generate \
321
+ npx openapi-multitarget-codegen generate \
240
322
  --input ./specs/openapi.yaml \
241
323
  --output ./generated \
242
324
  --target kubb \
@@ -253,10 +335,12 @@ openapi-multitarget-codegen generate \
253
335
  - `./generated/sdk/python/api_models.py`
254
336
  - `./generated/agent/tools/catalog_tools.py`
255
337
 
338
+ 주의: 위 상대 경로들은 현재 작업 디렉터리가 아니라 `--output ./generated` 기준으로 해석됩니다.
339
+
256
340
  ### LangGraph 코드만 생성
257
341
 
258
342
  ```bash
259
- openapi-multitarget-codegen generate \
343
+ npx openapi-multitarget-codegen generate \
260
344
  --input ./specs/openapi.yaml \
261
345
  --output ./generated \
262
346
  --target langgraph
@@ -268,7 +352,7 @@ openapi-multitarget-codegen generate \
268
352
  ### 여러 타깃 동시 생성
269
353
 
270
354
  ```bash
271
- openapi-multitarget-codegen generate \
355
+ npx openapi-multitarget-codegen generate \
272
356
  --input ./specs/openapi.yaml \
273
357
  --output ./generated \
274
358
  --target kubb \
@@ -287,7 +371,7 @@ openapi-multitarget-codegen generate \
287
371
  ## 스펙 유효성 확인
288
372
 
289
373
  ```bash
290
- openapi-multitarget-codegen check-spec --input ./specs/openapi.yaml
374
+ npx openapi-multitarget-codegen check-spec --input ./specs/openapi.yaml
291
375
  ```
292
376
 
293
377
  이 명령은 다음을 검사합니다.
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.1",
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",
@@ -33,14 +33,15 @@
33
33
  "generate:example:all": "node ./src/cli.mjs generate --input ./openapi/openapi.example.yaml --output ./.tmp-output --target kubb --target python --target langgraph --clean"
34
34
  },
35
35
  "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",
36
+ "@kubb/cli": "^4.37.2",
37
+ "@kubb/core": "^4.37.2",
38
+ "@kubb/plugin-client": "^4.37.2",
39
+ "@kubb/plugin-oas": "^4.37.2",
40
+ "@kubb/plugin-ts": "^4.37.2",
41
41
  "js-yaml": "^4.1.0"
42
42
  },
43
43
  "devDependencies": {
44
+ "prettier": "^3.8.2",
44
45
  "rimraf": "^6.0.1"
45
46
  }
46
47
  }
package/src/cli.mjs CHANGED
@@ -32,17 +32,22 @@ function main() {
32
32
  throw new Error(`Unsupported command: ${command}`)
33
33
  }
34
34
 
35
- const input = path.resolve(requireOption(options, 'input'))
36
- const output = path.resolve(requireOption(options, 'output'))
37
35
  const targets = normalizeTargets(options.target)
38
36
  const pythonModelType = options['python-model-type'] || 'pydantic_v2.BaseModel'
39
37
  const kubbOutputDir = options['kubb-output-dir']
38
+ const kubbConfigPath = options['kubb-config']
39
+ const kubbArgs = Array.isArray(options['kubb-arg']) ? options['kubb-arg'] : []
40
40
  const pythonOutputFile = options['python-output-file']
41
41
  const langgraphOutputDir = options['langgraph-output-dir']
42
42
  const langgraphFileName = options['langgraph-file'] || 'langgraph_tools.py'
43
43
  const kubbClient = options['kubb-client'] || 'fetch'
44
44
  const clean = Boolean(options.clean)
45
45
 
46
+ validateKubbOptionCompatibility({ options, targets, kubbConfigPath, kubbArgs })
47
+
48
+ const input = path.resolve(requireOption(options, 'input'))
49
+ const output = path.resolve(requireOption(options, 'output'))
50
+
46
51
  validateOpenApiFile(input)
47
52
 
48
53
  if (clean) {
@@ -55,7 +60,14 @@ function main() {
55
60
 
56
61
  for (const target of targets) {
57
62
  if (target === 'kubb') {
58
- runKubb({ input, outputRoot: output, outputDir: kubbOutputDir, client: kubbClient })
63
+ runKubb({
64
+ input,
65
+ outputRoot: output,
66
+ outputDir: kubbOutputDir,
67
+ client: kubbClient,
68
+ configPath: kubbConfigPath,
69
+ extraArgs: kubbArgs,
70
+ })
59
71
  } else if (target === 'python') {
60
72
  runPython({ input, outputRoot: output, outputFile: pythonOutputFile, modelType: pythonModelType })
61
73
  } else if (target === 'langgraph') {
@@ -82,20 +94,29 @@ function parseArgs(argv) {
82
94
  continue
83
95
  }
84
96
 
85
- const key = token.slice(2)
97
+ const inlineEqualsIndex = token.indexOf('=')
98
+ const key = inlineEqualsIndex >= 0 ? token.slice(2, inlineEqualsIndex) : token.slice(2)
86
99
  if (key === 'help' || key === 'clean') {
87
100
  options[key] = true
88
101
  continue
89
102
  }
90
103
 
91
- const value = args.shift()
92
- if (!value || value.startsWith('--')) {
93
- throw new Error(`Missing value for --${key}`)
104
+ let value
105
+ if (inlineEqualsIndex >= 0) {
106
+ value = token.slice(inlineEqualsIndex + 1)
107
+ if (!value) {
108
+ throw new Error(`Missing value for --${key}`)
109
+ }
110
+ } else {
111
+ value = args.shift()
112
+ if (!value || value.startsWith('--')) {
113
+ throw new Error(`Missing value for --${key}`)
114
+ }
94
115
  }
95
116
 
96
- if (key === 'target') {
97
- if (!options.target) options.target = []
98
- options.target.push(value)
117
+ if (key === 'target' || key === 'kubb-arg') {
118
+ if (!options[key]) options[key] = []
119
+ options[key].push(value)
99
120
  } else {
100
121
  options[key] = value
101
122
  }
@@ -146,7 +167,29 @@ function validateOpenApiFile(specPath) {
146
167
  return data
147
168
  }
148
169
 
149
- function runKubb({ input, outputRoot, outputDir, client }) {
170
+ function runKubb({ input, outputRoot, outputDir, client, configPath, extraArgs = [] }) {
171
+ if (configPath) {
172
+ const resolvedConfigPath = path.resolve(configPath)
173
+ if (!fs.existsSync(resolvedConfigPath)) {
174
+ throw new Error(`Kubb config not found: ${resolvedConfigPath}`)
175
+ }
176
+
177
+ const result = spawnSync(
178
+ process.execPath,
179
+ [resolveBinScript('@kubb/cli'), 'generate', '--config', resolvedConfigPath, ...extraArgs],
180
+ {
181
+ stdio: 'inherit',
182
+ },
183
+ )
184
+
185
+ if (result.status !== 0) {
186
+ throw new Error('Kubb generation failed')
187
+ }
188
+
189
+ console.log(`✓ generated kubb output via config -> ${resolvedConfigPath}`)
190
+ return
191
+ }
192
+
150
193
  const kubbOutputDir = resolveOutputPath(outputRoot, outputDir, path.join('kubb'))
151
194
  fs.mkdirSync(kubbOutputDir, { recursive: true })
152
195
 
@@ -568,6 +611,36 @@ function resolveOutputPath(outputRoot, candidatePath, defaultRelativePath) {
568
611
  : path.resolve(outputRoot, candidatePath)
569
612
  }
570
613
 
614
+ function validateKubbOptionCompatibility({ options, targets, kubbConfigPath, kubbArgs }) {
615
+ if (!targets.includes('kubb')) {
616
+ if (kubbConfigPath) {
617
+ throw new Error('--kubb-config requires --target kubb')
618
+ }
619
+ if (kubbArgs.length > 0) {
620
+ throw new Error('--kubb-arg requires --target kubb')
621
+ }
622
+ return
623
+ }
624
+
625
+ if (kubbArgs.length > 0 && !kubbConfigPath) {
626
+ throw new Error('--kubb-arg requires --kubb-config')
627
+ }
628
+
629
+ if (!kubbConfigPath) {
630
+ return
631
+ }
632
+
633
+ const conflicting = [
634
+ ['kubb-client', options['kubb-client']],
635
+ ['kubb-output-dir', options['kubb-output-dir']],
636
+ ].filter(([, value]) => value)
637
+
638
+ if (conflicting.length > 0) {
639
+ const names = conflicting.map(([name]) => `--${name}`).join(', ')
640
+ throw new Error(`${names} cannot be used together with --kubb-config`)
641
+ }
642
+ }
643
+
571
644
  function toPythonLiteral(value, indent = 0) {
572
645
  const spaces = ' '.repeat(indent)
573
646
  const childSpaces = ' '.repeat(indent + 1)
@@ -717,17 +790,23 @@ Usage:
717
790
  openapi-multitarget-codegen check-spec --input <spec>
718
791
 
719
792
  Options:
720
- --input <path> OpenAPI file path (.yaml or .json)
793
+ --input <path> OpenAPI file path (.yaml, .yml, or .json)
721
794
  --output <dir> Output root directory
722
795
  --target <name> Generation target (repeatable)
723
796
  --python-model-type <type> Python output model type (default: pydantic_v2.BaseModel)
724
797
  --kubb-output-dir <path> Kubb output directory (default: <output>/kubb)
798
+ --kubb-config <path> Use an existing Kubb config file as the source of truth
799
+ --kubb-arg <value> Pass an extra argument to 'kubb generate' (repeatable, requires --kubb-config)
725
800
  --python-output-file <path> Python output file (default: <output>/python/models.py)
726
801
  --langgraph-output-dir <path> LangGraph output directory (default: <output>/langgraph)
727
802
  --langgraph-file <name> LangGraph output filename (default: langgraph_tools.py)
728
803
  --kubb-client <client> Kubb client type (default: fetch)
729
804
  --clean Remove output root before generation
730
805
  --help Show help
806
+
807
+ Examples:
808
+ openapi-multitarget-codegen generate --input ./openapi/openapi.example.yaml --output ./.tmp-output --target kubb --clean
809
+ openapi-multitarget-codegen generate --input ./openapi/openapi.example.yaml --output ./.tmp-output --target kubb --kubb-config ./kubb.config.ts --kubb-arg=--watch
731
810
  `)
732
811
  }
733
812