* add `jiti` and `detective-typescript` dependencies
* use `jiti` and `detective-typescript`
Instead of `detective`, this way we will be able to support
`tailwind.config.ts` files and `ESM` files.
* use `@swc/core` instead of the built-in `babel` form `jiti`
* update changelog
* add `jiti` and `detective-typescript` dependencies to `stable`
* use `sucrase` to transform the configs
* add `sucrase` dependency to `stable` engine
* make loading the config easier
* use abstracted loading config utils
* WIP: make `load` related files public API
* use new config loader in PostCSS plugin
* add list of default config files to look for
* cleanup unused arguments
* find default config path when using CLI
* improve `init` command
* make eslint happy
* keep all files in `stubs` folder
* add `tailwind.config.js` stub file
* Initialize PostCSS config using the same format as Tailwind config
* Rename config content stubs to config.*.js
* Improve option descriptions for init options
* Remove unused code, remove `constants` file
* Fix TS warning
* apply CLI changes to the Oxide version
* update `--help` output in CLI tests
* WIP: make tests work on CI
TODO: Test all combinations of `--full`, `--ts`, `--postcss`, and `--esm`.
* wip
* remove unused `fs`
* Fix init tests
Did you know you could pass an empty args to a command? No? Me neither. ¯\_(ツ)_/¯
* bump `napi-derive`
* list extensions we are interested in
* no-op the `removeFile` if file doesn't exist
* ensure all `init` flags work
* ensure we cleanup the new files
* test ESM/CJS generation based on package.json
* remove unnecessary test
We are not displaying output in the `--help` anymore based on whether
`type: module` is present or not.
Therefore this test is unneeded.
* only look for `TypeScript` files when the entryFile is `TypeScript` as well
* refactor `load` to be `loadConfig`
This will allow you to use:
```js
import loadConfig from 'tailwindcss/loadConfig'
let config = loadConfig("/Users/xyz/projects/my-app/tailwind.config.ts")
```
The `loadConfig` function will return the configuration object based on
the given absolute path of a tailwind configuration file.
The given path can be a CJS, an ESM or a TS file.
* use the `config.full.js` stub instead of the `defaultConfig.stub.js` file
The root `defaultConfig` is still there for backwards compatibilty
reasons. But the `module.exports = requrie('./config.full.js')` was
causing some problems when actually using tailwindcss.
So dropped it instead.
* apply `load` -> `loadConfig` changes to `Oxide` engine CLI
* ensure we write the config file in the Oxide engine
* improve type in Oxide engine CLI
* catch errors instead of checking if the file exists
A little smaller but just for tests so doesn't matter too much here 👍
* ensure we publish the correct stub files
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Co-authored-by: Nate Moore <nate@natemoo.re>
Co-authored-by: Enzo Innocenzi <enzo@innocenzi.dev>
216 lines
6 KiB
JavaScript
216 lines
6 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
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)
|