uniapp自动化打包

958 阅读3分钟
uniapp自动化打包解决每次打包需要输入安卓证书密码和ios密码

自动化打包链接:hx.dcloud.net.cn/cli/pack

在项目根目录创建app-pack.json文件

文件内容配置

{
  //项目名字或项目绝对路径
  "project": "test-pack",
  //打包平台 默认值android  值有"android","ios" 如果要打多个逗号隔开打包平台
  "platform": "ios,android",
  //是否使用自定义基座 默认值false  true自定义基座 false自定义证书
  "iscustom": false,
  //打包方式是否为安心打包默认值false,true安心打包,false传统打包
  "safemode": false,
  //android打包参数
  "android": {
    //安卓包名
    "packagename": "com.test.android",
    //安卓打包类型 默认值0 0 使用自有证书 1 使用公共证书 2 使用老版证书
    "androidpacktype": "1",
    //安卓使用自有证书自有打包证书参数
    //安卓打包证书别名,自有证书打包填写的参数
    "certalias": "",
    //安卓打包证书文件路径,自有证书打包填写的参数
    "certfile": "",
    //安卓打包证书密码,自有证书打包填写的参数
    "certpassword": "",
    //安卓平台要打的渠道包 取值有"google","yyb","360","huawei","xiaomi","oppo","vivo",如果要打多个逗号隔开
    "channels": ""
  },
  //ios打包参数
  "ios": {
    //ios appid
    "bundle": "com.test.ios",
    //ios打包支持的设备类型 默认值iPhone 值有"iPhone","iPad" 如果要打多个逗号隔开打包平台
    "supporteddevice": "iPhone,iPad",
    //iOS使用自定义证书打包的profile文件路径
    "profile": "",
    //iOS使用自定义证书打包的p12文件路径
    "certfile": "",
    //iOS使用自定义证书打包的证书密码
    "certpassword": "123"
  },
  //是否混淆 true混淆 false关闭
  "isconfusion": false,
  //开屏广告 true打开 false关闭
  "splashads": false,
  //悬浮红包广告true打开 false关闭
  "rpads": false,
  //push广告 true打开 false关闭
  "pushads": false,
  //加入换量联盟 true加入 false不加入
  "exchange": false
}

文件存放证书路径

image.png

配置完成之后在package.json里面配置

image.png

iconv-lite 库来处理编码问题

pnpm install iconv-lite -D

在项目根目录创建shell文件夹build_app.js文件,以下是js全部代码

const fs = require('fs');
const path = require('path');
const { execSync, spawn } = require('child_process');
const os = require('os');
const iconv = require('iconv-lite');

// 获取命令行参数或环境变量
const HBUILDERX_DIR = process.argv[2] || process.env.HBUILDERX_DIR;
// 如果不传递参数,获取当前路径,进行打包
const PROJECT_DIR = process.argv[3] || process.cwd();
if (!HBUILDERX_DIR) {
  console.error('HBuilderX 的安装目录未设置,也未能自动检测。请传递参数或设置 HBUILDERX_DIR 环境变量。');
  process.exit(1);
}

if (!PROJECT_DIR) {
  console.error('项目目录未设置。请传递参数或设置 PROJECT_DIR 环境变量。');
  process.exit(1);
}

const CONFIG_FILE = path.join(PROJECT_DIR, 'app-pack.json');
const MANIFEST_FILE = path.join(PROJECT_DIR, 'manifest.config.js');
const BACKUP_MANIFEST_FILE = path.join(PROJECT_DIR, 'manifest.config.js.bak');
// 更新 versionName 和 versionCode
console.log('更新 versionName 和 versionCode...');
if (fs.existsSync(MANIFEST_FILE)) {
  // 备份 manifest.config.js 文件
  fs.copyFileSync(MANIFEST_FILE, BACKUP_MANIFEST_FILE);
  console.log(`已创建 manifest.config.js 的备份文件: ${BACKUP_MANIFEST_FILE}`);

  const manifestContent = fs.readFileSync(MANIFEST_FILE, 'utf8');
  const versionNameMatch = manifestContent.match(/versionName:\s*'(\d+.\d+.\d+)'/);
  const versionCodeMatch = manifestContent.match(/versionCode:\s*'(\d+)'/);

  if (!versionNameMatch || !versionCodeMatch) {
   console.error('未能找到 versionName 或 versionCode,请检查 manifest.config.js 文件。');
   process.exit(1);
  }

  let [major, minor, patch] = versionNameMatch[1].split('.').map(Number);
  let versionCode = parseInt(versionCodeMatch[1], 10);

  // Increment patch version and handle overflow
  patch += 1;
  if (patch >= 10) {
   patch = 0;
   minor += 1;
   if (minor >= 10) {
    minor = 0;
    major += 1;
   }
  }

  // Increment versionCode
  versionCode += 1;

  const newVersionName = `${major}.${minor}.${patch}`;
  const newVersionCode = versionCode.toString().padStart(3, '0');

  const newManifestContent = manifestContent
   .replace(/versionName:\s*'\d+.\d+.\d+'/, `versionName: '${newVersionName}'`)
   .replace(/versionCode:\s*'\d+'/, `versionCode: '${newVersionCode}'`);

  fs.writeFileSync(MANIFEST_FILE, newManifestContent);

  // 读取并解析 JSON 文件(移除注释)
  const readJSONFile = (filePath) => {
   const content = fs.readFileSync(filePath, 'utf8');
   const jsonContent = content.replace(///.*|/*[\s\S]*?*//g, '');
   return JSON.parse(jsonContent);
  };

  // 读取配置文件并解析
  const config = readJSONFile(CONFIG_FILE);

  // 将相对路径转换为绝对路径
  config.android.certfile = path.resolve(PROJECT_DIR, config.android.certfile);
  config.ios.profile = path.resolve(PROJECT_DIR, config.ios.profile);
  config.ios.certfile = path.resolve(PROJECT_DIR, config.ios.certfile);

  // 更新配置文件以确保路径正确
  fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));

  console.log(`versionName 更新为 ${newVersionName},versionCode 更新为 ${newVersionCode}。`);
} else {
  console.error('未找到 manifest.config.js 文件,请检查路径。');
  process.exit(1);
}
// 启动 HBuilderX
console.log(`启动 HBuilderX (${HBUILDERX_DIR})...`);
execSync(`"${path.join(HBUILDERX_DIR, 'cli.exe')}" open`, {
  stdio: 'inherit',
  env: { ...process.env, PYTHONIOENCODING: 'UTF-8' },
});

const restoreBackup = () => {
  if (fs.existsSync(BACKUP_MANIFEST_FILE)) {
   fs.copyFileSync(BACKUP_MANIFEST_FILE, MANIFEST_FILE);
   console.log('已恢复 manifest.config.js 的备份文件。');
  }
};

// 等待 HBuilderX 启动
setTimeout(() => {
  try {
   if (!fs.existsSync(CONFIG_FILE)) {
    console.error(`配置文件 ${CONFIG_FILE} 不存在,请检查路径。`);
    process.exit(1);
   }
   console.log(CONFIG_FILE);
   console.log('开始打包...');
   const command = `"${path.join(HBUILDERX_DIR, 'cli.exe')}" pack --config ${CONFIG_FILE}`;

   const childProcess = spawn(command, { shell: true });

   childProcess.stdout.on('data', (data) => {
    console.log(`stdout: ${iconv.decode(data, 'gbk')}`);
   });

   childProcess.stderr.on('data', (data) => {
    console.log(`stdout: ${iconv.decode(data, 'gbk')}`);
   });

   childProcess.on('close', (code) => {
    if (code !== 0) {
     console.log(`打包失败,退出码 ${code}`);
     restoreBackup();
    }
    console.log(`子进程退出,退出码 ${code}`);
   });
  } catch (e) {
   restoreBackup();
  }
}, 8000);

使用方法

  • 复制HBuilder X文件地址

image.png

  • 在控制台输入命令

image.png

-启动命令

image.png