This commit is contained in:
seb
2026-10-18 15:41:21 +02:00
parent 36d62138d4
commit 9c50d9efc3
4 changed files with 340 additions and 10 deletions

View File

@@ -435,6 +435,193 @@ export function createVfs(vfsRoot, options = {}) {
throw sanitizeFsError(err, p);
}
},
appendFile: (p, data, opt, cb) => {
const callback = typeof opt === 'function' ? opt : cb;
const options = typeof opt === 'function' ? undefined : opt;
try {
vfs.appendFileSync(p, data, options);
if (typeof callback === 'function') process.nextTick(callback, null);
} catch (err) {
if (typeof callback === 'function') process.nextTick(callback, err);
else throw err;
}
},
copyFileSync: (src, dest, mode) => {
assertNotReadOnlyDatabasePath(dest);
const hash = extractFullHash(src);
if (hash) {
const buf = syncFetchImage(hash);
if (buf) {
const destDisk = safeResolve(dest);
fs.mkdirSync(path.dirname(destDisk), { recursive: true });
if (typeof mode === 'number' && (mode & fs.constants.COPYFILE_EXCL) && fs.existsSync(destDisk)) {
const err = new Error(`EEXIST: file already exists, copyfile '${src}' -> '${dest}'`);
err.code = 'EEXIST';
err.path = dest;
err.syscall = 'copyfile';
throw err;
}
try {
return fs.writeFileSync(destDisk, buf);
} catch (err) {
throw sanitizeFsError(err, dest);
}
}
}
const srcDisk = safeResolve(src);
const destDisk = safeResolve(dest);
fs.mkdirSync(path.dirname(destDisk), { recursive: true });
try {
return fs.copyFileSync(srcDisk, destDisk, mode);
} catch (err) {
throw sanitizeFsError(err, `${src} -> ${dest}`);
}
},
copyFile: (src, dest, mode, cb) => {
const callback = typeof mode === 'function' ? mode : cb;
const m = typeof mode === 'function' ? undefined : mode;
try {
vfs.copyFileSync(src, dest, m);
if (typeof callback === 'function') process.nextTick(callback, null);
} catch (err) {
if (typeof callback === 'function') process.nextTick(callback, err);
else throw err;
}
},
cpSync: (src, dest, opt) => {
assertNotReadOnlyDatabasePath(dest);
const srcDisk = safeResolve(src);
const destDisk = safeResolve(dest);
fs.mkdirSync(path.dirname(destDisk), { recursive: true });
try {
return fs.cpSync(srcDisk, destDisk, opt);
} catch (err) {
throw sanitizeFsError(err, `${src} -> ${dest}`);
}
},
cp: (src, dest, opt, cb) => {
const callback = typeof opt === 'function' ? opt : cb;
const options = typeof opt === 'function' ? undefined : opt;
try {
vfs.cpSync(src, dest, options);
if (typeof callback === 'function') process.nextTick(callback, null);
} catch (err) {
if (typeof callback === 'function') process.nextTick(callback, err);
else throw err;
}
},
renameSync: (oldPath, newPath) => {
assertNotReadOnlyDatabasePath(oldPath);
assertNotReadOnlyDatabasePath(newPath);
const oldDisk = safeResolve(oldPath);
const newDisk = safeResolve(newPath);
fs.mkdirSync(path.dirname(newDisk), { recursive: true });
try {
return fs.renameSync(oldDisk, newDisk);
} catch (err) {
throw sanitizeFsError(err, `${oldPath} -> ${newPath}`);
}
},
rename: (oldPath, newPath, cb) => {
try {
vfs.renameSync(oldPath, newPath);
if (typeof cb === 'function') process.nextTick(cb, null);
} catch (err) {
if (typeof cb === 'function') process.nextTick(cb, err);
else throw err;
}
},
truncateSync: (p, len) => {
assertNotReadOnlyDatabasePath(p);
const diskPath = safeResolve(p);
try {
return fs.truncateSync(diskPath, len);
} catch (err) {
throw sanitizeFsError(err, p);
}
},
truncate: (p, len, cb) => {
const callback = typeof len === 'function' ? len : cb;
const l = typeof len === 'function' ? undefined : len;
try {
vfs.truncateSync(p, l);
if (typeof callback === 'function') process.nextTick(callback, null);
} catch (err) {
if (typeof callback === 'function') process.nextTick(callback, err);
else throw err;
}
},
accessSync: (p, mode) => {
const hash = extractFullHash(p);
if (hash) {
const buf = syncFetchImage(hash);
if (buf) {
if (typeof mode === 'number' && (mode & fs.constants.W_OK)) {
const err = new Error(`EACCES: permission denied, access '${p}'`);
err.code = 'EACCES';
err.path = p;
err.syscall = 'access';
throw err;
}
return;
}
}
const diskPath = safeResolve(p);
try {
return fs.accessSync(diskPath, mode);
} catch (err) {
throw sanitizeFsError(err, p);
}
},
access: (p, mode, cb) => {
const callback = typeof mode === 'function' ? mode : cb;
const m = typeof mode === 'function' ? undefined : mode;
try {
vfs.accessSync(p, m);
if (typeof callback === 'function') process.nextTick(callback, null);
} catch (err) {
if (typeof callback === 'function') process.nextTick(callback, err);
else throw err;
}
},
realpathSync: (p, opt) => {
const diskPath = safeResolve(p);
try {
const resolved = fs.realpathSync(diskPath, opt);
const rel = path.relative(vfsRoot, resolved);
if (rel.startsWith('..') || path.isAbsolute(rel)) {
throw new Error(`Access denied: path "${p}" escapes virtual filesystem boundary.`);
}
return rel ? rel.split(path.sep).join('/') : '.';
} catch (err) {
throw sanitizeFsError(err, p);
}
},
realpath: (p, opt, cb) => {
const callback = typeof opt === 'function' ? opt : cb;
const options = typeof opt === 'function' ? undefined : opt;
try {
const res = vfs.realpathSync(p, options);
if (typeof callback === 'function') process.nextTick(callback, null, res);
} catch (err) {
if (typeof callback === 'function') process.nextTick(callback, err);
else throw err;
}
},
existsSync: (p) => {
const hash = extractFullHash(p);
if (hash) {
@@ -449,6 +636,11 @@ export function createVfs(vfsRoot, options = {}) {
}
},
exists: (p, cb) => {
const res = vfs.existsSync(p);
if (typeof cb === 'function') process.nextTick(cb, res);
},
statSync: (p, opt) => {
const hash = extractFullHash(p);
if (hash) {
@@ -466,18 +658,63 @@ export function createVfs(vfsRoot, options = {}) {
}
}
const diskPath = safeResolve(p);
return fs.statSync(diskPath, opt);
try {
return fs.statSync(diskPath, opt);
} catch (err) {
throw sanitizeFsError(err, p);
}
},
stat: (p, opt, cb) => {
const callback = typeof opt === 'function' ? opt : cb;
const options = typeof opt === 'function' ? undefined : opt;
try {
const res = vfs.statSync(p, options);
if (typeof callback === 'function') process.nextTick(callback, null, res);
} catch (err) {
if (typeof callback === 'function') process.nextTick(callback, err);
else throw err;
}
},
lstatSync: (p, opt) => vfs.statSync(p, opt),
lstat: (p, opt, cb) => {
const callback = typeof opt === 'function' ? opt : cb;
const options = typeof opt === 'function' ? undefined : opt;
try {
const res = vfs.lstatSync(p, options);
if (typeof callback === 'function') process.nextTick(callback, null, res);
} catch (err) {
if (typeof callback === 'function') process.nextTick(callback, err);
else throw err;
}
},
readdirSync: (p = '.', opt) => {
const clean = typeof p === 'string' ? p.trim().replace(/^([a-zA-Z]:|\/)+/, '') : '';
if (VIRTUAL_IMAGE_FOLDERS.has(clean.toLowerCase())) {
return [];
}
const diskPath = safeResolve(p);
return fs.readdirSync(diskPath, opt);
try {
return fs.readdirSync(diskPath, opt);
} catch (err) {
throw sanitizeFsError(err, p);
}
},
readdir: (p = '.', opt, cb) => {
const callback = typeof opt === 'function' ? opt : (typeof p === 'function' ? p : cb);
const options = typeof opt === 'function' ? undefined : opt;
const pathArg = typeof p === 'function' ? '.' : p;
try {
const res = vfs.readdirSync(pathArg, options);
if (typeof callback === 'function') process.nextTick(callback, null, res);
} catch (err) {
if (typeof callback === 'function') process.nextTick(callback, err);
else throw err;
}
},
mkdirSync: (p, opt) => {
@@ -489,22 +726,81 @@ export function createVfs(vfsRoot, options = {}) {
throw sanitizeFsError(err, p);
}
},
mkdir: (p, opt, cb) => {
const callback = typeof opt === 'function' ? opt : cb;
const options = typeof opt === 'function' ? undefined : opt;
try {
const res = vfs.mkdirSync(p, options);
if (typeof callback === 'function') process.nextTick(callback, null, res);
} catch (err) {
if (typeof callback === 'function') process.nextTick(callback, err);
else throw err;
}
},
unlinkSync: (p) => {
assertNotReadOnlyDatabasePath(p);
const diskPath = safeResolve(p);
return fs.unlinkSync(diskPath);
try {
return fs.unlinkSync(diskPath);
} catch (err) {
throw sanitizeFsError(err, p);
}
},
unlink: (p, cb) => {
try {
vfs.unlinkSync(p);
if (typeof cb === 'function') process.nextTick(cb, null);
} catch (err) {
if (typeof cb === 'function') process.nextTick(cb, err);
else throw err;
}
},
rmdirSync: (p, opt) => {
assertNotReadOnlyDatabasePath(p);
const diskPath = safeResolve(p);
return fs.rmdirSync(diskPath, opt);
try {
return fs.rmdirSync(diskPath, opt);
} catch (err) {
throw sanitizeFsError(err, p);
}
},
rmdir: (p, opt, cb) => {
const callback = typeof opt === 'function' ? opt : cb;
const options = typeof opt === 'function' ? undefined : opt;
try {
vfs.rmdirSync(p, options);
if (typeof callback === 'function') process.nextTick(callback, null);
} catch (err) {
if (typeof callback === 'function') process.nextTick(callback, err);
else throw err;
}
},
rmSync: (p, opt) => {
assertNotReadOnlyDatabasePath(p);
const diskPath = safeResolve(p);
return fs.rmSync(diskPath, opt);
try {
return fs.rmSync(diskPath, opt);
} catch (err) {
throw sanitizeFsError(err, p);
}
},
rm: (p, opt, cb) => {
const callback = typeof opt === 'function' ? opt : cb;
const options = typeof opt === 'function' ? undefined : opt;
try {
vfs.rmSync(p, options);
if (typeof callback === 'function') process.nextTick(callback, null);
} catch (err) {
if (typeof callback === 'function') process.nextTick(callback, err);
else throw err;
}
},
promises: {
@@ -538,11 +834,41 @@ export function createVfs(vfsRoot, options = {}) {
},
writeFile: async (p, data, opt) => vfs.writeFileSync(p, data, opt),
appendFile: async (p, data, opt) => vfs.appendFileSync(p, data, opt),
copyFile: async (src, dest, mode) => {
assertNotReadOnlyDatabasePath(dest);
const hash = extractFullHash(src);
if (hash) {
const buf = await vfs.promises.readFile(src);
const destDisk = safeResolve(dest);
fs.mkdirSync(path.dirname(destDisk), { recursive: true });
if (typeof mode === 'number' && (mode & fs.constants.COPYFILE_EXCL) && fs.existsSync(destDisk)) {
const err = new Error(`EEXIST: file already exists, copyfile '${src}' -> '${dest}'`);
err.code = 'EEXIST';
err.path = dest;
err.syscall = 'copyfile';
throw err;
}
try {
return fs.writeFileSync(destDisk, buf);
} catch (err) {
throw sanitizeFsError(err, dest);
}
}
return vfs.copyFileSync(src, dest, mode);
},
cp: async (src, dest, opt) => vfs.cpSync(src, dest, opt),
rename: async (oldPath, newPath) => vfs.renameSync(oldPath, newPath),
truncate: async (p, len) => vfs.truncateSync(p, len),
access: async (p, mode) => vfs.accessSync(p, mode),
realpath: async (p, opt) => vfs.realpathSync(p, opt),
stat: async (p, opt) => vfs.statSync(p, opt),
lstat: async (p, opt) => vfs.lstatSync(p, opt),
readdir: async (p, opt) => vfs.readdirSync(p, opt),
mkdir: async (p, opt) => vfs.mkdirSync(p, opt),
mkdir: async (p, opt) => vfs.mkdirSync(p, opt),
rmdir: async (p, opt) => vfs.rmdirSync(p, opt),
unlink: async (p) => vfs.unlinkSync(p),
rm: async (p, opt) => vfs.rmSync(p, opt),
rm: async (p, opt) => vfs.rmSync(p, opt),
constants: fs.constants,
},
/**
@@ -573,5 +899,10 @@ export function createVfs(vfsRoot, options = {}) {
sanitizeError: (err, virtualPath) => sanitizeFsError(err, virtualPath),
};
vfs.realpathSync.native = (p, opt) => vfs.realpathSync(p, opt);
vfs.realpath.native = (p, opt, cb) => vfs.realpath(p, opt, cb);
vfs.promises.realpath.native = async (p, opt) => vfs.realpathSync(p, opt);
vfs.constants = fs.constants;
return vfs;
}