summaryrefslogtreecommitdiff
path: root/node_modules/tailwindcss/src/cli
diff options
context:
space:
mode:
authorPhilipp Tanlak <philipp.tanlak@gmail.com>2025-11-24 20:54:57 +0100
committerPhilipp Tanlak <philipp.tanlak@gmail.com>2025-11-24 20:57:48 +0100
commitb1e2c8fd5cb5dfa46bc440a12eafaf56cd844b1c (patch)
tree49d360fd6cbc6a2754efe93524ac47ff0fbe0f7d /node_modules/tailwindcss/src/cli
Docs
Diffstat (limited to 'node_modules/tailwindcss/src/cli')
-rw-r--r--node_modules/tailwindcss/src/cli/build/deps.js56
-rw-r--r--node_modules/tailwindcss/src/cli/build/index.js49
-rw-r--r--node_modules/tailwindcss/src/cli/build/plugin.js444
-rw-r--r--node_modules/tailwindcss/src/cli/build/utils.js76
-rw-r--r--node_modules/tailwindcss/src/cli/build/watching.js229
-rw-r--r--node_modules/tailwindcss/src/cli/help/index.js70
-rw-r--r--node_modules/tailwindcss/src/cli/index.js216
-rw-r--r--node_modules/tailwindcss/src/cli/init/index.js79
8 files changed, 1219 insertions, 0 deletions
diff --git a/node_modules/tailwindcss/src/cli/build/deps.js b/node_modules/tailwindcss/src/cli/build/deps.js
new file mode 100644
index 0000000..9435b92
--- /dev/null
+++ b/node_modules/tailwindcss/src/cli/build/deps.js
@@ -0,0 +1,56 @@
+// @ts-check
+
+import {
+ // @ts-ignore
+ lazyPostcss,
+
+ // @ts-ignore
+ lazyPostcssImport,
+
+ // @ts-ignore
+ lazyCssnano,
+
+ // @ts-ignore
+ lazyAutoprefixer,
+} from '../../../peers/index.js'
+
+/**
+ * @returns {import('postcss')}
+ */
+export function loadPostcss() {
+ // Try to load a local `postcss` version first
+ try {
+ return require('postcss')
+ } catch {}
+
+ return lazyPostcss()
+}
+
+export function loadPostcssImport() {
+ // Try to load a local `postcss-import` version first
+ try {
+ return require('postcss-import')
+ } catch {}
+
+ return lazyPostcssImport()
+}
+
+export function loadCssNano() {
+ let options = { preset: ['default', { cssDeclarationSorter: false }] }
+
+ // Try to load a local `cssnano` version first
+ try {
+ return require('cssnano')
+ } catch {}
+
+ return lazyCssnano()(options)
+}
+
+export function loadAutoprefixer() {
+ // Try to load a local `autoprefixer` version first
+ try {
+ return require('autoprefixer')
+ } catch {}
+
+ return lazyAutoprefixer()
+}
diff --git a/node_modules/tailwindcss/src/cli/build/index.js b/node_modules/tailwindcss/src/cli/build/index.js
new file mode 100644
index 0000000..62c020e
--- /dev/null
+++ b/node_modules/tailwindcss/src/cli/build/index.js
@@ -0,0 +1,49 @@
+// @ts-check
+
+import fs from 'fs'
+import path from 'path'
+import { resolveDefaultConfigPath } from '../../util/resolveConfigPath.js'
+import { createProcessor } from './plugin.js'
+
+export async function build(args) {
+ let input = args['--input']
+ let shouldWatch = args['--watch']
+
+ // TODO: Deprecate this in future versions
+ if (!input && args['_'][1]) {
+ console.error('[deprecation] Running tailwindcss without -i, please provide an input file.')
+ input = args['--input'] = args['_'][1]
+ }
+
+ if (input && input !== '-' && !fs.existsSync((input = path.resolve(input)))) {
+ console.error(`Specified input file ${args['--input']} does not exist.`)
+ process.exit(9)
+ }
+
+ if (args['--config'] && !fs.existsSync((args['--config'] = path.resolve(args['--config'])))) {
+ console.error(`Specified config file ${args['--config']} does not exist.`)
+ process.exit(9)
+ }
+
+ // TODO: Reference the @config path here if exists
+ let configPath = args['--config'] ? args['--config'] : resolveDefaultConfigPath()
+
+ let processor = await createProcessor(args, configPath)
+
+ if (shouldWatch) {
+ // Abort the watcher if stdin is closed to avoid zombie processes
+ // You can disable this behavior with --watch=always
+ if (args['--watch'] !== 'always') {
+ process.stdin.on('end', () => process.exit(0))
+ }
+
+ process.stdin.resume()
+
+ await processor.watch()
+ } else {
+ await processor.build().catch((e) => {
+ console.error(e)
+ process.exit(1)
+ })
+ }
+}
diff --git a/node_modules/tailwindcss/src/cli/build/plugin.js b/node_modules/tailwindcss/src/cli/build/plugin.js
new file mode 100644
index 0000000..6af590d
--- /dev/null
+++ b/node_modules/tailwindcss/src/cli/build/plugin.js
@@ -0,0 +1,444 @@
+// @ts-check
+
+import path from 'path'
+import fs from 'fs'
+import postcssrc from 'postcss-load-config'
+import { lilconfig } from 'lilconfig'
+import loadPlugins from 'postcss-load-config/src/plugins' // Little bit scary, looking at private/internal API
+import loadOptions from 'postcss-load-config/src/options' // Little bit scary, looking at private/internal API
+
+import tailwind from '../../processTailwindFeatures'
+import { loadAutoprefixer, loadCssNano, loadPostcss, loadPostcssImport } from './deps'
+import { formatNodes, drainStdin, outputFile } from './utils'
+import { env } from '../../lib/sharedState'
+import resolveConfig from '../../../resolveConfig.js'
+import { parseCandidateFiles } from '../../lib/content.js'
+import { createWatcher } from './watching.js'
+import fastGlob from 'fast-glob'
+import { findAtConfigPath } from '../../lib/findAtConfigPath.js'
+import log from '../../util/log'
+import { loadConfig } from '../../lib/load-config'
+import getModuleDependencies from '../../lib/getModuleDependencies'
+
+/**
+ *
+ * @param {string} [customPostCssPath ]
+ * @returns
+ */
+async function loadPostCssPlugins(customPostCssPath) {
+ let config = customPostCssPath
+ ? await (async () => {
+ let file = path.resolve(customPostCssPath)
+
+ // Implementation, see: https://unpkg.com/browse/postcss-load-config@3.1.0/src/index.js
+ // @ts-ignore
+ let { config = {} } = await lilconfig('postcss').load(file)
+ if (typeof config === 'function') {
+ config = config()
+ } else {
+ config = Object.assign({}, config)
+ }
+
+ if (!config.plugins) {
+ config.plugins = []
+ }
+
+ return {
+ file,
+ plugins: loadPlugins(config, file),
+ options: loadOptions(config, file),
+ }
+ })()
+ : await postcssrc()
+
+ let configPlugins = config.plugins
+
+ let configPluginTailwindIdx = configPlugins.findIndex((plugin) => {
+ if (typeof plugin === 'function' && plugin.name === 'tailwindcss') {
+ return true
+ }
+
+ if (typeof plugin === 'object' && plugin !== null && plugin.postcssPlugin === 'tailwindcss') {
+ return true
+ }
+
+ return false
+ })
+
+ let beforePlugins =
+ configPluginTailwindIdx === -1 ? [] : configPlugins.slice(0, configPluginTailwindIdx)
+ let afterPlugins =
+ configPluginTailwindIdx === -1
+ ? configPlugins
+ : configPlugins.slice(configPluginTailwindIdx + 1)
+
+ return [beforePlugins, afterPlugins, config.options]
+}
+
+function loadBuiltinPostcssPlugins() {
+ let postcss = loadPostcss()
+ let IMPORT_COMMENT = '__TAILWIND_RESTORE_IMPORT__: '
+ return [
+ [
+ (root) => {
+ root.walkAtRules('import', (rule) => {
+ if (rule.params.slice(1).startsWith('tailwindcss/')) {
+ rule.after(postcss.comment({ text: IMPORT_COMMENT + rule.params }))
+ rule.remove()
+ }
+ })
+ },
+ loadPostcssImport(),
+ (root) => {
+ root.walkComments((rule) => {
+ if (rule.text.startsWith(IMPORT_COMMENT)) {
+ rule.after(
+ postcss.atRule({
+ name: 'import',
+ params: rule.text.replace(IMPORT_COMMENT, ''),
+ })
+ )
+ rule.remove()
+ }
+ })
+ },
+ ],
+ [],
+ {},
+ ]
+}
+
+let state = {
+ /** @type {any} */
+ context: null,
+
+ /** @type {ReturnType<typeof createWatcher> | null} */
+ watcher: null,
+
+ /** @type {{content: string, extension: string}[]} */
+ changedContent: [],
+
+ /** @type {ReturnType<typeof load> | null} */
+ configBag: null,
+
+ contextDependencies: new Set(),
+
+ /** @type {import('../../lib/content.js').ContentPath[]} */
+ contentPaths: [],
+
+ refreshContentPaths() {
+ this.contentPaths = parseCandidateFiles(this.context, this.context?.tailwindConfig)
+ },
+
+ get config() {
+ return this.context.tailwindConfig
+ },
+
+ get contentPatterns() {
+ return {
+ all: this.contentPaths.map((contentPath) => contentPath.pattern),
+ dynamic: this.contentPaths
+ .filter((contentPath) => contentPath.glob !== undefined)
+ .map((contentPath) => contentPath.pattern),
+ }
+ },
+
+ loadConfig(configPath, content) {
+ if (this.watcher && configPath) {
+ this.refreshConfigDependencies()
+ }
+
+ let config = loadConfig(configPath)
+ let dependencies = getModuleDependencies(configPath)
+ this.configBag = {
+ config,
+ dependencies,
+ dispose() {
+ for (let file of dependencies) {
+ delete require.cache[require.resolve(file)]
+ }
+ },
+ }
+
+ // @ts-ignore
+ this.configBag.config = resolveConfig(this.configBag.config, { content: { files: [] } })
+
+ // Override content files if `--content` has been passed explicitly
+ if (content?.length > 0) {
+ this.configBag.config.content.files = content
+ }
+
+ return this.configBag.config
+ },
+
+ refreshConfigDependencies() {
+ env.DEBUG && console.time('Module dependencies')
+ this.configBag?.dispose()
+ env.DEBUG && console.timeEnd('Module dependencies')
+ },
+
+ readContentPaths() {
+ let content = []
+
+ // Resolve globs from the content config
+ // TODO: When we make the postcss plugin async-capable this can become async
+ let files = fastGlob.sync(this.contentPatterns.all)
+
+ for (let file of files) {
+ if (__OXIDE__) {
+ content.push({
+ file,
+ extension: path.extname(file).slice(1),
+ })
+ } else {
+ content.push({
+ content: fs.readFileSync(path.resolve(file), 'utf8'),
+ extension: path.extname(file).slice(1),
+ })
+ }
+ }
+
+ // Resolve raw content in the tailwind config
+ let rawContent = this.config.content.files.filter((file) => {
+ return file !== null && typeof file === 'object'
+ })
+
+ for (let { raw: htmlContent, extension = 'html' } of rawContent) {
+ content.push({ content: htmlContent, extension })
+ }
+
+ return content
+ },
+
+ getContext({ createContext, cliConfigPath, root, result, content }) {
+ if (this.context) {
+ this.context.changedContent = this.changedContent.splice(0)
+
+ return this.context
+ }
+
+ env.DEBUG && console.time('Searching for config')
+ let configPath = findAtConfigPath(root, result) ?? cliConfigPath
+ env.DEBUG && console.timeEnd('Searching for config')
+
+ env.DEBUG && console.time('Loading config')
+ let config = this.loadConfig(configPath, content)
+ env.DEBUG && console.timeEnd('Loading config')
+
+ env.DEBUG && console.time('Creating context')
+ this.context = createContext(config, [])
+ Object.assign(this.context, {
+ userConfigPath: configPath,
+ })
+ env.DEBUG && console.timeEnd('Creating context')
+
+ env.DEBUG && console.time('Resolving content paths')
+ this.refreshContentPaths()
+ env.DEBUG && console.timeEnd('Resolving content paths')
+
+ if (this.watcher) {
+ env.DEBUG && console.time('Watch new files')
+ this.watcher.refreshWatchedFiles()
+ env.DEBUG && console.timeEnd('Watch new files')
+ }
+
+ for (let file of this.readContentPaths()) {
+ this.context.changedContent.push(file)
+ }
+
+ return this.context
+ },
+}
+
+export async function createProcessor(args, cliConfigPath) {
+ let postcss = loadPostcss()
+
+ let input = args['--input']
+ let output = args['--output']
+ let includePostCss = args['--postcss']
+ let customPostCssPath = typeof args['--postcss'] === 'string' ? args['--postcss'] : undefined
+
+ let [beforePlugins, afterPlugins, postcssOptions] = includePostCss
+ ? await loadPostCssPlugins(customPostCssPath)
+ : loadBuiltinPostcssPlugins()
+
+ if (args['--purge']) {
+ log.warn('purge-flag-deprecated', [
+ 'The `--purge` flag has been deprecated.',
+ 'Please use `--content` instead.',
+ ])
+
+ if (!args['--content']) {
+ args['--content'] = args['--purge']
+ }
+ }
+
+ let content = args['--content']?.split(/(?<!{[^}]+),/) ?? []
+
+ let tailwindPlugin = () => {
+ return {
+ postcssPlugin: 'tailwindcss',
+ async Once(root, { result }) {
+ env.DEBUG && console.time('Compiling CSS')
+ await tailwind(({ createContext }) => {
+ console.error()
+ console.error('Rebuilding...')
+
+ return () => {
+ return state.getContext({
+ createContext,
+ cliConfigPath,
+ root,
+ result,
+ content,
+ })
+ }
+ })(root, result)
+ env.DEBUG && console.timeEnd('Compiling CSS')
+ },
+ }
+ }
+
+ tailwindPlugin.postcss = true
+
+ let plugins = [
+ ...beforePlugins,
+ tailwindPlugin,
+ !args['--minify'] && formatNodes,
+ ...afterPlugins,
+ !args['--no-autoprefixer'] && loadAutoprefixer(),
+ args['--minify'] && loadCssNano(),
+ ].filter(Boolean)
+
+ /** @type {import('postcss').Processor} */
+ // @ts-ignore
+ let processor = postcss(plugins)
+
+ async function readInput() {
+ // Piping in data, let's drain the stdin
+ if (input === '-') {
+ return drainStdin()
+ }
+
+ // Input file has been provided
+ if (input) {
+ return fs.promises.readFile(path.resolve(input), 'utf8')
+ }
+
+ // No input file provided, fallback to default atrules
+ return '@tailwind base; @tailwind components; @tailwind utilities'
+ }
+
+ async function build() {
+ let start = process.hrtime.bigint()
+
+ return readInput()
+ .then((css) => processor.process(css, { ...postcssOptions, from: input, to: output }))
+ .then((result) => {
+ if (!state.watcher) {
+ return result
+ }
+
+ env.DEBUG && console.time('Recording PostCSS dependencies')
+ for (let message of result.messages) {
+ if (message.type === 'dependency') {
+ state.contextDependencies.add(message.file)
+ }
+ }
+ env.DEBUG && console.timeEnd('Recording PostCSS dependencies')
+
+ // TODO: This needs to be in a different spot
+ env.DEBUG && console.time('Watch new files')
+ state.watcher.refreshWatchedFiles()
+ env.DEBUG && console.timeEnd('Watch new files')
+
+ return result
+ })
+ .then((result) => {
+ if (!output) {
+ process.stdout.write(result.css)
+ return
+ }
+
+ return Promise.all([
+ outputFile(result.opts.to, result.css),
+ result.map && outputFile(result.opts.to + '.map', result.map.toString()),
+ ])
+ })
+ .then(() => {
+ let end = process.hrtime.bigint()
+ console.error()
+ console.error('Done in', (end - start) / BigInt(1e6) + 'ms.')
+ })
+ .then(
+ () => {},
+ (err) => {
+ // TODO: If an initial build fails we can't easily pick up any PostCSS dependencies
+ // that were collected before the error occurred
+ // The result is not stored on the error so we have to store it externally
+ // and pull the messages off of it here somehow
+
+ // This results in a less than ideal DX because the watcher will not pick up
+ // changes to imported CSS if one of them caused an error during the initial build
+ // If you fix it and then save the main CSS file so there's no error
+ // The watcher will start watching the imported CSS files and will be
+ // resilient to future errors.
+
+ if (state.watcher) {
+ console.error(err)
+ } else {
+ return Promise.reject(err)
+ }
+ }
+ )
+ }
+
+ /**
+ * @param {{file: string, content(): Promise<string>, extension: string}[]} changes
+ */
+ async function parseChanges(changes) {
+ return Promise.all(
+ changes.map(async (change) => ({
+ content: await change.content(),
+ extension: change.extension,
+ }))
+ )
+ }
+
+ if (input !== undefined && input !== '-') {
+ state.contextDependencies.add(path.resolve(input))
+ }
+
+ return {
+ build,
+ watch: async () => {
+ state.watcher = createWatcher(args, {
+ state,
+
+ /**
+ * @param {{file: string, content(): Promise<string>, extension: string}[]} changes
+ */
+ async rebuild(changes) {
+ let needsNewContext = changes.some((change) => {
+ return (
+ state.configBag?.dependencies.has(change.file) ||
+ state.contextDependencies.has(change.file)
+ )
+ })
+
+ if (needsNewContext) {
+ state.context = null
+ } else {
+ for (let change of await parseChanges(changes)) {
+ state.changedContent.push(change)
+ }
+ }
+
+ return build()
+ },
+ })
+
+ await build()
+ },
+ }
+}
diff --git a/node_modules/tailwindcss/src/cli/build/utils.js b/node_modules/tailwindcss/src/cli/build/utils.js
new file mode 100644
index 0000000..3462a97
--- /dev/null
+++ b/node_modules/tailwindcss/src/cli/build/utils.js
@@ -0,0 +1,76 @@
+// @ts-check
+
+import fs from 'fs'
+import path from 'path'
+
+export function indentRecursive(node, indent = 0) {
+ node.each &&
+ node.each((child, i) => {
+ if (!child.raws.before || !child.raws.before.trim() || child.raws.before.includes('\n')) {
+ child.raws.before = `\n${node.type !== 'rule' && i > 0 ? '\n' : ''}${' '.repeat(indent)}`
+ }
+ child.raws.after = `\n${' '.repeat(indent)}`
+ indentRecursive(child, indent + 1)
+ })
+}
+
+export function formatNodes(root) {
+ indentRecursive(root)
+ if (root.first) {
+ root.first.raws.before = ''
+ }
+}
+
+/**
+ * When rapidly saving files atomically a couple of situations can happen:
+ * - The file is missing since the external program has deleted it by the time we've gotten around to reading it from the earlier save.
+ * - The file is being written to by the external program by the time we're going to read it and is thus treated as busy because a lock is held.
+ *
+ * To work around this we retry reading the file a handful of times with a delay between each attempt
+ *
+ * @param {string} path
+ * @param {number} tries
+ * @returns {Promise<string | undefined>}
+ * @throws {Error} If the file is still missing or busy after the specified number of tries
+ */
+export async function readFileWithRetries(path, tries = 5) {
+ for (let n = 0; n <= tries; n++) {
+ try {
+ return await fs.promises.readFile(path, 'utf8')
+ } catch (err) {
+ if (n !== tries) {
+ if (err.code === 'ENOENT' || err.code === 'EBUSY') {
+ await new Promise((resolve) => setTimeout(resolve, 10))
+
+ continue
+ }
+ }
+
+ throw err
+ }
+ }
+}
+
+export function drainStdin() {
+ return new Promise((resolve, reject) => {
+ let result = ''
+ process.stdin.on('data', (chunk) => {
+ result += chunk
+ })
+ process.stdin.on('end', () => resolve(result))
+ process.stdin.on('error', (err) => reject(err))
+ })
+}
+
+export async function outputFile(file, newContents) {
+ try {
+ let currentContents = await fs.promises.readFile(file, 'utf8')
+ if (currentContents === newContents) {
+ return // Skip writing the file
+ }
+ } catch {}
+
+ // Write the file
+ await fs.promises.mkdir(path.dirname(file), { recursive: true })
+ await fs.promises.writeFile(file, newContents, 'utf8')
+}
diff --git a/node_modules/tailwindcss/src/cli/build/watching.js b/node_modules/tailwindcss/src/cli/build/watching.js
new file mode 100644
index 0000000..b778872
--- /dev/null
+++ b/node_modules/tailwindcss/src/cli/build/watching.js
@@ -0,0 +1,229 @@
+// @ts-check
+
+import chokidar from 'chokidar'
+import fs from 'fs'
+import micromatch from 'micromatch'
+import normalizePath from 'normalize-path'
+import path from 'path'
+
+import { readFileWithRetries } from './utils.js'
+
+/**
+ * The core idea of this watcher is:
+ * 1. Whenever a file is added, changed, or renamed we queue a rebuild
+ * 2. Perform as few rebuilds as possible by batching them together
+ * 3. Coalesce events that happen in quick succession to avoid unnecessary rebuilds
+ * 4. Ensure another rebuild happens _if_ changed while a rebuild is in progress
+ */
+
+/**
+ *
+ * @param {*} args
+ * @param {{ state, rebuild(changedFiles: any[]): Promise<any> }} param1
+ * @returns {{
+ * fswatcher: import('chokidar').FSWatcher,
+ * refreshWatchedFiles(): void,
+ * }}
+ */
+export function createWatcher(args, { state, rebuild }) {
+ let shouldPoll = args['--poll']
+ let shouldCoalesceWriteEvents = shouldPoll || process.platform === 'win32'
+
+ // Polling interval in milliseconds
+ // Used only when polling or coalescing add/change events on Windows
+ let pollInterval = 10
+
+ let watcher = chokidar.watch([], {
+ // Force checking for atomic writes in all situations
+ // This causes chokidar to wait up to 100ms for a file to re-added after it's been unlinked
+ // This only works when watching directories though
+ atomic: true,
+
+ usePolling: shouldPoll,
+ interval: shouldPoll ? pollInterval : undefined,
+ ignoreInitial: true,
+ awaitWriteFinish: shouldCoalesceWriteEvents
+ ? {
+ stabilityThreshold: 50,
+ pollInterval: pollInterval,
+ }
+ : false,
+ })
+
+ // A queue of rebuilds, file reads, etc… to run
+ let chain = Promise.resolve()
+
+ /**
+ * A list of files that have been changed since the last rebuild
+ *
+ * @type {{file: string, content: () => Promise<string>, extension: string}[]}
+ */
+ let changedContent = []
+
+ /**
+ * A list of files for which a rebuild has already been queued.
+ * This is used to prevent duplicate rebuilds when multiple events are fired for the same file.
+ * The rebuilt file is cleared from this list when it's associated rebuild has _started_
+ * This is because if the file is changed during a rebuild it won't trigger a new rebuild which it should
+ **/
+ let pendingRebuilds = new Set()
+
+ let _timer
+ let _reject
+
+ /**
+ * Rebuilds the changed files and resolves when the rebuild is
+ * complete regardless of whether it was successful or not
+ */
+ async function rebuildAndContinue() {
+ let changes = changedContent.splice(0)
+
+ // There are no changes to rebuild so we can just do nothing
+ if (changes.length === 0) {
+ return Promise.resolve()
+ }
+
+ // Clear all pending rebuilds for the about-to-be-built files
+ changes.forEach((change) => pendingRebuilds.delete(change.file))
+
+ // Resolve the promise even when the rebuild fails
+ return rebuild(changes).then(
+ () => {},
+ (e) => {
+ console.error(e.toString())
+ }
+ )
+ }
+
+ /**
+ *
+ * @param {*} file
+ * @param {(() => Promise<string>) | null} content
+ * @param {boolean} skipPendingCheck
+ * @returns {Promise<void>}
+ */
+ function recordChangedFile(file, content = null, skipPendingCheck = false) {
+ file = path.resolve(file)
+
+ // Applications like Vim/Neovim fire both rename and change events in succession for atomic writes
+ // In that case rebuild has already been queued by rename, so can be skipped in change
+ if (pendingRebuilds.has(file) && !skipPendingCheck) {
+ return Promise.resolve()
+ }
+
+ // Mark that a rebuild of this file is going to happen
+ // It MUST happen synchronously before the rebuild is queued for this to be effective
+ pendingRebuilds.add(file)
+
+ changedContent.push({
+ file,
+ content: content ?? (() => fs.promises.readFile(file, 'utf8')),
+ extension: path.extname(file).slice(1),
+ })
+
+ if (_timer) {
+ clearTimeout(_timer)
+ _reject()
+ }
+
+ // If a rebuild is already in progress we don't want to start another one until the 10ms timer has expired
+ chain = chain.then(
+ () =>
+ new Promise((resolve, reject) => {
+ _timer = setTimeout(resolve, 10)
+ _reject = reject
+ })
+ )
+
+ // Resolves once this file has been rebuilt (or the rebuild for this file has failed)
+ // This queues as many rebuilds as there are changed files
+ // But those rebuilds happen after some delay
+ // And will immediately resolve if there are no changes
+ chain = chain.then(rebuildAndContinue, rebuildAndContinue)
+
+ return chain
+ }
+
+ watcher.on('change', (file) => recordChangedFile(file))
+ watcher.on('add', (file) => recordChangedFile(file))
+
+ // Restore watching any files that are "removed"
+ // This can happen when a file is pseudo-atomically replaced (a copy is created, overwritten, the old one is unlinked, and the new one is renamed)
+ // TODO: An an optimization we should allow removal when the config changes
+ watcher.on('unlink', (file) => {
+ file = normalizePath(file)
+
+ // Only re-add the file if it's not covered by a dynamic pattern
+ if (!micromatch.some([file], state.contentPatterns.dynamic)) {
+ watcher.add(file)
+ }
+ })
+
+ // Some applications such as Visual Studio (but not VS Code)
+ // will only fire a rename event for atomic writes and not a change event
+ // This is very likely a chokidar bug but it's one we need to work around
+ // We treat this as a change event and rebuild the CSS
+ watcher.on('raw', (evt, filePath, meta) => {
+ if (evt !== 'rename' || filePath === null) {
+ return
+ }
+
+ let watchedPath = meta.watchedPath
+
+ // Watched path might be the file itself
+ // Or the directory it is in
+ filePath = watchedPath.endsWith(filePath) ? watchedPath : path.join(watchedPath, filePath)
+
+ // Skip this event since the files it is for does not match any of the registered content globs
+ if (!micromatch.some([filePath], state.contentPatterns.all)) {
+ return
+ }
+
+ // Skip since we've already queued a rebuild for this file that hasn't happened yet
+ if (pendingRebuilds.has(filePath)) {
+ return
+ }
+
+ // We'll go ahead and add the file to the pending rebuilds list here
+ // It'll be removed when the rebuild starts unless the read fails
+ // which will be taken care of as well
+ pendingRebuilds.add(filePath)
+
+ async function enqueue() {
+ try {
+ // We need to read the file as early as possible outside of the chain
+ // because it may be gone by the time we get to it. doing the read
+ // immediately increases the chance that the file is still there
+ let content = await readFileWithRetries(path.resolve(filePath))
+
+ if (content === undefined) {
+ return
+ }
+
+ // This will push the rebuild onto the chain
+ // We MUST skip the rebuild check here otherwise the rebuild will never happen on Linux
+ // This is because the order of events and timing is different on Linux
+ // @ts-ignore: TypeScript isn't picking up that content is a string here
+ await recordChangedFile(filePath, () => content, true)
+ } catch {
+ // If reading the file fails, it's was probably a deleted temporary file
+ // So we can ignore it and no rebuild is needed
+ }
+ }
+
+ enqueue().then(() => {
+ // If the file read fails we still need to make sure the file isn't stuck in the pending rebuilds list
+ pendingRebuilds.delete(filePath)
+ })
+ })
+
+ return {
+ fswatcher: watcher,
+
+ refreshWatchedFiles() {
+ watcher.add(Array.from(state.contextDependencies))
+ watcher.add(Array.from(state.configBag.dependencies))
+ watcher.add(state.contentPatterns.all)
+ },
+ }
+}
diff --git a/node_modules/tailwindcss/src/cli/help/index.js b/node_modules/tailwindcss/src/cli/help/index.js
new file mode 100644
index 0000000..ea4137a
--- /dev/null
+++ b/node_modules/tailwindcss/src/cli/help/index.js
@@ -0,0 +1,70 @@
+// @ts-check
+import packageJson from '../../../package.json'
+
+export function help({ message, usage, commands, options }) {
+ let indent = 2
+
+ // Render header
+ console.log()
+ console.log(`${packageJson.name} v${packageJson.version}`)
+
+ // Render message
+ if (message) {
+ console.log()
+ for (let msg of message.split('\n')) {
+ console.log(msg)
+ }
+ }
+
+ // Render usage
+ if (usage && usage.length > 0) {
+ console.log()
+ console.log('Usage:')
+ for (let example of usage) {
+ console.log(' '.repeat(indent), example)
+ }
+ }
+
+ // Render commands
+ if (commands && commands.length > 0) {
+ console.log()
+ console.log('Commands:')
+ for (let command of commands) {
+ console.log(' '.repeat(indent), command)
+ }
+ }
+
+ // Render options
+ if (options) {
+ let groupedOptions = {}
+ for (let [key, value] of Object.entries(options)) {
+ if (typeof value === 'object') {
+ groupedOptions[key] = { ...value, flags: [key] }
+ } else {
+ groupedOptions[value].flags.push(key)
+ }
+ }
+
+ console.log()
+ console.log('Options:')
+ for (let { flags, description, deprecated } of Object.values(groupedOptions)) {
+ if (deprecated) continue
+
+ if (flags.length === 1) {
+ console.log(
+ ' '.repeat(indent + 4 /* 4 = "-i, ".length */),
+ flags.slice().reverse().join(', ').padEnd(20, ' '),
+ description
+ )
+ } else {
+ console.log(
+ ' '.repeat(indent),
+ flags.slice().reverse().join(', ').padEnd(24, ' '),
+ description
+ )
+ }
+ }
+ }
+
+ console.log()
+}
diff --git a/node_modules/tailwindcss/src/cli/index.js b/node_modules/tailwindcss/src/cli/index.js
new file mode 100644
index 0000000..fc1497f
--- /dev/null
+++ b/node_modules/tailwindcss/src/cli/index.js
@@ -0,0 +1,216 @@
+#!/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)
diff --git a/node_modules/tailwindcss/src/cli/init/index.js b/node_modules/tailwindcss/src/cli/init/index.js
new file mode 100644
index 0000000..6bd7e41
--- /dev/null
+++ b/node_modules/tailwindcss/src/cli/init/index.js
@@ -0,0 +1,79 @@
+// @ts-check
+
+import fs from 'fs'
+import path from 'path'
+
+function isESM() {
+ const pkgPath = path.resolve('./package.json')
+
+ try {
+ let pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
+ return pkg.type && pkg.type === 'module'
+ } catch (err) {
+ return false
+ }
+}
+
+export function init(args) {
+ let messages = []
+
+ let isProjectESM = args['--ts'] || args['--esm'] || isESM()
+ let syntax = args['--ts'] ? 'ts' : isProjectESM ? 'js' : 'cjs'
+ let extension = args['--ts'] ? 'ts' : 'js'
+
+ let tailwindConfigLocation = path.resolve(args['_'][1] ?? `./tailwind.config.${extension}`)
+
+ if (fs.existsSync(tailwindConfigLocation)) {
+ messages.push(`${path.basename(tailwindConfigLocation)} already exists.`)
+ } else {
+ let stubContentsFile = fs.readFileSync(
+ args['--full']
+ ? path.resolve(__dirname, '../../../stubs/config.full.js')
+ : path.resolve(__dirname, '../../../stubs/config.simple.js'),
+ 'utf8'
+ )
+
+ let stubFile = fs.readFileSync(
+ path.resolve(__dirname, `../../../stubs/tailwind.config.${syntax}`),
+ 'utf8'
+ )
+
+ // Change colors import
+ stubContentsFile = stubContentsFile.replace('../colors', 'tailwindcss/colors')
+
+ // Replace contents of {ts,js,cjs} file with the stub {simple,full}.
+ stubFile =
+ stubFile
+ .replace('__CONFIG__', stubContentsFile.replace('module.exports =', '').trim())
+ .trim() + '\n\n'
+
+ fs.writeFileSync(tailwindConfigLocation, stubFile, 'utf8')
+
+ messages.push(`Created Tailwind CSS config file: ${path.basename(tailwindConfigLocation)}`)
+ }
+
+ if (args['--postcss']) {
+ let postcssConfigLocation = path.resolve('./postcss.config.js')
+ if (fs.existsSync(postcssConfigLocation)) {
+ messages.push(`${path.basename(postcssConfigLocation)} already exists.`)
+ } else {
+ let stubFile = fs.readFileSync(
+ isProjectESM
+ ? path.resolve(__dirname, '../../../stubs/postcss.config.js')
+ : path.resolve(__dirname, '../../../stubs/postcss.config.cjs'),
+ 'utf8'
+ )
+
+ fs.writeFileSync(postcssConfigLocation, stubFile, 'utf8')
+
+ messages.push(`Created PostCSS config file: ${path.basename(postcssConfigLocation)}`)
+ }
+ }
+
+ if (messages.length > 0) {
+ console.log()
+ for (let message of messages) {
+ console.log(message)
+ }
+ }
+}