@kubb/cli 4.32.4 → 4.33.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/agent-CJ69TqoO.js +87 -0
- package/dist/agent-CJ69TqoO.js.map +1 -0
- package/dist/agent-CduUX7Ye.cjs +91 -0
- package/dist/agent-CduUX7Ye.cjs.map +1 -0
- package/dist/agent-D0A3RQho.js +57 -0
- package/dist/agent-D0A3RQho.js.map +1 -0
- package/dist/agent-DrnwQBZf.cjs +60 -0
- package/dist/agent-DrnwQBZf.cjs.map +1 -0
- package/dist/constants-CEKRremI.js +79 -0
- package/dist/constants-CEKRremI.js.map +1 -0
- package/dist/constants-CnPOlsJq.cjs +126 -0
- package/dist/constants-CnPOlsJq.cjs.map +1 -0
- package/dist/errors-BUjJsNoe.cjs +44 -0
- package/dist/errors-BUjJsNoe.cjs.map +1 -0
- package/dist/errors-bSLTEh4e.js +27 -0
- package/dist/errors-bSLTEh4e.js.map +1 -0
- package/dist/{generate-COj0aMS6.cjs → generate-ByMgAV76.cjs} +423 -577
- package/dist/generate-ByMgAV76.cjs.map +1 -0
- package/dist/generate-CiUPO5ds.cjs +65 -0
- package/dist/generate-CiUPO5ds.cjs.map +1 -0
- package/dist/generate-DIIxtkWT.js +66 -0
- package/dist/generate-DIIxtkWT.js.map +1 -0
- package/dist/{generate-CpWtSc45.js → generate-HP5ySfjV.js} +422 -577
- package/dist/generate-HP5ySfjV.js.map +1 -0
- package/dist/index.cjs +226 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +226 -35
- package/dist/index.js.map +1 -1
- package/dist/init-Cd1hCb7q.cjs +296 -0
- package/dist/init-Cd1hCb7q.cjs.map +1 -0
- package/dist/init-DLNrkDF4.js +25 -0
- package/dist/init-DLNrkDF4.js.map +1 -0
- package/dist/init-Df_aXezV.cjs +24 -0
- package/dist/init-Df_aXezV.cjs.map +1 -0
- package/dist/init-DyKK2fTp.js +291 -0
- package/dist/init-DyKK2fTp.js.map +1 -0
- package/dist/jiti-BdskUHhD.cjs +16 -0
- package/dist/jiti-BdskUHhD.cjs.map +1 -0
- package/dist/jiti-Cl7t20dO.js +11 -0
- package/dist/jiti-Cl7t20dO.js.map +1 -0
- package/dist/mcp-B73FC8dF.cjs +42 -0
- package/dist/mcp-B73FC8dF.cjs.map +1 -0
- package/dist/mcp-Bd9LITaI.js +16 -0
- package/dist/mcp-Bd9LITaI.js.map +1 -0
- package/dist/mcp-Cf-1dsB-.js +41 -0
- package/dist/mcp-Cf-1dsB-.js.map +1 -0
- package/dist/mcp-Clg-Qnkr.cjs +15 -0
- package/dist/mcp-Clg-Qnkr.cjs.map +1 -0
- package/dist/package-681jTtCk.js +6 -0
- package/dist/package-681jTtCk.js.map +1 -0
- package/dist/{package-aNQWvWbS.cjs → package-aKgzEJtp.cjs} +2 -2
- package/dist/package-aKgzEJtp.cjs.map +1 -0
- package/dist/{telemetry-DYWvlxqs.js → telemetry-C4gOKX2x.js} +31 -10
- package/dist/telemetry-C4gOKX2x.js.map +1 -0
- package/dist/{telemetry-BDSSqUiG.cjs → telemetry-T5IA2dWA.cjs} +40 -7
- package/dist/telemetry-T5IA2dWA.cjs.map +1 -0
- package/dist/types-CLtz0jem.js +25 -0
- package/dist/types-CLtz0jem.js.map +1 -0
- package/dist/types-Ck2lzFON.cjs +36 -0
- package/dist/types-Ck2lzFON.cjs.map +1 -0
- package/dist/validate-Chjg23AE.js +41 -0
- package/dist/validate-Chjg23AE.js.map +1 -0
- package/dist/validate-Cr26q5xX.js +25 -0
- package/dist/validate-Cr26q5xX.js.map +1 -0
- package/dist/validate-DURmg-2Q.cjs +24 -0
- package/dist/validate-DURmg-2Q.cjs.map +1 -0
- package/dist/validate-Dqi9T_c4.cjs +42 -0
- package/dist/validate-Dqi9T_c4.cjs.map +1 -0
- package/package.json +5 -6
- package/src/cli/adapters/nodeAdapter.ts +159 -0
- package/src/cli/help.ts +36 -0
- package/src/cli/index.ts +16 -0
- package/src/cli/parse.ts +18 -0
- package/src/cli/schema.ts +38 -0
- package/src/cli/types.ts +95 -0
- package/src/commands/agent/start.ts +27 -136
- package/src/commands/agent.ts +6 -25
- package/src/commands/generate.ts +26 -158
- package/src/commands/init.ts +9 -360
- package/src/commands/mcp.ts +7 -52
- package/src/commands/validate.ts +9 -60
- package/src/constants.ts +77 -0
- package/src/index.ts +36 -42
- package/src/loggers/clackLogger.ts +42 -140
- package/src/loggers/fileSystemLogger.ts +1 -12
- package/src/loggers/githubActionsLogger.ts +36 -101
- package/src/loggers/plainLogger.ts +23 -70
- package/src/loggers/utils.ts +66 -2
- package/src/runners/agent.ts +100 -0
- package/src/runners/generate.ts +208 -100
- package/src/runners/init.ts +322 -0
- package/src/runners/mcp.ts +32 -0
- package/src/runners/validate.ts +35 -0
- package/src/utils/Writables.ts +2 -2
- package/src/utils/envDetection.ts +34 -0
- package/src/utils/errors.ts +23 -0
- package/src/utils/executeHooks.ts +18 -6
- package/src/utils/getCosmiConfig.ts +10 -11
- package/src/utils/getIntro.ts +17 -18
- package/src/utils/getSummary.ts +11 -15
- package/src/utils/jiti.ts +9 -0
- package/src/utils/packageManager.ts +3 -3
- package/src/utils/randomColor.ts +3 -12
- package/src/utils/runHook.ts +75 -0
- package/src/utils/spawnAsync.ts +47 -0
- package/src/utils/telemetry.ts +8 -25
- package/src/utils/watcher.ts +2 -4
- package/dist/agent-6COck3B9.cjs +0 -20
- package/dist/agent-6COck3B9.cjs.map +0 -1
- package/dist/agent-DMm6c5Vg.js +0 -20
- package/dist/agent-DMm6c5Vg.js.map +0 -1
- package/dist/generate-COj0aMS6.cjs.map +0 -1
- package/dist/generate-CpWtSc45.js.map +0 -1
- package/dist/init-Bdn3_qir.js +0 -304
- package/dist/init-Bdn3_qir.js.map +0 -1
- package/dist/init-CFW2kWY8.cjs +0 -308
- package/dist/init-CFW2kWY8.cjs.map +0 -1
- package/dist/mcp-DkwtARfo.cjs +0 -57
- package/dist/mcp-DkwtARfo.cjs.map +0 -1
- package/dist/mcp-DrH93Vq4.js +0 -57
- package/dist/mcp-DrH93Vq4.js.map +0 -1
- package/dist/package-BnJbGmLm.js +0 -6
- package/dist/package-BnJbGmLm.js.map +0 -1
- package/dist/package-aNQWvWbS.cjs.map +0 -1
- package/dist/start-CqTUu14n.js +0 -131
- package/dist/start-CqTUu14n.js.map +0 -1
- package/dist/start-D-rsIJGo.cjs +0 -134
- package/dist/start-D-rsIJGo.cjs.map +0 -1
- package/dist/telemetry-BDSSqUiG.cjs.map +0 -1
- package/dist/telemetry-DYWvlxqs.js.map +0 -1
- package/dist/validate-BlV8L8gC.js +0 -66
- package/dist/validate-BlV8L8gC.js.map +0 -1
- package/dist/validate-COhZUXF8.cjs +0 -66
- package/dist/validate-COhZUXF8.cjs.map +0 -1
- package/src/loggers/envDetection.ts +0 -28
- package/src/loggers/index.ts +0 -5
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import process from 'node:process'
|
|
4
|
+
import { styleText } from 'node:util'
|
|
5
|
+
import * as clack from '@clack/prompts'
|
|
6
|
+
import { detectPackageManager, type PackageManagerInfo } from '@kubb/core'
|
|
7
|
+
import { initDefaults, pluginDefaultConfigs } from '../constants.ts'
|
|
8
|
+
import { hasPackageJson, initPackageJson, installPackages } from '../utils/packageManager.ts'
|
|
9
|
+
|
|
10
|
+
type PluginOption = {
|
|
11
|
+
value: string
|
|
12
|
+
label: string
|
|
13
|
+
hint?: string
|
|
14
|
+
packageName: string
|
|
15
|
+
importName: string
|
|
16
|
+
category: 'core' | 'typescript' | 'query' | 'validation' | 'testing' | 'mocking' | 'docs'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const availablePlugins: PluginOption[] = [
|
|
20
|
+
{
|
|
21
|
+
value: 'plugin-oas',
|
|
22
|
+
label: 'OpenAPI Parser',
|
|
23
|
+
hint: 'Required',
|
|
24
|
+
packageName: '@kubb/plugin-oas',
|
|
25
|
+
importName: 'pluginOas',
|
|
26
|
+
category: 'core',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
value: 'plugin-ts',
|
|
30
|
+
label: 'TypeScript',
|
|
31
|
+
hint: 'Recommended',
|
|
32
|
+
packageName: '@kubb/plugin-ts',
|
|
33
|
+
importName: 'pluginTs',
|
|
34
|
+
category: 'typescript',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
value: 'plugin-client',
|
|
38
|
+
label: 'Client (Fetch/Axios)',
|
|
39
|
+
packageName: '@kubb/plugin-client',
|
|
40
|
+
importName: 'pluginClient',
|
|
41
|
+
category: 'typescript',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
value: 'plugin-react-query',
|
|
45
|
+
label: 'React Query / TanStack Query',
|
|
46
|
+
packageName: '@kubb/plugin-react-query',
|
|
47
|
+
importName: 'pluginReactQuery',
|
|
48
|
+
category: 'query',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
value: 'plugin-solid-query',
|
|
52
|
+
label: 'Solid Query',
|
|
53
|
+
packageName: '@kubb/plugin-solid-query',
|
|
54
|
+
importName: 'pluginSolidQuery',
|
|
55
|
+
category: 'query',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
value: 'plugin-svelte-query',
|
|
59
|
+
label: 'Svelte Query',
|
|
60
|
+
packageName: '@kubb/plugin-svelte-query',
|
|
61
|
+
importName: 'pluginSvelteQuery',
|
|
62
|
+
category: 'query',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
value: 'plugin-vue-query',
|
|
66
|
+
label: 'Vue Query',
|
|
67
|
+
packageName: '@kubb/plugin-vue-query',
|
|
68
|
+
importName: 'pluginVueQuery',
|
|
69
|
+
category: 'query',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
value: 'plugin-swr',
|
|
73
|
+
label: 'SWR',
|
|
74
|
+
packageName: '@kubb/plugin-swr',
|
|
75
|
+
importName: 'pluginSwr',
|
|
76
|
+
category: 'query',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
value: 'plugin-zod',
|
|
80
|
+
label: 'Zod Schemas',
|
|
81
|
+
packageName: '@kubb/plugin-zod',
|
|
82
|
+
importName: 'pluginZod',
|
|
83
|
+
category: 'validation',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
value: 'plugin-faker',
|
|
87
|
+
label: 'Faker.js Mocks',
|
|
88
|
+
packageName: '@kubb/plugin-faker',
|
|
89
|
+
importName: 'pluginFaker',
|
|
90
|
+
category: 'mocking',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
value: 'plugin-msw',
|
|
94
|
+
label: 'MSW Handlers',
|
|
95
|
+
packageName: '@kubb/plugin-msw',
|
|
96
|
+
importName: 'pluginMsw',
|
|
97
|
+
category: 'mocking',
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
value: 'plugin-cypress',
|
|
101
|
+
label: 'Cypress Tests',
|
|
102
|
+
packageName: '@kubb/plugin-cypress',
|
|
103
|
+
importName: 'pluginCypress',
|
|
104
|
+
category: 'testing',
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
value: 'plugin-redoc',
|
|
108
|
+
label: 'ReDoc Documentation',
|
|
109
|
+
packageName: '@kubb/plugin-redoc',
|
|
110
|
+
importName: 'pluginRedoc',
|
|
111
|
+
category: 'docs',
|
|
112
|
+
},
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
function generateConfigFile(selectedPlugins: PluginOption[], inputPath: string, outputPath: string): string {
|
|
116
|
+
const imports = selectedPlugins.map((plugin) => `import { ${plugin.importName} } from '${plugin.packageName}'`).join('\n')
|
|
117
|
+
|
|
118
|
+
const pluginConfigs = selectedPlugins
|
|
119
|
+
.map((plugin) => {
|
|
120
|
+
const config = (pluginDefaultConfigs as Record<string, string>)[plugin.value] ?? `${plugin.importName}()`
|
|
121
|
+
return ` ${config},`
|
|
122
|
+
})
|
|
123
|
+
.join('\n')
|
|
124
|
+
|
|
125
|
+
return `import { defineConfig } from '@kubb/core'
|
|
126
|
+
${imports}
|
|
127
|
+
|
|
128
|
+
export default defineConfig({
|
|
129
|
+
root: '.',
|
|
130
|
+
input: {
|
|
131
|
+
path: '${inputPath}',
|
|
132
|
+
},
|
|
133
|
+
output: {
|
|
134
|
+
path: '${outputPath}',
|
|
135
|
+
clean: true,
|
|
136
|
+
},
|
|
137
|
+
plugins: [
|
|
138
|
+
${pluginConfigs}
|
|
139
|
+
],
|
|
140
|
+
})
|
|
141
|
+
`
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function cancelAndExit(message = 'Operation cancelled.'): never {
|
|
145
|
+
clack.cancel(message)
|
|
146
|
+
process.exit(0)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
type InitOptions = {
|
|
150
|
+
yes: boolean
|
|
151
|
+
version: string
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export async function runInit({ yes, version }: InitOptions): Promise<void> {
|
|
155
|
+
const cwd = process.cwd()
|
|
156
|
+
|
|
157
|
+
clack.intro(styleText('bgCyan', styleText('black', ' Kubb Init ')))
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
// Check/create package.json
|
|
161
|
+
let packageManager: PackageManagerInfo
|
|
162
|
+
if (!hasPackageJson(cwd)) {
|
|
163
|
+
if (!yes) {
|
|
164
|
+
const shouldInit = await clack.confirm({
|
|
165
|
+
message: 'No package.json found. Would you like to create one?',
|
|
166
|
+
initialValue: true,
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
if (clack.isCancel(shouldInit) || !shouldInit) {
|
|
170
|
+
cancelAndExit()
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
packageManager = detectPackageManager(cwd)
|
|
175
|
+
|
|
176
|
+
const spinner = clack.spinner()
|
|
177
|
+
spinner.start(`Initializing package.json with ${packageManager.name}`)
|
|
178
|
+
|
|
179
|
+
await initPackageJson(cwd, packageManager)
|
|
180
|
+
|
|
181
|
+
spinner.stop(`Created package.json with ${packageManager.name}`)
|
|
182
|
+
} else {
|
|
183
|
+
packageManager = detectPackageManager(cwd)
|
|
184
|
+
clack.log.info(`Detected package manager: ${styleText('cyan', packageManager.name)}`)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Prompt for OpenAPI spec path
|
|
188
|
+
let inputPath: string
|
|
189
|
+
if (yes) {
|
|
190
|
+
inputPath = initDefaults.inputPath
|
|
191
|
+
clack.log.info(`Using input path: ${styleText('cyan', inputPath)}`)
|
|
192
|
+
} else {
|
|
193
|
+
const inputPathResult = await clack.text({
|
|
194
|
+
message: 'Where is your OpenAPI specification located?',
|
|
195
|
+
placeholder: initDefaults.inputPath,
|
|
196
|
+
defaultValue: initDefaults.inputPath,
|
|
197
|
+
validate: (value) => {
|
|
198
|
+
if (!value) return 'Input path is required'
|
|
199
|
+
},
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
if (clack.isCancel(inputPathResult)) {
|
|
203
|
+
cancelAndExit()
|
|
204
|
+
}
|
|
205
|
+
inputPath = inputPathResult as string
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Prompt for output directory
|
|
209
|
+
let outputPath: string
|
|
210
|
+
if (yes) {
|
|
211
|
+
outputPath = initDefaults.outputPath
|
|
212
|
+
clack.log.info(`Using output path: ${styleText('cyan', outputPath)}`)
|
|
213
|
+
} else {
|
|
214
|
+
const outputPathResult = await clack.text({
|
|
215
|
+
message: 'Where should the generated files be output?',
|
|
216
|
+
placeholder: initDefaults.outputPath,
|
|
217
|
+
defaultValue: initDefaults.outputPath,
|
|
218
|
+
validate: (value) => {
|
|
219
|
+
if (!value) return 'Output path is required'
|
|
220
|
+
},
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
if (clack.isCancel(outputPathResult)) {
|
|
224
|
+
cancelAndExit()
|
|
225
|
+
}
|
|
226
|
+
outputPath = outputPathResult as string
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Plugin selection
|
|
230
|
+
let selectedPlugins: PluginOption[]
|
|
231
|
+
if (yes) {
|
|
232
|
+
selectedPlugins = availablePlugins.filter((plugin) => (initDefaults.plugins as readonly string[]).includes(plugin.value))
|
|
233
|
+
clack.log.info(`Using plugins: ${styleText('cyan', selectedPlugins.map((p) => p.label).join(', '))}`)
|
|
234
|
+
} else {
|
|
235
|
+
const selectedPluginValues = await clack.multiselect({
|
|
236
|
+
message: 'Select plugins to use:',
|
|
237
|
+
options: availablePlugins.map((plugin) => ({
|
|
238
|
+
value: plugin.value,
|
|
239
|
+
label: plugin.label,
|
|
240
|
+
hint: plugin.hint,
|
|
241
|
+
})),
|
|
242
|
+
initialValues: [...initDefaults.plugins],
|
|
243
|
+
required: true,
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
if (clack.isCancel(selectedPluginValues)) {
|
|
247
|
+
cancelAndExit()
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
selectedPlugins = availablePlugins.filter((plugin) => (selectedPluginValues as string[]).includes(plugin.value))
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Ensure plugin-oas is always included
|
|
254
|
+
if (!selectedPlugins.find((p) => p.value === 'plugin-oas')) {
|
|
255
|
+
selectedPlugins.unshift(availablePlugins.find((p) => p.value === 'plugin-oas')!)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Install packages
|
|
259
|
+
const packagesToInstall = ['@kubb/core', '@kubb/cli', '@kubb/agent', ...selectedPlugins.map((p) => p.packageName)]
|
|
260
|
+
|
|
261
|
+
const spinner = clack.spinner()
|
|
262
|
+
spinner.start(`Installing ${packagesToInstall.length} packages with ${packageManager.name}`)
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
await installPackages(packagesToInstall, packageManager, cwd)
|
|
266
|
+
spinner.stop(`Installed ${packagesToInstall.length} packages`)
|
|
267
|
+
} catch (error) {
|
|
268
|
+
spinner.stop('Installation failed')
|
|
269
|
+
throw error
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Generate config file
|
|
273
|
+
const configSpinner = clack.spinner()
|
|
274
|
+
configSpinner.start('Creating kubb.config.ts')
|
|
275
|
+
|
|
276
|
+
const configContent = generateConfigFile(selectedPlugins, inputPath, outputPath)
|
|
277
|
+
const configPath = path.join(cwd, 'kubb.config.ts')
|
|
278
|
+
|
|
279
|
+
if (fs.existsSync(configPath)) {
|
|
280
|
+
configSpinner.stop('kubb.config.ts already exists')
|
|
281
|
+
|
|
282
|
+
if (!yes) {
|
|
283
|
+
const shouldOverwrite = await clack.confirm({
|
|
284
|
+
message: 'kubb.config.ts already exists. Overwrite?',
|
|
285
|
+
initialValue: false,
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
if (clack.isCancel(shouldOverwrite) || !shouldOverwrite) {
|
|
289
|
+
cancelAndExit('Keeping existing configuration. Packages have been installed.')
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
configSpinner.start('Overwriting kubb.config.ts')
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
fs.writeFileSync(configPath, configContent, 'utf-8')
|
|
297
|
+
|
|
298
|
+
configSpinner.stop('Created kubb.config.ts')
|
|
299
|
+
|
|
300
|
+
clack.outro(
|
|
301
|
+
styleText('green', '✓ All set!') +
|
|
302
|
+
'\n\n' +
|
|
303
|
+
styleText('dim', 'Next steps:') +
|
|
304
|
+
'\n' +
|
|
305
|
+
styleText('cyan', ` 1. Make sure your OpenAPI spec is at: ${inputPath}`) +
|
|
306
|
+
'\n' +
|
|
307
|
+
styleText('cyan', ' 2. Generate code with: npx kubb generate') +
|
|
308
|
+
'\n' +
|
|
309
|
+
styleText('cyan', ' Or start a stream server with: npx kubb start') +
|
|
310
|
+
'\n' +
|
|
311
|
+
styleText('cyan', ` 3. Find generated files in: ${outputPath}`) +
|
|
312
|
+
'\n\n' +
|
|
313
|
+
styleText('dim', `Using ${packageManager.name} • Kubb v${version}`),
|
|
314
|
+
)
|
|
315
|
+
} catch (error) {
|
|
316
|
+
clack.log.error(styleText('red', 'An error occurred during initialization'))
|
|
317
|
+
if (error instanceof Error) {
|
|
318
|
+
clack.log.error(error.message)
|
|
319
|
+
}
|
|
320
|
+
process.exit(1)
|
|
321
|
+
}
|
|
322
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import process from 'node:process'
|
|
2
|
+
import { styleText } from 'node:util'
|
|
3
|
+
import type * as McpModule from '@kubb/mcp'
|
|
4
|
+
import { getErrorMessage } from '../utils/errors.ts'
|
|
5
|
+
import { jiti } from '../utils/jiti.ts'
|
|
6
|
+
import { buildTelemetryEvent, sendTelemetry } from '../utils/telemetry.ts'
|
|
7
|
+
|
|
8
|
+
type McpOptions = {
|
|
9
|
+
version: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function runMcp({ version }: McpOptions): Promise<void> {
|
|
13
|
+
let mod: typeof McpModule
|
|
14
|
+
try {
|
|
15
|
+
mod = (await jiti.import('@kubb/mcp', { default: true })) as typeof McpModule
|
|
16
|
+
} catch (_e) {
|
|
17
|
+
console.error(`Import of '@kubb/mcp' is required to start the MCP server`)
|
|
18
|
+
process.exit(1)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const { run } = mod
|
|
22
|
+
const hrStart = process.hrtime()
|
|
23
|
+
try {
|
|
24
|
+
console.log('⏳ Starting MCP server...')
|
|
25
|
+
console.warn(styleText('yellow', 'This feature is still under development — use with caution'))
|
|
26
|
+
run()
|
|
27
|
+
await sendTelemetry(buildTelemetryEvent({ command: 'mcp', kubbVersion: version, hrStart, status: 'success' }))
|
|
28
|
+
} catch (error) {
|
|
29
|
+
await sendTelemetry(buildTelemetryEvent({ command: 'mcp', kubbVersion: version, hrStart, status: 'failed' }))
|
|
30
|
+
console.error(getErrorMessage(error))
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import process from 'node:process'
|
|
2
|
+
import type * as OasModule from '@kubb/oas'
|
|
3
|
+
import { getErrorMessage } from '../utils/errors.ts'
|
|
4
|
+
import { jiti } from '../utils/jiti.ts'
|
|
5
|
+
import { buildTelemetryEvent, sendTelemetry } from '../utils/telemetry.ts'
|
|
6
|
+
|
|
7
|
+
type ValidateOptions = {
|
|
8
|
+
input: string
|
|
9
|
+
version: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function runValidate({ input, version }: ValidateOptions): Promise<void> {
|
|
13
|
+
let mod: typeof OasModule
|
|
14
|
+
try {
|
|
15
|
+
mod = (await jiti.import('@kubb/oas', { default: true })) as typeof OasModule
|
|
16
|
+
} catch (_e) {
|
|
17
|
+
console.error(`Import of '@kubb/oas' is required to do validation`)
|
|
18
|
+
process.exit(1)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const { parse } = mod
|
|
22
|
+
const hrStart = process.hrtime()
|
|
23
|
+
try {
|
|
24
|
+
const oas = await parse(input)
|
|
25
|
+
await oas.validate()
|
|
26
|
+
|
|
27
|
+
await sendTelemetry(buildTelemetryEvent({ command: 'validate', kubbVersion: version, hrStart, status: 'success' }))
|
|
28
|
+
console.log('✅ Validation success')
|
|
29
|
+
} catch (error) {
|
|
30
|
+
await sendTelemetry(buildTelemetryEvent({ command: 'validate', kubbVersion: version, hrStart, status: 'failed' }))
|
|
31
|
+
console.error('❌ Validation failed')
|
|
32
|
+
console.error(getErrorMessage(error))
|
|
33
|
+
process.exit(1)
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/utils/Writables.ts
CHANGED
|
@@ -10,8 +10,8 @@ export class ClackWritable extends Writable {
|
|
|
10
10
|
|
|
11
11
|
this.taskLog = taskLog
|
|
12
12
|
}
|
|
13
|
-
_write(chunk:
|
|
14
|
-
this.taskLog.message(`${styleText('dim', chunk
|
|
13
|
+
_write(chunk: Buffer, _encoding: BufferEncoding, callback: (error?: Error | null) => void): void {
|
|
14
|
+
this.taskLog.message(`${styleText('dim', chunk.toString())}`)
|
|
15
15
|
callback()
|
|
16
16
|
}
|
|
17
17
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if running in GitHub Actions environment
|
|
3
|
+
*/
|
|
4
|
+
export function isGitHubActions(): boolean {
|
|
5
|
+
return !!process.env.GITHUB_ACTIONS
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Check if running in any CI environment.
|
|
10
|
+
* Covers all major CI systems via their well-known environment variables.
|
|
11
|
+
*/
|
|
12
|
+
export function isCIEnvironment(): boolean {
|
|
13
|
+
return !!(
|
|
14
|
+
(
|
|
15
|
+
process.env.CI || // Generic (GitHub Actions, GitLab CI, CircleCI, Travis CI, etc.)
|
|
16
|
+
process.env.GITHUB_ACTIONS || // GitHub Actions
|
|
17
|
+
process.env.GITLAB_CI || // GitLab CI
|
|
18
|
+
process.env.BITBUCKET_BUILD_NUMBER || // Bitbucket Pipelines
|
|
19
|
+
process.env.JENKINS_URL || // Jenkins
|
|
20
|
+
process.env.CIRCLECI || // CircleCI
|
|
21
|
+
process.env.TRAVIS || // Travis CI
|
|
22
|
+
process.env.TEAMCITY_VERSION || // TeamCity
|
|
23
|
+
process.env.BUILDKITE || // Buildkite
|
|
24
|
+
process.env.TF_BUILD
|
|
25
|
+
) // Azure Pipelines
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Check if TTY is available for interactive output
|
|
31
|
+
*/
|
|
32
|
+
export function canUseTTY(): boolean {
|
|
33
|
+
return !!process.stdout.isTTY && !isCIEnvironment()
|
|
34
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coerce an unknown thrown value to an `Error` instance.
|
|
3
|
+
* When the value is already an `Error` it is returned as-is;
|
|
4
|
+
* otherwise a new `Error` is created whose message is `String(value)`.
|
|
5
|
+
*/
|
|
6
|
+
export function toError(value: unknown): Error {
|
|
7
|
+
return value instanceof Error ? value : new Error(String(value))
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Safely extract a human-readable message from any thrown value.
|
|
12
|
+
*/
|
|
13
|
+
export function getErrorMessage(value: unknown): string {
|
|
14
|
+
return value instanceof Error ? value.message : String(value)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Extract the `.cause` property of an `Error` as an `Error | undefined`.
|
|
19
|
+
* Returns `undefined` when the cause is absent or is not an `Error`.
|
|
20
|
+
*/
|
|
21
|
+
export function toCause(error: Error): Error | undefined {
|
|
22
|
+
return error.cause instanceof Error ? error.cause : undefined
|
|
23
|
+
}
|
|
@@ -20,14 +20,26 @@ export async function executeHooks({ hooks, events }: ExecutingHooksProps): Prom
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
const hookId = createHash('sha256').update(command).digest('hex')
|
|
23
|
-
await events.emit('hook:start', { id: hookId, command: cmd, args })
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
// Wire up the hook:end listener BEFORE emitting hook:start to avoid the race condition
|
|
25
|
+
// where hook:end fires synchronously inside emit('hook:start') before the listener is registered.
|
|
26
|
+
const hookEndPromise = new Promise<void>((resolve, reject) => {
|
|
27
|
+
const handler = ({ id, success, error }: { id?: string; command: string; args?: readonly string[]; success: boolean; error: Error | null }) => {
|
|
28
|
+
if (id !== hookId) return
|
|
29
|
+
events.off('hook:end', handler)
|
|
30
|
+
if (!success) {
|
|
31
|
+
reject(error ?? new Error(`Hook failed: ${command}`))
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
events
|
|
35
|
+
.emit('success', `${styleText('dim', command)} successfully executed`)
|
|
36
|
+
.then(resolve)
|
|
37
|
+
.catch(reject)
|
|
28
38
|
}
|
|
29
|
-
|
|
30
|
-
await events.emit('success', `${styleText('dim', command)} successfully executed`)
|
|
39
|
+
events.on('hook:end', handler)
|
|
31
40
|
})
|
|
41
|
+
|
|
42
|
+
await events.emit('hook:start', { id: hookId, command: cmd, args })
|
|
43
|
+
await hookEndPromise
|
|
32
44
|
}
|
|
33
45
|
}
|
|
@@ -2,24 +2,23 @@ import type { defineConfig, UserConfig } from '@kubb/core'
|
|
|
2
2
|
import { cosmiconfig } from 'cosmiconfig'
|
|
3
3
|
import { createJiti } from 'jiti'
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
type CosmiconfigResult = {
|
|
6
6
|
filepath: string
|
|
7
7
|
isEmpty?: boolean
|
|
8
8
|
config: ReturnType<typeof defineConfig> | UserConfig
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
})
|
|
11
|
+
const jiti = createJiti(import.meta.url, {
|
|
12
|
+
jsx: {
|
|
13
|
+
runtime: 'automatic',
|
|
14
|
+
importSource: '@kubb/react-fabric',
|
|
15
|
+
},
|
|
16
|
+
sourceMaps: true,
|
|
17
|
+
interopDefault: true,
|
|
18
|
+
})
|
|
20
19
|
|
|
20
|
+
const tsLoader = async (configFile: string) => {
|
|
21
21
|
const mod = await jiti.import(configFile, { default: true })
|
|
22
|
-
|
|
23
22
|
return mod
|
|
24
23
|
}
|
|
25
24
|
|
package/src/utils/getIntro.ts
CHANGED
|
@@ -5,28 +5,27 @@ import { styleText } from 'node:util'
|
|
|
5
5
|
* Supports hex color codes without external dependencies like chalk
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
/** Parse a hex color string into RGB components, defaulting to 255 on invalid input. */
|
|
9
|
+
function parseHex(color: string): { r: number; g: number; b: number } {
|
|
10
|
+
const c = color.replace('#', '')
|
|
11
|
+
const r = Number.parseInt(c.slice(0, 2), 16)
|
|
12
|
+
const g = Number.parseInt(c.slice(2, 4), 16)
|
|
13
|
+
const b = Number.parseInt(c.slice(4, 6), 16)
|
|
14
|
+
return {
|
|
15
|
+
r: Number.isNaN(r) ? 255 : r,
|
|
16
|
+
g: Number.isNaN(g) ? 255 : g,
|
|
17
|
+
b: Number.isNaN(b) ? 255 : b,
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
8
21
|
/**
|
|
9
22
|
* Convert hex color to ANSI 24-bit true color escape sequence
|
|
10
23
|
* @param color - Hex color code (with or without #), e.g., '#FF5500' or 'FF5500'
|
|
11
24
|
* @returns Function that wraps text with the color
|
|
12
25
|
*/
|
|
13
26
|
function hex(color: string): (text: string) => string {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
const g = Number.parseInt(cleanHex.slice(2, 4), 16)
|
|
17
|
-
const b = Number.parseInt(cleanHex.slice(4, 6), 16)
|
|
18
|
-
|
|
19
|
-
// Default to white (255) if parsing fails (NaN)
|
|
20
|
-
const safeR = Number.isNaN(r) ? 255 : r
|
|
21
|
-
const safeG = Number.isNaN(g) ? 255 : g
|
|
22
|
-
const safeB = Number.isNaN(b) ? 255 : b
|
|
23
|
-
|
|
24
|
-
return (text: string) => `\x1b[38;2;${safeR};${safeG};${safeB}m${text}\x1b[0m`
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function hexToRgb(color: string) {
|
|
28
|
-
const c = color.replace('#', '')
|
|
29
|
-
return { r: Number.parseInt(c.slice(0, 2), 16), g: Number.parseInt(c.slice(2, 4), 16), b: Number.parseInt(c.slice(4, 6), 16) }
|
|
27
|
+
const { r, g, b } = parseHex(color)
|
|
28
|
+
return (text: string) => `\x1b[38;2;${r};${g};${b}m${text}\x1b[0m`
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
function gradient(colors: string[]) {
|
|
@@ -37,8 +36,8 @@ function gradient(colors: string[]) {
|
|
|
37
36
|
const t = chars.length <= 1 ? 0 : i / (chars.length - 1)
|
|
38
37
|
const seg = Math.min(Math.floor(t * (colors.length - 1)), colors.length - 2)
|
|
39
38
|
const lt = t * (colors.length - 1) - seg
|
|
40
|
-
const from =
|
|
41
|
-
const to =
|
|
39
|
+
const from = parseHex(colors[seg]!)
|
|
40
|
+
const to = parseHex(colors[seg + 1]!)
|
|
42
41
|
const r = Math.round(from.r + (to.r - from.r) * lt)
|
|
43
42
|
const g = Math.round(from.g + (to.g - from.g) * lt)
|
|
44
43
|
const b = Math.round(from.b + (to.b - from.b) * lt)
|
package/src/utils/getSummary.ts
CHANGED
|
@@ -2,6 +2,7 @@ import path from 'node:path'
|
|
|
2
2
|
import { styleText } from 'node:util'
|
|
3
3
|
import type { Config, Plugin } from '@kubb/core'
|
|
4
4
|
import { formatHrtime } from '@kubb/core/utils'
|
|
5
|
+
import { SUMMARY_MAX_BAR_LENGTH, SUMMARY_TIME_SCALE_DIVISOR } from '../constants.ts'
|
|
5
6
|
import { randomCliColor } from './randomColor.ts'
|
|
6
7
|
|
|
7
8
|
type SummaryProps = {
|
|
@@ -16,7 +17,7 @@ type SummaryProps = {
|
|
|
16
17
|
export function getSummary({ failedPlugins, filesCreated, status, hrStart, config, pluginTimings }: SummaryProps): string[] {
|
|
17
18
|
const duration = formatHrtime(hrStart)
|
|
18
19
|
|
|
19
|
-
const pluginsCount = config.plugins?.length
|
|
20
|
+
const pluginsCount = config.plugins?.length ?? 0
|
|
20
21
|
const successCount = pluginsCount - failedPlugins.size
|
|
21
22
|
|
|
22
23
|
const meta = {
|
|
@@ -24,8 +25,8 @@ export function getSummary({ failedPlugins, filesCreated, status, hrStart, confi
|
|
|
24
25
|
status === 'success'
|
|
25
26
|
? `${styleText('green', `${successCount} successful`)}, ${pluginsCount} total`
|
|
26
27
|
: `${styleText('green', `${successCount} successful`)}, ${styleText('red', `${failedPlugins.size} failed`)}, ${pluginsCount} total`,
|
|
27
|
-
pluginsFailed: status === 'failed' ? [...failedPlugins]
|
|
28
|
-
filesCreated
|
|
28
|
+
pluginsFailed: status === 'failed' ? [...failedPlugins].map(({ plugin }) => randomCliColor(plugin.name)).join(', ') : undefined,
|
|
29
|
+
filesCreated,
|
|
29
30
|
time: styleText('green', duration),
|
|
30
31
|
output: path.isAbsolute(config.root) ? path.resolve(config.root, config.output.path) : config.root,
|
|
31
32
|
} as const
|
|
@@ -49,22 +50,17 @@ export function getSummary({ failedPlugins, filesCreated, status, hrStart, confi
|
|
|
49
50
|
summaryLines.push(`${labels.generated.padEnd(maxLength + 2)} ${meta.filesCreated} files in ${meta.time}`)
|
|
50
51
|
|
|
51
52
|
if (pluginTimings && pluginTimings.size > 0) {
|
|
52
|
-
const TIME_SCALE_DIVISOR = 100
|
|
53
|
-
const MAX_BAR_LENGTH = 10
|
|
54
|
-
|
|
55
53
|
const sortedTimings = Array.from(pluginTimings.entries()).sort((a, b) => b[1] - a[1])
|
|
56
54
|
|
|
57
|
-
|
|
58
|
-
summaryLines.push(`${labels.pluginTimings}`)
|
|
55
|
+
summaryLines.push(`${labels.pluginTimings}`)
|
|
59
56
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
57
|
+
sortedTimings.forEach(([name, time]) => {
|
|
58
|
+
const timeStr = time >= 1000 ? `${(time / 1000).toFixed(2)}s` : `${Math.round(time)}ms`
|
|
59
|
+
const barLength = Math.min(Math.ceil(time / SUMMARY_TIME_SCALE_DIVISOR), SUMMARY_MAX_BAR_LENGTH)
|
|
60
|
+
const bar = styleText('dim', '█'.repeat(barLength))
|
|
64
61
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
62
|
+
summaryLines.push(`${styleText('dim', '•')} ${name.padEnd(maxLength + 1)}${bar} ${timeStr}`)
|
|
63
|
+
})
|
|
68
64
|
}
|
|
69
65
|
|
|
70
66
|
summaryLines.push(`${labels.output.padEnd(maxLength + 2)} ${meta.output}`)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createJiti } from 'jiti'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared jiti instance for dynamic ESM/TS imports across CLI commands.
|
|
5
|
+
* Created once at module scope to avoid the overhead of repeated instantiation.
|
|
6
|
+
*/
|
|
7
|
+
export const jiti = createJiti(import.meta.url, {
|
|
8
|
+
sourceMaps: true,
|
|
9
|
+
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process'
|
|
2
1
|
import fs from 'node:fs'
|
|
3
2
|
import path from 'node:path'
|
|
4
3
|
import type { PackageManagerInfo, PackageManagerName } from '@kubb/core'
|
|
4
|
+
import { spawnAsync } from './spawnAsync.ts'
|
|
5
5
|
|
|
6
6
|
export function hasPackageJson(cwd: string = process.cwd()): boolean {
|
|
7
7
|
return fs.existsSync(path.join(cwd, 'package.json'))
|
|
@@ -15,9 +15,9 @@ export async function initPackageJson(cwd: string, packageManager: PackageManage
|
|
|
15
15
|
bun: ['init', '-y'],
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
await spawnAsync(packageManager.name, commands[packageManager.name], { cwd })
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export async function installPackages(packages: string[], packageManager: PackageManagerInfo, cwd: string = process.cwd()): Promise<void> {
|
|
22
|
-
|
|
22
|
+
await spawnAsync(packageManager.name, [...packageManager.installCommand, ...packages], { cwd })
|
|
23
23
|
}
|