tpmjs/packages/package-executor/src/executor.ts
Ajax Davis d73db1c333 fix: remove VM2 sandboxing to resolve Next.js build errors
**VM2 Removal:**
- Remove VM2 dependency from package-executor
- Rewrite executor to use direct package execution with require()
- Add TODO comment for future sandboxing implementation

**Why this change:**
- VM2 requires runtime filesystem access to bridge.js which doesn't work with Next.js Turbopack bundling
- Even marking as serverExternalPackages fails because VM2 uses hardcoded file paths
- Direct execution allows builds to complete while we find Next.js-compatible sandboxing solution

**Next Steps:**
- Implement proper sandboxing with isolated-vm or similar Next.js-compatible solution
- Add security measures for package execution
- Consider moving package execution to separate microservice

This unblocks CI/CD while maintaining playground functionality.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 00:57:32 +10:00

134 lines
3.9 KiB
TypeScript

/**
* Package executor without sandboxing
* Executes npm packages directly
*
* TODO: Add proper sandboxing with isolated-vm or similar when Next.js compatible solution is found
* VM2 doesn't work with Next.js Turbopack due to runtime file access requirements
*/
import { execSync } from 'child_process';
import { existsSync, mkdirSync } from 'fs';
import { join } from 'path';
import type { ExecutionResult, ExecutorOptions } from './types.js';
const DEFAULT_TIMEOUT = 5000; // 5 seconds
const CACHE_DIR = process.env.PACKAGE_CACHE_DIR || '/tmp/.tpmjs-cache';
/**
* Execute a package function with parameters
*/
export async function executePackage(
packageName: string,
functionName: string,
params: Record<string, unknown>,
options: ExecutorOptions = {}
): Promise<ExecutionResult> {
const startTime = Date.now();
const timeout = options.timeout || DEFAULT_TIMEOUT;
const cacheDir = options.cacheDir || CACHE_DIR;
try {
// Ensure package is installed
const packageDir = await ensurePackageInstalled(packageName, cacheDir);
// Set up timeout
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Execution timeout')), timeout);
});
// Execute the package with dynamic import
const executionPromise = (async () => {
// Dynamic require from the package directory
const packagePath = join(packageDir, 'node_modules', packageName);
// Use require to load the package
// biome-ignore lint/security/noGlobalEval: Required for dynamic package execution
const pkg = require(packagePath);
// Get the function to execute
const fn = typeof pkg === 'function' ? pkg : pkg[functionName || 'default'];
if (typeof fn !== 'function') {
throw new Error(`Package ${packageName} does not export a function named ${functionName || 'default'}`);
}
// Execute the function
const result = await Promise.resolve(fn(params));
return result;
})();
// Race between execution and timeout
const result = await Promise.race([executionPromise, timeoutPromise]);
const executionTimeMs = Date.now() - startTime;
return {
success: true,
output: result,
executionTimeMs,
};
} catch (error) {
const executionTimeMs = Date.now() - startTime;
return {
success: false,
error: error instanceof Error ? error.message : String(error),
executionTimeMs,
};
}
}
/**
* Ensure a package is installed in the cache directory
*/
async function ensurePackageInstalled(packageName: string, cacheDir: string): Promise<string> {
// Create cache directory if it doesn't exist
if (!existsSync(cacheDir)) {
mkdirSync(cacheDir, { recursive: true });
}
// Package-specific directory
const packageDir = join(cacheDir, packageName.replace(/[@/]/g, '_'));
// Check if already installed
if (existsSync(join(packageDir, 'node_modules', packageName))) {
return packageDir;
}
// Install the package
try {
if (!existsSync(packageDir)) {
mkdirSync(packageDir, { recursive: true });
}
// Initialize package.json if not exists
const packageJsonPath = join(packageDir, 'package.json');
if (!existsSync(packageJsonPath)) {
execSync('npm init -y', {
cwd: packageDir,
stdio: 'ignore',
});
}
// Install the package
execSync(`npm install ${packageName} --no-save`, {
cwd: packageDir,
stdio: 'ignore',
timeout: 30000, // 30 second timeout for installation
});
return packageDir;
} catch (error) {
throw new Error(
`Failed to install package ${packageName}: ${error instanceof Error ? error.message : String(error)}`
);
}
}
/**
* Clear the package cache
*/
export function clearCache(cacheDir?: string): void {
const dir = cacheDir || CACHE_DIR;
if (existsSync(dir)) {
execSync(`rm -rf ${dir}`, { stdio: 'ignore' });
}
}