From b15d2878e4f930d2052dfcdc7bcc2b40d40b0f6f Mon Sep 17 00:00:00 2001 From: Kyle Gill Date: Mon, 2 Nov 2020 17:28:15 -0700 Subject: [PATCH 1/3] add wip plugin configuration forms --- packages/create-gatsby/src/index.ts | 49 ++++ .../create-gatsby/src/plugin-schemas.json | 238 ++++++++++++++++++ 2 files changed, 287 insertions(+) create mode 100644 packages/create-gatsby/src/plugin-schemas.json diff --git a/packages/create-gatsby/src/index.ts b/packages/create-gatsby/src/index.ts index 5f6b835b8cab6..0ed356778d0f4 100644 --- a/packages/create-gatsby/src/index.ts +++ b/packages/create-gatsby/src/index.ts @@ -2,6 +2,7 @@ import { prompt } from "enquirer" import cmses from "./cmses.json" import styles from "./styles.json" import features from "./features.json" +import pluginSchemas from "./plugin-schemas.json" import { initStarter } from "./init-starter" import { installPlugins } from "./install-plugins" import c from "ansi-colors" @@ -44,6 +45,42 @@ const questions = [ }, ] +const supportedOptionTypes = [`string`, `boolean`, `number`] + +const makePluginConfigQuestions = ( + selectedPlugins: Array +): Array => { + const formPrompts = selectedPlugins.map((pluginName: string) => { + const schema = pluginSchemas[pluginName] + const { keys: options } = schema + const choices = Object.keys(schema?.keys).reduce((result, name) => { + const option = options[name] + + if (option?.flags?.presence === `required`) { + result.push({ + name, + initial: + option.flags?.default && + supportedOptionTypes.includes(typeof option.flags?.default) + ? option.flags?.default.toString() + : undefined, + message: name, + }) + } + return result + }, []) + + return { + type: `form`, + name: `config-${pluginName}`, + multiple: true, + message: `Configure required fields for ${pluginName}:`, + choices, + } + }) + return formPrompts +} + interface IAnswers { project: string styling?: keyof typeof styles @@ -71,6 +108,18 @@ export async function run(): Promise { console.log(`Let's answer some questions:\n`) const data = await prompt(questions) + const selectedPlugins = [data.cms, data.styling, ...data.features].filter( + pluginName => pluginName !== `none` + ) + + console.log( + `\nGreat! A few of the selections you made need to be configured, fill in the options for each plugin now:\n` + ) + const pluginConfig = await prompt( + makePluginConfigQuestions(selectedPlugins) + ) + console.log(pluginConfig) + const messages: Array = [ `🛠 Create a new Gatsby site in the folder ${c.blueBright(data.project)}`, ] diff --git a/packages/create-gatsby/src/plugin-schemas.json b/packages/create-gatsby/src/plugin-schemas.json new file mode 100644 index 0000000000000..2bc57869dd564 --- /dev/null +++ b/packages/create-gatsby/src/plugin-schemas.json @@ -0,0 +1,238 @@ +{ + "gatsby-source-wordpress": "TODO: no schema in https://github.com/gatsbyjs/gatsby-source-wordpress-experimental/blob/master/plugin/gatsby-node.js", + "gatsby-source-contentful": { + "type": "object", + "externals": [{}], + "keys": { + "accessToken": { + "type": "string", + "flags": { + "description": "Contentful delivery api key, when using the Preview API use your Preview API key", + "presence": "required" + } + }, + "spaceId": { + "type": "string", + "flags": { "description": "Contentful spaceId", "presence": "required" } + }, + "host": { + "type": "string", + "flags": { + "description": "The base host for all the API requests, by default it's 'cdn.contentful.com', if you want to use the Preview API set it to 'preview.contentful.com'. You can use your own host for debugging/testing purposes as long as you respect the same Contentful JSON structure.", + "default": "cdn.contentful.com" + } + }, + "environment": { + "type": "string", + "flags": { + "description": "The environment to pull the content from, for more info on environments check out this [Guide](https://www.contentful.com/developers/docs/concepts/multiple-environments/).", + "default": "master" + } + }, + "downloadLocal": { + "type": "boolean", + "flags": { + "description": "Downloads and caches ContentfulAsset's to the local filesystem. Allows you to query a ContentfulAsset's localFile field, which is not linked to Contentful's CDN. Useful for reducing data usage.\nYou can pass in any other options available in the [contentful.js SDK](https://github.com/contentful/contentful.js#configuration).", + "default": false + } + }, + "localeFilter": { + "type": "function", + "flags": { + "description": "Possibility to limit how many locales/nodes are created in GraphQL. This can limit the memory usage by reducing the amount of nodes created. Useful if you have a large space in contentful and only want to get the data from one selected locale.\nFor example, to filter locales on only germany `localeFilter: locale => locale.code === 'de-DE'`\n\nList of locales and their codes can be found in Contentful app -> Settings -> Locales" + } + }, + "forceFullSync": { + "type": "boolean", + "flags": { + "description": "Prevents the use of sync tokens when accessing the Contentful API.", + "default": false + } + }, + "pageLimit": { + "type": "number", + "flags": { + "description": "Number of entries to retrieve from Contentful at a time. Due to some technical limitations, the response payload should not be greater than 7MB when pulling content from Contentful. If you encounter this issue you can set this param to a lower number than 100, e.g 50.", + "default": 100 + }, + "rules": [{ "name": "integer" }] + }, + "assetDownloadWorkers": { + "type": "number", + "flags": { + "description": "Number of workers to use when downloading contentful assets. Due to technical limitations, opening too many concurrent requests can cause stalled downloads. If you encounter this issue you can set this param to a lower number than 50, e.g 25.", + "default": 50 + }, + "rules": [{ "name": "integer" }] + }, + "proxy": { + "type": "object", + "flags": { + "description": "Axios proxy configuration. See the [axios request config documentation](https://github.com/mzabriskie/axios#request-config) for further information about the supported values." + }, + "keys": { + "host": { "type": "string", "flags": { "presence": "required" } }, + "port": { "type": "number", "flags": { "presence": "required" } }, + "auth": { + "type": "object", + "keys": { + "username": { "type": "string" }, + "password": { "type": "string" } + } + } + } + }, + "useNameForId": { + "type": "boolean", + "flags": { + "description": "Use the content's `name` when generating the GraphQL schema e.g. a Content Type called `[Component] Navigation bar` will be named `contentfulComponentNavigationBar`.\n When set to `false`, the content's internal ID will be used instead e.g. a Content Type with the ID `navigationBar` will be called `contentfulNavigationBar`.\n\n Using the ID is a much more stable property to work with as it will change less often. However, in some scenarios, Content Types' IDs will be auto-generated (e.g. when creating a new Content Type without specifying an ID) which means the name in the GraphQL schema will be something like `contentfulC6XwpTaSiiI2Ak2Ww0oi6qa`. This won't change and will still function perfectly as a valid field name but it is obviously pretty ugly to work with.\n\n If you are confident your Content Types will have natural-language IDs (e.g. `blogPost`), then you should set this option to `false`. If you are unable to ensure this, then you should leave this option set to `true` (the default).", + "default": true + } + }, + "plugins": { "type": "array" }, + "richText": { + "type": "object", + "flags": { "default": {} }, + "keys": { + "resolveFieldLocales": { + "type": "boolean", + "flags": { + "description": "If you want to resolve the locales in fields of assets and entries that are referenced by rich text (e.g., via embedded entries or entry hyperlinks), set this to `true`. Otherwise, fields of referenced assets or entries will be objects keyed by locale.", + "default": false + } + } + } + } + } + }, + "gatsby-source-datocms": "TODO: no schema in https://github.com/datocms/gatsby-source-datocms/blob/master/src/gatsby-node.js", + "gatsby-source-sanity": "TODO: no schema in https://github.com/sanity-io/gatsby-source-sanity/blob/main/src/gatsby-node.ts", + "gatsby-source-agility": "TODO: no schema in https://github.com/agility/gatsby-source-agilitycms/blob/master/gatsby-node.js", + "gatsby-plugin-postcss": {}, + "gatsby-plugin-styled-components": {}, + "gatsby-plugin-emotion": {}, + "gatsby-plugin-sass": {}, + "gatsby-plugin-themeui": {}, + "gatsby-plugin-google-analytics": { + "type": "object", + "keys": { + "trackingId": { + "type": "string", + "flags": { + "description": "The property ID; the tracking code won't be generated without it", + "presence": "required" + } + }, + "head": { + "type": "boolean", + "flags": { + "default": false, + "description": "Defines where to place the tracking script - `true` in the head and `false` in the body" + } + }, + "anonymize": { "type": "boolean", "flags": { "default": false } }, + "respectDNT": { "type": "boolean", "flags": { "default": false } }, + "exclude": { + "type": "array", + "flags": { + "default": [], + "description": "Avoids sending pageview hits from custom paths" + }, + "items": [{ "type": "string" }] + }, + "pageTransitionDelay": { + "type": "number", + "flags": { + "default": 0, + "description": "Delays sending pageview hits on route update (in milliseconds)" + } + }, + "optimizeId": { + "type": "string", + "flags": { + "description": "Enables Google Optimize using your container Id" + } + }, + "experimentId": { + "type": "string", + "flags": { "description": "Enables Google Optimize Experiment ID" } + }, + "variationId": { + "type": "string", + "flags": { "description": "Set Variation ID. 0 for original 1,2,3...." } + }, + "defer": { + "type": "boolean", + "flags": { + "description": "Defers execution of google analytics script after page load" + } + }, + "sampleRate": { "type": "number" }, + "siteSpeedSampleRate": { "type": "number" }, + "cookieDomain": { "type": "string" } + } + }, + "gatsby-plugin-sitemap": {}, + "gatsby-plugin-mdx": {}, + "gatsby-plugin-offline": {}, + "gatsby-plugin-manifest": { + "type": "object", + "keys": { + "name": { "type": "string" }, + "short_name": { "type": "string" }, + "description": { "type": "string" }, + "lang": { "type": "string" }, + "localize": { + "type": "array", + "items": [ + { + "type": "object", + "keys": { + "start_url": { "type": "string" }, + "name": { "type": "string" }, + "short_name": { "type": "string" }, + "description": { "type": "string" }, + "lang": { "type": "string" } + } + } + ] + }, + "start_url": { "type": "string" }, + "background_color": { "type": "string" }, + "theme_color": { "type": "string" }, + "display": { "type": "string" }, + "legacy": { "type": "boolean" }, + "include_favicon": { "type": "boolean" }, + "icon": { "type": "string" }, + "theme_color_in_head": { "type": "boolean" }, + "crossOrigin": { + "type": "string", + "flags": { "only": true }, + "allow": ["use-credentials", "anonymous"] + }, + "cache_busting_mode": { + "type": "string", + "flags": { "only": true }, + "allow": ["query", "name", "none"] + }, + "icons": { + "type": "array", + "items": [ + { + "type": "object", + "keys": { + "src": { "type": "string" }, + "sizes": { "type": "string" }, + "type": { "type": "string" }, + "purpose": { "type": "string" } + } + } + ] + }, + "icon_options": { + "type": "object", + "keys": { "purpose": { "type": "string" } } + } + } + } +} From 7d9ef1a2863b39526b0a5eedcc8892532d63ea7c Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Tue, 3 Nov 2020 09:58:05 +0000 Subject: [PATCH 2/3] Use plugins array --- packages/create-gatsby/src/index.ts | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/packages/create-gatsby/src/index.ts b/packages/create-gatsby/src/index.ts index 0ed356778d0f4..a0ab3b611b8bd 100644 --- a/packages/create-gatsby/src/index.ts +++ b/packages/create-gatsby/src/index.ts @@ -108,18 +108,6 @@ export async function run(): Promise { console.log(`Let's answer some questions:\n`) const data = await prompt(questions) - const selectedPlugins = [data.cms, data.styling, ...data.features].filter( - pluginName => pluginName !== `none` - ) - - console.log( - `\nGreat! A few of the selections you made need to be configured, fill in the options for each plugin now:\n` - ) - const pluginConfig = await prompt( - makePluginConfigQuestions(selectedPlugins) - ) - console.log(pluginConfig) - const messages: Array = [ `🛠 Create a new Gatsby site in the folder ${c.blueBright(data.project)}`, ] @@ -151,6 +139,14 @@ export async function run(): Promise { plugins.push(...data.features) } + console.log( + `\nGreat! A few of the selections you made need to be configured, fill in the options for each plugin now:\n` + ) + const pluginConfig = await prompt( + makePluginConfigQuestions(plugins) + ) + console.log(pluginConfig) + console.log(` ${c.bold(`Thanks! Here's what we'll now do:`)} From 6400f70734bca4c7e7c7aa3e54c20a459796a7aa Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Tue, 3 Nov 2020 10:32:29 +0000 Subject: [PATCH 3/3] Typings --- packages/create-gatsby/package.json | 1 + packages/create-gatsby/src/index.ts | 53 ++++++++++++++++++----------- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/packages/create-gatsby/package.json b/packages/create-gatsby/package.json index a636dc176de90..74c137ae10410 100644 --- a/packages/create-gatsby/package.json +++ b/packages/create-gatsby/package.json @@ -26,6 +26,7 @@ "@types/node": "^14.14.5", "eslint": "^7.12.1", "mock-stdin": "^1.0.0", + "joi": "^17.2.1", "prettier": "^2.1.2", "stdout-stderr": "^0.1.13", "typescript": "^4.0.5" diff --git a/packages/create-gatsby/src/index.ts b/packages/create-gatsby/src/index.ts index a0ab3b611b8bd..883a556e2191f 100644 --- a/packages/create-gatsby/src/index.ts +++ b/packages/create-gatsby/src/index.ts @@ -7,6 +7,7 @@ import { initStarter } from "./init-starter" import { installPlugins } from "./install-plugins" import c from "ansi-colors" import path from "path" +import type Joi from "joi" const makeChoices = ( options: Record @@ -47,28 +48,42 @@ const questions = [ const supportedOptionTypes = [`string`, `boolean`, `number`] +type PluginName = keyof typeof pluginSchemas +type Schema = Joi.Description & { + // Limitation in Joi typings + // eslint-disable-next-line @typescript-eslint/no-explicit-any + flags?: Record +} + const makePluginConfigQuestions = ( - selectedPlugins: Array + selectedPlugins: Array ): Array => { - const formPrompts = selectedPlugins.map((pluginName: string) => { + const formPrompts = selectedPlugins.map((pluginName: PluginName) => { const schema = pluginSchemas[pluginName] - const { keys: options } = schema - const choices = Object.keys(schema?.keys).reduce((result, name) => { - const option = options[name] - - if (option?.flags?.presence === `required`) { - result.push({ - name, - initial: - option.flags?.default && - supportedOptionTypes.includes(typeof option.flags?.default) - ? option.flags?.default.toString() - : undefined, - message: name, - }) + if (typeof schema === `string` || !(`keys` in schema)) { + return [] + } + const options: Record | undefined = schema?.keys + const choices: Array<{ + name: string + initial: string + message: string + }> = [] + + Object.entries(options).forEach(([name, option]) => { + if (option?.flags?.presence !== `required`) { + return } - return result - }, []) + choices.push({ + name, + initial: + option.flags?.default && + supportedOptionTypes.includes(typeof option.flags?.default) + ? option.flags?.default.toString() + : undefined, + message: name, + }) + }) return { type: `form`, @@ -112,7 +127,7 @@ export async function run(): Promise { `🛠 Create a new Gatsby site in the folder ${c.blueBright(data.project)}`, ] - const plugins = [] + const plugins: Array = [] if (data.cms && data.cms !== `none`) { messages.push(