@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 +45 -20
- package/dist/index.mjs +394 -98
- package/dist/{plugin-T6zXsmt1.d.mts → plugin-DzhwzVfu.d.mts} +45 -2
- package/dist/plugins/index.d.mts +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
* 更新商店应用信息:简介/描述走
|
|
148
|
-
*
|
|
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
|
|
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
|
-
"
|
|
221
|
-
"
|
|
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.
|
|
240
|
-
*
|
|
241
|
-
*
|
|
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
|
|
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
|
-
* 上传 .
|
|
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
|
-
* 文档:
|
|
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 +
|
|
350
|
-
* → 绑定构建到版本 →
|
|
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.
|
|
32
|
-
* 加载用社区主流 c12(TS 配置执行 + 多层 merge),
|
|
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.
|
|
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.
|
|
133
|
+
/** 项目级凭据目录(<project>/.applift/credentials.json;gitignore 由用户决定:私有仓可提交,公开仓 ignore) */
|
|
107
134
|
function projectCredentialsPath(projectRoot) {
|
|
108
|
-
return path.join(projectRoot, ".applift", "credentials.
|
|
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.
|
|
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.
|
|
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.
|
|
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,
|
|
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
|
-
* 更新商店应用信息:简介/描述走
|
|
753
|
-
*
|
|
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/
|
|
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
|
-
*
|
|
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/
|
|
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
|
-
/**
|
|
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
|
|
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, {
|
|
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
|
|
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
|
|
1402
|
-
case
|
|
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
|
|
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
|
|
1496
|
+
case 0: return {
|
|
1497
|
+
state: "unknown",
|
|
1498
|
+
raw: "未发布(audit_status=0)"
|
|
1499
|
+
};
|
|
1500
|
+
case 7: return {
|
|
1414
1501
|
state: "unknown",
|
|
1415
|
-
raw: "
|
|
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
|
-
"
|
|
1467
|
-
"
|
|
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.
|
|
1603
|
-
*
|
|
1604
|
-
*
|
|
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
|
|
1611
|
-
if (
|
|
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 无
|
|
1706
|
+
raw: `app.query.details 无 status: ${JSON.stringify(data).slice(0, 200)}`
|
|
1614
1707
|
};
|
|
1615
|
-
const num = Number(
|
|
1708
|
+
const num = Number(st);
|
|
1616
1709
|
if (!Number.isFinite(num)) return {
|
|
1617
1710
|
state: "unknown",
|
|
1618
|
-
raw: `
|
|
1711
|
+
raw: `status=${String(st)}`
|
|
1619
1712
|
};
|
|
1620
1713
|
switch (num) {
|
|
1621
|
-
case
|
|
1714
|
+
case 2: return { state: "reviewing" };
|
|
1715
|
+
case 3: return {
|
|
1622
1716
|
state: "approved",
|
|
1623
1717
|
live: true
|
|
1624
1718
|
};
|
|
1625
|
-
case
|
|
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: `
|
|
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
|
-
|
|
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
|
|
1821
|
-
const
|
|
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-
|
|
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: "
|
|
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
|
|
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
|
|
2112
|
+
const detail = await yybApi("/query_app_detail", {
|
|
1932
2113
|
userId,
|
|
1933
2114
|
accessSecret
|
|
1934
|
-
}, {
|
|
2115
|
+
}, {
|
|
2116
|
+
pkg_name: pkgName,
|
|
2117
|
+
app_id: appId
|
|
2118
|
+
});
|
|
1935
2119
|
ctx.emit({
|
|
1936
2120
|
type: "progress",
|
|
1937
|
-
detail:
|
|
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
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
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
|
|
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
|
|
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
|
-
* 上传 .
|
|
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
|
|
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: "
|
|
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
|
-
* 文档:
|
|
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 +
|
|
2682
|
-
* → 绑定构建到版本 →
|
|
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
|
-
|
|
2993
|
+
earliestReleaseDate: ctx.releaseTime
|
|
2718
2994
|
} : { releaseType };
|
|
2719
|
-
const versions = await ascGet(jwt, `/v1/apps/${app.id}/appStoreVersions?filter[platform]=IOS&filter[
|
|
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
|
|
2757
|
-
type: "
|
|
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:
|
|
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
|
|
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}/
|
|
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 {
|
|
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 };
|
package/dist/plugins/index.d.mts
CHANGED