@docusaurus/plugin-client-redirects 0.0.0-5107 → 0.0.0-5115

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.
@@ -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 type { PluginContext, RedirectMetadata } from './types';
8
- export default function collectRedirects(pluginContext: PluginContext, trailingSlash: boolean | undefined): RedirectMetadata[];
7
+ import type { PluginContext, RedirectItem } from './types';
8
+ export default function collectRedirects(pluginContext: PluginContext, trailingSlash: boolean | undefined): RedirectItem[];
@@ -13,28 +13,32 @@ const utils_common_1 = require("@docusaurus/utils-common");
13
13
  const extensionRedirects_1 = require("./extensionRedirects");
14
14
  const redirectValidation_1 = require("./redirectValidation");
15
15
  function collectRedirects(pluginContext, trailingSlash) {
16
- let redirects = doCollectRedirects(pluginContext);
17
- redirects = applyRedirectsTrailingSlash(redirects, {
18
- trailingSlash,
19
- baseUrl: pluginContext.baseUrl,
20
- });
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
+ }));
21
38
  validateCollectedRedirects(redirects, pluginContext);
22
39
  return filterUnwantedRedirects(redirects, pluginContext);
23
40
  }
24
41
  exports.default = collectRedirects;
25
- // If users wants to redirect to=/abc and they enable trailingSlash=true then
26
- // => we don't want to reject the to=/abc (as only /abc/ is an existing/valid
27
- // path now)
28
- // => we want to redirect to=/abc/ without the user having to change all its
29
- // redirect plugin options
30
- // It should be easy to toggle siteConfig.trailingSlash option without having to
31
- // change other configs
32
- function applyRedirectsTrailingSlash(redirects, params) {
33
- return redirects.map((redirect) => ({
34
- ...redirect,
35
- to: (0, utils_common_1.applyTrailingSlash)(redirect.to, params),
36
- }));
37
- }
38
42
  function validateCollectedRedirects(redirects, pluginContext) {
39
43
  const redirectValidationErrors = redirects
40
44
  .map((redirect) => {
@@ -81,25 +85,13 @@ It is not possible to redirect the same pathname to multiple destinations: ${gro
81
85
  }
82
86
  return collectedRedirects.filter((redirect) => !pluginContext.relativeRoutesPaths.includes(redirect.from));
83
87
  }
84
- // For each plugin config option, create the appropriate redirects
85
- function doCollectRedirects(pluginContext) {
86
- return [
87
- ...(0, extensionRedirects_1.createFromExtensionsRedirects)(pluginContext.relativeRoutesPaths, pluginContext.options.fromExtensions),
88
- ...(0, extensionRedirects_1.createToExtensionsRedirects)(pluginContext.relativeRoutesPaths, pluginContext.options.toExtensions),
89
- ...createRedirectsOptionRedirects(pluginContext.options.redirects),
90
- ...createCreateRedirectsOptionRedirects(pluginContext.relativeRoutesPaths, pluginContext.options.createRedirects),
91
- ];
92
- }
93
88
  function createRedirectsOptionRedirects(redirectsOption) {
94
89
  // For convenience, user can use a string or a string[]
95
90
  function optionToRedirects(option) {
96
91
  if (typeof option.from === 'string') {
97
92
  return [{ from: option.from, to: option.to }];
98
93
  }
99
- return option.from.map((from) => ({
100
- from,
101
- to: option.to,
102
- }));
94
+ return option.from.map((from) => ({ from, to: option.to }));
103
95
  }
104
96
  return redirectsOption.flatMap(optionToRedirects);
105
97
  }
@@ -108,10 +100,7 @@ function createCreateRedirectsOptionRedirects(paths, createRedirects) {
108
100
  function createPathRedirects(path) {
109
101
  const fromsMixed = createRedirects?.(path) ?? [];
110
102
  const froms = typeof fromsMixed === 'string' ? [fromsMixed] : fromsMixed;
111
- return froms.map((from) => ({
112
- from,
113
- to: path,
114
- }));
103
+ return froms.map((from) => ({ from, to: path }));
115
104
  }
116
105
  return paths.flatMap(createPathRedirects);
117
106
  }
@@ -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 type { RedirectMetadata } from './types';
8
- export declare function createToExtensionsRedirects(paths: string[], extensions: string[]): RedirectMetadata[];
9
- export declare function createFromExtensionsRedirects(paths: string[], extensions: string[]): RedirectMetadata[];
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[];
@@ -24,26 +24,27 @@ const validateExtension = (ext) => {
24
24
  }
25
25
  };
26
26
  const addLeadingDot = (extension) => `.${extension}`;
27
- // Create new /path that redirects to existing an /path.html
27
+ /**
28
+ * Create new `/path` that redirects to existing an `/path.html`
29
+ */
28
30
  function createToExtensionsRedirects(paths, extensions) {
29
31
  extensions.forEach(validateExtension);
30
32
  const dottedExtensions = extensions.map(addLeadingDot);
31
33
  const createPathRedirects = (path) => {
32
34
  const extensionFound = dottedExtensions.find((ext) => path.endsWith(ext));
33
35
  if (extensionFound) {
34
- const routePathWithoutExtension = (0, utils_1.removeSuffix)(path, extensionFound);
35
- return [routePathWithoutExtension].map((from) => ({
36
- from,
37
- to: path,
38
- }));
36
+ return [{ from: (0, utils_1.removeSuffix)(path, extensionFound), to: path }];
39
37
  }
40
38
  return [];
41
39
  };
42
40
  return paths.flatMap(createPathRedirects);
43
41
  }
44
42
  exports.createToExtensionsRedirects = createToExtensionsRedirects;
45
- // Create new /path.html/index.html that redirects to existing an /path
46
- // The filename pattern might look weird but it's on purpose (see https://github.com/facebook/docusaurus/issues/5055)
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);
package/lib/index.js CHANGED
@@ -23,7 +23,7 @@ function pluginClientRedirectsPages(context, options) {
23
23
  options,
24
24
  };
25
25
  const redirects = (0, collectRedirects_1.default)(pluginContext, trailingSlash);
26
- const redirectFiles = (0, writeRedirectFiles_1.toRedirectFilesMetadata)(redirects, pluginContext, trailingSlash);
26
+ const redirectFiles = (0, writeRedirectFiles_1.toRedirectFiles)(redirects, pluginContext, trailingSlash);
27
27
  // Write files only at the end: make code more easy to test without IO
28
28
  await (0, writeRedirectFiles_1.default)(redirectFiles);
29
29
  },
package/lib/options.d.ts CHANGED
@@ -6,7 +6,9 @@
6
6
  */
7
7
  import type { OptionValidationContext } from '@docusaurus/types';
8
8
  export declare type RedirectOption = {
9
+ /** Pathname of an existing Docusaurus page */
9
10
  to: string;
11
+ /** Pathname of the new page(s) we should create */
10
12
  from: string | string[];
11
13
  };
12
14
  export declare type PluginOptions = {
@@ -19,7 +21,9 @@ export declare type PluginOptions = {
19
21
  /** The list of redirect rules, each one with multiple `from`s → one `to`. */
20
22
  redirects: RedirectOption[];
21
23
  /**
22
- * A callback to create a redirect rule.
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.
23
27
  * @returns All the paths from which we should redirect to `path`
24
28
  */
25
29
  createRedirects?: (
@@ -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 type { RedirectMetadata } from './types';
8
- export declare function validateRedirect(redirect: RedirectMetadata): void;
7
+ import type { RedirectItem } from './types';
8
+ export declare function validateRedirect(redirect: RedirectItem): void;
package/lib/types.d.ts CHANGED
@@ -18,7 +18,7 @@ export declare type PluginContext = Pick<Props, 'outDir' | 'baseUrl'> & {
18
18
  * /!\ easy to be confused: "from" is the new page we should create,
19
19
  * that redirects to "to": the existing Docusaurus page
20
20
  */
21
- export declare type RedirectMetadata = {
21
+ export declare type RedirectItem = {
22
22
  /** Pathname of the new page we should create */
23
23
  from: string;
24
24
  /** Pathname of an existing Docusaurus page */
@@ -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 type { PluginContext, RedirectMetadata } from './types';
7
+ import type { PluginContext, RedirectItem } from './types';
8
8
  export declare type WriteFilesPluginContext = Pick<PluginContext, 'baseUrl' | 'outDir'>;
9
- export declare type RedirectFileMetadata = {
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 toRedirectFilesMetadata(redirects: RedirectMetadata[], pluginContext: WriteFilesPluginContext, trailingSlash: boolean | undefined): RedirectFileMetadata[];
15
- export declare function writeRedirectFile(file: RedirectFileMetadata): Promise<void>;
16
- export default function writeRedirectFiles(redirectFiles: RedirectFileMetadata[]): Promise<void>;
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,7 +6,7 @@
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.toRedirectFilesMetadata = exports.createToUrl = void 0;
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"));
@@ -45,7 +45,7 @@ function getRedirectFilePath(fromPath, trailingSlash) {
45
45
  // tools
46
46
  return path_1.default.join(filePath, `${fileName}/index.html`);
47
47
  }
48
- function toRedirectFilesMetadata(redirects, pluginContext, trailingSlash) {
48
+ function toRedirectFiles(redirects, pluginContext, trailingSlash) {
49
49
  // Perf: avoid rendering the template twice with the exact same "props"
50
50
  // We might create multiple redirect pages for the same destination url
51
51
  // note: the first fn arg is the cache key!
@@ -63,7 +63,7 @@ function toRedirectFilesMetadata(redirects, pluginContext, trailingSlash) {
63
63
  };
64
64
  return redirects.map(createFileMetadata);
65
65
  }
66
- exports.toRedirectFilesMetadata = toRedirectFilesMetadata;
66
+ exports.toRedirectFiles = toRedirectFiles;
67
67
  async function writeRedirectFile(file) {
68
68
  try {
69
69
  // User-friendly security to prevent file overrides
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docusaurus/plugin-client-redirects",
3
- "version": "0.0.0-5107",
3
+ "version": "0.0.0-5115",
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": "0.0.0-5107",
22
- "@docusaurus/logger": "0.0.0-5107",
23
- "@docusaurus/utils": "0.0.0-5107",
24
- "@docusaurus/utils-common": "0.0.0-5107",
25
- "@docusaurus/utils-validation": "0.0.0-5107",
21
+ "@docusaurus/core": "0.0.0-5115",
22
+ "@docusaurus/logger": "0.0.0-5115",
23
+ "@docusaurus/utils": "0.0.0-5115",
24
+ "@docusaurus/utils-common": "0.0.0-5115",
25
+ "@docusaurus/utils-validation": "0.0.0-5115",
26
26
  "eta": "^1.12.3",
27
27
  "fs-extra": "^10.1.0",
28
28
  "lodash": "^4.17.21",
29
29
  "tslib": "^2.4.0"
30
30
  },
31
31
  "devDependencies": {
32
- "@docusaurus/types": "0.0.0-5107"
32
+ "@docusaurus/types": "0.0.0-5115"
33
33
  },
34
34
  "peerDependencies": {
35
35
  "react": "^16.8.4 || ^17.0.0",
@@ -38,5 +38,5 @@
38
38
  "engines": {
39
39
  "node": ">=16.14"
40
40
  },
41
- "gitHead": "e8a7e815b9c2d151dc3f1bfa62fcd29e0b587991"
41
+ "gitHead": "bb75d2b3b0b8a33b537ada84e0541dc32b3cc15d"
42
42
  }
@@ -7,52 +7,57 @@
7
7
 
8
8
  import _ from 'lodash';
9
9
  import logger from '@docusaurus/logger';
10
- import {
11
- applyTrailingSlash,
12
- type ApplyTrailingSlashParams,
13
- } from '@docusaurus/utils-common';
10
+ import {applyTrailingSlash} from '@docusaurus/utils-common';
14
11
  import {
15
12
  createFromExtensionsRedirects,
16
13
  createToExtensionsRedirects,
17
14
  } from './extensionRedirects';
18
15
  import {validateRedirect} from './redirectValidation';
19
16
  import type {PluginOptions, RedirectOption} from './options';
20
- import type {PluginContext, RedirectMetadata} from './types';
17
+ import type {PluginContext, RedirectItem} from './types';
21
18
 
22
19
  export default function collectRedirects(
23
20
  pluginContext: PluginContext,
24
21
  trailingSlash: boolean | undefined,
25
- ): RedirectMetadata[] {
26
- let redirects = doCollectRedirects(pluginContext);
27
-
28
- redirects = applyRedirectsTrailingSlash(redirects, {
29
- trailingSlash,
30
- baseUrl: pluginContext.baseUrl,
31
- });
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
+ }));
32
54
 
33
55
  validateCollectedRedirects(redirects, pluginContext);
34
56
  return filterUnwantedRedirects(redirects, pluginContext);
35
57
  }
36
58
 
37
- // If users wants to redirect to=/abc and they enable trailingSlash=true then
38
- // => we don't want to reject the to=/abc (as only /abc/ is an existing/valid
39
- // path now)
40
- // => we want to redirect to=/abc/ without the user having to change all its
41
- // redirect plugin options
42
- // It should be easy to toggle siteConfig.trailingSlash option without having to
43
- // change other configs
44
- function applyRedirectsTrailingSlash(
45
- redirects: RedirectMetadata[],
46
- params: ApplyTrailingSlashParams,
47
- ) {
48
- return redirects.map((redirect) => ({
49
- ...redirect,
50
- to: applyTrailingSlash(redirect.to, params),
51
- }));
52
- }
53
-
54
59
  function validateCollectedRedirects(
55
- redirects: RedirectMetadata[],
60
+ redirects: RedirectItem[],
56
61
  pluginContext: PluginContext,
57
62
  ) {
58
63
  const redirectValidationErrors = redirects
@@ -89,9 +94,9 @@ Valid paths you can redirect to:
89
94
  }
90
95
 
91
96
  function filterUnwantedRedirects(
92
- redirects: RedirectMetadata[],
97
+ redirects: RedirectItem[],
93
98
  pluginContext: PluginContext,
94
- ): RedirectMetadata[] {
99
+ ): RedirectItem[] {
95
100
  // We don't want to create the same redirect twice, since that would lead to
96
101
  // writing the same html redirection file twice.
97
102
  Object.entries(_.groupBy(redirects, (redirect) => redirect.from)).forEach(
@@ -120,37 +125,15 @@ It is not possible to redirect the same pathname to multiple destinations: ${gro
120
125
  );
121
126
  }
122
127
 
123
- // For each plugin config option, create the appropriate redirects
124
- function doCollectRedirects(pluginContext: PluginContext): RedirectMetadata[] {
125
- return [
126
- ...createFromExtensionsRedirects(
127
- pluginContext.relativeRoutesPaths,
128
- pluginContext.options.fromExtensions,
129
- ),
130
- ...createToExtensionsRedirects(
131
- pluginContext.relativeRoutesPaths,
132
- pluginContext.options.toExtensions,
133
- ),
134
- ...createRedirectsOptionRedirects(pluginContext.options.redirects),
135
- ...createCreateRedirectsOptionRedirects(
136
- pluginContext.relativeRoutesPaths,
137
- pluginContext.options.createRedirects,
138
- ),
139
- ];
140
- }
141
-
142
128
  function createRedirectsOptionRedirects(
143
129
  redirectsOption: PluginOptions['redirects'],
144
- ): RedirectMetadata[] {
130
+ ): RedirectItem[] {
145
131
  // For convenience, user can use a string or a string[]
146
- function optionToRedirects(option: RedirectOption): RedirectMetadata[] {
132
+ function optionToRedirects(option: RedirectOption): RedirectItem[] {
147
133
  if (typeof option.from === 'string') {
148
134
  return [{from: option.from, to: option.to}];
149
135
  }
150
- return option.from.map((from) => ({
151
- from,
152
- to: option.to,
153
- }));
136
+ return option.from.map((from) => ({from, to: option.to}));
154
137
  }
155
138
 
156
139
  return redirectsOption.flatMap(optionToRedirects);
@@ -160,17 +143,14 @@ function createRedirectsOptionRedirects(
160
143
  function createCreateRedirectsOptionRedirects(
161
144
  paths: string[],
162
145
  createRedirects: PluginOptions['createRedirects'],
163
- ): RedirectMetadata[] {
164
- function createPathRedirects(path: string): RedirectMetadata[] {
146
+ ): RedirectItem[] {
147
+ function createPathRedirects(path: string): RedirectItem[] {
165
148
  const fromsMixed: string | string[] = createRedirects?.(path) ?? [];
166
149
 
167
150
  const froms: string[] =
168
151
  typeof fromsMixed === 'string' ? [fromsMixed] : fromsMixed;
169
152
 
170
- return froms.map((from) => ({
171
- from,
172
- to: path,
173
- }));
153
+ return froms.map((from) => ({from, to: path}));
174
154
  }
175
155
 
176
156
  return paths.flatMap(createPathRedirects);
@@ -10,7 +10,7 @@ import {
10
10
  removeSuffix,
11
11
  removeTrailingSlash,
12
12
  } from '@docusaurus/utils';
13
- import type {RedirectMetadata} from './types';
13
+ import type {RedirectItem} from './types';
14
14
 
15
15
  const ExtensionAdditionalMessage =
16
16
  'If the redirect extension system is not good enough for your use case, you can create redirects yourself with the "createRedirects" plugin option.';
@@ -40,23 +40,21 @@ const validateExtension = (ext: string) => {
40
40
 
41
41
  const addLeadingDot = (extension: string) => `.${extension}`;
42
42
 
43
- // Create new /path that redirects to existing an /path.html
43
+ /**
44
+ * Create new `/path` that redirects to existing an `/path.html`
45
+ */
44
46
  export function createToExtensionsRedirects(
45
47
  paths: string[],
46
48
  extensions: string[],
47
- ): RedirectMetadata[] {
49
+ ): RedirectItem[] {
48
50
  extensions.forEach(validateExtension);
49
51
 
50
52
  const dottedExtensions = extensions.map(addLeadingDot);
51
53
 
52
- const createPathRedirects = (path: string): RedirectMetadata[] => {
54
+ const createPathRedirects = (path: string): RedirectItem[] => {
53
55
  const extensionFound = dottedExtensions.find((ext) => path.endsWith(ext));
54
56
  if (extensionFound) {
55
- const routePathWithoutExtension = removeSuffix(path, extensionFound);
56
- return [routePathWithoutExtension].map((from) => ({
57
- from,
58
- to: path,
59
- }));
57
+ return [{from: removeSuffix(path, extensionFound), to: path}];
60
58
  }
61
59
  return [];
62
60
  };
@@ -64,12 +62,15 @@ export function createToExtensionsRedirects(
64
62
  return paths.flatMap(createPathRedirects);
65
63
  }
66
64
 
67
- // Create new /path.html/index.html that redirects to existing an /path
68
- // The filename pattern might look weird but it's on purpose (see https://github.com/facebook/docusaurus/issues/5055)
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
+ */
69
70
  export function createFromExtensionsRedirects(
70
71
  paths: string[],
71
72
  extensions: string[],
72
- ): RedirectMetadata[] {
73
+ ): RedirectItem[] {
73
74
  extensions.forEach(validateExtension);
74
75
 
75
76
  const dottedExtensions = extensions.map(addLeadingDot);
@@ -77,7 +78,7 @@ export function createFromExtensionsRedirects(
77
78
  const alreadyEndsWithAnExtension = (str: string) =>
78
79
  dottedExtensions.some((ext) => str.endsWith(ext));
79
80
 
80
- const createPathRedirects = (path: string): RedirectMetadata[] => {
81
+ const createPathRedirects = (path: string): RedirectItem[] => {
81
82
  if (path === '' || path === '/' || alreadyEndsWithAnExtension(path)) {
82
83
  return [];
83
84
  }
package/src/index.ts CHANGED
@@ -8,11 +8,11 @@
8
8
  import {removePrefix, addLeadingSlash} from '@docusaurus/utils';
9
9
  import collectRedirects from './collectRedirects';
10
10
  import writeRedirectFiles, {
11
- toRedirectFilesMetadata,
12
- type RedirectFileMetadata,
11
+ toRedirectFiles,
12
+ type RedirectFile,
13
13
  } from './writeRedirectFiles';
14
14
  import type {LoadContext, Plugin} from '@docusaurus/types';
15
- import type {PluginContext, RedirectMetadata} from './types';
15
+ import type {PluginContext, RedirectItem} from './types';
16
16
  import type {PluginOptions, Options} from './options';
17
17
 
18
18
  export default function pluginClientRedirectsPages(
@@ -33,12 +33,12 @@ export default function pluginClientRedirectsPages(
33
33
  options,
34
34
  };
35
35
 
36
- const redirects: RedirectMetadata[] = collectRedirects(
36
+ const redirects: RedirectItem[] = collectRedirects(
37
37
  pluginContext,
38
38
  trailingSlash,
39
39
  );
40
40
 
41
- const redirectFiles: RedirectFileMetadata[] = toRedirectFilesMetadata(
41
+ const redirectFiles: RedirectFile[] = toRedirectFiles(
42
42
  redirects,
43
43
  pluginContext,
44
44
  trailingSlash,
package/src/options.ts CHANGED
@@ -9,7 +9,9 @@ import {Joi, PathnameSchema} from '@docusaurus/utils-validation';
9
9
  import type {OptionValidationContext} from '@docusaurus/types';
10
10
 
11
11
  export type RedirectOption = {
12
+ /** Pathname of an existing Docusaurus page */
12
13
  to: string;
14
+ /** Pathname of the new page(s) we should create */
13
15
  from: string | string[];
14
16
  };
15
17
 
@@ -23,7 +25,9 @@ export type PluginOptions = {
23
25
  /** The list of redirect rules, each one with multiple `from`s → one `to`. */
24
26
  redirects: RedirectOption[];
25
27
  /**
26
- * A callback to create a redirect rule.
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.
27
31
  * @returns All the paths from which we should redirect to `path`
28
32
  */
29
33
  createRedirects?: (
@@ -6,14 +6,14 @@
6
6
  */
7
7
 
8
8
  import {Joi, PathnameSchema} from '@docusaurus/utils-validation';
9
- import type {RedirectMetadata} from './types';
9
+ import type {RedirectItem} from './types';
10
10
 
11
- const RedirectSchema = Joi.object<RedirectMetadata>({
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: RedirectMetadata): void {
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
@@ -21,7 +21,7 @@ export type PluginContext = Pick<Props, 'outDir' | 'baseUrl'> & {
21
21
  * /!\ easy to be confused: "from" is the new page we should create,
22
22
  * that redirects to "to": the existing Docusaurus page
23
23
  */
24
- export type RedirectMetadata = {
24
+ export type RedirectItem = {
25
25
  /** Pathname of the new page we should create */
26
26
  from: string;
27
27
  /** Pathname of an existing Docusaurus page */
@@ -13,11 +13,11 @@ import {normalizeUrl} from '@docusaurus/utils';
13
13
 
14
14
  import createRedirectPageContent from './createRedirectPageContent';
15
15
 
16
- import type {PluginContext, RedirectMetadata} from './types';
16
+ import type {PluginContext, RedirectItem} from './types';
17
17
 
18
18
  export type WriteFilesPluginContext = Pick<PluginContext, 'baseUrl' | 'outDir'>;
19
19
 
20
- export type RedirectFileMetadata = {
20
+ export type RedirectFile = {
21
21
  fileAbsolutePath: string;
22
22
  fileContent: string;
23
23
  };
@@ -57,11 +57,11 @@ function getRedirectFilePath(
57
57
  return path.join(filePath, `${fileName}/index.html`);
58
58
  }
59
59
 
60
- export function toRedirectFilesMetadata(
61
- redirects: RedirectMetadata[],
60
+ export function toRedirectFiles(
61
+ redirects: RedirectItem[],
62
62
  pluginContext: WriteFilesPluginContext,
63
63
  trailingSlash: boolean | undefined,
64
- ): RedirectFileMetadata[] {
64
+ ): RedirectFile[] {
65
65
  // Perf: avoid rendering the template twice with the exact same "props"
66
66
  // We might create multiple redirect pages for the same destination url
67
67
  // note: the first fn arg is the cache key!
@@ -69,7 +69,7 @@ export function toRedirectFilesMetadata(
69
69
  createRedirectPageContent({toUrl}),
70
70
  );
71
71
 
72
- const createFileMetadata = (redirect: RedirectMetadata) => {
72
+ const createFileMetadata = (redirect: RedirectItem) => {
73
73
  const fileRelativePath = getRedirectFilePath(redirect.from, trailingSlash);
74
74
  const fileAbsolutePath = path.join(pluginContext.outDir, fileRelativePath);
75
75
  const toUrl = createToUrl(pluginContext.baseUrl, redirect.to);
@@ -84,9 +84,7 @@ export function toRedirectFilesMetadata(
84
84
  return redirects.map(createFileMetadata);
85
85
  }
86
86
 
87
- export async function writeRedirectFile(
88
- file: RedirectFileMetadata,
89
- ): Promise<void> {
87
+ export async function writeRedirectFile(file: RedirectFile): Promise<void> {
90
88
  try {
91
89
  // User-friendly security to prevent file overrides
92
90
  if (await fs.pathExists(file.fileAbsolutePath)) {
@@ -108,7 +106,7 @@ export async function writeRedirectFile(
108
106
  }
109
107
 
110
108
  export default async function writeRedirectFiles(
111
- redirectFiles: RedirectFileMetadata[],
109
+ redirectFiles: RedirectFile[],
112
110
  ): Promise<void> {
113
111
  await Promise.all(redirectFiles.map(writeRedirectFile));
114
112
  }