diff --git a/.eslintrc.json b/.eslintrc.json index 4fe69c2fb..58bc536d5 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -4,7 +4,8 @@ ], "env": { - "jest": true + "jest": true, + "browser": true }, "plugins": [ diff --git a/cli/actions/__tests__/dev.test.js b/cli/actions/__tests__/dev.test.js index 151174354..74062326a 100644 --- a/cli/actions/__tests__/dev.test.js +++ b/cli/actions/__tests__/dev.test.js @@ -58,13 +58,21 @@ describe('dev', () => { const hostname = 'hostname'; const mockURL = { href, port, hostname }; + require('../dev')({ + clientURL: mockURL, + serverURL: mockURL, + reactHotLoader: false, + }, + []); + it('runs correctly with a server enabled', () => { dev({ clientURL: mockURL, serverURL: mockURL, reactHotLoader: false, hasServer: true, - }); + }, + []); assert.deepEqual(logger.task.mock.calls[0], ['Cleaned ./build'], 'should log that it cleaned the build directory'); @@ -104,20 +112,22 @@ describe('dev', () => { 'second arg for server webpackCompiler should be a callback'); clientCompilerDone(stats); - assert.equal(logger.task.mock.calls[1][0], 'Client assets serving from publicPath', + + assert.equal(logger.task.mock.calls[2][0], 'Client assets serving from publicPath', 'should log a message about client server'); serverCompilerDone(stats); assert.deepEqual(nodemon.mock.calls[0][0], { script: 'fakePath', watch: ['fakePath'], + nodeArgs: [], }, 'should set up nodemon with correct arguments'); // nodemon.once assert.equal(nodemon.once.mock.calls[0][0], 'start', 'should call nodemon.once for start'); nodemon.once.mock.calls[0][1](); - assert.ok(/Server running at:/.test(logger.task.mock.calls[2][0]), + assert.ok(/Server running at:/.test(logger.task.mock.calls[3][0]), 'should log that the server is running'); assert.equal(logger.end.mock.calls[0][0], 'Development started', 'should call logger.end with the correct message'); @@ -126,7 +136,7 @@ describe('dev', () => { assert.equal(nodemon.on.mock.calls[0][0], 'restart', 'should set up nodemon restart listener'); nodemon.on.mock.calls[0][1](); - assert.equal(logger.task.mock.calls[3][0], 'Development server restarted', + assert.equal(logger.task.mock.calls[4][0], 'Development server restarted', 'should call logger.task with restart message'); // on quit diff --git a/cli/actions/__tests__/lint.test.js b/cli/actions/__tests__/lint.test.js index 2f65bdd0d..901d302c5 100644 --- a/cli/actions/__tests__/lint.test.js +++ b/cli/actions/__tests__/lint.test.js @@ -7,45 +7,37 @@ jest.setMock('path', { jest.mock('../../logger'); jest.mock('../../../utils/paths'); +jest.mock('shelljs', () => ( + { + exec: () => ({ + stdout: '', + code: 0, + }), + } +)); + describe('lint', () => { global.process.exit = jest.fn(); const logger = require('../../logger'); const lint = require('../lint'); - const eslint = require('eslint'); beforeEach(() => { jest.resetModules(); }); it('logs the user linter filename when found', () => { - lint(); + lint({}, []); expect(logger.info).toBeCalledWith('Using ESLint file: filename'); }); it('logs the base filename when there is no user file', () => { - lint(); + lint({}, []); expect(logger.info).toBeCalledWith('Using ESLint file: base filename'); }); it('logs and exits 0 when no errors', () => { - lint(); + lint({}, []); expect(logger.end).toBeCalledWith('Your JS looks great ✨'); expect(process.exit).toBeCalledWith(0); }); - - it('logs and exits 0 when there are warnings', () => { - eslint.__setExecuteOnFiles({ warningCount: 1 }); // eslint-disable-line no-underscore-dangle - lint(); - - expect(logger.end) - .toBeCalledWith('Your JS looks OK, though there were warnings 🤔👆'); - expect(process.exit).toBeCalledWith(0); - }); - - it('exits 1 when there are errors', () => { - eslint.__setExecuteOnFiles({ errorCount: 1 }); // eslint-disable-line no-underscore-dangle - lint(); - - expect(process.exit).toBeCalledWith(1); - }); }); diff --git a/cli/actions/__tests__/start.test.js b/cli/actions/__tests__/start.test.js index eb4a0e75b..66b78fec5 100644 --- a/cli/actions/__tests__/start.test.js +++ b/cli/actions/__tests__/start.test.js @@ -15,7 +15,7 @@ describe('start', () => { describe('default case', () => { beforeEach(() => { - start({ serverURL, hasServer: true }); + start({ serverURL, hasServer: true }, []); }); it('does not call process.exit or logger.error', () => { @@ -29,13 +29,13 @@ describe('start', () => { }); it('executes the node process asynchronously', () => { - expect(shell.exec).toBeCalledWith('node build/server/main.js', { async: true }); + expect(shell.exec).toBeCalledWith('node build/server/main.js ', { async: true }); }); }); describe('hasServer set to false', () => { beforeEach(() => { - start({ hasServer: false }); + start({ hasServer: false }, []); }); it('logs an error and exits', () => { diff --git a/cli/actions/dev.js b/cli/actions/dev.js index c6c811bed..3c21887e4 100644 --- a/cli/actions/dev.js +++ b/cli/actions/dev.js @@ -15,7 +15,7 @@ const buildConfigs = require('../../utils/buildConfigs'); const webpackCompiler = require('../../utils/webpackCompiler'); const { buildPath, serverSrcPath } = require('../../utils/paths')(); -module.exports = (config) => { +module.exports = (config, flags) => { logger.start('Starting development build...'); // Kill the server on exit. @@ -52,7 +52,7 @@ module.exports = (config) => { serverCompiler.options.output.path, `${Object.keys(serverCompiler.options.entry)[0]}.js` ); - nodemon({ script: serverPath, watch: [serverPath] }) + nodemon({ script: serverPath, watch: [serverPath], nodeArgs: flags }) .once('start', () => { logger.task(`Server running at: ${serverURL.href}`); logger.end('Development started'); diff --git a/cli/actions/lint.js b/cli/actions/lint.js index 2ae30a908..bbd3c6fef 100644 --- a/cli/actions/lint.js +++ b/cli/actions/lint.js @@ -1,13 +1,13 @@ // Command to lint src code -const CLIEngine = require('eslint').CLIEngine; +const shell = require('shelljs'); const path = require('path'); const logger = require('./../logger'); const glob = require('glob'); const { userRootPath } = require('../../utils/paths')(); -module.exports = () => { +module.exports = (config, flags) => { const eslintrc = glob.sync(`${userRootPath}/.*eslintrc*`); const configFile = eslintrc.length ? eslintrc[0] @@ -15,28 +15,18 @@ module.exports = () => { logger.info(`Using ESLint file: ${configFile}`); - // http://eslint.org/docs/developer-guide/nodejs-api - const eslintCLI = { - envs: ['browser'], - extensions: ['.js'], - useEslintrc: true, - configFile, - }; - - // Get the default dir or the dir specified by the user/-d. const lint = () => { - const files = ['src/']; - const cli = new CLIEngine(eslintCLI); - const report = cli.executeOnFiles(files); - const formatter = cli.getFormatter(); - logger.log(`${formatter(report.results)}\n`); - - if (report.errorCount === 0) { - logger.end(`Your JS looks ${report.warningCount === 0 ? 'great ✨' : + const eslintLib = require.resolve('eslint'); + const eslint = eslintLib.replace(/(.*node_modules)(.*)/, '$1/.bin/eslint'); + + const cmd = `${eslint} src/ -c ${configFile} --color ${flags.join(' ')}`; + const output = shell.exec(cmd); + if (output.code === 0) { + logger.end(`Your JS looks ${output.stdout === '' ? 'great ✨' : 'OK, though there were warnings 🤔👆'}`); } - process.exit(report.errorCount > 0 ? 1 : 0); + process.exit(output.code > 0 ? 1 : 0); }; lint(); diff --git a/cli/actions/setup.js b/cli/actions/setup.js index 06e174b24..add89015c 100644 --- a/cli/actions/setup.js +++ b/cli/actions/setup.js @@ -18,8 +18,7 @@ const { // eslint-disable-next-line import/no-dynamic-require const kytPkg = require(path.join(__dirname, '../../package.json')); -module.exports = (config, program) => { - const args = program.args[0]; +module.exports = (config, flags, args) => { const date = Date.now(); const tmpDir = path.resolve(userRootPath, '\.kyt-tmp'); // eslint-disable-line no-useless-escape const repoURL = args.repository || 'git@github.com:NYTimes/kyt-starter.git'; diff --git a/cli/actions/start.js b/cli/actions/start.js index 233ab8498..19d76a2ee 100644 --- a/cli/actions/start.js +++ b/cli/actions/start.js @@ -8,13 +8,14 @@ const logger = require('./../logger'); // prevent the code from reaching a line later that would // cause a ReferenceError // eslint-disable-next-line consistent-return -module.exports = (config) => { +module.exports = (config, flags) => { if (!config.hasServer) { logger.error('You have hasServer set to false, bailing'); return process.exit(1); } logger.start('Starting production server...'); - shell.exec('node build/server/main.js', { async: true }); + const cmd = `node build/server/main.js ${flags.join(' ')}`; + shell.exec(cmd, { async: true }); logger.end(`Server running at ${config.serverURL.href}`); }; diff --git a/cli/actions/test.js b/cli/actions/test.js index 8c1f9a243..f42a752aa 100644 --- a/cli/actions/test.js +++ b/cli/actions/test.js @@ -9,13 +9,10 @@ const { srcPath } = require('../../utils/paths')(); const buildConfigs = require('../../utils/buildConfigs'); -module.exports = (config, program) => { +module.exports = (config, flags) => { // Comment the following to see verbose shell ouput. shell.config.silent = false; - // Grab args to pass along (e.g. kyt test -- --watch) - const args = program.args.filter(a => typeof a === 'string'); - // set BABEL_ENV to test if undefined process.env.BABEL_ENV = process.env.BABEL_ENV || 'test'; @@ -27,5 +24,5 @@ module.exports = (config, program) => { jestConfig = config.modifyJestConfig(clone(jestConfig)); // Run Jest - jest.run(['--config', JSON.stringify(jestConfig), ...args]); + jest.run(['--config', JSON.stringify(jestConfig), ...flags]); }; diff --git a/cli/commands.js b/cli/commands.js index 91dcb166d..d71eabb8f 100755 --- a/cli/commands.js +++ b/cli/commands.js @@ -22,8 +22,10 @@ if (!shell.test('-f', userPackageJSONPath)) { } const loadConfigAndDo = (action, optionalConfig) => { + const args = program.args.filter(item => typeof item === 'object'); + const flags = program.args.filter(item => typeof item === 'string'); const config = kytConfigFn(optionalConfig); - action(config, program); + action(config, flags, args); }; program @@ -36,7 +38,8 @@ program .option('-C, --config ', 'config path') .description('Start an express server for development') .action(() => { - const config = program.args[0].config ? program.args[0].config : null; + const args = program.args.filter(item => typeof item === 'object'); + const config = args[0].config ? args[0].config : null; loadConfigAndDo(devAction, config); }); @@ -45,7 +48,8 @@ program .option('-C, --config ', 'config path') .description('Create a production build') .action(() => { - const config = program.args[0].config ? program.args[0].config : null; + const args = program.args.filter(item => typeof item === 'object'); + const config = args[0].config ? args[0].config : null; loadConfigAndDo(buildAction, config); }); diff --git a/config/.eslintrc.base.json b/config/.eslintrc.base.json index 9c95b4089..0d60ab891 100644 --- a/config/.eslintrc.base.json +++ b/config/.eslintrc.base.json @@ -4,7 +4,8 @@ ], "env": { - "jest": true + "jest": true, + "browser": true }, "plugins": [ diff --git a/docs/commands.md b/docs/commands.md index fffd277ff..d4c6d1c3c 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -80,6 +80,13 @@ If `hasServer` is set to `false` in [kyt.config.js](/docs/kytConfig.md), `src/se Optionally, you can configure urls for the development servers in the [kyt config](/docs/kytConfig.md). +You can pass flags to the node server through `kyt dev`. +For example: +``` +kyt dev -- --inspect +``` +will run the [node debugging for Chrome DevTools](https://medium.com/@paul_irish/debugging-node-js-nightlies-with-chrome-devtools-7c4a1b95ae27#.mpuwgy17v) + ## build The `build` command takes the entry index.js in `src/client/` and `src/server/` (ignoring the latter if `hasServer` set to false in [kyt.config.js](/docs/kytConfig.md)), compiles them, and saves them to a build folder. This is an optimized production build. @@ -94,11 +101,21 @@ The `start` command takes the compiled code from the production build and runs a Optionally, you can configure the server url in your [kyt.config.js](/docs/kytConfig.md). +You can also pass flags to node through `kyt start`: +``` +kyt start -- --no-warnings +``` + ## test The `test` command takes test files in your `src/` directory and runs them using [Jest](http://facebook.github.io/jest/). kyt test looks for any `*.test.js` files in `src/`. +You can pass flags to jest through `kyt test`. +``` +kyt test -- --no-cache +``` + ### test-watch Runs Jest with `--watch`. @@ -120,6 +137,12 @@ You can add or update any rules in this file. kyt's base ESLint config extends [Airbnb](https://github.com/airbnb/javascript) with a few overrides. You can find kyt's base ESLint configuration [here](/config/.eslintrc.json). +Flags can be passed to ESLint through `kyt lint` + +``` +kyt lint -- --fix +``` + ## lint-style The `lint-style` command uses Stylelint to lint all files in the `src/` directory. By convention, it looks for files with a `.css` or `.scss` extension.