mirror of
https://github.com/vuejs/vue-cli.git
synced 2026-01-17 04:40:03 -06:00
208 lines
5.8 KiB
JavaScript
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
|