fix(core): normalize slashes for url/baseUrl instead of throwing (#8066)

This commit is contained in:
Joshua Chen 2022-09-08 11:18:26 -04:00 committed by GitHub
parent 73d0ede21a
commit bcae7503ff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 73 additions and 16 deletions

View file

@ -69,6 +69,9 @@ export const URISchema = Joi.alternatives(
// This custom validation logic is required notably because Joi does not // This custom validation logic is required notably because Joi does not
// accept paths like /a/b/c ... // accept paths like /a/b/c ...
Joi.custom((val: unknown, helpers) => { Joi.custom((val: unknown, helpers) => {
if (typeof val !== 'string') {
return helpers.error('any.invalid');
}
try { try {
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new URL(String(val)); new URL(String(val));

View file

@ -138,11 +138,6 @@ exports[`normalizeConfig should throw error if themes is not array for the input
" "
`; `;
exports[`normalizeConfig throws error for baseUrl without trailing \`/\` 1`] = `
""baseUrl" must be a string with a trailing slash.
"
`;
exports[`normalizeConfig throws error for required fields 1`] = ` exports[`normalizeConfig throws error for required fields 1`] = `
""baseUrl" is required ""baseUrl" is required
"title" is required "title" is required

View file

@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import {jest} from '@jest/globals';
import { import {
ConfigSchema, ConfigSchema,
DEFAULT_CONFIG, DEFAULT_CONFIG,
@ -86,12 +87,68 @@ describe('normalizeConfig', () => {
}).toThrowErrorMatchingSnapshot(); }).toThrowErrorMatchingSnapshot();
}); });
it('throws error for baseUrl without trailing `/`', () => { it('throws for non-string URLs', () => {
expect(() => { expect(() =>
normalizeConfig({
// @ts-expect-error: test
url: 1,
}),
).toThrowErrorMatchingInlineSnapshot(`
""url" contains an invalid value
"
`);
});
it('normalizes various URLs', () => {
const consoleMock = jest
.spyOn(console, 'warn')
.mockImplementation(() => {});
expect(
normalizeConfig({
url: 'https://mysite.com/',
}).url,
).toBe('https://mysite.com');
expect(
normalizeConfig({
// This shouldn't happen
url: 'https://mysite.com/foo/',
}).url,
).toBe('https://mysite.com/foo');
expect(consoleMock.mock.calls[0][0]).toMatchInlineSnapshot(
`"[WARNING] Docusaurus config validation warning. Field "url": The url is not supposed to contain a sub-path like '/foo/'. Please use the baseUrl field for sub-paths."`,
);
});
it('throws for non-string base URLs', () => {
expect(() =>
normalizeConfig({
// @ts-expect-error: test
baseUrl: 1,
}),
).toThrowErrorMatchingInlineSnapshot(`
""baseUrl" must be a string
"
`);
});
it('normalizes various base URLs', () => {
expect(
normalizeConfig({ normalizeConfig({
baseUrl: 'noSlash', baseUrl: 'noSlash',
}); }).baseUrl,
}).toThrowErrorMatchingSnapshot(); ).toBe('/noSlash/');
expect(
normalizeConfig({
baseUrl: '/noSlash',
}).baseUrl,
).toBe('/noSlash/');
expect(
normalizeConfig({
baseUrl: 'noSlash/foo',
}).baseUrl,
).toBe('/noSlash/foo/');
}); });
it.each([ it.each([
@ -342,7 +399,7 @@ describe('config warnings', () => {
expect(warning).toBeDefined(); expect(warning).toBeDefined();
expect(warning.details).toHaveLength(1); expect(warning.details).toHaveLength(1);
expect(warning.details[0]!.message).toMatchInlineSnapshot( expect(warning.details[0]!.message).toMatchInlineSnapshot(
`"Docusaurus config validation warning. Field "url": the url is not supposed to contain a sub-path like '/someSubpath', please use the baseUrl field for sub-paths"`, `"Docusaurus config validation warning. Field "url": The url is not supposed to contain a sub-path like '/someSubpath'. Please use the baseUrl field for sub-paths."`,
); );
}); });
}); });

View file

@ -8,6 +8,9 @@
import { import {
DEFAULT_STATIC_DIR_NAME, DEFAULT_STATIC_DIR_NAME,
DEFAULT_I18N_DIR_NAME, DEFAULT_I18N_DIR_NAME,
addLeadingSlash,
addTrailingSlash,
removeTrailingSlash,
} from '@docusaurus/utils'; } from '@docusaurus/utils';
import {Joi, URISchema, printWarning} from '@docusaurus/utils-validation'; import {Joi, URISchema, printWarning} from '@docusaurus/utils-validation';
import type {DocusaurusConfig, I18nConfig} from '@docusaurus/types'; import type {DocusaurusConfig, I18nConfig} from '@docusaurus/types';
@ -149,24 +152,23 @@ const I18N_CONFIG_SCHEMA = Joi.object<I18nConfig>({
.optional() .optional()
.default(DEFAULT_I18N_CONFIG); .default(DEFAULT_I18N_CONFIG);
const SiteUrlSchema = URISchema.required().custom((value: unknown, helpers) => { const SiteUrlSchema = URISchema.required().custom((value: string, helpers) => {
try { try {
const {pathname} = new URL(String(value)); const {pathname} = new URL(String(value));
if (pathname !== '/') { if (pathname !== '/') {
helpers.warn('docusaurus.configValidationWarning', { helpers.warn('docusaurus.configValidationWarning', {
warningMessage: `the url is not supposed to contain a sub-path like '${pathname}', please use the baseUrl field for sub-paths`, warningMessage: `The url is not supposed to contain a sub-path like '${pathname}'. Please use the baseUrl field for sub-paths.`,
}); });
} }
} catch {} } catch {}
return value; return removeTrailingSlash(value);
}, 'siteUrlCustomValidation'); });
// TODO move to @docusaurus/utils-validation // TODO move to @docusaurus/utils-validation
export const ConfigSchema = Joi.object<DocusaurusConfig>({ export const ConfigSchema = Joi.object<DocusaurusConfig>({
baseUrl: Joi.string() baseUrl: Joi.string()
.required() .required()
.regex(/\/$/m) .custom((value: string) => addLeadingSlash(addTrailingSlash(value))),
.message('{{#label}} must be a string with a trailing slash.'),
baseUrlIssueBanner: Joi.boolean().default(DEFAULT_CONFIG.baseUrlIssueBanner), baseUrlIssueBanner: Joi.boolean().default(DEFAULT_CONFIG.baseUrlIssueBanner),
favicon: Joi.string().optional(), favicon: Joi.string().optional(),
title: Joi.string().required(), title: Joi.string().required(),