BMAD-METHOD/tools/installer/core/wsl-node-check.js

110 lines
3.0 KiB
JavaScript

const prompts = require('../prompts');
const WSL_UNC_PATTERN = /^\\\\wsl(?:\.localhost|\$)?\\/i;
function normalizePath(value) {
return typeof value === 'string' ? value.replaceAll('/', '\\').toLowerCase() : '';
}
function isLinuxStylePath(value) {
return (
typeof value === 'string' &&
value.startsWith('/') &&
!value.startsWith('//') &&
!/^\/[a-z](?:\/|$)/i.test(value) &&
!/^\/cygdrive\/[a-z](?:\/|$)/i.test(value)
);
}
function isWslUncPath(value) {
return WSL_UNC_PATTERN.test(value || '');
}
/**
* Detect the broken interop case where WSL resolved node/npx to Windows.
* @param {Object} [runtime]
* @param {string} [runtime.platform]
* @param {Object} [runtime.env]
* @param {string} [runtime.cwd]
* @param {string} [runtime.execPath]
* @returns {{isMismatch: boolean, reason: string|null, execPath: string}}
*/
function detectWindowsNodeFromWsl(runtime = {}) {
const platform = runtime.platform || process.platform;
const env = runtime.env || process.env;
const cwd = runtime.cwd || safeCwd();
const execPath = runtime.execPath || process.execPath || '';
if (platform !== 'win32') {
return { isMismatch: false, reason: null, execPath };
}
if (env.WSL_DISTRO_NAME) {
return { isMismatch: true, reason: 'WSL_DISTRO_NAME is set', execPath };
}
if (env.WSL_INTEROP) {
return { isMismatch: true, reason: 'WSL_INTEROP is set', execPath };
}
if (isLinuxStylePath(env.PWD)) {
return { isMismatch: true, reason: 'PWD is a Linux path', execPath };
}
if (isWslUncPath(cwd)) {
return { isMismatch: true, reason: 'current directory is a WSL UNC path', execPath };
}
const normalizedExecPath = normalizePath(execPath);
if (normalizedExecPath.includes('\\wsl$\\') || normalizedExecPath.includes('\\wsl.localhost\\')) {
return { isMismatch: true, reason: 'Node executable path is under a WSL UNC path', execPath };
}
return { isMismatch: false, reason: null, execPath };
}
function safeCwd() {
try {
return process.cwd();
} catch {
return '';
}
}
function formatWindowsNodeFromWslMessage(detection) {
const lines = [
'Windows Node.js was launched from a WSL shell.',
'',
'This usually means Node.js is not installed inside the WSL distro, so WSL resolved `node`/`npx` to Windows.',
'The installer cannot safely continue because Linux paths may be interpreted as Windows paths.',
'',
'Install Node.js inside WSL, then rerun the same command from the WSL terminal.',
];
if (detection.execPath) {
lines.push('', `Detected Node executable: ${detection.execPath}`);
}
if (detection.reason) {
lines.push(`Detection signal: ${detection.reason}`);
}
return lines.join('\n');
}
async function checkWindowsNodeFromWsl() {
const detection = module.exports.detectWindowsNodeFromWsl();
if (!detection.isMismatch) {
return detection;
}
await prompts.log.error(formatWindowsNodeFromWslMessage(detection));
process.exit(1);
}
module.exports = {
checkWindowsNodeFromWsl,
detectWindowsNodeFromWsl,
formatWindowsNodeFromWslMessage,
};