@applift/core 0.1.3 → 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;
@@ -13,17 +13,17 @@ export declare const registry: DefaultChannelRegistry;
13
13
  export declare const APPLIFT_HOME: string;
14
14
  export declare const CREDENTIALS_PATH: string;
15
15
  export declare const GLOBAL_CONFIG_PATH: string;
16
- /** 项目级凭据目录(<project>/.applift/credentials.toml;gitignore 由用户决定:私有仓可提交,公开仓 ignore) */
16
+ /** 项目级凭据目录(<project>/.applift/credentials.json;gitignore 由用户决定:私有仓可提交,公开仓 ignore) */
17
17
  export declare function projectCredentialsPath(projectRoot: string): string;
18
18
  /** 从 startDir 逐层向上找第一个存在的路径(monorepo 子包向上借用根配置/凭据);找不到返回 null */
19
19
  export declare function findUp(startDir: string, relative: string): string | null;
20
20
  /**
21
- * 逐级凭据加载:项目 <root>/.applift/credentials.toml(就近,逐键优先)→ 全局 ~/.applift/(逐键回退)。
21
+ * 逐级凭据加载:项目 <root>/.applift/credentials.json(就近,逐键优先)→ 全局 ~/.applift/(逐键回退)。
22
22
  * 场景:私有仓提交凭据进仓库;或公开仓 .applift/ 进 gitignore 由成员各自维护。
23
23
  * profile 合并:项目同名 profile 逐键覆盖全局;仅全局有则用全局。
24
24
  */
25
25
  export declare function loadCredentialsLayered(projectRoot?: string, globalPath?: string): CredentialsFile;
26
- /** 读取并校验 credentials.toml;不存在返回空 profiles;强制 0600 */
26
+ /** 读取并校验 credentials.json;不存在返回空 profiles;强制 0600 */
27
27
  export declare function loadCredentials(filePath?: string): CredentialsFile;
28
28
  /** 按项目 profile 取凭据;profile 不存在时报错并列出可用 profiles。
29
29
  * 环境变量覆盖(CI 场景):APPLIFT_<KEY> 前缀优先(如 APPLIFT_PGYER_API_KEY);
@@ -144,8 +144,9 @@ export declare class HuaweiAndroidChannel implements Channel, ListingCapableChan
144
144
  stage(ctx: ChannelContext): Promise<StageResult>;
145
145
  submit(ctx: ChannelContext): Promise<SubmitResult>;
146
146
  /**
147
- * 更新商店应用信息:简介/描述走 v3 app-language-info(briefInfo/appDesc)。
148
- * 图标/截图:华为 v3 app-file-info 的 fileType 取值未在官方文档确证(软件包=5 已验证,图标清单值不确定),
147
+ * 更新商店应用信息:简介/描述走 v2 app-language-info(briefInfo/appDesc)。
148
+ * 注:v3 app-language-info 为鸿蒙(HarmonyOS)专属接口族,安卓现行文档只有 v2。
149
+ * 图标/截图:华为 app-file-info 的 fileType 取值未在官方文档确证(软件包=5 已验证,图标清单值不确定),
149
150
  * 未经真机验证前只上传 OBS 不做 file-info 绑定(skip 并在结果中标注),避免误写损坏草稿。
150
151
  */
151
152
  updateListing(ctx: Pick<ChannelContext, "credentials" | "target" | "emit">, listing: ListingInput): Promise<ListingResult>;
@@ -191,6 +192,9 @@ export declare class OppoChannel implements Channel {
191
192
  "911215": string;
192
193
  "911216": string;
193
194
  "800002": string;
195
+ "910003": string;
196
+ "911045": string;
197
+ "300002": string;
194
198
  };
195
199
  /** stage 产物暂存(apkUrl/md5),submit 按 pkgName 取回 */
196
200
  private readonly staged;
@@ -204,9 +208,19 @@ export declare class OppoChannel implements Channel {
204
208
  */
205
209
  private uploadApk;
206
210
  submit(ctx: ChannelContext): Promise<SubmitResult>;
211
+ /**
212
+ * app/upd 异步任务结果轮询: POST /resource/v1/app/task-state(业务参数 pkg_name+version_code,
213
+ * 现行文档「获取任务状态」)。task_state: 1=待处理(继续等)、2=处理成功、3=处理失败(err_msg 透出);
214
+ * 间隔 10s × 最多 5 次,超时视为已受理但异步结果未知(warn 提示,稍后可用 status 查询审核状态)。
215
+ */
216
+ private pollTaskState;
207
217
  /**
208
218
  * 审核状态查询: GET /resource/v1/app/info(公共参数 + api_sign,同 stage)
209
- * → data.audit_status(android-publish oppo_query.py: 1=待提审 2=审核中 3=通过 4=驳回)。
219
+ * → data.audit_status 按现行「审核状态对照表」映射(id=10998):
220
+ * 1=审核中、4=运营审核中 → reviewing;2=审核通过、6=运营通过 → approved;111=上线 → approved+live;
221
+ * 3=测试不通过、5=运营打回、444=审核不通过 → rejected(原因读 refuse_reason,原样透出不解析);
222
+ * 0=未发布、7=定时发布、22=已冻结 → unknown+raw;其余编号(00/11/-11 资质类、222 下架等)→ unknown。
223
+ * 旧映射(1=待提审 2=审核中 3=通过 4=驳回,出自 python oppo_query.py)与现行对照表整体错位,已重写。
210
224
  */
211
225
  status(ctx: Pick<ChannelContext, "credentials" | "target">): Promise<ReviewStatus>;
212
226
  }
@@ -217,8 +231,10 @@ export declare class VivoChannel implements Channel {
217
231
  readonly label = "vivo 应用市场";
218
232
  readonly capabilities: ChannelCapabilities;
219
233
  readonly errorHints: {
220
- "1002": string;
221
- "46001": string;
234
+ "10001": string;
235
+ "10005": string;
236
+ "10011": string;
237
+ "12010": string;
222
238
  };
223
239
  /**
224
240
  * stage→submit 之间传递上传结果:Channel 接口不透传 ref,
@@ -236,10 +252,9 @@ export declare class VivoChannel implements Channel {
236
252
  stage(ctx: ChannelContext): Promise<StageResult>;
237
253
  submit(ctx: ChannelContext): Promise<SubmitResult>;
238
254
  /**
239
- * 审核状态查询: 复用 app.query.details 的 data.auditStatus(对拍 python vivo_query.py;
240
- * 未确证的 app.audit.status method 已删——python/apkgo 均无此调用)。
241
- * 映射 0=审核通过 1=审核中 2=驳回 沿自 v1,待真机校准;
242
- * 交叉见证: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 误记,已弃用)。
243
258
  */
244
259
  status(ctx: Pick<ChannelContext, "credentials" | "target">): Promise<ReviewStatus>;
245
260
  /** 驳回原因字段名文档未明确,按常见命名兜底取(auditMessage/auditMsg) */
@@ -252,6 +267,10 @@ export declare class HonorChannel implements Channel {
252
267
  readonly label = "荣耀应用市场";
253
268
  readonly capabilities: ChannelCapabilities;
254
269
  readonly errorHints: {
270
+ "10003": string;
271
+ "20022": string;
272
+ "30006": string;
273
+ "30011": string;
255
274
  "100030001": string;
256
275
  "100030002": string;
257
276
  };
@@ -292,7 +311,7 @@ export declare function getHarmonyAppId(opts: {
292
311
  bundleName: string;
293
312
  }): Promise<string>;
294
313
  export declare class AgcHarmonyChannel implements Channel {
295
- readonly id = "agc-harmony";
314
+ readonly id = "agc";
296
315
  readonly label = "华为 AGC(鸿蒙)";
297
316
  readonly capabilities: ChannelCapabilities;
298
317
  readonly errorHints: {
@@ -319,10 +338,16 @@ export declare class AgcHarmonyChannel implements Channel {
319
338
  /**
320
339
  * 测试发布(releaseType=6,2026-10 起走 Testing API——旧 app-package-info 直绑链路已被华为下线,
321
340
  * 其 releaseType 枚举仅剩 1=全网 / 7=企业非公开):
322
- * 上传 .hap → 新建测试版本(testType=3 邀请测试)→ 添加测试软件包 → 等云端编译 → 更新测试版本绑定;
323
- * 不自动提交审核,测试群组/邀请码由控制台人工完成。
341
+ * 上传 .app → 新建测试版本(testType 3=邀请测试 4=公开测试)→ 添加测试软件包 → 等云端编译 → 更新测试版本绑定;
342
+ * target.submitTest='true' 时:(可选按 testGroup 绑测试群组)自动提交测试版本审核;否则由控制台人工完成。
324
343
  */
325
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;
326
351
  /** v1 更新文案段(releaseHarmonyApp step 4 / uploadToAgc 尾段):查默认语言 → 写 newFeatures,失败仅警告不阻断 */
327
352
  private updateReleaseNotes;
328
353
  }
@@ -343,11 +368,11 @@ export declare class AscChannel implements Channel {
343
368
  stage(ctx: ChannelContext): Promise<StageResult>;
344
369
  /**
345
370
  * 提审 App Store —— 按 ASC 官方文档新实现,未经真实链路验证。
346
- * 文档: appStoreVersionSubmissions / Create a Version Submission
371
+ * 文档: reviewSubmissions / Create a Review Submission / Submit a Review Submission
347
372
  * https://developer.apple.com/documentation/appstoreconnectapi
348
373
  * 链路: 查 app(bundleId → 数字 id)→ 查最新已处理(VALID)构建 → 读取/创建 appStoreVersion
349
- * (releaseType 默认 MANUAL,ctx.releaseTime 提供时 SCHEDULED + earlyReleaseDate)
350
- * → 绑定构建到版本 → POST appStoreVersionSubmissions 进入审核
374
+ * (releaseType 默认 MANUAL,ctx.releaseTime 提供时 SCHEDULED + earliestReleaseDate)
375
+ * → 绑定构建到版本 → reviewSubmissions 三步流提交进入审核
351
376
  */
352
377
  submit(ctx: ChannelContext): Promise<SubmitResult>;
353
378
  status(): Promise<ReviewStatus>;
@@ -386,4 +411,4 @@ export interface ArtifactVersion {
386
411
  }
387
412
  export declare function readArtifactVersion(artifactPath: string): ArtifactVersion | undefined;
388
413
  //#endregion
389
- 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
@@ -28,8 +28,8 @@ const registry = new DefaultChannelRegistry();
28
28
  //#region src/config/schema.ts
29
29
  /**
30
30
  * 配置三层:项目 applift.config.ts > 内置默认(全局行为配置独立于 ~/.applift/config.toml)。
31
- * 凭据独立存放 ~/.applift/credentials.toml(0600,按 profile 组织,永不进项目目录/git)。
32
- * 加载用社区主流 c12(TS 配置执行 + 多层 merge),TOML 用 smol-toml,非自研。
31
+ * 凭据独立存放 ~/.applift/credentials.json(0600,按 profile 组织,永不进项目目录/git)。
32
+ * 加载用社区主流 c12(TS 配置执行 + 多层 merge),config.toml 用 smol-toml 解析,非自研。
33
33
  */
34
34
  const CredentialsProfileSchema = z.record(z.string(), z.string());
35
35
  const CredentialsFileSchema = z.object({
@@ -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({
@@ -101,11 +128,11 @@ const GlobalConfigSchema = z.object({
101
128
  * 凭据与配置装载:文件定位、profile 解析、0600 权限。
102
129
  */
103
130
  const APPLIFT_HOME = process.env.APPLIFT_HOME || path.join(os.homedir(), ".applift");
104
- const CREDENTIALS_PATH = path.join(APPLIFT_HOME, "credentials.toml");
131
+ const CREDENTIALS_PATH = path.join(APPLIFT_HOME, "credentials.json");
105
132
  const GLOBAL_CONFIG_PATH = path.join(APPLIFT_HOME, "config.toml");
106
- /** 项目级凭据目录(<project>/.applift/credentials.toml;gitignore 由用户决定:私有仓可提交,公开仓 ignore) */
133
+ /** 项目级凭据目录(<project>/.applift/credentials.json;gitignore 由用户决定:私有仓可提交,公开仓 ignore) */
107
134
  function projectCredentialsPath(projectRoot) {
108
- return path.join(projectRoot, ".applift", "credentials.toml");
135
+ return path.join(projectRoot, ".applift", "credentials.json");
109
136
  }
110
137
  /** 从 startDir 逐层向上找第一个存在的路径(monorepo 子包向上借用根配置/凭据);找不到返回 null */
111
138
  function findUp(startDir, relative) {
@@ -119,14 +146,14 @@ function findUp(startDir, relative) {
119
146
  }
120
147
  }
121
148
  /**
122
- * 逐级凭据加载:项目 <root>/.applift/credentials.toml(就近,逐键优先)→ 全局 ~/.applift/(逐键回退)。
149
+ * 逐级凭据加载:项目 <root>/.applift/credentials.json(就近,逐键优先)→ 全局 ~/.applift/(逐键回退)。
123
150
  * 场景:私有仓提交凭据进仓库;或公开仓 .applift/ 进 gitignore 由成员各自维护。
124
151
  * profile 合并:项目同名 profile 逐键覆盖全局;仅全局有则用全局。
125
152
  */
126
153
  function loadCredentialsLayered(projectRoot, globalPath = CREDENTIALS_PATH) {
127
154
  const global = loadCredentials(globalPath);
128
155
  if (!projectRoot) return global;
129
- const localPath = findUp(projectRoot, path.join(".applift", "credentials.toml"));
156
+ const localPath = findUp(projectRoot, path.join(".applift", "credentials.json"));
130
157
  if (!localPath) return global;
131
158
  const local = loadCredentials(localPath);
132
159
  const merged = {};
@@ -138,11 +165,11 @@ function loadCredentialsLayered(projectRoot, globalPath = CREDENTIALS_PATH) {
138
165
  return CredentialsFileSchema.parse({ profiles: merged });
139
166
  }
140
167
  const EMPTY_CREDENTIALS = { profiles: {} };
141
- /** 读取并校验 credentials.toml;不存在返回空 profiles;强制 0600 */
168
+ /** 读取并校验 credentials.json;不存在返回空 profiles;强制 0600 */
142
169
  function loadCredentials(filePath = CREDENTIALS_PATH) {
143
170
  if (!fs.existsSync(filePath)) return EMPTY_CREDENTIALS;
144
171
  if (fs.statSync(filePath).mode & 63) fs.chmodSync(filePath, 384);
145
- const raw = parse(fs.readFileSync(filePath, "utf8"));
172
+ const raw = JSON.parse(fs.readFileSync(filePath, "utf8"));
146
173
  return CredentialsFileSchema.parse(raw);
147
174
  }
148
175
  /** 按项目 profile 取凭据;profile 不存在时报错并列出可用 profiles。
@@ -172,26 +199,10 @@ function loadGlobalConfig(filePath = GLOBAL_CONFIG_PATH) {
172
199
  return GlobalConfigSchema.parse({});
173
200
  }
174
201
  }
175
- /** TOML 裸键合法字符集(含 - 与 _),超出则产出的 TOML 无法被解析回来 */
176
- const TOML_BARE_KEY = /^[A-Za-z0-9_-]+$/;
177
- /** 凭据序列化为 TOML(值经 JSON.stringify 转义,可含引号/反斜杠/#;键名与 profile 名仅允许裸键字符集,非法即抛错防写坏文件) */
178
- function credentialsToToml(data) {
179
- const lines = [];
180
- for (const [pname, kv] of Object.entries(data.profiles)) {
181
- if (!TOML_BARE_KEY.test(pname)) throw new Error(`凭据 profile 名 "${pname}" 含 TOML 裸键非法字符(仅允许字母/数字/_/-)`);
182
- lines.push(`[profiles.${pname}]`);
183
- for (const [k, v] of Object.entries(kv)) {
184
- if (!TOML_BARE_KEY.test(k)) throw new Error(`profile "${pname}" 的凭据键名 "${k}" 含 TOML 裸键非法字符(仅允许字母/数字/_/-)`);
185
- lines.push(`${k} = ${JSON.stringify(String(v))}`);
186
- }
187
- lines.push("");
188
- }
189
- return `${lines.join("\n")}\n`;
190
- }
191
202
  /** 写凭据(目录自动创建,文件强制 0600) */
192
203
  function saveCredentials(data, filePath = CREDENTIALS_PATH) {
193
204
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
194
- fs.writeFileSync(filePath, credentialsToToml(data), { mode: 384 });
205
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", { mode: 384 });
195
206
  fs.chmodSync(filePath, 384);
196
207
  }
197
208
  //#endregion
@@ -677,10 +688,10 @@ var HuaweiAndroidChannel = class {
677
688
  },
678
689
  body: JSON.stringify({
679
690
  fileType: 5,
680
- files: {
691
+ files: [{
681
692
  fileName,
682
693
  fileDestUrl: urlInfo.objectId
683
- }
694
+ }]
684
695
  })
685
696
  }, "登记软件包");
686
697
  ctx.emit({
@@ -695,6 +706,11 @@ var HuaweiAndroidChannel = class {
695
706
  }
696
707
  async submit(ctx) {
697
708
  const appId = required$5(ctx, "appId");
709
+ if (ctx.remark !== void 0 && ctx.remark !== "") {
710
+ const remarkLen = [...ctx.remark].length;
711
+ if (remarkLen < 10 || remarkLen > 300) throw new Error(`华为提审备注(remark)长度需在 10-300 字之间,当前 ${remarkLen} 字`);
712
+ }
713
+ if (ctx.updateDescription !== void 0 && [...ctx.updateDescription].length > 500) throw new Error(`华为更新说明(newFeatures)不能超过 500 字符,当前 ${[...ctx.updateDescription].length} 字符`);
698
714
  const { headers } = await this.authHeaders(ctx);
699
715
  if (ctx.updateDescription) try {
700
716
  await hwJson(`${BASE$1}/publish/v2/app-language-info?appId=${encodeURIComponent(appId)}`, {
@@ -749,8 +765,9 @@ var HuaweiAndroidChannel = class {
749
765
  };
750
766
  }
751
767
  /**
752
- * 更新商店应用信息:简介/描述走 v3 app-language-info(briefInfo/appDesc)。
753
- * 图标/截图:华为 v3 app-file-info 的 fileType 取值未在官方文档确证(软件包=5 已验证,图标清单值不确定),
768
+ * 更新商店应用信息:简介/描述走 v2 app-language-info(briefInfo/appDesc)。
769
+ * 注:v3 app-language-info 为鸿蒙(HarmonyOS)专属接口族,安卓现行文档只有 v2。
770
+ * 图标/截图:华为 app-file-info 的 fileType 取值未在官方文档确证(软件包=5 已验证,图标清单值不确定),
754
771
  * 未经真机验证前只上传 OBS 不做 file-info 绑定(skip 并在结果中标注),避免误写损坏草稿。
755
772
  */
756
773
  async updateListing(ctx, listing) {
@@ -763,7 +780,7 @@ var HuaweiAndroidChannel = class {
763
780
  const body = { lang: "zh-CN" };
764
781
  if (effective.brief !== void 0) body.briefInfo = effective.brief;
765
782
  if (effective.description !== void 0) body.appDesc = effective.description;
766
- await hwJson(`${BASE$1}/publish/v3/app-language-info?appId=${encodeURIComponent(appId)}`, {
783
+ await hwJson(`${BASE$1}/publish/v2/app-language-info?appId=${encodeURIComponent(appId)}`, {
767
784
  method: "PUT",
768
785
  headers: {
769
786
  ...headers,
@@ -825,7 +842,7 @@ var HuaweiAndroidChannel = class {
825
842
  case 1:
826
843
  case 8: return {
827
844
  state: "rejected",
828
- reason: json.appInfo?.auditDesc
845
+ reason: json.auditInfo?.auditOpinion ?? json.appInfo?.auditDesc
829
846
  };
830
847
  case 2: return {
831
848
  state: "unknown",
@@ -865,9 +882,12 @@ function required$5(ctx, key, where) {
865
882
  * sig = RSA_encrypt(证书公钥, JSON({sig: [{name, hash: MD5}...], password}))
866
883
  * 分段: 1024bit key → 每段 117 字节(128 - PKCS1 padding 11)
867
884
  * 链路: /dev/query 校验账号对包名权限 + 提审前版本回归预检 → /dev/push 上传即提审(synchroType=1 更新包,
868
- * 未传字段如 icon/privacyUrl 不覆盖线上值;无法只传包不提审)。
885
+ * icon 现行文档(pId=1134,2026-02)标必选,apkgo 同(每次 push 必带);旧官方 PDF(pId=33)标可选,
886
+ * 分歧按现行文档从严处理——applift 默认从工程 mipmap 自动发现(android.stores.xiaomi.icon 可覆盖),
887
+ * 未找到才中止;privacyUrl 是否强制仍待真机验证(apkgo 未传该字段)。
888
+ * 另 2026-02-04 起 testAccount 须用新结构化格式(如 {"zh_CN":{"accounts":[...]}}),当前未传暂不受影响。
869
889
  * 移植自(MIT): AngChow/android-publish scripts/_mi_common.py + mi_submit.py
870
- * 官方文档: https://dev.mi.com/distribute/doc/details?pId=1134
890
+ * 官方文档: https://dev.mi.com/xiaomihyperos/documentation/detail?pId=1134
871
891
  */
872
892
  const MI_API_BASE = "https://api.developer.xiaomi.com/devupload";
873
893
  /**
@@ -966,6 +986,9 @@ var XiaomiChannel = class {
966
986
  const localVc = Number(ctx.target.versionCode);
967
987
  const liveVc = Number(query.packageInfo?.onlineVersionCode ?? query.packageInfo?.versionCode);
968
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}`);
969
992
  const appInfo = {
970
993
  appName,
971
994
  packageName: pkgName,
@@ -977,7 +1000,7 @@ var XiaomiChannel = class {
977
1000
  synchroType: 1
978
1001
  };
979
1002
  const requestJson = JSON.stringify(request);
980
- const sig = generateMiSig(requestJson, [["apk", ctx.artifactPath]], password, certPem);
1003
+ const sig = generateMiSig(requestJson, [["apk", ctx.artifactPath], ["icon", iconPath]], password, certPem);
981
1004
  const fileName = path.basename(ctx.artifactPath);
982
1005
  ctx.emit({
983
1006
  type: "phase",
@@ -988,6 +1011,7 @@ var XiaomiChannel = class {
988
1011
  form.append("RequestData", requestJson);
989
1012
  form.append("SIG", sig);
990
1013
  form.append("apk", new Blob([fs.readFileSync(ctx.artifactPath)]), fileName);
1014
+ form.append("icon", new Blob([fs.readFileSync(iconPath)]), path.basename(iconPath));
991
1015
  const res = await fetch(`${MI_API_BASE}/dev/push`, {
992
1016
  method: "POST",
993
1017
  body: form,
@@ -1047,7 +1071,11 @@ var XiaomiChannel = class {
1047
1071
  };
1048
1072
  }
1049
1073
  };
1050
- /** MI_PASSWORD 为主键名(applift 沿用);兼容 python 权威键名 MI_PRIVATE_KEY(_mi_common.get_mi_credentials) */
1074
+ /**
1075
+ * MI_PASSWORD 为主键名(applift 沿用);兼容 python 权威键名 MI_PRIVATE_KEY(_mi_common.get_mi_credentials)。
1076
+ * 值为小米开放平台的「API 私钥」(SIG 加密的 password 材料),非开发者账号登录密码 ——
1077
+ * 文档明确:访问密码即私钥,重置私钥后私钥变、小米分配的公钥不变。
1078
+ */
1051
1079
  function miPassword(ctx) {
1052
1080
  const v = ctx.credentials.MI_PASSWORD ?? ctx.credentials.MI_PRIVATE_KEY;
1053
1081
  if (!v) throw new Error("小米渠道缺少 MI_PASSWORD(兼容别名 MI_PRIVATE_KEY)");
@@ -1062,10 +1090,14 @@ function required$4(ctx, key, where) {
1062
1090
  //#region src/channels/oppo.ts
1063
1091
  /**
1064
1092
  * OPPO 应用市场(安卓 APK)渠道 —— app/upd 发布字段集/参数名以 python/android-publish oppo_submit.py 为权威对拍。
1065
- * v2 链路: GET token(48h 有效)→ get-upload-url → multipart 上传(stage)→ 查 app/info(复用线上字段)→ app/upd 发布(submit)
1093
+ * v2 链路: GET token(48h 有效)→ get-upload-url → multipart 上传(stage)→ 查 app/info(复用线上字段)→ app/upd 发布
1094
+ * → task-state 轮询确认异步任务结果(submit;app/upd 为异步任务,errno=0 仅代表受理)
1066
1095
  * 签名: HmacSHA256(公共参数 access_token + timestamp(秒) + api_sign)
1067
1096
  */
1068
1097
  const API_BASE$3 = "https://oop-openapi-cn.heytapmobi.com";
1098
+ /** app/upd 异步任务结果轮询:间隔 10s(现行文档建议等待 ≥10s)× 最多 5 次 */
1099
+ const TASK_STATE_POLL_INTERVAL_MS = 1e4;
1100
+ const TASK_STATE_MAX_POLLS = 5;
1069
1101
  /** 获取 OPPO access_token(GET,48h 有效)—— 来源: oppo.mjs getOppoToken */
1070
1102
  async function getOppoToken(clientId, clientSecret) {
1071
1103
  const qs = new URLSearchParams({
@@ -1195,7 +1227,7 @@ function buildUpdParams(appInfo, apkUrl, apkMd5, versionCode, updateDesc) {
1195
1227
  app_name: infoField(appInfo, "app_name"),
1196
1228
  second_category_id: infoField(appInfo, "second_category_id"),
1197
1229
  third_category_id: infoField(appInfo, "third_category_id"),
1198
- summary: infoField(appInfo, "summary").slice(0, 13),
1230
+ summary: infoField(appInfo, "summary").slice(0, 13).replace(/[\p{P}\p{Z}\s]/gu, ""),
1199
1231
  detail_desc: infoField(appInfo, "detail_desc"),
1200
1232
  update_desc: updateDesc,
1201
1233
  privacy_source_url: infoField(appInfo, "privacy_source_url"),
@@ -1233,7 +1265,10 @@ var OppoChannel = class {
1233
1265
  errorHints = {
1234
1266
  "911215": "应用已有版本在审核中(等待审核完成或到控制台撤回后再提)",
1235
1267
  "911216": "版本更新任务处理中(稍后用 status 查询结果)",
1236
- "800002": "参数校验失败:常见于简介/截图等商店资料不符合规格"
1268
+ "800002": "参数校验失败:常见于简介/截图等商店资料不符合规格",
1269
+ "910003": "上传 sign 失效(sign 单次有效),重新发起上传即可重试",
1270
+ "911045": "client_id 与包名不匹配(检查 API 客户端与应用的归属关系)",
1271
+ "300002": "api_sign 签名错误(检查 client_secret 与系统时间偏移)"
1237
1272
  };
1238
1273
  /** stage 产物暂存(apkUrl/md5),submit 按 pkgName 取回 */
1239
1274
  staged = /* @__PURE__ */ new Map();
@@ -1327,7 +1362,7 @@ var OppoChannel = class {
1327
1362
  }
1328
1363
  async submit(ctx) {
1329
1364
  const auth = await this.auth(ctx);
1330
- const appInfo = checkErrno(await oppoApi("/resource/v1/app/info", auth, { pkgName: auth.pkgName }, "GET", "查询应用详情"), "查询应用详情").data ?? {};
1365
+ const appInfo = checkErrno(await oppoApi("/resource/v1/app/info", auth, { pkg_name: auth.pkgName }, "GET", "查询应用详情"), "查询应用详情").data ?? {};
1331
1366
  let staged = this.staged.get(auth.pkgName);
1332
1367
  if (!staged) {
1333
1368
  ctx.emit({
@@ -1352,6 +1387,7 @@ var OppoChannel = class {
1352
1387
  type: "warn",
1353
1388
  message: inReview ? "errno=911215 应用审核中:该版本已在 OPPO 审核队列,本次未重复创建任务" : "errno=911216 版本更新任务处理中:上一提交任务仍在处理,本次未创建新任务,稍后可用 status 查询审核状态"
1354
1389
  });
1390
+ if (!inReview) await this.pollTaskState(ctx, auth, versionCode);
1355
1391
  ctx.emit({
1356
1392
  type: "phase",
1357
1393
  phase: "done",
@@ -1363,6 +1399,7 @@ var OppoChannel = class {
1363
1399
  };
1364
1400
  }
1365
1401
  checkErrno(upd, "发布版本");
1402
+ await this.pollTaskState(ctx, auth, versionCode);
1366
1403
  ctx.emit({
1367
1404
  type: "phase",
1368
1405
  phase: "done",
@@ -1374,8 +1411,46 @@ var OppoChannel = class {
1374
1411
  };
1375
1412
  }
1376
1413
  /**
1414
+ * app/upd 异步任务结果轮询: POST /resource/v1/app/task-state(业务参数 pkg_name+version_code,
1415
+ * 现行文档「获取任务状态」)。task_state: 1=待处理(继续等)、2=处理成功、3=处理失败(err_msg 透出);
1416
+ * 间隔 10s × 最多 5 次,超时视为已受理但异步结果未知(warn 提示,稍后可用 status 查询审核状态)。
1417
+ */
1418
+ async pollTaskState(ctx, auth, versionCode) {
1419
+ for (let attempt = 1; attempt <= TASK_STATE_MAX_POLLS; attempt++) {
1420
+ await sleep(TASK_STATE_POLL_INTERVAL_MS);
1421
+ const st = checkErrno(await oppoApi("/resource/v1/app/task-state", auth, {
1422
+ pkg_name: auth.pkgName,
1423
+ version_code: versionCode
1424
+ }, "POST", "查询任务状态"), "查询任务状态");
1425
+ const taskState = st.data?.["task_state"];
1426
+ if (taskState === 2) {
1427
+ ctx.emit({
1428
+ type: "progress",
1429
+ detail: `任务处理成功(task_state=2,第 ${attempt} 次轮询确认)`
1430
+ });
1431
+ return;
1432
+ }
1433
+ if (taskState === 3) {
1434
+ const errMsg = st.data?.["err_msg"];
1435
+ throw new Error(`OPPO 发布任务处理失败(task_state=3): ${typeof errMsg === "string" && errMsg ? errMsg : JSON.stringify(st.data).slice(0, 200)}`);
1436
+ }
1437
+ ctx.emit({
1438
+ type: "progress",
1439
+ detail: `任务处理中(task_state=${String(taskState)},第 ${attempt}/${TASK_STATE_MAX_POLLS} 次)`
1440
+ });
1441
+ }
1442
+ ctx.emit({
1443
+ type: "warn",
1444
+ message: `任务状态轮询 ${TASK_STATE_MAX_POLLS} 次未到终态:app/upd 已受理但异步处理结果未知,请稍后用 status 查询审核状态`
1445
+ });
1446
+ }
1447
+ /**
1377
1448
  * 审核状态查询: GET /resource/v1/app/info(公共参数 + api_sign,同 stage)
1378
- * → data.audit_status(android-publish oppo_query.py: 1=待提审 2=审核中 3=通过 4=驳回)。
1449
+ * → data.audit_status 按现行「审核状态对照表」映射(id=10998):
1450
+ * 1=审核中、4=运营审核中 → reviewing;2=审核通过、6=运营通过 → approved;111=上线 → approved+live;
1451
+ * 3=测试不通过、5=运营打回、444=审核不通过 → rejected(原因读 refuse_reason,原样透出不解析);
1452
+ * 0=未发布、7=定时发布、22=已冻结 → unknown+raw;其余编号(00/11/-11 资质类、222 下架等)→ unknown。
1453
+ * 旧映射(1=待提审 2=审核中 3=通过 4=驳回,出自 python oppo_query.py)与现行对照表整体错位,已重写。
1379
1454
  */
1380
1455
  async status(ctx) {
1381
1456
  const clientId = this.required(ctx, "OPPO_CLIENT_ID");
@@ -1385,7 +1460,7 @@ var OppoChannel = class {
1385
1460
  clientId,
1386
1461
  clientSecret,
1387
1462
  token: await getOppoToken(clientId, clientSecret)
1388
- }, { pkgName }, "GET", "查询审核状态"), "查询审核状态");
1463
+ }, { pkg_name: pkgName }, "GET", "查询审核状态"), "查询审核状态");
1389
1464
  const data = info.data ?? {};
1390
1465
  const auditStatus = data["audit_status"] ?? data["auditStatus"];
1391
1466
  if (auditStatus === void 0) return {
@@ -1398,21 +1473,37 @@ var OppoChannel = class {
1398
1473
  raw: `audit_status=${String(auditStatus)}`
1399
1474
  };
1400
1475
  switch (num) {
1401
- case 2: return { state: "reviewing" };
1402
- case 3: return {
1476
+ case 1:
1477
+ case 4: return { state: "reviewing" };
1478
+ case 2:
1479
+ case 6: return {
1480
+ state: "approved",
1481
+ live: false
1482
+ };
1483
+ case 111: return {
1403
1484
  state: "approved",
1404
1485
  live: true
1405
1486
  };
1406
- case 4: {
1487
+ case 3:
1488
+ case 5:
1489
+ case 444: {
1407
1490
  const reason = data["refuse_reason"];
1408
1491
  return {
1409
1492
  state: "rejected",
1410
1493
  reason: typeof reason === "string" && reason ? reason : void 0
1411
1494
  };
1412
1495
  }
1413
- case 1: return {
1496
+ case 0: return {
1497
+ state: "unknown",
1498
+ raw: "未发布(audit_status=0)"
1499
+ };
1500
+ case 7: return {
1414
1501
  state: "unknown",
1415
- raw: "待提审(audit_status=1)"
1502
+ raw: "定时发布(audit_status=7)"
1503
+ };
1504
+ case 22: return {
1505
+ state: "unknown",
1506
+ raw: "已冻结(audit_status=22)"
1416
1507
  };
1417
1508
  default: return {
1418
1509
  state: "unknown",
@@ -1443,7 +1534,7 @@ function commonParams(accessKey, method) {
1443
1534
  timestamp: tsMillis(),
1444
1535
  format: "json",
1445
1536
  v: "1.0",
1446
- sign_method: "hmac",
1537
+ sign_method: "hmac-sha256",
1447
1538
  target_app_key: "developer"
1448
1539
  };
1449
1540
  }
@@ -1463,8 +1554,10 @@ var VivoChannel = class {
1463
1554
  status: true
1464
1555
  };
1465
1556
  errorHints = {
1466
- "1002": "参数缺失或不合法(常见:更新说明少于 5 字)",
1467
- "46001": "访问频率超限(正式环境每接口 50 次/天)"
1557
+ "10001": "签名校验失败(检查 VIVO_ACCESS_SECRET 是否正确)",
1558
+ "10005": "时间戳失效(检查系统时间偏差)",
1559
+ "10011": "当天调用次数超限(约 50 次/天)",
1560
+ "12010": "应用审核中,不允许更新"
1468
1561
  };
1469
1562
  /**
1470
1563
  * stage→submit 之间传递上传结果:Channel 接口不透传 ref,
@@ -1574,6 +1667,7 @@ var VivoChannel = class {
1574
1667
  if (!serialnumber) throw new Error("vivo 提审缺少 serialnumber(上传未返回)");
1575
1668
  const updateDesc = (ctx.updateDescription ?? "").trim();
1576
1669
  if (updateDesc.length < 5) throw new Error(`vivo 提审 updateDesc 需 5-200 字(当前 ${updateDesc.length} 字),请提供更新说明`);
1670
+ if (updateDesc.length > 200) throw new Error(`vivo 提审 updateDesc 需 5-200 字(当前 ${updateDesc.length} 字,超出上限),请精简更新说明`);
1577
1671
  const { accessKey, accessSecret } = this.creds(ctx);
1578
1672
  ctx.emit({
1579
1673
  type: "phase",
@@ -1599,37 +1693,44 @@ var VivoChannel = class {
1599
1693
  };
1600
1694
  }
1601
1695
  /**
1602
- * 审核状态查询: 复用 app.query.details 的 data.auditStatus(对拍 python vivo_query.py;
1603
- * 未确证的 app.audit.status method 已删——python/apkgo 均无此调用)。
1604
- * 映射 0=审核通过 1=审核中 2=驳回 沿自 v1,待真机校准;
1605
- * 交叉见证: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 误记,已弃用)。
1606
1699
  */
1607
1700
  async status(ctx) {
1608
1701
  required$3(ctx, "pkgName", "审核状态查询必需");
1609
1702
  const data = await this.queryDetails(ctx);
1610
- const auditStatus = data.auditStatus ?? data.audit_status;
1611
- if (auditStatus === void 0) return {
1703
+ const st = data.status ?? data.auditStatus ?? data.audit_status;
1704
+ if (st === void 0) return {
1612
1705
  state: "unknown",
1613
- raw: `app.query.details 无 auditStatus: ${JSON.stringify(data).slice(0, 200)}`
1706
+ raw: `app.query.details 无 status: ${JSON.stringify(data).slice(0, 200)}`
1614
1707
  };
1615
- const num = Number(auditStatus);
1708
+ const num = Number(st);
1616
1709
  if (!Number.isFinite(num)) return {
1617
1710
  state: "unknown",
1618
- raw: `auditStatus=${String(auditStatus)}`
1711
+ raw: `status=${String(st)}`
1619
1712
  };
1620
1713
  switch (num) {
1621
- case 0: return {
1714
+ case 2: return { state: "reviewing" };
1715
+ case 3: return {
1622
1716
  state: "approved",
1623
1717
  live: true
1624
1718
  };
1625
- case 1: return { state: "reviewing" };
1626
- case 2: return {
1719
+ case 4: return {
1627
1720
  state: "rejected",
1628
1721
  reason: this.auditReason(data)
1629
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
+ };
1630
1731
  default: return {
1631
1732
  state: "unknown",
1632
- raw: `auditStatus=${num}(未验证编号)`
1733
+ raw: `status=${num}(未验证编号)`
1633
1734
  };
1634
1735
  }
1635
1736
  }
@@ -1701,6 +1802,24 @@ async function honorPost(apiPath, token, bodyObj, context) {
1701
1802
  async function honorPostQ(apiPath, appId, token, bodyObj, context) {
1702
1803
  return honorPost(`${apiPath}?${new URLSearchParams({ appId }).toString()}`, token, bodyObj, context);
1703
1804
  }
1805
+ /** 带 appId query 的 GET(查询类接口,仅 Bearer 鉴权无 body) */
1806
+ async function honorGetQ(apiPath, appId, token, context) {
1807
+ const qs = new URLSearchParams({ appId }).toString();
1808
+ const res = await fetch(`${PUBLISH_BASE}${apiPath}?${qs}`, {
1809
+ method: "GET",
1810
+ headers: { Authorization: `Bearer ${token}` },
1811
+ signal: AbortSignal.timeout(6e4)
1812
+ });
1813
+ const text = await res.text();
1814
+ let json;
1815
+ try {
1816
+ json = JSON.parse(text);
1817
+ } catch {
1818
+ throw new Error(`荣耀 ${context}返回非 JSON(HTTP ${res.status}): ${text.slice(0, 300)}`);
1819
+ }
1820
+ if (json.code !== 0) throw new Error(`荣耀 ${context}失败 code=${json.code} ${json.msg ?? ""}: ${text.slice(0, 200)}`);
1821
+ return json;
1822
+ }
1704
1823
  /** RFC3339 → 荣耀定时发布格式:偏移不带冒号(+08:00→+0800)、Z→+0000,如 2026-06-20T10:00:00+0800(形状参照 apkgo submitAudit) */
1705
1824
  function honorReleaseTime(rfc3339) {
1706
1825
  const converted = rfc3339.replace(/([+-]\d{2}):(\d{2})$/, "$1$2").replace(/Z$/, "+0000");
@@ -1717,6 +1836,10 @@ var HonorChannel = class {
1717
1836
  status: true
1718
1837
  };
1719
1838
  errorHints = {
1839
+ "10003": "token 过期(CLI 每次现取,若出现请重试)",
1840
+ "20022": "已有审核中的版本,需等待审核完成后再提交",
1841
+ "30006": "版本号必须大于等于已上架版本",
1842
+ "30011": "提交过于频繁,请稍后再试",
1720
1843
  "100030001": "认证失败:Client ID/Secret 无效或未开通 API 权限",
1721
1844
  "100030002": "token 过期(CLI 每次现取,若出现请重试)"
1722
1845
  };
@@ -1791,7 +1914,19 @@ var HonorChannel = class {
1791
1914
  const appId = required$2(ctx, "appId");
1792
1915
  const token = await this.fetchToken(ctx);
1793
1916
  if (ctx.updateDescription) try {
1794
- await honorPostQ("/openapi/v1/publish/update-language-info", appId, token, { newFeature: ctx.updateDescription }, "更新文案");
1917
+ if (ctx.updateDescription.length > 500) throw new Error(`更新文案超过 500 字(当前 ${ctx.updateDescription.length} 字)`);
1918
+ const detail = await honorPostQ("/openapi/v1/publish/get-app-detail", appId, token, {}, "查询应用详情");
1919
+ const { appName, intro } = detail.data ?? {};
1920
+ if (!appName || !intro) throw new Error(`应用详情缺 appName/intro: ${JSON.stringify(detail.data ?? {}).slice(0, 200)}`);
1921
+ await honorPostQ("/openapi/v1/publish/update-language-info", appId, token, {
1922
+ languageInfoList: [{
1923
+ languageId: "zh-CN",
1924
+ appName,
1925
+ intro,
1926
+ newFeature: ctx.updateDescription
1927
+ }],
1928
+ setAll: 0
1929
+ }, "更新文案");
1795
1930
  } catch (e) {
1796
1931
  ctx.emit({
1797
1932
  type: "warn",
@@ -1817,14 +1952,11 @@ var HonorChannel = class {
1817
1952
  };
1818
1953
  }
1819
1954
  async status(ctx) {
1820
- const appId = required$2(ctx, "appId", "审核状态查询必需");
1821
- const appIdNum = Number(appId);
1822
- if (!Number.isFinite(appIdNum)) throw new Error(`荣耀 appId 非数字: ${appId}(get-audit-result 要求 body 内为数字)`);
1823
- const json = await honorPost("/openapi/v1/publish/get-audit-result", await this.fetchToken(ctx), { appId: [{ appId: appIdNum }] }, "查询审核结果");
1824
- const item = json.data?.[0];
1955
+ const json = await honorGetQ("/openapi/v1/publish/get-app-current-release", required$2(ctx, "appId", "审核状态查询必需"), await this.fetchToken(ctx), "查询审核状态");
1956
+ const item = json.data;
1825
1957
  if (!item || item.auditResult === void 0) return {
1826
1958
  state: "unknown",
1827
- raw: `get-audit-result 无数据: ${JSON.stringify(json).slice(0, 200)}`
1959
+ raw: `get-app-current-release 无数据: ${JSON.stringify(json).slice(0, 200)}`
1828
1960
  };
1829
1961
  switch (item.auditResult) {
1830
1962
  case 0: return { state: "reviewing" };
@@ -1838,7 +1970,7 @@ var HonorChannel = class {
1838
1970
  };
1839
1971
  case 3: return {
1840
1972
  state: "unknown",
1841
- raw: "非审核状态(auditResult=3)"
1973
+ raw: "未提交审核(auditResult=3)"
1842
1974
  };
1843
1975
  case 4: return {
1844
1976
  state: "unknown",
@@ -1858,12 +1990,61 @@ function required$2(ctx, key, where) {
1858
1990
  return v;
1859
1991
  }
1860
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
1861
2042
  //#region src/channels/yyb.ts
1862
2043
  /**
1863
2044
  * 腾讯应用宝(安卓 APK)渠道。
1864
2045
  * 权威: AngChow/android-publish scripts/_yyb_common.py + submit_apk.py + query_status.py(MIT),apkgo pkg/store/tencent 交叉验证
1865
2046
  * 官方文档: https://wikinew.open.qq.com/index.html#/iwiki/4015262492
1866
- * v2 链路: query_app_detail(校验应用 + 取线上版本)→ get_file_upload_info(pkg_name/app_id/file_type=apk/file_name → COS 预签名 URL + serial_number)
2047
+ * v2 链路: query_app_detail(校验应用)→ get_file_upload_info(pkg_name/app_id/file_type=apk/file_name → COS 预签名 URL + serial_number)
1867
2048
  * → PUT 预签名 URL 直传 COS(预签名自带鉴权,无需自签 sign)→ update_app 提审(serial_number + md5)
1868
2049
  * 签名: HmacSHA256(公共参数 user_id + timestamp(秒) + 业务参数,按 key ASCII 升序)——仅 open API 需要,COS 上传不需要
1869
2050
  * 限额: 上传 100 次/天、更新 50 次/天;审核状态 audit_status 1=审核中 2=驳回 3=通过 8=撤销
@@ -1928,13 +2109,16 @@ var YybChannel = class {
1928
2109
  type: "progress",
1929
2110
  detail: "查询应用详情"
1930
2111
  });
1931
- const appInfo = (await yybApi("/query_app_detail", {
2112
+ const detail = await yybApi("/query_app_detail", {
1932
2113
  userId,
1933
2114
  accessSecret
1934
- }, { app_id: appId })).app_info ?? {};
2115
+ }, {
2116
+ pkg_name: pkgName,
2117
+ app_id: appId
2118
+ });
1935
2119
  ctx.emit({
1936
2120
  type: "progress",
1937
- detail: `${appInfo.app_name ?? ""} 线上版本 ${appInfo.version ?? "?"}`
2121
+ detail: `应用「${detail.app_name ?? appId}」校验通过`
1938
2122
  });
1939
2123
  const upInfo = await yybApi("/get_file_upload_info", {
1940
2124
  userId,
@@ -1989,9 +2173,16 @@ var YybChannel = class {
1989
2173
  const businessParams = {
1990
2174
  pkg_name: pkgName,
1991
2175
  app_id: appId,
1992
- apk32_flag: "1",
1993
- apk32_file_serial_number: stagedInfo.serialNumber,
1994
- 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
+ },
1995
2186
  feature: ctx.updateDescription ?? ""
1996
2187
  };
1997
2188
  if (ctx.releaseTime) {
@@ -2083,6 +2274,8 @@ var YybChannel = class {
2083
2274
  const BASE = "https://connect-api.cloud.huawei.com/api";
2084
2275
  const COMPILE_TIMEOUT_MS = 9e5;
2085
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";
2086
2279
  /** AGC JSON API 封装:判 ret.code !== 0 抛错 —— v1 agc.mjs agcJson 原样 */
2087
2280
  async function agcJson(url, init, context) {
2088
2281
  const res = await fetch(url, init);
@@ -2128,7 +2321,7 @@ async function getDefaultLang(headers, appId) {
2128
2321
  return (await agcJson(`${BASE}/publish/v3/app-info?${qs}`, { headers }, "查询应用信息")).appInfo?.defaultLang || "zh-CN";
2129
2322
  }
2130
2323
  var AgcHarmonyChannel = class {
2131
- id = "agc-harmony";
2324
+ id = "agc";
2132
2325
  label = "华为 AGC(鸿蒙)";
2133
2326
  capabilities = {
2134
2327
  stage: true,
@@ -2181,7 +2374,7 @@ var AgcHarmonyChannel = class {
2181
2374
  artifactVersion: "",
2182
2375
  action: "stage-only",
2183
2376
  afterApproval: "none(beta)",
2184
- notes: "Testing API 邀请测试:上传→新建测试版本→绑定软件包;提交审核与测试群组到控制台完成,不自动提审"
2377
+ notes: ctx.target.submitTest === "true" ? "Testing API 邀请测试:上传→新建测试版本→绑定软件包→(可选绑测试群组)自动提交测试审核" : "Testing API 邀请测试:上传→新建测试版本→绑定软件包;提交审核与测试群组到控制台完成,不自动提审"
2185
2378
  };
2186
2379
  return {
2187
2380
  channel: this.id,
@@ -2212,7 +2405,7 @@ var AgcHarmonyChannel = class {
2212
2405
  async submit(ctx) {
2213
2406
  if (ctx.target.releaseType === "6") return {
2214
2407
  ok: false,
2215
- error: "AGC 测试发布(releaseType=6)不做自动提审,请到 AGC 控制台「测试」提交测试版本并指定测试群组"
2408
+ error: "AGC 测试发布(releaseType=6)的自动提审走 beta 配置 test['agc'].submit=true(上传绑定后自动提交测试版本审核)"
2216
2409
  };
2217
2410
  const { clientId, token, headers } = await this.authHeaders(ctx);
2218
2411
  const appId = await this.resolveAppId(ctx, clientId, token);
@@ -2223,6 +2416,10 @@ var AgcHarmonyChannel = class {
2223
2416
  });
2224
2417
  const { packageId } = await this.uploadFormal(ctx, appId, headers);
2225
2418
  if (packageId) await this.awaitCompile(ctx, appId, packageId, headers);
2419
+ if (!ctx.updateDescription) ctx.emit({
2420
+ type: "warn",
2421
+ message: "未提供更新说明(--note):AGC 升级版本要求 newFeatures 必填,提审可能被拒"
2422
+ });
2226
2423
  ctx.emit({
2227
2424
  type: "phase",
2228
2425
  phase: "submit"
@@ -2369,8 +2566,8 @@ var AgcHarmonyChannel = class {
2369
2566
  /**
2370
2567
  * 测试发布(releaseType=6,2026-10 起走 Testing API——旧 app-package-info 直绑链路已被华为下线,
2371
2568
  * 其 releaseType 枚举仅剩 1=全网 / 7=企业非公开):
2372
- * 上传 .hap → 新建测试版本(testType=3 邀请测试)→ 添加测试软件包 → 等云端编译 → 更新测试版本绑定;
2373
- * 不自动提交审核,测试群组/邀请码由控制台人工完成。
2569
+ * 上传 .app → 新建测试版本(testType 3=邀请测试 4=公开测试)→ 添加测试软件包 → 等云端编译 → 更新测试版本绑定;
2570
+ * target.submitTest='true' 时:(可选按 testGroup 绑测试群组)自动提交测试版本审核;否则由控制台人工完成。
2374
2571
  */
2375
2572
  async uploadBeta(ctx, appId, headers) {
2376
2573
  const fileName = path.basename(ctx.artifactPath);
@@ -2417,6 +2614,7 @@ var AgcHarmonyChannel = class {
2417
2614
  type: "progress",
2418
2615
  detail: "文件上传完成"
2419
2616
  });
2617
+ const testType = ctx.target.testType === "4" ? 4 : 3;
2420
2618
  const versionId = (await agcJson(`${BASE}/publish/v2/test/app/version?appId=${encodeURIComponent(appId)}`, {
2421
2619
  method: "POST",
2422
2620
  headers: {
@@ -2425,7 +2623,7 @@ var AgcHarmonyChannel = class {
2425
2623
  },
2426
2624
  body: JSON.stringify({
2427
2625
  releaseType: 6,
2428
- testType: 3,
2626
+ testType,
2429
2627
  testDesc: (ctx.updateDescription ?? "applift 测试发布").slice(0, 50)
2430
2628
  })
2431
2629
  }, "新建测试版本")).versionId ?? "";
@@ -2462,16 +2660,92 @@ var AgcHarmonyChannel = class {
2462
2660
  pkgId
2463
2661
  })
2464
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
+ }
2465
2710
  ctx.emit({
2466
2711
  type: "phase",
2467
2712
  phase: "done",
2468
- message: "测试版本已就绪(未提交审核);到 AGC 控制台「测试」提交并指定测试群组"
2713
+ message: "测试版本已就绪(未提交审核);到控制台提交或配置 test['agc'].submit 自动提审"
2469
2714
  });
2470
2715
  return {
2471
2716
  ok: true,
2472
2717
  ref: versionId
2473
2718
  };
2474
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
+ }
2475
2749
  /** v1 更新文案段(releaseHarmonyApp step 4 / uploadToAgc 尾段):查默认语言 → 写 newFeatures,失败仅警告不阻断 */
2476
2750
  async updateReleaseNotes(ctx, appId, headers, qs) {
2477
2751
  if (!ctx.updateDescription) return;
@@ -2575,6 +2849,8 @@ async function ascRequest(jwt, apiPath, method, body) {
2575
2849
  *
2576
2850
  * - stage: xcrun altool 上传 ipa(免 fastlane/Ruby 依赖,底层即 App Store Connect API)
2577
2851
  * —— 自 v1 tools/publish/lib/asc.mjs 的 uploadIpaToAsc 迁移(逻辑不变)。
2852
+ * 注意:xcrun altool 上传通道已被 Apple 列为弃用(deprecated),后续迁移至
2853
+ * ASC API buildUploads 资源或 Transporter(altool 本身仍可用,暂保留)。
2578
2854
  * 约定 p8 私钥:凭据 ASC_P8_PATH(支持 ~ 前缀)或 ~/.applift/private_keys/AuthKey_<ASC_KEY_ID>.p8
2579
2855
  * (v1 放 tools/publish/private_keys/;altool 本身认 <cwd>/private_keys、~/private_keys、
2580
2856
  * ~/.private_keys、~/.appstoreconnect/private_keys,见 xcrun altool --help)
@@ -2675,11 +2951,11 @@ var AscChannel = class {
2675
2951
  }
2676
2952
  /**
2677
2953
  * 提审 App Store —— 按 ASC 官方文档新实现,未经真实链路验证。
2678
- * 文档: appStoreVersionSubmissions / Create a Version Submission
2954
+ * 文档: reviewSubmissions / Create a Review Submission / Submit a Review Submission
2679
2955
  * https://developer.apple.com/documentation/appstoreconnectapi
2680
2956
  * 链路: 查 app(bundleId → 数字 id)→ 查最新已处理(VALID)构建 → 读取/创建 appStoreVersion
2681
- * (releaseType 默认 MANUAL,ctx.releaseTime 提供时 SCHEDULED + earlyReleaseDate)
2682
- * → 绑定构建到版本 → POST appStoreVersionSubmissions 进入审核
2957
+ * (releaseType 默认 MANUAL,ctx.releaseTime 提供时 SCHEDULED + earliestReleaseDate)
2958
+ * → 绑定构建到版本 → reviewSubmissions 三步流提交进入审核
2683
2959
  */
2684
2960
  async submit(ctx) {
2685
2961
  const { keyId, issuerId, p8Path } = this.resolveAuth(ctx);
@@ -2714,9 +2990,9 @@ var AscChannel = class {
2714
2990
  const releaseType = ctx.releaseTime ? "SCHEDULED" : "MANUAL";
2715
2991
  const releaseAttrs = ctx.releaseTime ? {
2716
2992
  releaseType,
2717
- earlyReleaseDate: ctx.releaseTime
2993
+ earliestReleaseDate: ctx.releaseTime
2718
2994
  } : { releaseType };
2719
- const versions = await ascGet(jwt, `/v1/apps/${app.id}/appStoreVersions?filter[platform]=IOS&filter[appStoreState]=PREPARE_FOR_SUBMISSION,READY_FOR_SUBMISSION&limit=5`);
2995
+ const versions = await ascGet(jwt, `/v1/apps/${app.id}/appStoreVersions?filter[platform]=IOS&filter[appVersionState]=PREPARE_FOR_SUBMISSION&limit=5`);
2720
2996
  let versionId = versions.data[0]?.id;
2721
2997
  if (versionId) {
2722
2998
  await ascRequest(jwt, `/v1/appStoreVersions/${versionId}`, "PATCH", { data: {
@@ -2753,13 +3029,27 @@ var AscChannel = class {
2753
3029
  type: "builds",
2754
3030
  id: build.id
2755
3031
  }] });
2756
- const submission = await ascRequest(jwt, "/v1/appStoreVersionSubmissions", "POST", { data: {
2757
- type: "appStoreVersionSubmissions",
3032
+ const reviewSubmissionId = (await ascRequest(jwt, "/v1/reviewSubmissions", "POST", { data: {
3033
+ type: "reviewSubmissions",
3034
+ attributes: {},
3035
+ relationships: { app: { data: {
3036
+ type: "apps",
3037
+ id: app.id
3038
+ } } }
3039
+ } })).data?.id;
3040
+ if (!reviewSubmissionId) throw new Error("ASC 创建 reviewSubmission 未返回 id");
3041
+ await ascRequest(jwt, `/v1/reviewSubmissions/${reviewSubmissionId}/items`, "POST", { data: {
3042
+ type: "reviewSubmissionItems",
3043
+ attributes: {},
2758
3044
  relationships: { appStoreVersion: { data: {
2759
3045
  type: "appStoreVersions",
2760
3046
  id: versionId
2761
3047
  } } }
2762
3048
  } });
3049
+ await ascRequest(jwt, `/v1/reviewSubmissions/${reviewSubmissionId}`, "PATCH", { data: {
3050
+ type: "reviewSubmissions",
3051
+ attributes: { action: "SUBMIT" }
3052
+ } });
2763
3053
  ctx.emit({
2764
3054
  type: "phase",
2765
3055
  phase: "done",
@@ -2767,7 +3057,7 @@ var AscChannel = class {
2767
3057
  });
2768
3058
  return {
2769
3059
  ok: true,
2770
- ref: submission.data?.id
3060
+ ref: reviewSubmissionId
2771
3061
  };
2772
3062
  }
2773
3063
  async status() {
@@ -2970,6 +3260,7 @@ var GooglePlayChannel = class {
2970
3260
  type: "progress",
2971
3261
  detail: `上传成功(versionCode: ${versionCode})`
2972
3262
  });
3263
+ const releases = [...(await apiFetch(`${API_BASE}/${pkgName}/edits/${editId}/tracks/${track}`, token, { method: "GET" }, `读取 ${track} 轨道现有 releases`)).releases ?? []];
2973
3264
  const release = {
2974
3265
  versionCodes: [versionCode],
2975
3266
  status: "completed"
@@ -2978,9 +3269,14 @@ var GooglePlayChannel = class {
2978
3269
  language: "zh-CN",
2979
3270
  text: releaseNotes
2980
3271
  }];
3272
+ const target = releases.find((r) => r.status === "completed");
3273
+ if (target) {
3274
+ target.versionCodes = [...target.versionCodes ?? [], versionCode];
3275
+ if (release.releaseNotes) target.releaseNotes = release.releaseNotes;
3276
+ } else releases.push(release);
2981
3277
  await apiFetch(`${API_BASE}/${pkgName}/edits/${editId}/tracks/${track}`, token, {
2982
3278
  method: "PUT",
2983
- body: JSON.stringify({ releases: [release] })
3279
+ body: JSON.stringify({ releases })
2984
3280
  }, `部署 ${track} 轨道`);
2985
3281
  ctx.emit({
2986
3282
  type: "phase",
@@ -2998,7 +3294,7 @@ var GooglePlayChannel = class {
2998
3294
  async status(ctx) {
2999
3295
  const pkgName = this.requirePkgName(ctx);
3000
3296
  const token = await getAccessToken(ctx.credentials);
3001
- const release = (await apiFetch(`${API_BASE}/${pkgName}/edits/-/tracks/production`, token, { method: "GET" }, "查询 production 轨道")).releases?.[0];
3297
+ const release = (await apiFetch(`${API_BASE}/${pkgName}/tracks/production`, token, { method: "GET" }, "查询 production 轨道")).releases?.[0];
3002
3298
  if (!release || !release.status) return {
3003
3299
  state: "unknown",
3004
3300
  raw: "production 轨道无 release"
@@ -3800,4 +4096,4 @@ function fromAapt(artifactPath) {
3800
4096
  }
3801
4097
  }
3802
4098
  //#endregion
3803
- 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.1.3",
3
+ "version": "0.3.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/hcg1023/applift.git",