@kubb/cli 5.0.0-beta.37 → 5.0.0-beta.39

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 (85) hide show
  1. package/dist/{agent-B8oJFhcN.js → agent-CLspGfSn.js} +4 -4
  2. package/dist/{agent-B8oJFhcN.js.map → agent-CLspGfSn.js.map} +1 -1
  3. package/dist/{agent-DtuTV_Qk.cjs → agent-RdNQgRXD.cjs} +4 -4
  4. package/dist/{agent-DtuTV_Qk.cjs.map → agent-RdNQgRXD.cjs.map} +1 -1
  5. package/dist/{constants-CYxk4aNm.js → constants-84a47qA-.js} +2 -6
  6. package/dist/constants-84a47qA-.js.map +1 -0
  7. package/dist/{constants-CAKUpLcQ.cjs → constants-BtmponZ3.cjs} +1 -11
  8. package/dist/constants-BtmponZ3.cjs.map +1 -0
  9. package/dist/{generate-BvaMqrBk.cjs → generate-DK1pLJMi.cjs} +3 -4
  10. package/dist/generate-DK1pLJMi.cjs.map +1 -0
  11. package/dist/{generate-CzTjeiji.js → generate-Db0pJmbG.js} +3 -4
  12. package/dist/{generate-CzTjeiji.js.map → generate-Db0pJmbG.js.map} +1 -1
  13. package/dist/index.cjs +9 -9
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.js +9 -9
  16. package/dist/index.js.map +1 -1
  17. package/dist/{init-CaMeuE1-.js → init-DKMwmbmx.js} +2 -2
  18. package/dist/{init-CaMeuE1-.js.map → init-DKMwmbmx.js.map} +1 -1
  19. package/dist/{init-C59u3T68.cjs → init-D_MQBDVz.cjs} +2 -2
  20. package/dist/{init-C59u3T68.cjs.map → init-D_MQBDVz.cjs.map} +1 -1
  21. package/dist/{mcp-Ca3ZcpKB.js → mcp-DqNyN0cN.js} +3 -3
  22. package/dist/{mcp-Ca3ZcpKB.js.map → mcp-DqNyN0cN.js.map} +1 -1
  23. package/dist/{mcp-D4NMV9lk.cjs → mcp-DvEeDWlW.cjs} +3 -3
  24. package/dist/{mcp-D4NMV9lk.cjs.map → mcp-DvEeDWlW.cjs.map} +1 -1
  25. package/dist/package-C8u_WvqI.js +6 -0
  26. package/dist/package-C8u_WvqI.js.map +1 -0
  27. package/dist/{package-DQFf9DB2.cjs → package-CusjBrSS.cjs} +2 -2
  28. package/dist/package-CusjBrSS.cjs.map +1 -0
  29. package/dist/{run-DJxYClJV.js → run-BQ3Qj0xB.js} +4 -4
  30. package/dist/run-BQ3Qj0xB.js.map +1 -0
  31. package/dist/{run-BvXxelGR.js → run-BQzoaxjR.js} +3 -3
  32. package/dist/run-BQzoaxjR.js.map +1 -0
  33. package/dist/{run-Ca2h07rN.js → run-BabEDDqN.js} +130 -369
  34. package/dist/run-BabEDDqN.js.map +1 -0
  35. package/dist/{run-BFEK9md9.js → run-CGf0KEts.js} +3 -3
  36. package/dist/run-CGf0KEts.js.map +1 -0
  37. package/dist/{run-CK8Cvq6n.cjs → run-CJUmJcbC.cjs} +130 -369
  38. package/dist/run-CJUmJcbC.cjs.map +1 -0
  39. package/dist/{run-Bz9IFMWg.cjs → run-CkTpemme.cjs} +3 -3
  40. package/dist/run-CkTpemme.cjs.map +1 -0
  41. package/dist/{run-BQZyg7If.cjs → run-Cl4SrSob.cjs} +3 -3
  42. package/dist/run-Cl4SrSob.cjs.map +1 -0
  43. package/dist/{run-BFv6avA_.cjs → run-D-s2LdlW.cjs} +5 -5
  44. package/dist/run-D-s2LdlW.cjs.map +1 -0
  45. package/dist/{validate-B_wfDSHQ.cjs → validate-CkW_AKZp.cjs} +3 -3
  46. package/dist/{validate-B_wfDSHQ.cjs.map → validate-CkW_AKZp.cjs.map} +1 -1
  47. package/dist/{validate-BEEerg2-.js → validate-jRewvR0c.js} +3 -3
  48. package/dist/{validate-BEEerg2-.js.map → validate-jRewvR0c.js.map} +1 -1
  49. package/package.json +7 -7
  50. package/src/commands/generate.ts +2 -3
  51. package/src/constants.ts +0 -15
  52. package/src/index.ts +2 -2
  53. package/src/loggers/clackLogger.ts +4 -6
  54. package/src/loggers/githubActionsLogger.ts +3 -3
  55. package/src/loggers/plainLogger.ts +2 -3
  56. package/src/loggers/utils.ts +29 -28
  57. package/src/runners/agent/run.ts +2 -2
  58. package/src/runners/generate/run.ts +21 -17
  59. package/src/runners/generate/utils.ts +2 -2
  60. package/src/runners/mcp/run.ts +2 -2
  61. package/src/runners/validate/run.ts +2 -2
  62. package/dist/constants-CAKUpLcQ.cjs.map +0 -1
  63. package/dist/constants-CYxk4aNm.js.map +0 -1
  64. package/dist/generate-BvaMqrBk.cjs.map +0 -1
  65. package/dist/package-DQFf9DB2.cjs.map +0 -1
  66. package/dist/package-DUwUSFeL.js +0 -6
  67. package/dist/package-DUwUSFeL.js.map +0 -1
  68. package/dist/run-BFEK9md9.js.map +0 -1
  69. package/dist/run-BFv6avA_.cjs.map +0 -1
  70. package/dist/run-BQZyg7If.cjs.map +0 -1
  71. package/dist/run-BvXxelGR.js.map +0 -1
  72. package/dist/run-Bz9IFMWg.cjs.map +0 -1
  73. package/dist/run-CK8Cvq6n.cjs.map +0 -1
  74. package/dist/run-Ca2h07rN.js.map +0 -1
  75. package/dist/run-DJxYClJV.js.map +0 -1
  76. package/dist/telemetry-B80oJfxR.cjs +0 -280
  77. package/dist/telemetry-B80oJfxR.cjs.map +0 -1
  78. package/dist/telemetry-ueaMzs_c.js +0 -243
  79. package/dist/telemetry-ueaMzs_c.js.map +0 -1
  80. package/src/loggers/diagnostics.ts +0 -77
  81. package/src/reporters/cliReporter.ts +0 -89
  82. package/src/reporters/fileReporter.ts +0 -103
  83. package/src/reporters/jsonReporter.ts +0 -15
  84. package/src/reporters/report.ts +0 -84
  85. package/src/telemetry.ts +0 -280
@@ -1,89 +0,0 @@
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
- })
@@ -1,103 +0,0 @@
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
- })
@@ -1,15 +0,0 @@
1
- import process from 'node:process'
2
- import { createReporter } from '@kubb/core'
3
- import { buildReport } from './report.ts'
4
-
5
- /**
6
- * The `json` reporter. Writes the {@link Report} for each config to stdout as JSON, for CI tooling.
7
- * The terminal reporter is suppressed while this is active so stdout stays valid JSON.
8
- */
9
- export const jsonReporter = createReporter({
10
- name: 'json',
11
- report(result) {
12
- const report = buildReport(result)
13
- process.stdout.write(`${JSON.stringify(report, null, 2)}\n`)
14
- },
15
- })
@@ -1,84 +0,0 @@
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/telemetry.ts DELETED
@@ -1,280 +0,0 @@
1
- import { randomBytes } from 'node:crypto'
2
- import os from 'node:os'
3
- import process from 'node:process'
4
- import { executeIfOnline, isCIEnvironment } from '@internals/utils'
5
- import { OTLP_ENDPOINT } from './constants.ts'
6
-
7
- // OpenTelemetry OTLP JSON types
8
- // https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/trace/v1/trace.proto
9
- // https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/common/v1/common.proto
10
-
11
- type OtlpStringValue = { stringValue: string }
12
- type OtlpBoolValue = { boolValue: boolean }
13
- type OtlpIntValue = { intValue: number }
14
- type OtlpDoubleValue = { doubleValue: number }
15
- type OtlpBytesValue = { bytesValue: string }
16
- type OtlpArrayValue = { arrayValue: { values: Array<OtlpAnyValue> } }
17
- type OtlpKvListValue = { kvlistValue: { values: Array<OtlpKeyValue> } }
18
-
19
- type OtlpAnyValue = OtlpStringValue | OtlpBoolValue | OtlpIntValue | OtlpDoubleValue | OtlpBytesValue | OtlpArrayValue | OtlpKvListValue
20
-
21
- type OtlpKeyValue = {
22
- key: string
23
- value: OtlpAnyValue
24
- }
25
-
26
- type OtlpResource = {
27
- attributes: Array<OtlpKeyValue>
28
- droppedAttributesCount?: number
29
- }
30
-
31
- type OtlpInstrumentationScope = {
32
- name: string
33
- version?: string
34
- attributes?: Array<OtlpKeyValue>
35
- droppedAttributesCount?: number
36
- }
37
-
38
- /** https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/trace/v1/trace.proto#L103 */
39
- type OtlpSpanKind = 0 | 1 | 2 | 3 | 4 | 5
40
-
41
- /** 0 = STATUS_CODE_UNSET, 1 = STATUS_CODE_OK, 2 = STATUS_CODE_ERROR */
42
- type OtlpStatusCode = 0 | 1 | 2
43
-
44
- type OtlpStatus = {
45
- code: OtlpStatusCode
46
- message?: string
47
- }
48
-
49
- type OtlpSpan = {
50
- traceId: string
51
- spanId: string
52
- traceState?: string
53
- parentSpanId?: string
54
- name: string
55
- kind: OtlpSpanKind
56
- startTimeUnixNano: string
57
- endTimeUnixNano: string
58
- attributes?: Array<OtlpKeyValue>
59
- droppedAttributesCount?: number
60
- events?: Array<OtlpSpanEvent>
61
- droppedEventsCount?: number
62
- links?: Array<OtlpSpanLink>
63
- droppedLinksCount?: number
64
- status?: OtlpStatus
65
- }
66
-
67
- type OtlpSpanEvent = {
68
- timeUnixNano: string
69
- name: string
70
- attributes?: Array<OtlpKeyValue>
71
- droppedAttributesCount?: number
72
- }
73
-
74
- type OtlpSpanLink = {
75
- traceId: string
76
- spanId: string
77
- traceState?: string
78
- attributes?: Array<OtlpKeyValue>
79
- droppedAttributesCount?: number
80
- }
81
-
82
- type OtlpScopeSpans = {
83
- scope: OtlpInstrumentationScope
84
- spans: Array<OtlpSpan>
85
- schemaUrl?: string
86
- }
87
-
88
- type OtlpResourceSpans = {
89
- resource: OtlpResource
90
- scopeSpans: Array<OtlpScopeSpans>
91
- schemaUrl?: string
92
- }
93
-
94
- /** Root payload sent to POST /v1/traces */
95
- type OtlpExportTraceServiceRequest = {
96
- resourceSpans: Array<OtlpResourceSpans>
97
- }
98
-
99
- /**
100
- * Anonymous plugin name and options snapshot sent with each telemetry event.
101
- */
102
- export type TelemetryPlugin = {
103
- /**
104
- * Plugin name as registered in the Kubb config, e.g. `'@kubb/plugin-ts'`.
105
- */
106
- name: string
107
- /**
108
- * anonymized plugin options snapshot, values are included but cannot be traced back to the user.
109
- */
110
- options: Record<string, unknown>
111
- }
112
-
113
- type TelemetryEvent = {
114
- command: string
115
- kubbVersion: string
116
- nodeVersion: string
117
- platform: string
118
- ci: boolean
119
- plugins: Array<TelemetryPlugin>
120
- duration: number
121
- filesCreated: number
122
- status: 'success' | 'failed'
123
- }
124
-
125
- /**
126
- * Returns `true` when the current process runs inside a CI environment.
127
- */
128
- export function isCi(): boolean {
129
- return isCIEnvironment()
130
- }
131
-
132
- /**
133
- * Returns `true` when telemetry is disabled via `DO_NOT_TRACK` or `KUBB_DISABLE_TELEMETRY`.
134
- */
135
- export function isTelemetryDisabled(): boolean {
136
- return (
137
- process.env['DO_NOT_TRACK'] === '1' ||
138
- process.env['DO_NOT_TRACK'] === 'true' ||
139
- process.env['KUBB_DISABLE_TELEMETRY'] === '1' ||
140
- process.env['KUBB_DISABLE_TELEMETRY'] === 'true'
141
- )
142
- }
143
-
144
- /**
145
- * Convert a TelemetryEvent into an OTLP-compatible JSON trace payload.
146
- * See https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter/
147
- */
148
- export function buildOtlpPayload(event: TelemetryEvent): OtlpExportTraceServiceRequest {
149
- const traceId = randomBytes(16).toString('hex')
150
- const spanId = randomBytes(8).toString('hex')
151
- const endTimeNs = BigInt(Date.now()) * 1_000_000n
152
- const startTimeNs = endTimeNs - BigInt(event.duration) * 1_000_000n
153
-
154
- const attributes: Array<OtlpKeyValue> = [
155
- { key: 'kubb.command', value: { stringValue: event.command } },
156
- { key: 'kubb.version', value: { stringValue: event.kubbVersion } },
157
- { key: 'kubb.node_version', value: { stringValue: event.nodeVersion } },
158
- { key: 'kubb.platform', value: { stringValue: event.platform } },
159
- { key: 'kubb.ci', value: { boolValue: event.ci } },
160
- { key: 'kubb.files_created', value: { intValue: event.filesCreated } },
161
- { key: 'kubb.status', value: { stringValue: event.status } },
162
- {
163
- key: 'kubb.plugins',
164
- value: {
165
- arrayValue: {
166
- values: event.plugins.map(
167
- (p): OtlpKvListValue => ({
168
- kvlistValue: {
169
- values: [
170
- { key: 'name', value: { stringValue: p.name } },
171
- {
172
- key: 'options',
173
- value: {
174
- stringValue: JSON.stringify({
175
- ...p.options,
176
- usedEnumNames: undefined,
177
- }),
178
- },
179
- },
180
- ],
181
- },
182
- }),
183
- ),
184
- },
185
- },
186
- },
187
- ]
188
-
189
- return {
190
- resourceSpans: [
191
- {
192
- resource: {
193
- attributes: [
194
- { key: 'service.name', value: { stringValue: 'kubb-cli' } },
195
- {
196
- key: 'service.version',
197
- value: { stringValue: event.kubbVersion },
198
- },
199
- { key: 'telemetry.sdk.language', value: { stringValue: 'nodejs' } },
200
- ],
201
- },
202
- scopeSpans: [
203
- {
204
- scope: { name: 'kubb-cli', version: event.kubbVersion },
205
- spans: [
206
- {
207
- traceId,
208
- spanId,
209
- name: event.command,
210
- kind: 1 satisfies OtlpSpanKind,
211
- startTimeUnixNano: String(startTimeNs),
212
- endTimeUnixNano: String(endTimeNs),
213
- attributes,
214
- status: {
215
- code: (event.status === 'success' ? 1 : 2) satisfies OtlpStatusCode,
216
- },
217
- },
218
- ],
219
- },
220
- ],
221
- },
222
- ],
223
- }
224
- }
225
-
226
- /**
227
- * Send an anonymous telemetry event to the Kubb OTLP endpoint.
228
- * Respects DO_NOT_TRACK and KUBB_DISABLE_TELEMETRY environment variables.
229
- * Fails silently to never interrupt the generation process.
230
- */
231
- export async function sendTelemetry(event: TelemetryEvent): Promise<void> {
232
- if (isTelemetryDisabled()) {
233
- return
234
- }
235
-
236
- await executeIfOnline(async () => {
237
- try {
238
- await fetch(`${OTLP_ENDPOINT}/v1/traces`, {
239
- method: 'POST',
240
- headers: {
241
- 'Content-Type': 'application/json',
242
- 'Kubb-Telemetry-Version': '1',
243
- 'Kubb-Telemetry-Source': 'kubb-cli',
244
- },
245
- body: JSON.stringify(buildOtlpPayload(event)),
246
- signal: AbortSignal.timeout(5_000),
247
- })
248
- } catch (_e) {
249
- // Fail silently, telemetry must never break the CLI
250
- }
251
- })
252
- }
253
-
254
- /**
255
- * Build an anonymous telemetry payload from a completed generation run.
256
- * No file paths, OpenAPI specs, or secrets are included.
257
- */
258
- export function buildTelemetryEvent(options: {
259
- command: 'generate' | 'mcp' | 'validate' | 'agent'
260
- kubbVersion: string
261
- plugins?: Array<TelemetryPlugin>
262
- hrStart: [number, number]
263
- filesCreated?: number
264
- status: 'success' | 'failed'
265
- }): TelemetryEvent {
266
- const [seconds, nanoseconds] = process.hrtime(options.hrStart)
267
- const duration = Math.round(seconds * 1000 + nanoseconds / 1e6)
268
-
269
- return {
270
- command: options.command,
271
- kubbVersion: options.kubbVersion,
272
- nodeVersion: process.versions.node.split('.')[0] as string,
273
- platform: os.platform(),
274
- ci: isCi(),
275
- plugins: options.plugins ?? [],
276
- duration,
277
- filesCreated: options.filesCreated ?? 0,
278
- status: options.status,
279
- }
280
- }