UNPKG

@ts-common/azure-js-dev-tools

Version:

Developer dependencies for TypeScript related projects

585 lines (543 loc) 21.1 kB
/** * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for * license information. */ import * as fs from "fs"; import { any } from "./arrays"; import { getParentFolderPath, getPathName, joinPath, isRooted } from "./path"; async function _entryExists(entryPath: string, condition?: (stats: fs.Stats) => (boolean | Promise<boolean>)): Promise<boolean> { return new Promise((resolve, reject) => { fs.lstat(entryPath, (error: NodeJS.ErrnoException | null, stats: fs.Stats) => { if (error) { if (error.code === "ENOENT" || error.code === "ENOTDIR") { resolve(false); } else { reject(error); } } else { resolve(!condition || condition(stats)); } }); }); } function _entryExistsSync(entryPath: string, condition?: (stats: fs.Stats) => boolean): boolean { let result = false; try { const stat: fs.Stats = fs.lstatSync(entryPath); result = !!(!condition || condition(stat)); } catch (error) { } return result; } /** * Get whether or not a file entry (file or folder) exists at the provided entryPath. * @param entryPath The path to the file entry to check. * @returns Whether or not a file entry (file or folder) exists at the provided entryPath. */ export function entryExists(entryPath: string): Promise<boolean> { return _entryExists(entryPath); } /** * Check whether or not a symbolic link exists at the provided path. * @param symbolicLinkPath The path to check. * @returns Whether or not a symbolic link exists at the provided path. */ export function symbolicLinkExists(symbolicLinkPath: string): Promise<boolean> { return _entryExists(symbolicLinkPath, (stats: fs.Stats) => stats.isSymbolicLink()); } /** * Check whether or not a file exists at the provided filePath. * @param filePath The path to check. * @returns Whether or not a file exists at the provided filePath. */ export function fileExists(filePath: string): Promise<boolean> { return _entryExists(filePath, (stats: fs.Stats) => stats.isFile()); } /** * Check whether or not a file exists at the provided filePath. * @param filePath The path to check. * @returns Whether or not a file exists at the provided filePath. */ export function fileExistsSync(filePath: string): boolean { return _entryExistsSync(filePath, (stats: fs.Stats) => stats.isFile()); } /** * Check whether or not a folder exists at the provided folderPath. * @param folderPath The path to check. * @returns Whether or not a folder exists at the provided folderPath. */ export function folderExists(folderPath: string): Promise<boolean> { return _entryExists(folderPath, (stats: fs.Stats) => stats.isDirectory()); } export function _createFolder(folderPath: string): Promise<boolean> { return new Promise((resolve, reject) => { fs.mkdir(folderPath, (error: NodeJS.ErrnoException | null) => { if (error) { if (error.code === "EEXIST") { resolve(false); } else { reject(error); } } else { resolve(true); } }); }); } /** * Create a folder at the provided folderPath. If the folder is successfully created, then true will * be returned. If the folder already exists, then false will be returned. * @param folderPath The path to create a folder at. */ export async function createFolder(folderPath: string): Promise<boolean> { let result: boolean | Promise<boolean> | undefined; try { result = await _createFolder(folderPath); } catch (createFolderError) { if (createFolderError.code !== "ENOENT") { result = Promise.reject(createFolderError); } else { try { await createFolder(getParentFolderPath(folderPath)); try { result = await _createFolder(folderPath); } catch (createFolderError2) { result = Promise.reject(createFolderError2); } } catch (createParentFolderError) { result = Promise.reject(createFolderError); } } } return result; } /** * Create a temporary folder and return the absolute path to the folder. If a parentFolderPath is * provided, then the temporary folder will be created as a child of the parentFolderPath. If * parentFolderPath is not provided, then the current working folder will be used as the * parentFolderPath. * @param The folder that the temporary folder will be created as a child of. Defaults to the * current working folder. */ export async function createTemporaryFolder(parentFolderPath?: string): Promise<string> { if (!parentFolderPath) { parentFolderPath = process.cwd(); } else if (!isRooted(parentFolderPath)) { parentFolderPath = joinPath(process.cwd(), parentFolderPath); } let result = ""; let i = 1; while (!result) { const tempFolderPath: string = joinPath(parentFolderPath, i.toString()); if (await createFolder(tempFolderPath)) { result = tempFolderPath; break; } else { ++i; } } return result; } /** * Copy the entry at the source entry path to the destination entry path. * @param sourceEntryPath The path to the entry to copy from. * @param destinationEntryPath The path to entry to copy to. */ export async function copyEntry(sourceEntryPath: string, destinationEntryPath: string): Promise<void> { let result: Promise<void>; if (await fileExists(sourceEntryPath)) { result = copyFile(sourceEntryPath, destinationEntryPath); } else if (await folderExists(sourceEntryPath)) { result = copyFolder(sourceEntryPath, destinationEntryPath); } else { const error: NodeJS.ErrnoException = new Error(`ENOENT: no such file or directory: ${sourceEntryPath}`); error.code = "ENOENT"; error.path = sourceEntryPath; result = Promise.reject(error); } return result; } /** * Copy the file at the source file path to the destination file path. * @param sourceFilePath The path to the file to copy from. * @param destinationFilePath The path to file to copy to. * @param createDestinationFolder Whether or not the destination parent folder will be created if it * doesn't exist. */ export async function copyFile(sourceFilePath: string, destinationFilePath: string, createDestinationFolder = true): Promise<void> { return new Promise((resolve, reject) => { fs.copyFile(sourceFilePath, destinationFilePath, async (error: NodeJS.ErrnoException | null) => { if (!error) { resolve(); } else if (error.code !== "ENOENT" || !(await fileExists(sourceFilePath)) || !createDestinationFolder) { reject(error); } else { const destinationFolderPath: string = getParentFolderPath(destinationFilePath); if (await folderExists(destinationFolderPath)) { reject(error); } else { try { await createFolder(destinationFolderPath); await copyFile(sourceFilePath, destinationFilePath, false); resolve(); } catch (error2) { reject(error); } } } }); }); } /** * Copy the folder at the source folder path to the destination folder path. * @param sourceFolderPath The path to the folder to copy from. * @param destinationFolderPath The path to the folder to copy to. This folder and its parent * folders will be created if they don't already exist. */ export async function copyFolder(sourceFolderPath: string, destinationFolderPath: string): Promise<void> { let result: Promise<void>; const childEntryPaths: string[] | undefined = await getChildEntryPaths(sourceFolderPath); if (!childEntryPaths) { const error: NodeJS.ErrnoException = new Error(`ENOENT: no such file or directory: ${sourceFolderPath}`); error.code = "ENOENT"; error.path = sourceFolderPath; result = Promise.reject(error); } else { for (const childEntryPath of childEntryPaths) { const childEntryName: string = getPathName(childEntryPath); await copyEntry(childEntryPath, joinPath(destinationFolderPath, childEntryName)); } result = Promise.resolve(); } return result; } /** * Get whether or not the provided string completely matches the provided regularExpression. */ function matches(regularExpression: RegExp, possibleMatch: string): boolean { const matchResult = possibleMatch.match(regularExpression); return !!(matchResult && matchResult[0].length === possibleMatch.length); } export async function findEntryInPath(entryName: string | RegExp, startFolderPath: string | undefined, condition: (entryPath: string) => (boolean | Promise<boolean>)): Promise<string | undefined> { let result: string | undefined; let folderPath: string = startFolderPath || process.cwd(); searchLoop: while (true) { if (typeof entryName === "string") { const folderEntryPath: string = joinPath(folderPath, entryName); if (await Promise.resolve(condition(folderEntryPath))) { result = folderEntryPath; break searchLoop; } } else { const folderEntryPaths: string[] | undefined = await getChildEntryPaths(folderPath); if (any(folderEntryPaths)) { for (const folderEntryPath of folderEntryPaths) { if (matches(entryName, folderEntryPath)) { if (await Promise.resolve(condition(folderEntryPath))) { result = folderEntryPath; break searchLoop; } } else { const folderEntryName: string = getPathName(folderEntryPath); if (matches(entryName, folderEntryName)) { if (await Promise.resolve(condition(folderEntryPath))) { result = folderEntryPath; break searchLoop; } } } } } } const parentFolderPath: string = getParentFolderPath(folderPath); if (!parentFolderPath || folderPath === parentFolderPath) { break searchLoop; } else { folderPath = parentFolderPath; } } return result; } function findEntryInPathSync(entryName: string, startFolderPath: string | undefined, condition: (entryPath: string) => boolean): string | undefined { let result: string | undefined; let folderPath: string = startFolderPath || process.cwd(); while (folderPath) { const possibleResult: string = joinPath(folderPath, entryName); if (condition(possibleResult)) { result = possibleResult; break; } else { const parentFolderPath: string = getParentFolderPath(folderPath); if (!parentFolderPath || folderPath === parentFolderPath) { break; } else { folderPath = parentFolderPath; } } } return result; } /** * Find the closest file with the provided name by searching the immediate child folders of the * folder at the provided startFolderPath. If no file is found with the provided fileName, then the * search will move up to the parent folder of the startFolderPath. This will continue until either * the file is found, or the folder being searched does not have a parent folder (if it is a root * folder). * @param fileName The name of the file to look for. * @param startFolderPath The path to the folder where the search will begin. * @returns The path to the closest file with the provided fileName, or undefined if no file could * be found. */ export function findFileInPath(fileName: string | RegExp, startFolderPath?: string): Promise<string | undefined> { return findEntryInPath(fileName, startFolderPath, fileExists); } /** * Find the closest file with the provided name by searching the immediate child folders of the * folder at the provided startFolderPath. If no file is found with the provided fileName, then the * search will move up to the parent folder of the startFolderPath. This will continue until either * the file is found, or the folder being searched does not have a parent folder (if it is a root * folder). * @param fileName The name of the file to look for. * @param startFolderPath The path to the folder where the search will begin. * @returns The path to the closest file with the provided fileName, or undefined if no file could * be found. */ export function findFileInPathSync(fileName: string, startFolderPath?: string): string | undefined { return findEntryInPathSync(fileName, startFolderPath, fileExistsSync); } /** * Find the closest folder with the provided name by searching the immediate child folders of the * folder at the provided startFolderPath. If no folder is found with the provided folderName, then * the search will move up to the parent folder of the startFolderPath. This will continue until * either the folder is found, or the folder being searched does not have a parent folder (it is a * root folder). * @param folderName The name of the folder to look for. * @param startFolderPath The path to the folder where the search will begin. * @returns The path to the closest folder with the provided folderName, or undefined if no folder * could be found. */ export function findFolderInPath(folderName: string | RegExp, startFolderPath?: string): Promise<string | undefined> { return findEntryInPath(folderName, startFolderPath, folderExists); } /** * Optional parameters to the getChildFilePaths() function. */ export interface GetChildEntriesOptions { /** * Whether or not to search sub-folders of the provided folderPath. */ recursive?: boolean | ((folderPath: string) => (boolean | Promise<boolean>)); /** * A condition that a child entry path must pass before it will be added to the result. */ condition?: (entryPath: string) => (boolean | Promise<boolean>); /** * A condition that a child file path must pass before it will be added to the result. */ fileCondition?: (filePath: string) => (boolean | Promise<boolean>); /** * A condition that a child folder path must pass before it will be added to the result. */ folderCondition?: (folderPath: string) => (boolean | Promise<boolean>); /** * The array where the matching child folder paths will be added. */ result?: string[]; } /** * Get the child entries of the folder at the provided folderPath. If the provided folder doesn't * exist, then undefined will be returned. * @param folderPath The path to the folder. * @returns The paths to the child entries of the folder at the provided folder path, or undefined * if the folder at the provided folder path doesn't exist. */ export function getChildEntryPaths(folderPath: string, options: GetChildEntriesOptions = {}): Promise<string[] | undefined> { return new Promise((resolve, reject) => { fs.readdir(folderPath, async (error: NodeJS.ErrnoException | null, entryNames: string[]) => { if (error) { if (error.code === "ENOENT" || error.code === "ENOTDIR") { resolve(undefined); } else { reject(error); } } else { const result: string[] = options.result || []; for (const entryName of entryNames) { const entryPath: string = joinPath(folderPath, entryName); try { if (await fileExists(entryPath)) { const addFile: boolean = (!options.condition || await Promise.resolve(options.condition(entryPath))) && (!options.fileCondition || await Promise.resolve(options.fileCondition(entryPath))); if (addFile) { result.push(entryPath); } } else if (await folderExists(entryPath)) { const addFolder: boolean = (!options.condition || await Promise.resolve(options.condition(entryPath))) && (!options.folderCondition || await Promise.resolve(options.folderCondition(entryPath))); if (addFolder) { result.push(entryPath); } if (options.recursive && (typeof options.recursive !== "function" || await Promise.resolve(options.recursive(entryPath)))) { options.result = result; await getChildEntryPaths(entryPath, options); } } } catch (error) { // If an error occurs while trying to get information about an entry, then just skip the // entry. It's most likely a permissions problem, which means we shouldn't be dealing // with that entry anyways. } } resolve(result); } }); }); } /** * Get the child folders of the folder at the provided folderPath. If the provided folder doesn't * exist, then undefined will be returned. * @param folderPath The path to the folder. * @returns The paths to the child folders of the folder at the provided folder path, or undefined * if the folder at the provided folder path doesn't exist. */ export async function getChildFolderPaths(folderPath: string, options: GetChildEntriesOptions = {}): Promise<string[] | undefined> { return getChildEntryPaths(folderPath, { ...options, fileCondition: () => false, }); } /** * Get the child folders of the folder at the provided folderPath. If the provided folder doesn't * exist, then undefined will be returned. * @param folderPath The path to the folder. * @returns The paths to the child folders of the folder at the provided folder path, or undefined * if the folder at the provided folder path doesn't exist. */ export function getChildFilePaths(folderPath: string, options: GetChildEntriesOptions = {}): Promise<string[] | undefined> { return getChildEntryPaths(folderPath, { ...options, folderCondition: () => false, }); } /** * Read the contents of the provided file. * @param filePath The path to the file to read. */ export function readFileContents(filePath: string): Promise<string | undefined> { return new Promise((resolve, reject) => { fs.readFile(filePath, { encoding: "utf8" }, (error: NodeJS.ErrnoException | null, content: string) => { if (error) { if (error.code === "ENOENT") { resolve(undefined); } else { reject(error); } } else { resolve(content); } }); }); } /** * Write the provided contents to the file at the provided filePath. * @param filePath The path to the file to write. * @param contents The contents to write to the file. */ export function writeFileContents(filePath: string, contents: string): Promise<void> { return new Promise((resolve, reject) => { fs.writeFile(filePath, contents, (error: NodeJS.ErrnoException | null) => { if (error) { reject(error); } else { resolve(); } }); }); } export async function deleteEntry(path: string): Promise<boolean> { return await folderExists(path) ? await deleteFolder(path) : await deleteFile(path); } /** * Delete the file at the provided file path. * @param {string} filePath The path to the file to delete. */ export function deleteFile(filePath: string): Promise<boolean> { return new Promise((resolve, reject) => { fs.unlink(filePath, (error: NodeJS.ErrnoException | null) => { if (error) { if (error.code === "ENOENT") { resolve(false); } else { reject(error); } } else { resolve(true); } }); }); } /** * Delete each of the provided file paths. * @param filePaths The file paths that should be deleted. */ export async function deleteFiles(...filePaths: string[]): Promise<void> { if (filePaths && filePaths.length > 0) { for (const filePath of filePaths) { await deleteFile(filePath); } } } function _deleteFolder(folderPath: string): Promise<boolean> { return new Promise((resolve, reject) => { fs.rmdir(folderPath, (error: NodeJS.ErrnoException | null) => { if (error) { if (error.code === "ENOENT") { resolve(false); } else { reject(error); } } else { resolve(true); } }); }); } /** * Delete the folder at the provided folder path. * @param {string} folderPath The path to the folder to delete. */ export async function deleteFolder(folderPath: string): Promise<boolean> { let result: boolean | Error; try { result = await _deleteFolder(folderPath); } catch (deleteFolderError) { if (deleteFolderError.code === "ENOENT") { result = false; } else if (deleteFolderError.code !== "ENOTEMPTY") { result = deleteFolderError; } else { try { const childEntryPaths: string[] = (await getChildEntryPaths(folderPath))!; for (const childEntryPath of childEntryPaths) { await deleteEntry(childEntryPath); } try { result = await _deleteFolder(folderPath); } catch (deleteFolderError2) { result = deleteFolderError2; } } catch (deleteChildEntryError) { result = deleteChildEntryError; } } } return typeof result === "boolean" ? Promise.resolve(result) : Promise.reject(result); }