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.
Files changed (3) hide show
  1. package/README.md +18 -6
  2. package/package.json +3 -1
  3. 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`은 전체 OpenAPI 스키마 검증이 아니라, 파일 존재 여부와 기본 구조(`openapi`, `paths`) 확인하는 최소 검사입니다.
112
- - 즉, 정식 lint/validation을 대체하지는 않습니다.
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의 실제 입력/출력/플러그인 설정은 config 파일 내용을 따릅니다.
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
- - `paths` 객체 존재 여부
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.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
- 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
  }
@@ -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 runKubb({ input, outputRoot, outputDir, client, configPath, extraArgs = [] }) {
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)