@nestjs/cli
Version:
Nest - modern, fast, powerful node.js web framework (@cli)
51 lines (50 loc) • 2.48 kB
JavaScript
import { rm } from 'fs/promises';
import * as ts from 'typescript';
import { getValueOrDefault } from './get-value-or-default.js';
import { areOutsidePathsAllowed, assertPathInsideProject, } from './path-confinement.js';
/**
* A recursive delete is not atomic: it walks the tree, and anything created
* between the "readdir" of a directory and its "rmdir" makes the removal fail
* with ENOTEMPTY. Concurrent writers into "outDir" are common in monorepos
* (a sibling "tsc --noEmit --incremental" dropping its buildinfo there, for
* instance), which turns "deleteOutDir" into a flaky build. Node already
* treats ENOTEMPTY - along with EBUSY, EMFILE, ENFILE and EPERM, the errors
* Windows raises for a file another process still holds - as retryable, but
* only retries when "maxRetries" is set; it defaults to 0.
*
* See https://github.com/nestjs/nest-cli/issues/3509
*/
const RM_MAX_RETRIES = 3;
export async function deleteOutDirIfEnabled(configuration, appName, dirPath, tsOptions) {
const isDeleteEnabled = getValueOrDefault(configuration, 'compilerOptions.deleteOutDir', appName);
if (!isDeleteEnabled) {
return;
}
const shouldValidate = !areOutsidePathsAllowed(configuration, appName);
// With "incremental" enabled but no explicit "tsBuildInfoFile", TypeScript
// derives a default location that can fall outside "outDir" (with "outDir"
// and "rootDir" both set, "tsconfig.build.tsbuildinfo" lands next to the
// tsconfig), so the recursive delete below would leave it behind. Reuse the
// compiler's own resolution to remove the buildinfo wherever it actually is.
const tsBuildInfoFile = tsOptions &&
(tsOptions.tsBuildInfoFile ?? ts.getTsBuildInfoEmitOutputFilePath(tsOptions));
// Both paths are validated before anything is removed, so that a rejected
// "tsBuildInfoFile" cannot leave a half-deleted output directory behind.
const outDirToDelete = shouldValidate
? assertPathInsideProject(dirPath, 'outDir')
: dirPath;
const tsBuildInfoFileToDelete = shouldValidate && tsBuildInfoFile
? assertPathInsideProject(tsBuildInfoFile, 'tsBuildInfoFile')
: tsBuildInfoFile;
await rm(outDirToDelete, {
recursive: true,
force: true,
maxRetries: RM_MAX_RETRIES,
});
if (tsBuildInfoFileToDelete) {
await rm(tsBuildInfoFileToDelete, {
force: true,
maxRetries: RM_MAX_RETRIES,
});
}
}