@docusaurus/plugin-client-redirects 2.0.0-beta.15d451942 → 2.0.0-beta.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/collectRedirects.d.ts +2 -2
- package/lib/collectRedirects.js +40 -28
- package/lib/createRedirectPageContent.js +4 -6
- package/lib/extensionRedirects.d.ts +1 -1
- package/lib/extensionRedirects.js +16 -12
- package/lib/index.d.ts +3 -3
- package/lib/index.js +10 -9
- package/lib/normalizePluginOptions.d.ts +1 -1
- package/lib/normalizePluginOptions.js +2 -2
- package/lib/redirectValidation.d.ts +1 -1
- package/lib/types.d.ts +2 -14
- package/lib/writeRedirectFiles.d.ts +2 -2
- package/lib/writeRedirectFiles.js +38 -12
- package/package.json +16 -13
- package/src/collectRedirects.ts +54 -37
- package/src/createRedirectPageContent.ts +4 -4
- package/src/extensionRedirects.ts +21 -15
- package/src/index.ts +13 -6
- package/src/normalizePluginOptions.ts +6 -2
- package/src/plugin-client-redirects.d.ts +27 -0
- package/src/redirectValidation.ts +1 -1
- package/src/types.ts +2 -22
- package/src/writeRedirectFiles.ts +42 -12
- package/lib/.tsbuildinfo +0 -1970
- package/src/__tests__/__snapshots__/collectRedirects.test.ts.snap +0 -27
- package/src/__tests__/__snapshots__/createRedirectPageContent.test.ts.snap +0 -29
- package/src/__tests__/__snapshots__/normalizePluginOptions.test.ts.snap +0 -35
- package/src/__tests__/__snapshots__/redirectValidation.test.ts.snap +0 -11
- package/src/__tests__/__snapshots__/writeRedirectFiles.test.ts.snap +0 -71
- package/src/__tests__/collectRedirects.test.ts +0 -255
- package/src/__tests__/createRedirectPageContent.test.ts +0 -26
- package/src/__tests__/extensionRedirects.test.ts +0 -109
- package/src/__tests__/normalizePluginOptions.test.ts +0 -76
- package/src/__tests__/redirectValidation.test.ts +0 -66
- package/src/__tests__/writeRedirectFiles.test.ts +0 -146
- package/tsconfig.json +0 -10
package/src/collectRedirects.ts
CHANGED
|
@@ -5,40 +5,67 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
10
|
-
PluginContext,
|
|
11
|
-
RedirectMetadata,
|
|
8
|
+
import _ from 'lodash';
|
|
9
|
+
import type {
|
|
12
10
|
PluginOptions,
|
|
13
11
|
RedirectOption,
|
|
14
|
-
} from '
|
|
12
|
+
} from '@docusaurus/plugin-client-redirects';
|
|
13
|
+
import type {PluginContext, RedirectMetadata} from './types';
|
|
15
14
|
import {
|
|
16
15
|
createFromExtensionsRedirects,
|
|
17
16
|
createToExtensionsRedirects,
|
|
18
17
|
} from './extensionRedirects';
|
|
19
18
|
import {validateRedirect} from './redirectValidation';
|
|
19
|
+
import {
|
|
20
|
+
applyTrailingSlash,
|
|
21
|
+
type ApplyTrailingSlashParams,
|
|
22
|
+
} from '@docusaurus/utils-common';
|
|
20
23
|
|
|
21
|
-
import
|
|
24
|
+
import logger from '@docusaurus/logger';
|
|
22
25
|
|
|
23
26
|
export default function collectRedirects(
|
|
24
27
|
pluginContext: PluginContext,
|
|
28
|
+
trailingSlash: boolean | undefined,
|
|
25
29
|
): RedirectMetadata[] {
|
|
26
|
-
|
|
30
|
+
let redirects = doCollectRedirects(pluginContext);
|
|
31
|
+
|
|
32
|
+
redirects = applyRedirectsTrailingSlash(redirects, {
|
|
33
|
+
trailingSlash,
|
|
34
|
+
baseUrl: pluginContext.baseUrl,
|
|
35
|
+
});
|
|
36
|
+
|
|
27
37
|
validateCollectedRedirects(redirects, pluginContext);
|
|
28
38
|
return filterUnwantedRedirects(redirects, pluginContext);
|
|
29
39
|
}
|
|
30
40
|
|
|
41
|
+
// If users wants to redirect to=/abc and they enable trailingSlash=true then
|
|
42
|
+
// => we don't want to reject the to=/abc (as only /abc/ is an existing/valid
|
|
43
|
+
// path now)
|
|
44
|
+
// => we want to redirect to=/abc/ without the user having to change all its
|
|
45
|
+
// redirect plugin options
|
|
46
|
+
// It should be easy to toggle siteConfig.trailingSlash option without having to
|
|
47
|
+
// change other configs
|
|
48
|
+
function applyRedirectsTrailingSlash(
|
|
49
|
+
redirects: RedirectMetadata[],
|
|
50
|
+
params: ApplyTrailingSlashParams,
|
|
51
|
+
) {
|
|
52
|
+
return redirects.map((redirect) => ({
|
|
53
|
+
...redirect,
|
|
54
|
+
to: applyTrailingSlash(redirect.to, params),
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
|
|
31
58
|
function validateCollectedRedirects(
|
|
32
59
|
redirects: RedirectMetadata[],
|
|
33
60
|
pluginContext: PluginContext,
|
|
34
61
|
) {
|
|
35
|
-
const redirectValidationErrors
|
|
62
|
+
const redirectValidationErrors = redirects
|
|
36
63
|
.map((redirect) => {
|
|
37
64
|
try {
|
|
38
65
|
validateRedirect(redirect);
|
|
39
66
|
return undefined;
|
|
40
|
-
} catch (
|
|
41
|
-
return
|
|
67
|
+
} catch (err) {
|
|
68
|
+
return (err as Error).message;
|
|
42
69
|
}
|
|
43
70
|
})
|
|
44
71
|
.filter(Boolean);
|
|
@@ -52,7 +79,7 @@ function validateCollectedRedirects(
|
|
|
52
79
|
|
|
53
80
|
const allowedToPaths = pluginContext.relativeRoutesPaths;
|
|
54
81
|
const toPaths = redirects.map((redirect) => redirect.to);
|
|
55
|
-
const illegalToPaths = difference(toPaths, allowedToPaths);
|
|
82
|
+
const illegalToPaths = _.difference(toPaths, allowedToPaths);
|
|
56
83
|
if (illegalToPaths.length > 0) {
|
|
57
84
|
throw new Error(
|
|
58
85
|
`You are trying to create client-side redirections to paths that do not exist:
|
|
@@ -71,34 +98,26 @@ function filterUnwantedRedirects(
|
|
|
71
98
|
): RedirectMetadata[] {
|
|
72
99
|
// we don't want to create twice the same redirect
|
|
73
100
|
// that would lead to writing twice the same html redirection file
|
|
74
|
-
Object.entries(groupBy(redirects, (redirect) => redirect.from)).forEach(
|
|
101
|
+
Object.entries(_.groupBy(redirects, (redirect) => redirect.from)).forEach(
|
|
75
102
|
([from, groupedFromRedirects]) => {
|
|
76
103
|
if (groupedFromRedirects.length > 1) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
- ${groupedFromRedirects.map((r) => JSON.stringify(r)).join('\n- ')}
|
|
82
|
-
`,
|
|
83
|
-
),
|
|
84
|
-
);
|
|
104
|
+
logger.error`name=${'@docusaurus/plugin-client-redirects'}: multiple redirects are created with the same "from" pathname: path=${from}
|
|
105
|
+
It is not possible to redirect the same pathname to multiple destinations: ${groupedFromRedirects.map(
|
|
106
|
+
(r) => JSON.stringify(r),
|
|
107
|
+
)}`;
|
|
85
108
|
}
|
|
86
109
|
},
|
|
87
110
|
);
|
|
88
|
-
const collectedRedirects = uniqBy(redirects, (redirect) => redirect.from);
|
|
111
|
+
const collectedRedirects = _.uniqBy(redirects, (redirect) => redirect.from);
|
|
89
112
|
|
|
90
113
|
// We don't want to override an already existing route with a redirect file!
|
|
91
114
|
const redirectsOverridingExistingPath = collectedRedirects.filter(
|
|
92
115
|
(redirect) => pluginContext.relativeRoutesPaths.includes(redirect.from),
|
|
93
116
|
);
|
|
94
117
|
if (redirectsOverridingExistingPath.length > 0) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
- ${redirectsOverridingExistingPath.map((r) => JSON.stringify(r)).join('\n- ')}
|
|
99
|
-
`,
|
|
100
|
-
),
|
|
101
|
-
);
|
|
118
|
+
logger.error`name=${'@docusaurus/plugin-client-redirects'}: some redirects would override existing paths, and will be ignored: ${redirectsOverridingExistingPath.map(
|
|
119
|
+
(r) => JSON.stringify(r),
|
|
120
|
+
)}`;
|
|
102
121
|
}
|
|
103
122
|
return collectedRedirects.filter(
|
|
104
123
|
(redirect) => !pluginContext.relativeRoutesPaths.includes(redirect.from),
|
|
@@ -127,7 +146,7 @@ function doCollectRedirects(pluginContext: PluginContext): RedirectMetadata[] {
|
|
|
127
146
|
function createRedirectsOptionRedirects(
|
|
128
147
|
redirectsOption: PluginOptions['redirects'],
|
|
129
148
|
): RedirectMetadata[] {
|
|
130
|
-
// For
|
|
149
|
+
// For convenience, user can use a string or a string[]
|
|
131
150
|
function optionToRedirects(option: RedirectOption): RedirectMetadata[] {
|
|
132
151
|
if (typeof option.from === 'string') {
|
|
133
152
|
return [{from: option.from, to: option.to}];
|
|
@@ -138,7 +157,7 @@ function createRedirectsOptionRedirects(
|
|
|
138
157
|
}));
|
|
139
158
|
}
|
|
140
159
|
|
|
141
|
-
return
|
|
160
|
+
return redirectsOption.flatMap(optionToRedirects);
|
|
142
161
|
}
|
|
143
162
|
|
|
144
163
|
// Create redirects from the "createRedirects" fn provided by the user
|
|
@@ -154,13 +173,11 @@ function createCreateRedirectsOptionRedirects(
|
|
|
154
173
|
const froms: string[] =
|
|
155
174
|
typeof fromsMixed === 'string' ? [fromsMixed] : fromsMixed;
|
|
156
175
|
|
|
157
|
-
return froms.map((from) => {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
};
|
|
162
|
-
});
|
|
176
|
+
return froms.map((from) => ({
|
|
177
|
+
from,
|
|
178
|
+
to: path,
|
|
179
|
+
}));
|
|
163
180
|
}
|
|
164
181
|
|
|
165
|
-
return
|
|
182
|
+
return paths.flatMap(createPathRedirects);
|
|
166
183
|
}
|
|
@@ -7,15 +7,15 @@
|
|
|
7
7
|
|
|
8
8
|
import * as eta from 'eta';
|
|
9
9
|
import redirectPageTemplate from './templates/redirectPage.template.html';
|
|
10
|
-
import
|
|
10
|
+
import _ from 'lodash';
|
|
11
11
|
|
|
12
12
|
type CreateRedirectPageOptions = {
|
|
13
13
|
toUrl: string;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
-
const getCompiledRedirectPageTemplate = memoize(() =>
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
const getCompiledRedirectPageTemplate = _.memoize(() =>
|
|
17
|
+
eta.compile(redirectPageTemplate.trim()),
|
|
18
|
+
);
|
|
19
19
|
|
|
20
20
|
function renderRedirectPageTemplate(data: Record<string, unknown>) {
|
|
21
21
|
const compiled = getCompiledRedirectPageTemplate();
|
|
@@ -5,34 +5,35 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
import {
|
|
9
|
+
addTrailingSlash,
|
|
10
|
+
removeSuffix,
|
|
11
|
+
removeTrailingSlash,
|
|
12
|
+
} from '@docusaurus/utils';
|
|
13
|
+
import type {RedirectMetadata} from './types';
|
|
11
14
|
|
|
12
15
|
const ExtensionAdditionalMessage =
|
|
13
|
-
|
|
16
|
+
'If the redirect extension system is not good enough for your use case, you can create redirects yourself with the "createRedirects" plugin option.';
|
|
14
17
|
|
|
15
18
|
const validateExtension = (ext: string) => {
|
|
16
19
|
if (!ext) {
|
|
17
20
|
throw new Error(
|
|
18
|
-
`Extension
|
|
19
|
-
ext,
|
|
20
|
-
)}'] is not allowed. ${ExtensionAdditionalMessage}`,
|
|
21
|
+
`Extension "${ext}" is not allowed.\n${ExtensionAdditionalMessage}`,
|
|
21
22
|
);
|
|
22
23
|
}
|
|
23
24
|
if (ext.includes('.')) {
|
|
24
25
|
throw new Error(
|
|
25
|
-
`Extension
|
|
26
|
+
`Extension "${ext}" contains a "." (dot) which is not allowed.\n${ExtensionAdditionalMessage}`,
|
|
26
27
|
);
|
|
27
28
|
}
|
|
28
29
|
if (ext.includes('/')) {
|
|
29
30
|
throw new Error(
|
|
30
|
-
`Extension
|
|
31
|
+
`Extension "${ext}" contains a "/" (slash) which is not allowed.\n${ExtensionAdditionalMessage}`,
|
|
31
32
|
);
|
|
32
33
|
}
|
|
33
34
|
if (encodeURIComponent(ext) !== ext) {
|
|
34
35
|
throw new Error(
|
|
35
|
-
`Extension
|
|
36
|
+
`Extension "${ext}" contains invalid URI characters.\n${ExtensionAdditionalMessage}`,
|
|
36
37
|
);
|
|
37
38
|
}
|
|
38
39
|
};
|
|
@@ -60,10 +61,11 @@ export function createToExtensionsRedirects(
|
|
|
60
61
|
return [];
|
|
61
62
|
};
|
|
62
63
|
|
|
63
|
-
return
|
|
64
|
+
return paths.flatMap(createPathRedirects);
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
// Create new /path.html that redirects to existing an /path
|
|
67
|
+
// Create new /path.html/index.html that redirects to existing an /path
|
|
68
|
+
// The filename pattern might look weird but it's on purpose (see https://github.com/facebook/docusaurus/issues/5055)
|
|
67
69
|
export function createFromExtensionsRedirects(
|
|
68
70
|
paths: string[],
|
|
69
71
|
extensions: string[],
|
|
@@ -76,14 +78,18 @@ export function createFromExtensionsRedirects(
|
|
|
76
78
|
dottedExtensions.some((ext) => str.endsWith(ext));
|
|
77
79
|
|
|
78
80
|
const createPathRedirects = (path: string): RedirectMetadata[] => {
|
|
79
|
-
if (path === '' || path
|
|
81
|
+
if (path === '' || path === '/' || alreadyEndsWithAnExtension(path)) {
|
|
80
82
|
return [];
|
|
81
83
|
}
|
|
82
84
|
return extensions.map((ext) => ({
|
|
83
|
-
|
|
85
|
+
// /path => /path.html
|
|
86
|
+
// /path/ => /path.html/
|
|
87
|
+
from: path.endsWith('/')
|
|
88
|
+
? addTrailingSlash(`${removeTrailingSlash(path)}.${ext}`)
|
|
89
|
+
: `${path}.${ext}`,
|
|
84
90
|
to: path,
|
|
85
91
|
}));
|
|
86
92
|
};
|
|
87
93
|
|
|
88
|
-
return
|
|
94
|
+
return paths.flatMap(createPathRedirects);
|
|
89
95
|
}
|
package/src/index.ts
CHANGED
|
@@ -5,21 +5,24 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {LoadContext, Plugin, Props} from '@docusaurus/types';
|
|
9
|
-
import {
|
|
8
|
+
import type {LoadContext, Plugin, Props} from '@docusaurus/types';
|
|
9
|
+
import type {PluginContext, RedirectMetadata} from './types';
|
|
10
|
+
import type {PluginOptions} from '@docusaurus/plugin-client-redirects';
|
|
10
11
|
|
|
11
12
|
import normalizePluginOptions from './normalizePluginOptions';
|
|
12
13
|
import collectRedirects from './collectRedirects';
|
|
13
14
|
import writeRedirectFiles, {
|
|
14
15
|
toRedirectFilesMetadata,
|
|
15
|
-
RedirectFileMetadata,
|
|
16
|
+
type RedirectFileMetadata,
|
|
16
17
|
} from './writeRedirectFiles';
|
|
17
18
|
import {removePrefix, addLeadingSlash} from '@docusaurus/utils';
|
|
18
19
|
|
|
19
20
|
export default function pluginClientRedirectsPages(
|
|
20
|
-
|
|
21
|
-
opts:
|
|
21
|
+
context: LoadContext,
|
|
22
|
+
opts: PluginOptions,
|
|
22
23
|
): Plugin<unknown> {
|
|
24
|
+
const {trailingSlash} = context.siteConfig;
|
|
25
|
+
|
|
23
26
|
const options = normalizePluginOptions(opts);
|
|
24
27
|
|
|
25
28
|
return {
|
|
@@ -34,11 +37,15 @@ export default function pluginClientRedirectsPages(
|
|
|
34
37
|
options,
|
|
35
38
|
};
|
|
36
39
|
|
|
37
|
-
const redirects: RedirectMetadata[] = collectRedirects(
|
|
40
|
+
const redirects: RedirectMetadata[] = collectRedirects(
|
|
41
|
+
pluginContext,
|
|
42
|
+
trailingSlash,
|
|
43
|
+
);
|
|
38
44
|
|
|
39
45
|
const redirectFiles: RedirectFileMetadata[] = toRedirectFilesMetadata(
|
|
40
46
|
redirects,
|
|
41
47
|
pluginContext,
|
|
48
|
+
trailingSlash,
|
|
42
49
|
);
|
|
43
50
|
|
|
44
51
|
// Write files only at the end: make code more easy to test without IO
|
|
@@ -5,9 +5,13 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import type {
|
|
9
|
+
PluginOptions,
|
|
10
|
+
Options as UserPluginOptions,
|
|
11
|
+
RedirectOption,
|
|
12
|
+
} from '@docusaurus/plugin-client-redirects';
|
|
9
13
|
import {Joi, PathnameSchema} from '@docusaurus/utils-validation';
|
|
10
|
-
import {DEFAULT_PLUGIN_ID} from '@docusaurus/
|
|
14
|
+
import {DEFAULT_PLUGIN_ID} from '@docusaurus/utils';
|
|
11
15
|
|
|
12
16
|
export const DefaultPluginOptions: PluginOptions = {
|
|
13
17
|
id: DEFAULT_PLUGIN_ID, // TODO temporary
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type RedirectOption = {
|
|
9
|
+
to: string;
|
|
10
|
+
from: string | string[];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// For a given existing route path,
|
|
14
|
+
// return all the paths from which we should redirect from
|
|
15
|
+
export type CreateRedirectsFnOption = (
|
|
16
|
+
path: string,
|
|
17
|
+
) => string[] | string | null | undefined;
|
|
18
|
+
|
|
19
|
+
export type PluginOptions = {
|
|
20
|
+
id: string;
|
|
21
|
+
fromExtensions: string[];
|
|
22
|
+
toExtensions: string[];
|
|
23
|
+
redirects: RedirectOption[];
|
|
24
|
+
createRedirects?: CreateRedirectsFnOption;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type Options = Partial<PluginOptions>;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {RedirectMetadata} from './types';
|
|
8
|
+
import type {RedirectMetadata} from './types';
|
|
9
9
|
import {Joi, PathnameSchema} from '@docusaurus/utils-validation';
|
|
10
10
|
|
|
11
11
|
const RedirectSchema = Joi.object<RedirectMetadata>({
|
package/src/types.ts
CHANGED
|
@@ -5,28 +5,8 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {Props} from '@docusaurus/types';
|
|
9
|
-
|
|
10
|
-
export type PluginOptions = {
|
|
11
|
-
id: string;
|
|
12
|
-
fromExtensions: string[];
|
|
13
|
-
toExtensions: string[];
|
|
14
|
-
redirects: RedirectOption[];
|
|
15
|
-
createRedirects?: CreateRedirectsFnOption;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
// For a given existing route path,
|
|
19
|
-
// return all the paths from which we should redirect from
|
|
20
|
-
export type CreateRedirectsFnOption = (
|
|
21
|
-
path: string,
|
|
22
|
-
) => string[] | string | null | undefined;
|
|
23
|
-
|
|
24
|
-
export type RedirectOption = {
|
|
25
|
-
to: string;
|
|
26
|
-
from: string | string[];
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export type UserPluginOptions = Partial<PluginOptions>;
|
|
8
|
+
import type {Props} from '@docusaurus/types';
|
|
9
|
+
import type {PluginOptions} from '@docusaurus/plugin-client-redirects';
|
|
30
10
|
|
|
31
11
|
// The minimal infos the plugin needs to work
|
|
32
12
|
export type PluginContext = Pick<Props, 'outDir' | 'baseUrl'> & {
|
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
|
|
8
8
|
import fs from 'fs-extra';
|
|
9
9
|
import path from 'path';
|
|
10
|
-
import
|
|
10
|
+
import _ from 'lodash';
|
|
11
11
|
|
|
12
|
-
import {PluginContext, RedirectMetadata} from './types';
|
|
12
|
+
import type {PluginContext, RedirectMetadata} from './types';
|
|
13
13
|
import createRedirectPageContent from './createRedirectPageContent';
|
|
14
|
-
import {
|
|
14
|
+
import {normalizeUrl} from '@docusaurus/utils';
|
|
15
15
|
|
|
16
16
|
export type WriteFilesPluginContext = Pick<PluginContext, 'baseUrl' | 'outDir'>;
|
|
17
17
|
|
|
@@ -24,22 +24,52 @@ export function createToUrl(baseUrl: string, to: string): string {
|
|
|
24
24
|
return normalizeUrl([baseUrl, to]);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
// Create redirect file path
|
|
28
|
+
// Make sure this path has lower precedence over the original file path when
|
|
29
|
+
// served by host providers!
|
|
30
|
+
// Otherwise it can produce infinite redirect loops!
|
|
31
|
+
//
|
|
32
|
+
// See https://github.com/facebook/docusaurus/issues/5055
|
|
33
|
+
// See https://github.com/facebook/docusaurus/pull/5085
|
|
34
|
+
// See https://github.com/facebook/docusaurus/pull/5102
|
|
35
|
+
function getRedirectFilePath(
|
|
36
|
+
fromPath: string,
|
|
37
|
+
trailingSlash: boolean | undefined, // Now unused, on purpose
|
|
38
|
+
): string {
|
|
39
|
+
const fileName = path.basename(fromPath);
|
|
40
|
+
const filePath = path.dirname(fromPath);
|
|
41
|
+
// Edge case for https://github.com/facebook/docusaurus/pull/5102
|
|
42
|
+
// If the redirect source path is /xyz, with file /xyz.html
|
|
43
|
+
// We can't write the redirect file at /xyz.html/index.html because for Unix
|
|
44
|
+
// FS, a file/folder can't have the same name "xyz.html"
|
|
45
|
+
// The only possible solution for a redirect file is thus /xyz.html.html (I
|
|
46
|
+
// know, looks suspicious)
|
|
47
|
+
if (trailingSlash === false && fileName.endsWith('.html')) {
|
|
48
|
+
return path.join(filePath, `${fileName}.html`);
|
|
49
|
+
}
|
|
50
|
+
// If the target path is /xyz, with file /xyz/index.html, we don't want the
|
|
51
|
+
// redirect file to be /xyz.html, otherwise it would be picked in priority and
|
|
52
|
+
// the redirect file would redirect to itself. We prefer the redirect file to
|
|
53
|
+
// be /xyz.html/index.html, served with lower priority for most static hosting
|
|
54
|
+
// tools
|
|
55
|
+
return path.join(filePath, `${fileName}/index.html`);
|
|
56
|
+
}
|
|
57
|
+
|
|
27
58
|
export function toRedirectFilesMetadata(
|
|
28
59
|
redirects: RedirectMetadata[],
|
|
29
60
|
pluginContext: WriteFilesPluginContext,
|
|
61
|
+
trailingSlash: boolean | undefined,
|
|
30
62
|
): RedirectFileMetadata[] {
|
|
31
63
|
// Perf: avoid rendering the template twice with the exact same "props"
|
|
32
64
|
// We might create multiple redirect pages for the same destination url
|
|
33
65
|
// note: the first fn arg is the cache key!
|
|
34
|
-
const createPageContentMemoized = memoize((toUrl: string) =>
|
|
35
|
-
|
|
36
|
-
|
|
66
|
+
const createPageContentMemoized = _.memoize((toUrl: string) =>
|
|
67
|
+
createRedirectPageContent({toUrl}),
|
|
68
|
+
);
|
|
37
69
|
|
|
38
70
|
const createFileMetadata = (redirect: RedirectMetadata) => {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
getFilePathForRoutePath(redirect.from),
|
|
42
|
-
);
|
|
71
|
+
const fileRelativePath = getRedirectFilePath(redirect.from, trailingSlash);
|
|
72
|
+
const fileAbsolutePath = path.join(pluginContext.outDir, fileRelativePath);
|
|
43
73
|
const toUrl = createToUrl(pluginContext.baseUrl, redirect.to);
|
|
44
74
|
const fileContent = createPageContentMemoized(toUrl);
|
|
45
75
|
return {
|
|
@@ -59,7 +89,7 @@ export async function writeRedirectFile(
|
|
|
59
89
|
// User-friendly security to prevent file overrides
|
|
60
90
|
if (await fs.pathExists(file.fileAbsolutePath)) {
|
|
61
91
|
throw new Error(
|
|
62
|
-
'The redirect plugin is not supposed to override existing files',
|
|
92
|
+
'The redirect plugin is not supposed to override existing files.',
|
|
63
93
|
);
|
|
64
94
|
}
|
|
65
95
|
await fs.ensureDir(path.dirname(file.fileAbsolutePath));
|
|
@@ -72,7 +102,7 @@ export async function writeRedirectFile(
|
|
|
72
102
|
);
|
|
73
103
|
} catch (err) {
|
|
74
104
|
throw new Error(
|
|
75
|
-
`Redirect file creation error for
|
|
105
|
+
`Redirect file creation error for "${file.fileAbsolutePath}" path: ${err}.`,
|
|
76
106
|
);
|
|
77
107
|
}
|
|
78
108
|
}
|