Files
2026-08-12 18:22:42 -05:00

146 lines
3.6 KiB
TypeScript

import Client from "ssh2-sftp-client";
import { readFileSync } from "node:fs";
import process from "node:process";
import { Worker, isMainThread, workerData } from "node:worker_threads";
type Config = {
host: string;
port: number;
username: string;
password: string;
};
type Profiles = Record<string, Config>;
type ConfigFile = {
profiles: Profiles;
};
const CONFIG_PATH = new URL("./config.json", import.meta.url);
function loadConfig(configArg?: string): Config {
let configFile: ConfigFile;
try {
configFile = JSON.parse(readFileSync(CONFIG_PATH, "utf-8")) as ConfigFile;
} catch (error) {
throw new Error(
`Could not read config file at ${CONFIG_PATH.href}. Copy config.example.json to config.json and fill in your credentials.`,
);
}
const profiles = configFile.profiles ?? {};
const profileName = configArg ?? "default";
const config = profiles[profileName];
if (!config) {
throw new Error(
`Unknown profile "${profileName}". Available profiles: ${Object.keys(profiles).join(", ")}`,
);
}
if (!config.host || !config.username || !config.password) {
throw new Error(
`Profile "${profileName}" is missing host, username, or password in config.json.`,
);
}
return { ...config, port: Number(config.port) || 22 };
}
class SftpService {
private readonly config: Config;
constructor(config: Config) {
this.config = config;
}
async uploadFile(sourcePath: string, remotePath: string) {
const client = new Client();
try {
await client.connect(this.config);
await client.put(sourcePath, remotePath);
} catch (error) {
if (error instanceof Error) {
console.error(error.message);
}
} finally {
await client.end();
}
}
async deleteFile(remotePath: string) {
const client = new Client();
try {
await client.connect(this.config);
await client.delete(remotePath);
} catch (error) {
if (error instanceof Error) {
console.error(error.message);
}
} finally {
await client.end();
}
}
}
function runWorker(runIndex: number, configArg?: string): Promise<void> {
return new Promise((resolve, reject) => {
const worker = new Worker(new URL(import.meta.url), {
workerData: { runIndex, configArg },
});
worker.on("exit", (code) => {
if (code !== 0) {
reject(new Error(`Worker ${runIndex} exited with code ${code}`));
} else {
resolve();
}
});
worker.on("error", (err) => reject(err));
});
}
async function executeIteration(runIndex: number, configArg?: string) {
const sftp = new SftpService(loadConfig(configArg));
const remotePath = `./${runIndex}-test.txt`;
const sourcePath = "./test.txt";
console.log(`[Worker ${runIndex}] Starting run number ${runIndex}`);
console.log(`[Worker ${runIndex}] Uploading file...`);
await sftp.uploadFile(sourcePath, remotePath);
console.log(`[Worker ${runIndex}] File uploaded`);
console.log(`[Worker ${runIndex}] Deleting file...`);
await sftp.deleteFile(remotePath);
console.log(`[Worker ${runIndex}] File deleted`);
}
async function main() {
if (isMainThread) {
const configArg = process.argv[2];
const TOTAL_RUNS = 6;
console.log(`[Main] Spawning ${TOTAL_RUNS} parallel workers...`);
const workerPromises: Promise<void>[] = [];
for (let i = 0; i < TOTAL_RUNS; i++) {
workerPromises.push(runWorker(i, configArg));
}
await Promise.all(workerPromises);
console.log("[Main] All workers completed execution successfully.");
} else {
const { runIndex, configArg } = workerData;
await executeIteration(runIndex, configArg);
}
}
await main();