diff --git a/CHANGELOG.md b/CHANGELOG.md index f87ef7da6dcb..1645f7212e9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Resolve values in functional utilities based on `@theme` options ([#15623](https://github.com/tailwindlabs/tailwindcss/pull/15623)) - Discard invalid variants such as `data-checked-[selected=1]:*` ([#15629](https://github.com/tailwindlabs/tailwindcss/pull/15629)) - Ensure `-outline-offset-*` utilities are suggested in IntelliSense ([#15646](https://github.com/tailwindlabs/tailwindcss/pull/15646)) +- Write to `stdout` when `--output` is set to `-` or omitted for `@tailwindcss/cli` ([#15656](https://github.com/tailwindlabs/tailwindcss/pull/15656)) +- Write to `stdout` when `--output -` flag is used for `@tailwindcss/cli` ([#15656](https://github.com/tailwindlabs/tailwindcss/pull/15656)) - _Upgrade (experimental)_: Pretty print `--spacing(…)` to prevent ambiguity ([#15596](https://github.com/tailwindlabs/tailwindcss/pull/15596)) ## [4.0.0-beta.9] - 2025-01-09 diff --git a/integrations/cli/index.test.ts b/integrations/cli/index.test.ts index 324492adb546..aff5b108994f 100644 --- a/integrations/cli/index.test.ts +++ b/integrations/cli/index.test.ts @@ -95,6 +95,149 @@ describe.each([ }, ) + test( + 'production build — read input from stdin', + { + fs: { + 'package.json': json`{}`, + 'pnpm-workspace.yaml': yaml` + # + packages: + - project-a + `, + 'project-a/package.json': json` + { + "dependencies": { + "tailwindcss": "workspace:^", + "@tailwindcss/cli": "workspace:^" + } + } + `, + 'project-a/index.html': html` +
+ `, + 'project-a/plugin.js': js` + module.exports = function ({ addVariant }) { + addVariant('inverted', '@media (inverted-colors: inverted)') + addVariant('hocus', ['&:focus', '&:hover']) + } + `, + 'project-a/tailwind.config.js': js` + module.exports = { + content: ['../project-b/src/**/*.js'], + } + `, + 'project-a/src/index.js': js` + const className = "content-['project-a/src/index.js']" + module.exports = { className } + `, + 'project-b/src/index.html': html` +
+ `, + 'project-b/src/index.js': js` + const className = "content-['project-b/src/index.js']" + module.exports = { className } + `, + }, + }, + async ({ root, fs, exec }) => { + await exec( + `${command} --input - --output dist/out.css`, + { cwd: path.join(root, 'project-a') }, + { + stdin: css` + @import 'tailwindcss/utilities'; + @config './tailwind.config.js'; + @source '../project-b/src/**/*.html'; + @plugin './plugin.js'; + `, + }, + ) + + await fs.expectFileToContain('project-a/dist/out.css', [ + candidate`underline`, + candidate`flex`, + candidate`content-['project-a/src/index.js']`, + candidate`content-['project-b/src/index.js']`, + candidate`inverted:flex`, + candidate`hocus:underline`, + candidate`*:flex`, + candidate`**:flex`, + ]) + }, + ) + + test( + 'production build — (write to stdout)', + { + fs: { + 'package.json': json`{}`, + 'pnpm-workspace.yaml': yaml` + # + packages: + - project-a + `, + 'project-a/package.json': json` + { + "dependencies": { + "tailwindcss": "workspace:^", + "@tailwindcss/cli": "workspace:^" + } + } + `, + 'project-a/index.html': html` +
+ `, + 'project-a/plugin.js': js` + module.exports = function ({ addVariant }) { + addVariant('inverted', '@media (inverted-colors: inverted)') + addVariant('hocus', ['&:focus', '&:hover']) + } + `, + 'project-a/tailwind.config.js': js` + module.exports = { + content: ['../project-b/src/**/*.js'], + } + `, + 'project-a/src/index.css': css` + @import 'tailwindcss/utilities'; + @config '../tailwind.config.js'; + @source '../../project-b/src/**/*.html'; + @plugin '../plugin.js'; + `, + 'project-a/src/index.js': js` + const className = "content-['project-a/src/index.js']" + module.exports = { className } + `, + 'project-b/src/index.html': html` +
+ `, + 'project-b/src/index.js': js` + const className = "content-['project-b/src/index.js']" + module.exports = { className } + `, + }, + }, + async ({ root, expect, exec }) => { + let stdout = await exec(`${command} --input src/index.css --output -`, { + cwd: path.join(root, 'project-a'), + }) + + expect(stdout).toContain(candidate`underline`) + expect(stdout).toContain(candidate`flex`) + expect(stdout).toContain(candidate`content-['project-a/src/index.js']`) + expect(stdout).toContain(candidate`content-['project-b/src/index.js']`) + expect(stdout).toContain(candidate`inverted:flex`) + expect(stdout).toContain(candidate`hocus:underline`) + expect(stdout).toContain(candidate`*:flex`) + expect(stdout).toContain(candidate`**:flex`) + }, + ) + test( 'watch mode', { diff --git a/integrations/utils.ts b/integrations/utils.ts index 8f916a3d042f..4644ece49ab8 100644 --- a/integrations/utils.ts +++ b/integrations/utils.ts @@ -25,6 +25,7 @@ interface ChildProcessOptions { interface ExecOptions { ignoreStdErr?: boolean + stdin?: string } interface TestConfig { @@ -112,7 +113,7 @@ export function test( } if (debug) console.log(`> ${command}`) return new Promise((resolve, reject) => { - exec( + let child = exec( command, { cwd, @@ -134,6 +135,10 @@ export function test( } }, ) + if (execOptions.stdin) { + child.stdin?.write(execOptions.stdin) + child.stdin?.end() + } }) }, async spawn(command: string, childProcessOptions: ChildProcessOptions = {}) { diff --git a/packages/@tailwindcss-cli/src/commands/build/index.ts b/packages/@tailwindcss-cli/src/commands/build/index.ts index 67c959cbf6cf..6991e7bf0f16 100644 --- a/packages/@tailwindcss-cli/src/commands/build/index.ts +++ b/packages/@tailwindcss-cli/src/commands/build/index.ts @@ -32,6 +32,7 @@ export function options() { type: 'string', description: 'Output file', alias: '-o', + default: '-', }, '--watch': { type: 'boolean | string', @@ -72,8 +73,10 @@ export async function handle(args: Result>) { let base = path.resolve(args['--cwd']) - // Resolve the output as an absolute path. - if (args['--output']) { + // Resolve the output as an absolute path. If the output is a `-`, then we + // don't need to resolve it because this is a flag to indicate that we want to + // use `stdout` instead. + if (args['--output'] && args['--output'] !== '-') { args['--output'] = path.resolve(base, args['--output']) } @@ -129,7 +132,7 @@ export async function handle(args: Result>) { // Write the output DEBUG && I.start('Write output') - if (args['--output']) { + if (args['--output'] && args['--output'] !== '-') { await outputFile(args['--output'], output) } else { println(output) diff --git a/packages/@tailwindcss-cli/src/index.ts b/packages/@tailwindcss-cli/src/index.ts index 511781759079..41807f174b94 100644 --- a/packages/@tailwindcss-cli/src/index.ts +++ b/packages/@tailwindcss-cli/src/index.ts @@ -36,7 +36,7 @@ if (command) { // // - `tailwindcss -o output.css` // should run the build command, not show the help message // - `tailwindcss > output.css` // should run the build command, not show the help message -if ((process.stdout.isTTY && !flags['--output']) || flags['--help']) { +if ((process.stdout.isTTY && process.argv[2] === undefined) || flags['--help']) { help({ usage: ['tailwindcss [--input input.css] [--output output.css] [--watch] [options…]'], options: { ...build.options(), ...sharedOptions }, diff --git a/packages/@tailwindcss-cli/src/utils/args.ts b/packages/@tailwindcss-cli/src/utils/args.ts index 81bd847d50d1..1e4e153b8a03 100644 --- a/packages/@tailwindcss-cli/src/utils/args.ts +++ b/packages/@tailwindcss-cli/src/utils/args.ts @@ -67,8 +67,20 @@ export type Result = { } export function args(options: T, argv = process.argv.slice(2)): Result { + for (let [idx, value] of argv.entries()) { + if (value === '-') { + argv[idx] = '__IO_DEFAULT_VALUE__' + } + } + let parsed = parse(argv) + for (let key in parsed) { + if (parsed[key] === '__IO_DEFAULT_VALUE__') { + parsed[key] = '-' + } + } + let result: { _: string[]; [key: string]: unknown } = { _: parsed._, }