@docusaurus/plugin-client-redirects 2.0.0-beta.ff31de0ff → 2.0.1
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 +45 -47
- package/lib/createRedirectPageContent.d.ts +2 -4
- package/lib/createRedirectPageContent.js +2 -4
- package/lib/extensionRedirects.d.ts +11 -3
- package/lib/extensionRedirects.js +22 -17
- package/lib/index.d.ts +5 -3
- package/lib/index.js +11 -8
- 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 +41 -14
- package/package.json +16 -13
- package/src/collectRedirects.ts +76 -83
- package/src/createRedirectPageContent.ts +8 -10
- package/src/deps.d.ts +16 -0
- package/src/extensionRedirects.ts +32 -25
- package/src/index.ts +21 -14
- package/src/options.ts +75 -0
- package/src/redirectValidation.ts +3 -3
- package/src/types.ts +16 -30
- package/src/writeRedirectFiles.ts +53 -25
- package/lib/.tsbuildinfo +0 -1970
- 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 -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/src/normalizePluginOptions.ts +0 -55
- package/tsconfig.json +0 -10
package/src/collectRedirects.ts
CHANGED
|
@@ -5,40 +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
|
-
|
|
21
|
-
import
|
|
16
|
+
import type {PluginOptions, RedirectOption} from './options';
|
|
17
|
+
import type {PluginContext, RedirectItem} from './types';
|
|
22
18
|
|
|
23
19
|
export default function collectRedirects(
|
|
24
20
|
pluginContext: PluginContext,
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
trailingSlash: boolean | undefined,
|
|
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
|
+
|
|
27
55
|
validateCollectedRedirects(redirects, pluginContext);
|
|
28
56
|
return filterUnwantedRedirects(redirects, pluginContext);
|
|
29
57
|
}
|
|
30
58
|
|
|
31
59
|
function validateCollectedRedirects(
|
|
32
|
-
redirects:
|
|
60
|
+
redirects: RedirectItem[],
|
|
33
61
|
pluginContext: PluginContext,
|
|
34
62
|
) {
|
|
35
|
-
const redirectValidationErrors
|
|
63
|
+
const redirectValidationErrors = redirects
|
|
36
64
|
.map((redirect) => {
|
|
37
65
|
try {
|
|
38
66
|
validateRedirect(redirect);
|
|
39
67
|
return undefined;
|
|
40
|
-
} catch (
|
|
41
|
-
return
|
|
68
|
+
} catch (err) {
|
|
69
|
+
return (err as Error).message;
|
|
42
70
|
}
|
|
43
71
|
})
|
|
44
72
|
.filter(Boolean);
|
|
@@ -52,7 +80,7 @@ function validateCollectedRedirects(
|
|
|
52
80
|
|
|
53
81
|
const allowedToPaths = pluginContext.relativeRoutesPaths;
|
|
54
82
|
const toPaths = redirects.map((redirect) => redirect.to);
|
|
55
|
-
const illegalToPaths = difference(toPaths, allowedToPaths);
|
|
83
|
+
const illegalToPaths = _.difference(toPaths, allowedToPaths);
|
|
56
84
|
if (illegalToPaths.length > 0) {
|
|
57
85
|
throw new Error(
|
|
58
86
|
`You are trying to create client-side redirections to paths that do not exist:
|
|
@@ -66,101 +94,66 @@ Valid paths you can redirect to:
|
|
|
66
94
|
}
|
|
67
95
|
|
|
68
96
|
function filterUnwantedRedirects(
|
|
69
|
-
redirects:
|
|
97
|
+
redirects: RedirectItem[],
|
|
70
98
|
pluginContext: PluginContext,
|
|
71
|
-
):
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
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(
|
|
75
103
|
([from, groupedFromRedirects]) => {
|
|
76
104
|
if (groupedFromRedirects.length > 1) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
It is not possible to redirect the same pathname to multiple destinations
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
),
|
|
84
|
-
);
|
|
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
|
+
)}`;
|
|
85
111
|
}
|
|
86
112
|
},
|
|
87
113
|
);
|
|
88
|
-
const collectedRedirects = uniqBy(redirects, (redirect) => redirect.from);
|
|
114
|
+
const collectedRedirects = _.uniqBy(redirects, (redirect) => redirect.from);
|
|
89
115
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
);
|
|
94
|
-
if (redirectsOverridingExistingPath.length > 0) {
|
|
95
|
-
console.error(
|
|
96
|
-
chalk.red(
|
|
97
|
-
`@docusaurus/plugin-client-redirects: some redirects would override existing paths, and will be ignored:
|
|
98
|
-
- ${redirectsOverridingExistingPath.map((r) => JSON.stringify(r)).join('\n- ')}
|
|
99
|
-
`,
|
|
100
|
-
),
|
|
116
|
+
const {false: newRedirects = [], true: redirectsOverridingExistingPath = []} =
|
|
117
|
+
_.groupBy(collectedRedirects, (redirect) =>
|
|
118
|
+
pluginContext.relativeRoutesPaths.includes(redirect.from),
|
|
101
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
|
+
)}`;
|
|
102
126
|
}
|
|
103
|
-
return
|
|
104
|
-
(redirect) => !pluginContext.relativeRoutesPaths.includes(redirect.from),
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// For each plugin config option, create the appropriate redirects
|
|
109
|
-
function doCollectRedirects(pluginContext: PluginContext): RedirectMetadata[] {
|
|
110
|
-
return [
|
|
111
|
-
...createFromExtensionsRedirects(
|
|
112
|
-
pluginContext.relativeRoutesPaths,
|
|
113
|
-
pluginContext.options.fromExtensions,
|
|
114
|
-
),
|
|
115
|
-
...createToExtensionsRedirects(
|
|
116
|
-
pluginContext.relativeRoutesPaths,
|
|
117
|
-
pluginContext.options.toExtensions,
|
|
118
|
-
),
|
|
119
|
-
...createRedirectsOptionRedirects(pluginContext.options.redirects),
|
|
120
|
-
...createCreateRedirectsOptionRedirects(
|
|
121
|
-
pluginContext.relativeRoutesPaths,
|
|
122
|
-
pluginContext.options.createRedirects,
|
|
123
|
-
),
|
|
124
|
-
];
|
|
127
|
+
return newRedirects;
|
|
125
128
|
}
|
|
126
129
|
|
|
127
130
|
function createRedirectsOptionRedirects(
|
|
128
131
|
redirectsOption: PluginOptions['redirects'],
|
|
129
|
-
):
|
|
130
|
-
// For
|
|
131
|
-
function optionToRedirects(option: RedirectOption):
|
|
132
|
+
): RedirectItem[] {
|
|
133
|
+
// For convenience, user can use a string or a string[]
|
|
134
|
+
function optionToRedirects(option: RedirectOption): RedirectItem[] {
|
|
132
135
|
if (typeof option.from === 'string') {
|
|
133
136
|
return [{from: option.from, to: option.to}];
|
|
134
137
|
}
|
|
135
|
-
return option.from.map((from) => ({
|
|
136
|
-
from,
|
|
137
|
-
to: option.to,
|
|
138
|
-
}));
|
|
138
|
+
return option.from.map((from) => ({from, to: option.to}));
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
return
|
|
141
|
+
return redirectsOption.flatMap(optionToRedirects);
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
// Create redirects from the "createRedirects" fn provided by the user
|
|
145
145
|
function createCreateRedirectsOptionRedirects(
|
|
146
146
|
paths: string[],
|
|
147
147
|
createRedirects: PluginOptions['createRedirects'],
|
|
148
|
-
):
|
|
149
|
-
function createPathRedirects(path: string):
|
|
150
|
-
const fromsMixed: string | string[] = createRedirects
|
|
151
|
-
? createRedirects(path) || []
|
|
152
|
-
: [];
|
|
148
|
+
): RedirectItem[] {
|
|
149
|
+
function createPathRedirects(path: string): RedirectItem[] {
|
|
150
|
+
const fromsMixed: string | string[] = createRedirects?.(path) ?? [];
|
|
153
151
|
|
|
154
152
|
const froms: string[] =
|
|
155
153
|
typeof fromsMixed === 'string' ? [fromsMixed] : fromsMixed;
|
|
156
154
|
|
|
157
|
-
return froms.map((from) => {
|
|
158
|
-
return {
|
|
159
|
-
from,
|
|
160
|
-
to: path,
|
|
161
|
-
};
|
|
162
|
-
});
|
|
155
|
+
return froms.map((from) => ({from, to: path}));
|
|
163
156
|
}
|
|
164
157
|
|
|
165
|
-
return
|
|
158
|
+
return paths.flatMap(createPathRedirects);
|
|
166
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,69 +5,72 @@
|
|
|
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
|
-
|
|
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
|
};
|
|
39
40
|
|
|
40
41
|
const addLeadingDot = (extension: string) => `.${extension}`;
|
|
41
42
|
|
|
42
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Create new `/path` that redirects to existing an `/path.html`
|
|
45
|
+
*/
|
|
43
46
|
export function createToExtensionsRedirects(
|
|
44
47
|
paths: string[],
|
|
45
48
|
extensions: string[],
|
|
46
|
-
):
|
|
49
|
+
): RedirectItem[] {
|
|
47
50
|
extensions.forEach(validateExtension);
|
|
48
51
|
|
|
49
52
|
const dottedExtensions = extensions.map(addLeadingDot);
|
|
50
53
|
|
|
51
|
-
const createPathRedirects = (path: string):
|
|
54
|
+
const createPathRedirects = (path: string): RedirectItem[] => {
|
|
52
55
|
const extensionFound = dottedExtensions.find((ext) => path.endsWith(ext));
|
|
53
56
|
if (extensionFound) {
|
|
54
|
-
|
|
55
|
-
return [routePathWithoutExtension].map((from) => ({
|
|
56
|
-
from,
|
|
57
|
-
to: path,
|
|
58
|
-
}));
|
|
57
|
+
return [{from: removeSuffix(path, extensionFound), to: path}];
|
|
59
58
|
}
|
|
60
59
|
return [];
|
|
61
60
|
};
|
|
62
61
|
|
|
63
|
-
return
|
|
62
|
+
return paths.flatMap(createPathRedirects);
|
|
64
63
|
}
|
|
65
64
|
|
|
66
|
-
|
|
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
|
+
*/
|
|
67
70
|
export function createFromExtensionsRedirects(
|
|
68
71
|
paths: string[],
|
|
69
72
|
extensions: string[],
|
|
70
|
-
):
|
|
73
|
+
): RedirectItem[] {
|
|
71
74
|
extensions.forEach(validateExtension);
|
|
72
75
|
|
|
73
76
|
const dottedExtensions = extensions.map(addLeadingDot);
|
|
@@ -75,15 +78,19 @@ export function createFromExtensionsRedirects(
|
|
|
75
78
|
const alreadyEndsWithAnExtension = (str: string) =>
|
|
76
79
|
dottedExtensions.some((ext) => str.endsWith(ext));
|
|
77
80
|
|
|
78
|
-
const createPathRedirects = (path: string):
|
|
79
|
-
if (path === '' || path
|
|
81
|
+
const createPathRedirects = (path: string): RedirectItem[] => {
|
|
82
|
+
if (path === '' || path === '/' || alreadyEndsWithAnExtension(path)) {
|
|
80
83
|
return [];
|
|
81
84
|
}
|
|
82
85
|
return extensions.map((ext) => ({
|
|
83
|
-
|
|
86
|
+
// /path => /path.html
|
|
87
|
+
// /path/ => /path.html/
|
|
88
|
+
from: path.endsWith('/')
|
|
89
|
+
? addTrailingSlash(`${removeTrailingSlash(path)}.${ext}`)
|
|
90
|
+
: `${path}.${ext}`,
|
|
84
91
|
to: path,
|
|
85
92
|
}));
|
|
86
93
|
};
|
|
87
94
|
|
|
88
|
-
return
|
|
95
|
+
return paths.flatMap(createPathRedirects);
|
|
89
96
|
}
|
package/src/index.ts
CHANGED
|
@@ -5,26 +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 {
|
|
14
|
+
import type {LoadContext, Plugin} from '@docusaurus/types';
|
|
15
|
+
import type {PluginContext, RedirectItem} from './types';
|
|
16
|
+
import type {PluginOptions, Options} from './options';
|
|
18
17
|
|
|
19
18
|
export default function pluginClientRedirectsPages(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
): Plugin<
|
|
23
|
-
const
|
|
19
|
+
context: LoadContext,
|
|
20
|
+
options: PluginOptions,
|
|
21
|
+
): Plugin<void> {
|
|
22
|
+
const {trailingSlash} = context.siteConfig;
|
|
24
23
|
|
|
25
24
|
return {
|
|
26
25
|
name: 'docusaurus-plugin-client-redirects',
|
|
27
|
-
async postBuild(props
|
|
26
|
+
async postBuild(props) {
|
|
28
27
|
const pluginContext: PluginContext = {
|
|
29
28
|
relativeRoutesPaths: props.routesPaths.map(
|
|
30
29
|
(path) => `${addLeadingSlash(removePrefix(path, props.baseUrl))}`,
|
|
@@ -32,13 +31,18 @@ export default function pluginClientRedirectsPages(
|
|
|
32
31
|
baseUrl: props.baseUrl,
|
|
33
32
|
outDir: props.outDir,
|
|
34
33
|
options,
|
|
34
|
+
siteConfig: props.siteConfig,
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
-
const redirects:
|
|
37
|
+
const redirects: RedirectItem[] = collectRedirects(
|
|
38
|
+
pluginContext,
|
|
39
|
+
trailingSlash,
|
|
40
|
+
);
|
|
38
41
|
|
|
39
|
-
const redirectFiles:
|
|
42
|
+
const redirectFiles: RedirectFile[] = toRedirectFiles(
|
|
40
43
|
redirects,
|
|
41
44
|
pluginContext,
|
|
45
|
+
trailingSlash,
|
|
42
46
|
);
|
|
43
47
|
|
|
44
48
|
// Write files only at the end: make code more easy to test without IO
|
|
@@ -46,3 +50,6 @@ export default function pluginClientRedirectsPages(
|
|
|
46
50
|
},
|
|
47
51
|
};
|
|
48
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
|
};
|