@kubb/core 5.0.0-alpha.5 → 5.0.0-alpha.51

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 (71) hide show
  1. package/README.md +3 -2
  2. package/dist/PluginDriver-6E8zd8MW.cjs +1086 -0
  3. package/dist/PluginDriver-6E8zd8MW.cjs.map +1 -0
  4. package/dist/PluginDriver-D6wQFD4r.js +983 -0
  5. package/dist/PluginDriver-D6wQFD4r.js.map +1 -0
  6. package/dist/index.cjs +1013 -1829
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.d.ts +279 -265
  9. package/dist/index.js +1003 -1799
  10. package/dist/index.js.map +1 -1
  11. package/dist/mocks.cjs +138 -0
  12. package/dist/mocks.cjs.map +1 -0
  13. package/dist/mocks.d.ts +74 -0
  14. package/dist/mocks.js +133 -0
  15. package/dist/mocks.js.map +1 -0
  16. package/dist/types-DfEv9d_c.d.ts +1721 -0
  17. package/package.json +51 -57
  18. package/src/FileManager.ts +133 -0
  19. package/src/FileProcessor.ts +86 -0
  20. package/src/Kubb.ts +154 -101
  21. package/src/PluginDriver.ts +418 -0
  22. package/src/constants.ts +43 -47
  23. package/src/createAdapter.ts +25 -0
  24. package/src/createKubb.ts +614 -0
  25. package/src/createRenderer.ts +57 -0
  26. package/src/createStorage.ts +58 -0
  27. package/src/defineGenerator.ts +88 -100
  28. package/src/defineLogger.ts +13 -3
  29. package/src/defineParser.ts +45 -0
  30. package/src/definePlugin.ts +68 -7
  31. package/src/defineResolver.ts +521 -0
  32. package/src/devtools.ts +14 -14
  33. package/src/index.ts +12 -17
  34. package/src/mocks.ts +171 -0
  35. package/src/renderNode.ts +35 -0
  36. package/src/storages/fsStorage.ts +40 -11
  37. package/src/storages/memoryStorage.ts +4 -3
  38. package/src/types.ts +575 -205
  39. package/src/utils/TreeNode.ts +47 -9
  40. package/src/utils/diagnostics.ts +4 -1
  41. package/src/utils/getBarrelFiles.ts +94 -16
  42. package/src/utils/isInputPath.ts +10 -0
  43. package/src/utils/packageJSON.ts +99 -0
  44. package/dist/PluginManager-vZodFEMe.d.ts +0 -1056
  45. package/dist/chunk-ByKO4r7w.cjs +0 -38
  46. package/dist/hooks.cjs +0 -60
  47. package/dist/hooks.cjs.map +0 -1
  48. package/dist/hooks.d.ts +0 -56
  49. package/dist/hooks.js +0 -56
  50. package/dist/hooks.js.map +0 -1
  51. package/src/BarrelManager.ts +0 -74
  52. package/src/PackageManager.ts +0 -180
  53. package/src/PluginManager.ts +0 -667
  54. package/src/PromiseManager.ts +0 -40
  55. package/src/build.ts +0 -419
  56. package/src/config.ts +0 -56
  57. package/src/defineAdapter.ts +0 -22
  58. package/src/defineStorage.ts +0 -56
  59. package/src/errors.ts +0 -1
  60. package/src/hooks/index.ts +0 -4
  61. package/src/hooks/useKubb.ts +0 -46
  62. package/src/hooks/useMode.ts +0 -11
  63. package/src/hooks/usePlugin.ts +0 -11
  64. package/src/hooks/usePluginManager.ts +0 -11
  65. package/src/utils/FunctionParams.ts +0 -155
  66. package/src/utils/executeStrategies.ts +0 -81
  67. package/src/utils/formatters.ts +0 -56
  68. package/src/utils/getConfigs.ts +0 -30
  69. package/src/utils/getPlugins.ts +0 -23
  70. package/src/utils/linters.ts +0 -25
  71. package/src/utils/resolveOptions.ts +0 -93
@@ -1,17 +1,26 @@
1
1
  import path from 'node:path'
2
- import type { KubbFile } from '@kubb/fabric-core/types'
3
- import { getMode } from '../PluginManager.ts'
2
+ import type { FileNode } from '@kubb/ast'
3
+ import { PluginDriver } from '../PluginDriver.ts'
4
4
 
5
5
  type BarrelData = {
6
- file?: KubbFile.File
6
+ file?: FileNode
7
7
  /**
8
8
  * @deprecated use file instead
9
9
  */
10
- type: KubbFile.Mode
10
+ type: 'single' | 'split'
11
11
  path: string
12
12
  name: string
13
13
  }
14
14
 
15
+ /**
16
+ * Tree structure used to build per-directory barrel (`index.ts`) files from a
17
+ * flat list of generated {@link FileNode} entries.
18
+ *
19
+ * Each node represents either a directory or a file within the output tree.
20
+ * Use {@link TreeNode.build} to construct a root node from a file list, then
21
+ * traverse with {@link TreeNode.forEach}, {@link TreeNode.leaves}, or the
22
+ * `*Deep` helpers.
23
+ */
15
24
  export class TreeNode {
16
25
  data: BarrelData
17
26
  parent?: TreeNode
@@ -32,6 +41,9 @@ export class TreeNode {
32
41
  return child
33
42
  }
34
43
 
44
+ /**
45
+ * Returns the root ancestor of this node, walking up via `parent` links.
46
+ */
35
47
  get root(): TreeNode {
36
48
  if (!this.parent) {
37
49
  return this
@@ -39,6 +51,11 @@ export class TreeNode {
39
51
  return this.parent.root
40
52
  }
41
53
 
54
+ /**
55
+ * Returns all leaf descendants (nodes with no children) of this node.
56
+ *
57
+ * Results are cached after the first traversal.
58
+ */
42
59
  get leaves(): Array<TreeNode> {
43
60
  if (!this.children || this.children.length === 0) {
44
61
  // this is a leaf
@@ -59,6 +76,9 @@ export class TreeNode {
59
76
  return leaves
60
77
  }
61
78
 
79
+ /**
80
+ * Visits this node and every descendant in depth-first order.
81
+ */
62
82
  forEach(callback: (treeNode: TreeNode) => void): this {
63
83
  if (typeof callback !== 'function') {
64
84
  throw new TypeError('forEach() callback must be a function')
@@ -73,6 +93,9 @@ export class TreeNode {
73
93
  return this
74
94
  }
75
95
 
96
+ /**
97
+ * Finds the first leaf that satisfies `predicate`, or `undefined` when none match.
98
+ */
76
99
  findDeep(predicate?: (value: TreeNode, index: number, obj: TreeNode[]) => boolean): TreeNode | undefined {
77
100
  if (typeof predicate !== 'function') {
78
101
  throw new TypeError('find() predicate must be a function')
@@ -81,6 +104,9 @@ export class TreeNode {
81
104
  return this.leaves.find(predicate)
82
105
  }
83
106
 
107
+ /**
108
+ * Calls `callback` for every leaf of this node.
109
+ */
84
110
  forEachDeep(callback: (treeNode: TreeNode) => void): void {
85
111
  if (typeof callback !== 'function') {
86
112
  throw new TypeError('forEach() callback must be a function')
@@ -89,6 +115,9 @@ export class TreeNode {
89
115
  this.leaves.forEach(callback)
90
116
  }
91
117
 
118
+ /**
119
+ * Returns all leaves that satisfy `callback`.
120
+ */
92
121
  filterDeep(callback: (treeNode: TreeNode) => boolean): Array<TreeNode> {
93
122
  if (typeof callback !== 'function') {
94
123
  throw new TypeError('filter() callback must be a function')
@@ -97,6 +126,9 @@ export class TreeNode {
97
126
  return this.leaves.filter(callback)
98
127
  }
99
128
 
129
+ /**
130
+ * Maps every leaf through `callback` and returns the resulting array.
131
+ */
100
132
  mapDeep<T>(callback: (treeNode: TreeNode) => T): Array<T> {
101
133
  if (typeof callback !== 'function') {
102
134
  throw new TypeError('map() callback must be a function')
@@ -105,7 +137,13 @@ export class TreeNode {
105
137
  return this.leaves.map(callback)
106
138
  }
107
139
 
108
- public static build(files: KubbFile.File[], root?: string): TreeNode | null {
140
+ /**
141
+ * Builds a {@link TreeNode} tree from a flat list of files.
142
+ *
143
+ * - Filters to files under `root` (when provided) and skips `.json` files.
144
+ * - Returns `null` when no files match.
145
+ */
146
+ public static build(files: FileNode[], root?: string): TreeNode | null {
109
147
  try {
110
148
  const filteredTree = buildDirectoryTree(files, root)
111
149
 
@@ -117,7 +155,7 @@ export class TreeNode {
117
155
  name: filteredTree.name,
118
156
  path: filteredTree.path,
119
157
  file: filteredTree.file,
120
- type: getMode(filteredTree.path),
158
+ type: PluginDriver.getMode(filteredTree.path),
121
159
  })
122
160
 
123
161
  const recurse = (node: typeof treeNode, item: DirectoryTree) => {
@@ -125,7 +163,7 @@ export class TreeNode {
125
163
  name: item.name,
126
164
  path: item.path,
127
165
  file: item.file,
128
- type: getMode(item.path),
166
+ type: PluginDriver.getMode(item.path),
129
167
  })
130
168
 
131
169
  if (item.children?.length) {
@@ -149,13 +187,13 @@ export class TreeNode {
149
187
  type DirectoryTree = {
150
188
  name: string
151
189
  path: string
152
- file?: KubbFile.File
190
+ file?: FileNode
153
191
  children: Array<DirectoryTree>
154
192
  }
155
193
 
156
194
  const normalizePath = (p: string): string => p.replaceAll('\\', '/')
157
195
 
158
- function buildDirectoryTree(files: Array<KubbFile.File>, rootFolder = ''): DirectoryTree | null {
196
+ function buildDirectoryTree(files: Array<FileNode>, rootFolder = ''): DirectoryTree | null {
159
197
  const normalizedRootFolder = normalizePath(rootFolder)
160
198
  const rootPrefix = normalizedRootFolder.endsWith('/') ? normalizedRootFolder : `${normalizedRootFolder}/`
161
199
 
@@ -2,7 +2,10 @@ import { version as nodeVersion } from 'node:process'
2
2
  import { version as KubbVersion } from '../../package.json'
3
3
 
4
4
  /**
5
- * Get diagnostic information for debugging
5
+ * Returns a snapshot of the current runtime environment.
6
+ *
7
+ * Useful for attaching context to debug logs and error reports so that
8
+ * issues can be reproduced without manual information gathering.
6
9
  */
7
10
  export function getDiagnosticInfo() {
8
11
  return {
@@ -1,8 +1,16 @@
1
1
  import { join } from 'node:path'
2
- import type { KubbFile } from '@kubb/fabric-core/types'
3
- import { BarrelManager } from '../BarrelManager.ts'
2
+ import { getRelativePath } from '@internals/utils'
3
+ import type { FileNode } from '@kubb/ast'
4
+ import { createExport, createFile, createSource } from '@kubb/ast'
5
+ import { BARREL_BASENAME, BARREL_FILENAME } from '../constants.ts'
4
6
  import type { BarrelType } from '../types.ts'
7
+ import { TreeNode } from './TreeNode.ts'
5
8
 
9
+ /**
10
+ * Minimal file metadata attached to every generated file for barrel-file bookkeeping.
11
+ *
12
+ * @internal
13
+ */
6
14
  export type FileMetaBase = {
7
15
  pluginName?: string
8
16
  }
@@ -10,11 +18,11 @@ export type FileMetaBase = {
10
18
  type AddIndexesProps = {
11
19
  type: BarrelType | false | undefined
12
20
  /**
13
- * Root based on root and output.path specified in the config
21
+ * Absolute output root derived from config `root` and `output.path`.
14
22
  */
15
23
  root: string
16
24
  /**
17
- * Output for plugin
25
+ * Output settings for the plugin.
18
26
  */
19
27
  output: {
20
28
  path: string
@@ -27,6 +35,74 @@ type AddIndexesProps = {
27
35
  meta?: FileMetaBase
28
36
  }
29
37
 
38
+ function getBarrelFilesByRoot(root: string | undefined, files: Array<FileNode>): Array<FileNode> {
39
+ const cachedFiles = new Map<string, FileNode>()
40
+
41
+ TreeNode.build(files, root)?.forEach((treeNode) => {
42
+ if (!treeNode?.children || !treeNode.parent?.data.path) {
43
+ return
44
+ }
45
+
46
+ const barrelFilePath = join(treeNode.parent?.data.path, BARREL_FILENAME)
47
+ const barrelFile = createFile({
48
+ path: barrelFilePath,
49
+ baseName: BARREL_FILENAME,
50
+ exports: [],
51
+ imports: [],
52
+ sources: [],
53
+ })
54
+ const previousBarrelFile = cachedFiles.get(barrelFile.path)
55
+ const leaves = treeNode.leaves
56
+
57
+ leaves.forEach((item) => {
58
+ if (!item.data.name) {
59
+ return
60
+ }
61
+
62
+ const sources = item.data.file?.sources || []
63
+
64
+ sources.forEach((source) => {
65
+ if (!item.data.file?.path || !source.isIndexable || !source.name) {
66
+ return
67
+ }
68
+ const alreadyContainInPreviousBarrelFile = previousBarrelFile?.sources.some(
69
+ (item) => item.name === source.name && item.isTypeOnly === source.isTypeOnly,
70
+ )
71
+
72
+ if (alreadyContainInPreviousBarrelFile) {
73
+ return
74
+ }
75
+
76
+ barrelFile.exports.push(
77
+ createExport({
78
+ name: [source.name],
79
+ path: getRelativePath(treeNode.parent?.data.path, item.data.path),
80
+ isTypeOnly: source.isTypeOnly,
81
+ }),
82
+ )
83
+
84
+ barrelFile.sources.push(
85
+ createSource({
86
+ name: source.name,
87
+ isTypeOnly: source.isTypeOnly,
88
+ isExportable: false,
89
+ isIndexable: false,
90
+ }),
91
+ )
92
+ })
93
+ })
94
+
95
+ if (previousBarrelFile) {
96
+ previousBarrelFile.sources.push(...barrelFile.sources)
97
+ previousBarrelFile.exports.push(...barrelFile.exports)
98
+ } else {
99
+ cachedFiles.set(barrelFile.path, barrelFile)
100
+ }
101
+ })
102
+
103
+ return [...cachedFiles.values()]
104
+ }
105
+
30
106
  function trimExtName(text: string): string {
31
107
  const dotIndex = text.lastIndexOf('.')
32
108
  // Only strip when the dot is found and no path separator follows it
@@ -37,36 +113,38 @@ function trimExtName(text: string): string {
37
113
  return text
38
114
  }
39
115
 
40
- export async function getBarrelFiles(files: Array<KubbFile.ResolvedFile>, { type, meta = {}, root, output }: AddIndexesProps): Promise<KubbFile.File[]> {
116
+ /**
117
+ * Generates `index.ts` barrel files for all directories under `root/output.path`.
118
+ *
119
+ * - Returns an empty array when `type` is falsy or `'propagate'`.
120
+ * - Skips generation when the output path itself ends with `index` (already a barrel).
121
+ * - When `type` is `'all'`, strips named exports so every re-export becomes a wildcard (`export * from`).
122
+ * - Attaches `meta` to each barrel file for downstream plugin identification.
123
+ */
124
+ export async function getBarrelFiles(files: Array<FileNode>, { type, meta = {}, root, output }: AddIndexesProps): Promise<Array<FileNode>> {
41
125
  if (!type || type === 'propagate') {
42
126
  return []
43
127
  }
44
128
 
45
- const barrelManager = new BarrelManager()
46
-
47
129
  const pathToBuildFrom = join(root, output.path)
48
130
 
49
- if (trimExtName(pathToBuildFrom).endsWith('index')) {
131
+ if (trimExtName(pathToBuildFrom).endsWith(BARREL_BASENAME)) {
50
132
  return []
51
133
  }
52
134
 
53
- const barrelFiles = barrelManager.getFiles({
54
- files,
55
- root: pathToBuildFrom,
56
- meta,
57
- })
135
+ const barrelFiles = getBarrelFilesByRoot(pathToBuildFrom, files)
58
136
 
59
137
  if (type === 'all') {
60
138
  return barrelFiles.map((file) => {
61
139
  return {
62
140
  ...file,
63
- exports: file.exports?.map((exportItem) => {
141
+ exports: file.exports.map((exportItem) => {
64
142
  return {
65
143
  ...exportItem,
66
144
  name: undefined,
67
145
  }
68
146
  }),
69
- }
147
+ } as FileNode
70
148
  })
71
149
  }
72
150
 
@@ -74,6 +152,6 @@ export async function getBarrelFiles(files: Array<KubbFile.ResolvedFile>, { type
74
152
  return {
75
153
  ...indexFile,
76
154
  meta,
77
- }
155
+ } as FileNode
78
156
  })
79
157
  }
@@ -0,0 +1,10 @@
1
+ import type { Config, InputPath, UserConfig } from '../types'
2
+
3
+ /**
4
+ * Type guard to check if a given config has an `input.path`.
5
+ */
6
+ export function isInputPath(config: UserConfig | undefined): config is UserConfig<InputPath>
7
+ export function isInputPath(config: Config | undefined): config is Config<InputPath>
8
+ export function isInputPath(config: Config | UserConfig | undefined): config is Config<InputPath> | UserConfig<InputPath> {
9
+ return typeof config?.input === 'object' && config.input !== null && 'path' in config.input
10
+ }
@@ -0,0 +1,99 @@
1
+ import { findPackageJSON, readSync } from '@internals/utils'
2
+
3
+ type PackageJSON = {
4
+ dependencies?: Record<string, string>
5
+ devDependencies?: Record<string, string>
6
+ }
7
+
8
+ type DependencyName = string
9
+ type DependencyVersion = string
10
+
11
+ function getPackageJSONSync(cwd?: string): PackageJSON | null {
12
+ const pkgPath = findPackageJSON(cwd)
13
+ if (!pkgPath) {
14
+ return null
15
+ }
16
+
17
+ return JSON.parse(readSync(pkgPath)) as PackageJSON
18
+ }
19
+
20
+ function match(packageJSON: PackageJSON, dependency: DependencyName | RegExp): string | null {
21
+ const dependencies = {
22
+ ...(packageJSON.dependencies || {}),
23
+ ...(packageJSON.devDependencies || {}),
24
+ }
25
+
26
+ if (typeof dependency === 'string' && dependencies[dependency]) {
27
+ return dependencies[dependency]
28
+ }
29
+
30
+ const matched = Object.keys(dependencies).find((dep) => dep.match(dependency))
31
+
32
+ return matched ? (dependencies[matched] ?? null) : null
33
+ }
34
+
35
+ function getVersionSync(dependency: DependencyName | RegExp, cwd?: string): DependencyVersion | null {
36
+ const packageJSON = getPackageJSONSync(cwd)
37
+
38
+ return packageJSON ? match(packageJSON, dependency) : null
39
+ }
40
+
41
+ /**
42
+ * Returns `true` when the nearest `package.json` declares a dependency that
43
+ * satisfies the given semver range.
44
+ *
45
+ * - Searches both `dependencies` and `devDependencies`.
46
+ * - Accepts a string package name or a `RegExp` to match scoped/pattern packages.
47
+ * - Uses `semver.satisfies` for range comparison; returns `false` when the
48
+ * version string cannot be coerced into a valid semver.
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * satisfiesDependency('react', '>=18') // true when react@18.x is installed
53
+ * satisfiesDependency(/^@tanstack\//, '>=5') // true when any @tanstack/* >=5 is found
54
+ * ```
55
+ */
56
+ function coerceSemver(version: string): [number, number, number] | null {
57
+ const m = version.match(/(\d+)(?:\.(\d+))?(?:\.(\d+))?/)
58
+ return m ? [Number(m[1]), Number(m[2] ?? 0), Number(m[3] ?? 0)] : null
59
+ }
60
+
61
+ function satisfiesSemver(v: [number, number, number], range: string): boolean {
62
+ return range
63
+ .trim()
64
+ .split(/\s+/)
65
+ .every((cond) => {
66
+ const m = cond.match(/^(>=|<=|>|<|=|\^|~)?(\d+)(?:\.(\d+))?(?:\.(\d+))?$/)
67
+ if (!m) return false
68
+ const op = m[1] ?? '='
69
+ const r: [number, number, number] = [Number(m[2]), Number(m[3] ?? 0), Number(m[4] ?? 0)]
70
+ const cmp = v[0] !== r[0] ? v[0] - r[0] : v[1] !== r[1] ? v[1] - r[1] : v[2] - r[2]
71
+ if (op === '>=') return cmp >= 0
72
+ if (op === '<=') return cmp <= 0
73
+ if (op === '>') return cmp > 0
74
+ if (op === '<') return cmp < 0
75
+ if (op === '^') return v[0] === r[0] && cmp >= 0
76
+ if (op === '~') return v[0] === r[0] && v[1] === r[1] && cmp >= 0
77
+ return cmp === 0
78
+ })
79
+ }
80
+
81
+ export function satisfiesDependency(dependency: DependencyName | RegExp, version: DependencyVersion, cwd?: string): boolean {
82
+ const packageVersion = getVersionSync(dependency, cwd)
83
+
84
+ if (!packageVersion) {
85
+ return false
86
+ }
87
+
88
+ if (packageVersion === version) {
89
+ return true
90
+ }
91
+
92
+ const semVer = coerceSemver(packageVersion)
93
+
94
+ if (!semVer) {
95
+ return false
96
+ }
97
+
98
+ return satisfiesSemver(semVer, version)
99
+ }