mirror of
https://github.com/facebook/docusaurus.git
synced 2025-08-03 16:59:06 +02:00
feat(v2): allow plugin to extendCli (#1843)
* feat(v2): allow plugin to extendCli * rename to externalCommand
This commit is contained in:
parent
46e8e03be0
commit
b1f6951fbb
10 changed files with 137 additions and 52 deletions
|
@ -9,6 +9,7 @@
|
|||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"commander": "^2.20.0",
|
||||
"@types/webpack": "^4.32.0",
|
||||
"querystring": "0.2.0"
|
||||
}
|
||||
|
|
2
packages/docusaurus-types/src/index.d.ts
vendored
2
packages/docusaurus-types/src/index.d.ts
vendored
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
25
packages/docusaurus/src/commands/external.ts
Normal file
25
packages/docusaurus/src/commands/external.ts
Normal 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);
|
||||
});
|
||||
}
|
|
@ -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';
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
45
packages/docusaurus/src/server/plugins/init.ts
Normal file
45
packages/docusaurus/src/server/plugins/init.ts
Normal 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;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue