問題の概要CI/CDのログでよく見かける光景です。ビルドパイプラインがクリーンアップの途中でクラッシュし、次のようなメッセージが表示されます。
Error: ENOTEMPTY: directory not empty, rmdir '/app/dist'
このエラーは、空であることを前提としたメソッドを使用してフォルダを削除しようとしたときに発生します。たとえ小さなファイルが一つでも残っていると、オペレーティングシステムはその要求をブロックします。本番環境では、これによりデプロイが停滞し、ビルド環境が壊れた状態のまま放置されることがよくあります。
なぜNode.jsでこのエラーが発生するのかもともとの fs.rmdir() 関数は、設計上厳格に作られています。ファイルやサブフォルダが全く含まれていないフォルダのみを削除します。macOSの .DS_Store や Git の .keep ファイルのような、わずか6KBの隠しファイルが一つあるだけでも、操作は即座に失敗します。
通常、次の3つの理由でこの問題に直面します。
- 書き込み中のプロセス: クリーンアップが開始されたときに、Vite や Webpack などのビルドツールがまだアセットを書き込んでいる。- 隠しファイル: コードが考慮していなかったシステムファイルがOSによって生成された。- 競合状態(レースコンディション): スクリプトの一部がファイルを追加している一方で、別の部分がディレクトリを削除しようとしている。## 最適な解決策:fs.rm を使用するNode.js バージョン 14.14.0 以降を使用している場合は、クリーンアップ作業に
fs.rmdirを使用するのはやめましょう。最新のfs.rmメソッドにはrecursiveフラグがあり、ターミナルのrm -rfと同様に動作します。ネストされたファイルやフォルダも問題なく処理できます。
同期的な手法:```
const fs = require('fs');
try { fs.rmSync('/app/dist', { recursive: true, force: true }); console.log('distフォルダをクリアしました。'); } catch (err) { console.error('クリーンアップに失敗しました:', err.message); }
### 非同期(Promise)の手法:```
const fs = require('fs').promises;
async function safeDelete(targetPath) {
try {
await fs.rm(targetPath, { recursive: true, force: true });
} catch (err) {
console.error(`${targetPath} を削除できませんでした:`, err);
}
}
ここでは force: true オプションが不可欠です。これにより、ディレクトリが既に存在しない場合でもスクリプトがクラッシュしなくなり、ビルドスクリプトの堅牢性が大幅に向上します。
レガシー環境への対応fs.rm が利用できない古いスタックで作業している場合もあります。その場合は、rimraf パッケージが最も信頼できる代替手段です。長年、クロスプラットフォームで削除を行うための定番ツールとして使われてきました。
npm 経由でインストールします。
npm install rimraf
次に、コードに実装します。
const rimraf = require('rimraf');
rimraf('/app/dist', (err) => {
if (err) console.error('rimrafエラー:', err);
else console.log('distの削除に成功しました');
});
削除できないフォルダのトラブルシューティング再帰的削除を使用してもエラーが解消されないことがあります。これは通常、そのフォルダ内のファイルがプロセスによって「ロック」されていることを意味します。ファイルが使用中の間、OSは物理的に削除を防止します。
1. ロックされているファイルを特定するLinux や macOS では、どのプロセスがフォルダを占有しているかを確認できます。ターミナルを開いて次を実行してください。
lsof +D /app/dist
プロセスID(PID)が表示された場合は、フォルダを削除する前にそのタスクを停止する必要があります。
2. CI/CD の競合状態を修正するDocker や GitHub Actions では、「clean」ステップと「build」ステップが同時に実行されていないことを確認してください。rm がディレクトリをスキャンしている最中に並列プロセスがファイルを作成すると、ENOTEMPTY エラーが発生します。
3. Windows のファイルロックWindows はファイルロックが特に強力です。エクスプローラーで /dist フォルダを開いていたり、VS Code のターミナルがそのディレクトリ内にいたりすると、削除は失敗します。フォルダへのすべてのハンドルを閉じてから再試行してください。
最終確認パイプラインの後半で予期せぬ失敗を防ぐために、常にスクリプト内で削除を確認してください。簡単なチェックを行うことで、環境が次のビルドステップに進む準備ができていることを保証できます。
const fs = require('fs');
const path = './dist';
fs.rmSync(path, { recursive: true, force: true });
if (!fs.existsSync(path)) {
console.log('✅ クリーンな状態を確認しました。');
} else {
console.error('❌ フォルダがまだ存在します。ファイルロックを確認してください。');
process.exit(1);
}

