-
-
Save Luiz-Monad/516e78c066bc285a5a9e562bc8284ffd to your computer and use it in GitHub Desktop.
nodejs require virtualization
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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