diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f3b0b97718c..682b66628eb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Ensure validation of `source(…)` happens relative to the file it is in ([#19274](https://github.com/tailwindlabs/tailwindcss/pull/19274)) + ### Added - _Experimental_: Add `@container-size` utility ([#18901](https://github.com/tailwindlabs/tailwindcss/pull/18901)) diff --git a/integrations/cli/index.test.ts b/integrations/cli/index.test.ts index be48a2ac4898..9b666f4ec313 100644 --- a/integrations/cli/index.test.ts +++ b/integrations/cli/index.test.ts @@ -1332,6 +1332,65 @@ test( }, ) +test( + 'source(…) and `@source` are relative to the file they are in', + { + fs: { + 'package.json': json` + { + "dependencies": { + "tailwindcss": "workspace:^", + "@tailwindcss/cli": "workspace:^" + } + } + `, + 'index.css': css` @import './project-a/src/index.css'; `, + + 'project-a/src/index.css': css` + /* Run auto-content detection in ../../project-b */ + @import 'tailwindcss/utilities' source('../../project-b'); + + /* Explicitly using node_modules in the @source allows git ignored folders */ + @source '../../project-c'; + `, + + // Project A is the current folder, but we explicitly configured + // `source(project-b)`, therefore project-a should not be included in + // the output. + 'project-a/src/index.html': html` +
+ `, + + // Project B is the configured `source(…)`, therefore auto source + // detection should include known extensions and folders in the output. + 'project-b/src/index.html': html` + + `, + + // Project C should apply auto source detection, therefore known + // extensions and folders should be included in the output. + 'project-c/src/index.html': html` + + `, + }, + }, + async ({ fs, exec, spawn, root, expect }) => { + await exec('pnpm tailwindcss --input ./index.css --output dist/out.css', { cwd: root }) + + let content = await fs.dumpFiles('./dist/*.css') + + expect(content).not.toContain(candidate`content-['project-a/src/index.html']`) + expect(content).toContain(candidate`content-['project-b/src/index.html']`) + expect(content).toContain(candidate`content-['project-c/src/index.html']`) + }, +) + test( 'auto source detection disabled', { diff --git a/integrations/postcss/index.test.ts b/integrations/postcss/index.test.ts index dd07fd90ebbd..08ea852f950c 100644 --- a/integrations/postcss/index.test.ts +++ b/integrations/postcss/index.test.ts @@ -825,3 +825,73 @@ test( }) }, ) + +test( + 'source(…) and `@source` are relative to the file they are in', + { + fs: { + 'package.json': json` + { + "dependencies": { + "postcss": "^8", + "postcss-cli": "^10", + "tailwindcss": "workspace:^", + "@tailwindcss/postcss": "workspace:^" + } + } + `, + + 'postcss.config.js': js` + module.exports = { + plugins: { + '@tailwindcss/postcss': {}, + }, + } + `, + + 'index.css': css` @import './project-a/src/index.css'; `, + + 'project-a/src/index.css': css` + /* Run auto-content detection in ../../project-b */ + @import 'tailwindcss/utilities' source('../../project-b'); + + /* Explicitly using node_modules in the @source allows git ignored folders */ + @source '../../project-c'; + `, + + // Project A is the current folder, but we explicitly configured + // `source(project-b)`, therefore project-a should not be included in + // the output. + 'project-a/src/index.html': html` + + `, + + // Project B is the configured `source(…)`, therefore auto source + // detection should include known extensions and folders in the output. + 'project-b/src/index.html': html` + + `, + + // Project C should apply auto source detection, therefore known + // extensions and folders should be included in the output. + 'project-c/src/index.html': html` + + `, + }, + }, + async ({ fs, exec, root, expect }) => { + await exec('pnpm postcss ./index.css --output dist/out.css', { cwd: root }) + + let content = await fs.dumpFiles('./dist/*.css') + + expect(content).not.toContain(candidate`content-['project-a/src/index.html']`) + expect(content).toContain(candidate`content-['project-b/src/index.html']`) + expect(content).toContain(candidate`content-['project-c/src/index.html']`) + }, +) diff --git a/integrations/vite/index.test.ts b/integrations/vite/index.test.ts index ead71b47296f..91570a6fcc2d 100644 --- a/integrations/vite/index.test.ts +++ b/integrations/vite/index.test.ts @@ -730,6 +730,86 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { expect(files).toHaveLength(0) }, ) + + test( + 'source(…) and `@source` are relative to the file they are in', + { + fs: { + 'package.json': json` + { + "type": "module", + "dependencies": { + "@tailwindcss/vite": "workspace:^", + "tailwindcss": "workspace:^" + }, + "devDependencies": { + ${transformer === 'lightningcss' ? `"lightningcss": "^1",` : ''} + "vite": "^7" + } + } + `, + 'vite.config.ts': ts` + import tailwindcss from '@tailwindcss/vite' + import { defineConfig } from 'vite' + + export default defineConfig({ + css: ${transformer === 'postcss' ? '{}' : "{ transformer: 'lightningcss' }"}, + build: { cssMinify: false }, + plugins: [tailwindcss()], + }) + `, + 'index.html': html` + + + + + `, + 'index.css': css` @import './project-a/src/index.css'; `, + + 'project-a/src/index.css': css` + /* Run auto-content detection in ../../project-b */ + @import 'tailwindcss/utilities' source('../../project-b'); + + /* Explicitly using node_modules in the @source allows git ignored folders */ + @source '../../project-c'; + `, + + // Project A is the current folder, but we explicitly configured + // `source(project-b)`, therefore project-a should not be included in + // the output. + 'project-a/src/index.html': html` + + `, + + // Project B is the configured `source(…)`, therefore auto source + // detection should include known extensions and folders in the output. + 'project-b/src/index.html': html` + + `, + + // Project C should apply auto source detection, therefore known + // extensions and folders should be included in the output. + 'project-c/src/index.html': html` + + `, + }, + }, + async ({ fs, exec, spawn, root, expect }) => { + await exec('pnpm vite build', { cwd: root }) + + let content = await fs.dumpFiles('./dist/assets/*.css') + + expect(content).not.toContain(candidate`content-['project-a/src/index.html']`) + expect(content).toContain(candidate`content-['project-b/src/index.html']`) + expect(content).toContain(candidate`content-['project-c/src/index.html']`) + }, + ) }) test( diff --git a/packages/@tailwindcss-node/src/compile.ts b/packages/@tailwindcss-node/src/compile.ts index 5ce6299af6a2..ac7cc6f15211 100644 --- a/packages/@tailwindcss-node/src/compile.ts +++ b/packages/@tailwindcss-node/src/compile.ts @@ -63,10 +63,9 @@ function createCompileOptions({ } } -async function ensureSourceDetectionRootExists( - compiler: { root: Awaited