import { NodeSSH } from 'node-ssh'; import path from 'node:path'; import fs from 'node:fs'; import process from 'node:process'; // 动态导入配置文件(支持文件不存在的情况) const loadConfig = async () => { try { const { default: config } = await import('./.deployrc.js'); return config; } catch (error) { if (error.code !== 'ERR_MODULE_NOT_FOUND') throw error; return { host: process.env.DEPLOY_HOST, port: process.env.DEPLOY_PORT || 22, username: process.env.DEPLOY_USER, password: process.env.DEPLOY_PASSWORD, privateKey: process.env.DEPLOY_PRIVATE_KEY_PATH ? fs.readFileSync(path.resolve(process.env.DEPLOY_PRIVATE_KEY_PATH)) : null, remotePath: process.env.DEPLOY_REMOTE_PATH, cleanRemote: process.env.DEPLOY_CLEAN === 'true' }; } }; const ssh = new NodeSSH(); async function deploy() { try { // 加载配置 const config = await loadConfig(); validateConfig(config); // 连接服务器 console.log('🔄 Connecting to server...'); await ssh.connect({ host: config.host, port: config.port, username: config.username, password: config.password, privateKey: config.privateKey }); // 确保目录存在 console.log('📂 Ensuring remote directory exists...'); await ssh.execCommand(`mkdir -p ${config.remotePath}`); // 清理目录 if (config.cleanRemote) { console.log('🧹 Cleaning remote directory...'); await ssh.execCommand(`rm -rf ${config.remotePath}/*`); } // 上传文件 console.log('🚀 Uploading files...'); const uploadResult = await ssh.putDirectory('./dist', config.remotePath, { recursive: true, concurrency: 10, tick: (localPath, remotePath, error) => { const relativePath = path.relative(process.cwd(), localPath); console[error ? 'error' : 'log'](`${error ? '❌' : '✅'} ${error ? 'Failed' : 'Uploaded'}: ${relativePath}`); } }); if (!uploadResult) throw new Error('Upload failed without specific error'); console.log('🎉 Deployment completed successfully!'); } catch (error) { console.error('🔥 Deployment failed:', error.message); process.exit(1); } finally { ssh.dispose(); } } function validateConfig(config) { const requiredFields = ['host', 'username', 'remotePath']; const missing = requiredFields.filter((field) => !config[field]); if (missing.length) { throw new Error(`Missing required config fields: ${missing.join(', ')}`); } if (!config.password && !config.privateKey) { throw new Error('Either password or privateKey must be provided'); } } // 执行部署 deploy();