@rushstack/heft
Version:
Build all your JavaScript projects the same way: A way that works.
99 lines • 5.04 kB
JavaScript
;
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
exports.deleteFilesAsync = deleteFilesAsync;
const node_core_library_1 = require("@rushstack/node-core-library");
const Constants_1 = require("../utilities/Constants");
const FileGlobSpecifier_1 = require("./FileGlobSpecifier");
async function _getPathsToDeleteAsync(rootFolderPath, deleteOperations) {
const result = {
filesToDelete: new Set(),
foldersToDelete: new Set()
};
await node_core_library_1.Async.forEachAsync(deleteOperations, async (deleteOperation) => {
const absoluteSpecifier = (0, FileGlobSpecifier_1.asAbsoluteFileSelectionSpecifier)(rootFolderPath, deleteOperation);
// Glob the files under the source path and add them to the set of files to delete
const sourcePaths = await (0, FileGlobSpecifier_1.getFileSelectionSpecifierPathsAsync)({
fileGlobSpecifier: absoluteSpecifier,
includeFolders: true
});
for (const [sourcePath, dirent] of sourcePaths) {
// If the sourcePath is a folder, add it to the foldersToDelete set. Otherwise, add it to
// the filesToDelete set. Symlinks and junctions are treated as files, and thus will fall
// into the filesToDelete set.
if (dirent.isDirectory()) {
result.foldersToDelete.add(sourcePath);
}
else {
result.filesToDelete.add(sourcePath);
}
}
}, { concurrency: Constants_1.Constants.maxParallelism });
return result;
}
async function deleteFilesAsync(rootFolderPath, deleteOperations, terminal) {
const pathsToDelete = await _getPathsToDeleteAsync(rootFolderPath, deleteOperations);
await _deleteFilesInnerAsync(pathsToDelete, terminal);
}
async function _deleteFilesInnerAsync(pathsToDelete, terminal) {
let deletedFiles = 0;
let deletedFolders = 0;
const { filesToDelete, foldersToDelete } = pathsToDelete;
await node_core_library_1.Async.forEachAsync(filesToDelete, async (pathToDelete) => {
try {
await node_core_library_1.FileSystem.deleteFileAsync(pathToDelete, { throwIfNotExists: true });
terminal.writeVerboseLine(`Deleted "${pathToDelete}".`);
deletedFiles++;
}
catch (error) {
// If it doesn't exist, we can ignore the error.
if (!node_core_library_1.FileSystem.isNotExistError(error)) {
throw error;
}
}
}, { concurrency: Constants_1.Constants.maxParallelism });
// Reverse the list of matching folders. Assuming that the list of folders came from
// the globber, the folders will be specified in tree-walk order, so by reversing the
// list we delete the deepest folders first and avoid not-exist errors for subfolders
// of an already-deleted parent folder.
const reversedFoldersToDelete = Array.from(foldersToDelete).reverse();
// Clear out any folders that were encountered during the file deletion process. This
// will recursively delete the folder and it's contents. There are two scenarios that
// this handles:
// - Deletions of empty folder structures (ex. when the delete glob is '**/*')
// - Deletions of folders that still contain files (ex. when the delete glob is 'lib')
// In the latter scenario, the count of deleted files will not be tracked. However,
// this is a fair trade-off for the performance benefit of not having to glob the
// folder structure again.
await node_core_library_1.Async.forEachAsync(reversedFoldersToDelete, async (folderToDelete) => {
try {
await node_core_library_1.FileSystem.deleteFolderAsync(folderToDelete);
terminal.writeVerboseLine(`Deleted folder "${folderToDelete}".`);
deletedFolders++;
}
catch (error) {
// If it doesn't exist, we can ignore the error.
if (!node_core_library_1.FileSystem.isNotExistError(error)) {
throw error;
}
}
}, { concurrency: Constants_1.Constants.maxParallelism });
if (deletedFiles > 0 || deletedFolders > 0) {
terminal.writeLine(`Deleted ${deletedFiles} file${deletedFiles !== 1 ? 's' : ''} ` +
`and ${deletedFolders} folder${deletedFolders !== 1 ? 's' : ''}`);
}
}
const PLUGIN_NAME = 'delete-files-plugin';
class DeleteFilesPlugin {
apply(taskSession, heftConfiguration, pluginOptions) {
taskSession.hooks.registerFileOperations.tap(PLUGIN_NAME, (fileOperations) => {
for (const deleteOperation of pluginOptions.deleteOperations) {
fileOperations.deleteOperations.add(deleteOperation);
}
return fileOperations;
});
}
}
exports.default = DeleteFilesPlugin;
//# sourceMappingURL=DeleteFilesPlugin.js.map