@kubb/core 4.11.1 → 4.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/{getBarrelFiles-BkDzzugQ.cjs → getBarrelFiles-8VEWWk9Z.cjs} +80 -111
  2. package/dist/getBarrelFiles-8VEWWk9Z.cjs.map +1 -0
  3. package/dist/{getBarrelFiles-BVMBhc50.d.cts → getBarrelFiles-B_2WDywH.d.cts} +3 -3
  4. package/dist/{getBarrelFiles-a-GlnjYa.js → getBarrelFiles-DQ0hksqD.js} +80 -111
  5. package/dist/getBarrelFiles-DQ0hksqD.js.map +1 -0
  6. package/dist/{getBarrelFiles-DjQ68d4e.d.ts → getBarrelFiles-ZIHk_1ln.d.ts} +3 -3
  7. package/dist/hooks.d.cts +1 -1
  8. package/dist/hooks.d.ts +1 -1
  9. package/dist/index.cjs +261 -42
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.cts +5 -7
  12. package/dist/index.d.ts +5 -7
  13. package/dist/index.js +262 -43
  14. package/dist/index.js.map +1 -1
  15. package/dist/{logger-DIA19Yfz.js → logger-CQn6sdC0.js} +72 -7
  16. package/dist/{logger-CPt4U57Z.cjs.map → logger-CQn6sdC0.js.map} +1 -1
  17. package/dist/{logger-CPt4U57Z.cjs → logger-US5g7KdM.cjs} +72 -7
  18. package/dist/logger-US5g7KdM.cjs.map +1 -0
  19. package/dist/{logger-C96jDrSt.d.ts → logger-mq06Cxxv.d.cts} +29 -4
  20. package/dist/{logger-BJDkLsF0.d.cts → logger-o16AyvGp.d.ts} +29 -4
  21. package/dist/logger.cjs +1 -1
  22. package/dist/logger.d.cts +1 -1
  23. package/dist/logger.d.ts +1 -1
  24. package/dist/logger.js +1 -1
  25. package/dist/{types-tSSA1oz8.d.cts → types-CCEy_FVr.d.cts} +36 -27
  26. package/dist/{types-69-evK37.d.ts → types-DgfEZ3IN.d.ts} +36 -27
  27. package/dist/utils.cjs +1 -1
  28. package/dist/utils.d.cts +2 -2
  29. package/dist/utils.d.ts +2 -2
  30. package/dist/utils.js +1 -1
  31. package/package.json +1 -1
  32. package/src/PluginManager.ts +81 -114
  33. package/src/build.ts +229 -25
  34. package/src/logger.ts +87 -9
  35. package/src/utils/ciDetection.ts +40 -0
  36. package/src/utils/diagnostics.ts +15 -0
  37. package/dist/getBarrelFiles-BkDzzugQ.cjs.map +0 -1
  38. package/dist/getBarrelFiles-a-GlnjYa.js.map +0 -1
  39. package/dist/logger-DIA19Yfz.js.map +0 -1
package/src/build.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { join, relative, resolve } from 'node:path'
2
+ import { performance } from 'node:perf_hooks'
2
3
  import type { KubbFile } from '@kubb/fabric-core/types'
3
4
  import type { Fabric } from '@kubb/react-fabric'
4
5
  import { createFabric } from '@kubb/react-fabric'
@@ -12,6 +13,7 @@ import type { Logger } from './logger.ts'
12
13
  import { createLogger } from './logger.ts'
13
14
  import { PluginManager } from './PluginManager.ts'
14
15
  import type { Config, Output, Plugin, UserConfig } from './types.ts'
16
+ import { getDiagnosticInfo } from './utils/diagnostics.ts'
15
17
  import { URLPath } from './utils/URLPath.ts'
16
18
 
17
19
  type BuildOptions = {
@@ -27,15 +29,12 @@ type BuildOutput = {
27
29
  fabric: Fabric
28
30
  files: Array<KubbFile.ResolvedFile>
29
31
  pluginManager: PluginManager
30
- // TODO check if we can remove error
31
- /**
32
- * Only for safeBuild,
33
- * @deprecated
34
- */
32
+ pluginTimings: Map<string, number>
35
33
  error?: Error
36
34
  }
37
35
 
38
36
  type SetupResult = {
37
+ logger: Logger
39
38
  fabric: Fabric
40
39
  pluginManager: PluginManager
41
40
  }
@@ -43,20 +42,50 @@ type SetupResult = {
43
42
  export async function setup(options: BuildOptions): Promise<SetupResult> {
44
43
  const { config: userConfig, logger = createLogger() } = options
45
44
 
45
+ const diagnosticInfo = getDiagnosticInfo()
46
+
46
47
  if (Array.isArray(userConfig.input)) {
47
48
  console.warn(pc.yellow('This feature is still under development — use with caution'))
48
49
  }
49
50
 
51
+ logger.emit('debug', {
52
+ date: new Date(),
53
+ category: 'setup',
54
+ logs: [
55
+ 'Configuration:',
56
+ ` • Name: ${userConfig.name || 'unnamed'}`,
57
+ ` • Root: ${userConfig.root || process.cwd()}`,
58
+ ` • Output: ${userConfig.output?.path || 'not specified'}`,
59
+ ` • Plugins: ${userConfig.plugins?.length || 0}`,
60
+ 'Output Settings:',
61
+ ` • Write: ${userConfig.output?.write !== false ? 'enabled' : 'disabled'}`,
62
+ ` • Formater: ${userConfig.output?.format || 'none'}`,
63
+ ` • Linter: ${userConfig.output?.lint || 'none'}`,
64
+ 'Environment:',
65
+ Object.entries(diagnosticInfo)
66
+ .map(([key, value]) => ` • ${key}: ${value}`)
67
+ .join('\n'),
68
+ ],
69
+ })
70
+
50
71
  try {
51
72
  if (isInputPath(userConfig) && !new URLPath(userConfig.input.path).isURL) {
52
73
  await exists(userConfig.input.path)
74
+
75
+ logger.emit('debug', {
76
+ date: new Date(),
77
+ category: 'setup',
78
+ logs: [`✓ Input file validated: ${userConfig.input.path}`],
79
+ })
53
80
  }
54
81
  } catch (e) {
55
82
  if (isInputPath(userConfig)) {
83
+ const error = e as Error
84
+
56
85
  throw new Error(
57
86
  `Cannot read file/URL defined in \`input.path\` or set with \`kubb generate PATH\` in the CLI of your Kubb config ${userConfig.input.path}`,
58
87
  {
59
- cause: e,
88
+ cause: error,
60
89
  },
61
90
  )
62
91
  }
@@ -78,6 +107,11 @@ export async function setup(options: BuildOptions): Promise<SetupResult> {
78
107
  }
79
108
 
80
109
  if (definedConfig.output.clean) {
110
+ logger.emit('debug', {
111
+ date: new Date(),
112
+ category: 'setup',
113
+ logs: ['Cleaning output directories', ` • Output: ${definedConfig.output.path}`, ' • Cache: .kubb'],
114
+ })
81
115
  await clean(definedConfig.output.path)
82
116
  await clean(join(definedConfig.root, '.kubb'))
83
117
  }
@@ -86,16 +120,122 @@ export async function setup(options: BuildOptions): Promise<SetupResult> {
86
120
  fabric.use(fsPlugin, { dryRun: !definedConfig.output.write })
87
121
  fabric.use(typescriptParser)
88
122
 
123
+ fabric.context.on('process:start', ({ files }) => {
124
+ logger.emit('progress_start', { id: 'files', size: files.length, message: 'Writing files ...' })
125
+ logger.emit('debug', {
126
+ date: new Date(),
127
+ category: 'file',
128
+ logs: [`Writing ${files.length} files...`],
129
+ })
130
+ })
131
+
132
+ fabric.context.on('process:progress', async ({ file, source }) => {
133
+ const message = file ? `Writing ${relative(definedConfig.root, file.path)}` : ''
134
+ logger.emit('progressed', { id: 'files', message })
135
+
136
+ if (source) {
137
+ await write(file.path, source, { sanity: false })
138
+ }
139
+ })
140
+
141
+ fabric.context.on('process:end', () => {
142
+ logger.emit('progress_stop', { id: 'files' })
143
+ logger.emit('debug', {
144
+ date: new Date(),
145
+ category: 'file',
146
+ logs: ['✓ File write process completed'],
147
+ })
148
+ })
149
+
150
+ logger.emit('debug', {
151
+ date: new Date(),
152
+ category: 'setup',
153
+ logs: [
154
+ '✓ Fabric initialized',
155
+ ` • File writing: ${definedConfig.output.write ? 'enabled' : 'disabled (dry-run)'}`,
156
+ ` • Barrel type: ${definedConfig.output.barrelType || 'none'}`,
157
+ ],
158
+ })
159
+
89
160
  const pluginManager = new PluginManager(definedConfig, { fabric, logger, concurrency: 5 })
90
161
 
162
+ pluginManager.on('executing', ({ plugin, hookName, strategy, parameters }) => {
163
+ logger.emit('debug', {
164
+ date: new Date(),
165
+ category: 'hook',
166
+ pluginName: plugin.name,
167
+ logs: [`Executing hook: ${hookName}`, ` • Strategy: ${strategy}`, ' • Parameters:', JSON.stringify(parameters, null, 2)],
168
+ })
169
+ })
170
+
171
+ pluginManager.on('executed', ({ plugin, hookName, duration, parameters }) => {
172
+ let message = ''
173
+ if (hookName === 'resolvePath') {
174
+ const [path] = parameters || []
175
+ message = `Resolving path '${path}'`
176
+ }
177
+
178
+ if (hookName === 'resolveName') {
179
+ const [name, type] = parameters || []
180
+ message = `Resolving name '${name}' and type '${type}'`
181
+ }
182
+
183
+ logger.emit('progressed', {
184
+ id: hookName,
185
+ message: `${plugin.name}: ${message}`,
186
+ })
187
+ logger.emit('debug', {
188
+ date: new Date(),
189
+ category: 'hook',
190
+ pluginName: plugin.name,
191
+ logs: [`✓ Completed in ${duration}ms`],
192
+ })
193
+ })
194
+
195
+ pluginManager.on('progress_start', ({ hookName, plugins }) => {
196
+ logger.emit('progress_start', { id: hookName, size: plugins.length, message: 'Running plugins...' })
197
+ })
198
+
199
+ pluginManager.on('progress_stop', ({ hookName }) => {
200
+ logger.emit('progress_stop', { id: hookName })
201
+ })
202
+
203
+ pluginManager.on('error', (error, { plugin, strategy, duration, parameters, hookName }) => {
204
+ const text = `${error.message} (plugin: ${plugin?.name || 'unknown'}, hook: ${hookName || 'unknown'})`
205
+
206
+ logger.emit('error', text, error)
207
+
208
+ logger.emit('debug', {
209
+ date: new Date(),
210
+ category: 'error',
211
+ pluginName: plugin.name,
212
+ logs: [
213
+ `✗ Hook '${hookName}' failed after ${duration}ms`,
214
+ ` • Strategy: ${strategy}`,
215
+ ` • Error: ${error.constructor.name} - ${error.message}`,
216
+ ' • Stack Trace:',
217
+ error.stack || 'No stack trace available',
218
+ ' • Parameters:',
219
+ JSON.stringify(parameters, null, 2),
220
+ ],
221
+ })
222
+ })
223
+
224
+ logger.emit('debug', {
225
+ date: new Date(),
226
+ category: 'setup',
227
+ logs: ['✓ PluginManager initialized', ' • Concurrency: 5', ` • Total plugins: ${pluginManager.plugins.length}`],
228
+ })
229
+
91
230
  return {
231
+ logger,
92
232
  fabric,
93
233
  pluginManager,
94
234
  }
95
235
  }
96
236
 
97
237
  export async function build(options: BuildOptions, overrides?: SetupResult): Promise<BuildOutput> {
98
- const { fabric, files, pluginManager, failedPlugins, error } = await safeBuild(options, overrides)
238
+ const { fabric, files, pluginManager, failedPlugins, pluginTimings, error } = await safeBuild(options, overrides)
99
239
 
100
240
  if (error) {
101
241
  throw error
@@ -106,14 +246,16 @@ export async function build(options: BuildOptions, overrides?: SetupResult): Pro
106
246
  fabric,
107
247
  files,
108
248
  pluginManager,
249
+ pluginTimings,
109
250
  error,
110
251
  }
111
252
  }
112
253
 
113
254
  export async function safeBuild(options: BuildOptions, overrides?: SetupResult): Promise<BuildOutput> {
114
- const { fabric, pluginManager } = overrides ? overrides : await setup(options)
255
+ const { fabric, pluginManager, logger } = overrides ? overrides : await setup(options)
115
256
 
116
257
  const failedPlugins = new Set<{ plugin: Plugin; error: Error }>()
258
+ const pluginTimings = new Map<string, number>()
117
259
  const config = pluginManager.config
118
260
 
119
261
  try {
@@ -123,9 +265,69 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
123
265
  const installer = plugin.install.bind(context)
124
266
 
125
267
  try {
268
+ const startTime = performance.now()
269
+ const timestamp = new Date()
270
+
271
+ // Start plugin group
272
+ logger.emit('debug', {
273
+ date: timestamp,
274
+ pluginGroupMarker: 'start',
275
+ pluginName: plugin.name,
276
+ logs: [],
277
+ })
278
+
279
+ logger.emit('debug', {
280
+ date: timestamp,
281
+ category: 'plugin',
282
+ pluginName: plugin.name,
283
+ logs: ['Installing plugin...', ` • Plugin Key: ${JSON.stringify(plugin.key)}`],
284
+ })
285
+
126
286
  await installer(context)
287
+
288
+ const duration = Math.round(performance.now() - startTime)
289
+ pluginTimings.set(plugin.name, duration)
290
+
291
+ logger.emit('debug', {
292
+ date: new Date(),
293
+ category: 'plugin',
294
+ pluginName: plugin.name,
295
+ logs: [`✓ Plugin installed successfully (${duration}ms)`],
296
+ })
297
+
298
+ // End plugin group
299
+ logger.emit('debug', {
300
+ date: new Date(),
301
+ pluginGroupMarker: 'end',
302
+ pluginName: plugin.name,
303
+ logs: [],
304
+ })
127
305
  } catch (e) {
128
- failedPlugins.add({ plugin, error: e as Error })
306
+ const error = e as Error
307
+ const errorTimestamp = new Date()
308
+
309
+ logger.emit('debug', {
310
+ date: errorTimestamp,
311
+ category: 'error',
312
+ pluginName: plugin.name,
313
+ logs: [
314
+ '✗ Plugin installation failed',
315
+ ` • Plugin Key: ${JSON.stringify(plugin.key)}`,
316
+ ` • Error: ${error.constructor.name} - ${error.message}`,
317
+ ' • Stack Trace:',
318
+ error.stack || 'No stack trace available',
319
+ ],
320
+ })
321
+
322
+ // End plugin group even on error
323
+ logger.emit('debug', {
324
+ date: errorTimestamp,
325
+ pluginGroupMarker: 'end',
326
+ pluginName: plugin.name,
327
+ logs: [],
328
+ })
329
+
330
+ failedPlugins.add({ plugin, error })
129
331
  }
130
332
  }
131
333
 
@@ -133,10 +335,20 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
133
335
  const root = resolve(config.root)
134
336
  const rootPath = resolve(root, config.output.path, 'index.ts')
135
337
 
338
+ logger.emit('debug', {
339
+ date: new Date(),
340
+ logs: ['Generating barrel file', ` • Type: ${config.output.barrelType}`, ` • Path: ${rootPath}`],
341
+ })
342
+
136
343
  const barrelFiles = fabric.files.filter((file) => {
137
344
  return file.sources.some((source) => source.isIndexable)
138
345
  })
139
346
 
347
+ logger.emit('debug', {
348
+ date: new Date(),
349
+ logs: [`Found ${barrelFiles.length} indexable files for barrel export`],
350
+ })
351
+
140
352
  const rootFile: KubbFile.File = {
141
353
  path: rootPath,
142
354
  baseName: 'index.ts',
@@ -175,24 +387,14 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
175
387
  }
176
388
 
177
389
  await fabric.upsertFile(rootFile)
178
- }
179
-
180
- fabric.context.on('process:start', ({ files }) => {
181
- pluginManager.logger.emit('progress_start', { id: 'files', size: files.length, message: 'Writing files ...' })
182
- })
183
-
184
- fabric.context.on('process:progress', async ({ file, source }) => {
185
- const message = file ? `Writing ${relative(config.root, file.path)}` : ''
186
- pluginManager.logger.emit('progressed', { id: 'files', message })
187
390
 
188
- if (source) {
189
- await write(file.path, source, { sanity: false })
190
- }
191
- })
391
+ logger.emit('debug', {
392
+ date: new Date(),
393
+ category: 'file',
394
+ logs: [`✓ Generated barrel file (${rootFile.exports?.length || 0} exports)`],
395
+ })
396
+ }
192
397
 
193
- fabric.context.on('process:end', () => {
194
- pluginManager.logger.emit('progress_stop', { id: 'files' })
195
- })
196
398
  const files = [...fabric.files]
197
399
 
198
400
  await fabric.write({ extension: config.output.extension })
@@ -202,6 +404,7 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
202
404
  fabric,
203
405
  files,
204
406
  pluginManager,
407
+ pluginTimings,
205
408
  }
206
409
  } catch (e) {
207
410
  return {
@@ -209,6 +412,7 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
209
412
  fabric,
210
413
  files: [],
211
414
  pluginManager,
415
+ pluginTimings,
212
416
  error: e as Error,
213
417
  }
214
418
  }
package/src/logger.ts CHANGED
@@ -4,16 +4,43 @@ import { createConsola } from 'consola'
4
4
  import pc from 'picocolors'
5
5
  import seedrandom from 'seedrandom'
6
6
  import { write } from './fs/write.ts'
7
+ import { endGroup, isGitHubActions, startGroup } from './utils/ciDetection.ts'
7
8
  import { EventEmitter } from './utils/EventEmitter.ts'
8
9
 
9
- type DebugEvent = { date: Date; logs: string[]; fileName?: string }
10
+ type DebugEvent = {
11
+ date: Date
12
+ logs: string[]
13
+ fileName?: string
14
+ /**
15
+ * Category of the debug log, used for GitHub Actions grouping
16
+ * - 'setup': Initial configuration and environment setup
17
+ * - 'plugin': Plugin installation and execution
18
+ * - 'hook': Plugin hook execution details
19
+ * - 'schema': Schema parsing and generation
20
+ * - 'file': File operations (read/write/generate)
21
+ * - 'error': Error details and stack traces
22
+ * - undefined: Generic logs (always inline)
23
+ */
24
+ category?: 'setup' | 'plugin' | 'hook' | 'schema' | 'file' | 'error'
25
+ /**
26
+ * Plugin name for grouping plugin-specific logs together
27
+ */
28
+ pluginName?: string
29
+ /**
30
+ * Indicates if this is the start or end of a plugin's execution
31
+ * - 'start': Start of plugin execution group
32
+ * - 'end': End of plugin execution group
33
+ */
34
+ pluginGroupMarker?: 'start' | 'end'
35
+ }
10
36
 
11
37
  type Events = {
12
38
  start: [message: string]
13
39
  success: [message: string]
14
- error: [message: string, cause: Error]
40
+ error: [message: string, error: Error]
15
41
  warning: [message: string]
16
42
  debug: [DebugEvent]
43
+ verbose: [DebugEvent]
17
44
  info: [message: string]
18
45
  progress_start: [{ id: string; size: number; message?: string }]
19
46
  progressed: [{ id: string; message?: string }]
@@ -22,10 +49,15 @@ type Events = {
22
49
 
23
50
  export const LogMapper = {
24
51
  silent: Number.NEGATIVE_INFINITY,
52
+ error: 0,
53
+ warn: 1,
25
54
  info: 3,
26
- debug: 4,
55
+ verbose: 4,
56
+ debug: 5,
27
57
  } as const
28
58
 
59
+ const DEBUG_LOG_TITLE_MAX_LENGTH = 50 // Characters - max length for group titles
60
+
29
61
  export type Logger = {
30
62
  /**
31
63
  * Optional config name to show in CLI output
@@ -35,7 +67,7 @@ export type Logger = {
35
67
  consola?: ConsolaInstance
36
68
  on: EventEmitter<Events>['on']
37
69
  emit: EventEmitter<Events>['emit']
38
- writeLogs: () => Promise<string[]>
70
+ writeLogs: () => Promise<void>
39
71
  }
40
72
 
41
73
  type Props = {
@@ -79,9 +111,54 @@ export function createLogger({ logLevel = 3, name, consola: _consola }: Props =
79
111
  consola.info(pc.yellow(message))
80
112
  })
81
113
 
114
+ events.on('verbose', (message) => {
115
+ if (logLevel >= LogMapper.verbose) {
116
+ const formattedLogs = message.logs.join('\n')
117
+ consola.log(pc.dim(formattedLogs))
118
+ }
119
+
120
+ cachedLogs.add(message)
121
+ })
122
+
82
123
  events.on('debug', (message) => {
83
- if (message.logs.join('\n\n').length <= 100 && logLevel === LogMapper.debug) {
84
- console.log(message.logs.join('\n\n'))
124
+ const fullLog = message.logs.join('\n')
125
+
126
+ if (logLevel >= LogMapper.debug) {
127
+ // Handle plugin group markers in GitHub Actions
128
+ if (isGitHubActions()) {
129
+ if (message.pluginGroupMarker === 'start') {
130
+ // Start a new plugin group
131
+ const title = message.pluginName || 'Plugin'
132
+ console.log(startGroup(title))
133
+
134
+ return undefined // Don't log the marker itself
135
+ }
136
+ if (message.pluginGroupMarker === 'end') {
137
+ // End the plugin group
138
+ console.log(endGroup())
139
+
140
+ return undefined // Don't log the marker itself
141
+ }
142
+
143
+ // For setup/file operations that aren't plugin-specific, create individual groups
144
+ if (!message.pluginName && message.category && ['setup', 'file'].includes(message.category)) {
145
+ const firstLine = message.logs[0] || 'Debug Details'
146
+ const title = firstLine.length > DEBUG_LOG_TITLE_MAX_LENGTH ? `${firstLine.substring(0, DEBUG_LOG_TITLE_MAX_LENGTH)}...` : firstLine
147
+
148
+ console.log(startGroup(title))
149
+ console.log(pc.dim(fullLog))
150
+ console.log(endGroup())
151
+ } else {
152
+ // Plugin-specific logs are shown inline within their plugin group
153
+ // Non-categorized logs are shown inline
154
+ consola.log(pc.dim(fullLog))
155
+ }
156
+ } else {
157
+ // Non-CI environments - show all logs inline (except group markers)
158
+ if (!message.pluginGroupMarker) {
159
+ consola.log(pc.dim(fullLog))
160
+ }
161
+ }
85
162
  }
86
163
 
87
164
  cachedLogs.add(message)
@@ -118,15 +195,16 @@ export function createLogger({ logLevel = 3, name, consola: _consola }: Props =
118
195
  files[fileName] = []
119
196
  }
120
197
 
121
- files[fileName] = [...files[fileName], `[${log.date.toLocaleString()}]: ${log.logs.join('\n\n')}`]
198
+ if (log.logs.length) {
199
+ files[fileName] = [...files[fileName], `[${log.date.toLocaleString()}]: \n${log.logs.join('\n')}`]
200
+ }
122
201
  })
202
+
123
203
  await Promise.all(
124
204
  Object.entries(files).map(async ([fileName, logs]) => {
125
205
  return write(fileName, logs.join('\n'))
126
206
  }),
127
207
  )
128
-
129
- return Object.keys(files)
130
208
  },
131
209
  }
132
210
 
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Detect if running in a CI environment
3
+ * Note: Currently exported for potential future use in plugins or extensions
4
+ */
5
+ export function isCI(): boolean {
6
+ return !!(process.env.CI || process.env.CONTINUOUS_INTEGRATION || process.env.BUILD_NUMBER || process.env.RUN_ID)
7
+ }
8
+
9
+ /**
10
+ * Detect if running in GitHub Actions
11
+ */
12
+ export function isGitHubActions(): boolean {
13
+ return process.env.GITHUB_ACTIONS === 'true'
14
+ }
15
+
16
+ /**
17
+ * GitHub Actions group markers
18
+ */
19
+ const GITHUB_ACTIONS_GROUP_START = '::group::'
20
+ const GITHUB_ACTIONS_GROUP_END = '::endgroup::'
21
+
22
+ /**
23
+ * Create a collapsible group marker for GitHub Actions logs
24
+ */
25
+ export function startGroup(title: string): string {
26
+ if (isCI() && isGitHubActions()) {
27
+ return `${GITHUB_ACTIONS_GROUP_START}${title}`
28
+ }
29
+ return ''
30
+ }
31
+
32
+ /**
33
+ * End a collapsible group in GitHub Actions logs
34
+ */
35
+ export function endGroup(): string {
36
+ if (isGitHubActions()) {
37
+ return GITHUB_ACTIONS_GROUP_END
38
+ }
39
+ return ''
40
+ }
@@ -0,0 +1,15 @@
1
+ import { version as nodeVersion } from 'node:process'
2
+ import { version as KubbVersion } from '../../package.json'
3
+
4
+ /**
5
+ * Get diagnostic information for debugging
6
+ */
7
+ export function getDiagnosticInfo() {
8
+ return {
9
+ nodeVersion,
10
+ KubbVersion,
11
+ platform: process.platform,
12
+ arch: process.arch,
13
+ cwd: process.cwd(),
14
+ } as const
15
+ }