mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-02 11:47:23 +02:00
feat(v2): Collect plugin versions to allow them to be inspected in debug plugin (#3050)
This commit is contained in:
parent
a3849860ae
commit
3ebe245b55
11 changed files with 193 additions and 13 deletions
|
@ -37,6 +37,29 @@ declare module '@generated/routesChunkNames' {
|
||||||
export default routesChunkNames;
|
export default routesChunkNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module '@generated/site-metadata' {
|
||||||
|
/**
|
||||||
|
* - `type: 'package'`, plugin is in a different package.
|
||||||
|
* - `type: 'project'`, plugin is in the same docusaurus project.
|
||||||
|
* - `type: 'local'`, none of plugin's ancestor directory contains any package.json.
|
||||||
|
* - `type: 'synthetic'`, docusaurus generated internal plugin.
|
||||||
|
*/
|
||||||
|
export type PluginVersionInformation =
|
||||||
|
| {readonly type: 'package'; readonly version?: string}
|
||||||
|
| {readonly type: 'project'}
|
||||||
|
| {readonly type: 'local'}
|
||||||
|
| {readonly type: 'synthetic'};
|
||||||
|
|
||||||
|
export type DocusaurusSiteMetadata = {
|
||||||
|
readonly docusaurusVersion: string;
|
||||||
|
readonly siteVersion?: string;
|
||||||
|
readonly pluginVersions: Record<string, PluginVersionInformation>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const siteMetadata: DocusaurusSiteMetadata;
|
||||||
|
export default siteMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
declare module '@theme/*';
|
declare module '@theme/*';
|
||||||
|
|
||||||
declare module '@theme-original/*';
|
declare module '@theme-original/*';
|
||||||
|
|
|
@ -10,6 +10,7 @@ import Layout from '@theme/Layout';
|
||||||
|
|
||||||
import registry from '@generated/registry';
|
import registry from '@generated/registry';
|
||||||
import routes from '@generated/routes';
|
import routes from '@generated/routes';
|
||||||
|
import siteMetadata from '@generated/site-metadata';
|
||||||
|
|
||||||
import styles from './styles.module.css';
|
import styles from './styles.module.css';
|
||||||
|
|
||||||
|
@ -17,7 +18,28 @@ function Debug() {
|
||||||
return (
|
return (
|
||||||
<Layout permalink="__docusaurus/debug" title="Debug">
|
<Layout permalink="__docusaurus/debug" title="Debug">
|
||||||
<main className={styles.Container}>
|
<main className={styles.Container}>
|
||||||
<section>
|
<section className={styles.Section}>
|
||||||
|
<h2>Site Metadata</h2>
|
||||||
|
<div>Docusaurus Version: {siteMetadata.docusaurusVersion}</div>
|
||||||
|
<div>
|
||||||
|
Site Version: {siteMetadata.siteVersion || 'No version specified'}
|
||||||
|
</div>
|
||||||
|
<h3>Plugins and themes:</h3>
|
||||||
|
<ul>
|
||||||
|
{Object.entries(siteMetadata.pluginVersions).map(
|
||||||
|
([name, versionInformation]) => (
|
||||||
|
<li key={name}>
|
||||||
|
<div>Name: {name}</div>
|
||||||
|
<div>Type: {versionInformation.type}</div>
|
||||||
|
{versionInformation.version && (
|
||||||
|
<div>Version: {versionInformation.version}</div>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
<section className={styles.Section}>
|
||||||
<h2>Registry</h2>
|
<h2>Registry</h2>
|
||||||
<ul>
|
<ul>
|
||||||
{Object.values(registry).map(([, aliasedPath, resolved]) => (
|
{Object.values(registry).map(([, aliasedPath, resolved]) => (
|
||||||
|
@ -28,7 +50,7 @@ function Debug() {
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section className={styles.Section}>
|
||||||
<h2>Routes</h2>
|
<h2>Routes</h2>
|
||||||
<ul>
|
<ul>
|
||||||
{routes.map(({path, exact}) => (
|
{routes.map(({path, exact}) => (
|
||||||
|
|
|
@ -7,5 +7,11 @@
|
||||||
|
|
||||||
.Container {
|
.Container {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
margin: 1em;
|
margin: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Section {
|
||||||
|
width: 500px;
|
||||||
|
}
|
||||||
|
|
|
@ -6,7 +6,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {generate} from '@docusaurus/utils';
|
import {generate} from '@docusaurus/utils';
|
||||||
import path from 'path';
|
import {DocusaurusSiteMetadata} from '@generated/site-metadata';
|
||||||
|
import path, {join} from 'path';
|
||||||
import {
|
import {
|
||||||
BUILD_DIR_NAME,
|
BUILD_DIR_NAME,
|
||||||
CONFIG_FILE_NAME,
|
CONFIG_FILE_NAME,
|
||||||
|
@ -26,6 +27,7 @@ import {
|
||||||
Props,
|
Props,
|
||||||
} from '@docusaurus/types';
|
} from '@docusaurus/types';
|
||||||
import {loadHtmlTags} from './html-tags';
|
import {loadHtmlTags} from './html-tags';
|
||||||
|
import {getPackageJsonVersion} from './versions';
|
||||||
|
|
||||||
export function loadContext(
|
export function loadContext(
|
||||||
siteDir: string,
|
siteDir: string,
|
||||||
|
@ -96,6 +98,7 @@ export async function load(
|
||||||
const {stylesheets = [], scripts = []} = siteConfig;
|
const {stylesheets = [], scripts = []} = siteConfig;
|
||||||
plugins.push({
|
plugins.push({
|
||||||
name: 'docusaurus-bootstrap-plugin',
|
name: 'docusaurus-bootstrap-plugin',
|
||||||
|
version: {type: 'synthetic'},
|
||||||
configureWebpack: () => ({
|
configureWebpack: () => ({
|
||||||
resolve: {
|
resolve: {
|
||||||
alias,
|
alias,
|
||||||
|
@ -178,12 +181,32 @@ ${Object.keys(registry)
|
||||||
|
|
||||||
const genRoutes = generate(generatedFilesDir, 'routes.js', routesConfig);
|
const genRoutes = generate(generatedFilesDir, 'routes.js', routesConfig);
|
||||||
|
|
||||||
|
// Version metadata.
|
||||||
|
const siteMetadata: DocusaurusSiteMetadata = {
|
||||||
|
docusaurusVersion: getPackageJsonVersion(
|
||||||
|
join(__dirname, '../../package.json'),
|
||||||
|
)!,
|
||||||
|
siteVersion: getPackageJsonVersion(join(siteDir, 'package.json')),
|
||||||
|
pluginVersions: {},
|
||||||
|
};
|
||||||
|
plugins
|
||||||
|
.filter(({version: {type}}) => type !== 'synthetic')
|
||||||
|
.forEach(({name, version}) => {
|
||||||
|
siteMetadata.pluginVersions[name] = version;
|
||||||
|
});
|
||||||
|
const genSiteMetadata = generate(
|
||||||
|
generatedFilesDir,
|
||||||
|
'site-metadata.json',
|
||||||
|
JSON.stringify(siteMetadata, null, 2),
|
||||||
|
);
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
genClientModules,
|
genClientModules,
|
||||||
genSiteConfig,
|
genSiteConfig,
|
||||||
genRegistry,
|
genRegistry,
|
||||||
genRoutesChunkNames,
|
genRoutesChunkNames,
|
||||||
genRoutes,
|
genRoutes,
|
||||||
|
genSiteMetadata,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const props: Props = {
|
const props: Props = {
|
||||||
|
|
|
@ -10,12 +10,11 @@ import fs from 'fs-extra';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import {
|
import {
|
||||||
LoadContext,
|
LoadContext,
|
||||||
Plugin,
|
|
||||||
PluginConfig,
|
PluginConfig,
|
||||||
PluginContentLoadedActions,
|
PluginContentLoadedActions,
|
||||||
RouteConfig,
|
RouteConfig,
|
||||||
} from '@docusaurus/types';
|
} from '@docusaurus/types';
|
||||||
import initPlugins from './init';
|
import initPlugins, {PluginWithVersionInformation} from './init';
|
||||||
|
|
||||||
export function sortConfig(routeConfigs: RouteConfig[]): void {
|
export function sortConfig(routeConfigs: RouteConfig[]): void {
|
||||||
// Sort the route config. This ensures that route with nested
|
// Sort the route config. This ensures that route with nested
|
||||||
|
@ -53,11 +52,14 @@ export async function loadPlugins({
|
||||||
pluginConfigs: PluginConfig[];
|
pluginConfigs: PluginConfig[];
|
||||||
context: LoadContext;
|
context: LoadContext;
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
plugins: Plugin<unknown>[];
|
plugins: PluginWithVersionInformation[];
|
||||||
pluginsRouteConfigs: RouteConfig[];
|
pluginsRouteConfigs: RouteConfig[];
|
||||||
}> {
|
}> {
|
||||||
// 1. Plugin Lifecycle - Initialization/Constructor.
|
// 1. Plugin Lifecycle - Initialization/Constructor.
|
||||||
const plugins: Plugin<unknown>[] = initPlugins({pluginConfigs, context});
|
const plugins: PluginWithVersionInformation[] = initPlugins({
|
||||||
|
pluginConfigs,
|
||||||
|
context,
|
||||||
|
});
|
||||||
|
|
||||||
// 2. Plugin Lifecycle - loadContent.
|
// 2. Plugin Lifecycle - loadContent.
|
||||||
// Currently plugins run lifecycle methods in parallel and are not order-dependent.
|
// Currently plugins run lifecycle methods in parallel and are not order-dependent.
|
||||||
|
|
|
@ -14,7 +14,9 @@ import {
|
||||||
PluginConfig,
|
PluginConfig,
|
||||||
ValidationSchema,
|
ValidationSchema,
|
||||||
} from '@docusaurus/types';
|
} from '@docusaurus/types';
|
||||||
|
import {PluginVersionInformation} from '@generated/site-metadata';
|
||||||
import {CONFIG_FILE_NAME} from '../../constants';
|
import {CONFIG_FILE_NAME} from '../../constants';
|
||||||
|
import {getPluginVersion} from '../versions';
|
||||||
|
|
||||||
function validate<T>(schema: ValidationSchema<T>, options: Partial<T>) {
|
function validate<T>(schema: ValidationSchema<T>, options: Partial<T>) {
|
||||||
const {error, value} = schema.validate(options, {
|
const {error, value} = schema.validate(options, {
|
||||||
|
@ -37,13 +39,17 @@ function validateAndStrip<T>(schema: ValidationSchema<T>, options: Partial<T>) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type PluginWithVersionInformation = Plugin<unknown> & {
|
||||||
|
readonly version: PluginVersionInformation;
|
||||||
|
};
|
||||||
|
|
||||||
export default function initPlugins({
|
export default function initPlugins({
|
||||||
pluginConfigs,
|
pluginConfigs,
|
||||||
context,
|
context,
|
||||||
}: {
|
}: {
|
||||||
pluginConfigs: PluginConfig[];
|
pluginConfigs: PluginConfig[];
|
||||||
context: LoadContext;
|
context: LoadContext;
|
||||||
}): Plugin<unknown>[] {
|
}): PluginWithVersionInformation[] {
|
||||||
// We need to resolve plugins from the perspective of the siteDir, since the siteDir's package.json
|
// We need to resolve plugins from the perspective of the siteDir, since the siteDir's package.json
|
||||||
// declares the dependency on these plugins.
|
// declares the dependency on these plugins.
|
||||||
// We need to fallback to createRequireFromPath since createRequire is only available in node v12.
|
// We need to fallback to createRequireFromPath since createRequire is only available in node v12.
|
||||||
|
@ -51,7 +57,7 @@ export default function initPlugins({
|
||||||
const createRequire = Module.createRequire || Module.createRequireFromPath;
|
const createRequire = Module.createRequire || Module.createRequireFromPath;
|
||||||
const pluginRequire = createRequire(join(context.siteDir, CONFIG_FILE_NAME));
|
const pluginRequire = createRequire(join(context.siteDir, CONFIG_FILE_NAME));
|
||||||
|
|
||||||
const plugins: Plugin<unknown>[] = pluginConfigs
|
const plugins: PluginWithVersionInformation[] = pluginConfigs
|
||||||
.map((pluginItem) => {
|
.map((pluginItem) => {
|
||||||
let pluginModuleImport: string | undefined;
|
let pluginModuleImport: string | undefined;
|
||||||
let pluginOptions = {};
|
let pluginOptions = {};
|
||||||
|
@ -72,9 +78,9 @@ export default function initPlugins({
|
||||||
|
|
||||||
// The pluginModuleImport value is any valid
|
// The pluginModuleImport value is any valid
|
||||||
// module identifier - npm package or locally-resolved path.
|
// module identifier - npm package or locally-resolved path.
|
||||||
const pluginModule: any = importFresh(
|
const pluginPath = pluginRequire.resolve(pluginModuleImport);
|
||||||
pluginRequire.resolve(pluginModuleImport),
|
const pluginModule: any = importFresh(pluginPath);
|
||||||
);
|
const pluginVersion = getPluginVersion(pluginPath, context.siteDir);
|
||||||
|
|
||||||
const plugin = pluginModule.default || pluginModule;
|
const plugin = pluginModule.default || pluginModule;
|
||||||
|
|
||||||
|
@ -106,7 +112,7 @@ export default function initPlugins({
|
||||||
...normalizedThemeConfig,
|
...normalizedThemeConfig,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return plugin(context, pluginOptions);
|
return {...plugin(context, pluginOptions), version: pluginVersion};
|
||||||
})
|
})
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
return plugins;
|
return plugins;
|
||||||
|
|
11
packages/docusaurus/src/server/types.d.ts
vendored
Normal file
11
packages/docusaurus/src/server/types.d.ts
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// triple slash is important to keep,
|
||||||
|
// see https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html
|
||||||
|
// eslint-disable-next-line
|
||||||
|
/// <reference types="@docusaurus/module-type-aliases" />
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"version": "random-version"
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {getPluginVersion} from '..';
|
||||||
|
import {join} from 'path';
|
||||||
|
|
||||||
|
describe('getPluginVersion', () => {
|
||||||
|
it('Can detect external packages plugins versions of correctly.', () => {
|
||||||
|
expect(
|
||||||
|
getPluginVersion(
|
||||||
|
join(__dirname, '..', '__fixtures__', 'dummy-plugin.js'),
|
||||||
|
// Make the plugin appear external.
|
||||||
|
join(__dirname, '..', '..', '..', '..', '..', '..', 'website'),
|
||||||
|
),
|
||||||
|
).toEqual({type: 'package', version: 'random-version'});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Can detect project plugins versions correctly.', () => {
|
||||||
|
expect(
|
||||||
|
getPluginVersion(
|
||||||
|
join(__dirname, '..', '__fixtures__', 'dummy-plugin.js'),
|
||||||
|
// Make the plugin appear project local.
|
||||||
|
join(__dirname, '..', '__fixtures__'),
|
||||||
|
),
|
||||||
|
).toEqual({type: 'project'});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Can detect local packages versions correctly.', () => {
|
||||||
|
expect(getPluginVersion('/', '/')).toEqual({type: 'local'});
|
||||||
|
});
|
||||||
|
});
|
49
packages/docusaurus/src/server/versions/index.ts
Normal file
49
packages/docusaurus/src/server/versions/index.ts
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {PluginVersionInformation} from '@generated/site-metadata';
|
||||||
|
import {existsSync, lstatSync} from 'fs-extra';
|
||||||
|
import {dirname, join} from 'path';
|
||||||
|
|
||||||
|
export function getPackageJsonVersion(
|
||||||
|
packageJsonPath: string,
|
||||||
|
): string | undefined {
|
||||||
|
if (existsSync(packageJsonPath)) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-dynamic-require, global-require
|
||||||
|
const {version} = require(packageJsonPath);
|
||||||
|
return typeof version === 'string' ? version : undefined;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPluginVersion(
|
||||||
|
pluginPath: string,
|
||||||
|
siteDir: string,
|
||||||
|
): PluginVersionInformation {
|
||||||
|
let potentialPluginPackageJsonDirectory = dirname(pluginPath);
|
||||||
|
while (potentialPluginPackageJsonDirectory !== '/') {
|
||||||
|
const packageJsonPath = join(
|
||||||
|
potentialPluginPackageJsonDirectory,
|
||||||
|
'package.json',
|
||||||
|
);
|
||||||
|
if (existsSync(packageJsonPath) && lstatSync(packageJsonPath).isFile()) {
|
||||||
|
if (potentialPluginPackageJsonDirectory === siteDir) {
|
||||||
|
// If the plugin belongs to the same docusaurus project, we classify it as local plugin.
|
||||||
|
return {type: 'project'};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: 'package',
|
||||||
|
version: getPackageJsonVersion(packageJsonPath),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
potentialPluginPackageJsonDirectory = dirname(
|
||||||
|
potentialPluginPackageJsonDirectory,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// In rare cases where a plugin is a path where no parent directory contains package.json, we can only classify it as local.
|
||||||
|
return {type: 'local'};
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue