deploy-h5.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. #!/usr/bin/env node
  2. const { execSync, spawn } = require('child_process');
  3. const fs = require('fs');
  4. const path = require('path');
  5. const readline = require('readline');
  6. // 创建命令行交互接口
  7. const rl = readline.createInterface({
  8. input: process.stdin,
  9. output: process.stdout
  10. });
  11. // 配置信息
  12. const CONFIG = {
  13. buildCommand: 'npm run build:h5',
  14. localDistPath: 'dist/h5',
  15. remoteHost: '192.168.110.13',
  16. remoteUser: 'ccos', // 默认用户名
  17. remotePassword: 'ccos', // 默认密码
  18. remoteTargetPath: '/home/ccos/dros/linux-arm64-unpacked/h5',
  19. remotePackagePath: '/home/ccos/dros/linux-arm64-unpacked',
  20. smbHost: '192.168.110.197',
  21. smbShare: 'Shared_Folder',
  22. smbUser: 'zskk',
  23. smbPassword: 'zskk',
  24. smbTargetDir: 'dros_human'
  25. };
  26. // 日志输出函数
  27. function log(message, type = 'info') {
  28. const timestamp = new Date().toLocaleString('zh-CN');
  29. const prefix = {
  30. info: '📝',
  31. success: '✅',
  32. error: '❌',
  33. warning: '⚠️'
  34. }[type] || '📝';
  35. console.log(`${prefix} [${timestamp}] ${message}`);
  36. }
  37. // 执行命令并输出日志
  38. function executeCommand(command, description, options = {}) {
  39. log(`开始: ${description}`, 'info');
  40. try {
  41. const result = execSync(command, {
  42. stdio: 'inherit',
  43. encoding: 'utf-8',
  44. ...options
  45. });
  46. log(`完成: ${description}`, 'success');
  47. return result;
  48. } catch (error) {
  49. log(`失败: ${description}`, 'error');
  50. log(`错误信息: ${error.message}`, 'error');
  51. throw error;
  52. }
  53. }
  54. // 使用 Node.js SSH2 执行远程命令
  55. async function executeSSHCommand(host, user, password, command, description, timeout = 600000) {
  56. log(`开始: ${description}`, 'info');
  57. return new Promise((resolve, reject) => {
  58. const { NodeSSH } = require('node-ssh');
  59. const ssh = new NodeSSH();
  60. ssh.connect({
  61. host: host,
  62. username: user,
  63. password: password,
  64. tryKeyboard: true,
  65. readyTimeout: 30000
  66. })
  67. .then(() => {
  68. // 设置命令执行超时时间(默认 10 分钟)
  69. return ssh.execCommand(command, {
  70. execOptions: {
  71. timeout: timeout
  72. }
  73. });
  74. })
  75. .then((result) => {
  76. if (result.code === 0) {
  77. log(`完成: ${description}`, 'success');
  78. if (result.stdout) {
  79. console.log(result.stdout);
  80. }
  81. ssh.dispose();
  82. resolve(result);
  83. } else {
  84. log(`失败: ${description}`, 'error');
  85. if (result.stderr) {
  86. log(`错误信息: ${result.stderr}`, 'error');
  87. }
  88. ssh.dispose();
  89. reject(new Error(result.stderr || '命令执行失败'));
  90. }
  91. })
  92. .catch((error) => {
  93. log(`失败: ${description}`, 'error');
  94. log(`错误信息: ${error.message}`, 'error');
  95. ssh.dispose();
  96. reject(error);
  97. });
  98. });
  99. }
  100. // 使用 SCP 上传文件
  101. async function uploadViaSCP(host, user, password, localPath, remotePath, description) {
  102. log(`开始: ${description}`, 'info');
  103. return new Promise((resolve, reject) => {
  104. const { NodeSSH } = require('node-ssh');
  105. const ssh = new NodeSSH();
  106. ssh.connect({
  107. host: host,
  108. username: user,
  109. password: password,
  110. tryKeyboard: true
  111. })
  112. .then(() => {
  113. return ssh.putDirectory(localPath, remotePath, {
  114. recursive: true,
  115. concurrency: 10,
  116. validate: (itemPath) => {
  117. return true;
  118. },
  119. tick: (localPath, remotePath, error) => {
  120. if (error) {
  121. log(`上传失败: ${localPath}`, 'error');
  122. }
  123. }
  124. });
  125. })
  126. .then((status) => {
  127. if (status) {
  128. log(`完成: ${description}`, 'success');
  129. ssh.dispose();
  130. resolve();
  131. } else {
  132. log(`失败: ${description}`, 'error');
  133. ssh.dispose();
  134. reject(new Error('文件上传失败'));
  135. }
  136. })
  137. .catch((error) => {
  138. log(`失败: ${description}`, 'error');
  139. log(`错误信息: ${error.message}`, 'error');
  140. ssh.dispose();
  141. reject(error);
  142. });
  143. });
  144. }
  145. // 读取 package.json 中的版本号
  146. function getVersionFromPackageJson() {
  147. try {
  148. const packageJsonPath = path.join(__dirname, '..', 'package.json');
  149. const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
  150. return packageJson.version;
  151. } catch (error) {
  152. log('无法读取 package.json 文件', 'error');
  153. throw new Error('无法获取版本号,请检查 package.json 文件');
  154. }
  155. }
  156. // 倒计时确认远程服务器凭据
  157. function confirmRemoteCredentials() {
  158. return new Promise((resolve) => {
  159. let countdown = 10;
  160. log(`将使用远程服务器凭据: 用户名=${CONFIG.remoteUser}, 密码=${CONFIG.remotePassword}`, 'info');
  161. log(`${countdown} 秒后自动使用默认凭据,按回车键立即确认,输入 'n' 修改...`, 'warning');
  162. const timer = setInterval(() => {
  163. countdown--;
  164. if (countdown > 0) {
  165. process.stdout.write(`\r⏱️ 倒计时: ${countdown} 秒...`);
  166. }
  167. }, 1000);
  168. const timeout = setTimeout(() => {
  169. clearInterval(timer);
  170. process.stdout.write('\r✅ 使用默认凭据\n');
  171. rl.removeAllListeners('line');
  172. resolve({ user: CONFIG.remoteUser, password: CONFIG.remotePassword });
  173. }, 10000);
  174. rl.once('line', (input) => {
  175. clearTimeout(timeout);
  176. clearInterval(timer);
  177. process.stdout.write('\r');
  178. const trimmedInput = input.trim().toLowerCase();
  179. if (trimmedInput === 'n' || trimmedInput === 'no') {
  180. // 用户选择修改
  181. rl.question('请输入远程服务器用户名: ', (user) => {
  182. const trimmedUser = user.trim() || CONFIG.remoteUser;
  183. rl.question('请输入远程服务器密码: ', (password) => {
  184. const trimmedPassword = password.trim() || CONFIG.remotePassword;
  185. resolve({ user: trimmedUser, password: trimmedPassword });
  186. });
  187. });
  188. } else {
  189. // 用户按回车或其他输入,使用默认值
  190. log('使用默认凭据', 'success');
  191. resolve({ user: CONFIG.remoteUser, password: CONFIG.remotePassword });
  192. }
  193. });
  194. });
  195. }
  196. // 检查本地构建目录是否存在
  197. function checkLocalBuildPath() {
  198. if (!fs.existsSync(CONFIG.localDistPath)) {
  199. log(`本地构建目录不存在: ${CONFIG.localDistPath}`, 'warning');
  200. return false;
  201. }
  202. return true;
  203. }
  204. // 检查 node-ssh 是否已安装
  205. function checkNodeSSH() {
  206. try {
  207. require.resolve('node-ssh');
  208. return true;
  209. } catch (e) {
  210. return false;
  211. }
  212. }
  213. // 构建 H5 文件
  214. function buildH5() {
  215. log('开始构建 H5 文件...', 'info');
  216. executeCommand(CONFIG.buildCommand, '构建 H5 项目');
  217. if (!checkLocalBuildPath()) {
  218. throw new Error('构建完成但未找到输出目录');
  219. }
  220. log(`构建文件位于: ${CONFIG.localDistPath}`, 'success');
  221. }
  222. // 复制文件到远程服务器
  223. async function copyToRemoteServer(remoteUser, remotePassword) {
  224. log(`开始复制文件到远程服务器 ${CONFIG.remoteHost}...`, 'info');
  225. // 先清空远程目标目录
  226. await executeSSHCommand(
  227. CONFIG.remoteHost,
  228. remoteUser,
  229. remotePassword,
  230. `rm -rf ${CONFIG.remoteTargetPath}/* && mkdir -p ${CONFIG.remoteTargetPath}`,
  231. '清空远程目标目录'
  232. );
  233. // 使用 SCP 复制文件
  234. await uploadViaSCP(
  235. CONFIG.remoteHost,
  236. remoteUser,
  237. remotePassword,
  238. CONFIG.localDistPath,
  239. CONFIG.remoteTargetPath,
  240. '复制文件到远程服务器'
  241. );
  242. }
  243. // 在远程服务器上打包
  244. async function packageOnRemoteServer(version, remoteUser, remotePassword) {
  245. const packageName = `linux-arm64-app-human-first-${version}.tar.gz`;
  246. log(`开始在远程服务器上打包: ${packageName}`, 'info');
  247. // 在 /home/ccos/dros/ 目录下打包 linux-arm64-unpacked 文件夹
  248. const packageDir = '/home/ccos/dros';
  249. const targetFolder = 'linux-arm64-unpacked';
  250. const packagePath = `${packageDir}/${packageName}`;
  251. await executeSSHCommand(
  252. CONFIG.remoteHost,
  253. remoteUser,
  254. remotePassword,
  255. `cd ${packageDir} && tar -czvf ${packageName} ${targetFolder}`,
  256. '远程打包'
  257. );
  258. log(`打包完成: ${packageName}`, 'success');
  259. log(`打包文件位置: ${packagePath}`, 'info');
  260. return packageName;
  261. }
  262. // 传输打包文件到共享文件夹
  263. async function transferToSharedFolder(packageName, remoteUser, remotePassword) {
  264. log(`开始传输打包文件到 ${CONFIG.smbHost}...`, 'info');
  265. log('提示: 大文件传输可能需要较长时间,请耐心等待...', 'info');
  266. // 打包文件在 /home/ccos/dros/ 目录下
  267. const remotePackageFile = `/home/ccos/dros/${packageName}`;
  268. // 直接在 192.168.110.13 上使用 smbclient 传输到 192.168.110.197
  269. // 添加 --timeout 参数设置 smbclient 超时时间(单位:秒)
  270. const smbCommand = `smbclient //${CONFIG.smbHost}/${CONFIG.smbShare} -U "${CONFIG.smbUser}%${CONFIG.smbPassword}" --timeout 600 -c "cd ${CONFIG.smbTargetDir}; put ${remotePackageFile} ${packageName}"`;
  271. // 设置 SSH 命令超时为 15 分钟(900000 毫秒)
  272. await executeSSHCommand(
  273. CONFIG.remoteHost,
  274. remoteUser,
  275. remotePassword,
  276. smbCommand,
  277. '从远程服务器直接上传到共享文件夹',
  278. 900000
  279. );
  280. // 可选:清理远程服务器上的打包文件
  281. try {
  282. await executeSSHCommand(
  283. CONFIG.remoteHost,
  284. remoteUser,
  285. remotePassword,
  286. `rm -f ${remotePackageFile}`,
  287. '清理远程服务器打包文件'
  288. );
  289. } catch (error) {
  290. log('清理远程打包文件失败(可忽略)', 'warning');
  291. }
  292. }
  293. // 主函数
  294. async function main() {
  295. console.log('\n========================================');
  296. console.log('🚀 H5 构建与部署脚本');
  297. console.log('========================================\n');
  298. try {
  299. // 检查 node-ssh 是否已安装
  300. if (!checkNodeSSH()) {
  301. log('检测到缺少 node-ssh 依赖,正在安装...', 'warning');
  302. executeCommand('npm install node-ssh', '安装 node-ssh');
  303. }
  304. // 1. 读取版本号
  305. const version = getVersionFromPackageJson();
  306. log(`从 package.json 读取版本号: ${version}`, 'success');
  307. console.log('');
  308. // 2. 倒计时确认远程服务器凭据
  309. const credentials = await confirmRemoteCredentials();
  310. rl.close();
  311. log(`远程服务器用户: ${credentials.user}`, 'info');
  312. console.log('\n========================================\n');
  313. // 4. 构建 H5 文件
  314. buildH5();
  315. console.log('\n========================================\n');
  316. // 5. 复制到远程服务器
  317. await copyToRemoteServer(credentials.user, credentials.password);
  318. console.log('\n========================================\n');
  319. // 6. 在远程服务器上打包
  320. const packageName = await packageOnRemoteServer(version, credentials.user, credentials.password);
  321. console.log('\n========================================\n');
  322. // 7. 传输到共享文件夹
  323. await transferToSharedFolder(packageName, credentials.user, credentials.password);
  324. console.log('\n========================================\n');
  325. log('🎉 所有步骤完成!', 'success');
  326. log(`打包文件: ${packageName}`, 'success');
  327. log(`已上传到: //${CONFIG.smbHost}/${CONFIG.smbShare}/${CONFIG.smbTargetDir}/${packageName}`, 'success');
  328. } catch (error) {
  329. log('部署过程中发生错误', 'error');
  330. log(error.message, 'error');
  331. process.exit(1);
  332. }
  333. }
  334. // 运行主函数
  335. main();