diff --git a/packages/gatsby-cli/src/structured-errors/error-map.ts b/packages/gatsby-cli/src/structured-errors/error-map.ts index 3ba19836f5066..a4319bfeeb7bb 100644 --- a/packages/gatsby-cli/src/structured-errors/error-map.ts +++ b/packages/gatsby-cli/src/structured-errors/error-map.ts @@ -244,6 +244,12 @@ const errors = { level: Level.ERROR, }, // Config errors + "10122": { + text: (context): string => + `The site's gatsby-config.js failed validation:\n\n${context.sourceMessage}`, + type: Type.CONFIG, + level: Level.ERROR, + }, "10123": { text: (context): string => `We encountered an error while trying to load your site's ${context.configName}. Please fix the error and try again.`, diff --git a/packages/gatsby/src/bootstrap/index.js b/packages/gatsby/src/bootstrap/index.js index 51b2467582ec2..397b9406c8cb8 100644 --- a/packages/gatsby/src/bootstrap/index.js +++ b/packages/gatsby/src/bootstrap/index.js @@ -15,6 +15,7 @@ import { getBrowsersList } from "../utils/browserslist" import { createSchemaCustomization } from "../utils/create-schema-customization" import { startPluginRunner } from "../redux/plugin-runner" const { store, emitter } = require(`../redux`) +import { internalActions } from "../redux/actions" const loadPlugins = require(`./load-plugins`) const loadThemes = require(`./load-themes`) const report = require(`gatsby-cli/lib/reporter`) @@ -154,10 +155,7 @@ module.exports = async (args: BootstrapArgs) => { ) } - store.dispatch({ - type: `SET_SITE_CONFIG`, - payload: config, - }) + store.dispatch(internalActions.setSiteConfig(config)) activity.end() diff --git a/packages/gatsby/src/joi-schemas/joi.ts b/packages/gatsby/src/joi-schemas/joi.ts index 9f626f19dfb42..597b0998b20dc 100644 --- a/packages/gatsby/src/joi-schemas/joi.ts +++ b/packages/gatsby/src/joi-schemas/joi.ts @@ -32,6 +32,9 @@ export const gatsbyConfigSchema: Joi.ObjectSchema = Joi.object() .replace(/^\/$/, ``) ) ), + linkPrefix: Joi.forbidden().error( + new Error(`"linkPrefix" should be changed to "pathPrefix"`) + ), siteMetadata: Joi.object({ siteUrl: stripTrailingSlash(Joi.string()).uri(), }).unknown(), diff --git a/packages/gatsby/src/redux/actions/__tests__/internal.ts b/packages/gatsby/src/redux/actions/__tests__/internal.ts new file mode 100644 index 0000000000000..2afce414ce42d --- /dev/null +++ b/packages/gatsby/src/redux/actions/__tests__/internal.ts @@ -0,0 +1,88 @@ +import { setSiteConfig } from "../internal" +import reporter from "gatsby-cli/lib/reporter" + +jest.mock(`gatsby-cli/lib/reporter`, () => { + return { + panic: jest.fn(), + } +}) + +beforeEach(() => { + reporter.panic.mockClear() +}) + +describe(`setSiteConfig`, () => { + it(`let's you add a config`, () => { + const action = setSiteConfig({ + siteMetadata: { + hi: true, + }, + }) + expect(action).toMatchInlineSnapshot(` + Object { + "payload": Object { + "pathPrefix": "", + "polyfill": true, + "siteMetadata": Object { + "hi": true, + }, + }, + "type": "SET_SITE_CONFIG", + } + `) + }) + + it(`handles empty configs`, () => { + const action = setSiteConfig() + expect(action).toMatchInlineSnapshot(` + Object { + "payload": Object { + "pathPrefix": "", + "polyfill": true, + }, + "type": "SET_SITE_CONFIG", + } + `) + }) + + it(`Validates configs with unsupported options`, () => { + setSiteConfig({ + someRandomThing: `hi people`, + plugins: [], + }) + + expect(reporter.panic).toBeCalledWith({ + id: `10122`, + context: { + sourceMessage: `"someRandomThing" is not allowed`, + }, + }) + }) + + it(`It corrects pathPrefixes without a forward slash at beginning`, () => { + const action = setSiteConfig({ + pathPrefix: `prefix`, + }) + + expect(action.payload.pathPrefix).toBe(`/prefix`) + }) + + it(`It removes trailing forward slash`, () => { + const action = setSiteConfig({ + pathPrefix: `/prefix/`, + }) + expect(action.payload.pathPrefix).toBe(`/prefix`) + }) + + it(`It removes pathPrefixes that are a single forward slash`, () => { + const action = setSiteConfig({ + pathPrefix: `/`, + }) + expect(action.payload.pathPrefix).toBe(``) + }) + + it(`It sets the pathPrefix to an empty string if it's not set`, () => { + const action = setSiteConfig({}) + expect(action.payload.pathPrefix).toBe(``) + }) +}) diff --git a/packages/gatsby/src/redux/actions/internal.ts b/packages/gatsby/src/redux/actions/internal.ts index c5bf17e419fb4..c43aef4ca19d0 100644 --- a/packages/gatsby/src/redux/actions/internal.ts +++ b/packages/gatsby/src/redux/actions/internal.ts @@ -1,4 +1,7 @@ +import reporter from "gatsby-cli/lib/reporter" + import { + IGatsbyConfig, IGatsbyPlugin, ProgramStatus, ICreatePageDependencyAction, @@ -12,8 +15,11 @@ import { ISetProgramStatusAction, IPageQueryRunAction, IRemoveStaleJobAction, + ISetSiteConfig, } from "../types" +import { gatsbyConfigSchema } from "../../joi-schemas/joi" + /** * Create a dependency between a page and data. Probably for * internal use only. @@ -217,3 +223,26 @@ export const removeStaleJob = ( }, } } + +/** + * Set gatsby config + * @private + */ +export const setSiteConfig = (config?: unknown): ISetSiteConfig => { + const result = gatsbyConfigSchema.validate(config || {}) + const normalizedPayload: IGatsbyConfig = result.value + + if (result.error) { + reporter.panic({ + id: `10122`, + context: { + sourceMessage: result.error.message, + }, + }) + } + + return { + type: `SET_SITE_CONFIG`, + payload: normalizedPayload, + } +} diff --git a/packages/gatsby/src/redux/reducers/__tests__/__snapshots__/config.js.snap b/packages/gatsby/src/redux/reducers/__tests__/__snapshots__/config.js.snap deleted file mode 100644 index ab1611e5df40d..0000000000000 --- a/packages/gatsby/src/redux/reducers/__tests__/__snapshots__/config.js.snap +++ /dev/null @@ -1,20 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`config reducer Validates configs with unsupported options 1`] = `"The site's gatsby-config.js failed validation"`; - -exports[`config reducer handles empty configs 1`] = ` -Object { - "pathPrefix": "", - "polyfill": true, -} -`; - -exports[`config reducer let's you add a config 1`] = ` -Object { - "pathPrefix": "", - "polyfill": true, - "siteMetadata": Object { - "hi": true, - }, -} -`; diff --git a/packages/gatsby/src/redux/reducers/__tests__/config.js b/packages/gatsby/src/redux/reducers/__tests__/config.js deleted file mode 100644 index 1dd62dcda4520..0000000000000 --- a/packages/gatsby/src/redux/reducers/__tests__/config.js +++ /dev/null @@ -1,81 +0,0 @@ -const reducer = require(`../config`) - -describe(`config reducer`, () => { - beforeEach(() => { - jest.resetModules() - }) - - it(`let's you add a config`, () => { - const action = { - type: `SET_SITE_CONFIG`, - payload: { - siteMetadata: { - hi: true, - }, - }, - } - expect(reducer(undefined, action)).toMatchSnapshot() - }) - - it(`handles empty configs`, () => { - const action = { - type: `SET_SITE_CONFIG`, - } - expect(reducer(undefined, action)).toMatchSnapshot() - }) - - it(`Validates configs with unsupported options`, () => { - const config = { - someRandomThing: `hi people`, - plugins: [], - } - function runReducer() { - return reducer( - {}, - { - type: `SET_SITE_CONFIG`, - payload: config, - } - ) - } - expect(runReducer).toThrowErrorMatchingSnapshot() - }) - - it(`It corrects pathPrefixes without a forward slash at beginning`, () => { - const action = { - type: `SET_SITE_CONFIG`, - payload: { - pathPrefix: `prefix`, - }, - } - expect(reducer(undefined, action).pathPrefix).toBe(`/prefix`) - }) - - it(`It removes trailing forward slash`, () => { - const action = { - type: `SET_SITE_CONFIG`, - payload: { - pathPrefix: `/prefix/`, - }, - } - expect(reducer(undefined, action).pathPrefix).toBe(`/prefix`) - }) - - it(`It removes pathPrefixes that are a single forward slash`, () => { - const action = { - type: `SET_SITE_CONFIG`, - payload: { - pathPrefix: `/`, - }, - } - expect(reducer(undefined, action).pathPrefix).toBe(``) - }) - - it(`It sets the pathPrefix to an empty string if it's not set`, () => { - const action = { - type: `SET_SITE_CONFIG`, - payload: {}, - } - expect(reducer(undefined, action).pathPrefix).toBe(``) - }) -}) diff --git a/packages/gatsby/src/redux/reducers/config.js b/packages/gatsby/src/redux/reducers/config.js deleted file mode 100644 index a32c7e41953da..0000000000000 --- a/packages/gatsby/src/redux/reducers/config.js +++ /dev/null @@ -1,32 +0,0 @@ -const chalk = require(`chalk`) - -const { gatsbyConfigSchema } = require(`../../joi-schemas/joi`) - -module.exports = (state = {}, action) => { - switch (action.type) { - case `SET_SITE_CONFIG`: { - // Validate the config. - const result = gatsbyConfigSchema.validate(action.payload || {}) - - const normalizedPayload = result.value - - // TODO use Redux for capturing errors from different - // parts of Gatsby so a) can capture richer errors and b) be - // more flexible how to display them. - if (result.error) { - console.log( - chalk.blue.bgYellow(`The site's gatsby-config.js failed validation`) - ) - console.log(chalk.bold.red(result.error)) - if (normalizedPayload.linkPrefix) { - console.log(`"linkPrefix" should be changed to "pathPrefix"`) - } - throw new Error(`The site's gatsby-config.js failed validation`) - } - - return normalizedPayload - } - default: - return state - } -} diff --git a/packages/gatsby/src/redux/reducers/config.ts b/packages/gatsby/src/redux/reducers/config.ts new file mode 100644 index 0000000000000..10e7c9557b9a3 --- /dev/null +++ b/packages/gatsby/src/redux/reducers/config.ts @@ -0,0 +1,14 @@ +import { IGatsbyConfig, ISetSiteConfig } from "../types" + +module.exports = ( + state: IGatsbyConfig = {}, + action: ISetSiteConfig +): IGatsbyConfig => { + switch (action.type) { + case `SET_SITE_CONFIG`: { + return action.payload + } + default: + return state + } +} diff --git a/packages/gatsby/src/redux/types.ts b/packages/gatsby/src/redux/types.ts index 5b0c643d6d8d9..5735e25f17911 100644 --- a/packages/gatsby/src/redux/types.ts +++ b/packages/gatsby/src/redux/types.ts @@ -47,6 +47,9 @@ export interface IGatsbyConfig { title?: string author?: string description?: string + sireUrl?: string + // siteMetadata is free form + [key: string]: unknown } // @deprecated polyfill?: boolean @@ -422,3 +425,8 @@ export interface ISetWebpackConfigAction { type: `SET_WEBPACK_CONFIG` payload: Partial } + +export interface ISetSiteConfig { + type: `SET_SITE_CONFIG` + payload: IGatsbyState["config"] +}