UNPKG

slidev-addon-sm

Version:

slidev-addon slidev sm

154 lines (131 loc) 5.31 kB
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);