"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const path_1 = __importDefault(require("path")); const immutable_1 = __importDefault(require("immutable")); const stream_1 = __importDefault(require("stream")); const file_1 = __importDefault(require("./file")); const promise_1 = __importDefault(require("../utils/promise")); const error_1 = __importDefault(require("../utils/error")); const path_2 = __importDefault(require("../utils/path")); class FS extends immutable_1.default.Record({ root: String(), fsExists: Function(), fsReadFile: Function(), fsStatFile: Function(), fsReadDir: Function(), fsLoadObject: null, fsReadAsStream: null }) { /** Return path to the root @return {string} */ getRoot() { return this.get("root"); } /** Verify that a file is in the fs scope @param {string} filename @return {boolean} */ isInScope(filename) { const rootPath = this.getRoot(); filename = path_1.default.join(rootPath, filename); return path_2.default.isInRoot(rootPath, filename); } /** Resolve a file in this FS @return {string} */ resolve(...args) { const rootPath = this.getRoot(); let filename = path_1.default.join.apply(path_1.default, [rootPath].concat(args)); filename = path_1.default.normalize(filename); if (!this.isInScope(filename)) { throw error_1.default.FileOutOfScopeError({ filename: filename, root: rootPath }); } return filename; } /** Check if a file exists, run a Promise(true) if that's the case, Promise(false) otherwise @param {string} filename @return {Promise} */ exists(filename) { const that = this; return (0, promise_1.default)().then(() => { filename = that.resolve(filename); const exists = that.get("fsExists"); return exists(filename); }); } /** Read a file and returns a promise with the content as a buffer @param {string} filename @return {Promise} */ read(filename) { const that = this; return (0, promise_1.default)().then(() => { filename = that.resolve(filename); const read = that.get("fsReadFile"); return read(filename); }); } /** Read a file as a string (utf-8) @return {Promise} */ readAsString(filename, encoding = "utf8") { return this.read(filename).then((buf) => { return buf.toString(encoding); }); } /** Read file as a stream @param {string} filename @return {Promise} */ readAsStream(filename) { const that = this; const filepath = that.resolve(filename); const fsReadAsStream = this.get("fsReadAsStream"); if (fsReadAsStream) { return (0, promise_1.default)(fsReadAsStream(filepath)); } return this.read(filename).then((buf) => { const bufferStream = new stream_1.default.PassThrough(); bufferStream.end(buf); return bufferStream; }); } /** Read stat infos about a file @param {string} filename @return {Promise} */ statFile(filename) { const that = this; return (0, promise_1.default)() .then(() => { const filepath = that.resolve(filename); const stat = that.get("fsStatFile"); return stat(filepath); }) .then((stat) => { return file_1.default.createFromStat(filename, stat); }); } /** List files/directories in a directory. Directories ends with '/' @param {string} dirname @return {Promise>} */ readDir(dirname) { const that = this; return (0, promise_1.default)() .then(() => { const dirpath = that.resolve(dirname); const readDir = that.get("fsReadDir"); return readDir(dirpath); }) .then((files) => { return immutable_1.default.List(files); }); } /** List only files in a diretcory Directories ends with '/' @param {string} dirname @return {Promise>} */ listFiles(dirname) { return this.readDir(dirname).then((files) => { return files.filterNot(pathIsFolder); }); } /** List all files in a directory @param {string} dirName @param {Function(dirName)} filterFn: call it for each file/directory to test if it should stop iterating @return {Promise>} */ listAllFiles(dirName, filterFn) { const that = this; dirName = dirName || "."; return this.readDir(dirName).then((files) => { return promise_1.default.reduce(files, (out, file) => { const isDirectory = pathIsFolder(file); const newDirName = path_1.default.join(dirName, file); if (filterFn && filterFn(newDirName) === false) { return out; } if (!isDirectory) { return out.push(newDirName); } return that.listAllFiles(newDirName, filterFn).then((inner) => { return out.concat(inner); }); }, immutable_1.default.List()); }); } /** Find a file in a folder (case insensitive) Return the found filename @param {string} dirname @param {string} filename @return {Promise} */ findFile(dirname, filename) { return this.listFiles(dirname).then((files) => { return files.find((file) => { return file.toLowerCase() == filename.toLowerCase(); }); }); } /** Load a JSON file By default, fs only supports JSON @param {string} filename @return {Promise} */ loadAsObject(filename) { const that = this; const fsLoadObject = this.get("fsLoadObject"); return this.exists(filename).then((exists) => { if (!exists) { const err = new Error("Module doesn't exist"); // @ts-expect-error ts-migrate(2339) FIXME: Property 'code' does not exist on type 'Error'. err.code = "MODULE_NOT_FOUND"; throw err; } if (fsLoadObject) { return fsLoadObject(that.resolve(filename)); } else { return that.readAsString(filename).then((str) => { return JSON.parse(str); }); } }); } /** Create a FS instance @param {Object} def @return {FS} */ static create(def) { return new FS(def); } /** Create a new FS instance with a reduced scope @param {FS} fs @param {string} scope @return {FS} */ static reduceScope(fs, scope) { return fs.set("root", path_1.default.join(fs.getRoot(), scope)); } } // .readdir return files/folder as a list of string, folder ending with '/' function pathIsFolder(filename) { const lastChar = filename[filename.length - 1]; return lastChar == "/" || lastChar == "\\"; } exports.default = FS;