"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var fs_exports = {}; __export(fs_exports, { FS: () => FS, FSPath: () => FSPath }); module.exports = __toCommonJS(fs_exports); var fs = __toESM(require("fs")); var pathModule = __toESM(require("path")); var import_streams = require("./streams"); /** * FS * Pokemon Showdown - http://pokemonshowdown.com/ * * An abstraction layer around Node's filesystem. * * Advantages: * - write() etc do nothing in unit tests * - paths are always relative to PS's base directory * - Promises (seriously wtf Node Core what are you thinking) * - PS-style API: FS("foo.txt").write("bar") for easier argument order * - mkdirp * * FS is used nearly everywhere, but exceptions include: * - crashlogger.js - in case the crash is in here * - repl.js - which use Unix sockets out of this file's scope * - launch script - happens before modules are loaded * - sim/ - intended to be self-contained * * @author Guangcong Luo * @license MIT */ const DIST = `${pathModule.sep}dist${pathModule.sep}`; const ROOT_PATH = pathModule.resolve(__dirname, __dirname.includes(DIST) ? ".." : "", ".."); if (!global.__fsState) { global.__fsState = { pendingUpdates: /* @__PURE__ */ new Map() }; } class FSPath { constructor(path) { this.path = pathModule.resolve(ROOT_PATH, path); } parentDir() { return new FSPath(pathModule.dirname(this.path)); } read(options = "utf8") { if (typeof options !== "string" && options.encoding === void 0) { options.encoding = "utf8"; } return new Promise((resolve, reject) => { fs.readFile(this.path, options, (err, data) => { err ? reject(err) : resolve(data); }); }); } readSync(options = "utf8") { if (typeof options !== "string" && options.encoding === void 0) { options.encoding = "utf8"; } return fs.readFileSync(this.path, options); } readBuffer(options = {}) { return new Promise((resolve, reject) => { fs.readFile(this.path, options, (err, data) => { err ? reject(err) : resolve(data); }); }); } readBufferSync(options = {}) { return fs.readFileSync(this.path, options); } exists() { return new Promise((resolve) => { fs.exists(this.path, (exists) => { resolve(exists); }); }); } existsSync() { return fs.existsSync(this.path); } readIfExists() { return new Promise((resolve, reject) => { fs.readFile(this.path, "utf8", (err, data) => { if (err && err.code === "ENOENT") return resolve(""); err ? reject(err) : resolve(data); }); }); } readIfExistsSync() { try { return fs.readFileSync(this.path, "utf8"); } catch (err) { if (err.code !== "ENOENT") throw err; } return ""; } write(data, options = {}) { if (global.Config?.nofswriting) return Promise.resolve(); return new Promise((resolve, reject) => { fs.writeFile(this.path, data, options, (err) => { err ? reject(err) : resolve(); }); }); } writeSync(data, options = {}) { if (global.Config?.nofswriting) return; return fs.writeFileSync(this.path, data, options); } /** * Writes to a new file before renaming to replace an old file. If * the process crashes while writing, the old file won't be lost. * Does not protect against simultaneous writing; use writeUpdate * for that. */ async safeWrite(data, options = {}) { await FS(this.path + ".NEW").write(data, options); await FS(this.path + ".NEW").rename(this.path); } safeWriteSync(data, options = {}) { FS(this.path + ".NEW").writeSync(data, options); FS(this.path + ".NEW").renameSync(this.path); } /** * Safest way to update a file with in-memory state. Pass a callback * that fetches the data to be written. It will write an update, * avoiding race conditions. The callback may not necessarily be * called, if `writeUpdate` is called many times in a short period. * * `options.throttle`, if it exists, will make sure updates are not * written more than once every `options.throttle` milliseconds. * * No synchronous version because there's no risk of race conditions * with synchronous code; just use `safeWriteSync`. */ writeUpdate(dataFetcher, options = {}) { if (global.Config?.nofswriting) return; const pendingUpdate = __fsState.pendingUpdates.get(this.path); const throttleTime = options.throttle ? Date.now() + options.throttle : 0; if (pendingUpdate) { pendingUpdate.pendingDataFetcher = dataFetcher; pendingUpdate.pendingOptions = options; if (pendingUpdate.throttleTimer && throttleTime < pendingUpdate.throttleTime) { pendingUpdate.throttleTime = throttleTime; clearTimeout(pendingUpdate.throttleTimer); pendingUpdate.throttleTimer = setTimeout(() => this.checkNextUpdate(), throttleTime - Date.now()); } return; } if (!throttleTime) { this.writeUpdateNow(dataFetcher, options); return; } const update = { isWriting: false, pendingDataFetcher: dataFetcher, pendingOptions: options, throttleTime, throttleTimer: setTimeout(() => this.checkNextUpdate(), throttleTime - Date.now()) }; __fsState.pendingUpdates.set(this.path, update); } writeUpdateNow(dataFetcher, options) { const throttleTime = options.throttle ? Date.now() + options.throttle : 0; const update = { isWriting: true, pendingDataFetcher: null, pendingOptions: null, throttleTime, throttleTimer: null }; __fsState.pendingUpdates.set(this.path, update); void this.safeWrite(dataFetcher(), options).then(() => this.finishUpdate()); } checkNextUpdate() { const pendingUpdate = __fsState.pendingUpdates.get(this.path); if (!pendingUpdate) throw new Error(`FS: Pending update not found`); if (pendingUpdate.isWriting) throw new Error(`FS: Conflicting update`); const { pendingDataFetcher: dataFetcher, pendingOptions: options } = pendingUpdate; if (!dataFetcher || !options) { __fsState.pendingUpdates.delete(this.path); return; } this.writeUpdateNow(dataFetcher, options); } finishUpdate() { const pendingUpdate = __fsState.pendingUpdates.get(this.path); if (!pendingUpdate) throw new Error(`FS: Pending update not found`); if (!pendingUpdate.isWriting) throw new Error(`FS: Conflicting update`); pendingUpdate.isWriting = false; const throttleTime = pendingUpdate.throttleTime; if (!throttleTime || throttleTime < Date.now()) { this.checkNextUpdate(); return; } pendingUpdate.throttleTimer = setTimeout(() => this.checkNextUpdate(), throttleTime - Date.now()); } append(data, options = {}) { if (global.Config?.nofswriting) return Promise.resolve(); return new Promise((resolve, reject) => { fs.appendFile(this.path, data, options, (err) => { err ? reject(err) : resolve(); }); }); } appendSync(data, options = {}) { if (global.Config?.nofswriting) return; return fs.appendFileSync(this.path, data, options); } symlinkTo(target) { if (global.Config?.nofswriting) return Promise.resolve(); return new Promise((resolve, reject) => { fs.symlink(target, this.path, (err) => { err ? reject(err) : resolve(); }); }); } symlinkToSync(target) { if (global.Config?.nofswriting) return; return fs.symlinkSync(target, this.path); } copyFile(dest) { if (global.Config?.nofswriting) return Promise.resolve(); return new Promise((resolve, reject) => { fs.copyFile(this.path, dest, (err) => { err ? reject(err) : resolve(); }); }); } rename(target) { if (global.Config?.nofswriting) return Promise.resolve(); return new Promise((resolve, reject) => { fs.rename(this.path, target, (err) => { err ? reject(err) : resolve(); }); }); } renameSync(target) { if (global.Config?.nofswriting) return; return fs.renameSync(this.path, target); } readdir() { return new Promise((resolve, reject) => { fs.readdir(this.path, (err, data) => { err ? reject(err) : resolve(data); }); }); } readdirSync() { return fs.readdirSync(this.path); } async readdirIfExists() { if (await this.exists()) return this.readdir(); return Promise.resolve([]); } readdirIfExistsSync() { if (this.existsSync()) return this.readdirSync(); return []; } createReadStream() { return new FileReadStream(this.path); } createWriteStream(options = {}) { if (global.Config?.nofswriting) { return new import_streams.WriteStream({ write() { } }); } return new import_streams.WriteStream(fs.createWriteStream(this.path, options)); } createAppendStream(options = {}) { if (global.Config?.nofswriting) { return new import_streams.WriteStream({ write() { } }); } options.flags = options.flags || "a"; return new import_streams.WriteStream(fs.createWriteStream(this.path, options)); } unlinkIfExists() { if (global.Config?.nofswriting) return Promise.resolve(); return new Promise((resolve, reject) => { fs.unlink(this.path, (err) => { if (err && err.code === "ENOENT") return resolve(); err ? reject(err) : resolve(); }); }); } unlinkIfExistsSync() { if (global.Config?.nofswriting) return; try { fs.unlinkSync(this.path); } catch (err) { if (err.code !== "ENOENT") throw err; } } async rmdir(recursive) { if (global.Config?.nofswriting) return Promise.resolve(); return new Promise((resolve, reject) => { fs.rmdir(this.path, { recursive }, (err) => { err ? reject(err) : resolve(); }); }); } rmdirSync(recursive) { if (global.Config?.nofswriting) return; return fs.rmdirSync(this.path, { recursive }); } mkdir(mode = 493) { if (global.Config?.nofswriting) return Promise.resolve(); return new Promise((resolve, reject) => { fs.mkdir(this.path, mode, (err) => { err ? reject(err) : resolve(); }); }); } mkdirSync(mode = 493) { if (global.Config?.nofswriting) return; return fs.mkdirSync(this.path, mode); } mkdirIfNonexistent(mode = 493) { if (global.Config?.nofswriting) return Promise.resolve(); return new Promise((resolve, reject) => { fs.mkdir(this.path, mode, (err) => { if (err && err.code === "EEXIST") return resolve(); err ? reject(err) : resolve(); }); }); } mkdirIfNonexistentSync(mode = 493) { if (global.Config?.nofswriting) return; try { fs.mkdirSync(this.path, mode); } catch (err) { if (err.code !== "EEXIST") throw err; } } /** * Creates the directory (and any parent directories if necessary). * Does not throw if the directory already exists. */ async mkdirp(mode = 493) { try { await this.mkdirIfNonexistent(mode); } catch (err) { if (err.code !== "ENOENT") throw err; await this.parentDir().mkdirp(mode); await this.mkdirIfNonexistent(mode); } } /** * Creates the directory (and any parent directories if necessary). * Does not throw if the directory already exists. Synchronous. */ mkdirpSync(mode = 493) { try { this.mkdirIfNonexistentSync(mode); } catch (err) { if (err.code !== "ENOENT") throw err; this.parentDir().mkdirpSync(mode); this.mkdirIfNonexistentSync(mode); } } /** Calls the callback if the file is modified. */ onModify(callback) { fs.watchFile(this.path, (curr, prev) => { if (curr.mtime > prev.mtime) return callback(); }); } /** Clears callbacks added with onModify(). */ unwatch() { fs.unwatchFile(this.path); } async isFile() { return new Promise((resolve, reject) => { fs.stat(this.path, (err, stats) => { err ? reject(err) : resolve(stats.isFile()); }); }); } isFileSync() { return fs.statSync(this.path).isFile(); } async isDirectory() { return new Promise((resolve, reject) => { fs.stat(this.path, (err, stats) => { err ? reject(err) : resolve(stats.isDirectory()); }); }); } isDirectorySync() { return fs.statSync(this.path).isDirectory(); } async realpath() { return new Promise((resolve, reject) => { fs.realpath(this.path, (err, path) => { err ? reject(err) : resolve(path); }); }); } realpathSync() { return fs.realpathSync(this.path); } } class FileReadStream extends import_streams.ReadStream { constructor(file) { super(); this.fd = new Promise((resolve, reject) => { fs.open(file, "r", (err, fd) => err ? reject(err) : resolve(fd)); }); this.atEOF = false; } _read(size = 16384) { return new Promise((resolve, reject) => { if (this.atEOF) return void resolve(); this.ensureCapacity(size); void this.fd.then((fd) => { fs.read(fd, this.buf, this.bufEnd, size, null, (err, bytesRead, buf) => { if (err) return reject(err); if (!bytesRead) { this.atEOF = true; this.resolvePush(); return resolve(); } this.bufEnd += bytesRead; this.resolvePush(); resolve(); }); }); }); } _destroy() { return new Promise((resolve) => { void this.fd.then((fd) => { fs.close(fd, () => resolve()); }); }); } } function getFs(path) { return new FSPath(path); } const FS = Object.assign(getFs, { FileReadStream, FSPath, ROOT_PATH }); //# sourceMappingURL=fs.js.map