Skip to content

Instantly share code, notes, and snippets.

@Luiz-Monad
Created August 27, 2024 04:56
Show Gist options
  • Save Luiz-Monad/516e78c066bc285a5a9e562bc8284ffd to your computer and use it in GitHub Desktop.
Save Luiz-Monad/516e78c066bc285a5a9e562bc8284ffd to your computer and use it in GitHub Desktop.
nodejs require virtualization
import fs from 'fs';
import path from 'path';
import resolveBin from 'resolve-bin';
import vm, { Context, Module, Script, SyntheticModule } from 'vm';
function copyProperties(target: unknown, source: unknown) {
const propertyNames = Object.getOwnPropertyNames(source);
const propertySymbols = Object.getOwnPropertySymbols(source);
for (const name of propertyNames) {
const descriptor = Object.getOwnPropertyDescriptor(source, name);
Object.defineProperty(target, name, descriptor);
}
for (const symbol of propertySymbols) {
const descriptor = Object.getOwnPropertyDescriptor(source, symbol);
Object.defineProperty(target, symbol, descriptor);
}
}
async function run() {
const binPath = await new Promise<string>((resolve) =>
resolveBin('@strapi/strapi', (err, binPath) => {
if (err) {
console.error(`Error resolving bin script: ${err.message}`);
resolve(null);
}
resolve(binPath);
})
);
if (!binPath) return;
// Monkey patch the Strapi Server.
const redirect = (req: Function, mod: string) => {
if (mod.match(/[\\/]services[\\/]server[\\/]index[.]js/)) {
console.debug(`patched ${mod}`);
return {
createServer: (args: any) => {
const { createServer } = req('../api/engine');
return createServer(args);
},
};
}
};
// Monkey patch require to have local cache.
const cache = {};
const createCustomImport =
(require: Function) => async (specifier: string, script: Script, importAttributes: any) => {
const module = new SyntheticModule(['default'], function () {
this.setExport('default', require(specifier));
});
await module.link(() => {
throw new Error('Linker should not be called');
});
await module.evaluate();
return module;
};
const createCustomRequire = (ctx: typeof sandbox, basePath: string) => {
const newRequire = (moduleName: string) => {
const modulePath = require.resolve(moduleName, { paths: [basePath] });
// fs.writeFileSync('out.txt', `${basePath} :: ${moduleName} -> ${modulePath}`, { flag: 'a' } );
if (path.extname(modulePath) === '.json') return require(modulePath);
if (!fs.existsSync(modulePath)) return require(moduleName);
const realModulePath = modulePath;
// const realModulePath = fs.realpathSync(modulePath);
if (cache[realModulePath]) return cache[realModulePath].exports;
const module = { exports: {} };
cache[realModulePath] = module;
let moduleCode = fs.readFileSync(realModulePath, 'utf8');
const custom_require = createCustomRequire(ctx, path.dirname(realModulePath));
const custom_import = createCustomImport(custom_require);
const redirected = redirect(require, realModulePath);
if (redirected) return redirected;
if (moduleCode.startsWith('#!')) moduleCode = moduleCode.split('\n', 2).slice(1).join('');
const script = new Script(
`(function (exports, require, module, __filename, __dirname) { ${moduleCode} \n })`,
{
filename: realModulePath,
importModuleDynamically: custom_import as any,
}
);
script
.runInContext(ctx.ctx)
.call(
module.exports,
module.exports,
custom_require,
module,
realModulePath,
path.dirname(realModulePath)
);
// console.debug(`required ${realModulePath}`);
return module.exports;
};
newRequire.resolve = require.resolve;
return newRequire;
};
// Run require inside a Virtual Machine context.
const stub = (req: Function) => {
req(binPath);
};
const scriptCode = 'module.stub(require)';
const sandbox: { ctx?: Context } = {};
const globals = {
require: createCustomRequire(sandbox, __dirname),
module: { stub },
exports: {},
};
copyProperties(globals, global);
copyProperties(globals, globalThis);
sandbox.ctx = vm.createContext(globals);
vm.runInNewContext(scriptCode, sandbox.ctx);
}
run();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment