@docusaurus/plugin-client-redirects 2.0.0-beta.2 → 2.0.0-beta.22

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.
Files changed (40) hide show
  1. package/lib/collectRedirects.d.ts +2 -2
  2. package/lib/collectRedirects.js +44 -60
  3. package/lib/createRedirectPageContent.d.ts +2 -4
  4. package/lib/createRedirectPageContent.js +2 -4
  5. package/lib/extensionRedirects.d.ts +11 -3
  6. package/lib/extensionRedirects.js +18 -13
  7. package/lib/index.d.ts +5 -3
  8. package/lib/index.js +10 -16
  9. package/lib/options.d.ts +35 -0
  10. package/lib/options.js +36 -0
  11. package/lib/redirectValidation.d.ts +2 -2
  12. package/lib/types.d.ts +14 -16
  13. package/lib/writeRedirectFiles.d.ts +5 -5
  14. package/lib/writeRedirectFiles.js +40 -14
  15. package/package.json +16 -14
  16. package/src/collectRedirects.ts +75 -101
  17. package/src/createRedirectPageContent.ts +8 -10
  18. package/src/deps.d.ts +16 -0
  19. package/src/extensionRedirects.ts +28 -19
  20. package/src/index.ts +15 -27
  21. package/src/options.ts +75 -0
  22. package/src/redirectValidation.ts +3 -3
  23. package/src/types.ts +16 -30
  24. package/src/writeRedirectFiles.ts +51 -22
  25. package/lib/.tsbuildinfo +0 -1
  26. package/lib/normalizePluginOptions.d.ts +0 -9
  27. package/lib/normalizePluginOptions.js +0 -44
  28. package/src/__tests__/__snapshots__/collectRedirects.test.ts.snap +0 -27
  29. package/src/__tests__/__snapshots__/createRedirectPageContent.test.ts.snap +0 -29
  30. package/src/__tests__/__snapshots__/normalizePluginOptions.test.ts.snap +0 -35
  31. package/src/__tests__/__snapshots__/redirectValidation.test.ts.snap +0 -11
  32. package/src/__tests__/__snapshots__/writeRedirectFiles.test.ts.snap +0 -147
  33. package/src/__tests__/collectRedirects.test.ts +0 -340
  34. package/src/__tests__/createRedirectPageContent.test.ts +0 -26
  35. package/src/__tests__/extensionRedirects.test.ts +0 -140
  36. package/src/__tests__/normalizePluginOptions.test.ts +0 -76
  37. package/src/__tests__/redirectValidation.test.ts +0 -66
  38. package/src/__tests__/writeRedirectFiles.test.ts +0 -203
  39. package/src/normalizePluginOptions.ts +0 -55
  40. package/tsconfig.json +0 -10
@@ -5,59 +5,68 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
- import {flatten, uniqBy, difference, groupBy} from 'lodash';
9
- import {
10
- PluginContext,
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
- import {applyTrailingSlash} from '@docusaurus/utils-common';
21
-
22
- import chalk from 'chalk';
16
+ import type {PluginOptions, RedirectOption} from './options';
17
+ import type {PluginContext, RedirectItem} from './types';
23
18
 
24
19
  export default function collectRedirects(
25
20
  pluginContext: PluginContext,
26
21
  trailingSlash: boolean | undefined,
27
- ): RedirectMetadata[] {
28
- let redirects = doCollectRedirects(pluginContext);
29
- redirects = applyRedirectsTrailingSlash(redirects, trailingSlash);
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
+
30
55
  validateCollectedRedirects(redirects, pluginContext);
31
56
  return filterUnwantedRedirects(redirects, pluginContext);
32
57
  }
33
58
 
34
- // If users wants to redirect to=/abc and they enable trailingSlash=true then
35
- // => we don't want to reject the to=/abc (as only /abc/ is an existing/valid path now)
36
- // => we want to redirect to=/abc/ without the user having to change all its redirect plugin options
37
- // It should be easy to toggle siteConfig.trailingSlash option without having to change other configs
38
- function applyRedirectsTrailingSlash(
39
- redirects: RedirectMetadata[],
40
- trailingSlash: boolean | undefined,
41
- ) {
42
- return redirects.map((redirect) => {
43
- return {
44
- ...redirect,
45
- to: applyTrailingSlash(redirect.to, trailingSlash),
46
- };
47
- });
48
- }
49
-
50
59
  function validateCollectedRedirects(
51
- redirects: RedirectMetadata[],
60
+ redirects: RedirectItem[],
52
61
  pluginContext: PluginContext,
53
62
  ) {
54
- const redirectValidationErrors: string[] = redirects
63
+ const redirectValidationErrors = redirects
55
64
  .map((redirect) => {
56
65
  try {
57
66
  validateRedirect(redirect);
58
67
  return undefined;
59
- } catch (e) {
60
- return e.message;
68
+ } catch (err) {
69
+ return (err as Error).message;
61
70
  }
62
71
  })
63
72
  .filter(Boolean);
@@ -71,7 +80,7 @@ function validateCollectedRedirects(
71
80
 
72
81
  const allowedToPaths = pluginContext.relativeRoutesPaths;
73
82
  const toPaths = redirects.map((redirect) => redirect.to);
74
- const illegalToPaths = difference(toPaths, allowedToPaths);
83
+ const illegalToPaths = _.difference(toPaths, allowedToPaths);
75
84
  if (illegalToPaths.length > 0) {
76
85
  throw new Error(
77
86
  `You are trying to create client-side redirections to paths that do not exist:
@@ -85,101 +94,66 @@ Valid paths you can redirect to:
85
94
  }
86
95
 
87
96
  function filterUnwantedRedirects(
88
- redirects: RedirectMetadata[],
97
+ redirects: RedirectItem[],
89
98
  pluginContext: PluginContext,
90
- ): RedirectMetadata[] {
91
- // we don't want to create twice the same redirect
92
- // that would lead to writing twice the same html redirection file
93
- 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(
94
103
  ([from, groupedFromRedirects]) => {
95
104
  if (groupedFromRedirects.length > 1) {
96
- console.error(
97
- chalk.red(
98
- `@docusaurus/plugin-client-redirects: multiple redirects are created with the same "from" pathname=${from}
99
- It is not possible to redirect the same pathname to multiple destinations:
100
- - ${groupedFromRedirects.map((r) => JSON.stringify(r)).join('\n- ')}
101
- `,
102
- ),
103
- );
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
+ )}`;
104
111
  }
105
112
  },
106
113
  );
107
- const collectedRedirects = uniqBy(redirects, (redirect) => redirect.from);
114
+ const collectedRedirects = _.uniqBy(redirects, (redirect) => redirect.from);
108
115
 
109
- // We don't want to override an already existing route with a redirect file!
110
- const redirectsOverridingExistingPath = collectedRedirects.filter(
111
- (redirect) => pluginContext.relativeRoutesPaths.includes(redirect.from),
112
- );
113
- if (redirectsOverridingExistingPath.length > 0) {
114
- console.error(
115
- chalk.red(
116
- `@docusaurus/plugin-client-redirects: some redirects would override existing paths, and will be ignored:
117
- - ${redirectsOverridingExistingPath.map((r) => JSON.stringify(r)).join('\n- ')}
118
- `,
119
- ),
116
+ const {false: newRedirects = [], true: redirectsOverridingExistingPath = []} =
117
+ _.groupBy(collectedRedirects, (redirect) =>
118
+ pluginContext.relativeRoutesPaths.includes(redirect.from),
120
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
+ )}`;
121
126
  }
122
- return collectedRedirects.filter(
123
- (redirect) => !pluginContext.relativeRoutesPaths.includes(redirect.from),
124
- );
125
- }
126
-
127
- // For each plugin config option, create the appropriate redirects
128
- function doCollectRedirects(pluginContext: PluginContext): RedirectMetadata[] {
129
- return [
130
- ...createFromExtensionsRedirects(
131
- pluginContext.relativeRoutesPaths,
132
- pluginContext.options.fromExtensions,
133
- ),
134
- ...createToExtensionsRedirects(
135
- pluginContext.relativeRoutesPaths,
136
- pluginContext.options.toExtensions,
137
- ),
138
- ...createRedirectsOptionRedirects(pluginContext.options.redirects),
139
- ...createCreateRedirectsOptionRedirects(
140
- pluginContext.relativeRoutesPaths,
141
- pluginContext.options.createRedirects,
142
- ),
143
- ];
127
+ return newRedirects;
144
128
  }
145
129
 
146
130
  function createRedirectsOptionRedirects(
147
131
  redirectsOption: PluginOptions['redirects'],
148
- ): RedirectMetadata[] {
149
- // For conveniency, user can use a string or a string[]
150
- function optionToRedirects(option: RedirectOption): RedirectMetadata[] {
132
+ ): RedirectItem[] {
133
+ // For convenience, user can use a string or a string[]
134
+ function optionToRedirects(option: RedirectOption): RedirectItem[] {
151
135
  if (typeof option.from === 'string') {
152
136
  return [{from: option.from, to: option.to}];
153
137
  }
154
- return option.from.map((from) => ({
155
- from,
156
- to: option.to,
157
- }));
138
+ return option.from.map((from) => ({from, to: option.to}));
158
139
  }
159
140
 
160
- return flatten(redirectsOption.map(optionToRedirects));
141
+ return redirectsOption.flatMap(optionToRedirects);
161
142
  }
162
143
 
163
144
  // Create redirects from the "createRedirects" fn provided by the user
164
145
  function createCreateRedirectsOptionRedirects(
165
146
  paths: string[],
166
147
  createRedirects: PluginOptions['createRedirects'],
167
- ): RedirectMetadata[] {
168
- function createPathRedirects(path: string): RedirectMetadata[] {
169
- const fromsMixed: string | string[] = createRedirects
170
- ? createRedirects(path) || []
171
- : [];
148
+ ): RedirectItem[] {
149
+ function createPathRedirects(path: string): RedirectItem[] {
150
+ const fromsMixed: string | string[] = createRedirects?.(path) ?? [];
172
151
 
173
152
  const froms: string[] =
174
153
  typeof fromsMixed === 'string' ? [fromsMixed] : fromsMixed;
175
154
 
176
- return froms.map((from) => {
177
- return {
178
- from,
179
- to: path,
180
- };
181
- });
155
+ return froms.map((from) => ({from, to: path}));
182
156
  }
183
157
 
184
- return flatten(paths.map(createPathRedirects));
158
+ return paths.flatMap(createPathRedirects);
185
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
- type CreateRedirectPageOptions = {
13
- toUrl: string;
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: Record<string, unknown>) {
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
- }: CreateRedirectPageOptions): string {
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,12 +5,15 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
- import {flatten} from 'lodash';
9
- import {removeSuffix} from '@docusaurus/utils';
10
- import {RedirectMetadata} from './types';
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
- 'If the redirect extension system is not good enough for your usecase, you can create redirects yourself with the "createRedirects" plugin option.';
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) {
@@ -37,35 +40,37 @@ const validateExtension = (ext: string) => {
37
40
 
38
41
  const addLeadingDot = (extension: string) => `.${extension}`;
39
42
 
40
- // Create new /path that redirects to existing an /path.html
43
+ /**
44
+ * Create new `/path` that redirects to existing an `/path.html`
45
+ */
41
46
  export function createToExtensionsRedirects(
42
47
  paths: string[],
43
48
  extensions: string[],
44
- ): RedirectMetadata[] {
49
+ ): RedirectItem[] {
45
50
  extensions.forEach(validateExtension);
46
51
 
47
52
  const dottedExtensions = extensions.map(addLeadingDot);
48
53
 
49
- const createPathRedirects = (path: string): RedirectMetadata[] => {
54
+ const createPathRedirects = (path: string): RedirectItem[] => {
50
55
  const extensionFound = dottedExtensions.find((ext) => path.endsWith(ext));
51
56
  if (extensionFound) {
52
- const routePathWithoutExtension = removeSuffix(path, extensionFound);
53
- return [routePathWithoutExtension].map((from) => ({
54
- from,
55
- to: path,
56
- }));
57
+ return [{from: removeSuffix(path, extensionFound), to: path}];
57
58
  }
58
59
  return [];
59
60
  };
60
61
 
61
- return flatten(paths.map(createPathRedirects));
62
+ return paths.flatMap(createPathRedirects);
62
63
  }
63
64
 
64
- // Create new /path.html that redirects to existing an /path
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
+ */
65
70
  export function createFromExtensionsRedirects(
66
71
  paths: string[],
67
72
  extensions: string[],
68
- ): RedirectMetadata[] {
73
+ ): RedirectItem[] {
69
74
  extensions.forEach(validateExtension);
70
75
 
71
76
  const dottedExtensions = extensions.map(addLeadingDot);
@@ -73,15 +78,19 @@ export function createFromExtensionsRedirects(
73
78
  const alreadyEndsWithAnExtension = (str: string) =>
74
79
  dottedExtensions.some((ext) => str.endsWith(ext));
75
80
 
76
- const createPathRedirects = (path: string): RedirectMetadata[] => {
77
- if (path === '' || path.endsWith('/') || alreadyEndsWithAnExtension(path)) {
81
+ const createPathRedirects = (path: string): RedirectItem[] => {
82
+ if (path === '' || path === '/' || alreadyEndsWithAnExtension(path)) {
78
83
  return [];
79
84
  }
80
85
  return extensions.map((ext) => ({
81
- from: `${path}.${ext}`,
86
+ // /path => /path.html
87
+ // /path/ => /path.html/
88
+ from: path.endsWith('/')
89
+ ? addTrailingSlash(`${removeTrailingSlash(path)}.${ext}`)
90
+ : `${path}.${ext}`,
82
91
  to: path,
83
92
  }));
84
93
  };
85
94
 
86
- return flatten(paths.map(createPathRedirects));
95
+ return paths.flatMap(createPathRedirects);
87
96
  }
package/src/index.ts CHANGED
@@ -5,41 +5,25 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
- import {LoadContext, Plugin, Props} from '@docusaurus/types';
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
- toRedirectFilesMetadata,
15
- RedirectFileMetadata,
11
+ toRedirectFiles,
12
+ type RedirectFile,
16
13
  } from './writeRedirectFiles';
17
- import {removePrefix, addLeadingSlash} from '@docusaurus/utils';
18
- import chalk from 'chalk';
14
+ import type {LoadContext, Plugin} from '@docusaurus/types';
15
+ import type {PluginContext, RedirectItem} from './types';
16
+ import type {PluginOptions, Options} from './options';
19
17
 
20
18
  export default function pluginClientRedirectsPages(
21
19
  context: LoadContext,
22
- opts: UserPluginOptions,
23
- ): Plugin<unknown> {
20
+ options: PluginOptions,
21
+ ): Plugin<void> {
24
22
  const {trailingSlash} = context.siteConfig;
25
23
 
26
- const options = normalizePluginOptions(opts);
27
-
28
- // Special case, when using trailingSlash=false we output /xyz.html files instead of /xyz/index.html
29
- // It makes no sense to use option fromExtensions=["html"]: the redirect files would be the same as the original files
30
- if (options.fromExtensions.includes('html') && trailingSlash === false) {
31
- console.warn(
32
- chalk.yellow(`Using the Docusaurus redirect plugin with fromExtensions=['html'] and siteConfig.trailingSlash=false is prevented.
33
- It would lead the redirect plugin to override all the page.html files created by Docusaurus.`),
34
- );
35
- options.fromExtensions = options.fromExtensions.filter(
36
- (ext) => ext !== 'html',
37
- );
38
- }
39
-
40
24
  return {
41
25
  name: 'docusaurus-plugin-client-redirects',
42
- async postBuild(props: Props) {
26
+ async postBuild(props) {
43
27
  const pluginContext: PluginContext = {
44
28
  relativeRoutesPaths: props.routesPaths.map(
45
29
  (path) => `${addLeadingSlash(removePrefix(path, props.baseUrl))}`,
@@ -47,14 +31,15 @@ It would lead the redirect plugin to override all the page.html files created by
47
31
  baseUrl: props.baseUrl,
48
32
  outDir: props.outDir,
49
33
  options,
34
+ siteConfig: props.siteConfig,
50
35
  };
51
36
 
52
- const redirects: RedirectMetadata[] = collectRedirects(
37
+ const redirects: RedirectItem[] = collectRedirects(
53
38
  pluginContext,
54
39
  trailingSlash,
55
40
  );
56
41
 
57
- const redirectFiles: RedirectFileMetadata[] = toRedirectFilesMetadata(
42
+ const redirectFiles: RedirectFile[] = toRedirectFiles(
58
43
  redirects,
59
44
  pluginContext,
60
45
  trailingSlash,
@@ -65,3 +50,6 @@ It would lead the redirect plugin to override all the page.html files created by
65
50
  },
66
51
  };
67
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<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
@@ -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
- export type PluginOptions = {
11
- id: string;
12
- fromExtensions: string[];
13
- toExtensions: string[];
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
- // In-memory representation of redirects we want: easier to test
38
- // /!\ easy to be confused: "from" is the new page we should create,
39
- // that redirects to "to": the existing Docusaurus page
40
- export type RedirectMetadata = {
41
- from: string; // pathname
42
- to: string; // pathname
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
  };