Prevent crash when using @tailwindcss/cli using --watch on Windows (#20242)
This PR fixes an issue on Windows where the `@tailwindcss/cli` with the `--watch` flag crashes when using a `@source` with a base path that doesn't exist on disk. This happens when setting up the `@parce/watcher` for directories that don't exist. This PR essentially filters out these directories that don't exist on disk to prevent the crash. It might be that if you add the folder _later_, while the watcher is already watching, that you have to restart the `@tailwindcss/cli` (or save the `index.css` (the file that contains the `@source` directives), this also recreates watchers from scratch). If this issue causes problems for `@tailwindcss/postcss` and `@tailwindcss/vite` in the future as well, then we can move this logic back to Oxide. We do maintain the incoming `@source` files as best as possible without resolving to absolute paths. Back when we did resolve them, if that process error'd we just never returned the glob. The root cause is still referencing folders that don't exist. Fixes: #20231 ## Test plan 1. Added a failing test, that did fail on Windows 2. Once fixed, all tests should pass [ci-all] --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
This commit is contained in:
parent
d1f9f53bf9
commit
abc4670a9a
3 changed files with 50 additions and 5 deletions
|
|
@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
- Nothing yet!
|
||||
### Fixed
|
||||
|
||||
- Ensure `@tailwindcss/cli` in `--watch` mode doesn't crash on Windows when `@source` points to a directory that doesn't exist ([#20242](https://github.com/tailwindlabs/tailwindcss/pull/20242))
|
||||
|
||||
## [4.3.1] - 2026-06-12
|
||||
|
||||
|
|
|
|||
|
|
@ -375,6 +375,35 @@ describe.each([
|
|||
},
|
||||
)
|
||||
|
||||
test(
|
||||
"watch mode with unknown @source paths shouldn't crash on Windows",
|
||||
{
|
||||
fs: {
|
||||
'package.json': json`
|
||||
{
|
||||
"dependencies": {
|
||||
"tailwindcss": "workspace:^",
|
||||
"@tailwindcss/cli": "workspace:^"
|
||||
}
|
||||
}
|
||||
`,
|
||||
'index.html': html`
|
||||
<div class="underline"></div>
|
||||
`,
|
||||
'src/index.css': css`
|
||||
@import 'tailwindcss';
|
||||
@source "unknown-folder/**/*";
|
||||
`,
|
||||
},
|
||||
},
|
||||
async ({ fs, spawn }) => {
|
||||
let process = await spawn(`${command} --input src/index.css --output dist/out.css --watch`)
|
||||
await process.onStderr((m) => m.includes('Done in'))
|
||||
|
||||
await fs.expectFileToContain('dist/out.css', [candidate`underline`])
|
||||
},
|
||||
)
|
||||
|
||||
test(
|
||||
'production build (stdin)',
|
||||
{
|
||||
|
|
|
|||
|
|
@ -272,7 +272,7 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
|
|||
if (args['--watch']) {
|
||||
let cleanupWatchers: (() => Promise<void>)[] = []
|
||||
cleanupWatchers.push(
|
||||
await createWatchers(watchDirectories(scanner), async function handle(files) {
|
||||
await createWatchers(await watchDirectories(scanner), async function handle(files) {
|
||||
try {
|
||||
// If the only change happened to the output file, then we don't want to
|
||||
// trigger a rebuild because that will result in an infinite loop.
|
||||
|
|
@ -347,7 +347,7 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
|
|||
|
||||
// Setup new watchers
|
||||
DEBUG && I.start('Setup new watchers')
|
||||
let newCleanupFunction = await createWatchers(watchDirectories(scanner), handle)
|
||||
let newCleanupFunction = await createWatchers(await watchDirectories(scanner), handle)
|
||||
DEBUG && I.end('Setup new watchers')
|
||||
|
||||
// Clear old watchers
|
||||
|
|
@ -586,8 +586,22 @@ async function createWatchers(dirs: string[], cb: (files: string[]) => void) {
|
|||
}
|
||||
}
|
||||
|
||||
function watchDirectories(scanner: Scanner) {
|
||||
return [...new Set(scanner.normalizedSources.flatMap((globEntry) => globEntry.base))]
|
||||
async function watchDirectories(scanner: Scanner) {
|
||||
let directories = (
|
||||
await Promise.all(
|
||||
scanner.normalizedSources.map(async (globEntry) => {
|
||||
let resolvedPath = path.resolve(globEntry.base)
|
||||
let realPath = await fs.realpath(resolvedPath).catch(() => resolvedPath)
|
||||
|
||||
return fs
|
||||
.stat(realPath)
|
||||
.then((stat) => (stat.isDirectory() ? [realPath] : []))
|
||||
.catch(() => [])
|
||||
}),
|
||||
)
|
||||
).flat(1)
|
||||
|
||||
return Array.from(new Set(directories))
|
||||
}
|
||||
|
||||
function dim(str: string) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue