@huoye/app-ship 0.1.2 → 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/README.md +21 -3
- package/dist/cli.js +733 -75
- package/dist/index.cjs +655 -67
- package/dist/index.d.cts +137 -14
- package/dist/index.d.ts +137 -14
- package/dist/index.js +629 -51
- package/package.json +11 -7
- package/dist/cli.js.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -552,16 +552,18 @@ var ConfigValidator = class {
|
|
|
552
552
|
logger.error(`Config file not found: ${filePath}`);
|
|
553
553
|
return null;
|
|
554
554
|
}
|
|
555
|
-
let content = fs.readFileSync(filePath, "utf8");
|
|
556
|
-
const envRegex = /\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
|
|
557
|
-
const matches = [...content.matchAll(envRegex)];
|
|
558
|
-
const requiredEnvs = Array.from(new Set(matches.map((m) => m[1])));
|
|
559
|
-
if (!EnvValidator.validate(requiredEnvs)) {
|
|
560
|
-
return null;
|
|
561
|
-
}
|
|
562
|
-
content = content.replace(envRegex, (_, varName) => process.env[varName] || "");
|
|
563
555
|
try {
|
|
564
|
-
const
|
|
556
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
557
|
+
const rawConfig = yaml.load(content);
|
|
558
|
+
if (!rawConfig || typeof rawConfig !== "object") {
|
|
559
|
+
logger.error("Config file must contain a YAML object");
|
|
560
|
+
return null;
|
|
561
|
+
}
|
|
562
|
+
const requiredEnvs = this.collectRequiredEnvVars(rawConfig);
|
|
563
|
+
if (!EnvValidator.validate(requiredEnvs)) {
|
|
564
|
+
return null;
|
|
565
|
+
}
|
|
566
|
+
const config = this.resolveEnvVars(rawConfig);
|
|
565
567
|
if (!this.validateSchema(config)) {
|
|
566
568
|
return null;
|
|
567
569
|
}
|
|
@@ -571,15 +573,71 @@ var ConfigValidator = class {
|
|
|
571
573
|
return null;
|
|
572
574
|
}
|
|
573
575
|
}
|
|
576
|
+
/**
|
|
577
|
+
* 只收集本次实际启用配置中的环境变量,避免禁用平台阻塞其它上传任务。
|
|
578
|
+
*/
|
|
579
|
+
static collectRequiredEnvVars(config) {
|
|
580
|
+
const activeConfig = {
|
|
581
|
+
...config,
|
|
582
|
+
publish: Object.fromEntries(
|
|
583
|
+
Object.entries(config.publish || {}).filter(([, value]) => value?.enable === true)
|
|
584
|
+
)
|
|
585
|
+
};
|
|
586
|
+
const requiredEnvs = /* @__PURE__ */ new Set();
|
|
587
|
+
const envRegex = /\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
|
|
588
|
+
const visit = (value) => {
|
|
589
|
+
if (typeof value === "string") {
|
|
590
|
+
for (const match of value.matchAll(envRegex)) {
|
|
591
|
+
requiredEnvs.add(match[1]);
|
|
592
|
+
}
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
if (Array.isArray(value)) {
|
|
596
|
+
value.forEach(visit);
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
if (value && typeof value === "object") {
|
|
600
|
+
Object.values(value).forEach(visit);
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
visit(activeConfig);
|
|
604
|
+
return Array.from(requiredEnvs);
|
|
605
|
+
}
|
|
606
|
+
/** 在内存中递归解析环境变量,不重新拼接 YAML,避免密钥字符破坏 YAML 结构。 */
|
|
607
|
+
static resolveEnvVars(value) {
|
|
608
|
+
if (typeof value === "string") {
|
|
609
|
+
return value.replace(
|
|
610
|
+
/\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g,
|
|
611
|
+
(_, varName) => process.env[varName] || ""
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
if (Array.isArray(value)) {
|
|
615
|
+
return value.map((item) => this.resolveEnvVars(item));
|
|
616
|
+
}
|
|
617
|
+
if (value && typeof value === "object") {
|
|
618
|
+
return Object.fromEntries(
|
|
619
|
+
Object.entries(value).map(([key, item]) => [key, this.resolveEnvVars(item)])
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
return value;
|
|
623
|
+
}
|
|
574
624
|
/**
|
|
575
625
|
* 验证配置 Schema
|
|
576
626
|
* @param config 配置对象
|
|
577
627
|
*/
|
|
578
628
|
static validateSchema(config) {
|
|
579
629
|
if (config.publish?.app_store?.enable) {
|
|
630
|
+
const mode = config.publish.app_store.mode || "app_store";
|
|
580
631
|
const auth = config.publish.app_store.auth;
|
|
581
|
-
|
|
582
|
-
|
|
632
|
+
const hasCliAuth = Boolean(auth?.appleId && auth?.appPassword);
|
|
633
|
+
const hasApiAuth = Boolean(
|
|
634
|
+
auth?.apiKeyId && auth?.apiIssuerId && (auth?.privateKeyPath || auth?.privateKeyContent)
|
|
635
|
+
);
|
|
636
|
+
const hasRequiredAuth = mode === "adhoc" || (mode === "testflight" ? hasApiAuth : hasCliAuth || hasApiAuth);
|
|
637
|
+
if (!hasRequiredAuth) {
|
|
638
|
+
logger.error(
|
|
639
|
+
mode === "testflight" ? "TestFlight enabled but missing complete API key credentials" : "App Store enabled but missing Apple ID credentials or complete API key credentials"
|
|
640
|
+
);
|
|
583
641
|
return false;
|
|
584
642
|
}
|
|
585
643
|
}
|
|
@@ -625,6 +683,20 @@ var ConfigValidator = class {
|
|
|
625
683
|
return false;
|
|
626
684
|
}
|
|
627
685
|
}
|
|
686
|
+
if (config.publish?.google_play?.enable) {
|
|
687
|
+
const googlePlay = config.publish.google_play;
|
|
688
|
+
const auth = googlePlay.auth;
|
|
689
|
+
if (!googlePlay.packageName || !auth?.serviceAccountJsonPath && !auth?.serviceAccountJsonContent) {
|
|
690
|
+
logger.error(
|
|
691
|
+
"Google Play enabled but missing packageName or service account JSON credentials"
|
|
692
|
+
);
|
|
693
|
+
return false;
|
|
694
|
+
}
|
|
695
|
+
if (googlePlay.track && googlePlay.track !== "internal") {
|
|
696
|
+
logger.error("Google Play first release only supports the internal track");
|
|
697
|
+
return false;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
628
700
|
return true;
|
|
629
701
|
}
|
|
630
702
|
};
|
|
@@ -966,6 +1038,7 @@ var UploadManager = class {
|
|
|
966
1038
|
this.registerUploader("oppo", new OppoUploader());
|
|
967
1039
|
this.registerUploader("vivo", new VivoUploader());
|
|
968
1040
|
this.registerUploader("tencent", new TencentUploader());
|
|
1041
|
+
this.registerUploader("google_play", new GooglePlayUploader());
|
|
969
1042
|
}
|
|
970
1043
|
/**
|
|
971
1044
|
* 注册上传器
|
|
@@ -984,6 +1057,8 @@ var UploadManager = class {
|
|
|
984
1057
|
if (config?.publish?.oppo?.enable) enabled.push("oppo");
|
|
985
1058
|
if (config?.publish?.vivo?.enable) enabled.push("vivo");
|
|
986
1059
|
if (config?.publish?.tencent?.enable) enabled.push("tencent");
|
|
1060
|
+
if (config?.publish?.harmony?.enable) enabled.push("harmony");
|
|
1061
|
+
if (config?.publish?.google_play?.enable) enabled.push("google_play");
|
|
987
1062
|
if (config?.publish?.app_store?.enable) {
|
|
988
1063
|
const mode = config.publish.app_store.mode || "app_store";
|
|
989
1064
|
enabled.push(mode);
|
|
@@ -1127,7 +1202,7 @@ var UploadManager = class {
|
|
|
1127
1202
|
artifactPath: artifactSelection
|
|
1128
1203
|
};
|
|
1129
1204
|
await this.validateArtifactSelection(platform, artifactSelection, effectiveMetadata);
|
|
1130
|
-
const pkgInfo = await parsePackageInfo(filePath, {
|
|
1205
|
+
const pkgInfo = path2.extname(filePath).toLowerCase() === ".aab" ? null : await parsePackageInfo(filePath, {
|
|
1131
1206
|
androidSdkPath: effectiveMetadata.globalSettings?.androidSdkPath
|
|
1132
1207
|
});
|
|
1133
1208
|
if (pkgInfo) {
|
|
@@ -1484,6 +1559,280 @@ var AppleJWTGenerator = class _AppleJWTGenerator {
|
|
|
1484
1559
|
}
|
|
1485
1560
|
};
|
|
1486
1561
|
|
|
1562
|
+
// src/utils/release-receipt.ts
|
|
1563
|
+
import { createHash, randomUUID } from "crypto";
|
|
1564
|
+
import { createReadStream } from "fs";
|
|
1565
|
+
import { chmod, mkdir, readFile, realpath, rename, stat, writeFile } from "fs/promises";
|
|
1566
|
+
import { homedir } from "os";
|
|
1567
|
+
import path3 from "path";
|
|
1568
|
+
var RELEASE_RECEIPT_SCHEMA_VERSION = 1;
|
|
1569
|
+
var DEFAULT_RELEASE_RECEIPT_LIMIT = 100;
|
|
1570
|
+
var REMOTE_IDENTIFIER_KEYS = [
|
|
1571
|
+
"id",
|
|
1572
|
+
"appId",
|
|
1573
|
+
"buildId",
|
|
1574
|
+
"versionId",
|
|
1575
|
+
"releaseId",
|
|
1576
|
+
"editId",
|
|
1577
|
+
"versionCode",
|
|
1578
|
+
"track",
|
|
1579
|
+
"apiTrack",
|
|
1580
|
+
"status",
|
|
1581
|
+
"committed"
|
|
1582
|
+
];
|
|
1583
|
+
function resolveReleaseProjectRoot(projectRoot, configPath) {
|
|
1584
|
+
return path3.resolve(projectRoot || path3.dirname(path3.resolve(configPath)));
|
|
1585
|
+
}
|
|
1586
|
+
function sanitizeReleaseMessage(value, maxLength = 500) {
|
|
1587
|
+
let message = typeof value === "string" ? value : String(value ?? "");
|
|
1588
|
+
message = message.replace(/-----BEGIN [^-]*PRIVATE KEY-----[\s\S]*?-----END [^-]*PRIVATE KEY-----/gi, "[\u5DF2\u8131\u654F]").replace(/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [\u5DF2\u8131\u654F]").replace(
|
|
1589
|
+
/(["']?(?:access[_-]?token|refresh[_-]?token|client[_-]?secret|private[_-]?key|api[_-]?key|app[_-]?password|authorization|credential|service[_-]?account(?:[_-]?json)?[_-]?content)["']?\s*[:=]\s*)["']?[^\s,"'}]+["']?/gi,
|
|
1590
|
+
"$1[\u5DF2\u8131\u654F]"
|
|
1591
|
+
).replace(/[\r\n\t]+/g, " ").replace(/\s{2,}/g, " ").trim();
|
|
1592
|
+
return message.length > maxLength ? `${message.slice(0, maxLength - 1)}\u2026` : message;
|
|
1593
|
+
}
|
|
1594
|
+
function createUploadTargets(results) {
|
|
1595
|
+
return results.map((result) => {
|
|
1596
|
+
const platform = sanitizeReleaseMessage(result.platform, 80) || "unknown";
|
|
1597
|
+
const data = isRecord(result.data) ? result.data : void 0;
|
|
1598
|
+
const isDraft = result.success && platform === "google_play" && String(data?.status || "").toLowerCase() === "draft";
|
|
1599
|
+
const remote = createRemoteReceipt(result, data);
|
|
1600
|
+
return {
|
|
1601
|
+
platform,
|
|
1602
|
+
state: result.success ? isDraft ? "draft_created" : "uploaded" : "failed",
|
|
1603
|
+
outcome: result.success ? "success" : "failed",
|
|
1604
|
+
message: sanitizeReleaseMessage(result.message) || (result.success ? "\u4E0A\u4F20\u6210\u529F" : "\u4E0A\u4F20\u5931\u8D25"),
|
|
1605
|
+
...remote ? { remote } : {}
|
|
1606
|
+
};
|
|
1607
|
+
});
|
|
1608
|
+
}
|
|
1609
|
+
async function createReleaseReceipt(input) {
|
|
1610
|
+
const projectRoot = await canonicalizeProjectRoot(input.projectRoot);
|
|
1611
|
+
const startedAt = toDate(input.startedAt ?? Date.now());
|
|
1612
|
+
const finishedAt = toDate(input.finishedAt ?? Date.now());
|
|
1613
|
+
const targets = input.targets.map(sanitizeTarget);
|
|
1614
|
+
return {
|
|
1615
|
+
schemaVersion: RELEASE_RECEIPT_SCHEMA_VERSION,
|
|
1616
|
+
id: randomUUID(),
|
|
1617
|
+
projectKey: createProjectKey(projectRoot),
|
|
1618
|
+
projectRoot,
|
|
1619
|
+
kind: input.kind,
|
|
1620
|
+
...cleanOptionalText(input.appName, 160) ? { appName: cleanOptionalText(input.appName, 160) } : {},
|
|
1621
|
+
...cleanOptionalText(input.packageName, 240) ? { packageName: cleanOptionalText(input.packageName, 240) } : {},
|
|
1622
|
+
...cleanOptionalText(input.versionName, 80) ? { versionName: cleanOptionalText(input.versionName, 80) } : {},
|
|
1623
|
+
...input.buildNumber !== void 0 ? { buildNumber: cleanOptionalText(String(input.buildNumber), 80) } : {},
|
|
1624
|
+
...cleanOptionalText(input.changelog, 4e3) ? { changelog: cleanOptionalText(input.changelog, 4e3) } : {},
|
|
1625
|
+
artifacts: await inspectArtifacts(input.artifactPaths),
|
|
1626
|
+
targets,
|
|
1627
|
+
outcome: input.outcome ?? inferOutcome(targets),
|
|
1628
|
+
startedAt: startedAt.toISOString(),
|
|
1629
|
+
finishedAt: finishedAt.toISOString(),
|
|
1630
|
+
durationMs: Math.max(0, finishedAt.getTime() - startedAt.getTime())
|
|
1631
|
+
};
|
|
1632
|
+
}
|
|
1633
|
+
function formatReleaseReceipt(receipt) {
|
|
1634
|
+
const lines = [
|
|
1635
|
+
`${receipt.appName || receipt.packageName || "\u5E94\u7528"} \xB7 ${receipt.kind === "build" ? "\u6784\u5EFA\u56DE\u6267" : "\u4E0A\u4F20\u56DE\u6267"}`,
|
|
1636
|
+
`\u7ED3\u679C\uFF1A${receipt.outcome}`,
|
|
1637
|
+
`\u7248\u672C\uFF1A${receipt.versionName || "-"} (${receipt.buildNumber || "-"})`,
|
|
1638
|
+
`\u5B8C\u6210\u65F6\u95F4\uFF1A${receipt.finishedAt}`
|
|
1639
|
+
];
|
|
1640
|
+
if (receipt.packageName) lines.push(`\u5305\u540D\uFF1A${receipt.packageName}`);
|
|
1641
|
+
if (receipt.changelog) lines.push(`\u66F4\u65B0\u65E5\u5FD7\uFF1A${receipt.changelog}`);
|
|
1642
|
+
for (const artifact of receipt.artifacts) {
|
|
1643
|
+
lines.push(
|
|
1644
|
+
`\u4EA7\u7269\uFF1A${artifact.path}${artifact.sizeBytes !== void 0 ? ` \xB7 ${artifact.sizeBytes} bytes` : ""}${artifact.sha256 ? ` \xB7 SHA-256 ${artifact.sha256}` : ""}`
|
|
1645
|
+
);
|
|
1646
|
+
}
|
|
1647
|
+
for (const target of receipt.targets) {
|
|
1648
|
+
lines.push(`${target.platform}\uFF1A${target.state} \xB7 ${target.message}`);
|
|
1649
|
+
if (target.remote?.consoleUrl) lines.push(`\u540E\u53F0\uFF1A${target.remote.consoleUrl}`);
|
|
1650
|
+
}
|
|
1651
|
+
return lines.join("\n");
|
|
1652
|
+
}
|
|
1653
|
+
var ReleaseReceiptStore = class {
|
|
1654
|
+
storageRoot;
|
|
1655
|
+
maxEntries;
|
|
1656
|
+
constructor(options = {}) {
|
|
1657
|
+
this.storageRoot = path3.resolve(options.storageRoot || path3.join(homedir(), ".flu-cli", "release-receipts"));
|
|
1658
|
+
this.maxEntries = Math.max(1, options.maxEntries ?? DEFAULT_RELEASE_RECEIPT_LIMIT);
|
|
1659
|
+
}
|
|
1660
|
+
async append(receipt) {
|
|
1661
|
+
await mkdir(this.storageRoot, { recursive: true, mode: 448 });
|
|
1662
|
+
await chmod(this.storageRoot, 448);
|
|
1663
|
+
const filePath = this.getFilePath(receipt.projectKey);
|
|
1664
|
+
const current = await this.readFile(filePath, receipt.projectKey);
|
|
1665
|
+
const receipts = [receipt, ...current.receipts.filter((item) => item.id !== receipt.id)].slice(
|
|
1666
|
+
0,
|
|
1667
|
+
this.maxEntries
|
|
1668
|
+
);
|
|
1669
|
+
const tempPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
1670
|
+
await writeFile(
|
|
1671
|
+
tempPath,
|
|
1672
|
+
`${JSON.stringify({
|
|
1673
|
+
schemaVersion: RELEASE_RECEIPT_SCHEMA_VERSION,
|
|
1674
|
+
projectKey: receipt.projectKey,
|
|
1675
|
+
receipts
|
|
1676
|
+
}, null, 2)}
|
|
1677
|
+
`,
|
|
1678
|
+
{ encoding: "utf8", mode: 384 }
|
|
1679
|
+
);
|
|
1680
|
+
await rename(tempPath, filePath);
|
|
1681
|
+
await chmod(filePath, 384);
|
|
1682
|
+
}
|
|
1683
|
+
async list(projectRoot, limit = 20) {
|
|
1684
|
+
const canonicalRoot = await canonicalizeProjectRoot(projectRoot);
|
|
1685
|
+
const projectKey = createProjectKey(canonicalRoot);
|
|
1686
|
+
const data = await this.readFile(this.getFilePath(projectKey), projectKey);
|
|
1687
|
+
return data.receipts.slice(0, Math.max(0, limit));
|
|
1688
|
+
}
|
|
1689
|
+
async save(input) {
|
|
1690
|
+
const receipt = await createReleaseReceipt(input);
|
|
1691
|
+
await this.append(receipt);
|
|
1692
|
+
return receipt;
|
|
1693
|
+
}
|
|
1694
|
+
getFilePath(projectKey) {
|
|
1695
|
+
return path3.join(this.storageRoot, `${projectKey}.json`);
|
|
1696
|
+
}
|
|
1697
|
+
async readFile(filePath, projectKey) {
|
|
1698
|
+
try {
|
|
1699
|
+
const raw = JSON.parse(await readFile(filePath, "utf8"));
|
|
1700
|
+
return {
|
|
1701
|
+
schemaVersion: RELEASE_RECEIPT_SCHEMA_VERSION,
|
|
1702
|
+
projectKey,
|
|
1703
|
+
receipts: Array.isArray(raw.receipts) ? raw.receipts : []
|
|
1704
|
+
};
|
|
1705
|
+
} catch (error) {
|
|
1706
|
+
if (error?.code === "ENOENT") {
|
|
1707
|
+
return { schemaVersion: RELEASE_RECEIPT_SCHEMA_VERSION, projectKey, receipts: [] };
|
|
1708
|
+
}
|
|
1709
|
+
throw new Error(`\u8BFB\u53D6\u53D1\u5E03\u56DE\u6267\u5931\u8D25: ${sanitizeReleaseMessage(error?.message || error)}`);
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
};
|
|
1713
|
+
function createProjectKey(projectRoot) {
|
|
1714
|
+
return createHash("sha256").update(path3.resolve(projectRoot)).digest("hex").slice(0, 24);
|
|
1715
|
+
}
|
|
1716
|
+
function createRemoteReceipt(result, data) {
|
|
1717
|
+
const identifiers = {};
|
|
1718
|
+
for (const key of REMOTE_IDENTIFIER_KEYS) {
|
|
1719
|
+
const value = data?.[key];
|
|
1720
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
1721
|
+
identifiers[key] = typeof value === "string" ? sanitizeReleaseMessage(value, 240) : value;
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
const consoleUrl = cleanHttpUrl(result.consoleUrl);
|
|
1725
|
+
const downloadUrl = cleanHttpUrl(result.url || result.link);
|
|
1726
|
+
if (!consoleUrl && !downloadUrl && Object.keys(identifiers).length === 0) return void 0;
|
|
1727
|
+
return {
|
|
1728
|
+
...consoleUrl ? { consoleUrl } : {},
|
|
1729
|
+
...downloadUrl ? { downloadUrl } : {},
|
|
1730
|
+
...Object.keys(identifiers).length > 0 ? { identifiers } : {}
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1733
|
+
function sanitizeTarget(target) {
|
|
1734
|
+
return {
|
|
1735
|
+
platform: sanitizeReleaseMessage(target.platform, 80) || "unknown",
|
|
1736
|
+
state: target.state,
|
|
1737
|
+
outcome: target.outcome,
|
|
1738
|
+
message: sanitizeReleaseMessage(target.message),
|
|
1739
|
+
...target.remote ? { remote: sanitizeRemote(target.remote) } : {},
|
|
1740
|
+
...cleanOptionalText(target.lastKnownRemoteStatus, 120) ? { lastKnownRemoteStatus: cleanOptionalText(target.lastKnownRemoteStatus, 120) } : {},
|
|
1741
|
+
...cleanOptionalText(target.checkedAt, 80) ? { checkedAt: cleanOptionalText(target.checkedAt, 80) } : {}
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
function sanitizeRemote(remote) {
|
|
1745
|
+
const identifiers = {};
|
|
1746
|
+
for (const [key, value] of Object.entries(remote.identifiers || {})) {
|
|
1747
|
+
if (!REMOTE_IDENTIFIER_KEYS.includes(key)) continue;
|
|
1748
|
+
if (typeof value === "string") identifiers[key] = sanitizeReleaseMessage(value, 240);
|
|
1749
|
+
if (typeof value === "number" || typeof value === "boolean") identifiers[key] = value;
|
|
1750
|
+
}
|
|
1751
|
+
const consoleUrl = cleanHttpUrl(remote.consoleUrl);
|
|
1752
|
+
const downloadUrl = cleanHttpUrl(remote.downloadUrl);
|
|
1753
|
+
return {
|
|
1754
|
+
...consoleUrl ? { consoleUrl } : {},
|
|
1755
|
+
...downloadUrl ? { downloadUrl } : {},
|
|
1756
|
+
...Object.keys(identifiers).length > 0 ? { identifiers } : {}
|
|
1757
|
+
};
|
|
1758
|
+
}
|
|
1759
|
+
async function inspectArtifacts(value) {
|
|
1760
|
+
const paths = Array.from(new Set(flattenArtifactPaths(value).map((item) => path3.resolve(item))));
|
|
1761
|
+
return Promise.all(paths.map(inspectArtifact));
|
|
1762
|
+
}
|
|
1763
|
+
async function inspectArtifact(filePath) {
|
|
1764
|
+
const base = { path: filePath, name: path3.basename(filePath) };
|
|
1765
|
+
try {
|
|
1766
|
+
const info = await stat(filePath);
|
|
1767
|
+
if (!info.isFile()) return { ...base, available: false };
|
|
1768
|
+
return {
|
|
1769
|
+
...base,
|
|
1770
|
+
sizeBytes: info.size,
|
|
1771
|
+
sha256: await hashFile(filePath),
|
|
1772
|
+
available: true
|
|
1773
|
+
};
|
|
1774
|
+
} catch {
|
|
1775
|
+
return { ...base, available: false };
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
function hashFile(filePath) {
|
|
1779
|
+
return new Promise((resolve, reject) => {
|
|
1780
|
+
const hash = createHash("sha256");
|
|
1781
|
+
const stream = createReadStream(filePath);
|
|
1782
|
+
stream.on("error", reject);
|
|
1783
|
+
stream.on("data", (chunk) => hash.update(chunk));
|
|
1784
|
+
stream.on("end", () => resolve(hash.digest("hex")));
|
|
1785
|
+
});
|
|
1786
|
+
}
|
|
1787
|
+
function flattenArtifactPaths(value) {
|
|
1788
|
+
if (typeof value === "string" && value.trim()) return [value];
|
|
1789
|
+
if (Array.isArray(value)) return value.flatMap(flattenArtifactPaths);
|
|
1790
|
+
if (isRecord(value)) return Object.values(value).flatMap(flattenArtifactPaths);
|
|
1791
|
+
return [];
|
|
1792
|
+
}
|
|
1793
|
+
function inferOutcome(targets) {
|
|
1794
|
+
if (targets.length === 0) return "failed";
|
|
1795
|
+
if (targets.every((target) => target.outcome === "cancelled")) return "cancelled";
|
|
1796
|
+
const successCount = targets.filter((target) => target.outcome === "success").length;
|
|
1797
|
+
if (successCount === targets.length) return "success";
|
|
1798
|
+
if (successCount > 0) return "partial";
|
|
1799
|
+
return "failed";
|
|
1800
|
+
}
|
|
1801
|
+
async function canonicalizeProjectRoot(projectRoot) {
|
|
1802
|
+
const resolved = path3.resolve(projectRoot);
|
|
1803
|
+
try {
|
|
1804
|
+
return await realpath(resolved);
|
|
1805
|
+
} catch {
|
|
1806
|
+
return resolved;
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
function toDate(value) {
|
|
1810
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
1811
|
+
return Number.isNaN(date.getTime()) ? /* @__PURE__ */ new Date() : date;
|
|
1812
|
+
}
|
|
1813
|
+
function cleanOptionalText(value, maxLength) {
|
|
1814
|
+
const cleaned = sanitizeReleaseMessage(value, maxLength);
|
|
1815
|
+
return cleaned || void 0;
|
|
1816
|
+
}
|
|
1817
|
+
function cleanHttpUrl(value) {
|
|
1818
|
+
if (typeof value !== "string") return void 0;
|
|
1819
|
+
try {
|
|
1820
|
+
const url = new URL(value);
|
|
1821
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return void 0;
|
|
1822
|
+
for (const key of Array.from(url.searchParams.keys())) {
|
|
1823
|
+
if (/token|secret|password|signature|credential|key/i.test(key)) {
|
|
1824
|
+
url.searchParams.set(key, "[\u5DF2\u8131\u654F]");
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
return url.toString();
|
|
1828
|
+
} catch {
|
|
1829
|
+
return void 0;
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
function isRecord(value) {
|
|
1833
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1487
1836
|
// src/adapters/pgyer_uploader.ts
|
|
1488
1837
|
var PgyerUploader = class _PgyerUploader {
|
|
1489
1838
|
name = "Pgyer";
|
|
@@ -1602,7 +1951,7 @@ var PgyerUploader = class _PgyerUploader {
|
|
|
1602
1951
|
|
|
1603
1952
|
// src/adapters/huawei_uploader.ts
|
|
1604
1953
|
import fs5 from "fs-extra";
|
|
1605
|
-
import
|
|
1954
|
+
import path4 from "path";
|
|
1606
1955
|
import { promisify } from "util";
|
|
1607
1956
|
import axios2 from "axios";
|
|
1608
1957
|
import FormData2 from "form-data";
|
|
@@ -1681,7 +2030,7 @@ var HuaweiUploader = class _HuaweiUploader {
|
|
|
1681
2030
|
const clientSecret = hwConfig.auth.clientSecret;
|
|
1682
2031
|
const appId = hwConfig.appId;
|
|
1683
2032
|
const releaseType = hwConfig.releaseType || "draft";
|
|
1684
|
-
const suffix =
|
|
2033
|
+
const suffix = path4.extname(filePath).substring(1);
|
|
1685
2034
|
const retryConfig = config.retry;
|
|
1686
2035
|
const tokenUrl = this.getTokenUrl(config);
|
|
1687
2036
|
const publishApiRoot = this.getApiRoot(config);
|
|
@@ -1707,8 +2056,8 @@ var HuaweiUploader = class _HuaweiUploader {
|
|
|
1707
2056
|
const uploadResults = [];
|
|
1708
2057
|
const uploadPromises = Object.entries(artifacts).map(async ([abi, p]) => {
|
|
1709
2058
|
if (!p || !fs5.existsSync(p)) return;
|
|
1710
|
-
const currentSuffix =
|
|
1711
|
-
logger.info(`[Huawei] \u6B63\u5728\u4E0A\u4F20 ${abi} \u5305: ${
|
|
2059
|
+
const currentSuffix = path4.extname(p).substring(1);
|
|
2060
|
+
logger.info(`[Huawei] \u6B63\u5728\u4E0A\u4F20 ${abi} \u5305: ${path4.basename(p)}`);
|
|
1712
2061
|
const uploadInfo = await retryStrategy.execute(
|
|
1713
2062
|
() => this.getUploadUrl(ctx, currentSuffix),
|
|
1714
2063
|
`Huawei get upload URL for ${abi}`
|
|
@@ -1724,7 +2073,7 @@ var HuaweiUploader = class _HuaweiUploader {
|
|
|
1724
2073
|
throw uploadFileRes.error || new Error(`File upload failed for ${abi}`);
|
|
1725
2074
|
}
|
|
1726
2075
|
uploadResults.push({
|
|
1727
|
-
fileName:
|
|
2076
|
+
fileName: path4.basename(p),
|
|
1728
2077
|
fileDestUrl: uploadFileRes.data[0].fileDestUrl
|
|
1729
2078
|
});
|
|
1730
2079
|
});
|
|
@@ -1896,7 +2245,7 @@ var HuaweiUploader = class _HuaweiUploader {
|
|
|
1896
2245
|
}
|
|
1897
2246
|
/** 通用文件上传(FormData 方式,无需 API 认证头) */
|
|
1898
2247
|
async executeUploadRequest(uploadUrl, authCode, filePath) {
|
|
1899
|
-
const fileName =
|
|
2248
|
+
const fileName = path4.basename(filePath);
|
|
1900
2249
|
const fileSize = fs5.statSync(filePath).size;
|
|
1901
2250
|
const form = new FormData2();
|
|
1902
2251
|
form.append("file", fs5.createReadStream(filePath), {
|
|
@@ -2063,7 +2412,7 @@ var HuaweiUploader = class _HuaweiUploader {
|
|
|
2063
2412
|
logger.warn(`Asset file not found, skipping: ${filePath}`);
|
|
2064
2413
|
continue;
|
|
2065
2414
|
}
|
|
2066
|
-
const suffix =
|
|
2415
|
+
const suffix = path4.extname(filePath).substring(1).toLowerCase();
|
|
2067
2416
|
if (!HUAWEI_SUPPORTED_SUFFIXES.image.includes(suffix)) {
|
|
2068
2417
|
logger.warn(`Unsupported file type ${suffix}, skipping: ${filePath}`);
|
|
2069
2418
|
continue;
|
|
@@ -2118,7 +2467,7 @@ var HuaweiUploader = class _HuaweiUploader {
|
|
|
2118
2467
|
|
|
2119
2468
|
// src/adapters/xiaomi_uploader.ts
|
|
2120
2469
|
import fs6 from "fs-extra";
|
|
2121
|
-
import
|
|
2470
|
+
import path5 from "path";
|
|
2122
2471
|
import axios3 from "axios";
|
|
2123
2472
|
import FormData3 from "form-data";
|
|
2124
2473
|
import crypto3 from "crypto";
|
|
@@ -2446,14 +2795,14 @@ ${lines.join("\n")}
|
|
|
2446
2795
|
if (arm64 && arm32) {
|
|
2447
2796
|
mainPath = arm64[1];
|
|
2448
2797
|
secondPath = arm32[1];
|
|
2449
|
-
logger.info(`[Xiaomi] \u8BC6\u522B\u5230\u591A\u67B6\u6784\u5206\u5305: \u4E3B\u5305(64\u4F4D)=${
|
|
2798
|
+
logger.info(`[Xiaomi] \u8BC6\u522B\u5230\u591A\u67B6\u6784\u5206\u5305: \u4E3B\u5305(64\u4F4D)=${path5.basename(mainPath)}, \u6B21\u5305(32\u4F4D)=${path5.basename(secondPath)}`);
|
|
2450
2799
|
} else {
|
|
2451
2800
|
mainPath = artifactEntries[0][1];
|
|
2452
2801
|
if (artifactEntries.length > 1) {
|
|
2453
2802
|
secondPath = artifactEntries[1][1];
|
|
2454
|
-
logger.info(`[Xiaomi] \u8BC6\u522B\u5230\u591A\u4E2A\u5305\uFF08\u975E\u6807\u51C6\u540D\uFF09\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u987A\u5E8F: \u4E3B\u5305=${
|
|
2803
|
+
logger.info(`[Xiaomi] \u8BC6\u522B\u5230\u591A\u4E2A\u5305\uFF08\u975E\u6807\u51C6\u540D\uFF09\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u987A\u5E8F: \u4E3B\u5305=${path5.basename(mainPath)}, \u6B21\u5305=${path5.basename(secondPath)}`);
|
|
2455
2804
|
} else {
|
|
2456
|
-
logger.info(`[Xiaomi] \u4F7F\u7528\u5355\u5305\u6A21\u5F0F: ${
|
|
2805
|
+
logger.info(`[Xiaomi] \u4F7F\u7528\u5355\u5305\u6A21\u5F0F: ${path5.basename(mainPath)}`);
|
|
2457
2806
|
}
|
|
2458
2807
|
}
|
|
2459
2808
|
const mainMD5 = await this.calculateFileMD5(mainPath);
|
|
@@ -2527,7 +2876,7 @@ ${lines.join("\n")}
|
|
|
2527
2876
|
|
|
2528
2877
|
// src/adapters/apple_store_uploader.ts
|
|
2529
2878
|
import fs7 from "fs-extra";
|
|
2530
|
-
import
|
|
2879
|
+
import path6 from "path";
|
|
2531
2880
|
import { exec as exec2 } from "child_process";
|
|
2532
2881
|
import { promisify as promisify2 } from "util";
|
|
2533
2882
|
import axios4 from "axios";
|
|
@@ -2693,7 +3042,7 @@ var AppleStoreUploader = class {
|
|
|
2693
3042
|
appPassword,
|
|
2694
3043
|
"-showTransferRate"
|
|
2695
3044
|
].join(" ");
|
|
2696
|
-
logger.info(`[Transporter] Uploading: ${
|
|
3045
|
+
logger.info(`[Transporter] Uploading: ${path6.basename(ipaPath)}`);
|
|
2697
3046
|
try {
|
|
2698
3047
|
const { stdout, stderr } = await execAsync(command, {
|
|
2699
3048
|
timeout: 30 * 60 * 1e3,
|
|
@@ -2744,7 +3093,7 @@ Stderr: ${error.stderr || ""}`);
|
|
|
2744
3093
|
});
|
|
2745
3094
|
command = innerArgs.join(" ");
|
|
2746
3095
|
}
|
|
2747
|
-
logger.info(`[altool] Uploading: ${
|
|
3096
|
+
logger.info(`[altool] Uploading: ${path6.basename(ipaPath)}`);
|
|
2748
3097
|
logger.info(`[altool] Command prefix: ${tool.command}`);
|
|
2749
3098
|
try {
|
|
2750
3099
|
const { stdout, stderr } = await execAsync(command, {
|
|
@@ -3328,7 +3677,7 @@ var TestFlightUploader = class _TestFlightUploader {
|
|
|
3328
3677
|
|
|
3329
3678
|
// src/adapters/adhoc_uploader.ts
|
|
3330
3679
|
import fs9 from "fs-extra";
|
|
3331
|
-
import
|
|
3680
|
+
import path7 from "path";
|
|
3332
3681
|
import { execSync } from "child_process";
|
|
3333
3682
|
var AdHocUploader = class {
|
|
3334
3683
|
name = "AdHoc";
|
|
@@ -3461,8 +3810,8 @@ var AdHocUploader = class {
|
|
|
3461
3810
|
* @param changelog 更新日志
|
|
3462
3811
|
*/
|
|
3463
3812
|
async createManifest(ipaPath, version, bundleId, changelog) {
|
|
3464
|
-
const ipaFileName =
|
|
3465
|
-
const appName = bundleId ||
|
|
3813
|
+
const ipaFileName = path7.basename(ipaPath);
|
|
3814
|
+
const appName = bundleId || path7.parse(ipaFileName).name;
|
|
3466
3815
|
const ipaUrl = `itms-services://?action=download-manifest&url=https://your-distribution-server.com/manifests/${ipaFileName}.plist`;
|
|
3467
3816
|
const manifestContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|
3468
3817
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
@@ -3521,14 +3870,14 @@ var AdHocUploader = class {
|
|
|
3521
3870
|
*/
|
|
3522
3871
|
async setupDistributionDirectory(ipaPath, manifestPath) {
|
|
3523
3872
|
const projectRoot = process.cwd();
|
|
3524
|
-
const distributionDir =
|
|
3873
|
+
const distributionDir = path7.join(projectRoot, "build", "adhoc-distribution");
|
|
3525
3874
|
await fs9.ensureDir(distributionDir);
|
|
3526
|
-
await fs9.ensureDir(
|
|
3527
|
-
await fs9.ensureDir(
|
|
3528
|
-
await fs9.ensureDir(
|
|
3529
|
-
const ipaFileName =
|
|
3530
|
-
await fs9.copy(ipaPath,
|
|
3531
|
-
await fs9.copy(manifestPath,
|
|
3875
|
+
await fs9.ensureDir(path7.join(distributionDir, "apps"));
|
|
3876
|
+
await fs9.ensureDir(path7.join(distributionDir, "manifests"));
|
|
3877
|
+
await fs9.ensureDir(path7.join(distributionDir, "icons"));
|
|
3878
|
+
const ipaFileName = path7.basename(ipaPath);
|
|
3879
|
+
await fs9.copy(ipaPath, path7.join(distributionDir, "apps", ipaFileName));
|
|
3880
|
+
await fs9.copy(manifestPath, path7.join(distributionDir, "manifests", `${ipaFileName}.plist`));
|
|
3532
3881
|
logger.info(`Distribution directory: ${distributionDir}`);
|
|
3533
3882
|
return distributionDir;
|
|
3534
3883
|
}
|
|
@@ -3538,7 +3887,7 @@ var AdHocUploader = class {
|
|
|
3538
3887
|
* @param bundleId Bundle ID
|
|
3539
3888
|
*/
|
|
3540
3889
|
generateInstallUrl(distributionPath, bundleId) {
|
|
3541
|
-
const manifestPath =
|
|
3890
|
+
const manifestPath = path7.join(distributionPath, "manifests", `*.plist`);
|
|
3542
3891
|
return `itms-services://?action=download-manifest&url=file://${manifestPath}`;
|
|
3543
3892
|
}
|
|
3544
3893
|
/**
|
|
@@ -3547,7 +3896,7 @@ var AdHocUploader = class {
|
|
|
3547
3896
|
* @param distributionPath 分发目录
|
|
3548
3897
|
*/
|
|
3549
3898
|
async generateQRCode(url, distributionPath) {
|
|
3550
|
-
const qrCodePath =
|
|
3899
|
+
const qrCodePath = path7.join(distributionPath, "install-qrcode.png");
|
|
3551
3900
|
try {
|
|
3552
3901
|
logger.info(`QR code would be saved to: ${qrCodePath}`);
|
|
3553
3902
|
logger.info(`Install URL: ${url}`);
|
|
@@ -3574,8 +3923,8 @@ var AdHocUploader = class {
|
|
|
3574
3923
|
logger.newLine();
|
|
3575
3924
|
logger.info("\u{1F4E6} Distribution Files:");
|
|
3576
3925
|
logger.info(` Directory: ${distributionPath}`);
|
|
3577
|
-
logger.info(` IPA: ${
|
|
3578
|
-
logger.info(` Manifest: ${
|
|
3926
|
+
logger.info(` IPA: ${path7.join(distributionPath, "apps", "*.ipa")}`);
|
|
3927
|
+
logger.info(` Manifest: ${path7.join(distributionPath, "manifests", "*.plist")}`);
|
|
3579
3928
|
logger.info(` QR Code: ${qrCodePath}`);
|
|
3580
3929
|
logger.newLine();
|
|
3581
3930
|
logger.info("\u{1F517} Installation Link:");
|
|
@@ -3634,7 +3983,7 @@ var AdHocUploader = class {
|
|
|
3634
3983
|
|
|
3635
3984
|
// src/adapters/harmony_uploader.ts
|
|
3636
3985
|
import fs10 from "fs-extra";
|
|
3637
|
-
import
|
|
3986
|
+
import path8 from "path";
|
|
3638
3987
|
import axios6 from "axios";
|
|
3639
3988
|
import FormData5 from "form-data";
|
|
3640
3989
|
var HarmonyUploader = class _HarmonyUploader {
|
|
@@ -3664,7 +4013,7 @@ var HarmonyUploader = class _HarmonyUploader {
|
|
|
3664
4013
|
const clientSecret = harmonyConfig.auth.clientSecret;
|
|
3665
4014
|
const appId = harmonyConfig.appId;
|
|
3666
4015
|
const releaseType = harmonyConfig.releaseType || "draft";
|
|
3667
|
-
const suffix =
|
|
4016
|
+
const suffix = path8.extname(filePath).substring(1);
|
|
3668
4017
|
const retryConfig = config.retry;
|
|
3669
4018
|
logger.info("Starting HarmonyOS AppGallery upload...");
|
|
3670
4019
|
try {
|
|
@@ -3781,7 +4130,7 @@ var HarmonyUploader = class _HarmonyUploader {
|
|
|
3781
4130
|
* @param filePath 文件路径
|
|
3782
4131
|
*/
|
|
3783
4132
|
async updateAppFileInfo(token, appId, authCode, filePath) {
|
|
3784
|
-
const fileName =
|
|
4133
|
+
const fileName = path8.basename(filePath);
|
|
3785
4134
|
const body = {
|
|
3786
4135
|
fileType: 7,
|
|
3787
4136
|
// 鸿蒙包的 fileType(需要根据华为最新文档确认)
|
|
@@ -3804,7 +4153,7 @@ var HarmonyUploader = class _HarmonyUploader {
|
|
|
3804
4153
|
|
|
3805
4154
|
// src/adapters/oppo_uploader.ts
|
|
3806
4155
|
import fs11 from "fs-extra";
|
|
3807
|
-
import
|
|
4156
|
+
import path9 from "path";
|
|
3808
4157
|
import axios7 from "axios";
|
|
3809
4158
|
import FormData6 from "form-data";
|
|
3810
4159
|
import crypto4 from "crypto";
|
|
@@ -3845,7 +4194,7 @@ var OppoUploader = class _OppoUploader {
|
|
|
3845
4194
|
* 尝试从文件名推断包名
|
|
3846
4195
|
*/
|
|
3847
4196
|
inferPackageName(filePath) {
|
|
3848
|
-
const fileName =
|
|
4197
|
+
const fileName = path9.basename(filePath);
|
|
3849
4198
|
const match = fileName.match(/^([\w.]+?)(?:-(?:release|debug|unsigned))?\.apk$/i);
|
|
3850
4199
|
if (match && match[1].includes(".")) {
|
|
3851
4200
|
return match[1];
|
|
@@ -4126,7 +4475,7 @@ var OppoUploader = class _OppoUploader {
|
|
|
4126
4475
|
|
|
4127
4476
|
// src/adapters/vivo_uploader.ts
|
|
4128
4477
|
import fs12 from "fs-extra";
|
|
4129
|
-
import
|
|
4478
|
+
import path10 from "path";
|
|
4130
4479
|
import axios8 from "axios";
|
|
4131
4480
|
import FormData7 from "form-data";
|
|
4132
4481
|
import crypto5 from "crypto";
|
|
@@ -4158,7 +4507,7 @@ var VivoUploader = class _VivoUploader {
|
|
|
4158
4507
|
const uploadPromises = Object.entries(artifacts).map(async ([abi, p]) => {
|
|
4159
4508
|
if (!p || !fs12.existsSync(p)) throw new Error(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${p}`);
|
|
4160
4509
|
const md5 = await this.calculateFileMD5(p);
|
|
4161
|
-
logger.info(`[vivo] \u6B63\u5728\u5904\u7406 ${abi} \u67B6\u6784\u5305: ${
|
|
4510
|
+
logger.info(`[vivo] \u6B63\u5728\u5904\u7406 ${abi} \u67B6\u6784\u5305: ${path10.basename(p)}`);
|
|
4162
4511
|
const serialnumber = await this.uploadApk(accessKey, accessSecret, p, vivoConfig.packageName || "", md5);
|
|
4163
4512
|
const pkgInfo = await parsePackageInfo(p, { androidSdkPath: config.globalSettings?.androidSdkPath });
|
|
4164
4513
|
const nativeCodes = pkgInfo?.nativeCode || [];
|
|
@@ -4222,7 +4571,7 @@ var VivoUploader = class _VivoUploader {
|
|
|
4222
4571
|
const form = new FormData7();
|
|
4223
4572
|
Object.entries(params).forEach(([k, v]) => form.append(k, v));
|
|
4224
4573
|
form.append("sign", sign);
|
|
4225
|
-
form.append("file", fs12.createReadStream(filePath), { filename:
|
|
4574
|
+
form.append("file", fs12.createReadStream(filePath), { filename: path10.basename(filePath) });
|
|
4226
4575
|
const res = await axios8.post(_VivoUploader.API_BASE, form, { headers: form.getHeaders(), timeout: 3e5 });
|
|
4227
4576
|
if (res.data.code !== 0) throw new Error(`\u4E0A\u4F20\u5305\u5931\u8D25: ${res.data.msg}`);
|
|
4228
4577
|
return res.data.data?.serialnumber || res.data.data;
|
|
@@ -4262,7 +4611,7 @@ var VivoUploader = class _VivoUploader {
|
|
|
4262
4611
|
|
|
4263
4612
|
// src/adapters/tencent_uploader.ts
|
|
4264
4613
|
import fs13 from "fs-extra";
|
|
4265
|
-
import
|
|
4614
|
+
import path11 from "path";
|
|
4266
4615
|
import axios9 from "axios";
|
|
4267
4616
|
import crypto6 from "crypto";
|
|
4268
4617
|
var TencentUploader = class _TencentUploader {
|
|
@@ -4302,7 +4651,7 @@ var TencentUploader = class _TencentUploader {
|
|
|
4302
4651
|
const uploadPromises = Object.entries(artifacts).map(async ([abi, p]) => {
|
|
4303
4652
|
if (!p || !fs13.existsSync(p)) throw new Error(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${p}`);
|
|
4304
4653
|
const stats = fs13.statSync(p);
|
|
4305
|
-
const fileName =
|
|
4654
|
+
const fileName = path11.basename(p);
|
|
4306
4655
|
logger.info(`[Tencent] \u6B63\u5728\u5904\u7406 ${abi} \u67B6\u6784\u5305: ${fileName}`);
|
|
4307
4656
|
const uploadInfo = await this.getFileUploadInfo(auth, stats.size, fileName, pkgName);
|
|
4308
4657
|
await this.uploadToCos(uploadInfo.pre_sign_url, p, stats.size);
|
|
@@ -4505,26 +4854,255 @@ var TencentUploader = class _TencentUploader {
|
|
|
4505
4854
|
return res.data;
|
|
4506
4855
|
}
|
|
4507
4856
|
};
|
|
4857
|
+
|
|
4858
|
+
// src/adapters/google_play_uploader.ts
|
|
4859
|
+
import fs14 from "fs-extra";
|
|
4860
|
+
import path12 from "path";
|
|
4861
|
+
import axios10 from "axios";
|
|
4862
|
+
import { GoogleAuth } from "google-auth-library";
|
|
4863
|
+
var ANDROID_PUBLISHER_SCOPE = "https://www.googleapis.com/auth/androidpublisher";
|
|
4864
|
+
var PUBLISH_API_ROOT = "https://androidpublisher.googleapis.com/androidpublisher/v3";
|
|
4865
|
+
var UPLOAD_API_ROOT = "https://androidpublisher.googleapis.com/upload/androidpublisher/v3";
|
|
4866
|
+
var GOOGLE_PLAY_CONSOLE_URL = "https://play.google.com/console/developers";
|
|
4867
|
+
var GOOGLE_PLAY_INTERNAL_API_TRACK = "internal";
|
|
4868
|
+
var UPLOAD_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
4869
|
+
var GooglePlayUploader = class {
|
|
4870
|
+
name = "Google Play";
|
|
4871
|
+
httpClient;
|
|
4872
|
+
getAccessToken;
|
|
4873
|
+
constructor(dependencies = {}) {
|
|
4874
|
+
this.httpClient = dependencies.httpClient || axios10;
|
|
4875
|
+
this.getAccessToken = dependencies.getAccessToken || this.createAccessToken.bind(this);
|
|
4876
|
+
}
|
|
4877
|
+
validateConfig(config) {
|
|
4878
|
+
const googleConfig = config?.publish?.google_play;
|
|
4879
|
+
if (!googleConfig?.enable) return false;
|
|
4880
|
+
if (!googleConfig.packageName || typeof googleConfig.packageName !== "string") {
|
|
4881
|
+
logger.error("Google Play \u914D\u7F6E\u7F3A\u5C11 packageName");
|
|
4882
|
+
return false;
|
|
4883
|
+
}
|
|
4884
|
+
const track = googleConfig.track || "internal";
|
|
4885
|
+
if (track !== "internal") {
|
|
4886
|
+
logger.error("Google Play \u9996\u7248\u4EC5\u652F\u6301 internal \u8F68\u9053");
|
|
4887
|
+
return false;
|
|
4888
|
+
}
|
|
4889
|
+
const auth = googleConfig.auth;
|
|
4890
|
+
const hasCredentialContent = typeof auth?.serviceAccountJsonContent === "string" && auth.serviceAccountJsonContent.trim().length > 0;
|
|
4891
|
+
const credentialPath = auth?.serviceAccountJsonPath;
|
|
4892
|
+
const hasCredentialPath = typeof credentialPath === "string" && credentialPath.trim().length > 0 && fs14.existsSync(path12.resolve(credentialPath));
|
|
4893
|
+
if (!hasCredentialContent && !hasCredentialPath) {
|
|
4894
|
+
logger.error("Google Play \u914D\u7F6E\u7F3A\u5C11\u6709\u6548\u7684\u670D\u52A1\u8D26\u53F7 JSON \u6587\u4EF6\u6216\u5185\u5BB9");
|
|
4895
|
+
return false;
|
|
4896
|
+
}
|
|
4897
|
+
return true;
|
|
4898
|
+
}
|
|
4899
|
+
canHandle(filePath) {
|
|
4900
|
+
return path12.extname(filePath).toLowerCase() === ".aab";
|
|
4901
|
+
}
|
|
4902
|
+
async upload(filePath, config, _metadata) {
|
|
4903
|
+
if (!this.validateConfig(config)) {
|
|
4904
|
+
return this.failure("Google Play \u914D\u7F6E\u65E0\u6548\uFF0C\u8BF7\u68C0\u67E5\u5305\u540D\u3001internal \u8F68\u9053\u548C\u670D\u52A1\u8D26\u53F7\u6587\u4EF6");
|
|
4905
|
+
}
|
|
4906
|
+
if (!this.canHandle(filePath)) {
|
|
4907
|
+
return this.failure("Google Play \u4EC5\u63A5\u53D7 Android App Bundle\uFF08.aab\uFF09");
|
|
4908
|
+
}
|
|
4909
|
+
if (!fs14.existsSync(filePath)) {
|
|
4910
|
+
return this.failure(`\u672A\u627E\u5230\u5F85\u4E0A\u4F20\u7684 AAB\uFF1A${filePath}`);
|
|
4911
|
+
}
|
|
4912
|
+
const googleConfig = config.publish.google_play;
|
|
4913
|
+
const packageName = googleConfig.packageName.trim();
|
|
4914
|
+
const encodedPackageName = encodeURIComponent(packageName);
|
|
4915
|
+
let editId;
|
|
4916
|
+
let accessToken;
|
|
4917
|
+
let committed = false;
|
|
4918
|
+
let stage = "create_edit";
|
|
4919
|
+
logger.info(`\u51C6\u5907\u5C06 ${path12.basename(filePath)} \u4E0A\u4F20\u5230 Google Play internal \u8349\u7A3F`);
|
|
4920
|
+
try {
|
|
4921
|
+
accessToken = await this.getAccessToken(googleConfig.auth);
|
|
4922
|
+
const requestConfig = {
|
|
4923
|
+
headers: { Authorization: `Bearer ${accessToken}` }
|
|
4924
|
+
};
|
|
4925
|
+
const editResponse = await this.httpClient.post(
|
|
4926
|
+
`${PUBLISH_API_ROOT}/applications/${encodedPackageName}/edits`,
|
|
4927
|
+
{},
|
|
4928
|
+
requestConfig
|
|
4929
|
+
);
|
|
4930
|
+
editId = editResponse.data.id;
|
|
4931
|
+
if (!editId) {
|
|
4932
|
+
throw new Error("Google Play \u672A\u8FD4\u56DE editId");
|
|
4933
|
+
}
|
|
4934
|
+
stage = "upload_bundle";
|
|
4935
|
+
const encodedEditId = encodeURIComponent(editId);
|
|
4936
|
+
const bundleResponse = await this.httpClient.post(
|
|
4937
|
+
`${UPLOAD_API_ROOT}/applications/${encodedPackageName}/edits/${encodedEditId}/bundles?uploadType=media`,
|
|
4938
|
+
fs14.createReadStream(filePath),
|
|
4939
|
+
{
|
|
4940
|
+
timeout: UPLOAD_TIMEOUT_MS,
|
|
4941
|
+
maxBodyLength: Infinity,
|
|
4942
|
+
maxContentLength: Infinity,
|
|
4943
|
+
headers: {
|
|
4944
|
+
Authorization: `Bearer ${accessToken}`,
|
|
4945
|
+
"Content-Type": "application/octet-stream",
|
|
4946
|
+
"Content-Length": fs14.statSync(filePath).size
|
|
4947
|
+
}
|
|
4948
|
+
}
|
|
4949
|
+
);
|
|
4950
|
+
const versionCode = bundleResponse.data.versionCode;
|
|
4951
|
+
if (!Number.isInteger(versionCode)) {
|
|
4952
|
+
throw new Error("Google Play \u4E0A\u4F20\u6210\u529F\u4F46\u672A\u8FD4\u56DE\u6709\u6548\u7684 versionCode");
|
|
4953
|
+
}
|
|
4954
|
+
stage = "update_internal_track";
|
|
4955
|
+
await this.httpClient.put(
|
|
4956
|
+
`${PUBLISH_API_ROOT}/applications/${encodedPackageName}/edits/${encodedEditId}/tracks/${GOOGLE_PLAY_INTERNAL_API_TRACK}`,
|
|
4957
|
+
{
|
|
4958
|
+
track: GOOGLE_PLAY_INTERNAL_API_TRACK,
|
|
4959
|
+
releases: [
|
|
4960
|
+
{
|
|
4961
|
+
versionCodes: [String(versionCode)],
|
|
4962
|
+
status: "draft"
|
|
4963
|
+
}
|
|
4964
|
+
]
|
|
4965
|
+
},
|
|
4966
|
+
requestConfig
|
|
4967
|
+
);
|
|
4968
|
+
stage = "validate_edit";
|
|
4969
|
+
await this.httpClient.post(
|
|
4970
|
+
`${PUBLISH_API_ROOT}/applications/${encodedPackageName}/edits/${encodedEditId}:validate`,
|
|
4971
|
+
{},
|
|
4972
|
+
requestConfig
|
|
4973
|
+
);
|
|
4974
|
+
stage = "commit_edit";
|
|
4975
|
+
await this.httpClient.post(
|
|
4976
|
+
`${PUBLISH_API_ROOT}/applications/${encodedPackageName}/edits/${encodedEditId}:commit`,
|
|
4977
|
+
{},
|
|
4978
|
+
requestConfig
|
|
4979
|
+
);
|
|
4980
|
+
committed = true;
|
|
4981
|
+
logger.success(`Google Play internal \u8349\u7A3F\u5DF2\u521B\u5EFA\uFF08versionCode ${versionCode}\uFF09`);
|
|
4982
|
+
return {
|
|
4983
|
+
platform: "google_play",
|
|
4984
|
+
success: true,
|
|
4985
|
+
message: `AAB \u5DF2\u4E0A\u4F20\u5230 Google Play internal \u8349\u7A3F\uFF08versionCode ${versionCode}\uFF09`,
|
|
4986
|
+
consoleUrl: GOOGLE_PLAY_CONSOLE_URL,
|
|
4987
|
+
data: {
|
|
4988
|
+
editId,
|
|
4989
|
+
versionCode,
|
|
4990
|
+
track: "internal",
|
|
4991
|
+
apiTrack: GOOGLE_PLAY_INTERNAL_API_TRACK,
|
|
4992
|
+
status: "draft",
|
|
4993
|
+
committed: true
|
|
4994
|
+
}
|
|
4995
|
+
};
|
|
4996
|
+
} catch (error) {
|
|
4997
|
+
if (editId && accessToken && !committed) {
|
|
4998
|
+
await this.deleteEditBestEffort(encodedPackageName, editId, accessToken);
|
|
4999
|
+
}
|
|
5000
|
+
const message = this.describeError(error, stage);
|
|
5001
|
+
logger.error(`Google Play \u4E0A\u4F20\u5931\u8D25\uFF1A${message}`);
|
|
5002
|
+
return this.failure(message, error);
|
|
5003
|
+
}
|
|
5004
|
+
}
|
|
5005
|
+
async createAccessToken(authConfig) {
|
|
5006
|
+
let credentials;
|
|
5007
|
+
if (authConfig.serviceAccountJsonContent?.trim()) {
|
|
5008
|
+
try {
|
|
5009
|
+
credentials = JSON.parse(authConfig.serviceAccountJsonContent);
|
|
5010
|
+
} catch {
|
|
5011
|
+
throw new Error("\u670D\u52A1\u8D26\u53F7 JSON \u5185\u5BB9\u683C\u5F0F\u65E0\u6548");
|
|
5012
|
+
}
|
|
5013
|
+
}
|
|
5014
|
+
const keyFile = !credentials && authConfig.serviceAccountJsonPath ? path12.resolve(authConfig.serviceAccountJsonPath) : void 0;
|
|
5015
|
+
const auth = new GoogleAuth({
|
|
5016
|
+
scopes: [ANDROID_PUBLISHER_SCOPE],
|
|
5017
|
+
keyFile,
|
|
5018
|
+
credentials
|
|
5019
|
+
});
|
|
5020
|
+
const token = await auth.getAccessToken();
|
|
5021
|
+
if (!token) {
|
|
5022
|
+
throw new Error("Google Play \u8BA4\u8BC1\u672A\u8FD4\u56DE\u8BBF\u95EE\u4EE4\u724C");
|
|
5023
|
+
}
|
|
5024
|
+
return token;
|
|
5025
|
+
}
|
|
5026
|
+
async deleteEditBestEffort(encodedPackageName, editId, accessToken) {
|
|
5027
|
+
try {
|
|
5028
|
+
await this.httpClient.delete(
|
|
5029
|
+
`${PUBLISH_API_ROOT}/applications/${encodedPackageName}/edits/${encodeURIComponent(editId)}`,
|
|
5030
|
+
{ headers: { Authorization: `Bearer ${accessToken}` } }
|
|
5031
|
+
);
|
|
5032
|
+
} catch {
|
|
5033
|
+
logger.warn("Google Play \u672A\u63D0\u4EA4 edit \u7684\u81EA\u52A8\u6E05\u7406\u5931\u8D25\uFF0C\u8BF7\u5728 Play Console \u4E2D\u68C0\u67E5\u8349\u7A3F\u72B6\u6001");
|
|
5034
|
+
}
|
|
5035
|
+
}
|
|
5036
|
+
describeError(error, stage) {
|
|
5037
|
+
const status = error?.response?.status;
|
|
5038
|
+
const rawMessage = error?.response?.data?.error?.message || error?.response?.data?.message || error?.message || "\u672A\u77E5\u9519\u8BEF";
|
|
5039
|
+
const safeMessage = this.redactAndTruncate(String(rawMessage));
|
|
5040
|
+
if (/version\s*code|versionCode/i.test(safeMessage)) {
|
|
5041
|
+
return `Google Play \u62D2\u7EDD\u8BE5 versionCode\uFF0C\u8BF7\u63D0\u9AD8 Flutter \u6784\u5EFA\u53F7\u540E\u91CD\u65B0\u751F\u6210 AAB\uFF1A${safeMessage}`;
|
|
5042
|
+
}
|
|
5043
|
+
if (status === 401) {
|
|
5044
|
+
return "Google Play \u8BA4\u8BC1\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u670D\u52A1\u8D26\u53F7 JSON \u662F\u5426\u6709\u6548";
|
|
5045
|
+
}
|
|
5046
|
+
if (status === 403) {
|
|
5047
|
+
return "Google Play \u62D2\u7EDD\u8BBF\u95EE\uFF0C\u8BF7\u786E\u8BA4\u670D\u52A1\u8D26\u53F7\u5DF2\u52A0\u5165 Play Console \u5E76\u5177\u6709\u5185\u90E8\u6D4B\u8BD5\u7248\u672C\u4E0A\u4F20\u6743\u9650";
|
|
5048
|
+
}
|
|
5049
|
+
if (status === 404) {
|
|
5050
|
+
if (stage === "update_internal_track") {
|
|
5051
|
+
return "Google Play \u4E2D\u672A\u627E\u5230 internal \u5185\u90E8\u6D4B\u8BD5\u8F68\u9053\uFF0C\u8BF7\u786E\u8BA4\u5E94\u7528\u5DF2\u5728 Play Console \u521B\u5EFA\u5185\u90E8\u6D4B\u8BD5\u8F68\u9053";
|
|
5052
|
+
}
|
|
5053
|
+
if (stage !== "create_edit") {
|
|
5054
|
+
return `Google Play API \u5728 ${stage} \u9636\u6BB5\u672A\u627E\u5230\u76EE\u6807\u8D44\u6E90\uFF1A${safeMessage}`;
|
|
5055
|
+
}
|
|
5056
|
+
return "Google Play \u4E2D\u672A\u627E\u5230\u53EF\u4F9B API \u7F16\u8F91\u7684\u5E94\u7528\u3002\u8BF7\u786E\u8BA4 packageName \u5B8C\u5168\u4E00\u81F4\uFF1B\u82E5\u8FD9\u662F\u9996\u6B21\u4E0A\u4F20\uFF0C\u8BF7\u5148\u5728 Play Console \u624B\u5DE5\u4E0A\u4F20\u5E76\u4FDD\u5B58\u4E00\u4E2A APK/AAB\uFF0C\u518D\u91CD\u8BD5 API \u4E0A\u4F20";
|
|
5057
|
+
}
|
|
5058
|
+
if (error?.code === "ECONNABORTED" || /timeout/i.test(safeMessage)) {
|
|
5059
|
+
return "Google Play \u4E0A\u4F20\u8D85\u65F6\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u540E\u91CD\u8BD5\uFF1B\u672C\u5730 AAB \u672A\u88AB\u5220\u9664";
|
|
5060
|
+
}
|
|
5061
|
+
return `Google Play API \u8BF7\u6C42\u5931\u8D25${status ? `\uFF08HTTP ${status}\uFF09` : ""}\uFF1A${safeMessage}`;
|
|
5062
|
+
}
|
|
5063
|
+
redactAndTruncate(message) {
|
|
5064
|
+
return message.replace(/-----BEGIN PRIVATE KEY-----[\s\S]*?-----END PRIVATE KEY-----/gi, "[\u5DF2\u8131\u654F\u79C1\u94A5]").replace(/Bearer\s+[^\s"']+/gi, "Bearer [\u5DF2\u8131\u654F]").replace(/"private_key"\s*:\s*"[^"]*"/gi, '"private_key":"[\u5DF2\u8131\u654F]"').replace(/"access_token"\s*:\s*"[^"]*"/gi, '"access_token":"[\u5DF2\u8131\u654F]"').slice(0, 500);
|
|
5065
|
+
}
|
|
5066
|
+
failure(message, error) {
|
|
5067
|
+
return {
|
|
5068
|
+
platform: "google_play",
|
|
5069
|
+
success: false,
|
|
5070
|
+
message,
|
|
5071
|
+
// 原始 Axios 错误可能包含 Authorization 请求头,只返回脱敏后的 Error。
|
|
5072
|
+
error: error ? new Error(message) : void 0
|
|
5073
|
+
};
|
|
5074
|
+
}
|
|
5075
|
+
};
|
|
4508
5076
|
export {
|
|
4509
5077
|
AdHocUploader,
|
|
4510
5078
|
AppleJWTGenerator,
|
|
4511
5079
|
AppleStoreUploader,
|
|
4512
5080
|
ConfigValidator,
|
|
5081
|
+
DEFAULT_RELEASE_RECEIPT_LIMIT,
|
|
4513
5082
|
DEFAULT_RETRY_CONFIG,
|
|
4514
5083
|
EnvValidator,
|
|
4515
5084
|
ErrorCategory,
|
|
5085
|
+
GooglePlayUploader,
|
|
4516
5086
|
HarmonyUploader,
|
|
4517
5087
|
HuaweiUploader,
|
|
4518
5088
|
OppoUploader,
|
|
4519
5089
|
PgyerUploader,
|
|
5090
|
+
RELEASE_RECEIPT_SCHEMA_VERSION,
|
|
5091
|
+
ReleaseReceiptStore,
|
|
4520
5092
|
RetryStrategy,
|
|
4521
5093
|
TencentUploader,
|
|
4522
5094
|
TestFlightUploader,
|
|
4523
5095
|
UploadManager,
|
|
4524
5096
|
VivoUploader,
|
|
4525
5097
|
XiaomiUploader,
|
|
5098
|
+
createProjectKey,
|
|
5099
|
+
createReleaseReceipt,
|
|
4526
5100
|
createUploadManager,
|
|
4527
5101
|
createUploadRetryStrategy,
|
|
4528
|
-
|
|
5102
|
+
createUploadTargets,
|
|
5103
|
+
formatReleaseReceipt,
|
|
5104
|
+
parsePackageInfo,
|
|
5105
|
+
resolveReleaseProjectRoot,
|
|
5106
|
+
sanitizeReleaseMessage
|
|
4529
5107
|
};
|
|
4530
5108
|
//# sourceMappingURL=index.js.map
|