@docusaurus/plugin-client-redirects 3.0.0-alpha.0 → 3.0.0-rc.0
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.js +17 -3
- package/lib/createRedirectPageContent.js +13 -0
- package/lib/options.js +1 -1
- package/lib/redirectValidation.js +1 -1
- package/lib/templates/redirectPage.template.html.d.ts +1 -1
- package/lib/templates/redirectPage.template.html.js +1 -1
- package/lib/writeRedirectFiles.js +4 -1
- package/package.json +12 -12
- package/src/collectRedirects.ts +19 -3
- package/src/createRedirectPageContent.ts +17 -1
- package/src/options.ts +1 -1
- package/src/redirectValidation.ts +1 -1
- package/src/templates/redirectPage.template.html.ts +1 -1
- package/src/writeRedirectFiles.ts +4 -1
package/lib/collectRedirects.js
CHANGED
|
@@ -57,8 +57,23 @@ function validateCollectedRedirects(redirects, pluginContext) {
|
|
|
57
57
|
- ${redirectValidationErrors.join('\n- ')}
|
|
58
58
|
`);
|
|
59
59
|
}
|
|
60
|
-
const allowedToPaths = pluginContext.relativeRoutesPaths;
|
|
61
|
-
const toPaths = redirects
|
|
60
|
+
const allowedToPaths = pluginContext.relativeRoutesPaths.map((p) => decodeURI(p));
|
|
61
|
+
const toPaths = redirects
|
|
62
|
+
.map((redirect) => redirect.to)
|
|
63
|
+
// We now allow "to" to contain any string
|
|
64
|
+
// We only do this "broken redirect" check from to that looks like pathnames
|
|
65
|
+
// note: we allow querystring/anchors
|
|
66
|
+
// See https://github.com/facebook/docusaurus/issues/6845
|
|
67
|
+
.map((to) => {
|
|
68
|
+
if (to.startsWith('/')) {
|
|
69
|
+
try {
|
|
70
|
+
return decodeURI(new URL(to, 'https://example.com').pathname);
|
|
71
|
+
}
|
|
72
|
+
catch (e) { }
|
|
73
|
+
}
|
|
74
|
+
return undefined;
|
|
75
|
+
})
|
|
76
|
+
.filter((to) => typeof to !== 'undefined');
|
|
62
77
|
const trailingSlashConfig = pluginContext.siteConfig.trailingSlash;
|
|
63
78
|
// Key is the path, value is whether a valid toPath with a different trailing
|
|
64
79
|
// slash exists; if the key doesn't exist it means it's valid
|
|
@@ -85,7 +100,6 @@ function validateCollectedRedirects(redirects, pluginContext) {
|
|
|
85
100
|
}
|
|
86
101
|
});
|
|
87
102
|
if (differByTrailSlash.size > 0) {
|
|
88
|
-
console.log(differByTrailSlash);
|
|
89
103
|
const errors = Array.from(differByTrailSlash.entries());
|
|
90
104
|
let message = 'You are trying to create client-side redirections to invalid paths.\n';
|
|
91
105
|
const [trailingSlashIssues, invalidPaths] = lodash_1.default.partition(errors, ([, differ]) => differ);
|
|
@@ -15,9 +15,22 @@ function renderRedirectPageTemplate(data) {
|
|
|
15
15
|
const compiled = getCompiledRedirectPageTemplate();
|
|
16
16
|
return compiled(data, eta.defaultConfig);
|
|
17
17
|
}
|
|
18
|
+
// if the target url does not include ?search#anchor,
|
|
19
|
+
// we forward search/anchor that the redirect page receives
|
|
20
|
+
function searchAnchorForwarding(toUrl) {
|
|
21
|
+
try {
|
|
22
|
+
const url = new URL(toUrl, 'https://example.com');
|
|
23
|
+
const containsSearchOrAnchor = url.search || url.hash;
|
|
24
|
+
return !containsSearchOrAnchor;
|
|
25
|
+
}
|
|
26
|
+
catch (e) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
18
30
|
function createRedirectPageContent({ toUrl, }) {
|
|
19
31
|
return renderRedirectPageTemplate({
|
|
20
32
|
toUrl: encodeURI(toUrl),
|
|
33
|
+
searchAnchorForwarding: searchAnchorForwarding(toUrl),
|
|
21
34
|
});
|
|
22
35
|
}
|
|
23
36
|
exports.default = createRedirectPageContent;
|
package/lib/options.js
CHANGED
|
@@ -14,8 +14,8 @@ exports.DEFAULT_OPTIONS = {
|
|
|
14
14
|
redirects: [],
|
|
15
15
|
};
|
|
16
16
|
const RedirectPluginOptionValidation = utils_validation_1.Joi.object({
|
|
17
|
-
to: utils_validation_1.PathnameSchema.required(),
|
|
18
17
|
from: utils_validation_1.Joi.alternatives().try(utils_validation_1.PathnameSchema.required(), utils_validation_1.Joi.array().items(utils_validation_1.PathnameSchema.required())),
|
|
18
|
+
to: utils_validation_1.Joi.string().required(),
|
|
19
19
|
});
|
|
20
20
|
const isString = utils_validation_1.Joi.string().required().not(null);
|
|
21
21
|
const UserOptionsSchema = utils_validation_1.Joi.object({
|
|
@@ -10,7 +10,7 @@ exports.validateRedirect = void 0;
|
|
|
10
10
|
const utils_validation_1 = require("@docusaurus/utils-validation");
|
|
11
11
|
const RedirectSchema = utils_validation_1.Joi.object({
|
|
12
12
|
from: utils_validation_1.PathnameSchema.required(),
|
|
13
|
-
to: utils_validation_1.
|
|
13
|
+
to: utils_validation_1.Joi.string().required(),
|
|
14
14
|
});
|
|
15
15
|
function validateRedirect(redirect) {
|
|
16
16
|
const { error } = RedirectSchema.validate(redirect, {
|
|
@@ -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
|
-
declare const _default: "\n<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"UTF-8\">\n <meta http-equiv=\"refresh\" content=\"0; url=<%= it.toUrl %>\">\n <link rel=\"canonical\" href=\"<%= it.toUrl %>\" />\n </head>\n <script>\n window.location.href = '<%= it.toUrl %>' + window.location.search + window.location.hash
|
|
7
|
+
declare const _default: "\n<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"UTF-8\">\n <meta http-equiv=\"refresh\" content=\"0; url=<%= it.toUrl %>\">\n <link rel=\"canonical\" href=\"<%= it.toUrl %>\" />\n </head>\n <script>\n window.location.href = '<%= it.toUrl %>'<%= it.searchAnchorForwarding ? ' + window.location.search + window.location.hash' : '' %>;\n </script>\n</html>\n";
|
|
8
8
|
export default _default;
|
|
@@ -15,7 +15,7 @@ exports.default = `
|
|
|
15
15
|
<link rel="canonical" href="<%= it.toUrl %>" />
|
|
16
16
|
</head>
|
|
17
17
|
<script>
|
|
18
|
-
window.location.href = '<%= it.toUrl %>' + window.location.search + window.location.hash
|
|
18
|
+
window.location.href = '<%= it.toUrl %>'<%= it.searchAnchorForwarding ? ' + window.location.search + window.location.hash' : '' %>;
|
|
19
19
|
</script>
|
|
20
20
|
</html>
|
|
21
21
|
`;
|
|
@@ -15,7 +15,10 @@ const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
|
|
|
15
15
|
const utils_1 = require("@docusaurus/utils");
|
|
16
16
|
const createRedirectPageContent_1 = tslib_1.__importDefault(require("./createRedirectPageContent"));
|
|
17
17
|
function createToUrl(baseUrl, to) {
|
|
18
|
-
|
|
18
|
+
if (to.startsWith('/')) {
|
|
19
|
+
return (0, utils_1.normalizeUrl)([baseUrl, to]);
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
19
22
|
}
|
|
20
23
|
exports.createToUrl = createToUrl;
|
|
21
24
|
// Create redirect file path
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docusaurus/plugin-client-redirects",
|
|
3
|
-
"version": "3.0.0-
|
|
3
|
+
"version": "3.0.0-rc.0",
|
|
4
4
|
"description": "Client redirects plugin for Docusaurus.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -18,25 +18,25 @@
|
|
|
18
18
|
},
|
|
19
19
|
"license": "MIT",
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@docusaurus/core": "3.0.0-
|
|
22
|
-
"@docusaurus/logger": "3.0.0-
|
|
23
|
-
"@docusaurus/utils": "3.0.0-
|
|
24
|
-
"@docusaurus/utils-common": "3.0.0-
|
|
25
|
-
"@docusaurus/utils-validation": "3.0.0-
|
|
26
|
-
"eta": "^2.0
|
|
27
|
-
"fs-extra": "^11.1.
|
|
21
|
+
"@docusaurus/core": "3.0.0-rc.0",
|
|
22
|
+
"@docusaurus/logger": "3.0.0-rc.0",
|
|
23
|
+
"@docusaurus/utils": "3.0.0-rc.0",
|
|
24
|
+
"@docusaurus/utils-common": "3.0.0-rc.0",
|
|
25
|
+
"@docusaurus/utils-validation": "3.0.0-rc.0",
|
|
26
|
+
"eta": "^2.2.0",
|
|
27
|
+
"fs-extra": "^11.1.1",
|
|
28
28
|
"lodash": "^4.17.21",
|
|
29
|
-
"tslib": "^2.
|
|
29
|
+
"tslib": "^2.6.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"@docusaurus/types": "3.0.0-
|
|
32
|
+
"@docusaurus/types": "3.0.0-rc.0"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"react": "^18.0.0",
|
|
36
36
|
"react-dom": "^18.0.0"
|
|
37
37
|
},
|
|
38
38
|
"engines": {
|
|
39
|
-
"node": ">=
|
|
39
|
+
"node": ">=18.0"
|
|
40
40
|
},
|
|
41
|
-
"gitHead": "
|
|
41
|
+
"gitHead": "e66dfe04d2972edce66f431908470c61340f8ffc"
|
|
42
42
|
}
|
package/src/collectRedirects.ts
CHANGED
|
@@ -79,8 +79,25 @@ function validateCollectedRedirects(
|
|
|
79
79
|
);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
const allowedToPaths = pluginContext.relativeRoutesPaths
|
|
83
|
-
|
|
82
|
+
const allowedToPaths = pluginContext.relativeRoutesPaths.map((p) =>
|
|
83
|
+
decodeURI(p),
|
|
84
|
+
);
|
|
85
|
+
const toPaths = redirects
|
|
86
|
+
.map((redirect) => redirect.to)
|
|
87
|
+
// We now allow "to" to contain any string
|
|
88
|
+
// We only do this "broken redirect" check from to that looks like pathnames
|
|
89
|
+
// note: we allow querystring/anchors
|
|
90
|
+
// See https://github.com/facebook/docusaurus/issues/6845
|
|
91
|
+
.map((to) => {
|
|
92
|
+
if (to.startsWith('/')) {
|
|
93
|
+
try {
|
|
94
|
+
return decodeURI(new URL(to, 'https://example.com').pathname);
|
|
95
|
+
} catch (e) {}
|
|
96
|
+
}
|
|
97
|
+
return undefined;
|
|
98
|
+
})
|
|
99
|
+
.filter((to): to is string => typeof to !== 'undefined');
|
|
100
|
+
|
|
84
101
|
const trailingSlashConfig = pluginContext.siteConfig.trailingSlash;
|
|
85
102
|
// Key is the path, value is whether a valid toPath with a different trailing
|
|
86
103
|
// slash exists; if the key doesn't exist it means it's valid
|
|
@@ -103,7 +120,6 @@ function validateCollectedRedirects(
|
|
|
103
120
|
}
|
|
104
121
|
});
|
|
105
122
|
if (differByTrailSlash.size > 0) {
|
|
106
|
-
console.log(differByTrailSlash);
|
|
107
123
|
const errors = Array.from(differByTrailSlash.entries());
|
|
108
124
|
|
|
109
125
|
let message =
|
|
@@ -13,11 +13,26 @@ const getCompiledRedirectPageTemplate = _.memoize(() =>
|
|
|
13
13
|
eta.compile(redirectPageTemplate.trim()),
|
|
14
14
|
);
|
|
15
15
|
|
|
16
|
-
function renderRedirectPageTemplate(data: {
|
|
16
|
+
function renderRedirectPageTemplate(data: {
|
|
17
|
+
toUrl: string;
|
|
18
|
+
searchAnchorForwarding: boolean;
|
|
19
|
+
}) {
|
|
17
20
|
const compiled = getCompiledRedirectPageTemplate();
|
|
18
21
|
return compiled(data, eta.defaultConfig);
|
|
19
22
|
}
|
|
20
23
|
|
|
24
|
+
// if the target url does not include ?search#anchor,
|
|
25
|
+
// we forward search/anchor that the redirect page receives
|
|
26
|
+
function searchAnchorForwarding(toUrl: string): boolean {
|
|
27
|
+
try {
|
|
28
|
+
const url = new URL(toUrl, 'https://example.com');
|
|
29
|
+
const containsSearchOrAnchor = url.search || url.hash;
|
|
30
|
+
return !containsSearchOrAnchor;
|
|
31
|
+
} catch (e) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
21
36
|
export default function createRedirectPageContent({
|
|
22
37
|
toUrl,
|
|
23
38
|
}: {
|
|
@@ -25,5 +40,6 @@ export default function createRedirectPageContent({
|
|
|
25
40
|
}): string {
|
|
26
41
|
return renderRedirectPageTemplate({
|
|
27
42
|
toUrl: encodeURI(toUrl),
|
|
43
|
+
searchAnchorForwarding: searchAnchorForwarding(toUrl),
|
|
28
44
|
});
|
|
29
45
|
}
|
package/src/options.ts
CHANGED
|
@@ -45,11 +45,11 @@ export const DEFAULT_OPTIONS: Partial<PluginOptions> = {
|
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
const RedirectPluginOptionValidation = Joi.object<RedirectOption>({
|
|
48
|
-
to: PathnameSchema.required(),
|
|
49
48
|
from: Joi.alternatives().try(
|
|
50
49
|
PathnameSchema.required(),
|
|
51
50
|
Joi.array().items(PathnameSchema.required()),
|
|
52
51
|
),
|
|
52
|
+
to: Joi.string().required(),
|
|
53
53
|
});
|
|
54
54
|
|
|
55
55
|
const isString = Joi.string().required().not(null);
|
|
@@ -10,7 +10,7 @@ import type {RedirectItem} from './types';
|
|
|
10
10
|
|
|
11
11
|
const RedirectSchema = Joi.object<RedirectItem>({
|
|
12
12
|
from: PathnameSchema.required(),
|
|
13
|
-
to:
|
|
13
|
+
to: Joi.string().required(),
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
export function validateRedirect(redirect: RedirectItem): void {
|
|
@@ -14,7 +14,7 @@ export default `
|
|
|
14
14
|
<link rel="canonical" href="<%= it.toUrl %>" />
|
|
15
15
|
</head>
|
|
16
16
|
<script>
|
|
17
|
-
window.location.href = '<%= it.toUrl %>' + window.location.search + window.location.hash
|
|
17
|
+
window.location.href = '<%= it.toUrl %>'<%= it.searchAnchorForwarding ? ' + window.location.search + window.location.hash' : '' %>;
|
|
18
18
|
</script>
|
|
19
19
|
</html>
|
|
20
20
|
`;
|
|
@@ -23,7 +23,10 @@ export type RedirectFile = {
|
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
export function createToUrl(baseUrl: string, to: string): string {
|
|
26
|
-
|
|
26
|
+
if (to.startsWith('/')) {
|
|
27
|
+
return normalizeUrl([baseUrl, to]);
|
|
28
|
+
}
|
|
29
|
+
return to;
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
// Create redirect file path
|