@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/cli.js CHANGED
@@ -7,25 +7,519 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
7
7
  });
8
8
 
9
9
  // src/utils/upload-manager.ts
10
- import path9 from "path";
10
+ import path10 from "path";
11
+
12
+ // node_modules/chalk/source/vendor/ansi-styles/index.js
13
+ var ANSI_BACKGROUND_OFFSET = 10;
14
+ var wrapAnsi16 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
15
+ var wrapAnsi256 = (offset = 0) => (code) => `\x1B[${38 + offset};5;${code}m`;
16
+ var wrapAnsi16m = (offset = 0) => (red, green, blue) => `\x1B[${38 + offset};2;${red};${green};${blue}m`;
17
+ var styles = {
18
+ modifier: {
19
+ reset: [0, 0],
20
+ // 21 isn't widely supported and 22 does the same thing
21
+ bold: [1, 22],
22
+ dim: [2, 22],
23
+ italic: [3, 23],
24
+ underline: [4, 24],
25
+ overline: [53, 55],
26
+ inverse: [7, 27],
27
+ hidden: [8, 28],
28
+ strikethrough: [9, 29]
29
+ },
30
+ color: {
31
+ black: [30, 39],
32
+ red: [31, 39],
33
+ green: [32, 39],
34
+ yellow: [33, 39],
35
+ blue: [34, 39],
36
+ magenta: [35, 39],
37
+ cyan: [36, 39],
38
+ white: [37, 39],
39
+ // Bright color
40
+ blackBright: [90, 39],
41
+ gray: [90, 39],
42
+ // Alias of `blackBright`
43
+ grey: [90, 39],
44
+ // Alias of `blackBright`
45
+ redBright: [91, 39],
46
+ greenBright: [92, 39],
47
+ yellowBright: [93, 39],
48
+ blueBright: [94, 39],
49
+ magentaBright: [95, 39],
50
+ cyanBright: [96, 39],
51
+ whiteBright: [97, 39]
52
+ },
53
+ bgColor: {
54
+ bgBlack: [40, 49],
55
+ bgRed: [41, 49],
56
+ bgGreen: [42, 49],
57
+ bgYellow: [43, 49],
58
+ bgBlue: [44, 49],
59
+ bgMagenta: [45, 49],
60
+ bgCyan: [46, 49],
61
+ bgWhite: [47, 49],
62
+ // Bright color
63
+ bgBlackBright: [100, 49],
64
+ bgGray: [100, 49],
65
+ // Alias of `bgBlackBright`
66
+ bgGrey: [100, 49],
67
+ // Alias of `bgBlackBright`
68
+ bgRedBright: [101, 49],
69
+ bgGreenBright: [102, 49],
70
+ bgYellowBright: [103, 49],
71
+ bgBlueBright: [104, 49],
72
+ bgMagentaBright: [105, 49],
73
+ bgCyanBright: [106, 49],
74
+ bgWhiteBright: [107, 49]
75
+ }
76
+ };
77
+ var modifierNames = Object.keys(styles.modifier);
78
+ var foregroundColorNames = Object.keys(styles.color);
79
+ var backgroundColorNames = Object.keys(styles.bgColor);
80
+ var colorNames = [...foregroundColorNames, ...backgroundColorNames];
81
+ function assembleStyles() {
82
+ const codes = /* @__PURE__ */ new Map();
83
+ for (const [groupName, group] of Object.entries(styles)) {
84
+ for (const [styleName, style] of Object.entries(group)) {
85
+ styles[styleName] = {
86
+ open: `\x1B[${style[0]}m`,
87
+ close: `\x1B[${style[1]}m`
88
+ };
89
+ group[styleName] = styles[styleName];
90
+ codes.set(style[0], style[1]);
91
+ }
92
+ Object.defineProperty(styles, groupName, {
93
+ value: group,
94
+ enumerable: false
95
+ });
96
+ }
97
+ Object.defineProperty(styles, "codes", {
98
+ value: codes,
99
+ enumerable: false
100
+ });
101
+ styles.color.close = "\x1B[39m";
102
+ styles.bgColor.close = "\x1B[49m";
103
+ styles.color.ansi = wrapAnsi16();
104
+ styles.color.ansi256 = wrapAnsi256();
105
+ styles.color.ansi16m = wrapAnsi16m();
106
+ styles.bgColor.ansi = wrapAnsi16(ANSI_BACKGROUND_OFFSET);
107
+ styles.bgColor.ansi256 = wrapAnsi256(ANSI_BACKGROUND_OFFSET);
108
+ styles.bgColor.ansi16m = wrapAnsi16m(ANSI_BACKGROUND_OFFSET);
109
+ Object.defineProperties(styles, {
110
+ rgbToAnsi256: {
111
+ value(red, green, blue) {
112
+ if (red === green && green === blue) {
113
+ if (red < 8) {
114
+ return 16;
115
+ }
116
+ if (red > 248) {
117
+ return 231;
118
+ }
119
+ return Math.round((red - 8) / 247 * 24) + 232;
120
+ }
121
+ return 16 + 36 * Math.round(red / 255 * 5) + 6 * Math.round(green / 255 * 5) + Math.round(blue / 255 * 5);
122
+ },
123
+ enumerable: false
124
+ },
125
+ hexToRgb: {
126
+ value(hex) {
127
+ const matches = /[a-f\d]{6}|[a-f\d]{3}/i.exec(hex.toString(16));
128
+ if (!matches) {
129
+ return [0, 0, 0];
130
+ }
131
+ let [colorString] = matches;
132
+ if (colorString.length === 3) {
133
+ colorString = [...colorString].map((character) => character + character).join("");
134
+ }
135
+ const integer = Number.parseInt(colorString, 16);
136
+ return [
137
+ /* eslint-disable no-bitwise */
138
+ integer >> 16 & 255,
139
+ integer >> 8 & 255,
140
+ integer & 255
141
+ /* eslint-enable no-bitwise */
142
+ ];
143
+ },
144
+ enumerable: false
145
+ },
146
+ hexToAnsi256: {
147
+ value: (hex) => styles.rgbToAnsi256(...styles.hexToRgb(hex)),
148
+ enumerable: false
149
+ },
150
+ ansi256ToAnsi: {
151
+ value(code) {
152
+ if (code < 8) {
153
+ return 30 + code;
154
+ }
155
+ if (code < 16) {
156
+ return 90 + (code - 8);
157
+ }
158
+ let red;
159
+ let green;
160
+ let blue;
161
+ if (code >= 232) {
162
+ red = ((code - 232) * 10 + 8) / 255;
163
+ green = red;
164
+ blue = red;
165
+ } else {
166
+ code -= 16;
167
+ const remainder = code % 36;
168
+ red = Math.floor(code / 36) / 5;
169
+ green = Math.floor(remainder / 6) / 5;
170
+ blue = remainder % 6 / 5;
171
+ }
172
+ const value = Math.max(red, green, blue) * 2;
173
+ if (value === 0) {
174
+ return 30;
175
+ }
176
+ let result = 30 + (Math.round(blue) << 2 | Math.round(green) << 1 | Math.round(red));
177
+ if (value === 2) {
178
+ result += 60;
179
+ }
180
+ return result;
181
+ },
182
+ enumerable: false
183
+ },
184
+ rgbToAnsi: {
185
+ value: (red, green, blue) => styles.ansi256ToAnsi(styles.rgbToAnsi256(red, green, blue)),
186
+ enumerable: false
187
+ },
188
+ hexToAnsi: {
189
+ value: (hex) => styles.ansi256ToAnsi(styles.hexToAnsi256(hex)),
190
+ enumerable: false
191
+ }
192
+ });
193
+ return styles;
194
+ }
195
+ var ansiStyles = assembleStyles();
196
+ var ansi_styles_default = ansiStyles;
197
+
198
+ // node_modules/chalk/source/vendor/supports-color/index.js
199
+ import process2 from "process";
200
+ import os from "os";
201
+ import tty from "tty";
202
+ function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process2.argv) {
203
+ const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
204
+ const position = argv.indexOf(prefix + flag);
205
+ const terminatorPosition = argv.indexOf("--");
206
+ return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
207
+ }
208
+ var { env } = process2;
209
+ var flagForceColor;
210
+ if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
211
+ flagForceColor = 0;
212
+ } else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
213
+ flagForceColor = 1;
214
+ }
215
+ function envForceColor() {
216
+ if ("FORCE_COLOR" in env) {
217
+ if (env.FORCE_COLOR === "true") {
218
+ return 1;
219
+ }
220
+ if (env.FORCE_COLOR === "false") {
221
+ return 0;
222
+ }
223
+ return env.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
224
+ }
225
+ }
226
+ function translateLevel(level) {
227
+ if (level === 0) {
228
+ return false;
229
+ }
230
+ return {
231
+ level,
232
+ hasBasic: true,
233
+ has256: level >= 2,
234
+ has16m: level >= 3
235
+ };
236
+ }
237
+ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
238
+ const noFlagForceColor = envForceColor();
239
+ if (noFlagForceColor !== void 0) {
240
+ flagForceColor = noFlagForceColor;
241
+ }
242
+ const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
243
+ if (forceColor === 0) {
244
+ return 0;
245
+ }
246
+ if (sniffFlags) {
247
+ if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
248
+ return 3;
249
+ }
250
+ if (hasFlag("color=256")) {
251
+ return 2;
252
+ }
253
+ }
254
+ if ("TF_BUILD" in env && "AGENT_NAME" in env) {
255
+ return 1;
256
+ }
257
+ if (haveStream && !streamIsTTY && forceColor === void 0) {
258
+ return 0;
259
+ }
260
+ const min = forceColor || 0;
261
+ if (env.TERM === "dumb") {
262
+ return min;
263
+ }
264
+ if (process2.platform === "win32") {
265
+ const osRelease = os.release().split(".");
266
+ if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
267
+ return Number(osRelease[2]) >= 14931 ? 3 : 2;
268
+ }
269
+ return 1;
270
+ }
271
+ if ("CI" in env) {
272
+ if (["GITHUB_ACTIONS", "GITEA_ACTIONS", "CIRCLECI"].some((key) => key in env)) {
273
+ return 3;
274
+ }
275
+ if (["TRAVIS", "APPVEYOR", "GITLAB_CI", "BUILDKITE", "DRONE"].some((sign) => sign in env) || env.CI_NAME === "codeship") {
276
+ return 1;
277
+ }
278
+ return min;
279
+ }
280
+ if ("TEAMCITY_VERSION" in env) {
281
+ return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
282
+ }
283
+ if (env.COLORTERM === "truecolor") {
284
+ return 3;
285
+ }
286
+ if (env.TERM === "xterm-kitty") {
287
+ return 3;
288
+ }
289
+ if (env.TERM === "xterm-ghostty") {
290
+ return 3;
291
+ }
292
+ if (env.TERM === "wezterm") {
293
+ return 3;
294
+ }
295
+ if ("TERM_PROGRAM" in env) {
296
+ const version = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
297
+ switch (env.TERM_PROGRAM) {
298
+ case "iTerm.app": {
299
+ return version >= 3 ? 3 : 2;
300
+ }
301
+ case "Apple_Terminal": {
302
+ return 2;
303
+ }
304
+ }
305
+ }
306
+ if (/-256(color)?$/i.test(env.TERM)) {
307
+ return 2;
308
+ }
309
+ if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
310
+ return 1;
311
+ }
312
+ if ("COLORTERM" in env) {
313
+ return 1;
314
+ }
315
+ return min;
316
+ }
317
+ function createSupportsColor(stream, options = {}) {
318
+ const level = _supportsColor(stream, {
319
+ streamIsTTY: stream && stream.isTTY,
320
+ ...options
321
+ });
322
+ return translateLevel(level);
323
+ }
324
+ var supportsColor = {
325
+ stdout: createSupportsColor({ isTTY: tty.isatty(1) }),
326
+ stderr: createSupportsColor({ isTTY: tty.isatty(2) })
327
+ };
328
+ var supports_color_default = supportsColor;
329
+
330
+ // node_modules/chalk/source/utilities.js
331
+ function stringReplaceAll(string, substring, replacer) {
332
+ let index = string.indexOf(substring);
333
+ if (index === -1) {
334
+ return string;
335
+ }
336
+ const substringLength = substring.length;
337
+ let endIndex = 0;
338
+ let returnValue = "";
339
+ do {
340
+ returnValue += string.slice(endIndex, index) + substring + replacer;
341
+ endIndex = index + substringLength;
342
+ index = string.indexOf(substring, endIndex);
343
+ } while (index !== -1);
344
+ returnValue += string.slice(endIndex);
345
+ return returnValue;
346
+ }
347
+ function stringEncaseCRLFWithFirstIndex(string, prefix, postfix, index) {
348
+ let endIndex = 0;
349
+ let returnValue = "";
350
+ do {
351
+ const gotCR = string[index - 1] === "\r";
352
+ returnValue += string.slice(endIndex, gotCR ? index - 1 : index) + prefix + (gotCR ? "\r\n" : "\n") + postfix;
353
+ endIndex = index + 1;
354
+ index = string.indexOf("\n", endIndex);
355
+ } while (index !== -1);
356
+ returnValue += string.slice(endIndex);
357
+ return returnValue;
358
+ }
359
+
360
+ // node_modules/chalk/source/index.js
361
+ var { stdout: stdoutColor, stderr: stderrColor } = supports_color_default;
362
+ var GENERATOR = /* @__PURE__ */ Symbol("GENERATOR");
363
+ var STYLER = /* @__PURE__ */ Symbol("STYLER");
364
+ var IS_EMPTY = /* @__PURE__ */ Symbol("IS_EMPTY");
365
+ var levelMapping = [
366
+ "ansi",
367
+ "ansi",
368
+ "ansi256",
369
+ "ansi16m"
370
+ ];
371
+ var styles2 = /* @__PURE__ */ Object.create(null);
372
+ var applyOptions = (object, options = {}) => {
373
+ if (options.level && !(Number.isInteger(options.level) && options.level >= 0 && options.level <= 3)) {
374
+ throw new Error("The `level` option should be an integer from 0 to 3");
375
+ }
376
+ const colorLevel = stdoutColor ? stdoutColor.level : 0;
377
+ object.level = options.level === void 0 ? colorLevel : options.level;
378
+ };
379
+ var chalkFactory = (options) => {
380
+ const chalk2 = (...strings) => strings.join(" ");
381
+ applyOptions(chalk2, options);
382
+ Object.setPrototypeOf(chalk2, createChalk.prototype);
383
+ return chalk2;
384
+ };
385
+ function createChalk(options) {
386
+ return chalkFactory(options);
387
+ }
388
+ Object.setPrototypeOf(createChalk.prototype, Function.prototype);
389
+ for (const [styleName, style] of Object.entries(ansi_styles_default)) {
390
+ styles2[styleName] = {
391
+ get() {
392
+ const builder = createBuilder(this, createStyler(style.open, style.close, this[STYLER]), this[IS_EMPTY]);
393
+ Object.defineProperty(this, styleName, { value: builder });
394
+ return builder;
395
+ }
396
+ };
397
+ }
398
+ styles2.visible = {
399
+ get() {
400
+ const builder = createBuilder(this, this[STYLER], true);
401
+ Object.defineProperty(this, "visible", { value: builder });
402
+ return builder;
403
+ }
404
+ };
405
+ var getModelAnsi = (model, level, type, ...arguments_) => {
406
+ if (model === "rgb") {
407
+ if (level === "ansi16m") {
408
+ return ansi_styles_default[type].ansi16m(...arguments_);
409
+ }
410
+ if (level === "ansi256") {
411
+ return ansi_styles_default[type].ansi256(ansi_styles_default.rgbToAnsi256(...arguments_));
412
+ }
413
+ return ansi_styles_default[type].ansi(ansi_styles_default.rgbToAnsi(...arguments_));
414
+ }
415
+ if (model === "hex") {
416
+ return getModelAnsi("rgb", level, type, ...ansi_styles_default.hexToRgb(...arguments_));
417
+ }
418
+ return ansi_styles_default[type][model](...arguments_);
419
+ };
420
+ var usedModels = ["rgb", "hex", "ansi256"];
421
+ for (const model of usedModels) {
422
+ styles2[model] = {
423
+ get() {
424
+ const { level } = this;
425
+ return function(...arguments_) {
426
+ const styler = createStyler(getModelAnsi(model, levelMapping[level], "color", ...arguments_), ansi_styles_default.color.close, this[STYLER]);
427
+ return createBuilder(this, styler, this[IS_EMPTY]);
428
+ };
429
+ }
430
+ };
431
+ const bgModel = "bg" + model[0].toUpperCase() + model.slice(1);
432
+ styles2[bgModel] = {
433
+ get() {
434
+ const { level } = this;
435
+ return function(...arguments_) {
436
+ const styler = createStyler(getModelAnsi(model, levelMapping[level], "bgColor", ...arguments_), ansi_styles_default.bgColor.close, this[STYLER]);
437
+ return createBuilder(this, styler, this[IS_EMPTY]);
438
+ };
439
+ }
440
+ };
441
+ }
442
+ var proto = Object.defineProperties(() => {
443
+ }, {
444
+ ...styles2,
445
+ level: {
446
+ enumerable: true,
447
+ get() {
448
+ return this[GENERATOR].level;
449
+ },
450
+ set(level) {
451
+ this[GENERATOR].level = level;
452
+ }
453
+ }
454
+ });
455
+ var createStyler = (open, close, parent) => {
456
+ let openAll;
457
+ let closeAll;
458
+ if (parent === void 0) {
459
+ openAll = open;
460
+ closeAll = close;
461
+ } else {
462
+ openAll = parent.openAll + open;
463
+ closeAll = close + parent.closeAll;
464
+ }
465
+ return {
466
+ open,
467
+ close,
468
+ openAll,
469
+ closeAll,
470
+ parent
471
+ };
472
+ };
473
+ var createBuilder = (self, _styler, _isEmpty) => {
474
+ const builder = (...arguments_) => applyStyle(builder, arguments_.length === 1 ? "" + arguments_[0] : arguments_.join(" "));
475
+ Object.setPrototypeOf(builder, proto);
476
+ builder[GENERATOR] = self;
477
+ builder[STYLER] = _styler;
478
+ builder[IS_EMPTY] = _isEmpty;
479
+ return builder;
480
+ };
481
+ var applyStyle = (self, string) => {
482
+ if (self.level <= 0 || !string) {
483
+ return self[IS_EMPTY] ? "" : string;
484
+ }
485
+ let styler = self[STYLER];
486
+ if (styler === void 0) {
487
+ return string;
488
+ }
489
+ const { openAll, closeAll } = styler;
490
+ if (string.includes("\x1B")) {
491
+ while (styler !== void 0) {
492
+ string = stringReplaceAll(string, styler.close, styler.open);
493
+ styler = styler.parent;
494
+ }
495
+ }
496
+ const lfIndex = string.indexOf("\n");
497
+ if (lfIndex !== -1) {
498
+ string = stringEncaseCRLFWithFirstIndex(string, closeAll, openAll, lfIndex);
499
+ }
500
+ return openAll + string + closeAll;
501
+ };
502
+ Object.defineProperties(createChalk.prototype, styles2);
503
+ var chalk = createChalk();
504
+ var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
505
+ var source_default = chalk;
11
506
 
12
507
  // src/utils/logger.ts
13
- import chalk from "chalk";
14
508
  var ConsoleLogger = class {
15
509
  info(message) {
16
- console.log(chalk.blue("\u2139\uFE0F " + message));
510
+ console.log(source_default.blue("\u2139\uFE0F " + message));
17
511
  }
18
512
  success(message) {
19
- console.log(chalk.green("\u2705 " + message));
513
+ console.log(source_default.green("\u2705 " + message));
20
514
  }
21
515
  warn(message) {
22
- console.log(chalk.yellow("\u26A0\uFE0F " + message));
516
+ console.log(source_default.yellow("\u26A0\uFE0F " + message));
23
517
  }
24
518
  error(message) {
25
- console.log(chalk.red("\u274C " + message));
519
+ console.log(source_default.red("\u274C " + message));
26
520
  }
27
521
  title(message) {
28
- console.log(chalk.bold.cyan("\n" + message + "\n"));
522
+ console.log(source_default.bold.cyan("\n" + message + "\n"));
29
523
  }
30
524
  newLine() {
31
525
  console.log("");
@@ -134,12 +628,7 @@ var RetryStrategy = class {
134
628
  logger.warn(`${operationName} failed (Attempt ${attempt}): ${errorMessage} [${category}]`);
135
629
  if (!this.shouldRetry(error, attempt)) {
136
630
  logger.error(`${operationName} failed with non-retryable error: ${errorMessage}`);
137
- return {
138
- success: false,
139
- error,
140
- totalAttempts: attempt,
141
- lastError: errorMessage
142
- };
631
+ throw error;
143
632
  }
144
633
  if (attempt <= this.config.maxRetries) {
145
634
  const delayMs = this.calculateDelay(attempt - 1);
@@ -150,12 +639,7 @@ var RetryStrategy = class {
150
639
  }
151
640
  }
152
641
  }
153
- return {
154
- success: false,
155
- error: lastError || new Error(`${operationName} failed after all retries`),
156
- totalAttempts: attempt,
157
- lastError: lastError?.message
158
- };
642
+ throw lastError || new Error(`${operationName} failed after all retries`);
159
643
  }
160
644
  };
161
645
  var DEFAULT_RETRY_CONFIG = {
@@ -699,27 +1183,39 @@ var HuaweiUploader = class _HuaweiUploader {
699
1183
  clientId,
700
1184
  appId
701
1185
  };
702
- logger.info("Requesting upload URL...");
703
- const uploadInfoResult = await retryStrategy.execute(
704
- () => this.getUploadUrl(ctx, suffix),
705
- "Huawei get upload URL"
706
- );
707
- if (!uploadInfoResult.success || !uploadInfoResult.data) {
708
- throw uploadInfoResult.error || new Error("Failed to get upload URL");
709
- }
710
- const uploadInfo = uploadInfoResult.data;
711
- logger.info("Uploading file (this may take a while)...");
712
- const uploadFileResult = await retryStrategy.execute(
713
- () => this.uploadFile(uploadInfo.uploadUrl, uploadInfo.authCode, filePath),
714
- "Huawei file upload"
715
- );
716
- if (!uploadFileResult.success || !uploadFileResult.data) {
717
- throw uploadFileResult.error || new Error("File upload failed");
1186
+ const artifacts = typeof metadata.artifactPath === "object" ? metadata.artifactPath : { universal: filePath || metadata.artifactPath || "" };
1187
+ logger.info(`[Huawei] \u5904\u7406\u4EA7\u7269\u6570\u91CF: ${Object.keys(artifacts).length}`);
1188
+ const uploadResults = [];
1189
+ const uploadPromises = Object.entries(artifacts).map(async ([abi, p]) => {
1190
+ if (!p || !fs4.existsSync(p)) return;
1191
+ const currentSuffix = path2.extname(p).substring(1);
1192
+ logger.info(`[Huawei] \u6B63\u5728\u4E0A\u4F20 ${abi} \u5305: ${path2.basename(p)}`);
1193
+ const uploadInfo = await retryStrategy.execute(
1194
+ () => this.getUploadUrl(ctx, currentSuffix),
1195
+ `Huawei get upload URL for ${abi}`
1196
+ );
1197
+ if (!uploadInfo.success || !uploadInfo.data) {
1198
+ throw uploadInfo.error || new Error(`Failed to get upload URL for ${abi}`);
1199
+ }
1200
+ const uploadFileRes = await retryStrategy.execute(
1201
+ () => this.executeUploadRequest(uploadInfo.data.uploadUrl, uploadInfo.data.authCode, p),
1202
+ `Huawei upload file for ${abi}`
1203
+ );
1204
+ if (!uploadFileRes.success || !uploadFileRes.data || uploadFileRes.data.length === 0) {
1205
+ throw uploadFileRes.error || new Error(`File upload failed for ${abi}`);
1206
+ }
1207
+ uploadResults.push({
1208
+ fileName: path2.basename(p),
1209
+ fileDestUrl: uploadFileRes.data[0].fileDestUrl
1210
+ });
1211
+ });
1212
+ await Promise.all(uploadPromises);
1213
+ if (uploadResults.length === 0) {
1214
+ throw new Error("No files were successfully uploaded");
718
1215
  }
719
- const fileDestUrl = uploadFileResult.data.fileDestUrl;
720
- logger.info("Linking file to app...");
1216
+ logger.info(`[Huawei] \u6B63\u5728\u5173\u8054 ${uploadResults.length} \u4E2A\u6587\u4EF6\u5230\u5E94\u7528...`);
721
1217
  const updateInfoResult = await retryStrategy.execute(
722
- () => this.updateAppFileInfo(ctx, fileDestUrl, filePath),
1218
+ () => this.updateAppFileInfo(ctx, uploadResults, filePath),
723
1219
  "Huawei update file info"
724
1220
  );
725
1221
  if (!updateInfoResult.success) {
@@ -923,14 +1419,17 @@ var HuaweiUploader = class _HuaweiUploader {
923
1419
  throw new Error("Upload succeeded but fileDestUrl not found in response");
924
1420
  }
925
1421
  /** 更新应用文件信息 */
926
- async updateAppFileInfo(ctx, fileDestUrl, filePath) {
927
- const fileName = path2.basename(filePath);
928
- const fileType = path2.extname(filePath).toLowerCase() === ".aab" ? 7 : 5;
1422
+ async updateAppFileInfo(ctx, files, anyFilePath) {
1423
+ const hasAab = files.some((f) => f.fileName.toLowerCase().endsWith(".aab"));
1424
+ const fileType = hasAab ? 7 /* AAB */ : 5 /* APP_PACKAGE */;
929
1425
  const res = await axios2.put(
930
1426
  `${ctx.apiRoot}/app-file-info`,
931
1427
  {
932
1428
  fileType,
933
- files: [{ fileName, fileDestUrl }]
1429
+ files: files.map((f) => ({
1430
+ fileName: f.fileName,
1431
+ fileDestUrl: f.fileDestUrl
1432
+ }))
934
1433
  },
935
1434
  {
936
1435
  params: { appId: ctx.appId },
@@ -1100,6 +1599,7 @@ var HuaweiUploader = class _HuaweiUploader {
1100
1599
 
1101
1600
  // src/adapters/xiaomi_uploader.ts
1102
1601
  import fs5 from "fs-extra";
1602
+ import path3 from "path";
1103
1603
  import axios3 from "axios";
1104
1604
  import FormData3 from "form-data";
1105
1605
  import crypto3 from "crypto";
@@ -1350,80 +1850,39 @@ ${lines.join("\n")}
1350
1850
  const xiaomiConfig = config.publish?.xiaomi;
1351
1851
  const { userName, privateKey, publicKeyPath } = xiaomiConfig.auth;
1352
1852
  const updateDesc = metadata.changelog || xiaomiConfig?.description || "App Update";
1353
- const retryConfig = config.retry;
1354
- if (!fs5.existsSync(filePath)) {
1355
- return {
1356
- platform: "xiaomi",
1357
- success: false,
1358
- message: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}`
1359
- };
1360
- }
1853
+ const artifacts = typeof metadata.artifactPath === "object" ? metadata.artifactPath : { universal: filePath || (typeof metadata.artifactPath === "string" ? metadata.artifactPath : "") };
1361
1854
  const resolvedPublicKey = await this.resolvePublicKey(publicKeyPath);
1362
1855
  if (!resolvedPublicKey) {
1363
- return {
1364
- platform: "xiaomi",
1365
- success: false,
1366
- message: "\u65E0\u6CD5\u83B7\u53D6 RSA \u516C\u94A5: publicKeyPath \u672A\u63D0\u4F9B\u6216\u6587\u4EF6\u4E0D\u5B58\u5728"
1367
- };
1368
- }
1369
- const packageName = xiaomiConfig.packageName || this.inferPackageName(filePath);
1370
- if (!packageName) {
1371
- return {
1372
- platform: "xiaomi",
1373
- success: false,
1374
- message: "\u7F3A\u5C11 packageName \u914D\u7F6E, \u4E14\u65E0\u6CD5\u4ECE\u6587\u4EF6\u540D\u63A8\u65AD\u5E94\u7528\u5305\u540D"
1375
- };
1856
+ throw new Error("\u65E0\u6CD5\u83B7\u53D6 RSA \u516C\u94A5");
1376
1857
  }
1377
- const appName = xiaomiConfig.appName || this.inferAppName(packageName);
1378
- if (!appName) {
1379
- return {
1380
- platform: "xiaomi",
1381
- success: false,
1382
- message: "\u7F3A\u5C11 appName \u914D\u7F6E, \u4E14\u65E0\u6CD5\u4ECE\u5305\u540D\u63A8\u65AD\u5E94\u7528\u540D\u79F0"
1383
- };
1384
- }
1385
- logger.info("\u5F00\u59CB\u4E0A\u4F20\u5230\u5C0F\u7C73\u5E94\u7528\u5546\u5E97...");
1386
- logger.info(`\u6587\u4EF6: ${filePath}`);
1387
- logger.info(`\u5305\u540D: ${packageName}`);
1388
- logger.info(`\u5E94\u7528\u540D: ${appName}`);
1389
1858
  const publicKeyObj = this.extractPublicKey(resolvedPublicKey);
1390
- const configUploadType = xiaomiConfig.uploadType || "auto";
1391
- let uploadType;
1392
- let existingAppInfo = null;
1393
- if (configUploadType === "auto") {
1394
- logger.info("\u6B63\u5728\u67E5\u8BE2\u5E94\u7528\u4FE1\u606F...");
1395
- const status = await this.queryAppStatus(userName, packageName, privateKey, publicKeyObj);
1396
- uploadType = status.type;
1397
- existingAppInfo = status.appInfo;
1398
- } else {
1399
- uploadType = configUploadType;
1400
- }
1401
- const uploadTypeDesc = uploadType === "update" ? "\u5E94\u7528\u66F4\u65B0 (synchroType=1)" : "\u65B0\u589E\u5E94\u7528 (synchroType=0)";
1402
- logger.info(`\u4E0A\u4F20\u7C7B\u578B: ${uploadTypeDesc}`);
1403
- if (uploadType === "new" && xiaomiConfig.uploadType === "new") {
1404
- const missing = [];
1405
- if (xiaomiConfig.category == null) missing.push("category (\u5206\u7C7BID)");
1406
- if (!xiaomiConfig.desc) missing.push("desc (\u5E94\u7528\u4ECB\u7ECD)");
1407
- if (missing.length > 0) {
1408
- return {
1409
- platform: "xiaomi",
1410
- success: false,
1411
- 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`
1412
- };
1413
- }
1414
- }
1859
+ const anyPath = Object.values(artifacts)[0];
1860
+ const packageName = xiaomiConfig.packageName || this.inferPackageName(anyPath);
1861
+ if (!packageName) throw new Error("\u7F3A\u5C11 packageName");
1862
+ const appName = xiaomiConfig.appName || this.inferAppName(packageName);
1415
1863
  try {
1416
- const retryStrategy = createUploadRetryStrategy(retryConfig);
1864
+ logger.info(`\u5F00\u59CB\u4E0A\u4F20\u5230\u5C0F\u7C73\u5E94\u7528\u5546\u5E97 (\u5305\u542B ${Object.keys(artifacts).length} \u4E2A\u5305)...`);
1865
+ let uploadType = xiaomiConfig.uploadType || "auto";
1866
+ let finalUploadType;
1867
+ let existingAppInfo = null;
1868
+ if (uploadType === "auto") {
1869
+ const status = await this.queryAppStatus(userName, packageName, privateKey, publicKeyObj);
1870
+ finalUploadType = status.type;
1871
+ existingAppInfo = status.appInfo;
1872
+ } else {
1873
+ finalUploadType = uploadType;
1874
+ }
1875
+ const retryStrategy = createUploadRetryStrategy(config.retry);
1417
1876
  const result = await retryStrategy.execute(
1418
1877
  () => this.performUpload({
1419
- filePath,
1878
+ artifacts,
1420
1879
  userName,
1421
1880
  privateKey,
1422
1881
  publicKey: resolvedPublicKey,
1882
+ uploadType: finalUploadType,
1423
1883
  packageName,
1424
- appName,
1884
+ appName: appName || "",
1425
1885
  updateDesc,
1426
- uploadType,
1427
1886
  category: xiaomiConfig.category || existingAppInfo?.category,
1428
1887
  keyWords: xiaomiConfig.keyWords || existingAppInfo?.keyWords,
1429
1888
  desc: xiaomiConfig.desc || existingAppInfo?.desc,
@@ -1433,50 +1892,19 @@ ${lines.join("\n")}
1433
1892
  }),
1434
1893
  "Xiaomi push upload"
1435
1894
  );
1436
- if (!result.success) {
1437
- throw result.error || new Error("Upload failed");
1438
- }
1439
- logger.success("\u4E0A\u4F20\u5230\u5C0F\u7C73\u5E94\u7528\u5546\u5E97\u6210\u529F!");
1440
1895
  return {
1441
1896
  platform: "xiaomi",
1442
1897
  success: true,
1443
- message: "\u4E0A\u4F20\u6210\u529F, \u7B49\u5F85\u5BA1\u6838",
1444
- data: result.data,
1898
+ message: "\u4E0A\u4F20\u6210\u529F",
1445
1899
  consoleUrl: "https://dev.mi.com/distribute/app/details"
1446
1900
  };
1447
1901
  } catch (error) {
1448
- logger.error(`\u4E0A\u4F20\u5230\u5C0F\u7C73\u5931\u8D25: ${error.message}`);
1449
- if (error.response?.data) {
1450
- logger.info(`\u9519\u8BEF\u8BE6\u60C5: ${JSON.stringify(error.response.data)}`);
1451
- }
1452
- return {
1453
- platform: "xiaomi",
1454
- success: false,
1455
- message: error.message,
1456
- error
1457
- };
1902
+ logger.error(`\u5C0F\u7C73\u4E0A\u4F20\u5931\u8D25: ${error.message}`);
1903
+ return { platform: "xiaomi", success: false, message: error.message, error };
1458
1904
  }
1459
1905
  }
1460
- /**
1461
- * 执行实际的上传操作
1462
- */
1463
1906
  async performUpload(params) {
1464
- const {
1465
- filePath,
1466
- userName,
1467
- privateKey,
1468
- publicKey,
1469
- packageName,
1470
- appName,
1471
- updateDesc,
1472
- uploadType,
1473
- category,
1474
- keyWords,
1475
- desc,
1476
- brief,
1477
- privacyUrl,
1478
- assets
1479
- } = params;
1907
+ const { artifacts, userName, privateKey, publicKey, packageName, appName, updateDesc, uploadType, category, keyWords, desc, brief, privacyUrl, assets } = params;
1480
1908
  const publicKeyObj = this.extractPublicKey(publicKey);
1481
1909
  const requestData = this.buildRequestData({
1482
1910
  userName,
@@ -1490,67 +1918,55 @@ ${lines.join("\n")}
1490
1918
  brief,
1491
1919
  privacyUrl
1492
1920
  });
1493
- logger.info("RequestData \u5DF2\u6784\u5EFA");
1494
- logger.info(`RequestData: ${requestData}`);
1495
1921
  const requestDataMD5 = this.calculateStringMD5(requestData);
1496
- const apkFileMD5 = await this.calculateFileMD5(filePath);
1497
- logger.info(`APK MD5: ${apkFileMD5}`);
1922
+ let mainPath;
1923
+ let secondPath = void 0;
1924
+ const artifactEntries = Object.entries(artifacts);
1925
+ const arm64 = artifactEntries.find(([abi]) => abi === "arm64-v8a");
1926
+ const arm32 = artifactEntries.find(([abi]) => abi === "armeabi-v7a");
1927
+ if (arm64 && arm32) {
1928
+ mainPath = arm64[1];
1929
+ secondPath = arm32[1];
1930
+ logger.info(`[Xiaomi] \u8BC6\u522B\u5230\u591A\u67B6\u6784\u5206\u5305: \u4E3B\u5305(64\u4F4D)=${path3.basename(mainPath)}, \u6B21\u5305(32\u4F4D)=${path3.basename(secondPath)}`);
1931
+ } else {
1932
+ mainPath = artifactEntries[0][1];
1933
+ if (artifactEntries.length > 1) {
1934
+ secondPath = artifactEntries[1][1];
1935
+ logger.info(`[Xiaomi] \u8BC6\u522B\u5230\u591A\u4E2A\u5305\uFF08\u975E\u6807\u51C6\u540D\uFF09\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u987A\u5E8F: \u4E3B\u5305=${path3.basename(mainPath)}, \u6B21\u5305=${path3.basename(secondPath)}`);
1936
+ } else {
1937
+ logger.info(`[Xiaomi] \u4F7F\u7528\u5355\u5305\u6A21\u5F0F: ${path3.basename(mainPath)}`);
1938
+ }
1939
+ }
1940
+ const mainMD5 = await this.calculateFileMD5(mainPath);
1498
1941
  const extraSigItems = [];
1499
- let iconPath;
1942
+ if (secondPath) {
1943
+ const secondMD5 = await this.calculateFileMD5(secondPath);
1944
+ extraSigItems.push({ name: "secondApk", hash: secondMD5 });
1945
+ }
1500
1946
  if (assets?.icon && fs5.existsSync(assets.icon)) {
1501
- iconPath = assets.icon;
1502
- const iconMD5 = await this.calculateFileMD5(iconPath);
1503
- extraSigItems.push({ name: "icon", hash: iconMD5 });
1504
- }
1505
- const screenshotPaths = [];
1506
- if (assets?.screenshots) {
1507
- for (let i = 0; i < assets.screenshots.length && i < 4; i++) {
1508
- const ssPath = assets.screenshots[i];
1509
- if (fs5.existsSync(ssPath)) {
1510
- screenshotPaths.push(ssPath);
1511
- const ssMD5 = await this.calculateFileMD5(ssPath);
1512
- extraSigItems.push({ name: `screenshot_${i + 1}`, hash: ssMD5 });
1513
- }
1514
- }
1947
+ extraSigItems.push({ name: "icon", hash: await this.calculateFileMD5(assets.icon) });
1515
1948
  }
1516
- const sig = this.generateUploadSIG(
1517
- requestDataMD5,
1518
- apkFileMD5,
1519
- privateKey,
1520
- publicKeyObj,
1521
- extraSigItems.length > 0 ? extraSigItems : void 0
1522
- );
1523
- logger.info("SIG \u7B7E\u540D\u5DF2\u751F\u6210");
1949
+ const sig = this.generateUploadSIG(requestDataMD5, mainMD5, privateKey, publicKeyObj, extraSigItems);
1524
1950
  const form = new FormData3();
1525
1951
  form.append("RequestData", requestData);
1526
1952
  form.append("SIG", sig);
1527
- form.append("apk", fs5.createReadStream(filePath));
1528
- if (iconPath) {
1529
- form.append("icon", fs5.createReadStream(iconPath));
1530
- logger.info(`\u9644\u52A0 icon: ${iconPath}`);
1953
+ form.append("apk", fs5.createReadStream(mainPath));
1954
+ if (secondPath) {
1955
+ form.append("secondApk", fs5.createReadStream(secondPath));
1531
1956
  }
1532
- for (let i = 0; i < screenshotPaths.length; i++) {
1533
- form.append(`screenshot_${i + 1}`, fs5.createReadStream(screenshotPaths[i]));
1534
- logger.info(`\u9644\u52A0 screenshot_${i + 1}: ${screenshotPaths[i]}`);
1957
+ if (assets?.icon && fs5.existsSync(assets.icon)) {
1958
+ form.append("icon", fs5.createReadStream(assets.icon));
1535
1959
  }
1536
- logger.info(`POST ${_XiaomiUploader.PUSH_API}`);
1537
1960
  const response = await axios3.post(_XiaomiUploader.PUSH_API, form, {
1538
- headers: {
1539
- ...form.getHeaders()
1540
- },
1961
+ headers: form.getHeaders(),
1541
1962
  maxContentLength: Infinity,
1542
1963
  maxBodyLength: Infinity,
1543
- timeout: 6e4
1544
- // 60 秒请求超时
1964
+ timeout: 12e4
1545
1965
  });
1546
- const data = response.data;
1547
- logger.info(`\u5C0F\u7C73 API \u539F\u59CB\u54CD\u5E94: ${JSON.stringify(data)}`);
1548
- if (data?.result !== 0) {
1549
- const errMsg = data?.message || data?.msg || JSON.stringify(data);
1550
- throw new Error(`\u5C0F\u7C73 API \u8FD4\u56DE\u9519\u8BEF (result=${data?.result}): ${errMsg}`);
1966
+ if (response.data?.result !== 0) {
1967
+ throw new Error(`\u5C0F\u7C73 API \u9519\u8BEF: ${response.data?.message || JSON.stringify(response.data)}`);
1551
1968
  }
1552
- logger.info(`\u5C0F\u7C73 API \u54CD\u5E94: ${JSON.stringify(data)}`);
1553
- return data;
1969
+ return response.data;
1554
1970
  }
1555
1971
  /**
1556
1972
  * 解析公钥: 优先使用内容字段, 其次从文件路径读取
@@ -1592,7 +2008,7 @@ ${lines.join("\n")}
1592
2008
 
1593
2009
  // src/adapters/apple_store_uploader.ts
1594
2010
  import fs6 from "fs-extra";
1595
- import path3 from "path";
2011
+ import path4 from "path";
1596
2012
  import { exec as exec2 } from "child_process";
1597
2013
  import { promisify as promisify2 } from "util";
1598
2014
  import axios4 from "axios";
@@ -1758,7 +2174,7 @@ var AppleStoreUploader = class {
1758
2174
  appPassword,
1759
2175
  "-showTransferRate"
1760
2176
  ].join(" ");
1761
- logger.info(`[Transporter] Uploading: ${path3.basename(ipaPath)}`);
2177
+ logger.info(`[Transporter] Uploading: ${path4.basename(ipaPath)}`);
1762
2178
  try {
1763
2179
  const { stdout, stderr } = await execAsync(command, {
1764
2180
  timeout: 30 * 60 * 1e3,
@@ -1809,7 +2225,7 @@ Stderr: ${error.stderr || ""}`);
1809
2225
  });
1810
2226
  command = innerArgs.join(" ");
1811
2227
  }
1812
- logger.info(`[altool] Uploading: ${path3.basename(ipaPath)}`);
2228
+ logger.info(`[altool] Uploading: ${path4.basename(ipaPath)}`);
1813
2229
  logger.info(`[altool] Command prefix: ${tool.command}`);
1814
2230
  try {
1815
2231
  const { stdout, stderr } = await execAsync(command, {
@@ -2393,7 +2809,7 @@ var TestFlightUploader = class _TestFlightUploader {
2393
2809
 
2394
2810
  // src/adapters/adhoc_uploader.ts
2395
2811
  import fs8 from "fs-extra";
2396
- import path4 from "path";
2812
+ import path5 from "path";
2397
2813
  import { execSync } from "child_process";
2398
2814
  var AdHocUploader = class {
2399
2815
  name = "AdHoc";
@@ -2526,8 +2942,8 @@ var AdHocUploader = class {
2526
2942
  * @param changelog 更新日志
2527
2943
  */
2528
2944
  async createManifest(ipaPath, version, bundleId, changelog) {
2529
- const ipaFileName = path4.basename(ipaPath);
2530
- const appName = bundleId || path4.parse(ipaFileName).name;
2945
+ const ipaFileName = path5.basename(ipaPath);
2946
+ const appName = bundleId || path5.parse(ipaFileName).name;
2531
2947
  const ipaUrl = `itms-services://?action=download-manifest&url=https://your-distribution-server.com/manifests/${ipaFileName}.plist`;
2532
2948
  const manifestContent = `<?xml version="1.0" encoding="UTF-8"?>
2533
2949
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -2586,14 +3002,14 @@ var AdHocUploader = class {
2586
3002
  */
2587
3003
  async setupDistributionDirectory(ipaPath, manifestPath) {
2588
3004
  const projectRoot = process.cwd();
2589
- const distributionDir = path4.join(projectRoot, "build", "adhoc-distribution");
3005
+ const distributionDir = path5.join(projectRoot, "build", "adhoc-distribution");
2590
3006
  await fs8.ensureDir(distributionDir);
2591
- await fs8.ensureDir(path4.join(distributionDir, "apps"));
2592
- await fs8.ensureDir(path4.join(distributionDir, "manifests"));
2593
- await fs8.ensureDir(path4.join(distributionDir, "icons"));
2594
- const ipaFileName = path4.basename(ipaPath);
2595
- await fs8.copy(ipaPath, path4.join(distributionDir, "apps", ipaFileName));
2596
- await fs8.copy(manifestPath, path4.join(distributionDir, "manifests", `${ipaFileName}.plist`));
3007
+ await fs8.ensureDir(path5.join(distributionDir, "apps"));
3008
+ await fs8.ensureDir(path5.join(distributionDir, "manifests"));
3009
+ await fs8.ensureDir(path5.join(distributionDir, "icons"));
3010
+ const ipaFileName = path5.basename(ipaPath);
3011
+ await fs8.copy(ipaPath, path5.join(distributionDir, "apps", ipaFileName));
3012
+ await fs8.copy(manifestPath, path5.join(distributionDir, "manifests", `${ipaFileName}.plist`));
2597
3013
  logger.info(`Distribution directory: ${distributionDir}`);
2598
3014
  return distributionDir;
2599
3015
  }
@@ -2603,7 +3019,7 @@ var AdHocUploader = class {
2603
3019
  * @param bundleId Bundle ID
2604
3020
  */
2605
3021
  generateInstallUrl(distributionPath, bundleId) {
2606
- const manifestPath = path4.join(distributionPath, "manifests", `*.plist`);
3022
+ const manifestPath = path5.join(distributionPath, "manifests", `*.plist`);
2607
3023
  return `itms-services://?action=download-manifest&url=file://${manifestPath}`;
2608
3024
  }
2609
3025
  /**
@@ -2612,7 +3028,7 @@ var AdHocUploader = class {
2612
3028
  * @param distributionPath 分发目录
2613
3029
  */
2614
3030
  async generateQRCode(url, distributionPath) {
2615
- const qrCodePath = path4.join(distributionPath, "install-qrcode.png");
3031
+ const qrCodePath = path5.join(distributionPath, "install-qrcode.png");
2616
3032
  try {
2617
3033
  logger.info(`QR code would be saved to: ${qrCodePath}`);
2618
3034
  logger.info(`Install URL: ${url}`);
@@ -2639,8 +3055,8 @@ var AdHocUploader = class {
2639
3055
  logger.newLine();
2640
3056
  logger.info("\u{1F4E6} Distribution Files:");
2641
3057
  logger.info(` Directory: ${distributionPath}`);
2642
- logger.info(` IPA: ${path4.join(distributionPath, "apps", "*.ipa")}`);
2643
- logger.info(` Manifest: ${path4.join(distributionPath, "manifests", "*.plist")}`);
3058
+ logger.info(` IPA: ${path5.join(distributionPath, "apps", "*.ipa")}`);
3059
+ logger.info(` Manifest: ${path5.join(distributionPath, "manifests", "*.plist")}`);
2644
3060
  logger.info(` QR Code: ${qrCodePath}`);
2645
3061
  logger.newLine();
2646
3062
  logger.info("\u{1F517} Installation Link:");
@@ -2699,7 +3115,7 @@ var AdHocUploader = class {
2699
3115
 
2700
3116
  // src/adapters/harmony_uploader.ts
2701
3117
  import fs9 from "fs-extra";
2702
- import path5 from "path";
3118
+ import path6 from "path";
2703
3119
  import axios6 from "axios";
2704
3120
  import FormData5 from "form-data";
2705
3121
  var HarmonyUploader = class _HarmonyUploader {
@@ -2729,7 +3145,7 @@ var HarmonyUploader = class _HarmonyUploader {
2729
3145
  const clientSecret = harmonyConfig.auth.clientSecret;
2730
3146
  const appId = harmonyConfig.appId;
2731
3147
  const releaseType = harmonyConfig.releaseType || "draft";
2732
- const suffix = path5.extname(filePath).substring(1);
3148
+ const suffix = path6.extname(filePath).substring(1);
2733
3149
  const retryConfig = config.retry;
2734
3150
  logger.info("Starting HarmonyOS AppGallery upload...");
2735
3151
  try {
@@ -2846,7 +3262,7 @@ var HarmonyUploader = class _HarmonyUploader {
2846
3262
  * @param filePath 文件路径
2847
3263
  */
2848
3264
  async updateAppFileInfo(token, appId, authCode, filePath) {
2849
- const fileName = path5.basename(filePath);
3265
+ const fileName = path6.basename(filePath);
2850
3266
  const body = {
2851
3267
  fileType: 7,
2852
3268
  // 鸿蒙包的 fileType(需要根据华为最新文档确认)
@@ -2869,7 +3285,7 @@ var HarmonyUploader = class _HarmonyUploader {
2869
3285
 
2870
3286
  // src/adapters/oppo_uploader.ts
2871
3287
  import fs10 from "fs-extra";
2872
- import path6 from "path";
3288
+ import path7 from "path";
2873
3289
  import axios7 from "axios";
2874
3290
  import FormData6 from "form-data";
2875
3291
  import crypto4 from "crypto";
@@ -2910,7 +3326,7 @@ var OppoUploader = class _OppoUploader {
2910
3326
  * 尝试从文件名推断包名
2911
3327
  */
2912
3328
  inferPackageName(filePath) {
2913
- const fileName = path6.basename(filePath);
3329
+ const fileName = path7.basename(filePath);
2914
3330
  const match = fileName.match(/^([\w.]+?)(?:-(?:release|debug|unsigned))?\.apk$/i);
2915
3331
  if (match && match[1].includes(".")) {
2916
3332
  return match[1];
@@ -2928,110 +3344,47 @@ var OppoUploader = class _OppoUploader {
2928
3344
  }
2929
3345
  async upload(filePath, config, metadata) {
2930
3346
  const oppoConfig = config.publish.oppo;
2931
- const clientId = oppoConfig.auth.clientId;
2932
- const clientSecret = oppoConfig.auth.clientSecret;
3347
+ const accessToken = await this.getAccessToken(oppoConfig.auth.clientId, oppoConfig.auth.clientSecret);
2933
3348
  const packageName = oppoConfig.packageName || this.inferPackageName(filePath);
2934
- if (!packageName) {
2935
- return {
2936
- platform: "oppo",
2937
- success: false,
2938
- 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",
2939
- consoleUrl: "https://open.oppomobile.com/new/ecology/app"
2940
- };
2941
- }
2942
- const retryConfig = config.retry;
2943
3349
  const consoleUrl = "https://open.oppomobile.com/new/ecology/app";
2944
- if (!fs10.existsSync(filePath)) {
2945
- return {
2946
- platform: "oppo",
2947
- success: false,
2948
- message: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}`,
2949
- consoleUrl
2950
- };
3350
+ if (!packageName) {
3351
+ return { platform: "oppo", success: false, message: "\u65E0\u6CD5\u83B7\u53D6\u5305\u540D", consoleUrl };
2951
3352
  }
2952
- logger.info("\u5F00\u59CB\u4E0A\u4F20\u5230 OPPO \u5E94\u7528\u5546\u5E97...");
2953
- logger.info(`\u6587\u4EF6: ${filePath}`);
3353
+ const artifacts = typeof metadata.artifactPath === "object" ? metadata.artifactPath : { universal: filePath || (typeof metadata.artifactPath === "string" ? metadata.artifactPath : "") };
2954
3354
  try {
2955
- const retryStrategy = createUploadRetryStrategy(retryConfig);
2956
- logger.info("\u6B63\u5728\u83B7\u53D6 access_token...");
2957
- const tokenResult = await retryStrategy.execute(
2958
- () => this.getAccessToken(clientId, clientSecret),
2959
- "OPPO authentication"
2960
- );
2961
- if (!tokenResult.success || !tokenResult.data) {
2962
- throw tokenResult.error || new Error("Failed to get access token");
2963
- }
2964
- const accessToken = tokenResult.data;
2965
- logger.info("\u6B63\u5728\u83B7\u53D6\u73B0\u6709\u5E94\u7528\u8BE6\u60C5...");
2966
- const appInfoResult = await retryStrategy.execute(
2967
- () => this.getAppInfo(accessToken, clientSecret, packageName),
2968
- "OPPO get app info"
2969
- );
2970
- const onlineAppInfo = appInfoResult.data || {};
2971
- if (onlineAppInfo) {
2972
- logger.info(
2973
- `\u83B7\u53D6\u5230\u7EBF\u4E0A\u4FE1\u606F: ${onlineAppInfo.app_name} (Category: ${onlineAppInfo.second_category_id})`
2974
- );
2975
- logger.info(`[OPPO AppInfo Full] ${JSON.stringify(onlineAppInfo)}`);
2976
- }
2977
- logger.info("\u6B63\u5728\u83B7\u53D6\u4E0A\u4F20\u5730\u5740...");
2978
- const uploadUrlResult = await retryStrategy.execute(
2979
- () => this.getUploadUrl(accessToken, clientSecret),
2980
- "OPPO get upload URL"
2981
- );
2982
- if (!uploadUrlResult.success || !uploadUrlResult.data) {
2983
- throw uploadUrlResult.error || new Error("Failed to get upload URL");
2984
- }
2985
- const { uploadUrl, sign: uploadSign } = uploadUrlResult.data;
2986
- logger.info("\u6B63\u5728\u4E0A\u4F20 APK \u6587\u4EF6...");
2987
- const uploadFileResult = await retryStrategy.execute(
2988
- () => this.uploadFile(uploadUrl, uploadSign, filePath),
2989
- "OPPO file upload"
2990
- );
2991
- if (!uploadFileResult.success || !uploadFileResult.data) {
2992
- throw uploadFileResult.error || new Error("File upload failed");
2993
- }
2994
- const rawUrl = uploadFileResult.data.url;
2995
- const fileUrl = rawUrl.replace(/^http:\/\//i, "https://").replace("storedl1.nearme.com.cn", "storedl.nearme.com.cn");
2996
- const md5 = uploadFileResult.data.md5;
2997
- logger.info("\u6B63\u5728\u63D0\u4EA4\u7248\u672C\u4FE1\u606F...");
2998
- const submitResult = await retryStrategy.execute(
2999
- () => this.submitVersion({
3000
- accessToken,
3001
- clientSecret,
3002
- packageName,
3003
- appName: oppoConfig.appName || metadata.appName || onlineAppInfo.app_name || this.inferAppName(packageName),
3004
- fileUrl,
3005
- fileMd5: md5,
3006
- versionName: metadata.version,
3007
- versionCode: metadata.buildNumber,
3008
- updateDesc: metadata.changelog || "App Update",
3009
- onlineType: oppoConfig.onlineType,
3010
- // 从配置读取
3011
- onlineAppInfo
3012
- // 传入线上信息
3013
- }),
3014
- "OPPO submit version"
3015
- );
3016
- if (!submitResult.success) {
3017
- throw submitResult.error || new Error("Submit version failed");
3018
- }
3355
+ const retryStrategy = createUploadRetryStrategy(config.retry);
3356
+ const onlineAppInfo = await this.getAppInfo(accessToken, oppoConfig.auth.clientSecret, packageName) || {};
3357
+ const uploadResults = [];
3358
+ const uploadPromises = Object.entries(artifacts).map(async ([abi, p]) => {
3359
+ if (!p || !fs10.existsSync(p)) throw new Error(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${p}`);
3360
+ logger.info(`[OPPO] \u6B63\u5728\u5904\u7406 ${abi} \u67B6\u6784\u5305...`);
3361
+ const { uploadUrl, sign } = await this.getUploadUrl(accessToken, oppoConfig.auth.clientSecret);
3362
+ const uploadFileResult = await this.uploadFile(uploadUrl, sign, p);
3363
+ const fileUrl = uploadFileResult.url.replace(/^http:\/\//i, "https://").replace("storedl1.nearme.com.cn", "storedl.nearme.com.cn");
3364
+ let cpu_code = 0;
3365
+ if (abi === "arm64-v8a") cpu_code = 3;
3366
+ else if (abi === "armeabi-v7a") cpu_code = 4;
3367
+ uploadResults.push({ url: fileUrl, md5: uploadFileResult.md5, cpu_code });
3368
+ });
3369
+ await Promise.all(uploadPromises);
3370
+ logger.info(`[OPPO] \u6B63\u5728\u63D0\u4EA4\u6D41\u6C34\u53F7\u7EC4: ${JSON.stringify(uploadResults)}`);
3371
+ await this.submitVersion({
3372
+ accessToken,
3373
+ clientSecret: oppoConfig.auth.clientSecret,
3374
+ packageName,
3375
+ appName: oppoConfig.appName || onlineAppInfo.app_name || metadata.appName || this.inferAppName(packageName),
3376
+ apkUrls: uploadResults,
3377
+ versionName: metadata.version,
3378
+ versionCode: metadata.buildNumber,
3379
+ updateDesc: metadata.changelog || "App Update",
3380
+ onlineType: oppoConfig.onlineType,
3381
+ onlineAppInfo
3382
+ });
3019
3383
  logger.success("\u4E0A\u4F20\u5230 OPPO \u5E94\u7528\u5546\u5E97\u6210\u529F!");
3020
- return {
3021
- platform: "oppo",
3022
- success: true,
3023
- message: "\u4E0A\u4F20\u6210\u529F, \u8BF7\u524D\u5F80\u540E\u53F0\u786E\u8BA4\u5E76\u63D0\u4EA4\u5BA1\u6838",
3024
- consoleUrl
3025
- };
3384
+ return { platform: "oppo", success: true, message: "\u4E0A\u4F20\u6210\u529F, \u8BF7\u786E\u8BA4", consoleUrl };
3026
3385
  } catch (error) {
3027
3386
  logger.error(`\u4E0A\u4F20\u5230 OPPO \u5931\u8D25: ${error.message}`);
3028
- return {
3029
- platform: "oppo",
3030
- success: false,
3031
- message: this.formatError(error),
3032
- error,
3033
- consoleUrl
3034
- };
3387
+ return { platform: "oppo", success: false, message: error.message, error, consoleUrl };
3035
3388
  }
3036
3389
  }
3037
3390
  /**
@@ -3152,16 +3505,7 @@ var OppoUploader = class _OppoUploader {
3152
3505
  const signParams = {
3153
3506
  access_token: params.accessToken,
3154
3507
  timestamp,
3155
- // OPPO 官方要求 apk_url 为 JSON 数组格式:[{url, md5, cpu_code}]
3156
- // cpu_code: 0=通用包, 3=arm64-v8a, 4=armeabi-v7a, 5=x86_64
3157
- apk_url: JSON.stringify([
3158
- {
3159
- url: params.fileUrl,
3160
- md5: params.fileMd5,
3161
- cpu_code: 0
3162
- // 通用包
3163
- }
3164
- ]),
3508
+ apk_url: JSON.stringify(params.apkUrls),
3165
3509
  version_name: params.versionName.split("+")[0],
3166
3510
  // 仅保留版本名部分
3167
3511
  version_code: vCode,
@@ -3263,7 +3607,7 @@ var OppoUploader = class _OppoUploader {
3263
3607
 
3264
3608
  // src/adapters/vivo_uploader.ts
3265
3609
  import fs11 from "fs-extra";
3266
- import path7 from "path";
3610
+ import path8 from "path";
3267
3611
  import axios8 from "axios";
3268
3612
  import FormData7 from "form-data";
3269
3613
  import crypto5 from "crypto";
@@ -3271,26 +3615,14 @@ var VivoUploader = class _VivoUploader {
3271
3615
  name = "vivo";
3272
3616
  /** vivo 开放 API 基础地址 */
3273
3617
  static API_BASE = "https://developer-api.vivo.com.cn/router/rest";
3274
- /**
3275
- * vivo API 签名生成
3276
- * 规则: 所有参数按 ASCII 码排序后拼接为 key=val&... 字符串,
3277
- * 以 accessSecret 为密钥进行 HMAC-SHA256 加密,结果转为小写 16 进制字符串
3278
- */
3279
3618
  generateSign(params, accessSecret) {
3280
3619
  const sorted = Object.keys(params).sort().filter((key) => params[key] !== void 0 && params[key] !== null).map((key) => `${key}=${params[key]}`).join("&");
3281
- const sign = crypto5.createHmac("sha256", accessSecret).update(sorted).digest("hex");
3282
- logger.info(`[VIVO DEBUG] \u5F85\u7B7E\u540D\u4E32: ${sorted}
3283
- -> \u7B7E\u540D\u7ED3\u679C: ${sign}`);
3284
- return sign;
3620
+ return crypto5.createHmac("sha256", accessSecret).update(sorted).digest("hex");
3285
3621
  }
3286
3622
  validateConfig(config) {
3287
3623
  if (!config?.publish?.vivo?.enable) return false;
3288
3624
  const vivo = config.publish.vivo;
3289
- if (!vivo.auth?.accessKey || !vivo.auth?.accessSecret) {
3290
- logger.error("vivo config missing: accessKey or accessSecret");
3291
- return false;
3292
- }
3293
- return true;
3625
+ return !!(vivo.auth?.accessKey && vivo.auth?.accessSecret);
3294
3626
  }
3295
3627
  canHandle(filePath) {
3296
3628
  const ext = filePath.split(".").pop()?.toLowerCase();
@@ -3300,67 +3632,51 @@ var VivoUploader = class _VivoUploader {
3300
3632
  const vivoConfig = config.publish.vivo;
3301
3633
  const accessKey = vivoConfig.auth.accessKey;
3302
3634
  const accessSecret = vivoConfig.auth.accessSecret;
3303
- const retryConfig = config.retry;
3304
3635
  const consoleUrl = "https://dev.vivo.com.cn/manage/app";
3305
- if (!fs11.existsSync(filePath)) {
3306
- return {
3307
- platform: "vivo",
3308
- success: false,
3309
- message: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}`,
3310
- consoleUrl
3311
- };
3312
- }
3313
- const fileSize = fs11.statSync(filePath).size;
3314
- logger.info("\u5F00\u59CB\u4E0A\u4F20\u5230 vivo \u5E94\u7528\u5546\u5E97...");
3315
- logger.info(`\u6587\u4EF6: ${filePath} (${(fileSize / 1024 / 1024).toFixed(2)} MB)`);
3636
+ const artifacts = typeof metadata.artifactPath === "object" ? metadata.artifactPath : { universal: filePath || (typeof metadata.artifactPath === "string" ? metadata.artifactPath : "") };
3316
3637
  try {
3317
- const retryStrategy = createUploadRetryStrategy(retryConfig);
3318
- const fileMd5 = await this.calculateFileMD5(filePath);
3319
- logger.info("\u6B63\u5728\u4E0A\u4F20 APK \u6587\u4EF6...");
3320
- const packageName = vivoConfig.packageName || "";
3321
- const uploadFileResult = await retryStrategy.execute(
3322
- () => this.uploadApk(accessKey, accessSecret, filePath, packageName, fileMd5),
3323
- "vivo file upload"
3324
- );
3325
- if (!uploadFileResult.success || !uploadFileResult.data) {
3326
- throw uploadFileResult.error || new Error("File upload failed or no serialnumber returned");
3638
+ const uploadResults = {};
3639
+ const uploadPromises = Object.entries(artifacts).map(async ([abi, p]) => {
3640
+ if (!p || !fs11.existsSync(p)) throw new Error(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${p}`);
3641
+ const md5 = await this.calculateFileMD5(p);
3642
+ logger.info(`[vivo] \u6B63\u5728\u5904\u7406 ${abi} \u67B6\u6784\u5305: ${path8.basename(p)}`);
3643
+ const serialnumber = await this.uploadApk(accessKey, accessSecret, p, vivoConfig.packageName || "", md5);
3644
+ const pkgInfo = await parsePackageInfo(p, { androidSdkPath: config.globalSettings?.androidSdkPath });
3645
+ const nativeCodes = pkgInfo?.nativeCode || [];
3646
+ const has64Bit = nativeCodes.includes("arm64-v8a") || nativeCodes.includes("x86_64") || abi === "arm64-v8a";
3647
+ const has32Bit = nativeCodes.includes("armeabi-v7a") || nativeCodes.includes("armeabi") || abi === "armeabi-v7a";
3648
+ uploadResults[abi] = { serialnumber, md5, is64Bit: has64Bit && !has32Bit };
3649
+ });
3650
+ await Promise.all(uploadPromises);
3651
+ const arm64Result = uploadResults["arm64-v8a"] || Object.values(uploadResults).find((r) => r.is64Bit);
3652
+ const arm32Result = uploadResults["armeabi-v7a"] || Object.values(uploadResults).find((r) => !r.is64Bit);
3653
+ const submitData = {
3654
+ packageName: vivoConfig.packageName,
3655
+ versionCode: parseInt(metadata.buildNumber) || 1,
3656
+ updateDesc: metadata.changelog || "App Update"
3657
+ };
3658
+ if (arm64Result) {
3659
+ submitData.serialnumber = arm64Result.serialnumber;
3660
+ submitData.fileMd5 = arm64Result.md5;
3327
3661
  }
3328
- const serialnumber = uploadFileResult.data;
3329
- logger.info("\u6B63\u5728\u63D0\u4EA4\u7248\u672C\u4FE1\u606F...");
3330
- const submitResult = await retryStrategy.execute(
3331
- () => this.submitVersion(accessKey, accessSecret, {
3332
- serialnumber,
3333
- packageName: vivoConfig.packageName,
3334
- versionCode: parseInt(metadata.buildNumber) || 1,
3335
- fileMd5,
3336
- updateDesc: metadata.changelog || "App Update"
3337
- }),
3338
- "vivo submit version"
3339
- );
3340
- if (!submitResult.success) {
3341
- throw submitResult.error || new Error("Submit version failed");
3662
+ if (arm32Result) {
3663
+ submitData.apk32 = arm32Result.serialnumber;
3664
+ submitData.md532 = arm32Result.md5;
3342
3665
  }
3666
+ if (!submitData.serialnumber && !submitData.apk32) {
3667
+ const single = Object.values(uploadResults)[0];
3668
+ submitData.serialnumber = single.serialnumber;
3669
+ submitData.fileMd5 = single.md5;
3670
+ }
3671
+ logger.info(`[vivo] \u6B63\u5728\u63D0\u4EA4\u5BA1\u6838\u7533\u62A5: ${JSON.stringify(submitData)}`);
3672
+ await this.submitVersion(accessKey, accessSecret, submitData);
3343
3673
  logger.success("\u4E0A\u4F20\u5230 vivo \u5E94\u7528\u5546\u5E97\u6210\u529F!");
3344
- return {
3345
- platform: "vivo",
3346
- success: true,
3347
- message: "\u4E0A\u4F20\u6210\u529F, \u8BF7\u524D\u5F80\u540E\u53F0\u786E\u8BA4\u5E76\u63D0\u4EA4\u5BA1\u6838",
3348
- consoleUrl
3349
- };
3674
+ return { platform: "vivo", success: true, message: "\u4E0A\u4F20\u5E76\u63D0\u4EA4\u5BA1\u6838\u6210\u529F", consoleUrl };
3350
3675
  } catch (error) {
3351
3676
  logger.error(`\u4E0A\u4F20\u5230 vivo \u5931\u8D25: ${error.message}`);
3352
- return {
3353
- platform: "vivo",
3354
- success: false,
3355
- message: this.formatError(error),
3356
- error,
3357
- consoleUrl
3358
- };
3677
+ return { platform: "vivo", success: false, message: error.message, error, consoleUrl };
3359
3678
  }
3360
3679
  }
3361
- /**
3362
- * 计算文件 MD5
3363
- */
3364
3680
  calculateFileMD5(filePath) {
3365
3681
  return new Promise((resolve, reject) => {
3366
3682
  const hash = crypto5.createHash("md5");
@@ -3370,14 +3686,9 @@ var VivoUploader = class _VivoUploader {
3370
3686
  stream.on("error", reject);
3371
3687
  });
3372
3688
  }
3373
- /**
3374
- * 上传 APK 文件并获取流水号
3375
- * 将公共参数与业务参数放入一个 multipart 请求中直接上传至 API_BASE
3376
- */
3377
3689
  async uploadApk(accessKey, accessSecret, filePath, packageName, fileMd5) {
3378
3690
  const timestamp = String(Date.now());
3379
- const paramsForSign = {
3380
- // 公共参数
3691
+ const params = {
3381
3692
  access_key: accessKey,
3382
3693
  timestamp,
3383
3694
  format: "json",
@@ -3385,41 +3696,19 @@ var VivoUploader = class _VivoUploader {
3385
3696
  sign_method: "hmac",
3386
3697
  target_app_key: "developer",
3387
3698
  method: "app.upload.apk.app",
3388
- // 业务参数
3389
3699
  packageName,
3390
3700
  fileMd5
3391
3701
  };
3392
- const sign = this.generateSign(paramsForSign, accessSecret);
3702
+ const sign = this.generateSign(params, accessSecret);
3393
3703
  const form = new FormData7();
3394
- for (const [key, value] of Object.entries(paramsForSign)) {
3395
- form.append(key, value);
3396
- }
3704
+ Object.entries(params).forEach(([k, v]) => form.append(k, v));
3397
3705
  form.append("sign", sign);
3398
- form.append("file", fs11.createReadStream(filePath), {
3399
- filename: path7.basename(filePath),
3400
- contentType: "application/vnd.android.package-archive"
3401
- });
3402
- logger.info(
3403
- `[VIVO DEBUG] \u5F00\u59CB\u4E0A\u4F20 APK (API\u8BF7\u6C42) \u53C2\u6570(\u4E0D\u542B\u6587\u4EF6): ${JSON.stringify({ ...paramsForSign, sign })}`
3404
- );
3405
- const res = await axios8.post(_VivoUploader.API_BASE, form, {
3406
- headers: form.getHeaders(),
3407
- maxContentLength: Infinity,
3408
- maxBodyLength: Infinity,
3409
- timeout: 3e5
3410
- });
3411
- logger.info(`[VIVO DEBUG] \u4E0A\u4F20\u54CD\u5E94: ${JSON.stringify(res.data)}`);
3412
- if (res.data.code !== 0) {
3413
- throw new Error(`\u6587\u4EF6\u4E0A\u4F20\u5931\u8D25: ${res.data.msg || JSON.stringify(res.data)}`);
3414
- }
3415
- const serialnumber = res.data.data?.serialnumber || res.data.data;
3416
- logger.info(`APK \u6587\u4EF6\u4E0A\u4F20\u5B8C\u6210\uFF0C\u6D41\u6C34\u53F7: ${serialnumber}`);
3417
- return serialnumber;
3706
+ form.append("file", fs11.createReadStream(filePath), { filename: path8.basename(filePath) });
3707
+ const res = await axios8.post(_VivoUploader.API_BASE, form, { headers: form.getHeaders(), timeout: 3e5 });
3708
+ if (res.data.code !== 0) throw new Error(`\u4E0A\u4F20\u5305\u5931\u8D25: ${res.data.msg}`);
3709
+ return res.data.data?.serialnumber || res.data.data;
3418
3710
  }
3419
- /**
3420
- * 提交版本更新信息
3421
- */
3422
- async submitVersion(accessKey, accessSecret, params) {
3711
+ async submitVersion(accessKey, accessSecret, data) {
3423
3712
  const timestamp = String(Date.now());
3424
3713
  const reqParams = {
3425
3714
  access_key: accessKey,
@@ -3429,51 +3718,32 @@ var VivoUploader = class _VivoUploader {
3429
3718
  sign_method: "hmac",
3430
3719
  target_app_key: "developer",
3431
3720
  method: "app.sync.update.app",
3432
- packageName: params.packageName || "",
3433
- // 文档必填
3434
- versionCode: String(params.versionCode),
3435
- // 文档必填 (Integer)
3436
- apk: params.serialnumber,
3437
- // 文档中的 apk 字段即为上传接口返回的 serialnumber
3438
- fileMd5: params.fileMd5,
3439
- // 文档必填
3721
+ packageName: data.packageName || "",
3722
+ versionCode: String(data.versionCode),
3723
+ apk: data.serialnumber || "",
3724
+ fileMd5: data.fileMd5 || "",
3440
3725
  onlineType: "1",
3441
- // 1:立即上架 2:定时上架 (必填)
3442
3726
  compatibleDevice: "1",
3443
- // 1:手机 2:手机和平板 3:平板 (必填)
3444
- updateDesc: params.updateDesc
3727
+ updateDesc: data.updateDesc
3445
3728
  };
3729
+ if (data.apk32) {
3730
+ reqParams.apk32 = data.apk32;
3731
+ if (data.md532) reqParams.md532 = data.md532;
3732
+ }
3446
3733
  reqParams.sign = this.generateSign(reqParams, accessSecret);
3447
3734
  const formData = new URLSearchParams();
3448
- for (const [key, value] of Object.entries(reqParams)) {
3449
- formData.append(key, value);
3450
- }
3735
+ Object.entries(reqParams).forEach(([k, v]) => formData.append(k, v));
3451
3736
  const res = await axios8.post(_VivoUploader.API_BASE, formData.toString(), {
3452
- headers: {
3453
- "Content-Type": "application/x-www-form-urlencoded"
3454
- },
3737
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
3455
3738
  timeout: 3e4
3456
3739
  });
3457
- if (res.data.code !== 0) {
3458
- throw new Error(`\u63D0\u4EA4\u7248\u672C\u5931\u8D25: ${res.data.msg || JSON.stringify(res.data)}`);
3459
- }
3460
- logger.info("\u7248\u672C\u4FE1\u606F\u5DF2\u63D0\u4EA4");
3461
- }
3462
- /**
3463
- * 格式化错误信息
3464
- */
3465
- formatError(error) {
3466
- if (error.response?.data) {
3467
- const data = error.response.data;
3468
- return data.msg || data.message || error.message;
3469
- }
3470
- return error.message;
3740
+ if (res.data.code !== 0) throw new Error(`\u63D0\u4EA4\u5931\u8D25: ${res.data.msg}`);
3471
3741
  }
3472
3742
  };
3473
3743
 
3474
3744
  // src/adapters/tencent_uploader.ts
3475
3745
  import fs12 from "fs-extra";
3476
- import path8 from "path";
3746
+ import path9 from "path";
3477
3747
  import axios9 from "axios";
3478
3748
  import crypto6 from "crypto";
3479
3749
  var TencentUploader = class _TencentUploader {
@@ -3505,54 +3775,71 @@ var TencentUploader = class _TencentUploader {
3505
3775
  async upload(filePath, config, metadata) {
3506
3776
  const tencentConfig = config.publish.tencent;
3507
3777
  const auth = tencentConfig.auth;
3508
- logger.info(`Tencent \u8FD0\u884C\u65F6\u914D\u7F6E: ${JSON.stringify(tencentConfig)}`);
3509
3778
  const consoleUrl = "https://app.open.qq.com";
3510
- if (!fs12.existsSync(filePath)) {
3511
- return { platform: "tencent", success: false, message: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}`, consoleUrl };
3512
- }
3779
+ const pkgName = tencentConfig.packageName || "";
3780
+ const artifacts = typeof metadata.artifactPath === "object" ? metadata.artifactPath : { universal: filePath || (typeof metadata.artifactPath === "string" ? metadata.artifactPath : "") };
3513
3781
  try {
3514
- const stats = fs12.statSync(filePath);
3515
- const fileName = path8.basename(filePath);
3516
- logger.info("\u6B63\u5728\u83B7\u53D6\u9884\u7B7E\u540D\u4E0A\u4F20\u5730\u5740...");
3517
- const pkgName = tencentConfig.packageName || "";
3518
- const uploadInfo = await this.getFileUploadInfo(auth, stats.size, fileName, pkgName);
3519
- logger.info(`\u83B7\u53D6\u6587\u4EF6\u4E0A\u4F20\u4FE1\u606F\u7ED3\u679C ${JSON.stringify(uploadInfo)}`);
3520
- logger.info("\u6B63\u5728\u4E0A\u4F20\u6587\u4EF6\u5230\u817E\u8BAF\u4E91 COS (\u7ED5\u8FC7 API \u7F51\u5173\u9650\u5236)...");
3521
- await this.uploadToCos(uploadInfo.pre_sign_url, filePath, stats.size);
3522
- logger.info("\u6B63\u5728\u83B7\u53D6\u7EBF\u4E0A\u5E94\u7528\u8BE6\u60C5\u4EE5\u4FDD\u7559\u73B0\u6709\u914D\u7F6E...");
3523
- const pkgInfo = await parsePackageInfo(filePath, {
3782
+ const uploadResults = {};
3783
+ const uploadPromises = Object.entries(artifacts).map(async ([abi, p]) => {
3784
+ if (!p || !fs12.existsSync(p)) throw new Error(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${p}`);
3785
+ const stats = fs12.statSync(p);
3786
+ const fileName = path9.basename(p);
3787
+ logger.info(`[Tencent] \u6B63\u5728\u5904\u7406 ${abi} \u67B6\u6784\u5305: ${fileName}`);
3788
+ const uploadInfo = await this.getFileUploadInfo(auth, stats.size, fileName, pkgName);
3789
+ await this.uploadToCos(uploadInfo.pre_sign_url, p, stats.size);
3790
+ const pkgInfo = await parsePackageInfo(p, {
3791
+ androidSdkPath: config.globalSettings?.androidSdkPath
3792
+ });
3793
+ const nativeCodes = pkgInfo?.nativeCode || [];
3794
+ const has64Bit = nativeCodes.includes("arm64-v8a") || nativeCodes.includes("x86_64") || abi === "arm64-v8a";
3795
+ const has32Bit = nativeCodes.includes("armeabi-v7a") || nativeCodes.includes("armeabi") || nativeCodes.includes("x86") || abi === "armeabi-v7a";
3796
+ uploadResults[abi] = {
3797
+ serial_number: uploadInfo.serial_number,
3798
+ is64Bit: has64Bit && !has32Bit,
3799
+ md5: pkgInfo?.md5 || ""
3800
+ };
3801
+ });
3802
+ await Promise.all(uploadPromises);
3803
+ logger.info("[Tencent] \u6B63\u5728\u83B7\u53D6\u7EBF\u4E0A\u5E94\u7528\u8BE6\u60C5...");
3804
+ const anyPkgPath = Object.values(artifacts)[0];
3805
+ const samplePkgInfo = await parsePackageInfo(anyPkgPath, {
3524
3806
  androidSdkPath: config.globalSettings?.androidSdkPath
3525
3807
  });
3526
- const nativeCodes = pkgInfo?.nativeCode || [];
3527
- const has64Bit = nativeCodes.includes("arm64-v8a") || nativeCodes.includes("x86_64");
3528
- const has32Bit = nativeCodes.includes("armeabi-v7a") || nativeCodes.includes("armeabi") || nativeCodes.includes("x86");
3529
- const is64Bit = has64Bit && !has32Bit;
3530
- const md5 = pkgInfo?.md5;
3531
3808
  const onlineDetail = await this.queryAppDetail(
3532
3809
  auth,
3533
- tencentConfig.packageName || pkgInfo?.packageName || ""
3810
+ pkgName || samplePkgInfo?.packageName || ""
3534
3811
  );
3535
- logger.info(`\u83B7\u53D6\u7EBF\u4E0A\u8BE6\u60C5\u6210\u529F: ${onlineDetail.app_name || "\u672A\u547D\u540D"}`);
3536
- logger.info("\u6B63\u5728\u63D0\u4EA4\u6D41\u6C34\u53F7\u5B8C\u6210\u5BA1\u6838\u7533\u62A5...");
3537
- const submitResult = await this.submitModifyAppInfo(
3538
- auth,
3539
- {
3540
- ...onlineDetail,
3541
- // 以前序详情为蓝本
3542
- pkg_name: tencentConfig.packageName || pkgInfo?.packageName || onlineDetail.pkg_name || "",
3543
- serial_number: uploadInfo.serial_number,
3544
- update_desc: metadata.changelog || "\u7248\u672C\u66F4\u65B0",
3545
- is64Bit,
3546
- md5
3547
- },
3548
- { timeout: tencentConfig.timeout || 6e4 }
3549
- );
3550
- logger.info(`\u63D0\u4EA4\u6D41\u6C34\u53F7\u5B8C\u6210\u5BA1\u6838\u7533\u62A5\u7ED3\u679C: ${JSON.stringify(submitResult)}`);
3812
+ const submitData = {
3813
+ ...onlineDetail,
3814
+ pkg_name: pkgName || samplePkgInfo?.packageName || onlineDetail.pkg_name || "",
3815
+ update_desc: metadata.changelog || "\u7248\u672C\u66F4\u65B0"
3816
+ };
3817
+ const arm64Result = uploadResults["arm64-v8a"] || Object.values(uploadResults).find((r) => r.is64Bit);
3818
+ const arm32Result = uploadResults["armeabi-v7a"] || Object.values(uploadResults).find((r) => !r.is64Bit);
3819
+ if (arm64Result) {
3820
+ submitData.apk64_serial_number = arm64Result.serial_number;
3821
+ submitData.apk64_md5 = arm64Result.md5;
3822
+ }
3823
+ if (arm32Result) {
3824
+ submitData.apk32_serial_number = arm32Result.serial_number;
3825
+ submitData.apk32_md5 = arm32Result.md5;
3826
+ }
3827
+ if (Object.keys(uploadResults).length === 1 && !submitData.apk64_serial_number && !submitData.apk32_serial_number) {
3828
+ const single = Object.values(uploadResults)[0];
3829
+ if (single.is64Bit) {
3830
+ submitData.apk64_serial_number = single.serial_number;
3831
+ submitData.apk64_md5 = single.md5;
3832
+ } else {
3833
+ submitData.apk32_serial_number = single.serial_number;
3834
+ submitData.apk32_md5 = single.md5;
3835
+ }
3836
+ }
3837
+ logger.info("[Tencent] \u6B63\u5728\u63D0\u4EA4\u5BA1\u6838\u7533\u62A5...");
3838
+ await this.submitModifyAppInfo(auth, submitData, { timeout: tencentConfig.timeout || 6e4 });
3551
3839
  logger.success("\u817E\u8BAF\u5E94\u7528\u5B9D\u4E0A\u4F20\u5E76\u63D0\u4EA4\u6210\u529F!");
3552
3840
  return { platform: "tencent", success: true, message: "\u4E0A\u4F20\u5E76\u63D0\u4EA4\u5BA1\u6838\u6210\u529F", consoleUrl };
3553
3841
  } catch (error) {
3554
- logger.error(`\u817E\u8BAF\u4E0A\u4F20\u5931\u8D25: ${JSON.stringify(error)}`);
3555
- logger.error(`\u817E\u8BAF\u4E0A\u4F20\u5931\u8D25: ${error.stack}`);
3842
+ logger.error(`\u817E\u8BAF\u4E0A\u4F20\u5931\u8D25: ${error.message}`);
3556
3843
  return { platform: "tencent", success: false, message: error.message, error, consoleUrl };
3557
3844
  }
3558
3845
  }
@@ -3666,16 +3953,22 @@ var TencentUploader = class _TencentUploader {
3666
3953
  signParams[key] = String(data[key]);
3667
3954
  }
3668
3955
  });
3669
- if (data.is64Bit) {
3670
- signParams.apk64_file_serial_number = String(data.serial_number || "");
3671
- if (data.md5) signParams.apk64_file_md5 = data.md5;
3672
- delete signParams.apk32_file_serial_number;
3673
- delete signParams.apk32_file_md5;
3674
- } else {
3675
- signParams.apk32_file_serial_number = String(data.serial_number || "");
3676
- if (data.md5) signParams.apk32_file_md5 = data.md5;
3677
- delete signParams.apk64_file_serial_number;
3678
- delete signParams.apk64_file_md5;
3956
+ if (data.apk64_serial_number) {
3957
+ signParams.apk64_file_serial_number = String(data.apk64_serial_number);
3958
+ if (data.apk64_md5) signParams.apk64_file_md5 = data.apk64_md5;
3959
+ }
3960
+ if (data.apk32_serial_number) {
3961
+ signParams.apk32_file_serial_number = String(data.apk32_serial_number);
3962
+ if (data.apk32_md5) signParams.apk32_file_md5 = data.apk32_md5;
3963
+ }
3964
+ if (!data.apk64_serial_number && !data.apk32_serial_number && data.serial_number) {
3965
+ if (data.is64Bit) {
3966
+ signParams.apk64_file_serial_number = String(data.serial_number || "");
3967
+ if (data.md5) signParams.apk64_file_md5 = data.md5;
3968
+ } else {
3969
+ signParams.apk32_file_serial_number = String(data.serial_number || "");
3970
+ if (data.md5) signParams.apk32_file_md5 = data.md5;
3971
+ }
3679
3972
  }
3680
3973
  const sign = this.generateSign(signParams, accessSecret);
3681
3974
  const formParams = {
@@ -3695,6 +3988,7 @@ var TencentUploader = class _TencentUploader {
3695
3988
  };
3696
3989
 
3697
3990
  // src/utils/upload-manager.ts
3991
+ var GROUPED_ARTIFACT_PLATFORMS = /* @__PURE__ */ new Set(["huawei", "xiaomi", "oppo", "vivo", "tencent"]);
3698
3992
  var UploadManager = class {
3699
3993
  uploaders;
3700
3994
  config;
@@ -3801,9 +4095,22 @@ var UploadManager = class {
3801
4095
  if (matchedFiles.length === 0) {
3802
4096
  logger.warn(`[${platform}] No compatible file found in provided files`);
3803
4097
  } else {
3804
- matchedFiles.forEach((file) => {
3805
- tasks.push({ platform, file });
3806
- });
4098
+ if (this.shouldGroupArtifacts(platform)) {
4099
+ const artifactSelection = this.resolveArtifactSelection(platform, matchedFiles, metadata);
4100
+ if (!artifactSelection) {
4101
+ logger.warn(`[${platform}] Failed to build grouped artifact selection`);
4102
+ return;
4103
+ }
4104
+ tasks.push({
4105
+ platform,
4106
+ file: this.getRepresentativeFilePath(artifactSelection),
4107
+ artifactSelection
4108
+ });
4109
+ } else {
4110
+ matchedFiles.forEach((file) => {
4111
+ tasks.push({ platform, file, artifactSelection: file });
4112
+ });
4113
+ }
3807
4114
  }
3808
4115
  });
3809
4116
  if (tasks.length === 0) {
@@ -3821,8 +4128,14 @@ var UploadManager = class {
3821
4128
  }
3822
4129
  logger.info(`Scheduled ${tasks.length} upload task(s) for ${validPlatforms.length} platform(s)`);
3823
4130
  const uploadPromises = tasks.map((task) => {
3824
- logger.info(`[${task.platform}] Matched file: ${path9.basename(task.file)}`);
3825
- return this.uploadToPlatform(task.platform, task.file, config, metadata);
4131
+ logger.info(`[${task.platform}] Matched file: ${path10.basename(task.file)}`);
4132
+ return this.uploadToPlatform(
4133
+ task.platform,
4134
+ task.file,
4135
+ config,
4136
+ metadata,
4137
+ task.artifactSelection
4138
+ );
3826
4139
  });
3827
4140
  const results = await Promise.all(uploadPromises);
3828
4141
  const report = this.aggregateResults(results, tasks.length, startTime);
@@ -3832,7 +4145,7 @@ var UploadManager = class {
3832
4145
  /**
3833
4146
  * 上传到单个平台
3834
4147
  */
3835
- async uploadToPlatform(platform, filePath, config, metadata) {
4148
+ async uploadToPlatform(platform, filePath, config, metadata, artifactSelection = filePath) {
3836
4149
  if (this.config.cancellationToken?.isCancelled) {
3837
4150
  logger.warn(`[${platform.toUpperCase()}] Upload skipped due to cancellation`);
3838
4151
  return {
@@ -3853,25 +4166,30 @@ var UploadManager = class {
3853
4166
  if (this.config.verbose) {
3854
4167
  logger.info(`[${platform.toUpperCase()}] Starting upload...`);
3855
4168
  }
4169
+ const effectiveMetadata = {
4170
+ ...metadata,
4171
+ artifactPath: artifactSelection
4172
+ };
4173
+ await this.validateArtifactSelection(platform, artifactSelection, effectiveMetadata);
3856
4174
  const pkgInfo = await parsePackageInfo(filePath, {
3857
- androidSdkPath: metadata.globalSettings?.androidSdkPath
4175
+ androidSdkPath: effectiveMetadata.globalSettings?.androidSdkPath
3858
4176
  });
3859
4177
  if (pkgInfo) {
3860
4178
  if (pkgInfo.versionName) {
3861
- metadata.version = pkgInfo.versionName;
4179
+ effectiveMetadata.version = pkgInfo.versionName;
3862
4180
  }
3863
4181
  if (pkgInfo.versionCode) {
3864
- metadata.buildNumber = pkgInfo.versionCode;
4182
+ effectiveMetadata.buildNumber = pkgInfo.versionCode;
3865
4183
  }
3866
4184
  if (pkgInfo.packageName) {
3867
4185
  ;
3868
- metadata.packageName = pkgInfo.packageName;
4186
+ effectiveMetadata.packageName = pkgInfo.packageName;
3869
4187
  }
3870
4188
  logger.info(
3871
- `[${platform.toUpperCase()}] \u5DF2\u6821\u51C6\u7248\u672C\u4FE1\u606F: v${metadata.version}+${metadata.buildNumber}`
4189
+ `[${platform.toUpperCase()}] \u5DF2\u6821\u51C6\u7248\u672C\u4FE1\u606F: v${effectiveMetadata.version}+${effectiveMetadata.buildNumber}`
3872
4190
  );
3873
4191
  }
3874
- const result = await uploader.upload(filePath, config, metadata);
4192
+ const result = await uploader.upload(filePath, config, effectiveMetadata);
3875
4193
  return result;
3876
4194
  } catch (error) {
3877
4195
  const errorMessage = error.message || String(error);
@@ -3884,6 +4202,85 @@ var UploadManager = class {
3884
4202
  };
3885
4203
  }
3886
4204
  }
4205
+ shouldGroupArtifacts(platform) {
4206
+ return GROUPED_ARTIFACT_PLATFORMS.has(platform);
4207
+ }
4208
+ resolveArtifactSelection(platform, matchedFiles, metadata) {
4209
+ const explicitArtifacts = metadata.platformArtifacts?.[platform];
4210
+ if (explicitArtifacts) {
4211
+ if (typeof explicitArtifacts === "string") {
4212
+ return explicitArtifacts;
4213
+ }
4214
+ if (Object.keys(explicitArtifacts).length > 0) {
4215
+ return explicitArtifacts;
4216
+ }
4217
+ }
4218
+ if (matchedFiles.length === 0) {
4219
+ return null;
4220
+ }
4221
+ if (matchedFiles.length === 1) {
4222
+ return matchedFiles[0];
4223
+ }
4224
+ return this.buildArtifactMapFromFiles(matchedFiles);
4225
+ }
4226
+ buildArtifactMapFromFiles(files) {
4227
+ return files.reduce(
4228
+ (acc, filePath, index) => {
4229
+ acc[this.inferArtifactKey(filePath, index)] = filePath;
4230
+ return acc;
4231
+ },
4232
+ {}
4233
+ );
4234
+ }
4235
+ inferArtifactKey(filePath, index) {
4236
+ const fileName = path10.basename(filePath).toLowerCase();
4237
+ if (fileName.includes("arm64-v8a")) return "arm64-v8a";
4238
+ if (fileName.includes("armeabi-v7a")) return "armeabi-v7a";
4239
+ if (fileName.includes("x86_64")) return "x86_64";
4240
+ if (fileName.includes("universal")) return "universal";
4241
+ return `artifact_${index + 1}`;
4242
+ }
4243
+ getRepresentativeFilePath(artifactSelection) {
4244
+ if (typeof artifactSelection === "string") {
4245
+ return artifactSelection;
4246
+ }
4247
+ return Object.values(artifactSelection)[0];
4248
+ }
4249
+ async validateArtifactSelection(platform, artifactSelection, metadata) {
4250
+ if (typeof artifactSelection === "string") {
4251
+ return;
4252
+ }
4253
+ const artifactPaths = Object.values(artifactSelection).filter(Boolean);
4254
+ if (artifactPaths.length < 2) {
4255
+ return;
4256
+ }
4257
+ const parsedInfos = await Promise.all(
4258
+ artifactPaths.map(
4259
+ (artifactPath) => parsePackageInfo(artifactPath, {
4260
+ androidSdkPath: metadata.globalSettings?.androidSdkPath
4261
+ })
4262
+ )
4263
+ );
4264
+ const availableInfos = parsedInfos.filter((info) => !!info);
4265
+ if (availableInfos.length < 2) {
4266
+ logger.warn(
4267
+ `[${platform.toUpperCase()}] Unable to fully validate multi-artifact package info; continuing with upload.`
4268
+ );
4269
+ return;
4270
+ }
4271
+ const [baseInfo, ...restInfos] = availableInfos;
4272
+ for (const info of restInfos) {
4273
+ if (baseInfo?.packageName && info?.packageName && baseInfo.packageName !== info.packageName) {
4274
+ throw new Error("\u53CC\u5305\u4E0A\u4F20\u5931\u8D25\uFF1A\u6240\u9009\u5B89\u88C5\u5305\u7684 packageName \u4E0D\u4E00\u81F4");
4275
+ }
4276
+ if (baseInfo?.versionName && info?.versionName && baseInfo.versionName !== info.versionName) {
4277
+ throw new Error("\u53CC\u5305\u4E0A\u4F20\u5931\u8D25\uFF1A\u6240\u9009\u5B89\u88C5\u5305\u7684 versionName \u4E0D\u4E00\u81F4");
4278
+ }
4279
+ if (baseInfo?.versionCode && info?.versionCode && baseInfo.versionCode !== info.versionCode) {
4280
+ throw new Error("\u53CC\u5305\u4E0A\u4F20\u5931\u8D25\uFF1A\u6240\u9009\u5B89\u88C5\u5305\u7684 versionCode \u4E0D\u4E00\u81F4");
4281
+ }
4282
+ }
4283
+ }
3887
4284
  /**
3888
4285
  * 聚合上传结果
3889
4286
  */
@@ -4101,7 +4498,7 @@ var ConfigValidator = class {
4101
4498
  };
4102
4499
 
4103
4500
  // src/cli.ts
4104
- import path10 from "path";
4501
+ import path11 from "path";
4105
4502
  import fs14 from "fs-extra";
4106
4503
  import yaml2 from "js-yaml";
4107
4504
  var VERSION = "0.1.0";
@@ -4167,8 +4564,8 @@ async function cmdUpload(args) {
4167
4564
  logger.error("Missing file path. Use: app-ship upload -c <config.yaml> -f <file>");
4168
4565
  process.exit(1);
4169
4566
  }
4170
- const absConfig = path10.resolve(configPath);
4171
- const absFile = path10.resolve(filePath);
4567
+ const absConfig = path11.resolve(configPath);
4568
+ const absFile = path11.resolve(filePath);
4172
4569
  if (!fs14.existsSync(absFile)) {
4173
4570
  logger.error(`File not found: ${absFile}`);
4174
4571
  process.exit(1);