Skip to main content
Glama
parseFileContent.ts3.82 kB
import { type Context, runInNewContext } from 'node:vm'; import * as esbuild from 'esbuild'; import { type LoadEnvFileOptions, loadEnvFile } from '../loadEnvFile'; import { getProjectRequire } from '../utils/ESMxCJSHelpers'; export type SandBoxContextOptions = { envVarOptions?: LoadEnvFileOptions; projectRequire?: NodeJS.Require; additionalEnvVars?: Record<string, string>; /** * Map of specifier -> mocked export to be returned when code in the VM calls require(specifier). * Example: * mocks: { * '@intlayer/config/built': { getConfig: () => ({}), Locales: {} } * } */ mocks?: Record<string, any>; /** * Optional alias map if you want to redirect specifiers. * Useful when user code imports a subpath you want to collapse. * Example: * aliases: { '@intlayer/config/built': '@intlayer/config' } */ aliases?: Record<string, string>; }; export const getSandBoxContext = (options?: SandBoxContextOptions): Context => { const { envVarOptions, projectRequire, additionalEnvVars, mocks, aliases } = options ?? {}; let additionalGlobalVar = {}; const baseRequire: NodeJS.Require = typeof projectRequire === 'function' ? projectRequire : getProjectRequire(); // Wrap require to honor mocks and aliases inside the VM const mockedRequire: NodeJS.Require = (() => { const mockTable = Object.assign( { esbuild, }, mocks ); const aliasTable = Object.assign({}, aliases); const wrappedRequire = function mockableRequire(id: string) { const target = aliasTable?.[id] ? aliasTable[id] : id; if (mockTable && Object.hasOwn(mockTable, target)) { return mockTable[target]; } // If the original id was aliased, allow mocks to be defined on either key. if (target !== id && mockTable && Object.hasOwn(mockTable, id)) { return mockTable[id]; } return baseRequire(target); } as NodeJS.Require; // Mirror NodeJS.Require properties wrappedRequire.resolve = baseRequire.resolve.bind(baseRequire); wrappedRequire.main = baseRequire.main; wrappedRequire.extensions = baseRequire.extensions; wrappedRequire.cache = baseRequire.cache; return wrappedRequire; })(); try { // Dynamically try to require React if it's installed in the project additionalGlobalVar = { React: baseRequire('react'), }; } catch (_err) { // React is not installed, so we don't inject it } const sandboxContext: Context = { exports: { default: {}, }, module: { exports: {}, }, process: { ...process, env: { ...process.env, ...loadEnvFile(envVarOptions), ...additionalEnvVars, }, }, console, require: mockedRequire, ...additionalGlobalVar, }; // Dynamically inject all global variables Object.getOwnPropertyNames(globalThis).forEach((key) => { if (!(key in sandboxContext)) { sandboxContext[key] = globalThis[key as keyof typeof globalThis]; } }); return sandboxContext; }; export const parseFileContent = <T>( fileContentString: string, options?: SandBoxContextOptions ): T | undefined => { const sandboxContext = getSandBoxContext(options); // Force strict mode so illegal writes throw instead of silently failing. runInNewContext(`"use strict";\n${fileContentString}`, sandboxContext); const candidates: unknown[] = [ sandboxContext.exports?.default, sandboxContext.module?.exports?.defaults, sandboxContext.module?.exports?.default, sandboxContext.module?.exports, ]; for (const candidate of candidates) { if ( candidate && typeof candidate === 'object' && Object.keys(candidate as object).length > 0 ) { return candidate as T; } } };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aymericzip/intlayer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server