Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for plugin dependencies #27995

Merged
merged 3 commits into from Nov 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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