Skip to content

Commit

Permalink
Improve recipes providers (#22783)
Browse files Browse the repository at this point in the history
* Don't overwrite files by default

* Handle object style plugin config

* Handle object style plugin config

* Update todo

* Use fs-extra, improve plugin config tests

* Fix file check

* Remove overwrite logic, this will be part of the plan step

* Queue operations and prompt user for each step

* Remove unused graphql code

* Return a plan for next step and reveal in ui

* Begin using plan data for rendering

Co-authored-by: John Otander <johnotander@gmail.com>
  • Loading branch information
KyleAMathews and johno authored Apr 3, 2020
1 parent 66ff708 commit d3d8a7b
Show file tree
Hide file tree
Showing 7 changed files with 301 additions and 133 deletions.
2 changes: 2 additions & 0 deletions packages/gatsby/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@hapi/joi": "^15.1.1",
"@mdx-js/mdx": "^1.5.8",
"@mdx-js/react": "^1.5.8",
"@mdx-js/runtime": "^1.5.8",
"@mikaelkristiansson/domready": "^1.0.10",
"@pieh/friendly-errors-webpack-plugin": "1.7.0-chalk-2",
"@pmmmwh/react-refresh-webpack-plugin": "^0.2.0",
Expand Down Expand Up @@ -144,6 +145,7 @@
"terser-webpack-plugin": "^1.4.3",
"true-case-path": "^2.2.1",
"type-of": "^2.0.1",
"unist-util-remove": "^2.0.0",
"unist-util-visit": "^2.0.2",
"url-loader": "^1.1.2",
"urql": "^1.9.5",
Expand Down
190 changes: 98 additions & 92 deletions packages/gatsby/src/recipes/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,11 @@ const fs = require(`fs`)
const path = require(`path`)

const React = require(`react`)
const { useState, useContext, useEffect } = require(`react`)
const { useState, useContext } = require(`react`)
const { render, Box, Text, useInput, useApp } = require(`ink`)
const Spinner = require(`ink-spinner`).default
const unified = require(`unified`)
const remarkMdx = require(`remark-mdx`)
const remarkParse = require(`remark-parse`)
const remarkStringify = require(`remark-stringify`)
const jsxToJson = require(`simplified-jsx-to-json`)
const visit = require(`unist-util-visit`)
const Link = require(`ink-link`)
const MDX = require(`@mdx-js/runtime`)
const humanizeList = require(`humanize-list`)
const {
createClient,
Expand All @@ -24,6 +20,40 @@ const { SubscriptionClient } = require(`subscriptions-transport-ws`)
const fetch = require(`node-fetch`)
const ws = require(`ws`)

const parser = require(`./parser`)

const PlanDescribe = ({ resourceName }) => {
const { planForNextStep = [] } = usePlan()
const plans = planForNextStep.filter(p => p.resourceName === resourceName)

return (
<Box>
{plans.map((stepPlan, i) => (
<Text key={i}>{stepPlan.describe}</Text>
))}
</Box>
)
}

const components = {
inlineCode: Text,
h1: props => <Text bold underline {...props} />,
h2: props => <Text bold underline {...props} />,
h3: props => <Text bold underline {...props} />,
h4: props => <Text bold underline {...props} />,
h5: props => <Text bold underline {...props} />,
h6: props => <Text bold underline {...props} />,
a: ({ href, children }) => <Link url={href}>{children}</Link>,
paragraph: props => (
<Box width="100%" flexDirection="row" textWrap="wrap" {...props} />
),
Config: () => null,
GatsbyPlugin: () => <PlanDescribe resourceName='GatsbyPlugin' />,
NPMPackage: () => <PlanDescribe resourceName='NPMPackage' />,
File: () => <PlanDescribe resourceName='File' />,
ShadowFile: () => <PlanDescribe resourceName='ShadowFile' />
}

const isRelative = path => {
if (path.slice(0, 1) == `.`) {
return true
Expand All @@ -32,6 +62,26 @@ const isRelative = path => {
return false
}

const log = (label, textOrObj) => {
const text =
typeof textOrObj === `string`
? textOrObj
: JSON.stringify(textOrObj, null, 2)

let contents = ``
try {
contents = fs.readFileSync(`recipe-client.log`, `utf8`)
} catch (e) {
// File doesn't exist yet
}

contents += label + `: ` + text + `\n`
fs.writeFileSync(`recipe-client.log`, contents)
}

const PlanContext = React.createContext({})
const usePlan = () => useContext(PlanContext)

module.exports = ({ recipe, projectRoot }) => {
let recipePath
if (isRelative(recipe)) {
Expand All @@ -42,8 +92,8 @@ module.exports = ({ recipe, projectRoot }) => {
if (recipePath.slice(-4) !== `.mdx`) {
recipePath += `.mdx`
}
const recipeSrc = fs.readFileSync(recipePath, `utf8`)

const recipeSrc = fs.readFileSync(recipePath, `utf8`)
const GRAPHQL_ENDPOINT = `http://localhost:4000/graphql`

const subscriptionClient = new SubscriptionClient(
Expand All @@ -67,75 +117,18 @@ module.exports = ({ recipe, projectRoot }) => {
],
})

const {
commands: allCommands,
stepsAsMdx: stepsAsMDX,
stepsAsMdxWithoutJsx: stepsAsMDXNoJSX,
} = parser(recipeSrc)

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

const u = unified()
.use(remarkParse)
.use(remarkStringify)
.use(remarkMdx)

const ast = u.parse(recipeSrc)

const steps = []
let index = 0
ast.children.forEach(node => {
if (node.type === `thematicBreak`) {
index++
return
}

steps[index] = steps[index] || []
steps[index].push(node)
})

const asRoot = nodes => {
return {
type: `root`,
children: nodes,
}
}

const stepsAsMDX = steps.map(nodes => {
const stepAst = asRoot(nodes)
return u.stringify(stepAst)
})

const toJson = value => {
const obj = {}
jsxToJson(value).forEach(([type, props = {}]) => {
if (type === `\n`) {
return
}
obj[type] = obj[type] || []
obj[type].push(props)
})
return obj
}

const allCommands = steps
.map(nodes => {
const stepAst = asRoot(nodes)
let cmds = []
visit(stepAst, `jsx`, node => {
const jsx = node.value
cmds = cmds.concat(toJson(jsx))
})
return cmds
})
.reduce((acc, curr) => {
const cmdByName = {}
curr.map(v => {
Object.entries(v).forEach(([key, value]) => {
cmdByName[key] = cmdByName[key] || []
cmdByName[key] = cmdByName[key].concat(value)
})
})
return [...acc, cmdByName]
}, [])

const RecipeInterpreter = ({ commands }) => {
const [currentStep, setCurrentStep] = useState(0)
const [lastKeyPress, setLastKeyPress] = useState(``)
const { exit } = useApp()
const [subscriptionResponse] = useSubscription(
Expand All @@ -145,20 +138,26 @@ module.exports = ({ recipe, projectRoot }) => {
operation {
state
data
planForNextStep
}
}
`,
},
(_prev, now) => now
)
const [_, createOperation] = useMutation(`
mutation ($commands: String!) {
createOperation(commands: $commands)
}
`)
mutation ($commands: String!) {
createOperation(commands: $commands)
}
`)
const [__, applyOperationStep] = useMutation(`
mutation {
applyOperationStep
}
`)

subscriptionClient.connectionCallback = () => {
createOperation({ commands: JSON.stringify(commands) })
subscriptionClient.connectionCallback = async () => {
await createOperation({ commands: JSON.stringify(commands) })
}

const { data } = subscriptionResponse
Expand All @@ -169,6 +168,13 @@ module.exports = ({ recipe, projectRoot }) => {
JSON.parse(data.operation.data)) ||
commands

const planForNextStep =
(data &&
data.operation &&
data.operation.planForNextStep &&
JSON.parse(data.operation.planForNextStep)) ||
[]

const state = data && data.operation && data.operation.state

useInput((_, key) => {
Expand All @@ -178,25 +184,25 @@ module.exports = ({ recipe, projectRoot }) => {
exit()
// TODO figure out why exiting ink isn't enough.
process.exit()
} else if (key.return) {
setCurrentStep(currentStep + 1)
applyOperationStep()
}
})

if (process.env.DEBUG) {
log(`subscriptionResponse`, subscriptionResponse)
log(`lastKeyPress`, lastKeyPress)
log(`state`, state)
}

return (
<>
<Div>
{process.env.DEBUG ? (
<Text>{JSON.stringify(subscriptionResponse, null, 2)}</Text>
) : null}
</Div>
<Div>
{process.env.DEBUG ? (
<Text>Last Key Press: {JSON.stringify(lastKeyPress, null, 2)}</Text>
) : null}
</Div>
<Div>{process.env.DEBUG ? <Text>STATE: {state}</Text> : null}</Div>
<PlanContext.Provider value={{planForNextStep}}>
<MDX components={components}>{stepsAsMDX[currentStep]}</MDX>
<Div />
<Text>Press enter to apply!</Text>
{operation.map((command, i) => (
<Div key={i}>
<Step command={command} />
<Div />
</Div>
))}
Expand All @@ -206,7 +212,7 @@ module.exports = ({ recipe, projectRoot }) => {
<Text>Your recipe is served! Press enter to exit.</Text>
</Div>
) : null}
</>
</PlanContext.Provider>
)
}

Expand Down
Loading

0 comments on commit d3d8a7b

Please sign in to comment.