@kubb/core 5.0.0-alpha.2 → 5.0.0-alpha.21

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 (55) hide show
  1. package/dist/{types-B7eZvqwD.d.ts → PluginDriver-CEQPafXV.d.ts} +687 -298
  2. package/dist/hooks.cjs +15 -9
  3. package/dist/hooks.cjs.map +1 -1
  4. package/dist/hooks.d.ts +11 -5
  5. package/dist/hooks.js +16 -10
  6. package/dist/hooks.js.map +1 -1
  7. package/dist/index.cjs +1131 -536
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.d.ts +674 -89
  10. package/dist/index.js +1114 -532
  11. package/dist/index.js.map +1 -1
  12. package/package.json +6 -6
  13. package/src/Kubb.ts +37 -55
  14. package/src/{PluginManager.ts → PluginDriver.ts} +51 -40
  15. package/src/build.ts +74 -29
  16. package/src/config.ts +9 -8
  17. package/src/constants.ts +44 -1
  18. package/src/createAdapter.ts +25 -0
  19. package/src/createPlugin.ts +28 -0
  20. package/src/createStorage.ts +58 -0
  21. package/src/defineBuilder.ts +26 -0
  22. package/src/defineGenerator.ts +137 -0
  23. package/src/defineLogger.ts +13 -3
  24. package/src/definePreset.ts +27 -0
  25. package/src/definePresets.ts +16 -0
  26. package/src/defineResolver.ts +448 -0
  27. package/src/hooks/index.ts +1 -1
  28. package/src/hooks/useDriver.ts +8 -0
  29. package/src/hooks/useMode.ts +5 -2
  30. package/src/hooks/usePlugin.ts +5 -2
  31. package/src/index.ts +21 -6
  32. package/src/renderNode.tsx +105 -0
  33. package/src/storages/fsStorage.ts +2 -2
  34. package/src/storages/memoryStorage.ts +2 -2
  35. package/src/types.ts +342 -42
  36. package/src/utils/FunctionParams.ts +2 -2
  37. package/src/utils/TreeNode.ts +24 -1
  38. package/src/utils/diagnostics.ts +4 -1
  39. package/src/utils/executeStrategies.ts +23 -10
  40. package/src/utils/formatters.ts +10 -21
  41. package/src/utils/getBarrelFiles.ts +79 -9
  42. package/src/utils/getConfigs.ts +8 -22
  43. package/src/utils/getPreset.ts +52 -0
  44. package/src/utils/linters.ts +23 -3
  45. package/src/utils/mergeResolvers.ts +8 -0
  46. package/src/utils/packageJSON.ts +76 -0
  47. package/src/BarrelManager.ts +0 -74
  48. package/src/PackageManager.ts +0 -180
  49. package/src/PromiseManager.ts +0 -40
  50. package/src/defineAdapter.ts +0 -22
  51. package/src/definePlugin.ts +0 -12
  52. package/src/defineStorage.ts +0 -56
  53. package/src/errors.ts +0 -1
  54. package/src/hooks/usePluginManager.ts +0 -8
  55. package/src/utils/getPlugins.ts +0 -23
@@ -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,27 +19,21 @@ 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> {
47
- const formatterNames: Formatter[] = ['biome', 'oxfmt', 'prettier']
35
+ export async function detectFormatter(): Promise<Formatter | null> {
36
+ const formatterNames = new Set(['biome', 'oxfmt', 'prettier'] as const)
48
37
 
49
38
  for (const formatter of formatterNames) {
50
39
  if (await isFormatterAvailable(formatter)) {
@@ -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
  }
@@ -1,7 +1,9 @@
1
+ /** biome-ignore-all lint/suspicious/useIterableCallbackReturn: not needed */
1
2
  import { join } from 'node:path'
3
+ import { getRelativePath } from '@internals/utils'
2
4
  import type { KubbFile } from '@kubb/fabric-core/types'
3
- import { BarrelManager } from '../BarrelManager.ts'
4
5
  import type { BarrelType } from '../types.ts'
6
+ import { TreeNode } from './TreeNode.ts'
5
7
 
6
8
  export type FileMetaBase = {
7
9
  pluginName?: string
@@ -27,6 +29,72 @@ type AddIndexesProps = {
27
29
  meta?: FileMetaBase
28
30
  }
29
31
 
32
+ function getBarrelFilesByRoot(root: string | undefined, files: Array<KubbFile.ResolvedFile>): Array<KubbFile.File> {
33
+ const cachedFiles = new Map<KubbFile.Path, KubbFile.File>()
34
+
35
+ TreeNode.build(files, root)?.forEach((treeNode) => {
36
+ if (!treeNode?.children || !treeNode.parent?.data.path) {
37
+ return
38
+ }
39
+
40
+ const barrelFilePath = join(treeNode.parent?.data.path, 'index.ts') as KubbFile.Path
41
+ const barrelFile: KubbFile.File = {
42
+ path: barrelFilePath,
43
+ baseName: 'index.ts',
44
+ exports: [],
45
+ imports: [],
46
+ sources: [],
47
+ }
48
+ const previousBarrelFile = cachedFiles.get(barrelFile.path)
49
+ const leaves = treeNode.leaves
50
+
51
+ leaves.forEach((item) => {
52
+ if (!item.data.name) {
53
+ return
54
+ }
55
+
56
+ const sources = item.data.file?.sources || []
57
+
58
+ sources.forEach((source) => {
59
+ if (!item.data.file?.path || !source.isIndexable || !source.name) {
60
+ return
61
+ }
62
+ const alreadyContainInPreviousBarrelFile = previousBarrelFile?.sources.some(
63
+ (item) => item.name === source.name && item.isTypeOnly === source.isTypeOnly,
64
+ )
65
+
66
+ if (alreadyContainInPreviousBarrelFile) {
67
+ return
68
+ }
69
+
70
+ barrelFile.exports!.push({
71
+ name: [source.name],
72
+ path: getRelativePath(treeNode.parent?.data.path, item.data.path),
73
+ isTypeOnly: source.isTypeOnly,
74
+ })
75
+
76
+ barrelFile.sources.push({
77
+ name: source.name,
78
+ isTypeOnly: source.isTypeOnly,
79
+ //TODO use parser to generate import
80
+ value: '',
81
+ isExportable: false,
82
+ isIndexable: false,
83
+ })
84
+ })
85
+ })
86
+
87
+ if (previousBarrelFile) {
88
+ previousBarrelFile.sources.push(...barrelFile.sources)
89
+ previousBarrelFile.exports?.push(...(barrelFile.exports || []))
90
+ } else {
91
+ cachedFiles.set(barrelFile.path, barrelFile)
92
+ }
93
+ })
94
+
95
+ return [...cachedFiles.values()]
96
+ }
97
+
30
98
  function trimExtName(text: string): string {
31
99
  const dotIndex = text.lastIndexOf('.')
32
100
  // Only strip when the dot is found and no path separator follows it
@@ -37,24 +105,26 @@ function trimExtName(text: string): string {
37
105
  return text
38
106
  }
39
107
 
40
- export async function getBarrelFiles(files: Array<KubbFile.ResolvedFile>, { type, meta = {}, root, output }: AddIndexesProps): Promise<KubbFile.File[]> {
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
+ */
116
+ export async function getBarrelFiles(files: Array<KubbFile.ResolvedFile>, { type, meta = {}, root, output }: AddIndexesProps): Promise<Array<KubbFile.File>> {
41
117
  if (!type || type === 'propagate') {
42
118
  return []
43
119
  }
44
120
 
45
- const barrelManager = new BarrelManager()
46
-
47
121
  const pathToBuildFrom = join(root, output.path)
48
122
 
49
123
  if (trimExtName(pathToBuildFrom).endsWith('index')) {
50
124
  return []
51
125
  }
52
126
 
53
- const barrelFiles = barrelManager.getFiles({
54
- files,
55
- root: pathToBuildFrom,
56
- meta,
57
- })
127
+ const barrelFiles = getBarrelFilesByRoot(pathToBuildFrom, files)
58
128
 
59
129
  if (type === 'all') {
60
130
  return barrelFiles.map((file) => {
@@ -1,30 +1,16 @@
1
1
  import type { CLIOptions, ConfigInput } from '../config.ts'
2
2
  import type { Config, UserConfig } from '../types.ts'
3
- import { getPlugins } from './getPlugins.ts'
4
3
 
5
4
  /**
6
- * 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.
7
10
  */
8
11
  export async function getConfigs(config: ConfigInput | UserConfig, args: CLIOptions): Promise<Array<Config>> {
9
- const resolvedConfig: Promise<UserConfig | Array<UserConfig>> =
10
- typeof config === 'function' ? Promise.resolve(config(args as CLIOptions)) : Promise.resolve(config)
12
+ const resolved = await (typeof config === 'function' ? config(args as CLIOptions) : config)
13
+ const userConfigs = Array.isArray(resolved) ? resolved : [resolved]
11
14
 
12
- let userConfigs = await resolvedConfig
13
-
14
- if (!Array.isArray(userConfigs)) {
15
- userConfigs = [userConfigs]
16
- }
17
-
18
- const results: Array<Config> = []
19
-
20
- for (const item of userConfigs) {
21
- const plugins = item.plugins ? await getPlugins(item.plugins) : undefined
22
-
23
- results.push({
24
- ...item,
25
- plugins,
26
- } as Config)
27
- }
28
-
29
- return results
15
+ return userConfigs.map((item) => ({ ...item }) as Config)
30
16
  }
@@ -0,0 +1,52 @@
1
+ import type { Visitor } from '@kubb/ast/types'
2
+ import type { CompatibilityPreset, Generator, Preset, Presets, Resolver } from '../types.ts'
3
+ import { mergeResolvers } from './mergeResolvers.ts'
4
+
5
+ type GetPresetParams<TResolver extends Resolver> = {
6
+ preset: CompatibilityPreset
7
+ presets: Presets<TResolver>
8
+ resolvers: Array<TResolver>
9
+ /**
10
+ * User-supplied generators to append after the preset's generators.
11
+ */
12
+ generators: Array<Generator<any>>
13
+ transformers?: Array<Visitor>
14
+ }
15
+
16
+ type GetPresetResult<TResolver extends Resolver> = {
17
+ resolver: TResolver
18
+ transformers: Array<Visitor>
19
+ generators: Array<Generator<any>>
20
+ preset: Preset<TResolver> | undefined
21
+ }
22
+
23
+ /**
24
+ * Resolves a named preset into merged resolvers, transformers, and generators.
25
+ *
26
+ * - Merges the preset's resolvers on top of the first (default)
27
+ * - Merges any additional user-supplied resolvers on top of that to produce the final `resolver`.
28
+ * - Concatenates preset transformers before user-supplied transformers.
29
+ * - Combines preset generators with user-supplied generators; falls back to the `default` preset's generators when neither provides any.
30
+ */
31
+ export function getPreset<TResolver extends Resolver = Resolver>(params: GetPresetParams<TResolver>): GetPresetResult<TResolver> {
32
+ const { preset: presetName, presets, resolvers, transformers: userTransformers, generators: userGenerators } = params
33
+ const [defaultResolver, ...userResolvers] = resolvers
34
+ const preset = presets[presetName]
35
+
36
+ const baseResolver = mergeResolvers(defaultResolver!, ...(preset?.resolvers ?? []))
37
+ const resolver = mergeResolvers(baseResolver, ...(userResolvers ?? []))
38
+ const transformers = [...(preset?.transformers ?? []), ...(userTransformers ?? [])]
39
+
40
+ const presetGenerators = preset?.generators ?? []
41
+ const defaultPresetGenerators = presets['default']?.generators ?? []
42
+ const generators = (presetGenerators.length > 0 || userGenerators.length
43
+ ? [...presetGenerators, ...userGenerators]
44
+ : defaultPresetGenerators) as unknown as Array<Generator<any>>
45
+
46
+ return {
47
+ resolver,
48
+ transformers,
49
+ generators,
50
+ preset,
51
+ }
52
+ }
@@ -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,8 +18,22 @@ async function isLinterAvailable(linter: Linter): Promise<boolean> {
12
18
  }
13
19
  }
14
20
 
15
- export async function detectLinter(): Promise<Linter | undefined> {
16
- const linterNames: Linter[] = ['biome', 'oxlint', 'eslint']
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> {
36
+ const linterNames = new Set(['biome', 'oxlint', 'eslint'] as const)
17
37
 
18
38
  for (const linter of linterNames) {
19
39
  if (await isLinterAvailable(linter)) {
@@ -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
  }
@@ -0,0 +1,8 @@
1
+ import type { Resolver } from '../types.ts'
2
+
3
+ /**
4
+ * Merges an array of resolvers into a single resolver. Later entries override earlier ones (last wins).
5
+ */
6
+ export function mergeResolvers<T extends Resolver>(...resolvers: Array<T>): T {
7
+ return resolvers.reduce<T>((acc, curr) => ({ ...acc, ...curr }), resolvers[0]!)
8
+ }
@@ -0,0 +1,76 @@
1
+ import { readSync } from '@internals/utils'
2
+ import * as pkg from 'empathic/package'
3
+ import { coerce, satisfies } from 'semver'
4
+
5
+ type PackageJSON = {
6
+ dependencies?: Record<string, string>
7
+ devDependencies?: Record<string, string>
8
+ }
9
+
10
+ type DependencyName = string
11
+ type DependencyVersion = string
12
+
13
+ function getPackageJSONSync(cwd?: string): PackageJSON | null {
14
+ const pkgPath = pkg.up({ cwd })
15
+ if (!pkgPath) {
16
+ return null
17
+ }
18
+
19
+ return JSON.parse(readSync(pkgPath)) as PackageJSON
20
+ }
21
+
22
+ function match(packageJSON: PackageJSON, dependency: DependencyName | RegExp): string | null {
23
+ const dependencies = {
24
+ ...(packageJSON.dependencies || {}),
25
+ ...(packageJSON.devDependencies || {}),
26
+ }
27
+
28
+ if (typeof dependency === 'string' && dependencies[dependency]) {
29
+ return dependencies[dependency]
30
+ }
31
+
32
+ const matched = Object.keys(dependencies).find((dep) => dep.match(dependency))
33
+
34
+ return matched ? (dependencies[matched] ?? null) : null
35
+ }
36
+
37
+ function getVersionSync(dependency: DependencyName | RegExp, cwd?: string): DependencyVersion | null {
38
+ const packageJSON = getPackageJSONSync(cwd)
39
+
40
+ return packageJSON ? match(packageJSON, dependency) : null
41
+ }
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
+ */
58
+ export function satisfiesDependency(dependency: DependencyName | RegExp, version: DependencyVersion, cwd?: string): boolean {
59
+ const packageVersion = getVersionSync(dependency, cwd)
60
+
61
+ if (!packageVersion) {
62
+ return false
63
+ }
64
+
65
+ if (packageVersion === version) {
66
+ return true
67
+ }
68
+
69
+ const semVer = coerce(packageVersion)
70
+
71
+ if (!semVer) {
72
+ return false
73
+ }
74
+
75
+ return satisfies(semVer, version)
76
+ }
@@ -1,74 +0,0 @@
1
- /** biome-ignore-all lint/suspicious/useIterableCallbackReturn: not needed */
2
- import { join } from 'node:path'
3
- import { getRelativePath } from '@internals/utils'
4
- import type { KubbFile } from '@kubb/fabric-core/types'
5
-
6
- import type { FileMetaBase } from './utils/getBarrelFiles.ts'
7
- import { TreeNode } from './utils/TreeNode.ts'
8
-
9
- export class BarrelManager {
10
- getFiles({ files: generatedFiles, root }: { files: KubbFile.File[]; root?: string; meta?: FileMetaBase | undefined }): Array<KubbFile.File> {
11
- const cachedFiles = new Map<KubbFile.Path, KubbFile.File>()
12
-
13
- TreeNode.build(generatedFiles, root)?.forEach((treeNode) => {
14
- if (!treeNode || !treeNode.children || !treeNode.parent?.data.path) {
15
- return
16
- }
17
-
18
- const barrelFile: KubbFile.File = {
19
- path: join(treeNode.parent?.data.path, 'index.ts') as KubbFile.Path,
20
- baseName: 'index.ts',
21
- exports: [],
22
- imports: [],
23
- sources: [],
24
- }
25
- const previousBarrelFile = cachedFiles.get(barrelFile.path)
26
- const leaves = treeNode.leaves
27
-
28
- leaves.forEach((item) => {
29
- if (!item.data.name) {
30
- return
31
- }
32
-
33
- const sources = item.data.file?.sources || []
34
-
35
- sources.forEach((source) => {
36
- if (!item.data.file?.path || !source.isIndexable || !source.name) {
37
- return
38
- }
39
- const alreadyContainInPreviousBarrelFile = previousBarrelFile?.sources.some(
40
- (item) => item.name === source.name && item.isTypeOnly === source.isTypeOnly,
41
- )
42
-
43
- if (alreadyContainInPreviousBarrelFile) {
44
- return
45
- }
46
-
47
- barrelFile.exports!.push({
48
- name: [source.name],
49
- path: getRelativePath(treeNode.parent?.data.path, item.data.path),
50
- isTypeOnly: source.isTypeOnly,
51
- })
52
-
53
- barrelFile.sources.push({
54
- name: source.name,
55
- isTypeOnly: source.isTypeOnly,
56
- //TODO use parser to generate import
57
- value: '',
58
- isExportable: false,
59
- isIndexable: false,
60
- })
61
- })
62
- })
63
-
64
- if (previousBarrelFile) {
65
- previousBarrelFile.sources.push(...barrelFile.sources)
66
- previousBarrelFile.exports?.push(...(barrelFile.exports || []))
67
- } else {
68
- cachedFiles.set(barrelFile.path, barrelFile)
69
- }
70
- })
71
-
72
- return [...cachedFiles.values()]
73
- }
74
- }
@@ -1,180 +0,0 @@
1
- import mod from 'node:module'
2
- import os from 'node:os'
3
- import { pathToFileURL } from 'node:url'
4
- import { read, readSync } from '@internals/utils'
5
- import * as pkg from 'empathic/package'
6
- import { coerce, satisfies } from 'semver'
7
- import { PATH_SEPARATORS } from './constants.ts'
8
-
9
- type PackageJSON = {
10
- dependencies?: Record<string, string>
11
- devDependencies?: Record<string, string>
12
- }
13
-
14
- type DependencyName = string
15
-
16
- type DependencyVersion = string
17
-
18
- export class PackageManager {
19
- static #cache: Record<DependencyName, DependencyVersion> = {}
20
-
21
- #cwd?: string
22
-
23
- constructor(workspace?: string) {
24
- if (workspace) {
25
- this.#cwd = workspace
26
- }
27
- }
28
-
29
- set workspace(workspace: string) {
30
- this.#cwd = workspace
31
- }
32
-
33
- get workspace(): string | undefined {
34
- return this.#cwd
35
- }
36
-
37
- normalizeDirectory(directory: string): string {
38
- const lastChar = directory[directory.length - 1]
39
- if (lastChar && !(PATH_SEPARATORS as readonly string[]).includes(lastChar)) {
40
- return `${directory}/`
41
- }
42
-
43
- return directory
44
- }
45
-
46
- getLocation(path: string): string {
47
- let location = path
48
-
49
- if (this.#cwd) {
50
- const require = mod.createRequire(this.normalizeDirectory(this.#cwd))
51
- location = require.resolve(path)
52
- }
53
-
54
- return location
55
- }
56
-
57
- async import(path: string): Promise<unknown> {
58
- let location = this.getLocation(path)
59
-
60
- if (os.platform() === 'win32') {
61
- location = pathToFileURL(location).href
62
- }
63
-
64
- const module = await import(location)
65
-
66
- return module?.default ?? module
67
- }
68
-
69
- async getPackageJSON(): Promise<PackageJSON | undefined> {
70
- const pkgPath = pkg.up({
71
- cwd: this.#cwd,
72
- })
73
- if (!pkgPath) {
74
- return undefined
75
- }
76
-
77
- const json = await read(pkgPath)
78
-
79
- return JSON.parse(json) as PackageJSON
80
- }
81
-
82
- getPackageJSONSync(): PackageJSON | undefined {
83
- const pkgPath = pkg.up({
84
- cwd: this.#cwd,
85
- })
86
- if (!pkgPath) {
87
- return undefined
88
- }
89
-
90
- const json = readSync(pkgPath)
91
-
92
- return JSON.parse(json) as PackageJSON
93
- }
94
-
95
- static setVersion(dependency: DependencyName, version: DependencyVersion): void {
96
- PackageManager.#cache[dependency] = version
97
- }
98
-
99
- #match(packageJSON: PackageJSON, dependency: DependencyName | RegExp): string | undefined {
100
- const dependencies = {
101
- ...(packageJSON.dependencies || {}),
102
- ...(packageJSON.devDependencies || {}),
103
- }
104
-
105
- if (typeof dependency === 'string' && dependencies[dependency]) {
106
- return dependencies[dependency]
107
- }
108
-
109
- const matchedDependency = Object.keys(dependencies).find((dep) => dep.match(dependency))
110
-
111
- return matchedDependency ? dependencies[matchedDependency] : undefined
112
- }
113
-
114
- async getVersion(dependency: DependencyName | RegExp): Promise<DependencyVersion | undefined> {
115
- if (typeof dependency === 'string' && PackageManager.#cache[dependency]) {
116
- return PackageManager.#cache[dependency]
117
- }
118
-
119
- const packageJSON = await this.getPackageJSON()
120
-
121
- if (!packageJSON) {
122
- return undefined
123
- }
124
-
125
- return this.#match(packageJSON, dependency)
126
- }
127
-
128
- getVersionSync(dependency: DependencyName | RegExp): DependencyVersion | undefined {
129
- if (typeof dependency === 'string' && PackageManager.#cache[dependency]) {
130
- return PackageManager.#cache[dependency]
131
- }
132
-
133
- const packageJSON = this.getPackageJSONSync()
134
-
135
- if (!packageJSON) {
136
- return undefined
137
- }
138
-
139
- return this.#match(packageJSON, dependency)
140
- }
141
-
142
- async isValid(dependency: DependencyName | RegExp, version: DependencyVersion): Promise<boolean> {
143
- const packageVersion = await this.getVersion(dependency)
144
-
145
- if (!packageVersion) {
146
- return false
147
- }
148
-
149
- if (packageVersion === version) {
150
- return true
151
- }
152
-
153
- const semVer = coerce(packageVersion)
154
-
155
- if (!semVer) {
156
- return false
157
- }
158
-
159
- return satisfies(semVer, version)
160
- }
161
- isValidSync(dependency: DependencyName | RegExp, version: DependencyVersion): boolean {
162
- const packageVersion = this.getVersionSync(dependency)
163
-
164
- if (!packageVersion) {
165
- return false
166
- }
167
-
168
- if (packageVersion === version) {
169
- return true
170
- }
171
-
172
- const semVer = coerce(packageVersion)
173
-
174
- if (!semVer) {
175
- return false
176
- }
177
-
178
- return satisfies(semVer, version)
179
- }
180
- }