| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381 |
- #!/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();
|