123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299 |
- import { exec } from 'child_process'
- import { Client as SSHClient } from 'ssh2'
- import ora from 'ora'
- import SftpClient from 'ssh2-sftp-client'
- import chalk from 'chalk'
- import fs from 'fs'
- import { loadEnv } from 'vite'
- // 存储原始的 接口请求地址
- let originalApiUrl = '';
- // 创建分隔线
- const createSeparator = (text) => {
- const width = 60;
- const padding = Math.floor((width - text.length - 2) / 2);
- const separator = '='.repeat(padding) + ' ' + text + ' ' + '='.repeat(padding);
- return chalk.blue('\n' + separator + '\n');
- };
- const spinner = ora('正在构建项目...').start()
- spinner.stopAndPersist({
- text:
- ' __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ \n' +
- ' | |\n' +
- ' | *** 运行发布流程 *** |\n' +
- ' | 【pigx管理系统】 |\n' +
- ' | __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ |\n'
- })
- spinner.start()
- let speed = 20
- let time = 20
- const count = 400
- const getProgress = () => {
- const done = '█'
- const undone = '░'
- let progress = Math.floor((speed / count) * 50)
- if (progress > 50) {
- progress = 50
- }
- const progressBar = done.repeat(progress) + undone.repeat(50 - progress)
- return progressBar
- }
- const interval = setInterval(() => {
- spinner.text = `正在构建项目 ${getProgress()} 进度${((speed / count) * 100).toFixed(1)}% 耗时${(time / 10).toFixed(1)}秒 `
- speed++
- time++
- if (speed > count) {
- speed = 400
- }
- }, 100)
- const env = loadEnv('', process.cwd())
- const updateProxyConfig = (url) => {
- try {
- const envPath = '.env';
- let content = fs.readFileSync(envPath, 'utf-8');
- // 提取当前的 apiBaseUrl (如果需要)
- const match = content.match(/VITE_API_URL = (.*)/);
- if (!originalApiUrl && match && match[1]) {
- originalApiUrl = match[1];
- console.log(chalk.gray(`原始 API 地址已保存: ${originalApiUrl}`));
- }
- // 使用正则表达式替换apiBaseUrl的值,但保留注释
- content = content.replace(
- /VITE_API_URL = (.*)/,
- `VITE_API_URL = ${url}`
- );
- fs.writeFileSync(envPath, content, 'utf-8');
- console.log(chalk.green('✓'), chalk.blue(`已更新 API 地址为: ${url}`));
- } catch (error) {
- console.error(chalk.red('✗'), chalk.red('更新 API 地址失败:'), error);
- process.exit(1);
- }
- };
- const command = (list, config, host) => {
- return new Promise((resolveCommand, rejectCommand) => {
- const ssh = new SSHClient();
- const runCommand = (index) => {
- if (index >= list.length) {
- ssh.end();
- console.log(chalk.green('✓'), chalk.blue(`端口 ${host} 的远程命令执行完毕`));
- resolveCommand();
- return;
- }
- const cmd = list[index];
- console.log(chalk.blue('→'), chalk.gray(`执行命令: ${cmd}`));
- ssh.exec(cmd, (err, stream) => {
- if (err) {
- console.error(chalk.red('✗'), chalk.red(`命令执行失败: ${err}`));
- ssh.end();
- rejectCommand(err);
- return;
- }
- let hasError = false;
- stream.on('close', (code, signal) => {
- if (hasError && !cmd.includes('docker build')) {
- console.error(chalk.red('✗'), chalk.red(`命令执行失败,退出码: ${code}`));
- ssh.end();
- rejectCommand(new Error(`命令 "${cmd}" 失败,退出码: ${code}`));
- return;
- }
- console.log(chalk.green('✓'), chalk.gray(`命令执行完成: ${cmd}`));
- runCommand(index + 1);
- }).on('data', (data) => {
- console.log(chalk.gray(data.toString())); // 实时输出命令结果
- }).stderr.on('data', (data) => {
- hasError = true;
- console.error(chalk.yellow('!'), chalk.yellow(data.toString())); // 实时输出错误信息
- });
- });
- };
- ssh.on('ready', () => {
- console.log(chalk.green('✓'), chalk.blue('SSH 连接成功'));
- runCommand(0);
- }).connect(config);
- // Handle SSH connection errors
- ssh.on('error', (err) => {
- console.error(chalk.red('✗'), chalk.red('SSH 连接错误:'), err);
- rejectCommand(err);
- });
- });
- };
- const updateFiles = async () => {
- spinner.text = '正在链接服务器...';
-
- const config = {
- host: env.VITE_HOST,
- port: env.VITE_POST,
- username: env.VITE_USERNAME,
- password: env.VITE_PASSWORD
- };
- const sftp = new SftpClient();
- if (!fs.existsSync('dist')) {
- console.error(chalk.red('✗'), chalk.red('构建目录不存在'));
- spinner.fail('构建目录不存在');
- spinner.stopAndPersist({ text: '' }); // 停止并清理 spinner
- return;
- }
- try {
- await sftp.connect(config);
- spinner.succeed(chalk.green('服务器连接成功'));
- console.log(chalk.blue('→'), chalk.gray('开始上传文件...'));
- await sftp.uploadDir('dist_6888', '/data/seo-vue/dist');
- console.log(chalk.green('✓'), chalk.blue('6888端口文件上传完成'));
- await sftp.uploadDir('dist_16888', '/data/seo-vue-test/dist');
- console.log(chalk.green('✓'), chalk.blue('16888端口文件上传完成'));
-
- sftp.end();
- console.log(createSeparator('开始部署 6888 端口'));
- await command([
- "cd /data/seo-vue && pwd",
- "cd /data/seo-vue && ls -la",
- "cd /data/seo-vue && docker rm -f seo-vue",
- "cd /data/seo-vue && docker rmi seo-vue",
- "cd /data/seo-vue && docker build -t seo-vue .",
- "cd /data/seo-vue && docker run -d --name seo-vue -p 6888:80 seo-vue"
- ], config, "6888");
- console.log(createSeparator('6888 端口部署完成'));
- console.log(createSeparator('开始部署 16888 端口'));
- await command([
- "cd /data/seo-vue-test && pwd",
- "cd /data/seo-vue-test && ls -la",
- "cd /data/seo-vue-test && docker rm -f seo-vue-test",
- "cd /data/seo-vue-test && docker rmi seo-vue-test",
- "cd /data/seo-vue-test && docker build -t seo-vue-test .",
- "cd /data/seo-vue-test && docker run -d --name seo-vue-test -p 16888:80 seo-vue-test"
- ], config, "16888");
- console.log(createSeparator('16888 端口部署完成'));
-
- } catch (err) {
- console.error(chalk.red('✗'), chalk.red('部署或文件上传失败:'), err);
- spinner.fail(err);
- spinner.stopAndPersist({ text: '' }); // 停止并清理 spinner
- sftp.end();
- throw err; // 向上抛出错误,让main函数捕获并处理退出
- }
- };
- // 项目构建
- console.log(createSeparator('开始构建项目'));
- // 使用 Promise 包装构建过程
- const buildWithProgress = (port, apiUrl) => {
- return new Promise((resolve, reject) => {
- console.log(chalk.blue(`\n构建 ${port} 端口版本`));
- console.log(chalk.gray(`API 地址: ${apiUrl}`));
- updateProxyConfig(apiUrl);
- // 重置进度条
- speed = 20;
- time = 20;
- spinner.start(); // 确保每次构建前启动 spinner
- const buildProcess = exec('npm run build');
- let buildOutput = '';
- buildProcess.stdout.on('data', (data) => {
- buildOutput += data;
- // 根据输出更新进度
- if (data.includes('compiled successfully')) {
- speed = count;
- }
- });
- buildProcess.stderr.on('data', (data) => {
- buildOutput += data;
- });
- buildProcess.on('close', async (code) => {
- if (code === 0) {
- // 构建成功后,将dist目录复制到对应的端口目录
- const distDir = 'dist';
- const targetDir = `dist_${port}`;
- try {
- // 如果目标目录已存在,先删除
- if (fs.existsSync(targetDir)) {
- fs.rmSync(targetDir, { recursive: true, force: true });
- }
- // 复制dist目录到目标目录
- fs.cpSync(distDir, targetDir, { recursive: true });
- console.log(chalk.green('✓'), chalk.blue(`已保存 ${port} 端口构建文件到 ${targetDir}`));
- } catch (err) {
- console.error(chalk.red('✗'), chalk.red(`保存构建文件失败: ${err}`));
- reject(err);
- return;
- }
- spinner.succeed(chalk.green(`构建成功`));
- resolve(buildOutput);
- } else {
- spinner.fail(chalk.red(`构建失败`));
- reject(new Error(buildOutput));
- }
- });
- });
- };
- // 主构建流程
- const main = async () => {
- spinner.start(); // 在主流程开始时启动 spinner,用于总体的进度指示
- try {
- const envPath = '.env';
- const content = fs.readFileSync(envPath, 'utf-8');
- const match = content.match(/VITE_API_URL = (.*)/);
- if (match && match[1]) {
- originalApiUrl = match[1];
- } else {
- console.warn(chalk.yellow('!'), chalk.yellow('无法从 env 中读取原始 VITE_API_URL,可能无法还原。'));
- }
- // 构建 6888 端口版本
- await buildWithProgress('6888', 'http://192.168.10.101:9999');
- // 构建 16888 端口版本
- await buildWithProgress('16888', 'http://192.168.3.17:9999');
- // 开始部署
- await updateFiles();
- // 所有任务完成后,打印最终访问路径并停止 spinner
- clearInterval(interval); // 在所有任务完成后停止进度条动画
- console.log(createSeparator('部署完成'));
- console.log(chalk.green('🎉'), chalk.cyan('所有环境部署成功!'));
- console.log(chalk.green('🚀'), chalk.cyan(`6888 端口访问地址: http://${env.VITE_HOST}:6888`));
- console.log(chalk.green('🚀'), chalk.cyan(`16888 端口访问地址: http://${env.VITE_HOST}:16888`));
- // 还原 VITE_API_URL 到原始值
- if (originalApiUrl) {
- console.log(createSeparator('还原配置'));
- updateProxyConfig(originalApiUrl);
- } else {
- console.warn(chalk.yellow('!'), chalk.yellow('未找到原始 VITE_API_URL,跳过还原。'));
- }
-
- spinner.stopAndPersist({ text: '' }); // 停止并清理 spinner,返回控制台
- process.exit(0); // 正常退出,返回控制台
- } catch (error) {
- console.error(chalk.red('✗'), chalk.red('发布流程出错:'), error);
- spinner.fail(error);
- spinner.stopAndPersist({ text: '' }); // 停止并清理 spinner,返回控制台
- process.exit(1); // 异常退出
- }};
- // 启动主流程
- main();
|