Files
vue-cli/packages/@vue/cli/lib/util/ProjectPackageManager.js

208 lines
5.8 KiB
JavaScript

const fs = require('fs-extra')
const path = require('path')
const execa = require('execa')
const minimist = require('minimist')
const semver = require('semver')
const LRU = require('lru-cache')
const {
hasYarn,
hasProjectYarn,
hasPnpm3OrLater,
hasProjectPnpm
} = require('@vue/cli-shared-utils/lib/env')
const { isOfficialPlugin, resolvePluginId } = require('@vue/cli-shared-utils/lib/pluginResolution')
const { loadOptions } = require('../options')
const getPackageJson = require('./getPackageJson')
const { executeCommand } = require('./executeCommand')
const registries = require('./registries')
const shouldUseTaobao = require('./shouldUseTaobao')
const metadataCache = new LRU({
max: 200,
maxAge: 1000 * 60 * 30 // 30 min.
})
const isTestOrDebug = process.env.VUE_CLI_TEST || process.env.VUE_CLI_DEBUG
const TAOBAO_DIST_URL = 'https://npm.taobao.org/dist'
const SUPPORTED_PACKAGE_MANAGERS = ['yarn', 'pnpm', 'npm']
const PACKAGE_MANAGER_CONFIG = {
npm: {
install: ['install', '--loglevel', 'error'],
add: ['install', '--loglevel', 'error'],
upgrade: ['update', '--loglevel', 'error'],
remove: ['uninstall', '--loglevel', 'error']
},
pnpm: {
install: ['install', '--loglevel', 'error', '--shamefully-flatten'],
add: ['install', '--loglevel', 'error', '--shamefully-flatten'],
upgrade: ['update', '--loglevel', 'error'],
remove: ['uninstall', '--loglevel', 'error']
},
yarn: {
install: [],
add: ['add'],
upgrade: ['upgrade'],
remove: ['remove']
}
}
// extract the package name 'xx' from the format 'xx@1.1'
function stripVersion (packageName) {
const nameRegExp = /^(@?[^@]+)(@.*)?$/
const result = packageName.match(nameRegExp)
if (!result) {
throw new Error(`Invalid package name ${packageName}`)
}
return result[1]
}
class PackageManager {
constructor ({ context, forcePackageManager } = {}) {
this.context = context
if (forcePackageManager) {
this.bin = forcePackageManager
} else if (context) {
this.bin = hasProjectYarn(context) ? 'yarn' : hasProjectPnpm(context) ? 'pnpm' : 'npm'
} else {
this.bin = loadOptions().packageManager || (hasYarn() ? 'yarn' : hasPnpm3OrLater() ? 'pnpm' : 'npm')
}
if (!SUPPORTED_PACKAGE_MANAGERS.includes(this.bin)) {
throw new Error(`Unknown package manager: ${this.bin}`)
}
}
// Any command that implemented registry-related feature should support
// `-r` / `--registry` option
async getRegistry () {
if (this._registry) {
return this._registry
}
const args = minimist(process.argv, {
alias: {
r: 'registry'
}
})
if (args.registry) {
this._registry = args.registry
} else if (await shouldUseTaobao(this.bin)) {
this._registry = registries.taobao
} else {
const { stdout } = await execa(this.bin, ['config', 'get', 'registry'])
this._registry = stdout
}
return this._registry
}
async addRegistryToArgs (args) {
const registry = await this.getRegistry()
args.push(`--registry=${registry}`)
if (registry === registries.taobao) {
args.push(`--disturl=${TAOBAO_DIST_URL}`)
}
return args
}
async getMetadata (packageName, { field = '' } = {}) {
const registry = await this.getRegistry()
const metadataKey = `${this.bin}-${registry}-${packageName}`
let metadata = metadataCache.get(metadataKey)
if (metadata) {
return metadata
}
const args = await this.addRegistryToArgs(['info', packageName, field, '--json'])
const { stdout } = await execa(this.bin, args)
metadata = JSON.parse(stdout)
if (this.bin === 'yarn') {
// `yarn info` outputs messages in the form of `{"type": "inspect", data: {}}`
metadata = metadata.data
}
metadataCache.set(metadataKey, metadata)
return metadata
}
async getRemoteVersion (packageName, versionRange = 'latest') {
const metadata = await this.getMetadata(packageName)
if (Object.keys(metadata['dist-tags']).includes(versionRange)) {
return metadata['dist-tags'][versionRange]
}
const versions = Array.isArray(metadata.versions) ? metadata.versions : Object.keys(metadata.versions)
return semver.maxSatisfying(versions, versionRange)
}
getInstalledVersion (packageName) {
// for first level deps, read package.json directly is way faster than `npm list`
try {
const packageJson = getPackageJson(
path.resolve(this.context, 'node_modules', packageName)
)
return packageJson.version
} catch (e) {
return 'N/A'
}
}
async install () {
const args = await this.addRegistryToArgs(PACKAGE_MANAGER_CONFIG[this.bin].install)
return executeCommand(this.bin, args, this.context)
}
async add (packageName, isDev = true) {
const args = await this.addRegistryToArgs([
...PACKAGE_MANAGER_CONFIG[this.bin].add,
packageName,
...(isDev ? ['-D'] : [])
])
return executeCommand(this.bin, args, this.context)
}
async upgrade (packageName) {
const realname = stripVersion(packageName)
if (
isTestOrDebug &&
(packageName === '@vue/cli-service' || isOfficialPlugin(resolvePluginId(realname)))
) {
// link packages in current repo for test
const src = path.resolve(__dirname, `../../../../${realname}`)
const dest = path.join(this.context, 'node_modules', realname)
await fs.remove(dest)
await fs.symlink(src, dest, 'dir')
return
}
const args = await this.addRegistryToArgs([
...PACKAGE_MANAGER_CONFIG[this.bin].add,
packageName
])
return executeCommand(this.bin, args, this.context)
}
async remove (packageName) {
const args = [
...PACKAGE_MANAGER_CONFIG[this.bin].remove,
packageName
]
return executeCommand(this.bin, args, this.context)
}
}
module.exports = PackageManager