Skip to content

Commit

Permalink
refactor(gatsby-cli): rework cli to use ink (gatsbyjs#13089)
Browse files Browse the repository at this point in the history
Replaces our augmented yurnalist reporter with ink. Everything still works the same as before so no changes were made to the reporter api.
  • Loading branch information
wardpeet authored May 21, 2019
1 parent 2e82028 commit 82848c9
Show file tree
Hide file tree
Showing 11 changed files with 425 additions and 35 deletions.
8 changes: 7 additions & 1 deletion packages/gatsby-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,26 @@
"fs-extra": "^4.0.1",
"gatsby-telemetry": "^1.0.9",
"hosted-git-info": "^2.6.0",
"ink": "^2.0.5",
"ink-spinner": "^3.0.1",
"is-valid-path": "^0.1.1",
"lodash": "^4.17.10",
"meant": "^1.0.1",
"node-fetch": "2.3.0",
"object.entries": "^1.1.0",
"opentracing": "^0.14.3",
"pretty-error": "^2.1.1",
"prompts": "^2.0.4",
"react": "^16.8.4",
"resolve-cwd": "^2.0.0",
"semver": "^6.0.0",
"source-map": "^0.5.7",
"stack-trace": "^0.0.10",
"strip-ansi": "^5.2.0",
"update-notifier": "^2.3.0",
"uuid": "3.3.2",
"yargs": "^12.0.5",
"yurnalist": "^1.0.2"
"yurnalist": "^1.0.5"
},
"devDependencies": {
"@babel/cli": "^7.0.0",
Expand Down
5 changes: 4 additions & 1 deletion packages/gatsby-cli/src/reporter/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const chalk = require(`chalk`)
const { trackError } = require(`gatsby-telemetry`)
const tracer = require(`opentracing`).globalTracer()
const { getErrorFormatter } = require(`./errors`)
const reporterInstance = require(`./reporters/yurnalist`)
const reporterInstance = require(`./reporters`)

const errorFormatter = getErrorFormatter()

Expand Down Expand Up @@ -61,6 +61,7 @@ const reporter: Reporter = {
error = message
message = error.message
}

reporterInstance.error(message)
if (error) this.log(errorFormatter.render(error))
},
Expand All @@ -71,11 +72,13 @@ const reporter: Reporter = {
uptime(prefix) {
this.verbose(`${prefix}: ${(process.uptime() * 1000).toFixed(3)}ms`)
},

success: reporterInstance.success,
verbose: reporterInstance.verbose,
info: reporterInstance.info,
warn: reporterInstance.warn,
log: reporterInstance.log,

/**
* Time an activity.
* @param {string} name - Name of activity.
Expand Down
8 changes: 8 additions & 0 deletions packages/gatsby-cli/src/reporter/reporters/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const semver = require(`semver`)
const { isCI } = require(`ci-info`)

if (semver.satisfies(process.version, `>=8`) && !isCI) {
module.exports = require(`./ink`).default
} else {
module.exports = require(`./yurnalist`)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from "react"
import convertHrtime from "convert-hrtime"
import { Box } from "ink"
import Spinner from "ink-spinner"

export const calcElapsedTime = startTime => {
const elapsed = process.hrtime(startTime)

return convertHrtime(elapsed)[`seconds`].toFixed(3)
}

export default function Activity({ name, status }) {
let statusText = name
if (status) {
statusText += ` — ${status}`
}

return (
<Box>
<Spinner type="dots" /> {statusText}
</Box>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from "react"
import { Color } from "ink"
import stripAnsi from "strip-ansi"

const ColorSwitcher = ({ hideColors, children, ...props }) => {
if (hideColors) {
return <>{stripAnsi(children)}</>
}

return <Color {...props}>{children}</Color>
}

const createLabel = (text, color) => (...props) => (
<ColorSwitcher {...{ [color]: true, ...props }}>{text}</ColorSwitcher>
)

const getLabel = type => {
switch (type) {
case `success`:
return createLabel(`success`, `green`)
case `error`:
return createLabel(`error`, `red`)
case `warn`:
return createLabel(`warn`, `yellow`)
case `verbose`:
return createLabel(`verbose`, `gray`)
case `info`:
return createLabel(`info`, `blue`)
default:
return createLabel(`debug ${type}`, `blue`)
}
}

export const Message = ({ type, hideColors, children }) => {
if (!type) {
return <>{children}</>
}

const TextLabel = getLabel(type)

return (
<>
<TextLabel hideColors={hideColors} />
{` `}
{children}
</>
)
}
9 changes: 9 additions & 0 deletions packages/gatsby-cli/src/reporter/reporters/ink/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from "react"
import { render } from "ink"
import GatsbyReporter from "./reporter"

// render the react component and expose it so it can be changed from the outside world
const inkReporter = React.createRef()
render(<GatsbyReporter ref={inkReporter} />)

export default /** @type {GatsbyReporter} */ (inkReporter.current)
144 changes: 144 additions & 0 deletions packages/gatsby-cli/src/reporter/reporters/ink/reporter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import React from "react"
import { Static, Box } from "ink"
import { isCI } from "ci-info"
import chalk from "chalk"
import Activity, { calcElapsedTime } from "./components/activity"
import { Message } from "./components/messages"

const showProgress = process.stdout.isTTY && !isCI

const generateActivityFinishedText = (name, activity) => {
let successText = `${name} - ${calcElapsedTime(activity.startTime)} s`
if (activity.status) {
successText += ` — ${activity.status}`
}

return successText
}

export default class GatsbyReporter extends React.Component {
verbose = process.env.gatsby_log_level === `verbose`
state = {
verbose: false,
messages: [],
activities: {},
}

format = chalk

createActivity = name => {
return {
start: () => {
this.setState(state => {
return {
activities: {
...state.activities,
[name]: {
status: ``,
startTime: process.hrtime(),
},
},
}
})
},
setStatus: status => {
this.setState(state => {
const activity = state.activities[name]

return {
activities: {
...state.activities,
[name]: {
...activity,
status: status,
},
},
}
})
},
end: () => {
const activity = this.state.activities[name]

this.success(generateActivityFinishedText(name, activity))

this.setState(state => {
const activities = { ...state.activities }
delete activities[name]

return {
activities,
}
})
},
}
}

setColors(useColors = false) {
this.setState({
disableColors: !useColors,
})
}

setVerbose(isVerbose = true) {
this.verbose = isVerbose
}

_addMessage(type, str) {
// threat null/undefind as an empty character, it seems like ink can't handle empty str
if (!str) {
str = `\u2800`
}

this.setState(state => {
return {
messages: [
...state.messages,
{
text: str,
type,
},
],
}
})
}

log = this._addMessage.bind(this, null)
info = this._addMessage.bind(this, `info`)
success = this._addMessage.bind(this, `success`)
warn = this._addMessage.bind(this, `warn`)
error = this._addMessage.bind(this, `error`)
verbose = str => {
if (!this.verbose) {
return
}

this._addMessage(`verbose`, str)
}

render() {
return (
<Box flexDirection="column">
<Box flexDirection="column">
<Static>
{this.state.messages.map((msg, index) => (
<Box textWrap="wrap" key={index}>
<Message type={msg.type} hideColors={this.state.disableColors}>
{msg.text}
</Message>
</Box>
))}
</Static>

{showProgress &&
Object.keys(this.state.activities).map(activityName => (
<Activity
key={activityName}
name={activityName}
{...this.state.activities[activityName]}
/>
))}
</Box>
</Box>
)
}
}
2 changes: 2 additions & 0 deletions packages/gatsby/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@
"@babel/runtime": "^7.0.0",
"babel-preset-gatsby-package": "^0.1.4",
"cross-env": "^5.1.4",
"react": "^16.4.2",
"react-dom": "^16.4.2",
"rimraf": "^2.6.1"
},
"engines": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -976,18 +976,36 @@ Map {
`;

exports[`File parser extracts query AST correctly from files 2`] = `
Array [
"warning
[MockFunction] {
"calls": Array [
Array [
"
We were unable to find the declaration of variable \\"strangeQueryName\\", which you passed as the \\"query\\" prop into the <StaticQuery> declaration in \\"query-not-defined.js\\".
Perhaps the variable name has a typo?
Also note that we are currently unable to use queries defined in files other than the file where the <StaticQuery> is defined. If you're attempting to import the query, please move it into \\"query-not-defined.js\\". If being able to import queries from another file is an important capability for you, we invite your help fixing it.",
"warning
Also note that we are currently unable to use queries defined in files other than the file where the <StaticQuery> is defined. If you're attempting to import the query, please move it into \\"query-not-defined.js\\". If being able to import queries from another file is an important capability for you, we invite your help fixing it.
",
],
Array [
"
We were unable to find the declaration of variable \\"strangeQueryName\\", which you passed as the \\"query\\" prop into the <StaticQuery> declaration in \\"query-imported.js\\".
Perhaps the variable name has a typo?
Also note that we are currently unable to use queries defined in files other than the file where the <StaticQuery> is defined. If you're attempting to import the query, please move it into \\"query-imported.js\\". If being able to import queries from another file is an important capability for you, we invite your help fixing it.",
]
Also note that we are currently unable to use queries defined in files other than the file where the <StaticQuery> is defined. If you're attempting to import the query, please move it into \\"query-imported.js\\". If being able to import queries from another file is an important capability for you, we invite your help fixing it.
",
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
Object {
"type": "return",
"value": undefined,
},
],
}
`;
15 changes: 4 additions & 11 deletions packages/gatsby/src/query/__tests__/file-parser.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
jest.mock(`fs-extra`, () => {
const fs = jest.requireActual(`fs`)
const fs = jest.requireActual(`fs-extra`)
return {
...fs,
readFile: jest.fn(),
}
})
jest.mock(`../../utils/api-runner-node`, () => () => [])

jest.mock(`gatsby-cli/lib/reporter/index`)
const reporter = require(`gatsby-cli/lib/reporter`)
const fs = require(`fs-extra`)

const FileParser = require(`../file-parser`).default
Expand Down Expand Up @@ -153,16 +154,8 @@ export default () => (
})

it(`extracts query AST correctly from files`, async () => {
const spyStderr = jest.spyOn(process.stderr, `write`)
const results = await parser.parseFiles(Object.keys(MOCK_FILE_INFO))
expect(results).toMatchSnapshot()
expect(
spyStderr.mock.calls
.filter(c => c[0].includes(`warning`))
// Remove console colors + trim whitespace
// eslint-disable-next-line
.map(c => c[0].replace(/\x1B[[(?);]{0,2}(;?\d)*./g, ``).trim())
).toMatchSnapshot()
spyStderr.mockRestore()
expect(reporter.warn).toMatchSnapshot()
})
})
Loading

0 comments on commit 82848c9

Please sign in to comment.