-
Notifications
You must be signed in to change notification settings - Fork 4.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #554 from MattStypa/cli
CLI Beautification
- Loading branch information
Showing
15 changed files
with
486 additions
and
118 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,21 +1,53 @@ | ||
import { spawnSync } from 'child_process' | ||
import fs from 'fs' | ||
import path from 'path' | ||
|
||
function runCli(task, options) { | ||
return spawnSync('node', [`${path.join(process.cwd(), 'lib/cli.js')}`, `${task}`, ...options]) | ||
} | ||
import cli from '../src/cli/main' | ||
import * as constants from '../src/cli/constants' | ||
import * as utils from '../src/cli/utils' | ||
|
||
function pathToFixture(fixture) { | ||
return path.resolve(`${__dirname}/fixtures/${fixture}`) | ||
} | ||
describe('cli', () => { | ||
const inputCssPath = path.resolve(__dirname, 'fixtures/tailwind-input.css') | ||
const customConfigPath = path.resolve(__dirname, 'fixtures/custom-config.js') | ||
|
||
function readFixture(fixture) { | ||
return fs.readFileSync(pathToFixture(fixture), 'utf8') | ||
} | ||
beforeEach(() => { | ||
console.log = jest.fn() | ||
process.stdout.write = jest.fn() | ||
utils.writeFile = jest.fn() | ||
}) | ||
|
||
test('stdout only contains processed output', () => { | ||
const expected = readFixture('tailwind-cli-output.css') | ||
const result = runCli('build', [pathToFixture('tailwind-cli-input.css')]) | ||
expect(result.stdout.toString()).toEqual(expected) | ||
describe('init', () => { | ||
it('creates a Tailwind config file', () => { | ||
cli(['init']).then(() => { | ||
expect(utils.writeFile.mock.calls[0][0]).toEqual(constants.defaultConfigFile) | ||
expect(utils.writeFile.mock.calls[0][1]).toContain('defaultConfig') | ||
}) | ||
}) | ||
|
||
it('creates a Tailwind config file in a custom location', () => { | ||
cli(['init', 'custom.js']).then(() => { | ||
expect(utils.writeFile.mock.calls[0][0]).toEqual('custom.js') | ||
expect(utils.writeFile.mock.calls[0][1]).toContain('defaultConfig') | ||
}) | ||
}) | ||
}) | ||
|
||
describe('build', () => { | ||
it('compiles CSS file', () => { | ||
cli(['build', inputCssPath]).then(() => { | ||
expect(process.stdout.write.mock.calls[0][0]).toContain('.example') | ||
}) | ||
}) | ||
|
||
it('compiles CSS file using custom configuration', () => { | ||
cli(['build', inputCssPath, '--config', customConfigPath]).then(() => { | ||
expect(process.stdout.write.mock.calls[0][0]).toContain('400px') | ||
}) | ||
}) | ||
|
||
it('creates compiled CSS file', () => { | ||
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') | ||
}) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,96 +1,6 @@ | ||
#!/usr/bin/env node | ||
/* eslint-disable no-process-exit */ | ||
|
||
import path from 'path' | ||
import fs from 'fs-extra' | ||
import tailwind from '..' | ||
import postcss from 'postcss' | ||
import process from 'process' | ||
import program from 'commander' | ||
import main from './cli/main' | ||
import * as utils from './cli/utils' | ||
|
||
function writeStrategy(options) { | ||
if (options.output === undefined) { | ||
return output => { | ||
process.stdout.write(output) | ||
} | ||
} | ||
return output => { | ||
fs.outputFileSync(options.output, output) | ||
} | ||
} | ||
|
||
function buildTailwind(inputFile, config, write) { | ||
console.warn('Building Tailwind!') | ||
|
||
const input = fs.readFileSync(inputFile, 'utf8') | ||
|
||
return postcss([tailwind(config)]) | ||
.process(input, { from: inputFile }) | ||
.then(result => { | ||
write(result.css) | ||
console.warn('Finished building Tailwind!') | ||
}) | ||
.catch(error => console.error(error)) | ||
} | ||
|
||
const packageJson = require(path.resolve(__dirname, '../package.json')) | ||
|
||
program.version(packageJson.version).usage('<command> [<args>]') | ||
|
||
program | ||
.command('init [filename]') | ||
.usage('[options] [filename]') | ||
.action((filename = 'tailwind.js') => { | ||
let destination = path.resolve(filename) | ||
|
||
if (!path.extname(filename).includes('.js')) { | ||
destination += '.js' | ||
} | ||
|
||
if (fs.existsSync(destination)) { | ||
console.error(`Destination ${destination} already exists, aborting.`) | ||
process.exit(1) | ||
} | ||
|
||
const output = fs.readFileSync(path.resolve(__dirname, '../defaultConfig.stub.js'), 'utf8') | ||
fs.outputFileSync(destination, output.replace('// let defaultConfig', 'let defaultConfig')) | ||
fs.outputFileSync( | ||
destination, | ||
output.replace("require('./plugins/container')", "require('tailwindcss/plugins/container')") | ||
) | ||
console.warn(`Generated Tailwind config: ${destination}`) | ||
process.exit() | ||
}) | ||
|
||
program | ||
.command('build') | ||
.usage('[options] <file ...>') | ||
.option('-c, --config [path]', 'Path to config file') | ||
.option('-o, --output [path]', 'Output file') | ||
.action((file, options) => { | ||
let inputFile = program.args[0] | ||
|
||
if (!inputFile) { | ||
console.error('No input file given!') | ||
process.exit(1) | ||
} | ||
|
||
buildTailwind(inputFile, options.config, writeStrategy(options)).then(() => { | ||
process.exit() | ||
}) | ||
}) | ||
|
||
program | ||
.command('*', null, { | ||
noHelp: true, | ||
}) | ||
.action(() => { | ||
program.help() | ||
}) | ||
|
||
program.parse(process.argv) | ||
|
||
if (program.args.length === 0) { | ||
program.help() | ||
process.exit() | ||
} | ||
main(process.argv.slice(2)).catch(error => utils.die(error.stack)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
import autoprefixer from 'autoprefixer' | ||
import bytes from 'bytes' | ||
import chalk from 'chalk' | ||
import postcss from 'postcss' | ||
import prettyHrtime from 'pretty-hrtime' | ||
|
||
import tailwind from '../..' | ||
|
||
import commands from '.' | ||
import * as emoji from '../emoji' | ||
import * as utils from '../utils' | ||
|
||
export const usage = 'build <file> [options]' | ||
export const description = 'Compiles Tailwind CSS file.' | ||
|
||
export const options = [ | ||
{ | ||
usage: '-o, --output <file>', | ||
description: 'Output file.', | ||
}, | ||
{ | ||
usage: '-c, --config <file>', | ||
description: 'Tailwind config file.', | ||
}, | ||
] | ||
|
||
export const optionMap = { | ||
output: ['output', 'o'], | ||
config: ['config', 'c'], | ||
} | ||
|
||
/** | ||
* Prints the error message and stops the process. | ||
* | ||
* @param {...string} [msgs] | ||
*/ | ||
function stop(...msgs) { | ||
utils.header() | ||
utils.error(...msgs) | ||
utils.die() | ||
} | ||
|
||
/** | ||
* Prints the error message and help for this command, then stops the process. | ||
* | ||
* @param {...string} [msgs] | ||
*/ | ||
function stopWithHelp(...msgs) { | ||
utils.header() | ||
utils.error(...msgs) | ||
commands.help.forCommand(commands.build) | ||
utils.die() | ||
} | ||
|
||
/** | ||
* Compiles CSS file. | ||
* | ||
* @param {string} inputFile | ||
* @param {string} configFile | ||
* @param {string} outputFile | ||
* @return {Promise} | ||
*/ | ||
function build(inputFile, configFile, outputFile) { | ||
const css = utils.readFile(inputFile) | ||
|
||
return new Promise((resolve, reject) => { | ||
postcss([tailwind(configFile), autoprefixer]) | ||
.process(css, { | ||
from: inputFile, | ||
to: outputFile, | ||
}) | ||
.then(resolve) | ||
.catch(reject) | ||
}) | ||
} | ||
|
||
/** | ||
* Compiles CSS file and writes it to stdout. | ||
* | ||
* @param {string} inputFile | ||
* @param {string} configFile | ||
* @param {string} outputFile | ||
* @return {Promise} | ||
*/ | ||
function buildToStdout(inputFile, configFile, outputFile) { | ||
return build(inputFile, configFile, outputFile).then(result => process.stdout.write(result.css)) | ||
} | ||
|
||
/** | ||
* Compiles CSS file and writes it to a file. | ||
* | ||
* @param {string} inputFile | ||
* @param {string} configFile | ||
* @param {string} outputFile | ||
* @param {int[]} startTime | ||
* @return {Promise} | ||
*/ | ||
function buildToFile(inputFile, configFile, outputFile, startTime) { | ||
utils.header() | ||
utils.log() | ||
utils.log(emoji.go, 'Building...', chalk.bold.cyan(inputFile)) | ||
|
||
return build(inputFile, configFile, outputFile).then(result => { | ||
utils.writeFile(outputFile, result.css) | ||
|
||
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(outputFile)) | ||
utils.footer() | ||
}) | ||
} | ||
|
||
/** | ||
* Runs the command. | ||
* | ||
* @param {string[]} cliParams | ||
* @param {object} cliOptions | ||
* @return {Promise} | ||
*/ | ||
export function run(cliParams, cliOptions) { | ||
return new Promise((resolve, reject) => { | ||
const startTime = process.hrtime() | ||
const inputFile = cliParams[0] | ||
const configFile = cliOptions.config && cliOptions.config[0] | ||
const outputFile = cliOptions.output && cliOptions.output[0] | ||
|
||
!inputFile && stopWithHelp('CSS file is required.') | ||
!utils.exists(inputFile) && stop(chalk.bold.magenta(inputFile), 'does not exist.') | ||
|
||
configFile && | ||
!utils.exists(configFile) && | ||
stop(chalk.bold.magenta(configFile), 'does not exist.') | ||
|
||
const buildPromise = outputFile | ||
? buildToFile(inputFile, configFile, outputFile, startTime) | ||
: buildToStdout(inputFile, configFile, outputFile) | ||
|
||
buildPromise.then(resolve).catch(reject) | ||
}) | ||
} |
Oops, something went wrong.