"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.FSWatcher = exports.StatWatcher = exports.Volume = void 0; exports.pathToSteps = pathToSteps; exports.dataToStr = dataToStr; exports.toUnixTimestamp = toUnixTimestamp; const path_1 = require("@jsonjoy.com/fs-node-builtins/lib/path"); const fs_core_1 = require("@jsonjoy.com/fs-core"); const util_1 = require("@jsonjoy.com/fs-core/lib/util"); const Stats_1 = require("./Stats"); const Dirent_1 = require("./Dirent"); const StatFs_1 = require("./StatFs"); const buffer_1 = require("@jsonjoy.com/fs-node-builtins/lib/internal/buffer"); const setTimeoutUnref_1 = require("@jsonjoy.com/fs-node-utils/lib/setTimeoutUnref"); const stream_1 = require("@jsonjoy.com/fs-node-builtins/lib/stream"); const fs_node_utils_1 = require("@jsonjoy.com/fs-node-utils"); const events_1 = require("@jsonjoy.com/fs-node-builtins/lib/events"); const FileHandle_1 = require("./FileHandle"); const util_2 = require("@jsonjoy.com/fs-node-builtins/lib/util"); const FsPromises_1 = require("./FsPromises"); const fs_print_1 = require("@jsonjoy.com/fs-print"); const fsSnapshot = require("@jsonjoy.com/fs-snapshot"); const fs_node_utils_2 = require("@jsonjoy.com/fs-node-utils"); const errors = require("@jsonjoy.com/fs-node-builtins/lib/internal/errors"); const options_1 = require("./options"); const util_3 = require("./util"); const Dir_1 = require("./Dir"); const resolveCrossPlatform = path_1.resolve; const { O_SYMLINK, F_OK, R_OK, W_OK, X_OK, COPYFILE_EXCL, COPYFILE_FICLONE_FORCE } = fs_node_utils_1.constants; const pathSep = path_1.posix ? path_1.posix.sep : path_1.sep; const pathRelative = path_1.posix ? path_1.posix.relative : path_1.relative; const pathJoin = path_1.posix ? path_1.posix.join : path_1.join; const pathDirname = path_1.posix ? path_1.posix.dirname : path_1.dirname; const pathNormalize = path_1.posix ? path_1.posix.normalize : path_1.normalize; // ------------------------------------------------------------------ Constants const kMinPoolSpace = 128; // ---------------------------------------------------------- Utility functions function pathToSteps(path) { return (0, fs_core_1.filenameToSteps)((0, util_3.pathToFilename)(path)); } function dataToStr(data, encoding = fs_node_utils_1.ENCODING_UTF8) { if (buffer_1.Buffer.isBuffer(data)) return data.toString(encoding); else if (data instanceof Uint8Array) return (0, buffer_1.bufferFrom)(data).toString(encoding); else return String(data); } // converts Date or number to a fractional UNIX timestamp function toUnixTimestamp(time) { // tslint:disable-next-line triple-equals if (typeof time === 'string' && +time == time) { return +time; } if (time instanceof Date) { return time.getTime() / 1000; } if (isFinite(time)) { if (time < 0) { return Date.now() / 1000; } return time; } throw new Error('Cannot parse time: ' + time); } function validateUid(uid) { if (typeof uid !== 'number') throw TypeError(fs_node_utils_2.ERRSTR.UID); } function validateGid(gid) { if (typeof gid !== 'number') throw TypeError(fs_node_utils_2.ERRSTR.GID); } /** * `Volume` represents a file system. */ class Volume { get promises() { if (this.promisesApi === null) throw new Error('Promise is not supported in this environment.'); return this.promisesApi; } constructor(_core = new fs_core_1.Superblock()) { this._core = _core; this.promisesApi = new FsPromises_1.FsPromises(this, FileHandle_1.FileHandle); this.openSync = (path, flags, mode = 438 /* MODE.DEFAULT */) => { // Validate (1) mode; (2) path; (3) flags - in that order. const modeNum = (0, util_3.modeToNumber)(mode); const fileName = (0, util_3.pathToFilename)(path); const flagsNum = (0, util_3.flagsToNumber)(flags); return this._core.open(fileName, flagsNum, modeNum, !(flagsNum & O_SYMLINK)); }; this.open = (path, flags, a, b) => { let mode = a; let callback = b; if (typeof a === 'function') { mode = 438 /* MODE.DEFAULT */; callback = a; } mode = mode || 438 /* MODE.DEFAULT */; const modeNum = (0, util_3.modeToNumber)(mode); const fileName = (0, util_3.pathToFilename)(path); const flagsNum = (0, util_3.flagsToNumber)(flags); this.wrapAsync(this._core.open, [fileName, flagsNum, modeNum, !(flagsNum & O_SYMLINK)], callback); }; this.closeSync = (fd) => { this._core.close(fd); }; this.close = (fd, callback) => { (0, fs_core_1.validateFd)(fd); const file = this._core.getFileByFdOrThrow(fd, 'close'); this.wrapAsync(this._core.close, [file.fd], callback); }; this.readSync = (fd, buffer, offset, length, position) => { (0, fs_core_1.validateFd)(fd); return this._core.read(fd, buffer, offset, length, position); }; this.read = (fd, buffer, offset, length, position, callback) => { (0, util_3.validateCallback)(callback); if (length === 0) { // This `if` branch is from Node.js return queueMicrotask(() => { if (callback) callback(null, 0, buffer); }); } Promise.resolve().then(() => { try { const bytes = this._core.read(fd, buffer, offset, length, position); callback(null, bytes, buffer); } catch (err) { callback(err); } }); }; this.readv = (fd, buffers, a, b) => { let position = a; let callback = b; if (typeof a === 'function') [position, callback] = [null, a]; (0, util_3.validateCallback)(callback); Promise.resolve().then(() => { try { const bytes = this._core.readv(fd, buffers, position); callback(null, bytes, buffers); } catch (err) { callback(err); } }); }; this.readvSync = (fd, buffers, position) => { (0, fs_core_1.validateFd)(fd); return this._core.readv(fd, buffers, position ?? null); }; this._readfile = (id, flagsNum, encoding) => { let result; const isUserFd = typeof id === 'number'; const userOwnsFd = isUserFd && (0, fs_core_1.isFd)(id); let fd; if (userOwnsFd) fd = id; else { const filename = (0, util_3.pathToFilename)(id); // Check if original path had trailing slash (indicates directory intent) const originalPath = String(id); const hasTrailingSlash = originalPath.length > 1 && originalPath.endsWith('/'); const link = this._core.getResolvedLinkOrThrow(filename, 'open'); const node = link.getNode(); if (node.isDirectory()) throw (0, util_3.createError)("EISDIR" /* ERROR_CODE.EISDIR */, 'open', link.getPath()); // If path had trailing slash but resolved to a file, throw ENOTDIR if (hasTrailingSlash && node.isFile()) { throw (0, util_3.createError)("ENOTDIR" /* ERROR_CODE.ENOTDIR */, 'open', originalPath); } fd = this.openSync(id, flagsNum); } try { result = (0, util_3.bufferToEncoding)(this._core.getFileByFdOrThrow(fd).getBuffer(), encoding); } finally { if (!userOwnsFd) { this.closeSync(fd); } } return result; }; this.readFileSync = (file, options) => { const opts = (0, options_1.getReadFileOptions)(options); const flagsNum = (0, util_3.flagsToNumber)(opts.flag); return this._readfile(file, flagsNum, opts.encoding); }; this.readFile = (id, a, b) => { const [opts, callback] = (0, options_1.optsAndCbGenerator)(options_1.getReadFileOptions)(a, b); const flagsNum = (0, util_3.flagsToNumber)(opts.flag); this.wrapAsync(this._readfile, [id, flagsNum, opts.encoding], callback); }; this.writeSync = (fd, a, b, c, d) => { const [, buf, offset, length, position] = (0, util_3.getWriteSyncArgs)(fd, a, b, c, d); return this._write(fd, buf, offset, length, position); }; this.write = (fd, a, b, c, d, e) => { const [, asStr, buf, offset, length, position, cb] = (0, util_3.getWriteArgs)(fd, a, b, c, d, e); Promise.resolve().then(() => { try { const bytes = this._write(fd, buf, offset, length, position); if (!asStr) { cb(null, bytes, buf); } else { cb(null, bytes, a); } } catch (err) { cb(err); } }); }; this.writev = (fd, buffers, a, b) => { let position = a; let callback = b; if (typeof a === 'function') [position, callback] = [null, a]; (0, util_3.validateCallback)(callback); Promise.resolve().then(() => { try { const bytes = this.writevBase(fd, buffers, position); callback(null, bytes, buffers); } catch (err) { callback(err); } }); }; this.writevSync = (fd, buffers, position) => { (0, fs_core_1.validateFd)(fd); return this.writevBase(fd, buffers, position ?? null); }; this.writeFileSync = (id, data, options) => { const opts = (0, options_1.getWriteFileOptions)(options); const flagsNum = (0, util_3.flagsToNumber)(opts.flag); const modeNum = (0, util_3.modeToNumber)(opts.mode); const buf = (0, fs_core_1.dataToBuffer)(data, opts.encoding); this._core.writeFile(id, buf, flagsNum, modeNum); }; this.writeFile = (id, data, a, b) => { let options = a; let callback = b; if (typeof a === 'function') [options, callback] = [options_1.writeFileDefaults, a]; const cb = (0, util_3.validateCallback)(callback); const opts = (0, options_1.getWriteFileOptions)(options); const flagsNum = (0, util_3.flagsToNumber)(opts.flag); const modeNum = (0, util_3.modeToNumber)(opts.mode); const buf = (0, fs_core_1.dataToBuffer)(data, opts.encoding); this.wrapAsync(this._core.writeFile, [id, buf, flagsNum, modeNum], cb); }; this.copyFileSync = (src, dest, flags) => { const srcFilename = (0, util_3.pathToFilename)(src); const destFilename = (0, util_3.pathToFilename)(dest); return this._copyFile(srcFilename, destFilename, (flags || 0) | 0); }; this.copyFile = (src, dest, a, b) => { const srcFilename = (0, util_3.pathToFilename)(src); const destFilename = (0, util_3.pathToFilename)(dest); let flags; let callback; if (typeof a === 'function') [flags, callback] = [0, a]; else [flags, callback] = [a, b]; (0, util_3.validateCallback)(callback); this.wrapAsync(this._copyFile, [srcFilename, destFilename, flags], callback); }; this._cp = (src, dest, options) => { if (options.filter && !options.filter(src, dest)) return; const srcStat = options.dereference ? this.statSync(src) : this.lstatSync(src); let destStat = null; try { destStat = this.lstatSync(dest); } catch (err) { if (err.code !== 'ENOENT') { throw err; } } // Check if src and dest are the same (both exist and have same inode) if (destStat && srcStat.ino === destStat.ino && srcStat.dev === destStat.dev) throw (0, util_3.createError)("EINVAL" /* ERROR_CODE.EINVAL */, 'cp', src, dest); // Check type compatibility if (destStat) { if (srcStat.isDirectory() && !destStat.isDirectory()) throw (0, util_3.createError)("EISDIR" /* ERROR_CODE.EISDIR */, 'cp', src, dest); if (!srcStat.isDirectory() && destStat.isDirectory()) throw (0, util_3.createError)("ENOTDIR" /* ERROR_CODE.ENOTDIR */, 'cp', src, dest); } // Check if trying to copy directory to subdirectory of itself if (srcStat.isDirectory() && this.isSrcSubdir(src, dest)) throw (0, util_3.createError)("EINVAL" /* ERROR_CODE.EINVAL */, 'cp', src, dest); ENDURE_PARENT_DIR_EXISTS: { const parent = pathDirname(dest); if (!this.existsSync(parent)) this.mkdirSync(parent, { recursive: true }); } // Handle different file types if (srcStat.isDirectory()) { if (!options.recursive) throw (0, util_3.createError)("EISDIR" /* ERROR_CODE.EISDIR */, 'cp', src); this.cpDirSync(srcStat, destStat, src, dest, options); } else if (srcStat.isFile() || srcStat.isCharacterDevice() || srcStat.isBlockDevice()) { this.cpFileSync(srcStat, destStat, src, dest, options); } else if (srcStat.isSymbolicLink() && !options.dereference) { // Only handle as symlink if not dereferencing this.cpSymlinkSync(destStat, src, dest, options); } else { throw (0, util_3.createError)("EINVAL" /* ERROR_CODE.EINVAL */, 'cp', src); } }; this.linkSync = (existingPath, newPath) => { const existingPathFilename = (0, util_3.pathToFilename)(existingPath); const newPathFilename = (0, util_3.pathToFilename)(newPath); this._core.link(existingPathFilename, newPathFilename); }; this.link = (existingPath, newPath, callback) => { const existingPathFilename = (0, util_3.pathToFilename)(existingPath); const newPathFilename = (0, util_3.pathToFilename)(newPath); this.wrapAsync(this._core.link, [existingPathFilename, newPathFilename], callback); }; this.unlinkSync = (path) => { const filename = (0, util_3.pathToFilename)(path); this._core.unlink(filename); }; this.unlink = (path, callback) => { const filename = (0, util_3.pathToFilename)(path); this.wrapAsync(this._core.unlink, [filename], callback); }; /** * `type` argument works only on Windows. * @param target * @param path * @param type */ this.symlinkSync = (target, path, type) => { const targetFilename = (0, util_3.pathToFilename)(target); const pathFilename = (0, util_3.pathToFilename)(path); this._core.symlink(targetFilename, pathFilename); }; this.symlink = (target, path, a, b) => { const callback = (0, util_3.validateCallback)(typeof a === 'function' ? a : b); const targetFilename = (0, util_3.pathToFilename)(target); const pathFilename = (0, util_3.pathToFilename)(path); this.wrapAsync(this._core.symlink, [targetFilename, pathFilename], callback); }; this._lstat = (filename, bigint = false, throwIfNoEntry = false) => { let link; try { link = this._core.getLinkOrThrow(filename, 'lstat'); } catch (err) { if (err.code === "ENOENT" /* ERROR_CODE.ENOENT */ && !throwIfNoEntry) return undefined; else throw err; } return Stats_1.default.build(link.getNode(), bigint); }; this.lstatSync = (path, options) => { const { throwIfNoEntry = true, bigint = false } = (0, options_1.getStatOptions)(options); return this._lstat((0, util_3.pathToFilename)(path), bigint, throwIfNoEntry); }; this.renameSync = (oldPath, newPath) => { const oldPathFilename = (0, util_3.pathToFilename)(oldPath); const newPathFilename = (0, util_3.pathToFilename)(newPath); this._core.rename(oldPathFilename, newPathFilename); }; this.rename = (oldPath, newPath, callback) => { const oldPathFilename = (0, util_3.pathToFilename)(oldPath); const newPathFilename = (0, util_3.pathToFilename)(newPath); this.wrapAsync(this._core.rename, [oldPathFilename, newPathFilename], callback); }; this.existsSync = (path) => { try { return this._exists((0, util_3.pathToFilename)(path)).ok; } catch (err) { return false; } }; this.exists = (path, callback) => { const filename = (0, util_3.pathToFilename)(path); if (typeof callback !== 'function') throw Error(fs_node_utils_2.ERRSTR.CB); Promise.resolve().then(() => { try { callback(this._exists(filename).ok); } catch (err) { callback(false); } }); }; this.accessSync = (path, mode = F_OK) => { const filename = (0, util_3.pathToFilename)(path); mode = mode | 0; this._access(filename, mode); }; this.access = (path, a, b) => { let mode = F_OK; let callback; if (typeof a !== 'function') [mode, callback] = [a | 0, (0, util_3.validateCallback)(b)]; else callback = a; const filename = (0, util_3.pathToFilename)(path); this.wrapAsync(this._access, [filename, mode], callback); }; this.appendFileSync = (id, data, options) => { const opts = (0, options_1.getAppendFileOpts)(options); // Force append behavior when using a supplied file descriptor. if (!opts.flag || (0, fs_core_1.isFd)(id)) opts.flag = 'a'; this.writeFileSync(id, data, opts); }; this.appendFile = (id, data, a, b) => { const [opts, callback] = (0, options_1.getAppendFileOptsAndCb)(a, b); // Force append behavior when using a supplied file descriptor. if (!opts.flag || (0, fs_core_1.isFd)(id)) opts.flag = 'a'; this.writeFile(id, data, opts, callback); }; this._readdir = (filename, options) => { const steps = (0, fs_core_1.filenameToSteps)(filename); const link = this._core.getResolvedLinkOrThrow(filename, 'scandir'); const node = link.getNode(); if (!node.isDirectory()) throw (0, util_3.createError)("ENOTDIR" /* ERROR_CODE.ENOTDIR */, 'scandir', filename); // Check we have permissions if (!node.canRead()) throw (0, util_3.createError)("EACCES" /* ERROR_CODE.EACCES */, 'scandir', filename); const list = []; // output list for (const name of link.children.keys()) { const child = link.getChild(name); if (!child || name === '.' || name === '..') continue; list.push(Dirent_1.default.build(child, options.encoding)); // recursion if (options.recursive && child.children.size) { const recurseOptions = { ...options, recursive: true, withFileTypes: true }; const childList = this._readdir(child.getPath(), recurseOptions); list.push(...childList); } } if (!util_1.isWin && options.encoding !== 'buffer') list.sort((a, b) => { if (a.name < b.name) return -1; if (a.name > b.name) return 1; return 0; }); if (options.withFileTypes) return list; let filename2 = filename; if (util_1.isWin) filename2 = filename2.replace(/\\/g, '/'); return list.map(dirent => { if (options.recursive) { let fullPath = pathJoin(dirent.parentPath, dirent.name.toString()); if (util_1.isWin) { fullPath = fullPath.replace(/\\/g, '/'); } return fullPath.replace(filename2 + path_1.posix.sep, ''); } return dirent.name; }); }; this.readdirSync = (path, options) => { const opts = (0, options_1.getReaddirOptions)(options); const filename = (0, util_3.pathToFilename)(path); return this._readdir(filename, opts); }; this.readdir = (path, a, b) => { const [options, callback] = (0, options_1.getReaddirOptsAndCb)(a, b); const filename = (0, util_3.pathToFilename)(path); this.wrapAsync(this._readdir, [filename, options], callback); }; this._readlink = (filename, encoding) => { const link = this._core.getLinkOrThrow(filename, 'readlink'); const node = link.getNode(); if (!node.isSymlink()) throw (0, util_3.createError)("EINVAL" /* ERROR_CODE.EINVAL */, 'readlink', filename); return (0, fs_node_utils_1.strToEncoding)(node.symlink, encoding); }; this.readlinkSync = (path, options) => { const opts = (0, options_1.getDefaultOpts)(options); const filename = (0, util_3.pathToFilename)(path); return this._readlink(filename, opts.encoding); }; this.readlink = (path, a, b) => { const [opts, callback] = (0, options_1.getDefaultOptsAndCb)(a, b); const filename = (0, util_3.pathToFilename)(path); this.wrapAsync(this._readlink, [filename, opts.encoding], callback); }; this._fsync = (fd) => { this._core.getFileByFdOrThrow(fd, 'fsync'); }; this.fsyncSync = (fd) => { this._fsync(fd); }; this.fsync = (fd, callback) => { this.wrapAsync(this._fsync, [fd], callback); }; this._fdatasync = (fd) => { this._core.getFileByFdOrThrow(fd, 'fdatasync'); }; this.fdatasyncSync = (fd) => { this._fdatasync(fd); }; this.fdatasync = (fd, callback) => { this.wrapAsync(this._fdatasync, [fd], callback); }; this._ftruncate = (fd, len) => { this._core.ftruncate(fd, len); }; this.ftruncateSync = (fd, len) => { this._ftruncate(fd, len); }; this.ftruncate = (fd, a, b) => { const len = typeof a === 'number' ? a : 0; const callback = (0, util_3.validateCallback)(typeof a === 'number' ? b : a); this.wrapAsync(this._ftruncate, [fd, len], callback); }; this._truncate = (path, len) => { const fd = this.openSync(path, 'r+'); try { this.ftruncateSync(fd, len); } finally { this.closeSync(fd); } }; /** * `id` should be a file descriptor or a path. `id` as file descriptor will * not be supported soon. */ this.truncateSync = (id, len) => { if ((0, fs_core_1.isFd)(id)) return this.ftruncateSync(id, len); this._truncate(id, len); }; this.truncate = (id, a, b) => { const len = typeof a === 'number' ? a : 0; const callback = (0, util_3.validateCallback)(typeof a === 'number' ? b : a); if ((0, fs_core_1.isFd)(id)) return this.ftruncate(id, len, callback); this.wrapAsync(this._truncate, [id, len], callback); }; this._futimes = (fd, atime, mtime) => { this._core.futimes(fd, atime, mtime); }; this.futimesSync = (fd, atime, mtime) => { this._futimes(fd, toUnixTimestamp(atime), toUnixTimestamp(mtime)); }; this.futimes = (fd, atime, mtime, callback) => { this.wrapAsync(this._futimes, [fd, toUnixTimestamp(atime), toUnixTimestamp(mtime)], callback); }; this._utimes = (filename, atime, mtime, followSymlinks = true) => { this._core.utimes(filename, atime, mtime, followSymlinks); }; this.utimesSync = (path, atime, mtime) => { this._utimes((0, util_3.pathToFilename)(path), toUnixTimestamp(atime), toUnixTimestamp(mtime), true); }; this.utimes = (path, atime, mtime, callback) => { this.wrapAsync(this._utimes, [(0, util_3.pathToFilename)(path), toUnixTimestamp(atime), toUnixTimestamp(mtime), true], callback); }; this.lutimesSync = (path, atime, mtime) => { this._utimes((0, util_3.pathToFilename)(path), toUnixTimestamp(atime), toUnixTimestamp(mtime), false); }; this.lutimes = (path, atime, mtime, callback) => { this.wrapAsync(this._utimes, [(0, util_3.pathToFilename)(path), toUnixTimestamp(atime), toUnixTimestamp(mtime), false], callback); }; this.mkdirSync = (path, options) => { const opts = (0, options_1.getMkdirOptions)(options); const modeNum = (0, util_3.modeToNumber)(opts.mode, 0o777); const filename = (0, util_3.pathToFilename)(path); if (opts.recursive) return this._core.mkdirp(filename, modeNum); this._core.mkdir(filename, modeNum); }; this.mkdir = (path, a, b) => { const opts = (0, options_1.getMkdirOptions)(a); const callback = (0, util_3.validateCallback)(typeof a === 'function' ? a : b); const modeNum = (0, util_3.modeToNumber)(opts.mode, 0o777); const filename = (0, util_3.pathToFilename)(path); if (opts.recursive) this.wrapAsync(this._core.mkdirp, [filename, modeNum], callback); else this.wrapAsync(this._core.mkdir, [filename, modeNum], callback); }; this._mkdtemp = (prefix, encoding, retry = 5) => { const filename = prefix + (0, util_3.genRndStr6)(); try { this._core.mkdir(filename, 511 /* MODE.DIR */); return (0, fs_node_utils_1.strToEncoding)(filename, encoding); } catch (err) { if (err.code === "EEXIST" /* ERROR_CODE.EEXIST */) { if (retry > 1) return this._mkdtemp(prefix, encoding, retry - 1); else throw Error('Could not create temp dir.'); } else throw err; } }; this.mkdtempSync = (prefix, options) => { const { encoding } = (0, options_1.getDefaultOpts)(options); if (!prefix || typeof prefix !== 'string') throw new TypeError('filename prefix is required'); (0, util_3.nullCheck)(prefix); return this._mkdtemp(prefix, encoding); }; this.mkdtemp = (prefix, a, b) => { const [{ encoding }, callback] = (0, options_1.getDefaultOptsAndCb)(a, b); if (!prefix || typeof prefix !== 'string') throw new TypeError('filename prefix is required'); if (!(0, util_3.nullCheck)(prefix)) return; this.wrapAsync(this._mkdtemp, [prefix, encoding], callback); }; this.rmdirSync = (path, options) => { const opts = (0, options_1.getRmdirOptions)(options); this._core.rmdir((0, util_3.pathToFilename)(path), opts.recursive); }; this.rmdir = (path, a, b) => { const opts = (0, options_1.getRmdirOptions)(a); const callback = (0, util_3.validateCallback)(typeof a === 'function' ? a : b); this.wrapAsync(this._core.rmdir, [(0, util_3.pathToFilename)(path), opts.recursive], callback); }; this.rmSync = (path, options) => { this._core.rm((0, util_3.pathToFilename)(path), options?.force, options?.recursive); }; this.rm = (path, a, b) => { const [opts, callback] = (0, options_1.getRmOptsAndCb)(a, b); this.wrapAsync(this._core.rm, [(0, util_3.pathToFilename)(path), opts?.force, opts?.recursive], callback); }; this._fchmod = (fd, modeNum) => { this._core.fchmod(fd, modeNum); }; this.fchmodSync = (fd, mode) => { this._fchmod(fd, (0, util_3.modeToNumber)(mode)); }; this.fchmod = (fd, mode, callback) => { this.wrapAsync(this._fchmod, [fd, (0, util_3.modeToNumber)(mode)], callback); }; this._chmod = (filename, modeNum, followSymlinks = true) => { if (followSymlinks) { this._core.chmod(filename, modeNum); } else { this._core.lchmod(filename, modeNum); } }; this.chmodSync = (path, mode) => { const modeNum = (0, util_3.modeToNumber)(mode); const filename = (0, util_3.pathToFilename)(path); this._chmod(filename, modeNum, true); }; this.chmod = (path, mode, callback) => { const modeNum = (0, util_3.modeToNumber)(mode); const filename = (0, util_3.pathToFilename)(path); this.wrapAsync(this._chmod, [filename, modeNum], callback); }; this._lchmod = (filename, modeNum) => { this._core.lchmod(filename, modeNum); }; this.lchmodSync = (path, mode) => { const modeNum = (0, util_3.modeToNumber)(mode); const filename = (0, util_3.pathToFilename)(path); this._lchmod(filename, modeNum); }; this.lchmod = (path, mode, callback) => { const modeNum = (0, util_3.modeToNumber)(mode); const filename = (0, util_3.pathToFilename)(path); this.wrapAsync(this._lchmod, [filename, modeNum], callback); }; this._fchown = (fd, uid, gid) => { this._core.fchown(fd, uid, gid); }; this.fchownSync = (fd, uid, gid) => { validateUid(uid); validateGid(gid); this._fchown(fd, uid, gid); }; this.fchown = (fd, uid, gid, callback) => { validateUid(uid); validateGid(gid); this.wrapAsync(this._fchown, [fd, uid, gid], callback); }; this._chown = (filename, uid, gid) => { this._core.chown(filename, uid, gid); }; this.chownSync = (path, uid, gid) => { validateUid(uid); validateGid(gid); this._chown((0, util_3.pathToFilename)(path), uid, gid); }; this.chown = (path, uid, gid, callback) => { validateUid(uid); validateGid(gid); this.wrapAsync(this._chown, [(0, util_3.pathToFilename)(path), uid, gid], callback); }; this._lchown = (filename, uid, gid) => { this._core.lchown(filename, uid, gid); }; this.lchownSync = (path, uid, gid) => { validateUid(uid); validateGid(gid); this._lchown((0, util_3.pathToFilename)(path), uid, gid); }; this.lchown = (path, uid, gid, callback) => { validateUid(uid); validateGid(gid); this.wrapAsync(this._lchown, [(0, util_3.pathToFilename)(path), uid, gid], callback); }; this.statWatchers = {}; this.cpSync = (src, dest, options) => { const srcFilename = (0, util_3.pathToFilename)(src); const destFilename = (0, util_3.pathToFilename)(dest); const opts_ = { dereference: options?.dereference ?? false, errorOnExist: options?.errorOnExist ?? false, filter: options?.filter, force: options?.force ?? true, mode: options?.mode ?? 0, preserveTimestamps: options?.preserveTimestamps ?? false, recursive: options?.recursive ?? false, verbatimSymlinks: options?.verbatimSymlinks ?? false, }; return this._cp(srcFilename, destFilename, opts_); }; this.cp = (src, dest, a, b) => { const srcFilename = (0, util_3.pathToFilename)(src); const destFilename = (0, util_3.pathToFilename)(dest); let options; let callback; if (typeof a === 'function') [options, callback] = [{}, a]; else [options, callback] = [a || {}, b]; (0, util_3.validateCallback)(callback); const opts_ = { dereference: options?.dereference ?? false, errorOnExist: options?.errorOnExist ?? false, filter: options?.filter, force: options?.force ?? true, mode: options?.mode ?? 0, preserveTimestamps: options?.preserveTimestamps ?? false, recursive: options?.recursive ?? false, verbatimSymlinks: options?.verbatimSymlinks ?? false, }; this.wrapAsync(this._cp, [srcFilename, destFilename, opts_], callback); }; this.openAsBlob = async (path, options) => { const filename = (0, util_3.pathToFilename)(path); let link; try { link = this._core.getResolvedLinkOrThrow(filename, 'open'); } catch (error) { // Convert ENOENT to Node.js-compatible error for openAsBlob if (error && typeof error === 'object' && error.code === 'ENOENT') { const nodeError = new errors.TypeError('ERR_INVALID_ARG_VALUE'); throw nodeError; } throw error; } const node = link.getNode(); // Note: Node.js allows opening directories as blobs, so we don't throw EISDIR const buffer = node.getBuffer(); const type = options?.type || ''; return new Blob([buffer], { type }); }; this.glob = (pattern, ...args) => { const [options, callback] = args.length === 1 ? [{}, args[0]] : [args[0], args[1]]; this.wrapAsync(this._globSync, [pattern, options || {}], callback); }; this.globSync = (pattern, options = {}) => { return this._globSync(pattern, options); }; this._globSync = (pattern, options = {}) => { const { globSync } = require('./glob'); return globSync(this, pattern, options); }; this._opendir = (filename, options) => { const link = this._core.getResolvedLinkOrThrow(filename, 'scandir'); const node = link.getNode(); if (!node.isDirectory()) throw (0, util_3.createError)("ENOTDIR" /* ERROR_CODE.ENOTDIR */, 'scandir', filename); return new Dir_1.Dir(link, options); }; this.opendirSync = (path, options) => { const opts = (0, options_1.getOpendirOptions)(options); const filename = (0, util_3.pathToFilename)(path); return this._opendir(filename, opts); }; this.opendir = (path, a, b) => { const [options, callback] = (0, options_1.getOpendirOptsAndCb)(a, b); const filename = (0, util_3.pathToFilename)(path); this.wrapAsync(this._opendir, [filename, options], callback); }; const self = this; // tslint:disable-line no-this-assignment this.StatWatcher = class extends StatWatcher { constructor() { super(self); } }; const _ReadStream = FsReadStream; this.ReadStream = class extends _ReadStream { constructor(...args) { super(self, ...args); } }; const _WriteStream = FsWriteStream; this.WriteStream = class extends _WriteStream { constructor(...args) { super(self, ...args); } }; this.FSWatcher = class extends FSWatcher { constructor() { super(self); } }; const _realpath = (filename, encoding) => { const realLink = this._core.getResolvedLinkOrThrow(filename, 'realpath'); return (0, fs_node_utils_1.strToEncoding)(realLink.getPath() || '/', encoding); }; const realpathImpl = (path, a, b) => { const [opts, callback] = (0, options_1.getRealpathOptsAndCb)(a, b); const pathFilename = (0, util_3.pathToFilename)(path); self.wrapAsync(_realpath, [pathFilename, opts.encoding], callback); }; const realpathSyncImpl = (path, options) => _realpath((0, util_3.pathToFilename)(path), (0, options_1.getRealpathOptions)(options).encoding); this.realpath = realpathImpl; this.realpath.native = realpathImpl; this.realpathSync = realpathSyncImpl; this.realpathSync.native = realpathSyncImpl; } wrapAsync(method, args, callback) { (0, util_3.validateCallback)(callback); Promise.resolve().then(() => { let result; try { result = method.apply(this, args); } catch (err) { callback(err); return; } callback(null, result); }); } reset() { this._core.reset(); } toJSON(paths, json = {}, isRelative = false, asBuffer = false) { return this._core.toJSON(paths, json, isRelative, asBuffer); } fromJSON(json, cwd) { return this._core.fromJSON(json, cwd); } fromNestedJSON(json, cwd) { return this._core.fromNestedJSON(json, cwd); } // Legacy interface mountSync(mountpoint, json) { this._core.fromJSON(json, mountpoint); } _write(fd, buf, offset, length, position) { return this._core.write(fd, buf, offset, length, position); } writevBase(fd, buffers, position) { this._core.getFileByFdOrThrow(fd); let p = position ?? undefined; if (p === -1) { p = undefined; } let bytesWritten = 0; for (const buffer of buffers) { const nodeBuf = buffer_1.Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength); const bytes = this._core.write(fd, nodeBuf, 0, nodeBuf.byteLength, p ?? null); p = undefined; bytesWritten += bytes; if (bytes < nodeBuf.byteLength) break; } return bytesWritten; } _copyFile(src, dest, flags) { const buf = this.readFileSync(src); if (flags & COPYFILE_EXCL && this.existsSync(dest)) throw (0, util_3.createError)("EEXIST" /* ERROR_CODE.EEXIST */, 'copyFile', src, dest); if (flags & COPYFILE_FICLONE_FORCE) throw (0, util_3.createError)("ENOSYS" /* ERROR_CODE.ENOSYS */, 'copyFile', src, dest); this._core.writeFile(dest, buf, fs_node_utils_2.FLAGS.w, 438 /* MODE.DEFAULT */); } isSrcSubdir(src, dest) { try { const normalizedSrc = pathNormalize(src.startsWith('/') ? src : '/' + src); const normalizedDest = pathNormalize(dest.startsWith('/') ? dest : '/' + dest); if (normalizedSrc === normalizedDest) return true; // Check if dest is under src by using relative path // If dest is under src, the relative path from src to dest won't start with '..' const relativePath = pathRelative(normalizedSrc, normalizedDest); // If relative path is empty or doesn't start with '..', dest is under src return relativePath === '' || (!relativePath.startsWith('..') && !(0, path_1.isAbsolute)(relativePath)); } catch (error) { // If path operations fail, assume it's safe (don't block the copy) return false; } } cpFileSync(srcStat, destStat, src, dest, options) { if (destStat) { if (options.errorOnExist) throw (0, util_3.createError)("EEXIST" /* ERROR_CODE.EEXIST */, 'cp', dest); if (!options.force) return; this.unlinkSync(dest); } // Copy the file this.copyFileSync(src, dest, options.mode); // Preserve timestamps if requested if (options.preserveTimestamps) this.utimesSync(dest, srcStat.atime, srcStat.mtime); // Set file mode this.chmodSync(dest, Number(srcStat.mode)); } cpDirSync(srcStat, destStat, src, dest, options) { if (!destStat) { this.mkdirSync(dest); } // Read directory contents const entries = this.readdirSync(src); for (const entry of entries) { const srcItem = pathJoin(src, String(entry)); const destItem = pathJoin(dest, String(entry)); // Apply filter to each item if (options.filter && !options.filter(srcItem, destItem)) { continue; } this._cp(srcItem, destItem, options); } // Set directory mode this.chmodSync(dest, Number(srcStat.mode)); } cpSymlinkSync(destStat, src, dest, options) { let linkTarget = String(this.readlinkSync(src)); if (!options.verbatimSymlinks && !(0, path_1.isAbsolute)(linkTarget)) linkTarget = resolveCrossPlatform(pathDirname(src), linkTarget); if (destStat) this.unlinkSync(dest); this.symlinkSync(linkTarget, dest); } lstat(path, a, b) { const [{ throwIfNoEntry = true, bigint = false }, callback] = (0, options_1.getStatOptsAndCb)(a, b); this.wrapAsync(this._lstat, [(0, util_3.pathToFilename)(path), bigint, throwIfNoEntry], callback); } _stat(filename, bigint = false, throwIfNoEntry = true) { const result = this._core.getResolvedLinkResult(filename, 'stat'); if (result.ok) { return (0, fs_core_1.Ok)(Stats_1.default.build(result.value.getNode(), bigint)); } if (result.err.code === "ENOENT" /* ERROR_CODE.ENOENT */ && !throwIfNoEntry) { return (0, fs_core_1.Ok)(undefined); } else { return result; } } _statOrThrow(filename, bigint = false, throwIfNoEntry = true) { const result = this._stat(filename, bigint, throwIfNoEntry); if (result.ok) { return result.value; } else { throw result.err.toError(); } } statSync(path, options) { const { bigint = true, throwIfNoEntry = true } = (0, options_1.getStatOptions)(options); const result = this._stat((0, util_3.pathToFilename)(path), bigint, throwIfNoEntry); if (result.ok) { return result.value; } else { throw result.err.toError(); } } stat(path, a, b) { const [{ bigint = false, throwIfNoEntry = true }, callback] = (0, options_1.getStatOptsAndCb)(a, b); this.wrapAsync(this._statOrThrow, [(0, util_3.pathToFilename)(path), bigint, throwIfNoEntry], callback); } fstatBase(fd, bigint = false) { const file = this._core.getFileByFd(fd); if (!file) throw (0, util_3.createError)("EBADF" /* ERROR_CODE.EBADF */, 'fstat'); return Stats_1.default.build(file.node, bigint); } fstatSync(fd, options) { return this.fstatBase(fd, (0, options_1.getStatOptions)(options).bigint); } fstat(fd, a, b) { const [opts, callback] = (0, options_1.getStatOptsAndCb)(a, b); this.wrapAsync(this.fstatBase, [fd, opts.bigint], callback); } _exists(filename) { const result = this._stat(filename); return result.ok ? (0, fs_core_1.Ok)(true) : result; } _access(filename, mode) { const link = this._core.getResolvedLinkOrThrow(filename, 'access'); const node = link.getNode(); // F_OK (0) just checks for existence, which we already confirmed above if (mode === F_OK) { return; } // Check read permission if (mode & R_OK && !node.canRead()) { throw (0, util_3.createError)("EACCES" /* ERROR_CODE.EACCES */, 'access', filename); } // Check write permission if (mode & W_OK && !node.canWrite()) { throw (0, util_3.createError)("EACCES" /* ERROR_CODE.EACCES */, 'access', filename); } // Check execute permission if (mode & X_OK && !node.canExecute()) { throw (0, util_3.createError)("EACCES" /* ERROR_CODE.EACCES */, 'access', filename); } } watchFile(path, a, b) { const filename = (0, util_3.pathToFilename)(path); let options = a; let listener = b; if (typeof options === 'function') { listener = a; options = null; } if (typeof listener !== 'function') { throw new TypeError('The "listener" argument must be of type function. Received ' + typeof listener); } let interval = 5007; let persistent = true; let bigint = false; if (options && typeof options === 'object') { if (options.interval !== undefined) { if (typeof options.interval !== 'number') throw new TypeError('The "options.interval" property must be of type number. Received ' + typeof options.interval); if (!Number.isInteger(options.interval) || options.interval < 0 || options.interval > 0xffffffff) throw new RangeError('The value of "options.interval" is out of range. ' + `It must be an integer >= 0 && <= 4294967295. Received ${options.interval}`); interval = options.interval; } if (typeof options.persistent === 'boolean') persistent = options.persistent; if (typeof options.bigint === 'boolean') bigint = options.bigint; } let watcher = this.statWatchers[filename]; if (!watcher) { watcher = new this.StatWatcher(); watcher.start(filename, persistent, interval, bigint); this.statWatchers[filename] = watcher; } watcher.addListener('change', listener); return watcher; } unwatchFile(path, listener) { const filename = (0, util_3.pathToFilename)(path); const watcher = this.statWatchers[filename]; if (!watcher) return; if (typeof listener === 'function') { watcher.removeListener('change', listener); } else { watcher.removeAllListeners('change'); } if (watcher.listenerCount('change') === 0) { watcher.stop(); delete this.statWatchers[filename]; } } createReadStream(path, options) { return new this.ReadStream(path, options); } createWriteStream(path, options) { return new this.WriteStream(path, options); } watch(path, options, listener) { const filename = (0, util_3.pathToFilename)(path); let givenOptions = options; if (typeof options === 'function') { listener = options; givenOptions = null; } // tslint:disable-next-line prefer-const let { persistent, recursive, encoding, signal, throwIfNoEntry, ignore } = (0, options_1.getDefaultOpts)(givenOptions); if (persistent === undefined) persistent = true; if (recursive === undefined) recursive = false; const watcher = new this.FSWatcher(); try { watcher.start(filename, persistent, recursive, encoding, ignore); } catch (err) { if (throwIfNoEntry === false && err.code === 'ENOENT') return watcher; throw err; } if (listener) { watcher.addListener('change', listener); } if (signal) { if (signal.aborted) { watcher.close(); } else { const onAbort = () => watcher.close(); signal.addEventListener('abort', onAbort, { once: true }); watcher.once('close', () => signal.removeEventListener('abort', onAbort)); } } return watcher; } _statfs(filename, bigint = false) { // Verify the path exists to match Node.js behavior this._core.getResolvedLinkOrThrow(filename, 'statfs'); return StatFs_1.default.build(this._core, bigint); } statfsSync(path, options) { const { bigint = false } = (0, options_1.getStatfsOptions)(options); return this._statfs((0, util_3.pathToFilename)(path), bigint); } statfs(path, a, b) { const [{ bigint = false }, callback] = (0, options_1.getStatfsOptsAndCb)(a, b); this.wrapAsync(this._statfs, [(0, util_3.pathToFilename)(path), bigint], callback); } // ---------------------------------------------------------------- Tree View toTree(opts = { separator: path_1.sep }) { return (0, fs_print_1.toTreeSync)(this, opts); } // ---------------------------------------------------------------- Snapshots toSnapshot(path = '/') { return fsSnapshot.toSnapshotSync({ fs: this, path }); } fromSnapshot(snapshot, path = '/') { return fsSnapshot.fromSnapshotSync(snapshot, { fs: this, path }); } toBinarySnapshot(path = '/') { return fsSnapshot.toBinarySnapshotSync({ fs: this, path }); } fromBinarySnapshot(binary, path = '/') { return fsSnapshot.fromBinarySnapshotSync(binary, { fs: this, path }); } toJsonSnapshot(path = '/') { const uint8 = fsSnapshot.toJsonSnapshotSync({ fs: this, path }); return buffer_1.Buffer.from(uint8).toString('utf8'); } fromJsonSnapshot(json, path = '/') { const uint8 = new Uint8Array(buffer_1.Buffer.from(json, 'utf8')); return fsSnapshot.fromJsonSnapshotSync(uint8, { fs: this, path }); } } exports.Volume = Volume; Volume.fromJSON = (json, cwd, opts) => new Volume(fs_core_1.Superblock.fromJSON(json, cwd, opts)); Volume.fromNestedJSON = (json, cwd, opts) => new Volume(fs_core_1.Superblock.fromNestedJSON(json, cwd, opts)); function emitStop(self) { self.emit('stop'); } /** All-zero stats passed to `watchFile` listeners when the file is missing, like in Node.js. */ function buildZeroStats(bigint) { const stats = new Stats_1.default(); const zero = bigint ? BigInt(0) : 0; const epoch = new Date(0); stats.uid = zero; stats.gid = zero; stats.rdev = zero; stats.blksize = zero; stats.ino = zero; stats.size = zero; stats.blocks = zero; stats.atime = epoch; stats.mtime = epoch; stats.ctime = epoch; stats.birthtime = epoch; stats.atimeMs = zero; stats.mtimeMs = zero; stats.ctimeMs = zero; stats.birthtimeMs = zero; if (bigint) { stats.atimeNs = zero; stats.mtimeNs = zero; stats.ctimeNs = zero; stats.birthtimeNs = zero; } stats.dev = zero; stats.mode = zero; stats.nlink = zero; return stats; } class StatWatcher extends events_1.EventEmitter { constructor(vol) { super(); this.bigint = false; this.fileExists = false; this.stopped = false; this.onInterval = () => { if (this.stopped) return; const stats = this.statSafe(); if (stats) { if (!this.fileExists) { // Reappearance: `prev` is the stats from before the file disappeared, not the zeroed stats. this.fileExists = true; const prev = this.prev; this.prev = stats; this.emit('change', stats, prev); } else if (this.hasChanged(stats)) { const prev = this.prev; this.prev = stats; this.emit('change', stats, prev); } } else if (this.fileExists) { // Disappearance is reported once, with zeroed current stats, `prev` // keeps the last real stats for the eventual reappearance event. this.fileExists = false; this.emit('change', buildZeroStats(this.bigint), this.prev); } this.loop(); }; this.vol = vol; } loop() { clearTimeout(this.timeoutRef); this.timeoutRef = this.setTimeout(this.onInterval, this.interval); } hasChanged(stats) { const prev = this.prev; return (stats.mtimeMs !== prev.mtimeMs || stats.ctimeMs !== prev.ctimeMs || stats.size !== prev.size || stats.ino !== prev.ino || stats.mode !== prev.mode || stats.uid !== prev.uid || stats.gid !== prev.gid || stats.nlink !== prev.nlink); } statSafe() { try { return (this.bigint ? this.vol.statSync(this.filename, { bigint: true }) : this.vol.statSync(this.filename)); } catch { return null; } } start(path, persistent = true, interval = 5007, bigint = false) { this.filename = (0, util_3.pathToFilename)(path); this.setTimeout = persistent ? setTimeout.bind(typeof globalThis !== 'undefined' ? globalThis : global) : setTimeoutUnref_1.default; this.interval = interval; this.bigint = bigint; const stats = this.statSafe(); if (stats) { this.prev = stats; this.fileExists = true; } else { // Watching a missing path does not throw, the listener is called once // with zeroed stats, then again when the file is created. this.prev = buildZeroStats(bigint); this.fileExists = false; queueMicrotask(() => { if (!this.stopped) this.emit('change', this.prev, this.prev); }); } this.loop(); } stop() { this.stopped = true; clearTimeout(this.timeoutRef); queueMicrotask(() => { emitStop.call(this, this); }); } /** * Keep the event loop alive while the watcher is active. Polling continues * either way; this only controls whether the process is kept running. */ ref() { this.setTimeout = setTimeout.bind(typeof globalThis !== 'undefined' ? globalThis : global); const ref = this.timeoutRef; if (ref && typeof ref === 'object' && typeof ref.ref === 'function') ref.ref(); return this; } /** Let the process exit while the watcher is active. Polling continues. */ unref() { this.setTimeout = setTimeoutUnref_1.default; const ref = this.timeoutRef; if (ref && typeof ref === 'object' && typeof ref.unref === 'function') ref.unref(); return this; } } exports.StatWatcher = StatWatcher; /* tslint:disable no-var-keyword prefer-const */ // ---------------------------------------- ReadStream var pool; function allocNewPool(poolSize) { pool = (0, buffer_1.bufferAllocUnsafe)(poolSize); pool.used = 0; } (0, util_2.inherits)(FsReadStream, stream_1.Readable); exports.ReadStream = FsReadStream; function FsReadStream(vol, path, options) { if (!(this instanceof FsReadStream)) return new FsReadStream(vol, path, options); this._vol = vol; // a little bit bigger buffer and water marks by default options = Object.assign({}, (0, options_1.getOptions)(options, {})); if (options.highWaterMark === undefined) options.highWaterMark = 64 * 1024; stream_1.Readable.call(this, options); this.path = (0, util_3.pathToFilename)(path); this._fileHandle = options.fd && typeof options.fd !== 'number' ? options.fd : null; this.fd = options.fd === undefined ? null : typeof options.fd !== 'number' ? options.fd.fd : options.fd; this.flags = options.flags === undefined ? 'r' : options.flags; this.mode = options.mode === undefined ? 0o666 : options.mode; this.start = options.start; this.end = options.end; this.autoClose = options.autoClose === undefined ? true : options.autoClose; this.pos = undefined; this.bytesRead = 0; if (this.start !== undefined) { if (typeof this.start !== 'number') { throw new TypeError('"start" option must be a Number'); } if (this.end === undefined) { this.end = Infinity; } else if (typeof this.end !== 'number') { throw new TypeError('"end" option must be a Number'); } if (this.start > this.end) { throw new Error('"start" option must be <= "end" option'); } this.pos = this.start; } if (typeof this.fd !== 'number') this.open(); this.on('end', function () { if (this.autoClose) { if (this.destroy) this.destroy(); } }); } FsReadStream.prototype.open = function () { var self = this; // tslint:disable-line no-this-assignment this._vol.open(this.path, this.flags, this.mode, (er, fd) => { if (er) { if (self.autoClose) { if (self.destroy) self.destroy(); } self.emit('error', er); return; } self.fd = fd; self.emit('open', fd); // start the flow of data. self.read(); }); }; FsReadStream.prototype._read = function (n) { if (typeof this.fd !== 'number') { return this.once('open', function () { this._read(n); }); } if (this.destroyed) return; if (!pool || pool.length - pool.used < kMinPoolSpace) { // discard the old pool. allocNewPool(this._readableState.highWaterMark); } // Grab another reference to the pool in the case that while we're // in the thread pool another read() finishes up the pool, and // allocates a new one. var thisPool = pool; var toRead = Math.min(pool.length - pool.used, n); var start = pool.used; if (this.pos !== undefined) toRead = Math.min(this.end - this.pos + 1, toRead); // already read everything we were supposed to read! // treat as EOF. if (toRead <= 0) return this.push(null); // the actual read. var self = this; // tslint:disable-line no-this-assignment this._vol.read(this.fd, pool, pool.used, toRead, this.pos, onread); // move the pool positions, and internal position for reading. if (this.pos !== undefined) this.pos += toRead; pool.used += toRead; function onread(er, bytesRead) { if (er) { if (self.autoClose && self.destroy) { self.destroy(); } self.emit('error', er); } else { var b = null; if (bytesRead > 0) { self.bytesRead += bytesRead; b = thisPool.slice(start, start + bytesRead); } self.push(b); } } }; FsReadStream.prototype._destroy = function (err, cb) { this.close(err2 => { cb(err || err2); }); }; FsReadStream.prototype.close = function (cb) { if (cb) this.once('close', cb); if (this.closed || typeof this.fd !== 'number') { if (typeof this.fd !== 'number') { this.once('open', closeOnOpen); return; } return queueMicrotask(() => this.emit('close')); } // Since Node 18, there is only a getter for '.closed'. // The first branch mimics other setters from Readable. // See https://github.com/nodejs/node/blob/v18.0.0/lib/internal/streams/readable.js#L1243 if (typeof this._readableState?.closed === 'boolean') { this._readableState.closed = true; } else { this.closed = true; } if (this._fileHandle) { this._fileHandle.close().then(() => this.emit('close'), er => this.emit('error', er)); } else { this._vol.close(this.fd, er => { if (er) this.emit('error', er); else this.emit('close'); }); } this.fd = null; }; // needed because as it will be called with arguments // that does not match this.close() signature function closeOnOpen(fd) { this.close(); } (0, util_2.inherits)(FsWriteStream, stream_1.Writable); exports.WriteStream = FsWriteStream; function FsWriteStream(vol, path, options) { if (!(this instanceof FsWriteStream)) return new FsWriteStream(vol, path, options); this._vol = vol; options = Object.assign({}, (0, options_1.getOptions)(options, {})); stream_1.Writable.call(this, options); this.path = (0, util_3.pathToFilename)(path); this._fileHandle = options.fd && typeof options.fd !== 'number' ? options.fd : null; this.fd = options.fd === undefined ? null : typeof options.fd !== 'number' ? options.fd.fd : options.fd; this.flags = options.flags === undefined ? 'w' : options.flags; this.mode = options.mode === undefined ? 0o666 : options.mode; this.start = options.start; this.autoClose = options.autoClose === undefined ? true : !!options.autoClose; this.pos = undefined; this.bytesWritten = 0; this.pending = true; if (this.start !== undefined) { if (typeof this.start !== 'number') { throw new TypeError('"start" option must be a Number'); } if (this.start < 0) { throw new Error('"start" must be >= zero'); } this.pos = this.start; } if (options.encoding) this.setDefaultEncoding(options.encoding); if (typeof this.fd !== 'number') this.open(); // dispose on finish. this.once('finish', function () { if (this.autoClose) { this.close(); } }); } FsWriteStream.prototype.open = function () { this._vol.open(this.path, this.flags, this.mode, function (er, fd) { if (er) { if (this.autoClose && this.destroy) { this.destroy(); } this.emit('error', er); return; } this.fd = fd; this.pending = false; this.emit('open', fd); }.bind(this)); }; FsWriteStream.prototype._write = function (data, encoding, cb) { if (!(data instanceof buffer_1.Buffer || data instanceof Uint8Array)) return this.emit('error', new Error('Invalid data')); if (typeof this.fd !== 'number') { return this.once('open', function () { this._write(data, encoding, cb); }); } var self = this; // tslint:disable-line no-this-assignment this._vol.write(this.fd, data, 0, data.length, this.pos, (er, bytes) => { if (er) { if (self.autoClose && self.destroy) { self.destroy(); } return cb(er); } self.bytesWritten += bytes; cb(); }); if (this.pos !== undefined) this.pos += data.length; }; FsWriteStream.prototype._writev = function (data, cb) { if (typeof this.fd !== 'number') { return this.once('open', function () { this._writev(data, cb); }); } const self = this; // tslint:disable-line no-this-assignment const len = data.length; const chunks = new Array(len); var size = 0; for (var i = 0; i < len; i++) { var chunk = data[i].chunk; chunks[i] = chunk; size += chunk.length; } const buf = buffer_1.Buffer.concat(chunks); this._vol.write(this.fd, buf, 0, buf.length, this.pos, (er, bytes) => { if (er) { if (self.destroy) self.destroy(); return cb(er); } self.bytesWritten += bytes; cb(); }); if (this.pos !== undefined) this.pos += size; }; FsWriteStream.prototype.close = function (cb) { if (cb) this.once('close', cb); if (this.closed || typeof this.fd !== 'number') { if (typeof this.fd !== 'number') { this.once('open', closeOnOpen); return; } return queueMicrotask(() => this.emit('close')); } // Since Node 18, there is only a getter for '.closed'. // The first branch mimics other setters from Writable. // See https://github.com/nodejs/node/blob/v18.0.0/lib/internal/streams/writable.js#L766 if (typeof this._writableState?.closed === 'boolean') { this._writableState.closed = true; } else { this.closed = true; } if (this._fileHandle) { this._fileHandle.close().then(() => this.emit('close'), er => this.emit('error', er)); } else { this._vol.close(this.fd, er => { if (er) this.emit('error', er); else this.emit('close'); }); } this.fd = null; }; FsWriteStream.prototype._destroy = FsReadStream.prototype._destroy; // There is no shutdown() for files. FsWriteStream.prototype.destroySoon = FsWriteStream.prototype.end; // ---------------------------------------- FSWatcher class FSWatcher extends events_1.EventEmitter { constructor(vol) { super(); this._filename = ''; this._filenameEncoded = ''; this._recursive = false; this._encoding = fs_node_utils_1.ENCODING_UTF8; this._closed = false; this._onEvent = (event) => { switch (event.type) { case 0 /* FsEventType.CREATE */: case 1 /* FsEventType.DELETE */: this._emit('rename', event.steps); break; case 4 /* FsEventType.MOVE */: if (event.oldSteps) this._emit('rename', event.oldSteps); this._emit('rename', event.steps); break; case 2 /* FsEventType.MODIFY */: case 3 /* FsEventType.ATTRIB */: this._emit('change', event.steps); break; } }; this._persist = () => { this._timer = setTimeout(this._persist, 1e6); }; this._vol = vol; } _emit(type, relativeSteps) { const filename = relativeSteps.length ? relativeSteps.join(pathSep) : this._watcher.link.getName(); if (this._ignore && this._ignore(filename)) return; this.emit('change', type, (0, fs_node_utils_1.strToEncoding)(filename, this._encoding)); } /** * Keep the event loop alive while the watcher is active. All watchers are * ref'ed by default (unless started with `persistent: false`). No-op when * already ref'ed, closed, or never started. */ ref() { if (this._watcher && !this._closed && !this._timer) this._persist(); return this; } /** * Let the process exit while the watcher is active. Events are still * delivered. No-op when already unref'ed. */ unref() { clearTimeout(this._timer); this._timer = undefined; return this; } start(path, persistent = true, recursive = false, encoding = fs_node_utils_1.ENCODING_UTF8, ignore) { this._filename = (0, util_3.pathToFilename)(path); this._steps = (0, fs_core_1.filenameToSteps)(this._filename); this._filenameEncoded = (0, fs_node_utils_1.strToEncoding)(this._filename); this._recursive = recursive; this._encoding = encoding; this._ignore = ignore === undefined ? undefined : (0, fs_node_utils_1.watchIgnoreToMatcher)(ignore); try { this._watcher = new fs_core_1.CoreWatcher(this._vol._core, this._filename, { recursive }); } catch (err) { const error = new Error(`watch ${this._filename} ${err.code}`); error.code = err.code; error.errno = err.code; throw error; } this._link = this._watcher.link; this._watcher.changes.listen(this._onEvent); if (persistent) this._persist(); } close() { clearTimeout(this._timer); this._timer = undefined; if (!this._watcher || this._closed) return; this._closed = true; this._watcher.close(); queueMicrotask(() => this.emit('close')); } } exports.FSWatcher = FSWatcher; //# sourceMappingURL=volume.js.map