关于使用 Node.js 来辅助进行 CI/CD 的一些想法

2023-12-11 21:02:38 浏览数 (2)

  由于使用到的 CI/CD 工具可能会更换,对应的学习成本也相应增加,但是 Node.js 其实可以帮助我们实现这些工具的大部分功能,包括操作文件、执行 cmd 等等。   所以我们如果把大部分的打包或集成操作使用 Node.js 去实现,那么无论工具如何更换,我们只需学习如何使用该工具执行 npm 即可,从而大大降低迁移与学习成本。   当然这只是我最近迁移时的一些解决方案与想法,如果有大佬指教一些其他的方式,那自然是更好啦哈哈哈~

Flutter 根据安卓版本打包 Demo

代码

代码语言:javascript复制
const fs = require('fs');
const nodeCmd = require('node-cmd');

let arguments = process.argv.slice(2); // 获取命令行传入参数
let targetBranch = arguments[0] || 11; // 需要打包的对应安卓版本
let shouldBuild = arguments[1] == undefined ? true : arguments[1] == 'true'; // 是否需要打包
let menusStation = arguments[2] == undefined ? 'changzhou' : arguments[2]; // 菜单地址
let filePath = arguments[3] || './pubspec.yaml'; // pubspec 配置文件位置
let buildFilePath = arguments[4] || './android/app/build.gradle'; // build.gradle 配置文件位置
let menusConfigPath = arguments[5] || './lib/configuration/menus.dart'; // menus.dart 配置文件位置

console.table({ arguments, targetBranch, shouldBuild, filePath, buildFilePath });

/// 读取对应的安卓目录配置文件
fs.readFile(buildFilePath, 'utf8', function (err, data) {
  if (err) return console.log(`%c出错啦!${data}`, 'color:red;');

  let result = data.replace(/minSdkVersionsd{2}/g, `minSdkVersion ${targetBranch == 11 ? 26 : 21}`);

  console.log('正在修改安卓 build 配置文件......');

  fs.writeFile(buildFilePath, result, 'utf8', function (err) {
    if (err) return console.log(`%c出错啦!${data}`, 'color:red;');

    setMenus();
  });
});

/// 设置对应版本的菜单
function setMenus() {
  fs.readFile(menusConfigPath, 'utf8', function (err, data) {
    if (err) return console.log(`%c出错啦!${data}`, 'color:red;');

    let result = data.replace(
      /staticsListsmenuLists=sX_MENU_w ;/g,
      `static List menuList = X_MENU_${menusStation};`
    );

    console.log(`正在修改菜单 build 配置文件 [X_MENU_${menusStation}]......`);

    fs.writeFile(menusConfigPath, result, 'utf8', function (err) {
      if (err) return console.log(`%c出错啦!${data}`, 'color:red;');

      buildStart();
    });
  });
}

/// 开始打包
function buildStart() {
  fs.readFile(filePath, 'utf8', function (err, data) {
    if (err) return console.log(`%c出错啦!${data}`, 'color:red;');

    let result = data.replace(/_android_d{1,2}_scan/g, `_android_${targetBranch}_scan`); // 修改对呀安卓版本的 SDK 插件版本

    console.log('正在修改 Flutter 配置文件......');

    fs.writeFile(filePath, result, 'utf8', function (err) {
      if (err) return console.log(`%c出错啦!${data}`, 'color:red;');

      getFlutterPackages();
    });
  });
}

/// 获取 flutter 相关插件
function getFlutterPackages() {
  console.log('执行下载命令 flutter pub get');

  nodeCmd.run('flutter pub get', (err, data, stderr) => {
    if (err) return console.log(`%c出错啦!${data}`, 'color:red;');

    shouldBuild && buildFlutterApk();
  });
}

/// 打包
function buildFlutterApk() {
  console.log('打包中... flutter build apk --target-platform android-arm64');

  nodeCmd.run('flutter build apk --target-platform android-arm64', (err, data, stderr) => {
    if (err) return console.log(`%c出错啦!${data}`, 'color:red;');

    console.log(`%c${data} %c成功,请检查 apk 文件!`, 'color:green;', 'color:chocolate;');
  });
}



// 这样的话,我们只需要每次切换 CI/CD 工具时,学会使用 node 执行这个脚本即可。

其他

  • 以上脚本既可以用于本地打包,也可以设置在推送时自动运行。
  • 另外如果我们还要集成到服务端的不同目录,也可以使用 Node.js 去实现文件复制或者移动。
  • 如果需要在 commit 或者 push 前进行一些操作,我们还可以使用 package.json-scripts 定义一些钩子来实现。
代码语言:javascript复制
prepublish: 在包发布之前运行,也会在 npm install 安装到本地时运行。
publish,postpublish: 包被发布之后运行
preinstall: 包被安装前运行
install,postinstall: 包被安装后运行
preuninstall,uninstall: 包被卸载前运行
postuninstall: 包被卸载后运行
preversion: bump 包版本前运行
postversion: bump 包版本后运行
pretest,test,posttest: 通过 npm test 命令运行
prestop,stop,poststop: 通过 npm stop 命令运行
prestart,start,poststart: 通过 npm start 命令运行
prerestart,restart,postrestart: 通过 npm restart 运行

需要复制指定目录提交到某个仓库的 Demo

代码

代码语言:javascript复制
const path = require('path');
const fs = require('fs');
const isLocalPublish = process.argv[2] === 'true';
const targetDir = process.argv[3] ?? './test';
const pkg = require(path.resolve('package.json'));
const nodeCmd = require('node-cmd');

// git clone url[请设置带 token 的地址,或者先设置 SSH。]
// 将对外目录 git 仓库拉取到本地

/**
 * 判断目录是否存在,不存在则创建目录。
 */
const isDirExist = (path) => {
  // fs.access(path, function (err) {
  //   if (err) {
  //     // 目录不存在时创建目录
  //     fs.mkdirSync(path);
  //   }
  // });
  if (!fs.existsSync(path)) {
    fs.mkdirSync(path);
  }
};

/*
 * 复制目录、子目录,及其中的文件。
 * @param src {String} 要复制的目录
 * @param target {String} 复制到目标目录
 */
const copyDir = (err, src, target) => {
  if (err) {
    console.log({ 'copyDir error': err });
    return;
  }
  
  fs.readdir(src, function (err, paths) {
    if (err) {
      console.log({ 'copyDir error': err });
      return;
    }
  
    paths.forEach(function (path) {
      let _src = src   '/'   path;
      let _target = target   '/'   path;
      fs.stat(_src, function (err, stat) {
        if (err) {
          console.log({ 'copyDir error': err });
          return;
        }
    
        // 判断是文件还是目录
        if (stat.isFile()) {
          fs.writeFileSync(_target, fs.readFileSync(_src));
        } else if (stat.isDirectory()) {
          // 当是目录是,递归复制。
          isDirExist(_target);
          copyDir(null, _src, _target);
        }
      });
    });
  });
};

/**
 * 复制文件
 * @param {*} src
 * @param {*} target
 */
const copyFile = (src, target) => {
  try {
    fs.writeFileSync(target, fs.readFileSync(src));
  } catch (e) {
    console.log({ 'copyFile error': e });
    try {
      fs.createReadStream(src).pipe(fs.createWriteStream(target)); // 大文件复制
    } catch (err) {
      console.log({ 'copyBigFile error': err });
    }
  }
};

/**
 * 获取发布的所有对外文件
 * @param {*} targetDir
 */
const getSubmitFiles = (targetDir) => {
  isDirExist(targetDir);

  ['dir1', 'dir2', 'dir3'].forEach(function (srcDir) {
    let copyTargetDir = `${targetDir}/${srcDir}`;
    isDirExist(copyTargetDir);
    copyDir(null, srcDir, copyTargetDir);
  });

  ['package.json', 'README.md', 'README.en.md'].forEach(function (file) {
    copyFile(file, `${targetDir}/${file}`);
  });

  publishPackage();
};

/**
 * 发布包
 */
const publishPackage = () => {
  const dateObj = new Date();
  const versionNo = `${dateObj.getMilliseconds()}${dateObj.getSeconds()}${dateObj.getMinutes()}${dateObj.getHours()}${dateObj.getDate()}${
    dateObj.getMonth()   1
  }${dateObj.getFullYear()}`;
  const versionId = `(${pkg.version}) ${versionNo}`;
  
  nodeCmd.run(
    `cd ${targetDir} && git checkout main && git config --global user.name "doubleam" && git config --global user.email "admin@biugle.cn" && git status && git add -A . && git commit -m "Auto publish, Version ${versionId}." && git push origin main -f`,
    (err, data, stderr) => {
      if (err) return console.log(`%c 提交出错啦!${data}`, 'color:red;');

      if (isLocalPublish) {
        // 需要有 .npmrc 文件,才可自动登录并执行 npm publish。或者设置 CI/CD 专用 token。 https://docs.npmjs.com/using-private-packages-in-a-ci-cd-workflow
        nodeCmd.run('npm publish', (err, data, stderr) => {
          if (err) return console.log(`%c 发布版本出错啦!${data}`, 'color:red;');
      
          console.log(data);
          nodeCmd.run(`cd ../ && rimraf ${targetDir}`, (err, data, stderr) => {
            if (err) return console.log(`%c 删除文档出错啦!${data}`, 'color:red;');
      
            console.log(data);
          });
        });
      }
    }
  );
};

getSubmitFiles(targetDir);

其他

  • 适用于我们部分源码不方便公开的情况,我们可以设置一个目录来暴露生产包,但是保留源码的私有性。
  • 为了简化命令参数,我们可以预先写好放到 package.json-scripts 中去,方便直接使用 npm run xxx 执行。
  • 以上内容仅供参考 (0.0)

0 人点赞