u
This commit is contained in:
@@ -48,7 +48,7 @@ Applets run in an isolated worker thread pool sandbox.
|
|||||||
- **No `require()` or `import`**: Attempting to import modules throws an error.
|
- **No `require()` or `import`**: Attempting to import modules throws an error.
|
||||||
- **Pre-injected Globals**:
|
- **Pre-injected Globals**:
|
||||||
- `fetch(path, options)`: Authenticated `fetch` pre-configured with a JWT scoped to the current user and selected company. Use root-relative API routes, e.g. `fetch('/article/42000009')`.
|
- `fetch(path, options)`: Authenticated `fetch` pre-configured with a JWT scoped to the current user and selected company. Use root-relative API routes, e.g. `fetch('/article/42000009')`.
|
||||||
- `fs` / `vfs`: Sandboxed VFS filesystem interface for reading/writing local company files.
|
- `fs`: Sandboxed filesystem interface for reading/writing local company files.
|
||||||
- `ExcelJS`: Pre-injected spreadsheet library.
|
- `ExcelJS`: Pre-injected spreadsheet library.
|
||||||
- `PDFDocument`: Pre-injected PDFKit library.
|
- `PDFDocument`: Pre-injected PDFKit library.
|
||||||
- `sharp`: Pre-injected image manipulation library.
|
- `sharp`: Pre-injected image manipulation library.
|
||||||
|
|||||||
@@ -600,7 +600,6 @@ async function runJob(job) {
|
|||||||
sandbox: {
|
sandbox: {
|
||||||
console: sandboxConsole,
|
console: sandboxConsole,
|
||||||
fs: vfs,
|
fs: vfs,
|
||||||
vfs,
|
|
||||||
sharp: bridgeSharp(vfs),
|
sharp: bridgeSharp(vfs),
|
||||||
ExcelJS: bridgedExcel,
|
ExcelJS: bridgedExcel,
|
||||||
exceljs: bridgedExcel,
|
exceljs: bridgedExcel,
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export const TOOL_DESCRIPTIONS = {
|
|||||||
javascript_execute:
|
javascript_execute:
|
||||||
'Execute JS in an isolated sandbox via inline `code` (assign to `module.exports`) or `appletvfsfile` ' +
|
'Execute JS in an isolated sandbox via inline `code` (assign to `module.exports`) or `appletvfsfile` ' +
|
||||||
'(.applet.js returning typed CSV per @applet-input-schema, @applet-output-schema, and @applet-description; details in applet.SKILL). ' +
|
'(.applet.js returning typed CSV per @applet-input-schema, @applet-output-schema, and @applet-description; details in applet.SKILL). ' +
|
||||||
'Pre-injected globals: fs (vfs; use only root-relative paths for all file operations, absolute paths will fail), sharp, ExcelJS, PDFDocument, crypto, and fetch("/path"). No require/import.',
|
'Pre-injected globals: fs (use only root-relative paths for all file operations, absolute paths will fail), sharp, ExcelJS, PDFDocument, crypto, and fetch("/path"). No require/import.',
|
||||||
|
|
||||||
find_files:
|
find_files:
|
||||||
'List VFS files (root-relative paths). Set skills=true to list root-level "<slug>.SKILL" files.',
|
'List VFS files (root-relative paths). Set skills=true to list root-level "<slug>.SKILL" files.',
|
||||||
|
|||||||
345
src/tools/vfs.js
345
src/tools/vfs.js
@@ -435,6 +435,193 @@ export function createVfs(vfsRoot, options = {}) {
|
|||||||
throw sanitizeFsError(err, p);
|
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) => {
|
existsSync: (p) => {
|
||||||
const hash = extractFullHash(p);
|
const hash = extractFullHash(p);
|
||||||
if (hash) {
|
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) => {
|
statSync: (p, opt) => {
|
||||||
const hash = extractFullHash(p);
|
const hash = extractFullHash(p);
|
||||||
if (hash) {
|
if (hash) {
|
||||||
@@ -466,18 +658,63 @@ export function createVfs(vfsRoot, options = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const diskPath = safeResolve(p);
|
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),
|
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) => {
|
readdirSync: (p = '.', opt) => {
|
||||||
const clean = typeof p === 'string' ? p.trim().replace(/^([a-zA-Z]:|\/)+/, '') : '';
|
const clean = typeof p === 'string' ? p.trim().replace(/^([a-zA-Z]:|\/)+/, '') : '';
|
||||||
if (VIRTUAL_IMAGE_FOLDERS.has(clean.toLowerCase())) {
|
if (VIRTUAL_IMAGE_FOLDERS.has(clean.toLowerCase())) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const diskPath = safeResolve(p);
|
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) => {
|
mkdirSync: (p, opt) => {
|
||||||
@@ -489,22 +726,81 @@ export function createVfs(vfsRoot, options = {}) {
|
|||||||
throw sanitizeFsError(err, p);
|
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) => {
|
unlinkSync: (p) => {
|
||||||
assertNotReadOnlyDatabasePath(p);
|
assertNotReadOnlyDatabasePath(p);
|
||||||
const diskPath = safeResolve(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) => {
|
rmdirSync: (p, opt) => {
|
||||||
assertNotReadOnlyDatabasePath(p);
|
assertNotReadOnlyDatabasePath(p);
|
||||||
const diskPath = safeResolve(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) => {
|
rmSync: (p, opt) => {
|
||||||
assertNotReadOnlyDatabasePath(p);
|
assertNotReadOnlyDatabasePath(p);
|
||||||
const diskPath = safeResolve(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: {
|
promises: {
|
||||||
@@ -538,11 +834,41 @@ export function createVfs(vfsRoot, options = {}) {
|
|||||||
},
|
},
|
||||||
writeFile: async (p, data, opt) => vfs.writeFileSync(p, data, opt),
|
writeFile: async (p, data, opt) => vfs.writeFileSync(p, data, opt),
|
||||||
appendFile: async (p, data, opt) => vfs.appendFileSync(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),
|
stat: async (p, opt) => vfs.statSync(p, opt),
|
||||||
|
lstat: async (p, opt) => vfs.lstatSync(p, opt),
|
||||||
readdir: async (p, opt) => vfs.readdirSync(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),
|
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),
|
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;
|
return vfs;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user