Files
pikasTech-unidesk/scripts/src/ssh-windows-fs.ts
T
Codex 3a17d3b9fd fix: harden Windows trans helpers
Resolve #1691 by preserving argv boundaries, adding bounded native rg and wc/skill query support, surfacing WSL-to-Windows hints, and splitting the oversized SSH module and embedded remote scripts.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-10 13:13:03 +02:00

78 lines
3.2 KiB
TypeScript

import { readFileSync } from "node:fs";
import { rootPath } from "./config";
export type WindowsFsReadOnlyOperation = "pwd" | "ls" | "cat" | "head" | "tail" | "stat" | "wc" | "rg";
export const windowsFsReadOnlyOperations = new Set<string>(["pwd", "ls", "cat", "head", "tail", "stat", "wc", "rg"]);
interface WindowsRgPolicy {
timeoutMs: number;
maxFiles: number;
maxMatches: number;
maxBytesPerFile: number;
}
const configPath = "config/unidesk-cli.yaml";
const commonScriptSource = loadScript("common.ps1");
const operationScriptSources: Record<WindowsFsReadOnlyOperation, string> = {
pwd: loadScript("pwd.ps1"),
ls: loadScript("ls.ps1"),
cat: loadScript("cat.ps1"),
head: loadScript("head-tail.ps1"),
tail: loadScript("head-tail.ps1"),
stat: loadScript("stat.ps1"),
wc: loadScript("wc.ps1"),
rg: loadScript("rg.ps1"),
};
export function isWindowsFsReadOnlyOperation(operation: string): operation is WindowsFsReadOnlyOperation {
return windowsFsReadOnlyOperations.has(operation);
}
export function windowsFsReadOnlyScript(basePath: string | null, operation: WindowsFsReadOnlyOperation, args: string[]): string {
const rgPolicy = readWindowsRgPolicy();
const variables = [
`$basePath = ${powerShellSingleQuote(basePath ?? "")}`,
`$operation = ${powerShellSingleQuote(operation)}`,
`$argsJsonB64 = ${powerShellSingleQuote(Buffer.from(JSON.stringify(args), "utf8").toString("base64"))}`,
`$rgPolicyJsonB64 = ${powerShellSingleQuote(Buffer.from(JSON.stringify(rgPolicy), "utf8").toString("base64"))}`,
].join("; ");
return `${variables}; ${commonScriptSource} ${operationScriptSources[operation]}`;
}
function loadScript(name: string): string {
return readFileSync(new URL(`./ssh-windows-fs/${name}`, import.meta.url), "utf8");
}
function readWindowsRgPolicy(): WindowsRgPolicy {
const document = asRecord(Bun.YAML.parse(readFileSync(rootPath(configPath), "utf8")), configPath);
const trans = requiredRecord(document, "trans", configPath);
const windowsFs = requiredRecord(trans, "windowsFs", `${configPath}#trans`);
const rg = requiredRecord(windowsFs, "rg", `${configPath}#trans.windowsFs`);
return {
timeoutMs: positiveInt(rg, "timeoutMs", `${configPath}#trans.windowsFs.rg`),
maxFiles: positiveInt(rg, "maxFiles", `${configPath}#trans.windowsFs.rg`),
maxMatches: positiveInt(rg, "maxMatches", `${configPath}#trans.windowsFs.rg`),
maxBytesPerFile: positiveInt(rg, "maxBytesPerFile", `${configPath}#trans.windowsFs.rg`),
};
}
function asRecord(value: unknown, path: string): Record<string, unknown> {
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${path} must be an object`);
return value as Record<string, unknown>;
}
function requiredRecord(value: Record<string, unknown>, key: string, path: string): Record<string, unknown> {
return asRecord(value[key], `${path}.${key}`);
}
function positiveInt(value: Record<string, unknown>, key: string, path: string): number {
const raw = value[key];
if (!Number.isSafeInteger(raw) || Number(raw) <= 0) throw new Error(`${path}.${key} must be a positive integer`);
return Number(raw);
}
function powerShellSingleQuote(value: string): string {
return `'${value.replace(/'/g, "''")}'`;
}