@huoye/app-ship 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +57 -0
- package/README.md +332 -298
- package/dist/cli.js +1602 -547
- package/dist/index.cjs +1524 -539
- package/dist/index.d.cts +278 -55
- package/dist/index.d.ts +278 -55
- package/dist/index.js +1504 -529
- package/package.json +44 -12
- package/dist/cli.js.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
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(
|
|
506
|
+
console.log(source_default.blue("\u2139\uFE0F " + message));
|
|
13
507
|
}
|
|
14
508
|
success(message) {
|
|
15
|
-
console.log(
|
|
509
|
+
console.log(source_default.green("\u2705 " + message));
|
|
16
510
|
}
|
|
17
511
|
warn(message) {
|
|
18
|
-
console.log(
|
|
512
|
+
console.log(source_default.yellow("\u26A0\uFE0F " + message));
|
|
19
513
|
}
|
|
20
514
|
error(message) {
|
|
21
|
-
console.log(
|
|
515
|
+
console.log(source_default.red("\u274C " + message));
|
|
22
516
|
}
|
|
23
517
|
title(message) {
|
|
24
|
-
console.log(
|
|
518
|
+
console.log(source_default.bold.cyan("\n" + message + "\n"));
|
|
25
519
|
}
|
|
26
520
|
newLine() {
|
|
27
521
|
console.log("");
|
|
@@ -58,16 +552,18 @@ var ConfigValidator = class {
|
|
|
58
552
|
logger.error(`Config file not found: ${filePath}`);
|
|
59
553
|
return null;
|
|
60
554
|
}
|
|
61
|
-
let content = fs.readFileSync(filePath, "utf8");
|
|
62
|
-
const envRegex = /\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
|
|
63
|
-
const matches = [...content.matchAll(envRegex)];
|
|
64
|
-
const requiredEnvs = Array.from(new Set(matches.map((m) => m[1])));
|
|
65
|
-
if (!EnvValidator.validate(requiredEnvs)) {
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
content = content.replace(envRegex, (_, varName) => process.env[varName] || "");
|
|
69
555
|
try {
|
|
70
|
-
const
|
|
556
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
557
|
+
const rawConfig = yaml.load(content);
|
|
558
|
+
if (!rawConfig || typeof rawConfig !== "object") {
|
|
559
|
+
logger.error("Config file must contain a YAML object");
|
|
560
|
+
return null;
|
|
561
|
+
}
|
|
562
|
+
const requiredEnvs = this.collectRequiredEnvVars(rawConfig);
|
|
563
|
+
if (!EnvValidator.validate(requiredEnvs)) {
|
|
564
|
+
return null;
|
|
565
|
+
}
|
|
566
|
+
const config = this.resolveEnvVars(rawConfig);
|
|
71
567
|
if (!this.validateSchema(config)) {
|
|
72
568
|
return null;
|
|
73
569
|
}
|
|
@@ -77,15 +573,71 @@ var ConfigValidator = class {
|
|
|
77
573
|
return null;
|
|
78
574
|
}
|
|
79
575
|
}
|
|
576
|
+
/**
|
|
577
|
+
* 只收集本次实际启用配置中的环境变量,避免禁用平台阻塞其它上传任务。
|
|
578
|
+
*/
|
|
579
|
+
static collectRequiredEnvVars(config) {
|
|
580
|
+
const activeConfig = {
|
|
581
|
+
...config,
|
|
582
|
+
publish: Object.fromEntries(
|
|
583
|
+
Object.entries(config.publish || {}).filter(([, value]) => value?.enable === true)
|
|
584
|
+
)
|
|
585
|
+
};
|
|
586
|
+
const requiredEnvs = /* @__PURE__ */ new Set();
|
|
587
|
+
const envRegex = /\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
|
|
588
|
+
const visit = (value) => {
|
|
589
|
+
if (typeof value === "string") {
|
|
590
|
+
for (const match of value.matchAll(envRegex)) {
|
|
591
|
+
requiredEnvs.add(match[1]);
|
|
592
|
+
}
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
if (Array.isArray(value)) {
|
|
596
|
+
value.forEach(visit);
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
if (value && typeof value === "object") {
|
|
600
|
+
Object.values(value).forEach(visit);
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
visit(activeConfig);
|
|
604
|
+
return Array.from(requiredEnvs);
|
|
605
|
+
}
|
|
606
|
+
/** 在内存中递归解析环境变量,不重新拼接 YAML,避免密钥字符破坏 YAML 结构。 */
|
|
607
|
+
static resolveEnvVars(value) {
|
|
608
|
+
if (typeof value === "string") {
|
|
609
|
+
return value.replace(
|
|
610
|
+
/\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g,
|
|
611
|
+
(_, varName) => process.env[varName] || ""
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
if (Array.isArray(value)) {
|
|
615
|
+
return value.map((item) => this.resolveEnvVars(item));
|
|
616
|
+
}
|
|
617
|
+
if (value && typeof value === "object") {
|
|
618
|
+
return Object.fromEntries(
|
|
619
|
+
Object.entries(value).map(([key, item]) => [key, this.resolveEnvVars(item)])
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
return value;
|
|
623
|
+
}
|
|
80
624
|
/**
|
|
81
625
|
* 验证配置 Schema
|
|
82
626
|
* @param config 配置对象
|
|
83
627
|
*/
|
|
84
628
|
static validateSchema(config) {
|
|
85
629
|
if (config.publish?.app_store?.enable) {
|
|
630
|
+
const mode = config.publish.app_store.mode || "app_store";
|
|
86
631
|
const auth = config.publish.app_store.auth;
|
|
87
|
-
|
|
88
|
-
|
|
632
|
+
const hasCliAuth = Boolean(auth?.appleId && auth?.appPassword);
|
|
633
|
+
const hasApiAuth = Boolean(
|
|
634
|
+
auth?.apiKeyId && auth?.apiIssuerId && (auth?.privateKeyPath || auth?.privateKeyContent)
|
|
635
|
+
);
|
|
636
|
+
const hasRequiredAuth = mode === "adhoc" || (mode === "testflight" ? hasApiAuth : hasCliAuth || hasApiAuth);
|
|
637
|
+
if (!hasRequiredAuth) {
|
|
638
|
+
logger.error(
|
|
639
|
+
mode === "testflight" ? "TestFlight enabled but missing complete API key credentials" : "App Store enabled but missing Apple ID credentials or complete API key credentials"
|
|
640
|
+
);
|
|
89
641
|
return false;
|
|
90
642
|
}
|
|
91
643
|
}
|
|
@@ -131,6 +683,20 @@ var ConfigValidator = class {
|
|
|
131
683
|
return false;
|
|
132
684
|
}
|
|
133
685
|
}
|
|
686
|
+
if (config.publish?.google_play?.enable) {
|
|
687
|
+
const googlePlay = config.publish.google_play;
|
|
688
|
+
const auth = googlePlay.auth;
|
|
689
|
+
if (!googlePlay.packageName || !auth?.serviceAccountJsonPath && !auth?.serviceAccountJsonContent) {
|
|
690
|
+
logger.error(
|
|
691
|
+
"Google Play enabled but missing packageName or service account JSON credentials"
|
|
692
|
+
);
|
|
693
|
+
return false;
|
|
694
|
+
}
|
|
695
|
+
if (googlePlay.track && googlePlay.track !== "internal") {
|
|
696
|
+
logger.error("Google Play first release only supports the internal track");
|
|
697
|
+
return false;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
134
700
|
return true;
|
|
135
701
|
}
|
|
136
702
|
};
|
|
@@ -245,12 +811,7 @@ var RetryStrategy = class {
|
|
|
245
811
|
logger.warn(`${operationName} failed (Attempt ${attempt}): ${errorMessage} [${category}]`);
|
|
246
812
|
if (!this.shouldRetry(error, attempt)) {
|
|
247
813
|
logger.error(`${operationName} failed with non-retryable error: ${errorMessage}`);
|
|
248
|
-
|
|
249
|
-
success: false,
|
|
250
|
-
error,
|
|
251
|
-
totalAttempts: attempt,
|
|
252
|
-
lastError: errorMessage
|
|
253
|
-
};
|
|
814
|
+
throw error;
|
|
254
815
|
}
|
|
255
816
|
if (attempt <= this.config.maxRetries) {
|
|
256
817
|
const delayMs = this.calculateDelay(attempt - 1);
|
|
@@ -261,12 +822,7 @@ var RetryStrategy = class {
|
|
|
261
822
|
}
|
|
262
823
|
}
|
|
263
824
|
}
|
|
264
|
-
|
|
265
|
-
success: false,
|
|
266
|
-
error: lastError || new Error(`${operationName} failed after all retries`),
|
|
267
|
-
totalAttempts: attempt,
|
|
268
|
-
lastError: lastError?.message
|
|
269
|
-
};
|
|
825
|
+
throw lastError || new Error(`${operationName} failed after all retries`);
|
|
270
826
|
}
|
|
271
827
|
};
|
|
272
828
|
var DEFAULT_RETRY_CONFIG = {
|
|
@@ -460,6 +1016,7 @@ async function parseHap(filePath) {
|
|
|
460
1016
|
}
|
|
461
1017
|
|
|
462
1018
|
// src/utils/upload-manager.ts
|
|
1019
|
+
var GROUPED_ARTIFACT_PLATFORMS = /* @__PURE__ */ new Set(["huawei", "xiaomi", "oppo", "vivo", "tencent"]);
|
|
463
1020
|
var UploadManager = class {
|
|
464
1021
|
uploaders;
|
|
465
1022
|
config;
|
|
@@ -481,6 +1038,7 @@ var UploadManager = class {
|
|
|
481
1038
|
this.registerUploader("oppo", new OppoUploader());
|
|
482
1039
|
this.registerUploader("vivo", new VivoUploader());
|
|
483
1040
|
this.registerUploader("tencent", new TencentUploader());
|
|
1041
|
+
this.registerUploader("google_play", new GooglePlayUploader());
|
|
484
1042
|
}
|
|
485
1043
|
/**
|
|
486
1044
|
* 注册上传器
|
|
@@ -499,6 +1057,8 @@ var UploadManager = class {
|
|
|
499
1057
|
if (config?.publish?.oppo?.enable) enabled.push("oppo");
|
|
500
1058
|
if (config?.publish?.vivo?.enable) enabled.push("vivo");
|
|
501
1059
|
if (config?.publish?.tencent?.enable) enabled.push("tencent");
|
|
1060
|
+
if (config?.publish?.harmony?.enable) enabled.push("harmony");
|
|
1061
|
+
if (config?.publish?.google_play?.enable) enabled.push("google_play");
|
|
502
1062
|
if (config?.publish?.app_store?.enable) {
|
|
503
1063
|
const mode = config.publish.app_store.mode || "app_store";
|
|
504
1064
|
enabled.push(mode);
|
|
@@ -566,9 +1126,22 @@ var UploadManager = class {
|
|
|
566
1126
|
if (matchedFiles.length === 0) {
|
|
567
1127
|
logger.warn(`[${platform}] No compatible file found in provided files`);
|
|
568
1128
|
} else {
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
1129
|
+
if (this.shouldGroupArtifacts(platform)) {
|
|
1130
|
+
const artifactSelection = this.resolveArtifactSelection(platform, matchedFiles, metadata);
|
|
1131
|
+
if (!artifactSelection) {
|
|
1132
|
+
logger.warn(`[${platform}] Failed to build grouped artifact selection`);
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
tasks.push({
|
|
1136
|
+
platform,
|
|
1137
|
+
file: this.getRepresentativeFilePath(artifactSelection),
|
|
1138
|
+
artifactSelection
|
|
1139
|
+
});
|
|
1140
|
+
} else {
|
|
1141
|
+
matchedFiles.forEach((file) => {
|
|
1142
|
+
tasks.push({ platform, file, artifactSelection: file });
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
572
1145
|
}
|
|
573
1146
|
});
|
|
574
1147
|
if (tasks.length === 0) {
|
|
@@ -587,7 +1160,13 @@ var UploadManager = class {
|
|
|
587
1160
|
logger.info(`Scheduled ${tasks.length} upload task(s) for ${validPlatforms.length} platform(s)`);
|
|
588
1161
|
const uploadPromises = tasks.map((task) => {
|
|
589
1162
|
logger.info(`[${task.platform}] Matched file: ${path2.basename(task.file)}`);
|
|
590
|
-
return this.uploadToPlatform(
|
|
1163
|
+
return this.uploadToPlatform(
|
|
1164
|
+
task.platform,
|
|
1165
|
+
task.file,
|
|
1166
|
+
config,
|
|
1167
|
+
metadata,
|
|
1168
|
+
task.artifactSelection
|
|
1169
|
+
);
|
|
591
1170
|
});
|
|
592
1171
|
const results = await Promise.all(uploadPromises);
|
|
593
1172
|
const report = this.aggregateResults(results, tasks.length, startTime);
|
|
@@ -597,7 +1176,7 @@ var UploadManager = class {
|
|
|
597
1176
|
/**
|
|
598
1177
|
* 上传到单个平台
|
|
599
1178
|
*/
|
|
600
|
-
async uploadToPlatform(platform, filePath, config, metadata) {
|
|
1179
|
+
async uploadToPlatform(platform, filePath, config, metadata, artifactSelection = filePath) {
|
|
601
1180
|
if (this.config.cancellationToken?.isCancelled) {
|
|
602
1181
|
logger.warn(`[${platform.toUpperCase()}] Upload skipped due to cancellation`);
|
|
603
1182
|
return {
|
|
@@ -618,25 +1197,30 @@ var UploadManager = class {
|
|
|
618
1197
|
if (this.config.verbose) {
|
|
619
1198
|
logger.info(`[${platform.toUpperCase()}] Starting upload...`);
|
|
620
1199
|
}
|
|
621
|
-
const
|
|
622
|
-
|
|
1200
|
+
const effectiveMetadata = {
|
|
1201
|
+
...metadata,
|
|
1202
|
+
artifactPath: artifactSelection
|
|
1203
|
+
};
|
|
1204
|
+
await this.validateArtifactSelection(platform, artifactSelection, effectiveMetadata);
|
|
1205
|
+
const pkgInfo = path2.extname(filePath).toLowerCase() === ".aab" ? null : await parsePackageInfo(filePath, {
|
|
1206
|
+
androidSdkPath: effectiveMetadata.globalSettings?.androidSdkPath
|
|
623
1207
|
});
|
|
624
1208
|
if (pkgInfo) {
|
|
625
1209
|
if (pkgInfo.versionName) {
|
|
626
|
-
|
|
1210
|
+
effectiveMetadata.version = pkgInfo.versionName;
|
|
627
1211
|
}
|
|
628
1212
|
if (pkgInfo.versionCode) {
|
|
629
|
-
|
|
1213
|
+
effectiveMetadata.buildNumber = pkgInfo.versionCode;
|
|
630
1214
|
}
|
|
631
1215
|
if (pkgInfo.packageName) {
|
|
632
1216
|
;
|
|
633
|
-
|
|
1217
|
+
effectiveMetadata.packageName = pkgInfo.packageName;
|
|
634
1218
|
}
|
|
635
1219
|
logger.info(
|
|
636
|
-
`[${platform.toUpperCase()}] \u5DF2\u6821\u51C6\u7248\u672C\u4FE1\u606F: v${
|
|
1220
|
+
`[${platform.toUpperCase()}] \u5DF2\u6821\u51C6\u7248\u672C\u4FE1\u606F: v${effectiveMetadata.version}+${effectiveMetadata.buildNumber}`
|
|
637
1221
|
);
|
|
638
1222
|
}
|
|
639
|
-
const result = await uploader.upload(filePath, config,
|
|
1223
|
+
const result = await uploader.upload(filePath, config, effectiveMetadata);
|
|
640
1224
|
return result;
|
|
641
1225
|
} catch (error) {
|
|
642
1226
|
const errorMessage = error.message || String(error);
|
|
@@ -649,6 +1233,85 @@ var UploadManager = class {
|
|
|
649
1233
|
};
|
|
650
1234
|
}
|
|
651
1235
|
}
|
|
1236
|
+
shouldGroupArtifacts(platform) {
|
|
1237
|
+
return GROUPED_ARTIFACT_PLATFORMS.has(platform);
|
|
1238
|
+
}
|
|
1239
|
+
resolveArtifactSelection(platform, matchedFiles, metadata) {
|
|
1240
|
+
const explicitArtifacts = metadata.platformArtifacts?.[platform];
|
|
1241
|
+
if (explicitArtifacts) {
|
|
1242
|
+
if (typeof explicitArtifacts === "string") {
|
|
1243
|
+
return explicitArtifacts;
|
|
1244
|
+
}
|
|
1245
|
+
if (Object.keys(explicitArtifacts).length > 0) {
|
|
1246
|
+
return explicitArtifacts;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
if (matchedFiles.length === 0) {
|
|
1250
|
+
return null;
|
|
1251
|
+
}
|
|
1252
|
+
if (matchedFiles.length === 1) {
|
|
1253
|
+
return matchedFiles[0];
|
|
1254
|
+
}
|
|
1255
|
+
return this.buildArtifactMapFromFiles(matchedFiles);
|
|
1256
|
+
}
|
|
1257
|
+
buildArtifactMapFromFiles(files) {
|
|
1258
|
+
return files.reduce(
|
|
1259
|
+
(acc, filePath, index) => {
|
|
1260
|
+
acc[this.inferArtifactKey(filePath, index)] = filePath;
|
|
1261
|
+
return acc;
|
|
1262
|
+
},
|
|
1263
|
+
{}
|
|
1264
|
+
);
|
|
1265
|
+
}
|
|
1266
|
+
inferArtifactKey(filePath, index) {
|
|
1267
|
+
const fileName = path2.basename(filePath).toLowerCase();
|
|
1268
|
+
if (fileName.includes("arm64-v8a")) return "arm64-v8a";
|
|
1269
|
+
if (fileName.includes("armeabi-v7a")) return "armeabi-v7a";
|
|
1270
|
+
if (fileName.includes("x86_64")) return "x86_64";
|
|
1271
|
+
if (fileName.includes("universal")) return "universal";
|
|
1272
|
+
return `artifact_${index + 1}`;
|
|
1273
|
+
}
|
|
1274
|
+
getRepresentativeFilePath(artifactSelection) {
|
|
1275
|
+
if (typeof artifactSelection === "string") {
|
|
1276
|
+
return artifactSelection;
|
|
1277
|
+
}
|
|
1278
|
+
return Object.values(artifactSelection)[0];
|
|
1279
|
+
}
|
|
1280
|
+
async validateArtifactSelection(platform, artifactSelection, metadata) {
|
|
1281
|
+
if (typeof artifactSelection === "string") {
|
|
1282
|
+
return;
|
|
1283
|
+
}
|
|
1284
|
+
const artifactPaths = Object.values(artifactSelection).filter(Boolean);
|
|
1285
|
+
if (artifactPaths.length < 2) {
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
const parsedInfos = await Promise.all(
|
|
1289
|
+
artifactPaths.map(
|
|
1290
|
+
(artifactPath) => parsePackageInfo(artifactPath, {
|
|
1291
|
+
androidSdkPath: metadata.globalSettings?.androidSdkPath
|
|
1292
|
+
})
|
|
1293
|
+
)
|
|
1294
|
+
);
|
|
1295
|
+
const availableInfos = parsedInfos.filter((info) => !!info);
|
|
1296
|
+
if (availableInfos.length < 2) {
|
|
1297
|
+
logger.warn(
|
|
1298
|
+
`[${platform.toUpperCase()}] Unable to fully validate multi-artifact package info; continuing with upload.`
|
|
1299
|
+
);
|
|
1300
|
+
return;
|
|
1301
|
+
}
|
|
1302
|
+
const [baseInfo, ...restInfos] = availableInfos;
|
|
1303
|
+
for (const info of restInfos) {
|
|
1304
|
+
if (baseInfo?.packageName && info?.packageName && baseInfo.packageName !== info.packageName) {
|
|
1305
|
+
throw new Error("\u53CC\u5305\u4E0A\u4F20\u5931\u8D25\uFF1A\u6240\u9009\u5B89\u88C5\u5305\u7684 packageName \u4E0D\u4E00\u81F4");
|
|
1306
|
+
}
|
|
1307
|
+
if (baseInfo?.versionName && info?.versionName && baseInfo.versionName !== info.versionName) {
|
|
1308
|
+
throw new Error("\u53CC\u5305\u4E0A\u4F20\u5931\u8D25\uFF1A\u6240\u9009\u5B89\u88C5\u5305\u7684 versionName \u4E0D\u4E00\u81F4");
|
|
1309
|
+
}
|
|
1310
|
+
if (baseInfo?.versionCode && info?.versionCode && baseInfo.versionCode !== info.versionCode) {
|
|
1311
|
+
throw new Error("\u53CC\u5305\u4E0A\u4F20\u5931\u8D25\uFF1A\u6240\u9009\u5B89\u88C5\u5305\u7684 versionCode \u4E0D\u4E00\u81F4");
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
652
1315
|
/**
|
|
653
1316
|
* 聚合上传结果
|
|
654
1317
|
*/
|
|
@@ -896,6 +1559,280 @@ var AppleJWTGenerator = class _AppleJWTGenerator {
|
|
|
896
1559
|
}
|
|
897
1560
|
};
|
|
898
1561
|
|
|
1562
|
+
// src/utils/release-receipt.ts
|
|
1563
|
+
import { createHash, randomUUID } from "crypto";
|
|
1564
|
+
import { createReadStream } from "fs";
|
|
1565
|
+
import { chmod, mkdir, readFile, realpath, rename, stat, writeFile } from "fs/promises";
|
|
1566
|
+
import { homedir } from "os";
|
|
1567
|
+
import path3 from "path";
|
|
1568
|
+
var RELEASE_RECEIPT_SCHEMA_VERSION = 1;
|
|
1569
|
+
var DEFAULT_RELEASE_RECEIPT_LIMIT = 100;
|
|
1570
|
+
var REMOTE_IDENTIFIER_KEYS = [
|
|
1571
|
+
"id",
|
|
1572
|
+
"appId",
|
|
1573
|
+
"buildId",
|
|
1574
|
+
"versionId",
|
|
1575
|
+
"releaseId",
|
|
1576
|
+
"editId",
|
|
1577
|
+
"versionCode",
|
|
1578
|
+
"track",
|
|
1579
|
+
"apiTrack",
|
|
1580
|
+
"status",
|
|
1581
|
+
"committed"
|
|
1582
|
+
];
|
|
1583
|
+
function resolveReleaseProjectRoot(projectRoot, configPath) {
|
|
1584
|
+
return path3.resolve(projectRoot || path3.dirname(path3.resolve(configPath)));
|
|
1585
|
+
}
|
|
1586
|
+
function sanitizeReleaseMessage(value, maxLength = 500) {
|
|
1587
|
+
let message = typeof value === "string" ? value : String(value ?? "");
|
|
1588
|
+
message = message.replace(/-----BEGIN [^-]*PRIVATE KEY-----[\s\S]*?-----END [^-]*PRIVATE KEY-----/gi, "[\u5DF2\u8131\u654F]").replace(/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [\u5DF2\u8131\u654F]").replace(
|
|
1589
|
+
/(["']?(?:access[_-]?token|refresh[_-]?token|client[_-]?secret|private[_-]?key|api[_-]?key|app[_-]?password|authorization|credential|service[_-]?account(?:[_-]?json)?[_-]?content)["']?\s*[:=]\s*)["']?[^\s,"'}]+["']?/gi,
|
|
1590
|
+
"$1[\u5DF2\u8131\u654F]"
|
|
1591
|
+
).replace(/[\r\n\t]+/g, " ").replace(/\s{2,}/g, " ").trim();
|
|
1592
|
+
return message.length > maxLength ? `${message.slice(0, maxLength - 1)}\u2026` : message;
|
|
1593
|
+
}
|
|
1594
|
+
function createUploadTargets(results) {
|
|
1595
|
+
return results.map((result) => {
|
|
1596
|
+
const platform = sanitizeReleaseMessage(result.platform, 80) || "unknown";
|
|
1597
|
+
const data = isRecord(result.data) ? result.data : void 0;
|
|
1598
|
+
const isDraft = result.success && platform === "google_play" && String(data?.status || "").toLowerCase() === "draft";
|
|
1599
|
+
const remote = createRemoteReceipt(result, data);
|
|
1600
|
+
return {
|
|
1601
|
+
platform,
|
|
1602
|
+
state: result.success ? isDraft ? "draft_created" : "uploaded" : "failed",
|
|
1603
|
+
outcome: result.success ? "success" : "failed",
|
|
1604
|
+
message: sanitizeReleaseMessage(result.message) || (result.success ? "\u4E0A\u4F20\u6210\u529F" : "\u4E0A\u4F20\u5931\u8D25"),
|
|
1605
|
+
...remote ? { remote } : {}
|
|
1606
|
+
};
|
|
1607
|
+
});
|
|
1608
|
+
}
|
|
1609
|
+
async function createReleaseReceipt(input) {
|
|
1610
|
+
const projectRoot = await canonicalizeProjectRoot(input.projectRoot);
|
|
1611
|
+
const startedAt = toDate(input.startedAt ?? Date.now());
|
|
1612
|
+
const finishedAt = toDate(input.finishedAt ?? Date.now());
|
|
1613
|
+
const targets = input.targets.map(sanitizeTarget);
|
|
1614
|
+
return {
|
|
1615
|
+
schemaVersion: RELEASE_RECEIPT_SCHEMA_VERSION,
|
|
1616
|
+
id: randomUUID(),
|
|
1617
|
+
projectKey: createProjectKey(projectRoot),
|
|
1618
|
+
projectRoot,
|
|
1619
|
+
kind: input.kind,
|
|
1620
|
+
...cleanOptionalText(input.appName, 160) ? { appName: cleanOptionalText(input.appName, 160) } : {},
|
|
1621
|
+
...cleanOptionalText(input.packageName, 240) ? { packageName: cleanOptionalText(input.packageName, 240) } : {},
|
|
1622
|
+
...cleanOptionalText(input.versionName, 80) ? { versionName: cleanOptionalText(input.versionName, 80) } : {},
|
|
1623
|
+
...input.buildNumber !== void 0 ? { buildNumber: cleanOptionalText(String(input.buildNumber), 80) } : {},
|
|
1624
|
+
...cleanOptionalText(input.changelog, 4e3) ? { changelog: cleanOptionalText(input.changelog, 4e3) } : {},
|
|
1625
|
+
artifacts: await inspectArtifacts(input.artifactPaths),
|
|
1626
|
+
targets,
|
|
1627
|
+
outcome: input.outcome ?? inferOutcome(targets),
|
|
1628
|
+
startedAt: startedAt.toISOString(),
|
|
1629
|
+
finishedAt: finishedAt.toISOString(),
|
|
1630
|
+
durationMs: Math.max(0, finishedAt.getTime() - startedAt.getTime())
|
|
1631
|
+
};
|
|
1632
|
+
}
|
|
1633
|
+
function formatReleaseReceipt(receipt) {
|
|
1634
|
+
const lines = [
|
|
1635
|
+
`${receipt.appName || receipt.packageName || "\u5E94\u7528"} \xB7 ${receipt.kind === "build" ? "\u6784\u5EFA\u56DE\u6267" : "\u4E0A\u4F20\u56DE\u6267"}`,
|
|
1636
|
+
`\u7ED3\u679C\uFF1A${receipt.outcome}`,
|
|
1637
|
+
`\u7248\u672C\uFF1A${receipt.versionName || "-"} (${receipt.buildNumber || "-"})`,
|
|
1638
|
+
`\u5B8C\u6210\u65F6\u95F4\uFF1A${receipt.finishedAt}`
|
|
1639
|
+
];
|
|
1640
|
+
if (receipt.packageName) lines.push(`\u5305\u540D\uFF1A${receipt.packageName}`);
|
|
1641
|
+
if (receipt.changelog) lines.push(`\u66F4\u65B0\u65E5\u5FD7\uFF1A${receipt.changelog}`);
|
|
1642
|
+
for (const artifact of receipt.artifacts) {
|
|
1643
|
+
lines.push(
|
|
1644
|
+
`\u4EA7\u7269\uFF1A${artifact.path}${artifact.sizeBytes !== void 0 ? ` \xB7 ${artifact.sizeBytes} bytes` : ""}${artifact.sha256 ? ` \xB7 SHA-256 ${artifact.sha256}` : ""}`
|
|
1645
|
+
);
|
|
1646
|
+
}
|
|
1647
|
+
for (const target of receipt.targets) {
|
|
1648
|
+
lines.push(`${target.platform}\uFF1A${target.state} \xB7 ${target.message}`);
|
|
1649
|
+
if (target.remote?.consoleUrl) lines.push(`\u540E\u53F0\uFF1A${target.remote.consoleUrl}`);
|
|
1650
|
+
}
|
|
1651
|
+
return lines.join("\n");
|
|
1652
|
+
}
|
|
1653
|
+
var ReleaseReceiptStore = class {
|
|
1654
|
+
storageRoot;
|
|
1655
|
+
maxEntries;
|
|
1656
|
+
constructor(options = {}) {
|
|
1657
|
+
this.storageRoot = path3.resolve(options.storageRoot || path3.join(homedir(), ".flu-cli", "release-receipts"));
|
|
1658
|
+
this.maxEntries = Math.max(1, options.maxEntries ?? DEFAULT_RELEASE_RECEIPT_LIMIT);
|
|
1659
|
+
}
|
|
1660
|
+
async append(receipt) {
|
|
1661
|
+
await mkdir(this.storageRoot, { recursive: true, mode: 448 });
|
|
1662
|
+
await chmod(this.storageRoot, 448);
|
|
1663
|
+
const filePath = this.getFilePath(receipt.projectKey);
|
|
1664
|
+
const current = await this.readFile(filePath, receipt.projectKey);
|
|
1665
|
+
const receipts = [receipt, ...current.receipts.filter((item) => item.id !== receipt.id)].slice(
|
|
1666
|
+
0,
|
|
1667
|
+
this.maxEntries
|
|
1668
|
+
);
|
|
1669
|
+
const tempPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
1670
|
+
await writeFile(
|
|
1671
|
+
tempPath,
|
|
1672
|
+
`${JSON.stringify({
|
|
1673
|
+
schemaVersion: RELEASE_RECEIPT_SCHEMA_VERSION,
|
|
1674
|
+
projectKey: receipt.projectKey,
|
|
1675
|
+
receipts
|
|
1676
|
+
}, null, 2)}
|
|
1677
|
+
`,
|
|
1678
|
+
{ encoding: "utf8", mode: 384 }
|
|
1679
|
+
);
|
|
1680
|
+
await rename(tempPath, filePath);
|
|
1681
|
+
await chmod(filePath, 384);
|
|
1682
|
+
}
|
|
1683
|
+
async list(projectRoot, limit = 20) {
|
|
1684
|
+
const canonicalRoot = await canonicalizeProjectRoot(projectRoot);
|
|
1685
|
+
const projectKey = createProjectKey(canonicalRoot);
|
|
1686
|
+
const data = await this.readFile(this.getFilePath(projectKey), projectKey);
|
|
1687
|
+
return data.receipts.slice(0, Math.max(0, limit));
|
|
1688
|
+
}
|
|
1689
|
+
async save(input) {
|
|
1690
|
+
const receipt = await createReleaseReceipt(input);
|
|
1691
|
+
await this.append(receipt);
|
|
1692
|
+
return receipt;
|
|
1693
|
+
}
|
|
1694
|
+
getFilePath(projectKey) {
|
|
1695
|
+
return path3.join(this.storageRoot, `${projectKey}.json`);
|
|
1696
|
+
}
|
|
1697
|
+
async readFile(filePath, projectKey) {
|
|
1698
|
+
try {
|
|
1699
|
+
const raw = JSON.parse(await readFile(filePath, "utf8"));
|
|
1700
|
+
return {
|
|
1701
|
+
schemaVersion: RELEASE_RECEIPT_SCHEMA_VERSION,
|
|
1702
|
+
projectKey,
|
|
1703
|
+
receipts: Array.isArray(raw.receipts) ? raw.receipts : []
|
|
1704
|
+
};
|
|
1705
|
+
} catch (error) {
|
|
1706
|
+
if (error?.code === "ENOENT") {
|
|
1707
|
+
return { schemaVersion: RELEASE_RECEIPT_SCHEMA_VERSION, projectKey, receipts: [] };
|
|
1708
|
+
}
|
|
1709
|
+
throw new Error(`\u8BFB\u53D6\u53D1\u5E03\u56DE\u6267\u5931\u8D25: ${sanitizeReleaseMessage(error?.message || error)}`);
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
};
|
|
1713
|
+
function createProjectKey(projectRoot) {
|
|
1714
|
+
return createHash("sha256").update(path3.resolve(projectRoot)).digest("hex").slice(0, 24);
|
|
1715
|
+
}
|
|
1716
|
+
function createRemoteReceipt(result, data) {
|
|
1717
|
+
const identifiers = {};
|
|
1718
|
+
for (const key of REMOTE_IDENTIFIER_KEYS) {
|
|
1719
|
+
const value = data?.[key];
|
|
1720
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
1721
|
+
identifiers[key] = typeof value === "string" ? sanitizeReleaseMessage(value, 240) : value;
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
const consoleUrl = cleanHttpUrl(result.consoleUrl);
|
|
1725
|
+
const downloadUrl = cleanHttpUrl(result.url || result.link);
|
|
1726
|
+
if (!consoleUrl && !downloadUrl && Object.keys(identifiers).length === 0) return void 0;
|
|
1727
|
+
return {
|
|
1728
|
+
...consoleUrl ? { consoleUrl } : {},
|
|
1729
|
+
...downloadUrl ? { downloadUrl } : {},
|
|
1730
|
+
...Object.keys(identifiers).length > 0 ? { identifiers } : {}
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1733
|
+
function sanitizeTarget(target) {
|
|
1734
|
+
return {
|
|
1735
|
+
platform: sanitizeReleaseMessage(target.platform, 80) || "unknown",
|
|
1736
|
+
state: target.state,
|
|
1737
|
+
outcome: target.outcome,
|
|
1738
|
+
message: sanitizeReleaseMessage(target.message),
|
|
1739
|
+
...target.remote ? { remote: sanitizeRemote(target.remote) } : {},
|
|
1740
|
+
...cleanOptionalText(target.lastKnownRemoteStatus, 120) ? { lastKnownRemoteStatus: cleanOptionalText(target.lastKnownRemoteStatus, 120) } : {},
|
|
1741
|
+
...cleanOptionalText(target.checkedAt, 80) ? { checkedAt: cleanOptionalText(target.checkedAt, 80) } : {}
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
function sanitizeRemote(remote) {
|
|
1745
|
+
const identifiers = {};
|
|
1746
|
+
for (const [key, value] of Object.entries(remote.identifiers || {})) {
|
|
1747
|
+
if (!REMOTE_IDENTIFIER_KEYS.includes(key)) continue;
|
|
1748
|
+
if (typeof value === "string") identifiers[key] = sanitizeReleaseMessage(value, 240);
|
|
1749
|
+
if (typeof value === "number" || typeof value === "boolean") identifiers[key] = value;
|
|
1750
|
+
}
|
|
1751
|
+
const consoleUrl = cleanHttpUrl(remote.consoleUrl);
|
|
1752
|
+
const downloadUrl = cleanHttpUrl(remote.downloadUrl);
|
|
1753
|
+
return {
|
|
1754
|
+
...consoleUrl ? { consoleUrl } : {},
|
|
1755
|
+
...downloadUrl ? { downloadUrl } : {},
|
|
1756
|
+
...Object.keys(identifiers).length > 0 ? { identifiers } : {}
|
|
1757
|
+
};
|
|
1758
|
+
}
|
|
1759
|
+
async function inspectArtifacts(value) {
|
|
1760
|
+
const paths = Array.from(new Set(flattenArtifactPaths(value).map((item) => path3.resolve(item))));
|
|
1761
|
+
return Promise.all(paths.map(inspectArtifact));
|
|
1762
|
+
}
|
|
1763
|
+
async function inspectArtifact(filePath) {
|
|
1764
|
+
const base = { path: filePath, name: path3.basename(filePath) };
|
|
1765
|
+
try {
|
|
1766
|
+
const info = await stat(filePath);
|
|
1767
|
+
if (!info.isFile()) return { ...base, available: false };
|
|
1768
|
+
return {
|
|
1769
|
+
...base,
|
|
1770
|
+
sizeBytes: info.size,
|
|
1771
|
+
sha256: await hashFile(filePath),
|
|
1772
|
+
available: true
|
|
1773
|
+
};
|
|
1774
|
+
} catch {
|
|
1775
|
+
return { ...base, available: false };
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
function hashFile(filePath) {
|
|
1779
|
+
return new Promise((resolve, reject) => {
|
|
1780
|
+
const hash = createHash("sha256");
|
|
1781
|
+
const stream = createReadStream(filePath);
|
|
1782
|
+
stream.on("error", reject);
|
|
1783
|
+
stream.on("data", (chunk) => hash.update(chunk));
|
|
1784
|
+
stream.on("end", () => resolve(hash.digest("hex")));
|
|
1785
|
+
});
|
|
1786
|
+
}
|
|
1787
|
+
function flattenArtifactPaths(value) {
|
|
1788
|
+
if (typeof value === "string" && value.trim()) return [value];
|
|
1789
|
+
if (Array.isArray(value)) return value.flatMap(flattenArtifactPaths);
|
|
1790
|
+
if (isRecord(value)) return Object.values(value).flatMap(flattenArtifactPaths);
|
|
1791
|
+
return [];
|
|
1792
|
+
}
|
|
1793
|
+
function inferOutcome(targets) {
|
|
1794
|
+
if (targets.length === 0) return "failed";
|
|
1795
|
+
if (targets.every((target) => target.outcome === "cancelled")) return "cancelled";
|
|
1796
|
+
const successCount = targets.filter((target) => target.outcome === "success").length;
|
|
1797
|
+
if (successCount === targets.length) return "success";
|
|
1798
|
+
if (successCount > 0) return "partial";
|
|
1799
|
+
return "failed";
|
|
1800
|
+
}
|
|
1801
|
+
async function canonicalizeProjectRoot(projectRoot) {
|
|
1802
|
+
const resolved = path3.resolve(projectRoot);
|
|
1803
|
+
try {
|
|
1804
|
+
return await realpath(resolved);
|
|
1805
|
+
} catch {
|
|
1806
|
+
return resolved;
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
function toDate(value) {
|
|
1810
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
1811
|
+
return Number.isNaN(date.getTime()) ? /* @__PURE__ */ new Date() : date;
|
|
1812
|
+
}
|
|
1813
|
+
function cleanOptionalText(value, maxLength) {
|
|
1814
|
+
const cleaned = sanitizeReleaseMessage(value, maxLength);
|
|
1815
|
+
return cleaned || void 0;
|
|
1816
|
+
}
|
|
1817
|
+
function cleanHttpUrl(value) {
|
|
1818
|
+
if (typeof value !== "string") return void 0;
|
|
1819
|
+
try {
|
|
1820
|
+
const url = new URL(value);
|
|
1821
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return void 0;
|
|
1822
|
+
for (const key of Array.from(url.searchParams.keys())) {
|
|
1823
|
+
if (/token|secret|password|signature|credential|key/i.test(key)) {
|
|
1824
|
+
url.searchParams.set(key, "[\u5DF2\u8131\u654F]");
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
return url.toString();
|
|
1828
|
+
} catch {
|
|
1829
|
+
return void 0;
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
function isRecord(value) {
|
|
1833
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
1834
|
+
}
|
|
1835
|
+
|
|
899
1836
|
// src/adapters/pgyer_uploader.ts
|
|
900
1837
|
var PgyerUploader = class _PgyerUploader {
|
|
901
1838
|
name = "Pgyer";
|
|
@@ -1014,7 +1951,7 @@ var PgyerUploader = class _PgyerUploader {
|
|
|
1014
1951
|
|
|
1015
1952
|
// src/adapters/huawei_uploader.ts
|
|
1016
1953
|
import fs5 from "fs-extra";
|
|
1017
|
-
import
|
|
1954
|
+
import path4 from "path";
|
|
1018
1955
|
import { promisify } from "util";
|
|
1019
1956
|
import axios2 from "axios";
|
|
1020
1957
|
import FormData2 from "form-data";
|
|
@@ -1093,7 +2030,7 @@ var HuaweiUploader = class _HuaweiUploader {
|
|
|
1093
2030
|
const clientSecret = hwConfig.auth.clientSecret;
|
|
1094
2031
|
const appId = hwConfig.appId;
|
|
1095
2032
|
const releaseType = hwConfig.releaseType || "draft";
|
|
1096
|
-
const suffix =
|
|
2033
|
+
const suffix = path4.extname(filePath).substring(1);
|
|
1097
2034
|
const retryConfig = config.retry;
|
|
1098
2035
|
const tokenUrl = this.getTokenUrl(config);
|
|
1099
2036
|
const publishApiRoot = this.getApiRoot(config);
|
|
@@ -1114,27 +2051,39 @@ var HuaweiUploader = class _HuaweiUploader {
|
|
|
1114
2051
|
clientId,
|
|
1115
2052
|
appId
|
|
1116
2053
|
};
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
2054
|
+
const artifacts = typeof metadata.artifactPath === "object" ? metadata.artifactPath : { universal: filePath || metadata.artifactPath || "" };
|
|
2055
|
+
logger.info(`[Huawei] \u5904\u7406\u4EA7\u7269\u6570\u91CF: ${Object.keys(artifacts).length}`);
|
|
2056
|
+
const uploadResults = [];
|
|
2057
|
+
const uploadPromises = Object.entries(artifacts).map(async ([abi, p]) => {
|
|
2058
|
+
if (!p || !fs5.existsSync(p)) return;
|
|
2059
|
+
const currentSuffix = path4.extname(p).substring(1);
|
|
2060
|
+
logger.info(`[Huawei] \u6B63\u5728\u4E0A\u4F20 ${abi} \u5305: ${path4.basename(p)}`);
|
|
2061
|
+
const uploadInfo = await retryStrategy.execute(
|
|
2062
|
+
() => this.getUploadUrl(ctx, currentSuffix),
|
|
2063
|
+
`Huawei get upload URL for ${abi}`
|
|
2064
|
+
);
|
|
2065
|
+
if (!uploadInfo.success || !uploadInfo.data) {
|
|
2066
|
+
throw uploadInfo.error || new Error(`Failed to get upload URL for ${abi}`);
|
|
2067
|
+
}
|
|
2068
|
+
const uploadFileRes = await retryStrategy.execute(
|
|
2069
|
+
() => this.executeUploadRequest(uploadInfo.data.uploadUrl, uploadInfo.data.authCode, p),
|
|
2070
|
+
`Huawei upload file for ${abi}`
|
|
2071
|
+
);
|
|
2072
|
+
if (!uploadFileRes.success || !uploadFileRes.data || uploadFileRes.data.length === 0) {
|
|
2073
|
+
throw uploadFileRes.error || new Error(`File upload failed for ${abi}`);
|
|
2074
|
+
}
|
|
2075
|
+
uploadResults.push({
|
|
2076
|
+
fileName: path4.basename(p),
|
|
2077
|
+
fileDestUrl: uploadFileRes.data[0].fileDestUrl
|
|
2078
|
+
});
|
|
2079
|
+
});
|
|
2080
|
+
await Promise.all(uploadPromises);
|
|
2081
|
+
if (uploadResults.length === 0) {
|
|
2082
|
+
throw new Error("No files were successfully uploaded");
|
|
1133
2083
|
}
|
|
1134
|
-
|
|
1135
|
-
logger.info("Linking file to app...");
|
|
2084
|
+
logger.info(`[Huawei] \u6B63\u5728\u5173\u8054 ${uploadResults.length} \u4E2A\u6587\u4EF6\u5230\u5E94\u7528...`);
|
|
1136
2085
|
const updateInfoResult = await retryStrategy.execute(
|
|
1137
|
-
() => this.updateAppFileInfo(ctx,
|
|
2086
|
+
() => this.updateAppFileInfo(ctx, uploadResults, filePath),
|
|
1138
2087
|
"Huawei update file info"
|
|
1139
2088
|
);
|
|
1140
2089
|
if (!updateInfoResult.success) {
|
|
@@ -1296,7 +2245,7 @@ var HuaweiUploader = class _HuaweiUploader {
|
|
|
1296
2245
|
}
|
|
1297
2246
|
/** 通用文件上传(FormData 方式,无需 API 认证头) */
|
|
1298
2247
|
async executeUploadRequest(uploadUrl, authCode, filePath) {
|
|
1299
|
-
const fileName =
|
|
2248
|
+
const fileName = path4.basename(filePath);
|
|
1300
2249
|
const fileSize = fs5.statSync(filePath).size;
|
|
1301
2250
|
const form = new FormData2();
|
|
1302
2251
|
form.append("file", fs5.createReadStream(filePath), {
|
|
@@ -1338,14 +2287,17 @@ var HuaweiUploader = class _HuaweiUploader {
|
|
|
1338
2287
|
throw new Error("Upload succeeded but fileDestUrl not found in response");
|
|
1339
2288
|
}
|
|
1340
2289
|
/** 更新应用文件信息 */
|
|
1341
|
-
async updateAppFileInfo(ctx,
|
|
1342
|
-
const
|
|
1343
|
-
const fileType =
|
|
2290
|
+
async updateAppFileInfo(ctx, files, anyFilePath) {
|
|
2291
|
+
const hasAab = files.some((f) => f.fileName.toLowerCase().endsWith(".aab"));
|
|
2292
|
+
const fileType = hasAab ? 7 /* AAB */ : 5 /* APP_PACKAGE */;
|
|
1344
2293
|
const res = await axios2.put(
|
|
1345
2294
|
`${ctx.apiRoot}/app-file-info`,
|
|
1346
2295
|
{
|
|
1347
2296
|
fileType,
|
|
1348
|
-
files:
|
|
2297
|
+
files: files.map((f) => ({
|
|
2298
|
+
fileName: f.fileName,
|
|
2299
|
+
fileDestUrl: f.fileDestUrl
|
|
2300
|
+
}))
|
|
1349
2301
|
},
|
|
1350
2302
|
{
|
|
1351
2303
|
params: { appId: ctx.appId },
|
|
@@ -1460,7 +2412,7 @@ var HuaweiUploader = class _HuaweiUploader {
|
|
|
1460
2412
|
logger.warn(`Asset file not found, skipping: ${filePath}`);
|
|
1461
2413
|
continue;
|
|
1462
2414
|
}
|
|
1463
|
-
const suffix =
|
|
2415
|
+
const suffix = path4.extname(filePath).substring(1).toLowerCase();
|
|
1464
2416
|
if (!HUAWEI_SUPPORTED_SUFFIXES.image.includes(suffix)) {
|
|
1465
2417
|
logger.warn(`Unsupported file type ${suffix}, skipping: ${filePath}`);
|
|
1466
2418
|
continue;
|
|
@@ -1515,6 +2467,7 @@ var HuaweiUploader = class _HuaweiUploader {
|
|
|
1515
2467
|
|
|
1516
2468
|
// src/adapters/xiaomi_uploader.ts
|
|
1517
2469
|
import fs6 from "fs-extra";
|
|
2470
|
+
import path5 from "path";
|
|
1518
2471
|
import axios3 from "axios";
|
|
1519
2472
|
import FormData3 from "form-data";
|
|
1520
2473
|
import crypto3 from "crypto";
|
|
@@ -1760,85 +2713,44 @@ ${lines.join("\n")}
|
|
|
1760
2713
|
logger.warn(`\u83B7\u53D6\u5206\u7C7B\u5217\u8868\u5F02\u5E38: ${error.message}`);
|
|
1761
2714
|
return null;
|
|
1762
2715
|
}
|
|
1763
|
-
}
|
|
1764
|
-
async upload(filePath, config, metadata) {
|
|
1765
|
-
const xiaomiConfig = config.publish?.xiaomi;
|
|
1766
|
-
const { userName, privateKey, publicKeyPath } = xiaomiConfig.auth;
|
|
1767
|
-
const updateDesc = metadata.changelog || xiaomiConfig?.description || "App Update";
|
|
1768
|
-
const
|
|
1769
|
-
if (!fs6.existsSync(filePath)) {
|
|
1770
|
-
return {
|
|
1771
|
-
platform: "xiaomi",
|
|
1772
|
-
success: false,
|
|
1773
|
-
message: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}`
|
|
1774
|
-
};
|
|
1775
|
-
}
|
|
2716
|
+
}
|
|
2717
|
+
async upload(filePath, config, metadata) {
|
|
2718
|
+
const xiaomiConfig = config.publish?.xiaomi;
|
|
2719
|
+
const { userName, privateKey, publicKeyPath } = xiaomiConfig.auth;
|
|
2720
|
+
const updateDesc = metadata.changelog || xiaomiConfig?.description || "App Update";
|
|
2721
|
+
const artifacts = typeof metadata.artifactPath === "object" ? metadata.artifactPath : { universal: filePath || (typeof metadata.artifactPath === "string" ? metadata.artifactPath : "") };
|
|
1776
2722
|
const resolvedPublicKey = await this.resolvePublicKey(publicKeyPath);
|
|
1777
2723
|
if (!resolvedPublicKey) {
|
|
1778
|
-
|
|
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
|
-
};
|
|
2724
|
+
throw new Error("\u65E0\u6CD5\u83B7\u53D6 RSA \u516C\u94A5");
|
|
1799
2725
|
}
|
|
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
2726
|
const publicKeyObj = this.extractPublicKey(resolvedPublicKey);
|
|
1805
|
-
const
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
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
|
-
}
|
|
2727
|
+
const anyPath = Object.values(artifacts)[0];
|
|
2728
|
+
const packageName = xiaomiConfig.packageName || this.inferPackageName(anyPath);
|
|
2729
|
+
if (!packageName) throw new Error("\u7F3A\u5C11 packageName");
|
|
2730
|
+
const appName = xiaomiConfig.appName || this.inferAppName(packageName);
|
|
1830
2731
|
try {
|
|
1831
|
-
|
|
2732
|
+
logger.info(`\u5F00\u59CB\u4E0A\u4F20\u5230\u5C0F\u7C73\u5E94\u7528\u5546\u5E97 (\u5305\u542B ${Object.keys(artifacts).length} \u4E2A\u5305)...`);
|
|
2733
|
+
let uploadType = xiaomiConfig.uploadType || "auto";
|
|
2734
|
+
let finalUploadType;
|
|
2735
|
+
let existingAppInfo = null;
|
|
2736
|
+
if (uploadType === "auto") {
|
|
2737
|
+
const status = await this.queryAppStatus(userName, packageName, privateKey, publicKeyObj);
|
|
2738
|
+
finalUploadType = status.type;
|
|
2739
|
+
existingAppInfo = status.appInfo;
|
|
2740
|
+
} else {
|
|
2741
|
+
finalUploadType = uploadType;
|
|
2742
|
+
}
|
|
2743
|
+
const retryStrategy = createUploadRetryStrategy(config.retry);
|
|
1832
2744
|
const result = await retryStrategy.execute(
|
|
1833
2745
|
() => this.performUpload({
|
|
1834
|
-
|
|
2746
|
+
artifacts,
|
|
1835
2747
|
userName,
|
|
1836
2748
|
privateKey,
|
|
1837
2749
|
publicKey: resolvedPublicKey,
|
|
2750
|
+
uploadType: finalUploadType,
|
|
1838
2751
|
packageName,
|
|
1839
|
-
appName,
|
|
2752
|
+
appName: appName || "",
|
|
1840
2753
|
updateDesc,
|
|
1841
|
-
uploadType,
|
|
1842
2754
|
category: xiaomiConfig.category || existingAppInfo?.category,
|
|
1843
2755
|
keyWords: xiaomiConfig.keyWords || existingAppInfo?.keyWords,
|
|
1844
2756
|
desc: xiaomiConfig.desc || existingAppInfo?.desc,
|
|
@@ -1848,50 +2760,19 @@ ${lines.join("\n")}
|
|
|
1848
2760
|
}),
|
|
1849
2761
|
"Xiaomi push upload"
|
|
1850
2762
|
);
|
|
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
2763
|
return {
|
|
1856
2764
|
platform: "xiaomi",
|
|
1857
2765
|
success: true,
|
|
1858
|
-
message: "\u4E0A\u4F20\u6210\u529F
|
|
1859
|
-
data: result.data,
|
|
2766
|
+
message: "\u4E0A\u4F20\u6210\u529F",
|
|
1860
2767
|
consoleUrl: "https://dev.mi.com/distribute/app/details"
|
|
1861
2768
|
};
|
|
1862
2769
|
} catch (error) {
|
|
1863
|
-
logger.error(`\u4E0A\u4F20\
|
|
1864
|
-
|
|
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
|
-
};
|
|
2770
|
+
logger.error(`\u5C0F\u7C73\u4E0A\u4F20\u5931\u8D25: ${error.message}`);
|
|
2771
|
+
return { platform: "xiaomi", success: false, message: error.message, error };
|
|
1873
2772
|
}
|
|
1874
2773
|
}
|
|
1875
|
-
/**
|
|
1876
|
-
* 执行实际的上传操作
|
|
1877
|
-
*/
|
|
1878
2774
|
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;
|
|
2775
|
+
const { artifacts, userName, privateKey, publicKey, packageName, appName, updateDesc, uploadType, category, keyWords, desc, brief, privacyUrl, assets } = params;
|
|
1895
2776
|
const publicKeyObj = this.extractPublicKey(publicKey);
|
|
1896
2777
|
const requestData = this.buildRequestData({
|
|
1897
2778
|
userName,
|
|
@@ -1905,67 +2786,55 @@ ${lines.join("\n")}
|
|
|
1905
2786
|
brief,
|
|
1906
2787
|
privacyUrl
|
|
1907
2788
|
});
|
|
1908
|
-
logger.info("RequestData \u5DF2\u6784\u5EFA");
|
|
1909
|
-
logger.info(`RequestData: ${requestData}`);
|
|
1910
2789
|
const requestDataMD5 = this.calculateStringMD5(requestData);
|
|
1911
|
-
|
|
1912
|
-
|
|
2790
|
+
let mainPath;
|
|
2791
|
+
let secondPath = void 0;
|
|
2792
|
+
const artifactEntries = Object.entries(artifacts);
|
|
2793
|
+
const arm64 = artifactEntries.find(([abi]) => abi === "arm64-v8a");
|
|
2794
|
+
const arm32 = artifactEntries.find(([abi]) => abi === "armeabi-v7a");
|
|
2795
|
+
if (arm64 && arm32) {
|
|
2796
|
+
mainPath = arm64[1];
|
|
2797
|
+
secondPath = arm32[1];
|
|
2798
|
+
logger.info(`[Xiaomi] \u8BC6\u522B\u5230\u591A\u67B6\u6784\u5206\u5305: \u4E3B\u5305(64\u4F4D)=${path5.basename(mainPath)}, \u6B21\u5305(32\u4F4D)=${path5.basename(secondPath)}`);
|
|
2799
|
+
} else {
|
|
2800
|
+
mainPath = artifactEntries[0][1];
|
|
2801
|
+
if (artifactEntries.length > 1) {
|
|
2802
|
+
secondPath = artifactEntries[1][1];
|
|
2803
|
+
logger.info(`[Xiaomi] \u8BC6\u522B\u5230\u591A\u4E2A\u5305\uFF08\u975E\u6807\u51C6\u540D\uFF09\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u987A\u5E8F: \u4E3B\u5305=${path5.basename(mainPath)}, \u6B21\u5305=${path5.basename(secondPath)}`);
|
|
2804
|
+
} else {
|
|
2805
|
+
logger.info(`[Xiaomi] \u4F7F\u7528\u5355\u5305\u6A21\u5F0F: ${path5.basename(mainPath)}`);
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
const mainMD5 = await this.calculateFileMD5(mainPath);
|
|
1913
2809
|
const extraSigItems = [];
|
|
1914
|
-
|
|
2810
|
+
if (secondPath) {
|
|
2811
|
+
const secondMD5 = await this.calculateFileMD5(secondPath);
|
|
2812
|
+
extraSigItems.push({ name: "secondApk", hash: secondMD5 });
|
|
2813
|
+
}
|
|
1915
2814
|
if (assets?.icon && fs6.existsSync(assets.icon)) {
|
|
1916
|
-
|
|
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
|
-
}
|
|
2815
|
+
extraSigItems.push({ name: "icon", hash: await this.calculateFileMD5(assets.icon) });
|
|
1930
2816
|
}
|
|
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");
|
|
2817
|
+
const sig = this.generateUploadSIG(requestDataMD5, mainMD5, privateKey, publicKeyObj, extraSigItems);
|
|
1939
2818
|
const form = new FormData3();
|
|
1940
2819
|
form.append("RequestData", requestData);
|
|
1941
2820
|
form.append("SIG", sig);
|
|
1942
|
-
form.append("apk", fs6.createReadStream(
|
|
1943
|
-
if (
|
|
1944
|
-
form.append("
|
|
1945
|
-
logger.info(`\u9644\u52A0 icon: ${iconPath}`);
|
|
2821
|
+
form.append("apk", fs6.createReadStream(mainPath));
|
|
2822
|
+
if (secondPath) {
|
|
2823
|
+
form.append("secondApk", fs6.createReadStream(secondPath));
|
|
1946
2824
|
}
|
|
1947
|
-
|
|
1948
|
-
form.append(
|
|
1949
|
-
logger.info(`\u9644\u52A0 screenshot_${i + 1}: ${screenshotPaths[i]}`);
|
|
2825
|
+
if (assets?.icon && fs6.existsSync(assets.icon)) {
|
|
2826
|
+
form.append("icon", fs6.createReadStream(assets.icon));
|
|
1950
2827
|
}
|
|
1951
|
-
logger.info(`POST ${_XiaomiUploader.PUSH_API}`);
|
|
1952
2828
|
const response = await axios3.post(_XiaomiUploader.PUSH_API, form, {
|
|
1953
|
-
headers:
|
|
1954
|
-
...form.getHeaders()
|
|
1955
|
-
},
|
|
2829
|
+
headers: form.getHeaders(),
|
|
1956
2830
|
maxContentLength: Infinity,
|
|
1957
2831
|
maxBodyLength: Infinity,
|
|
1958
|
-
timeout:
|
|
1959
|
-
// 60 秒请求超时
|
|
2832
|
+
timeout: 12e4
|
|
1960
2833
|
});
|
|
1961
|
-
|
|
1962
|
-
|
|
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}`);
|
|
2834
|
+
if (response.data?.result !== 0) {
|
|
2835
|
+
throw new Error(`\u5C0F\u7C73 API \u9519\u8BEF: ${response.data?.message || JSON.stringify(response.data)}`);
|
|
1966
2836
|
}
|
|
1967
|
-
|
|
1968
|
-
return data;
|
|
2837
|
+
return response.data;
|
|
1969
2838
|
}
|
|
1970
2839
|
/**
|
|
1971
2840
|
* 解析公钥: 优先使用内容字段, 其次从文件路径读取
|
|
@@ -2007,7 +2876,7 @@ ${lines.join("\n")}
|
|
|
2007
2876
|
|
|
2008
2877
|
// src/adapters/apple_store_uploader.ts
|
|
2009
2878
|
import fs7 from "fs-extra";
|
|
2010
|
-
import
|
|
2879
|
+
import path6 from "path";
|
|
2011
2880
|
import { exec as exec2 } from "child_process";
|
|
2012
2881
|
import { promisify as promisify2 } from "util";
|
|
2013
2882
|
import axios4 from "axios";
|
|
@@ -2173,7 +3042,7 @@ var AppleStoreUploader = class {
|
|
|
2173
3042
|
appPassword,
|
|
2174
3043
|
"-showTransferRate"
|
|
2175
3044
|
].join(" ");
|
|
2176
|
-
logger.info(`[Transporter] Uploading: ${
|
|
3045
|
+
logger.info(`[Transporter] Uploading: ${path6.basename(ipaPath)}`);
|
|
2177
3046
|
try {
|
|
2178
3047
|
const { stdout, stderr } = await execAsync(command, {
|
|
2179
3048
|
timeout: 30 * 60 * 1e3,
|
|
@@ -2224,7 +3093,7 @@ Stderr: ${error.stderr || ""}`);
|
|
|
2224
3093
|
});
|
|
2225
3094
|
command = innerArgs.join(" ");
|
|
2226
3095
|
}
|
|
2227
|
-
logger.info(`[altool] Uploading: ${
|
|
3096
|
+
logger.info(`[altool] Uploading: ${path6.basename(ipaPath)}`);
|
|
2228
3097
|
logger.info(`[altool] Command prefix: ${tool.command}`);
|
|
2229
3098
|
try {
|
|
2230
3099
|
const { stdout, stderr } = await execAsync(command, {
|
|
@@ -2808,7 +3677,7 @@ var TestFlightUploader = class _TestFlightUploader {
|
|
|
2808
3677
|
|
|
2809
3678
|
// src/adapters/adhoc_uploader.ts
|
|
2810
3679
|
import fs9 from "fs-extra";
|
|
2811
|
-
import
|
|
3680
|
+
import path7 from "path";
|
|
2812
3681
|
import { execSync } from "child_process";
|
|
2813
3682
|
var AdHocUploader = class {
|
|
2814
3683
|
name = "AdHoc";
|
|
@@ -2941,8 +3810,8 @@ var AdHocUploader = class {
|
|
|
2941
3810
|
* @param changelog 更新日志
|
|
2942
3811
|
*/
|
|
2943
3812
|
async createManifest(ipaPath, version, bundleId, changelog) {
|
|
2944
|
-
const ipaFileName =
|
|
2945
|
-
const appName = bundleId ||
|
|
3813
|
+
const ipaFileName = path7.basename(ipaPath);
|
|
3814
|
+
const appName = bundleId || path7.parse(ipaFileName).name;
|
|
2946
3815
|
const ipaUrl = `itms-services://?action=download-manifest&url=https://your-distribution-server.com/manifests/${ipaFileName}.plist`;
|
|
2947
3816
|
const manifestContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|
2948
3817
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
@@ -3001,14 +3870,14 @@ var AdHocUploader = class {
|
|
|
3001
3870
|
*/
|
|
3002
3871
|
async setupDistributionDirectory(ipaPath, manifestPath) {
|
|
3003
3872
|
const projectRoot = process.cwd();
|
|
3004
|
-
const distributionDir =
|
|
3873
|
+
const distributionDir = path7.join(projectRoot, "build", "adhoc-distribution");
|
|
3005
3874
|
await fs9.ensureDir(distributionDir);
|
|
3006
|
-
await fs9.ensureDir(
|
|
3007
|
-
await fs9.ensureDir(
|
|
3008
|
-
await fs9.ensureDir(
|
|
3009
|
-
const ipaFileName =
|
|
3010
|
-
await fs9.copy(ipaPath,
|
|
3011
|
-
await fs9.copy(manifestPath,
|
|
3875
|
+
await fs9.ensureDir(path7.join(distributionDir, "apps"));
|
|
3876
|
+
await fs9.ensureDir(path7.join(distributionDir, "manifests"));
|
|
3877
|
+
await fs9.ensureDir(path7.join(distributionDir, "icons"));
|
|
3878
|
+
const ipaFileName = path7.basename(ipaPath);
|
|
3879
|
+
await fs9.copy(ipaPath, path7.join(distributionDir, "apps", ipaFileName));
|
|
3880
|
+
await fs9.copy(manifestPath, path7.join(distributionDir, "manifests", `${ipaFileName}.plist`));
|
|
3012
3881
|
logger.info(`Distribution directory: ${distributionDir}`);
|
|
3013
3882
|
return distributionDir;
|
|
3014
3883
|
}
|
|
@@ -3018,7 +3887,7 @@ var AdHocUploader = class {
|
|
|
3018
3887
|
* @param bundleId Bundle ID
|
|
3019
3888
|
*/
|
|
3020
3889
|
generateInstallUrl(distributionPath, bundleId) {
|
|
3021
|
-
const manifestPath =
|
|
3890
|
+
const manifestPath = path7.join(distributionPath, "manifests", `*.plist`);
|
|
3022
3891
|
return `itms-services://?action=download-manifest&url=file://${manifestPath}`;
|
|
3023
3892
|
}
|
|
3024
3893
|
/**
|
|
@@ -3027,7 +3896,7 @@ var AdHocUploader = class {
|
|
|
3027
3896
|
* @param distributionPath 分发目录
|
|
3028
3897
|
*/
|
|
3029
3898
|
async generateQRCode(url, distributionPath) {
|
|
3030
|
-
const qrCodePath =
|
|
3899
|
+
const qrCodePath = path7.join(distributionPath, "install-qrcode.png");
|
|
3031
3900
|
try {
|
|
3032
3901
|
logger.info(`QR code would be saved to: ${qrCodePath}`);
|
|
3033
3902
|
logger.info(`Install URL: ${url}`);
|
|
@@ -3054,8 +3923,8 @@ var AdHocUploader = class {
|
|
|
3054
3923
|
logger.newLine();
|
|
3055
3924
|
logger.info("\u{1F4E6} Distribution Files:");
|
|
3056
3925
|
logger.info(` Directory: ${distributionPath}`);
|
|
3057
|
-
logger.info(` IPA: ${
|
|
3058
|
-
logger.info(` Manifest: ${
|
|
3926
|
+
logger.info(` IPA: ${path7.join(distributionPath, "apps", "*.ipa")}`);
|
|
3927
|
+
logger.info(` Manifest: ${path7.join(distributionPath, "manifests", "*.plist")}`);
|
|
3059
3928
|
logger.info(` QR Code: ${qrCodePath}`);
|
|
3060
3929
|
logger.newLine();
|
|
3061
3930
|
logger.info("\u{1F517} Installation Link:");
|
|
@@ -3114,7 +3983,7 @@ var AdHocUploader = class {
|
|
|
3114
3983
|
|
|
3115
3984
|
// src/adapters/harmony_uploader.ts
|
|
3116
3985
|
import fs10 from "fs-extra";
|
|
3117
|
-
import
|
|
3986
|
+
import path8 from "path";
|
|
3118
3987
|
import axios6 from "axios";
|
|
3119
3988
|
import FormData5 from "form-data";
|
|
3120
3989
|
var HarmonyUploader = class _HarmonyUploader {
|
|
@@ -3144,7 +4013,7 @@ var HarmonyUploader = class _HarmonyUploader {
|
|
|
3144
4013
|
const clientSecret = harmonyConfig.auth.clientSecret;
|
|
3145
4014
|
const appId = harmonyConfig.appId;
|
|
3146
4015
|
const releaseType = harmonyConfig.releaseType || "draft";
|
|
3147
|
-
const suffix =
|
|
4016
|
+
const suffix = path8.extname(filePath).substring(1);
|
|
3148
4017
|
const retryConfig = config.retry;
|
|
3149
4018
|
logger.info("Starting HarmonyOS AppGallery upload...");
|
|
3150
4019
|
try {
|
|
@@ -3261,7 +4130,7 @@ var HarmonyUploader = class _HarmonyUploader {
|
|
|
3261
4130
|
* @param filePath 文件路径
|
|
3262
4131
|
*/
|
|
3263
4132
|
async updateAppFileInfo(token, appId, authCode, filePath) {
|
|
3264
|
-
const fileName =
|
|
4133
|
+
const fileName = path8.basename(filePath);
|
|
3265
4134
|
const body = {
|
|
3266
4135
|
fileType: 7,
|
|
3267
4136
|
// 鸿蒙包的 fileType(需要根据华为最新文档确认)
|
|
@@ -3284,7 +4153,7 @@ var HarmonyUploader = class _HarmonyUploader {
|
|
|
3284
4153
|
|
|
3285
4154
|
// src/adapters/oppo_uploader.ts
|
|
3286
4155
|
import fs11 from "fs-extra";
|
|
3287
|
-
import
|
|
4156
|
+
import path9 from "path";
|
|
3288
4157
|
import axios7 from "axios";
|
|
3289
4158
|
import FormData6 from "form-data";
|
|
3290
4159
|
import crypto4 from "crypto";
|
|
@@ -3325,7 +4194,7 @@ var OppoUploader = class _OppoUploader {
|
|
|
3325
4194
|
* 尝试从文件名推断包名
|
|
3326
4195
|
*/
|
|
3327
4196
|
inferPackageName(filePath) {
|
|
3328
|
-
const fileName =
|
|
4197
|
+
const fileName = path9.basename(filePath);
|
|
3329
4198
|
const match = fileName.match(/^([\w.]+?)(?:-(?:release|debug|unsigned))?\.apk$/i);
|
|
3330
4199
|
if (match && match[1].includes(".")) {
|
|
3331
4200
|
return match[1];
|
|
@@ -3343,110 +4212,47 @@ var OppoUploader = class _OppoUploader {
|
|
|
3343
4212
|
}
|
|
3344
4213
|
async upload(filePath, config, metadata) {
|
|
3345
4214
|
const oppoConfig = config.publish.oppo;
|
|
3346
|
-
const
|
|
3347
|
-
const clientSecret = oppoConfig.auth.clientSecret;
|
|
4215
|
+
const accessToken = await this.getAccessToken(oppoConfig.auth.clientId, oppoConfig.auth.clientSecret);
|
|
3348
4216
|
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
4217
|
const consoleUrl = "https://open.oppomobile.com/new/ecology/app";
|
|
3359
|
-
if (!
|
|
3360
|
-
return {
|
|
3361
|
-
platform: "oppo",
|
|
3362
|
-
success: false,
|
|
3363
|
-
message: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}`,
|
|
3364
|
-
consoleUrl
|
|
3365
|
-
};
|
|
4218
|
+
if (!packageName) {
|
|
4219
|
+
return { platform: "oppo", success: false, message: "\u65E0\u6CD5\u83B7\u53D6\u5305\u540D", consoleUrl };
|
|
3366
4220
|
}
|
|
3367
|
-
|
|
3368
|
-
logger.info(`\u6587\u4EF6: ${filePath}`);
|
|
4221
|
+
const artifacts = typeof metadata.artifactPath === "object" ? metadata.artifactPath : { universal: filePath || (typeof metadata.artifactPath === "string" ? metadata.artifactPath : "") };
|
|
3369
4222
|
try {
|
|
3370
|
-
const retryStrategy = createUploadRetryStrategy(
|
|
3371
|
-
|
|
3372
|
-
const
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
(
|
|
3383
|
-
|
|
3384
|
-
);
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
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
|
-
}
|
|
4223
|
+
const retryStrategy = createUploadRetryStrategy(config.retry);
|
|
4224
|
+
const onlineAppInfo = await this.getAppInfo(accessToken, oppoConfig.auth.clientSecret, packageName) || {};
|
|
4225
|
+
const uploadResults = [];
|
|
4226
|
+
const uploadPromises = Object.entries(artifacts).map(async ([abi, p]) => {
|
|
4227
|
+
if (!p || !fs11.existsSync(p)) throw new Error(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${p}`);
|
|
4228
|
+
logger.info(`[OPPO] \u6B63\u5728\u5904\u7406 ${abi} \u67B6\u6784\u5305...`);
|
|
4229
|
+
const { uploadUrl, sign } = await this.getUploadUrl(accessToken, oppoConfig.auth.clientSecret);
|
|
4230
|
+
const uploadFileResult = await this.uploadFile(uploadUrl, sign, p);
|
|
4231
|
+
const fileUrl = uploadFileResult.url.replace(/^http:\/\//i, "https://").replace("storedl1.nearme.com.cn", "storedl.nearme.com.cn");
|
|
4232
|
+
let cpu_code = 0;
|
|
4233
|
+
if (abi === "arm64-v8a") cpu_code = 3;
|
|
4234
|
+
else if (abi === "armeabi-v7a") cpu_code = 4;
|
|
4235
|
+
uploadResults.push({ url: fileUrl, md5: uploadFileResult.md5, cpu_code });
|
|
4236
|
+
});
|
|
4237
|
+
await Promise.all(uploadPromises);
|
|
4238
|
+
logger.info(`[OPPO] \u6B63\u5728\u63D0\u4EA4\u6D41\u6C34\u53F7\u7EC4: ${JSON.stringify(uploadResults)}`);
|
|
4239
|
+
await this.submitVersion({
|
|
4240
|
+
accessToken,
|
|
4241
|
+
clientSecret: oppoConfig.auth.clientSecret,
|
|
4242
|
+
packageName,
|
|
4243
|
+
appName: oppoConfig.appName || onlineAppInfo.app_name || metadata.appName || this.inferAppName(packageName),
|
|
4244
|
+
apkUrls: uploadResults,
|
|
4245
|
+
versionName: metadata.version,
|
|
4246
|
+
versionCode: metadata.buildNumber,
|
|
4247
|
+
updateDesc: metadata.changelog || "App Update",
|
|
4248
|
+
onlineType: oppoConfig.onlineType,
|
|
4249
|
+
onlineAppInfo
|
|
4250
|
+
});
|
|
3434
4251
|
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
|
-
};
|
|
4252
|
+
return { platform: "oppo", success: true, message: "\u4E0A\u4F20\u6210\u529F, \u8BF7\u786E\u8BA4", consoleUrl };
|
|
3441
4253
|
} catch (error) {
|
|
3442
4254
|
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
|
-
};
|
|
4255
|
+
return { platform: "oppo", success: false, message: error.message, error, consoleUrl };
|
|
3450
4256
|
}
|
|
3451
4257
|
}
|
|
3452
4258
|
/**
|
|
@@ -3567,16 +4373,7 @@ var OppoUploader = class _OppoUploader {
|
|
|
3567
4373
|
const signParams = {
|
|
3568
4374
|
access_token: params.accessToken,
|
|
3569
4375
|
timestamp,
|
|
3570
|
-
|
|
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
|
-
]),
|
|
4376
|
+
apk_url: JSON.stringify(params.apkUrls),
|
|
3580
4377
|
version_name: params.versionName.split("+")[0],
|
|
3581
4378
|
// 仅保留版本名部分
|
|
3582
4379
|
version_code: vCode,
|
|
@@ -3678,7 +4475,7 @@ var OppoUploader = class _OppoUploader {
|
|
|
3678
4475
|
|
|
3679
4476
|
// src/adapters/vivo_uploader.ts
|
|
3680
4477
|
import fs12 from "fs-extra";
|
|
3681
|
-
import
|
|
4478
|
+
import path10 from "path";
|
|
3682
4479
|
import axios8 from "axios";
|
|
3683
4480
|
import FormData7 from "form-data";
|
|
3684
4481
|
import crypto5 from "crypto";
|
|
@@ -3686,26 +4483,14 @@ var VivoUploader = class _VivoUploader {
|
|
|
3686
4483
|
name = "vivo";
|
|
3687
4484
|
/** vivo 开放 API 基础地址 */
|
|
3688
4485
|
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
4486
|
generateSign(params, accessSecret) {
|
|
3695
4487
|
const sorted = Object.keys(params).sort().filter((key) => params[key] !== void 0 && params[key] !== null).map((key) => `${key}=${params[key]}`).join("&");
|
|
3696
|
-
|
|
3697
|
-
logger.info(`[VIVO DEBUG] \u5F85\u7B7E\u540D\u4E32: ${sorted}
|
|
3698
|
-
-> \u7B7E\u540D\u7ED3\u679C: ${sign}`);
|
|
3699
|
-
return sign;
|
|
4488
|
+
return crypto5.createHmac("sha256", accessSecret).update(sorted).digest("hex");
|
|
3700
4489
|
}
|
|
3701
4490
|
validateConfig(config) {
|
|
3702
4491
|
if (!config?.publish?.vivo?.enable) return false;
|
|
3703
4492
|
const vivo = config.publish.vivo;
|
|
3704
|
-
|
|
3705
|
-
logger.error("vivo config missing: accessKey or accessSecret");
|
|
3706
|
-
return false;
|
|
3707
|
-
}
|
|
3708
|
-
return true;
|
|
4493
|
+
return !!(vivo.auth?.accessKey && vivo.auth?.accessSecret);
|
|
3709
4494
|
}
|
|
3710
4495
|
canHandle(filePath) {
|
|
3711
4496
|
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
@@ -3715,67 +4500,51 @@ var VivoUploader = class _VivoUploader {
|
|
|
3715
4500
|
const vivoConfig = config.publish.vivo;
|
|
3716
4501
|
const accessKey = vivoConfig.auth.accessKey;
|
|
3717
4502
|
const accessSecret = vivoConfig.auth.accessSecret;
|
|
3718
|
-
const retryConfig = config.retry;
|
|
3719
4503
|
const consoleUrl = "https://dev.vivo.com.cn/manage/app";
|
|
3720
|
-
|
|
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)`);
|
|
4504
|
+
const artifacts = typeof metadata.artifactPath === "object" ? metadata.artifactPath : { universal: filePath || (typeof metadata.artifactPath === "string" ? metadata.artifactPath : "") };
|
|
3731
4505
|
try {
|
|
3732
|
-
const
|
|
3733
|
-
const
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
4506
|
+
const uploadResults = {};
|
|
4507
|
+
const uploadPromises = Object.entries(artifacts).map(async ([abi, p]) => {
|
|
4508
|
+
if (!p || !fs12.existsSync(p)) throw new Error(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${p}`);
|
|
4509
|
+
const md5 = await this.calculateFileMD5(p);
|
|
4510
|
+
logger.info(`[vivo] \u6B63\u5728\u5904\u7406 ${abi} \u67B6\u6784\u5305: ${path10.basename(p)}`);
|
|
4511
|
+
const serialnumber = await this.uploadApk(accessKey, accessSecret, p, vivoConfig.packageName || "", md5);
|
|
4512
|
+
const pkgInfo = await parsePackageInfo(p, { androidSdkPath: config.globalSettings?.androidSdkPath });
|
|
4513
|
+
const nativeCodes = pkgInfo?.nativeCode || [];
|
|
4514
|
+
const has64Bit = nativeCodes.includes("arm64-v8a") || nativeCodes.includes("x86_64") || abi === "arm64-v8a";
|
|
4515
|
+
const has32Bit = nativeCodes.includes("armeabi-v7a") || nativeCodes.includes("armeabi") || abi === "armeabi-v7a";
|
|
4516
|
+
uploadResults[abi] = { serialnumber, md5, is64Bit: has64Bit && !has32Bit };
|
|
4517
|
+
});
|
|
4518
|
+
await Promise.all(uploadPromises);
|
|
4519
|
+
const arm64Result = uploadResults["arm64-v8a"] || Object.values(uploadResults).find((r) => r.is64Bit);
|
|
4520
|
+
const arm32Result = uploadResults["armeabi-v7a"] || Object.values(uploadResults).find((r) => !r.is64Bit);
|
|
4521
|
+
const submitData = {
|
|
4522
|
+
packageName: vivoConfig.packageName,
|
|
4523
|
+
versionCode: parseInt(metadata.buildNumber) || 1,
|
|
4524
|
+
updateDesc: metadata.changelog || "App Update"
|
|
4525
|
+
};
|
|
4526
|
+
if (arm64Result) {
|
|
4527
|
+
submitData.serialnumber = arm64Result.serialnumber;
|
|
4528
|
+
submitData.fileMd5 = arm64Result.md5;
|
|
3742
4529
|
}
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
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");
|
|
4530
|
+
if (arm32Result) {
|
|
4531
|
+
submitData.apk32 = arm32Result.serialnumber;
|
|
4532
|
+
submitData.md532 = arm32Result.md5;
|
|
3757
4533
|
}
|
|
4534
|
+
if (!submitData.serialnumber && !submitData.apk32) {
|
|
4535
|
+
const single = Object.values(uploadResults)[0];
|
|
4536
|
+
submitData.serialnumber = single.serialnumber;
|
|
4537
|
+
submitData.fileMd5 = single.md5;
|
|
4538
|
+
}
|
|
4539
|
+
logger.info(`[vivo] \u6B63\u5728\u63D0\u4EA4\u5BA1\u6838\u7533\u62A5: ${JSON.stringify(submitData)}`);
|
|
4540
|
+
await this.submitVersion(accessKey, accessSecret, submitData);
|
|
3758
4541
|
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
|
-
};
|
|
4542
|
+
return { platform: "vivo", success: true, message: "\u4E0A\u4F20\u5E76\u63D0\u4EA4\u5BA1\u6838\u6210\u529F", consoleUrl };
|
|
3765
4543
|
} catch (error) {
|
|
3766
4544
|
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
|
-
};
|
|
4545
|
+
return { platform: "vivo", success: false, message: error.message, error, consoleUrl };
|
|
3774
4546
|
}
|
|
3775
4547
|
}
|
|
3776
|
-
/**
|
|
3777
|
-
* 计算文件 MD5
|
|
3778
|
-
*/
|
|
3779
4548
|
calculateFileMD5(filePath) {
|
|
3780
4549
|
return new Promise((resolve, reject) => {
|
|
3781
4550
|
const hash = crypto5.createHash("md5");
|
|
@@ -3785,14 +4554,9 @@ var VivoUploader = class _VivoUploader {
|
|
|
3785
4554
|
stream.on("error", reject);
|
|
3786
4555
|
});
|
|
3787
4556
|
}
|
|
3788
|
-
/**
|
|
3789
|
-
* 上传 APK 文件并获取流水号
|
|
3790
|
-
* 将公共参数与业务参数放入一个 multipart 请求中直接上传至 API_BASE
|
|
3791
|
-
*/
|
|
3792
4557
|
async uploadApk(accessKey, accessSecret, filePath, packageName, fileMd5) {
|
|
3793
4558
|
const timestamp = String(Date.now());
|
|
3794
|
-
const
|
|
3795
|
-
// 公共参数
|
|
4559
|
+
const params = {
|
|
3796
4560
|
access_key: accessKey,
|
|
3797
4561
|
timestamp,
|
|
3798
4562
|
format: "json",
|
|
@@ -3800,41 +4564,19 @@ var VivoUploader = class _VivoUploader {
|
|
|
3800
4564
|
sign_method: "hmac",
|
|
3801
4565
|
target_app_key: "developer",
|
|
3802
4566
|
method: "app.upload.apk.app",
|
|
3803
|
-
// 业务参数
|
|
3804
4567
|
packageName,
|
|
3805
4568
|
fileMd5
|
|
3806
4569
|
};
|
|
3807
|
-
const sign = this.generateSign(
|
|
4570
|
+
const sign = this.generateSign(params, accessSecret);
|
|
3808
4571
|
const form = new FormData7();
|
|
3809
|
-
|
|
3810
|
-
form.append(key, value);
|
|
3811
|
-
}
|
|
4572
|
+
Object.entries(params).forEach(([k, v]) => form.append(k, v));
|
|
3812
4573
|
form.append("sign", sign);
|
|
3813
|
-
form.append("file", fs12.createReadStream(filePath), {
|
|
3814
|
-
|
|
3815
|
-
|
|
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;
|
|
4574
|
+
form.append("file", fs12.createReadStream(filePath), { filename: path10.basename(filePath) });
|
|
4575
|
+
const res = await axios8.post(_VivoUploader.API_BASE, form, { headers: form.getHeaders(), timeout: 3e5 });
|
|
4576
|
+
if (res.data.code !== 0) throw new Error(`\u4E0A\u4F20\u5305\u5931\u8D25: ${res.data.msg}`);
|
|
4577
|
+
return res.data.data?.serialnumber || res.data.data;
|
|
3833
4578
|
}
|
|
3834
|
-
|
|
3835
|
-
* 提交版本更新信息
|
|
3836
|
-
*/
|
|
3837
|
-
async submitVersion(accessKey, accessSecret, params) {
|
|
4579
|
+
async submitVersion(accessKey, accessSecret, data) {
|
|
3838
4580
|
const timestamp = String(Date.now());
|
|
3839
4581
|
const reqParams = {
|
|
3840
4582
|
access_key: accessKey,
|
|
@@ -3844,51 +4586,32 @@ var VivoUploader = class _VivoUploader {
|
|
|
3844
4586
|
sign_method: "hmac",
|
|
3845
4587
|
target_app_key: "developer",
|
|
3846
4588
|
method: "app.sync.update.app",
|
|
3847
|
-
packageName:
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
apk: params.serialnumber,
|
|
3852
|
-
// 文档中的 apk 字段即为上传接口返回的 serialnumber
|
|
3853
|
-
fileMd5: params.fileMd5,
|
|
3854
|
-
// 文档必填
|
|
4589
|
+
packageName: data.packageName || "",
|
|
4590
|
+
versionCode: String(data.versionCode),
|
|
4591
|
+
apk: data.serialnumber || "",
|
|
4592
|
+
fileMd5: data.fileMd5 || "",
|
|
3855
4593
|
onlineType: "1",
|
|
3856
|
-
// 1:立即上架 2:定时上架 (必填)
|
|
3857
4594
|
compatibleDevice: "1",
|
|
3858
|
-
|
|
3859
|
-
updateDesc: params.updateDesc
|
|
4595
|
+
updateDesc: data.updateDesc
|
|
3860
4596
|
};
|
|
4597
|
+
if (data.apk32) {
|
|
4598
|
+
reqParams.apk32 = data.apk32;
|
|
4599
|
+
if (data.md532) reqParams.md532 = data.md532;
|
|
4600
|
+
}
|
|
3861
4601
|
reqParams.sign = this.generateSign(reqParams, accessSecret);
|
|
3862
4602
|
const formData = new URLSearchParams();
|
|
3863
|
-
|
|
3864
|
-
formData.append(key, value);
|
|
3865
|
-
}
|
|
4603
|
+
Object.entries(reqParams).forEach(([k, v]) => formData.append(k, v));
|
|
3866
4604
|
const res = await axios8.post(_VivoUploader.API_BASE, formData.toString(), {
|
|
3867
|
-
headers: {
|
|
3868
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
3869
|
-
},
|
|
4605
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
3870
4606
|
timeout: 3e4
|
|
3871
4607
|
});
|
|
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;
|
|
4608
|
+
if (res.data.code !== 0) throw new Error(`\u63D0\u4EA4\u5931\u8D25: ${res.data.msg}`);
|
|
3886
4609
|
}
|
|
3887
4610
|
};
|
|
3888
4611
|
|
|
3889
4612
|
// src/adapters/tencent_uploader.ts
|
|
3890
4613
|
import fs13 from "fs-extra";
|
|
3891
|
-
import
|
|
4614
|
+
import path11 from "path";
|
|
3892
4615
|
import axios9 from "axios";
|
|
3893
4616
|
import crypto6 from "crypto";
|
|
3894
4617
|
var TencentUploader = class _TencentUploader {
|
|
@@ -3920,54 +4643,71 @@ var TencentUploader = class _TencentUploader {
|
|
|
3920
4643
|
async upload(filePath, config, metadata) {
|
|
3921
4644
|
const tencentConfig = config.publish.tencent;
|
|
3922
4645
|
const auth = tencentConfig.auth;
|
|
3923
|
-
logger.info(`Tencent \u8FD0\u884C\u65F6\u914D\u7F6E: ${JSON.stringify(tencentConfig)}`);
|
|
3924
4646
|
const consoleUrl = "https://app.open.qq.com";
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
}
|
|
4647
|
+
const pkgName = tencentConfig.packageName || "";
|
|
4648
|
+
const artifacts = typeof metadata.artifactPath === "object" ? metadata.artifactPath : { universal: filePath || (typeof metadata.artifactPath === "string" ? metadata.artifactPath : "") };
|
|
3928
4649
|
try {
|
|
3929
|
-
const
|
|
3930
|
-
const
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
4650
|
+
const uploadResults = {};
|
|
4651
|
+
const uploadPromises = Object.entries(artifacts).map(async ([abi, p]) => {
|
|
4652
|
+
if (!p || !fs13.existsSync(p)) throw new Error(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${p}`);
|
|
4653
|
+
const stats = fs13.statSync(p);
|
|
4654
|
+
const fileName = path11.basename(p);
|
|
4655
|
+
logger.info(`[Tencent] \u6B63\u5728\u5904\u7406 ${abi} \u67B6\u6784\u5305: ${fileName}`);
|
|
4656
|
+
const uploadInfo = await this.getFileUploadInfo(auth, stats.size, fileName, pkgName);
|
|
4657
|
+
await this.uploadToCos(uploadInfo.pre_sign_url, p, stats.size);
|
|
4658
|
+
const pkgInfo = await parsePackageInfo(p, {
|
|
4659
|
+
androidSdkPath: config.globalSettings?.androidSdkPath
|
|
4660
|
+
});
|
|
4661
|
+
const nativeCodes = pkgInfo?.nativeCode || [];
|
|
4662
|
+
const has64Bit = nativeCodes.includes("arm64-v8a") || nativeCodes.includes("x86_64") || abi === "arm64-v8a";
|
|
4663
|
+
const has32Bit = nativeCodes.includes("armeabi-v7a") || nativeCodes.includes("armeabi") || nativeCodes.includes("x86") || abi === "armeabi-v7a";
|
|
4664
|
+
uploadResults[abi] = {
|
|
4665
|
+
serial_number: uploadInfo.serial_number,
|
|
4666
|
+
is64Bit: has64Bit && !has32Bit,
|
|
4667
|
+
md5: pkgInfo?.md5 || ""
|
|
4668
|
+
};
|
|
4669
|
+
});
|
|
4670
|
+
await Promise.all(uploadPromises);
|
|
4671
|
+
logger.info("[Tencent] \u6B63\u5728\u83B7\u53D6\u7EBF\u4E0A\u5E94\u7528\u8BE6\u60C5...");
|
|
4672
|
+
const anyPkgPath = Object.values(artifacts)[0];
|
|
4673
|
+
const samplePkgInfo = await parsePackageInfo(anyPkgPath, {
|
|
3939
4674
|
androidSdkPath: config.globalSettings?.androidSdkPath
|
|
3940
4675
|
});
|
|
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
4676
|
const onlineDetail = await this.queryAppDetail(
|
|
3947
4677
|
auth,
|
|
3948
|
-
|
|
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 }
|
|
4678
|
+
pkgName || samplePkgInfo?.packageName || ""
|
|
3964
4679
|
);
|
|
3965
|
-
|
|
4680
|
+
const submitData = {
|
|
4681
|
+
...onlineDetail,
|
|
4682
|
+
pkg_name: pkgName || samplePkgInfo?.packageName || onlineDetail.pkg_name || "",
|
|
4683
|
+
update_desc: metadata.changelog || "\u7248\u672C\u66F4\u65B0"
|
|
4684
|
+
};
|
|
4685
|
+
const arm64Result = uploadResults["arm64-v8a"] || Object.values(uploadResults).find((r) => r.is64Bit);
|
|
4686
|
+
const arm32Result = uploadResults["armeabi-v7a"] || Object.values(uploadResults).find((r) => !r.is64Bit);
|
|
4687
|
+
if (arm64Result) {
|
|
4688
|
+
submitData.apk64_serial_number = arm64Result.serial_number;
|
|
4689
|
+
submitData.apk64_md5 = arm64Result.md5;
|
|
4690
|
+
}
|
|
4691
|
+
if (arm32Result) {
|
|
4692
|
+
submitData.apk32_serial_number = arm32Result.serial_number;
|
|
4693
|
+
submitData.apk32_md5 = arm32Result.md5;
|
|
4694
|
+
}
|
|
4695
|
+
if (Object.keys(uploadResults).length === 1 && !submitData.apk64_serial_number && !submitData.apk32_serial_number) {
|
|
4696
|
+
const single = Object.values(uploadResults)[0];
|
|
4697
|
+
if (single.is64Bit) {
|
|
4698
|
+
submitData.apk64_serial_number = single.serial_number;
|
|
4699
|
+
submitData.apk64_md5 = single.md5;
|
|
4700
|
+
} else {
|
|
4701
|
+
submitData.apk32_serial_number = single.serial_number;
|
|
4702
|
+
submitData.apk32_md5 = single.md5;
|
|
4703
|
+
}
|
|
4704
|
+
}
|
|
4705
|
+
logger.info("[Tencent] \u6B63\u5728\u63D0\u4EA4\u5BA1\u6838\u7533\u62A5...");
|
|
4706
|
+
await this.submitModifyAppInfo(auth, submitData, { timeout: tencentConfig.timeout || 6e4 });
|
|
3966
4707
|
logger.success("\u817E\u8BAF\u5E94\u7528\u5B9D\u4E0A\u4F20\u5E76\u63D0\u4EA4\u6210\u529F!");
|
|
3967
4708
|
return { platform: "tencent", success: true, message: "\u4E0A\u4F20\u5E76\u63D0\u4EA4\u5BA1\u6838\u6210\u529F", consoleUrl };
|
|
3968
4709
|
} catch (error) {
|
|
3969
|
-
logger.error(`\u817E\u8BAF\u4E0A\u4F20\u5931\u8D25: ${
|
|
3970
|
-
logger.error(`\u817E\u8BAF\u4E0A\u4F20\u5931\u8D25: ${error.stack}`);
|
|
4710
|
+
logger.error(`\u817E\u8BAF\u4E0A\u4F20\u5931\u8D25: ${error.message}`);
|
|
3971
4711
|
return { platform: "tencent", success: false, message: error.message, error, consoleUrl };
|
|
3972
4712
|
}
|
|
3973
4713
|
}
|
|
@@ -4081,16 +4821,22 @@ var TencentUploader = class _TencentUploader {
|
|
|
4081
4821
|
signParams[key] = String(data[key]);
|
|
4082
4822
|
}
|
|
4083
4823
|
});
|
|
4084
|
-
if (data.
|
|
4085
|
-
signParams.apk64_file_serial_number = String(data.
|
|
4086
|
-
if (data.
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
signParams.
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4824
|
+
if (data.apk64_serial_number) {
|
|
4825
|
+
signParams.apk64_file_serial_number = String(data.apk64_serial_number);
|
|
4826
|
+
if (data.apk64_md5) signParams.apk64_file_md5 = data.apk64_md5;
|
|
4827
|
+
}
|
|
4828
|
+
if (data.apk32_serial_number) {
|
|
4829
|
+
signParams.apk32_file_serial_number = String(data.apk32_serial_number);
|
|
4830
|
+
if (data.apk32_md5) signParams.apk32_file_md5 = data.apk32_md5;
|
|
4831
|
+
}
|
|
4832
|
+
if (!data.apk64_serial_number && !data.apk32_serial_number && data.serial_number) {
|
|
4833
|
+
if (data.is64Bit) {
|
|
4834
|
+
signParams.apk64_file_serial_number = String(data.serial_number || "");
|
|
4835
|
+
if (data.md5) signParams.apk64_file_md5 = data.md5;
|
|
4836
|
+
} else {
|
|
4837
|
+
signParams.apk32_file_serial_number = String(data.serial_number || "");
|
|
4838
|
+
if (data.md5) signParams.apk32_file_md5 = data.md5;
|
|
4839
|
+
}
|
|
4094
4840
|
}
|
|
4095
4841
|
const sign = this.generateSign(signParams, accessSecret);
|
|
4096
4842
|
const formParams = {
|
|
@@ -4108,26 +4854,255 @@ var TencentUploader = class _TencentUploader {
|
|
|
4108
4854
|
return res.data;
|
|
4109
4855
|
}
|
|
4110
4856
|
};
|
|
4857
|
+
|
|
4858
|
+
// src/adapters/google_play_uploader.ts
|
|
4859
|
+
import fs14 from "fs-extra";
|
|
4860
|
+
import path12 from "path";
|
|
4861
|
+
import axios10 from "axios";
|
|
4862
|
+
import { GoogleAuth } from "google-auth-library";
|
|
4863
|
+
var ANDROID_PUBLISHER_SCOPE = "https://www.googleapis.com/auth/androidpublisher";
|
|
4864
|
+
var PUBLISH_API_ROOT = "https://androidpublisher.googleapis.com/androidpublisher/v3";
|
|
4865
|
+
var UPLOAD_API_ROOT = "https://androidpublisher.googleapis.com/upload/androidpublisher/v3";
|
|
4866
|
+
var GOOGLE_PLAY_CONSOLE_URL = "https://play.google.com/console/developers";
|
|
4867
|
+
var GOOGLE_PLAY_INTERNAL_API_TRACK = "internal";
|
|
4868
|
+
var UPLOAD_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
4869
|
+
var GooglePlayUploader = class {
|
|
4870
|
+
name = "Google Play";
|
|
4871
|
+
httpClient;
|
|
4872
|
+
getAccessToken;
|
|
4873
|
+
constructor(dependencies = {}) {
|
|
4874
|
+
this.httpClient = dependencies.httpClient || axios10;
|
|
4875
|
+
this.getAccessToken = dependencies.getAccessToken || this.createAccessToken.bind(this);
|
|
4876
|
+
}
|
|
4877
|
+
validateConfig(config) {
|
|
4878
|
+
const googleConfig = config?.publish?.google_play;
|
|
4879
|
+
if (!googleConfig?.enable) return false;
|
|
4880
|
+
if (!googleConfig.packageName || typeof googleConfig.packageName !== "string") {
|
|
4881
|
+
logger.error("Google Play \u914D\u7F6E\u7F3A\u5C11 packageName");
|
|
4882
|
+
return false;
|
|
4883
|
+
}
|
|
4884
|
+
const track = googleConfig.track || "internal";
|
|
4885
|
+
if (track !== "internal") {
|
|
4886
|
+
logger.error("Google Play \u9996\u7248\u4EC5\u652F\u6301 internal \u8F68\u9053");
|
|
4887
|
+
return false;
|
|
4888
|
+
}
|
|
4889
|
+
const auth = googleConfig.auth;
|
|
4890
|
+
const hasCredentialContent = typeof auth?.serviceAccountJsonContent === "string" && auth.serviceAccountJsonContent.trim().length > 0;
|
|
4891
|
+
const credentialPath = auth?.serviceAccountJsonPath;
|
|
4892
|
+
const hasCredentialPath = typeof credentialPath === "string" && credentialPath.trim().length > 0 && fs14.existsSync(path12.resolve(credentialPath));
|
|
4893
|
+
if (!hasCredentialContent && !hasCredentialPath) {
|
|
4894
|
+
logger.error("Google Play \u914D\u7F6E\u7F3A\u5C11\u6709\u6548\u7684\u670D\u52A1\u8D26\u53F7 JSON \u6587\u4EF6\u6216\u5185\u5BB9");
|
|
4895
|
+
return false;
|
|
4896
|
+
}
|
|
4897
|
+
return true;
|
|
4898
|
+
}
|
|
4899
|
+
canHandle(filePath) {
|
|
4900
|
+
return path12.extname(filePath).toLowerCase() === ".aab";
|
|
4901
|
+
}
|
|
4902
|
+
async upload(filePath, config, _metadata) {
|
|
4903
|
+
if (!this.validateConfig(config)) {
|
|
4904
|
+
return this.failure("Google Play \u914D\u7F6E\u65E0\u6548\uFF0C\u8BF7\u68C0\u67E5\u5305\u540D\u3001internal \u8F68\u9053\u548C\u670D\u52A1\u8D26\u53F7\u6587\u4EF6");
|
|
4905
|
+
}
|
|
4906
|
+
if (!this.canHandle(filePath)) {
|
|
4907
|
+
return this.failure("Google Play \u4EC5\u63A5\u53D7 Android App Bundle\uFF08.aab\uFF09");
|
|
4908
|
+
}
|
|
4909
|
+
if (!fs14.existsSync(filePath)) {
|
|
4910
|
+
return this.failure(`\u672A\u627E\u5230\u5F85\u4E0A\u4F20\u7684 AAB\uFF1A${filePath}`);
|
|
4911
|
+
}
|
|
4912
|
+
const googleConfig = config.publish.google_play;
|
|
4913
|
+
const packageName = googleConfig.packageName.trim();
|
|
4914
|
+
const encodedPackageName = encodeURIComponent(packageName);
|
|
4915
|
+
let editId;
|
|
4916
|
+
let accessToken;
|
|
4917
|
+
let committed = false;
|
|
4918
|
+
let stage = "create_edit";
|
|
4919
|
+
logger.info(`\u51C6\u5907\u5C06 ${path12.basename(filePath)} \u4E0A\u4F20\u5230 Google Play internal \u8349\u7A3F`);
|
|
4920
|
+
try {
|
|
4921
|
+
accessToken = await this.getAccessToken(googleConfig.auth);
|
|
4922
|
+
const requestConfig = {
|
|
4923
|
+
headers: { Authorization: `Bearer ${accessToken}` }
|
|
4924
|
+
};
|
|
4925
|
+
const editResponse = await this.httpClient.post(
|
|
4926
|
+
`${PUBLISH_API_ROOT}/applications/${encodedPackageName}/edits`,
|
|
4927
|
+
{},
|
|
4928
|
+
requestConfig
|
|
4929
|
+
);
|
|
4930
|
+
editId = editResponse.data.id;
|
|
4931
|
+
if (!editId) {
|
|
4932
|
+
throw new Error("Google Play \u672A\u8FD4\u56DE editId");
|
|
4933
|
+
}
|
|
4934
|
+
stage = "upload_bundle";
|
|
4935
|
+
const encodedEditId = encodeURIComponent(editId);
|
|
4936
|
+
const bundleResponse = await this.httpClient.post(
|
|
4937
|
+
`${UPLOAD_API_ROOT}/applications/${encodedPackageName}/edits/${encodedEditId}/bundles?uploadType=media`,
|
|
4938
|
+
fs14.createReadStream(filePath),
|
|
4939
|
+
{
|
|
4940
|
+
timeout: UPLOAD_TIMEOUT_MS,
|
|
4941
|
+
maxBodyLength: Infinity,
|
|
4942
|
+
maxContentLength: Infinity,
|
|
4943
|
+
headers: {
|
|
4944
|
+
Authorization: `Bearer ${accessToken}`,
|
|
4945
|
+
"Content-Type": "application/octet-stream",
|
|
4946
|
+
"Content-Length": fs14.statSync(filePath).size
|
|
4947
|
+
}
|
|
4948
|
+
}
|
|
4949
|
+
);
|
|
4950
|
+
const versionCode = bundleResponse.data.versionCode;
|
|
4951
|
+
if (!Number.isInteger(versionCode)) {
|
|
4952
|
+
throw new Error("Google Play \u4E0A\u4F20\u6210\u529F\u4F46\u672A\u8FD4\u56DE\u6709\u6548\u7684 versionCode");
|
|
4953
|
+
}
|
|
4954
|
+
stage = "update_internal_track";
|
|
4955
|
+
await this.httpClient.put(
|
|
4956
|
+
`${PUBLISH_API_ROOT}/applications/${encodedPackageName}/edits/${encodedEditId}/tracks/${GOOGLE_PLAY_INTERNAL_API_TRACK}`,
|
|
4957
|
+
{
|
|
4958
|
+
track: GOOGLE_PLAY_INTERNAL_API_TRACK,
|
|
4959
|
+
releases: [
|
|
4960
|
+
{
|
|
4961
|
+
versionCodes: [String(versionCode)],
|
|
4962
|
+
status: "draft"
|
|
4963
|
+
}
|
|
4964
|
+
]
|
|
4965
|
+
},
|
|
4966
|
+
requestConfig
|
|
4967
|
+
);
|
|
4968
|
+
stage = "validate_edit";
|
|
4969
|
+
await this.httpClient.post(
|
|
4970
|
+
`${PUBLISH_API_ROOT}/applications/${encodedPackageName}/edits/${encodedEditId}:validate`,
|
|
4971
|
+
{},
|
|
4972
|
+
requestConfig
|
|
4973
|
+
);
|
|
4974
|
+
stage = "commit_edit";
|
|
4975
|
+
await this.httpClient.post(
|
|
4976
|
+
`${PUBLISH_API_ROOT}/applications/${encodedPackageName}/edits/${encodedEditId}:commit`,
|
|
4977
|
+
{},
|
|
4978
|
+
requestConfig
|
|
4979
|
+
);
|
|
4980
|
+
committed = true;
|
|
4981
|
+
logger.success(`Google Play internal \u8349\u7A3F\u5DF2\u521B\u5EFA\uFF08versionCode ${versionCode}\uFF09`);
|
|
4982
|
+
return {
|
|
4983
|
+
platform: "google_play",
|
|
4984
|
+
success: true,
|
|
4985
|
+
message: `AAB \u5DF2\u4E0A\u4F20\u5230 Google Play internal \u8349\u7A3F\uFF08versionCode ${versionCode}\uFF09`,
|
|
4986
|
+
consoleUrl: GOOGLE_PLAY_CONSOLE_URL,
|
|
4987
|
+
data: {
|
|
4988
|
+
editId,
|
|
4989
|
+
versionCode,
|
|
4990
|
+
track: "internal",
|
|
4991
|
+
apiTrack: GOOGLE_PLAY_INTERNAL_API_TRACK,
|
|
4992
|
+
status: "draft",
|
|
4993
|
+
committed: true
|
|
4994
|
+
}
|
|
4995
|
+
};
|
|
4996
|
+
} catch (error) {
|
|
4997
|
+
if (editId && accessToken && !committed) {
|
|
4998
|
+
await this.deleteEditBestEffort(encodedPackageName, editId, accessToken);
|
|
4999
|
+
}
|
|
5000
|
+
const message = this.describeError(error, stage);
|
|
5001
|
+
logger.error(`Google Play \u4E0A\u4F20\u5931\u8D25\uFF1A${message}`);
|
|
5002
|
+
return this.failure(message, error);
|
|
5003
|
+
}
|
|
5004
|
+
}
|
|
5005
|
+
async createAccessToken(authConfig) {
|
|
5006
|
+
let credentials;
|
|
5007
|
+
if (authConfig.serviceAccountJsonContent?.trim()) {
|
|
5008
|
+
try {
|
|
5009
|
+
credentials = JSON.parse(authConfig.serviceAccountJsonContent);
|
|
5010
|
+
} catch {
|
|
5011
|
+
throw new Error("\u670D\u52A1\u8D26\u53F7 JSON \u5185\u5BB9\u683C\u5F0F\u65E0\u6548");
|
|
5012
|
+
}
|
|
5013
|
+
}
|
|
5014
|
+
const keyFile = !credentials && authConfig.serviceAccountJsonPath ? path12.resolve(authConfig.serviceAccountJsonPath) : void 0;
|
|
5015
|
+
const auth = new GoogleAuth({
|
|
5016
|
+
scopes: [ANDROID_PUBLISHER_SCOPE],
|
|
5017
|
+
keyFile,
|
|
5018
|
+
credentials
|
|
5019
|
+
});
|
|
5020
|
+
const token = await auth.getAccessToken();
|
|
5021
|
+
if (!token) {
|
|
5022
|
+
throw new Error("Google Play \u8BA4\u8BC1\u672A\u8FD4\u56DE\u8BBF\u95EE\u4EE4\u724C");
|
|
5023
|
+
}
|
|
5024
|
+
return token;
|
|
5025
|
+
}
|
|
5026
|
+
async deleteEditBestEffort(encodedPackageName, editId, accessToken) {
|
|
5027
|
+
try {
|
|
5028
|
+
await this.httpClient.delete(
|
|
5029
|
+
`${PUBLISH_API_ROOT}/applications/${encodedPackageName}/edits/${encodeURIComponent(editId)}`,
|
|
5030
|
+
{ headers: { Authorization: `Bearer ${accessToken}` } }
|
|
5031
|
+
);
|
|
5032
|
+
} catch {
|
|
5033
|
+
logger.warn("Google Play \u672A\u63D0\u4EA4 edit \u7684\u81EA\u52A8\u6E05\u7406\u5931\u8D25\uFF0C\u8BF7\u5728 Play Console \u4E2D\u68C0\u67E5\u8349\u7A3F\u72B6\u6001");
|
|
5034
|
+
}
|
|
5035
|
+
}
|
|
5036
|
+
describeError(error, stage) {
|
|
5037
|
+
const status = error?.response?.status;
|
|
5038
|
+
const rawMessage = error?.response?.data?.error?.message || error?.response?.data?.message || error?.message || "\u672A\u77E5\u9519\u8BEF";
|
|
5039
|
+
const safeMessage = this.redactAndTruncate(String(rawMessage));
|
|
5040
|
+
if (/version\s*code|versionCode/i.test(safeMessage)) {
|
|
5041
|
+
return `Google Play \u62D2\u7EDD\u8BE5 versionCode\uFF0C\u8BF7\u63D0\u9AD8 Flutter \u6784\u5EFA\u53F7\u540E\u91CD\u65B0\u751F\u6210 AAB\uFF1A${safeMessage}`;
|
|
5042
|
+
}
|
|
5043
|
+
if (status === 401) {
|
|
5044
|
+
return "Google Play \u8BA4\u8BC1\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u670D\u52A1\u8D26\u53F7 JSON \u662F\u5426\u6709\u6548";
|
|
5045
|
+
}
|
|
5046
|
+
if (status === 403) {
|
|
5047
|
+
return "Google Play \u62D2\u7EDD\u8BBF\u95EE\uFF0C\u8BF7\u786E\u8BA4\u670D\u52A1\u8D26\u53F7\u5DF2\u52A0\u5165 Play Console \u5E76\u5177\u6709\u5185\u90E8\u6D4B\u8BD5\u7248\u672C\u4E0A\u4F20\u6743\u9650";
|
|
5048
|
+
}
|
|
5049
|
+
if (status === 404) {
|
|
5050
|
+
if (stage === "update_internal_track") {
|
|
5051
|
+
return "Google Play \u4E2D\u672A\u627E\u5230 internal \u5185\u90E8\u6D4B\u8BD5\u8F68\u9053\uFF0C\u8BF7\u786E\u8BA4\u5E94\u7528\u5DF2\u5728 Play Console \u521B\u5EFA\u5185\u90E8\u6D4B\u8BD5\u8F68\u9053";
|
|
5052
|
+
}
|
|
5053
|
+
if (stage !== "create_edit") {
|
|
5054
|
+
return `Google Play API \u5728 ${stage} \u9636\u6BB5\u672A\u627E\u5230\u76EE\u6807\u8D44\u6E90\uFF1A${safeMessage}`;
|
|
5055
|
+
}
|
|
5056
|
+
return "Google Play \u4E2D\u672A\u627E\u5230\u53EF\u4F9B API \u7F16\u8F91\u7684\u5E94\u7528\u3002\u8BF7\u786E\u8BA4 packageName \u5B8C\u5168\u4E00\u81F4\uFF1B\u82E5\u8FD9\u662F\u9996\u6B21\u4E0A\u4F20\uFF0C\u8BF7\u5148\u5728 Play Console \u624B\u5DE5\u4E0A\u4F20\u5E76\u4FDD\u5B58\u4E00\u4E2A APK/AAB\uFF0C\u518D\u91CD\u8BD5 API \u4E0A\u4F20";
|
|
5057
|
+
}
|
|
5058
|
+
if (error?.code === "ECONNABORTED" || /timeout/i.test(safeMessage)) {
|
|
5059
|
+
return "Google Play \u4E0A\u4F20\u8D85\u65F6\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u540E\u91CD\u8BD5\uFF1B\u672C\u5730 AAB \u672A\u88AB\u5220\u9664";
|
|
5060
|
+
}
|
|
5061
|
+
return `Google Play API \u8BF7\u6C42\u5931\u8D25${status ? `\uFF08HTTP ${status}\uFF09` : ""}\uFF1A${safeMessage}`;
|
|
5062
|
+
}
|
|
5063
|
+
redactAndTruncate(message) {
|
|
5064
|
+
return message.replace(/-----BEGIN PRIVATE KEY-----[\s\S]*?-----END PRIVATE KEY-----/gi, "[\u5DF2\u8131\u654F\u79C1\u94A5]").replace(/Bearer\s+[^\s"']+/gi, "Bearer [\u5DF2\u8131\u654F]").replace(/"private_key"\s*:\s*"[^"]*"/gi, '"private_key":"[\u5DF2\u8131\u654F]"').replace(/"access_token"\s*:\s*"[^"]*"/gi, '"access_token":"[\u5DF2\u8131\u654F]"').slice(0, 500);
|
|
5065
|
+
}
|
|
5066
|
+
failure(message, error) {
|
|
5067
|
+
return {
|
|
5068
|
+
platform: "google_play",
|
|
5069
|
+
success: false,
|
|
5070
|
+
message,
|
|
5071
|
+
// 原始 Axios 错误可能包含 Authorization 请求头,只返回脱敏后的 Error。
|
|
5072
|
+
error: error ? new Error(message) : void 0
|
|
5073
|
+
};
|
|
5074
|
+
}
|
|
5075
|
+
};
|
|
4111
5076
|
export {
|
|
4112
5077
|
AdHocUploader,
|
|
4113
5078
|
AppleJWTGenerator,
|
|
4114
5079
|
AppleStoreUploader,
|
|
4115
5080
|
ConfigValidator,
|
|
5081
|
+
DEFAULT_RELEASE_RECEIPT_LIMIT,
|
|
4116
5082
|
DEFAULT_RETRY_CONFIG,
|
|
4117
5083
|
EnvValidator,
|
|
4118
5084
|
ErrorCategory,
|
|
5085
|
+
GooglePlayUploader,
|
|
4119
5086
|
HarmonyUploader,
|
|
4120
5087
|
HuaweiUploader,
|
|
4121
5088
|
OppoUploader,
|
|
4122
5089
|
PgyerUploader,
|
|
5090
|
+
RELEASE_RECEIPT_SCHEMA_VERSION,
|
|
5091
|
+
ReleaseReceiptStore,
|
|
4123
5092
|
RetryStrategy,
|
|
4124
5093
|
TencentUploader,
|
|
4125
5094
|
TestFlightUploader,
|
|
4126
5095
|
UploadManager,
|
|
4127
5096
|
VivoUploader,
|
|
4128
5097
|
XiaomiUploader,
|
|
5098
|
+
createProjectKey,
|
|
5099
|
+
createReleaseReceipt,
|
|
4129
5100
|
createUploadManager,
|
|
4130
5101
|
createUploadRetryStrategy,
|
|
4131
|
-
|
|
5102
|
+
createUploadTargets,
|
|
5103
|
+
formatReleaseReceipt,
|
|
5104
|
+
parsePackageInfo,
|
|
5105
|
+
resolveReleaseProjectRoot,
|
|
5106
|
+
sanitizeReleaseMessage
|
|
4132
5107
|
};
|
|
4133
5108
|
//# sourceMappingURL=index.js.map
|