diff --git a/packages/gatsby-recipes/package.json b/packages/gatsby-recipes/package.json index 8018df513bf09..fdeba00a02fb1 100644 --- a/packages/gatsby-recipes/package.json +++ b/packages/gatsby-recipes/package.json @@ -22,6 +22,7 @@ "@mdx-js/runtime": "^1.6.1", "acorn": "^7.2.0", "acorn-jsx": "^5.2.0", + "ansi-html": "^0.0.7", "cors": "^2.8.5", "debug": "^4.1.1", "detect-port": "^1.3.0", @@ -52,9 +53,11 @@ "lodash": "^4.17.15", "mkdirp": "^0.5.1", "node-fetch": "^2.6.0", + "p-queue": "^6.4.0", "pkg-dir": "^4.2.0", "prettier": "^2.0.5", "react-reconciler": "^0.25.1", + "react-circular-progressbar": "^2.0.0", "remark-mdx": "^1.6.1", "remark-parse": "^6.0.3", "remark-stringify": "^8.0.0", @@ -77,11 +80,7 @@ "tmp-promise": "^2.1.0" }, "homepage": "https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-recipes#readme", - "keywords": [ - "gatsby", - "gatsby-recipes", - "mdx" - ], + "keywords": ["gatsby", "gatsby-recipes", "mdx"], "license": "MIT", "repository": { "type": "git", @@ -92,10 +91,7 @@ "graphql": "^14.6.0" }, "jest": { - "testPathIgnorePatterns": [ - "/.cache/", - "dist" - ], + "testPathIgnorePatterns": ["/.cache/", "dist"], "testEnvironment": "node" }, "scripts": { diff --git a/packages/gatsby-recipes/src/cli.js b/packages/gatsby-recipes/src/cli.js index a5f10c9889209..e9416ada10624 100644 --- a/packages/gatsby-recipes/src/cli.js +++ b/packages/gatsby-recipes/src/cli.js @@ -280,6 +280,8 @@ const components = { Directory: () => null, GatsbyShadowFile: () => null, NPMScript: () => null, + RecipeIntroduction: () => null, + RecipeStep: () => null, } let logStream diff --git a/packages/gatsby-recipes/src/create-plan.js b/packages/gatsby-recipes/src/create-plan.js index 39d7815197fe5..cbd1d601bf42c 100644 --- a/packages/gatsby-recipes/src/create-plan.js +++ b/packages/gatsby-recipes/src/create-plan.js @@ -4,11 +4,11 @@ const render = require(`./renderer`) // const SITE_ROOT = process.cwd() // const ctx = { root: SITE_ROOT } -module.exports = async context => { +module.exports = async (context, cb) => { const stepAsMdx = context.steps[context.currentStep] try { - const result = await render(stepAsMdx) + const result = await render(stepAsMdx, cb) return result } catch (e) { throw e diff --git a/packages/gatsby-recipes/src/gui.js b/packages/gatsby-recipes/src/gui.js new file mode 100644 index 0000000000000..e2b150f7a58bb --- /dev/null +++ b/packages/gatsby-recipes/src/gui.js @@ -0,0 +1,574 @@ +/** @jsx jsx */ +import { jsx } from "theme-ui" +const lodash = require(`lodash`) +const React = require(`react`) +const { useState } = require(`react`) +const MDX = require(`@mdx-js/runtime`).default +const ansi2HTML = require(`ansi-html`) +const { + CircularProgressbarWithChildren, +} = require(`react-circular-progressbar`) +require(`react-circular-progressbar/dist/styles.css`) +const { + createClient, + useMutation, + useSubscription, + Provider, + defaultExchanges, + subscriptionExchange, +} = require(`urql`) +const { SubscriptionClient } = require(`subscriptions-transport-ws`) +const semver = require(`semver`) + +const SelectInput = `select` + +// Check for what version of React is loaded & warn if it's too low. +if (semver.lt(React.version, `16.8.0`)) { + console.log( + `Recipes works best with newer versions of React. Please file a bug report if you see this warning.` + ) +} + +const PROJECT_ROOT = `/Users/kylemathews/projects/gatsby/starters/blog` + +const Boxen = `div` +const Text = `p` +const Static = `div` +const Color = `span` +const Spinner = () => Loading... + +const WelcomeMessage = () => ( + <> + + Thank you for trying the experimental version of Gatsby Recipes! + +
+ Please ask questions, share your recipes, report bugs, and subscribe for + updates in our umbrella issue at + https://github.com/gatsbyjs/gatsby/issues/22991 +
+ +) + +const RecipesList = ({ setRecipe }) => { + const items = [ + { + label: `Add a custom ESLint config`, + value: `eslint.mdx`, + }, + { + label: `Add Jest`, + value: `jest.mdx`, + }, + { + label: `Add Gatsby Theme Blog`, + value: `gatsby-theme-blog`, + }, + { + label: `Add persistent layout component with gatsby-plugin-layout`, + value: `gatsby-plugin-layout`, + }, + { + label: `Add Theme UI`, + value: `theme-ui.mdx`, + }, + { + label: `Add Emotion`, + value: `emotion.mdx`, + }, + { + label: `Add support for MDX Pages`, + value: `mdx-pages.mdx`, + }, + { + label: `Add support for MDX Pages with images`, + value: `mdx-images.mdx`, + }, + { + label: `Add Styled Components`, + value: `styled-components.mdx`, + }, + { + label: `Add Tailwind`, + value: `tailwindcss.mdx`, + }, + { + label: `Add Sass`, + value: `sass.mdx`, + }, + { + label: `Add Typescript`, + value: `typescript.mdx`, + }, + { + label: `Add Cypress testing`, + value: `cypress.mdx`, + }, + { + label: `Add animated page transition support`, + value: `animated-page-transitions.mdx`, + }, + { + label: `Add plugins to make site a PWA`, + value: `pwa.mdx`, + }, + { + label: `Add React Helmet`, + value: `gatsby-plugin-react-helmet.mdx`, + }, + { + label: `Add Storybook - JavaScript`, + value: `storybook-js.mdx`, + }, + { + label: `Add Storybook - TypeScript`, + value: `storybook-ts.mdx`, + }, + // TODO remaining recipes + ] + + return ( + setRecipe(e.target.value)}> + {items.map(item => ( + + ))} + + ) +} + +const Div = props =>
+ +const components = { + inlineCode: props => , + Config: () => null, + GatsbyPlugin: () => null, + NPMPackageJson: () => null, + NPMPackage: () => null, + File: () => null, + GatsbyShadowFile: () => null, + NPMScript: () => null, + RecipeIntroduction: props => ( +
+ ), + RecipeStep: props => ( +
+ ), +} + +const log = (label, textOrObj) => { + console.log(label, textOrObj) +} + +log( + `started client`, + `======================================= ${new Date().toJSON()}` +) + +const RecipeGui = ({ + recipe = `jest.mdx`, + graphqlPort = 4000, + projectRoot = PROJECT_ROOT, +}) => { + try { + const GRAPHQL_ENDPOINT = `http://localhost:${graphqlPort}/graphql` + + const subscriptionClient = new SubscriptionClient( + `ws://localhost:${graphqlPort}/graphql`, + { + reconnect: true, + } + ) + + let showRecipesList = false + + if (!recipe) { + showRecipesList = true + } + + const client = createClient({ + fetch, + url: GRAPHQL_ENDPOINT, + exchanges: [ + ...defaultExchanges, + subscriptionExchange({ + forwardSubscription(operation) { + return subscriptionClient.request(operation) + }, + }), + ], + }) + + const RecipeInterpreter = () => { + // eslint-disable-next-line + const [localRecipe, setRecipe] = useState(recipe) + + const [subscriptionResponse] = useSubscription( + { + query: ` + subscription { + operation { + state + } + } + `, + }, + (_prev, now) => now + ) + + // eslint-disable-next-line + const [_, createOperation] = useMutation(` + mutation ($recipePath: String!, $projectRoot: String!) { + createOperation(recipePath: $recipePath, projectRoot: $projectRoot) + } + `) + // eslint-disable-next-line + const [__, sendEvent] = useMutation(` + mutation($event: String!) { + sendEvent(event: $event) + } + `) + + subscriptionClient.connectionCallback = async () => { + if (!showRecipesList) { + log(`createOperation`) + try { + await createOperation({ recipePath: localRecipe, projectRoot }) + } catch (e) { + log(`error creating operation`, e) + } + } + } + + log(`subscriptionResponse`, subscriptionResponse) + const state = + subscriptionResponse.data && + JSON.parse(subscriptionResponse.data.operation.state) + + log(`subscriptionResponse.data`, subscriptionResponse.data) + + function Wrapper({ children }) { + return
{children}
+ } + + if (showRecipesList) { + return ( + + + Select a recipe to run + { + console.log(recipeItem) + showRecipesList = false + try { + await createOperation({ + recipePath: recipeItem, + projectRoot, + }) + } catch (e) { + log(`error creating operation`, e) + } + }} + /> + + ) + } + + if (!state) { + console.log(`Loading recipe!`) + return ( + + Loading recipe + + ) + } + + console.log(state) + console.log(`!!!!!!`) + + const isDone = state.value === `done` + + if (state.value === `doneError`) { + console.error(state) + } + + if (true) { + log(`state`, state) + log(`plan`, state.context.plan) + log(`stepResources`, state.context.stepResources) + } + + const Step = ({ state, step, i }) => { + const [output, setOutput] = useState({ + title: ``, + body: ``, + date: new Date(), + }) + + const [complete, setComplete] = useState(false) + if (output.title !== `` && output.body !== ``) { + setTimeout(() => { + setComplete(true) + }, 0) + } else { + setTimeout(() => { + setComplete(false) + }, 0) + } + + return ( +
+
*": { + marginY: 0, + }, + background: `PaleGoldenRod`, + borderTopLeftRadius: 20, + borderTopRightRadius: 20, + padding: 2, + }} + > +
*": { + marginY: 0, + }, + display: `flex`, + alignItems: `flex-start`, + }} + > +
+ + {/* Put any JSX content in here that you'd like. It'll be vertically and horizonally centered. */} + +
+ 5/7 +
+
+
+
+
*": { + marginTop: 0, + }, + }} + > + {step} +
+
+
+
+
+ +
+
+ { + const newOutput = { ...output, title: e.target.value } + setOutput(newOutput) + }} + /> +
+
+ +
+
+