Skip to content

Commit

Permalink
Add support for plugin dependencies (#27995)
Browse files Browse the repository at this point in the history
* feat(create-gatsby): Add support for plugin dependencies

* Fix extra plugin handling
  • Loading branch information
ascorbic committed Nov 12, 2020
1 parent 43ea67b commit 009ed62
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 27 deletions.
52 changes: 45 additions & 7 deletions packages/create-gatsby/src/features.json
@@ -1,9 +1,47 @@
{
"gatsby-plugin-google-analytics": {"message": "Add the Google Analytics tracking script"},
"gatsby-plugin-react-helmet": {"message": "Add page metadata", "dependencies": ["react-helmet"]},
"gatsby-plugin-sitemap": {"message": "Add an automatic sitemap"},
"gatsby-plugin-offline": {"message": "Enable offline functionality"},
"gatsby-plugin-manifest": {"message": "Generate a manifest file"},
"gatsby-plugin-mdx": {"message": "Add MDX support", "dependencies": ["@mdx-js/react", "@mdx-js/mdx"]}

"gatsby-plugin-google-analytics": {
"message": "Add the Google Analytics tracking script"
},
"gatsby-plugin-image": {
"message": "Add responsive images",
"plugins": [
"gatsby-plugin-sharp",
"gatsby-transformer-sharp",
"gatsby-source-filesystem:images"
],
"options": {
"gatsby-source-filesystem:images": {
"name": "images",
"path": "./src/images/"
}
}
},
"gatsby-plugin-react-helmet": {
"message": "Add page meta tags with React Helmet",
"dependencies": ["react-helmet"]
},
"gatsby-plugin-sitemap": { "message": "Add an automatic sitemap" },
"gatsby-plugin-offline": { "message": "Enable offline functionality" },
"gatsby-plugin-manifest": { "message": "Generate a manifest file" },
"gatsby-transformer-remark": {
"message": "Add Markdown support (without MDX)",
"plugins": ["gatsby-source-filesystem:pages"],
"options": {
"gatsby-source-filesystem:pages": {
"name": "pages",
"path": "./src/pages/"
}
}
},
"gatsby-plugin-mdx": {
"message": "Add Markdown and MDX support",
"plugins": ["gatsby-source-filesystem:pages"],
"dependencies": ["@mdx-js/react", "@mdx-js/mdx"],
"options": {
"gatsby-source-filesystem:pages": {
"name": "pages",
"path": "./src/pages/"
}
}
}
}
76 changes: 60 additions & 16 deletions packages/create-gatsby/src/index.ts
Expand Up @@ -10,6 +10,7 @@ import fs from "fs"
import { plugin } from "./components/plugin"
import { makePluginConfigQuestions } from "./plugin-options-form"
import { center, rule, wrap } from "./components/utils"
import { stripIndent } from "common-tags"

// eslint-disable-next-line no-control-regex
const INVALID_FILENAMES = /[<>:"/\\|?*\u0000-\u001F]/g
Expand Down Expand Up @@ -81,7 +82,7 @@ export const questions: any = [
type: `multiselectinput`,
name: `features`,
message: `Would you like to install additional features with other plugins?`,
hint: `(Multiple choice) Use arrow keys to move, spacebar to select, and enter to confirm your choices`,
hint: `(Multiple choice) Use arrow keys to move, enter to select, and choose "Done" to confirm your choices`,
choices: makeChoices(features, true),
},
]
Expand All @@ -93,12 +94,31 @@ interface IAnswers {
}

interface IPluginEntry {
/**
* Message displayed in the menu when selecting the plugin
*/
message: string
/**
* Extra NPM packages to install
*/
dependencies?: Array<string>
/**
* Items are either the plugin name, or the plugin name and key, separated by a colon (":")
* This allows duplicate entries for plugins such as gatsby-source-filesystem.
*/
plugins?: Array<string>
/**
* Keys must match plugin names or name:key combinations from the plugins array
*/
options?: PluginConfigMap
}

export type PluginMap = Record<string, IPluginEntry>

export type PluginConfigMap = Record<string, Record<string, unknown>>

const removeKey = (plugin: string): string => plugin.split(`:`)[0]

export async function run(): Promise<void> {
const { version } = require(`../package.json`)

Expand Down Expand Up @@ -141,15 +161,22 @@ ${center(c.blueBright.bold.underline(`Welcome to Gatsby!`))}

const plugins: Array<string> = []
const packages: Array<string> = []
let pluginConfig: PluginConfigMap = {}

if (data.cms && data.cms !== `none`) {
messages.push(
`📚 Install and configure the plugin for ${c.magenta(
cmses[data.cms].message
)}`
)
plugins.push(data.cms)
packages.push(data.cms, ...(cmses[data.cms].dependencies || []))
const extraPlugins = cmses[data.cms].plugins || []
plugins.push(data.cms, ...extraPlugins)
packages.push(
data.cms,
...(cmses[data.cms].dependencies || []),
...extraPlugins
)
pluginConfig = { ...pluginConfig, ...cmses[data.cms].options }
}

if (data.styling && data.styling !== `none`) {
Expand All @@ -158,8 +185,15 @@ ${center(c.blueBright.bold.underline(`Welcome to Gatsby!`))}
styles[data.styling].message
)} for styling your site`
)
plugins.push(data.styling)
packages.push(data.styling, ...(styles[data.styling].dependencies || []))
const extraPlugins = styles[data.styling].plugins || []

plugins.push(data.styling, ...extraPlugins)
packages.push(
data.styling,
...(styles[data.styling].dependencies || []),
...extraPlugins
)
pluginConfig = { ...pluginConfig, ...styles[data.styling].options }
}

if (data.features?.length) {
Expand All @@ -169,27 +203,37 @@ ${center(c.blueBright.bold.underline(`Welcome to Gatsby!`))}
.join(`, `)}`
)
plugins.push(...data.features)
const featureDependencies = data.features?.map(
featureKey => features[featureKey].dependencies || []
)
const featureDependencies = data.features?.map(featureKey => {
const extraPlugins = features[featureKey].plugins || []
plugins.push(...extraPlugins)
return [
// Spread in extra dependencies
...(features[featureKey].dependencies || []),
// Spread in plugins
...extraPlugins,
]
})
const flattenedDependencies = ([] as Array<string>).concat.apply(
[],
featureDependencies
) // here until we upgrade to node 11 and can use flatMap

packages.push(...data.features, ...flattenedDependencies)
// Merge plugin options
pluginConfig = data.features.reduce((prev, key) => {
return { ...prev, ...features[key].options }
}, pluginConfig)
}

const config = makePluginConfigQuestions(plugins)
let pluginConfig
if (config.length) {
console.log(
`\nGreat! A few of the selections you made need to be configured. Please fill in the options for each plugin now:\n`
)

const enquirer = new Enquirer<Record<string, {}>>()
enquirer.use(plugin)
pluginConfig = await enquirer.prompt(config)
pluginConfig = { ...pluginConfig, ...(await enquirer.prompt(config)) }
}

console.log(`
Expand All @@ -212,10 +256,10 @@ ${c.bold(`Thanks! Here's what we'll now do:`)}
return
}

await initStarter(DEFAULT_STARTER, data.project, packages)
await initStarter(DEFAULT_STARTER, data.project, packages.map(removeKey))

console.log(c.green(`✔ `) + `Created site in ` + c.green(data.project))

console.log({ plugins, pluginConfig })
if (plugins.length) {
console.log(c.bold(`🔌 Installing plugins...`))
await installPlugins(plugins, pluginConfig, path.resolve(data.project), [])
Expand All @@ -226,11 +270,11 @@ ${c.bold(`Thanks! Here's what we'll now do:`)}
const runCommand = pm === `npm` ? `npm run` : `yarn`

console.log(
`🎉 Your new Gatsby site ${c.bold(
stripIndent`
🎉 Your new Gatsby site ${c.bold(
data.project
)} has been successfully bootstrapped at ${c.bold(
path.resolve(data.project)
)}.
)} has been successfully bootstrapped
at ${c.bold(path.resolve(data.project))}.
`
)
console.log(`Start by going to the directory with\n
Expand Down
3 changes: 2 additions & 1 deletion packages/create-gatsby/src/install-plugins.ts
@@ -1,8 +1,9 @@
import { reporter } from "./reporter"
import path from "path"
import { PluginConfigMap } from "."
export async function installPlugins(
plugins: Array<string>,
pluginOptions: Record<string, Record<string, any> | undefined> = {},
pluginOptions: PluginConfigMap = {},
rootPath: string,
packages: Array<string>
): Promise<void> {
Expand Down
2 changes: 1 addition & 1 deletion packages/create-gatsby/src/plugin-options-form.ts
Expand Up @@ -56,7 +56,7 @@ export const makePluginConfigQuestions = (

selectedPlugins.forEach((pluginName: string): void => {
const schema = pluginSchemas[pluginName as PluginName]
if (typeof schema === `string` || !(`keys` in schema)) {
if (!schema || typeof schema === `string` || !(`keys` in schema)) {
return
}
const options: Record<string, Schema> | undefined = schema?.keys
Expand Down
7 changes: 5 additions & 2 deletions packages/gatsby-cli/src/plugin-add.ts
Expand Up @@ -37,16 +37,19 @@ async function installPluginConfig(
options: Record<string, unknown> | undefined,
root: string
): Promise<void> {
// Plugins can optionally include a key, to allow duplicates
const [pluginName, pluginKey] = plugin.split(`:`)

const installTimer = reporter.activityTimer(
`Adding ${plugin} to gatsby-config`
)

installTimer.start()
reporter.info(`Adding ${plugin}`)
reporter.info(`Adding ${pluginName}`)
try {
const result = await GatsbyPlugin.create(
{ root },
{ name: plugin, options }
{ name: pluginName, options, key: pluginKey }
)
reporter.info(result._message)
} catch (err) {
Expand Down

0 comments on commit 009ed62

Please sign in to comment.