mirror of
https://github.com/facebook/docusaurus.git
synced 2025-12-26 01:33:02 +00:00
* chore: clean up ESLint config, enable a few rules * enable max-len for comments * fix build
115 lines
4.0 KiB
TypeScript
115 lines
4.0 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 fs from 'fs-extra';
|
|
import path from 'path';
|
|
import {memoize} from 'lodash';
|
|
|
|
import type {PluginContext, RedirectMetadata} from './types';
|
|
import createRedirectPageContent from './createRedirectPageContent';
|
|
import {normalizeUrl} from '@docusaurus/utils';
|
|
|
|
export type WriteFilesPluginContext = Pick<PluginContext, 'baseUrl' | 'outDir'>;
|
|
|
|
export type RedirectFileMetadata = {
|
|
fileAbsolutePath: string;
|
|
fileContent: string;
|
|
};
|
|
|
|
export function createToUrl(baseUrl: string, to: string): string {
|
|
return normalizeUrl([baseUrl, to]);
|
|
}
|
|
|
|
// Create redirect file path
|
|
// Make sure this path has lower precedence over the original file path when
|
|
// served by host providers!
|
|
// Otherwise it can produce infinite redirect loops!
|
|
//
|
|
// See https://github.com/facebook/docusaurus/issues/5055
|
|
// See https://github.com/facebook/docusaurus/pull/5085
|
|
// See https://github.com/facebook/docusaurus/pull/5102
|
|
function getRedirectFilePath(
|
|
fromPath: string,
|
|
trailingSlash: boolean | undefined, // Now unused, on purpose
|
|
): string {
|
|
const fileName = path.basename(fromPath);
|
|
const filePath = path.dirname(fromPath);
|
|
// Edge case for https://github.com/facebook/docusaurus/pull/5102
|
|
// If the redirect source path is /xyz, with file /xyz.html
|
|
// We can't write the redirect file at /xyz.html/index.html because for Unix
|
|
// FS, a file/folder can't have the same name "xyz.html"
|
|
// The only possible solution for a redirect file is thus /xyz.html.html (I
|
|
// know, looks suspicious)
|
|
if (trailingSlash === false && fileName.endsWith('.html')) {
|
|
return path.join(filePath, `${fileName}.html`);
|
|
}
|
|
// If the target path is /xyz, with file /xyz/index.html, we don't want the
|
|
// redirect file to be /xyz.html, otherwise it would be picked in priority and
|
|
// the redirect file would redirect to itself. We prefer the redirect file to
|
|
// be /xyz.html/index.html, served with lower priority for most static hosting
|
|
// tools
|
|
return path.join(filePath, `${fileName}/index.html`);
|
|
}
|
|
|
|
export function toRedirectFilesMetadata(
|
|
redirects: RedirectMetadata[],
|
|
pluginContext: WriteFilesPluginContext,
|
|
trailingSlash: boolean | undefined,
|
|
): RedirectFileMetadata[] {
|
|
// Perf: avoid rendering the template twice with the exact same "props"
|
|
// We might create multiple redirect pages for the same destination url
|
|
// note: the first fn arg is the cache key!
|
|
const createPageContentMemoized = memoize((toUrl: string) =>
|
|
createRedirectPageContent({toUrl}),
|
|
);
|
|
|
|
const createFileMetadata = (redirect: RedirectMetadata) => {
|
|
const fileRelativePath = getRedirectFilePath(redirect.from, trailingSlash);
|
|
const fileAbsolutePath = path.join(pluginContext.outDir, fileRelativePath);
|
|
const toUrl = createToUrl(pluginContext.baseUrl, redirect.to);
|
|
const fileContent = createPageContentMemoized(toUrl);
|
|
return {
|
|
...redirect,
|
|
fileAbsolutePath,
|
|
fileContent,
|
|
};
|
|
};
|
|
|
|
return redirects.map(createFileMetadata);
|
|
}
|
|
|
|
export async function writeRedirectFile(
|
|
file: RedirectFileMetadata,
|
|
): Promise<void> {
|
|
try {
|
|
// User-friendly security to prevent file overrides
|
|
if (await fs.pathExists(file.fileAbsolutePath)) {
|
|
throw new Error(
|
|
'The redirect plugin is not supposed to override existing files.',
|
|
);
|
|
}
|
|
await fs.ensureDir(path.dirname(file.fileAbsolutePath));
|
|
await fs.writeFile(
|
|
file.fileAbsolutePath,
|
|
file.fileContent,
|
|
// Hard security to prevent file overrides
|
|
// See https://stackoverflow.com/a/34187712/82609
|
|
{flag: 'wx'},
|
|
);
|
|
} catch (err) {
|
|
throw new Error(
|
|
`Redirect file creation error for "${file.fileAbsolutePath}" path: ${err}.`,
|
|
);
|
|
}
|
|
}
|
|
|
|
export default async function writeRedirectFiles(
|
|
redirectFiles: RedirectFileMetadata[],
|
|
): Promise<void> {
|
|
await Promise.all(redirectFiles.map(writeRedirectFile));
|
|
}
|