123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310 |
- const fs = require('fs-extra')
- const path = require('path')
- const minimist = require('minimist')
- const LRU = require('lru-cache')
- const {
- chalk,
- execa,
- semver,
- request,
- resolvePkg,
- loadModule,
- hasYarn,
- hasProjectYarn,
- hasPnpm3OrLater,
- hasPnpmVersionOrLater,
- hasProjectPnpm,
- hasProjectNpm,
- isOfficialPlugin,
- resolvePluginId,
- log,
- warn,
- error
- } = require('@vue/cli-shared-utils')
- const { loadOptions } = require('../options')
- 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 SUPPORTED_PACKAGE_MANAGERS = ['yarn', 'pnpm', 'npm']
- const PACKAGE_MANAGER_PNPM4_CONFIG = {
- install: ['install', '--reporter', 'silent', '--shamefully-hoist'],
- add: ['install', '--reporter', 'silent', '--shamefully-hoist'],
- upgrade: ['update', '--reporter', 'silent'],
- remove: ['uninstall', '--reporter', 'silent']
- }
- const PACKAGE_MANAGER_PNPM3_CONFIG = {
- install: ['install', '--loglevel', 'error', '--shamefully-flatten'],
- add: ['install', '--loglevel', 'error', '--shamefully-flatten'],
- upgrade: ['update', '--loglevel', 'error'],
- remove: ['uninstall', '--loglevel', 'error']
- }
- const PACKAGE_MANAGER_CONFIG = {
- npm: {
- install: ['install', '--loglevel', 'error'],
- add: ['install', '--loglevel', 'error'],
- upgrade: ['update', '--loglevel', 'error'],
- remove: ['uninstall', '--loglevel', 'error']
- },
- pnpm: hasPnpmVersionOrLater('4.0.0') ? PACKAGE_MANAGER_PNPM4_CONFIG : PACKAGE_MANAGER_PNPM3_CONFIG,
- 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 || process.cwd()
- if (forcePackageManager) {
- this.bin = forcePackageManager
- } else if (context) {
- if (hasProjectYarn(context)) {
- this.bin = 'yarn'
- } else if (hasProjectPnpm(context)) {
- this.bin = 'pnpm'
- } else if (hasProjectNpm(context)) {
- this.bin = 'npm'
- }
- }
- // if no package managers specified, and no lockfile exists
- if (!this.bin) {
- this.bin = loadOptions().packageManager || (hasYarn() ? 'yarn' : hasPnpm3OrLater() ? 'pnpm' : 'npm')
- }
- if (!SUPPORTED_PACKAGE_MANAGERS.includes(this.bin)) {
- log()
- warn(
- `The package manager ${chalk.red(this.bin)} is ${chalk.red('not officially supported')}.\n` +
- `It will be treated like ${chalk.cyan('npm')}, but compatibility issues may occur.\n` +
- `See if you can use ${chalk.cyan('--registry')} instead.`
- )
- PACKAGE_MANAGER_CONFIG[this.bin] = PACKAGE_MANAGER_CONFIG.npm
- }
- // Plugin may be located in another location if `resolveFrom` presents.
- const projectPkg = resolvePkg(this.context)
- const resolveFrom = projectPkg && projectPkg.vuePlugins && projectPkg.vuePlugins.resolveFrom
- // Logically, `resolveFrom` and `context` are distinct fields.
- // But in Vue CLI we only care about plugins.
- // So it is fine to let all other operations take place in the `resolveFrom` directory.
- if (resolveFrom) {
- this.context = path.resolve(context, resolveFrom)
- }
- }
- // 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 (!process.env.VUE_CLI_TEST && await shouldUseTaobao(this.bin)) {
- this._registry = registries.taobao
- } else {
- try {
- this._registry = (await execa(this.bin, ['config', 'get', 'registry'])).stdout
- } catch (e) {
- // Yarn 2 uses `npmRegistryServer` instead of `registry`
- this._registry = (await execa(this.bin, ['config', 'get', 'npmRegistryServer'])).stdout
- }
- }
- return this._registry
- }
- async setRegistryEnvs () {
- const registry = await this.getRegistry()
- process.env.npm_config_registry = registry
- process.env.YARN_NPM_REGISTRY_SERVER = registry
- this.setBinaryMirrors()
- }
- // set mirror urls for users in china
- async setBinaryMirrors () {
- const registry = await this.getRegistry()
- if (registry !== registries.taobao) {
- return
- }
- try {
- // node-sass, chromedriver, etc.
- const binaryMirrorConfig = await this.getMetadata('binary-mirror-config')
- const mirrors = binaryMirrorConfig.mirrors.china
- for (const key in mirrors.ENVS) {
- process.env[key] = mirrors.ENVS[key]
- }
- // Cypress
- const cypressMirror = mirrors.cypress
- const defaultPlatforms = {
- darwin: 'osx64',
- linux: 'linux64',
- win32: 'win64'
- }
- const platforms = cypressMirror.newPlatforms || defaultPlatforms
- const targetPlatform = platforms[require('os').platform()]
- // Do not override user-defined env variable
- // Because we may construct a wrong download url and an escape hatch is necessary
- if (targetPlatform && !process.env.CYPRESS_INSTALL_BINARY) {
- // We only support cypress 3 for the current major version
- const latestCypressVersion = await this.getRemoteVersion('cypress', '^3')
- process.env.CYPRESS_INSTALL_BINARY =
- `${cypressMirror.host}/${latestCypressVersion}/${targetPlatform}/cypress.zip`
- }
- } catch (e) {
- // get binary mirror config failed
- }
- }
- // https://github.com/npm/registry/blob/master/docs/responses/package-metadata.md
- async getMetadata (packageName, { full = false } = {}) {
- const registry = await this.getRegistry()
- const metadataKey = `${this.bin}-${registry}-${packageName}`
- let metadata = metadataCache.get(metadataKey)
- if (metadata) {
- return metadata
- }
- const headers = {}
- if (!full) {
- headers.Accept = 'application/vnd.npm.install-v1+json'
- }
- const url = `${registry.replace(/\/$/g, '')}/${packageName}`
- try {
- metadata = (await request.get(url, { headers })).body
- metadataCache.set(metadataKey, metadata)
- return metadata
- } catch (e) {
- error(`Failed to get response from ${url}`)
- throw e
- }
- }
- 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 = loadModule(`${packageName}/package.json`, this.context, true)
- return packageJson.version
- } catch (e) {}
- }
- async runCommand (command, args) {
- await this.setRegistryEnvs()
- return await executeCommand(
- this.bin,
- [
- ...PACKAGE_MANAGER_CONFIG[this.bin][command],
- ...(args || [])
- ],
- this.context
- )
- }
- async install () {
- if (process.env.VUE_CLI_TEST) {
- try {
- await this.runCommand('install', ['--offline'])
- } catch (e) {
- await this.runCommand('install')
- }
- }
- return await this.runCommand('install')
- }
- async add (packageName, {
- tilde = false,
- dev = true
- } = {}) {
- const args = dev ? ['-D'] : []
- if (tilde) {
- if (this.bin === 'yarn') {
- args.push('--tilde')
- } else {
- process.env.npm_config_save_prefix = '~'
- }
- }
- return await this.runCommand('add', [packageName, ...args])
- }
- async remove (packageName) {
- return await this.runCommand('remove', [packageName])
- }
- 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
- }
- return await this.runCommand('add', [packageName])
- }
- }
- module.exports = PackageManager
|