@kubb/cli 5.0.0-beta.36 → 5.0.0-beta.38
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/README.md +14 -11
- package/dist/{agent-DajReUxm.cjs → agent-Bl8JwjMa.cjs} +4 -4
- package/dist/{agent-DajReUxm.cjs.map → agent-Bl8JwjMa.cjs.map} +1 -1
- package/dist/{agent-cAalLgeU.js → agent-CfZ_Uqde.js} +4 -4
- package/dist/{agent-cAalLgeU.js.map → agent-CfZ_Uqde.js.map} +1 -1
- package/dist/{constants-FhPsMOdo.cjs → constants-CAKUpLcQ.cjs} +3 -12
- package/dist/{constants-FhPsMOdo.cjs.map → constants-CAKUpLcQ.cjs.map} +1 -1
- package/dist/{constants-Co6NWt3U.js → constants-CYxk4aNm.js} +4 -7
- package/dist/{constants-Co6NWt3U.js.map → constants-CYxk4aNm.js.map} +1 -1
- package/dist/{generate-C6oskVzT.cjs → generate-Bgds6Zx3.cjs} +19 -14
- package/dist/generate-Bgds6Zx3.cjs.map +1 -0
- package/dist/{generate-DmYQJcBv.js → generate-CfxFqNeb.js} +19 -14
- package/dist/generate-CfxFqNeb.js.map +1 -0
- package/dist/index.cjs +8 -8
- package/dist/index.js +8 -8
- package/dist/{init-Dbb4U-Xs.cjs → init-C5wnuzeK.cjs} +2 -2
- package/dist/{init-Dbb4U-Xs.cjs.map → init-C5wnuzeK.cjs.map} +1 -1
- package/dist/{init-iOg_X-uh.js → init-TIec3Dym.js} +2 -2
- package/dist/{init-iOg_X-uh.js.map → init-TIec3Dym.js.map} +1 -1
- package/dist/{mcp-DxrSTT8i.cjs → mcp-Cr753GW1.cjs} +3 -3
- package/dist/{mcp-DxrSTT8i.cjs.map → mcp-Cr753GW1.cjs.map} +1 -1
- package/dist/{mcp-Ci2OkdBj.js → mcp-Damue5Mq.js} +3 -3
- package/dist/{mcp-Ci2OkdBj.js.map → mcp-Damue5Mq.js.map} +1 -1
- package/dist/package-Cnt1K03J.js +6 -0
- package/dist/package-Cnt1K03J.js.map +1 -0
- package/dist/{package-DbsOo2rT.cjs → package-guApEHiW.cjs} +2 -2
- package/dist/package-guApEHiW.cjs.map +1 -0
- package/dist/{run-v-75bcU1.js → run-BFEK9md9.js} +2 -2
- package/dist/{run-v-75bcU1.js.map → run-BFEK9md9.js.map} +1 -1
- package/dist/{run-GvXhj9XF.cjs → run-BFZtWpcW.cjs} +491 -314
- package/dist/run-BFZtWpcW.cjs.map +1 -0
- package/dist/{run-CCgNPz0F.cjs → run-BFv6avA_.cjs} +3 -3
- package/dist/{run-CCgNPz0F.cjs.map → run-BFv6avA_.cjs.map} +1 -1
- package/dist/{run-DpDKN_rb.cjs → run-BQZyg7If.cjs} +2 -2
- package/dist/{run-DpDKN_rb.cjs.map → run-BQZyg7If.cjs.map} +1 -1
- package/dist/{run-CPimpDgO.js → run-BvXxelGR.js} +2 -2
- package/dist/{run-CPimpDgO.js.map → run-BvXxelGR.js.map} +1 -1
- package/dist/{run-Lnupy7qb.cjs → run-Bz9IFMWg.cjs} +2 -2
- package/dist/{run-Lnupy7qb.cjs.map → run-Bz9IFMWg.cjs.map} +1 -1
- package/dist/{run-B9ZkldVt.js → run-C752fag9.js} +557 -380
- package/dist/run-C752fag9.js.map +1 -0
- package/dist/run-C_NMctua.cjs.map +1 -1
- package/dist/run-D8dCWepS.js.map +1 -1
- package/dist/{run-BRrNHp24.js → run-DJxYClJV.js} +3 -3
- package/dist/{run-BRrNHp24.js.map → run-DJxYClJV.js.map} +1 -1
- package/dist/{telemetry-DRhd3joO.cjs → telemetry-B80oJfxR.cjs} +2 -2
- package/dist/telemetry-B80oJfxR.cjs.map +1 -0
- package/dist/{telemetry-ne1IOrz1.js → telemetry-ueaMzs_c.js} +2 -2
- package/dist/telemetry-ueaMzs_c.js.map +1 -0
- package/dist/{validate-Bh7MgISX.js → validate-CYTKdezO.js} +3 -3
- package/dist/{validate-Bh7MgISX.js.map → validate-CYTKdezO.js.map} +1 -1
- package/dist/{validate-DVkJx4q8.cjs → validate-DMzjP-hd.cjs} +3 -3
- package/dist/{validate-DVkJx4q8.cjs.map → validate-DMzjP-hd.cjs.map} +1 -1
- package/package.json +6 -6
- package/src/commands/generate.ts +16 -10
- package/src/constants.ts +1 -1
- package/src/loggers/clackLogger.ts +68 -71
- package/src/loggers/diagnostics.ts +77 -0
- package/src/loggers/githubActionsLogger.ts +38 -31
- package/src/loggers/plainLogger.ts +10 -26
- package/src/loggers/types.ts +1 -1
- package/src/loggers/utils.ts +47 -94
- package/src/reporters/cliReporter.ts +89 -0
- package/src/reporters/fileReporter.ts +103 -0
- package/src/reporters/jsonReporter.ts +20 -0
- package/src/reporters/report.ts +84 -0
- package/src/runners/agent/run.ts +2 -2
- package/src/runners/generate/run.ts +130 -44
- package/src/runners/generate/utils.ts +8 -11
- package/src/runners/init/run.ts +1 -1
- package/src/telemetry.ts +2 -2
- package/dist/generate-C6oskVzT.cjs.map +0 -1
- package/dist/generate-DmYQJcBv.js.map +0 -1
- package/dist/package-DbsOo2rT.cjs.map +0 -1
- package/dist/package-_R15a7lY.js +0 -6
- package/dist/package-_R15a7lY.js.map +0 -1
- package/dist/run-B9ZkldVt.js.map +0 -1
- package/dist/run-GvXhj9XF.cjs.map +0 -1
- package/dist/telemetry-DRhd3joO.cjs.map +0 -1
- package/dist/telemetry-ne1IOrz1.js.map +0 -1
- package/src/loggers/fileSystemLogger.ts +0 -151
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { styleText } from 'node:util'
|
|
2
|
-
import {
|
|
3
|
-
import { type Config, defineLogger, type KubbHooks, logLevel as logLevelMap } from '@kubb/core'
|
|
2
|
+
import { formatMs, formatMsWithColor, toCause } from '@internals/utils'
|
|
3
|
+
import { type Config, defineLogger, diagnosticCode, Diagnostics, isProblemDiagnostic, type KubbHooks, logLevel as logLevelMap } from '@kubb/core'
|
|
4
4
|
import {
|
|
5
5
|
buildProgressLine,
|
|
6
6
|
createHookTimer,
|
|
@@ -127,14 +127,12 @@ export const githubActionsLogger = defineLogger({
|
|
|
127
127
|
// (e.g., when getConfigs or kubb.setup throws) doesn't leak an open section.
|
|
128
128
|
closeAllGroups()
|
|
129
129
|
|
|
130
|
-
|
|
131
|
-
return
|
|
132
|
-
}
|
|
130
|
+
// Errors are always surfaced, even at silent, so failures stay visible.
|
|
133
131
|
const message = error.message || String(error)
|
|
134
132
|
console.error(`::error::${message}`)
|
|
135
133
|
|
|
136
|
-
// Show stack trace in
|
|
137
|
-
if (logLevel >= logLevelMap.
|
|
134
|
+
// Show stack trace in verbose mode (first 3 frames)
|
|
135
|
+
if (logLevel >= logLevelMap.verbose && error.stack) {
|
|
138
136
|
const frames = error.stack.split('\n').slice(1, 4)
|
|
139
137
|
for (const frame of frames) {
|
|
140
138
|
console.log(getMessage(styleText('dim', frame.trim())))
|
|
@@ -151,15 +149,40 @@ export const githubActionsLogger = defineLogger({
|
|
|
151
149
|
}
|
|
152
150
|
})
|
|
153
151
|
|
|
152
|
+
context.on('kubb:diagnostic', ({ diagnostic }) => {
|
|
153
|
+
closeAllGroups()
|
|
154
|
+
|
|
155
|
+
// Silent still surfaces errors so failures stay visible. It drops warnings and info.
|
|
156
|
+
if (logLevel <= logLevelMap.silent && diagnostic.severity !== 'error') {
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (!isProblemDiagnostic(diagnostic)) {
|
|
161
|
+
console.log(`::notice::${diagnostic.message}`)
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const parts = [`${diagnostic.code} ${diagnostic.message}`]
|
|
166
|
+
if (diagnostic.location && 'pointer' in diagnostic.location) {
|
|
167
|
+
parts.push(`(at ${diagnostic.location.pointer})`)
|
|
168
|
+
}
|
|
169
|
+
if (diagnostic.plugin) {
|
|
170
|
+
parts.push(`[plugin: ${diagnostic.plugin}]`)
|
|
171
|
+
}
|
|
172
|
+
if (diagnostic.help) {
|
|
173
|
+
parts.push(`help: ${diagnostic.help}`)
|
|
174
|
+
}
|
|
175
|
+
if (diagnostic.code !== diagnosticCode.unknown) {
|
|
176
|
+
parts.push(`docs: ${Diagnostics.docsUrl(diagnostic.code)}`)
|
|
177
|
+
}
|
|
178
|
+
console.error(`::error::${parts.join(' ')}`)
|
|
179
|
+
})
|
|
180
|
+
|
|
154
181
|
context.on('kubb:lifecycle:start', ({ version }) => {
|
|
155
182
|
console.log(styleText('yellow', `Kubb ${version} 🧩`))
|
|
156
183
|
reset()
|
|
157
184
|
})
|
|
158
185
|
|
|
159
|
-
context.on('kubb:version:new', ({ currentVersion, latestVersion }) => {
|
|
160
|
-
console.log(`::notice::Update available for Kubb: v${currentVersion} → v${latestVersion}. Run \`npm install -g @kubb/cli\` to update.`)
|
|
161
|
-
})
|
|
162
|
-
|
|
163
186
|
context.on('kubb:config:start', () => {
|
|
164
187
|
if (logLevel <= logLevelMap.silent) {
|
|
165
188
|
return
|
|
@@ -289,6 +312,10 @@ export const githubActionsLogger = defineLogger({
|
|
|
289
312
|
)
|
|
290
313
|
|
|
291
314
|
console.log(text)
|
|
315
|
+
|
|
316
|
+
if (state.currentConfigs.length > 1) {
|
|
317
|
+
closeGroup(config.name ? `Generation for ${styleText('bold', config.name)}` : 'Generation')
|
|
318
|
+
}
|
|
292
319
|
})
|
|
293
320
|
|
|
294
321
|
onGroupStart('kubb:format:start', 'Format started', 'Formatting')
|
|
@@ -338,26 +365,6 @@ export const githubActionsLogger = defineLogger({
|
|
|
338
365
|
}
|
|
339
366
|
})
|
|
340
367
|
|
|
341
|
-
context.on('kubb:generation:summary', ({ config, status, hrStart, failedPlugins }) => {
|
|
342
|
-
const pluginsCount = config.plugins?.length ?? 0
|
|
343
|
-
const successCount = pluginsCount - failedPlugins.size
|
|
344
|
-
const duration = formatHrtime(hrStart)
|
|
345
|
-
|
|
346
|
-
if (state.currentConfigs.length > 1) {
|
|
347
|
-
console.log(' ')
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
console.log(
|
|
351
|
-
status === 'success'
|
|
352
|
-
? `Kubb Summary: ${styleText('blue', '✓')} ${`${successCount} successful`}, ${pluginsCount} total, ${styleText('green', duration)}`
|
|
353
|
-
: `Kubb Summary: ${styleText('blue', '✓')} ${`${successCount} successful`}, ✗ ${`${failedPlugins.size} failed`}, ${pluginsCount} total, ${styleText('green', duration)}`,
|
|
354
|
-
)
|
|
355
|
-
|
|
356
|
-
if (state.currentConfigs.length > 1) {
|
|
357
|
-
closeGroup(config.name ? `Generation for ${styleText('bold', config.name)}` : 'Generation')
|
|
358
|
-
}
|
|
359
|
-
})
|
|
360
|
-
|
|
361
368
|
context.on('kubb:lifecycle:end', () => {
|
|
362
369
|
reset()
|
|
363
370
|
})
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { relative } from 'node:path'
|
|
2
2
|
import { formatMs, toCause } from '@internals/utils'
|
|
3
3
|
import { defineLogger, type KubbHooks, logLevel as logLevelMap } from '@kubb/core'
|
|
4
|
-
import {
|
|
5
|
-
import { getSummary } from './utils.ts'
|
|
4
|
+
import { formatDiagnostic } from './diagnostics.ts'
|
|
6
5
|
import { createHookTimer, formatCommandWithArgs, formatMessage } from './utils.ts'
|
|
7
6
|
|
|
8
7
|
/**
|
|
@@ -65,8 +64,8 @@ export const plainLogger = defineLogger({
|
|
|
65
64
|
|
|
66
65
|
console.log(text)
|
|
67
66
|
|
|
68
|
-
// Show stack trace in
|
|
69
|
-
if (logLevel >= logLevelMap.
|
|
67
|
+
// Show stack trace in verbose mode (first 3 frames)
|
|
68
|
+
if (logLevel >= logLevelMap.verbose && error.stack) {
|
|
70
69
|
const frames = error.stack.split('\n').slice(1, 4)
|
|
71
70
|
for (const frame of frames) {
|
|
72
71
|
console.log(getMessage(frame.trim()))
|
|
@@ -83,16 +82,16 @@ export const plainLogger = defineLogger({
|
|
|
83
82
|
}
|
|
84
83
|
})
|
|
85
84
|
|
|
86
|
-
context.on('kubb:
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
context.on('kubb:version:new', ({ currentVersion, latestVersion }) => {
|
|
91
|
-
if (logLevel <= logLevelMap.silent) {
|
|
85
|
+
context.on('kubb:diagnostic', ({ diagnostic }) => {
|
|
86
|
+
// Silent still surfaces errors so failures stay visible. It drops warnings and info.
|
|
87
|
+
if (logLevel <= logLevelMap.silent && diagnostic.severity !== 'error') {
|
|
92
88
|
return
|
|
93
89
|
}
|
|
90
|
+
console.log(getMessage(formatDiagnostic(diagnostic).join('\n')))
|
|
91
|
+
})
|
|
94
92
|
|
|
95
|
-
|
|
93
|
+
context.on('kubb:lifecycle:start', ({ version }) => {
|
|
94
|
+
console.log(`Kubb CLI v${version}`)
|
|
96
95
|
})
|
|
97
96
|
|
|
98
97
|
onStep('kubb:config:start', 'Configuration started')
|
|
@@ -198,21 +197,6 @@ export const plainLogger = defineLogger({
|
|
|
198
197
|
}
|
|
199
198
|
})
|
|
200
199
|
|
|
201
|
-
context.on('kubb:generation:summary', ({ config, pluginTimings, status, hrStart, failedPlugins, filesCreated }) => {
|
|
202
|
-
const summary = getSummary({
|
|
203
|
-
failedPlugins,
|
|
204
|
-
filesCreated,
|
|
205
|
-
config,
|
|
206
|
-
status,
|
|
207
|
-
hrStart,
|
|
208
|
-
pluginTimings: logLevel >= logLevelMap.verbose ? pluginTimings : undefined,
|
|
209
|
-
})
|
|
210
|
-
|
|
211
|
-
console.log(SUMMARY_SEPARATOR)
|
|
212
|
-
console.log(summary.join('\n'))
|
|
213
|
-
console.log(SUMMARY_SEPARATOR)
|
|
214
|
-
})
|
|
215
|
-
|
|
216
200
|
return (_commandWithArgs: string, _hookId: string) => ({
|
|
217
201
|
onStdout: logLevel > logLevelMap.silent ? (s: string) => console.log(s) : undefined,
|
|
218
202
|
onStderr: logLevel > logLevelMap.silent ? (s: string) => console.error(s) : undefined,
|
package/src/loggers/types.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Logger adapter selected by `
|
|
2
|
+
* Logger adapter selected by `setupReporters` based on the runtime environment.
|
|
3
3
|
* - `'clack'`: TTY-aware output with spinners and progress bars.
|
|
4
4
|
* - `'github-actions'`: CI output using `::group::` annotations.
|
|
5
5
|
* - `'plain'`: Plain `console.log` output for non-TTY environments.
|
package/src/loggers/utils.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import path from 'node:path'
|
|
2
1
|
import process from 'node:process'
|
|
3
2
|
import { styleText } from 'node:util'
|
|
4
|
-
import { canUseTTY, formatHrtime, getElapsedMs, isGitHubActions
|
|
5
|
-
import type {
|
|
3
|
+
import { canUseTTY, formatHrtime, getElapsedMs, isGitHubActions } from '@internals/utils'
|
|
4
|
+
import type { Logger, LoggerContext, LoggerOptions, Reporter, ReporterContext, ReporterName } from '@kubb/core'
|
|
6
5
|
import { logLevel as logLevelMap } from '@kubb/core'
|
|
7
|
-
import {
|
|
6
|
+
import { cliReporter } from '../reporters/cliReporter.ts'
|
|
7
|
+
import { fileReporter } from '../reporters/fileReporter.ts'
|
|
8
|
+
import { jsonReporter } from '../reporters/jsonReporter.ts'
|
|
8
9
|
import { clackLogger } from './clackLogger.ts'
|
|
9
|
-
import { fileSystemLogger } from './fileSystemLogger.ts'
|
|
10
10
|
import { githubActionsLogger } from './githubActionsLogger.ts'
|
|
11
11
|
import { plainLogger } from './plainLogger.ts'
|
|
12
12
|
import type { LoggerType } from './types.ts'
|
|
@@ -226,105 +226,58 @@ const logMapper: Record<LoggerType, CLILogger> = {
|
|
|
226
226
|
'github-actions': githubActionsLogger,
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const makeSink = await logger.install(context, { logLevel })
|
|
229
|
+
/**
|
|
230
|
+
* Bridges a {@link Reporter} onto the run's event emitter: calls `report` with each config's
|
|
231
|
+
* {@link GenerationResult} on `kubb:generation:end`. The reporter never touches the emitter.
|
|
232
|
+
*/
|
|
233
|
+
export function installReporter(context: LoggerContext, reporter: Reporter, ctx: ReporterContext): void {
|
|
234
|
+
context.on('kubb:generation:end', async ({ config, diagnostics = [], filesCreated = 0, status = 'success', hrStart = process.hrtime() }) => {
|
|
235
|
+
await reporter.report({ config, diagnostics, filesCreated, status, hrStart }, ctx)
|
|
236
|
+
})
|
|
239
237
|
|
|
240
|
-
if (
|
|
241
|
-
|
|
238
|
+
if (reporter.flush) {
|
|
239
|
+
context.on('kubb:lifecycle:end', () => reporter.flush?.(ctx))
|
|
242
240
|
}
|
|
243
|
-
|
|
244
|
-
return typeof makeSink === 'function' ? makeSink : null
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
type SummaryProps = {
|
|
248
|
-
/**
|
|
249
|
-
* Set of plugins that failed during this generation run, each with its error.
|
|
250
|
-
*/
|
|
251
|
-
failedPlugins: Set<{ plugin: Plugin; error: Error }>
|
|
252
|
-
/**
|
|
253
|
-
* Overall generation status used to choose success or failure formatting.
|
|
254
|
-
*/
|
|
255
|
-
status: 'success' | 'failed'
|
|
256
|
-
/**
|
|
257
|
-
* `process.hrtime()` snapshot taken at the start of generation, used to compute elapsed time.
|
|
258
|
-
*/
|
|
259
|
-
hrStart: [number, number]
|
|
260
|
-
/**
|
|
261
|
-
* Total number of files written during this generation run.
|
|
262
|
-
*/
|
|
263
|
-
filesCreated: number
|
|
264
|
-
/**
|
|
265
|
-
* Resolved Kubb config for this generation entry, used to read plugin count and output path.
|
|
266
|
-
*/
|
|
267
|
-
config: Config
|
|
268
|
-
/**
|
|
269
|
-
* Per-plugin timing map (plugin name → duration in ms). When provided, a timing bar chart is appended.
|
|
270
|
-
*/
|
|
271
|
-
pluginTimings?: Map<string, number>
|
|
272
241
|
}
|
|
273
242
|
|
|
274
243
|
/**
|
|
275
|
-
*
|
|
276
|
-
*
|
|
244
|
+
* Installs the live logger (the TUI view) and the selected reporters (the output), returning the
|
|
245
|
+
* terminal logger's hook sink when one was installed. Loggers and reporters are independent: the
|
|
246
|
+
* `cli` selection activates the env logger plus the {@link cliReporter} summary.
|
|
247
|
+
*
|
|
248
|
+
* The `json` reporter owns stdout, so the terminal logger and `cli` summary are suppressed whenever
|
|
249
|
+
* `json` is selected, even if `cli` is also listed.
|
|
277
250
|
*/
|
|
278
|
-
export function
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
failed: 'Failed:',
|
|
298
|
-
generated: 'Generated:',
|
|
299
|
-
pluginTimings: 'Plugin Timings:',
|
|
300
|
-
output: 'Output:',
|
|
251
|
+
export async function setupReporters(
|
|
252
|
+
context: LoggerContext,
|
|
253
|
+
{ logLevel, reporters }: LoggerOptions & { reporters: ReadonlyArray<ReporterName> },
|
|
254
|
+
): Promise<HookSinkFactory | null> {
|
|
255
|
+
const unique = new Set<ReporterName>(reporters.length ? reporters : ['cli'])
|
|
256
|
+
const hasJson = unique.has('json')
|
|
257
|
+
const ctx: ReporterContext = { logLevel }
|
|
258
|
+
|
|
259
|
+
let makeSink: HookSinkFactory | null = null
|
|
260
|
+
|
|
261
|
+
if (unique.has('cli') && !hasJson) {
|
|
262
|
+
const type = detectLogger()
|
|
263
|
+
const logger = logMapper[type]
|
|
264
|
+
if (!logger) {
|
|
265
|
+
throw new Error(`Unknown adapter type: ${type}`)
|
|
266
|
+
}
|
|
267
|
+
const sink = await logger.install(context, { logLevel })
|
|
268
|
+
makeSink = typeof sink === 'function' ? sink : null
|
|
269
|
+
installReporter(context, cliReporter, ctx)
|
|
301
270
|
}
|
|
302
|
-
const maxLength = Math.max(0, ...[...Object.values(labels), ...(pluginTimings ? Array.from(pluginTimings.keys()) : [])].map((s) => s.length))
|
|
303
|
-
|
|
304
|
-
const summaryLines: Array<string> = []
|
|
305
|
-
summaryLines.push(`${labels.plugins.padEnd(maxLength + 2)} ${meta.plugins}`)
|
|
306
271
|
|
|
307
|
-
if (
|
|
308
|
-
|
|
272
|
+
if (hasJson) {
|
|
273
|
+
// json aggregates across configs: report buffers each result and flush writes one array on
|
|
274
|
+
// lifecycle end, rather than printing per config (which would concatenate documents and break `jq .`).
|
|
275
|
+
installReporter(context, jsonReporter, ctx)
|
|
309
276
|
}
|
|
310
277
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
if (pluginTimings && pluginTimings.size > 0) {
|
|
314
|
-
const sortedTimings = Array.from(pluginTimings.entries()).sort((a, b) => b[1] - a[1])
|
|
315
|
-
|
|
316
|
-
summaryLines.push(`${labels.pluginTimings}`)
|
|
317
|
-
|
|
318
|
-
sortedTimings.forEach(([name, time]) => {
|
|
319
|
-
const timeStr = time >= 1000 ? `${(time / 1000).toFixed(2)}s` : `${Math.round(time)}ms`
|
|
320
|
-
const barLength = Math.min(Math.ceil(time / SUMMARY_TIME_SCALE_DIVISOR), SUMMARY_MAX_BAR_LENGTH)
|
|
321
|
-
const bar = styleText('dim', '█'.repeat(barLength))
|
|
322
|
-
|
|
323
|
-
summaryLines.push(`${styleText('dim', '•')} ${name.padEnd(maxLength + 1)}${bar} ${timeStr}`)
|
|
324
|
-
})
|
|
278
|
+
if (unique.has('file')) {
|
|
279
|
+
installReporter(context, fileReporter, ctx)
|
|
325
280
|
}
|
|
326
281
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
return summaryLines
|
|
282
|
+
return makeSink
|
|
330
283
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { styleText } from 'node:util'
|
|
2
|
+
import { formatMs, randomCliColor } from '@internals/utils'
|
|
3
|
+
import { createReporter, logLevel as logLevelMap } from '@kubb/core'
|
|
4
|
+
import { SUMMARY_MAX_BAR_LENGTH, SUMMARY_TIME_SCALE_DIVISOR } from '../constants.ts'
|
|
5
|
+
import { buildReport, type Report } from './report.ts'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Builds the vitest/jest-style summary for one {@link Report}: right-aligned dim labels with
|
|
9
|
+
* `N passed (total)` counts, and a per-plugin `Timings` section when `showTimings`.
|
|
10
|
+
*/
|
|
11
|
+
function buildSummaryLines(report: Report, { showTimings }: { showTimings: boolean }): Array<string> {
|
|
12
|
+
const { status, plugins, counts, filesCreated, durationMs, output, timings } = report
|
|
13
|
+
|
|
14
|
+
const rows: Array<[label: string, value: string]> = []
|
|
15
|
+
|
|
16
|
+
rows.push([
|
|
17
|
+
'Plugins',
|
|
18
|
+
status === 'success'
|
|
19
|
+
? `${styleText('green', `${plugins.passed} passed`)} (${plugins.total})`
|
|
20
|
+
: `${styleText('green', `${plugins.passed} passed`)} | ${styleText('red', `${plugins.failed.length} failed`)} (${plugins.total})`,
|
|
21
|
+
])
|
|
22
|
+
|
|
23
|
+
if (status === 'failed' && plugins.failed.length > 0) {
|
|
24
|
+
rows.push(['Failed', plugins.failed.map((name) => randomCliColor(name)).join(', ')])
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (counts.errors > 0 || counts.warnings > 0) {
|
|
28
|
+
const issues = [
|
|
29
|
+
counts.errors > 0 ? styleText('red', `${counts.errors} ${counts.errors === 1 ? 'error' : 'errors'}`) : undefined,
|
|
30
|
+
counts.warnings > 0 ? styleText('yellow', `${counts.warnings} ${counts.warnings === 1 ? 'warning' : 'warnings'}`) : undefined,
|
|
31
|
+
]
|
|
32
|
+
.filter(Boolean)
|
|
33
|
+
.join(' | ')
|
|
34
|
+
rows.push(['Issues', issues])
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
rows.push(['Files', `${styleText('green', String(filesCreated))} generated`])
|
|
38
|
+
rows.push(['Duration', styleText('green', formatMs(durationMs))])
|
|
39
|
+
rows.push(['Output', output])
|
|
40
|
+
|
|
41
|
+
const labelWidth = Math.max(...rows.map(([label]) => label.length), timings.length > 0 ? 'Timings'.length : 0)
|
|
42
|
+
const lines = rows.map(([label, value]) => `${styleText('dim', label.padStart(labelWidth))} ${value}`)
|
|
43
|
+
|
|
44
|
+
if (showTimings && timings.length > 0) {
|
|
45
|
+
const nameWidth = Math.max(0, ...timings.map((timing) => timing.plugin.length))
|
|
46
|
+
const indent = ' '.repeat(labelWidth + 2)
|
|
47
|
+
|
|
48
|
+
lines.push(styleText('dim', 'Timings'.padStart(labelWidth)))
|
|
49
|
+
for (const timing of timings) {
|
|
50
|
+
const timeStr = formatMs(timing.durationMs)
|
|
51
|
+
const barLength = Math.min(Math.ceil(timing.durationMs / SUMMARY_TIME_SCALE_DIVISOR), SUMMARY_MAX_BAR_LENGTH)
|
|
52
|
+
const bar = styleText('dim', '█'.repeat(barLength))
|
|
53
|
+
lines.push(`${indent}${styleText('dim', '•')} ${timing.plugin.padEnd(nameWidth)} ${bar} ${timeStr}`)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return lines
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Renders the summary as plain `console.log` lines so it works in every CLI (no clack/TTY
|
|
62
|
+
* dependency): a blank line, the config name colored by status, then the summary rows.
|
|
63
|
+
*/
|
|
64
|
+
function renderSummary(lines: ReadonlyArray<string>, { title, status }: { title: string; status: 'success' | 'failed' }): void {
|
|
65
|
+
console.log('')
|
|
66
|
+
if (title) {
|
|
67
|
+
console.log(styleText(status === 'failed' ? 'red' : 'green', title))
|
|
68
|
+
}
|
|
69
|
+
for (const line of lines) {
|
|
70
|
+
console.log(line)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The default `cli` reporter. Renders the {@link Report} for each config as it finishes, independent
|
|
76
|
+
* of the live logger view. Suppressed at `silent`; the `verbose` level adds the per-plugin timings.
|
|
77
|
+
*/
|
|
78
|
+
export const cliReporter = createReporter({
|
|
79
|
+
name: 'cli',
|
|
80
|
+
report(result, { logLevel }) {
|
|
81
|
+
if (logLevel <= logLevelMap.silent) {
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const report = buildReport(result)
|
|
86
|
+
const lines = buildSummaryLines(report, { showTimings: logLevel >= logLevelMap.verbose })
|
|
87
|
+
renderSummary(lines, { title: report.name, status: report.status })
|
|
88
|
+
},
|
|
89
|
+
})
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { relative, resolve } from 'node:path'
|
|
2
|
+
import process from 'node:process'
|
|
3
|
+
import { stripVTControlCharacters } from 'node:util'
|
|
4
|
+
import { formatMs, write } from '@internals/utils'
|
|
5
|
+
import { createReporter, type Diagnostic, isProblemDiagnostic } from '@kubb/core'
|
|
6
|
+
import { formatDiagnostic } from '../loggers/diagnostics.ts'
|
|
7
|
+
import { buildReport, type Report } from './report.ts'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Builds the `## Summary` section: the same counts the cli and json reporters expose, as a list of
|
|
11
|
+
* `label value` rows with the labels padded to a common width.
|
|
12
|
+
*/
|
|
13
|
+
function buildSummarySection(report: Report): Array<string> {
|
|
14
|
+
const { status, plugins, counts, filesCreated, durationMs, output } = report
|
|
15
|
+
|
|
16
|
+
const rows: Array<[label: string, value: string]> = [
|
|
17
|
+
['Status', status],
|
|
18
|
+
[
|
|
19
|
+
'Plugins',
|
|
20
|
+
status === 'success' ? `${plugins.passed} passed (${plugins.total})` : `${plugins.passed} passed | ${plugins.failed.length} failed (${plugins.total})`,
|
|
21
|
+
],
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
if (plugins.failed.length > 0) {
|
|
25
|
+
rows.push(['Failed', plugins.failed.join(', ')])
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
rows.push(['Issues', `${counts.errors} errors | ${counts.warnings} warnings | ${counts.infos} infos`])
|
|
29
|
+
rows.push(['Files', `${filesCreated} generated`])
|
|
30
|
+
rows.push(['Duration', formatMs(durationMs)])
|
|
31
|
+
rows.push(['Output', output])
|
|
32
|
+
|
|
33
|
+
const labelWidth = Math.max(...rows.map(([label]) => label.length))
|
|
34
|
+
const lines = rows.map(([label, value]) => ` ${label.padEnd(labelWidth)} ${value}`)
|
|
35
|
+
|
|
36
|
+
return ['## Summary', '', ...lines]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Builds the `## Problems` section: each problem rendered in the miette block format, blocks
|
|
41
|
+
* separated by a blank line. Returns an empty array when there are no problems, so the caller
|
|
42
|
+
* can drop the heading.
|
|
43
|
+
*/
|
|
44
|
+
function buildProblemSection(diagnostics: ReadonlyArray<Diagnostic>): Array<string> {
|
|
45
|
+
const problems = diagnostics.filter(isProblemDiagnostic)
|
|
46
|
+
if (problems.length === 0) {
|
|
47
|
+
return []
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const blocks = problems.map((diagnostic) => formatDiagnostic(diagnostic).join('\n'))
|
|
51
|
+
return ['## Problems', '', blocks.join('\n\n')]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Builds the `## Timings` section from a {@link Report}: one `plugin duration` row per record,
|
|
56
|
+
* slowest first with the plugin names left-aligned and the durations right-aligned. Returns an
|
|
57
|
+
* empty array when there are no timings.
|
|
58
|
+
*/
|
|
59
|
+
function buildTimingSection(report: Report): Array<string> {
|
|
60
|
+
const { timings } = report
|
|
61
|
+
if (timings.length === 0) {
|
|
62
|
+
return []
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const nameWidth = Math.max(...timings.map((timing) => timing.plugin.length))
|
|
66
|
+
const durations = timings.map((timing) => formatMs(timing.durationMs))
|
|
67
|
+
const durationWidth = Math.max(...durations.map((duration) => duration.length))
|
|
68
|
+
const rows = timings.map((timing, index) => ` ${timing.plugin.padEnd(nameWidth)} ${durations[index]!.padStart(durationWidth)}`)
|
|
69
|
+
|
|
70
|
+
return ['## Timings', '', ...rows]
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The `file` reporter. Writes a config's {@link Report} to `.kubb/kubb-<name>-<timestamp>.log` as a
|
|
75
|
+
* plain-text document: a `# <name> — <timestamp>` header, a `## Summary` with the same counts the
|
|
76
|
+
* cli and json reporters expose, a `## Problems` section in the miette block format, and a
|
|
77
|
+
* `## Timings` section. Selected with `--reporter file` (or `reporters: ['file']`), replacing the
|
|
78
|
+
* old `--debug` flag.
|
|
79
|
+
*
|
|
80
|
+
* @note Unlike the streaming logger it replaced, it captures the collected diagnostics once a
|
|
81
|
+
* config finishes, not the live `kubb:info`/`kubb:plugin` event stream. Color is stripped so the
|
|
82
|
+
* file stays plain text even when the run is attached to a TTY.
|
|
83
|
+
*/
|
|
84
|
+
export const fileReporter = createReporter({
|
|
85
|
+
name: 'file',
|
|
86
|
+
async report(result) {
|
|
87
|
+
const { diagnostics, config } = result
|
|
88
|
+
if (diagnostics.length === 0) {
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const report = buildReport(result)
|
|
93
|
+
const header = config.name ? `# ${config.name} — ${new Date().toISOString()}` : `# ${new Date().toISOString()}`
|
|
94
|
+
const sections = [buildSummarySection(report), buildProblemSection(diagnostics), buildTimingSection(report)].filter((section) => section.length > 0)
|
|
95
|
+
const content = stripVTControlCharacters([header, ...sections.map((section) => section.join('\n'))].join('\n\n'))
|
|
96
|
+
|
|
97
|
+
const baseName = `${['kubb', config.name, Date.now()].filter(Boolean).join('-')}.log`
|
|
98
|
+
const pathName = resolve(process.cwd(), '.kubb', baseName)
|
|
99
|
+
|
|
100
|
+
await write(pathName, `${content}\n`)
|
|
101
|
+
console.error(`Debug log written to ${relative(process.cwd(), pathName)}`)
|
|
102
|
+
},
|
|
103
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import process from 'node:process'
|
|
2
|
+
import { createReporter } from '@kubb/core'
|
|
3
|
+
import { buildReport } from './report.ts'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The `json` reporter. `report` returns one config's {@link Report}, which {@link createReporter}
|
|
7
|
+
* buffers, and `flush` writes them as a single pretty-printed JSON array on `kubb:lifecycle:end`.
|
|
8
|
+
* Buffering keeps a multi-config run one valid JSON document on stdout instead of concatenated
|
|
9
|
+
* objects that would break `jq .`. The terminal reporter is suppressed while `json` is active so
|
|
10
|
+
* stdout stays valid JSON.
|
|
11
|
+
*/
|
|
12
|
+
export const jsonReporter = createReporter({
|
|
13
|
+
name: 'json',
|
|
14
|
+
report(result) {
|
|
15
|
+
return buildReport(result)
|
|
16
|
+
},
|
|
17
|
+
flush(_context, reports) {
|
|
18
|
+
process.stdout.write(`${JSON.stringify(reports, null, 2)}\n`)
|
|
19
|
+
},
|
|
20
|
+
})
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { resolve } from 'node:path'
|
|
2
|
+
import { getElapsedMs } from '@internals/utils'
|
|
3
|
+
import { Diagnostics, type GenerationResult, isPerformanceDiagnostic, isProblemDiagnostic, type SerializedDiagnostic } from '@kubb/core'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* One plugin's elapsed time, derived from a `performance` diagnostic.
|
|
7
|
+
*/
|
|
8
|
+
export type ReportTiming = {
|
|
9
|
+
plugin: string
|
|
10
|
+
durationMs: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The normalized result of generating one config, shared by every reporter. Each reporter renders
|
|
15
|
+
* the same {@link Report} in its own format (the `cli` summary, the `json` document, the `file`
|
|
16
|
+
* log), so they always agree on the numbers. Build it with {@link buildReport}.
|
|
17
|
+
*/
|
|
18
|
+
export type Report = {
|
|
19
|
+
/**
|
|
20
|
+
* The config name, or an empty string when it is unnamed.
|
|
21
|
+
*/
|
|
22
|
+
name: string
|
|
23
|
+
status: 'success' | 'failed'
|
|
24
|
+
plugins: {
|
|
25
|
+
passed: number
|
|
26
|
+
/**
|
|
27
|
+
* Names of the plugins that failed.
|
|
28
|
+
*/
|
|
29
|
+
failed: Array<string>
|
|
30
|
+
total: number
|
|
31
|
+
}
|
|
32
|
+
counts: {
|
|
33
|
+
errors: number
|
|
34
|
+
warnings: number
|
|
35
|
+
infos: number
|
|
36
|
+
}
|
|
37
|
+
filesCreated: number
|
|
38
|
+
/**
|
|
39
|
+
* Wall-clock time spent generating this config, in milliseconds.
|
|
40
|
+
*/
|
|
41
|
+
durationMs: number
|
|
42
|
+
/**
|
|
43
|
+
* Absolute output directory the files were written to.
|
|
44
|
+
*/
|
|
45
|
+
output: string
|
|
46
|
+
/**
|
|
47
|
+
* Per-plugin durations, slowest first.
|
|
48
|
+
*/
|
|
49
|
+
timings: Array<ReportTiming>
|
|
50
|
+
/**
|
|
51
|
+
* The build problems, serialized to their JSON-safe fields plus a `docsUrl`.
|
|
52
|
+
*/
|
|
53
|
+
diagnostics: Array<SerializedDiagnostic>
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Builds the normalized {@link Report} for one config from its {@link GenerationResult}. Splits the
|
|
58
|
+
* diagnostics into problems and per-plugin timings (slowest first) and derives the plugin and issue
|
|
59
|
+
* counts, so every reporter renders the same data.
|
|
60
|
+
*/
|
|
61
|
+
export function buildReport(result: GenerationResult): Report {
|
|
62
|
+
const { config, diagnostics, filesCreated, status, hrStart } = result
|
|
63
|
+
|
|
64
|
+
const failed = Diagnostics.failedPlugins(diagnostics)
|
|
65
|
+
const total = config.plugins?.length ?? 0
|
|
66
|
+
const counts = Diagnostics.count(diagnostics)
|
|
67
|
+
const problems = diagnostics.filter(isProblemDiagnostic)
|
|
68
|
+
const timings = diagnostics
|
|
69
|
+
.filter(isPerformanceDiagnostic)
|
|
70
|
+
.sort((a, b) => b.duration - a.duration)
|
|
71
|
+
.map((diagnostic) => ({ plugin: diagnostic.plugin, durationMs: diagnostic.duration }))
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
name: config.name ?? '',
|
|
75
|
+
status,
|
|
76
|
+
plugins: { passed: total - failed.length, failed, total },
|
|
77
|
+
counts,
|
|
78
|
+
filesCreated,
|
|
79
|
+
durationMs: getElapsedMs(hrStart),
|
|
80
|
+
output: resolve(config.root, config.output.path),
|
|
81
|
+
timings,
|
|
82
|
+
diagnostics: problems.map((diagnostic) => Diagnostics.serialize(diagnostic)),
|
|
83
|
+
}
|
|
84
|
+
}
|
package/src/runners/agent/run.ts
CHANGED
|
@@ -50,10 +50,10 @@ export async function run({ port, host, configPath, allowWrite, allowAll, versio
|
|
|
50
50
|
try {
|
|
51
51
|
process.loadEnvFile()
|
|
52
52
|
} catch {
|
|
53
|
-
// .env file may not exist
|
|
53
|
+
// .env file may not exist. Ignore
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
// Resolve the @kubb/agent package path
|
|
56
|
+
// Resolve the @kubb/agent package path, createRequire is CJS/ESM compatible (import.meta.resolve is ESM-only)
|
|
57
57
|
const require = createRequire(import.meta.url)
|
|
58
58
|
let agentPkgPath: string
|
|
59
59
|
try {
|