diff --git a/packages/create-gatsby/src/features.json b/packages/create-gatsby/src/features.json index 8bc78252ff1dd..7087fe6617531 100644 --- a/packages/create-gatsby/src/features.json +++ b/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/" + } + } + } } diff --git a/packages/create-gatsby/src/index.ts b/packages/create-gatsby/src/index.ts index 48a3b5b2ee882..f2b2f62082d64 100644 --- a/packages/create-gatsby/src/index.ts +++ b/packages/create-gatsby/src/index.ts @@ -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 @@ -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), }, ] @@ -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 + /** + * 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 + /** + * Keys must match plugin names or name:key combinations from the plugins array + */ + options?: PluginConfigMap } export type PluginMap = Record +export type PluginConfigMap = Record> + +const removeKey = (plugin: string): string => plugin.split(`:`)[0] + export async function run(): Promise { const { version } = require(`../package.json`) @@ -141,6 +161,7 @@ ${center(c.blueBright.bold.underline(`Welcome to Gatsby!`))} const plugins: Array = [] const packages: Array = [] + let pluginConfig: PluginConfigMap = {} if (data.cms && data.cms !== `none`) { messages.push( @@ -148,8 +169,14 @@ ${center(c.blueBright.bold.underline(`Welcome to Gatsby!`))} 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`) { @@ -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) { @@ -169,19 +203,29 @@ ${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).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` @@ -189,7 +233,7 @@ ${center(c.blueBright.bold.underline(`Welcome to Gatsby!`))} const enquirer = new Enquirer>() enquirer.use(plugin) - pluginConfig = await enquirer.prompt(config) + pluginConfig = { ...pluginConfig, ...(await enquirer.prompt(config)) } } console.log(` @@ -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), []) @@ -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 diff --git a/packages/create-gatsby/src/install-plugins.ts b/packages/create-gatsby/src/install-plugins.ts index a360e07f1db53..f87a5d8d4fe64 100644 --- a/packages/create-gatsby/src/install-plugins.ts +++ b/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, - pluginOptions: Record | undefined> = {}, + pluginOptions: PluginConfigMap = {}, rootPath: string, packages: Array ): Promise { diff --git a/packages/create-gatsby/src/plugin-options-form.ts b/packages/create-gatsby/src/plugin-options-form.ts index 8e2e12d90c4bb..5081e51ce51a2 100644 --- a/packages/create-gatsby/src/plugin-options-form.ts +++ b/packages/create-gatsby/src/plugin-options-form.ts @@ -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 | undefined = schema?.keys diff --git a/packages/gatsby-cli/src/plugin-add.ts b/packages/gatsby-cli/src/plugin-add.ts index ac98d5fa2e61d..3cd2c111650f8 100644 --- a/packages/gatsby-cli/src/plugin-add.ts +++ b/packages/gatsby-cli/src/plugin-add.ts @@ -37,16 +37,19 @@ async function installPluginConfig( options: Record | undefined, root: string ): Promise { + // 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) {