diff --git a/packages/@vue/cli-service/lib/commands/serve.js b/packages/@vue/cli-service/lib/commands/serve.js index 0852171c5b..d90e442250 100644 --- a/packages/@vue/cli-service/lib/commands/serve.js +++ b/packages/@vue/cli-service/lib/commands/serve.js @@ -1,6 +1,7 @@ const { info, hasProjectYarn, + hasPnpm, openBrowser, IpcMessenger } = require('@vue/cli-shared-utils') @@ -234,7 +235,7 @@ module.exports = (api, options) => { isFirstCompile = false if (!isProduction) { - const buildCommand = hasProjectYarn(api.getCwd()) ? `yarn build` : `npm run build` + const buildCommand = hasProjectYarn(api.getCwd()) ? `yarn build` : hasPnpm() ? `pnpm run build` : `npm run build` console.log(` Note that the development build is not optimized.`) console.log(` To create a production build, run ${chalk.cyan(buildCommand)}.`) } else { diff --git a/packages/@vue/cli-shared-utils/lib/env.js b/packages/@vue/cli-shared-utils/lib/env.js index fc3c29e7a1..84673f6567 100644 --- a/packages/@vue/cli-shared-utils/lib/env.js +++ b/packages/@vue/cli-shared-utils/lib/env.js @@ -2,6 +2,7 @@ const { execSync } = require('child_process') const fs = require('fs') const path = require('path') const LRU = require('lru-cache') +const semver = require('semver') let _hasYarn const _yarnProjects = new LRU({ @@ -13,6 +14,7 @@ const _gitProjects = new LRU({ max: 10, maxAge: 1000 }) +let _hasPnpm // env detection exports.hasYarn = () => { @@ -77,6 +79,25 @@ exports.hasProjectGit = (cwd) => { return result } +exports.hasPnpm = () => { + if (process.env.VUE_CLI_TEST) { + return true + } + if (_hasPnpm != null) { + return _hasPnpm + } + try { + const pnpmVersion = execSync('pnpm --version').toString() + // there's a critical bug in pnpm 2 + // https://github.com/pnpm/pnpm/issues/1678#issuecomment-469981972 + // so we only support pnpm >= 3.0.0 + _hasPnpm = semver.gte(pnpmVersion, '3.0.0') + return _hasPnpm + } catch (e) { + return (_hasPnpm = false) + } +} + // OS exports.isWindows = process.platform === 'win32' exports.isMacintosh = process.platform === 'darwin' diff --git a/packages/@vue/cli-ui/apollo-server/type-defs.js b/packages/@vue/cli-ui/apollo-server/type-defs.js index 6376453ddc..0d2be647fd 100644 --- a/packages/@vue/cli-ui/apollo-server/type-defs.js +++ b/packages/@vue/cli-ui/apollo-server/type-defs.js @@ -8,6 +8,7 @@ scalar JSON enum PackageManager { npm yarn + pnpm } interface DescribedEntity { diff --git a/packages/@vue/cli-ui/apollo-server/util/command.js b/packages/@vue/cli-ui/apollo-server/util/command.js index 64e37dcef6..4cbc7507d3 100644 --- a/packages/@vue/cli-ui/apollo-server/util/command.js +++ b/packages/@vue/cli-ui/apollo-server/util/command.js @@ -1,12 +1,13 @@ const { hasYarn, - hasProjectYarn + hasProjectYarn, + hasPnpm } = require('@vue/cli-shared-utils') const { loadOptions } = require('@vue/cli/lib/options') exports.getCommand = function (cwd = undefined) { if (!cwd) { - return loadOptions().packageManager || (hasYarn() ? 'yarn' : 'npm') + return loadOptions().packageManager || (hasYarn() ? 'yarn' : hasPnpm() ? 'pnpm' : 'npm') } - return hasProjectYarn(cwd) ? 'yarn' : 'npm' + return hasProjectYarn(cwd) ? 'yarn' : hasPnpm() ? 'pnpm' : 'npm' } diff --git a/packages/@vue/cli-ui/src/components/project-create/ProjectCreate.vue b/packages/@vue/cli-ui/src/components/project-create/ProjectCreate.vue index ca5a9e7aa9..801bf3ff13 100644 --- a/packages/@vue/cli-ui/src/components/project-create/ProjectCreate.vue +++ b/packages/@vue/cli-ui/src/components/project-create/ProjectCreate.vue @@ -96,6 +96,10 @@ value="yarn" label="yarn" /> + diff --git a/packages/@vue/cli/__tests__/Creator.spec.js b/packages/@vue/cli/__tests__/Creator.spec.js index 4089bf5056..b32bc7cc60 100644 --- a/packages/@vue/cli/__tests__/Creator.spec.js +++ b/packages/@vue/cli/__tests__/Creator.spec.js @@ -16,7 +16,7 @@ test('default', async () => { }, { message: 'package manager', - choices: ['Yarn', 'NPM'], + choices: ['Yarn', 'PNPM', 'NPM'], choose: 0 } ] diff --git a/packages/@vue/cli/lib/Creator.js b/packages/@vue/cli/lib/Creator.js index b6c5e85e1b..1cd6014fa4 100644 --- a/packages/@vue/cli/lib/Creator.js +++ b/packages/@vue/cli/lib/Creator.js @@ -33,6 +33,7 @@ const { hasGit, hasProjectGit, hasYarn, + hasPnpm, logWithSpinner, stopSpinner, exit, @@ -97,7 +98,8 @@ module.exports = class Creator extends EventEmitter { const packageManager = ( cliOptions.packageManager || loadOptions().packageManager || - (hasYarn() ? 'yarn' : 'npm') + (hasYarn() ? 'yarn' : null) || + (hasPnpm() ? 'pnpm' : 'npm') ) await clearConsole() @@ -214,7 +216,7 @@ module.exports = class Creator extends EventEmitter { log( `👉 Get started with the following commands:\n\n` + (this.context === process.cwd() ? `` : chalk.cyan(` ${chalk.gray('$')} cd ${name}\n`)) + - chalk.cyan(` ${chalk.gray('$')} ${packageManager === 'yarn' ? 'yarn serve' : 'npm run serve'}`) + chalk.cyan(` ${chalk.gray('$')} ${packageManager === 'yarn' ? 'yarn serve' : packageManager === 'pnpm' ? 'pnpm run serve' : 'npm run serve'}`) ) } log() @@ -410,23 +412,36 @@ module.exports = class Creator extends EventEmitter { // ask for packageManager once const savedOptions = loadOptions() - if (!savedOptions.packageManager && hasYarn()) { + if (!savedOptions.packageManager && (hasYarn() || hasPnpm())) { + const packageManagerChoices = [] + + if (hasYarn()) { + packageManagerChoices.push({ + name: 'Use Yarn', + value: 'yarn', + short: 'Yarn' + }) + } + + if (hasPnpm()) { + packageManagerChoices.push({ + name: 'Use PNPM', + value: 'pnpm', + short: 'PNPM' + }) + } + + packageManagerChoices.push({ + name: 'Use NPM', + value: 'npm', + short: 'NPM' + }) + outroPrompts.push({ name: 'packageManager', type: 'list', message: 'Pick the package manager to use when installing dependencies:', - choices: [ - { - name: 'Use Yarn', - value: 'yarn', - short: 'Yarn' - }, - { - name: 'Use NPM', - value: 'npm', - short: 'NPM' - } - ] + choices: packageManagerChoices }) } diff --git a/packages/@vue/cli/lib/add.js b/packages/@vue/cli/lib/add.js index a4e231664b..2fa0a0febc 100644 --- a/packages/@vue/cli/lib/add.js +++ b/packages/@vue/cli/lib/add.js @@ -6,6 +6,7 @@ const { log, error, hasProjectYarn, + hasPnpm, resolvePluginId, resolveModule, loadModule @@ -26,7 +27,7 @@ async function add (pluginName, options = {}, context = process.cwd()) { log(`📦 Installing ${chalk.cyan(packageName)}...`) log() - const packageManager = loadOptions().packageManager || (hasProjectYarn(context) ? 'yarn' : 'npm') + const packageManager = loadOptions().packageManager || (hasProjectYarn(context) ? 'yarn' : hasPnpm() ? 'pnpm' : 'npm') await installPackage(context, packageManager, options.registry, packageName) log(`${chalk.green('✔')} Successfully installed plugin: ${chalk.cyan(packageName)}`) diff --git a/packages/@vue/cli/lib/invoke.js b/packages/@vue/cli/lib/invoke.js index 02045cef34..1f24ccb527 100644 --- a/packages/@vue/cli/lib/invoke.js +++ b/packages/@vue/cli/lib/invoke.js @@ -14,6 +14,7 @@ const { error, hasProjectYarn, hasProjectGit, + hasPnpm, logWithSpinner, stopSpinner, resolvePluginId, @@ -138,7 +139,7 @@ async function runGenerator (context, plugin, pkg = getPkg(context)) { log(`📦 Installing additional dependencies...`) log() const packageManager = - loadOptions().packageManager || (hasProjectYarn(context) ? 'yarn' : 'npm') + loadOptions().packageManager || (hasProjectYarn(context) ? 'yarn' : hasPnpm() ? 'pnpm' : 'npm') await installDeps(context, packageManager, plugin.options && plugin.options.registry) } diff --git a/packages/@vue/cli/lib/options.js b/packages/@vue/cli/lib/options.js index 47f6eb0256..163f180830 100644 --- a/packages/@vue/cli/lib/options.js +++ b/packages/@vue/cli/lib/options.js @@ -22,7 +22,7 @@ const presetSchema = createSchema(joi => joi.object().keys({ const schema = createSchema(joi => joi.object().keys({ latestVersion: joi.string().regex(/^\d+\.\d+\.\d+$/), lastChecked: joi.date().timestamp(), - packageManager: joi.string().only(['yarn', 'npm']), + packageManager: joi.string().only(['yarn', 'npm', 'pnpm']), useTaobaoRegistry: joi.boolean(), presets: joi.object().pattern(/^/, presetSchema) })) diff --git a/packages/@vue/cli/lib/util/installDeps.js b/packages/@vue/cli/lib/util/installDeps.js index 9091f338f1..03c443434e 100644 --- a/packages/@vue/cli/lib/util/installDeps.js +++ b/packages/@vue/cli/lib/util/installDeps.js @@ -9,7 +9,7 @@ const debug = require('debug')('vue-cli:install') const taobaoDistURL = 'https://npm.taobao.org/dist' -const supportPackageManagerList = ['npm', 'yarn'] +const supportPackageManagerList = ['npm', 'yarn', 'pnpm'] class InstallProgress extends EventEmitter { constructor () { @@ -176,7 +176,7 @@ exports.installDeps = async function installDeps (targetDir, command, cliRegistr const args = [] - if (command === 'npm') { + if (command === 'npm' || command === 'pnpm') { args.push('install', '--loglevel', 'error') } else if (command === 'yarn') { // do nothing @@ -195,7 +195,7 @@ exports.installPackage = async function (targetDir, command, cliRegistry, packag const args = [] - if (command === 'npm') { + if (command === 'npm' || command === 'pnpm') { args.push('install', '--loglevel', 'error') } else if (command === 'yarn') { args.push('add') @@ -218,7 +218,7 @@ exports.uninstallPackage = async function (targetDir, command, cliRegistry, pack const args = [] - if (command === 'npm') { + if (command === 'npm' || command === 'pnpm') { args.push('uninstall', '--loglevel', 'error') } else if (command === 'yarn') { args.push('remove') @@ -239,7 +239,7 @@ exports.updatePackage = async function (targetDir, command, cliRegistry, package const args = [] - if (command === 'npm') { + if (command === 'npm' || command === 'pnpm') { args.push('update', '--loglevel', 'error') } else if (command === 'yarn') { args.push('upgrade') diff --git a/packages/@vue/cli/lib/util/loadCommand.js b/packages/@vue/cli/lib/util/loadCommand.js index 51d0edc85b..c5e764c9b8 100644 --- a/packages/@vue/cli/lib/util/loadCommand.js +++ b/packages/@vue/cli/lib/util/loadCommand.js @@ -11,8 +11,13 @@ module.exports = function loadCommand (commandName, moduleName) { } catch (err2) { if (isNotFoundError(err2)) { const chalk = require('chalk') - const { hasYarn } = require('@vue/cli-shared-utils') - const installCommand = hasYarn() ? `yarn global add` : `npm install -g` + const { hasYarn, hasPnpm } = require('@vue/cli-shared-utils') + let installCommand = `npm install -g` + if (hasYarn()) { + installCommand = `yarn global add` + } else if (hasPnpm()) { + installCommand = `pnpm install -g` + } console.log() console.log( ` Command ${chalk.cyan(`vue ${commandName}`)} requires a global addon to be installed.\n` + diff --git a/packages/@vue/cli/lib/util/registries.js b/packages/@vue/cli/lib/util/registries.js index cda5834685..70352aa06b 100644 --- a/packages/@vue/cli/lib/util/registries.js +++ b/packages/@vue/cli/lib/util/registries.js @@ -1,7 +1,8 @@ const registries = { npm: 'https://registry.npmjs.org', yarn: 'https://registry.yarnpkg.com', - taobao: 'https://registry.npm.taobao.org' + taobao: 'https://registry.npm.taobao.org', + pnpm: 'https://registry.npmjs.org' } module.exports = registries