3a17d3b9fd
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>
78 lines
3.2 KiB
TypeScript
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, "''")}'`;
|
|
}
|