@rushstack/heft
Version:
Build all your JavaScript projects the same way: A way that works.
186 lines • 9.93 kB
JavaScript
;
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.asAbsoluteCopyOperation = asAbsoluteCopyOperation;
exports.asRelativeCopyOperation = asRelativeCopyOperation;
exports.copyFilesAsync = copyFilesAsync;
const node_crypto_1 = require("node:crypto");
const path = __importStar(require("node:path"));
const node_core_library_1 = require("@rushstack/node-core-library");
const Constants_1 = require("../utilities/Constants");
const FileGlobSpecifier_1 = require("./FileGlobSpecifier");
const IncrementalBuildInfo_1 = require("../pluginFramework/IncrementalBuildInfo");
function asAbsoluteCopyOperation(rootFolderPath, copyOperation) {
const absoluteCopyOperation = (0, FileGlobSpecifier_1.asAbsoluteFileSelectionSpecifier)(rootFolderPath, copyOperation);
absoluteCopyOperation.destinationFolders = copyOperation.destinationFolders.map((folder) => path.resolve(rootFolderPath, folder));
return absoluteCopyOperation;
}
function asRelativeCopyOperation(rootFolderPath, copyOperation) {
return {
...copyOperation,
destinationFolders: copyOperation.destinationFolders.map((folder) => (0, IncrementalBuildInfo_1.makePathRelative)(folder, rootFolderPath)),
sourcePath: copyOperation.sourcePath && (0, IncrementalBuildInfo_1.makePathRelative)(copyOperation.sourcePath, rootFolderPath)
};
}
async function copyFilesAsync(copyOperations, terminal, buildInfoPath, configHash, watchFileSystemAdapter) {
const copyDescriptorByDestination = await _getCopyDescriptorsAsync(copyOperations, watchFileSystemAdapter);
await _copyFilesInnerAsync(copyDescriptorByDestination, configHash, buildInfoPath, terminal);
}
async function _getCopyDescriptorsAsync(copyConfigurations, fileSystemAdapter) {
// Create a map to deduplicate and prevent double-writes
// resolvedDestinationFilePath -> descriptor
const copyDescriptorByDestination = new Map();
await node_core_library_1.Async.forEachAsync(copyConfigurations, async (copyConfiguration) => {
// "sourcePath" is required to be a folder. To copy a single file, put the parent folder in "sourcePath"
// and the filename in "includeGlobs".
const sourceFolder = copyConfiguration.sourcePath;
const sourceFiles = await (0, FileGlobSpecifier_1.getFileSelectionSpecifierPathsAsync)({
fileGlobSpecifier: copyConfiguration,
fileSystemAdapter
});
// Dedupe and throw if a double-write is detected
for (const destinationFolderPath of copyConfiguration.destinationFolders) {
// We only need to care about the keys of the map since we know all the keys are paths to files
for (const sourceFilePath of sourceFiles.keys()) {
// Only include the relative path from the sourceFolder if flatten is false
const resolvedDestinationPath = path.resolve(destinationFolderPath, copyConfiguration.flatten
? path.basename(sourceFilePath)
: path.relative(sourceFolder, sourceFilePath));
// Throw if a duplicate copy target with a different source or options is specified
const existingDestinationCopyDescriptor = copyDescriptorByDestination.get(resolvedDestinationPath);
if (existingDestinationCopyDescriptor) {
if (existingDestinationCopyDescriptor.sourcePath === sourceFilePath &&
existingDestinationCopyDescriptor.hardlink === !!copyConfiguration.hardlink) {
// Found a duplicate, avoid adding again
continue;
}
throw new Error(`Cannot copy multiple files to the same destination "${resolvedDestinationPath}".`);
}
// Finally, default hardlink to false, add to the result, and add to the map for deduping
const processedCopyDescriptor = {
sourcePath: sourceFilePath,
destinationPath: resolvedDestinationPath,
hardlink: !!copyConfiguration.hardlink
};
copyDescriptorByDestination.set(resolvedDestinationPath, processedCopyDescriptor);
}
}
}, { concurrency: Constants_1.Constants.maxParallelism });
return copyDescriptorByDestination;
}
async function _copyFilesInnerAsync(copyDescriptors, configHash, buildInfoPath, terminal) {
if (copyDescriptors.size === 0) {
return;
}
let oldBuildInfo = await (0, IncrementalBuildInfo_1.tryReadBuildInfoAsync)(buildInfoPath);
if (oldBuildInfo && oldBuildInfo.configHash !== configHash) {
terminal.writeVerboseLine(`File copy configuration changed, discarding incremental state.`);
oldBuildInfo = undefined;
}
// Since in watch mode only changed files will get passed in, need to ensure that all files from
// the previous build are still tracked.
const inputFileVersions = new Map(oldBuildInfo === null || oldBuildInfo === void 0 ? void 0 : oldBuildInfo.inputFileVersions);
const buildInfo = {
configHash,
inputFileVersions
};
const allInputFiles = new Set();
for (const copyDescriptor of copyDescriptors.values()) {
allInputFiles.add(copyDescriptor.sourcePath);
}
await node_core_library_1.Async.forEachAsync(allInputFiles, async (inputFilePath) => {
const fileContent = await node_core_library_1.FileSystem.readFileToBufferAsync(inputFilePath);
const fileHash = (0, node_crypto_1.createHash)('sha256').update(fileContent).digest('base64');
inputFileVersions.set(inputFilePath, fileHash);
}, {
concurrency: Constants_1.Constants.maxParallelism
});
const copyDescriptorsWithWork = [];
for (const copyDescriptor of copyDescriptors.values()) {
const { sourcePath } = copyDescriptor;
const sourceFileHash = inputFileVersions.get(sourcePath);
if (!sourceFileHash) {
throw new Error(`Missing hash for input file: ${sourcePath}`);
}
if ((oldBuildInfo === null || oldBuildInfo === void 0 ? void 0 : oldBuildInfo.inputFileVersions.get(sourcePath)) === sourceFileHash) {
continue;
}
copyDescriptorsWithWork.push(copyDescriptor);
}
if (copyDescriptorsWithWork.length === 0) {
terminal.writeLine('All requested file copy operations are up to date. Nothing to do.');
return;
}
let copiedFileCount = 0;
let linkedFileCount = 0;
await node_core_library_1.Async.forEachAsync(copyDescriptorsWithWork, async (copyDescriptor) => {
if (copyDescriptor.hardlink) {
linkedFileCount++;
await node_core_library_1.FileSystem.createHardLinkAsync({
linkTargetPath: copyDescriptor.sourcePath,
newLinkPath: copyDescriptor.destinationPath,
alreadyExistsBehavior: node_core_library_1.AlreadyExistsBehavior.Overwrite
});
terminal.writeVerboseLine(`Linked "${copyDescriptor.sourcePath}" to "${copyDescriptor.destinationPath}".`);
}
else {
copiedFileCount++;
await node_core_library_1.FileSystem.copyFilesAsync({
sourcePath: copyDescriptor.sourcePath,
destinationPath: copyDescriptor.destinationPath,
alreadyExistsBehavior: node_core_library_1.AlreadyExistsBehavior.Overwrite
});
terminal.writeVerboseLine(`Copied "${copyDescriptor.sourcePath}" to "${copyDescriptor.destinationPath}".`);
}
}, { concurrency: Constants_1.Constants.maxParallelism });
terminal.writeLine(`Copied ${copiedFileCount} file${copiedFileCount === 1 ? '' : 's'} and ` +
`linked ${linkedFileCount} file${linkedFileCount === 1 ? '' : 's'}`);
await (0, IncrementalBuildInfo_1.writeBuildInfoAsync)(buildInfo, buildInfoPath);
}
const PLUGIN_NAME = 'copy-files-plugin';
class CopyFilesPlugin {
apply(taskSession, heftConfiguration, pluginOptions) {
taskSession.hooks.registerFileOperations.tap(PLUGIN_NAME, (operations) => {
for (const operation of pluginOptions.copyOperations) {
operations.copyOperations.add(operation);
}
return operations;
});
}
}
exports.default = CopyFilesPlugin;
//# sourceMappingURL=CopyFilesPlugin.js.map