docusaurus/website/src/plugins/changelog/index.ts

118 lines
3.7 KiB
TypeScript

/**
* 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 path from 'path';
import fs from 'fs-extra';
import pluginContentBlog from '@docusaurus/plugin-content-blog';
import {
aliasedSitePath,
docuHash,
normalizeUrl,
safeGlobby,
} from '@docusaurus/utils';
import {createBlogFiles, toChangelogEntries} from './utils';
export {validateOptions} from '@docusaurus/plugin-content-blog';
const MonorepoRoot = path.resolve(path.join(__dirname, '../../../..'));
const ChangelogFilePattern = 'CHANGELOG(-v[0-9]*)?.md';
async function getChangelogFiles() {
const files = await safeGlobby([ChangelogFilePattern], {
cwd: MonorepoRoot,
});
// As of today, there are 2 changelog files
// and this is only going to increase
if (files.length < 2) {
throw new Error(
"Looks like the changelog plugin didn't detect Docusaurus changelog files",
);
}
// Note: the returned file order doesn't matter.
return files;
}
function readChangelogFile(filename: string) {
return fs.readFile(path.join(MonorepoRoot, filename), 'utf-8');
}
async function loadChangelogEntries(changelogFiles: string[]) {
const filesContent = await Promise.all(changelogFiles.map(readChangelogFile));
return toChangelogEntries(filesContent);
}
const ChangelogPlugin: typeof pluginContentBlog =
async function ChangelogPlugin(context, options) {
const generateDir = path.join(context.siteDir, 'changelog/source');
const blogPlugin = await pluginContentBlog(context, {
...options,
path: generateDir,
id: 'changelog',
blogListComponent: '@theme/ChangelogList',
blogPostComponent: '@theme/ChangelogPage',
});
const changelogFiles = await getChangelogFiles();
return {
...blogPlugin,
name: 'changelog-plugin',
async loadContent() {
const changelogEntries = await loadChangelogEntries(changelogFiles);
// We have to create intermediate files here
// Unfortunately Docusaurus doesn't have yet any concept of virtual file
await createBlogFiles(generateDir, changelogEntries);
// Read the files we actually just wrote
const content = (await blogPlugin.loadContent?.())!;
content.blogPosts.forEach((post, index) => {
const pageIndex = Math.floor(
index / (options.postsPerPage as number),
);
// @ts-expect-error: TODO Docusaurus use interface declaration merging
post.metadata.listPageLink = normalizeUrl([
context.baseUrl,
options.routeBasePath,
pageIndex === 0 ? '/' : `/page/${pageIndex + 1}`,
]);
});
return content;
},
configureWebpack(...args) {
const config = blogPlugin.configureWebpack?.(...args);
const pluginDataDirRoot = path.join(
context.generatedFilesDir,
'changelog-plugin',
'default',
);
// Redirect the metadata path to our folder
// @ts-expect-error: unsafe but works
const mdxLoader = config.module.rules[0].use[0];
mdxLoader.options.metadataPath = (mdxPath: string) => {
// Note that metadataPath must be the same/in-sync as
// the path from createData for each MDX.
const aliasedPath = aliasedSitePath(mdxPath, context.siteDir);
return path.join(pluginDataDirRoot, `${docuHash(aliasedPath)}.json`);
};
return config;
},
getThemePath() {
return './theme';
},
getPathsToWatch() {
return [path.join(MonorepoRoot, ChangelogFilePattern)];
},
};
};
export default ChangelogPlugin;