Reputation: 12589
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?
Upvotes: 2
Views: 812
Reputation: 636
In Deno 2 the subprocess API has changed (see Migration Guide) which requires to replace Deno.run
with new Deno.Command
. Using @jsejcksn's answer answer, the updated Deno 2 version would be:
./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,
args: string[],
): Promise<ProcessOutput> {
const command = new Deno.Command(cmd, {
args,
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): {cmd: string; args: string[]} {
const isWindows = Deno.build.os === "windows";
const processName = isWindows ? "pscp" : "scp";
const platformArgs: string[] = [];
// 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 { cmd: processName, args: 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 { cmd, args } = createTransferArgs(options);
const { success, stderr, stdout } = await getProcessOutput(cmd, args);
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.: I've slightly changed the ProcessOutput
to not return a status
object, but only the success
variable.
Upvotes: 0
Reputation: 33749
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/[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);
Upvotes: 3