Skip to content

Commit 1b1ae8a

Browse files
committed
When no output file is specified for the build command, the result will be piped to stdout
1 parent d91eea8 commit 1b1ae8a

File tree

8 files changed

+172
-106
lines changed

8 files changed

+172
-106
lines changed

__tests__/cli.test.js

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import path from 'path'
22

3-
import cli from '../src/cli/main.js'
4-
import constants from '../src/cli/constants.js'
5-
import * as utils from '../src/cli/utils.js'
3+
import cli from '../src/cli/main'
4+
import constants from '../src/cli/constants'
5+
import * as utils from '../src/cli/utils'
66

77
describe('cli', () => {
88
const inputCssPath = path.resolve(__dirname, 'fixtures/tailwind-input.css')
99
const customConfigPath = path.resolve(__dirname, 'fixtures/custom-config.js')
1010

1111
beforeEach(() => {
12-
utils.log = jest.fn()
12+
console.log = jest.fn()
13+
process.stdout.write = jest.fn()
1314
utils.writeFile = jest.fn()
1415
})
1516

@@ -32,21 +33,19 @@ describe('cli', () => {
3233
describe('build', () => {
3334
it('compiles CSS file', () => {
3435
cli(['build', inputCssPath]).then(() => {
35-
expect(utils.writeFile.mock.calls[0][0]).toEqual(constants.defaultOutputFile)
36-
expect(utils.writeFile.mock.calls[0][1]).toContain('.example')
36+
expect(process.stdout.write.mock.calls[0][0]).toContain('.example')
3737
})
3838
})
3939

4040
it('compiles CSS file using custom configuration', () => {
4141
cli(['build', inputCssPath, '--config', customConfigPath]).then(() => {
42-
expect(utils.writeFile.mock.calls[0][0]).toEqual(constants.defaultOutputFile)
43-
expect(utils.writeFile.mock.calls[0][1]).toContain('400px')
42+
expect(process.stdout.write.mock.calls[0][0]).toContain('400px')
4443
})
4544
})
4645

47-
it('creates compiled CSS file in a custom location', () => {
48-
cli(['build', inputCssPath, '--output', 'custom.css']).then(() => {
49-
expect(utils.writeFile.mock.calls[0][0]).toEqual('custom.css')
46+
it('creates compiled CSS file', () => {
47+
cli(['build', inputCssPath, '--output', 'output.css']).then(() => {
48+
expect(utils.writeFile.mock.calls[0][0]).toEqual('output.css')
5049
expect(utils.writeFile.mock.calls[0][1]).toContain('.example')
5150
})
5251
})

src/cli.js

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,6 @@
11
#!/usr/bin/env node
22

33
import main from './cli/main'
4-
import { log, die } from './cli/utils'
4+
import { die } from './cli/utils'
55

6-
/**
7-
* Runs the CLI application.
8-
*/
9-
function run() {
10-
main(process.argv.slice(2))
11-
.then(() => log())
12-
.catch(error => die(error.stack))
13-
}
14-
15-
run()
6+
main(process.argv.slice(2)).catch(error => die(error.stack))

src/cli/commands/build.js

Lines changed: 100 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -5,76 +5,135 @@ import postcss from 'postcss'
55
import prettyHrtime from 'pretty-hrtime'
66

77
import commands from '.'
8-
import constants from '../constants'
98
import emoji from '../emoji'
109
import tailwind from '../..'
11-
import { error, exists, die, log, readFile, writeFile } from '../utils'
10+
import { die, error, exists, footer, header, log, readFile, writeFile } from '../utils'
1211

1312
export const usage = 'build <file> [options]'
1413
export const description = 'Compiles Tailwind CSS file.'
1514

1615
export const options = [
1716
{
18-
usage: '-c --config <file>',
19-
description: 'Tailwind config file.',
17+
usage: '-o, --output <file>',
18+
description: 'Output file.',
2019
},
2120
{
22-
usage: '-o --output <file>',
23-
description: 'Compiled CSS file. Default: ' + chalk.bold.magenta(constants.defaultOutputFile),
21+
usage: '-c, --config <file>',
22+
description: 'Tailwind config file.',
2423
},
2524
]
2625

2726
export const optionMap = {
28-
config: ['c', 'config'],
29-
output: ['o', 'output'],
27+
output: ['output', 'o'],
28+
config: ['config', 'c'],
3029
}
3130

3231
/**
33-
* Runs the command.
32+
* Prints the error message and stops the process.
3433
*
35-
* @param {string[]} cliParams
36-
* @param {object} cliOptions
34+
* @param {...string} [msgs]
35+
*/
36+
function stop(...msgs) {
37+
header()
38+
error(...msgs)
39+
die()
40+
}
41+
42+
/**
43+
* Prints the error message and help for this command, then stops the process.
44+
*
45+
* @param {...string} [msgs]
46+
*/
47+
function stopWithHelp(...msgs) {
48+
header()
49+
error(...msgs)
50+
commands.help.forCommand(commands.build)
51+
die()
52+
}
53+
54+
/**
55+
* Compiles CSS file.
56+
*
57+
* @param {string} inputFile
58+
* @param {string} configFile
59+
* @param {string} outputFile
3760
* @return {Promise}
3861
*/
39-
export function run(cliParams, cliOptions) {
62+
function build(inputFile, configFile, outputFile) {
63+
const css = readFile(inputFile)
64+
4065
return new Promise((resolve, reject) => {
41-
const time = process.hrtime()
42-
const inputFile = cliParams[1]
43-
const configFile = cliOptions.config && cliOptions.config[0]
44-
const outputFile = (cliOptions.output && cliOptions.output[0]) || constants.defaultOutputFile
66+
postcss([tailwind(configFile), autoprefixer])
67+
.process(css, {
68+
from: inputFile,
69+
to: outputFile,
70+
})
71+
.then(resolve)
72+
.catch(reject)
73+
})
74+
}
4575

46-
if (!inputFile) {
47-
error('CSS file is required.')
48-
commands.help.forCommand(this)
49-
die()
50-
}
76+
/**
77+
* Compiles CSS file and writes it to stdout.
78+
*
79+
* @param {string} inputFile
80+
* @param {string} configFile
81+
* @param {string} outputFile
82+
* @return {Promise}
83+
*/
84+
function buildToStdout(inputFile, configFile, outputFile) {
85+
return build(inputFile, configFile, outputFile).then(result => process.stdout.write(result.css))
86+
}
5187

52-
!exists(inputFile) && die(chalk.bold.magenta(inputFile), 'does not exist.')
53-
configFile && !exists(configFile) && die(chalk.bold.magenta(configFile), 'does not exist.')
88+
/**
89+
* Compiles CSS file and writes it to a file.
90+
*
91+
* @param {string} inputFile
92+
* @param {string} configFile
93+
* @param {string} outputFile
94+
* @param {int[]} startTime
95+
* @return {Promise}
96+
*/
97+
function buildToFile(inputFile, configFile, outputFile, startTime) {
98+
header()
99+
log()
100+
log(emoji.go, 'Building...', chalk.bold.cyan(inputFile))
54101

55-
log()
56-
log(emoji.go, 'Building', chalk.bold.cyan(inputFile))
102+
return build(inputFile, configFile, outputFile).then(result => {
103+
writeFile(outputFile, result.css)
57104

58-
const css = readFile(inputFile)
105+
const prettyTime = prettyHrtime(process.hrtime(startTime))
59106

60-
const postcssPromise = postcss([tailwind(configFile), autoprefixer]).process(css, {
61-
from: inputFile,
62-
to: outputFile,
63-
})
107+
log()
108+
log(emoji.yes, 'Finished in', chalk.bold.magenta(prettyTime))
109+
log(emoji.pack, 'Size:', chalk.bold.magenta(bytes(result.css.length)))
110+
log(emoji.disk, 'Saved to', chalk.bold.cyan(outputFile))
111+
footer()
112+
})
113+
}
64114

65-
postcssPromise
66-
.then(result => {
67-
writeFile(outputFile, result.css)
115+
/**
116+
* Runs the command.
117+
*
118+
* @param {string[]} cliParams
119+
* @param {object} cliOptions
120+
* @return {Promise}
121+
*/
122+
export function run(cliParams, cliOptions) {
123+
return new Promise((resolve, reject) => {
124+
const startTime = process.hrtime()
125+
const inputFile = cliParams[0]
126+
const configFile = cliOptions.config && cliOptions.config[0]
127+
const outputFile = cliOptions.output && cliOptions.output[0]
68128

69-
const prettyTime = prettyHrtime(process.hrtime(time))
129+
!inputFile && stopWithHelp('CSS file is required.')
130+
!exists(inputFile) && stop(chalk.bold.magenta(inputFile), 'does not exist.')
131+
configFile && !exists(configFile) && stop(chalk.bold.magenta(configFile), 'does not exist.')
70132

71-
log()
72-
log(emoji.yes, 'Finished in', chalk.bold.magenta(prettyTime))
73-
log(emoji.pack, 'Size:', chalk.bold.magenta(bytes(result.css.length)))
74-
log(emoji.disk, 'Saved to', chalk.bold.cyan(outputFile))
133+
const promise = outputFile
134+
? buildToFile(inputFile, configFile, outputFile, startTime)
135+
: buildToStdout(inputFile, configFile, outputFile)
75136

76-
resolve()
77-
})
78-
.catch(reject)
137+
promise.then(resolve).catch(reject)
79138
})
80139
}

src/cli/commands/help.js

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import chalk from 'chalk'
2-
import { forEach, map } from 'lodash'
2+
import { forEach, map, padEnd } from 'lodash'
33

44
import commands from '.'
55
import constants from '../constants'
6-
import { error, die, log } from '../utils'
6+
import { die, error, footer, header, log } from '../utils'
77

88
export const usage = 'help [command]'
99
export const description = 'More information about the command.'
@@ -20,7 +20,7 @@ export function forApp() {
2020
log()
2121
log('Commands:')
2222
forEach(commands, command => {
23-
log(' ', chalk.bold(command.usage.padEnd(pad)), command.description)
23+
log(' ', chalk.bold(padEnd(command.usage, pad)), command.description)
2424
})
2525
}
2626

@@ -43,7 +43,7 @@ export function forCommand(command) {
4343
log()
4444
log('Options:')
4545
forEach(command.options, option => {
46-
log(' ', chalk.bold(option.usage.padEnd(pad)), option.description)
46+
log(' ', chalk.bold(padEnd(option.usage, pad)), option.description)
4747
})
4848
}
4949
}
@@ -67,12 +67,16 @@ export function invalidCommand(commandName) {
6767
*/
6868
export function run(cliParams) {
6969
return new Promise(resolve => {
70-
const command = cliParams[1]
70+
header()
7171

72-
!command && forApp()
73-
command && commands[command] && forCommand(commands[command])
74-
command && !commands[command] && invalidCommand(command)
72+
const commandName = cliParams[0]
73+
const command = commands[commandName]
7574

75+
!commandName && forApp()
76+
commandName && command && forCommand(command)
77+
commandName && !command && invalidCommand(commandName)
78+
79+
footer()
7680
resolve()
7781
})
7882
}

src/cli/commands/init.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import chalk from 'chalk'
22

33
import constants from '../constants'
44
import emoji from '../emoji'
5-
import { exists, die, log, readFile, writeFile } from '../utils'
5+
import { die, exists, footer, header, log, readFile, writeFile } from '../utils'
66

77
export const usage = 'init [file]'
88
export const description =
@@ -16,22 +16,22 @@ export const description =
1616
*/
1717
export function run(cliParams) {
1818
return new Promise(resolve => {
19-
const file = cliParams[1] || constants.defaultConfigFile
19+
header()
20+
21+
const file = cliParams[0] || constants.defaultConfigFile
2022

2123
exists(file) && die(chalk.bold.magenta(file), 'already exists.')
2224

23-
let stub = readFile(constants.configStubFile)
24-
stub = stub.replace('// let defaultConfig', 'let defaultConfig')
25-
stub = stub.replace(
26-
"require('./plugins/container')",
27-
"require('tailwindcss/plugins/container')"
28-
)
25+
const stub = readFile(constants.configStubFile)
26+
.replace('// let defaultConfig', 'let defaultConfig')
27+
.replace("require('./plugins/container')", "require('tailwindcss/plugins/container')")
2928

3029
writeFile(file, stub)
3130

3231
log()
3332
log(emoji.yes, 'Created Tailwind config file:', chalk.bold.magenta(file))
3433

34+
footer()
3535
resolve()
3636
})
3737
}

src/cli/constants.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,5 @@ import path from 'path'
33
export default {
44
cli: 'tailwind',
55
defaultConfigFile: 'tailwind.js',
6-
defaultOutputFile: 'output.css',
76
configStubFile: path.resolve(__dirname, '../../defaultConfig.stub.js'),
87
}

src/cli/main.js

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,20 @@
1-
import chalk from 'chalk'
2-
31
import commands from './commands'
4-
import packageJson from '../../package.json'
5-
import { log, parseCliOptions, parseCliParams } from './utils'
2+
import { parseCliOptions, parseCliParams } from './utils'
63

74
/**
85
* CLI application entrypoint.
6+
*
7+
* @param {string[]} cliArgs
8+
* @return {Promise}
99
*/
10-
export default function run(args) {
11-
log()
12-
log(chalk.bold(packageJson.name), chalk.bold.cyan(packageJson.version))
13-
14-
const params = parseCliParams(args)
15-
const commandName = params[0] || 'help'
16-
17-
!commands[commandName] && commands.help.invalidCommand(commandName)
10+
export default function run(cliArgs) {
11+
return new Promise((resolve, reject) => {
12+
const params = parseCliParams(cliArgs)
13+
const command = commands[params[0]]
14+
const options = command ? parseCliOptions(cliArgs, command.optionMap) : {}
1815

19-
const options = parseCliOptions(args, commands[commandName].optionMap)
16+
const promise = command ? command.run(params.slice(1), options) : commands.help.run(params)
2017

21-
return commands[commandName].run(params, options)
18+
promise.then(resolve).catch(reject)
19+
})
2220
}

0 commit comments

Comments
 (0)