BREAKING CHANGE(SmartFileFactory): Refactor to in-memory file API and introduce SmartFileFactory; delegate filesystem operations to @push.rocks/smartfs; bump to 12.0.0
This commit is contained in:
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/smartfile',
|
||||
version: '11.2.7',
|
||||
description: 'Provides comprehensive tools for efficient file management in Node.js using TypeScript, including handling streams, virtual directories, and various file operations.'
|
||||
version: '13.0.0',
|
||||
description: 'High-level file representation classes (SmartFile, StreamFile, VirtualDirectory) for efficient in-memory file management in Node.js using TypeScript. Works seamlessly with @push.rocks/smartfs for filesystem operations.'
|
||||
}
|
||||
|
||||
224
ts/classes.smartfile.factory.ts
Normal file
224
ts/classes.smartfile.factory.ts
Normal file
@@ -0,0 +1,224 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import { SmartFile } from './classes.smartfile.js';
|
||||
import { StreamFile } from './classes.streamfile.js';
|
||||
import { VirtualDirectory } from './classes.virtualdirectory.js';
|
||||
|
||||
export class SmartFileFactory {
|
||||
private smartFs: any; // Will be typed as SmartFs once we import from @push.rocks/smartfs
|
||||
|
||||
constructor(smartFs: any) {
|
||||
this.smartFs = smartFs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a default factory using Node.js filesystem provider
|
||||
*/
|
||||
public static nodeFs(): SmartFileFactory {
|
||||
// Temporarily using a placeholder - will be replaced with actual SmartFs initialization
|
||||
// const smartFs = new SmartFs(new SmartFsProviderNode());
|
||||
const smartFs = null; // Placeholder
|
||||
return new SmartFileFactory(smartFs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the underlying SmartFs instance
|
||||
*/
|
||||
public getSmartFs(): any {
|
||||
return this.smartFs;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// SmartFile Factory Methods
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Creates a SmartFile from a file path on disk
|
||||
*/
|
||||
public async fromFilePath(
|
||||
filePath: string,
|
||||
baseArg: string = process.cwd()
|
||||
): Promise<SmartFile> {
|
||||
if (!this.smartFs) {
|
||||
throw new Error('No SmartFs instance available. Cannot read from filesystem without SmartFs.');
|
||||
}
|
||||
|
||||
filePath = plugins.path.resolve(filePath);
|
||||
const content = await this.smartFs.file(filePath).read();
|
||||
const fileBuffer = Buffer.from(content);
|
||||
|
||||
return new SmartFile({
|
||||
contentBuffer: fileBuffer,
|
||||
base: baseArg,
|
||||
path: plugins.path.relative(baseArg, filePath),
|
||||
}, this.smartFs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a SmartFile from a URL
|
||||
*/
|
||||
public async fromUrl(urlArg: string): Promise<SmartFile> {
|
||||
const response = await plugins.smartrequest.SmartRequest.create()
|
||||
.url(urlArg)
|
||||
.accept('binary')
|
||||
.get();
|
||||
const buffer = Buffer.from(await response.arrayBuffer());
|
||||
|
||||
return new SmartFile({
|
||||
contentBuffer: buffer,
|
||||
base: process.cwd(),
|
||||
path: urlArg,
|
||||
}, this.smartFs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a SmartFile from a Buffer
|
||||
*/
|
||||
public fromBuffer(
|
||||
filePath: string,
|
||||
contentBufferArg: Buffer,
|
||||
baseArg: string = process.cwd()
|
||||
): SmartFile {
|
||||
// Use filePath as-is if it's already relative, otherwise compute relative path
|
||||
const relativePath = plugins.path.isAbsolute(filePath)
|
||||
? plugins.path.relative(baseArg, filePath)
|
||||
: filePath;
|
||||
|
||||
return new SmartFile({
|
||||
contentBuffer: contentBufferArg,
|
||||
base: baseArg,
|
||||
path: relativePath,
|
||||
}, this.smartFs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a SmartFile from a string
|
||||
*/
|
||||
public fromString(
|
||||
filePath: string,
|
||||
contentStringArg: string,
|
||||
encodingArg: 'utf8' | 'binary' = 'utf8',
|
||||
baseArg: string = process.cwd()
|
||||
): SmartFile {
|
||||
// Use filePath as-is if it's already relative, otherwise compute relative path
|
||||
const relativePath = plugins.path.isAbsolute(filePath)
|
||||
? plugins.path.relative(baseArg, filePath)
|
||||
: filePath;
|
||||
|
||||
return new SmartFile({
|
||||
contentBuffer: Buffer.from(contentStringArg, encodingArg),
|
||||
base: baseArg,
|
||||
path: relativePath,
|
||||
}, this.smartFs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a SmartFile from a stream
|
||||
*/
|
||||
public async fromStream(
|
||||
stream: plugins.stream.Readable,
|
||||
filePath: string,
|
||||
baseArg: string = process.cwd()
|
||||
): Promise<SmartFile> {
|
||||
return new Promise<SmartFile>((resolve, reject) => {
|
||||
const chunks: Buffer[] = [];
|
||||
stream.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
|
||||
stream.on('error', (error) => reject(error));
|
||||
stream.on('end', () => {
|
||||
const contentBuffer = Buffer.concat(chunks);
|
||||
const smartfile = new SmartFile({
|
||||
contentBuffer: contentBuffer,
|
||||
base: baseArg,
|
||||
path: plugins.path.relative(baseArg, filePath),
|
||||
}, this.smartFs);
|
||||
resolve(smartfile);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a SmartFile from folded JSON
|
||||
*/
|
||||
public async fromFoldedJson(foldedJsonArg: string): Promise<SmartFile> {
|
||||
const parsed = plugins.smartjson.parse(foldedJsonArg);
|
||||
return new SmartFile(parsed, this.smartFs);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// StreamFile Factory Methods
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Creates a StreamFile from a file path
|
||||
*/
|
||||
public async streamFromPath(filePath: string): Promise<StreamFile> {
|
||||
return StreamFile.fromPath(filePath, this.smartFs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a StreamFile from a URL
|
||||
*/
|
||||
public async streamFromUrl(url: string): Promise<StreamFile> {
|
||||
return StreamFile.fromUrl(url, this.smartFs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a StreamFile from a Buffer
|
||||
*/
|
||||
public streamFromBuffer(buffer: Buffer, relativeFilePath?: string): StreamFile {
|
||||
return StreamFile.fromBuffer(buffer, relativeFilePath, this.smartFs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a StreamFile from a Node.js Readable stream
|
||||
*/
|
||||
public streamFromStream(
|
||||
stream: plugins.stream.Readable,
|
||||
relativeFilePath?: string,
|
||||
multiUse: boolean = false
|
||||
): StreamFile {
|
||||
return StreamFile.fromStream(stream, relativeFilePath, multiUse, this.smartFs);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// VirtualDirectory Factory Methods
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Creates a VirtualDirectory from a filesystem directory path
|
||||
*/
|
||||
public async virtualDirectoryFromPath(pathArg: string): Promise<VirtualDirectory> {
|
||||
return VirtualDirectory.fromFsDirPath(pathArg, this.smartFs, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an empty VirtualDirectory
|
||||
*/
|
||||
public virtualDirectoryEmpty(): VirtualDirectory {
|
||||
return new VirtualDirectory(this.smartFs, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a VirtualDirectory from an array of SmartFiles
|
||||
*/
|
||||
public virtualDirectoryFromFileArray(files: SmartFile[]): VirtualDirectory {
|
||||
const vdir = new VirtualDirectory(this.smartFs, this);
|
||||
vdir.addSmartfiles(files);
|
||||
return vdir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a VirtualDirectory from a transferable object
|
||||
*/
|
||||
public async virtualDirectoryFromTransferable(
|
||||
virtualDirTransferableObjectArg: plugins.smartfileInterfaces.VirtualDirTransferableObject
|
||||
): Promise<VirtualDirectory> {
|
||||
const newVirtualDir = new VirtualDirectory(this.smartFs, this);
|
||||
for (const fileArg of virtualDirTransferableObjectArg.files) {
|
||||
const smartFile = SmartFile.enfoldFromJson(fileArg) as SmartFile;
|
||||
// Update the smartFs reference
|
||||
(smartFile as any).smartFs = this.smartFs;
|
||||
newVirtualDir.addSmartfiles([smartFile]);
|
||||
}
|
||||
return newVirtualDir;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import * as fs from './fs.js';
|
||||
import * as memory from './memory.js';
|
||||
|
||||
export interface ISmartfileConstructorOptions {
|
||||
path: string;
|
||||
@@ -10,103 +8,17 @@ export interface ISmartfileConstructorOptions {
|
||||
|
||||
/**
|
||||
* an vinyl file compatible in memory file class
|
||||
* Use SmartFileFactory to create instances of this class
|
||||
*/
|
||||
export class SmartFile extends plugins.smartjson.Smartjson {
|
||||
// ======
|
||||
// STATIC
|
||||
// ======
|
||||
|
||||
/**
|
||||
* creates a Smartfile from a filePath
|
||||
* @param filePath
|
||||
*/
|
||||
public static async fromFilePath(
|
||||
filePath: string,
|
||||
baseArg: string = process.cwd(),
|
||||
) {
|
||||
filePath = plugins.path.resolve(filePath);
|
||||
const fileBuffer = fs.toBufferSync(filePath);
|
||||
const smartfile = new SmartFile({
|
||||
contentBuffer: fileBuffer,
|
||||
base: baseArg,
|
||||
path: plugins.path.relative(baseArg, filePath),
|
||||
});
|
||||
return smartfile;
|
||||
}
|
||||
|
||||
public static async fromBuffer(
|
||||
filePath: string,
|
||||
contentBufferArg: Buffer,
|
||||
baseArg: string = process.cwd(),
|
||||
) {
|
||||
const smartfile = new SmartFile({
|
||||
contentBuffer: contentBufferArg,
|
||||
base: baseArg,
|
||||
path: plugins.path.relative(baseArg, filePath),
|
||||
});
|
||||
|
||||
return smartfile;
|
||||
}
|
||||
|
||||
public static async fromString(
|
||||
filePath: string,
|
||||
contentStringArg: string,
|
||||
encodingArg: 'utf8' | 'binary',
|
||||
baseArg = process.cwd(),
|
||||
) {
|
||||
const smartfile = new SmartFile({
|
||||
contentBuffer: Buffer.from(contentStringArg, encodingArg),
|
||||
base: baseArg,
|
||||
path: plugins.path.relative(baseArg, filePath),
|
||||
});
|
||||
|
||||
return smartfile;
|
||||
}
|
||||
|
||||
public static async fromFoldedJson(foldedJsonArg: string) {
|
||||
return new SmartFile(plugins.smartjson.parse(foldedJsonArg));
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a Smartfile from a ReadableStream
|
||||
* @param stream a readable stream that provides file content
|
||||
* @param filePath the file path to associate with the content
|
||||
* @param baseArg the base path to use for the file
|
||||
*/
|
||||
public static async fromStream(
|
||||
stream: plugins.stream.Readable,
|
||||
filePath: string,
|
||||
baseArg: string = process.cwd(),
|
||||
): Promise<SmartFile> {
|
||||
return new Promise<SmartFile>((resolve, reject) => {
|
||||
const chunks: Buffer[] = [];
|
||||
stream.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
|
||||
stream.on('error', (error) => reject(error));
|
||||
stream.on('end', () => {
|
||||
const contentBuffer = Buffer.concat(chunks);
|
||||
const smartfile = new SmartFile({
|
||||
contentBuffer: contentBuffer,
|
||||
base: baseArg,
|
||||
path: plugins.path.relative(baseArg, filePath),
|
||||
});
|
||||
resolve(smartfile);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public static async fromUrl(urlArg: string) {
|
||||
const response = await plugins.smartrequest.SmartRequest.create()
|
||||
.url(urlArg)
|
||||
.accept('binary')
|
||||
.get();
|
||||
const buffer = Buffer.from(await response.arrayBuffer());
|
||||
const smartfile = await SmartFile.fromBuffer(urlArg, buffer);
|
||||
return smartfile;
|
||||
}
|
||||
|
||||
// ========
|
||||
// INSTANCE
|
||||
// ========
|
||||
|
||||
/**
|
||||
* Reference to the SmartFs instance for filesystem operations
|
||||
*/
|
||||
private smartFs?: any;
|
||||
/**
|
||||
* the relative path of the file
|
||||
*/
|
||||
@@ -149,9 +61,10 @@ export class SmartFile extends plugins.smartjson.Smartjson {
|
||||
/**
|
||||
* the constructor of Smartfile
|
||||
* @param optionsArg
|
||||
* @param smartFs optional SmartFs instance for filesystem operations
|
||||
*/
|
||||
|
||||
constructor(optionsArg: ISmartfileConstructorOptions) {
|
||||
constructor(optionsArg: ISmartfileConstructorOptions, smartFs?: any) {
|
||||
super();
|
||||
if (optionsArg.contentBuffer) {
|
||||
this.contentBuffer = optionsArg.contentBuffer;
|
||||
@@ -160,6 +73,7 @@ export class SmartFile extends plugins.smartjson.Smartjson {
|
||||
}
|
||||
this.path = optionsArg.path;
|
||||
this.base = optionsArg.base;
|
||||
this.smartFs = smartFs;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -177,14 +91,19 @@ export class SmartFile extends plugins.smartjson.Smartjson {
|
||||
* write file to disk at its original location
|
||||
* Behaviours:
|
||||
* - no argument write to exactly where the file was picked up
|
||||
* - Requires SmartFs instance (create via SmartFileFactory)
|
||||
*/
|
||||
public async write() {
|
||||
let writePath = plugins.smartpath.transform.makeAbsolute(
|
||||
if (!this.smartFs) {
|
||||
throw new Error('No SmartFs instance available. Create SmartFile through SmartFileFactory.');
|
||||
}
|
||||
|
||||
const writePath = plugins.smartpath.transform.makeAbsolute(
|
||||
this.path,
|
||||
this.base,
|
||||
);
|
||||
console.log(`writing to ${writePath}`);
|
||||
await memory.toFs(this.contentBuffer, writePath);
|
||||
await this.smartFs.file(writePath).write(this.contentBuffer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -193,10 +112,15 @@ export class SmartFile extends plugins.smartjson.Smartjson {
|
||||
* @param filePathArg
|
||||
*/
|
||||
public async writeToDiskAtPath(filePathArg: string) {
|
||||
if (!this.smartFs) {
|
||||
throw new Error('No SmartFs instance available. Create SmartFile through SmartFileFactory.');
|
||||
}
|
||||
|
||||
if (!plugins.path.isAbsolute(filePathArg)) {
|
||||
filePathArg = plugins.path.join(process.cwd(), filePathArg);
|
||||
}
|
||||
await memory.toFs(this.contentBuffer, filePathArg);
|
||||
|
||||
await this.smartFs.file(filePathArg).write(this.contentBuffer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -205,9 +129,13 @@ export class SmartFile extends plugins.smartjson.Smartjson {
|
||||
* @returns
|
||||
*/
|
||||
public async writeToDir(dirPathArg: string) {
|
||||
if (!this.smartFs) {
|
||||
throw new Error('No SmartFs instance available. Create SmartFile through SmartFileFactory.');
|
||||
}
|
||||
|
||||
dirPathArg = plugins.smartpath.transform.toAbsolute(dirPathArg) as string;
|
||||
const filePath = plugins.path.join(dirPathArg, this.path);
|
||||
await memory.toFs(this.contentBuffer, filePath);
|
||||
await this.smartFs.file(filePath).write(this.contentBuffer);
|
||||
return filePath;
|
||||
}
|
||||
|
||||
@@ -215,16 +143,25 @@ export class SmartFile extends plugins.smartjson.Smartjson {
|
||||
* read file from disk
|
||||
*/
|
||||
public async read() {
|
||||
this.contentBuffer = await fs.toBuffer(
|
||||
plugins.path.join(this.base, this.path),
|
||||
);
|
||||
if (!this.smartFs) {
|
||||
throw new Error('No SmartFs instance available. Create SmartFile through SmartFileFactory.');
|
||||
}
|
||||
|
||||
const filePath = plugins.path.join(this.base, this.path);
|
||||
const content = await this.smartFs.file(filePath).read();
|
||||
this.contentBuffer = Buffer.from(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* deletes the file from disk at its original location
|
||||
*/
|
||||
public async delete() {
|
||||
await fs.remove(plugins.path.join(this.base, this.path));
|
||||
if (!this.smartFs) {
|
||||
throw new Error('No SmartFs instance available. Create SmartFile through SmartFileFactory.');
|
||||
}
|
||||
|
||||
const filePath = plugins.path.join(this.base, this.path);
|
||||
await this.smartFs.file(filePath).delete();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -380,4 +317,18 @@ export class SmartFile extends plugins.smartjson.Smartjson {
|
||||
public async getSize(): Promise<number> {
|
||||
return this.contentBuffer.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse content as string with specified encoding
|
||||
*/
|
||||
public parseContentAsString(encodingArg: BufferEncoding = 'utf8'): string {
|
||||
return this.contentBuffer.toString(encodingArg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse content as buffer
|
||||
*/
|
||||
public parseContentAsBuffer(): Buffer {
|
||||
return this.contentBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import * as smartfileFs from './fs.js';
|
||||
import * as smartfileFsStream from './fsstream.js';
|
||||
import { Readable } from 'stream';
|
||||
|
||||
type TStreamSource = (streamFile: StreamFile) => Promise<Readable | ReadableStream>;
|
||||
@@ -8,30 +6,36 @@ type TStreamSource = (streamFile: StreamFile) => Promise<Readable | ReadableStre
|
||||
/**
|
||||
* The StreamFile class represents a file as a stream.
|
||||
* It allows creating streams from a file path, a URL, or a buffer.
|
||||
* Use SmartFileFactory to create instances of this class.
|
||||
*/
|
||||
export class StreamFile {
|
||||
// STATIC
|
||||
|
||||
public static async fromPath(filePath: string): Promise<StreamFile> {
|
||||
const streamSource: TStreamSource = async (streamFileArg) =>
|
||||
smartfileFsStream.createReadStream(filePath);
|
||||
const streamFile = new StreamFile(streamSource, filePath);
|
||||
public static async fromPath(filePath: string, smartFs?: any): Promise<StreamFile> {
|
||||
if (!smartFs) {
|
||||
throw new Error('No SmartFs instance available. Create StreamFile through SmartFileFactory.');
|
||||
}
|
||||
|
||||
const streamSource: TStreamSource = async (streamFileArg) => {
|
||||
return await streamFileArg.smartFs.file(filePath).readStream();
|
||||
};
|
||||
const streamFile = new StreamFile(streamSource, filePath, smartFs);
|
||||
streamFile.multiUse = true;
|
||||
streamFile.byteLengthComputeFunction = async () => {
|
||||
const stats = await smartfileFs.stat(filePath);
|
||||
const stats = await smartFs.file(filePath).stat();
|
||||
return stats.size;
|
||||
};
|
||||
return streamFile;
|
||||
}
|
||||
|
||||
public static async fromUrl(url: string): Promise<StreamFile> {
|
||||
public static async fromUrl(url: string, smartFs?: any): Promise<StreamFile> {
|
||||
const streamSource: TStreamSource = async (streamFileArg) => {
|
||||
const response = await plugins.smartrequest.SmartRequest.create()
|
||||
.url(url)
|
||||
.get();
|
||||
return response.stream();
|
||||
};
|
||||
const streamFile = new StreamFile(streamSource);
|
||||
const streamFile = new StreamFile(streamSource, undefined, smartFs);
|
||||
streamFile.multiUse = true;
|
||||
streamFile.byteLengthComputeFunction = async () => {
|
||||
const response = await plugins.smartrequest.SmartRequest.create()
|
||||
@@ -47,6 +51,7 @@ export class StreamFile {
|
||||
public static fromBuffer(
|
||||
buffer: Buffer,
|
||||
relativeFilePath?: string,
|
||||
smartFs?: any
|
||||
): StreamFile {
|
||||
const streamSource: TStreamSource = async (streamFileArg) => {
|
||||
const stream = new Readable();
|
||||
@@ -54,7 +59,7 @@ export class StreamFile {
|
||||
stream.push(null); // End of stream
|
||||
return stream;
|
||||
};
|
||||
const streamFile = new StreamFile(streamSource, relativeFilePath);
|
||||
const streamFile = new StreamFile(streamSource, relativeFilePath, smartFs);
|
||||
streamFile.multiUse = true;
|
||||
streamFile.byteLengthComputeFunction = async () => buffer.length;
|
||||
return streamFile;
|
||||
@@ -65,12 +70,14 @@ export class StreamFile {
|
||||
* @param stream A Node.js Readable stream.
|
||||
* @param relativeFilePath Optional file path for the stream.
|
||||
* @param multiUse If true, the stream can be read multiple times, caching its content.
|
||||
* @param smartFs Optional SmartFs instance for filesystem operations
|
||||
* @returns A StreamFile instance.
|
||||
*/
|
||||
public static fromStream(
|
||||
stream: Readable,
|
||||
relativeFilePath?: string,
|
||||
multiUse: boolean = false,
|
||||
smartFs?: any
|
||||
): StreamFile {
|
||||
const streamSource: TStreamSource = (streamFileArg) => {
|
||||
if (streamFileArg.multiUse) {
|
||||
@@ -84,7 +91,7 @@ export class StreamFile {
|
||||
}
|
||||
};
|
||||
|
||||
const streamFile = new StreamFile(streamSource, relativeFilePath);
|
||||
const streamFile = new StreamFile(streamSource, relativeFilePath, smartFs);
|
||||
streamFile.multiUse = multiUse;
|
||||
|
||||
// If multi-use is enabled, cache the stream when it's first read
|
||||
@@ -106,6 +113,7 @@ export class StreamFile {
|
||||
// INSTANCE
|
||||
relativeFilePath?: string;
|
||||
private streamSource: TStreamSource;
|
||||
private smartFs?: any;
|
||||
|
||||
// enable stream based multi use
|
||||
private cachedStreamBuffer?: Buffer;
|
||||
@@ -113,9 +121,10 @@ export class StreamFile {
|
||||
public used: boolean = false;
|
||||
public byteLengthComputeFunction: () => Promise<number>;
|
||||
|
||||
private constructor(streamSource: TStreamSource, relativeFilePath?: string) {
|
||||
private constructor(streamSource: TStreamSource, relativeFilePath?: string, smartFs?: any) {
|
||||
this.streamSource = streamSource;
|
||||
this.relativeFilePath = relativeFilePath;
|
||||
this.smartFs = smartFs;
|
||||
}
|
||||
|
||||
// METHODS
|
||||
@@ -148,9 +157,13 @@ export class StreamFile {
|
||||
* @param filePathArg The file path where the stream should be written.
|
||||
*/
|
||||
public async writeToDisk(filePathArg: string): Promise<void> {
|
||||
if (!this.smartFs) {
|
||||
throw new Error('No SmartFs instance available. Create StreamFile through SmartFileFactory.');
|
||||
}
|
||||
|
||||
this.checkMultiUse();
|
||||
const readStream = await this.createReadStream();
|
||||
const writeStream = smartfileFsStream.createWriteStream(filePathArg);
|
||||
const writeStream = await this.smartFs.file(filePathArg).writeStream();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
readStream.pipe(writeStream);
|
||||
@@ -161,9 +174,14 @@ export class StreamFile {
|
||||
}
|
||||
|
||||
public async writeToDir(dirPathArg: string) {
|
||||
if (!this.smartFs) {
|
||||
throw new Error('No SmartFs instance available. Create StreamFile through SmartFileFactory.');
|
||||
}
|
||||
|
||||
this.checkMultiUse();
|
||||
const filePath = plugins.path.join(dirPathArg, this.relativeFilePath);
|
||||
await smartfileFs.ensureDir(plugins.path.parse(filePath).dir);
|
||||
const dirPath = plugins.path.parse(filePath).dir;
|
||||
await this.smartFs.directory(dirPath).create({ recursive: true });
|
||||
return this.writeToDisk(filePath);
|
||||
}
|
||||
|
||||
@@ -196,4 +214,17 @@ export class StreamFile {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the StreamFile to a SmartFile by loading content into memory
|
||||
*/
|
||||
public async toSmartFile(): Promise<any> {
|
||||
const { SmartFile } = await import('./classes.smartfile.js');
|
||||
const buffer = await this.getContentAsBuffer();
|
||||
return new SmartFile({
|
||||
path: this.relativeFilePath || 'stream',
|
||||
contentBuffer: buffer,
|
||||
base: process.cwd()
|
||||
}, this.smartFs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { SmartFile } from './classes.smartfile.js';
|
||||
import * as plugins from './plugins.js';
|
||||
import * as fs from './fs.js';
|
||||
|
||||
export interface IVirtualDirectoryConstructorOptions {
|
||||
mode: '';
|
||||
@@ -8,46 +7,149 @@ export interface IVirtualDirectoryConstructorOptions {
|
||||
|
||||
/**
|
||||
* a virtual directory exposes a fs api
|
||||
* Use SmartFileFactory to create instances of this class
|
||||
*/
|
||||
export class VirtualDirectory {
|
||||
consstructor(options = {}) {}
|
||||
|
||||
// STATIC
|
||||
public static async fromFsDirPath(
|
||||
pathArg: string,
|
||||
smartFs?: any,
|
||||
factory?: any
|
||||
): Promise<VirtualDirectory> {
|
||||
const newVirtualDir = new VirtualDirectory();
|
||||
newVirtualDir.addSmartfiles(await fs.fileTreeToObject(pathArg, '**/*'));
|
||||
if (!smartFs || !factory) {
|
||||
throw new Error('No SmartFs/Factory instance available. Create VirtualDirectory through SmartFileFactory.');
|
||||
}
|
||||
|
||||
const newVirtualDir = new VirtualDirectory(smartFs, factory);
|
||||
|
||||
// Use smartFs to list directory and factory to create SmartFiles
|
||||
const entries = await smartFs.directory(pathArg).list({ recursive: true });
|
||||
const smartfiles = await Promise.all(
|
||||
entries
|
||||
.filter((entry: any) => entry.isFile)
|
||||
.map((entry: any) => factory.fromFilePath(entry.path, pathArg))
|
||||
);
|
||||
newVirtualDir.addSmartfiles(smartfiles);
|
||||
|
||||
return newVirtualDir;
|
||||
}
|
||||
|
||||
public static async fromVirtualDirTransferableObject(
|
||||
virtualDirTransferableObjectArg: plugins.smartfileInterfaces.VirtualDirTransferableObject,
|
||||
smartFs?: any,
|
||||
factory?: any
|
||||
): Promise<VirtualDirectory> {
|
||||
const newVirtualDir = new VirtualDirectory();
|
||||
const newVirtualDir = new VirtualDirectory(smartFs, factory);
|
||||
for (const fileArg of virtualDirTransferableObjectArg.files) {
|
||||
newVirtualDir.addSmartfiles([
|
||||
SmartFile.enfoldFromJson(fileArg) as SmartFile,
|
||||
]);
|
||||
const smartFile = SmartFile.enfoldFromJson(fileArg) as SmartFile;
|
||||
// Update smartFs reference if available
|
||||
if (smartFs) {
|
||||
(smartFile as any).smartFs = smartFs;
|
||||
}
|
||||
newVirtualDir.addSmartfiles([smartFile]);
|
||||
}
|
||||
return newVirtualDir;
|
||||
}
|
||||
|
||||
public static fromFileArray(files: SmartFile[], smartFs?: any, factory?: any): VirtualDirectory {
|
||||
const vdir = new VirtualDirectory(smartFs, factory);
|
||||
vdir.addSmartfiles(files);
|
||||
return vdir;
|
||||
}
|
||||
|
||||
public static empty(smartFs?: any, factory?: any): VirtualDirectory {
|
||||
return new VirtualDirectory(smartFs, factory);
|
||||
}
|
||||
|
||||
// INSTANCE
|
||||
public smartfileArray: SmartFile[] = [];
|
||||
private smartFs?: any;
|
||||
private factory?: any;
|
||||
|
||||
constructor() {}
|
||||
constructor(smartFs?: any, factory?: any) {
|
||||
this.smartFs = smartFs;
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Collection Mutations
|
||||
// ============================================
|
||||
|
||||
public addSmartfiles(smartfileArrayArg: SmartFile[]) {
|
||||
this.smartfileArray = this.smartfileArray.concat(smartfileArrayArg);
|
||||
}
|
||||
|
||||
public async getFileByPath(pathArg: string) {
|
||||
for (const smartfile of this.smartfileArray) {
|
||||
if (smartfile.path === pathArg) {
|
||||
return smartfile;
|
||||
public addSmartfile(smartfileArg: SmartFile): void {
|
||||
this.smartfileArray.push(smartfileArg);
|
||||
}
|
||||
|
||||
public removeByPath(pathArg: string): boolean {
|
||||
const initialLength = this.smartfileArray.length;
|
||||
this.smartfileArray = this.smartfileArray.filter(f => f.path !== pathArg);
|
||||
return this.smartfileArray.length < initialLength;
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this.smartfileArray = [];
|
||||
}
|
||||
|
||||
public merge(otherVDir: VirtualDirectory): void {
|
||||
this.addSmartfiles(otherVDir.smartfileArray);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Collection Queries
|
||||
// ============================================
|
||||
|
||||
public exists(pathArg: string): boolean {
|
||||
return this.smartfileArray.some(f => f.path === pathArg);
|
||||
}
|
||||
|
||||
public has(pathArg: string): boolean {
|
||||
return this.exists(pathArg);
|
||||
}
|
||||
|
||||
public async getFileByPath(pathArg: string): Promise<SmartFile | undefined> {
|
||||
return this.smartfileArray.find(f => f.path === pathArg);
|
||||
}
|
||||
|
||||
public listFiles(): SmartFile[] {
|
||||
return [...this.smartfileArray];
|
||||
}
|
||||
|
||||
public listDirectories(): string[] {
|
||||
const dirs = new Set<string>();
|
||||
for (const file of this.smartfileArray) {
|
||||
const dir = plugins.path.dirname(file.path);
|
||||
if (dir !== '.') {
|
||||
dirs.add(dir);
|
||||
}
|
||||
}
|
||||
return Array.from(dirs).sort();
|
||||
}
|
||||
|
||||
public filter(predicate: (file: SmartFile) => boolean): VirtualDirectory {
|
||||
const newVDir = new VirtualDirectory(this.smartFs, this.factory);
|
||||
newVDir.addSmartfiles(this.smartfileArray.filter(predicate));
|
||||
return newVDir;
|
||||
}
|
||||
|
||||
public map(fn: (file: SmartFile) => SmartFile): VirtualDirectory {
|
||||
const newVDir = new VirtualDirectory(this.smartFs, this.factory);
|
||||
newVDir.addSmartfiles(this.smartfileArray.map(fn));
|
||||
return newVDir;
|
||||
}
|
||||
|
||||
public find(predicate: (file: SmartFile) => boolean): SmartFile | undefined {
|
||||
return this.smartfileArray.find(predicate);
|
||||
}
|
||||
|
||||
public size(): number {
|
||||
return this.smartfileArray.length;
|
||||
}
|
||||
|
||||
public isEmpty(): boolean {
|
||||
return this.smartfileArray.length === 0;
|
||||
}
|
||||
|
||||
public async toVirtualDirTransferableObject(): Promise<plugins.smartfileInterfaces.VirtualDirTransferableObject> {
|
||||
@@ -69,7 +171,7 @@ export class VirtualDirectory {
|
||||
}
|
||||
|
||||
public async shiftToSubdirectory(subDir: string): Promise<VirtualDirectory> {
|
||||
const newVirtualDir = new VirtualDirectory();
|
||||
const newVirtualDir = new VirtualDirectory(this.smartFs, this.factory);
|
||||
for (const file of this.smartfileArray) {
|
||||
if (file.path.startsWith(subDir)) {
|
||||
const adjustedFilePath = plugins.path.relative(subDir, file.path);
|
||||
@@ -80,6 +182,13 @@ export class VirtualDirectory {
|
||||
return newVirtualDir;
|
||||
}
|
||||
|
||||
public async loadFromDisk(dirArg: string): Promise<void> {
|
||||
// Load from disk, replacing current collection
|
||||
this.clear();
|
||||
const loaded = await VirtualDirectory.fromFsDirPath(dirArg, this.smartFs, this.factory);
|
||||
this.addSmartfiles(loaded.smartfileArray);
|
||||
}
|
||||
|
||||
public async addVirtualDirectory(
|
||||
virtualDir: VirtualDirectory,
|
||||
newRoot: string,
|
||||
|
||||
715
ts/fs.ts
715
ts/fs.ts
@@ -1,715 +0,0 @@
|
||||
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 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)
|
||||
) {
|
||||
await remove(toArg);
|
||||
}
|
||||
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)
|
||||
) {
|
||||
removeSync(toArg);
|
||||
}
|
||||
return plugins.fsExtra.copySync(
|
||||
fromArg,
|
||||
toArg,
|
||||
optionsArg as plugins.fsExtra.CopyOptionsSync,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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,
|
||||
};
|
||||
|
||||
// Fix inconsistent **/* glob behavior across systems
|
||||
// Some glob implementations don't include root-level files when using **/*
|
||||
// To ensure consistent behavior, we expand **/* patterns to include both root and nested files
|
||||
let patterns: string[];
|
||||
if (miniMatchFilter.startsWith('**/')) {
|
||||
// Extract the part after **/ (e.g., "*.ts" from "**/*.ts")
|
||||
const rootPattern = miniMatchFilter.substring(3);
|
||||
// Use both the root pattern and the original pattern to ensure we catch everything
|
||||
patterns = [rootPattern, miniMatchFilter];
|
||||
} else {
|
||||
patterns = [miniMatchFilter];
|
||||
}
|
||||
|
||||
// Collect results from all patterns
|
||||
const allFiles = new Set<string>();
|
||||
for (const pattern of patterns) {
|
||||
const files = await plugins.glob.glob(pattern, options);
|
||||
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));
|
||||
});
|
||||
}
|
||||
|
||||
return fileList;
|
||||
};
|
||||
|
||||
/**
|
||||
* Watches for file stability before resolving the promise.
|
||||
* Ensures that the directory/file exists before setting up the watcher.
|
||||
*
|
||||
* **New behavior**: If the given path is a directory, this function will:
|
||||
* 1. Wait for that directory to exist (creating a timeout if needed).
|
||||
* 2. Watch the directory until at least one file appears.
|
||||
* 3. Then wait for the first file in the directory to stabilize before resolving.
|
||||
*
|
||||
* @param fileOrDirPathArg The path of the file or directory to monitor.
|
||||
* @param timeoutMs The maximum time to wait for the file to stabilize (in milliseconds). Default is 60 seconds.
|
||||
* @returns A promise that resolves when the target is stable or rejects on timeout/error.
|
||||
*/
|
||||
export const waitForFileToBeReady = async (
|
||||
fileOrDirPathArg: string,
|
||||
timeoutMs: number = 60000,
|
||||
): Promise<void> => {
|
||||
const startTime = Date.now();
|
||||
|
||||
/**
|
||||
* Ensure that a path (file or directory) exists. If it doesn't yet exist,
|
||||
* wait until it does (or time out).
|
||||
* @param pathToCheck The file or directory path to check.
|
||||
*/
|
||||
const ensurePathExists = async (pathToCheck: string): Promise<void> => {
|
||||
while (true) {
|
||||
try {
|
||||
await plugins.smartpromise.fromCallback((cb) =>
|
||||
plugins.fs.access(pathToCheck, plugins.fs.constants.F_OK, cb),
|
||||
);
|
||||
return;
|
||||
} catch (err: any) {
|
||||
if (err.code !== 'ENOENT') {
|
||||
throw err; // Propagate unexpected errors
|
||||
}
|
||||
if (Date.now() - startTime > timeoutMs) {
|
||||
throw new Error(`Timeout waiting for path to exist: ${pathToCheck}`);
|
||||
}
|
||||
await plugins.smartdelay.delayFor(500);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if a file (not directory) is stable by comparing sizes
|
||||
* across successive checks.
|
||||
* @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> => {
|
||||
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),
|
||||
);
|
||||
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}`,
|
||||
);
|
||||
}
|
||||
if (stats.size === lastFileSize) {
|
||||
fileIsStable = true;
|
||||
} else {
|
||||
lastFileSize = stats.size;
|
||||
fileIsStable = false;
|
||||
}
|
||||
} catch (err: any) {
|
||||
// Ignore only if file not found
|
||||
if (err.code !== 'ENOENT') {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Ensure file exists first
|
||||
await ensurePathExists(filePathArg);
|
||||
|
||||
// Set up a watcher on the file itself
|
||||
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}`,
|
||||
);
|
||||
}
|
||||
await checkFileStability();
|
||||
if (!fileIsStable) {
|
||||
await plugins.smartdelay.delayFor(1000);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
fileWatcher.close();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Main logic: check if we have a directory or file at fileOrDirPathArg.
|
||||
* If directory, wait for first file in the directory to appear and stabilize.
|
||||
* If file, do the old single-file wait logic.
|
||||
*/
|
||||
const statsForGivenPath = await (async () => {
|
||||
try {
|
||||
await ensurePathExists(fileOrDirPathArg);
|
||||
return await plugins.smartpromise.fromCallback<plugins.fs.Stats>((cb) =>
|
||||
plugins.fs.stat(fileOrDirPathArg, cb),
|
||||
);
|
||||
} catch (err) {
|
||||
// If there's an error (including timeout), just rethrow
|
||||
throw err;
|
||||
}
|
||||
})();
|
||||
|
||||
if (!statsForGivenPath.isDirectory()) {
|
||||
// It's a file – just do the single-file stability wait
|
||||
await waitForSingleFileToBeStable(fileOrDirPathArg);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, it's a directory. Wait for the first file inside to appear and be stable
|
||||
const dirPath = fileOrDirPathArg;
|
||||
|
||||
// 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),
|
||||
);
|
||||
// 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),
|
||||
);
|
||||
if (entryStats.isFile()) {
|
||||
return entryPath;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// Wait for a file to appear in this directory
|
||||
let firstFilePath = await getFirstFileInDirectory();
|
||||
if (!firstFilePath) {
|
||||
// Set up a watcher on the directory to see if a file appears
|
||||
const directoryWatcher = plugins.fs.watch(dirPath, { persistent: true });
|
||||
try {
|
||||
// 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}`,
|
||||
);
|
||||
}
|
||||
firstFilePath = await getFirstFileInDirectory();
|
||||
if (!firstFilePath) {
|
||||
await plugins.smartdelay.delayFor(1000);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
directoryWatcher.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we have a file path, wait for that file to stabilize
|
||||
await waitForSingleFileToBeStable(firstFilePath);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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);
|
||||
};
|
||||
215
ts/fsstream.ts
215
ts/fsstream.ts
@@ -1,215 +0,0 @@
|
||||
/*
|
||||
This file contains logic for streaming things from and to the filesystem
|
||||
*/
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
export const createReadStream = (pathArg: string) => {
|
||||
return plugins.fs.createReadStream(pathArg);
|
||||
};
|
||||
|
||||
export const createWriteStream = (pathArg: string) => {
|
||||
return plugins.fs.createWriteStream(pathArg);
|
||||
};
|
||||
|
||||
export const processFile = async (
|
||||
filePath: string,
|
||||
asyncFunc: (fileStream: plugins.stream.Readable) => Promise<void>,
|
||||
): Promise<void> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const fileStream = createReadStream(filePath);
|
||||
asyncFunc(fileStream).then(resolve).catch(reject);
|
||||
});
|
||||
};
|
||||
|
||||
export const processDirectory = async (
|
||||
directoryPath: string,
|
||||
asyncFunc: (fileStream: plugins.stream.Readable) => Promise<void>,
|
||||
): Promise<void> => {
|
||||
const files = plugins.fs.readdirSync(directoryPath, { withFileTypes: true });
|
||||
|
||||
for (const file of files) {
|
||||
const fullPath = plugins.path.join(directoryPath, file.name);
|
||||
|
||||
if (file.isDirectory()) {
|
||||
await processDirectory(fullPath, asyncFunc); // Recursively call processDirectory for directories
|
||||
} else if (file.isFile()) {
|
||||
await processFile(fullPath, asyncFunc); // Call async function with the file stream and wait for it
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if a file is ready to be streamed (exists and is not empty).
|
||||
*/
|
||||
export const isFileReadyForStreaming = async (
|
||||
filePathArg: string,
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
const stats = await plugins.fs.promises.stat(filePathArg);
|
||||
return stats.size > 0;
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
// File does not exist
|
||||
return false;
|
||||
}
|
||||
throw error; // Rethrow other unexpected errors
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Waits for a file to be ready for streaming (exists and is not empty).
|
||||
*/
|
||||
export const waitForFileToBeReadyForStreaming = (
|
||||
filePathArg: string,
|
||||
): Promise<void> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Normalize and resolve the file path
|
||||
const filePath = plugins.path.resolve(filePathArg);
|
||||
|
||||
// Function to check file stats
|
||||
const checkFile = (resolve: () => void, reject: (reason: any) => void) => {
|
||||
plugins.fs.stat(filePath, (err, stats) => {
|
||||
if (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
// File not found, wait and try again
|
||||
return;
|
||||
}
|
||||
// Some other error occurred
|
||||
return reject(err);
|
||||
}
|
||||
if (stats.size > 0) {
|
||||
// File exists and is not empty, resolve the promise
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Set up file watcher
|
||||
const watcher = plugins.fs.watch(
|
||||
filePath,
|
||||
{ persistent: false },
|
||||
(eventType) => {
|
||||
if (eventType === 'change' || eventType === 'rename') {
|
||||
checkFile(resolve, reject);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Check file immediately in case it's already ready
|
||||
checkFile(resolve, reject);
|
||||
|
||||
// Error handling
|
||||
watcher.on('error', (error) => {
|
||||
watcher.close();
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export class SmartReadStream extends plugins.stream.Readable {
|
||||
private watcher: plugins.fs.FSWatcher | null = null;
|
||||
private lastReadSize: number = 0;
|
||||
private endTimeout: NodeJS.Timeout | null = null;
|
||||
private filePath: string;
|
||||
private endDelay: number;
|
||||
private reading: boolean = false;
|
||||
|
||||
constructor(
|
||||
filePath: string,
|
||||
endDelay = 60000,
|
||||
opts?: plugins.stream.ReadableOptions,
|
||||
) {
|
||||
super(opts);
|
||||
this.filePath = filePath;
|
||||
this.endDelay = endDelay;
|
||||
}
|
||||
|
||||
private startWatching(): void {
|
||||
this.watcher = plugins.fs.watch(this.filePath, (eventType) => {
|
||||
if (eventType === 'change') {
|
||||
this.resetEndTimeout();
|
||||
}
|
||||
});
|
||||
|
||||
this.watcher.on('error', (error) => {
|
||||
this.cleanup();
|
||||
this.emit('error', error);
|
||||
});
|
||||
}
|
||||
|
||||
private resetEndTimeout(): void {
|
||||
if (this.endTimeout) clearTimeout(this.endTimeout);
|
||||
this.endTimeout = setTimeout(() => this.checkForEnd(), this.endDelay);
|
||||
}
|
||||
|
||||
private checkForEnd(): void {
|
||||
plugins.fs.stat(this.filePath, (err, stats) => {
|
||||
if (err) {
|
||||
this.emit('error', err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.lastReadSize === stats.size) {
|
||||
this.push(null); // Signal the end of the stream
|
||||
this.cleanup();
|
||||
} else {
|
||||
this.lastReadSize = stats.size;
|
||||
this.resetEndTimeout();
|
||||
if (!this.reading) {
|
||||
// We only want to continue reading if we were previously waiting for more data
|
||||
this.reading = true;
|
||||
this._read(10000); // Try to read more data
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private cleanup(): void {
|
||||
if (this.endTimeout) clearTimeout(this.endTimeout);
|
||||
if (this.watcher) this.watcher.close();
|
||||
}
|
||||
|
||||
_read(size: number): void {
|
||||
this.reading = true;
|
||||
const chunkSize = Math.min(size, 16384); // Read in chunks of 16KB
|
||||
const buffer = Buffer.alloc(chunkSize);
|
||||
plugins.fs.open(this.filePath, 'r', (err, fd) => {
|
||||
if (err) {
|
||||
this.emit('error', err);
|
||||
return;
|
||||
}
|
||||
plugins.fs.read(
|
||||
fd,
|
||||
buffer,
|
||||
0,
|
||||
chunkSize,
|
||||
this.lastReadSize,
|
||||
(err, bytesRead, buffer) => {
|
||||
if (err) {
|
||||
this.emit('error', err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (bytesRead > 0) {
|
||||
this.lastReadSize += bytesRead;
|
||||
this.push(buffer.slice(0, bytesRead)); // Push the data onto the stream
|
||||
} else {
|
||||
this.reading = false; // No more data to read for now
|
||||
this.resetEndTimeout();
|
||||
}
|
||||
|
||||
plugins.fs.close(fd, (err) => {
|
||||
if (err) {
|
||||
this.emit('error', err);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
_destroy(error: Error | null, callback: (error: Error | null) => void): void {
|
||||
this.cleanup();
|
||||
callback(error);
|
||||
}
|
||||
}
|
||||
13
ts/index.ts
13
ts/index.ts
@@ -1,14 +1,11 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import * as fsMod from './fs.js';
|
||||
import * as fsStreamMod from './fsstream.js';
|
||||
import * as interpreterMod from './interpreter.js';
|
||||
import * as memoryMod from './memory.js';
|
||||
|
||||
// Export main classes - focused on in-memory file representations
|
||||
export * from './classes.smartfile.js';
|
||||
export * from './classes.streamfile.js';
|
||||
export * from './classes.virtualdirectory.js';
|
||||
export * from './classes.smartfile.factory.js';
|
||||
|
||||
export const fs = fsMod;
|
||||
export const fsStream = fsStreamMod;
|
||||
export const interpreter = interpreterMod;
|
||||
export const memory = memoryMod;
|
||||
// Note: Filesystem operations (fs, memory, fsStream, interpreter) have been removed.
|
||||
// Use @push.rocks/smartfs for low-level filesystem operations.
|
||||
// Use SmartFileFactory for creating SmartFile/StreamFile/VirtualDirectory instances.
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
export let filetype = (pathArg: string): string => {
|
||||
const extName = plugins.path.extname(pathArg);
|
||||
const fileType = extName.replace(/\.([a-z]*)/, '$1'); // remove . form fileType
|
||||
return fileType;
|
||||
};
|
||||
|
||||
export let objectFile = (fileStringArg: string, fileTypeArg) => {
|
||||
switch (fileTypeArg) {
|
||||
case 'yml':
|
||||
case 'yaml':
|
||||
return plugins.yaml.load(fileStringArg);
|
||||
case 'json':
|
||||
return JSON.parse(fileStringArg);
|
||||
default:
|
||||
console.error('file type ' + fileTypeArg.blue + ' not supported');
|
||||
break;
|
||||
}
|
||||
};
|
||||
102
ts/memory.ts
102
ts/memory.ts
@@ -1,102 +0,0 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import { SmartFile } from './classes.smartfile.js';
|
||||
import * as smartfileFs from './fs.js';
|
||||
import * as interpreter from './interpreter.js';
|
||||
import type { StreamFile } from './classes.streamfile.js';
|
||||
|
||||
/**
|
||||
* converts file to Object
|
||||
* @param fileStringArg
|
||||
* @param fileTypeArg
|
||||
* @returns {any|any}
|
||||
*/
|
||||
export let toObject = (fileStringArg: string, fileTypeArg: string) => {
|
||||
return interpreter.objectFile(fileStringArg, fileTypeArg);
|
||||
};
|
||||
|
||||
export interface IToFsOptions {
|
||||
respectRelative?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* writes string or Smartfile to disk.
|
||||
* @param fileArg
|
||||
* @param fileNameArg
|
||||
* @param fileBaseArg
|
||||
*/
|
||||
export let toFs = async (
|
||||
fileContentArg: string | Buffer | SmartFile | StreamFile,
|
||||
filePathArg: string,
|
||||
optionsArg: IToFsOptions = {},
|
||||
) => {
|
||||
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 smartfileFs.ensureDir(plugins.path.parse(filePath).dir);
|
||||
plugins.fsExtra.writeFile(
|
||||
filePath,
|
||||
fileContent,
|
||||
{ encoding: fileEncoding },
|
||||
done.resolve,
|
||||
);
|
||||
return await done.promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* writes a string or a Smartfile to disk synchronously, only supports string
|
||||
* @param fileArg
|
||||
* @param filePathArg
|
||||
*/
|
||||
export const toFsSync = (fileArg: string, filePathArg: string) => {
|
||||
// function checks to abort if needed
|
||||
if (!fileArg || !filePathArg) {
|
||||
throw new Error('expected a valid arguments');
|
||||
}
|
||||
|
||||
// prepare actual write action
|
||||
let fileString: string;
|
||||
const filePath: string = filePathArg;
|
||||
|
||||
if (typeof fileArg !== 'string') {
|
||||
throw new Error('fileArg is not of type String.');
|
||||
} else if (typeof fileArg === 'string') {
|
||||
fileString = fileArg;
|
||||
}
|
||||
plugins.fsExtra.writeFileSync(filePath, fileString, { encoding: 'utf8' });
|
||||
};
|
||||
|
||||
export let smartfileArrayToFs = async (
|
||||
smartfileArrayArg: SmartFile[],
|
||||
dirArg: string,
|
||||
) => {
|
||||
await smartfileFs.ensureDir(dirArg);
|
||||
for (const smartfile of smartfileArrayArg) {
|
||||
await toFs(smartfile, dirArg, {
|
||||
respectRelative: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user