Files
orca/src/main/ssh/ssh-remote-platform.ts
T
Neil ce687221d3 lint(unicorn): enable prefer-number-properties, prefer-array-find, prefer-array-index-of (#7516)
Enable three unicorn rules — one correctness, two performance — and fix every
existing violation repo-wide so the rules pass as errors.

prefer-number-properties (76 sites)
- parseInt/parseFloat/NaN -> Number.* : safe aliases (autofixed).
- isNaN -> Number.isNaN (12 sites, hand-converted): global isNaN coerces its
  argument, Number.isNaN does not. Verified every call site already passes a
  number (Number.parseInt results, number-typed fields, Date.getTime()), so the
  conversion is behavior-preserving today and guards against a future non-numeric
  argument silently coercing.

prefer-array-find (26 sites)
- .filter(pred)[0] -> .find(pred); .filter(pred).at(-1) / .pop() -> .findLast(pred).
  Drops the intermediate array and short-circuits.

prefer-array-index-of (5 sites)
- .findIndex(x => x === v) -> .indexOf(v).

Verified: typecheck (node/cli/web) clean, 53 affected suites pass (1679 tests),
oxlint clean repo-wide. mobile/ uses findLast safely (already ships ES2023
.toReversed()); config scripts and e2e helpers run on Node 24.
2026-07-05 23:56:37 -07:00

148 lines
4.0 KiB
TypeScript

import type { RelayPlatform } from './relay-protocol'
export type RemotePathFlavor = 'posix' | 'windows'
export type RemoteCommandDialect = 'posix' | 'powershell'
export type RemoteOperatingSystem = 'linux' | 'darwin' | 'win32'
export type RemoteArchitecture = 'x64' | 'arm64'
export type RemoteHostPlatform = {
relayPlatform: RelayPlatform
os: RemoteOperatingSystem
arch: RemoteArchitecture
pathFlavor: RemotePathFlavor
commandDialect: RemoteCommandDialect
pathSeparator: '/' | '\\'
pathDelimiter: ':' | ';'
}
const PLATFORM_INFO: Record<RelayPlatform, RemoteHostPlatform> = {
'linux-x64': {
relayPlatform: 'linux-x64',
os: 'linux',
arch: 'x64',
pathFlavor: 'posix',
commandDialect: 'posix',
pathSeparator: '/',
pathDelimiter: ':'
},
'linux-arm64': {
relayPlatform: 'linux-arm64',
os: 'linux',
arch: 'arm64',
pathFlavor: 'posix',
commandDialect: 'posix',
pathSeparator: '/',
pathDelimiter: ':'
},
'darwin-x64': {
relayPlatform: 'darwin-x64',
os: 'darwin',
arch: 'x64',
pathFlavor: 'posix',
commandDialect: 'posix',
pathSeparator: '/',
pathDelimiter: ':'
},
'darwin-arm64': {
relayPlatform: 'darwin-arm64',
os: 'darwin',
arch: 'arm64',
pathFlavor: 'posix',
commandDialect: 'posix',
pathSeparator: '/',
pathDelimiter: ':'
},
'win32-x64': {
relayPlatform: 'win32-x64',
os: 'win32',
arch: 'x64',
pathFlavor: 'windows',
commandDialect: 'powershell',
pathSeparator: '\\',
pathDelimiter: ';'
},
'win32-arm64': {
relayPlatform: 'win32-arm64',
os: 'win32',
arch: 'arm64',
pathFlavor: 'windows',
commandDialect: 'powershell',
pathSeparator: '\\',
pathDelimiter: ';'
}
}
export function getRemoteHostPlatform(platform: RelayPlatform): RemoteHostPlatform {
return PLATFORM_INFO[platform]
}
export function isWindowsRemoteHost(host: RemoteHostPlatform): boolean {
return host.os === 'win32'
}
export function normalizeWindowsRemotePath(path: string): string {
return path.replace(/\\/g, '/')
}
export function normalizeRemoteHome(rawHome: string, host: RemoteHostPlatform): string {
const home = rawHome.trim()
return isWindowsRemoteHost(host) ? normalizeWindowsRemotePath(home).replace(/\/+$/, '') : home
}
function hasUnsafeRemotePathChar(value: string): boolean {
for (let i = 0; i < value.length; i += 1) {
const code = value.charCodeAt(i)
if (code === 0 || code === 10 || code === 13) {
return true
}
}
return false
}
export function validateRemoteHome(home: string, host: RemoteHostPlatform): boolean {
if (!home || hasUnsafeRemotePathChar(home)) {
return false
}
if (host.pathFlavor === 'windows') {
return /^[a-zA-Z]:\//.test(home) || home.startsWith('//')
}
return home.startsWith('/')
}
export function joinRemotePath(host: RemoteHostPlatform, ...segments: string[]): string {
const cleaned = segments.filter(Boolean)
if (cleaned.length === 0) {
return ''
}
if (host.pathFlavor === 'windows') {
const [first, ...rest] = cleaned.map((segment) => normalizeWindowsRemotePath(segment))
return rest.reduce((acc, segment) => {
const left = acc.replace(/\/+$/, '')
const right = segment.replace(/^\/+/, '')
return `${left}/${right}`
}, first)
}
const [first, ...rest] = cleaned
return rest.reduce((acc, segment) => {
const left = acc.replace(/\/+$/, '')
const right = segment.replace(/^\/+/, '')
return `${left}/${right}`
}, first)
}
export function remoteBasename(path: string, host: RemoteHostPlatform): string {
const normalized = host.pathFlavor === 'windows' ? normalizeWindowsRemotePath(path) : path
return normalized.split('/').findLast(Boolean) ?? ''
}
export function remoteDirname(path: string, host: RemoteHostPlatform): string {
const normalized = host.pathFlavor === 'windows' ? normalizeWindowsRemotePath(path) : path
const parts = normalized.split('/')
parts.pop()
const joined = parts.join('/')
if (host.pathFlavor === 'windows') {
return joined
}
return joined || '/'
}