我正在寻找一种使用 Deno 通过 SSH 传输文件的方法。我并不是试图允许用户通过网站上传文件,而是想使用 Deno 作为脚本语言将文件上传到服务器,类似于
scp
或 pscp
。不幸的是,这些都没有在任何 Deno 包装器中使用,所以我想知道如果我想保持交叉兼容性,最好最快的解决方案是什么?
创建包装器比您想象的要简单:您可以使用 subprocess API 创建对
scp
或 pscp
的调用,并且可以使用 Deno.build.os
区分平台环境。将它们结合起来实现您的目标非常简单:
./scp.ts
:
const decoder = new TextDecoder();
export type ProcessOutput = {
status: Deno.ProcessStatus;
stderr: string;
stdout: string;
};
/**
* Convenience wrapper around subprocess API.
* Requires permission `--allow-run`.
*/
export async function getProcessOutput(cmd: string[]): Promise<ProcessOutput> {
const process = Deno.run({ cmd, stderr: "piped", stdout: "piped" });
const [status, stderr, stdout] = await Promise.all([
process.status(),
decoder.decode(await process.stderrOutput()),
decoder.decode(await process.output()),
]);
process.close();
return { status, stderr, stdout };
}
// Add any config options you want to use here
// (e.g. maybe a config instead of username/host)
// The point is that you decide the API:
export type TransferOptions = {
sourcePath: string;
host: string;
username: string;
destPath: string;
};
export function createTransferArgs(options: TransferOptions): string[] {
const isWindows = Deno.build.os === "windows";
const processName = isWindows ? "pscp" : "scp";
const platformArgs: string[] = [processName];
// Construct your process args here using your options,
// handling any platform variations:
if (isWindows) {
// Translate to pscp args here...
} else {
// Translate to scp args here...
// example:
platformArgs.push(options.sourcePath);
platformArgs.push(
`${options.username}@${options.host}:${options.destPath}`,
);
}
return platformArgs;
}
./main.ts
:
import * as path from "https://deno.land/[email protected]/path/mod.ts";
import {
createTransferArgs,
getProcessOutput,
type TransferOptions,
} from "./scp.ts";
// locally (relative to CWD): ./data/example.json (or on Windows: .\data\example.json)
const fileName = "example.json";
const sourcePath = path.join(Deno.cwd(), "data", fileName);
// on remote (uses *nix FS paths): /repo/example.json
const destPath = path.posix.join("/", "repo", fileName);
const options: TransferOptions = {
sourcePath,
host: "server.local",
username: "user1",
destPath,
};
const transferArgs = createTransferArgs(options);
const { status: { success }, stderr, stdout } = await getProcessOutput(
transferArgs,
);
if (!success) {
// something went wrong, do something with stderr if you want
console.error(stderr);
Deno.exit(1);
}
// else continue...
console.log(stdout);
Deno.exit(0);
在 Deno 2 中,子流程 API 已更改(请参阅迁移指南),需要将
Deno.run
替换为 new Deno.Command
。使用 @jsejcksn 的答案 答案,更新后的 Deno 2 版本将是:
./scp.ts
const decoder = new TextDecoder();
export type ProcessOutput = {
success: boolean;
stderr: string;
stdout: string;
};
/**
* Convenience wrapper around subprocess API.
* Requires permission `--allow-run`.
*/
export async function getProcessOutput(
cmd: string[],
): Promise<ProcessOutput> {
const command = new Deno.Command(Deno.execPath(), {
args: cmd,
stderr: "piped",
stdout: "piped",
});
const result = await command.output();
const success = result.success;
const stderr = decoder.decode(result.stderr);
const stdout = decoder.decode(result.stdout);
return { success, stderr, stdout };
}
// Add any config options you want to use here
// (e.g. maybe a config instead of username/host)
// The point is that you decide the API:
export type TransferOptions = {
sourcePath: string;
host: string;
username: string;
destPath: string;
};
export function createTransferArgs(options: TransferOptions): string[] {
const isWindows = Deno.build.os === "windows";
const processName = isWindows ? "pscp" : "scp";
const platformArgs: string[] = [processName];
// Construct your process args here using your options,
// handling any platform variations:
if (isWindows) {
// Translate to pscp args here...
} else {
// Translate to scp args here...
// example:
platformArgs.push(options.sourcePath);
platformArgs.push(
`${options.username}@${options.host}:${options.destPath}`,
);
}
return platformArgs;
}
./main.ts
import {
createTransferArgs,
getProcessOutput,
type TransferOptions,
} from "./scp.ts";
// locally (relative to CWD): ./data/example.json (or on Windows: .\data\example.json)
const fileName = "example.json";
const sourcePath = path.join(Deno.cwd(), "data", fileName);
// on remote (uses *nix FS paths): /repo/example.json
const destPath = path.posix.join("/", "repo", fileName);
const options: TransferOptions = {
sourcePath,
host: "server.local",
username: "user1",
destPath,
};
const transferArgs = createTransferArgs(options);
const { success, stderr, stdout } = await getProcessOutput(
transferArgs,
);
if (!success) {
// something went wrong, do something with stderr if you want
console.error(stderr);
Deno.exit(1);
}
// else continue...
console.log(stdout);
Deno.exit(0);
p.s.:我稍微更改了
ProcessOutput
,不返回 status
对象,而只返回 success
变量。