@parcel/fs
Advanced tools
Comparing version 2.0.0-alpha.1.1 to 2.0.0-alpha.2
@@ -7,11 +7,8 @@ "use strict"; | ||
var _exportNames = { | ||
MemoryFS: true | ||
ncp: true | ||
}; | ||
Object.defineProperty(exports, "MemoryFS", { | ||
enumerable: true, | ||
get: function () { | ||
return _MemoryFS.MemoryFS; | ||
} | ||
}); | ||
exports.ncp = ncp; | ||
var _path = _interopRequireDefault(require("path")); | ||
var _types = require("./types"); | ||
@@ -43,2 +40,50 @@ | ||
var _MemoryFS = require("./MemoryFS"); | ||
var _MemoryFS = require("./MemoryFS"); | ||
Object.keys(_MemoryFS).forEach(function (key) { | ||
if (key === "default" || key === "__esModule") return; | ||
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; | ||
Object.defineProperty(exports, key, { | ||
enumerable: true, | ||
get: function () { | ||
return _MemoryFS[key]; | ||
} | ||
}); | ||
}); | ||
var _OverlayFS = require("./OverlayFS"); | ||
Object.keys(_OverlayFS).forEach(function (key) { | ||
if (key === "default" || key === "__esModule") return; | ||
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; | ||
Object.defineProperty(exports, key, { | ||
enumerable: true, | ||
get: function () { | ||
return _OverlayFS[key]; | ||
} | ||
}); | ||
}); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
// Recursively copies a directory from the sourceFS to the destinationFS | ||
async function ncp(sourceFS, source, destinationFS, destination) { | ||
await destinationFS.mkdirp(destination); | ||
let files = await sourceFS.readdir(source); | ||
for (let file of files) { | ||
let sourcePath = _path.default.join(source, file); | ||
let destPath = _path.default.join(destination, file); | ||
let stats = await sourceFS.stat(sourcePath); | ||
if (stats.isFile()) { | ||
await new Promise((resolve, reject) => { | ||
sourceFS.createReadStream(sourcePath).pipe(destinationFS.createWriteStream(destPath)).on('finish', () => resolve()).on('error', reject); | ||
}); | ||
} else if (stats.isDirectory()) { | ||
await ncp(sourceFS, sourcePath, destinationFS, destPath); | ||
} | ||
} | ||
} |
@@ -6,3 +6,2 @@ "use strict"; | ||
}); | ||
exports._handle = _handle; | ||
exports.MemoryFS = void 0; | ||
@@ -18,8 +17,14 @@ | ||
var _workers = _interopRequireDefault(require("@parcel/workers")); | ||
var _workers = _interopRequireWildcard(require("@parcel/workers")); | ||
var _nullthrows = _interopRequireDefault(require("nullthrows")); | ||
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
function _classPrivateFieldGet(receiver, privateMap) { var descriptor = privateMap.get(receiver); if (!descriptor) { throw new TypeError("attempted to get private field on non-instance"); } if (descriptor.get) { return descriptor.get.call(receiver); } return descriptor.value; } | ||
function _classPrivateFieldSet(receiver, privateMap, value) { var descriptor = privateMap.get(receiver); if (!descriptor) { throw new TypeError("attempted to set private field on non-instance"); } if (descriptor.set) { descriptor.set.call(receiver, value); } else { if (!descriptor.writable) { throw new TypeError("attempted to set read only private field"); } descriptor.value = value; } return value; } | ||
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } | ||
@@ -31,3 +36,3 @@ | ||
class MemoryFS { | ||
constructor() { | ||
constructor(workerFarm) { | ||
_defineProperty(this, "dirs", void 0); | ||
@@ -37,7 +42,32 @@ | ||
_defineProperty(this, "symlinks", void 0); | ||
_defineProperty(this, "watchers", void 0); | ||
_defineProperty(this, "events", void 0); | ||
_defineProperty(this, "id", void 0); | ||
_defineProperty(this, "handle", void 0); | ||
_defineProperty(this, "farm", void 0); | ||
_defineProperty(this, "_cwd", void 0); | ||
_defineProperty(this, "_eventQueue", void 0); | ||
_defineProperty(this, "_watcherTimer", void 0); | ||
_defineProperty(this, "workers", void 0); | ||
this.farm = workerFarm; | ||
this.dirs = new Map([['/', new Directory()]]); | ||
this.files = new Map(); | ||
this.symlinks = new Map(); | ||
this.watchers = new Map(); | ||
this.events = []; | ||
this.id = id++; | ||
this._cwd = '/'; | ||
this.workers = []; | ||
this._eventQueue = []; | ||
instances.set(this.id, this); | ||
@@ -47,8 +77,28 @@ } | ||
static deserialize(opts) { | ||
return instances.get(opts.id) || new WorkerFS(opts.id); | ||
if (instances.has(opts.id)) { | ||
return instances.get(opts.id); | ||
} | ||
let fs = new WorkerFS(opts.id, (0, _nullthrows.default)(opts.handle)); | ||
fs.dirs = opts.dirs; | ||
fs.files = opts.files; | ||
fs.symlinks = opts.symlinks; | ||
return fs; | ||
} | ||
serialize() { | ||
if (!this.handle) { | ||
this.handle = this.farm.createReverseHandle((fn, args) => { | ||
// $FlowFixMe | ||
return this[fn](...args); | ||
}); | ||
} | ||
return { | ||
id: this.id | ||
$$raw: false, | ||
id: this.id, | ||
handle: this.handle, | ||
dirs: this.dirs, | ||
files: this.files, | ||
symlinks: this.symlinks | ||
}; | ||
@@ -58,9 +108,37 @@ } | ||
cwd() { | ||
return '/'; | ||
return this._cwd; | ||
} | ||
_normalizePath(filePath) { | ||
return _path.default.resolve(this.cwd(), filePath); | ||
chdir(dir) { | ||
this._cwd = dir; | ||
} | ||
_normalizePath(filePath, realpath = true) { | ||
filePath = _path.default.resolve(this.cwd(), filePath); // get realpath by following symlinks | ||
if (realpath) { | ||
let { | ||
root, | ||
dir, | ||
base | ||
} = _path.default.parse(filePath); | ||
let parts = dir.slice(root.length).split(_path.default.sep).concat(base); | ||
let res = root; | ||
for (let part of parts) { | ||
res = _path.default.join(res, part); | ||
let symlink = this.symlinks.get(res); | ||
if (symlink) { | ||
res = symlink; | ||
} | ||
} | ||
return res; | ||
} | ||
return filePath; | ||
} | ||
async writeFile(filePath, contents, options) { | ||
@@ -85,8 +163,24 @@ filePath = this._normalizePath(filePath); | ||
file.write(buffer, mode); | ||
this.files.set(filePath, file); | ||
} else { | ||
this.files.set(filePath, new File(buffer, mode)); | ||
} | ||
await this._sendWorkerEvent({ | ||
type: 'writeFile', | ||
path: filePath, | ||
entry: this.files.get(filePath) | ||
}); | ||
this._triggerEvent({ | ||
type: file ? 'update' : 'create', | ||
path: filePath | ||
}); | ||
} | ||
async readFile(filePath, encoding) { | ||
readFile(filePath, encoding) { | ||
return Promise.resolve(this.readFileSync(filePath, encoding)); | ||
} | ||
readFileSync(filePath, encoding) { | ||
filePath = this._normalizePath(filePath); | ||
@@ -113,3 +207,3 @@ let file = this.files.get(filePath); | ||
async stat(filePath) { | ||
statSync(filePath) { | ||
filePath = this._normalizePath(filePath); | ||
@@ -131,3 +225,7 @@ let dir = this.dirs.get(filePath); | ||
async readdir(dir) { | ||
stat(filePath) { | ||
return Promise.resolve(this.statSync(filePath)); | ||
} | ||
readdir(dir, opts) { | ||
dir = this._normalizePath(dir); | ||
@@ -142,15 +240,27 @@ | ||
for (let filePath of this.files.keys()) { | ||
if (filePath.startsWith(dir)) { | ||
let end = filePath.indexOf(_path.default.sep, dir.length); | ||
for (let [filePath, entry] of this.dirs) { | ||
if (filePath.startsWith(dir) && filePath.indexOf(_path.default.sep, dir.length) === -1) { | ||
let name = filePath.slice(dir.length); | ||
if (end === -1) { | ||
end = filePath.length; | ||
if (opts === null || opts === void 0 ? void 0 : opts.withFileTypes) { | ||
res.push(new Dirent(name, entry)); | ||
} else { | ||
res.push(name); | ||
} | ||
} | ||
} | ||
res.push(filePath.slice(dir.length, end)); | ||
for (let [filePath, entry] of this.files) { | ||
if (filePath.startsWith(dir) && filePath.indexOf(_path.default.sep, dir.length) === -1) { | ||
let name = filePath.slice(dir.length); | ||
if (opts === null || opts === void 0 ? void 0 : opts.withFileTypes) { | ||
res.push(new Dirent(name, entry)); | ||
} else { | ||
res.push(name); | ||
} | ||
} | ||
} | ||
return res; | ||
return Promise.resolve(res); | ||
} | ||
@@ -167,2 +277,14 @@ | ||
this.dirs.delete(filePath); | ||
this.watchers.delete(filePath); | ||
await this._sendWorkerEvent({ | ||
type: 'unlink', | ||
path: filePath | ||
}); | ||
this._triggerEvent({ | ||
type: 'delete', | ||
path: filePath | ||
}); | ||
return Promise.resolve(); | ||
} | ||
@@ -174,3 +296,3 @@ | ||
if (this.dirs.has(dir)) { | ||
return; | ||
return Promise.resolve(); | ||
} | ||
@@ -190,4 +312,16 @@ | ||
this.dirs.set(dir, new Directory()); | ||
await this._sendWorkerEvent({ | ||
type: 'mkdir', | ||
path: dir | ||
}); | ||
this._triggerEvent({ | ||
type: 'create', | ||
path: dir | ||
}); | ||
dir = _path.default.dirname(dir); | ||
} | ||
return Promise.resolve(); | ||
} | ||
@@ -204,2 +338,11 @@ | ||
this.files.delete(filePath); | ||
await this._sendWorkerEvent({ | ||
type: 'unlink', | ||
path: filePath | ||
}); | ||
this._triggerEvent({ | ||
type: 'delete', | ||
path: filePath | ||
}); | ||
} | ||
@@ -211,8 +354,49 @@ } | ||
this.dirs.delete(dirPath); | ||
this.watchers.delete(dirPath); | ||
await this._sendWorkerEvent({ | ||
type: 'unlink', | ||
path: filePath | ||
}); | ||
this._triggerEvent({ | ||
type: 'delete', | ||
path: dirPath | ||
}); | ||
} | ||
} | ||
for (let filePath of this.symlinks.keys()) { | ||
if (filePath.startsWith(dir)) { | ||
this.symlinks.delete(filePath); | ||
await this._sendWorkerEvent({ | ||
type: 'unlink', | ||
path: filePath | ||
}); | ||
} | ||
} | ||
this.dirs.delete(filePath); | ||
await this._sendWorkerEvent({ | ||
type: 'unlink', | ||
path: filePath | ||
}); | ||
this._triggerEvent({ | ||
type: 'delete', | ||
path: filePath | ||
}); | ||
} else if (this.files.has(filePath)) { | ||
this.files.delete(filePath); | ||
await this._sendWorkerEvent({ | ||
type: 'unlink', | ||
path: filePath | ||
}); | ||
this._triggerEvent({ | ||
type: 'delete', | ||
path: filePath | ||
}); | ||
} | ||
this.dirs.delete(filePath); | ||
this.files.delete(filePath); | ||
return Promise.resolve(); | ||
} | ||
@@ -226,2 +410,11 @@ | ||
this.dirs.set(destination, new Directory()); | ||
await this._sendWorkerEvent({ | ||
type: 'mkdir', | ||
path: destination | ||
}); | ||
this._triggerEvent({ | ||
type: 'create', | ||
path: destination | ||
}); | ||
} | ||
@@ -237,2 +430,11 @@ | ||
this.dirs.set(destName, new Directory()); | ||
await this._sendWorkerEvent({ | ||
type: 'mkdir', | ||
path: destination | ||
}); | ||
this._triggerEvent({ | ||
type: 'create', | ||
path: destName | ||
}); | ||
} | ||
@@ -242,7 +444,18 @@ } | ||
for (let [filePath, buffer] of this.files) { | ||
for (let [filePath, file] of this.files) { | ||
if (filePath.startsWith(dir)) { | ||
let destName = _path.default.join(destination, filePath.slice(dir.length)); | ||
this.files.set(destName, buffer); | ||
let exists = this.files.has(destName); | ||
this.files.set(destName, file); | ||
await this._sendWorkerEvent({ | ||
type: 'writeFile', | ||
path: destName, | ||
entry: file | ||
}); | ||
this._triggerEvent({ | ||
type: exists ? 'update' : 'create', | ||
path: destName | ||
}); | ||
} | ||
@@ -263,8 +476,22 @@ } | ||
async realpath(filePath) { | ||
filePath = this._normalizePath(filePath); | ||
return filePath; | ||
realpathSync(filePath) { | ||
return this._normalizePath(filePath); | ||
} | ||
async exists(filePath) { | ||
realpath(filePath) { | ||
return Promise.resolve(this.realpathSync(filePath)); | ||
} | ||
async symlink(target, path) { | ||
target = this._normalizePath(target); | ||
path = this._normalizePath(path); | ||
this.symlinks.set(path, target); | ||
await this._sendWorkerEvent({ | ||
type: 'symlink', | ||
path, | ||
target | ||
}); | ||
} | ||
existsSync(filePath) { | ||
filePath = this._normalizePath(filePath); | ||
@@ -274,2 +501,87 @@ return this.files.has(filePath) || this.dirs.has(filePath); | ||
exists(filePath) { | ||
return Promise.resolve(this.existsSync(filePath)); | ||
} | ||
_triggerEvent(event) { | ||
this.events.push(event); | ||
if (this.watchers.size === 0) { | ||
return; | ||
} // Batch events | ||
this._eventQueue.push(event); | ||
clearTimeout(this._watcherTimer); | ||
this._watcherTimer = setTimeout(() => { | ||
let events = this._eventQueue; | ||
this._eventQueue = []; | ||
for (let [dir, watchers] of this.watchers) { | ||
if (!dir.endsWith(_path.default.sep)) { | ||
dir += _path.default.sep; | ||
} | ||
if (event.path.startsWith(dir)) { | ||
for (let watcher of watchers) { | ||
watcher.trigger(events); | ||
} | ||
} | ||
} | ||
}, 50); | ||
} | ||
_registerWorker(fn) { | ||
this.workers.push(fn); | ||
} | ||
async _sendWorkerEvent(event) { | ||
for (let worker of this.workers) { | ||
await worker(event); | ||
} | ||
} | ||
watch(dir, fn, opts) { | ||
dir = this._normalizePath(dir); | ||
let watcher = new Watcher(fn, opts); | ||
let watchers = this.watchers.get(dir); | ||
if (!watchers) { | ||
watchers = new Set(); | ||
this.watchers.set(dir, watchers); | ||
} | ||
watchers.add(watcher); | ||
return Promise.resolve({ | ||
unsubscribe: () => { | ||
watchers = (0, _nullthrows.default)(watchers); | ||
watchers.delete(watcher); | ||
if (watchers.size === 0) { | ||
this.watchers.delete(dir); | ||
} | ||
return Promise.resolve(); | ||
} | ||
}); | ||
} | ||
async getEventsSince(dir, snapshot, opts) { | ||
let contents = await this.readFile(snapshot, 'utf8'); | ||
let len = Number(contents); | ||
let events = this.events.slice(len); | ||
let ignore = opts.ignore; | ||
if (ignore) { | ||
events = events.filter(event => !ignore.some(i => event.path.startsWith(i + _path.default.sep))); | ||
} | ||
return events; | ||
} | ||
async writeSnapshot(dir, snapshot) { | ||
await this.writeFile(snapshot, '' + this.events.length); | ||
} | ||
} | ||
@@ -279,2 +591,24 @@ | ||
class Watcher { | ||
constructor(fn, options) { | ||
_defineProperty(this, "fn", void 0); | ||
_defineProperty(this, "options", void 0); | ||
this.fn = fn; | ||
this.options = options; | ||
} | ||
trigger(events) { | ||
let ignore = this.options.ignore; | ||
if (ignore) { | ||
events = events.filter(event => !ignore.some(i => event.path.startsWith(i + _path.default.sep))); | ||
} | ||
this.fn(null, events); | ||
} | ||
} | ||
class FSError extends Error { | ||
@@ -406,29 +740,3 @@ constructor(code, path, message) { | ||
stat() { | ||
return { | ||
dev: 0, | ||
ino: 0, | ||
mode: this.mode, | ||
nlink: 0, | ||
uid: 0, | ||
gid: 0, | ||
rdev: 0, | ||
size: this.getSize(), | ||
blksize: 0, | ||
blocks: 0, | ||
atimeMs: this.atime, | ||
mtimeMs: this.mtime, | ||
ctimeMs: this.ctime, | ||
birthtimeMs: this.birthtime, | ||
atime: new Date(this.atime), | ||
mtime: new Date(this.mtime), | ||
ctime: new Date(this.ctime), | ||
birthtime: new Date(this.birthtime), | ||
isFile: () => Boolean(this.mode & S_IFREG), | ||
isDirectory: () => Boolean(this.mode & S_IFDIR), | ||
isBlockDevice: () => false, | ||
isCharacterDevice: () => false, | ||
isSymbolicLink: () => false, | ||
isFIFO: () => false, | ||
isSocket: () => false | ||
}; | ||
return new Stat(this); | ||
} | ||
@@ -438,2 +746,128 @@ | ||
class Stat { | ||
constructor(entry) { | ||
_defineProperty(this, "dev", 0); | ||
_defineProperty(this, "ino", 0); | ||
_defineProperty(this, "mode", void 0); | ||
_defineProperty(this, "nlink", 0); | ||
_defineProperty(this, "uid", 0); | ||
_defineProperty(this, "gid", 0); | ||
_defineProperty(this, "rdev", 0); | ||
_defineProperty(this, "size", void 0); | ||
_defineProperty(this, "blksize", 0); | ||
_defineProperty(this, "blocks", 0); | ||
_defineProperty(this, "atimeMs", void 0); | ||
_defineProperty(this, "mtimeMs", void 0); | ||
_defineProperty(this, "ctimeMs", void 0); | ||
_defineProperty(this, "birthtimeMs", void 0); | ||
_defineProperty(this, "atime", void 0); | ||
_defineProperty(this, "mtime", void 0); | ||
_defineProperty(this, "ctime", void 0); | ||
_defineProperty(this, "birthtime", void 0); | ||
this.mode = entry.mode; | ||
this.size = entry.getSize(); | ||
this.atimeMs = entry.atime; | ||
this.mtimeMs = entry.mtime; | ||
this.ctimeMs = entry.ctime; | ||
this.birthtimeMs = entry.birthtime; | ||
this.atime = new Date(entry.atime); | ||
this.mtime = new Date(entry.mtime); | ||
this.ctime = new Date(entry.ctime); | ||
this.birthtime = new Date(entry.birthtime); | ||
} | ||
isFile() { | ||
return Boolean(this.mode & S_IFREG); | ||
} | ||
isDirectory() { | ||
return Boolean(this.mode & S_IFDIR); | ||
} | ||
isBlockDevice() { | ||
return false; | ||
} | ||
isCharacterDevice() { | ||
return false; | ||
} | ||
isSymbolicLink() { | ||
return false; | ||
} | ||
isFIFO() { | ||
return false; | ||
} | ||
isSocket() { | ||
return false; | ||
} | ||
} | ||
class Dirent { | ||
constructor(name, entry) { | ||
_defineProperty(this, "name", void 0); | ||
_mode.set(this, { | ||
writable: true, | ||
value: void 0 | ||
}); | ||
this.name = name; | ||
_classPrivateFieldSet(this, _mode, entry.mode); | ||
} | ||
isFile() { | ||
return Boolean(_classPrivateFieldGet(this, _mode) & S_IFREG); | ||
} | ||
isDirectory() { | ||
return Boolean(_classPrivateFieldGet(this, _mode) & S_IFDIR); | ||
} | ||
isBlockDevice() { | ||
return false; | ||
} | ||
isCharacterDevice() { | ||
return false; | ||
} | ||
isSymbolicLink() { | ||
return false; | ||
} | ||
isFIFO() { | ||
return false; | ||
} | ||
isSocket() { | ||
return false; | ||
} | ||
} | ||
var _mode = new WeakMap(); | ||
class File extends Entry { | ||
@@ -450,3 +884,3 @@ constructor(buffer, mode) { | ||
super.access(); | ||
return this.buffer; | ||
return Buffer.from(this.buffer); | ||
} | ||
@@ -460,3 +894,3 @@ | ||
getSize() { | ||
return this.buffer.length; | ||
return this.buffer.byteLength; | ||
} | ||
@@ -475,3 +909,3 @@ | ||
if (typeof contents !== 'string' && contents.buffer instanceof SharedArrayBuffer) { | ||
return Buffer.from(contents.buffer); | ||
return contents; | ||
} | ||
@@ -490,13 +924,8 @@ | ||
return buffer; | ||
} // exported for worker farm IPC | ||
async function _handle(id, method, args) { | ||
let instance = (0, _nullthrows.default)(instances.get(id)); // $FlowFixMe | ||
return instance[method](...args); | ||
} | ||
class WorkerFS extends MemoryFS { | ||
constructor(id) { | ||
constructor(id, handleFn) { | ||
// TODO Make this not a subclass | ||
// $FlowFixMe | ||
super(); | ||
@@ -506,3 +935,27 @@ | ||
_defineProperty(this, "handleFn", void 0); | ||
this.id = id; | ||
this.handleFn = handleFn; | ||
handleFn('_registerWorker', [_workers.default.getWorkerApi().createReverseHandle(event => { | ||
switch (event.type) { | ||
case 'writeFile': | ||
this.files.set(event.path, event.entry); | ||
break; | ||
case 'unlink': | ||
this.files.delete(event.path); | ||
this.dirs.delete(event.path); | ||
this.symlinks.delete(event.path); | ||
break; | ||
case 'mkdir': | ||
this.dirs.set(event.path, new Directory()); | ||
break; | ||
case 'symlink': | ||
this.symlinks.set(event.path, event.target); | ||
break; | ||
} | ||
})]); | ||
} | ||
@@ -515,2 +968,3 @@ | ||
serialize() { | ||
// $FlowFixMe | ||
return { | ||
@@ -521,56 +975,39 @@ id: this.id | ||
handle(method, args) { | ||
return _workers.default.callMaster({ | ||
location: __filename, | ||
args: [this.id, method, args], | ||
method: '_handle' | ||
}); | ||
} | ||
async writeFile(filePath, contents, options) { | ||
writeFile(filePath, contents, options) { | ||
super.writeFile(filePath, contents, options); | ||
let buffer = makeShared(contents); | ||
return this.handle('writeFile', [filePath, buffer, options]); | ||
return this.handleFn('writeFile', [filePath, buffer, options]); | ||
} | ||
async readFile(filePath, encoding) { | ||
let buffer = await this.handle('readFile', [filePath]); | ||
if (encoding) { | ||
return Buffer.from(buffer).toString(encoding); | ||
} | ||
return buffer; | ||
unlink(filePath) { | ||
super.unlink(filePath); | ||
return this.handleFn('unlink', [filePath]); | ||
} | ||
async stat(filePath) { | ||
return this.handle('stat', [filePath]); | ||
mkdirp(dir) { | ||
super.mkdirp(dir); | ||
return this.handleFn('mkdirp', [dir]); | ||
} | ||
async readdir(dir) { | ||
return this.handle('readdir', [dir]); | ||
rimraf(filePath) { | ||
super.rimraf(filePath); | ||
return this.handleFn('rimraf', [filePath]); | ||
} | ||
async unlink(filePath) { | ||
return this.handle('unlink', [filePath]); | ||
ncp(source, destination) { | ||
super.ncp(source, destination); | ||
return this.handleFn('ncp', [source, destination]); | ||
} | ||
async mkdirp(dir) { | ||
return this.handle('mkdirp', [dir]); | ||
symlink(target, path) { | ||
super.symlink(target, path); | ||
return this.handleFn('symlink', [target, path]); | ||
} | ||
async rimraf(filePath) { | ||
return this.handle('rimraf', [filePath]); | ||
} | ||
async ncp(source, destination) { | ||
return this.handle('ncp', [source, destination]); | ||
} | ||
async exists(filePath) { | ||
return this.handle('exists', [filePath]); | ||
} | ||
} | ||
(0, _utils.registerSerializableClass)(`${_package.default.version}:MemoryFS`, MemoryFS); | ||
(0, _utils.registerSerializableClass)(`${_package.default.version}:WorkerFS`, WorkerFS); | ||
(0, _utils.registerSerializableClass)(`${_package.default.version}:WorkerFS`, WorkerFS); | ||
(0, _utils.registerSerializableClass)(`${_package.default.version}:Stat`, Stat); | ||
(0, _utils.registerSerializableClass)(`${_package.default.version}:File`, File); | ||
(0, _utils.registerSerializableClass)(`${_package.default.version}:Directory`, Directory); |
@@ -18,2 +18,4 @@ "use strict"; | ||
var _watcher = _interopRequireDefault(require("@parcel/watcher")); | ||
var _package = _interopRequireDefault(require("../package.json")); | ||
@@ -43,2 +45,4 @@ | ||
_defineProperty(this, "utimes", (0, _utils.promisify)(_fs.default.utimes)); | ||
_defineProperty(this, "mkdirp", (0, _utils.promisify)(_mkdirp.default)); | ||
@@ -55,2 +59,12 @@ | ||
_defineProperty(this, "cwd", process.cwd); | ||
_defineProperty(this, "chdir", process.chdir); | ||
_defineProperty(this, "readFileSync", _fs.default.readFileSync); | ||
_defineProperty(this, "statSync", _fs.default.statSync); | ||
_defineProperty(this, "realpathSync", _fs.default.realpathSync); | ||
_defineProperty(this, "existsSync", _fs.default.existsSync); | ||
} | ||
@@ -73,2 +87,14 @@ | ||
watch(dir, fn, opts) { | ||
return _watcher.default.subscribe(dir, fn, opts); | ||
} | ||
getEventsSince(dir, snapshot, opts) { | ||
return _watcher.default.getEventsSince(dir, snapshot, opts); | ||
} | ||
async writeSnapshot(dir, snapshot, opts) { | ||
await _watcher.default.writeSnapshot(dir, snapshot, opts); | ||
} | ||
static deserialize() { | ||
@@ -75,0 +101,0 @@ return new NodeFS(); |
{ | ||
"name": "@parcel/fs", | ||
"version": "2.0.0-alpha.1.1", | ||
"version": "2.0.0-alpha.2", | ||
"description": "Blazing fast, zero configuration web application bundler", | ||
@@ -12,3 +12,3 @@ "main": "lib/index.js", | ||
"engines": { | ||
"node": ">= 8.0.0" | ||
"node": ">= 10.0.0" | ||
}, | ||
@@ -18,9 +18,6 @@ "publishConfig": { | ||
}, | ||
"scripts": { | ||
"test": "echo this package has no tests yet", | ||
"test-ci": "yarn test" | ||
}, | ||
"dependencies": { | ||
"@parcel/utils": "^2.0.0-alpha.1.1", | ||
"@parcel/workers": "^2.0.0-alpha.1.1", | ||
"@parcel/utils": "^2.0.0-alpha.2", | ||
"@parcel/watcher": "^2.0.0-alpha.3", | ||
"@parcel/workers": "^2.0.0-alpha.2", | ||
"mkdirp": "^0.5.1", | ||
@@ -31,3 +28,3 @@ "ncp": "^2.0.0", | ||
}, | ||
"gitHead": "8f9c49c1c53753b1139501cc4090cd4b1ab5ac0b" | ||
"gitHead": "aa2619af0a50fecbab024d30521391814560337f" | ||
} |
// @flow strict-local | ||
import type {FileSystem} from './types'; | ||
import type {FilePath} from '@parcel/types'; | ||
import path from 'path'; | ||
export * from './types'; | ||
export * from './NodeFS'; | ||
export {MemoryFS} from './MemoryFS'; | ||
export * from './MemoryFS'; | ||
export * from './OverlayFS'; | ||
// Recursively copies a directory from the sourceFS to the destinationFS | ||
export async function ncp( | ||
sourceFS: FileSystem, | ||
source: FilePath, | ||
destinationFS: FileSystem, | ||
destination: FilePath | ||
) { | ||
await destinationFS.mkdirp(destination); | ||
let files = await sourceFS.readdir(source); | ||
for (let file of files) { | ||
let sourcePath = path.join(source, file); | ||
let destPath = path.join(destination, file); | ||
let stats = await sourceFS.stat(sourcePath); | ||
if (stats.isFile()) { | ||
await new Promise((resolve, reject) => { | ||
sourceFS | ||
.createReadStream(sourcePath) | ||
.pipe(destinationFS.createWriteStream(destPath)) | ||
.on('finish', () => resolve()) | ||
.on('error', reject); | ||
}); | ||
} else if (stats.isDirectory()) { | ||
await ncp(sourceFS, sourcePath, destinationFS, destPath); | ||
} | ||
} | ||
} |
// @flow | ||
import type {FileSystem, FileOptions} from './types'; | ||
import type {FileSystem, FileOptions, ReaddirOptions} from './types'; | ||
import type {FilePath} from '@parcel/types'; | ||
import type { | ||
Event, | ||
Options as WatcherOptions, | ||
AsyncSubscription | ||
} from '@parcel/watcher'; | ||
import path from 'path'; | ||
@@ -8,3 +14,3 @@ import {Readable, Writable} from 'stream'; | ||
import packageJSON from '../package.json'; | ||
import WorkerFarm from '@parcel/workers'; | ||
import WorkerFarm, {Handle} from '@parcel/workers'; | ||
import nullthrows from 'nullthrows'; | ||
@@ -15,14 +21,44 @@ | ||
type HandleFunction = (...args: Array<any>) => any; | ||
type SerializedMemoryFS = { | ||
id: number | ||
id: number, | ||
handle: any, | ||
dirs: Map<FilePath, Directory>, | ||
files: Map<FilePath, File>, | ||
symlinks: Map<FilePath, FilePath>, | ||
... | ||
}; | ||
type WorkerEvent = {| | ||
type: 'writeFile' | 'unlink' | 'mkdir' | 'symlink', | ||
path: FilePath, | ||
entry?: Entry, | ||
target?: FilePath | ||
|}; | ||
export class MemoryFS implements FileSystem { | ||
dirs: Map<FilePath, Directory>; | ||
files: Map<FilePath, File>; | ||
symlinks: Map<FilePath, FilePath>; | ||
watchers: Map<FilePath, Set<Watcher>>; | ||
events: Array<Event>; | ||
id: number; | ||
constructor() { | ||
handle: Handle; | ||
farm: WorkerFarm; | ||
_cwd: FilePath; | ||
_eventQueue: Array<Event>; | ||
_watcherTimer: TimeoutID; | ||
workers: Array<(WorkerEvent) => Promise<mixed>>; | ||
constructor(workerFarm: WorkerFarm) { | ||
this.farm = workerFarm; | ||
this.dirs = new Map([['/', new Directory()]]); | ||
this.files = new Map(); | ||
this.symlinks = new Map(); | ||
this.watchers = new Map(); | ||
this.events = []; | ||
this.id = id++; | ||
this._cwd = '/'; | ||
this.workers = []; | ||
this._eventQueue = []; | ||
instances.set(this.id, this); | ||
@@ -32,8 +68,30 @@ } | ||
static deserialize(opts: SerializedMemoryFS) { | ||
return instances.get(opts.id) || new WorkerFS(opts.id); | ||
if (instances.has(opts.id)) { | ||
return instances.get(opts.id); | ||
} | ||
let fs = new WorkerFS(opts.id, nullthrows(opts.handle)); | ||
fs.dirs = opts.dirs; | ||
fs.files = opts.files; | ||
fs.symlinks = opts.symlinks; | ||
return fs; | ||
} | ||
serialize(): SerializedMemoryFS { | ||
if (!this.handle) { | ||
this.handle = this.farm.createReverseHandle( | ||
(fn: string, args: Array<mixed>) => { | ||
// $FlowFixMe | ||
return this[fn](...args); | ||
} | ||
); | ||
} | ||
return { | ||
id: this.id | ||
$$raw: false, | ||
id: this.id, | ||
handle: this.handle, | ||
dirs: this.dirs, | ||
files: this.files, | ||
symlinks: this.symlinks | ||
}; | ||
@@ -43,9 +101,34 @@ } | ||
cwd() { | ||
return '/'; | ||
return this._cwd; | ||
} | ||
_normalizePath(filePath: FilePath): FilePath { | ||
return path.resolve(this.cwd(), filePath); | ||
chdir(dir: FilePath) { | ||
this._cwd = dir; | ||
} | ||
_normalizePath(filePath: FilePath, realpath: boolean = true): FilePath { | ||
filePath = path.resolve(this.cwd(), filePath); | ||
// get realpath by following symlinks | ||
if (realpath) { | ||
let {root, dir, base} = path.parse(filePath); | ||
let parts = dir | ||
.slice(root.length) | ||
.split(path.sep) | ||
.concat(base); | ||
let res = root; | ||
for (let part of parts) { | ||
res = path.join(res, part); | ||
let symlink = this.symlinks.get(res); | ||
if (symlink) { | ||
res = symlink; | ||
} | ||
} | ||
return res; | ||
} | ||
return filePath; | ||
} | ||
async writeFile( | ||
@@ -71,8 +154,24 @@ filePath: FilePath, | ||
file.write(buffer, mode); | ||
this.files.set(filePath, file); | ||
} else { | ||
this.files.set(filePath, new File(buffer, mode)); | ||
} | ||
await this._sendWorkerEvent({ | ||
type: 'writeFile', | ||
path: filePath, | ||
entry: this.files.get(filePath) | ||
}); | ||
this._triggerEvent({ | ||
type: file ? 'update' : 'create', | ||
path: filePath | ||
}); | ||
} | ||
async readFile(filePath: FilePath, encoding?: buffer$Encoding): Promise<any> { | ||
readFile(filePath: FilePath, encoding?: buffer$Encoding): Promise<any> { | ||
return Promise.resolve(this.readFileSync(filePath, encoding)); | ||
} | ||
readFileSync(filePath: FilePath, encoding?: buffer$Encoding): any { | ||
filePath = this._normalizePath(filePath); | ||
@@ -97,3 +196,3 @@ let file = this.files.get(filePath); | ||
async stat(filePath: FilePath) { | ||
statSync(filePath: FilePath) { | ||
filePath = this._normalizePath(filePath); | ||
@@ -114,3 +213,7 @@ | ||
async readdir(dir: FilePath) { | ||
stat(filePath: FilePath) { | ||
return Promise.resolve(this.statSync(filePath)); | ||
} | ||
readdir(dir: FilePath, opts?: ReaddirOptions): Promise<any> { | ||
dir = this._normalizePath(dir); | ||
@@ -124,13 +227,31 @@ if (!this.dirs.has(dir)) { | ||
let res = []; | ||
for (let filePath of this.files.keys()) { | ||
if (filePath.startsWith(dir)) { | ||
let end = filePath.indexOf(path.sep, dir.length); | ||
if (end === -1) { | ||
end = filePath.length; | ||
for (let [filePath, entry] of this.dirs) { | ||
if ( | ||
filePath.startsWith(dir) && | ||
filePath.indexOf(path.sep, dir.length) === -1 | ||
) { | ||
let name = filePath.slice(dir.length); | ||
if (opts?.withFileTypes) { | ||
res.push(new Dirent(name, entry)); | ||
} else { | ||
res.push(name); | ||
} | ||
res.push(filePath.slice(dir.length, end)); | ||
} | ||
} | ||
return res; | ||
for (let [filePath, entry] of this.files) { | ||
if ( | ||
filePath.startsWith(dir) && | ||
filePath.indexOf(path.sep, dir.length) === -1 | ||
) { | ||
let name = filePath.slice(dir.length); | ||
if (opts?.withFileTypes) { | ||
res.push(new Dirent(name, entry)); | ||
} else { | ||
res.push(name); | ||
} | ||
} | ||
} | ||
return Promise.resolve(res); | ||
} | ||
@@ -146,2 +267,15 @@ | ||
this.dirs.delete(filePath); | ||
this.watchers.delete(filePath); | ||
await this._sendWorkerEvent({ | ||
type: 'unlink', | ||
path: filePath | ||
}); | ||
this._triggerEvent({ | ||
type: 'delete', | ||
path: filePath | ||
}); | ||
return Promise.resolve(); | ||
} | ||
@@ -152,3 +286,3 @@ | ||
if (this.dirs.has(dir)) { | ||
return; | ||
return Promise.resolve(); | ||
} | ||
@@ -167,4 +301,16 @@ | ||
this.dirs.set(dir, new Directory()); | ||
await this._sendWorkerEvent({ | ||
type: 'mkdir', | ||
path: dir | ||
}); | ||
this._triggerEvent({ | ||
type: 'create', | ||
path: dir | ||
}); | ||
dir = path.dirname(dir); | ||
} | ||
return Promise.resolve(); | ||
} | ||
@@ -180,2 +326,11 @@ | ||
this.files.delete(filePath); | ||
await this._sendWorkerEvent({ | ||
type: 'unlink', | ||
path: filePath | ||
}); | ||
this._triggerEvent({ | ||
type: 'delete', | ||
path: filePath | ||
}); | ||
} | ||
@@ -187,8 +342,49 @@ } | ||
this.dirs.delete(dirPath); | ||
this.watchers.delete(dirPath); | ||
await this._sendWorkerEvent({ | ||
type: 'unlink', | ||
path: filePath | ||
}); | ||
this._triggerEvent({ | ||
type: 'delete', | ||
path: dirPath | ||
}); | ||
} | ||
} | ||
for (let filePath of this.symlinks.keys()) { | ||
if (filePath.startsWith(dir)) { | ||
this.symlinks.delete(filePath); | ||
await this._sendWorkerEvent({ | ||
type: 'unlink', | ||
path: filePath | ||
}); | ||
} | ||
} | ||
this.dirs.delete(filePath); | ||
await this._sendWorkerEvent({ | ||
type: 'unlink', | ||
path: filePath | ||
}); | ||
this._triggerEvent({ | ||
type: 'delete', | ||
path: filePath | ||
}); | ||
} else if (this.files.has(filePath)) { | ||
this.files.delete(filePath); | ||
await this._sendWorkerEvent({ | ||
type: 'unlink', | ||
path: filePath | ||
}); | ||
this._triggerEvent({ | ||
type: 'delete', | ||
path: filePath | ||
}); | ||
} | ||
this.dirs.delete(filePath); | ||
this.files.delete(filePath); | ||
return Promise.resolve(); | ||
} | ||
@@ -202,2 +398,11 @@ | ||
this.dirs.set(destination, new Directory()); | ||
await this._sendWorkerEvent({ | ||
type: 'mkdir', | ||
path: destination | ||
}); | ||
this._triggerEvent({ | ||
type: 'create', | ||
path: destination | ||
}); | ||
} | ||
@@ -211,2 +416,10 @@ | ||
this.dirs.set(destName, new Directory()); | ||
await this._sendWorkerEvent({ | ||
type: 'mkdir', | ||
path: destination | ||
}); | ||
this._triggerEvent({ | ||
type: 'create', | ||
path: destName | ||
}); | ||
} | ||
@@ -216,6 +429,17 @@ } | ||
for (let [filePath, buffer] of this.files) { | ||
for (let [filePath, file] of this.files) { | ||
if (filePath.startsWith(dir)) { | ||
let destName = path.join(destination, filePath.slice(dir.length)); | ||
this.files.set(destName, buffer); | ||
let exists = this.files.has(destName); | ||
this.files.set(destName, file); | ||
await this._sendWorkerEvent({ | ||
type: 'writeFile', | ||
path: destName, | ||
entry: file | ||
}); | ||
this._triggerEvent({ | ||
type: exists ? 'update' : 'create', | ||
path: destName | ||
}); | ||
} | ||
@@ -236,13 +460,144 @@ } | ||
async realpath(filePath: FilePath) { | ||
filePath = this._normalizePath(filePath); | ||
return filePath; | ||
realpathSync(filePath: FilePath) { | ||
return this._normalizePath(filePath); | ||
} | ||
async exists(filePath: FilePath) { | ||
realpath(filePath: FilePath) { | ||
return Promise.resolve(this.realpathSync(filePath)); | ||
} | ||
async symlink(target: FilePath, path: FilePath) { | ||
target = this._normalizePath(target); | ||
path = this._normalizePath(path); | ||
this.symlinks.set(path, target); | ||
await this._sendWorkerEvent({ | ||
type: 'symlink', | ||
path, | ||
target | ||
}); | ||
} | ||
existsSync(filePath: FilePath) { | ||
filePath = this._normalizePath(filePath); | ||
return this.files.has(filePath) || this.dirs.has(filePath); | ||
} | ||
exists(filePath: FilePath) { | ||
return Promise.resolve(this.existsSync(filePath)); | ||
} | ||
_triggerEvent(event: Event) { | ||
this.events.push(event); | ||
if (this.watchers.size === 0) { | ||
return; | ||
} | ||
// Batch events | ||
this._eventQueue.push(event); | ||
clearTimeout(this._watcherTimer); | ||
this._watcherTimer = setTimeout(() => { | ||
let events = this._eventQueue; | ||
this._eventQueue = []; | ||
for (let [dir, watchers] of this.watchers) { | ||
if (!dir.endsWith(path.sep)) { | ||
dir += path.sep; | ||
} | ||
if (event.path.startsWith(dir)) { | ||
for (let watcher of watchers) { | ||
watcher.trigger(events); | ||
} | ||
} | ||
} | ||
}, 50); | ||
} | ||
_registerWorker(fn: WorkerEvent => Promise<mixed>) { | ||
this.workers.push(fn); | ||
} | ||
async _sendWorkerEvent(event: WorkerEvent) { | ||
for (let worker of this.workers) { | ||
await worker(event); | ||
} | ||
} | ||
watch( | ||
dir: FilePath, | ||
fn: (err: ?Error, events: Array<Event>) => mixed, | ||
opts: WatcherOptions | ||
): Promise<AsyncSubscription> { | ||
dir = this._normalizePath(dir); | ||
let watcher = new Watcher(fn, opts); | ||
let watchers = this.watchers.get(dir); | ||
if (!watchers) { | ||
watchers = new Set(); | ||
this.watchers.set(dir, watchers); | ||
} | ||
watchers.add(watcher); | ||
return Promise.resolve({ | ||
unsubscribe: () => { | ||
watchers = nullthrows(watchers); | ||
watchers.delete(watcher); | ||
if (watchers.size === 0) { | ||
this.watchers.delete(dir); | ||
} | ||
return Promise.resolve(); | ||
} | ||
}); | ||
} | ||
async getEventsSince( | ||
dir: FilePath, | ||
snapshot: FilePath, | ||
opts: WatcherOptions | ||
): Promise<Array<Event>> { | ||
let contents = await this.readFile(snapshot, 'utf8'); | ||
let len = Number(contents); | ||
let events = this.events.slice(len); | ||
let ignore = opts.ignore; | ||
if (ignore) { | ||
events = events.filter( | ||
event => !ignore.some(i => event.path.startsWith(i + path.sep)) | ||
); | ||
} | ||
return events; | ||
} | ||
async writeSnapshot(dir: FilePath, snapshot: FilePath): Promise<void> { | ||
await this.writeFile(snapshot, '' + this.events.length); | ||
} | ||
} | ||
class Watcher { | ||
fn: (err: ?Error, events: Array<Event>) => mixed; | ||
options: WatcherOptions; | ||
constructor( | ||
fn: (err: ?Error, events: Array<Event>) => mixed, | ||
options: WatcherOptions | ||
) { | ||
this.fn = fn; | ||
this.options = options; | ||
} | ||
trigger(events: Array<Event>) { | ||
let ignore = this.options.ignore; | ||
if (ignore) { | ||
events = events.filter( | ||
event => !ignore.some(i => event.path.startsWith(i + path.sep)) | ||
); | ||
} | ||
this.fn(null, events); | ||
} | ||
} | ||
class FSError extends Error { | ||
@@ -362,32 +717,106 @@ code: string; | ||
stat() { | ||
return { | ||
dev: 0, | ||
ino: 0, | ||
mode: this.mode, | ||
nlink: 0, | ||
uid: 0, | ||
gid: 0, | ||
rdev: 0, | ||
size: this.getSize(), | ||
blksize: 0, | ||
blocks: 0, | ||
atimeMs: this.atime, | ||
mtimeMs: this.mtime, | ||
ctimeMs: this.ctime, | ||
birthtimeMs: this.birthtime, | ||
atime: new Date(this.atime), | ||
mtime: new Date(this.mtime), | ||
ctime: new Date(this.ctime), | ||
birthtime: new Date(this.birthtime), | ||
isFile: () => Boolean(this.mode & S_IFREG), | ||
isDirectory: () => Boolean(this.mode & S_IFDIR), | ||
isBlockDevice: () => false, | ||
isCharacterDevice: () => false, | ||
isSymbolicLink: () => false, | ||
isFIFO: () => false, | ||
isSocket: () => false | ||
}; | ||
return new Stat(this); | ||
} | ||
} | ||
class Stat { | ||
dev = 0; | ||
ino = 0; | ||
mode: number; | ||
nlink = 0; | ||
uid = 0; | ||
gid = 0; | ||
rdev = 0; | ||
size: number; | ||
blksize = 0; | ||
blocks = 0; | ||
atimeMs: number; | ||
mtimeMs: number; | ||
ctimeMs: number; | ||
birthtimeMs: number; | ||
atime: Date; | ||
mtime: Date; | ||
ctime: Date; | ||
birthtime: Date; | ||
constructor(entry: Entry) { | ||
this.mode = entry.mode; | ||
this.size = entry.getSize(); | ||
this.atimeMs = entry.atime; | ||
this.mtimeMs = entry.mtime; | ||
this.ctimeMs = entry.ctime; | ||
this.birthtimeMs = entry.birthtime; | ||
this.atime = new Date(entry.atime); | ||
this.mtime = new Date(entry.mtime); | ||
this.ctime = new Date(entry.ctime); | ||
this.birthtime = new Date(entry.birthtime); | ||
} | ||
isFile() { | ||
return Boolean(this.mode & S_IFREG); | ||
} | ||
isDirectory() { | ||
return Boolean(this.mode & S_IFDIR); | ||
} | ||
isBlockDevice() { | ||
return false; | ||
} | ||
isCharacterDevice() { | ||
return false; | ||
} | ||
isSymbolicLink() { | ||
return false; | ||
} | ||
isFIFO() { | ||
return false; | ||
} | ||
isSocket() { | ||
return false; | ||
} | ||
} | ||
class Dirent { | ||
name: string; | ||
#mode: number; | ||
constructor(name: string, entry: Entry) { | ||
this.name = name; | ||
this.#mode = entry.mode; | ||
} | ||
isFile() { | ||
return Boolean(this.#mode & S_IFREG); | ||
} | ||
isDirectory() { | ||
return Boolean(this.#mode & S_IFDIR); | ||
} | ||
isBlockDevice() { | ||
return false; | ||
} | ||
isCharacterDevice() { | ||
return false; | ||
} | ||
isSymbolicLink() { | ||
return false; | ||
} | ||
isFIFO() { | ||
return false; | ||
} | ||
isSocket() { | ||
return false; | ||
} | ||
} | ||
class File extends Entry { | ||
@@ -402,3 +831,3 @@ buffer: Buffer; | ||
super.access(); | ||
return this.buffer; | ||
return Buffer.from(this.buffer); | ||
} | ||
@@ -412,3 +841,3 @@ | ||
getSize() { | ||
return this.buffer.length; | ||
return this.buffer.byteLength; | ||
} | ||
@@ -428,3 +857,3 @@ } | ||
) { | ||
return Buffer.from(contents.buffer); | ||
return contents; | ||
} | ||
@@ -444,15 +873,33 @@ | ||
// exported for worker farm IPC | ||
export async function _handle(id: number, method: string, args: Array<any>) { | ||
let instance = nullthrows(instances.get(id)); | ||
// $FlowFixMe | ||
return instance[method](...args); | ||
} | ||
class WorkerFS extends MemoryFS { | ||
id: number; | ||
handleFn: HandleFunction; | ||
constructor(id: number) { | ||
constructor(id: number, handleFn: HandleFunction) { | ||
// TODO Make this not a subclass | ||
// $FlowFixMe | ||
super(); | ||
this.id = id; | ||
this.handleFn = handleFn; | ||
handleFn('_registerWorker', [ | ||
WorkerFarm.getWorkerApi().createReverseHandle(event => { | ||
switch (event.type) { | ||
case 'writeFile': | ||
this.files.set(event.path, event.entry); | ||
break; | ||
case 'unlink': | ||
this.files.delete(event.path); | ||
this.dirs.delete(event.path); | ||
this.symlinks.delete(event.path); | ||
break; | ||
case 'mkdir': | ||
this.dirs.set(event.path, new Directory()); | ||
break; | ||
case 'symlink': | ||
this.symlinks.set(event.path, event.target); | ||
break; | ||
} | ||
}) | ||
]); | ||
} | ||
@@ -465,2 +912,3 @@ | ||
serialize(): SerializedMemoryFS { | ||
// $FlowFixMe | ||
return { | ||
@@ -471,11 +919,3 @@ id: this.id | ||
handle(method: string, args: Array<any>): Promise<any> { | ||
return WorkerFarm.callMaster({ | ||
location: __filename, | ||
args: [this.id, method, args], | ||
method: '_handle' | ||
}); | ||
} | ||
async writeFile( | ||
writeFile( | ||
filePath: FilePath, | ||
@@ -485,42 +925,31 @@ contents: Buffer | string, | ||
) { | ||
super.writeFile(filePath, contents, options); | ||
let buffer = makeShared(contents); | ||
return this.handle('writeFile', [filePath, buffer, options]); | ||
return this.handleFn('writeFile', [filePath, buffer, options]); | ||
} | ||
async readFile(filePath: FilePath, encoding?: buffer$Encoding) { | ||
let buffer = await this.handle('readFile', [filePath]); | ||
if (encoding) { | ||
return Buffer.from(buffer).toString(encoding); | ||
} | ||
return buffer; | ||
unlink(filePath: FilePath) { | ||
super.unlink(filePath); | ||
return this.handleFn('unlink', [filePath]); | ||
} | ||
async stat(filePath: FilePath) { | ||
return this.handle('stat', [filePath]); | ||
mkdirp(dir: FilePath) { | ||
super.mkdirp(dir); | ||
return this.handleFn('mkdirp', [dir]); | ||
} | ||
async readdir(dir: FilePath) { | ||
return this.handle('readdir', [dir]); | ||
rimraf(filePath: FilePath) { | ||
super.rimraf(filePath); | ||
return this.handleFn('rimraf', [filePath]); | ||
} | ||
async unlink(filePath: FilePath) { | ||
return this.handle('unlink', [filePath]); | ||
ncp(source: FilePath, destination: FilePath) { | ||
super.ncp(source, destination); | ||
return this.handleFn('ncp', [source, destination]); | ||
} | ||
async mkdirp(dir: FilePath) { | ||
return this.handle('mkdirp', [dir]); | ||
symlink(target: FilePath, path: FilePath) { | ||
super.symlink(target, path); | ||
return this.handleFn('symlink', [target, path]); | ||
} | ||
async rimraf(filePath: FilePath) { | ||
return this.handle('rimraf', [filePath]); | ||
} | ||
async ncp(source: FilePath, destination: FilePath) { | ||
return this.handle('ncp', [source, destination]); | ||
} | ||
async exists(filePath: FilePath) { | ||
return this.handle('exists', [filePath]); | ||
} | ||
} | ||
@@ -530,1 +959,4 @@ | ||
registerSerializableClass(`${packageJSON.version}:WorkerFS`, WorkerFS); | ||
registerSerializableClass(`${packageJSON.version}:Stat`, Stat); | ||
registerSerializableClass(`${packageJSON.version}:File`, File); | ||
registerSerializableClass(`${packageJSON.version}:Directory`, Directory); |
// @flow | ||
import type {FileSystem} from './types'; | ||
import type {FilePath} from '@parcel/types'; | ||
import type { | ||
Event, | ||
Options as WatcherOptions, | ||
AsyncSubscription | ||
} from '@parcel/watcher'; | ||
@@ -10,2 +15,3 @@ import fs from 'fs'; | ||
import {registerSerializableClass, promisify} from '@parcel/utils'; | ||
import watcher from '@parcel/watcher'; | ||
import packageJSON from '../package.json'; | ||
@@ -25,2 +31,3 @@ | ||
unlink = promisify(fs.unlink); | ||
utimes = promisify(fs.utimes); | ||
mkdirp = promisify(mkdirp); | ||
@@ -32,3 +39,9 @@ rimraf = promisify(rimraf); | ||
cwd = process.cwd; | ||
chdir = process.chdir; | ||
readFileSync = fs.readFileSync; | ||
statSync = fs.statSync; | ||
realpathSync = fs.realpathSync; | ||
existsSync = fs.existsSync; | ||
async realpath(originalPath: string): Promise<string> { | ||
@@ -50,2 +63,26 @@ try { | ||
watch( | ||
dir: FilePath, | ||
fn: (err: ?Error, events: Array<Event>) => mixed, | ||
opts: WatcherOptions | ||
): Promise<AsyncSubscription> { | ||
return watcher.subscribe(dir, fn, opts); | ||
} | ||
getEventsSince( | ||
dir: FilePath, | ||
snapshot: FilePath, | ||
opts: WatcherOptions | ||
): Promise<Array<Event>> { | ||
return watcher.getEventsSince(dir, snapshot, opts); | ||
} | ||
async writeSnapshot( | ||
dir: FilePath, | ||
snapshot: FilePath, | ||
opts: WatcherOptions | ||
): Promise<void> { | ||
await watcher.writeSnapshot(dir, snapshot, opts); | ||
} | ||
static deserialize() { | ||
@@ -52,0 +89,0 @@ return new NodeFS(); |
@@ -5,6 +5,12 @@ // @flow | ||
import type {Readable, Writable} from 'stream'; | ||
import type { | ||
Event, | ||
Options as WatcherOptions, | ||
AsyncSubscription | ||
} from '@parcel/watcher'; | ||
export type FileOptions = { | ||
mode?: number | ||
}; | ||
export type FileOptions = {mode?: number, ...}; | ||
export type ReaddirOptions = | ||
| {withFileTypes?: false, ...} | ||
| {withFileTypes: true, ...}; | ||
@@ -14,2 +20,4 @@ export interface FileSystem { | ||
readFile(filePath: FilePath, encoding?: buffer$Encoding): Promise<string>; | ||
readFileSync(filePath: FilePath): Buffer; | ||
readFileSync(filePath: FilePath, encoding?: buffer$Encoding): string; | ||
writeFile( | ||
@@ -26,6 +34,13 @@ filePath: FilePath, | ||
stat(filePath: FilePath): Promise<$Shape<Stats>>; | ||
readdir(path: FilePath): Promise<FilePath[]>; | ||
statSync(filePath: FilePath): $Shape<Stats>; | ||
readdir( | ||
path: FilePath, | ||
opts?: {withFileTypes?: false, ...} | ||
): Promise<FilePath[]>; | ||
readdir(path: FilePath, opts: {withFileTypes: true, ...}): Promise<Dirent[]>; | ||
unlink(path: FilePath): Promise<void>; | ||
realpath(path: FilePath): Promise<FilePath>; | ||
realpathSync(path: FilePath): FilePath; | ||
exists(path: FilePath): Promise<boolean>; | ||
existsSync(path: FilePath): boolean; | ||
mkdirp(path: FilePath): Promise<void>; | ||
@@ -37,2 +52,30 @@ rimraf(path: FilePath): Promise<void>; | ||
cwd(): FilePath; | ||
chdir(dir: FilePath): void; | ||
watch( | ||
dir: FilePath, | ||
fn: (err: ?Error, events: Array<Event>) => mixed, | ||
opts: WatcherOptions | ||
): Promise<AsyncSubscription>; | ||
getEventsSince( | ||
dir: FilePath, | ||
snapshot: FilePath, | ||
opts: WatcherOptions | ||
): Promise<Array<Event>>; | ||
writeSnapshot( | ||
dir: FilePath, | ||
snapshot: FilePath, | ||
opts: WatcherOptions | ||
): Promise<void>; | ||
} | ||
// https://nodejs.org/api/fs.html#fs_class_fs_dirent | ||
export interface Dirent { | ||
+name: string; | ||
isBlockDevice(): boolean; | ||
isCharacterDevice(): boolean; | ||
isDirectory(): boolean; | ||
isFIFO(): boolean; | ||
isFile(): boolean; | ||
isSocket(): boolean; | ||
isSymbolicLink(): boolean; | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No tests
QualityPackage does not have any tests. This is a strong signal of a poorly maintained or low quality package.
Found 1 instance in 1 package
65236
13
2141
2
7