UNPKG

@mdfriday/foundry

Version:

The core engine of MDFriday. Convert Markdown and shortcodes into fully themed static sites – Hugo-style, powered by TypeScript.

307 lines 9.73 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.OverlayFs = void 0; exports.newOverlayFs = newOverlayFs; exports.createOverlayFs = createOverlayFs; const type_1 = require("../type"); const overlayoptions_1 = require("../../../domain/fs/vo/overlayoptions"); const overlaydir_1 = require("./overlaydir"); const log_1 = require("../../../../pkg/log"); // Create domain-specific logger for fs operations const log = (0, log_1.getDomainLogger)('fs', { component: 'overlayfs' }); /** * OverlayFs entity - main overlay filesystem implementation * TypeScript version of Go's OverlayFs struct */ class OverlayFs { constructor(options) { this.fss = [...options.fss]; // Create a copy this.mergeDirs = options.dirsMerger; this.firstWritable = options.firstWritable; } /** * Get filesystem name */ name() { return 'overlayfs'; } /** * Implementation of FilesystemIterator interface */ filesystem(i) { if (i < 0 || i >= this.fss.length) { return null; } return this.fss[i]; } /** * Get number of filesystems in overlay */ numFilesystems() { return this.fss.length; } /** * Append filesystems to overlay (returns new instance) */ append(...fss) { const newOptions = new overlayoptions_1.OverlayOptions({ fss: [...this.fss, ...fss], firstWritable: this.firstWritable, dirsMerger: this.mergeDirs, }); return new OverlayFs(newOptions); } /** * Set directory merger (returns new instance) */ withDirsMerger(merger) { const newOptions = new overlayoptions_1.OverlayOptions({ fss: this.fss, firstWritable: this.firstWritable, dirsMerger: merger, }); return new OverlayFs(newOptions); } /** * Get writable filesystem (first filesystem if firstWritable is true) */ writeFs() { if (this.fss.length === 0) { throw type_1.ErrNoFilesystems; } return this.fss[0]; } /** * Collect directories from all filesystems */ async collectDirs(name, withFs) { for (const fs of this.fss) { await this.collectDirsRecursive(fs, name, withFs); } } /** * Recursively collect directories from filesystem hierarchy */ async collectDirsRecursive(fs, name, withFs) { try { const fi = await fs.stat(name); if (fi.isDir()) { withFs(fs); } } catch (error) { // File doesn't exist in this filesystem, continue } // Check if filesystem implements FilesystemIterator const fsi = fs; if (fsi.filesystem && fsi.numFilesystems) { for (let i = 0; i < fsi.numFilesystems(); i++) { const subFs = fsi.filesystem(i); if (subFs) { await this.collectDirsRecursive(subFs, name, withFs); } } } } /** * Stat file/directory across all filesystems */ async statInternal(name, lstatIfPossible) { for (let i = 0; i < this.fss.length; i++) { const fs = this.fss[i]; const [fs2, fi, ok, err] = await this.statRecursive(fs, name, lstatIfPossible); if (err === null || !this.isNotExistError(err)) { return [fs2, fi, ok, err]; } } return [null, null, false, type_1.ErrFileNotFound]; } /** * Recursive stat implementation */ async statRecursive(fs, name, lstatIfPossible) { try { // For now, we'll use regular stat since TypeScript doesn't have lstat equivalent const fi = await fs.stat(name); return [fs, fi, false, null]; } catch (error) { if (!this.isNotExistError(error)) { return [fs, null, false, error]; } } // Check if filesystem implements FilesystemIterator const fsi = fs; if (fsi.filesystem && fsi.numFilesystems) { for (let i = 0; i < fsi.numFilesystems(); i++) { const subFs = fsi.filesystem(i); if (subFs) { const [fs2, fi, ok, err] = await this.statRecursive(subFs, name, lstatIfPossible); if (err === null || !this.isNotExistError(err)) { return [fs2, fi, ok, err]; } } } } return [null, null, false, type_1.ErrFileNotFound]; } /** * Check if error is "not exist" error */ isNotExistError(error) { return error.message.includes('ENOENT') || error.message.includes('no such file') || error === type_1.ErrFileNotFound; } /** * Create a file (write operation) */ async create(name) { if (!this.firstWritable) { throw new type_1.OverlayFsError('filesystem is read-only', 'READ_ONLY'); } return await this.writeFs().create(name); } /** * Create directory (write operation) */ async mkdir(name, perm) { if (!this.firstWritable) { throw new type_1.OverlayFsError('filesystem is read-only', 'READ_ONLY'); } return await this.writeFs().mkdir(name, perm); } /** * Create directory tree (write operation) */ async mkdirAll(path, perm) { if (!this.firstWritable) { throw new type_1.OverlayFsError('filesystem is read-only', 'READ_ONLY'); } return await this.writeFs().mkdirAll(path, perm); } /** * Open file for reading (searches all filesystems) */ async open(name) { // Handle empty filesystem case if (this.fss.length === 0) { throw type_1.ErrFileNotFound; } const [fs, fi, , err] = await this.statInternal(name, false); if (err) { throw err; } if (fi.isDir()) { // Return merged directory - like Go version const dirFss = []; await this.collectDirs(name, (fs) => { dirFss.push(fs); }); if (dirFss.length === 0) { // They may have been deleted throw type_1.ErrFileNotFound; } if (dirFss.length === 1) { return await dirFss[0].open(name); } return await (0, overlaydir_1.openDir)(name, this.mergeDirs, async () => fi, dirFss // Pass filesystems directly ); } return await fs.open(name); } /** * Open file with flags (write operations use first filesystem) */ async openFile(name, flag, perm) { const isWrite = (flag & type_1.OpenFlags.O_WRONLY) !== 0 || (flag & type_1.OpenFlags.O_RDWR) !== 0; if (isWrite && !this.firstWritable) { throw new type_1.OverlayFsError('filesystem is read-only', 'READ_ONLY'); } if (isWrite) { return await this.writeFs().openFile(name, flag, perm); } return await this.open(name); } /** * Remove file (write operation) */ async remove(name) { if (!this.firstWritable) { throw new type_1.OverlayFsError('filesystem is read-only', 'READ_ONLY'); } return await this.writeFs().remove(name); } /** * Remove directory tree (write operation) */ async removeAll(path) { if (!this.firstWritable) { throw new type_1.OverlayFsError('filesystem is read-only', 'READ_ONLY'); } return await this.writeFs().removeAll(path); } /** * Rename file (write operation) */ async rename(oldname, newname) { if (!this.firstWritable) { throw new type_1.OverlayFsError('filesystem is read-only', 'READ_ONLY'); } return await this.writeFs().rename(oldname, newname); } /** * Get file info (searches all filesystems) */ async stat(name) { // Convert "/" to "" for root directory access in BaseFs const fsPath = name === '/' ? '' : name; const [, fi, , err] = await this.statInternal(fsPath, false); if (err) { throw err; } return fi; } /** * Change file mode (write operation) */ async chmod(name, mode) { if (!this.firstWritable) { throw new type_1.OverlayFsError('filesystem is read-only', 'READ_ONLY'); } return await this.writeFs().chmod(name, mode); } /** * Change file ownership (write operation) */ async chown(name, uid, gid) { if (!this.firstWritable) { throw new type_1.OverlayFsError('filesystem is read-only', 'READ_ONLY'); } return await this.writeFs().chown(name, uid, gid); } /** * Change file times (write operation) */ async chtimes(name, atime, mtime) { if (!this.firstWritable) { throw new type_1.OverlayFsError('filesystem is read-only', 'READ_ONLY'); } return await this.writeFs().chtimes(name, atime, mtime); } } exports.OverlayFs = OverlayFs; /** * Factory function to create new OverlayFs */ function newOverlayFs(options) { return new OverlayFs(options); } /** * Create OverlayFs from raw options */ function createOverlayFs(opts) { const options = new overlayoptions_1.OverlayOptions(opts); return new OverlayFs(options); } //# sourceMappingURL=overlayfs.js.map