Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 47 additions & 15 deletions __tests__/cli.test.js
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')
})
})
})
})
2 changes: 1 addition & 1 deletion __tests__/customConfig.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import postcss from 'postcss'
import tailwind from '../src/index'

test('it uses the values from the custom config file', () => {
return postcss([tailwind(path.resolve(`${__dirname}/fixtures/customConfig.js`))])
return postcss([tailwind(path.resolve(`${__dirname}/fixtures/custom-config.js`))])
.process(
`
@responsive {
Expand Down
File renamed without changes.
3 changes: 0 additions & 3 deletions __tests__/fixtures/tailwind-cli-input.css

This file was deleted.

3 changes: 0 additions & 3 deletions __tests__/fixtures/tailwind-cli-output.css

This file was deleted.

9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
"test": "jest && eslint . && nsp check"
},
"devDependencies": {
"autoprefixer": "^7.1.6",
"babel-cli": "^6.6.5",
"babel-core": "^6.7.2",
"babel-jest": "^20.0.3",
Expand All @@ -43,16 +42,20 @@
"rimraf": "^2.6.1"
},
"dependencies": {
"commander": "^2.11.0",
"autoprefixer": "^7.1.6",
"bytes": "^3.0.0",
"chalk": "^2.4.1",
"css.escape": "^1.5.1",
"fs-extra": "^4.0.2",
"lodash": "^4.17.5",
"node-emoji": "^1.8.1",
"perfectionist": "^2.4.0",
"postcss": "^6.0.9",
"postcss-functions": "^3.0.0",
"postcss-js": "^1.0.1",
"postcss-nested": "^3.0.0",
"postcss-selector-parser": "^3.1.1"
"postcss-selector-parser": "^3.1.1",
"pretty-hrtime": "^1.0.3"
},
"browserslist": [
"> 1%"
Expand Down
96 changes: 3 additions & 93 deletions src/cli.js
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))
143 changes: 143 additions & 0 deletions src/cli/commands/build.js
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)
})
}
Loading