@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/match/index.d.cts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import '../index-
|
|
2
|
-
import '../cert-
|
|
3
|
-
import '../index-
|
|
4
|
-
export { M as MatchOptions, a as MatchResult, d as decrypt, h as decryptFiles, f as decryptV1, c as decryptV2, e as encrypt, g as encryptFiles, b as encryptV2, s as syncSigningAssets } from '../index-
|
|
5
|
-
import '../enums-
|
|
1
|
+
import '../index-DrmS802s.cjs';
|
|
2
|
+
import '../cert-eOoxGMN1.cjs';
|
|
3
|
+
import '../index-DWOLZNlz.cjs';
|
|
4
|
+
export { M as MatchOptions, a as MatchResult, d as decrypt, h as decryptFiles, f as decryptV1, c as decryptV2, e as encrypt, g as encryptFiles, b as encryptV2, s as syncSigningAssets } from '../index-icIH5e5V.cjs';
|
|
5
|
+
import '../enums-yXNVh3UI.cjs';
|
|
6
|
+
import '../apple-session-jUzBrql5.cjs';
|
package/dist/match/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import '../index-
|
|
2
|
-
import '../cert-
|
|
3
|
-
import '../index-
|
|
4
|
-
export { M as MatchOptions, a as MatchResult, d as decrypt, h as decryptFiles, f as decryptV1, c as decryptV2, e as encrypt, g as encryptFiles, b as encryptV2, s as syncSigningAssets } from '../index-
|
|
5
|
-
import '../enums-
|
|
1
|
+
import '../index-lsuS2R1T.js';
|
|
2
|
+
import '../cert-RvXmdMke.js';
|
|
3
|
+
import '../index-C01Rkrir.js';
|
|
4
|
+
export { M as MatchOptions, a as MatchResult, d as decrypt, h as decryptFiles, f as decryptV1, c as decryptV2, e as encrypt, g as encryptFiles, b as encryptV2, s as syncSigningAssets } from '../index-BvyZ4uiw.js';
|
|
5
|
+
import '../enums-yXNVh3UI.js';
|
|
6
|
+
import '../apple-session-jUzBrql5.js';
|
package/dist/pem/index.d.cts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import '../index-
|
|
2
|
-
import '../enums-
|
|
3
|
-
export { a as PemOptions, b as PemResult, P as PushCertKind, d as checkExpiration, c as createPushCertificate, g as getPushCertificateType, r as renewPushCertificate } from '../index-
|
|
1
|
+
import '../index-DrmS802s.cjs';
|
|
2
|
+
import '../enums-yXNVh3UI.cjs';
|
|
3
|
+
export { a as PemOptions, b as PemResult, P as PushCertKind, d as checkExpiration, c as createPushCertificate, g as getPushCertificateType, r as renewPushCertificate } from '../index-DGkgRnZp.cjs';
|
|
4
|
+
import '../apple-session-jUzBrql5.cjs';
|
package/dist/pem/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import '../index-
|
|
2
|
-
import '../enums-
|
|
3
|
-
export { a as PemOptions, b as PemResult, P as PushCertKind, d as checkExpiration, c as createPushCertificate, g as getPushCertificateType, r as renewPushCertificate } from '../index-
|
|
1
|
+
import '../index-lsuS2R1T.js';
|
|
2
|
+
import '../enums-yXNVh3UI.js';
|
|
3
|
+
export { a as PemOptions, b as PemResult, P as PushCertKind, d as checkExpiration, c as createPushCertificate, g as getPushCertificateType, r as renewPushCertificate } from '../index-rTZOdMRE.js';
|
|
4
|
+
import '../apple-session-jUzBrql5.js';
|
package/dist/pilot/index.cjs
CHANGED
|
@@ -145,9 +145,10 @@ async function uploadWithTransporter(options) {
|
|
|
145
145
|
const transporterBin = await resolveTransporter();
|
|
146
146
|
const platform = options.platform ?? "ios";
|
|
147
147
|
const timeout = options.timeout ?? 18e5;
|
|
148
|
-
const keyDir =
|
|
148
|
+
const keyDir = path.join(os.homedir(), ".appstoreconnect", "private_keys");
|
|
149
|
+
await promises.mkdir(keyDir, { recursive: true });
|
|
149
150
|
const keyPath = path.join(keyDir, `AuthKey_${options.apiKeyId}.p8`);
|
|
150
|
-
await promises.writeFile(keyPath, options.apiPrivateKey);
|
|
151
|
+
await promises.writeFile(keyPath, options.apiPrivateKey, { mode: 384 });
|
|
151
152
|
return new Promise((resolve) => {
|
|
152
153
|
const args = [
|
|
153
154
|
"-m",
|
|
@@ -163,6 +164,9 @@ async function uploadWithTransporter(options) {
|
|
|
163
164
|
"-WONoPause",
|
|
164
165
|
"true"
|
|
165
166
|
];
|
|
167
|
+
if (options.assetDescriptionPath) {
|
|
168
|
+
args.push("-assetDescription", options.assetDescriptionPath);
|
|
169
|
+
}
|
|
166
170
|
if (platform !== "ios") {
|
|
167
171
|
args.push("-bundleType", platform);
|
|
168
172
|
}
|
|
@@ -186,13 +190,13 @@ async function uploadWithTransporter(options) {
|
|
|
186
190
|
child.stderr.on("data", onData);
|
|
187
191
|
child.on("close", async (code) => {
|
|
188
192
|
clearTimeout(timer);
|
|
189
|
-
await promises.rm(
|
|
193
|
+
await promises.rm(keyPath, { force: true }).catch(() => {
|
|
190
194
|
});
|
|
191
195
|
resolve({ success: code === 0, output, backend: "transporter" });
|
|
192
196
|
});
|
|
193
197
|
child.on("error", async (err) => {
|
|
194
198
|
clearTimeout(timer);
|
|
195
|
-
await promises.rm(
|
|
199
|
+
await promises.rm(keyPath, { force: true }).catch(() => {
|
|
196
200
|
});
|
|
197
201
|
resolve({
|
|
198
202
|
success: false,
|
package/dist/pilot/index.cjs.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","mkdtemp","join","tmpdir","writeFile","spawn","unlink","rm","emojiRegex"],"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,CAACA,aAAA,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,MAAMC,gBAAA,CAAQC,UAAKC,SAAA,EAAO,EAAG,kBAAkB,CAAC,CAAA;AAC/D,EAAA,MAAM,UAAUD,SAAA,CAAK,MAAA,EAAQ,CAAA,QAAA,EAAW,OAAA,CAAQ,QAAQ,CAAA,GAAA,CAAK,CAAA;AAC7D,EAAA,MAAME,kBAAA,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,GAAQC,mBAAA,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,MAAMC,eAAA,CAAO,OAAO,CAAA,CAAE,KAAA,CAAM,MAAM;AAAA,MAAC,CAAC,CAAA;AACpC,MAAA,MAAMC,WAAA,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,CAACP,aAAAA,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,aAAAA,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,GAAQK,mBAAAA,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,CAACL,aAAAA,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;AAGnC,EAAA,MAAM,SAAS,MAAMC,gBAAAA,CAAQC,UAAKC,SAAAA,EAAO,EAAG,gBAAgB,CAAC,CAAA;AAC7D,EAAA,MAAM,UAAUD,SAAAA,CAAK,MAAA,EAAQ,CAAA,QAAA,EAAW,OAAA,CAAQ,QAAQ,CAAA,GAAA,CAAK,CAAA;AAC7D,EAAA,MAAME,kBAAAA,CAAU,OAAA,EAAS,OAAA,CAAQ,aAAa,CAAA;AAE9C,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;AAKA,IAAA,IAAI,aAAa,KAAA,EAAO;AACtB,MAAA,IAAA,CAAK,IAAA,CAAK,eAAe,QAAQ,CAAA;AAAA,IACnC;AAEA,IAAA,MAAM,KAAA,GAAQC,mBAAAA,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,MAAME,WAAAA,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,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,WAAAA,CAAG,MAAA,EAAQ,EAAE,SAAA,EAAW,IAAA,EAAM,OAAO,IAAA,EAAM,CAAA,CAAE,KAAA,CAAM,MAAM;AAAA,MAAC,CAAC,CAAA;AACjE,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;;;ACpEA,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,CAAQC,2BAAA,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.cjs","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","mkdtemp","join","tmpdir","writeFile","spawn","unlink","rm","homedir","mkdir","emojiRegex"],"mappings":"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","file":"index.cjs","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"]}
|
package/dist/pilot/index.d.cts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import '../index-
|
|
2
|
-
export { P as PilotDistributeOptions, a as PilotUploadOptions, T as TransientUploadError, U as UploadBackend, b as UploadResult, g as classifyUploadError, d as distributeToGroups, e as expireBuild, h as isTransientUploadError, r as resolveTransporter, s as setChangelog, u as uploadToTestFlight, c as uploadWithAltool, f as uploadWithTransporter, v as verifyJava, w as waitForProcessing } from '../index-
|
|
3
|
-
import '../enums-
|
|
1
|
+
import '../index-DrmS802s.cjs';
|
|
2
|
+
export { P as PilotDistributeOptions, a as PilotUploadOptions, T as TransientUploadError, U as UploadBackend, b as UploadResult, g as classifyUploadError, d as distributeToGroups, e as expireBuild, h as isTransientUploadError, r as resolveTransporter, s as setChangelog, u as uploadToTestFlight, c as uploadWithAltool, f as uploadWithTransporter, v as verifyJava, w as waitForProcessing } from '../index-laE3_wm9.cjs';
|
|
3
|
+
import '../enums-yXNVh3UI.cjs';
|
|
4
|
+
import '../apple-session-jUzBrql5.cjs';
|
package/dist/pilot/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import '../index-
|
|
2
|
-
export { P as PilotDistributeOptions, a as PilotUploadOptions, T as TransientUploadError, U as UploadBackend, b as UploadResult, g as classifyUploadError, d as distributeToGroups, e as expireBuild, h as isTransientUploadError, r as resolveTransporter, s as setChangelog, u as uploadToTestFlight, c as uploadWithAltool, f as uploadWithTransporter, v as verifyJava, w as waitForProcessing } from '../index-
|
|
3
|
-
import '../enums-
|
|
1
|
+
import '../index-lsuS2R1T.js';
|
|
2
|
+
export { P as PilotDistributeOptions, a as PilotUploadOptions, T as TransientUploadError, U as UploadBackend, b as UploadResult, g as classifyUploadError, d as distributeToGroups, e as expireBuild, h as isTransientUploadError, r as resolveTransporter, s as setChangelog, u as uploadToTestFlight, c as uploadWithAltool, f as uploadWithTransporter, v as verifyJava, w as waitForProcessing } from '../index-CAEiXVWd.js';
|
|
3
|
+
import '../enums-yXNVh3UI.js';
|
|
4
|
+
import '../apple-session-jUzBrql5.js';
|
package/dist/pilot/index.mjs
CHANGED
|
@@ -3,9 +3,9 @@ import 'crypto';
|
|
|
3
3
|
import 'readline';
|
|
4
4
|
import { spawn } from 'child_process';
|
|
5
5
|
import { existsSync } from 'fs';
|
|
6
|
-
import { mkdtemp, writeFile, unlink, rm } from 'fs/promises';
|
|
6
|
+
import { mkdtemp, writeFile, unlink, rm, mkdir } from 'fs/promises';
|
|
7
7
|
import { join } from 'path';
|
|
8
|
-
import { tmpdir } from 'os';
|
|
8
|
+
import { tmpdir, homedir } from 'os';
|
|
9
9
|
|
|
10
10
|
// src/pilot/index.ts
|
|
11
11
|
BigInt(
|
|
@@ -139,9 +139,10 @@ async function uploadWithTransporter(options) {
|
|
|
139
139
|
const transporterBin = await resolveTransporter();
|
|
140
140
|
const platform = options.platform ?? "ios";
|
|
141
141
|
const timeout = options.timeout ?? 18e5;
|
|
142
|
-
const keyDir =
|
|
142
|
+
const keyDir = join(homedir(), ".appstoreconnect", "private_keys");
|
|
143
|
+
await mkdir(keyDir, { recursive: true });
|
|
143
144
|
const keyPath = join(keyDir, `AuthKey_${options.apiKeyId}.p8`);
|
|
144
|
-
await writeFile(keyPath, options.apiPrivateKey);
|
|
145
|
+
await writeFile(keyPath, options.apiPrivateKey, { mode: 384 });
|
|
145
146
|
return new Promise((resolve) => {
|
|
146
147
|
const args = [
|
|
147
148
|
"-m",
|
|
@@ -157,6 +158,9 @@ async function uploadWithTransporter(options) {
|
|
|
157
158
|
"-WONoPause",
|
|
158
159
|
"true"
|
|
159
160
|
];
|
|
161
|
+
if (options.assetDescriptionPath) {
|
|
162
|
+
args.push("-assetDescription", options.assetDescriptionPath);
|
|
163
|
+
}
|
|
160
164
|
if (platform !== "ios") {
|
|
161
165
|
args.push("-bundleType", platform);
|
|
162
166
|
}
|
|
@@ -180,13 +184,13 @@ async function uploadWithTransporter(options) {
|
|
|
180
184
|
child.stderr.on("data", onData);
|
|
181
185
|
child.on("close", async (code) => {
|
|
182
186
|
clearTimeout(timer);
|
|
183
|
-
await rm(
|
|
187
|
+
await rm(keyPath, { force: true }).catch(() => {
|
|
184
188
|
});
|
|
185
189
|
resolve({ success: code === 0, output, backend: "transporter" });
|
|
186
190
|
});
|
|
187
191
|
child.on("error", async (err) => {
|
|
188
192
|
clearTimeout(timer);
|
|
189
|
-
await rm(
|
|
193
|
+
await rm(keyPath, { force: true }).catch(() => {
|
|
190
194
|
});
|
|
191
195
|
resolve({
|
|
192
196
|
success: false,
|