未验证 提交 e963731d 编写于 作者: U ULIVZ 提交者: GitHub

feat($core): registerCommand Plugin Option API (#1069)

上级 5ee2b2bd
......@@ -12,6 +12,7 @@
"boot": "node scripts/bootstrap.js",
"dev": "yarn workspace docs dev",
"build": "yarn workspace docs build",
"show-help": "yarn workspace docs show-help",
"dev:blog": "yarn workspace blog dev",
"build:blog": "yarn workspace blog build",
"register-vuepress": "lerna exec --scope vuepress -- yarn link",
......
# @vuepress/cli
> cli for vuepress
## APIs
### program
Current instance of [commander.js](https://github.com/tj/commander.js)
### bootstrap(options)
Launch the cli.
#### options.plugins
#### options.theme
\ No newline at end of file
{
"name": "@vuepress/cli",
"version": "1.0.0-alpha.27",
"description": "cli for vuepress",
"main": "index.js",
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/vue-cli.git"
},
"keywords": [
"documentation",
"vue",
"vuepress",
"generator"
],
"author": "Evan You",
"maintainers": [
{
"name": "ULIVZ",
"email": "chl814@foxmail.com"
}
],
"license": "MIT",
"bugs": {
"url": "https://github.com/vuejs/vuepress/issues"
},
"dependencies": {
"cac": "^6.3.6",
"chalk": "^2.3.2",
"semver": "^5.5.0"
},
"peerDependencies": {
"@vuepress/core": "^1.0.0-alpha.1"
},
"homepage": "https://github.com/vuejs/vuepress/packages/@vuepress/cli#readme"
}
......@@ -19,7 +19,8 @@ const PLUGIN_OPTION_META_MAP = {
ADDITIONAL_PAGES: { name: 'additionalPages', types: [Function, Array] },
GLOBAL_UI_COMPONENTS: { name: 'globalUIComponents', types: [String, Array] },
DEFINE: { name: 'define', types: [Function, Object] },
ALIAS: { name: 'alias', types: [Function, Object] }
ALIAS: { name: 'alias', types: [Function, Object] },
EXTEND_CLI: { name: 'extendCli', types: [Function] }
}
const PLUGIN_OPTION_MAP = {}
......
......@@ -206,7 +206,8 @@ module.exports = class PluginAPI {
additionalPages,
globalUIComponents,
define,
alias
alias,
extendCli
}) {
const isInternalPlugin = pluginName.startsWith('@vuepress/internal-')
logger[isInternalPlugin ? 'debug' : 'tip'](pluginLog(pluginName, shortcut))
......@@ -229,6 +230,7 @@ module.exports = class PluginAPI {
.registerOption(PLUGIN_OPTION_MAP.GLOBAL_UI_COMPONENTS.key, globalUIComponents, pluginName)
.registerOption(PLUGIN_OPTION_MAP.DEFINE.key, define, pluginName)
.registerOption(PLUGIN_OPTION_MAP.ALIAS.key, alias, pluginName)
.registerOption(PLUGIN_OPTION_MAP.EXTEND_CLI.key, extendCli, pluginName)
}
}
......
......@@ -43,6 +43,7 @@ module.exports = class AppContext {
*/
constructor (sourceDir, cliOptions = {}, isProd) {
logger.debug('sourceDir', sourceDir)
this.sourceDir = sourceDir
this.cliOptions = cliOptions
this.isProd = isProd
......
......@@ -68,10 +68,12 @@
"webpack-chain": "^4.6.0",
"webpack-merge": "^4.1.2",
"webpack-serve": "^1.0.2",
"webpackbar": "^2.6.1"
"webpackbar": "^2.6.1",
"semver": "^5.5.0",
"cac": "^6.3.9"
},
"engines": {
"node": ">=8"
"node": ">=8.6"
},
"browserslist": [
">1%"
......
......@@ -187,9 +187,8 @@ Then the final route of i18n UI is `/i18n/`.
## Others
With the goal of decoupling, we were able to separate VuePress into the following libraries by introducing monorepo:
With the goal of decoupling, we were able to separate VuePress into the following two libraries by introducing monorepo:
- [@vuepress/cli](https://github.com/vuejs/vuepress/tree/master/packages/@vuepress/cli): Management of command line;
- [@vuepress/core](https://github.com/vuejs/vuepress/tree/master/packages/@vuepress/core):Including the core implementation of `dev`, `build` and `Plugin API`;
- [@vuepress/theme-default](https://github.com/vuejs/vuepress/tree/master/packages/@vuepress/theme-default):The default theme you see now.
......
......@@ -434,3 +434,29 @@ Then, VuePress will automatically inject these components behind the layout comp
</div>
</div>
```
## registerCommand
- Type: `function`
- Default: `undefined`
Register a extra command to enhance the CLI of vuepress. The function will be called with a [CAC](https://github.com/cacjs/cac)'s instance as the first argument.
```js
module.exports = {
registerCommand (cli) {
cli
.command('info [targetDir]', '')
.option('--debug', 'display info in debug mode')
.action((dir = '.') => {
console.log('Display info of your website')
})
}
}
```
Now you can use `vuepress info [targetDir]` a in your project!
::: tip
Note that a custom command registered by a plugin requires VuePress to locate your site configuration like `vuepress dev` and `vuepress build`, so when developing a command, be sure to lead the user to pass `targetDir` as an CLI argument.
:::
......@@ -187,9 +187,8 @@ i18n UI 最终的路由将是 `/i18n/`.
## 其他
本着解耦的目标,引入 monorepo 后,我们也得以将 VuePress 分离成以下个库:
本着解耦的目标,引入 monorepo 后,我们也得以将 VuePress 分离成以下个库:
- [@vuepress/cli](https://github.com/vuejs/vuepress/tree/master/packages/@vuepress/cli): 命令行指令的管理;
- [@vuepress/core](https://github.com/vuejs/vuepress/tree/master/packages/@vuepress/core):包含 dev、build 的核心实现和 Plugin API;
- [@vuepress/theme-default](https://github.com/vuejs/vuepress/tree/master/packages/@vuepress/theme-default):你现在所看到的默认主题。
......
......@@ -436,3 +436,30 @@ VuePress 将会自动将这些组件注入到布局组件的隔壁:
</div>
</div>
```
## registerCommand
- 类型: `function`
- 默认值: `undefined`
注册一个额外的 command 来增强 vuepress 的 CLI。这个函数将会以一个 [CAC](https://github.com/cacjs/cac) 的实例作为第一个参数被调用。
```js
module.exports = {
registerCommand (cli) {
cli
.command('info [targetDir]', '')
.option('--debug', 'display info in debug mode')
.action((dir = '.') => {
console.log('Display info of your website')
})
}
}
```
现在你可以在你项目中使用 `vuepress info [targetDir]` 了!
::: tip
值得注意的是,一个自定义的 command 需要 VuePress 像 `vuepress dev``vuepress build` 去定位到你的站点配置,所以在开发一个 command 时,请确保引导用户去传入 `targetDir` 作为 CLI 参数的一部分。
:::
......@@ -5,7 +5,8 @@
"description": "docs of VuePress",
"scripts": {
"dev": "vuepress dev docs --temp .temp",
"build": "vuepress build docs --temp .temp"
"build": "vuepress build docs --temp .temp",
"show-help": "vuepress --help"
},
"repository": {
"type": "git",
......
'use strict'
/**
* Module dependencies.
*/
const { chalk } = require('@vuepress/shared-utils')
const semver = require('semver')
/**
* Expose handleUnknownCommand function.
*/
module.exports = function checkEnv (pkg) {
const requiredVersion = pkg.engines.node
if (!semver.satisfies(process.version, requiredVersion)) {
console.log(chalk.red(
`\n[vuepress] minimum Node version not met:` +
`\nYou are using Node ${process.version}, but VuePress ` +
`requires Node ${requiredVersion}.\nPlease upgrade your Node version.\n`
))
process.exit(1)
}
}
'use strict'
/**
* Module dependencies.
*/
const prepare = require('@vuepress/core/lib/prepare')
const { path, logger, globby, chalk } = require('@vuepress/shared-utils')
const { isKnownCommand, CLI } = require('./util')
const pwd = process.cwd()
/**
* Expose handleUnknownCommand function.
*/
module.exports = async function (cli, options) {
registerUnknownCommands(cli, options)
const argv = process.argv.slice(2)
const inferredUserDocsDirectory = await inferUserDocsDirectory(pwd)
logger.developer('inferredUserDocsDirectory', inferredUserDocsDirectory)
const needPrepareBeforeLaunchCLI = inferredUserDocsDirectory &&
(isHelpFlag(argv[0]) || isUnknownCommandHelp(argv))
logger.developer('needPrepareBeforeLaunchCLI', needPrepareBeforeLaunchCLI)
if (needPrepareBeforeLaunchCLI) {
let context
let [, sourceDir] = argv
if (!sourceDir || sourceDir.startsWith('-')) {
sourceDir = inferredUserDocsDirectory
} else {
sourceDir = pwd
}
logger.setOptions({ logLevel: 1 })
if (sourceDir) {
context = await prepare(sourceDir, options)
context.pluginAPI.options.extendCli.apply(cli)
}
logger.setOptions({ logLevel: 3 })
}
}
// When user type `vuepress [customCommand] --help`,
// VuePress will try to detect where user to place docs.
async function inferUserDocsDirectory (cwd) {
const paths = await globby([
'**/.vuepress/config.js',
'!node_modules'
], {
cwd,
dot: true
})
const siteConfigPath = paths && paths[0]
if (siteConfigPath) {
return path.resolve(
cwd,
siteConfigPath.replace('.vuepress/config.js', '')
)
}
return null
}
/**
* Register a command to match all unmatched commands
* @param {CAC} cli
*/
function registerUnknownCommands (cli, options) {
cli.on('command:*', async () => {
const { args, options: commandoptions } = cli
logger.debug('global_options', options)
logger.debug('cli_options', commandoptions)
logger.debug('cli_args', args)
const [commandName] = args
const sourceDir = args[1] ? path.resolve(args[1]) : pwd
const inferredUserDocsDirectory = await inferUserDocsDirectory(pwd)
logger.developer('inferredUserDocsDirectory', inferredUserDocsDirectory)
logger.developer('sourceDir', sourceDir)
if (inferredUserDocsDirectory && sourceDir !== inferredUserDocsDirectory) {
logUnknownCommand(cli)
console.log()
logger.tip(`Did you miss to specify the target docs dir? e.g. ${chalk.cyan(`vuepress ${commandName} [targetDir]`)}.`)
logger.tip(`A custom command registered by a plugin requires VuePress to locate your site configuration like ${chalk.cyan('vuepress dev')} or ${chalk.cyan('vuepress build')}.`)
console.log()
process.exit(1)
}
if (!inferredUserDocsDirectory) {
logUnknownCommand(cli)
process.exit(1)
}
logger.debug('Custom command', chalk.cyan(commandName))
CLI({
async beforeParse (subCli) {
const context = await prepare(sourceDir, {
...options,
...commandoptions
}, false /* isProd */)
await context.pluginAPI.options.extendCli.apply(subCli)
},
async afterParse (subCli) {
if (!subCli.matchedCommand) {
logUnknownCommand(subCli)
console.log()
}
}
})
})
}
function isHelpFlag (v) {
return v === '--help' || v === '-h'
}
function isUnknownCommandHelp (argv) {
return !isKnownCommand(argv) && isHelpFlag(argv[1])
}
function logUnknownCommand (cli) {
console.error('Unknown command: %s', cli.args.join(' '))
}
const { chalk, performance } = require('@vuepress/shared-utils')
const semver = require('semver')
'use strict'
try {
require.resolve('@vuepress/core')
} catch (err) {
console.log(chalk.red(
`\n[vuepress] @vuepress/cli ` +
`requires @vuepress/core to be installed.\n`
))
process.exit(1)
}
const pkg = require('@vuepress/core/package.json')
const requiredVersion = pkg.engines.node
if (!semver.satisfies(process.version, requiredVersion)) {
console.log(chalk.red(
`\n[vuepress] minimum Node version not met:` +
`\nYou are using Node ${process.version}, but VuePress ` +
`requires Node ${requiredVersion}.\nPlease upgrade your Node version.\n`
))
process.exit(1)
}
/**
* Module dependencies.
*/
const cli = require('cac')()
const { dev, build, eject } = require('@vuepress/core')
const { path, logger, env } = require('@vuepress/shared-utils')
const { wrapCommand } = require('./util')
exports.cli = cli
exports.bootstrap = function ({
plugins,
theme
} = {}) {
const { path, logger, env } = require('@vuepress/shared-utils')
const { dev, build, eject } = require('@vuepress/core')
performance.start()
cli
.version(pkg.version)
.help()
/**
* Expose registerCoreCommands function.
*/
module.exports = function (cli, options) {
cli
.command('dev [targetDir]', 'start development server')
.allowUnknownOptions()
.command(`dev [targetDir]`, 'start development server')
.option('-p, --port <port>', 'use specified port (default: 8080)')
.option('-t, --temp <temp>', 'set the directory of the temporary file')
.option('-c, --cache [cache]', 'set the directory of cache')
......@@ -49,58 +22,39 @@ exports.bootstrap = function ({
.option('--no-cache', 'clean the cache before build')
.option('--debug', 'start development server in debug mode')
.option('--silent', 'start development server in silent mode')
.action((sourceDir = '.', options) => {
const {
host,
port,
debug,
temp,
cache,
silent
} = options
.action((sourceDir = '.', commandOptions) => {
const { debug, silent } = commandOptions
logger.setOptions({ logLevel: silent ? 1 : debug ? 4 : 3 })
logger.debug('cli_options', options)
logger.debug('global_options', options)
logger.debug('dev_options', commandOptions)
env.setOptions({ isDebug: debug, isTest: process.env.NODE_ENV === 'test' })
wrapCommand(dev)(path.resolve(sourceDir), {
host,
port,
temp,
cache,
plugins,
theme
...options,
...commandOptions
})
})
cli
.command('build [targetDir]', 'build dir as static site')
.allowUnknownOptions()
.option('-d, --dest <dest>', 'specify build output dir (default: .vuepress/dist)')
.option('-t, --temp <temp>', 'set the directory of the temporary file')
.option('-c, --cache [cache]', 'set the directory of cache')
.option('--no-cache', 'clean the cache before build')
.option('--debug', 'build in development mode for debugging')
.option('--silent', 'build static site in silent mode')
.action((sourceDir = '.', options) => {
const {
debug,
dest,
temp,
cache,
silent
} = options
.action((sourceDir = '.', commandOptions) => {
const { debug, silent } = commandOptions
logger.setOptions({ logLevel: silent ? 1 : debug ? 4 : 3 })
logger.debug('cli_options', options)
logger.debug('global_options', options)
logger.debug('build_options', commandOptions)
env.setOptions({ isDebug: debug, isTest: process.env.NODE_ENV === 'test' })
wrapCommand(build)(path.resolve(sourceDir), {
debug,
dest,
plugins,
theme,
temp,
cache,
silent
...options,
...commandOptions
})
})
......@@ -110,25 +64,4 @@ exports.bootstrap = function ({
.action((dir = '.') => {
wrapCommand(eject)(path.resolve(dir))
})
// output help information on unknown commands
cli.on('command:*', () => {
console.error('Unknown command: %s', cli.args.join(' '))
console.log()
})
function wrapCommand (fn) {
return (...args) => {
return fn(...args).catch(err => {
console.error(chalk.red(err.stack))
process.exitCode = 1
})
}
}
cli.parse(process.argv)
if (!process.argv.slice(2).length) {
cli.outputHelp()
}
}
'use strict'
/**
* Module dependencies.
*/
const { chalk } = require('@vuepress/shared-utils')
const CAC = require('cac')
/**
* Bootstrap a CAC cli
* @param {function} beforeParse
* @param {function} adterParse
* @returns {Promise<void>}
*/
async function CLI ({
beforeParse,
afterParse
}) {
const cli = CAC()
beforeParse && await beforeParse(cli)
cli.parse(process.argv)
afterParse && await afterParse(cli)
}
/**
* Wrap a function to catch error.
* @param {function} fn
* @returns {function(...[*]): (*|Promise|Promise<T | never>)}
*/
function wrapCommand (fn) {
return (...args) => {
return fn(...args).catch(err => {
console.error(chalk.red(err.stack))
process.exitCode = 1
})
}
}
/**
* Check if a command is built-in
* @param {array} argv
* @returns {boolean}
*/
function isKnownCommand (argv) {
return ['dev', 'build', 'eject'].includes(argv[0])
}
module.exports = {
CLI,
isKnownCommand,
wrapCommand
}
......@@ -28,9 +28,8 @@
},
"homepage": "https://github.com/vuejs/vuepress#readme",
"dependencies": {
"@vuepress/cli": "^1.0.0-alpha.27",
"@vuepress/core": "^1.0.0-alpha.27",
"@vuepress/theme-default": "^1.0.0-alpha.27"
"@vuepress/core": "^1.0.0-alpha.24",
"@vuepress/theme-default": "^1.0.0-alpha.24"
},
"engines": {
"node": ">=8"
......
#!/usr/bin/env node
require('@vuepress/cli').bootstrap({ theme: '@vuepress/default' })
const checkEnv = require('./lib/checkEnv')
const { CLI } = require('./lib/util')
const registerCoreCommands = require('./lib/registerCoreCommands')
const handleUnknownCommand = require('./lib/handleUnknownCommand')
const OPTIONS = {
theme: '@vuepress/default'
}
CLI({
async beforeParse (cli) {
const pkg = require('@vuepress/core/package.json')
checkEnv(pkg)
registerCoreCommands(cli, OPTIONS)
await handleUnknownCommand(cli, OPTIONS)
cli.version(pkg.version).help()
},
async afterParse (cli) {
if (!process.argv.slice(2).length) {
cli.outputHelp()
}
}
})
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册