@docusaurus/plugin-client-redirects 3.0.0-alpha.0 → 3.0.0-beta.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.
@@ -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.map((redirect) => redirect.to);
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.PathnameSchema.required(),
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;\n </script>\n</html>\n";
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
- return (0, utils_1.normalizeUrl)([baseUrl, to]);
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-alpha.0",
3
+ "version": "3.0.0-beta.0",
4
4
  "description": "Client redirects plugin for Docusaurus.",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -18,18 +18,18 @@
18
18
  },
19
19
  "license": "MIT",
20
20
  "dependencies": {
21
- "@docusaurus/core": "3.0.0-alpha.0",
22
- "@docusaurus/logger": "3.0.0-alpha.0",
23
- "@docusaurus/utils": "3.0.0-alpha.0",
24
- "@docusaurus/utils-common": "3.0.0-alpha.0",
25
- "@docusaurus/utils-validation": "3.0.0-alpha.0",
26
- "eta": "^2.0.1",
27
- "fs-extra": "^11.1.0",
21
+ "@docusaurus/core": "3.0.0-beta.0",
22
+ "@docusaurus/logger": "3.0.0-beta.0",
23
+ "@docusaurus/utils": "3.0.0-beta.0",
24
+ "@docusaurus/utils-common": "3.0.0-beta.0",
25
+ "@docusaurus/utils-validation": "3.0.0-beta.0",
26
+ "eta": "^2.2.0",
27
+ "fs-extra": "^11.1.1",
28
28
  "lodash": "^4.17.21",
29
- "tslib": "^2.5.0"
29
+ "tslib": "^2.6.0"
30
30
  },
31
31
  "devDependencies": {
32
- "@docusaurus/types": "3.0.0-alpha.0"
32
+ "@docusaurus/types": "3.0.0-beta.0"
33
33
  },
34
34
  "peerDependencies": {
35
35
  "react": "^18.0.0",
@@ -38,5 +38,5 @@
38
38
  "engines": {
39
39
  "node": ">=16.14"
40
40
  },
41
- "gitHead": "7327f7ff880ed97ad7855744e59c9c55d467a950"
41
+ "gitHead": "27a1e90d9fff88af90ecad35bea16d4d7230482a"
42
42
  }
@@ -79,8 +79,25 @@ function validateCollectedRedirects(
79
79
  );
80
80
  }
81
81
 
82
- const allowedToPaths = pluginContext.relativeRoutesPaths;
83
- const toPaths = redirects.map((redirect) => redirect.to);
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: {toUrl: string}) {
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: PathnameSchema.required(),
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
- return normalizeUrl([baseUrl, to]);
26
+ if (to.startsWith('/')) {
27
+ return normalizeUrl([baseUrl, to]);
28
+ }
29
+ return to;
27
30
  }
28
31
 
29
32
  // Create redirect file path