@sitepong/fastnode 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +148 -0
  2. package/dist/actions/index.cjs +964 -137
  3. package/dist/actions/index.cjs.map +1 -1
  4. package/dist/actions/index.mjs +964 -137
  5. package/dist/actions/index.mjs.map +1 -1
  6. package/dist/cert/index.cjs +40 -0
  7. package/dist/cert/index.cjs.map +1 -1
  8. package/dist/cert/index.d.cts +3 -3
  9. package/dist/cert/index.d.ts +3 -3
  10. package/dist/cert/index.mjs +37 -1
  11. package/dist/cert/index.mjs.map +1 -1
  12. package/dist/{cert-BmJGtYtb.d.cts → cert-6m1R8UeW.d.cts} +1 -1
  13. package/dist/{cert-owkOFNW8.d.ts → cert-DtYDJBjG.d.ts} +1 -1
  14. package/dist/cli/index.cjs +964 -137
  15. package/dist/cli/index.cjs.map +1 -1
  16. package/dist/cli/index.d.cts +1 -1
  17. package/dist/cli/index.d.ts +1 -1
  18. package/dist/cli/index.mjs +964 -137
  19. package/dist/cli/index.mjs.map +1 -1
  20. package/dist/client/index.cjs +730 -109
  21. package/dist/client/index.cjs.map +1 -1
  22. package/dist/client/index.d.cts +1 -1
  23. package/dist/client/index.d.ts +1 -1
  24. package/dist/client/index.mjs +726 -110
  25. package/dist/client/index.mjs.map +1 -1
  26. package/dist/deliver/index.d.cts +2 -2
  27. package/dist/deliver/index.d.ts +2 -2
  28. package/dist/index-AD8QXY1T.d.cts +158 -0
  29. package/dist/{index-DfCUVJX1.d.cts → index-BW8wQUGC.d.cts} +1 -1
  30. package/dist/{index-EcVvViW8.d.cts → index-BWK-T2-S.d.cts} +1 -1
  31. package/dist/{index-B0yhu_LZ.d.ts → index-BpiEBPVd.d.ts} +1 -1
  32. package/dist/index-CCs5irG7.d.cts +61 -0
  33. package/dist/{index-B9yXicrQ.d.ts → index-CLsvyTkh.d.ts} +1 -1
  34. package/dist/{index-BLubSlgK.d.cts → index-CMH_GpN9.d.cts} +358 -2
  35. package/dist/index-CQKpbHZt.d.ts +61 -0
  36. package/dist/{index-BQJIJlK7.d.cts → index-CQd4bo3G.d.cts} +3 -3
  37. package/dist/{index-CWNLnSOL.d.cts → index-CdYdwxpQ.d.cts} +1 -1
  38. package/dist/{index-BFdNOmPJ.d.cts → index-Ce-WMV7c.d.cts} +1 -1
  39. package/dist/{index-TfXqwuac.d.ts → index-CojSGJ7m.d.ts} +1 -1
  40. package/dist/{index-1Vu2cRQ2.d.ts → index-CsFIp7OO.d.ts} +1 -1
  41. package/dist/{index-D1Izm7qt.d.ts → index-D2z7RiAM.d.ts} +358 -2
  42. package/dist/index-D82CmjMP.d.ts +158 -0
  43. package/dist/{index-BN0ubxjZ.d.ts → index-DZaPkUnC.d.ts} +3 -3
  44. package/dist/{index-D5eihzwM.d.cts → index-KCB2q1-8.d.cts} +1 -1
  45. package/dist/{index-A5MimeQk.d.ts → index-Kq3rbG0u.d.ts} +1 -1
  46. package/dist/index.cjs +1322 -138
  47. package/dist/index.cjs.map +1 -1
  48. package/dist/index.d.cts +199 -10
  49. package/dist/index.d.ts +199 -10
  50. package/dist/index.mjs +1319 -139
  51. package/dist/index.mjs.map +1 -1
  52. package/dist/lane/index.cjs +964 -137
  53. package/dist/lane/index.cjs.map +1 -1
  54. package/dist/lane/index.mjs +964 -137
  55. package/dist/lane/index.mjs.map +1 -1
  56. package/dist/match/index.d.cts +4 -4
  57. package/dist/match/index.d.ts +4 -4
  58. package/dist/pem/index.d.cts +2 -2
  59. package/dist/pem/index.d.ts +2 -2
  60. package/dist/pilot/index.cjs +190 -12
  61. package/dist/pilot/index.cjs.map +1 -1
  62. package/dist/pilot/index.d.cts +2 -2
  63. package/dist/pilot/index.d.ts +2 -2
  64. package/dist/pilot/index.mjs +186 -14
  65. package/dist/pilot/index.mjs.map +1 -1
  66. package/dist/precheck/index.d.cts +2 -2
  67. package/dist/precheck/index.d.ts +2 -2
  68. package/dist/produce/index.d.cts +2 -2
  69. package/dist/produce/index.d.ts +2 -2
  70. package/dist/sigh/index.d.cts +2 -2
  71. package/dist/sigh/index.d.ts +2 -2
  72. package/package.json +3 -1
  73. package/dist/index-DfirmFbC.d.ts +0 -32
  74. package/dist/index-DkvinY7_.d.ts +0 -54
  75. package/dist/index-HRiaaX-Q.d.cts +0 -54
  76. package/dist/index-lt819Jgj.d.cts +0 -32
@@ -1,11 +1,11 @@
1
+ import emojiRegex from 'emoji-regex';
2
+ import 'crypto';
3
+ import 'readline';
1
4
  import { spawn } from 'child_process';
2
5
  import { existsSync } from 'fs';
3
- import { mkdtemp, writeFile, unlink } from 'fs/promises';
6
+ import { mkdtemp, writeFile, unlink, rm } from 'fs/promises';
4
7
  import { join } from 'path';
5
8
  import { tmpdir } from 'os';
6
- import emojiRegex from 'emoji-regex';
7
- import 'crypto';
8
- import 'readline';
9
9
 
10
10
  // src/pilot/index.ts
11
11
  BigInt(
@@ -23,15 +23,13 @@ var AppStoreConnectError = class extends Error {
23
23
  status;
24
24
  errors;
25
25
  };
26
-
27
- // src/pilot/index.ts
28
- async function uploadToTestFlight(options) {
26
+ async function uploadWithAltool(options) {
29
27
  if (!existsSync(options.ipaPath)) {
30
28
  throw new Error(`File not found: ${options.ipaPath}`);
31
29
  }
32
30
  const platform = options.platform ?? "ios";
33
31
  const timeout = options.timeout ?? 18e5;
34
- const keyDir = await mkdtemp(join(tmpdir(), "fastnode-key-"));
32
+ const keyDir = await mkdtemp(join(tmpdir(), "fastnode-altool-"));
35
33
  const keyPath = join(keyDir, `AuthKey_${options.apiKeyId}.p8`);
36
34
  await writeFile(keyPath, options.apiPrivateKey);
37
35
  return new Promise((resolve) => {
@@ -55,24 +53,198 @@ async function uploadToTestFlight(options) {
55
53
  const timer = setTimeout(() => {
56
54
  child.kill("SIGTERM");
57
55
  }, timeout);
58
- child.stdout.on("data", (data) => {
56
+ const onData = (data) => {
59
57
  const chunk = data.toString();
60
58
  output += chunk;
61
59
  options.onOutput?.(chunk);
60
+ };
61
+ child.stdout.on("data", onData);
62
+ child.stderr.on("data", onData);
63
+ child.on("close", async (code) => {
64
+ clearTimeout(timer);
65
+ await unlink(keyPath).catch(() => {
66
+ });
67
+ await rm(keyDir, { recursive: true, force: true }).catch(() => {
68
+ });
69
+ resolve({ success: code === 0, output, backend: "altool" });
62
70
  });
63
- child.stderr.on("data", (data) => {
71
+ });
72
+ }
73
+ async function resolveTransporter() {
74
+ const override = process.env.ITMSTRANSPORTER_BIN;
75
+ if (override) {
76
+ if (!existsSync(override)) {
77
+ throw new Error(
78
+ `ITMSTRANSPORTER_BIN is set to "${override}" but that path does not exist.`
79
+ );
80
+ }
81
+ return override;
82
+ }
83
+ const candidates = process.platform === "darwin" ? [
84
+ // Xcode 15+: Transporter.app bundled with Xcode
85
+ "/Applications/Xcode.app/Contents/SharedFrameworks/ContentDeliveryServices.framework/Versions/A/itms/bin/iTMSTransporter",
86
+ // Standalone Transporter.app from the Mac App Store
87
+ "/Applications/Transporter.app/Contents/itms/bin/iTMSTransporter"
88
+ ] : [
89
+ "/usr/local/itms/bin/iTMSTransporter",
90
+ "/opt/itms/bin/iTMSTransporter",
91
+ "/usr/local/bin/iTMSTransporter"
92
+ ];
93
+ for (const path of candidates) {
94
+ if (existsSync(path)) return path;
95
+ }
96
+ throw new Error(
97
+ [
98
+ "iTMSTransporter not found.",
99
+ "",
100
+ "Install options:",
101
+ process.platform === "darwin" ? " - Install Xcode (includes Transporter)" : " - Download Apple's standalone iTMSTransporter package for Linux",
102
+ " - Or set ITMSTRANSPORTER_BIN to the absolute path of the binary",
103
+ "",
104
+ `Searched: ${candidates.join(", ")}`
105
+ ].join("\n")
106
+ );
107
+ }
108
+ async function verifyJava() {
109
+ return new Promise((resolve, reject) => {
110
+ const child = spawn("java", ["-version"], { stdio: ["ignore", "pipe", "pipe"] });
111
+ let stderr = "";
112
+ child.stderr.on("data", (d) => {
113
+ stderr += d.toString();
114
+ });
115
+ child.on("error", (err) => {
116
+ reject(
117
+ new Error(
118
+ `java not found on PATH (${err.message}). Install Java 17 LTS and ensure it is on PATH.`
119
+ )
120
+ );
121
+ });
122
+ child.on("close", (code) => {
123
+ if (code !== 0) {
124
+ reject(new Error(`java -version exited ${code}: ${stderr}`));
125
+ return;
126
+ }
127
+ const match = stderr.match(/version "([^"]+)"/);
128
+ resolve({ version: match?.[1] ?? "unknown" });
129
+ });
130
+ });
131
+ }
132
+
133
+ // src/pilot/upload-transporter.ts
134
+ async function uploadWithTransporter(options) {
135
+ if (!existsSync(options.ipaPath)) {
136
+ throw new Error(`File not found: ${options.ipaPath}`);
137
+ }
138
+ await verifyJava();
139
+ const transporterBin = await resolveTransporter();
140
+ const platform = options.platform ?? "ios";
141
+ const timeout = options.timeout ?? 18e5;
142
+ const keyDir = await mkdtemp(join(tmpdir(), "fastnode-itms-"));
143
+ const keyPath = join(keyDir, `AuthKey_${options.apiKeyId}.p8`);
144
+ await writeFile(keyPath, options.apiPrivateKey);
145
+ return new Promise((resolve) => {
146
+ const args = [
147
+ "-m",
148
+ "upload",
149
+ "-assetFile",
150
+ options.ipaPath,
151
+ "-apiKey",
152
+ options.apiKeyId,
153
+ "-apiIssuer",
154
+ options.apiIssuerId,
155
+ // WONoPause prevents the JVM from waiting on stdin in non-TTY
156
+ // environments (CI, LaunchAgent) where it would hang forever.
157
+ "-WONoPause",
158
+ "true"
159
+ ];
160
+ if (platform !== "ios") {
161
+ args.push("-bundleType", platform);
162
+ }
163
+ const child = spawn(transporterBin, args, {
164
+ env: {
165
+ ...process.env,
166
+ API_PRIVATE_KEYS_DIR: keyDir
167
+ },
168
+ stdio: ["ignore", "pipe", "pipe"]
169
+ });
170
+ let output = "";
171
+ const timer = setTimeout(() => {
172
+ child.kill("SIGTERM");
173
+ }, timeout);
174
+ const onData = (data) => {
64
175
  const chunk = data.toString();
65
176
  output += chunk;
66
177
  options.onOutput?.(chunk);
67
- });
178
+ };
179
+ child.stdout.on("data", onData);
180
+ child.stderr.on("data", onData);
68
181
  child.on("close", async (code) => {
69
182
  clearTimeout(timer);
70
- await unlink(keyPath).catch(() => {
183
+ await rm(keyDir, { recursive: true, force: true }).catch(() => {
184
+ });
185
+ resolve({ success: code === 0, output, backend: "transporter" });
186
+ });
187
+ child.on("error", async (err) => {
188
+ clearTimeout(timer);
189
+ await rm(keyDir, { recursive: true, force: true }).catch(() => {
190
+ });
191
+ resolve({
192
+ success: false,
193
+ output: output + `
194
+ Process error: ${err.message}`,
195
+ backend: "transporter"
71
196
  });
72
- resolve({ success: code === 0, output });
73
197
  });
74
198
  });
75
199
  }
200
+
201
+ // src/pilot/classify.ts
202
+ var RULES = [
203
+ // Apple's finalize-upload call returning 5xx — the most common transient
204
+ // failure we see in practice. Example:
205
+ // "CHANGE UPLOAD STATE TO COMPLETE (UPLOADING ASSET DESCRIPTION):
206
+ // received status code 500; internal server error."
207
+ { pattern: /CHANGE UPLOAD STATE TO COMPLETE/i, kind: "finalize_failed" },
208
+ // Generic 5xx from ASC backend services
209
+ { pattern: /status code 5\d\d/i, kind: "apple_5xx" },
210
+ { pattern: /internal server error/i, kind: "apple_5xx" },
211
+ { pattern: /Service Unavailable/i, kind: "apple_5xx" },
212
+ { pattern: /Bad Gateway/i, kind: "apple_5xx" },
213
+ // Network-level interruptions during the upload body transfer
214
+ { pattern: /Connection reset/i, kind: "connection_reset" },
215
+ { pattern: /SocketException/i, kind: "connection_reset" },
216
+ { pattern: /Broken pipe/i, kind: "connection_reset" },
217
+ // Timeouts — distinct from resets because the other side may have
218
+ // actually processed the request, so dedupe logic may matter.
219
+ { pattern: /Read timed out/i, kind: "network_timeout" },
220
+ { pattern: /SocketTimeoutException/i, kind: "network_timeout" },
221
+ { pattern: /Operation timed out/i, kind: "network_timeout" },
222
+ // JVM-internal errors from iTMSTransporter (OOM, thread interruption, etc.)
223
+ { pattern: /java\.lang\.OutOfMemoryError/i, kind: "transporter_jvm_error" },
224
+ { pattern: /EOFException/i, kind: "transporter_jvm_error" }
225
+ ];
226
+ function classifyUploadError(output) {
227
+ for (const { pattern, kind } of RULES) {
228
+ if (pattern.test(output)) return kind;
229
+ }
230
+ return null;
231
+ }
232
+ function isTransientUploadError(output) {
233
+ return classifyUploadError(output) !== null;
234
+ }
235
+
236
+ // src/pilot/index.ts
237
+ async function uploadToTestFlight(options) {
238
+ const backend = selectBackend(options.backend ?? "auto");
239
+ if (backend === "altool") {
240
+ return uploadWithAltool(options);
241
+ }
242
+ return uploadWithTransporter(options);
243
+ }
244
+ function selectBackend(pref) {
245
+ if (pref !== "auto") return pref;
246
+ return process.platform === "darwin" ? "altool" : "transporter";
247
+ }
76
248
  async function waitForProcessing(client, params) {
77
249
  const timeout = params.timeout ?? 18e5;
78
250
  const pollInterval = params.pollInterval ?? 3e4;
@@ -191,6 +363,6 @@ function sleep(ms) {
191
363
  return new Promise((resolve) => setTimeout(resolve, ms));
192
364
  }
193
365
 
194
- export { distributeToGroups, expireBuild, setChangelog, uploadToTestFlight, waitForProcessing };
366
+ export { classifyUploadError, distributeToGroups, expireBuild, isTransientUploadError, resolveTransporter, setChangelog, uploadToTestFlight, uploadWithAltool, uploadWithTransporter, verifyJava, waitForProcessing };
195
367
  //# sourceMappingURL=index.mjs.map
196
368
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/client/srp.ts","../../src/client/request.ts","../../src/pilot/index.ts"],"names":[],"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 } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { tmpdir } from \"node:os\";\nimport emojiRegex from \"emoji-regex\";\nimport type { AppStoreConnectClient } from \"../client/index.js\";\nimport { AppStoreConnectError } from \"../client/request.js\";\nimport type { Build } from \"../types/index.js\";\n\nexport interface PilotUploadOptions {\n ipaPath: string;\n apiKeyId: string;\n apiIssuerId: string;\n apiPrivateKey: string;\n platform?: string;\n timeout?: number;\n onOutput?: (chunk: string) => void;\n}\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\nexport interface UploadResult {\n success: boolean;\n output: string;\n}\n\n// ── Upload ─────────────────────────────────────────────────────────────────\n\nexport async function uploadToTestFlight(\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\n const keyDir = await mkdtemp(join(tmpdir(), \"fastnode-key-\"));\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 child.stdout.on(\"data\", (data: Buffer) => {\n const chunk = data.toString();\n output += chunk;\n options.onOutput?.(chunk);\n });\n\n child.stderr.on(\"data\", (data: Buffer) => {\n const chunk = data.toString();\n output += chunk;\n options.onOutput?.(chunk);\n });\n\n child.on(\"close\", async (code) => {\n clearTimeout(timer);\n await unlink(keyPath).catch(() => {});\n resolve({ success: code === 0, output });\n });\n });\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","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,3 +1,3 @@
1
- import '../index-BLubSlgK.cjs';
2
- export { A as ALL_RULES, P as PrecheckOptions, a as PrecheckRule, R as Rule, c as RuleResult, b as ValidationIssue, V as ValidationResult, v as validateMetadata } from '../index-DfCUVJX1.cjs';
1
+ import '../index-CMH_GpN9.cjs';
2
+ export { A as ALL_RULES, P as PrecheckOptions, a as PrecheckRule, R as Rule, c as RuleResult, b as ValidationIssue, V as ValidationResult, v as validateMetadata } from '../index-BW8wQUGC.cjs';
3
3
  import '../enums-B609j2rb.cjs';
@@ -1,3 +1,3 @@
1
- import '../index-D1Izm7qt.js';
2
- export { A as ALL_RULES, P as PrecheckOptions, a as PrecheckRule, R as Rule, c as RuleResult, b as ValidationIssue, V as ValidationResult, v as validateMetadata } from '../index-B9yXicrQ.js';
1
+ import '../index-D2z7RiAM.js';
2
+ export { A as ALL_RULES, P as PrecheckOptions, a as PrecheckRule, R as Rule, c as RuleResult, b as ValidationIssue, V as ValidationResult, v as validateMetadata } from '../index-CLsvyTkh.js';
3
3
  import '../enums-B609j2rb.js';
@@ -1,3 +1,3 @@
1
- import '../index-BLubSlgK.cjs';
1
+ import '../index-CMH_GpN9.cjs';
2
2
  import '../enums-B609j2rb.cjs';
3
- export { P as ProduceOptions, S as SERVICE_NAME_TO_CAPABILITY, a as addCapability, b as buildCapabilitySettings, d as createApp, e as enableServices, l as listCapabilities, r as registerBundleId, c as removeCapability } from '../index-D5eihzwM.cjs';
3
+ export { P as ProduceOptions, S as SERVICE_NAME_TO_CAPABILITY, a as addCapability, b as buildCapabilitySettings, d as createApp, e as enableServices, l as listCapabilities, r as registerBundleId, c as removeCapability } from '../index-KCB2q1-8.cjs';
@@ -1,3 +1,3 @@
1
- import '../index-D1Izm7qt.js';
1
+ import '../index-D2z7RiAM.js';
2
2
  import '../enums-B609j2rb.js';
3
- export { P as ProduceOptions, S as SERVICE_NAME_TO_CAPABILITY, a as addCapability, b as buildCapabilitySettings, d as createApp, e as enableServices, l as listCapabilities, r as registerBundleId, c as removeCapability } from '../index-B0yhu_LZ.js';
3
+ export { P as ProduceOptions, S as SERVICE_NAME_TO_CAPABILITY, a as addCapability, b as buildCapabilitySettings, d as createApp, e as enableServices, l as listCapabilities, r as registerBundleId, c as removeCapability } from '../index-BpiEBPVd.js';
@@ -1,3 +1,3 @@
1
- import '../index-BLubSlgK.cjs';
1
+ import '../index-CMH_GpN9.cjs';
2
2
  import '../enums-B609j2rb.cjs';
3
- export { a as SighOptions, S as SighResult, c as createProfile, d as downloadProfile, f as findMatchingProfile, b as installProfile } from '../index-BFdNOmPJ.cjs';
3
+ export { a as SighOptions, S as SighResult, c as createProfile, d as downloadProfile, f as findMatchingProfile, b as installProfile } from '../index-Ce-WMV7c.cjs';
@@ -1,3 +1,3 @@
1
- import '../index-D1Izm7qt.js';
1
+ import '../index-D2z7RiAM.js';
2
2
  import '../enums-B609j2rb.js';
3
- export { a as SighOptions, S as SighResult, c as createProfile, d as downloadProfile, f as findMatchingProfile, b as installProfile } from '../index-TfXqwuac.js';
3
+ export { a as SighOptions, S as SighResult, c as createProfile, d as downloadProfile, f as findMatchingProfile, b as installProfile } from '../index-CojSGJ7m.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sitepong/fastnode",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "A complete Node.js/TypeScript port of Fastlane — iOS & Android build automation, code signing, and app distribution",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -210,10 +210,12 @@
210
210
  "commander": "^13.1.0",
211
211
  "emoji-regex": "^10.3.0",
212
212
  "jose": "^6.0.11",
213
+ "node-forge": "^1.3.1",
213
214
  "plist": "^3.1.0"
214
215
  },
215
216
  "devDependencies": {
216
217
  "@types/node": "^20.10.0",
218
+ "@types/node-forge": "^1.3.11",
217
219
  "@types/plist": "^3.0.5",
218
220
  "@vitest/coverage-v8": "^1.6.1",
219
221
  "tsup": "^8.0.1",
@@ -1,32 +0,0 @@
1
- import { C as CertOptions, a as CertResult, c as createCertificate, d as downloadCertificate, f as findValidCertificates, r as revokeCertificate, b as revokeExpiredCertificates } from './cert-owkOFNW8.js';
2
-
3
- /**
4
- * Generate EC P-256 CSR — used by App Store Connect API (JWT auth).
5
- */
6
- declare function generateCSR(): Promise<{
7
- csr: string;
8
- privateKey: string;
9
- }>;
10
- /**
11
- * Generate RSA 2048 CSR — required by Apple Developer Portal (session auth).
12
- * Matches Fastlane's create_certificate_signing_request which uses RSA 2048.
13
- */
14
- declare function generateRSACSR(): {
15
- csr: string;
16
- privateKey: string;
17
- };
18
-
19
- declare const index_CertOptions: typeof CertOptions;
20
- declare const index_CertResult: typeof CertResult;
21
- declare const index_createCertificate: typeof createCertificate;
22
- declare const index_downloadCertificate: typeof downloadCertificate;
23
- declare const index_findValidCertificates: typeof findValidCertificates;
24
- declare const index_generateCSR: typeof generateCSR;
25
- declare const index_generateRSACSR: typeof generateRSACSR;
26
- declare const index_revokeCertificate: typeof revokeCertificate;
27
- declare const index_revokeExpiredCertificates: typeof revokeExpiredCertificates;
28
- declare namespace index {
29
- export { index_CertOptions as CertOptions, index_CertResult as CertResult, index_createCertificate as createCertificate, index_downloadCertificate as downloadCertificate, index_findValidCertificates as findValidCertificates, index_generateCSR as generateCSR, index_generateRSACSR as generateRSACSR, index_revokeCertificate as revokeCertificate, index_revokeExpiredCertificates as revokeExpiredCertificates };
30
- }
31
-
32
- export { generateRSACSR as a, generateCSR as g, index as i };
@@ -1,54 +0,0 @@
1
- import { A as AppStoreConnectClient, j as Build } from './index-D1Izm7qt.js';
2
-
3
- interface PilotUploadOptions {
4
- ipaPath: string;
5
- apiKeyId: string;
6
- apiIssuerId: string;
7
- apiPrivateKey: string;
8
- platform?: string;
9
- timeout?: number;
10
- onOutput?: (chunk: string) => void;
11
- }
12
- interface PilotDistributeOptions {
13
- client: AppStoreConnectClient;
14
- buildId: string;
15
- groupNames?: string[];
16
- changelog?: string;
17
- locale?: string;
18
- expirePreviousBuilds?: boolean;
19
- submitForReview?: boolean;
20
- }
21
- interface UploadResult {
22
- success: boolean;
23
- output: string;
24
- }
25
- declare function uploadToTestFlight(options: PilotUploadOptions): Promise<UploadResult>;
26
- declare function waitForProcessing(client: AppStoreConnectClient, params: {
27
- bundleId: string;
28
- buildNumber: string;
29
- appVersion: string;
30
- timeout?: number;
31
- pollInterval?: number;
32
- }): Promise<Build>;
33
- declare function distributeToGroups(options: PilotDistributeOptions): Promise<string[]>;
34
- declare function setChangelog(options: {
35
- client: AppStoreConnectClient;
36
- buildId: string;
37
- changelog: string;
38
- locale?: string;
39
- }): Promise<void>;
40
- declare function expireBuild(client: AppStoreConnectClient, buildId: string): Promise<void>;
41
-
42
- type index_PilotDistributeOptions = PilotDistributeOptions;
43
- type index_PilotUploadOptions = PilotUploadOptions;
44
- type index_UploadResult = UploadResult;
45
- declare const index_distributeToGroups: typeof distributeToGroups;
46
- declare const index_expireBuild: typeof expireBuild;
47
- declare const index_setChangelog: typeof setChangelog;
48
- declare const index_uploadToTestFlight: typeof uploadToTestFlight;
49
- declare const index_waitForProcessing: typeof waitForProcessing;
50
- declare namespace index {
51
- export { type index_PilotDistributeOptions as PilotDistributeOptions, type index_PilotUploadOptions as PilotUploadOptions, type index_UploadResult as UploadResult, index_distributeToGroups as distributeToGroups, index_expireBuild as expireBuild, index_setChangelog as setChangelog, index_uploadToTestFlight as uploadToTestFlight, index_waitForProcessing as waitForProcessing };
52
- }
53
-
54
- export { type PilotUploadOptions as P, type UploadResult as U, type PilotDistributeOptions as a, distributeToGroups as d, expireBuild as e, index as i, setChangelog as s, uploadToTestFlight as u, waitForProcessing as w };