"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); let originalFsMethods = null; let lazyRestoreFs = () => { }; // optional Win32 file namespace prefix followed by drive letter and colon const windowsFullPathRegex = /^(\\{2}\?\\)?([a-zA-Z]):/; const upcaseDriveLetter = (s) => s.replace(windowsFullPathRegex, (_match, ns, drive) => `${ns || ''}${drive.toUpperCase()}:`); function shimFs(binary, fs = require('fs')) { if (originalFsMethods !== null) { return; } originalFsMethods = Object.assign({}, fs); const { blobPath, resources: manifest } = binary, { resourceStart, stat } = binary.layout, directories = {}, notAFile = '!@#$%^&*', isWin = process.platform.startsWith('win'), isString = (x) => typeof x === 'string' || x instanceof String, noop = () => { }, path = require('path'), winPath = isWin ? upcaseDriveLetter : (s) => s, baseDir = winPath(path.dirname(process.execPath)); let log = (_) => true; let loggedManifest = false; if ((process.env.DEBUG || '').toLowerCase().includes('nexe:require')) { log = (text) => { setupManifest(); if (!loggedManifest) { process.stderr.write('[nexe] - MANIFEST' + JSON.stringify(manifest, null, 4) + '\n'); process.stderr.write('[nexe] - DIRECTORIES' + JSON.stringify(directories, null, 4) + '\n'); loggedManifest = true; } return process.stderr.write('[nexe] - ' + text + '\n'); }; } const getKey = function getKey(filepath) { if (Buffer.isBuffer(filepath)) { filepath = filepath.toString(); } if (!isString(filepath)) { return notAFile; } let key = path.resolve(baseDir, filepath); return winPath(key); }; const statTime = function () { return { dev: 0, ino: 0, nlink: 0, rdev: 0, uid: 123, gid: 500, blksize: 4096, blocks: 0, atime: new Date(stat.atime), atimeMs: stat.atime.getTime(), mtime: new Date(stat.mtime), mtimeMs: stat.mtime.getTime(), ctime: new Date(stat.ctime), ctimMs: stat.ctime.getTime(), birthtime: new Date(stat.birthtime), birthtimeMs: stat.birthtime.getTime(), }; }; let BigInt; try { BigInt = eval('BigInt'); } catch (ignored) { } const createStat = function (extensions, options) { const stat = Object.assign(new fs.Stats(), binary.layout.stat, statTime(), extensions); if (options && options.bigint && BigInt) { for (const k in stat) { if (Object.prototype.hasOwnProperty.call(stat, k) && typeof stat[k] === 'number') { stat[k] = BigInt(stat[k]); } } } return stat; }; const ownStat = function (filepath, options) { setupManifest(); const key = getKey(filepath); if (directories[key]) { let mode = binary.layout.stat.mode; mode |= fs.constants.S_IFDIR; mode &= ~fs.constants.S_IFREG; return createStat({ mode, size: 0 }, options); } if (manifest[key]) { return createStat({ size: manifest[key][1] }, options); } }; const getStat = function (fn) { return function stat(filepath, options, callback) { let stat; if (typeof options === 'function') { callback = options; stat = ownStat(filepath, null); } else { stat = ownStat(filepath, options); } if (stat) { process.nextTick(() => { callback(null, stat); }); } else { return originalFsMethods[fn].apply(fs, arguments); } }; }; function makeLong(filepath) { return path._makeLong && path._makeLong(filepath); } function fileOpts(options) { return !options ? {} : isString(options) ? { encoding: options } : options; } let setupManifest = () => { Object.keys(manifest).forEach((filepath) => { const entry = manifest[filepath]; const absolutePath = getKey(filepath); const longPath = makeLong(absolutePath); const normalizedPath = winPath(path.normalize(filepath)); if (!manifest[absolutePath]) { manifest[absolutePath] = entry; } if (longPath && !manifest[longPath]) { manifest[longPath] = entry; } if (!manifest[normalizedPath]) { manifest[normalizedPath] = manifest[filepath]; } let currentDir = path.dirname(absolutePath); let prevDir = absolutePath; while (currentDir !== prevDir) { directories[currentDir] = directories[currentDir] || {}; directories[currentDir][path.basename(prevDir)] = true; const longDir = makeLong(currentDir); if (longDir && !directories[longDir]) { directories[longDir] = directories[currentDir]; } prevDir = currentDir; currentDir = path.dirname(currentDir); } }); manifest[notAFile] = false; directories[notAFile] = false; setupManifest = noop; }; //naive patches intended to work for most use cases const nfs = { existsSync: function existsSync(filepath) { setupManifest(); const key = getKey(filepath); if (manifest[key] || directories[key]) { return true; } return originalFsMethods.existsSync.apply(fs, arguments); }, realpath: function realpath(filepath, options, cb) { setupManifest(); const key = getKey(filepath); if (isString(filepath) && (manifest[filepath] || manifest[key])) { return process.nextTick(() => cb(null, filepath)); } return originalFsMethods.realpath.call(fs, filepath, options, cb); }, realpathSync: function realpathSync(filepath, options) { setupManifest(); const key = getKey(filepath); if (manifest[key]) { return filepath; } return originalFsMethods.realpathSync.call(fs, filepath, options); }, readdir: function readdir(filepath, options, callback) { setupManifest(); const dir = directories[getKey(filepath)]; if (dir) { if ('function' === typeof options) { callback = options; options = { encoding: 'utf8' }; } process.nextTick(() => callback(null, Object.keys(dir))); } else { return originalFsMethods.readdir.apply(fs, arguments); } }, readdirSync: function readdirSync(filepath, options) { setupManifest(); const dir = directories[getKey(filepath)]; if (dir) { return Object.keys(dir); } return originalFsMethods.readdirSync.apply(fs, arguments); }, readFile: function readFile(filepath, options, callback) { setupManifest(); const entry = manifest[getKey(filepath)]; if (!entry) { return originalFsMethods.readFile.apply(fs, arguments); } const [offset, length] = entry; const resourceOffset = resourceStart + offset; const encoding = fileOpts(options).encoding; callback = typeof options === 'function' ? options : callback; originalFsMethods.open(blobPath, 'r', function (err, fd) { if (err) return callback(err, null); originalFsMethods.read(fd, Buffer.alloc(length), 0, length, resourceOffset, function (error, bytesRead, result) { if (error) { return originalFsMethods.close(fd, function () { callback(error, null); }); } originalFsMethods.close(fd, function (err) { if (err) { return callback(err, result); } callback(err, encoding ? result.toString(encoding) : result); }); }); }); }, createReadStream: function createReadStream(filepath, options) { setupManifest(); const entry = manifest[getKey(filepath)]; if (!entry) { return originalFsMethods.createReadStream.apply(fs, arguments); } const [offset, length] = entry; const resourceOffset = resourceStart + offset; const opts = fileOpts(options); return originalFsMethods.createReadStream(blobPath, Object.assign({}, opts, { start: resourceOffset, end: resourceOffset + length - 1, })); }, readFileSync: function readFileSync(filepath, options) { setupManifest(); const entry = manifest[getKey(filepath)]; if (!entry) { return originalFsMethods.readFileSync.apply(fs, arguments); } const [offset, length] = entry; const resourceOffset = resourceStart + offset; const encoding = fileOpts(options).encoding; const fd = originalFsMethods.openSync(process.execPath, 'r'); const result = Buffer.alloc(length); originalFsMethods.readSync(fd, result, 0, length, resourceOffset); originalFsMethods.closeSync(fd); return encoding ? result.toString(encoding) : result; }, statSync: function statSync(filepath, options) { const stat = ownStat(filepath, options); if (stat) { return stat; } return originalFsMethods.statSync.apply(fs, arguments); }, stat: getStat('stat'), lstat: getStat('lstat'), lstatSync: function statSync(filepath, options) { const stat = ownStat(filepath, options); if (stat) { return stat; } return originalFsMethods.lstatSync.apply(fs, arguments); }, }; if (typeof fs.exists === 'function') { nfs.exists = function (filepath, cb) { cb = cb || noop; const exists = nfs.existsSync(filepath); process.nextTick(() => cb(exists)); }; } const patches = process.nexe.patches || {}; delete process.nexe; patches.internalModuleReadFile = function (original, ...args) { setupManifest(); const filepath = getKey(args[0]); if (manifest[filepath]) { log('read (hit) ' + filepath); return nfs.readFileSync(filepath, 'utf-8'); } log('read (miss) ' + filepath); return original.call(this, ...args); }; let returningArray; patches.internalModuleReadJSON = function (original, ...args) { if (returningArray == null) returningArray = Array.isArray(original.call(this, '')); const res = patches.internalModuleReadFile.call(this, original, ...args); return returningArray && !Array.isArray(res) ? [res, /"(main|name|type|exports|imports)"/.test(res)] : res; }; patches.internalModuleStat = function (original, ...args) { setupManifest(); const filepath = getKey(args[0]); if (manifest[filepath]) { log('stat (hit) ' + filepath + ' ' + 0); return 0; } if (directories[filepath]) { log('stat dir (hit) ' + filepath + ' ' + 1); return 1; } const res = original.call(this, ...args); if (res === 0) { log('stat (miss) ' + filepath + ' ' + res); } else if (res === 1) { log('stat dir (miss) ' + filepath + ' ' + res); } else { log('stat (fail) ' + filepath + ' ' + res); } return res; }; if (typeof fs.exists === 'function') { nfs.exists = function (filepath, cb) { cb = cb || noop; const exists = nfs.existsSync(filepath); if (!exists) { return originalFsMethods.exists(filepath, cb); } process.nextTick(() => cb(exists)); }; } if (typeof fs.copyFile === 'function') { nfs.copyFile = function (filepath, dest, flags, callback) { setupManifest(); const entry = manifest[getKey(filepath)]; if (!entry) { return originalFsMethods.copyFile.apply(fs, arguments); } if (typeof flags === 'function') { callback = flags; flags = 0; } nfs.readFile(filepath, (err, buffer) => { if (err) { return callback(err); } originalFsMethods.writeFile(dest, buffer, (err) => { if (err) { return callback(err); } callback(null); }); }); }; nfs.copyFileSync = function (filepath, dest) { setupManifest(); const entry = manifest[getKey(filepath)]; if (!entry) { return originalFsMethods.copyFileSync.apply(fs, arguments); } return originalFsMethods.writeFileSync(dest, nfs.readFileSync(filepath)); }; } Object.assign(fs, nfs); lazyRestoreFs = () => { Object.keys(nfs).forEach((key) => { fs[key] = originalFsMethods[key]; }); lazyRestoreFs = () => { }; }; return true; } exports.shimFs = shimFs; function restoreFs() { lazyRestoreFs(); } exports.restoreFs = restoreFs;