diff --git a/packages/gatsby-cli/.gitignore b/packages/gatsby-cli/.gitignore index 2132b55e6405e..53c36b2056e94 100644 --- a/packages/gatsby-cli/.gitignore +++ b/packages/gatsby-cli/.gitignore @@ -1,2 +1,2 @@ -/*.js +/lib yarn.lock diff --git a/packages/gatsby-cli/package.json b/packages/gatsby-cli/package.json index 24e830931c13e..fe83756506874 100644 --- a/packages/gatsby-cli/package.json +++ b/packages/gatsby-cli/package.json @@ -4,17 +4,21 @@ "version": "1.0.12", "author": "Kyle Mathews ", "bin": { - "gatsby": "./index.js" + "gatsby": "lib/index.js" }, "dependencies": { "babel-runtime": "^6.25.0", - "commander": "^2.11.0", + "common-tags": "^1.4.0", + "convert-hrtime": "^2.0.0", "core-js": "^2.5.0", + "execa": "^0.8.0", "fs-extra": "^4.0.1", "hosted-git-info": "^2.5.0", "lodash": "^4.17.4", + "pretty-error": "^2.1.1", "resolve-cwd": "^2.0.0", - "tracer": "^0.8.9" + "yargs": "^8.0.2", + "yurnalist": "^0.2.1" }, "devDependencies": { "babel-cli": "^6.24.1" @@ -23,9 +27,9 @@ "gatsby" ], "license": "MIT", - "main": "index.js", + "main": "lib/index.js", "scripts": { - "build": "babel src --out-dir . --ignore __tests__", - "watch": "babel -w src --out-dir . --ignore __tests__" + "build": "babel src --out-dir lib --ignore __tests__", + "watch": "babel -w src --out-dir lib --ignore __tests__" } } diff --git a/packages/gatsby-cli/src/.gitkeep b/packages/gatsby-cli/src/.gitkeep deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/packages/gatsby-cli/src/create-cli.js b/packages/gatsby-cli/src/create-cli.js new file mode 100644 index 0000000000000..968fe4f7fd619 --- /dev/null +++ b/packages/gatsby-cli/src/create-cli.js @@ -0,0 +1,172 @@ +const path = require(`path`) +const resolveCwd = require(`resolve-cwd`) +const yargs = require(`yargs`) +const report = require(`./reporter`) + +const DEFAULT_BROWSERS = [ + `> 1%`, + `last 2 versions`, + `IE >= 9`, +] + +function buildLocalCommands(cli, isLocalSite) { + const defaultHost = `localhost` + const directory = path.resolve(`.`) + + function getSiteInfo() { + if (!isLocalSite) return {} + + const sitePackageJson = require(path.join(directory, `package.json`)) + const browserslist = sitePackageJson.browserslist || DEFAULT_BROWSERS + return { sitePackageJson, browserslist } + } + + function resolveLocalCommand(command) { + if (!isLocalSite) { + cli.showHelp() + report.verbose(`current directory: ${directory}`) + return report.panic( + `gatsby <${command}> can only be run for a gatsby site. \n` + + `Either the current working directory does not contain a package.json or ` + + `'gatsby' is not specified as a dependency` + ) + } + try { + return require(resolveCwd(`gatsby/dist/commands/${command}`)) + } catch (err) { + cli.showHelp() + return report.panic( + `There was a problem loading the local ${command} command. Gatsby may not be installed.`, + err + ) + } + } + + cli.command({ + command: `develop`, + desc: + `Start development server. Watches files, rebuilds, and hot reloads ` + + `if something changes`, + builder: _ => _ + .option(`H`, { + alias: `host`, + type: `string`, + describe: `Set host. Defaults to ${defaultHost}`, + }) + .option(`p`, { + alias: `port`, + type: `string`, + default: `8000`, + describe: `Set port. Defaults to 8000`, + }) + .option(`o`, { + alias: `open`, + type: `boolean`, + describe: `Open the site in your browser for you.`, + }), + handler: argv => { + const { sitePackageJson, browserslist } = getSiteInfo() + + resolveLocalCommand(`develop`)({ + ...argv, + directory, + sitePackageJson, + browserslist, + }) + }, + }) + + cli + .command({ + command: `build`, + desc: `Build a Gatsby project.`, + builder: _ => _ + .option(`prefix-paths`, { + type: `string`, + describe: `Build site with link paths prefixed (set prefix in your config).`, + }), + handler: argv => { + process.env.NODE_ENV = `production` + const { sitePackageJson, browserslist } = getSiteInfo() + + resolveLocalCommand(`build`)({ + ...argv, + directory, + sitePackageJson, + browserslist, + }) + }, + }) + + cli + .command({ + command: `serve`, + desc: `Serve previously built Gatsby site.`, + builder: _ => _ + .option(`H`, { + alias: `host`, + type: `string`, + describe: `Set host. Defaults to ${defaultHost}`, + }) + .option(`p`, { + alias: `port`, + type: `string`, + default: `8000`, + describe: `Set port. Defaults to 8000`, + }) + .option(`o`, { + alias: `open`, + type: `boolean`, + default: `8000`, + describe: `Open the site in your browser for you.`, + }), + + handler: argv => { + const { sitePackageJson, browserslist } = getSiteInfo() + + resolveLocalCommand(`serve`)({ + ...argv, + directory, + sitePackageJson, + browserslist, + }) + }, + }) +} + +function isLocalGatsbySite() { + let inGatsbySite = false + try { + let { dependencies, devDependencies } = require(path.resolve(`./package.json`)) + inGatsbySite = ( + (dependencies && dependencies.gatsby) || + (devDependencies && devDependencies.gatsby) + ) + } catch (err) { /* ignore */ } + return inGatsbySite +} + +module.exports = (argv, handlers) => { + let cli = yargs() + let isLocalSite = isLocalGatsbySite() + + cli + .usage(`Usage: $0 [options]`) + .help(`h`).alias(`h`, `help`) + .version().alias(`v`, `version`) + + buildLocalCommands(cli, isLocalSite) + + return cli + .command({ + command: `new [rootPath] [starter]`, + desc: `Create new Gatsby project.`, + handler: ({ rootPath, starter = `gatsbyjs/gatsby-starter-default` }) => { + require(`./init-starter`)(starter, { rootPath }) + }, + }) + .wrap(cli.terminalWidth()) + .demandCommand(1, `Pass --help to see all available commands and options.`) + .showHelpOnFail(true, `A command is required.`) + .parse(argv.slice(2)) +} diff --git a/packages/gatsby-cli/src/index.js b/packages/gatsby-cli/src/index.js index 0147df21abd82..5580b804c5be8 100755 --- a/packages/gatsby-cli/src/index.js +++ b/packages/gatsby-cli/src/index.js @@ -4,150 +4,40 @@ // use require() with backtick strings so use the es6 syntax import "babel-polyfill" -const program = require(`commander`) -const packageJson = require(`./package.json`) -const path = require(`path`) -const _ = require(`lodash`) const resolveCwd = require(`resolve-cwd`) +const createCli = require(`./create-cli`) +const report = require(`./reporter`) -program.version(packageJson.version).usage(`[command] [options]`) +global.Promise = require(`bluebird`) -let inGatsbySite = false -let localPackageJSON -try { - localPackageJSON = require(path.resolve(`./package.json`)) - if ( - (localPackageJSON.dependencies && localPackageJSON.dependencies.gatsby) || - (localPackageJSON.devDependencies && - localPackageJSON.devDependencies.gatsby) - ) { - inGatsbySite = true - } else if ( - localPackageJSON.devDependencies && - localPackageJSON.devDependencies.gatsby - ) { - inGatsbySite = true - } -} catch (err) { - // ignore -} - -const defaultHost = `localhost` - -const directory = path.resolve(`.`) -const getSiteInfo = () => { - const sitePackageJson = require(path.join(directory, `package.json`)) - const browserslist = sitePackageJson.browserslist || [ - `> 1%`, - `last 2 versions`, - `IE >= 9`, - ] - return { sitePackageJson, browserslist } -} - -// If there's a package.json in the current directory w/ a gatsby dependency -// include the develop/build/serve commands. Otherwise, just the new. -if (inGatsbySite) { - program - .command(`develop`) - .description( - `Start development server. Watches files and rebuilds and hot reloads ` + - `if something changes` - ) // eslint-disable-line max-len - .option( - `-H, --host `, - `Set host. Defaults to ${defaultHost}`, - defaultHost - ) - .option(`-p, --port `, `Set port. Defaults to 8000`, `8000`) - .option(`-o, --open`, `Open the site in your browser for you.`) - .action(command => { - const developPath = resolveCwd(`gatsby/dist/utils/develop`) - const develop = require(developPath) - const { sitePackageJson, browserslist } = getSiteInfo() - const p = { - ...command, - directory, - sitePackageJson, - browserslist, - } - develop(p) - }) - - program - .command(`build`) - .description(`Build a Gatsby project.`) - .option( - `--prefix-paths`, - `Build site with link paths prefixed (set prefix in your config).` - ) - .action(command => { - // Set NODE_ENV to 'production' - process.env.NODE_ENV = `production` - - const buildPath = resolveCwd(`gatsby/dist/utils/build`) - const build = require(buildPath) - const { sitePackageJson, browserslist } = getSiteInfo() - const p = { - ...command, - directory, - sitePackageJson, - browserslist, - } - build(p).then(() => { - console.log(`Done building in`, process.uptime(), `seconds`) - process.exit() - }) - }) +const version = process.version +const verDigit = Number(version.match(/\d+/)[0]) - program - .command(`serve`) - .description(`Serve built site.`) - .option( - `-H, --host `, - `Set host. Defaults to ${defaultHost}`, - defaultHost - ) - .option(`-p, --port `, `Set port. Defaults to 9000`, `9000`) - .option(`-o, --open`, `Open the site in your browser for you.`) - .action(command => { - const servePath = resolveCwd(`gatsby/dist/utils/serve`) - const serve = require(servePath) - const { sitePackageJson, browserslist } = getSiteInfo() - const p = { - ...command, - directory, - sitePackageJson, - browserslist, - } - serve(p) - }) +if (verDigit < 4) { + report.panic( + `Gatsby 1.0+ requires node.js v4 or higher (you have ${version}). \n` + + `Upgrade node to the latest stable release.` + ) } -program - .command(`new [rootPath] [starter]`) - .description(`Create new Gatsby project.`) - .action((rootPath, starter) => { - const newCommand = require(`./new`) - newCommand(rootPath, starter) - }) +Promise.onPossiblyUnhandledRejection(error => { + report.error(error) + throw error +}) -program.on(`--help`, () => { - console.log( - `To show subcommand help: +process.on(`unhandledRejection`, error => { + // This will exit the process in newer Node anyway so lets be consistent + // across versions and crash + report.panic(`UNHANDLED REJECTION`, error) +}) - gatsby [command] -h -` - ) +process.on(`uncaughtException`, error => { + report.panic(`UNHANDLED EXCEPTION`, error) }) -// If the user types an unknown sub-command, just display the help. -const subCmd = process.argv.slice(2, 3)[0] -let cmds = _.map(program.commands, `_name`) -cmds = cmds.concat([`--version`, `-V`]) -if (!_.includes(cmds, subCmd)) { - program.help() -} else { - program.parse(process.argv) -} +createCli(process.argv, { + develop: () => require(resolveCwd(`gatsby/dist/commands/develop`)), + build: () => require(resolveCwd(`gatsby/dist/commands/build`)), + serve: () => require(resolveCwd(`gatsby/dist/commands/serve`)), +}) diff --git a/packages/gatsby-cli/src/init-starter.js b/packages/gatsby-cli/src/init-starter.js index e14dcfeb6c645..2a27ebbe1814e 100644 --- a/packages/gatsby-cli/src/init-starter.js +++ b/packages/gatsby-cli/src/init-starter.js @@ -1,10 +1,16 @@ -/* @flow weak */ -import { exec, execSync } from "child_process" -import hostedGitInfo from "hosted-git-info" -import fs from "fs-extra" -import sysPath from "path" - -let logger = console +/* @flow */ +const { execSync } = require(`child_process`) +const execa = require(`execa`) +const hostedGitInfo = require(`hosted-git-info`) +const fs = require(`fs-extra`) +const sysPath = require(`path`) +const report = require(`./reporter`) + + +const spawn = (cmd: string) => { + const [file, ...args] = cmd.split(/\s+/) + return execa(file, args, { stdio: `inherit` }) +} // Checks the existence of yarn package // We use yarnpkg instead of yarn to avoid conflict with Hadoop yarn @@ -20,113 +26,79 @@ const shouldUseYarn = () => { } } -// Executes `npm install` and `bower install` in rootPath. -// -// rootPath - String. Path to directory in which command will be executed. -// callback - Function. Takes stderr and stdout of executed process. -// -// Returns nothing. -const install = (rootPath, callback) => { +// Executes `npm install` or `yarn install` in rootPath. +const install = async (rootPath) => { const prevDir = process.cwd() - logger.log(`Installing packages...`) + + report.info(`Installing packages...`) process.chdir(rootPath) - const installCmd = shouldUseYarn() ? `yarnpkg` : `npm install` - exec(installCmd, (error, stdout, stderr) => { + + try { + let cmd = shouldUseYarn() ? spawn(`yarnpkg`) : spawn(`npm install`) + await cmd + } finally { process.chdir(prevDir) - if (stdout) console.log(stdout.toString()) - if (error !== null) { - const msg = stderr.toString() - callback(new Error(msg)) - } - callback(null, stdout) - }) + } } const ignored = path => !/^\.(git|hg)$/.test(sysPath.basename(path)) // Copy starter from file system. -// -// starterPath - String, file system path from which files will be taken. -// rootPath - String, directory to which starter files will be copied. -// callback - Function. -// -// Returns nothing. -const copy = (starterPath, rootPath, callback) => { - const copyDirectory = () => { - fs.copy(starterPath, rootPath, { filter: ignored }, error => { - if (error !== null) return callback(new Error(error)) - logger.log(`Created starter directory layout`) - install(rootPath, callback) - return false - }) - } - +const copy = async (starterPath: string, rootPath: string) => { // Chmod with 755. // 493 = parseInt('755', 8) - fs.mkdirp(rootPath, { mode: 493 }, error => { - if (error !== null) callback(new Error(error)) - return fs.exists(starterPath, exists => { - if (!exists) { - const chmodError = `starter ${starterPath} doesn't exist` - return callback(new Error(chmodError)) - } - logger.log(`Copying local starter to ${rootPath} ...`) - - copyDirectory() - return true - }) - }) + await fs.mkdirp(rootPath, { mode: 493 }) + + if (!fs.existsSync(starterPath)) { + throw new Error(`starter ${starterPath} doesn't exist`) + } + report.info(`Creating new site from local starter: ${starterPath}`) + + report.log(`Copying local starter to ${rootPath} ...`) + + await fs.copy(starterPath, rootPath, { filter: ignored }) + + report.success(`Created starter directory layout`) + + await install(rootPath) + + return true } // Clones starter from URI. -// -// address - String, URI. https:, github: or git: may be used. -// rootPath - String, directory to which starter files will be copied. -// callback - Function. -// -// Returns nothing. -const clone = (hostInfo, rootPath, callback) => { +const clone = async (hostInfo: any, rootPath: string) => { const url = hostInfo.git({ noCommittish: true }) const branch = hostInfo.committish ? `-b ${hostInfo.committish}` : `` - logger.log(`Cloning git repo ${url} to ${rootPath}...`) - const cmd = `git clone ${branch} ${url} ${rootPath} --single-branch` - - exec(cmd, (error, stdout, stderr) => { - if (error !== null) { - return callback(new Error(`Git clone error: ${stderr.toString()}`)) - } - logger.log(`Created starter directory layout`) - return fs.remove(sysPath.join(rootPath, `.git`), removeError => { - if (error !== null) return callback(new Error(removeError)) - install(rootPath, callback) - return true - }) - }) + report.info(`Creating new site from git: ${url}`) + + await spawn(`git clone ${branch} ${url} ${rootPath} --single-branch`) + + report.success(`Created starter directory layout`) + + await fs.remove(sysPath.join(rootPath, `.git`)) + + await install(rootPath) } -// Main function that clones or copies the starter. -// -// starter - String, file system path or URI of starter. -// rootPath - String, directory to which starter files will be copied. -// callback - Function. -// -// Returns nothing. -const initStarter = (starter, options = {}) => - new Promise((resolve, reject) => { - const callback = (err, value) => (err ? reject(err) : resolve(value)) +type InitOptions = { + rootPath?: string +}; - const cwd = process.cwd() - const rootPath = options.rootPath || cwd - if (options.logger) logger = options.logger - if (fs.existsSync(sysPath.join(rootPath, `package.json`))) - throw new Error(`Directory ${rootPath} is already an npm project`) +/** + * Main function that clones or copies the starter. + */ +module.exports = async (starter: string, options: InitOptions = {}) => { + const rootPath = options.rootPath || process.cwd() - const hostedInfo = hostedGitInfo.fromUrl(starter) + if (fs.existsSync(sysPath.join(rootPath, `package.json`))) { + report.panic(`Directory ${rootPath} is already an npm project`) + return + } - if (hostedInfo) clone(hostedInfo, rootPath, callback) - else copy(starter, rootPath, callback) - }) + const hostedInfo = hostedGitInfo.fromUrl(starter) + if (hostedInfo) await clone(hostedInfo, rootPath) + else await copy(starter, rootPath) +} -module.exports = initStarter diff --git a/packages/gatsby-cli/src/new.js b/packages/gatsby-cli/src/new.js deleted file mode 100644 index afa14d139110e..0000000000000 --- a/packages/gatsby-cli/src/new.js +++ /dev/null @@ -1,8 +0,0 @@ -/* @flow weak */ -const logger = require(`tracer`).colorConsole() - -const initStarter = require(`./init-starter`) - -module.exports = (rootPath, starter = `gatsbyjs/gatsby-starter-default`) => { - initStarter(starter, { rootPath, logger }).catch(error => logger.error(error)) -} diff --git a/packages/gatsby/src/reporter/errors.js b/packages/gatsby-cli/src/reporter/errors.js similarity index 100% rename from packages/gatsby/src/reporter/errors.js rename to packages/gatsby-cli/src/reporter/errors.js diff --git a/packages/gatsby/src/reporter/index.js b/packages/gatsby-cli/src/reporter/index.js similarity index 100% rename from packages/gatsby/src/reporter/index.js rename to packages/gatsby-cli/src/reporter/index.js diff --git a/packages/gatsby/package.json b/packages/gatsby/package.json index 20b058ed0ce2b..71eb5f480b7a8 100644 --- a/packages/gatsby/package.json +++ b/packages/gatsby/package.json @@ -4,7 +4,7 @@ "version": "1.8.16", "author": "Kyle Mathews ", "bin": { - "gatsby": "./dist/gatsby-cli.js" + "gatsby": "./bin/gatsby.js" }, "bugs": { "url": "https://github.com/gatsbyjs/gatsby/issues" @@ -50,6 +50,7 @@ "friendly-errors-webpack-plugin": "^1.6.1", "front-matter": "^2.1.0", "fs-extra": "^3.0.1", + "gatsby-cli": "^1.0.12", "glob": "^7.1.1", "graphql": "^0.10.3", "graphql-relay": "^0.5.1", diff --git a/packages/gatsby/src/bin/cli.js b/packages/gatsby/src/bin/cli.js deleted file mode 100644 index 9b5545d15ecf1..0000000000000 --- a/packages/gatsby/src/bin/cli.js +++ /dev/null @@ -1,165 +0,0 @@ -// babel-preset-env doesn't find this import if you -// use require() with backtick strings so use the es6 syntax -import "babel-polyfill" - -const program = require(`commander`) -const packageJson = require(`../../package.json`) -const path = require(`path`) -const _ = require(`lodash`) -const Promise = require(`bluebird`) -const resolveCwd = require(`resolve-cwd`) - -const report = require(`../reporter`) - -// Improve Promise error handling. Maybe... what's the best -// practice for this these days? -global.Promise = require(`bluebird`) - -Promise.onPossiblyUnhandledRejection(error => { - report.error(error) - throw error -}) - -process.on(`unhandledRejection`, error => { - // This will exit the process in newer Node anyway so lets be consistent - // across versions and crash - report.panic(`UNHANDLED REJECTION`, error) -}) - -process.on(`uncaughtException`, error => { - report.panic(`UNHANDLED EXCEPTION`, error) -}) - -const defaultHost = `localhost` - -let inGatsbySite = false -let localPackageJSON -try { - localPackageJSON = require(path.resolve(`./package.json`)) - if (localPackageJSON.dependencies && localPackageJSON.dependencies.gatsby) { - inGatsbySite = true - } -} catch (err) { - // ignore -} - -const directory = path.resolve(`.`) -const getSiteInfo = () => { - const sitePackageJson = require(path.join(directory, `package.json`)) - const browserslist = sitePackageJson.browserslist || [ - `> 1%`, - `last 2 versions`, - `IE >= 9`, - ] - return { sitePackageJson, browserslist } -} - -program.version(packageJson.version).usage(`[command] [options]`) - -// If there's a package.json in the current directory w/ a gatsby dependency -// include the develop/build/serve commands. Otherwise, just the new. -if (inGatsbySite) { - program - .command(`develop`) - .description( - `Start development server. Watches files and rebuilds and hot reloads ` + - `if something changes` - ) // eslint-disable-line max-len - .option( - `-H, --host `, - `Set host. Defaults to ${defaultHost}`, - defaultHost - ) - .option(`-p, --port `, `Set port. Defaults to 8000`, `8000`) - .option(`-o, --open`, `Open the site in your browser for you.`) - .action(command => { - const developPath = resolveCwd(`gatsby/dist/utils/develop`) - const develop = require(developPath) - // console.timeEnd(`time to load develop`) - const { sitePackageJson, browserslist } = getSiteInfo() - const p = { - ...command, - directory, - sitePackageJson, - browserslist, - } - develop(p) - }) - - program - .command(`build`) - .description(`Build a Gatsby project.`) - .option( - `--prefix-paths`, - `Build site with link paths prefixed (set prefix in your config).` - ) - .action(command => { - // Set NODE_ENV to 'production' - process.env.NODE_ENV = `production` - - const buildPath = resolveCwd(`gatsby/dist/utils/build`) - const build = require(buildPath) - const { sitePackageJson, browserslist } = getSiteInfo() - const p = { - ...command, - directory, - sitePackageJson, - browserslist, - } - build(p).then(() => { - report.success(`Done building in ${process.uptime()} seconds`) - process.exit() - }) - }) - - program - .command(`serve`) - .description(`Serve built site.`) - .option( - `-H, --host `, - `Set host. Defaults to ${defaultHost}`, - defaultHost - ) - .option(`-p, --port `, `Set port. Defaults to 9000`, `9000`) - .option(`-o, --open`, `Open the site in your browser for you.`) - .action(command => { - const servePath = resolveCwd(`gatsby/dist/utils/serve`) - const serve = require(servePath) - const { sitePackageJson, browserslist } = getSiteInfo() - const p = { - ...command, - directory, - sitePackageJson, - browserslist, - } - serve(p) - }) -} - -program - .command(`new [rootPath] [starter]`) - .description(`Create new Gatsby project.`) - .action((rootPath, starter) => { - const newCommand = require(`../utils/new`) - newCommand(rootPath, starter) - }) - -program.on(`--help`, () => { - console.log( - `To show subcommand help: - - gatsby [command] -h -` - ) -}) - -// If the user types an unknown sub-command, just display the help. -const subCmd = process.argv.slice(2, 3)[0] -let cmds = _.map(program.commands, `_name`) -cmds = cmds.concat([`--version`, `-V`]) - -if (!_.includes(cmds, subCmd)) { - program.help() -} else { - program.parse(process.argv) -} diff --git a/packages/gatsby/src/bin/gatsby.js b/packages/gatsby/src/bin/gatsby.js new file mode 100644 index 0000000000000..6c430b053d924 --- /dev/null +++ b/packages/gatsby/src/bin/gatsby.js @@ -0,0 +1,3 @@ +#!/usr/bin/env node + +require(`gatsby-cli`) diff --git a/packages/gatsby/src/bootstrap/index.js b/packages/gatsby/src/bootstrap/index.js index 2bf2c0d42a976..6859400186b6b 100644 --- a/packages/gatsby/src/bootstrap/index.js +++ b/packages/gatsby/src/bootstrap/index.js @@ -14,7 +14,7 @@ const { graphql } = require(`graphql`) const { store, emitter } = require(`../redux`) const loadPlugins = require(`./load-plugins`) const { initCache } = require(`../utils/cache`) -const report = require(`../reporter`) +const report = require(`gatsby-cli/lib/reporter`) const { extractQueries, diff --git a/packages/gatsby/src/bootstrap/load-plugins.js b/packages/gatsby/src/bootstrap/load-plugins.js index 9b9953f003e66..82da423dd477d 100644 --- a/packages/gatsby/src/bootstrap/load-plugins.js +++ b/packages/gatsby/src/bootstrap/load-plugins.js @@ -8,7 +8,7 @@ const glob = require(`glob`) const { store } = require(`../redux`) const nodeAPIs = require(`../utils/api-node-docs`) const testRequireError = require(`../utils/test-require-error`) -const report = require(`../reporter`) +const report = require(`gatsby-cli/lib/reporter`) function createFileContentHash(root, globPattern) { const hash = crypto.createHash(`md5`) diff --git a/packages/gatsby/src/utils/build-css.js b/packages/gatsby/src/commands/build-css.js similarity index 84% rename from packages/gatsby/src/utils/build-css.js rename to packages/gatsby/src/commands/build-css.js index abf9fd1b11f32..a2f1ff6329348 100644 --- a/packages/gatsby/src/utils/build-css.js +++ b/packages/gatsby/src/commands/build-css.js @@ -1,8 +1,7 @@ /* @flow */ -import webpack from "webpack" -import fs from "fs-extra" -import Promise from "bluebird" -import webpackConfig from "./webpack.config" +const webpack = require(`webpack`) +const fs = require(`fs-extra`) +const webpackConfig = require(`../utils/webpack.config`) module.exports = async (program: any) => { const { directory } = program diff --git a/packages/gatsby/src/utils/build-html.js b/packages/gatsby/src/commands/build-html.js similarity index 84% rename from packages/gatsby/src/utils/build-html.js rename to packages/gatsby/src/commands/build-html.js index 327516c0956aa..836c453c5c711 100644 --- a/packages/gatsby/src/utils/build-html.js +++ b/packages/gatsby/src/commands/build-html.js @@ -1,10 +1,9 @@ /* @flow */ -import webpack from "webpack" -import Promise from "bluebird" -import fs from "fs" -import webpackConfig from "./webpack.config" +const webpack = require(`webpack`) +const fs = require(`fs`) +const webpackConfig = require(`../utils/webpack.config`) const { store } = require(`../redux`) -const { createErrorFromString } = require(`../reporter/errors`) +const { createErrorFromString } = require(`gatsby-cli/lib/reporter/errors`) const debug = require(`debug`)(`gatsby:html`) diff --git a/packages/gatsby/src/utils/build-javascript.js b/packages/gatsby/src/commands/build-javascript.js similarity index 63% rename from packages/gatsby/src/utils/build-javascript.js rename to packages/gatsby/src/commands/build-javascript.js index f51570b0ad4af..316137b28a141 100644 --- a/packages/gatsby/src/utils/build-javascript.js +++ b/packages/gatsby/src/commands/build-javascript.js @@ -1,9 +1,8 @@ /* @flow */ -import webpack from "webpack" -import Promise from "bluebird" -import webpackConfig from "./webpack.config" +const webpack = require(`webpack`) +const webpackConfig = require(`../utils/webpack.config`) -module.exports = async program => { +module.exports = async (program) => { const { directory } = program const compilerConfig = await webpackConfig( diff --git a/packages/gatsby/src/utils/build.js b/packages/gatsby/src/commands/build.js similarity index 75% rename from packages/gatsby/src/utils/build.js rename to packages/gatsby/src/commands/build.js index 079c1aaae1a3c..23722a0b8fea7 100644 --- a/packages/gatsby/src/utils/build.js +++ b/packages/gatsby/src/commands/build.js @@ -4,10 +4,10 @@ const buildCSS = require(`./build-css`) const buildHTML = require(`./build-html`) const buildProductionBundle = require(`./build-javascript`) const bootstrap = require(`../bootstrap`) -const report = require(`../reporter`) -const { formatStaticBuildError } = require(`../reporter/errors`) -const apiRunnerNode = require(`./api-runner-node`) -const copyStaticDirectory = require(`./copy-static-directory`) +const report = require(`gatsby-cli/lib/reporter`) +const { formatStaticBuildError } = require(`gatsby-cli/lib/reporter/errors`) +const apiRunnerNode = require(`../utils/api-runner-node`) +const copyStaticDirectory = require(`../utils/copy-static-directory`) function reportFailure(msg, err: Error) { report.log(``) @@ -17,7 +17,7 @@ function reportFailure(msg, err: Error) { ) } -async function html(program: any) { +module.exports = async function build(program: any) { const { graphqlRunner } = await bootstrap(program) // Copy files from the static directory to // an equivalent static directory within public. @@ -33,7 +33,7 @@ async function html(program: any) { activity = report.activityTimer(`Compiling production bundle.js`) activity.start() await buildProductionBundle(program).catch(err => { - reportFailure(`Generating JS failed`, err) + reportFailure(`Generating site JavaScript failed`, err) }) activity.end() @@ -52,6 +52,7 @@ async function html(program: any) { activity.end() await apiRunnerNode(`onPostBuild`, { graphql: graphqlRunner }) + + report.info(`Done building in ${process.uptime()} sec`) } -module.exports = html diff --git a/packages/gatsby/src/utils/develop-html.js b/packages/gatsby/src/commands/develop-html.js similarity index 86% rename from packages/gatsby/src/utils/develop-html.js rename to packages/gatsby/src/commands/develop-html.js index b2ac221e618c1..806af43ede6f2 100644 --- a/packages/gatsby/src/utils/develop-html.js +++ b/packages/gatsby/src/commands/develop-html.js @@ -1,10 +1,9 @@ /* @flow */ -const webpack = require(`webpack`) -const Promise = require(`bluebird`) const fs = require(`fs`) -const webpackConfig = require(`./webpack.config`) -const { createErrorFromString } = require(`../reporter/errors`) +const webpack = require(`webpack`) +const { createErrorFromString } = require(`gatsby-cli/lib/reporter/errors`) const debug = require(`debug`)(`gatsby:html`) +const webpackConfig = require(`../utils/webpack.config`) module.exports = async (program: any) => { const { directory } = program diff --git a/packages/gatsby/src/utils/develop.js b/packages/gatsby/src/commands/develop.js similarity index 94% rename from packages/gatsby/src/utils/develop.js rename to packages/gatsby/src/commands/develop.js index 8a97d9fdd3942..bce0425dcf91f 100644 --- a/packages/gatsby/src/utils/develop.js +++ b/packages/gatsby/src/commands/develop.js @@ -1,20 +1,20 @@ /* @flow */ +const chokidar = require(`chokidar`) const express = require(`express`) const graphqlHTTP = require(`express-graphql`) +const parsePath = require(`parse-filepath`) const request = require(`request`) -const bootstrap = require(`../bootstrap`) -const chokidar = require(`chokidar`) -const webpack = require(`webpack`) -const webpackConfig = require(`./webpack.config`) const rl = require(`readline`) -const parsePath = require(`parse-filepath`) +const webpack = require(`webpack`) +const webpackConfig = require(`../utils/webpack.config`) +const bootstrap = require(`../bootstrap`) const { store } = require(`../redux`) -const copyStaticDirectory = require(`./copy-static-directory`) +const copyStaticDirectory = require(`../utils/copy-static-directory`) const developHtml = require(`./develop-html`) -const { withBasePath } = require(`./path`) -const report = require(`../reporter`) -const { formatStaticBuildError } = require(`../reporter/errors`) +const { withBasePath } = require(`../utils/path`) +const report = require(`gatsby-cli/lib/reporter`) +const { formatStaticBuildError } = require(`gatsby-cli/lib/reporter/errors`) // Watch the static directory and copy files to public as they're added or // changed. Wait 10 seconds so copying doesn't interfer with the regular diff --git a/packages/gatsby/src/gatsby-cli.js b/packages/gatsby/src/gatsby-cli.js deleted file mode 100755 index c49c9cb08e52f..0000000000000 --- a/packages/gatsby/src/gatsby-cli.js +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env node -const path = require(`path`) -const fs = require(`fs`) -const _ = require(`lodash`) - -const report = require(`./reporter`) - -const version = process.version -const verDigit = Number(version.match(/\d+/)[0]) - -if (verDigit < 4) { - report.panic( - `Gatsby 1.0+ requires node.js v4 or higher (you have ${version}). \n` + - `Upgrade node to the latest stable release.` - ) -} - -/* - Get the locally installed version of gatsby/lib/bin/cli.js from the place - where this program was executed. -*/ -const cliFile = `dist/bin/cli.js` -const localPath = path.resolve(`node_modules/gatsby`, cliFile) - -const useGlobalGatsby = function() { - // Never use global install *except* for new and help commands - if (!_.includes([`new`, `--help`], process.argv[2])) { - report.panic( - `A local install of Gatsby was not found. \n` + - `You should save Gatsby as a site dependency e.g. npm install --save gatsby` - ) - } - - require(`./bin/cli`) -} - -if (fs.existsSync(localPath)) { - try { - require(localPath) - } catch (error) { - report.error(`A local install of Gatsby exists but failed to load.`, error) - } -} else { - useGlobalGatsby() -} diff --git a/packages/gatsby/src/internal-plugins/query-runner/file-parser.js b/packages/gatsby/src/internal-plugins/query-runner/file-parser.js index 90a1f7fdf8b24..69ed5dfecb010 100644 --- a/packages/gatsby/src/internal-plugins/query-runner/file-parser.js +++ b/packages/gatsby/src/internal-plugins/query-runner/file-parser.js @@ -6,7 +6,7 @@ const crypto = require(`crypto`) import traverse from "babel-traverse" const babylon = require(`babylon`) -const report = require(`../../reporter`) +const report = require(`gatsby-cli/lib/reporter`) const { getGraphQLTag } = require(`../../utils/babel-plugin-extract-graphql`) import type { DocumentNode, DefinitionNode } from "graphql" diff --git a/packages/gatsby/src/internal-plugins/query-runner/graphql-errors.js b/packages/gatsby/src/internal-plugins/query-runner/graphql-errors.js index 7425b82ea28f4..169895db389aa 100644 --- a/packages/gatsby/src/internal-plugins/query-runner/graphql-errors.js +++ b/packages/gatsby/src/internal-plugins/query-runner/graphql-errors.js @@ -3,7 +3,7 @@ import { print, visit, GraphQLError, getLocation } from "graphql" import babelCodeFrame from "babel-code-frame" import _ from "lodash" -import report from "../../reporter" +import report from "gatsby-cli/lib/reporter" type RelayGraphQLError = Error & { validationErrors?: Object } diff --git a/packages/gatsby/src/internal-plugins/query-runner/query-compiler.js b/packages/gatsby/src/internal-plugins/query-runner/query-compiler.js index 73edd8664e84c..07b2bfd2e5a6a 100644 --- a/packages/gatsby/src/internal-plugins/query-runner/query-compiler.js +++ b/packages/gatsby/src/internal-plugins/query-runner/query-compiler.js @@ -19,7 +19,7 @@ import { graphqlValidationError, multipleRootQueriesError, } from "./graphql-errors" -import report from "../../reporter" +import report from "gatsby-cli/lib/reporter" import type { DocumentNode, GraphQLSchema } from "graphql" diff --git a/packages/gatsby/src/internal-plugins/query-runner/query-runner.js b/packages/gatsby/src/internal-plugins/query-runner/query-runner.js index 39a2bb3d25769..4ef2b0d82da81 100644 --- a/packages/gatsby/src/internal-plugins/query-runner/query-runner.js +++ b/packages/gatsby/src/internal-plugins/query-runner/query-runner.js @@ -1,10 +1,10 @@ import { graphql as graphqlFunction } from "graphql" const fs = require(`fs`) const Promise = require(`bluebird`) +const report = require(`gatsby-cli/lib/reporter`) const writeFileAsync = Promise.promisify(fs.writeFile) const { joinPath } = require(`../../utils/path`) -const report = require(`../../reporter`) const { store } = require(`../../redux`) @@ -41,14 +41,7 @@ module.exports = async (pageOrLayout, component) => { ${component.query} ` ) - console.log(``) - console.log(``) - console.log(``) - console.log(`Query:`) - console.log(component.query) - console.log(``) - console.log(`GraphQL Error:`) - console.log(result.errors) + // Perhaps this isn't the best way to see if we're building? if (program._name === `build`) { process.exit(1) diff --git a/packages/gatsby/src/utils/api-runner-node.js b/packages/gatsby/src/utils/api-runner-node.js index ba4c23bc5bd92..ad51d22d67b51 100644 --- a/packages/gatsby/src/utils/api-runner-node.js +++ b/packages/gatsby/src/utils/api-runner-node.js @@ -3,7 +3,7 @@ const glob = require(`glob`) const _ = require(`lodash`) const mapSeries = require(`async/mapSeries`) -const reporter = require(`../reporter`) +const reporter = require(`gatsby-cli/lib/reporter`) const cache = require(`./cache`) const apiList = require(`./api-node-docs`) diff --git a/packages/gatsby/src/utils/init-starter.js b/packages/gatsby/src/utils/init-starter.js deleted file mode 100644 index e14dcfeb6c645..0000000000000 --- a/packages/gatsby/src/utils/init-starter.js +++ /dev/null @@ -1,132 +0,0 @@ -/* @flow weak */ -import { exec, execSync } from "child_process" -import hostedGitInfo from "hosted-git-info" -import fs from "fs-extra" -import sysPath from "path" - -let logger = console - -// Checks the existence of yarn package -// We use yarnpkg instead of yarn to avoid conflict with Hadoop yarn -// Refer to https://github.com/yarnpkg/yarn/issues/673 -// -// Returns true if yarn exists, false otherwise -const shouldUseYarn = () => { - try { - execSync(`yarnpkg --version`, { stdio: `ignore` }) - return true - } catch (e) { - return false - } -} - -// Executes `npm install` and `bower install` in rootPath. -// -// rootPath - String. Path to directory in which command will be executed. -// callback - Function. Takes stderr and stdout of executed process. -// -// Returns nothing. -const install = (rootPath, callback) => { - const prevDir = process.cwd() - logger.log(`Installing packages...`) - process.chdir(rootPath) - const installCmd = shouldUseYarn() ? `yarnpkg` : `npm install` - exec(installCmd, (error, stdout, stderr) => { - process.chdir(prevDir) - if (stdout) console.log(stdout.toString()) - if (error !== null) { - const msg = stderr.toString() - callback(new Error(msg)) - } - callback(null, stdout) - }) -} - -const ignored = path => !/^\.(git|hg)$/.test(sysPath.basename(path)) - -// Copy starter from file system. -// -// starterPath - String, file system path from which files will be taken. -// rootPath - String, directory to which starter files will be copied. -// callback - Function. -// -// Returns nothing. -const copy = (starterPath, rootPath, callback) => { - const copyDirectory = () => { - fs.copy(starterPath, rootPath, { filter: ignored }, error => { - if (error !== null) return callback(new Error(error)) - logger.log(`Created starter directory layout`) - install(rootPath, callback) - return false - }) - } - - // Chmod with 755. - // 493 = parseInt('755', 8) - fs.mkdirp(rootPath, { mode: 493 }, error => { - if (error !== null) callback(new Error(error)) - return fs.exists(starterPath, exists => { - if (!exists) { - const chmodError = `starter ${starterPath} doesn't exist` - return callback(new Error(chmodError)) - } - logger.log(`Copying local starter to ${rootPath} ...`) - - copyDirectory() - return true - }) - }) -} - -// Clones starter from URI. -// -// address - String, URI. https:, github: or git: may be used. -// rootPath - String, directory to which starter files will be copied. -// callback - Function. -// -// Returns nothing. -const clone = (hostInfo, rootPath, callback) => { - const url = hostInfo.git({ noCommittish: true }) - const branch = hostInfo.committish ? `-b ${hostInfo.committish}` : `` - - logger.log(`Cloning git repo ${url} to ${rootPath}...`) - const cmd = `git clone ${branch} ${url} ${rootPath} --single-branch` - - exec(cmd, (error, stdout, stderr) => { - if (error !== null) { - return callback(new Error(`Git clone error: ${stderr.toString()}`)) - } - logger.log(`Created starter directory layout`) - return fs.remove(sysPath.join(rootPath, `.git`), removeError => { - if (error !== null) return callback(new Error(removeError)) - install(rootPath, callback) - return true - }) - }) -} - -// Main function that clones or copies the starter. -// -// starter - String, file system path or URI of starter. -// rootPath - String, directory to which starter files will be copied. -// callback - Function. -// -// Returns nothing. -const initStarter = (starter, options = {}) => - new Promise((resolve, reject) => { - const callback = (err, value) => (err ? reject(err) : resolve(value)) - - const cwd = process.cwd() - const rootPath = options.rootPath || cwd - if (options.logger) logger = options.logger - - if (fs.existsSync(sysPath.join(rootPath, `package.json`))) - throw new Error(`Directory ${rootPath} is already an npm project`) - - const hostedInfo = hostedGitInfo.fromUrl(starter) - - if (hostedInfo) clone(hostedInfo, rootPath, callback) - else copy(starter, rootPath, callback) - }) - -module.exports = initStarter diff --git a/packages/gatsby/src/utils/new.js b/packages/gatsby/src/utils/new.js deleted file mode 100644 index afa14d139110e..0000000000000 --- a/packages/gatsby/src/utils/new.js +++ /dev/null @@ -1,8 +0,0 @@ -/* @flow weak */ -const logger = require(`tracer`).colorConsole() - -const initStarter = require(`./init-starter`) - -module.exports = (rootPath, starter = `gatsbyjs/gatsby-starter-default`) => { - initStarter(starter, { rootPath, logger }).catch(error => logger.error(error)) -} diff --git a/packages/gatsby/src/utils/reporter/errors.js b/packages/gatsby/src/utils/reporter/errors.js new file mode 100644 index 0000000000000..ff86f399ffffe --- /dev/null +++ b/packages/gatsby/src/utils/reporter/errors.js @@ -0,0 +1,67 @@ +// @flow + +const PrettyError = require(`pretty-error`) + +function getErrorFormatter() { + const prettyError = new PrettyError() + + prettyError.skipNodeFiles() + prettyError.skipPackage( + `regenerator-runtime`, + `graphql`, + `core-js` + // `static-site-generator-webpack-plugin`, + // `tapable`, // webpack + ) + + prettyError.skip((traceLine, ln) => { + if (traceLine && traceLine.file === `asyncToGenerator.js`) return true + return false + }) + + prettyError.appendStyle({ + "pretty-error": { + marginTop: 1, + }, + }) + + return prettyError +} + +/** + * Convert a stringified webpack compilation error back into + * an Error instance so it can be formatted properly + * @param {string} errorStr + */ +function createErrorFromString(errorStr: string) { + let [message, ...rest] = errorStr.split(/\r\n|[\n\r]/g) + // pull the message from the first line then remove the `Error:` prefix + // FIXME: when https://github.com/AriaMinaei/pretty-error/pull/49 is merged + let error = new Error() + error.stack = [message.split(`:`).slice(1).join(`:`), rest.join(`\n`)].join( + `\n` + ) + error.name = `WebpackError` + return error +} + +/** + * Format a html stage compilation error to only show stack lines + * for 'render-page.js' the output file for those stages, since it contains + * the relevant details for debugging. + * + * @param {Error} error + */ +function formatStaticBuildError(error: Error) { + // For HTML compilation issues we filter down the error + // to only the bits that are relevant for debugging + const formatter = getErrorFormatter() + formatter.skip(traceLine => !traceLine || traceLine.file !== `render-page.js`) + return formatter.render(error) +} + +module.exports = { + createErrorFromString, + getErrorFormatter, + formatStaticBuildError, +} diff --git a/packages/gatsby/src/utils/reporter/index.js b/packages/gatsby/src/utils/reporter/index.js new file mode 100644 index 0000000000000..44cd3c3b5426f --- /dev/null +++ b/packages/gatsby/src/utils/reporter/index.js @@ -0,0 +1,56 @@ +// @flow + +const { createReporter } = require(`yurnalist`) +const { stripIndent } = require(`common-tags`) +const convertHrtime = require(`convert-hrtime`) +const { getErrorFormatter } = require(`./errors`) + +const errorFormatter = getErrorFormatter() +const reporter = createReporter({ emoji: true }) +const base = Object.getPrototypeOf(reporter) + +module.exports = Object.assign(reporter, { + stripIndent, + + setVerbose(isVerbose) { + this.isVerbose = true + }, + + panic(...args) { + this.error(...args) + process.exit(1) + }, + + error(message, error) { + if (arguments.length === 1 && typeof message !== `string`) { + error = message + message = error.message + } + base.error.call(this, message) + if (error) console.log(errorFormatter.render(error)) + }, + + uptime(prefix: string) { + this.verbose(`${prefix}: ${(process.uptime() * 1000).toFixed(3)}ms`) + }, + + activityTimer(name) { + const spinner = reporter.activity() + const start = process.hrtime() + + const elapsedTime = () => { + var elapsed = process.hrtime(start) + return `${convertHrtime(elapsed)[`seconds`].toFixed(3)} s` + } + + return { + start: () => { + spinner.tick(name) + }, + end: () => { + reporter.success(`${name} — ${elapsedTime()}`) + spinner.end() + }, + } + }, +})