diff --git a/packages/docusaurus-plugin-client-redirects/src/__tests__/collectRedirects.test.ts b/packages/docusaurus-plugin-client-redirects/src/__tests__/collectRedirects.test.ts index f43e1d795542..8cf956a077c9 100644 --- a/packages/docusaurus-plugin-client-redirects/src/__tests__/collectRedirects.test.ts +++ b/packages/docusaurus-plugin-client-redirects/src/__tests__/collectRedirects.test.ts @@ -12,12 +12,12 @@ import {removeTrailingSlash} from '@docusaurus/utils'; function createTestPluginContext( options?: UserPluginOptions, - routesPaths: string[] = [], + relativeRoutesPaths: string[] = [], ): PluginContext { return { outDir: '/tmp', baseUrl: 'https://docusaurus.io', - routesPaths, + relativeRoutesPaths, options: normalizePluginOptions(options), }; } diff --git a/packages/docusaurus-plugin-client-redirects/src/__tests__/extensionRedirects.test.ts b/packages/docusaurus-plugin-client-redirects/src/__tests__/extensionRedirects.test.ts index ce327688a22a..1bb284447e37 100644 --- a/packages/docusaurus-plugin-client-redirects/src/__tests__/extensionRedirects.test.ts +++ b/packages/docusaurus-plugin-client-redirects/src/__tests__/extensionRedirects.test.ts @@ -15,33 +15,32 @@ const createExtensionValidationTests = ( extensionRedirectCreatorFn: ( paths: string[], extensions: string[], - baseUrl: string, ) => RedirectMetadata[], ) => { test('should reject empty extensions', () => { expect(() => { - extensionRedirectCreatorFn(['/'], ['.html'], '/'); + extensionRedirectCreatorFn(['/'], ['.html']); }).toThrowErrorMatchingInlineSnapshot( `"Extension=['.html'] contains a . (dot) and is not allowed. If the redirect extension system is not good enough for your usecase, you can create redirects yourself with the 'createRedirects' plugin option."`, ); }); test('should reject extensions with .', () => { expect(() => { - extensionRedirectCreatorFn(['/'], ['.html'], '/'); + extensionRedirectCreatorFn(['/'], ['.html']); }).toThrowErrorMatchingInlineSnapshot( `"Extension=['.html'] contains a . (dot) and is not allowed. If the redirect extension system is not good enough for your usecase, you can create redirects yourself with the 'createRedirects' plugin option."`, ); }); test('should reject extensions with /', () => { expect(() => { - extensionRedirectCreatorFn(['/'], ['ht/ml'], '/'); + extensionRedirectCreatorFn(['/'], ['ht/ml']); }).toThrowErrorMatchingInlineSnapshot( `"Extension=['ht/ml'] contains a / and is not allowed. If the redirect extension system is not good enough for your usecase, you can create redirects yourself with the 'createRedirects' plugin option."`, ); }); test('should reject extensions with illegal url char', () => { expect(() => { - extensionRedirectCreatorFn(['/'], [','], '/'); + extensionRedirectCreatorFn(['/'], [',']); }).toThrowErrorMatchingInlineSnapshot( `"Extension=[','] contains invalid uri characters. If the redirect extension system is not good enough for your usecase, you can create redirects yourself with the 'createRedirects' plugin option."`, ); @@ -53,28 +52,28 @@ describe('createToExtensionsRedirects', () => { test('should create redirects from html/htm extensions', () => { const ext = ['html', 'htm']; - expect(createToExtensionsRedirects([''], ext, '/')).toEqual([]); - expect(createToExtensionsRedirects(['/'], ext, '/')).toEqual([]); - expect(createToExtensionsRedirects(['/abc.html'], ext, '/')).toEqual([ + expect(createToExtensionsRedirects([''], ext)).toEqual([]); + expect(createToExtensionsRedirects(['/'], ext)).toEqual([]); + expect(createToExtensionsRedirects(['/abc.html'], ext)).toEqual([ {from: '/abc', to: '/abc.html'}, ]); - expect(createToExtensionsRedirects(['/abc.htm'], ext, '/')).toEqual([ + expect(createToExtensionsRedirects(['/abc.htm'], ext)).toEqual([ {from: '/abc', to: '/abc.htm'}, ]); - expect(createToExtensionsRedirects(['/abc.xyz'], ext, '/')).toEqual([]); + expect(createToExtensionsRedirects(['/abc.xyz'], ext)).toEqual([]); }); - test('should create "to" redirects without baseUrl when baseUrl is used', () => { + test('should create "to" redirects when relativeRoutesPath contains a prefix', () => { expect( - createToExtensionsRedirects(['/prefix/file.html'], ['html'], '/prefix/'), - ).toEqual([{from: '/file', to: '/file.html'}]); + createToExtensionsRedirects(['/prefix/file.html'], ['html']), + ).toEqual([{from: '/prefix/file', to: '/prefix/file.html'}]); }); test('should not create redirection for an empty extension array', () => { const ext: string[] = []; - expect(createToExtensionsRedirects([''], ext, '/')).toEqual([]); - expect(createToExtensionsRedirects(['/'], ext, '/')).toEqual([]); - expect(createToExtensionsRedirects(['/abc.html'], ext, '/')).toEqual([]); + expect(createToExtensionsRedirects([''], ext)).toEqual([]); + expect(createToExtensionsRedirects(['/'], ext)).toEqual([]); + expect(createToExtensionsRedirects(['/abc.html'], ext)).toEqual([]); }); }); @@ -83,28 +82,28 @@ describe('createFromExtensionsRedirects', () => { test('should create redirects to html/htm extensions', () => { const ext = ['html', 'htm']; - expect(createFromExtensionsRedirects([''], ext, '/')).toEqual([]); - expect(createFromExtensionsRedirects(['/'], ext, '/')).toEqual([]); - expect(createFromExtensionsRedirects(['/abc'], ext, '/')).toEqual([ + expect(createFromExtensionsRedirects([''], ext)).toEqual([]); + expect(createFromExtensionsRedirects(['/'], ext)).toEqual([]); + expect(createFromExtensionsRedirects(['/abc'], ext)).toEqual([ {from: '/abc.html', to: '/abc'}, {from: '/abc.htm', to: '/abc'}, ]); - expect(createFromExtensionsRedirects(['/def.html'], ext, '/')).toEqual([]); - expect(createFromExtensionsRedirects(['/def/'], ext, '/')).toEqual([]); + expect(createFromExtensionsRedirects(['/def.html'], ext)).toEqual([]); + expect(createFromExtensionsRedirects(['/def/'], ext)).toEqual([]); }); - test('should create "from" redirects without baseUrl when baseUrl is used', () => { - expect( - createFromExtensionsRedirects(['/prefix/file'], ['html'], '/prefix/'), - ).toEqual([{from: '/file.html', to: '/file'}]); + test('should create "from" redirects when relativeRoutesPath contains a prefix', () => { + expect(createFromExtensionsRedirects(['/prefix/file'], ['html'])).toEqual([ + {from: '/prefix/file.html', to: '/prefix/file'}, + ]); }); test('should not create redirection for an empty extension array', () => { const ext: string[] = []; - expect(createFromExtensionsRedirects([''], ext, '/')).toEqual([]); - expect(createFromExtensionsRedirects(['/'], ext, '/')).toEqual([]); - expect(createFromExtensionsRedirects(['/abc'], ext, '/')).toEqual([]); - expect(createFromExtensionsRedirects(['/def.html'], ext, '/')).toEqual([]); - expect(createFromExtensionsRedirects(['/def/'], ext, '/')).toEqual([]); + expect(createFromExtensionsRedirects([''], ext)).toEqual([]); + expect(createFromExtensionsRedirects(['/'], ext)).toEqual([]); + expect(createFromExtensionsRedirects(['/abc'], ext)).toEqual([]); + expect(createFromExtensionsRedirects(['/def.html'], ext)).toEqual([]); + expect(createFromExtensionsRedirects(['/def/'], ext)).toEqual([]); }); }); diff --git a/packages/docusaurus-plugin-client-redirects/src/collectRedirects.ts b/packages/docusaurus-plugin-client-redirects/src/collectRedirects.ts index 73cf5f09833e..1a2541897d90 100644 --- a/packages/docusaurus-plugin-client-redirects/src/collectRedirects.ts +++ b/packages/docusaurus-plugin-client-redirects/src/collectRedirects.ts @@ -50,9 +50,7 @@ function validateCollectedRedirects( ); } - const allowedToPaths = pluginContext.routesPaths.map((path) => - path.replace(pluginContext.baseUrl, '/'), - ); + const allowedToPaths = pluginContext.relativeRoutesPaths; const toPaths = redirects.map((redirect) => redirect.to); const illegalToPaths = difference(toPaths, allowedToPaths); if (illegalToPaths.length > 0) { @@ -91,7 +89,7 @@ It is not possible to redirect the same pathname to multiple destinations: // We don't want to override an already existing route with a redirect file! const redirectsOverridingExistingPath = redirects.filter((redirect) => - pluginContext.routesPaths.includes(redirect.from), + pluginContext.relativeRoutesPaths.includes(redirect.from), ); if (redirectsOverridingExistingPath.length > 0) { console.error( @@ -103,7 +101,7 @@ It is not possible to redirect the same pathname to multiple destinations: ); } redirects = redirects.filter( - (redirect) => !pluginContext.routesPaths.includes(redirect.from), + (redirect) => !pluginContext.relativeRoutesPaths.includes(redirect.from), ); return redirects; @@ -113,18 +111,16 @@ It is not possible to redirect the same pathname to multiple destinations: function doCollectRedirects(pluginContext: PluginContext): RedirectMetadata[] { return [ ...createFromExtensionsRedirects( - pluginContext.routesPaths, + pluginContext.relativeRoutesPaths, pluginContext.options.fromExtensions, - pluginContext.baseUrl, ), ...createToExtensionsRedirects( - pluginContext.routesPaths, + pluginContext.relativeRoutesPaths, pluginContext.options.toExtensions, - pluginContext.baseUrl, ), ...createRedirectsOptionRedirects(pluginContext.options.redirects), ...createCreateRedirectsOptionRedirects( - pluginContext.routesPaths, + pluginContext.relativeRoutesPaths, pluginContext.options.createRedirects, ), ]; diff --git a/packages/docusaurus-plugin-client-redirects/src/extensionRedirects.ts b/packages/docusaurus-plugin-client-redirects/src/extensionRedirects.ts index ee43f329d7a7..9adebf43b437 100644 --- a/packages/docusaurus-plugin-client-redirects/src/extensionRedirects.ts +++ b/packages/docusaurus-plugin-client-redirects/src/extensionRedirects.ts @@ -43,7 +43,6 @@ const addLeadingDot = (extension: string) => `.${extension}`; export function createToExtensionsRedirects( paths: string[], extensions: string[], - baseUrl: string, ): RedirectMetadata[] { extensions.forEach(validateExtension); @@ -54,8 +53,8 @@ export function createToExtensionsRedirects( if (extensionFound) { const routePathWithoutExtension = removeSuffix(path, extensionFound); return [routePathWithoutExtension].map((from) => ({ - from: trimBaseUrl(from, baseUrl), - to: trimBaseUrl(path, baseUrl), + from, + to: path, })); } return []; @@ -68,7 +67,6 @@ export function createToExtensionsRedirects( export function createFromExtensionsRedirects( paths: string[], extensions: string[], - baseUrl: string, ): RedirectMetadata[] { extensions.forEach(validateExtension); @@ -82,14 +80,10 @@ export function createFromExtensionsRedirects( return []; } return extensions.map((ext) => ({ - from: `${trimBaseUrl(path, baseUrl)}.${ext}`, - to: trimBaseUrl(path, baseUrl), + from: `${path}.${ext}`, + to: path, })); }; return flatten(paths.map(createPathRedirects)); } - -function trimBaseUrl(path: string, baseUrl: string) { - return path.startsWith(baseUrl) ? path.replace(baseUrl, '/') : path; -} diff --git a/packages/docusaurus-plugin-client-redirects/src/index.ts b/packages/docusaurus-plugin-client-redirects/src/index.ts index c94b734514e4..ef630903c8c7 100644 --- a/packages/docusaurus-plugin-client-redirects/src/index.ts +++ b/packages/docusaurus-plugin-client-redirects/src/index.ts @@ -14,6 +14,7 @@ import writeRedirectFiles, { toRedirectFilesMetadata, RedirectFileMetadata, } from './writeRedirectFiles'; +import {removePrefix} from '@docusaurus/utils'; export default function pluginClientRedirectsPages( _context: LoadContext, @@ -25,7 +26,9 @@ export default function pluginClientRedirectsPages( name: 'docusaurus-plugin-client-redirects', async postBuild(props: Props) { const pluginContext: PluginContext = { - routesPaths: props.routesPaths, + relativeRoutesPaths: props.routesPaths.map( + (path) => `/${removePrefix(path, props.baseUrl)}`, + ), baseUrl: props.baseUrl, outDir: props.outDir, options, diff --git a/packages/docusaurus-plugin-client-redirects/src/types.ts b/packages/docusaurus-plugin-client-redirects/src/types.ts index fe8b0296ed03..15c0987e0dd0 100644 --- a/packages/docusaurus-plugin-client-redirects/src/types.ts +++ b/packages/docusaurus-plugin-client-redirects/src/types.ts @@ -28,11 +28,9 @@ export type RedirectOption = { export type UserPluginOptions = Partial; // The minimal infos the plugin needs to work -export type PluginContext = Pick< - Props, - 'routesPaths' | 'outDir' | 'baseUrl' -> & { +export type PluginContext = Pick & { options: PluginOptions; + relativeRoutesPaths: string[]; }; // In-memory representation of redirects we want: easier to test diff --git a/packages/docusaurus-utils/src/__tests__/index.test.ts b/packages/docusaurus-utils/src/__tests__/index.test.ts index 29cde6684ae4..23167e457351 100644 --- a/packages/docusaurus-utils/src/__tests__/index.test.ts +++ b/packages/docusaurus-utils/src/__tests__/index.test.ts @@ -22,6 +22,7 @@ import { addTrailingSlash, removeTrailingSlash, removeSuffix, + removePrefix, getFilePathForRoutePath, } from '../index'; @@ -419,6 +420,21 @@ describe('removeSuffix', () => { }); }); +describe('removePrefix', () => { + test('should no-op 1', () => { + expect(removePrefix('abcdef', 'ijk')).toEqual('abcdef'); + }); + test('should no-op 2', () => { + expect(removePrefix('abcdef', 'def')).toEqual('abcdef'); + }); + test('should no-op 3', () => { + expect(removePrefix('abcdef', '')).toEqual('abcdef'); + }); + test('should remove prefix', () => { + expect(removePrefix('abcdef', 'ab')).toEqual('cdef'); + }); +}); + describe('getFilePathForRoutePath', () => { test('works for /', () => { expect(getFilePathForRoutePath('/')).toEqual('/index.html'); diff --git a/packages/docusaurus-utils/src/index.ts b/packages/docusaurus-utils/src/index.ts index 542b05ba2450..25acc1b709e8 100644 --- a/packages/docusaurus-utils/src/index.ts +++ b/packages/docusaurus-utils/src/index.ts @@ -384,6 +384,10 @@ export function removeSuffix(str: string, suffix: string): string { return str.endsWith(suffix) ? str.slice(0, -suffix.length) : str; } +export function removePrefix(str: string, prefix: string): string { + return str.startsWith(prefix) ? str.slice(prefix.length) : str; +} + export function getFilePathForRoutePath(routePath: string): string { const fileName = path.basename(routePath); const filePath = path.dirname(routePath); diff --git a/packages/docusaurus/src/server/configValidation.ts b/packages/docusaurus/src/server/configValidation.ts index f745a35cfc35..c80525930f4e 100644 --- a/packages/docusaurus/src/server/configValidation.ts +++ b/packages/docusaurus/src/server/configValidation.ts @@ -90,23 +90,23 @@ export function validateConfig( abortEarly: false, }); if (error) { - const unknownFields = error.details.reduce((formatedError, err) => { + const unknownFields = error.details.reduce((formattedError, err) => { if (err.type === 'object.unknown') { - return `${formatedError}"${err.path}",`; + return `${formattedError}"${err.path}",`; } - return formatedError; + return formattedError; }, ''); - let formatedError = error.details.reduce( - (accumalatedErr, err) => + let formattedError = error.details.reduce( + (accumulatedErr, err) => err.type !== 'object.unknown' - ? `${accumalatedErr}${err.message}\n` - : accumalatedErr, + ? `${accumulatedErr}${err.message}\n` + : accumulatedErr, '', ); - formatedError = unknownFields - ? `${formatedError}These field(s) [${unknownFields}] are not recognized in ${CONFIG_FILE_NAME}.\nIf you still want these fields to be in your configuration, put them in the 'customFields' attribute.\nSee https://v2.docusaurus.io/docs/docusaurus.config.js/#customfields` - : formatedError; - throw new Error(formatedError); + formattedError = unknownFields + ? `${formattedError}These field(s) [${unknownFields}] are not recognized in ${CONFIG_FILE_NAME}.\nIf you still want these fields to be in your configuration, put them in the 'customFields' attribute.\nSee https://v2.docusaurus.io/docs/docusaurus.config.js/#customfields` + : formattedError; + throw new Error(formattedError); } else { return value; } diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index 43c06f7943ef..1567e83c7f5d 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -8,9 +8,9 @@ const versions = require('./versions.json'); const allDocHomesPaths = [ - '/docs', - '/docs/next', - ...versions.slice(1).map((version) => `/docs/${version}`), + '/docs/', + '/docs/next/', + ...versions.slice(1).map((version) => `/docs/${version}/`), ]; module.exports = {