@huoye/app-ship 0.1.1 → 0.1.2

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.js CHANGED
@@ -5,23 +5,517 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
5
5
  throw Error('Dynamic require of "' + x + '" is not supported');
6
6
  });
7
7
 
8
+ // node_modules/chalk/source/vendor/ansi-styles/index.js
9
+ var ANSI_BACKGROUND_OFFSET = 10;
10
+ var wrapAnsi16 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
11
+ var wrapAnsi256 = (offset = 0) => (code) => `\x1B[${38 + offset};5;${code}m`;
12
+ var wrapAnsi16m = (offset = 0) => (red, green, blue) => `\x1B[${38 + offset};2;${red};${green};${blue}m`;
13
+ var styles = {
14
+ modifier: {
15
+ reset: [0, 0],
16
+ // 21 isn't widely supported and 22 does the same thing
17
+ bold: [1, 22],
18
+ dim: [2, 22],
19
+ italic: [3, 23],
20
+ underline: [4, 24],
21
+ overline: [53, 55],
22
+ inverse: [7, 27],
23
+ hidden: [8, 28],
24
+ strikethrough: [9, 29]
25
+ },
26
+ color: {
27
+ black: [30, 39],
28
+ red: [31, 39],
29
+ green: [32, 39],
30
+ yellow: [33, 39],
31
+ blue: [34, 39],
32
+ magenta: [35, 39],
33
+ cyan: [36, 39],
34
+ white: [37, 39],
35
+ // Bright color
36
+ blackBright: [90, 39],
37
+ gray: [90, 39],
38
+ // Alias of `blackBright`
39
+ grey: [90, 39],
40
+ // Alias of `blackBright`
41
+ redBright: [91, 39],
42
+ greenBright: [92, 39],
43
+ yellowBright: [93, 39],
44
+ blueBright: [94, 39],
45
+ magentaBright: [95, 39],
46
+ cyanBright: [96, 39],
47
+ whiteBright: [97, 39]
48
+ },
49
+ bgColor: {
50
+ bgBlack: [40, 49],
51
+ bgRed: [41, 49],
52
+ bgGreen: [42, 49],
53
+ bgYellow: [43, 49],
54
+ bgBlue: [44, 49],
55
+ bgMagenta: [45, 49],
56
+ bgCyan: [46, 49],
57
+ bgWhite: [47, 49],
58
+ // Bright color
59
+ bgBlackBright: [100, 49],
60
+ bgGray: [100, 49],
61
+ // Alias of `bgBlackBright`
62
+ bgGrey: [100, 49],
63
+ // Alias of `bgBlackBright`
64
+ bgRedBright: [101, 49],
65
+ bgGreenBright: [102, 49],
66
+ bgYellowBright: [103, 49],
67
+ bgBlueBright: [104, 49],
68
+ bgMagentaBright: [105, 49],
69
+ bgCyanBright: [106, 49],
70
+ bgWhiteBright: [107, 49]
71
+ }
72
+ };
73
+ var modifierNames = Object.keys(styles.modifier);
74
+ var foregroundColorNames = Object.keys(styles.color);
75
+ var backgroundColorNames = Object.keys(styles.bgColor);
76
+ var colorNames = [...foregroundColorNames, ...backgroundColorNames];
77
+ function assembleStyles() {
78
+ const codes = /* @__PURE__ */ new Map();
79
+ for (const [groupName, group] of Object.entries(styles)) {
80
+ for (const [styleName, style] of Object.entries(group)) {
81
+ styles[styleName] = {
82
+ open: `\x1B[${style[0]}m`,
83
+ close: `\x1B[${style[1]}m`
84
+ };
85
+ group[styleName] = styles[styleName];
86
+ codes.set(style[0], style[1]);
87
+ }
88
+ Object.defineProperty(styles, groupName, {
89
+ value: group,
90
+ enumerable: false
91
+ });
92
+ }
93
+ Object.defineProperty(styles, "codes", {
94
+ value: codes,
95
+ enumerable: false
96
+ });
97
+ styles.color.close = "\x1B[39m";
98
+ styles.bgColor.close = "\x1B[49m";
99
+ styles.color.ansi = wrapAnsi16();
100
+ styles.color.ansi256 = wrapAnsi256();
101
+ styles.color.ansi16m = wrapAnsi16m();
102
+ styles.bgColor.ansi = wrapAnsi16(ANSI_BACKGROUND_OFFSET);
103
+ styles.bgColor.ansi256 = wrapAnsi256(ANSI_BACKGROUND_OFFSET);
104
+ styles.bgColor.ansi16m = wrapAnsi16m(ANSI_BACKGROUND_OFFSET);
105
+ Object.defineProperties(styles, {
106
+ rgbToAnsi256: {
107
+ value(red, green, blue) {
108
+ if (red === green && green === blue) {
109
+ if (red < 8) {
110
+ return 16;
111
+ }
112
+ if (red > 248) {
113
+ return 231;
114
+ }
115
+ return Math.round((red - 8) / 247 * 24) + 232;
116
+ }
117
+ return 16 + 36 * Math.round(red / 255 * 5) + 6 * Math.round(green / 255 * 5) + Math.round(blue / 255 * 5);
118
+ },
119
+ enumerable: false
120
+ },
121
+ hexToRgb: {
122
+ value(hex) {
123
+ const matches = /[a-f\d]{6}|[a-f\d]{3}/i.exec(hex.toString(16));
124
+ if (!matches) {
125
+ return [0, 0, 0];
126
+ }
127
+ let [colorString] = matches;
128
+ if (colorString.length === 3) {
129
+ colorString = [...colorString].map((character) => character + character).join("");
130
+ }
131
+ const integer = Number.parseInt(colorString, 16);
132
+ return [
133
+ /* eslint-disable no-bitwise */
134
+ integer >> 16 & 255,
135
+ integer >> 8 & 255,
136
+ integer & 255
137
+ /* eslint-enable no-bitwise */
138
+ ];
139
+ },
140
+ enumerable: false
141
+ },
142
+ hexToAnsi256: {
143
+ value: (hex) => styles.rgbToAnsi256(...styles.hexToRgb(hex)),
144
+ enumerable: false
145
+ },
146
+ ansi256ToAnsi: {
147
+ value(code) {
148
+ if (code < 8) {
149
+ return 30 + code;
150
+ }
151
+ if (code < 16) {
152
+ return 90 + (code - 8);
153
+ }
154
+ let red;
155
+ let green;
156
+ let blue;
157
+ if (code >= 232) {
158
+ red = ((code - 232) * 10 + 8) / 255;
159
+ green = red;
160
+ blue = red;
161
+ } else {
162
+ code -= 16;
163
+ const remainder = code % 36;
164
+ red = Math.floor(code / 36) / 5;
165
+ green = Math.floor(remainder / 6) / 5;
166
+ blue = remainder % 6 / 5;
167
+ }
168
+ const value = Math.max(red, green, blue) * 2;
169
+ if (value === 0) {
170
+ return 30;
171
+ }
172
+ let result = 30 + (Math.round(blue) << 2 | Math.round(green) << 1 | Math.round(red));
173
+ if (value === 2) {
174
+ result += 60;
175
+ }
176
+ return result;
177
+ },
178
+ enumerable: false
179
+ },
180
+ rgbToAnsi: {
181
+ value: (red, green, blue) => styles.ansi256ToAnsi(styles.rgbToAnsi256(red, green, blue)),
182
+ enumerable: false
183
+ },
184
+ hexToAnsi: {
185
+ value: (hex) => styles.ansi256ToAnsi(styles.hexToAnsi256(hex)),
186
+ enumerable: false
187
+ }
188
+ });
189
+ return styles;
190
+ }
191
+ var ansiStyles = assembleStyles();
192
+ var ansi_styles_default = ansiStyles;
193
+
194
+ // node_modules/chalk/source/vendor/supports-color/index.js
195
+ import process2 from "process";
196
+ import os from "os";
197
+ import tty from "tty";
198
+ function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process2.argv) {
199
+ const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
200
+ const position = argv.indexOf(prefix + flag);
201
+ const terminatorPosition = argv.indexOf("--");
202
+ return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
203
+ }
204
+ var { env } = process2;
205
+ var flagForceColor;
206
+ if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
207
+ flagForceColor = 0;
208
+ } else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
209
+ flagForceColor = 1;
210
+ }
211
+ function envForceColor() {
212
+ if ("FORCE_COLOR" in env) {
213
+ if (env.FORCE_COLOR === "true") {
214
+ return 1;
215
+ }
216
+ if (env.FORCE_COLOR === "false") {
217
+ return 0;
218
+ }
219
+ return env.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
220
+ }
221
+ }
222
+ function translateLevel(level) {
223
+ if (level === 0) {
224
+ return false;
225
+ }
226
+ return {
227
+ level,
228
+ hasBasic: true,
229
+ has256: level >= 2,
230
+ has16m: level >= 3
231
+ };
232
+ }
233
+ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
234
+ const noFlagForceColor = envForceColor();
235
+ if (noFlagForceColor !== void 0) {
236
+ flagForceColor = noFlagForceColor;
237
+ }
238
+ const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
239
+ if (forceColor === 0) {
240
+ return 0;
241
+ }
242
+ if (sniffFlags) {
243
+ if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
244
+ return 3;
245
+ }
246
+ if (hasFlag("color=256")) {
247
+ return 2;
248
+ }
249
+ }
250
+ if ("TF_BUILD" in env && "AGENT_NAME" in env) {
251
+ return 1;
252
+ }
253
+ if (haveStream && !streamIsTTY && forceColor === void 0) {
254
+ return 0;
255
+ }
256
+ const min = forceColor || 0;
257
+ if (env.TERM === "dumb") {
258
+ return min;
259
+ }
260
+ if (process2.platform === "win32") {
261
+ const osRelease = os.release().split(".");
262
+ if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
263
+ return Number(osRelease[2]) >= 14931 ? 3 : 2;
264
+ }
265
+ return 1;
266
+ }
267
+ if ("CI" in env) {
268
+ if (["GITHUB_ACTIONS", "GITEA_ACTIONS", "CIRCLECI"].some((key) => key in env)) {
269
+ return 3;
270
+ }
271
+ if (["TRAVIS", "APPVEYOR", "GITLAB_CI", "BUILDKITE", "DRONE"].some((sign) => sign in env) || env.CI_NAME === "codeship") {
272
+ return 1;
273
+ }
274
+ return min;
275
+ }
276
+ if ("TEAMCITY_VERSION" in env) {
277
+ return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
278
+ }
279
+ if (env.COLORTERM === "truecolor") {
280
+ return 3;
281
+ }
282
+ if (env.TERM === "xterm-kitty") {
283
+ return 3;
284
+ }
285
+ if (env.TERM === "xterm-ghostty") {
286
+ return 3;
287
+ }
288
+ if (env.TERM === "wezterm") {
289
+ return 3;
290
+ }
291
+ if ("TERM_PROGRAM" in env) {
292
+ const version = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
293
+ switch (env.TERM_PROGRAM) {
294
+ case "iTerm.app": {
295
+ return version >= 3 ? 3 : 2;
296
+ }
297
+ case "Apple_Terminal": {
298
+ return 2;
299
+ }
300
+ }
301
+ }
302
+ if (/-256(color)?$/i.test(env.TERM)) {
303
+ return 2;
304
+ }
305
+ if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
306
+ return 1;
307
+ }
308
+ if ("COLORTERM" in env) {
309
+ return 1;
310
+ }
311
+ return min;
312
+ }
313
+ function createSupportsColor(stream, options = {}) {
314
+ const level = _supportsColor(stream, {
315
+ streamIsTTY: stream && stream.isTTY,
316
+ ...options
317
+ });
318
+ return translateLevel(level);
319
+ }
320
+ var supportsColor = {
321
+ stdout: createSupportsColor({ isTTY: tty.isatty(1) }),
322
+ stderr: createSupportsColor({ isTTY: tty.isatty(2) })
323
+ };
324
+ var supports_color_default = supportsColor;
325
+
326
+ // node_modules/chalk/source/utilities.js
327
+ function stringReplaceAll(string, substring, replacer) {
328
+ let index = string.indexOf(substring);
329
+ if (index === -1) {
330
+ return string;
331
+ }
332
+ const substringLength = substring.length;
333
+ let endIndex = 0;
334
+ let returnValue = "";
335
+ do {
336
+ returnValue += string.slice(endIndex, index) + substring + replacer;
337
+ endIndex = index + substringLength;
338
+ index = string.indexOf(substring, endIndex);
339
+ } while (index !== -1);
340
+ returnValue += string.slice(endIndex);
341
+ return returnValue;
342
+ }
343
+ function stringEncaseCRLFWithFirstIndex(string, prefix, postfix, index) {
344
+ let endIndex = 0;
345
+ let returnValue = "";
346
+ do {
347
+ const gotCR = string[index - 1] === "\r";
348
+ returnValue += string.slice(endIndex, gotCR ? index - 1 : index) + prefix + (gotCR ? "\r\n" : "\n") + postfix;
349
+ endIndex = index + 1;
350
+ index = string.indexOf("\n", endIndex);
351
+ } while (index !== -1);
352
+ returnValue += string.slice(endIndex);
353
+ return returnValue;
354
+ }
355
+
356
+ // node_modules/chalk/source/index.js
357
+ var { stdout: stdoutColor, stderr: stderrColor } = supports_color_default;
358
+ var GENERATOR = /* @__PURE__ */ Symbol("GENERATOR");
359
+ var STYLER = /* @__PURE__ */ Symbol("STYLER");
360
+ var IS_EMPTY = /* @__PURE__ */ Symbol("IS_EMPTY");
361
+ var levelMapping = [
362
+ "ansi",
363
+ "ansi",
364
+ "ansi256",
365
+ "ansi16m"
366
+ ];
367
+ var styles2 = /* @__PURE__ */ Object.create(null);
368
+ var applyOptions = (object, options = {}) => {
369
+ if (options.level && !(Number.isInteger(options.level) && options.level >= 0 && options.level <= 3)) {
370
+ throw new Error("The `level` option should be an integer from 0 to 3");
371
+ }
372
+ const colorLevel = stdoutColor ? stdoutColor.level : 0;
373
+ object.level = options.level === void 0 ? colorLevel : options.level;
374
+ };
375
+ var chalkFactory = (options) => {
376
+ const chalk2 = (...strings) => strings.join(" ");
377
+ applyOptions(chalk2, options);
378
+ Object.setPrototypeOf(chalk2, createChalk.prototype);
379
+ return chalk2;
380
+ };
381
+ function createChalk(options) {
382
+ return chalkFactory(options);
383
+ }
384
+ Object.setPrototypeOf(createChalk.prototype, Function.prototype);
385
+ for (const [styleName, style] of Object.entries(ansi_styles_default)) {
386
+ styles2[styleName] = {
387
+ get() {
388
+ const builder = createBuilder(this, createStyler(style.open, style.close, this[STYLER]), this[IS_EMPTY]);
389
+ Object.defineProperty(this, styleName, { value: builder });
390
+ return builder;
391
+ }
392
+ };
393
+ }
394
+ styles2.visible = {
395
+ get() {
396
+ const builder = createBuilder(this, this[STYLER], true);
397
+ Object.defineProperty(this, "visible", { value: builder });
398
+ return builder;
399
+ }
400
+ };
401
+ var getModelAnsi = (model, level, type, ...arguments_) => {
402
+ if (model === "rgb") {
403
+ if (level === "ansi16m") {
404
+ return ansi_styles_default[type].ansi16m(...arguments_);
405
+ }
406
+ if (level === "ansi256") {
407
+ return ansi_styles_default[type].ansi256(ansi_styles_default.rgbToAnsi256(...arguments_));
408
+ }
409
+ return ansi_styles_default[type].ansi(ansi_styles_default.rgbToAnsi(...arguments_));
410
+ }
411
+ if (model === "hex") {
412
+ return getModelAnsi("rgb", level, type, ...ansi_styles_default.hexToRgb(...arguments_));
413
+ }
414
+ return ansi_styles_default[type][model](...arguments_);
415
+ };
416
+ var usedModels = ["rgb", "hex", "ansi256"];
417
+ for (const model of usedModels) {
418
+ styles2[model] = {
419
+ get() {
420
+ const { level } = this;
421
+ return function(...arguments_) {
422
+ const styler = createStyler(getModelAnsi(model, levelMapping[level], "color", ...arguments_), ansi_styles_default.color.close, this[STYLER]);
423
+ return createBuilder(this, styler, this[IS_EMPTY]);
424
+ };
425
+ }
426
+ };
427
+ const bgModel = "bg" + model[0].toUpperCase() + model.slice(1);
428
+ styles2[bgModel] = {
429
+ get() {
430
+ const { level } = this;
431
+ return function(...arguments_) {
432
+ const styler = createStyler(getModelAnsi(model, levelMapping[level], "bgColor", ...arguments_), ansi_styles_default.bgColor.close, this[STYLER]);
433
+ return createBuilder(this, styler, this[IS_EMPTY]);
434
+ };
435
+ }
436
+ };
437
+ }
438
+ var proto = Object.defineProperties(() => {
439
+ }, {
440
+ ...styles2,
441
+ level: {
442
+ enumerable: true,
443
+ get() {
444
+ return this[GENERATOR].level;
445
+ },
446
+ set(level) {
447
+ this[GENERATOR].level = level;
448
+ }
449
+ }
450
+ });
451
+ var createStyler = (open, close, parent) => {
452
+ let openAll;
453
+ let closeAll;
454
+ if (parent === void 0) {
455
+ openAll = open;
456
+ closeAll = close;
457
+ } else {
458
+ openAll = parent.openAll + open;
459
+ closeAll = close + parent.closeAll;
460
+ }
461
+ return {
462
+ open,
463
+ close,
464
+ openAll,
465
+ closeAll,
466
+ parent
467
+ };
468
+ };
469
+ var createBuilder = (self, _styler, _isEmpty) => {
470
+ const builder = (...arguments_) => applyStyle(builder, arguments_.length === 1 ? "" + arguments_[0] : arguments_.join(" "));
471
+ Object.setPrototypeOf(builder, proto);
472
+ builder[GENERATOR] = self;
473
+ builder[STYLER] = _styler;
474
+ builder[IS_EMPTY] = _isEmpty;
475
+ return builder;
476
+ };
477
+ var applyStyle = (self, string) => {
478
+ if (self.level <= 0 || !string) {
479
+ return self[IS_EMPTY] ? "" : string;
480
+ }
481
+ let styler = self[STYLER];
482
+ if (styler === void 0) {
483
+ return string;
484
+ }
485
+ const { openAll, closeAll } = styler;
486
+ if (string.includes("\x1B")) {
487
+ while (styler !== void 0) {
488
+ string = stringReplaceAll(string, styler.close, styler.open);
489
+ styler = styler.parent;
490
+ }
491
+ }
492
+ const lfIndex = string.indexOf("\n");
493
+ if (lfIndex !== -1) {
494
+ string = stringEncaseCRLFWithFirstIndex(string, closeAll, openAll, lfIndex);
495
+ }
496
+ return openAll + string + closeAll;
497
+ };
498
+ Object.defineProperties(createChalk.prototype, styles2);
499
+ var chalk = createChalk();
500
+ var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
501
+ var source_default = chalk;
502
+
8
503
  // src/utils/logger.ts
9
- import chalk from "chalk";
10
504
  var ConsoleLogger = class {
11
505
  info(message) {
12
- console.log(chalk.blue("\u2139\uFE0F " + message));
506
+ console.log(source_default.blue("\u2139\uFE0F " + message));
13
507
  }
14
508
  success(message) {
15
- console.log(chalk.green("\u2705 " + message));
509
+ console.log(source_default.green("\u2705 " + message));
16
510
  }
17
511
  warn(message) {
18
- console.log(chalk.yellow("\u26A0\uFE0F " + message));
512
+ console.log(source_default.yellow("\u26A0\uFE0F " + message));
19
513
  }
20
514
  error(message) {
21
- console.log(chalk.red("\u274C " + message));
515
+ console.log(source_default.red("\u274C " + message));
22
516
  }
23
517
  title(message) {
24
- console.log(chalk.bold.cyan("\n" + message + "\n"));
518
+ console.log(source_default.bold.cyan("\n" + message + "\n"));
25
519
  }
26
520
  newLine() {
27
521
  console.log("");
@@ -245,12 +739,7 @@ var RetryStrategy = class {
245
739
  logger.warn(`${operationName} failed (Attempt ${attempt}): ${errorMessage} [${category}]`);
246
740
  if (!this.shouldRetry(error, attempt)) {
247
741
  logger.error(`${operationName} failed with non-retryable error: ${errorMessage}`);
248
- return {
249
- success: false,
250
- error,
251
- totalAttempts: attempt,
252
- lastError: errorMessage
253
- };
742
+ throw error;
254
743
  }
255
744
  if (attempt <= this.config.maxRetries) {
256
745
  const delayMs = this.calculateDelay(attempt - 1);
@@ -261,12 +750,7 @@ var RetryStrategy = class {
261
750
  }
262
751
  }
263
752
  }
264
- return {
265
- success: false,
266
- error: lastError || new Error(`${operationName} failed after all retries`),
267
- totalAttempts: attempt,
268
- lastError: lastError?.message
269
- };
753
+ throw lastError || new Error(`${operationName} failed after all retries`);
270
754
  }
271
755
  };
272
756
  var DEFAULT_RETRY_CONFIG = {
@@ -460,6 +944,7 @@ async function parseHap(filePath) {
460
944
  }
461
945
 
462
946
  // src/utils/upload-manager.ts
947
+ var GROUPED_ARTIFACT_PLATFORMS = /* @__PURE__ */ new Set(["huawei", "xiaomi", "oppo", "vivo", "tencent"]);
463
948
  var UploadManager = class {
464
949
  uploaders;
465
950
  config;
@@ -566,9 +1051,22 @@ var UploadManager = class {
566
1051
  if (matchedFiles.length === 0) {
567
1052
  logger.warn(`[${platform}] No compatible file found in provided files`);
568
1053
  } else {
569
- matchedFiles.forEach((file) => {
570
- tasks.push({ platform, file });
571
- });
1054
+ if (this.shouldGroupArtifacts(platform)) {
1055
+ const artifactSelection = this.resolveArtifactSelection(platform, matchedFiles, metadata);
1056
+ if (!artifactSelection) {
1057
+ logger.warn(`[${platform}] Failed to build grouped artifact selection`);
1058
+ return;
1059
+ }
1060
+ tasks.push({
1061
+ platform,
1062
+ file: this.getRepresentativeFilePath(artifactSelection),
1063
+ artifactSelection
1064
+ });
1065
+ } else {
1066
+ matchedFiles.forEach((file) => {
1067
+ tasks.push({ platform, file, artifactSelection: file });
1068
+ });
1069
+ }
572
1070
  }
573
1071
  });
574
1072
  if (tasks.length === 0) {
@@ -587,7 +1085,13 @@ var UploadManager = class {
587
1085
  logger.info(`Scheduled ${tasks.length} upload task(s) for ${validPlatforms.length} platform(s)`);
588
1086
  const uploadPromises = tasks.map((task) => {
589
1087
  logger.info(`[${task.platform}] Matched file: ${path2.basename(task.file)}`);
590
- return this.uploadToPlatform(task.platform, task.file, config, metadata);
1088
+ return this.uploadToPlatform(
1089
+ task.platform,
1090
+ task.file,
1091
+ config,
1092
+ metadata,
1093
+ task.artifactSelection
1094
+ );
591
1095
  });
592
1096
  const results = await Promise.all(uploadPromises);
593
1097
  const report = this.aggregateResults(results, tasks.length, startTime);
@@ -597,7 +1101,7 @@ var UploadManager = class {
597
1101
  /**
598
1102
  * 上传到单个平台
599
1103
  */
600
- async uploadToPlatform(platform, filePath, config, metadata) {
1104
+ async uploadToPlatform(platform, filePath, config, metadata, artifactSelection = filePath) {
601
1105
  if (this.config.cancellationToken?.isCancelled) {
602
1106
  logger.warn(`[${platform.toUpperCase()}] Upload skipped due to cancellation`);
603
1107
  return {
@@ -618,25 +1122,30 @@ var UploadManager = class {
618
1122
  if (this.config.verbose) {
619
1123
  logger.info(`[${platform.toUpperCase()}] Starting upload...`);
620
1124
  }
1125
+ const effectiveMetadata = {
1126
+ ...metadata,
1127
+ artifactPath: artifactSelection
1128
+ };
1129
+ await this.validateArtifactSelection(platform, artifactSelection, effectiveMetadata);
621
1130
  const pkgInfo = await parsePackageInfo(filePath, {
622
- androidSdkPath: metadata.globalSettings?.androidSdkPath
1131
+ androidSdkPath: effectiveMetadata.globalSettings?.androidSdkPath
623
1132
  });
624
1133
  if (pkgInfo) {
625
1134
  if (pkgInfo.versionName) {
626
- metadata.version = pkgInfo.versionName;
1135
+ effectiveMetadata.version = pkgInfo.versionName;
627
1136
  }
628
1137
  if (pkgInfo.versionCode) {
629
- metadata.buildNumber = pkgInfo.versionCode;
1138
+ effectiveMetadata.buildNumber = pkgInfo.versionCode;
630
1139
  }
631
1140
  if (pkgInfo.packageName) {
632
1141
  ;
633
- metadata.packageName = pkgInfo.packageName;
1142
+ effectiveMetadata.packageName = pkgInfo.packageName;
634
1143
  }
635
1144
  logger.info(
636
- `[${platform.toUpperCase()}] \u5DF2\u6821\u51C6\u7248\u672C\u4FE1\u606F: v${metadata.version}+${metadata.buildNumber}`
1145
+ `[${platform.toUpperCase()}] \u5DF2\u6821\u51C6\u7248\u672C\u4FE1\u606F: v${effectiveMetadata.version}+${effectiveMetadata.buildNumber}`
637
1146
  );
638
1147
  }
639
- const result = await uploader.upload(filePath, config, metadata);
1148
+ const result = await uploader.upload(filePath, config, effectiveMetadata);
640
1149
  return result;
641
1150
  } catch (error) {
642
1151
  const errorMessage = error.message || String(error);
@@ -649,6 +1158,85 @@ var UploadManager = class {
649
1158
  };
650
1159
  }
651
1160
  }
1161
+ shouldGroupArtifacts(platform) {
1162
+ return GROUPED_ARTIFACT_PLATFORMS.has(platform);
1163
+ }
1164
+ resolveArtifactSelection(platform, matchedFiles, metadata) {
1165
+ const explicitArtifacts = metadata.platformArtifacts?.[platform];
1166
+ if (explicitArtifacts) {
1167
+ if (typeof explicitArtifacts === "string") {
1168
+ return explicitArtifacts;
1169
+ }
1170
+ if (Object.keys(explicitArtifacts).length > 0) {
1171
+ return explicitArtifacts;
1172
+ }
1173
+ }
1174
+ if (matchedFiles.length === 0) {
1175
+ return null;
1176
+ }
1177
+ if (matchedFiles.length === 1) {
1178
+ return matchedFiles[0];
1179
+ }
1180
+ return this.buildArtifactMapFromFiles(matchedFiles);
1181
+ }
1182
+ buildArtifactMapFromFiles(files) {
1183
+ return files.reduce(
1184
+ (acc, filePath, index) => {
1185
+ acc[this.inferArtifactKey(filePath, index)] = filePath;
1186
+ return acc;
1187
+ },
1188
+ {}
1189
+ );
1190
+ }
1191
+ inferArtifactKey(filePath, index) {
1192
+ const fileName = path2.basename(filePath).toLowerCase();
1193
+ if (fileName.includes("arm64-v8a")) return "arm64-v8a";
1194
+ if (fileName.includes("armeabi-v7a")) return "armeabi-v7a";
1195
+ if (fileName.includes("x86_64")) return "x86_64";
1196
+ if (fileName.includes("universal")) return "universal";
1197
+ return `artifact_${index + 1}`;
1198
+ }
1199
+ getRepresentativeFilePath(artifactSelection) {
1200
+ if (typeof artifactSelection === "string") {
1201
+ return artifactSelection;
1202
+ }
1203
+ return Object.values(artifactSelection)[0];
1204
+ }
1205
+ async validateArtifactSelection(platform, artifactSelection, metadata) {
1206
+ if (typeof artifactSelection === "string") {
1207
+ return;
1208
+ }
1209
+ const artifactPaths = Object.values(artifactSelection).filter(Boolean);
1210
+ if (artifactPaths.length < 2) {
1211
+ return;
1212
+ }
1213
+ const parsedInfos = await Promise.all(
1214
+ artifactPaths.map(
1215
+ (artifactPath) => parsePackageInfo(artifactPath, {
1216
+ androidSdkPath: metadata.globalSettings?.androidSdkPath
1217
+ })
1218
+ )
1219
+ );
1220
+ const availableInfos = parsedInfos.filter((info) => !!info);
1221
+ if (availableInfos.length < 2) {
1222
+ logger.warn(
1223
+ `[${platform.toUpperCase()}] Unable to fully validate multi-artifact package info; continuing with upload.`
1224
+ );
1225
+ return;
1226
+ }
1227
+ const [baseInfo, ...restInfos] = availableInfos;
1228
+ for (const info of restInfos) {
1229
+ if (baseInfo?.packageName && info?.packageName && baseInfo.packageName !== info.packageName) {
1230
+ throw new Error("\u53CC\u5305\u4E0A\u4F20\u5931\u8D25\uFF1A\u6240\u9009\u5B89\u88C5\u5305\u7684 packageName \u4E0D\u4E00\u81F4");
1231
+ }
1232
+ if (baseInfo?.versionName && info?.versionName && baseInfo.versionName !== info.versionName) {
1233
+ throw new Error("\u53CC\u5305\u4E0A\u4F20\u5931\u8D25\uFF1A\u6240\u9009\u5B89\u88C5\u5305\u7684 versionName \u4E0D\u4E00\u81F4");
1234
+ }
1235
+ if (baseInfo?.versionCode && info?.versionCode && baseInfo.versionCode !== info.versionCode) {
1236
+ throw new Error("\u53CC\u5305\u4E0A\u4F20\u5931\u8D25\uFF1A\u6240\u9009\u5B89\u88C5\u5305\u7684 versionCode \u4E0D\u4E00\u81F4");
1237
+ }
1238
+ }
1239
+ }
652
1240
  /**
653
1241
  * 聚合上传结果
654
1242
  */
@@ -1114,27 +1702,39 @@ var HuaweiUploader = class _HuaweiUploader {
1114
1702
  clientId,
1115
1703
  appId
1116
1704
  };
1117
- logger.info("Requesting upload URL...");
1118
- const uploadInfoResult = await retryStrategy.execute(
1119
- () => this.getUploadUrl(ctx, suffix),
1120
- "Huawei get upload URL"
1121
- );
1122
- if (!uploadInfoResult.success || !uploadInfoResult.data) {
1123
- throw uploadInfoResult.error || new Error("Failed to get upload URL");
1124
- }
1125
- const uploadInfo = uploadInfoResult.data;
1126
- logger.info("Uploading file (this may take a while)...");
1127
- const uploadFileResult = await retryStrategy.execute(
1128
- () => this.uploadFile(uploadInfo.uploadUrl, uploadInfo.authCode, filePath),
1129
- "Huawei file upload"
1130
- );
1131
- if (!uploadFileResult.success || !uploadFileResult.data) {
1132
- throw uploadFileResult.error || new Error("File upload failed");
1705
+ const artifacts = typeof metadata.artifactPath === "object" ? metadata.artifactPath : { universal: filePath || metadata.artifactPath || "" };
1706
+ logger.info(`[Huawei] \u5904\u7406\u4EA7\u7269\u6570\u91CF: ${Object.keys(artifacts).length}`);
1707
+ const uploadResults = [];
1708
+ const uploadPromises = Object.entries(artifacts).map(async ([abi, p]) => {
1709
+ if (!p || !fs5.existsSync(p)) return;
1710
+ const currentSuffix = path3.extname(p).substring(1);
1711
+ logger.info(`[Huawei] \u6B63\u5728\u4E0A\u4F20 ${abi} \u5305: ${path3.basename(p)}`);
1712
+ const uploadInfo = await retryStrategy.execute(
1713
+ () => this.getUploadUrl(ctx, currentSuffix),
1714
+ `Huawei get upload URL for ${abi}`
1715
+ );
1716
+ if (!uploadInfo.success || !uploadInfo.data) {
1717
+ throw uploadInfo.error || new Error(`Failed to get upload URL for ${abi}`);
1718
+ }
1719
+ const uploadFileRes = await retryStrategy.execute(
1720
+ () => this.executeUploadRequest(uploadInfo.data.uploadUrl, uploadInfo.data.authCode, p),
1721
+ `Huawei upload file for ${abi}`
1722
+ );
1723
+ if (!uploadFileRes.success || !uploadFileRes.data || uploadFileRes.data.length === 0) {
1724
+ throw uploadFileRes.error || new Error(`File upload failed for ${abi}`);
1725
+ }
1726
+ uploadResults.push({
1727
+ fileName: path3.basename(p),
1728
+ fileDestUrl: uploadFileRes.data[0].fileDestUrl
1729
+ });
1730
+ });
1731
+ await Promise.all(uploadPromises);
1732
+ if (uploadResults.length === 0) {
1733
+ throw new Error("No files were successfully uploaded");
1133
1734
  }
1134
- const fileDestUrl = uploadFileResult.data.fileDestUrl;
1135
- logger.info("Linking file to app...");
1735
+ logger.info(`[Huawei] \u6B63\u5728\u5173\u8054 ${uploadResults.length} \u4E2A\u6587\u4EF6\u5230\u5E94\u7528...`);
1136
1736
  const updateInfoResult = await retryStrategy.execute(
1137
- () => this.updateAppFileInfo(ctx, fileDestUrl, filePath),
1737
+ () => this.updateAppFileInfo(ctx, uploadResults, filePath),
1138
1738
  "Huawei update file info"
1139
1739
  );
1140
1740
  if (!updateInfoResult.success) {
@@ -1338,14 +1938,17 @@ var HuaweiUploader = class _HuaweiUploader {
1338
1938
  throw new Error("Upload succeeded but fileDestUrl not found in response");
1339
1939
  }
1340
1940
  /** 更新应用文件信息 */
1341
- async updateAppFileInfo(ctx, fileDestUrl, filePath) {
1342
- const fileName = path3.basename(filePath);
1343
- const fileType = path3.extname(filePath).toLowerCase() === ".aab" ? 7 : 5;
1941
+ async updateAppFileInfo(ctx, files, anyFilePath) {
1942
+ const hasAab = files.some((f) => f.fileName.toLowerCase().endsWith(".aab"));
1943
+ const fileType = hasAab ? 7 /* AAB */ : 5 /* APP_PACKAGE */;
1344
1944
  const res = await axios2.put(
1345
1945
  `${ctx.apiRoot}/app-file-info`,
1346
1946
  {
1347
1947
  fileType,
1348
- files: [{ fileName, fileDestUrl }]
1948
+ files: files.map((f) => ({
1949
+ fileName: f.fileName,
1950
+ fileDestUrl: f.fileDestUrl
1951
+ }))
1349
1952
  },
1350
1953
  {
1351
1954
  params: { appId: ctx.appId },
@@ -1515,6 +2118,7 @@ var HuaweiUploader = class _HuaweiUploader {
1515
2118
 
1516
2119
  // src/adapters/xiaomi_uploader.ts
1517
2120
  import fs6 from "fs-extra";
2121
+ import path4 from "path";
1518
2122
  import axios3 from "axios";
1519
2123
  import FormData3 from "form-data";
1520
2124
  import crypto3 from "crypto";
@@ -1765,80 +2369,39 @@ ${lines.join("\n")}
1765
2369
  const xiaomiConfig = config.publish?.xiaomi;
1766
2370
  const { userName, privateKey, publicKeyPath } = xiaomiConfig.auth;
1767
2371
  const updateDesc = metadata.changelog || xiaomiConfig?.description || "App Update";
1768
- const retryConfig = config.retry;
1769
- if (!fs6.existsSync(filePath)) {
1770
- return {
1771
- platform: "xiaomi",
1772
- success: false,
1773
- message: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}`
1774
- };
1775
- }
2372
+ const artifacts = typeof metadata.artifactPath === "object" ? metadata.artifactPath : { universal: filePath || (typeof metadata.artifactPath === "string" ? metadata.artifactPath : "") };
1776
2373
  const resolvedPublicKey = await this.resolvePublicKey(publicKeyPath);
1777
2374
  if (!resolvedPublicKey) {
1778
- return {
1779
- platform: "xiaomi",
1780
- success: false,
1781
- message: "\u65E0\u6CD5\u83B7\u53D6 RSA \u516C\u94A5: publicKeyPath \u672A\u63D0\u4F9B\u6216\u6587\u4EF6\u4E0D\u5B58\u5728"
1782
- };
1783
- }
1784
- const packageName = xiaomiConfig.packageName || this.inferPackageName(filePath);
1785
- if (!packageName) {
1786
- return {
1787
- platform: "xiaomi",
1788
- success: false,
1789
- message: "\u7F3A\u5C11 packageName \u914D\u7F6E, \u4E14\u65E0\u6CD5\u4ECE\u6587\u4EF6\u540D\u63A8\u65AD\u5E94\u7528\u5305\u540D"
1790
- };
1791
- }
1792
- const appName = xiaomiConfig.appName || this.inferAppName(packageName);
1793
- if (!appName) {
1794
- return {
1795
- platform: "xiaomi",
1796
- success: false,
1797
- message: "\u7F3A\u5C11 appName \u914D\u7F6E, \u4E14\u65E0\u6CD5\u4ECE\u5305\u540D\u63A8\u65AD\u5E94\u7528\u540D\u79F0"
1798
- };
2375
+ throw new Error("\u65E0\u6CD5\u83B7\u53D6 RSA \u516C\u94A5");
1799
2376
  }
1800
- logger.info("\u5F00\u59CB\u4E0A\u4F20\u5230\u5C0F\u7C73\u5E94\u7528\u5546\u5E97...");
1801
- logger.info(`\u6587\u4EF6: ${filePath}`);
1802
- logger.info(`\u5305\u540D: ${packageName}`);
1803
- logger.info(`\u5E94\u7528\u540D: ${appName}`);
1804
2377
  const publicKeyObj = this.extractPublicKey(resolvedPublicKey);
1805
- const configUploadType = xiaomiConfig.uploadType || "auto";
1806
- let uploadType;
1807
- let existingAppInfo = null;
1808
- if (configUploadType === "auto") {
1809
- logger.info("\u6B63\u5728\u67E5\u8BE2\u5E94\u7528\u4FE1\u606F...");
1810
- const status = await this.queryAppStatus(userName, packageName, privateKey, publicKeyObj);
1811
- uploadType = status.type;
1812
- existingAppInfo = status.appInfo;
1813
- } else {
1814
- uploadType = configUploadType;
1815
- }
1816
- const uploadTypeDesc = uploadType === "update" ? "\u5E94\u7528\u66F4\u65B0 (synchroType=1)" : "\u65B0\u589E\u5E94\u7528 (synchroType=0)";
1817
- logger.info(`\u4E0A\u4F20\u7C7B\u578B: ${uploadTypeDesc}`);
1818
- if (uploadType === "new" && xiaomiConfig.uploadType === "new") {
1819
- const missing = [];
1820
- if (xiaomiConfig.category == null) missing.push("category (\u5206\u7C7BID)");
1821
- if (!xiaomiConfig.desc) missing.push("desc (\u5E94\u7528\u4ECB\u7ECD)");
1822
- if (missing.length > 0) {
1823
- return {
1824
- platform: "xiaomi",
1825
- success: false,
1826
- message: `\u9996\u6B21\u63D0\u5BA1\u6A21\u5F0F\u7F3A\u5C11\u5FC5\u586B\u5B57\u6BB5: ${missing.join(", ")}\u3002\u8BF7\u5728\u540E\u53F0\u8865\u5168\u6216\u5728\u914D\u7F6E\u4E2D\u6DFB\u52A0\u3002`
1827
- };
1828
- }
1829
- }
2378
+ const anyPath = Object.values(artifacts)[0];
2379
+ const packageName = xiaomiConfig.packageName || this.inferPackageName(anyPath);
2380
+ if (!packageName) throw new Error("\u7F3A\u5C11 packageName");
2381
+ const appName = xiaomiConfig.appName || this.inferAppName(packageName);
1830
2382
  try {
1831
- const retryStrategy = createUploadRetryStrategy(retryConfig);
2383
+ logger.info(`\u5F00\u59CB\u4E0A\u4F20\u5230\u5C0F\u7C73\u5E94\u7528\u5546\u5E97 (\u5305\u542B ${Object.keys(artifacts).length} \u4E2A\u5305)...`);
2384
+ let uploadType = xiaomiConfig.uploadType || "auto";
2385
+ let finalUploadType;
2386
+ let existingAppInfo = null;
2387
+ if (uploadType === "auto") {
2388
+ const status = await this.queryAppStatus(userName, packageName, privateKey, publicKeyObj);
2389
+ finalUploadType = status.type;
2390
+ existingAppInfo = status.appInfo;
2391
+ } else {
2392
+ finalUploadType = uploadType;
2393
+ }
2394
+ const retryStrategy = createUploadRetryStrategy(config.retry);
1832
2395
  const result = await retryStrategy.execute(
1833
2396
  () => this.performUpload({
1834
- filePath,
2397
+ artifacts,
1835
2398
  userName,
1836
2399
  privateKey,
1837
2400
  publicKey: resolvedPublicKey,
2401
+ uploadType: finalUploadType,
1838
2402
  packageName,
1839
- appName,
2403
+ appName: appName || "",
1840
2404
  updateDesc,
1841
- uploadType,
1842
2405
  category: xiaomiConfig.category || existingAppInfo?.category,
1843
2406
  keyWords: xiaomiConfig.keyWords || existingAppInfo?.keyWords,
1844
2407
  desc: xiaomiConfig.desc || existingAppInfo?.desc,
@@ -1848,50 +2411,19 @@ ${lines.join("\n")}
1848
2411
  }),
1849
2412
  "Xiaomi push upload"
1850
2413
  );
1851
- if (!result.success) {
1852
- throw result.error || new Error("Upload failed");
1853
- }
1854
- logger.success("\u4E0A\u4F20\u5230\u5C0F\u7C73\u5E94\u7528\u5546\u5E97\u6210\u529F!");
1855
2414
  return {
1856
2415
  platform: "xiaomi",
1857
2416
  success: true,
1858
- message: "\u4E0A\u4F20\u6210\u529F, \u7B49\u5F85\u5BA1\u6838",
1859
- data: result.data,
2417
+ message: "\u4E0A\u4F20\u6210\u529F",
1860
2418
  consoleUrl: "https://dev.mi.com/distribute/app/details"
1861
2419
  };
1862
2420
  } catch (error) {
1863
- logger.error(`\u4E0A\u4F20\u5230\u5C0F\u7C73\u5931\u8D25: ${error.message}`);
1864
- if (error.response?.data) {
1865
- logger.info(`\u9519\u8BEF\u8BE6\u60C5: ${JSON.stringify(error.response.data)}`);
1866
- }
1867
- return {
1868
- platform: "xiaomi",
1869
- success: false,
1870
- message: error.message,
1871
- error
1872
- };
2421
+ logger.error(`\u5C0F\u7C73\u4E0A\u4F20\u5931\u8D25: ${error.message}`);
2422
+ return { platform: "xiaomi", success: false, message: error.message, error };
1873
2423
  }
1874
2424
  }
1875
- /**
1876
- * 执行实际的上传操作
1877
- */
1878
2425
  async performUpload(params) {
1879
- const {
1880
- filePath,
1881
- userName,
1882
- privateKey,
1883
- publicKey,
1884
- packageName,
1885
- appName,
1886
- updateDesc,
1887
- uploadType,
1888
- category,
1889
- keyWords,
1890
- desc,
1891
- brief,
1892
- privacyUrl,
1893
- assets
1894
- } = params;
2426
+ const { artifacts, userName, privateKey, publicKey, packageName, appName, updateDesc, uploadType, category, keyWords, desc, brief, privacyUrl, assets } = params;
1895
2427
  const publicKeyObj = this.extractPublicKey(publicKey);
1896
2428
  const requestData = this.buildRequestData({
1897
2429
  userName,
@@ -1905,67 +2437,55 @@ ${lines.join("\n")}
1905
2437
  brief,
1906
2438
  privacyUrl
1907
2439
  });
1908
- logger.info("RequestData \u5DF2\u6784\u5EFA");
1909
- logger.info(`RequestData: ${requestData}`);
1910
2440
  const requestDataMD5 = this.calculateStringMD5(requestData);
1911
- const apkFileMD5 = await this.calculateFileMD5(filePath);
1912
- logger.info(`APK MD5: ${apkFileMD5}`);
2441
+ let mainPath;
2442
+ let secondPath = void 0;
2443
+ const artifactEntries = Object.entries(artifacts);
2444
+ const arm64 = artifactEntries.find(([abi]) => abi === "arm64-v8a");
2445
+ const arm32 = artifactEntries.find(([abi]) => abi === "armeabi-v7a");
2446
+ if (arm64 && arm32) {
2447
+ mainPath = arm64[1];
2448
+ secondPath = arm32[1];
2449
+ logger.info(`[Xiaomi] \u8BC6\u522B\u5230\u591A\u67B6\u6784\u5206\u5305: \u4E3B\u5305(64\u4F4D)=${path4.basename(mainPath)}, \u6B21\u5305(32\u4F4D)=${path4.basename(secondPath)}`);
2450
+ } else {
2451
+ mainPath = artifactEntries[0][1];
2452
+ if (artifactEntries.length > 1) {
2453
+ secondPath = artifactEntries[1][1];
2454
+ logger.info(`[Xiaomi] \u8BC6\u522B\u5230\u591A\u4E2A\u5305\uFF08\u975E\u6807\u51C6\u540D\uFF09\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u987A\u5E8F: \u4E3B\u5305=${path4.basename(mainPath)}, \u6B21\u5305=${path4.basename(secondPath)}`);
2455
+ } else {
2456
+ logger.info(`[Xiaomi] \u4F7F\u7528\u5355\u5305\u6A21\u5F0F: ${path4.basename(mainPath)}`);
2457
+ }
2458
+ }
2459
+ const mainMD5 = await this.calculateFileMD5(mainPath);
1913
2460
  const extraSigItems = [];
1914
- let iconPath;
2461
+ if (secondPath) {
2462
+ const secondMD5 = await this.calculateFileMD5(secondPath);
2463
+ extraSigItems.push({ name: "secondApk", hash: secondMD5 });
2464
+ }
1915
2465
  if (assets?.icon && fs6.existsSync(assets.icon)) {
1916
- iconPath = assets.icon;
1917
- const iconMD5 = await this.calculateFileMD5(iconPath);
1918
- extraSigItems.push({ name: "icon", hash: iconMD5 });
1919
- }
1920
- const screenshotPaths = [];
1921
- if (assets?.screenshots) {
1922
- for (let i = 0; i < assets.screenshots.length && i < 4; i++) {
1923
- const ssPath = assets.screenshots[i];
1924
- if (fs6.existsSync(ssPath)) {
1925
- screenshotPaths.push(ssPath);
1926
- const ssMD5 = await this.calculateFileMD5(ssPath);
1927
- extraSigItems.push({ name: `screenshot_${i + 1}`, hash: ssMD5 });
1928
- }
1929
- }
2466
+ extraSigItems.push({ name: "icon", hash: await this.calculateFileMD5(assets.icon) });
1930
2467
  }
1931
- const sig = this.generateUploadSIG(
1932
- requestDataMD5,
1933
- apkFileMD5,
1934
- privateKey,
1935
- publicKeyObj,
1936
- extraSigItems.length > 0 ? extraSigItems : void 0
1937
- );
1938
- logger.info("SIG \u7B7E\u540D\u5DF2\u751F\u6210");
2468
+ const sig = this.generateUploadSIG(requestDataMD5, mainMD5, privateKey, publicKeyObj, extraSigItems);
1939
2469
  const form = new FormData3();
1940
2470
  form.append("RequestData", requestData);
1941
2471
  form.append("SIG", sig);
1942
- form.append("apk", fs6.createReadStream(filePath));
1943
- if (iconPath) {
1944
- form.append("icon", fs6.createReadStream(iconPath));
1945
- logger.info(`\u9644\u52A0 icon: ${iconPath}`);
2472
+ form.append("apk", fs6.createReadStream(mainPath));
2473
+ if (secondPath) {
2474
+ form.append("secondApk", fs6.createReadStream(secondPath));
1946
2475
  }
1947
- for (let i = 0; i < screenshotPaths.length; i++) {
1948
- form.append(`screenshot_${i + 1}`, fs6.createReadStream(screenshotPaths[i]));
1949
- logger.info(`\u9644\u52A0 screenshot_${i + 1}: ${screenshotPaths[i]}`);
2476
+ if (assets?.icon && fs6.existsSync(assets.icon)) {
2477
+ form.append("icon", fs6.createReadStream(assets.icon));
1950
2478
  }
1951
- logger.info(`POST ${_XiaomiUploader.PUSH_API}`);
1952
2479
  const response = await axios3.post(_XiaomiUploader.PUSH_API, form, {
1953
- headers: {
1954
- ...form.getHeaders()
1955
- },
2480
+ headers: form.getHeaders(),
1956
2481
  maxContentLength: Infinity,
1957
2482
  maxBodyLength: Infinity,
1958
- timeout: 6e4
1959
- // 60 秒请求超时
2483
+ timeout: 12e4
1960
2484
  });
1961
- const data = response.data;
1962
- logger.info(`\u5C0F\u7C73 API \u539F\u59CB\u54CD\u5E94: ${JSON.stringify(data)}`);
1963
- if (data?.result !== 0) {
1964
- const errMsg = data?.message || data?.msg || JSON.stringify(data);
1965
- throw new Error(`\u5C0F\u7C73 API \u8FD4\u56DE\u9519\u8BEF (result=${data?.result}): ${errMsg}`);
2485
+ if (response.data?.result !== 0) {
2486
+ throw new Error(`\u5C0F\u7C73 API \u9519\u8BEF: ${response.data?.message || JSON.stringify(response.data)}`);
1966
2487
  }
1967
- logger.info(`\u5C0F\u7C73 API \u54CD\u5E94: ${JSON.stringify(data)}`);
1968
- return data;
2488
+ return response.data;
1969
2489
  }
1970
2490
  /**
1971
2491
  * 解析公钥: 优先使用内容字段, 其次从文件路径读取
@@ -2007,7 +2527,7 @@ ${lines.join("\n")}
2007
2527
 
2008
2528
  // src/adapters/apple_store_uploader.ts
2009
2529
  import fs7 from "fs-extra";
2010
- import path4 from "path";
2530
+ import path5 from "path";
2011
2531
  import { exec as exec2 } from "child_process";
2012
2532
  import { promisify as promisify2 } from "util";
2013
2533
  import axios4 from "axios";
@@ -2173,7 +2693,7 @@ var AppleStoreUploader = class {
2173
2693
  appPassword,
2174
2694
  "-showTransferRate"
2175
2695
  ].join(" ");
2176
- logger.info(`[Transporter] Uploading: ${path4.basename(ipaPath)}`);
2696
+ logger.info(`[Transporter] Uploading: ${path5.basename(ipaPath)}`);
2177
2697
  try {
2178
2698
  const { stdout, stderr } = await execAsync(command, {
2179
2699
  timeout: 30 * 60 * 1e3,
@@ -2224,7 +2744,7 @@ Stderr: ${error.stderr || ""}`);
2224
2744
  });
2225
2745
  command = innerArgs.join(" ");
2226
2746
  }
2227
- logger.info(`[altool] Uploading: ${path4.basename(ipaPath)}`);
2747
+ logger.info(`[altool] Uploading: ${path5.basename(ipaPath)}`);
2228
2748
  logger.info(`[altool] Command prefix: ${tool.command}`);
2229
2749
  try {
2230
2750
  const { stdout, stderr } = await execAsync(command, {
@@ -2808,7 +3328,7 @@ var TestFlightUploader = class _TestFlightUploader {
2808
3328
 
2809
3329
  // src/adapters/adhoc_uploader.ts
2810
3330
  import fs9 from "fs-extra";
2811
- import path5 from "path";
3331
+ import path6 from "path";
2812
3332
  import { execSync } from "child_process";
2813
3333
  var AdHocUploader = class {
2814
3334
  name = "AdHoc";
@@ -2941,8 +3461,8 @@ var AdHocUploader = class {
2941
3461
  * @param changelog 更新日志
2942
3462
  */
2943
3463
  async createManifest(ipaPath, version, bundleId, changelog) {
2944
- const ipaFileName = path5.basename(ipaPath);
2945
- const appName = bundleId || path5.parse(ipaFileName).name;
3464
+ const ipaFileName = path6.basename(ipaPath);
3465
+ const appName = bundleId || path6.parse(ipaFileName).name;
2946
3466
  const ipaUrl = `itms-services://?action=download-manifest&url=https://your-distribution-server.com/manifests/${ipaFileName}.plist`;
2947
3467
  const manifestContent = `<?xml version="1.0" encoding="UTF-8"?>
2948
3468
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -3001,14 +3521,14 @@ var AdHocUploader = class {
3001
3521
  */
3002
3522
  async setupDistributionDirectory(ipaPath, manifestPath) {
3003
3523
  const projectRoot = process.cwd();
3004
- const distributionDir = path5.join(projectRoot, "build", "adhoc-distribution");
3524
+ const distributionDir = path6.join(projectRoot, "build", "adhoc-distribution");
3005
3525
  await fs9.ensureDir(distributionDir);
3006
- await fs9.ensureDir(path5.join(distributionDir, "apps"));
3007
- await fs9.ensureDir(path5.join(distributionDir, "manifests"));
3008
- await fs9.ensureDir(path5.join(distributionDir, "icons"));
3009
- const ipaFileName = path5.basename(ipaPath);
3010
- await fs9.copy(ipaPath, path5.join(distributionDir, "apps", ipaFileName));
3011
- await fs9.copy(manifestPath, path5.join(distributionDir, "manifests", `${ipaFileName}.plist`));
3526
+ await fs9.ensureDir(path6.join(distributionDir, "apps"));
3527
+ await fs9.ensureDir(path6.join(distributionDir, "manifests"));
3528
+ await fs9.ensureDir(path6.join(distributionDir, "icons"));
3529
+ const ipaFileName = path6.basename(ipaPath);
3530
+ await fs9.copy(ipaPath, path6.join(distributionDir, "apps", ipaFileName));
3531
+ await fs9.copy(manifestPath, path6.join(distributionDir, "manifests", `${ipaFileName}.plist`));
3012
3532
  logger.info(`Distribution directory: ${distributionDir}`);
3013
3533
  return distributionDir;
3014
3534
  }
@@ -3018,7 +3538,7 @@ var AdHocUploader = class {
3018
3538
  * @param bundleId Bundle ID
3019
3539
  */
3020
3540
  generateInstallUrl(distributionPath, bundleId) {
3021
- const manifestPath = path5.join(distributionPath, "manifests", `*.plist`);
3541
+ const manifestPath = path6.join(distributionPath, "manifests", `*.plist`);
3022
3542
  return `itms-services://?action=download-manifest&url=file://${manifestPath}`;
3023
3543
  }
3024
3544
  /**
@@ -3027,7 +3547,7 @@ var AdHocUploader = class {
3027
3547
  * @param distributionPath 分发目录
3028
3548
  */
3029
3549
  async generateQRCode(url, distributionPath) {
3030
- const qrCodePath = path5.join(distributionPath, "install-qrcode.png");
3550
+ const qrCodePath = path6.join(distributionPath, "install-qrcode.png");
3031
3551
  try {
3032
3552
  logger.info(`QR code would be saved to: ${qrCodePath}`);
3033
3553
  logger.info(`Install URL: ${url}`);
@@ -3054,8 +3574,8 @@ var AdHocUploader = class {
3054
3574
  logger.newLine();
3055
3575
  logger.info("\u{1F4E6} Distribution Files:");
3056
3576
  logger.info(` Directory: ${distributionPath}`);
3057
- logger.info(` IPA: ${path5.join(distributionPath, "apps", "*.ipa")}`);
3058
- logger.info(` Manifest: ${path5.join(distributionPath, "manifests", "*.plist")}`);
3577
+ logger.info(` IPA: ${path6.join(distributionPath, "apps", "*.ipa")}`);
3578
+ logger.info(` Manifest: ${path6.join(distributionPath, "manifests", "*.plist")}`);
3059
3579
  logger.info(` QR Code: ${qrCodePath}`);
3060
3580
  logger.newLine();
3061
3581
  logger.info("\u{1F517} Installation Link:");
@@ -3114,7 +3634,7 @@ var AdHocUploader = class {
3114
3634
 
3115
3635
  // src/adapters/harmony_uploader.ts
3116
3636
  import fs10 from "fs-extra";
3117
- import path6 from "path";
3637
+ import path7 from "path";
3118
3638
  import axios6 from "axios";
3119
3639
  import FormData5 from "form-data";
3120
3640
  var HarmonyUploader = class _HarmonyUploader {
@@ -3144,7 +3664,7 @@ var HarmonyUploader = class _HarmonyUploader {
3144
3664
  const clientSecret = harmonyConfig.auth.clientSecret;
3145
3665
  const appId = harmonyConfig.appId;
3146
3666
  const releaseType = harmonyConfig.releaseType || "draft";
3147
- const suffix = path6.extname(filePath).substring(1);
3667
+ const suffix = path7.extname(filePath).substring(1);
3148
3668
  const retryConfig = config.retry;
3149
3669
  logger.info("Starting HarmonyOS AppGallery upload...");
3150
3670
  try {
@@ -3261,7 +3781,7 @@ var HarmonyUploader = class _HarmonyUploader {
3261
3781
  * @param filePath 文件路径
3262
3782
  */
3263
3783
  async updateAppFileInfo(token, appId, authCode, filePath) {
3264
- const fileName = path6.basename(filePath);
3784
+ const fileName = path7.basename(filePath);
3265
3785
  const body = {
3266
3786
  fileType: 7,
3267
3787
  // 鸿蒙包的 fileType(需要根据华为最新文档确认)
@@ -3284,7 +3804,7 @@ var HarmonyUploader = class _HarmonyUploader {
3284
3804
 
3285
3805
  // src/adapters/oppo_uploader.ts
3286
3806
  import fs11 from "fs-extra";
3287
- import path7 from "path";
3807
+ import path8 from "path";
3288
3808
  import axios7 from "axios";
3289
3809
  import FormData6 from "form-data";
3290
3810
  import crypto4 from "crypto";
@@ -3325,7 +3845,7 @@ var OppoUploader = class _OppoUploader {
3325
3845
  * 尝试从文件名推断包名
3326
3846
  */
3327
3847
  inferPackageName(filePath) {
3328
- const fileName = path7.basename(filePath);
3848
+ const fileName = path8.basename(filePath);
3329
3849
  const match = fileName.match(/^([\w.]+?)(?:-(?:release|debug|unsigned))?\.apk$/i);
3330
3850
  if (match && match[1].includes(".")) {
3331
3851
  return match[1];
@@ -3343,110 +3863,47 @@ var OppoUploader = class _OppoUploader {
3343
3863
  }
3344
3864
  async upload(filePath, config, metadata) {
3345
3865
  const oppoConfig = config.publish.oppo;
3346
- const clientId = oppoConfig.auth.clientId;
3347
- const clientSecret = oppoConfig.auth.clientSecret;
3866
+ const accessToken = await this.getAccessToken(oppoConfig.auth.clientId, oppoConfig.auth.clientSecret);
3348
3867
  const packageName = oppoConfig.packageName || this.inferPackageName(filePath);
3349
- if (!packageName) {
3350
- return {
3351
- platform: "oppo",
3352
- success: false,
3353
- message: "\u65E0\u6CD5\u83B7\u53D6\u5305\u540D (packageName)\uFF0C\u8BF7\u5728\u914D\u7F6E\u4E2D\u6307\u5B9A\u6216\u786E\u4FDD\u6587\u4EF6\u540D\u5305\u542B\u5305\u540D",
3354
- consoleUrl: "https://open.oppomobile.com/new/ecology/app"
3355
- };
3356
- }
3357
- const retryConfig = config.retry;
3358
3868
  const consoleUrl = "https://open.oppomobile.com/new/ecology/app";
3359
- if (!fs11.existsSync(filePath)) {
3360
- return {
3361
- platform: "oppo",
3362
- success: false,
3363
- message: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}`,
3364
- consoleUrl
3365
- };
3869
+ if (!packageName) {
3870
+ return { platform: "oppo", success: false, message: "\u65E0\u6CD5\u83B7\u53D6\u5305\u540D", consoleUrl };
3366
3871
  }
3367
- logger.info("\u5F00\u59CB\u4E0A\u4F20\u5230 OPPO \u5E94\u7528\u5546\u5E97...");
3368
- logger.info(`\u6587\u4EF6: ${filePath}`);
3872
+ const artifacts = typeof metadata.artifactPath === "object" ? metadata.artifactPath : { universal: filePath || (typeof metadata.artifactPath === "string" ? metadata.artifactPath : "") };
3369
3873
  try {
3370
- const retryStrategy = createUploadRetryStrategy(retryConfig);
3371
- logger.info("\u6B63\u5728\u83B7\u53D6 access_token...");
3372
- const tokenResult = await retryStrategy.execute(
3373
- () => this.getAccessToken(clientId, clientSecret),
3374
- "OPPO authentication"
3375
- );
3376
- if (!tokenResult.success || !tokenResult.data) {
3377
- throw tokenResult.error || new Error("Failed to get access token");
3378
- }
3379
- const accessToken = tokenResult.data;
3380
- logger.info("\u6B63\u5728\u83B7\u53D6\u73B0\u6709\u5E94\u7528\u8BE6\u60C5...");
3381
- const appInfoResult = await retryStrategy.execute(
3382
- () => this.getAppInfo(accessToken, clientSecret, packageName),
3383
- "OPPO get app info"
3384
- );
3385
- const onlineAppInfo = appInfoResult.data || {};
3386
- if (onlineAppInfo) {
3387
- logger.info(
3388
- `\u83B7\u53D6\u5230\u7EBF\u4E0A\u4FE1\u606F: ${onlineAppInfo.app_name} (Category: ${onlineAppInfo.second_category_id})`
3389
- );
3390
- logger.info(`[OPPO AppInfo Full] ${JSON.stringify(onlineAppInfo)}`);
3391
- }
3392
- logger.info("\u6B63\u5728\u83B7\u53D6\u4E0A\u4F20\u5730\u5740...");
3393
- const uploadUrlResult = await retryStrategy.execute(
3394
- () => this.getUploadUrl(accessToken, clientSecret),
3395
- "OPPO get upload URL"
3396
- );
3397
- if (!uploadUrlResult.success || !uploadUrlResult.data) {
3398
- throw uploadUrlResult.error || new Error("Failed to get upload URL");
3399
- }
3400
- const { uploadUrl, sign: uploadSign } = uploadUrlResult.data;
3401
- logger.info("\u6B63\u5728\u4E0A\u4F20 APK \u6587\u4EF6...");
3402
- const uploadFileResult = await retryStrategy.execute(
3403
- () => this.uploadFile(uploadUrl, uploadSign, filePath),
3404
- "OPPO file upload"
3405
- );
3406
- if (!uploadFileResult.success || !uploadFileResult.data) {
3407
- throw uploadFileResult.error || new Error("File upload failed");
3408
- }
3409
- const rawUrl = uploadFileResult.data.url;
3410
- const fileUrl = rawUrl.replace(/^http:\/\//i, "https://").replace("storedl1.nearme.com.cn", "storedl.nearme.com.cn");
3411
- const md5 = uploadFileResult.data.md5;
3412
- logger.info("\u6B63\u5728\u63D0\u4EA4\u7248\u672C\u4FE1\u606F...");
3413
- const submitResult = await retryStrategy.execute(
3414
- () => this.submitVersion({
3415
- accessToken,
3416
- clientSecret,
3417
- packageName,
3418
- appName: oppoConfig.appName || metadata.appName || onlineAppInfo.app_name || this.inferAppName(packageName),
3419
- fileUrl,
3420
- fileMd5: md5,
3421
- versionName: metadata.version,
3422
- versionCode: metadata.buildNumber,
3423
- updateDesc: metadata.changelog || "App Update",
3424
- onlineType: oppoConfig.onlineType,
3425
- // 从配置读取
3426
- onlineAppInfo
3427
- // 传入线上信息
3428
- }),
3429
- "OPPO submit version"
3430
- );
3431
- if (!submitResult.success) {
3432
- throw submitResult.error || new Error("Submit version failed");
3433
- }
3874
+ const retryStrategy = createUploadRetryStrategy(config.retry);
3875
+ const onlineAppInfo = await this.getAppInfo(accessToken, oppoConfig.auth.clientSecret, packageName) || {};
3876
+ const uploadResults = [];
3877
+ const uploadPromises = Object.entries(artifacts).map(async ([abi, p]) => {
3878
+ if (!p || !fs11.existsSync(p)) throw new Error(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${p}`);
3879
+ logger.info(`[OPPO] \u6B63\u5728\u5904\u7406 ${abi} \u67B6\u6784\u5305...`);
3880
+ const { uploadUrl, sign } = await this.getUploadUrl(accessToken, oppoConfig.auth.clientSecret);
3881
+ const uploadFileResult = await this.uploadFile(uploadUrl, sign, p);
3882
+ const fileUrl = uploadFileResult.url.replace(/^http:\/\//i, "https://").replace("storedl1.nearme.com.cn", "storedl.nearme.com.cn");
3883
+ let cpu_code = 0;
3884
+ if (abi === "arm64-v8a") cpu_code = 3;
3885
+ else if (abi === "armeabi-v7a") cpu_code = 4;
3886
+ uploadResults.push({ url: fileUrl, md5: uploadFileResult.md5, cpu_code });
3887
+ });
3888
+ await Promise.all(uploadPromises);
3889
+ logger.info(`[OPPO] \u6B63\u5728\u63D0\u4EA4\u6D41\u6C34\u53F7\u7EC4: ${JSON.stringify(uploadResults)}`);
3890
+ await this.submitVersion({
3891
+ accessToken,
3892
+ clientSecret: oppoConfig.auth.clientSecret,
3893
+ packageName,
3894
+ appName: oppoConfig.appName || onlineAppInfo.app_name || metadata.appName || this.inferAppName(packageName),
3895
+ apkUrls: uploadResults,
3896
+ versionName: metadata.version,
3897
+ versionCode: metadata.buildNumber,
3898
+ updateDesc: metadata.changelog || "App Update",
3899
+ onlineType: oppoConfig.onlineType,
3900
+ onlineAppInfo
3901
+ });
3434
3902
  logger.success("\u4E0A\u4F20\u5230 OPPO \u5E94\u7528\u5546\u5E97\u6210\u529F!");
3435
- return {
3436
- platform: "oppo",
3437
- success: true,
3438
- message: "\u4E0A\u4F20\u6210\u529F, \u8BF7\u524D\u5F80\u540E\u53F0\u786E\u8BA4\u5E76\u63D0\u4EA4\u5BA1\u6838",
3439
- consoleUrl
3440
- };
3903
+ return { platform: "oppo", success: true, message: "\u4E0A\u4F20\u6210\u529F, \u8BF7\u786E\u8BA4", consoleUrl };
3441
3904
  } catch (error) {
3442
3905
  logger.error(`\u4E0A\u4F20\u5230 OPPO \u5931\u8D25: ${error.message}`);
3443
- return {
3444
- platform: "oppo",
3445
- success: false,
3446
- message: this.formatError(error),
3447
- error,
3448
- consoleUrl
3449
- };
3906
+ return { platform: "oppo", success: false, message: error.message, error, consoleUrl };
3450
3907
  }
3451
3908
  }
3452
3909
  /**
@@ -3567,16 +4024,7 @@ var OppoUploader = class _OppoUploader {
3567
4024
  const signParams = {
3568
4025
  access_token: params.accessToken,
3569
4026
  timestamp,
3570
- // OPPO 官方要求 apk_url 为 JSON 数组格式:[{url, md5, cpu_code}]
3571
- // cpu_code: 0=通用包, 3=arm64-v8a, 4=armeabi-v7a, 5=x86_64
3572
- apk_url: JSON.stringify([
3573
- {
3574
- url: params.fileUrl,
3575
- md5: params.fileMd5,
3576
- cpu_code: 0
3577
- // 通用包
3578
- }
3579
- ]),
4027
+ apk_url: JSON.stringify(params.apkUrls),
3580
4028
  version_name: params.versionName.split("+")[0],
3581
4029
  // 仅保留版本名部分
3582
4030
  version_code: vCode,
@@ -3678,7 +4126,7 @@ var OppoUploader = class _OppoUploader {
3678
4126
 
3679
4127
  // src/adapters/vivo_uploader.ts
3680
4128
  import fs12 from "fs-extra";
3681
- import path8 from "path";
4129
+ import path9 from "path";
3682
4130
  import axios8 from "axios";
3683
4131
  import FormData7 from "form-data";
3684
4132
  import crypto5 from "crypto";
@@ -3686,26 +4134,14 @@ var VivoUploader = class _VivoUploader {
3686
4134
  name = "vivo";
3687
4135
  /** vivo 开放 API 基础地址 */
3688
4136
  static API_BASE = "https://developer-api.vivo.com.cn/router/rest";
3689
- /**
3690
- * vivo API 签名生成
3691
- * 规则: 所有参数按 ASCII 码排序后拼接为 key=val&... 字符串,
3692
- * 以 accessSecret 为密钥进行 HMAC-SHA256 加密,结果转为小写 16 进制字符串
3693
- */
3694
4137
  generateSign(params, accessSecret) {
3695
4138
  const sorted = Object.keys(params).sort().filter((key) => params[key] !== void 0 && params[key] !== null).map((key) => `${key}=${params[key]}`).join("&");
3696
- const sign = crypto5.createHmac("sha256", accessSecret).update(sorted).digest("hex");
3697
- logger.info(`[VIVO DEBUG] \u5F85\u7B7E\u540D\u4E32: ${sorted}
3698
- -> \u7B7E\u540D\u7ED3\u679C: ${sign}`);
3699
- return sign;
4139
+ return crypto5.createHmac("sha256", accessSecret).update(sorted).digest("hex");
3700
4140
  }
3701
4141
  validateConfig(config) {
3702
4142
  if (!config?.publish?.vivo?.enable) return false;
3703
4143
  const vivo = config.publish.vivo;
3704
- if (!vivo.auth?.accessKey || !vivo.auth?.accessSecret) {
3705
- logger.error("vivo config missing: accessKey or accessSecret");
3706
- return false;
3707
- }
3708
- return true;
4144
+ return !!(vivo.auth?.accessKey && vivo.auth?.accessSecret);
3709
4145
  }
3710
4146
  canHandle(filePath) {
3711
4147
  const ext = filePath.split(".").pop()?.toLowerCase();
@@ -3715,67 +4151,51 @@ var VivoUploader = class _VivoUploader {
3715
4151
  const vivoConfig = config.publish.vivo;
3716
4152
  const accessKey = vivoConfig.auth.accessKey;
3717
4153
  const accessSecret = vivoConfig.auth.accessSecret;
3718
- const retryConfig = config.retry;
3719
4154
  const consoleUrl = "https://dev.vivo.com.cn/manage/app";
3720
- if (!fs12.existsSync(filePath)) {
3721
- return {
3722
- platform: "vivo",
3723
- success: false,
3724
- message: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}`,
3725
- consoleUrl
3726
- };
3727
- }
3728
- const fileSize = fs12.statSync(filePath).size;
3729
- logger.info("\u5F00\u59CB\u4E0A\u4F20\u5230 vivo \u5E94\u7528\u5546\u5E97...");
3730
- logger.info(`\u6587\u4EF6: ${filePath} (${(fileSize / 1024 / 1024).toFixed(2)} MB)`);
4155
+ const artifacts = typeof metadata.artifactPath === "object" ? metadata.artifactPath : { universal: filePath || (typeof metadata.artifactPath === "string" ? metadata.artifactPath : "") };
3731
4156
  try {
3732
- const retryStrategy = createUploadRetryStrategy(retryConfig);
3733
- const fileMd5 = await this.calculateFileMD5(filePath);
3734
- logger.info("\u6B63\u5728\u4E0A\u4F20 APK \u6587\u4EF6...");
3735
- const packageName = vivoConfig.packageName || "";
3736
- const uploadFileResult = await retryStrategy.execute(
3737
- () => this.uploadApk(accessKey, accessSecret, filePath, packageName, fileMd5),
3738
- "vivo file upload"
3739
- );
3740
- if (!uploadFileResult.success || !uploadFileResult.data) {
3741
- throw uploadFileResult.error || new Error("File upload failed or no serialnumber returned");
4157
+ const uploadResults = {};
4158
+ const uploadPromises = Object.entries(artifacts).map(async ([abi, p]) => {
4159
+ if (!p || !fs12.existsSync(p)) throw new Error(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${p}`);
4160
+ const md5 = await this.calculateFileMD5(p);
4161
+ logger.info(`[vivo] \u6B63\u5728\u5904\u7406 ${abi} \u67B6\u6784\u5305: ${path9.basename(p)}`);
4162
+ const serialnumber = await this.uploadApk(accessKey, accessSecret, p, vivoConfig.packageName || "", md5);
4163
+ const pkgInfo = await parsePackageInfo(p, { androidSdkPath: config.globalSettings?.androidSdkPath });
4164
+ const nativeCodes = pkgInfo?.nativeCode || [];
4165
+ const has64Bit = nativeCodes.includes("arm64-v8a") || nativeCodes.includes("x86_64") || abi === "arm64-v8a";
4166
+ const has32Bit = nativeCodes.includes("armeabi-v7a") || nativeCodes.includes("armeabi") || abi === "armeabi-v7a";
4167
+ uploadResults[abi] = { serialnumber, md5, is64Bit: has64Bit && !has32Bit };
4168
+ });
4169
+ await Promise.all(uploadPromises);
4170
+ const arm64Result = uploadResults["arm64-v8a"] || Object.values(uploadResults).find((r) => r.is64Bit);
4171
+ const arm32Result = uploadResults["armeabi-v7a"] || Object.values(uploadResults).find((r) => !r.is64Bit);
4172
+ const submitData = {
4173
+ packageName: vivoConfig.packageName,
4174
+ versionCode: parseInt(metadata.buildNumber) || 1,
4175
+ updateDesc: metadata.changelog || "App Update"
4176
+ };
4177
+ if (arm64Result) {
4178
+ submitData.serialnumber = arm64Result.serialnumber;
4179
+ submitData.fileMd5 = arm64Result.md5;
3742
4180
  }
3743
- const serialnumber = uploadFileResult.data;
3744
- logger.info("\u6B63\u5728\u63D0\u4EA4\u7248\u672C\u4FE1\u606F...");
3745
- const submitResult = await retryStrategy.execute(
3746
- () => this.submitVersion(accessKey, accessSecret, {
3747
- serialnumber,
3748
- packageName: vivoConfig.packageName,
3749
- versionCode: parseInt(metadata.buildNumber) || 1,
3750
- fileMd5,
3751
- updateDesc: metadata.changelog || "App Update"
3752
- }),
3753
- "vivo submit version"
3754
- );
3755
- if (!submitResult.success) {
3756
- throw submitResult.error || new Error("Submit version failed");
4181
+ if (arm32Result) {
4182
+ submitData.apk32 = arm32Result.serialnumber;
4183
+ submitData.md532 = arm32Result.md5;
3757
4184
  }
4185
+ if (!submitData.serialnumber && !submitData.apk32) {
4186
+ const single = Object.values(uploadResults)[0];
4187
+ submitData.serialnumber = single.serialnumber;
4188
+ submitData.fileMd5 = single.md5;
4189
+ }
4190
+ logger.info(`[vivo] \u6B63\u5728\u63D0\u4EA4\u5BA1\u6838\u7533\u62A5: ${JSON.stringify(submitData)}`);
4191
+ await this.submitVersion(accessKey, accessSecret, submitData);
3758
4192
  logger.success("\u4E0A\u4F20\u5230 vivo \u5E94\u7528\u5546\u5E97\u6210\u529F!");
3759
- return {
3760
- platform: "vivo",
3761
- success: true,
3762
- message: "\u4E0A\u4F20\u6210\u529F, \u8BF7\u524D\u5F80\u540E\u53F0\u786E\u8BA4\u5E76\u63D0\u4EA4\u5BA1\u6838",
3763
- consoleUrl
3764
- };
4193
+ return { platform: "vivo", success: true, message: "\u4E0A\u4F20\u5E76\u63D0\u4EA4\u5BA1\u6838\u6210\u529F", consoleUrl };
3765
4194
  } catch (error) {
3766
4195
  logger.error(`\u4E0A\u4F20\u5230 vivo \u5931\u8D25: ${error.message}`);
3767
- return {
3768
- platform: "vivo",
3769
- success: false,
3770
- message: this.formatError(error),
3771
- error,
3772
- consoleUrl
3773
- };
4196
+ return { platform: "vivo", success: false, message: error.message, error, consoleUrl };
3774
4197
  }
3775
4198
  }
3776
- /**
3777
- * 计算文件 MD5
3778
- */
3779
4199
  calculateFileMD5(filePath) {
3780
4200
  return new Promise((resolve, reject) => {
3781
4201
  const hash = crypto5.createHash("md5");
@@ -3785,14 +4205,9 @@ var VivoUploader = class _VivoUploader {
3785
4205
  stream.on("error", reject);
3786
4206
  });
3787
4207
  }
3788
- /**
3789
- * 上传 APK 文件并获取流水号
3790
- * 将公共参数与业务参数放入一个 multipart 请求中直接上传至 API_BASE
3791
- */
3792
4208
  async uploadApk(accessKey, accessSecret, filePath, packageName, fileMd5) {
3793
4209
  const timestamp = String(Date.now());
3794
- const paramsForSign = {
3795
- // 公共参数
4210
+ const params = {
3796
4211
  access_key: accessKey,
3797
4212
  timestamp,
3798
4213
  format: "json",
@@ -3800,41 +4215,19 @@ var VivoUploader = class _VivoUploader {
3800
4215
  sign_method: "hmac",
3801
4216
  target_app_key: "developer",
3802
4217
  method: "app.upload.apk.app",
3803
- // 业务参数
3804
4218
  packageName,
3805
4219
  fileMd5
3806
4220
  };
3807
- const sign = this.generateSign(paramsForSign, accessSecret);
4221
+ const sign = this.generateSign(params, accessSecret);
3808
4222
  const form = new FormData7();
3809
- for (const [key, value] of Object.entries(paramsForSign)) {
3810
- form.append(key, value);
3811
- }
4223
+ Object.entries(params).forEach(([k, v]) => form.append(k, v));
3812
4224
  form.append("sign", sign);
3813
- form.append("file", fs12.createReadStream(filePath), {
3814
- filename: path8.basename(filePath),
3815
- contentType: "application/vnd.android.package-archive"
3816
- });
3817
- logger.info(
3818
- `[VIVO DEBUG] \u5F00\u59CB\u4E0A\u4F20 APK (API\u8BF7\u6C42) \u53C2\u6570(\u4E0D\u542B\u6587\u4EF6): ${JSON.stringify({ ...paramsForSign, sign })}`
3819
- );
3820
- const res = await axios8.post(_VivoUploader.API_BASE, form, {
3821
- headers: form.getHeaders(),
3822
- maxContentLength: Infinity,
3823
- maxBodyLength: Infinity,
3824
- timeout: 3e5
3825
- });
3826
- logger.info(`[VIVO DEBUG] \u4E0A\u4F20\u54CD\u5E94: ${JSON.stringify(res.data)}`);
3827
- if (res.data.code !== 0) {
3828
- throw new Error(`\u6587\u4EF6\u4E0A\u4F20\u5931\u8D25: ${res.data.msg || JSON.stringify(res.data)}`);
3829
- }
3830
- const serialnumber = res.data.data?.serialnumber || res.data.data;
3831
- logger.info(`APK \u6587\u4EF6\u4E0A\u4F20\u5B8C\u6210\uFF0C\u6D41\u6C34\u53F7: ${serialnumber}`);
3832
- return serialnumber;
4225
+ form.append("file", fs12.createReadStream(filePath), { filename: path9.basename(filePath) });
4226
+ const res = await axios8.post(_VivoUploader.API_BASE, form, { headers: form.getHeaders(), timeout: 3e5 });
4227
+ if (res.data.code !== 0) throw new Error(`\u4E0A\u4F20\u5305\u5931\u8D25: ${res.data.msg}`);
4228
+ return res.data.data?.serialnumber || res.data.data;
3833
4229
  }
3834
- /**
3835
- * 提交版本更新信息
3836
- */
3837
- async submitVersion(accessKey, accessSecret, params) {
4230
+ async submitVersion(accessKey, accessSecret, data) {
3838
4231
  const timestamp = String(Date.now());
3839
4232
  const reqParams = {
3840
4233
  access_key: accessKey,
@@ -3844,51 +4237,32 @@ var VivoUploader = class _VivoUploader {
3844
4237
  sign_method: "hmac",
3845
4238
  target_app_key: "developer",
3846
4239
  method: "app.sync.update.app",
3847
- packageName: params.packageName || "",
3848
- // 文档必填
3849
- versionCode: String(params.versionCode),
3850
- // 文档必填 (Integer)
3851
- apk: params.serialnumber,
3852
- // 文档中的 apk 字段即为上传接口返回的 serialnumber
3853
- fileMd5: params.fileMd5,
3854
- // 文档必填
4240
+ packageName: data.packageName || "",
4241
+ versionCode: String(data.versionCode),
4242
+ apk: data.serialnumber || "",
4243
+ fileMd5: data.fileMd5 || "",
3855
4244
  onlineType: "1",
3856
- // 1:立即上架 2:定时上架 (必填)
3857
4245
  compatibleDevice: "1",
3858
- // 1:手机 2:手机和平板 3:平板 (必填)
3859
- updateDesc: params.updateDesc
4246
+ updateDesc: data.updateDesc
3860
4247
  };
4248
+ if (data.apk32) {
4249
+ reqParams.apk32 = data.apk32;
4250
+ if (data.md532) reqParams.md532 = data.md532;
4251
+ }
3861
4252
  reqParams.sign = this.generateSign(reqParams, accessSecret);
3862
4253
  const formData = new URLSearchParams();
3863
- for (const [key, value] of Object.entries(reqParams)) {
3864
- formData.append(key, value);
3865
- }
4254
+ Object.entries(reqParams).forEach(([k, v]) => formData.append(k, v));
3866
4255
  const res = await axios8.post(_VivoUploader.API_BASE, formData.toString(), {
3867
- headers: {
3868
- "Content-Type": "application/x-www-form-urlencoded"
3869
- },
4256
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
3870
4257
  timeout: 3e4
3871
4258
  });
3872
- if (res.data.code !== 0) {
3873
- throw new Error(`\u63D0\u4EA4\u7248\u672C\u5931\u8D25: ${res.data.msg || JSON.stringify(res.data)}`);
3874
- }
3875
- logger.info("\u7248\u672C\u4FE1\u606F\u5DF2\u63D0\u4EA4");
3876
- }
3877
- /**
3878
- * 格式化错误信息
3879
- */
3880
- formatError(error) {
3881
- if (error.response?.data) {
3882
- const data = error.response.data;
3883
- return data.msg || data.message || error.message;
3884
- }
3885
- return error.message;
4259
+ if (res.data.code !== 0) throw new Error(`\u63D0\u4EA4\u5931\u8D25: ${res.data.msg}`);
3886
4260
  }
3887
4261
  };
3888
4262
 
3889
4263
  // src/adapters/tencent_uploader.ts
3890
4264
  import fs13 from "fs-extra";
3891
- import path9 from "path";
4265
+ import path10 from "path";
3892
4266
  import axios9 from "axios";
3893
4267
  import crypto6 from "crypto";
3894
4268
  var TencentUploader = class _TencentUploader {
@@ -3920,54 +4294,71 @@ var TencentUploader = class _TencentUploader {
3920
4294
  async upload(filePath, config, metadata) {
3921
4295
  const tencentConfig = config.publish.tencent;
3922
4296
  const auth = tencentConfig.auth;
3923
- logger.info(`Tencent \u8FD0\u884C\u65F6\u914D\u7F6E: ${JSON.stringify(tencentConfig)}`);
3924
4297
  const consoleUrl = "https://app.open.qq.com";
3925
- if (!fs13.existsSync(filePath)) {
3926
- return { platform: "tencent", success: false, message: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}`, consoleUrl };
3927
- }
4298
+ const pkgName = tencentConfig.packageName || "";
4299
+ const artifacts = typeof metadata.artifactPath === "object" ? metadata.artifactPath : { universal: filePath || (typeof metadata.artifactPath === "string" ? metadata.artifactPath : "") };
3928
4300
  try {
3929
- const stats = fs13.statSync(filePath);
3930
- const fileName = path9.basename(filePath);
3931
- logger.info("\u6B63\u5728\u83B7\u53D6\u9884\u7B7E\u540D\u4E0A\u4F20\u5730\u5740...");
3932
- const pkgName = tencentConfig.packageName || "";
3933
- const uploadInfo = await this.getFileUploadInfo(auth, stats.size, fileName, pkgName);
3934
- logger.info(`\u83B7\u53D6\u6587\u4EF6\u4E0A\u4F20\u4FE1\u606F\u7ED3\u679C ${JSON.stringify(uploadInfo)}`);
3935
- logger.info("\u6B63\u5728\u4E0A\u4F20\u6587\u4EF6\u5230\u817E\u8BAF\u4E91 COS (\u7ED5\u8FC7 API \u7F51\u5173\u9650\u5236)...");
3936
- await this.uploadToCos(uploadInfo.pre_sign_url, filePath, stats.size);
3937
- logger.info("\u6B63\u5728\u83B7\u53D6\u7EBF\u4E0A\u5E94\u7528\u8BE6\u60C5\u4EE5\u4FDD\u7559\u73B0\u6709\u914D\u7F6E...");
3938
- const pkgInfo = await parsePackageInfo(filePath, {
4301
+ const uploadResults = {};
4302
+ const uploadPromises = Object.entries(artifacts).map(async ([abi, p]) => {
4303
+ if (!p || !fs13.existsSync(p)) throw new Error(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${p}`);
4304
+ const stats = fs13.statSync(p);
4305
+ const fileName = path10.basename(p);
4306
+ logger.info(`[Tencent] \u6B63\u5728\u5904\u7406 ${abi} \u67B6\u6784\u5305: ${fileName}`);
4307
+ const uploadInfo = await this.getFileUploadInfo(auth, stats.size, fileName, pkgName);
4308
+ await this.uploadToCos(uploadInfo.pre_sign_url, p, stats.size);
4309
+ const pkgInfo = await parsePackageInfo(p, {
4310
+ androidSdkPath: config.globalSettings?.androidSdkPath
4311
+ });
4312
+ const nativeCodes = pkgInfo?.nativeCode || [];
4313
+ const has64Bit = nativeCodes.includes("arm64-v8a") || nativeCodes.includes("x86_64") || abi === "arm64-v8a";
4314
+ const has32Bit = nativeCodes.includes("armeabi-v7a") || nativeCodes.includes("armeabi") || nativeCodes.includes("x86") || abi === "armeabi-v7a";
4315
+ uploadResults[abi] = {
4316
+ serial_number: uploadInfo.serial_number,
4317
+ is64Bit: has64Bit && !has32Bit,
4318
+ md5: pkgInfo?.md5 || ""
4319
+ };
4320
+ });
4321
+ await Promise.all(uploadPromises);
4322
+ logger.info("[Tencent] \u6B63\u5728\u83B7\u53D6\u7EBF\u4E0A\u5E94\u7528\u8BE6\u60C5...");
4323
+ const anyPkgPath = Object.values(artifacts)[0];
4324
+ const samplePkgInfo = await parsePackageInfo(anyPkgPath, {
3939
4325
  androidSdkPath: config.globalSettings?.androidSdkPath
3940
4326
  });
3941
- const nativeCodes = pkgInfo?.nativeCode || [];
3942
- const has64Bit = nativeCodes.includes("arm64-v8a") || nativeCodes.includes("x86_64");
3943
- const has32Bit = nativeCodes.includes("armeabi-v7a") || nativeCodes.includes("armeabi") || nativeCodes.includes("x86");
3944
- const is64Bit = has64Bit && !has32Bit;
3945
- const md5 = pkgInfo?.md5;
3946
4327
  const onlineDetail = await this.queryAppDetail(
3947
4328
  auth,
3948
- tencentConfig.packageName || pkgInfo?.packageName || ""
3949
- );
3950
- logger.info(`\u83B7\u53D6\u7EBF\u4E0A\u8BE6\u60C5\u6210\u529F: ${onlineDetail.app_name || "\u672A\u547D\u540D"}`);
3951
- logger.info("\u6B63\u5728\u63D0\u4EA4\u6D41\u6C34\u53F7\u5B8C\u6210\u5BA1\u6838\u7533\u62A5...");
3952
- const submitResult = await this.submitModifyAppInfo(
3953
- auth,
3954
- {
3955
- ...onlineDetail,
3956
- // 以前序详情为蓝本
3957
- pkg_name: tencentConfig.packageName || pkgInfo?.packageName || onlineDetail.pkg_name || "",
3958
- serial_number: uploadInfo.serial_number,
3959
- update_desc: metadata.changelog || "\u7248\u672C\u66F4\u65B0",
3960
- is64Bit,
3961
- md5
3962
- },
3963
- { timeout: tencentConfig.timeout || 6e4 }
4329
+ pkgName || samplePkgInfo?.packageName || ""
3964
4330
  );
3965
- logger.info(`\u63D0\u4EA4\u6D41\u6C34\u53F7\u5B8C\u6210\u5BA1\u6838\u7533\u62A5\u7ED3\u679C: ${JSON.stringify(submitResult)}`);
4331
+ const submitData = {
4332
+ ...onlineDetail,
4333
+ pkg_name: pkgName || samplePkgInfo?.packageName || onlineDetail.pkg_name || "",
4334
+ update_desc: metadata.changelog || "\u7248\u672C\u66F4\u65B0"
4335
+ };
4336
+ const arm64Result = uploadResults["arm64-v8a"] || Object.values(uploadResults).find((r) => r.is64Bit);
4337
+ const arm32Result = uploadResults["armeabi-v7a"] || Object.values(uploadResults).find((r) => !r.is64Bit);
4338
+ if (arm64Result) {
4339
+ submitData.apk64_serial_number = arm64Result.serial_number;
4340
+ submitData.apk64_md5 = arm64Result.md5;
4341
+ }
4342
+ if (arm32Result) {
4343
+ submitData.apk32_serial_number = arm32Result.serial_number;
4344
+ submitData.apk32_md5 = arm32Result.md5;
4345
+ }
4346
+ if (Object.keys(uploadResults).length === 1 && !submitData.apk64_serial_number && !submitData.apk32_serial_number) {
4347
+ const single = Object.values(uploadResults)[0];
4348
+ if (single.is64Bit) {
4349
+ submitData.apk64_serial_number = single.serial_number;
4350
+ submitData.apk64_md5 = single.md5;
4351
+ } else {
4352
+ submitData.apk32_serial_number = single.serial_number;
4353
+ submitData.apk32_md5 = single.md5;
4354
+ }
4355
+ }
4356
+ logger.info("[Tencent] \u6B63\u5728\u63D0\u4EA4\u5BA1\u6838\u7533\u62A5...");
4357
+ await this.submitModifyAppInfo(auth, submitData, { timeout: tencentConfig.timeout || 6e4 });
3966
4358
  logger.success("\u817E\u8BAF\u5E94\u7528\u5B9D\u4E0A\u4F20\u5E76\u63D0\u4EA4\u6210\u529F!");
3967
4359
  return { platform: "tencent", success: true, message: "\u4E0A\u4F20\u5E76\u63D0\u4EA4\u5BA1\u6838\u6210\u529F", consoleUrl };
3968
4360
  } catch (error) {
3969
- logger.error(`\u817E\u8BAF\u4E0A\u4F20\u5931\u8D25: ${JSON.stringify(error)}`);
3970
- logger.error(`\u817E\u8BAF\u4E0A\u4F20\u5931\u8D25: ${error.stack}`);
4361
+ logger.error(`\u817E\u8BAF\u4E0A\u4F20\u5931\u8D25: ${error.message}`);
3971
4362
  return { platform: "tencent", success: false, message: error.message, error, consoleUrl };
3972
4363
  }
3973
4364
  }
@@ -4081,16 +4472,22 @@ var TencentUploader = class _TencentUploader {
4081
4472
  signParams[key] = String(data[key]);
4082
4473
  }
4083
4474
  });
4084
- if (data.is64Bit) {
4085
- signParams.apk64_file_serial_number = String(data.serial_number || "");
4086
- if (data.md5) signParams.apk64_file_md5 = data.md5;
4087
- delete signParams.apk32_file_serial_number;
4088
- delete signParams.apk32_file_md5;
4089
- } else {
4090
- signParams.apk32_file_serial_number = String(data.serial_number || "");
4091
- if (data.md5) signParams.apk32_file_md5 = data.md5;
4092
- delete signParams.apk64_file_serial_number;
4093
- delete signParams.apk64_file_md5;
4475
+ if (data.apk64_serial_number) {
4476
+ signParams.apk64_file_serial_number = String(data.apk64_serial_number);
4477
+ if (data.apk64_md5) signParams.apk64_file_md5 = data.apk64_md5;
4478
+ }
4479
+ if (data.apk32_serial_number) {
4480
+ signParams.apk32_file_serial_number = String(data.apk32_serial_number);
4481
+ if (data.apk32_md5) signParams.apk32_file_md5 = data.apk32_md5;
4482
+ }
4483
+ if (!data.apk64_serial_number && !data.apk32_serial_number && data.serial_number) {
4484
+ if (data.is64Bit) {
4485
+ signParams.apk64_file_serial_number = String(data.serial_number || "");
4486
+ if (data.md5) signParams.apk64_file_md5 = data.md5;
4487
+ } else {
4488
+ signParams.apk32_file_serial_number = String(data.serial_number || "");
4489
+ if (data.md5) signParams.apk32_file_md5 = data.md5;
4490
+ }
4094
4491
  }
4095
4492
  const sign = this.generateSign(signParams, accessSecret);
4096
4493
  const formParams = {