Files
antigravity-claude-proxy/src/utils/native-module-helper.js
jgor20 2d4693b4c6 refactor(utils): rename and refactor clearModuleCache to recursively clear dependencies
The function has been renamed to clearRequireCache and updated to recursively
clear the require cache for a module and all its dependencies, preventing
cycles with a visited set. This improves reliability after rebuilding native
modules by ensuring complete cache invalidation. Removed unused createRequire
import as it's no longer needed.
2026-01-05 01:13:45 +00:00

163 lines
5.1 KiB
JavaScript

/**
* Native Module Helper
* Detects and auto-rebuilds native Node.js modules when they become
* incompatible after a Node.js version update.
*/
import { execSync } from 'child_process';
import { dirname, join } from 'path';
import { existsSync } from 'fs';
import { logger } from './logger.js';
/**
* Check if an error is a NODE_MODULE_VERSION mismatch error
* @param {Error} error - The error to check
* @returns {boolean} True if it's a version mismatch error
*/
export function isModuleVersionError(error) {
const message = error?.message || '';
return message.includes('NODE_MODULE_VERSION') &&
message.includes('was compiled against a different Node.js version');
}
/**
* Extract the module path from a NODE_MODULE_VERSION error message
* @param {Error} error - The error containing the module path
* @returns {string|null} The path to the .node file, or null if not found
*/
export function extractModulePath(error) {
const message = error?.message || '';
// Match pattern like: "The module '/path/to/module.node'"
const match = message.match(/The module '([^']+\.node)'/);
return match ? match[1] : null;
}
/**
* Find the package root directory from a .node file path
* @param {string} nodeFilePath - Path to the .node file
* @returns {string|null} Path to the package root, or null if not found
*/
export function findPackageRoot(nodeFilePath) {
// Walk up from the .node file to find package.json
let dir = dirname(nodeFilePath);
while (dir) {
const packageJsonPath = join(dir, 'package.json');
if (existsSync(packageJsonPath)) {
return dir;
}
const parentDir = dirname(dir);
// Stop when we've reached the filesystem root (dirname returns same path)
if (parentDir === dir) {
break;
}
dir = parentDir;
}
return null;
}
/**
* Attempt to rebuild a native module
* @param {string} packagePath - Path to the package root directory
* @returns {boolean} True if rebuild succeeded, false otherwise
*/
export function rebuildModule(packagePath) {
try {
logger.info(`[NativeModule] Rebuilding native module at: ${packagePath}`);
// Run npm rebuild in the package directory
const output = execSync('npm rebuild', {
cwd: packagePath,
stdio: 'pipe', // Capture output instead of printing
timeout: 120000 // 2 minute timeout
});
// Log rebuild output for debugging
const outputStr = output?.toString().trim();
if (outputStr) {
logger.debug(`[NativeModule] Rebuild output:\n${outputStr}`);
}
logger.success('[NativeModule] Rebuild completed successfully');
return true;
} catch (error) {
// Include stdout/stderr from the failed command for troubleshooting
const stdout = error.stdout?.toString().trim();
const stderr = error.stderr?.toString().trim();
let errorDetails = `[NativeModule] Rebuild failed: ${error.message}`;
if (stdout) {
errorDetails += `\n[NativeModule] stdout: ${stdout}`;
}
if (stderr) {
errorDetails += `\n[NativeModule] stderr: ${stderr}`;
}
logger.error(errorDetails);
return false;
}
}
/**
* Attempt to auto-rebuild a native module from an error
* @param {Error} error - The NODE_MODULE_VERSION error
* @returns {boolean} True if rebuild succeeded, false otherwise
*/
export function attemptAutoRebuild(error) {
const nodePath = extractModulePath(error);
if (!nodePath) {
logger.error('[NativeModule] Could not extract module path from error');
return false;
}
const packagePath = findPackageRoot(nodePath);
if (!packagePath) {
logger.error('[NativeModule] Could not find package root');
return false;
}
logger.warn('[NativeModule] Native module version mismatch detected');
logger.info('[NativeModule] Attempting automatic rebuild...');
return rebuildModule(packagePath);
}
/**
* Recursively clear a module and its dependencies from the require cache
* This is needed after rebuilding a native module to force re-import
* @param {string} modulePath - Resolved path to the module
* @param {object} cache - The require.cache object
* @param {Set} [visited] - Set of already-visited paths to prevent cycles
*/
export function clearRequireCache(modulePath, cache, visited = new Set()) {
if (visited.has(modulePath)) return;
visited.add(modulePath);
const mod = cache[modulePath];
if (!mod) return;
// Recursively clear children first
if (mod.children) {
for (const child of mod.children) {
clearRequireCache(child.id, cache, visited);
}
}
// Remove from parent's children array
if (mod.parent && mod.parent.children) {
const idx = mod.parent.children.indexOf(mod);
if (idx !== -1) {
mod.parent.children.splice(idx, 1);
}
}
// Delete from cache
delete cache[modulePath];
}
export default {
isModuleVersionError,
extractModulePath,
findPackageRoot,
rebuildModule,
attemptAutoRebuild,
clearRequireCache
};