@docusaurus/plugin-client-redirects 2.0.0-beta.2 → 2.0.0-beta.22
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 +44 -60
- package/lib/createRedirectPageContent.d.ts +2 -4
- package/lib/createRedirectPageContent.js +2 -4
- package/lib/extensionRedirects.d.ts +11 -3
- package/lib/extensionRedirects.js +18 -13
- package/lib/index.d.ts +5 -3
- package/lib/index.js +10 -16
- package/lib/options.d.ts +35 -0
- package/lib/options.js +36 -0
- package/lib/redirectValidation.d.ts +2 -2
- package/lib/types.d.ts +14 -16
- package/lib/writeRedirectFiles.d.ts +5 -5
- package/lib/writeRedirectFiles.js +40 -14
- package/package.json +16 -14
- package/src/collectRedirects.ts +75 -101
- package/src/createRedirectPageContent.ts +8 -10
- package/src/deps.d.ts +16 -0
- package/src/extensionRedirects.ts +28 -19
- package/src/index.ts +15 -27
- package/src/options.ts +75 -0
- package/src/redirectValidation.ts +3 -3
- package/src/types.ts +16 -30
- package/src/writeRedirectFiles.ts +51 -22
- package/lib/.tsbuildinfo +0 -1
- package/lib/normalizePluginOptions.d.ts +0 -9
- package/lib/normalizePluginOptions.js +0 -44
- 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 -147
- package/src/__tests__/collectRedirects.test.ts +0 -340
- package/src/__tests__/createRedirectPageContent.test.ts +0 -26
- package/src/__tests__/extensionRedirects.test.ts +0 -140
- package/src/__tests__/normalizePluginOptions.test.ts +0 -76
- package/src/__tests__/redirectValidation.test.ts +0 -66
- package/src/__tests__/writeRedirectFiles.test.ts +0 -203
- package/src/normalizePluginOptions.ts +0 -55
- package/tsconfig.json +0 -10
package/src/collectRedirects.ts
CHANGED
|
@@ -5,59 +5,68 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
RedirectMetadata,
|
|
12
|
-
PluginOptions,
|
|
13
|
-
RedirectOption,
|
|
14
|
-
} from './types';
|
|
8
|
+
import _ from 'lodash';
|
|
9
|
+
import logger from '@docusaurus/logger';
|
|
10
|
+
import {applyTrailingSlash} from '@docusaurus/utils-common';
|
|
15
11
|
import {
|
|
16
12
|
createFromExtensionsRedirects,
|
|
17
13
|
createToExtensionsRedirects,
|
|
18
14
|
} from './extensionRedirects';
|
|
19
15
|
import {validateRedirect} from './redirectValidation';
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
import chalk from 'chalk';
|
|
16
|
+
import type {PluginOptions, RedirectOption} from './options';
|
|
17
|
+
import type {PluginContext, RedirectItem} from './types';
|
|
23
18
|
|
|
24
19
|
export default function collectRedirects(
|
|
25
20
|
pluginContext: PluginContext,
|
|
26
21
|
trailingSlash: boolean | undefined,
|
|
27
|
-
):
|
|
28
|
-
|
|
29
|
-
redirects =
|
|
22
|
+
): RedirectItem[] {
|
|
23
|
+
// For each plugin config option, create the appropriate redirects
|
|
24
|
+
const redirects = [
|
|
25
|
+
...createFromExtensionsRedirects(
|
|
26
|
+
pluginContext.relativeRoutesPaths,
|
|
27
|
+
pluginContext.options.fromExtensions,
|
|
28
|
+
),
|
|
29
|
+
...createToExtensionsRedirects(
|
|
30
|
+
pluginContext.relativeRoutesPaths,
|
|
31
|
+
pluginContext.options.toExtensions,
|
|
32
|
+
),
|
|
33
|
+
...createRedirectsOptionRedirects(pluginContext.options.redirects),
|
|
34
|
+
...createCreateRedirectsOptionRedirects(
|
|
35
|
+
pluginContext.relativeRoutesPaths,
|
|
36
|
+
pluginContext.options.createRedirects,
|
|
37
|
+
),
|
|
38
|
+
].map((redirect) => ({
|
|
39
|
+
...redirect,
|
|
40
|
+
// Given a redirect with `to: "/abc"` and `trailingSlash` enabled:
|
|
41
|
+
//
|
|
42
|
+
// - We don't want to reject `to: "/abc"`, as that unambiguously points to
|
|
43
|
+
// `/abc/` now;
|
|
44
|
+
// - We want to redirect `to: /abc/` without the user having to change all
|
|
45
|
+
// her redirect plugin options
|
|
46
|
+
//
|
|
47
|
+
// It should be easy to toggle `trailingSlash` option without having to
|
|
48
|
+
// change other configs
|
|
49
|
+
to: applyTrailingSlash(redirect.to, {
|
|
50
|
+
trailingSlash,
|
|
51
|
+
baseUrl: pluginContext.baseUrl,
|
|
52
|
+
}),
|
|
53
|
+
}));
|
|
54
|
+
|
|
30
55
|
validateCollectedRedirects(redirects, pluginContext);
|
|
31
56
|
return filterUnwantedRedirects(redirects, pluginContext);
|
|
32
57
|
}
|
|
33
58
|
|
|
34
|
-
// If users wants to redirect to=/abc and they enable trailingSlash=true then
|
|
35
|
-
// => we don't want to reject the to=/abc (as only /abc/ is an existing/valid path now)
|
|
36
|
-
// => we want to redirect to=/abc/ without the user having to change all its redirect plugin options
|
|
37
|
-
// It should be easy to toggle siteConfig.trailingSlash option without having to change other configs
|
|
38
|
-
function applyRedirectsTrailingSlash(
|
|
39
|
-
redirects: RedirectMetadata[],
|
|
40
|
-
trailingSlash: boolean | undefined,
|
|
41
|
-
) {
|
|
42
|
-
return redirects.map((redirect) => {
|
|
43
|
-
return {
|
|
44
|
-
...redirect,
|
|
45
|
-
to: applyTrailingSlash(redirect.to, trailingSlash),
|
|
46
|
-
};
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
59
|
function validateCollectedRedirects(
|
|
51
|
-
redirects:
|
|
60
|
+
redirects: RedirectItem[],
|
|
52
61
|
pluginContext: PluginContext,
|
|
53
62
|
) {
|
|
54
|
-
const redirectValidationErrors
|
|
63
|
+
const redirectValidationErrors = redirects
|
|
55
64
|
.map((redirect) => {
|
|
56
65
|
try {
|
|
57
66
|
validateRedirect(redirect);
|
|
58
67
|
return undefined;
|
|
59
|
-
} catch (
|
|
60
|
-
return
|
|
68
|
+
} catch (err) {
|
|
69
|
+
return (err as Error).message;
|
|
61
70
|
}
|
|
62
71
|
})
|
|
63
72
|
.filter(Boolean);
|
|
@@ -71,7 +80,7 @@ function validateCollectedRedirects(
|
|
|
71
80
|
|
|
72
81
|
const allowedToPaths = pluginContext.relativeRoutesPaths;
|
|
73
82
|
const toPaths = redirects.map((redirect) => redirect.to);
|
|
74
|
-
const illegalToPaths = difference(toPaths, allowedToPaths);
|
|
83
|
+
const illegalToPaths = _.difference(toPaths, allowedToPaths);
|
|
75
84
|
if (illegalToPaths.length > 0) {
|
|
76
85
|
throw new Error(
|
|
77
86
|
`You are trying to create client-side redirections to paths that do not exist:
|
|
@@ -85,101 +94,66 @@ Valid paths you can redirect to:
|
|
|
85
94
|
}
|
|
86
95
|
|
|
87
96
|
function filterUnwantedRedirects(
|
|
88
|
-
redirects:
|
|
97
|
+
redirects: RedirectItem[],
|
|
89
98
|
pluginContext: PluginContext,
|
|
90
|
-
):
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
Object.entries(groupBy(redirects, (redirect) => redirect.from)).forEach(
|
|
99
|
+
): RedirectItem[] {
|
|
100
|
+
// We don't want to create the same redirect twice, since that would lead to
|
|
101
|
+
// writing the same html redirection file twice.
|
|
102
|
+
Object.entries(_.groupBy(redirects, (redirect) => redirect.from)).forEach(
|
|
94
103
|
([from, groupedFromRedirects]) => {
|
|
95
104
|
if (groupedFromRedirects.length > 1) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
It is not possible to redirect the same pathname to multiple destinations
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
),
|
|
103
|
-
);
|
|
105
|
+
logger.report(
|
|
106
|
+
pluginContext.siteConfig.onDuplicateRoutes,
|
|
107
|
+
)`name=${'@docusaurus/plugin-client-redirects'}: multiple redirects are created with the same "from" pathname: path=${from}
|
|
108
|
+
It is not possible to redirect the same pathname to multiple destinations:${groupedFromRedirects.map(
|
|
109
|
+
(r) => JSON.stringify(r),
|
|
110
|
+
)}`;
|
|
104
111
|
}
|
|
105
112
|
},
|
|
106
113
|
);
|
|
107
|
-
const collectedRedirects = uniqBy(redirects, (redirect) => redirect.from);
|
|
114
|
+
const collectedRedirects = _.uniqBy(redirects, (redirect) => redirect.from);
|
|
108
115
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
);
|
|
113
|
-
if (redirectsOverridingExistingPath.length > 0) {
|
|
114
|
-
console.error(
|
|
115
|
-
chalk.red(
|
|
116
|
-
`@docusaurus/plugin-client-redirects: some redirects would override existing paths, and will be ignored:
|
|
117
|
-
- ${redirectsOverridingExistingPath.map((r) => JSON.stringify(r)).join('\n- ')}
|
|
118
|
-
`,
|
|
119
|
-
),
|
|
116
|
+
const {false: newRedirects = [], true: redirectsOverridingExistingPath = []} =
|
|
117
|
+
_.groupBy(collectedRedirects, (redirect) =>
|
|
118
|
+
pluginContext.relativeRoutesPaths.includes(redirect.from),
|
|
120
119
|
);
|
|
120
|
+
if (redirectsOverridingExistingPath.length > 0) {
|
|
121
|
+
logger.report(
|
|
122
|
+
pluginContext.siteConfig.onDuplicateRoutes,
|
|
123
|
+
)`name=${'@docusaurus/plugin-client-redirects'}: some redirects would override existing paths, and will be ignored:${redirectsOverridingExistingPath.map(
|
|
124
|
+
(r) => JSON.stringify(r),
|
|
125
|
+
)}`;
|
|
121
126
|
}
|
|
122
|
-
return
|
|
123
|
-
(redirect) => !pluginContext.relativeRoutesPaths.includes(redirect.from),
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// For each plugin config option, create the appropriate redirects
|
|
128
|
-
function doCollectRedirects(pluginContext: PluginContext): RedirectMetadata[] {
|
|
129
|
-
return [
|
|
130
|
-
...createFromExtensionsRedirects(
|
|
131
|
-
pluginContext.relativeRoutesPaths,
|
|
132
|
-
pluginContext.options.fromExtensions,
|
|
133
|
-
),
|
|
134
|
-
...createToExtensionsRedirects(
|
|
135
|
-
pluginContext.relativeRoutesPaths,
|
|
136
|
-
pluginContext.options.toExtensions,
|
|
137
|
-
),
|
|
138
|
-
...createRedirectsOptionRedirects(pluginContext.options.redirects),
|
|
139
|
-
...createCreateRedirectsOptionRedirects(
|
|
140
|
-
pluginContext.relativeRoutesPaths,
|
|
141
|
-
pluginContext.options.createRedirects,
|
|
142
|
-
),
|
|
143
|
-
];
|
|
127
|
+
return newRedirects;
|
|
144
128
|
}
|
|
145
129
|
|
|
146
130
|
function createRedirectsOptionRedirects(
|
|
147
131
|
redirectsOption: PluginOptions['redirects'],
|
|
148
|
-
):
|
|
149
|
-
// For
|
|
150
|
-
function optionToRedirects(option: RedirectOption):
|
|
132
|
+
): RedirectItem[] {
|
|
133
|
+
// For convenience, user can use a string or a string[]
|
|
134
|
+
function optionToRedirects(option: RedirectOption): RedirectItem[] {
|
|
151
135
|
if (typeof option.from === 'string') {
|
|
152
136
|
return [{from: option.from, to: option.to}];
|
|
153
137
|
}
|
|
154
|
-
return option.from.map((from) => ({
|
|
155
|
-
from,
|
|
156
|
-
to: option.to,
|
|
157
|
-
}));
|
|
138
|
+
return option.from.map((from) => ({from, to: option.to}));
|
|
158
139
|
}
|
|
159
140
|
|
|
160
|
-
return
|
|
141
|
+
return redirectsOption.flatMap(optionToRedirects);
|
|
161
142
|
}
|
|
162
143
|
|
|
163
144
|
// Create redirects from the "createRedirects" fn provided by the user
|
|
164
145
|
function createCreateRedirectsOptionRedirects(
|
|
165
146
|
paths: string[],
|
|
166
147
|
createRedirects: PluginOptions['createRedirects'],
|
|
167
|
-
):
|
|
168
|
-
function createPathRedirects(path: string):
|
|
169
|
-
const fromsMixed: string | string[] = createRedirects
|
|
170
|
-
? createRedirects(path) || []
|
|
171
|
-
: [];
|
|
148
|
+
): RedirectItem[] {
|
|
149
|
+
function createPathRedirects(path: string): RedirectItem[] {
|
|
150
|
+
const fromsMixed: string | string[] = createRedirects?.(path) ?? [];
|
|
172
151
|
|
|
173
152
|
const froms: string[] =
|
|
174
153
|
typeof fromsMixed === 'string' ? [fromsMixed] : fromsMixed;
|
|
175
154
|
|
|
176
|
-
return froms.map((from) => {
|
|
177
|
-
return {
|
|
178
|
-
from,
|
|
179
|
-
to: path,
|
|
180
|
-
};
|
|
181
|
-
});
|
|
155
|
+
return froms.map((from) => ({from, to: path}));
|
|
182
156
|
}
|
|
183
157
|
|
|
184
|
-
return
|
|
158
|
+
return paths.flatMap(createPathRedirects);
|
|
185
159
|
}
|
|
@@ -5,26 +5,24 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import _ from 'lodash';
|
|
8
9
|
import * as eta from 'eta';
|
|
9
10
|
import redirectPageTemplate from './templates/redirectPage.template.html';
|
|
10
|
-
import {memoize} from 'lodash';
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const getCompiledRedirectPageTemplate = memoize(() => {
|
|
17
|
-
return eta.compile(redirectPageTemplate.trim());
|
|
18
|
-
});
|
|
12
|
+
const getCompiledRedirectPageTemplate = _.memoize(() =>
|
|
13
|
+
eta.compile(redirectPageTemplate.trim()),
|
|
14
|
+
);
|
|
19
15
|
|
|
20
|
-
function renderRedirectPageTemplate(data:
|
|
16
|
+
function renderRedirectPageTemplate(data: {toUrl: string}) {
|
|
21
17
|
const compiled = getCompiledRedirectPageTemplate();
|
|
22
18
|
return compiled(data, eta.defaultConfig);
|
|
23
19
|
}
|
|
24
20
|
|
|
25
21
|
export default function createRedirectPageContent({
|
|
26
22
|
toUrl,
|
|
27
|
-
}:
|
|
23
|
+
}: {
|
|
24
|
+
toUrl: string;
|
|
25
|
+
}): string {
|
|
28
26
|
return renderRedirectPageTemplate({
|
|
29
27
|
toUrl: encodeURI(toUrl),
|
|
30
28
|
});
|
package/src/deps.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
// TODO incompatible declaration file
|
|
9
|
+
declare module 'eta' {
|
|
10
|
+
export const defaultConfig: object;
|
|
11
|
+
|
|
12
|
+
export function compile(
|
|
13
|
+
template: string,
|
|
14
|
+
options?: object,
|
|
15
|
+
): (data: object, config: object) => string;
|
|
16
|
+
}
|
|
@@ -5,12 +5,15 @@
|
|
|
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 {RedirectItem} from './types';
|
|
11
14
|
|
|
12
15
|
const ExtensionAdditionalMessage =
|
|
13
|
-
'If the redirect extension system is not good enough for your
|
|
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) {
|
|
@@ -37,35 +40,37 @@ const validateExtension = (ext: string) => {
|
|
|
37
40
|
|
|
38
41
|
const addLeadingDot = (extension: string) => `.${extension}`;
|
|
39
42
|
|
|
40
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Create new `/path` that redirects to existing an `/path.html`
|
|
45
|
+
*/
|
|
41
46
|
export function createToExtensionsRedirects(
|
|
42
47
|
paths: string[],
|
|
43
48
|
extensions: string[],
|
|
44
|
-
):
|
|
49
|
+
): RedirectItem[] {
|
|
45
50
|
extensions.forEach(validateExtension);
|
|
46
51
|
|
|
47
52
|
const dottedExtensions = extensions.map(addLeadingDot);
|
|
48
53
|
|
|
49
|
-
const createPathRedirects = (path: string):
|
|
54
|
+
const createPathRedirects = (path: string): RedirectItem[] => {
|
|
50
55
|
const extensionFound = dottedExtensions.find((ext) => path.endsWith(ext));
|
|
51
56
|
if (extensionFound) {
|
|
52
|
-
|
|
53
|
-
return [routePathWithoutExtension].map((from) => ({
|
|
54
|
-
from,
|
|
55
|
-
to: path,
|
|
56
|
-
}));
|
|
57
|
+
return [{from: removeSuffix(path, extensionFound), to: path}];
|
|
57
58
|
}
|
|
58
59
|
return [];
|
|
59
60
|
};
|
|
60
61
|
|
|
61
|
-
return
|
|
62
|
+
return paths.flatMap(createPathRedirects);
|
|
62
63
|
}
|
|
63
64
|
|
|
64
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Create new `/path.html/index.html` that redirects to existing an `/path`
|
|
67
|
+
* The filename pattern might look weird but it's on purpose (see
|
|
68
|
+
* https://github.com/facebook/docusaurus/issues/5055)
|
|
69
|
+
*/
|
|
65
70
|
export function createFromExtensionsRedirects(
|
|
66
71
|
paths: string[],
|
|
67
72
|
extensions: string[],
|
|
68
|
-
):
|
|
73
|
+
): RedirectItem[] {
|
|
69
74
|
extensions.forEach(validateExtension);
|
|
70
75
|
|
|
71
76
|
const dottedExtensions = extensions.map(addLeadingDot);
|
|
@@ -73,15 +78,19 @@ export function createFromExtensionsRedirects(
|
|
|
73
78
|
const alreadyEndsWithAnExtension = (str: string) =>
|
|
74
79
|
dottedExtensions.some((ext) => str.endsWith(ext));
|
|
75
80
|
|
|
76
|
-
const createPathRedirects = (path: string):
|
|
77
|
-
if (path === '' || path
|
|
81
|
+
const createPathRedirects = (path: string): RedirectItem[] => {
|
|
82
|
+
if (path === '' || path === '/' || alreadyEndsWithAnExtension(path)) {
|
|
78
83
|
return [];
|
|
79
84
|
}
|
|
80
85
|
return extensions.map((ext) => ({
|
|
81
|
-
|
|
86
|
+
// /path => /path.html
|
|
87
|
+
// /path/ => /path.html/
|
|
88
|
+
from: path.endsWith('/')
|
|
89
|
+
? addTrailingSlash(`${removeTrailingSlash(path)}.${ext}`)
|
|
90
|
+
: `${path}.${ext}`,
|
|
82
91
|
to: path,
|
|
83
92
|
}));
|
|
84
93
|
};
|
|
85
94
|
|
|
86
|
-
return
|
|
95
|
+
return paths.flatMap(createPathRedirects);
|
|
87
96
|
}
|
package/src/index.ts
CHANGED
|
@@ -5,41 +5,25 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import {UserPluginOptions, PluginContext, RedirectMetadata} from './types';
|
|
10
|
-
|
|
11
|
-
import normalizePluginOptions from './normalizePluginOptions';
|
|
8
|
+
import {removePrefix, addLeadingSlash} from '@docusaurus/utils';
|
|
12
9
|
import collectRedirects from './collectRedirects';
|
|
13
10
|
import writeRedirectFiles, {
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
toRedirectFiles,
|
|
12
|
+
type RedirectFile,
|
|
16
13
|
} from './writeRedirectFiles';
|
|
17
|
-
import {
|
|
18
|
-
import
|
|
14
|
+
import type {LoadContext, Plugin} from '@docusaurus/types';
|
|
15
|
+
import type {PluginContext, RedirectItem} from './types';
|
|
16
|
+
import type {PluginOptions, Options} from './options';
|
|
19
17
|
|
|
20
18
|
export default function pluginClientRedirectsPages(
|
|
21
19
|
context: LoadContext,
|
|
22
|
-
|
|
23
|
-
): Plugin<
|
|
20
|
+
options: PluginOptions,
|
|
21
|
+
): Plugin<void> {
|
|
24
22
|
const {trailingSlash} = context.siteConfig;
|
|
25
23
|
|
|
26
|
-
const options = normalizePluginOptions(opts);
|
|
27
|
-
|
|
28
|
-
// Special case, when using trailingSlash=false we output /xyz.html files instead of /xyz/index.html
|
|
29
|
-
// It makes no sense to use option fromExtensions=["html"]: the redirect files would be the same as the original files
|
|
30
|
-
if (options.fromExtensions.includes('html') && trailingSlash === false) {
|
|
31
|
-
console.warn(
|
|
32
|
-
chalk.yellow(`Using the Docusaurus redirect plugin with fromExtensions=['html'] and siteConfig.trailingSlash=false is prevented.
|
|
33
|
-
It would lead the redirect plugin to override all the page.html files created by Docusaurus.`),
|
|
34
|
-
);
|
|
35
|
-
options.fromExtensions = options.fromExtensions.filter(
|
|
36
|
-
(ext) => ext !== 'html',
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
24
|
return {
|
|
41
25
|
name: 'docusaurus-plugin-client-redirects',
|
|
42
|
-
async postBuild(props
|
|
26
|
+
async postBuild(props) {
|
|
43
27
|
const pluginContext: PluginContext = {
|
|
44
28
|
relativeRoutesPaths: props.routesPaths.map(
|
|
45
29
|
(path) => `${addLeadingSlash(removePrefix(path, props.baseUrl))}`,
|
|
@@ -47,14 +31,15 @@ It would lead the redirect plugin to override all the page.html files created by
|
|
|
47
31
|
baseUrl: props.baseUrl,
|
|
48
32
|
outDir: props.outDir,
|
|
49
33
|
options,
|
|
34
|
+
siteConfig: props.siteConfig,
|
|
50
35
|
};
|
|
51
36
|
|
|
52
|
-
const redirects:
|
|
37
|
+
const redirects: RedirectItem[] = collectRedirects(
|
|
53
38
|
pluginContext,
|
|
54
39
|
trailingSlash,
|
|
55
40
|
);
|
|
56
41
|
|
|
57
|
-
const redirectFiles:
|
|
42
|
+
const redirectFiles: RedirectFile[] = toRedirectFiles(
|
|
58
43
|
redirects,
|
|
59
44
|
pluginContext,
|
|
60
45
|
trailingSlash,
|
|
@@ -65,3 +50,6 @@ It would lead the redirect plugin to override all the page.html files created by
|
|
|
65
50
|
},
|
|
66
51
|
};
|
|
67
52
|
}
|
|
53
|
+
|
|
54
|
+
export {validateOptions} from './options';
|
|
55
|
+
export type {PluginOptions, Options};
|
package/src/options.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
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
|
+
import {Joi, PathnameSchema} from '@docusaurus/utils-validation';
|
|
9
|
+
import type {OptionValidationContext} from '@docusaurus/types';
|
|
10
|
+
|
|
11
|
+
export type RedirectOption = {
|
|
12
|
+
/** Pathname of an existing Docusaurus page */
|
|
13
|
+
to: string;
|
|
14
|
+
/** Pathname of the new page(s) we should create */
|
|
15
|
+
from: string | string[];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type PluginOptions = {
|
|
19
|
+
/** Plugin ID. */
|
|
20
|
+
id: string;
|
|
21
|
+
/** The extensions to be removed from the route after redirecting. */
|
|
22
|
+
fromExtensions: string[];
|
|
23
|
+
/** The extensions to be appended to the route after redirecting. */
|
|
24
|
+
toExtensions: string[];
|
|
25
|
+
/** The list of redirect rules, each one with multiple `from`s → one `to`. */
|
|
26
|
+
redirects: RedirectOption[];
|
|
27
|
+
/**
|
|
28
|
+
* A callback to create a redirect rule. Docusaurus query this callback
|
|
29
|
+
* against every path it has created, and use its return value to output more
|
|
30
|
+
* paths.
|
|
31
|
+
* @returns All the paths from which we should redirect to `path`
|
|
32
|
+
*/
|
|
33
|
+
createRedirects?: (
|
|
34
|
+
/** An existing Docusaurus route path */
|
|
35
|
+
path: string,
|
|
36
|
+
) => string[] | string | null | undefined;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type Options = Partial<PluginOptions>;
|
|
40
|
+
|
|
41
|
+
export const DEFAULT_OPTIONS: Partial<PluginOptions> = {
|
|
42
|
+
fromExtensions: [],
|
|
43
|
+
toExtensions: [],
|
|
44
|
+
redirects: [],
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const RedirectPluginOptionValidation = Joi.object<RedirectOption>({
|
|
48
|
+
to: PathnameSchema.required(),
|
|
49
|
+
from: Joi.alternatives().try(
|
|
50
|
+
PathnameSchema.required(),
|
|
51
|
+
Joi.array().items(PathnameSchema.required()),
|
|
52
|
+
),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const isString = Joi.string().required().not(null);
|
|
56
|
+
|
|
57
|
+
const UserOptionsSchema = Joi.object<PluginOptions>({
|
|
58
|
+
fromExtensions: Joi.array()
|
|
59
|
+
.items(isString)
|
|
60
|
+
.default(DEFAULT_OPTIONS.fromExtensions),
|
|
61
|
+
toExtensions: Joi.array()
|
|
62
|
+
.items(isString)
|
|
63
|
+
.default(DEFAULT_OPTIONS.toExtensions),
|
|
64
|
+
redirects: Joi.array()
|
|
65
|
+
.items(RedirectPluginOptionValidation)
|
|
66
|
+
.default(DEFAULT_OPTIONS.redirects),
|
|
67
|
+
createRedirects: Joi.function().maxArity(1),
|
|
68
|
+
}).default(DEFAULT_OPTIONS);
|
|
69
|
+
|
|
70
|
+
export function validateOptions({
|
|
71
|
+
validate,
|
|
72
|
+
options: userOptions,
|
|
73
|
+
}: OptionValidationContext<Options | undefined, PluginOptions>): PluginOptions {
|
|
74
|
+
return validate(UserOptionsSchema, userOptions);
|
|
75
|
+
}
|
|
@@ -5,15 +5,15 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {RedirectMetadata} from './types';
|
|
9
8
|
import {Joi, PathnameSchema} from '@docusaurus/utils-validation';
|
|
9
|
+
import type {RedirectItem} from './types';
|
|
10
10
|
|
|
11
|
-
const RedirectSchema = Joi.object<
|
|
11
|
+
const RedirectSchema = Joi.object<RedirectItem>({
|
|
12
12
|
from: PathnameSchema.required(),
|
|
13
13
|
to: PathnameSchema.required(),
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
-
export function validateRedirect(redirect:
|
|
16
|
+
export function validateRedirect(redirect: RedirectItem): void {
|
|
17
17
|
const {error} = RedirectSchema.validate(redirect, {
|
|
18
18
|
abortEarly: true,
|
|
19
19
|
convert: false,
|
package/src/types.ts
CHANGED
|
@@ -5,39 +5,25 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {Props} from '@docusaurus/types';
|
|
8
|
+
import type {Props} from '@docusaurus/types';
|
|
9
|
+
import type {PluginOptions} from './options';
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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>;
|
|
30
|
-
|
|
31
|
-
// The minimal infos the plugin needs to work
|
|
32
|
-
export type PluginContext = Pick<Props, 'outDir' | 'baseUrl'> & {
|
|
11
|
+
/**
|
|
12
|
+
* The minimal infos the plugin needs to work
|
|
13
|
+
*/
|
|
14
|
+
export type PluginContext = Pick<Props, 'outDir' | 'baseUrl' | 'siteConfig'> & {
|
|
33
15
|
options: PluginOptions;
|
|
34
16
|
relativeRoutesPaths: string[];
|
|
35
17
|
};
|
|
36
18
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
19
|
+
/**
|
|
20
|
+
* In-memory representation of redirects we want: easier to test
|
|
21
|
+
* /!\ easy to be confused: "from" is the new page we should create,
|
|
22
|
+
* that redirects to "to": the existing Docusaurus page
|
|
23
|
+
*/
|
|
24
|
+
export type RedirectItem = {
|
|
25
|
+
/** Pathname of the new page we should create */
|
|
26
|
+
from: string;
|
|
27
|
+
/** Pathname of an existing Docusaurus page */
|
|
28
|
+
to: string;
|
|
43
29
|
};
|