@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
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
* This source code is licensed under the MIT license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
|
-
import { PluginContext,
|
|
8
|
-
export default function collectRedirects(pluginContext: PluginContext):
|
|
7
|
+
import type { PluginContext, RedirectItem } from './types';
|
|
8
|
+
export default function collectRedirects(pluginContext: PluginContext, trailingSlash: boolean | undefined): RedirectItem[];
|
package/lib/collectRedirects.js
CHANGED
|
@@ -7,12 +7,34 @@
|
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
const tslib_1 = require("tslib");
|
|
10
|
-
const lodash_1 = require("lodash");
|
|
10
|
+
const lodash_1 = tslib_1.__importDefault(require("lodash"));
|
|
11
|
+
const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
|
|
12
|
+
const utils_common_1 = require("@docusaurus/utils-common");
|
|
11
13
|
const extensionRedirects_1 = require("./extensionRedirects");
|
|
12
14
|
const redirectValidation_1 = require("./redirectValidation");
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const redirects =
|
|
15
|
+
function collectRedirects(pluginContext, trailingSlash) {
|
|
16
|
+
// For each plugin config option, create the appropriate redirects
|
|
17
|
+
const redirects = [
|
|
18
|
+
...(0, extensionRedirects_1.createFromExtensionsRedirects)(pluginContext.relativeRoutesPaths, pluginContext.options.fromExtensions),
|
|
19
|
+
...(0, extensionRedirects_1.createToExtensionsRedirects)(pluginContext.relativeRoutesPaths, pluginContext.options.toExtensions),
|
|
20
|
+
...createRedirectsOptionRedirects(pluginContext.options.redirects),
|
|
21
|
+
...createCreateRedirectsOptionRedirects(pluginContext.relativeRoutesPaths, pluginContext.options.createRedirects),
|
|
22
|
+
].map((redirect) => ({
|
|
23
|
+
...redirect,
|
|
24
|
+
// Given a redirect with `to: "/abc"` and `trailingSlash` enabled:
|
|
25
|
+
//
|
|
26
|
+
// - We don't want to reject `to: "/abc"`, as that unambiguously points to
|
|
27
|
+
// `/abc/` now;
|
|
28
|
+
// - We want to redirect `to: /abc/` without the user having to change all
|
|
29
|
+
// her redirect plugin options
|
|
30
|
+
//
|
|
31
|
+
// It should be easy to toggle `trailingSlash` option without having to
|
|
32
|
+
// change other configs
|
|
33
|
+
to: (0, utils_common_1.applyTrailingSlash)(redirect.to, {
|
|
34
|
+
trailingSlash,
|
|
35
|
+
baseUrl: pluginContext.baseUrl,
|
|
36
|
+
}),
|
|
37
|
+
}));
|
|
16
38
|
validateCollectedRedirects(redirects, pluginContext);
|
|
17
39
|
return filterUnwantedRedirects(redirects, pluginContext);
|
|
18
40
|
}
|
|
@@ -21,11 +43,11 @@ function validateCollectedRedirects(redirects, pluginContext) {
|
|
|
21
43
|
const redirectValidationErrors = redirects
|
|
22
44
|
.map((redirect) => {
|
|
23
45
|
try {
|
|
24
|
-
redirectValidation_1.validateRedirect(redirect);
|
|
46
|
+
(0, redirectValidation_1.validateRedirect)(redirect);
|
|
25
47
|
return undefined;
|
|
26
48
|
}
|
|
27
|
-
catch (
|
|
28
|
-
return
|
|
49
|
+
catch (err) {
|
|
50
|
+
return err.message;
|
|
29
51
|
}
|
|
30
52
|
})
|
|
31
53
|
.filter(Boolean);
|
|
@@ -36,7 +58,7 @@ function validateCollectedRedirects(redirects, pluginContext) {
|
|
|
36
58
|
}
|
|
37
59
|
const allowedToPaths = pluginContext.relativeRoutesPaths;
|
|
38
60
|
const toPaths = redirects.map((redirect) => redirect.to);
|
|
39
|
-
const illegalToPaths = lodash_1.difference(toPaths, allowedToPaths);
|
|
61
|
+
const illegalToPaths = lodash_1.default.difference(toPaths, allowedToPaths);
|
|
40
62
|
if (illegalToPaths.length > 0) {
|
|
41
63
|
throw new Error(`You are trying to create client-side redirections to paths that do not exist:
|
|
42
64
|
- ${illegalToPaths.join('\n- ')}
|
|
@@ -47,61 +69,37 @@ Valid paths you can redirect to:
|
|
|
47
69
|
}
|
|
48
70
|
}
|
|
49
71
|
function filterUnwantedRedirects(redirects, pluginContext) {
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
Object.entries(lodash_1.groupBy(redirects, (redirect) => redirect.from)).forEach(([from, groupedFromRedirects]) => {
|
|
72
|
+
// We don't want to create the same redirect twice, since that would lead to
|
|
73
|
+
// writing the same html redirection file twice.
|
|
74
|
+
Object.entries(lodash_1.default.groupBy(redirects, (redirect) => redirect.from)).forEach(([from, groupedFromRedirects]) => {
|
|
53
75
|
if (groupedFromRedirects.length > 1) {
|
|
54
|
-
|
|
55
|
-
It is not possible to redirect the same pathname to multiple destinations
|
|
56
|
-
- ${groupedFromRedirects.map((r) => JSON.stringify(r)).join('\n- ')}
|
|
57
|
-
`));
|
|
76
|
+
logger_1.default.report(pluginContext.siteConfig.onDuplicateRoutes) `name=${'@docusaurus/plugin-client-redirects'}: multiple redirects are created with the same "from" pathname: path=${from}
|
|
77
|
+
It is not possible to redirect the same pathname to multiple destinations:${groupedFromRedirects.map((r) => JSON.stringify(r))}`;
|
|
58
78
|
}
|
|
59
79
|
});
|
|
60
|
-
const collectedRedirects = lodash_1.uniqBy(redirects, (redirect) => redirect.from);
|
|
61
|
-
|
|
62
|
-
const redirectsOverridingExistingPath = collectedRedirects.filter((redirect) => pluginContext.relativeRoutesPaths.includes(redirect.from));
|
|
80
|
+
const collectedRedirects = lodash_1.default.uniqBy(redirects, (redirect) => redirect.from);
|
|
81
|
+
const { false: newRedirects = [], true: redirectsOverridingExistingPath = [] } = lodash_1.default.groupBy(collectedRedirects, (redirect) => pluginContext.relativeRoutesPaths.includes(redirect.from));
|
|
63
82
|
if (redirectsOverridingExistingPath.length > 0) {
|
|
64
|
-
|
|
65
|
-
- ${redirectsOverridingExistingPath.map((r) => JSON.stringify(r)).join('\n- ')}
|
|
66
|
-
`));
|
|
83
|
+
logger_1.default.report(pluginContext.siteConfig.onDuplicateRoutes) `name=${'@docusaurus/plugin-client-redirects'}: some redirects would override existing paths, and will be ignored:${redirectsOverridingExistingPath.map((r) => JSON.stringify(r))}`;
|
|
67
84
|
}
|
|
68
|
-
return
|
|
69
|
-
}
|
|
70
|
-
// For each plugin config option, create the appropriate redirects
|
|
71
|
-
function doCollectRedirects(pluginContext) {
|
|
72
|
-
return [
|
|
73
|
-
...extensionRedirects_1.createFromExtensionsRedirects(pluginContext.relativeRoutesPaths, pluginContext.options.fromExtensions),
|
|
74
|
-
...extensionRedirects_1.createToExtensionsRedirects(pluginContext.relativeRoutesPaths, pluginContext.options.toExtensions),
|
|
75
|
-
...createRedirectsOptionRedirects(pluginContext.options.redirects),
|
|
76
|
-
...createCreateRedirectsOptionRedirects(pluginContext.relativeRoutesPaths, pluginContext.options.createRedirects),
|
|
77
|
-
];
|
|
85
|
+
return newRedirects;
|
|
78
86
|
}
|
|
79
87
|
function createRedirectsOptionRedirects(redirectsOption) {
|
|
80
|
-
// For
|
|
88
|
+
// For convenience, user can use a string or a string[]
|
|
81
89
|
function optionToRedirects(option) {
|
|
82
90
|
if (typeof option.from === 'string') {
|
|
83
91
|
return [{ from: option.from, to: option.to }];
|
|
84
92
|
}
|
|
85
|
-
return option.from.map((from) => ({
|
|
86
|
-
from,
|
|
87
|
-
to: option.to,
|
|
88
|
-
}));
|
|
93
|
+
return option.from.map((from) => ({ from, to: option.to }));
|
|
89
94
|
}
|
|
90
|
-
return
|
|
95
|
+
return redirectsOption.flatMap(optionToRedirects);
|
|
91
96
|
}
|
|
92
97
|
// Create redirects from the "createRedirects" fn provided by the user
|
|
93
98
|
function createCreateRedirectsOptionRedirects(paths, createRedirects) {
|
|
94
99
|
function createPathRedirects(path) {
|
|
95
|
-
const fromsMixed = createRedirects
|
|
96
|
-
? createRedirects(path) || []
|
|
97
|
-
: [];
|
|
100
|
+
const fromsMixed = createRedirects?.(path) ?? [];
|
|
98
101
|
const froms = typeof fromsMixed === 'string' ? [fromsMixed] : fromsMixed;
|
|
99
|
-
return froms.map((from) => {
|
|
100
|
-
return {
|
|
101
|
-
from,
|
|
102
|
-
to: path,
|
|
103
|
-
};
|
|
104
|
-
});
|
|
102
|
+
return froms.map((from) => ({ from, to: path }));
|
|
105
103
|
}
|
|
106
|
-
return
|
|
104
|
+
return paths.flatMap(createPathRedirects);
|
|
107
105
|
}
|
|
@@ -4,8 +4,6 @@
|
|
|
4
4
|
* This source code is licensed under the MIT license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
|
-
|
|
7
|
+
export default function createRedirectPageContent({ toUrl, }: {
|
|
8
8
|
toUrl: string;
|
|
9
|
-
};
|
|
10
|
-
export default function createRedirectPageContent({ toUrl, }: CreateRedirectPageOptions): string;
|
|
11
|
-
export {};
|
|
9
|
+
}): string;
|
|
@@ -7,12 +7,10 @@
|
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
const tslib_1 = require("tslib");
|
|
10
|
+
const lodash_1 = tslib_1.__importDefault(require("lodash"));
|
|
10
11
|
const eta = tslib_1.__importStar(require("eta"));
|
|
11
12
|
const redirectPage_template_html_1 = tslib_1.__importDefault(require("./templates/redirectPage.template.html"));
|
|
12
|
-
const
|
|
13
|
-
const getCompiledRedirectPageTemplate = lodash_1.memoize(() => {
|
|
14
|
-
return eta.compile(redirectPage_template_html_1.default.trim());
|
|
15
|
-
});
|
|
13
|
+
const getCompiledRedirectPageTemplate = lodash_1.default.memoize(() => eta.compile(redirectPage_template_html_1.default.trim()));
|
|
16
14
|
function renderRedirectPageTemplate(data) {
|
|
17
15
|
const compiled = getCompiledRedirectPageTemplate();
|
|
18
16
|
return compiled(data, eta.defaultConfig);
|
|
@@ -4,6 +4,14 @@
|
|
|
4
4
|
* This source code is licensed under the MIT license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
import type { RedirectItem } from './types';
|
|
8
|
+
/**
|
|
9
|
+
* Create new `/path` that redirects to existing an `/path.html`
|
|
10
|
+
*/
|
|
11
|
+
export declare function createToExtensionsRedirects(paths: string[], extensions: string[]): RedirectItem[];
|
|
12
|
+
/**
|
|
13
|
+
* Create new `/path.html/index.html` that redirects to existing an `/path`
|
|
14
|
+
* The filename pattern might look weird but it's on purpose (see
|
|
15
|
+
* https://github.com/facebook/docusaurus/issues/5055)
|
|
16
|
+
*/
|
|
17
|
+
export declare function createFromExtensionsRedirects(paths: string[], extensions: string[]): RedirectItem[];
|
|
@@ -7,56 +7,61 @@
|
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.createFromExtensionsRedirects = exports.createToExtensionsRedirects = void 0;
|
|
10
|
-
const lodash_1 = require("lodash");
|
|
11
10
|
const utils_1 = require("@docusaurus/utils");
|
|
12
|
-
const ExtensionAdditionalMessage =
|
|
11
|
+
const ExtensionAdditionalMessage = 'If the redirect extension system is not good enough for your use case, you can create redirects yourself with the "createRedirects" plugin option.';
|
|
13
12
|
const validateExtension = (ext) => {
|
|
14
13
|
if (!ext) {
|
|
15
|
-
throw new Error(`Extension
|
|
14
|
+
throw new Error(`Extension "${ext}" is not allowed.\n${ExtensionAdditionalMessage}`);
|
|
16
15
|
}
|
|
17
16
|
if (ext.includes('.')) {
|
|
18
|
-
throw new Error(`Extension
|
|
17
|
+
throw new Error(`Extension "${ext}" contains a "." (dot) which is not allowed.\n${ExtensionAdditionalMessage}`);
|
|
19
18
|
}
|
|
20
19
|
if (ext.includes('/')) {
|
|
21
|
-
throw new Error(`Extension
|
|
20
|
+
throw new Error(`Extension "${ext}" contains a "/" (slash) which is not allowed.\n${ExtensionAdditionalMessage}`);
|
|
22
21
|
}
|
|
23
22
|
if (encodeURIComponent(ext) !== ext) {
|
|
24
|
-
throw new Error(`Extension
|
|
23
|
+
throw new Error(`Extension "${ext}" contains invalid URI characters.\n${ExtensionAdditionalMessage}`);
|
|
25
24
|
}
|
|
26
25
|
};
|
|
27
26
|
const addLeadingDot = (extension) => `.${extension}`;
|
|
28
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Create new `/path` that redirects to existing an `/path.html`
|
|
29
|
+
*/
|
|
29
30
|
function createToExtensionsRedirects(paths, extensions) {
|
|
30
31
|
extensions.forEach(validateExtension);
|
|
31
32
|
const dottedExtensions = extensions.map(addLeadingDot);
|
|
32
33
|
const createPathRedirects = (path) => {
|
|
33
34
|
const extensionFound = dottedExtensions.find((ext) => path.endsWith(ext));
|
|
34
35
|
if (extensionFound) {
|
|
35
|
-
|
|
36
|
-
return [routePathWithoutExtension].map((from) => ({
|
|
37
|
-
from,
|
|
38
|
-
to: path,
|
|
39
|
-
}));
|
|
36
|
+
return [{ from: (0, utils_1.removeSuffix)(path, extensionFound), to: path }];
|
|
40
37
|
}
|
|
41
38
|
return [];
|
|
42
39
|
};
|
|
43
|
-
return
|
|
40
|
+
return paths.flatMap(createPathRedirects);
|
|
44
41
|
}
|
|
45
42
|
exports.createToExtensionsRedirects = createToExtensionsRedirects;
|
|
46
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Create new `/path.html/index.html` that redirects to existing an `/path`
|
|
45
|
+
* The filename pattern might look weird but it's on purpose (see
|
|
46
|
+
* https://github.com/facebook/docusaurus/issues/5055)
|
|
47
|
+
*/
|
|
47
48
|
function createFromExtensionsRedirects(paths, extensions) {
|
|
48
49
|
extensions.forEach(validateExtension);
|
|
49
50
|
const dottedExtensions = extensions.map(addLeadingDot);
|
|
50
51
|
const alreadyEndsWithAnExtension = (str) => dottedExtensions.some((ext) => str.endsWith(ext));
|
|
51
52
|
const createPathRedirects = (path) => {
|
|
52
|
-
if (path === '' || path
|
|
53
|
+
if (path === '' || path === '/' || alreadyEndsWithAnExtension(path)) {
|
|
53
54
|
return [];
|
|
54
55
|
}
|
|
55
56
|
return extensions.map((ext) => ({
|
|
56
|
-
|
|
57
|
+
// /path => /path.html
|
|
58
|
+
// /path/ => /path.html/
|
|
59
|
+
from: path.endsWith('/')
|
|
60
|
+
? (0, utils_1.addTrailingSlash)(`${(0, utils_1.removeTrailingSlash)(path)}.${ext}`)
|
|
61
|
+
: `${path}.${ext}`,
|
|
57
62
|
to: path,
|
|
58
63
|
}));
|
|
59
64
|
};
|
|
60
|
-
return
|
|
65
|
+
return paths.flatMap(createPathRedirects);
|
|
61
66
|
}
|
|
62
67
|
exports.createFromExtensionsRedirects = createFromExtensionsRedirects;
|
package/lib/index.d.ts
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* This source code is licensed under the MIT license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
|
-
import { LoadContext, Plugin } from '@docusaurus/types';
|
|
8
|
-
import {
|
|
9
|
-
export default function pluginClientRedirectsPages(
|
|
7
|
+
import type { LoadContext, Plugin } from '@docusaurus/types';
|
|
8
|
+
import type { PluginOptions, Options } from './options';
|
|
9
|
+
export default function pluginClientRedirectsPages(context: LoadContext, options: PluginOptions): Plugin<void>;
|
|
10
|
+
export { validateOptions } from './options';
|
|
11
|
+
export type { PluginOptions, Options };
|
package/lib/index.js
CHANGED
|
@@ -6,27 +6,30 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.validateOptions = void 0;
|
|
9
10
|
const tslib_1 = require("tslib");
|
|
10
|
-
const
|
|
11
|
+
const utils_1 = require("@docusaurus/utils");
|
|
11
12
|
const collectRedirects_1 = tslib_1.__importDefault(require("./collectRedirects"));
|
|
12
13
|
const writeRedirectFiles_1 = tslib_1.__importStar(require("./writeRedirectFiles"));
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const options = normalizePluginOptions_1.default(opts);
|
|
14
|
+
function pluginClientRedirectsPages(context, options) {
|
|
15
|
+
const { trailingSlash } = context.siteConfig;
|
|
16
16
|
return {
|
|
17
17
|
name: 'docusaurus-plugin-client-redirects',
|
|
18
18
|
async postBuild(props) {
|
|
19
19
|
const pluginContext = {
|
|
20
|
-
relativeRoutesPaths: props.routesPaths.map((path) => `${utils_1.addLeadingSlash(utils_1.removePrefix(path, props.baseUrl))}`),
|
|
20
|
+
relativeRoutesPaths: props.routesPaths.map((path) => `${(0, utils_1.addLeadingSlash)((0, utils_1.removePrefix)(path, props.baseUrl))}`),
|
|
21
21
|
baseUrl: props.baseUrl,
|
|
22
22
|
outDir: props.outDir,
|
|
23
23
|
options,
|
|
24
|
+
siteConfig: props.siteConfig,
|
|
24
25
|
};
|
|
25
|
-
const redirects = collectRedirects_1.default(pluginContext);
|
|
26
|
-
const redirectFiles = writeRedirectFiles_1.
|
|
26
|
+
const redirects = (0, collectRedirects_1.default)(pluginContext, trailingSlash);
|
|
27
|
+
const redirectFiles = (0, writeRedirectFiles_1.toRedirectFiles)(redirects, pluginContext, trailingSlash);
|
|
27
28
|
// Write files only at the end: make code more easy to test without IO
|
|
28
|
-
await writeRedirectFiles_1.default(redirectFiles);
|
|
29
|
+
await (0, writeRedirectFiles_1.default)(redirectFiles);
|
|
29
30
|
},
|
|
30
31
|
};
|
|
31
32
|
}
|
|
32
33
|
exports.default = pluginClientRedirectsPages;
|
|
34
|
+
var options_1 = require("./options");
|
|
35
|
+
Object.defineProperty(exports, "validateOptions", { enumerable: true, get: function () { return options_1.validateOptions; } });
|
package/lib/options.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
import type { OptionValidationContext } from '@docusaurus/types';
|
|
8
|
+
export declare type RedirectOption = {
|
|
9
|
+
/** Pathname of an existing Docusaurus page */
|
|
10
|
+
to: string;
|
|
11
|
+
/** Pathname of the new page(s) we should create */
|
|
12
|
+
from: string | string[];
|
|
13
|
+
};
|
|
14
|
+
export declare type PluginOptions = {
|
|
15
|
+
/** Plugin ID. */
|
|
16
|
+
id: string;
|
|
17
|
+
/** The extensions to be removed from the route after redirecting. */
|
|
18
|
+
fromExtensions: string[];
|
|
19
|
+
/** The extensions to be appended to the route after redirecting. */
|
|
20
|
+
toExtensions: string[];
|
|
21
|
+
/** The list of redirect rules, each one with multiple `from`s → one `to`. */
|
|
22
|
+
redirects: RedirectOption[];
|
|
23
|
+
/**
|
|
24
|
+
* A callback to create a redirect rule. Docusaurus query this callback
|
|
25
|
+
* against every path it has created, and use its return value to output more
|
|
26
|
+
* paths.
|
|
27
|
+
* @returns All the paths from which we should redirect to `path`
|
|
28
|
+
*/
|
|
29
|
+
createRedirects?: (
|
|
30
|
+
/** An existing Docusaurus route path */
|
|
31
|
+
path: string) => string[] | string | null | undefined;
|
|
32
|
+
};
|
|
33
|
+
export declare type Options = Partial<PluginOptions>;
|
|
34
|
+
export declare const DEFAULT_OPTIONS: Partial<PluginOptions>;
|
|
35
|
+
export declare function validateOptions({ validate, options: userOptions, }: OptionValidationContext<Options | undefined, PluginOptions>): PluginOptions;
|
package/lib/options.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.validateOptions = exports.DEFAULT_OPTIONS = void 0;
|
|
10
|
+
const utils_validation_1 = require("@docusaurus/utils-validation");
|
|
11
|
+
exports.DEFAULT_OPTIONS = {
|
|
12
|
+
fromExtensions: [],
|
|
13
|
+
toExtensions: [],
|
|
14
|
+
redirects: [],
|
|
15
|
+
};
|
|
16
|
+
const RedirectPluginOptionValidation = utils_validation_1.Joi.object({
|
|
17
|
+
to: utils_validation_1.PathnameSchema.required(),
|
|
18
|
+
from: utils_validation_1.Joi.alternatives().try(utils_validation_1.PathnameSchema.required(), utils_validation_1.Joi.array().items(utils_validation_1.PathnameSchema.required())),
|
|
19
|
+
});
|
|
20
|
+
const isString = utils_validation_1.Joi.string().required().not(null);
|
|
21
|
+
const UserOptionsSchema = utils_validation_1.Joi.object({
|
|
22
|
+
fromExtensions: utils_validation_1.Joi.array()
|
|
23
|
+
.items(isString)
|
|
24
|
+
.default(exports.DEFAULT_OPTIONS.fromExtensions),
|
|
25
|
+
toExtensions: utils_validation_1.Joi.array()
|
|
26
|
+
.items(isString)
|
|
27
|
+
.default(exports.DEFAULT_OPTIONS.toExtensions),
|
|
28
|
+
redirects: utils_validation_1.Joi.array()
|
|
29
|
+
.items(RedirectPluginOptionValidation)
|
|
30
|
+
.default(exports.DEFAULT_OPTIONS.redirects),
|
|
31
|
+
createRedirects: utils_validation_1.Joi.function().maxArity(1),
|
|
32
|
+
}).default(exports.DEFAULT_OPTIONS);
|
|
33
|
+
function validateOptions({ validate, options: userOptions, }) {
|
|
34
|
+
return validate(UserOptionsSchema, userOptions);
|
|
35
|
+
}
|
|
36
|
+
exports.validateOptions = validateOptions;
|
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
* This source code is licensed under the MIT license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
8
|
-
export declare function validateRedirect(redirect:
|
|
7
|
+
import type { RedirectItem } from './types';
|
|
8
|
+
export declare function validateRedirect(redirect: RedirectItem): void;
|
package/lib/types.d.ts
CHANGED
|
@@ -4,25 +4,23 @@
|
|
|
4
4
|
* This source code is licensed under the MIT license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
|
-
import { Props } from '@docusaurus/types';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
createRedirects?: CreateRedirectsFnOption;
|
|
14
|
-
};
|
|
15
|
-
export declare type CreateRedirectsFnOption = (path: string) => string[] | string | null | undefined;
|
|
16
|
-
export declare type RedirectOption = {
|
|
17
|
-
to: string;
|
|
18
|
-
from: string | string[];
|
|
19
|
-
};
|
|
20
|
-
export declare type UserPluginOptions = Partial<PluginOptions>;
|
|
21
|
-
export declare type PluginContext = Pick<Props, 'outDir' | 'baseUrl'> & {
|
|
7
|
+
import type { Props } from '@docusaurus/types';
|
|
8
|
+
import type { PluginOptions } from './options';
|
|
9
|
+
/**
|
|
10
|
+
* The minimal infos the plugin needs to work
|
|
11
|
+
*/
|
|
12
|
+
export declare type PluginContext = Pick<Props, 'outDir' | 'baseUrl' | 'siteConfig'> & {
|
|
22
13
|
options: PluginOptions;
|
|
23
14
|
relativeRoutesPaths: string[];
|
|
24
15
|
};
|
|
25
|
-
|
|
16
|
+
/**
|
|
17
|
+
* In-memory representation of redirects we want: easier to test
|
|
18
|
+
* /!\ easy to be confused: "from" is the new page we should create,
|
|
19
|
+
* that redirects to "to": the existing Docusaurus page
|
|
20
|
+
*/
|
|
21
|
+
export declare type RedirectItem = {
|
|
22
|
+
/** Pathname of the new page we should create */
|
|
26
23
|
from: string;
|
|
24
|
+
/** Pathname of an existing Docusaurus page */
|
|
27
25
|
to: string;
|
|
28
26
|
};
|
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
* This source code is licensed under the MIT license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
|
-
import { PluginContext,
|
|
7
|
+
import type { PluginContext, RedirectItem } from './types';
|
|
8
8
|
export declare type WriteFilesPluginContext = Pick<PluginContext, 'baseUrl' | 'outDir'>;
|
|
9
|
-
export declare type
|
|
9
|
+
export declare type RedirectFile = {
|
|
10
10
|
fileAbsolutePath: string;
|
|
11
11
|
fileContent: string;
|
|
12
12
|
};
|
|
13
13
|
export declare function createToUrl(baseUrl: string, to: string): string;
|
|
14
|
-
export declare function
|
|
15
|
-
export declare function writeRedirectFile(file:
|
|
16
|
-
export default function writeRedirectFiles(redirectFiles:
|
|
14
|
+
export declare function toRedirectFiles(redirects: RedirectItem[], pluginContext: WriteFilesPluginContext, trailingSlash: boolean | undefined): RedirectFile[];
|
|
15
|
+
export declare function writeRedirectFile(file: RedirectFile): Promise<void>;
|
|
16
|
+
export default function writeRedirectFiles(redirectFiles: RedirectFile[]): Promise<void>;
|
|
@@ -6,26 +6,53 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.writeRedirectFile = exports.
|
|
9
|
+
exports.writeRedirectFile = exports.toRedirectFiles = exports.createToUrl = void 0;
|
|
10
10
|
const tslib_1 = require("tslib");
|
|
11
11
|
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
|
|
12
12
|
const path_1 = tslib_1.__importDefault(require("path"));
|
|
13
|
-
const lodash_1 = require("lodash");
|
|
14
|
-
const
|
|
13
|
+
const lodash_1 = tslib_1.__importDefault(require("lodash"));
|
|
14
|
+
const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
|
|
15
15
|
const utils_1 = require("@docusaurus/utils");
|
|
16
|
+
const createRedirectPageContent_1 = tslib_1.__importDefault(require("./createRedirectPageContent"));
|
|
16
17
|
function createToUrl(baseUrl, to) {
|
|
17
|
-
return utils_1.normalizeUrl([baseUrl, to]);
|
|
18
|
+
return (0, utils_1.normalizeUrl)([baseUrl, to]);
|
|
18
19
|
}
|
|
19
20
|
exports.createToUrl = createToUrl;
|
|
20
|
-
|
|
21
|
+
// Create redirect file path
|
|
22
|
+
// Make sure this path has lower precedence over the original file path when
|
|
23
|
+
// served by host providers!
|
|
24
|
+
// Otherwise it can produce infinite redirect loops!
|
|
25
|
+
//
|
|
26
|
+
// See https://github.com/facebook/docusaurus/issues/5055
|
|
27
|
+
// See https://github.com/facebook/docusaurus/pull/5085
|
|
28
|
+
// See https://github.com/facebook/docusaurus/pull/5102
|
|
29
|
+
function getRedirectFilePath(fromPath, trailingSlash) {
|
|
30
|
+
const fileName = path_1.default.basename(fromPath);
|
|
31
|
+
const filePath = path_1.default.dirname(fromPath);
|
|
32
|
+
// Edge case for https://github.com/facebook/docusaurus/pull/5102
|
|
33
|
+
// If the redirect source path is /xyz, with file /xyz.html
|
|
34
|
+
// We can't write the redirect file at /xyz.html/index.html because for Unix
|
|
35
|
+
// FS, a file/folder can't have the same name "xyz.html"
|
|
36
|
+
// The only possible solution for a redirect file is thus /xyz.html.html (I
|
|
37
|
+
// know, looks suspicious)
|
|
38
|
+
if (trailingSlash === false && fileName.endsWith('.html')) {
|
|
39
|
+
return path_1.default.join(filePath, `${fileName}.html`);
|
|
40
|
+
}
|
|
41
|
+
// If the target path is /xyz, with file /xyz/index.html, we don't want the
|
|
42
|
+
// redirect file to be /xyz.html, otherwise it would be picked in priority and
|
|
43
|
+
// the redirect file would redirect to itself. We prefer the redirect file to
|
|
44
|
+
// be /xyz.html/index.html, served with lower priority for most static hosting
|
|
45
|
+
// tools
|
|
46
|
+
return path_1.default.join(filePath, `${fileName}/index.html`);
|
|
47
|
+
}
|
|
48
|
+
function toRedirectFiles(redirects, pluginContext, trailingSlash) {
|
|
21
49
|
// Perf: avoid rendering the template twice with the exact same "props"
|
|
22
50
|
// We might create multiple redirect pages for the same destination url
|
|
23
51
|
// note: the first fn arg is the cache key!
|
|
24
|
-
const createPageContentMemoized = lodash_1.memoize((toUrl) => {
|
|
25
|
-
return createRedirectPageContent_1.default({ toUrl });
|
|
26
|
-
});
|
|
52
|
+
const createPageContentMemoized = lodash_1.default.memoize((toUrl) => (0, createRedirectPageContent_1.default)({ toUrl }));
|
|
27
53
|
const createFileMetadata = (redirect) => {
|
|
28
|
-
const
|
|
54
|
+
const fileRelativePath = getRedirectFilePath(redirect.from, trailingSlash);
|
|
55
|
+
const fileAbsolutePath = path_1.default.join(pluginContext.outDir, fileRelativePath);
|
|
29
56
|
const toUrl = createToUrl(pluginContext.baseUrl, redirect.to);
|
|
30
57
|
const fileContent = createPageContentMemoized(toUrl);
|
|
31
58
|
return {
|
|
@@ -36,21 +63,21 @@ function toRedirectFilesMetadata(redirects, pluginContext) {
|
|
|
36
63
|
};
|
|
37
64
|
return redirects.map(createFileMetadata);
|
|
38
65
|
}
|
|
39
|
-
exports.
|
|
66
|
+
exports.toRedirectFiles = toRedirectFiles;
|
|
40
67
|
async function writeRedirectFile(file) {
|
|
41
68
|
try {
|
|
42
69
|
// User-friendly security to prevent file overrides
|
|
43
70
|
if (await fs_extra_1.default.pathExists(file.fileAbsolutePath)) {
|
|
44
|
-
throw new Error('The redirect plugin is not supposed to override existing files');
|
|
71
|
+
throw new Error('The redirect plugin is not supposed to override existing files.');
|
|
45
72
|
}
|
|
46
|
-
await fs_extra_1.default.
|
|
47
|
-
await fs_extra_1.default.writeFile(file.fileAbsolutePath, file.fileContent,
|
|
73
|
+
await fs_extra_1.default.outputFile(file.fileAbsolutePath, file.fileContent,
|
|
48
74
|
// Hard security to prevent file overrides
|
|
49
75
|
// See https://stackoverflow.com/a/34187712/82609
|
|
50
76
|
{ flag: 'wx' });
|
|
51
77
|
}
|
|
52
78
|
catch (err) {
|
|
53
|
-
|
|
79
|
+
logger_1.default.error `Redirect file creation error for path=${file.fileAbsolutePath}.`;
|
|
80
|
+
throw err;
|
|
54
81
|
}
|
|
55
82
|
}
|
|
56
83
|
exports.writeRedirectFile = writeRedirectFile;
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docusaurus/plugin-client-redirects",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Client redirects plugin for Docusaurus.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
|
+
"types": "lib/index.d.ts",
|
|
6
7
|
"scripts": {
|
|
7
8
|
"build": "tsc",
|
|
8
9
|
"watch": "tsc --watch"
|
|
@@ -17,23 +18,25 @@
|
|
|
17
18
|
},
|
|
18
19
|
"license": "MIT",
|
|
19
20
|
"dependencies": {
|
|
20
|
-
"@docusaurus/core": "2.0.
|
|
21
|
-
"@docusaurus/
|
|
22
|
-
"@docusaurus/utils": "2.0.
|
|
23
|
-
"@docusaurus/utils-
|
|
24
|
-
"
|
|
25
|
-
"eta": "^1.
|
|
26
|
-
"fs-extra": "^10.
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
|
|
21
|
+
"@docusaurus/core": "2.0.1",
|
|
22
|
+
"@docusaurus/logger": "2.0.1",
|
|
23
|
+
"@docusaurus/utils": "2.0.1",
|
|
24
|
+
"@docusaurus/utils-common": "2.0.1",
|
|
25
|
+
"@docusaurus/utils-validation": "2.0.1",
|
|
26
|
+
"eta": "^1.12.3",
|
|
27
|
+
"fs-extra": "^10.1.0",
|
|
28
|
+
"lodash": "^4.17.21",
|
|
29
|
+
"tslib": "^2.4.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@docusaurus/types": "2.0.1"
|
|
30
33
|
},
|
|
31
34
|
"peerDependencies": {
|
|
32
35
|
"react": "^16.8.4 || ^17.0.0",
|
|
33
36
|
"react-dom": "^16.8.4 || ^17.0.0"
|
|
34
37
|
},
|
|
35
38
|
"engines": {
|
|
36
|
-
"node": ">=
|
|
39
|
+
"node": ">=16.14"
|
|
37
40
|
},
|
|
38
|
-
"gitHead": "
|
|
41
|
+
"gitHead": "1ddee1c29cabf9bb52e4d78af6ebfaaabb1bc1f9"
|
|
39
42
|
}
|