Skip to content

Commit

Permalink
Add browserAPIs and ssrAPIs to loaded plugin info (#3989)
Browse files Browse the repository at this point in the history
* Add browserAPIs and ssrAPIs to loaded plugin info

* Output correct error message for each bad export

* Use lodash for Node 6 compatibility
  • Loading branch information
m-allanson authored and KyleAMathews committed Feb 12, 2018
1 parent 10e6702 commit 0414068
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
exports[`Load plugins Loads plugins defined with an object but without an option key 1`] = `
Array [
Object {
"browserAPIs": Array [],
"id": "Plugin dev-404-page",
"name": "dev-404-page",
"nodeAPIs": Array [
Expand All @@ -12,9 +13,11 @@ Array [
"plugins": Array [],
},
"resolve": "",
"ssrAPIs": Array [],
"version": "1.0.0",
},
Object {
"browserAPIs": Array [],
"id": "Plugin component-page-creator",
"name": "component-page-creator",
"nodeAPIs": Array [
Expand All @@ -24,9 +27,11 @@ Array [
"plugins": Array [],
},
"resolve": "",
"ssrAPIs": Array [],
"version": "1.0.0",
},
Object {
"browserAPIs": Array [],
"id": "Plugin component-layout-creator",
"name": "component-layout-creator",
"nodeAPIs": Array [
Expand All @@ -36,9 +41,11 @@ Array [
"plugins": Array [],
},
"resolve": "",
"ssrAPIs": Array [],
"version": "1.0.0",
},
Object {
"browserAPIs": Array [],
"id": "Plugin internal-data-bridge",
"name": "internal-data-bridge",
"nodeAPIs": Array [
Expand All @@ -49,9 +56,11 @@ Array [
"plugins": Array [],
},
"resolve": "",
"ssrAPIs": Array [],
"version": "1.0.0",
},
Object {
"browserAPIs": Array [],
"id": "Plugin prod-404",
"name": "prod-404",
"nodeAPIs": Array [
Expand All @@ -61,9 +70,11 @@ Array [
"plugins": Array [],
},
"resolve": "",
"ssrAPIs": Array [],
"version": "1.0.0",
},
Object {
"browserAPIs": Array [],
"id": "Plugin query-runner",
"name": "query-runner",
"nodeAPIs": Array [
Expand All @@ -74,24 +85,29 @@ Array [
"plugins": Array [],
},
"resolve": "",
"ssrAPIs": Array [],
"version": "1.0.0",
},
Object {
"browserAPIs": Array [],
"name": "TEST",
"nodeAPIs": Array [],
"pluginOptions": Object {
"plugins": Array [],
},
"resolve": "",
"ssrAPIs": Array [],
},
Object {
"browserAPIs": Array [],
"id": "Plugin default-site-plugin",
"name": "default-site-plugin",
"nodeAPIs": Array [],
"pluginOptions": Object {
"plugins": Array [],
},
"resolve": "",
"ssrAPIs": Array [],
"version": "d41d8cd98f00b204e9800998ecf8427e",
},
]
Expand All @@ -100,6 +116,7 @@ Array [
exports[`Load plugins load plugins for a site 1`] = `
Array [
Object {
"browserAPIs": Array [],
"id": "Plugin dev-404-page",
"name": "dev-404-page",
"nodeAPIs": Array [
Expand All @@ -109,9 +126,11 @@ Array [
"plugins": Array [],
},
"resolve": "",
"ssrAPIs": Array [],
"version": "1.0.0",
},
Object {
"browserAPIs": Array [],
"id": "Plugin component-page-creator",
"name": "component-page-creator",
"nodeAPIs": Array [
Expand All @@ -121,9 +140,11 @@ Array [
"plugins": Array [],
},
"resolve": "",
"ssrAPIs": Array [],
"version": "1.0.0",
},
Object {
"browserAPIs": Array [],
"id": "Plugin component-layout-creator",
"name": "component-layout-creator",
"nodeAPIs": Array [
Expand All @@ -133,9 +154,11 @@ Array [
"plugins": Array [],
},
"resolve": "",
"ssrAPIs": Array [],
"version": "1.0.0",
},
Object {
"browserAPIs": Array [],
"id": "Plugin internal-data-bridge",
"name": "internal-data-bridge",
"nodeAPIs": Array [
Expand All @@ -146,9 +169,11 @@ Array [
"plugins": Array [],
},
"resolve": "",
"ssrAPIs": Array [],
"version": "1.0.0",
},
Object {
"browserAPIs": Array [],
"id": "Plugin prod-404",
"name": "prod-404",
"nodeAPIs": Array [
Expand All @@ -158,9 +183,11 @@ Array [
"plugins": Array [],
},
"resolve": "",
"ssrAPIs": Array [],
"version": "1.0.0",
},
Object {
"browserAPIs": Array [],
"id": "Plugin query-runner",
"name": "query-runner",
"nodeAPIs": Array [
Expand All @@ -171,16 +198,19 @@ Array [
"plugins": Array [],
},
"resolve": "",
"ssrAPIs": Array [],
"version": "1.0.0",
},
Object {
"browserAPIs": Array [],
"id": "Plugin default-site-plugin",
"name": "default-site-plugin",
"nodeAPIs": Array [],
"pluginOptions": Object {
"plugins": Array [],
},
"resolve": "",
"ssrAPIs": Array [],
"version": "d41d8cd98f00b204e9800998ecf8427e",
},
]
Expand Down
178 changes: 116 additions & 62 deletions packages/gatsby/src/bootstrap/load-plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,80 @@ const nodeAPIs = require(`../utils/api-node-docs`)
const testRequireError = require(`../utils/test-require-error`)
const report = require(`gatsby-cli/lib/reporter`)

// Given a plugin object and a moduleName like `gatsby-node`, check that the
// path to moduleName can be resolved.
const resolvePluginModule = (plugin, moduleName) => {
let resolved = false
try {
resolved = require(`${plugin.resolve}/${moduleName}`)
} catch (err) {
if (!testRequireError(moduleName, err)) {
// ignore
} else {
report.panic(`Error requiring ${plugin.resolve}/${moduleName}.js`, err)
}
}
return resolved
}

// 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 getBadExportsMessage = (badExports, exportType, apis) => {
const { stripIndent } = require(`common-tags`)
const stringSimiliarity = require(`string-similarity`)
let capitalized = `${exportType[0].toUpperCase()}${exportType.slice(1)}`
if (capitalized === `Ssr`) capitalized = `SSR`

let message = `\n`
message += stripIndent`
Your plugins must export known APIs from their gatsby-${exportType}.js.
The following exports aren't APIs. Perhaps you made a typo or
your plugin is outdated?
See https://www.gatsbyjs.org/docs/${exportType}-apis/ for the list of Gatsby ${capitalized} APIs`

badExports.forEach(bady => {
const similarities = stringSimiliarity.findBestMatch(
bady.exportName,
apis
)
message += `\n — `
if (bady.pluginName == `default-site-plugin`) {
message += `Your site's gatsby-${exportType}.js is exporting a variable named "${
bady.exportName
}" which isn't an API.`
} else {
message += `The plugin "${bady.pluginName}@${
bady.pluginVersion
}" is exporting a variable named "${
bady.exportName
}" which isn't an API.`
}
if (similarities.bestMatch.rating > 0.5) {
message += ` Perhaps you meant to export "${
similarities.bestMatch.target
}"?`
}
})

return message
}

function createFileContentHash(root, globPattern) {
const hash = crypto.createHash(`md5`)
const files = glob.sync(`${root}/${globPattern}`, { nodir: true })
Expand Down Expand Up @@ -201,79 +275,59 @@ module.exports = async (config = {}) => {
acc[value] = []
return acc
}, {})
let badExports = []


const badExports = {
node: [],
browser: [],
ssr: [],
}

flattenedPlugins.forEach(plugin => {
let gatsbyNode
plugin.nodeAPIs = []
try {
gatsbyNode = require(`${plugin.resolve}/gatsby-node`)
} catch (err) {
if (!testRequireError(`gatsby-node`, err)) {
// ignore
} else {
report.panic(`Error requiring ${plugin.resolve}/gatsby-node.js`, err)
}
}
plugin.browserAPIs = []
plugin.ssrAPIs = []

const gatsbyNode = resolvePluginModule(plugin, `gatsby-node`)
const gatsbyBrowser = resolvePluginModule(plugin, `gatsby-browser`)
const gatsbySSR = resolvePluginModule(plugin, `gatsby-ssr`)

// 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.
if (gatsbyNode) {
const gatsbyNodeKeys = _.keys(gatsbyNode)
// Discover which nodeAPIs this plugin implements and store
// an array against the plugin node itself *and* in a node
// API to plugins map for faster lookups later.
plugin.nodeAPIs = _.intersection(gatsbyNodeKeys, apis)
plugin.nodeAPIs.map(nodeAPI => apiToPlugins[nodeAPI].push(plugin.name))
// Discover any exports from plugins which are not "known"
badExports = badExports.concat(
_.difference(gatsbyNodeKeys, apis).map(e => {
return {
exportName: e,
pluginName: plugin.name,
pluginVersion: plugin.version,
}
})
)
badExports.node = getBadExports(plugin, gatsbyNodeKeys, apis) // Collate any bad exports
}

if (gatsbyBrowser) {
const gatsbyBrowserKeys = _.keys(gatsbyBrowser)
plugin.browserAPIs = _.intersection(gatsbyBrowserKeys, apis)
plugin.browserAPIs.map(browserAPI => apiToPlugins[browserAPI].push(plugin.name))
badExports.browser = getBadExports(plugin, gatsbyBrowserKeys, apis) // Collate any bad exports
}

if (gatsbySSR) {
const gatsbySSRKeys = _.keys(gatsbySSR)
plugin.ssrAPIs = _.intersection(gatsbySSRKeys, apis)
plugin.ssrAPIs.map(ssrAPI => apiToPlugins[ssrAPI].push(plugin.name))
badExports.ssr = getBadExports(plugin, gatsbySSRKeys, apis) // Collate any bad exports
}
})

if (badExports.length > 0) {
const stringSimiliarity = require(`string-similarity`)
const { stripIndent } = require(`common-tags`)
console.log(`\n`)
console.log(
stripIndent`
Your plugins must export known APIs from their gatsby-node.js.
The following exports aren't APIs. Perhaps you made a typo or
your plugin is outdated?
See https://www.gatsbyjs.org/docs/node-apis/ for the list of Gatsby Node APIs`
)
badExports.forEach(bady => {
const similarities = stringSimiliarity.findBestMatch(
bady.exportName,
apis
)
let message = `\n — `
if (bady.pluginName == `default-site-plugin`) {
message += `Your site's gatsby-node.js is exporting a variable named "${
bady.exportName
}" which isn't an API.`
} else {
message += `The plugin "${bady.pluginName}@${
bady.pluginVersion
}" is exporting a variable named "${
bady.exportName
}" which isn't an API.`
}
if (similarities.bestMatch.rating > 0.5) {
message += ` Perhaps you meant to export "${
similarities.bestMatch.target
}"?`
}
// Output error messages for all bad exports
let bad = false
_.toPairs(badExports).forEach(bad => {
const [exportType, entries] = bad
if (entries.length > 0) {
bad = true
console.log(getBadExportsMessage(entries, exportType, apis))
}
})

console.log(message)
})
process.exit()
}
if (bad) process.exit()

store.dispatch({
type: `SET_SITE_PLUGINS`,
Expand Down

0 comments on commit 0414068

Please sign in to comment.