@sitepong/fastnode 0.2.1 → 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.
Files changed (126) hide show
  1. package/README.md +58 -1
  2. package/dist/actions/index.cjs +891 -72
  3. package/dist/actions/index.cjs.map +1 -1
  4. package/dist/actions/index.mjs +892 -73
  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 +17868 -24
  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 +17853 -7
  13. package/dist/cert/index.mjs.map +1 -1
  14. package/dist/{cert-e5GAgAPb.d.ts → cert-RvXmdMke.d.ts} +2 -2
  15. package/dist/{cert-uLLdb5a7.d.cts → cert-eOoxGMN1.d.cts} +2 -2
  16. package/dist/cli/index.cjs +1364 -78
  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 +1365 -79
  21. package/dist/cli/index.mjs.map +1 -1
  22. package/dist/client/index.cjs +480 -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 +480 -31
  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-DrUpRXRG.d.cts → index-B6-MJpO4.d.cts} +1 -1
  46. package/dist/{index-Clyf4-lu.d.cts → index-BE7bwNff.d.cts} +1 -1
  47. package/dist/index-BGhV0gS-.d.ts +65 -0
  48. package/dist/index-BaNcf7jt.d.cts +201 -0
  49. package/dist/{index-Di4Ic0EE.d.ts → index-BvyZ4uiw.d.ts} +3 -3
  50. package/dist/{index-CEldrbyz.d.ts → index-C01Rkrir.d.ts} +2 -2
  51. package/dist/{index-jeI44Q5G.d.ts → index-C9mHBJED.d.ts} +1 -1
  52. package/dist/{index-BgHagzj1.d.cts → index-CAEiXVWd.d.ts} +9 -1
  53. package/dist/index-CBf0-NUQ.d.ts +201 -0
  54. package/dist/index-CXJlvmB4.d.cts +63 -0
  55. package/dist/index-CXJlvmB4.d.ts +63 -0
  56. package/dist/{index-BNP7Bpu5.d.ts → index-D1GHEM3c.d.ts} +32 -3
  57. package/dist/index-DFstwgGK.d.cts +65 -0
  58. package/dist/{index-DrCOh0Vh.d.cts → index-DGkgRnZp.d.cts} +2 -2
  59. package/dist/{index-CtydPNxA.d.cts → index-DLJso7Vs.d.cts} +32 -3
  60. package/dist/{index-BN0iWhuw.d.cts → index-DWOLZNlz.d.cts} +2 -2
  61. package/dist/{index-BjkOUQcQ.d.cts → index-DfkIz7i0.d.cts} +18 -4
  62. package/dist/{index-EKU_G1G5.d.ts → index-DjC_gmnL.d.ts} +18 -4
  63. package/dist/index-DnHQ2uT2.d.cts +104 -0
  64. package/dist/{index-CdfgEZwh.d.cts → index-DrmS802s.d.cts} +356 -130
  65. package/dist/index-Dv-Mek07.d.cts +84 -0
  66. package/dist/index-Dv-Mek07.d.ts +84 -0
  67. package/dist/{index-DHxi-QPC.d.cts → index-icIH5e5V.d.cts} +3 -3
  68. package/dist/{index-mddpvrRX.d.ts → index-laE3_wm9.d.cts} +9 -1
  69. package/dist/{index-BwwpAlk2.d.ts → index-lsuS2R1T.d.ts} +356 -130
  70. package/dist/{index-DImN9PWc.d.ts → index-mEEB7An6.d.ts} +1 -1
  71. package/dist/{index-C2YLoFVl.d.ts → index-rTZOdMRE.d.ts} +2 -2
  72. package/dist/index.cjs +20496 -217
  73. package/dist/index.cjs.map +1 -1
  74. package/dist/index.d.cts +278 -22
  75. package/dist/index.d.ts +278 -22
  76. package/dist/index.mjs +20489 -220
  77. package/dist/index.mjs.map +1 -1
  78. package/dist/lane/index.cjs +891 -72
  79. package/dist/lane/index.cjs.map +1 -1
  80. package/dist/lane/index.mjs +892 -73
  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 +38 -18
  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 +38 -19
  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-BbY_H_f6.d.ts +0 -99
  126. package/dist/index-XsMsVc3O.d.cts +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,34 @@ var init_srp = __esm({
1779
1780
  };
1780
1781
  }
1781
1782
  });
1783
+
1784
+ // src/client/apple-session.ts
1785
+ var apple_session_exports = {};
1786
+ __export(apple_session_exports, {
1787
+ ProgramLicenseAgreementError: () => ProgramLicenseAgreementError,
1788
+ appleJson: () => appleJson,
1789
+ beginLogin: () => beginLogin,
1790
+ checkPortalResponse: () => checkPortalResponse,
1791
+ fetchServiceKey: () => fetchServiceKey,
1792
+ loginWithAppleId: () => loginWithAppleId,
1793
+ requestSmsCode: () => requestSmsCode,
1794
+ serviceKeyFromLocation: () => serviceKeyFromLocation,
1795
+ submitTwoFactorCode: () => submitTwoFactorCode
1796
+ });
1797
+ async function appleJson(res, what) {
1798
+ const text = await res.text();
1799
+ try {
1800
+ return JSON.parse(text);
1801
+ } catch {
1802
+ const contentType = (res.headers.get("content-type") ?? "").split(";")[0].trim();
1803
+ const title = /<title>([^<]{1,120})<\/title>/i.exec(text)?.[1]?.trim();
1804
+ const looksHtml = /^\s*<(!doctype|html)/i.test(text) || contentType === "text/html";
1805
+ const page = looksHtml ? "an HTML page" : contentType ? `a ${contentType} response` : "a non-JSON response";
1806
+ throw new Error(
1807
+ `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.`
1808
+ );
1809
+ }
1810
+ }
1782
1811
  function checkPortalResponse(body, session) {
1783
1812
  if (!body || typeof body !== "object") return;
1784
1813
  if (body.resultCode === 4100) {
@@ -1855,7 +1884,7 @@ async function beginLogin(config) {
1855
1884
  }
1856
1885
  async function fetchTwoFactorChallenge(pending) {
1857
1886
  const r = await fetch(`${AUTH_BASE}`, { headers: buildAuthHeadersFromPending(pending) });
1858
- const body = await r.json();
1887
+ const body = await appleJson(r, "two-factor challenge");
1859
1888
  const trustedPhoneNumbers = (body.trustedPhoneNumbers ?? []).map((p) => ({
1860
1889
  id: p.id,
1861
1890
  numberWithDialCode: p.numberWithDialCode,
@@ -1876,7 +1905,8 @@ async function requestSmsCode(input) {
1876
1905
  body: JSON.stringify({ phoneNumber: { id: input.phoneId }, mode: input.mode ?? "sms" })
1877
1906
  });
1878
1907
  }
1879
- async function submitTwoFactorCode(input) {
1908
+ async function submitTwoFactorCode(rawInput) {
1909
+ const input = { ...rawInput, code: rawInput.code.replace(/[\s\u00a0]+/g, "") };
1880
1910
  const log = input.onLog ?? (() => {
1881
1911
  });
1882
1912
  let path;
@@ -1988,9 +2018,12 @@ async function doSirp(user, password, serviceKey, cookieStr, hashcash) {
1988
2018
  body: JSON.stringify({ a: aBase64, accountName: user, protocols: ["s2k", "s2k_fo"] }),
1989
2019
  redirect: "manual"
1990
2020
  });
1991
- const initBody = await initRes.json();
2021
+ const initBody = await appleJson(initRes, "sign-in init");
1992
2022
  if (initBody.serviceErrors) throw new Error(`Apple auth error: ${JSON.stringify(initBody.serviceErrors)}`);
1993
2023
  const { iteration: iterations, salt: saltB64, b: bB64, c, protocol } = initBody;
2024
+ if (!iterations || !saltB64 || !bB64 || !c || !protocol) {
2025
+ throw new Error(`Apple's sign-in init answered without a challenge (HTTP ${initRes.status}): ${JSON.stringify(initBody).slice(0, 300)}`);
2026
+ }
1994
2027
  const salt = Buffer.from(saltB64, "base64");
1995
2028
  const b = Buffer.from(bB64, "base64");
1996
2029
  const encryptedPassword = applePbkdf2(password, salt, iterations, 32, protocol);
@@ -2033,7 +2066,7 @@ async function fetchOlympusSession(session) {
2033
2066
  const cookieStr = Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join("; ");
2034
2067
  const res = await fetch(OLYMPUS_SESSION, { headers: { Cookie: cookieStr, Accept: "application/json" } });
2035
2068
  if (res.ok) {
2036
- const body = await res.json();
2069
+ const body = await appleJson(res, "session lookup");
2037
2070
  if (body?.user?.emailAddress) session.userEmail = body.user.emailAddress;
2038
2071
  if (body?.provider?.providerId) session.teamId = String(body.provider.providerId);
2039
2072
  }
@@ -2045,7 +2078,7 @@ async function fetchContractMessages(session) {
2045
2078
  headers: { Cookie: cookieStr, Accept: "application/json" }
2046
2079
  });
2047
2080
  if (!res.ok) return [];
2048
- const body = await res.json();
2081
+ const body = await appleJson(res, "agreements lookup");
2049
2082
  return (body ?? []).map((m) => ({
2050
2083
  id: m.id ?? "",
2051
2084
  group: m.group ?? "",
@@ -2056,10 +2089,22 @@ async function fetchContractMessages(session) {
2056
2089
  return [];
2057
2090
  }
2058
2091
  }
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");
2092
+ function serviceKeyFromLocation(location) {
2093
+ if (!location) return null;
2094
+ try {
2095
+ const key = new URL(location).searchParams.get("widgetKey")?.trim();
2096
+ return key && /^[0-9a-f]{16,}$/i.test(key) ? key : null;
2097
+ } catch {
2098
+ return null;
2099
+ }
2100
+ }
2101
+ async function fetchServiceKey(fetchImpl = fetch) {
2102
+ const head = await fetchImpl(LOGOUT_URL, { method: "HEAD", redirect: "manual" });
2103
+ const fromRedirect = serviceKeyFromLocation(head.headers.get("location"));
2104
+ if (fromRedirect) return fromRedirect;
2105
+ const res = await fetchImpl(WIDGET_KEY_URL);
2106
+ const body = await appleJson(res, "service key request");
2107
+ 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
2108
  return body.authServiceKey;
2064
2109
  }
2065
2110
  function buildAuthHeaders(session) {
@@ -2107,7 +2152,7 @@ async function defaultPrompt(message) {
2107
2152
  });
2108
2153
  });
2109
2154
  }
2110
- var AUTH_BASE, OLYMPUS_SESSION, WIDGET_KEY_URL, ProgramLicenseAgreementError;
2155
+ var AUTH_BASE, OLYMPUS_SESSION, WIDGET_KEY_URL, ProgramLicenseAgreementError, LOGOUT_URL;
2111
2156
  var init_apple_session = __esm({
2112
2157
  "src/client/apple-session.ts"() {
2113
2158
  init_srp();
@@ -2122,6 +2167,7 @@ var init_apple_session = __esm({
2122
2167
  }
2123
2168
  agreements;
2124
2169
  };
2170
+ LOGOUT_URL = "https://appstoreconnect.apple.com/logout";
2125
2171
  }
2126
2172
  });
2127
2173
 
@@ -2438,9 +2484,10 @@ async function uploadWithTransporter(options) {
2438
2484
  const transporterBin = await resolveTransporter();
2439
2485
  const platform = options.platform ?? "ios";
2440
2486
  const timeout = options.timeout ?? 18e5;
2441
- const keyDir = await promises.mkdtemp(path.join(os.tmpdir(), "fastnode-itms-"));
2487
+ const keyDir = path.join(os.homedir(), ".appstoreconnect", "private_keys");
2488
+ await promises.mkdir(keyDir, { recursive: true });
2442
2489
  const keyPath = path.join(keyDir, `AuthKey_${options.apiKeyId}.p8`);
2443
- await promises.writeFile(keyPath, options.apiPrivateKey);
2490
+ await promises.writeFile(keyPath, options.apiPrivateKey, { mode: 384 });
2444
2491
  return new Promise((resolve4) => {
2445
2492
  const args = [
2446
2493
  "-m",
@@ -2456,6 +2503,9 @@ async function uploadWithTransporter(options) {
2456
2503
  "-WONoPause",
2457
2504
  "true"
2458
2505
  ];
2506
+ if (options.assetDescriptionPath) {
2507
+ args.push("-assetDescription", options.assetDescriptionPath);
2508
+ }
2459
2509
  if (platform !== "ios") {
2460
2510
  args.push("-bundleType", platform);
2461
2511
  }
@@ -2479,13 +2529,13 @@ async function uploadWithTransporter(options) {
2479
2529
  child.stderr.on("data", onData);
2480
2530
  child.on("close", async (code) => {
2481
2531
  clearTimeout(timer);
2482
- await promises.rm(keyDir, { recursive: true, force: true }).catch(() => {
2532
+ await promises.rm(keyPath, { force: true }).catch(() => {
2483
2533
  });
2484
2534
  resolve4({ success: code === 0, output, backend: "transporter" });
2485
2535
  });
2486
2536
  child.on("error", async (err) => {
2487
2537
  clearTimeout(timer);
2488
- await promises.rm(keyDir, { recursive: true, force: true }).catch(() => {
2538
+ await promises.rm(keyPath, { force: true }).catch(() => {
2489
2539
  });
2490
2540
  resolve4({
2491
2541
  success: false,
@@ -2777,6 +2827,311 @@ var init_metadata = __esm({
2777
2827
  };
2778
2828
  }
2779
2829
  });
2830
+ function categoryFor(placementType) {
2831
+ return placementType === "APP_SCREENSHOT" || placementType === "IMESSAGE_APP_SCREENSHOT" || placementType === "APP_PREVIEW" ? "APP_SCREENSHOTS_AND_PREVIEWS" : "CREATIVE_ASSETS";
2832
+ }
2833
+ function readImageSize(data) {
2834
+ if (data.length >= 26 && data.readUInt32BE(0) === 2303741511) {
2835
+ const colorType = data[25];
2836
+ return {
2837
+ width: data.readUInt32BE(16),
2838
+ height: data.readUInt32BE(20),
2839
+ alpha: colorType === 4 || colorType === 6
2840
+ };
2841
+ }
2842
+ if (data.length >= 4 && data[0] === 255 && data[1] === 216) {
2843
+ let offset = 2;
2844
+ while (offset + 9 < data.length) {
2845
+ if (data[offset] !== 255) return null;
2846
+ const marker = data[offset + 1];
2847
+ const length = data.readUInt16BE(offset + 2);
2848
+ const isSof = marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204;
2849
+ if (isSof) {
2850
+ return { height: data.readUInt16BE(offset + 5), width: data.readUInt16BE(offset + 7), alpha: false };
2851
+ }
2852
+ offset += 2 + length;
2853
+ }
2854
+ }
2855
+ return null;
2856
+ }
2857
+ function fits(spec, width, height) {
2858
+ const d = spec.dimensions;
2859
+ return width >= d.minWidth && width <= d.maxWidth && height >= d.minHeight && height <= d.maxHeight;
2860
+ }
2861
+ function groupsForFeature(refData, feature, placementType) {
2862
+ const mappings = refData.placementTypes?.find((p) => p.placementTypeId === placementType)?.specMappings ?? [];
2863
+ const all = mappings.map((m) => m.placementGroupId);
2864
+ const policy = refData.features?.find((f) => f.featureId === feature)?.placementPolicies?.find((p) => p.placementType === placementType);
2865
+ const limited = policy?.groupLimits?.flatMap((l) => l.groupIds);
2866
+ return limited?.length ? all.filter((g) => limited.includes(g)) : all;
2867
+ }
2868
+ function maxCountFor(refData, feature, placementType, placementGroup) {
2869
+ return refData.features?.find((f) => f.featureId === feature)?.placementPolicies?.find((p) => p.placementType === placementType)?.groupLimits?.find((l) => l.groupIds.includes(placementGroup))?.maxCount;
2870
+ }
2871
+ function resolveImageGroups(refData, feature, placementType, size) {
2872
+ const specs = (refData.imageSpecs ?? []).filter(
2873
+ (s) => s.compatiblePlacementTypes.includes(placementType) && fits(s, size.width, size.height)
2874
+ );
2875
+ const specIds = new Set(specs.map((s) => s.specId));
2876
+ const allowed = new Set(groupsForFeature(refData, feature, placementType));
2877
+ const mappings = refData.placementTypes?.find((p) => p.placementTypeId === placementType)?.specMappings ?? [];
2878
+ const groups = mappings.filter((m) => allowed.has(m.placementGroupId) && m.specs.some((id) => specIds.has(id))).map((m) => m.placementGroupId);
2879
+ return { groups: [...new Set(groups)], spec: specs[0] };
2880
+ }
2881
+ async function runUploadOperations(ops, data) {
2882
+ for (const op of ops) {
2883
+ const headers = {};
2884
+ for (const h of op.requestHeaders) headers[h.name] = h.value;
2885
+ const res = await fetch(op.url, {
2886
+ method: op.method,
2887
+ headers,
2888
+ body: data.subarray(op.offset, op.offset + op.length)
2889
+ });
2890
+ if (!res.ok) {
2891
+ throw new Error(`Upload operation failed: ${op.method} ${new URL(op.url).host} \u2192 HTTP ${res.status}`);
2892
+ }
2893
+ }
2894
+ }
2895
+ async function uploadLibraryAsset(options) {
2896
+ const { client, libraryId, filePath, category } = options;
2897
+ const data = await promises.readFile(filePath);
2898
+ const fileName = path.basename(filePath);
2899
+ const kind = VIDEO_EXTENSIONS.includes(path.extname(filePath).toLowerCase()) ? "video" : "image";
2900
+ const hash = crypto.createHash("sha256").update(data).digest("hex").slice(0, 12);
2901
+ const referenceName = `${options.referenceName ?? fileName} #${hash}`;
2902
+ const media = kind === "image" ? client.appAssetLibraryImages : client.appAssetLibraryVideos;
2903
+ const filter = { filter: { referenceName } };
2904
+ const existing = kind === "image" ? await client.appAssetLibraries.listImages(libraryId, filter) : await client.appAssetLibraries.listVideos(libraryId, filter);
2905
+ const reusable = existing.find(
2906
+ (a) => a.attributes.category === category && a.attributes.state && READY_STATES.includes(a.attributes.state)
2907
+ );
2908
+ if (reusable) {
2909
+ return { kind, id: reusable.id, state: reusable.attributes.state, reused: true };
2910
+ }
2911
+ const reserved = kind === "image" ? await client.appAssetLibraryImages.create({ libraryId, fileName, fileSize: data.length, category, referenceName }) : await client.appAssetLibraryVideos.create({
2912
+ libraryId,
2913
+ fileName,
2914
+ fileSize: data.length,
2915
+ category,
2916
+ referenceName,
2917
+ previewFrameTimeCode: options.previewFrameTimeCode
2918
+ });
2919
+ await runUploadOperations(reserved.attributes.uploadOperations ?? [], data);
2920
+ await media.update(reserved.id, { uploaded: true });
2921
+ options.log?.(`uploaded ${fileName} (${kind}, ${data.length} bytes)`);
2922
+ const state = await waitForAssetReady(
2923
+ () => media.get(reserved.id),
2924
+ options.pollIntervalMs ?? 3e3,
2925
+ options.timeoutMs ?? (kind === "video" ? 30 * 6e4 : 5 * 6e4)
2926
+ );
2927
+ return { kind, id: reserved.id, state, reused: false };
2928
+ }
2929
+ async function waitForAssetReady(get, intervalMs, timeoutMs) {
2930
+ const deadline = Date.now() + timeoutMs;
2931
+ for (; ; ) {
2932
+ const asset = await get();
2933
+ const state = asset.attributes.state;
2934
+ if (state === "FAILED") {
2935
+ const why = (asset.attributes.stateDetails ?? []).map((d) => d.message ?? d.code).filter(Boolean).join("; ");
2936
+ throw new Error(`Asset processing failed${why ? `: ${why}` : ""}`);
2937
+ }
2938
+ if (state && READY_STATES.includes(state)) return state;
2939
+ if (Date.now() > deadline) throw new Error(`Asset still ${state ?? "unknown"} after ${timeoutMs / 1e3}s`);
2940
+ await new Promise((r) => setTimeout(r, intervalMs));
2941
+ }
2942
+ }
2943
+ async function listFiles(dir) {
2944
+ return (await promises.readdir(dir)).filter((f) => !f.startsWith(".")).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }));
2945
+ }
2946
+ async function isDir(path) {
2947
+ return (await promises.stat(path)).isDirectory();
2948
+ }
2949
+ async function planLocale(localeDir, refData, feature, folders, warn) {
2950
+ const planned = [];
2951
+ for (const [folder, placementType] of Object.entries(folders)) {
2952
+ const typeDir = path.join(localeDir, folder);
2953
+ if (!fs.existsSync(typeDir)) continue;
2954
+ const known = new Set(groupsForFeature(refData, feature, placementType));
2955
+ const entries = [];
2956
+ for (const entry of await listFiles(typeDir)) {
2957
+ const full = path.join(typeDir, entry);
2958
+ if (await isDir(full)) {
2959
+ if (!known.has(entry)) {
2960
+ warn(`${path.relative(localeDir, full)}: "${entry}" is not a ${placementType} placement group for ${feature} \u2014 skipped`);
2961
+ continue;
2962
+ }
2963
+ for (const f of await listFiles(full)) entries.push({ filePath: path.join(full, f), group: entry });
2964
+ } else {
2965
+ entries.push({ filePath: full });
2966
+ }
2967
+ }
2968
+ for (const { filePath, group } of entries) {
2969
+ const ext = path.extname(filePath).toLowerCase();
2970
+ const isVideo = VIDEO_EXTENSIONS.includes(ext);
2971
+ if (!isVideo && !IMAGE_EXTENSIONS.includes(ext)) continue;
2972
+ const needsVideo = placementType === "APP_PREVIEW";
2973
+ const needsImage = placementType === "APP_SCREENSHOT" || placementType === "IMESSAGE_APP_SCREENSHOT";
2974
+ if (needsVideo && !isVideo || needsImage && isVideo) {
2975
+ warn(`${path.relative(localeDir, filePath)}: ${isVideo ? "video" : "image"} cannot be a ${placementType} \u2014 skipped`);
2976
+ continue;
2977
+ }
2978
+ if (group) {
2979
+ planned.push({ filePath, placementType, groups: [group] });
2980
+ continue;
2981
+ }
2982
+ if (isVideo) {
2983
+ if (known.size === 1) planned.push({ filePath, placementType, groups: [...known] });
2984
+ else warn(`${path.relative(localeDir, filePath)}: put videos in a placement-group folder (one of ${[...known].join(", ")}) \u2014 skipped`);
2985
+ continue;
2986
+ }
2987
+ const size = readImageSize(await promises.readFile(filePath));
2988
+ if (!size) {
2989
+ warn(`${path.relative(localeDir, filePath)}: could not read image size \u2014 skipped`);
2990
+ continue;
2991
+ }
2992
+ const { groups, spec } = resolveImageGroups(refData, feature, placementType, size);
2993
+ if (!groups.length) {
2994
+ warn(`${path.relative(localeDir, filePath)}: ${size.width}\xD7${size.height} matches no ${placementType} spec \u2014 skipped`);
2995
+ continue;
2996
+ }
2997
+ if (size.alpha && spec && spec.alphaAllowed === false) {
2998
+ warn(`${path.relative(localeDir, filePath)}: has an alpha channel, which Apple rejects for this spec`);
2999
+ }
3000
+ planned.push({ filePath, placementType, groups: groups.slice(0, 1) });
3001
+ }
3002
+ }
3003
+ return planned;
3004
+ }
3005
+ async function uploadAssetLibrary(options) {
3006
+ const { client } = options;
3007
+ const log = options.log ?? (() => {
3008
+ });
3009
+ const feature = "APP_STORE_VERSIONS";
3010
+ const result = { uploaded: 0, reused: 0, placed: 0, removed: 0, warnings: [] };
3011
+ const warn = (line) => {
3012
+ result.warnings.push(line);
3013
+ log(`warning: ${line}`);
3014
+ };
3015
+ const [library, refData, localizations] = await Promise.all([
3016
+ client.appAssetLibraries.getForApp(options.appId),
3017
+ client.appAssetLibraries.refData({ features: [feature] }),
3018
+ client.appStoreVersions.getLocalizations(options.versionId)
3019
+ ]);
3020
+ const folders = { ...options.folders ?? ASSET_FOLDERS };
3021
+ if (options.rootPlacementType) folders["."] = options.rootPlacementType;
3022
+ const uploads = /* @__PURE__ */ new Map();
3023
+ const uploadOnce = (filePath, placementType) => {
3024
+ const key = `${filePath}|${categoryFor(placementType)}`;
3025
+ if (!uploads.has(key)) {
3026
+ uploads.set(
3027
+ key,
3028
+ uploadLibraryAsset({
3029
+ client,
3030
+ libraryId: library.id,
3031
+ filePath,
3032
+ category: categoryFor(placementType),
3033
+ referenceName: path.relative(options.assetsPath, filePath),
3034
+ log
3035
+ }).then((a) => {
3036
+ if (a.reused) result.reused++;
3037
+ else result.uploaded++;
3038
+ return a;
3039
+ })
3040
+ );
3041
+ }
3042
+ return uploads.get(key);
3043
+ };
3044
+ for (const loc of localizations) {
3045
+ const localeDir = path.join(options.assetsPath, loc.attributes.locale);
3046
+ if (!fs.existsSync(localeDir)) continue;
3047
+ const target = { kind: "appStoreVersionLocalization", id: loc.id };
3048
+ const plan = await planLocale(localeDir, refData, feature, folders, warn);
3049
+ const slots = /* @__PURE__ */ new Map();
3050
+ for (const p of plan) {
3051
+ for (const group of p.groups) {
3052
+ const key = `${p.placementType}|${group}`;
3053
+ if (!slots.has(key)) slots.set(key, { placementType: p.placementType, group, files: [] });
3054
+ slots.get(key).files.push(p.filePath);
3055
+ }
3056
+ }
3057
+ for (const slot of slots.values()) {
3058
+ const where = `${loc.attributes.locale} ${slot.placementType} ${slot.group}`;
3059
+ const max = maxCountFor(refData, feature, slot.placementType, slot.group);
3060
+ if (max !== void 0 && slot.files.length > max) {
3061
+ warn(`${where}: ${slot.files.length} files but Apple allows ${max} \u2014 keeping the first ${max}`);
3062
+ slot.files = slot.files.slice(0, max);
3063
+ }
3064
+ const existing = await client.appAssetLibraryPlacements.listForTarget(target, {
3065
+ placementType: slot.placementType,
3066
+ placementGroup: slot.group,
3067
+ include: "image,video"
3068
+ });
3069
+ if (options.overwrite) {
3070
+ for (const p of existing) {
3071
+ await client.appAssetLibraryPlacements.delete(p.id);
3072
+ result.removed++;
3073
+ }
3074
+ }
3075
+ const kept = options.overwrite ? [] : existing;
3076
+ const keptAssetIds = new Set(kept.map((p) => placementAssetId(p)).filter(Boolean));
3077
+ const ours = [];
3078
+ for (const filePath of slot.files) {
3079
+ const asset = await uploadOnce(filePath, slot.placementType);
3080
+ if (keptAssetIds.has(asset.id)) continue;
3081
+ const placement = await client.appAssetLibraryPlacements.create({
3082
+ placementType: slot.placementType,
3083
+ placementGroup: slot.group,
3084
+ ...asset.kind === "image" ? { imageId: asset.id } : { videoId: asset.id },
3085
+ target
3086
+ });
3087
+ ours.push(placement.id);
3088
+ result.placed++;
3089
+ }
3090
+ log(`${where}: placed ${ours.length}${kept.length ? `, kept ${kept.length}` : ""}`);
3091
+ const orderable = slot.placementType === "APP_SCREENSHOT" || slot.placementType === "APP_PREVIEW" || slot.placementType === "IMESSAGE_APP_SCREENSHOT";
3092
+ if (orderable && ours.length + kept.length > 1) {
3093
+ await client.appAssetLibraryPlacements.order({
3094
+ placementGroup: slot.group,
3095
+ placementIds: [...ours, ...kept.map((p) => p.id)],
3096
+ target
3097
+ });
3098
+ }
3099
+ }
3100
+ }
3101
+ return result;
3102
+ }
3103
+ function placementAssetId(p) {
3104
+ for (const rel of ["image", "video"]) {
3105
+ const data = p.relationships?.[rel]?.data;
3106
+ if (data?.id) return data.id;
3107
+ }
3108
+ return void 0;
3109
+ }
3110
+ var ASSET_FOLDERS, IMAGE_EXTENSIONS, VIDEO_EXTENSIONS, READY_STATES;
3111
+ var init_assets = __esm({
3112
+ "src/deliver/assets.ts"() {
3113
+ ASSET_FOLDERS = {
3114
+ screenshots: "APP_SCREENSHOT",
3115
+ imessage_screenshots: "IMESSAGE_APP_SCREENSHOT",
3116
+ previews: "APP_PREVIEW",
3117
+ header: "PRODUCT_PAGE_HEADER_ASSET",
3118
+ search_results: "APP_STORE_SEARCH_RESULTS_ASSET",
3119
+ event_card: "EVENT_CARD_ASSET",
3120
+ event_details: "EVENT_DETAILS_PAGE_ASSET"
3121
+ };
3122
+ IMAGE_EXTENSIONS = [".png", ".jpg", ".jpeg"];
3123
+ VIDEO_EXTENSIONS = [".mp4", ".mov", ".m4v"];
3124
+ READY_STATES = [
3125
+ "COMPLETE",
3126
+ "PREPARE_FOR_SUBMISSION",
3127
+ "READY_FOR_REVIEW",
3128
+ "WAITING_FOR_REVIEW",
3129
+ "IN_REVIEW",
3130
+ "ACCEPTED",
3131
+ "APPROVED"
3132
+ ];
3133
+ }
3134
+ });
2780
3135
 
2781
3136
  // src/xcode/upload.ts
2782
3137
  var upload_exports = {};
@@ -2842,8 +3197,14 @@ var init_upload = __esm({
2842
3197
  // src/deliver/index.ts
2843
3198
  var deliver_exports = {};
2844
3199
  __export(deliver_exports, {
3200
+ ASSET_FOLDERS: () => ASSET_FOLDERS,
3201
+ categoryFor: () => categoryFor,
2845
3202
  loadMetadata: () => loadMetadata,
3203
+ readImageSize: () => readImageSize,
3204
+ resolveImageGroups: () => resolveImageGroups,
2846
3205
  submitForReview: () => submitForReview,
3206
+ uploadAssetLibrary: () => uploadAssetLibrary,
3207
+ uploadLibraryAsset: () => uploadLibraryAsset,
2847
3208
  uploadMetadata: () => uploadMetadata,
2848
3209
  uploadScreenshots: () => uploadScreenshots,
2849
3210
  uploadToAppStore: () => uploadToAppStore
@@ -2882,11 +3243,36 @@ async function uploadToAppStore(options) {
2882
3243
  copyright: options.copyright
2883
3244
  });
2884
3245
  }
3246
+ const log = (line) => options.onOutput?.(`${line}
3247
+ `);
2885
3248
  if (!options.skipScreenshots && options.screenshotsPath && fs.existsSync(options.screenshotsPath)) {
2886
- await uploadScreenshots({
3249
+ if (options.useAssetLibrary) {
3250
+ await uploadAssetLibrary({
3251
+ client: options.client,
3252
+ appId: app.id,
3253
+ versionId: version.id,
3254
+ assetsPath: options.screenshotsPath,
3255
+ folders: {},
3256
+ rootPlacementType: "APP_SCREENSHOT",
3257
+ overwrite: options.overwriteAssets,
3258
+ log
3259
+ });
3260
+ } else {
3261
+ await uploadScreenshots({
3262
+ client: options.client,
3263
+ versionId: version.id,
3264
+ screenshotsPath: options.screenshotsPath
3265
+ });
3266
+ }
3267
+ }
3268
+ if (!options.skipAssets && options.assetsPath && fs.existsSync(options.assetsPath)) {
3269
+ await uploadAssetLibrary({
2887
3270
  client: options.client,
3271
+ appId: app.id,
2888
3272
  versionId: version.id,
2889
- screenshotsPath: options.screenshotsPath
3273
+ assetsPath: options.assetsPath,
3274
+ overwrite: options.overwriteAssets,
3275
+ log
2890
3276
  });
2891
3277
  }
2892
3278
  if (!options.skipBinaryUpload) {
@@ -2971,17 +3357,7 @@ async function uploadScreenshots(options) {
2971
3357
  fileName: file,
2972
3358
  fileSize: fileData.length
2973
3359
  });
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
- }
3360
+ await runUploadOperations(reserved.attributes.uploadOperations ?? [], fileData);
2985
3361
  await options.client.appScreenshots.commitScreenshot(reserved.id, {
2986
3362
  sourceFileChecksum: checksum,
2987
3363
  uploaded: true
@@ -3001,7 +3377,9 @@ function detectDisplayType(filename) {
3001
3377
  var init_deliver = __esm({
3002
3378
  "src/deliver/index.ts"() {
3003
3379
  init_metadata();
3380
+ init_assets();
3004
3381
  init_metadata();
3382
+ init_assets();
3005
3383
  }
3006
3384
  });
3007
3385
 
@@ -3010,6 +3388,7 @@ var produce_exports = {};
3010
3388
  __export(produce_exports, {
3011
3389
  SERVICE_NAME_TO_CAPABILITY: () => SERVICE_NAME_TO_CAPABILITY,
3012
3390
  addCapability: () => addCapability,
3391
+ associateAppGroups: () => associateAppGroups,
3013
3392
  buildCapabilitySettings: () => buildCapabilitySettings,
3014
3393
  createApp: () => createApp,
3015
3394
  enableServices: () => enableServices,
@@ -3104,6 +3483,30 @@ async function enableServices(options) {
3104
3483
  });
3105
3484
  }
3106
3485
  }
3486
+ async function associateAppGroups(options) {
3487
+ if (!options.appGroups?.length) return;
3488
+ await options.portalClient.enableAppGroups(options.bundleIdId).catch(() => {
3489
+ });
3490
+ const existing = await options.portalClient.listAppGroups();
3491
+ const appGroupIds = [];
3492
+ for (const group of options.appGroups) {
3493
+ let record = existing.find((ag) => ag.identifier === group.identifier);
3494
+ if (!record) {
3495
+ record = await options.portalClient.createAppGroup({
3496
+ name: group.name,
3497
+ identifier: group.identifier
3498
+ });
3499
+ }
3500
+ const id = record?.applicationGroup ?? record?.applicationGroupId ?? record?.appGroupId ?? record?.id;
3501
+ if (id) appGroupIds.push(id);
3502
+ }
3503
+ if (appGroupIds.length) {
3504
+ await options.portalClient.assignAppGroups({
3505
+ appIdId: options.bundleIdId,
3506
+ appGroupIds
3507
+ });
3508
+ }
3509
+ }
3107
3510
  async function createApp(options) {
3108
3511
  const existing = await options.client.apps.findByBundleId(options.bundleIdentifier);
3109
3512
  if (existing) return existing;
@@ -3122,25 +3525,20 @@ async function createApp(options) {
3122
3525
  });
3123
3526
  }
3124
3527
  }
3125
- const sku = options.sku ?? `${options.bundleIdentifier}.${Date.now()}`;
3126
- const primaryLocale = options.primaryLocale ?? "en-US";
3127
- const response = await options.client.http.post("/v1/apps", {
3128
- data: {
3129
- type: "apps",
3130
- attributes: {
3131
- bundleId: options.bundleIdentifier,
3132
- name: options.appName,
3133
- sku,
3134
- primaryLocale
3135
- },
3136
- relationships: {
3137
- bundleId: {
3138
- data: { type: "bundleIds", id: bundleId.id }
3139
- }
3140
- }
3141
- }
3528
+ if (options.appGroups?.length && options.portalClient) {
3529
+ await associateAppGroups({
3530
+ portalClient: options.portalClient,
3531
+ bundleIdId: bundleId.id,
3532
+ appGroups: options.appGroups
3533
+ });
3534
+ }
3535
+ return options.client.apps.create({
3536
+ bundleIdentifier: options.bundleIdentifier,
3537
+ bundleIdId: bundleId.id,
3538
+ appName: options.appName,
3539
+ sku: options.sku ?? `${options.bundleIdentifier}.${Date.now()}`,
3540
+ primaryLocale: options.primaryLocale
3142
3541
  });
3143
- return response.data;
3144
3542
  }
3145
3543
  var SERVICE_NAME_TO_CAPABILITY;
3146
3544
  var init_produce = __esm({
@@ -3943,10 +4341,10 @@ async function parseTestResults(options) {
3943
4341
  } else if (inputPath.endsWith(".plist")) {
3944
4342
  testSuites = parsePlistResults(inputPath);
3945
4343
  } else {
3946
- const stat6 = await import('fs').then(
4344
+ const stat7 = await import('fs').then(
3947
4345
  (fs) => fs.statSync(inputPath)
3948
4346
  );
3949
- if (stat6.isDirectory()) {
4347
+ if (stat7.isDirectory()) {
3950
4348
  requireMacOS("xcresult parsing");
3951
4349
  testSuites = await parseXCResult(inputPath);
3952
4350
  } else {
@@ -4934,6 +5332,43 @@ var init_apps = __esm({
4934
5332
  });
4935
5333
  return results[0] ?? null;
4936
5334
  }
5335
+ /**
5336
+ * Create an App Store Connect **app record** (the thing that shows up
5337
+ * under "My Apps" and that TestFlight / App Store uploads require).
5338
+ *
5339
+ * This is distinct from registering an App ID in the Developer Portal —
5340
+ * you need BOTH. The Developer Portal App ID unlocks provisioning
5341
+ * profiles and entitlements; the App Store Connect app record unlocks
5342
+ * uploads. `upload_to_testflight` fails with "Couldn't find app
5343
+ * 'com.foo.bar' on the account…" when this record is missing.
5344
+ *
5345
+ * `bundleIdId` is the REST API primary key of the bundle id resource
5346
+ * (not the identifier string) — fetch it via `bundleIds.find(identifier)`
5347
+ * or from the response of `bundleIds.create()`.
5348
+ *
5349
+ * `sku` is an arbitrary unique string Apple uses to identify the app in
5350
+ * their internal records. If you don't have a meaningful one, pass the
5351
+ * bundle identifier itself or append a timestamp for uniqueness.
5352
+ */
5353
+ async create(params) {
5354
+ const response = await this.http.post("/v1/apps", {
5355
+ data: {
5356
+ type: "apps",
5357
+ attributes: {
5358
+ bundleId: params.bundleIdentifier,
5359
+ name: params.appName,
5360
+ sku: params.sku,
5361
+ primaryLocale: params.primaryLocale ?? "en-US"
5362
+ },
5363
+ relationships: {
5364
+ bundleId: {
5365
+ data: { type: "bundleIds", id: params.bundleIdId }
5366
+ }
5367
+ }
5368
+ }
5369
+ });
5370
+ return response.data;
5371
+ }
4937
5372
  };
4938
5373
  }
4939
5374
  });
@@ -6086,9 +6521,22 @@ var init_pre_release_versions = __esm({
6086
6521
  });
6087
6522
 
6088
6523
  // src/resources/review-submission-items.ts
6089
- var ReviewSubmissionItemsResource;
6524
+ var ITEM_RELATIONSHIPS, ReviewSubmissionItemsResource;
6090
6525
  var init_review_submission_items = __esm({
6091
6526
  "src/resources/review-submission-items.ts"() {
6527
+ ITEM_RELATIONSHIPS = {
6528
+ appStoreVersionId: ["appStoreVersion", "appStoreVersions"],
6529
+ appCustomProductPageVersionId: ["appCustomProductPageVersion", "appCustomProductPageVersions"],
6530
+ appStoreVersionExperimentId: ["appStoreVersionExperiment", "appStoreVersionExperiments"],
6531
+ appStoreVersionExperimentV2Id: ["appStoreVersionExperimentV2", "appStoreVersionExperiments"],
6532
+ appEventId: ["appEvent", "appEvents"],
6533
+ appAssetLibraryImageId: ["appAssetLibraryImage", "appAssetLibraryImages"],
6534
+ appAssetLibraryVideoId: ["appAssetLibraryVideo", "appAssetLibraryVideos"],
6535
+ backgroundAssetVersionId: ["backgroundAssetVersion", "backgroundAssetVersions"],
6536
+ inAppPurchaseVersionId: ["inAppPurchaseVersion", "inAppPurchaseVersions"],
6537
+ subscriptionVersionId: ["subscriptionVersion", "subscriptionVersions"],
6538
+ subscriptionGroupVersionId: ["subscriptionGroupVersion", "subscriptionGroupVersions"]
6539
+ };
6092
6540
  ReviewSubmissionItemsResource = class {
6093
6541
  constructor(http) {
6094
6542
  this.http = http;
@@ -6110,32 +6558,20 @@ var init_review_submission_items = __esm({
6110
6558
  );
6111
6559
  return response.data;
6112
6560
  }
6561
+ /**
6562
+ * Add one item to a review submission. Besides versions, asset library
6563
+ * images and videos can be submitted on their own (ASC API 4.5.1) so
6564
+ * creative assets are approved ahead of a campaign.
6565
+ */
6113
6566
  async create(params) {
6114
6567
  const relationships = {
6115
6568
  reviewSubmission: {
6116
6569
  data: { type: "reviewSubmissions", id: params.reviewSubmissionId }
6117
6570
  }
6118
6571
  };
6119
- if (params.appStoreVersionId) {
6120
- relationships.appStoreVersion = {
6121
- data: { type: "appStoreVersions", id: params.appStoreVersionId }
6122
- };
6123
- }
6124
- if (params.appCustomProductPageVersionId) {
6125
- relationships.appCustomProductPageVersion = {
6126
- data: {
6127
- type: "appCustomProductPageVersions",
6128
- id: params.appCustomProductPageVersionId
6129
- }
6130
- };
6131
- }
6132
- if (params.appStoreVersionExperimentId) {
6133
- relationships.appStoreVersionExperiment = {
6134
- data: {
6135
- type: "appStoreVersionExperiments",
6136
- id: params.appStoreVersionExperimentId
6137
- }
6138
- };
6572
+ for (const [param, [relationship, type]] of Object.entries(ITEM_RELATIONSHIPS)) {
6573
+ const id = params[param];
6574
+ if (id) relationships[relationship] = { data: { type, id } };
6139
6575
  }
6140
6576
  const response = await this.http.post(
6141
6577
  "/v1/reviewSubmissionItems",
@@ -6366,6 +6802,204 @@ var init_xcode_metrics = __esm({
6366
6802
  }
6367
6803
  });
6368
6804
 
6805
+ // src/resources/app-asset-libraries.ts
6806
+ function mergeRefData(parts) {
6807
+ const merged = {
6808
+ features: [],
6809
+ placementProfileGroups: [],
6810
+ imageSpecs: [],
6811
+ videoSpecs: [],
6812
+ placementTypes: [],
6813
+ displayClasses: []
6814
+ };
6815
+ for (const part of parts) {
6816
+ for (const key of Object.keys(merged)) {
6817
+ merged[key].push(...part[key] ?? []);
6818
+ }
6819
+ }
6820
+ return merged;
6821
+ }
6822
+ var AppAssetLibrariesResource;
6823
+ var init_app_asset_libraries = __esm({
6824
+ "src/resources/app-asset-libraries.ts"() {
6825
+ AppAssetLibrariesResource = class {
6826
+ constructor(http) {
6827
+ this.http = http;
6828
+ }
6829
+ http;
6830
+ async getForApp(appId) {
6831
+ const response = await this.http.get(
6832
+ `/v1/apps/${appId}/assetLibrary`
6833
+ );
6834
+ return response.data;
6835
+ }
6836
+ async listImages(libraryId, params = {}) {
6837
+ return this.http.all(`/v1/appAssetLibraries/${libraryId}/images`, params);
6838
+ }
6839
+ async listVideos(libraryId, params = {}) {
6840
+ return this.http.all(`/v1/appAssetLibraries/${libraryId}/videos`, params);
6841
+ }
6842
+ /**
6843
+ * Specs, placement groups and limits. Apple adds placement groups (new
6844
+ * devices) without an API version bump, so read them from here rather than
6845
+ * hard-coding display types.
6846
+ */
6847
+ async refData(params = {}) {
6848
+ const filter = {};
6849
+ if (params.features?.length) filter.features = params.features;
6850
+ if (params.placementTypes?.length) filter.placementTypes = params.placementTypes;
6851
+ if (params.placementProfileGroups?.length) filter.placementProfileGroups = params.placementProfileGroups;
6852
+ const data = await this.http.all("/v1/appAssetLibraryRefData", { filter });
6853
+ return mergeRefData(data.map((d) => d.attributes));
6854
+ }
6855
+ };
6856
+ }
6857
+ });
6858
+
6859
+ // src/resources/app-asset-library-assets.ts
6860
+ var AssetLibraryMediaResource, AppAssetLibraryImagesResource, AppAssetLibraryVideosResource;
6861
+ var init_app_asset_library_assets = __esm({
6862
+ "src/resources/app-asset-library-assets.ts"() {
6863
+ AssetLibraryMediaResource = class {
6864
+ constructor(http) {
6865
+ this.http = http;
6866
+ }
6867
+ http;
6868
+ async get(id) {
6869
+ const response = await this.http.get(`/v1/${this.type}/${id}`);
6870
+ return response.data;
6871
+ }
6872
+ async reserve(params, extra = {}) {
6873
+ const { libraryId, ...attributes } = params;
6874
+ const response = await this.http.post(`/v1/${this.type}`, {
6875
+ data: {
6876
+ type: this.type,
6877
+ attributes: { ...attributes, ...extra },
6878
+ relationships: {
6879
+ assetLibrary: { data: { type: "appAssetLibraries", id: libraryId } }
6880
+ }
6881
+ }
6882
+ });
6883
+ return response.data;
6884
+ }
6885
+ async patch(id, attributes) {
6886
+ const response = await this.http.patch(`/v1/${this.type}/${id}`, {
6887
+ data: { type: this.type, id, attributes }
6888
+ });
6889
+ return response.data;
6890
+ }
6891
+ /** Fails with 409 STATE_ERROR.ASSET_HAS_PLACEMENTS while the asset is still placed. */
6892
+ async delete(id) {
6893
+ await this.http.delete(`/v1/${this.type}/${id}`);
6894
+ }
6895
+ async listPlacements(id, params = {}) {
6896
+ return this.http.all(`/v1/${this.type}/${id}/placements`, params);
6897
+ }
6898
+ };
6899
+ AppAssetLibraryImagesResource = class extends AssetLibraryMediaResource {
6900
+ type = "appAssetLibraryImages";
6901
+ async create(params) {
6902
+ return this.reserve(params);
6903
+ }
6904
+ async update(id, attributes) {
6905
+ return this.patch(id, { ...attributes });
6906
+ }
6907
+ };
6908
+ AppAssetLibraryVideosResource = class extends AssetLibraryMediaResource {
6909
+ type = "appAssetLibraryVideos";
6910
+ async create(params) {
6911
+ const { previewFrameTimeCode, ...rest } = params;
6912
+ return this.reserve(rest, previewFrameTimeCode ? { previewFrameTimeCode } : {});
6913
+ }
6914
+ async update(id, attributes) {
6915
+ return this.patch(id, { ...attributes });
6916
+ }
6917
+ };
6918
+ }
6919
+ });
6920
+
6921
+ // src/resources/app-asset-library-placements.ts
6922
+ function targetRelationship(target) {
6923
+ return { [target.kind]: { data: { type: TARGET_TYPES[target.kind], id: target.id } } };
6924
+ }
6925
+ var TARGET_TYPES, AppAssetLibraryPlacementsResource;
6926
+ var init_app_asset_library_placements = __esm({
6927
+ "src/resources/app-asset-library-placements.ts"() {
6928
+ TARGET_TYPES = {
6929
+ appStoreVersionLocalization: "appStoreVersionLocalizations",
6930
+ appCustomProductPageLocalization: "appCustomProductPageLocalizations",
6931
+ appEventLocalization: "appEventLocalizations",
6932
+ appStoreVersionExperimentTreatmentLocalization: "appStoreVersionExperimentTreatmentLocalizations"
6933
+ };
6934
+ AppAssetLibraryPlacementsResource = class {
6935
+ constructor(http) {
6936
+ this.http = http;
6937
+ }
6938
+ http;
6939
+ /**
6940
+ * Place an image or video on one localization. Placements cannot be edited —
6941
+ * delete and recreate. The parent version/page must be editable.
6942
+ */
6943
+ async create(params) {
6944
+ if (!params.imageId === !params.videoId) {
6945
+ throw new Error("A placement needs exactly one of imageId or videoId");
6946
+ }
6947
+ const media = params.imageId ? { image: { data: { type: "appAssetLibraryImages", id: params.imageId } } } : { video: { data: { type: "appAssetLibraryVideos", id: params.videoId } } };
6948
+ const response = await this.http.post(
6949
+ "/v1/appAssetLibraryPlacements",
6950
+ {
6951
+ data: {
6952
+ type: "appAssetLibraryPlacements",
6953
+ attributes: { placementType: params.placementType, placementGroup: params.placementGroup },
6954
+ relationships: { ...media, ...targetRelationship(params.target) }
6955
+ }
6956
+ }
6957
+ );
6958
+ return response.data;
6959
+ }
6960
+ async get(id, params = {}) {
6961
+ const response = await this.http.get(
6962
+ `/v1/appAssetLibraryPlacements/${id}`,
6963
+ params.include ? { include: params.include } : void 0
6964
+ );
6965
+ return response.data;
6966
+ }
6967
+ async delete(id) {
6968
+ await this.http.delete(`/v1/appAssetLibraryPlacements/${id}`);
6969
+ }
6970
+ /** Placements on one localization, in display order by default. */
6971
+ async listForTarget(target, params = {}) {
6972
+ const filter = {};
6973
+ if (params.placementType) filter.placementType = params.placementType;
6974
+ if (params.placementGroup) filter.placementGroup = params.placementGroup;
6975
+ const listParams = { filter, include: params.include, sort: "placementGroupPosition" };
6976
+ return this.http.all(
6977
+ `/v1/${TARGET_TYPES[target.kind]}/${target.id}/placements`,
6978
+ listParams
6979
+ );
6980
+ }
6981
+ /** Set the display order of a placement group. In-app event localizations cannot be ordered. */
6982
+ async order(params) {
6983
+ if (params.target.kind === "appEventLocalization") {
6984
+ throw new Error("In-app event placements cannot be ordered");
6985
+ }
6986
+ await this.http.post("/v1/appAssetLibraryPlacementOrderingRequests", {
6987
+ data: {
6988
+ type: "appAssetLibraryPlacementOrderingRequests",
6989
+ attributes: { placementGroup: params.placementGroup },
6990
+ relationships: {
6991
+ orderedPlacements: {
6992
+ data: params.placementIds.map((id) => ({ type: "appAssetLibraryPlacements", id }))
6993
+ },
6994
+ ...targetRelationship(params.target)
6995
+ }
6996
+ }
6997
+ });
6998
+ }
6999
+ };
7000
+ }
7001
+ });
7002
+
6369
7003
  // src/client/portal.ts
6370
7004
  function pickUniqueKeyName(base, taken) {
6371
7005
  if (!taken.has(base)) return base;
@@ -6705,7 +7339,10 @@ var init_portal = __esm({
6705
7339
  }
6706
7340
  // ── Teams ──────────────────────────────────────────────────────────────
6707
7341
  async listTeams() {
6708
- const data = await this.post("account/listTeams.action", {});
7342
+ const data = await this.post("account/listTeams.action", {
7343
+ pageSize: "500",
7344
+ pageNumber: "1"
7345
+ });
6709
7346
  return data.teams ?? [];
6710
7347
  }
6711
7348
  // ── App IDs / Bundle IDs ───────────────────────────────────────────────
@@ -6719,11 +7356,12 @@ var init_portal = __esm({
6719
7356
  return data.appIds ?? [];
6720
7357
  }
6721
7358
  async createAppId(params) {
7359
+ const safeName = params.name.replace(/[^0-9A-Za-z\s]/g, " ").replace(/\s+/g, " ").trim() || "App";
6722
7360
  const reqParams = {
6723
7361
  teamId: this.teamId,
6724
7362
  type: "explicit",
6725
7363
  identifier: params.identifier,
6726
- name: params.name
7364
+ name: safeName
6727
7365
  };
6728
7366
  if (params.enablePush) reqParams.push = "on";
6729
7367
  if (params.enableIAP !== false) reqParams.inAppPurchase = "on";
@@ -6758,6 +7396,191 @@ var init_portal = __esm({
6758
7396
  featureValue: params.enabled ? "on" : "off"
6759
7397
  });
6760
7398
  }
7399
+ // ── Application Groups ──────────────────────────────────────────────────
7400
+ //
7401
+ // App Groups (e.g. `group.com.playspina.com`) are the only clean way to
7402
+ // share data — an auth token, in our case — between a host app and an
7403
+ // embedded extension (Messages / Notification Service / etc.). App Store
7404
+ // Connect API keys (.p8) genuinely cannot manage them; Apple only exposes
7405
+ // group create/associate through this legacy session-cookie portal API,
7406
+ // which is why they live on PortalClient (Apple ID session) and not on the
7407
+ // ASC REST client. Endpoint shapes ported 1:1 from Spaceship's
7408
+ // portal_client.rb (listApplicationGroups / addApplicationGroup /
7409
+ // assignApplicationGroupToAppId).
7410
+ /** Apple's internal feature code for the App Groups capability. */
7411
+ static APP_GROUPS_FEATURE = "APG3427HIY";
7412
+ /** List all app groups in the team. */
7413
+ async listAppGroups() {
7414
+ const data = await this.post("account/ios/identifiers/listApplicationGroups.action", {
7415
+ teamId: this.teamId,
7416
+ pageNumber: "1",
7417
+ pageSize: "500",
7418
+ sort: "name=asc"
7419
+ });
7420
+ return data.applicationGroupList ?? [];
7421
+ }
7422
+ /**
7423
+ * Create an app group, returning the raw group record. Idempotent —
7424
+ * Apple rejects a duplicate group identifier ("Multiple App Groups may
7425
+ * not have the same identifier"), so we list first and reuse an existing
7426
+ * group when the identifier already exists in the team.
7427
+ *
7428
+ * Note the two ids on the returned record: `identifier` is the
7429
+ * `group.com.…` string you type, while `applicationGroup` is Apple's
7430
+ * opaque internal handle that association actually requires — do NOT pass
7431
+ * the human identifier to assignAppGroups.
7432
+ */
7433
+ async createAppGroup(params) {
7434
+ const existing = await this.listAppGroups();
7435
+ const hit = existing.find((g) => g.identifier === params.identifier);
7436
+ if (hit) return hit;
7437
+ const safeName = params.name.replace(/[^0-9A-Za-z\s]/g, " ").replace(/\s+/g, " ").trim() || "App Group";
7438
+ const data = await this.post("account/ios/identifiers/addApplicationGroup.action", {
7439
+ teamId: this.teamId,
7440
+ identifier: params.identifier,
7441
+ name: safeName
7442
+ });
7443
+ return data.applicationGroup;
7444
+ }
7445
+ /**
7446
+ * Enable the App Groups capability on an App ID. Required before an
7447
+ * App ID can have a group associated with it — mirrors Spaceship
7448
+ * enabling `AppService::AppGroup` before `associate_groups`.
7449
+ */
7450
+ async enableAppGroups(appIdId) {
7451
+ await this.post("account/ios/identifiers/updateService.action", {
7452
+ teamId: this.teamId,
7453
+ displayId: appIdId,
7454
+ featureType: _PortalClient.APP_GROUPS_FEATURE,
7455
+ featureValue: "true"
7456
+ });
7457
+ }
7458
+ /**
7459
+ * Associate one or more app groups with an App ID. `appGroupIds` are the
7460
+ * internal ids (Spaceship's `app_group_id`), NOT the `group.com.…`
7461
+ * identifiers. Apple wants both `appIdId` and `displayId` set to the
7462
+ * App ID's internal id, per Spaceship's associate_groups_with_app.
7463
+ */
7464
+ async assignAppGroups(params) {
7465
+ await this.post("account/ios/identifiers/assignApplicationGroupToAppId.action", {
7466
+ teamId: this.teamId,
7467
+ appIdId: params.appIdId,
7468
+ displayId: params.appIdId,
7469
+ applicationGroups: params.appGroupIds
7470
+ });
7471
+ }
7472
+ // ── Apple Pay merchants (OMCs) ─────────────────────────────────────────
7473
+ //
7474
+ // Merchant IDs are team-level identifiers (merchant.com.…) that an App ID's
7475
+ // Apple Pay capability must be LINKED to — enabling the capability alone
7476
+ // produces profiles that fail with "doesn't support the … Merchant ID".
7477
+ // Like App Groups, merchants are only manageable through the legacy
7478
+ // session-cookie portal API. Endpoint shapes ported 1:1 from Spaceship's
7479
+ // portal_client.rb (merchants / create_merchant! /
7480
+ // associate_merchants_with_app).
7481
+ /** Apple's internal feature code for the Apple Pay capability. */
7482
+ static APPLE_PAY_FEATURE = "OM633U5T5G";
7483
+ /**
7484
+ * List all Apple Pay merchant IDs in the team. Each record maps
7485
+ * (per Spaceship's merchant.rb attr_mapping): `omcId` = internal id,
7486
+ * `identifier` = the human `merchant.com.…` string, `name` = display name.
7487
+ */
7488
+ async listMerchants() {
7489
+ const data = await this.post("account/ios/identifiers/listOMCs.action", {
7490
+ teamId: this.teamId,
7491
+ pageNumber: "1",
7492
+ pageSize: "500",
7493
+ sort: "name=asc"
7494
+ });
7495
+ return data.identifierList ?? [];
7496
+ }
7497
+ /**
7498
+ * Register (or reuse) a merchant ID, returning its INTERNAL id (`omcId`) —
7499
+ * Apple's opaque handle that association requires, NOT the human
7500
+ * `merchant.com.…` identifier. Idempotent: lists first and reuses an
7501
+ * existing merchant with the same identifier. Endpoint + params ported 1:1
7502
+ * from Spaceship's create_merchant! (addOMC.action).
7503
+ */
7504
+ async createMerchant(params) {
7505
+ const existing = await this.listMerchants();
7506
+ const hit = existing.find((m) => m.identifier === params.identifier);
7507
+ if (hit?.omcId) return String(hit.omcId);
7508
+ const safeName = params.name.replace(/[^0-9A-Za-z\s]/g, " ").replace(/\s+/g, " ").trim() || "Merchant";
7509
+ const data = await this.post("account/ios/identifiers/addOMC.action", {
7510
+ name: safeName,
7511
+ identifier: params.identifier,
7512
+ teamId: this.teamId
7513
+ });
7514
+ const rec = data.omcId ?? data;
7515
+ const id = typeof rec === "string" ? rec : rec?.omcId ?? rec?.merchantId;
7516
+ if (!id) {
7517
+ throw new Error("Apple returned no internal merchant id (omcId) for the registered merchant.");
7518
+ }
7519
+ return String(id);
7520
+ }
7521
+ /**
7522
+ * Enable the Apple Pay capability on an App ID. Apple Pay is a standard
7523
+ * AppService (service_id OM633U5T5G) whose service_uri is the default
7524
+ * `updateService.action` — confirmed in Spaceship's app_service.rb.
7525
+ */
7526
+ async enableApplePay(appIdId) {
7527
+ await this.post("account/ios/identifiers/updateService.action", {
7528
+ teamId: this.teamId,
7529
+ displayId: appIdId,
7530
+ featureType: _PortalClient.APPLE_PAY_FEATURE,
7531
+ featureValue: "true"
7532
+ });
7533
+ }
7534
+ /**
7535
+ * Associate merchant IDs with an App ID's Apple Pay capability. `omcIds`
7536
+ * are the internal ids (each merchant record's `omcId`), NOT the
7537
+ * `merchant.com.…` identifiers. Endpoint + params ported 1:1 from
7538
+ * Spaceship's associate_merchants_with_app (assignOMCToAppId.action — note
7539
+ * the singular "OMC", and NO displayId param).
7540
+ */
7541
+ async assignMerchants(params) {
7542
+ await this.post("account/ios/identifiers/assignOMCToAppId.action", {
7543
+ teamId: this.teamId,
7544
+ appIdId: params.appIdId,
7545
+ omcIds: params.omcIds
7546
+ });
7547
+ }
7548
+ /**
7549
+ * Create an Apple Pay **Payment Processing Certificate** on a merchant ID
7550
+ * from a CSR — the certificate a PSP (e.g. Stripe) hands you as a CSR, which
7551
+ * must be uploaded to the Merchant ID in Apple Developer, then the resulting
7552
+ * `.cer` downloaded and given back to the PSP so it can decrypt payment
7553
+ * tokens. This is the one manual Apple-portal step in wiring up Apple Pay.
7554
+ *
7555
+ * Ported 1:1 from Spaceship's create_certificate! (submitCertificateRequest
7556
+ * .action): the merchant's INTERNAL id (`omcId`) is passed as BOTH `appIdId`
7557
+ * and `specialIdentifierDisplayId` — the latter is how the portal scopes a
7558
+ * cert to a merchant/special identifier rather than an App ID. The cert type
7559
+ * is APPLE_PAY (`4APLUP237T`). Returns the created certRequest (its
7560
+ * `certificateId` feeds `downloadApplePayCertificate`).
7561
+ *
7562
+ * @param omcId the merchant's internal id (from createMerchant/listMerchants)
7563
+ * @param csrContent the PEM CSR string (Stripe's "Apple Pay CSR")
7564
+ */
7565
+ async createApplePayCertificate(params) {
7566
+ const data = await this.post("account/ios/certificate/submitCertificateRequest.action", {
7567
+ teamId: this.teamId,
7568
+ type: _PortalClient.CERT_TYPES.APPLE_PAY,
7569
+ csrContent: params.csrContent,
7570
+ appIdId: params.omcId,
7571
+ specialIdentifierDisplayId: params.omcId
7572
+ });
7573
+ return data.certRequest;
7574
+ }
7575
+ /**
7576
+ * Download an Apple Pay Payment Processing Certificate's raw bytes by its
7577
+ * certificate id (from `createApplePayCertificate`). Thin wrapper over
7578
+ * `downloadCertificate` pinned to the APPLE_PAY type — the DER `.cer` you
7579
+ * upload back into Stripe.
7580
+ */
7581
+ async downloadApplePayCertificate(certificateId) {
7582
+ return this.downloadCertificate(certificateId, _PortalClient.CERT_TYPES.APPLE_PAY);
7583
+ }
6761
7584
  // ── Certificates ───────────────────────────────────────────────────────
6762
7585
  async listCertificates(types) {
6763
7586
  const iosTypes = types ?? [
@@ -7297,6 +8120,9 @@ var init_client = __esm({
7297
8120
  init_in_app_purchases();
7298
8121
  init_game_center_detail();
7299
8122
  init_xcode_metrics();
8123
+ init_app_asset_libraries();
8124
+ init_app_asset_library_assets();
8125
+ init_app_asset_library_placements();
7300
8126
  init_auth();
7301
8127
  init_request();
7302
8128
  init_apple_session();
@@ -7338,6 +8164,10 @@ var init_client = __esm({
7338
8164
  inAppPurchases;
7339
8165
  gameCenterDetail;
7340
8166
  xcodeMetrics;
8167
+ appAssetLibraries;
8168
+ appAssetLibraryImages;
8169
+ appAssetLibraryVideos;
8170
+ appAssetLibraryPlacements;
7341
8171
  constructor(config) {
7342
8172
  const tokenManager = new TokenManager({
7343
8173
  keyId: config.keyId,
@@ -7381,6 +8211,10 @@ var init_client = __esm({
7381
8211
  this.inAppPurchases = new InAppPurchasesResource(this.http);
7382
8212
  this.gameCenterDetail = new GameCenterDetailResource(this.http);
7383
8213
  this.xcodeMetrics = new XcodeMetricsResource(this.http);
8214
+ this.appAssetLibraries = new AppAssetLibrariesResource(this.http);
8215
+ this.appAssetLibraryImages = new AppAssetLibraryImagesResource(this.http);
8216
+ this.appAssetLibraryVideos = new AppAssetLibraryVideosResource(this.http);
8217
+ this.appAssetLibraryPlacements = new AppAssetLibraryPlacementsResource(this.http);
7384
8218
  }
7385
8219
  async request(path, options) {
7386
8220
  return this.http.request(path, options);
@@ -8148,8 +8982,8 @@ var init_utilities = __esm({
8148
8982
  description: "Ask the user for input",
8149
8983
  async run(params) {
8150
8984
  const text = params.text ?? "Enter a value:";
8151
- const { createInterface: createInterface2 } = await import('readline');
8152
- const rl = createInterface2({ input: process.stdin, output: process.stdout });
8985
+ const { createInterface: createInterface4 } = await import('readline');
8986
+ const rl = createInterface4({ input: process.stdin, output: process.stdout });
8153
8987
  return new Promise((resolve4) => {
8154
8988
  rl.question(text + " ", (answer) => {
8155
8989
  rl.close();
@@ -8541,6 +9375,308 @@ var init_options = __esm({
8541
9375
  init_client();
8542
9376
  }
8543
9377
  });
9378
+
9379
+ // src/privacy/index.ts
9380
+ var privacy_exports = {};
9381
+ __export(privacy_exports, {
9382
+ DATA_PROTECTIONS: () => DATA_PROTECTIONS,
9383
+ DATA_USAGE_CATEGORIES: () => DATA_USAGE_CATEGORIES,
9384
+ DATA_USAGE_PURPOSES: () => DATA_USAGE_PURPOSES,
9385
+ createAppDataUsage: () => createAppDataUsage,
9386
+ deleteAppDataUsage: () => deleteAppDataUsage,
9387
+ getAppDataUsagesPublishState: () => getAppDataUsagesPublishState,
9388
+ listAppDataUsages: () => listAppDataUsages,
9389
+ publishAppDataUsages: () => publishAppDataUsages,
9390
+ selectProviderForApp: () => selectProviderForApp,
9391
+ uploadAppPrivacyDetails: () => uploadAppPrivacyDetails,
9392
+ validateUsages: () => validateUsages
9393
+ });
9394
+ async function iris(session, method, path, body) {
9395
+ const res = await fetch(path.startsWith("http") ? path : `${IRIS_BASE2}${path}`, {
9396
+ method,
9397
+ headers: {
9398
+ Cookie: cookieHeader(session),
9399
+ Accept: "application/vnd.api+json, application/json",
9400
+ "Content-Type": "application/vnd.api+json",
9401
+ "X-Requested-With": "XMLHttpRequest"
9402
+ },
9403
+ body: body ? JSON.stringify(body) : void 0
9404
+ });
9405
+ absorbCookies(session, res);
9406
+ const text = await res.text();
9407
+ let parsed = null;
9408
+ try {
9409
+ parsed = text ? JSON.parse(text) : null;
9410
+ } catch {
9411
+ }
9412
+ return { status: res.status, body: parsed, text };
9413
+ }
9414
+ function cookieHeader(session) {
9415
+ return Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join("; ");
9416
+ }
9417
+ function absorbCookies(session, res) {
9418
+ for (const raw of res.headers.getSetCookie?.() ?? []) {
9419
+ const m = raw.match(/^([^=]+)=([^;]*)/);
9420
+ if (m) session.cookies[m[1]] = m[2];
9421
+ }
9422
+ }
9423
+ function fail(what, r) {
9424
+ const detail = r.body?.errors?.map((e) => e.detail ?? e.title).join("; ") || r.text.slice(0, 300);
9425
+ throw new Error(`${what} failed (HTTP ${r.status}): ${detail}`);
9426
+ }
9427
+ async function selectProviderForApp(session, appId, onLog) {
9428
+ const owns = async () => (await iris(session, "GET", `/apps/${appId}?fields[apps]=name`)).status === 200;
9429
+ if (await owns()) return;
9430
+ const res = await fetch(OLYMPUS_SESSION2, { headers: { Cookie: cookieHeader(session), Accept: "application/json" } });
9431
+ absorbCookies(session, res);
9432
+ const info = await res.json().catch(() => null);
9433
+ const providers = info?.availableProviders ?? [];
9434
+ for (const p of providers) {
9435
+ const sw = await fetch(OLYMPUS_SESSION2, {
9436
+ method: "POST",
9437
+ headers: { Cookie: cookieHeader(session), "Content-Type": "application/json", Accept: "application/json" },
9438
+ body: JSON.stringify({ provider: { providerId: p.providerId } })
9439
+ });
9440
+ absorbCookies(session, sw);
9441
+ if (sw.ok && await owns()) {
9442
+ onLog?.(`Switched to team "${p.name}"`);
9443
+ session.teamId = String(p.providerId);
9444
+ return;
9445
+ }
9446
+ }
9447
+ throw new Error(`None of this Apple ID's ${providers.length} teams can see app ${appId}.`);
9448
+ }
9449
+ async function listAppDataUsages(session, appId) {
9450
+ const all = [];
9451
+ let next = `/apps/${appId}/dataUsages?include=category,grouping,purpose,dataProtection&limit=500`;
9452
+ while (next) {
9453
+ const r = await iris(session, "GET", next);
9454
+ if (r.status !== 200) fail("List app data usages", r);
9455
+ all.push(...(r.body?.data ?? []).map((d) => ({ id: d.id })));
9456
+ next = r.body?.links?.next ?? null;
9457
+ }
9458
+ return all;
9459
+ }
9460
+ async function createAppDataUsage(session, input) {
9461
+ const relationships = { app: { data: { type: "apps", id: input.appId } } };
9462
+ if (input.category) relationships.category = { data: { type: "appDataUsageCategories", id: input.category } };
9463
+ if (input.purpose) relationships.purpose = { data: { type: "appDataUsagePurposes", id: input.purpose } };
9464
+ relationships.dataProtection = { data: { type: "appDataUsageDataProtections", id: input.dataProtection } };
9465
+ const r = await iris(session, "POST", "/appDataUsages", { data: { type: "appDataUsages", relationships } });
9466
+ if (r.status !== 201 && r.status !== 200) fail(`Create ${input.category ?? ""} ${input.purpose ?? ""} ${input.dataProtection}`.replace(/\s+/g, " "), r);
9467
+ return r.body?.data?.id;
9468
+ }
9469
+ async function deleteAppDataUsage(session, id) {
9470
+ const r = await iris(session, "DELETE", `/appDataUsages/${id}`);
9471
+ if (r.status !== 204 && r.status !== 200) fail(`Delete app data usage ${id}`, r);
9472
+ }
9473
+ async function getAppDataUsagesPublishState(session, appId) {
9474
+ const r = await iris(session, "GET", `/apps/${appId}/dataUsagePublishState`);
9475
+ if (r.status !== 200) fail("Read privacy publish state", r);
9476
+ return { id: r.body.data.id, published: Boolean(r.body.data.attributes?.published) };
9477
+ }
9478
+ async function publishAppDataUsages(session, publishStateId) {
9479
+ const r = await iris(session, "PATCH", `/appDataUsagesPublishState/${publishStateId}`, {
9480
+ data: { type: "appDataUsagesPublishState", id: publishStateId, attributes: { published: true } }
9481
+ });
9482
+ if (r.status !== 200) fail("Publish privacy details", r);
9483
+ }
9484
+ async function uploadAppPrivacyDetails(opts) {
9485
+ const { session, appId, usages, skipPublish, onLog = () => {
9486
+ } } = opts;
9487
+ validateUsages(usages);
9488
+ await selectProviderForApp(session, appId, onLog);
9489
+ const existing = await listAppDataUsages(session, appId);
9490
+ onLog(`Deleting ${existing.length} existing answer${existing.length === 1 ? "" : "s"}`);
9491
+ for (const u of existing) await deleteAppDataUsage(session, u.id);
9492
+ let created = 0;
9493
+ for (const usage of usages) {
9494
+ const purposes = usage.purposes?.length ? usage.purposes : [null];
9495
+ for (const purpose of purposes) {
9496
+ for (const protection of usage.data_protections) {
9497
+ onLog(protection === "DATA_NOT_COLLECTED" ? `Setting ${protection}` : `Setting ${usage.category} and ${purpose} to ${protection}`);
9498
+ await createAppDataUsage(session, { appId, category: usage.category ?? null, purpose, dataProtection: protection });
9499
+ created++;
9500
+ }
9501
+ }
9502
+ }
9503
+ if (skipPublish) {
9504
+ onLog("Skipping publish");
9505
+ return { deleted: existing.length, created, published: false };
9506
+ }
9507
+ const state = await getAppDataUsagesPublishState(session, appId);
9508
+ await publishAppDataUsages(session, state.id);
9509
+ onLog("Published");
9510
+ return { deleted: existing.length, created, published: true };
9511
+ }
9512
+ function validateUsages(usages) {
9513
+ if (!Array.isArray(usages) || usages.length === 0) throw new Error("No privacy usages given.");
9514
+ const notCollected = usages.some((u) => u.data_protections?.includes("DATA_NOT_COLLECTED"));
9515
+ if (notCollected && usages.length > 1) {
9516
+ throw new Error("DATA_NOT_COLLECTED must be the only entry: an app cannot both collect nothing and collect something.");
9517
+ }
9518
+ const known = (list, v, what) => {
9519
+ if (!list.includes(v)) throw new Error(`"${v}" is not an App Store Connect ${what}. Use one of: ${list.join(", ")}.`);
9520
+ };
9521
+ for (const u of usages) {
9522
+ if (!u.data_protections?.length) throw new Error(`${u.category ?? "An entry"} has no data_protections.`);
9523
+ if (u.category) known(DATA_USAGE_CATEGORIES, u.category, "data category");
9524
+ for (const p of u.purposes ?? []) known(DATA_USAGE_PURPOSES, p, "purpose");
9525
+ for (const d of u.data_protections) known(DATA_PROTECTIONS, d, "data protection");
9526
+ if (u.data_protections.includes("DATA_NOT_COLLECTED")) continue;
9527
+ if (!u.category) throw new Error("Every collected entry needs a category.");
9528
+ if (!u.purposes?.length) throw new Error(`${u.category} needs at least one purpose.`);
9529
+ const linked = u.data_protections.includes("DATA_LINKED_TO_YOU");
9530
+ const unlinked = u.data_protections.includes("DATA_NOT_LINKED_TO_YOU");
9531
+ if (linked && unlinked) throw new Error(`${u.category} cannot be both linked and not linked to the user.`);
9532
+ }
9533
+ }
9534
+ var IRIS_BASE2, OLYMPUS_SESSION2, DATA_USAGE_CATEGORIES, DATA_USAGE_PURPOSES, DATA_PROTECTIONS;
9535
+ var init_privacy = __esm({
9536
+ "src/privacy/index.ts"() {
9537
+ IRIS_BASE2 = "https://appstoreconnect.apple.com/iris/v1";
9538
+ OLYMPUS_SESSION2 = "https://appstoreconnect.apple.com/olympus/v1/session";
9539
+ DATA_USAGE_CATEGORIES = [
9540
+ "PAYMENT_INFORMATION",
9541
+ "CREDIT_AND_FRAUD",
9542
+ "OTHER_FINANCIAL_INFO",
9543
+ "PRECISE_LOCATION",
9544
+ "COARSE_LOCATION",
9545
+ "SENSITIVE_INFO",
9546
+ "PHYSICAL_ADDRESS",
9547
+ "EMAIL_ADDRESS",
9548
+ "NAME",
9549
+ "PHONE_NUMBER",
9550
+ "OTHER_CONTACT_INFO",
9551
+ "CONTACTS",
9552
+ "EMAILS_OR_TEXT_MESSAGES",
9553
+ "PHOTOS_OR_VIDEOS",
9554
+ "AUDIO",
9555
+ "GAMEPLAY_CONTENT",
9556
+ "CUSTOMER_SUPPORT",
9557
+ "OTHER_USER_CONTENT",
9558
+ "BROWSING_HISTORY",
9559
+ "SEARCH_HISTORY",
9560
+ "USER_ID",
9561
+ "DEVICE_ID",
9562
+ "PURCHASE_HISTORY",
9563
+ "PRODUCT_INTERACTION",
9564
+ "ADVERTISING_DATA",
9565
+ "OTHER_USAGE_DATA",
9566
+ "CRASH_DATA",
9567
+ "PERFORMANCE_DATA",
9568
+ "OTHER_DIAGNOSTIC_DATA",
9569
+ "OTHER_DATA",
9570
+ "HEALTH",
9571
+ "FITNESS"
9572
+ ];
9573
+ DATA_USAGE_PURPOSES = [
9574
+ "THIRD_PARTY_ADVERTISING",
9575
+ "DEVELOPERS_ADVERTISING",
9576
+ "ANALYTICS",
9577
+ "PRODUCT_PERSONALIZATION",
9578
+ "APP_FUNCTIONALITY",
9579
+ "OTHER_PURPOSES"
9580
+ ];
9581
+ DATA_PROTECTIONS = [
9582
+ "DATA_USED_TO_TRACK_YOU",
9583
+ "DATA_LINKED_TO_YOU",
9584
+ "DATA_NOT_LINKED_TO_YOU",
9585
+ "DATA_NOT_COLLECTED"
9586
+ ];
9587
+ }
9588
+ });
9589
+
9590
+ // src/review/index.ts
9591
+ var review_exports = {};
9592
+ __export(review_exports, {
9593
+ getReviewFeedback: () => getReviewFeedback,
9594
+ getThreadMessages: () => getThreadMessages,
9595
+ htmlToText: () => htmlToText
9596
+ });
9597
+ async function iris2(session, path) {
9598
+ const res = await fetch(`${IRIS_BASE3}${path}`, {
9599
+ headers: {
9600
+ Cookie: Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join("; "),
9601
+ Accept: "application/vnd.api+json, application/json",
9602
+ "X-Requested-With": "XMLHttpRequest"
9603
+ }
9604
+ });
9605
+ for (const raw of res.headers.getSetCookie?.() ?? []) {
9606
+ const m = raw.match(/^([^=]+)=([^;]*)/);
9607
+ if (m) session.cookies[m[1]] = m[2];
9608
+ }
9609
+ const text = await res.text();
9610
+ let body = null;
9611
+ try {
9612
+ body = text ? JSON.parse(text) : null;
9613
+ } catch {
9614
+ }
9615
+ if (!res.ok) {
9616
+ const detail = body?.errors?.map((e) => e.detail ?? e.title).join("; ") || text.slice(0, 300);
9617
+ throw new Error(`GET ${path} failed (HTTP ${res.status}): ${detail}`);
9618
+ }
9619
+ return body;
9620
+ }
9621
+ function htmlToText(html) {
9622
+ if (!html) return "";
9623
+ 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();
9624
+ }
9625
+ function reasonsFrom(rejection) {
9626
+ const reasons = rejection?.attributes?.reasons ?? [];
9627
+ return (Array.isArray(reasons) ? reasons : []).map((r) => ({
9628
+ section: r.reasonSection ?? r.section,
9629
+ description: r.reasonDescription ?? r.description,
9630
+ code: r.reasonCode ?? r.code
9631
+ }));
9632
+ }
9633
+ async function getThreadMessages(session, threadId) {
9634
+ const body = await iris2(session, `/resolutionCenterThreads/${threadId}/resolutionCenterMessages?include=fromActor,rejections`);
9635
+ const included = new Map((body?.included ?? []).map((i) => [`${i.type}:${i.id}`, i]));
9636
+ return (body?.data ?? []).map((m) => {
9637
+ const actor = m.relationships?.fromActor?.data;
9638
+ const actorRow = actor ? included.get(`${actor.type}:${actor.id}`) : null;
9639
+ const rejections = (m.relationships?.rejections?.data ?? []).map((r) => included.get(`${r.type}:${r.id}`)).filter(Boolean);
9640
+ return {
9641
+ id: m.id,
9642
+ createdDate: m.attributes?.createdDate ?? null,
9643
+ from: actorRow?.attributes?.actorType ?? actorRow?.attributes?.userFirstName ?? null,
9644
+ body: htmlToText(m.attributes?.messageBody),
9645
+ reasons: rejections.flatMap(reasonsFrom)
9646
+ };
9647
+ });
9648
+ }
9649
+ async function getReviewFeedback(session, appId, opts = {}) {
9650
+ await selectProviderForApp(session, appId, opts.onLog);
9651
+ const subs = await iris2(session, `/apps/${appId}/reviewSubmissions?limit=10`);
9652
+ const out = [];
9653
+ for (const s of subs?.data ?? []) {
9654
+ const state = s.attributes?.state ?? "";
9655
+ if (opts.onlyOpen && state !== "UNRESOLVED_ISSUES") continue;
9656
+ const threadsBody = await iris2(session, `/resolutionCenterThreads?filter[reviewSubmission]=${s.id}&include=reviewSubmission`);
9657
+ const threads = [];
9658
+ for (const t of threadsBody?.data ?? []) {
9659
+ threads.push({
9660
+ id: t.id,
9661
+ threadType: t.attributes?.threadType ?? null,
9662
+ state: t.attributes?.state ?? null,
9663
+ createdDate: t.attributes?.createdDate ?? null,
9664
+ messages: await getThreadMessages(session, t.id)
9665
+ });
9666
+ }
9667
+ if (threads.length || !opts.onlyOpen) {
9668
+ out.push({ submissionId: s.id, state, submittedDate: s.attributes?.submittedDate ?? null, threads });
9669
+ }
9670
+ }
9671
+ return out;
9672
+ }
9673
+ var IRIS_BASE3;
9674
+ var init_review = __esm({
9675
+ "src/review/index.ts"() {
9676
+ init_privacy();
9677
+ IRIS_BASE3 = "https://appstoreconnect.apple.com/iris/v1";
9678
+ }
9679
+ });
8544
9680
  function createRunCommand() {
8545
9681
  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) => {
8546
9682
  const { loadFastfile: loadFastfile2 } = await Promise.resolve().then(() => (init_fastfile(), fastfile_exports));
@@ -8661,10 +9797,10 @@ function createPilotCommand() {
8661
9797
  const cmd = new commander.Command("pilot").description("Manage TestFlight builds and testers");
8662
9798
  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) => {
8663
9799
  const { uploadToTestFlight: uploadToTestFlight2 } = await Promise.resolve().then(() => (init_pilot(), pilot_exports));
8664
- const { readFile: readFile8 } = await import('fs/promises');
9800
+ const { readFile: readFile9 } = await import('fs/promises');
8665
9801
  let apiPrivateKey = "";
8666
9802
  if (opts.apiKey) {
8667
- apiPrivateKey = await readFile8(opts.apiKey, "utf-8");
9803
+ apiPrivateKey = await readFile9(opts.apiKey, "utf-8");
8668
9804
  }
8669
9805
  console.log(`Uploading ${opts.ipa}...`);
8670
9806
  const result = await uploadToTestFlight2({
@@ -8696,7 +9832,10 @@ function createPilotCommand() {
8696
9832
  return cmd;
8697
9833
  }
8698
9834
  function createDeliverCommand() {
8699
- 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) => {
9835
+ 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(
9836
+ "--assets-path <path>",
9837
+ "App Asset Library folder: <locale>/{screenshots,previews,header,search_results}/"
9838
+ ).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) => {
8700
9839
  const { resolveClientFromEnv: resolveClientFromEnv2 } = await Promise.resolve().then(() => (init_options(), options_exports));
8701
9840
  const { uploadToAppStore: uploadToAppStore2 } = await Promise.resolve().then(() => (init_deliver(), deliver_exports));
8702
9841
  const client = await resolveClientFromEnv2();
@@ -8715,7 +9854,12 @@ function createDeliverCommand() {
8715
9854
  submitForReview: opts.submitForReview,
8716
9855
  skipBinaryUpload: opts.skipBinaryUpload,
8717
9856
  skipMetadata: opts.skipMetadata,
8718
- skipScreenshots: opts.skipScreenshots
9857
+ skipScreenshots: opts.skipScreenshots,
9858
+ assetsPath: opts.assetsPath,
9859
+ useAssetLibrary: opts.assetLibrary,
9860
+ overwriteAssets: opts.overwriteAssets,
9861
+ skipAssets: opts.skipAssets,
9862
+ onOutput: (chunk) => process.stdout.write(chunk)
8719
9863
  });
8720
9864
  console.log(`App: ${result.appId}`);
8721
9865
  console.log(`Version: ${result.versionId}`);
@@ -8819,6 +9963,145 @@ function createLanesCommand() {
8819
9963
  }
8820
9964
  });
8821
9965
  }
9966
+ function createPrivacyCommand() {
9967
+ 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", process.env.FASTNODE_USER ?? process.env.FASTLANE_USER).option("--skip-publish", "Save the answers without publishing them", false).action(async (opts) => {
9968
+ const { loginWithAppleId: loginWithAppleId2 } = await Promise.resolve().then(() => (init_apple_session(), apple_session_exports));
9969
+ const { uploadAppPrivacyDetails: uploadAppPrivacyDetails2, validateUsages: validateUsages2 } = await Promise.resolve().then(() => (init_privacy(), privacy_exports));
9970
+ const usages = JSON.parse(fs.readFileSync(opts.jsonPath, "utf8"));
9971
+ validateUsages2(usages);
9972
+ const email = opts.username || await ask("Apple ID: ");
9973
+ const password = process.env.FASTNODE_PASSWORD ?? process.env.FASTLANE_PASSWORD ?? await ask(`Password for ${email}: `, true);
9974
+ const session = await loginWithAppleId2({ email, password, onLog: (m) => console.log(m) });
9975
+ const result = await uploadAppPrivacyDetails2({
9976
+ session,
9977
+ appId: opts.appId,
9978
+ usages,
9979
+ skipPublish: opts.skipPublish,
9980
+ onLog: (m) => console.log(m)
9981
+ });
9982
+ console.log(`Done: ${result.created} answers${result.published ? ", published" : ""}.`);
9983
+ });
9984
+ }
9985
+ function ask(question, secret = false) {
9986
+ let muted = false;
9987
+ const output = new stream.Writable({
9988
+ write(chunk, _enc, cb) {
9989
+ if (!muted) process.stdout.write(chunk);
9990
+ cb();
9991
+ }
9992
+ });
9993
+ const rl = readline.createInterface({ input: process.stdin, output, terminal: true });
9994
+ return new Promise((resolve4) => {
9995
+ rl.question(question, (answer) => {
9996
+ rl.close();
9997
+ if (secret) process.stdout.write("\n");
9998
+ resolve4(answer.trim());
9999
+ });
10000
+ muted = secret;
10001
+ });
10002
+ }
10003
+ function createRejectionsCommand() {
10004
+ 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", process.env.FASTNODE_USER ?? process.env.FASTLANE_USER).option("--all", "Include resolved submissions, not just ones awaiting you", false).option("--json", "Print JSON instead of text", false).action(async (opts) => {
10005
+ const { loginWithAppleId: loginWithAppleId2 } = await Promise.resolve().then(() => (init_apple_session(), apple_session_exports));
10006
+ const { getReviewFeedback: getReviewFeedback2 } = await Promise.resolve().then(() => (init_review(), review_exports));
10007
+ const email = opts.username || await ask2("Apple ID: ");
10008
+ const password = process.env.FASTNODE_PASSWORD ?? process.env.FASTLANE_PASSWORD ?? await ask2(`Password for ${email}: `, true);
10009
+ const session = await loginWithAppleId2({ email, password, onLog: (m) => console.error(m) });
10010
+ const all = {};
10011
+ for (const appId of opts.appId) {
10012
+ const feedback = await getReviewFeedback2(session, appId, { onlyOpen: !opts.all, onLog: (m) => console.error(m) });
10013
+ all[appId] = feedback;
10014
+ if (opts.json) continue;
10015
+ console.log(`
10016
+ \u2550\u2550 App ${appId} \u2550\u2550`);
10017
+ if (!feedback.length) console.log("No open App Review feedback.");
10018
+ for (const s of feedback) {
10019
+ console.log(`
10020
+ Submission ${s.submissionId} \u2014 ${s.state} (submitted ${s.submittedDate ?? "?"})`);
10021
+ for (const t of s.threads) {
10022
+ console.log(` Thread ${t.threadType ?? "?"} \xB7 ${t.state ?? "?"} \xB7 ${t.createdDate ?? ""}`);
10023
+ for (const m of t.messages) {
10024
+ console.log(`
10025
+ \u2500\u2500 ${m.from ?? "App Review"} \xB7 ${m.createdDate ?? ""}`);
10026
+ for (const r of m.reasons) console.log(` \u25B8 ${[r.section, r.description].filter(Boolean).join(" \u2014 ")}${r.code ? ` [${r.code}]` : ""}`);
10027
+ console.log(m.body.split("\n").map((l) => ` ${l}`).join("\n"));
10028
+ }
10029
+ }
10030
+ }
10031
+ }
10032
+ if (opts.json) console.log(JSON.stringify(all, null, 2));
10033
+ });
10034
+ }
10035
+ function ask2(question, secret = false) {
10036
+ let muted = false;
10037
+ const output = new stream.Writable({ write(chunk, _enc, cb) {
10038
+ if (!muted) process.stdout.write(chunk);
10039
+ cb();
10040
+ } });
10041
+ const rl = readline.createInterface({ input: process.stdin, output, terminal: true });
10042
+ return new Promise((resolve4) => {
10043
+ rl.question(question, (a) => {
10044
+ rl.close();
10045
+ if (secret) process.stdout.write("\n");
10046
+ resolve4(a.trim());
10047
+ });
10048
+ muted = secret;
10049
+ });
10050
+ }
10051
+ function createAssetsCommand() {
10052
+ const assets = new commander.Command("assets").description(
10053
+ "App Store asset library \u2014 screenshots, previews, product page headers, search results assets"
10054
+ );
10055
+ 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) => {
10056
+ const { resolveClientFromEnv: resolveClientFromEnv2 } = await Promise.resolve().then(() => (init_options(), options_exports));
10057
+ const client = await resolveClientFromEnv2();
10058
+ const feature = opts.feature;
10059
+ const refData = await client.appAssetLibraries.refData({
10060
+ features: [feature],
10061
+ placementTypes: opts.placementType ? [opts.placementType] : void 0
10062
+ });
10063
+ if (opts.json) {
10064
+ console.log(JSON.stringify(refData, null, 2));
10065
+ return;
10066
+ }
10067
+ const policies = refData.features?.find((f) => f.featureId === feature)?.placementPolicies ?? [];
10068
+ const specs = new Map([...refData.imageSpecs ?? [], ...refData.videoSpecs ?? []].map((s) => [s.specId, s]));
10069
+ for (const type of refData.placementTypes ?? []) {
10070
+ if (opts.placementType && type.placementTypeId !== opts.placementType) continue;
10071
+ const policy = policies.find((p) => p.placementType === type.placementTypeId);
10072
+ if (policies.length && !policy) continue;
10073
+ console.log(`
10074
+ ${type.placementTypeId} (${(type.acceptsAssetCategories ?? []).join(", ")})`);
10075
+ for (const mapping of type.specMappings ?? []) {
10076
+ const max = policy?.groupLimits?.find((l) => l.groupIds.includes(mapping.placementGroupId))?.maxCount;
10077
+ const sizes = mapping.specs.map((id) => specs.get(id)).filter((s) => Boolean(s)).map((s) => {
10078
+ const d = s.dimensions;
10079
+ const w = d.minWidth === d.maxWidth ? `${d.minWidth}` : `${d.minWidth}-${d.maxWidth}`;
10080
+ const h = d.minHeight === d.maxHeight ? `${d.minHeight}` : `${d.minHeight}-${d.maxHeight}`;
10081
+ return `${w}\xD7${h}`;
10082
+ });
10083
+ console.log(` ${mapping.placementGroupId.padEnd(44)} ${max ? `max ${max} ` : ""}${sizes.join(", ")}`);
10084
+ }
10085
+ }
10086
+ });
10087
+ assets.command("list").description("List images and videos in an app's asset library").requiredOption("-a, --app-identifier <id>", "Bundle identifier").action(async (opts) => {
10088
+ const { resolveClientFromEnv: resolveClientFromEnv2 } = await Promise.resolve().then(() => (init_options(), options_exports));
10089
+ const client = await resolveClientFromEnv2();
10090
+ const app = await client.apps.findByBundleId(opts.appIdentifier);
10091
+ if (!app) throw new Error(`App not found: ${opts.appIdentifier}`);
10092
+ const library = await client.appAssetLibraries.getForApp(app.id);
10093
+ const [images, videos] = await Promise.all([
10094
+ client.appAssetLibraries.listImages(library.id),
10095
+ client.appAssetLibraries.listVideos(library.id)
10096
+ ]);
10097
+ for (const a of [...images, ...videos]) {
10098
+ const at = a.attributes;
10099
+ console.log(`${a.type === "appAssetLibraryImages" ? "image" : "video"} ${a.id} ${String(at.state).padEnd(22)} ${at.category} ${at.referenceName ?? at.fileName}`);
10100
+ }
10101
+ if (!images.length && !videos.length) console.log("Asset library is empty");
10102
+ });
10103
+ return assets;
10104
+ }
8822
10105
 
8823
10106
  // src/cli/index.ts
8824
10107
  init_options();
@@ -8837,6 +10120,9 @@ function createCli() {
8837
10120
  program.addCommand(createMatchCommand());
8838
10121
  program.addCommand(createActionsCommand());
8839
10122
  program.addCommand(createLanesCommand());
10123
+ program.addCommand(createPrivacyCommand());
10124
+ program.addCommand(createRejectionsCommand());
10125
+ program.addCommand(createAssetsCommand());
8840
10126
  return program;
8841
10127
  }
8842
10128