Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve recipes providers #22783

Merged
merged 12 commits into from
Apr 3, 2020
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