I'm looking for a way to transfer files via SSH using Deno. I'm not trying to allow the user to upload files through a website, instead I want to use Deno as a scripting language to upload files to a server similarly to scp
or pscp
. Unfortunately, neither of those have been used in any Deno wrapper, so I wonder what the best fastest solution would be if I want maintain cross-compatibility?
Creating a wrapper is simpler than you might think: you can use the subprocess API to create calls to scp
or pscp
, and you can discriminate platform environment using Deno.build.os
. Combining them to achieve your goal is straightforward:
./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/std@0.129.0/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);