@applift/core 0.2.0 → 0.3.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: {
@@ -340,10 +338,16 @@ export declare class AgcHarmonyChannel implements Channel {
340
338
  /**
341
339
  * 测试发布(releaseType=6,2026-10 起走 Testing API——旧 app-package-info 直绑链路已被华为下线,
342
340
  * 其 releaseType 枚举仅剩 1=全网 / 7=企业非公开):
343
- * 上传 .hap → 新建测试版本(testType=3 邀请测试)→ 添加测试软件包 → 等云端编译 → 更新测试版本绑定;
344
- * 不自动提交审核,测试群组/邀请码由控制台人工完成。
341
+ * 上传 .app → 新建测试版本(testType 3=邀请测试 4=公开测试)→ 添加测试软件包 → 等云端编译 → 更新测试版本绑定;
342
+ * target.submitTest='true' 时:(可选按 testGroup 绑测试群组)自动提交测试版本审核;否则由控制台人工完成。
345
343
  */
346
344
  private uploadBeta;
345
+ /**
346
+ * 按群组名查测试群组 groupId(AppTest API GET /test-group/list,appId 走 header,rtnCode 外壳)。
347
+ * 分页遍历;匹配不到报错提示到控制台建组。
348
+ * 文档: agc-help-test-api-get-test-grouplist-0000002271000717
349
+ */
350
+ private resolveTestGroupId;
347
351
  /** v1 更新文案段(releaseHarmonyApp step 4 / uploadToAgc 尾段):查默认语言 → 写 newFeatures,失败仅警告不阻断 */
348
352
  private updateReleaseNotes;
349
353
  }
@@ -407,4 +411,4 @@ export interface ArtifactVersion {
407
411
  }
408
412
  export declare function readArtifactVersion(artifactPath: string): ArtifactVersion | undefined;
409
413
  //#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 };
414
+ 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
@@ -47,7 +47,32 @@ const StoresSchema = z.union([z.record(z.string(), z.union([StoreConfigSchema, z
47
47
  if (Array.isArray(stores)) return Object.fromEntries(stores.map((id) => [id, {}]));
48
48
  return Object.fromEntries(Object.entries(stores).map(([id, cfg]) => [id, typeof cfg === "string" ? { appId: cfg } : cfg]));
49
49
  }).default({});
50
- /** 项目级配置(applift.config.ts 的形状) */
50
+ /** 项目级配置(applift.config.ts 的形状);beta 段见上方 BetaConfigSchema */
51
+ /**
52
+ * 内测分发配置(applift beta 无平台参数时并行发布的渠道开关):
53
+ * - pgyer: 蒲公英多平台上传(缺省只发 android)
54
+ * - agc: AGC 邀请测试(Testing API,submit=true 上传绑定后自动提审)
55
+ * - testflight: App Store Connect 上传(ipa 进 TestFlight)
56
+ */
57
+ const BetaConfigSchema = z.object({
58
+ pgyer: z.object({
59
+ platforms: z.array(z.enum([
60
+ "android",
61
+ "harmony",
62
+ "ios"
63
+ ])).default(["android"]),
64
+ installPassword: z.string().optional()
65
+ }).optional(),
66
+ agc: z.object({
67
+ /** 上传绑定后自动提交测试审核(默认 false:只到「已就绪」,群组/提审到控制台人工完成) */
68
+ submit: z.boolean().default(false),
69
+ /** 测试群组名(submit 时按名查 groupId 绑定;匹配不到报错提示到控制台建组) */
70
+ group: z.string().optional(),
71
+ /** 3=邀请测试(默认)4=公开测试 */
72
+ testType: z.union([z.literal(3), z.literal(4)]).default(3)
73
+ }).optional(),
74
+ testflight: z.object({ note: z.string().optional() }).optional()
75
+ });
51
76
  const ProjectConfigSchema = z.object({
52
77
  /** 项目显示名 */
53
78
  name: z.string().optional(),
@@ -83,7 +108,9 @@ const ProjectConfigSchema = z.object({
83
108
  windows: z.array(z.string()).optional(),
84
109
  macos: z.array(z.string()).optional(),
85
110
  linux: z.array(z.string()).optional()
86
- }).catchall(z.array(z.string()).optional()).optional()
111
+ }).catchall(z.array(z.string()).optional()).optional(),
112
+ /** 内测分发(beta 无平台参数时的多渠道并行配置) */
113
+ beta: BetaConfigSchema.optional()
87
114
  });
88
115
  /** 全局行为配置(~/.applift/config.toml) */
89
116
  const GlobalConfigSchema = z.object({
@@ -855,9 +882,9 @@ function required$5(ctx, key, where) {
855
882
  * sig = RSA_encrypt(证书公钥, JSON({sig: [{name, hash: MD5}...], password}))
856
883
  * 分段: 1024bit key → 每段 117 字节(128 - PKCS1 padding 11)
857
884
  * 链路: /dev/query 校验账号对包名权限 + 提审前版本回归预检 → /dev/push 上传即提审(synchroType=1 更新包,
858
- * 无法只传包不提审)。现行文档(2026-09 更新)文件参数表 icon 标「必选」、appInfo.privacyUrl 标「必选」,
859
- * 旧版「未传字段如 icon/privacyUrl 不覆盖线上值」表述已不被现行文档支持;synchroType=1 不带素材是否仍被
860
- * 受理待真机验证,若被拒需补传 icon 文件(其 MD5 追加进 SIG 的 sig 数组)。
885
+ * icon 现行文档(pId=1134,2026-02)标必选,apkgo 同(每次 push 必带);旧官方 PDF(pId=33)标可选,
886
+ * 分歧按现行文档从严处理——applift 默认从工程 mipmap 自动发现(android.stores.xiaomi.icon 可覆盖),
887
+ * 未找到才中止;privacyUrl 是否强制仍待真机验证(apkgo 未传该字段)。
861
888
  * 另 2026-02-04 起 testAccount 须用新结构化格式(如 {"zh_CN":{"accounts":[...]}}),当前未传暂不受影响。
862
889
  * 移植自(MIT): AngChow/android-publish scripts/_mi_common.py + mi_submit.py
863
890
  * 官方文档: https://dev.mi.com/xiaomihyperos/documentation/detail?pId=1134
@@ -959,6 +986,9 @@ var XiaomiChannel = class {
959
986
  const localVc = Number(ctx.target.versionCode);
960
987
  const liveVc = Number(query.packageInfo?.onlineVersionCode ?? query.packageInfo?.versionCode);
961
988
  if (Number.isFinite(localVc) && Number.isFinite(liveVc) && localVc < liveVc) throw new Error(`小米提审中止:待提审 versionCode ${localVc} 低于线上 ${liveVc}(版本回退),请确认 APK 版本或项目配置`);
989
+ const iconPath = String(ctx.target.icon ?? "");
990
+ if (!iconPath) throw new Error("小米提审缺少 icon:applift.config.ts → android.stores.xiaomi.icon 配置图标路径(官方必选,apkgo 同)");
991
+ if (!fs.existsSync(iconPath)) throw new Error(`小米 icon 文件不存在: ${iconPath}`);
962
992
  const appInfo = {
963
993
  appName,
964
994
  packageName: pkgName,
@@ -970,7 +1000,7 @@ var XiaomiChannel = class {
970
1000
  synchroType: 1
971
1001
  };
972
1002
  const requestJson = JSON.stringify(request);
973
- const sig = generateMiSig(requestJson, [["apk", ctx.artifactPath]], password, certPem);
1003
+ const sig = generateMiSig(requestJson, [["apk", ctx.artifactPath], ["icon", iconPath]], password, certPem);
974
1004
  const fileName = path.basename(ctx.artifactPath);
975
1005
  ctx.emit({
976
1006
  type: "phase",
@@ -981,6 +1011,7 @@ var XiaomiChannel = class {
981
1011
  form.append("RequestData", requestJson);
982
1012
  form.append("SIG", sig);
983
1013
  form.append("apk", new Blob([fs.readFileSync(ctx.artifactPath)]), fileName);
1014
+ form.append("icon", new Blob([fs.readFileSync(iconPath)]), path.basename(iconPath));
984
1015
  const res = await fetch(`${MI_API_BASE}/dev/push`, {
985
1016
  method: "POST",
986
1017
  body: form,
@@ -1356,6 +1387,7 @@ var OppoChannel = class {
1356
1387
  type: "warn",
1357
1388
  message: inReview ? "errno=911215 应用审核中:该版本已在 OPPO 审核队列,本次未重复创建任务" : "errno=911216 版本更新任务处理中:上一提交任务仍在处理,本次未创建新任务,稍后可用 status 查询审核状态"
1358
1389
  });
1390
+ if (!inReview) await this.pollTaskState(ctx, auth, versionCode);
1359
1391
  ctx.emit({
1360
1392
  type: "phase",
1361
1393
  phase: "done",
@@ -1502,7 +1534,7 @@ function commonParams(accessKey, method) {
1502
1534
  timestamp: tsMillis(),
1503
1535
  format: "json",
1504
1536
  v: "1.0",
1505
- sign_method: "hmac",
1537
+ sign_method: "hmac-sha256",
1506
1538
  target_app_key: "developer"
1507
1539
  };
1508
1540
  }
@@ -1661,38 +1693,44 @@ var VivoChannel = class {
1661
1693
  };
1662
1694
  }
1663
1695
  /**
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撤销),冲突以真机为准。
1696
+ * 审核状态查询: 复用 app.query.details 的 data.status(apkgo vivo.go mapVivoAuditState 权威,
1697
+ * 2026-10 源码仲裁:编号 1=草稿 2=待审核 3=审核通过 4=审核不通过 5=已撤销;
1698
+ * 旧 auditStatus 字段与 0/1/2 编号表系 v1 python 误记,已弃用)。
1669
1699
  */
1670
1700
  async status(ctx) {
1671
1701
  required$3(ctx, "pkgName", "审核状态查询必需");
1672
1702
  const data = await this.queryDetails(ctx);
1673
- const auditStatus = data.auditStatus ?? data.audit_status;
1674
- if (auditStatus === void 0) return {
1703
+ const st = data.status ?? data.auditStatus ?? data.audit_status;
1704
+ if (st === void 0) return {
1675
1705
  state: "unknown",
1676
- raw: `app.query.details 无 auditStatus: ${JSON.stringify(data).slice(0, 200)}`
1706
+ raw: `app.query.details 无 status: ${JSON.stringify(data).slice(0, 200)}`
1677
1707
  };
1678
- const num = Number(auditStatus);
1708
+ const num = Number(st);
1679
1709
  if (!Number.isFinite(num)) return {
1680
1710
  state: "unknown",
1681
- raw: `auditStatus=${String(auditStatus)}`
1711
+ raw: `status=${String(st)}`
1682
1712
  };
1683
1713
  switch (num) {
1684
- case 0: return {
1714
+ case 2: return { state: "reviewing" };
1715
+ case 3: return {
1685
1716
  state: "approved",
1686
1717
  live: true
1687
1718
  };
1688
- case 1: return { state: "reviewing" };
1689
- case 2: return {
1719
+ case 4: return {
1690
1720
  state: "rejected",
1691
1721
  reason: this.auditReason(data)
1692
1722
  };
1723
+ case 1: return {
1724
+ state: "unknown",
1725
+ raw: "status=1(草稿,未提交审核)"
1726
+ };
1727
+ case 5: return {
1728
+ state: "unknown",
1729
+ raw: "status=5(已撤销)"
1730
+ };
1693
1731
  default: return {
1694
1732
  state: "unknown",
1695
- raw: `auditStatus=${num}(未验证编号)`
1733
+ raw: `status=${num}(未验证编号)`
1696
1734
  };
1697
1735
  }
1698
1736
  }
@@ -1952,6 +1990,55 @@ function required$2(ctx, key, where) {
1952
1990
  return v;
1953
1991
  }
1954
1992
  //#endregion
1993
+ //#region src/channels/shared/apk-abi.ts
1994
+ /**
1995
+ * APK(zip)CPU ABI 探测——纯 Node 解析 zip 中央目录(零依赖、不解压内容)。
1996
+ * 用途:应用宝 update_app 的 apk32_flag/apk64_flag 分支(apkgo tencent.go isAPK64BitOnly 同构)。
1997
+ */
1998
+ const EOCD_MAGIC = 101010256;
1999
+ const CEN_MAGIC = 33639248;
2000
+ /** 列出 APK 内 lib/<abi>/ 出现过的 ABI 名;非 zip/无 lib 段返回空数组(按 32/64 兼容处理) */
2001
+ function listApkLibAbis(apkPath) {
2002
+ const fd = fs.openSync(apkPath, "r");
2003
+ try {
2004
+ const size = fs.fstatSync(fd).size;
2005
+ const tailLen = Math.min(size, 65557);
2006
+ const tail = Buffer.alloc(tailLen);
2007
+ fs.readSync(fd, tail, 0, tailLen, size - tailLen);
2008
+ let eocd = -1;
2009
+ for (let i = tail.length - 22; i >= 0; i--) if (tail.readUInt32LE(i) === EOCD_MAGIC) {
2010
+ eocd = i;
2011
+ break;
2012
+ }
2013
+ if (eocd < 0) return [];
2014
+ const count = tail.readUInt16LE(eocd + 10);
2015
+ let offset = tail.readUInt32LE(eocd + 16);
2016
+ const abis = /* @__PURE__ */ new Set();
2017
+ for (let i = 0; i < count; i++) {
2018
+ const hdr = Buffer.alloc(46);
2019
+ if (fs.readSync(fd, hdr, 0, 46, offset) < 46) break;
2020
+ if (hdr.readUInt32LE(0) !== CEN_MAGIC) break;
2021
+ const nameLen = hdr.readUInt16LE(28);
2022
+ const extraLen = hdr.readUInt16LE(30);
2023
+ const commentLen = hdr.readUInt16LE(32);
2024
+ const nameBuf = Buffer.alloc(nameLen);
2025
+ if (fs.readSync(fd, nameBuf, 0, nameLen, offset + 46) < nameLen) break;
2026
+ const m = nameBuf.toString("utf8").match(/^lib\/([^/]+)\//);
2027
+ if (m?.[1]) abis.add(m[1]);
2028
+ offset += 46 + nameLen + extraLen + commentLen;
2029
+ }
2030
+ return [...abis];
2031
+ } finally {
2032
+ fs.closeSync(fd);
2033
+ }
2034
+ }
2035
+ /** 是否 arm64-only 包:含 lib/ 且全部为 arm64-v8a(apkgo isAPK64BitOnly 同构;无 native 库返回 false) */
2036
+ function isApkArm64Only(apkPath) {
2037
+ const abis = listApkLibAbis(apkPath);
2038
+ if (abis.length === 0) return false;
2039
+ return abis.every((a) => a === "arm64-v8a");
2040
+ }
2041
+ //#endregion
1955
2042
  //#region src/channels/yyb.ts
1956
2043
  /**
1957
2044
  * 腾讯应用宝(安卓 APK)渠道。
@@ -2086,9 +2173,16 @@ var YybChannel = class {
2086
2173
  const businessParams = {
2087
2174
  pkg_name: pkgName,
2088
2175
  app_id: appId,
2089
- apk32_flag: "1",
2090
- apk32_file_serial_number: stagedInfo.serialNumber,
2091
- apk32_file_md5: stagedInfo.md5,
2176
+ ...isApkArm64Only(ctx.artifactPath) ? {
2177
+ apk32_flag: "2",
2178
+ apk64_flag: "1",
2179
+ apk64_file_serial_number: stagedInfo.serialNumber,
2180
+ apk64_file_md5: stagedInfo.md5
2181
+ } : {
2182
+ apk32_flag: "1",
2183
+ apk32_file_serial_number: stagedInfo.serialNumber,
2184
+ apk32_file_md5: stagedInfo.md5
2185
+ },
2092
2186
  feature: ctx.updateDescription ?? ""
2093
2187
  };
2094
2188
  if (ctx.releaseTime) {
@@ -2180,6 +2274,8 @@ var YybChannel = class {
2180
2274
  const BASE = "https://connect-api.cloud.huawei.com/api";
2181
2275
  const COMPILE_TIMEOUT_MS = 9e5;
2182
2276
  const COMPILE_POLL_MS = 15e3;
2277
+ /** AppTest 测试群组 API 前缀(与 publish API 同域不同路径段) */
2278
+ const APP_TEST_BASE = "https://connect-api.cloud.huawei.com/api/app-test/v1";
2183
2279
  /** AGC JSON API 封装:判 ret.code !== 0 抛错 —— v1 agc.mjs agcJson 原样 */
2184
2280
  async function agcJson(url, init, context) {
2185
2281
  const res = await fetch(url, init);
@@ -2225,7 +2321,7 @@ async function getDefaultLang(headers, appId) {
2225
2321
  return (await agcJson(`${BASE}/publish/v3/app-info?${qs}`, { headers }, "查询应用信息")).appInfo?.defaultLang || "zh-CN";
2226
2322
  }
2227
2323
  var AgcHarmonyChannel = class {
2228
- id = "agc-harmony";
2324
+ id = "agc";
2229
2325
  label = "华为 AGC(鸿蒙)";
2230
2326
  capabilities = {
2231
2327
  stage: true,
@@ -2278,7 +2374,7 @@ var AgcHarmonyChannel = class {
2278
2374
  artifactVersion: "",
2279
2375
  action: "stage-only",
2280
2376
  afterApproval: "none(beta)",
2281
- notes: "Testing API 邀请测试:上传→新建测试版本→绑定软件包;提交审核与测试群组到控制台完成,不自动提审"
2377
+ notes: ctx.target.submitTest === "true" ? "Testing API 邀请测试:上传→新建测试版本→绑定软件包→(可选绑测试群组)自动提交测试审核" : "Testing API 邀请测试:上传→新建测试版本→绑定软件包;提交审核与测试群组到控制台完成,不自动提审"
2282
2378
  };
2283
2379
  return {
2284
2380
  channel: this.id,
@@ -2309,7 +2405,7 @@ var AgcHarmonyChannel = class {
2309
2405
  async submit(ctx) {
2310
2406
  if (ctx.target.releaseType === "6") return {
2311
2407
  ok: false,
2312
- error: "AGC 测试发布(releaseType=6)不做自动提审,请到 AGC 控制台「测试」提交测试版本并指定测试群组"
2408
+ error: "AGC 测试发布(releaseType=6)的自动提审走 beta 配置 test['agc'].submit=true(上传绑定后自动提交测试版本审核)"
2313
2409
  };
2314
2410
  const { clientId, token, headers } = await this.authHeaders(ctx);
2315
2411
  const appId = await this.resolveAppId(ctx, clientId, token);
@@ -2470,8 +2566,8 @@ var AgcHarmonyChannel = class {
2470
2566
  /**
2471
2567
  * 测试发布(releaseType=6,2026-10 起走 Testing API——旧 app-package-info 直绑链路已被华为下线,
2472
2568
  * 其 releaseType 枚举仅剩 1=全网 / 7=企业非公开):
2473
- * 上传 .hap → 新建测试版本(testType=3 邀请测试)→ 添加测试软件包 → 等云端编译 → 更新测试版本绑定;
2474
- * 不自动提交审核,测试群组/邀请码由控制台人工完成。
2569
+ * 上传 .app → 新建测试版本(testType 3=邀请测试 4=公开测试)→ 添加测试软件包 → 等云端编译 → 更新测试版本绑定;
2570
+ * target.submitTest='true' 时:(可选按 testGroup 绑测试群组)自动提交测试版本审核;否则由控制台人工完成。
2475
2571
  */
2476
2572
  async uploadBeta(ctx, appId, headers) {
2477
2573
  const fileName = path.basename(ctx.artifactPath);
@@ -2518,6 +2614,7 @@ var AgcHarmonyChannel = class {
2518
2614
  type: "progress",
2519
2615
  detail: "文件上传完成"
2520
2616
  });
2617
+ const testType = ctx.target.testType === "4" ? 4 : 3;
2521
2618
  const versionId = (await agcJson(`${BASE}/publish/v2/test/app/version?appId=${encodeURIComponent(appId)}`, {
2522
2619
  method: "POST",
2523
2620
  headers: {
@@ -2526,7 +2623,7 @@ var AgcHarmonyChannel = class {
2526
2623
  },
2527
2624
  body: JSON.stringify({
2528
2625
  releaseType: 6,
2529
- testType: 3,
2626
+ testType,
2530
2627
  testDesc: (ctx.updateDescription ?? "applift 测试发布").slice(0, 50)
2531
2628
  })
2532
2629
  }, "新建测试版本")).versionId ?? "";
@@ -2563,16 +2660,92 @@ var AgcHarmonyChannel = class {
2563
2660
  pkgId
2564
2661
  })
2565
2662
  }, "绑定测试软件包");
2663
+ if (ctx.target.submitTest === "true") {
2664
+ const groupName = ctx.target.testGroup?.trim();
2665
+ if (groupName) {
2666
+ const groupId = await this.resolveTestGroupId(ctx, appId, groupName, headers);
2667
+ await agcJson(`${BASE}/publish/v2/test/app/version?appId=${encodeURIComponent(appId)}`, {
2668
+ method: "PUT",
2669
+ headers: {
2670
+ ...headers,
2671
+ "Content-Type": "application/json"
2672
+ },
2673
+ body: JSON.stringify({
2674
+ versionId,
2675
+ pkgId,
2676
+ openTestInfo: { testTaskInfo: { groupInfos: [{
2677
+ groupId,
2678
+ groupName
2679
+ }] } }
2680
+ })
2681
+ }, "绑定测试群组");
2682
+ ctx.emit({
2683
+ type: "progress",
2684
+ detail: `测试群组「${groupName}」已绑定(groupId: ${groupId})`
2685
+ });
2686
+ }
2687
+ ctx.emit({
2688
+ type: "phase",
2689
+ phase: "submit",
2690
+ message: "提交测试版本审核…"
2691
+ });
2692
+ await agcJson(`${BASE}/publish/v2/test/app/version/submit?appId=${encodeURIComponent(appId)}`, {
2693
+ method: "POST",
2694
+ headers: {
2695
+ ...headers,
2696
+ "Content-Type": "application/json"
2697
+ },
2698
+ body: JSON.stringify({ versionId })
2699
+ }, "提交测试审核");
2700
+ ctx.emit({
2701
+ type: "phase",
2702
+ phase: "done",
2703
+ message: `测试版本已提交审核(versionId: ${versionId})`
2704
+ });
2705
+ return {
2706
+ ok: true,
2707
+ ref: versionId
2708
+ };
2709
+ }
2566
2710
  ctx.emit({
2567
2711
  type: "phase",
2568
2712
  phase: "done",
2569
- message: "测试版本已就绪(未提交审核);到 AGC 控制台「测试」提交并指定测试群组"
2713
+ message: "测试版本已就绪(未提交审核);到控制台提交或配置 test['agc'].submit 自动提审"
2570
2714
  });
2571
2715
  return {
2572
2716
  ok: true,
2573
2717
  ref: versionId
2574
2718
  };
2575
2719
  }
2720
+ /**
2721
+ * 按群组名查测试群组 groupId(AppTest API GET /test-group/list,appId 走 header,rtnCode 外壳)。
2722
+ * 分页遍历;匹配不到报错提示到控制台建组。
2723
+ * 文档: agc-help-test-api-get-test-grouplist-0000002271000717
2724
+ */
2725
+ async resolveTestGroupId(ctx, appId, groupName, headers) {
2726
+ ctx.emit({
2727
+ type: "progress",
2728
+ detail: `按群组名「${groupName}」查询 AGC 测试群组…`
2729
+ });
2730
+ for (let current = 1; current <= 50; current++) {
2731
+ const qs = new URLSearchParams({
2732
+ current: String(current),
2733
+ pageSize: "50"
2734
+ });
2735
+ const res = await fetch(`${APP_TEST_BASE}/test-group/list?${qs}`, { headers: {
2736
+ ...headers,
2737
+ appId
2738
+ } });
2739
+ if (!res.ok) throw new Error(`AGC 查询测试群组失败 HTTP ${res.status}`);
2740
+ const json = await res.json();
2741
+ if (Number(json.rtnCode) !== 0) throw new Error(`AGC 查询测试群组失败: rtnCode=${json.rtnCode ?? "?"}${json.rtnDesc ? ` ${json.rtnDesc}` : ""}`);
2742
+ const hit = json.groups?.find((g) => g.groupName === groupName);
2743
+ if (hit?.groupId) return hit.groupId;
2744
+ const totalPage = json.pageInfo?.totalPage ?? current;
2745
+ if (current >= totalPage) break;
2746
+ }
2747
+ throw new Error(`AGC 未找到名为「${groupName}」的测试群组:到 AGC 控制台「测试 → 测试群组」创建后再试`);
2748
+ }
2576
2749
  /** v1 更新文案段(releaseHarmonyApp step 4 / uploadToAgc 尾段):查默认语言 → 写 newFeatures,失败仅警告不阻断 */
2577
2750
  async updateReleaseNotes(ctx, appId, headers, qs) {
2578
2751
  if (!ctx.updateDescription) return;
@@ -3923,4 +4096,4 @@ function fromAapt(artifactPath) {
3923
4096
  }
3924
4097
  }
3925
4098
  //#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 };
4099
+ 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.3.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/hcg1023/applift.git",