tweak registry check

This commit is contained in:
Evan You
2018-01-03 13:21:48 -05:00
parent 2a0b91655d
commit b1e858eb8d
9 changed files with 182 additions and 76 deletions

View File

@@ -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 || '')

View File

@@ -34,3 +34,11 @@ exports.stopSpinner = (persist) => {
}
lastMsg = null
}
exports.pauseSpinner = () => {
spinner.stop()
}
exports.resumeSpinner = () => {
spinner.start()
}

View File

@@ -23,6 +23,10 @@ program
program
.command('create <app-name>')
.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 <url>', 'Use specified NPM registry when installing dependencies')
.option('-p, --package-manager <command>', '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 <command> --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)

View File

@@ -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})`,

View File

@@ -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 },

View File

@@ -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)

View File

@@ -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) {

View File

@@ -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'

View File

@@ -1,4 +1,5 @@
module.exports = function sortObject (obj, keyOrder) {
if (!obj) return
const res = {}
const keys = Object.keys(obj)
const getOrder = key => {