From 26e400742884db0e474a904f43c43fa4663167a1 Mon Sep 17 00:00:00 2001 From: mattstypa Date: Wed, 20 Mar 2019 08:17:56 -0500 Subject: [PATCH] Few CLI tweaks --- __tests__/cli.test.js | 47 +++++++++++++++------------------- __tests__/cli.utils.test.js | 14 ++++++++++ __tests__/customConfig.test.js | 14 +--------- jest/runInTempDirectory.js | 21 +++++++++++++++ src/cli/colors.js | 46 +++++++++++++++++++++++++++++++++ src/cli/commands/build.js | 19 +++++++++----- src/cli/commands/help.js | 14 +++++----- src/cli/commands/init.js | 14 +++++----- src/cli/utils.js | 30 ++++++++++++++++++---- 9 files changed, 154 insertions(+), 65 deletions(-) create mode 100644 jest/runInTempDirectory.js create mode 100644 src/cli/colors.js diff --git a/__tests__/cli.test.js b/__tests__/cli.test.js index 2e04a0234..e8b937230 100644 --- a/__tests__/cli.test.js +++ b/__tests__/cli.test.js @@ -3,6 +3,7 @@ import path from 'path' import cli from '../src/cli/main' import * as constants from '../src/constants' import * as utils from '../src/cli/utils' +import runInTempDirectory from '../jest/runInTempDirectory' describe('cli', () => { const inputCssPath = path.resolve(__dirname, 'fixtures/tailwind-input.css') @@ -13,37 +14,30 @@ describe('cli', () => { beforeEach(() => { console.log = jest.fn() process.stdout.write = jest.fn() - utils.writeFile = jest.fn() }) describe('init', () => { it('creates a Tailwind config file', () => { - return cli(['init']).then(() => { - expect(utils.writeFile.mock.calls[0][0]).toEqual(constants.defaultConfigFile) - }) - }) - - it('creates a Tailwind config file in a custom location', () => { - return cli(['init', 'custom.js']).then(() => { - expect(utils.writeFile.mock.calls[0][0]).toEqual('custom.js') - }) - }) - - it('creates a Tailwind config file without comments', () => { - return cli(['init', '--no-comments']).then(() => { - expect(utils.writeFile.mock.calls[0][1]).not.toContain('/**') - }) - }) - - it('creates a simple Tailwind config file', () => { - return cli(['init']).then(() => { - expect(utils.writeFile.mock.calls[0][1]).toEqual(simpleConfigFixture) + return runInTempDirectory(() => { + return cli(['init']).then(() => { + expect(utils.readFile(constants.defaultConfigFile)).toEqual(simpleConfigFixture) + }) }) }) it('creates a full Tailwind config file', () => { - return cli(['init', '--full']).then(() => { - expect(utils.writeFile.mock.calls[0][1]).toEqual(defaultConfigFixture) + return runInTempDirectory(() => { + return cli(['init', '--full']).then(() => { + expect(utils.readFile(constants.defaultConfigFile)).toEqual(defaultConfigFixture) + }) + }) + }) + + it('creates a Tailwind config file in a custom location', () => { + return runInTempDirectory(() => { + return cli(['init', 'custom.js']).then(() => { + expect(utils.exists('custom.js')).toEqual(true) + }) }) }) }) @@ -62,9 +56,10 @@ describe('cli', () => { }) it('creates compiled CSS file', () => { - return cli(['build', inputCssPath, '--output', 'output.css']).then(() => { - expect(utils.writeFile.mock.calls[0][0]).toEqual('output.css') - expect(utils.writeFile.mock.calls[0][1]).toContain('.example') + return runInTempDirectory(() => { + return cli(['build', inputCssPath, '--output', 'output.css']).then(() => { + expect(utils.readFile('output.css')).toContain('.example') + }) }) }) diff --git a/__tests__/cli.utils.test.js b/__tests__/cli.utils.test.js index 2d9082d5c..ee167c8da 100644 --- a/__tests__/cli.utils.test.js +++ b/__tests__/cli.utils.test.js @@ -56,4 +56,18 @@ describe('cli utils', () => { expect(result).toEqual({ test: ['c', 'd', 'h'] }) }) }) + + describe('getSimplePath', () => { + it('strips leading ./', () => { + const result = utils.getSimplePath('./test') + + expect(result).toEqual('test') + }) + + it('returns unchanged path if it does not begin with ./', () => { + const result = utils.getSimplePath('../test') + + expect(result).toEqual('../test') + }) + }) }) diff --git a/__tests__/customConfig.test.js b/__tests__/customConfig.test.js index 97e15cfcf..0d9cd1581 100644 --- a/__tests__/customConfig.test.js +++ b/__tests__/customConfig.test.js @@ -1,21 +1,9 @@ import fs from 'fs' import path from 'path' -import rimraf from 'rimraf' import postcss from 'postcss' import tailwind from '../src/index' import { defaultConfigFile } from '../src/constants' - -function inTempDirectory(callback) { - return new Promise(resolve => { - rimraf.sync('./__tmp') - fs.mkdirSync(path.resolve('./__tmp')) - process.chdir(path.resolve('./__tmp')) - callback().then(() => { - process.chdir(path.resolve('../')) - rimraf('./__tmp', resolve) - }) - }) -} +import inTempDirectory from '../jest/runInTempDirectory' test('it uses the values from the custom config file', () => { return postcss([tailwind(path.resolve(`${__dirname}/fixtures/custom-config.js`))]) diff --git a/jest/runInTempDirectory.js b/jest/runInTempDirectory.js new file mode 100644 index 000000000..c6d025eb3 --- /dev/null +++ b/jest/runInTempDirectory.js @@ -0,0 +1,21 @@ +import fs from 'fs' +import path from 'path' + +import rimraf from 'rimraf' + +const tmpPath = path.resolve(__dirname, '../__tmp') + +export default function(callback) { + return new Promise(resolve => { + const currentPath = process.cwd() + + rimraf.sync(tmpPath) + fs.mkdirSync(tmpPath) + process.chdir(tmpPath) + + callback().then(() => { + process.chdir(currentPath) + rimraf(tmpPath, resolve) + }) + }) +} diff --git a/src/cli/colors.js b/src/cli/colors.js new file mode 100644 index 000000000..8ba560ae2 --- /dev/null +++ b/src/cli/colors.js @@ -0,0 +1,46 @@ +import chalk from 'chalk' + +/** + * Applies colors to emphasize + * + * @param {...string} msgs + */ +export function bold(...msgs) { + return chalk.bold(...msgs) +} + +/** + * Applies colors to inform + * + * @param {...string} msgs + */ +export function info(...msgs) { + return chalk.bold.cyan(...msgs) +} + +/** + * Applies colors to signify error + * + * @param {...string} msgs + */ +export function error(...msgs) { + return chalk.bold.red(...msgs) +} + +/** + * Applies colors to represent a command + * + * @param {...string} msgs + */ +export function command(...msgs) { + return chalk.bold.magenta(...msgs) +} + +/** + * Applies colors to represent a file + * + * @param {...string} msgs + */ +export function file(...msgs) { + return chalk.bold.magenta(...msgs) +} diff --git a/src/cli/commands/build.js b/src/cli/commands/build.js index 99bbcf126..6d79b48f7 100644 --- a/src/cli/commands/build.js +++ b/src/cli/commands/build.js @@ -1,12 +1,12 @@ import autoprefixer from 'autoprefixer' import bytes from 'bytes' -import chalk from 'chalk' import prettyHrtime from 'pretty-hrtime' import tailwind from '../..' import commands from '.' import compile from '../compile' +import * as colors from '../colors' import * as emoji from '../emoji' import * as utils from '../utils' @@ -75,9 +75,12 @@ function buildToStdout(compileOptions) { * @return {Promise} */ function buildToFile(compileOptions, startTime) { + const inputFileSimplePath = utils.getSimplePath(compileOptions.inputFile) + const outputFileSimplePath = utils.getSimplePath(compileOptions.outputFile) + utils.header() utils.log() - utils.log(emoji.go, 'Building...', chalk.bold.cyan(compileOptions.inputFile)) + utils.log(emoji.go, 'Building...', colors.file(inputFileSimplePath)) return compile(compileOptions).then(result => { utils.writeFile(compileOptions.outputFile, result.css) @@ -85,9 +88,9 @@ function buildToFile(compileOptions, startTime) { const prettyTime = prettyHrtime(process.hrtime(startTime)) utils.log() - utils.log(emoji.yes, 'Finished in', chalk.bold.magenta(prettyTime)) - utils.log(emoji.pack, 'Size:', chalk.bold.magenta(bytes(result.css.length))) - utils.log(emoji.disk, 'Saved to', chalk.bold.cyan(compileOptions.outputFile)) + utils.log(emoji.yes, 'Finished in', colors.info(prettyTime)) + utils.log(emoji.pack, 'Size:', colors.info(bytes(result.css.length))) + utils.log(emoji.disk, 'Saved to', colors.file(outputFileSimplePath)) utils.footer() }) } @@ -106,13 +109,15 @@ export function run(cliParams, cliOptions) { const configFile = cliOptions.config && cliOptions.config[0] const outputFile = cliOptions.output && cliOptions.output[0] const autoprefix = !cliOptions.noAutoprefixer + const inputFileSimplePath = utils.getSimplePath(inputFile) + const configFileSimplePath = utils.getSimplePath(configFile) !inputFile && stopWithHelp('CSS file is required.') - !utils.exists(inputFile) && stop(chalk.bold.magenta(inputFile), 'does not exist.') + !utils.exists(inputFile) && stop(colors.file(inputFileSimplePath), 'does not exist.') configFile && !utils.exists(configFile) && - stop(chalk.bold.magenta(configFile), 'does not exist.') + stop(colors.file(configFileSimplePath), 'does not exist.') const compileOptions = { inputFile, diff --git a/src/cli/commands/help.js b/src/cli/commands/help.js index 321bca399..3c63e884e 100644 --- a/src/cli/commands/help.js +++ b/src/cli/commands/help.js @@ -1,8 +1,8 @@ -import chalk from 'chalk' import { forEach, map, padEnd } from 'lodash' import commands from '.' import * as constants from '../../constants' +import * as colors from '../colors' import * as utils from '../utils' export const usage = 'help [command]' @@ -18,11 +18,11 @@ export function forApp() { utils.log() utils.log('Usage:') - utils.log(' ', chalk.bold(constants.cli + ' [options]')) + utils.log(' ', colors.bold(constants.cli + ' [options]')) utils.log() utils.log('Commands:') forEach(commands, command => { - utils.log(' ', chalk.bold(padEnd(command.usage, pad)), command.description) + utils.log(' ', colors.bold(padEnd(command.usage, pad)), command.description) }) } @@ -34,10 +34,10 @@ export function forApp() { export function forCommand(command) { utils.log() utils.log('Usage:') - utils.log(' ', chalk.bold(constants.cli, command.usage)) + utils.log(' ', colors.bold(constants.cli, command.usage)) utils.log() utils.log('Description:') - utils.log(' ', chalk.bold(command.description)) + utils.log(' ', colors.bold(command.description)) if (command.options) { const pad = Math.max(...map(command.options, 'usage.length')) + PADDING_SIZE @@ -45,7 +45,7 @@ export function forCommand(command) { utils.log() utils.log('Options:') forEach(command.options, option => { - utils.log(' ', chalk.bold(padEnd(option.usage, pad)), option.description) + utils.log(' ', colors.bold(padEnd(option.usage, pad)), option.description) }) } } @@ -56,7 +56,7 @@ export function forCommand(command) { * @param {string} commandName */ export function invalidCommand(commandName) { - utils.error('Invalid command:', chalk.bold.magenta(commandName)) + utils.error('Invalid command:', colors.command(commandName)) forApp() utils.die() } diff --git a/src/cli/commands/init.js b/src/cli/commands/init.js index 0b3b34584..f13160d82 100644 --- a/src/cli/commands/init.js +++ b/src/cli/commands/init.js @@ -1,12 +1,12 @@ -import chalk from 'chalk' - import * as constants from '../../constants' +import * as colors from '../colors' import * as emoji from '../emoji' import * as utils from '../utils' export const usage = 'init [file]' export const description = - 'Creates Tailwind config file. Default: ' + chalk.bold.magenta(constants.defaultConfigFile) + 'Creates Tailwind config file. Default: ' + + colors.file(utils.getSimplePath(constants.defaultConfigFile)) export const options = [ { @@ -32,16 +32,16 @@ export function run(cliParams, cliOptions) { const full = cliOptions.full const file = cliParams[0] || constants.defaultConfigFile + const simplePath = utils.getSimplePath(file) - utils.exists(file) && utils.die(chalk.bold.magenta(file), 'already exists.') + utils.exists(file) && utils.die(colors.file(simplePath), 'already exists.') const stubFile = full ? constants.defaultConfigStubFile : constants.simpleConfigStubFile - const stub = utils.readFile(stubFile) - utils.writeFile(file, stub) + utils.copyFile(stubFile, file) utils.log() - utils.log(emoji.yes, 'Created Tailwind config file:', chalk.bold.magenta(file)) + utils.log(emoji.yes, 'Created Tailwind config file:', colors.file(simplePath)) utils.footer() diff --git a/src/cli/utils.js b/src/cli/utils.js index ac138b854..0224a7b1b 100644 --- a/src/cli/utils.js +++ b/src/cli/utils.js @@ -1,7 +1,7 @@ -import chalk from 'chalk' -import { ensureFileSync, existsSync, outputFileSync, readFileSync } from 'fs-extra' -import { findKey, mapValues, trimStart } from 'lodash' +import { copyFileSync, ensureFileSync, existsSync, outputFileSync, readFileSync } from 'fs-extra' +import { findKey, mapValues, startsWith, trimStart } from 'lodash' +import * as colors from './colors' import * as emoji from './emoji' import packageJson from '../../package.json' @@ -58,7 +58,7 @@ export function log(...msgs) { */ export function header() { log() - log(chalk.bold(packageJson.name), chalk.bold.cyan(packageJson.version)) + log(colors.bold(packageJson.name), colors.info(packageJson.version)) } /** @@ -75,7 +75,7 @@ export function footer() { */ export function error(...msgs) { log() - console.error(' ', emoji.no, chalk.bold.red(msgs.join(' '))) + console.error(' ', emoji.no, colors.error(msgs.join(' '))) } /** @@ -99,6 +99,16 @@ export function exists(path) { return existsSync(path) } +/** + * Copies file source to destination. + * + * @param {string} source + * @param {string} destination + */ +export function copyFile(source, destination) { + copyFileSync(source, destination) +} + /** * Gets file content. * @@ -121,3 +131,13 @@ export function writeFile(path, content) { return outputFileSync(path, content) } + +/** + * Strips leading ./ from path + * + * @param {string} path + * @return {string} + */ +export function getSimplePath(path) { + return startsWith(path, './') ? path.slice(2) : path +}