"use strict"; const _ = require(`lodash`); const semver = require(`semver`); const stringSimiliarity = require(`string-similarity`); const { version: gatsbyVersion } = require(`gatsby/package.json`); const reporter = require(`gatsby-cli/lib/reporter`); const resolveModuleExports = require(`../resolve-module-exports`); const getGatsbyUpgradeVersion = entries => entries.reduce((version, entry) => { if (entry.api && entry.api.version) { return semver.gt(entry.api.version, version || `0.0.0`) ? entry.api.version : version; } return version; }, ``); // Given a plugin object, an array of the API names it exports and an // array of valid API names, return an array of invalid API exports. const getBadExports = (plugin, pluginAPIKeys, apis) => { let badExports = []; // Discover any exports from plugins which are not "known" badExports = badExports.concat(_.difference(pluginAPIKeys, apis).map(e => { return { exportName: e, pluginName: plugin.name, pluginVersion: plugin.version }; })); return badExports; }; const getErrorContext = (badExports, exportType, currentAPIs, latestAPIs) => { const entries = badExports.map(ex => { return Object.assign({}, ex, { api: latestAPIs[exportType][ex.exportName] }); }); const gatsbyUpgradeVersion = getGatsbyUpgradeVersion(entries); let errors = []; let fixes = [].concat(gatsbyUpgradeVersion ? [`npm install gatsby@^${gatsbyUpgradeVersion}`] : []); entries.forEach(entry => { const similarities = stringSimiliarity.findBestMatch(entry.exportName, currentAPIs[exportType]); const isDefaultPlugin = entry.pluginName == `default-site-plugin`; const message = entry.api ? entry.api.version ? `was introduced in gatsby@${entry.api.version}` : `is not available in your version of Gatsby` : `is not a known API`; if (isDefaultPlugin) { errors.push(`- Your local gatsby-${exportType}.js is using the API "${entry.exportName}" which ${message}.`); } else { errors.push(`- The plugin ${entry.pluginName}@${entry.pluginVersion} is using the API "${entry.exportName}" which ${message}.`); } if (similarities.bestMatch.rating > 0.5) { fixes.push(`Rename "${entry.exportName}" -> "${similarities.bestMatch.target}"`); } }); return { errors, entries, exportType, fixes, // note: this is a fallback if gatsby-cli is not updated with structured error sourceMessage: [`Your plugins must export known APIs from their gatsby-node.js.`].concat(errors).concat(fixes.length > 0 && [`\n`, `Some of the following may help fix the error(s):`, ...fixes]).filter(Boolean).join(`\n`) }; }; const handleBadExports = ({ currentAPIs, latestAPIs, badExports }) => { // Output error messages for all bad exports _.toPairs(badExports).forEach(badItem => { const [exportType, entries] = badItem; if (entries.length > 0) { const context = getErrorContext(entries, exportType, currentAPIs, latestAPIs); reporter.error({ id: `11329`, context }); } }); }; /** * Identify which APIs each plugin exports */ const collatePluginAPIs = ({ currentAPIs, flattenedPlugins }) => { // Get a list of bad exports const badExports = { node: [], browser: [], ssr: [] }; flattenedPlugins.forEach(plugin => { plugin.nodeAPIs = []; plugin.browserAPIs = []; plugin.ssrAPIs = []; // Discover which APIs this plugin implements and store an array against // the plugin node itself *and* in an API to plugins map for faster lookups // later. const pluginNodeExports = resolveModuleExports(`${plugin.resolve}/gatsby-node`, { mode: `require` }); const pluginBrowserExports = resolveModuleExports(`${plugin.resolve}/gatsby-browser`); const pluginSSRExports = resolveModuleExports(`${plugin.resolve}/gatsby-ssr`); if (pluginNodeExports.length > 0) { plugin.nodeAPIs = _.intersection(pluginNodeExports, currentAPIs.node); badExports.node = badExports.node.concat(getBadExports(plugin, pluginNodeExports, currentAPIs.node)); // Collate any bad exports } if (pluginBrowserExports.length > 0) { plugin.browserAPIs = _.intersection(pluginBrowserExports, currentAPIs.browser); badExports.browser = badExports.browser.concat(getBadExports(plugin, pluginBrowserExports, currentAPIs.browser)); // Collate any bad exports } if (pluginSSRExports.length > 0) { plugin.ssrAPIs = _.intersection(pluginSSRExports, currentAPIs.ssr); badExports.ssr = badExports.ssr.concat(getBadExports(plugin, pluginSSRExports, currentAPIs.ssr)); // Collate any bad exports } }); return { flattenedPlugins, badExports }; }; const handleMultipleReplaceRenderers = ({ flattenedPlugins }) => { // multiple replaceRenderers may cause problems at build time const rendererPlugins = flattenedPlugins.filter(plugin => plugin.ssrAPIs.includes(`replaceRenderer`)).map(plugin => plugin.name); if (rendererPlugins.length > 1) { if (rendererPlugins.includes(`default-site-plugin`)) { reporter.warn(`replaceRenderer API found in these plugins:`); reporter.warn(rendererPlugins.join(`, `)); reporter.warn(`This might be an error, see: https://www.gatsbyjs.org/docs/debugging-replace-renderer-api/`); } else { console.log(``); reporter.error(`Gatsby's replaceRenderer API is implemented by multiple plugins:`); reporter.error(rendererPlugins.join(`, `)); reporter.error(`This will break your build`); reporter.error(`See: https://www.gatsbyjs.org/docs/debugging-replace-renderer-api/`); if (process.env.NODE_ENV === `production`) process.exit(1); } // Now update plugin list so only final replaceRenderer will run const ignorable = rendererPlugins.slice(0, -1); // For each plugin in ignorable, set a skipSSR flag to true // This prevents apiRunnerSSR() from attempting to run it later const messages = []; flattenedPlugins.forEach((fp, i) => { if (ignorable.includes(fp.name)) { messages.push(`Duplicate replaceRenderer found, skipping gatsby-ssr.js for plugin: ${fp.name}`); flattenedPlugins[i].skipSSR = true; } }); if (messages.length > 0) { console.log(``); messages.forEach(m => reporter.warn(m)); console.log(``); } } return flattenedPlugins; }; function warnOnIncompatiblePeerDependency(name, packageJSON) { // Note: In the future the peer dependency should be enforced for all plugins. const gatsbyPeerDependency = _.get(packageJSON, `peerDependencies.gatsby`); if (gatsbyPeerDependency && !semver.satisfies(gatsbyVersion, gatsbyPeerDependency, { includePrerelease: true })) { reporter.warn(`Plugin ${name} is not compatible with your gatsby version ${gatsbyVersion} - It requires gatsby@${gatsbyPeerDependency}`); } } module.exports = { collatePluginAPIs, handleBadExports, handleMultipleReplaceRenderers, warnOnIncompatiblePeerDependency }; //# sourceMappingURL=validate.js.map