fix: support windows ssh apply-patch
This commit is contained in:
@@ -32,7 +32,20 @@ export interface ApplyPatchV2Options {
|
||||
}
|
||||
|
||||
export interface ApplyPatchV2Executor {
|
||||
run(command: string[], input?: string): Promise<ApplyPatchV2RemoteResult>;
|
||||
run?: (command: string[], input?: string) => Promise<ApplyPatchV2RemoteResult>;
|
||||
fs?: ApplyPatchV2FileSystem;
|
||||
}
|
||||
|
||||
export interface ApplyPatchV2FileSystem {
|
||||
stat(path: string): Promise<ApplyPatchV2FileStat>;
|
||||
readBlock(path: string, blockIndex: number, blockBytes: number): Promise<Buffer>;
|
||||
writeFile(path: string, content: Buffer): Promise<void>;
|
||||
deleteFile(path: string): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ApplyPatchV2FileStat {
|
||||
bytes: number;
|
||||
sha256: string;
|
||||
}
|
||||
|
||||
export interface ApplyPatchV2RemoteResult {
|
||||
@@ -258,6 +271,10 @@ async function executePlannedOperation(executor: ApplyPatchV2Executor, operation
|
||||
await writeRemoteText(executor, operation.path, operation.content);
|
||||
return;
|
||||
}
|
||||
if (executor.fs) {
|
||||
await executor.fs.deleteFile(operation.path);
|
||||
return;
|
||||
}
|
||||
await checkedRemoteV2(executor, "delete", [operation.path]);
|
||||
}
|
||||
|
||||
@@ -266,6 +283,45 @@ const writeB64ArgvLimit = 48_000;
|
||||
const writeB64ChunkChars = 12_000;
|
||||
|
||||
async function readRemoteText(executor: ApplyPatchV2Executor, target: string): Promise<string> {
|
||||
if (executor.fs) {
|
||||
const stat = await executor.fs.stat(target);
|
||||
if (!Number.isSafeInteger(stat.bytes) || stat.bytes < 0 || !/^[0-9a-f]{64}$/u.test(stat.sha256)) {
|
||||
throw new ApplyPatchV2Error("remote apply-patch v2 fs stat returned invalid metadata", { path: target, stat });
|
||||
}
|
||||
const chunks: Buffer[] = [];
|
||||
let actualBytes = 0;
|
||||
for (let blockIndex = 0; actualBytes < stat.bytes; blockIndex += 1) {
|
||||
const chunk = await executor.fs.readBlock(target, blockIndex, readBlockBytes);
|
||||
if (chunk.length === 0) {
|
||||
throw new ApplyPatchV2Error("remote apply-patch v2 fs read returned an empty block before EOF", {
|
||||
path: target,
|
||||
blockIndex,
|
||||
expectedBytes: stat.bytes,
|
||||
actualBytes,
|
||||
});
|
||||
}
|
||||
chunks.push(chunk);
|
||||
actualBytes += chunk.length;
|
||||
}
|
||||
const contentBuffer = Buffer.concat(chunks);
|
||||
if (contentBuffer.length !== stat.bytes) {
|
||||
throw new ApplyPatchV2Error("remote apply-patch v2 fs read byte count mismatch", {
|
||||
path: target,
|
||||
expectedBytes: stat.bytes,
|
||||
actualBytes: contentBuffer.length,
|
||||
});
|
||||
}
|
||||
const actualSha256 = sha256Hex(contentBuffer);
|
||||
if (actualSha256 !== stat.sha256) {
|
||||
throw new ApplyPatchV2Error("remote apply-patch v2 fs read sha256 mismatch", {
|
||||
path: target,
|
||||
expectedSha256: stat.sha256,
|
||||
actualSha256,
|
||||
});
|
||||
}
|
||||
return contentBuffer.toString("utf8");
|
||||
}
|
||||
|
||||
const stat = await checkedRemoteV2(executor, "stat", [target]);
|
||||
const [bytesText, expectedSha256] = stat.stdout.trim().split(/\s+/u);
|
||||
const expectedBytes = Number(bytesText);
|
||||
@@ -312,6 +368,10 @@ async function readRemoteText(executor: ApplyPatchV2Executor, target: string): P
|
||||
|
||||
async function writeRemoteText(executor: ApplyPatchV2Executor, target: string, content: string): Promise<void> {
|
||||
const contentBuffer = Buffer.from(content, "utf8");
|
||||
if (executor.fs) {
|
||||
await executor.fs.writeFile(target, contentBuffer);
|
||||
return;
|
||||
}
|
||||
const encoded = contentBuffer.toString("base64");
|
||||
const expectedBytes = String(contentBuffer.length);
|
||||
const expectedSha256 = sha256Hex(contentBuffer);
|
||||
@@ -347,6 +407,9 @@ type RemoteV2Operation =
|
||||
| "move";
|
||||
|
||||
async function checkedRemoteV2(executor: ApplyPatchV2Executor, operation: RemoteV2Operation, args: string[], input?: string): Promise<{ stdout: string }> {
|
||||
if (!executor.run) {
|
||||
throw new ApplyPatchV2Error("remote apply-patch v2 executor does not provide a command runner", { operation, args });
|
||||
}
|
||||
const result = await executor.run(remoteV2Script(operation, args), input);
|
||||
if (result.exitCode === 0) return result;
|
||||
throw new ApplyPatchV2Error("remote apply-patch v2 operation failed", {
|
||||
|
||||
Reference in New Issue
Block a user