diff --git a/src/cli/colors.js b/src/cli/colors.js deleted file mode 100644 index 8ba560ae2..000000000 --- a/src/cli/colors.js +++ /dev/null @@ -1,46 +0,0 @@ -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 deleted file mode 100644 index 265a4daa4..000000000 --- a/src/cli/commands/build.js +++ /dev/null @@ -1,127 +0,0 @@ -import autoprefixer from 'autoprefixer' -import bytes from 'bytes' -import prettyHrtime from 'pretty-hrtime' - -import tailwind from '../..' - -import compile from '../compile' -import * as colors from '../colors' -import * as emoji from '../emoji' -import * as utils from '../utils' - -export const usage = 'build [options]' -export const description = 'Compiles Tailwind CSS file.' - -export const options = [ - { - usage: '-o, --output ', - description: 'Output file.', - }, - { - usage: '-c, --config ', - description: 'Tailwind config file.', - }, - { - usage: '--no-autoprefixer', - description: "Don't add vendor prefixes using autoprefixer.", - }, -] - -export const optionMap = { - output: ['output', 'o'], - config: ['config', 'c'], - noAutoprefixer: ['no-autoprefixer'], -} - -/** - * Prints the error message and stops the process. - * - * @param {...string} [msgs] - */ -function stop(...msgs) { - utils.header() - utils.error(...msgs) - utils.die() -} - -/** - * Compiles CSS file and writes it to stdout. - * - * @param {CompileOptions} compileOptions - * @return {Promise} - */ -function buildToStdout(compileOptions) { - return compile(compileOptions).then((result) => process.stdout.write(result.css)) -} - -/** - * Compiles CSS file and writes it to a file. - * - * @param {CompileOptions} compileOptions - * @param {int[]} startTime - * @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, - ...(inputFileSimplePath - ? ['Building:', colors.file(inputFileSimplePath)] - : ['Building from default CSS...', colors.info('(No input file provided)')]) - ) - - return compile(compileOptions).then((result) => { - utils.writeFile(compileOptions.outputFile, result.css) - - const prettyTime = prettyHrtime(process.hrtime(startTime)) - - utils.log() - 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() - }) -} - -/** - * 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] - const autoprefix = !cliOptions.noAutoprefixer - const inputFileSimplePath = utils.getSimplePath(inputFile) - const configFileSimplePath = utils.getSimplePath(configFile) - - if (inputFile) { - !utils.exists(inputFile) && stop(colors.file(inputFileSimplePath), 'does not exist.') - } - - configFile && - !utils.exists(configFile) && - stop(colors.file(configFileSimplePath), 'does not exist.') - - const compileOptions = { - inputFile, - outputFile, - plugins: [tailwind(configFile)].concat(autoprefix ? [autoprefixer] : []), - } - - const buildPromise = outputFile - ? buildToFile(compileOptions, startTime) - : buildToStdout(compileOptions) - - buildPromise.then(resolve).catch(reject) - }) -} diff --git a/src/cli/commands/help.js b/src/cli/commands/help.js deleted file mode 100644 index 879b0b60c..000000000 --- a/src/cli/commands/help.js +++ /dev/null @@ -1,85 +0,0 @@ -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]' -export const description = 'More information about the command.' - -const PADDING_SIZE = 3 - -/** - * Prints general help. - */ -export function forApp() { - const pad = Math.max(...map(commands, 'usage.length')) + PADDING_SIZE - - utils.log() - utils.log('Usage:') - utils.log(' ', colors.bold(constants.cli + ' [options]')) - utils.log() - utils.log('Commands:') - forEach(commands, (command) => { - utils.log(' ', colors.bold(padEnd(command.usage, pad)), command.description) - }) -} - -/** - * Prints help for a command. - * - * @param {object} command - */ -export function forCommand(command) { - utils.log() - utils.log('Usage:') - utils.log(' ', colors.bold(constants.cli, command.usage)) - utils.log() - utils.log('Description:') - utils.log(' ', colors.bold(command.description)) - - if (command.options) { - const pad = Math.max(...map(command.options, 'usage.length')) + PADDING_SIZE - - utils.log() - utils.log('Options:') - forEach(command.options, (option) => { - utils.log(' ', colors.bold(padEnd(option.usage, pad)), option.description) - }) - } -} - -/** - * Prints invalid command error and general help. Kills the process. - * - * @param {string} commandName - */ -export function invalidCommand(commandName) { - utils.error('Invalid command:', colors.command(commandName)) - forApp() - utils.die() -} - -/** - * Runs the command. - * - * @param {string[]} cliParams - * @return {Promise} - */ -export function run(cliParams) { - return new Promise((resolve) => { - utils.header() - - const commandName = cliParams[0] - const command = commands[commandName] - - !commandName && forApp() - commandName && command && forCommand(command) - commandName && !command && invalidCommand(commandName) - - utils.footer() - - resolve() - }) -} diff --git a/src/cli/commands/index.js b/src/cli/commands/index.js deleted file mode 100644 index 180121917..000000000 --- a/src/cli/commands/index.js +++ /dev/null @@ -1,5 +0,0 @@ -import * as help from './help' -import * as init from './init' -import * as build from './build' - -export default { help, init, build } diff --git a/src/cli/commands/init.js b/src/cli/commands/init.js deleted file mode 100644 index 157523ee5..000000000 --- a/src/cli/commands/init.js +++ /dev/null @@ -1,70 +0,0 @@ -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: ' + - colors.file(utils.getSimplePath(constants.defaultConfigFile)) - -export const options = [ - { - usage: '--full', - description: 'Generate complete configuration file.', - }, - { - usage: '-p', - description: 'Generate PostCSS config file.', - }, -] - -export const optionMap = { - full: ['full'], - postcss: ['p'], -} - -/** - * Runs the command. - * - * @param {string[]} cliParams - * @param {object} cliOptions - * @return {Promise} - */ -export function run(cliParams, cliOptions) { - return new Promise((resolve) => { - utils.header() - - const isModule = utils.isModule() - const full = cliOptions.full - const file = cliParams[0] || (isModule ? constants.cjsConfigFile : constants.defaultConfigFile) - const simplePath = utils.getSimplePath(file) - - utils.exists(file) && utils.die(colors.file(simplePath), 'already exists.') - - const stubFile = full ? constants.defaultConfigStubFile : constants.simpleConfigStubFile - const stubFileContents = utils - .readFile(stubFile, 'utf-8') - .replace('../colors', 'tailwindcss/colors') - - utils.writeFile(file, stubFileContents) - - utils.log() - utils.log(emoji.yes, 'Created Tailwind config file:', colors.file(simplePath)) - - if (cliOptions.postcss) { - const postCssConfigFile = isModule - ? constants.cjsPostCssConfigFile - : constants.defaultPostCssConfigFile - const path = utils.getSimplePath(postCssConfigFile) - utils.exists(constants.defaultPostCssConfigFile) && - utils.die(colors.file(path), 'already exists.') - utils.copyFile(constants.defaultPostCssConfigStubFile, postCssConfigFile) - utils.log(emoji.yes, 'Created PostCSS config file:', colors.file(path)) - } - - utils.footer() - - resolve() - }) -} diff --git a/src/cli/compile.js b/src/cli/compile.js deleted file mode 100644 index a98dac2cf..000000000 --- a/src/cli/compile.js +++ /dev/null @@ -1,47 +0,0 @@ -import path from 'path' -import postcss from 'postcss' - -import * as utils from './utils' - -/** - * Compiler options - * - * @typedef {Object} CompileOptions - * @property {string} inputFile - * @property {string} outputFile - * @property {array} plugins - */ - -const defaultOptions = { - inputFile: null, - outputFile: null, - plugins: [], -} - -/** - * Compiles CSS file. - * - * @param {CompileOptions} options - * @return {Promise} - */ -export default function compile(options = {}) { - const config = { ...defaultOptions, ...options } - - const css = config.inputFile - ? utils.readFile(config.inputFile) - : ` - @tailwind base; - @tailwind components; - @tailwind utilities; - ` - - return new Promise((resolve, reject) => { - postcss(config.plugins) - .process(css, { - from: config.inputFile || path.resolve(__dirname, '../../tailwind.css'), - to: config.outputFile, - }) - .then(resolve) - .catch(reject) - }) -} diff --git a/src/cli/emoji.js b/src/cli/emoji.js deleted file mode 100644 index e5548b715..000000000 --- a/src/cli/emoji.js +++ /dev/null @@ -1,8 +0,0 @@ -import { get } from 'node-emoji' - -export const yes = get('white_check_mark') -export const no = get('no_entry_sign') -export const go = get('rocket') -export const pack = get('package') -export const disk = get('floppy_disk') -export const warning = get('warning') diff --git a/src/cli/index.js b/src/cli/index.js deleted file mode 100755 index c30f2c6f3..000000000 --- a/src/cli/index.js +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env node - -import main from './main' -import * as utils from './utils' - -main(process.argv.slice(2)).catch((error) => utils.die(error.stack)) diff --git a/src/cli/main.js b/src/cli/main.js deleted file mode 100644 index 06d2947a4..000000000 --- a/src/cli/main.js +++ /dev/null @@ -1,22 +0,0 @@ -import commands from './commands' -import * as utils from './utils' - -/** - * CLI application entrypoint. - * - * @param {string[]} cliArgs - * @return {Promise} - */ -export default function run(cliArgs) { - return new Promise((resolve, reject) => { - const params = utils.parseCliParams(cliArgs) - const command = commands[params[0]] - const options = command ? utils.parseCliOptions(cliArgs, command.optionMap) : {} - - const commandPromise = command - ? command.run(params.slice(1), options) - : commands.help.run(params) - - commandPromise.then(resolve).catch(reject) - }) -} diff --git a/src/cli/utils.js b/src/cli/utils.js deleted file mode 100644 index cc80768aa..000000000 --- a/src/cli/utils.js +++ /dev/null @@ -1,158 +0,0 @@ -import { copyFileSync, ensureFileSync, existsSync, outputFileSync, readFileSync } from 'fs-extra' -import { findKey, mapValues, startsWith, trimStart } from 'lodash' -import path from 'path' - -import * as colors from './colors' -import * as emoji from './emoji' -import packageJson from '../../package.json' - -/** - * Gets CLI parameters. - * - * @param {string[]} cliArgs - * @return {string[]} - */ -export function parseCliParams(cliArgs) { - const firstOptionIndex = cliArgs.findIndex((cliArg) => cliArg.startsWith('-')) - - return firstOptionIndex > -1 ? cliArgs.slice(0, firstOptionIndex) : cliArgs -} - -/** - * Gets mapped CLI options. - * - * @param {string[]} cliArgs - * @param {object} [optionMap] - * @return {object} - */ -export function parseCliOptions(cliArgs, optionMap = {}) { - let options = {} - let currentOption = [] - - cliArgs.forEach((cliArg) => { - const option = cliArg.startsWith('-') && trimStart(cliArg, '-').toLowerCase() - const resolvedOption = findKey(optionMap, (aliases) => aliases.includes(option)) - - if (resolvedOption) { - currentOption = options[resolvedOption] || (options[resolvedOption] = []) - } else if (option) { - currentOption = [] - } else { - currentOption.push(cliArg) - } - }) - - return { ...mapValues(optionMap, () => undefined), ...options } -} - -/** - * Prints messages to console. - * - * @param {...string} [msgs] - */ -export function log(...msgs) { - console.log(' ', ...msgs) -} - -/** - * Prints application header to console. - */ -export function header() { - log() - log(colors.bold(packageJson.name), colors.info(packageJson.version)) -} - -/** - * Prints application footer to console. - */ -export function footer() { - log() -} - -/** - * Prints error messages to console. - * - * @param {...string} [msgs] - */ -export function error(...msgs) { - log() - console.error(' ', emoji.no, colors.error(msgs.join(' '))) -} - -/** - * Kills the process. Optionally prints error messages to console. - * - * @param {...string} [msgs] - */ -export function die(...msgs) { - msgs.length && error(...msgs) - footer() - process.exit(1) // eslint-disable-line -} - -/** - * Checks if path exists. - * - * @param {string} path - * @return {boolean} - */ -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. - * - * @param {string} path - * @return {string} - */ -export function readFile(path) { - return readFileSync(path, 'utf-8') -} - -/** - * Checks if current package.json uses type "module" - * - * @return {boolean} - */ -export function isModule() { - const pkgPath = path.resolve('./package.json') - if (exists(pkgPath)) { - const pkg = JSON.parse(readFile(pkgPath)) - return pkg.type && pkg.type === 'module' - } - return false -} - -/** - * Writes content to file. - * - * @param {string} path - * @param {string} content - * @return {string} - */ -export function writeFile(path, content) { - ensureFileSync(path) - - return outputFileSync(path, content) -} - -/** - * Strips leading ./ from path - * - * @param {string} path - * @return {string} - */ -export function getSimplePath(path) { - return startsWith(path, './') ? path.slice(2) : path -} diff --git a/tests/cli.compile.test.js b/tests/cli.compile.test.js deleted file mode 100644 index dc93a0702..000000000 --- a/tests/cli.compile.test.js +++ /dev/null @@ -1,19 +0,0 @@ -import path from 'path' - -import autoprefixer from 'autoprefixer' - -import tailwind from '../src' -import compile from '../src/cli/compile' - -describe('cli compile', () => { - const inputFile = path.resolve(__dirname, 'fixtures/tailwind-input.css') - const outputFile = 'output.css' - const plugins = [tailwind(), autoprefixer] - - it('compiles CSS file', () => { - return compile({ inputFile, outputFile, plugins }).then((result) => { - expect(result.css).toContain('.example') - expect(result.css).toContain('-webkit-background-clip') - }) - }) -}) diff --git a/tests/cli.test.js b/tests/cli.test.js deleted file mode 100644 index b464c2749..000000000 --- a/tests/cli.test.js +++ /dev/null @@ -1,122 +0,0 @@ -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') - const customConfigPath = path.resolve(__dirname, 'fixtures/custom-config.js') - const esmPackageJsonPath = path.resolve(__dirname, 'fixtures/esm-package.json') - const defaultConfigFixture = utils.readFile(constants.defaultConfigStubFile) - const simpleConfigFixture = utils.readFile(constants.simpleConfigStubFile) - const defaultPostCssConfigFixture = utils.readFile(constants.defaultPostCssConfigStubFile) - - beforeEach(() => { - console.log = jest.fn() - process.stdout.write = jest.fn() - }) - - describe('init', () => { - it('creates a Tailwind config file', () => { - return runInTempDirectory(() => { - return cli(['init']).then(() => { - expect(utils.readFile(constants.defaultConfigFile)).toEqual(simpleConfigFixture) - }) - }) - }) - - it('creates a Tailwind config file and a postcss.config.js file', () => { - return runInTempDirectory(() => { - return cli(['init', '-p']).then(() => { - expect(utils.readFile(constants.defaultConfigFile)).toEqual(simpleConfigFixture) - expect(utils.readFile(constants.defaultPostCssConfigFile)).toEqual( - defaultPostCssConfigFixture - ) - }) - }) - }) - - it('creates a full Tailwind config file', () => { - return runInTempDirectory(() => { - return cli(['init', '--full']).then(() => { - expect(utils.readFile(constants.defaultConfigFile)).toEqual( - defaultConfigFixture.replace('../colors', 'tailwindcss/colors') - ) - }) - }) - }) - - it('creates a .cjs Tailwind config file inside of an ESM project', () => { - return runInTempDirectory(() => { - utils.writeFile('package.json', utils.readFile(esmPackageJsonPath)) - return cli(['init']).then(() => { - expect(utils.readFile(constants.cjsConfigFile)).toEqual(simpleConfigFixture) - }) - }) - }) - - it('creates a .cjs Tailwind config file and a postcss.config.cjs file inside of an ESM project', () => { - return runInTempDirectory(() => { - utils.writeFile('package.json', utils.readFile(esmPackageJsonPath)) - return cli(['init', '-p']).then(() => { - expect(utils.readFile(constants.cjsConfigFile)).toEqual(simpleConfigFixture) - expect(utils.readFile(constants.cjsPostCssConfigFile)).toEqual( - defaultPostCssConfigFixture - ) - }) - }) - }) - - 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) - }) - }) - }) - }) - - describe('build', () => { - it('compiles CSS file using an input css file', () => { - return cli(['build', inputCssPath]).then(() => { - expect(process.stdout.write.mock.calls[0][0]).toContain('.example') - }) - }) - - it('compiles CSS file without an input css file', () => { - return cli(['build']).then(() => { - expect(process.stdout.write.mock.calls[0][0]).toContain('normalize.css') // base - expect(process.stdout.write.mock.calls[0][0]).toContain('.container') // components - expect(process.stdout.write.mock.calls[0][0]).toContain('.mx-auto') // utilities - }) - }) - - it('compiles CSS file using custom configuration', () => { - return cli(['build', inputCssPath, '--config', customConfigPath]).then(() => { - expect(process.stdout.write.mock.calls[0][0]).toContain('400px') - }) - }) - - it('creates compiled CSS file', () => { - return runInTempDirectory(() => { - return cli(['build', inputCssPath, '--output', 'output.css']).then(() => { - expect(utils.readFile('output.css')).toContain('.example') - }) - }) - }) - - it('compiles CSS file with autoprefixer', () => { - return cli(['build', inputCssPath]).then(() => { - expect(process.stdout.write.mock.calls[0][0]).toContain('-webkit-background-clip') - }) - }) - - it('compiles CSS file without autoprefixer', () => { - return cli(['build', inputCssPath, '--no-autoprefixer']).then(() => { - expect(process.stdout.write.mock.calls[0][0]).not.toContain('-webkit-background-clip') - }) - }) - }) -}) diff --git a/tests/cli.utils.test.js b/tests/cli.utils.test.js deleted file mode 100644 index ee167c8da..000000000 --- a/tests/cli.utils.test.js +++ /dev/null @@ -1,73 +0,0 @@ -import * as utils from '../src/cli/utils' - -describe('cli utils', () => { - describe('parseCliParams', () => { - it('parses CLI parameters', () => { - const result = utils.parseCliParams(['a', 'b', '-c', 'd']) - - expect(result).toEqual(['a', 'b']) - }) - }) - - describe('parseCliOptions', () => { - it('parses CLI options', () => { - const result = utils.parseCliOptions(['a', '-b', 'c'], { test: ['b'] }) - - expect(result).toEqual({ test: ['c'] }) - }) - - it('parses multiple types of options', () => { - const result = utils.parseCliOptions(['a', '-b', 'c', '--test', 'd', '-test', 'e'], { - test: ['test', 'b'], - }) - - expect(result).toEqual({ test: ['c', 'd', 'e'] }) - }) - - it('ignores unknown options', () => { - const result = utils.parseCliOptions(['a', '-b', 'c'], {}) - - expect(result).toEqual({}) - }) - - it('maps options', () => { - const result = utils.parseCliOptions(['a', '-b', 'c', '-d', 'e'], { test: ['b', 'd'] }) - - expect(result).toEqual({ test: ['c', 'e'] }) - }) - - it('parses undefined options', () => { - const result = utils.parseCliOptions(['a'], { test: ['b'] }) - - expect(result).toEqual({ test: undefined }) - }) - - it('parses flags', () => { - const result = utils.parseCliOptions(['a', '-b'], { test: ['b'] }) - - expect(result).toEqual({ test: [] }) - }) - - it('accepts multiple values per option', () => { - const result = utils.parseCliOptions(['a', '-b', 'c', 'd', '-e', 'f', '-g', 'h'], { - test: ['b', 'g'], - }) - - 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') - }) - }) -})