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
}
文件存放证书路径
配置完成之后在package.json里面配置
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文件地址
- 在控制台输入命令
-启动命令