publish-apk 0.0.1-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +795 -0
- package/package.json +40 -0
- package/src/apk.js +694 -0
- package/src/cert.js +1468 -0
- package/src/cli.js +185 -0
- package/src/google.js +1884 -0
- package/src/hap.js +939 -0
- package/src/harmony-material.js +174 -0
- package/src/help.js +50 -0
- package/src/huawei-signing.js +1279 -0
- package/src/huawei.js +3295 -0
- package/src/jwt.js +161 -0
- package/src/profile.js +753 -0
- package/src/publish-apk.js +161 -0
package/README.md
ADDED
|
@@ -0,0 +1,795 @@
|
|
|
1
|
+
# publish-apk
|
|
2
|
+
|
|
3
|
+
发布 APP 的命令行工具:为应用市场**获取鉴权令牌**、**上传安装包**并**登记软件包 / 发布版本**,供上传/发布流水线使用。
|
|
4
|
+
当前支持华为应用市场(AppGallery Connect / AGC Connect API)与 Google Play(Android Publisher API v3)。
|
|
5
|
+
另提供一个与厂商无关的 `cert` 命令(证书 / PKCS#12 密钥库)与 `profile` 命令(P7B Provisioning Profile)。
|
|
6
|
+
华为侧另有 `huawei signing-config`,用 `.cer` / `.p7b` / `.p12` 与明文口令生成 `build-profile.json5` 的签名配置。
|
|
7
|
+
|
|
8
|
+
- 纯 Node.js 实现,**无任何 npm 第三方依赖**(Node 内置模块;查看 PKCS#12 时调用系统 `openssl`)
|
|
9
|
+
- `stdout` 只写结果、`stderr` 只写日志与错误,便于 `TOKEN=$(publish-apk …)` 与管道取用
|
|
10
|
+
- 厂商隔离:根命令不认识任何厂商,华为相关实现全部在 [src/huawei.js](src/huawei.js),Google Play 相关实现全部在 [src/google.js](src/google.js);
|
|
11
|
+
与厂商无关的证书 / 密钥库查看在 [src/cert.js](src/cert.js),P7B profile 查看在 [src/profile.js](src/profile.js)
|
|
12
|
+
|
|
13
|
+
> 状态:`0.0.1-alpha.x` 早期版本,参数与输出可能调整,请以 `--help` 为准。
|
|
14
|
+
|
|
15
|
+
## 声明:与各应用商店无关
|
|
16
|
+
|
|
17
|
+
- 本项目是**个人/社区开源工具,不是华为、Google 官方产品**,与华为技术有限公司、Google LLC 及其关联公司**没有任何隶属、合作、赞助或背书关系**。
|
|
18
|
+
- 本项目与 Google Play、小米应用商店、OPPO 软件商店、vivo 应用商店等**任何应用商店/分发平台同样没有任何隶属、合作、赞助或背书关系**;支持某个平台只表示本工具能调用它的公开接口。
|
|
19
|
+
- "华为""HUAWEI""AppGallery""AppGallery Connect""AGC""Google""Google Play""Android"等是各自权利人的商标。本项目仅在**描述性意义**上使用这些名称(指明所调用的服务),不主张任何商标权利;[MIT 许可证](LICENSE) 也不授予任何商标使用权。
|
|
20
|
+
- 本项目调用的接口地址、路径、参数与错误码均来自厂商**公开文档**;帮助文档只做中文转述并给出原文链接,不复制厂商文档正文,见 [参考文档](#参考文档上游)。
|
|
21
|
+
|
|
22
|
+
## 免责声明与责任限制
|
|
23
|
+
|
|
24
|
+
- 本软件按**"现状"(AS IS)**提供,不附带任何明示或默示担保,包括但不限于适销性、特定用途适用性及非侵权担保。
|
|
25
|
+
- 本工具会使用**你提供的签名私钥 / 客户端密钥**调用厂商的接口。相关操作可能**直接影响线上发布**(创建版本、上传安装包、提交审核、发布到轨道等),部分操作**不可撤销**。请先在小号/测试应用上验证,并自行做好密钥管理与版本备份。
|
|
26
|
+
- 在任何情况下,作者或版权持有人均不对因使用或无法使用本软件而产生的任何索赔、损害或其他责任负责,无论其源自合同诉讼、侵权行为或其他方式。
|
|
27
|
+
- 使用本工具还需遵守厂商的开发者协议与平台规则(如华为开发者协议、AGC 平台规范、Google API 服务条款、Google Play 开发者计划政策)、以及适用的法律法规。相关合规责任由使用者自行承担。
|
|
28
|
+
- 本节与 [LICENSE](LICENSE) 中的条款共同适用;若两者冲突,以 [LICENSE](LICENSE) 为准。本节不构成法律意见。
|
|
29
|
+
|
|
30
|
+
## 环境要求
|
|
31
|
+
|
|
32
|
+
- Node.js **>= 18**(使用内置 `fetch` 与 `AbortSignal.timeout`)
|
|
33
|
+
- 查看 PKCS#12(如 `storeFile.p12`)需要系统 `openssl` 命令(OpenSSL 1.1.1+ / LibreSSL);其它功能只依赖 Node 内置模块
|
|
34
|
+
- `huawei signing-config` 的 keytool 自检是可选的:没有 JDK / keytool 时用 `--key-alias` 指定别名即可
|
|
35
|
+
|
|
36
|
+
## 安装
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# 全局安装
|
|
40
|
+
npm install -g publish-apk
|
|
41
|
+
|
|
42
|
+
# 或免安装直接运行
|
|
43
|
+
npx publish-apk huawei --help
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
仓库内开发时可直接执行:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
node src/publish-apk.js huawei --help
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## 命令
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
publish-apk # 根帮助
|
|
56
|
+
publish-apk -v # 版本号
|
|
57
|
+
publish-apk cert --file ./certpath.cer # 查看证书
|
|
58
|
+
publish-apk cert --file ./storeFile.p12 --password <密码> # 查看 PKCS#12 密钥库
|
|
59
|
+
publish-apk profile --file ./profile.p7b # 查看 P7B Provisioning Profile
|
|
60
|
+
publish-apk hap --file ./app-signed.hap # 查看 HAP 安装包信息
|
|
61
|
+
publish-apk hap --file ./*.hap # 一次查看多个 HAP
|
|
62
|
+
publish-apk app --file ./app-signed.app # 查看 APP 安装包信息
|
|
63
|
+
publish-apk app --file ./*.app # 一次查看多个 APP
|
|
64
|
+
publish-apk apk --file ./app-arm64-v8a-release.apk # 查看 APK 安装包信息
|
|
65
|
+
publish-apk apk --file ./*.apk # 一次查看多个 APK
|
|
66
|
+
publish-apk huawei # huawei 子命令帮助
|
|
67
|
+
publish-apk huawei <命令> --help # 某个子命令的帮助文档
|
|
68
|
+
publish-apk google # google 子命令帮助
|
|
69
|
+
publish-apk google <命令> --help # 某个子命令的帮助文档
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 为什么会有查看证书 / profile / HAP / APP / APK 的命令?
|
|
73
|
+
|
|
74
|
+
本项目的主流程是把手机 APP 上传到应用商店;`cert` / `profile` 是**签名材料的本地核对工具**,`hap` / `app` / `apk` 是**安装包的本地核对工具**,都不是上传流程中的一步。
|
|
75
|
+
|
|
76
|
+
华为鸿蒙 APP 在打包 / 签名时需要 `.cer`、`.p12`、`.p7b` 三类文件。同一开发者账号下往往有多套证书与 Profile,文件名、别名、Bundle、有效期、设备范围都很接近,容易拿错;`cert` / `profile` 把关键字段逐行列出,**主要用于区分多个鸿蒙证书 / Profile,确认当前签名配置用的是哪一份**。`hap` / `app` / `apk` 则用于核对构建产物本身,尤其是区分同名的 `signed.*` 与 `unsigned.*`:
|
|
77
|
+
|
|
78
|
+
| 文件 | 作用 | 查看方式 |
|
|
79
|
+
| --- | --- | --- |
|
|
80
|
+
| `.cer` | X.509 证书 / 证书链(如 `certpath.cer`) | `publish-apk cert --file ./certpath.cer` |
|
|
81
|
+
| `.p12` | PKCS#12 密钥库(如 `storeFile.p12`,含私钥与证书,需要密码) | `publish-apk cert --file ./storeFile.p12 --password <密码>` |
|
|
82
|
+
| `.p7b` | 鸿蒙 Provisioning Profile(Bundle、有效期、设备、权限、证书链) | `publish-apk profile --file ./profile.p7b` |
|
|
83
|
+
| `.hap` | 鸿蒙单模块安装包(包名、版本、模块、设备、API、签名状态) | `publish-apk hap --file ./app-signed.hap` |
|
|
84
|
+
| `.app` | 鸿蒙 App Pack(外层包信息、内层 HAP / HSP、签名状态) | `publish-apk app --file ./app-signed.app` |
|
|
85
|
+
| `.apk` | Android 安装包(包名、SDK、ABI、权限、签名证书) | `publish-apk apk --file ./app-arm64-v8a-release.apk` |
|
|
86
|
+
|
|
87
|
+
查看 `.p12` 不会输出私钥内容,`profile` 也不会输出证书 PEM 原文,`hap` / `app` / `apk` 只读包内信息与签名块头;这些命令只读取本地文件,不会访问应用商店。
|
|
88
|
+
|
|
89
|
+
### cert(查看证书 / PKCS#12 密钥库信息)
|
|
90
|
+
|
|
91
|
+
与厂商无关的本地工具:读取 X.509 证书或 PKCS#12 密钥库,把信息**每行一条**写到 `stdout`。
|
|
92
|
+
文件必须用 `--file`(或环境变量 `CERT_FILE`)显式指定,**没有默认文件**。
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
publish-apk cert --file ./certpath.cer # 只输出常用字段
|
|
96
|
+
publish-apk cert --file ./certpath.cer --verbose # 输出全部字段
|
|
97
|
+
CERT_FILE=./certpath.cer publish-apk cert # 环境变量形式
|
|
98
|
+
CERT_PASSWORD=<密码> publish-apk cert --file ./storeFile.p12
|
|
99
|
+
publish-apk cert --file ./storeFile.p12 --password <密码> --verbose
|
|
100
|
+
CERT_VERBOSE=1 publish-apk cert --file ./a.cer # 环境变量开启全部字段
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
- 证书默认字段:主题、颁发者、生效 / 到期时间、状态(剩余天数)、版本、序列号、签名算法、公钥算法、SHA-256 指纹、是否 CA、是否自签名。
|
|
104
|
+
- `--verbose` / `CERT_VERBOSE=1` 追加:主题与颁发者的每一项、GMT 时间、SHA-1 / SHA-512 指纹、公钥曲线 / 位数 / 指纹,
|
|
105
|
+
以及 X.509 扩展(Key Usage、Basic Constraints、SKI / AKI、SAN、EKU、AIA、CRL 分发点、证书策略等)。
|
|
106
|
+
- PEM 文件可以包含多张证书(证书链),按文件里的顺序逐张输出,并以 `证书 #N:` 分隔;DER 文件按一张证书处理。
|
|
107
|
+
- PKCS#12(`.p12` / `.pfx`,Android 的 `storeFile`)输出密钥数量、证书数量、每个别名的密钥算法与证书链;
|
|
108
|
+
`--verbose` 追加 MAC / 加密参数、`localKeyID`、密钥曲线 / 位数与全部证书字段。私钥只显示算法,不输出任何密钥材料。
|
|
109
|
+
- PKCS#12 密码用 `--password` 或 `CERT_PASSWORD` 传入;不传时先尝试空密码,失败再提示需要密码。
|
|
110
|
+
`--password` 会进入 `ps` / shell 历史,建议用 `CERT_PASSWORD`。PKCS#12 只使用一个密码(Android 的 `storePassword`;`keyPassword` 通常与之相同)。
|
|
111
|
+
- PKCS#12 需要系统 `openssl` 命令(OpenSSL 1.1.1+ / LibreSSL);项目仍然没有 npm 第三方依赖。
|
|
112
|
+
- 输出是「标签: 值」的纯文本,值里的换行会被压成空格,保证一条信息只占一行;证书原文(PEM/DER)与私钥内容不打印。
|
|
113
|
+
- `--verbose` 表示「输出全部字段」,不是运行日志;证书与密钥库信息属于结果,统一写 `stdout`。
|
|
114
|
+
|
|
115
|
+
### profile(查看 P7B Provisioning Profile)
|
|
116
|
+
|
|
117
|
+
与厂商无关的 P7B 查看工具:解析 PKCS#7 SignedData(DER / PEM),输出 profile 常用字段。
|
|
118
|
+
华为 AppGallery 的 `profile.p7b` 内嵌一段 JSON(bundle、类型、有效期、设备、权限)与证书链。
|
|
119
|
+
文件必须用 `--file`(或环境变量 `PROFILE_FILE`)显式指定,**没有默认文件**。
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
publish-apk profile --file ./profile.p7b # 只输出常用字段
|
|
123
|
+
publish-apk profile --file ./profile.p7b --verbose # 输出全部字段
|
|
124
|
+
PROFILE_FILE=./profile.p7b publish-apk profile # 环境变量形式
|
|
125
|
+
PROFILE_VERBOSE=1 publish-apk profile --file ./profile.p7b
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
- 默认输出:类型、版本名称 / 代码、UUID、签发者、Bundle、开发者 ID、App ID、APL、App Feature、
|
|
129
|
+
生效 / 到期时间、状态(剩余天数)、设备数量、权限 / ACL / 应用特权 / 应用服务数量、签名证书数量、
|
|
130
|
+
开发证书 / 发布证书摘要。
|
|
131
|
+
- `--verbose` / `PROFILE_VERBOSE=1` 追加:P7B 的摘要 / 签名算法与签名者、全部 JSON 字段
|
|
132
|
+
(设备 UDID、权限、ACL、服务能力等逐项一行)、签名证书链与开发 / 发布证书的完整字段。
|
|
133
|
+
- 支持 DER 与 PEM 包裹的 PKCS#7;不输出证书 PEM 原文,P7B 里没有私钥。
|
|
134
|
+
- 内嵌数据不是 JSON(例如纯证书 P7B)时仍会统计签名证书,并在 `--verbose` 里输出完整证书链与原因。
|
|
135
|
+
|
|
136
|
+
### hap(查看 HAP 安装包信息)
|
|
137
|
+
|
|
138
|
+
HAP 是 HarmonyOS 的安装包,本质是 ZIP:`pack.info` / `module.json` 记录包名、版本、模块、设备、API,
|
|
139
|
+
签名包还会在 ZIP 中央目录前带 HAP 签名块。本命令用于核对
|
|
140
|
+
`signed.hap` 与 `unsigned.hap` 的包信息,并判断是否带签名块。
|
|
141
|
+
文件必须用 `--file`(或环境变量 `HAP_FILE`)显式指定,**没有默认文件**。
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
publish-apk hap --file ./app-signed.hap # signed / unsigned 都适用
|
|
145
|
+
publish-apk hap --file ./*.hap # 一次查看多个 HAP,按顺序输出
|
|
146
|
+
publish-apk hap --file ./app-unsigned.hap --verbose
|
|
147
|
+
HAP_FILE=./app.hap publish-apk hap # 环境变量形式
|
|
148
|
+
HAP_VERBOSE=1 publish-apk hap --file ./app.hap
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
- `--file` 支持一次给多个文件,也支持 shell glob(如 `./*.hap`);按参数顺序逐个输出,文件之间空一行。
|
|
152
|
+
- 默认输出:文件大小、ZIP 条目数、签名状态、包名、包类型、版本 / 构建号、模块、设备、主 Ability、
|
|
153
|
+
API 兼容 / 目标版本、安装方式、构建模式、调试开关、权限,以及签名证书(HAP 签名方案与 Profile 签名各取叶子证书)。
|
|
154
|
+
- `--verbose` / `HAP_VERBOSE=1` 追加:应用标签、厂商、编译 SDK、模块包名,
|
|
155
|
+
HAP 签名块版本 / 大小 / 子块类型,全部 ZIP 条目、完整签名证书链,以及完整 `pack.info` / `module.json`。
|
|
156
|
+
- 签名证书从 HAP 签名块内嵌的 PKCS#7 中提取;默认输出每组叶子证书,`--verbose` 输出完整证书链。
|
|
157
|
+
证书字段与 `cert` 命令一致,但不输出证书 PEM 原文。
|
|
158
|
+
- 签名状态只检测 HAP 签名块(以及 Code Sign Block),**不执行证书链、摘要或 Merkle 树验签**;
|
|
159
|
+
输出里会明确写「未验签」。signed / unsigned 的包内容通常相同,区别在是否带签名块。
|
|
160
|
+
- HAP 解析只使用 Node 内置能力(ZIP 中央目录 + `node:zlib`),不依赖 JDK、`openssl` 或 npm 第三方包。
|
|
161
|
+
- 不输出证书 PEM 原文;HAP 里没有私钥。
|
|
162
|
+
|
|
163
|
+
### app(查看 APP 安装包信息)
|
|
164
|
+
|
|
165
|
+
APP 是 HarmonyOS 的 App Pack,本质也是 ZIP:外层有 `pack.info`、`pac.json`,并包含一个或多个
|
|
166
|
+
内层 `.hap` / `.hsp`。签名包会在外层 ZIP 中央目录前带 HAP 签名块;内层 HAP 可能没有单独签名。
|
|
167
|
+
本命令用于核对 `signed.app` / `unsigned.app` 的外层包信息、内层模块与签名证书。
|
|
168
|
+
文件必须用 `--file`(或环境变量 `APP_FILE`)显式指定,**没有默认文件**。
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
publish-apk app --file ./app-signed.app # signed / unsigned 都适用
|
|
172
|
+
publish-apk app --file ./*.app # 一次查看多个 APP,按顺序输出
|
|
173
|
+
publish-apk app --file ./app-unsigned.app --verbose
|
|
174
|
+
APP_FILE=./app.app publish-apk app # 环境变量形式
|
|
175
|
+
APP_VERBOSE=1 publish-apk app --file ./app.app
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
- `--file` 支持一次给多个文件,也支持 shell glob(如 `./*.app`);按参数顺序逐个输出,文件之间空一行。
|
|
179
|
+
- 默认输出:文件大小、ZIP 条目数、外层签名状态、包名、包类型、版本 / 构建号、模块、设备、
|
|
180
|
+
主 Ability、API 兼容 / 目标版本、安装方式、构建模式、调试开关、权限、内层模块列表,
|
|
181
|
+
以及外层签名证书(HAP 签名方案与 Profile 签名各取叶子证书)。
|
|
182
|
+
- `--verbose` / `APP_VERBOSE=1` 追加:应用标签、厂商、编译 SDK、模块包名,
|
|
183
|
+
外层签名块版本 / 大小 / 子块类型,全部 ZIP 条目、完整签名证书链,以及完整 `pack.info` / `pac.json`。
|
|
184
|
+
- 签名证书从外层 HAP 签名块内嵌的 PKCS#7 中提取;默认输出每组叶子证书,`--verbose` 输出完整证书链。
|
|
185
|
+
证书字段与 `cert` 命令一致,但不输出证书 PEM 原文。
|
|
186
|
+
- 外层签名只做检测,**不执行证书链、摘要或 Merkle 树验签**;输出里会明确写「未验签」。
|
|
187
|
+
- APP 解析只使用 Node 内置能力(ZIP 中央目录 + `node:zlib`),不依赖 JDK、`openssl` 或 npm 第三方包。
|
|
188
|
+
|
|
189
|
+
### apk(查看 APK 安装包信息)
|
|
190
|
+
|
|
191
|
+
APK 是 Android 安装包,本质也是 ZIP:`AndroidManifest.xml` 是 Android 二进制 XML,
|
|
192
|
+
签名可能是 JAR v1(`META-INF/*.RSA` 等)或 APK Signature Scheme v2 / v3 / v3.1。
|
|
193
|
+
本命令直接解析二进制 Manifest 与 APK Signing Block,不依赖 `aapt` / `apkanalyzer`。
|
|
194
|
+
文件必须用 `--file`(或环境变量 `APK_FILE`)显式指定,**没有默认文件**。
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
publish-apk apk --file ./app-arm64-v8a-release.apk
|
|
198
|
+
publish-apk apk --file ./*.apk # 一次查看多个 APK,按顺序输出
|
|
199
|
+
publish-apk apk --file ./app-armeabi-v7a-release.apk --verbose
|
|
200
|
+
APK_FILE=./app.apk publish-apk apk
|
|
201
|
+
APK_VERBOSE=1 publish-apk apk --file ./app.apk
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
- `--file` 支持一次给多个文件,也支持 shell glob(如 `./*.apk`);按参数顺序逐个输出,文件之间空一行。
|
|
205
|
+
- 默认输出:文件大小、ZIP 条目数、签名状态与签名方案、包名、版本 / versionCode、
|
|
206
|
+
Min / Target / 编译 SDK、ABI、DEX 数量、权限、启动 Activity,以及签名证书(叶子证书)。
|
|
207
|
+
- `--verbose` / `APK_VERBOSE=1` 追加:应用标签、允许备份、uses-feature、全部 Activity,
|
|
208
|
+
APK Signing Block 的 pair(v2 / v3 / v3.1 / Source Stamp / Verity Padding)、
|
|
209
|
+
全部 ZIP 条目与完整签名证书链。
|
|
210
|
+
- 签名只做检测,**不执行证书链、摘要或签名验签**;输出里会明确写「未验签」。
|
|
211
|
+
- 应用标签是资源引用(如 `@0x7f090001`)时不会解析 `resources.arsc`,`--verbose` 会原样输出。
|
|
212
|
+
- APK 解析只使用 Node 内置能力(ZIP 中央目录 + `node:zlib`),不依赖 JDK、`openssl` 或 npm 第三方包。
|
|
213
|
+
- 不输出证书 PEM 原文;APK 里没有私钥。
|
|
214
|
+
|
|
215
|
+
### huawei 子命令
|
|
216
|
+
|
|
217
|
+
华为的《获取服务端授权》(服务器鉴权)文档里写着**两套鉴权方式**,本项目两套都实现了:
|
|
218
|
+
|
|
219
|
+
- **Service Account 方式**:用私钥在本地签发 PS256 JWT,直接作为 `Authorization: Bearer` 使用 → `service-account` / `service-account-upload-url` / `service-account-upload-file` / `service-account-upload-file-package-info-update`
|
|
220
|
+
- **API 客户端方式**:用 `client_id` + `client_secret` 调接口换取 `access_token`,业务接口除 Bearer 外还要带 `client_id` 头 → `client-credentials` / `client-credentials-upload-url` / `client-credentials-upload-file` / `client-credentials-upload-file-package-info-update`
|
|
221
|
+
- **鸿蒙签名配置**:用 `.cer` / `.p7b` / `.p12` 与明文口令生成 `build-profile.json5` 的 `material`(含 material 目录与两串密文口令)→ `signing-config`
|
|
222
|
+
|
|
223
|
+
| 命令 | 说明 | stdout |
|
|
224
|
+
| --- | --- | --- |
|
|
225
|
+
| `huawei service-account` | 用 Service Account 私钥签发 PS256 JWT | JWT |
|
|
226
|
+
| `huawei service-account-upload-url` | 确定 Token(本地签发优先,其次 `--token`)后获取文件上传地址 | 接口原始响应 JSON |
|
|
227
|
+
| `huawei service-account-upload-file` | 确定 Token 后取上传地址,并把本地文件上传上去 | 文件对象 ID(`objectId`) |
|
|
228
|
+
| `huawei service-account-upload-file-package-info-update` | 确定 Token 后**上传文件并登记软件包**(未给 `--object-id` 时自己上传) | 文件对象 ID(`objectId`) |
|
|
229
|
+
| `huawei client-credentials` | 用 API 客户端凭证换取 `access_token` | `access_token` |
|
|
230
|
+
| `huawei client-credentials-upload-url` | 确定 `access_token`(本地换取优先,其次 `--token`)后获取文件上传地址,并同时携带 `client_id` 头 | 接口原始响应 JSON |
|
|
231
|
+
| `huawei client-credentials-upload-file` | 同上确定 `access_token` 后取上传地址并上传本地文件 | 文件对象 ID(`objectId`) |
|
|
232
|
+
| `huawei client-credentials-upload-file-package-info-update` | 同上确定 `access_token` 后**上传文件并登记软件包**(未给 `--object-id` 时自己上传) | 文件对象 ID(`objectId`) |
|
|
233
|
+
| `huawei signing-config` | 用 `.cer` / `.p7b` / `.p12` 与明文口令生成鸿蒙签名配置(material + 密文口令) | `signingConfig` JSON |
|
|
234
|
+
|
|
235
|
+
### 生成鸿蒙签名配置(`huawei signing-config`)
|
|
236
|
+
|
|
237
|
+
鸿蒙 `build-profile.json5` 的 `app.signingConfigs[].material` 里,`storePassword` / `keyPassword`
|
|
238
|
+
不是明文,而是用 `.p12` 同级目录下 `material/` 里的 workKey 加密过的十六进制串。
|
|
239
|
+
本命令把「选文件 + 输明文口令 + 生成 material + 加密口令」合成一步:
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
HARMONY_STORE_PASSWORD=<p12口令> publish-apk huawei signing-config \
|
|
243
|
+
--store-file ./storeFile.p12 \
|
|
244
|
+
--certpath ./certpath.cer \
|
|
245
|
+
--profile ./profile.p7b \
|
|
246
|
+
--key-alias dev \
|
|
247
|
+
--config ./build-profile.json5 \
|
|
248
|
+
--name default \
|
|
249
|
+
--write
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
证书也可以不落成 `.cer` 文件,直接把内容或 BASE64 放进环境变量:
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
# PEM 内容
|
|
256
|
+
HARMONY_STORE_PASSWORD=<p12口令> HARMONY_CERTPATH="$(cat certpath.cer)" \
|
|
257
|
+
publish-apk huawei signing-config --store-file ./storeFile.p12 \
|
|
258
|
+
--profile ./profile.p7b --certpath-out ./certpath.cer
|
|
259
|
+
|
|
260
|
+
# BASE64(DER 证书)
|
|
261
|
+
HARMONY_STORE_PASSWORD=<p12口令> \
|
|
262
|
+
HARMONY_CERTPATH_BASE64="$(openssl x509 -in certpath.cer -outform DER | openssl base64 -A)" \
|
|
263
|
+
publish-apk huawei signing-config --store-file ./storeFile.p12 \
|
|
264
|
+
--profile ./profile.p7b --certpath-out ./certpath.cer
|
|
265
|
+
|
|
266
|
+
# .p12 也可以来自 BASE64
|
|
267
|
+
HARMONY_STORE_PASSWORD=<p12口令> \
|
|
268
|
+
HARMONY_STORE_FILE_BASE64="$(openssl base64 -A -in storeFile.p12)" \
|
|
269
|
+
publish-apk huawei signing-config --store-file-out ./storeFile.p12 \
|
|
270
|
+
--certpath ./certpath.cer --profile ./profile.p7b
|
|
271
|
+
|
|
272
|
+
# .p7b 也可以来自 BASE64
|
|
273
|
+
HARMONY_STORE_PASSWORD=<p12口令> \
|
|
274
|
+
HARMONY_PROFILE_BASE64="$(openssl base64 -A -in profile.p7b)" \
|
|
275
|
+
publish-apk huawei signing-config --store-file ./storeFile.p12 \
|
|
276
|
+
--certpath ./certpath.cer --profile-out ./profile.p7b
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
- `.p12` / `.cer` / `.p7b` 都必填;`material/` 固定生成在 `.p12` 同级目录(hvigor 的 DecipherUtil 从这里取 workKey),已有可用 material 会复用。
|
|
280
|
+
- `--store-file` / `HARMONY_STORE_FILE` / `HARMONY_STORE_FILE_BASE64` 除了 `.p12` 路径,也接受 **BASE64**;内容会写到 `--store-file-out`,未给时写到当前目录的 `storeFile.p12`;已有不同文件会先备份再覆盖。
|
|
281
|
+
- `--certpath` / `HARMONY_CERTPATH` 除了 `.cer` 路径,也接受 **PEM 内容**或 **BASE64**(DER 或 PEM);内容会写到 `--certpath-out`,未给时写到 `.p12` 同级的 `certpath.cer`;已有不同证书会先备份再覆盖。
|
|
282
|
+
- `--profile` / `HARMONY_PROFILE` / `HARMONY_PROFILE_BASE64` 除了 `.p7b` 路径,也接受 **BASE64**;内容会写到 `--profile-out`,未给时写到 `.p12` 同级的 `profile.p7b`;已有不同 profile 会先备份再覆盖。
|
|
283
|
+
- `--key-alias` 不传时用 `keytool` 从 `.p12` 读取;keytool 还会用 `-certreq` 自检两个明文口令能否打开私钥。
|
|
284
|
+
- 不给 `--write` 时只把 `signingConfig` JSON 写到 `stdout`,方便手工粘贴;`--write` 只更新同名 `signingConfig` 的 `material`,改前备份到 `build-profile.json5` 所在目录的 `.signing/backup/`。
|
|
285
|
+
- 明文口令优先用 `HARMONY_STORE_PASSWORD` / `HARMONY_KEY_PASSWORD`,也可以交互输入;`--store-password` / `--key-password` 会进入 shell 历史,不推荐。
|
|
286
|
+
- material 是解开两串密文的唯一钥匙,不能删除、不能入库;`--new-material` 可重造一套(旧的自动备份)。
|
|
287
|
+
|
|
288
|
+
### 上传一个安装包分三步
|
|
289
|
+
|
|
290
|
+
只做前两步时文件**只躺在华为对象存储里,控制台「软件包管理」看不到它**,必须再调第三步登记:
|
|
291
|
+
|
|
292
|
+
| 步骤 | 接口 | 谁来调 |
|
|
293
|
+
| --- | --- | --- |
|
|
294
|
+
| 1. 取上传地址 | `GET /api/publish/v2/upload-url/for-obs` | `*-upload-url` / `*-upload-file` / `*-upload-file-package-info-update` |
|
|
295
|
+
| 2. 上传到 OBS | `PUT urlInfo.url` | `*-upload-file` / `*-upload-file-package-info-update` |
|
|
296
|
+
| 3. 登记为应用软件包 | `.app` → `PUT /api/publish/v3/app-package-info`;`.apk` 等 → `PUT /api/publish/v2/app-file-info` | `*-upload-file-package-info-update` |
|
|
297
|
+
|
|
298
|
+
第三步的接口**按安装包后缀自动二选一**(华为把它拆成了两套互斥的接口,串用会失败):
|
|
299
|
+
|
|
300
|
+
| 后缀 | 登记接口 | 载荷 | `--release-type` | 返回的软件包标识 |
|
|
301
|
+
| --- | --- | --- | --- | --- |
|
|
302
|
+
| `.app`(HarmonyOS 5 及以上) | `PUT /api/publish/v3/app-package-info?appId=…[&releaseType=…][&releasePhase=…]` | `{"fileName":…,"objectId":…}` | `1` 全网 / `7` 企业非公开发布 | `packageId` |
|
|
303
|
+
| `.apk`、`.aab`、`.rpk`、`.hap`(Android 与 HarmonyOS 4 及以下) | `PUT /api/publish/v2/app-file-info?appId=…[&releaseType=…]` | `{"fileType":5,"files":[{"fileName":…,"fileDestUrl":…}]}` | `1` 全网 / `3` 分阶段 | `pkgVersion` |
|
|
304
|
+
|
|
305
|
+
- 用 `.apk` 去调 v3 接口会被华为按后缀拦下:`AGC 返回错误码 204144641: [AppGalleryConnectPublishService]The suffix name of the software package is incorrect`。
|
|
306
|
+
- `--release-phase`(`0` 全网 / `3` 分阶段)只有 `.app` 那套接口有;`.apk` 等的分阶段发布用 `--release-type 3`。
|
|
307
|
+
- 后缀写错、认不出的后缀(如 `.exe`)会在**本地**报用法错误,不会等上传完才失败。
|
|
308
|
+
|
|
309
|
+
三种用法按需选:
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
# A. 只要上传地址,上传自己来(脚本里用 curl,最灵活)
|
|
313
|
+
publish-apk huawei service-account-upload-url --app-id <应用ID> --file ./a1.apk --token <Token>
|
|
314
|
+
|
|
315
|
+
# B. 上传到对象存储(输出 objectId;后台「软件包管理」里还看不到)
|
|
316
|
+
OBJECT_ID=$(publish-apk huawei service-account-upload-file \
|
|
317
|
+
--app-id <应用ID> --file ./a1.apk --token <Token>)
|
|
318
|
+
|
|
319
|
+
# C. 上传 + 登记,一条命令走完三步(最省事;输出 objectId,软件包标识见 --verbose)
|
|
320
|
+
publish-apk huawei service-account-upload-file-package-info-update \
|
|
321
|
+
--app-id <应用ID> --file ./a1.apk --kid <密钥ID> --iss <签发者> --private-key ./private.pem
|
|
322
|
+
|
|
323
|
+
# C'. 文件已经在别处上传过:给 --object-id 就跳过上传,只做登记
|
|
324
|
+
publish-apk huawei service-account-upload-file-package-info-update \
|
|
325
|
+
--app-id <应用ID> --file-name a1.apk --object-id "$OBJECT_ID" --token <Token>
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
用法 C 里 `--file` 一条就够:**文件名取它的 basename、文件大小取它的字节数**。
|
|
329
|
+
这三个值(取址的 `fileName` 与 `contentLength`、登记时的 `fileName`)都取自同一处,
|
|
330
|
+
不会出现「上传用的名字」与「登记用的名字」不一致——华为按后缀校验,不一致就会失败。
|
|
331
|
+
想指定上传后的名字就加 `--file-name`(此时以它为准,后缀也以它为准)。
|
|
332
|
+
|
|
333
|
+
### 示例
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
# 1. 签发 Service Account JWT
|
|
337
|
+
publish-apk huawei service-account \
|
|
338
|
+
--kid <密钥ID> --iss <签发者> --private-key ./private.pem
|
|
339
|
+
|
|
340
|
+
# 2. 获取文件上传地址(token 已在环境变量里,或本命令用 service-account 参数本地签发)
|
|
341
|
+
# --file 会用本地文件的文件名补 --file-name、用它的字节数补 --content-length
|
|
342
|
+
RESP=$(publish-apk huawei service-account-upload-url \
|
|
343
|
+
--app-id <应用ID> --file ./a1.apk \
|
|
344
|
+
--kid <密钥ID> --iss <签发者> --private-key ./private.pem)
|
|
345
|
+
|
|
346
|
+
# 3. 按接口返回的方法与地址上传(请求头见 urlInfo.headers,其中含 OBS 签名所需信息)
|
|
347
|
+
curl -X "$(jq -r .urlInfo.method <<<"$RESP")" \
|
|
348
|
+
-T a1.apk "$(jq -r .urlInfo.url <<<"$RESP")"
|
|
349
|
+
|
|
350
|
+
# 3'. 或者一步到位:取地址 + 上传,stdout 直接是 objectId
|
|
351
|
+
OBJECT_ID=$(publish-apk huawei service-account-upload-file \
|
|
352
|
+
--app-id <应用ID> --file ./a1.apk \
|
|
353
|
+
--kid <密钥ID> --iss <签发者> --private-key ./private.pem)
|
|
354
|
+
|
|
355
|
+
# 3''. 或者一条命令做完第 2+3 步:上传 + 登记(不做第 3 步,后台「软件包管理」里看不到)
|
|
356
|
+
publish-apk huawei service-account-upload-file-package-info-update \
|
|
357
|
+
--app-id <应用ID> --file ./a1.apk \
|
|
358
|
+
--kid <密钥ID> --iss <签发者> --private-key ./private.pem
|
|
359
|
+
|
|
360
|
+
# 4. API 客户端方式:先换 access_token,再取上传地址(或直接上传)
|
|
361
|
+
publish-apk huawei client-credentials --client-id <客户端ID> --client-secret <密钥>
|
|
362
|
+
publish-apk huawei client-credentials-upload-url \
|
|
363
|
+
--app-id <应用ID> --file ./a1.apk \
|
|
364
|
+
--client-id <客户端ID> --client-secret <密钥>
|
|
365
|
+
publish-apk huawei client-credentials-upload-file \
|
|
366
|
+
--app-id <应用ID> --file ./a1.apk \
|
|
367
|
+
--client-id <客户端ID> --client-secret <密钥>
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### 文件参数:`--file` / `--file-name` / `--content-length`
|
|
371
|
+
|
|
372
|
+
两个 `*-upload-url` 命令都要把 `fileName` 与 `contentLength` 作为查询参数发给华为接口,这两个值可以自动推导,不必手算:
|
|
373
|
+
|
|
374
|
+
| 参数 | 是否必填 | 说明 |
|
|
375
|
+
| --- | --- | --- |
|
|
376
|
+
| `--file <路径>` | 否 | **本地文件路径**,用来补下面两项:文件名取它的 basename,大小取它的字节数(`HUAWEI_FILE`) |
|
|
377
|
+
| `--file-name <文件名>` | 否 | 上传后的对象名;显式给出时以它为准,否则取 `--file` 的文件名 |
|
|
378
|
+
| `--content-length <字节>` | 否 | 文件大小;显式给出时以它为准,否则取 `--file` 的大小 |
|
|
379
|
+
|
|
380
|
+
- 规则是「**`--file` 只补缺**」:`--file-name` / `--content-length`(命令行或环境变量)给了哪个就听哪个,都没给才从 `--file` 推导。
|
|
381
|
+
- 只有真的需要推导时才读文件:两项都显式给出时,`--file` 不会被读取,路径不存在也不会报错。
|
|
382
|
+
- 反过来,只要还缺一项,`--file` 就必须指向存在的**普通文件**,否则报用法错误(目录、路径写错都会被拦下)。
|
|
383
|
+
- 与 `--token` 之类一样,`--file-name` / `--content-length` 没有 `--file` 时仍然可用,行为与旧版本完全一致。
|
|
384
|
+
|
|
385
|
+
```bash
|
|
386
|
+
# 只给文件,其余自动推导(fileName=a1.apk,contentLength=文件字节数)
|
|
387
|
+
publish-apk huawei service-account-upload-url --app-id <应用ID> --file ./a1.apk --token <Token>
|
|
388
|
+
|
|
389
|
+
# 文件在本地叫 a1.apk,但希望上传后叫 a1-release.apk:显式覆盖名字,大小仍然自动推导
|
|
390
|
+
publish-apk huawei service-account-upload-url --app-id <应用ID> --file ./a1.apk --file-name a1-release.apk
|
|
391
|
+
|
|
392
|
+
# 不读本地文件,完全手工指定(旧写法,仍然支持)
|
|
393
|
+
publish-apk huawei service-account-upload-url --app-id <应用ID> --file-name a1.apk --content-length 111223
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
两个 `*-upload-file` 命令的规则不同——它们**必须**有本地文件,因为要上传的就是它的内容:
|
|
397
|
+
|
|
398
|
+
| 参数 | 是否必填 | 说明 |
|
|
399
|
+
| --- | --- | --- |
|
|
400
|
+
| `--file <路径>` | **是** | 待上传的本地文件(`HUAWEI_FILE`);文件名与文件大小都取它 |
|
|
401
|
+
| `--file-name <文件名>` | 否 | 上传后的对象名;默认取 `--file` 的文件名 |
|
|
402
|
+
|
|
403
|
+
- 华为要求上传的字节数与取址时填写的 `contentLength` 一致,所以文件大小**恒取本地文件的实际字节数**;`--content-length` 在本命令里已不接受(写错也不会静默失败,而是直接报未知选项)。
|
|
404
|
+
- `--file-name` 与 `--file` 一致时,华为侧的文件后缀校验才容易过(华为要求后缀与取址时的 `fileName` 相同)。
|
|
405
|
+
- 大文件注意:文件内容会一次性读入内存,数 GB 的安装包需要给 Node 更多内存(后续可改为流式上传)。
|
|
406
|
+
- 本命令**不会**让软件包出现在后台:它只把文件传到对象存储。
|
|
407
|
+
想一条命令走完「上传 + 登记」,用 `*-upload-file-package-info-update`(见上面的「上传一个安装包分三步」)。
|
|
408
|
+
|
|
409
|
+
### 组合命令:`*-upload-file-package-info-update`
|
|
410
|
+
|
|
411
|
+
它在 `*-upload-file` 的基础上多接一步「登记软件包」,两种用法:
|
|
412
|
+
|
|
413
|
+
| 场景 | 参数 | 行为 |
|
|
414
|
+
| --- | --- | --- |
|
|
415
|
+
| 文件还没上传 | `--app-id` + `--file`(`--file-name` 可选) | 取址 → 上传 → 登记,**三步都走**(与 `*-upload-file` 完全相同的上传逻辑) |
|
|
416
|
+
| 文件已上传过 | `--app-id` + `--file-name` + `--object-id` | 跳过取址与上传,只登记(此时不需要 `--file`,连路径都不读) |
|
|
417
|
+
|
|
418
|
+
- `stdout` 是 **`objectId`**(与 `*-upload-file` 一致);登记接口返回的软件包标识只在 `--verbose` 日志里
|
|
419
|
+
(`.app` 是 `packageId`,`.apk` 等是 `pkgVersion`,两者都用于查询软件包编译状态)。
|
|
420
|
+
- 想显式指定上传后的名字就加 `--file-name`,它同时用于取址、上传与登记三处,保证一致;登记走哪套接口也由它的后缀决定。
|
|
421
|
+
|
|
422
|
+
### 上传是怎么发的(`*-upload-file`)
|
|
423
|
+
|
|
424
|
+
上传是 `PUT urlInfo.url`,请求头**完全照抄** `urlInfo.headers`(`Authorization`、`x-amz-content-sha256`、`x-amz-date`、`Host`、
|
|
425
|
+
`user-agent`、`Content-Type`),除了按实际字节数补 `Content-Length` 之外不增删任何头部:
|
|
426
|
+
|
|
427
|
+
- 这些头部是 **OBS 预签名**的一部分,多一个、少一个、值不对都会得到 `SignatureDoesNotMatch`。
|
|
428
|
+
- 因此上传没有走 Node 内置 `fetch`(undici 会用连接目标覆盖 `Host`,签名必然不匹配),而是用 `node:https` 原样发送 `Host`。
|
|
429
|
+
实测对比(本地服务回显收到的头部):`fetch` 把 `Host` 改成连接目标,并额外加上 `accept`、`accept-language`、`sec-fetch-mode` 等头部;`node:https` 配上 `setHost: false` 后收到的头部与期望完全一致。
|
|
430
|
+
- 地址有效期为**约 5 分钟**,所以本命令把"取地址 → 上传"串在一条命令里完成,中间不做别的耗时操作。
|
|
431
|
+
- 上传成功时华为只返回 HTTP 200(响应体为空),因此 `stdout` 输出的是取址响应里的 `objectId`——后续接口(如上传软件包)需要它。
|
|
432
|
+
|
|
433
|
+
### google 子命令
|
|
434
|
+
|
|
435
|
+
Google Play 走 **Android Publisher API v3**,鉴权用**服务账号 JSON 密钥**:本地签发 RS256 JWT → 换取 OAuth 2.0 `access_token`:
|
|
436
|
+
|
|
437
|
+
| 命令 | 说明 | stdout |
|
|
438
|
+
| --- | --- | --- |
|
|
439
|
+
| `google access-token` | 用服务账号 JSON 换取 `access_token` | `access_token` |
|
|
440
|
+
| `google upload-aab` | 创建 edit → 上传 `.aab` →(可选)更新轨道 → 提交 edit | bundle 的 `versionCode` |
|
|
441
|
+
|
|
442
|
+
### 上传一个 .aab 分四步
|
|
443
|
+
|
|
444
|
+
| 步骤 | 接口 | 谁来调 |
|
|
445
|
+
| --- | --- | --- |
|
|
446
|
+
| 1. 创建 edit | `POST /androidpublisher/v3/applications/{packageName}/edits` | `google upload-aab` |
|
|
447
|
+
| 2. 上传 bundle | `POST /upload/androidpublisher/v3/applications/{packageName}/edits/{editId}/bundles?uploadType=resumable` 取会话地址,再 `PUT` 会话地址上传文件内容 | `google upload-aab` |
|
|
448
|
+
| 3. 更新轨道 | `PUT /androidpublisher/v3/applications/{packageName}/edits/{editId}/tracks/{track}` | 给了 `--track` 时 |
|
|
449
|
+
| 4. 提交 edit | `POST /androidpublisher/v3/applications/{packageName}/edits/{editId}:commit` | `google upload-aab` |
|
|
450
|
+
|
|
451
|
+
- **只有第 4 步提交成功,修改才真正生效**。中途失败时命令会尽力 `DELETE` 这个 edit:已上传的 bundle 一并丢弃,不留下半成品。
|
|
452
|
+
- 不给 `--track` 时只上传 bundle 并提交,**不进入任何轨道**;给了 `--track` 才创建 / 更新对应轨道的发布。
|
|
453
|
+
- `.aab` 后缀、包名、发布状态、分阶段比例都会在**本地**先校验,不会等传完才失败。
|
|
454
|
+
- 上传用 Google 的 **resumable upload**(先取会话地址、再 PUT 文件内容),这是官方对大文件的做法;`.aab` 仍会一次性读入内存,超大包需给 Node 更多内存。
|
|
455
|
+
- 若提交返回 `changesNotSentForReview`(例如应用启用了**托管发布(managed publishing)**):加 `--changes-not-sent-for-review`(或 `GOOGLE_CHANGES_NOT_SENT_FOR_REVIEW=1`),变更会留在 Play 控制台等待人工送审。
|
|
456
|
+
|
|
457
|
+
`--track` 的取值是轨道标识:`production`(正式版)、`beta`、`alpha`、`internal`(内部测试),或其他自定义轨道名(外形轨道如 `wear:production` 也可直接传)。给了 `--track` 时的发布状态:
|
|
458
|
+
|
|
459
|
+
| `--release-status` | 含义 |
|
|
460
|
+
| --- | --- |
|
|
461
|
+
| `completed` | 全部用户可更新(默认) |
|
|
462
|
+
| `draft` | 仅保存为草稿,需要人工在 Play 控制台发起发布 |
|
|
463
|
+
| `inProgress` | 分阶段发布,**必须**同时给出 `--user-fraction`(0 与 1 之间,如 `0.1` 表示 10% 用户) |
|
|
464
|
+
| `halted` | 暂停分阶段发布,可配合 `--user-fraction` 调整比例 |
|
|
465
|
+
|
|
466
|
+
```bash
|
|
467
|
+
# 1. 用服务账号换 access_token
|
|
468
|
+
publish-apk google access-token --service-account ./google-play-service-account.json
|
|
469
|
+
|
|
470
|
+
# 2. 只上传 .aab(不发布到轨道),stdout 是 versionCode
|
|
471
|
+
publish-apk google upload-aab \
|
|
472
|
+
--package-name com.example.app --file ./app-release.aab \
|
|
473
|
+
--service-account ./google-play-service-account.json
|
|
474
|
+
|
|
475
|
+
# 3. 上传并发布到内部测试轨道(默认 completed)
|
|
476
|
+
publish-apk google upload-aab \
|
|
477
|
+
--package-name com.example.app --file ./app-release.aab \
|
|
478
|
+
--service-account ./google-play-service-account.json --track internal
|
|
479
|
+
|
|
480
|
+
# 4. 正式轨道 + 发布说明(默认语言 zh-CN,可用 --release-notes-language 改)
|
|
481
|
+
publish-apk google upload-aab \
|
|
482
|
+
--package-name com.example.app --file ./app-release.aab \
|
|
483
|
+
--service-account ./google-play-service-account.json --track production \
|
|
484
|
+
--release-notes "修复若干问题"
|
|
485
|
+
|
|
486
|
+
# 5. 10% 分阶段发布
|
|
487
|
+
publish-apk google upload-aab \
|
|
488
|
+
--package-name com.example.app --file ./app-release.aab \
|
|
489
|
+
--service-account ./google-play-service-account.json --track production \
|
|
490
|
+
--release-status inProgress --user-fraction 0.1
|
|
491
|
+
|
|
492
|
+
# 6. 已有 access_token(例如由别处换取),不必再给服务账号
|
|
493
|
+
publish-apk google upload-aab --package-name com.example.app --file ./app-release.aab \
|
|
494
|
+
--token "$GOOGLE_TOKEN" --track beta
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
### Google Play 服务账号准备
|
|
498
|
+
|
|
499
|
+
1. 在 Google Cloud 控制台创建项目,并启用 **Google Play Developer API**;
|
|
500
|
+
2. 创建服务账号并生成 **JSON 密钥**(含 `client_email`、`private_key`、`token_uri`);
|
|
501
|
+
3. 在 Google Play 控制台的「用户和权限」里邀请该 `client_email`,按需授予 **Release to testing tracks**、
|
|
502
|
+
**Release to production, exclude devices, and use app signing by Google Play** 等权限;
|
|
503
|
+
4. 应用的**第一个版本仍需在 Play 控制台手动上传**,之后才能用 API 上传后续版本。
|
|
504
|
+
|
|
505
|
+
凭据有两种传法,任选其一:
|
|
506
|
+
|
|
507
|
+
**方式 A:完整 JSON**(推荐,字段不易拼错)
|
|
508
|
+
|
|
509
|
+
- `--service-account <文件>` / `GOOGLE_SERVICE_ACCOUNT`
|
|
510
|
+
- `--service-account-data <JSON>` / `GOOGLE_SERVICE_ACCOUNT_DATA`
|
|
511
|
+
|
|
512
|
+
**方式 B:把 JSON 里的属性拆开传**(适合 CI 把各字段存成独立 Secret)
|
|
513
|
+
|
|
514
|
+
| JSON 字段 | 命令行选项 | 环境变量 | 说明 |
|
|
515
|
+
| --- | --- | --- | --- |
|
|
516
|
+
| `client_email` | `--client-email` | `GOOGLE_CLIENT_EMAIL` | 必填 |
|
|
517
|
+
| `private_key` | `--private-key-data` | `GOOGLE_PRIVATE_KEY_DATA` | 必填,PEM 数据;也可存成 PEM 文件后用 `--private-key` / `GOOGLE_PRIVATE_KEY` |
|
|
518
|
+
| `token_uri` | `--token-uri` | `GOOGLE_TOKEN_URI` | 可选,默认 `https://oauth2.googleapis.com/token` |
|
|
519
|
+
| `project_id`、`private_key_id` | — | — | 不参与 RS256 JWT 鉴权,无需传递 |
|
|
520
|
+
|
|
521
|
+
```bash
|
|
522
|
+
# 方式 B:命令行分项参数
|
|
523
|
+
publish-apk google upload-aab \
|
|
524
|
+
--package-name com.example.app --file ./app-release.aab \
|
|
525
|
+
--client-email publisher@project.iam.gserviceaccount.com \
|
|
526
|
+
--private-key ./service-account.pem --track internal
|
|
527
|
+
|
|
528
|
+
# 方式 B:环境变量(私钥建议只走环境变量,避免出现在 ps / CI 日志里)
|
|
529
|
+
export GOOGLE_CLIENT_EMAIL=publisher@project.iam.gserviceaccount.com
|
|
530
|
+
export GOOGLE_PRIVATE_KEY_DATA="$(cat service-account.pem)"
|
|
531
|
+
publish-apk google upload-aab --package-name com.example.app --file ./app-release.aab --track internal
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
凭据优先级(高 → 低):命令行 JSON → 命令行分项 → 环境变量 JSON → 环境变量分项。
|
|
535
|
+
命令行 JSON 与分项不能同时给;环境变量层两者都在时优先完整 JSON 并在 stderr 提示。
|
|
536
|
+
|
|
537
|
+
本工具只支持**服务账号 JSON 密钥**(`type` 为 `service_account`)或它的分项字段;Workload Identity Federation / ADC 生成的凭据文件暂不支持。
|
|
538
|
+
|
|
539
|
+
## 环境变量
|
|
540
|
+
|
|
541
|
+
带值的选项都有同名环境变量兜底,**命令行选项优先**;开关型选项 `--verbose` 对应各命令的 `*_VERBOSE`(`cert` 用 `CERT_VERBOSE`,`profile` 用 `PROFILE_VERBOSE`,`hap` 用 `HAP_VERBOSE`,`app` 用 `APP_VERBOSE`,`apk` 用 `APK_VERBOSE`),取值规则见下表。
|
|
542
|
+
|
|
543
|
+
### 证书(`CERT_*`)
|
|
544
|
+
|
|
545
|
+
| 环境变量 | 等价选项 | 说明 |
|
|
546
|
+
| --- | --- | --- |
|
|
547
|
+
| `CERT_FILE` | `--file` | 证书 / PKCS#12 文件路径(PEM / DER / `.p12` / `.pfx`);**必填**,没有默认文件 |
|
|
548
|
+
| `CERT_PASSWORD` | `--password` | PKCS#12 密钥库密码;建议用环境变量,避免密码进入 `ps` / shell 历史 |
|
|
549
|
+
| `CERT_VERBOSE` | `--verbose` | 输出全部字段:`1`/`true`/`yes`/`on` 开启,`0`/`false`/`no`/`off` 关闭 |
|
|
550
|
+
|
|
551
|
+
### P7B Profile(`PROFILE_*`)
|
|
552
|
+
|
|
553
|
+
| 环境变量 | 等价选项 | 说明 |
|
|
554
|
+
| --- | --- | --- |
|
|
555
|
+
| `PROFILE_FILE` | `--file` | P7B 文件路径(PKCS#7 SignedData,DER / PEM);**必填**,没有默认文件 |
|
|
556
|
+
| `PROFILE_VERBOSE` | `--verbose` | 输出全部字段:`1`/`true`/`yes`/`on` 开启,`0`/`false`/`no`/`off` 关闭 |
|
|
557
|
+
|
|
558
|
+
### HAP 安装包(`HAP_*`)
|
|
559
|
+
|
|
560
|
+
| 环境变量 | 等价选项 | 说明 |
|
|
561
|
+
| --- | --- | --- |
|
|
562
|
+
| `HAP_FILE` | `--file` | HAP 文件路径;**必填**,没有默认文件 |
|
|
563
|
+
| `HAP_VERBOSE` | `--verbose` | 输出全部字段:`1`/`true`/`yes`/`on` 开启,`0`/`false`/`no`/`off` 关闭 |
|
|
564
|
+
|
|
565
|
+
### APP 安装包(`APP_*`)
|
|
566
|
+
|
|
567
|
+
| 环境变量 | 等价选项 | 说明 |
|
|
568
|
+
| --- | --- | --- |
|
|
569
|
+
| `APP_FILE` | `--file` | APP 文件路径;**必填**,没有默认文件 |
|
|
570
|
+
| `APP_VERBOSE` | `--verbose` | 输出全部字段:`1`/`true`/`yes`/`on` 开启,`0`/`false`/`no`/`off` 关闭 |
|
|
571
|
+
|
|
572
|
+
### APK 安装包(`APK_*`)
|
|
573
|
+
|
|
574
|
+
| 环境变量 | 等价选项 | 说明 |
|
|
575
|
+
| --- | --- | --- |
|
|
576
|
+
| `APK_FILE` | `--file` | APK 文件路径;**必填**,没有默认文件 |
|
|
577
|
+
| `APK_VERBOSE` | `--verbose` | 输出全部字段:`1`/`true`/`yes`/`on` 开启,`0`/`false`/`no`/`off` 关闭 |
|
|
578
|
+
|
|
579
|
+
### 华为(`HUAWEI_*`)
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
| 环境变量 | 等价选项 | 说明 |
|
|
583
|
+
| --- | --- | --- |
|
|
584
|
+
| `HUAWEI_KID` | `--kid` | JWT 请求头中的密钥 ID |
|
|
585
|
+
| `HUAWEI_ISS` | `--iss` | JWT 载荷中的签发者 |
|
|
586
|
+
| `HUAWEI_PRIVATE_KEY` | `--private-key` | 私钥 PEM 文件路径 |
|
|
587
|
+
| `HUAWEI_PRIVATE_KEY_DATA` | `--private-key-data` | 私钥 PEM 数据(与前者二选一;两者都在时**数据优先**并在 stderr 提示) |
|
|
588
|
+
| `HUAWEI_IAT` | `--iat` | JWT 签发时间(UTC 秒级时间戳),默认当前时间 |
|
|
589
|
+
| `HUAWEI_CLIENT_ID` | `--client-id` | API 客户端 ID |
|
|
590
|
+
| `HUAWEI_CLIENT_SECRET` | `--client-secret` | API 客户端密钥 |
|
|
591
|
+
| `HUAWEI_APP_ID` | `--app-id` | AppGallery Connect 的 `appId`(非负整数) |
|
|
592
|
+
| `HUAWEI_FILE` | `--file` | 本地文件路径;`*-upload-url` 用它补 `--file-name` 与 `--content-length`,`*-upload-file` 则必须给出 |
|
|
593
|
+
| `HUAWEI_FILE_NAME` | `--file-name` | 上传的文件名,如 `a1.apk`;未显式给出时取 `HUAWEI_FILE` 的文件名 |
|
|
594
|
+
| `HUAWEI_CONTENT_LENGTH` | `--content-length` | 文件大小(字节,非负整数);只有 `*-upload-url` 用它(未给出时取 `HUAWEI_FILE` 的字节数),`*-upload-file` 不接受该选项 |
|
|
595
|
+
| `HUAWEI_TOKEN` | `--token` | 已签发的 JWT 或 `access_token` |
|
|
596
|
+
| `HUAWEI_OBJECT_ID` | `--object-id` | `*-upload-file-package-info-update`:已上传文件的 `objectId`;给出则跳过上传,只做登记 |
|
|
597
|
+
| `HUAWEI_RELEASE_TYPE` | `--release-type` | `*-upload-file-package-info-update`:应用发布方式,默认 `1`;`.app` 用 `1` 全网 / `7` 企业非公开发布,`.apk` 等用 `1` 全网 / `3` 分阶段 |
|
|
598
|
+
| `HUAWEI_RELEASE_PHASE` | `--release-phase` | `*-upload-file-package-info-update`:分阶段发布标识,`0` 全网 / `3` 分阶段;只有 `.app`(v3 接口)有这个参数 |
|
|
599
|
+
| `HUAWEI_VERBOSE` | `--verbose` | 开启运行日志:`1`/`true`/`yes`/`on` 开启,`0`/`false`/`no`/`off` 关闭 |
|
|
600
|
+
| `HUAWEI_API_BASE` | —(仅环境变量) | 接口域名,默认 `https://connect-api.cloud.huawei.com`;其他区域(`connect-api-dre/dra/drru.cloud.huawei.com`)或本地联调时覆盖 |
|
|
601
|
+
|
|
602
|
+
示例(密钥全程只走环境变量,不出现在命令行参数里;文件大小由 `HUAWEI_FILE` 自动推导):
|
|
603
|
+
|
|
604
|
+
```bash
|
|
605
|
+
export HUAWEI_KID=<密钥ID>
|
|
606
|
+
export HUAWEI_ISS=<签发者>
|
|
607
|
+
export HUAWEI_PRIVATE_KEY=./private.pem
|
|
608
|
+
export HUAWEI_APP_ID=<应用ID>
|
|
609
|
+
export HUAWEI_FILE=./a1.apk
|
|
610
|
+
|
|
611
|
+
publish-apk huawei service-account-upload-url
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
如果本机拿不到文件(例如由别的步骤产出),就用 `HUAWEI_FILE_NAME` + `HUAWEI_CONTENT_LENGTH` 显式给出这两个值
|
|
615
|
+
(这只适用于 `*-upload-url`;`*-upload-file` 必须能读到本地文件)。
|
|
616
|
+
|
|
617
|
+
### 鸿蒙签名配置(`HARMONY_*`)
|
|
618
|
+
|
|
619
|
+
| 环境变量 | 等价选项 | 说明 |
|
|
620
|
+
| --- | --- | --- |
|
|
621
|
+
| `HARMONY_STORE_FILE` | `--store-file` | 鸿蒙签名密钥库 `.p12` 路径,或 BASE64 |
|
|
622
|
+
| `HARMONY_STORE_FILE_BASE64` | `--store-file` | BASE64 的 `.p12`(别名) |
|
|
623
|
+
| `HARMONY_STORE_FILE_OUT` | `--store-file-out` | p12 内容/复制目标;默认当前目录的 `storeFile.p12` |
|
|
624
|
+
| `HARMONY_CERTPATH` | `--certpath` | 鸿蒙签名证书 `.cer` 路径,或直接给 PEM 内容 / BASE64(DER 或 PEM);也接受 `HARMONY_CERT`、`HARMONY_CERTPATH_BASE64`、`HARMONY_CERT_BASE64` |
|
|
625
|
+
| `HARMONY_CERTPATH_OUT` | `--certpath-out` | 证书内容/复制目标;默认 `.p12` 同级的 `certpath.cer` |
|
|
626
|
+
| `HARMONY_PROFILE` | `--profile` | 鸿蒙 Provisioning Profile `.p7b` 路径,或 BASE64 |
|
|
627
|
+
| `HARMONY_PROFILE_BASE64` | `--profile` | BASE64 的 `.p7b`(别名) |
|
|
628
|
+
| `HARMONY_PROFILE_OUT` | `--profile-out` | profile 内容/复制目标;默认 `.p12` 同级的 `profile.p7b` |
|
|
629
|
+
| `HARMONY_KEY_ALIAS` | `--key-alias` | p12 里的密钥别名;不传时用 keytool 自动读取 |
|
|
630
|
+
| `HARMONY_SIGN_ALG` | `--sign-alg` | 签名算法,默认 `SHA256withECDSA` |
|
|
631
|
+
| `HARMONY_STORE_PASSWORD` | `--store-password` | `.p12` 明文口令;推荐用环境变量/交互输入;make 只把它作为环境变量传给子进程,不转成 `--store-password` |
|
|
632
|
+
| `HARMONY_KEY_PASSWORD` | `--key-password` | 密钥明文口令;默认与 `HARMONY_STORE_PASSWORD` 相同 |
|
|
633
|
+
| `HARMONY_CONFIG` | `--config` | `build-profile.json5` 路径,默认 `./build-profile.json5` |
|
|
634
|
+
| `HARMONY_SIGNING_NAME` | `--name` | 要更新/输出的 signingConfig 名字,默认 `default` |
|
|
635
|
+
| `HARMONY_PRODUCT` | `--product` | 按产品名找它挂的 signingConfig 作为名字 |
|
|
636
|
+
|
|
637
|
+
### Google Play(`GOOGLE_*`)
|
|
638
|
+
|
|
639
|
+
| 环境变量 | 等价选项 | 说明 |
|
|
640
|
+
| --- | --- | --- |
|
|
641
|
+
| `GOOGLE_PACKAGE_NAME` | `--package-name` | Google Play 应用包名,如 `com.example.app` |
|
|
642
|
+
| `GOOGLE_FILE` | `--file` | `.aab` 文件路径 |
|
|
643
|
+
| `GOOGLE_SERVICE_ACCOUNT` | `--service-account` | 服务账号 JSON 密钥文件路径(完整 JSON) |
|
|
644
|
+
| `GOOGLE_SERVICE_ACCOUNT_DATA` | `--service-account-data` | 服务账号 JSON 数据(与前者二选一;两者都在时**数据优先**并在 stderr 提示) |
|
|
645
|
+
| `GOOGLE_CLIENT_EMAIL` | `--client-email` | 服务账号 JSON 的 `client_email`(分项字段) |
|
|
646
|
+
| `GOOGLE_PRIVATE_KEY` | `--private-key` | 私钥 PEM 文件路径(分项字段,与 `GOOGLE_PRIVATE_KEY_DATA` 二选一) |
|
|
647
|
+
| `GOOGLE_PRIVATE_KEY_DATA` | `--private-key-data` | 私钥 PEM 数据(分项字段;建议只走环境变量) |
|
|
648
|
+
| `GOOGLE_TOKEN` | `--token` | 已换取的 `access_token` |
|
|
649
|
+
| `GOOGLE_TRACK` | `--track` | 发布到哪个轨道;不给则只上传 bundle |
|
|
650
|
+
| `GOOGLE_RELEASE_STATUS` | `--release-status` | 轨道发布状态,默认 `completed` |
|
|
651
|
+
| `GOOGLE_RELEASE_NAME` | `--release-name` | 轨道发布名称 |
|
|
652
|
+
| `GOOGLE_RELEASE_NOTES` | `--release-notes` | 轨道发布说明 |
|
|
653
|
+
| `GOOGLE_RELEASE_NOTES_LANGUAGE` | `--release-notes-language` | 发布说明语言(BCP-47),默认 `zh-CN` |
|
|
654
|
+
| `GOOGLE_USER_FRACTION` | `--user-fraction` | 分阶段发布比例(0 与 1 之间) |
|
|
655
|
+
| `GOOGLE_CHANGES_NOT_SENT_FOR_REVIEW` | `--changes-not-sent-for-review` | 提交时 `changesNotSentForReview=true`(托管发布时变更留待人工送审);`1`/`true`/`yes`/`on` 开启 |
|
|
656
|
+
| `GOOGLE_VERBOSE` | `--verbose` | 开启运行日志:`1`/`true`/`yes`/`on` 开启,`0`/`false`/`no`/`off` 关闭 |
|
|
657
|
+
| `GOOGLE_TOKEN_URI` | `--token-uri` | 令牌接口地址;默认取服务账号 JSON 的 `token_uri`(`https://oauth2.googleapis.com/token`) |
|
|
658
|
+
| `GOOGLE_API_BASE` | —(仅环境变量) | 接口域名,默认 `https://androidpublisher.googleapis.com`;本地联调或代理时覆盖 |
|
|
659
|
+
|
|
660
|
+
示例(密钥全程只走环境变量,不出现在命令行参数里):
|
|
661
|
+
|
|
662
|
+
```bash
|
|
663
|
+
export GOOGLE_SERVICE_ACCOUNT=./google-play-service-account.json
|
|
664
|
+
export GOOGLE_PACKAGE_NAME=com.example.app
|
|
665
|
+
export GOOGLE_FILE=./app-release.aab
|
|
666
|
+
export GOOGLE_TRACK=internal
|
|
667
|
+
|
|
668
|
+
publish-apk google upload-aab
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
`upload-aab` 的 Token 来源优先级(高 → 低):命令行 JSON 参数 → 命令行分项参数(两者都是本地换取)→
|
|
672
|
+
`--token`(或 `GOOGLE_TOKEN`)→ 环境变量 JSON → 环境变量分项(本地换取)。
|
|
673
|
+
|
|
674
|
+
## 常见华为错误码
|
|
675
|
+
|
|
676
|
+
登记软件包时遇到的错误码,多数不是请求写法问题,而是**应用/版本当前状态**不允许修改:
|
|
677
|
+
|
|
678
|
+
| 错误码 | 华为描述 | 含义与处理 |
|
|
679
|
+
| --- | --- | --- |
|
|
680
|
+
| `204144641` | `The suffix name of the software package is incorrect` | 用 v3 接口登记 `.apk` 等非 `.app` 安装包。本工具已按后缀自动选接口;手写请求时 `.apk`/`.aab`/`.rpk`/`.hap` 要调 `/api/publish/v2/app-file-info` |
|
|
681
|
+
| `204144647` | `[cds]update service failed … can't be edited service` | 应用版本处于**审核中**(`app-info` 的 `releaseState` 为 `4`)或分阶段发布中,此时不允许更新文件信息。等版本回到可编辑状态(如 `0` 已上架、`7` 草稿)再试;注意华为**仍可能**把这次软件包加进「软件包管理」列表 |
|
|
682
|
+
| `204144743` | `app state can not be modified!` | 同上:应用正在审核(例如首次审核),不允许修改 |
|
|
683
|
+
|
|
684
|
+
## 常见 Google Play 错误
|
|
685
|
+
|
|
686
|
+
服务账号换取令牌成功不代表有发布权限;下面几类错误最常见:
|
|
687
|
+
|
|
688
|
+
| HTTP / 错误 | 含义与处理 |
|
|
689
|
+
| --- | --- |
|
|
690
|
+
| `401 invalid_grant` | 服务账号 JSON 与项目不匹配、私钥损坏,或本机时间偏差过大(JWT 的 `iat` / `exp`)。检查系统时间并重新下载密钥 |
|
|
691
|
+
| `403 PERMISSION_DENIED` | 服务账号未在 Play 控制台「用户和权限」中被邀请,或缺少对应轨道 / 正式版的发布权限 |
|
|
692
|
+
| `404 applicationNotFound` | `--package-name` 写错,或应用尚未在 Play 控制台创建 |
|
|
693
|
+
| `400` 上传或更新轨道失败 | 常见原因:`.aab` 损坏或签名不对、versionCode 已存在且未提升、上一版本还在处理中(稍后重试)、`inProgress` 缺少 `userFraction` |
|
|
694
|
+
|
|
695
|
+
## 输出与退出代码约定
|
|
696
|
+
|
|
697
|
+
- `stdout`:只写结果(JWT / `access_token` / 接口原始响应 JSON / `objectId` / `versionCode` / 证书、PKCS#12 与 P7B profile 信息 / `signingConfig` JSON),可直接被 `$(…)` 与 `jq` 取用。
|
|
698
|
+
- `stderr`:错误信息与 `--verbose` / `HUAWEI_VERBOSE` / `GOOGLE_VERBOSE` 的运行日志;日志中 **Token、`client_id`、私钥路径、服务账号路径只显示掩码**,私钥与服务账号 JSON 数据永不写入日志(`--file` 的本地路径按原样打印,它不是凭证,出错时正是靠它定位)。
|
|
699
|
+
- `cert` 的 `--verbose` / `CERT_VERBOSE` 是「输出全部证书 / 密钥库字段」而不是运行日志,信息统一写 `stdout`;PKCS#12 的私钥内容不会被输出。
|
|
700
|
+
- `profile` 的 `--verbose` / `PROFILE_VERBOSE` 输出全部 profile JSON 字段与证书字段,信息同样写 `stdout`。
|
|
701
|
+
- `huawei signing-config` 的 `stdout` 是 `signingConfig` JSON(含密文口令),material 路径、keytool 自检结果与 `--write` 备份路径写 `stderr`;明文口令不会打印。
|
|
702
|
+
- 用法错误(参数缺失、参数非法):先输出对应帮助文档(`stdout`)、再输出错误(`stderr`),退出代码 `1`。
|
|
703
|
+
- 退出代码:`0` 成功,`1` 参数缺失、参数错误或调用失败。
|
|
704
|
+
|
|
705
|
+
## Makefile
|
|
706
|
+
|
|
707
|
+
仓库自带 [Makefile](Makefile) 封装常用调用,`make help` 查看全部目标:
|
|
708
|
+
|
|
709
|
+
```bash
|
|
710
|
+
make help # 目标列表
|
|
711
|
+
make h # 根帮助
|
|
712
|
+
make cert CERT_FILE=./certpath.cer # 查看证书
|
|
713
|
+
CERT_PASSWORD=<密码> make cert CERT_FILE=./storeFile.p12 # 查看 PKCS#12(密码只走环境变量)
|
|
714
|
+
make cert-help # cert 命令帮助
|
|
715
|
+
make profile PROFILE_FILE=./profile.p7b # 查看 P7B Provisioning Profile
|
|
716
|
+
make profile-help # profile 命令帮助
|
|
717
|
+
make hap HAP_FILE=./app-signed.hap # 查看 HAP 安装包信息
|
|
718
|
+
make hap-help # hap 命令帮助
|
|
719
|
+
make app APP_FILE=./app-signed.app # 查看 APP 安装包信息
|
|
720
|
+
make app-help # app 命令帮助
|
|
721
|
+
make apk APK_FILE=./app-arm64-v8a-release.apk # 查看 APK 安装包信息
|
|
722
|
+
make apk-help # apk 命令帮助
|
|
723
|
+
HARMONY_STORE_PASSWORD=<p12口令> make huawei-signing-config \
|
|
724
|
+
HARMONY_STORE_FILE=./storeFile.p12 HARMONY_CERTPATH=./certpath.cer \
|
|
725
|
+
HARMONY_PROFILE=./profile.p7b HARMONY_SIGNING_NAME=default HARMONY_ARGS="--write"
|
|
726
|
+
make huawei-signing-config-help # 鸿蒙签名配置帮助
|
|
727
|
+
make huawei-help # huawei 子命令帮助
|
|
728
|
+
make huawei-service-account HUAWEI_KID=xxx HUAWEI_PRIVATE_KEY=./private.pem
|
|
729
|
+
make huawei-service-account-upload-url HUAWEI_APP_ID=xxx HUAWEI_FILE=./a1.apk
|
|
730
|
+
make huawei-service-account-upload-file HUAWEI_APP_ID=xxx HUAWEI_FILE=./a1.apk
|
|
731
|
+
make huawei-client-credentials HUAWEI_CLIENT_ID=xxx HUAWEI_CLIENT_SECRET=xxx
|
|
732
|
+
make huawei-client-credentials-upload-file HUAWEI_APP_ID=xxx HUAWEI_FILE=./a1.apk
|
|
733
|
+
make huawei-service-account-upload-file-package-info-update HUAWEI_APP_ID=xxx HUAWEI_FILE=./a1.apk
|
|
734
|
+
make google-help # google 子命令帮助
|
|
735
|
+
make google-access-token GOOGLE_SERVICE_ACCOUNT=./google-play-service-account.json
|
|
736
|
+
make google-upload-aab GOOGLE_PACKAGE_NAME=com.example.app GOOGLE_FILE=./app-release.aab GOOGLE_TRACK=internal
|
|
737
|
+
make google-upload-aab GOOGLE_PACKAGE_NAME=com.example.app GOOGLE_FILE=./app-release.aab \
|
|
738
|
+
GOOGLE_CLIENT_EMAIL=xxx@project.iam.gserviceaccount.com GOOGLE_PRIVATE_KEY=./service-account.pem
|
|
739
|
+
make pack-check # 查看 npm 发布物内容(npm pack --dry-run,只打印清单、不生成 .tgz)
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
设计约定:**只有 make 命令行上显式传入的变量才转成选项**;来自环境变量(含 `source .env`)的值一律不转发,交给命令自己读取,以免密钥进入命令行参数(`ps` 可见)或被 make 回显到 CI 日志。`HUAWEI_VERBOSE`、`GOOGLE_VERBOSE`、`CERT_VERBOSE`、`CERT_PASSWORD`、`PROFILE_VERBOSE`、`HAP_VERBOSE`、`APP_VERBOSE`、`APK_VERBOSE`、`HARMONY_STORE_PASSWORD`、`HARMONY_KEY_PASSWORD`、`GOOGLE_CHANGES_NOT_SENT_FOR_REVIEW`、`HUAWEI_API_BASE`、`GOOGLE_API_BASE`、`GOOGLE_TOKEN_URI` 无需转发——make 会把命令行变量导出给子进程,命令直接读取(开关型选项转成选项反而无法表达"关闭")。`CERT_PASSWORD`、`HARMONY_STORE_PASSWORD`、`HARMONY_KEY_PASSWORD` 还刻意不转成 `--password` / `--store-password` / `--key-password`,避免密码进入子进程的 argv。`GOOGLE_SERVICE_ACCOUNT_DATA` 含双引号、`GOOGLE_PRIVATE_KEY_DATA` 是多行 PEM,也刻意不作选项转发,同样由子进程从环境变量读取。
|
|
743
|
+
|
|
744
|
+
`make pack-check` 只做发布前检查:打印会打进 npm 包的文件清单,不改动工作区。`package.json` 的 `files` 只写了 `src`,但 npm 无论 `files` 怎么写都会带上 `package.json`、`README*` 与 `LICENSE*`(以及 `bin`/`main` 指向的文件),所以 `LICENSE` 与 `README.md` 不需要写进 `files`。
|
|
745
|
+
|
|
746
|
+
## 参考文档(上游)
|
|
747
|
+
|
|
748
|
+
### Node.js
|
|
749
|
+
|
|
750
|
+
- `X509Certificate`(`cert` 命令读取证书所用的内置能力):
|
|
751
|
+
<https://nodejs.org/api/crypto.html#class-x509certificate>
|
|
752
|
+
|
|
753
|
+
### OpenSSL
|
|
754
|
+
|
|
755
|
+
- `openssl-pkcs12`(`cert` 命令查看 PKCS#12 时调用的系统命令):
|
|
756
|
+
<https://docs.openssl.org/master/man1/openssl-pkcs12/>
|
|
757
|
+
|
|
758
|
+
### PKCS#7 / CMS
|
|
759
|
+
|
|
760
|
+
- PKCS #7 / CMS SignedData(`profile` 命令解析的 P7B 结构):
|
|
761
|
+
<https://datatracker.ietf.org/doc/html/rfc5652>
|
|
762
|
+
|
|
763
|
+
### 华为
|
|
764
|
+
|
|
765
|
+
- 获取服务端授权(服务器鉴权)——**Service Account 方式**与**API 客户端方式**都写在这一篇里:
|
|
766
|
+
<https://developer.huawei.com/consumer/cn/doc/doccenter-connect-api/agc-help-connect-api-obtain-server-auth-0000002271134661>
|
|
767
|
+
- 获取文件上传地址(Upload Management API):
|
|
768
|
+
<https://developer.huawei.com/consumer/cn/doc/doccenter-connect-api/agc-help-upload-api-upload-url-0000002236201294>
|
|
769
|
+
- 上传文件(Upload Management API):
|
|
770
|
+
<https://developer.huawei.com/consumer/cn/doc/doccenter-connect-api/agc-help-upload-api-upload-file-0000002271160621>
|
|
771
|
+
- 更新应用软件包信息(Publishing API,上传三步中的第三步,HarmonyOS 5 及以上,`.app`):
|
|
772
|
+
<https://developer.huawei.com/consumer/cn/doc/doccenter-connect-api/agc-help-publish-api-app-package-info-update-0000002236201250>
|
|
773
|
+
- 更新应用文件信息(Publishing API,上传三步中的第三步,HarmonyOS 3.1/4.0 及以下、Android,`.apk`/`.aab`/`.rpk`/`.hap`):
|
|
774
|
+
<https://developer.huawei.com/consumer/cn/doc/AppGallery-connect-References/agcapi-app-file-info-0000001111685202>
|
|
775
|
+
|
|
776
|
+
以上文档归华为所有;本项目仅在帮助文档中做中文转述并给出链接,不复制其正文。
|
|
777
|
+
|
|
778
|
+
### Google Play
|
|
779
|
+
|
|
780
|
+
- 接入准备(创建 Google Cloud 项目、启用 API、服务账号与 Play 控制台授权):
|
|
781
|
+
<https://developers.google.com/android-publisher/getting_started?hl=zh-cn>
|
|
782
|
+
- Edits(edit 的生命周期与提交):
|
|
783
|
+
<https://developers.google.com/android-publisher/edits?hl=zh-cn>
|
|
784
|
+
- 上传 `.aab`(`edits.bundles.upload`):
|
|
785
|
+
<https://developers.google.com/android-publisher/api-ref/rest/v3/edits.bundles/upload?hl=zh-cn>
|
|
786
|
+
- 轨道与发布(`Track` / `Release` 字段、发布状态):
|
|
787
|
+
<https://developers.google.com/android-publisher/tracks?hl=zh-cn>
|
|
788
|
+
- 服务账号 OAuth 2.0(JWT Bearer 换取 `access_token`):
|
|
789
|
+
<https://developers.google.com/identity/protocols/oauth2/service-account>
|
|
790
|
+
|
|
791
|
+
以上文档归 Google 所有;本项目仅在帮助文档中做中文转述并给出链接,不复制其正文。
|
|
792
|
+
|
|
793
|
+
## License
|
|
794
|
+
|
|
795
|
+
[MIT](LICENSE) © 2026 徐晓伟
|