slidev-addon-sm
Version:
slidev-addon slidev sm
154 lines (131 loc) • 5.31 kB
JavaScript
import Client from "ssh2-sftp-client";
import readlineSync from "readline-sync";
import { $, cd, fs, path } from "zx";
// 配置信息
const host = "noicoding.cn";
const args = process.argv;
const srcPath = args[2];
const dstPath = args[3];
console.log(srcPath + " ==> " + dstPath);
const mutableStdout = process.stdout;
/**
* 兼容旧版本的路径存在性判断
* @param {Object} sftp sftp 客户端实例
* @param {string} remotePath 服务器路径
* @returns {Promise<boolean>} 是否存在
*/
async function isPathExists(sftp, remotePath) {
try {
// 通过 stat() 方法判断:存在则返回文件信息,不存在则抛出异常
await sftp.stat(remotePath);
return true;
} catch (err) {
// 错误码 2 表示「文件/目录不存在」,其他错误抛出
return false;
}
}
/**
* 递归遍历本地文件夹,获取所有文件的详细信息
* @param {string} localRootDir 本地根目录(待同步的文件夹)
* @param {string} currentSubDir 当前子目录(递归用,初始为本地根目录)
* @returns {Array} 包含所有文件的 { localAbsPath, relativePath } 数组
*/
function recursiveReadLocalDir(localRootDir, currentSubDir = localRootDir) {
let fileList = [];
// 读取当前目录下的所有文件/目录
const files = fs.readdirSync(currentSubDir, { withFileTypes: true });
for (const file of files) {
let localAbsPath = path.join(currentSubDir, file.name);
// 计算相对路径(相对于本地根目录,用于保持服务器目录结构)
let relativePath = path.relative(localRootDir, localAbsPath);
localAbsPath = localAbsPath.replace(/\\/g, "/");
relativePath = relativePath.replace(/\\/g, "/");
// 过滤不需要同步的文件/目录(可根据需求自定义)
const ignorePatterns = [
/node_modules/, // 过滤 node_modules 目录
/\.DS_Store/, // 过滤 Mac 隐藏文件
/\.git/, // 过滤 git 目录
/\.env/, // 过滤环境配置文件
];
const isIgnored = ignorePatterns.some((pattern) =>
pattern.test(localAbsPath)
);
if (isIgnored) continue;
if (file.isDirectory()) {
// 递归遍历子目录
fileList = fileList.concat(
recursiveReadLocalDir(localRootDir, localAbsPath)
);
} else if (file.isFile()) {
// 收集文件信息
fileList.push({
localAbsPath, // 本地文件绝对路径
relativePath, // 相对于根目录的相对路径
});
}
}
return fileList;
}
/**
* Node.js 连接 SSH(SFTP)并上传文件(ssh2-sftp-client 实现)
*/
async function uploadFileWithSftpClient(passwd) {
const sftp = new Client();
let fileList = [];
try {
let localDir = path.resolve(srcPath); // 转为绝对路径
let remoteDir = dstPath;
// 2. 建立 SFTP 连接 // 1. 建立 SSH/SFTP 连接
await sftp.connect({
host: host,
port: 22,
username: "root",
// 方式1:密码登录
password: passwd,
// 方式2:密钥登录
// privateKey: fs.readFileSync("/本地私钥路径/id_rsa"),
// passphrase: "私钥密码(可选)"
});
console.log("✅ SFTP 连接成功!");
// 3. 递归获取本地所有文件列表
fileList = recursiveReadLocalDir(localDir);
if (fileList.length === 0) {
console.log("ℹ️ 本地目录为空,无需同步");
return;
}
console.log(`ℹ️ 共发现 ${fileList.length} 个待同步文件`);
// 4. 遍历文件,逐个上传(自动创建服务器目录)
for (const fileInfo of fileList) {
let { localAbsPath, relativePath } = fileInfo;
// 服务器目标文件路径(根目录 + 相对路径)
let remoteFilePath = path.posix.join(remoteDir, relativePath); // 使用 posix 路径(服务器多为 Linux)
// 服务器目标目录路径(文件所在目录)
let remoteFileDir = path.posix.dirname(remoteFilePath);
relativePath = relativePath.replace(/\\/g, "/");
remoteFileDir = remoteFileDir.replace(/\\/g, "/");
// 自动创建服务器目录(递归创建,不存在则创建,存在则忽略)
if (!(await isPathExists(sftp, remoteFileDir + "/"))) {
await sftp.mkdir(remoteFileDir, true);
console.log(`📁 自动创建服务器目录:${remoteFileDir}`);
}
// 上传文件(覆盖已有文件,如需跳过可先判断 sftp.exists(remoteFilePath))
await sftp.put(localAbsPath, remoteFilePath);
console.log(`✅ 上传成功:${relativePath} → ${remoteFilePath}`);
}
console.log(`\n🎉 文件夹递归同步完成!共上传 ${fileList.length} 个文件`);
} catch (err) {
console.error(`❌ 同步失败:${err.message}`);
throw err; // 抛出异常,便于上层处理
} finally {
// 5. 无论成功/失败,关闭 SFTP 连接
if (sftp) {
await sftp.end();
console.log("🔌 SFTP 连接已关闭");
}
}
}
const password = readlineSync.question("Enter Passwd: ", {
hideEchoBack: true,
mask: "*", // 占位符
});
uploadFileWithSftpClient(password);