UNPKG

convex

Version:

Client for the Convex Cloud

437 lines (436 loc) 11.5 kB
"use strict"; import chalk from "chalk"; import stdFs from "fs"; import os from "os"; import path from "path"; const tmpDirOverrideVar = "CONVEX_TMPDIR"; function tmpDirPath() { const envTmpDir = process.env[tmpDirOverrideVar]; return envTmpDir ?? os.tmpdir(); } const tmpDirRoot = tmpDirPath(); let warned = false; function warnCrossFilesystem(dstPath) { const dstDir = path.dirname(dstPath); if (!warned) { console.warn( chalk.yellow( `Temporary directory '${tmpDirRoot}' and project directory '${dstDir}' are on different filesystems.` ) ); console.warn( chalk.gray( ` If you're running into errors with other tools watching the project directory, override the temporary directory location with the ${chalk.bold( tmpDirOverrideVar )} environment variable.` ) ); console.warn( chalk.gray( ` Be sure to pick a temporary directory that's on the same filesystem as your project.` ) ); warned = true; } } export async function mkdtemp(prefix, callback) { const tmpPath = stdFs.mkdtempSync(path.join(tmpDirRoot, prefix)); try { await callback({ tmpPath }); } finally { stdFs.rmSync(tmpPath, { force: true, recursive: true }); } } class NodeFs { listDir(dirPath) { return stdFs.readdirSync(dirPath, { withFileTypes: true }); } exists(path2) { try { stdFs.statSync(path2); return true; } catch (e) { if (e.code === "ENOENT") { return false; } throw e; } } stat(path2) { return stdFs.statSync(path2); } readUtf8File(path2) { return stdFs.readFileSync(path2, { encoding: "utf-8" }); } createReadStream(path2) { return stdFs.createReadStream(path2); } access(path2) { return stdFs.accessSync(path2); } writeUtf8File(path2, contents, mode) { const fd = stdFs.openSync(path2, "w", mode); try { stdFs.writeFileSync(fd, contents, { encoding: "utf-8" }); stdFs.fsyncSync(fd); } finally { stdFs.closeSync(fd); } } mkdir(dirPath, options) { try { stdFs.mkdirSync(dirPath); } catch (e) { if (options?.allowExisting && e.code === "EEXIST") { return; } throw e; } } rm(path2, options) { stdFs.rmSync(path2, options); } rmdir(path2) { stdFs.rmdirSync(path2); } unlink(path2) { return stdFs.unlinkSync(path2); } renameFile(fromPath, toPath) { try { return stdFs.renameSync(fromPath, toPath); } catch (e) { if (e.code === "EXDEV") { warnCrossFilesystem(toPath); stdFs.copyFileSync(fromPath, toPath); return; } throw e; } } registerPath(_path, _st) { } invalidate() { } } export const nodeFs = new NodeFs(); export class RecordingFs { constructor(traceEvents) { this.observedDirectories = /* @__PURE__ */ new Map(); this.observedFiles = /* @__PURE__ */ new Map(); this.invalidated = false; this.traceEvents = traceEvents; } listDir(dirPath) { const absDirPath = path.resolve(dirPath); const dirSt = nodeFs.stat(absDirPath); this.registerNormalized(absDirPath, dirSt); const entries = nodeFs.listDir(dirPath); for (const entry of entries) { const childPath = path.join(absDirPath, entry.name); const childSt = nodeFs.stat(childPath); this.registerPath(childPath, childSt); } const observedNames = new Set(entries.map((e) => e.name)); const existingNames = this.observedDirectories.get(absDirPath); if (existingNames) { if (!setsEqual(observedNames, existingNames)) { if (this.traceEvents) { console.log( "Invalidating due to directory children mismatch", observedNames, existingNames ); } this.invalidated = true; } } this.observedDirectories.set(absDirPath, observedNames); return entries; } exists(path2) { try { const st = nodeFs.stat(path2); this.registerPath(path2, st); return true; } catch (err) { if (err.code === "ENOENT") { this.registerPath(path2, null); return false; } throw err; } } stat(path2) { try { const st = nodeFs.stat(path2); this.registerPath(path2, st); return st; } catch (err) { if (err.code === "ENOENT") { this.registerPath(path2, null); } throw err; } } readUtf8File(path2) { try { const st = nodeFs.stat(path2); this.registerPath(path2, st); return nodeFs.readUtf8File(path2); } catch (err) { if (err.code === "ENOENT") { this.registerPath(path2, null); } throw err; } } createReadStream(path2) { try { const st = nodeFs.stat(path2); this.registerPath(path2, st); return nodeFs.createReadStream(path2); } catch (err) { if (err.code === "ENOENT") { this.registerPath(path2, null); } throw err; } } access(path2) { try { const st = nodeFs.stat(path2); this.registerPath(path2, st); return nodeFs.access(path2); } catch (err) { if (err.code === "ENOENT") { this.registerPath(path2, null); } throw err; } } writeUtf8File(filePath, contents, mode) { const absPath = path.resolve(filePath); nodeFs.writeUtf8File(filePath, contents, mode); this.updateOnWrite(absPath); } mkdir(dirPath, options) { const absPath = path.resolve(dirPath); try { stdFs.mkdirSync(absPath); } catch (e) { if (options?.allowExisting && e.code === "EEXIST") { const st = nodeFs.stat(absPath); this.registerNormalized(absPath, st); return; } throw e; } this.updateOnWrite(absPath); } rm(entityPath, options) { const absPath = path.resolve(entityPath); const isDir = this.exists(absPath) && this.stat(absPath).isDirectory(); if (options?.recursive && isDir) { const entries = this.listDir(entityPath); for (const entry of entries) { this.rm(path.join(absPath, entry.name), options); } } try { if (isDir) { stdFs.rmdirSync(absPath); } else { stdFs.rmSync(absPath); } } catch (e) { const allowed = options?.force && e.code === "ENOENT"; if (!allowed) { throw e; } } this.updateOnDelete(absPath); } rmdir(dirPath) { const absPath = path.resolve(dirPath); stdFs.rmdirSync(absPath); this.updateOnDelete(absPath); } unlink(filePath) { const absPath = path.resolve(filePath); stdFs.unlinkSync(absPath); this.updateOnDelete(absPath); } renameFile(fromPath, toPath) { const absFromPath = path.resolve(fromPath); const absToPath = path.resolve(toPath); nodeFs.renameFile(absFromPath, absToPath); this.updateOnDelete(absFromPath); this.updateOnWrite(absToPath); } updateOnWrite(absPath) { const newSt = nodeFs.stat(absPath); this.observedFiles.set(absPath, newSt); const parentPath = path.resolve(path.dirname(absPath)); const observedParent = this.observedDirectories.get(parentPath); if (observedParent !== void 0) { observedParent.add(path.basename(absPath)); } } updateOnDelete(absPath) { this.observedFiles.set(absPath, null); const parentPath = path.resolve(path.dirname(absPath)); const observedParent = this.observedDirectories.get(parentPath); if (observedParent !== void 0) { observedParent.delete(path.basename(absPath)); } } registerPath(p, st) { const absPath = path.resolve(p); this.registerNormalized(absPath, st); } invalidate() { this.invalidated = true; } registerNormalized(absPath, observed) { const existing = this.observedFiles.get(absPath); if (existing !== void 0) { const stMatch = stMatches(observed, existing); if (!stMatch.matches) { if (this.traceEvents) { console.log( "Invalidating due to st mismatch", absPath, observed, existing, stMatch.reason ); } this.invalidated = true; } } this.observedFiles.set(absPath, observed); } finalize() { if (this.invalidated) { return "invalidated"; } return new Observations(this.observedDirectories, this.observedFiles); } } export class Observations { constructor(directories, files) { this.directories = directories; this.files = files; } paths() { const out = []; for (const path2 of this.directories.keys()) { out.push(path2); } for (const path2 of this.files.keys()) { out.push(path2); } return out; } overlaps({ absPath }) { let currentSt; try { currentSt = nodeFs.stat(absPath); } catch (e) { if (e.code === "ENOENT") { currentSt = null; } else { throw e; } } const observedSt = this.files.get(absPath); if (observedSt !== void 0) { const stMatch = stMatches(observedSt, currentSt); if (!stMatch.matches) { const reason = `modified (${stMatch.reason})`; return { overlaps: true, reason }; } } const parentPath = path.resolve(path.dirname(absPath)); const observedParent = this.directories.get(parentPath); if (observedParent !== void 0) { const filename = path.basename(absPath); if (currentSt === null && observedParent.has(filename)) { return { overlaps: true, reason: "deleted" }; } if (currentSt !== null && !observedParent.has(filename)) { return { overlaps: true, reason: "added" }; } } return { overlaps: false }; } } function setsEqual(a, b) { if (a.size !== b.size) { return false; } for (const elem of a.keys()) { if (!b.has(elem)) { return false; } } return true; } export function stMatches(a, b) { if (a === null && b === null) { return { matches: true }; } if (a !== null && b !== null) { if (a.dev !== b.dev) { return { matches: false, reason: "device boundary" }; } if (a.isFile() || b.isFile()) { if (!a.isFile() || !b.isFile()) { return { matches: false, reason: "file type" }; } if (a.ino !== b.ino) { return { matches: false, reason: `file inode (${a.ino} vs. ${b.ino})` }; } if (a.size !== b.size) { return { matches: false, reason: `file size (${a.size} vs. ${b.size})` }; } if (a.mtimeMs !== b.mtimeMs) { return { matches: false, reason: `file mtime (${a.mtimeMs} vs. ${b.mtimeMs})` }; } return { matches: true }; } if (a.isDirectory() || b.isDirectory()) { if (!b.isDirectory() || !b.isDirectory()) { return { matches: false, reason: "dir file type" }; } if (a.ino !== b.ino) { return { matches: false, reason: `dir inode (${a.ino} vs. ${b.ino})` }; } return { matches: true }; } if (a.ino !== b.ino) { return { matches: false, reason: `special inode (${a.ino} vs. ${b.ino})` }; } return { matches: true }; } return { matches: false, reason: "deleted mismatch" }; } //# sourceMappingURL=fs.js.map