Skip to content

Commit

Permalink
Convert create-cli.js (gatsby-cli) to TypeScript (#23650)
Browse files Browse the repository at this point in the history
* chore(gatsby-cli) Add basic type annotations to create-cli.ts

* Update resolve-cwd to 3.0.0 as it has TS definitions
* Add @types/yargs
* Add most obvious type annotations and checks to create-cli.ts
* Make report.panic return "never" as it's supposed to

NOTE:
This still leaves a lot of any types in create-cli,
especially when it comes to require('./some-local-file')
expressions and parameters/options of yargs commands.
Making everything type-safe might introduce a lot of complexity though,
so one should think about benefits vs costs of doing this

* chore(create-cli) Replace .fail with .recommendCommands

* chore(gatsby-cli) Add types and type checks to command handlers

NOTE: gatsby new arguments are now stringified before use.
This makes the compiler happy and also it's now possible
to init a gastsby project inside a folder with number as a name,
in case you want to "gatsby new 42", which caused an error before

* Address test failures

Co-authored-by: Blaine Kasten <blainekasten@gmail.com>
  • Loading branch information
alexpyzhianov and blainekasten authored May 15, 2020
1 parent 4b332ed commit 623bb06
Show file tree
Hide file tree
Showing 10 changed files with 109 additions and 110 deletions.
2 changes: 1 addition & 1 deletion integration-tests/gatsby-cli/__tests__/develop.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ describe(`gatsby develop`, () => {
const [childProcess, getLogs] = GatsbyCLI.from(cwd).invokeAsync(`develop`)

// 2. Wait for the build process to finish
await timeout(10)
await timeout(15)

// 3. kill the `gatsby develop` command so we can get logs
childProcess.kill()
Expand Down
3 changes: 2 additions & 1 deletion packages/gatsby-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"prompts": "^2.3.2",
"react": "^16.8.0",
"redux": "^4.0.5",
"resolve-cwd": "^2.0.0",
"resolve-cwd": "^3.0.0",
"semver": "^6.3.0",
"signal-exit": "^3.0.3",
"source-map": "0.7.3",
Expand All @@ -55,6 +55,7 @@
"@babel/cli": "^7.8.4",
"@babel/core": "^7.9.6",
"@types/hosted-git-info": "^3.0.0",
"@types/yargs": "^15.0.4",
"babel-preset-gatsby-package": "^0.4.1",
"cross-env": "^5.2.1"
},
Expand Down
12 changes: 6 additions & 6 deletions packages/gatsby-cli/src/__tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ const getCLI = () => {
jest.resetModules()

const reporter = require(`../reporter`)
const createCLI = require(`../create-cli`)
const { createCli } = require(`../create-cli`)

require(`../`)

return {
reporter,
createCLI,
createCli,
}
}

Expand Down Expand Up @@ -93,10 +93,10 @@ describe(`normal behavior`, () => {
})
})

it(`invokes createCLI`, () => {
const { createCLI } = setup()
it(`invokes createCli`, () => {
const { createCli } = setup()

expect(createCLI).toHaveBeenCalledTimes(1)
expect(createCLI).toHaveBeenCalledWith(process.argv)
expect(createCli).toHaveBeenCalledTimes(1)
expect(createCli).toHaveBeenCalledWith(process.argv)
})
})
Original file line number Diff line number Diff line change
@@ -1,28 +1,24 @@
const path = require(`path`)
const resolveCwd = require(`resolve-cwd`)
const yargs = require(`yargs`)
const report = require(`./reporter`)
const { setStore } = require(`./reporter/redux`)
const { didYouMean } = require(`./did-you-mean`)
const { getLocalGatsbyVersion } = require(`./util/version`)
const envinfo = require(`envinfo`)
const existsSync = require(`fs-exists-cached`).sync
const clipboardy = require(`clipboardy`)
const {
trackCli,
setDefaultTags,
setTelemetryEnabled,
} = require(`gatsby-telemetry`)
const { recipesHandler } = require(`./recipes`)

const handlerP = fn => (...args) => {
import path from "path"
import resolveCwd from "resolve-cwd"
import yargs from "yargs"
import report from "./reporter"
import { setStore } from "./reporter/redux"
import { getLocalGatsbyVersion } from "./util/version"
import envinfo from "envinfo"
import { sync as existsSync } from "fs-exists-cached"
import clipboardy from "clipboardy"
import { trackCli, setDefaultTags, setTelemetryEnabled } from "gatsby-telemetry"
import { initStarter } from "./init-starter"
import { recipesHandler } from "./recipes"

const handlerP = (fn: Function) => (...args: unknown[]): void => {
Promise.resolve(fn(...args)).then(
() => process.exit(0),
err => report.panic(err)
)
}

function buildLocalCommands(cli, isLocalSite) {
function buildLocalCommands(cli: yargs.Argv, isLocalSite: boolean): void {
const defaultHost = `localhost`
const defaultPort = `8000`
const directory = path.resolve(`.`)
Expand All @@ -33,25 +29,30 @@ function buildLocalCommands(cli, isLocalSite) {
? [`> 1%`, `last 2 versions`, `IE >= 9`]
: [`>0.25%`, `not dead`]

const siteInfo = { directory, browserslist: DEFAULT_BROWSERS }
const siteInfo = {
directory,
browserslist: DEFAULT_BROWSERS,
sitePackageJson: undefined,
}

const useYarn = existsSync(path.join(directory, `yarn.lock`))
if (isLocalSite) {
const json = require(path.join(directory, `package.json`))
siteInfo.sitePackageJson = json
siteInfo.browserslist = json.browserslist || siteInfo.browserslist
}

function getLocalGatsbyMajorVersion() {
let version = getLocalGatsbyVersion()
function getLocalGatsbyMajorVersion(): number | undefined {
const version = getLocalGatsbyVersion()

if (version) {
version = Number(version.split(`.`)[0])
return Number(version.split(`.`)[0])
}

return version
return undefined
}

function resolveLocalCommand(command) {
function resolveLocalCommand(command: string): Function | never {
if (!isLocalSite) {
cli.showHelp()
report.verbose(`current directory: ${directory}`)
Expand All @@ -73,7 +74,15 @@ function buildLocalCommands(cli, isLocalSite) {
)

report.verbose(`loading local command from: ${cmdPath}`)
return require(cmdPath)

const cmd = require(cmdPath)
if (cmd instanceof Function) {
return cmd
}

return report.panic(
`Handler for command "${command}" is not a function. Your Gatsby package might be corrupted, try reinstalling it and running the command again.`
)
} catch (err) {
cli.showHelp()
return report.panic(
Expand All @@ -83,11 +92,14 @@ function buildLocalCommands(cli, isLocalSite) {
}
}

function getCommandHandler(command, handler) {
return argv => {
function getCommandHandler(
command: string,
handler?: (args: yargs.Arguments, cmd: Function) => void
) {
return (argv: yargs.Arguments): void => {
report.setVerbose(!!argv.verbose)

report.setNoColor(argv.noColor || process.env.NO_COLOR)
report.setNoColor(!!(argv.noColor || process.env.NO_COLOR))

process.env.gatsby_log_level = argv.verbose ? `verbose` : `normal`
report.verbose(`set gatsby_log_level: "${process.env.gatsby_log_level}"`)
Expand All @@ -105,7 +117,7 @@ function buildLocalCommands(cli, isLocalSite) {

cli.command({
command: `develop`,
desc:
describe:
`Start development server. Watches files, rebuilds, and hot reloads ` +
`if something changes`,
builder: _ =>
Expand Down Expand Up @@ -160,20 +172,20 @@ function buildLocalCommands(cli, isLocalSite) {
describe: `Tracer configuration file (OpenTracing compatible). See https://gatsby.dev/tracing`,
}),
handler: handlerP(
getCommandHandler(`develop`, (args, cmd) => {
getCommandHandler(`develop`, (args: yargs.Arguments, cmd: Function) => {
process.env.NODE_ENV = process.env.NODE_ENV || `development`
cmd(args)
// Return an empty promise to prevent handlerP from exiting early.
// The development server shouldn't ever exit until the user directly
// kills it so this is fine.
return new Promise(resolve => {})
return new Promise(() => {})
})
),
})

cli.command({
command: `build`,
desc: `Build a Gatsby project.`,
describe: `Build a Gatsby project.`,
builder: _ =>
_.option(`prefix-paths`, {
type: `boolean`,
Expand Down Expand Up @@ -216,7 +228,7 @@ function buildLocalCommands(cli, isLocalSite) {
hidden: true,
}),
handler: handlerP(
getCommandHandler(`build`, (args, cmd) => {
getCommandHandler(`build`, (args: yargs.Arguments, cmd: Function) => {
process.env.NODE_ENV = `production`
return cmd(args)
})
Expand All @@ -225,7 +237,7 @@ function buildLocalCommands(cli, isLocalSite) {

cli.command({
command: `serve`,
desc: `Serve previously built Gatsby site.`,
describe: `Serve previously built Gatsby site.`,
builder: _ =>
_.option(`H`, {
alias: `host`,
Expand Down Expand Up @@ -257,15 +269,15 @@ function buildLocalCommands(cli, isLocalSite) {

cli.command({
command: `info`,
desc: `Get environment information for debugging and issue reporting`,
describe: `Get environment information for debugging and issue reporting`,
builder: _ =>
_.option(`C`, {
alias: `clipboard`,
type: `boolean`,
default: false,
describe: `Automagically copy environment information to clipboard`,
}),
handler: args => {
handler: (args: yargs.Arguments) => {
try {
const copyToClipboard =
// Clipboard is not accessible when on a linux tty
Expand Down Expand Up @@ -311,27 +323,38 @@ function buildLocalCommands(cli, isLocalSite) {

cli.command({
command: `clean`,
desc: `Wipe the local gatsby environment including built assets and cache`,
describe: `Wipe the local gatsby environment including built assets and cache`,
handler: getCommandHandler(`clean`),
})

cli.command({
command: `repl`,
desc: `Get a node repl with context of Gatsby environment, see (https://www.gatsbyjs.org/docs/gatsby-repl/)`,
handler: getCommandHandler(`repl`, (args, cmd) => {
process.env.NODE_ENV = process.env.NODE_ENV || `development`
return cmd(args)
}),
describe: `Get a node repl with context of Gatsby environment, see (https://www.gatsbyjs.org/docs/gatsby-repl/)`,
handler: getCommandHandler(
`repl`,
(args: yargs.Arguments, cmd: Function) => {
process.env.NODE_ENV = process.env.NODE_ENV || `development`
return cmd(args)
}
),
})

cli.command({
command: `recipes [recipe]`,
desc: `[EXPERIMENTAL] Run a recipe`,
handler: handlerP(({ recipe }) => recipesHandler(recipe)),
describe: `[EXPERIMENTAL] Run a recipe`,
handler: handlerP(({ recipe }: yargs.Arguments) => {
if (typeof recipe !== `string`) {
throw new Error(
`Error: gatsby recipes needs to be called with a specific recipe`
)
}

recipesHandler(recipe)
}),
})
}

function isLocalGatsbySite() {
function isLocalGatsbySite(): boolean {
let inGatsbySite = false
try {
const { dependencies, devDependencies } = require(path.resolve(
Expand All @@ -346,7 +369,7 @@ function isLocalGatsbySite() {
return !!inGatsbySite
}

function getVersionInfo() {
function getVersionInfo(): string {
const { version } = require(`../package.json`)
const isGatsbySite = isLocalGatsbySite()
if (isGatsbySite) {
Expand All @@ -365,10 +388,11 @@ Gatsby version: ${gatsbyVersion}
}
}

module.exports = argv => {
const cli = yargs().parserConfiguration({
export const createCli = (argv: string[]): yargs.Arguments => {
const cli = yargs(argv).parserConfiguration({
"boolean-negation": false,
})

const isLocalSite = isLocalGatsbySite()

cli
Expand Down Expand Up @@ -415,17 +439,19 @@ module.exports = argv => {
return cli
.command({
command: `new [rootPath] [starter]`,
desc: `Create new Gatsby project.`,
handler: handlerP(({ rootPath, starter }) => {
const { initStarter } = require(`./init-starter`)
return initStarter(starter, { rootPath })
describe: `Create new Gatsby project.`,
handler: handlerP(async ({ rootPath, starter }) => {
const starterStr = starter ? String(starter) : undefined
const rootPathStr = rootPath ? String(rootPath) : undefined

await initStarter(starterStr, rootPathStr)
}),
})
.command(`plugin`, `Useful commands relating to Gatsby plugins`, yargs =>
yargs
.command({
command: `docs`,
desc: `Helpful info about using and creating plugins`,
describe: `Helpful info about using and creating plugins`,
handler: handlerP(() =>
console.log(`
Using a plugin:
Expand Down Expand Up @@ -456,7 +482,7 @@ Creating a plugin:
)
.command({
command: `telemetry`,
desc: `Enable or disable Gatsby anonymous analytics collection.`,
describe: `Enable or disable Gatsby anonymous analytics collection.`,
builder: yargs =>
yargs
.option(`enable`, {
Expand All @@ -468,7 +494,7 @@ Creating a plugin:
description: `Disable telemetry`,
}),

handler: handlerP(({ enable, disable }) => {
handler: handlerP(({ enable, disable }: yargs.Arguments) => {
const enabled = enable || !disable
setTelemetryEnabled(enabled)
report.log(`Telemetry collection ${enabled ? `enabled` : `disabled`}`)
Expand All @@ -477,17 +503,6 @@ Creating a plugin:
.wrap(cli.terminalWidth())
.demandCommand(1, `Pass --help to see all available commands and options.`)
.strict()
.fail((msg, err, yargs) => {
const availableCommands = yargs.getCommands().map(commandDescription => {
const [command] = commandDescription
return command.split(` `)[0]
})
const arg = argv.slice(2)[0]
const suggestion = arg ? didYouMean(arg, availableCommands) : ``

cli.showHelp()
report.log(suggestion)
report.log(msg)
})
.recommendCommands()
.parse(argv.slice(2))
}
20 changes: 0 additions & 20 deletions packages/gatsby-cli/src/did-you-mean.ts

This file was deleted.

Loading

0 comments on commit 623bb06

Please sign in to comment.