static-fs
Version:
A static filesystem to bundle files and read them using Node.js
1,618 lines (1,285 loc) • 88.7 kB
JavaScript
(function(e, a) { for(var i in a) e[i] = a[i]; }(exports, /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ({
/***/ "./node_modules/webpack/buildin/harmony-module.js":
/***/ (function(module, exports) {
module.exports = function(originalModule) {
if (!originalModule.webpackPolyfill) {
var module = Object.create(originalModule);
// module.parent = undefined by default
if (!module.children) module.children = [];
Object.defineProperty(module, "loaded", {
enumerable: true,
get: function() {
return module.l;
}
});
Object.defineProperty(module, "id", {
enumerable: true,
get: function() {
return module.i;
}
});
Object.defineProperty(module, "exports", {
enumerable: true
});
module.webpackPolyfill = 1;
}
return module;
};
/***/ }),
/***/ "./src/common/index.js":
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
// EXPORTS
__webpack_require__.d(__webpack_exports__, "a", function() { return /* reexport */ INT_SIZE; });
__webpack_require__.d(__webpack_exports__, "d", function() { return /* reexport */ isWindows; });
__webpack_require__.d(__webpack_exports__, "b", function() { return /* reexport */ calculateHash; });
__webpack_require__.d(__webpack_exports__, "c", function() { return /* reexport */ fs_close; });
__webpack_require__.d(__webpack_exports__, "h", function() { return /* reexport */ fs_open; });
__webpack_require__.d(__webpack_exports__, "j", function() { return /* reexport */ readdir; });
__webpack_require__.d(__webpack_exports__, "i", function() { return /* reexport */ readFile; });
__webpack_require__.d(__webpack_exports__, "k", function() { return /* reexport */ stat; });
__webpack_require__.d(__webpack_exports__, "o", function() { return /* reexport */ write; });
__webpack_require__.d(__webpack_exports__, "p", function() { return /* reexport */ writeFile; });
__webpack_require__.d(__webpack_exports__, "f", function() { return /* reexport */ mkdir; });
__webpack_require__.d(__webpack_exports__, "e", function() { return /* reexport */ isWindowsPath; });
__webpack_require__.d(__webpack_exports__, "g", function() { return /* reexport */ nodePathToString; });
__webpack_require__.d(__webpack_exports__, "n", function() { return /* reexport */ unixifyPath; });
__webpack_require__.d(__webpack_exports__, "m", function() { return /* reexport */ stripBOM; });
__webpack_require__.d(__webpack_exports__, "l", function() { return /* reexport */ strToEncoding; });
// UNUSED EXPORTS: lstat, read, copyFile, exists, isDirectory, isFile
// CONCATENATED MODULE: ./src/common/constants.js
// integers on node are 6B Big-Endian
const INT_SIZE = 6;
const isWindows = process.platform === 'win32';
// EXTERNAL MODULE: external "crypto"
var external_crypto_ = __webpack_require__("crypto");
// CONCATENATED MODULE: ./src/common/crypto.js
function calculateHash(content) {
return Object(external_crypto_["createHash"])('sha256')
.update(JSON.stringify(content))
.digest('base64');
}
// EXTERNAL MODULE: external "fs"
var external_fs_ = __webpack_require__("fs");
// EXTERNAL MODULE: external "path"
var external_path_ = __webpack_require__("path");
// EXTERNAL MODULE: external "util"
var external_util_ = __webpack_require__("util");
// CONCATENATED MODULE: ./src/common/fs.js
// promisified functions from the real fs
async function fs_close(fd) {
return Object(external_util_["promisify"])(external_fs_["close"])(fd);
}
async function lstat(path) {
return Object(external_util_["promisify"])(external_fs_["lstat"])(path);
}
async function fs_open(path, flags, mode) {
return Object(external_util_["promisify"])(external_fs_["open"])(path, flags, mode);
}
async function read(fd, buffer, offset, length, position) {
return Object(external_util_["promisify"])(external_fs_["read"])(fd, buffer, offset, length, position || null);
}
async function readdir(path) {
return Object(external_util_["promisify"])(external_fs_["readdir"])(path);
}
async function readFile(path, options) {
return Object(external_util_["promisify"])(external_fs_["readFile"])(path, options);
}
async function stat(path) {
return Object(external_util_["promisify"])(external_fs_["stat"])(path);
}
async function write(fd, buffer, offset, length, position) {
return Object(external_util_["promisify"])(external_fs_["write"])(fd, buffer, offset || 0, length || buffer.length, position || undefined);
}
async function writeFile(filename, content) {
return Object(external_util_["promisify"])(external_fs_["writeFile"])(filename, content);
}
// custom functions
async function copyFile(source, target) {
await mkdir(Object(external_path_["dirname"])(target));
return await new Promise((resolve, reject) => {
const rd = external_fs_["createReadStream"](source);
rd.on('error', rejectCleanup);
const wr = external_fs_["createWriteStream"](target);
wr.on('error', rejectCleanup);
function rejectCleanup(err) {
rd.destroy();
wr.end();
reject(err);
}
wr.on('finish', () => {
rd.close();
wr.close();
resolve();
});
rd.pipe(wr);
});
}
async function exists(path) {
try {
await stat(path);
return true;
} catch {
/* no-op */
}
return false;
}
async function isDirectory(dirPath) {
try {
if (await exists(dirPath)) {
return (await lstat(dirPath)).isDirectory();
}
} catch {
/* no-op */
}
return false;
}
async function isFile(filePath) {
try {
if (await exists(filePath)) {
return !(await lstat(filePath)).isDirectory();
}
} catch {
/* no-op */
}
return false;
}
async function mkdir(dirPath) {
if (!(await isDirectory(dirPath))) {
const p = Object(external_path_["normalize"])(dirPath + '/');
const parent = Object(external_path_["dirname"])(dirPath);
if (!(await isDirectory(parent))) {
if (p !== parent) {
await mkdir(parent);
}
}
try {
await Object(external_util_["promisify"])(external_fs_["mkdir"])(p);
} catch (e) {
if (!(await isDirectory(p))) {
throw new Error(e);
}
}
}
}
// CONCATENATED MODULE: ./src/common/path.js
function getPathFromURLPosix(url) {
if (url.hostname !== '') {
throw new Error(`file URL host must be "localhost" or empty on ${process.platform}`);
}
const pathname = url.pathname;
for (let n = 0; n < pathname.length; n++) {
if (pathname[n] === '%') {
const third = pathname.codePointAt(n + 2) | 0x20;
if (pathname[n + 1] === '2' && third === 102) {
throw new Error(`file URL path must not include encoded / characters`);
}
}
}
return decodeURIComponent(pathname);
}
function isWindowsPath(filePath) {
if (!isWindows) return filePath;
if (filePath && filePath.length >= 3) {
if (filePath.charCodeAt(0) === 92 && filePath.charCodeAt(1) === 92) {
return true;
}
if (filePath.charCodeAt(1) === 58 && filePath.charCodeAt(2) === 92) {
const code = filePath.charCodeAt(0);
return (code >= 65 && code <= 90) || (code >= 97 && code <= 122);
}
}
return false;
}
function nodePathToString(path) {
if (typeof path !== 'string' && !Buffer.isBuffer(path)) {
if (!(path instanceof __webpack_require__("url").URL))
throw new Error(`The "path" argument must be one of type string, Buffer, or URL. Received type ${typeof path}`);
path = getPathFromURLPosix(path);
}
// in case it is a buffer convert
const pathString = String(path);
// null check
if (('' + pathString).indexOf('\u0000') !== -1) {
const er = new Error('path must be a string without null bytes');
er.code = 'ENOENT';
throw er;
}
return pathString;
}
function unixifyPath(filePath) {
if (!isWindows) return filePath;
if (filePath && typeof filePath === 'string') {
return (
filePath
// simplify drive letter
.replace(/^\\\\\?\\(.):\\/, '$1:\\')
// back slashes -> forward slashes with deduplicate
// eslint-disable-next-line no-useless-escape
.replace(/[\\\/]+/g, '/')
// remove drive letter
.replace(/^([a-zA-Z]+:|\.\/)/, '')
// remove trailing slash
.replace(/(.+?)\/$/, '$1')
);
}
return filePath;
}
// CONCATENATED MODULE: ./src/common/string.js
function stripBOM(content) {
return content && content.charCodeAt(0) === 0xfeff ? content.slice(1) : content;
}
function strToEncoding(str, encoding = 'utf8') {
if (encoding === 'utf8') return str;
if (encoding === 'buffer') return new Buffer(str);
return Buffer.from(str).toString(encoding);
}
// CONCATENATED MODULE: ./src/common/index.js
/***/ }),
/***/ "./src/filesystem/index.js":
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
// EXPORTS
__webpack_require__.d(__webpack_exports__, "a", function() { return /* reexport */ filesystem_StaticFilesystem; });
// UNUSED EXPORTS: ReadableStaticVolume, WritableStaticVolume
// EXTERNAL MODULE: external "fs"
var external_fs_ = __webpack_require__("fs");
// EXTERNAL MODULE: external "path"
var external_path_ = __webpack_require__("path");
// EXTERNAL MODULE: external "os"
var external_os_ = __webpack_require__("os");
// EXTERNAL MODULE: ./src/common/index.js + 5 modules
var common = __webpack_require__("./src/common/index.js");
// EXTERNAL MODULE: external "stream"
var external_stream_ = __webpack_require__("stream");
// EXTERNAL MODULE: external "util"
var external_util_ = __webpack_require__("util");
var external_util_default = /*#__PURE__*/__webpack_require__.n(external_util_);
// CONCATENATED MODULE: ./src/filesystem/streams/read.js
// NOTE: this is a raw re-implementation of https://github.com/nodejs/node/blob/v10.x/lib/internal/fs/streams.js#L45
// that is setup to follow our needs for the static filesystem
const ERR_STR_OPTS = (typeOfOptions) =>
`Expected options to be either an object or a string, but got ${typeOfOptions} instead`;
function assertEncoding(encoding) {
if (encoding && !Buffer.isEncoding(encoding)) throw new Error(`ERR_INVALID_OPT_VALUE_ENCODING ${encoding}`);
}
function getOptions(options, defaultOptions) {
if (options === null || options === undefined || typeof options === 'function') {
return defaultOptions;
}
if (typeof options === 'string') {
defaultOptions = external_util_default.a._extend({}, defaultOptions);
defaultOptions.encoding = options;
options = defaultOptions;
} else if (typeof options !== 'object') {
throw new ERR_STR_OPTS(typeof options);
}
if (options.encoding !== 'buffer') assertEncoding(options.encoding);
return options;
}
function copyObject(source) {
const target = {};
for (const key in source) target[key] = source[key];
return target;
}
const kMinPoolSpace = 128;
let pool;
// It can happen that we expect to read a large chunk of data, and reserve
// a large chunk of the pool accordingly, but the read() call only filled
// a portion of it. If a concurrently executing read() then uses the same pool,
// the "reserved" portion cannot be used, so we allow it to be re-used as a
// new pool later.
const poolFragments = [];
function allocNewPool(poolSize) {
if (poolFragments.length > 0) pool = poolFragments.pop();
else pool = Buffer.allocUnsafe(poolSize);
pool.used = 0;
}
function ReadStream(sfs, path, options) {
if (!(this instanceof ReadStream)) return new ReadStream(sfs, path, options);
this._sfs = sfs;
// a little bit bigger buffer and water marks by default
options = copyObject(getOptions(options, {}));
if (options.highWaterMark === undefined) options.highWaterMark = 64 * 1024;
// for backwards compat do not emit close on destroy.
options.emitClose = false;
external_stream_["Readable"].call(this, options);
this.path = path;
this.fd = options.fd === undefined ? null : options.fd;
this.flags = options.flags === undefined ? 'r' : options.flags;
this.mode = options.mode === undefined ? 0o666 : options.mode;
this.start = options.start;
this.end = options.end;
this.autoClose = options.autoClose === undefined ? true : options.autoClose;
this.pos = undefined;
this.bytesRead = 0;
this.closed = false;
if (this.start !== undefined) {
if (typeof this.start !== 'number' || Number.isNaN(this.start)) {
throw new TypeError('"start" option must be a Number');
}
if (this.end === undefined) {
this.end = Infinity;
} else if (typeof this.end !== 'number' || Number.isNaN(this.end)) {
throw new TypeError('"end" option must be a Number');
}
if (this.start > this.end) {
throw new Error('"start" option must be <= "end" option');
}
this.pos = this.start;
}
if (typeof this.end !== 'number') this.end = Infinity;
else if (Number.isNaN(this.end)) throw new TypeError('"end" option must be a Number');
if (!this.fd || !this.fd.type || this.fd.type !== 'static_fs_file_descriptor') this.open();
this.on('end', function() {
if (this.autoClose) {
this.destroy();
}
});
}
external_util_default.a.inherits(ReadStream, external_stream_["Readable"]);
ReadStream.prototype.open = function() {
this._sfs.open(this.path, (er, fd) => {
if (er) {
if (this.autoClose) {
this.destroy();
}
this.emit('error', er);
return;
}
this.fd = fd;
this.emit('open', fd);
this.emit('ready');
// start the flow of data.
this.read();
});
};
ReadStream.prototype._read = function(n) {
if (!this.fd || !this.fd.type || this.fd.type !== 'static_fs_file_descriptor') {
return this.once('open', function() {
this._read(n);
});
}
if (this.destroyed) return;
if (!pool || pool.length - pool.used < kMinPoolSpace) {
// discard the old pool.
allocNewPool(this.readableHighWaterMark);
}
// Grab another reference to the pool in the case that while we're
// in the thread pool another read() finishes up the pool, and
// allocates a new one.
const thisPool = pool;
let toRead = Math.min(pool.length - pool.used, n);
const start = pool.used;
if (this.pos !== undefined) toRead = Math.min(this.end - this.pos + 1, toRead);
else toRead = Math.min(this.end - this.bytesRead + 1, toRead);
// already read everything we were supposed to read!
// treat as EOF.
if (toRead <= 0) return this.push(null);
// the actual read.
this._sfs.read(this.fd, pool, pool.used, toRead, this.pos, (er, bytesRead) => {
if (er) {
if (this.autoClose) {
this.destroy();
}
this.emit('error', er);
} else {
let b = null;
// Now that we know how much data we have actually read, re-wind the
// 'used' field if we can, and otherwise allow the remainder of our
// reservation to be used as a new pool later.
if (start + toRead === thisPool.used && thisPool === pool) thisPool.used += bytesRead - toRead;
else if (toRead - bytesRead > kMinPoolSpace)
poolFragments.push(thisPool.slice(start + bytesRead, start + toRead));
if (bytesRead > 0) {
this.bytesRead += bytesRead;
b = thisPool.slice(start, start + bytesRead);
}
this.push(b);
}
});
// move the pool positions, and internal position for reading.
if (this.pos !== undefined) this.pos += toRead;
pool.used += toRead;
};
ReadStream.prototype._destroy = function(err, cb) {
if (!this.fd || !this.fd.type || this.fd.type !== 'static_fs_file_descriptor') {
this.once('open', closeFsStream.bind(null, this, cb, err));
return;
}
closeFsStream(this, cb, err);
this.fd = null;
};
function closeFsStream(stream, cb, err) {
stream._sfs.close(stream.fd, (er) => {
er = er || err;
cb(er);
stream.closed = true;
if (!er) stream.emit('close');
});
}
ReadStream.prototype.close = function(cb) {
this.destroy(null, cb);
};
Object.defineProperty(ReadStream.prototype, 'pending', {
get() {
return this.fd === null;
},
configurable: true,
});
// CONCATENATED MODULE: ./src/filesystem/streams/index.js
// CONCATENATED MODULE: ./src/filesystem/volume/readable.js
class readable_ReadableStaticVolume {
constructor(sourcePath) {
this.sourcePath = sourcePath;
this.indexPath = Object(external_path_["resolve"])(Object(external_path_["dirname"])(this.sourcePath), 'static_fs_index.json');
this.moutingRoot = Object(external_path_["resolve"])(Object(external_path_["dirname"])(this.sourcePath), '../');
this.runtimePath = Object(external_path_["resolve"])(Object(external_path_["dirname"])(this.sourcePath), 'static_fs_runtime.js');
this.reset();
}
reset() {
this.directoriesIndex = {};
this.filesBeingRead = {};
this.filesIndex = {};
this.hash = '';
this.volumeFd = -1;
this.volumeStats = {};
}
load() {
// already load?
if (this.volumeFd >= 0) {
return;
}
// read the index
this.volumeFd = external_fs_["openSync"](this.sourcePath, 'r');
// read first int into int buffer
const intBuffer = Buffer.alloc(common["a" /* INT_SIZE */]);
// read hash
let hashSize = this.readInt(intBuffer);
let hashBuffer = Buffer.alloc(hashSize);
this.readBuffer(hashBuffer, hashSize);
this.hash = hashBuffer.toString('utf8', 0, hashSize);
const indexContent = JSON.parse(external_fs_["readFileSync"](this.indexPath, 'utf8'));
this.volumeStats = this.buildVolumeStatsFromJSON(indexContent.volumeStats);
this.directoriesIndex = indexContent.directoriesIndex;
this.filesIndex = indexContent.filesIndex;
// verify hash
const hashCheckIndex = new Set();
Object.keys(this.filesIndex).forEach((filePath) => hashCheckIndex.add(filePath));
const hashCheck = Object(common["b" /* calculateHash */])(Array.from(hashCheckIndex.values()).sort());
if (hashCheck !== this.hash) {
throw new Error(
`Something went wrong loading the volume ${this.sourcePath}. Check hash after loading is different from the one stored in the volume.`,
);
}
}
buildVolumeStatsFromJSON(jsonVolumeStats) {
return {
isDirectory: () => false,
isSymbolicLink: () => false,
isBlockDevice: () => false,
isCharacterDevice: () => false,
isFile: () => true,
isFIFO: () => false,
isSocket: () => false,
...jsonVolumeStats,
atime: new Date(jsonVolumeStats.atime),
mtime: new Date(jsonVolumeStats.mtime),
ctime: new Date(jsonVolumeStats.ctime),
birthtime: new Date(jsonVolumeStats.birthtime),
};
}
readBuffer(buffer, length) {
return external_fs_["readSync"](this.volumeFd, buffer, 0, length || buffer.length, null);
}
readInt(intBuffer) {
external_fs_["readSync"](this.volumeFd, intBuffer, 0, common["a" /* INT_SIZE */], null);
return intBuffer.readIntBE(0, 6);
}
shutdown() {
// In case fd is open close it
// to release the file
if (this.volumeFd > 0) {
external_fs_["closeSync"](this.volumeFd);
}
this.reset();
}
getFromFilesIndex(filePath) {
return this.filesIndex[this._resolveAndUnmountPath(filePath)];
}
getFromDirectoriesIndex(dirPath) {
return this.directoriesIndex[this._resolveAndUnmountPath(dirPath)];
}
getFromIndex(itemPath) {
const fileItem = this.getFromFilesIndex(itemPath);
const dirItem = this.getFromDirectoriesIndex(itemPath);
if (!fileItem && !dirItem) {
return null;
}
const item = fileItem
? Object.assign({}, fileItem, { isDirectory: () => false, isFile: () => true })
: { isDirectory: () => true, isFile: () => false };
return {
...this.volumeStats,
...item,
blocks: fileItem ? 1 : 0,
blksize: fileItem ? fileItem.size : this.volumeStats.blksize,
};
}
getStatsFromFilepath(filePath, bigInt = false) {
const baseStats = this.getFromIndex(filePath);
if (!bigInt) {
return baseStats;
}
const getBigInt = (num) => {
if (typeof BigInt !== 'function') {
throw new Error('BigInt is not supported.');
}
return BigInt(num);
};
const bigIntStats = [
'size',
'dev',
'mode',
'nlink',
'uid',
'gid',
'rdev',
'blksize',
'ino',
'blocks',
'atimeMs',
'mtimeMs',
'ctimeMs',
'birthtimeMs',
].reduce((newStats, statVal) => {
if (Object.prototype.hasOwnProperty.call(baseStats, statVal)) {
newStats[statVal] = getBigInt(baseStats[statVal]);
}
return newStats;
}, {});
return {
...baseStats,
...bigIntStats(),
};
}
getRealpath(filePath, encoding = 'utf8') {
if (!this.getFromIndex(filePath)) {
return undefined;
}
return Object(common["l" /* strToEncoding */])(filePath, encoding);
}
getDirInfo(dirPath, encoding = 'utf8', withFileTypes = false) {
const dirIdxData = this.getFromDirectoriesIndex(dirPath);
if (!dirIdxData) {
return undefined;
}
return dirIdxData.sort().map((dirInfoElem) => {
const encodedDirInfoElem = Object(common["l" /* strToEncoding */])(dirInfoElem, encoding);
if (!withFileTypes) {
return encodedDirInfoElem;
}
const isElemDir = !!this.getFromDirectoriesIndex(dirInfoElem);
return {
name: encodedDirInfoElem,
isDirectory: () => isElemDir,
isFile: () => !isElemDir,
isBlockDevice: () => false,
isCharacterDevice: () => false,
isSymbolicLink: () => false,
isFIFO: () => false,
isSocket: () => false,
};
});
}
_resolveAndUnmountPath(mountedPath) {
if (!mountedPath.includes(this.moutingRoot)) {
return mountedPath;
}
// mountRoot path + slash
return Object(common["n" /* unixifyPath */])(mountedPath).slice(Object(common["n" /* unixifyPath */])(this.moutingRoot).length + 1);
}
readFileSync(filePath, options) {
const item = this.getFromIndex(filePath);
if (!item || !item.isFile()) {
return undefined;
}
const encoding = options
? typeof options === 'string'
? options
: typeof options === 'object'
? options.encoding
: null
: null;
const buf = Buffer.alloc(item.size);
// read the content into the created buffer
external_fs_["readSync"](this.volumeFd, buf, 0, item.size, item.ino);
if (!encoding) {
return buf;
}
return buf.toString(encoding, 0, item.size);
}
_deleteReadFileFromCache(filePath) {
if (this.filesBeingRead[filePath]) {
delete this.filesBeingRead[filePath];
}
}
_readFromCache(filePath, buffer, offset, length, position) {
const cachedBuffer = this.filesBeingRead[filePath];
if (position >= cachedBuffer.length) {
this._deleteReadFileFromCache(filePath);
return 0;
}
const copiedBytes = cachedBuffer.copy(buffer, offset, position, Math.min(position + length, cachedBuffer.length));
if (copiedBytes + position === cachedBuffer.length) {
this._deleteReadFileFromCache(filePath);
}
return copiedBytes;
}
readSync(filePath, buffer, offset, length, position) {
const item = this.getFromIndex(filePath);
if (!item || !item.isFile()) {
return undefined;
}
if (position >= item.size) {
// it is not possible to read
// more than the file size
return 0;
}
if (this.filesBeingRead[filePath]) {
return this._readFromCache(filePath, buffer, offset, length, position);
} else {
const cachedFileBuffer = (this.filesBeingRead[filePath] = Buffer.alloc(item.size));
external_fs_["readSync"](this.volumeFd, cachedFileBuffer, 0, item.size, item.ino);
return this._readFromCache(filePath, buffer, offset, length, position);
}
}
}
// CONCATENATED MODULE: ./src/filesystem/volume/writable.js
class writable_WritableStaticVolume {
constructor(mountingRoot) {
this.mountingRoot = mountingRoot;
this.indexFile = Object(external_path_["resolve"])(this.mountingRoot, '.static_fs/static_fs_index.json');
this.manifestFile = Object(external_path_["resolve"])(this.mountingRoot, '.static_fs/static_fs_manifest.json');
this.outputFile = Object(external_path_["resolve"])(this.mountingRoot, '.static_fs/static_fs_volume.sfsv');
this.reset();
}
reset() {
this.directoriesIndex = {};
this.hash = '';
this.hashBuffer = Buffer.allocUnsafe(0);
this.filesIndex = {};
this.intBuffer = Buffer.alloc(common["a" /* INT_SIZE */]);
}
async addFolder(sourceFolder, exclusions) {
if (this.mountingRoot === sourceFolder) {
throw new Error('You cannot add the mounting root of the project to the static filesystem');
}
if (!sourceFolder.includes(this.mountingRoot)) {
throw new Error(
`All the files to include into the static filesystem should has mountingRoot has parent: ${this.mountingRoot}`,
);
}
const calculatedTargetFolder = Object(external_path_["relative"])(this.mountingRoot, sourceFolder);
const isFolder = (await Object(common["k" /* stat */])(sourceFolder)).isDirectory();
if (!isFolder) {
throw new Error(`The given path ${sourceFolder} is not a folder.`);
}
// mark folder without native modules by default
// it would be updated in the next walk update function
this.directoriesIndex[calculatedTargetFolder] = {
hasNativeModules: false,
content: new Set(),
};
await this.getFileNames(sourceFolder, calculatedTargetFolder, exclusions);
}
getHeaderLength() {
this.hashBuffer = Buffer.from(this.hash, 'utf-8');
let size = common["a" /* INT_SIZE */]; // hashSize integer
size += this.hashBuffer.byteLength; // hashString
size += common["a" /* INT_SIZE */]; // end of header with a control zero
return size;
}
writeInt(fd, value, position) {
this.intBuffer.writeIntBE(value, 0, 6);
return Object(common["o" /* write */])(fd, this.intBuffer, 0, common["a" /* INT_SIZE */], position);
}
async write() {
await Object(common["f" /* mkdir */])(Object(external_path_["dirname"])(this.outputFile));
this.hash = Object(common["b" /* calculateHash */])(
Object.keys(this.filesIndex)
.map((filePath) => Object(common["n" /* unixifyPath */])(filePath))
.sort(),
);
// write the main volume
await this.writeVolume();
// write the index file
await this.writeIndex();
// write the manifest file
await this.writeManifest();
return this.hash;
}
async writeIndex() {
const directoriesIndex = Object.keys(this.directoriesIndex).reduce((dirsIdx, dirPath) => {
const unixifiedDirPath = Object(common["n" /* unixifyPath */])(dirPath);
dirsIdx[unixifiedDirPath] = Array.from(this.directoriesIndex[dirPath].content.values());
return dirsIdx;
}, {});
let totalDataSize = this.getHeaderLength();
const filesIndex = Object.keys(this.filesIndex).reduce((filesIdx, filePath) => {
const unixifiedFilePath = Object(common["n" /* unixifyPath */])(filePath);
filesIdx[unixifiedFilePath] = {
ino: totalDataSize,
size: this.filesIndex[filePath].size,
};
totalDataSize += this.filesIndex[filePath].size;
return filesIdx;
}, {});
const volumeStats = await Object(common["k" /* stat */])(this.outputFile);
await Object(common["p" /* writeFile */])(
this.indexFile,
JSON.stringify({
directoriesIndex,
filesIndex,
volumeStats,
}),
);
}
async writeVolume() {
const volumeFd = await Object(common["h" /* open */])(this.outputFile, 'w');
let dataOffset = this.getHeaderLength();
let headerPosition = await this.writeInt(volumeFd, this.hashBuffer.byteLength);
await Object(common["o" /* write */])(volumeFd, this.hashBuffer, 0, this.hashBuffer.byteLength, headerPosition);
const filePaths = Object.keys(this.filesIndex);
// write the data
for (const each of filePaths) {
const entry = this.filesIndex[each];
const position = dataOffset;
dataOffset += entry.size;
const buf = await this.filesIndex[each].getBuffer();
await Object(common["o" /* write */])(volumeFd, buf, 0, buf.length, position);
}
await Object(common["c" /* close */])(volumeFd);
}
async writeManifest() {
const baseManifestFileDir = Object(external_path_["dirname"])(this.manifestFile);
// gather useful info
const manifestContent = {
manifest: Object(external_path_["basename"])(this.manifestFile),
mountingRoot: Object(external_path_["relative"])(baseManifestFileDir, this.mountingRoot),
hash: this.hash,
volume: Object(external_path_["relative"])(baseManifestFileDir, this.outputFile),
directories: Object.keys(this.directoriesIndex).sort(),
files: Object.keys(this.filesIndex).sort(),
};
await Object(common["p" /* writeFile */])(this.manifestFile, JSON.stringify(manifestContent, null, 2));
}
async getFileNames(sourceFolder, targetFolder, exclusions) {
const files = await Object(common["j" /* readdir */])(sourceFolder);
const all = [];
for (const file of files) {
// compute the path names
const sourcePath = `${sourceFolder}${external_path_["sep"]}${file}`;
const targetPath = `${targetFolder}${external_path_["sep"]}${file}`;
// is declared exclusion?
const foundExclusion = exclusions[sourceFolder] || exclusions[sourcePath];
if (foundExclusion) {
continue;
}
// is this a directory
const ss = await Object(common["k" /* stat */])(sourcePath);
if (ss.isDirectory()) {
this.directoriesIndex[targetPath] = {
hasNativeModules: false,
content: new Set(),
};
all.push(this.getFileNames(sourcePath, targetPath, exclusions));
continue;
}
// add native module metadata to folders index
const isNativeModuleFile = file.endsWith('.node');
if (isNativeModuleFile) {
this.directoriesIndex[targetFolder].hasNativeModules = true;
continue;
}
// adds file to the content
this.directoriesIndex[targetFolder].content.add(file);
// it's a file. capture the details.
this.filesIndex[targetPath] = {
size: ss.size,
getBuffer: () => Object(common["i" /* readFile */])(sourcePath),
};
}
// wait for children to finish
await Promise.all(all);
}
getAddedFilesAndFolders() {
// Recursive help function to build the parent
// hierarchy for a given folder
const addParentsForFolder = (folderPath, accum) => {
const calculatedParent = Object(external_path_["dirname"])(Object(external_path_["resolve"])(this.mountingRoot, folderPath));
const parent = Object(external_path_["dirname"])(folderPath);
if (calculatedParent && calculatedParent !== this.mountingRoot && calculatedParent.includes(this.mountingRoot)) {
accum[parent] = true;
return addParentsForFolder(parent, accum);
}
};
// Get the base folders with native modules files on it
// and build the complete path with parents
const foldersWithNativeModulesIndex = Object.keys(this.directoriesIndex).reduce((accum, folderPath) => {
if (this.directoriesIndex[folderPath].hasNativeModules && !accum[folderPath]) {
accum[folderPath] = true;
addParentsForFolder(folderPath, accum);
}
return accum;
}, {});
// To the entire list of added folders
// remove the entire hierarchy for the ones
// with native modules
const addedFolders = Object.keys(this.directoriesIndex)
.filter((folderPath) => !foldersWithNativeModulesIndex[folderPath])
.map((folderPath) => Object(external_path_["resolve"])(this.mountingRoot, folderPath));
const addedFiles = Object.keys(this.filesIndex).map((filePath) => Object(external_path_["resolve"])(this.mountingRoot, filePath));
// Finally return the curated list of filtered folders
// and added files
return addedFiles.concat(addedFolders).sort((a, b) => b.localeCompare(a));
}
}
// CONCATENATED MODULE: ./src/filesystem/volume/index.js
// CONCATENATED MODULE: ./src/filesystem/filesystem.js
class filesystem_StaticFilesystem {
static NewError(code, method, data) {
switch (code) {
case external_os_["constants"].errno.ENOENT:
return {
...new Error(`ENOENT: no such file or directory, ${method} '${data}'`),
code: 'ENOENT',
path: data,
errno: external_os_["constants"].errno.ENOENT,
};
case external_os_["constants"].errno.EISDIR:
return {
...new Error(`EISDIR: illegal operation on a directory, ${method} '${data}'`),
code: 'EISDIR',
path: data,
errno: external_os_["constants"].errno.EISDIR,
};
case external_os_["constants"].errno.EBADF:
return {
...new Error(`EBADF: bad file number, ${method} ${data}`),
code: 'EBADF',
info: data,
errno: external_os_["constants"].errno.EBADF,
};
case external_os_["constants"].errno.ENOTDIR:
return {
...new Error(`ENOTDIR: not a directory, ${method} '${data}'`),
code: 'ENOTDIR',
path: data,
errno: external_os_["constants"].errno.ENOTDIR,
};
case external_os_["constants"].errno.EROFS:
return {
...new Error(`EROFS: Static-Fs is a read-only filesystem, ${method} '${data}'`),
code: 'EROFS',
path: data,
errno: external_os_["constants"].errno.EROFS,
};
case external_os_["constants"].errno.EACCES:
return {
...new Error(`EACCES: permission denied, ${method} '${data}'`),
code: 'EACCES',
path: data,
errno: external_os_["constants"].errno.EACCES,
};
}
return {
...new Error(`UNKNOWN_ERROR: Something unexpected happened , ${method} ${data}`),
code: 'UNKNOWN_ERROR',
info: data,
errno: -9999,
};
}
constructor() {
this.fds = {};
this.volumes = {};
}
shutdown() {
for (const volume of Object.values(this.volumes)) {
volume.shutdown();
}
}
load(sourcePath) {
sourcePath = Object(external_path_["resolve"])(sourcePath);
if (this.volumes[sourcePath]) {
// already loaded?
return this;
}
const volume = new readable_ReadableStaticVolume(sourcePath);
volume.load();
this.volumes[volume.sourcePath] = volume;
return this;
}
get loadedVolumes() {
return Object.keys(this.volumes);
}
areFlagsValid(flags) {
return !(flags && flags !== 'r');
}
isValidFD(fd) {
const isCorrectFormat = fd && fd.id && fd.type && fd.type === 'static_fs_file_descriptor';
const isPresent = this.fds[fd.id];
return isCorrectFormat && isPresent;
}
getValidatedFD(fd) {
if (!this.isValidFD(fd)) {
throw filesystem_StaticFilesystem.NewError(external_os_["constants"].errno.EBADF, 'getValidatedFD', fd);
}
return this.fds[fd.id];
}
getVolumeForPath(itemPath) {
const volKeys = Object.keys(this.volumes);
for (let i = 0; i < volKeys.length; i++) {
const vol = this.volumes[volKeys[i]];
const exists = vol.getRealpath(itemPath);
if (exists) {
return volKeys[i];
}
}
return undefined;
}
volumeForFilepathSync(itemPath) {
const volumePathForFilePath = this.getVolumeForPath(itemPath);
if (!volumePathForFilePath) {
return undefined;
}
const volumeForFilePath = this.volumes[volumePathForFilePath];
if (!volumeForFilePath) {
return undefined;
}
return volumeForFilePath;
}
wrapAsync(method, args, callback) {
if (typeof callback !== 'function') throw new Error('Callback is not a function');
process.nextTick(() => {
try {
callback(null, method.apply(this, args));
} catch (err) {
err.message = err.message.replace(/Sync/gi, '');
callback(err);
}
});
}
accessSync(path, mode) {
const filePath = Object(common["g" /* nodePathToString */])(path);
const volume = this.volumeForFilepathSync(filePath);
if (!volume) {
throw filesystem_StaticFilesystem.NewError(external_os_["constants"].errno.ENOENT, 'accessSync', filePath);
}
if (mode !== external_fs_["constants"].F_OK && mode !== external_fs_["constants"].R_OK && mode !== external_fs_["constants"].X_OK) {
throw filesystem_StaticFilesystem.NewError(external_os_["constants"].errno.EACCES, 'accessSync', filePath);
}
}
access(path, mode, callback) {
this.wrapAsync(this.accessSync, [path, mode], callback);
}
readFileSync(path, options) {
const isFd = this.isValidFD(path);
const filePath = isFd ? path.filePath : Object(common["g" /* nodePathToString */])(path);
const volume = this.volumeForFilepathSync(filePath);
if (!volume) {
throw filesystem_StaticFilesystem.NewError(external_os_["constants"].errno.ENOENT, 'readFileSync', filePath);
}
return volume.readFileSync(filePath, options);
}
readFile(path, options, callback) {
this.wrapAsync(this.readFileSync, [path, options], callback);
}
readSync(fd, buffer, offset, length, position) {
try {
this.getValidatedFD(fd);
} catch (e) {
throw filesystem_StaticFilesystem.NewError(external_os_["constants"].errno.ENOENT, 'readSync', e);
}
const filePath = fd.filePath;
const volume = this.volumeForFilepathSync(filePath);
if (!volume) {
throw filesystem_StaticFilesystem.NewError(external_os_["constants"].errno.ENOENT, 'readSync', fd);
}
return volume.readSync(filePath, buffer, offset, length, position);
}
read(fd, buffer, offset, length, position, callback) {
// copied from Node implementation
if (length === 0) {
return process.nextTick(() => {
if (callback) callback(null, 0, buffer);
});
}
process.nextTick(() => {
try {
const readBytes = this.readSync(fd, buffer, offset, length, position);
callback(null, readBytes, buffer);
} catch (err) {
err.message = err.message.replace(/Sync/gi, '');
callback(err);
}
});
}
realpathSync(path, options) {
const filePath = Object(common["g" /* nodePathToString */])(path);
const encoding = options && options.encoding;
const volume = this.volumeForFilepathSync(filePath);
if (!volume) {
throw filesystem_StaticFilesystem.NewError(external_os_["constants"].errno.ENOENT, 'realpathSync', filePath);
}
return volume.getRealpath(filePath, encoding);
}
realpath(path, options, callback) {
this.wrapAsync(this.realpathSync, [path, options], callback);
}
statSync(path, options) {
const filePath = Object(common["g" /* nodePathToString */])(path);
const bigInt = options && options.bigint;
const volume = this.volumeForFilepathSync(filePath);
if (!volume) {
throw filesystem_StaticFilesystem.NewError(external_os_["constants"].errno.ENOENT, 'statSync', filePath);
}
return volume.getStatsFromFilepath(filePath, bigInt);
}
stat(path, options, callback) {
this.wrapAsync(this.statSync, [path, options], callback);
}
readdirSync(path, options) {
const dirPath = Object(common["g" /* nodePathToString */])(path);
const encoding = options && options.encoding;
const withFileTypes = options && options.withFileTypes;
const volume = this.volumeForFilepathSync(dirPath);
if (!volume) {
throw filesystem_StaticFilesystem.NewError(external_os_["constants"].errno.ENOENT, 'readdirSync', dirPath);
}
const dirInfo = volume.getDirInfo(dirPath, encoding, withFileTypes);
if (!dirInfo) {
throw filesystem_StaticFilesystem.NewError(external_os_["constants"].errno.ENOTDIR, 'readdirSync', dirPath);
}
return dirInfo;
}
readdir(path, options, callback) {
this.wrapAsync(this.readdirSync, [path, options], callback);
}
openSync(path, flags) {
const filePath = Object(common["g" /* nodePathToString */])(path);
const volume = this.volumeForFilepathSync(filePath);
if (!volume) {
throw filesystem_StaticFilesystem.NewError(external_os_["constants"].errno.ENOENT, 'openSync', filePath);
}
if (!this.areFlagsValid(flags)) {
throw filesystem_StaticFilesystem.NewError(external_os_["constants"].errno.EROFS, 'openSync', filePath);
}
const fdIdentifier = `${volume.sourcePath}#${Object(common["n" /* unixifyPath */])(filePath)}`;
this.fds[fdIdentifier] = {
type: 'static_fs_file_descriptor',
id: fdIdentifier,
volumeSourcePath: volume.sourcePath,
filePath: filePath,
};
return this.fds[fdIdentifier];
}
open(path, flags, callback) {
this.wrapAsync(this.openSync, [path, flags], callback);
}
closeSync(fd) {
try {
this.getValidatedFD(fd);
} catch (e) {
throw filesystem_StaticFilesystem.NewError(external_os_["constants"].errno.ENOENT, 'closeSync', e);
}
delete this.fds[fd.id];
}
close(fd, callback) {
this.wrapAsync(this.closeSync, [fd], callback);
}
fstatSync(fd, options) {
try {
this.getValidatedFD(fd);
} catch (e) {
throw filesystem_StaticFilesystem.NewError(external_os_["constants"].errno.ENOENT, 'fstatSync', e);
}
return this.statSync(fd.filePath, options);
}
fstat(fd, options, callback) {
this.wrapAsync(this.fstatSync, [fd, options], callback);
}
createReadStream(path, options) {
const optionsFlags = options && options.flags;
const optionsFd = options && options.fd;
const filePath = optionsFd ? path : Object(common["g" /* nodePathToString */])(path);
if (!this.areFlagsValid(optionsFlags)) {
throw filesystem_StaticFilesystem.NewError(external_os_["constants"].errno.EROFS, 'createReadStream', filePath);
}
return new ReadStream(this, filePath, options);
}
}
// CONCATENATED MODULE: ./src/filesystem/index.js
/***/ }),
/***/ "./src/runtime/index.js":
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _runtime__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/runtime/runtime.js");
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "load", function() { return _runtime__WEBPACK_IMPORTED_MODULE_0__["a"]; });
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "shutdown", function() { return _runtime__WEBPACK_IMPORTED_MODULE_0__["b"]; });
/* harmony import */ var _patch__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__("./src/runtime/patch/index.js");
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "patchChildProcess", function() { return _patch__WEBPACK_IMPORTED_MODULE_1__["a"]; });
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "patchFilesystem", function() { return _patch__WEBPACK_IMPORTED_MODULE_1__["b"]; });
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "patchModuleLoader", function() { return _patch__WEBPACK_IMPORTED_MODULE_1__["c"]; });
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "patchProcess", function() { return _patch__WEBPACK_IMPORTED_MODULE_1__["d"]; });
/***/ }),
/***/ "./src/runtime/patch/index.js":
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
// EXPORTS
__webpack_require__.d(__webpack_exports__, "a", function() { return /* reexport */ patchChildProcess; });
__webpack_require__.d(__webpack_exports__, "b", function() { return /* reexport */ patchFilesystem; });
__webpack_require__.d(__webpack_exports__, "c", function() { return /* reexport */ patchModuleLoader; });
__webpack_require__.d(__webpack_exports__, "d", function() { return /* reexport */ patchProcess; });
// EXTERNAL MODULE: external "child_process"
var external_child_process_ = __webpack_require__("child_process");
// EXTERNAL MODULE: external "fs"
var external_fs_ = __webpack_require__("fs");
// EXTERNAL MODULE: external "util"
var external_util_ = __webpack_require__("util");
// CONCATENATED MODULE: ./src/runtime/patch/child_process.js
const nodeBinPossibilities = ['node', 'node.exe', process.execPath, process.argv[0]];
// assureCwdOnSanitizedOptions is needed as it is reasonable
// to declare cwd in the child_process function's options
// for resources bundled inside the static-fs volumes
// and in that case will not exist on the real filesystem.
// As nodejs uses uv_spawn to launch the child_process ultimately,
// that library would throw an error if the cwd does not exist
function assureCwdOnSanitizedOptions(options, sfs, existsOnRealFs) {
const cwd = options && options.cwd;
if (!cwd) {
return;
}
try {
const cwdStat = sfs.statSync(cwd);
if (!!cwdStat && cwdStat.isDirectory() && !existsOnRealFs(cwd)) {
external_fs_["mkdirSync"](cwd, { recursive: true });
}
} catch {
/* no-op */
}
}
// Build the args order for the child_process functions
// The order is always the same:
// 1 - first add the arguments starting with -- or -
// 2 - add the main static fs runtime path
// 3 - add the static-fs-volumes flag
// 4 - add the main entry
// 5 - add the rest of the args
//
// However for fork the order is different as fork don't accept
// node args starting by -- or - unless we set the options.execArgs
// So for fork (when mainStaticFsRuntimePath === null) the order is
//
// 1 - add the static-fs-volumes flag
// 2 - add the main entry
// 3 - add the rest of the args
function buildStaticFsArgs(args, mainStaticFsRuntimePath, staticFsVolumesPaths, mainEntry = null) {
const sanitizedArgs = Array.isArray(args) ? args : typeof args === 'string' ? args.split(' ') : [];
const builtArgs = [];
let toAddMetaArgs = true;
const iterableArgs = [...sanitizedArgs];
do {
const wasArrayEmptyBeforeArg = iterableArgs.length === 0;
const arg = iterableArgs.shift();
if (!arg && !wasArrayEmptyBeforeArg) {
return;
}
if ((typeof arg === 'string' && arg.startsWith('-') && mainStaticFsRuntimePath) ||