mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-04 04:37:28 +02:00
feat(client-redirects-plugin): support fully qualified urls and querystring/hash in destination/to url (#9171)
This commit is contained in:
parent
4ea0a70f93
commit
09ea3bcfab
17 changed files with 260 additions and 44 deletions
|
@ -5,6 +5,8 @@ exports[`collectRedirects throw if plugin option redirects contain invalid to pa
|
||||||
|
|
||||||
These paths are redirected to but do not exist:
|
These paths are redirected to but do not exist:
|
||||||
- /this/path/does/not/exist2
|
- /this/path/does/not/exist2
|
||||||
|
- /this/path/does/not/exist3
|
||||||
|
- /this/path/does/not/exist4
|
||||||
|
|
||||||
Valid paths you can redirect to:
|
Valid paths you can redirect to:
|
||||||
- /
|
- /
|
||||||
|
@ -37,8 +39,8 @@ exports[`collectRedirects throws if redirect creator creates array of array redi
|
||||||
|
|
||||||
exports[`collectRedirects throws if redirect creator creates invalid redirects 1`] = `
|
exports[`collectRedirects throws if redirect creator creates invalid redirects 1`] = `
|
||||||
"Some created redirects are invalid:
|
"Some created redirects are invalid:
|
||||||
- {"from":"https://google.com/","to":"/"} => Validation error: "from" is not a valid pathname. Pathname should start with slash and not contain any domain or query string.
|
- {"from":"https://google.com/","to":"/"} => Validation error: "from" (https://google.com/) is not a valid pathname. Pathname should start with slash and not contain any domain or query string.
|
||||||
- {"from":"//abc","to":"/"} => Validation error: "from" is not a valid pathname. Pathname should start with slash and not contain any domain or query string.
|
- {"from":"//abc","to":"/"} => Validation error: "from" (//abc) is not a valid pathname. Pathname should start with slash and not contain any domain or query string.
|
||||||
- {"from":"/def?queryString=toto","to":"/"} => Validation error: "from" is not a valid pathname. Pathname should start with slash and not contain any domain or query string.
|
- {"from":"/def?queryString=toto","to":"/"} => Validation error: "from" (/def?queryString=toto) is not a valid pathname. Pathname should start with slash and not contain any domain or query string.
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`validateRedirect throw for bad redirects 1`] = `"{"from":"https://fb.com/fromSomePath","to":"/toSomePath"} => Validation error: "from" is not a valid pathname. Pathname should start with slash and not contain any domain or query string."`;
|
exports[`validateRedirect throw for bad redirects 1`] = `"{"from":"https://fb.com/fromSomePath","to":"/toSomePath"} => Validation error: "from" (https://fb.com/fromSomePath) is not a valid pathname. Pathname should start with slash and not contain any domain or query string."`;
|
||||||
|
|
||||||
exports[`validateRedirect throw for bad redirects 2`] = `"{"from":"/fromSomePath","to":"https://fb.com/toSomePath"} => Validation error: "to" is not a valid pathname. Pathname should start with slash and not contain any domain or query string."`;
|
exports[`validateRedirect throw for bad redirects 2`] = `"{"from":"/fromSomePath?a=1","to":"/toSomePath"} => Validation error: "from" (/fromSomePath?a=1) is not a valid pathname. Pathname should start with slash and not contain any domain or query string."`;
|
||||||
|
|
||||||
exports[`validateRedirect throw for bad redirects 3`] = `"{"from":"/fromSomePath","to":"/toSomePath?queryString=xyz"} => Validation error: "to" is not a valid pathname. Pathname should start with slash and not contain any domain or query string."`;
|
exports[`validateRedirect throw for bad redirects 3`] = `"{"from":"/fromSomePath#anchor","to":"/toSomePath"} => Validation error: "from" (/fromSomePath#anchor) is not a valid pathname. Pathname should start with slash and not contain any domain or query string."`;
|
||||||
|
|
||||||
exports[`validateRedirect throw for bad redirects 4`] = `"{"from":null,"to":"/toSomePath?queryString=xyz"} => Validation error: "from" must be a string"`;
|
|
||||||
|
|
||||||
exports[`validateRedirect throw for bad redirects 5`] = `"{"from":["hey"],"to":"/toSomePath?queryString=xyz"} => Validation error: "from" must be a string"`;
|
|
||||||
|
|
|
@ -1,5 +1,43 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`toRedirectFiles creates appropriate metadata absolute url: fileContent 1`] = `
|
||||||
|
[
|
||||||
|
"<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="refresh" content="0; url=https://docusaurus.io/">
|
||||||
|
<link rel="canonical" href="https://docusaurus.io/" />
|
||||||
|
</head>
|
||||||
|
<script>
|
||||||
|
window.location.href = 'https://docusaurus.io/' + window.location.search + window.location.hash;
|
||||||
|
</script>
|
||||||
|
</html>",
|
||||||
|
"<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="refresh" content="0; url=https://docusaurus.io/docs/intro?a=1">
|
||||||
|
<link rel="canonical" href="https://docusaurus.io/docs/intro?a=1" />
|
||||||
|
</head>
|
||||||
|
<script>
|
||||||
|
window.location.href = 'https://docusaurus.io/docs/intro?a=1';
|
||||||
|
</script>
|
||||||
|
</html>",
|
||||||
|
"<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="refresh" content="0; url=https://docusaurus.io/docs/intro#anchor">
|
||||||
|
<link rel="canonical" href="https://docusaurus.io/docs/intro#anchor" />
|
||||||
|
</head>
|
||||||
|
<script>
|
||||||
|
window.location.href = 'https://docusaurus.io/docs/intro#anchor';
|
||||||
|
</script>
|
||||||
|
</html>",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`toRedirectFiles creates appropriate metadata for empty baseUrl: fileContent baseUrl=empty 1`] = `
|
exports[`toRedirectFiles creates appropriate metadata for empty baseUrl: fileContent baseUrl=empty 1`] = `
|
||||||
[
|
[
|
||||||
"<!DOCTYPE html>
|
"<!DOCTYPE html>
|
||||||
|
|
|
@ -95,13 +95,51 @@ describe('collectRedirects', () => {
|
||||||
from: '/someLegacyPath',
|
from: '/someLegacyPath',
|
||||||
to: '/somePath',
|
to: '/somePath',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
from: '/someLegacyPath2',
|
||||||
|
to: '/some Path2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: '/someLegacyPath3',
|
||||||
|
to: '/some%20Path3',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
from: ['/someLegacyPathArray1', '/someLegacyPathArray2'],
|
from: ['/someLegacyPathArray1', '/someLegacyPathArray2'],
|
||||||
to: '/',
|
to: '/',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
from: '/localQS',
|
||||||
|
to: '/somePath?a=1&b=2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: '/localAnchor',
|
||||||
|
to: '/somePath#anchor',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: '/localQSAnchor',
|
||||||
|
to: '/somePath?a=1&b=2#anchor',
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
from: '/absolute',
|
||||||
|
to: 'https://docusaurus.io/somePath',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: '/absoluteQS',
|
||||||
|
to: 'https://docusaurus.io/somePath?a=1&b=2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: '/absoluteAnchor',
|
||||||
|
to: 'https://docusaurus.io/somePath#anchor',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: '/absoluteQSAnchor',
|
||||||
|
to: 'https://docusaurus.io/somePath?a=1&b=2#anchor',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
['/', '/somePath'],
|
['/', '/somePath', '/some%20Path2', '/some Path3'],
|
||||||
),
|
),
|
||||||
undefined,
|
undefined,
|
||||||
),
|
),
|
||||||
|
@ -110,6 +148,14 @@ describe('collectRedirects', () => {
|
||||||
from: '/someLegacyPath',
|
from: '/someLegacyPath',
|
||||||
to: '/somePath',
|
to: '/somePath',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
from: '/someLegacyPath2',
|
||||||
|
to: '/some Path2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: '/someLegacyPath3',
|
||||||
|
to: '/some%20Path3',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
from: '/someLegacyPathArray1',
|
from: '/someLegacyPathArray1',
|
||||||
to: '/',
|
to: '/',
|
||||||
|
@ -118,6 +164,35 @@ describe('collectRedirects', () => {
|
||||||
from: '/someLegacyPathArray2',
|
from: '/someLegacyPathArray2',
|
||||||
to: '/',
|
to: '/',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
from: '/localQS',
|
||||||
|
to: '/somePath?a=1&b=2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: '/localAnchor',
|
||||||
|
to: '/somePath#anchor',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: '/localQSAnchor',
|
||||||
|
to: '/somePath?a=1&b=2#anchor',
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
from: '/absolute',
|
||||||
|
to: 'https://docusaurus.io/somePath',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: '/absoluteQS',
|
||||||
|
to: 'https://docusaurus.io/somePath?a=1&b=2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: '/absoluteAnchor',
|
||||||
|
to: 'https://docusaurus.io/somePath#anchor',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: '/absoluteQSAnchor',
|
||||||
|
to: 'https://docusaurus.io/somePath?a=1&b=2#anchor',
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -209,7 +284,11 @@ describe('collectRedirects', () => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
from: '/someLegacyPath',
|
from: '/someLegacyPath',
|
||||||
to: '/this/path/does/not/exist2',
|
to: '/this/path/does/not/exist3',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: '/someLegacyPath',
|
||||||
|
to: '/this/path/does/not/exist4?a=b#anchor',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -26,6 +26,18 @@ describe('validateRedirect', () => {
|
||||||
from: '/fromSomePath',
|
from: '/fromSomePath',
|
||||||
to: '/to/Some/Path',
|
to: '/to/Some/Path',
|
||||||
});
|
});
|
||||||
|
validateRedirect({
|
||||||
|
from: '/fromSomePath',
|
||||||
|
to: '/toSomePath?a=1',
|
||||||
|
});
|
||||||
|
validateRedirect({
|
||||||
|
from: '/fromSomePath',
|
||||||
|
to: '/toSomePath#anchor',
|
||||||
|
});
|
||||||
|
validateRedirect({
|
||||||
|
from: '/fromSomePath',
|
||||||
|
to: '/toSomePath?a=1&b=2#anchor',
|
||||||
|
});
|
||||||
}).not.toThrow();
|
}).not.toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -39,29 +51,15 @@ describe('validateRedirect', () => {
|
||||||
|
|
||||||
expect(() =>
|
expect(() =>
|
||||||
validateRedirect({
|
validateRedirect({
|
||||||
from: '/fromSomePath',
|
from: '/fromSomePath?a=1',
|
||||||
to: 'https://fb.com/toSomePath',
|
to: '/toSomePath',
|
||||||
}),
|
}),
|
||||||
).toThrowErrorMatchingSnapshot();
|
).toThrowErrorMatchingSnapshot();
|
||||||
|
|
||||||
expect(() =>
|
expect(() =>
|
||||||
validateRedirect({
|
validateRedirect({
|
||||||
from: '/fromSomePath',
|
from: '/fromSomePath#anchor',
|
||||||
to: '/toSomePath?queryString=xyz',
|
to: '/toSomePath',
|
||||||
}),
|
|
||||||
).toThrowErrorMatchingSnapshot();
|
|
||||||
|
|
||||||
expect(() =>
|
|
||||||
validateRedirect({
|
|
||||||
from: null as unknown as string,
|
|
||||||
to: '/toSomePath?queryString=xyz',
|
|
||||||
}),
|
|
||||||
).toThrowErrorMatchingSnapshot();
|
|
||||||
|
|
||||||
expect(() =>
|
|
||||||
validateRedirect({
|
|
||||||
from: ['hey'] as unknown as string,
|
|
||||||
to: '/toSomePath?queryString=xyz',
|
|
||||||
}),
|
}),
|
||||||
).toThrowErrorMatchingSnapshot();
|
).toThrowErrorMatchingSnapshot();
|
||||||
});
|
});
|
||||||
|
|
|
@ -24,8 +24,12 @@ describe('createToUrl', () => {
|
||||||
expect(createToUrl('/', '/docs/something/else/')).toBe(
|
expect(createToUrl('/', '/docs/something/else/')).toBe(
|
||||||
'/docs/something/else/',
|
'/docs/something/else/',
|
||||||
);
|
);
|
||||||
expect(createToUrl('/', 'docs/something/else')).toBe(
|
expect(createToUrl('/', 'docs/something/else')).toBe('docs/something/else');
|
||||||
'/docs/something/else',
|
expect(createToUrl('/', './docs/something/else')).toBe(
|
||||||
|
'./docs/something/else',
|
||||||
|
);
|
||||||
|
expect(createToUrl('/', 'https://docs/something/else')).toBe(
|
||||||
|
'https://docs/something/else',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -37,12 +41,45 @@ describe('createToUrl', () => {
|
||||||
'/baseUrl/docs/something/else/',
|
'/baseUrl/docs/something/else/',
|
||||||
);
|
);
|
||||||
expect(createToUrl('/baseUrl/', 'docs/something/else')).toBe(
|
expect(createToUrl('/baseUrl/', 'docs/something/else')).toBe(
|
||||||
'/baseUrl/docs/something/else',
|
'docs/something/else',
|
||||||
|
);
|
||||||
|
expect(createToUrl('/baseUrl/', './docs/something/else')).toBe(
|
||||||
|
'./docs/something/else',
|
||||||
|
);
|
||||||
|
expect(createToUrl('/baseUrl/', 'https://docs/something/else')).toBe(
|
||||||
|
'https://docs/something/else',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('toRedirectFiles', () => {
|
describe('toRedirectFiles', () => {
|
||||||
|
it('creates appropriate metadata absolute url', () => {
|
||||||
|
const pluginContext = {
|
||||||
|
outDir: '/tmp/someFixedOutDir',
|
||||||
|
baseUrl: '/',
|
||||||
|
};
|
||||||
|
|
||||||
|
const redirectFiles = toRedirectFiles(
|
||||||
|
[
|
||||||
|
{from: '/abc', to: 'https://docusaurus.io/'},
|
||||||
|
{from: '/def', to: 'https://docusaurus.io/docs/intro?a=1'},
|
||||||
|
{from: '/ijk', to: 'https://docusaurus.io/docs/intro#anchor'},
|
||||||
|
],
|
||||||
|
pluginContext,
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(redirectFiles.map((f) => f.fileAbsolutePath)).toEqual([
|
||||||
|
path.join(pluginContext.outDir, '/abc/index.html'),
|
||||||
|
path.join(pluginContext.outDir, '/def/index.html'),
|
||||||
|
path.join(pluginContext.outDir, '/ijk/index.html'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(redirectFiles.map((f) => f.fileContent)).toMatchSnapshot(
|
||||||
|
'fileContent',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('creates appropriate metadata trailingSlash=undefined', () => {
|
it('creates appropriate metadata trailingSlash=undefined', () => {
|
||||||
const pluginContext = {
|
const pluginContext = {
|
||||||
outDir: '/tmp/someFixedOutDir',
|
outDir: '/tmp/someFixedOutDir',
|
||||||
|
|
|
@ -79,8 +79,25 @@ function validateCollectedRedirects(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const allowedToPaths = pluginContext.relativeRoutesPaths;
|
const allowedToPaths = pluginContext.relativeRoutesPaths.map((p) =>
|
||||||
const toPaths = redirects.map((redirect) => redirect.to);
|
decodeURI(p),
|
||||||
|
);
|
||||||
|
const toPaths = redirects
|
||||||
|
.map((redirect) => redirect.to)
|
||||||
|
// We now allow "to" to contain any string
|
||||||
|
// We only do this "broken redirect" check from to that looks like pathnames
|
||||||
|
// note: we allow querystring/anchors
|
||||||
|
// See https://github.com/facebook/docusaurus/issues/6845
|
||||||
|
.map((to) => {
|
||||||
|
if (to.startsWith('/')) {
|
||||||
|
try {
|
||||||
|
return decodeURI(new URL(to, 'https://example.com').pathname);
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
})
|
||||||
|
.filter((to): to is string => typeof to !== 'undefined');
|
||||||
|
|
||||||
const trailingSlashConfig = pluginContext.siteConfig.trailingSlash;
|
const trailingSlashConfig = pluginContext.siteConfig.trailingSlash;
|
||||||
// Key is the path, value is whether a valid toPath with a different trailing
|
// Key is the path, value is whether a valid toPath with a different trailing
|
||||||
// slash exists; if the key doesn't exist it means it's valid
|
// slash exists; if the key doesn't exist it means it's valid
|
||||||
|
@ -103,7 +120,6 @@ function validateCollectedRedirects(
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (differByTrailSlash.size > 0) {
|
if (differByTrailSlash.size > 0) {
|
||||||
console.log(differByTrailSlash);
|
|
||||||
const errors = Array.from(differByTrailSlash.entries());
|
const errors = Array.from(differByTrailSlash.entries());
|
||||||
|
|
||||||
let message =
|
let message =
|
||||||
|
|
|
@ -13,11 +13,26 @@ const getCompiledRedirectPageTemplate = _.memoize(() =>
|
||||||
eta.compile(redirectPageTemplate.trim()),
|
eta.compile(redirectPageTemplate.trim()),
|
||||||
);
|
);
|
||||||
|
|
||||||
function renderRedirectPageTemplate(data: {toUrl: string}) {
|
function renderRedirectPageTemplate(data: {
|
||||||
|
toUrl: string;
|
||||||
|
searchAnchorForwarding: boolean;
|
||||||
|
}) {
|
||||||
const compiled = getCompiledRedirectPageTemplate();
|
const compiled = getCompiledRedirectPageTemplate();
|
||||||
return compiled(data, eta.defaultConfig);
|
return compiled(data, eta.defaultConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if the target url does not include ?search#anchor,
|
||||||
|
// we forward search/anchor that the redirect page receives
|
||||||
|
function searchAnchorForwarding(toUrl: string): boolean {
|
||||||
|
try {
|
||||||
|
const url = new URL(toUrl, 'https://example.com');
|
||||||
|
const containsSearchOrAnchor = url.search || url.hash;
|
||||||
|
return !containsSearchOrAnchor;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default function createRedirectPageContent({
|
export default function createRedirectPageContent({
|
||||||
toUrl,
|
toUrl,
|
||||||
}: {
|
}: {
|
||||||
|
@ -25,5 +40,6 @@ export default function createRedirectPageContent({
|
||||||
}): string {
|
}): string {
|
||||||
return renderRedirectPageTemplate({
|
return renderRedirectPageTemplate({
|
||||||
toUrl: encodeURI(toUrl),
|
toUrl: encodeURI(toUrl),
|
||||||
|
searchAnchorForwarding: searchAnchorForwarding(toUrl),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,8 @@ export default function pluginClientRedirectsPages(
|
||||||
siteConfig: props.siteConfig,
|
siteConfig: props.siteConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
console.log({propsBaseUrl: props.baseUrl});
|
||||||
|
|
||||||
const redirects: RedirectItem[] = collectRedirects(
|
const redirects: RedirectItem[] = collectRedirects(
|
||||||
pluginContext,
|
pluginContext,
|
||||||
trailingSlash,
|
trailingSlash,
|
||||||
|
|
|
@ -45,11 +45,11 @@ export const DEFAULT_OPTIONS: Partial<PluginOptions> = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const RedirectPluginOptionValidation = Joi.object<RedirectOption>({
|
const RedirectPluginOptionValidation = Joi.object<RedirectOption>({
|
||||||
to: PathnameSchema.required(),
|
|
||||||
from: Joi.alternatives().try(
|
from: Joi.alternatives().try(
|
||||||
PathnameSchema.required(),
|
PathnameSchema.required(),
|
||||||
Joi.array().items(PathnameSchema.required()),
|
Joi.array().items(PathnameSchema.required()),
|
||||||
),
|
),
|
||||||
|
to: Joi.string().required(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const isString = Joi.string().required().not(null);
|
const isString = Joi.string().required().not(null);
|
||||||
|
|
|
@ -10,7 +10,7 @@ import type {RedirectItem} from './types';
|
||||||
|
|
||||||
const RedirectSchema = Joi.object<RedirectItem>({
|
const RedirectSchema = Joi.object<RedirectItem>({
|
||||||
from: PathnameSchema.required(),
|
from: PathnameSchema.required(),
|
||||||
to: PathnameSchema.required(),
|
to: Joi.string().required(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export function validateRedirect(redirect: RedirectItem): void {
|
export function validateRedirect(redirect: RedirectItem): void {
|
||||||
|
|
|
@ -14,7 +14,7 @@ export default `
|
||||||
<link rel="canonical" href="<%= it.toUrl %>" />
|
<link rel="canonical" href="<%= it.toUrl %>" />
|
||||||
</head>
|
</head>
|
||||||
<script>
|
<script>
|
||||||
window.location.href = '<%= it.toUrl %>' + window.location.search + window.location.hash;
|
window.location.href = '<%= it.toUrl %>'<%= it.searchAnchorForwarding ? ' + window.location.search + window.location.hash' : '' %>;
|
||||||
</script>
|
</script>
|
||||||
</html>
|
</html>
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -23,8 +23,11 @@ export type RedirectFile = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createToUrl(baseUrl: string, to: string): string {
|
export function createToUrl(baseUrl: string, to: string): string {
|
||||||
|
if (to.startsWith('/')) {
|
||||||
return normalizeUrl([baseUrl, to]);
|
return normalizeUrl([baseUrl, to]);
|
||||||
}
|
}
|
||||||
|
return to;
|
||||||
|
}
|
||||||
|
|
||||||
// Create redirect file path
|
// Create redirect file path
|
||||||
// Make sure this path has lower precedence over the original file path when
|
// Make sure this path has lower precedence over the original file path when
|
||||||
|
|
|
@ -30,9 +30,9 @@ exports[`validation schemas contentVisibilitySchema: for value={"unlisted":"bad
|
||||||
|
|
||||||
exports[`validation schemas contentVisibilitySchema: for value={"unlisted":42} 1`] = `""unlisted" must be a boolean"`;
|
exports[`validation schemas contentVisibilitySchema: for value={"unlisted":42} 1`] = `""unlisted" must be a boolean"`;
|
||||||
|
|
||||||
exports[`validation schemas pathnameSchema: for value="foo" 1`] = `""value" is not a valid pathname. Pathname should start with slash and not contain any domain or query string."`;
|
exports[`validation schemas pathnameSchema: for value="foo" 1`] = `""value" (foo) is not a valid pathname. Pathname should start with slash and not contain any domain or query string."`;
|
||||||
|
|
||||||
exports[`validation schemas pathnameSchema: for value="https://github.com/foo" 1`] = `""value" is not a valid pathname. Pathname should start with slash and not contain any domain or query string."`;
|
exports[`validation schemas pathnameSchema: for value="https://github.com/foo" 1`] = `""value" (https://github.com/foo) is not a valid pathname. Pathname should start with slash and not contain any domain or query string."`;
|
||||||
|
|
||||||
exports[`validation schemas pluginIdSchema: for value="/docs" 1`] = `"Illegal plugin ID value "/docs": it should only contain alphanumerics, underscores, and dashes."`;
|
exports[`validation schemas pluginIdSchema: for value="/docs" 1`] = `"Illegal plugin ID value "/docs": it should only contain alphanumerics, underscores, and dashes."`;
|
||||||
|
|
||||||
|
|
|
@ -91,7 +91,7 @@ export const PathnameSchema = Joi.string()
|
||||||
return val;
|
return val;
|
||||||
})
|
})
|
||||||
.message(
|
.message(
|
||||||
'{{#label}} is not a valid pathname. Pathname should start with slash and not contain any domain or query string.',
|
'{{#label}} ({{#value}}) is not a valid pathname. Pathname should start with slash and not contain any domain or query string.',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Normalized schema for url path segments: baseUrl + routeBasePath...
|
// Normalized schema for url path segments: baseUrl + routeBasePath...
|
||||||
|
|
|
@ -100,3 +100,30 @@ const dogfoodingPluginInstances = [
|
||||||
];
|
];
|
||||||
|
|
||||||
exports.dogfoodingPluginInstances = dogfoodingPluginInstances;
|
exports.dogfoodingPluginInstances = dogfoodingPluginInstances;
|
||||||
|
|
||||||
|
exports.dogfoodingRedirects = [
|
||||||
|
{
|
||||||
|
from: ['/home/'],
|
||||||
|
to: '/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: ['/home/qs'],
|
||||||
|
to: '/?a=1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: ['/home/anchor'],
|
||||||
|
to: '/#anchor',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: ['/home/absolute'],
|
||||||
|
to: 'https://docusaurus.io/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: ['/home/absolute/qs'],
|
||||||
|
to: 'https://docusaurus.io/?a=1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: ['/home/absolute/anchor'],
|
||||||
|
to: 'https://docusaurus.io/#anchor',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
|
@ -13,6 +13,7 @@ const VersionsArchived = require('./versionsArchived.json');
|
||||||
const {
|
const {
|
||||||
dogfoodingPluginInstances,
|
dogfoodingPluginInstances,
|
||||||
dogfoodingThemeInstances,
|
dogfoodingThemeInstances,
|
||||||
|
dogfoodingRedirects,
|
||||||
} = require('./_dogfooding/dogfooding.config');
|
} = require('./_dogfooding/dogfooding.config');
|
||||||
|
|
||||||
/** @type {Record<string,Record<string,string>>} */
|
/** @type {Record<string,Record<string,string>>} */
|
||||||
|
@ -260,6 +261,7 @@ module.exports = async function createConfigAsync() {
|
||||||
from: ['/docs/resources', '/docs/next/resources'],
|
from: ['/docs/resources', '/docs/next/resources'],
|
||||||
to: '/community/resources',
|
to: '/community/resources',
|
||||||
},
|
},
|
||||||
|
...dogfoodingRedirects,
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|
Loading…
Add table
Reference in a new issue