@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/{generate-BlUnSQtu.js → generate-CZAftsLc.js} +210 -95
- package/dist/generate-CZAftsLc.js.map +1 -0
- package/dist/{generate-DNdqYqwF.cjs → generate-CpqmNPdN.cjs} +209 -94
- package/dist/generate-CpqmNPdN.cjs.map +1 -0
- package/dist/index.cjs +3 -3
- package/dist/index.js +3 -3
- package/dist/package-BCi1eAUE.js +6 -0
- package/dist/package-BCi1eAUE.js.map +1 -0
- package/dist/{package-uCB9uBUP.cjs → package-rLoLT3Y2.cjs} +2 -2
- package/dist/package-rLoLT3Y2.cjs.map +1 -0
- package/package.json +4 -4
- package/src/commands/generate.ts +1 -0
- package/src/runners/generate.ts +175 -146
- package/src/utils/detectFormatter.ts +55 -0
- package/src/utils/detectLinter.ts +55 -0
- package/dist/generate-BlUnSQtu.js.map +0 -1
- package/dist/generate-DNdqYqwF.cjs.map +0 -1
- package/dist/package-BTZmtBhm.js +0 -6
- package/dist/package-BTZmtBhm.js.map +0 -1
- package/dist/package-uCB9uBUP.cjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as version } from "./package-
|
|
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-
|
|
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-
|
|
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 @@
|
|
|
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.
|
|
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-
|
|
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.
|
|
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.
|
|
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.
|
|
72
|
-
"@kubb/oas": "4.
|
|
71
|
+
"@kubb/mcp": "4.13.0",
|
|
72
|
+
"@kubb/oas": "4.13.0"
|
|
73
73
|
},
|
|
74
74
|
"engines": {
|
|
75
75
|
"node": ">=20"
|
package/src/commands/generate.ts
CHANGED
package/src/runners/generate.ts
CHANGED
|
@@ -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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
+
}
|