@sitepong/fastnode 0.2.2 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/README.md +64 -1
  2. package/dist/actions/index.cjs +928 -54
  3. package/dist/actions/index.cjs.map +1 -1
  4. package/dist/actions/index.mjs +930 -56
  5. package/dist/actions/index.mjs.map +1 -1
  6. package/dist/apple-session-jUzBrql5.d.cts +122 -0
  7. package/dist/apple-session-jUzBrql5.d.ts +122 -0
  8. package/dist/cert/index.cjs +782 -736
  9. package/dist/cert/index.cjs.map +1 -1
  10. package/dist/cert/index.d.cts +5 -4
  11. package/dist/cert/index.d.ts +5 -4
  12. package/dist/cert/index.mjs +782 -737
  13. package/dist/cert/index.mjs.map +1 -1
  14. package/dist/{cert-Ddl7FpFT.d.ts → cert-ClacRx9y.d.ts} +2 -2
  15. package/dist/{cert-FehieKeb.d.cts → cert-DffLKURp.d.cts} +2 -2
  16. package/dist/cli/index.cjs +1408 -60
  17. package/dist/cli/index.cjs.map +1 -1
  18. package/dist/cli/index.d.cts +3 -2
  19. package/dist/cli/index.d.ts +3 -2
  20. package/dist/cli/index.mjs +1410 -62
  21. package/dist/cli/index.mjs.map +1 -1
  22. package/dist/client/index.cjs +519 -31
  23. package/dist/client/index.cjs.map +1 -1
  24. package/dist/client/index.d.cts +3 -2
  25. package/dist/client/index.d.ts +3 -2
  26. package/dist/client/index.mjs +514 -32
  27. package/dist/client/index.mjs.map +1 -1
  28. package/dist/deliver/index.cjs +334 -13
  29. package/dist/deliver/index.cjs.map +1 -1
  30. package/dist/deliver/index.d.cts +4 -3
  31. package/dist/deliver/index.d.ts +4 -3
  32. package/dist/deliver/index.mjs +330 -15
  33. package/dist/deliver/index.mjs.map +1 -1
  34. package/dist/{enums-B609j2rb.d.cts → enums-yXNVh3UI.d.cts} +8 -1
  35. package/dist/{enums-B609j2rb.d.ts → enums-yXNVh3UI.d.ts} +8 -1
  36. package/dist/gradle/index.cjs +222 -0
  37. package/dist/gradle/index.cjs.map +1 -0
  38. package/dist/gradle/index.d.cts +1 -0
  39. package/dist/gradle/index.d.ts +1 -0
  40. package/dist/gradle/index.mjs +217 -0
  41. package/dist/gradle/index.mjs.map +1 -0
  42. package/dist/gym/index.d.cts +2 -2
  43. package/dist/gym/index.d.ts +2 -2
  44. package/dist/index-8djTPW8O.d.ts +104 -0
  45. package/dist/{index-Bk1Gd-qE.d.cts → index-925_Ov7X.d.cts} +2 -2
  46. package/dist/{index-DrUpRXRG.d.cts → index-B6-MJpO4.d.cts} +1 -1
  47. package/dist/index-BGhV0gS-.d.ts +65 -0
  48. package/dist/{index-OelCoOth.d.cts → index-BbVQzSV1.d.cts} +18 -4
  49. package/dist/index-BhGaVlK1.d.cts +201 -0
  50. package/dist/{index-CtPyeMLH.d.ts → index-C-EEYXSE.d.ts} +18 -4
  51. package/dist/{index-CNHf7I4v.d.cts → index-C18cijCd.d.ts} +32 -3
  52. package/dist/{index-DFni8oZt.d.ts → index-CBM5XZKC.d.ts} +2 -2
  53. package/dist/index-CXJlvmB4.d.cts +63 -0
  54. package/dist/index-CXJlvmB4.d.ts +63 -0
  55. package/dist/{index-BKUapQP2.d.cts → index-CemR3UI9.d.ts} +356 -124
  56. package/dist/{index-DI1ZHr-3.d.ts → index-CmcFPn_G.d.cts} +9 -1
  57. package/dist/{index-CCtYhPN9.d.ts → index-CpGV1f0L.d.cts} +356 -124
  58. package/dist/{index-B8FOfxOw.d.ts → index-D9YQQ3FM.d.ts} +2 -2
  59. package/dist/index-DFstwgGK.d.cts +65 -0
  60. package/dist/{index-YcKz4aRS.d.ts → index-Dkf5sH1E.d.ts} +1 -1
  61. package/dist/index-DnHQ2uT2.d.cts +104 -0
  62. package/dist/index-Dv-Mek07.d.cts +84 -0
  63. package/dist/index-Dv-Mek07.d.ts +84 -0
  64. package/dist/{index-BICa8odo.d.cts → index-N6eV5yym.d.ts} +9 -1
  65. package/dist/{index-oVyLLIEK.d.cts → index-O3EMrd5s.d.cts} +3 -3
  66. package/dist/{index-DZoPGCbE.d.ts → index-Ol0RtbQA.d.ts} +3 -3
  67. package/dist/{index-CplRWSUc.d.ts → index-_24DRp3a.d.cts} +32 -3
  68. package/dist/{index-BZWmOTWE.d.cts → index-isT08RuY.d.cts} +2 -2
  69. package/dist/{index-DImN9PWc.d.ts → index-mEEB7An6.d.ts} +1 -1
  70. package/dist/{index-D13sn8DO.d.cts → index-on573dnO.d.cts} +1 -1
  71. package/dist/index-s2PigtDM.d.ts +201 -0
  72. package/dist/index.cjs +3425 -955
  73. package/dist/index.cjs.map +1 -1
  74. package/dist/index.d.cts +237 -14
  75. package/dist/index.d.ts +237 -14
  76. package/dist/index.mjs +3417 -958
  77. package/dist/index.mjs.map +1 -1
  78. package/dist/lane/index.cjs +928 -54
  79. package/dist/lane/index.cjs.map +1 -1
  80. package/dist/lane/index.mjs +930 -56
  81. package/dist/lane/index.mjs.map +1 -1
  82. package/dist/match/index.d.cts +6 -5
  83. package/dist/match/index.d.ts +6 -5
  84. package/dist/pem/index.d.cts +4 -3
  85. package/dist/pem/index.d.ts +4 -3
  86. package/dist/pilot/index.cjs +8 -4
  87. package/dist/pilot/index.cjs.map +1 -1
  88. package/dist/pilot/index.d.cts +4 -3
  89. package/dist/pilot/index.d.ts +4 -3
  90. package/dist/pilot/index.mjs +10 -6
  91. package/dist/pilot/index.mjs.map +1 -1
  92. package/dist/playstore/index.cjs +207 -0
  93. package/dist/playstore/index.cjs.map +1 -0
  94. package/dist/playstore/index.d.cts +1 -0
  95. package/dist/playstore/index.d.ts +1 -0
  96. package/dist/playstore/index.mjs +203 -0
  97. package/dist/playstore/index.mjs.map +1 -0
  98. package/dist/precheck/index.d.cts +4 -3
  99. package/dist/precheck/index.d.ts +4 -3
  100. package/dist/privacy/index.cjs +207 -0
  101. package/dist/privacy/index.cjs.map +1 -0
  102. package/dist/privacy/index.d.cts +2 -0
  103. package/dist/privacy/index.d.ts +2 -0
  104. package/dist/privacy/index.mjs +195 -0
  105. package/dist/privacy/index.mjs.map +1 -0
  106. package/dist/produce/index.cjs +32 -0
  107. package/dist/produce/index.cjs.map +1 -1
  108. package/dist/produce/index.d.cts +4 -3
  109. package/dist/produce/index.d.ts +4 -3
  110. package/dist/produce/index.mjs +32 -1
  111. package/dist/produce/index.mjs.map +1 -1
  112. package/dist/review/index.cjs +141 -0
  113. package/dist/review/index.cjs.map +1 -0
  114. package/dist/review/index.d.cts +2 -0
  115. package/dist/review/index.d.ts +2 -0
  116. package/dist/review/index.mjs +137 -0
  117. package/dist/review/index.mjs.map +1 -0
  118. package/dist/sigh/index.d.cts +4 -3
  119. package/dist/sigh/index.d.ts +4 -3
  120. package/dist/xcode/index.cjs +1 -1
  121. package/dist/xcode/index.cjs.map +1 -1
  122. package/dist/xcode/index.mjs +1 -1
  123. package/dist/xcode/index.mjs.map +1 -1
  124. package/package.json +27 -1
  125. package/dist/index-B9QeTpQK.d.cts +0 -99
  126. package/dist/index-xykpq6Lz.d.ts +0 -99
@@ -13,6 +13,7 @@ var readline = require('readline');
13
13
  var emojiRegex = require('emoji-regex');
14
14
  var jose = require('jose');
15
15
  var commander = require('commander');
16
+ var stream = require('stream');
16
17
 
17
18
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
18
19
 
@@ -209,8 +210,8 @@ async function revokeExpiredCertificates(options) {
209
210
  return count;
210
211
  }
211
212
  async function importToKeychain(certData, keychainPath, keychainPassword) {
212
- const { tmpdir: tmpdir8 } = await import('os');
213
- const tempCertPath = path.join(tmpdir8(), `fastnode-cert-${Date.now()}.cer`);
213
+ const { tmpdir: tmpdir7 } = await import('os');
214
+ const tempCertPath = path.join(tmpdir7(), `fastnode-cert-${Date.now()}.cer`);
214
215
  await promises.writeFile(tempCertPath, certData);
215
216
  try {
216
217
  const args = [
@@ -1779,6 +1780,20 @@ var init_srp = __esm({
1779
1780
  };
1780
1781
  }
1781
1782
  });
1783
+ async function appleJson(res, what) {
1784
+ const text = await res.text();
1785
+ try {
1786
+ return JSON.parse(text);
1787
+ } catch {
1788
+ const contentType = (res.headers.get("content-type") ?? "").split(";")[0].trim();
1789
+ const title = /<title>([^<]{1,120})<\/title>/i.exec(text)?.[1]?.trim();
1790
+ const looksHtml = /^\s*<(!doctype|html)/i.test(text) || contentType === "text/html";
1791
+ const page = looksHtml ? "an HTML page" : contentType ? `a ${contentType} response` : "a non-JSON response";
1792
+ throw new Error(
1793
+ `Apple answered the ${what} with ${page} (HTTP ${res.status}${title ? `, "${title}"` : ""}) instead of JSON. Apple is refusing or rate-limiting sign-ins from this server right now; wait a few minutes and try again. If it keeps happening, sign in once at appleid.apple.com to clear any hold on the account.`
1794
+ );
1795
+ }
1796
+ }
1782
1797
  function checkPortalResponse(body, session) {
1783
1798
  if (!body || typeof body !== "object") return;
1784
1799
  if (body.resultCode === 4100) {
@@ -1855,7 +1870,7 @@ async function beginLogin(config) {
1855
1870
  }
1856
1871
  async function fetchTwoFactorChallenge(pending) {
1857
1872
  const r = await fetch(`${AUTH_BASE}`, { headers: buildAuthHeadersFromPending(pending) });
1858
- const body = await r.json();
1873
+ const body = await appleJson(r, "two-factor challenge");
1859
1874
  const trustedPhoneNumbers = (body.trustedPhoneNumbers ?? []).map((p) => ({
1860
1875
  id: p.id,
1861
1876
  numberWithDialCode: p.numberWithDialCode,
@@ -1876,7 +1891,8 @@ async function requestSmsCode(input) {
1876
1891
  body: JSON.stringify({ phoneNumber: { id: input.phoneId }, mode: input.mode ?? "sms" })
1877
1892
  });
1878
1893
  }
1879
- async function submitTwoFactorCode(input) {
1894
+ async function submitTwoFactorCode(rawInput) {
1895
+ const input = { ...rawInput, code: rawInput.code.replace(/[\s\u00a0]+/g, "") };
1880
1896
  const log = input.onLog ?? (() => {
1881
1897
  });
1882
1898
  let path;
@@ -1988,9 +2004,12 @@ async function doSirp(user, password, serviceKey, cookieStr, hashcash) {
1988
2004
  body: JSON.stringify({ a: aBase64, accountName: user, protocols: ["s2k", "s2k_fo"] }),
1989
2005
  redirect: "manual"
1990
2006
  });
1991
- const initBody = await initRes.json();
2007
+ const initBody = await appleJson(initRes, "sign-in init");
1992
2008
  if (initBody.serviceErrors) throw new Error(`Apple auth error: ${JSON.stringify(initBody.serviceErrors)}`);
1993
2009
  const { iteration: iterations, salt: saltB64, b: bB64, c, protocol } = initBody;
2010
+ if (!iterations || !saltB64 || !bB64 || !c || !protocol) {
2011
+ throw new Error(`Apple's sign-in init answered without a challenge (HTTP ${initRes.status}): ${JSON.stringify(initBody).slice(0, 300)}`);
2012
+ }
1994
2013
  const salt = Buffer.from(saltB64, "base64");
1995
2014
  const b = Buffer.from(bB64, "base64");
1996
2015
  const encryptedPassword = applePbkdf2(password, salt, iterations, 32, protocol);
@@ -2033,7 +2052,7 @@ async function fetchOlympusSession(session) {
2033
2052
  const cookieStr = Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join("; ");
2034
2053
  const res = await fetch(OLYMPUS_SESSION, { headers: { Cookie: cookieStr, Accept: "application/json" } });
2035
2054
  if (res.ok) {
2036
- const body = await res.json();
2055
+ const body = await appleJson(res, "session lookup");
2037
2056
  if (body?.user?.emailAddress) session.userEmail = body.user.emailAddress;
2038
2057
  if (body?.provider?.providerId) session.teamId = String(body.provider.providerId);
2039
2058
  }
@@ -2045,7 +2064,7 @@ async function fetchContractMessages(session) {
2045
2064
  headers: { Cookie: cookieStr, Accept: "application/json" }
2046
2065
  });
2047
2066
  if (!res.ok) return [];
2048
- const body = await res.json();
2067
+ const body = await appleJson(res, "agreements lookup");
2049
2068
  return (body ?? []).map((m) => ({
2050
2069
  id: m.id ?? "",
2051
2070
  group: m.group ?? "",
@@ -2056,10 +2075,22 @@ async function fetchContractMessages(session) {
2056
2075
  return [];
2057
2076
  }
2058
2077
  }
2059
- async function fetchServiceKey() {
2060
- const res = await fetch(WIDGET_KEY_URL);
2061
- const body = await res.json();
2062
- if (!body?.authServiceKey) throw new Error("Could not fetch Apple service key");
2078
+ function serviceKeyFromLocation(location) {
2079
+ if (!location) return null;
2080
+ try {
2081
+ const key = new URL(location).searchParams.get("widgetKey")?.trim();
2082
+ return key && /^[0-9a-f]{16,}$/i.test(key) ? key : null;
2083
+ } catch {
2084
+ return null;
2085
+ }
2086
+ }
2087
+ async function fetchServiceKey(fetchImpl = fetch) {
2088
+ const head = await fetchImpl(LOGOUT_URL, { method: "HEAD", redirect: "manual" });
2089
+ const fromRedirect = serviceKeyFromLocation(head.headers.get("location"));
2090
+ if (fromRedirect) return fromRedirect;
2091
+ const res = await fetchImpl(WIDGET_KEY_URL);
2092
+ const body = await appleJson(res, "service key request");
2093
+ if (!body?.authServiceKey) throw new Error("Could not fetch Apple service key: the sign-out redirect carried no widgetKey and the config endpoint had no authServiceKey.");
2063
2094
  return body.authServiceKey;
2064
2095
  }
2065
2096
  function buildAuthHeaders(session) {
@@ -2107,7 +2138,7 @@ async function defaultPrompt(message) {
2107
2138
  });
2108
2139
  });
2109
2140
  }
2110
- var AUTH_BASE, OLYMPUS_SESSION, WIDGET_KEY_URL, ProgramLicenseAgreementError;
2141
+ var AUTH_BASE, OLYMPUS_SESSION, WIDGET_KEY_URL, ProgramLicenseAgreementError, LOGOUT_URL;
2111
2142
  var init_apple_session = __esm({
2112
2143
  "src/client/apple-session.ts"() {
2113
2144
  init_srp();
@@ -2122,6 +2153,7 @@ var init_apple_session = __esm({
2122
2153
  }
2123
2154
  agreements;
2124
2155
  };
2156
+ LOGOUT_URL = "https://appstoreconnect.apple.com/logout";
2125
2157
  }
2126
2158
  });
2127
2159
 
@@ -2438,9 +2470,10 @@ async function uploadWithTransporter(options) {
2438
2470
  const transporterBin = await resolveTransporter();
2439
2471
  const platform = options.platform ?? "ios";
2440
2472
  const timeout = options.timeout ?? 18e5;
2441
- const keyDir = await promises.mkdtemp(path.join(os.tmpdir(), "fastnode-itms-"));
2473
+ const keyDir = path.join(os.homedir(), ".appstoreconnect", "private_keys");
2474
+ await promises.mkdir(keyDir, { recursive: true });
2442
2475
  const keyPath = path.join(keyDir, `AuthKey_${options.apiKeyId}.p8`);
2443
- await promises.writeFile(keyPath, options.apiPrivateKey);
2476
+ await promises.writeFile(keyPath, options.apiPrivateKey, { mode: 384 });
2444
2477
  return new Promise((resolve4) => {
2445
2478
  const args = [
2446
2479
  "-m",
@@ -2456,6 +2489,9 @@ async function uploadWithTransporter(options) {
2456
2489
  "-WONoPause",
2457
2490
  "true"
2458
2491
  ];
2492
+ if (options.assetDescriptionPath) {
2493
+ args.push("-assetDescription", options.assetDescriptionPath);
2494
+ }
2459
2495
  if (platform !== "ios") {
2460
2496
  args.push("-bundleType", platform);
2461
2497
  }
@@ -2479,13 +2515,13 @@ async function uploadWithTransporter(options) {
2479
2515
  child.stderr.on("data", onData);
2480
2516
  child.on("close", async (code) => {
2481
2517
  clearTimeout(timer);
2482
- await promises.rm(keyDir, { recursive: true, force: true }).catch(() => {
2518
+ await promises.rm(keyPath, { force: true }).catch(() => {
2483
2519
  });
2484
2520
  resolve4({ success: code === 0, output, backend: "transporter" });
2485
2521
  });
2486
2522
  child.on("error", async (err) => {
2487
2523
  clearTimeout(timer);
2488
- await promises.rm(keyDir, { recursive: true, force: true }).catch(() => {
2524
+ await promises.rm(keyPath, { force: true }).catch(() => {
2489
2525
  });
2490
2526
  resolve4({
2491
2527
  success: false,
@@ -2777,6 +2813,311 @@ var init_metadata = __esm({
2777
2813
  };
2778
2814
  }
2779
2815
  });
2816
+ function categoryFor(placementType) {
2817
+ return placementType === "APP_SCREENSHOT" || placementType === "IMESSAGE_APP_SCREENSHOT" || placementType === "APP_PREVIEW" ? "APP_SCREENSHOTS_AND_PREVIEWS" : "CREATIVE_ASSETS";
2818
+ }
2819
+ function readImageSize(data) {
2820
+ if (data.length >= 26 && data.readUInt32BE(0) === 2303741511) {
2821
+ const colorType = data[25];
2822
+ return {
2823
+ width: data.readUInt32BE(16),
2824
+ height: data.readUInt32BE(20),
2825
+ alpha: colorType === 4 || colorType === 6
2826
+ };
2827
+ }
2828
+ if (data.length >= 4 && data[0] === 255 && data[1] === 216) {
2829
+ let offset = 2;
2830
+ while (offset + 9 < data.length) {
2831
+ if (data[offset] !== 255) return null;
2832
+ const marker = data[offset + 1];
2833
+ const length = data.readUInt16BE(offset + 2);
2834
+ const isSof = marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204;
2835
+ if (isSof) {
2836
+ return { height: data.readUInt16BE(offset + 5), width: data.readUInt16BE(offset + 7), alpha: false };
2837
+ }
2838
+ offset += 2 + length;
2839
+ }
2840
+ }
2841
+ return null;
2842
+ }
2843
+ function fits(spec, width, height) {
2844
+ const d = spec.dimensions;
2845
+ return width >= d.minWidth && width <= d.maxWidth && height >= d.minHeight && height <= d.maxHeight;
2846
+ }
2847
+ function groupsForFeature(refData, feature, placementType) {
2848
+ const mappings = refData.placementTypes?.find((p) => p.placementTypeId === placementType)?.specMappings ?? [];
2849
+ const all = mappings.map((m) => m.placementGroupId);
2850
+ const policy = refData.features?.find((f) => f.featureId === feature)?.placementPolicies?.find((p) => p.placementType === placementType);
2851
+ const limited = policy?.groupLimits?.flatMap((l) => l.groupIds);
2852
+ return limited?.length ? all.filter((g) => limited.includes(g)) : all;
2853
+ }
2854
+ function maxCountFor(refData, feature, placementType, placementGroup) {
2855
+ return refData.features?.find((f) => f.featureId === feature)?.placementPolicies?.find((p) => p.placementType === placementType)?.groupLimits?.find((l) => l.groupIds.includes(placementGroup))?.maxCount;
2856
+ }
2857
+ function resolveImageGroups(refData, feature, placementType, size) {
2858
+ const specs = (refData.imageSpecs ?? []).filter(
2859
+ (s) => s.compatiblePlacementTypes.includes(placementType) && fits(s, size.width, size.height)
2860
+ );
2861
+ const specIds = new Set(specs.map((s) => s.specId));
2862
+ const allowed = new Set(groupsForFeature(refData, feature, placementType));
2863
+ const mappings = refData.placementTypes?.find((p) => p.placementTypeId === placementType)?.specMappings ?? [];
2864
+ const groups = mappings.filter((m) => allowed.has(m.placementGroupId) && m.specs.some((id) => specIds.has(id))).map((m) => m.placementGroupId);
2865
+ return { groups: [...new Set(groups)], spec: specs[0] };
2866
+ }
2867
+ async function runUploadOperations(ops, data) {
2868
+ for (const op of ops) {
2869
+ const headers = {};
2870
+ for (const h of op.requestHeaders) headers[h.name] = h.value;
2871
+ const res = await fetch(op.url, {
2872
+ method: op.method,
2873
+ headers,
2874
+ body: data.subarray(op.offset, op.offset + op.length)
2875
+ });
2876
+ if (!res.ok) {
2877
+ throw new Error(`Upload operation failed: ${op.method} ${new URL(op.url).host} \u2192 HTTP ${res.status}`);
2878
+ }
2879
+ }
2880
+ }
2881
+ async function uploadLibraryAsset(options) {
2882
+ const { client, libraryId, filePath, category } = options;
2883
+ const data = await promises.readFile(filePath);
2884
+ const fileName = path.basename(filePath);
2885
+ const kind = VIDEO_EXTENSIONS.includes(path.extname(filePath).toLowerCase()) ? "video" : "image";
2886
+ const hash = crypto.createHash("sha256").update(data).digest("hex").slice(0, 12);
2887
+ const referenceName = `${options.referenceName ?? fileName} #${hash}`;
2888
+ const media = kind === "image" ? client.appAssetLibraryImages : client.appAssetLibraryVideos;
2889
+ const filter = { filter: { referenceName } };
2890
+ const existing = kind === "image" ? await client.appAssetLibraries.listImages(libraryId, filter) : await client.appAssetLibraries.listVideos(libraryId, filter);
2891
+ const reusable = existing.find(
2892
+ (a) => a.attributes.category === category && a.attributes.state && READY_STATES.includes(a.attributes.state)
2893
+ );
2894
+ if (reusable) {
2895
+ return { kind, id: reusable.id, state: reusable.attributes.state, reused: true };
2896
+ }
2897
+ const reserved = kind === "image" ? await client.appAssetLibraryImages.create({ libraryId, fileName, fileSize: data.length, category, referenceName }) : await client.appAssetLibraryVideos.create({
2898
+ libraryId,
2899
+ fileName,
2900
+ fileSize: data.length,
2901
+ category,
2902
+ referenceName,
2903
+ previewFrameTimeCode: options.previewFrameTimeCode
2904
+ });
2905
+ await runUploadOperations(reserved.attributes.uploadOperations ?? [], data);
2906
+ await media.update(reserved.id, { uploaded: true });
2907
+ options.log?.(`uploaded ${fileName} (${kind}, ${data.length} bytes)`);
2908
+ const state = await waitForAssetReady(
2909
+ () => media.get(reserved.id),
2910
+ options.pollIntervalMs ?? 3e3,
2911
+ options.timeoutMs ?? (kind === "video" ? 30 * 6e4 : 5 * 6e4)
2912
+ );
2913
+ return { kind, id: reserved.id, state, reused: false };
2914
+ }
2915
+ async function waitForAssetReady(get, intervalMs, timeoutMs) {
2916
+ const deadline = Date.now() + timeoutMs;
2917
+ for (; ; ) {
2918
+ const asset = await get();
2919
+ const state = asset.attributes.state;
2920
+ if (state === "FAILED") {
2921
+ const why = (asset.attributes.stateDetails ?? []).map((d) => d.message ?? d.code).filter(Boolean).join("; ");
2922
+ throw new Error(`Asset processing failed${why ? `: ${why}` : ""}`);
2923
+ }
2924
+ if (state && READY_STATES.includes(state)) return state;
2925
+ if (Date.now() > deadline) throw new Error(`Asset still ${state ?? "unknown"} after ${timeoutMs / 1e3}s`);
2926
+ await new Promise((r) => setTimeout(r, intervalMs));
2927
+ }
2928
+ }
2929
+ async function listFiles(dir) {
2930
+ return (await promises.readdir(dir)).filter((f) => !f.startsWith(".")).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }));
2931
+ }
2932
+ async function isDir(path) {
2933
+ return (await promises.stat(path)).isDirectory();
2934
+ }
2935
+ async function planLocale(localeDir, refData, feature, folders, warn) {
2936
+ const planned = [];
2937
+ for (const [folder, placementType] of Object.entries(folders)) {
2938
+ const typeDir = path.join(localeDir, folder);
2939
+ if (!fs.existsSync(typeDir)) continue;
2940
+ const known = new Set(groupsForFeature(refData, feature, placementType));
2941
+ const entries = [];
2942
+ for (const entry of await listFiles(typeDir)) {
2943
+ const full = path.join(typeDir, entry);
2944
+ if (await isDir(full)) {
2945
+ if (!known.has(entry)) {
2946
+ warn(`${path.relative(localeDir, full)}: "${entry}" is not a ${placementType} placement group for ${feature} \u2014 skipped`);
2947
+ continue;
2948
+ }
2949
+ for (const f of await listFiles(full)) entries.push({ filePath: path.join(full, f), group: entry });
2950
+ } else {
2951
+ entries.push({ filePath: full });
2952
+ }
2953
+ }
2954
+ for (const { filePath, group } of entries) {
2955
+ const ext = path.extname(filePath).toLowerCase();
2956
+ const isVideo = VIDEO_EXTENSIONS.includes(ext);
2957
+ if (!isVideo && !IMAGE_EXTENSIONS.includes(ext)) continue;
2958
+ const needsVideo = placementType === "APP_PREVIEW";
2959
+ const needsImage = placementType === "APP_SCREENSHOT" || placementType === "IMESSAGE_APP_SCREENSHOT";
2960
+ if (needsVideo && !isVideo || needsImage && isVideo) {
2961
+ warn(`${path.relative(localeDir, filePath)}: ${isVideo ? "video" : "image"} cannot be a ${placementType} \u2014 skipped`);
2962
+ continue;
2963
+ }
2964
+ if (group) {
2965
+ planned.push({ filePath, placementType, groups: [group] });
2966
+ continue;
2967
+ }
2968
+ if (isVideo) {
2969
+ if (known.size === 1) planned.push({ filePath, placementType, groups: [...known] });
2970
+ else warn(`${path.relative(localeDir, filePath)}: put videos in a placement-group folder (one of ${[...known].join(", ")}) \u2014 skipped`);
2971
+ continue;
2972
+ }
2973
+ const size = readImageSize(await promises.readFile(filePath));
2974
+ if (!size) {
2975
+ warn(`${path.relative(localeDir, filePath)}: could not read image size \u2014 skipped`);
2976
+ continue;
2977
+ }
2978
+ const { groups, spec } = resolveImageGroups(refData, feature, placementType, size);
2979
+ if (!groups.length) {
2980
+ warn(`${path.relative(localeDir, filePath)}: ${size.width}\xD7${size.height} matches no ${placementType} spec \u2014 skipped`);
2981
+ continue;
2982
+ }
2983
+ if (size.alpha && spec && spec.alphaAllowed === false) {
2984
+ warn(`${path.relative(localeDir, filePath)}: has an alpha channel, which Apple rejects for this spec`);
2985
+ }
2986
+ planned.push({ filePath, placementType, groups: groups.slice(0, 1) });
2987
+ }
2988
+ }
2989
+ return planned;
2990
+ }
2991
+ async function uploadAssetLibrary(options) {
2992
+ const { client } = options;
2993
+ const log = options.log ?? (() => {
2994
+ });
2995
+ const feature = "APP_STORE_VERSIONS";
2996
+ const result = { uploaded: 0, reused: 0, placed: 0, removed: 0, warnings: [] };
2997
+ const warn = (line) => {
2998
+ result.warnings.push(line);
2999
+ log(`warning: ${line}`);
3000
+ };
3001
+ const [library, refData, localizations] = await Promise.all([
3002
+ client.appAssetLibraries.getForApp(options.appId),
3003
+ client.appAssetLibraries.refData({ features: [feature] }),
3004
+ client.appStoreVersions.getLocalizations(options.versionId)
3005
+ ]);
3006
+ const folders = { ...options.folders ?? ASSET_FOLDERS };
3007
+ if (options.rootPlacementType) folders["."] = options.rootPlacementType;
3008
+ const uploads = /* @__PURE__ */ new Map();
3009
+ const uploadOnce = (filePath, placementType) => {
3010
+ const key = `${filePath}|${categoryFor(placementType)}`;
3011
+ if (!uploads.has(key)) {
3012
+ uploads.set(
3013
+ key,
3014
+ uploadLibraryAsset({
3015
+ client,
3016
+ libraryId: library.id,
3017
+ filePath,
3018
+ category: categoryFor(placementType),
3019
+ referenceName: path.relative(options.assetsPath, filePath),
3020
+ log
3021
+ }).then((a) => {
3022
+ if (a.reused) result.reused++;
3023
+ else result.uploaded++;
3024
+ return a;
3025
+ })
3026
+ );
3027
+ }
3028
+ return uploads.get(key);
3029
+ };
3030
+ for (const loc of localizations) {
3031
+ const localeDir = path.join(options.assetsPath, loc.attributes.locale);
3032
+ if (!fs.existsSync(localeDir)) continue;
3033
+ const target = { kind: "appStoreVersionLocalization", id: loc.id };
3034
+ const plan = await planLocale(localeDir, refData, feature, folders, warn);
3035
+ const slots = /* @__PURE__ */ new Map();
3036
+ for (const p of plan) {
3037
+ for (const group of p.groups) {
3038
+ const key = `${p.placementType}|${group}`;
3039
+ if (!slots.has(key)) slots.set(key, { placementType: p.placementType, group, files: [] });
3040
+ slots.get(key).files.push(p.filePath);
3041
+ }
3042
+ }
3043
+ for (const slot of slots.values()) {
3044
+ const where = `${loc.attributes.locale} ${slot.placementType} ${slot.group}`;
3045
+ const max = maxCountFor(refData, feature, slot.placementType, slot.group);
3046
+ if (max !== void 0 && slot.files.length > max) {
3047
+ warn(`${where}: ${slot.files.length} files but Apple allows ${max} \u2014 keeping the first ${max}`);
3048
+ slot.files = slot.files.slice(0, max);
3049
+ }
3050
+ const existing = await client.appAssetLibraryPlacements.listForTarget(target, {
3051
+ placementType: slot.placementType,
3052
+ placementGroup: slot.group,
3053
+ include: "image,video"
3054
+ });
3055
+ if (options.overwrite) {
3056
+ for (const p of existing) {
3057
+ await client.appAssetLibraryPlacements.delete(p.id);
3058
+ result.removed++;
3059
+ }
3060
+ }
3061
+ const kept = options.overwrite ? [] : existing;
3062
+ const keptAssetIds = new Set(kept.map((p) => placementAssetId(p)).filter(Boolean));
3063
+ const ours = [];
3064
+ for (const filePath of slot.files) {
3065
+ const asset = await uploadOnce(filePath, slot.placementType);
3066
+ if (keptAssetIds.has(asset.id)) continue;
3067
+ const placement = await client.appAssetLibraryPlacements.create({
3068
+ placementType: slot.placementType,
3069
+ placementGroup: slot.group,
3070
+ ...asset.kind === "image" ? { imageId: asset.id } : { videoId: asset.id },
3071
+ target
3072
+ });
3073
+ ours.push(placement.id);
3074
+ result.placed++;
3075
+ }
3076
+ log(`${where}: placed ${ours.length}${kept.length ? `, kept ${kept.length}` : ""}`);
3077
+ const orderable = slot.placementType === "APP_SCREENSHOT" || slot.placementType === "APP_PREVIEW" || slot.placementType === "IMESSAGE_APP_SCREENSHOT";
3078
+ if (orderable && ours.length + kept.length > 1) {
3079
+ await client.appAssetLibraryPlacements.order({
3080
+ placementGroup: slot.group,
3081
+ placementIds: [...ours, ...kept.map((p) => p.id)],
3082
+ target
3083
+ });
3084
+ }
3085
+ }
3086
+ }
3087
+ return result;
3088
+ }
3089
+ function placementAssetId(p) {
3090
+ for (const rel of ["image", "video"]) {
3091
+ const data = p.relationships?.[rel]?.data;
3092
+ if (data?.id) return data.id;
3093
+ }
3094
+ return void 0;
3095
+ }
3096
+ var ASSET_FOLDERS, IMAGE_EXTENSIONS, VIDEO_EXTENSIONS, READY_STATES;
3097
+ var init_assets = __esm({
3098
+ "src/deliver/assets.ts"() {
3099
+ ASSET_FOLDERS = {
3100
+ screenshots: "APP_SCREENSHOT",
3101
+ imessage_screenshots: "IMESSAGE_APP_SCREENSHOT",
3102
+ previews: "APP_PREVIEW",
3103
+ header: "PRODUCT_PAGE_HEADER_ASSET",
3104
+ search_results: "APP_STORE_SEARCH_RESULTS_ASSET",
3105
+ event_card: "EVENT_CARD_ASSET",
3106
+ event_details: "EVENT_DETAILS_PAGE_ASSET"
3107
+ };
3108
+ IMAGE_EXTENSIONS = [".png", ".jpg", ".jpeg"];
3109
+ VIDEO_EXTENSIONS = [".mp4", ".mov", ".m4v"];
3110
+ READY_STATES = [
3111
+ "COMPLETE",
3112
+ "PREPARE_FOR_SUBMISSION",
3113
+ "READY_FOR_REVIEW",
3114
+ "WAITING_FOR_REVIEW",
3115
+ "IN_REVIEW",
3116
+ "ACCEPTED",
3117
+ "APPROVED"
3118
+ ];
3119
+ }
3120
+ });
2780
3121
 
2781
3122
  // src/xcode/upload.ts
2782
3123
  var upload_exports = {};
@@ -2842,8 +3183,14 @@ var init_upload = __esm({
2842
3183
  // src/deliver/index.ts
2843
3184
  var deliver_exports = {};
2844
3185
  __export(deliver_exports, {
3186
+ ASSET_FOLDERS: () => ASSET_FOLDERS,
3187
+ categoryFor: () => categoryFor,
2845
3188
  loadMetadata: () => loadMetadata,
3189
+ readImageSize: () => readImageSize,
3190
+ resolveImageGroups: () => resolveImageGroups,
2846
3191
  submitForReview: () => submitForReview,
3192
+ uploadAssetLibrary: () => uploadAssetLibrary,
3193
+ uploadLibraryAsset: () => uploadLibraryAsset,
2847
3194
  uploadMetadata: () => uploadMetadata,
2848
3195
  uploadScreenshots: () => uploadScreenshots,
2849
3196
  uploadToAppStore: () => uploadToAppStore
@@ -2882,11 +3229,36 @@ async function uploadToAppStore(options) {
2882
3229
  copyright: options.copyright
2883
3230
  });
2884
3231
  }
3232
+ const log = (line) => options.onOutput?.(`${line}
3233
+ `);
2885
3234
  if (!options.skipScreenshots && options.screenshotsPath && fs.existsSync(options.screenshotsPath)) {
2886
- await uploadScreenshots({
3235
+ if (options.useAssetLibrary) {
3236
+ await uploadAssetLibrary({
3237
+ client: options.client,
3238
+ appId: app.id,
3239
+ versionId: version.id,
3240
+ assetsPath: options.screenshotsPath,
3241
+ folders: {},
3242
+ rootPlacementType: "APP_SCREENSHOT",
3243
+ overwrite: options.overwriteAssets,
3244
+ log
3245
+ });
3246
+ } else {
3247
+ await uploadScreenshots({
3248
+ client: options.client,
3249
+ versionId: version.id,
3250
+ screenshotsPath: options.screenshotsPath
3251
+ });
3252
+ }
3253
+ }
3254
+ if (!options.skipAssets && options.assetsPath && fs.existsSync(options.assetsPath)) {
3255
+ await uploadAssetLibrary({
2887
3256
  client: options.client,
3257
+ appId: app.id,
2888
3258
  versionId: version.id,
2889
- screenshotsPath: options.screenshotsPath
3259
+ assetsPath: options.assetsPath,
3260
+ overwrite: options.overwriteAssets,
3261
+ log
2890
3262
  });
2891
3263
  }
2892
3264
  if (!options.skipBinaryUpload) {
@@ -2971,17 +3343,7 @@ async function uploadScreenshots(options) {
2971
3343
  fileName: file,
2972
3344
  fileSize: fileData.length
2973
3345
  });
2974
- const ops = reserved.attributes.uploadOperations;
2975
- if (ops) {
2976
- for (const op of ops) {
2977
- const chunk = fileData.subarray(op.offset, op.offset + op.length);
2978
- const headers = {};
2979
- for (const h of op.requestHeaders) {
2980
- headers[h.name] = h.value;
2981
- }
2982
- await fetch(op.url, { method: op.method, headers, body: chunk });
2983
- }
2984
- }
3346
+ await runUploadOperations(reserved.attributes.uploadOperations ?? [], fileData);
2985
3347
  await options.client.appScreenshots.commitScreenshot(reserved.id, {
2986
3348
  sourceFileChecksum: checksum,
2987
3349
  uploaded: true
@@ -3001,7 +3363,9 @@ function detectDisplayType(filename) {
3001
3363
  var init_deliver = __esm({
3002
3364
  "src/deliver/index.ts"() {
3003
3365
  init_metadata();
3366
+ init_assets();
3004
3367
  init_metadata();
3368
+ init_assets();
3005
3369
  }
3006
3370
  });
3007
3371
 
@@ -3010,6 +3374,7 @@ var produce_exports = {};
3010
3374
  __export(produce_exports, {
3011
3375
  SERVICE_NAME_TO_CAPABILITY: () => SERVICE_NAME_TO_CAPABILITY,
3012
3376
  addCapability: () => addCapability,
3377
+ associateAppGroups: () => associateAppGroups,
3013
3378
  buildCapabilitySettings: () => buildCapabilitySettings,
3014
3379
  createApp: () => createApp,
3015
3380
  enableServices: () => enableServices,
@@ -3104,6 +3469,30 @@ async function enableServices(options) {
3104
3469
  });
3105
3470
  }
3106
3471
  }
3472
+ async function associateAppGroups(options) {
3473
+ if (!options.appGroups?.length) return;
3474
+ await options.portalClient.enableAppGroups(options.bundleIdId).catch(() => {
3475
+ });
3476
+ const existing = await options.portalClient.listAppGroups();
3477
+ const appGroupIds = [];
3478
+ for (const group of options.appGroups) {
3479
+ let record = existing.find((ag) => ag.identifier === group.identifier);
3480
+ if (!record) {
3481
+ record = await options.portalClient.createAppGroup({
3482
+ name: group.name,
3483
+ identifier: group.identifier
3484
+ });
3485
+ }
3486
+ const id = record?.applicationGroup ?? record?.applicationGroupId ?? record?.appGroupId ?? record?.id;
3487
+ if (id) appGroupIds.push(id);
3488
+ }
3489
+ if (appGroupIds.length) {
3490
+ await options.portalClient.assignAppGroups({
3491
+ appIdId: options.bundleIdId,
3492
+ appGroupIds
3493
+ });
3494
+ }
3495
+ }
3107
3496
  async function createApp(options) {
3108
3497
  const existing = await options.client.apps.findByBundleId(options.bundleIdentifier);
3109
3498
  if (existing) return existing;
@@ -3122,6 +3511,13 @@ async function createApp(options) {
3122
3511
  });
3123
3512
  }
3124
3513
  }
3514
+ if (options.appGroups?.length && options.portalClient) {
3515
+ await associateAppGroups({
3516
+ portalClient: options.portalClient,
3517
+ bundleIdId: bundleId.id,
3518
+ appGroups: options.appGroups
3519
+ });
3520
+ }
3125
3521
  return options.client.apps.create({
3126
3522
  bundleIdentifier: options.bundleIdentifier,
3127
3523
  bundleIdId: bundleId.id,
@@ -3931,10 +4327,10 @@ async function parseTestResults(options) {
3931
4327
  } else if (inputPath.endsWith(".plist")) {
3932
4328
  testSuites = parsePlistResults(inputPath);
3933
4329
  } else {
3934
- const stat6 = await import('fs').then(
4330
+ const stat7 = await import('fs').then(
3935
4331
  (fs) => fs.statSync(inputPath)
3936
4332
  );
3937
- if (stat6.isDirectory()) {
4333
+ if (stat7.isDirectory()) {
3938
4334
  requireMacOS("xcresult parsing");
3939
4335
  testSuites = await parseXCResult(inputPath);
3940
4336
  } else {
@@ -6111,9 +6507,22 @@ var init_pre_release_versions = __esm({
6111
6507
  });
6112
6508
 
6113
6509
  // src/resources/review-submission-items.ts
6114
- var ReviewSubmissionItemsResource;
6510
+ var ITEM_RELATIONSHIPS, ReviewSubmissionItemsResource;
6115
6511
  var init_review_submission_items = __esm({
6116
6512
  "src/resources/review-submission-items.ts"() {
6513
+ ITEM_RELATIONSHIPS = {
6514
+ appStoreVersionId: ["appStoreVersion", "appStoreVersions"],
6515
+ appCustomProductPageVersionId: ["appCustomProductPageVersion", "appCustomProductPageVersions"],
6516
+ appStoreVersionExperimentId: ["appStoreVersionExperiment", "appStoreVersionExperiments"],
6517
+ appStoreVersionExperimentV2Id: ["appStoreVersionExperimentV2", "appStoreVersionExperiments"],
6518
+ appEventId: ["appEvent", "appEvents"],
6519
+ appAssetLibraryImageId: ["appAssetLibraryImage", "appAssetLibraryImages"],
6520
+ appAssetLibraryVideoId: ["appAssetLibraryVideo", "appAssetLibraryVideos"],
6521
+ backgroundAssetVersionId: ["backgroundAssetVersion", "backgroundAssetVersions"],
6522
+ inAppPurchaseVersionId: ["inAppPurchaseVersion", "inAppPurchaseVersions"],
6523
+ subscriptionVersionId: ["subscriptionVersion", "subscriptionVersions"],
6524
+ subscriptionGroupVersionId: ["subscriptionGroupVersion", "subscriptionGroupVersions"]
6525
+ };
6117
6526
  ReviewSubmissionItemsResource = class {
6118
6527
  constructor(http) {
6119
6528
  this.http = http;
@@ -6135,32 +6544,20 @@ var init_review_submission_items = __esm({
6135
6544
  );
6136
6545
  return response.data;
6137
6546
  }
6547
+ /**
6548
+ * Add one item to a review submission. Besides versions, asset library
6549
+ * images and videos can be submitted on their own (ASC API 4.5.1) so
6550
+ * creative assets are approved ahead of a campaign.
6551
+ */
6138
6552
  async create(params) {
6139
6553
  const relationships = {
6140
6554
  reviewSubmission: {
6141
6555
  data: { type: "reviewSubmissions", id: params.reviewSubmissionId }
6142
6556
  }
6143
6557
  };
6144
- if (params.appStoreVersionId) {
6145
- relationships.appStoreVersion = {
6146
- data: { type: "appStoreVersions", id: params.appStoreVersionId }
6147
- };
6148
- }
6149
- if (params.appCustomProductPageVersionId) {
6150
- relationships.appCustomProductPageVersion = {
6151
- data: {
6152
- type: "appCustomProductPageVersions",
6153
- id: params.appCustomProductPageVersionId
6154
- }
6155
- };
6156
- }
6157
- if (params.appStoreVersionExperimentId) {
6158
- relationships.appStoreVersionExperiment = {
6159
- data: {
6160
- type: "appStoreVersionExperiments",
6161
- id: params.appStoreVersionExperimentId
6162
- }
6163
- };
6558
+ for (const [param, [relationship, type]] of Object.entries(ITEM_RELATIONSHIPS)) {
6559
+ const id = params[param];
6560
+ if (id) relationships[relationship] = { data: { type, id } };
6164
6561
  }
6165
6562
  const response = await this.http.post(
6166
6563
  "/v1/reviewSubmissionItems",
@@ -6391,6 +6788,288 @@ var init_xcode_metrics = __esm({
6391
6788
  }
6392
6789
  });
6393
6790
 
6791
+ // src/resources/app-asset-libraries.ts
6792
+ function mergeRefData(parts) {
6793
+ const merged = {
6794
+ features: [],
6795
+ placementProfileGroups: [],
6796
+ imageSpecs: [],
6797
+ videoSpecs: [],
6798
+ placementTypes: [],
6799
+ displayClasses: []
6800
+ };
6801
+ for (const part of parts) {
6802
+ for (const key of Object.keys(merged)) {
6803
+ merged[key].push(...part[key] ?? []);
6804
+ }
6805
+ }
6806
+ return merged;
6807
+ }
6808
+ var AppAssetLibrariesResource;
6809
+ var init_app_asset_libraries = __esm({
6810
+ "src/resources/app-asset-libraries.ts"() {
6811
+ AppAssetLibrariesResource = class {
6812
+ constructor(http) {
6813
+ this.http = http;
6814
+ }
6815
+ http;
6816
+ async getForApp(appId) {
6817
+ const response = await this.http.get(
6818
+ `/v1/apps/${appId}/assetLibrary`
6819
+ );
6820
+ return response.data;
6821
+ }
6822
+ async listImages(libraryId, params = {}) {
6823
+ return this.http.all(`/v1/appAssetLibraries/${libraryId}/images`, params);
6824
+ }
6825
+ async listVideos(libraryId, params = {}) {
6826
+ return this.http.all(`/v1/appAssetLibraries/${libraryId}/videos`, params);
6827
+ }
6828
+ /**
6829
+ * Specs, placement groups and limits. Apple adds placement groups (new
6830
+ * devices) without an API version bump, so read them from here rather than
6831
+ * hard-coding display types.
6832
+ */
6833
+ async refData(params = {}) {
6834
+ const filter = {};
6835
+ if (params.features?.length) filter.features = params.features;
6836
+ if (params.placementTypes?.length) filter.placementTypes = params.placementTypes;
6837
+ if (params.placementProfileGroups?.length) filter.placementProfileGroups = params.placementProfileGroups;
6838
+ const data = await this.http.all("/v1/appAssetLibraryRefData", { filter });
6839
+ return mergeRefData(data.map((d) => d.attributes));
6840
+ }
6841
+ };
6842
+ }
6843
+ });
6844
+
6845
+ // src/resources/app-asset-library-assets.ts
6846
+ var AssetLibraryMediaResource, AppAssetLibraryImagesResource, AppAssetLibraryVideosResource;
6847
+ var init_app_asset_library_assets = __esm({
6848
+ "src/resources/app-asset-library-assets.ts"() {
6849
+ AssetLibraryMediaResource = class {
6850
+ constructor(http) {
6851
+ this.http = http;
6852
+ }
6853
+ http;
6854
+ async get(id) {
6855
+ const response = await this.http.get(`/v1/${this.type}/${id}`);
6856
+ return response.data;
6857
+ }
6858
+ async reserve(params, extra = {}) {
6859
+ const { libraryId, ...attributes } = params;
6860
+ const response = await this.http.post(`/v1/${this.type}`, {
6861
+ data: {
6862
+ type: this.type,
6863
+ attributes: { ...attributes, ...extra },
6864
+ relationships: {
6865
+ assetLibrary: { data: { type: "appAssetLibraries", id: libraryId } }
6866
+ }
6867
+ }
6868
+ });
6869
+ return response.data;
6870
+ }
6871
+ async patch(id, attributes) {
6872
+ const response = await this.http.patch(`/v1/${this.type}/${id}`, {
6873
+ data: { type: this.type, id, attributes }
6874
+ });
6875
+ return response.data;
6876
+ }
6877
+ /** Fails with 409 STATE_ERROR.ASSET_HAS_PLACEMENTS while the asset is still placed. */
6878
+ async delete(id) {
6879
+ await this.http.delete(`/v1/${this.type}/${id}`);
6880
+ }
6881
+ async listPlacements(id, params = {}) {
6882
+ return this.http.all(`/v1/${this.type}/${id}/placements`, params);
6883
+ }
6884
+ };
6885
+ AppAssetLibraryImagesResource = class extends AssetLibraryMediaResource {
6886
+ type = "appAssetLibraryImages";
6887
+ async create(params) {
6888
+ return this.reserve(params);
6889
+ }
6890
+ async update(id, attributes) {
6891
+ return this.patch(id, { ...attributes });
6892
+ }
6893
+ };
6894
+ AppAssetLibraryVideosResource = class extends AssetLibraryMediaResource {
6895
+ type = "appAssetLibraryVideos";
6896
+ async create(params) {
6897
+ const { previewFrameTimeCode, ...rest } = params;
6898
+ return this.reserve(rest, previewFrameTimeCode ? { previewFrameTimeCode } : {});
6899
+ }
6900
+ async update(id, attributes) {
6901
+ return this.patch(id, { ...attributes });
6902
+ }
6903
+ };
6904
+ }
6905
+ });
6906
+
6907
+ // src/resources/app-asset-library-placements.ts
6908
+ function targetRelationship(target) {
6909
+ return { [target.kind]: { data: { type: TARGET_TYPES[target.kind], id: target.id } } };
6910
+ }
6911
+ var TARGET_TYPES, AppAssetLibraryPlacementsResource;
6912
+ var init_app_asset_library_placements = __esm({
6913
+ "src/resources/app-asset-library-placements.ts"() {
6914
+ TARGET_TYPES = {
6915
+ appStoreVersionLocalization: "appStoreVersionLocalizations",
6916
+ appCustomProductPageLocalization: "appCustomProductPageLocalizations",
6917
+ appEventLocalization: "appEventLocalizations",
6918
+ appStoreVersionExperimentTreatmentLocalization: "appStoreVersionExperimentTreatmentLocalizations"
6919
+ };
6920
+ AppAssetLibraryPlacementsResource = class {
6921
+ constructor(http) {
6922
+ this.http = http;
6923
+ }
6924
+ http;
6925
+ /**
6926
+ * Place an image or video on one localization. Placements cannot be edited —
6927
+ * delete and recreate. The parent version/page must be editable.
6928
+ */
6929
+ async create(params) {
6930
+ if (!params.imageId === !params.videoId) {
6931
+ throw new Error("A placement needs exactly one of imageId or videoId");
6932
+ }
6933
+ const media = params.imageId ? { image: { data: { type: "appAssetLibraryImages", id: params.imageId } } } : { video: { data: { type: "appAssetLibraryVideos", id: params.videoId } } };
6934
+ const response = await this.http.post(
6935
+ "/v1/appAssetLibraryPlacements",
6936
+ {
6937
+ data: {
6938
+ type: "appAssetLibraryPlacements",
6939
+ attributes: { placementType: params.placementType, placementGroup: params.placementGroup },
6940
+ relationships: { ...media, ...targetRelationship(params.target) }
6941
+ }
6942
+ }
6943
+ );
6944
+ return response.data;
6945
+ }
6946
+ async get(id, params = {}) {
6947
+ const response = await this.http.get(
6948
+ `/v1/appAssetLibraryPlacements/${id}`,
6949
+ params.include ? { include: params.include } : void 0
6950
+ );
6951
+ return response.data;
6952
+ }
6953
+ async delete(id) {
6954
+ await this.http.delete(`/v1/appAssetLibraryPlacements/${id}`);
6955
+ }
6956
+ /** Placements on one localization, in display order by default. */
6957
+ async listForTarget(target, params = {}) {
6958
+ const filter = {};
6959
+ if (params.placementType) filter.placementType = params.placementType;
6960
+ if (params.placementGroup) filter.placementGroup = params.placementGroup;
6961
+ const listParams = { filter, include: params.include, sort: "placementGroupPosition" };
6962
+ return this.http.all(
6963
+ `/v1/${TARGET_TYPES[target.kind]}/${target.id}/placements`,
6964
+ listParams
6965
+ );
6966
+ }
6967
+ /** Set the display order of a placement group. In-app event localizations cannot be ordered. */
6968
+ async order(params) {
6969
+ if (params.target.kind === "appEventLocalization") {
6970
+ throw new Error("In-app event placements cannot be ordered");
6971
+ }
6972
+ await this.http.post("/v1/appAssetLibraryPlacementOrderingRequests", {
6973
+ data: {
6974
+ type: "appAssetLibraryPlacementOrderingRequests",
6975
+ attributes: { placementGroup: params.placementGroup },
6976
+ relationships: {
6977
+ orderedPlacements: {
6978
+ data: params.placementIds.map((id) => ({ type: "appAssetLibraryPlacements", id }))
6979
+ },
6980
+ ...targetRelationship(params.target)
6981
+ }
6982
+ }
6983
+ });
6984
+ }
6985
+ };
6986
+ }
6987
+ });
6988
+
6989
+ // src/client/session-store.ts
6990
+ var session_store_exports = {};
6991
+ __export(session_store_exports, {
6992
+ clearAppleSession: () => clearAppleSession,
6993
+ lastAppleId: () => lastAppleId,
6994
+ loadAppleSession: () => loadAppleSession,
6995
+ saveAppleSession: () => saveAppleSession,
6996
+ sessionDir: () => sessionDir,
6997
+ signInWithAppleId: () => signInWithAppleId
6998
+ });
6999
+ function sessionDir() {
7000
+ return process.env.FASTNODE_SESSION_DIR || path.join(os.homedir(), ".fastnode", "sessions");
7001
+ }
7002
+ function fileFor(email) {
7003
+ const key = crypto.createHash("sha256").update(email.trim().toLowerCase()).digest("hex").slice(0, 16);
7004
+ return path.join(sessionDir(), `${key}.json`);
7005
+ }
7006
+ function ensureDir() {
7007
+ const dir = sessionDir();
7008
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true, mode: 448 });
7009
+ fs.chmodSync(dir, 448);
7010
+ }
7011
+ function saveAppleSession(session) {
7012
+ ensureDir();
7013
+ const stored = { email: session.userEmail, savedAt: (/* @__PURE__ */ new Date()).toISOString(), session };
7014
+ const path$1 = fileFor(session.userEmail);
7015
+ fs.writeFileSync(path$1, JSON.stringify(stored), { mode: 384 });
7016
+ fs.chmodSync(path$1, 384);
7017
+ fs.writeFileSync(path.join(sessionDir(), "last-apple-id"), session.userEmail, { mode: 384 });
7018
+ }
7019
+ function clearAppleSession(email) {
7020
+ const path = fileFor(email);
7021
+ if (fs.existsSync(path)) fs.unlinkSync(path);
7022
+ }
7023
+ function lastAppleId() {
7024
+ try {
7025
+ return fs.readFileSync(path.join(sessionDir(), "last-apple-id"), "utf8").trim() || null;
7026
+ } catch {
7027
+ return null;
7028
+ }
7029
+ }
7030
+ async function loadAppleSession(email, fetchImpl = fetch) {
7031
+ const path = fileFor(email);
7032
+ if (!fs.existsSync(path)) return null;
7033
+ let stored;
7034
+ try {
7035
+ stored = JSON.parse(fs.readFileSync(path, "utf8"));
7036
+ } catch {
7037
+ return null;
7038
+ }
7039
+ const cookie = Object.entries(stored.session.cookies).map(([k, v]) => `${k}=${v}`).join("; ");
7040
+ try {
7041
+ const res = await fetchImpl(OLYMPUS_SESSION2, { headers: { Cookie: cookie, Accept: "application/json" } });
7042
+ if (!res.ok) {
7043
+ clearAppleSession(email);
7044
+ return null;
7045
+ }
7046
+ } catch {
7047
+ return null;
7048
+ }
7049
+ return stored.session;
7050
+ }
7051
+ async function signInWithAppleId(options) {
7052
+ const email = options.email || lastAppleId() || await options.askEmail();
7053
+ if (!options.fresh) {
7054
+ const saved = await loadAppleSession(email);
7055
+ if (saved) {
7056
+ options.onLog?.(`Using saved Apple ID session for ${email}`);
7057
+ return saved;
7058
+ }
7059
+ }
7060
+ const password = await options.askPassword(email);
7061
+ const session = await loginWithAppleId({ email, password, onLog: options.onLog });
7062
+ saveAppleSession(session);
7063
+ return session;
7064
+ }
7065
+ var OLYMPUS_SESSION2;
7066
+ var init_session_store = __esm({
7067
+ "src/client/session-store.ts"() {
7068
+ init_apple_session();
7069
+ OLYMPUS_SESSION2 = "https://appstoreconnect.apple.com/olympus/v1/session";
7070
+ }
7071
+ });
7072
+
6394
7073
  // src/client/portal.ts
6395
7074
  function pickUniqueKeyName(base, taken) {
6396
7075
  if (!taken.has(base)) return base;
@@ -6730,7 +7409,10 @@ var init_portal = __esm({
6730
7409
  }
6731
7410
  // ── Teams ──────────────────────────────────────────────────────────────
6732
7411
  async listTeams() {
6733
- const data = await this.post("account/listTeams.action", {});
7412
+ const data = await this.post("account/listTeams.action", {
7413
+ pageSize: "500",
7414
+ pageNumber: "1"
7415
+ });
6734
7416
  return data.teams ?? [];
6735
7417
  }
6736
7418
  // ── App IDs / Bundle IDs ───────────────────────────────────────────────
@@ -6744,11 +7426,12 @@ var init_portal = __esm({
6744
7426
  return data.appIds ?? [];
6745
7427
  }
6746
7428
  async createAppId(params) {
7429
+ const safeName = params.name.replace(/[^0-9A-Za-z\s]/g, " ").replace(/\s+/g, " ").trim() || "App";
6747
7430
  const reqParams = {
6748
7431
  teamId: this.teamId,
6749
7432
  type: "explicit",
6750
7433
  identifier: params.identifier,
6751
- name: params.name
7434
+ name: safeName
6752
7435
  };
6753
7436
  if (params.enablePush) reqParams.push = "on";
6754
7437
  if (params.enableIAP !== false) reqParams.inAppPurchase = "on";
@@ -6783,6 +7466,191 @@ var init_portal = __esm({
6783
7466
  featureValue: params.enabled ? "on" : "off"
6784
7467
  });
6785
7468
  }
7469
+ // ── Application Groups ──────────────────────────────────────────────────
7470
+ //
7471
+ // App Groups (e.g. `group.com.playspina.com`) are the only clean way to
7472
+ // share data — an auth token, in our case — between a host app and an
7473
+ // embedded extension (Messages / Notification Service / etc.). App Store
7474
+ // Connect API keys (.p8) genuinely cannot manage them; Apple only exposes
7475
+ // group create/associate through this legacy session-cookie portal API,
7476
+ // which is why they live on PortalClient (Apple ID session) and not on the
7477
+ // ASC REST client. Endpoint shapes ported 1:1 from Spaceship's
7478
+ // portal_client.rb (listApplicationGroups / addApplicationGroup /
7479
+ // assignApplicationGroupToAppId).
7480
+ /** Apple's internal feature code for the App Groups capability. */
7481
+ static APP_GROUPS_FEATURE = "APG3427HIY";
7482
+ /** List all app groups in the team. */
7483
+ async listAppGroups() {
7484
+ const data = await this.post("account/ios/identifiers/listApplicationGroups.action", {
7485
+ teamId: this.teamId,
7486
+ pageNumber: "1",
7487
+ pageSize: "500",
7488
+ sort: "name=asc"
7489
+ });
7490
+ return data.applicationGroupList ?? [];
7491
+ }
7492
+ /**
7493
+ * Create an app group, returning the raw group record. Idempotent —
7494
+ * Apple rejects a duplicate group identifier ("Multiple App Groups may
7495
+ * not have the same identifier"), so we list first and reuse an existing
7496
+ * group when the identifier already exists in the team.
7497
+ *
7498
+ * Note the two ids on the returned record: `identifier` is the
7499
+ * `group.com.…` string you type, while `applicationGroup` is Apple's
7500
+ * opaque internal handle that association actually requires — do NOT pass
7501
+ * the human identifier to assignAppGroups.
7502
+ */
7503
+ async createAppGroup(params) {
7504
+ const existing = await this.listAppGroups();
7505
+ const hit = existing.find((g) => g.identifier === params.identifier);
7506
+ if (hit) return hit;
7507
+ const safeName = params.name.replace(/[^0-9A-Za-z\s]/g, " ").replace(/\s+/g, " ").trim() || "App Group";
7508
+ const data = await this.post("account/ios/identifiers/addApplicationGroup.action", {
7509
+ teamId: this.teamId,
7510
+ identifier: params.identifier,
7511
+ name: safeName
7512
+ });
7513
+ return data.applicationGroup;
7514
+ }
7515
+ /**
7516
+ * Enable the App Groups capability on an App ID. Required before an
7517
+ * App ID can have a group associated with it — mirrors Spaceship
7518
+ * enabling `AppService::AppGroup` before `associate_groups`.
7519
+ */
7520
+ async enableAppGroups(appIdId) {
7521
+ await this.post("account/ios/identifiers/updateService.action", {
7522
+ teamId: this.teamId,
7523
+ displayId: appIdId,
7524
+ featureType: _PortalClient.APP_GROUPS_FEATURE,
7525
+ featureValue: "true"
7526
+ });
7527
+ }
7528
+ /**
7529
+ * Associate one or more app groups with an App ID. `appGroupIds` are the
7530
+ * internal ids (Spaceship's `app_group_id`), NOT the `group.com.…`
7531
+ * identifiers. Apple wants both `appIdId` and `displayId` set to the
7532
+ * App ID's internal id, per Spaceship's associate_groups_with_app.
7533
+ */
7534
+ async assignAppGroups(params) {
7535
+ await this.post("account/ios/identifiers/assignApplicationGroupToAppId.action", {
7536
+ teamId: this.teamId,
7537
+ appIdId: params.appIdId,
7538
+ displayId: params.appIdId,
7539
+ applicationGroups: params.appGroupIds
7540
+ });
7541
+ }
7542
+ // ── Apple Pay merchants (OMCs) ─────────────────────────────────────────
7543
+ //
7544
+ // Merchant IDs are team-level identifiers (merchant.com.…) that an App ID's
7545
+ // Apple Pay capability must be LINKED to — enabling the capability alone
7546
+ // produces profiles that fail with "doesn't support the … Merchant ID".
7547
+ // Like App Groups, merchants are only manageable through the legacy
7548
+ // session-cookie portal API. Endpoint shapes ported 1:1 from Spaceship's
7549
+ // portal_client.rb (merchants / create_merchant! /
7550
+ // associate_merchants_with_app).
7551
+ /** Apple's internal feature code for the Apple Pay capability. */
7552
+ static APPLE_PAY_FEATURE = "OM633U5T5G";
7553
+ /**
7554
+ * List all Apple Pay merchant IDs in the team. Each record maps
7555
+ * (per Spaceship's merchant.rb attr_mapping): `omcId` = internal id,
7556
+ * `identifier` = the human `merchant.com.…` string, `name` = display name.
7557
+ */
7558
+ async listMerchants() {
7559
+ const data = await this.post("account/ios/identifiers/listOMCs.action", {
7560
+ teamId: this.teamId,
7561
+ pageNumber: "1",
7562
+ pageSize: "500",
7563
+ sort: "name=asc"
7564
+ });
7565
+ return data.identifierList ?? [];
7566
+ }
7567
+ /**
7568
+ * Register (or reuse) a merchant ID, returning its INTERNAL id (`omcId`) —
7569
+ * Apple's opaque handle that association requires, NOT the human
7570
+ * `merchant.com.…` identifier. Idempotent: lists first and reuses an
7571
+ * existing merchant with the same identifier. Endpoint + params ported 1:1
7572
+ * from Spaceship's create_merchant! (addOMC.action).
7573
+ */
7574
+ async createMerchant(params) {
7575
+ const existing = await this.listMerchants();
7576
+ const hit = existing.find((m) => m.identifier === params.identifier);
7577
+ if (hit?.omcId) return String(hit.omcId);
7578
+ const safeName = params.name.replace(/[^0-9A-Za-z\s]/g, " ").replace(/\s+/g, " ").trim() || "Merchant";
7579
+ const data = await this.post("account/ios/identifiers/addOMC.action", {
7580
+ name: safeName,
7581
+ identifier: params.identifier,
7582
+ teamId: this.teamId
7583
+ });
7584
+ const rec = data.omcId ?? data;
7585
+ const id = typeof rec === "string" ? rec : rec?.omcId ?? rec?.merchantId;
7586
+ if (!id) {
7587
+ throw new Error("Apple returned no internal merchant id (omcId) for the registered merchant.");
7588
+ }
7589
+ return String(id);
7590
+ }
7591
+ /**
7592
+ * Enable the Apple Pay capability on an App ID. Apple Pay is a standard
7593
+ * AppService (service_id OM633U5T5G) whose service_uri is the default
7594
+ * `updateService.action` — confirmed in Spaceship's app_service.rb.
7595
+ */
7596
+ async enableApplePay(appIdId) {
7597
+ await this.post("account/ios/identifiers/updateService.action", {
7598
+ teamId: this.teamId,
7599
+ displayId: appIdId,
7600
+ featureType: _PortalClient.APPLE_PAY_FEATURE,
7601
+ featureValue: "true"
7602
+ });
7603
+ }
7604
+ /**
7605
+ * Associate merchant IDs with an App ID's Apple Pay capability. `omcIds`
7606
+ * are the internal ids (each merchant record's `omcId`), NOT the
7607
+ * `merchant.com.…` identifiers. Endpoint + params ported 1:1 from
7608
+ * Spaceship's associate_merchants_with_app (assignOMCToAppId.action — note
7609
+ * the singular "OMC", and NO displayId param).
7610
+ */
7611
+ async assignMerchants(params) {
7612
+ await this.post("account/ios/identifiers/assignOMCToAppId.action", {
7613
+ teamId: this.teamId,
7614
+ appIdId: params.appIdId,
7615
+ omcIds: params.omcIds
7616
+ });
7617
+ }
7618
+ /**
7619
+ * Create an Apple Pay **Payment Processing Certificate** on a merchant ID
7620
+ * from a CSR — the certificate a PSP (e.g. Stripe) hands you as a CSR, which
7621
+ * must be uploaded to the Merchant ID in Apple Developer, then the resulting
7622
+ * `.cer` downloaded and given back to the PSP so it can decrypt payment
7623
+ * tokens. This is the one manual Apple-portal step in wiring up Apple Pay.
7624
+ *
7625
+ * Ported 1:1 from Spaceship's create_certificate! (submitCertificateRequest
7626
+ * .action): the merchant's INTERNAL id (`omcId`) is passed as BOTH `appIdId`
7627
+ * and `specialIdentifierDisplayId` — the latter is how the portal scopes a
7628
+ * cert to a merchant/special identifier rather than an App ID. The cert type
7629
+ * is APPLE_PAY (`4APLUP237T`). Returns the created certRequest (its
7630
+ * `certificateId` feeds `downloadApplePayCertificate`).
7631
+ *
7632
+ * @param omcId the merchant's internal id (from createMerchant/listMerchants)
7633
+ * @param csrContent the PEM CSR string (Stripe's "Apple Pay CSR")
7634
+ */
7635
+ async createApplePayCertificate(params) {
7636
+ const data = await this.post("account/ios/certificate/submitCertificateRequest.action", {
7637
+ teamId: this.teamId,
7638
+ type: _PortalClient.CERT_TYPES.APPLE_PAY,
7639
+ csrContent: params.csrContent,
7640
+ appIdId: params.omcId,
7641
+ specialIdentifierDisplayId: params.omcId
7642
+ });
7643
+ return data.certRequest;
7644
+ }
7645
+ /**
7646
+ * Download an Apple Pay Payment Processing Certificate's raw bytes by its
7647
+ * certificate id (from `createApplePayCertificate`). Thin wrapper over
7648
+ * `downloadCertificate` pinned to the APPLE_PAY type — the DER `.cer` you
7649
+ * upload back into Stripe.
7650
+ */
7651
+ async downloadApplePayCertificate(certificateId) {
7652
+ return this.downloadCertificate(certificateId, _PortalClient.CERT_TYPES.APPLE_PAY);
7653
+ }
6786
7654
  // ── Certificates ───────────────────────────────────────────────────────
6787
7655
  async listCertificates(types) {
6788
7656
  const iosTypes = types ?? [
@@ -7279,8 +8147,14 @@ __export(client_exports, {
7279
8147
  TokenManager: () => TokenManager,
7280
8148
  beginLogin: () => beginLogin,
7281
8149
  checkPortalResponse: () => checkPortalResponse,
8150
+ clearAppleSession: () => clearAppleSession,
8151
+ lastAppleId: () => lastAppleId,
8152
+ loadAppleSession: () => loadAppleSession,
7282
8153
  loginWithAppleId: () => loginWithAppleId,
7283
8154
  requestSmsCode: () => requestSmsCode,
8155
+ saveAppleSession: () => saveAppleSession,
8156
+ sessionDir: () => sessionDir,
8157
+ signInWithAppleId: () => signInWithAppleId,
7284
8158
  submitTwoFactorCode: () => submitTwoFactorCode
7285
8159
  });
7286
8160
  var AppStoreConnectClient;
@@ -7322,9 +8196,13 @@ var init_client = __esm({
7322
8196
  init_in_app_purchases();
7323
8197
  init_game_center_detail();
7324
8198
  init_xcode_metrics();
8199
+ init_app_asset_libraries();
8200
+ init_app_asset_library_assets();
8201
+ init_app_asset_library_placements();
7325
8202
  init_auth();
7326
8203
  init_request();
7327
8204
  init_apple_session();
8205
+ init_session_store();
7328
8206
  init_portal();
7329
8207
  init_iris();
7330
8208
  AppStoreConnectClient = class {
@@ -7363,6 +8241,10 @@ var init_client = __esm({
7363
8241
  inAppPurchases;
7364
8242
  gameCenterDetail;
7365
8243
  xcodeMetrics;
8244
+ appAssetLibraries;
8245
+ appAssetLibraryImages;
8246
+ appAssetLibraryVideos;
8247
+ appAssetLibraryPlacements;
7366
8248
  constructor(config) {
7367
8249
  const tokenManager = new TokenManager({
7368
8250
  keyId: config.keyId,
@@ -7406,6 +8288,10 @@ var init_client = __esm({
7406
8288
  this.inAppPurchases = new InAppPurchasesResource(this.http);
7407
8289
  this.gameCenterDetail = new GameCenterDetailResource(this.http);
7408
8290
  this.xcodeMetrics = new XcodeMetricsResource(this.http);
8291
+ this.appAssetLibraries = new AppAssetLibrariesResource(this.http);
8292
+ this.appAssetLibraryImages = new AppAssetLibraryImagesResource(this.http);
8293
+ this.appAssetLibraryVideos = new AppAssetLibraryVideosResource(this.http);
8294
+ this.appAssetLibraryPlacements = new AppAssetLibraryPlacementsResource(this.http);
7409
8295
  }
7410
8296
  async request(path, options) {
7411
8297
  return this.http.request(path, options);
@@ -8173,8 +9059,8 @@ var init_utilities = __esm({
8173
9059
  description: "Ask the user for input",
8174
9060
  async run(params) {
8175
9061
  const text = params.text ?? "Enter a value:";
8176
- const { createInterface: createInterface2 } = await import('readline');
8177
- const rl = createInterface2({ input: process.stdin, output: process.stdout });
9062
+ const { createInterface: createInterface4 } = await import('readline');
9063
+ const rl = createInterface4({ input: process.stdin, output: process.stdout });
8178
9064
  return new Promise((resolve4) => {
8179
9065
  rl.question(text + " ", (answer) => {
8180
9066
  rl.close();
@@ -8566,6 +9452,308 @@ var init_options = __esm({
8566
9452
  init_client();
8567
9453
  }
8568
9454
  });
9455
+
9456
+ // src/privacy/index.ts
9457
+ var privacy_exports = {};
9458
+ __export(privacy_exports, {
9459
+ DATA_PROTECTIONS: () => DATA_PROTECTIONS,
9460
+ DATA_USAGE_CATEGORIES: () => DATA_USAGE_CATEGORIES,
9461
+ DATA_USAGE_PURPOSES: () => DATA_USAGE_PURPOSES,
9462
+ createAppDataUsage: () => createAppDataUsage,
9463
+ deleteAppDataUsage: () => deleteAppDataUsage,
9464
+ getAppDataUsagesPublishState: () => getAppDataUsagesPublishState,
9465
+ listAppDataUsages: () => listAppDataUsages,
9466
+ publishAppDataUsages: () => publishAppDataUsages,
9467
+ selectProviderForApp: () => selectProviderForApp,
9468
+ uploadAppPrivacyDetails: () => uploadAppPrivacyDetails,
9469
+ validateUsages: () => validateUsages
9470
+ });
9471
+ async function iris(session, method, path, body) {
9472
+ const res = await fetch(path.startsWith("http") ? path : `${IRIS_BASE2}${path}`, {
9473
+ method,
9474
+ headers: {
9475
+ Cookie: cookieHeader(session),
9476
+ Accept: "application/vnd.api+json, application/json",
9477
+ "Content-Type": "application/vnd.api+json",
9478
+ "X-Requested-With": "XMLHttpRequest"
9479
+ },
9480
+ body: body ? JSON.stringify(body) : void 0
9481
+ });
9482
+ absorbCookies(session, res);
9483
+ const text = await res.text();
9484
+ let parsed = null;
9485
+ try {
9486
+ parsed = text ? JSON.parse(text) : null;
9487
+ } catch {
9488
+ }
9489
+ return { status: res.status, body: parsed, text };
9490
+ }
9491
+ function cookieHeader(session) {
9492
+ return Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join("; ");
9493
+ }
9494
+ function absorbCookies(session, res) {
9495
+ for (const raw of res.headers.getSetCookie?.() ?? []) {
9496
+ const m = raw.match(/^([^=]+)=([^;]*)/);
9497
+ if (m) session.cookies[m[1]] = m[2];
9498
+ }
9499
+ }
9500
+ function fail(what, r) {
9501
+ const detail = r.body?.errors?.map((e) => e.detail ?? e.title).join("; ") || r.text.slice(0, 300);
9502
+ throw new Error(`${what} failed (HTTP ${r.status}): ${detail}`);
9503
+ }
9504
+ async function selectProviderForApp(session, appId, onLog) {
9505
+ const owns = async () => (await iris(session, "GET", `/apps/${appId}?fields[apps]=name`)).status === 200;
9506
+ if (await owns()) return;
9507
+ const res = await fetch(OLYMPUS_SESSION3, { headers: { Cookie: cookieHeader(session), Accept: "application/json" } });
9508
+ absorbCookies(session, res);
9509
+ const info = await res.json().catch(() => null);
9510
+ const providers = info?.availableProviders ?? [];
9511
+ for (const p of providers) {
9512
+ const sw = await fetch(OLYMPUS_SESSION3, {
9513
+ method: "POST",
9514
+ headers: { Cookie: cookieHeader(session), "Content-Type": "application/json", Accept: "application/json" },
9515
+ body: JSON.stringify({ provider: { providerId: p.providerId } })
9516
+ });
9517
+ absorbCookies(session, sw);
9518
+ if (sw.ok && await owns()) {
9519
+ onLog?.(`Switched to team "${p.name}"`);
9520
+ session.teamId = String(p.providerId);
9521
+ return;
9522
+ }
9523
+ }
9524
+ throw new Error(`None of this Apple ID's ${providers.length} teams can see app ${appId}.`);
9525
+ }
9526
+ async function listAppDataUsages(session, appId) {
9527
+ const all = [];
9528
+ let next = `/apps/${appId}/dataUsages?include=category,grouping,purpose,dataProtection&limit=500`;
9529
+ while (next) {
9530
+ const r = await iris(session, "GET", next);
9531
+ if (r.status !== 200) fail("List app data usages", r);
9532
+ all.push(...(r.body?.data ?? []).map((d) => ({ id: d.id })));
9533
+ next = r.body?.links?.next ?? null;
9534
+ }
9535
+ return all;
9536
+ }
9537
+ async function createAppDataUsage(session, input) {
9538
+ const relationships = { app: { data: { type: "apps", id: input.appId } } };
9539
+ if (input.category) relationships.category = { data: { type: "appDataUsageCategories", id: input.category } };
9540
+ if (input.purpose) relationships.purpose = { data: { type: "appDataUsagePurposes", id: input.purpose } };
9541
+ relationships.dataProtection = { data: { type: "appDataUsageDataProtections", id: input.dataProtection } };
9542
+ const r = await iris(session, "POST", "/appDataUsages", { data: { type: "appDataUsages", relationships } });
9543
+ if (r.status !== 201 && r.status !== 200) fail(`Create ${input.category ?? ""} ${input.purpose ?? ""} ${input.dataProtection}`.replace(/\s+/g, " "), r);
9544
+ return r.body?.data?.id;
9545
+ }
9546
+ async function deleteAppDataUsage(session, id) {
9547
+ const r = await iris(session, "DELETE", `/appDataUsages/${id}`);
9548
+ if (r.status !== 204 && r.status !== 200) fail(`Delete app data usage ${id}`, r);
9549
+ }
9550
+ async function getAppDataUsagesPublishState(session, appId) {
9551
+ const r = await iris(session, "GET", `/apps/${appId}/dataUsagePublishState`);
9552
+ if (r.status !== 200) fail("Read privacy publish state", r);
9553
+ return { id: r.body.data.id, published: Boolean(r.body.data.attributes?.published) };
9554
+ }
9555
+ async function publishAppDataUsages(session, publishStateId) {
9556
+ const r = await iris(session, "PATCH", `/appDataUsagesPublishState/${publishStateId}`, {
9557
+ data: { type: "appDataUsagesPublishState", id: publishStateId, attributes: { published: true } }
9558
+ });
9559
+ if (r.status !== 200) fail("Publish privacy details", r);
9560
+ }
9561
+ async function uploadAppPrivacyDetails(opts) {
9562
+ const { session, appId, usages, skipPublish, onLog = () => {
9563
+ } } = opts;
9564
+ validateUsages(usages);
9565
+ await selectProviderForApp(session, appId, onLog);
9566
+ const existing = await listAppDataUsages(session, appId);
9567
+ onLog(`Deleting ${existing.length} existing answer${existing.length === 1 ? "" : "s"}`);
9568
+ for (const u of existing) await deleteAppDataUsage(session, u.id);
9569
+ let created = 0;
9570
+ for (const usage of usages) {
9571
+ const purposes = usage.purposes?.length ? usage.purposes : [null];
9572
+ for (const purpose of purposes) {
9573
+ for (const protection of usage.data_protections) {
9574
+ onLog(protection === "DATA_NOT_COLLECTED" ? `Setting ${protection}` : `Setting ${usage.category} and ${purpose} to ${protection}`);
9575
+ await createAppDataUsage(session, { appId, category: usage.category ?? null, purpose, dataProtection: protection });
9576
+ created++;
9577
+ }
9578
+ }
9579
+ }
9580
+ if (skipPublish) {
9581
+ onLog("Skipping publish");
9582
+ return { deleted: existing.length, created, published: false };
9583
+ }
9584
+ const state = await getAppDataUsagesPublishState(session, appId);
9585
+ await publishAppDataUsages(session, state.id);
9586
+ onLog("Published");
9587
+ return { deleted: existing.length, created, published: true };
9588
+ }
9589
+ function validateUsages(usages) {
9590
+ if (!Array.isArray(usages) || usages.length === 0) throw new Error("No privacy usages given.");
9591
+ const notCollected = usages.some((u) => u.data_protections?.includes("DATA_NOT_COLLECTED"));
9592
+ if (notCollected && usages.length > 1) {
9593
+ throw new Error("DATA_NOT_COLLECTED must be the only entry: an app cannot both collect nothing and collect something.");
9594
+ }
9595
+ const known = (list, v, what) => {
9596
+ if (!list.includes(v)) throw new Error(`"${v}" is not an App Store Connect ${what}. Use one of: ${list.join(", ")}.`);
9597
+ };
9598
+ for (const u of usages) {
9599
+ if (!u.data_protections?.length) throw new Error(`${u.category ?? "An entry"} has no data_protections.`);
9600
+ if (u.category) known(DATA_USAGE_CATEGORIES, u.category, "data category");
9601
+ for (const p of u.purposes ?? []) known(DATA_USAGE_PURPOSES, p, "purpose");
9602
+ for (const d of u.data_protections) known(DATA_PROTECTIONS, d, "data protection");
9603
+ if (u.data_protections.includes("DATA_NOT_COLLECTED")) continue;
9604
+ if (!u.category) throw new Error("Every collected entry needs a category.");
9605
+ if (!u.purposes?.length) throw new Error(`${u.category} needs at least one purpose.`);
9606
+ const linked = u.data_protections.includes("DATA_LINKED_TO_YOU");
9607
+ const unlinked = u.data_protections.includes("DATA_NOT_LINKED_TO_YOU");
9608
+ if (linked && unlinked) throw new Error(`${u.category} cannot be both linked and not linked to the user.`);
9609
+ }
9610
+ }
9611
+ var IRIS_BASE2, OLYMPUS_SESSION3, DATA_USAGE_CATEGORIES, DATA_USAGE_PURPOSES, DATA_PROTECTIONS;
9612
+ var init_privacy = __esm({
9613
+ "src/privacy/index.ts"() {
9614
+ IRIS_BASE2 = "https://appstoreconnect.apple.com/iris/v1";
9615
+ OLYMPUS_SESSION3 = "https://appstoreconnect.apple.com/olympus/v1/session";
9616
+ DATA_USAGE_CATEGORIES = [
9617
+ "PAYMENT_INFORMATION",
9618
+ "CREDIT_AND_FRAUD",
9619
+ "OTHER_FINANCIAL_INFO",
9620
+ "PRECISE_LOCATION",
9621
+ "COARSE_LOCATION",
9622
+ "SENSITIVE_INFO",
9623
+ "PHYSICAL_ADDRESS",
9624
+ "EMAIL_ADDRESS",
9625
+ "NAME",
9626
+ "PHONE_NUMBER",
9627
+ "OTHER_CONTACT_INFO",
9628
+ "CONTACTS",
9629
+ "EMAILS_OR_TEXT_MESSAGES",
9630
+ "PHOTOS_OR_VIDEOS",
9631
+ "AUDIO",
9632
+ "GAMEPLAY_CONTENT",
9633
+ "CUSTOMER_SUPPORT",
9634
+ "OTHER_USER_CONTENT",
9635
+ "BROWSING_HISTORY",
9636
+ "SEARCH_HISTORY",
9637
+ "USER_ID",
9638
+ "DEVICE_ID",
9639
+ "PURCHASE_HISTORY",
9640
+ "PRODUCT_INTERACTION",
9641
+ "ADVERTISING_DATA",
9642
+ "OTHER_USAGE_DATA",
9643
+ "CRASH_DATA",
9644
+ "PERFORMANCE_DATA",
9645
+ "OTHER_DIAGNOSTIC_DATA",
9646
+ "OTHER_DATA",
9647
+ "HEALTH",
9648
+ "FITNESS"
9649
+ ];
9650
+ DATA_USAGE_PURPOSES = [
9651
+ "THIRD_PARTY_ADVERTISING",
9652
+ "DEVELOPERS_ADVERTISING",
9653
+ "ANALYTICS",
9654
+ "PRODUCT_PERSONALIZATION",
9655
+ "APP_FUNCTIONALITY",
9656
+ "OTHER_PURPOSES"
9657
+ ];
9658
+ DATA_PROTECTIONS = [
9659
+ "DATA_USED_TO_TRACK_YOU",
9660
+ "DATA_LINKED_TO_YOU",
9661
+ "DATA_NOT_LINKED_TO_YOU",
9662
+ "DATA_NOT_COLLECTED"
9663
+ ];
9664
+ }
9665
+ });
9666
+
9667
+ // src/review/index.ts
9668
+ var review_exports = {};
9669
+ __export(review_exports, {
9670
+ getReviewFeedback: () => getReviewFeedback,
9671
+ getThreadMessages: () => getThreadMessages,
9672
+ htmlToText: () => htmlToText
9673
+ });
9674
+ async function iris2(session, path) {
9675
+ const res = await fetch(`${IRIS_BASE3}${path}`, {
9676
+ headers: {
9677
+ Cookie: Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join("; "),
9678
+ Accept: "application/vnd.api+json, application/json",
9679
+ "X-Requested-With": "XMLHttpRequest"
9680
+ }
9681
+ });
9682
+ for (const raw of res.headers.getSetCookie?.() ?? []) {
9683
+ const m = raw.match(/^([^=]+)=([^;]*)/);
9684
+ if (m) session.cookies[m[1]] = m[2];
9685
+ }
9686
+ const text = await res.text();
9687
+ let body = null;
9688
+ try {
9689
+ body = text ? JSON.parse(text) : null;
9690
+ } catch {
9691
+ }
9692
+ if (!res.ok) {
9693
+ const detail = body?.errors?.map((e) => e.detail ?? e.title).join("; ") || text.slice(0, 300);
9694
+ throw new Error(`GET ${path} failed (HTTP ${res.status}): ${detail}`);
9695
+ }
9696
+ return body;
9697
+ }
9698
+ function htmlToText(html) {
9699
+ if (!html) return "";
9700
+ return html.replace(/<br\s*\/?>/gi, "\n").replace(/<\/(p|div|li|h\d)>/gi, "\n").replace(/<li[^>]*>/gi, "- ").replace(/<[^>]+>/g, "").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/\n{3,}/g, "\n\n").trim();
9701
+ }
9702
+ function reasonsFrom(rejection) {
9703
+ const reasons = rejection?.attributes?.reasons ?? [];
9704
+ return (Array.isArray(reasons) ? reasons : []).map((r) => ({
9705
+ section: r.reasonSection ?? r.section,
9706
+ description: r.reasonDescription ?? r.description,
9707
+ code: r.reasonCode ?? r.code
9708
+ }));
9709
+ }
9710
+ async function getThreadMessages(session, threadId) {
9711
+ const body = await iris2(session, `/resolutionCenterThreads/${threadId}/resolutionCenterMessages?include=fromActor,rejections`);
9712
+ const included = new Map((body?.included ?? []).map((i) => [`${i.type}:${i.id}`, i]));
9713
+ return (body?.data ?? []).map((m) => {
9714
+ const actor = m.relationships?.fromActor?.data;
9715
+ const actorRow = actor ? included.get(`${actor.type}:${actor.id}`) : null;
9716
+ const rejections = (m.relationships?.rejections?.data ?? []).map((r) => included.get(`${r.type}:${r.id}`)).filter(Boolean);
9717
+ return {
9718
+ id: m.id,
9719
+ createdDate: m.attributes?.createdDate ?? null,
9720
+ from: actorRow?.attributes?.actorType ?? actorRow?.attributes?.userFirstName ?? null,
9721
+ body: htmlToText(m.attributes?.messageBody),
9722
+ reasons: rejections.flatMap(reasonsFrom)
9723
+ };
9724
+ });
9725
+ }
9726
+ async function getReviewFeedback(session, appId, opts = {}) {
9727
+ await selectProviderForApp(session, appId, opts.onLog);
9728
+ const subs = await iris2(session, `/apps/${appId}/reviewSubmissions?limit=10`);
9729
+ const out = [];
9730
+ for (const s of subs?.data ?? []) {
9731
+ const state = s.attributes?.state ?? "";
9732
+ if (opts.onlyOpen && state !== "UNRESOLVED_ISSUES") continue;
9733
+ const threadsBody = await iris2(session, `/resolutionCenterThreads?filter[reviewSubmission]=${s.id}&include=reviewSubmission`);
9734
+ const threads = [];
9735
+ for (const t of threadsBody?.data ?? []) {
9736
+ threads.push({
9737
+ id: t.id,
9738
+ threadType: t.attributes?.threadType ?? null,
9739
+ state: t.attributes?.state ?? null,
9740
+ createdDate: t.attributes?.createdDate ?? null,
9741
+ messages: await getThreadMessages(session, t.id)
9742
+ });
9743
+ }
9744
+ if (threads.length || !opts.onlyOpen) {
9745
+ out.push({ submissionId: s.id, state, submittedDate: s.attributes?.submittedDate ?? null, threads });
9746
+ }
9747
+ }
9748
+ return out;
9749
+ }
9750
+ var IRIS_BASE3;
9751
+ var init_review = __esm({
9752
+ "src/review/index.ts"() {
9753
+ init_privacy();
9754
+ IRIS_BASE3 = "https://appstoreconnect.apple.com/iris/v1";
9755
+ }
9756
+ });
8569
9757
  function createRunCommand() {
8570
9758
  const cmd = new commander.Command("run").description("Execute a lane from Fastfile.ts").argument("<lane>", "Lane name to execute").argument("[params...]", "Key:value parameters").option("-p, --platform <platform>", "Platform (ios, android)").action(async (laneName, params, opts) => {
8571
9759
  const { loadFastfile: loadFastfile2 } = await Promise.resolve().then(() => (init_fastfile(), fastfile_exports));
@@ -8686,10 +9874,10 @@ function createPilotCommand() {
8686
9874
  const cmd = new commander.Command("pilot").description("Manage TestFlight builds and testers");
8687
9875
  cmd.command("upload").description("Upload a build to TestFlight").requiredOption("-i, --ipa <path>", "Path to IPA file").option("--api-key-id <id>", "ASC API Key ID").option("--api-issuer-id <id>", "ASC API Issuer ID").option("--api-key <path>", "Path to .p8 API key file").action(async (opts) => {
8688
9876
  const { uploadToTestFlight: uploadToTestFlight2 } = await Promise.resolve().then(() => (init_pilot(), pilot_exports));
8689
- const { readFile: readFile8 } = await import('fs/promises');
9877
+ const { readFile: readFile9 } = await import('fs/promises');
8690
9878
  let apiPrivateKey = "";
8691
9879
  if (opts.apiKey) {
8692
- apiPrivateKey = await readFile8(opts.apiKey, "utf-8");
9880
+ apiPrivateKey = await readFile9(opts.apiKey, "utf-8");
8693
9881
  }
8694
9882
  console.log(`Uploading ${opts.ipa}...`);
8695
9883
  const result = await uploadToTestFlight2({
@@ -8721,7 +9909,10 @@ function createPilotCommand() {
8721
9909
  return cmd;
8722
9910
  }
8723
9911
  function createDeliverCommand() {
8724
- return new commander.Command("deliver").description("Upload metadata and binary to App Store Connect").option("-a, --app-identifier <id>", "Bundle identifier").option("-i, --ipa <path>", "Path to IPA file").option("-m, --metadata-path <path>", "Path to metadata directory").option("--screenshots-path <path>", "Path to screenshots directory").option("--submit-for-review", "Submit for review after upload").option("--skip-binary-upload", "Skip binary upload").option("--skip-metadata", "Skip metadata upload").option("--skip-screenshots", "Skip screenshot upload").option("--app-version <version>", "App version string").action(async (opts) => {
9912
+ return new commander.Command("deliver").description("Upload metadata and binary to App Store Connect").option("-a, --app-identifier <id>", "Bundle identifier").option("-i, --ipa <path>", "Path to IPA file").option("-m, --metadata-path <path>", "Path to metadata directory").option("--screenshots-path <path>", "Path to screenshots directory").option("--submit-for-review", "Submit for review after upload").option("--skip-binary-upload", "Skip binary upload").option("--skip-metadata", "Skip metadata upload").option("--skip-screenshots", "Skip screenshot upload").option(
9913
+ "--assets-path <path>",
9914
+ "App Asset Library folder: <locale>/{screenshots,previews,header,search_results}/"
9915
+ ).option("--asset-library", "Upload --screenshots-path through the App Asset Library (screenshot sets are deprecated)").option("--overwrite-assets", "Replace existing placements in each slot that gets new files").option("--skip-assets", "Skip asset library upload").option("--app-version <version>", "App version string").action(async (opts) => {
8725
9916
  const { resolveClientFromEnv: resolveClientFromEnv2 } = await Promise.resolve().then(() => (init_options(), options_exports));
8726
9917
  const { uploadToAppStore: uploadToAppStore2 } = await Promise.resolve().then(() => (init_deliver(), deliver_exports));
8727
9918
  const client = await resolveClientFromEnv2();
@@ -8740,7 +9931,12 @@ function createDeliverCommand() {
8740
9931
  submitForReview: opts.submitForReview,
8741
9932
  skipBinaryUpload: opts.skipBinaryUpload,
8742
9933
  skipMetadata: opts.skipMetadata,
8743
- skipScreenshots: opts.skipScreenshots
9934
+ skipScreenshots: opts.skipScreenshots,
9935
+ assetsPath: opts.assetsPath,
9936
+ useAssetLibrary: opts.assetLibrary,
9937
+ overwriteAssets: opts.overwriteAssets,
9938
+ skipAssets: opts.skipAssets,
9939
+ onOutput: (chunk) => process.stdout.write(chunk)
8744
9940
  });
8745
9941
  console.log(`App: ${result.appId}`);
8746
9942
  console.log(`Version: ${result.versionId}`);
@@ -8844,6 +10040,155 @@ function createLanesCommand() {
8844
10040
  }
8845
10041
  });
8846
10042
  }
10043
+ function createPrivacyCommand() {
10044
+ return new commander.Command("privacy").description("Upload App Privacy details (nutrition labels) to App Store Connect").requiredOption("--app-id <id>", "App Store Connect app id (numeric Apple ID)").requiredOption("-j, --json-path <path>", "Fastlane-format app_privacy_details.json").option("-u, --username <email>", "Apple ID (defaults to the last one signed in)", process.env.FASTNODE_USER ?? process.env.FASTLANE_USER).option("--fresh-login", "Ignore the saved Apple ID session and sign in again", false).option("--skip-publish", "Save the answers without publishing them", false).action(async (opts) => {
10045
+ const { signInWithAppleId: signInWithAppleId2, saveAppleSession: saveAppleSession2 } = await Promise.resolve().then(() => (init_session_store(), session_store_exports));
10046
+ const { uploadAppPrivacyDetails: uploadAppPrivacyDetails2, validateUsages: validateUsages2 } = await Promise.resolve().then(() => (init_privacy(), privacy_exports));
10047
+ const usages = JSON.parse(fs.readFileSync(opts.jsonPath, "utf8"));
10048
+ validateUsages2(usages);
10049
+ const session = await signInWithAppleId2({
10050
+ email: opts.username,
10051
+ askEmail: () => ask("Apple ID: "),
10052
+ askPassword: async (email) => process.env.FASTNODE_PASSWORD ?? process.env.FASTLANE_PASSWORD ?? await ask(`Password for ${email}: `, true),
10053
+ fresh: opts.freshLogin,
10054
+ onLog: (m) => console.log(m)
10055
+ });
10056
+ const result = await uploadAppPrivacyDetails2({
10057
+ session,
10058
+ appId: opts.appId,
10059
+ usages,
10060
+ skipPublish: opts.skipPublish,
10061
+ onLog: (m) => console.log(m)
10062
+ });
10063
+ saveAppleSession2(session);
10064
+ console.log(`Done: ${result.created} answers${result.published ? ", published" : ""}.`);
10065
+ });
10066
+ }
10067
+ function ask(question, secret = false) {
10068
+ let muted = false;
10069
+ const output = new stream.Writable({
10070
+ write(chunk, _enc, cb) {
10071
+ if (!muted) process.stdout.write(chunk);
10072
+ cb();
10073
+ }
10074
+ });
10075
+ const rl = readline.createInterface({ input: process.stdin, output, terminal: true });
10076
+ return new Promise((resolve4) => {
10077
+ rl.question(question, (answer) => {
10078
+ rl.close();
10079
+ if (secret) process.stdout.write("\n");
10080
+ resolve4(answer.trim());
10081
+ });
10082
+ muted = secret;
10083
+ });
10084
+ }
10085
+ function createRejectionsCommand() {
10086
+ return new commander.Command("rejections").description("Show App Review's Resolution Center messages and rejection reasons").requiredOption("--app-id <ids...>", "App Store Connect app id(s) (numeric Apple ID)").option("-u, --username <email>", "Apple ID (defaults to the last one signed in)", process.env.FASTNODE_USER ?? process.env.FASTLANE_USER).option("--fresh-login", "Ignore the saved Apple ID session and sign in again", false).option("--all", "Include resolved submissions, not just ones awaiting you", false).option("--json", "Print JSON instead of text", false).action(async (opts) => {
10087
+ const { signInWithAppleId: signInWithAppleId2, saveAppleSession: saveAppleSession2 } = await Promise.resolve().then(() => (init_session_store(), session_store_exports));
10088
+ const { getReviewFeedback: getReviewFeedback2 } = await Promise.resolve().then(() => (init_review(), review_exports));
10089
+ const session = await signInWithAppleId2({
10090
+ email: opts.username,
10091
+ askEmail: () => ask2("Apple ID: "),
10092
+ askPassword: async (email) => process.env.FASTNODE_PASSWORD ?? process.env.FASTLANE_PASSWORD ?? await ask2(`Password for ${email}: `, true),
10093
+ fresh: opts.freshLogin,
10094
+ onLog: (m) => console.error(m)
10095
+ });
10096
+ const all = {};
10097
+ for (const appId of opts.appId) {
10098
+ const feedback = await getReviewFeedback2(session, appId, { onlyOpen: !opts.all, onLog: (m) => console.error(m) });
10099
+ all[appId] = feedback;
10100
+ if (opts.json) continue;
10101
+ console.log(`
10102
+ \u2550\u2550 App ${appId} \u2550\u2550`);
10103
+ if (!feedback.length) console.log("No open App Review feedback.");
10104
+ for (const s of feedback) {
10105
+ console.log(`
10106
+ Submission ${s.submissionId} \u2014 ${s.state} (submitted ${s.submittedDate ?? "?"})`);
10107
+ for (const t of s.threads) {
10108
+ console.log(` Thread ${t.threadType ?? "?"} \xB7 ${t.state ?? "?"} \xB7 ${t.createdDate ?? ""}`);
10109
+ for (const m of t.messages) {
10110
+ console.log(`
10111
+ \u2500\u2500 ${m.from ?? "App Review"} \xB7 ${m.createdDate ?? ""}`);
10112
+ for (const r of m.reasons) console.log(` \u25B8 ${[r.section, r.description].filter(Boolean).join(" \u2014 ")}${r.code ? ` [${r.code}]` : ""}`);
10113
+ console.log(m.body.split("\n").map((l) => ` ${l}`).join("\n"));
10114
+ }
10115
+ }
10116
+ }
10117
+ }
10118
+ saveAppleSession2(session);
10119
+ if (opts.json) console.log(JSON.stringify(all, null, 2));
10120
+ });
10121
+ }
10122
+ function ask2(question, secret = false) {
10123
+ let muted = false;
10124
+ const output = new stream.Writable({ write(chunk, _enc, cb) {
10125
+ if (!muted) process.stdout.write(chunk);
10126
+ cb();
10127
+ } });
10128
+ const rl = readline.createInterface({ input: process.stdin, output, terminal: true });
10129
+ return new Promise((resolve4) => {
10130
+ rl.question(question, (a) => {
10131
+ rl.close();
10132
+ if (secret) process.stdout.write("\n");
10133
+ resolve4(a.trim());
10134
+ });
10135
+ muted = secret;
10136
+ });
10137
+ }
10138
+ function createAssetsCommand() {
10139
+ const assets = new commander.Command("assets").description(
10140
+ "App Store asset library \u2014 screenshots, previews, product page headers, search results assets"
10141
+ );
10142
+ assets.command("specs").description("List placement groups and the image sizes Apple accepts for each (from ASC reference data)").option("--feature <feature>", "APP_STORE_VERSIONS, CUSTOM_PRODUCT_PAGES, IN_APP_EVENTS, \u2026", "APP_STORE_VERSIONS").option("--placement-type <type>", "e.g. APP_SCREENSHOT, PRODUCT_PAGE_HEADER_ASSET").option("--json", "Print the raw reference data").action(async (opts) => {
10143
+ const { resolveClientFromEnv: resolveClientFromEnv2 } = await Promise.resolve().then(() => (init_options(), options_exports));
10144
+ const client = await resolveClientFromEnv2();
10145
+ const feature = opts.feature;
10146
+ const refData = await client.appAssetLibraries.refData({
10147
+ features: [feature],
10148
+ placementTypes: opts.placementType ? [opts.placementType] : void 0
10149
+ });
10150
+ if (opts.json) {
10151
+ console.log(JSON.stringify(refData, null, 2));
10152
+ return;
10153
+ }
10154
+ const policies = refData.features?.find((f) => f.featureId === feature)?.placementPolicies ?? [];
10155
+ const specs = new Map([...refData.imageSpecs ?? [], ...refData.videoSpecs ?? []].map((s) => [s.specId, s]));
10156
+ for (const type of refData.placementTypes ?? []) {
10157
+ if (opts.placementType && type.placementTypeId !== opts.placementType) continue;
10158
+ const policy = policies.find((p) => p.placementType === type.placementTypeId);
10159
+ if (policies.length && !policy) continue;
10160
+ console.log(`
10161
+ ${type.placementTypeId} (${(type.acceptsAssetCategories ?? []).join(", ")})`);
10162
+ for (const mapping of type.specMappings ?? []) {
10163
+ const max = policy?.groupLimits?.find((l) => l.groupIds.includes(mapping.placementGroupId))?.maxCount;
10164
+ const sizes = mapping.specs.map((id) => specs.get(id)).filter((s) => Boolean(s)).map((s) => {
10165
+ const d = s.dimensions;
10166
+ const w = d.minWidth === d.maxWidth ? `${d.minWidth}` : `${d.minWidth}-${d.maxWidth}`;
10167
+ const h = d.minHeight === d.maxHeight ? `${d.minHeight}` : `${d.minHeight}-${d.maxHeight}`;
10168
+ return `${w}\xD7${h}`;
10169
+ });
10170
+ console.log(` ${mapping.placementGroupId.padEnd(44)} ${max ? `max ${max} ` : ""}${sizes.join(", ")}`);
10171
+ }
10172
+ }
10173
+ });
10174
+ assets.command("list").description("List images and videos in an app's asset library").requiredOption("-a, --app-identifier <id>", "Bundle identifier").action(async (opts) => {
10175
+ const { resolveClientFromEnv: resolveClientFromEnv2 } = await Promise.resolve().then(() => (init_options(), options_exports));
10176
+ const client = await resolveClientFromEnv2();
10177
+ const app = await client.apps.findByBundleId(opts.appIdentifier);
10178
+ if (!app) throw new Error(`App not found: ${opts.appIdentifier}`);
10179
+ const library = await client.appAssetLibraries.getForApp(app.id);
10180
+ const [images, videos] = await Promise.all([
10181
+ client.appAssetLibraries.listImages(library.id),
10182
+ client.appAssetLibraries.listVideos(library.id)
10183
+ ]);
10184
+ for (const a of [...images, ...videos]) {
10185
+ const at = a.attributes;
10186
+ console.log(`${a.type === "appAssetLibraryImages" ? "image" : "video"} ${a.id} ${String(at.state).padEnd(22)} ${at.category} ${at.referenceName ?? at.fileName}`);
10187
+ }
10188
+ if (!images.length && !videos.length) console.log("Asset library is empty");
10189
+ });
10190
+ return assets;
10191
+ }
8847
10192
 
8848
10193
  // src/cli/index.ts
8849
10194
  init_options();
@@ -8862,6 +10207,9 @@ function createCli() {
8862
10207
  program.addCommand(createMatchCommand());
8863
10208
  program.addCommand(createActionsCommand());
8864
10209
  program.addCommand(createLanesCommand());
10210
+ program.addCommand(createPrivacyCommand());
10211
+ program.addCommand(createRejectionsCommand());
10212
+ program.addCommand(createAssetsCommand());
8865
10213
  return program;
8866
10214
  }
8867
10215