IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    前端自动化:Node 命令行前端自动构建发布系统

    zuley发表于 2022-04-03 14:40:46
    love 0

    目前就我所呆的公司来说,前端的发版都是开发完之后执行编译,然后通过 ftp 上传到服务器中。项目多起来之后,加上测试环境和正式环境的分离,导致管理混乱。而且整个流程也很麻烦,要一步步手动去做。

    所以一直就有一个想法,能不能做一个像 Vue Cli 一样的自动化工具,可以通过命令输入命令和选择选项,进行自动化的编译和发版。说干就干立马就开发了一个 ~~~

    GitHub 地址: https://github.com/zuley/zuley-cli

    一、技术栈

    • chalk 美化命令行,进行着色等
    • commander 解析用户命令行输入
    • inquirer 命令行交互功能,像用户提问等。
    • node-ssh ssh 模块
    • ora 命令行环境的 loading 效果
    • shelljs 重新包装了 child_process 子进程模块,调用系统命令更方便。

    二、创建项目

    1. 直接使用 npm init 创建一个空项目
    2. 在根目录创建一个不带后缀的系统文件 app,作为主入口文件。

    三、简单命令行示例

    在入口文件中输入以下代码

    const program = require('commander')
    const inquirer = require('inquirer')
    const chalk = require('chalk')
    program
      .command('module')
      .alias('m')
      .description('创建新的模块')
      .option('-n, --name [moduleName]', '模块名称')
      .action(option => {
        console.log('Hello World', option.name)
      })
        
    program.parse(process.argv)
    复制代码

    执行看效果

    $ node app m -n zuley // 输出:Hello World zuley
    复制代码

    四、以全局方式运行

    上面的示例输入起来太麻烦,需要进入项目所在目录才能执行文件,现在我们需要一个简单的方式 zuley m -n zuley

    1、配置 package.json 中的 bin 字段

    "bin": {
        "zuley": "app"
    }
    复制代码

    2、注册全局命令

    然后执行注册符号链接,它会把 zuley 这个字段复制到 npm 全局模块安装文件夹 node_modules 内,也就是将 zuley 的路径加入环境变量 PATH 中。

    如果是MAC,则需要加上 sudo 前缀,使用管理员权限。

    本文中所有使用了 sudo 的地方,均是MAC系统限制,win用户需要删除此句。

    评论有人反馈说不加 sudo 也可以,可以先尝试不加,如果有报错再加。

    $ npm link
    
    # or mac
    $ sudo npm link
    复制代码

    3、声明为可执行应用

    在入口文件的最上方加入声明,声明这是一个可执行的应用。

    #! /usr/bin/env node
    
    ...code
    复制代码

    四、执行命令

    $ zuley m -n zuley
    复制代码

    五、项目架构

    实际开发中,我们以 src为源码目录,自动发布系统做为其中的一个模块,放置在模块目录中。

    |-/src/ // 源码目录
    |---/modules/ 模块目录
    |-----/Automation/ // 发布模块目录
    |-------index.js // 模块入口文件
    |-------config.promps.js  // 选项配置文件
    |-------config.service.js // 服务器配置文件
    |-app  // 入口文件
    复制代码

    六、在入口文件 app 中载入自动发布系统模块

    #! /usr/bin/env node
    
    // 导入自动化任务模块
    require('./src/modules/Automation/index')
    复制代码

    七、编写模块入口功能

    编写模块入口文件 /src/modules/Automation/index.js

    1、命令行询问选项

    执行命令之后,我们需要提供命令行界面,让用户输入或者选择选项来确定接下来的操作。这里用到了 inquirer 包。

    具体用例请去 npm 或者自行搜索学习。

    以下是代码片段

    // 导入选项配置
    const prompsConfig = require('./config.promps')
    
    // 项目名称
    let { name } = await inquirer.prompt(prompsConfig.name)
    // 项目渠道
    let source = ''
    if (prompsConfig.source[name].length > 0) {
      source = await inquirer.prompt(prompsConfig.source[name])
      source = source.source
    }
    // 项目环境
    let { type } = await inquirer.prompt(prompsConfig.type)
        
    // 确认选项
    log('请确认你选择了以下选项')
    log(chalk.green('项目名称:') + chalk.red(TEXTDATA[name]))
    log(chalk.green('项目渠道:') + chalk.red(TEXTDATA[source]))
    log(chalk.green('项目环境:') + chalk.red(TEXTDATA[type]))
    复制代码

    2、执行shell命令编译项目

    这里使用了 shelljs 包。shelljs 重新包装了 child_process 子进程模块,调用系统命令更方便。

    本文中所有使用了 sudo 的地方,均是MAC系统限制,win用户需要删除此句。

    以下是代码片段

    async function compile (config, type) {
      // 进入项目本地目录
      shell.cd(config.localPath)
      if (type === 'TEST') {
        log('测试环境编译')
        shell.exec(`sudo npm run test`)
      } else {
        log('正式环境编译')
        shell.exec(`npm run build`)
      }
      log('编译完成')
    }
    复制代码

    3、上传文件

    上传文件使用了 node-ssh包,该包封装了一些简单易用的方法,具体可以查询官网或者搜索教程。

    以下是代码片段

    /**
     * 上传文件
     * @param {Object} config 项目配置
     */
    async function updateFile (config) {
      // 存储失败序列
      let failed = []
      // 存储成功序列
      let successful = []
      let spinner = ora('准备上传文件').start()
      // 上传文件夹
      let status = await ssh.putDirectory(config.localPath + '/dist', config.remotePath, {
        // 递归
        recursive: true,
        // 并发数
        concurrency: 10,
        tick (localPath, remotePath, error) {
          if (error) {
            failed.push(localPath)
          } else {
            spinner.text = '正在上传文件:' + localPath
            successful.push(localPath)
          }
        }
      })
      spinner.stop()
      if (status) { 
        log(chalk.green('完成上传'))
      } else {
        log(chalk.red('上传失败'))
      }
      if (failed.length > 0) {
        log(`一共有${chalk.red(failed.length)}个上传失败的文件`)
        log(failed)
      }
    }
    复制代码

    本例只做简单的演示,实际应用还需要扩展上传失败的处理。比如断点续传,失败文件续传等。

    而且单个上传会很慢,可以先运行命令压缩后再上传,再执行下服务器命令解压文件。

    如果上传失败,检查远程目录是否有权限,使用命令修改权限。

    $ chmod -R 777 [目录]
    复制代码

    4、源码

    1、index.js

    const program = require('commander')
    const inquirer = require('inquirer')
    const chalk = require('chalk')
    const ora = require('ora')
    const shell = require('shelljs')
    const node_ssh = require('node-ssh')
    const ssh = new node_ssh()
    
    // 导入选项配置
    const prompsConfig = require('./config.promps')
    // 导入服务器配置
    const serviceConfig = require('./config.service')
    const log = console.log
    
    // 字段字典
    const TEXTDATA = {
      'A': '项目A',
      'B': '项目B',
      'PC': 'PC 网站',
      'WX': '微信公众号',
      'TEST': '测试环境',
      'PROD': '正式环境',
      '': '其他'
    }
    
    // 添加一个名字为 a 别名为 automation 的命令模块
    program
      .command('a')
      .alias('automation')
      .description('前端自动化发布系统')
      .action(async option => {
        // 项目名称
        let { name } = await inquirer.prompt(prompsConfig.name)
        // 项目渠道
        let source = ''
        if (prompsConfig.source[name].length > 0) {
          source = await inquirer.prompt(prompsConfig.source[name])
          source = source.source
        }
        // 项目环境
        let { type } = await inquirer.prompt(prompsConfig.type)
        
        // 确认选项
        log('请确认你选择了以下选项')
        log(chalk.green('项目名称:') + chalk.red(TEXTDATA[name]))
        log(chalk.green('项目渠道:') + chalk.red(TEXTDATA[source]))
        log(chalk.green('项目环境:') + chalk.red(TEXTDATA[type]))
        
        // 获取配置
        let config = serviceConfig[`${name}-${source}-${type}`]
    
        log(`使用服务器配置:${name}-${source}-${type}`)
    
        // 编译项目
        compile(config, type)
    
        // 连接服务器
        await ConnectService(config)
    
        // 上传文件
        await updateFile(config)
      })
    
    program.parse(process.argv)
    
    /**
     * 连接服务器
     * @param {Object} config 项目配置
     */
    async function ConnectService (config) {
      log('尝试连接服务:' + chalk.red(config.service.host))
      let spinner = ora('正在连接')
      spinner.start()
      await ssh.connect(config.service)
      spinner.stop()
      log(chalk.green('成功连接到服务器'))
    }
    
    /**
     * 上传文件
     * @param {Object} config 项目配置
     */
    async function updateFile (config) {
      // 存储失败序列
      let failed = []
      // 存储成功序列
      let successful = []
      let spinner = ora('准备上传文件').start()
      // 上传文件夹
      let status = await ssh.putDirectory(config.localPath + '/dist', config.remotePath, {
        // 递归
        recursive: true,
        // 并发数
        concurrency: 10,
        tick (localPath, remotePath, error) {
          if (error) {
            failed.push(localPath)
          } else {
            spinner.text = '正在上传文件:' + localPath
            successful.push(localPath)
          }
        }
      })
      spinner.stop()
      if (status) { 
        log(chalk.green('完成上传'))
      } else {
        log(chalk.red('上传失败'))
      }
      if (failed.length > 0) {
        log(`一共有${chalk.red(failed.length)}个上传失败的文件`)
        log(failed)
      }
    }
    
    /**
     * 编译源码
     * @param {Object} config 项目配置
     * @param {String} type 编译类型 TEST or PROD
     */
    async function compile (config, type) {
      // 进入项目本地目录
      shell.cd(config.localPath)
      if (type === 'TEST') {
        log('测试环境编译')
        shell.exec(`sudo npm run test`)
      } else {
        log('正式环境编译')
        shell.exec(`sudo npm run build`)
      }
      log('编译完成')
    }
    复制代码

    2、选项配置源码:config.promps.js

    此文件配置用户在命令行中输入或者选择的数据,供后续拼接生成 A-WX-TEST 类的字段。

    A-WX-TEST 代表用户选择了:项目A-微信-测试环境

    程序将通过此拼接字段,去 config.service.js 中获取项目配置

    /**
     * 自动化模块 - 选项配置文件
     */
    module.exports = {
      // 项目名字
      name: [
        {
          type: 'list',
          name: 'name',
          message: '请选择要发布的项目',
          choices: [
            {
              name: '项目A',
              value: 'A'
            },
            {
              name: '项目B',
              value: 'B'
            }
          ]
        }
      ],
      // 项目渠道
      source: {
        'A': [
          {
            type: 'list',
            name: 'source',
            message: '请选择要发布的渠道',
            choices: [
              {
                name: 'PC网站',
                value: 'PC'
              },
              {
                name: '微信',
                value: 'WX'
              }
            ]
          }
        ],
        'B': [
          {
            type: 'list',
            name: 'source',
            message: '请选择要发布的渠道',
            choices: [
              {
                name: 'PC网站',
                value: 'PC'
              },
              {
                name: '微信',
                value: 'WX'
              }
            ]
          }
        ]
      },
      // 项目环境
      type: [
        {
          type: 'list',
          name: 'type',
          message: '请选择发布环境',
          choices: [
            {
              name: '测试环境',
              value: 'TEST'
            },
            {
              name: '正式环境',
              value: 'PROD'
            }
          ]
        }
      ]
    }
    复制代码

    3、服务器配置源码:config.service.js

    服务器配置只配置了两个作为演示,实际看现实情况补充。

    在 GitHub 上拉取项目测试的时候,记得一定要修改次文件。

    1. 修改服务器为自己的服务器和ssh账号密码
    2. 修改项目的本地目录和远程目录
    3. A-WX-TEST 这个字段代表用户输入的选项,具体看 config.promps.js
    // 服务器 A
    const serviceA = {
      // 服务器 IP
      host: 'xxx.xxx.xxx.xxx',
      // ssh 账号
      username: 'xxx',
      // ss 密码
      password: 'xxxxxx'
    }
    
    module.exports = {
      // 项目A,微信测试环境
      'A-WX-TEST': {
        service: serviceA,
        // 本地目录
        localPath: '/Users/zuley/ChuDaoNew/minshengjingwu-h5',
        // 远程目录
        remotePath: '/root/html/test'
      },
      // 项目A,微信正式环境
      'A-WX-PROD': {
        service: serviceA,
        // 本地目录
        localPath: '/Users/zuley/ChuDaoNew/minshengjingwu-h5',
        // 远程目录
        remotePath: '/root/html/prod'
      }
    }
    
    复制代码

    八、执行自动化发布

    执行以下命令即可启动自动化

    $ zuley a
    复制代码

    输入有误想终止命令可输入

    $ Ctrl+C
    复制代码

    九、参考文章

    GitHub 地址: https://github.com/zuley/zuley-cli

    跟着老司机玩转Node命令行

    chalk:美化命令行,进行着色

    commander:解析用户命令行输入

    inquirer:命令行交互功能,像用户提问等

    node-ssh:ssh 模块

    ora:命令行环境的 loading 效果

    shelljs:更方便的调用系统命令



沪ICP备19023445号-2号
友情链接