#!/usr/bin/env node const { execSync, spawn } = require('child_process'); const fs = require('fs'); const path = require('path'); const readline = require('readline'); // 创建命令行交互接口 const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); // 配置信息 const CONFIG = { buildCommand: 'npm run build:h5', localDistPath: 'dist/h5', remoteHost: '192.168.110.13', remoteUser: 'ccos', // 默认用户名 remotePassword: 'ccos', // 默认密码 remoteTargetPath: '/home/ccos/dros/linux-arm64-unpacked/h5', remotePackagePath: '/home/ccos/dros/linux-arm64-unpacked', smbHost: '192.168.110.197', smbShare: 'Shared_Folder', smbUser: 'zskk', smbPassword: 'zskk', smbTargetDir: 'dros_human' }; // 日志输出函数 function log(message, type = 'info') { const timestamp = new Date().toLocaleString('zh-CN'); const prefix = { info: '📝', success: '✅', error: '❌', warning: '⚠️' }[type] || '📝'; console.log(`${prefix} [${timestamp}] ${message}`); } // 执行命令并输出日志 function executeCommand(command, description, options = {}) { log(`开始: ${description}`, 'info'); try { const result = execSync(command, { stdio: 'inherit', encoding: 'utf-8', ...options }); log(`完成: ${description}`, 'success'); return result; } catch (error) { log(`失败: ${description}`, 'error'); log(`错误信息: ${error.message}`, 'error'); throw error; } } // 使用 Node.js SSH2 执行远程命令 async function executeSSHCommand(host, user, password, command, description, timeout = 600000) { log(`开始: ${description}`, 'info'); return new Promise((resolve, reject) => { const { NodeSSH } = require('node-ssh'); const ssh = new NodeSSH(); ssh.connect({ host: host, username: user, password: password, tryKeyboard: true, readyTimeout: 30000 }) .then(() => { // 设置命令执行超时时间(默认 10 分钟) return ssh.execCommand(command, { execOptions: { timeout: timeout } }); }) .then((result) => { if (result.code === 0) { log(`完成: ${description}`, 'success'); if (result.stdout) { console.log(result.stdout); } ssh.dispose(); resolve(result); } else { log(`失败: ${description}`, 'error'); if (result.stderr) { log(`错误信息: ${result.stderr}`, 'error'); } ssh.dispose(); reject(new Error(result.stderr || '命令执行失败')); } }) .catch((error) => { log(`失败: ${description}`, 'error'); log(`错误信息: ${error.message}`, 'error'); ssh.dispose(); reject(error); }); }); } // 使用 SCP 上传文件 async function uploadViaSCP(host, user, password, localPath, remotePath, description) { log(`开始: ${description}`, 'info'); return new Promise((resolve, reject) => { const { NodeSSH } = require('node-ssh'); const ssh = new NodeSSH(); ssh.connect({ host: host, username: user, password: password, tryKeyboard: true }) .then(() => { return ssh.putDirectory(localPath, remotePath, { recursive: true, concurrency: 10, validate: (itemPath) => { return true; }, tick: (localPath, remotePath, error) => { if (error) { log(`上传失败: ${localPath}`, 'error'); } } }); }) .then((status) => { if (status) { log(`完成: ${description}`, 'success'); ssh.dispose(); resolve(); } else { log(`失败: ${description}`, 'error'); ssh.dispose(); reject(new Error('文件上传失败')); } }) .catch((error) => { log(`失败: ${description}`, 'error'); log(`错误信息: ${error.message}`, 'error'); ssh.dispose(); reject(error); }); }); } // 读取 package.json 中的版本号 function getVersionFromPackageJson() { try { const packageJsonPath = path.join(__dirname, '..', 'package.json'); const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); return packageJson.version; } catch (error) { log('无法读取 package.json 文件', 'error'); throw new Error('无法获取版本号,请检查 package.json 文件'); } } // 倒计时确认远程服务器凭据 function confirmRemoteCredentials() { return new Promise((resolve) => { let countdown = 10; log(`将使用远程服务器凭据: 用户名=${CONFIG.remoteUser}, 密码=${CONFIG.remotePassword}`, 'info'); log(`${countdown} 秒后自动使用默认凭据,按回车键立即确认,输入 'n' 修改...`, 'warning'); const timer = setInterval(() => { countdown--; if (countdown > 0) { process.stdout.write(`\r⏱️ 倒计时: ${countdown} 秒...`); } }, 1000); const timeout = setTimeout(() => { clearInterval(timer); process.stdout.write('\r✅ 使用默认凭据\n'); rl.removeAllListeners('line'); resolve({ user: CONFIG.remoteUser, password: CONFIG.remotePassword }); }, 10000); rl.once('line', (input) => { clearTimeout(timeout); clearInterval(timer); process.stdout.write('\r'); const trimmedInput = input.trim().toLowerCase(); if (trimmedInput === 'n' || trimmedInput === 'no') { // 用户选择修改 rl.question('请输入远程服务器用户名: ', (user) => { const trimmedUser = user.trim() || CONFIG.remoteUser; rl.question('请输入远程服务器密码: ', (password) => { const trimmedPassword = password.trim() || CONFIG.remotePassword; resolve({ user: trimmedUser, password: trimmedPassword }); }); }); } else { // 用户按回车或其他输入,使用默认值 log('使用默认凭据', 'success'); resolve({ user: CONFIG.remoteUser, password: CONFIG.remotePassword }); } }); }); } // 检查本地构建目录是否存在 function checkLocalBuildPath() { if (!fs.existsSync(CONFIG.localDistPath)) { log(`本地构建目录不存在: ${CONFIG.localDistPath}`, 'warning'); return false; } return true; } // 检查 node-ssh 是否已安装 function checkNodeSSH() { try { require.resolve('node-ssh'); return true; } catch (e) { return false; } } // 构建 H5 文件 function buildH5() { log('开始构建 H5 文件...', 'info'); executeCommand(CONFIG.buildCommand, '构建 H5 项目'); if (!checkLocalBuildPath()) { throw new Error('构建完成但未找到输出目录'); } log(`构建文件位于: ${CONFIG.localDistPath}`, 'success'); } // 复制文件到远程服务器 async function copyToRemoteServer(remoteUser, remotePassword) { log(`开始复制文件到远程服务器 ${CONFIG.remoteHost}...`, 'info'); // 先清空远程目标目录 await executeSSHCommand( CONFIG.remoteHost, remoteUser, remotePassword, `rm -rf ${CONFIG.remoteTargetPath}/* && mkdir -p ${CONFIG.remoteTargetPath}`, '清空远程目标目录' ); // 使用 SCP 复制文件 await uploadViaSCP( CONFIG.remoteHost, remoteUser, remotePassword, CONFIG.localDistPath, CONFIG.remoteTargetPath, '复制文件到远程服务器' ); } // 在远程服务器上打包 async function packageOnRemoteServer(version, remoteUser, remotePassword) { const packageName = `linux-arm64-app-human-first-${version}.tar.gz`; log(`开始在远程服务器上打包: ${packageName}`, 'info'); // 在 /home/ccos/dros/ 目录下打包 linux-arm64-unpacked 文件夹 const packageDir = '/home/ccos/dros'; const targetFolder = 'linux-arm64-unpacked'; const packagePath = `${packageDir}/${packageName}`; await executeSSHCommand( CONFIG.remoteHost, remoteUser, remotePassword, `cd ${packageDir} && tar -czvf ${packageName} ${targetFolder}`, '远程打包' ); log(`打包完成: ${packageName}`, 'success'); log(`打包文件位置: ${packagePath}`, 'info'); return packageName; } // 传输打包文件到共享文件夹 async function transferToSharedFolder(packageName, remoteUser, remotePassword) { log(`开始传输打包文件到 ${CONFIG.smbHost}...`, 'info'); log('提示: 大文件传输可能需要较长时间,请耐心等待...', 'info'); // 打包文件在 /home/ccos/dros/ 目录下 const remotePackageFile = `/home/ccos/dros/${packageName}`; // 直接在 192.168.110.13 上使用 smbclient 传输到 192.168.110.197 // 添加 --timeout 参数设置 smbclient 超时时间(单位:秒) const smbCommand = `smbclient //${CONFIG.smbHost}/${CONFIG.smbShare} -U "${CONFIG.smbUser}%${CONFIG.smbPassword}" --timeout 600 -c "cd ${CONFIG.smbTargetDir}; put ${remotePackageFile} ${packageName}"`; // 设置 SSH 命令超时为 15 分钟(900000 毫秒) await executeSSHCommand( CONFIG.remoteHost, remoteUser, remotePassword, smbCommand, '从远程服务器直接上传到共享文件夹', 900000 ); // 可选:清理远程服务器上的打包文件 try { await executeSSHCommand( CONFIG.remoteHost, remoteUser, remotePassword, `rm -f ${remotePackageFile}`, '清理远程服务器打包文件' ); } catch (error) { log('清理远程打包文件失败(可忽略)', 'warning'); } } // 主函数 async function main() { console.log('\n========================================'); console.log('🚀 H5 构建与部署脚本'); console.log('========================================\n'); try { // 检查 node-ssh 是否已安装 if (!checkNodeSSH()) { log('检测到缺少 node-ssh 依赖,正在安装...', 'warning'); executeCommand('npm install node-ssh', '安装 node-ssh'); } // 1. 读取版本号 const version = getVersionFromPackageJson(); log(`从 package.json 读取版本号: ${version}`, 'success'); console.log(''); // 2. 倒计时确认远程服务器凭据 const credentials = await confirmRemoteCredentials(); rl.close(); log(`远程服务器用户: ${credentials.user}`, 'info'); console.log('\n========================================\n'); // 4. 构建 H5 文件 buildH5(); console.log('\n========================================\n'); // 5. 复制到远程服务器 await copyToRemoteServer(credentials.user, credentials.password); console.log('\n========================================\n'); // 6. 在远程服务器上打包 const packageName = await packageOnRemoteServer(version, credentials.user, credentials.password); console.log('\n========================================\n'); // 7. 传输到共享文件夹 await transferToSharedFolder(packageName, credentials.user, credentials.password); console.log('\n========================================\n'); log('🎉 所有步骤完成!', 'success'); log(`打包文件: ${packageName}`, 'success'); log(`已上传到: //${CONFIG.smbHost}/${CONFIG.smbShare}/${CONFIG.smbTargetDir}/${packageName}`, 'success'); } catch (error) { log('部署过程中发生错误', 'error'); log(error.message, 'error'); process.exit(1); } } // 运行主函数 main();