feat(wsl): enhance WSL integration with improved path resolution and error handling
- Refactored the `toWslUncPath` function to accept a prefix for better flexibility. - Introduced `getWslExecutableCandidates` to dynamically find WSL executables. - Implemented `runWsl` to handle WSL command execution with error management. - Updated `listWslDistros` to include a fallback mechanism for inconsistent `-q` behavior. - Enhanced `resolveWslHome` and `handleFindWslClaudeRoots` to improve home path resolution and avoid duplicates in candidate paths.
This commit is contained in:
parent
7f315619d0
commit
5c563f40ec
1 changed files with 102 additions and 27 deletions
|
|
@ -744,25 +744,77 @@ function normalizeWslHomePath(home: string): string | null {
|
||||||
return normalized;
|
return normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
function toWslUncPath(distro: string, posixPath: string): string {
|
function toWslUncPath(prefix: '\\\\wsl.localhost' | '\\\\wsl$', distro: string, posixPath: string): string {
|
||||||
const uncSuffix = posixPath.replace(/\//g, '\\');
|
const uncSuffix = posixPath.replace(/\//g, '\\');
|
||||||
return `\\\\wsl.localhost\\${distro}${uncSuffix}`;
|
return `${prefix}\\${distro}${uncSuffix}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWslExecutableCandidates(): string[] {
|
||||||
|
const candidates = new Set<string>();
|
||||||
|
|
||||||
|
const windir = process.env.WINDIR;
|
||||||
|
if (windir) {
|
||||||
|
candidates.add(path.join(windir, 'System32', 'wsl.exe'));
|
||||||
|
candidates.add(path.join(windir, 'Sysnative', 'wsl.exe'));
|
||||||
|
}
|
||||||
|
|
||||||
|
candidates.add('wsl.exe');
|
||||||
|
return Array.from(candidates);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runWsl(args: string[], timeout = 5000): Promise<{ stdout: string; stderr: string }> {
|
||||||
|
const candidates = getWslExecutableCandidates();
|
||||||
|
let lastError: unknown = null;
|
||||||
|
|
||||||
|
for (const executable of candidates) {
|
||||||
|
try {
|
||||||
|
const result = await execFileAsync(executable, args, { timeout });
|
||||||
|
return {
|
||||||
|
stdout: result.stdout ?? '',
|
||||||
|
stderr: result.stderr ?? '',
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
lastError = error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw lastError instanceof Error ? lastError : new Error('Unable to execute wsl.exe');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function listWslDistros(): Promise<string[]> {
|
async function listWslDistros(): Promise<string[]> {
|
||||||
const { stdout } = await execFileAsync('wsl.exe', ['-l', '-q'], { timeout: 4000 });
|
try {
|
||||||
return stdout
|
const { stdout } = await runWsl(['-l', '-q'], 4000);
|
||||||
.split(/\r?\n/)
|
return stdout
|
||||||
.map((line) => line.trim())
|
.split(/\r?\n/)
|
||||||
.filter((line) => line.length > 0);
|
.map((line) => line.trim())
|
||||||
|
.filter((line) => line.length > 0);
|
||||||
|
} catch {
|
||||||
|
// Fallback for environments where -q behavior is inconsistent.
|
||||||
|
const { stdout } = await runWsl(['-l'], 4000);
|
||||||
|
return stdout
|
||||||
|
.split(/\r?\n/)
|
||||||
|
.map((line) => line.trim())
|
||||||
|
.filter((line) => line.length > 0)
|
||||||
|
.filter((line) => !line.toLowerCase().startsWith('windows subsystem for linux'))
|
||||||
|
.filter((line) => !line.toLowerCase().includes('default version'))
|
||||||
|
.map(stripDefaultSuffix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function stripDefaultSuffix(input: string): string {
|
||||||
|
const suffix = '(default)';
|
||||||
|
if (!input.toLowerCase().endsWith(suffix)) {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
return input.slice(0, input.length - suffix.length).trimEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resolveWslHome(distro: string): Promise<string | null> {
|
async function resolveWslHome(distro: string): Promise<string | null> {
|
||||||
try {
|
try {
|
||||||
const { stdout } = await execFileAsync(
|
const { stdout } = await runWsl(
|
||||||
'wsl.exe',
|
|
||||||
['-d', distro, '--', 'sh', '-lc', 'printf %s "$HOME"'],
|
['-d', distro, '--', 'sh', '-lc', 'printf %s "$HOME"'],
|
||||||
{ timeout: 4000 }
|
5000
|
||||||
);
|
);
|
||||||
return normalizeWslHomePath(stdout);
|
return normalizeWslHomePath(stdout);
|
||||||
} catch {
|
} catch {
|
||||||
|
|
@ -787,29 +839,52 @@ async function handleFindWslClaudeRoots(
|
||||||
}
|
}
|
||||||
|
|
||||||
const candidates: WslClaudeRootCandidate[] = [];
|
const candidates: WslClaudeRootCandidate[] = [];
|
||||||
|
const seen = new Set<string>();
|
||||||
for (const distro of distros) {
|
for (const distro of distros) {
|
||||||
const homePath = await resolveWslHome(distro);
|
const homePath = await resolveWslHome(distro);
|
||||||
if (!homePath) {
|
const homeCandidates = new Set<string>();
|
||||||
continue;
|
if (homePath) {
|
||||||
|
homeCandidates.add(homePath);
|
||||||
}
|
}
|
||||||
|
if (process.env.USERNAME) {
|
||||||
|
homeCandidates.add(`/home/${process.env.USERNAME}`);
|
||||||
|
}
|
||||||
|
homeCandidates.add('/root');
|
||||||
|
|
||||||
const claudePosixPath = path.posix.join(homePath, '.claude');
|
for (const homeCandidate of homeCandidates) {
|
||||||
const claudeUncPath = toWslUncPath(distro, claudePosixPath);
|
const normalizedHome = normalizeWslHomePath(homeCandidate);
|
||||||
const projectsPath = path.join(claudeUncPath, 'projects');
|
if (!normalizedHome) {
|
||||||
|
continue;
|
||||||
const hasProjectsDir = (() => {
|
|
||||||
try {
|
|
||||||
return fs.existsSync(projectsPath) && fs.statSync(projectsPath).isDirectory();
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
})();
|
const claudePosixPath = path.posix.join(normalizedHome, '.claude');
|
||||||
|
|
||||||
candidates.push({
|
const uncPaths = [
|
||||||
distro,
|
toWslUncPath('\\\\wsl.localhost', distro, claudePosixPath),
|
||||||
path: claudeUncPath,
|
toWslUncPath('\\\\wsl$', distro, claudePosixPath),
|
||||||
hasProjectsDir,
|
];
|
||||||
});
|
|
||||||
|
for (const claudeUncPath of uncPaths) {
|
||||||
|
if (seen.has(claudeUncPath.toLowerCase())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
seen.add(claudeUncPath.toLowerCase());
|
||||||
|
|
||||||
|
const projectsPath = path.join(claudeUncPath, 'projects');
|
||||||
|
const hasProjectsDir = (() => {
|
||||||
|
try {
|
||||||
|
return fs.existsSync(projectsPath) && fs.statSync(projectsPath).isDirectory();
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
candidates.push({
|
||||||
|
distro,
|
||||||
|
path: claudeUncPath,
|
||||||
|
hasProjectsDir,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { success: true, data: candidates };
|
return { success: true, data: candidates };
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue