import * as plugins from './plugins.js'; export interface ISmartfileConstructorOptions { path: string; contentBuffer: Buffer; base: string; } /** * an vinyl file compatible in memory file class * Use SmartFileFactory to create instances of this class */ export class SmartFile extends plugins.smartjson.Smartjson { // ======== // INSTANCE // ======== /** * Reference to the SmartFs instance for filesystem operations */ private smartFs?: any; /** * the relative path of the file */ @plugins.smartjson.foldDec() public path: string; /** * a parsed path */ public get parsedPath(): plugins.path.ParsedPath { return plugins.path.parse(this.path); } public get absolutePath() { return plugins.path.join(this.base, this.path); } public get absoluteParsedPath() { return plugins.path.parse(this.absolutePath); } /** * the content of the file as Buffer */ @plugins.smartjson.foldDec() public contentBuffer: Buffer; /** * The current working directory of the file * Note:this is similar to gulp and different from native node path base */ @plugins.smartjson.foldDec() public base: string; /** * sync the file with disk */ @plugins.smartjson.foldDec() public sync: boolean; /** * the constructor of Smartfile * @param optionsArg * @param smartFs optional SmartFs instance for filesystem operations */ constructor(optionsArg: ISmartfileConstructorOptions, smartFs?: any) { super(); if (optionsArg.contentBuffer) { this.contentBuffer = optionsArg.contentBuffer; } else { console.log('created empty Smartfile?'); } this.path = optionsArg.path; this.base = optionsArg.base; this.smartFs = smartFs; } /** * set contents from string * @param contentString */ public setContentsFromString( contentString: string, encodingArg: 'utf8' | 'binary' = 'utf8', ) { this.contents = Buffer.from(contentString, encodingArg); } /** * 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() { 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 this.smartFs.file(writePath).write(this.contentBuffer); } /** * writes the file to path given as argument * note: if the path is not absolute, takes process.cwd() as base * @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 this.smartFs.file(filePathArg).write(this.contentBuffer); } /** * writes the file to a directory combined with the relative path portion * @param dirPathArg * @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 this.smartFs.file(filePath).write(this.contentBuffer); return filePath; } /** * read file from disk */ public async read() { 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() { 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(); } /** * Renames the file to the specified new name. * - Updates the `path` property with the new name. * - Writes the file to the new location if it exists on disk. * @param newName The new name of the file (including extension if applicable). * @param writeToDisk (optional) If true, also renames the file on the disk. * @returns The updated file path after renaming. */ public async rename( newName: string, writeToDisk: boolean = false, ): Promise { // Validate the new name if (!newName || typeof newName !== 'string') { throw new Error('Invalid new name provided.'); } // Extract the directory path const oldFilePath = this.path; const dirPath = plugins.path.dirname(this.path); // Create the new file path const newFilePath = plugins.path.join(dirPath, newName); // Update the `path` property this.path = newFilePath; // Optionally write the renamed file to disk if (writeToDisk) { const oldAbsolutePath = plugins.smartpath.transform.makeAbsolute( oldFilePath, this.base, ); const newAbsolutePath = plugins.smartpath.transform.makeAbsolute( newFilePath, this.base, ); // Rename the file on disk await plugins.fsExtra.rename(oldAbsolutePath, newAbsolutePath); } // Return the new path return this.path; } // ----------------------------------------------- // vinyl compatibility // ----------------------------------------------- /** * vinyl-compatibility: alias of this.contentBuffer */ get contents(): Buffer { return this.contentBuffer; } set contents(contentsArg) { this.contentBuffer = contentsArg; } /** * vinyl-compatibility */ public get cwd() { return process.cwd(); } /** * return relative path of file */ public get relative(): string { return this.path; } /** * return truw when the file has content */ public isNull(): boolean { if (!this.contentBuffer) { return true; } return false; } /** * return true if contents are Buffer */ public isBuffer(): boolean { if (this.contents instanceof Buffer) { return true; } return false; } public isDirectory() { return false; } public isStream() { return false; } public isSymbolic() { return false; } public async getHash(typeArg: 'path' | 'content' | 'all' = 'all') { const pathHash = await plugins.smarthash.sha256FromString(this.path); const contentHash = await plugins.smarthash.sha256FromBuffer( this.contentBuffer, ); const combinedHash = await plugins.smarthash.sha256FromString( pathHash + contentHash, ); switch (typeArg) { case 'path': return pathHash; case 'content': return contentHash; case 'all': default: return combinedHash; } } // update things public updateFileName(fileNameArg: string) { const oldFileName = this.parsedPath.base; this.path = this.path.replace(new RegExp(oldFileName + '$'), fileNameArg); } public async editContentAsString( editFuncArg: (fileStringArg: string) => Promise, ) { const newFileString = await editFuncArg(this.contentBuffer.toString()); this.contentBuffer = Buffer.from(newFileString); } /** * Returns a ReadableStream from the file's content buffer */ public getStream(): plugins.stream.Readable { const stream = new plugins.stream.Readable(); stream.push(this.contentBuffer); // Push the content buffer to the stream stream.push(null); // Push null to signify the end of the stream (EOF) return stream; } /** * Returns the size of the file in bytes */ public async getSize(): Promise { 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; } }