From b1e858eb8d80ae6234ffca044ed92cdba3262670 Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 3 Jan 2018 13:21:48 -0500 Subject: [PATCH] tweak registry check --- packages/@vue/cli-shared-utils/index.js | 5 +- packages/@vue/cli-shared-utils/spinner.js | 8 ++ packages/@vue/cli/bin/vue | 41 +++++-- packages/@vue/cli/lib/Creator.js | 39 ++++--- packages/@vue/cli/lib/__tests__/options.js | 18 +-- packages/@vue/cli/lib/create.js | 8 +- packages/@vue/cli/lib/options.js | 14 ++- packages/@vue/cli/lib/util/installDeps.js | 124 +++++++++++++++------ packages/@vue/cli/lib/util/sortObject.js | 1 + 9 files changed, 182 insertions(+), 76 deletions(-) diff --git a/packages/@vue/cli-shared-utils/index.js b/packages/@vue/cli-shared-utils/index.js index d11e74268..686ac7ef2 100644 --- a/packages/@vue/cli-shared-utils/index.js +++ b/packages/@vue/cli-shared-utils/index.js @@ -2,7 +2,7 @@ const chalk = require('chalk') const readline = require('readline') const { execSync } = require('child_process') const padStart = require('string.prototype.padstart') -const { logWithSpinner, stopSpinner } = require('./spinner') +const spinner = require('./spinner') const format = (label, msg) => { return msg.split('\n').map((line, i) => { @@ -12,8 +12,7 @@ const format = (label, msg) => { }).join('\n') } -exports.logWithSpinner = logWithSpinner -exports.stopSpinner = stopSpinner +Object.assign(exports, spinner) exports.log = msg => console.log(msg || '') diff --git a/packages/@vue/cli-shared-utils/spinner.js b/packages/@vue/cli-shared-utils/spinner.js index 5750e84cf..377bf9b00 100644 --- a/packages/@vue/cli-shared-utils/spinner.js +++ b/packages/@vue/cli-shared-utils/spinner.js @@ -34,3 +34,11 @@ exports.stopSpinner = (persist) => { } lastMsg = null } + +exports.pauseSpinner = () => { + spinner.stop() +} + +exports.resumeSpinner = () => { + spinner.start() +} diff --git a/packages/@vue/cli/bin/vue b/packages/@vue/cli/bin/vue index 8b004c369..fa6e92a7e 100755 --- a/packages/@vue/cli/bin/vue +++ b/packages/@vue/cli/bin/vue @@ -23,6 +23,10 @@ program program .command('create ') .description('create a new project powered by vue-cli-service') + .option('-s, --saved', 'Skip prompts and use saved options') + .option('-d, --default', 'Skip prompts and use default options') + .option('-r, --registry ', 'Use specified NPM registry when installing dependencies') + .option('-p, --package-manager ', 'Use specified NPM client when installing dependencies') .action(require('../lib/create')) program @@ -46,23 +50,38 @@ program loadCommand('build', '@vue/cli-service-global').build(filename) }) +// add some useful info on help program.on('--help', () => { console.log() + console.log(` Run ${chalk.cyan(`vue --help`)} for detailed usage of given command.`) + console.log() }) -// customize missing arg messages -const formatArgs = ({ name, required }) => { - return `${required ? '<' : '['}${name}${required ? '>' : ']'}` +program.commands.forEach(c => c.on('--help', () => console.log())) + +// enhance common error messages +const enhanceErrorMessages = (methodName, log) => { + program.Command.prototype[methodName] = function (...args) { + this.outputHelp() + console.log(` ` + chalk.red(log(...args))) + console.log() + process.exit(1) + } } -program.Command.prototype.missingArgument = function (argName) { - console.log() - console.log(` Missing required argument ${chalk.yellow(`<${argName}>`)}.`) - console.log() - console.log(` Usage: vue ${this._name} ${this._args.map(formatArgs).join(' ')}`) - console.log() - process.exit(1) -} +enhanceErrorMessages('missingArgument', argName => { + return `Missing required argument ${chalk.yellow(`<${argName}>`)}.` +}) + +enhanceErrorMessages('unknownOption', optionName => { + return `Unknown option ${chalk.yellow(optionName)}.` +}) + +enhanceErrorMessages('optionMissingArgument', (option, flag) => { + return `Missing required argument for option ${chalk.yellow(option.flags)}` + ( + flag ? `, got ${chalk.yellow(flag)}` : `` + ) +}) program.parse(process.argv) diff --git a/packages/@vue/cli/lib/Creator.js b/packages/@vue/cli/lib/Creator.js index ce79392c6..01c1f54de 100644 --- a/packages/@vue/cli/lib/Creator.js +++ b/packages/@vue/cli/lib/Creator.js @@ -15,7 +15,7 @@ const exec = require('util').promisify(require('child_process').exec) const { defaults, saveOptions, - loadSavedOptions + loadOptions } = require('./options') const { @@ -44,9 +44,28 @@ module.exports = class Creator { promptModules.forEach(m => m(promptAPI)) } - async create () { + async create (cliOptions = {}) { const { name, context, createCompleteCbs } = this - const options = await this.promptAndResolveOptions() + + let options + if (cliOptions.saved) { + options = loadOptions() + } else if (cliOptions.default) { + options = defaults + } else { + options = await this.promptAndResolveOptions() + } + + // inject core service + options.plugins['@vue/cli-service'] = { + projectName: name + } + + const packageManager = ( + cliOptions.packageManager || + options.packageManager || + (hasYarn ? 'yarn' : 'npm') + ) // write base package.json to disk clearConsole() @@ -72,7 +91,7 @@ module.exports = class Creator { // in development, avoid installation process setupDevProject(context, deps) } else { - await installDeps(context, options.packageManager, deps) + await installDeps(context, packageManager, deps, cliOptions.registry) } // run generator @@ -90,7 +109,7 @@ module.exports = class Creator { // install additional deps (injected by generators) logWithSpinner('📦', `Installing additional dependencies...`) if (!process.env.VUE_CLI_TEST) { - await installDeps(context, options.packageManager) + await installDeps(context, packageManager, null, cliOptions.registry) } // run complete cbs if any (injected by generators) @@ -125,7 +144,7 @@ module.exports = class Creator { let options if (answers.mode === 'saved') { - options = this.savedOptions // this is loaded when resolving prompts + options = loadOptions() } else if (answers.mode === 'default') { options = defaults } else { @@ -143,11 +162,6 @@ module.exports = class Creator { saveOptions(options) } - // inject core service - options.plugins['@vue/cli-service'] = { - projectName: this.name - } - debug('vue:cli-ptions')(options) return options } @@ -181,9 +195,8 @@ module.exports = class Creator { } ] } - const savedOptions = loadSavedOptions() + const savedOptions = loadOptions() if (savedOptions.plugins) { - this.savedOptions = savedOptions const savedFeatures = formatFeatures(savedOptions.plugins) modePrompt.choices.unshift({ name: `Use previously saved preferences (${savedFeatures})`, diff --git a/packages/@vue/cli/lib/__tests__/options.js b/packages/@vue/cli/lib/__tests__/options.js index 600375f87..9e5a3a574 100644 --- a/packages/@vue/cli/lib/__tests__/options.js +++ b/packages/@vue/cli/lib/__tests__/options.js @@ -3,16 +3,16 @@ jest.mock('fs') const fs = require('fs') const { rcPath, - saveOptions, - loadSavedOptions + loadOptions, + saveOptions } = require('../options') it('load options', () => { - expect(loadSavedOptions()).toEqual({}) + expect(loadOptions()).toEqual({}) fs.writeFileSync(rcPath, JSON.stringify({ plugins: {} }, null, 2)) - expect(loadSavedOptions()).toEqual({ + expect(loadOptions()).toEqual({ plugins: {} }) }) @@ -21,7 +21,7 @@ it('should not save unknown fields', () => { saveOptions({ foo: 'bar' }) - expect(loadSavedOptions()).toEqual({ + expect(loadOptions()).toEqual({ plugins: {} }) }) @@ -30,7 +30,7 @@ it('save options (merge)', () => { saveOptions({ packageManager: 'yarn' }) - expect(loadSavedOptions()).toEqual({ + expect(loadOptions()).toEqual({ packageManager: 'yarn', plugins: {} }) @@ -40,7 +40,7 @@ it('save options (merge)', () => { foo: { a: 1 } } }) - expect(loadSavedOptions()).toEqual({ + expect(loadOptions()).toEqual({ packageManager: 'yarn', plugins: { foo: { a: 1 } @@ -53,7 +53,7 @@ it('save options (merge)', () => { bar: { b: 2 } } }) - expect(loadSavedOptions()).toEqual({ + expect(loadOptions()).toEqual({ packageManager: 'yarn', plugins: { bar: { b: 2 } @@ -67,7 +67,7 @@ it('save options (merge)', () => { bar: { d: 4 } } }, true) - expect(loadSavedOptions()).toEqual({ + expect(loadOptions()).toEqual({ packageManager: 'yarn', plugins: { foo: { a: 2, c: 3 }, diff --git a/packages/@vue/cli/lib/create.js b/packages/@vue/cli/lib/create.js index e03748005..3c709719c 100644 --- a/packages/@vue/cli/lib/create.js +++ b/packages/@vue/cli/lib/create.js @@ -7,7 +7,7 @@ const Creator = require('./Creator') const clearConsole = require('./util/clearConsole') const { error, stopSpinner } = require('@vue/cli-shared-utils') -async function create (projectName) { +async function create (projectName, options) { const targetDir = path.resolve(process.cwd(), projectName) if (fs.existsSync(targetDir)) { clearConsole() @@ -36,11 +36,11 @@ async function create (projectName) { .map(file => require(`./promptModules/${file}`)) const creator = new Creator(projectName, targetDir, promptModules) - await creator.create() + await creator.create(options) } -module.exports = projectName => { - create(projectName).catch(err => { +module.exports = (...args) => { + create(...args).catch(err => { stopSpinner(false) // do not persist error(err) process.exit(1) diff --git a/packages/@vue/cli/lib/options.js b/packages/@vue/cli/lib/options.js index 1c25cde7e..ad43f3c46 100644 --- a/packages/@vue/cli/lib/options.js +++ b/packages/@vue/cli/lib/options.js @@ -9,6 +9,7 @@ const rcPath = exports.rcPath = ( ) exports.defaults = { + useTaobaoRegistry: null, packageManager: hasYarn ? 'yarn' : 'npm', plugins: { '@vue/cli-plugin-babel': {}, @@ -17,10 +18,16 @@ exports.defaults = { } } -exports.loadSavedOptions = () => { +let cachedOptions + +exports.loadOptions = () => { + if (cachedOptions) { + return cachedOptions + } if (fs.existsSync(rcPath)) { try { - return JSON.parse(fs.readFileSync(rcPath, 'utf-8')) + cachedOptions = JSON.parse(fs.readFileSync(rcPath, 'utf-8')) + return cachedOptions } catch (e) { error( `Error loading saved preferences: ` + @@ -36,7 +43,7 @@ exports.loadSavedOptions = () => { } exports.saveOptions = (toSave, deep) => { - const options = exports.loadSavedOptions() + const options = exports.loadOptions() if (deep) { deepMerge(options, toSave) } else { @@ -47,6 +54,7 @@ exports.saveOptions = (toSave, deep) => { delete options[key] } } + cachedOptions = options try { fs.writeFileSync(rcPath, JSON.stringify(options, null, 2)) } catch (e) { diff --git a/packages/@vue/cli/lib/util/installDeps.js b/packages/@vue/cli/lib/util/installDeps.js index 4d7f2f7a8..80f2704c9 100644 --- a/packages/@vue/cli/lib/util/installDeps.js +++ b/packages/@vue/cli/lib/util/installDeps.js @@ -1,6 +1,13 @@ const { URL } = require('url') const https = require('https') -const { spawn } = require('child_process') +const chalk = require('chalk') +const inquirer = require('inquirer') +const { promisify } = require('util') +const { spawn, exec } = require('child_process') +const { loadOptions, saveOptions } = require('../options') +const { pauseSpinner, resumeSpinner } = require('@vue/cli-shared-utils') + +const debug = require('debug')('vue-cli:install') const registries = { npm: 'https://registry.npmjs.org', @@ -20,42 +27,93 @@ const ping = url => new Promise((resolve, reject) => { req.end() }) -const findFastestRegistry = () => { - return Promise.race(Object.keys(registries).map(name => { - return ping(registries[name]) - })) +let checked +let result +const shouldUseTaobao = async (command) => { + // ensure this only gets called once. + if (checked) return result + checked = true + + // previously saved preference + const saved = loadOptions().useTaobaoRegistry + if (typeof saved === 'boolean') { + return (result = saved) + } + + const save = val => { + result = val + saveOptions({ useTaobaoRegistry: val }) + return val + } + + const configValue = await promisify(exec)(`${command} config get registry`) + const userCurrent = configValue.stdout.toString().trim() + const defaultRegistry = registries[command] + if (userCurrent !== defaultRegistry) { + // user has configured custom regsitry, respect that + return save(false) + } + const faster = await Promise.race([ + ping(defaultRegistry), + ping(registries.taobao) + ]) + if (faster !== registries.taobao) { + // default is already faster + return save(false) + } + + // ask and save preference + pauseSpinner() + const { useTaobaoRegistry } = await inquirer.prompt([{ + name: 'useTaobaoRegistry', + type: 'confirm', + message: chalk.yellow( + ` Your connection to the the default ${command} registry seems to be slow.\n` + + ` Use ${chalk.cyan(registries.taobao)} for faster installation?` + ) + }]) + resumeSpinner() + return save(useTaobaoRegistry) } -let registry -module.exports = async function installDeps (targetDir, command, deps) { - registry = registry || await findFastestRegistry() +module.exports = async function installDeps (targetDir, command, deps, cliRegistry) { + const args = [] + if (command === 'npm') { + args.push('install', '--loglevel', 'error') + if (deps) { + args.push('--save-dev') + } + } else if (command === 'yarn') { + if (deps) { + args.push('add', '--dev') + } + } else { + throw new Error(`unknown package manager: ${command}`) + } + + const altRegistry = ( + cliRegistry || ( + (await shouldUseTaobao(command)) + ? registries.taobao + : null + ) + ) + + if (altRegistry) { + args.push(`--registry=${altRegistry}`) + if (command === 'npm' && altRegistry === registries.taobao) { + args.push(`--disturl=${taobaoDistURL}`) + } + } + + if (deps) { + args.push.apply(args, deps) + } + + debug(`command: `, command) + debug(`args: `, args) await new Promise((resolve, reject) => { - const args = [] - if (command === 'npm') { - args.push('install', '--loglevel', 'error') - if (deps) { - args.push('--save-dev') - } - } else if (command === 'yarn') { - if (deps) { - args.push('add', '--dev') - } - } else { - throw new Error(`unknown package manager: ${command}`) - } - - if (registry !== registries[command]) { - args.push(`--registry=${registry}`) - if (registry === 'npm' && registry === registries.taobao) { - args.push(`--disturl=${taobaoDistURL}`) - } - } - - if (deps) { - args.push.apply(args, deps) - } - const child = spawn(command, args, { cwd: targetDir, stdio: 'pipe' diff --git a/packages/@vue/cli/lib/util/sortObject.js b/packages/@vue/cli/lib/util/sortObject.js index 5882a0cab..046aeb981 100644 --- a/packages/@vue/cli/lib/util/sortObject.js +++ b/packages/@vue/cli/lib/util/sortObject.js @@ -1,4 +1,5 @@ module.exports = function sortObject (obj, keyOrder) { + if (!obj) return const res = {} const keys = Object.keys(obj) const getOrder = key => {