@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.
- package/dist/{agent-B8oJFhcN.js → agent-CLspGfSn.js} +4 -4
- package/dist/{agent-B8oJFhcN.js.map → agent-CLspGfSn.js.map} +1 -1
- package/dist/{agent-DtuTV_Qk.cjs → agent-RdNQgRXD.cjs} +4 -4
- package/dist/{agent-DtuTV_Qk.cjs.map → agent-RdNQgRXD.cjs.map} +1 -1
- package/dist/{constants-CYxk4aNm.js → constants-84a47qA-.js} +2 -6
- package/dist/constants-84a47qA-.js.map +1 -0
- package/dist/{constants-CAKUpLcQ.cjs → constants-BtmponZ3.cjs} +1 -11
- package/dist/constants-BtmponZ3.cjs.map +1 -0
- package/dist/{generate-BvaMqrBk.cjs → generate-DK1pLJMi.cjs} +3 -4
- package/dist/generate-DK1pLJMi.cjs.map +1 -0
- package/dist/{generate-CzTjeiji.js → generate-Db0pJmbG.js} +3 -4
- package/dist/{generate-CzTjeiji.js.map → generate-Db0pJmbG.js.map} +1 -1
- package/dist/index.cjs +9 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +9 -9
- package/dist/index.js.map +1 -1
- package/dist/{init-CaMeuE1-.js → init-DKMwmbmx.js} +2 -2
- package/dist/{init-CaMeuE1-.js.map → init-DKMwmbmx.js.map} +1 -1
- package/dist/{init-C59u3T68.cjs → init-D_MQBDVz.cjs} +2 -2
- package/dist/{init-C59u3T68.cjs.map → init-D_MQBDVz.cjs.map} +1 -1
- package/dist/{mcp-Ca3ZcpKB.js → mcp-DqNyN0cN.js} +3 -3
- package/dist/{mcp-Ca3ZcpKB.js.map → mcp-DqNyN0cN.js.map} +1 -1
- package/dist/{mcp-D4NMV9lk.cjs → mcp-DvEeDWlW.cjs} +3 -3
- package/dist/{mcp-D4NMV9lk.cjs.map → mcp-DvEeDWlW.cjs.map} +1 -1
- package/dist/package-C8u_WvqI.js +6 -0
- package/dist/package-C8u_WvqI.js.map +1 -0
- package/dist/{package-DQFf9DB2.cjs → package-CusjBrSS.cjs} +2 -2
- package/dist/package-CusjBrSS.cjs.map +1 -0
- package/dist/{run-DJxYClJV.js → run-BQ3Qj0xB.js} +4 -4
- package/dist/run-BQ3Qj0xB.js.map +1 -0
- package/dist/{run-BvXxelGR.js → run-BQzoaxjR.js} +3 -3
- package/dist/run-BQzoaxjR.js.map +1 -0
- package/dist/{run-Ca2h07rN.js → run-BabEDDqN.js} +130 -369
- package/dist/run-BabEDDqN.js.map +1 -0
- package/dist/{run-BFEK9md9.js → run-CGf0KEts.js} +3 -3
- package/dist/run-CGf0KEts.js.map +1 -0
- package/dist/{run-CK8Cvq6n.cjs → run-CJUmJcbC.cjs} +130 -369
- package/dist/run-CJUmJcbC.cjs.map +1 -0
- package/dist/{run-Bz9IFMWg.cjs → run-CkTpemme.cjs} +3 -3
- package/dist/run-CkTpemme.cjs.map +1 -0
- package/dist/{run-BQZyg7If.cjs → run-Cl4SrSob.cjs} +3 -3
- package/dist/run-Cl4SrSob.cjs.map +1 -0
- package/dist/{run-BFv6avA_.cjs → run-D-s2LdlW.cjs} +5 -5
- package/dist/run-D-s2LdlW.cjs.map +1 -0
- package/dist/{validate-B_wfDSHQ.cjs → validate-CkW_AKZp.cjs} +3 -3
- package/dist/{validate-B_wfDSHQ.cjs.map → validate-CkW_AKZp.cjs.map} +1 -1
- package/dist/{validate-BEEerg2-.js → validate-jRewvR0c.js} +3 -3
- package/dist/{validate-BEEerg2-.js.map → validate-jRewvR0c.js.map} +1 -1
- package/package.json +7 -7
- package/src/commands/generate.ts +2 -3
- package/src/constants.ts +0 -15
- package/src/index.ts +2 -2
- package/src/loggers/clackLogger.ts +4 -6
- package/src/loggers/githubActionsLogger.ts +3 -3
- package/src/loggers/plainLogger.ts +2 -3
- package/src/loggers/utils.ts +29 -28
- package/src/runners/agent/run.ts +2 -2
- package/src/runners/generate/run.ts +21 -17
- package/src/runners/generate/utils.ts +2 -2
- package/src/runners/mcp/run.ts +2 -2
- package/src/runners/validate/run.ts +2 -2
- package/dist/constants-CAKUpLcQ.cjs.map +0 -1
- package/dist/constants-CYxk4aNm.js.map +0 -1
- package/dist/generate-BvaMqrBk.cjs.map +0 -1
- package/dist/package-DQFf9DB2.cjs.map +0 -1
- package/dist/package-DUwUSFeL.js +0 -6
- package/dist/package-DUwUSFeL.js.map +0 -1
- package/dist/run-BFEK9md9.js.map +0 -1
- package/dist/run-BFv6avA_.cjs.map +0 -1
- package/dist/run-BQZyg7If.cjs.map +0 -1
- package/dist/run-BvXxelGR.js.map +0 -1
- package/dist/run-Bz9IFMWg.cjs.map +0 -1
- package/dist/run-CK8Cvq6n.cjs.map +0 -1
- package/dist/run-Ca2h07rN.js.map +0 -1
- package/dist/run-DJxYClJV.js.map +0 -1
- package/dist/telemetry-B80oJfxR.cjs +0 -280
- package/dist/telemetry-B80oJfxR.cjs.map +0 -1
- package/dist/telemetry-ueaMzs_c.js +0 -243
- package/dist/telemetry-ueaMzs_c.js.map +0 -1
- package/src/loggers/diagnostics.ts +0 -77
- package/src/reporters/cliReporter.ts +0 -89
- package/src/reporters/fileReporter.ts +0 -103
- package/src/reporters/jsonReporter.ts +0 -15
- package/src/reporters/report.ts +0 -84
- 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
|
-
})
|
package/src/reporters/report.ts
DELETED
|
@@ -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
|
-
}
|