@codesandbox/sandpack-client
Version:
<img style="width:100%" src="https://user-images.githubusercontent.com/4838076/143581035-ebee5ba2-9cb1-4fe8-a05b-2f44bd69bb4b.gif" alt="Component toolkit for live running code editing experiences" />
453 lines • 16.8 kB
JavaScript
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var file_system_1 = require("../core/file_system");
var InMemory_1 = require("./InMemory");
var api_error_1 = require("../core/api_error");
var node_fs_1 = require("../core/node_fs");
var path = require("path");
var util_1 = require("../core/util");
/**
* The MountableFileSystem allows you to mount multiple backend types or
* multiple instantiations of the same backend into a single file system tree.
* The file systems do not need to know about each other; all interactions are
* automatically facilitated through this interface.
*
* For example, if a file system is mounted at /mnt/blah, and a request came in
* for /mnt/blah/foo.txt, the file system would see a request for /foo.txt.
*
* You can mount file systems when you configure the file system:
* ```javascript
* BrowserFS.configure({
* fs: "MountableFileSystem",
* options: {
* '/data': { fs: 'HTTPRequest', options: { index: "http://mysite.com/files/index.json" } },
* '/home': { fs: 'LocalStorage' }
* }
* }, function(e) {
*
* });
* ```
*
* For advanced users, you can also mount file systems *after* MFS is constructed:
* ```javascript
* BrowserFS.FileSystem.HTTPRequest.Create({
* index: "http://mysite.com/files/index.json"
* }, function(e, xhrfs) {
* BrowserFS.FileSystem.MountableFileSystem.Create({
* '/data': xhrfs
* }, function(e, mfs) {
* BrowserFS.initialize(mfs);
*
* // Added after-the-fact...
* BrowserFS.FileSystem.LocalStorage.Create(function(e, lsfs) {
* mfs.mount('/home', lsfs);
* });
* });
* });
* ```
*
* Since MountableFileSystem simply proxies requests to mounted file systems, it supports all of the operations that the mounted file systems support.
*
* With no mounted file systems, `MountableFileSystem` acts as a simple `InMemory` filesystem.
*/
var MountableFileSystem = /** @class */ (function (_super) {
__extends(MountableFileSystem, _super);
/**
* Creates a new, empty MountableFileSystem.
*/
function MountableFileSystem(rootFs) {
var _this = _super.call(this) || this;
// Contains the list of mount points in mntMap, sorted by string length in decreasing order.
// Ensures that we scan the most specific mount points for a match first, which lets us
// nest mount points.
_this.mountList = [];
_this.mntMap = {};
_this.rootFs = rootFs;
return _this;
}
/**
* Creates a MountableFileSystem instance with the given options.
*/
MountableFileSystem.Create = function (opts, cb) {
InMemory_1.default.Create({}, function (e, imfs) {
if (imfs) {
var fs_1 = new MountableFileSystem(imfs);
try {
Object.keys(opts).forEach(function (mountPoint) {
fs_1.mount(mountPoint, opts[mountPoint]);
});
}
catch (e) {
return cb(e);
}
cb(null, fs_1);
}
else {
cb(e);
}
});
};
MountableFileSystem.isAvailable = function () {
return true;
};
/**
* Mounts the file system at the given mount point.
*/
MountableFileSystem.prototype.mount = function (mountPoint, fs) {
if (mountPoint[0] !== '/') {
mountPoint = "/".concat(mountPoint);
}
mountPoint = path.resolve(mountPoint);
if (this.mntMap[mountPoint]) {
throw new api_error_1.ApiError(api_error_1.ErrorCode.EINVAL, "Mount point " + mountPoint + " is already taken.");
}
(0, util_1.mkdirpSync)(mountPoint, 0x1ff, this.rootFs);
this.mntMap[mountPoint] = fs;
this.mountList.push(mountPoint);
this.mountList = this.mountList.sort(function (a, b) { return b.length - a.length; });
};
MountableFileSystem.prototype.umount = function (mountPoint) {
if (mountPoint[0] !== '/') {
mountPoint = "/".concat(mountPoint);
}
mountPoint = path.resolve(mountPoint);
if (!this.mntMap[mountPoint]) {
throw new api_error_1.ApiError(api_error_1.ErrorCode.EINVAL, "Mount point " + mountPoint + " is already unmounted.");
}
delete this.mntMap[mountPoint];
this.mountList.splice(this.mountList.indexOf(mountPoint), 1);
while (mountPoint !== '/') {
if (this.rootFs.readdirSync(mountPoint).length === 0) {
this.rootFs.rmdirSync(mountPoint);
mountPoint = path.dirname(mountPoint);
}
else {
break;
}
}
};
/**
* Returns the file system that the path points to.
*/
MountableFileSystem.prototype._getFs = function (path) {
var mountList = this.mountList, len = mountList.length;
for (var i = 0; i < len; i++) {
var mountPoint = mountList[i];
// We know path is normalized, so it is a substring of the mount point.
if (mountPoint.length <= path.length && path.indexOf(mountPoint) === 0) {
path = path.substr(mountPoint.length > 1 ? mountPoint.length : 0);
if (path === '') {
path = '/';
}
return { fs: this.mntMap[mountPoint], path: path, mountPoint: mountPoint };
}
}
// Query our root file system.
return { fs: this.rootFs, path: path, mountPoint: '/' };
};
// Global information methods
MountableFileSystem.prototype.getName = function () {
return MountableFileSystem.Name;
};
MountableFileSystem.prototype.diskSpace = function (path, cb) {
cb(0, 0);
};
MountableFileSystem.prototype.isReadOnly = function () {
return false;
};
MountableFileSystem.prototype.supportsLinks = function () {
// I'm not ready for cross-FS links yet.
return false;
};
MountableFileSystem.prototype.supportsProps = function () {
return false;
};
MountableFileSystem.prototype.supportsSynch = function () {
return true;
};
/**
* Fixes up error messages so they mention the mounted file location relative
* to the MFS root, not to the particular FS's root.
* Mutates the input error, and returns it.
*/
MountableFileSystem.prototype.standardizeError = function (err, path, realPath) {
var index = err.message.indexOf(path);
if (index !== -1) {
err.message = err.message.substr(0, index) + realPath + err.message.substr(index + path.length);
err.path = realPath;
}
return err;
};
// The following methods involve multiple file systems, and thus have custom
// logic.
// Note that we go through the Node API to use its robust default argument
// processing.
MountableFileSystem.prototype.rename = function (oldPath, newPath, cb) {
var _this = this;
// Scenario 1: old and new are on same FS.
var fs1rv = this._getFs(oldPath);
var fs2rv = this._getFs(newPath);
if (fs1rv.fs === fs2rv.fs) {
return fs1rv.fs.rename(fs1rv.path, fs2rv.path, function (e) {
if (e) {
_this.standardizeError(_this.standardizeError(e, fs1rv.path, oldPath), fs2rv.path, newPath);
}
cb(e);
});
}
// Scenario 2: Different file systems.
// Read old file, write new file, delete old file.
return node_fs_1.default.readFile(oldPath, function (err, data) {
if (err) {
return cb(err);
}
node_fs_1.default.writeFile(newPath, data, function (err) {
if (err) {
return cb(err);
}
node_fs_1.default.unlink(oldPath, cb);
});
});
};
MountableFileSystem.prototype.renameSync = function (oldPath, newPath) {
// Scenario 1: old and new are on same FS.
var fs1rv = this._getFs(oldPath);
var fs2rv = this._getFs(newPath);
if (fs1rv.fs === fs2rv.fs) {
try {
return fs1rv.fs.renameSync(fs1rv.path, fs2rv.path);
}
catch (e) {
this.standardizeError(this.standardizeError(e, fs1rv.path, oldPath), fs2rv.path, newPath);
throw e;
}
}
// Scenario 2: Different file systems.
var data = node_fs_1.default.readFileSync(oldPath);
node_fs_1.default.writeFileSync(newPath, data);
return node_fs_1.default.unlinkSync(oldPath);
};
MountableFileSystem.prototype.readdirSync = function (p) {
var fsInfo = this._getFs(p);
// If null, rootfs did not have the directory
// (or the target FS is the root fs).
var rv = null;
// Mount points are all defined in the root FS.
// Ensure that we list those, too.
if (fsInfo.fs !== this.rootFs) {
try {
rv = this.rootFs.readdirSync(p);
}
catch (e) {
// Ignore.
}
}
try {
var rv2_1 = fsInfo.fs.readdirSync(fsInfo.path);
if (rv === null) {
return rv2_1;
}
else {
// Filter out duplicates.
return rv2_1.concat(rv.filter(function (val) { return rv2_1.indexOf(val) === -1; }));
}
}
catch (e) {
if (rv === null) {
throw this.standardizeError(e, fsInfo.path, p);
}
else {
// The root FS had something.
return rv;
}
}
};
MountableFileSystem.prototype.readdir = function (p, cb) {
var _this = this;
var fsInfo = this._getFs(p);
fsInfo.fs.readdir(fsInfo.path, function (err, files) {
if (fsInfo.fs !== _this.rootFs) {
try {
var rv = _this.rootFs.readdirSync(p);
if (files) {
// Filter out duplicates.
files = files.concat(rv.filter(function (val) { return files.indexOf(val) === -1; }));
}
else {
files = rv;
}
}
catch (e) {
// Root FS and target FS did not have directory.
if (err) {
return cb(_this.standardizeError(err, fsInfo.path, p));
}
}
}
else if (err) {
// Root FS and target FS are the same, and did not have directory.
return cb(_this.standardizeError(err, fsInfo.path, p));
}
cb(null, files);
});
};
MountableFileSystem.prototype.realpathSync = function (p, cache) {
var fsInfo = this._getFs(p);
try {
var mountedPath = fsInfo.fs.realpathSync(fsInfo.path, {});
// resolve is there to remove any trailing slash that may be present
return path.resolve(path.join(fsInfo.mountPoint, mountedPath));
}
catch (e) {
throw this.standardizeError(e, fsInfo.path, p);
}
};
MountableFileSystem.prototype.realpath = function (p, cache, cb) {
var _this = this;
var fsInfo = this._getFs(p);
fsInfo.fs.realpath(fsInfo.path, {}, function (err, rv) {
if (err) {
cb(_this.standardizeError(err, fsInfo.path, p));
}
else {
// resolve is there to remove any trailing slash that may be present
cb(null, path.resolve(path.join(fsInfo.mountPoint, rv)));
}
});
};
MountableFileSystem.prototype.rmdirSync = function (p) {
var fsInfo = this._getFs(p);
if (this._containsMountPt(p)) {
throw api_error_1.ApiError.ENOTEMPTY(p);
}
else {
try {
fsInfo.fs.rmdirSync(fsInfo.path);
}
catch (e) {
throw this.standardizeError(e, fsInfo.path, p);
}
}
};
MountableFileSystem.prototype.rmdir = function (p, cb) {
var _this = this;
var fsInfo = this._getFs(p);
if (this._containsMountPt(p)) {
cb(api_error_1.ApiError.ENOTEMPTY(p));
}
else {
fsInfo.fs.rmdir(fsInfo.path, function (err) {
cb(err ? _this.standardizeError(err, fsInfo.path, p) : null);
});
}
};
/**
* Returns true if the given path contains a mount point.
*/
MountableFileSystem.prototype._containsMountPt = function (p) {
var mountPoints = this.mountList, len = mountPoints.length;
for (var i = 0; i < len; i++) {
var pt = mountPoints[i];
if (pt.length >= p.length && pt.slice(0, p.length) === p) {
return true;
}
}
return false;
};
MountableFileSystem.Name = "MountableFileSystem";
MountableFileSystem.Options = {};
return MountableFileSystem;
}(file_system_1.BaseFileSystem));
exports.default = MountableFileSystem;
/**
* Tricky: Define all of the functions that merely forward arguments to the
* relevant file system, or return/throw an error.
* Take advantage of the fact that the *first* argument is always the path, and
* the *last* is the callback function (if async).
* @todo Can use numArgs to make proxying more efficient.
* @hidden
*/
function defineFcn(name, isSync, numArgs) {
if (isSync) {
return function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
var path = args[0];
var rv = this._getFs(path);
args[0] = rv.path;
try {
return rv.fs[name].apply(rv.fs, args);
}
catch (e) {
this.standardizeError(e, rv.path, path);
throw e;
}
};
}
else {
return function () {
var _this = this;
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
var path = args[0];
var rv = this._getFs(path);
args[0] = rv.path;
if (typeof args[args.length - 1] === 'function') {
var cb_1 = args[args.length - 1];
args[args.length - 1] = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
if (args.length > 0 && args[0] instanceof api_error_1.ApiError) {
_this.standardizeError(args[0], rv.path, path);
}
cb_1.apply(null, args);
};
}
return rv.fs[name].apply(rv.fs, args);
};
}
}
/**
* @hidden
*/
var fsCmdMap = [
// 1 arg functions
['exists', 'unlink', 'readlink'],
// 2 arg functions
['stat', 'mkdir', 'truncate'],
// 3 arg functions
['open', 'readFile', 'chmod', 'utimes'],
// 4 arg functions
['chown'],
// 5 arg functions
['writeFile', 'appendFile']
];
for (var i = 0; i < fsCmdMap.length; i++) {
var cmds = fsCmdMap[i];
for (var _i = 0, cmds_1 = cmds; _i < cmds_1.length; _i++) {
var fnName = cmds_1[_i];
MountableFileSystem.prototype[fnName] = defineFcn(fnName, false, i + 1);
MountableFileSystem.prototype[fnName + 'Sync'] = defineFcn(fnName + 'Sync', true, i + 1);
}
}