@kubb/core 5.0.0-beta.38 → 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/src/constants.ts CHANGED
@@ -1,12 +1,22 @@
1
1
  /**
2
- * Base URL for the Kubb Studio web app.
2
+ * Number of file writes to batch in parallel during `flushPendingFiles`.
3
3
  */
4
- export const DEFAULT_STUDIO_URL = 'https://kubb.studio' as const
4
+ export const STREAM_FLUSH_EVERY = 50
5
5
 
6
6
  /**
7
- * Number of file writes to batch in parallel during `flushPendingFiles`.
7
+ * OpenTelemetry ingestion endpoint for anonymous usage telemetry.
8
8
  */
9
- export const STREAM_FLUSH_EVERY = 50
9
+ export const OTLP_ENDPOINT = 'https://otlp.kubb.dev' as const
10
+
11
+ /**
12
+ * Maximum number of █ characters in a plugin timing bar.
13
+ */
14
+ export const SUMMARY_MAX_BAR_LENGTH = 10 as const
15
+
16
+ /**
17
+ * Divides elapsed milliseconds into bar-length units (1 block per 100 ms).
18
+ */
19
+ export const SUMMARY_TIME_SCALE_DIVISOR = 100 as const
10
20
 
11
21
  /**
12
22
  * Number of schema/operation nodes to dispatch concurrently during generation.
@@ -27,19 +37,6 @@ export const HOOK_LISTENERS_PER_PLUGIN = 4
27
37
  */
28
38
  export const OPERATION_FILTER_TYPES: ReadonlySet<string> = new Set(['tag', 'operationId', 'path', 'method', 'contentType'])
29
39
 
30
- /**
31
- * Numeric log-level thresholds used internally to compare verbosity.
32
- *
33
- * Higher numbers are more verbose.
34
- */
35
- export const logLevel = {
36
- silent: Number.NEGATIVE_INFINITY,
37
- error: 0,
38
- warn: 1,
39
- info: 3,
40
- verbose: 4,
41
- } as const
42
-
43
40
  /**
44
41
  * Stable codes Kubb attaches to a `Diagnostic`. Each maps to a known failure mode
45
42
  * and stays stable so it can be referenced in tooling and (later) docs. Reference
@@ -97,10 +94,6 @@ export const diagnosticCode = {
97
94
  * without one.
98
95
  */
99
96
  adapterRequired: 'KUBB_ADAPTER_REQUIRED',
100
- /**
101
- * The `devtools` config is set to something other than an object.
102
- */
103
- devtoolsInvalid: 'KUBB_DEVTOOLS_INVALID',
104
97
  /**
105
98
  * A resolved output path escapes the output directory, which can stem from a path
106
99
  * traversal in the spec or a misconfigured `group.name`.
package/src/createKubb.ts CHANGED
@@ -2,15 +2,15 @@ import { resolve } from 'node:path'
2
2
  import type { PossiblePromise } from '@internals/utils'
3
3
  import { AsyncEventEmitter, BuildError } from '@internals/utils'
4
4
  import type { FileNode, InputMeta, OperationNode, SchemaNode } from '@kubb/ast'
5
- import { DEFAULT_STUDIO_URL, HOOK_LISTENERS_PER_PLUGIN } from './constants.ts'
5
+ import { HOOK_LISTENERS_PER_PLUGIN } from './constants.ts'
6
6
  import type { Adapter } from './createAdapter.ts'
7
- import { type Diagnostic, DiagnosticError, Diagnostics, isProblemDiagnostic, type ProblemDiagnostic, type UpdateDiagnostic } from './diagnostics.ts'
7
+ import { type Diagnostic, Diagnostics, type ProblemDiagnostic, type UpdateDiagnostic } from './diagnostics.ts'
8
8
  import { createStorage, type Storage } from './createStorage.ts'
9
9
  import type { GeneratorContext } from './defineGenerator.ts'
10
10
  import type { Middleware } from './defineMiddleware.ts'
11
11
  import type { Parser } from './defineParser.ts'
12
12
  import type { KubbPluginEndContext, KubbPluginSetupContext, KubbPluginStartContext, Plugin } from './definePlugin.ts'
13
- import type { ReporterName } from './createReporter.ts'
13
+ import type { Reporter, ReporterName } from './createReporter.ts'
14
14
 
15
15
  import { KubbDriver } from './KubbDriver.ts'
16
16
  import { fsStorage } from './storages/fsStorage.ts'
@@ -269,28 +269,6 @@ export type Config<TInput = Input> = {
269
269
  * @see {@link defineMiddleware} to create custom middleware.
270
270
  */
271
271
  middleware?: Array<Middleware>
272
- /**
273
- * Kubb Studio cloud integration settings.
274
- *
275
- * Kubb Studio (https://kubb.studio) is a web-based IDE for managing API specs and generated code.
276
- * Set to `true` to enable with default settings, or pass an object to customize the Studio URL.
277
- *
278
- * @default false // disabled by default
279
- * @example
280
- * ```ts
281
- * devtools: true // use default Kubb Studio
282
- * devtools: { studioUrl: 'https://my-studio.dev' } // custom Studio instance
283
- * ```
284
- */
285
- devtools?:
286
- | true
287
- | {
288
- /**
289
- * Override the Kubb Studio base URL.
290
- * @default 'https://kubb.studio'
291
- */
292
- studioUrl?: typeof DEFAULT_STUDIO_URL | (string & {})
293
- }
294
272
  /**
295
273
  * Lifecycle hooks that execute during or after the build process.
296
274
  *
@@ -323,21 +301,23 @@ export type Config<TInput = Input> = {
323
301
  done?: string | Array<string>
324
302
  }
325
303
  /**
326
- * Reporters that render the run's output, like Vitest. List one or more by name;
327
- * the CLI `--reporter` flag overrides this when set.
304
+ * The reporters available to the run, registered as instances. The host
305
+ * (the CLI via `--reporter`) selects which ones to trigger by `name` with {@link selectReporters}.
306
+ * `defineConfig` from the `kubb` package registers the built-in `cli`, `json`, and `file`
307
+ * reporters by default.
328
308
  *
329
309
  * - `cli` writes the end-of-run summary to the terminal.
330
310
  * - `json` writes a machine-readable report to stdout, for CI.
331
311
  * - `file` writes a debug log to `.kubb/<name>-<timestamp>.log`.
332
312
  *
333
- * @default ['cli']
334
- *
335
313
  * @example
336
314
  * ```ts
337
- * reporters: ['cli', 'file']
315
+ * import { cliReporter, jsonReporter } from '@kubb/core'
316
+ *
317
+ * reporters: [cliReporter, jsonReporter, myReporter]
338
318
  * ```
339
319
  */
340
- reporters?: Array<ReporterName>
320
+ reporters: Array<Reporter>
341
321
  }
342
322
 
343
323
  /**
@@ -345,7 +325,7 @@ export type Config<TInput = Input> = {
345
325
  *
346
326
  * `UserConfig` is what you pass to `defineConfig()`. It has optional `root`, `plugins`, `parsers`, and `adapter`
347
327
  * fields (which fall back to sensible defaults). All other Config options are available, including `output`, `input`,
348
- * `storage`, `middleware`, `devtools`, and `hooks`.
328
+ * `storage`, `middleware`, and `hooks`.
349
329
  *
350
330
  * @example
351
331
  * ```ts
@@ -356,7 +336,7 @@ export type Config<TInput = Input> = {
356
336
  * })
357
337
  * ```
358
338
  */
359
- export type UserConfig<TInput = Input> = Omit<Config<TInput>, 'root' | 'plugins' | 'parsers' | 'adapter' | 'storage'> & {
339
+ export type UserConfig<TInput = Input> = Omit<Config<TInput>, 'root' | 'plugins' | 'parsers' | 'adapter' | 'storage' | 'reporters'> & {
360
340
  /**
361
341
  * Project root directory, absolute or relative to the config file location.
362
342
  * @default process.cwd()
@@ -382,6 +362,12 @@ export type UserConfig<TInput = Input> = Omit<Config<TInput>, 'root' | 'plugins'
382
362
  * @default fsStorage()
383
363
  */
384
364
  storage?: Storage
365
+ /**
366
+ * Reporters available to the run. `defineConfig` registers the built-in `cli`, `json`, and
367
+ * `file` reporters when omitted.
368
+ * @default [cliReporter, jsonReporter, fileReporter] // applied by `defineConfig` from the `kubb` package
369
+ */
370
+ reporters?: Array<Reporter>
385
371
  }
386
372
 
387
373
  declare global {
@@ -877,25 +863,11 @@ function resolveConfig(userConfig: UserConfig): Config {
877
863
  ...userConfig.output,
878
864
  },
879
865
  storage: userConfig.storage ?? fsStorage(),
880
- devtools: userConfig.devtools
881
- ? {
882
- studioUrl: DEFAULT_STUDIO_URL,
883
- ...(typeof userConfig.devtools === 'boolean' ? {} : userConfig.devtools),
884
- }
885
- : undefined,
866
+ reporters: userConfig.reporters ?? [],
886
867
  plugins: userConfig.plugins ?? [],
887
868
  }
888
869
  }
889
870
 
890
- /**
891
- * Type guard to check if a given config has an `input.path`.
892
- */
893
- export function isInputPath(config: UserConfig | undefined): config is UserConfig<InputPath> & { input: InputPath }
894
- export function isInputPath(config: Config | undefined): config is Config<InputPath> & { input: InputPath }
895
- export function isInputPath(config: Config | UserConfig | undefined): config is (Config<InputPath> | UserConfig<InputPath>) & { input: InputPath } {
896
- return typeof config?.input === 'object' && config.input !== null && 'path' in config.input
897
- }
898
-
899
871
  type CreateKubbOptions = {
900
872
  hooks?: AsyncEventEmitter<KubbHooks>
901
873
  }
@@ -973,9 +945,9 @@ export class Kubb {
973
945
  const out = await this.safeBuild()
974
946
  if (Diagnostics.hasError(out.diagnostics)) {
975
947
  const errors = out.diagnostics
976
- .filter(isProblemDiagnostic)
948
+ .filter(Diagnostics.isProblem)
977
949
  .filter((diagnostic) => diagnostic.severity === 'error')
978
- .map((diagnostic) => diagnostic.cause ?? new DiagnosticError(diagnostic))
950
+ .map((diagnostic) => diagnostic.cause ?? new Diagnostics.Error(diagnostic))
979
951
  throw new BuildError(`Build failed with ${errors.length} ${errors.length === 1 ? 'error' : 'errors'}`, { errors })
980
952
  }
981
953
  return out
@@ -1,4 +1,4 @@
1
- import type { logLevel } from './constants.ts'
1
+ import type { logLevel } from './defineLogger.ts'
2
2
  import type { Config } from './createKubb.ts'
3
3
  import type { Diagnostic } from './diagnostics.ts'
4
4
 
@@ -45,7 +45,7 @@ export type ReporterContext = {
45
45
 
46
46
  /**
47
47
  * Host-facing reporter, as installed onto a run. Unlike a Logger (the live TUI view), a reporter
48
- * never sees the event emitter. `report` runs once per config; `flush`, when present, runs once
48
+ * never sees the event emitter. `report` runs once per config; `drain`, when present, runs once
49
49
  * after the last config.
50
50
  */
51
51
  export type Reporter = {
@@ -61,23 +61,23 @@ export type Reporter = {
61
61
  * Optional finalizer called once after the run's last config. The host wires it to
62
62
  * `kubb:lifecycle:end`. {@link createReporter} closes it over the reports `report` returned.
63
63
  */
64
- flush?: (context: ReporterContext) => void | Promise<void>
64
+ drain?: (context: ReporterContext) => void | Promise<void>
65
65
  }
66
66
 
67
67
  /**
68
68
  * Reporter definition passed to {@link createReporter}. `report` returns the value to collect for
69
- * this config (e.g. a built report), and the optional `flush` receives the collected reports to
69
+ * this config (e.g. a built report), and the optional `drain` receives the collected reports to
70
70
  * emit as one document. `T` is inferred from `report`'s return type.
71
71
  */
72
72
  export type UserReporter<T = void> = {
73
73
  name: string
74
74
  report: (result: GenerationResult, context: ReporterContext) => T | Promise<T>
75
- flush?: (context: ReporterContext, reports: Array<T>) => void | Promise<void>
75
+ drain?: (context: ReporterContext, reports: Array<T>) => void | Promise<void>
76
76
  }
77
77
 
78
78
  /**
79
- * Defines a reporter. When the definition has a `flush`, the returned reporter buffers each value
80
- * `report` returns and hands the array to `flush` once, then clears it. Without a `flush`, nothing
79
+ * Defines a reporter. When the definition has a `drain`, the returned reporter buffers each value
80
+ * `report` returns and hands the array to `drain` once, then clears it. Without a `drain`, nothing
81
81
  * is buffered. Wiring the reporter onto the run's events is the host's job, so the reporter only
82
82
  * ever deals with a {@link GenerationResult}.
83
83
  *
@@ -90,16 +90,21 @@ export type UserReporter<T = void> = {
90
90
  * report(result) {
91
91
  * return { status: Diagnostics.hasError(result.diagnostics) ? 'failed' : 'success', diagnostics: result.diagnostics }
92
92
  * },
93
- * flush(context, reports) {
93
+ * drain(context, reports) {
94
94
  * process.stdout.write(`${JSON.stringify(reports, null, 2)}\n`)
95
95
  * },
96
96
  * })
97
97
  * ```
98
98
  */
99
99
  export function createReporter<T = void>(reporter: UserReporter<T>): Reporter {
100
- const flush = reporter.flush
101
- if (!flush) {
102
- return { name: reporter.name, report: reporter.report }
100
+ const drain = reporter.drain
101
+ if (!drain) {
102
+ return {
103
+ name: reporter.name,
104
+ async report(result, context) {
105
+ await reporter.report(result, context)
106
+ },
107
+ }
103
108
  }
104
109
 
105
110
  const reports: Array<T> = []
@@ -109,9 +114,34 @@ export function createReporter<T = void>(reporter: UserReporter<T>): Reporter {
109
114
  async report(result, context) {
110
115
  reports.push(await reporter.report(result, context))
111
116
  },
112
- async flush(context) {
113
- await flush(context, reports)
117
+ async drain(context) {
118
+ await drain(context, reports)
114
119
  reports.length = 0
115
120
  },
116
121
  }
117
122
  }
123
+
124
+ /**
125
+ * Picks the reporters whose `name` matches one of `names`, in the order the names are given.
126
+ * The config carries every available reporter, and the host selects which to activate by name
127
+ * (the CLI maps `--reporter` to this). Duplicate names and names without a matching reporter are
128
+ * skipped.
129
+ */
130
+ export function selectReporters(reporters: ReadonlyArray<Reporter>, names: ReadonlyArray<string>): Array<Reporter> {
131
+ const seen = new Set<string>()
132
+ const selected: Array<Reporter> = []
133
+
134
+ for (const name of names) {
135
+ if (seen.has(name)) {
136
+ continue
137
+ }
138
+ seen.add(name)
139
+
140
+ const reporter = reporters.find((candidate) => candidate.name === name)
141
+ if (reporter) {
142
+ selected.push(reporter)
143
+ }
144
+ }
145
+
146
+ return selected
147
+ }
@@ -6,7 +6,7 @@ import type { KubbHooks } from './types.ts'
6
6
  import type { KubbDriver } from './KubbDriver.ts'
7
7
  import type { Plugin, PluginFactoryOptions } from './definePlugin.ts'
8
8
  import type { Resolver } from './defineResolver.ts'
9
- import type { Config, DevtoolsOptions } from './types.ts'
9
+ import type { Config } from './types.ts'
10
10
 
11
11
  /**
12
12
  * Context object passed to generator `schema`, `operation`, and `operations` methods.
@@ -67,7 +67,7 @@ export type GeneratorContext<TOptions extends PluginFactoryOptions = PluginFacto
67
67
  * Report a warning. Collected as a `warning` diagnostic attributed to the current
68
68
  * plugin. It surfaces in the run summary but does not fail the build. For a structured
69
69
  * diagnostic with a code and source location, use `Diagnostics.report` or throw a
70
- * `DiagnosticError` directly.
70
+ * `Diagnostics.Error` directly.
71
71
  */
72
72
  warn: (message: string) => void
73
73
  /**
@@ -80,10 +80,6 @@ export type GeneratorContext<TOptions extends PluginFactoryOptions = PluginFacto
80
80
  * the current plugin.
81
81
  */
82
82
  info: (message: string) => void
83
- /**
84
- * Open the current input node in Kubb Studio.
85
- */
86
- openInStudio: (options?: DevtoolsOptions) => Promise<void>
87
83
  /**
88
84
  * The configured adapter instance.
89
85
  */
@@ -1,7 +1,19 @@
1
1
  import type { AsyncEventEmitter } from '@internals/utils'
2
- import type { logLevel } from './constants.ts'
3
2
  import type { KubbHooks } from './types.ts'
4
3
 
4
+ /**
5
+ * Numeric log-level thresholds used internally to compare verbosity.
6
+ *
7
+ * Higher numbers are more verbose.
8
+ */
9
+ export const logLevel = {
10
+ silent: Number.NEGATIVE_INFINITY,
11
+ error: 0,
12
+ warn: 1,
13
+ info: 3,
14
+ verbose: 4,
15
+ } as const
16
+
5
17
  /**
6
18
  * Options accepted by a logger's `install` callback.
7
19
  */
@@ -2,8 +2,7 @@ import path from 'node:path'
2
2
  import { camelCase, pascalCase } from '@internals/utils'
3
3
  import type { FileNode, InputMeta, Node, OperationNode, SchemaNode } from '@kubb/ast'
4
4
  import { createFile, isOperationNode, isSchemaNode } from '@kubb/ast'
5
- import { diagnosticCode } from './constants.ts'
6
- import { DiagnosticError } from './diagnostics.ts'
5
+ import { Diagnostics } from './diagnostics.ts'
7
6
  import type { PluginFactoryOptions } from './definePlugin.ts'
8
7
  import { getMode } from './definePlugin.ts'
9
8
  import type { Config, Group, Output } from './types.ts'
@@ -456,8 +455,8 @@ export function defaultResolvePath({ baseName, pathMode, tag, path: groupPath }:
456
455
  const outputDir = path.resolve(root, output.path)
457
456
  const outputDirWithSep = outputDir.endsWith(path.sep) ? outputDir : `${outputDir}${path.sep}`
458
457
  if (result !== outputDir && !result.startsWith(outputDirWithSep)) {
459
- throw new DiagnosticError({
460
- code: diagnosticCode.pathTraversal,
458
+ throw new Diagnostics.Error({
459
+ code: Diagnostics.code.pathTraversal,
461
460
  severity: 'error',
462
461
  message: `Resolved path "${result}" is outside the output directory "${outputDir}".`,
463
462
  help: 'This can stem from a path traversal in the OpenAPI specification or a misconfigured `group.name` function. Keep generated paths within the output directory.',