UNPKG

@quasar/cli

Version:

Quasar Framework - the Global CLI

316 lines (258 loc) 7.41 kB
import fs from 'node:fs' import { join, normalize, sep } from 'node:path' import { sync as crossSpawnSync } from 'cross-spawn' import { execSync } from 'node:child_process' import appPaths from './app-paths.js' import { fatal } from './logger.js' import { spawnSync } from './spawn.js' const versionRegex = /^(\d+)\.[\d]+\.[\d]+-?(alpha|beta|rc)?/ function getNpmRegistryUrl() { try { const url = execSync('npm config get registry') if (url) { return url.endsWith('/') ? url : url + '/' } } catch {} return 'https://registry.npmjs.org/' } async function getPackageVersionList(packageName, npmRegistryUrl) { const https = await import('node:https') const url = `${npmRegistryUrl}${packageName}` return new Promise(resolve => { https.get(url, async response => { let data = '' try { for await (const chunk of response) { data += chunk } } catch { resolve(null) return } try { const json = JSON.parse(data) const versionList = Object.keys(json.versions) resolve(versionList.length !== 0 ? versionList : null) } catch { // oxlint-disable-next-line promise/no-multiple-resolved resolve(null) } }) }) } // returns a Promise! function run({ name, params, cwd, onFail, env = 'development' }) { return spawnSync( name, params.filter(param => typeof param === 'string' && param.length !== 0), { cwd: cwd || appPaths.appDir, env: { ...process.env, NODE_ENV: env } }, onFail ) } function getMajorVersion(name) { try { const child = crossSpawnSync(name, ['--version']) if (child.status === 0) { const version = String(child.output[1]).trim() return Number.parseInt(version.split('.')[0], 10) } } catch { /* do nothing; we return null below */ } return null } class PackageManager { /** * To be declared by subclasses */ name = 'unknown' lockFiles = ['unknown'] getInstallParams(/* env */) { return [] } getInstallPackageParams(/* names, isDev */) { return [] } getUninstallPackageParams(/* names */) { return [] } /** * Implementation of the actual package manager */ majorVersion = null cachedIsInstalled = null #npmRegistryUrl = null isInstalled() { if (this.cachedIsInstalled !== null) { return this.cachedIsInstalled } this.majorVersion = getMajorVersion(this.name) this.cachedIsInstalled = this.majorVersion !== null return this.cachedIsInstalled } // returns a Promise! install({ cwd, params, env = 'development' } = {}) { return run({ name: this.name, params: params && params.length !== 0 ? params : this.getInstallParams(env), cwd, env }) } // returns a Promise! installPackage(name, { cwd, isDevDependency = false } = {}) { return run({ name: this.name, params: this.getInstallPackageParams( Array.isArray(name) ? name : [name], isDevDependency ), cwd }) } // returns a Promise! uninstallPackage(name, { cwd } = {}) { return run({ name: this.name, params: this.getUninstallPackageParams( Array.isArray(name) ? name : [name] ), cwd }) } get npmRegistryUrl() { if (this.#npmRegistryUrl === null) { this.#npmRegistryUrl = getNpmRegistryUrl() } return this.#npmRegistryUrl } set npmRegistryUrl(url) { if (url) { this.#npmRegistryUrl = url.endsWith('/') ? url : url + '/' } } async getPackageLatestVersion({ packageName, npmRegistryUrl = this.#npmRegistryUrl, currentVersion = null, majorVersion = false, preReleaseVersion = false }) { const versionList = await getPackageVersionList(packageName, npmRegistryUrl) if (versionList === null) { return null } if (currentVersion === null) { return versionList.at(-1) } const [, major, prerelease] = currentVersion.match(versionRegex) const majorSyntax = majorVersion ? String.raw`(\d+)` : major const regex = new RegExp( prerelease || preReleaseVersion ? `^${majorSyntax}\\.(\\d+)\\.(\\d+)-?(alpha|beta|rc)?` : `^${majorSyntax}\\.(\\d+)\\.(\\d+)$` ) const list = versionList.filter(version => regex.test(version)) return list.at(-1) || null } } class Npm extends PackageManager { name = 'npm' lockFiles = ['package-lock.json'] getInstallParams(env) { if (env === 'development') { return ['install'] } return this.majorVersion >= 9 ? ['install'] // env will be set to production : ['install', '--production'] } getInstallPackageParams(names, isDevDependency) { return ['install', isDevDependency ? '--save-dev' : '', ...names] } getUninstallPackageParams(names) { return ['uninstall', ...names] } } class Yarn extends PackageManager { name = 'yarn' lockFiles = ['yarn.lock'] getInstallParams(env) { if (env === 'development') { return ['install'] } return this.majorVersion >= 2 ? ['workspaces', 'focus', '--all', '--production'] : ['install', '--production'] } getInstallPackageParams(names, isDevDependency) { return ['add', isDevDependency ? '--dev' : '', ...names] } getUninstallPackageParams(names) { return ['remove', ...names] } } class Pnpm extends PackageManager { name = 'pnpm' lockFiles = ['pnpm-lock.yaml'] getInstallParams(env) { return env === 'development' ? ['install'] : ['install', '--prod'] } getInstallPackageParams(names, isDevDependency) { return ['add', isDevDependency ? '--save-dev' : '', ...names] } getUninstallPackageParams(names) { return ['remove', ...names] } } class Bun extends PackageManager { name = 'bun' lockFiles = ['bun.lock', 'bun.lockb'] getInstallParams(env) { return env === 'development' ? ['install'] : ['install', '--production'] } getInstallPackageParams(names, isDevDependency) { return ['add', isDevDependency ? '--dev' : '', ...names] } getUninstallPackageParams(names) { return ['remove', ...names] } } const packageManagersList = [new Yarn(), new Pnpm(), new Npm(), new Bun()] /** * @returns {PackageManager} */ function getProjectPackageManager(folder) { // Recursively checks for presence of the lock file by traversing // the folder tree up to the root while (folder.length !== 0 && folder.at(-1) !== sep) { for (const pm of packageManagersList) { if ( pm.lockFiles.some(lockFile => fs.existsSync(join(folder, lockFile))) ) { return pm } } folder = normalize(join(folder, '..')) } } /** * @returns {PackageManager} */ export function getNodePackager(folder = appPaths.appDir) { const projectPackageManager = getProjectPackageManager(folder) // if the project folder uses a supported package manager // and it is installed on this machine then use it if (projectPackageManager !== void 0 && projectPackageManager.isInstalled()) { return projectPackageManager } // otherwise, use the first installed package manager for (const pm of packageManagersList) { if (pm.isInstalled()) { return pm } } fatal('Please install Yarn, PNPM, NPM or Bun before running this command.\n') } export const nodePackager = getNodePackager()