mirror of
https://github.com/stablyai/orca.git
synced 2026-11-03 18:47:40 +00:00
* feat(wsl): open WSL host paths from Windows through one owner-routed capability (WSL-via-orcad C8) * test(wsl): typed shell handler lookup for the WSL owner tests * fix(wsl): read known WSL hosts through a registered accessor; e2e for the Windows folder picker * fix(wsl): open the host-path entry filled in after a Windows pick; e2e screenshots * test(wsl): read the e2e picker's default path through the main process env * refactor(wsl): read a WSL host's distro from its record instead of a new accessor * fix(wsl): skip the host browser only for a path Windows' picker or an Explorer drop filled in A path left filled from an earlier pick on an SSH or paired host opens the host browser again, as it did before.
97 lines
3.8 KiB
TypeScript
97 lines
3.8 KiB
TypeScript
/**
|
|
* How Windows reaches the paths of a resource's OWNER host (WSL-via-orcad D5). Only a WSL host
|
|
* qualifies: its files are on `\\wsl.localhost\<distro>\...`. Every other host gets null, so SSH,
|
|
* paired servers and this computer keep their behavior. Translation is pure string work on the
|
|
* desktop side; nothing runs inside the distro.
|
|
*/
|
|
import { parseRoutableExecutionHostId } from './execution-host'
|
|
import { parseWslUncPath, toWindowsWslUncPath } from './wsl-paths'
|
|
|
|
export type WindowsPathAccess = {
|
|
/** The distro as registered, which is the casing every emitted path uses. */
|
|
readonly wslDistro: string
|
|
/** The Windows spelling of an absolute Linux path on the host, or null when it has none. */
|
|
toWindows(linuxPath: string): string | null
|
|
/** The host's Linux path for a `\\wsl.localhost` / `\\wsl$` path into its distro, else null. */
|
|
fromWindows(windowsPath: string): string | null
|
|
}
|
|
|
|
export type WindowsPathAccessContext = {
|
|
platform: NodeJS.Platform | null
|
|
/** The distro behind a WSL host's runtime environment; null for every other environment. */
|
|
wslDistroForEnvironment: (environmentId: string) => string | null | undefined
|
|
}
|
|
|
|
/** The one capability every Windows path guard asks, keyed on the owner host, never on focus. */
|
|
export function windowsPathAccess(
|
|
ownerHostId: string | null | undefined,
|
|
context: WindowsPathAccessContext
|
|
): WindowsPathAccess | null {
|
|
if (context.platform !== 'win32') {
|
|
return null
|
|
}
|
|
const host = parseRoutableExecutionHostId(ownerHostId)
|
|
if (host?.kind !== 'runtime') {
|
|
return null
|
|
}
|
|
const distro = context.wslDistroForEnvironment(host.environmentId)?.trim()
|
|
return distro ? wslDistroWindowsPathAccess(distro) : null
|
|
}
|
|
|
|
export function wslDistroWindowsPathAccess(distro: string): WindowsPathAccess {
|
|
return {
|
|
wslDistro: distro,
|
|
toWindows: (linuxPath) => {
|
|
const normalized = normalizeLinuxPath(linuxPath)
|
|
return normalized === null ? null : toWindowsWslUncPath(normalized, distro)
|
|
},
|
|
fromWindows: (windowsPath) => {
|
|
const parsed = parseWslUncPath(stripWin32LongPathPrefix(windowsPath.trim()))
|
|
// Why case-insensitive: Windows folds the distro in UNC paths (`\\wsl.localhost\ubuntu`).
|
|
if (!parsed || !sameWslDistroName(parsed.distro, distro)) {
|
|
return null
|
|
}
|
|
return normalizeLinuxPath(parsed.linuxPath)
|
|
}
|
|
}
|
|
}
|
|
|
|
export function sameWslDistroName(left: string, right: string): boolean {
|
|
return left.trim().toLowerCase() === right.trim().toLowerCase()
|
|
}
|
|
|
|
export type WindowsPathRejection =
|
|
| { kind: 'windows-drive' }
|
|
| { kind: 'other-distro'; distro: string }
|
|
| { kind: 'not-wsl' }
|
|
|
|
/** Why `fromWindows` refused a path, for copy that tells the user what to pick instead. */
|
|
export function classifyRejectedWindowsPath(windowsPath: string): WindowsPathRejection {
|
|
const trimmed = stripWin32LongPathPrefix(windowsPath.trim())
|
|
if (/^[A-Za-z]:([\\/]|$)/.test(trimmed)) {
|
|
return { kind: 'windows-drive' }
|
|
}
|
|
const parsed = parseWslUncPath(trimmed)
|
|
return parsed ? { kind: 'other-distro', distro: parsed.distro } : { kind: 'not-wsl' }
|
|
}
|
|
|
|
/** `\\?\UNC\wsl.localhost\...` and `\\?\C:\...` are the same paths Explorer may hand over. */
|
|
function stripWin32LongPathPrefix(path: string): string {
|
|
if (/^\\\\\?\\UNC\\/i.test(path)) {
|
|
return `\\\\${path.slice('\\\\?\\UNC\\'.length)}`
|
|
}
|
|
return /^\\\\\?\\[A-Za-z]:/.test(path) ? path.slice('\\\\?\\'.length) : path
|
|
}
|
|
|
|
/** Absolute, no `..`/backslash/NUL (Windows would read those differently); trailing slash dropped. */
|
|
function normalizeLinuxPath(linuxPath: string): string | null {
|
|
if (!linuxPath.startsWith('/') || linuxPath.startsWith('//') || /[\\\0]/.test(linuxPath)) {
|
|
return null
|
|
}
|
|
const segments = linuxPath.split('/').filter((segment) => segment !== '' && segment !== '.')
|
|
if (segments.includes('..')) {
|
|
return null
|
|
}
|
|
return `/${segments.join('/')}`
|
|
}
|