Spaces:
Running
Running
import path from 'path' | |
import arg from 'arg' | |
import fs from 'fs' | |
import { build } from './build' | |
import { help } from './help' | |
import { init } from './init' | |
function oneOf(...options) { | |
return Object.assign( | |
(value = true) => { | |
for (let option of options) { | |
let parsed = option(value) | |
if (parsed === value) { | |
return parsed | |
} | |
} | |
throw new Error('...') | |
}, | |
{ manualParsing: true } | |
) | |
} | |
let commands = { | |
init: { | |
run: init, | |
args: { | |
'--esm': { type: Boolean, description: `Initialize configuration file as ESM` }, | |
'--ts': { type: Boolean, description: `Initialize configuration file as TypeScript` }, | |
'--postcss': { type: Boolean, description: `Initialize a \`postcss.config.js\` file` }, | |
'--full': { | |
type: Boolean, | |
description: `Include the default values for all options in the generated configuration file`, | |
}, | |
'-f': '--full', | |
'-p': '--postcss', | |
}, | |
}, | |
build: { | |
run: build, | |
args: { | |
'--input': { type: String, description: 'Input file' }, | |
'--output': { type: String, description: 'Output file' }, | |
'--watch': { | |
type: oneOf(String, Boolean), | |
description: 'Watch for changes and rebuild as needed', | |
}, | |
'--poll': { | |
type: Boolean, | |
description: 'Use polling instead of filesystem events when watching', | |
}, | |
'--content': { | |
type: String, | |
description: 'Content paths to use for removing unused classes', | |
}, | |
'--purge': { | |
type: String, | |
deprecated: true, | |
}, | |
'--postcss': { | |
type: oneOf(String, Boolean), | |
description: 'Load custom PostCSS configuration', | |
}, | |
'--minify': { type: Boolean, description: 'Minify the output' }, | |
'--config': { | |
type: String, | |
description: 'Path to a custom config file', | |
}, | |
'--no-autoprefixer': { | |
type: Boolean, | |
description: 'Disable autoprefixer', | |
}, | |
'-c': '--config', | |
'-i': '--input', | |
'-o': '--output', | |
'-m': '--minify', | |
'-w': '--watch', | |
'-p': '--poll', | |
}, | |
}, | |
} | |
let sharedFlags = { | |
'--help': { type: Boolean, description: 'Display usage information' }, | |
'-h': '--help', | |
} | |
if ( | |
process.stdout.isTTY /* Detect redirecting output to a file */ && | |
(process.argv[2] === undefined || | |
process.argv.slice(2).every((flag) => sharedFlags[flag] !== undefined)) | |
) { | |
help({ | |
usage: [ | |
'tailwindcss [--input input.css] [--output output.css] [--watch] [options...]', | |
'tailwindcss init [--full] [--postcss] [options...]', | |
], | |
commands: Object.keys(commands) | |
.filter((command) => command !== 'build') | |
.map((command) => `${command} [options]`), | |
options: { ...commands.build.args, ...sharedFlags }, | |
}) | |
process.exit(0) | |
} | |
let command = ((arg = '') => (arg.startsWith('-') ? undefined : arg))(process.argv[2]) || 'build' | |
if (commands[command] === undefined) { | |
if (fs.existsSync(path.resolve(command))) { | |
// TODO: Deprecate this in future versions | |
// Check if non-existing command, might be a file. | |
command = 'build' | |
} else { | |
help({ | |
message: `Invalid command: ${command}`, | |
usage: ['tailwindcss <command> [options]'], | |
commands: Object.keys(commands) | |
.filter((command) => command !== 'build') | |
.map((command) => `${command} [options]`), | |
options: sharedFlags, | |
}) | |
process.exit(1) | |
} | |
} | |
// Execute command | |
let { args: flags, run } = commands[command] | |
let args = (() => { | |
try { | |
let result = arg( | |
Object.fromEntries( | |
Object.entries({ ...flags, ...sharedFlags }) | |
.filter(([_key, value]) => !value?.type?.manualParsing) | |
.map(([key, value]) => [key, typeof value === 'object' ? value.type : value]) | |
), | |
{ permissive: true } | |
) | |
// Manual parsing of flags to allow for special flags like oneOf(Boolean, String) | |
for (let i = result['_'].length - 1; i >= 0; --i) { | |
let flag = result['_'][i] | |
if (!flag.startsWith('-')) continue | |
let [flagName, flagValue] = flag.split('=') | |
let handler = flags[flagName] | |
// Resolve flagName & handler | |
while (typeof handler === 'string') { | |
flagName = handler | |
handler = flags[handler] | |
} | |
if (!handler) continue | |
let args = [] | |
let offset = i + 1 | |
// --flag value syntax was used so we need to pull `value` from `args` | |
if (flagValue === undefined) { | |
// Parse args for current flag | |
while (result['_'][offset] && !result['_'][offset].startsWith('-')) { | |
args.push(result['_'][offset++]) | |
} | |
// Cleanup manually parsed flags + args | |
result['_'].splice(i, 1 + args.length) | |
// No args were provided, use default value defined in handler | |
// One arg was provided, use that directly | |
// Multiple args were provided so pass them all in an array | |
flagValue = args.length === 0 ? undefined : args.length === 1 ? args[0] : args | |
} else { | |
// Remove the whole flag from the args array | |
result['_'].splice(i, 1) | |
} | |
// Set the resolved value in the `result` object | |
result[flagName] = handler.type(flagValue, flagName) | |
} | |
// Ensure that the `command` is always the first argument in the `args`. | |
// This is important so that we don't have to check if a default command | |
// (build) was used or not from within each plugin. | |
// | |
// E.g.: tailwindcss input.css -> _: ['build', 'input.css'] | |
// E.g.: tailwindcss build input.css -> _: ['build', 'input.css'] | |
if (result['_'][0] !== command) { | |
result['_'].unshift(command) | |
} | |
return result | |
} catch (err) { | |
if (err.code === 'ARG_UNKNOWN_OPTION') { | |
help({ | |
message: err.message, | |
usage: ['tailwindcss <command> [options]'], | |
options: sharedFlags, | |
}) | |
process.exit(1) | |
} | |
throw err | |
} | |
})() | |
if (args['--help']) { | |
help({ | |
options: { ...flags, ...sharedFlags }, | |
usage: [`tailwindcss ${command} [options]`], | |
}) | |
process.exit(0) | |
} | |
run(args) | |