@applift/core 0.5.0 → 0.6.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 +47 -6
- package/dist/index.mjs +243 -40
- package/dist/{plugin-CTt0LO8c.d.mts → plugin-B2GGlI_2.d.mts} +21 -31
- package/dist/plugins/index.d.mts +2 -2
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as electronAdapter, A as ReleaseConfigSchema, B as ChannelCapabilities, C as CredentialsProfile, D as ProjectConfig, E as GlobalConfigSchema, F as VersionCodeConfig, G as ReviewStatus, H as ChannelEvent, I as VersionCodeConfigSchema, J as BundleIds, K as StageResult, L as VersionConfig, M as StoreConfigSchema, N as Stores, O as ProjectConfigSchema, P as StoresSchema, Q as detectFramework, R as VersionConfigSchema, S as CredentialsFileSchema, T as GlobalConfig, U as ChannelRegistry, V as ChannelContext, W as ReleasePlanEntry, X as Platform, Y as FrameworkAdapter, Z as detectBundleIds, _ as appendEntry, a as
|
|
1
|
+
import { $ as electronAdapter, A as ReleaseConfigSchema, B as ChannelCapabilities, C as CredentialsProfile, D as ProjectConfig, E as GlobalConfigSchema, F as VersionCodeConfig, G as ReviewStatus, H as ChannelEvent, I as VersionCodeConfigSchema, J as BundleIds, K as StageResult, L as VersionConfig, M as StoreConfigSchema, N as Stores, O as ProjectConfigSchema, P as StoresSchema, Q as detectFramework, R as VersionConfigSchema, S as CredentialsFileSchema, T as GlobalConfig, U as ChannelRegistry, V as ChannelContext, W as ReleasePlanEntry, X as Platform, Y as FrameworkAdapter, Z as detectBundleIds, _ as appendEntry, a as PluginHost, at as rnAdapter, b as BetaConfigSchema, c as PipelineInput, d as runStage, et as findArtifacts, f as runSubmit, g as HistoryQuery, h as HistoryEvent, i as PluginHookName, it as nativeIosAdapter, j as StoreConfig, k as ReleaseConfig, l as PipelineSummary, m as HistoryEntry, n as AppliftUserConfig, nt as nativeAndroidAdapter, o as defineConfig, ot as tauriAdapter, p as HISTORY_PATH, q as SubmitResult, r as HookContext, rt as nativeHarmonyAdapter, s as resolvePlugins, t as AppliftPlugin, tt as flutterAdapter, u as PipelineSummaryEntry, v as readEntries, w as CredentialsProfileSchema, x as CredentialsFile, y as BetaConfig, z as Channel } from "./plugin-B2GGlI_2.mjs";
|
|
2
2
|
//#region src/registry.d.ts
|
|
3
3
|
export declare class DefaultChannelRegistry implements ChannelRegistry {
|
|
4
4
|
private readonly channels;
|
|
@@ -30,8 +30,13 @@ export declare function loadCredentials(filePath?: string): CredentialsFile;
|
|
|
30
30
|
* knownKeys 提供时其裸键名(如 PGYER_API_KEY)也认(向后兼容),未提供的裸大写键不捡(避免误吸无关变量)。
|
|
31
31
|
*/
|
|
32
32
|
export declare function resolveProfile(creds: CredentialsFile, profile: string, knownKeys?: string[]): Record<string, string>;
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
export interface GlobalConfigLoadResult {
|
|
34
|
+
config: GlobalConfig;
|
|
35
|
+
/** 配置文件损坏降级默认值时的警告(调用方决定呈现方式;core 保持 headless 不写 stderr) */
|
|
36
|
+
warning?: string;
|
|
37
|
+
}
|
|
38
|
+
/** 全局配置装载(缺失/损坏降级默认值;警告经返回值交给调用方,不阻断) */
|
|
39
|
+
export declare function loadGlobalConfig(filePath?: string): GlobalConfigLoadResult;
|
|
35
40
|
/** 写凭据(目录自动创建,文件强制 0600) */
|
|
36
41
|
export declare function saveCredentials(data: CredentialsFile, filePath?: string): void;
|
|
37
42
|
//#endregion
|
|
@@ -42,7 +47,7 @@ export interface LoadedProjectConfig {
|
|
|
42
47
|
* c12 原始配置(保留 plugins 等 schema 外键)——插件发现(resolvePlugins)用;
|
|
43
48
|
* 业务字段一律以 config(zod 解析后)为准。
|
|
44
49
|
*/
|
|
45
|
-
raw:
|
|
50
|
+
raw: AppliftUserConfig;
|
|
46
51
|
/** 配置文件所在目录(解析相对路径用),无配置文件时为 cwd */
|
|
47
52
|
cwd: string;
|
|
48
53
|
}
|
|
@@ -80,6 +85,10 @@ export declare class PgyerChannel implements Channel {
|
|
|
80
85
|
readonly id = "pgyer";
|
|
81
86
|
readonly label = "蒲公英";
|
|
82
87
|
readonly capabilities: ChannelCapabilities;
|
|
88
|
+
/** 平台错误码 → 人话(1247:源码注释已证 = buildKey 尚未就绪/COS 仍在入库) */
|
|
89
|
+
readonly errorHints: {
|
|
90
|
+
"1247": string;
|
|
91
|
+
};
|
|
83
92
|
plan(ctx: ChannelContext): Promise<ReleasePlanEntry>;
|
|
84
93
|
stage(ctx: ChannelContext): Promise<StageResult>;
|
|
85
94
|
submit(): Promise<SubmitResult>;
|
|
@@ -172,6 +181,11 @@ export declare class XiaomiChannel implements Channel {
|
|
|
172
181
|
readonly id = "xiaomi";
|
|
173
182
|
readonly label = "小米应用商店";
|
|
174
183
|
readonly capabilities: ChannelCapabilities;
|
|
184
|
+
/** 平台错误码 → 人话(result 码:依据为本仓测试已固化的真实失败路径文案) */
|
|
185
|
+
readonly errorHints: {
|
|
186
|
+
"401": string;
|
|
187
|
+
"1005": string;
|
|
188
|
+
};
|
|
175
189
|
plan(_ctx: ChannelContext): Promise<ReleasePlanEntry>;
|
|
176
190
|
/** 小米不支持只传包:push 接口上传即提审 */
|
|
177
191
|
stage(_ctx: ChannelContext): Promise<StageResult>;
|
|
@@ -286,6 +300,10 @@ export declare class YybChannel implements Channel {
|
|
|
286
300
|
readonly id = "yyb";
|
|
287
301
|
readonly label = "腾讯应用宝";
|
|
288
302
|
readonly capabilities: ChannelCapabilities;
|
|
303
|
+
/** 平台错误码 → 人话(ret 码:依据为本仓测试已固化的真实失败路径) */
|
|
304
|
+
readonly errorHints: {
|
|
305
|
+
"10010": string;
|
|
306
|
+
};
|
|
289
307
|
/** stage 产物暂存(serial_number + md5),submit 按 appId 取回 */
|
|
290
308
|
private readonly staged;
|
|
291
309
|
private required;
|
|
@@ -384,11 +402,24 @@ export declare class GooglePlayChannel implements Channel {
|
|
|
384
402
|
readonly id = "googleplay";
|
|
385
403
|
readonly label = "Google Play";
|
|
386
404
|
readonly capabilities: ChannelCapabilities;
|
|
405
|
+
/** 平台错误码 → 人话(HTTP 状态:依据为本仓测试已固化的失败路径) */
|
|
406
|
+
readonly errorHints: {
|
|
407
|
+
"403": string;
|
|
408
|
+
};
|
|
387
409
|
plan(_ctx: ChannelContext): Promise<ReleasePlanEntry>;
|
|
388
410
|
stage(ctx: ChannelContext): Promise<StageResult>;
|
|
389
|
-
submit(ctx: ChannelContext): Promise<SubmitResult>;
|
|
411
|
+
submit(ctx: ChannelContext, stagedRef?: string): Promise<SubmitResult>;
|
|
412
|
+
/** 复用 stage 已上传的 versionCode:开新 edit(仍需 auth)→ 部署 production → commit,跳过上传 */
|
|
413
|
+
private assignExisting;
|
|
390
414
|
/** 上传 + 部署到指定轨道 + commit,返回 `editId:versionCode` */
|
|
391
415
|
private uploadAndAssign;
|
|
416
|
+
/**
|
|
417
|
+
* 部署 versionCode 到指定轨道并 commit:
|
|
418
|
+
* 先 GET 轨道现有 releases 再合并 PUT——目标 release(同 status=completed 配置)追加新 versionCode,
|
|
419
|
+
* 其余 releases 原样保留(status/userFraction 不变),避免整轨覆盖把旧版本从轨道剔除;
|
|
420
|
+
* :commit 冒号语法(对拍 apkgo googleplay.go 权威;对 production 含提交审核语义,internal 即生效)
|
|
421
|
+
*/
|
|
422
|
+
private deployAndCommit;
|
|
392
423
|
status(ctx: Pick<ChannelContext, "credentials" | "target">): Promise<ReviewStatus>;
|
|
393
424
|
private requirePkgName;
|
|
394
425
|
}
|
|
@@ -400,9 +431,19 @@ export declare function sha256File(filePath: string): string;
|
|
|
400
431
|
//#region src/channels/shared/error-codes.d.ts
|
|
401
432
|
/**
|
|
402
433
|
* 错误码人话化:消费各渠道自带 errorHints(平台内聚——表与渠道实现同文件)。
|
|
434
|
+
* 网络类故障(AbortSignal 超时/断连/DNS)渠道层不转译,统一在此兜底提示。
|
|
403
435
|
*/
|
|
404
436
|
export declare function humanizeError(hints: Record<string, string> | undefined, message: string): string;
|
|
405
437
|
//#endregion
|
|
438
|
+
//#region src/channels/shared/redact.d.ts
|
|
439
|
+
/**
|
|
440
|
+
* 文本脱敏(三层层叠,逐层尝试):
|
|
441
|
+
* 1) http(s) URL 带 query/userinfo → query 按名脱敏 + 剥 userinfo
|
|
442
|
+
* 2) { / [ 开头且整串为合法 JSON → 递归按键名脱敏
|
|
443
|
+
* 3) 其余 → name=value 正则扫(错误消息等无结构文本)
|
|
444
|
+
*/
|
|
445
|
+
export declare function redactText(v: string): string;
|
|
446
|
+
//#endregion
|
|
406
447
|
//#region src/version/project.d.ts
|
|
407
448
|
export type VersionFileKind = "gradle" | "pbxproj" | "app-json5" | "pubspec" | "package-json" | "tauri" | "expo-app-json";
|
|
408
449
|
export interface PlatformVersionInfo {
|
|
@@ -589,4 +630,4 @@ export interface ArtifactVersion {
|
|
|
589
630
|
}
|
|
590
631
|
export declare function readArtifactVersion(artifactPath: string): ArtifactVersion | undefined;
|
|
591
632
|
//#endregion
|
|
592
|
-
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,
|
|
633
|
+
export { AppliftPlugin, AppliftUserConfig, 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, ReleaseConfig, ReleaseConfigSchema, ReleasePlanEntry, ReviewStatus, StageResult, StoreConfig, StoreConfigSchema, Stores, StoresSchema, SubmitResult, VersionCodeConfig, VersionCodeConfigSchema, VersionConfig, VersionConfigSchema, appendEntry, defineConfig, detectBundleIds, detectFramework, electronAdapter, findArtifacts, flutterAdapter, nativeAndroidAdapter, nativeHarmonyAdapter, nativeIosAdapter, readEntries, resolvePlugins, rnAdapter, runStage, runSubmit, tauriAdapter };
|
package/dist/index.mjs
CHANGED
|
@@ -41,15 +41,23 @@ profiles: z.record(z.string(), CredentialsProfileSchema) });
|
|
|
41
41
|
/** 单渠道配置(appId、pkgName 等渠道侧键) */
|
|
42
42
|
const StoreConfigSchema = z.record(z.string(), z.string());
|
|
43
43
|
/**
|
|
44
|
-
* stores
|
|
44
|
+
* stores 等价写法,统一规范化为 record:
|
|
45
45
|
* ① 对象:{ huawei: { appId: 'x' } }(原有写法,原样保留)
|
|
46
46
|
* ② 数组:['oppo', 'vivo'] → { oppo: {}, vivo: {} }(无配置渠道)
|
|
47
47
|
* ③ 字符串值:{ huawei: '10xxx' } → { huawei: { appId: '10xxx' } }(appId 简写)
|
|
48
|
+
* ④ 混排数组(README 首例):['huawei', { yyb: 'appId值' }] → 元素对象须单键 { 渠道: 值 },
|
|
49
|
+
* 值走 ③ 同款 appId 简写归一
|
|
48
50
|
*/
|
|
49
|
-
const
|
|
50
|
-
if (Array.isArray(stores)) return Object.fromEntries(stores.map((
|
|
51
|
+
const storesNormalize = (stores) => {
|
|
52
|
+
if (Array.isArray(stores)) return Object.fromEntries(stores.map((el) => typeof el === "string" ? [el, {}] : (() => {
|
|
53
|
+
const entries = Object.entries(el);
|
|
54
|
+
if (entries.length !== 1) throw new Error(`stores 数组内的对象元素必须形如 { 渠道id: appId简写 }(单键),收到: ${JSON.stringify(el)}`);
|
|
55
|
+
const [id, cfg] = entries[0];
|
|
56
|
+
return [id, typeof cfg === "string" ? { appId: cfg } : cfg];
|
|
57
|
+
})()));
|
|
51
58
|
return Object.fromEntries(Object.entries(stores).map(([id, cfg]) => [id, typeof cfg === "string" ? { appId: cfg } : cfg]));
|
|
52
|
-
}
|
|
59
|
+
};
|
|
60
|
+
const StoresSchema = z.union([z.record(z.string(), z.union([StoreConfigSchema, z.string()])), z.array(z.union([z.string(), StoreConfigSchema]))]).transform(storesNormalize).default({});
|
|
53
61
|
/** 项目级配置(applift.config.ts 的形状);beta 段见上方 BetaConfigSchema */
|
|
54
62
|
/**
|
|
55
63
|
* 内测分发配置(applift beta 无平台参数时并行发布的渠道开关):
|
|
@@ -94,16 +102,19 @@ const ProjectConfigSchema = z.object({
|
|
|
94
102
|
/** 团队默认凭据 profile(个人用 applift use / --profile 覆盖),缺省 default */
|
|
95
103
|
profile: z.string().default("default"),
|
|
96
104
|
android: z.object({
|
|
97
|
-
|
|
105
|
+
/** 缺省经 detectBundleIds 从工程文件自动推导(README 首例即只有 stores 的最小配置) */
|
|
106
|
+
pkgName: z.string().optional(),
|
|
98
107
|
/** 渠道 → 渠道侧配置(appId 等);stores 里列出的渠道才发 */
|
|
99
108
|
stores: StoresSchema
|
|
100
109
|
}).optional(),
|
|
101
110
|
harmony: z.object({
|
|
102
|
-
|
|
111
|
+
/** 缺省自动推导(AppScope/app.json5) */
|
|
112
|
+
bundleName: z.string().optional(),
|
|
103
113
|
appId: z.string().optional()
|
|
104
114
|
}).optional(),
|
|
105
115
|
ios: z.object({
|
|
106
|
-
scheme
|
|
116
|
+
/** 缺省自动推导(工程 scheme 探测) */
|
|
117
|
+
scheme: z.string().optional(),
|
|
107
118
|
workspace: z.string().optional(),
|
|
108
119
|
/** App Store Connect 按 bundleId 定位 App(asc 渠道 submit 用) */
|
|
109
120
|
bundleId: z.string().optional()
|
|
@@ -208,14 +219,16 @@ function resolveProfile(creds, profile, knownKeys) {
|
|
|
208
219
|
for (const [envName, value] of Object.entries(process.env)) if (envName.startsWith("APPLIFT_") && value) merged[envName.slice(8)] = value;
|
|
209
220
|
return merged;
|
|
210
221
|
}
|
|
211
|
-
/**
|
|
222
|
+
/** 全局配置装载(缺失/损坏降级默认值;警告经返回值交给调用方,不阻断) */
|
|
212
223
|
function loadGlobalConfig(filePath = GLOBAL_CONFIG_PATH) {
|
|
213
|
-
if (!fs.existsSync(filePath)) return GlobalConfigSchema.parse({});
|
|
224
|
+
if (!fs.existsSync(filePath)) return { config: GlobalConfigSchema.parse({}) };
|
|
214
225
|
try {
|
|
215
|
-
return GlobalConfigSchema.parse(parse(fs.readFileSync(filePath, "utf8")));
|
|
226
|
+
return { config: GlobalConfigSchema.parse(parse(fs.readFileSync(filePath, "utf8"))) };
|
|
216
227
|
} catch (e) {
|
|
217
|
-
|
|
218
|
-
|
|
228
|
+
return {
|
|
229
|
+
config: GlobalConfigSchema.parse({}),
|
|
230
|
+
warning: `全局配置解析失败,使用默认值: ${e.message}`
|
|
231
|
+
};
|
|
219
232
|
}
|
|
220
233
|
}
|
|
221
234
|
/** 写凭据(目录自动创建,文件强制 0600) */
|
|
@@ -266,8 +279,13 @@ async function loadProjectConfig(cwd) {
|
|
|
266
279
|
packageJson: false,
|
|
267
280
|
defaults: ProjectConfigSchema.parse({})
|
|
268
281
|
});
|
|
282
|
+
const parsed = ProjectConfigSchema.safeParse(result.config ?? {});
|
|
283
|
+
if (!parsed.success) {
|
|
284
|
+
const details = parsed.error.issues.map((i) => `${i.path.join(".") || "(根)"}: ${i.message}`).join(";");
|
|
285
|
+
throw new Error(`applift.config.ts 字段校验失败 → ${details}`);
|
|
286
|
+
}
|
|
269
287
|
return {
|
|
270
|
-
config:
|
|
288
|
+
config: parsed.data,
|
|
271
289
|
raw: result.config ?? {},
|
|
272
290
|
cwd: result.cwd ?? cwd
|
|
273
291
|
};
|
|
@@ -370,8 +388,19 @@ async function formPost(url, params, { timeout = 6e4 } = {}) {
|
|
|
370
388
|
}
|
|
371
389
|
}
|
|
372
390
|
//#endregion
|
|
391
|
+
//#region src/channels/shared/file-blob.ts
|
|
392
|
+
/**
|
|
393
|
+
* 惰性文件 Blob:FormData multipart 上传的文件部件。
|
|
394
|
+
* 优先 fs.openAsBlob(Node ≥19.8,按需分片读,零整读内存);
|
|
395
|
+
* 能力检测不存在时回退整读(旧运行时兜底)。
|
|
396
|
+
*/
|
|
397
|
+
async function fileBlob(p) {
|
|
398
|
+
if (typeof fs.openAsBlob === "function") return fs.openAsBlob(p);
|
|
399
|
+
return new Blob([await fs.promises.readFile(p)]);
|
|
400
|
+
}
|
|
401
|
+
//#endregion
|
|
373
402
|
//#region src/util.ts
|
|
374
|
-
/** core
|
|
403
|
+
/** core 内部共用小工具(sleep/formatSize 仅内部使用;sha256File 经 index 导出,CLI 历史指纹用) */
|
|
375
404
|
const sleep = (ms) => new Promise((res) => setTimeout(res, ms));
|
|
376
405
|
/** 分块计算文件 SHA256(产物可达上百 MB,8MB 分块避免整读内存,同 sign.ts md5File 先例) */
|
|
377
406
|
function sha256File(filePath) {
|
|
@@ -413,7 +442,8 @@ async function pgyerPost(host, apiPath, form) {
|
|
|
413
442
|
const res = await fetch(`https://${host}${apiPath}`, {
|
|
414
443
|
method: "POST",
|
|
415
444
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
416
|
-
body
|
|
445
|
+
body,
|
|
446
|
+
signal: AbortSignal.timeout(3e4)
|
|
417
447
|
});
|
|
418
448
|
const text = await res.text();
|
|
419
449
|
try {
|
|
@@ -431,6 +461,8 @@ var PgyerChannel = class {
|
|
|
431
461
|
scheduled: false,
|
|
432
462
|
status: false
|
|
433
463
|
};
|
|
464
|
+
/** 平台错误码 → 人话(1247:源码注释已证 = buildKey 尚未就绪/COS 仍在入库) */
|
|
465
|
+
errorHints = { "1247": "buildKey 尚未就绪(COS 上传仍在入库),属处理中状态,稍候即可" };
|
|
434
466
|
async plan(ctx) {
|
|
435
467
|
return {
|
|
436
468
|
channel: this.id,
|
|
@@ -487,7 +519,7 @@ var PgyerChannel = class {
|
|
|
487
519
|
form.append("x-cos-security-token", params["x-cos-security-token"]);
|
|
488
520
|
form.append("key", params.key);
|
|
489
521
|
form.append("x-cos-meta-file-name", path.basename(ctx.artifactPath));
|
|
490
|
-
form.append("file",
|
|
522
|
+
form.append("file", await fileBlob(ctx.artifactPath), path.basename(ctx.artifactPath));
|
|
491
523
|
const upRes = await fetch(endpoint, {
|
|
492
524
|
method: "POST",
|
|
493
525
|
body: form,
|
|
@@ -970,6 +1002,11 @@ var XiaomiChannel = class {
|
|
|
970
1002
|
scheduled: false,
|
|
971
1003
|
status: true
|
|
972
1004
|
};
|
|
1005
|
+
/** 平台错误码 → 人话(result 码:依据为本仓测试已固化的真实失败路径文案) */
|
|
1006
|
+
errorHints = {
|
|
1007
|
+
"401": "签名无效或账号无该包名权限(检查 MI_CLIENT_SECRET 与 /dev/query 权限)",
|
|
1008
|
+
"1005": "push SIG 校验失败(检查证书公钥与密码是否匹配)"
|
|
1009
|
+
};
|
|
973
1010
|
async plan(_ctx) {
|
|
974
1011
|
return {
|
|
975
1012
|
channel: this.id,
|
|
@@ -1051,8 +1088,8 @@ var XiaomiChannel = class {
|
|
|
1051
1088
|
const form = new FormData();
|
|
1052
1089
|
form.append("RequestData", requestJson);
|
|
1053
1090
|
form.append("SIG", sig);
|
|
1054
|
-
form.append("apk",
|
|
1055
|
-
form.append("icon",
|
|
1091
|
+
form.append("apk", await fileBlob(ctx.artifactPath), fileName);
|
|
1092
|
+
form.append("icon", await fileBlob(iconPath), path.basename(iconPath));
|
|
1056
1093
|
const res = await fetch(`${MI_API_BASE}/dev/push`, {
|
|
1057
1094
|
method: "POST",
|
|
1058
1095
|
body: form,
|
|
@@ -1375,7 +1412,7 @@ var OppoChannel = class {
|
|
|
1375
1412
|
const form = new FormData();
|
|
1376
1413
|
form.append("type", "apk");
|
|
1377
1414
|
form.append("sign", sign);
|
|
1378
|
-
form.append("file",
|
|
1415
|
+
form.append("file", await fileBlob(ctx.artifactPath), fileName);
|
|
1379
1416
|
const upRes = await fetch(uploadUrl, {
|
|
1380
1417
|
method: "POST",
|
|
1381
1418
|
body: form,
|
|
@@ -1670,7 +1707,7 @@ var VivoChannel = class {
|
|
|
1670
1707
|
params.sign = hmacSign(accessSecret, params);
|
|
1671
1708
|
const form = new FormData();
|
|
1672
1709
|
for (const [k, v] of Object.entries(params)) form.append(k, v);
|
|
1673
|
-
form.append("file",
|
|
1710
|
+
form.append("file", await fileBlob(ctx.artifactPath), fileName);
|
|
1674
1711
|
const upRes = await fetch(API_BASE$2, {
|
|
1675
1712
|
method: "POST",
|
|
1676
1713
|
body: form,
|
|
@@ -1925,7 +1962,7 @@ var HonorChannel = class {
|
|
|
1925
1962
|
message: `上传 ${fileName}(${formatSize(fileSize)})`
|
|
1926
1963
|
});
|
|
1927
1964
|
const form = new FormData();
|
|
1928
|
-
form.append("file",
|
|
1965
|
+
form.append("file", await fileBlob(ctx.artifactPath), fileName);
|
|
1929
1966
|
const upRes = await fetch(uploadUrl, {
|
|
1930
1967
|
method: "POST",
|
|
1931
1968
|
headers: { Authorization: `Bearer ${token}` },
|
|
@@ -2117,6 +2154,8 @@ var YybChannel = class {
|
|
|
2117
2154
|
scheduled: true,
|
|
2118
2155
|
status: true
|
|
2119
2156
|
};
|
|
2157
|
+
/** 平台错误码 → 人话(ret 码:依据为本仓测试已固化的真实失败路径) */
|
|
2158
|
+
errorHints = { "10010": "应用不存在(检查 YYB_APP_ID 与 user_id 是否匹配该应用)" };
|
|
2120
2159
|
/** stage 产物暂存(serial_number + md5),submit 按 appId 取回 */
|
|
2121
2160
|
staged = /* @__PURE__ */ new Map();
|
|
2122
2161
|
required(ctx, key, where) {
|
|
@@ -2319,7 +2358,10 @@ const COMPILE_POLL_MS = 15e3;
|
|
|
2319
2358
|
const APP_TEST_BASE = "https://connect-api.cloud.huawei.com/api/app-test/v1";
|
|
2320
2359
|
/** AGC JSON API 封装:判 ret.code !== 0 抛错 —— v1 agc.mjs agcJson 原样 */
|
|
2321
2360
|
async function agcJson(url, init, context) {
|
|
2322
|
-
const res = await fetch(url,
|
|
2361
|
+
const res = await fetch(url, {
|
|
2362
|
+
...init,
|
|
2363
|
+
signal: init.signal ?? AbortSignal.timeout(6e4)
|
|
2364
|
+
});
|
|
2323
2365
|
let json;
|
|
2324
2366
|
try {
|
|
2325
2367
|
json = await res.json();
|
|
@@ -2779,10 +2821,13 @@ var AgcHarmonyChannel = class {
|
|
|
2779
2821
|
current: String(current),
|
|
2780
2822
|
pageSize: "50"
|
|
2781
2823
|
});
|
|
2782
|
-
const res = await fetch(`${APP_TEST_BASE}/test-group/list?${qs}`, {
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2824
|
+
const res = await fetch(`${APP_TEST_BASE}/test-group/list?${qs}`, {
|
|
2825
|
+
headers: {
|
|
2826
|
+
...headers,
|
|
2827
|
+
appId
|
|
2828
|
+
},
|
|
2829
|
+
signal: AbortSignal.timeout(6e4)
|
|
2830
|
+
});
|
|
2786
2831
|
if (!res.ok) throw new Error(`AGC 查询测试群组失败 HTTP ${res.status}`);
|
|
2787
2832
|
const json = await res.json();
|
|
2788
2833
|
if (Number(json.rtnCode) !== 0) throw new Error(`AGC 查询测试群组失败: rtnCode=${json.rtnCode ?? "?"}${json.rtnDesc ? ` ${json.rtnDesc}` : ""}`);
|
|
@@ -3247,6 +3292,8 @@ var GooglePlayChannel = class {
|
|
|
3247
3292
|
scheduled: false,
|
|
3248
3293
|
status: true
|
|
3249
3294
|
};
|
|
3295
|
+
/** 平台错误码 → 人话(HTTP 状态:依据为本仓测试已固化的失败路径) */
|
|
3296
|
+
errorHints = { "403": "权限不足(检查 Service Account 是否已关联 Play Console 且有发布权限)" };
|
|
3250
3297
|
async plan(_ctx) {
|
|
3251
3298
|
return {
|
|
3252
3299
|
channel: this.id,
|
|
@@ -3263,12 +3310,40 @@ var GooglePlayChannel = class {
|
|
|
3263
3310
|
ref: await this.uploadAndAssign(ctx, "internal", void 0)
|
|
3264
3311
|
};
|
|
3265
3312
|
}
|
|
3266
|
-
async submit(ctx) {
|
|
3313
|
+
async submit(ctx, stagedRef) {
|
|
3314
|
+
const reusedVc = stagedRef?.split(":")[1];
|
|
3315
|
+
if (reusedVc !== void 0 && reusedVc !== "") return {
|
|
3316
|
+
ok: true,
|
|
3317
|
+
ref: await this.assignExisting(ctx, "production", Number(reusedVc))
|
|
3318
|
+
};
|
|
3267
3319
|
return {
|
|
3268
3320
|
ok: true,
|
|
3269
3321
|
ref: await this.uploadAndAssign(ctx, "production", ctx.updateDescription)
|
|
3270
3322
|
};
|
|
3271
3323
|
}
|
|
3324
|
+
/** 复用 stage 已上传的 versionCode:开新 edit(仍需 auth)→ 部署 production → commit,跳过上传 */
|
|
3325
|
+
async assignExisting(ctx, track, versionCode) {
|
|
3326
|
+
const pkgName = this.requirePkgName(ctx);
|
|
3327
|
+
const token = await getAccessToken(ctx.credentials);
|
|
3328
|
+
ctx.emit({
|
|
3329
|
+
type: "phase",
|
|
3330
|
+
phase: "auth",
|
|
3331
|
+
message: "OAuth2 token 获取成功"
|
|
3332
|
+
});
|
|
3333
|
+
const edit = await apiFetch(`${API_BASE}/${pkgName}/edits`, token, {
|
|
3334
|
+
method: "POST",
|
|
3335
|
+
body: "{}"
|
|
3336
|
+
}, "创建 edit");
|
|
3337
|
+
if (!edit.id) throw new Error(`Google Play 创建 edit 响应缺少 id: ${JSON.stringify(edit).slice(0, 300)}`);
|
|
3338
|
+
const editId = edit.id;
|
|
3339
|
+
ctx.emit({
|
|
3340
|
+
type: "phase",
|
|
3341
|
+
phase: "upload",
|
|
3342
|
+
message: `复用 stage 上传的 versionCode ${versionCode}(跳过重复上传)`
|
|
3343
|
+
});
|
|
3344
|
+
await this.deployAndCommit(ctx, pkgName, token, editId, track, versionCode, ctx.updateDescription);
|
|
3345
|
+
return `${editId}:${versionCode}`;
|
|
3346
|
+
}
|
|
3272
3347
|
/** 上传 + 部署到指定轨道 + commit,返回 `editId:versionCode` */
|
|
3273
3348
|
async uploadAndAssign(ctx, track, releaseNotes) {
|
|
3274
3349
|
const pkgName = this.requirePkgName(ctx);
|
|
@@ -3307,6 +3382,15 @@ var GooglePlayChannel = class {
|
|
|
3307
3382
|
type: "progress",
|
|
3308
3383
|
detail: `上传成功(versionCode: ${versionCode})`
|
|
3309
3384
|
});
|
|
3385
|
+
return this.deployAndCommit(ctx, pkgName, token, editId, track, versionCode, releaseNotes);
|
|
3386
|
+
}
|
|
3387
|
+
/**
|
|
3388
|
+
* 部署 versionCode 到指定轨道并 commit:
|
|
3389
|
+
* 先 GET 轨道现有 releases 再合并 PUT——目标 release(同 status=completed 配置)追加新 versionCode,
|
|
3390
|
+
* 其余 releases 原样保留(status/userFraction 不变),避免整轨覆盖把旧版本从轨道剔除;
|
|
3391
|
+
* :commit 冒号语法(对拍 apkgo googleplay.go 权威;对 production 含提交审核语义,internal 即生效)
|
|
3392
|
+
*/
|
|
3393
|
+
async deployAndCommit(ctx, pkgName, token, editId, track, versionCode, releaseNotes) {
|
|
3310
3394
|
const releases = [...(await apiFetch(`${API_BASE}/${pkgName}/edits/${editId}/tracks/${track}`, token, { method: "GET" }, `读取 ${track} 轨道现有 releases`)).releases ?? []];
|
|
3311
3395
|
const release = {
|
|
3312
3396
|
versionCodes: [versionCode],
|
|
@@ -3374,6 +3458,11 @@ var GooglePlayChannel = class {
|
|
|
3374
3458
|
};
|
|
3375
3459
|
//#endregion
|
|
3376
3460
|
//#region src/pipeline.ts
|
|
3461
|
+
/**
|
|
3462
|
+
* 发布编排引擎:构建计划 → 确认协议 → 并发执行 → 汇总。
|
|
3463
|
+
* headless:确认通过注入的 confirm 回调完成(CLI 用 clack/弹窗),core 不碰终端。
|
|
3464
|
+
* 插件:可选 PluginHost,在 plan/stage/submit 生命周期触发钩子(可拦截可修改)。
|
|
3465
|
+
*/
|
|
3377
3466
|
async function runLimited(items, limit, fn) {
|
|
3378
3467
|
const queue = [...items];
|
|
3379
3468
|
const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
@@ -3497,12 +3586,13 @@ async function runSubmit(input, options) {
|
|
|
3497
3586
|
label: channel.label,
|
|
3498
3587
|
ok: false,
|
|
3499
3588
|
submitError: "被插件跳过",
|
|
3500
|
-
plan: planFor(plans, basePlans, channel.id)
|
|
3589
|
+
plan: planFor(plans, basePlans, channel.id, ctx.artifactPath)
|
|
3501
3590
|
});
|
|
3502
3591
|
return;
|
|
3503
3592
|
}
|
|
3504
3593
|
effectiveCtx = r.value;
|
|
3505
3594
|
}
|
|
3595
|
+
let stagedRef;
|
|
3506
3596
|
if (!options.skipStage && channel.capabilities.stage) {
|
|
3507
3597
|
const stageResult = await channel.stage(effectiveCtx);
|
|
3508
3598
|
if (!stageResult.ok) {
|
|
@@ -3511,7 +3601,7 @@ async function runSubmit(input, options) {
|
|
|
3511
3601
|
label: channel.label,
|
|
3512
3602
|
ok: false,
|
|
3513
3603
|
stageError: stageResult.error,
|
|
3514
|
-
plan: planFor(plans, basePlans, channel.id)
|
|
3604
|
+
plan: planFor(plans, basePlans, channel.id, ctx.artifactPath)
|
|
3515
3605
|
});
|
|
3516
3606
|
input.historySink?.({
|
|
3517
3607
|
channel: channel.id,
|
|
@@ -3521,6 +3611,7 @@ async function runSubmit(input, options) {
|
|
|
3521
3611
|
});
|
|
3522
3612
|
return;
|
|
3523
3613
|
}
|
|
3614
|
+
stagedRef = stageResult.ref;
|
|
3524
3615
|
input.historySink?.({
|
|
3525
3616
|
channel: channel.id,
|
|
3526
3617
|
action: "stage",
|
|
@@ -3529,13 +3620,13 @@ async function runSubmit(input, options) {
|
|
|
3529
3620
|
artifactVersion: plans.find((p) => p.channel === channel.id)?.artifactVersion || void 0
|
|
3530
3621
|
});
|
|
3531
3622
|
}
|
|
3532
|
-
const r = await channel.submit(effectiveCtx);
|
|
3623
|
+
const r = await channel.submit(effectiveCtx, stagedRef);
|
|
3533
3624
|
summary.push({
|
|
3534
3625
|
channel: channel.id,
|
|
3535
3626
|
label: channel.label,
|
|
3536
3627
|
ok: r.ok,
|
|
3537
3628
|
submitError: r.error,
|
|
3538
|
-
plan: planFor(plans, basePlans, channel.id)
|
|
3629
|
+
plan: planFor(plans, basePlans, channel.id, ctx.artifactPath)
|
|
3539
3630
|
});
|
|
3540
3631
|
input.historySink?.({
|
|
3541
3632
|
channel: channel.id,
|
|
@@ -3550,7 +3641,7 @@ async function runSubmit(input, options) {
|
|
|
3550
3641
|
label: channel.label,
|
|
3551
3642
|
ok: false,
|
|
3552
3643
|
submitError: e.message,
|
|
3553
|
-
plan: planFor(plans, basePlans, channel.id)
|
|
3644
|
+
plan: planFor(plans, basePlans, channel.id, ctx.artifactPath)
|
|
3554
3645
|
});
|
|
3555
3646
|
input.historySink?.({
|
|
3556
3647
|
channel: channel.id,
|
|
@@ -3573,22 +3664,29 @@ async function safePlan(channel, ctx) {
|
|
|
3573
3664
|
try {
|
|
3574
3665
|
return await channel.plan(ctx);
|
|
3575
3666
|
} catch {
|
|
3576
|
-
return fallbackPlan(channel.id);
|
|
3667
|
+
return fallbackPlan(channel.id, ctx.artifactPath);
|
|
3577
3668
|
}
|
|
3578
3669
|
}
|
|
3579
|
-
/**
|
|
3580
|
-
function
|
|
3670
|
+
/** 按产物扩展名推断平台(兜底条目用):.ipa→ios,.hap→harmony,.apk/.aab 及其余→android */
|
|
3671
|
+
function platformOfArtifact(artifactPath) {
|
|
3672
|
+
const ext = path.extname(artifactPath).toLowerCase();
|
|
3673
|
+
if (ext === ".ipa") return "ios";
|
|
3674
|
+
if (ext === ".hap") return "harmony";
|
|
3675
|
+
return "android";
|
|
3676
|
+
}
|
|
3677
|
+
/** plan 失败/被插件删除时的兜底条目(保 summary.plan 非可选契约;平台按产物推断不再硬编码 android) */
|
|
3678
|
+
function fallbackPlan(channelId, artifactPath) {
|
|
3581
3679
|
return {
|
|
3582
3680
|
channel: channelId,
|
|
3583
|
-
platform:
|
|
3681
|
+
platform: platformOfArtifact(artifactPath),
|
|
3584
3682
|
artifactVersion: "",
|
|
3585
3683
|
action: "stage+submit",
|
|
3586
3684
|
afterApproval: "auto-publish"
|
|
3587
3685
|
};
|
|
3588
3686
|
}
|
|
3589
3687
|
/** 汇总条目取计划:plan:before 改写后的 plans 优先,被插件删除时回退改写前快照,永不返回 undefined */
|
|
3590
|
-
function planFor(plans, base, id) {
|
|
3591
|
-
return plans.find((p) => p.channel === id) ?? base.get(id) ?? fallbackPlan(id);
|
|
3688
|
+
function planFor(plans, base, id, artifactPath) {
|
|
3689
|
+
return plans.find((p) => p.channel === id) ?? base.get(id) ?? fallbackPlan(id, artifactPath);
|
|
3592
3690
|
}
|
|
3593
3691
|
//#endregion
|
|
3594
3692
|
//#region src/plugin.ts
|
|
@@ -3599,6 +3697,8 @@ var PluginHost = class {
|
|
|
3599
3697
|
}
|
|
3600
3698
|
/**
|
|
3601
3699
|
* 顺序执行某钩子;支持「链式替换」(返回对象则替换后继续传给下一插件)与「拦截」(返回 false 立即中止)。
|
|
3700
|
+
* 信任边界:返回值按 T 强转、不做形状校验(vite 同款取舍)——插件作者自负其责,
|
|
3701
|
+
* 坏形状会在后续消费点显式抛错,宿主不代为静默兜底。
|
|
3602
3702
|
* @returns {[被拦截?, 最终值]} false 表示被某插件拦截
|
|
3603
3703
|
*/
|
|
3604
3704
|
async apply(hook, value, ctx) {
|
|
@@ -4073,13 +4173,116 @@ function readEntries(query = {}, filePath = HISTORY_PATH) {
|
|
|
4073
4173
|
//#region src/channels/shared/error-codes.ts
|
|
4074
4174
|
/**
|
|
4075
4175
|
* 错误码人话化:消费各渠道自带 errorHints(平台内聚——表与渠道实现同文件)。
|
|
4176
|
+
* 网络类故障(AbortSignal 超时/断连/DNS)渠道层不转译,统一在此兜底提示。
|
|
4076
4177
|
*/
|
|
4178
|
+
/** 网络故障特征 → 人话提示(命中即返回,优先于渠道码表) */
|
|
4179
|
+
const NETWORK_HINTS = [[/aborted due to timeout|TimeoutError/i, "请求超时(服务端无响应或网络不稳):可重跑;大包上传建议在稳定网络环境执行"], [/\b(?:ECONNRESET|ECONNREFUSED|ENOTFOUND|EAI_AGAIN|EPIPE)\b|socket hang up|fetch failed/i, "网络故障(连接被重置/DNS 解析失败等):检查网络或代理后重跑"]];
|
|
4180
|
+
/** 码匹配:纯数字码加数字边界(防 1003 误中 11003/10031);其余按包含匹配 */
|
|
4181
|
+
function matchesCode(message, code) {
|
|
4182
|
+
if (/^\d+$/.test(code)) return new RegExp(`(?<!\\d)${code}(?!\\d)`).test(message);
|
|
4183
|
+
return message.includes(code);
|
|
4184
|
+
}
|
|
4077
4185
|
function humanizeError(hints, message) {
|
|
4186
|
+
for (const [re, hint] of NETWORK_HINTS) if (re.test(message)) return `${message}\n 提示: ${hint}`;
|
|
4078
4187
|
if (!hints) return message;
|
|
4079
|
-
for (const [code, hint] of Object.entries(hints)) if (message
|
|
4188
|
+
for (const [code, hint] of Object.entries(hints)) if (matchesCode(message, code)) return `${message}\n 提示: ${hint}`;
|
|
4080
4189
|
return message;
|
|
4081
4190
|
}
|
|
4082
4191
|
//#endregion
|
|
4192
|
+
//#region src/channels/shared/redact.ts
|
|
4193
|
+
/**
|
|
4194
|
+
* 凭据脱敏(对拍 apkgo pkg/httptrace/redact.go 权威)。
|
|
4195
|
+
* 判定依据是“名字”:JSON 键 / 表单字段 / query 参数 / header 名携带凭据语义即整值掩码为
|
|
4196
|
+
* [REDACTED <len>](保留长度线索),空串保持空串——“签名为空”本身就是排查线索。
|
|
4197
|
+
* 非敏感名下的值仍递归扫内容(signed URL / 内嵌 JSON / name=value 自由文本)。
|
|
4198
|
+
* 故意分歧:掩码不做 percent-encode(日志可读性优先);JSON 再序列化保持原键序(Go map 序随机)。
|
|
4199
|
+
* 挂接点:CLI / 错误消息层接入(core 先提供能力)。
|
|
4200
|
+
*/
|
|
4201
|
+
/** 归一化后「包含即敏感」的词(对拍 redact.go sensitiveWords) */
|
|
4202
|
+
const SENSITIVE_WORDS = [
|
|
4203
|
+
"secret",
|
|
4204
|
+
"token",
|
|
4205
|
+
"password",
|
|
4206
|
+
"passwd",
|
|
4207
|
+
"authorization",
|
|
4208
|
+
"credential",
|
|
4209
|
+
"cookie",
|
|
4210
|
+
"privatekey",
|
|
4211
|
+
"apikey",
|
|
4212
|
+
"assertion",
|
|
4213
|
+
"jwt",
|
|
4214
|
+
"signature"
|
|
4215
|
+
];
|
|
4216
|
+
/** 归一化后全等即敏感的名字(对拍 redact.go sensitiveNames) */
|
|
4217
|
+
const SENSITIVE_NAMES = {
|
|
4218
|
+
sig: true,
|
|
4219
|
+
sign: true,
|
|
4220
|
+
pwd: true,
|
|
4221
|
+
auth: true
|
|
4222
|
+
};
|
|
4223
|
+
/** 名字是否携带凭据语义(JSON 键 / 表单字段 / query 参数 / header 名通用;归一化 = 小写去非字母数字) */
|
|
4224
|
+
function isSensitiveName(name) {
|
|
4225
|
+
const n = name.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
4226
|
+
if (n in SENSITIVE_NAMES) return true;
|
|
4227
|
+
if (n === "tokentype" || n.startsWith("accesscontrol")) return false;
|
|
4228
|
+
if (n.endsWith("sign") && n !== "design") return true;
|
|
4229
|
+
return SENSITIVE_WORDS.some((w) => n.includes(w));
|
|
4230
|
+
}
|
|
4231
|
+
/** 整值掩码:保留长度;空串保持(“签名为空”是有效诊断信息)。3 处调用点需锁步一致 */
|
|
4232
|
+
function mask(v) {
|
|
4233
|
+
return v === "" ? "" : `[REDACTED ${v.length}]`;
|
|
4234
|
+
}
|
|
4235
|
+
/**
|
|
4236
|
+
* 按名字脱敏单个值:敏感名 → 整值掩码;Authorization 保留 scheme("Bearer [REDACTED 812]",
|
|
4237
|
+
* 对拍 redact.go redactHeaders);非敏感名 → 递归 redactText 扫值内凭据。
|
|
4238
|
+
*/
|
|
4239
|
+
function redactValue(name, v) {
|
|
4240
|
+
if (/^authorization$/i.test(name) && v.includes(" ")) {
|
|
4241
|
+
const schemeEnd = v.indexOf(" ");
|
|
4242
|
+
return `${v.slice(0, schemeEnd)} ${mask(v.slice(schemeEnd + 1))}`;
|
|
4243
|
+
}
|
|
4244
|
+
return isSensitiveName(name) ? mask(v) : redactText(v);
|
|
4245
|
+
}
|
|
4246
|
+
/** redactURL:剥离 userinfo + query 参数按名脱敏,路径/fragment 原样保留 */
|
|
4247
|
+
function redactUrlString(v) {
|
|
4248
|
+
let out = v.replace(/^(https?:\/\/)[^/?#]*@/, "$1");
|
|
4249
|
+
const hashIdx = out.indexOf("#");
|
|
4250
|
+
const hash = hashIdx >= 0 ? out.slice(hashIdx) : "";
|
|
4251
|
+
if (hashIdx >= 0) out = out.slice(0, hashIdx);
|
|
4252
|
+
const qIdx = out.indexOf("?");
|
|
4253
|
+
if (qIdx < 0) return out + hash;
|
|
4254
|
+
const query = new URLSearchParams(out.slice(qIdx + 1));
|
|
4255
|
+
const pairs = [];
|
|
4256
|
+
for (const [k, val] of query) pairs.push(`${k}=${isSensitiveName(k) ? mask(val) : val}`);
|
|
4257
|
+
return `${out.slice(0, qIdx)}?${pairs.join("&")}${hash}`;
|
|
4258
|
+
}
|
|
4259
|
+
/** free 文本中的 name=value / "name":"value" 凭据对(对拍 redact.go scrubRe;掩码无长度) */
|
|
4260
|
+
const SCRUB_RE = /([\w.-]*(?:secret|token|password|passwd|signature|credential|authorization|api_?key|assertion)[\w.-]*"?\s*[=:]\s*"?)([^"&\s,;<}]+)/gi;
|
|
4261
|
+
/** JSON 树递归:敏感名下字符串掩码、数字/布尔置 [REDACTED],数组元素继承外层键名 */
|
|
4262
|
+
function redactTree(name, v) {
|
|
4263
|
+
if (v === null || typeof v !== "object") {
|
|
4264
|
+
if (typeof v === "string") return redactValue(name, v);
|
|
4265
|
+
return isSensitiveName(name) ? "[REDACTED]" : v;
|
|
4266
|
+
}
|
|
4267
|
+
if (Array.isArray(v)) return v.map((x) => redactTree(name, x));
|
|
4268
|
+
const out = {};
|
|
4269
|
+
for (const [k, val] of Object.entries(v)) out[k] = redactTree(k, val);
|
|
4270
|
+
return out;
|
|
4271
|
+
}
|
|
4272
|
+
/**
|
|
4273
|
+
* 文本脱敏(三层层叠,逐层尝试):
|
|
4274
|
+
* 1) http(s) URL 带 query/userinfo → query 按名脱敏 + 剥 userinfo
|
|
4275
|
+
* 2) { / [ 开头且整串为合法 JSON → 递归按键名脱敏
|
|
4276
|
+
* 3) 其余 → name=value 正则扫(错误消息等无结构文本)
|
|
4277
|
+
*/
|
|
4278
|
+
function redactText(v) {
|
|
4279
|
+
if (/^https?:\/\//i.test(v) && (v.includes("?") || /^https?:\/\/[^/?#]*@/i.test(v))) return redactUrlString(v);
|
|
4280
|
+
if (v.length > 1 && (v.startsWith("{") || v.startsWith("["))) try {
|
|
4281
|
+
return JSON.stringify(redactTree("", JSON.parse(v)));
|
|
4282
|
+
} catch {}
|
|
4283
|
+
return v.replace(SCRUB_RE, "$1[REDACTED]");
|
|
4284
|
+
}
|
|
4285
|
+
//#endregion
|
|
4083
4286
|
//#region src/version/project.ts
|
|
4084
4287
|
/**
|
|
4085
4288
|
* 工程文件版本读写(可接管模式:版本事实源永远是工程文件,applift 只代读代写)。
|
|
@@ -5039,4 +5242,4 @@ function fromAapt(artifactPath) {
|
|
|
5039
5242
|
}
|
|
5040
5243
|
}
|
|
5041
5244
|
//#endregion
|
|
5042
|
-
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, ReleaseConfigSchema, StoreConfigSchema, StoresSchema, VersionCodeConfigSchema, VersionConfigSchema, VivoChannel, WAIT_BEFORE_SUBMIT_MS, XiaomiChannel, YybChannel, appendEntry, applyBump, assertListingFiles, commitsSince, decideNextVersion, defineConfig, detectBundleIds, detectFramework, electronAdapter, evalVersionCodeFormula, findArtifacts, findUp, flutterAdapter, formPost, getAgcToken, getHarmonyAppId, git, hmacSign, humanizeError, inferBumpLevel, isGitRepo, latestSemverTag, loadCredentials, loadCredentialsLayered, loadGlobalConfig, loadListingFile, loadProjectConfig, loadProjectProfileMap, looseSemver, md5File, md5String, nativeAndroidAdapter, nativeHarmonyAdapter, nativeIosAdapter, nextVersionCode, planBump, projectCredentialsPath, readArtifactVersion, readEntries, readProjectVersions, registry, renderChangelogSection, resolveListing, resolvePlugins, resolveProfile, resolveProfileFor, rnAdapter, runStage, runSubmit, saveCredentials, saveProjectProfileMap, setProjectProfile, sha256File, sortedQueryString, statusShort, storeVersionName, tauriAdapter, toChangelogEntries, tsMillis, tsSeconds, upsertChangelog, writeProjectVersions };
|
|
5245
|
+
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, ReleaseConfigSchema, StoreConfigSchema, StoresSchema, VersionCodeConfigSchema, VersionConfigSchema, VivoChannel, WAIT_BEFORE_SUBMIT_MS, XiaomiChannel, YybChannel, appendEntry, applyBump, assertListingFiles, commitsSince, decideNextVersion, defineConfig, detectBundleIds, detectFramework, electronAdapter, evalVersionCodeFormula, findArtifacts, findUp, flutterAdapter, formPost, getAgcToken, getHarmonyAppId, git, hmacSign, humanizeError, inferBumpLevel, isGitRepo, latestSemverTag, loadCredentials, loadCredentialsLayered, loadGlobalConfig, loadListingFile, loadProjectConfig, loadProjectProfileMap, looseSemver, md5File, md5String, nativeAndroidAdapter, nativeHarmonyAdapter, nativeIosAdapter, nextVersionCode, planBump, projectCredentialsPath, readArtifactVersion, readEntries, readProjectVersions, redactText, registry, renderChangelogSection, resolveListing, resolvePlugins, resolveProfile, resolveProfileFor, rnAdapter, runStage, runSubmit, saveCredentials, saveProjectProfileMap, setProjectProfile, sha256File, sortedQueryString, statusShort, storeVersionName, tauriAdapter, toChangelogEntries, tsMillis, tsSeconds, upsertChangelog, writeProjectVersions };
|
|
@@ -46,16 +46,6 @@ interface BundleIds {
|
|
|
46
46
|
declare function detectBundleIds(cwd: string): BundleIds;
|
|
47
47
|
//#endregion
|
|
48
48
|
//#region src/channel.d.ts
|
|
49
|
-
/**
|
|
50
|
-
* 渠道适配器统一接口 —— applift 可扩展性的核心。
|
|
51
|
-
*
|
|
52
|
-
* 每个渠道(六商店/AGC/ASC/蒲公英…)实现本接口并注册到 registry;
|
|
53
|
-
* 编排引擎(pipeline)只面对本接口,不感知任何渠道细节。
|
|
54
|
-
*
|
|
55
|
-
* 两段式安全模型:
|
|
56
|
-
* stage = 只把包送到渠道草稿区,不动线上(iOS 上传构建≈只进 TestFlight)
|
|
57
|
-
* submit = 提审;除 iOS(默认 manual 发布)外,审核通过即自动上架 —— 不可逆,需计划确认
|
|
58
|
-
*/
|
|
59
49
|
/** 渠道能力声明(编排与确认界面据此渲染/拦截) */
|
|
60
50
|
interface ChannelCapabilities {
|
|
61
51
|
/** 支持只传包不提审(小米 push 一体 → false,submit 即上传) */
|
|
@@ -145,8 +135,12 @@ interface Channel {
|
|
|
145
135
|
plan(ctx: ChannelContext): Promise<ReleasePlanEntry>;
|
|
146
136
|
/** 传包到草稿(capabilities.stage=false 的渠道返回明确错误) */
|
|
147
137
|
stage(ctx: ChannelContext): Promise<StageResult>;
|
|
148
|
-
/**
|
|
149
|
-
|
|
138
|
+
/**
|
|
139
|
+
* 提审(=审核通过自动上架,L3 不可逆操作)。
|
|
140
|
+
* stagedRef = 同渠道 stage 成功返回的 ref(如 googleplay 的 `editId:versionCode`),
|
|
141
|
+
* 供实现复用 stage 已上传的草稿、避免重复上传;实现方可忽略。
|
|
142
|
+
*/
|
|
143
|
+
submit(ctx: ChannelContext, stagedRef?: string): Promise<SubmitResult>;
|
|
150
144
|
/** 查询审核状态(capabilities.status=false 时抛 NotSupported) */
|
|
151
145
|
status(ctx: Pick<ChannelContext, "credentials" | "target">): Promise<ReviewStatus>;
|
|
152
146
|
}
|
|
@@ -167,13 +161,7 @@ type CredentialsFile = z.infer<typeof CredentialsFileSchema>;
|
|
|
167
161
|
/** 单渠道配置(appId、pkgName 等渠道侧键) */
|
|
168
162
|
declare const StoreConfigSchema: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
169
163
|
type StoreConfig = z.infer<typeof StoreConfigSchema>;
|
|
170
|
-
|
|
171
|
-
* stores 三种等价写法,统一规范化为 record:
|
|
172
|
-
* ① 对象:{ huawei: { appId: 'x' } }(原有写法,原样保留)
|
|
173
|
-
* ② 数组:['oppo', 'vivo'] → { oppo: {}, vivo: {} }(无配置渠道)
|
|
174
|
-
* ③ 字符串值:{ huawei: '10xxx' } → { huawei: { appId: '10xxx' } }(appId 简写)
|
|
175
|
-
*/
|
|
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>>>>>;
|
|
164
|
+
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.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>]>, z.ZodTransform<Record<string, Record<string, string>>, (string | Record<string, string>)[] | Record<string, string | Record<string, string>>>>>;
|
|
177
165
|
type Stores = z.infer<typeof StoresSchema>;
|
|
178
166
|
/** 项目级配置(applift.config.ts 的形状);beta 段见上方 BetaConfigSchema */
|
|
179
167
|
/**
|
|
@@ -223,15 +211,15 @@ declare const ProjectConfigSchema: z.ZodObject<{
|
|
|
223
211
|
name: z.ZodOptional<z.ZodString>;
|
|
224
212
|
profile: z.ZodDefault<z.ZodString>;
|
|
225
213
|
android: z.ZodOptional<z.ZodObject<{
|
|
226
|
-
pkgName: z.ZodString
|
|
227
|
-
stores: 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>>>>>;
|
|
214
|
+
pkgName: z.ZodOptional<z.ZodString>;
|
|
215
|
+
stores: 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.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>]>, z.ZodTransform<Record<string, Record<string, string>>, (string | Record<string, string>)[] | Record<string, string | Record<string, string>>>>>;
|
|
228
216
|
}, z.core.$strip>>;
|
|
229
217
|
harmony: z.ZodOptional<z.ZodObject<{
|
|
230
|
-
bundleName: z.ZodString
|
|
218
|
+
bundleName: z.ZodOptional<z.ZodString>;
|
|
231
219
|
appId: z.ZodOptional<z.ZodString>;
|
|
232
220
|
}, z.core.$strip>>;
|
|
233
221
|
ios: z.ZodOptional<z.ZodObject<{
|
|
234
|
-
scheme: z.ZodString
|
|
222
|
+
scheme: z.ZodOptional<z.ZodString>;
|
|
235
223
|
workspace: z.ZodOptional<z.ZodString>;
|
|
236
224
|
bundleId: z.ZodOptional<z.ZodString>;
|
|
237
225
|
}, z.core.$strip>>;
|
|
@@ -359,7 +347,7 @@ interface HookContext {
|
|
|
359
347
|
/** 项目根目录 */
|
|
360
348
|
cwd: string;
|
|
361
349
|
}
|
|
362
|
-
interface
|
|
350
|
+
interface AppliftPlugin {
|
|
363
351
|
name: string;
|
|
364
352
|
"config:resolved"?(config: ProjectConfig, ctx: HookContext): Promise<ProjectConfig | void> | ProjectConfig | void;
|
|
365
353
|
"channel:register"?(registry: ChannelRegistry, ctx: HookContext): Promise<void> | void;
|
|
@@ -377,12 +365,14 @@ interface QypubPlugin {
|
|
|
377
365
|
"submit:after"?(summary: PipelineSummary, ctx: HookContext): Promise<void> | void;
|
|
378
366
|
notify?(summary: PipelineSummary, ctx: HookContext): Promise<void> | void;
|
|
379
367
|
}
|
|
380
|
-
type PluginHookName = Exclude<keyof
|
|
368
|
+
type PluginHookName = Exclude<keyof AppliftPlugin, "name">;
|
|
381
369
|
declare class PluginHost {
|
|
382
370
|
private readonly plugins;
|
|
383
|
-
constructor(plugins:
|
|
371
|
+
constructor(plugins: AppliftPlugin[]);
|
|
384
372
|
/**
|
|
385
373
|
* 顺序执行某钩子;支持「链式替换」(返回对象则替换后继续传给下一插件)与「拦截」(返回 false 立即中止)。
|
|
374
|
+
* 信任边界:返回值按 T 强转、不做形状校验(vite 同款取舍)——插件作者自负其责,
|
|
375
|
+
* 坏形状会在后续消费点显式抛错,宿主不代为静默兜底。
|
|
386
376
|
* @returns {[被拦截?, 最终值]} false 表示被某插件拦截
|
|
387
377
|
*/
|
|
388
378
|
apply<T>(hook: PluginHookName, value: T, ctx: HookContext): Promise<{
|
|
@@ -396,11 +386,11 @@ declare class PluginHost {
|
|
|
396
386
|
* defineConfig —— vite 同款体验:applift.config.ts 导出时提供类型提示。
|
|
397
387
|
* plugins 中允许嵌套工厂函数(如 dingtalkNotify({...}) 直接返回插件对象)。
|
|
398
388
|
*/
|
|
399
|
-
interface
|
|
400
|
-
plugins?: Array<
|
|
389
|
+
interface AppliftUserConfig extends Partial<ProjectConfig> {
|
|
390
|
+
plugins?: Array<AppliftPlugin | (() => AppliftPlugin)>;
|
|
401
391
|
}
|
|
402
|
-
declare function defineConfig(config:
|
|
392
|
+
declare function defineConfig(config: AppliftUserConfig): AppliftUserConfig;
|
|
403
393
|
/** 解析用户配置中的插件(工厂惰性求值),供 PluginHost 构造 */
|
|
404
|
-
declare function resolvePlugins(userConfig:
|
|
394
|
+
declare function resolvePlugins(userConfig: AppliftUserConfig): AppliftPlugin[];
|
|
405
395
|
//#endregion
|
|
406
|
-
export { electronAdapter as $, ReleaseConfigSchema as A, ChannelCapabilities as B, CredentialsProfile as C, ProjectConfig as D, GlobalConfigSchema as E, VersionCodeConfig as F, ReviewStatus as G, ChannelEvent as H, VersionCodeConfigSchema as I, BundleIds as J, StageResult as K, VersionConfig as L, StoreConfigSchema as M, Stores as N, ProjectConfigSchema as O, StoresSchema as P, detectFramework as Q, VersionConfigSchema as R, CredentialsFileSchema as S, GlobalConfig as T, ChannelRegistry as U, ChannelContext as V, ReleasePlanEntry as W, Platform as X, FrameworkAdapter as Y, detectBundleIds as Z, appendEntry as _,
|
|
396
|
+
export { electronAdapter as $, ReleaseConfigSchema as A, ChannelCapabilities as B, CredentialsProfile as C, ProjectConfig as D, GlobalConfigSchema as E, VersionCodeConfig as F, ReviewStatus as G, ChannelEvent as H, VersionCodeConfigSchema as I, BundleIds as J, StageResult as K, VersionConfig as L, StoreConfigSchema as M, Stores as N, ProjectConfigSchema as O, StoresSchema as P, detectFramework as Q, VersionConfigSchema as R, CredentialsFileSchema as S, GlobalConfig as T, ChannelRegistry as U, ChannelContext as V, ReleasePlanEntry as W, Platform as X, FrameworkAdapter as Y, detectBundleIds as Z, appendEntry as _, PluginHost as a, rnAdapter as at, BetaConfigSchema as b, PipelineInput as c, runStage as d, findArtifacts as et, runSubmit as f, HistoryQuery as g, HistoryEvent as h, PluginHookName as i, nativeIosAdapter as it, StoreConfig as j, ReleaseConfig as k, PipelineSummary as l, HistoryEntry as m, AppliftUserConfig as n, nativeAndroidAdapter as nt, defineConfig as o, tauriAdapter as ot, HISTORY_PATH as p, SubmitResult as q, HookContext as r, nativeHarmonyAdapter as rt, resolvePlugins as s, AppliftPlugin as t, flutterAdapter as tt, PipelineSummaryEntry as u, readEntries as v, CredentialsProfileSchema as w, CredentialsFile as x, BetaConfig as y, Channel as z };
|
package/dist/plugins/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { t as AppliftPlugin } from "../plugin-B2GGlI_2.mjs";
|
|
2
2
|
//#region src/plugins/dingtalk-notify.d.ts
|
|
3
3
|
interface DingtalkNotifyOptions {
|
|
4
4
|
webhook: string;
|
|
@@ -6,6 +6,6 @@ interface DingtalkNotifyOptions {
|
|
|
6
6
|
secret?: string;
|
|
7
7
|
atMobiles?: string[];
|
|
8
8
|
}
|
|
9
|
-
export declare function dingtalkNotify(options: DingtalkNotifyOptions):
|
|
9
|
+
export declare function dingtalkNotify(options: DingtalkNotifyOptions): AppliftPlugin;
|
|
10
10
|
//#endregion
|
|
11
11
|
export type { DingtalkNotifyOptions };
|