Spaces:
Configuration error
Configuration error
File size: 5,258 Bytes
d613519 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | /**
* 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
};
|