Skip to content

Commit

Permalink
feat(recipes): iterate on recipe design (#22911)
Browse files Browse the repository at this point in the history
  • Loading branch information
KyleAMathews committed Apr 8, 2020
1 parent 6d390c5 commit a08e767
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 32 deletions.
120 changes: 103 additions & 17 deletions packages/gatsby/src/recipes/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ const path = require(`path`)
const { inspect } = require(`util`)

const React = require(`react`)
const { useState, useContext } = require(`react`)
const { render, Box, Text, useInput, useApp } = require(`ink`)
const { useState, useContext, useEffect } = require(`react`)
const { render, Box, Text, useInput, useApp, Static } = require(`ink`)
const Spinner = require(`ink-spinner`).default
const Link = require(`ink-link`)
const MDX = require(`@mdx-js/runtime`)
Expand All @@ -26,6 +26,7 @@ const PlanDescribe = ({ resourceName }) => {
const { planForNextStep = [] } = usePlan()
const plans = planForNextStep.filter(p => p.resourceName === resourceName)

return null
return (
<Box>
{plans.map((stepPlan, i) => (
Expand All @@ -35,9 +36,17 @@ const PlanDescribe = ({ resourceName }) => {
)
}

const Div = props => (
<Box width={80} textWrap="wrap" flexDirection="column" {...props} />
)

const components = {
inlineCode: props => <Text {...props} />,
h1: props => <Text bold underline {...props} />,
h1: props => (
<Div marginBottom={1}>
<Text bold underline {...props} />
</Div>
),
h2: props => <Text bold {...props} />,
h3: props => <Text bold italic {...props} />,
h4: props => <Text bold {...props} />,
Expand All @@ -55,19 +64,15 @@ const components = {
{...props}
/>
),
ul: props => <Div marginBottom={1}>{props.children}</Div>,
li: props => <Text>* {props.children}</Text>,
Config: () => null,
GatsbyPlugin: () => <PlanDescribe resourceName="GatsbyPlugin" />,
NPMPackageJson: () => <PlanDescribe resourceName="NPMPackageJson" />,
NPMPackage: ({ name }) => (
// const { planForNextStep = [] } = usePlan()

<Box>
<Text>* {name}</Text>
</Box>
),
NPMPackage: () => null,
File: () => <PlanDescribe resourceName="File" />,
ShadowFile: () => <PlanDescribe resourceName="ShadowFile" />,
NPMScript: () => null,
}

const isRelative = path => {
Expand Down Expand Up @@ -149,10 +154,6 @@ module.exports = ({ recipe, projectRoot }) => {

const { commands: allCommands, stepsAsMdx: stepsAsMDX } = parsed

const Div = props => (
<Box width={80} textWrap="wrap" flexDirection="column" {...props} />
)

class ErrorBoundary extends React.Component {
constructor(props) {
super(props)
Expand All @@ -179,6 +180,7 @@ module.exports = ({ recipe, projectRoot }) => {
}
}

let renderCount = 1
const RecipeInterpreter = ({ commands }) => {
const [lastKeyPress, setLastKeyPress] = useState(``)
const { exit } = useApp()
Expand Down Expand Up @@ -226,12 +228,20 @@ module.exports = ({ recipe, projectRoot }) => {
}
})

log(`render`, new Date().toJSON())
/*
* TODOs
* on last step w/ no plan just exit
* show what's happened in Static — probably the state machine should put these in new context key.
*/

log(`render`, `${renderCount} ${new Date().toJSON()}`)
renderCount += 1

if (!subscriptionResponse.data) {
return null
}

// If we're done, exit.
if (state.value === `done`) {
process.exit()
}
Expand All @@ -241,14 +251,90 @@ module.exports = ({ recipe, projectRoot }) => {
log(`plan`, state.context.plan)
}

const PresentStep = ({ state }) => {
const isPlan = state.context.plan && state.context.plan.length > 0
const isPresetPlanState = state.value === `present plan`
const isRunningStep = state.value === `applyingPlan`

if (isRunningStep) {
return null
}

if (!isPlan || !isPresetPlanState) {
return (
<Div>
<Text>Press enter to continue</Text>
</Div>
)
}

return (
<Div>
{state.context.plan.map(p => {
return (
<Div key={p.resourceName}>
<Text italic>{p.resourceName}:</Text>
<Text> * {p.describe}</Text>
</Div>
)
})}
<Div marginTop={1}>
<Text>Do you want to run this step? Y/n</Text>
</Div>
</Div>
)
}

const RunningStep = ({ state }) => {
const isPlan = state.context.plan && state.context.plan.length > 0
const isRunningStep = state.value === `applyingPlan`

if (!isPlan || !isRunningStep) {
return null
}

return (
<Div>
{state.context.plan.map(p => {
return (
<Div key={p.resourceName}>
<Text italic>{p.resourceName}:</Text>
<Text>
{` `}
<Spinner /> {p.describe}
</Text>
</Div>
)
})}
</Div>
)
}

// <Static>
// <Text bold>Finished</Text>
// <Text italic>Step 1</Text>
// <Text>✅ Wrote out file to src/pages/what-about-bob.js</Text>
// <Text italic>Step 2</Text>
// <Text>
// ✅ Shadowed file src/gatsby-theme-blog/foo.js from gatsby-theme-blog
// </Text>
// </Static>
return (
<ErrorBoundary>
{state.context.currentStep > 0 && (
<Div>
<Text>
Step {state.context.currentStep} /{` `}
{state.context.steps.length - 1}
</Text>
</Div>
)}
<PlanContext.Provider value={{ planForNextStep: state.plan }}>
<MDX components={components}>
{stepsAsMDX[state.context.currentStep]}
</MDX>
<Text>{` `}</Text>
<Text>Press enter to apply!</Text>
<PresentStep state={state} />
<RunningStep state={state} />
</PlanContext.Provider>
</ErrorBoundary>
)
Expand Down
4 changes: 4 additions & 0 deletions packages/gatsby/src/recipes/create-plan.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ module.exports = async context => {
const cmds = context.steps[context.currentStep]
const commandPlans = Object.entries(cmds).map(async ([key, val]) => {
const resource = resources[key]
// Filter out the Config resource
if (key === `Config`) {
return
}

// Does this resource support creating a plan?
if (!resource || !resource.plan) {
Expand Down
7 changes: 4 additions & 3 deletions packages/gatsby/src/recipes/eslint.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Add a custom ESLint config

## Introduction to ESLint
ESLint is an open source JavaScript linting utility. Code linting is a type of
static analysis that is frequently used to find problematic patterns. There are
code linters for most programming languages, and compilers sometimes
Expand All @@ -11,19 +12,19 @@ is typically executed in order to find syntax or other errors. Linting tools
like ESLint allow developers to discover problems with their JavaScript code
without executing it.

## How to use ESLint
## Why use this recipe

Gatsby ships with a built-in ESLint setup. For most users,
our built-in ESlint setup is all you need. If you know however that you’d like
to customize your ESlint config e.g. your company has their own custom ESlint
setup, this shows how this can be done.
setup, this recipe sets this up for you.

You’ll replicate (mostly) the ESLint config Gatsby ships with so you can then
add additional presets, plugins, and rules.

---

Installing necessary packages
Install necessary packages

<NPMPackage name="eslint-config-react-app" dependencyType={"dev"} />

Expand Down
13 changes: 8 additions & 5 deletions packages/gatsby/src/recipes/graphql.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,14 @@ const applyPlan = plan => {
state: state.value,
currentStep: state.context.currentStep,
})
emitOperation({
context: state.context,
lastEvent: state.event,
value: state.value,
})
// Wait until plans are created before updating the UI
if (state.value !== `creatingPlan`) {
emitOperation({
context: state.context,
lastEvent: state.event,
value: state.value,
})
}
}
})

Expand Down
2 changes: 1 addition & 1 deletion packages/gatsby/src/recipes/providers/gatsby/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,6 @@ module.exports.plan = async ({ root }, { id, name }) => {
name,
currentState: src,
newState: newContents,
describe: `Configure ${fullName}`,
describe: `Install ${fullName} in gatsby-config.js`,
}
}
4 changes: 2 additions & 2 deletions packages/gatsby/src/recipes/providers/npm/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ module.exports.all = async ({ root }) => {
module.exports.plan = async ({ root }, { name, command }) => {
const resource = await read({ root }, name)

const scriptDescription = (name, command) => `"${name}": "${command}`
const scriptDescription = (name, command) => `"${name}": "${command}"`

let currentState = ``
if (resource) {
Expand All @@ -76,7 +76,7 @@ module.exports.plan = async ({ root }, { name, command }) => {
return {
currentState,
newState: scriptDescription(name, command),
describe: `Add new command to your package.json${scriptDescription(
describe: `Add new command to your package.json:\n${scriptDescription(
name,
command
)}`,
Expand Down
9 changes: 5 additions & 4 deletions packages/gatsby/src/recipes/todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@
- [x] Run as a command
- [x] Boot up server as a process
- [x] Then run the CLI
- [ ] Clean up server after
- [x] Clean up server after

## Near future

- [x] Make it support relative paths for custom recipes (./src/recipes/foo.mdx)
- [ ] Document the supported components and trivial guide on recipe authoring (help)
- [ ] Theme UI preset selection (runs dependent install and file write)
- [ ] Move to more random port

## alpha
Expand All @@ -21,7 +20,6 @@
- [ ] Make `dependencyType` in NPMPackage an enum
- [ ] Add large warning to recipes output that this is an experimental feature & might change at any moment + link to docs / umbrella issue for bug reports & discussions
- [x] Step by step design
- [ ] Select input supported recipes
- [ ] Make port selection dynamic
- [x] Use `fs-extra`
- [x] Handle object style plugins
Expand All @@ -30,13 +28,16 @@
- [x] convert to xstate
- [ ] reasonably test resources
- [x] add Joi for validating resource objects
- [ ] handle error states
- [x] handle template strings in JSX parser
- [ ] integration test for each resource (read, create, update, delete)
- [ ] show plan to create/update or that nothing is necessary & then show in `<static>` what was done
- [ ] handle error states

## Near-ish future

- [ ] Make a proper "Config" provider to add recipes dir, store data, etc.
- [ ] Move parsing to the server
- [ ] init.js for providers to setup clients
- [ ] validate resource config
- [ ] Theme UI preset selection (runs dependent install and file write)
- [ ] Select input supported recipes

0 comments on commit a08e767

Please sign in to comment.