497 lines
14 KiB
TypeScript
497 lines
14 KiB
TypeScript
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<boolean> => {
|
|
const done = plugins.smartpromise.defer<boolean>();
|
|
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<boolean> => {
|
|
const done = plugins.smartpromise.defer<boolean>();
|
|
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<void>
|
|
* @exec ASYNC
|
|
*/
|
|
export const ensureFile = async (filePathArg, initFileStringArg): Promise<void> => {
|
|
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<void>
|
|
* @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<void> => {
|
|
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<Promise<void>> = [];
|
|
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<Buffer> => {
|
|
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<string[]> => {
|
|
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<string[]> => {
|
|
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<string[]>
|
|
*/
|
|
export const listAllItems = async (pathArg: string, regexFilter?: RegExp): Promise<string[]> => {
|
|
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[]> string array with the absolute paths of all matching files
|
|
*/
|
|
export const listFileTree = async (
|
|
dirPathArg: string,
|
|
miniMatchFilter: string,
|
|
absolutePathsBool: boolean = false
|
|
): Promise<string[]> => {
|
|
// 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<void> => {
|
|
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);
|
|
}
|