mcard-js
Version:
MCard - Content-addressable storage with cryptographic hashing, handle resolution, and vector search for Node.js and browsers
125 lines (124 loc) • 4.29 kB
JavaScript
/**
* JavaScript runtime executor.
*
* Executes JavaScript code in VM sandbox or subprocess for module/import support.
*/
import * as fs from 'fs';
import * as path from 'path';
import * as vm from 'vm';
import * as child_process from 'child_process';
import * as util from 'util';
import { execFile, parseOutput } from './base.js';
import { findProjectRoot } from '../FileSystemUtils.js';
const writeFile = util.promisify(fs.writeFile);
const unlink = util.promisify(fs.unlink);
export class JavaScriptRuntime {
async execute(code, context, config) {
// Detect if code needs Node.js features not available in VM sandbox
if (this.requiresSubprocess(code)) {
return this.executeSubprocess(code, context);
}
return this.executeInVM(code, context);
}
/**
* Check if code requires subprocess execution (has imports/exports).
*/
requiresSubprocess(code) {
return (code.includes('require(') ||
code.includes('import(') ||
code.includes('import ') ||
code.includes('export '));
}
/**
* Execute code in VM sandbox (fast, for simple scripts).
*/
async executeInVM(code, context) {
const sandbox = {
context,
target: context,
result: undefined,
console: {
log: (...args) => console.log('[JS]', ...args),
error: (...args) => console.error('[JS]', ...args),
warn: (...args) => console.warn('[JS]', ...args),
},
Math,
JSON,
Date,
setTimeout,
clearTimeout,
setInterval,
clearInterval,
process: process,
spawn: child_process.spawn,
exec: child_process.exec,
// Network capabilities
fetch: global.fetch ? global.fetch.bind(global) : undefined,
Headers: global.Headers,
Request: global.Request,
Response: global.Response,
URL: global.URL,
URLSearchParams: global.URLSearchParams,
};
const codeRunnable = code + '\nresult;';
const script = new vm.Script(codeRunnable);
const vmContext = vm.createContext(sandbox);
const executionResult = script.runInContext(vmContext, { timeout: 5000 });
return sandbox.result !== undefined ? sandbox.result : executionResult;
}
/**
* Execute code in subprocess (for modules/imports).
*/
async executeSubprocess(code, context) {
const contextStr = JSON.stringify(context);
const projectRoot = findProjectRoot();
const wrapper = `
const process = require('process');
const context = JSON.parse(process.argv[2]);
global.context = context;
let result;
(async () => {
try {
${code}
console.log(JSON.stringify(result));
} catch (e) {
console.error(JSON.stringify({ error: e.message, stack: e.stack }));
process.exit(1);
}
})();
`;
const tmpFile = path.resolve(projectRoot, `tmp_js_${Date.now()}.js`);
await writeFile(tmpFile, wrapper);
try {
const { stdout, stderr } = await execFile('node', [tmpFile, contextStr], {
cwd: projectRoot,
env: { ...process.env }
});
if (stderr && stderr.trim()) {
try {
const errObj = JSON.parse(stderr.trim());
if (errObj.error)
throw new Error(errObj.error);
}
catch (e) {
// Not JSON, ignore warnings
}
}
return parseOutput(stdout);
}
catch (error) {
const stderr = error.stderr ? `\nStderr: ${error.stderr.toString()}` : '';
try {
const errObj = JSON.parse(error.stderr?.toString().trim() || '');
if (errObj.error)
throw new Error(errObj.error);
}
catch { }
throw new Error(`JavaScript execution failed: ${error.message}${stderr}`);
}
finally {
await unlink(tmpFile);
}
}
}
//# sourceMappingURL=javascript.js.map