@kubb/cli 4.19.1 → 4.20.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.
Files changed (32) hide show
  1. package/README.md +50 -0
  2. package/dist/{generate-BQ34rNhd.cjs → generate-CWAZRLYc.cjs} +2 -2
  3. package/dist/{generate-BQ34rNhd.cjs.map → generate-CWAZRLYc.cjs.map} +1 -1
  4. package/dist/{generate-BnP8jP3k.js → generate-DSJR_rMo.js} +2 -2
  5. package/dist/{generate-BnP8jP3k.js.map → generate-DSJR_rMo.js.map} +1 -1
  6. package/dist/index.cjs +8 -6
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.js +8 -6
  9. package/dist/index.js.map +1 -1
  10. package/dist/init-1NSq94WO.js +301 -0
  11. package/dist/init-1NSq94WO.js.map +1 -0
  12. package/dist/init-B7cfUzZM.cjs +306 -0
  13. package/dist/init-B7cfUzZM.cjs.map +1 -0
  14. package/dist/{mcp-YL9yfMsp.cjs → mcp-BYpzTkE1.cjs} +1 -1
  15. package/dist/{mcp-YL9yfMsp.cjs.map → mcp-BYpzTkE1.cjs.map} +1 -1
  16. package/dist/{mcp-D7dx28gT.js → mcp-g6I9AZG_.js} +1 -1
  17. package/dist/{mcp-D7dx28gT.js.map → mcp-g6I9AZG_.js.map} +1 -1
  18. package/dist/{package-CNHUHlHr.cjs → package-BJVLTP7y.cjs} +2 -2
  19. package/dist/package-BJVLTP7y.cjs.map +1 -0
  20. package/dist/package-BveKd8no.js +6 -0
  21. package/dist/package-BveKd8no.js.map +1 -0
  22. package/dist/{validate-BQJ4DDaA.js → validate-B3wmOn_B.js} +1 -1
  23. package/dist/{validate-BQJ4DDaA.js.map → validate-B3wmOn_B.js.map} +1 -1
  24. package/dist/{validate-C2Mxy9Zc.cjs → validate-D7BwOS9u.cjs} +1 -1
  25. package/dist/{validate-C2Mxy9Zc.cjs.map → validate-D7BwOS9u.cjs.map} +1 -1
  26. package/package.json +5 -5
  27. package/src/commands/init.ts +326 -0
  28. package/src/index.ts +2 -1
  29. package/src/utils/packageManager.ts +87 -0
  30. package/dist/package-CNHUHlHr.cjs.map +0 -1
  31. package/dist/package-D9HFEyVu.js +0 -6
  32. package/dist/package-D9HFEyVu.js.map +0 -1
@@ -0,0 +1,326 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import process from 'node:process'
4
+ import * as clack from '@clack/prompts'
5
+ import { defineCommand } from 'citty'
6
+ import pc from 'picocolors'
7
+ import { version } from '../../package.json'
8
+ import { detectPackageManager, hasPackageJson, initPackageJson, installPackages, type PackageManagerInfo } 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 plugins: 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
+ if (plugin.value === 'plugin-oas') {
121
+ return ' pluginOas(),'
122
+ }
123
+ if (plugin.value === 'plugin-ts') {
124
+ return ` pluginTs({\n output: {\n path: 'models',\n },\n }),`
125
+ }
126
+ if (plugin.value === 'plugin-client') {
127
+ return ` pluginClient({\n output: {\n path: 'clients',\n },\n }),`
128
+ }
129
+ if (plugin.value === 'plugin-react-query') {
130
+ return ` pluginReactQuery({\n output: {\n path: 'hooks',\n },\n }),`
131
+ }
132
+ if (plugin.value === 'plugin-zod') {
133
+ return ` pluginZod({\n output: {\n path: 'zod',\n },\n }),`
134
+ }
135
+ if (plugin.value === 'plugin-faker') {
136
+ return ` pluginFaker({\n output: {\n path: 'mocks',\n },\n }),`
137
+ }
138
+ if (plugin.value === 'plugin-msw') {
139
+ return ` pluginMsw({\n output: {\n path: 'msw',\n },\n }),`
140
+ }
141
+ if (plugin.value === 'plugin-swr') {
142
+ return ` pluginSwr({\n output: {\n path: 'hooks',\n },\n }),`
143
+ }
144
+ // Default config for other plugins
145
+ return ` ${plugin.importName}(),`
146
+ })
147
+ .join('\n')
148
+
149
+ return `import { defineConfig } from '@kubb/core'
150
+ ${imports}
151
+
152
+ export default defineConfig({
153
+ root: '.',
154
+ input: {
155
+ path: '${inputPath}',
156
+ },
157
+ output: {
158
+ path: '${outputPath}',
159
+ clean: true,
160
+ },
161
+ plugins: [
162
+ ${pluginConfigs}
163
+ ],
164
+ })
165
+ `
166
+ }
167
+
168
+ const command = defineCommand({
169
+ meta: {
170
+ name: 'init',
171
+ description: 'Initialize a new Kubb project with interactive setup',
172
+ },
173
+ async run() {
174
+ const cwd = process.cwd()
175
+
176
+ clack.intro(pc.bgCyan(pc.black(' Kubb Init ')))
177
+
178
+ try {
179
+ // Check/create package.json
180
+ let packageManager: PackageManagerInfo
181
+ if (!hasPackageJson(cwd)) {
182
+ const shouldInit = await clack.confirm({
183
+ message: 'No package.json found. Would you like to create one?',
184
+ initialValue: true,
185
+ })
186
+
187
+ if (clack.isCancel(shouldInit) || !shouldInit) {
188
+ clack.cancel('Operation cancelled.')
189
+ process.exit(0)
190
+ }
191
+
192
+ // Detect package manager before initializing
193
+ packageManager = detectPackageManager(cwd)
194
+
195
+ const spinner = clack.spinner()
196
+ spinner.start(`Initializing package.json with ${packageManager.name}`)
197
+
198
+ await initPackageJson(cwd, packageManager)
199
+
200
+ spinner.stop(`Created package.json with ${packageManager.name}`)
201
+ } else {
202
+ packageManager = detectPackageManager(cwd)
203
+ clack.log.info(`Detected package manager: ${pc.cyan(packageManager.name)}`)
204
+ }
205
+
206
+ // Prompt for OpenAPI spec path
207
+ const inputPath = await clack.text({
208
+ message: 'Where is your OpenAPI specification located?',
209
+ placeholder: './openapi.yaml',
210
+ defaultValue: './openapi.yaml',
211
+ validate: (value) => {
212
+ if (!value) return 'Input path is required'
213
+ },
214
+ })
215
+
216
+ if (clack.isCancel(inputPath)) {
217
+ clack.cancel('Operation cancelled.')
218
+ process.exit(0)
219
+ }
220
+
221
+ // Prompt for output directory
222
+ const outputPath = await clack.text({
223
+ message: 'Where should the generated files be output?',
224
+ placeholder: './src/gen',
225
+ defaultValue: './src/gen',
226
+ validate: (value) => {
227
+ if (!value) return 'Output path is required'
228
+ },
229
+ })
230
+
231
+ if (clack.isCancel(outputPath)) {
232
+ clack.cancel('Operation cancelled.')
233
+ process.exit(0)
234
+ }
235
+
236
+ // Plugin selection
237
+ const selectedPluginValues = await clack.multiselect({
238
+ message: 'Select plugins to use:',
239
+ options: plugins.map((plugin) => ({
240
+ value: plugin.value,
241
+ label: plugin.label,
242
+ hint: plugin.hint,
243
+ })),
244
+ initialValues: ['plugin-oas', 'plugin-ts'],
245
+ required: true,
246
+ })
247
+
248
+ if (clack.isCancel(selectedPluginValues)) {
249
+ clack.cancel('Operation cancelled.')
250
+ process.exit(0)
251
+ }
252
+
253
+ const selectedPlugins = plugins.filter((plugin) => (selectedPluginValues as string[]).includes(plugin.value))
254
+
255
+ // Ensure plugin-oas is always included
256
+ if (!selectedPlugins.find((p) => p.value === 'plugin-oas')) {
257
+ selectedPlugins.unshift(plugins.find((p) => p.value === 'plugin-oas')!)
258
+ }
259
+
260
+ // Install packages
261
+ const packagesToInstall = ['@kubb/core', ...selectedPlugins.map((p) => p.packageName)]
262
+
263
+ const spinner = clack.spinner()
264
+ spinner.start(`Installing ${packagesToInstall.length} packages with ${packageManager.name}`)
265
+
266
+ try {
267
+ await installPackages(packagesToInstall, packageManager, cwd)
268
+ spinner.stop(`Installed ${packagesToInstall.length} packages`)
269
+ } catch (error) {
270
+ spinner.stop('Installation failed')
271
+ throw error
272
+ }
273
+
274
+ // Generate config file
275
+ const configSpinner = clack.spinner()
276
+ configSpinner.start('Creating kubb.config.ts')
277
+
278
+ const configContent = generateConfigFile(selectedPlugins, inputPath as string, outputPath as string)
279
+ const configPath = path.join(cwd, 'kubb.config.ts')
280
+
281
+ // Check if config already exists
282
+ if (fs.existsSync(configPath)) {
283
+ configSpinner.stop('kubb.config.ts already exists')
284
+
285
+ const shouldOverwrite = await clack.confirm({
286
+ message: 'kubb.config.ts already exists. Overwrite?',
287
+ initialValue: false,
288
+ })
289
+
290
+ if (clack.isCancel(shouldOverwrite) || !shouldOverwrite) {
291
+ clack.cancel('Keeping existing configuration. Packages have been installed.')
292
+ process.exit(0)
293
+ }
294
+
295
+ configSpinner.start('Overwriting kubb.config.ts')
296
+ }
297
+
298
+ fs.writeFileSync(configPath, configContent, 'utf-8')
299
+
300
+ configSpinner.stop('Created kubb.config.ts')
301
+
302
+ // Success message
303
+ clack.outro(
304
+ pc.green('✓ All set!') +
305
+ '\n\n' +
306
+ pc.dim('Next steps:') +
307
+ '\n' +
308
+ pc.cyan(` 1. Make sure your OpenAPI spec is at: ${inputPath}`) +
309
+ '\n' +
310
+ pc.cyan(' 2. Run: npx kubb generate') +
311
+ '\n' +
312
+ pc.cyan(` 3. Find generated files in: ${outputPath}`) +
313
+ '\n\n' +
314
+ pc.dim(`Using ${packageManager.name} • Kubb v${version}`),
315
+ )
316
+ } catch (error) {
317
+ clack.log.error(pc.red('An error occurred during initialization'))
318
+ if (error instanceof Error) {
319
+ clack.log.error(error.message)
320
+ }
321
+ process.exit(1)
322
+ }
323
+ },
324
+ })
325
+
326
+ export default command
package/src/index.ts CHANGED
@@ -20,7 +20,7 @@ const main = defineCommand({
20
20
  process.exit(0)
21
21
  }
22
22
 
23
- if (!['generate', 'validate', 'mcp'].includes(rawArgs[0] as string)) {
23
+ if (!['generate', 'validate', 'mcp', 'init'].includes(rawArgs[0] as string)) {
24
24
  // generate is not being used
25
25
  const generateCommand = await import('./commands/generate.ts').then((r) => r.default)
26
26
 
@@ -33,6 +33,7 @@ const main = defineCommand({
33
33
  generate: () => import('./commands/generate.ts').then((r) => r.default),
34
34
  validate: () => import('./commands/validate.ts').then((r) => r.default),
35
35
  mcp: () => import('./commands/mcp.ts').then((r) => r.default),
36
+ init: () => import('./commands/init.ts').then((r) => r.default),
36
37
  },
37
38
  })
38
39
 
@@ -0,0 +1,87 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { execa } from 'execa'
4
+
5
+ export type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun'
6
+
7
+ export interface PackageManagerInfo {
8
+ name: PackageManager
9
+ lockFile: string
10
+ installCommand: string[]
11
+ }
12
+
13
+ const packageManagers: Record<PackageManager, PackageManagerInfo> = {
14
+ pnpm: {
15
+ name: 'pnpm',
16
+ lockFile: 'pnpm-lock.yaml',
17
+ installCommand: ['add', '-D'],
18
+ },
19
+ yarn: {
20
+ name: 'yarn',
21
+ lockFile: 'yarn.lock',
22
+ installCommand: ['add', '-D'],
23
+ },
24
+ bun: {
25
+ name: 'bun',
26
+ lockFile: 'bun.lockb',
27
+ installCommand: ['add', '-d'],
28
+ },
29
+ npm: {
30
+ name: 'npm',
31
+ lockFile: 'package-lock.json',
32
+ installCommand: ['install', '--save-dev'],
33
+ },
34
+ }
35
+
36
+ export function detectPackageManager(cwd: string = process.cwd()): PackageManagerInfo {
37
+ // Check for packageManager field in package.json
38
+ const packageJsonPath = path.join(cwd, 'package.json')
39
+ if (fs.existsSync(packageJsonPath)) {
40
+ try {
41
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'))
42
+ if (packageJson.packageManager) {
43
+ const [name] = packageJson.packageManager.split('@')
44
+ if (name in packageManagers) {
45
+ return packageManagers[name as PackageManager]
46
+ }
47
+ }
48
+ } catch {
49
+ // Continue to lock file detection
50
+ }
51
+ }
52
+
53
+ // Check for lock files
54
+ for (const pm of Object.values(packageManagers)) {
55
+ if (fs.existsSync(path.join(cwd, pm.lockFile))) {
56
+ return pm
57
+ }
58
+ }
59
+
60
+ // Default to npm
61
+ return packageManagers.npm
62
+ }
63
+
64
+ export function hasPackageJson(cwd: string = process.cwd()): boolean {
65
+ return fs.existsSync(path.join(cwd, 'package.json'))
66
+ }
67
+
68
+ export async function initPackageJson(cwd: string, packageManager: PackageManagerInfo): Promise<void> {
69
+ const commands: Record<PackageManager, string[]> = {
70
+ npm: ['init', '-y'],
71
+ pnpm: ['init'],
72
+ yarn: ['init', '-y'],
73
+ bun: ['init', '-y'],
74
+ }
75
+
76
+ await execa(packageManager.name, commands[packageManager.name], {
77
+ cwd,
78
+ stdio: 'inherit',
79
+ })
80
+ }
81
+
82
+ export async function installPackages(packages: string[], packageManager: PackageManagerInfo, cwd: string = process.cwd()): Promise<void> {
83
+ await execa(packageManager.name, [...packageManager.installCommand, ...packages], {
84
+ cwd,
85
+ stdio: 'inherit',
86
+ })
87
+ }
@@ -1 +0,0 @@
1
- {"version":3,"file":"package-CNHUHlHr.cjs","names":[],"sources":["../package.json"],"sourcesContent":[""],"mappings":""}
@@ -1,6 +0,0 @@
1
- //#region package.json
2
- var version = "4.19.1";
3
-
4
- //#endregion
5
- export { version as t };
6
- //# sourceMappingURL=package-D9HFEyVu.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"package-D9HFEyVu.js","names":[],"sources":["../package.json"],"sourcesContent":[""],"mappings":""}