Skip to content

Commit

Permalink
feat(create-gatsby): Add create-gatsby (#27703)
Browse files Browse the repository at this point in the history
* Add create-gatsby

* add question and readme, update some wording

* Update readme

* Update handling of questions

* Stylish!

* Update message

* Update messages

* Catch ctrl^c

* v0.0.0-2

* Add test

* Fix test

* feat(gatsby): Add "gatsby plugin add" command

* Load readme from local package

* ensure skipped steps are actually skipped

* attempt to add some more tests

* Move command to cli

* Try to install plugins

* feat(create-gatsby): add wip plugin configuration forms (#27801)

* add wip plugin configuration forms

* Use plugins array

* Typings

Co-authored-by: Matt Kane <matt@gatsbyjs.com>

* Install plugins

* Add error handling

* Return, don't exit

* Fix tests

* Resolve themes relative to root

* Change back to original dir

* Use starter with canary

* v0.0.0-3

* Fix to force publish

* Working!

* Change from review

* Fix package name

* Use gatsby-source-wordpress-experimental

* Add schema import script

* v0.0.0-4

* Add dep

* v0.0.0-5

* handle peer dependencies

* forgot to save a file

* bump core-utils dependency

* update styling and text to match Flo's design, in progress

* use magenta for all actions to be taken

* add final prompts

* consistent coloring

* consistent coloring

* consistent coloring

* Initial input test

* Layout helpers

* Add custom textinput prompt

* Fixes to hint

* Fix error

* Add select control

* update options for gatsby new

* Tab to end

* update tests

* send both tabs

* it was the right hex code, or not

* order shouldn't matter but I'm very confused

* will slash tab work

* Formatting fixes

* v0.0.0-6

* use down inside of tab

* test enter

* trying one more thing, but suspect it's unrelated

* Update packages/create-gatsby/src/cmses.json

Co-authored-by: Lennart <lekoarts@gmail.com>

* Update packages/create-gatsby/src/styles.json

Co-authored-by: Lennart <lekoarts@gmail.com>

* one more time

* bane of my existence

* try different keys for different OS

* somehow bypasses linter

* remove console.log

* add fake plugin schemas for other cmses

* Fix test

* Longer tick

* Add description to form inputs

* Test changes

* increase interval

* add initial working test

* missing return type

* not sure why linter keeps skipping these diles

* all tests except project name exists

* just in case it's a timeout thing

* does a single question pass

* have CI run individual questions and see if we falter

* Test

* Burn it with fire 🔥

* Move components into plugin

* Replace Prismic with Dato

* Install plugins at the start

* Add support for plugin dependencies (#27995)

* feat(create-gatsby): Add support for plugin dependencies

* Fix extra plugin handling

* Apply suggestions from code review

Co-authored-by: Max Stoiber <contact@mxstbr.com>

* Remove broken tests 😢

Co-authored-by: Kyle Gill <kylerobertgill@gmail.com>
Co-authored-by: Laurie <laurie@gatsbyjs.com>
Co-authored-by: gatsbybot <mathews.kyle+gatsbybot@gmail.com>
Co-authored-by: Lennart <lekoarts@gmail.com>
Co-authored-by: Max Stoiber <contact@mxstbr.com>
  • Loading branch information
6 people committed Nov 12, 2020
1 parent 3d0de4a commit 2371fd5
Show file tree
Hide file tree
Showing 32 changed files with 2,005 additions and 25 deletions.
2 changes: 2 additions & 0 deletions packages/create-gatsby/.gitignore
@@ -0,0 +1,2 @@
node_modules
lib
21 changes: 21 additions & 0 deletions packages/create-gatsby/README.md
@@ -0,0 +1,21 @@
# create-gatsby (alpha)

Create Gatsby apps in an interactive CLI experience that does the plumbing for you.

## Quick Overview

Create a new Gatsby app by running the following command:

```shell
npm init gatsby
```

or

```shell
yarn create gatsby
```

It will ask you questions about what you're building, and set up a Gatsby project for you.

_Note: this package is different from the Gatsby CLI, it is intended solely to create new sites.._
44 changes: 44 additions & 0 deletions packages/create-gatsby/package.json
@@ -0,0 +1,44 @@
{
"name": "create-gatsby",
"version": "0.0.0-6",
"main": "lib/index.js",
"bin": "lib/cli.js",
"license": "MIT",
"scripts": {
"build": "tsc",
"watch": "tsc --watch",
"prepare": "yarn build",
"import-plugin-options": "node ./scripts/import-options-schema.js"
},
"homepage": "https://github.com/gatsbyjs/gatsby/tree/master/packages/create-gatsby#readme",
"dependencies": {
"@babel/runtime": "^7.12.1",
"ansi-wordwrap": "^1.0.2",
"common-tags": "^1.8.0",
"enquirer": "^2.3.6",
"execa": "^4.0.3",
"fs-extra": "^9.0.1",
"gatsby-core-utils": "^1.4.0-next.0",
"stream-filter": "^2.1.0",
"string-length": "^4.0.1",
"terminal-link": "^2.1.1"
},
"files": [
"lib"
],
"devDependencies": {
"@types/configstore": "^4.0.0",
"@types/fs-extra": "^9.0.2",
"@types/node": "^14.14.5",
"eslint": "^7.12.1",
"joi": "^17.2.1",
"prettier": "^2.1.2",
"typescript": "^4.0.5"
},
"repository": {
"type": "git",
"url": "https://github.com/gatsbyjs/gatsby.git",
"directory": "packages/create-gatsby"
},
"author": "Matt Kane <matt@gatsbyjs.com>"
}
93 changes: 93 additions & 0 deletions packages/create-gatsby/scripts/import-options-schema.js
@@ -0,0 +1,93 @@
#!/usr/bin/env node

const path = require("path")
const fs = require("fs-extra")
const pluginPath= process.argv[2]
const Joi = require("gatsby-plugin-utils")
async function run() {
if(!pluginPath) {
console.error("Please pass a path to the plugin directory")
return
}


const rootDir = path.resolve(pluginPath)
if(!fs.existsSync(rootDir)) {
console.error(`The plugin directory ${rootDir} does not exist`)
return
}

const stat = await fs.stat(rootDir)

if(!stat.isDirectory()) {
console.error(`The plugin path ${rootDir} is not a directory`)
return
}

let pluginName

try {
const { name } = require(path.resolve(rootDir, "package.json"))
if(!name) {
console.error("Plugin package.json does not have a name field")
return
}
pluginName = name

} catch (e) {
console.error("Could not open package.json. Are you sure the plugin directory is correct?")
return
}

const gatsbyNodePath = path.resolve(rootDir, "gatsby-node.js")

if(!fs.existsSync(gatsbyNodePath)) {
console.error(`Could not find gatsby-node.js in ${gatsbyNodePath}. Are you sure this is a plugin directory?`)
return
}

let pluginOptionsSchema

try {
const gatsbyNode = require(gatsbyNodePath)
pluginOptionsSchema = gatsbyNode.pluginOptionsSchema
} catch(e) {
console.error(`Could not load gatsby-node.js. You may need to build the plugin first.`)
console.log("Error was:", e.message)
return
}

if(!pluginOptionsSchema) {
console.error("The plugin does not include a pluginOptionsSchema")
return
}

let optionsSchema

try {
const schema = pluginOptionsSchema({ Joi })
optionsSchema = schema.describe()
} catch (e) {
console.error("Failed to generate schema")
console.error(e.message)
return
}

const schemataPath = path.resolve(__dirname, "..", "src", "plugin-schemas.json")

if(!fs.existsSync(schemataPath)) {
console.error("Could not find output file")
return
}

const json = await fs.readJSON(schemataPath)

json[pluginName] = optionsSchema

console.log(`Writing "${pluginName} to schemataPath`)
await fs.writeJSON(schemataPath, json)


}

run()
6 changes: 6 additions & 0 deletions packages/create-gatsby/src/cli.ts
@@ -0,0 +1,6 @@
#!/usr/bin/env node
import { run } from "."

run().catch(e => {
console.warn(e)
})
7 changes: 7 additions & 0 deletions packages/create-gatsby/src/cmses.json
@@ -0,0 +1,7 @@
{
"gatsby-source-wordpress-experimental": { "message": "WordPress" },
"gatsby-source-contentful": { "message": "Contentful" },
"gatsby-source-sanity": { "message": "Sanity" },
"gatsby-source-datocms": { "message": "DatoCMS" },
"gatsby-source-shopify": { "message": "Shopify" }
}
5 changes: 5 additions & 0 deletions packages/create-gatsby/src/cmses.json.d.ts
@@ -0,0 +1,5 @@
import {PluginMap} from "."

declare const cmses: PluginMap

export default cmses
72 changes: 72 additions & 0 deletions packages/create-gatsby/src/components/form.js
@@ -0,0 +1,72 @@
import { Form } from "enquirer"
import placeholder from "./placeholder"
import colors from "ansi-colors"

export class FormInput extends Form {
async renderChoice(choice, i) {
await this.onChoice(choice, i)

let { state, styles } = this
let { cursor, initial = ``, name, input = `` } = choice
let { muted, submitted, primary, danger } = styles

let focused = this.index === i
let validate = choice.validate || (() => true)
let sep = await this.choiceSeparator(choice, i)
let msg = choice.message

if (this.align === `right`) msg = msg.padStart(this.longest + 1, ` `)
if (this.align === `left`) msg = msg.padEnd(this.longest + 1, ` `)

// re-populate the form values (answers) object
let value = (this.values[name] = input || initial)
let color = input ? `success` : `dark`

if ((await validate.call(choice, value, this.state)) !== true) {
color = `danger`
}

let style = styles[color]
let indicator = style(await this.indicator(choice, i)) + (choice.pad || ``)

let indent = this.indent(choice)
let line = () =>
[indent, indicator, msg + sep, input].filter(Boolean).join(` `)

if (state.submitted) {
msg = colors.unstyle(msg)
input = submitted(input)
return line()
}

if (choice.format) {
input = await choice.format.call(this, input, choice, i)
} else {
let color = this.styles.muted
let options = { input, initial, pos: cursor, showCursor: focused, color }
input = placeholder(this, options)
}

if (!this.isValue(input)) {
input = this.styles.muted(this.symbols.ellipsis)
}

if (choice.result) {
this.values[name] = await choice.result.call(this, value, choice, i)
}

if (focused) {
msg = primary(msg)
}

if (choice.error) {
input += (input ? ` ` : ``) + danger(choice.error.trim())
} else if (choice.hint && focused) {
input +=
(input ? `\n${` `.repeat(this.longest + 6)}` : ``) +
muted(choice.hint.trim())
}

return line()
}
}
66 changes: 66 additions & 0 deletions packages/create-gatsby/src/components/placeholder.js
@@ -0,0 +1,66 @@
/**
* This file is taken almost unchanged from enquirer, because it's not exported from the module
*/

const isPrimitive = val =>
val != null && typeof val !== `object` && typeof val !== `function`

/**
* Render a placeholder value with cursor and styling based on the
* position of the cursor.
*
* @param {Object} `prompt` Prompt instance.
* @param {String} `input` Input string.
* @param {String} `initial` The initial user-provided value.
* @param {Number} `pos` Current cursor position.
* @param {Boolean} `showCursor` Render a simulated cursor using the inverse primary style.
* @return {String} Returns the styled placeholder string.
* @api public
*/

module.exports = (prompt, options = {}) => {
prompt.cursorHide()

let { input = ``, initial = ``, pos, showCursor = true, color } = options
let style = color || prompt.styles.placeholder
let inverse = prompt.styles.primary.inverse
let blinker = str => inverse(str)
let output = input
let char = ` `
let reverse = blinker(char)

if (prompt.blink && prompt.blink.off === true) {
blinker = str => str
reverse = ``
}

if (showCursor && pos === 0 && initial === `` && input === ``) {
return blinker(char)
}

if (showCursor && pos === 0 && (input === initial || input === ``)) {
return blinker(initial[0]) + style(initial.slice(1))
}

initial = isPrimitive(initial) ? `${initial}` : ``
input = isPrimitive(input) ? `${input}` : ``

let placeholder = initial && initial.startsWith(input) && initial !== input
let cursor = placeholder ? blinker(initial[input.length]) : reverse

if (pos !== input.length && showCursor === true) {
output = input.slice(0, pos) + blinker(input[pos]) + input.slice(pos + 1)
cursor = ``
}

if (showCursor === false) {
cursor = ``
}

if (placeholder) {
let raw = prompt.styles.unstyle(output + cursor)
return output + cursor + style(initial.slice(raw.length))
}

return output + cursor
}
17 changes: 17 additions & 0 deletions packages/create-gatsby/src/components/plugin.js
@@ -0,0 +1,17 @@
import { FormInput } from "./form"
import { TextInput } from "./text"
import { SelectInput, MultiSelectInput } from "./select"

/**
* Enquirer plugin to add custom fields
*
* @param enquirer {import("enquirer")}
* @returns {import("enquirer")}
*/
export const plugin = enquirer => {
enquirer.register(`textinput`, TextInput)
enquirer.register(`selectinput`, SelectInput)
enquirer.register(`multiselectinput`, MultiSelectInput)
enquirer.register(`forminput`, FormInput)
return enquirer
}

0 comments on commit 2371fd5

Please sign in to comment.