import * as plugins from './plugins.js'; import * as fs from './fs.js'; import * as memory from './memory.js'; export interface ISmartfileConstructorOptions { path: string; contentBuffer: Buffer; base: string; } /** * an vinyl file compatible in memory file 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 { return new Promise((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.getBinary(urlArg); const smartfile = await SmartFile.fromBuffer(urlArg, response.body); return smartfile; } // ======== // INSTANCE // ======== /** * 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 */ constructor(optionsArg: ISmartfileConstructorOptions) { super(); if (optionsArg.contentBuffer) { this.contentBuffer = optionsArg.contentBuffer; } else { console.log('created empty Smartfile?'); } this.path = optionsArg.path; this.base = optionsArg.base; } /** * 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 */ public async write() { let writePath = plugins.smartpath.transform.makeAbsolute(this.path, this.base); console.log(`writing to ${writePath}`); await memory.toFs(this.contentBuffer, writePath); } /** * 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 (!plugins.path.isAbsolute(filePathArg)) { filePathArg = plugins.path.join(process.cwd(), filePathArg); } await memory.toFs(this.contentBuffer, filePathArg); } /** * writes the file to a directory combined with the relative path portion * @param dirPathArg * @returns */ public async writeToDir(dirPathArg: string) { dirPathArg = plugins.smartpath.transform.toAbsolute(dirPathArg) as string; const filePath = plugins.path.join(dirPathArg, this.path); await memory.toFs(this.contentBuffer, filePath); return filePath; } /** * read file from disk */ public async read() { this.contentBuffer = await fs.toBuffer(plugins.path.join(this.base, this.path)); } /** * deletes the file from disk at its original location */ public async delete() { await fs.remove(plugins.path.join(this.base, 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; } }