Preamble
This is a supplementary answer. EPERM
(see comments) error code may also be caused by a file under the path being unlinked still being in use. I encountered this issue while setting up a cleanup process after unit testing with Mocha.
Problem
Unlinking files with unlink()
or unlinkSync()
may not work as expected on Windows, because files aren't actually deleted, but basically marked to be deleted once all file handles are released (see the related question).
Thus, for example, despite unlinkSync()
successfully "deleting" the file, when rmdirSync()
gets to the containing folder, it might fail with ENOTEMPTY
since, from the filesystem point of view, the file is still there.
There is not much you can do if the file is in use by processes you don't control apart from polling for the file becoming available (see this question), but if you just need to do a cleanup - spawn a child process right before exiting from the process:
//parent process
const { spawn } = require('child_process');
/**
* @summary Spawns a completely detached child
* @param {NodeJS.Process} parent
* @param {string} path
* @param {object} env
* @return {NodeJS.Process}
*/
const spawnResponsible = (parent, path, env) => {
const child = spawn(parent.argv0, [path], {
env,
detached: true,
stdio: "ignore"
});
child.unref();
return child;
};
process.once('beforeExit', () => {
spawnResponsible(
process,
'path to your child process script file',
{ PATHS: [] } // an array of paths
);
});
Your child process might look like this (note that using sync methods is not recommended unless you are doing a setup or cleanup and cannot proceed without [using child process partially solves the problem, but at the expense of additional process running]):
// child process
const fs = require('fs');
const pt = require('path');
const { PATHS } = process.env;
/**
* @summary Cleans root path and subpaths
* @param {string} rootPath
*/
const cleanOfEntries = (rootPath) => {
if (fs.existsSync(rootPath)) {
const entries = fs.readdirSync(rootPath, { withFileTypes: true });
for (const entry of entries) {
const thisPath = pt.resolve(rootPath, entry.name);
if (fs.existsSync(thisPath)) {
entry.isDirectory() && cleanOfEntries(thisPath);
entry.isFile() && fs.unlinkSync(thisPath);
}
}
fs.rmdirSync(rootPath);
}
};
for (const path of PATHS.split(',')) {
cleanOfEntries(path);
}
References
unlinkSync
spawn
rmdirSync