|
@@ -0,0 +1,381 @@
|
|
|
|
|
+#!/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();
|