fix(fs): Improve fs and stream handling, enhance SmartFile/StreamFile, update tests and CI configs
This commit is contained in:
165
ts/fs.ts
165
ts/fs.ts
@@ -74,21 +74,45 @@ export const isFile = (pathArg): boolean => {
|
||||
/**
|
||||
* copies a file or directory from A to B on the local disk
|
||||
*/
|
||||
export const copy = async (fromArg: string, toArg: string, optionsArg?: plugins.fsExtra.CopyOptions & { replaceTargetDir?: boolean }): Promise<void> => {
|
||||
if (optionsArg?.replaceTargetDir && isDirectory(fromArg) && isDirectory(toArg)) {
|
||||
export const copy = async (
|
||||
fromArg: string,
|
||||
toArg: string,
|
||||
optionsArg?: plugins.fsExtra.CopyOptions & { replaceTargetDir?: boolean },
|
||||
): Promise<void> => {
|
||||
if (
|
||||
optionsArg?.replaceTargetDir &&
|
||||
isDirectory(fromArg) &&
|
||||
isDirectory(toArg)
|
||||
) {
|
||||
await remove(toArg);
|
||||
}
|
||||
return await plugins.fsExtra.copy(fromArg, toArg, optionsArg as plugins.fsExtra.CopyOptions);
|
||||
return await plugins.fsExtra.copy(
|
||||
fromArg,
|
||||
toArg,
|
||||
optionsArg as plugins.fsExtra.CopyOptions,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* copies a file or directory SYNCHRONOUSLY from A to B on the local disk
|
||||
*/
|
||||
export const copySync = (fromArg: string, toArg: string, optionsArg?: plugins.fsExtra.CopyOptionsSync & { replaceTargetDir?: boolean }): void => {
|
||||
if (optionsArg?.replaceTargetDir && isDirectory(fromArg) && isDirectory(toArg)) {
|
||||
export const copySync = (
|
||||
fromArg: string,
|
||||
toArg: string,
|
||||
optionsArg?: plugins.fsExtra.CopyOptionsSync & { replaceTargetDir?: boolean },
|
||||
): void => {
|
||||
if (
|
||||
optionsArg?.replaceTargetDir &&
|
||||
isDirectory(fromArg) &&
|
||||
isDirectory(toArg)
|
||||
) {
|
||||
removeSync(toArg);
|
||||
}
|
||||
return plugins.fsExtra.copySync(fromArg, toArg, optionsArg as plugins.fsExtra.CopyOptionsSync);
|
||||
return plugins.fsExtra.copySync(
|
||||
fromArg,
|
||||
toArg,
|
||||
optionsArg as plugins.fsExtra.CopyOptionsSync,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -130,7 +154,10 @@ export const ensureEmptyDirSync = (dirPathArg: string) => {
|
||||
* @returns Promise<void>
|
||||
* @exec ASYNC
|
||||
*/
|
||||
export const ensureFile = async (filePathArg, initFileStringArg): Promise<void> => {
|
||||
export const ensureFile = async (
|
||||
filePathArg,
|
||||
initFileStringArg,
|
||||
): Promise<void> => {
|
||||
ensureFileSync(filePathArg, initFileStringArg);
|
||||
};
|
||||
|
||||
@@ -141,7 +168,10 @@ export const ensureFile = async (filePathArg, initFileStringArg): Promise<void>
|
||||
* @returns Promise<void>
|
||||
* @exec SYNC
|
||||
*/
|
||||
export const ensureFileSync = (filePathArg: string, initFileStringArg: string): void => {
|
||||
export const ensureFileSync = (
|
||||
filePathArg: string,
|
||||
initFileStringArg: string,
|
||||
): void => {
|
||||
if (fileExistsSync(filePathArg)) {
|
||||
return null;
|
||||
} else {
|
||||
@@ -197,7 +227,9 @@ export const removeManySync = (filePathArrayArg: string[]): void => {
|
||||
export const toObjectSync = (filePathArg, fileTypeArg?) => {
|
||||
const fileString = plugins.fsExtra.readFileSync(filePathArg, 'utf8');
|
||||
let fileType;
|
||||
fileTypeArg ? (fileType = fileTypeArg) : (fileType = interpreter.filetype(filePathArg));
|
||||
fileTypeArg
|
||||
? (fileType = fileTypeArg)
|
||||
: (fileType = interpreter.filetype(filePathArg));
|
||||
try {
|
||||
return interpreter.objectFile(fileString, fileType);
|
||||
} catch (err) {
|
||||
@@ -211,7 +243,10 @@ export const toObjectSync = (filePathArg, fileTypeArg?) => {
|
||||
*/
|
||||
export const toStringSync = (filePath: string): string => {
|
||||
const encoding = plugins.smartmime.getEncodingForPathSync(filePath);
|
||||
let fileString: string | Buffer = plugins.fsExtra.readFileSync(filePath, encoding);
|
||||
let fileString: string | Buffer = plugins.fsExtra.readFileSync(
|
||||
filePath,
|
||||
encoding,
|
||||
);
|
||||
if (Buffer.isBuffer(fileString)) {
|
||||
fileString = fileString.toString('binary');
|
||||
}
|
||||
@@ -238,7 +273,10 @@ export const toReadStream = (filePath: string): plugins.fs.ReadStream => {
|
||||
return plugins.fsExtra.createReadStream(filePath);
|
||||
};
|
||||
|
||||
export const fileTreeToHash = async (dirPathArg: string, miniMatchFilter: string) => {
|
||||
export const fileTreeToHash = async (
|
||||
dirPathArg: string,
|
||||
miniMatchFilter: string,
|
||||
) => {
|
||||
const fileTreeObject = await fileTreeToObject(dirPathArg, miniMatchFilter);
|
||||
let combinedString = '';
|
||||
for (const smartfile of fileTreeObject) {
|
||||
@@ -253,7 +291,10 @@ export const fileTreeToHash = async (dirPathArg: string, miniMatchFilter: string
|
||||
* @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) => {
|
||||
export const fileTreeToObject = async (
|
||||
dirPathArg: string,
|
||||
miniMatchFilter: string,
|
||||
) => {
|
||||
// handle absolute miniMatchFilter
|
||||
let dirPath: string;
|
||||
if (plugins.path.isAbsolute(miniMatchFilter)) {
|
||||
@@ -280,7 +321,7 @@ export const fileTreeToObject = async (dirPathArg: string, miniMatchFilter: stri
|
||||
contentBuffer: fileBuffer,
|
||||
base: dirPath,
|
||||
path: filePath,
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
return smartfileArray;
|
||||
@@ -290,7 +331,10 @@ export const fileTreeToObject = async (dirPathArg: string, miniMatchFilter: stri
|
||||
* 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[]> => {
|
||||
export const listFolders = async (
|
||||
pathArg: string,
|
||||
regexFilter?: RegExp,
|
||||
): Promise<string[]> => {
|
||||
return listFoldersSync(pathArg, regexFilter);
|
||||
};
|
||||
|
||||
@@ -298,9 +342,14 @@ export const listFolders = async (pathArg: string, regexFilter?: RegExp): Promis
|
||||
* 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[] => {
|
||||
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();
|
||||
return plugins.fsExtra
|
||||
.statSync(plugins.path.join(pathArg, file))
|
||||
.isDirectory();
|
||||
});
|
||||
if (regexFilter) {
|
||||
folderArray = folderArray.filter((fileItem) => {
|
||||
@@ -314,7 +363,10 @@ export const listFoldersSync = (pathArg: string, regexFilter?: RegExp): string[]
|
||||
* lists Files in a directory on local disk
|
||||
* @returns Promise
|
||||
*/
|
||||
export const listFiles = async (pathArg: string, regexFilter?: RegExp): Promise<string[]> => {
|
||||
export const listFiles = async (
|
||||
pathArg: string,
|
||||
regexFilter?: RegExp,
|
||||
): Promise<string[]> => {
|
||||
return listFilesSync(pathArg, regexFilter);
|
||||
};
|
||||
|
||||
@@ -322,7 +374,10 @@ export const listFiles = async (pathArg: string, regexFilter?: RegExp): Promise<
|
||||
* 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[] => {
|
||||
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();
|
||||
});
|
||||
@@ -338,7 +393,10 @@ export const listFilesSync = (pathArg: string, regexFilter?: RegExp): string[] =
|
||||
* 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[]> => {
|
||||
export const listAllItems = async (
|
||||
pathArg: string,
|
||||
regexFilter?: RegExp,
|
||||
): Promise<string[]> => {
|
||||
return listAllItemsSync(pathArg, regexFilter);
|
||||
};
|
||||
|
||||
@@ -347,7 +405,10 @@ export const listAllItems = async (pathArg: string, regexFilter?: RegExp): Promi
|
||||
* @returns an array with the folder names as strings
|
||||
* @executes SYNC
|
||||
*/
|
||||
export const listAllItemsSync = (pathArg: string, regexFilter?: RegExp): string[] => {
|
||||
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();
|
||||
});
|
||||
@@ -367,7 +428,7 @@ export const listAllItemsSync = (pathArg: string, regexFilter?: RegExp): string[
|
||||
export const listFileTree = async (
|
||||
dirPathArg: string,
|
||||
miniMatchFilter: string,
|
||||
absolutePathsBool: boolean = false
|
||||
absolutePathsBool: boolean = false,
|
||||
): Promise<string[]> => {
|
||||
// handle absolute miniMatchFilter
|
||||
let dirPath: string;
|
||||
@@ -400,11 +461,11 @@ export const listFileTree = async (
|
||||
const allFiles = new Set<string>();
|
||||
for (const pattern of patterns) {
|
||||
const files = await plugins.glob.glob(pattern, options);
|
||||
files.forEach(file => allFiles.add(file));
|
||||
files.forEach((file) => allFiles.add(file));
|
||||
}
|
||||
|
||||
let fileList = Array.from(allFiles).sort();
|
||||
|
||||
|
||||
if (absolutePathsBool) {
|
||||
fileList = fileList.map((filePath) => {
|
||||
return plugins.path.resolve(plugins.path.join(dirPath, filePath));
|
||||
@@ -429,7 +490,7 @@ export const listFileTree = async (
|
||||
*/
|
||||
export const waitForFileToBeReady = async (
|
||||
fileOrDirPathArg: string,
|
||||
timeoutMs: number = 60000
|
||||
timeoutMs: number = 60000,
|
||||
): Promise<void> => {
|
||||
const startTime = Date.now();
|
||||
|
||||
@@ -442,7 +503,7 @@ export const waitForFileToBeReady = async (
|
||||
while (true) {
|
||||
try {
|
||||
await plugins.smartpromise.fromCallback((cb) =>
|
||||
plugins.fs.access(pathToCheck, plugins.fs.constants.F_OK, cb)
|
||||
plugins.fs.access(pathToCheck, plugins.fs.constants.F_OK, cb),
|
||||
);
|
||||
return;
|
||||
} catch (err: any) {
|
||||
@@ -463,19 +524,23 @@ export const waitForFileToBeReady = async (
|
||||
* @param filePathArg The path of the file to check.
|
||||
* @returns A promise that resolves once the file stops changing.
|
||||
*/
|
||||
const waitForSingleFileToBeStable = async (filePathArg: string): Promise<void> => {
|
||||
const waitForSingleFileToBeStable = async (
|
||||
filePathArg: string,
|
||||
): Promise<void> => {
|
||||
let lastFileSize = -1;
|
||||
let fileIsStable = false;
|
||||
|
||||
// We'll create a helper for repeated stats-checking logic
|
||||
const checkFileStability = async () => {
|
||||
try {
|
||||
const stats = await plugins.smartpromise.fromCallback<plugins.fs.Stats>((cb) =>
|
||||
plugins.fs.stat(filePathArg, cb)
|
||||
const stats = await plugins.smartpromise.fromCallback<plugins.fs.Stats>(
|
||||
(cb) => plugins.fs.stat(filePathArg, cb),
|
||||
);
|
||||
if (stats.isDirectory()) {
|
||||
// If it unexpectedly turns out to be a directory here, throw
|
||||
throw new Error(`Expected a file but found a directory: ${filePathArg}`);
|
||||
throw new Error(
|
||||
`Expected a file but found a directory: ${filePathArg}`,
|
||||
);
|
||||
}
|
||||
if (stats.size === lastFileSize) {
|
||||
fileIsStable = true;
|
||||
@@ -495,17 +560,23 @@ export const waitForFileToBeReady = async (
|
||||
await ensurePathExists(filePathArg);
|
||||
|
||||
// Set up a watcher on the file itself
|
||||
const fileWatcher = plugins.fs.watch(filePathArg, { persistent: true }, async () => {
|
||||
if (!fileIsStable) {
|
||||
await checkFileStability();
|
||||
}
|
||||
});
|
||||
const fileWatcher = plugins.fs.watch(
|
||||
filePathArg,
|
||||
{ persistent: true },
|
||||
async () => {
|
||||
if (!fileIsStable) {
|
||||
await checkFileStability();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
try {
|
||||
// Poll until stable or timeout
|
||||
while (!fileIsStable) {
|
||||
if (Date.now() - startTime > timeoutMs) {
|
||||
throw new Error(`Timeout waiting for file to stabilize: ${filePathArg}`);
|
||||
throw new Error(
|
||||
`Timeout waiting for file to stabilize: ${filePathArg}`,
|
||||
);
|
||||
}
|
||||
await checkFileStability();
|
||||
if (!fileIsStable) {
|
||||
@@ -526,7 +597,7 @@ export const waitForFileToBeReady = async (
|
||||
try {
|
||||
await ensurePathExists(fileOrDirPathArg);
|
||||
return await plugins.smartpromise.fromCallback<plugins.fs.Stats>((cb) =>
|
||||
plugins.fs.stat(fileOrDirPathArg, cb)
|
||||
plugins.fs.stat(fileOrDirPathArg, cb),
|
||||
);
|
||||
} catch (err) {
|
||||
// If there's an error (including timeout), just rethrow
|
||||
@@ -546,14 +617,15 @@ export const waitForFileToBeReady = async (
|
||||
// Helper to find the first file in the directory if it exists
|
||||
const getFirstFileInDirectory = async (): Promise<string | null> => {
|
||||
const entries = await plugins.smartpromise.fromCallback<string[]>((cb) =>
|
||||
plugins.fs.readdir(dirPath, cb)
|
||||
plugins.fs.readdir(dirPath, cb),
|
||||
);
|
||||
// We only want actual files, not subdirectories
|
||||
for (const entry of entries) {
|
||||
const entryPath = plugins.path.join(dirPath, entry);
|
||||
const entryStats = await plugins.smartpromise.fromCallback<plugins.fs.Stats>((cb) =>
|
||||
plugins.fs.stat(entryPath, cb)
|
||||
);
|
||||
const entryStats =
|
||||
await plugins.smartpromise.fromCallback<plugins.fs.Stats>((cb) =>
|
||||
plugins.fs.stat(entryPath, cb),
|
||||
);
|
||||
if (entryStats.isFile()) {
|
||||
return entryPath;
|
||||
}
|
||||
@@ -570,7 +642,9 @@ export const waitForFileToBeReady = async (
|
||||
// We'll poll for the existence of a file in that directory
|
||||
while (!firstFilePath) {
|
||||
if (Date.now() - startTime > timeoutMs) {
|
||||
throw new Error(`Timeout waiting for a file to appear in directory: ${dirPath}`);
|
||||
throw new Error(
|
||||
`Timeout waiting for a file to appear in directory: ${dirPath}`,
|
||||
);
|
||||
}
|
||||
firstFilePath = await getFirstFileInDirectory();
|
||||
if (!firstFilePath) {
|
||||
@@ -597,7 +671,7 @@ export let toFs = async (
|
||||
filePathArg: string,
|
||||
optionsArg: {
|
||||
respectRelative?: boolean;
|
||||
} = {}
|
||||
} = {},
|
||||
) => {
|
||||
const done = plugins.smartpromise.defer();
|
||||
|
||||
@@ -627,7 +701,12 @@ export let toFs = async (
|
||||
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);
|
||||
plugins.fsExtra.writeFile(
|
||||
filePath,
|
||||
fileContent,
|
||||
{ encoding: fileEncoding },
|
||||
done.resolve,
|
||||
);
|
||||
return await done.promise;
|
||||
};
|
||||
|
||||
|
Reference in New Issue
Block a user