@kubb/cli 2.18.3 → 2.18.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/cli",
3
- "version": "2.18.3",
3
+ "version": "2.18.5",
4
4
  "description": "Generator cli",
5
5
  "keywords": [
6
6
  "typescript",
@@ -41,26 +41,30 @@
41
41
  ],
42
42
  "dependencies": {
43
43
  "bundle-require": "^4.1.0",
44
- "cac": "^6.7.14",
45
44
  "chokidar": "^3.6.0",
45
+ "citty": "^0.1.6",
46
+ "consola": "^3.2.3",
46
47
  "cosmiconfig": "^9.0.0",
47
48
  "esbuild": "^0.20.2",
48
49
  "execa": "^8.0.1",
49
50
  "js-runtime": "^0.0.8",
51
+ "latest-version": "^9.0.0",
50
52
  "ora": "^8.0.1",
51
- "pretty-error": "^4.0.0",
53
+ "semver": "^7.6.2",
52
54
  "string-argv": "^0.3.2",
53
55
  "tinyrainbow": "^1.1.1",
54
- "@kubb/core": "2.18.3"
56
+ "@kubb/core": "2.18.5",
57
+ "@kubb/fs": "2.18.5"
55
58
  },
56
59
  "devDependencies": {
57
60
  "@types/node": "^20.12.11",
61
+ "@types/semver": "^7.5.8",
58
62
  "source-map-support": "^0.5.21",
59
63
  "tsup": "^8.0.2",
60
64
  "typescript": "^5.4.5",
61
- "@kubb/config-ts": "2.18.3",
62
- "@kubb/config-tsup": "2.18.3",
63
- "@kubb/plugin-oas": "2.18.3"
65
+ "@kubb/config-ts": "2.18.5",
66
+ "@kubb/config-tsup": "2.18.5",
67
+ "@kubb/plugin-oas": "2.18.5"
64
68
  },
65
69
  "engines": {
66
70
  "node": ">=18",
@@ -0,0 +1,115 @@
1
+ import { LogLevel } from '@kubb/core/logger'
2
+ import { defineCommand, showUsage } from 'citty'
3
+ import type { ArgsDef, ParsedArgs } from 'citty'
4
+ import { execa } from 'execa'
5
+ import c from 'tinyrainbow'
6
+
7
+ import path from 'node:path'
8
+ import { getConfig } from '../utils/getConfig.ts'
9
+ import { getCosmiConfig } from '../utils/getCosmiConfig.ts'
10
+ import { spinner } from '../utils/spinner.ts'
11
+ import { startWatcher } from '../utils/watcher.ts'
12
+
13
+ import { PromiseManager, isInputPath } from '@kubb/core'
14
+ import { generate } from '../generate.ts'
15
+
16
+ const args = {
17
+ config: {
18
+ type: 'string',
19
+ description: 'Path to the Kubb config',
20
+ alias: 'c',
21
+ },
22
+ logLevel: {
23
+ type: 'string',
24
+ description: 'Info, silent or debug',
25
+ alias: 'l',
26
+ default: LogLevel.info,
27
+ valueHint: `${LogLevel.silent}|${LogLevel.info}|${LogLevel.debug}`,
28
+ },
29
+ watch: {
30
+ type: 'boolean',
31
+ description: 'Watch mode based on the input file',
32
+ alias: 'w',
33
+ default: false,
34
+ },
35
+ bun: {
36
+ type: 'boolean',
37
+ description: 'Run Kubb with Bun',
38
+ alias: 'b',
39
+ default: false,
40
+ },
41
+ debug: {
42
+ type: 'boolean',
43
+ description: 'Override logLevel to debug',
44
+ alias: 'd',
45
+ default: false,
46
+ },
47
+ help: {
48
+ type: 'boolean',
49
+ description: 'Show help',
50
+ alias: 'h',
51
+ default: false,
52
+ },
53
+ } as const satisfies ArgsDef
54
+
55
+ export type Args = ParsedArgs<typeof args>
56
+
57
+ const command = defineCommand({
58
+ meta: {
59
+ name: 'generate',
60
+ description: "[input] Generate files based on a 'kubb.config.ts' file",
61
+ },
62
+ args,
63
+ setup() {
64
+ spinner.start('🔍 Loading config')
65
+ },
66
+ async run({ args }) {
67
+ const input = args._[0]
68
+
69
+ if (args.help) {
70
+ showUsage(command)
71
+ return
72
+ }
73
+
74
+ if (args.debug) {
75
+ args.logLevel = LogLevel.debug
76
+ }
77
+
78
+ if (args.bun) {
79
+ const command = process.argv.splice(2).filter((item) => item !== '--bun')
80
+
81
+ await execa('bkubb', command, { stdout: process.stdout, stderr: process.stderr })
82
+ return
83
+ }
84
+
85
+ const result = await getCosmiConfig('kubb', args.config)
86
+ spinner.succeed(`🔍 Config loaded(${c.dim(path.relative(process.cwd(), result.filepath))})`)
87
+
88
+ const config = await getConfig(result, args)
89
+
90
+ if (args.watch) {
91
+ if (Array.isArray(config)) {
92
+ throw new Error('Cannot use watcher with multiple Configs(array)')
93
+ }
94
+
95
+ if (isInputPath(config)) {
96
+ return startWatcher([input || config.input.path], async (paths) => {
97
+ await generate({ config, args })
98
+ spinner.spinner = 'simpleDotsScrolling'
99
+ spinner.start(c.yellow(c.bold(`Watching for changes in ${paths.join(' and ')}`)))
100
+ })
101
+ }
102
+ }
103
+
104
+ if (Array.isArray(config)) {
105
+ const promiseManager = new PromiseManager()
106
+ const promises = config.map((item) => () => generate({ input, config: item, args }))
107
+
108
+ return promiseManager.run('seq', promises)
109
+ }
110
+
111
+ await generate({ input, config, args })
112
+ },
113
+ })
114
+
115
+ export default command
package/src/generate.ts CHANGED
@@ -1,91 +1,49 @@
1
- import { LogLevel, createLogger, randomCliColour } from '@kubb/core/logger'
1
+ import { LogLevel, LogMapper, createLogger, randomCliColour } from '@kubb/core/logger'
2
2
 
3
- import { execa } from 'execa'
4
- import { get } from 'js-runtime'
5
- import { parseArgsStringToArgv } from 'string-argv'
6
3
  import c from 'tinyrainbow'
7
4
 
8
- import { OraWritable } from './utils/OraWritable.ts'
9
5
  import { spinner } from './utils/spinner.ts'
10
6
 
11
- import type { Writable } from 'node:stream'
12
- import { type CLIOptions, type Config, safeBuild } from '@kubb/core'
7
+ import { type Config, Warning, safeBuild } from '@kubb/core'
8
+ import { createConsola } from 'consola'
9
+ import type { Args } from './commands/generate.ts'
10
+ import { executeHooks } from './utils/executeHooks.ts'
11
+ import { getErrorCauses } from './utils/getErrorCauses.ts'
13
12
  import { getSummary } from './utils/getSummary.ts'
13
+ import { writeLog } from './utils/writeLog.ts'
14
14
 
15
15
  type GenerateProps = {
16
16
  input?: string
17
17
  config: Config
18
- CLIOptions: CLIOptions
18
+ args: Args
19
19
  }
20
20
 
21
- type ExecutingHooksProps = {
22
- hooks: Config['hooks']
23
- logLevel: LogLevel
24
- }
25
-
26
- async function executeHooks({ hooks, logLevel }: ExecutingHooksProps): Promise<void> {
27
- if (!hooks?.done) {
28
- return
29
- }
30
-
31
- const commands = Array.isArray(hooks.done) ? hooks.done : [hooks.done]
32
-
33
- if (logLevel === LogLevel.silent) {
34
- spinner.start('Executing hooks')
35
- }
36
-
37
- const executers = commands
38
- .map(async (command) => {
39
- const oraWritable = new OraWritable(spinner, command)
40
- const abortController = new AbortController()
41
- const [cmd, ..._args] = [...parseArgsStringToArgv(command)]
42
-
43
- if (!cmd) {
44
- return null
45
- }
46
-
47
- spinner.start(`Executing hook ${logLevel !== 'silent' ? c.dim(command) : ''}`)
48
-
49
- const subProcess = await execa(cmd, _args, {
50
- detached: true,
51
- signal: abortController.signal,
52
- }).pipeStdout?.(oraWritable as Writable)
53
- spinner.suffixText = ''
54
-
55
- if (logLevel === LogLevel.silent) {
56
- spinner.succeed(`Executing hook ${logLevel !== 'silent' ? c.dim(command) : ''}`)
57
-
58
- if (subProcess) {
59
- console.log(subProcess.stdout)
60
- }
61
- }
62
-
63
- oraWritable.destroy()
64
- return { subProcess, abort: abortController.abort.bind(abortController) }
65
- })
66
- .filter(Boolean)
67
-
68
- await Promise.all(executers)
69
-
70
- if (logLevel === LogLevel.silent) {
71
- spinner.succeed('Executing hooks')
72
- }
73
- }
74
-
75
- export async function generate({ input, config, CLIOptions }: GenerateProps): Promise<void> {
21
+ export async function generate({ input, config, args }: GenerateProps): Promise<void> {
22
+ const logLevel = (args.logLevel as LogLevel) || LogLevel.silent
76
23
  const logger = createLogger({
77
- logLevel: CLIOptions.logLevel || LogLevel.silent,
24
+ logLevel,
78
25
  name: config.name,
79
26
  spinner,
27
+ consola: createConsola({
28
+ level: LogMapper[logLevel] || 3,
29
+ }),
80
30
  })
81
31
 
32
+ logger.on('debug', async (messages: string[]) => {
33
+ if (logLevel === LogLevel.debug) {
34
+ await writeLog(messages.join('\n'))
35
+ }
36
+ })
37
+
38
+ logger.consola?.wrapConsole()
39
+
82
40
  if (logger.name) {
83
41
  spinner.prefixText = randomCliColour(logger.name)
84
42
  }
85
43
 
86
44
  const hrstart = process.hrtime()
87
45
 
88
- if (CLIOptions.logLevel === LogLevel.debug) {
46
+ if (args.logLevel === LogLevel.debug) {
89
47
  const { performance, PerformanceObserver } = await import('node:perf_hooks')
90
48
 
91
49
  const performanceOpserver = new PerformanceObserver((items) => {
@@ -100,10 +58,9 @@ export async function generate({ input, config, CLIOptions }: GenerateProps): Pr
100
58
  }
101
59
 
102
60
  const { root: _root, ...userConfig } = config
103
- const logLevel = logger.logLevel
104
61
  const inputPath = input ?? ('path' in userConfig.input ? userConfig.input.path : undefined)
105
62
 
106
- spinner.start(`🚀 Building with ${get()} ${logLevel !== 'silent' ? c.dim(inputPath) : ''}`)
63
+ spinner.start(`🚀 Building ${logLevel !== 'silent' ? c.dim(inputPath) : ''}`)
107
64
 
108
65
  const definedConfig: Config = {
109
66
  root: process.cwd(),
@@ -138,13 +95,27 @@ export async function generate({ input, config, CLIOptions }: GenerateProps): Pr
138
95
 
139
96
  console.log(summary.join(''))
140
97
 
141
- throw error
98
+ if (error instanceof Warning) {
99
+ spinner.warn(c.yellow(error.message))
100
+ process.exit(0)
101
+ }
102
+
103
+ const errors = getErrorCauses([error])
104
+ if (logger.consola && errors.length && logLevel === LogLevel.debug) {
105
+ errors.forEach((err) => {
106
+ logger.consola!.error(err)
107
+ })
108
+ }
109
+
110
+ logger.consola?.error(error)
111
+
112
+ process.exit(0)
142
113
  }
143
114
 
144
115
  await executeHooks({ hooks: config.hooks, logLevel })
145
116
 
146
117
  spinner.suffixText = ''
147
- spinner.succeed(`🚀 Build completed with ${get()} ${logLevel !== 'silent' ? c.dim(inputPath) : ''}`)
118
+ spinner.succeed(`🚀 Build completed ${logLevel !== 'silent' ? c.dim(inputPath) : ''}`)
148
119
 
149
120
  console.log(summary.join(''))
150
121
  }
package/src/index.ts CHANGED
@@ -1,110 +1,50 @@
1
- import path from 'node:path'
2
-
3
- import { PromiseManager, Warning, isInputPath } from '@kubb/core'
4
-
5
- import { cac } from 'cac'
6
- import c from 'tinyrainbow'
1
+ import { defineCommand, runCommand, runMain } from 'citty'
2
+ import getLatestVersion from 'latest-version'
3
+ import { lt } from 'semver'
7
4
 
5
+ import consola from 'consola'
8
6
  import { version } from '../package.json'
9
- import { generate } from './generate.ts'
10
- import { init } from './init.ts'
11
- import { getConfig } from './utils/getConfig.ts'
12
- import { getCosmiConfig } from './utils/getCosmiConfig.ts'
13
- import { renderErrors } from './utils/renderErrors.ts'
14
- import { spinner } from './utils/spinner.ts'
15
- import { startWatcher } from './utils/watcher.ts'
16
-
17
- import type { CLIOptions } from '@kubb/core'
18
- import { execa } from 'execa'
19
-
20
- const moduleName = 'kubb'
21
-
22
- function programCatcher(e: unknown, CLIOptions: CLIOptions): void {
23
- const error = e as Error
24
- const message = renderErrors(error, { logLevel: CLIOptions.logLevel })
25
-
26
- if (error instanceof Warning) {
27
- spinner.warn(c.yellow(error.message))
28
- process.exit(0)
29
- }
30
- spinner.fail(message)
31
- process.exit(1)
32
- }
33
-
34
- async function generateAction(input: string, CLIOptions: CLIOptions) {
35
- if (CLIOptions.bun) {
36
- const command = process.argv.splice(2).filter((item) => item !== '--bun')
37
-
38
- await execa('bkubb', command, { stdout: process.stdout, stderr: process.stderr })
39
- return
40
- }
41
-
42
- spinner.start('🔍 Loading config')
43
- const result = await getCosmiConfig(moduleName, CLIOptions.config)
44
- spinner.succeed(`🔍 Config loaded(${c.dim(path.relative(process.cwd(), result.filepath))})`)
45
-
46
- const config = await getConfig(result, CLIOptions)
47
-
48
- if (CLIOptions.watch) {
49
- if (Array.isArray(config)) {
50
- throw new Error('Cannot use watcher with multiple Configs(array)')
51
- }
52
7
 
53
- if (isInputPath(config)) {
54
- return startWatcher([input || config.input.path], async (paths) => {
55
- await generate({ config, CLIOptions })
56
- spinner.spinner = 'simpleDotsScrolling'
57
- spinner.start(c.yellow(c.bold(`Watching for changes in ${paths.join(' and ')}`)))
58
- })
8
+ const name = 'kubb'
9
+
10
+ const main = defineCommand({
11
+ meta: {
12
+ name,
13
+ version,
14
+ description: 'Kubb generation',
15
+ },
16
+ async setup({ rawArgs }) {
17
+ try {
18
+ const latestVersion = await getLatestVersion('@kubb/cli')
19
+
20
+ if (lt(version, latestVersion)) {
21
+ consola.box({
22
+ title: 'Update available for `Kubb` ',
23
+ message: `\`v${version}\` → \`v${latestVersion}\`
24
+ Run \`npm install -g @kubb/cli\` to update`,
25
+ style: {
26
+ padding: 2,
27
+ borderColor: 'yellow',
28
+ borderStyle: 'rounded',
29
+ },
30
+ })
31
+ }
32
+ } catch (_e) {}
33
+
34
+ if (rawArgs[0] !== 'generate') {
35
+ // generate is not being used
36
+ const generateCommand = await import('./commands/generate.ts').then((r) => r.default)
37
+
38
+ await runCommand(generateCommand, { rawArgs })
39
+
40
+ process.exit(0)
59
41
  }
60
- }
61
-
62
- if (Array.isArray(config)) {
63
- const promiseManager = new PromiseManager()
64
- const promises = config.map((item) => () => generate({ input, config: item, CLIOptions }))
65
-
66
- await promiseManager.run('seq', promises)
67
-
68
- return
69
- }
70
-
71
- await generate({ input, config, CLIOptions })
42
+ },
43
+ subCommands: {
44
+ generate: () => import('./commands/generate.ts').then((r) => r.default),
45
+ },
46
+ })
47
+
48
+ export async function run(_argv?: string[]): Promise<void> {
49
+ await runMain(main)
72
50
  }
73
-
74
- export async function run(argv?: string[]): Promise<void> {
75
- const program = cac(moduleName)
76
-
77
- program
78
- .command('[input]', 'Path of the input file(overrides the one in `kubb.config.js`)')
79
- .option('-c, --config <path>', 'Path to the Kubb config')
80
- .option('-l, --log-level <type>', 'Info, silent or debug')
81
- .option('-w, --watch', 'Watch mode based on the input file')
82
- .option('-b, --bun', 'Run Kubb with Bun')
83
- .action(generateAction)
84
-
85
- program
86
- .command('generate [input]', 'Path of the input file(overrides the one in `kubb.config.js`)')
87
- .option('-c, --config <path>', 'Path to the Kubb config')
88
- .option('-l, --log-level <type>', 'Info, silent or debug')
89
- .option('-w, --watch', 'Watch mode based on the input file')
90
- .option('-b, --bun', 'Run Kubb with Bun')
91
- .action(generateAction)
92
-
93
- program.command('init', 'Init Kubb').action(async () => {
94
- return init({ logLevel: 'info' })
95
- })
96
-
97
- program.help()
98
- program.version(version)
99
- program.parse(argv, { run: false })
100
-
101
- try {
102
- await program.runMatchedCommand()
103
-
104
- process.exit(0)
105
- } catch (e) {
106
- programCatcher(e, program.options)
107
- }
108
- }
109
-
110
- export default run
@@ -0,0 +1,64 @@
1
+ import { LogLevel } from '@kubb/core/logger'
2
+ import { execa } from 'execa'
3
+ import { parseArgsStringToArgv } from 'string-argv'
4
+ import c from 'tinyrainbow'
5
+
6
+ import { OraWritable } from './OraWritable.ts'
7
+ import { spinner } from './spinner.ts'
8
+
9
+ import type { Writable } from 'node:stream'
10
+ import type { Config } from '@kubb/core'
11
+
12
+ type ExecutingHooksProps = {
13
+ hooks: Config['hooks']
14
+ logLevel: LogLevel
15
+ }
16
+
17
+ export async function executeHooks({ hooks, logLevel }: ExecutingHooksProps): Promise<void> {
18
+ if (!hooks?.done) {
19
+ return
20
+ }
21
+
22
+ const commands = Array.isArray(hooks.done) ? hooks.done : [hooks.done]
23
+
24
+ if (logLevel === LogLevel.silent) {
25
+ spinner.start('Executing hooks')
26
+ }
27
+
28
+ const executers = commands
29
+ .map(async (command) => {
30
+ const oraWritable = new OraWritable(spinner, command)
31
+ const abortController = new AbortController()
32
+ const [cmd, ..._args] = [...parseArgsStringToArgv(command)]
33
+
34
+ if (!cmd) {
35
+ return null
36
+ }
37
+
38
+ spinner.start(`Executing hook ${logLevel !== 'silent' ? c.dim(command) : ''}`)
39
+
40
+ const subProcess = await execa(cmd, _args, {
41
+ detached: true,
42
+ signal: abortController.signal,
43
+ }).pipeStdout?.(oraWritable as Writable)
44
+ spinner.suffixText = ''
45
+
46
+ if (logLevel === LogLevel.silent) {
47
+ spinner.succeed(`Executing hook ${logLevel !== 'silent' ? c.dim(command) : ''}`)
48
+
49
+ if (subProcess) {
50
+ console.log(subProcess.stdout)
51
+ }
52
+ }
53
+
54
+ oraWritable.destroy()
55
+ return { subProcess, abort: abortController.abort.bind(abortController) }
56
+ })
57
+ .filter(Boolean)
58
+
59
+ await Promise.all(executers)
60
+
61
+ if (logLevel === LogLevel.silent) {
62
+ spinner.succeed('Executing hooks')
63
+ }
64
+ }
@@ -2,19 +2,20 @@ import { isPromise } from '@kubb/core/utils'
2
2
 
3
3
  import { getPlugins } from './getPlugins.ts'
4
4
 
5
- import type { CLIOptions, Config, UserConfig, defineConfig } from '@kubb/core'
5
+ import type { Config, UserConfig } from '@kubb/core'
6
+ import type { Args } from '../commands/generate.ts'
6
7
  import type { CosmiconfigResult } from './getCosmiConfig.ts'
7
8
 
8
9
  /**
9
10
  * Converting UserConfig to Config without a change in the object beside the JSON convert.
10
11
  */
11
- export async function getConfig(result: CosmiconfigResult, CLIOptions: CLIOptions): Promise<Array<Config> | Config> {
12
+ export async function getConfig(result: CosmiconfigResult, args: Args): Promise<Array<Config> | Config> {
12
13
  const config = result?.config
13
14
  let kubbUserConfig = Promise.resolve(config) as Promise<UserConfig | Array<UserConfig>>
14
15
 
15
16
  // for ts or js files
16
17
  if (typeof config === 'function') {
17
- const possiblePromise = config(CLIOptions)
18
+ const possiblePromise = config(args)
18
19
  if (isPromise(possiblePromise)) {
19
20
  kubbUserConfig = possiblePromise
20
21
  }
@@ -0,0 +1,14 @@
1
+ export function getErrorCauses(errors: Error[]): Error[] {
2
+ return errors
3
+ .reduce((prev, error) => {
4
+ const causedError = error?.cause as Error
5
+ if (causedError) {
6
+ prev = [...prev, ...getErrorCauses([causedError])]
7
+ return prev
8
+ }
9
+ prev = [...prev, error]
10
+
11
+ return prev
12
+ }, [] as Error[])
13
+ .filter(Boolean)
14
+ }
@@ -72,7 +72,6 @@ export function getSummary({ pluginManager, status, hrstart, config, logger }: S
72
72
  [` ${c.bold('Time:')} ${meta.time}`, true],
73
73
  [` ${c.bold('Ended:')} ${meta.endTime}`, true],
74
74
  [` ${c.bold('Output:')} ${meta.output}`, true],
75
- ['\n', true],
76
75
  ]
77
76
  .map((item) => {
78
77
  if (item.at(1)) {
@@ -0,0 +1,18 @@
1
+ import { resolve } from 'node:path'
2
+ import { read, write } from '@kubb/fs'
3
+
4
+ export async function writeLog(data: string): Promise<string | undefined> {
5
+ if (data.trim() === '') {
6
+ return undefined
7
+ }
8
+ const path = resolve(process.cwd(), 'kubb-log.log')
9
+ let previousLogs = ''
10
+
11
+ try {
12
+ previousLogs = await read(resolve(path))
13
+ } catch (_err) {
14
+ /* empty */
15
+ }
16
+
17
+ return write(path, [previousLogs, data.trim()].filter(Boolean).join('\n\n\n'), { sanity: false })
18
+ }