import path from "path";
import { NodeSSH } from "node-ssh";
/**
* Utilities functions for SSH
* @module SSH
*
* @example
* import {
* getSSHClient,
* sshConnect,
* joinPath,
* zipFolder,
* sshPutFile,
* sshExecCommand,
* } from "nsuite";
* import { PATH_ROOT } from "#scripts/ConstantUtils";
* import { sshConfig } from "#hosts/Shanghai-Tencent/nginx/build/config";
*
* const sshClient = getSSHClient();
* await sshConnect({
* ssh: sshClient,
* ...sshConfig,
* });
* const pathDist = joinPath(PATH_ROOT, "apps-home/blog", "dist");
* const pathDistZip = joinPath(pathDist, "../dist.zip");
* await zipFolder({
* pathFolder: pathDist,
* pathOutputFile: pathDistZip,
* });
*
* const pathRemote = "/www/sites/www.orzzone.com/public";
* const pathRemoteZip = `${pathRemote}/dist.zip`;
* await sshPutFile({
* ssh: sshClient,
* localFile: pathDistZip,
* remoteFile: pathRemoteZip,
* });
*
* const execCommand = async (command: string): Promise<void> => {
* await sshExecCommand({
* ssh: sshClient,
* cwd: pathRemote,
* command,
* });
* };
* await execCommand("rm dist");
* await execCommand("unzip -o dist.zip");
* await execCommand("rm dist.zip");
*
* process.exit(0);
*/
/**
* @typedef {import('node-ssh').NodeSSH} SSH
* @typedef {import('node-ssh').SSHGetPutDirectoryOptions} GetPutDirectoryOptions
* @typedef {import('node-ssh').SSHPutFilesOptions} PutFilesOptions
*/
/**
* @typedef {Object} PathPair
* @property {string} local
* @property {string} remote
*/
/**
* Get SSH instance
* @returns {SSH}
*/
export function getSSHClient() {
return new NodeSSH();
}
/**
* @typedef {Object} ParamsConnect
* @property {SSH} ssh
* @property {string} host
* @property {number} port
* @property {string} username
* @property {string} password
*/
/**
* Connect to SSH
* @param {ParamsConnect} payload
* @returns {Promise<void>}
*/
export async function sshConnect(payload) {
const { ssh, ...config } = payload;
await ssh.connect(config);
}
/**
* @callback SSHUploadFileCallback
* @param {string} local
* @param {string} remote
* @param {Error | null} error
*/
/**
* @typedef {Object} ParamsPutDir
* @property {SSH} ssh
* @property {string} fromPath
* @property {string} toPath
* @property {GetPutDirectoryOptions} [options]
* @property {SSHUploadFileCallback} [uploadCallback] - callback function for upload progress
*/
/**
* @typedef {Object} ReturnPutDir
* @property {boolean} success
* @property {PathPair[]} failItems
* @property {PathPair[]} successItems
*/
/**
* Put directory
* @param {ParamsPutDir} payload
* @returns {Promise<ReturnPutDir>}
*/
export async function sshPutDirectory(payload) {
const { ssh, fromPath, toPath, options, uploadCallback } = payload;
/** @type {PathPair[]} */
const failItems = [];
/** @type {PathPair[]} */
const successItems = [];
const success = await ssh.putDirectory(fromPath, toPath, {
recursive: true,
concurrency: 10,
validate(itemPath) {
const baseName = path.basename(itemPath);
return baseName !== "node_modules";
},
tick(local, remote, error) {
if (typeof uploadCallback === "function") {
uploadCallback(local, remote, error);
}
if (error) {
failItems.push({ local, remote });
} else {
successItems.push({ local, remote });
}
},
...(options || {}),
});
return {
success,
failItems,
successItems,
};
}
/**
* @typedef {Object} ParamsSSHGetDir
* @property {SSH} ssh
* @property {string} localDirectory
* @property {string} remoteDirectory
* @property {GetPutDirectoryOptions} [options]
*/
/**
* Download directory from remote server
* @param { ParamsSSHGetDir } payload
* @returns {Promise<boolean>}
*/
export async function sshGetDirectory(payload) {
const { ssh, localDirectory, remoteDirectory, options } = payload;
return await ssh.getDirectory(localDirectory, remoteDirectory, options);
}
/**
* @typedef {Object} ParamsSSHGetFile
* @property {SSH} ssh
* @property {string} localFile
* @property {string} remoteFile
* @property {import('ssh2').SFTPWrapper | null} [givenSftp]
* @property {import('ssh2').TransferOptions} [transferOptions]
*/
/**
* Download file from server
* @param {ParamsSSHGetFile} payload
* @returns {Promise<void>}
*/
export async function sshGetFile(payload) {
const { ssh, localFile, remoteFile, givenSftp, transferOptions } = payload;
return await ssh.getFile(localFile, remoteFile, givenSftp, transferOptions);
}
/**
* Put file to server
* @param {ParamsSSHGetFile} payload
* @returns {Promise<void>}
*/
export async function sshPutFile(payload) {
const { ssh, localFile, remoteFile, givenSftp, transferOptions } = payload;
return await ssh.putFile(localFile, remoteFile, givenSftp, transferOptions);
}
/**
* @typedef {Object} ParamsPutFiles
* @property {SSH} ssh
* @property {PathPair[]} files
* @property {PutFilesOptions} [options]
*/
/**
* Put files
* @param {ParamsPutFiles} payload
* @returns {Promise<void>}
*/
export async function sshPutFiles(payload) {
const { ssh, files, options } = payload;
return ssh.putFiles(files, options);
}
/**
* @typedef {Object} ParamsExecCommand
* @property {SSH} ssh
* @property {string} cwd
* @property {string} command
* @property {function(Buffer): void} [onStdout]
* @property {function(Buffer): void} [onStderr]
*/
/**
* Execute command
* @param {ParamsExecCommand} payload
* @returns {Promise<void>}
*/
export async function sshExecCommand(payload) {
const { ssh, cwd, command, onStdout, onStderr } = payload;
await ssh.execCommand(command, {
cwd,
onStdout(chunk) {
if (typeof onStdout === "function") {
onStdout(chunk);
}
},
onStderr(chunk) {
if (typeof onStderr === "function") {
onStderr(chunk);
}
},
});
}