Socket
Socket
Sign inDemoInstall

@parcel/fs

Package Overview
Dependencies
Maintainers
1
Versions
876
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@parcel/fs - npm Package Compare versions

Comparing version 2.0.0-alpha.1.1 to 2.0.0-alpha.2

lib/OverlayFS.js

61

lib/index.js

@@ -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();

15

package.json
{
"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;
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc