@kubb/core 5.0.0-beta.35 → 5.0.0-beta.37

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.
@@ -64,15 +64,20 @@ export type GeneratorContext<TOptions extends PluginFactoryOptions = PluginFacto
64
64
  */
65
65
  transformer: Visitor | undefined
66
66
  /**
67
- * Emit a warning.
67
+ * Report a warning. Collected as a `warning` diagnostic attributed to the current
68
+ * plugin. It surfaces in the run summary but does not fail the build. For a structured
69
+ * diagnostic with a code and source location, use `Diagnostics.report` or throw a
70
+ * `DiagnosticError` directly.
68
71
  */
69
72
  warn: (message: string) => void
70
73
  /**
71
- * Emit an error.
74
+ * Report an error. Collected as an `error` diagnostic attributed to the current
75
+ * plugin, which fails the build.
72
76
  */
73
77
  error: (error: string | Error) => void
74
78
  /**
75
- * Emit an info message.
79
+ * Report an informational message. Collected as an `info` diagnostic attributed to
80
+ * the current plugin.
76
81
  */
77
82
  info: (message: string) => void
78
83
  /**
@@ -84,7 +89,7 @@ export type GeneratorContext<TOptions extends PluginFactoryOptions = PluginFacto
84
89
  */
85
90
  adapter: Adapter
86
91
  /**
87
- * Document metadata from the adapter title, version, base URL, and pre-computed
92
+ * Document metadata from the adapter: title, version, base URL, and pre-computed
88
93
  * schema index fields (`circularNames`, `enumNames`).
89
94
  */
90
95
  meta: InputMeta
@@ -131,8 +136,7 @@ export type Generator<TOptions extends PluginFactoryOptions = PluginFactoryOptio
131
136
  *
132
137
  * Generators that only return `Array<FileNode>` or `void` do not need to set this.
133
138
  *
134
- * Set `renderer: null` to explicitly opt out of rendering even when the parent plugin
135
- * declares a `renderer` (overrides the plugin-level fallback).
139
+ * Leave it unset or set `renderer: null` to opt out of rendering.
136
140
  *
137
141
  * @example
138
142
  * ```ts
@@ -172,7 +176,7 @@ export type Generator<TOptions extends PluginFactoryOptions = PluginFactoryOptio
172
176
  * The returned object is the input as-is, but with `this` types preserved so
173
177
  * `schema`/`operation`/`operations` methods are correctly typed against the
174
178
  * plugin's `PluginFactoryOptions`. Renderer elements and `FileNode[]` returns
175
- * are both handled by the runtime pick whichever style fits.
179
+ * are both handled by the runtime, so pick whichever style fits.
176
180
  *
177
181
  * @example JSX-based schema generator
178
182
  * ```tsx
@@ -6,7 +6,7 @@ type PrintOptions = {
6
6
 
7
7
  /**
8
8
  * Converts a resolved {@link FileNode} into the final source string that gets
9
- * written to disk. Kubb ships with TypeScript and TSX parsers; add your own
9
+ * written to disk. Kubb ships with TypeScript and TSX parsers. Add your own
10
10
  * for new file types (JSON, Markdown, ...).
11
11
  */
12
12
  export type Parser<TMeta extends object = object, TNode = unknown> = {
@@ -1,6 +1,5 @@
1
1
  import { extname } from 'node:path'
2
2
  import type { FileNode, HttpMethod, UserFileNode, Visitor } from '@kubb/ast'
3
- import type { RendererFactory } from './createRenderer.ts'
4
3
  import type { Generator } from './defineGenerator.ts'
5
4
  import type { BannerMeta, Resolver } from './defineResolver.ts'
6
5
  import type { Config, KubbHooks } from './types.ts'
@@ -29,8 +28,8 @@ export type Output<_TOptions = unknown> = {
29
28
  * lint disables, or `@ts-nocheck` directives.
30
29
  *
31
30
  * A string is applied to every file (including barrel and aggregation re-export files).
32
- * Pass a function to compute the banner from the file's `BannerMeta` document metadata
33
- * plus per-file context (`isBarrel`, `isAggregation`, `filePath`, `baseName`) so you can
31
+ * Pass a function to compute the banner from the file's `BannerMeta` document metadata
32
+ * plus per-file context (`isBarrel`, `isAggregation`, `filePath`, `baseName`), so you can
34
33
  * skip the banner on specific files.
35
34
  *
36
35
  * @example Add a directive to source files but not re-export files
@@ -58,8 +57,8 @@ export type Output<_TOptions = unknown> = {
58
57
  export type Group = {
59
58
  /**
60
59
  * Property used to assign each operation to a group.
61
- * - `'tag'` uses the first tag (`operation.getTags().at(0)?.name`).
62
- * - `'path'` uses the first segment of the operation's URL.
60
+ * - `'tag'` uses the first tag (`operation.getTags().at(0)?.name`).
61
+ * - `'path'` uses the first segment of the operation's URL.
63
62
  */
64
63
  type: 'tag' | 'path'
65
64
  /**
@@ -112,11 +111,6 @@ type ByMethod = {
112
111
  */
113
112
  pattern: HttpMethod | RegExp
114
113
  }
115
- // TODO implement as alternative for ByMethod
116
- // type ByMethods = {
117
- // type: 'methods'
118
- // pattern: Array<HttpMethod>
119
- // }
120
114
 
121
115
  type BySchemaName = {
122
116
  /**
@@ -175,7 +169,7 @@ export type Include = ByTag | ByOperationId | ByPath | ByMethod | ByContentType
175
169
  * options are merged on top of the plugin defaults for that operation only.
176
170
  * Useful for "this one tag goes to a different folder" rules.
177
171
  *
178
- * Entries are evaluated top to bottom; the first matching entry wins.
172
+ * Entries are evaluated top to bottom. The first matching entry wins.
179
173
  *
180
174
  * @example
181
175
  * ```ts
@@ -194,8 +188,7 @@ export type Include = ByTag | ByOperationId | ByPath | ByMethod | ByContentType
194
188
  * ```
195
189
  */
196
190
  export type Override<TOptions> = (ByTag | ByOperationId | ByPath | ByMethod | BySchemaName | ByContentType) & {
197
- //TODO should be options: Omit<Partial<TOptions>, 'override'>
198
- options: Partial<TOptions>
191
+ options: Omit<Partial<TOptions>, 'override'>
199
192
  }
200
193
 
201
194
  export type PluginFactoryOptions<
@@ -242,10 +235,6 @@ export type KubbPluginSetupContext<TFactory extends PluginFactoryOptions = Plugi
242
235
  * Set the AST transformer to pre-process nodes before they reach generators.
243
236
  */
244
237
  setTransformer(visitor: Visitor): void
245
- /**
246
- * Set the renderer factory to process JSX elements from generators.
247
- */
248
- setRenderer(renderer: RendererFactory): void
249
238
  /**
250
239
  * Set resolved options merged into the normalized plugin's `options`.
251
240
  * Call this in `kubb:plugin:setup` to provide options generators need.
@@ -289,9 +278,9 @@ export type Plugin<TFactory extends PluginFactoryOptions = PluginFactoryOptions>
289
278
  /**
290
279
  * Controls the execution order of this plugin relative to others.
291
280
  *
292
- * - `'pre'` runs before all normal plugins.
293
- * - `'post'` runs after all normal plugins.
294
- * - `undefined` (default) runs in declaration order among normal plugins.
281
+ * - `'pre'` runs before all normal plugins.
282
+ * - `'post'` runs after all normal plugins.
283
+ * - `undefined` (default), runs in declaration order among normal plugins.
295
284
  *
296
285
  * Dependency constraints always take precedence over `enforce`.
297
286
  */
@@ -313,7 +302,7 @@ export type Plugin<TFactory extends PluginFactoryOptions = PluginFactoryOptions>
313
302
 
314
303
  /**
315
304
  * Normalized plugin after setup, with runtime fields populated.
316
- * For internal use only plugins use the public `Plugin` type externally.
305
+ * For internal use only, plugins use the public `Plugin` type externally.
317
306
  *
318
307
  * @internal
319
308
  */
@@ -326,7 +315,6 @@ export type NormalizedPlugin<TOptions extends PluginFactoryOptions = PluginFacto
326
315
  }
327
316
  resolver: TOptions['resolver']
328
317
  transformer?: Visitor
329
- renderer?: RendererFactory
330
318
  generators?: Array<Generator>
331
319
  apply?: (config: Config) => boolean
332
320
  version?: string
@@ -2,6 +2,8 @@ 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
7
  import type { PluginFactoryOptions } from './definePlugin.ts'
6
8
  import { getMode } from './definePlugin.ts'
7
9
  import type { Config, Group, Output } from './types.ts'
@@ -37,7 +39,7 @@ export type ResolveOptionsContext<TOptions> = {
37
39
  * Base constraint for all plugin resolver objects.
38
40
  *
39
41
  * `default`, `resolveOptions`, `resolvePath`, `resolveFile`, `resolveBanner`, and `resolveFooter`
40
- * are injected automatically by `defineResolver` extend this type to add custom resolution methods.
42
+ * are injected automatically by `defineResolver`. Extend this type to add custom resolution methods.
41
43
  *
42
44
  * @example
43
45
  * ```ts
@@ -143,7 +145,7 @@ export type ResolverFileParams = {
143
145
  * Per-file context describing the file a banner/footer is being resolved for.
144
146
  *
145
147
  * Supplied by the generator (or the barrel middleware) at resolve-time and merged
146
- * into `BannerMeta` so a `banner`/`footer` function can branch on the file kind
148
+ * into `BannerMeta` so a `banner`/`footer` function can branch on the file kind,
147
149
  * e.g. omit a `'use server'` directive on re-export files.
148
150
  */
149
151
  export type ResolveBannerFile = {
@@ -196,7 +198,7 @@ export type BannerMeta = InputMeta & {
196
198
  /**
197
199
  * Context passed to `Resolver.resolveBanner` and `Resolver.resolveFooter`.
198
200
  *
199
- * `output` is optional not every plugin configures a banner/footer.
201
+ * `output` is optional, since not every plugin configures a banner/footer.
200
202
  * `config` carries the global Kubb config, used to derive the default Kubb banner.
201
203
  * `file` carries per-file context forwarded to a `banner`/`footer` function.
202
204
  *
@@ -236,7 +238,7 @@ function buildBannerMeta({ meta, file }: { meta: InputMeta | undefined; file: Re
236
238
  * Builder type for the plugin-specific resolver fields.
237
239
  *
238
240
  * `default`, `resolveOptions`, `resolvePath`, `resolveFile`, `resolveBanner`, and `resolveFooter`
239
- * are optional built-in fallbacks are injected when omitted.
241
+ * are optional, with built-in fallbacks injected when omitted.
240
242
  *
241
243
  * Methods in the returned object can call sibling resolver methods via `this`.
242
244
  */
@@ -249,7 +251,7 @@ type ResolverBuilder<T extends PluginFactoryOptions> = () => Omit<
249
251
  pluginName: T['name']
250
252
  } & ThisType<T['resolver']>
251
253
 
252
- // String patterns are compiled lazily and cached the same filter is reused for every node.
254
+ // String patterns are compiled lazily and cached, so the same filter is reused for every node.
253
255
  const stringPatternCache = new Map<string, RegExp>()
254
256
 
255
257
  function testPattern(value: string, pattern: string | RegExp): boolean {
@@ -301,7 +303,7 @@ function defaultResolver(name: string, type?: 'file' | 'function' | 'type' | 'co
301
303
  }
302
304
 
303
305
  /**
304
- * Default option resolver applies include/exclude filters and merges matching override options.
306
+ * Default option resolver. Applies include/exclude filters and merges matching override options.
305
307
  *
306
308
  * Returns `null` when the node is filtered out by an `exclude` rule or not matched by any `include` rule.
307
309
  *
@@ -345,7 +347,7 @@ function computeOptions<TOptions>(
345
347
  if (exclude.some(({ type, pattern }) => matchesSchemaPattern(node, type, pattern) === true)) return null
346
348
  if (include) {
347
349
  const results = include.map(({ type, pattern }) => matchesSchemaPattern(node, type, pattern))
348
- const applicable = results.filter((r) => r !== null)
350
+ const applicable = results.filter((result) => result !== null)
349
351
 
350
352
  if (applicable.length > 0 && !applicable.includes(true)) return null
351
353
  }
@@ -433,12 +435,12 @@ export function defaultResolvePath({ baseName, pathMode, tag, path: groupPath }:
433
435
  const groupValue = group.type === 'path' ? groupPath! : tag!
434
436
  const defaultName =
435
437
  group.type === 'tag'
436
- ? ({ group: g }: { group: string }) => `${camelCase(g)}Controller`
437
- : ({ group: g }: { group: string }) => {
438
+ ? ({ group: groupName }: { group: string }) => `${camelCase(groupName)}Controller`
439
+ : ({ group: groupName }: { group: string }) => {
438
440
  // Strip traversal components (empty, '.', '..') before taking the first meaningful segment.
439
441
  // When every segment is a traversal component (e.g. '../../') we fall back to '' so the
440
- // file is placed directly in the output root the boundary check below ensures safety.
441
- const segment = g.split('/').filter((s) => s !== '' && s !== '.' && s !== '..')[0]
442
+ // file is placed directly in the output root, and the boundary check below ensures safety.
443
+ const segment = groupName.split('/').filter((part) => part !== '' && part !== '.' && part !== '..')[0]
442
444
  return segment ? camelCase(segment) : ''
443
445
  }
444
446
  const resolveName = group.name ?? defaultName
@@ -454,10 +456,13 @@ export function defaultResolvePath({ baseName, pathMode, tag, path: groupPath }:
454
456
  const outputDir = path.resolve(root, output.path)
455
457
  const outputDirWithSep = outputDir.endsWith(path.sep) ? outputDir : `${outputDir}${path.sep}`
456
458
  if (result !== outputDir && !result.startsWith(outputDirWithSep)) {
457
- throw new Error(
458
- `[Kubb] Resolved path "${result}" is outside the output directory "${outputDir}". ` +
459
- 'This may indicate a path traversal attempt in the OpenAPI specification or a misconfigured group.name function.',
460
- )
459
+ throw new DiagnosticError({
460
+ code: diagnosticCode.pathTraversal,
461
+ severity: 'error',
462
+ message: `Resolved path "${result}" is outside the output directory "${outputDir}".`,
463
+ 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.',
464
+ location: { kind: 'config' },
465
+ })
461
466
  }
462
467
 
463
468
  return result
@@ -468,7 +473,7 @@ export function defaultResolvePath({ baseName, pathMode, tag, path: groupPath }:
468
473
  *
469
474
  * Resolves a `FileNode` by combining name resolution (`resolver.default`) with
470
475
  * path resolution (`resolver.resolvePath`). The resolved file always has empty
471
- * `sources`, `imports`, and `exports` arrays consumers populate those separately.
476
+ * `sources`, `imports`, and `exports` arrays, which consumers populate separately.
472
477
  *
473
478
  * In `single` mode the name is omitted and the file sits directly in the output directory.
474
479
  *
@@ -568,7 +573,7 @@ export function buildDefaultBanner({
568
573
  }
569
574
 
570
575
  /**
571
- * Default banner resolver returns the banner string for a generated file.
576
+ * Default banner resolver. Returns the banner string for a generated file.
572
577
  *
573
578
  * A user-supplied `output.banner` overrides the default Kubb "Generated by Kubb" notice.
574
579
  * When no `output.banner` is set, the Kubb notice is used (including `title` and `version`
@@ -597,7 +602,7 @@ export function buildDefaultBanner({
597
602
  * // → ''
598
603
  * ```
599
604
  *
600
- * @example No user banner Kubb notice with OAS metadata
605
+ * @example No user banner, Kubb notice with OAS metadata
601
606
  * ```ts
602
607
  * defaultResolveBanner(meta, { config })
603
608
  * // → '/** Generated by Kubb ... Title: Pet Store ... *\/'
@@ -630,7 +635,7 @@ export function defaultResolveBanner(meta: InputMeta | undefined, { output, conf
630
635
  }
631
636
 
632
637
  /**
633
- * Default footer resolver returns the footer string for a generated file.
638
+ * Default footer resolver. Returns the footer string for a generated file.
634
639
  *
635
640
  * - When `output.footer` is a function, calls it with the file's `BannerMeta` and returns the result.
636
641
  * - When `output.footer` is a string, returns it directly.
@@ -664,11 +669,11 @@ export function defaultResolveFooter(meta: InputMeta | undefined, { output, file
664
669
  * name casing, include/exclude/override filtering, output path computation,
665
670
  * and file construction. Supply your own to override any of them:
666
671
  *
667
- * - `default` name casing strategy (camelCase / PascalCase).
668
- * - `resolveOptions` include/exclude/override filtering.
669
- * - `resolvePath` output path computation.
670
- * - `resolveFile` full `FileNode` construction.
671
- * - `resolveBanner` / `resolveFooter` top/bottom-of-file text.
672
+ * - `default` sets the name casing strategy (camelCase or PascalCase).
673
+ * - `resolveOptions` does include/exclude/override filtering.
674
+ * - `resolvePath` computes the output path.
675
+ * - `resolveFile` builds the full `FileNode`.
676
+ * - `resolveBanner` and `resolveFooter` produce the top and bottom of file text.
672
677
  *
673
678
  * Methods in the returned object can call sibling resolver methods via `this`,
674
679
  * which keeps custom rules small (`this.default(name, 'type')` to delegate).
package/src/devtools.ts CHANGED
@@ -14,7 +14,7 @@ export type DevtoolsOptions = {
14
14
  * Encodes an `InputNode` as a compressed, URL-safe string.
15
15
  *
16
16
  * The JSON representation is deflate-compressed with {@link deflateSync} before
17
- * base64url encoding, which typically reduces payload size by 7080 % and
17
+ * base64url encoding, which typically reduces payload size by 70, 80 % and
18
18
  * keeps URLs well within browser and server path-length limits.
19
19
  */
20
20
  export function encodeAst(input: InputNode): string {
@@ -36,8 +36,7 @@ export function getStudioUrl(input: InputNode, studioUrl: string, options: Devto
36
36
  }
37
37
 
38
38
  /**
39
- * Opens the Kubb Studio URL for the given `InputNode` in the default browser
40
- *
39
+ * Opens the Kubb Studio URL for the given `InputNode` in the default browser, *
41
40
  * Falls back to printing the URL if the browser cannot be launched.
42
41
  */
43
42
  export async function openInStudio(input: InputNode, studioUrl: string, options: DevtoolsOptions = {}): Promise<void> {