@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.
- package/dist/{KubbDriver-T8W1wm4O.cjs → KubbDriver-CXoKVRxI.cjs} +1091 -413
- package/dist/KubbDriver-CXoKVRxI.cjs.map +1 -0
- package/dist/{KubbDriver-7JLUadUM.js → KubbDriver-CckeYpMG.js} +1050 -402
- package/dist/KubbDriver-CckeYpMG.js.map +1 -0
- package/dist/{createKubb-BKpcUB6g.d.ts → diagnostics-B0ONXReg.d.ts} +790 -287
- package/dist/index.cjs +48 -89
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +40 -90
- package/dist/index.js.map +1 -1
- package/dist/mocks.cjs +23 -23
- package/dist/mocks.cjs.map +1 -1
- package/dist/mocks.d.ts +2 -2
- package/dist/mocks.js +23 -23
- package/dist/mocks.js.map +1 -1
- package/package.json +4 -4
- package/src/FileManager.ts +23 -18
- package/src/FileProcessor.ts +142 -24
- package/src/HookRegistry.ts +45 -0
- package/src/KubbDriver.ts +333 -319
- package/src/Transform.ts +58 -0
- package/src/constants.ts +105 -11
- package/src/createKubb.ts +95 -201
- package/src/createRenderer.ts +2 -2
- package/src/createReporter.ts +84 -0
- package/src/createStorage.ts +1 -1
- package/src/defineGenerator.ts +11 -7
- package/src/defineParser.ts +1 -1
- package/src/definePlugin.ts +10 -22
- package/src/defineResolver.ts +29 -24
- package/src/devtools.ts +2 -3
- package/src/diagnostics.ts +591 -0
- package/src/index.ts +11 -1
- package/src/mocks.ts +5 -5
- package/src/types.ts +16 -4
- package/dist/KubbDriver-7JLUadUM.js.map +0 -1
- package/dist/KubbDriver-T8W1wm4O.cjs.map +0 -1
package/src/defineGenerator.ts
CHANGED
|
@@ -64,15 +64,20 @@ export type GeneratorContext<TOptions extends PluginFactoryOptions = PluginFacto
|
|
|
64
64
|
*/
|
|
65
65
|
transformer: Visitor | undefined
|
|
66
66
|
/**
|
|
67
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
|
179
|
+
* are both handled by the runtime, so pick whichever style fits.
|
|
176
180
|
*
|
|
177
181
|
* @example JSX-based schema generator
|
|
178
182
|
* ```tsx
|
package/src/defineParser.ts
CHANGED
|
@@ -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
|
|
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> = {
|
package/src/definePlugin.ts
CHANGED
|
@@ -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`
|
|
33
|
-
* plus per-file context (`isBarrel`, `isAggregation`, `filePath`, `baseName`)
|
|
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'`
|
|
62
|
-
* - `'path'`
|
|
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
|
|
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
|
-
|
|
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'`
|
|
293
|
-
* - `'post'`
|
|
294
|
-
* - `undefined` (default)
|
|
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
|
|
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
|
package/src/defineResolver.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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((
|
|
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:
|
|
437
|
-
: ({ group:
|
|
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
|
|
441
|
-
const segment =
|
|
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
|
|
458
|
-
|
|
459
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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`
|
|
668
|
-
* - `resolveOptions`
|
|
669
|
-
* - `resolvePath`
|
|
670
|
-
* - `resolveFile`
|
|
671
|
-
* - `resolveBanner`
|
|
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 70
|
|
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> {
|