UNPKG

next

Version:

The React Framework

81 lines (80 loc) 2.96 kB
import * as fs from 'node:fs'; import { join } from 'node:path'; import isError from './is-error'; import { wait } from './wait'; // We use an exponential backoff. See the unit test for example values. // // - Node's `fs` module uses a linear backoff, starting with 100ms. // - Rust tries 64 times with only a `thread::yield_now` in between. // // We want something more aggressive, as `recursiveDelete` is in the critical // path of `next dev` and `next build` startup. const INITIAL_RETRY_MS = 8; const MAX_RETRY_MS = 64; const MAX_RETRIES = 6; /** * Used in unit test. * @ignore */ export function calcBackoffMs(attempt) { return Math.min(INITIAL_RETRY_MS * Math.pow(2, attempt), MAX_RETRY_MS); } function unlinkPath(p, isDir = false, attempt = 0) { try { if (isDir) { fs.rmdirSync(p); } else { fs.unlinkSync(p); } } catch (e) { const code = isError(e) && e.code; if ((code === 'EBUSY' || code === 'ENOTEMPTY' || code === 'EPERM' || code === 'EMFILE') && attempt < MAX_RETRIES) { // retrying is unlikely to succeed on POSIX platforms, but Windows can // fail due to temporarily-open files return (async ()=>{ await wait(calcBackoffMs(attempt)); return unlinkPath(p, isDir, attempt + 1); })(); } if (code === 'ENOENT') { return; } throw e; } } /** * Recursively delete directory contents. * * This is used when cleaning the `distDir`, and is part of the critical path * for starting the server, so we use synchronous file IO, as we're always * blocked on it anyways. * * Despite using sync IO, the function signature is still `async` because we * asynchronously perform retries. */ export async function recursiveDeleteSyncWithAsyncRetries(/** Directory to delete the contents of */ dir, /** Exclude based on relative file path */ exclude, /** Relative path to the directory being deleted, used for exclude */ previousPath = '') { let result; try { result = fs.readdirSync(dir, { withFileTypes: true }); } catch (e) { if (isError(e) && e.code === 'ENOENT') { return; } throw e; } await Promise.all(result.map(async (part)=>{ const absolutePath = join(dir, part.name); const pp = join(previousPath, part.name); const isNotExcluded = !exclude || !exclude.test(pp); if (isNotExcluded) { // Note: readdir does not follow symbolic links, that's good: we want to // delete the links and not the destination. let isDirectory = part.isDirectory(); if (isDirectory) { await recursiveDeleteSyncWithAsyncRetries(absolutePath, exclude, pp); } return unlinkPath(absolutePath, isDirectory); } })); } //# sourceMappingURL=recursive-delete.js.map