@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/cli.js
CHANGED
|
@@ -7,25 +7,519 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
7
7
|
});
|
|
8
8
|
|
|
9
9
|
// src/utils/upload-manager.ts
|
|
10
|
-
import
|
|
10
|
+
import path12 from "path";
|
|
11
|
+
|
|
12
|
+
// node_modules/chalk/source/vendor/ansi-styles/index.js
|
|
13
|
+
var ANSI_BACKGROUND_OFFSET = 10;
|
|
14
|
+
var wrapAnsi16 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
|
|
15
|
+
var wrapAnsi256 = (offset = 0) => (code) => `\x1B[${38 + offset};5;${code}m`;
|
|
16
|
+
var wrapAnsi16m = (offset = 0) => (red, green, blue) => `\x1B[${38 + offset};2;${red};${green};${blue}m`;
|
|
17
|
+
var styles = {
|
|
18
|
+
modifier: {
|
|
19
|
+
reset: [0, 0],
|
|
20
|
+
// 21 isn't widely supported and 22 does the same thing
|
|
21
|
+
bold: [1, 22],
|
|
22
|
+
dim: [2, 22],
|
|
23
|
+
italic: [3, 23],
|
|
24
|
+
underline: [4, 24],
|
|
25
|
+
overline: [53, 55],
|
|
26
|
+
inverse: [7, 27],
|
|
27
|
+
hidden: [8, 28],
|
|
28
|
+
strikethrough: [9, 29]
|
|
29
|
+
},
|
|
30
|
+
color: {
|
|
31
|
+
black: [30, 39],
|
|
32
|
+
red: [31, 39],
|
|
33
|
+
green: [32, 39],
|
|
34
|
+
yellow: [33, 39],
|
|
35
|
+
blue: [34, 39],
|
|
36
|
+
magenta: [35, 39],
|
|
37
|
+
cyan: [36, 39],
|
|
38
|
+
white: [37, 39],
|
|
39
|
+
// Bright color
|
|
40
|
+
blackBright: [90, 39],
|
|
41
|
+
gray: [90, 39],
|
|
42
|
+
// Alias of `blackBright`
|
|
43
|
+
grey: [90, 39],
|
|
44
|
+
// Alias of `blackBright`
|
|
45
|
+
redBright: [91, 39],
|
|
46
|
+
greenBright: [92, 39],
|
|
47
|
+
yellowBright: [93, 39],
|
|
48
|
+
blueBright: [94, 39],
|
|
49
|
+
magentaBright: [95, 39],
|
|
50
|
+
cyanBright: [96, 39],
|
|
51
|
+
whiteBright: [97, 39]
|
|
52
|
+
},
|
|
53
|
+
bgColor: {
|
|
54
|
+
bgBlack: [40, 49],
|
|
55
|
+
bgRed: [41, 49],
|
|
56
|
+
bgGreen: [42, 49],
|
|
57
|
+
bgYellow: [43, 49],
|
|
58
|
+
bgBlue: [44, 49],
|
|
59
|
+
bgMagenta: [45, 49],
|
|
60
|
+
bgCyan: [46, 49],
|
|
61
|
+
bgWhite: [47, 49],
|
|
62
|
+
// Bright color
|
|
63
|
+
bgBlackBright: [100, 49],
|
|
64
|
+
bgGray: [100, 49],
|
|
65
|
+
// Alias of `bgBlackBright`
|
|
66
|
+
bgGrey: [100, 49],
|
|
67
|
+
// Alias of `bgBlackBright`
|
|
68
|
+
bgRedBright: [101, 49],
|
|
69
|
+
bgGreenBright: [102, 49],
|
|
70
|
+
bgYellowBright: [103, 49],
|
|
71
|
+
bgBlueBright: [104, 49],
|
|
72
|
+
bgMagentaBright: [105, 49],
|
|
73
|
+
bgCyanBright: [106, 49],
|
|
74
|
+
bgWhiteBright: [107, 49]
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
var modifierNames = Object.keys(styles.modifier);
|
|
78
|
+
var foregroundColorNames = Object.keys(styles.color);
|
|
79
|
+
var backgroundColorNames = Object.keys(styles.bgColor);
|
|
80
|
+
var colorNames = [...foregroundColorNames, ...backgroundColorNames];
|
|
81
|
+
function assembleStyles() {
|
|
82
|
+
const codes = /* @__PURE__ */ new Map();
|
|
83
|
+
for (const [groupName, group] of Object.entries(styles)) {
|
|
84
|
+
for (const [styleName, style] of Object.entries(group)) {
|
|
85
|
+
styles[styleName] = {
|
|
86
|
+
open: `\x1B[${style[0]}m`,
|
|
87
|
+
close: `\x1B[${style[1]}m`
|
|
88
|
+
};
|
|
89
|
+
group[styleName] = styles[styleName];
|
|
90
|
+
codes.set(style[0], style[1]);
|
|
91
|
+
}
|
|
92
|
+
Object.defineProperty(styles, groupName, {
|
|
93
|
+
value: group,
|
|
94
|
+
enumerable: false
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
Object.defineProperty(styles, "codes", {
|
|
98
|
+
value: codes,
|
|
99
|
+
enumerable: false
|
|
100
|
+
});
|
|
101
|
+
styles.color.close = "\x1B[39m";
|
|
102
|
+
styles.bgColor.close = "\x1B[49m";
|
|
103
|
+
styles.color.ansi = wrapAnsi16();
|
|
104
|
+
styles.color.ansi256 = wrapAnsi256();
|
|
105
|
+
styles.color.ansi16m = wrapAnsi16m();
|
|
106
|
+
styles.bgColor.ansi = wrapAnsi16(ANSI_BACKGROUND_OFFSET);
|
|
107
|
+
styles.bgColor.ansi256 = wrapAnsi256(ANSI_BACKGROUND_OFFSET);
|
|
108
|
+
styles.bgColor.ansi16m = wrapAnsi16m(ANSI_BACKGROUND_OFFSET);
|
|
109
|
+
Object.defineProperties(styles, {
|
|
110
|
+
rgbToAnsi256: {
|
|
111
|
+
value(red, green, blue) {
|
|
112
|
+
if (red === green && green === blue) {
|
|
113
|
+
if (red < 8) {
|
|
114
|
+
return 16;
|
|
115
|
+
}
|
|
116
|
+
if (red > 248) {
|
|
117
|
+
return 231;
|
|
118
|
+
}
|
|
119
|
+
return Math.round((red - 8) / 247 * 24) + 232;
|
|
120
|
+
}
|
|
121
|
+
return 16 + 36 * Math.round(red / 255 * 5) + 6 * Math.round(green / 255 * 5) + Math.round(blue / 255 * 5);
|
|
122
|
+
},
|
|
123
|
+
enumerable: false
|
|
124
|
+
},
|
|
125
|
+
hexToRgb: {
|
|
126
|
+
value(hex) {
|
|
127
|
+
const matches = /[a-f\d]{6}|[a-f\d]{3}/i.exec(hex.toString(16));
|
|
128
|
+
if (!matches) {
|
|
129
|
+
return [0, 0, 0];
|
|
130
|
+
}
|
|
131
|
+
let [colorString] = matches;
|
|
132
|
+
if (colorString.length === 3) {
|
|
133
|
+
colorString = [...colorString].map((character) => character + character).join("");
|
|
134
|
+
}
|
|
135
|
+
const integer = Number.parseInt(colorString, 16);
|
|
136
|
+
return [
|
|
137
|
+
/* eslint-disable no-bitwise */
|
|
138
|
+
integer >> 16 & 255,
|
|
139
|
+
integer >> 8 & 255,
|
|
140
|
+
integer & 255
|
|
141
|
+
/* eslint-enable no-bitwise */
|
|
142
|
+
];
|
|
143
|
+
},
|
|
144
|
+
enumerable: false
|
|
145
|
+
},
|
|
146
|
+
hexToAnsi256: {
|
|
147
|
+
value: (hex) => styles.rgbToAnsi256(...styles.hexToRgb(hex)),
|
|
148
|
+
enumerable: false
|
|
149
|
+
},
|
|
150
|
+
ansi256ToAnsi: {
|
|
151
|
+
value(code) {
|
|
152
|
+
if (code < 8) {
|
|
153
|
+
return 30 + code;
|
|
154
|
+
}
|
|
155
|
+
if (code < 16) {
|
|
156
|
+
return 90 + (code - 8);
|
|
157
|
+
}
|
|
158
|
+
let red;
|
|
159
|
+
let green;
|
|
160
|
+
let blue;
|
|
161
|
+
if (code >= 232) {
|
|
162
|
+
red = ((code - 232) * 10 + 8) / 255;
|
|
163
|
+
green = red;
|
|
164
|
+
blue = red;
|
|
165
|
+
} else {
|
|
166
|
+
code -= 16;
|
|
167
|
+
const remainder = code % 36;
|
|
168
|
+
red = Math.floor(code / 36) / 5;
|
|
169
|
+
green = Math.floor(remainder / 6) / 5;
|
|
170
|
+
blue = remainder % 6 / 5;
|
|
171
|
+
}
|
|
172
|
+
const value = Math.max(red, green, blue) * 2;
|
|
173
|
+
if (value === 0) {
|
|
174
|
+
return 30;
|
|
175
|
+
}
|
|
176
|
+
let result = 30 + (Math.round(blue) << 2 | Math.round(green) << 1 | Math.round(red));
|
|
177
|
+
if (value === 2) {
|
|
178
|
+
result += 60;
|
|
179
|
+
}
|
|
180
|
+
return result;
|
|
181
|
+
},
|
|
182
|
+
enumerable: false
|
|
183
|
+
},
|
|
184
|
+
rgbToAnsi: {
|
|
185
|
+
value: (red, green, blue) => styles.ansi256ToAnsi(styles.rgbToAnsi256(red, green, blue)),
|
|
186
|
+
enumerable: false
|
|
187
|
+
},
|
|
188
|
+
hexToAnsi: {
|
|
189
|
+
value: (hex) => styles.ansi256ToAnsi(styles.hexToAnsi256(hex)),
|
|
190
|
+
enumerable: false
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
return styles;
|
|
194
|
+
}
|
|
195
|
+
var ansiStyles = assembleStyles();
|
|
196
|
+
var ansi_styles_default = ansiStyles;
|
|
197
|
+
|
|
198
|
+
// node_modules/chalk/source/vendor/supports-color/index.js
|
|
199
|
+
import process2 from "process";
|
|
200
|
+
import os from "os";
|
|
201
|
+
import tty from "tty";
|
|
202
|
+
function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process2.argv) {
|
|
203
|
+
const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
|
|
204
|
+
const position = argv.indexOf(prefix + flag);
|
|
205
|
+
const terminatorPosition = argv.indexOf("--");
|
|
206
|
+
return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
|
|
207
|
+
}
|
|
208
|
+
var { env } = process2;
|
|
209
|
+
var flagForceColor;
|
|
210
|
+
if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
|
|
211
|
+
flagForceColor = 0;
|
|
212
|
+
} else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
|
|
213
|
+
flagForceColor = 1;
|
|
214
|
+
}
|
|
215
|
+
function envForceColor() {
|
|
216
|
+
if ("FORCE_COLOR" in env) {
|
|
217
|
+
if (env.FORCE_COLOR === "true") {
|
|
218
|
+
return 1;
|
|
219
|
+
}
|
|
220
|
+
if (env.FORCE_COLOR === "false") {
|
|
221
|
+
return 0;
|
|
222
|
+
}
|
|
223
|
+
return env.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function translateLevel(level) {
|
|
227
|
+
if (level === 0) {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
level,
|
|
232
|
+
hasBasic: true,
|
|
233
|
+
has256: level >= 2,
|
|
234
|
+
has16m: level >= 3
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
|
|
238
|
+
const noFlagForceColor = envForceColor();
|
|
239
|
+
if (noFlagForceColor !== void 0) {
|
|
240
|
+
flagForceColor = noFlagForceColor;
|
|
241
|
+
}
|
|
242
|
+
const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
|
|
243
|
+
if (forceColor === 0) {
|
|
244
|
+
return 0;
|
|
245
|
+
}
|
|
246
|
+
if (sniffFlags) {
|
|
247
|
+
if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
|
|
248
|
+
return 3;
|
|
249
|
+
}
|
|
250
|
+
if (hasFlag("color=256")) {
|
|
251
|
+
return 2;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if ("TF_BUILD" in env && "AGENT_NAME" in env) {
|
|
255
|
+
return 1;
|
|
256
|
+
}
|
|
257
|
+
if (haveStream && !streamIsTTY && forceColor === void 0) {
|
|
258
|
+
return 0;
|
|
259
|
+
}
|
|
260
|
+
const min = forceColor || 0;
|
|
261
|
+
if (env.TERM === "dumb") {
|
|
262
|
+
return min;
|
|
263
|
+
}
|
|
264
|
+
if (process2.platform === "win32") {
|
|
265
|
+
const osRelease = os.release().split(".");
|
|
266
|
+
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
|
|
267
|
+
return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
268
|
+
}
|
|
269
|
+
return 1;
|
|
270
|
+
}
|
|
271
|
+
if ("CI" in env) {
|
|
272
|
+
if (["GITHUB_ACTIONS", "GITEA_ACTIONS", "CIRCLECI"].some((key) => key in env)) {
|
|
273
|
+
return 3;
|
|
274
|
+
}
|
|
275
|
+
if (["TRAVIS", "APPVEYOR", "GITLAB_CI", "BUILDKITE", "DRONE"].some((sign) => sign in env) || env.CI_NAME === "codeship") {
|
|
276
|
+
return 1;
|
|
277
|
+
}
|
|
278
|
+
return min;
|
|
279
|
+
}
|
|
280
|
+
if ("TEAMCITY_VERSION" in env) {
|
|
281
|
+
return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
|
|
282
|
+
}
|
|
283
|
+
if (env.COLORTERM === "truecolor") {
|
|
284
|
+
return 3;
|
|
285
|
+
}
|
|
286
|
+
if (env.TERM === "xterm-kitty") {
|
|
287
|
+
return 3;
|
|
288
|
+
}
|
|
289
|
+
if (env.TERM === "xterm-ghostty") {
|
|
290
|
+
return 3;
|
|
291
|
+
}
|
|
292
|
+
if (env.TERM === "wezterm") {
|
|
293
|
+
return 3;
|
|
294
|
+
}
|
|
295
|
+
if ("TERM_PROGRAM" in env) {
|
|
296
|
+
const version = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
|
|
297
|
+
switch (env.TERM_PROGRAM) {
|
|
298
|
+
case "iTerm.app": {
|
|
299
|
+
return version >= 3 ? 3 : 2;
|
|
300
|
+
}
|
|
301
|
+
case "Apple_Terminal": {
|
|
302
|
+
return 2;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (/-256(color)?$/i.test(env.TERM)) {
|
|
307
|
+
return 2;
|
|
308
|
+
}
|
|
309
|
+
if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
|
|
310
|
+
return 1;
|
|
311
|
+
}
|
|
312
|
+
if ("COLORTERM" in env) {
|
|
313
|
+
return 1;
|
|
314
|
+
}
|
|
315
|
+
return min;
|
|
316
|
+
}
|
|
317
|
+
function createSupportsColor(stream, options = {}) {
|
|
318
|
+
const level = _supportsColor(stream, {
|
|
319
|
+
streamIsTTY: stream && stream.isTTY,
|
|
320
|
+
...options
|
|
321
|
+
});
|
|
322
|
+
return translateLevel(level);
|
|
323
|
+
}
|
|
324
|
+
var supportsColor = {
|
|
325
|
+
stdout: createSupportsColor({ isTTY: tty.isatty(1) }),
|
|
326
|
+
stderr: createSupportsColor({ isTTY: tty.isatty(2) })
|
|
327
|
+
};
|
|
328
|
+
var supports_color_default = supportsColor;
|
|
329
|
+
|
|
330
|
+
// node_modules/chalk/source/utilities.js
|
|
331
|
+
function stringReplaceAll(string, substring, replacer) {
|
|
332
|
+
let index = string.indexOf(substring);
|
|
333
|
+
if (index === -1) {
|
|
334
|
+
return string;
|
|
335
|
+
}
|
|
336
|
+
const substringLength = substring.length;
|
|
337
|
+
let endIndex = 0;
|
|
338
|
+
let returnValue = "";
|
|
339
|
+
do {
|
|
340
|
+
returnValue += string.slice(endIndex, index) + substring + replacer;
|
|
341
|
+
endIndex = index + substringLength;
|
|
342
|
+
index = string.indexOf(substring, endIndex);
|
|
343
|
+
} while (index !== -1);
|
|
344
|
+
returnValue += string.slice(endIndex);
|
|
345
|
+
return returnValue;
|
|
346
|
+
}
|
|
347
|
+
function stringEncaseCRLFWithFirstIndex(string, prefix, postfix, index) {
|
|
348
|
+
let endIndex = 0;
|
|
349
|
+
let returnValue = "";
|
|
350
|
+
do {
|
|
351
|
+
const gotCR = string[index - 1] === "\r";
|
|
352
|
+
returnValue += string.slice(endIndex, gotCR ? index - 1 : index) + prefix + (gotCR ? "\r\n" : "\n") + postfix;
|
|
353
|
+
endIndex = index + 1;
|
|
354
|
+
index = string.indexOf("\n", endIndex);
|
|
355
|
+
} while (index !== -1);
|
|
356
|
+
returnValue += string.slice(endIndex);
|
|
357
|
+
return returnValue;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// node_modules/chalk/source/index.js
|
|
361
|
+
var { stdout: stdoutColor, stderr: stderrColor } = supports_color_default;
|
|
362
|
+
var GENERATOR = /* @__PURE__ */ Symbol("GENERATOR");
|
|
363
|
+
var STYLER = /* @__PURE__ */ Symbol("STYLER");
|
|
364
|
+
var IS_EMPTY = /* @__PURE__ */ Symbol("IS_EMPTY");
|
|
365
|
+
var levelMapping = [
|
|
366
|
+
"ansi",
|
|
367
|
+
"ansi",
|
|
368
|
+
"ansi256",
|
|
369
|
+
"ansi16m"
|
|
370
|
+
];
|
|
371
|
+
var styles2 = /* @__PURE__ */ Object.create(null);
|
|
372
|
+
var applyOptions = (object, options = {}) => {
|
|
373
|
+
if (options.level && !(Number.isInteger(options.level) && options.level >= 0 && options.level <= 3)) {
|
|
374
|
+
throw new Error("The `level` option should be an integer from 0 to 3");
|
|
375
|
+
}
|
|
376
|
+
const colorLevel = stdoutColor ? stdoutColor.level : 0;
|
|
377
|
+
object.level = options.level === void 0 ? colorLevel : options.level;
|
|
378
|
+
};
|
|
379
|
+
var chalkFactory = (options) => {
|
|
380
|
+
const chalk2 = (...strings) => strings.join(" ");
|
|
381
|
+
applyOptions(chalk2, options);
|
|
382
|
+
Object.setPrototypeOf(chalk2, createChalk.prototype);
|
|
383
|
+
return chalk2;
|
|
384
|
+
};
|
|
385
|
+
function createChalk(options) {
|
|
386
|
+
return chalkFactory(options);
|
|
387
|
+
}
|
|
388
|
+
Object.setPrototypeOf(createChalk.prototype, Function.prototype);
|
|
389
|
+
for (const [styleName, style] of Object.entries(ansi_styles_default)) {
|
|
390
|
+
styles2[styleName] = {
|
|
391
|
+
get() {
|
|
392
|
+
const builder = createBuilder(this, createStyler(style.open, style.close, this[STYLER]), this[IS_EMPTY]);
|
|
393
|
+
Object.defineProperty(this, styleName, { value: builder });
|
|
394
|
+
return builder;
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
styles2.visible = {
|
|
399
|
+
get() {
|
|
400
|
+
const builder = createBuilder(this, this[STYLER], true);
|
|
401
|
+
Object.defineProperty(this, "visible", { value: builder });
|
|
402
|
+
return builder;
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
var getModelAnsi = (model, level, type, ...arguments_) => {
|
|
406
|
+
if (model === "rgb") {
|
|
407
|
+
if (level === "ansi16m") {
|
|
408
|
+
return ansi_styles_default[type].ansi16m(...arguments_);
|
|
409
|
+
}
|
|
410
|
+
if (level === "ansi256") {
|
|
411
|
+
return ansi_styles_default[type].ansi256(ansi_styles_default.rgbToAnsi256(...arguments_));
|
|
412
|
+
}
|
|
413
|
+
return ansi_styles_default[type].ansi(ansi_styles_default.rgbToAnsi(...arguments_));
|
|
414
|
+
}
|
|
415
|
+
if (model === "hex") {
|
|
416
|
+
return getModelAnsi("rgb", level, type, ...ansi_styles_default.hexToRgb(...arguments_));
|
|
417
|
+
}
|
|
418
|
+
return ansi_styles_default[type][model](...arguments_);
|
|
419
|
+
};
|
|
420
|
+
var usedModels = ["rgb", "hex", "ansi256"];
|
|
421
|
+
for (const model of usedModels) {
|
|
422
|
+
styles2[model] = {
|
|
423
|
+
get() {
|
|
424
|
+
const { level } = this;
|
|
425
|
+
return function(...arguments_) {
|
|
426
|
+
const styler = createStyler(getModelAnsi(model, levelMapping[level], "color", ...arguments_), ansi_styles_default.color.close, this[STYLER]);
|
|
427
|
+
return createBuilder(this, styler, this[IS_EMPTY]);
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
const bgModel = "bg" + model[0].toUpperCase() + model.slice(1);
|
|
432
|
+
styles2[bgModel] = {
|
|
433
|
+
get() {
|
|
434
|
+
const { level } = this;
|
|
435
|
+
return function(...arguments_) {
|
|
436
|
+
const styler = createStyler(getModelAnsi(model, levelMapping[level], "bgColor", ...arguments_), ansi_styles_default.bgColor.close, this[STYLER]);
|
|
437
|
+
return createBuilder(this, styler, this[IS_EMPTY]);
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
var proto = Object.defineProperties(() => {
|
|
443
|
+
}, {
|
|
444
|
+
...styles2,
|
|
445
|
+
level: {
|
|
446
|
+
enumerable: true,
|
|
447
|
+
get() {
|
|
448
|
+
return this[GENERATOR].level;
|
|
449
|
+
},
|
|
450
|
+
set(level) {
|
|
451
|
+
this[GENERATOR].level = level;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
var createStyler = (open, close, parent) => {
|
|
456
|
+
let openAll;
|
|
457
|
+
let closeAll;
|
|
458
|
+
if (parent === void 0) {
|
|
459
|
+
openAll = open;
|
|
460
|
+
closeAll = close;
|
|
461
|
+
} else {
|
|
462
|
+
openAll = parent.openAll + open;
|
|
463
|
+
closeAll = close + parent.closeAll;
|
|
464
|
+
}
|
|
465
|
+
return {
|
|
466
|
+
open,
|
|
467
|
+
close,
|
|
468
|
+
openAll,
|
|
469
|
+
closeAll,
|
|
470
|
+
parent
|
|
471
|
+
};
|
|
472
|
+
};
|
|
473
|
+
var createBuilder = (self, _styler, _isEmpty) => {
|
|
474
|
+
const builder = (...arguments_) => applyStyle(builder, arguments_.length === 1 ? "" + arguments_[0] : arguments_.join(" "));
|
|
475
|
+
Object.setPrototypeOf(builder, proto);
|
|
476
|
+
builder[GENERATOR] = self;
|
|
477
|
+
builder[STYLER] = _styler;
|
|
478
|
+
builder[IS_EMPTY] = _isEmpty;
|
|
479
|
+
return builder;
|
|
480
|
+
};
|
|
481
|
+
var applyStyle = (self, string) => {
|
|
482
|
+
if (self.level <= 0 || !string) {
|
|
483
|
+
return self[IS_EMPTY] ? "" : string;
|
|
484
|
+
}
|
|
485
|
+
let styler = self[STYLER];
|
|
486
|
+
if (styler === void 0) {
|
|
487
|
+
return string;
|
|
488
|
+
}
|
|
489
|
+
const { openAll, closeAll } = styler;
|
|
490
|
+
if (string.includes("\x1B")) {
|
|
491
|
+
while (styler !== void 0) {
|
|
492
|
+
string = stringReplaceAll(string, styler.close, styler.open);
|
|
493
|
+
styler = styler.parent;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
const lfIndex = string.indexOf("\n");
|
|
497
|
+
if (lfIndex !== -1) {
|
|
498
|
+
string = stringEncaseCRLFWithFirstIndex(string, closeAll, openAll, lfIndex);
|
|
499
|
+
}
|
|
500
|
+
return openAll + string + closeAll;
|
|
501
|
+
};
|
|
502
|
+
Object.defineProperties(createChalk.prototype, styles2);
|
|
503
|
+
var chalk = createChalk();
|
|
504
|
+
var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
|
|
505
|
+
var source_default = chalk;
|
|
11
506
|
|
|
12
507
|
// src/utils/logger.ts
|
|
13
|
-
import chalk from "chalk";
|
|
14
508
|
var ConsoleLogger = class {
|
|
15
509
|
info(message) {
|
|
16
|
-
console.log(
|
|
510
|
+
console.log(source_default.blue("\u2139\uFE0F " + message));
|
|
17
511
|
}
|
|
18
512
|
success(message) {
|
|
19
|
-
console.log(
|
|
513
|
+
console.log(source_default.green("\u2705 " + message));
|
|
20
514
|
}
|
|
21
515
|
warn(message) {
|
|
22
|
-
console.log(
|
|
516
|
+
console.log(source_default.yellow("\u26A0\uFE0F " + message));
|
|
23
517
|
}
|
|
24
518
|
error(message) {
|
|
25
|
-
console.log(
|
|
519
|
+
console.log(source_default.red("\u274C " + message));
|
|
26
520
|
}
|
|
27
521
|
title(message) {
|
|
28
|
-
console.log(
|
|
522
|
+
console.log(source_default.bold.cyan("\n" + message + "\n"));
|
|
29
523
|
}
|
|
30
524
|
newLine() {
|
|
31
525
|
console.log("");
|
|
@@ -134,12 +628,7 @@ var RetryStrategy = class {
|
|
|
134
628
|
logger.warn(`${operationName} failed (Attempt ${attempt}): ${errorMessage} [${category}]`);
|
|
135
629
|
if (!this.shouldRetry(error, attempt)) {
|
|
136
630
|
logger.error(`${operationName} failed with non-retryable error: ${errorMessage}`);
|
|
137
|
-
|
|
138
|
-
success: false,
|
|
139
|
-
error,
|
|
140
|
-
totalAttempts: attempt,
|
|
141
|
-
lastError: errorMessage
|
|
142
|
-
};
|
|
631
|
+
throw error;
|
|
143
632
|
}
|
|
144
633
|
if (attempt <= this.config.maxRetries) {
|
|
145
634
|
const delayMs = this.calculateDelay(attempt - 1);
|
|
@@ -150,12 +639,7 @@ var RetryStrategy = class {
|
|
|
150
639
|
}
|
|
151
640
|
}
|
|
152
641
|
}
|
|
153
|
-
|
|
154
|
-
success: false,
|
|
155
|
-
error: lastError || new Error(`${operationName} failed after all retries`),
|
|
156
|
-
totalAttempts: attempt,
|
|
157
|
-
lastError: lastError?.message
|
|
158
|
-
};
|
|
642
|
+
throw lastError || new Error(`${operationName} failed after all retries`);
|
|
159
643
|
}
|
|
160
644
|
};
|
|
161
645
|
var DEFAULT_RETRY_CONFIG = {
|
|
@@ -481,6 +965,280 @@ async function parseHap(filePath) {
|
|
|
481
965
|
return info;
|
|
482
966
|
}
|
|
483
967
|
|
|
968
|
+
// src/utils/release-receipt.ts
|
|
969
|
+
import { createHash, randomUUID } from "crypto";
|
|
970
|
+
import { createReadStream } from "fs";
|
|
971
|
+
import { chmod, mkdir, readFile, realpath, rename, stat, writeFile } from "fs/promises";
|
|
972
|
+
import { homedir } from "os";
|
|
973
|
+
import path2 from "path";
|
|
974
|
+
var RELEASE_RECEIPT_SCHEMA_VERSION = 1;
|
|
975
|
+
var DEFAULT_RELEASE_RECEIPT_LIMIT = 100;
|
|
976
|
+
var REMOTE_IDENTIFIER_KEYS = [
|
|
977
|
+
"id",
|
|
978
|
+
"appId",
|
|
979
|
+
"buildId",
|
|
980
|
+
"versionId",
|
|
981
|
+
"releaseId",
|
|
982
|
+
"editId",
|
|
983
|
+
"versionCode",
|
|
984
|
+
"track",
|
|
985
|
+
"apiTrack",
|
|
986
|
+
"status",
|
|
987
|
+
"committed"
|
|
988
|
+
];
|
|
989
|
+
function resolveReleaseProjectRoot(projectRoot, configPath) {
|
|
990
|
+
return path2.resolve(projectRoot || path2.dirname(path2.resolve(configPath)));
|
|
991
|
+
}
|
|
992
|
+
function sanitizeReleaseMessage(value, maxLength = 500) {
|
|
993
|
+
let message = typeof value === "string" ? value : String(value ?? "");
|
|
994
|
+
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(
|
|
995
|
+
/(["']?(?:access[_-]?token|refresh[_-]?token|client[_-]?secret|private[_-]?key|api[_-]?key|app[_-]?password|authorization|credential|service[_-]?account(?:[_-]?json)?[_-]?content)["']?\s*[:=]\s*)["']?[^\s,"'}]+["']?/gi,
|
|
996
|
+
"$1[\u5DF2\u8131\u654F]"
|
|
997
|
+
).replace(/[\r\n\t]+/g, " ").replace(/\s{2,}/g, " ").trim();
|
|
998
|
+
return message.length > maxLength ? `${message.slice(0, maxLength - 1)}\u2026` : message;
|
|
999
|
+
}
|
|
1000
|
+
function createUploadTargets(results) {
|
|
1001
|
+
return results.map((result) => {
|
|
1002
|
+
const platform = sanitizeReleaseMessage(result.platform, 80) || "unknown";
|
|
1003
|
+
const data = isRecord(result.data) ? result.data : void 0;
|
|
1004
|
+
const isDraft = result.success && platform === "google_play" && String(data?.status || "").toLowerCase() === "draft";
|
|
1005
|
+
const remote = createRemoteReceipt(result, data);
|
|
1006
|
+
return {
|
|
1007
|
+
platform,
|
|
1008
|
+
state: result.success ? isDraft ? "draft_created" : "uploaded" : "failed",
|
|
1009
|
+
outcome: result.success ? "success" : "failed",
|
|
1010
|
+
message: sanitizeReleaseMessage(result.message) || (result.success ? "\u4E0A\u4F20\u6210\u529F" : "\u4E0A\u4F20\u5931\u8D25"),
|
|
1011
|
+
...remote ? { remote } : {}
|
|
1012
|
+
};
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
async function createReleaseReceipt(input) {
|
|
1016
|
+
const projectRoot = await canonicalizeProjectRoot(input.projectRoot);
|
|
1017
|
+
const startedAt = toDate(input.startedAt ?? Date.now());
|
|
1018
|
+
const finishedAt = toDate(input.finishedAt ?? Date.now());
|
|
1019
|
+
const targets = input.targets.map(sanitizeTarget);
|
|
1020
|
+
return {
|
|
1021
|
+
schemaVersion: RELEASE_RECEIPT_SCHEMA_VERSION,
|
|
1022
|
+
id: randomUUID(),
|
|
1023
|
+
projectKey: createProjectKey(projectRoot),
|
|
1024
|
+
projectRoot,
|
|
1025
|
+
kind: input.kind,
|
|
1026
|
+
...cleanOptionalText(input.appName, 160) ? { appName: cleanOptionalText(input.appName, 160) } : {},
|
|
1027
|
+
...cleanOptionalText(input.packageName, 240) ? { packageName: cleanOptionalText(input.packageName, 240) } : {},
|
|
1028
|
+
...cleanOptionalText(input.versionName, 80) ? { versionName: cleanOptionalText(input.versionName, 80) } : {},
|
|
1029
|
+
...input.buildNumber !== void 0 ? { buildNumber: cleanOptionalText(String(input.buildNumber), 80) } : {},
|
|
1030
|
+
...cleanOptionalText(input.changelog, 4e3) ? { changelog: cleanOptionalText(input.changelog, 4e3) } : {},
|
|
1031
|
+
artifacts: await inspectArtifacts(input.artifactPaths),
|
|
1032
|
+
targets,
|
|
1033
|
+
outcome: input.outcome ?? inferOutcome(targets),
|
|
1034
|
+
startedAt: startedAt.toISOString(),
|
|
1035
|
+
finishedAt: finishedAt.toISOString(),
|
|
1036
|
+
durationMs: Math.max(0, finishedAt.getTime() - startedAt.getTime())
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
function formatReleaseReceipt(receipt) {
|
|
1040
|
+
const lines = [
|
|
1041
|
+
`${receipt.appName || receipt.packageName || "\u5E94\u7528"} \xB7 ${receipt.kind === "build" ? "\u6784\u5EFA\u56DE\u6267" : "\u4E0A\u4F20\u56DE\u6267"}`,
|
|
1042
|
+
`\u7ED3\u679C\uFF1A${receipt.outcome}`,
|
|
1043
|
+
`\u7248\u672C\uFF1A${receipt.versionName || "-"} (${receipt.buildNumber || "-"})`,
|
|
1044
|
+
`\u5B8C\u6210\u65F6\u95F4\uFF1A${receipt.finishedAt}`
|
|
1045
|
+
];
|
|
1046
|
+
if (receipt.packageName) lines.push(`\u5305\u540D\uFF1A${receipt.packageName}`);
|
|
1047
|
+
if (receipt.changelog) lines.push(`\u66F4\u65B0\u65E5\u5FD7\uFF1A${receipt.changelog}`);
|
|
1048
|
+
for (const artifact of receipt.artifacts) {
|
|
1049
|
+
lines.push(
|
|
1050
|
+
`\u4EA7\u7269\uFF1A${artifact.path}${artifact.sizeBytes !== void 0 ? ` \xB7 ${artifact.sizeBytes} bytes` : ""}${artifact.sha256 ? ` \xB7 SHA-256 ${artifact.sha256}` : ""}`
|
|
1051
|
+
);
|
|
1052
|
+
}
|
|
1053
|
+
for (const target of receipt.targets) {
|
|
1054
|
+
lines.push(`${target.platform}\uFF1A${target.state} \xB7 ${target.message}`);
|
|
1055
|
+
if (target.remote?.consoleUrl) lines.push(`\u540E\u53F0\uFF1A${target.remote.consoleUrl}`);
|
|
1056
|
+
}
|
|
1057
|
+
return lines.join("\n");
|
|
1058
|
+
}
|
|
1059
|
+
var ReleaseReceiptStore = class {
|
|
1060
|
+
storageRoot;
|
|
1061
|
+
maxEntries;
|
|
1062
|
+
constructor(options = {}) {
|
|
1063
|
+
this.storageRoot = path2.resolve(options.storageRoot || path2.join(homedir(), ".flu-cli", "release-receipts"));
|
|
1064
|
+
this.maxEntries = Math.max(1, options.maxEntries ?? DEFAULT_RELEASE_RECEIPT_LIMIT);
|
|
1065
|
+
}
|
|
1066
|
+
async append(receipt) {
|
|
1067
|
+
await mkdir(this.storageRoot, { recursive: true, mode: 448 });
|
|
1068
|
+
await chmod(this.storageRoot, 448);
|
|
1069
|
+
const filePath = this.getFilePath(receipt.projectKey);
|
|
1070
|
+
const current = await this.readFile(filePath, receipt.projectKey);
|
|
1071
|
+
const receipts = [receipt, ...current.receipts.filter((item) => item.id !== receipt.id)].slice(
|
|
1072
|
+
0,
|
|
1073
|
+
this.maxEntries
|
|
1074
|
+
);
|
|
1075
|
+
const tempPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
1076
|
+
await writeFile(
|
|
1077
|
+
tempPath,
|
|
1078
|
+
`${JSON.stringify({
|
|
1079
|
+
schemaVersion: RELEASE_RECEIPT_SCHEMA_VERSION,
|
|
1080
|
+
projectKey: receipt.projectKey,
|
|
1081
|
+
receipts
|
|
1082
|
+
}, null, 2)}
|
|
1083
|
+
`,
|
|
1084
|
+
{ encoding: "utf8", mode: 384 }
|
|
1085
|
+
);
|
|
1086
|
+
await rename(tempPath, filePath);
|
|
1087
|
+
await chmod(filePath, 384);
|
|
1088
|
+
}
|
|
1089
|
+
async list(projectRoot, limit = 20) {
|
|
1090
|
+
const canonicalRoot = await canonicalizeProjectRoot(projectRoot);
|
|
1091
|
+
const projectKey = createProjectKey(canonicalRoot);
|
|
1092
|
+
const data = await this.readFile(this.getFilePath(projectKey), projectKey);
|
|
1093
|
+
return data.receipts.slice(0, Math.max(0, limit));
|
|
1094
|
+
}
|
|
1095
|
+
async save(input) {
|
|
1096
|
+
const receipt = await createReleaseReceipt(input);
|
|
1097
|
+
await this.append(receipt);
|
|
1098
|
+
return receipt;
|
|
1099
|
+
}
|
|
1100
|
+
getFilePath(projectKey) {
|
|
1101
|
+
return path2.join(this.storageRoot, `${projectKey}.json`);
|
|
1102
|
+
}
|
|
1103
|
+
async readFile(filePath, projectKey) {
|
|
1104
|
+
try {
|
|
1105
|
+
const raw = JSON.parse(await readFile(filePath, "utf8"));
|
|
1106
|
+
return {
|
|
1107
|
+
schemaVersion: RELEASE_RECEIPT_SCHEMA_VERSION,
|
|
1108
|
+
projectKey,
|
|
1109
|
+
receipts: Array.isArray(raw.receipts) ? raw.receipts : []
|
|
1110
|
+
};
|
|
1111
|
+
} catch (error) {
|
|
1112
|
+
if (error?.code === "ENOENT") {
|
|
1113
|
+
return { schemaVersion: RELEASE_RECEIPT_SCHEMA_VERSION, projectKey, receipts: [] };
|
|
1114
|
+
}
|
|
1115
|
+
throw new Error(`\u8BFB\u53D6\u53D1\u5E03\u56DE\u6267\u5931\u8D25: ${sanitizeReleaseMessage(error?.message || error)}`);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
};
|
|
1119
|
+
function createProjectKey(projectRoot) {
|
|
1120
|
+
return createHash("sha256").update(path2.resolve(projectRoot)).digest("hex").slice(0, 24);
|
|
1121
|
+
}
|
|
1122
|
+
function createRemoteReceipt(result, data) {
|
|
1123
|
+
const identifiers = {};
|
|
1124
|
+
for (const key of REMOTE_IDENTIFIER_KEYS) {
|
|
1125
|
+
const value = data?.[key];
|
|
1126
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
1127
|
+
identifiers[key] = typeof value === "string" ? sanitizeReleaseMessage(value, 240) : value;
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
const consoleUrl = cleanHttpUrl(result.consoleUrl);
|
|
1131
|
+
const downloadUrl = cleanHttpUrl(result.url || result.link);
|
|
1132
|
+
if (!consoleUrl && !downloadUrl && Object.keys(identifiers).length === 0) return void 0;
|
|
1133
|
+
return {
|
|
1134
|
+
...consoleUrl ? { consoleUrl } : {},
|
|
1135
|
+
...downloadUrl ? { downloadUrl } : {},
|
|
1136
|
+
...Object.keys(identifiers).length > 0 ? { identifiers } : {}
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
function sanitizeTarget(target) {
|
|
1140
|
+
return {
|
|
1141
|
+
platform: sanitizeReleaseMessage(target.platform, 80) || "unknown",
|
|
1142
|
+
state: target.state,
|
|
1143
|
+
outcome: target.outcome,
|
|
1144
|
+
message: sanitizeReleaseMessage(target.message),
|
|
1145
|
+
...target.remote ? { remote: sanitizeRemote(target.remote) } : {},
|
|
1146
|
+
...cleanOptionalText(target.lastKnownRemoteStatus, 120) ? { lastKnownRemoteStatus: cleanOptionalText(target.lastKnownRemoteStatus, 120) } : {},
|
|
1147
|
+
...cleanOptionalText(target.checkedAt, 80) ? { checkedAt: cleanOptionalText(target.checkedAt, 80) } : {}
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
function sanitizeRemote(remote) {
|
|
1151
|
+
const identifiers = {};
|
|
1152
|
+
for (const [key, value] of Object.entries(remote.identifiers || {})) {
|
|
1153
|
+
if (!REMOTE_IDENTIFIER_KEYS.includes(key)) continue;
|
|
1154
|
+
if (typeof value === "string") identifiers[key] = sanitizeReleaseMessage(value, 240);
|
|
1155
|
+
if (typeof value === "number" || typeof value === "boolean") identifiers[key] = value;
|
|
1156
|
+
}
|
|
1157
|
+
const consoleUrl = cleanHttpUrl(remote.consoleUrl);
|
|
1158
|
+
const downloadUrl = cleanHttpUrl(remote.downloadUrl);
|
|
1159
|
+
return {
|
|
1160
|
+
...consoleUrl ? { consoleUrl } : {},
|
|
1161
|
+
...downloadUrl ? { downloadUrl } : {},
|
|
1162
|
+
...Object.keys(identifiers).length > 0 ? { identifiers } : {}
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
async function inspectArtifacts(value) {
|
|
1166
|
+
const paths = Array.from(new Set(flattenArtifactPaths(value).map((item) => path2.resolve(item))));
|
|
1167
|
+
return Promise.all(paths.map(inspectArtifact));
|
|
1168
|
+
}
|
|
1169
|
+
async function inspectArtifact(filePath) {
|
|
1170
|
+
const base = { path: filePath, name: path2.basename(filePath) };
|
|
1171
|
+
try {
|
|
1172
|
+
const info = await stat(filePath);
|
|
1173
|
+
if (!info.isFile()) return { ...base, available: false };
|
|
1174
|
+
return {
|
|
1175
|
+
...base,
|
|
1176
|
+
sizeBytes: info.size,
|
|
1177
|
+
sha256: await hashFile(filePath),
|
|
1178
|
+
available: true
|
|
1179
|
+
};
|
|
1180
|
+
} catch {
|
|
1181
|
+
return { ...base, available: false };
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
function hashFile(filePath) {
|
|
1185
|
+
return new Promise((resolve, reject) => {
|
|
1186
|
+
const hash = createHash("sha256");
|
|
1187
|
+
const stream = createReadStream(filePath);
|
|
1188
|
+
stream.on("error", reject);
|
|
1189
|
+
stream.on("data", (chunk) => hash.update(chunk));
|
|
1190
|
+
stream.on("end", () => resolve(hash.digest("hex")));
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1193
|
+
function flattenArtifactPaths(value) {
|
|
1194
|
+
if (typeof value === "string" && value.trim()) return [value];
|
|
1195
|
+
if (Array.isArray(value)) return value.flatMap(flattenArtifactPaths);
|
|
1196
|
+
if (isRecord(value)) return Object.values(value).flatMap(flattenArtifactPaths);
|
|
1197
|
+
return [];
|
|
1198
|
+
}
|
|
1199
|
+
function inferOutcome(targets) {
|
|
1200
|
+
if (targets.length === 0) return "failed";
|
|
1201
|
+
if (targets.every((target) => target.outcome === "cancelled")) return "cancelled";
|
|
1202
|
+
const successCount = targets.filter((target) => target.outcome === "success").length;
|
|
1203
|
+
if (successCount === targets.length) return "success";
|
|
1204
|
+
if (successCount > 0) return "partial";
|
|
1205
|
+
return "failed";
|
|
1206
|
+
}
|
|
1207
|
+
async function canonicalizeProjectRoot(projectRoot) {
|
|
1208
|
+
const resolved = path2.resolve(projectRoot);
|
|
1209
|
+
try {
|
|
1210
|
+
return await realpath(resolved);
|
|
1211
|
+
} catch {
|
|
1212
|
+
return resolved;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
function toDate(value) {
|
|
1216
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
1217
|
+
return Number.isNaN(date.getTime()) ? /* @__PURE__ */ new Date() : date;
|
|
1218
|
+
}
|
|
1219
|
+
function cleanOptionalText(value, maxLength) {
|
|
1220
|
+
const cleaned = sanitizeReleaseMessage(value, maxLength);
|
|
1221
|
+
return cleaned || void 0;
|
|
1222
|
+
}
|
|
1223
|
+
function cleanHttpUrl(value) {
|
|
1224
|
+
if (typeof value !== "string") return void 0;
|
|
1225
|
+
try {
|
|
1226
|
+
const url = new URL(value);
|
|
1227
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return void 0;
|
|
1228
|
+
for (const key of Array.from(url.searchParams.keys())) {
|
|
1229
|
+
if (/token|secret|password|signature|credential|key/i.test(key)) {
|
|
1230
|
+
url.searchParams.set(key, "[\u5DF2\u8131\u654F]");
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
return url.toString();
|
|
1234
|
+
} catch {
|
|
1235
|
+
return void 0;
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
function isRecord(value) {
|
|
1239
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
1240
|
+
}
|
|
1241
|
+
|
|
484
1242
|
// src/adapters/pgyer_uploader.ts
|
|
485
1243
|
var PgyerUploader = class _PgyerUploader {
|
|
486
1244
|
name = "Pgyer";
|
|
@@ -599,7 +1357,7 @@ var PgyerUploader = class _PgyerUploader {
|
|
|
599
1357
|
|
|
600
1358
|
// src/adapters/huawei_uploader.ts
|
|
601
1359
|
import fs4 from "fs-extra";
|
|
602
|
-
import
|
|
1360
|
+
import path3 from "path";
|
|
603
1361
|
import { promisify } from "util";
|
|
604
1362
|
import axios2 from "axios";
|
|
605
1363
|
import FormData2 from "form-data";
|
|
@@ -678,7 +1436,7 @@ var HuaweiUploader = class _HuaweiUploader {
|
|
|
678
1436
|
const clientSecret = hwConfig.auth.clientSecret;
|
|
679
1437
|
const appId = hwConfig.appId;
|
|
680
1438
|
const releaseType = hwConfig.releaseType || "draft";
|
|
681
|
-
const suffix =
|
|
1439
|
+
const suffix = path3.extname(filePath).substring(1);
|
|
682
1440
|
const retryConfig = config.retry;
|
|
683
1441
|
const tokenUrl = this.getTokenUrl(config);
|
|
684
1442
|
const publishApiRoot = this.getApiRoot(config);
|
|
@@ -699,27 +1457,39 @@ var HuaweiUploader = class _HuaweiUploader {
|
|
|
699
1457
|
clientId,
|
|
700
1458
|
appId
|
|
701
1459
|
};
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
1460
|
+
const artifacts = typeof metadata.artifactPath === "object" ? metadata.artifactPath : { universal: filePath || metadata.artifactPath || "" };
|
|
1461
|
+
logger.info(`[Huawei] \u5904\u7406\u4EA7\u7269\u6570\u91CF: ${Object.keys(artifacts).length}`);
|
|
1462
|
+
const uploadResults = [];
|
|
1463
|
+
const uploadPromises = Object.entries(artifacts).map(async ([abi, p]) => {
|
|
1464
|
+
if (!p || !fs4.existsSync(p)) return;
|
|
1465
|
+
const currentSuffix = path3.extname(p).substring(1);
|
|
1466
|
+
logger.info(`[Huawei] \u6B63\u5728\u4E0A\u4F20 ${abi} \u5305: ${path3.basename(p)}`);
|
|
1467
|
+
const uploadInfo = await retryStrategy.execute(
|
|
1468
|
+
() => this.getUploadUrl(ctx, currentSuffix),
|
|
1469
|
+
`Huawei get upload URL for ${abi}`
|
|
1470
|
+
);
|
|
1471
|
+
if (!uploadInfo.success || !uploadInfo.data) {
|
|
1472
|
+
throw uploadInfo.error || new Error(`Failed to get upload URL for ${abi}`);
|
|
1473
|
+
}
|
|
1474
|
+
const uploadFileRes = await retryStrategy.execute(
|
|
1475
|
+
() => this.executeUploadRequest(uploadInfo.data.uploadUrl, uploadInfo.data.authCode, p),
|
|
1476
|
+
`Huawei upload file for ${abi}`
|
|
1477
|
+
);
|
|
1478
|
+
if (!uploadFileRes.success || !uploadFileRes.data || uploadFileRes.data.length === 0) {
|
|
1479
|
+
throw uploadFileRes.error || new Error(`File upload failed for ${abi}`);
|
|
1480
|
+
}
|
|
1481
|
+
uploadResults.push({
|
|
1482
|
+
fileName: path3.basename(p),
|
|
1483
|
+
fileDestUrl: uploadFileRes.data[0].fileDestUrl
|
|
1484
|
+
});
|
|
1485
|
+
});
|
|
1486
|
+
await Promise.all(uploadPromises);
|
|
1487
|
+
if (uploadResults.length === 0) {
|
|
1488
|
+
throw new Error("No files were successfully uploaded");
|
|
718
1489
|
}
|
|
719
|
-
|
|
720
|
-
logger.info("Linking file to app...");
|
|
1490
|
+
logger.info(`[Huawei] \u6B63\u5728\u5173\u8054 ${uploadResults.length} \u4E2A\u6587\u4EF6\u5230\u5E94\u7528...`);
|
|
721
1491
|
const updateInfoResult = await retryStrategy.execute(
|
|
722
|
-
() => this.updateAppFileInfo(ctx,
|
|
1492
|
+
() => this.updateAppFileInfo(ctx, uploadResults, filePath),
|
|
723
1493
|
"Huawei update file info"
|
|
724
1494
|
);
|
|
725
1495
|
if (!updateInfoResult.success) {
|
|
@@ -881,7 +1651,7 @@ var HuaweiUploader = class _HuaweiUploader {
|
|
|
881
1651
|
}
|
|
882
1652
|
/** 通用文件上传(FormData 方式,无需 API 认证头) */
|
|
883
1653
|
async executeUploadRequest(uploadUrl, authCode, filePath) {
|
|
884
|
-
const fileName =
|
|
1654
|
+
const fileName = path3.basename(filePath);
|
|
885
1655
|
const fileSize = fs4.statSync(filePath).size;
|
|
886
1656
|
const form = new FormData2();
|
|
887
1657
|
form.append("file", fs4.createReadStream(filePath), {
|
|
@@ -923,14 +1693,17 @@ var HuaweiUploader = class _HuaweiUploader {
|
|
|
923
1693
|
throw new Error("Upload succeeded but fileDestUrl not found in response");
|
|
924
1694
|
}
|
|
925
1695
|
/** 更新应用文件信息 */
|
|
926
|
-
async updateAppFileInfo(ctx,
|
|
927
|
-
const
|
|
928
|
-
const fileType =
|
|
1696
|
+
async updateAppFileInfo(ctx, files, anyFilePath) {
|
|
1697
|
+
const hasAab = files.some((f) => f.fileName.toLowerCase().endsWith(".aab"));
|
|
1698
|
+
const fileType = hasAab ? 7 /* AAB */ : 5 /* APP_PACKAGE */;
|
|
929
1699
|
const res = await axios2.put(
|
|
930
1700
|
`${ctx.apiRoot}/app-file-info`,
|
|
931
1701
|
{
|
|
932
1702
|
fileType,
|
|
933
|
-
files:
|
|
1703
|
+
files: files.map((f) => ({
|
|
1704
|
+
fileName: f.fileName,
|
|
1705
|
+
fileDestUrl: f.fileDestUrl
|
|
1706
|
+
}))
|
|
934
1707
|
},
|
|
935
1708
|
{
|
|
936
1709
|
params: { appId: ctx.appId },
|
|
@@ -1045,7 +1818,7 @@ var HuaweiUploader = class _HuaweiUploader {
|
|
|
1045
1818
|
logger.warn(`Asset file not found, skipping: ${filePath}`);
|
|
1046
1819
|
continue;
|
|
1047
1820
|
}
|
|
1048
|
-
const suffix =
|
|
1821
|
+
const suffix = path3.extname(filePath).substring(1).toLowerCase();
|
|
1049
1822
|
if (!HUAWEI_SUPPORTED_SUFFIXES.image.includes(suffix)) {
|
|
1050
1823
|
logger.warn(`Unsupported file type ${suffix}, skipping: ${filePath}`);
|
|
1051
1824
|
continue;
|
|
@@ -1100,6 +1873,7 @@ var HuaweiUploader = class _HuaweiUploader {
|
|
|
1100
1873
|
|
|
1101
1874
|
// src/adapters/xiaomi_uploader.ts
|
|
1102
1875
|
import fs5 from "fs-extra";
|
|
1876
|
+
import path4 from "path";
|
|
1103
1877
|
import axios3 from "axios";
|
|
1104
1878
|
import FormData3 from "form-data";
|
|
1105
1879
|
import crypto3 from "crypto";
|
|
@@ -1350,80 +2124,39 @@ ${lines.join("\n")}
|
|
|
1350
2124
|
const xiaomiConfig = config.publish?.xiaomi;
|
|
1351
2125
|
const { userName, privateKey, publicKeyPath } = xiaomiConfig.auth;
|
|
1352
2126
|
const updateDesc = metadata.changelog || xiaomiConfig?.description || "App Update";
|
|
1353
|
-
const
|
|
1354
|
-
if (!fs5.existsSync(filePath)) {
|
|
1355
|
-
return {
|
|
1356
|
-
platform: "xiaomi",
|
|
1357
|
-
success: false,
|
|
1358
|
-
message: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}`
|
|
1359
|
-
};
|
|
1360
|
-
}
|
|
2127
|
+
const artifacts = typeof metadata.artifactPath === "object" ? metadata.artifactPath : { universal: filePath || (typeof metadata.artifactPath === "string" ? metadata.artifactPath : "") };
|
|
1361
2128
|
const resolvedPublicKey = await this.resolvePublicKey(publicKeyPath);
|
|
1362
2129
|
if (!resolvedPublicKey) {
|
|
1363
|
-
|
|
1364
|
-
platform: "xiaomi",
|
|
1365
|
-
success: false,
|
|
1366
|
-
message: "\u65E0\u6CD5\u83B7\u53D6 RSA \u516C\u94A5: publicKeyPath \u672A\u63D0\u4F9B\u6216\u6587\u4EF6\u4E0D\u5B58\u5728"
|
|
1367
|
-
};
|
|
1368
|
-
}
|
|
1369
|
-
const packageName = xiaomiConfig.packageName || this.inferPackageName(filePath);
|
|
1370
|
-
if (!packageName) {
|
|
1371
|
-
return {
|
|
1372
|
-
platform: "xiaomi",
|
|
1373
|
-
success: false,
|
|
1374
|
-
message: "\u7F3A\u5C11 packageName \u914D\u7F6E, \u4E14\u65E0\u6CD5\u4ECE\u6587\u4EF6\u540D\u63A8\u65AD\u5E94\u7528\u5305\u540D"
|
|
1375
|
-
};
|
|
1376
|
-
}
|
|
1377
|
-
const appName = xiaomiConfig.appName || this.inferAppName(packageName);
|
|
1378
|
-
if (!appName) {
|
|
1379
|
-
return {
|
|
1380
|
-
platform: "xiaomi",
|
|
1381
|
-
success: false,
|
|
1382
|
-
message: "\u7F3A\u5C11 appName \u914D\u7F6E, \u4E14\u65E0\u6CD5\u4ECE\u5305\u540D\u63A8\u65AD\u5E94\u7528\u540D\u79F0"
|
|
1383
|
-
};
|
|
2130
|
+
throw new Error("\u65E0\u6CD5\u83B7\u53D6 RSA \u516C\u94A5");
|
|
1384
2131
|
}
|
|
1385
|
-
logger.info("\u5F00\u59CB\u4E0A\u4F20\u5230\u5C0F\u7C73\u5E94\u7528\u5546\u5E97...");
|
|
1386
|
-
logger.info(`\u6587\u4EF6: ${filePath}`);
|
|
1387
|
-
logger.info(`\u5305\u540D: ${packageName}`);
|
|
1388
|
-
logger.info(`\u5E94\u7528\u540D: ${appName}`);
|
|
1389
2132
|
const publicKeyObj = this.extractPublicKey(resolvedPublicKey);
|
|
1390
|
-
const
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
logger.info("\u6B63\u5728\u67E5\u8BE2\u5E94\u7528\u4FE1\u606F...");
|
|
1395
|
-
const status = await this.queryAppStatus(userName, packageName, privateKey, publicKeyObj);
|
|
1396
|
-
uploadType = status.type;
|
|
1397
|
-
existingAppInfo = status.appInfo;
|
|
1398
|
-
} else {
|
|
1399
|
-
uploadType = configUploadType;
|
|
1400
|
-
}
|
|
1401
|
-
const uploadTypeDesc = uploadType === "update" ? "\u5E94\u7528\u66F4\u65B0 (synchroType=1)" : "\u65B0\u589E\u5E94\u7528 (synchroType=0)";
|
|
1402
|
-
logger.info(`\u4E0A\u4F20\u7C7B\u578B: ${uploadTypeDesc}`);
|
|
1403
|
-
if (uploadType === "new" && xiaomiConfig.uploadType === "new") {
|
|
1404
|
-
const missing = [];
|
|
1405
|
-
if (xiaomiConfig.category == null) missing.push("category (\u5206\u7C7BID)");
|
|
1406
|
-
if (!xiaomiConfig.desc) missing.push("desc (\u5E94\u7528\u4ECB\u7ECD)");
|
|
1407
|
-
if (missing.length > 0) {
|
|
1408
|
-
return {
|
|
1409
|
-
platform: "xiaomi",
|
|
1410
|
-
success: false,
|
|
1411
|
-
message: `\u9996\u6B21\u63D0\u5BA1\u6A21\u5F0F\u7F3A\u5C11\u5FC5\u586B\u5B57\u6BB5: ${missing.join(", ")}\u3002\u8BF7\u5728\u540E\u53F0\u8865\u5168\u6216\u5728\u914D\u7F6E\u4E2D\u6DFB\u52A0\u3002`
|
|
1412
|
-
};
|
|
1413
|
-
}
|
|
1414
|
-
}
|
|
2133
|
+
const anyPath = Object.values(artifacts)[0];
|
|
2134
|
+
const packageName = xiaomiConfig.packageName || this.inferPackageName(anyPath);
|
|
2135
|
+
if (!packageName) throw new Error("\u7F3A\u5C11 packageName");
|
|
2136
|
+
const appName = xiaomiConfig.appName || this.inferAppName(packageName);
|
|
1415
2137
|
try {
|
|
1416
|
-
|
|
2138
|
+
logger.info(`\u5F00\u59CB\u4E0A\u4F20\u5230\u5C0F\u7C73\u5E94\u7528\u5546\u5E97 (\u5305\u542B ${Object.keys(artifacts).length} \u4E2A\u5305)...`);
|
|
2139
|
+
let uploadType = xiaomiConfig.uploadType || "auto";
|
|
2140
|
+
let finalUploadType;
|
|
2141
|
+
let existingAppInfo = null;
|
|
2142
|
+
if (uploadType === "auto") {
|
|
2143
|
+
const status = await this.queryAppStatus(userName, packageName, privateKey, publicKeyObj);
|
|
2144
|
+
finalUploadType = status.type;
|
|
2145
|
+
existingAppInfo = status.appInfo;
|
|
2146
|
+
} else {
|
|
2147
|
+
finalUploadType = uploadType;
|
|
2148
|
+
}
|
|
2149
|
+
const retryStrategy = createUploadRetryStrategy(config.retry);
|
|
1417
2150
|
const result = await retryStrategy.execute(
|
|
1418
2151
|
() => this.performUpload({
|
|
1419
|
-
|
|
2152
|
+
artifacts,
|
|
1420
2153
|
userName,
|
|
1421
2154
|
privateKey,
|
|
1422
2155
|
publicKey: resolvedPublicKey,
|
|
2156
|
+
uploadType: finalUploadType,
|
|
1423
2157
|
packageName,
|
|
1424
|
-
appName,
|
|
2158
|
+
appName: appName || "",
|
|
1425
2159
|
updateDesc,
|
|
1426
|
-
uploadType,
|
|
1427
2160
|
category: xiaomiConfig.category || existingAppInfo?.category,
|
|
1428
2161
|
keyWords: xiaomiConfig.keyWords || existingAppInfo?.keyWords,
|
|
1429
2162
|
desc: xiaomiConfig.desc || existingAppInfo?.desc,
|
|
@@ -1433,50 +2166,19 @@ ${lines.join("\n")}
|
|
|
1433
2166
|
}),
|
|
1434
2167
|
"Xiaomi push upload"
|
|
1435
2168
|
);
|
|
1436
|
-
if (!result.success) {
|
|
1437
|
-
throw result.error || new Error("Upload failed");
|
|
1438
|
-
}
|
|
1439
|
-
logger.success("\u4E0A\u4F20\u5230\u5C0F\u7C73\u5E94\u7528\u5546\u5E97\u6210\u529F!");
|
|
1440
2169
|
return {
|
|
1441
2170
|
platform: "xiaomi",
|
|
1442
2171
|
success: true,
|
|
1443
|
-
message: "\u4E0A\u4F20\u6210\u529F
|
|
1444
|
-
data: result.data,
|
|
2172
|
+
message: "\u4E0A\u4F20\u6210\u529F",
|
|
1445
2173
|
consoleUrl: "https://dev.mi.com/distribute/app/details"
|
|
1446
2174
|
};
|
|
1447
2175
|
} catch (error) {
|
|
1448
|
-
logger.error(`\u4E0A\u4F20\
|
|
1449
|
-
|
|
1450
|
-
logger.info(`\u9519\u8BEF\u8BE6\u60C5: ${JSON.stringify(error.response.data)}`);
|
|
1451
|
-
}
|
|
1452
|
-
return {
|
|
1453
|
-
platform: "xiaomi",
|
|
1454
|
-
success: false,
|
|
1455
|
-
message: error.message,
|
|
1456
|
-
error
|
|
1457
|
-
};
|
|
2176
|
+
logger.error(`\u5C0F\u7C73\u4E0A\u4F20\u5931\u8D25: ${error.message}`);
|
|
2177
|
+
return { platform: "xiaomi", success: false, message: error.message, error };
|
|
1458
2178
|
}
|
|
1459
2179
|
}
|
|
1460
|
-
/**
|
|
1461
|
-
* 执行实际的上传操作
|
|
1462
|
-
*/
|
|
1463
2180
|
async performUpload(params) {
|
|
1464
|
-
const {
|
|
1465
|
-
filePath,
|
|
1466
|
-
userName,
|
|
1467
|
-
privateKey,
|
|
1468
|
-
publicKey,
|
|
1469
|
-
packageName,
|
|
1470
|
-
appName,
|
|
1471
|
-
updateDesc,
|
|
1472
|
-
uploadType,
|
|
1473
|
-
category,
|
|
1474
|
-
keyWords,
|
|
1475
|
-
desc,
|
|
1476
|
-
brief,
|
|
1477
|
-
privacyUrl,
|
|
1478
|
-
assets
|
|
1479
|
-
} = params;
|
|
2181
|
+
const { artifacts, userName, privateKey, publicKey, packageName, appName, updateDesc, uploadType, category, keyWords, desc, brief, privacyUrl, assets } = params;
|
|
1480
2182
|
const publicKeyObj = this.extractPublicKey(publicKey);
|
|
1481
2183
|
const requestData = this.buildRequestData({
|
|
1482
2184
|
userName,
|
|
@@ -1490,67 +2192,55 @@ ${lines.join("\n")}
|
|
|
1490
2192
|
brief,
|
|
1491
2193
|
privacyUrl
|
|
1492
2194
|
});
|
|
1493
|
-
logger.info("RequestData \u5DF2\u6784\u5EFA");
|
|
1494
|
-
logger.info(`RequestData: ${requestData}`);
|
|
1495
2195
|
const requestDataMD5 = this.calculateStringMD5(requestData);
|
|
1496
|
-
|
|
1497
|
-
|
|
2196
|
+
let mainPath;
|
|
2197
|
+
let secondPath = void 0;
|
|
2198
|
+
const artifactEntries = Object.entries(artifacts);
|
|
2199
|
+
const arm64 = artifactEntries.find(([abi]) => abi === "arm64-v8a");
|
|
2200
|
+
const arm32 = artifactEntries.find(([abi]) => abi === "armeabi-v7a");
|
|
2201
|
+
if (arm64 && arm32) {
|
|
2202
|
+
mainPath = arm64[1];
|
|
2203
|
+
secondPath = arm32[1];
|
|
2204
|
+
logger.info(`[Xiaomi] \u8BC6\u522B\u5230\u591A\u67B6\u6784\u5206\u5305: \u4E3B\u5305(64\u4F4D)=${path4.basename(mainPath)}, \u6B21\u5305(32\u4F4D)=${path4.basename(secondPath)}`);
|
|
2205
|
+
} else {
|
|
2206
|
+
mainPath = artifactEntries[0][1];
|
|
2207
|
+
if (artifactEntries.length > 1) {
|
|
2208
|
+
secondPath = artifactEntries[1][1];
|
|
2209
|
+
logger.info(`[Xiaomi] \u8BC6\u522B\u5230\u591A\u4E2A\u5305\uFF08\u975E\u6807\u51C6\u540D\uFF09\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u987A\u5E8F: \u4E3B\u5305=${path4.basename(mainPath)}, \u6B21\u5305=${path4.basename(secondPath)}`);
|
|
2210
|
+
} else {
|
|
2211
|
+
logger.info(`[Xiaomi] \u4F7F\u7528\u5355\u5305\u6A21\u5F0F: ${path4.basename(mainPath)}`);
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
const mainMD5 = await this.calculateFileMD5(mainPath);
|
|
1498
2215
|
const extraSigItems = [];
|
|
1499
|
-
|
|
2216
|
+
if (secondPath) {
|
|
2217
|
+
const secondMD5 = await this.calculateFileMD5(secondPath);
|
|
2218
|
+
extraSigItems.push({ name: "secondApk", hash: secondMD5 });
|
|
2219
|
+
}
|
|
1500
2220
|
if (assets?.icon && fs5.existsSync(assets.icon)) {
|
|
1501
|
-
|
|
1502
|
-
const iconMD5 = await this.calculateFileMD5(iconPath);
|
|
1503
|
-
extraSigItems.push({ name: "icon", hash: iconMD5 });
|
|
1504
|
-
}
|
|
1505
|
-
const screenshotPaths = [];
|
|
1506
|
-
if (assets?.screenshots) {
|
|
1507
|
-
for (let i = 0; i < assets.screenshots.length && i < 4; i++) {
|
|
1508
|
-
const ssPath = assets.screenshots[i];
|
|
1509
|
-
if (fs5.existsSync(ssPath)) {
|
|
1510
|
-
screenshotPaths.push(ssPath);
|
|
1511
|
-
const ssMD5 = await this.calculateFileMD5(ssPath);
|
|
1512
|
-
extraSigItems.push({ name: `screenshot_${i + 1}`, hash: ssMD5 });
|
|
1513
|
-
}
|
|
1514
|
-
}
|
|
2221
|
+
extraSigItems.push({ name: "icon", hash: await this.calculateFileMD5(assets.icon) });
|
|
1515
2222
|
}
|
|
1516
|
-
const sig = this.generateUploadSIG(
|
|
1517
|
-
requestDataMD5,
|
|
1518
|
-
apkFileMD5,
|
|
1519
|
-
privateKey,
|
|
1520
|
-
publicKeyObj,
|
|
1521
|
-
extraSigItems.length > 0 ? extraSigItems : void 0
|
|
1522
|
-
);
|
|
1523
|
-
logger.info("SIG \u7B7E\u540D\u5DF2\u751F\u6210");
|
|
2223
|
+
const sig = this.generateUploadSIG(requestDataMD5, mainMD5, privateKey, publicKeyObj, extraSigItems);
|
|
1524
2224
|
const form = new FormData3();
|
|
1525
2225
|
form.append("RequestData", requestData);
|
|
1526
2226
|
form.append("SIG", sig);
|
|
1527
|
-
form.append("apk", fs5.createReadStream(
|
|
1528
|
-
if (
|
|
1529
|
-
form.append("
|
|
1530
|
-
logger.info(`\u9644\u52A0 icon: ${iconPath}`);
|
|
2227
|
+
form.append("apk", fs5.createReadStream(mainPath));
|
|
2228
|
+
if (secondPath) {
|
|
2229
|
+
form.append("secondApk", fs5.createReadStream(secondPath));
|
|
1531
2230
|
}
|
|
1532
|
-
|
|
1533
|
-
form.append(
|
|
1534
|
-
logger.info(`\u9644\u52A0 screenshot_${i + 1}: ${screenshotPaths[i]}`);
|
|
2231
|
+
if (assets?.icon && fs5.existsSync(assets.icon)) {
|
|
2232
|
+
form.append("icon", fs5.createReadStream(assets.icon));
|
|
1535
2233
|
}
|
|
1536
|
-
logger.info(`POST ${_XiaomiUploader.PUSH_API}`);
|
|
1537
2234
|
const response = await axios3.post(_XiaomiUploader.PUSH_API, form, {
|
|
1538
|
-
headers:
|
|
1539
|
-
...form.getHeaders()
|
|
1540
|
-
},
|
|
2235
|
+
headers: form.getHeaders(),
|
|
1541
2236
|
maxContentLength: Infinity,
|
|
1542
2237
|
maxBodyLength: Infinity,
|
|
1543
|
-
timeout:
|
|
1544
|
-
// 60 秒请求超时
|
|
2238
|
+
timeout: 12e4
|
|
1545
2239
|
});
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
if (data?.result !== 0) {
|
|
1549
|
-
const errMsg = data?.message || data?.msg || JSON.stringify(data);
|
|
1550
|
-
throw new Error(`\u5C0F\u7C73 API \u8FD4\u56DE\u9519\u8BEF (result=${data?.result}): ${errMsg}`);
|
|
2240
|
+
if (response.data?.result !== 0) {
|
|
2241
|
+
throw new Error(`\u5C0F\u7C73 API \u9519\u8BEF: ${response.data?.message || JSON.stringify(response.data)}`);
|
|
1551
2242
|
}
|
|
1552
|
-
|
|
1553
|
-
return data;
|
|
2243
|
+
return response.data;
|
|
1554
2244
|
}
|
|
1555
2245
|
/**
|
|
1556
2246
|
* 解析公钥: 优先使用内容字段, 其次从文件路径读取
|
|
@@ -1592,7 +2282,7 @@ ${lines.join("\n")}
|
|
|
1592
2282
|
|
|
1593
2283
|
// src/adapters/apple_store_uploader.ts
|
|
1594
2284
|
import fs6 from "fs-extra";
|
|
1595
|
-
import
|
|
2285
|
+
import path5 from "path";
|
|
1596
2286
|
import { exec as exec2 } from "child_process";
|
|
1597
2287
|
import { promisify as promisify2 } from "util";
|
|
1598
2288
|
import axios4 from "axios";
|
|
@@ -1758,7 +2448,7 @@ var AppleStoreUploader = class {
|
|
|
1758
2448
|
appPassword,
|
|
1759
2449
|
"-showTransferRate"
|
|
1760
2450
|
].join(" ");
|
|
1761
|
-
logger.info(`[Transporter] Uploading: ${
|
|
2451
|
+
logger.info(`[Transporter] Uploading: ${path5.basename(ipaPath)}`);
|
|
1762
2452
|
try {
|
|
1763
2453
|
const { stdout, stderr } = await execAsync(command, {
|
|
1764
2454
|
timeout: 30 * 60 * 1e3,
|
|
@@ -1809,7 +2499,7 @@ Stderr: ${error.stderr || ""}`);
|
|
|
1809
2499
|
});
|
|
1810
2500
|
command = innerArgs.join(" ");
|
|
1811
2501
|
}
|
|
1812
|
-
logger.info(`[altool] Uploading: ${
|
|
2502
|
+
logger.info(`[altool] Uploading: ${path5.basename(ipaPath)}`);
|
|
1813
2503
|
logger.info(`[altool] Command prefix: ${tool.command}`);
|
|
1814
2504
|
try {
|
|
1815
2505
|
const { stdout, stderr } = await execAsync(command, {
|
|
@@ -2393,7 +3083,7 @@ var TestFlightUploader = class _TestFlightUploader {
|
|
|
2393
3083
|
|
|
2394
3084
|
// src/adapters/adhoc_uploader.ts
|
|
2395
3085
|
import fs8 from "fs-extra";
|
|
2396
|
-
import
|
|
3086
|
+
import path6 from "path";
|
|
2397
3087
|
import { execSync } from "child_process";
|
|
2398
3088
|
var AdHocUploader = class {
|
|
2399
3089
|
name = "AdHoc";
|
|
@@ -2526,8 +3216,8 @@ var AdHocUploader = class {
|
|
|
2526
3216
|
* @param changelog 更新日志
|
|
2527
3217
|
*/
|
|
2528
3218
|
async createManifest(ipaPath, version, bundleId, changelog) {
|
|
2529
|
-
const ipaFileName =
|
|
2530
|
-
const appName = bundleId ||
|
|
3219
|
+
const ipaFileName = path6.basename(ipaPath);
|
|
3220
|
+
const appName = bundleId || path6.parse(ipaFileName).name;
|
|
2531
3221
|
const ipaUrl = `itms-services://?action=download-manifest&url=https://your-distribution-server.com/manifests/${ipaFileName}.plist`;
|
|
2532
3222
|
const manifestContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|
2533
3223
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
@@ -2586,14 +3276,14 @@ var AdHocUploader = class {
|
|
|
2586
3276
|
*/
|
|
2587
3277
|
async setupDistributionDirectory(ipaPath, manifestPath) {
|
|
2588
3278
|
const projectRoot = process.cwd();
|
|
2589
|
-
const distributionDir =
|
|
3279
|
+
const distributionDir = path6.join(projectRoot, "build", "adhoc-distribution");
|
|
2590
3280
|
await fs8.ensureDir(distributionDir);
|
|
2591
|
-
await fs8.ensureDir(
|
|
2592
|
-
await fs8.ensureDir(
|
|
2593
|
-
await fs8.ensureDir(
|
|
2594
|
-
const ipaFileName =
|
|
2595
|
-
await fs8.copy(ipaPath,
|
|
2596
|
-
await fs8.copy(manifestPath,
|
|
3281
|
+
await fs8.ensureDir(path6.join(distributionDir, "apps"));
|
|
3282
|
+
await fs8.ensureDir(path6.join(distributionDir, "manifests"));
|
|
3283
|
+
await fs8.ensureDir(path6.join(distributionDir, "icons"));
|
|
3284
|
+
const ipaFileName = path6.basename(ipaPath);
|
|
3285
|
+
await fs8.copy(ipaPath, path6.join(distributionDir, "apps", ipaFileName));
|
|
3286
|
+
await fs8.copy(manifestPath, path6.join(distributionDir, "manifests", `${ipaFileName}.plist`));
|
|
2597
3287
|
logger.info(`Distribution directory: ${distributionDir}`);
|
|
2598
3288
|
return distributionDir;
|
|
2599
3289
|
}
|
|
@@ -2603,7 +3293,7 @@ var AdHocUploader = class {
|
|
|
2603
3293
|
* @param bundleId Bundle ID
|
|
2604
3294
|
*/
|
|
2605
3295
|
generateInstallUrl(distributionPath, bundleId) {
|
|
2606
|
-
const manifestPath =
|
|
3296
|
+
const manifestPath = path6.join(distributionPath, "manifests", `*.plist`);
|
|
2607
3297
|
return `itms-services://?action=download-manifest&url=file://${manifestPath}`;
|
|
2608
3298
|
}
|
|
2609
3299
|
/**
|
|
@@ -2612,7 +3302,7 @@ var AdHocUploader = class {
|
|
|
2612
3302
|
* @param distributionPath 分发目录
|
|
2613
3303
|
*/
|
|
2614
3304
|
async generateQRCode(url, distributionPath) {
|
|
2615
|
-
const qrCodePath =
|
|
3305
|
+
const qrCodePath = path6.join(distributionPath, "install-qrcode.png");
|
|
2616
3306
|
try {
|
|
2617
3307
|
logger.info(`QR code would be saved to: ${qrCodePath}`);
|
|
2618
3308
|
logger.info(`Install URL: ${url}`);
|
|
@@ -2639,8 +3329,8 @@ var AdHocUploader = class {
|
|
|
2639
3329
|
logger.newLine();
|
|
2640
3330
|
logger.info("\u{1F4E6} Distribution Files:");
|
|
2641
3331
|
logger.info(` Directory: ${distributionPath}`);
|
|
2642
|
-
logger.info(` IPA: ${
|
|
2643
|
-
logger.info(` Manifest: ${
|
|
3332
|
+
logger.info(` IPA: ${path6.join(distributionPath, "apps", "*.ipa")}`);
|
|
3333
|
+
logger.info(` Manifest: ${path6.join(distributionPath, "manifests", "*.plist")}`);
|
|
2644
3334
|
logger.info(` QR Code: ${qrCodePath}`);
|
|
2645
3335
|
logger.newLine();
|
|
2646
3336
|
logger.info("\u{1F517} Installation Link:");
|
|
@@ -2699,7 +3389,7 @@ var AdHocUploader = class {
|
|
|
2699
3389
|
|
|
2700
3390
|
// src/adapters/harmony_uploader.ts
|
|
2701
3391
|
import fs9 from "fs-extra";
|
|
2702
|
-
import
|
|
3392
|
+
import path7 from "path";
|
|
2703
3393
|
import axios6 from "axios";
|
|
2704
3394
|
import FormData5 from "form-data";
|
|
2705
3395
|
var HarmonyUploader = class _HarmonyUploader {
|
|
@@ -2729,7 +3419,7 @@ var HarmonyUploader = class _HarmonyUploader {
|
|
|
2729
3419
|
const clientSecret = harmonyConfig.auth.clientSecret;
|
|
2730
3420
|
const appId = harmonyConfig.appId;
|
|
2731
3421
|
const releaseType = harmonyConfig.releaseType || "draft";
|
|
2732
|
-
const suffix =
|
|
3422
|
+
const suffix = path7.extname(filePath).substring(1);
|
|
2733
3423
|
const retryConfig = config.retry;
|
|
2734
3424
|
logger.info("Starting HarmonyOS AppGallery upload...");
|
|
2735
3425
|
try {
|
|
@@ -2846,7 +3536,7 @@ var HarmonyUploader = class _HarmonyUploader {
|
|
|
2846
3536
|
* @param filePath 文件路径
|
|
2847
3537
|
*/
|
|
2848
3538
|
async updateAppFileInfo(token, appId, authCode, filePath) {
|
|
2849
|
-
const fileName =
|
|
3539
|
+
const fileName = path7.basename(filePath);
|
|
2850
3540
|
const body = {
|
|
2851
3541
|
fileType: 7,
|
|
2852
3542
|
// 鸿蒙包的 fileType(需要根据华为最新文档确认)
|
|
@@ -2869,7 +3559,7 @@ var HarmonyUploader = class _HarmonyUploader {
|
|
|
2869
3559
|
|
|
2870
3560
|
// src/adapters/oppo_uploader.ts
|
|
2871
3561
|
import fs10 from "fs-extra";
|
|
2872
|
-
import
|
|
3562
|
+
import path8 from "path";
|
|
2873
3563
|
import axios7 from "axios";
|
|
2874
3564
|
import FormData6 from "form-data";
|
|
2875
3565
|
import crypto4 from "crypto";
|
|
@@ -2910,7 +3600,7 @@ var OppoUploader = class _OppoUploader {
|
|
|
2910
3600
|
* 尝试从文件名推断包名
|
|
2911
3601
|
*/
|
|
2912
3602
|
inferPackageName(filePath) {
|
|
2913
|
-
const fileName =
|
|
3603
|
+
const fileName = path8.basename(filePath);
|
|
2914
3604
|
const match = fileName.match(/^([\w.]+?)(?:-(?:release|debug|unsigned))?\.apk$/i);
|
|
2915
3605
|
if (match && match[1].includes(".")) {
|
|
2916
3606
|
return match[1];
|
|
@@ -2928,110 +3618,47 @@ var OppoUploader = class _OppoUploader {
|
|
|
2928
3618
|
}
|
|
2929
3619
|
async upload(filePath, config, metadata) {
|
|
2930
3620
|
const oppoConfig = config.publish.oppo;
|
|
2931
|
-
const
|
|
2932
|
-
const clientSecret = oppoConfig.auth.clientSecret;
|
|
3621
|
+
const accessToken = await this.getAccessToken(oppoConfig.auth.clientId, oppoConfig.auth.clientSecret);
|
|
2933
3622
|
const packageName = oppoConfig.packageName || this.inferPackageName(filePath);
|
|
2934
|
-
if (!packageName) {
|
|
2935
|
-
return {
|
|
2936
|
-
platform: "oppo",
|
|
2937
|
-
success: false,
|
|
2938
|
-
message: "\u65E0\u6CD5\u83B7\u53D6\u5305\u540D (packageName)\uFF0C\u8BF7\u5728\u914D\u7F6E\u4E2D\u6307\u5B9A\u6216\u786E\u4FDD\u6587\u4EF6\u540D\u5305\u542B\u5305\u540D",
|
|
2939
|
-
consoleUrl: "https://open.oppomobile.com/new/ecology/app"
|
|
2940
|
-
};
|
|
2941
|
-
}
|
|
2942
|
-
const retryConfig = config.retry;
|
|
2943
3623
|
const consoleUrl = "https://open.oppomobile.com/new/ecology/app";
|
|
2944
|
-
if (!
|
|
2945
|
-
return {
|
|
2946
|
-
platform: "oppo",
|
|
2947
|
-
success: false,
|
|
2948
|
-
message: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}`,
|
|
2949
|
-
consoleUrl
|
|
2950
|
-
};
|
|
3624
|
+
if (!packageName) {
|
|
3625
|
+
return { platform: "oppo", success: false, message: "\u65E0\u6CD5\u83B7\u53D6\u5305\u540D", consoleUrl };
|
|
2951
3626
|
}
|
|
2952
|
-
|
|
2953
|
-
logger.info(`\u6587\u4EF6: ${filePath}`);
|
|
3627
|
+
const artifacts = typeof metadata.artifactPath === "object" ? metadata.artifactPath : { universal: filePath || (typeof metadata.artifactPath === "string" ? metadata.artifactPath : "") };
|
|
2954
3628
|
try {
|
|
2955
|
-
const retryStrategy = createUploadRetryStrategy(
|
|
2956
|
-
|
|
2957
|
-
const
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
(
|
|
2968
|
-
|
|
2969
|
-
);
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
throw uploadUrlResult.error || new Error("Failed to get upload URL");
|
|
2984
|
-
}
|
|
2985
|
-
const { uploadUrl, sign: uploadSign } = uploadUrlResult.data;
|
|
2986
|
-
logger.info("\u6B63\u5728\u4E0A\u4F20 APK \u6587\u4EF6...");
|
|
2987
|
-
const uploadFileResult = await retryStrategy.execute(
|
|
2988
|
-
() => this.uploadFile(uploadUrl, uploadSign, filePath),
|
|
2989
|
-
"OPPO file upload"
|
|
2990
|
-
);
|
|
2991
|
-
if (!uploadFileResult.success || !uploadFileResult.data) {
|
|
2992
|
-
throw uploadFileResult.error || new Error("File upload failed");
|
|
2993
|
-
}
|
|
2994
|
-
const rawUrl = uploadFileResult.data.url;
|
|
2995
|
-
const fileUrl = rawUrl.replace(/^http:\/\//i, "https://").replace("storedl1.nearme.com.cn", "storedl.nearme.com.cn");
|
|
2996
|
-
const md5 = uploadFileResult.data.md5;
|
|
2997
|
-
logger.info("\u6B63\u5728\u63D0\u4EA4\u7248\u672C\u4FE1\u606F...");
|
|
2998
|
-
const submitResult = await retryStrategy.execute(
|
|
2999
|
-
() => this.submitVersion({
|
|
3000
|
-
accessToken,
|
|
3001
|
-
clientSecret,
|
|
3002
|
-
packageName,
|
|
3003
|
-
appName: oppoConfig.appName || metadata.appName || onlineAppInfo.app_name || this.inferAppName(packageName),
|
|
3004
|
-
fileUrl,
|
|
3005
|
-
fileMd5: md5,
|
|
3006
|
-
versionName: metadata.version,
|
|
3007
|
-
versionCode: metadata.buildNumber,
|
|
3008
|
-
updateDesc: metadata.changelog || "App Update",
|
|
3009
|
-
onlineType: oppoConfig.onlineType,
|
|
3010
|
-
// 从配置读取
|
|
3011
|
-
onlineAppInfo
|
|
3012
|
-
// 传入线上信息
|
|
3013
|
-
}),
|
|
3014
|
-
"OPPO submit version"
|
|
3015
|
-
);
|
|
3016
|
-
if (!submitResult.success) {
|
|
3017
|
-
throw submitResult.error || new Error("Submit version failed");
|
|
3018
|
-
}
|
|
3629
|
+
const retryStrategy = createUploadRetryStrategy(config.retry);
|
|
3630
|
+
const onlineAppInfo = await this.getAppInfo(accessToken, oppoConfig.auth.clientSecret, packageName) || {};
|
|
3631
|
+
const uploadResults = [];
|
|
3632
|
+
const uploadPromises = Object.entries(artifacts).map(async ([abi, p]) => {
|
|
3633
|
+
if (!p || !fs10.existsSync(p)) throw new Error(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${p}`);
|
|
3634
|
+
logger.info(`[OPPO] \u6B63\u5728\u5904\u7406 ${abi} \u67B6\u6784\u5305...`);
|
|
3635
|
+
const { uploadUrl, sign } = await this.getUploadUrl(accessToken, oppoConfig.auth.clientSecret);
|
|
3636
|
+
const uploadFileResult = await this.uploadFile(uploadUrl, sign, p);
|
|
3637
|
+
const fileUrl = uploadFileResult.url.replace(/^http:\/\//i, "https://").replace("storedl1.nearme.com.cn", "storedl.nearme.com.cn");
|
|
3638
|
+
let cpu_code = 0;
|
|
3639
|
+
if (abi === "arm64-v8a") cpu_code = 3;
|
|
3640
|
+
else if (abi === "armeabi-v7a") cpu_code = 4;
|
|
3641
|
+
uploadResults.push({ url: fileUrl, md5: uploadFileResult.md5, cpu_code });
|
|
3642
|
+
});
|
|
3643
|
+
await Promise.all(uploadPromises);
|
|
3644
|
+
logger.info(`[OPPO] \u6B63\u5728\u63D0\u4EA4\u6D41\u6C34\u53F7\u7EC4: ${JSON.stringify(uploadResults)}`);
|
|
3645
|
+
await this.submitVersion({
|
|
3646
|
+
accessToken,
|
|
3647
|
+
clientSecret: oppoConfig.auth.clientSecret,
|
|
3648
|
+
packageName,
|
|
3649
|
+
appName: oppoConfig.appName || onlineAppInfo.app_name || metadata.appName || this.inferAppName(packageName),
|
|
3650
|
+
apkUrls: uploadResults,
|
|
3651
|
+
versionName: metadata.version,
|
|
3652
|
+
versionCode: metadata.buildNumber,
|
|
3653
|
+
updateDesc: metadata.changelog || "App Update",
|
|
3654
|
+
onlineType: oppoConfig.onlineType,
|
|
3655
|
+
onlineAppInfo
|
|
3656
|
+
});
|
|
3019
3657
|
logger.success("\u4E0A\u4F20\u5230 OPPO \u5E94\u7528\u5546\u5E97\u6210\u529F!");
|
|
3020
|
-
return {
|
|
3021
|
-
platform: "oppo",
|
|
3022
|
-
success: true,
|
|
3023
|
-
message: "\u4E0A\u4F20\u6210\u529F, \u8BF7\u524D\u5F80\u540E\u53F0\u786E\u8BA4\u5E76\u63D0\u4EA4\u5BA1\u6838",
|
|
3024
|
-
consoleUrl
|
|
3025
|
-
};
|
|
3658
|
+
return { platform: "oppo", success: true, message: "\u4E0A\u4F20\u6210\u529F, \u8BF7\u786E\u8BA4", consoleUrl };
|
|
3026
3659
|
} catch (error) {
|
|
3027
3660
|
logger.error(`\u4E0A\u4F20\u5230 OPPO \u5931\u8D25: ${error.message}`);
|
|
3028
|
-
return {
|
|
3029
|
-
platform: "oppo",
|
|
3030
|
-
success: false,
|
|
3031
|
-
message: this.formatError(error),
|
|
3032
|
-
error,
|
|
3033
|
-
consoleUrl
|
|
3034
|
-
};
|
|
3661
|
+
return { platform: "oppo", success: false, message: error.message, error, consoleUrl };
|
|
3035
3662
|
}
|
|
3036
3663
|
}
|
|
3037
3664
|
/**
|
|
@@ -3152,16 +3779,7 @@ var OppoUploader = class _OppoUploader {
|
|
|
3152
3779
|
const signParams = {
|
|
3153
3780
|
access_token: params.accessToken,
|
|
3154
3781
|
timestamp,
|
|
3155
|
-
|
|
3156
|
-
// cpu_code: 0=通用包, 3=arm64-v8a, 4=armeabi-v7a, 5=x86_64
|
|
3157
|
-
apk_url: JSON.stringify([
|
|
3158
|
-
{
|
|
3159
|
-
url: params.fileUrl,
|
|
3160
|
-
md5: params.fileMd5,
|
|
3161
|
-
cpu_code: 0
|
|
3162
|
-
// 通用包
|
|
3163
|
-
}
|
|
3164
|
-
]),
|
|
3782
|
+
apk_url: JSON.stringify(params.apkUrls),
|
|
3165
3783
|
version_name: params.versionName.split("+")[0],
|
|
3166
3784
|
// 仅保留版本名部分
|
|
3167
3785
|
version_code: vCode,
|
|
@@ -3263,7 +3881,7 @@ var OppoUploader = class _OppoUploader {
|
|
|
3263
3881
|
|
|
3264
3882
|
// src/adapters/vivo_uploader.ts
|
|
3265
3883
|
import fs11 from "fs-extra";
|
|
3266
|
-
import
|
|
3884
|
+
import path9 from "path";
|
|
3267
3885
|
import axios8 from "axios";
|
|
3268
3886
|
import FormData7 from "form-data";
|
|
3269
3887
|
import crypto5 from "crypto";
|
|
@@ -3271,26 +3889,14 @@ var VivoUploader = class _VivoUploader {
|
|
|
3271
3889
|
name = "vivo";
|
|
3272
3890
|
/** vivo 开放 API 基础地址 */
|
|
3273
3891
|
static API_BASE = "https://developer-api.vivo.com.cn/router/rest";
|
|
3274
|
-
/**
|
|
3275
|
-
* vivo API 签名生成
|
|
3276
|
-
* 规则: 所有参数按 ASCII 码排序后拼接为 key=val&... 字符串,
|
|
3277
|
-
* 以 accessSecret 为密钥进行 HMAC-SHA256 加密,结果转为小写 16 进制字符串
|
|
3278
|
-
*/
|
|
3279
3892
|
generateSign(params, accessSecret) {
|
|
3280
3893
|
const sorted = Object.keys(params).sort().filter((key) => params[key] !== void 0 && params[key] !== null).map((key) => `${key}=${params[key]}`).join("&");
|
|
3281
|
-
|
|
3282
|
-
logger.info(`[VIVO DEBUG] \u5F85\u7B7E\u540D\u4E32: ${sorted}
|
|
3283
|
-
-> \u7B7E\u540D\u7ED3\u679C: ${sign}`);
|
|
3284
|
-
return sign;
|
|
3894
|
+
return crypto5.createHmac("sha256", accessSecret).update(sorted).digest("hex");
|
|
3285
3895
|
}
|
|
3286
3896
|
validateConfig(config) {
|
|
3287
3897
|
if (!config?.publish?.vivo?.enable) return false;
|
|
3288
3898
|
const vivo = config.publish.vivo;
|
|
3289
|
-
|
|
3290
|
-
logger.error("vivo config missing: accessKey or accessSecret");
|
|
3291
|
-
return false;
|
|
3292
|
-
}
|
|
3293
|
-
return true;
|
|
3899
|
+
return !!(vivo.auth?.accessKey && vivo.auth?.accessSecret);
|
|
3294
3900
|
}
|
|
3295
3901
|
canHandle(filePath) {
|
|
3296
3902
|
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
@@ -3300,67 +3906,51 @@ var VivoUploader = class _VivoUploader {
|
|
|
3300
3906
|
const vivoConfig = config.publish.vivo;
|
|
3301
3907
|
const accessKey = vivoConfig.auth.accessKey;
|
|
3302
3908
|
const accessSecret = vivoConfig.auth.accessSecret;
|
|
3303
|
-
const retryConfig = config.retry;
|
|
3304
3909
|
const consoleUrl = "https://dev.vivo.com.cn/manage/app";
|
|
3305
|
-
|
|
3306
|
-
return {
|
|
3307
|
-
platform: "vivo",
|
|
3308
|
-
success: false,
|
|
3309
|
-
message: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}`,
|
|
3310
|
-
consoleUrl
|
|
3311
|
-
};
|
|
3312
|
-
}
|
|
3313
|
-
const fileSize = fs11.statSync(filePath).size;
|
|
3314
|
-
logger.info("\u5F00\u59CB\u4E0A\u4F20\u5230 vivo \u5E94\u7528\u5546\u5E97...");
|
|
3315
|
-
logger.info(`\u6587\u4EF6: ${filePath} (${(fileSize / 1024 / 1024).toFixed(2)} MB)`);
|
|
3910
|
+
const artifacts = typeof metadata.artifactPath === "object" ? metadata.artifactPath : { universal: filePath || (typeof metadata.artifactPath === "string" ? metadata.artifactPath : "") };
|
|
3316
3911
|
try {
|
|
3317
|
-
const
|
|
3318
|
-
const
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3912
|
+
const uploadResults = {};
|
|
3913
|
+
const uploadPromises = Object.entries(artifacts).map(async ([abi, p]) => {
|
|
3914
|
+
if (!p || !fs11.existsSync(p)) throw new Error(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${p}`);
|
|
3915
|
+
const md5 = await this.calculateFileMD5(p);
|
|
3916
|
+
logger.info(`[vivo] \u6B63\u5728\u5904\u7406 ${abi} \u67B6\u6784\u5305: ${path9.basename(p)}`);
|
|
3917
|
+
const serialnumber = await this.uploadApk(accessKey, accessSecret, p, vivoConfig.packageName || "", md5);
|
|
3918
|
+
const pkgInfo = await parsePackageInfo(p, { androidSdkPath: config.globalSettings?.androidSdkPath });
|
|
3919
|
+
const nativeCodes = pkgInfo?.nativeCode || [];
|
|
3920
|
+
const has64Bit = nativeCodes.includes("arm64-v8a") || nativeCodes.includes("x86_64") || abi === "arm64-v8a";
|
|
3921
|
+
const has32Bit = nativeCodes.includes("armeabi-v7a") || nativeCodes.includes("armeabi") || abi === "armeabi-v7a";
|
|
3922
|
+
uploadResults[abi] = { serialnumber, md5, is64Bit: has64Bit && !has32Bit };
|
|
3923
|
+
});
|
|
3924
|
+
await Promise.all(uploadPromises);
|
|
3925
|
+
const arm64Result = uploadResults["arm64-v8a"] || Object.values(uploadResults).find((r) => r.is64Bit);
|
|
3926
|
+
const arm32Result = uploadResults["armeabi-v7a"] || Object.values(uploadResults).find((r) => !r.is64Bit);
|
|
3927
|
+
const submitData = {
|
|
3928
|
+
packageName: vivoConfig.packageName,
|
|
3929
|
+
versionCode: parseInt(metadata.buildNumber) || 1,
|
|
3930
|
+
updateDesc: metadata.changelog || "App Update"
|
|
3931
|
+
};
|
|
3932
|
+
if (arm64Result) {
|
|
3933
|
+
submitData.serialnumber = arm64Result.serialnumber;
|
|
3934
|
+
submitData.fileMd5 = arm64Result.md5;
|
|
3327
3935
|
}
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
() => this.submitVersion(accessKey, accessSecret, {
|
|
3332
|
-
serialnumber,
|
|
3333
|
-
packageName: vivoConfig.packageName,
|
|
3334
|
-
versionCode: parseInt(metadata.buildNumber) || 1,
|
|
3335
|
-
fileMd5,
|
|
3336
|
-
updateDesc: metadata.changelog || "App Update"
|
|
3337
|
-
}),
|
|
3338
|
-
"vivo submit version"
|
|
3339
|
-
);
|
|
3340
|
-
if (!submitResult.success) {
|
|
3341
|
-
throw submitResult.error || new Error("Submit version failed");
|
|
3936
|
+
if (arm32Result) {
|
|
3937
|
+
submitData.apk32 = arm32Result.serialnumber;
|
|
3938
|
+
submitData.md532 = arm32Result.md5;
|
|
3342
3939
|
}
|
|
3940
|
+
if (!submitData.serialnumber && !submitData.apk32) {
|
|
3941
|
+
const single = Object.values(uploadResults)[0];
|
|
3942
|
+
submitData.serialnumber = single.serialnumber;
|
|
3943
|
+
submitData.fileMd5 = single.md5;
|
|
3944
|
+
}
|
|
3945
|
+
logger.info(`[vivo] \u6B63\u5728\u63D0\u4EA4\u5BA1\u6838\u7533\u62A5: ${JSON.stringify(submitData)}`);
|
|
3946
|
+
await this.submitVersion(accessKey, accessSecret, submitData);
|
|
3343
3947
|
logger.success("\u4E0A\u4F20\u5230 vivo \u5E94\u7528\u5546\u5E97\u6210\u529F!");
|
|
3344
|
-
return {
|
|
3345
|
-
platform: "vivo",
|
|
3346
|
-
success: true,
|
|
3347
|
-
message: "\u4E0A\u4F20\u6210\u529F, \u8BF7\u524D\u5F80\u540E\u53F0\u786E\u8BA4\u5E76\u63D0\u4EA4\u5BA1\u6838",
|
|
3348
|
-
consoleUrl
|
|
3349
|
-
};
|
|
3948
|
+
return { platform: "vivo", success: true, message: "\u4E0A\u4F20\u5E76\u63D0\u4EA4\u5BA1\u6838\u6210\u529F", consoleUrl };
|
|
3350
3949
|
} catch (error) {
|
|
3351
3950
|
logger.error(`\u4E0A\u4F20\u5230 vivo \u5931\u8D25: ${error.message}`);
|
|
3352
|
-
return {
|
|
3353
|
-
platform: "vivo",
|
|
3354
|
-
success: false,
|
|
3355
|
-
message: this.formatError(error),
|
|
3356
|
-
error,
|
|
3357
|
-
consoleUrl
|
|
3358
|
-
};
|
|
3951
|
+
return { platform: "vivo", success: false, message: error.message, error, consoleUrl };
|
|
3359
3952
|
}
|
|
3360
3953
|
}
|
|
3361
|
-
/**
|
|
3362
|
-
* 计算文件 MD5
|
|
3363
|
-
*/
|
|
3364
3954
|
calculateFileMD5(filePath) {
|
|
3365
3955
|
return new Promise((resolve, reject) => {
|
|
3366
3956
|
const hash = crypto5.createHash("md5");
|
|
@@ -3370,14 +3960,9 @@ var VivoUploader = class _VivoUploader {
|
|
|
3370
3960
|
stream.on("error", reject);
|
|
3371
3961
|
});
|
|
3372
3962
|
}
|
|
3373
|
-
/**
|
|
3374
|
-
* 上传 APK 文件并获取流水号
|
|
3375
|
-
* 将公共参数与业务参数放入一个 multipart 请求中直接上传至 API_BASE
|
|
3376
|
-
*/
|
|
3377
3963
|
async uploadApk(accessKey, accessSecret, filePath, packageName, fileMd5) {
|
|
3378
3964
|
const timestamp = String(Date.now());
|
|
3379
|
-
const
|
|
3380
|
-
// 公共参数
|
|
3965
|
+
const params = {
|
|
3381
3966
|
access_key: accessKey,
|
|
3382
3967
|
timestamp,
|
|
3383
3968
|
format: "json",
|
|
@@ -3385,41 +3970,19 @@ var VivoUploader = class _VivoUploader {
|
|
|
3385
3970
|
sign_method: "hmac",
|
|
3386
3971
|
target_app_key: "developer",
|
|
3387
3972
|
method: "app.upload.apk.app",
|
|
3388
|
-
// 业务参数
|
|
3389
3973
|
packageName,
|
|
3390
3974
|
fileMd5
|
|
3391
3975
|
};
|
|
3392
|
-
const sign = this.generateSign(
|
|
3976
|
+
const sign = this.generateSign(params, accessSecret);
|
|
3393
3977
|
const form = new FormData7();
|
|
3394
|
-
|
|
3395
|
-
form.append(key, value);
|
|
3396
|
-
}
|
|
3978
|
+
Object.entries(params).forEach(([k, v]) => form.append(k, v));
|
|
3397
3979
|
form.append("sign", sign);
|
|
3398
|
-
form.append("file", fs11.createReadStream(filePath), {
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
logger.info(
|
|
3403
|
-
`[VIVO DEBUG] \u5F00\u59CB\u4E0A\u4F20 APK (API\u8BF7\u6C42) \u53C2\u6570(\u4E0D\u542B\u6587\u4EF6): ${JSON.stringify({ ...paramsForSign, sign })}`
|
|
3404
|
-
);
|
|
3405
|
-
const res = await axios8.post(_VivoUploader.API_BASE, form, {
|
|
3406
|
-
headers: form.getHeaders(),
|
|
3407
|
-
maxContentLength: Infinity,
|
|
3408
|
-
maxBodyLength: Infinity,
|
|
3409
|
-
timeout: 3e5
|
|
3410
|
-
});
|
|
3411
|
-
logger.info(`[VIVO DEBUG] \u4E0A\u4F20\u54CD\u5E94: ${JSON.stringify(res.data)}`);
|
|
3412
|
-
if (res.data.code !== 0) {
|
|
3413
|
-
throw new Error(`\u6587\u4EF6\u4E0A\u4F20\u5931\u8D25: ${res.data.msg || JSON.stringify(res.data)}`);
|
|
3414
|
-
}
|
|
3415
|
-
const serialnumber = res.data.data?.serialnumber || res.data.data;
|
|
3416
|
-
logger.info(`APK \u6587\u4EF6\u4E0A\u4F20\u5B8C\u6210\uFF0C\u6D41\u6C34\u53F7: ${serialnumber}`);
|
|
3417
|
-
return serialnumber;
|
|
3980
|
+
form.append("file", fs11.createReadStream(filePath), { filename: path9.basename(filePath) });
|
|
3981
|
+
const res = await axios8.post(_VivoUploader.API_BASE, form, { headers: form.getHeaders(), timeout: 3e5 });
|
|
3982
|
+
if (res.data.code !== 0) throw new Error(`\u4E0A\u4F20\u5305\u5931\u8D25: ${res.data.msg}`);
|
|
3983
|
+
return res.data.data?.serialnumber || res.data.data;
|
|
3418
3984
|
}
|
|
3419
|
-
|
|
3420
|
-
* 提交版本更新信息
|
|
3421
|
-
*/
|
|
3422
|
-
async submitVersion(accessKey, accessSecret, params) {
|
|
3985
|
+
async submitVersion(accessKey, accessSecret, data) {
|
|
3423
3986
|
const timestamp = String(Date.now());
|
|
3424
3987
|
const reqParams = {
|
|
3425
3988
|
access_key: accessKey,
|
|
@@ -3429,51 +3992,32 @@ var VivoUploader = class _VivoUploader {
|
|
|
3429
3992
|
sign_method: "hmac",
|
|
3430
3993
|
target_app_key: "developer",
|
|
3431
3994
|
method: "app.sync.update.app",
|
|
3432
|
-
packageName:
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
apk: params.serialnumber,
|
|
3437
|
-
// 文档中的 apk 字段即为上传接口返回的 serialnumber
|
|
3438
|
-
fileMd5: params.fileMd5,
|
|
3439
|
-
// 文档必填
|
|
3995
|
+
packageName: data.packageName || "",
|
|
3996
|
+
versionCode: String(data.versionCode),
|
|
3997
|
+
apk: data.serialnumber || "",
|
|
3998
|
+
fileMd5: data.fileMd5 || "",
|
|
3440
3999
|
onlineType: "1",
|
|
3441
|
-
// 1:立即上架 2:定时上架 (必填)
|
|
3442
4000
|
compatibleDevice: "1",
|
|
3443
|
-
|
|
3444
|
-
updateDesc: params.updateDesc
|
|
4001
|
+
updateDesc: data.updateDesc
|
|
3445
4002
|
};
|
|
4003
|
+
if (data.apk32) {
|
|
4004
|
+
reqParams.apk32 = data.apk32;
|
|
4005
|
+
if (data.md532) reqParams.md532 = data.md532;
|
|
4006
|
+
}
|
|
3446
4007
|
reqParams.sign = this.generateSign(reqParams, accessSecret);
|
|
3447
4008
|
const formData = new URLSearchParams();
|
|
3448
|
-
|
|
3449
|
-
formData.append(key, value);
|
|
3450
|
-
}
|
|
4009
|
+
Object.entries(reqParams).forEach(([k, v]) => formData.append(k, v));
|
|
3451
4010
|
const res = await axios8.post(_VivoUploader.API_BASE, formData.toString(), {
|
|
3452
|
-
headers: {
|
|
3453
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
3454
|
-
},
|
|
4011
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
3455
4012
|
timeout: 3e4
|
|
3456
4013
|
});
|
|
3457
|
-
if (res.data.code !== 0) {
|
|
3458
|
-
throw new Error(`\u63D0\u4EA4\u7248\u672C\u5931\u8D25: ${res.data.msg || JSON.stringify(res.data)}`);
|
|
3459
|
-
}
|
|
3460
|
-
logger.info("\u7248\u672C\u4FE1\u606F\u5DF2\u63D0\u4EA4");
|
|
3461
|
-
}
|
|
3462
|
-
/**
|
|
3463
|
-
* 格式化错误信息
|
|
3464
|
-
*/
|
|
3465
|
-
formatError(error) {
|
|
3466
|
-
if (error.response?.data) {
|
|
3467
|
-
const data = error.response.data;
|
|
3468
|
-
return data.msg || data.message || error.message;
|
|
3469
|
-
}
|
|
3470
|
-
return error.message;
|
|
4014
|
+
if (res.data.code !== 0) throw new Error(`\u63D0\u4EA4\u5931\u8D25: ${res.data.msg}`);
|
|
3471
4015
|
}
|
|
3472
4016
|
};
|
|
3473
4017
|
|
|
3474
4018
|
// src/adapters/tencent_uploader.ts
|
|
3475
4019
|
import fs12 from "fs-extra";
|
|
3476
|
-
import
|
|
4020
|
+
import path10 from "path";
|
|
3477
4021
|
import axios9 from "axios";
|
|
3478
4022
|
import crypto6 from "crypto";
|
|
3479
4023
|
var TencentUploader = class _TencentUploader {
|
|
@@ -3505,54 +4049,71 @@ var TencentUploader = class _TencentUploader {
|
|
|
3505
4049
|
async upload(filePath, config, metadata) {
|
|
3506
4050
|
const tencentConfig = config.publish.tencent;
|
|
3507
4051
|
const auth = tencentConfig.auth;
|
|
3508
|
-
logger.info(`Tencent \u8FD0\u884C\u65F6\u914D\u7F6E: ${JSON.stringify(tencentConfig)}`);
|
|
3509
4052
|
const consoleUrl = "https://app.open.qq.com";
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
}
|
|
4053
|
+
const pkgName = tencentConfig.packageName || "";
|
|
4054
|
+
const artifacts = typeof metadata.artifactPath === "object" ? metadata.artifactPath : { universal: filePath || (typeof metadata.artifactPath === "string" ? metadata.artifactPath : "") };
|
|
3513
4055
|
try {
|
|
3514
|
-
const
|
|
3515
|
-
const
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
4056
|
+
const uploadResults = {};
|
|
4057
|
+
const uploadPromises = Object.entries(artifacts).map(async ([abi, p]) => {
|
|
4058
|
+
if (!p || !fs12.existsSync(p)) throw new Error(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${p}`);
|
|
4059
|
+
const stats = fs12.statSync(p);
|
|
4060
|
+
const fileName = path10.basename(p);
|
|
4061
|
+
logger.info(`[Tencent] \u6B63\u5728\u5904\u7406 ${abi} \u67B6\u6784\u5305: ${fileName}`);
|
|
4062
|
+
const uploadInfo = await this.getFileUploadInfo(auth, stats.size, fileName, pkgName);
|
|
4063
|
+
await this.uploadToCos(uploadInfo.pre_sign_url, p, stats.size);
|
|
4064
|
+
const pkgInfo = await parsePackageInfo(p, {
|
|
4065
|
+
androidSdkPath: config.globalSettings?.androidSdkPath
|
|
4066
|
+
});
|
|
4067
|
+
const nativeCodes = pkgInfo?.nativeCode || [];
|
|
4068
|
+
const has64Bit = nativeCodes.includes("arm64-v8a") || nativeCodes.includes("x86_64") || abi === "arm64-v8a";
|
|
4069
|
+
const has32Bit = nativeCodes.includes("armeabi-v7a") || nativeCodes.includes("armeabi") || nativeCodes.includes("x86") || abi === "armeabi-v7a";
|
|
4070
|
+
uploadResults[abi] = {
|
|
4071
|
+
serial_number: uploadInfo.serial_number,
|
|
4072
|
+
is64Bit: has64Bit && !has32Bit,
|
|
4073
|
+
md5: pkgInfo?.md5 || ""
|
|
4074
|
+
};
|
|
4075
|
+
});
|
|
4076
|
+
await Promise.all(uploadPromises);
|
|
4077
|
+
logger.info("[Tencent] \u6B63\u5728\u83B7\u53D6\u7EBF\u4E0A\u5E94\u7528\u8BE6\u60C5...");
|
|
4078
|
+
const anyPkgPath = Object.values(artifacts)[0];
|
|
4079
|
+
const samplePkgInfo = await parsePackageInfo(anyPkgPath, {
|
|
3524
4080
|
androidSdkPath: config.globalSettings?.androidSdkPath
|
|
3525
4081
|
});
|
|
3526
|
-
const nativeCodes = pkgInfo?.nativeCode || [];
|
|
3527
|
-
const has64Bit = nativeCodes.includes("arm64-v8a") || nativeCodes.includes("x86_64");
|
|
3528
|
-
const has32Bit = nativeCodes.includes("armeabi-v7a") || nativeCodes.includes("armeabi") || nativeCodes.includes("x86");
|
|
3529
|
-
const is64Bit = has64Bit && !has32Bit;
|
|
3530
|
-
const md5 = pkgInfo?.md5;
|
|
3531
4082
|
const onlineDetail = await this.queryAppDetail(
|
|
3532
4083
|
auth,
|
|
3533
|
-
|
|
3534
|
-
);
|
|
3535
|
-
logger.info(`\u83B7\u53D6\u7EBF\u4E0A\u8BE6\u60C5\u6210\u529F: ${onlineDetail.app_name || "\u672A\u547D\u540D"}`);
|
|
3536
|
-
logger.info("\u6B63\u5728\u63D0\u4EA4\u6D41\u6C34\u53F7\u5B8C\u6210\u5BA1\u6838\u7533\u62A5...");
|
|
3537
|
-
const submitResult = await this.submitModifyAppInfo(
|
|
3538
|
-
auth,
|
|
3539
|
-
{
|
|
3540
|
-
...onlineDetail,
|
|
3541
|
-
// 以前序详情为蓝本
|
|
3542
|
-
pkg_name: tencentConfig.packageName || pkgInfo?.packageName || onlineDetail.pkg_name || "",
|
|
3543
|
-
serial_number: uploadInfo.serial_number,
|
|
3544
|
-
update_desc: metadata.changelog || "\u7248\u672C\u66F4\u65B0",
|
|
3545
|
-
is64Bit,
|
|
3546
|
-
md5
|
|
3547
|
-
},
|
|
3548
|
-
{ timeout: tencentConfig.timeout || 6e4 }
|
|
4084
|
+
pkgName || samplePkgInfo?.packageName || ""
|
|
3549
4085
|
);
|
|
3550
|
-
|
|
4086
|
+
const submitData = {
|
|
4087
|
+
...onlineDetail,
|
|
4088
|
+
pkg_name: pkgName || samplePkgInfo?.packageName || onlineDetail.pkg_name || "",
|
|
4089
|
+
update_desc: metadata.changelog || "\u7248\u672C\u66F4\u65B0"
|
|
4090
|
+
};
|
|
4091
|
+
const arm64Result = uploadResults["arm64-v8a"] || Object.values(uploadResults).find((r) => r.is64Bit);
|
|
4092
|
+
const arm32Result = uploadResults["armeabi-v7a"] || Object.values(uploadResults).find((r) => !r.is64Bit);
|
|
4093
|
+
if (arm64Result) {
|
|
4094
|
+
submitData.apk64_serial_number = arm64Result.serial_number;
|
|
4095
|
+
submitData.apk64_md5 = arm64Result.md5;
|
|
4096
|
+
}
|
|
4097
|
+
if (arm32Result) {
|
|
4098
|
+
submitData.apk32_serial_number = arm32Result.serial_number;
|
|
4099
|
+
submitData.apk32_md5 = arm32Result.md5;
|
|
4100
|
+
}
|
|
4101
|
+
if (Object.keys(uploadResults).length === 1 && !submitData.apk64_serial_number && !submitData.apk32_serial_number) {
|
|
4102
|
+
const single = Object.values(uploadResults)[0];
|
|
4103
|
+
if (single.is64Bit) {
|
|
4104
|
+
submitData.apk64_serial_number = single.serial_number;
|
|
4105
|
+
submitData.apk64_md5 = single.md5;
|
|
4106
|
+
} else {
|
|
4107
|
+
submitData.apk32_serial_number = single.serial_number;
|
|
4108
|
+
submitData.apk32_md5 = single.md5;
|
|
4109
|
+
}
|
|
4110
|
+
}
|
|
4111
|
+
logger.info("[Tencent] \u6B63\u5728\u63D0\u4EA4\u5BA1\u6838\u7533\u62A5...");
|
|
4112
|
+
await this.submitModifyAppInfo(auth, submitData, { timeout: tencentConfig.timeout || 6e4 });
|
|
3551
4113
|
logger.success("\u817E\u8BAF\u5E94\u7528\u5B9D\u4E0A\u4F20\u5E76\u63D0\u4EA4\u6210\u529F!");
|
|
3552
4114
|
return { platform: "tencent", success: true, message: "\u4E0A\u4F20\u5E76\u63D0\u4EA4\u5BA1\u6838\u6210\u529F", consoleUrl };
|
|
3553
4115
|
} catch (error) {
|
|
3554
|
-
logger.error(`\u817E\u8BAF\u4E0A\u4F20\u5931\u8D25: ${
|
|
3555
|
-
logger.error(`\u817E\u8BAF\u4E0A\u4F20\u5931\u8D25: ${error.stack}`);
|
|
4116
|
+
logger.error(`\u817E\u8BAF\u4E0A\u4F20\u5931\u8D25: ${error.message}`);
|
|
3556
4117
|
return { platform: "tencent", success: false, message: error.message, error, consoleUrl };
|
|
3557
4118
|
}
|
|
3558
4119
|
}
|
|
@@ -3666,16 +4227,22 @@ var TencentUploader = class _TencentUploader {
|
|
|
3666
4227
|
signParams[key] = String(data[key]);
|
|
3667
4228
|
}
|
|
3668
4229
|
});
|
|
3669
|
-
if (data.
|
|
3670
|
-
signParams.apk64_file_serial_number = String(data.
|
|
3671
|
-
if (data.
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
signParams.
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
4230
|
+
if (data.apk64_serial_number) {
|
|
4231
|
+
signParams.apk64_file_serial_number = String(data.apk64_serial_number);
|
|
4232
|
+
if (data.apk64_md5) signParams.apk64_file_md5 = data.apk64_md5;
|
|
4233
|
+
}
|
|
4234
|
+
if (data.apk32_serial_number) {
|
|
4235
|
+
signParams.apk32_file_serial_number = String(data.apk32_serial_number);
|
|
4236
|
+
if (data.apk32_md5) signParams.apk32_file_md5 = data.apk32_md5;
|
|
4237
|
+
}
|
|
4238
|
+
if (!data.apk64_serial_number && !data.apk32_serial_number && data.serial_number) {
|
|
4239
|
+
if (data.is64Bit) {
|
|
4240
|
+
signParams.apk64_file_serial_number = String(data.serial_number || "");
|
|
4241
|
+
if (data.md5) signParams.apk64_file_md5 = data.md5;
|
|
4242
|
+
} else {
|
|
4243
|
+
signParams.apk32_file_serial_number = String(data.serial_number || "");
|
|
4244
|
+
if (data.md5) signParams.apk32_file_md5 = data.md5;
|
|
4245
|
+
}
|
|
3679
4246
|
}
|
|
3680
4247
|
const sign = this.generateSign(signParams, accessSecret);
|
|
3681
4248
|
const formParams = {
|
|
@@ -3694,7 +4261,227 @@ var TencentUploader = class _TencentUploader {
|
|
|
3694
4261
|
}
|
|
3695
4262
|
};
|
|
3696
4263
|
|
|
4264
|
+
// src/adapters/google_play_uploader.ts
|
|
4265
|
+
import fs13 from "fs-extra";
|
|
4266
|
+
import path11 from "path";
|
|
4267
|
+
import axios10 from "axios";
|
|
4268
|
+
import { GoogleAuth } from "google-auth-library";
|
|
4269
|
+
var ANDROID_PUBLISHER_SCOPE = "https://www.googleapis.com/auth/androidpublisher";
|
|
4270
|
+
var PUBLISH_API_ROOT = "https://androidpublisher.googleapis.com/androidpublisher/v3";
|
|
4271
|
+
var UPLOAD_API_ROOT = "https://androidpublisher.googleapis.com/upload/androidpublisher/v3";
|
|
4272
|
+
var GOOGLE_PLAY_CONSOLE_URL = "https://play.google.com/console/developers";
|
|
4273
|
+
var GOOGLE_PLAY_INTERNAL_API_TRACK = "internal";
|
|
4274
|
+
var UPLOAD_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
4275
|
+
var GooglePlayUploader = class {
|
|
4276
|
+
name = "Google Play";
|
|
4277
|
+
httpClient;
|
|
4278
|
+
getAccessToken;
|
|
4279
|
+
constructor(dependencies = {}) {
|
|
4280
|
+
this.httpClient = dependencies.httpClient || axios10;
|
|
4281
|
+
this.getAccessToken = dependencies.getAccessToken || this.createAccessToken.bind(this);
|
|
4282
|
+
}
|
|
4283
|
+
validateConfig(config) {
|
|
4284
|
+
const googleConfig = config?.publish?.google_play;
|
|
4285
|
+
if (!googleConfig?.enable) return false;
|
|
4286
|
+
if (!googleConfig.packageName || typeof googleConfig.packageName !== "string") {
|
|
4287
|
+
logger.error("Google Play \u914D\u7F6E\u7F3A\u5C11 packageName");
|
|
4288
|
+
return false;
|
|
4289
|
+
}
|
|
4290
|
+
const track = googleConfig.track || "internal";
|
|
4291
|
+
if (track !== "internal") {
|
|
4292
|
+
logger.error("Google Play \u9996\u7248\u4EC5\u652F\u6301 internal \u8F68\u9053");
|
|
4293
|
+
return false;
|
|
4294
|
+
}
|
|
4295
|
+
const auth = googleConfig.auth;
|
|
4296
|
+
const hasCredentialContent = typeof auth?.serviceAccountJsonContent === "string" && auth.serviceAccountJsonContent.trim().length > 0;
|
|
4297
|
+
const credentialPath = auth?.serviceAccountJsonPath;
|
|
4298
|
+
const hasCredentialPath = typeof credentialPath === "string" && credentialPath.trim().length > 0 && fs13.existsSync(path11.resolve(credentialPath));
|
|
4299
|
+
if (!hasCredentialContent && !hasCredentialPath) {
|
|
4300
|
+
logger.error("Google Play \u914D\u7F6E\u7F3A\u5C11\u6709\u6548\u7684\u670D\u52A1\u8D26\u53F7 JSON \u6587\u4EF6\u6216\u5185\u5BB9");
|
|
4301
|
+
return false;
|
|
4302
|
+
}
|
|
4303
|
+
return true;
|
|
4304
|
+
}
|
|
4305
|
+
canHandle(filePath) {
|
|
4306
|
+
return path11.extname(filePath).toLowerCase() === ".aab";
|
|
4307
|
+
}
|
|
4308
|
+
async upload(filePath, config, _metadata) {
|
|
4309
|
+
if (!this.validateConfig(config)) {
|
|
4310
|
+
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");
|
|
4311
|
+
}
|
|
4312
|
+
if (!this.canHandle(filePath)) {
|
|
4313
|
+
return this.failure("Google Play \u4EC5\u63A5\u53D7 Android App Bundle\uFF08.aab\uFF09");
|
|
4314
|
+
}
|
|
4315
|
+
if (!fs13.existsSync(filePath)) {
|
|
4316
|
+
return this.failure(`\u672A\u627E\u5230\u5F85\u4E0A\u4F20\u7684 AAB\uFF1A${filePath}`);
|
|
4317
|
+
}
|
|
4318
|
+
const googleConfig = config.publish.google_play;
|
|
4319
|
+
const packageName = googleConfig.packageName.trim();
|
|
4320
|
+
const encodedPackageName = encodeURIComponent(packageName);
|
|
4321
|
+
let editId;
|
|
4322
|
+
let accessToken;
|
|
4323
|
+
let committed = false;
|
|
4324
|
+
let stage = "create_edit";
|
|
4325
|
+
logger.info(`\u51C6\u5907\u5C06 ${path11.basename(filePath)} \u4E0A\u4F20\u5230 Google Play internal \u8349\u7A3F`);
|
|
4326
|
+
try {
|
|
4327
|
+
accessToken = await this.getAccessToken(googleConfig.auth);
|
|
4328
|
+
const requestConfig = {
|
|
4329
|
+
headers: { Authorization: `Bearer ${accessToken}` }
|
|
4330
|
+
};
|
|
4331
|
+
const editResponse = await this.httpClient.post(
|
|
4332
|
+
`${PUBLISH_API_ROOT}/applications/${encodedPackageName}/edits`,
|
|
4333
|
+
{},
|
|
4334
|
+
requestConfig
|
|
4335
|
+
);
|
|
4336
|
+
editId = editResponse.data.id;
|
|
4337
|
+
if (!editId) {
|
|
4338
|
+
throw new Error("Google Play \u672A\u8FD4\u56DE editId");
|
|
4339
|
+
}
|
|
4340
|
+
stage = "upload_bundle";
|
|
4341
|
+
const encodedEditId = encodeURIComponent(editId);
|
|
4342
|
+
const bundleResponse = await this.httpClient.post(
|
|
4343
|
+
`${UPLOAD_API_ROOT}/applications/${encodedPackageName}/edits/${encodedEditId}/bundles?uploadType=media`,
|
|
4344
|
+
fs13.createReadStream(filePath),
|
|
4345
|
+
{
|
|
4346
|
+
timeout: UPLOAD_TIMEOUT_MS,
|
|
4347
|
+
maxBodyLength: Infinity,
|
|
4348
|
+
maxContentLength: Infinity,
|
|
4349
|
+
headers: {
|
|
4350
|
+
Authorization: `Bearer ${accessToken}`,
|
|
4351
|
+
"Content-Type": "application/octet-stream",
|
|
4352
|
+
"Content-Length": fs13.statSync(filePath).size
|
|
4353
|
+
}
|
|
4354
|
+
}
|
|
4355
|
+
);
|
|
4356
|
+
const versionCode = bundleResponse.data.versionCode;
|
|
4357
|
+
if (!Number.isInteger(versionCode)) {
|
|
4358
|
+
throw new Error("Google Play \u4E0A\u4F20\u6210\u529F\u4F46\u672A\u8FD4\u56DE\u6709\u6548\u7684 versionCode");
|
|
4359
|
+
}
|
|
4360
|
+
stage = "update_internal_track";
|
|
4361
|
+
await this.httpClient.put(
|
|
4362
|
+
`${PUBLISH_API_ROOT}/applications/${encodedPackageName}/edits/${encodedEditId}/tracks/${GOOGLE_PLAY_INTERNAL_API_TRACK}`,
|
|
4363
|
+
{
|
|
4364
|
+
track: GOOGLE_PLAY_INTERNAL_API_TRACK,
|
|
4365
|
+
releases: [
|
|
4366
|
+
{
|
|
4367
|
+
versionCodes: [String(versionCode)],
|
|
4368
|
+
status: "draft"
|
|
4369
|
+
}
|
|
4370
|
+
]
|
|
4371
|
+
},
|
|
4372
|
+
requestConfig
|
|
4373
|
+
);
|
|
4374
|
+
stage = "validate_edit";
|
|
4375
|
+
await this.httpClient.post(
|
|
4376
|
+
`${PUBLISH_API_ROOT}/applications/${encodedPackageName}/edits/${encodedEditId}:validate`,
|
|
4377
|
+
{},
|
|
4378
|
+
requestConfig
|
|
4379
|
+
);
|
|
4380
|
+
stage = "commit_edit";
|
|
4381
|
+
await this.httpClient.post(
|
|
4382
|
+
`${PUBLISH_API_ROOT}/applications/${encodedPackageName}/edits/${encodedEditId}:commit`,
|
|
4383
|
+
{},
|
|
4384
|
+
requestConfig
|
|
4385
|
+
);
|
|
4386
|
+
committed = true;
|
|
4387
|
+
logger.success(`Google Play internal \u8349\u7A3F\u5DF2\u521B\u5EFA\uFF08versionCode ${versionCode}\uFF09`);
|
|
4388
|
+
return {
|
|
4389
|
+
platform: "google_play",
|
|
4390
|
+
success: true,
|
|
4391
|
+
message: `AAB \u5DF2\u4E0A\u4F20\u5230 Google Play internal \u8349\u7A3F\uFF08versionCode ${versionCode}\uFF09`,
|
|
4392
|
+
consoleUrl: GOOGLE_PLAY_CONSOLE_URL,
|
|
4393
|
+
data: {
|
|
4394
|
+
editId,
|
|
4395
|
+
versionCode,
|
|
4396
|
+
track: "internal",
|
|
4397
|
+
apiTrack: GOOGLE_PLAY_INTERNAL_API_TRACK,
|
|
4398
|
+
status: "draft",
|
|
4399
|
+
committed: true
|
|
4400
|
+
}
|
|
4401
|
+
};
|
|
4402
|
+
} catch (error) {
|
|
4403
|
+
if (editId && accessToken && !committed) {
|
|
4404
|
+
await this.deleteEditBestEffort(encodedPackageName, editId, accessToken);
|
|
4405
|
+
}
|
|
4406
|
+
const message = this.describeError(error, stage);
|
|
4407
|
+
logger.error(`Google Play \u4E0A\u4F20\u5931\u8D25\uFF1A${message}`);
|
|
4408
|
+
return this.failure(message, error);
|
|
4409
|
+
}
|
|
4410
|
+
}
|
|
4411
|
+
async createAccessToken(authConfig) {
|
|
4412
|
+
let credentials;
|
|
4413
|
+
if (authConfig.serviceAccountJsonContent?.trim()) {
|
|
4414
|
+
try {
|
|
4415
|
+
credentials = JSON.parse(authConfig.serviceAccountJsonContent);
|
|
4416
|
+
} catch {
|
|
4417
|
+
throw new Error("\u670D\u52A1\u8D26\u53F7 JSON \u5185\u5BB9\u683C\u5F0F\u65E0\u6548");
|
|
4418
|
+
}
|
|
4419
|
+
}
|
|
4420
|
+
const keyFile = !credentials && authConfig.serviceAccountJsonPath ? path11.resolve(authConfig.serviceAccountJsonPath) : void 0;
|
|
4421
|
+
const auth = new GoogleAuth({
|
|
4422
|
+
scopes: [ANDROID_PUBLISHER_SCOPE],
|
|
4423
|
+
keyFile,
|
|
4424
|
+
credentials
|
|
4425
|
+
});
|
|
4426
|
+
const token = await auth.getAccessToken();
|
|
4427
|
+
if (!token) {
|
|
4428
|
+
throw new Error("Google Play \u8BA4\u8BC1\u672A\u8FD4\u56DE\u8BBF\u95EE\u4EE4\u724C");
|
|
4429
|
+
}
|
|
4430
|
+
return token;
|
|
4431
|
+
}
|
|
4432
|
+
async deleteEditBestEffort(encodedPackageName, editId, accessToken) {
|
|
4433
|
+
try {
|
|
4434
|
+
await this.httpClient.delete(
|
|
4435
|
+
`${PUBLISH_API_ROOT}/applications/${encodedPackageName}/edits/${encodeURIComponent(editId)}`,
|
|
4436
|
+
{ headers: { Authorization: `Bearer ${accessToken}` } }
|
|
4437
|
+
);
|
|
4438
|
+
} catch {
|
|
4439
|
+
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");
|
|
4440
|
+
}
|
|
4441
|
+
}
|
|
4442
|
+
describeError(error, stage) {
|
|
4443
|
+
const status = error?.response?.status;
|
|
4444
|
+
const rawMessage = error?.response?.data?.error?.message || error?.response?.data?.message || error?.message || "\u672A\u77E5\u9519\u8BEF";
|
|
4445
|
+
const safeMessage = this.redactAndTruncate(String(rawMessage));
|
|
4446
|
+
if (/version\s*code|versionCode/i.test(safeMessage)) {
|
|
4447
|
+
return `Google Play \u62D2\u7EDD\u8BE5 versionCode\uFF0C\u8BF7\u63D0\u9AD8 Flutter \u6784\u5EFA\u53F7\u540E\u91CD\u65B0\u751F\u6210 AAB\uFF1A${safeMessage}`;
|
|
4448
|
+
}
|
|
4449
|
+
if (status === 401) {
|
|
4450
|
+
return "Google Play \u8BA4\u8BC1\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u670D\u52A1\u8D26\u53F7 JSON \u662F\u5426\u6709\u6548";
|
|
4451
|
+
}
|
|
4452
|
+
if (status === 403) {
|
|
4453
|
+
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";
|
|
4454
|
+
}
|
|
4455
|
+
if (status === 404) {
|
|
4456
|
+
if (stage === "update_internal_track") {
|
|
4457
|
+
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";
|
|
4458
|
+
}
|
|
4459
|
+
if (stage !== "create_edit") {
|
|
4460
|
+
return `Google Play API \u5728 ${stage} \u9636\u6BB5\u672A\u627E\u5230\u76EE\u6807\u8D44\u6E90\uFF1A${safeMessage}`;
|
|
4461
|
+
}
|
|
4462
|
+
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";
|
|
4463
|
+
}
|
|
4464
|
+
if (error?.code === "ECONNABORTED" || /timeout/i.test(safeMessage)) {
|
|
4465
|
+
return "Google Play \u4E0A\u4F20\u8D85\u65F6\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u540E\u91CD\u8BD5\uFF1B\u672C\u5730 AAB \u672A\u88AB\u5220\u9664";
|
|
4466
|
+
}
|
|
4467
|
+
return `Google Play API \u8BF7\u6C42\u5931\u8D25${status ? `\uFF08HTTP ${status}\uFF09` : ""}\uFF1A${safeMessage}`;
|
|
4468
|
+
}
|
|
4469
|
+
redactAndTruncate(message) {
|
|
4470
|
+
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);
|
|
4471
|
+
}
|
|
4472
|
+
failure(message, error) {
|
|
4473
|
+
return {
|
|
4474
|
+
platform: "google_play",
|
|
4475
|
+
success: false,
|
|
4476
|
+
message,
|
|
4477
|
+
// 原始 Axios 错误可能包含 Authorization 请求头,只返回脱敏后的 Error。
|
|
4478
|
+
error: error ? new Error(message) : void 0
|
|
4479
|
+
};
|
|
4480
|
+
}
|
|
4481
|
+
};
|
|
4482
|
+
|
|
3697
4483
|
// src/utils/upload-manager.ts
|
|
4484
|
+
var GROUPED_ARTIFACT_PLATFORMS = /* @__PURE__ */ new Set(["huawei", "xiaomi", "oppo", "vivo", "tencent"]);
|
|
3698
4485
|
var UploadManager = class {
|
|
3699
4486
|
uploaders;
|
|
3700
4487
|
config;
|
|
@@ -3716,6 +4503,7 @@ var UploadManager = class {
|
|
|
3716
4503
|
this.registerUploader("oppo", new OppoUploader());
|
|
3717
4504
|
this.registerUploader("vivo", new VivoUploader());
|
|
3718
4505
|
this.registerUploader("tencent", new TencentUploader());
|
|
4506
|
+
this.registerUploader("google_play", new GooglePlayUploader());
|
|
3719
4507
|
}
|
|
3720
4508
|
/**
|
|
3721
4509
|
* 注册上传器
|
|
@@ -3734,6 +4522,8 @@ var UploadManager = class {
|
|
|
3734
4522
|
if (config?.publish?.oppo?.enable) enabled.push("oppo");
|
|
3735
4523
|
if (config?.publish?.vivo?.enable) enabled.push("vivo");
|
|
3736
4524
|
if (config?.publish?.tencent?.enable) enabled.push("tencent");
|
|
4525
|
+
if (config?.publish?.harmony?.enable) enabled.push("harmony");
|
|
4526
|
+
if (config?.publish?.google_play?.enable) enabled.push("google_play");
|
|
3737
4527
|
if (config?.publish?.app_store?.enable) {
|
|
3738
4528
|
const mode = config.publish.app_store.mode || "app_store";
|
|
3739
4529
|
enabled.push(mode);
|
|
@@ -3801,9 +4591,22 @@ var UploadManager = class {
|
|
|
3801
4591
|
if (matchedFiles.length === 0) {
|
|
3802
4592
|
logger.warn(`[${platform}] No compatible file found in provided files`);
|
|
3803
4593
|
} else {
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
4594
|
+
if (this.shouldGroupArtifacts(platform)) {
|
|
4595
|
+
const artifactSelection = this.resolveArtifactSelection(platform, matchedFiles, metadata);
|
|
4596
|
+
if (!artifactSelection) {
|
|
4597
|
+
logger.warn(`[${platform}] Failed to build grouped artifact selection`);
|
|
4598
|
+
return;
|
|
4599
|
+
}
|
|
4600
|
+
tasks.push({
|
|
4601
|
+
platform,
|
|
4602
|
+
file: this.getRepresentativeFilePath(artifactSelection),
|
|
4603
|
+
artifactSelection
|
|
4604
|
+
});
|
|
4605
|
+
} else {
|
|
4606
|
+
matchedFiles.forEach((file) => {
|
|
4607
|
+
tasks.push({ platform, file, artifactSelection: file });
|
|
4608
|
+
});
|
|
4609
|
+
}
|
|
3807
4610
|
}
|
|
3808
4611
|
});
|
|
3809
4612
|
if (tasks.length === 0) {
|
|
@@ -3821,8 +4624,14 @@ var UploadManager = class {
|
|
|
3821
4624
|
}
|
|
3822
4625
|
logger.info(`Scheduled ${tasks.length} upload task(s) for ${validPlatforms.length} platform(s)`);
|
|
3823
4626
|
const uploadPromises = tasks.map((task) => {
|
|
3824
|
-
logger.info(`[${task.platform}] Matched file: ${
|
|
3825
|
-
return this.uploadToPlatform(
|
|
4627
|
+
logger.info(`[${task.platform}] Matched file: ${path12.basename(task.file)}`);
|
|
4628
|
+
return this.uploadToPlatform(
|
|
4629
|
+
task.platform,
|
|
4630
|
+
task.file,
|
|
4631
|
+
config,
|
|
4632
|
+
metadata,
|
|
4633
|
+
task.artifactSelection
|
|
4634
|
+
);
|
|
3826
4635
|
});
|
|
3827
4636
|
const results = await Promise.all(uploadPromises);
|
|
3828
4637
|
const report = this.aggregateResults(results, tasks.length, startTime);
|
|
@@ -3832,7 +4641,7 @@ var UploadManager = class {
|
|
|
3832
4641
|
/**
|
|
3833
4642
|
* 上传到单个平台
|
|
3834
4643
|
*/
|
|
3835
|
-
async uploadToPlatform(platform, filePath, config, metadata) {
|
|
4644
|
+
async uploadToPlatform(platform, filePath, config, metadata, artifactSelection = filePath) {
|
|
3836
4645
|
if (this.config.cancellationToken?.isCancelled) {
|
|
3837
4646
|
logger.warn(`[${platform.toUpperCase()}] Upload skipped due to cancellation`);
|
|
3838
4647
|
return {
|
|
@@ -3853,25 +4662,30 @@ var UploadManager = class {
|
|
|
3853
4662
|
if (this.config.verbose) {
|
|
3854
4663
|
logger.info(`[${platform.toUpperCase()}] Starting upload...`);
|
|
3855
4664
|
}
|
|
3856
|
-
const
|
|
3857
|
-
|
|
4665
|
+
const effectiveMetadata = {
|
|
4666
|
+
...metadata,
|
|
4667
|
+
artifactPath: artifactSelection
|
|
4668
|
+
};
|
|
4669
|
+
await this.validateArtifactSelection(platform, artifactSelection, effectiveMetadata);
|
|
4670
|
+
const pkgInfo = path12.extname(filePath).toLowerCase() === ".aab" ? null : await parsePackageInfo(filePath, {
|
|
4671
|
+
androidSdkPath: effectiveMetadata.globalSettings?.androidSdkPath
|
|
3858
4672
|
});
|
|
3859
4673
|
if (pkgInfo) {
|
|
3860
4674
|
if (pkgInfo.versionName) {
|
|
3861
|
-
|
|
4675
|
+
effectiveMetadata.version = pkgInfo.versionName;
|
|
3862
4676
|
}
|
|
3863
4677
|
if (pkgInfo.versionCode) {
|
|
3864
|
-
|
|
4678
|
+
effectiveMetadata.buildNumber = pkgInfo.versionCode;
|
|
3865
4679
|
}
|
|
3866
4680
|
if (pkgInfo.packageName) {
|
|
3867
4681
|
;
|
|
3868
|
-
|
|
4682
|
+
effectiveMetadata.packageName = pkgInfo.packageName;
|
|
3869
4683
|
}
|
|
3870
4684
|
logger.info(
|
|
3871
|
-
`[${platform.toUpperCase()}] \u5DF2\u6821\u51C6\u7248\u672C\u4FE1\u606F: v${
|
|
4685
|
+
`[${platform.toUpperCase()}] \u5DF2\u6821\u51C6\u7248\u672C\u4FE1\u606F: v${effectiveMetadata.version}+${effectiveMetadata.buildNumber}`
|
|
3872
4686
|
);
|
|
3873
4687
|
}
|
|
3874
|
-
const result = await uploader.upload(filePath, config,
|
|
4688
|
+
const result = await uploader.upload(filePath, config, effectiveMetadata);
|
|
3875
4689
|
return result;
|
|
3876
4690
|
} catch (error) {
|
|
3877
4691
|
const errorMessage = error.message || String(error);
|
|
@@ -3884,6 +4698,85 @@ var UploadManager = class {
|
|
|
3884
4698
|
};
|
|
3885
4699
|
}
|
|
3886
4700
|
}
|
|
4701
|
+
shouldGroupArtifacts(platform) {
|
|
4702
|
+
return GROUPED_ARTIFACT_PLATFORMS.has(platform);
|
|
4703
|
+
}
|
|
4704
|
+
resolveArtifactSelection(platform, matchedFiles, metadata) {
|
|
4705
|
+
const explicitArtifacts = metadata.platformArtifacts?.[platform];
|
|
4706
|
+
if (explicitArtifacts) {
|
|
4707
|
+
if (typeof explicitArtifacts === "string") {
|
|
4708
|
+
return explicitArtifacts;
|
|
4709
|
+
}
|
|
4710
|
+
if (Object.keys(explicitArtifacts).length > 0) {
|
|
4711
|
+
return explicitArtifacts;
|
|
4712
|
+
}
|
|
4713
|
+
}
|
|
4714
|
+
if (matchedFiles.length === 0) {
|
|
4715
|
+
return null;
|
|
4716
|
+
}
|
|
4717
|
+
if (matchedFiles.length === 1) {
|
|
4718
|
+
return matchedFiles[0];
|
|
4719
|
+
}
|
|
4720
|
+
return this.buildArtifactMapFromFiles(matchedFiles);
|
|
4721
|
+
}
|
|
4722
|
+
buildArtifactMapFromFiles(files) {
|
|
4723
|
+
return files.reduce(
|
|
4724
|
+
(acc, filePath, index) => {
|
|
4725
|
+
acc[this.inferArtifactKey(filePath, index)] = filePath;
|
|
4726
|
+
return acc;
|
|
4727
|
+
},
|
|
4728
|
+
{}
|
|
4729
|
+
);
|
|
4730
|
+
}
|
|
4731
|
+
inferArtifactKey(filePath, index) {
|
|
4732
|
+
const fileName = path12.basename(filePath).toLowerCase();
|
|
4733
|
+
if (fileName.includes("arm64-v8a")) return "arm64-v8a";
|
|
4734
|
+
if (fileName.includes("armeabi-v7a")) return "armeabi-v7a";
|
|
4735
|
+
if (fileName.includes("x86_64")) return "x86_64";
|
|
4736
|
+
if (fileName.includes("universal")) return "universal";
|
|
4737
|
+
return `artifact_${index + 1}`;
|
|
4738
|
+
}
|
|
4739
|
+
getRepresentativeFilePath(artifactSelection) {
|
|
4740
|
+
if (typeof artifactSelection === "string") {
|
|
4741
|
+
return artifactSelection;
|
|
4742
|
+
}
|
|
4743
|
+
return Object.values(artifactSelection)[0];
|
|
4744
|
+
}
|
|
4745
|
+
async validateArtifactSelection(platform, artifactSelection, metadata) {
|
|
4746
|
+
if (typeof artifactSelection === "string") {
|
|
4747
|
+
return;
|
|
4748
|
+
}
|
|
4749
|
+
const artifactPaths = Object.values(artifactSelection).filter(Boolean);
|
|
4750
|
+
if (artifactPaths.length < 2) {
|
|
4751
|
+
return;
|
|
4752
|
+
}
|
|
4753
|
+
const parsedInfos = await Promise.all(
|
|
4754
|
+
artifactPaths.map(
|
|
4755
|
+
(artifactPath) => parsePackageInfo(artifactPath, {
|
|
4756
|
+
androidSdkPath: metadata.globalSettings?.androidSdkPath
|
|
4757
|
+
})
|
|
4758
|
+
)
|
|
4759
|
+
);
|
|
4760
|
+
const availableInfos = parsedInfos.filter((info) => !!info);
|
|
4761
|
+
if (availableInfos.length < 2) {
|
|
4762
|
+
logger.warn(
|
|
4763
|
+
`[${platform.toUpperCase()}] Unable to fully validate multi-artifact package info; continuing with upload.`
|
|
4764
|
+
);
|
|
4765
|
+
return;
|
|
4766
|
+
}
|
|
4767
|
+
const [baseInfo, ...restInfos] = availableInfos;
|
|
4768
|
+
for (const info of restInfos) {
|
|
4769
|
+
if (baseInfo?.packageName && info?.packageName && baseInfo.packageName !== info.packageName) {
|
|
4770
|
+
throw new Error("\u53CC\u5305\u4E0A\u4F20\u5931\u8D25\uFF1A\u6240\u9009\u5B89\u88C5\u5305\u7684 packageName \u4E0D\u4E00\u81F4");
|
|
4771
|
+
}
|
|
4772
|
+
if (baseInfo?.versionName && info?.versionName && baseInfo.versionName !== info.versionName) {
|
|
4773
|
+
throw new Error("\u53CC\u5305\u4E0A\u4F20\u5931\u8D25\uFF1A\u6240\u9009\u5B89\u88C5\u5305\u7684 versionName \u4E0D\u4E00\u81F4");
|
|
4774
|
+
}
|
|
4775
|
+
if (baseInfo?.versionCode && info?.versionCode && baseInfo.versionCode !== info.versionCode) {
|
|
4776
|
+
throw new Error("\u53CC\u5305\u4E0A\u4F20\u5931\u8D25\uFF1A\u6240\u9009\u5B89\u88C5\u5305\u7684 versionCode \u4E0D\u4E00\u81F4");
|
|
4777
|
+
}
|
|
4778
|
+
}
|
|
4779
|
+
}
|
|
3887
4780
|
/**
|
|
3888
4781
|
* 聚合上传结果
|
|
3889
4782
|
*/
|
|
@@ -3993,7 +4886,7 @@ var UploadManager = class {
|
|
|
3993
4886
|
};
|
|
3994
4887
|
|
|
3995
4888
|
// src/validators/config_validator.ts
|
|
3996
|
-
import
|
|
4889
|
+
import fs14 from "fs-extra";
|
|
3997
4890
|
import yaml from "js-yaml";
|
|
3998
4891
|
|
|
3999
4892
|
// src/validators/env_validator.ts
|
|
@@ -4019,20 +4912,22 @@ var ConfigValidator = class {
|
|
|
4019
4912
|
* @param filePath 配置文件路径
|
|
4020
4913
|
*/
|
|
4021
4914
|
static loadAndValidate(filePath) {
|
|
4022
|
-
if (!
|
|
4915
|
+
if (!fs14.existsSync(filePath)) {
|
|
4023
4916
|
logger.error(`Config file not found: ${filePath}`);
|
|
4024
4917
|
return null;
|
|
4025
4918
|
}
|
|
4026
|
-
let content = fs13.readFileSync(filePath, "utf8");
|
|
4027
|
-
const envRegex = /\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
|
|
4028
|
-
const matches = [...content.matchAll(envRegex)];
|
|
4029
|
-
const requiredEnvs = Array.from(new Set(matches.map((m) => m[1])));
|
|
4030
|
-
if (!EnvValidator.validate(requiredEnvs)) {
|
|
4031
|
-
return null;
|
|
4032
|
-
}
|
|
4033
|
-
content = content.replace(envRegex, (_, varName) => process.env[varName] || "");
|
|
4034
4919
|
try {
|
|
4035
|
-
const
|
|
4920
|
+
const content = fs14.readFileSync(filePath, "utf8");
|
|
4921
|
+
const rawConfig = yaml.load(content);
|
|
4922
|
+
if (!rawConfig || typeof rawConfig !== "object") {
|
|
4923
|
+
logger.error("Config file must contain a YAML object");
|
|
4924
|
+
return null;
|
|
4925
|
+
}
|
|
4926
|
+
const requiredEnvs = this.collectRequiredEnvVars(rawConfig);
|
|
4927
|
+
if (!EnvValidator.validate(requiredEnvs)) {
|
|
4928
|
+
return null;
|
|
4929
|
+
}
|
|
4930
|
+
const config = this.resolveEnvVars(rawConfig);
|
|
4036
4931
|
if (!this.validateSchema(config)) {
|
|
4037
4932
|
return null;
|
|
4038
4933
|
}
|
|
@@ -4042,15 +4937,71 @@ var ConfigValidator = class {
|
|
|
4042
4937
|
return null;
|
|
4043
4938
|
}
|
|
4044
4939
|
}
|
|
4940
|
+
/**
|
|
4941
|
+
* 只收集本次实际启用配置中的环境变量,避免禁用平台阻塞其它上传任务。
|
|
4942
|
+
*/
|
|
4943
|
+
static collectRequiredEnvVars(config) {
|
|
4944
|
+
const activeConfig = {
|
|
4945
|
+
...config,
|
|
4946
|
+
publish: Object.fromEntries(
|
|
4947
|
+
Object.entries(config.publish || {}).filter(([, value]) => value?.enable === true)
|
|
4948
|
+
)
|
|
4949
|
+
};
|
|
4950
|
+
const requiredEnvs = /* @__PURE__ */ new Set();
|
|
4951
|
+
const envRegex = /\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
|
|
4952
|
+
const visit = (value) => {
|
|
4953
|
+
if (typeof value === "string") {
|
|
4954
|
+
for (const match of value.matchAll(envRegex)) {
|
|
4955
|
+
requiredEnvs.add(match[1]);
|
|
4956
|
+
}
|
|
4957
|
+
return;
|
|
4958
|
+
}
|
|
4959
|
+
if (Array.isArray(value)) {
|
|
4960
|
+
value.forEach(visit);
|
|
4961
|
+
return;
|
|
4962
|
+
}
|
|
4963
|
+
if (value && typeof value === "object") {
|
|
4964
|
+
Object.values(value).forEach(visit);
|
|
4965
|
+
}
|
|
4966
|
+
};
|
|
4967
|
+
visit(activeConfig);
|
|
4968
|
+
return Array.from(requiredEnvs);
|
|
4969
|
+
}
|
|
4970
|
+
/** 在内存中递归解析环境变量,不重新拼接 YAML,避免密钥字符破坏 YAML 结构。 */
|
|
4971
|
+
static resolveEnvVars(value) {
|
|
4972
|
+
if (typeof value === "string") {
|
|
4973
|
+
return value.replace(
|
|
4974
|
+
/\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g,
|
|
4975
|
+
(_, varName) => process.env[varName] || ""
|
|
4976
|
+
);
|
|
4977
|
+
}
|
|
4978
|
+
if (Array.isArray(value)) {
|
|
4979
|
+
return value.map((item) => this.resolveEnvVars(item));
|
|
4980
|
+
}
|
|
4981
|
+
if (value && typeof value === "object") {
|
|
4982
|
+
return Object.fromEntries(
|
|
4983
|
+
Object.entries(value).map(([key, item]) => [key, this.resolveEnvVars(item)])
|
|
4984
|
+
);
|
|
4985
|
+
}
|
|
4986
|
+
return value;
|
|
4987
|
+
}
|
|
4045
4988
|
/**
|
|
4046
4989
|
* 验证配置 Schema
|
|
4047
4990
|
* @param config 配置对象
|
|
4048
4991
|
*/
|
|
4049
4992
|
static validateSchema(config) {
|
|
4050
4993
|
if (config.publish?.app_store?.enable) {
|
|
4994
|
+
const mode = config.publish.app_store.mode || "app_store";
|
|
4051
4995
|
const auth = config.publish.app_store.auth;
|
|
4052
|
-
|
|
4053
|
-
|
|
4996
|
+
const hasCliAuth = Boolean(auth?.appleId && auth?.appPassword);
|
|
4997
|
+
const hasApiAuth = Boolean(
|
|
4998
|
+
auth?.apiKeyId && auth?.apiIssuerId && (auth?.privateKeyPath || auth?.privateKeyContent)
|
|
4999
|
+
);
|
|
5000
|
+
const hasRequiredAuth = mode === "adhoc" || (mode === "testflight" ? hasApiAuth : hasCliAuth || hasApiAuth);
|
|
5001
|
+
if (!hasRequiredAuth) {
|
|
5002
|
+
logger.error(
|
|
5003
|
+
mode === "testflight" ? "TestFlight enabled but missing complete API key credentials" : "App Store enabled but missing Apple ID credentials or complete API key credentials"
|
|
5004
|
+
);
|
|
4054
5005
|
return false;
|
|
4055
5006
|
}
|
|
4056
5007
|
}
|
|
@@ -4096,23 +5047,38 @@ var ConfigValidator = class {
|
|
|
4096
5047
|
return false;
|
|
4097
5048
|
}
|
|
4098
5049
|
}
|
|
5050
|
+
if (config.publish?.google_play?.enable) {
|
|
5051
|
+
const googlePlay = config.publish.google_play;
|
|
5052
|
+
const auth = googlePlay.auth;
|
|
5053
|
+
if (!googlePlay.packageName || !auth?.serviceAccountJsonPath && !auth?.serviceAccountJsonContent) {
|
|
5054
|
+
logger.error(
|
|
5055
|
+
"Google Play enabled but missing packageName or service account JSON credentials"
|
|
5056
|
+
);
|
|
5057
|
+
return false;
|
|
5058
|
+
}
|
|
5059
|
+
if (googlePlay.track && googlePlay.track !== "internal") {
|
|
5060
|
+
logger.error("Google Play first release only supports the internal track");
|
|
5061
|
+
return false;
|
|
5062
|
+
}
|
|
5063
|
+
}
|
|
4099
5064
|
return true;
|
|
4100
5065
|
}
|
|
4101
5066
|
};
|
|
4102
5067
|
|
|
4103
5068
|
// src/cli.ts
|
|
4104
|
-
import
|
|
4105
|
-
import
|
|
5069
|
+
import path13 from "path";
|
|
5070
|
+
import fs15 from "fs-extra";
|
|
4106
5071
|
import yaml2 from "js-yaml";
|
|
4107
|
-
var VERSION = "0.1.
|
|
5072
|
+
var VERSION = "0.1.2";
|
|
4108
5073
|
function printHelp() {
|
|
4109
5074
|
console.log(`
|
|
4110
5075
|
app-ship v${VERSION} \u2014 Multi-platform app store uploader
|
|
4111
5076
|
|
|
4112
5077
|
Usage:
|
|
4113
|
-
app-ship upload -c <config> -f <file> [--platform pgyer,huawei]
|
|
5078
|
+
app-ship upload -c <config> -f <file> [--platform pgyer,huawei] [--project <dir>]
|
|
4114
5079
|
app-ship platforms List supported platforms
|
|
4115
5080
|
app-ship init Generate sample config file
|
|
5081
|
+
app-ship history [--project <dir>] [--json]
|
|
4116
5082
|
app-ship --help Show this help
|
|
4117
5083
|
app-ship --version Show version
|
|
4118
5084
|
|
|
@@ -4123,7 +5089,13 @@ Upload Options:
|
|
|
4123
5089
|
-v, --version <ver> Version string (default: from config or "1.0.0")
|
|
4124
5090
|
-b, --build-number <n> Build number (default: "1")
|
|
4125
5091
|
--changelog <text> Changelog / release notes
|
|
5092
|
+
--project <dir> Project directory recorded in the release receipt
|
|
5093
|
+
--confirm-google-play Confirm upload to Google Play internal draft
|
|
4126
5094
|
--verbose Show detailed output
|
|
5095
|
+
|
|
5096
|
+
History Options:
|
|
5097
|
+
--project <dir> Project directory (default: current directory)
|
|
5098
|
+
--json Print structured receipts as JSON
|
|
4127
5099
|
`);
|
|
4128
5100
|
}
|
|
4129
5101
|
function parseArgs(argv) {
|
|
@@ -4137,6 +5109,12 @@ function parseArgs(argv) {
|
|
|
4137
5109
|
args.showVersion = true;
|
|
4138
5110
|
} else if (arg === "--verbose") {
|
|
4139
5111
|
args.verbose = true;
|
|
5112
|
+
} else if (arg === "--confirm-google-play") {
|
|
5113
|
+
args.confirmGooglePlay = true;
|
|
5114
|
+
} else if (arg === "--json") {
|
|
5115
|
+
args.json = true;
|
|
5116
|
+
} else if (arg === "--project" && argv[i + 1]) {
|
|
5117
|
+
args.project = argv[++i];
|
|
4140
5118
|
} else if ((arg === "-c" || arg === "--config") && argv[i + 1]) {
|
|
4141
5119
|
args.config = argv[++i];
|
|
4142
5120
|
} else if ((arg === "-f" || arg === "--file") && argv[i + 1]) {
|
|
@@ -4167,9 +5145,9 @@ async function cmdUpload(args) {
|
|
|
4167
5145
|
logger.error("Missing file path. Use: app-ship upload -c <config.yaml> -f <file>");
|
|
4168
5146
|
process.exit(1);
|
|
4169
5147
|
}
|
|
4170
|
-
const absConfig =
|
|
4171
|
-
const absFile =
|
|
4172
|
-
if (!
|
|
5148
|
+
const absConfig = path13.resolve(configPath);
|
|
5149
|
+
const absFile = path13.resolve(filePath);
|
|
5150
|
+
if (!fs15.existsSync(absFile)) {
|
|
4173
5151
|
logger.error(`File not found: ${absFile}`);
|
|
4174
5152
|
process.exit(1);
|
|
4175
5153
|
}
|
|
@@ -4178,27 +5156,92 @@ async function cmdUpload(args) {
|
|
|
4178
5156
|
logger.error("Config validation failed");
|
|
4179
5157
|
process.exit(1);
|
|
4180
5158
|
}
|
|
4181
|
-
const platforms = args.platform ?
|
|
5159
|
+
const platforms = args.platform ? Array.from(
|
|
5160
|
+
new Set(
|
|
5161
|
+
args.platform.split(",").map((platform) => platform.trim()).filter(Boolean)
|
|
5162
|
+
)
|
|
5163
|
+
) : void 0;
|
|
5164
|
+
const manager = new UploadManager({
|
|
5165
|
+
maxConcurrent: 3,
|
|
5166
|
+
continueOnError: true,
|
|
5167
|
+
verbose: !!args.verbose
|
|
5168
|
+
});
|
|
5169
|
+
const effectivePlatforms = platforms || manager.getEnabledPlatforms(config);
|
|
5170
|
+
if (effectivePlatforms.length === 0) {
|
|
5171
|
+
logger.error("No upload platform is enabled. Enable one in config or pass --platform.");
|
|
5172
|
+
process.exit(1);
|
|
5173
|
+
}
|
|
4182
5174
|
const metadata = {
|
|
4183
5175
|
version: args.ver || config.version || "1.0.0",
|
|
4184
5176
|
buildNumber: args.buildNumber || "1",
|
|
4185
|
-
changelog: args.changelog || ""
|
|
5177
|
+
changelog: args.changelog || "",
|
|
5178
|
+
platform: effectivePlatforms
|
|
4186
5179
|
};
|
|
4187
5180
|
logger.title("App Ship \u2014 Starting Upload");
|
|
4188
5181
|
logger.info(`File: ${absFile}`);
|
|
4189
5182
|
logger.info(`Config: ${absConfig}`);
|
|
4190
|
-
|
|
4191
|
-
|
|
5183
|
+
logger.info(`Platforms: ${effectivePlatforms.join(", ") || "none"}`);
|
|
5184
|
+
if (effectivePlatforms.includes("google_play")) {
|
|
5185
|
+
if (path13.extname(absFile).toLowerCase() !== ".aab") {
|
|
5186
|
+
logger.error("Google Play only accepts Android App Bundle (.aab) files");
|
|
5187
|
+
process.exit(1);
|
|
5188
|
+
}
|
|
5189
|
+
logger.info(`Google Play package: ${config.publish?.google_play?.packageName || "unknown"}`);
|
|
5190
|
+
logger.info("Google Play target: internal draft");
|
|
5191
|
+
if (!args.confirmGooglePlay) {
|
|
5192
|
+
logger.error(
|
|
5193
|
+
"Google Play upload requires explicit confirmation. Review the preview and rerun with --confirm-google-play"
|
|
5194
|
+
);
|
|
5195
|
+
process.exit(1);
|
|
5196
|
+
}
|
|
4192
5197
|
}
|
|
4193
5198
|
logger.newLine();
|
|
4194
|
-
const
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
5199
|
+
const startedAt = Date.now();
|
|
5200
|
+
const report = await manager.uploadToMultiplePlatforms(
|
|
5201
|
+
absFile,
|
|
5202
|
+
config,
|
|
5203
|
+
metadata,
|
|
5204
|
+
effectivePlatforms
|
|
5205
|
+
);
|
|
5206
|
+
try {
|
|
5207
|
+
const receipt = await new ReleaseReceiptStore().save({
|
|
5208
|
+
projectRoot: resolveReleaseProjectRoot(args.project, absConfig),
|
|
5209
|
+
kind: "upload",
|
|
5210
|
+
appName: metadata.appName,
|
|
5211
|
+
packageName: resolvePackageName(config),
|
|
5212
|
+
versionName: metadata.version,
|
|
5213
|
+
buildNumber: metadata.buildNumber,
|
|
5214
|
+
changelog: metadata.changelog,
|
|
5215
|
+
artifactPaths: absFile,
|
|
5216
|
+
targets: createUploadTargets(report.results),
|
|
5217
|
+
startedAt
|
|
5218
|
+
});
|
|
5219
|
+
logger.info(`Receipt: ${receipt.id}`);
|
|
5220
|
+
} catch (error) {
|
|
5221
|
+
logger.warn(`Failed to save release receipt: ${error.message}`);
|
|
5222
|
+
}
|
|
4200
5223
|
process.exit(report.failureCount > 0 ? 1 : 0);
|
|
4201
5224
|
}
|
|
5225
|
+
async function cmdHistory(args) {
|
|
5226
|
+
const projectRoot = path13.resolve(args.project || process.cwd());
|
|
5227
|
+
const receipts = await new ReleaseReceiptStore().list(projectRoot);
|
|
5228
|
+
if (args.json) {
|
|
5229
|
+
console.log(JSON.stringify(receipts, null, 2));
|
|
5230
|
+
return;
|
|
5231
|
+
}
|
|
5232
|
+
if (receipts.length === 0) {
|
|
5233
|
+
logger.info(`No release receipts found for ${projectRoot}`);
|
|
5234
|
+
return;
|
|
5235
|
+
}
|
|
5236
|
+
logger.title(`Release Receipts \u2014 ${projectRoot}`);
|
|
5237
|
+
receipts.forEach((receipt, index) => {
|
|
5238
|
+
if (index > 0) console.log("");
|
|
5239
|
+
console.log(formatReleaseReceipt(receipt));
|
|
5240
|
+
});
|
|
5241
|
+
}
|
|
5242
|
+
function resolvePackageName(config) {
|
|
5243
|
+
return config.appId || config.publish?.google_play?.packageName || Object.values(config.publish || {}).find((value) => value?.packageName)?.packageName;
|
|
5244
|
+
}
|
|
4202
5245
|
function cmdPlatforms() {
|
|
4203
5246
|
const manager = new UploadManager();
|
|
4204
5247
|
const platforms = manager.getAvailablePlatforms();
|
|
@@ -4211,7 +5254,7 @@ Total: ${platforms.length} platforms`);
|
|
|
4211
5254
|
}
|
|
4212
5255
|
function cmdInit() {
|
|
4213
5256
|
const filename = "app-ship.yaml";
|
|
4214
|
-
if (
|
|
5257
|
+
if (fs15.existsSync(filename)) {
|
|
4215
5258
|
logger.warn(`${filename} already exists. Skipping.`);
|
|
4216
5259
|
return;
|
|
4217
5260
|
}
|
|
@@ -4219,7 +5262,7 @@ function cmdInit() {
|
|
|
4219
5262
|
version: "1.0.0",
|
|
4220
5263
|
publish: {
|
|
4221
5264
|
pgyer: {
|
|
4222
|
-
enable:
|
|
5265
|
+
enable: false,
|
|
4223
5266
|
apiKey: "YOUR_PGYER_API_KEY"
|
|
4224
5267
|
},
|
|
4225
5268
|
huawei: {
|
|
@@ -4255,6 +5298,7 @@ function cmdInit() {
|
|
|
4255
5298
|
tencent: {
|
|
4256
5299
|
enable: false,
|
|
4257
5300
|
auth: {
|
|
5301
|
+
userId: "",
|
|
4258
5302
|
appId: "",
|
|
4259
5303
|
accessSecret: ""
|
|
4260
5304
|
}
|
|
@@ -4273,10 +5317,18 @@ function cmdInit() {
|
|
|
4273
5317
|
appleId: "",
|
|
4274
5318
|
appPassword: ""
|
|
4275
5319
|
}
|
|
5320
|
+
},
|
|
5321
|
+
google_play: {
|
|
5322
|
+
enable: false,
|
|
5323
|
+
auth: {
|
|
5324
|
+
serviceAccountJsonPath: "./service-account.json"
|
|
5325
|
+
},
|
|
5326
|
+
packageName: "com.example.app",
|
|
5327
|
+
track: "internal"
|
|
4276
5328
|
}
|
|
4277
5329
|
}
|
|
4278
5330
|
};
|
|
4279
|
-
|
|
5331
|
+
fs15.writeFileSync(filename, yaml2.dump(sample, { indent: 2 }), "utf8");
|
|
4280
5332
|
logger.success(`Created ${filename}`);
|
|
4281
5333
|
logger.info("Edit the file to configure your platform credentials.");
|
|
4282
5334
|
}
|
|
@@ -4301,6 +5353,9 @@ async function main() {
|
|
|
4301
5353
|
case "init":
|
|
4302
5354
|
cmdInit();
|
|
4303
5355
|
break;
|
|
5356
|
+
case "history":
|
|
5357
|
+
await cmdHistory(args);
|
|
5358
|
+
break;
|
|
4304
5359
|
default:
|
|
4305
5360
|
printHelp();
|
|
4306
5361
|
break;
|