@applift/core 0.2.0 → 0.4.0

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/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as StoresSchema, B as BundleIds, C as GlobalConfig, D as StoreConfig, E as ProjectConfigSchema, F as ChannelRegistry, G as electronAdapter, H as Platform, I as ReleasePlanEntry, J as nativeAndroidAdapter, K as findArtifacts, L as ReviewStatus, M as ChannelCapabilities, N as ChannelContext, O as StoreConfigSchema, P as ChannelEvent, Q as tauriAdapter, R as StageResult, S as CredentialsProfileSchema, T as ProjectConfig, U as detectBundleIds, V as FrameworkAdapter, W as detectFramework, X as nativeIosAdapter, Y as nativeHarmonyAdapter, Z as rnAdapter, _ as appendEntry, a as QypubUserConfig, b as CredentialsFileSchema, c as PipelineInput, d as runStage, f as runSubmit, g as HistoryQuery, h as HistoryEvent, i as QypubPlugin, j as Channel, k as Stores, l as PipelineSummary, m as HistoryEntry, n as PluginHookName, o as defineConfig, p as HISTORY_PATH, q as flutterAdapter, r as PluginHost, s as resolvePlugins, t as HookContext, u as PipelineSummaryEntry, v as readEntries, w as GlobalConfigSchema, x as CredentialsProfile, y as CredentialsFile, z as SubmitResult } from "./plugin-T6zXsmt1.mjs";
1
+ import { $ as rnAdapter, A as StoreConfigSchema, B as StageResult, C as CredentialsProfile, D as ProjectConfig, E as GlobalConfigSchema, F as ChannelContext, G as detectBundleIds, H as BundleIds, I as ChannelEvent, J as findArtifacts, K as detectFramework, L as ChannelRegistry, M as StoresSchema, N as Channel, O as ProjectConfigSchema, P as ChannelCapabilities, Q as nativeIosAdapter, R as ReleasePlanEntry, S as CredentialsFileSchema, T as GlobalConfig, U as FrameworkAdapter, V as SubmitResult, W as Platform, X as nativeAndroidAdapter, Y as flutterAdapter, Z as nativeHarmonyAdapter, _ as appendEntry, a as QypubUserConfig, b as BetaConfigSchema, c as PipelineInput, d as runStage, et as tauriAdapter, f as runSubmit, g as HistoryQuery, h as HistoryEvent, i as QypubPlugin, j as Stores, k as StoreConfig, l as PipelineSummary, m as HistoryEntry, n as PluginHookName, o as defineConfig, p as HISTORY_PATH, q as electronAdapter, r as PluginHost, s as resolvePlugins, t as HookContext, u as PipelineSummaryEntry, v as readEntries, w as CredentialsProfileSchema, x as CredentialsFile, y as BetaConfig, z as ReviewStatus } from "./plugin-DzhwzVfu.mjs";
2
2
  //#region src/registry.d.ts
3
3
  export declare class DefaultChannelRegistry implements ChannelRegistry {
4
4
  private readonly channels;
@@ -252,11 +252,9 @@ export declare class VivoChannel implements Channel {
252
252
  stage(ctx: ChannelContext): Promise<StageResult>;
253
253
  submit(ctx: ChannelContext): Promise<SubmitResult>;
254
254
  /**
255
- * 审核状态查询: 复用 app.query.details 的 data.auditStatus(对拍 python vivo_query.py;
256
- * 未确证的 app.audit.status method 已删——python/apkgo 均无此调用)。
257
- * 映射 0=审核通过 1=审核中 2=驳回 沿自 v1,待真机校准;
258
- * 注意:该编号映射未在现行中国站文档成文(国际站同型接口字典编号整体错一位),需真机校准;
259
- * 交叉见证:apkgo vivo.go 读 data.status 且编号不同(1草稿/2待审核/3通过/4不通过/5撤销),冲突以真机为准。
255
+ * 审核状态查询: 复用 app.query.details 的 data.status(apkgo vivo.go mapVivoAuditState 权威,
256
+ * 2026-10 源码仲裁:编号 1=草稿 2=待审核 3=审核通过 4=审核不通过 5=已撤销;
257
+ * 旧 auditStatus 字段与 0/1/2 编号表系 v1 python 误记,已弃用)。
260
258
  */
261
259
  status(ctx: Pick<ChannelContext, "credentials" | "target">): Promise<ReviewStatus>;
262
260
  /** 驳回原因字段名文档未明确,按常见命名兜底取(auditMessage/auditMsg) */
@@ -313,7 +311,7 @@ export declare function getHarmonyAppId(opts: {
313
311
  bundleName: string;
314
312
  }): Promise<string>;
315
313
  export declare class AgcHarmonyChannel implements Channel {
316
- readonly id = "agc-harmony";
314
+ readonly id = "agc";
317
315
  readonly label = "华为 AGC(鸿蒙)";
318
316
  readonly capabilities: ChannelCapabilities;
319
317
  readonly errorHints: {
@@ -323,6 +321,7 @@ export declare class AgcHarmonyChannel implements Channel {
323
321
  "1003": string;
324
322
  "204144702": string;
325
323
  "204144677": string;
324
+ "204144692": string;
326
325
  };
327
326
  private authHeaders;
328
327
  /** appId 缺省时按 bundleName 查(v1 全链路 step 0,可选) */
@@ -340,10 +339,16 @@ export declare class AgcHarmonyChannel implements Channel {
340
339
  /**
341
340
  * 测试发布(releaseType=6,2026-10 起走 Testing API——旧 app-package-info 直绑链路已被华为下线,
342
341
  * 其 releaseType 枚举仅剩 1=全网 / 7=企业非公开):
343
- * 上传 .hap → 新建测试版本(testType=3 邀请测试)→ 添加测试软件包 → 等云端编译 → 更新测试版本绑定;
344
- * 不自动提交审核,测试群组/邀请码由控制台人工完成。
342
+ * 上传 .app → 新建测试版本(testType 3=邀请测试 4=公开测试)→ 添加测试软件包 → 等云端编译 → 更新测试版本绑定;
343
+ * target.submitTest='true' 时:(可选按 testGroup 绑测试群组)自动提交测试版本审核;否则由控制台人工完成。
345
344
  */
346
345
  private uploadBeta;
346
+ /**
347
+ * 按群组名查测试群组 groupId(AppTest API GET /test-group/list,appId 走 header,rtnCode 外壳)。
348
+ * 分页遍历;匹配不到报错提示到控制台建组。
349
+ * 文档: agc-help-test-api-get-test-grouplist-0000002271000717
350
+ */
351
+ private resolveTestGroupId;
347
352
  /** v1 更新文案段(releaseHarmonyApp step 4 / uploadToAgc 尾段):查默认语言 → 写 newFeatures,失败仅警告不阻断 */
348
353
  private updateReleaseNotes;
349
354
  }
@@ -407,4 +412,4 @@ export interface ArtifactVersion {
407
412
  }
408
413
  export declare function readArtifactVersion(artifactPath: string): ArtifactVersion | undefined;
409
414
  //#endregion
410
- export { BundleIds, Channel, ChannelCapabilities, ChannelContext, ChannelEvent, ChannelRegistry, CredentialsFile, CredentialsFileSchema, CredentialsProfile, CredentialsProfileSchema, FrameworkAdapter, GlobalConfig, GlobalConfigSchema, HISTORY_PATH, HistoryEntry, HistoryEvent, HistoryQuery, HookContext, PipelineInput, PipelineSummary, PipelineSummaryEntry, Platform, PluginHookName, PluginHost, ProjectConfig, ProjectConfigSchema, QypubPlugin, QypubUserConfig, ReleasePlanEntry, ReviewStatus, StageResult, StoreConfig, StoreConfigSchema, Stores, StoresSchema, SubmitResult, appendEntry, defineConfig, detectBundleIds, detectFramework, electronAdapter, findArtifacts, flutterAdapter, nativeAndroidAdapter, nativeHarmonyAdapter, nativeIosAdapter, readEntries, resolvePlugins, rnAdapter, runStage, runSubmit, tauriAdapter };
415
+ export { BetaConfig, BetaConfigSchema, BundleIds, Channel, ChannelCapabilities, ChannelContext, ChannelEvent, ChannelRegistry, CredentialsFile, CredentialsFileSchema, CredentialsProfile, CredentialsProfileSchema, FrameworkAdapter, GlobalConfig, GlobalConfigSchema, HISTORY_PATH, HistoryEntry, HistoryEvent, HistoryQuery, HookContext, PipelineInput, PipelineSummary, PipelineSummaryEntry, Platform, PluginHookName, PluginHost, ProjectConfig, ProjectConfigSchema, QypubPlugin, QypubUserConfig, ReleasePlanEntry, ReviewStatus, StageResult, StoreConfig, StoreConfigSchema, Stores, StoresSchema, SubmitResult, appendEntry, defineConfig, detectBundleIds, detectFramework, electronAdapter, findArtifacts, flutterAdapter, nativeAndroidAdapter, nativeHarmonyAdapter, nativeIosAdapter, readEntries, resolvePlugins, rnAdapter, runStage, runSubmit, tauriAdapter };
package/dist/index.mjs CHANGED
@@ -3,6 +3,7 @@ import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import os from "node:os";
5
5
  import { parse } from "smol-toml";
6
+ import { fileURLToPath } from "node:url";
6
7
  import { loadConfig } from "c12";
7
8
  import { createJiti } from "jiti";
8
9
  import crypto, { X509Certificate, constants, createHash, createHmac, createSign, publicEncrypt } from "node:crypto";
@@ -47,7 +48,32 @@ const StoresSchema = z.union([z.record(z.string(), z.union([StoreConfigSchema, z
47
48
  if (Array.isArray(stores)) return Object.fromEntries(stores.map((id) => [id, {}]));
48
49
  return Object.fromEntries(Object.entries(stores).map(([id, cfg]) => [id, typeof cfg === "string" ? { appId: cfg } : cfg]));
49
50
  }).default({});
50
- /** 项目级配置(applift.config.ts 的形状) */
51
+ /** 项目级配置(applift.config.ts 的形状);beta 段见上方 BetaConfigSchema */
52
+ /**
53
+ * 内测分发配置(applift beta 无平台参数时并行发布的渠道开关):
54
+ * - pgyer: 蒲公英多平台上传(缺省只发 android)
55
+ * - agc: AGC 邀请测试(Testing API,submit=true 上传绑定后自动提审)
56
+ * - testflight: App Store Connect 上传(ipa 进 TestFlight)
57
+ */
58
+ const BetaConfigSchema = z.object({
59
+ pgyer: z.object({
60
+ platforms: z.array(z.enum([
61
+ "android",
62
+ "harmony",
63
+ "ios"
64
+ ])).default(["android"]),
65
+ installPassword: z.string().optional()
66
+ }).optional(),
67
+ agc: z.object({
68
+ /** 上传绑定后自动提交测试审核(默认 false:只到「已就绪」,群组/提审到控制台人工完成) */
69
+ submit: z.boolean().default(false),
70
+ /** 测试群组名(submit 时按名查 groupId 绑定;匹配不到报错提示到控制台建组) */
71
+ group: z.string().optional(),
72
+ /** 3=邀请测试(默认)4=公开测试 */
73
+ testType: z.union([z.literal(3), z.literal(4)]).default(3)
74
+ }).optional(),
75
+ testflight: z.object({ note: z.string().optional() }).optional()
76
+ });
51
77
  const ProjectConfigSchema = z.object({
52
78
  /** 项目显示名 */
53
79
  name: z.string().optional(),
@@ -83,7 +109,9 @@ const ProjectConfigSchema = z.object({
83
109
  windows: z.array(z.string()).optional(),
84
110
  macos: z.array(z.string()).optional(),
85
111
  linux: z.array(z.string()).optional()
86
- }).catchall(z.array(z.string()).optional()).optional()
112
+ }).catchall(z.array(z.string()).optional()).optional(),
113
+ /** 内测分发(beta 无平台参数时的多渠道并行配置) */
114
+ beta: BetaConfigSchema.optional()
87
115
  });
88
116
  /** 全局行为配置(~/.applift/config.toml) */
89
117
  const GlobalConfigSchema = z.object({
@@ -184,11 +212,33 @@ function saveCredentials(data, filePath = CREDENTIALS_PATH) {
184
212
  * 项目配置装载:c12(社区主流)加载 applift.config.ts。
185
213
  * c12 负责:TS 配置文件执行、缺省值、多源合并 —— 非自研。
186
214
  */
215
+ /**
216
+ * 裸工程兼容:applift.config.ts 里的 `@applift/core` / `@applift/cli` 导入按目标工程解析会
217
+ * 找不到包(工程未安装依赖)——jiti 别名统一指到 CLI 进程自带的那份 core 入口,
218
+ * 使 `import { defineConfig } from "@applift/cli"`(或 @applift/core)在任何工程可用。
219
+ */
220
+ function appliftAlias() {
221
+ try {
222
+ const distEntry = fileURLToPath(import.meta.resolve("@applift/core"));
223
+ const coreEntry = fs.existsSync(distEntry) ? distEntry : path.join(path.dirname(path.dirname(distEntry)), "src", "index.ts");
224
+ if (!fs.existsSync(coreEntry)) return {};
225
+ return {
226
+ "@applift/core": coreEntry,
227
+ "@applift/cli": coreEntry
228
+ };
229
+ } catch {
230
+ return {};
231
+ }
232
+ }
187
233
  async function loadProjectConfig(cwd) {
188
234
  const cfgPath = findUp(cwd, "applift.config.ts");
189
235
  const cfgDir = cfgPath ? path.dirname(cfgPath) : cwd;
190
236
  const bunRuntime = globalThis.Bun;
191
- const jitiInstance = bunRuntime ? createJiti(import.meta.url, { transform: (opts) => ({ code: new bunRuntime.Transpiler({ loader: "ts" }).transformSync(opts.source) }) }) : void 0;
237
+ const alias = appliftAlias();
238
+ const jitiInstance = bunRuntime ? createJiti(import.meta.url, {
239
+ alias,
240
+ transform: (opts) => ({ code: new bunRuntime.Transpiler({ loader: "ts" }).transformSync(opts.source) })
241
+ }) : Object.keys(alias).length ? createJiti(import.meta.url, { alias }) : void 0;
192
242
  const result = await loadConfig({
193
243
  name: "applift",
194
244
  cwd: cfgDir,
@@ -855,9 +905,9 @@ function required$5(ctx, key, where) {
855
905
  * sig = RSA_encrypt(证书公钥, JSON({sig: [{name, hash: MD5}...], password}))
856
906
  * 分段: 1024bit key → 每段 117 字节(128 - PKCS1 padding 11)
857
907
  * 链路: /dev/query 校验账号对包名权限 + 提审前版本回归预检 → /dev/push 上传即提审(synchroType=1 更新包,
858
- * 无法只传包不提审)。现行文档(2026-09 更新)文件参数表 icon 标「必选」、appInfo.privacyUrl 标「必选」,
859
- * 旧版「未传字段如 icon/privacyUrl 不覆盖线上值」表述已不被现行文档支持;synchroType=1 不带素材是否仍被
860
- * 受理待真机验证,若被拒需补传 icon 文件(其 MD5 追加进 SIG 的 sig 数组)。
908
+ * icon 现行文档(pId=1134,2026-02)标必选,apkgo 同(每次 push 必带);旧官方 PDF(pId=33)标可选,
909
+ * 分歧按现行文档从严处理——applift 默认从工程 mipmap 自动发现(android.stores.xiaomi.icon 可覆盖),
910
+ * 未找到才中止;privacyUrl 是否强制仍待真机验证(apkgo 未传该字段)。
861
911
  * 另 2026-02-04 起 testAccount 须用新结构化格式(如 {"zh_CN":{"accounts":[...]}}),当前未传暂不受影响。
862
912
  * 移植自(MIT): AngChow/android-publish scripts/_mi_common.py + mi_submit.py
863
913
  * 官方文档: https://dev.mi.com/xiaomihyperos/documentation/detail?pId=1134
@@ -959,6 +1009,9 @@ var XiaomiChannel = class {
959
1009
  const localVc = Number(ctx.target.versionCode);
960
1010
  const liveVc = Number(query.packageInfo?.onlineVersionCode ?? query.packageInfo?.versionCode);
961
1011
  if (Number.isFinite(localVc) && Number.isFinite(liveVc) && localVc < liveVc) throw new Error(`小米提审中止:待提审 versionCode ${localVc} 低于线上 ${liveVc}(版本回退),请确认 APK 版本或项目配置`);
1012
+ const iconPath = String(ctx.target.icon ?? "");
1013
+ if (!iconPath) throw new Error("小米提审缺少 icon:applift.config.ts → android.stores.xiaomi.icon 配置图标路径(官方必选,apkgo 同)");
1014
+ if (!fs.existsSync(iconPath)) throw new Error(`小米 icon 文件不存在: ${iconPath}`);
962
1015
  const appInfo = {
963
1016
  appName,
964
1017
  packageName: pkgName,
@@ -970,7 +1023,7 @@ var XiaomiChannel = class {
970
1023
  synchroType: 1
971
1024
  };
972
1025
  const requestJson = JSON.stringify(request);
973
- const sig = generateMiSig(requestJson, [["apk", ctx.artifactPath]], password, certPem);
1026
+ const sig = generateMiSig(requestJson, [["apk", ctx.artifactPath], ["icon", iconPath]], password, certPem);
974
1027
  const fileName = path.basename(ctx.artifactPath);
975
1028
  ctx.emit({
976
1029
  type: "phase",
@@ -981,6 +1034,7 @@ var XiaomiChannel = class {
981
1034
  form.append("RequestData", requestJson);
982
1035
  form.append("SIG", sig);
983
1036
  form.append("apk", new Blob([fs.readFileSync(ctx.artifactPath)]), fileName);
1037
+ form.append("icon", new Blob([fs.readFileSync(iconPath)]), path.basename(iconPath));
984
1038
  const res = await fetch(`${MI_API_BASE}/dev/push`, {
985
1039
  method: "POST",
986
1040
  body: form,
@@ -1356,6 +1410,7 @@ var OppoChannel = class {
1356
1410
  type: "warn",
1357
1411
  message: inReview ? "errno=911215 应用审核中:该版本已在 OPPO 审核队列,本次未重复创建任务" : "errno=911216 版本更新任务处理中:上一提交任务仍在处理,本次未创建新任务,稍后可用 status 查询审核状态"
1358
1412
  });
1413
+ if (!inReview) await this.pollTaskState(ctx, auth, versionCode);
1359
1414
  ctx.emit({
1360
1415
  type: "phase",
1361
1416
  phase: "done",
@@ -1502,7 +1557,7 @@ function commonParams(accessKey, method) {
1502
1557
  timestamp: tsMillis(),
1503
1558
  format: "json",
1504
1559
  v: "1.0",
1505
- sign_method: "hmac",
1560
+ sign_method: "hmac-sha256",
1506
1561
  target_app_key: "developer"
1507
1562
  };
1508
1563
  }
@@ -1661,38 +1716,44 @@ var VivoChannel = class {
1661
1716
  };
1662
1717
  }
1663
1718
  /**
1664
- * 审核状态查询: 复用 app.query.details 的 data.auditStatus(对拍 python vivo_query.py;
1665
- * 未确证的 app.audit.status method 已删——python/apkgo 均无此调用)。
1666
- * 映射 0=审核通过 1=审核中 2=驳回 沿自 v1,待真机校准;
1667
- * 注意:该编号映射未在现行中国站文档成文(国际站同型接口字典编号整体错一位),需真机校准;
1668
- * 交叉见证:apkgo vivo.go 读 data.status 且编号不同(1草稿/2待审核/3通过/4不通过/5撤销),冲突以真机为准。
1719
+ * 审核状态查询: 复用 app.query.details 的 data.status(apkgo vivo.go mapVivoAuditState 权威,
1720
+ * 2026-10 源码仲裁:编号 1=草稿 2=待审核 3=审核通过 4=审核不通过 5=已撤销;
1721
+ * 旧 auditStatus 字段与 0/1/2 编号表系 v1 python 误记,已弃用)。
1669
1722
  */
1670
1723
  async status(ctx) {
1671
1724
  required$3(ctx, "pkgName", "审核状态查询必需");
1672
1725
  const data = await this.queryDetails(ctx);
1673
- const auditStatus = data.auditStatus ?? data.audit_status;
1674
- if (auditStatus === void 0) return {
1726
+ const st = data.status ?? data.auditStatus ?? data.audit_status;
1727
+ if (st === void 0) return {
1675
1728
  state: "unknown",
1676
- raw: `app.query.details 无 auditStatus: ${JSON.stringify(data).slice(0, 200)}`
1729
+ raw: `app.query.details 无 status: ${JSON.stringify(data).slice(0, 200)}`
1677
1730
  };
1678
- const num = Number(auditStatus);
1731
+ const num = Number(st);
1679
1732
  if (!Number.isFinite(num)) return {
1680
1733
  state: "unknown",
1681
- raw: `auditStatus=${String(auditStatus)}`
1734
+ raw: `status=${String(st)}`
1682
1735
  };
1683
1736
  switch (num) {
1684
- case 0: return {
1737
+ case 2: return { state: "reviewing" };
1738
+ case 3: return {
1685
1739
  state: "approved",
1686
1740
  live: true
1687
1741
  };
1688
- case 1: return { state: "reviewing" };
1689
- case 2: return {
1742
+ case 4: return {
1690
1743
  state: "rejected",
1691
1744
  reason: this.auditReason(data)
1692
1745
  };
1746
+ case 1: return {
1747
+ state: "unknown",
1748
+ raw: "status=1(草稿,未提交审核)"
1749
+ };
1750
+ case 5: return {
1751
+ state: "unknown",
1752
+ raw: "status=5(已撤销)"
1753
+ };
1693
1754
  default: return {
1694
1755
  state: "unknown",
1695
- raw: `auditStatus=${num}(未验证编号)`
1756
+ raw: `status=${num}(未验证编号)`
1696
1757
  };
1697
1758
  }
1698
1759
  }
@@ -1952,6 +2013,55 @@ function required$2(ctx, key, where) {
1952
2013
  return v;
1953
2014
  }
1954
2015
  //#endregion
2016
+ //#region src/channels/shared/apk-abi.ts
2017
+ /**
2018
+ * APK(zip)CPU ABI 探测——纯 Node 解析 zip 中央目录(零依赖、不解压内容)。
2019
+ * 用途:应用宝 update_app 的 apk32_flag/apk64_flag 分支(apkgo tencent.go isAPK64BitOnly 同构)。
2020
+ */
2021
+ const EOCD_MAGIC = 101010256;
2022
+ const CEN_MAGIC = 33639248;
2023
+ /** 列出 APK 内 lib/<abi>/ 出现过的 ABI 名;非 zip/无 lib 段返回空数组(按 32/64 兼容处理) */
2024
+ function listApkLibAbis(apkPath) {
2025
+ const fd = fs.openSync(apkPath, "r");
2026
+ try {
2027
+ const size = fs.fstatSync(fd).size;
2028
+ const tailLen = Math.min(size, 65557);
2029
+ const tail = Buffer.alloc(tailLen);
2030
+ fs.readSync(fd, tail, 0, tailLen, size - tailLen);
2031
+ let eocd = -1;
2032
+ for (let i = tail.length - 22; i >= 0; i--) if (tail.readUInt32LE(i) === EOCD_MAGIC) {
2033
+ eocd = i;
2034
+ break;
2035
+ }
2036
+ if (eocd < 0) return [];
2037
+ const count = tail.readUInt16LE(eocd + 10);
2038
+ let offset = tail.readUInt32LE(eocd + 16);
2039
+ const abis = /* @__PURE__ */ new Set();
2040
+ for (let i = 0; i < count; i++) {
2041
+ const hdr = Buffer.alloc(46);
2042
+ if (fs.readSync(fd, hdr, 0, 46, offset) < 46) break;
2043
+ if (hdr.readUInt32LE(0) !== CEN_MAGIC) break;
2044
+ const nameLen = hdr.readUInt16LE(28);
2045
+ const extraLen = hdr.readUInt16LE(30);
2046
+ const commentLen = hdr.readUInt16LE(32);
2047
+ const nameBuf = Buffer.alloc(nameLen);
2048
+ if (fs.readSync(fd, nameBuf, 0, nameLen, offset + 46) < nameLen) break;
2049
+ const m = nameBuf.toString("utf8").match(/^lib\/([^/]+)\//);
2050
+ if (m?.[1]) abis.add(m[1]);
2051
+ offset += 46 + nameLen + extraLen + commentLen;
2052
+ }
2053
+ return [...abis];
2054
+ } finally {
2055
+ fs.closeSync(fd);
2056
+ }
2057
+ }
2058
+ /** 是否 arm64-only 包:含 lib/ 且全部为 arm64-v8a(apkgo isAPK64BitOnly 同构;无 native 库返回 false) */
2059
+ function isApkArm64Only(apkPath) {
2060
+ const abis = listApkLibAbis(apkPath);
2061
+ if (abis.length === 0) return false;
2062
+ return abis.every((a) => a === "arm64-v8a");
2063
+ }
2064
+ //#endregion
1955
2065
  //#region src/channels/yyb.ts
1956
2066
  /**
1957
2067
  * 腾讯应用宝(安卓 APK)渠道。
@@ -2086,9 +2196,16 @@ var YybChannel = class {
2086
2196
  const businessParams = {
2087
2197
  pkg_name: pkgName,
2088
2198
  app_id: appId,
2089
- apk32_flag: "1",
2090
- apk32_file_serial_number: stagedInfo.serialNumber,
2091
- apk32_file_md5: stagedInfo.md5,
2199
+ ...isApkArm64Only(ctx.artifactPath) ? {
2200
+ apk32_flag: "2",
2201
+ apk64_flag: "1",
2202
+ apk64_file_serial_number: stagedInfo.serialNumber,
2203
+ apk64_file_md5: stagedInfo.md5
2204
+ } : {
2205
+ apk32_flag: "1",
2206
+ apk32_file_serial_number: stagedInfo.serialNumber,
2207
+ apk32_file_md5: stagedInfo.md5
2208
+ },
2092
2209
  feature: ctx.updateDescription ?? ""
2093
2210
  };
2094
2211
  if (ctx.releaseTime) {
@@ -2180,6 +2297,8 @@ var YybChannel = class {
2180
2297
  const BASE = "https://connect-api.cloud.huawei.com/api";
2181
2298
  const COMPILE_TIMEOUT_MS = 9e5;
2182
2299
  const COMPILE_POLL_MS = 15e3;
2300
+ /** AppTest 测试群组 API 前缀(与 publish API 同域不同路径段) */
2301
+ const APP_TEST_BASE = "https://connect-api.cloud.huawei.com/api/app-test/v1";
2183
2302
  /** AGC JSON API 封装:判 ret.code !== 0 抛错 —— v1 agc.mjs agcJson 原样 */
2184
2303
  async function agcJson(url, init, context) {
2185
2304
  const res = await fetch(url, init);
@@ -2225,7 +2344,7 @@ async function getDefaultLang(headers, appId) {
2225
2344
  return (await agcJson(`${BASE}/publish/v3/app-info?${qs}`, { headers }, "查询应用信息")).appInfo?.defaultLang || "zh-CN";
2226
2345
  }
2227
2346
  var AgcHarmonyChannel = class {
2228
- id = "agc-harmony";
2347
+ id = "agc";
2229
2348
  label = "华为 AGC(鸿蒙)";
2230
2349
  capabilities = {
2231
2350
  stage: true,
@@ -2239,7 +2358,8 @@ var AgcHarmonyChannel = class {
2239
2358
  "204144660": "提交失败,包可能仍在解析(稍后重试或到控制台查看)",
2240
2359
  "1003": "client_id/client_secret 无效(AGC → 项目设置 → 常规 → API Client 重新获取)",
2241
2360
  "204144702": "签名/证书不匹配:检查签名证书指纹(SHA256)与 AGC 后台登记是否一致",
2242
- "204144677": "bundleName 与应用不匹配:确认 appId 对应的应用包名"
2361
+ "204144677": "bundleName 与应用不匹配:确认 appId 对应的应用包名",
2362
+ "204144692": "应用未登记隐私政策协议:AGC 控制台「更多服务 → 协议服务」新建隐私政策并提交(一次性前置,API 无法代办)"
2243
2363
  };
2244
2364
  async authHeaders(ctx) {
2245
2365
  const clientId = required$1(ctx, "AGC_CLIENT_ID");
@@ -2278,7 +2398,7 @@ var AgcHarmonyChannel = class {
2278
2398
  artifactVersion: "",
2279
2399
  action: "stage-only",
2280
2400
  afterApproval: "none(beta)",
2281
- notes: "Testing API 邀请测试:上传→新建测试版本→绑定软件包;提交审核与测试群组到控制台完成,不自动提审"
2401
+ notes: ctx.target.submitTest === "true" ? "Testing API 邀请测试:上传→新建测试版本→绑定软件包→(可选绑测试群组)自动提交测试审核" : "Testing API 邀请测试:上传→新建测试版本→绑定软件包;提交审核与测试群组到控制台完成,不自动提审"
2282
2402
  };
2283
2403
  return {
2284
2404
  channel: this.id,
@@ -2309,7 +2429,7 @@ var AgcHarmonyChannel = class {
2309
2429
  async submit(ctx) {
2310
2430
  if (ctx.target.releaseType === "6") return {
2311
2431
  ok: false,
2312
- error: "AGC 测试发布(releaseType=6)不做自动提审,请到 AGC 控制台「测试」提交测试版本并指定测试群组"
2432
+ error: "AGC 测试发布(releaseType=6)的自动提审走 beta 配置 test['agc'].submit=true(上传绑定后自动提交测试版本审核)"
2313
2433
  };
2314
2434
  const { clientId, token, headers } = await this.authHeaders(ctx);
2315
2435
  const appId = await this.resolveAppId(ctx, clientId, token);
@@ -2470,8 +2590,8 @@ var AgcHarmonyChannel = class {
2470
2590
  /**
2471
2591
  * 测试发布(releaseType=6,2026-10 起走 Testing API——旧 app-package-info 直绑链路已被华为下线,
2472
2592
  * 其 releaseType 枚举仅剩 1=全网 / 7=企业非公开):
2473
- * 上传 .hap → 新建测试版本(testType=3 邀请测试)→ 添加测试软件包 → 等云端编译 → 更新测试版本绑定;
2474
- * 不自动提交审核,测试群组/邀请码由控制台人工完成。
2593
+ * 上传 .app → 新建测试版本(testType 3=邀请测试 4=公开测试)→ 添加测试软件包 → 等云端编译 → 更新测试版本绑定;
2594
+ * target.submitTest='true' 时:(可选按 testGroup 绑测试群组)自动提交测试版本审核;否则由控制台人工完成。
2475
2595
  */
2476
2596
  async uploadBeta(ctx, appId, headers) {
2477
2597
  const fileName = path.basename(ctx.artifactPath);
@@ -2518,6 +2638,7 @@ var AgcHarmonyChannel = class {
2518
2638
  type: "progress",
2519
2639
  detail: "文件上传完成"
2520
2640
  });
2641
+ const testType = ctx.target.testType === "4" ? 4 : 3;
2521
2642
  const versionId = (await agcJson(`${BASE}/publish/v2/test/app/version?appId=${encodeURIComponent(appId)}`, {
2522
2643
  method: "POST",
2523
2644
  headers: {
@@ -2526,7 +2647,7 @@ var AgcHarmonyChannel = class {
2526
2647
  },
2527
2648
  body: JSON.stringify({
2528
2649
  releaseType: 6,
2529
- testType: 3,
2650
+ testType,
2530
2651
  testDesc: (ctx.updateDescription ?? "applift 测试发布").slice(0, 50)
2531
2652
  })
2532
2653
  }, "新建测试版本")).versionId ?? "";
@@ -2563,16 +2684,92 @@ var AgcHarmonyChannel = class {
2563
2684
  pkgId
2564
2685
  })
2565
2686
  }, "绑定测试软件包");
2687
+ if (ctx.target.submitTest === "true") {
2688
+ const groupName = ctx.target.testGroup?.trim();
2689
+ if (groupName) {
2690
+ const groupId = await this.resolveTestGroupId(ctx, appId, groupName, headers);
2691
+ await agcJson(`${BASE}/publish/v2/test/app/version?appId=${encodeURIComponent(appId)}`, {
2692
+ method: "PUT",
2693
+ headers: {
2694
+ ...headers,
2695
+ "Content-Type": "application/json"
2696
+ },
2697
+ body: JSON.stringify({
2698
+ versionId,
2699
+ pkgId,
2700
+ openTestInfo: { testTaskInfo: { groupInfos: [{
2701
+ groupId,
2702
+ groupName
2703
+ }] } }
2704
+ })
2705
+ }, "绑定测试群组");
2706
+ ctx.emit({
2707
+ type: "progress",
2708
+ detail: `测试群组「${groupName}」已绑定(groupId: ${groupId})`
2709
+ });
2710
+ }
2711
+ ctx.emit({
2712
+ type: "phase",
2713
+ phase: "submit",
2714
+ message: "提交测试版本审核…"
2715
+ });
2716
+ await agcJson(`${BASE}/publish/v2/test/app/version/submit?appId=${encodeURIComponent(appId)}`, {
2717
+ method: "POST",
2718
+ headers: {
2719
+ ...headers,
2720
+ "Content-Type": "application/json"
2721
+ },
2722
+ body: JSON.stringify({ versionId })
2723
+ }, "提交测试审核");
2724
+ ctx.emit({
2725
+ type: "phase",
2726
+ phase: "done",
2727
+ message: `测试版本已提交审核(versionId: ${versionId})`
2728
+ });
2729
+ return {
2730
+ ok: true,
2731
+ ref: versionId
2732
+ };
2733
+ }
2566
2734
  ctx.emit({
2567
2735
  type: "phase",
2568
2736
  phase: "done",
2569
- message: "测试版本已就绪(未提交审核);到 AGC 控制台「测试」提交并指定测试群组"
2737
+ message: "测试版本已就绪(未提交审核);到控制台提交或配置 test['agc'].submit 自动提审"
2570
2738
  });
2571
2739
  return {
2572
2740
  ok: true,
2573
2741
  ref: versionId
2574
2742
  };
2575
2743
  }
2744
+ /**
2745
+ * 按群组名查测试群组 groupId(AppTest API GET /test-group/list,appId 走 header,rtnCode 外壳)。
2746
+ * 分页遍历;匹配不到报错提示到控制台建组。
2747
+ * 文档: agc-help-test-api-get-test-grouplist-0000002271000717
2748
+ */
2749
+ async resolveTestGroupId(ctx, appId, groupName, headers) {
2750
+ ctx.emit({
2751
+ type: "progress",
2752
+ detail: `按群组名「${groupName}」查询 AGC 测试群组…`
2753
+ });
2754
+ for (let current = 1; current <= 50; current++) {
2755
+ const qs = new URLSearchParams({
2756
+ current: String(current),
2757
+ pageSize: "50"
2758
+ });
2759
+ const res = await fetch(`${APP_TEST_BASE}/test-group/list?${qs}`, { headers: {
2760
+ ...headers,
2761
+ appId
2762
+ } });
2763
+ if (!res.ok) throw new Error(`AGC 查询测试群组失败 HTTP ${res.status}`);
2764
+ const json = await res.json();
2765
+ if (Number(json.rtnCode) !== 0) throw new Error(`AGC 查询测试群组失败: rtnCode=${json.rtnCode ?? "?"}${json.rtnDesc ? ` ${json.rtnDesc}` : ""}`);
2766
+ const hit = json.groups?.find((g) => g.groupName === groupName);
2767
+ if (hit?.groupId) return hit.groupId;
2768
+ const totalPage = json.pageInfo?.totalPage ?? current;
2769
+ if (current >= totalPage) break;
2770
+ }
2771
+ throw new Error(`AGC 未找到名为「${groupName}」的测试群组:到 AGC 控制台「测试 → 测试群组」创建后再试`);
2772
+ }
2576
2773
  /** v1 更新文案段(releaseHarmonyApp step 4 / uploadToAgc 尾段):查默认语言 → 写 newFeatures,失败仅警告不阻断 */
2577
2774
  async updateReleaseNotes(ctx, appId, headers, qs) {
2578
2775
  if (!ctx.updateDescription) return;
@@ -3577,7 +3774,10 @@ const nativeHarmonyAdapter = {
3577
3774
  return [
3578
3775
  "entry/build/default/outputs/**/entry-*-signed.hap",
3579
3776
  "entry/build/default/outputs/**/entry-*.app",
3580
- "harmony/entry/build/default/outputs/**/*-signed.hap"
3777
+ "harmony/entry/build/default/outputs/**/*-signed.hap",
3778
+ "build/outputs/**/*.app",
3779
+ "harmony/build/outputs/**/*.app",
3780
+ "ohos/build/outputs/**/*.app"
3581
3781
  ];
3582
3782
  },
3583
3783
  buildCommand() {
@@ -3923,4 +4123,4 @@ function fromAapt(artifactPath) {
3923
4123
  }
3924
4124
  }
3925
4125
  //#endregion
3926
- export { APPLIFT_HOME, AgcHarmonyChannel, AscChannel, CREDENTIALS_PATH, CredentialsFileSchema, CredentialsProfileSchema, DefaultChannelRegistry, GLOBAL_CONFIG_PATH, GlobalConfigSchema, GooglePlayChannel, HISTORY_PATH, HonorChannel, HuaweiAndroidChannel, OppoChannel, PROJECTS_MAP_PATH, PgyerChannel, PluginHost, ProjectConfigSchema, StoreConfigSchema, StoresSchema, VivoChannel, WAIT_BEFORE_SUBMIT_MS, XiaomiChannel, YybChannel, appendEntry, assertListingFiles, defineConfig, detectBundleIds, detectFramework, electronAdapter, findArtifacts, findUp, flutterAdapter, formPost, getAgcToken, getHarmonyAppId, hmacSign, humanizeError, loadCredentials, loadCredentialsLayered, loadGlobalConfig, loadListingFile, loadProjectConfig, loadProjectProfileMap, md5File, md5String, nativeAndroidAdapter, nativeHarmonyAdapter, nativeIosAdapter, projectCredentialsPath, readArtifactVersion, readEntries, registry, resolveListing, resolvePlugins, resolveProfile, resolveProfileFor, rnAdapter, runStage, runSubmit, saveCredentials, saveProjectProfileMap, setProjectProfile, sha256File, sortedQueryString, tauriAdapter, tsMillis, tsSeconds };
4126
+ export { APPLIFT_HOME, AgcHarmonyChannel, AscChannel, BetaConfigSchema, CREDENTIALS_PATH, CredentialsFileSchema, CredentialsProfileSchema, DefaultChannelRegistry, GLOBAL_CONFIG_PATH, GlobalConfigSchema, GooglePlayChannel, HISTORY_PATH, HonorChannel, HuaweiAndroidChannel, OppoChannel, PROJECTS_MAP_PATH, PgyerChannel, PluginHost, ProjectConfigSchema, StoreConfigSchema, StoresSchema, VivoChannel, WAIT_BEFORE_SUBMIT_MS, XiaomiChannel, YybChannel, appendEntry, assertListingFiles, defineConfig, detectBundleIds, detectFramework, electronAdapter, findArtifacts, findUp, flutterAdapter, formPost, getAgcToken, getHarmonyAppId, hmacSign, humanizeError, loadCredentials, loadCredentialsLayered, loadGlobalConfig, loadListingFile, loadProjectConfig, loadProjectProfileMap, md5File, md5String, nativeAndroidAdapter, nativeHarmonyAdapter, nativeIosAdapter, projectCredentialsPath, readArtifactVersion, readEntries, registry, resolveListing, resolvePlugins, resolveProfile, resolveProfileFor, rnAdapter, runStage, runSubmit, saveCredentials, saveProjectProfileMap, setProjectProfile, sha256File, sortedQueryString, tauriAdapter, tsMillis, tsSeconds };
@@ -175,7 +175,31 @@ type StoreConfig = z.infer<typeof StoreConfigSchema>;
175
175
  */
176
176
  declare const StoresSchema: z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodRecord<z.ZodString, z.ZodString>, z.ZodString]>>, z.ZodArray<z.ZodString>]>, z.ZodTransform<Record<string, Record<string, string>>, string[] | Record<string, string | Record<string, string>>>>>;
177
177
  type Stores = z.infer<typeof StoresSchema>;
178
- /** 项目级配置(applift.config.ts 的形状) */
178
+ /** 项目级配置(applift.config.ts 的形状);beta 段见上方 BetaConfigSchema */
179
+ /**
180
+ * 内测分发配置(applift beta 无平台参数时并行发布的渠道开关):
181
+ * - pgyer: 蒲公英多平台上传(缺省只发 android)
182
+ * - agc: AGC 邀请测试(Testing API,submit=true 上传绑定后自动提审)
183
+ * - testflight: App Store Connect 上传(ipa 进 TestFlight)
184
+ */
185
+ declare const BetaConfigSchema: z.ZodObject<{
186
+ pgyer: z.ZodOptional<z.ZodObject<{
187
+ platforms: z.ZodDefault<z.ZodArray<z.ZodEnum<{
188
+ android: "android";
189
+ harmony: "harmony";
190
+ ios: "ios";
191
+ }>>>;
192
+ installPassword: z.ZodOptional<z.ZodString>;
193
+ }, z.core.$strip>>;
194
+ agc: z.ZodOptional<z.ZodObject<{
195
+ submit: z.ZodDefault<z.ZodBoolean>;
196
+ group: z.ZodOptional<z.ZodString>;
197
+ testType: z.ZodDefault<z.ZodUnion<readonly [z.ZodLiteral<3>, z.ZodLiteral<4>]>>;
198
+ }, z.core.$strip>>;
199
+ testflight: z.ZodOptional<z.ZodObject<{
200
+ note: z.ZodOptional<z.ZodString>;
201
+ }, z.core.$strip>>;
202
+ }, z.core.$strip>;
179
203
  declare const ProjectConfigSchema: z.ZodObject<{
180
204
  name: z.ZodOptional<z.ZodString>;
181
205
  profile: z.ZodDefault<z.ZodString>;
@@ -206,7 +230,26 @@ declare const ProjectConfigSchema: z.ZodObject<{
206
230
  macos: z.ZodOptional<z.ZodArray<z.ZodString>>;
207
231
  linux: z.ZodOptional<z.ZodArray<z.ZodString>>;
208
232
  }, z.core.$catchall<z.ZodOptional<z.ZodArray<z.ZodString>>>>>;
233
+ beta: z.ZodOptional<z.ZodObject<{
234
+ pgyer: z.ZodOptional<z.ZodObject<{
235
+ platforms: z.ZodDefault<z.ZodArray<z.ZodEnum<{
236
+ android: "android";
237
+ harmony: "harmony";
238
+ ios: "ios";
239
+ }>>>;
240
+ installPassword: z.ZodOptional<z.ZodString>;
241
+ }, z.core.$strip>>;
242
+ agc: z.ZodOptional<z.ZodObject<{
243
+ submit: z.ZodDefault<z.ZodBoolean>;
244
+ group: z.ZodOptional<z.ZodString>;
245
+ testType: z.ZodDefault<z.ZodUnion<readonly [z.ZodLiteral<3>, z.ZodLiteral<4>]>>;
246
+ }, z.core.$strip>>;
247
+ testflight: z.ZodOptional<z.ZodObject<{
248
+ note: z.ZodOptional<z.ZodString>;
249
+ }, z.core.$strip>>;
250
+ }, z.core.$strip>>;
209
251
  }, z.core.$strip>;
252
+ type BetaConfig = z.infer<typeof BetaConfigSchema>;
210
253
  type ProjectConfig = z.infer<typeof ProjectConfigSchema>;
211
254
  /** 全局行为配置(~/.applift/config.toml) */
212
255
  declare const GlobalConfigSchema: z.ZodObject<{
@@ -331,4 +374,4 @@ declare function defineConfig(config: QypubUserConfig): QypubUserConfig;
331
374
  /** 解析用户配置中的插件(工厂惰性求值),供 PluginHost 构造 */
332
375
  declare function resolvePlugins(userConfig: QypubUserConfig): QypubPlugin[];
333
376
  //#endregion
334
- export { StoresSchema as A, BundleIds as B, GlobalConfig as C, StoreConfig as D, ProjectConfigSchema as E, ChannelRegistry as F, electronAdapter as G, Platform as H, ReleasePlanEntry as I, nativeAndroidAdapter as J, findArtifacts as K, ReviewStatus as L, ChannelCapabilities as M, ChannelContext as N, StoreConfigSchema as O, ChannelEvent as P, tauriAdapter as Q, StageResult as R, CredentialsProfileSchema as S, ProjectConfig as T, detectBundleIds as U, FrameworkAdapter as V, detectFramework as W, nativeIosAdapter as X, nativeHarmonyAdapter as Y, rnAdapter as Z, appendEntry as _, QypubUserConfig as a, CredentialsFileSchema as b, PipelineInput as c, runStage as d, runSubmit as f, HistoryQuery as g, HistoryEvent as h, QypubPlugin as i, Channel as j, Stores as k, PipelineSummary as l, HistoryEntry as m, PluginHookName as n, defineConfig as o, HISTORY_PATH as p, flutterAdapter as q, PluginHost as r, resolvePlugins as s, HookContext as t, PipelineSummaryEntry as u, readEntries as v, GlobalConfigSchema as w, CredentialsProfile as x, CredentialsFile as y, SubmitResult as z };
377
+ export { rnAdapter as $, StoreConfigSchema as A, StageResult as B, CredentialsProfile as C, ProjectConfig as D, GlobalConfigSchema as E, ChannelContext as F, detectBundleIds as G, BundleIds as H, ChannelEvent as I, findArtifacts as J, detectFramework as K, ChannelRegistry as L, StoresSchema as M, Channel as N, ProjectConfigSchema as O, ChannelCapabilities as P, nativeIosAdapter as Q, ReleasePlanEntry as R, CredentialsFileSchema as S, GlobalConfig as T, FrameworkAdapter as U, SubmitResult as V, Platform as W, nativeAndroidAdapter as X, flutterAdapter as Y, nativeHarmonyAdapter as Z, appendEntry as _, QypubUserConfig as a, BetaConfigSchema as b, PipelineInput as c, runStage as d, tauriAdapter as et, runSubmit as f, HistoryQuery as g, HistoryEvent as h, QypubPlugin as i, Stores as j, StoreConfig as k, PipelineSummary as l, HistoryEntry as m, PluginHookName as n, defineConfig as o, HISTORY_PATH as p, electronAdapter as q, PluginHost as r, resolvePlugins as s, HookContext as t, PipelineSummaryEntry as u, readEntries as v, CredentialsProfileSchema as w, CredentialsFile as x, BetaConfig as y, ReviewStatus as z };
@@ -1,4 +1,4 @@
1
- import { i as QypubPlugin } from "../plugin-T6zXsmt1.mjs";
1
+ import { i as QypubPlugin } from "../plugin-DzhwzVfu.mjs";
2
2
  //#region src/plugins/dingtalk-notify.d.ts
3
3
  interface DingtalkNotifyOptions {
4
4
  webhook: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applift/core",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/hcg1023/applift.git",