@huoye/app-ship 0.1.2 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -34,22 +34,32 @@ __export(index_exports, {
34
34
  AppleJWTGenerator: () => AppleJWTGenerator,
35
35
  AppleStoreUploader: () => AppleStoreUploader,
36
36
  ConfigValidator: () => ConfigValidator,
37
+ DEFAULT_RELEASE_RECEIPT_LIMIT: () => DEFAULT_RELEASE_RECEIPT_LIMIT,
37
38
  DEFAULT_RETRY_CONFIG: () => DEFAULT_RETRY_CONFIG,
38
39
  EnvValidator: () => EnvValidator,
39
40
  ErrorCategory: () => ErrorCategory,
41
+ GooglePlayUploader: () => GooglePlayUploader,
40
42
  HarmonyUploader: () => HarmonyUploader,
41
43
  HuaweiUploader: () => HuaweiUploader,
42
44
  OppoUploader: () => OppoUploader,
43
45
  PgyerUploader: () => PgyerUploader,
46
+ RELEASE_RECEIPT_SCHEMA_VERSION: () => RELEASE_RECEIPT_SCHEMA_VERSION,
47
+ ReleaseReceiptStore: () => ReleaseReceiptStore,
44
48
  RetryStrategy: () => RetryStrategy,
45
49
  TencentUploader: () => TencentUploader,
46
50
  TestFlightUploader: () => TestFlightUploader,
47
51
  UploadManager: () => UploadManager,
48
52
  VivoUploader: () => VivoUploader,
49
53
  XiaomiUploader: () => XiaomiUploader,
54
+ createProjectKey: () => createProjectKey,
55
+ createReleaseReceipt: () => createReleaseReceipt,
50
56
  createUploadManager: () => createUploadManager,
51
57
  createUploadRetryStrategy: () => createUploadRetryStrategy,
52
- parsePackageInfo: () => parsePackageInfo
58
+ createUploadTargets: () => createUploadTargets,
59
+ formatReleaseReceipt: () => formatReleaseReceipt,
60
+ parsePackageInfo: () => parsePackageInfo,
61
+ resolveReleaseProjectRoot: () => resolveReleaseProjectRoot,
62
+ sanitizeReleaseMessage: () => sanitizeReleaseMessage
53
63
  });
54
64
  module.exports = __toCommonJS(index_exports);
55
65
 
@@ -600,16 +610,18 @@ var ConfigValidator = class {
600
610
  logger.error(`Config file not found: ${filePath}`);
601
611
  return null;
602
612
  }
603
- let content = import_fs_extra.default.readFileSync(filePath, "utf8");
604
- const envRegex = /\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
605
- const matches = [...content.matchAll(envRegex)];
606
- const requiredEnvs = Array.from(new Set(matches.map((m) => m[1])));
607
- if (!EnvValidator.validate(requiredEnvs)) {
608
- return null;
609
- }
610
- content = content.replace(envRegex, (_, varName) => process.env[varName] || "");
611
613
  try {
612
- const config = import_js_yaml.default.load(content);
614
+ const content = import_fs_extra.default.readFileSync(filePath, "utf8");
615
+ const rawConfig = import_js_yaml.default.load(content);
616
+ if (!rawConfig || typeof rawConfig !== "object") {
617
+ logger.error("Config file must contain a YAML object");
618
+ return null;
619
+ }
620
+ const requiredEnvs = this.collectRequiredEnvVars(rawConfig);
621
+ if (!EnvValidator.validate(requiredEnvs)) {
622
+ return null;
623
+ }
624
+ const config = this.resolveEnvVars(rawConfig);
613
625
  if (!this.validateSchema(config)) {
614
626
  return null;
615
627
  }
@@ -619,15 +631,71 @@ var ConfigValidator = class {
619
631
  return null;
620
632
  }
621
633
  }
634
+ /**
635
+ * 只收集本次实际启用配置中的环境变量,避免禁用平台阻塞其它上传任务。
636
+ */
637
+ static collectRequiredEnvVars(config) {
638
+ const activeConfig = {
639
+ ...config,
640
+ publish: Object.fromEntries(
641
+ Object.entries(config.publish || {}).filter(([, value]) => value?.enable === true)
642
+ )
643
+ };
644
+ const requiredEnvs = /* @__PURE__ */ new Set();
645
+ const envRegex = /\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
646
+ const visit = (value) => {
647
+ if (typeof value === "string") {
648
+ for (const match of value.matchAll(envRegex)) {
649
+ requiredEnvs.add(match[1]);
650
+ }
651
+ return;
652
+ }
653
+ if (Array.isArray(value)) {
654
+ value.forEach(visit);
655
+ return;
656
+ }
657
+ if (value && typeof value === "object") {
658
+ Object.values(value).forEach(visit);
659
+ }
660
+ };
661
+ visit(activeConfig);
662
+ return Array.from(requiredEnvs);
663
+ }
664
+ /** 在内存中递归解析环境变量,不重新拼接 YAML,避免密钥字符破坏 YAML 结构。 */
665
+ static resolveEnvVars(value) {
666
+ if (typeof value === "string") {
667
+ return value.replace(
668
+ /\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g,
669
+ (_, varName) => process.env[varName] || ""
670
+ );
671
+ }
672
+ if (Array.isArray(value)) {
673
+ return value.map((item) => this.resolveEnvVars(item));
674
+ }
675
+ if (value && typeof value === "object") {
676
+ return Object.fromEntries(
677
+ Object.entries(value).map(([key, item]) => [key, this.resolveEnvVars(item)])
678
+ );
679
+ }
680
+ return value;
681
+ }
622
682
  /**
623
683
  * 验证配置 Schema
624
684
  * @param config 配置对象
625
685
  */
626
686
  static validateSchema(config) {
627
687
  if (config.publish?.app_store?.enable) {
688
+ const mode = config.publish.app_store.mode || "app_store";
628
689
  const auth = config.publish.app_store.auth;
629
- if (!auth?.apiKeyId || !auth?.apiIssuerId) {
630
- logger.error("App Store enabled but missing auth info (apiKeyId, apiIssuerId)");
690
+ const hasCliAuth = Boolean(auth?.appleId && auth?.appPassword);
691
+ const hasApiAuth = Boolean(
692
+ auth?.apiKeyId && auth?.apiIssuerId && (auth?.privateKeyPath || auth?.privateKeyContent)
693
+ );
694
+ const hasRequiredAuth = mode === "adhoc" || (mode === "testflight" ? hasApiAuth : hasCliAuth || hasApiAuth);
695
+ if (!hasRequiredAuth) {
696
+ logger.error(
697
+ mode === "testflight" ? "TestFlight enabled but missing complete API key credentials" : "App Store enabled but missing Apple ID credentials or complete API key credentials"
698
+ );
631
699
  return false;
632
700
  }
633
701
  }
@@ -673,6 +741,20 @@ var ConfigValidator = class {
673
741
  return false;
674
742
  }
675
743
  }
744
+ if (config.publish?.google_play?.enable) {
745
+ const googlePlay = config.publish.google_play;
746
+ const auth = googlePlay.auth;
747
+ if (!googlePlay.packageName || !auth?.serviceAccountJsonPath && !auth?.serviceAccountJsonContent) {
748
+ logger.error(
749
+ "Google Play enabled but missing packageName or service account JSON credentials"
750
+ );
751
+ return false;
752
+ }
753
+ if (googlePlay.track && googlePlay.track !== "internal") {
754
+ logger.error("Google Play first release only supports the internal track");
755
+ return false;
756
+ }
757
+ }
676
758
  return true;
677
759
  }
678
760
  };
@@ -1014,6 +1096,7 @@ var UploadManager = class {
1014
1096
  this.registerUploader("oppo", new OppoUploader());
1015
1097
  this.registerUploader("vivo", new VivoUploader());
1016
1098
  this.registerUploader("tencent", new TencentUploader());
1099
+ this.registerUploader("google_play", new GooglePlayUploader());
1017
1100
  }
1018
1101
  /**
1019
1102
  * 注册上传器
@@ -1032,6 +1115,8 @@ var UploadManager = class {
1032
1115
  if (config?.publish?.oppo?.enable) enabled.push("oppo");
1033
1116
  if (config?.publish?.vivo?.enable) enabled.push("vivo");
1034
1117
  if (config?.publish?.tencent?.enable) enabled.push("tencent");
1118
+ if (config?.publish?.harmony?.enable) enabled.push("harmony");
1119
+ if (config?.publish?.google_play?.enable) enabled.push("google_play");
1035
1120
  if (config?.publish?.app_store?.enable) {
1036
1121
  const mode = config.publish.app_store.mode || "app_store";
1037
1122
  enabled.push(mode);
@@ -1175,7 +1260,7 @@ var UploadManager = class {
1175
1260
  artifactPath: artifactSelection
1176
1261
  };
1177
1262
  await this.validateArtifactSelection(platform, artifactSelection, effectiveMetadata);
1178
- const pkgInfo = await parsePackageInfo(filePath, {
1263
+ const pkgInfo = import_path2.default.extname(filePath).toLowerCase() === ".aab" ? null : await parsePackageInfo(filePath, {
1179
1264
  androidSdkPath: effectiveMetadata.globalSettings?.androidSdkPath
1180
1265
  });
1181
1266
  if (pkgInfo) {
@@ -1532,6 +1617,280 @@ var AppleJWTGenerator = class _AppleJWTGenerator {
1532
1617
  }
1533
1618
  };
1534
1619
 
1620
+ // src/utils/release-receipt.ts
1621
+ var import_crypto3 = require("crypto");
1622
+ var import_fs2 = require("fs");
1623
+ var import_promises = require("fs/promises");
1624
+ var import_os = require("os");
1625
+ var import_path3 = __toESM(require("path"), 1);
1626
+ var RELEASE_RECEIPT_SCHEMA_VERSION = 1;
1627
+ var DEFAULT_RELEASE_RECEIPT_LIMIT = 100;
1628
+ var REMOTE_IDENTIFIER_KEYS = [
1629
+ "id",
1630
+ "appId",
1631
+ "buildId",
1632
+ "versionId",
1633
+ "releaseId",
1634
+ "editId",
1635
+ "versionCode",
1636
+ "track",
1637
+ "apiTrack",
1638
+ "status",
1639
+ "committed"
1640
+ ];
1641
+ function resolveReleaseProjectRoot(projectRoot, configPath) {
1642
+ return import_path3.default.resolve(projectRoot || import_path3.default.dirname(import_path3.default.resolve(configPath)));
1643
+ }
1644
+ function sanitizeReleaseMessage(value, maxLength = 500) {
1645
+ let message = typeof value === "string" ? value : String(value ?? "");
1646
+ 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(
1647
+ /(["']?(?:access[_-]?token|refresh[_-]?token|client[_-]?secret|private[_-]?key|api[_-]?key|app[_-]?password|authorization|credential|service[_-]?account(?:[_-]?json)?[_-]?content)["']?\s*[:=]\s*)["']?[^\s,"'}]+["']?/gi,
1648
+ "$1[\u5DF2\u8131\u654F]"
1649
+ ).replace(/[\r\n\t]+/g, " ").replace(/\s{2,}/g, " ").trim();
1650
+ return message.length > maxLength ? `${message.slice(0, maxLength - 1)}\u2026` : message;
1651
+ }
1652
+ function createUploadTargets(results) {
1653
+ return results.map((result) => {
1654
+ const platform = sanitizeReleaseMessage(result.platform, 80) || "unknown";
1655
+ const data = isRecord(result.data) ? result.data : void 0;
1656
+ const isDraft = result.success && platform === "google_play" && String(data?.status || "").toLowerCase() === "draft";
1657
+ const remote = createRemoteReceipt(result, data);
1658
+ return {
1659
+ platform,
1660
+ state: result.success ? isDraft ? "draft_created" : "uploaded" : "failed",
1661
+ outcome: result.success ? "success" : "failed",
1662
+ message: sanitizeReleaseMessage(result.message) || (result.success ? "\u4E0A\u4F20\u6210\u529F" : "\u4E0A\u4F20\u5931\u8D25"),
1663
+ ...remote ? { remote } : {}
1664
+ };
1665
+ });
1666
+ }
1667
+ async function createReleaseReceipt(input) {
1668
+ const projectRoot = await canonicalizeProjectRoot(input.projectRoot);
1669
+ const startedAt = toDate(input.startedAt ?? Date.now());
1670
+ const finishedAt = toDate(input.finishedAt ?? Date.now());
1671
+ const targets = input.targets.map(sanitizeTarget);
1672
+ return {
1673
+ schemaVersion: RELEASE_RECEIPT_SCHEMA_VERSION,
1674
+ id: (0, import_crypto3.randomUUID)(),
1675
+ projectKey: createProjectKey(projectRoot),
1676
+ projectRoot,
1677
+ kind: input.kind,
1678
+ ...cleanOptionalText(input.appName, 160) ? { appName: cleanOptionalText(input.appName, 160) } : {},
1679
+ ...cleanOptionalText(input.packageName, 240) ? { packageName: cleanOptionalText(input.packageName, 240) } : {},
1680
+ ...cleanOptionalText(input.versionName, 80) ? { versionName: cleanOptionalText(input.versionName, 80) } : {},
1681
+ ...input.buildNumber !== void 0 ? { buildNumber: cleanOptionalText(String(input.buildNumber), 80) } : {},
1682
+ ...cleanOptionalText(input.changelog, 4e3) ? { changelog: cleanOptionalText(input.changelog, 4e3) } : {},
1683
+ artifacts: await inspectArtifacts(input.artifactPaths),
1684
+ targets,
1685
+ outcome: input.outcome ?? inferOutcome(targets),
1686
+ startedAt: startedAt.toISOString(),
1687
+ finishedAt: finishedAt.toISOString(),
1688
+ durationMs: Math.max(0, finishedAt.getTime() - startedAt.getTime())
1689
+ };
1690
+ }
1691
+ function formatReleaseReceipt(receipt) {
1692
+ const lines = [
1693
+ `${receipt.appName || receipt.packageName || "\u5E94\u7528"} \xB7 ${receipt.kind === "build" ? "\u6784\u5EFA\u56DE\u6267" : "\u4E0A\u4F20\u56DE\u6267"}`,
1694
+ `\u7ED3\u679C\uFF1A${receipt.outcome}`,
1695
+ `\u7248\u672C\uFF1A${receipt.versionName || "-"} (${receipt.buildNumber || "-"})`,
1696
+ `\u5B8C\u6210\u65F6\u95F4\uFF1A${receipt.finishedAt}`
1697
+ ];
1698
+ if (receipt.packageName) lines.push(`\u5305\u540D\uFF1A${receipt.packageName}`);
1699
+ if (receipt.changelog) lines.push(`\u66F4\u65B0\u65E5\u5FD7\uFF1A${receipt.changelog}`);
1700
+ for (const artifact of receipt.artifacts) {
1701
+ lines.push(
1702
+ `\u4EA7\u7269\uFF1A${artifact.path}${artifact.sizeBytes !== void 0 ? ` \xB7 ${artifact.sizeBytes} bytes` : ""}${artifact.sha256 ? ` \xB7 SHA-256 ${artifact.sha256}` : ""}`
1703
+ );
1704
+ }
1705
+ for (const target of receipt.targets) {
1706
+ lines.push(`${target.platform}\uFF1A${target.state} \xB7 ${target.message}`);
1707
+ if (target.remote?.consoleUrl) lines.push(`\u540E\u53F0\uFF1A${target.remote.consoleUrl}`);
1708
+ }
1709
+ return lines.join("\n");
1710
+ }
1711
+ var ReleaseReceiptStore = class {
1712
+ storageRoot;
1713
+ maxEntries;
1714
+ constructor(options = {}) {
1715
+ this.storageRoot = import_path3.default.resolve(options.storageRoot || import_path3.default.join((0, import_os.homedir)(), ".flu-cli", "release-receipts"));
1716
+ this.maxEntries = Math.max(1, options.maxEntries ?? DEFAULT_RELEASE_RECEIPT_LIMIT);
1717
+ }
1718
+ async append(receipt) {
1719
+ await (0, import_promises.mkdir)(this.storageRoot, { recursive: true, mode: 448 });
1720
+ await (0, import_promises.chmod)(this.storageRoot, 448);
1721
+ const filePath = this.getFilePath(receipt.projectKey);
1722
+ const current = await this.readFile(filePath, receipt.projectKey);
1723
+ const receipts = [receipt, ...current.receipts.filter((item) => item.id !== receipt.id)].slice(
1724
+ 0,
1725
+ this.maxEntries
1726
+ );
1727
+ const tempPath = `${filePath}.${process.pid}.${(0, import_crypto3.randomUUID)()}.tmp`;
1728
+ await (0, import_promises.writeFile)(
1729
+ tempPath,
1730
+ `${JSON.stringify({
1731
+ schemaVersion: RELEASE_RECEIPT_SCHEMA_VERSION,
1732
+ projectKey: receipt.projectKey,
1733
+ receipts
1734
+ }, null, 2)}
1735
+ `,
1736
+ { encoding: "utf8", mode: 384 }
1737
+ );
1738
+ await (0, import_promises.rename)(tempPath, filePath);
1739
+ await (0, import_promises.chmod)(filePath, 384);
1740
+ }
1741
+ async list(projectRoot, limit = 20) {
1742
+ const canonicalRoot = await canonicalizeProjectRoot(projectRoot);
1743
+ const projectKey = createProjectKey(canonicalRoot);
1744
+ const data = await this.readFile(this.getFilePath(projectKey), projectKey);
1745
+ return data.receipts.slice(0, Math.max(0, limit));
1746
+ }
1747
+ async save(input) {
1748
+ const receipt = await createReleaseReceipt(input);
1749
+ await this.append(receipt);
1750
+ return receipt;
1751
+ }
1752
+ getFilePath(projectKey) {
1753
+ return import_path3.default.join(this.storageRoot, `${projectKey}.json`);
1754
+ }
1755
+ async readFile(filePath, projectKey) {
1756
+ try {
1757
+ const raw = JSON.parse(await (0, import_promises.readFile)(filePath, "utf8"));
1758
+ return {
1759
+ schemaVersion: RELEASE_RECEIPT_SCHEMA_VERSION,
1760
+ projectKey,
1761
+ receipts: Array.isArray(raw.receipts) ? raw.receipts : []
1762
+ };
1763
+ } catch (error) {
1764
+ if (error?.code === "ENOENT") {
1765
+ return { schemaVersion: RELEASE_RECEIPT_SCHEMA_VERSION, projectKey, receipts: [] };
1766
+ }
1767
+ throw new Error(`\u8BFB\u53D6\u53D1\u5E03\u56DE\u6267\u5931\u8D25: ${sanitizeReleaseMessage(error?.message || error)}`);
1768
+ }
1769
+ }
1770
+ };
1771
+ function createProjectKey(projectRoot) {
1772
+ return (0, import_crypto3.createHash)("sha256").update(import_path3.default.resolve(projectRoot)).digest("hex").slice(0, 24);
1773
+ }
1774
+ function createRemoteReceipt(result, data) {
1775
+ const identifiers = {};
1776
+ for (const key of REMOTE_IDENTIFIER_KEYS) {
1777
+ const value = data?.[key];
1778
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
1779
+ identifiers[key] = typeof value === "string" ? sanitizeReleaseMessage(value, 240) : value;
1780
+ }
1781
+ }
1782
+ const consoleUrl = cleanHttpUrl(result.consoleUrl);
1783
+ const downloadUrl = cleanHttpUrl(result.url || result.link);
1784
+ if (!consoleUrl && !downloadUrl && Object.keys(identifiers).length === 0) return void 0;
1785
+ return {
1786
+ ...consoleUrl ? { consoleUrl } : {},
1787
+ ...downloadUrl ? { downloadUrl } : {},
1788
+ ...Object.keys(identifiers).length > 0 ? { identifiers } : {}
1789
+ };
1790
+ }
1791
+ function sanitizeTarget(target) {
1792
+ return {
1793
+ platform: sanitizeReleaseMessage(target.platform, 80) || "unknown",
1794
+ state: target.state,
1795
+ outcome: target.outcome,
1796
+ message: sanitizeReleaseMessage(target.message),
1797
+ ...target.remote ? { remote: sanitizeRemote(target.remote) } : {},
1798
+ ...cleanOptionalText(target.lastKnownRemoteStatus, 120) ? { lastKnownRemoteStatus: cleanOptionalText(target.lastKnownRemoteStatus, 120) } : {},
1799
+ ...cleanOptionalText(target.checkedAt, 80) ? { checkedAt: cleanOptionalText(target.checkedAt, 80) } : {}
1800
+ };
1801
+ }
1802
+ function sanitizeRemote(remote) {
1803
+ const identifiers = {};
1804
+ for (const [key, value] of Object.entries(remote.identifiers || {})) {
1805
+ if (!REMOTE_IDENTIFIER_KEYS.includes(key)) continue;
1806
+ if (typeof value === "string") identifiers[key] = sanitizeReleaseMessage(value, 240);
1807
+ if (typeof value === "number" || typeof value === "boolean") identifiers[key] = value;
1808
+ }
1809
+ const consoleUrl = cleanHttpUrl(remote.consoleUrl);
1810
+ const downloadUrl = cleanHttpUrl(remote.downloadUrl);
1811
+ return {
1812
+ ...consoleUrl ? { consoleUrl } : {},
1813
+ ...downloadUrl ? { downloadUrl } : {},
1814
+ ...Object.keys(identifiers).length > 0 ? { identifiers } : {}
1815
+ };
1816
+ }
1817
+ async function inspectArtifacts(value) {
1818
+ const paths = Array.from(new Set(flattenArtifactPaths(value).map((item) => import_path3.default.resolve(item))));
1819
+ return Promise.all(paths.map(inspectArtifact));
1820
+ }
1821
+ async function inspectArtifact(filePath) {
1822
+ const base = { path: filePath, name: import_path3.default.basename(filePath) };
1823
+ try {
1824
+ const info = await (0, import_promises.stat)(filePath);
1825
+ if (!info.isFile()) return { ...base, available: false };
1826
+ return {
1827
+ ...base,
1828
+ sizeBytes: info.size,
1829
+ sha256: await hashFile(filePath),
1830
+ available: true
1831
+ };
1832
+ } catch {
1833
+ return { ...base, available: false };
1834
+ }
1835
+ }
1836
+ function hashFile(filePath) {
1837
+ return new Promise((resolve, reject) => {
1838
+ const hash = (0, import_crypto3.createHash)("sha256");
1839
+ const stream = (0, import_fs2.createReadStream)(filePath);
1840
+ stream.on("error", reject);
1841
+ stream.on("data", (chunk) => hash.update(chunk));
1842
+ stream.on("end", () => resolve(hash.digest("hex")));
1843
+ });
1844
+ }
1845
+ function flattenArtifactPaths(value) {
1846
+ if (typeof value === "string" && value.trim()) return [value];
1847
+ if (Array.isArray(value)) return value.flatMap(flattenArtifactPaths);
1848
+ if (isRecord(value)) return Object.values(value).flatMap(flattenArtifactPaths);
1849
+ return [];
1850
+ }
1851
+ function inferOutcome(targets) {
1852
+ if (targets.length === 0) return "failed";
1853
+ if (targets.every((target) => target.outcome === "cancelled")) return "cancelled";
1854
+ const successCount = targets.filter((target) => target.outcome === "success").length;
1855
+ if (successCount === targets.length) return "success";
1856
+ if (successCount > 0) return "partial";
1857
+ return "failed";
1858
+ }
1859
+ async function canonicalizeProjectRoot(projectRoot) {
1860
+ const resolved = import_path3.default.resolve(projectRoot);
1861
+ try {
1862
+ return await (0, import_promises.realpath)(resolved);
1863
+ } catch {
1864
+ return resolved;
1865
+ }
1866
+ }
1867
+ function toDate(value) {
1868
+ const date = value instanceof Date ? value : new Date(value);
1869
+ return Number.isNaN(date.getTime()) ? /* @__PURE__ */ new Date() : date;
1870
+ }
1871
+ function cleanOptionalText(value, maxLength) {
1872
+ const cleaned = sanitizeReleaseMessage(value, maxLength);
1873
+ return cleaned || void 0;
1874
+ }
1875
+ function cleanHttpUrl(value) {
1876
+ if (typeof value !== "string") return void 0;
1877
+ try {
1878
+ const url = new URL(value);
1879
+ if (url.protocol !== "http:" && url.protocol !== "https:") return void 0;
1880
+ for (const key of Array.from(url.searchParams.keys())) {
1881
+ if (/token|secret|password|signature|credential|key/i.test(key)) {
1882
+ url.searchParams.set(key, "[\u5DF2\u8131\u654F]");
1883
+ }
1884
+ }
1885
+ return url.toString();
1886
+ } catch {
1887
+ return void 0;
1888
+ }
1889
+ }
1890
+ function isRecord(value) {
1891
+ return !!value && typeof value === "object" && !Array.isArray(value);
1892
+ }
1893
+
1535
1894
  // src/adapters/pgyer_uploader.ts
1536
1895
  var PgyerUploader = class _PgyerUploader {
1537
1896
  name = "Pgyer";
@@ -1650,7 +2009,7 @@ var PgyerUploader = class _PgyerUploader {
1650
2009
 
1651
2010
  // src/adapters/huawei_uploader.ts
1652
2011
  var import_fs_extra4 = __toESM(require("fs-extra"), 1);
1653
- var import_path3 = __toESM(require("path"), 1);
2012
+ var import_path4 = __toESM(require("path"), 1);
1654
2013
  var import_util = require("util");
1655
2014
  var import_axios2 = __toESM(require("axios"), 1);
1656
2015
  var import_form_data2 = __toESM(require("form-data"), 1);
@@ -1729,7 +2088,7 @@ var HuaweiUploader = class _HuaweiUploader {
1729
2088
  const clientSecret = hwConfig.auth.clientSecret;
1730
2089
  const appId = hwConfig.appId;
1731
2090
  const releaseType = hwConfig.releaseType || "draft";
1732
- const suffix = import_path3.default.extname(filePath).substring(1);
2091
+ const suffix = import_path4.default.extname(filePath).substring(1);
1733
2092
  const retryConfig = config.retry;
1734
2093
  const tokenUrl = this.getTokenUrl(config);
1735
2094
  const publishApiRoot = this.getApiRoot(config);
@@ -1755,8 +2114,8 @@ var HuaweiUploader = class _HuaweiUploader {
1755
2114
  const uploadResults = [];
1756
2115
  const uploadPromises = Object.entries(artifacts).map(async ([abi, p]) => {
1757
2116
  if (!p || !import_fs_extra4.default.existsSync(p)) return;
1758
- const currentSuffix = import_path3.default.extname(p).substring(1);
1759
- logger.info(`[Huawei] \u6B63\u5728\u4E0A\u4F20 ${abi} \u5305: ${import_path3.default.basename(p)}`);
2117
+ const currentSuffix = import_path4.default.extname(p).substring(1);
2118
+ logger.info(`[Huawei] \u6B63\u5728\u4E0A\u4F20 ${abi} \u5305: ${import_path4.default.basename(p)}`);
1760
2119
  const uploadInfo = await retryStrategy.execute(
1761
2120
  () => this.getUploadUrl(ctx, currentSuffix),
1762
2121
  `Huawei get upload URL for ${abi}`
@@ -1772,7 +2131,7 @@ var HuaweiUploader = class _HuaweiUploader {
1772
2131
  throw uploadFileRes.error || new Error(`File upload failed for ${abi}`);
1773
2132
  }
1774
2133
  uploadResults.push({
1775
- fileName: import_path3.default.basename(p),
2134
+ fileName: import_path4.default.basename(p),
1776
2135
  fileDestUrl: uploadFileRes.data[0].fileDestUrl
1777
2136
  });
1778
2137
  });
@@ -1944,7 +2303,7 @@ var HuaweiUploader = class _HuaweiUploader {
1944
2303
  }
1945
2304
  /** 通用文件上传(FormData 方式,无需 API 认证头) */
1946
2305
  async executeUploadRequest(uploadUrl, authCode, filePath) {
1947
- const fileName = import_path3.default.basename(filePath);
2306
+ const fileName = import_path4.default.basename(filePath);
1948
2307
  const fileSize = import_fs_extra4.default.statSync(filePath).size;
1949
2308
  const form = new import_form_data2.default();
1950
2309
  form.append("file", import_fs_extra4.default.createReadStream(filePath), {
@@ -2111,7 +2470,7 @@ var HuaweiUploader = class _HuaweiUploader {
2111
2470
  logger.warn(`Asset file not found, skipping: ${filePath}`);
2112
2471
  continue;
2113
2472
  }
2114
- const suffix = import_path3.default.extname(filePath).substring(1).toLowerCase();
2473
+ const suffix = import_path4.default.extname(filePath).substring(1).toLowerCase();
2115
2474
  if (!HUAWEI_SUPPORTED_SUFFIXES.image.includes(suffix)) {
2116
2475
  logger.warn(`Unsupported file type ${suffix}, skipping: ${filePath}`);
2117
2476
  continue;
@@ -2166,10 +2525,10 @@ var HuaweiUploader = class _HuaweiUploader {
2166
2525
 
2167
2526
  // src/adapters/xiaomi_uploader.ts
2168
2527
  var import_fs_extra5 = __toESM(require("fs-extra"), 1);
2169
- var import_path4 = __toESM(require("path"), 1);
2528
+ var import_path5 = __toESM(require("path"), 1);
2170
2529
  var import_axios3 = __toESM(require("axios"), 1);
2171
2530
  var import_form_data3 = __toESM(require("form-data"), 1);
2172
- var import_crypto3 = __toESM(require("crypto"), 1);
2531
+ var import_crypto4 = __toESM(require("crypto"), 1);
2173
2532
  var XiaomiUploader = class _XiaomiUploader {
2174
2533
  name = "Xiaomi";
2175
2534
  static API_BASE = "https://api.developer.xiaomi.com/devupload";
@@ -2189,10 +2548,10 @@ var XiaomiUploader = class _XiaomiUploader {
2189
2548
  const chunks = [];
2190
2549
  for (let offset = 0; offset < buffer.length; offset += maxBlock) {
2191
2550
  const chunk = buffer.subarray(offset, offset + maxBlock);
2192
- const encrypted = import_crypto3.default.publicEncrypt(
2551
+ const encrypted = import_crypto4.default.publicEncrypt(
2193
2552
  {
2194
2553
  key: publicKey,
2195
- padding: import_crypto3.default.constants.RSA_PKCS1_PADDING
2554
+ padding: import_crypto4.default.constants.RSA_PKCS1_PADDING
2196
2555
  },
2197
2556
  chunk
2198
2557
  );
@@ -2206,15 +2565,15 @@ var XiaomiUploader = class _XiaomiUploader {
2206
2565
  */
2207
2566
  extractPublicKey(buffer) {
2208
2567
  try {
2209
- const x509 = new import_crypto3.default.X509Certificate(buffer);
2568
+ const x509 = new import_crypto4.default.X509Certificate(buffer);
2210
2569
  return x509.publicKey;
2211
2570
  } catch (e) {
2212
2571
  try {
2213
2572
  const content = buffer.toString();
2214
2573
  if (content.includes("-----BEGIN")) {
2215
- return import_crypto3.default.createPublicKey(content);
2574
+ return import_crypto4.default.createPublicKey(content);
2216
2575
  }
2217
- return import_crypto3.default.createPublicKey(this.normalizePEM(content));
2576
+ return import_crypto4.default.createPublicKey(this.normalizePEM(content));
2218
2577
  } catch (e2) {
2219
2578
  throw new Error(`\u65E0\u6CD5\u63D0\u53D6\u516C\u94A5: ${e instanceof Error ? e.message : "\u672A\u77E5\u9519\u8BEF"}`);
2220
2579
  }
@@ -2225,7 +2584,7 @@ var XiaomiUploader = class _XiaomiUploader {
2225
2584
  */
2226
2585
  async calculateFileMD5(filePath) {
2227
2586
  return new Promise((resolve, reject) => {
2228
- const hash = import_crypto3.default.createHash("md5");
2587
+ const hash = import_crypto4.default.createHash("md5");
2229
2588
  const stream = import_fs_extra5.default.createReadStream(filePath);
2230
2589
  stream.on("data", (data) => hash.update(data));
2231
2590
  stream.on("end", () => resolve(hash.digest("hex")));
@@ -2236,7 +2595,7 @@ var XiaomiUploader = class _XiaomiUploader {
2236
2595
  * 计算字符串 MD5 (小写十六进制)
2237
2596
  */
2238
2597
  calculateStringMD5(str) {
2239
- return import_crypto3.default.createHash("md5").update(str, "utf-8").digest("hex");
2598
+ return import_crypto4.default.createHash("md5").update(str, "utf-8").digest("hex");
2240
2599
  }
2241
2600
  /**
2242
2601
  * 构建 RequestData JSON
@@ -2494,14 +2853,14 @@ ${lines.join("\n")}
2494
2853
  if (arm64 && arm32) {
2495
2854
  mainPath = arm64[1];
2496
2855
  secondPath = arm32[1];
2497
- logger.info(`[Xiaomi] \u8BC6\u522B\u5230\u591A\u67B6\u6784\u5206\u5305: \u4E3B\u5305(64\u4F4D)=${import_path4.default.basename(mainPath)}, \u6B21\u5305(32\u4F4D)=${import_path4.default.basename(secondPath)}`);
2856
+ logger.info(`[Xiaomi] \u8BC6\u522B\u5230\u591A\u67B6\u6784\u5206\u5305: \u4E3B\u5305(64\u4F4D)=${import_path5.default.basename(mainPath)}, \u6B21\u5305(32\u4F4D)=${import_path5.default.basename(secondPath)}`);
2498
2857
  } else {
2499
2858
  mainPath = artifactEntries[0][1];
2500
2859
  if (artifactEntries.length > 1) {
2501
2860
  secondPath = artifactEntries[1][1];
2502
- logger.info(`[Xiaomi] \u8BC6\u522B\u5230\u591A\u4E2A\u5305\uFF08\u975E\u6807\u51C6\u540D\uFF09\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u987A\u5E8F: \u4E3B\u5305=${import_path4.default.basename(mainPath)}, \u6B21\u5305=${import_path4.default.basename(secondPath)}`);
2861
+ logger.info(`[Xiaomi] \u8BC6\u522B\u5230\u591A\u4E2A\u5305\uFF08\u975E\u6807\u51C6\u540D\uFF09\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u987A\u5E8F: \u4E3B\u5305=${import_path5.default.basename(mainPath)}, \u6B21\u5305=${import_path5.default.basename(secondPath)}`);
2503
2862
  } else {
2504
- logger.info(`[Xiaomi] \u4F7F\u7528\u5355\u5305\u6A21\u5F0F: ${import_path4.default.basename(mainPath)}`);
2863
+ logger.info(`[Xiaomi] \u4F7F\u7528\u5355\u5305\u6A21\u5F0F: ${import_path5.default.basename(mainPath)}`);
2505
2864
  }
2506
2865
  }
2507
2866
  const mainMD5 = await this.calculateFileMD5(mainPath);
@@ -2575,7 +2934,7 @@ ${lines.join("\n")}
2575
2934
 
2576
2935
  // src/adapters/apple_store_uploader.ts
2577
2936
  var import_fs_extra6 = __toESM(require("fs-extra"), 1);
2578
- var import_path5 = __toESM(require("path"), 1);
2937
+ var import_path6 = __toESM(require("path"), 1);
2579
2938
  var import_child_process = require("child_process");
2580
2939
  var import_util2 = require("util");
2581
2940
  var import_axios4 = __toESM(require("axios"), 1);
@@ -2741,7 +3100,7 @@ var AppleStoreUploader = class {
2741
3100
  appPassword,
2742
3101
  "-showTransferRate"
2743
3102
  ].join(" ");
2744
- logger.info(`[Transporter] Uploading: ${import_path5.default.basename(ipaPath)}`);
3103
+ logger.info(`[Transporter] Uploading: ${import_path6.default.basename(ipaPath)}`);
2745
3104
  try {
2746
3105
  const { stdout, stderr } = await execAsync(command, {
2747
3106
  timeout: 30 * 60 * 1e3,
@@ -2792,7 +3151,7 @@ Stderr: ${error.stderr || ""}`);
2792
3151
  });
2793
3152
  command = innerArgs.join(" ");
2794
3153
  }
2795
- logger.info(`[altool] Uploading: ${import_path5.default.basename(ipaPath)}`);
3154
+ logger.info(`[altool] Uploading: ${import_path6.default.basename(ipaPath)}`);
2796
3155
  logger.info(`[altool] Command prefix: ${tool.command}`);
2797
3156
  try {
2798
3157
  const { stdout, stderr } = await execAsync(command, {
@@ -3376,7 +3735,7 @@ var TestFlightUploader = class _TestFlightUploader {
3376
3735
 
3377
3736
  // src/adapters/adhoc_uploader.ts
3378
3737
  var import_fs_extra8 = __toESM(require("fs-extra"), 1);
3379
- var import_path6 = __toESM(require("path"), 1);
3738
+ var import_path7 = __toESM(require("path"), 1);
3380
3739
  var import_child_process2 = require("child_process");
3381
3740
  var AdHocUploader = class {
3382
3741
  name = "AdHoc";
@@ -3509,8 +3868,8 @@ var AdHocUploader = class {
3509
3868
  * @param changelog 更新日志
3510
3869
  */
3511
3870
  async createManifest(ipaPath, version, bundleId, changelog) {
3512
- const ipaFileName = import_path6.default.basename(ipaPath);
3513
- const appName = bundleId || import_path6.default.parse(ipaFileName).name;
3871
+ const ipaFileName = import_path7.default.basename(ipaPath);
3872
+ const appName = bundleId || import_path7.default.parse(ipaFileName).name;
3514
3873
  const ipaUrl = `itms-services://?action=download-manifest&url=https://your-distribution-server.com/manifests/${ipaFileName}.plist`;
3515
3874
  const manifestContent = `<?xml version="1.0" encoding="UTF-8"?>
3516
3875
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -3569,14 +3928,14 @@ var AdHocUploader = class {
3569
3928
  */
3570
3929
  async setupDistributionDirectory(ipaPath, manifestPath) {
3571
3930
  const projectRoot = process.cwd();
3572
- const distributionDir = import_path6.default.join(projectRoot, "build", "adhoc-distribution");
3931
+ const distributionDir = import_path7.default.join(projectRoot, "build", "adhoc-distribution");
3573
3932
  await import_fs_extra8.default.ensureDir(distributionDir);
3574
- await import_fs_extra8.default.ensureDir(import_path6.default.join(distributionDir, "apps"));
3575
- await import_fs_extra8.default.ensureDir(import_path6.default.join(distributionDir, "manifests"));
3576
- await import_fs_extra8.default.ensureDir(import_path6.default.join(distributionDir, "icons"));
3577
- const ipaFileName = import_path6.default.basename(ipaPath);
3578
- await import_fs_extra8.default.copy(ipaPath, import_path6.default.join(distributionDir, "apps", ipaFileName));
3579
- await import_fs_extra8.default.copy(manifestPath, import_path6.default.join(distributionDir, "manifests", `${ipaFileName}.plist`));
3933
+ await import_fs_extra8.default.ensureDir(import_path7.default.join(distributionDir, "apps"));
3934
+ await import_fs_extra8.default.ensureDir(import_path7.default.join(distributionDir, "manifests"));
3935
+ await import_fs_extra8.default.ensureDir(import_path7.default.join(distributionDir, "icons"));
3936
+ const ipaFileName = import_path7.default.basename(ipaPath);
3937
+ await import_fs_extra8.default.copy(ipaPath, import_path7.default.join(distributionDir, "apps", ipaFileName));
3938
+ await import_fs_extra8.default.copy(manifestPath, import_path7.default.join(distributionDir, "manifests", `${ipaFileName}.plist`));
3580
3939
  logger.info(`Distribution directory: ${distributionDir}`);
3581
3940
  return distributionDir;
3582
3941
  }
@@ -3586,7 +3945,7 @@ var AdHocUploader = class {
3586
3945
  * @param bundleId Bundle ID
3587
3946
  */
3588
3947
  generateInstallUrl(distributionPath, bundleId) {
3589
- const manifestPath = import_path6.default.join(distributionPath, "manifests", `*.plist`);
3948
+ const manifestPath = import_path7.default.join(distributionPath, "manifests", `*.plist`);
3590
3949
  return `itms-services://?action=download-manifest&url=file://${manifestPath}`;
3591
3950
  }
3592
3951
  /**
@@ -3595,7 +3954,7 @@ var AdHocUploader = class {
3595
3954
  * @param distributionPath 分发目录
3596
3955
  */
3597
3956
  async generateQRCode(url, distributionPath) {
3598
- const qrCodePath = import_path6.default.join(distributionPath, "install-qrcode.png");
3957
+ const qrCodePath = import_path7.default.join(distributionPath, "install-qrcode.png");
3599
3958
  try {
3600
3959
  logger.info(`QR code would be saved to: ${qrCodePath}`);
3601
3960
  logger.info(`Install URL: ${url}`);
@@ -3622,8 +3981,8 @@ var AdHocUploader = class {
3622
3981
  logger.newLine();
3623
3982
  logger.info("\u{1F4E6} Distribution Files:");
3624
3983
  logger.info(` Directory: ${distributionPath}`);
3625
- logger.info(` IPA: ${import_path6.default.join(distributionPath, "apps", "*.ipa")}`);
3626
- logger.info(` Manifest: ${import_path6.default.join(distributionPath, "manifests", "*.plist")}`);
3984
+ logger.info(` IPA: ${import_path7.default.join(distributionPath, "apps", "*.ipa")}`);
3985
+ logger.info(` Manifest: ${import_path7.default.join(distributionPath, "manifests", "*.plist")}`);
3627
3986
  logger.info(` QR Code: ${qrCodePath}`);
3628
3987
  logger.newLine();
3629
3988
  logger.info("\u{1F517} Installation Link:");
@@ -3682,7 +4041,7 @@ var AdHocUploader = class {
3682
4041
 
3683
4042
  // src/adapters/harmony_uploader.ts
3684
4043
  var import_fs_extra9 = __toESM(require("fs-extra"), 1);
3685
- var import_path7 = __toESM(require("path"), 1);
4044
+ var import_path8 = __toESM(require("path"), 1);
3686
4045
  var import_axios6 = __toESM(require("axios"), 1);
3687
4046
  var import_form_data5 = __toESM(require("form-data"), 1);
3688
4047
  var HarmonyUploader = class _HarmonyUploader {
@@ -3712,7 +4071,7 @@ var HarmonyUploader = class _HarmonyUploader {
3712
4071
  const clientSecret = harmonyConfig.auth.clientSecret;
3713
4072
  const appId = harmonyConfig.appId;
3714
4073
  const releaseType = harmonyConfig.releaseType || "draft";
3715
- const suffix = import_path7.default.extname(filePath).substring(1);
4074
+ const suffix = import_path8.default.extname(filePath).substring(1);
3716
4075
  const retryConfig = config.retry;
3717
4076
  logger.info("Starting HarmonyOS AppGallery upload...");
3718
4077
  try {
@@ -3829,7 +4188,7 @@ var HarmonyUploader = class _HarmonyUploader {
3829
4188
  * @param filePath 文件路径
3830
4189
  */
3831
4190
  async updateAppFileInfo(token, appId, authCode, filePath) {
3832
- const fileName = import_path7.default.basename(filePath);
4191
+ const fileName = import_path8.default.basename(filePath);
3833
4192
  const body = {
3834
4193
  fileType: 7,
3835
4194
  // 鸿蒙包的 fileType(需要根据华为最新文档确认)
@@ -3852,10 +4211,10 @@ var HarmonyUploader = class _HarmonyUploader {
3852
4211
 
3853
4212
  // src/adapters/oppo_uploader.ts
3854
4213
  var import_fs_extra10 = __toESM(require("fs-extra"), 1);
3855
- var import_path8 = __toESM(require("path"), 1);
4214
+ var import_path9 = __toESM(require("path"), 1);
3856
4215
  var import_axios7 = __toESM(require("axios"), 1);
3857
4216
  var import_form_data6 = __toESM(require("form-data"), 1);
3858
- var import_crypto4 = __toESM(require("crypto"), 1);
4217
+ var import_crypto5 = __toESM(require("crypto"), 1);
3859
4218
  var OppoUploader = class _OppoUploader {
3860
4219
  name = "OPPO";
3861
4220
  /** OPPO 开放 API 基础地址 */
@@ -3874,7 +4233,7 @@ var OppoUploader = class _OppoUploader {
3874
4233
  */
3875
4234
  generateSign(params, clientSecret) {
3876
4235
  const sorted = Object.keys(params).sort().filter((key) => params[key] !== void 0 && params[key] !== null).map((key) => `${key}=${params[key]}`).join("&");
3877
- return import_crypto4.default.createHmac("sha256", clientSecret).update(sorted).digest("hex");
4236
+ return import_crypto5.default.createHmac("sha256", clientSecret).update(sorted).digest("hex");
3878
4237
  }
3879
4238
  validateConfig(config) {
3880
4239
  if (!config?.publish?.oppo?.enable) return false;
@@ -3893,7 +4252,7 @@ var OppoUploader = class _OppoUploader {
3893
4252
  * 尝试从文件名推断包名
3894
4253
  */
3895
4254
  inferPackageName(filePath) {
3896
- const fileName = import_path8.default.basename(filePath);
4255
+ const fileName = import_path9.default.basename(filePath);
3897
4256
  const match = fileName.match(/^([\w.]+?)(?:-(?:release|debug|unsigned))?\.apk$/i);
3898
4257
  if (match && match[1].includes(".")) {
3899
4258
  return match[1];
@@ -4174,17 +4533,17 @@ var OppoUploader = class _OppoUploader {
4174
4533
 
4175
4534
  // src/adapters/vivo_uploader.ts
4176
4535
  var import_fs_extra11 = __toESM(require("fs-extra"), 1);
4177
- var import_path9 = __toESM(require("path"), 1);
4536
+ var import_path10 = __toESM(require("path"), 1);
4178
4537
  var import_axios8 = __toESM(require("axios"), 1);
4179
4538
  var import_form_data7 = __toESM(require("form-data"), 1);
4180
- var import_crypto5 = __toESM(require("crypto"), 1);
4539
+ var import_crypto6 = __toESM(require("crypto"), 1);
4181
4540
  var VivoUploader = class _VivoUploader {
4182
4541
  name = "vivo";
4183
4542
  /** vivo 开放 API 基础地址 */
4184
4543
  static API_BASE = "https://developer-api.vivo.com.cn/router/rest";
4185
4544
  generateSign(params, accessSecret) {
4186
4545
  const sorted = Object.keys(params).sort().filter((key) => params[key] !== void 0 && params[key] !== null).map((key) => `${key}=${params[key]}`).join("&");
4187
- return import_crypto5.default.createHmac("sha256", accessSecret).update(sorted).digest("hex");
4546
+ return import_crypto6.default.createHmac("sha256", accessSecret).update(sorted).digest("hex");
4188
4547
  }
4189
4548
  validateConfig(config) {
4190
4549
  if (!config?.publish?.vivo?.enable) return false;
@@ -4206,7 +4565,7 @@ var VivoUploader = class _VivoUploader {
4206
4565
  const uploadPromises = Object.entries(artifacts).map(async ([abi, p]) => {
4207
4566
  if (!p || !import_fs_extra11.default.existsSync(p)) throw new Error(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${p}`);
4208
4567
  const md5 = await this.calculateFileMD5(p);
4209
- logger.info(`[vivo] \u6B63\u5728\u5904\u7406 ${abi} \u67B6\u6784\u5305: ${import_path9.default.basename(p)}`);
4568
+ logger.info(`[vivo] \u6B63\u5728\u5904\u7406 ${abi} \u67B6\u6784\u5305: ${import_path10.default.basename(p)}`);
4210
4569
  const serialnumber = await this.uploadApk(accessKey, accessSecret, p, vivoConfig.packageName || "", md5);
4211
4570
  const pkgInfo = await parsePackageInfo(p, { androidSdkPath: config.globalSettings?.androidSdkPath });
4212
4571
  const nativeCodes = pkgInfo?.nativeCode || [];
@@ -4246,7 +4605,7 @@ var VivoUploader = class _VivoUploader {
4246
4605
  }
4247
4606
  calculateFileMD5(filePath) {
4248
4607
  return new Promise((resolve, reject) => {
4249
- const hash = import_crypto5.default.createHash("md5");
4608
+ const hash = import_crypto6.default.createHash("md5");
4250
4609
  const stream = import_fs_extra11.default.createReadStream(filePath);
4251
4610
  stream.on("data", (data) => hash.update(data));
4252
4611
  stream.on("end", () => resolve(hash.digest("hex")));
@@ -4270,7 +4629,7 @@ var VivoUploader = class _VivoUploader {
4270
4629
  const form = new import_form_data7.default();
4271
4630
  Object.entries(params).forEach(([k, v]) => form.append(k, v));
4272
4631
  form.append("sign", sign);
4273
- form.append("file", import_fs_extra11.default.createReadStream(filePath), { filename: import_path9.default.basename(filePath) });
4632
+ form.append("file", import_fs_extra11.default.createReadStream(filePath), { filename: import_path10.default.basename(filePath) });
4274
4633
  const res = await import_axios8.default.post(_VivoUploader.API_BASE, form, { headers: form.getHeaders(), timeout: 3e5 });
4275
4634
  if (res.data.code !== 0) throw new Error(`\u4E0A\u4F20\u5305\u5931\u8D25: ${res.data.msg}`);
4276
4635
  return res.data.data?.serialnumber || res.data.data;
@@ -4310,9 +4669,9 @@ var VivoUploader = class _VivoUploader {
4310
4669
 
4311
4670
  // src/adapters/tencent_uploader.ts
4312
4671
  var import_fs_extra12 = __toESM(require("fs-extra"), 1);
4313
- var import_path10 = __toESM(require("path"), 1);
4672
+ var import_path11 = __toESM(require("path"), 1);
4314
4673
  var import_axios9 = __toESM(require("axios"), 1);
4315
- var import_crypto6 = __toESM(require("crypto"), 1);
4674
+ var import_crypto7 = __toESM(require("crypto"), 1);
4316
4675
  var TencentUploader = class _TencentUploader {
4317
4676
  name = "Tencent";
4318
4677
  /** 腾讯开放平台 API 基础地址 */
@@ -4325,7 +4684,7 @@ var TencentUploader = class _TencentUploader {
4325
4684
  generateSign(params, accessSecret) {
4326
4685
  const sortedKeys = Object.keys(params).sort();
4327
4686
  const signStr = sortedKeys.filter((key) => params[key] !== void 0 && params[key] !== null).map((key) => `${key}=${params[key]}`).join("&");
4328
- return import_crypto6.default.createHmac("sha256", accessSecret).update(signStr).digest("hex").toLowerCase();
4687
+ return import_crypto7.default.createHmac("sha256", accessSecret).update(signStr).digest("hex").toLowerCase();
4329
4688
  }
4330
4689
  validateConfig(config) {
4331
4690
  if (!config?.publish?.tencent?.enable) return false;
@@ -4350,7 +4709,7 @@ var TencentUploader = class _TencentUploader {
4350
4709
  const uploadPromises = Object.entries(artifacts).map(async ([abi, p]) => {
4351
4710
  if (!p || !import_fs_extra12.default.existsSync(p)) throw new Error(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${p}`);
4352
4711
  const stats = import_fs_extra12.default.statSync(p);
4353
- const fileName = import_path10.default.basename(p);
4712
+ const fileName = import_path11.default.basename(p);
4354
4713
  logger.info(`[Tencent] \u6B63\u5728\u5904\u7406 ${abi} \u67B6\u6784\u5305: ${fileName}`);
4355
4714
  const uploadInfo = await this.getFileUploadInfo(auth, stats.size, fileName, pkgName);
4356
4715
  await this.uploadToCos(uploadInfo.pre_sign_url, p, stats.size);
@@ -4553,27 +4912,256 @@ var TencentUploader = class _TencentUploader {
4553
4912
  return res.data;
4554
4913
  }
4555
4914
  };
4915
+
4916
+ // src/adapters/google_play_uploader.ts
4917
+ var import_fs_extra13 = __toESM(require("fs-extra"), 1);
4918
+ var import_path12 = __toESM(require("path"), 1);
4919
+ var import_axios10 = __toESM(require("axios"), 1);
4920
+ var import_google_auth_library = require("google-auth-library");
4921
+ var ANDROID_PUBLISHER_SCOPE = "https://www.googleapis.com/auth/androidpublisher";
4922
+ var PUBLISH_API_ROOT = "https://androidpublisher.googleapis.com/androidpublisher/v3";
4923
+ var UPLOAD_API_ROOT = "https://androidpublisher.googleapis.com/upload/androidpublisher/v3";
4924
+ var GOOGLE_PLAY_CONSOLE_URL = "https://play.google.com/console/developers";
4925
+ var GOOGLE_PLAY_INTERNAL_API_TRACK = "internal";
4926
+ var UPLOAD_TIMEOUT_MS = 5 * 60 * 1e3;
4927
+ var GooglePlayUploader = class {
4928
+ name = "Google Play";
4929
+ httpClient;
4930
+ getAccessToken;
4931
+ constructor(dependencies = {}) {
4932
+ this.httpClient = dependencies.httpClient || import_axios10.default;
4933
+ this.getAccessToken = dependencies.getAccessToken || this.createAccessToken.bind(this);
4934
+ }
4935
+ validateConfig(config) {
4936
+ const googleConfig = config?.publish?.google_play;
4937
+ if (!googleConfig?.enable) return false;
4938
+ if (!googleConfig.packageName || typeof googleConfig.packageName !== "string") {
4939
+ logger.error("Google Play \u914D\u7F6E\u7F3A\u5C11 packageName");
4940
+ return false;
4941
+ }
4942
+ const track = googleConfig.track || "internal";
4943
+ if (track !== "internal") {
4944
+ logger.error("Google Play \u9996\u7248\u4EC5\u652F\u6301 internal \u8F68\u9053");
4945
+ return false;
4946
+ }
4947
+ const auth = googleConfig.auth;
4948
+ const hasCredentialContent = typeof auth?.serviceAccountJsonContent === "string" && auth.serviceAccountJsonContent.trim().length > 0;
4949
+ const credentialPath = auth?.serviceAccountJsonPath;
4950
+ const hasCredentialPath = typeof credentialPath === "string" && credentialPath.trim().length > 0 && import_fs_extra13.default.existsSync(import_path12.default.resolve(credentialPath));
4951
+ if (!hasCredentialContent && !hasCredentialPath) {
4952
+ logger.error("Google Play \u914D\u7F6E\u7F3A\u5C11\u6709\u6548\u7684\u670D\u52A1\u8D26\u53F7 JSON \u6587\u4EF6\u6216\u5185\u5BB9");
4953
+ return false;
4954
+ }
4955
+ return true;
4956
+ }
4957
+ canHandle(filePath) {
4958
+ return import_path12.default.extname(filePath).toLowerCase() === ".aab";
4959
+ }
4960
+ async upload(filePath, config, _metadata) {
4961
+ if (!this.validateConfig(config)) {
4962
+ 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");
4963
+ }
4964
+ if (!this.canHandle(filePath)) {
4965
+ return this.failure("Google Play \u4EC5\u63A5\u53D7 Android App Bundle\uFF08.aab\uFF09");
4966
+ }
4967
+ if (!import_fs_extra13.default.existsSync(filePath)) {
4968
+ return this.failure(`\u672A\u627E\u5230\u5F85\u4E0A\u4F20\u7684 AAB\uFF1A${filePath}`);
4969
+ }
4970
+ const googleConfig = config.publish.google_play;
4971
+ const packageName = googleConfig.packageName.trim();
4972
+ const encodedPackageName = encodeURIComponent(packageName);
4973
+ let editId;
4974
+ let accessToken;
4975
+ let committed = false;
4976
+ let stage = "create_edit";
4977
+ logger.info(`\u51C6\u5907\u5C06 ${import_path12.default.basename(filePath)} \u4E0A\u4F20\u5230 Google Play internal \u8349\u7A3F`);
4978
+ try {
4979
+ accessToken = await this.getAccessToken(googleConfig.auth);
4980
+ const requestConfig = {
4981
+ headers: { Authorization: `Bearer ${accessToken}` }
4982
+ };
4983
+ const editResponse = await this.httpClient.post(
4984
+ `${PUBLISH_API_ROOT}/applications/${encodedPackageName}/edits`,
4985
+ {},
4986
+ requestConfig
4987
+ );
4988
+ editId = editResponse.data.id;
4989
+ if (!editId) {
4990
+ throw new Error("Google Play \u672A\u8FD4\u56DE editId");
4991
+ }
4992
+ stage = "upload_bundle";
4993
+ const encodedEditId = encodeURIComponent(editId);
4994
+ const bundleResponse = await this.httpClient.post(
4995
+ `${UPLOAD_API_ROOT}/applications/${encodedPackageName}/edits/${encodedEditId}/bundles?uploadType=media`,
4996
+ import_fs_extra13.default.createReadStream(filePath),
4997
+ {
4998
+ timeout: UPLOAD_TIMEOUT_MS,
4999
+ maxBodyLength: Infinity,
5000
+ maxContentLength: Infinity,
5001
+ headers: {
5002
+ Authorization: `Bearer ${accessToken}`,
5003
+ "Content-Type": "application/octet-stream",
5004
+ "Content-Length": import_fs_extra13.default.statSync(filePath).size
5005
+ }
5006
+ }
5007
+ );
5008
+ const versionCode = bundleResponse.data.versionCode;
5009
+ if (!Number.isInteger(versionCode)) {
5010
+ throw new Error("Google Play \u4E0A\u4F20\u6210\u529F\u4F46\u672A\u8FD4\u56DE\u6709\u6548\u7684 versionCode");
5011
+ }
5012
+ stage = "update_internal_track";
5013
+ await this.httpClient.put(
5014
+ `${PUBLISH_API_ROOT}/applications/${encodedPackageName}/edits/${encodedEditId}/tracks/${GOOGLE_PLAY_INTERNAL_API_TRACK}`,
5015
+ {
5016
+ track: GOOGLE_PLAY_INTERNAL_API_TRACK,
5017
+ releases: [
5018
+ {
5019
+ versionCodes: [String(versionCode)],
5020
+ status: "draft"
5021
+ }
5022
+ ]
5023
+ },
5024
+ requestConfig
5025
+ );
5026
+ stage = "validate_edit";
5027
+ await this.httpClient.post(
5028
+ `${PUBLISH_API_ROOT}/applications/${encodedPackageName}/edits/${encodedEditId}:validate`,
5029
+ {},
5030
+ requestConfig
5031
+ );
5032
+ stage = "commit_edit";
5033
+ await this.httpClient.post(
5034
+ `${PUBLISH_API_ROOT}/applications/${encodedPackageName}/edits/${encodedEditId}:commit`,
5035
+ {},
5036
+ requestConfig
5037
+ );
5038
+ committed = true;
5039
+ logger.success(`Google Play internal \u8349\u7A3F\u5DF2\u521B\u5EFA\uFF08versionCode ${versionCode}\uFF09`);
5040
+ return {
5041
+ platform: "google_play",
5042
+ success: true,
5043
+ message: `AAB \u5DF2\u4E0A\u4F20\u5230 Google Play internal \u8349\u7A3F\uFF08versionCode ${versionCode}\uFF09`,
5044
+ consoleUrl: GOOGLE_PLAY_CONSOLE_URL,
5045
+ data: {
5046
+ editId,
5047
+ versionCode,
5048
+ track: "internal",
5049
+ apiTrack: GOOGLE_PLAY_INTERNAL_API_TRACK,
5050
+ status: "draft",
5051
+ committed: true
5052
+ }
5053
+ };
5054
+ } catch (error) {
5055
+ if (editId && accessToken && !committed) {
5056
+ await this.deleteEditBestEffort(encodedPackageName, editId, accessToken);
5057
+ }
5058
+ const message = this.describeError(error, stage);
5059
+ logger.error(`Google Play \u4E0A\u4F20\u5931\u8D25\uFF1A${message}`);
5060
+ return this.failure(message, error);
5061
+ }
5062
+ }
5063
+ async createAccessToken(authConfig) {
5064
+ let credentials;
5065
+ if (authConfig.serviceAccountJsonContent?.trim()) {
5066
+ try {
5067
+ credentials = JSON.parse(authConfig.serviceAccountJsonContent);
5068
+ } catch {
5069
+ throw new Error("\u670D\u52A1\u8D26\u53F7 JSON \u5185\u5BB9\u683C\u5F0F\u65E0\u6548");
5070
+ }
5071
+ }
5072
+ const keyFile = !credentials && authConfig.serviceAccountJsonPath ? import_path12.default.resolve(authConfig.serviceAccountJsonPath) : void 0;
5073
+ const auth = new import_google_auth_library.GoogleAuth({
5074
+ scopes: [ANDROID_PUBLISHER_SCOPE],
5075
+ keyFile,
5076
+ credentials
5077
+ });
5078
+ const token = await auth.getAccessToken();
5079
+ if (!token) {
5080
+ throw new Error("Google Play \u8BA4\u8BC1\u672A\u8FD4\u56DE\u8BBF\u95EE\u4EE4\u724C");
5081
+ }
5082
+ return token;
5083
+ }
5084
+ async deleteEditBestEffort(encodedPackageName, editId, accessToken) {
5085
+ try {
5086
+ await this.httpClient.delete(
5087
+ `${PUBLISH_API_ROOT}/applications/${encodedPackageName}/edits/${encodeURIComponent(editId)}`,
5088
+ { headers: { Authorization: `Bearer ${accessToken}` } }
5089
+ );
5090
+ } catch {
5091
+ 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");
5092
+ }
5093
+ }
5094
+ describeError(error, stage) {
5095
+ const status = error?.response?.status;
5096
+ const rawMessage = error?.response?.data?.error?.message || error?.response?.data?.message || error?.message || "\u672A\u77E5\u9519\u8BEF";
5097
+ const safeMessage = this.redactAndTruncate(String(rawMessage));
5098
+ if (/version\s*code|versionCode/i.test(safeMessage)) {
5099
+ return `Google Play \u62D2\u7EDD\u8BE5 versionCode\uFF0C\u8BF7\u63D0\u9AD8 Flutter \u6784\u5EFA\u53F7\u540E\u91CD\u65B0\u751F\u6210 AAB\uFF1A${safeMessage}`;
5100
+ }
5101
+ if (status === 401) {
5102
+ return "Google Play \u8BA4\u8BC1\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u670D\u52A1\u8D26\u53F7 JSON \u662F\u5426\u6709\u6548";
5103
+ }
5104
+ if (status === 403) {
5105
+ 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";
5106
+ }
5107
+ if (status === 404) {
5108
+ if (stage === "update_internal_track") {
5109
+ 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";
5110
+ }
5111
+ if (stage !== "create_edit") {
5112
+ return `Google Play API \u5728 ${stage} \u9636\u6BB5\u672A\u627E\u5230\u76EE\u6807\u8D44\u6E90\uFF1A${safeMessage}`;
5113
+ }
5114
+ 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";
5115
+ }
5116
+ if (error?.code === "ECONNABORTED" || /timeout/i.test(safeMessage)) {
5117
+ return "Google Play \u4E0A\u4F20\u8D85\u65F6\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u540E\u91CD\u8BD5\uFF1B\u672C\u5730 AAB \u672A\u88AB\u5220\u9664";
5118
+ }
5119
+ return `Google Play API \u8BF7\u6C42\u5931\u8D25${status ? `\uFF08HTTP ${status}\uFF09` : ""}\uFF1A${safeMessage}`;
5120
+ }
5121
+ redactAndTruncate(message) {
5122
+ 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);
5123
+ }
5124
+ failure(message, error) {
5125
+ return {
5126
+ platform: "google_play",
5127
+ success: false,
5128
+ message,
5129
+ // 原始 Axios 错误可能包含 Authorization 请求头,只返回脱敏后的 Error。
5130
+ error: error ? new Error(message) : void 0
5131
+ };
5132
+ }
5133
+ };
4556
5134
  // Annotate the CommonJS export names for ESM import in node:
4557
5135
  0 && (module.exports = {
4558
5136
  AdHocUploader,
4559
5137
  AppleJWTGenerator,
4560
5138
  AppleStoreUploader,
4561
5139
  ConfigValidator,
5140
+ DEFAULT_RELEASE_RECEIPT_LIMIT,
4562
5141
  DEFAULT_RETRY_CONFIG,
4563
5142
  EnvValidator,
4564
5143
  ErrorCategory,
5144
+ GooglePlayUploader,
4565
5145
  HarmonyUploader,
4566
5146
  HuaweiUploader,
4567
5147
  OppoUploader,
4568
5148
  PgyerUploader,
5149
+ RELEASE_RECEIPT_SCHEMA_VERSION,
5150
+ ReleaseReceiptStore,
4569
5151
  RetryStrategy,
4570
5152
  TencentUploader,
4571
5153
  TestFlightUploader,
4572
5154
  UploadManager,
4573
5155
  VivoUploader,
4574
5156
  XiaomiUploader,
5157
+ createProjectKey,
5158
+ createReleaseReceipt,
4575
5159
  createUploadManager,
4576
5160
  createUploadRetryStrategy,
4577
- parsePackageInfo
5161
+ createUploadTargets,
5162
+ formatReleaseReceipt,
5163
+ parsePackageInfo,
5164
+ resolveReleaseProjectRoot,
5165
+ sanitizeReleaseMessage
4578
5166
  });
4579
5167
  //# sourceMappingURL=index.cjs.map