@kubb/cli 4.32.3 → 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-DFdkL6Kp.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-zZuxBP8z.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-C2pulzfz.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-BuijLPSZ.cjs +0 -20
- package/dist/agent-BuijLPSZ.cjs.map +0 -1
- package/dist/agent-Dswt_kxP.js +0 -20
- package/dist/agent-Dswt_kxP.js.map +0 -1
- package/dist/generate-DFdkL6Kp.cjs.map +0 -1
- package/dist/generate-zZuxBP8z.js.map +0 -1
- package/dist/init-CNLk2fNd.js +0 -304
- package/dist/init-CNLk2fNd.js.map +0 -1
- package/dist/init-CSP6FGaW.cjs +0 -308
- package/dist/init-CSP6FGaW.cjs.map +0 -1
- package/dist/mcp-44Od-yig.cjs +0 -57
- package/dist/mcp-44Od-yig.cjs.map +0 -1
- package/dist/mcp-CgaHrkDs.js +0 -57
- package/dist/mcp-CgaHrkDs.js.map +0 -1
- package/dist/package--eaEMq2R.js +0 -6
- package/dist/package--eaEMq2R.js.map +0 -1
- package/dist/package-C2pulzfz.cjs.map +0 -1
- package/dist/start-CB8afXV6.cjs +0 -134
- package/dist/start-CB8afXV6.cjs.map +0 -1
- package/dist/start-DHPjtHJj.js +0 -131
- package/dist/start-DHPjtHJj.js.map +0 -1
- package/dist/telemetry-BDSSqUiG.cjs.map +0 -1
- package/dist/telemetry-DYWvlxqs.js.map +0 -1
- package/dist/validate-C7s0cFnp.cjs +0 -66
- package/dist/validate-C7s0cFnp.cjs.map +0 -1
- package/dist/validate-_7cmvjg_.js +0 -66
- package/dist/validate-_7cmvjg_.js.map +0 -1
- package/src/loggers/envDetection.ts +0 -28
- package/src/loggers/index.ts +0 -5
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { relative } from 'node:path'
|
|
2
2
|
import { defineLogger, LogLevel } from '@kubb/core'
|
|
3
3
|
import { formatMs } from '@kubb/core/utils'
|
|
4
|
-
import {
|
|
4
|
+
import { SUMMARY_SEPARATOR } from '../constants.ts'
|
|
5
|
+
import { toCause } from '../utils/errors.ts'
|
|
5
6
|
import { getSummary } from '../utils/getSummary.ts'
|
|
7
|
+
import { runHook } from '../utils/runHook.ts'
|
|
8
|
+
import { formatCommandWithArgs, formatMessage } from './utils.ts'
|
|
6
9
|
|
|
7
10
|
/**
|
|
8
11
|
* Plain console adapter for non-TTY environments
|
|
@@ -11,21 +14,10 @@ import { getSummary } from '../utils/getSummary.ts'
|
|
|
11
14
|
export const plainLogger = defineLogger({
|
|
12
15
|
name: 'plain',
|
|
13
16
|
install(context, options) {
|
|
14
|
-
const logLevel = options?.logLevel
|
|
17
|
+
const logLevel = options?.logLevel ?? LogLevel.info
|
|
15
18
|
|
|
16
19
|
function getMessage(message: string): string {
|
|
17
|
-
|
|
18
|
-
const timestamp = new Date().toLocaleTimeString('en-US', {
|
|
19
|
-
hour12: false,
|
|
20
|
-
hour: '2-digit',
|
|
21
|
-
minute: '2-digit',
|
|
22
|
-
second: '2-digit',
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
return [`[${timestamp}]`, message].join(' ')
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return message
|
|
20
|
+
return formatMessage(message, logLevel)
|
|
29
21
|
}
|
|
30
22
|
|
|
31
23
|
context.on('info', (message, info) => {
|
|
@@ -59,7 +51,7 @@ export const plainLogger = defineLogger({
|
|
|
59
51
|
})
|
|
60
52
|
|
|
61
53
|
context.on('error', (error) => {
|
|
62
|
-
const caused = error
|
|
54
|
+
const caused = toCause(error)
|
|
63
55
|
|
|
64
56
|
const text = getMessage(['✗', error.message].join(' '))
|
|
65
57
|
|
|
@@ -108,7 +100,7 @@ export const plainLogger = defineLogger({
|
|
|
108
100
|
})
|
|
109
101
|
|
|
110
102
|
context.on('generation:start', () => {
|
|
111
|
-
const text = getMessage('
|
|
103
|
+
const text = getMessage('Generation started')
|
|
112
104
|
|
|
113
105
|
console.log(text)
|
|
114
106
|
})
|
|
@@ -210,7 +202,7 @@ export const plainLogger = defineLogger({
|
|
|
210
202
|
})
|
|
211
203
|
|
|
212
204
|
context.on('hook:start', async ({ id, command, args }) => {
|
|
213
|
-
const commandWithArgs =
|
|
205
|
+
const commandWithArgs = formatCommandWithArgs(command, args)
|
|
214
206
|
const text = getMessage(`Hook ${commandWithArgs} started`)
|
|
215
207
|
|
|
216
208
|
if (logLevel > LogLevel.silent) {
|
|
@@ -222,56 +214,17 @@ export const plainLogger = defineLogger({
|
|
|
222
214
|
return
|
|
223
215
|
}
|
|
224
216
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
if (logLevel > LogLevel.silent) {
|
|
237
|
-
console.log(result.stdout.trimEnd())
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
await context.emit('hook:end', {
|
|
241
|
-
command,
|
|
242
|
-
args,
|
|
243
|
-
id,
|
|
244
|
-
success: true,
|
|
245
|
-
error: null,
|
|
246
|
-
})
|
|
247
|
-
} catch (err) {
|
|
248
|
-
const error = err as NonZeroExitError
|
|
249
|
-
const stderr = error.output?.stderr ?? ''
|
|
250
|
-
const stdout = error.output?.stdout ?? ''
|
|
251
|
-
|
|
252
|
-
await context.emit('debug', {
|
|
253
|
-
date: new Date(),
|
|
254
|
-
logs: [stdout, stderr].filter(Boolean),
|
|
255
|
-
})
|
|
256
|
-
|
|
257
|
-
if (stderr) {
|
|
258
|
-
console.error(stderr)
|
|
259
|
-
}
|
|
260
|
-
if (stdout) {
|
|
261
|
-
console.log(stdout)
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
const errorMessage = new Error(`Hook execute failed: ${commandWithArgs}`)
|
|
265
|
-
|
|
266
|
-
await context.emit('hook:end', {
|
|
267
|
-
command,
|
|
268
|
-
args,
|
|
269
|
-
id,
|
|
270
|
-
success: false,
|
|
271
|
-
error: errorMessage,
|
|
272
|
-
})
|
|
273
|
-
await context.emit('error', errorMessage)
|
|
274
|
-
}
|
|
217
|
+
await runHook({
|
|
218
|
+
id,
|
|
219
|
+
command,
|
|
220
|
+
args,
|
|
221
|
+
commandWithArgs,
|
|
222
|
+
context,
|
|
223
|
+
sink: {
|
|
224
|
+
onStdout: logLevel > LogLevel.silent ? (s) => console.log(s) : undefined,
|
|
225
|
+
onStderr: logLevel > LogLevel.silent ? (s) => console.error(s) : undefined,
|
|
226
|
+
},
|
|
227
|
+
})
|
|
275
228
|
})
|
|
276
229
|
|
|
277
230
|
context.on('hook:end', ({ command, args }) => {
|
|
@@ -279,7 +232,7 @@ export const plainLogger = defineLogger({
|
|
|
279
232
|
return
|
|
280
233
|
}
|
|
281
234
|
|
|
282
|
-
const commandWithArgs =
|
|
235
|
+
const commandWithArgs = formatCommandWithArgs(command, args)
|
|
283
236
|
const text = getMessage(`Hook ${commandWithArgs} completed`)
|
|
284
237
|
|
|
285
238
|
console.log(text)
|
|
@@ -295,9 +248,9 @@ export const plainLogger = defineLogger({
|
|
|
295
248
|
pluginTimings: logLevel >= LogLevel.verbose ? pluginTimings : undefined,
|
|
296
249
|
})
|
|
297
250
|
|
|
298
|
-
console.log(
|
|
251
|
+
console.log(SUMMARY_SEPARATOR)
|
|
299
252
|
console.log(summary.join('\n'))
|
|
300
|
-
console.log(
|
|
253
|
+
console.log(SUMMARY_SEPARATOR)
|
|
301
254
|
})
|
|
302
255
|
},
|
|
303
256
|
})
|
package/src/loggers/utils.ts
CHANGED
|
@@ -1,13 +1,77 @@
|
|
|
1
|
+
import { styleText } from 'node:util'
|
|
1
2
|
import type { Logger, LoggerContext, LoggerOptions } from '@kubb/core'
|
|
2
3
|
import { LogLevel } from '@kubb/core'
|
|
4
|
+
import { formatHrtime } from '@kubb/core/utils'
|
|
5
|
+
import { canUseTTY, isGitHubActions } from '../utils/envDetection.ts'
|
|
3
6
|
import { clackLogger } from './clackLogger.ts'
|
|
4
|
-
import { canUseTTY, isGitHubActions } from './envDetection.ts'
|
|
5
7
|
import { fileSystemLogger } from './fileSystemLogger.ts'
|
|
6
8
|
import { githubActionsLogger } from './githubActionsLogger.ts'
|
|
7
9
|
import { plainLogger } from './plainLogger.ts'
|
|
8
10
|
import type { LoggerType } from './types.ts'
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Optionally prefix a message with a [HH:MM:SS] timestamp when logLevel >= verbose.
|
|
14
|
+
* Shared across all logger adapters to avoid duplication.
|
|
15
|
+
*/
|
|
16
|
+
export function formatMessage(message: string, logLevel: number): string {
|
|
17
|
+
if (logLevel >= LogLevel.verbose) {
|
|
18
|
+
const timestamp = new Date().toLocaleTimeString('en-US', {
|
|
19
|
+
hour12: false,
|
|
20
|
+
hour: '2-digit',
|
|
21
|
+
minute: '2-digit',
|
|
22
|
+
second: '2-digit',
|
|
23
|
+
})
|
|
24
|
+
return `${styleText('dim', `[${timestamp}]`)} ${message}`
|
|
25
|
+
}
|
|
26
|
+
return message
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type ProgressState = {
|
|
30
|
+
totalPlugins: number
|
|
31
|
+
completedPlugins: number
|
|
32
|
+
failedPlugins: number
|
|
33
|
+
totalFiles: number
|
|
34
|
+
processedFiles: number
|
|
35
|
+
hrStart: [number, number]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Build the progress summary line shared by clack and GitHub Actions loggers.
|
|
40
|
+
* Returns null when there is nothing to display.
|
|
41
|
+
*/
|
|
42
|
+
export function buildProgressLine(state: ProgressState): string | null {
|
|
43
|
+
const parts: string[] = []
|
|
44
|
+
const duration = formatHrtime(state.hrStart)
|
|
45
|
+
|
|
46
|
+
if (state.totalPlugins > 0) {
|
|
47
|
+
const pluginStr =
|
|
48
|
+
state.failedPlugins > 0
|
|
49
|
+
? `Plugins ${styleText('green', state.completedPlugins.toString())}/${state.totalPlugins} ${styleText('red', `(${state.failedPlugins} failed)`)}`
|
|
50
|
+
: `Plugins ${styleText('green', state.completedPlugins.toString())}/${state.totalPlugins}`
|
|
51
|
+
parts.push(pluginStr)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (state.totalFiles > 0) {
|
|
55
|
+
parts.push(`Files ${styleText('green', state.processedFiles.toString())}/${state.totalFiles}`)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (parts.length === 0) {
|
|
59
|
+
return null
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
parts.push(`${styleText('green', duration)} elapsed`)
|
|
63
|
+
return parts.join(styleText('dim', ' | '))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Join a command and its optional args into a single display string.
|
|
68
|
+
* e.g. ("prettier", ["--write", "."]) → "prettier --write ."
|
|
69
|
+
*/
|
|
70
|
+
export function formatCommandWithArgs(command: string, args?: readonly string[]): string {
|
|
71
|
+
return args?.length ? `${command} ${args.join(' ')}` : command
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function detectLogger(): LoggerType {
|
|
11
75
|
if (isGitHubActions()) {
|
|
12
76
|
return 'github-actions'
|
|
13
77
|
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import net from 'node:net'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import * as process from 'node:process'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
import { styleText } from 'node:util'
|
|
6
|
+
import * as clack from '@clack/prompts'
|
|
7
|
+
import { agentDefaults } from '../constants.ts'
|
|
8
|
+
import { spawnAsync } from '../utils/spawnAsync.ts'
|
|
9
|
+
import { buildTelemetryEvent, sendTelemetry } from '../utils/telemetry.ts'
|
|
10
|
+
|
|
11
|
+
type AgentStartOptions = {
|
|
12
|
+
port: string | undefined
|
|
13
|
+
host: string
|
|
14
|
+
configPath: string
|
|
15
|
+
allowWrite: boolean
|
|
16
|
+
allowAll: boolean
|
|
17
|
+
version: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function isPortAvailable(port: number, host: string): Promise<boolean> {
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
|
+
const server = net.createServer()
|
|
23
|
+
server.once('error', () => resolve(false))
|
|
24
|
+
server.once('listening', () => {
|
|
25
|
+
server.close()
|
|
26
|
+
resolve(true)
|
|
27
|
+
})
|
|
28
|
+
server.listen(port, host)
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function runAgentStart({ port, host, configPath, allowWrite, allowAll, version }: AgentStartOptions): Promise<void> {
|
|
33
|
+
const hrStart = process.hrtime()
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
// Load .env file into process.env using Node.js built-in (v20.12.0+)
|
|
37
|
+
try {
|
|
38
|
+
process.loadEnvFile()
|
|
39
|
+
} catch {
|
|
40
|
+
// .env file may not exist; ignore
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Resolve the @kubb/agent package path
|
|
44
|
+
const agentPkgUrl = import.meta.resolve('@kubb/agent/package.json')
|
|
45
|
+
const agentPkgPath = fileURLToPath(agentPkgUrl)
|
|
46
|
+
const agentDir = path.dirname(agentPkgPath)
|
|
47
|
+
const serverPath = path.join(agentDir, agentDefaults.serverEntryPath)
|
|
48
|
+
|
|
49
|
+
// CLI params take priority over process.env; process.env fills in what the CLI didn't specify;
|
|
50
|
+
// agentDefaults are the last resort. Build env as: defaults ← process.env ← CLI.
|
|
51
|
+
const PORT = port !== undefined ? port : (process.env.PORT ?? agentDefaults.port)
|
|
52
|
+
const HOST = host !== agentDefaults.host ? host : (process.env.HOST ?? agentDefaults.host)
|
|
53
|
+
const KUBB_AGENT_ROOT = process.env.KUBB_AGENT_ROOT ?? process.cwd()
|
|
54
|
+
const KUBB_AGENT_CONFIG = configPath !== agentDefaults.configFile ? configPath : (process.env.KUBB_AGENT_CONFIG ?? agentDefaults.configFile)
|
|
55
|
+
const KUBB_AGENT_ALLOW_WRITE = allowAll || allowWrite ? 'true' : (process.env.KUBB_AGENT_ALLOW_WRITE ?? 'false')
|
|
56
|
+
const KUBB_AGENT_ALLOW_ALL = allowAll ? 'true' : (process.env.KUBB_AGENT_ALLOW_ALL ?? 'false')
|
|
57
|
+
const KUBB_AGENT_TOKEN = process.env.KUBB_AGENT_TOKEN
|
|
58
|
+
const KUBB_AGENT_RETRY_TIMEOUT = process.env.KUBB_AGENT_RETRY_TIMEOUT ?? agentDefaults.retryTimeout
|
|
59
|
+
const KUBB_STUDIO_URL = process.env.KUBB_STUDIO_URL ?? agentDefaults.studioUrl
|
|
60
|
+
|
|
61
|
+
const env = {
|
|
62
|
+
...process.env,
|
|
63
|
+
PORT,
|
|
64
|
+
HOST,
|
|
65
|
+
KUBB_AGENT_ROOT,
|
|
66
|
+
KUBB_AGENT_CONFIG,
|
|
67
|
+
KUBB_AGENT_ALLOW_WRITE,
|
|
68
|
+
KUBB_AGENT_ALLOW_ALL,
|
|
69
|
+
KUBB_AGENT_TOKEN,
|
|
70
|
+
KUBB_AGENT_RETRY_TIMEOUT,
|
|
71
|
+
KUBB_STUDIO_URL,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
clack.log.step(styleText('cyan', 'Starting agent server...'))
|
|
75
|
+
clack.log.info(styleText('dim', `Config: ${KUBB_AGENT_CONFIG}`))
|
|
76
|
+
clack.log.info(styleText('dim', `Host: ${HOST}`))
|
|
77
|
+
clack.log.info(styleText('dim', `Port: ${PORT}`))
|
|
78
|
+
if (!KUBB_AGENT_ALLOW_WRITE && !KUBB_AGENT_ALLOW_ALL) {
|
|
79
|
+
clack.log.warn(styleText('yellow', 'Filesystem writes disabled. Use --allow-write or --allow-all to enable.'))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!(await isPortAvailable(Number(PORT), HOST))) {
|
|
83
|
+
clack.log.error(styleText('red', `Port ${PORT} is already in use. Stop the existing process or choose a different port with --port.`))
|
|
84
|
+
process.exit(1)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Spawns the server as a detached background process so the CLI can exit independently.
|
|
88
|
+
await spawnAsync('node', [serverPath], {
|
|
89
|
+
env,
|
|
90
|
+
cwd: process.cwd(),
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
await sendTelemetry(buildTelemetryEvent({ command: 'agent', kubbVersion: version, hrStart, status: 'success' }))
|
|
94
|
+
} catch (error) {
|
|
95
|
+
await sendTelemetry(buildTelemetryEvent({ command: 'agent', kubbVersion: version, hrStart, status: 'failed' }))
|
|
96
|
+
clack.log.error(styleText('red', 'Failed to start agent server'))
|
|
97
|
+
console.error(error)
|
|
98
|
+
process.exit(1)
|
|
99
|
+
}
|
|
100
|
+
}
|
package/src/runners/generate.ts
CHANGED
|
@@ -2,12 +2,18 @@ import { createHash } from 'node:crypto'
|
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import process from 'node:process'
|
|
4
4
|
import { styleText } from 'node:util'
|
|
5
|
-
import
|
|
5
|
+
import * as clack from '@clack/prompts'
|
|
6
|
+
import { type CLIOptions, type Config, isInputPath, type KubbEvents, LogLevel, PromiseManager, safeBuild, setup } from '@kubb/core'
|
|
6
7
|
import type { AsyncEventEmitter } from '@kubb/core/utils'
|
|
7
|
-
import { detectFormatter, detectLinter, formatters, linters } from '@kubb/core/utils'
|
|
8
|
+
import { AsyncEventEmitter as AsyncEventEmitterClass, detectFormatter, detectLinter, executeIfOnline, formatters, getConfigs, linters } from '@kubb/core/utils'
|
|
8
9
|
import { version } from '../../package.json'
|
|
10
|
+
import { KUBB_NPM_PACKAGE_URL } from '../constants.ts'
|
|
11
|
+
import { setupLogger } from '../loggers/utils.ts'
|
|
12
|
+
import { toError } from '../utils/errors.ts'
|
|
9
13
|
import { executeHooks } from '../utils/executeHooks.ts'
|
|
14
|
+
import { getCosmiConfig } from '../utils/getCosmiConfig.ts'
|
|
10
15
|
import { buildTelemetryEvent, sendTelemetry } from '../utils/telemetry.ts'
|
|
16
|
+
import { startWatcher } from '../utils/watcher.ts'
|
|
11
17
|
|
|
12
18
|
type GenerateProps = {
|
|
13
19
|
input?: string
|
|
@@ -16,7 +22,103 @@ type GenerateProps = {
|
|
|
16
22
|
logLevel: number
|
|
17
23
|
}
|
|
18
24
|
|
|
19
|
-
|
|
25
|
+
type ToolMap = typeof formatters | typeof linters
|
|
26
|
+
|
|
27
|
+
type RunToolPassOptions = {
|
|
28
|
+
toolValue: string
|
|
29
|
+
detect: () => Promise<string | undefined>
|
|
30
|
+
toolMap: ToolMap
|
|
31
|
+
/** Short noun used in "Auto-detected <toolLabel>:" message, e.g. "formatter" or "linter". */
|
|
32
|
+
toolLabel: string
|
|
33
|
+
/** Verb prefix for the success message, e.g. "Formatting" or "Linting". */
|
|
34
|
+
successPrefix: string
|
|
35
|
+
noToolMessage: string
|
|
36
|
+
configName: string | undefined
|
|
37
|
+
outputPath: string
|
|
38
|
+
logLevel: number
|
|
39
|
+
events: AsyncEventEmitter<KubbEvents>
|
|
40
|
+
onStart: () => Promise<void>
|
|
41
|
+
onEnd: () => Promise<void>
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function runToolPass({
|
|
45
|
+
toolValue,
|
|
46
|
+
detect,
|
|
47
|
+
toolMap,
|
|
48
|
+
toolLabel,
|
|
49
|
+
successPrefix,
|
|
50
|
+
noToolMessage,
|
|
51
|
+
configName,
|
|
52
|
+
outputPath,
|
|
53
|
+
logLevel,
|
|
54
|
+
events,
|
|
55
|
+
onStart,
|
|
56
|
+
onEnd,
|
|
57
|
+
}: RunToolPassOptions) {
|
|
58
|
+
await onStart()
|
|
59
|
+
|
|
60
|
+
let resolvedTool = toolValue
|
|
61
|
+
if (resolvedTool === 'auto') {
|
|
62
|
+
const detected = await detect()
|
|
63
|
+
if (!detected) {
|
|
64
|
+
await events.emit('warn', noToolMessage)
|
|
65
|
+
} else {
|
|
66
|
+
resolvedTool = detected
|
|
67
|
+
await events.emit('info', `Auto-detected ${toolLabel}: ${styleText('dim', resolvedTool)}`)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (resolvedTool && resolvedTool !== 'auto' && resolvedTool in toolMap) {
|
|
72
|
+
const toolConfig = toolMap[resolvedTool as keyof ToolMap]
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const hookId = createHash('sha256').update([configName, resolvedTool].filter(Boolean).join('-')).digest('hex')
|
|
76
|
+
|
|
77
|
+
// Wire up the hook:end listener BEFORE emitting hook:start to avoid the race condition
|
|
78
|
+
// where hook:end fires synchronously inside emit('hook:start') before the listener is registered.
|
|
79
|
+
const hookEndPromise = new Promise<void>((resolve, reject) => {
|
|
80
|
+
const handler = ({ id, success, error }: { id?: string; command: string; args?: readonly string[]; success: boolean; error: Error | null }) => {
|
|
81
|
+
if (id !== hookId) return
|
|
82
|
+
events.off('hook:end', handler)
|
|
83
|
+
if (!success) {
|
|
84
|
+
reject(error ?? new Error(`${toolConfig.errorMessage}`))
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
events
|
|
88
|
+
.emit(
|
|
89
|
+
'success',
|
|
90
|
+
[
|
|
91
|
+
`${successPrefix} with ${styleText('dim', resolvedTool)}`,
|
|
92
|
+
logLevel >= LogLevel.info ? `on ${styleText('dim', outputPath)}` : undefined,
|
|
93
|
+
'successfully',
|
|
94
|
+
]
|
|
95
|
+
.filter(Boolean)
|
|
96
|
+
.join(' '),
|
|
97
|
+
)
|
|
98
|
+
.then(resolve)
|
|
99
|
+
.catch(reject)
|
|
100
|
+
}
|
|
101
|
+
events.on('hook:end', handler)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
await events.emit('hook:start', {
|
|
105
|
+
id: hookId,
|
|
106
|
+
command: toolConfig.command,
|
|
107
|
+
args: toolConfig.args(outputPath),
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
await hookEndPromise
|
|
111
|
+
} catch (caughtError) {
|
|
112
|
+
const err = new Error(toolConfig.errorMessage)
|
|
113
|
+
err.cause = caughtError
|
|
114
|
+
await events.emit('error', err)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
await onEnd()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function generate({ input, config: userConfig, events, logLevel }: GenerateProps): Promise<void> {
|
|
20
122
|
const inputPath = input ?? ('path' in userConfig.input ? userConfig.input.path : undefined)
|
|
21
123
|
const hrStart = process.hrtime()
|
|
22
124
|
|
|
@@ -73,9 +175,9 @@ export async function generate({ input, config: userConfig, events, logLevel }:
|
|
|
73
175
|
.map((it) => it.error),
|
|
74
176
|
].filter(Boolean)
|
|
75
177
|
|
|
76
|
-
|
|
77
|
-
events.emit('error', err)
|
|
78
|
-
}
|
|
178
|
+
for (const err of allErrors) {
|
|
179
|
+
await events.emit('error', err)
|
|
180
|
+
}
|
|
79
181
|
|
|
80
182
|
await events.emit('generation:end', config, files, sources)
|
|
81
183
|
|
|
@@ -104,100 +206,40 @@ export async function generate({ input, config: userConfig, events, logLevel }:
|
|
|
104
206
|
await events.emit('success', 'Generation successfully', inputPath)
|
|
105
207
|
await events.emit('generation:end', config, files, sources)
|
|
106
208
|
|
|
107
|
-
|
|
108
|
-
if (config.output.format) {
|
|
109
|
-
await events.emit('format:start')
|
|
110
|
-
|
|
111
|
-
let formatter = config.output.format
|
|
112
|
-
if (formatter === 'auto') {
|
|
113
|
-
const detectedFormatter = await detectFormatter()
|
|
114
|
-
if (!detectedFormatter) {
|
|
115
|
-
await events.emit('warn', 'No formatter found (biome, prettier, or oxfmt). Skipping formatting.')
|
|
116
|
-
} else {
|
|
117
|
-
formatter = detectedFormatter
|
|
118
|
-
await events.emit('info', `Auto-detected formatter: ${styleText('dim', formatter)}`)
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (formatter && formatter !== 'auto' && formatter in formatters) {
|
|
123
|
-
const formatterConfig = formatters[formatter as keyof typeof formatters]
|
|
124
|
-
const outputPath = path.resolve(config.root, config.output.path)
|
|
125
|
-
|
|
126
|
-
try {
|
|
127
|
-
const hookId = createHash('sha256').update([config.name, formatter].filter(Boolean).join('-')).digest('hex')
|
|
128
|
-
await events.emit('hook:start', {
|
|
129
|
-
id: hookId,
|
|
130
|
-
command: formatterConfig.command,
|
|
131
|
-
args: formatterConfig.args(outputPath),
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
await events.onOnce('hook:end', async ({ success, error }) => {
|
|
135
|
-
if (!success) throw error
|
|
136
|
-
|
|
137
|
-
await events.emit(
|
|
138
|
-
'success',
|
|
139
|
-
[`Formatting with ${styleText('dim', formatter)}`, logLevel >= LogLevel.info ? `on ${styleText('dim', outputPath)}` : undefined, 'successfully']
|
|
140
|
-
.filter(Boolean)
|
|
141
|
-
.join(' '),
|
|
142
|
-
)
|
|
143
|
-
})
|
|
144
|
-
} catch (caughtError) {
|
|
145
|
-
const error = new Error(formatterConfig.errorMessage)
|
|
146
|
-
error.cause = caughtError
|
|
147
|
-
await events.emit('error', error)
|
|
148
|
-
}
|
|
149
|
-
}
|
|
209
|
+
const outputPath = path.resolve(config.root, config.output.path)
|
|
150
210
|
|
|
151
|
-
|
|
211
|
+
if (config.output.format) {
|
|
212
|
+
await runToolPass({
|
|
213
|
+
toolValue: config.output.format,
|
|
214
|
+
detect: detectFormatter,
|
|
215
|
+
toolMap: formatters,
|
|
216
|
+
toolLabel: 'formatter',
|
|
217
|
+
successPrefix: 'Formatting',
|
|
218
|
+
noToolMessage: 'No formatter found (biome, prettier, or oxfmt). Skipping formatting.',
|
|
219
|
+
configName: config.name,
|
|
220
|
+
outputPath,
|
|
221
|
+
logLevel,
|
|
222
|
+
events,
|
|
223
|
+
onStart: () => events.emit('format:start'),
|
|
224
|
+
onEnd: () => events.emit('format:end'),
|
|
225
|
+
})
|
|
152
226
|
}
|
|
153
227
|
|
|
154
|
-
// linting
|
|
155
228
|
if (config.output.lint) {
|
|
156
|
-
await
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
// Only proceed with linting if we have a valid linter
|
|
171
|
-
if (linter && linter !== 'auto' && linter in linters) {
|
|
172
|
-
const linterConfig = linters[linter as keyof typeof linters]
|
|
173
|
-
const outputPath = path.resolve(config.root, config.output.path)
|
|
174
|
-
|
|
175
|
-
try {
|
|
176
|
-
const hookId = createHash('sha256').update([config.name, linter].filter(Boolean).join('-')).digest('hex')
|
|
177
|
-
await events.emit('hook:start', {
|
|
178
|
-
id: hookId,
|
|
179
|
-
command: linterConfig.command,
|
|
180
|
-
args: linterConfig.args(outputPath),
|
|
181
|
-
})
|
|
182
|
-
|
|
183
|
-
await events.onOnce('hook:end', async ({ success, error }) => {
|
|
184
|
-
if (!success) throw error
|
|
185
|
-
|
|
186
|
-
await events.emit(
|
|
187
|
-
'success',
|
|
188
|
-
[`Linting with ${styleText('dim', linter)}`, logLevel >= LogLevel.info ? `on ${styleText('dim', outputPath)}` : undefined, 'successfully']
|
|
189
|
-
.filter(Boolean)
|
|
190
|
-
.join(' '),
|
|
191
|
-
)
|
|
192
|
-
})
|
|
193
|
-
} catch (caughtError) {
|
|
194
|
-
const error = new Error(linterConfig.errorMessage)
|
|
195
|
-
error.cause = caughtError
|
|
196
|
-
await events.emit('error', error)
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
await events.emit('lint:end')
|
|
229
|
+
await runToolPass({
|
|
230
|
+
toolValue: config.output.lint,
|
|
231
|
+
detect: detectLinter,
|
|
232
|
+
toolMap: linters,
|
|
233
|
+
toolLabel: 'linter',
|
|
234
|
+
successPrefix: 'Linting',
|
|
235
|
+
noToolMessage: 'No linter found (biome, oxlint, or eslint). Skipping linting.',
|
|
236
|
+
configName: config.name,
|
|
237
|
+
outputPath,
|
|
238
|
+
logLevel,
|
|
239
|
+
events,
|
|
240
|
+
onStart: () => events.emit('lint:start'),
|
|
241
|
+
onEnd: () => events.emit('lint:end'),
|
|
242
|
+
})
|
|
201
243
|
}
|
|
202
244
|
|
|
203
245
|
if (config.hooks) {
|
|
@@ -207,12 +249,11 @@ export async function generate({ input, config: userConfig, events, logLevel }:
|
|
|
207
249
|
await events.emit('hooks:end')
|
|
208
250
|
}
|
|
209
251
|
|
|
210
|
-
|
|
211
|
-
|
|
252
|
+
// Only reached when there are no failures (process.exit(1) is called above otherwise)
|
|
212
253
|
await events.emit('generation:summary', config, {
|
|
213
254
|
failedPlugins,
|
|
214
255
|
filesCreated: files.length,
|
|
215
|
-
status:
|
|
256
|
+
status: 'success',
|
|
216
257
|
hrStart,
|
|
217
258
|
pluginTimings,
|
|
218
259
|
})
|
|
@@ -223,8 +264,75 @@ export async function generate({ input, config: userConfig, events, logLevel }:
|
|
|
223
264
|
plugins: pluginManager.plugins.map((p) => ({ name: p.name, options: p.options as Record<string, unknown> })),
|
|
224
265
|
hrStart,
|
|
225
266
|
filesCreated: files.length,
|
|
226
|
-
status:
|
|
267
|
+
status: 'success',
|
|
227
268
|
})
|
|
228
269
|
|
|
229
270
|
await sendTelemetry(telemetryEvent)
|
|
230
271
|
}
|
|
272
|
+
|
|
273
|
+
type GenerateCommandOptions = {
|
|
274
|
+
input?: string
|
|
275
|
+
configPath?: string
|
|
276
|
+
logLevel: string
|
|
277
|
+
watch: boolean
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export async function runGenerateCommand({ input, configPath, logLevel: logLevelKey, watch }: GenerateCommandOptions): Promise<void> {
|
|
281
|
+
const logLevel = LogLevel[logLevelKey as keyof typeof LogLevel] ?? LogLevel.info
|
|
282
|
+
const events = new AsyncEventEmitterClass<KubbEvents>()
|
|
283
|
+
const promiseManager = new PromiseManager()
|
|
284
|
+
|
|
285
|
+
await setupLogger(events, { logLevel })
|
|
286
|
+
|
|
287
|
+
await executeIfOnline(async () => {
|
|
288
|
+
try {
|
|
289
|
+
const res = await fetch(KUBB_NPM_PACKAGE_URL)
|
|
290
|
+
const data = (await res.json()) as { version: string }
|
|
291
|
+
const latestVersion = data.version
|
|
292
|
+
|
|
293
|
+
if (latestVersion && version < latestVersion) {
|
|
294
|
+
await events.emit('version:new', version, latestVersion)
|
|
295
|
+
}
|
|
296
|
+
} catch {
|
|
297
|
+
// Ignore network errors for version check
|
|
298
|
+
}
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
try {
|
|
302
|
+
const result = await getCosmiConfig('kubb', configPath)
|
|
303
|
+
const configs = await getConfigs(result.config, { input } as CLIOptions)
|
|
304
|
+
|
|
305
|
+
await events.emit('config:start')
|
|
306
|
+
await events.emit('info', 'Config loaded', path.relative(process.cwd(), result.filepath))
|
|
307
|
+
await events.emit('success', 'Config loaded successfully', path.relative(process.cwd(), result.filepath))
|
|
308
|
+
await events.emit('config:end', configs)
|
|
309
|
+
|
|
310
|
+
await events.emit('lifecycle:start', version)
|
|
311
|
+
|
|
312
|
+
const promises = configs.map((config) => {
|
|
313
|
+
return async () => {
|
|
314
|
+
if (isInputPath(config) && watch) {
|
|
315
|
+
await startWatcher([input || config.input.path], async (paths) => {
|
|
316
|
+
// remove to avoid duplicate listeners after each change
|
|
317
|
+
events.removeAll()
|
|
318
|
+
|
|
319
|
+
await generate({ input, config, logLevel, events })
|
|
320
|
+
|
|
321
|
+
clack.log.step(styleText('yellow', `Watching for changes in ${paths.join(' and ')}`))
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
return
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
await generate({ input, config, logLevel, events })
|
|
328
|
+
}
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
await promiseManager.run('seq', promises)
|
|
332
|
+
|
|
333
|
+
await events.emit('lifecycle:end')
|
|
334
|
+
} catch (error) {
|
|
335
|
+
await events.emit('error', toError(error))
|
|
336
|
+
process.exit(1)
|
|
337
|
+
}
|
|
338
|
+
}
|