import * as plugins from './plugins.js'; import * as interpreter from './interpreter.js'; import { SmartFile } from './classes.smartfile.js'; import * as memory from './memory.js'; import type { StreamFile } from './classes.streamfile.js'; /*=============================================================== ============================ Checks ============================= ===============================================================*/ /** * * @param filePath * @returns {boolean} */ export const fileExistsSync = (filePath): boolean => { let fileExistsBool: boolean = false; try { plugins.fsExtra.readFileSync(filePath); fileExistsBool = true; } catch (err) { fileExistsBool = false; } return fileExistsBool; }; /** * * @param filePath * @returns {any} */ export const fileExists = async (filePath): Promise => { const done = plugins.smartpromise.defer(); plugins.fs.access(filePath, 4, (err) => { err ? done.resolve(false) : done.resolve(true); }); return done.promise; }; /** * Checks if given path points to an existing directory */ export const isDirectory = (pathArg: string): boolean => { try { return plugins.fsExtra.statSync(pathArg).isDirectory(); } catch (err) { return false; } }; /** * Checks if given path points to an existing directory */ export const isDirectorySync = (pathArg: string): boolean => { try { return plugins.fsExtra.statSync(pathArg).isDirectory(); } catch (err) { return false; } }; /** * Checks if a given path points to an existing file */ export const isFile = (pathArg): boolean => { return plugins.fsExtra.statSync(pathArg).isFile(); }; /*=============================================================== ============================ FS ACTIONS ========================= ===============================================================*/ /** * copies a file from A to B on the local disk */ export const copy = async (fromArg: string, toArg: string): Promise => { const done = plugins.smartpromise.defer(); plugins.fsExtra.copy(fromArg, toArg, {}, (err) => { if (err) { throw new Error(`Could not copy from ${fromArg} to ${toArg}: ${err}`); } done.resolve(true); }); return done.promise; }; /** * copies a file SYNCHRONOUSLY from A to B on the local disk */ export const copySync = (fromArg: string, toArg: string): boolean => { plugins.fsExtra.copySync(fromArg, toArg); return true; }; /** * ensures that a directory is in place */ export const ensureDir = async (dirPathArg: string) => { await plugins.fsExtra.ensureDir(dirPathArg); }; /** * ensures that a directory is in place */ export const ensureDirSync = (dirPathArg: string) => { plugins.fsExtra.ensureDirSync(dirPathArg); }; /** * ensure an empty directory * @executes ASYNC */ export const ensureEmptyDir = async (dirPathArg: string) => { await plugins.fsExtra.ensureDir(dirPathArg); await plugins.fsExtra.emptyDir(dirPathArg); }; /** * ensure an empty directory * @executes SYNC */ export const ensureEmptyDirSync = (dirPathArg: string) => { plugins.fsExtra.ensureDirSync(dirPathArg); plugins.fsExtra.emptyDirSync(dirPathArg); }; /** * ensures that a file is on disk * @param filePath the filePath to ensureDir * @param the fileContent to place into a new file in case it doesn't exist yet * @returns Promise * @exec ASYNC */ export const ensureFile = async (filePathArg, initFileStringArg): Promise => { ensureFileSync(filePathArg, initFileStringArg); }; /** * ensures that a file is on disk * @param filePath the filePath to ensureDir * @param the fileContent to place into a new file in case it doesn't exist yet * @returns Promise * @exec SYNC */ export const ensureFileSync = (filePathArg: string, initFileStringArg: string): void => { if (fileExistsSync(filePathArg)) { return null; } else { memory.toFsSync(initFileStringArg, filePathArg); } }; /** * removes a file or folder from local disk */ export const remove = async (pathArg: string): Promise => { await plugins.fsExtra.remove(pathArg); }; /** * removes a file SYNCHRONOUSLY from local disk */ export const removeSync = (pathArg: string): void => { plugins.fsExtra.removeSync(pathArg); }; /** * removes an array of filePaths from disk */ export const removeMany = async (filePathArrayArg: string[]) => { const promiseArray: Array> = []; for (const filePath of filePathArrayArg) { promiseArray.push(remove(filePath)); } await Promise.all(promiseArray); }; /** * like removeFilePathArray but SYNCHRONOUSLY */ export const removeManySync = (filePathArrayArg: string[]): void => { for (const filePath of filePathArrayArg) { removeSync(filePath); } }; /*=============================================================== ============================ Write/Read ========================= ===============================================================*/ /** * reads a file content to an object * good for JSON, YAML, TOML, etc. * @param filePathArg * @param fileTypeArg * @returns {any} */ export const toObjectSync = (filePathArg, fileTypeArg?) => { const fileString = plugins.fsExtra.readFileSync(filePathArg, 'utf8'); let fileType; fileTypeArg ? (fileType = fileTypeArg) : (fileType = interpreter.filetype(filePathArg)); try { return interpreter.objectFile(fileString, fileType); } catch (err) { err.message = `Failed to read file at ${filePathArg}` + err.message; throw err; }; }; /** * reads a file content to a String */ export const toStringSync = (filePath: string): string => { const encoding = plugins.smartmime.getEncodingForPathSync(filePath); let fileString: string | Buffer = plugins.fsExtra.readFileSync(filePath, encoding); if (Buffer.isBuffer(fileString)) { fileString = fileString.toString('binary'); } return fileString; }; export const toBuffer = async (filePath: string): Promise => { return plugins.fsExtra.readFile(filePath); }; export const toBufferSync = (filePath: string): Buffer => { return plugins.fsExtra.readFileSync(filePath); }; /** * Creates a Readable Stream from a file path. * @param filePath The path to the file. * @returns {fs.ReadStream} */ export const toReadStream = (filePath: string): plugins.fs.ReadStream => { if (!fileExistsSync(filePath)) { throw new Error(`File does not exist at path: ${filePath}`); } return plugins.fsExtra.createReadStream(filePath); }; export const fileTreeToHash = async (dirPathArg: string, miniMatchFilter: string) => { const fileTreeObject = await fileTreeToObject(dirPathArg, miniMatchFilter); let combinedString = ''; for (const smartfile of fileTreeObject) { combinedString += await smartfile.getHash(); } const hash = await plugins.smarthash.sha256FromString(combinedString); return hash; }; /** * creates a smartfile array from a directory * @param dirPathArg the directory to start from * @param miniMatchFilter a minimatch filter of what files to include */ export const fileTreeToObject = async (dirPathArg: string, miniMatchFilter: string) => { // handle absolute miniMatchFilter let dirPath: string; if (plugins.path.isAbsolute(miniMatchFilter)) { dirPath = '/'; } else { dirPath = plugins.smartpath.transform.toAbsolute(dirPathArg) as string; } const fileTree = await listFileTree(dirPath, miniMatchFilter); const smartfileArray: SmartFile[] = []; for (const filePath of fileTree) { const readPath = ((): string => { if (!plugins.path.isAbsolute(filePath)) { return plugins.path.join(dirPath, filePath); } else { return filePath; } })(); const fileBuffer = plugins.fs.readFileSync(readPath); // push a read file as Smartfile smartfileArray.push( new SmartFile({ contentBuffer: fileBuffer, base: dirPath, path: filePath, }) ); } return smartfileArray; }; /** * lists Folders in a directory on local disk * @returns Promise with an array that contains the folder names */ export const listFolders = async (pathArg: string, regexFilter?: RegExp): Promise => { return listFoldersSync(pathArg, regexFilter); }; /** * lists Folders SYNCHRONOUSLY in a directory on local disk * @returns an array with the folder names as strings */ export const listFoldersSync = (pathArg: string, regexFilter?: RegExp): string[] => { let folderArray = plugins.fsExtra.readdirSync(pathArg).filter((file) => { return plugins.fsExtra.statSync(plugins.path.join(pathArg, file)).isDirectory(); }); if (regexFilter) { folderArray = folderArray.filter((fileItem) => { return regexFilter.test(fileItem); }); } return folderArray; }; /** * lists Files in a directory on local disk * @returns Promise */ export const listFiles = async (pathArg: string, regexFilter?: RegExp): Promise => { return listFilesSync(pathArg, regexFilter); }; /** * lists Files SYNCHRONOUSLY in a directory on local disk * @returns an array with the folder names as strings */ export const listFilesSync = (pathArg: string, regexFilter?: RegExp): string[] => { let fileArray = plugins.fsExtra.readdirSync(pathArg).filter((file) => { return plugins.fsExtra.statSync(plugins.path.join(pathArg, file)).isFile(); }); if (regexFilter) { fileArray = fileArray.filter((fileItem) => { return regexFilter.test(fileItem); }); } return fileArray; }; /** * lists all items (folders AND files) in a directory on local disk * @returns Promise */ export const listAllItems = async (pathArg: string, regexFilter?: RegExp): Promise => { return listAllItemsSync(pathArg, regexFilter); }; /** * lists all items (folders AND files) in a directory on local disk * @returns an array with the folder names as strings * @executes SYNC */ export const listAllItemsSync = (pathArg: string, regexFilter?: RegExp): string[] => { let allItmesArray = plugins.fsExtra.readdirSync(pathArg).filter((file) => { return plugins.fsExtra.statSync(plugins.path.join(pathArg, file)).isFile(); }); if (regexFilter) { allItmesArray = allItmesArray.filter((fileItem) => { return regexFilter.test(fileItem); }); } return allItmesArray; }; /** * lists a file tree using a miniMatch filter * note: if the miniMatch Filter is an absolute path, the cwdArg will be omitted * @returns Promise string array with the absolute paths of all matching files */ export const listFileTree = async ( dirPathArg: string, miniMatchFilter: string, absolutePathsBool: boolean = false ): Promise => { // handle absolute miniMatchFilter let dirPath: string; if (plugins.path.isAbsolute(miniMatchFilter)) { dirPath = '/'; } else { dirPath = dirPathArg; } const options = { cwd: dirPath, nodir: true, dot: true, }; let fileList = await plugins.glob.glob(miniMatchFilter, options); if (absolutePathsBool) { fileList = fileList.map((filePath) => { return plugins.path.resolve(plugins.path.join(dirPath, filePath)); }); } return fileList; }; /** * Watches for file stability before resolving the promise. */ export const waitForFileToBeReady = (filePathArg: string): Promise => { return new Promise(async (resolve, reject) => { let lastFileSize = -1; let fileIsStable = false; const checkFileStability = async () => { let currentFileSize: number; const deferred = plugins.smartpromise.defer(); plugins.fs.stat(filePathArg, (err, stats) => { if (err) { fileIsStable = true; watcher.close(); reject(err); return; } currentFileSize = stats.size; deferred.resolve(); }); await deferred.promise; if (currentFileSize === lastFileSize) { fileIsStable = true; await plugins.smartdelay.delayFor(100); resolve(); } lastFileSize = currentFileSize; }; const watcher = plugins.fs.watch(filePathArg, (eventType, filename) => { if (eventType === 'change') { checkFileStability(); } }); watcher.on('error', (error) => { watcher.close(); reject(error); }); while (!fileIsStable) { await checkFileStability(); if (!fileIsStable) { await plugins.smartdelay.delayFor(5000); } } watcher.close(); }); }; /** * writes string or Smartfile to disk. * @param fileArg * @param fileNameArg * @param fileBaseArg */ export let toFs = async ( fileContentArg: string | Buffer | SmartFile | StreamFile, filePathArg: string, optionsArg: { respectRelative?: boolean; } = {} ) => { const done = plugins.smartpromise.defer(); // check args if (!fileContentArg || !filePathArg) { throw new Error('expected valid arguments'); } // prepare actual write action let fileContent: string | Buffer; let fileEncoding: 'utf8' | 'binary' = 'utf8'; let filePath: string = filePathArg; // handle Smartfile if (fileContentArg instanceof SmartFile) { fileContent = fileContentArg.contentBuffer; // handle options if (optionsArg.respectRelative) { filePath = plugins.path.join(filePath, fileContentArg.path); } } else if (Buffer.isBuffer(fileContentArg)) { fileContent = fileContentArg; fileEncoding = 'binary'; } else if (typeof fileContentArg === 'string') { fileContent = fileContentArg; } else { throw new Error('fileContent is neither string nor Smartfile'); } await ensureDir(plugins.path.parse(filePath).dir); plugins.fsExtra.writeFile(filePath, fileContent, { encoding: fileEncoding }, done.resolve); return await done.promise; }; export const stat = async (filePathArg: string) => { return plugins.fsPromises.stat(filePathArg); }