feat(v2): allow plugin to extendCli (#1843)

* feat(v2): allow plugin to extendCli

* rename to externalCommand
This commit is contained in:
Endi 2019-10-16 13:16:26 +07:00 committed by GitHub
parent 46e8e03be0
commit b1f6951fbb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 137 additions and 52 deletions

View file

@ -9,6 +9,7 @@
},
"license": "MIT",
"dependencies": {
"commander": "^2.20.0",
"@types/webpack": "^4.32.0",
"querystring": "0.2.0"
}

View file

@ -1,4 +1,5 @@
import {Loader, Configuration} from 'webpack';
import {CommanderStatic} from 'commander';
import {ParsedUrlQueryInput} from 'querystring';
export interface DocusaurusConfig {
@ -92,6 +93,7 @@ export interface Plugin<T> {
getThemePath?(): string;
getPathsToWatch?(): string[];
getClientModules?(): string[];
extendCli?(cli: CommanderStatic): any;
}
export type PluginConfig = [string, Object] | [string] | string;

View file

@ -10,8 +10,8 @@
const chalk = require('chalk');
const semver = require('semver');
const path = require('path');
const program = require('commander');
const {build, swizzle, deploy, start} = require('../lib');
const cli = require('commander');
const {build, swizzle, deploy, start, externalCommand} = require('../lib');
const requiredVersion = require('../package.json').engines.node;
if (!semver.satisfies(process.version, requiredVersion)) {
@ -34,11 +34,9 @@ function wrapCommand(fn) {
});
}
program
.version(require('../package.json').version)
.usage('<command> [options]');
cli.version(require('../package.json').version).usage('<command> [options]');
program
cli
.command('build [siteDir]')
.description('Build website')
.option(
@ -51,21 +49,21 @@ program
});
});
program
cli
.command('swizzle <themeName> [componentName] [siteDir]')
.description('Copy the theme files into website folder for customization.')
.action((themeName, componentName, siteDir = '.') => {
wrapCommand(swizzle)(path.resolve(siteDir), themeName, componentName);
});
program
cli
.command('deploy [siteDir]')
.description('Deploy website to GitHub pages')
.action((siteDir = '.') => {
wrapCommand(deploy)(path.resolve(siteDir));
});
program
cli
.command('start [siteDir]')
.description('Start development server')
.option('-p, --port <port>', 'use specified port (default: 3000)')
@ -82,14 +80,22 @@ program
});
});
program.arguments('<command>').action(cmd => {
program.outputHelp();
cli.arguments('<command>').action(cmd => {
cli.outputHelp();
console.log(` ${chalk.red(`\n Unknown command ${chalk.yellow(cmd)}.`)}`);
console.log();
});
program.parse(process.argv);
function isInternalCommand(command) {
return ['start', 'build', 'swizzle', 'deploy'].includes(command);
}
if (!isInternalCommand(process.argv.slice(2)[0])) {
externalCommand(cli, path.resolve('.'));
}
cli.parse(process.argv);
if (!process.argv.slice(2).length) {
program.outputHelp();
cli.outputHelp();
}

View file

@ -0,0 +1,25 @@
/**
* Copyright (c) 2017-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {CommanderStatic} from 'commander';
import {loadContext, loadPluginConfigs} from '../server';
import {initPlugins} from '../server/plugins/init';
export function externalCommand(cli: CommanderStatic, siteDir: string): void {
const context = loadContext(siteDir);
const pluginConfigs = loadPluginConfigs(context);
const plugins = initPlugins({pluginConfigs, context});
// Plugin lifecycle - extendCli
plugins.forEach(plugin => {
const {extendCli} = plugin;
if (!extendCli) {
return;
}
extendCli(cli);
});
}

View file

@ -9,3 +9,4 @@ export {build} from './commands/build';
export {start} from './commands/start';
export {swizzle} from './commands/swizzle';
export {deploy} from './commands/deploy';
export {externalCommand} from './commands/external';

View file

@ -27,41 +27,48 @@ import {
Props,
} from '@docusaurus/types';
export async function load(siteDir: string): Promise<Props> {
export function loadContext(siteDir: string): LoadContext {
const generatedFilesDir: string = path.resolve(
siteDir,
GENERATED_FILES_DIR_NAME,
);
const siteConfig: DocusaurusConfig = loadConfig(siteDir);
const genSiteConfig = generate(
generatedFilesDir,
CONFIG_FILE_NAME,
`export default ${JSON.stringify(siteConfig, null, 2)};`,
);
const outDir = path.resolve(siteDir, BUILD_DIR_NAME);
const {baseUrl} = siteConfig;
const context: LoadContext = {
return {
siteDir,
generatedFilesDir,
siteConfig,
outDir,
baseUrl,
};
}
// Presets.
export function loadPluginConfigs(context: LoadContext): PluginConfig[] {
const {plugins: presetPlugins, themes: presetThemes} = loadPresets(context);
// Plugins.
const pluginConfigs: PluginConfig[] = [
const {siteConfig} = context;
return [
...presetPlugins,
...presetThemes,
// Site config should the highest priority.
...(siteConfig.plugins || []),
...(siteConfig.themes || []),
];
}
export async function load(siteDir: string): Promise<Props> {
// Context
const context: LoadContext = loadContext(siteDir);
const {generatedFilesDir, siteConfig, outDir, baseUrl} = context;
const genSiteConfig = generate(
generatedFilesDir,
CONFIG_FILE_NAME,
`export default ${JSON.stringify(siteConfig, null, 2)};`,
);
// Plugins
const pluginConfigs: PluginConfig[] = loadPluginConfigs(context);
const {plugins, pluginsRouteConfigs} = await loadPlugins({
pluginConfigs,
context,

View file

@ -5,10 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
import _ from 'lodash';
import {generate} from '@docusaurus/utils';
import fs from 'fs-extra';
import importFresh from 'import-fresh';
import path from 'path';
import {
LoadContext,
@ -17,6 +15,7 @@ import {
PluginContentLoadedActions,
RouteConfig,
} from '@docusaurus/types';
import {initPlugins} from './init';
export async function loadPlugins({
pluginConfigs,
@ -29,30 +28,7 @@ export async function loadPlugins({
pluginsRouteConfigs: RouteConfig[];
}> {
// 1. Plugin Lifecycle - Initialization/Constructor
const plugins: Plugin<any>[] = _.compact(
pluginConfigs.map(pluginItem => {
let pluginModuleImport;
let pluginOptions = {};
if (!pluginItem) {
return null;
}
if (typeof pluginItem === 'string') {
pluginModuleImport = pluginItem;
} else if (Array.isArray(pluginItem)) {
pluginModuleImport = pluginItem[0];
pluginOptions = pluginItem[1] || {};
}
if (!pluginModuleImport) {
return null;
}
// module is any valid module identifier - npm package or locally-resolved path.
const pluginModule: any = importFresh(pluginModuleImport);
return (pluginModule.default || pluginModule)(context, pluginOptions);
}),
);
const plugins: Plugin<any>[] = initPlugins({pluginConfigs, context});
// 2. Plugin lifecycle - loadContent
// Currently plugins run lifecycle in parallel and are not order-dependent. We could change

View file

@ -0,0 +1,45 @@
/**
* Copyright (c) 2017-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import _ from 'lodash';
import importFresh from 'import-fresh';
import {LoadContext, Plugin, PluginConfig} from '@docusaurus/types';
export function initPlugins({
pluginConfigs,
context,
}: {
pluginConfigs: PluginConfig[];
context: LoadContext;
}): Plugin<any>[] {
const plugins: Plugin<any>[] = _.compact(
pluginConfigs.map(pluginItem => {
let pluginModuleImport;
let pluginOptions = {};
if (!pluginItem) {
return null;
}
if (typeof pluginItem === 'string') {
pluginModuleImport = pluginItem;
} else if (Array.isArray(pluginItem)) {
pluginModuleImport = pluginItem[0];
pluginOptions = pluginItem[1] || {};
}
if (!pluginModuleImport) {
return null;
}
// module is any valid module identifier - npm package or locally-resolved path.
const pluginModule: any = importFresh(pluginModuleImport);
return (pluginModule.default || pluginModule)(context, pluginOptions);
}),
);
return plugins;
}