openapi-multitarget-codegen 0.1.1 → 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 +18 -6
- package/package.json +3 -1
- package/src/cli.mjs +50 -3
package/README.md
CHANGED
|
@@ -108,8 +108,9 @@ openapi-multitarget-codegen check-spec --input <spec>
|
|
|
108
108
|
|
|
109
109
|
### `check-spec` 범위
|
|
110
110
|
|
|
111
|
-
- `check-spec`은
|
|
112
|
-
-
|
|
111
|
+
- `check-spec`은 파일 존재 여부와 기본 구조(`openapi`, `paths`) 확인에 더해, OpenAPI 문서를 실제로 parse/validate 합니다.
|
|
112
|
+
- unresolved `$ref`, 잘못된 스키마 구조, 문서 수준의 OpenAPI 오류를 조기에 잡는 용도입니다.
|
|
113
|
+
- 다만 **target별 생성기 호환성까지 전부 보장하는 것은 아닙니다.** 즉, `kubb`, `python`, `langgraph` 각각의 세부 제약은 실제 generate 단계에서 추가 확인이 필요할 수 있습니다.
|
|
113
114
|
|
|
114
115
|
### 추가 옵션
|
|
115
116
|
|
|
@@ -119,6 +120,12 @@ openapi-multitarget-codegen check-spec --input <spec>
|
|
|
119
120
|
- 기본값: `<output>/kubb`
|
|
120
121
|
- 상대 경로는 `--output` 기준으로 해석됩니다.
|
|
121
122
|
- `--kubb-config`와 함께 사용할 수 없습니다.
|
|
123
|
+
- `--kubb-format <mode>`
|
|
124
|
+
- 기본값: `false`
|
|
125
|
+
- preset 모드에서만 사용합니다.
|
|
126
|
+
- 허용값: `auto`, `prettier`, `biome`, `oxfmt`, `false`
|
|
127
|
+
- 기본값 `false`는 Kubb 생성은 유지하되 formatter 미설치로 인한 혼란스러운 메시지를 피하기 위한 설정입니다.
|
|
128
|
+
- `--kubb-config`와 함께 사용할 수 없습니다.
|
|
122
129
|
- `--python-output-file <path>`
|
|
123
130
|
- 기본값: `<output>/python/models.py`
|
|
124
131
|
- 상대 경로는 `--output` 기준으로 해석됩니다.
|
|
@@ -150,6 +157,8 @@ openapi-multitarget-codegen check-spec --input <spec>
|
|
|
150
157
|
|
|
151
158
|
1. **기본 preset 모드**
|
|
152
159
|
- 이 패키지에 포함된 기본 Kubb 구성으로 생성합니다.
|
|
160
|
+
- 기본 formatter 설정은 `false`입니다.
|
|
161
|
+
- formatter를 켜고 싶다면 `--kubb-format auto` 또는 `--kubb-format prettier` 같은 식으로 명시합니다.
|
|
153
162
|
- 현재 포함된 핵심 의존성:
|
|
154
163
|
- `@kubb/cli`
|
|
155
164
|
- `@kubb/core`
|
|
@@ -158,10 +167,10 @@ openapi-multitarget-codegen check-spec --input <spec>
|
|
|
158
167
|
- `@kubb/plugin-client`
|
|
159
168
|
2. **native config pass-through 모드**
|
|
160
169
|
- `--kubb-config`를 주면 사용자의 기존 Kubb config를 그대로 사용합니다.
|
|
161
|
-
- Kubb의 실제
|
|
170
|
+
- Kubb의 실제 입력/출력/플러그인/formatter 설정은 config 파일 내용을 따릅니다.
|
|
162
171
|
- 다만 이 래퍼 CLI는 `--input`, `--output`을 계속 요구합니다.
|
|
163
172
|
- `--input`은 OpenAPI 파일 사전 검증에 사용되고, `--output`은 `--clean` 기준 루트입니다.
|
|
164
|
-
- 이 모드에서는 `--kubb-output-dir`, `--kubb-client
|
|
173
|
+
- 이 모드에서는 `--kubb-output-dir`, `--kubb-client`, `--kubb-format`을 함께 사용할 수 없습니다.
|
|
165
174
|
- 이 경우 추가 Kubb 플러그인은 소비자 프로젝트에서 직접 설치/관리하는 것을 권장합니다.
|
|
166
175
|
|
|
167
176
|
즉, 이 패키지는 **기본 preset에 필요한 핵심 Kubb 플러그인만 포함**하고, 나머지 Kubb 생태계는 **기존 Kubb CLI/config를 그대로 통과시키는 방향**을 따릅니다.
|
|
@@ -376,8 +385,11 @@ npx openapi-multitarget-codegen check-spec --input ./specs/openapi.yaml
|
|
|
376
385
|
|
|
377
386
|
이 명령은 다음을 검사합니다.
|
|
378
387
|
- 파일 존재 여부
|
|
379
|
-
- `openapi`
|
|
380
|
-
-
|
|
388
|
+
- 기본 구조(`openapi`, `paths`) 존재 여부
|
|
389
|
+
- OpenAPI 문서 parse/validate
|
|
390
|
+
- unresolved `$ref`, 잘못된 스키마 구조 등 문서 수준 오류
|
|
391
|
+
|
|
392
|
+
다만 이 단계가 `kubb`, `python`, `langgraph` 각 타깃 생성기의 세부 호환성까지 모두 보장하는 것은 아닙니다. 타깃별 제약은 실제 `generate` 단계에서 추가 확인이 필요할 수 있습니다.
|
|
381
393
|
|
|
382
394
|
---
|
|
383
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,6 +34,7 @@
|
|
|
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": {
|
|
37
|
+
"@apidevtools/swagger-parser": "^12.1.0",
|
|
36
38
|
"@kubb/cli": "^4.37.2",
|
|
37
39
|
"@kubb/core": "^4.37.2",
|
|
38
40
|
"@kubb/plugin-client": "^4.37.2",
|
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
|
}
|
|
@@ -37,6 +38,7 @@ function main() {
|
|
|
37
38
|
const kubbOutputDir = options['kubb-output-dir']
|
|
38
39
|
const kubbConfigPath = options['kubb-config']
|
|
39
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'
|
|
@@ -67,6 +69,7 @@ function main() {
|
|
|
67
69
|
client: kubbClient,
|
|
68
70
|
configPath: kubbConfigPath,
|
|
69
71
|
extraArgs: kubbArgs,
|
|
72
|
+
format: kubbFormat,
|
|
70
73
|
})
|
|
71
74
|
} else if (target === 'python') {
|
|
72
75
|
runPython({ input, outputRoot: output, outputFile: pythonOutputFile, modelType: pythonModelType })
|
|
@@ -133,6 +136,20 @@ function normalizeTargets(targetValue) {
|
|
|
133
136
|
return values
|
|
134
137
|
}
|
|
135
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
|
+
|
|
136
153
|
function requireOption(options, key) {
|
|
137
154
|
const value = options[key]
|
|
138
155
|
if (!value) {
|
|
@@ -146,6 +163,10 @@ function validateOpenApiFile(specPath) {
|
|
|
146
163
|
throw new Error(`OpenAPI spec not found: ${specPath}`)
|
|
147
164
|
}
|
|
148
165
|
|
|
166
|
+
return loadOpenApiFile(specPath)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function loadOpenApiFile(specPath) {
|
|
149
170
|
const text = fs.readFileSync(specPath, 'utf8')
|
|
150
171
|
let data
|
|
151
172
|
try {
|
|
@@ -167,7 +188,30 @@ function validateOpenApiFile(specPath) {
|
|
|
167
188
|
return data
|
|
168
189
|
}
|
|
169
190
|
|
|
170
|
-
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 }) {
|
|
171
215
|
if (configPath) {
|
|
172
216
|
const resolvedConfigPath = path.resolve(configPath)
|
|
173
217
|
if (!fs.existsSync(resolvedConfigPath)) {
|
|
@@ -211,6 +255,7 @@ export default defineConfig({
|
|
|
211
255
|
output: {
|
|
212
256
|
path: ${JSON.stringify(kubbOutputDir)},
|
|
213
257
|
clean: true,
|
|
258
|
+
format: ${format === false ? 'false' : JSON.stringify(format)},
|
|
214
259
|
},
|
|
215
260
|
plugins: [
|
|
216
261
|
pluginOas(),
|
|
@@ -633,6 +678,7 @@ function validateKubbOptionCompatibility({ options, targets, kubbConfigPath, kub
|
|
|
633
678
|
const conflicting = [
|
|
634
679
|
['kubb-client', options['kubb-client']],
|
|
635
680
|
['kubb-output-dir', options['kubb-output-dir']],
|
|
681
|
+
['kubb-format', options['kubb-format']],
|
|
636
682
|
].filter(([, value]) => value)
|
|
637
683
|
|
|
638
684
|
if (conflicting.length > 0) {
|
|
@@ -797,6 +843,7 @@ Options:
|
|
|
797
843
|
--kubb-output-dir <path> Kubb output directory (default: <output>/kubb)
|
|
798
844
|
--kubb-config <path> Use an existing Kubb config file as the source of truth
|
|
799
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)
|
|
800
847
|
--python-output-file <path> Python output file (default: <output>/python/models.py)
|
|
801
848
|
--langgraph-output-dir <path> LangGraph output directory (default: <output>/langgraph)
|
|
802
849
|
--langgraph-file <name> LangGraph output filename (default: langgraph_tools.py)
|