Windows の `--watch` モードで `@source` が指す不存在ディレクトリが原因のクラッシュを防止

tailwindlabs/tailwindcss

@tailwindcss/cli--watch フラグ使用時に、Windows 環境で存在しないディレクトリを指定した @source が原因でプロセスが異常終了していた問題を解決しました。ディレクトリ存在チェックを導入し、ウォッチャー作成段階で安全に除外するよう変更されています。

背景

Windows で @tailwindcss/cli --watch を実行し、@source "unknown-folder/**/*" のように実体のないパスを指定すると、内部のディレクトリウォッチャー生成が失敗し Invalid handle エラーでプロセスが終了していました。原因は @parce/watcher が不存在ディレクトリに対しても監視設定を行おうとしたためです。PR はこのシナリオを再現するテストを追加し、Windows での失敗を明示的に捕捉できるようにしました。

この問題は、watchDirectories が単に scanner.normalizedSourcesbase パスを列挙していたことに起因し、実際のファイルシステム上にディレクトリが存在しないことを考慮していなかった点にあります。結果として、@parce/watcher が無効なハンドルを取得し、Node のランタイムが例外をスローしていました。

技術的な変更

watchDirectories 関数が非同期化 され、各 @source のベースパスを実際に確認するロジックが追加されました。具体的には path.resolvefs.realpath(失敗時は元パス) → fs.stat を順に実行し、stat.isDirectory() が真の場合にのみディレクトリパスを結果に含めます。不存在またはファイルであるケースは空配列で置き換えられ、最終的に重複除去された配列が返されます。

@@
-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))
+}

createWatchers 呼び出し側の修正 では、非同期化された watchDirectories の結果を await で取得してから渡すよう変更しました。これにより、ウォッチャー作成前に不存在ディレクトリが除外され、クラッシュが防止されます。

@@
-      await createWatchers(watchDirectories(scanner), async function handle(files) {
+      await createWatchers(await watchDirectories(scanner), async function handle(files) {
@@
-            let newCleanupFunction = await createWatchers(watchDirectories(scanner), handle)
+            let newCleanupFunction = await createWatchers(await watchDirectories(scanner), handle)

テスト追加 により Windows 環境での再現ケースが検証されています。unknown-folder/**/* を指す @source を含む CSS をビルドし、--watch がクラッシュせずに出力ファイルが生成されることを確認しています。

設計判断

本修正は CLI 層でディレクトリの有無を判定し除外する 方針を採用しました。@tailwindcss/postcss@tailwindcss/vite といった他の統合層に影響を与えず、@source 記述自体は変更せずに済むため後方互換性が保たれます。実装は watchDirectories に非同期チェックロジックを追加し、呼び出し側で await を付与するだけの最小限の変更です。

このアプローチはプラットフォーム固有の問題(Windows のハンドルエラー)に対処しつつ、既存のコードパスを極力そのまま維持する設計といえます。不存在ディレクトリが除外されることで、@parce/watcher が安全に初期化され、他の OS への振る舞いは変更されません。

まとめ

watchDirectories にディレクトリ存在確認を組み込み、呼び出し側で非同期取得するようにしたことで、Windows の --watch モードで不存在 @source が原因でクラッシュしていた問題が解消されました。テストにより Windows 環境でも安定してウォッチが機能することが確認され、CLI の信頼性が向上しています。ディレクトリを後から追加する場合は、現状 CLI の再起動が必要であることが PR の記述に残されています。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
d1d9fb9e

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
リトライ後承認
Review Count:
2回 (改善を経て承認)
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文、背景、技術的変更、設計判断(任意)、まとめの5要素が揃っており、総論→各論→結論の流れが明確です。

カスタムMarkdown構文 ⚠ WARNING

シンタックスハイライト・GitHubリンク記法の正確性

GitHubリンク記法が `[PR #20242](URL)` となっており、推奨フォーマットの `[#20242](URL)` と若干異なります。ファイル名付きシンタックスハイライトは使用されていないため問題はありません。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

エンジニア向けの技術的内容で、初心者向けの過度な説明はありません。

パラグラフ・ライティング ⚠ WARNING

トピックセンテンス・1段落1トピック・段落長

各セクションは総論・各論の構成がほぼ整っていますが、背景セクションに明確な結論パラグラフが欠けており、まとめが不足しています。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事中のコードブロックは提供されたDiffと一致しており、変更点(watchDirectories の非同期化、createWatchers へのawait 追加)が正確に反映されています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

PRで使用されている用語(@tailwindcss/cli、@parce/watcher、watchDirectories 等)が正しく使われています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

技術的な主張はPRの説明とDiffに裏付けられており、因果関係も適切です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

全ての事実はPRタイトル・説明・Diffで裏付けられており、推測や捏造はありません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号 #20242 が正確に記載されており、他の数値・固有名詞の誤りは見当たりません。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事タイトルはPRタイトル「Prevent crash ... on Windows」の内容を日本語で適切に要約しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

LTS、EOL、リリース日程などPRに無関係な外部知識は記載されていません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

時間表現のずれはなく、PRの記述と一致しています。