@kubb/core 5.0.0-alpha.16 → 5.0.0-alpha.18

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/core",
3
- "version": "5.0.0-alpha.16",
3
+ "version": "5.0.0-alpha.18",
4
4
  "description": "Core functionality for Kubb's plugin-based code generation system, providing the foundation for transforming OpenAPI specifications.",
5
5
  "keywords": [
6
6
  "typescript",
@@ -64,14 +64,14 @@
64
64
  }
65
65
  ],
66
66
  "dependencies": {
67
- "@kubb/fabric-core": "0.14.0",
68
- "@kubb/react-fabric": "0.14.0",
67
+ "@kubb/fabric-core": "0.15.1",
68
+ "@kubb/react-fabric": "0.15.1",
69
69
  "empathic": "^2.0.0",
70
70
  "fflate": "^0.8.2",
71
71
  "remeda": "^2.33.6",
72
72
  "semver": "^7.7.4",
73
73
  "tinyexec": "^1.0.4",
74
- "@kubb/ast": "5.0.0-alpha.16"
74
+ "@kubb/ast": "5.0.0-alpha.18"
75
75
  },
76
76
  "devDependencies": {
77
77
  "@types/semver": "^7.7.1",
package/src/Kubb.ts CHANGED
@@ -167,15 +167,25 @@ export interface KubbEvents {
167
167
  */
168
168
  'file:processing:update': [
169
169
  {
170
- /** Number of files processed so far. */
170
+ /**
171
+ * Number of files processed so far.
172
+ */
171
173
  processed: number
172
- /** Total number of files to process. */
174
+ /**
175
+ * Total number of files to process.
176
+ */
173
177
  total: number
174
- /** Processing percentage (0–100). */
178
+ /**
179
+ * Processing percentage (0–100).
180
+ */
175
181
  percentage: number
176
- /** Optional source identifier. */
182
+ /**
183
+ * Optional source identifier.
184
+ */
177
185
  source?: string
178
- /** The file being processed. */
186
+ /**
187
+ * The file being processed.
188
+ */
179
189
  file: KubbFile.ResolvedFile
180
190
  /**
181
191
  * Kubb configuration (not present in Fabric).
package/src/build.ts CHANGED
@@ -17,16 +17,31 @@ type BuildOptions = {
17
17
  events?: AsyncEventEmitter<KubbEvents>
18
18
  }
19
19
 
20
+ /**
21
+ * Full output produced by a successful or failed build.
22
+ */
20
23
  type BuildOutput = {
24
+ /**
25
+ * Plugins that threw during installation, paired with the caught error.
26
+ */
21
27
  failedPlugins: Set<{ plugin: Plugin; error: Error }>
22
28
  fabric: FabricType
23
29
  files: Array<KubbFile.ResolvedFile>
24
30
  driver: PluginDriver
31
+ /**
32
+ * Elapsed time in milliseconds for each plugin, keyed by plugin name.
33
+ */
25
34
  pluginTimings: Map<string, number>
26
35
  error?: Error
36
+ /**
37
+ * Raw generated source, keyed by absolute file path.
38
+ */
27
39
  sources: Map<KubbFile.Path, string>
28
40
  }
29
41
 
42
+ /**
43
+ * Intermediate result returned by {@link setup} and accepted by {@link safeBuild}.
44
+ */
30
45
  type SetupResult = {
31
46
  events: AsyncEventEmitter<KubbEvents>
32
47
  fabric: FabricType
@@ -34,6 +49,17 @@ type SetupResult = {
34
49
  sources: Map<KubbFile.Path, string>
35
50
  }
36
51
 
52
+ /**
53
+ * Initializes all Kubb infrastructure for a build without executing any plugins.
54
+ *
55
+ * - Validates the input path (when applicable).
56
+ * - Applies config defaults (`root`, `output.*`, `devtools`).
57
+ * - Creates the Fabric instance and wires storage, format, and lint hooks.
58
+ * - Runs the adapter (if configured) to produce the universal `RootNode`.
59
+ *
60
+ * Pass the returned {@link SetupResult} directly to {@link safeBuild} or {@link build}
61
+ * via the `overrides` argument to reuse the same infrastructure across multiple runs.
62
+ */
37
63
  export async function setup(options: BuildOptions): Promise<SetupResult> {
38
64
  const { config: userConfig, events = new AsyncEventEmitter<KubbEvents>() } = options
39
65
 
@@ -199,6 +225,12 @@ export async function setup(options: BuildOptions): Promise<SetupResult> {
199
225
  }
200
226
  }
201
227
 
228
+ /**
229
+ * Runs a full Kubb build and throws on any error or plugin failure.
230
+ *
231
+ * Internally delegates to {@link safeBuild} and rethrows collected errors.
232
+ * Pass an existing {@link SetupResult} via `overrides` to skip the setup phase.
233
+ */
202
234
  export async function build(options: BuildOptions, overrides?: SetupResult): Promise<BuildOutput> {
203
235
  const { fabric, files, driver, failedPlugins, pluginTimings, error, sources } = await safeBuild(options, overrides)
204
236
 
@@ -223,6 +255,16 @@ export async function build(options: BuildOptions, overrides?: SetupResult): Pro
223
255
  }
224
256
  }
225
257
 
258
+ /**
259
+ * Runs a full Kubb build and captures errors instead of throwing.
260
+ *
261
+ * - Installs each plugin in order, recording failures in `failedPlugins`.
262
+ * - Generates the root barrel file when `output.barrelType` is set.
263
+ * - Writes all files through Fabric.
264
+ *
265
+ * Returns a {@link BuildOutput} even on failure — inspect `error` and
266
+ * `failedPlugins` to determine whether the build succeeded.
267
+ */
226
268
  export async function safeBuild(options: BuildOptions, overrides?: SetupResult): Promise<BuildOutput> {
227
269
  const { fabric, driver, events, sources } = overrides ? overrides : await setup(options)
228
270
 
package/src/config.ts CHANGED
@@ -5,12 +5,14 @@ import type { InputPath, UserConfig } from './types.ts'
5
5
  * CLI options derived from command-line flags.
6
6
  */
7
7
  export type CLIOptions = {
8
- /** Path to `kubb.config.js` */
8
+ /**
9
+ * Path to `kubb.config.js`.
10
+ */
9
11
  config?: string
10
-
11
- /** Enable watch mode for input files */
12
+ /**
13
+ * Enable watch mode for input files.
14
+ */
12
15
  watch?: boolean
13
-
14
16
  /**
15
17
  * Logging verbosity for CLI usage.
16
18
  *
@@ -20,12 +22,11 @@ export type CLIOptions = {
20
22
  * @default 'silent'
21
23
  */
22
24
  logLevel?: 'silent' | 'info' | 'debug'
23
-
24
- /** Run Kubb with Bun */
25
- bun?: boolean
26
25
  }
27
26
 
28
- /** All accepted forms of a Kubb configuration. */
27
+ /**
28
+ * All accepted forms of a Kubb configuration.
29
+ */
29
30
  export type ConfigInput = PossiblePromise<UserConfig | UserConfig[]> | ((cli: CLIOptions) => PossiblePromise<UserConfig | UserConfig[]>)
30
31
 
31
32
  /**
package/src/constants.ts CHANGED
@@ -1,21 +1,50 @@
1
1
  import type { KubbFile } from '@kubb/fabric-core/types'
2
2
 
3
+ /**
4
+ * Base URL for the Kubb Studio web app.
5
+ */
3
6
  export const DEFAULT_STUDIO_URL = 'https://studio.kubb.dev' as const
4
7
 
8
+ /**
9
+ * Internal plugin name used to identify the core Kubb runtime.
10
+ */
5
11
  export const CORE_PLUGIN_NAME = 'core' as const
6
12
 
13
+ /**
14
+ * Maximum number of event-emitter listeners before Node.js emits a warning.
15
+ */
7
16
  export const DEFAULT_MAX_LISTENERS = 100
8
17
 
18
+ /**
19
+ * Default number of plugins that may run concurrently during a build.
20
+ */
9
21
  export const DEFAULT_CONCURRENCY = 15
10
22
 
23
+ /**
24
+ * File name used for generated barrel (index) files.
25
+ */
11
26
  export const BARREL_FILENAME = 'index.ts' as const
12
27
 
28
+ /**
29
+ * Default banner style written at the top of every generated file.
30
+ */
13
31
  export const DEFAULT_BANNER = 'simple' as const
14
32
 
33
+ /**
34
+ * Default file-extension mapping used when no explicit mapping is configured.
35
+ */
15
36
  export const DEFAULT_EXTENSION: Record<KubbFile.Extname, KubbFile.Extname | ''> = { '.ts': '.ts' }
16
37
 
38
+ /**
39
+ * Characters recognized as path separators on both POSIX and Windows.
40
+ */
17
41
  export const PATH_SEPARATORS = new Set(['/', '\\'] as const)
18
42
 
43
+ /**
44
+ * Numeric log-level thresholds used internally to compare verbosity.
45
+ *
46
+ * Higher numbers are more verbose.
47
+ */
19
48
  export const logLevel = {
20
49
  silent: Number.NEGATIVE_INFINITY,
21
50
  error: 0,
@@ -25,6 +54,13 @@ export const logLevel = {
25
54
  debug: 5,
26
55
  } as const
27
56
 
57
+ /**
58
+ * CLI command descriptors for each supported linter.
59
+ *
60
+ * Each entry contains the executable `command`, an `args` factory that maps an
61
+ * output path to the correct argument list, and an `errorMessage` shown when
62
+ * the linter is not found.
63
+ */
28
64
  export const linters = {
29
65
  eslint: {
30
66
  command: 'eslint',
@@ -43,6 +79,13 @@ export const linters = {
43
79
  },
44
80
  } as const
45
81
 
82
+ /**
83
+ * CLI command descriptors for each supported code formatter.
84
+ *
85
+ * Each entry contains the executable `command`, an `args` factory that maps an
86
+ * output path to the correct argument list, and an `errorMessage` shown when
87
+ * the formatter is not found.
88
+ */
46
89
  export const formatters = {
47
90
  prettier: {
48
91
  command: 'prettier',
@@ -97,6 +97,22 @@ function buildDefaultBanner({ title, description, version, config }: { title?: s
97
97
  }
98
98
  }
99
99
 
100
+ /**
101
+ * React-Fabric hook that exposes the current plugin context inside a generator component.
102
+ *
103
+ * Returns the active `plugin`, `mode`, `config`, and a set of resolver helpers
104
+ * (`getFile`, `resolveName`, `resolvePath`, `resolveBanner`, `resolveFooter`) that
105
+ * all default to the current plugin when no explicit `pluginName` is provided.
106
+ *
107
+ * @example
108
+ * ```ts
109
+ * function Operation({ node }: OperationProps) {
110
+ * const { config, resolvePath } = useKubb()
111
+ * const filePath = resolvePath({ baseName: node.operationId })
112
+ * return <File path={filePath}>...</File>
113
+ * }
114
+ * ```
115
+ */
100
116
  export function useKubb<TOptions extends PluginFactoryOptions = PluginFactoryOptions>(): UseKubbReturn<TOptions> {
101
117
  const { meta } = useFabric<{
102
118
  plugin: Plugin<TOptions>
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export { AsyncEventEmitter, URLPath } from '@internals/utils'
1
2
  export { definePrinter } from '@kubb/ast'
2
3
  export { build, build as default, safeBuild, setup } from './build.ts'
3
4
  export { type CLIOptions, type ConfigInput, defineConfig, isInputPath } from './config.ts'
package/src/types.ts CHANGED
@@ -91,11 +91,17 @@ export type AdapterFactoryOptions<TName extends string = string, TOptions extend
91
91
  * ```
92
92
  */
93
93
  export type Adapter<TOptions extends AdapterFactoryOptions = AdapterFactoryOptions> = {
94
- /** Human-readable identifier, e.g. `'oas'`, `'drizzle'`, `'asyncapi'`. */
94
+ /**
95
+ * Human-readable identifier, e.g. `'oas'`, `'drizzle'`, `'asyncapi'`.
96
+ */
95
97
  name: TOptions['name']
96
- /** Resolved options (after defaults have been applied). */
98
+ /**
99
+ * Resolved options (after defaults have been applied).
100
+ */
97
101
  options: TOptions['resolvedOptions']
98
- /** Convert the raw source into a universal `RootNode`. */
102
+ /**
103
+ * Convert the raw source into a universal `RootNode`.
104
+ */
99
105
  parse: (source: AdapterSource) => PossiblePromise<RootNode>
100
106
  /**
101
107
  * Extracts `KubbFile.Import` entries needed by a `SchemaNode` tree.
@@ -12,6 +12,15 @@ type BarrelData = {
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 KubbFile.File} 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
@@ -105,6 +122,12 @@ export class TreeNode {
105
122
  return this.leaves.map(callback)
106
123
  }
107
124
 
125
+ /**
126
+ * Builds a {@link TreeNode} tree from a flat list of files.
127
+ *
128
+ * - Filters to files under `root` (when provided) and skips `.json` files.
129
+ * - Returns `null` when no files match.
130
+ */
108
131
  public static build(files: KubbFile.File[], root?: string): TreeNode | null {
109
132
  try {
110
133
  const filteredTree = buildDirectoryTree(files, root)
@@ -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 {
@@ -7,7 +7,11 @@ type ValueOfPromiseFuncArray<TInput extends Array<unknown>> = TInput extends Arr
7
7
  type SeqOutput<TInput extends Array<PromiseFunc<TValue, null>>, TValue> = Promise<Array<Awaited<ValueOfPromiseFuncArray<TInput>>>>
8
8
 
9
9
  /**
10
- * Chains promises
10
+ * Runs promise functions in sequence, threading each result into the next call.
11
+ *
12
+ * - Each function receives the accumulated state from the previous call.
13
+ * - Skips functions that return a falsy value (acts as a no-op for that step).
14
+ * - Returns an array of all individual results.
11
15
  */
12
16
  export function hookSeq<TInput extends Array<PromiseFunc<TValue, null>>, TValue, TOutput = SeqOutput<TInput, TValue>>(promises: TInput): TOutput {
13
17
  return promises.filter(Boolean).reduce(
@@ -33,7 +37,10 @@ export function hookSeq<TInput extends Array<PromiseFunc<TValue, null>>, TValue,
33
37
  type HookFirstOutput<TInput extends Array<PromiseFunc<TValue, null>>, TValue = unknown> = ValueOfPromiseFuncArray<TInput>
34
38
 
35
39
  /**
36
- * Chains promises, first non-null result stops and returns
40
+ * Runs promise functions in sequence and returns the first non-null result.
41
+ *
42
+ * - Stops as soon as `nullCheck` passes for a result (default: `!== null`).
43
+ * - Subsequent functions are skipped once a match is found.
37
44
  */
38
45
  export function hookFirst<TInput extends Array<PromiseFunc<TValue, null>>, TValue = unknown, TOutput = HookFirstOutput<TInput, TValue>>(
39
46
  promises: TInput,
@@ -57,7 +64,10 @@ export function hookFirst<TInput extends Array<PromiseFunc<TValue, null>>, TValu
57
64
  type HookParallelOutput<TInput extends Array<PromiseFunc<TValue, null>>, TValue> = Promise<PromiseSettledResult<Awaited<ValueOfPromiseFuncArray<TInput>>>[]>
58
65
 
59
66
  /**
60
- * Runs an array of promise functions with optional concurrency limit.
67
+ * Runs promise functions concurrently and returns all settled results.
68
+ *
69
+ * - Limits simultaneous executions to `concurrency` (default: unlimited).
70
+ * - Uses `Promise.allSettled` so individual failures do not cancel other tasks.
61
71
  */
62
72
  export function hookParallel<TInput extends Array<PromiseFunc<TValue, null>>, TValue = unknown, TOutput = HookParallelOutput<TInput, TValue>>(
63
73
  promises: TInput,
@@ -70,6 +80,9 @@ export function hookParallel<TInput extends Array<PromiseFunc<TValue, null>>, TV
70
80
  return Promise.allSettled(tasks) as TOutput
71
81
  }
72
82
 
83
+ /**
84
+ * Execution strategy used when dispatching plugin hook calls.
85
+ */
73
86
  export type Strategy = 'seq' | 'first' | 'parallel'
74
87
 
75
88
  type StrategyOutputMap<TInput extends Array<PromiseFunc<TValue, null>>, TValue> = {
@@ -4,18 +4,13 @@ import type { formatters } from '../constants.ts'
4
4
  type Formatter = keyof typeof formatters
5
5
 
6
6
  /**
7
- * Check if a formatter command is available in the system.
7
+ * Returns `true` when the given formatter is installed and callable.
8
8
  *
9
- * @param formatter - The formatter to check ('biome', 'prettier', or 'oxfmt')
10
- * @returns Promise that resolves to true if the formatter is available, false otherwise
11
- *
12
- * @remarks
13
- * This function checks availability by running `<formatter> --version` command.
14
- * All supported formatters (biome, prettier, oxfmt) implement the --version flag.
9
+ * Availability is detected by running `<formatter> --version` and checking
10
+ * that the process exits without error.
15
11
  */
16
12
  async function isFormatterAvailable(formatter: Formatter): Promise<boolean> {
17
13
  try {
18
- // Try to get the version of the formatter to check if it's installed
19
14
  await x(formatter, ['--version'], { nodeOptions: { stdio: 'ignore' } })
20
15
  return true
21
16
  } catch {
@@ -24,26 +19,20 @@ async function isFormatterAvailable(formatter: Formatter): Promise<boolean> {
24
19
  }
25
20
 
26
21
  /**
27
- * Detect which formatter is available in the system.
28
- *
29
- * @returns Promise that resolves to the first available formatter or undefined if none are found
22
+ * Detects the first available code formatter on the current system.
30
23
  *
31
- * @remarks
32
- * Checks in order of preference: biome, oxfmt, prettier.
33
- * Uses the `--version` flag to detect if each formatter command is available.
34
- * This is a reliable method as all supported formatters implement this flag.
24
+ * - Checks in preference order: `biome`, `oxfmt`, `prettier`.
25
+ * - Returns `null` when none are found.
35
26
  *
36
27
  * @example
37
- * ```typescript
28
+ * ```ts
38
29
  * const formatter = await detectFormatter()
39
30
  * if (formatter) {
40
31
  * console.log(`Using ${formatter} for formatting`)
41
- * } else {
42
- * console.log('No formatter found')
43
32
  * }
44
33
  * ```
45
34
  */
46
- export async function detectFormatter(): Promise<Formatter | undefined> {
35
+ export async function detectFormatter(): Promise<Formatter | null> {
47
36
  const formatterNames = new Set(['biome', 'oxfmt', 'prettier'] as const)
48
37
 
49
38
  for (const formatter of formatterNames) {
@@ -52,5 +41,5 @@ export async function detectFormatter(): Promise<Formatter | undefined> {
52
41
  }
53
42
  }
54
43
 
55
- return undefined
44
+ return null
56
45
  }
@@ -105,6 +105,14 @@ function trimExtName(text: string): string {
105
105
  return text
106
106
  }
107
107
 
108
+ /**
109
+ * Generates `index.ts` barrel files for all directories under `root/output.path`.
110
+ *
111
+ * - Returns an empty array when `type` is falsy or `'propagate'`.
112
+ * - Skips generation when the output path itself ends with `index` (already a barrel).
113
+ * - When `type` is `'all'`, strips named exports so every re-export becomes a wildcard (`export * from`).
114
+ * - Attaches `meta` to each barrel file for downstream plugin identification.
115
+ */
108
116
  export async function getBarrelFiles(files: Array<KubbFile.ResolvedFile>, { type, meta = {}, root, output }: AddIndexesProps): Promise<Array<KubbFile.File>> {
109
117
  if (!type || type === 'propagate') {
110
118
  return []
@@ -2,7 +2,11 @@ import type { CLIOptions, ConfigInput } from '../config.ts'
2
2
  import type { Config, UserConfig } from '../types.ts'
3
3
 
4
4
  /**
5
- * Converting UserConfig to Config Array without a change in the object beside the JSON convert.
5
+ * Resolves a {@link ConfigInput} into a normalized array of {@link Config} objects.
6
+ *
7
+ * - Awaits the config when it is a `Promise`.
8
+ * - Calls the factory function with `args` when the config is a function.
9
+ * - Wraps a single config object in an array for uniform downstream handling.
6
10
  */
7
11
  export async function getConfigs(config: ConfigInput | UserConfig, args: CLIOptions): Promise<Array<Config>> {
8
12
  const resolved = await (typeof config === 'function' ? config(args as CLIOptions) : config)
@@ -16,6 +16,13 @@ type GetPresetResult<TResolver extends Resolver> = {
16
16
  preset: Preset<TResolver> | undefined
17
17
  }
18
18
 
19
+ /**
20
+ * Resolves a named preset into merged resolvers and transformers.
21
+ *
22
+ * - Merges the preset's resolvers on top of the first (default) resolver to produce `baseResolver`.
23
+ * - Merges any additional user-supplied resolvers on top of that to produce the final `resolver`.
24
+ * - Concatenates preset transformers before user-supplied transformers.
25
+ */
19
26
  export function getPreset<TResolver extends Resolver = Resolver>(params: GetPresetParams<TResolver>): GetPresetResult<TResolver> {
20
27
  const { preset: presetName, presets, resolvers, transformers: userTransformers } = params
21
28
  const [defaultResolver, ...userResolvers] = resolvers
@@ -3,6 +3,12 @@ import type { linters } from '../constants.ts'
3
3
 
4
4
  type Linter = keyof typeof linters
5
5
 
6
+ /**
7
+ * Returns `true` when the given linter is installed and callable.
8
+ *
9
+ * Availability is detected by running `<linter> --version` and checking
10
+ * that the process exits without error.
11
+ */
6
12
  async function isLinterAvailable(linter: Linter): Promise<boolean> {
7
13
  try {
8
14
  await x(linter, ['--version'], { nodeOptions: { stdio: 'ignore' } })
@@ -12,7 +18,21 @@ async function isLinterAvailable(linter: Linter): Promise<boolean> {
12
18
  }
13
19
  }
14
20
 
15
- export async function detectLinter(): Promise<Linter | undefined> {
21
+ /**
22
+ * Detects the first available linter on the current system.
23
+ *
24
+ * - Checks in preference order: `biome`, `oxlint`, `eslint`.
25
+ * - Returns `null` when none are found.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * const linter = await detectLinter()
30
+ * if (linter) {
31
+ * console.log(`Using ${linter} for linting`)
32
+ * }
33
+ * ```
34
+ */
35
+ export async function detectLinter(): Promise<Linter | null> {
16
36
  const linterNames = new Set(['biome', 'oxlint', 'eslint'] as const)
17
37
 
18
38
  for (const linter of linterNames) {
@@ -21,5 +41,5 @@ export async function detectLinter(): Promise<Linter | undefined> {
21
41
  }
22
42
  }
23
43
 
24
- return undefined
44
+ return null
25
45
  }
@@ -10,16 +10,16 @@ type PackageJSON = {
10
10
  type DependencyName = string
11
11
  type DependencyVersion = string
12
12
 
13
- function getPackageJSONSync(cwd?: string): PackageJSON | undefined {
13
+ function getPackageJSONSync(cwd?: string): PackageJSON | null {
14
14
  const pkgPath = pkg.up({ cwd })
15
15
  if (!pkgPath) {
16
- return undefined
16
+ return null
17
17
  }
18
18
 
19
19
  return JSON.parse(readSync(pkgPath)) as PackageJSON
20
20
  }
21
21
 
22
- function match(packageJSON: PackageJSON, dependency: DependencyName | RegExp): string | undefined {
22
+ function match(packageJSON: PackageJSON, dependency: DependencyName | RegExp): string | null {
23
23
  const dependencies = {
24
24
  ...(packageJSON.dependencies || {}),
25
25
  ...(packageJSON.devDependencies || {}),
@@ -31,15 +31,30 @@ function match(packageJSON: PackageJSON, dependency: DependencyName | RegExp): s
31
31
 
32
32
  const matched = Object.keys(dependencies).find((dep) => dep.match(dependency))
33
33
 
34
- return matched ? dependencies[matched] : undefined
34
+ return matched ? (dependencies[matched] ?? null) : null
35
35
  }
36
36
 
37
- function getVersionSync(dependency: DependencyName | RegExp, cwd?: string): DependencyVersion | undefined {
37
+ function getVersionSync(dependency: DependencyName | RegExp, cwd?: string): DependencyVersion | null {
38
38
  const packageJSON = getPackageJSONSync(cwd)
39
39
 
40
- return packageJSON ? match(packageJSON, dependency) : undefined
40
+ return packageJSON ? match(packageJSON, dependency) : null
41
41
  }
42
42
 
43
+ /**
44
+ * Returns `true` when the nearest `package.json` declares a dependency that
45
+ * satisfies the given semver range.
46
+ *
47
+ * - Searches both `dependencies` and `devDependencies`.
48
+ * - Accepts a string package name or a `RegExp` to match scoped/pattern packages.
49
+ * - Uses `semver.satisfies` for range comparison; returns `false` when the
50
+ * version string cannot be coerced into a valid semver.
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * satisfiesDependency('react', '>=18') // true when react@18.x is installed
55
+ * satisfiesDependency(/^@tanstack\//, '>=5') // true when any @tanstack/* >=5 is found
56
+ * ```
57
+ */
43
58
  export function satisfiesDependency(dependency: DependencyName | RegExp, version: DependencyVersion, cwd?: string): boolean {
44
59
  const packageVersion = getVersionSync(dependency, cwd)
45
60