@kubb/cli 4.12.7 → 4.12.9

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.
@@ -1,6 +1,7 @@
1
1
  import { resolve } from 'node:path'
2
2
  import { defineLogger } from '@kubb/core'
3
3
  import { write } from '@kubb/core/fs'
4
+ import { formatMs } from '@kubb/core/utils'
4
5
 
5
6
  type CachedEvent = {
6
7
  date: Date
@@ -18,26 +19,34 @@ type CachedEvent = {
18
19
  export const fileSystemLogger = defineLogger({
19
20
  name: 'filesystem',
20
21
  install(context) {
21
- const cachedLogs: Set<CachedEvent> = new Set()
22
- const startDate = Date.now()
22
+ const state = {
23
+ cachedLogs: new Set<CachedEvent>(),
24
+ startDate: Date.now(),
25
+ }
26
+
27
+ function reset() {
28
+ state.cachedLogs = new Set<CachedEvent>()
29
+ state.startDate = Date.now()
30
+ }
23
31
 
24
- async function writeLogs() {
25
- if (cachedLogs.size === 0) {
32
+ async function writeLogs(name?: string) {
33
+ if (state.cachedLogs.size === 0) {
26
34
  return
27
35
  }
28
36
 
29
37
  const files: Record<string, string[]> = {}
30
38
 
31
- for (const log of cachedLogs) {
32
- const fileName = resolve(process.cwd(), '.kubb', log.fileName || `kubb-${startDate}.log`)
39
+ for (const log of state.cachedLogs) {
40
+ const baseName = log.fileName || `${['kubb', name, state.startDate].filter(Boolean).join('-')}.log`
41
+ const pathName = resolve(process.cwd(), '.kubb', baseName)
33
42
 
34
- if (!files[fileName]) {
35
- files[fileName] = []
43
+ if (!files[pathName]) {
44
+ files[pathName] = []
36
45
  }
37
46
 
38
47
  if (log.logs.length > 0) {
39
48
  const timestamp = log.date.toLocaleString()
40
- files[fileName].push(`[${timestamp}]\n${log.logs.join('\n')}`)
49
+ files[pathName].push(`[${timestamp}]\n${log.logs.join('\n')}`)
41
50
  }
42
51
  }
43
52
 
@@ -46,26 +55,83 @@ export const fileSystemLogger = defineLogger({
46
55
  return write(fileName, logs.join('\n\n'))
47
56
  }),
48
57
  )
49
-
50
- cachedLogs.clear()
51
58
  }
52
59
 
60
+ context.on('info', (message, info) => {
61
+ state.cachedLogs.add({
62
+ date: new Date(),
63
+ logs: [`ℹ ${message} ${info}`],
64
+ fileName: undefined,
65
+ })
66
+ })
67
+
68
+ context.on('success', (message, info) => {
69
+ state.cachedLogs.add({
70
+ date: new Date(),
71
+ logs: [`✓ ${message} ${info}`],
72
+ fileName: undefined,
73
+ })
74
+ })
75
+
76
+ context.on('warn', (message, info) => {
77
+ state.cachedLogs.add({
78
+ date: new Date(),
79
+ logs: [`⚠ ${message} ${info}`],
80
+ fileName: undefined,
81
+ })
82
+ })
83
+
84
+ context.on('error', (error) => {
85
+ state.cachedLogs.add({
86
+ date: new Date(),
87
+ logs: [`✗ ${error.message}`, error.stack || 'unknown stack'],
88
+ fileName: undefined,
89
+ })
90
+ })
91
+
53
92
  context.on('debug', (message) => {
54
- cachedLogs.add({
93
+ state.cachedLogs.add({
55
94
  date: new Date(),
56
95
  logs: message.logs,
57
96
  fileName: undefined,
58
97
  })
59
98
  })
60
99
 
61
- context.on('lifecycle:end', async () => {
62
- await writeLogs()
100
+ context.on('plugin:start', (plugin) => {
101
+ state.cachedLogs.add({
102
+ date: new Date(),
103
+ logs: [`Generating ${plugin.name}`],
104
+ fileName: undefined,
105
+ })
106
+ })
107
+
108
+ context.on('plugin:end', (plugin, { duration, success }) => {
109
+ const durationStr = formatMs(duration)
110
+
111
+ state.cachedLogs.add({
112
+ date: new Date(),
113
+ logs: [success ? `${plugin.name} completed in ${durationStr}` : `${plugin.name} failed in ${durationStr}`],
114
+ fileName: undefined,
115
+ })
116
+ })
117
+
118
+ context.on('files:processing:start', (files) => {
119
+ state.cachedLogs.add({
120
+ date: new Date(),
121
+ logs: [`Start ${files.length} writing:`, ...files.map((file) => file.path)],
122
+ fileName: undefined,
123
+ })
124
+ })
125
+
126
+ context.on('generation:end', async (config) => {
127
+ await writeLogs(config.name)
128
+ reset()
63
129
  })
64
130
 
65
131
  // Fallback: Write logs on process exit to handle crashes
66
132
  const exitHandler = () => {
67
133
  // Synchronous write on exit - best effort
68
- if (cachedLogs.size > 0) {
134
+ if (state.cachedLogs.size > 0) {
69
135
  writeLogs().catch(() => {
70
136
  // Ignore errors on exit
71
137
  })
@@ -21,6 +21,15 @@ export const githubActionsLogger = defineLogger({
21
21
  currentConfigs: [] as Array<Config>,
22
22
  }
23
23
 
24
+ function reset() {
25
+ state.totalPlugins = 0
26
+ state.completedPlugins = 0
27
+ state.failedPlugins = 0
28
+ state.totalFiles = 0
29
+ state.processedFiles = 0
30
+ state.hrStart = process.hrtime()
31
+ }
32
+
24
33
  function showProgressStep() {
25
34
  if (logLevel <= LogLevel.silent) {
26
35
  return
@@ -42,8 +51,8 @@ export const githubActionsLogger = defineLogger({
42
51
  }
43
52
 
44
53
  if (parts.length > 0) {
45
- parts.push(pc.green(duration))
46
- console.log(parts.join(pc.dim(' | ')))
54
+ parts.push(`${pc.green(duration)} elapsed`)
55
+ console.log(getMessage(parts.join(pc.dim(' | '))))
47
56
  }
48
57
  }
49
58
 
@@ -110,6 +119,7 @@ export const githubActionsLogger = defineLogger({
110
119
 
111
120
  context.on('lifecycle:start', (version) => {
112
121
  console.log(pc.yellow(`Kubb ${version} 🧩`))
122
+ reset()
113
123
  })
114
124
 
115
125
  context.on('config:start', () => {
@@ -141,9 +151,6 @@ export const githubActionsLogger = defineLogger({
141
151
  context.on('generation:start', (config) => {
142
152
  // Initialize progress tracking
143
153
  state.totalPlugins = config.plugins?.length || 0
144
- state.completedPlugins = 0
145
- state.failedPlugins = 0
146
- state.hrStart = process.hrtime()
147
154
 
148
155
  const text = config.name ? `Generation for ${pc.bold(config.name)}` : 'Generation'
149
156
 
@@ -154,6 +161,8 @@ export const githubActionsLogger = defineLogger({
154
161
  if (state.currentConfigs.length === 1) {
155
162
  console.log(getMessage(text))
156
163
  }
164
+
165
+ reset()
157
166
  })
158
167
 
159
168
  context.on('plugin:start', (plugin) => {
@@ -250,34 +259,6 @@ export const githubActionsLogger = defineLogger({
250
259
  console.log(text)
251
260
  })
252
261
 
253
- context.on('hook:execute', async ({ command, args }, cb) => {
254
- try {
255
- const result = await execa(command, args, {
256
- detached: true,
257
- stripFinalNewline: true,
258
- })
259
-
260
- await context.emit('debug', {
261
- date: new Date(),
262
- logs: [result.stdout],
263
- })
264
-
265
- console.log(result.stdout)
266
-
267
- cb()
268
- } catch (err) {
269
- const error = new Error('Hook execute failed')
270
- error.cause = err
271
-
272
- await context.emit('debug', {
273
- date: new Date(),
274
- logs: [(err as any).stdout],
275
- })
276
-
277
- await context.emit('error', error)
278
- }
279
- })
280
-
281
262
  context.on('format:start', () => {
282
263
  if (logLevel <= LogLevel.silent) {
283
264
  return
@@ -334,21 +315,45 @@ export const githubActionsLogger = defineLogger({
334
315
  }
335
316
  })
336
317
 
337
- context.on('hook:start', (command) => {
338
- if (logLevel <= LogLevel.silent) {
339
- return
340
- }
341
-
318
+ context.on('hook:start', async ({ id, command, args }) => {
342
319
  const text = getMessage(`Hook ${pc.dim(command)} started`)
343
320
 
344
- if (state.currentConfigs.length === 1) {
345
- openGroup(`Hook ${command}`)
321
+ if (logLevel > LogLevel.silent) {
322
+ if (state.currentConfigs.length === 1) {
323
+ openGroup(`Hook ${command}`)
324
+ }
325
+
326
+ console.log(text)
346
327
  }
347
328
 
348
- console.log(text)
329
+ try {
330
+ const result = await execa(command, args, {
331
+ detached: true,
332
+ stripFinalNewline: true,
333
+ })
334
+
335
+ await context.emit('debug', {
336
+ date: new Date(),
337
+ logs: [result.stdout],
338
+ })
339
+
340
+ console.log(result.stdout)
341
+
342
+ await context.emit('hook:end', { command, id })
343
+ } catch (err) {
344
+ const error = new Error('Hook execute failed')
345
+ error.cause = err
346
+
347
+ await context.emit('debug', {
348
+ date: new Date(),
349
+ logs: [(err as any).stdout],
350
+ })
351
+
352
+ await context.emit('error', error)
353
+ }
349
354
  })
350
355
 
351
- context.on('hook:end', (command) => {
356
+ context.on('hook:end', ({ command }) => {
352
357
  if (logLevel <= LogLevel.silent) {
353
358
  return
354
359
  }
@@ -362,9 +367,10 @@ export const githubActionsLogger = defineLogger({
362
367
  }
363
368
  })
364
369
 
365
- context.on('generation:summary', (config, { status, failedPlugins }) => {
370
+ context.on('generation:summary', (config, { status, hrStart, failedPlugins }) => {
366
371
  const pluginsCount = config.plugins?.length || 0
367
372
  const successCount = pluginsCount - failedPlugins.size
373
+ const duration = formatHrtime(hrStart)
368
374
 
369
375
  if (state.currentConfigs.length > 1) {
370
376
  console.log(' ')
@@ -372,8 +378,8 @@ export const githubActionsLogger = defineLogger({
372
378
 
373
379
  console.log(
374
380
  status === 'success'
375
- ? `Kubb Summary: ${pc.blue('✓')} ${`${successCount} successful`}, ${pluginsCount} total`
376
- : `Kubb Summary: ${pc.blue('✓')} ${`${successCount} successful`}, ✗ ${`${failedPlugins.size} failed`}, ${pluginsCount} total`,
381
+ ? `Kubb Summary: ${pc.blue('✓')} ${`${successCount} successful`}, ${pluginsCount} total, ${pc.green(duration)}`
382
+ : `Kubb Summary: ${pc.blue('✓')} ${`${successCount} successful`}, ✗ ${`${failedPlugins.size} failed`}, ${pluginsCount} total, ${pc.green(duration)}`,
377
383
  )
378
384
 
379
385
  if (state.currentConfigs.length > 1) {
@@ -169,34 +169,6 @@ export const plainLogger = defineLogger({
169
169
  console.log(text)
170
170
  })
171
171
 
172
- context.on('hook:execute', async ({ command, args }, cb) => {
173
- try {
174
- const result = await execa(command, args, {
175
- detached: true,
176
- stripFinalNewline: true,
177
- })
178
-
179
- await context.emit('debug', {
180
- date: new Date(),
181
- logs: [result.stdout],
182
- })
183
-
184
- console.log(result.stdout)
185
-
186
- cb()
187
- } catch (err) {
188
- const error = new Error('Hook execute failed')
189
- error.cause = err
190
-
191
- await context.emit('debug', {
192
- date: new Date(),
193
- logs: [(err as any).stdout],
194
- })
195
-
196
- await context.emit('error', error)
197
- }
198
- })
199
-
200
172
  context.on('format:start', () => {
201
173
  if (logLevel <= LogLevel.silent) {
202
174
  return
@@ -237,14 +209,38 @@ export const plainLogger = defineLogger({
237
209
  console.log(text)
238
210
  })
239
211
 
240
- context.on('hook:start', (command) => {
241
- if (logLevel <= LogLevel.silent) {
242
- return
212
+ context.on('hook:start', async ({ id, command, args }) => {
213
+ const text = getMessage(`Hook ${command} started`)
214
+
215
+ if (logLevel > LogLevel.silent) {
216
+ console.log(text)
243
217
  }
244
218
 
245
- const text = getMessage(`Hook ${command} started`)
219
+ try {
220
+ const result = await execa(command, args, {
221
+ detached: true,
222
+ stripFinalNewline: true,
223
+ })
246
224
 
247
- console.log(text)
225
+ await context.emit('debug', {
226
+ date: new Date(),
227
+ logs: [result.stdout],
228
+ })
229
+
230
+ console.log(result.stdout)
231
+
232
+ await context.emit('hook:end', { command, id })
233
+ } catch (err) {
234
+ const error = new Error('Hook execute failed')
235
+ error.cause = err
236
+
237
+ await context.emit('debug', {
238
+ date: new Date(),
239
+ logs: [(err as any).stdout],
240
+ })
241
+
242
+ await context.emit('error', error)
243
+ }
248
244
  })
249
245
 
250
246
  context.on('hook:end', (command) => {
@@ -1,3 +1,4 @@
1
+ import { createHash } from 'node:crypto'
1
2
  import path from 'node:path'
2
3
  import process from 'node:process'
3
4
  import { type Config, type KubbEvents, LogLevel, safeBuild, setup } from '@kubb/core'
@@ -106,13 +107,15 @@ export async function generate({ input, config, events, logLevel }: GenerateProp
106
107
 
107
108
  if (config.output.format === 'prettier') {
108
109
  try {
109
- await events.emit(
110
- 'hook:execute',
111
- {
112
- command: 'prettier',
113
- args: ['--ignore-unknown', '--write', path.resolve(definedConfig.root, definedConfig.output.path)],
114
- },
115
- async () => {
110
+ const hookId = createHash('sha256').update([config.name, config.output.format].filter(Boolean).join('-')).digest('hex')
111
+ await events.emit('hook:start', {
112
+ id: hookId,
113
+ command: 'prettier',
114
+ args: ['--ignore-unknown', '--write', path.resolve(definedConfig.root, definedConfig.output.path)],
115
+ })
116
+
117
+ await events.on('hook:end', async ({ id }) => {
118
+ if (id === hookId) {
116
119
  await events.emit(
117
120
  'success',
118
121
  [
@@ -123,8 +126,8 @@ export async function generate({ input, config, events, logLevel }: GenerateProp
123
126
  .filter(Boolean)
124
127
  .join(' '),
125
128
  )
126
- },
127
- )
129
+ }
130
+ })
128
131
  } catch (caughtError) {
129
132
  await events.emit('error', caughtError as Error)
130
133
  }
@@ -134,13 +137,15 @@ export async function generate({ input, config, events, logLevel }: GenerateProp
134
137
 
135
138
  if (config.output.format === 'biome') {
136
139
  try {
137
- await events.emit(
138
- 'hook:execute',
139
- {
140
- command: 'biome',
141
- args: ['format', '--write', path.resolve(definedConfig.root, definedConfig.output.path)],
142
- },
143
- async () => {
140
+ const hookId = createHash('sha256').update([config.name, config.output.format].filter(Boolean).join('-')).digest('hex')
141
+ await events.emit('hook:start', {
142
+ id: hookId,
143
+ command: 'biome',
144
+ args: ['format', '--write', path.resolve(definedConfig.root, definedConfig.output.path)],
145
+ })
146
+
147
+ await events.on('hook:end', async ({ id }) => {
148
+ if (id === hookId) {
144
149
  await events.emit(
145
150
  'success',
146
151
  [
@@ -151,8 +156,8 @@ export async function generate({ input, config, events, logLevel }: GenerateProp
151
156
  .filter(Boolean)
152
157
  .join(' '),
153
158
  )
154
- },
155
- )
159
+ }
160
+ })
156
161
  } catch (caughtError) {
157
162
  const error = new Error('Biome not found')
158
163
  error.cause = caughtError
@@ -179,13 +184,15 @@ export async function generate({ input, config, events, logLevel }: GenerateProp
179
184
 
180
185
  if (config.output.lint === 'eslint') {
181
186
  try {
182
- await events.emit(
183
- 'hook:execute',
184
- {
185
- command: 'eslint',
186
- args: [path.resolve(definedConfig.root, definedConfig.output.path), '--fix'],
187
- },
188
- async () => {
187
+ const hookId = createHash('sha256').update([config.name, config.output.lint].filter(Boolean).join('-')).digest('hex')
188
+ await events.emit('hook:start', {
189
+ id: hookId,
190
+ command: 'eslint',
191
+ args: [path.resolve(definedConfig.root, definedConfig.output.path), '--fix'],
192
+ })
193
+
194
+ await events.on('hook:end', async ({ id }) => {
195
+ if (id === hookId) {
189
196
  await events.emit(
190
197
  'success',
191
198
  [
@@ -196,8 +203,8 @@ export async function generate({ input, config, events, logLevel }: GenerateProp
196
203
  .filter(Boolean)
197
204
  .join(' '),
198
205
  )
199
- },
200
- )
206
+ }
207
+ })
201
208
  } catch (caughtError) {
202
209
  const error = new Error('Eslint not found')
203
210
  error.cause = caughtError
@@ -207,13 +214,15 @@ export async function generate({ input, config, events, logLevel }: GenerateProp
207
214
 
208
215
  if (config.output.lint === 'biome') {
209
216
  try {
210
- await events.emit(
211
- 'hook:execute',
212
- {
213
- command: 'biome',
214
- args: ['lint', '--fix', path.resolve(definedConfig.root, definedConfig.output.path)],
215
- },
216
- async () => {
217
+ const hookId = createHash('sha256').update([config.name, config.output.lint].filter(Boolean).join('-')).digest('hex')
218
+ await events.emit('hook:start', {
219
+ id: hookId,
220
+ command: 'biome',
221
+ args: ['lint', '--fix', path.resolve(definedConfig.root, definedConfig.output.path)],
222
+ })
223
+
224
+ await events.on('hook:end', async ({ id }) => {
225
+ if (id === hookId) {
217
226
  await events.emit(
218
227
  'success',
219
228
  [
@@ -224,8 +233,8 @@ export async function generate({ input, config, events, logLevel }: GenerateProp
224
233
  .filter(Boolean)
225
234
  .join(' '),
226
235
  )
227
- },
228
- )
236
+ }
237
+ })
229
238
  } catch (caughtError) {
230
239
  const error = new Error('Biome not found')
231
240
  error.cause = caughtError
@@ -235,13 +244,15 @@ export async function generate({ input, config, events, logLevel }: GenerateProp
235
244
 
236
245
  if (config.output.lint === 'oxlint') {
237
246
  try {
238
- await events.emit(
239
- 'hook:execute',
240
- {
241
- command: 'oxlint',
242
- args: ['--fix', path.resolve(definedConfig.root, definedConfig.output.path)],
243
- },
244
- async () => {
247
+ const hookId = createHash('sha256').update([config.name, config.output.lint].filter(Boolean).join('-')).digest('hex')
248
+ await events.emit('hook:start', {
249
+ id: hookId,
250
+ command: 'oxlint',
251
+ args: ['--fix', path.resolve(definedConfig.root, definedConfig.output.path)],
252
+ })
253
+
254
+ await events.on('hook:end', async ({ id }) => {
255
+ if (id === hookId) {
245
256
  await events.emit(
246
257
  'success',
247
258
  [
@@ -252,8 +263,8 @@ export async function generate({ input, config, events, logLevel }: GenerateProp
252
263
  .filter(Boolean)
253
264
  .join(' '),
254
265
  )
255
- },
256
- )
266
+ }
267
+ })
257
268
  } catch (caughtError) {
258
269
  const error = new Error('Oxlint not found')
259
270
  error.cause = caughtError
@@ -1,3 +1,4 @@
1
+ import { createHash } from 'node:crypto'
1
2
  import type { Config, KubbEvents } from '@kubb/core'
2
3
  import type { AsyncEventEmitter } from '@kubb/core/utils'
3
4
 
@@ -19,12 +20,14 @@ export async function executeHooks({ hooks, events }: ExecutingHooksProps): Prom
19
20
  continue
20
21
  }
21
22
 
22
- await events.emit('hook:start', command)
23
+ const hookId = createHash('sha256').update(command).digest('hex')
23
24
 
24
- await events.emit('hook:execute', { command: cmd, args }, async () => {
25
- await events.emit('success', `${pc.dim(command)} successfully executed`)
25
+ await events.emit('hook:start', { id: hookId, command: cmd, args })
26
26
 
27
- await events.emit('hook:end', command)
27
+ await events.on('hook:end', async ({ id }) => {
28
+ if (id === hookId) {
29
+ await events.emit('success', `${pc.dim(command)} successfully executed`)
30
+ }
28
31
  })
29
32
  }
30
33
  }