diff --git a/packages/gatsby/src/bootstrap/load-plugins/__tests__/load-plugins.ts b/packages/gatsby/src/bootstrap/load-plugins/__tests__/load-plugins.ts index 7cae03721ecc6..6902016fceefa 100644 --- a/packages/gatsby/src/bootstrap/load-plugins/__tests__/load-plugins.ts +++ b/packages/gatsby/src/bootstrap/load-plugins/__tests__/load-plugins.ts @@ -576,6 +576,58 @@ describe(`Load plugins`, () => { expect(mockProcessExit).toHaveBeenCalledWith(1) }) + it(`validates subplugin schemas (if not in options.plugins)`, async () => { + await loadPlugins( + { + plugins: [ + { + resolve: `gatsby-plugin-mdx`, + options: { + gatsbyRemarkPlugins: [ + { + resolve: `gatsby-remark-autolink-headers`, + options: { + maintainCase: `should be boolean`, + }, + }, + ], + }, + }, + ], + }, + process.cwd() + ) + + expect(reporter.error as jest.Mock).toHaveBeenCalledTimes(1) + expect((reporter.error as jest.Mock).mock.calls[0]) + .toMatchInlineSnapshot(` + Array [ + Object { + "context": Object { + "configDir": null, + "pluginName": "gatsby-remark-autolink-headers", + "validationErrors": Array [ + Object { + "context": Object { + "key": "maintainCase", + "label": "maintainCase", + "value": "should be boolean", + }, + "message": "\\"maintainCase\\" must be a boolean", + "path": Array [ + "maintainCase", + ], + "type": "boolean.base", + }, + ], + }, + "id": "11331", + }, + ] + `) + expect(mockProcessExit).toHaveBeenCalledWith(1) + }) + it(`subplugins are resolved using "main" in package.json`, async () => { // in fixtures/subplugins/node_modules/gatsby-plugin-child-with-main/package.json // "main" field points to "lib/index.js" diff --git a/packages/gatsby/src/bootstrap/load-plugins/validate.ts b/packages/gatsby/src/bootstrap/load-plugins/validate.ts index 5e091913c3cf2..6bca15195da53 100644 --- a/packages/gatsby/src/bootstrap/load-plugins/validate.ts +++ b/packages/gatsby/src/bootstrap/load-plugins/validate.ts @@ -181,26 +181,25 @@ export async function handleBadExports({ } } -interface ISubPluginCustomReturn { - resolve: string - modulePath: string - options: { - [key: string]: unknown - } - module: any -} +const addModuleImportAndValidateOptions = + (rootDir: string, incErrors: (inc: number) => void) => + async (value: Array): Promise> => { + for (const plugin of value) { + if (plugin.modulePath) { + const importedModule = await import( + maybeAddFileProtocol(plugin.modulePath) + ) + const pluginModule = preferDefault(importedModule) + plugin.module = pluginModule + } + } -const addModuleImport = async ( - value: Array -): Promise> => { - for (const plugin of value) { - const importedModule = await import(maybeAddFileProtocol(plugin.modulePath)) - const pluginModule = preferDefault(importedModule) - plugin.module = pluginModule - } + const { errors: subErrors, plugins: subPlugins } = + await validatePluginsOptions(value as Array, rootDir) - return value -} + incErrors(subErrors) + return subPlugins + } async function validatePluginsOptions( plugins: Array, @@ -290,7 +289,15 @@ async function validatePluginsOptions( }) }, `Gatsby specific subplugin validation`) .default([]) - .external(addModuleImport, `add module key to subplugin`), + .external( + addModuleImportAndValidateOptions( + rootDir, + (inc: number): void => { + errors += inc + } + ), + `add module key to subplugin` + ), args: (schema: any, args: any): any => { if ( args?.entry && @@ -364,8 +371,8 @@ async function validatePluginsOptions( // We do not increment errors++ here as we do not want to process.exit if there are only warnings } - // Validate subplugins - if (plugin.options?.plugins) { + // Validate subplugins if they weren't handled already + if (!subPluginPaths.has(`plugins`) && plugin.options?.plugins) { const { errors: subErrors, plugins: subPlugins } = await validatePluginsOptions( plugin.options.plugins as Array,