@kubb/cli 4.12.14 → 4.13.0

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/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as version } from "./package-BTZmtBhm.js";
1
+ import { t as version } from "./package-BCi1eAUE.js";
2
2
  import { defineCommand, runCommand, runMain } from "citty";
3
3
 
4
4
  //#region src/index.ts
@@ -23,12 +23,12 @@ const main = defineCommand({
23
23
  "validate",
24
24
  "mcp"
25
25
  ].includes(rawArgs[0])) {
26
- await runCommand(await import("./generate-BlUnSQtu.js").then((r) => r.default), { rawArgs });
26
+ await runCommand(await import("./generate-CZAftsLc.js").then((r) => r.default), { rawArgs });
27
27
  process.exit(0);
28
28
  }
29
29
  },
30
30
  subCommands: {
31
- generate: () => import("./generate-BlUnSQtu.js").then((r) => r.default),
31
+ generate: () => import("./generate-CZAftsLc.js").then((r) => r.default),
32
32
  validate: () => import("./validate-Bt922JN-.js").then((r) => r.default),
33
33
  mcp: () => import("./mcp-B5FWDVjp.js").then((r) => r.default)
34
34
  }
@@ -0,0 +1,6 @@
1
+ //#region package.json
2
+ var version = "4.13.0";
3
+
4
+ //#endregion
5
+ export { version as t };
6
+ //# sourceMappingURL=package-BCi1eAUE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-BCi1eAUE.js","names":[],"sources":["../package.json"],"sourcesContent":[""],"mappings":""}
@@ -1,6 +1,6 @@
1
1
 
2
2
  //#region package.json
3
- var version = "4.12.14";
3
+ var version = "4.13.0";
4
4
 
5
5
  //#endregion
6
6
  Object.defineProperty(exports, 'version', {
@@ -9,4 +9,4 @@ Object.defineProperty(exports, 'version', {
9
9
  return version;
10
10
  }
11
11
  });
12
- //# sourceMappingURL=package-uCB9uBUP.cjs.map
12
+ //# sourceMappingURL=package-rLoLT3Y2.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-rLoLT3Y2.cjs","names":[],"sources":["../package.json"],"sourcesContent":[""],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/cli",
3
- "version": "4.12.14",
3
+ "version": "4.13.0",
4
4
  "description": "Command-line interface for Kubb, enabling easy generation of TypeScript, React-Query, Zod, and other code from OpenAPI specifications.",
5
5
  "keywords": [
6
6
  "cli",
@@ -62,14 +62,14 @@
62
62
  "seedrandom": "^3.0.5",
63
63
  "semver": "^7.7.3",
64
64
  "string-argv": "^0.3.2",
65
- "@kubb/core": "4.12.14"
65
+ "@kubb/core": "4.13.0"
66
66
  },
67
67
  "devDependencies": {
68
68
  "@types/seedrandom": "^3.0.8",
69
69
  "@types/semver": "^7.7.1",
70
70
  "source-map-support": "^0.5.21",
71
- "@kubb/mcp": "4.12.14",
72
- "@kubb/oas": "4.12.14"
71
+ "@kubb/mcp": "4.13.0",
72
+ "@kubb/oas": "4.13.0"
73
73
  },
74
74
  "engines": {
75
75
  "node": ">=20"
@@ -146,6 +146,7 @@ const command = defineCommand({
146
146
  await events.emit('lifecycle:end')
147
147
  } catch (error) {
148
148
  await events.emit('error', error as Error)
149
+ process.exit(1)
149
150
  }
150
151
  },
151
152
  })
@@ -4,6 +4,8 @@ import process from 'node:process'
4
4
  import { type Config, type KubbEvents, LogLevel, safeBuild, setup } from '@kubb/core'
5
5
  import type { AsyncEventEmitter } from '@kubb/core/utils'
6
6
  import pc from 'picocolors'
7
+ import { detectFormatter } from '../utils/detectFormatter.ts'
8
+ import { detectLinter } from '../utils/detectLinter.ts'
7
9
  import { executeHooks } from '../utils/executeHooks.ts'
8
10
 
9
11
  type GenerateProps = {
@@ -94,69 +96,84 @@ export async function generate({ input, config: userConfig, events, logLevel }:
94
96
  if (config.output.format) {
95
97
  await events.emit('format:start')
96
98
 
97
- await events.emit(
98
- 'info',
99
- [
100
- `Formatting with ${pc.dim(config.output.format as string)}`,
101
- logLevel >= LogLevel.info ? `on ${pc.dim(path.resolve(config.root, config.output.path))}` : undefined,
102
- ]
103
- .filter(Boolean)
104
- .join(' '),
105
- )
106
-
107
- if (config.output.format === 'prettier') {
108
- try {
109
- const hookId = createHash('sha256').update([config.name, config.output.format].filter(Boolean).join('-')).digest('hex')
110
- await events.emit('hook:start', {
111
- id: hookId,
112
- command: 'prettier',
113
- args: ['--ignore-unknown', '--write', path.resolve(config.root, config.output.path)],
114
- })
115
-
116
- await events.onOnce('hook:end', async () => {
117
- await events.emit(
118
- 'success',
119
- [
120
- `Formatting with ${pc.dim(config.output.format as string)}`,
121
- logLevel >= LogLevel.info ? `on ${pc.dim(path.resolve(config.root, config.output.path))}` : undefined,
122
- 'successfully',
123
- ]
124
- .filter(Boolean)
125
- .join(' '),
126
- )
127
- })
128
- } catch (caughtError) {
129
- await events.emit('error', caughtError as Error)
99
+ // Detect formatter if set to 'auto'
100
+ let formatter = config.output.format
101
+ if (formatter === 'auto') {
102
+ const detectedFormatter = await detectFormatter()
103
+ if (!detectedFormatter) {
104
+ await events.emit('warn', 'No formatter found (biome or prettier). Skipping formatting.')
105
+ } else {
106
+ formatter = detectedFormatter
107
+ await events.emit('info', `Auto-detected formatter: ${pc.dim(formatter)}`)
130
108
  }
131
-
132
- await events.emit('success', `Formatted with ${config.output.format}`)
133
109
  }
134
110
 
135
- if (config.output.format === 'biome') {
136
- try {
137
- const hookId = createHash('sha256').update([config.name, config.output.format].filter(Boolean).join('-')).digest('hex')
138
- await events.emit('hook:start', {
139
- id: hookId,
140
- command: 'biome',
141
- args: ['format', '--write', path.resolve(config.root, config.output.path)],
142
- })
143
-
144
- await events.onOnce('hook:end', async () => {
145
- await events.emit(
146
- 'success',
147
- [
148
- `Formatting with ${pc.dim(config.output.format as string)}`,
149
- logLevel >= LogLevel.info ? `on ${pc.dim(path.resolve(config.root, config.output.path))}` : undefined,
150
- 'successfully',
151
- ]
152
- .filter(Boolean)
153
- .join(' '),
154
- )
155
- })
156
- } catch (caughtError) {
157
- const error = new Error('Biome not found')
158
- error.cause = caughtError
159
- await events.emit('error', error)
111
+ // Only proceed with formatting if we have a valid formatter
112
+ if (formatter && formatter !== 'auto') {
113
+ await events.emit(
114
+ 'info',
115
+ [
116
+ `Formatting with ${pc.dim(formatter as string)}`,
117
+ logLevel >= LogLevel.info ? `on ${pc.dim(path.resolve(config.root, config.output.path))}` : undefined,
118
+ ]
119
+ .filter(Boolean)
120
+ .join(' '),
121
+ )
122
+
123
+ if (formatter === 'prettier') {
124
+ try {
125
+ const hookId = createHash('sha256').update([config.name, formatter].filter(Boolean).join('-')).digest('hex')
126
+ await events.emit('hook:start', {
127
+ id: hookId,
128
+ command: 'prettier',
129
+ args: ['--ignore-unknown', '--write', path.resolve(config.root, config.output.path)],
130
+ })
131
+
132
+ await events.onOnce('hook:end', async () => {
133
+ await events.emit(
134
+ 'success',
135
+ [
136
+ `Formatting with ${pc.dim(formatter as string)}`,
137
+ logLevel >= LogLevel.info ? `on ${pc.dim(path.resolve(config.root, config.output.path))}` : undefined,
138
+ 'successfully',
139
+ ]
140
+ .filter(Boolean)
141
+ .join(' '),
142
+ )
143
+ })
144
+ } catch (caughtError) {
145
+ await events.emit('error', caughtError as Error)
146
+ }
147
+
148
+ await events.emit('success', `Formatted with ${formatter}`)
149
+ }
150
+
151
+ if (formatter === 'biome') {
152
+ try {
153
+ const hookId = createHash('sha256').update([config.name, formatter].filter(Boolean).join('-')).digest('hex')
154
+ await events.emit('hook:start', {
155
+ id: hookId,
156
+ command: 'biome',
157
+ args: ['format', '--write', path.resolve(config.root, config.output.path)],
158
+ })
159
+
160
+ await events.onOnce('hook:end', async () => {
161
+ await events.emit(
162
+ 'success',
163
+ [
164
+ `Formatting with ${pc.dim(formatter as string)}`,
165
+ logLevel >= LogLevel.info ? `on ${pc.dim(path.resolve(config.root, config.output.path))}` : undefined,
166
+ 'successfully',
167
+ ]
168
+ .filter(Boolean)
169
+ .join(' '),
170
+ )
171
+ })
172
+ } catch (caughtError) {
173
+ const error = new Error('Biome not found')
174
+ error.cause = caughtError
175
+ await events.emit('error', error)
176
+ }
160
177
  }
161
178
  }
162
179
 
@@ -167,97 +184,109 @@ export async function generate({ input, config: userConfig, events, logLevel }:
167
184
  if (config.output.lint) {
168
185
  await events.emit('lint:start')
169
186
 
170
- await events.emit(
171
- 'info',
172
- [
173
- `Linting with ${pc.dim(config.output.lint as string)}`,
174
- logLevel >= LogLevel.info ? `on ${pc.dim(path.resolve(config.root, config.output.path))}` : undefined,
175
- ]
176
- .filter(Boolean)
177
- .join(' '),
178
- )
179
-
180
- if (config.output.lint === 'eslint') {
181
- try {
182
- const hookId = createHash('sha256').update([config.name, config.output.lint].filter(Boolean).join('-')).digest('hex')
183
- await events.emit('hook:start', {
184
- id: hookId,
185
- command: 'eslint',
186
- args: [path.resolve(config.root, config.output.path), '--fix'],
187
- })
188
-
189
- await events.onOnce('hook:end', async () => {
190
- await events.emit(
191
- 'success',
192
- [
193
- `Linted with ${pc.dim(config.output.lint as string)}`,
194
- logLevel >= LogLevel.info ? `on ${pc.dim(path.resolve(config.root, config.output.path))}` : undefined,
195
- 'successfully',
196
- ]
197
- .filter(Boolean)
198
- .join(' '),
199
- )
200
- })
201
- } catch (caughtError) {
202
- const error = new Error('Eslint not found')
203
- error.cause = caughtError
204
- await events.emit('error', error)
187
+ // Detect linter if set to 'auto'
188
+ let linter = config.output.lint
189
+ if (linter === 'auto') {
190
+ const detectedLinter = await detectLinter()
191
+ if (!detectedLinter) {
192
+ await events.emit('warn', 'No linter found (biome, oxlint, or eslint). Skipping linting.')
193
+ } else {
194
+ linter = detectedLinter
195
+ await events.emit('info', `Auto-detected linter: ${pc.dim(linter)}`)
205
196
  }
206
197
  }
207
198
 
208
- if (config.output.lint === 'biome') {
209
- try {
210
- const hookId = createHash('sha256').update([config.name, config.output.lint].filter(Boolean).join('-')).digest('hex')
211
- await events.emit('hook:start', {
212
- id: hookId,
213
- command: 'biome',
214
- args: ['lint', '--fix', path.resolve(config.root, config.output.path)],
215
- })
216
-
217
- await events.onOnce('hook:end', async () => {
218
- await events.emit(
219
- 'success',
220
- [
221
- `Linted with ${pc.dim(config.output.lint as string)}`,
222
- logLevel >= LogLevel.info ? `on ${pc.dim(path.resolve(config.root, config.output.path))}` : undefined,
223
- 'successfully',
224
- ]
225
- .filter(Boolean)
226
- .join(' '),
227
- )
228
- })
229
- } catch (caughtError) {
230
- const error = new Error('Biome not found')
231
- error.cause = caughtError
232
- await events.emit('error', error)
199
+ // Only proceed with linting if we have a valid linter
200
+ if (linter && linter !== 'auto') {
201
+ await events.emit(
202
+ 'info',
203
+ [`Linting with ${pc.dim(linter as string)}`, logLevel >= LogLevel.info ? `on ${pc.dim(path.resolve(config.root, config.output.path))}` : undefined]
204
+ .filter(Boolean)
205
+ .join(' '),
206
+ )
207
+
208
+ if (linter === 'eslint') {
209
+ try {
210
+ const hookId = createHash('sha256').update([config.name, linter].filter(Boolean).join('-')).digest('hex')
211
+ await events.emit('hook:start', {
212
+ id: hookId,
213
+ command: 'eslint',
214
+ args: [path.resolve(config.root, config.output.path), '--fix'],
215
+ })
216
+
217
+ await events.onOnce('hook:end', async () => {
218
+ await events.emit(
219
+ 'success',
220
+ [
221
+ `Linted with ${pc.dim(linter as string)}`,
222
+ logLevel >= LogLevel.info ? `on ${pc.dim(path.resolve(config.root, config.output.path))}` : undefined,
223
+ 'successfully',
224
+ ]
225
+ .filter(Boolean)
226
+ .join(' '),
227
+ )
228
+ })
229
+ } catch (caughtError) {
230
+ const error = new Error('Eslint not found')
231
+ error.cause = caughtError
232
+ await events.emit('error', error)
233
+ }
234
+ }
235
+
236
+ if (linter === 'biome') {
237
+ try {
238
+ const hookId = createHash('sha256').update([config.name, linter].filter(Boolean).join('-')).digest('hex')
239
+ await events.emit('hook:start', {
240
+ id: hookId,
241
+ command: 'biome',
242
+ args: ['lint', '--fix', path.resolve(config.root, config.output.path)],
243
+ })
244
+
245
+ await events.onOnce('hook:end', async () => {
246
+ await events.emit(
247
+ 'success',
248
+ [
249
+ `Linted with ${pc.dim(linter as string)}`,
250
+ logLevel >= LogLevel.info ? `on ${pc.dim(path.resolve(config.root, config.output.path))}` : undefined,
251
+ 'successfully',
252
+ ]
253
+ .filter(Boolean)
254
+ .join(' '),
255
+ )
256
+ })
257
+ } catch (caughtError) {
258
+ const error = new Error('Biome not found')
259
+ error.cause = caughtError
260
+ await events.emit('error', error)
261
+ }
233
262
  }
234
- }
235
263
 
236
- if (config.output.lint === 'oxlint') {
237
- try {
238
- const hookId = createHash('sha256').update([config.name, config.output.lint].filter(Boolean).join('-')).digest('hex')
239
- await events.emit('hook:start', {
240
- id: hookId,
241
- command: 'oxlint',
242
- args: ['--fix', path.resolve(config.root, config.output.path)],
243
- })
244
-
245
- await events.onOnce('hook:end', async () => {
246
- await events.emit(
247
- 'success',
248
- [
249
- `Linted with ${pc.dim(config.output.lint as string)}`,
250
- logLevel >= LogLevel.info ? `on ${pc.dim(path.resolve(config.root, config.output.path))}` : undefined,
251
- 'successfully',
252
- ]
253
- .filter(Boolean)
254
- .join(' '),
255
- )
256
- })
257
- } catch (caughtError) {
258
- const error = new Error('Oxlint not found')
259
- error.cause = caughtError
260
- await events.emit('error', error)
264
+ if (linter === 'oxlint') {
265
+ try {
266
+ const hookId = createHash('sha256').update([config.name, linter].filter(Boolean).join('-')).digest('hex')
267
+ await events.emit('hook:start', {
268
+ id: hookId,
269
+ command: 'oxlint',
270
+ args: ['--fix', path.resolve(config.root, config.output.path)],
271
+ })
272
+
273
+ await events.onOnce('hook:end', async () => {
274
+ await events.emit(
275
+ 'success',
276
+ [
277
+ `Linted with ${pc.dim(linter as string)}`,
278
+ logLevel >= LogLevel.info ? `on ${pc.dim(path.resolve(config.root, config.output.path))}` : undefined,
279
+ 'successfully',
280
+ ]
281
+ .filter(Boolean)
282
+ .join(' '),
283
+ )
284
+ })
285
+ } catch (caughtError) {
286
+ const error = new Error('Oxlint not found')
287
+ error.cause = caughtError
288
+ await events.emit('error', error)
289
+ }
261
290
  }
262
291
  }
263
292
 
@@ -0,0 +1,55 @@
1
+ import { execaCommand } from 'execa'
2
+
3
+ type Formatter = 'biome' | 'prettier'
4
+
5
+ /**
6
+ * Check if a formatter command is available in the system.
7
+ *
8
+ * @param formatter - The formatter to check ('biome' or 'prettier')
9
+ * @returns Promise that resolves to true if the formatter is available, false otherwise
10
+ *
11
+ * @remarks
12
+ * This function checks availability by running `<formatter> --version` command.
13
+ * All supported formatters (biome, prettier) implement the --version flag.
14
+ */
15
+ async function isFormatterAvailable(formatter: Formatter): Promise<boolean> {
16
+ try {
17
+ // Try to get the version of the formatter to check if it's installed
18
+ await execaCommand(`${formatter} --version`, { stdio: 'ignore' })
19
+ return true
20
+ } catch {
21
+ return false
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Detect which formatter is available in the system.
27
+ *
28
+ * @returns Promise that resolves to the first available formatter or undefined if none are found
29
+ *
30
+ * @remarks
31
+ * Checks in order of preference: biome, prettier.
32
+ * Uses the `--version` flag to detect if each formatter command is available.
33
+ * This is a reliable method as all supported formatters implement this flag.
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * const formatter = await detectFormatter()
38
+ * if (formatter) {
39
+ * console.log(`Using ${formatter} for formatting`)
40
+ * } else {
41
+ * console.log('No formatter found')
42
+ * }
43
+ * ```
44
+ */
45
+ export async function detectFormatter(): Promise<Formatter | undefined> {
46
+ const formatters: Formatter[] = ['biome', 'prettier']
47
+
48
+ for (const formatter of formatters) {
49
+ if (await isFormatterAvailable(formatter)) {
50
+ return formatter
51
+ }
52
+ }
53
+
54
+ return undefined
55
+ }
@@ -0,0 +1,55 @@
1
+ import { execaCommand } from 'execa'
2
+
3
+ type Linter = 'biome' | 'oxlint' | 'eslint'
4
+
5
+ /**
6
+ * Check if a linter command is available in the system.
7
+ *
8
+ * @param linter - The linter to check ('biome', 'oxlint', or 'eslint')
9
+ * @returns Promise that resolves to true if the linter is available, false otherwise
10
+ *
11
+ * @remarks
12
+ * This function checks availability by running `<linter> --version` command.
13
+ * All supported linters (biome, oxlint, eslint) implement the --version flag.
14
+ */
15
+ async function isLinterAvailable(linter: Linter): Promise<boolean> {
16
+ try {
17
+ // Try to get the version of the linter to check if it's installed
18
+ await execaCommand(`${linter} --version`, { stdio: 'ignore' })
19
+ return true
20
+ } catch {
21
+ return false
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Detect which linter is available in the system.
27
+ *
28
+ * @returns Promise that resolves to the first available linter or undefined if none are found
29
+ *
30
+ * @remarks
31
+ * Checks in order of preference: biome, oxlint, eslint.
32
+ * Uses the `--version` flag to detect if each linter command is available.
33
+ * This is a reliable method as all supported linters implement this flag.
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * const linter = await detectLinter()
38
+ * if (linter) {
39
+ * console.log(`Using ${linter} for linting`)
40
+ * } else {
41
+ * console.log('No linter found')
42
+ * }
43
+ * ```
44
+ */
45
+ export async function detectLinter(): Promise<Linter | undefined> {
46
+ const linters: Linter[] = ['biome', 'oxlint', 'eslint']
47
+
48
+ for (const linter of linters) {
49
+ if (await isLinterAvailable(linter)) {
50
+ return linter
51
+ }
52
+ }
53
+
54
+ return undefined
55
+ }