in Tasks/CopyFilesV2/copyfiles.ts [57:261]
async function main(): Promise<void> {
// we allow broken symlinks - since there could be broken symlinks found in source folder, but filtered by contents pattern
const findOptions: tl.FindOptions = {
allowBrokenSymbolicLinks: true,
followSpecifiedSymbolicLink: true,
followSymbolicLinks: true
};
tl.setResourcePath(path.join(__dirname, 'task.json'));
// contents is a multiline input containing glob patterns
let contents: string[] = tl.getDelimitedInput('Contents', '\n', true);
let sourceFolder: string = tl.getPathInput('SourceFolder', true, true);
let targetFolder: string = tl.getPathInput('TargetFolder', true);
let cleanTargetFolder: boolean = tl.getBoolInput('CleanTargetFolder', false);
let overWrite: boolean = tl.getBoolInput('OverWrite', false);
let flattenFolders: boolean = tl.getBoolInput('flattenFolders', false);
let retryCount: number = parseInt(tl.getInput('retryCount'));
let delayBetweenRetries: number = parseInt(tl.getInput('delayBetweenRetries'));
if (isNaN(retryCount) || retryCount < 0) {
retryCount = 0;
}
if (isNaN(delayBetweenRetries) || delayBetweenRetries < 0) {
delayBetweenRetries = 0;
}
const retryOptions: RetryOptions = {
timeoutBetweenRetries: delayBetweenRetries,
numberOfReties: retryCount
};
const retryHelper = new RetryHelper(retryOptions);
const preserveTimestamp: boolean = tl.getBoolInput('preserveTimestamp', false);
const ignoreMakeDirErrors: boolean = tl.getBoolInput('ignoreMakeDirErrors', false);
// normalize the source folder path. this is important for later in order to accurately
// determine the relative path of each found file (substring using sourceFolder.length).
sourceFolder = path.normalize(sourceFolder);
let allPaths: string[] = tl.find(sourceFolder, findOptions);
let sourceFolderPattern = sourceFolder.replace('[', '[[]'); // directories can have [] in them, and they have special meanings as a pattern, so escape them
let matchedPaths: string[] = tl.match(allPaths, contents, sourceFolderPattern); // default match options
let matchedFiles: string[] = matchedPaths.filter((itemPath: string) => !stats(itemPath, false).isDirectory()); // filter-out directories
// copy the files to the target folder
console.log(tl.loc('FoundNFiles', matchedFiles.length));
if (matchedFiles.length > 0) {
// clean target folder if required
if (cleanTargetFolder) {
console.log(tl.loc('CleaningTargetFolder', targetFolder));
// stat the targetFolder path
let targetFolderStats: tl.FsStats;
targetFolderStats = await retryHelper.RunWithRetry<tl.FsStats>(
() => stats(targetFolder, true),
`stats for ${targetFolder}`
);
if (targetFolderStats) {
if (targetFolderStats.isDirectory()) {
// delete the child items
const folderItems: string[] = await retryHelper.RunWithRetry<string[]>(
() => fs.readdirSync(targetFolder),
`readdirSync for ${targetFolder}`
);
for (let item of folderItems) {
let itemPath = path.join(targetFolder, item);
await retryHelper.RunWithRetry(() =>
tl.rmRF(itemPath),
`delete of ${itemPath}`
);
}
} else {
await retryHelper.RunWithRetry(() =>
tl.rmRF(targetFolder),
`delete of ${targetFolder}`
);
}
}
}
// make sure the target folder exists
await retryHelper.RunWithRetry(() =>
makeDirP(targetFolder, ignoreMakeDirErrors),
`makeDirP for ${targetFolder}`
);
try {
let createdFolders: { [folder: string]: boolean } = {};
for (let file of matchedFiles) {
let relativePath;
if (flattenFolders) {
relativePath = path.basename(file);
} else {
relativePath = file.substring(sourceFolder.length);
// trim leading path separator
// note, assumes normalized above
if (relativePath.startsWith(path.sep)) {
relativePath = relativePath.substr(1);
}
}
let targetPath = path.join(targetFolder, relativePath);
let targetDir = path.dirname(targetPath);
if (!createdFolders[targetDir]) {
await retryHelper.RunWithRetry(
() => makeDirP(targetDir, ignoreMakeDirErrors),
`makeDirP for ${targetDir}`
);
createdFolders[targetDir] = true;
}
// stat the target
let targetStats: tl.FsStats;
if (!cleanTargetFolder) { // optimization - no need to check if relative target exists when CleanTargetFolder=true
targetStats = await retryHelper.RunWithRetry<tl.FsStats>(
() => stats(targetPath, true),
`Stats for ${targetPath}`
);
}
// validate the target is not a directory
if (targetStats && targetStats.isDirectory()) {
throw new Error(tl.loc('TargetIsDir', file, targetPath));
}
if (!overWrite) {
if (targetStats) { // exists, skip
console.log(tl.loc('FileAlreadyExistAt', file, targetPath));
} else { // copy
console.log(tl.loc('CopyingTo', file, targetPath));
await retryHelper.RunWithRetry(
() => tl.cp(file, targetPath),
`copy ${file} to ${targetPath}`
);
if (preserveTimestamp) {
try {
let fileStats;
fileStats = await retryHelper.RunWithRetry<tl.FsStats>(
() => stats(file, false),
`stats for ${file}`
);
fs.utimes(targetPath, fileStats.atime, fileStats.mtime, (err) => {
displayTimestampChangeResults(fileStats, err);
});
} catch (err) {
console.warn(`Problem preserving the timestamp: ${err}`)
}
}
}
} else { // copy
console.log(tl.loc('CopyingTo', file, targetPath));
if (process.platform == 'win32' && targetStats && (targetStats.mode & 146) != 146) {
// The readonly attribute can be interpreted by performing a bitwise-AND operation on
// "fs.Stats.mode" and the integer 146. The integer 146 represents "-w--w--w-" or (128 + 16 + 2),
// see following chart:
// R W X R W X R W X
// 256 128 64 32 16 8 4 2 1
//
// "fs.Stats.mode" on Windows is based on whether the readonly attribute is set.
// If the readonly attribute is set, then the mode is set to "r--r--r--".
// If the readonly attribute is not set, then the mode is set to "rw-rw-rw-".
//
// Note, additional bits may also be set (e.g. if directory). Therefore, a bitwise
// comparison is appropriate.
//
// For additional information, refer to the fs source code and ctrl+f "st_mode":
// https://github.com/nodejs/node/blob/v5.x/deps/uv/src/win/fs.c#L1064
tl.debug(`removing readonly attribute on '${targetPath}'`);
await retryHelper.RunWithRetry(
() => fs.chmodSync(targetPath, targetStats.mode | 146),
`chmodSync for ${targetPath}`
);
}
await retryHelper.RunWithRetry(
() => tl.cp(file, targetPath, "-f"),
`copy ${file} to ${targetPath}`
);
if (preserveTimestamp) {
try {
const fileStats = await retryHelper.RunWithRetry<tl.FsStats>(
() => stats(file, false),
`stats for ${file}`
);
fs.utimes(targetPath, fileStats.atime, fileStats.mtime, (err) => {
displayTimestampChangeResults(fileStats, err);
});
} catch (err) {
console.warn(`Problem preserving the timestamp: ${err}`)
}
}
}
}
} catch (err) {
tl.setResult(tl.TaskResult.Failed, err);
}
}
}