@sitepong/fastnode 0.2.2 → 0.3.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 +58 -1
- package/dist/actions/index.cjs +848 -54
- package/dist/actions/index.cjs.map +1 -1
- package/dist/actions/index.mjs +849 -55
- package/dist/actions/index.mjs.map +1 -1
- package/dist/apple-session-jUzBrql5.d.cts +122 -0
- package/dist/apple-session-jUzBrql5.d.ts +122 -0
- package/dist/cert/index.cjs +782 -736
- package/dist/cert/index.cjs.map +1 -1
- package/dist/cert/index.d.cts +5 -4
- package/dist/cert/index.d.ts +5 -4
- package/dist/cert/index.mjs +782 -737
- package/dist/cert/index.mjs.map +1 -1
- package/dist/{cert-Ddl7FpFT.d.ts → cert-RvXmdMke.d.ts} +2 -2
- package/dist/{cert-FehieKeb.d.cts → cert-eOoxGMN1.d.cts} +2 -2
- package/dist/cli/index.cjs +1321 -60
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.d.cts +3 -2
- package/dist/cli/index.d.ts +3 -2
- package/dist/cli/index.mjs +1322 -61
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/index.cjs +443 -31
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +3 -2
- package/dist/client/index.d.ts +3 -2
- package/dist/client/index.mjs +443 -31
- package/dist/client/index.mjs.map +1 -1
- package/dist/deliver/index.cjs +334 -13
- package/dist/deliver/index.cjs.map +1 -1
- package/dist/deliver/index.d.cts +4 -3
- package/dist/deliver/index.d.ts +4 -3
- package/dist/deliver/index.mjs +330 -15
- package/dist/deliver/index.mjs.map +1 -1
- package/dist/{enums-B609j2rb.d.cts → enums-yXNVh3UI.d.cts} +8 -1
- package/dist/{enums-B609j2rb.d.ts → enums-yXNVh3UI.d.ts} +8 -1
- package/dist/gradle/index.cjs +222 -0
- package/dist/gradle/index.cjs.map +1 -0
- package/dist/gradle/index.d.cts +1 -0
- package/dist/gradle/index.d.ts +1 -0
- package/dist/gradle/index.mjs +217 -0
- package/dist/gradle/index.mjs.map +1 -0
- package/dist/gym/index.d.cts +2 -2
- package/dist/gym/index.d.ts +2 -2
- package/dist/index-8djTPW8O.d.ts +104 -0
- package/dist/{index-DrUpRXRG.d.cts → index-B6-MJpO4.d.cts} +1 -1
- package/dist/{index-D13sn8DO.d.cts → index-BE7bwNff.d.cts} +1 -1
- package/dist/index-BGhV0gS-.d.ts +65 -0
- package/dist/index-BaNcf7jt.d.cts +201 -0
- package/dist/{index-DZoPGCbE.d.ts → index-BvyZ4uiw.d.ts} +3 -3
- package/dist/{index-B8FOfxOw.d.ts → index-C01Rkrir.d.ts} +2 -2
- package/dist/{index-YcKz4aRS.d.ts → index-C9mHBJED.d.ts} +1 -1
- package/dist/{index-BICa8odo.d.cts → index-CAEiXVWd.d.ts} +9 -1
- package/dist/index-CBf0-NUQ.d.ts +201 -0
- package/dist/index-CXJlvmB4.d.cts +63 -0
- package/dist/index-CXJlvmB4.d.ts +63 -0
- package/dist/{index-CNHf7I4v.d.cts → index-D1GHEM3c.d.ts} +32 -3
- package/dist/index-DFstwgGK.d.cts +65 -0
- package/dist/{index-Bk1Gd-qE.d.cts → index-DGkgRnZp.d.cts} +2 -2
- package/dist/{index-CplRWSUc.d.ts → index-DLJso7Vs.d.cts} +32 -3
- package/dist/{index-BZWmOTWE.d.cts → index-DWOLZNlz.d.cts} +2 -2
- package/dist/{index-OelCoOth.d.cts → index-DfkIz7i0.d.cts} +18 -4
- package/dist/{index-CtPyeMLH.d.ts → index-DjC_gmnL.d.ts} +18 -4
- package/dist/index-DnHQ2uT2.d.cts +104 -0
- package/dist/{index-CCtYhPN9.d.ts → index-DrmS802s.d.cts} +331 -130
- package/dist/index-Dv-Mek07.d.cts +84 -0
- package/dist/index-Dv-Mek07.d.ts +84 -0
- package/dist/{index-oVyLLIEK.d.cts → index-icIH5e5V.d.cts} +3 -3
- package/dist/{index-DI1ZHr-3.d.ts → index-laE3_wm9.d.cts} +9 -1
- package/dist/{index-BKUapQP2.d.cts → index-lsuS2R1T.d.ts} +331 -130
- package/dist/{index-DImN9PWc.d.ts → index-mEEB7An6.d.ts} +1 -1
- package/dist/{index-DFni8oZt.d.ts → index-rTZOdMRE.d.ts} +2 -2
- package/dist/index.cjs +3342 -959
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +237 -14
- package/dist/index.d.ts +237 -14
- package/dist/index.mjs +3335 -963
- package/dist/index.mjs.map +1 -1
- package/dist/lane/index.cjs +848 -54
- package/dist/lane/index.cjs.map +1 -1
- package/dist/lane/index.mjs +849 -55
- package/dist/lane/index.mjs.map +1 -1
- package/dist/match/index.d.cts +6 -5
- package/dist/match/index.d.ts +6 -5
- package/dist/pem/index.d.cts +4 -3
- package/dist/pem/index.d.ts +4 -3
- package/dist/pilot/index.cjs +8 -4
- package/dist/pilot/index.cjs.map +1 -1
- package/dist/pilot/index.d.cts +4 -3
- package/dist/pilot/index.d.ts +4 -3
- package/dist/pilot/index.mjs +10 -6
- package/dist/pilot/index.mjs.map +1 -1
- package/dist/playstore/index.cjs +207 -0
- package/dist/playstore/index.cjs.map +1 -0
- package/dist/playstore/index.d.cts +1 -0
- package/dist/playstore/index.d.ts +1 -0
- package/dist/playstore/index.mjs +203 -0
- package/dist/playstore/index.mjs.map +1 -0
- package/dist/precheck/index.d.cts +4 -3
- package/dist/precheck/index.d.ts +4 -3
- package/dist/privacy/index.cjs +207 -0
- package/dist/privacy/index.cjs.map +1 -0
- package/dist/privacy/index.d.cts +2 -0
- package/dist/privacy/index.d.ts +2 -0
- package/dist/privacy/index.mjs +195 -0
- package/dist/privacy/index.mjs.map +1 -0
- package/dist/produce/index.cjs +32 -0
- package/dist/produce/index.cjs.map +1 -1
- package/dist/produce/index.d.cts +4 -3
- package/dist/produce/index.d.ts +4 -3
- package/dist/produce/index.mjs +32 -1
- package/dist/produce/index.mjs.map +1 -1
- package/dist/review/index.cjs +141 -0
- package/dist/review/index.cjs.map +1 -0
- package/dist/review/index.d.cts +2 -0
- package/dist/review/index.d.ts +2 -0
- package/dist/review/index.mjs +137 -0
- package/dist/review/index.mjs.map +1 -0
- package/dist/sigh/index.d.cts +4 -3
- package/dist/sigh/index.d.ts +4 -3
- package/dist/xcode/index.cjs +1 -1
- package/dist/xcode/index.cjs.map +1 -1
- package/dist/xcode/index.mjs +1 -1
- package/dist/xcode/index.mjs.map +1 -1
- package/package.json +27 -1
- package/dist/index-B9QeTpQK.d.cts +0 -99
- package/dist/index-xykpq6Lz.d.ts +0 -99
package/dist/pilot/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/client/srp.ts","../../src/client/request.ts","../../src/pilot/upload-altool.ts","../../src/pilot/transporter-install.ts","../../src/pilot/upload-transporter.ts","../../src/pilot/classify.ts","../../src/pilot/index.ts"],"names":["existsSync","spawn","mkdtemp","join","tmpdir","writeFile","rm"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * SRP (Secure Remote Password) implementation\n * 1:1 port of the fastlane-sirp Ruby gem\n * Used for Apple ID authentication\n */\n\nimport { createHash, randomBytes, pbkdf2Sync } from \"node:crypto\";\n\n// ── RFC 5054 SRP Group Parameters ──────────────────────────────────────────\n\n// Copied exactly from fastlane-sirp parameters.rb (2048-bit group)\nconst N_2048 = BigInt(\n \"0x\" +\n \"AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050\" +\n \"A37329CBB4A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50\" +\n \"E8083969EDB767B0CF6095179A163AB3661A05FBD5FAAAE82918A9962F0B93B8\" +\n \"55F97993EC975EEAA80D740ADBF4FF747359D041D5C33EA71D281E446B14773B\" +\n \"CA97B43A23FB801676BD207A436C6481F1D2B9078717461A5B9D32E688F87748\" +\n \"544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB3786160279004E57AE6\" +\n \"AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DBFBB\" +\n \"694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73\"\n);\nconst g_2048 = 2n;\n\n// ── Utility Functions ──────────────────────────────────────────────────────\n\nfunction numToHex(num: bigint): string {\n let hex = num.toString(16);\n if (hex.length % 2 !== 0) hex = \"0\" + hex;\n return hex.toLowerCase();\n}\n\nfunction hexToBytes(hex: string): Buffer {\n return Buffer.from(hex, \"hex\");\n}\n\nfunction shaHex(h: string, algorithm: string): string {\n return createHash(algorithm).update(hexToBytes(h)).digest(\"hex\");\n}\n\nfunction shaStr(s: string, algorithm: string): string {\n return createHash(algorithm).update(s).digest(\"hex\");\n}\n\nfunction modExp(base: bigint, exp: bigint, mod: bigint): bigint {\n if (mod === 1n) return 0n;\n let result = 1n;\n base = base % mod;\n while (exp > 0n) {\n if (exp % 2n === 1n) {\n result = (result * base) % mod;\n }\n exp = exp / 2n;\n base = (base * base) % mod;\n }\n return result;\n}\n\nfunction H(algorithm: string, n: bigint, ...args: (bigint | string | undefined)[]): bigint {\n const nHex = numToHex(n);\n // Must match Ruby: nlen = 2 * ((('%x' % [n]).length * 4 + 7) >> 3)\n // Use floor division (>>3), NOT ceiling\n const nlen = 2 * (((nHex.length * 4 + 7) >> 3));\n\n let hashin = \"\";\n for (const s of args) {\n if (s === undefined) continue;\n const shex = typeof s === \"string\" ? s : numToHex(s);\n if (shex.length > nlen) {\n throw new Error(\"Bit width does not match - client uses different prime\");\n }\n hashin += \"0\".repeat(nlen - shex.length) + shex;\n }\n\n const hashed = shaHex(hashin, algorithm);\n return BigInt(\"0x\" + hashed) % n;\n}\n\n// ── SRP Client ─────────────────────────────────────────────────────────────\n\nexport class SRPClient {\n private N: bigint;\n private g: bigint;\n private hashAlg: string;\n private k: bigint;\n private a: bigint;\n A: string;\n private S: string | null = null;\n private K: string | null = null;\n M: string | null = null;\n H_AMK: string | null = null;\n\n constructor(group: number = 2048) {\n if (group !== 2048) throw new Error(\"Only 2048-bit group is supported\");\n this.N = N_2048;\n this.g = g_2048;\n this.hashAlg = \"sha256\";\n this.k = H(this.hashAlg, this.N, this.N, this.g);\n // Generate random 'a'\n this.a = BigInt(\"0x\" + randomBytes(256).toString(\"hex\"));\n // Compute A = g^a mod N\n this.A = numToHex(modExp(this.g, this.a, this.N));\n }\n\n startAuthentication(): string {\n return this.A;\n }\n\n processChallenge(\n username: string,\n password: string, // hex-encoded encrypted password\n xsalt: string, // hex\n xbb: string, // hex\n isPasswordEncrypted: boolean = false\n ): string | false {\n const bb = BigInt(\"0x\" + xbb);\n\n // SRP-6a safety check\n if (bb % this.N === 0n) return false;\n\n let x: bigint;\n if (isPasswordEncrypted) {\n x = this.calcXHex(password, xsalt);\n } else {\n x = this.calcX(username, password, xsalt);\n }\n\n const u = H(this.hashAlg, this.N, this.A, xbb);\n if (u === 0n) return false;\n\n // S = (B - k * g^x) ^ (a + u*x) mod N\n const gx = modExp(this.g, x, this.N);\n let base = (bb - this.k * gx) % this.N;\n if (base < 0n) base += this.N;\n const exp = this.a + x * u;\n this.S = numToHex(modExp(base, exp, this.N));\n this.K = shaHex(this.S, this.hashAlg);\n\n // M = H(H(N) xor H(g), H(I), s, A, B, K)\n const hxor = H(this.hashAlg, this.N, this.N) ^ H(this.hashAlg, this.N, this.g);\n const buf =\n numToHex(hxor) +\n shaStr(username, this.hashAlg) +\n xsalt +\n this.A +\n xbb +\n this.K;\n const bytes = hexToBytes(buf);\n this.M = createHash(this.hashAlg).update(bytes).digest(\"hex\");\n\n // H_AMK = H(A, M, K)\n const amkBytes = hexToBytes(this.A + this.M + this.K);\n this.H_AMK = numToHex(\n BigInt(\"0x\" + createHash(this.hashAlg).update(amkBytes).digest(\"hex\"))\n );\n\n return this.M;\n }\n\n private calcX(username: string, password: string, xsalt: string): bigint {\n const spad = xsalt.length % 2 !== 0 ? \"0\" : \"\";\n const inner = shaStr(`${username}:${password}`, this.hashAlg);\n return BigInt(\"0x\" + shaHex(spad + xsalt + inner, this.hashAlg));\n }\n\n private calcXHex(xpassword: string, xsalt: string): bigint {\n // calc_x_hex: sha_hex(xsalt + sha_hex(\":\" hex + xpassword), hash)\n const colonHex = \":\".charCodeAt(0).toString(16);\n const inner = shaHex(colonHex + xpassword, this.hashAlg);\n return BigInt(\"0x\" + shaHex(xsalt + inner, this.hashAlg));\n }\n}\n\n// ── Apple PBKDF2 password derivation ───────────────────────────────────────\n\nexport function applePbkdf2(\n password: string,\n salt: Buffer,\n iterations: number,\n keyLength: number,\n protocol: string\n): Buffer {\n if (![\"s2k\", \"s2k_fo\"].includes(protocol)) {\n throw new Error(`Unsupported protocol '${protocol}' for pbkdf2`);\n }\n\n // First hash the password with SHA-256\n let passwordBytes: Buffer | string = createHash(\"sha256\").update(password).digest();\n\n if (protocol === \"s2k_fo\") {\n // Legacy: convert to hex string\n passwordBytes = passwordBytes.toString(\"hex\");\n }\n\n return pbkdf2Sync(passwordBytes, salt, iterations, keyLength, \"sha256\");\n}\n","import type { TokenManager } from \"./auth.js\";\nimport { ProgramLicenseAgreementError } from \"./apple-session.js\";\nimport type {\n JsonApiErrorResponse,\n JsonApiListResponse,\n JsonApiResponse,\n ListParams,\n RequestOptions,\n} from \"./types.js\";\n\nconst PRODUCTION_BASE = \"https://api.appstoreconnect.apple.com\";\nconst ENTERPRISE_BASE = \"https://api.enterprise.developer.apple.com\";\n\nconst MAX_RETRIES = 5;\nconst INITIAL_BACKOFF_MS = 1000;\nconst MAX_BACKOFF_MS = 60_000;\n/** Fastlane's TooManyRequestsError gives up after 3600s cumulative backoff. */\nconst MAX_RATE_LIMIT_BACKOFF_MS = 3_600_000;\n\nexport class AppStoreConnectError extends Error {\n constructor(\n message: string,\n public status: number,\n public errors: JsonApiErrorResponse[\"errors\"] = []\n ) {\n super(message);\n this.name = \"AppStoreConnectError\";\n }\n}\n\nexport class RateLimitError extends AppStoreConnectError {\n constructor(public retryAfter: number) {\n super(`Rate limited. Retry after ${retryAfter}s`, 429);\n this.name = \"RateLimitError\";\n }\n}\n\nexport class RequestClient {\n private baseUrl: string;\n private tokenManager: TokenManager;\n\n constructor(tokenManager: TokenManager, enterprise = false) {\n this.tokenManager = tokenManager;\n this.baseUrl = enterprise ? ENTERPRISE_BASE : PRODUCTION_BASE;\n }\n\n async request<T>(path: string, options: RequestOptions = {}): Promise<T> {\n const { method = \"GET\", body, params, headers: extraHeaders } = options;\n\n let url = `${this.baseUrl}${path}`;\n if (params) {\n const searchParams = new URLSearchParams();\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined) continue;\n if (Array.isArray(value)) {\n searchParams.set(key, value.join(\",\"));\n } else {\n searchParams.set(key, String(value));\n }\n }\n const qs = searchParams.toString();\n if (qs) url += `?${qs}`;\n }\n\n let lastError: Error | null = null;\n // Soft-cumulative backoff for 429 that doubles on each hit, mirroring\n // Fastlane's Spaceship::ConnectAPI::APIClient#with_asc_retry 429 handler:\n // `Kernel.sleep(backoff); backoff *= 2`, giving up past 3600s.\n let rateLimitBackoffMs = INITIAL_BACKOFF_MS;\n\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n const token = await this.tokenManager.getToken();\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n ...extraHeaders,\n };\n\n const res = await fetch(url, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n });\n\n if (res.ok) {\n if (res.status === 204) return undefined as T;\n return (await res.json()) as T;\n }\n\n // Handle 401 — refresh token and retry once\n if (res.status === 401 && attempt === 0) {\n this.tokenManager.invalidate();\n continue;\n }\n\n // Handle 429 — rate limit. Prefer server's Retry-After header when\n // provided, otherwise fall back to Fastlane's doubling backoff.\n if (res.status === 429) {\n if (rateLimitBackoffMs > MAX_RATE_LIMIT_BACKOFF_MS) {\n throw new RateLimitError(Math.floor(rateLimitBackoffMs / 1000));\n }\n const retryAfterHeader = res.headers.get(\"Retry-After\");\n const sleepMs = retryAfterHeader\n ? parseInt(retryAfterHeader, 10) * 1000\n : rateLimitBackoffMs;\n await sleep(sleepMs);\n rateLimitBackoffMs *= 2;\n continue;\n }\n\n // Parse error body early so we can inspect it for 403/502 special cases.\n const errorBody = await res.json().catch(() => null);\n const errors = (errorBody as JsonApiErrorResponse)?.errors || [];\n\n // Handle 403 — detect missing/expired program license agreement.\n // Mirrors api_client.rb's ProgramLicenseAgreementUpdated branch.\n if (res.status === 403) {\n const firstCode = errors[0]?.code;\n if (firstCode === \"FORBIDDEN.REQUIRED_AGREEMENTS_MISSING_OR_EXPIRED\") {\n const detail =\n errors.map((e) => e.detail || e.title).join(\"; \") ||\n \"Required agreements are missing or expired.\";\n throw new ProgramLicenseAgreementError(\n `${detail} Please manually log into your Apple Developer account to review and accept the updated agreement.`,\n []\n );\n }\n }\n\n // Handle 502 — special-case \"Could not process this request\" retry.\n // Mirrors api_client.rb's BadGatewayError branch (issue fastlane#19264).\n if (res.status === 502 && attempt < MAX_RETRIES) {\n const bodyText = errorBody ? JSON.stringify(errorBody) : \"\";\n if (bodyText.includes(\"Could not process this request\")) {\n const backoff = Math.min(\n INITIAL_BACKOFF_MS * Math.pow(2, attempt),\n MAX_BACKOFF_MS\n );\n await sleep(backoff + Math.random() * 500);\n continue;\n }\n }\n\n // Handle 5xx — retry with exponential backoff\n if (res.status >= 500 && attempt < MAX_RETRIES) {\n const backoff = Math.min(\n INITIAL_BACKOFF_MS * Math.pow(2, attempt),\n MAX_BACKOFF_MS\n );\n await sleep(backoff + Math.random() * 500);\n continue;\n }\n\n const detail = errors.map((e) => e.detail || e.title).join(\"; \") || `HTTP ${res.status}`;\n\n lastError = new AppStoreConnectError(detail, res.status, errors);\n break;\n }\n\n throw lastError || new Error(\"Request failed after retries\");\n }\n\n async get<T>(path: string, params?: Record<string, string | string[] | number | undefined>): Promise<T> {\n return this.request<T>(path, { method: \"GET\", params });\n }\n\n async post<T>(path: string, body: unknown): Promise<T> {\n return this.request<T>(path, { method: \"POST\", body });\n }\n\n async patch<T>(path: string, body: unknown): Promise<T> {\n return this.request<T>(path, { method: \"PATCH\", body });\n }\n\n async delete(path: string): Promise<void> {\n await this.request(path, { method: \"DELETE\" });\n }\n\n // Build query params from ListParams\n buildListParams(params: ListParams): Record<string, string | undefined> {\n const result: Record<string, string | undefined> = {};\n\n if (params.filter) {\n for (const [key, value] of Object.entries(params.filter)) {\n result[`filter[${key}]`] = Array.isArray(value) ? value.join(\",\") : value;\n }\n }\n\n if (params.include) {\n result[\"include\"] = params.include;\n }\n\n if (params.fields) {\n for (const [key, value] of Object.entries(params.fields)) {\n result[`fields[${key}]`] = value;\n }\n }\n\n if (params.limit !== undefined) {\n result[\"limit\"] = String(params.limit);\n }\n\n if (params.sort) {\n result[\"sort\"] = params.sort;\n }\n\n if (params.cursor) {\n // ASC uses page[after] for cursor pagination\n result[\"page[after]\"] = params.cursor;\n }\n\n return result;\n }\n\n async list<T>(path: string, params: ListParams = {}): Promise<JsonApiListResponse<T>> {\n const queryParams = this.buildListParams(params);\n return this.get<JsonApiListResponse<T>>(path, queryParams);\n }\n\n async *paginate<T>(path: string, params: ListParams = {}): AsyncGenerator<T, void, undefined> {\n let cursor: string | undefined = params.cursor;\n\n while (true) {\n const response = await this.list<T>(path, { ...params, cursor });\n\n for (const item of response.data) {\n yield item;\n }\n\n // Check for next page\n const nextLink = response.links?.next;\n if (!nextLink) break;\n\n // Extract cursor from next link\n const nextUrl = new URL(nextLink);\n cursor = nextUrl.searchParams.get(\"page[after]\") || undefined;\n if (!cursor) break;\n }\n }\n\n async all<T>(path: string, params: ListParams = {}): Promise<T[]> {\n const results: T[] = [];\n for await (const item of this.paginate<T>(path, params)) {\n results.push(item);\n }\n return results;\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import { spawn } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { writeFile, unlink, mkdtemp, rm } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { tmpdir } from \"node:os\";\nimport type { PilotUploadOptions, UploadResult } from \"./types.js\";\n\n/**\n * Upload a build to TestFlight via Apple's `altool` (macOS only).\n *\n * altool ships with Xcode command-line tools. It provides richer error\n * output than iTMSTransporter but is only available on macOS.\n */\nexport async function uploadWithAltool(\n options: PilotUploadOptions\n): Promise<UploadResult> {\n if (!existsSync(options.ipaPath)) {\n throw new Error(`File not found: ${options.ipaPath}`);\n }\n\n const platform = options.platform ?? \"ios\";\n const timeout = options.timeout ?? 1_800_000; // 30 min\n\n // Write temp .p8 key file — altool reads from API_PRIVATE_KEYS_DIR\n const keyDir = await mkdtemp(join(tmpdir(), \"fastnode-altool-\"));\n const keyPath = join(keyDir, `AuthKey_${options.apiKeyId}.p8`);\n await writeFile(keyPath, options.apiPrivateKey);\n\n return new Promise<UploadResult>((resolve) => {\n const args = [\n \"altool\",\n \"--upload-app\",\n \"-f\",\n options.ipaPath,\n \"-t\",\n platform,\n \"--apiKey\",\n options.apiKeyId,\n \"--apiIssuer\",\n options.apiIssuerId,\n ];\n\n const child = spawn(\"xcrun\", args, {\n env: { ...process.env, API_PRIVATE_KEYS_DIR: keyDir },\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let output = \"\";\n const timer = setTimeout(() => {\n child.kill(\"SIGTERM\");\n }, timeout);\n\n const onData = (data: Buffer) => {\n const chunk = data.toString();\n output += chunk;\n options.onOutput?.(chunk);\n };\n\n child.stdout.on(\"data\", onData);\n child.stderr.on(\"data\", onData);\n\n child.on(\"close\", async (code) => {\n clearTimeout(timer);\n await unlink(keyPath).catch(() => {});\n await rm(keyDir, { recursive: true, force: true }).catch(() => {});\n resolve({ success: code === 0, output, backend: \"altool\" });\n });\n });\n}\n","import { spawn } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\n\n/**\n * Locate the iTMSTransporter binary on this host.\n *\n * Apple ships iTMSTransporter as a Java-based tool bundled with Xcode\n * on macOS, and as a standalone Linux distribution used by media\n * publishers for CI. Either works for uploading IPAs — same protocol,\n * same ASC API key format.\n *\n * Resolution order:\n * 1. ITMSTRANSPORTER_BIN env var (pinning for prod deployments)\n * 2. Well-known system paths for the host OS\n * 3. Throw with a clear install hint\n *\n * We deliberately do NOT auto-download Transporter. Apple's download\n * URL is not versioned and the license/redistribution story is fuzzy.\n * Prod hosts should install it once via apt/brew/manual, then pin via\n * env var so CI is reproducible.\n */\nexport async function resolveTransporter(): Promise<string> {\n // 1. Env override — the canonical path for prod\n const override = process.env.ITMSTRANSPORTER_BIN;\n if (override) {\n if (!existsSync(override)) {\n throw new Error(\n `ITMSTRANSPORTER_BIN is set to \"${override}\" but that path does not exist.`\n );\n }\n return override;\n }\n\n // 2. Well-known system paths, in preferred order per OS\n const candidates =\n process.platform === \"darwin\"\n ? [\n // Xcode 15+: Transporter.app bundled with Xcode\n \"/Applications/Xcode.app/Contents/SharedFrameworks/ContentDeliveryServices.framework/Versions/A/itms/bin/iTMSTransporter\",\n // Standalone Transporter.app from the Mac App Store\n \"/Applications/Transporter.app/Contents/itms/bin/iTMSTransporter\",\n ]\n : [\n \"/usr/local/itms/bin/iTMSTransporter\",\n \"/opt/itms/bin/iTMSTransporter\",\n \"/usr/local/bin/iTMSTransporter\",\n ];\n\n for (const path of candidates) {\n if (existsSync(path)) return path;\n }\n\n throw new Error(\n [\n \"iTMSTransporter not found.\",\n \"\",\n \"Install options:\",\n process.platform === \"darwin\"\n ? \" - Install Xcode (includes Transporter)\"\n : \" - Download Apple's standalone iTMSTransporter package for Linux\",\n \" - Or set ITMSTRANSPORTER_BIN to the absolute path of the binary\",\n \"\",\n `Searched: ${candidates.join(\", \")}`,\n ].join(\"\\n\")\n );\n}\n\n/**\n * Verify that Java is available on PATH. iTMSTransporter is a Java\n * application and won't run without a JRE.\n */\nexport async function verifyJava(): Promise<{ version: string }> {\n return new Promise((resolve, reject) => {\n const child = spawn(\"java\", [\"-version\"], { stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n let stderr = \"\";\n child.stderr.on(\"data\", (d: Buffer) => {\n stderr += d.toString();\n });\n child.on(\"error\", (err) => {\n reject(\n new Error(\n `java not found on PATH (${err.message}). Install Java 17 LTS and ensure it is on PATH.`\n )\n );\n });\n child.on(\"close\", (code) => {\n if (code !== 0) {\n reject(new Error(`java -version exited ${code}: ${stderr}`));\n return;\n }\n // Output format: `openjdk version \"17.0.8\" 2023-07-18 ...`\n const match = stderr.match(/version \"([^\"]+)\"/);\n resolve({ version: match?.[1] ?? \"unknown\" });\n });\n });\n}\n","import { spawn } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { writeFile, mkdtemp, rm } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { tmpdir } from \"node:os\";\nimport type { PilotUploadOptions, UploadResult } from \"./types.js\";\nimport { resolveTransporter, verifyJava } from \"./transporter-install.js\";\n\n/**\n * Upload a build to TestFlight via Apple's iTMSTransporter (Java).\n *\n * This path works on Linux (and macOS) and does NOT require Xcode.\n * It is the primary upload path for the ship worker LXC.\n *\n * iTMSTransporter reads the ASC API private key from a directory\n * named by API_PRIVATE_KEYS_DIR, looking for the file `AuthKey_<keyId>.p8`.\n */\nexport async function uploadWithTransporter(\n options: PilotUploadOptions\n): Promise<UploadResult> {\n if (!existsSync(options.ipaPath)) {\n throw new Error(`File not found: ${options.ipaPath}`);\n }\n\n // Fail fast if Java is missing — avoids a cryptic exec error deep inside\n // Transporter. This is cheap (~20ms) and only runs once per upload.\n await verifyJava();\n\n const transporterBin = await resolveTransporter();\n\n const platform = options.platform ?? \"ios\";\n const timeout = options.timeout ?? 1_800_000; // 30 min\n\n // Write temp .p8 key file\n const keyDir = await mkdtemp(join(tmpdir(), \"fastnode-itms-\"));\n const keyPath = join(keyDir, `AuthKey_${options.apiKeyId}.p8`);\n await writeFile(keyPath, options.apiPrivateKey);\n\n return new Promise<UploadResult>((resolve) => {\n const args = [\n \"-m\", \"upload\",\n \"-assetFile\", options.ipaPath,\n \"-apiKey\", options.apiKeyId,\n \"-apiIssuer\", options.apiIssuerId,\n // WONoPause prevents the JVM from waiting on stdin in non-TTY\n // environments (CI, LaunchAgent) where it would hang forever.\n \"-WONoPause\", \"true\",\n ];\n\n // Transporter distinguishes iOS vs macOS/tvOS via a -bundleType flag\n // on some code paths. For standard IPAs the protocol is platform-aware\n // via the asset itself, so we only pass the hint when it's non-default.\n if (platform !== \"ios\") {\n args.push(\"-bundleType\", platform);\n }\n\n const child = spawn(transporterBin, args, {\n env: {\n ...process.env,\n API_PRIVATE_KEYS_DIR: keyDir,\n },\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let output = \"\";\n const timer = setTimeout(() => {\n child.kill(\"SIGTERM\");\n }, timeout);\n\n const onData = (data: Buffer) => {\n const chunk = data.toString();\n output += chunk;\n options.onOutput?.(chunk);\n };\n\n child.stdout.on(\"data\", onData);\n child.stderr.on(\"data\", onData);\n\n child.on(\"close\", async (code) => {\n clearTimeout(timer);\n await rm(keyDir, { recursive: true, force: true }).catch(() => {});\n resolve({ success: code === 0, output, backend: \"transporter\" });\n });\n\n child.on(\"error\", async (err) => {\n clearTimeout(timer);\n await rm(keyDir, { recursive: true, force: true }).catch(() => {});\n resolve({\n success: false,\n output: output + `\\nProcess error: ${err.message}`,\n backend: \"transporter\",\n });\n });\n });\n}\n","/**\n * Classify upload failures into transient vs. permanent buckets.\n *\n * Apple's App Store Connect / iTMSTransporter backend is flaky:\n * the finalize step (\"CHANGE UPLOAD STATE TO COMPLETE\") periodically\n * returns 500, and the Java transporter surfaces various connection\n * errors during long uploads. These are all safe to retry — the IPA\n * is deterministic and Apple deduplicates on build number.\n *\n * Non-transient errors (invalid binary, duplicate build number, auth\n * failure) should NOT be retried — they will never succeed without\n * the caller taking some action.\n */\n\nexport type TransientUploadError =\n | \"apple_5xx\"\n | \"finalize_failed\"\n | \"network_timeout\"\n | \"connection_reset\"\n | \"transporter_jvm_error\";\n\ninterface ClassifierRule {\n pattern: RegExp;\n kind: TransientUploadError;\n}\n\nconst RULES: ClassifierRule[] = [\n // Apple's finalize-upload call returning 5xx — the most common transient\n // failure we see in practice. Example:\n // \"CHANGE UPLOAD STATE TO COMPLETE (UPLOADING ASSET DESCRIPTION):\n // received status code 500; internal server error.\"\n { pattern: /CHANGE UPLOAD STATE TO COMPLETE/i, kind: \"finalize_failed\" },\n\n // Generic 5xx from ASC backend services\n { pattern: /status code 5\\d\\d/i, kind: \"apple_5xx\" },\n { pattern: /internal server error/i, kind: \"apple_5xx\" },\n { pattern: /Service Unavailable/i, kind: \"apple_5xx\" },\n { pattern: /Bad Gateway/i, kind: \"apple_5xx\" },\n\n // Network-level interruptions during the upload body transfer\n { pattern: /Connection reset/i, kind: \"connection_reset\" },\n { pattern: /SocketException/i, kind: \"connection_reset\" },\n { pattern: /Broken pipe/i, kind: \"connection_reset\" },\n\n // Timeouts — distinct from resets because the other side may have\n // actually processed the request, so dedupe logic may matter.\n { pattern: /Read timed out/i, kind: \"network_timeout\" },\n { pattern: /SocketTimeoutException/i, kind: \"network_timeout\" },\n { pattern: /Operation timed out/i, kind: \"network_timeout\" },\n\n // JVM-internal errors from iTMSTransporter (OOM, thread interruption, etc.)\n { pattern: /java\\.lang\\.OutOfMemoryError/i, kind: \"transporter_jvm_error\" },\n { pattern: /EOFException/i, kind: \"transporter_jvm_error\" },\n];\n\n/**\n * Returns the transient-error kind if the output matches any known\n * retry-worthy pattern, or `null` if the error is non-transient.\n */\nexport function classifyUploadError(output: string): TransientUploadError | null {\n for (const { pattern, kind } of RULES) {\n if (pattern.test(output)) return kind;\n }\n return null;\n}\n\n/**\n * Convenience: `true` if the upload should be retried.\n */\nexport function isTransientUploadError(output: string): boolean {\n return classifyUploadError(output) !== null;\n}\n","import emojiRegex from \"emoji-regex\";\nimport type { AppStoreConnectClient } from \"../client/index.js\";\nimport { AppStoreConnectError } from \"../client/request.js\";\nimport type { Build } from \"../types/index.js\";\nimport type {\n PilotUploadOptions,\n UploadBackend,\n UploadResult,\n} from \"./types.js\";\nimport { uploadWithAltool } from \"./upload-altool.js\";\nimport { uploadWithTransporter } from \"./upload-transporter.js\";\n\nexport type { PilotUploadOptions, UploadBackend, UploadResult } from \"./types.js\";\nexport { uploadWithAltool } from \"./upload-altool.js\";\nexport { uploadWithTransporter } from \"./upload-transporter.js\";\nexport {\n classifyUploadError,\n isTransientUploadError,\n} from \"./classify.js\";\nexport type { TransientUploadError } from \"./classify.js\";\nexport { resolveTransporter, verifyJava } from \"./transporter-install.js\";\n\nexport interface PilotDistributeOptions {\n client: AppStoreConnectClient;\n buildId: string;\n groupNames?: string[];\n changelog?: string;\n locale?: string;\n expirePreviousBuilds?: boolean;\n submitForReview?: boolean;\n}\n\n// ── Upload ─────────────────────────────────────────────────────────────────\n\n/**\n * Upload a build to TestFlight.\n *\n * Selects the upload backend based on the host OS:\n * - macOS → altool (ships with Xcode CLI)\n * - Linux → iTMSTransporter (standalone Java tool from Apple)\n *\n * Pass `backend` to force a specific path, e.g. for testing the\n * Linux path on a mac dev machine.\n */\nexport async function uploadToTestFlight(\n options: PilotUploadOptions\n): Promise<UploadResult> {\n const backend = selectBackend(options.backend ?? \"auto\");\n if (backend === \"altool\") {\n return uploadWithAltool(options);\n }\n return uploadWithTransporter(options);\n}\n\nfunction selectBackend(pref: UploadBackend | \"auto\"): UploadBackend {\n if (pref !== \"auto\") return pref;\n return process.platform === \"darwin\" ? \"altool\" : \"transporter\";\n}\n\n// ── Wait for Processing ────────────────────────────────────────────────────\n\nexport async function waitForProcessing(\n client: AppStoreConnectClient,\n params: {\n bundleId: string;\n buildNumber: string;\n appVersion: string;\n timeout?: number;\n pollInterval?: number;\n }\n): Promise<Build> {\n const timeout = params.timeout ?? 1_800_000; // 30 min\n const pollInterval = params.pollInterval ?? 30_000; // 30s\n const deadline = Date.now() + timeout;\n\n // Find app\n const app = await client.apps.findByBundleId(params.bundleId);\n if (!app) throw new Error(`App not found for bundle ID: ${params.bundleId}`);\n\n while (Date.now() < deadline) {\n const builds = await client.builds.list({\n filter: {\n app: app.id,\n version: params.buildNumber,\n \"preReleaseVersion.version\": params.appVersion,\n },\n limit: 1,\n });\n\n if (builds.length > 0) {\n const build = builds[0];\n const state = build.attributes.processingState;\n\n if (state === \"VALID\") return build;\n if (state === \"FAILED\" || state === \"INVALID\") {\n throw new Error(`Build processing ${state}: ${params.buildNumber}`);\n }\n }\n\n await sleep(pollInterval);\n }\n\n throw new Error(\n `Timeout waiting for build ${params.buildNumber} after ${timeout / 1000}s`\n );\n}\n\n// ── Distribute ─────────────────────────────────────────────────────────────\n\nexport async function distributeToGroups(\n options: PilotDistributeOptions\n): Promise<string[]> {\n const groupNames = options.groupNames ?? [];\n if (groupNames.length === 0) return [];\n\n // Get the build's app\n const build = await options.client.builds.get(options.buildId, {\n include: \"app\",\n });\n const appId =\n build.relationships?.app?.data?.id;\n if (!appId) throw new Error(\"Could not determine app for build\");\n\n // Set changelog if provided\n if (options.changelog) {\n await setChangelog({\n client: options.client,\n buildId: options.buildId,\n changelog: options.changelog,\n locale: options.locale,\n });\n }\n\n // Expire previous builds if requested\n if (options.expirePreviousBuilds) {\n const allBuilds = await options.client.builds.list({\n filter: { app: appId, expired: \"false\" },\n limit: 50,\n });\n for (const b of allBuilds) {\n if (b.id !== options.buildId) {\n await options.client.builds.expireBuild(b.id).catch(() => {});\n }\n }\n }\n\n // Find and add to groups\n const allGroups = await options.client.betaGroups.listForApp(appId);\n const matched: string[] = [];\n\n for (const name of groupNames) {\n const group = allGroups.find((g) => g.attributes.name === name);\n if (!group) continue;\n\n try {\n await options.client.betaGroups.addBuilds(group.id, [options.buildId]);\n matched.push(name);\n } catch (err) {\n // 409 = already added, that's fine\n if (err instanceof AppStoreConnectError && err.status === 409) {\n matched.push(name);\n }\n }\n }\n\n // Submit for beta review if requested\n if (options.submitForReview) {\n try {\n await options.client.builds.submitForBetaReview(options.buildId);\n } catch {\n // May already be submitted\n }\n }\n\n return matched;\n}\n\n// ── Changelog ──────────────────────────────────────────────────────────────\n\nexport async function setChangelog(options: {\n client: AppStoreConnectClient;\n buildId: string;\n changelog: string;\n locale?: string;\n}): Promise<void> {\n const locale = options.locale ?? \"en-US\";\n const sanitized = sanitizeChangelog(options.changelog);\n\n const localizations = await options.client.builds.getLocalizations(\n options.buildId\n );\n const existing = localizations.find(\n (l) => l.attributes.locale === locale\n );\n\n if (existing) {\n await options.client.builds.updateLocalization(existing.id, {\n whatsNew: sanitized,\n });\n } else {\n await options.client.builds.createLocalization({\n buildId: options.buildId,\n locale,\n whatsNew: sanitized,\n });\n }\n}\n\n// ── Expire ─────────────────────────────────────────────────────────────────\n\nexport async function expireBuild(\n client: AppStoreConnectClient,\n buildId: string\n): Promise<void> {\n await client.builds.expireBuild(buildId);\n}\n\n// ── Helpers ────────────────────────────────────────────────────────────────\n\nfunction sanitizeChangelog(text: string): string {\n // Strip RGI-standard emojis using the `emoji-regex` package — same\n // approach as Fastlane's pilot (gem `emoji_regex`), which tracks the\n // Unicode \"Recommended for General Interchange\" spec so new emoji\n // sequences are covered without hand-maintaining ranges.\n let sanitized = text.replace(emojiRegex(), \"\");\n\n // Strip < characters (not allowed by Apple)\n sanitized = sanitized.replace(/</g, \"\");\n\n // Truncate to 4000 bytes\n const encoder = new TextEncoder();\n const encoded = encoder.encode(sanitized);\n if (encoded.length > 4000) {\n const decoder = new TextDecoder();\n sanitized = decoder.decode(encoded.slice(0, 3997)) + \"...\";\n }\n\n return sanitized;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/client/srp.ts","../../src/client/request.ts","../../src/pilot/upload-altool.ts","../../src/pilot/transporter-install.ts","../../src/pilot/upload-transporter.ts","../../src/pilot/classify.ts","../../src/pilot/index.ts"],"names":["existsSync","spawn","join","writeFile","rm"],"mappings":";;;;;;;;;;AAWe,MAAA;AAAA,EACb;AASF;;;ACFO,IAAM,oBAAA,GAAN,cAAmC,KAAA,CAAM;AAAA,EAC9C,WAAA,CACE,OAAA,EACO,MAAA,EACA,MAAA,GAAyC,EAAC,EACjD;AACA,IAAA,KAAA,CAAM,OAAO,CAAA;AAHN,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AACA,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAGP,IAAA,IAAA,CAAK,IAAA,GAAO,sBAAA;AAAA,EACd;AAAA,EALS,MAAA;AAAA,EACA,MAAA;AAKX,CAAA;ACfA,eAAsB,iBACpB,OAAA,EACuB;AACvB,EAAA,IAAI,CAAC,UAAA,CAAW,OAAA,CAAQ,OAAO,CAAA,EAAG;AAChC,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,gBAAA,EAAmB,OAAA,CAAQ,OAAO,CAAA,CAAE,CAAA;AAAA,EACtD;AAEA,EAAA,MAAM,QAAA,GAAW,QAAQ,QAAA,IAAY,KAAA;AACrC,EAAA,MAAM,OAAA,GAAU,QAAQ,OAAA,IAAW,IAAA;AAGnC,EAAA,MAAM,SAAS,MAAM,OAAA,CAAQ,KAAK,MAAA,EAAO,EAAG,kBAAkB,CAAC,CAAA;AAC/D,EAAA,MAAM,UAAU,IAAA,CAAK,MAAA,EAAQ,CAAA,QAAA,EAAW,OAAA,CAAQ,QAAQ,CAAA,GAAA,CAAK,CAAA;AAC7D,EAAA,MAAM,SAAA,CAAU,OAAA,EAAS,OAAA,CAAQ,aAAa,CAAA;AAE9C,EAAA,OAAO,IAAI,OAAA,CAAsB,CAAC,OAAA,KAAY;AAC5C,IAAA,MAAM,IAAA,GAAO;AAAA,MACX,QAAA;AAAA,MACA,cAAA;AAAA,MACA,IAAA;AAAA,MACA,OAAA,CAAQ,OAAA;AAAA,MACR,IAAA;AAAA,MACA,QAAA;AAAA,MACA,UAAA;AAAA,MACA,OAAA,CAAQ,QAAA;AAAA,MACR,aAAA;AAAA,MACA,OAAA,CAAQ;AAAA,KACV;AAEA,IAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,OAAA,EAAS,IAAA,EAAM;AAAA,MACjC,KAAK,EAAE,GAAG,OAAA,CAAQ,GAAA,EAAK,sBAAsB,MAAA,EAAO;AAAA,MACpD,KAAA,EAAO,CAAC,QAAA,EAAU,MAAA,EAAQ,MAAM;AAAA,KACjC,CAAA;AAED,IAAA,IAAI,MAAA,GAAS,EAAA;AACb,IAAA,MAAM,KAAA,GAAQ,WAAW,MAAM;AAC7B,MAAA,KAAA,CAAM,KAAK,SAAS,CAAA;AAAA,IACtB,GAAG,OAAO,CAAA;AAEV,IAAA,MAAM,MAAA,GAAS,CAAC,IAAA,KAAiB;AAC/B,MAAA,MAAM,KAAA,GAAQ,KAAK,QAAA,EAAS;AAC5B,MAAA,MAAA,IAAU,KAAA;AACV,MAAA,OAAA,CAAQ,WAAW,KAAK,CAAA;AAAA,IAC1B,CAAA;AAEA,IAAA,KAAA,CAAM,MAAA,CAAO,EAAA,CAAG,MAAA,EAAQ,MAAM,CAAA;AAC9B,IAAA,KAAA,CAAM,MAAA,CAAO,EAAA,CAAG,MAAA,EAAQ,MAAM,CAAA;AAE9B,IAAA,KAAA,CAAM,EAAA,CAAG,OAAA,EAAS,OAAO,IAAA,KAAS;AAChC,MAAA,YAAA,CAAa,KAAK,CAAA;AAClB,MAAA,MAAM,MAAA,CAAO,OAAO,CAAA,CAAE,KAAA,CAAM,MAAM;AAAA,MAAC,CAAC,CAAA;AACpC,MAAA,MAAM,EAAA,CAAG,MAAA,EAAQ,EAAE,SAAA,EAAW,IAAA,EAAM,OAAO,IAAA,EAAM,CAAA,CAAE,KAAA,CAAM,MAAM;AAAA,MAAC,CAAC,CAAA;AACjE,MAAA,OAAA,CAAQ,EAAE,OAAA,EAAS,IAAA,KAAS,GAAG,MAAA,EAAQ,OAAA,EAAS,UAAU,CAAA;AAAA,IAC5D,CAAC,CAAA;AAAA,EACH,CAAC,CAAA;AACH;AC/CA,eAAsB,kBAAA,GAAsC;AAE1D,EAAA,MAAM,QAAA,GAAW,QAAQ,GAAA,CAAI,mBAAA;AAC7B,EAAA,IAAI,QAAA,EAAU;AACZ,IAAA,IAAI,CAACA,UAAAA,CAAW,QAAQ,CAAA,EAAG;AACzB,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,kCAAkC,QAAQ,CAAA,+BAAA;AAAA,OAC5C;AAAA,IACF;AACA,IAAA,OAAO,QAAA;AAAA,EACT;AAGA,EAAA,MAAM,UAAA,GACJ,OAAA,CAAQ,QAAA,KAAa,QAAA,GACjB;AAAA;AAAA,IAEE,yHAAA;AAAA;AAAA,IAEA;AAAA,GACF,GACA;AAAA,IACE,qCAAA;AAAA,IACA,+BAAA;AAAA,IACA;AAAA,GACF;AAEN,EAAA,KAAA,MAAW,QAAQ,UAAA,EAAY;AAC7B,IAAA,IAAIA,UAAAA,CAAW,IAAI,CAAA,EAAG,OAAO,IAAA;AAAA,EAC/B;AAEA,EAAA,MAAM,IAAI,KAAA;AAAA,IACR;AAAA,MACE,4BAAA;AAAA,MACA,EAAA;AAAA,MACA,kBAAA;AAAA,MACA,OAAA,CAAQ,QAAA,KAAa,QAAA,GACjB,0CAAA,GACA,mEAAA;AAAA,MACJ,mEAAA;AAAA,MACA,EAAA;AAAA,MACA,CAAA,UAAA,EAAa,UAAA,CAAW,IAAA,CAAK,IAAI,CAAC,CAAA;AAAA,KACpC,CAAE,KAAK,IAAI;AAAA,GACb;AACF;AAMA,eAAsB,UAAA,GAA2C;AAC/D,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AACtC,IAAA,MAAM,KAAA,GAAQC,KAAAA,CAAM,MAAA,EAAQ,CAAC,UAAU,CAAA,EAAG,EAAE,KAAA,EAAO,CAAC,QAAA,EAAU,MAAA,EAAQ,MAAM,GAAG,CAAA;AAC/E,IAAA,IAAI,MAAA,GAAS,EAAA;AACb,IAAA,KAAA,CAAM,MAAA,CAAO,EAAA,CAAG,MAAA,EAAQ,CAAC,CAAA,KAAc;AACrC,MAAA,MAAA,IAAU,EAAE,QAAA,EAAS;AAAA,IACvB,CAAC,CAAA;AACD,IAAA,KAAA,CAAM,EAAA,CAAG,OAAA,EAAS,CAAC,GAAA,KAAQ;AACzB,MAAA,MAAA;AAAA,QACE,IAAI,KAAA;AAAA,UACF,CAAA,wBAAA,EAA2B,IAAI,OAAO,CAAA,gDAAA;AAAA;AACxC,OACF;AAAA,IACF,CAAC,CAAA;AACD,IAAA,KAAA,CAAM,EAAA,CAAG,OAAA,EAAS,CAAC,IAAA,KAAS;AAC1B,MAAA,IAAI,SAAS,CAAA,EAAG;AACd,QAAA,MAAA,CAAO,IAAI,KAAA,CAAM,CAAA,qBAAA,EAAwB,IAAI,CAAA,EAAA,EAAK,MAAM,EAAE,CAAC,CAAA;AAC3D,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,KAAA,CAAM,mBAAmB,CAAA;AAC9C,MAAA,OAAA,CAAQ,EAAE,OAAA,EAAS,KAAA,GAAQ,CAAC,CAAA,IAAK,WAAW,CAAA;AAAA,IAC9C,CAAC,CAAA;AAAA,EACH,CAAC,CAAA;AACH;;;AC9EA,eAAsB,sBACpB,OAAA,EACuB;AACvB,EAAA,IAAI,CAACD,UAAAA,CAAW,OAAA,CAAQ,OAAO,CAAA,EAAG;AAChC,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,gBAAA,EAAmB,OAAA,CAAQ,OAAO,CAAA,CAAE,CAAA;AAAA,EACtD;AAIA,EAAA,MAAM,UAAA,EAAW;AAEjB,EAAA,MAAM,cAAA,GAAiB,MAAM,kBAAA,EAAmB;AAEhD,EAAA,MAAM,QAAA,GAAW,QAAQ,QAAA,IAAY,KAAA;AACrC,EAAA,MAAM,OAAA,GAAU,QAAQ,OAAA,IAAW,IAAA;AAenC,EAAA,MAAM,MAAA,GAASE,IAAAA,CAAK,OAAA,EAAQ,EAAG,oBAAoB,cAAc,CAAA;AACjE,EAAA,MAAM,KAAA,CAAM,MAAA,EAAQ,EAAE,SAAA,EAAW,MAAM,CAAA;AACvC,EAAA,MAAM,UAAUA,IAAAA,CAAK,MAAA,EAAQ,CAAA,QAAA,EAAW,OAAA,CAAQ,QAAQ,CAAA,GAAA,CAAK,CAAA;AAC7D,EAAA,MAAMC,UAAU,OAAA,EAAS,OAAA,CAAQ,eAAe,EAAE,IAAA,EAAM,KAAO,CAAA;AAE/D,EAAA,OAAO,IAAI,OAAA,CAAsB,CAAC,OAAA,KAAY;AAC5C,IAAA,MAAM,IAAA,GAAO;AAAA,MACX,IAAA;AAAA,MAAM,QAAA;AAAA,MACN,YAAA;AAAA,MAAc,OAAA,CAAQ,OAAA;AAAA,MACtB,SAAA;AAAA,MAAW,OAAA,CAAQ,QAAA;AAAA,MACnB,YAAA;AAAA,MAAc,OAAA,CAAQ,WAAA;AAAA;AAAA;AAAA,MAGtB,YAAA;AAAA,MAAc;AAAA,KAChB;AAOA,IAAA,IAAI,QAAQ,oBAAA,EAAsB;AAChC,MAAA,IAAA,CAAK,IAAA,CAAK,mBAAA,EAAqB,OAAA,CAAQ,oBAAoB,CAAA;AAAA,IAC7D;AAKA,IAAA,IAAI,aAAa,KAAA,EAAO;AACtB,MAAA,IAAA,CAAK,IAAA,CAAK,eAAe,QAAQ,CAAA;AAAA,IACnC;AAEA,IAAA,MAAM,KAAA,GAAQF,KAAAA,CAAM,cAAA,EAAgB,IAAA,EAAM;AAAA,MACxC,GAAA,EAAK;AAAA,QACH,GAAG,OAAA,CAAQ,GAAA;AAAA,QACX,oBAAA,EAAsB;AAAA,OACxB;AAAA,MACA,KAAA,EAAO,CAAC,QAAA,EAAU,MAAA,EAAQ,MAAM;AAAA,KACjC,CAAA;AAED,IAAA,IAAI,MAAA,GAAS,EAAA;AACb,IAAA,MAAM,KAAA,GAAQ,WAAW,MAAM;AAC7B,MAAA,KAAA,CAAM,KAAK,SAAS,CAAA;AAAA,IACtB,GAAG,OAAO,CAAA;AAEV,IAAA,MAAM,MAAA,GAAS,CAAC,IAAA,KAAiB;AAC/B,MAAA,MAAM,KAAA,GAAQ,KAAK,QAAA,EAAS;AAC5B,MAAA,MAAA,IAAU,KAAA;AACV,MAAA,OAAA,CAAQ,WAAW,KAAK,CAAA;AAAA,IAC1B,CAAA;AAEA,IAAA,KAAA,CAAM,MAAA,CAAO,EAAA,CAAG,MAAA,EAAQ,MAAM,CAAA;AAC9B,IAAA,KAAA,CAAM,MAAA,CAAO,EAAA,CAAG,MAAA,EAAQ,MAAM,CAAA;AAE9B,IAAA,KAAA,CAAM,EAAA,CAAG,OAAA,EAAS,OAAO,IAAA,KAAS;AAChC,MAAA,YAAA,CAAa,KAAK,CAAA;AAClB,MAAA,MAAMG,EAAAA,CAAG,SAAS,EAAE,KAAA,EAAO,MAAM,CAAA,CAAE,MAAM,MAAM;AAAA,MAAC,CAAC,CAAA;AACjD,MAAA,OAAA,CAAQ,EAAE,OAAA,EAAS,IAAA,KAAS,GAAG,MAAA,EAAQ,OAAA,EAAS,eAAe,CAAA;AAAA,IACjE,CAAC,CAAA;AAED,IAAA,KAAA,CAAM,EAAA,CAAG,OAAA,EAAS,OAAO,GAAA,KAAQ;AAC/B,MAAA,YAAA,CAAa,KAAK,CAAA;AAClB,MAAA,MAAMA,EAAAA,CAAG,SAAS,EAAE,KAAA,EAAO,MAAM,CAAA,CAAE,MAAM,MAAM;AAAA,MAAC,CAAC,CAAA;AACjD,MAAA,OAAA,CAAQ;AAAA,QACN,OAAA,EAAS,KAAA;AAAA,QACT,QAAQ,MAAA,GAAS;AAAA,eAAA,EAAoB,IAAI,OAAO,CAAA,CAAA;AAAA,QAChD,OAAA,EAAS;AAAA,OACV,CAAA;AAAA,IACH,CAAC,CAAA;AAAA,EACH,CAAC,CAAA;AACH;;;AC1FA,IAAM,KAAA,GAA0B;AAAA;AAAA;AAAA;AAAA;AAAA,EAK9B,EAAE,OAAA,EAAS,kCAAA,EAAoC,IAAA,EAAM,iBAAA,EAAkB;AAAA;AAAA,EAGvE,EAAE,OAAA,EAAS,oBAAA,EAAsB,IAAA,EAAM,WAAA,EAAY;AAAA,EACnD,EAAE,OAAA,EAAS,wBAAA,EAA0B,IAAA,EAAM,WAAA,EAAY;AAAA,EACvD,EAAE,OAAA,EAAS,sBAAA,EAAwB,IAAA,EAAM,WAAA,EAAY;AAAA,EACrD,EAAE,OAAA,EAAS,cAAA,EAAgB,IAAA,EAAM,WAAA,EAAY;AAAA;AAAA,EAG7C,EAAE,OAAA,EAAS,mBAAA,EAAqB,IAAA,EAAM,kBAAA,EAAmB;AAAA,EACzD,EAAE,OAAA,EAAS,kBAAA,EAAoB,IAAA,EAAM,kBAAA,EAAmB;AAAA,EACxD,EAAE,OAAA,EAAS,cAAA,EAAgB,IAAA,EAAM,kBAAA,EAAmB;AAAA;AAAA;AAAA,EAIpD,EAAE,OAAA,EAAS,iBAAA,EAAmB,IAAA,EAAM,iBAAA,EAAkB;AAAA,EACtD,EAAE,OAAA,EAAS,yBAAA,EAA2B,IAAA,EAAM,iBAAA,EAAkB;AAAA,EAC9D,EAAE,OAAA,EAAS,sBAAA,EAAwB,IAAA,EAAM,iBAAA,EAAkB;AAAA;AAAA,EAG3D,EAAE,OAAA,EAAS,+BAAA,EAAiC,IAAA,EAAM,uBAAA,EAAwB;AAAA,EAC1E,EAAE,OAAA,EAAS,eAAA,EAAiB,IAAA,EAAM,uBAAA;AACpC,CAAA;AAMO,SAAS,oBAAoB,MAAA,EAA6C;AAC/E,EAAA,KAAA,MAAW,EAAE,OAAA,EAAS,IAAA,EAAK,IAAK,KAAA,EAAO;AACrC,IAAA,IAAI,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAA,EAAG,OAAO,IAAA;AAAA,EACnC;AACA,EAAA,OAAO,IAAA;AACT;AAKO,SAAS,uBAAuB,MAAA,EAAyB;AAC9D,EAAA,OAAO,mBAAA,CAAoB,MAAM,CAAA,KAAM,IAAA;AACzC;;;AC3BA,eAAsB,mBACpB,OAAA,EACuB;AACvB,EAAA,MAAM,OAAA,GAAU,aAAA,CAAc,OAAA,CAAQ,OAAA,IAAW,MAAM,CAAA;AACvD,EAAA,IAAI,YAAY,QAAA,EAAU;AACxB,IAAA,OAAO,iBAAiB,OAAO,CAAA;AAAA,EACjC;AACA,EAAA,OAAO,sBAAsB,OAAO,CAAA;AACtC;AAEA,SAAS,cAAc,IAAA,EAA6C;AAClE,EAAA,IAAI,IAAA,KAAS,QAAQ,OAAO,IAAA;AAC5B,EAAA,OAAO,OAAA,CAAQ,QAAA,KAAa,QAAA,GAAW,QAAA,GAAW,aAAA;AACpD;AAIA,eAAsB,iBAAA,CACpB,QACA,MAAA,EAOgB;AAChB,EAAA,MAAM,OAAA,GAAU,OAAO,OAAA,IAAW,IAAA;AAClC,EAAA,MAAM,YAAA,GAAe,OAAO,YAAA,IAAgB,GAAA;AAC5C,EAAA,MAAM,QAAA,GAAW,IAAA,CAAK,GAAA,EAAI,GAAI,OAAA;AAG9B,EAAA,MAAM,MAAM,MAAM,MAAA,CAAO,IAAA,CAAK,cAAA,CAAe,OAAO,QAAQ,CAAA;AAC5D,EAAA,IAAI,CAAC,KAAK,MAAM,IAAI,MAAM,CAAA,6BAAA,EAAgC,MAAA,CAAO,QAAQ,CAAA,CAAE,CAAA;AAE3E,EAAA,OAAO,IAAA,CAAK,GAAA,EAAI,GAAI,QAAA,EAAU;AAC5B,IAAA,MAAM,MAAA,GAAS,MAAM,MAAA,CAAO,MAAA,CAAO,IAAA,CAAK;AAAA,MACtC,MAAA,EAAQ;AAAA,QACN,KAAK,GAAA,CAAI,EAAA;AAAA,QACT,SAAS,MAAA,CAAO,WAAA;AAAA,QAChB,6BAA6B,MAAA,CAAO;AAAA,OACtC;AAAA,MACA,KAAA,EAAO;AAAA,KACR,CAAA;AAED,IAAA,IAAI,MAAA,CAAO,SAAS,CAAA,EAAG;AACrB,MAAA,MAAM,KAAA,GAAQ,OAAO,CAAC,CAAA;AACtB,MAAA,MAAM,KAAA,GAAQ,MAAM,UAAA,CAAW,eAAA;AAE/B,MAAA,IAAI,KAAA,KAAU,SAAS,OAAO,KAAA;AAC9B,MAAA,IAAI,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,SAAA,EAAW;AAC7C,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,iBAAA,EAAoB,KAAK,CAAA,EAAA,EAAK,MAAA,CAAO,WAAW,CAAA,CAAE,CAAA;AAAA,MACpE;AAAA,IACF;AAEA,IAAA,MAAM,MAAM,YAAY,CAAA;AAAA,EAC1B;AAEA,EAAA,MAAM,IAAI,KAAA;AAAA,IACR,CAAA,0BAAA,EAA6B,MAAA,CAAO,WAAW,CAAA,OAAA,EAAU,UAAU,GAAI,CAAA,CAAA;AAAA,GACzE;AACF;AAIA,eAAsB,mBACpB,OAAA,EACmB;AACnB,EAAA,MAAM,UAAA,GAAa,OAAA,CAAQ,UAAA,IAAc,EAAC;AAC1C,EAAA,IAAI,UAAA,CAAW,MAAA,KAAW,CAAA,EAAG,OAAO,EAAC;AAGrC,EAAA,MAAM,QAAQ,MAAM,OAAA,CAAQ,OAAO,MAAA,CAAO,GAAA,CAAI,QAAQ,OAAA,EAAS;AAAA,IAC7D,OAAA,EAAS;AAAA,GACV,CAAA;AACD,EAAA,MAAM,KAAA,GACJ,KAAA,CAAM,aAAA,EAAe,GAAA,EAAK,IAAA,EAAM,EAAA;AAClC,EAAA,IAAI,CAAC,KAAA,EAAO,MAAM,IAAI,MAAM,mCAAmC,CAAA;AAG/D,EAAA,IAAI,QAAQ,SAAA,EAAW;AACrB,IAAA,MAAM,YAAA,CAAa;AAAA,MACjB,QAAQ,OAAA,CAAQ,MAAA;AAAA,MAChB,SAAS,OAAA,CAAQ,OAAA;AAAA,MACjB,WAAW,OAAA,CAAQ,SAAA;AAAA,MACnB,QAAQ,OAAA,CAAQ;AAAA,KACjB,CAAA;AAAA,EACH;AAGA,EAAA,IAAI,QAAQ,oBAAA,EAAsB;AAChC,IAAA,MAAM,SAAA,GAAY,MAAM,OAAA,CAAQ,MAAA,CAAO,OAAO,IAAA,CAAK;AAAA,MACjD,MAAA,EAAQ,EAAE,GAAA,EAAK,KAAA,EAAO,SAAS,OAAA,EAAQ;AAAA,MACvC,KAAA,EAAO;AAAA,KACR,CAAA;AACD,IAAA,KAAA,MAAW,KAAK,SAAA,EAAW;AACzB,MAAA,IAAI,CAAA,CAAE,EAAA,KAAO,OAAA,CAAQ,OAAA,EAAS;AAC5B,QAAA,MAAM,OAAA,CAAQ,OAAO,MAAA,CAAO,WAAA,CAAY,EAAE,EAAE,CAAA,CAAE,MAAM,MAAM;AAAA,QAAC,CAAC,CAAA;AAAA,MAC9D;AAAA,IACF;AAAA,EACF;AAGA,EAAA,MAAM,YAAY,MAAM,OAAA,CAAQ,MAAA,CAAO,UAAA,CAAW,WAAW,KAAK,CAAA;AAClE,EAAA,MAAM,UAAoB,EAAC;AAE3B,EAAA,KAAA,MAAW,QAAQ,UAAA,EAAY;AAC7B,IAAA,MAAM,KAAA,GAAQ,UAAU,IAAA,CAAK,CAAC,MAAM,CAAA,CAAE,UAAA,CAAW,SAAS,IAAI,CAAA;AAC9D,IAAA,IAAI,CAAC,KAAA,EAAO;AAEZ,IAAA,IAAI;AACF,MAAA,MAAM,OAAA,CAAQ,OAAO,UAAA,CAAW,SAAA,CAAU,MAAM,EAAA,EAAI,CAAC,OAAA,CAAQ,OAAO,CAAC,CAAA;AACrE,MAAA,OAAA,CAAQ,KAAK,IAAI,CAAA;AAAA,IACnB,SAAS,GAAA,EAAK;AAEZ,MAAA,IAAI,GAAA,YAAe,oBAAA,IAAwB,GAAA,CAAI,MAAA,KAAW,GAAA,EAAK;AAC7D,QAAA,OAAA,CAAQ,KAAK,IAAI,CAAA;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AAGA,EAAA,IAAI,QAAQ,eAAA,EAAiB;AAC3B,IAAA,IAAI;AACF,MAAA,MAAM,OAAA,CAAQ,MAAA,CAAO,MAAA,CAAO,mBAAA,CAAoB,QAAQ,OAAO,CAAA;AAAA,IACjE,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF;AAEA,EAAA,OAAO,OAAA;AACT;AAIA,eAAsB,aAAa,OAAA,EAKjB;AAChB,EAAA,MAAM,MAAA,GAAS,QAAQ,MAAA,IAAU,OAAA;AACjC,EAAA,MAAM,SAAA,GAAY,iBAAA,CAAkB,OAAA,CAAQ,SAAS,CAAA;AAErD,EAAA,MAAM,aAAA,GAAgB,MAAM,OAAA,CAAQ,MAAA,CAAO,MAAA,CAAO,gBAAA;AAAA,IAChD,OAAA,CAAQ;AAAA,GACV;AACA,EAAA,MAAM,WAAW,aAAA,CAAc,IAAA;AAAA,IAC7B,CAAC,CAAA,KAAM,CAAA,CAAE,UAAA,CAAW,MAAA,KAAW;AAAA,GACjC;AAEA,EAAA,IAAI,QAAA,EAAU;AACZ,IAAA,MAAM,OAAA,CAAQ,MAAA,CAAO,MAAA,CAAO,kBAAA,CAAmB,SAAS,EAAA,EAAI;AAAA,MAC1D,QAAA,EAAU;AAAA,KACX,CAAA;AAAA,EACH,CAAA,MAAO;AACL,IAAA,MAAM,OAAA,CAAQ,MAAA,CAAO,MAAA,CAAO,kBAAA,CAAmB;AAAA,MAC7C,SAAS,OAAA,CAAQ,OAAA;AAAA,MACjB,MAAA;AAAA,MACA,QAAA,EAAU;AAAA,KACX,CAAA;AAAA,EACH;AACF;AAIA,eAAsB,WAAA,CACpB,QACA,OAAA,EACe;AACf,EAAA,MAAM,MAAA,CAAO,MAAA,CAAO,WAAA,CAAY,OAAO,CAAA;AACzC;AAIA,SAAS,kBAAkB,IAAA,EAAsB;AAK/C,EAAA,IAAI,SAAA,GAAY,IAAA,CAAK,OAAA,CAAQ,UAAA,IAAc,EAAE,CAAA;AAG7C,EAAA,SAAA,GAAY,SAAA,CAAU,OAAA,CAAQ,IAAA,EAAM,EAAE,CAAA;AAGtC,EAAA,MAAM,OAAA,GAAU,IAAI,WAAA,EAAY;AAChC,EAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,MAAA,CAAO,SAAS,CAAA;AACxC,EAAA,IAAI,OAAA,CAAQ,SAAS,GAAA,EAAM;AACzB,IAAA,MAAM,OAAA,GAAU,IAAI,WAAA,EAAY;AAChC,IAAA,SAAA,GAAY,QAAQ,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA,EAAG,IAAI,CAAC,CAAA,GAAI,KAAA;AAAA,EACvD;AAEA,EAAA,OAAO,SAAA;AACT;AAEA,SAAS,MAAM,EAAA,EAA2B;AACxC,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,YAAY,UAAA,CAAW,OAAA,EAAS,EAAE,CAAC,CAAA;AACzD","file":"index.mjs","sourcesContent":["/**\n * SRP (Secure Remote Password) implementation\n * 1:1 port of the fastlane-sirp Ruby gem\n * Used for Apple ID authentication\n */\n\nimport { createHash, randomBytes, pbkdf2Sync } from \"node:crypto\";\n\n// ── RFC 5054 SRP Group Parameters ──────────────────────────────────────────\n\n// Copied exactly from fastlane-sirp parameters.rb (2048-bit group)\nconst N_2048 = BigInt(\n \"0x\" +\n \"AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050\" +\n \"A37329CBB4A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50\" +\n \"E8083969EDB767B0CF6095179A163AB3661A05FBD5FAAAE82918A9962F0B93B8\" +\n \"55F97993EC975EEAA80D740ADBF4FF747359D041D5C33EA71D281E446B14773B\" +\n \"CA97B43A23FB801676BD207A436C6481F1D2B9078717461A5B9D32E688F87748\" +\n \"544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB3786160279004E57AE6\" +\n \"AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DBFBB\" +\n \"694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73\"\n);\nconst g_2048 = 2n;\n\n// ── Utility Functions ──────────────────────────────────────────────────────\n\nfunction numToHex(num: bigint): string {\n let hex = num.toString(16);\n if (hex.length % 2 !== 0) hex = \"0\" + hex;\n return hex.toLowerCase();\n}\n\nfunction hexToBytes(hex: string): Buffer {\n return Buffer.from(hex, \"hex\");\n}\n\nfunction shaHex(h: string, algorithm: string): string {\n return createHash(algorithm).update(hexToBytes(h)).digest(\"hex\");\n}\n\nfunction shaStr(s: string, algorithm: string): string {\n return createHash(algorithm).update(s).digest(\"hex\");\n}\n\nfunction modExp(base: bigint, exp: bigint, mod: bigint): bigint {\n if (mod === 1n) return 0n;\n let result = 1n;\n base = base % mod;\n while (exp > 0n) {\n if (exp % 2n === 1n) {\n result = (result * base) % mod;\n }\n exp = exp / 2n;\n base = (base * base) % mod;\n }\n return result;\n}\n\nfunction H(algorithm: string, n: bigint, ...args: (bigint | string | undefined)[]): bigint {\n const nHex = numToHex(n);\n // Must match Ruby: nlen = 2 * ((('%x' % [n]).length * 4 + 7) >> 3)\n // Use floor division (>>3), NOT ceiling\n const nlen = 2 * (((nHex.length * 4 + 7) >> 3));\n\n let hashin = \"\";\n for (const s of args) {\n if (s === undefined) continue;\n const shex = typeof s === \"string\" ? s : numToHex(s);\n if (shex.length > nlen) {\n throw new Error(\"Bit width does not match - client uses different prime\");\n }\n hashin += \"0\".repeat(nlen - shex.length) + shex;\n }\n\n const hashed = shaHex(hashin, algorithm);\n return BigInt(\"0x\" + hashed) % n;\n}\n\n// ── SRP Client ─────────────────────────────────────────────────────────────\n\nexport class SRPClient {\n private N: bigint;\n private g: bigint;\n private hashAlg: string;\n private k: bigint;\n private a: bigint;\n A: string;\n private S: string | null = null;\n private K: string | null = null;\n M: string | null = null;\n H_AMK: string | null = null;\n\n constructor(group: number = 2048) {\n if (group !== 2048) throw new Error(\"Only 2048-bit group is supported\");\n this.N = N_2048;\n this.g = g_2048;\n this.hashAlg = \"sha256\";\n this.k = H(this.hashAlg, this.N, this.N, this.g);\n // Generate random 'a'\n this.a = BigInt(\"0x\" + randomBytes(256).toString(\"hex\"));\n // Compute A = g^a mod N\n this.A = numToHex(modExp(this.g, this.a, this.N));\n }\n\n startAuthentication(): string {\n return this.A;\n }\n\n processChallenge(\n username: string,\n password: string, // hex-encoded encrypted password\n xsalt: string, // hex\n xbb: string, // hex\n isPasswordEncrypted: boolean = false\n ): string | false {\n const bb = BigInt(\"0x\" + xbb);\n\n // SRP-6a safety check\n if (bb % this.N === 0n) return false;\n\n let x: bigint;\n if (isPasswordEncrypted) {\n x = this.calcXHex(password, xsalt);\n } else {\n x = this.calcX(username, password, xsalt);\n }\n\n const u = H(this.hashAlg, this.N, this.A, xbb);\n if (u === 0n) return false;\n\n // S = (B - k * g^x) ^ (a + u*x) mod N\n const gx = modExp(this.g, x, this.N);\n let base = (bb - this.k * gx) % this.N;\n if (base < 0n) base += this.N;\n const exp = this.a + x * u;\n this.S = numToHex(modExp(base, exp, this.N));\n this.K = shaHex(this.S, this.hashAlg);\n\n // M = H(H(N) xor H(g), H(I), s, A, B, K)\n const hxor = H(this.hashAlg, this.N, this.N) ^ H(this.hashAlg, this.N, this.g);\n const buf =\n numToHex(hxor) +\n shaStr(username, this.hashAlg) +\n xsalt +\n this.A +\n xbb +\n this.K;\n const bytes = hexToBytes(buf);\n this.M = createHash(this.hashAlg).update(bytes).digest(\"hex\");\n\n // H_AMK = H(A, M, K)\n const amkBytes = hexToBytes(this.A + this.M + this.K);\n this.H_AMK = numToHex(\n BigInt(\"0x\" + createHash(this.hashAlg).update(amkBytes).digest(\"hex\"))\n );\n\n return this.M;\n }\n\n private calcX(username: string, password: string, xsalt: string): bigint {\n const spad = xsalt.length % 2 !== 0 ? \"0\" : \"\";\n const inner = shaStr(`${username}:${password}`, this.hashAlg);\n return BigInt(\"0x\" + shaHex(spad + xsalt + inner, this.hashAlg));\n }\n\n private calcXHex(xpassword: string, xsalt: string): bigint {\n // calc_x_hex: sha_hex(xsalt + sha_hex(\":\" hex + xpassword), hash)\n const colonHex = \":\".charCodeAt(0).toString(16);\n const inner = shaHex(colonHex + xpassword, this.hashAlg);\n return BigInt(\"0x\" + shaHex(xsalt + inner, this.hashAlg));\n }\n}\n\n// ── Apple PBKDF2 password derivation ───────────────────────────────────────\n\nexport function applePbkdf2(\n password: string,\n salt: Buffer,\n iterations: number,\n keyLength: number,\n protocol: string\n): Buffer {\n if (![\"s2k\", \"s2k_fo\"].includes(protocol)) {\n throw new Error(`Unsupported protocol '${protocol}' for pbkdf2`);\n }\n\n // First hash the password with SHA-256\n let passwordBytes: Buffer | string = createHash(\"sha256\").update(password).digest();\n\n if (protocol === \"s2k_fo\") {\n // Legacy: convert to hex string\n passwordBytes = passwordBytes.toString(\"hex\");\n }\n\n return pbkdf2Sync(passwordBytes, salt, iterations, keyLength, \"sha256\");\n}\n","import type { TokenManager } from \"./auth.js\";\nimport { ProgramLicenseAgreementError } from \"./apple-session.js\";\nimport type {\n JsonApiErrorResponse,\n JsonApiListResponse,\n JsonApiResponse,\n ListParams,\n RequestOptions,\n} from \"./types.js\";\n\nconst PRODUCTION_BASE = \"https://api.appstoreconnect.apple.com\";\nconst ENTERPRISE_BASE = \"https://api.enterprise.developer.apple.com\";\n\nconst MAX_RETRIES = 5;\nconst INITIAL_BACKOFF_MS = 1000;\nconst MAX_BACKOFF_MS = 60_000;\n/** Fastlane's TooManyRequestsError gives up after 3600s cumulative backoff. */\nconst MAX_RATE_LIMIT_BACKOFF_MS = 3_600_000;\n\nexport class AppStoreConnectError extends Error {\n constructor(\n message: string,\n public status: number,\n public errors: JsonApiErrorResponse[\"errors\"] = []\n ) {\n super(message);\n this.name = \"AppStoreConnectError\";\n }\n}\n\nexport class RateLimitError extends AppStoreConnectError {\n constructor(public retryAfter: number) {\n super(`Rate limited. Retry after ${retryAfter}s`, 429);\n this.name = \"RateLimitError\";\n }\n}\n\nexport class RequestClient {\n private baseUrl: string;\n private tokenManager: TokenManager;\n\n constructor(tokenManager: TokenManager, enterprise = false) {\n this.tokenManager = tokenManager;\n this.baseUrl = enterprise ? ENTERPRISE_BASE : PRODUCTION_BASE;\n }\n\n async request<T>(path: string, options: RequestOptions = {}): Promise<T> {\n const { method = \"GET\", body, params, headers: extraHeaders } = options;\n\n let url = `${this.baseUrl}${path}`;\n if (params) {\n const searchParams = new URLSearchParams();\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined) continue;\n if (Array.isArray(value)) {\n searchParams.set(key, value.join(\",\"));\n } else {\n searchParams.set(key, String(value));\n }\n }\n const qs = searchParams.toString();\n if (qs) url += `?${qs}`;\n }\n\n let lastError: Error | null = null;\n // Soft-cumulative backoff for 429 that doubles on each hit, mirroring\n // Fastlane's Spaceship::ConnectAPI::APIClient#with_asc_retry 429 handler:\n // `Kernel.sleep(backoff); backoff *= 2`, giving up past 3600s.\n let rateLimitBackoffMs = INITIAL_BACKOFF_MS;\n\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n const token = await this.tokenManager.getToken();\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n ...extraHeaders,\n };\n\n const res = await fetch(url, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n });\n\n if (res.ok) {\n if (res.status === 204) return undefined as T;\n return (await res.json()) as T;\n }\n\n // Handle 401 — refresh token and retry once\n if (res.status === 401 && attempt === 0) {\n this.tokenManager.invalidate();\n continue;\n }\n\n // Handle 429 — rate limit. Prefer server's Retry-After header when\n // provided, otherwise fall back to Fastlane's doubling backoff.\n if (res.status === 429) {\n if (rateLimitBackoffMs > MAX_RATE_LIMIT_BACKOFF_MS) {\n throw new RateLimitError(Math.floor(rateLimitBackoffMs / 1000));\n }\n const retryAfterHeader = res.headers.get(\"Retry-After\");\n const sleepMs = retryAfterHeader\n ? parseInt(retryAfterHeader, 10) * 1000\n : rateLimitBackoffMs;\n await sleep(sleepMs);\n rateLimitBackoffMs *= 2;\n continue;\n }\n\n // Parse error body early so we can inspect it for 403/502 special cases.\n const errorBody = await res.json().catch(() => null);\n const errors = (errorBody as JsonApiErrorResponse)?.errors || [];\n\n // Handle 403 — detect missing/expired program license agreement.\n // Mirrors api_client.rb's ProgramLicenseAgreementUpdated branch.\n if (res.status === 403) {\n const firstCode = errors[0]?.code;\n if (firstCode === \"FORBIDDEN.REQUIRED_AGREEMENTS_MISSING_OR_EXPIRED\") {\n const detail =\n errors.map((e) => e.detail || e.title).join(\"; \") ||\n \"Required agreements are missing or expired.\";\n throw new ProgramLicenseAgreementError(\n `${detail} Please manually log into your Apple Developer account to review and accept the updated agreement.`,\n []\n );\n }\n }\n\n // Handle 502 — special-case \"Could not process this request\" retry.\n // Mirrors api_client.rb's BadGatewayError branch (issue fastlane#19264).\n if (res.status === 502 && attempt < MAX_RETRIES) {\n const bodyText = errorBody ? JSON.stringify(errorBody) : \"\";\n if (bodyText.includes(\"Could not process this request\")) {\n const backoff = Math.min(\n INITIAL_BACKOFF_MS * Math.pow(2, attempt),\n MAX_BACKOFF_MS\n );\n await sleep(backoff + Math.random() * 500);\n continue;\n }\n }\n\n // Handle 5xx — retry with exponential backoff\n if (res.status >= 500 && attempt < MAX_RETRIES) {\n const backoff = Math.min(\n INITIAL_BACKOFF_MS * Math.pow(2, attempt),\n MAX_BACKOFF_MS\n );\n await sleep(backoff + Math.random() * 500);\n continue;\n }\n\n const detail = errors.map((e) => e.detail || e.title).join(\"; \") || `HTTP ${res.status}`;\n\n lastError = new AppStoreConnectError(detail, res.status, errors);\n break;\n }\n\n throw lastError || new Error(\"Request failed after retries\");\n }\n\n async get<T>(path: string, params?: Record<string, string | string[] | number | undefined>): Promise<T> {\n return this.request<T>(path, { method: \"GET\", params });\n }\n\n async post<T>(path: string, body: unknown): Promise<T> {\n return this.request<T>(path, { method: \"POST\", body });\n }\n\n async patch<T>(path: string, body: unknown): Promise<T> {\n return this.request<T>(path, { method: \"PATCH\", body });\n }\n\n async delete(path: string): Promise<void> {\n await this.request(path, { method: \"DELETE\" });\n }\n\n // Build query params from ListParams\n buildListParams(params: ListParams): Record<string, string | undefined> {\n const result: Record<string, string | undefined> = {};\n\n if (params.filter) {\n for (const [key, value] of Object.entries(params.filter)) {\n result[`filter[${key}]`] = Array.isArray(value) ? value.join(\",\") : value;\n }\n }\n\n if (params.include) {\n result[\"include\"] = params.include;\n }\n\n if (params.fields) {\n for (const [key, value] of Object.entries(params.fields)) {\n result[`fields[${key}]`] = value;\n }\n }\n\n if (params.limit !== undefined) {\n result[\"limit\"] = String(params.limit);\n }\n\n if (params.sort) {\n result[\"sort\"] = params.sort;\n }\n\n if (params.cursor) {\n // ASC uses page[after] for cursor pagination\n result[\"page[after]\"] = params.cursor;\n }\n\n return result;\n }\n\n async list<T>(path: string, params: ListParams = {}): Promise<JsonApiListResponse<T>> {\n const queryParams = this.buildListParams(params);\n return this.get<JsonApiListResponse<T>>(path, queryParams);\n }\n\n async *paginate<T>(path: string, params: ListParams = {}): AsyncGenerator<T, void, undefined> {\n let cursor: string | undefined = params.cursor;\n\n while (true) {\n const response = await this.list<T>(path, { ...params, cursor });\n\n for (const item of response.data) {\n yield item;\n }\n\n // Check for next page\n const nextLink = response.links?.next;\n if (!nextLink) break;\n\n // Extract cursor from next link\n const nextUrl = new URL(nextLink);\n cursor = nextUrl.searchParams.get(\"page[after]\") || undefined;\n if (!cursor) break;\n }\n }\n\n async all<T>(path: string, params: ListParams = {}): Promise<T[]> {\n const results: T[] = [];\n for await (const item of this.paginate<T>(path, params)) {\n results.push(item);\n }\n return results;\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import { spawn } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { writeFile, unlink, mkdtemp, rm } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { tmpdir } from \"node:os\";\nimport type { PilotUploadOptions, UploadResult } from \"./types.js\";\n\n/**\n * Upload a build to TestFlight via Apple's `altool` (macOS only).\n *\n * altool ships with Xcode command-line tools. It provides richer error\n * output than iTMSTransporter but is only available on macOS.\n */\nexport async function uploadWithAltool(\n options: PilotUploadOptions\n): Promise<UploadResult> {\n if (!existsSync(options.ipaPath)) {\n throw new Error(`File not found: ${options.ipaPath}`);\n }\n\n const platform = options.platform ?? \"ios\";\n const timeout = options.timeout ?? 1_800_000; // 30 min\n\n // Write temp .p8 key file — altool reads from API_PRIVATE_KEYS_DIR\n const keyDir = await mkdtemp(join(tmpdir(), \"fastnode-altool-\"));\n const keyPath = join(keyDir, `AuthKey_${options.apiKeyId}.p8`);\n await writeFile(keyPath, options.apiPrivateKey);\n\n return new Promise<UploadResult>((resolve) => {\n const args = [\n \"altool\",\n \"--upload-app\",\n \"-f\",\n options.ipaPath,\n \"-t\",\n platform,\n \"--apiKey\",\n options.apiKeyId,\n \"--apiIssuer\",\n options.apiIssuerId,\n ];\n\n const child = spawn(\"xcrun\", args, {\n env: { ...process.env, API_PRIVATE_KEYS_DIR: keyDir },\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let output = \"\";\n const timer = setTimeout(() => {\n child.kill(\"SIGTERM\");\n }, timeout);\n\n const onData = (data: Buffer) => {\n const chunk = data.toString();\n output += chunk;\n options.onOutput?.(chunk);\n };\n\n child.stdout.on(\"data\", onData);\n child.stderr.on(\"data\", onData);\n\n child.on(\"close\", async (code) => {\n clearTimeout(timer);\n await unlink(keyPath).catch(() => {});\n await rm(keyDir, { recursive: true, force: true }).catch(() => {});\n resolve({ success: code === 0, output, backend: \"altool\" });\n });\n });\n}\n","import { spawn } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\n\n/**\n * Locate the iTMSTransporter binary on this host.\n *\n * Apple ships iTMSTransporter as a Java-based tool bundled with Xcode\n * on macOS, and as a standalone Linux distribution used by media\n * publishers for CI. Either works for uploading IPAs — same protocol,\n * same ASC API key format.\n *\n * Resolution order:\n * 1. ITMSTRANSPORTER_BIN env var (pinning for prod deployments)\n * 2. Well-known system paths for the host OS\n * 3. Throw with a clear install hint\n *\n * We deliberately do NOT auto-download Transporter. Apple's download\n * URL is not versioned and the license/redistribution story is fuzzy.\n * Prod hosts should install it once via apt/brew/manual, then pin via\n * env var so CI is reproducible.\n */\nexport async function resolveTransporter(): Promise<string> {\n // 1. Env override — the canonical path for prod\n const override = process.env.ITMSTRANSPORTER_BIN;\n if (override) {\n if (!existsSync(override)) {\n throw new Error(\n `ITMSTRANSPORTER_BIN is set to \"${override}\" but that path does not exist.`\n );\n }\n return override;\n }\n\n // 2. Well-known system paths, in preferred order per OS\n const candidates =\n process.platform === \"darwin\"\n ? [\n // Xcode 15+: Transporter.app bundled with Xcode\n \"/Applications/Xcode.app/Contents/SharedFrameworks/ContentDeliveryServices.framework/Versions/A/itms/bin/iTMSTransporter\",\n // Standalone Transporter.app from the Mac App Store\n \"/Applications/Transporter.app/Contents/itms/bin/iTMSTransporter\",\n ]\n : [\n \"/usr/local/itms/bin/iTMSTransporter\",\n \"/opt/itms/bin/iTMSTransporter\",\n \"/usr/local/bin/iTMSTransporter\",\n ];\n\n for (const path of candidates) {\n if (existsSync(path)) return path;\n }\n\n throw new Error(\n [\n \"iTMSTransporter not found.\",\n \"\",\n \"Install options:\",\n process.platform === \"darwin\"\n ? \" - Install Xcode (includes Transporter)\"\n : \" - Download Apple's standalone iTMSTransporter package for Linux\",\n \" - Or set ITMSTRANSPORTER_BIN to the absolute path of the binary\",\n \"\",\n `Searched: ${candidates.join(\", \")}`,\n ].join(\"\\n\")\n );\n}\n\n/**\n * Verify that Java is available on PATH. iTMSTransporter is a Java\n * application and won't run without a JRE.\n */\nexport async function verifyJava(): Promise<{ version: string }> {\n return new Promise((resolve, reject) => {\n const child = spawn(\"java\", [\"-version\"], { stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n let stderr = \"\";\n child.stderr.on(\"data\", (d: Buffer) => {\n stderr += d.toString();\n });\n child.on(\"error\", (err) => {\n reject(\n new Error(\n `java not found on PATH (${err.message}). Install Java 17 LTS and ensure it is on PATH.`\n )\n );\n });\n child.on(\"close\", (code) => {\n if (code !== 0) {\n reject(new Error(`java -version exited ${code}: ${stderr}`));\n return;\n }\n // Output format: `openjdk version \"17.0.8\" 2023-07-18 ...`\n const match = stderr.match(/version \"([^\"]+)\"/);\n resolve({ version: match?.[1] ?? \"unknown\" });\n });\n });\n}\n","import { spawn } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { writeFile, mkdir, rm } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport type { PilotUploadOptions, UploadResult } from \"./types.js\";\nimport { resolveTransporter, verifyJava } from \"./transporter-install.js\";\n\n/**\n * Upload a build to TestFlight via Apple's iTMSTransporter (Java).\n *\n * This path works on Linux (and macOS) and does NOT require Xcode.\n * It is the primary upload path for the ship worker LXC.\n *\n * iTMSTransporter reads the ASC API private key from a directory\n * named by API_PRIVATE_KEYS_DIR, looking for the file `AuthKey_<keyId>.p8`.\n */\nexport async function uploadWithTransporter(\n options: PilotUploadOptions\n): Promise<UploadResult> {\n if (!existsSync(options.ipaPath)) {\n throw new Error(`File not found: ${options.ipaPath}`);\n }\n\n // Fail fast if Java is missing — avoids a cryptic exec error deep inside\n // Transporter. This is cheap (~20ms) and only runs once per upload.\n await verifyJava();\n\n const transporterBin = await resolveTransporter();\n\n const platform = options.platform ?? \"ios\";\n const timeout = options.timeout ?? 1_800_000; // 30 min\n\n // Write the ASC API .p8 key where iTMSTransporter will actually find it.\n //\n // Transporter does NOT reliably honor the API_PRIVATE_KEYS_DIR env var for\n // `-m upload` (observed on the Linux standalone build on the ship worker: it\n // ignored the temp dir and failed with \"Could not locate the private key\n // file named either: AuthKey_<id>.p8 or ApiKey_<id>.p8\"). What it always\n // searches is a fixed set of directories — chiefly `~/.appstoreconnect/\n // private_keys` — so we place the key there. API_PRIVATE_KEYS_DIR is still\n // set below as belt-and-suspenders for versions that do honor it.\n //\n // Uploads are serialized by the ship worker (one build at a time), and the\n // filename is keyed by keyId, so there is no cross-upload collision. We clean\n // up only the file we wrote, never the shared directory.\n const keyDir = join(homedir(), \".appstoreconnect\", \"private_keys\");\n await mkdir(keyDir, { recursive: true });\n const keyPath = join(keyDir, `AuthKey_${options.apiKeyId}.p8`);\n await writeFile(keyPath, options.apiPrivateKey, { mode: 0o600 });\n\n return new Promise<UploadResult>((resolve) => {\n const args = [\n \"-m\", \"upload\",\n \"-assetFile\", options.ipaPath,\n \"-apiKey\", options.apiKeyId,\n \"-apiIssuer\", options.apiIssuerId,\n // WONoPause prevents the JVM from waiting on stdin in non-TTY\n // environments (CI, LaunchAgent) where it would hang forever.\n \"-WONoPause\", \"true\",\n ];\n\n // On Linux, iTMSTransporter cannot run the software-analysis phase that\n // inspects the Mach-O binary, so it needs a pre-generated AppStoreInfo.plist\n // supplied via -assetDescription (created on the macOS build runner). We pass\n // an absolute path because transporter otherwise resolves the descriptor\n // relative to its own install dir, not the cwd.\n if (options.assetDescriptionPath) {\n args.push(\"-assetDescription\", options.assetDescriptionPath);\n }\n\n // Transporter distinguishes iOS vs macOS/tvOS via a -bundleType flag\n // on some code paths. For standard IPAs the protocol is platform-aware\n // via the asset itself, so we only pass the hint when it's non-default.\n if (platform !== \"ios\") {\n args.push(\"-bundleType\", platform);\n }\n\n const child = spawn(transporterBin, args, {\n env: {\n ...process.env,\n API_PRIVATE_KEYS_DIR: keyDir,\n },\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let output = \"\";\n const timer = setTimeout(() => {\n child.kill(\"SIGTERM\");\n }, timeout);\n\n const onData = (data: Buffer) => {\n const chunk = data.toString();\n output += chunk;\n options.onOutput?.(chunk);\n };\n\n child.stdout.on(\"data\", onData);\n child.stderr.on(\"data\", onData);\n\n child.on(\"close\", async (code) => {\n clearTimeout(timer);\n await rm(keyPath, { force: true }).catch(() => {});\n resolve({ success: code === 0, output, backend: \"transporter\" });\n });\n\n child.on(\"error\", async (err) => {\n clearTimeout(timer);\n await rm(keyPath, { force: true }).catch(() => {});\n resolve({\n success: false,\n output: output + `\\nProcess error: ${err.message}`,\n backend: \"transporter\",\n });\n });\n });\n}\n","/**\n * Classify upload failures into transient vs. permanent buckets.\n *\n * Apple's App Store Connect / iTMSTransporter backend is flaky:\n * the finalize step (\"CHANGE UPLOAD STATE TO COMPLETE\") periodically\n * returns 500, and the Java transporter surfaces various connection\n * errors during long uploads. These are all safe to retry — the IPA\n * is deterministic and Apple deduplicates on build number.\n *\n * Non-transient errors (invalid binary, duplicate build number, auth\n * failure) should NOT be retried — they will never succeed without\n * the caller taking some action.\n */\n\nexport type TransientUploadError =\n | \"apple_5xx\"\n | \"finalize_failed\"\n | \"network_timeout\"\n | \"connection_reset\"\n | \"transporter_jvm_error\";\n\ninterface ClassifierRule {\n pattern: RegExp;\n kind: TransientUploadError;\n}\n\nconst RULES: ClassifierRule[] = [\n // Apple's finalize-upload call returning 5xx — the most common transient\n // failure we see in practice. Example:\n // \"CHANGE UPLOAD STATE TO COMPLETE (UPLOADING ASSET DESCRIPTION):\n // received status code 500; internal server error.\"\n { pattern: /CHANGE UPLOAD STATE TO COMPLETE/i, kind: \"finalize_failed\" },\n\n // Generic 5xx from ASC backend services\n { pattern: /status code 5\\d\\d/i, kind: \"apple_5xx\" },\n { pattern: /internal server error/i, kind: \"apple_5xx\" },\n { pattern: /Service Unavailable/i, kind: \"apple_5xx\" },\n { pattern: /Bad Gateway/i, kind: \"apple_5xx\" },\n\n // Network-level interruptions during the upload body transfer\n { pattern: /Connection reset/i, kind: \"connection_reset\" },\n { pattern: /SocketException/i, kind: \"connection_reset\" },\n { pattern: /Broken pipe/i, kind: \"connection_reset\" },\n\n // Timeouts — distinct from resets because the other side may have\n // actually processed the request, so dedupe logic may matter.\n { pattern: /Read timed out/i, kind: \"network_timeout\" },\n { pattern: /SocketTimeoutException/i, kind: \"network_timeout\" },\n { pattern: /Operation timed out/i, kind: \"network_timeout\" },\n\n // JVM-internal errors from iTMSTransporter (OOM, thread interruption, etc.)\n { pattern: /java\\.lang\\.OutOfMemoryError/i, kind: \"transporter_jvm_error\" },\n { pattern: /EOFException/i, kind: \"transporter_jvm_error\" },\n];\n\n/**\n * Returns the transient-error kind if the output matches any known\n * retry-worthy pattern, or `null` if the error is non-transient.\n */\nexport function classifyUploadError(output: string): TransientUploadError | null {\n for (const { pattern, kind } of RULES) {\n if (pattern.test(output)) return kind;\n }\n return null;\n}\n\n/**\n * Convenience: `true` if the upload should be retried.\n */\nexport function isTransientUploadError(output: string): boolean {\n return classifyUploadError(output) !== null;\n}\n","import emojiRegex from \"emoji-regex\";\nimport type { AppStoreConnectClient } from \"../client/index.js\";\nimport { AppStoreConnectError } from \"../client/request.js\";\nimport type { Build } from \"../types/index.js\";\nimport type {\n PilotUploadOptions,\n UploadBackend,\n UploadResult,\n} from \"./types.js\";\nimport { uploadWithAltool } from \"./upload-altool.js\";\nimport { uploadWithTransporter } from \"./upload-transporter.js\";\n\nexport type { PilotUploadOptions, UploadBackend, UploadResult } from \"./types.js\";\nexport { uploadWithAltool } from \"./upload-altool.js\";\nexport { uploadWithTransporter } from \"./upload-transporter.js\";\nexport {\n classifyUploadError,\n isTransientUploadError,\n} from \"./classify.js\";\nexport type { TransientUploadError } from \"./classify.js\";\nexport { resolveTransporter, verifyJava } from \"./transporter-install.js\";\n\nexport interface PilotDistributeOptions {\n client: AppStoreConnectClient;\n buildId: string;\n groupNames?: string[];\n changelog?: string;\n locale?: string;\n expirePreviousBuilds?: boolean;\n submitForReview?: boolean;\n}\n\n// ── Upload ─────────────────────────────────────────────────────────────────\n\n/**\n * Upload a build to TestFlight.\n *\n * Selects the upload backend based on the host OS:\n * - macOS → altool (ships with Xcode CLI)\n * - Linux → iTMSTransporter (standalone Java tool from Apple)\n *\n * Pass `backend` to force a specific path, e.g. for testing the\n * Linux path on a mac dev machine.\n */\nexport async function uploadToTestFlight(\n options: PilotUploadOptions\n): Promise<UploadResult> {\n const backend = selectBackend(options.backend ?? \"auto\");\n if (backend === \"altool\") {\n return uploadWithAltool(options);\n }\n return uploadWithTransporter(options);\n}\n\nfunction selectBackend(pref: UploadBackend | \"auto\"): UploadBackend {\n if (pref !== \"auto\") return pref;\n return process.platform === \"darwin\" ? \"altool\" : \"transporter\";\n}\n\n// ── Wait for Processing ────────────────────────────────────────────────────\n\nexport async function waitForProcessing(\n client: AppStoreConnectClient,\n params: {\n bundleId: string;\n buildNumber: string;\n appVersion: string;\n timeout?: number;\n pollInterval?: number;\n }\n): Promise<Build> {\n const timeout = params.timeout ?? 1_800_000; // 30 min\n const pollInterval = params.pollInterval ?? 30_000; // 30s\n const deadline = Date.now() + timeout;\n\n // Find app\n const app = await client.apps.findByBundleId(params.bundleId);\n if (!app) throw new Error(`App not found for bundle ID: ${params.bundleId}`);\n\n while (Date.now() < deadline) {\n const builds = await client.builds.list({\n filter: {\n app: app.id,\n version: params.buildNumber,\n \"preReleaseVersion.version\": params.appVersion,\n },\n limit: 1,\n });\n\n if (builds.length > 0) {\n const build = builds[0];\n const state = build.attributes.processingState;\n\n if (state === \"VALID\") return build;\n if (state === \"FAILED\" || state === \"INVALID\") {\n throw new Error(`Build processing ${state}: ${params.buildNumber}`);\n }\n }\n\n await sleep(pollInterval);\n }\n\n throw new Error(\n `Timeout waiting for build ${params.buildNumber} after ${timeout / 1000}s`\n );\n}\n\n// ── Distribute ─────────────────────────────────────────────────────────────\n\nexport async function distributeToGroups(\n options: PilotDistributeOptions\n): Promise<string[]> {\n const groupNames = options.groupNames ?? [];\n if (groupNames.length === 0) return [];\n\n // Get the build's app\n const build = await options.client.builds.get(options.buildId, {\n include: \"app\",\n });\n const appId =\n build.relationships?.app?.data?.id;\n if (!appId) throw new Error(\"Could not determine app for build\");\n\n // Set changelog if provided\n if (options.changelog) {\n await setChangelog({\n client: options.client,\n buildId: options.buildId,\n changelog: options.changelog,\n locale: options.locale,\n });\n }\n\n // Expire previous builds if requested\n if (options.expirePreviousBuilds) {\n const allBuilds = await options.client.builds.list({\n filter: { app: appId, expired: \"false\" },\n limit: 50,\n });\n for (const b of allBuilds) {\n if (b.id !== options.buildId) {\n await options.client.builds.expireBuild(b.id).catch(() => {});\n }\n }\n }\n\n // Find and add to groups\n const allGroups = await options.client.betaGroups.listForApp(appId);\n const matched: string[] = [];\n\n for (const name of groupNames) {\n const group = allGroups.find((g) => g.attributes.name === name);\n if (!group) continue;\n\n try {\n await options.client.betaGroups.addBuilds(group.id, [options.buildId]);\n matched.push(name);\n } catch (err) {\n // 409 = already added, that's fine\n if (err instanceof AppStoreConnectError && err.status === 409) {\n matched.push(name);\n }\n }\n }\n\n // Submit for beta review if requested\n if (options.submitForReview) {\n try {\n await options.client.builds.submitForBetaReview(options.buildId);\n } catch {\n // May already be submitted\n }\n }\n\n return matched;\n}\n\n// ── Changelog ──────────────────────────────────────────────────────────────\n\nexport async function setChangelog(options: {\n client: AppStoreConnectClient;\n buildId: string;\n changelog: string;\n locale?: string;\n}): Promise<void> {\n const locale = options.locale ?? \"en-US\";\n const sanitized = sanitizeChangelog(options.changelog);\n\n const localizations = await options.client.builds.getLocalizations(\n options.buildId\n );\n const existing = localizations.find(\n (l) => l.attributes.locale === locale\n );\n\n if (existing) {\n await options.client.builds.updateLocalization(existing.id, {\n whatsNew: sanitized,\n });\n } else {\n await options.client.builds.createLocalization({\n buildId: options.buildId,\n locale,\n whatsNew: sanitized,\n });\n }\n}\n\n// ── Expire ─────────────────────────────────────────────────────────────────\n\nexport async function expireBuild(\n client: AppStoreConnectClient,\n buildId: string\n): Promise<void> {\n await client.builds.expireBuild(buildId);\n}\n\n// ── Helpers ────────────────────────────────────────────────────────────────\n\nfunction sanitizeChangelog(text: string): string {\n // Strip RGI-standard emojis using the `emoji-regex` package — same\n // approach as Fastlane's pilot (gem `emoji_regex`), which tracks the\n // Unicode \"Recommended for General Interchange\" spec so new emoji\n // sequences are covered without hand-maintaining ranges.\n let sanitized = text.replace(emojiRegex(), \"\");\n\n // Strip < characters (not allowed by Apple)\n sanitized = sanitized.replace(/</g, \"\");\n\n // Truncate to 4000 bytes\n const encoder = new TextEncoder();\n const encoded = encoder.encode(sanitized);\n if (encoded.length > 4000) {\n const decoder = new TextDecoder();\n sanitized = decoder.decode(encoded.slice(0, 3997)) + \"...\";\n }\n\n return sanitized;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"]}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var promises = require('fs/promises');
|
|
4
|
+
var path = require('path');
|
|
5
|
+
var jose = require('jose');
|
|
6
|
+
|
|
7
|
+
// src/playstore/index.ts
|
|
8
|
+
var ANDROIDPUBLISHER_SCOPE = "https://www.googleapis.com/auth/androidpublisher";
|
|
9
|
+
var OAUTH_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
10
|
+
var API_BASE = "https://androidpublisher.googleapis.com/androidpublisher/v3";
|
|
11
|
+
var PlayApiError = class extends Error {
|
|
12
|
+
status;
|
|
13
|
+
constructor(message, status) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = "PlayApiError";
|
|
16
|
+
this.status = status;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
async function getAccessToken(account) {
|
|
20
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
21
|
+
const privateKey = await jose.importPKCS8(account.private_key, "RS256");
|
|
22
|
+
const assertion = await new jose.SignJWT({ scope: ANDROIDPUBLISHER_SCOPE }).setProtectedHeader({ alg: "RS256", typ: "JWT" }).setIssuer(account.client_email).setAudience(OAUTH_TOKEN_URL).setIssuedAt(now).setExpirationTime(now + 3600).sign(privateKey);
|
|
23
|
+
const response = await fetch(OAUTH_TOKEN_URL, {
|
|
24
|
+
method: "POST",
|
|
25
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
26
|
+
body: new URLSearchParams({
|
|
27
|
+
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
|
|
28
|
+
assertion
|
|
29
|
+
})
|
|
30
|
+
});
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
const err = await response.text().catch(() => "");
|
|
33
|
+
throw new PlayApiError(
|
|
34
|
+
`OAuth token request failed: ${response.status} - ${err}`,
|
|
35
|
+
response.status
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
const data = await response.json();
|
|
39
|
+
return data.access_token;
|
|
40
|
+
}
|
|
41
|
+
async function readErrorBody(response) {
|
|
42
|
+
const text = await response.text().catch(() => "");
|
|
43
|
+
return text;
|
|
44
|
+
}
|
|
45
|
+
async function uploadToPlay(options) {
|
|
46
|
+
const progress = (msg) => options.onProgress?.(msg);
|
|
47
|
+
let account;
|
|
48
|
+
try {
|
|
49
|
+
account = JSON.parse(options.serviceAccountJson);
|
|
50
|
+
} catch {
|
|
51
|
+
throw new PlayApiError("Invalid service account JSON");
|
|
52
|
+
}
|
|
53
|
+
if (!account.client_email || !account.private_key) {
|
|
54
|
+
throw new PlayApiError(
|
|
55
|
+
"Service account JSON missing client_email or private_key"
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
const isAab = options.artifactPath.toLowerCase().endsWith(".aab");
|
|
59
|
+
const isApk = options.artifactPath.toLowerCase().endsWith(".apk");
|
|
60
|
+
if (!isAab && !isApk) {
|
|
61
|
+
throw new PlayApiError(
|
|
62
|
+
`Unsupported artifact extension: ${path.basename(options.artifactPath)} (expected .aab or .apk)`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
progress("Reading artifact\u2026");
|
|
66
|
+
const artifact = await promises.readFile(options.artifactPath);
|
|
67
|
+
progress("Authenticating with Google\u2026");
|
|
68
|
+
const accessToken = await getAccessToken(account);
|
|
69
|
+
const authHeader = { Authorization: `Bearer ${accessToken}` };
|
|
70
|
+
const editsBase = `${API_BASE}/applications/${encodeURIComponent(
|
|
71
|
+
options.packageName
|
|
72
|
+
)}/edits`;
|
|
73
|
+
progress("Creating edit\u2026");
|
|
74
|
+
const createRes = await fetch(editsBase, {
|
|
75
|
+
method: "POST",
|
|
76
|
+
headers: { ...authHeader, "Content-Type": "application/json" },
|
|
77
|
+
body: "{}"
|
|
78
|
+
});
|
|
79
|
+
if (!createRes.ok) {
|
|
80
|
+
throw new PlayApiError(
|
|
81
|
+
`Failed to create edit: ${createRes.status} - ${await readErrorBody(
|
|
82
|
+
createRes
|
|
83
|
+
)}`,
|
|
84
|
+
createRes.status
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
const edit = await createRes.json();
|
|
88
|
+
const editId = edit.id;
|
|
89
|
+
const deleteEdit = async () => {
|
|
90
|
+
await fetch(`${editsBase}/${editId}`, {
|
|
91
|
+
method: "DELETE",
|
|
92
|
+
headers: authHeader
|
|
93
|
+
}).catch(() => {
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
try {
|
|
97
|
+
const uploadKind = isAab ? "bundles" : "apks";
|
|
98
|
+
progress(`Uploading ${isAab ? "bundle" : "apk"}\u2026`);
|
|
99
|
+
const uploadUrl = `https://androidpublisher.googleapis.com/upload/androidpublisher/v3/applications/${encodeURIComponent(
|
|
100
|
+
options.packageName
|
|
101
|
+
)}/edits/${editId}/${uploadKind}?uploadType=media`;
|
|
102
|
+
const uploadRes = await fetch(uploadUrl, {
|
|
103
|
+
method: "POST",
|
|
104
|
+
headers: {
|
|
105
|
+
...authHeader,
|
|
106
|
+
"Content-Type": "application/octet-stream"
|
|
107
|
+
},
|
|
108
|
+
body: artifact
|
|
109
|
+
});
|
|
110
|
+
if (!uploadRes.ok) {
|
|
111
|
+
throw new PlayApiError(
|
|
112
|
+
`Failed to upload artifact: ${uploadRes.status} - ${await readErrorBody(
|
|
113
|
+
uploadRes
|
|
114
|
+
)}`,
|
|
115
|
+
uploadRes.status
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
const uploaded = await uploadRes.json();
|
|
119
|
+
const versionCode = uploaded.versionCode;
|
|
120
|
+
progress(`Uploaded versionCode ${versionCode}`);
|
|
121
|
+
progress(`Assigning to ${options.track} track\u2026`);
|
|
122
|
+
const release = {
|
|
123
|
+
versionCodes: [versionCode],
|
|
124
|
+
status: "completed"
|
|
125
|
+
};
|
|
126
|
+
if (options.releaseNotes) {
|
|
127
|
+
release.releaseNotes = [
|
|
128
|
+
{ language: "en-US", text: options.releaseNotes }
|
|
129
|
+
];
|
|
130
|
+
}
|
|
131
|
+
if (options.releaseName) {
|
|
132
|
+
release.name = options.releaseName;
|
|
133
|
+
}
|
|
134
|
+
const trackRes = await fetch(
|
|
135
|
+
`${editsBase}/${editId}/tracks/${options.track}`,
|
|
136
|
+
{
|
|
137
|
+
method: "PUT",
|
|
138
|
+
headers: { ...authHeader, "Content-Type": "application/json" },
|
|
139
|
+
body: JSON.stringify({
|
|
140
|
+
track: options.track,
|
|
141
|
+
releases: [release]
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
if (!trackRes.ok) {
|
|
146
|
+
throw new PlayApiError(
|
|
147
|
+
`Failed to update track: ${trackRes.status} - ${await readErrorBody(
|
|
148
|
+
trackRes
|
|
149
|
+
)}`,
|
|
150
|
+
trackRes.status
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
progress("Committing edit\u2026");
|
|
154
|
+
const commitRes = await fetch(`${editsBase}/${editId}:commit`, {
|
|
155
|
+
method: "POST",
|
|
156
|
+
headers: authHeader
|
|
157
|
+
});
|
|
158
|
+
if (!commitRes.ok) {
|
|
159
|
+
throw new PlayApiError(
|
|
160
|
+
`Failed to commit edit: ${commitRes.status} - ${await readErrorBody(
|
|
161
|
+
commitRes
|
|
162
|
+
)}`,
|
|
163
|
+
commitRes.status
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
progress("Done.");
|
|
167
|
+
return { success: true, versionCode, track: options.track };
|
|
168
|
+
} catch (err) {
|
|
169
|
+
await deleteEdit();
|
|
170
|
+
throw err;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function classifyPlayError(err) {
|
|
174
|
+
if (err instanceof PlayApiError && typeof err.status === "number") {
|
|
175
|
+
const status = err.status;
|
|
176
|
+
if (status === 429 || status >= 500) return { transient: true };
|
|
177
|
+
if (status >= 400) return { transient: false };
|
|
178
|
+
}
|
|
179
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
180
|
+
if (/\b429\b/.test(message) || /status code 5\d\d/i.test(message)) {
|
|
181
|
+
return { transient: true };
|
|
182
|
+
}
|
|
183
|
+
if (/\b4\d\d\b/.test(message)) {
|
|
184
|
+
return { transient: false };
|
|
185
|
+
}
|
|
186
|
+
const networkPatterns = [
|
|
187
|
+
/ECONNRESET/i,
|
|
188
|
+
/ECONNREFUSED/i,
|
|
189
|
+
/ETIMEDOUT/i,
|
|
190
|
+
/EAI_AGAIN/i,
|
|
191
|
+
/ENOTFOUND/i,
|
|
192
|
+
/socket hang up/i,
|
|
193
|
+
/network/i,
|
|
194
|
+
/timed out/i,
|
|
195
|
+
/fetch failed/i
|
|
196
|
+
];
|
|
197
|
+
if (networkPatterns.some((p) => p.test(message))) {
|
|
198
|
+
return { transient: true };
|
|
199
|
+
}
|
|
200
|
+
return { transient: false };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
exports.PlayApiError = PlayApiError;
|
|
204
|
+
exports.classifyPlayError = classifyPlayError;
|
|
205
|
+
exports.uploadToPlay = uploadToPlay;
|
|
206
|
+
//# sourceMappingURL=index.cjs.map
|
|
207
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/playstore/index.ts"],"names":["importPKCS8","SignJWT","basename","readFile"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Google Play Developer API v3 uploader — the Android-side equivalent of\n * `pilot` (TestFlight). Uses a service-account JSON to mint an OAuth2 access\n * token (scope androidpublisher), then drives the Edits → upload → track →\n * commit flow against the Android Publisher REST API.\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { basename } from \"node:path\";\n\nimport { SignJWT, importPKCS8 } from \"jose\";\n\n// ── Constants ──────────────────────────────────────────────────────────────\n\nconst ANDROIDPUBLISHER_SCOPE =\n \"https://www.googleapis.com/auth/androidpublisher\";\nconst OAUTH_TOKEN_URL = \"https://oauth2.googleapis.com/token\";\nconst API_BASE = \"https://androidpublisher.googleapis.com/androidpublisher/v3\";\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\nexport type PlayTrack = \"internal\" | \"alpha\" | \"beta\" | \"production\";\n\nexport interface PlayUploadOptions {\n packageName: string;\n /** Raw service-account JSON string (with client_email + private_key). */\n serviceAccountJson: string;\n /** Path to the .aab or .apk artifact to upload. */\n artifactPath: string;\n track: PlayTrack;\n releaseNotes?: string;\n releaseName?: string;\n onProgress?: (msg: string) => void;\n}\n\nexport interface PlayUploadResult {\n success: true;\n versionCode: number;\n track: PlayTrack;\n}\n\ninterface ServiceAccount {\n client_email: string;\n private_key: string;\n}\n\n/**\n * Error thrown for any Play upload failure, carrying the HTTP status (if any)\n * so callers/`classifyPlayError` can decide whether to retry.\n */\nexport class PlayApiError extends Error {\n readonly status?: number;\n\n constructor(message: string, status?: number) {\n super(message);\n this.name = \"PlayApiError\";\n this.status = status;\n }\n}\n\n// ── Auth ─────────────────────────────────────────────────────────────────────\n\n/**\n * Mint an OAuth2 access token from a service account, same flow as the FCM\n * client: jose SignJWT (RS256) → POST oauth2.googleapis.com/token.\n */\nasync function getAccessToken(account: ServiceAccount): Promise<string> {\n const now = Math.floor(Date.now() / 1000);\n const privateKey = await importPKCS8(account.private_key, \"RS256\");\n\n const assertion = await new SignJWT({ scope: ANDROIDPUBLISHER_SCOPE })\n .setProtectedHeader({ alg: \"RS256\", typ: \"JWT\" })\n .setIssuer(account.client_email)\n .setAudience(OAUTH_TOKEN_URL)\n .setIssuedAt(now)\n .setExpirationTime(now + 3600)\n .sign(privateKey);\n\n const response = await fetch(OAUTH_TOKEN_URL, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({\n grant_type: \"urn:ietf:params:oauth:grant-type:jwt-bearer\",\n assertion,\n }),\n });\n\n if (!response.ok) {\n const err = await response.text().catch(() => \"\");\n throw new PlayApiError(\n `OAuth token request failed: ${response.status} - ${err}`,\n response.status,\n );\n }\n\n const data = (await response.json()) as { access_token: string };\n return data.access_token;\n}\n\n// ── HTTP helpers ─────────────────────────────────────────────────────────────\n\nasync function readErrorBody(response: Response): Promise<string> {\n const text = await response.text().catch(() => \"\");\n return text;\n}\n\n// ── Main entry point ─────────────────────────────────────────────────────────\n\n/**\n * Upload an artifact (.aab or .apk) to a Google Play track and roll it out.\n *\n * Flow:\n * 1. Create an edit.\n * 2. Upload the bundle/apk bytes, capturing the returned versionCode.\n * 3. Assign that versionCode to the requested track (status \"completed\").\n * 4. Commit the edit.\n * 5. On any failure, delete the edit (best effort) and throw.\n */\nexport async function uploadToPlay(\n options: PlayUploadOptions,\n): Promise<PlayUploadResult> {\n const progress = (msg: string) => options.onProgress?.(msg);\n\n let account: ServiceAccount;\n try {\n account = JSON.parse(options.serviceAccountJson) as ServiceAccount;\n } catch {\n throw new PlayApiError(\"Invalid service account JSON\");\n }\n if (!account.client_email || !account.private_key) {\n throw new PlayApiError(\n \"Service account JSON missing client_email or private_key\",\n );\n }\n\n const isAab = options.artifactPath.toLowerCase().endsWith(\".aab\");\n const isApk = options.artifactPath.toLowerCase().endsWith(\".apk\");\n if (!isAab && !isApk) {\n throw new PlayApiError(\n `Unsupported artifact extension: ${basename(options.artifactPath)} ` +\n `(expected .aab or .apk)`,\n );\n }\n\n progress(\"Reading artifact…\");\n const artifact = await readFile(options.artifactPath);\n\n progress(\"Authenticating with Google…\");\n const accessToken = await getAccessToken(account);\n\n const authHeader = { Authorization: `Bearer ${accessToken}` };\n const editsBase = `${API_BASE}/applications/${encodeURIComponent(\n options.packageName,\n )}/edits`;\n\n // ── 1. Create edit ────────────────────────────────────────────────────\n progress(\"Creating edit…\");\n const createRes = await fetch(editsBase, {\n method: \"POST\",\n headers: { ...authHeader, \"Content-Type\": \"application/json\" },\n body: \"{}\",\n });\n if (!createRes.ok) {\n throw new PlayApiError(\n `Failed to create edit: ${createRes.status} - ${await readErrorBody(\n createRes,\n )}`,\n createRes.status,\n );\n }\n const edit = (await createRes.json()) as { id: string };\n const editId = edit.id;\n\n const deleteEdit = async () => {\n await fetch(`${editsBase}/${editId}`, {\n method: \"DELETE\",\n headers: authHeader,\n }).catch(() => {});\n };\n\n try {\n // ── 2. Upload artifact ──────────────────────────────────────────────\n const uploadKind = isAab ? \"bundles\" : \"apks\";\n progress(`Uploading ${isAab ? \"bundle\" : \"apk\"}…`);\n const uploadUrl = `https://androidpublisher.googleapis.com/upload/androidpublisher/v3/applications/${encodeURIComponent(\n options.packageName,\n )}/edits/${editId}/${uploadKind}?uploadType=media`;\n\n const uploadRes = await fetch(uploadUrl, {\n method: \"POST\",\n headers: {\n ...authHeader,\n \"Content-Type\": \"application/octet-stream\",\n },\n body: artifact,\n });\n if (!uploadRes.ok) {\n throw new PlayApiError(\n `Failed to upload artifact: ${uploadRes.status} - ${await readErrorBody(\n uploadRes,\n )}`,\n uploadRes.status,\n );\n }\n const uploaded = (await uploadRes.json()) as { versionCode: number };\n const versionCode = uploaded.versionCode;\n progress(`Uploaded versionCode ${versionCode}`);\n\n // ── 3. Assign to track ──────────────────────────────────────────────\n progress(`Assigning to ${options.track} track…`);\n const release: Record<string, unknown> = {\n versionCodes: [versionCode],\n status: \"completed\",\n };\n if (options.releaseNotes) {\n release.releaseNotes = [\n { language: \"en-US\", text: options.releaseNotes },\n ];\n }\n if (options.releaseName) {\n release.name = options.releaseName;\n }\n\n const trackRes = await fetch(\n `${editsBase}/${editId}/tracks/${options.track}`,\n {\n method: \"PUT\",\n headers: { ...authHeader, \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n track: options.track,\n releases: [release],\n }),\n },\n );\n if (!trackRes.ok) {\n throw new PlayApiError(\n `Failed to update track: ${trackRes.status} - ${await readErrorBody(\n trackRes,\n )}`,\n trackRes.status,\n );\n }\n\n // ── 4. Commit edit ──────────────────────────────────────────────────\n progress(\"Committing edit…\");\n const commitRes = await fetch(`${editsBase}/${editId}:commit`, {\n method: \"POST\",\n headers: authHeader,\n });\n if (!commitRes.ok) {\n throw new PlayApiError(\n `Failed to commit edit: ${commitRes.status} - ${await readErrorBody(\n commitRes,\n )}`,\n commitRes.status,\n );\n }\n\n progress(\"Done.\");\n return { success: true, versionCode, track: options.track };\n } catch (err) {\n // Best-effort cleanup so a half-finished edit doesn't linger.\n await deleteEdit();\n throw err;\n }\n}\n\n// ── Error classification ─────────────────────────────────────────────────────\n\n/**\n * Classify a Play upload failure into transient vs. fatal — mirrors\n * pilot/classify.ts. HTTP 429/5xx and network-level errors are transient\n * (safe to retry); 4xx (auth, invalid bundle, version conflict) are fatal.\n */\nexport function classifyPlayError(err: unknown): { transient: boolean } {\n if (err instanceof PlayApiError && typeof err.status === \"number\") {\n const status = err.status;\n if (status === 429 || status >= 500) return { transient: true };\n if (status >= 400) return { transient: false };\n }\n\n const message = err instanceof Error ? err.message : String(err);\n\n // Explicit HTTP status mentioned in the message (e.g. wrapped errors).\n if (/\\b429\\b/.test(message) || /status code 5\\d\\d/i.test(message)) {\n return { transient: true };\n }\n if (/\\b4\\d\\d\\b/.test(message)) {\n return { transient: false };\n }\n\n // Network-level failures from fetch/undici are transient.\n const networkPatterns = [\n /ECONNRESET/i,\n /ECONNREFUSED/i,\n /ETIMEDOUT/i,\n /EAI_AGAIN/i,\n /ENOTFOUND/i,\n /socket hang up/i,\n /network/i,\n /timed out/i,\n /fetch failed/i,\n ];\n if (networkPatterns.some((p) => p.test(message))) {\n return { transient: true };\n }\n\n // Default: treat unknown errors as fatal so we don't retry forever.\n return { transient: false };\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { c as PlayApiError, P as PlayTrack, a as PlayUploadOptions, b as PlayUploadResult, d as classifyPlayError, u as uploadToPlay } from '../index-CXJlvmB4.cjs';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { c as PlayApiError, P as PlayTrack, a as PlayUploadOptions, b as PlayUploadResult, d as classifyPlayError, u as uploadToPlay } from '../index-CXJlvmB4.js';
|