@kubb/core
Version:
Core functionality for Kubb's plugin-based code generation system, providing the foundation for transforming OpenAPI specifications.
188 lines (138 loc) • 4.4 kB
text/typescript
import mod from 'node:module'
import os from 'node:os'
import { pathToFileURL } from 'node:url'
import { findUp, findUpSync } from 'find-up'
import { coerce, satisfies } from 'semver'
import { read, readSync } from './fs/index.ts'
type PackageJSON = {
dependencies?: Record<string, string>
devDependencies?: Record<string, string>
}
type DependencyName = string
type DependencyVersion = string
export class PackageManager {
static #cache: Record<DependencyName, DependencyVersion> = {}
#cwd?: string
#SLASHES = new Set(['/', '\\'])
constructor(workspace?: string) {
if (workspace) {
this.#cwd = workspace
}
return this
}
set workspace(workspace: string) {
this.#cwd = workspace
}
get workspace(): string | undefined {
return this.#cwd
}
normalizeDirectory(directory: string): string {
if (!this.#SLASHES.has(directory[directory.length - 1]!)) {
return `${directory}/`
}
return directory
}
getLocation(path: string): string {
let location = path
if (this.#cwd) {
const require = mod.createRequire(this.normalizeDirectory(this.#cwd))
location = require.resolve(path)
}
return location
}
async import(path: string): Promise<any | undefined> {
try {
let location = this.getLocation(path)
if (os.platform() === 'win32') {
location = pathToFileURL(location).href
}
const module = await import(location)
return module?.default ?? module
} catch (e) {
console.error(e)
return undefined
}
}
async getPackageJSON(): Promise<PackageJSON | undefined> {
const pkgPath = await findUp(['package.json'], {
cwd: this.#cwd,
})
if (!pkgPath) {
return undefined
}
const json = await read(pkgPath)
return JSON.parse(json) as PackageJSON
}
getPackageJSONSync(): PackageJSON | undefined {
const pkgPath = findUpSync(['package.json'], {
cwd: this.#cwd,
})
if (!pkgPath) {
return undefined
}
const json = readSync(pkgPath)
return JSON.parse(json) as PackageJSON
}
static setVersion(dependency: DependencyName, version: DependencyVersion): void {
PackageManager.#cache[dependency] = version
}
#match(packageJSON: PackageJSON, dependency: DependencyName | RegExp): string | undefined {
const dependencies = {
...(packageJSON['dependencies'] || {}),
...(packageJSON['devDependencies'] || {}),
}
if (typeof dependency === 'string' && dependencies[dependency]) {
return dependencies[dependency]
}
const matchedDependency = Object.keys(dependencies).find((dep) => dep.match(dependency))
return matchedDependency ? dependencies[matchedDependency] : undefined
}
async getVersion(dependency: DependencyName | RegExp): Promise<DependencyVersion | undefined> {
if (typeof dependency === 'string' && PackageManager.#cache[dependency]) {
return PackageManager.#cache[dependency]
}
const packageJSON = await this.getPackageJSON()
if (!packageJSON) {
return undefined
}
return this.#match(packageJSON, dependency)
}
getVersionSync(dependency: DependencyName | RegExp): DependencyVersion | undefined {
if (typeof dependency === 'string' && PackageManager.#cache[dependency]) {
return PackageManager.#cache[dependency]
}
const packageJSON = this.getPackageJSONSync()
if (!packageJSON) {
return undefined
}
return this.#match(packageJSON, dependency)
}
async isValid(dependency: DependencyName | RegExp, version: DependencyVersion): Promise<boolean> {
const packageVersion = await this.getVersion(dependency)
if (!packageVersion) {
return false
}
if (packageVersion === version) {
return true
}
const semVer = coerce(packageVersion)
if (!semVer) {
throw new Error(`${packageVersion} is not valid`)
}
return satisfies(semVer, version)
}
isValidSync(dependency: DependencyName | RegExp, version: DependencyVersion): boolean {
const packageVersion = this.getVersionSync(dependency)
if (!packageVersion) {
return false
}
if (version === 'next' && packageVersion === version) {
return true
}
const semVer = coerce(packageVersion)
if (!semVer) {
return false
}
return satisfies(semVer, version)
}
}