@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
JavaScript
"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