@kubb/core 5.0.0-beta.4 → 5.0.0-beta.40

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 (57) hide show
  1. package/README.md +15 -148
  2. package/dist/diagnostics-DhfW8YpT.d.ts +2963 -0
  3. package/dist/index.cjs +775 -1193
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.ts +165 -171
  6. package/dist/index.js +760 -1188
  7. package/dist/index.js.map +1 -1
  8. package/dist/memoryStorage-DTv1Kub1.js +2852 -0
  9. package/dist/memoryStorage-DTv1Kub1.js.map +1 -0
  10. package/dist/memoryStorage-Dkxnid2K.cjs +2985 -0
  11. package/dist/memoryStorage-Dkxnid2K.cjs.map +1 -0
  12. package/dist/mocks.cjs +80 -18
  13. package/dist/mocks.cjs.map +1 -1
  14. package/dist/mocks.d.ts +35 -8
  15. package/dist/mocks.js +81 -21
  16. package/dist/mocks.js.map +1 -1
  17. package/package.json +8 -19
  18. package/src/FileManager.ts +85 -63
  19. package/src/FileProcessor.ts +171 -43
  20. package/src/HookRegistry.ts +45 -0
  21. package/src/KubbDriver.ts +897 -0
  22. package/src/Telemetry.ts +278 -0
  23. package/src/Transform.ts +58 -0
  24. package/src/constants.ts +111 -19
  25. package/src/createAdapter.ts +113 -17
  26. package/src/createKubb.ts +921 -493
  27. package/src/createRenderer.ts +58 -27
  28. package/src/createReporter.ts +147 -0
  29. package/src/createStorage.ts +36 -23
  30. package/src/defineGenerator.ts +129 -17
  31. package/src/defineLogger.ts +78 -7
  32. package/src/defineMiddleware.ts +19 -17
  33. package/src/defineParser.ts +30 -13
  34. package/src/definePlugin.ts +320 -17
  35. package/src/defineResolver.ts +381 -179
  36. package/src/diagnostics.ts +660 -0
  37. package/src/index.ts +8 -6
  38. package/src/mocks.ts +92 -19
  39. package/src/reporters/cliReporter.ts +90 -0
  40. package/src/reporters/fileReporter.ts +103 -0
  41. package/src/reporters/jsonReporter.ts +20 -0
  42. package/src/reporters/report.ts +85 -0
  43. package/src/storages/fsStorage.ts +13 -37
  44. package/src/types.ts +59 -1297
  45. package/dist/PluginDriver-Ds-Us-e4.cjs +0 -1036
  46. package/dist/PluginDriver-Ds-Us-e4.cjs.map +0 -1
  47. package/dist/PluginDriver-Wi34Pegx.js +0 -945
  48. package/dist/PluginDriver-Wi34Pegx.js.map +0 -1
  49. package/dist/types-Cd0jhNmx.d.ts +0 -2153
  50. package/src/Kubb.ts +0 -300
  51. package/src/PluginDriver.ts +0 -424
  52. package/src/devtools.ts +0 -59
  53. package/src/renderNode.ts +0 -35
  54. package/src/utils/diagnostics.ts +0 -18
  55. package/src/utils/isInputPath.ts +0 -10
  56. package/src/utils/packageJSON.ts +0 -99
  57. /package/dist/{chunk--u3MIqq1.js → chunk-C0LytTxp.js} +0 -0
@@ -1,20 +1,19 @@
1
- import type { KubbHooks } from './Kubb.ts'
1
+ import type { KubbHooks } from './types.ts'
2
2
 
3
3
  /**
4
- * A middleware instance produced by calling a factory created with `defineMiddleware`.
5
- * It declares event handlers under a `hooks` object which are registered on the
6
- * shared emitter after all plugin hooks, so middleware handlers for any event
7
- * always fire last.
4
+ * A middleware instance. Subscribes to lifecycle events via `hooks`. Middleware
5
+ * handlers always fire after every plugin handler for the same event, so they
6
+ * see the full set of generated files.
8
7
  */
9
8
  export type Middleware = {
10
9
  /**
11
- * Unique identifier for this middleware.
10
+ * Unique name. Use a `middleware-<feature>` convention (e.g.
11
+ * `middleware-barrel`).
12
12
  */
13
13
  name: string
14
14
  /**
15
- * Lifecycle event handlers for this middleware.
16
- * Any event from the global `KubbHooks` map can be subscribed to here.
17
- * Handlers are registered after all plugin handlers, so they always fire last.
15
+ * Lifecycle event handlers. Any event from the global `KubbHooks` map can be
16
+ * subscribed to here. Handlers run after all plugin handlers for that event.
18
17
  */
19
18
  hooks: {
20
19
  [K in keyof KubbHooks]?: (...args: KubbHooks[K]) => void | Promise<void>
@@ -22,18 +21,17 @@ export type Middleware = {
22
21
  }
23
22
 
24
23
  /**
25
- * Creates a middleware factory using the hook-style `hooks` API.
24
+ * Creates a middleware factory. Middleware fires after every plugin handler
25
+ * for the same event, which makes it the natural place for post-processing
26
+ * (barrel files, lint runs, audit logs).
26
27
  *
27
- * Middleware handlers fire after all plugin handlers for any given event, making them ideal for post-processing, logging, and auditing.
28
- * Per-build state (such as accumulators) belongs inside the factory closure so each `createKubb` invocation gets its own isolated instance.
28
+ * Per-build state belongs inside the factory closure so each `createKubb`
29
+ * invocation gets its own isolated instance.
29
30
  *
30
- * @note The factory can accept typed options. See examples for using options and per-build state patterns.
31
- *
32
- * @example
31
+ * @example Stateless middleware
33
32
  * ```ts
34
33
  * import { defineMiddleware } from '@kubb/core'
35
34
  *
36
- * // Stateless middleware
37
35
  * export const logMiddleware = defineMiddleware(() => ({
38
36
  * name: 'log-middleware',
39
37
  * hooks: {
@@ -42,8 +40,12 @@ export type Middleware = {
42
40
  * },
43
41
  * },
44
42
  * }))
43
+ * ```
44
+ *
45
+ * @example Middleware with options and per-build state
46
+ * ```ts
47
+ * import { defineMiddleware } from '@kubb/core'
45
48
  *
46
- * // Middleware with options and per-build state
47
49
  * export const prefixMiddleware = defineMiddleware((options: { prefix: string } = { prefix: '' }) => {
48
50
  * const seen = new Set<string>()
49
51
  * return {
@@ -4,41 +4,58 @@ type PrintOptions = {
4
4
  extname?: FileNode['extname']
5
5
  }
6
6
 
7
- export type Parser<TMeta extends object = any> = {
7
+ /**
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
10
+ * for new file types (JSON, Markdown, ...).
11
+ */
12
+ export type Parser<TMeta extends object = object, TNode = unknown> = {
13
+ /**
14
+ * Display name used in diagnostics and the parser registry.
15
+ */
8
16
  name: string
9
17
  /**
10
- * File extensions this parser handles.
11
- * Use `undefined` to create a catch-all fallback parser.
18
+ * File extensions this parser handles. Set to `undefined` to define a
19
+ * catch-all fallback used when no other parser claims the extension.
12
20
  *
13
- * @example Handled extensions
21
+ * @example
14
22
  * `['.ts', '.js']`
15
23
  */
16
24
  extNames: Array<FileNode['extname']> | undefined
17
25
  /**
18
- * Convert a resolved file to a string.
26
+ * Serialise the file's AST into source code.
19
27
  */
20
- parse(file: FileNode<TMeta>, options?: PrintOptions): Promise<string> | string
28
+ parse(file: FileNode<TMeta>, options?: PrintOptions): string
29
+ /**
30
+ * Render compiler AST nodes for this parser's language into source text.
31
+ * Plugins call this to format the nodes they assemble before handing them
32
+ * back to the parser as `FileNode.sources`.
33
+ */
34
+ print(...nodes: Array<TNode>): string
21
35
  }
22
36
 
23
37
  /**
24
- * Defines a parser with type safety. Creates parsers that transform generated files to strings based on their extension.
25
- *
26
- * @note Call the returned factory with optional options to instantiate the parser.
38
+ * Defines a parser with type-safe `this`. Used to register handlers for new
39
+ * file extensions or to plug a non-TypeScript output into the build.
27
40
  *
28
41
  * @example
29
42
  * ```ts
30
- * import { defineParser } from '@kubb/core'
43
+ * import { defineParser, ast } from '@kubb/core'
31
44
  *
32
45
  * export const jsonParser = defineParser({
33
46
  * name: 'json',
34
47
  * extNames: ['.json'],
35
48
  * parse(file) {
36
- * const { extractStringsFromNodes } = await import('@kubb/ast')
37
- * return file.sources.map((s) => extractStringsFromNodes(s.nodes ?? [])).join('\n')
49
+ * return file.sources
50
+ * .map((source) => ast.extractStringsFromNodes(source.nodes ?? []))
51
+ * .join('\n')
52
+ * },
53
+ * print(...nodes) {
54
+ * return nodes.map(String).join('\n')
38
55
  * },
39
56
  * })
40
57
  * ```
41
58
  */
42
- export function defineParser<TMeta extends object = any>(parser: Parser<TMeta>): Parser<TMeta> {
59
+ export function defineParser<T extends Parser>(parser: T): T {
43
60
  return parser
44
61
  }
@@ -1,5 +1,262 @@
1
- import type { KubbHooks } from './Kubb.ts'
2
- import type { KubbPluginSetupContext, PluginFactoryOptions } from './types.ts'
1
+ import { extname } from 'node:path'
2
+ import type { FileNode, HttpMethod, UserFileNode, Visitor } from '@kubb/ast'
3
+ import type { Generator } from './defineGenerator.ts'
4
+ import type { BannerMeta, Resolver } from './defineResolver.ts'
5
+ import type { Config, KubbHooks } from './types.ts'
6
+
7
+ /**
8
+ * Safely extracts a type from a registry, returning `{}` if the key doesn't exist.
9
+ * Enables optional interface augmentation for `Kubb.ConfigOptionsRegistry` and `Kubb.PluginOptionsRegistry`
10
+ * without requiring changes to core.
11
+ *
12
+ * @internal
13
+ */
14
+ type ExtractRegistryKey<T, K extends PropertyKey> = K extends keyof T ? T[K] : {}
15
+
16
+ /**
17
+ * Output configuration shared by every plugin. Each plugin extends this with
18
+ * its own keys via the `Kubb.PluginOptionsRegistry.output` interface merge.
19
+ */
20
+ export type Output<_TOptions = unknown> = {
21
+ /**
22
+ * Folder (or single file) where the plugin writes its generated code.
23
+ * Resolved against the global `output.path` set on `defineConfig`.
24
+ */
25
+ path: string
26
+ /**
27
+ * Text prepended to every generated file. Useful for license headers,
28
+ * lint disables, or `@ts-nocheck` directives.
29
+ *
30
+ * A string is applied to every file (including barrel and aggregation re-export files).
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
33
+ * skip the banner on specific files.
34
+ *
35
+ * @example Add a directive to source files but not re-export files
36
+ * `banner: (meta) => (meta.isBarrel || meta.isAggregation) ? '' : "'use server'"`
37
+ */
38
+ banner?: string | ((meta: BannerMeta) => string)
39
+ /**
40
+ * Text appended at the end of every generated file. Mirror of `banner`.
41
+ * Pass a function to compute the footer from the file's `BannerMeta`.
42
+ */
43
+ footer?: string | ((meta: BannerMeta) => string)
44
+ /**
45
+ * Allows the plugin to overwrite hand-written files at the same path.
46
+ * Defaults to `false` to protect manual edits.
47
+ *
48
+ * @default false
49
+ */
50
+ override?: boolean
51
+ } & ExtractRegistryKey<Kubb.PluginOptionsRegistry, 'output'>
52
+
53
+ /**
54
+ * Groups generated files into subdirectories based on an OpenAPI tag or path
55
+ * segment.
56
+ */
57
+ export type Group = {
58
+ /**
59
+ * Property used to assign each operation to a group.
60
+ * - `'tag'` uses the first tag (`operation.getTags().at(0)?.name`).
61
+ * - `'path'` uses the first segment of the operation's URL.
62
+ */
63
+ type: 'tag' | 'path'
64
+ /**
65
+ * Returns the subdirectory name from the group key. Defaults to
66
+ * `${camelCase(group)}Controller` for tags, or the first path segment.
67
+ */
68
+ name?: (context: { group: string }) => string
69
+ }
70
+
71
+ type ByTag = {
72
+ /**
73
+ * Filter by OpenAPI `tags` field. Matches one or more tags assigned to operations.
74
+ */
75
+ type: 'tag'
76
+ /**
77
+ * Tag name to match (case-sensitive). Can be a literal string or regex pattern.
78
+ */
79
+ pattern: string | RegExp
80
+ }
81
+
82
+ type ByOperationId = {
83
+ /**
84
+ * Filter by OpenAPI `operationId` field. Each operation (GET, POST, etc.) has a unique identifier.
85
+ */
86
+ type: 'operationId'
87
+ /**
88
+ * Operation ID to match (case-sensitive). Can be a literal string or regex pattern.
89
+ */
90
+ pattern: string | RegExp
91
+ }
92
+
93
+ type ByPath = {
94
+ /**
95
+ * Filter by OpenAPI `path` (URL endpoint). Useful to group or filter by service segments like `/pets`, `/users`, etc.
96
+ */
97
+ type: 'path'
98
+ /**
99
+ * URL path to match (case-sensitive). Can be a literal string or regex pattern. Matches against the full path.
100
+ */
101
+ pattern: string | RegExp
102
+ }
103
+
104
+ type ByMethod = {
105
+ /**
106
+ * Filter by HTTP method: `'get'`, `'post'`, `'put'`, `'delete'`, `'patch'`, `'head'`, `'options'`.
107
+ */
108
+ type: 'method'
109
+ /**
110
+ * HTTP method to match (case-insensitive when using string, or regex for dynamic matching).
111
+ */
112
+ pattern: HttpMethod | RegExp
113
+ }
114
+
115
+ type BySchemaName = {
116
+ /**
117
+ * Filter by schema component name (TypeScript or JSON schema). Matches schemas in `#/components/schemas`.
118
+ */
119
+ type: 'schemaName'
120
+ /**
121
+ * Schema name to match (case-sensitive). Can be a literal string or regex pattern.
122
+ */
123
+ pattern: string | RegExp
124
+ }
125
+
126
+ type ByContentType = {
127
+ /**
128
+ * Filter by response or request content type: `'application/json'`, `'application/xml'`, etc.
129
+ */
130
+ type: 'contentType'
131
+ /**
132
+ * Content type to match (case-sensitive). Can be a literal string or regex pattern.
133
+ */
134
+ pattern: string | RegExp
135
+ }
136
+
137
+ /**
138
+ * Filter that skips matching operations or schemas during generation. Use it
139
+ * to drop deprecated endpoints, internal-only schemas, or anything you do
140
+ * not want code generated for.
141
+ *
142
+ * @example
143
+ * ```ts
144
+ * exclude: [
145
+ * { type: 'tag', pattern: 'internal' },
146
+ * { type: 'path', pattern: /^\/admin/ },
147
+ * { type: 'operationId', pattern: /^deprecated_/ },
148
+ * ]
149
+ * ```
150
+ */
151
+ export type Exclude = ByTag | ByOperationId | ByPath | ByMethod | ByContentType | BySchemaName
152
+
153
+ /**
154
+ * Filter that restricts generation to operations or schemas matching at least
155
+ * one entry. Useful for partial builds (one tag, one API version).
156
+ *
157
+ * @example
158
+ * ```ts
159
+ * include: [
160
+ * { type: 'tag', pattern: 'public' },
161
+ * { type: 'path', pattern: /^\/api\/v1/ },
162
+ * ]
163
+ * ```
164
+ */
165
+ export type Include = ByTag | ByOperationId | ByPath | ByMethod | ByContentType | BySchemaName
166
+
167
+ /**
168
+ * Filter paired with a partial options object. When the filter matches, the
169
+ * options are merged on top of the plugin defaults for that operation only.
170
+ * Useful for "this one tag goes to a different folder" rules.
171
+ *
172
+ * Entries are evaluated top to bottom. The first matching entry wins.
173
+ *
174
+ * @example
175
+ * ```ts
176
+ * override: [
177
+ * {
178
+ * type: 'tag',
179
+ * pattern: 'admin',
180
+ * options: { output: { path: './src/gen/admin' } },
181
+ * },
182
+ * {
183
+ * type: 'operationId',
184
+ * pattern: 'listPets',
185
+ * options: { enumType: 'literal' },
186
+ * },
187
+ * ]
188
+ * ```
189
+ */
190
+ export type Override<TOptions> = (ByTag | ByOperationId | ByPath | ByMethod | BySchemaName | ByContentType) & {
191
+ options: Omit<Partial<TOptions>, 'override'>
192
+ }
193
+
194
+ export type PluginFactoryOptions<
195
+ /**
196
+ * Unique plugin name.
197
+ */
198
+ TName extends string = string,
199
+ /**
200
+ * User-facing plugin options.
201
+ */
202
+ TOptions extends object = object,
203
+ /**
204
+ * Plugin options after defaults are applied.
205
+ */
206
+ TResolvedOptions extends object = TOptions,
207
+ /**
208
+ * Resolver that encapsulates naming and path-resolution helpers.
209
+ * Define with `defineResolver` and export alongside the plugin.
210
+ */
211
+ TResolver extends Resolver = Resolver,
212
+ > = {
213
+ name: TName
214
+ options: TOptions
215
+ resolvedOptions: TResolvedOptions
216
+ resolver: TResolver
217
+ }
218
+
219
+ /**
220
+ * Context for hook-style plugin `kubb:plugin:setup` handler.
221
+ * Provides methods to register generators, configure resolvers, transformers, and renderers.
222
+ */
223
+ export type KubbPluginSetupContext<TFactory extends PluginFactoryOptions = PluginFactoryOptions> = {
224
+ /**
225
+ * Register a generator dynamically. Generators fire during the AST walk (schema/operation/operations)
226
+ * just like generators declared statically on `createPlugin`.
227
+ */
228
+ addGenerator<TElement = unknown>(generator: Generator<TFactory, TElement>): void
229
+ /**
230
+ * Set or override the resolver for this plugin.
231
+ * The resolver controls file naming and path resolution.
232
+ */
233
+ setResolver(resolver: Partial<TFactory['resolver']>): void
234
+ /**
235
+ * Set the AST transformer to pre-process nodes before they reach generators.
236
+ */
237
+ setTransformer(visitor: Visitor): void
238
+ /**
239
+ * Set resolved options merged into the normalized plugin's `options`.
240
+ * Call this in `kubb:plugin:setup` to provide options generators need.
241
+ */
242
+ setOptions(options: TFactory['resolvedOptions']): void
243
+ /**
244
+ * Inject a raw file into the build output, bypassing the generation pipeline.
245
+ */
246
+ injectFile(userFileNode: UserFileNode): void
247
+ /**
248
+ * Merge a partial config update into the current build configuration.
249
+ */
250
+ updateConfig(config: Partial<Config>): void
251
+ /**
252
+ * The resolved build configuration at setup time.
253
+ */
254
+ config: Config
255
+ /**
256
+ * The plugin's user-provided options.
257
+ */
258
+ options: TFactory['options']
259
+ }
3
260
 
4
261
  /**
5
262
  * A plugin object produced by `definePlugin`.
@@ -21,9 +278,9 @@ export type Plugin<TFactory extends PluginFactoryOptions = PluginFactoryOptions>
21
278
  /**
22
279
  * Controls the execution order of this plugin relative to others.
23
280
  *
24
- * - `'pre'` runs before all normal plugins.
25
- * - `'post'` runs after all normal plugins.
26
- * - `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.
27
284
  *
28
285
  * Dependency constraints always take precedence over `enforce`.
29
286
  */
@@ -37,30 +294,61 @@ export type Plugin<TFactory extends PluginFactoryOptions = PluginFactoryOptions>
37
294
  * Any event from the global `KubbHooks` map can be subscribed to here.
38
295
  */
39
296
  hooks: {
40
- [K in Exclude<keyof KubbHooks, 'kubb:plugin:setup'>]?: (...args: KubbHooks[K]) => void | Promise<void>
297
+ [K in keyof KubbHooks as K extends 'kubb:plugin:setup' ? never : K]?: (...args: KubbHooks[K]) => void | Promise<void>
41
298
  } & {
42
299
  'kubb:plugin:setup'?(ctx: KubbPluginSetupContext<TFactory>): void | Promise<void>
43
300
  }
44
301
  }
45
302
 
46
303
  /**
47
- * Returns `true` when `plugin` is a hook-style plugin created with `definePlugin`.
304
+ * Normalized plugin after setup, with runtime fields populated.
305
+ * For internal use only, plugins use the public `Plugin` type externally.
48
306
  *
49
- * Used by `PluginDriver` to distinguish hook-style plugins from legacy `createPlugin` plugins
50
- * so it can normalize them and register their handlers on the `AsyncEventEmitter`.
307
+ * @internal
51
308
  */
52
- export function isPlugin(plugin: unknown): plugin is Plugin {
53
- return typeof plugin === 'object' && plugin !== null && 'hooks' in plugin
309
+ export type NormalizedPlugin<TOptions extends PluginFactoryOptions = PluginFactoryOptions> = Plugin<TOptions> & {
310
+ options: TOptions['resolvedOptions'] & {
311
+ output: Output
312
+ include?: Array<Include>
313
+ exclude: Array<Exclude>
314
+ override: Array<Override<TOptions['resolvedOptions']>>
315
+ }
316
+ resolver: TOptions['resolver']
317
+ transformer?: Visitor
318
+ generators?: Array<Generator>
319
+ apply?: (config: Config) => boolean
320
+ version?: string
321
+ }
322
+
323
+ export type KubbPluginStartContext = {
324
+ plugin: NormalizedPlugin
325
+ }
326
+
327
+ export type KubbPluginEndContext = {
328
+ plugin: NormalizedPlugin
329
+ duration: number
330
+ success: boolean
331
+ error?: Error
332
+ config: Config
333
+ /**
334
+ * Returns all files currently in the file manager (lazy snapshot).
335
+ * Includes files added by plugins that have already run.
336
+ */
337
+ readonly files: ReadonlyArray<FileNode>
338
+ /**
339
+ * Upsert one or more files into the file manager.
340
+ */
341
+ upsertFile: (...files: Array<FileNode>) => void
54
342
  }
55
343
 
56
344
  /**
57
- * Wraps a factory function and returns a typed `Plugin` with lifecycle handlers grouped under `hooks`.
58
- *
59
- * Handlers live in a single `hooks` object (inspired by Astro integrations).
60
- * All lifecycle events from `KubbHooks` are available for subscription.
345
+ * Wraps a plugin factory and returns a function that accepts user options and
346
+ * yields a fully typed `Plugin`. Lifecycle handlers go inside a single
347
+ * `hooks` object (inspired by Astro integrations).
61
348
  *
62
- * @note For real plugins, use a `PluginFactoryOptions` type parameter to get type-safe context in `kubb:plugin:setup`.
63
- * Plugin names should follow the convention `plugin-<feature>` (e.g., `plugin-react-query`, `plugin-zod`).
349
+ * Pass a `PluginFactoryOptions` type parameter to get a typed `ctx` inside
350
+ * `kubb:plugin:setup`. Plugin names should follow the `plugin-<feature>`
351
+ * convention (`plugin-react-query`, `plugin-zod`, ...).
64
352
  *
65
353
  * @example
66
354
  * ```ts
@@ -81,3 +369,18 @@ export function definePlugin<TFactory extends PluginFactoryOptions = PluginFacto
81
369
  ): (options?: TFactory['options']) => Plugin<TFactory> {
82
370
  return (options) => factory(options ?? ({} as TFactory['options']))
83
371
  }
372
+
373
+ /**
374
+ * Detects whether an output path points at a single file (`'single'`) or a
375
+ * directory (`'split'`). Decided purely from the presence of a file extension.
376
+ *
377
+ * @example Directory
378
+ * `getMode('./types') // 'split'`
379
+ *
380
+ * @example Single file
381
+ * `getMode('./api.ts') // 'single'`
382
+ */
383
+ export function getMode(fileOrFolder: string | undefined | null): 'single' | 'split' {
384
+ if (!fileOrFolder) return 'split'
385
+ return extname(fileOrFolder) ? 'single' : 'split'
386
+ }