UNPKG

@quasar/cli

Version:

Quasar Framework - the Global CLI

358 lines (299 loc) 8.07 kB
import fs from 'node:fs' import { normalize, join, sep } from 'node:path' import { sync as crossSpawnSync } from 'cross-spawn' import { execSync } from 'node:child_process' import appPaths from './app-paths.js' import { log, 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 (_) { resolve(null) } }) }) } function run ({ name, params, cwd, onFail, env = 'development' }) { 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 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 } install ({ cwd, params, displayName, env = 'development' } = {}) { displayName = displayName ? displayName + ' ' : '' log(`Installing ${ displayName }dependencies...`) run({ name: this.name, params: params && params.length !== 0 ? params : this.getInstallParams(env), cwd, env, onFail: () => fatal(`Failed to install ${ displayName }dependencies`, 'FAIL') }) } installPackage (name, { cwd, displayName = name, isDevDependency = false } = {}) { log(`Installing ${ displayName }...`) run({ name: this.name, params: this.getInstallPackageParams(Array.isArray(name) ? name : [ name ], isDevDependency), cwd, onFail: () => fatal(`Failed to install ${ displayName }`, 'FAIL') }) } uninstallPackage (name, { cwd, displayName = name } = {}) { log(`Uninstalling ${ displayName }...`) run({ name: this.name, params: this.getUninstallPackageParams(Array.isArray(name) ? name : [ name ]), cwd, onFail: () => fatal(`Failed to uninstall ${ displayName }`, 'FAIL') }) } 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[ versionList.length - 1 ] } const [ , major, prerelease ] = currentVersion.match(versionRegex) const majorSyntax = majorVersion ? '(\\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[ list.length - 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 && folder[ folder.length - 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()