@applift/core 0.8.2 → 0.9.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 +42 -5
- package/dist/index.mjs +243 -24
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -349,7 +349,6 @@ export declare class AgcHarmonyChannel implements Channel {
|
|
|
349
349
|
"204144692": string;
|
|
350
350
|
};
|
|
351
351
|
private authHeaders;
|
|
352
|
-
/** appId 缺省时按 bundleName 查(v1 全链路 step 0,可选) */
|
|
353
352
|
private resolveAppId;
|
|
354
353
|
plan(ctx: ChannelContext): Promise<ReleasePlanEntry>;
|
|
355
354
|
/** stage = 上传+绑定(不提审):正式 .app 走 v3 绑定;ctx.target.releaseType==='6' 走测试发布(.hap) */
|
|
@@ -359,7 +358,15 @@ export declare class AgcHarmonyChannel implements Channel {
|
|
|
359
358
|
submit(ctx: ChannelContext, stagedRef?: string): Promise<SubmitResult>;
|
|
360
359
|
/** 轮询软件包云端编译:successStatus 0=成功 2=拒绝(终态,多为签名/证书/bundleName 不匹配)其他=编译中 */
|
|
361
360
|
private awaitCompile;
|
|
362
|
-
|
|
361
|
+
/**
|
|
362
|
+
* 查询商用版本审核状态:POST /publish/v3/version/brief-info/list。
|
|
363
|
+
* - 只取商用版本(testType 缺省或 1);测试版本(3=邀请/4=公开)走 Testing API 链路,不混入
|
|
364
|
+
* status 语义——过滤后为空即 unknown(fail-safe,不会误报测试版本状态)
|
|
365
|
+
* - state 取值同 AppInfo.releaseState 全表(与华为安卓渠道同表,apkgo/hw_query.py 交叉验证):
|
|
366
|
+
* 0=已上架 1/8=审核不通过 2=已下架 3=待上架 4/5=审核中 7=草稿 11=撤销上架
|
|
367
|
+
* - 多个商用版本取 updateTime 最新
|
|
368
|
+
*/
|
|
369
|
+
status(ctx: Pick<ChannelContext, "credentials" | "target">): Promise<ReviewStatus>;
|
|
363
370
|
/** v1 releaseHarmonyApp step 1-4:.app 校验 → 上传地址 → PUT 整包 → v3 绑定(releaseType=1)→ 文案(不阻断) */
|
|
364
371
|
private uploadFormal;
|
|
365
372
|
/**
|
|
@@ -389,20 +396,50 @@ export declare class AscChannel implements Channel {
|
|
|
389
396
|
private resolveAuth;
|
|
390
397
|
/**
|
|
391
398
|
* 上传 ipa 到 App Store Connect(TestFlight / 正式皆走此入口)。
|
|
399
|
+
* 双引擎:iTMSTransporter(JWT 认证,官方推荐 API 用户)优先,未装 Transporter.app 时回退 altool。
|
|
400
|
+
*/
|
|
401
|
+
stage(ctx: ChannelContext): Promise<StageResult>;
|
|
402
|
+
/**
|
|
403
|
+
* iTMSTransporter 路径:凭据 ASC_TRANSPORTER_PATH 显式指定(支持 ~ 前缀,校验存在性),
|
|
404
|
+
* 缺省探测 Transporter.app 默认安装路径;都不存在返回 undefined(回退 altool)。
|
|
405
|
+
* public 便于测试注入(spyOn 覆写返回值)。
|
|
406
|
+
*/
|
|
407
|
+
resolveTransporterPath(ctx: Pick<ChannelContext, "credentials">): string | undefined;
|
|
408
|
+
/**
|
|
409
|
+
* 引擎一:iTMSTransporter + JWT 上传(-m upload -jwt -assetFile)。
|
|
410
|
+
* - JWT 与 ASC REST API 同源(Apple 官方建议:Upload builds),20 分钟有效期覆盖典型上传时长;
|
|
411
|
+
* - -assetFile 成功后 Apple 侧删除源文件、多字节文件名上传失败 → 复制到临时随机 ASCII 名
|
|
412
|
+
* 再传(对齐 fastlane deliver 的防御做法),临时文件 finally 清理;
|
|
413
|
+
* - 成败判定(fastlane itunes_transporter 语义):exit 0 且 DBG-X: Returning 0(或无该行)为成功;
|
|
414
|
+
* Returning 非 0 或 exit 非 0 为失败,错误信息优先取 ERROR 行。
|
|
415
|
+
*/
|
|
416
|
+
private stageWithTransporter;
|
|
417
|
+
/**
|
|
418
|
+
* 引擎二(兜底):xcrun altool 上传。
|
|
392
419
|
* 来源: v1 tools/publish/lib/asc.mjs 的 uploadIpaToAsc(逻辑不变;v1 经 run 继承 stdio 输出,
|
|
393
420
|
* v2 输出统一走 ctx.emit,故改为捕获后透传,失败语义与 v1 run 一致)
|
|
394
421
|
*/
|
|
395
|
-
|
|
422
|
+
private stageWithAltool;
|
|
396
423
|
/**
|
|
397
424
|
* 提审 App Store —— 按 ASC 官方文档新实现,未经真实链路验证。
|
|
398
425
|
* 文档: reviewSubmissions / Create a Review Submission / Submit a Review Submission
|
|
399
426
|
* https://developer.apple.com/documentation/appstoreconnectapi
|
|
400
427
|
* 链路: 查 app(bundleId → 数字 id)→ 查最新已处理(VALID)构建 → 读取/创建 appStoreVersion
|
|
401
428
|
* (releaseType 默认 MANUAL,ctx.releaseTime 提供时 SCHEDULED + earliestReleaseDate)
|
|
402
|
-
* → 绑定构建到版本 → reviewSubmissions 三步流提交进入审核
|
|
429
|
+
* → 提审前预检(本地化描述)→ 绑定构建到版本 → reviewSubmissions 三步流提交进入审核
|
|
403
430
|
*/
|
|
404
431
|
submit(ctx: ChannelContext): Promise<SubmitResult>;
|
|
405
|
-
|
|
432
|
+
/**
|
|
433
|
+
* 查询审核状态:最新 appStoreVersion 的 appVersionState 映射到 ReviewStatus。
|
|
434
|
+
* 枚举见 AppVersionState(15 值):审核中/被拒/已批准未发布/已上架,未识别状态归 unknown。
|
|
435
|
+
* 跳过 PREPARE_FOR_SUBMISSION / REPLACED_WITH_NEW_VERSION(非活跃提审记录)。
|
|
436
|
+
*/
|
|
437
|
+
status(ctx: Pick<ChannelContext, "credentials" | "target">): Promise<ReviewStatus>;
|
|
438
|
+
/**
|
|
439
|
+
* 提审前预检(只读):目标版本的全部本地化必须有商店描述(description)。
|
|
440
|
+
* Apple 提审硬性要求;缺失时提前报错,避免 reviewSubmissions 最后一步被拒的返工。
|
|
441
|
+
*/
|
|
442
|
+
private preflightLocalizations;
|
|
406
443
|
}
|
|
407
444
|
//#endregion
|
|
408
445
|
//#region src/channels/googleplay.d.ts
|
package/dist/index.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { parse } from "smol-toml";
|
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { loadConfig } from "c12";
|
|
9
9
|
import { createJiti } from "jiti";
|
|
10
|
-
import crypto, { X509Certificate, constants, createHash, createHmac, createSign, publicEncrypt } from "node:crypto";
|
|
10
|
+
import crypto, { X509Certificate, constants, createHash, createHmac, createSign, publicEncrypt, randomUUID } from "node:crypto";
|
|
11
11
|
import { Readable } from "node:stream";
|
|
12
12
|
import { spawnSync } from "node:child_process";
|
|
13
13
|
import semver from "semver";
|
|
@@ -2434,6 +2434,7 @@ var YybChannel = class {
|
|
|
2434
2434
|
* 4. PUT /api/publish/v3/app-language-info newFeatures 更新文案(lang 缺省取 defaultLang)
|
|
2435
2435
|
* 5. GET /api/publish/v3/package/compile/status 轮询编译(0=成功 2=拒绝终态 其他=编译中)
|
|
2436
2436
|
* 6. POST /api/publish/v3/app-submit 提审(ret 204144719/204144727 = 编译中可重试)
|
|
2437
|
+
* 状态查询: POST /api/publish/v3/version/brief-info/list 查商用版本 state(同 AppInfo.releaseState 全表)
|
|
2437
2438
|
*
|
|
2438
2439
|
* 测试发布(ctx.target.releaseType === '6',2026-10 起走 Testing API;旧 releaseType=6 直绑 app-package-info 链路已下线,枚举仅剩 1/7):
|
|
2439
2440
|
* 0. GET /api/publish/v2/upload-url/for-obs?releaseType=6 .hap 上传地址 → PUT OBS
|
|
@@ -2502,7 +2503,7 @@ var AgcHarmonyChannel = class {
|
|
|
2502
2503
|
stage: true,
|
|
2503
2504
|
submit: true,
|
|
2504
2505
|
scheduled: true,
|
|
2505
|
-
status:
|
|
2506
|
+
status: true
|
|
2506
2507
|
};
|
|
2507
2508
|
errorHints = {
|
|
2508
2509
|
"204144719": "软件包正在编译中(正常时序,CLI 已自动重试;持续出现请到 AGC 控制台看包状态)",
|
|
@@ -2528,12 +2529,11 @@ var AgcHarmonyChannel = class {
|
|
|
2528
2529
|
}
|
|
2529
2530
|
};
|
|
2530
2531
|
}
|
|
2531
|
-
/** appId 缺省时按 bundleName 查(v1 全链路 step 0,可选) */
|
|
2532
2532
|
async resolveAppId(ctx, clientId, token) {
|
|
2533
2533
|
const appId = ctx.target.appId;
|
|
2534
2534
|
if (appId) return appId;
|
|
2535
2535
|
const bundleName = required$1(ctx, "bundleName", "项目配置 appId / bundleName 至少配一个");
|
|
2536
|
-
ctx.emit({
|
|
2536
|
+
ctx.emit?.({
|
|
2537
2537
|
type: "progress",
|
|
2538
2538
|
detail: `appId 缺省,按 bundleName=${bundleName} 查询 AGC 应用列表…`
|
|
2539
2539
|
});
|
|
@@ -2670,11 +2670,72 @@ var AgcHarmonyChannel = class {
|
|
|
2670
2670
|
await sleep(COMPILE_POLL_MS);
|
|
2671
2671
|
}
|
|
2672
2672
|
}
|
|
2673
|
-
|
|
2674
|
-
|
|
2673
|
+
/**
|
|
2674
|
+
* 查询商用版本审核状态:POST /publish/v3/version/brief-info/list。
|
|
2675
|
+
* - 只取商用版本(testType 缺省或 1);测试版本(3=邀请/4=公开)走 Testing API 链路,不混入
|
|
2676
|
+
* status 语义——过滤后为空即 unknown(fail-safe,不会误报测试版本状态)
|
|
2677
|
+
* - state 取值同 AppInfo.releaseState 全表(与华为安卓渠道同表,apkgo/hw_query.py 交叉验证):
|
|
2678
|
+
* 0=已上架 1/8=审核不通过 2=已下架 3=待上架 4/5=审核中 7=草稿 11=撤销上架
|
|
2679
|
+
* - 多个商用版本取 updateTime 最新
|
|
2680
|
+
*/
|
|
2681
|
+
async status(ctx) {
|
|
2682
|
+
const { clientId, token, headers } = await this.authHeaders(ctx);
|
|
2683
|
+
const appId = await this.resolveAppId(ctx, clientId, token);
|
|
2684
|
+
const packageName = ctx.target.bundleName;
|
|
2685
|
+
const commercial = ((await agcJson(`${BASE}/publish/v3/version/brief-info/list`, {
|
|
2686
|
+
method: "POST",
|
|
2687
|
+
headers: {
|
|
2688
|
+
...headers,
|
|
2689
|
+
appId,
|
|
2690
|
+
"Content-Type": "application/json"
|
|
2691
|
+
},
|
|
2692
|
+
body: JSON.stringify(packageName ? { packageName } : {})
|
|
2693
|
+
}, "查询版本列表")).versionList ?? []).filter((v) => v.testType === void 0 || v.testType === 1);
|
|
2694
|
+
if (commercial.length === 0) return {
|
|
2675
2695
|
state: "unknown",
|
|
2676
|
-
raw: "AGC
|
|
2696
|
+
raw: "AGC 无商用版本记录(仅测试版本或从未发布)"
|
|
2677
2697
|
};
|
|
2698
|
+
const latest = [...commercial].sort((a, b) => (b.updateTime ?? 0) - (a.updateTime ?? 0))[0];
|
|
2699
|
+
const versionName = latest.packageList?.[0]?.versionName ?? "?";
|
|
2700
|
+
const state = latest.state;
|
|
2701
|
+
if (state === void 0) return {
|
|
2702
|
+
state: "unknown",
|
|
2703
|
+
raw: `商用版本 ${versionName} 无 state 字段`
|
|
2704
|
+
};
|
|
2705
|
+
const label = `${versionName}(releaseState=${state})`;
|
|
2706
|
+
switch (state) {
|
|
2707
|
+
case 0: return {
|
|
2708
|
+
state: "approved",
|
|
2709
|
+
live: true
|
|
2710
|
+
};
|
|
2711
|
+
case 3: return {
|
|
2712
|
+
state: "approved",
|
|
2713
|
+
live: false
|
|
2714
|
+
};
|
|
2715
|
+
case 4:
|
|
2716
|
+
case 5: return { state: "reviewing" };
|
|
2717
|
+
case 1:
|
|
2718
|
+
case 8: return {
|
|
2719
|
+
state: "rejected",
|
|
2720
|
+
reason: label
|
|
2721
|
+
};
|
|
2722
|
+
case 2: return {
|
|
2723
|
+
state: "unknown",
|
|
2724
|
+
raw: `已下架(${label})`
|
|
2725
|
+
};
|
|
2726
|
+
case 7: return {
|
|
2727
|
+
state: "unknown",
|
|
2728
|
+
raw: `草稿,未提审(${label})`
|
|
2729
|
+
};
|
|
2730
|
+
case 11: return {
|
|
2731
|
+
state: "unknown",
|
|
2732
|
+
raw: `撤销上架(${label})`
|
|
2733
|
+
};
|
|
2734
|
+
default: return {
|
|
2735
|
+
state: "unknown",
|
|
2736
|
+
raw: label
|
|
2737
|
+
};
|
|
2738
|
+
}
|
|
2678
2739
|
}
|
|
2679
2740
|
/** v1 releaseHarmonyApp step 1-4:.app 校验 → 上传地址 → PUT 整包 → v3 绑定(releaseType=1)→ 文案(不阻断) */
|
|
2680
2741
|
async uploadFormal(ctx, appId, headers) {
|
|
@@ -3045,15 +3106,22 @@ async function ascRequest(jwt, apiPath, method, body) {
|
|
|
3045
3106
|
/**
|
|
3046
3107
|
* App Store Connect(iOS)渠道。
|
|
3047
3108
|
*
|
|
3048
|
-
* - stage
|
|
3049
|
-
*
|
|
3050
|
-
*
|
|
3051
|
-
*
|
|
3109
|
+
* - stage 上传双引擎(2026-10-22 重构):
|
|
3110
|
+
* 1) iTMSTransporter(首选):Xcode 26 起 iTMSTransporter 已从 Xcode 剥离,需从 Mac App Store
|
|
3111
|
+
* 安装 Transporter.app(CLI 位于 /Applications/Transporter.app/Contents/itms/bin/)。
|
|
3112
|
+
* JWT 认证与 ASC API 同源——Apple 官方建议 API 用户用同一 JWT 上传二进制
|
|
3113
|
+
* (App Store Connect Help "Upload builds")。注意两点(fastlane deliver 实证):
|
|
3114
|
+
* -assetFile 成功后 Apple 侧会删除源文件、多字节文件名上传失败,故复制到临时随机
|
|
3115
|
+
* ASCII 名再传;JWT 20 分钟有效期覆盖典型上传时长(5-15 分钟),超长上传会认证过期。
|
|
3116
|
+
* 2) xcrun altool 兜底:Xcode 自带、零额外安装;altool 的 --upload-app 并未弃用
|
|
3117
|
+
* (Apple 官方文档仍列名为四条官方上传路径之一,弃用的是其公证功能),
|
|
3118
|
+
* 但 altool 依赖 private_keys 目录查找,Transporter+JWT 是长期方向。
|
|
3119
|
+
* 凭据 ASC_TRANSPORTER_PATH 可显式指定 iTMSTransporter 路径(校验存在性);
|
|
3120
|
+
* 缺省探测默认安装路径,不存在则回退 altool。
|
|
3052
3121
|
* 约定 p8 私钥:凭据 ASC_P8_PATH(支持 ~ 前缀)或 ~/.applift/private_keys/AuthKey_<ASC_KEY_ID>.p8
|
|
3053
|
-
*
|
|
3054
|
-
* ~/.private_keys、~/.appstoreconnect/private_keys,见 xcrun altool --help)
|
|
3055
|
-
* - submit: ASC REST API 提审 —— 按 ASC 官方文档新实现,未经真实链路验证
|
|
3122
|
+
* - submit: ASC REST API 提审(含提审前预检:本地化描述完备性),按 ASC 官方文档实现,未经真实链路验证
|
|
3056
3123
|
* 官方文档: https://developer.apple.com/documentation/appstoreconnectapi
|
|
3124
|
+
* - status: 查最新 appStoreVersion 的 appVersionState(AppVersionState 15 枚举 → ReviewStatus)
|
|
3057
3125
|
*/
|
|
3058
3126
|
var AscChannel = class {
|
|
3059
3127
|
id = "asc";
|
|
@@ -3062,7 +3130,7 @@ var AscChannel = class {
|
|
|
3062
3130
|
stage: true,
|
|
3063
3131
|
submit: true,
|
|
3064
3132
|
scheduled: true,
|
|
3065
|
-
status:
|
|
3133
|
+
status: true
|
|
3066
3134
|
};
|
|
3067
3135
|
async plan(ctx) {
|
|
3068
3136
|
return {
|
|
@@ -3071,7 +3139,7 @@ var AscChannel = class {
|
|
|
3071
3139
|
artifactVersion: "",
|
|
3072
3140
|
action: "stage+submit",
|
|
3073
3141
|
afterApproval: ctx.releaseTime ? "scheduled" : "manual",
|
|
3074
|
-
notes: "stage=
|
|
3142
|
+
notes: "stage=iTMSTransporter(或 altool 兜底)上传构建(TestFlight,约 10 分钟完成处理);submit=ASC API 提审(含预检),默认手动发布"
|
|
3075
3143
|
};
|
|
3076
3144
|
}
|
|
3077
3145
|
/** 凭据三元组(Key ID / Issuer ID / p8 路径,缺任一抛错) */
|
|
@@ -3088,11 +3156,99 @@ var AscChannel = class {
|
|
|
3088
3156
|
}
|
|
3089
3157
|
/**
|
|
3090
3158
|
* 上传 ipa 到 App Store Connect(TestFlight / 正式皆走此入口)。
|
|
3159
|
+
* 双引擎:iTMSTransporter(JWT 认证,官方推荐 API 用户)优先,未装 Transporter.app 时回退 altool。
|
|
3160
|
+
*/
|
|
3161
|
+
async stage(ctx) {
|
|
3162
|
+
const auth = this.resolveAuth(ctx);
|
|
3163
|
+
const transporter = this.resolveTransporterPath(ctx);
|
|
3164
|
+
if (transporter) return this.stageWithTransporter(ctx, auth, transporter);
|
|
3165
|
+
return this.stageWithAltool(ctx, auth);
|
|
3166
|
+
}
|
|
3167
|
+
/**
|
|
3168
|
+
* iTMSTransporter 路径:凭据 ASC_TRANSPORTER_PATH 显式指定(支持 ~ 前缀,校验存在性),
|
|
3169
|
+
* 缺省探测 Transporter.app 默认安装路径;都不存在返回 undefined(回退 altool)。
|
|
3170
|
+
* public 便于测试注入(spyOn 覆写返回值)。
|
|
3171
|
+
*/
|
|
3172
|
+
resolveTransporterPath(ctx) {
|
|
3173
|
+
const explicit = ctx.credentials["ASC_TRANSPORTER_PATH"];
|
|
3174
|
+
if (explicit) {
|
|
3175
|
+
const p = explicit.startsWith("~/") ? path.join(os.homedir(), explicit.slice(2)) : path.resolve(explicit);
|
|
3176
|
+
if (!fs.existsSync(p)) throw new Error(`ASC_TRANSPORTER_PATH 指向的 iTMSTransporter 不存在: ${p}(iTMSTransporter 需从 Mac App Store 安装 Transporter.app,或清空该凭据回退 altool)`);
|
|
3177
|
+
return p;
|
|
3178
|
+
}
|
|
3179
|
+
const def = "/Applications/Transporter.app/Contents/itms/bin/iTMSTransporter";
|
|
3180
|
+
return fs.existsSync(def) ? def : void 0;
|
|
3181
|
+
}
|
|
3182
|
+
/**
|
|
3183
|
+
* 引擎一:iTMSTransporter + JWT 上传(-m upload -jwt -assetFile)。
|
|
3184
|
+
* - JWT 与 ASC REST API 同源(Apple 官方建议:Upload builds),20 分钟有效期覆盖典型上传时长;
|
|
3185
|
+
* - -assetFile 成功后 Apple 侧删除源文件、多字节文件名上传失败 → 复制到临时随机 ASCII 名
|
|
3186
|
+
* 再传(对齐 fastlane deliver 的防御做法),临时文件 finally 清理;
|
|
3187
|
+
* - 成败判定(fastlane itunes_transporter 语义):exit 0 且 DBG-X: Returning 0(或无该行)为成功;
|
|
3188
|
+
* Returning 非 0 或 exit 非 0 为失败,错误信息优先取 ERROR 行。
|
|
3189
|
+
*/
|
|
3190
|
+
async stageWithTransporter(ctx, auth, transporterPath) {
|
|
3191
|
+
const jwt = makeAscJwt(auth);
|
|
3192
|
+
ctx.emit({
|
|
3193
|
+
type: "phase",
|
|
3194
|
+
phase: "upload",
|
|
3195
|
+
message: `iTMSTransporter 上传 ${path.basename(ctx.artifactPath)}(JWT 认证,约 5-15 分钟)`
|
|
3196
|
+
});
|
|
3197
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "applift-asc-itms-"));
|
|
3198
|
+
const tmpIpa = path.join(tmpDir, `${randomUUID()}.ipa`);
|
|
3199
|
+
fs.copyFileSync(ctx.artifactPath, tmpIpa);
|
|
3200
|
+
try {
|
|
3201
|
+
const r = spawnSync(transporterPath, [
|
|
3202
|
+
"-m",
|
|
3203
|
+
"upload",
|
|
3204
|
+
"-jwt",
|
|
3205
|
+
jwt,
|
|
3206
|
+
"-assetFile",
|
|
3207
|
+
tmpIpa,
|
|
3208
|
+
"-k",
|
|
3209
|
+
"100000"
|
|
3210
|
+
], {
|
|
3211
|
+
encoding: "utf8",
|
|
3212
|
+
timeout: 18e5
|
|
3213
|
+
});
|
|
3214
|
+
if (r.error) {
|
|
3215
|
+
if ("code" in r.error && r.error.code === "ETIMEDOUT") throw new Error("iTMSTransporter 超时(30min)——检查网络/上传体积");
|
|
3216
|
+
throw new Error(`无法执行 iTMSTransporter: ${r.error.message}(需 macOS + Transporter.app)`);
|
|
3217
|
+
}
|
|
3218
|
+
const output = `${r.stdout ?? ""}${r.stderr ?? ""}`.trim();
|
|
3219
|
+
const ret = output.match(/DBG-X:\s*Returning\s+(\d+)/);
|
|
3220
|
+
if (r.status !== 0 || ret !== null && ret[1] !== "0") {
|
|
3221
|
+
const errLines = output.split(/\r?\n/).filter((l) => /ERROR:/i.test(l)).slice(-5).join("\n");
|
|
3222
|
+
throw new Error(`iTMSTransporter 上传失败(exit ${r.status ?? "?"}${ret !== null ? `,Returning ${ret[1]}` : ""}): ` + (errLines || output.slice(-2e3) || "无输出"));
|
|
3223
|
+
}
|
|
3224
|
+
if (output) ctx.emit({
|
|
3225
|
+
type: "progress",
|
|
3226
|
+
detail: output.slice(-2e3)
|
|
3227
|
+
});
|
|
3228
|
+
} finally {
|
|
3229
|
+
fs.rmSync(tmpDir, {
|
|
3230
|
+
recursive: true,
|
|
3231
|
+
force: true
|
|
3232
|
+
});
|
|
3233
|
+
}
|
|
3234
|
+
ctx.emit({
|
|
3235
|
+
type: "phase",
|
|
3236
|
+
phase: "done",
|
|
3237
|
+
message: "ipa 已上传至 App Store Connect(约 10 分钟后出现在 TestFlight)"
|
|
3238
|
+
});
|
|
3239
|
+
ctx.emit({
|
|
3240
|
+
type: "progress",
|
|
3241
|
+
detail: "内部测试员即刻可装;外部测试组首次需在 ASC 后台过 Beta 审核"
|
|
3242
|
+
});
|
|
3243
|
+
return { ok: true };
|
|
3244
|
+
}
|
|
3245
|
+
/**
|
|
3246
|
+
* 引擎二(兜底):xcrun altool 上传。
|
|
3091
3247
|
* 来源: v1 tools/publish/lib/asc.mjs 的 uploadIpaToAsc(逻辑不变;v1 经 run 继承 stdio 输出,
|
|
3092
3248
|
* v2 输出统一走 ctx.emit,故改为捕获后透传,失败语义与 v1 run 一致)
|
|
3093
3249
|
*/
|
|
3094
|
-
async
|
|
3095
|
-
const { keyId, issuerId, p8Path } =
|
|
3250
|
+
async stageWithAltool(ctx, auth) {
|
|
3251
|
+
const { keyId, issuerId, p8Path } = auth;
|
|
3096
3252
|
const ipaPath = ctx.artifactPath;
|
|
3097
3253
|
ctx.emit({
|
|
3098
3254
|
type: "phase",
|
|
@@ -3157,7 +3313,7 @@ var AscChannel = class {
|
|
|
3157
3313
|
* https://developer.apple.com/documentation/appstoreconnectapi
|
|
3158
3314
|
* 链路: 查 app(bundleId → 数字 id)→ 查最新已处理(VALID)构建 → 读取/创建 appStoreVersion
|
|
3159
3315
|
* (releaseType 默认 MANUAL,ctx.releaseTime 提供时 SCHEDULED + earliestReleaseDate)
|
|
3160
|
-
* → 绑定构建到版本 → reviewSubmissions 三步流提交进入审核
|
|
3316
|
+
* → 提审前预检(本地化描述)→ 绑定构建到版本 → reviewSubmissions 三步流提交进入审核
|
|
3161
3317
|
*/
|
|
3162
3318
|
async submit(ctx) {
|
|
3163
3319
|
const { keyId, issuerId, p8Path } = this.resolveAuth(ctx);
|
|
@@ -3227,6 +3383,7 @@ var AscChannel = class {
|
|
|
3227
3383
|
message: `已创建版本 ${versionString}(releaseType=${releaseType})`
|
|
3228
3384
|
});
|
|
3229
3385
|
}
|
|
3386
|
+
await this.preflightLocalizations(jwt, versionId, ctx);
|
|
3230
3387
|
await ascRequest(jwt, `/v1/appStoreVersions/${versionId}/relationships/builds`, "PATCH", { data: [{
|
|
3231
3388
|
type: "builds",
|
|
3232
3389
|
id: build.id
|
|
@@ -3262,16 +3419,78 @@ var AscChannel = class {
|
|
|
3262
3419
|
ref: reviewSubmissionId
|
|
3263
3420
|
};
|
|
3264
3421
|
}
|
|
3265
|
-
|
|
3266
|
-
|
|
3422
|
+
/**
|
|
3423
|
+
* 查询审核状态:最新 appStoreVersion 的 appVersionState 映射到 ReviewStatus。
|
|
3424
|
+
* 枚举见 AppVersionState(15 值):审核中/被拒/已批准未发布/已上架,未识别状态归 unknown。
|
|
3425
|
+
* 跳过 PREPARE_FOR_SUBMISSION / REPLACED_WITH_NEW_VERSION(非活跃提审记录)。
|
|
3426
|
+
*/
|
|
3427
|
+
async status(ctx) {
|
|
3428
|
+
const auth = this.resolveAuth(ctx);
|
|
3429
|
+
const bundleId = ctx.target["bundleId"] ?? ctx.target["bundleName"];
|
|
3430
|
+
if (!bundleId) throw new Error("ASC 渠道缺少 bundleId(项目配置 ios.bundleId)");
|
|
3431
|
+
const jwt = makeAscJwt(auth);
|
|
3432
|
+
const apps = await ascGet(jwt, `/v1/apps?filter[bundleId]=${encodeURIComponent(bundleId)}&limit=5`);
|
|
3433
|
+
const app = apps.data.find((a) => a.attributes?.bundleId === bundleId) ?? apps.data[0];
|
|
3434
|
+
if (!app?.id) throw new Error(`ASC 未找到 bundleId=${bundleId} 对应的 App`);
|
|
3435
|
+
const versions = await ascGet(jwt, `/v1/apps/${app.id}/appStoreVersions?filter[platform]=IOS&limit=10`);
|
|
3436
|
+
const active = versions.data.find((v) => v.attributes?.appVersionState !== "PREPARE_FOR_SUBMISSION" && v.attributes?.appVersionState !== "REPLACED_WITH_NEW_VERSION") ?? versions.data[0];
|
|
3437
|
+
if (!active?.id) return {
|
|
3267
3438
|
state: "unknown",
|
|
3268
|
-
raw: "ASC
|
|
3439
|
+
raw: "ASC 无 IOS appStoreVersion 记录"
|
|
3269
3440
|
};
|
|
3441
|
+
const state = active.attributes?.appVersionState ?? "UNKNOWN";
|
|
3442
|
+
const label = `${active.attributes?.versionString ?? "?"}(${state})`;
|
|
3443
|
+
switch (state) {
|
|
3444
|
+
case "WAITING_FOR_REVIEW":
|
|
3445
|
+
case "IN_REVIEW":
|
|
3446
|
+
case "WAITING_FOR_EXPORT_COMPLIANCE":
|
|
3447
|
+
case "READY_FOR_REVIEW": return { state: "reviewing" };
|
|
3448
|
+
case "REJECTED":
|
|
3449
|
+
case "METADATA_REJECTED":
|
|
3450
|
+
case "DEVELOPER_REJECTED":
|
|
3451
|
+
case "INVALID_BINARY": return {
|
|
3452
|
+
state: "rejected",
|
|
3453
|
+
reason: label
|
|
3454
|
+
};
|
|
3455
|
+
case "ACCEPTED":
|
|
3456
|
+
case "PENDING_APPLE_RELEASE":
|
|
3457
|
+
case "PENDING_DEVELOPER_RELEASE":
|
|
3458
|
+
case "PROCESSING_FOR_DISTRIBUTION": return {
|
|
3459
|
+
state: "approved",
|
|
3460
|
+
live: false
|
|
3461
|
+
};
|
|
3462
|
+
case "READY_FOR_DISTRIBUTION": return {
|
|
3463
|
+
state: "approved",
|
|
3464
|
+
live: true
|
|
3465
|
+
};
|
|
3466
|
+
default: return {
|
|
3467
|
+
state: "unknown",
|
|
3468
|
+
raw: label
|
|
3469
|
+
};
|
|
3470
|
+
}
|
|
3471
|
+
}
|
|
3472
|
+
/**
|
|
3473
|
+
* 提审前预检(只读):目标版本的全部本地化必须有商店描述(description)。
|
|
3474
|
+
* Apple 提审硬性要求;缺失时提前报错,避免 reviewSubmissions 最后一步被拒的返工。
|
|
3475
|
+
*/
|
|
3476
|
+
async preflightLocalizations(jwt, versionId, ctx) {
|
|
3477
|
+
const locs = await ascGet(jwt, `/v1/appStoreVersions/${versionId}/appStoreVersionLocalizations?limit=50`);
|
|
3478
|
+
if (locs.data.length === 0) throw new Error("ASC 版本无任何本地化记录,无法提审——请在 ASC 后台补全商店元数据(描述/关键词/截图)后重试");
|
|
3479
|
+
const missing = locs.data.filter((d) => !(d.attributes?.description ?? "").trim()).map((d) => d.attributes?.locale ?? d.id);
|
|
3480
|
+
if (missing.length > 0) throw new Error(`以下本地化缺少商店描述(description),无法提审: ${missing.join(", ")}——请在 ASC 后台或经 API 补全后重试`);
|
|
3481
|
+
ctx.emit({
|
|
3482
|
+
type: "progress",
|
|
3483
|
+
detail: `提审预检通过:本地化 ${locs.data.map((d) => d.attributes?.locale ?? d.id).join(", ")} 描述齐备`
|
|
3484
|
+
});
|
|
3485
|
+
ctx.emit({
|
|
3486
|
+
type: "progress",
|
|
3487
|
+
detail: "出口合规提示:Info.plist 声明 ITSAppUsesNonExemptEncryption(按实际加密用途取值)可免每次提审被问询"
|
|
3488
|
+
});
|
|
3270
3489
|
}
|
|
3271
3490
|
};
|
|
3272
|
-
function required(ctx, key
|
|
3491
|
+
function required(ctx, key) {
|
|
3273
3492
|
const v = ctx.credentials[key] ?? ctx.target[key];
|
|
3274
|
-
if (!v) throw new Error(`ASC 渠道缺少 ${key}
|
|
3493
|
+
if (!v) throw new Error(`ASC 渠道缺少 ${key}`);
|
|
3275
3494
|
return v;
|
|
3276
3495
|
}
|
|
3277
3496
|
/** p8 路径:凭据 ASC_P8_PATH(支持 ~ 前缀)优先,否则 ~/.applift/private_keys/AuthKey_<KeyID>.p8 */
|