@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
@@ -1,8 +1,8 @@
1
1
  import * as x509 from '@peculiar/x509';
2
2
  import { randomUUID, createHash, webcrypto, randomBytes, pbkdf2Sync, createCipheriv, createDecipheriv } from 'crypto';
3
3
  import { spawn, execFile, execFileSync } from 'child_process';
4
- import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, copyFileSync, mkdtempSync, rmSync, chmodSync } from 'fs';
5
- import { join, extname, resolve, basename } from 'path';
4
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, chmodSync, unlinkSync, copyFileSync, mkdtempSync, rmSync } from 'fs';
5
+ import { join, extname, resolve, basename, relative } from 'path';
6
6
  import { homedir, tmpdir } from 'os';
7
7
  import { promisify } from 'util';
8
8
  import { mkdir, writeFile, readFile, readdir, unlink, stat, rm, mkdtemp } from 'fs/promises';
@@ -12,6 +12,7 @@ import { createInterface } from 'readline';
12
12
  import emojiRegex from 'emoji-regex';
13
13
  import { importPKCS8, SignJWT } from 'jose';
14
14
  import { Command } from 'commander';
15
+ import { Writable } from 'stream';
15
16
 
16
17
  var __defProp = Object.defineProperty;
17
18
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -184,8 +185,8 @@ async function revokeExpiredCertificates(options) {
184
185
  return count;
185
186
  }
186
187
  async function importToKeychain(certData, keychainPath, keychainPassword) {
187
- const { tmpdir: tmpdir8 } = await import('os');
188
- const tempCertPath = join(tmpdir8(), `fastnode-cert-${Date.now()}.cer`);
188
+ const { tmpdir: tmpdir7 } = await import('os');
189
+ const tempCertPath = join(tmpdir7(), `fastnode-cert-${Date.now()}.cer`);
189
190
  await writeFile(tempCertPath, certData);
190
191
  try {
191
192
  const args = [
@@ -1754,6 +1755,20 @@ var init_srp = __esm({
1754
1755
  };
1755
1756
  }
1756
1757
  });
1758
+ async function appleJson(res, what) {
1759
+ const text = await res.text();
1760
+ try {
1761
+ return JSON.parse(text);
1762
+ } catch {
1763
+ const contentType = (res.headers.get("content-type") ?? "").split(";")[0].trim();
1764
+ const title = /<title>([^<]{1,120})<\/title>/i.exec(text)?.[1]?.trim();
1765
+ const looksHtml = /^\s*<(!doctype|html)/i.test(text) || contentType === "text/html";
1766
+ const page = looksHtml ? "an HTML page" : contentType ? `a ${contentType} response` : "a non-JSON response";
1767
+ throw new Error(
1768
+ `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.`
1769
+ );
1770
+ }
1771
+ }
1757
1772
  function checkPortalResponse(body, session) {
1758
1773
  if (!body || typeof body !== "object") return;
1759
1774
  if (body.resultCode === 4100) {
@@ -1830,7 +1845,7 @@ async function beginLogin(config) {
1830
1845
  }
1831
1846
  async function fetchTwoFactorChallenge(pending) {
1832
1847
  const r = await fetch(`${AUTH_BASE}`, { headers: buildAuthHeadersFromPending(pending) });
1833
- const body = await r.json();
1848
+ const body = await appleJson(r, "two-factor challenge");
1834
1849
  const trustedPhoneNumbers = (body.trustedPhoneNumbers ?? []).map((p) => ({
1835
1850
  id: p.id,
1836
1851
  numberWithDialCode: p.numberWithDialCode,
@@ -1851,7 +1866,8 @@ async function requestSmsCode(input) {
1851
1866
  body: JSON.stringify({ phoneNumber: { id: input.phoneId }, mode: input.mode ?? "sms" })
1852
1867
  });
1853
1868
  }
1854
- async function submitTwoFactorCode(input) {
1869
+ async function submitTwoFactorCode(rawInput) {
1870
+ const input = { ...rawInput, code: rawInput.code.replace(/[\s\u00a0]+/g, "") };
1855
1871
  const log = input.onLog ?? (() => {
1856
1872
  });
1857
1873
  let path;
@@ -1963,9 +1979,12 @@ async function doSirp(user, password, serviceKey, cookieStr, hashcash) {
1963
1979
  body: JSON.stringify({ a: aBase64, accountName: user, protocols: ["s2k", "s2k_fo"] }),
1964
1980
  redirect: "manual"
1965
1981
  });
1966
- const initBody = await initRes.json();
1982
+ const initBody = await appleJson(initRes, "sign-in init");
1967
1983
  if (initBody.serviceErrors) throw new Error(`Apple auth error: ${JSON.stringify(initBody.serviceErrors)}`);
1968
1984
  const { iteration: iterations, salt: saltB64, b: bB64, c, protocol } = initBody;
1985
+ if (!iterations || !saltB64 || !bB64 || !c || !protocol) {
1986
+ throw new Error(`Apple's sign-in init answered without a challenge (HTTP ${initRes.status}): ${JSON.stringify(initBody).slice(0, 300)}`);
1987
+ }
1969
1988
  const salt = Buffer.from(saltB64, "base64");
1970
1989
  const b = Buffer.from(bB64, "base64");
1971
1990
  const encryptedPassword = applePbkdf2(password, salt, iterations, 32, protocol);
@@ -2008,7 +2027,7 @@ async function fetchOlympusSession(session) {
2008
2027
  const cookieStr = Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join("; ");
2009
2028
  const res = await fetch(OLYMPUS_SESSION, { headers: { Cookie: cookieStr, Accept: "application/json" } });
2010
2029
  if (res.ok) {
2011
- const body = await res.json();
2030
+ const body = await appleJson(res, "session lookup");
2012
2031
  if (body?.user?.emailAddress) session.userEmail = body.user.emailAddress;
2013
2032
  if (body?.provider?.providerId) session.teamId = String(body.provider.providerId);
2014
2033
  }
@@ -2020,7 +2039,7 @@ async function fetchContractMessages(session) {
2020
2039
  headers: { Cookie: cookieStr, Accept: "application/json" }
2021
2040
  });
2022
2041
  if (!res.ok) return [];
2023
- const body = await res.json();
2042
+ const body = await appleJson(res, "agreements lookup");
2024
2043
  return (body ?? []).map((m) => ({
2025
2044
  id: m.id ?? "",
2026
2045
  group: m.group ?? "",
@@ -2031,10 +2050,22 @@ async function fetchContractMessages(session) {
2031
2050
  return [];
2032
2051
  }
2033
2052
  }
2034
- async function fetchServiceKey() {
2035
- const res = await fetch(WIDGET_KEY_URL);
2036
- const body = await res.json();
2037
- if (!body?.authServiceKey) throw new Error("Could not fetch Apple service key");
2053
+ function serviceKeyFromLocation(location) {
2054
+ if (!location) return null;
2055
+ try {
2056
+ const key = new URL(location).searchParams.get("widgetKey")?.trim();
2057
+ return key && /^[0-9a-f]{16,}$/i.test(key) ? key : null;
2058
+ } catch {
2059
+ return null;
2060
+ }
2061
+ }
2062
+ async function fetchServiceKey(fetchImpl = fetch) {
2063
+ const head = await fetchImpl(LOGOUT_URL, { method: "HEAD", redirect: "manual" });
2064
+ const fromRedirect = serviceKeyFromLocation(head.headers.get("location"));
2065
+ if (fromRedirect) return fromRedirect;
2066
+ const res = await fetchImpl(WIDGET_KEY_URL);
2067
+ const body = await appleJson(res, "service key request");
2068
+ 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.");
2038
2069
  return body.authServiceKey;
2039
2070
  }
2040
2071
  function buildAuthHeaders(session) {
@@ -2082,7 +2113,7 @@ async function defaultPrompt(message) {
2082
2113
  });
2083
2114
  });
2084
2115
  }
2085
- var AUTH_BASE, OLYMPUS_SESSION, WIDGET_KEY_URL, ProgramLicenseAgreementError;
2116
+ var AUTH_BASE, OLYMPUS_SESSION, WIDGET_KEY_URL, ProgramLicenseAgreementError, LOGOUT_URL;
2086
2117
  var init_apple_session = __esm({
2087
2118
  "src/client/apple-session.ts"() {
2088
2119
  init_srp();
@@ -2097,6 +2128,7 @@ var init_apple_session = __esm({
2097
2128
  }
2098
2129
  agreements;
2099
2130
  };
2131
+ LOGOUT_URL = "https://appstoreconnect.apple.com/logout";
2100
2132
  }
2101
2133
  });
2102
2134
 
@@ -2413,9 +2445,10 @@ async function uploadWithTransporter(options) {
2413
2445
  const transporterBin = await resolveTransporter();
2414
2446
  const platform = options.platform ?? "ios";
2415
2447
  const timeout = options.timeout ?? 18e5;
2416
- const keyDir = await mkdtemp(join(tmpdir(), "fastnode-itms-"));
2448
+ const keyDir = join(homedir(), ".appstoreconnect", "private_keys");
2449
+ await mkdir(keyDir, { recursive: true });
2417
2450
  const keyPath = join(keyDir, `AuthKey_${options.apiKeyId}.p8`);
2418
- await writeFile(keyPath, options.apiPrivateKey);
2451
+ await writeFile(keyPath, options.apiPrivateKey, { mode: 384 });
2419
2452
  return new Promise((resolve4) => {
2420
2453
  const args = [
2421
2454
  "-m",
@@ -2431,6 +2464,9 @@ async function uploadWithTransporter(options) {
2431
2464
  "-WONoPause",
2432
2465
  "true"
2433
2466
  ];
2467
+ if (options.assetDescriptionPath) {
2468
+ args.push("-assetDescription", options.assetDescriptionPath);
2469
+ }
2434
2470
  if (platform !== "ios") {
2435
2471
  args.push("-bundleType", platform);
2436
2472
  }
@@ -2454,13 +2490,13 @@ async function uploadWithTransporter(options) {
2454
2490
  child.stderr.on("data", onData);
2455
2491
  child.on("close", async (code) => {
2456
2492
  clearTimeout(timer);
2457
- await rm(keyDir, { recursive: true, force: true }).catch(() => {
2493
+ await rm(keyPath, { force: true }).catch(() => {
2458
2494
  });
2459
2495
  resolve4({ success: code === 0, output, backend: "transporter" });
2460
2496
  });
2461
2497
  child.on("error", async (err) => {
2462
2498
  clearTimeout(timer);
2463
- await rm(keyDir, { recursive: true, force: true }).catch(() => {
2499
+ await rm(keyPath, { force: true }).catch(() => {
2464
2500
  });
2465
2501
  resolve4({
2466
2502
  success: false,
@@ -2752,6 +2788,311 @@ var init_metadata = __esm({
2752
2788
  };
2753
2789
  }
2754
2790
  });
2791
+ function categoryFor(placementType) {
2792
+ return placementType === "APP_SCREENSHOT" || placementType === "IMESSAGE_APP_SCREENSHOT" || placementType === "APP_PREVIEW" ? "APP_SCREENSHOTS_AND_PREVIEWS" : "CREATIVE_ASSETS";
2793
+ }
2794
+ function readImageSize(data) {
2795
+ if (data.length >= 26 && data.readUInt32BE(0) === 2303741511) {
2796
+ const colorType = data[25];
2797
+ return {
2798
+ width: data.readUInt32BE(16),
2799
+ height: data.readUInt32BE(20),
2800
+ alpha: colorType === 4 || colorType === 6
2801
+ };
2802
+ }
2803
+ if (data.length >= 4 && data[0] === 255 && data[1] === 216) {
2804
+ let offset = 2;
2805
+ while (offset + 9 < data.length) {
2806
+ if (data[offset] !== 255) return null;
2807
+ const marker = data[offset + 1];
2808
+ const length = data.readUInt16BE(offset + 2);
2809
+ const isSof = marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204;
2810
+ if (isSof) {
2811
+ return { height: data.readUInt16BE(offset + 5), width: data.readUInt16BE(offset + 7), alpha: false };
2812
+ }
2813
+ offset += 2 + length;
2814
+ }
2815
+ }
2816
+ return null;
2817
+ }
2818
+ function fits(spec, width, height) {
2819
+ const d = spec.dimensions;
2820
+ return width >= d.minWidth && width <= d.maxWidth && height >= d.minHeight && height <= d.maxHeight;
2821
+ }
2822
+ function groupsForFeature(refData, feature, placementType) {
2823
+ const mappings = refData.placementTypes?.find((p) => p.placementTypeId === placementType)?.specMappings ?? [];
2824
+ const all = mappings.map((m) => m.placementGroupId);
2825
+ const policy = refData.features?.find((f) => f.featureId === feature)?.placementPolicies?.find((p) => p.placementType === placementType);
2826
+ const limited = policy?.groupLimits?.flatMap((l) => l.groupIds);
2827
+ return limited?.length ? all.filter((g) => limited.includes(g)) : all;
2828
+ }
2829
+ function maxCountFor(refData, feature, placementType, placementGroup) {
2830
+ return refData.features?.find((f) => f.featureId === feature)?.placementPolicies?.find((p) => p.placementType === placementType)?.groupLimits?.find((l) => l.groupIds.includes(placementGroup))?.maxCount;
2831
+ }
2832
+ function resolveImageGroups(refData, feature, placementType, size) {
2833
+ const specs = (refData.imageSpecs ?? []).filter(
2834
+ (s) => s.compatiblePlacementTypes.includes(placementType) && fits(s, size.width, size.height)
2835
+ );
2836
+ const specIds = new Set(specs.map((s) => s.specId));
2837
+ const allowed = new Set(groupsForFeature(refData, feature, placementType));
2838
+ const mappings = refData.placementTypes?.find((p) => p.placementTypeId === placementType)?.specMappings ?? [];
2839
+ const groups = mappings.filter((m) => allowed.has(m.placementGroupId) && m.specs.some((id) => specIds.has(id))).map((m) => m.placementGroupId);
2840
+ return { groups: [...new Set(groups)], spec: specs[0] };
2841
+ }
2842
+ async function runUploadOperations(ops, data) {
2843
+ for (const op of ops) {
2844
+ const headers = {};
2845
+ for (const h of op.requestHeaders) headers[h.name] = h.value;
2846
+ const res = await fetch(op.url, {
2847
+ method: op.method,
2848
+ headers,
2849
+ body: data.subarray(op.offset, op.offset + op.length)
2850
+ });
2851
+ if (!res.ok) {
2852
+ throw new Error(`Upload operation failed: ${op.method} ${new URL(op.url).host} \u2192 HTTP ${res.status}`);
2853
+ }
2854
+ }
2855
+ }
2856
+ async function uploadLibraryAsset(options) {
2857
+ const { client, libraryId, filePath, category } = options;
2858
+ const data = await readFile(filePath);
2859
+ const fileName = basename(filePath);
2860
+ const kind = VIDEO_EXTENSIONS.includes(extname(filePath).toLowerCase()) ? "video" : "image";
2861
+ const hash = createHash("sha256").update(data).digest("hex").slice(0, 12);
2862
+ const referenceName = `${options.referenceName ?? fileName} #${hash}`;
2863
+ const media = kind === "image" ? client.appAssetLibraryImages : client.appAssetLibraryVideos;
2864
+ const filter = { filter: { referenceName } };
2865
+ const existing = kind === "image" ? await client.appAssetLibraries.listImages(libraryId, filter) : await client.appAssetLibraries.listVideos(libraryId, filter);
2866
+ const reusable = existing.find(
2867
+ (a) => a.attributes.category === category && a.attributes.state && READY_STATES.includes(a.attributes.state)
2868
+ );
2869
+ if (reusable) {
2870
+ return { kind, id: reusable.id, state: reusable.attributes.state, reused: true };
2871
+ }
2872
+ const reserved = kind === "image" ? await client.appAssetLibraryImages.create({ libraryId, fileName, fileSize: data.length, category, referenceName }) : await client.appAssetLibraryVideos.create({
2873
+ libraryId,
2874
+ fileName,
2875
+ fileSize: data.length,
2876
+ category,
2877
+ referenceName,
2878
+ previewFrameTimeCode: options.previewFrameTimeCode
2879
+ });
2880
+ await runUploadOperations(reserved.attributes.uploadOperations ?? [], data);
2881
+ await media.update(reserved.id, { uploaded: true });
2882
+ options.log?.(`uploaded ${fileName} (${kind}, ${data.length} bytes)`);
2883
+ const state = await waitForAssetReady(
2884
+ () => media.get(reserved.id),
2885
+ options.pollIntervalMs ?? 3e3,
2886
+ options.timeoutMs ?? (kind === "video" ? 30 * 6e4 : 5 * 6e4)
2887
+ );
2888
+ return { kind, id: reserved.id, state, reused: false };
2889
+ }
2890
+ async function waitForAssetReady(get, intervalMs, timeoutMs) {
2891
+ const deadline = Date.now() + timeoutMs;
2892
+ for (; ; ) {
2893
+ const asset = await get();
2894
+ const state = asset.attributes.state;
2895
+ if (state === "FAILED") {
2896
+ const why = (asset.attributes.stateDetails ?? []).map((d) => d.message ?? d.code).filter(Boolean).join("; ");
2897
+ throw new Error(`Asset processing failed${why ? `: ${why}` : ""}`);
2898
+ }
2899
+ if (state && READY_STATES.includes(state)) return state;
2900
+ if (Date.now() > deadline) throw new Error(`Asset still ${state ?? "unknown"} after ${timeoutMs / 1e3}s`);
2901
+ await new Promise((r) => setTimeout(r, intervalMs));
2902
+ }
2903
+ }
2904
+ async function listFiles(dir) {
2905
+ return (await readdir(dir)).filter((f) => !f.startsWith(".")).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }));
2906
+ }
2907
+ async function isDir(path) {
2908
+ return (await stat(path)).isDirectory();
2909
+ }
2910
+ async function planLocale(localeDir, refData, feature, folders, warn) {
2911
+ const planned = [];
2912
+ for (const [folder, placementType] of Object.entries(folders)) {
2913
+ const typeDir = join(localeDir, folder);
2914
+ if (!existsSync(typeDir)) continue;
2915
+ const known = new Set(groupsForFeature(refData, feature, placementType));
2916
+ const entries = [];
2917
+ for (const entry of await listFiles(typeDir)) {
2918
+ const full = join(typeDir, entry);
2919
+ if (await isDir(full)) {
2920
+ if (!known.has(entry)) {
2921
+ warn(`${relative(localeDir, full)}: "${entry}" is not a ${placementType} placement group for ${feature} \u2014 skipped`);
2922
+ continue;
2923
+ }
2924
+ for (const f of await listFiles(full)) entries.push({ filePath: join(full, f), group: entry });
2925
+ } else {
2926
+ entries.push({ filePath: full });
2927
+ }
2928
+ }
2929
+ for (const { filePath, group } of entries) {
2930
+ const ext = extname(filePath).toLowerCase();
2931
+ const isVideo = VIDEO_EXTENSIONS.includes(ext);
2932
+ if (!isVideo && !IMAGE_EXTENSIONS.includes(ext)) continue;
2933
+ const needsVideo = placementType === "APP_PREVIEW";
2934
+ const needsImage = placementType === "APP_SCREENSHOT" || placementType === "IMESSAGE_APP_SCREENSHOT";
2935
+ if (needsVideo && !isVideo || needsImage && isVideo) {
2936
+ warn(`${relative(localeDir, filePath)}: ${isVideo ? "video" : "image"} cannot be a ${placementType} \u2014 skipped`);
2937
+ continue;
2938
+ }
2939
+ if (group) {
2940
+ planned.push({ filePath, placementType, groups: [group] });
2941
+ continue;
2942
+ }
2943
+ if (isVideo) {
2944
+ if (known.size === 1) planned.push({ filePath, placementType, groups: [...known] });
2945
+ else warn(`${relative(localeDir, filePath)}: put videos in a placement-group folder (one of ${[...known].join(", ")}) \u2014 skipped`);
2946
+ continue;
2947
+ }
2948
+ const size = readImageSize(await readFile(filePath));
2949
+ if (!size) {
2950
+ warn(`${relative(localeDir, filePath)}: could not read image size \u2014 skipped`);
2951
+ continue;
2952
+ }
2953
+ const { groups, spec } = resolveImageGroups(refData, feature, placementType, size);
2954
+ if (!groups.length) {
2955
+ warn(`${relative(localeDir, filePath)}: ${size.width}\xD7${size.height} matches no ${placementType} spec \u2014 skipped`);
2956
+ continue;
2957
+ }
2958
+ if (size.alpha && spec && spec.alphaAllowed === false) {
2959
+ warn(`${relative(localeDir, filePath)}: has an alpha channel, which Apple rejects for this spec`);
2960
+ }
2961
+ planned.push({ filePath, placementType, groups: groups.slice(0, 1) });
2962
+ }
2963
+ }
2964
+ return planned;
2965
+ }
2966
+ async function uploadAssetLibrary(options) {
2967
+ const { client } = options;
2968
+ const log = options.log ?? (() => {
2969
+ });
2970
+ const feature = "APP_STORE_VERSIONS";
2971
+ const result = { uploaded: 0, reused: 0, placed: 0, removed: 0, warnings: [] };
2972
+ const warn = (line) => {
2973
+ result.warnings.push(line);
2974
+ log(`warning: ${line}`);
2975
+ };
2976
+ const [library, refData, localizations] = await Promise.all([
2977
+ client.appAssetLibraries.getForApp(options.appId),
2978
+ client.appAssetLibraries.refData({ features: [feature] }),
2979
+ client.appStoreVersions.getLocalizations(options.versionId)
2980
+ ]);
2981
+ const folders = { ...options.folders ?? ASSET_FOLDERS };
2982
+ if (options.rootPlacementType) folders["."] = options.rootPlacementType;
2983
+ const uploads = /* @__PURE__ */ new Map();
2984
+ const uploadOnce = (filePath, placementType) => {
2985
+ const key = `${filePath}|${categoryFor(placementType)}`;
2986
+ if (!uploads.has(key)) {
2987
+ uploads.set(
2988
+ key,
2989
+ uploadLibraryAsset({
2990
+ client,
2991
+ libraryId: library.id,
2992
+ filePath,
2993
+ category: categoryFor(placementType),
2994
+ referenceName: relative(options.assetsPath, filePath),
2995
+ log
2996
+ }).then((a) => {
2997
+ if (a.reused) result.reused++;
2998
+ else result.uploaded++;
2999
+ return a;
3000
+ })
3001
+ );
3002
+ }
3003
+ return uploads.get(key);
3004
+ };
3005
+ for (const loc of localizations) {
3006
+ const localeDir = join(options.assetsPath, loc.attributes.locale);
3007
+ if (!existsSync(localeDir)) continue;
3008
+ const target = { kind: "appStoreVersionLocalization", id: loc.id };
3009
+ const plan = await planLocale(localeDir, refData, feature, folders, warn);
3010
+ const slots = /* @__PURE__ */ new Map();
3011
+ for (const p of plan) {
3012
+ for (const group of p.groups) {
3013
+ const key = `${p.placementType}|${group}`;
3014
+ if (!slots.has(key)) slots.set(key, { placementType: p.placementType, group, files: [] });
3015
+ slots.get(key).files.push(p.filePath);
3016
+ }
3017
+ }
3018
+ for (const slot of slots.values()) {
3019
+ const where = `${loc.attributes.locale} ${slot.placementType} ${slot.group}`;
3020
+ const max = maxCountFor(refData, feature, slot.placementType, slot.group);
3021
+ if (max !== void 0 && slot.files.length > max) {
3022
+ warn(`${where}: ${slot.files.length} files but Apple allows ${max} \u2014 keeping the first ${max}`);
3023
+ slot.files = slot.files.slice(0, max);
3024
+ }
3025
+ const existing = await client.appAssetLibraryPlacements.listForTarget(target, {
3026
+ placementType: slot.placementType,
3027
+ placementGroup: slot.group,
3028
+ include: "image,video"
3029
+ });
3030
+ if (options.overwrite) {
3031
+ for (const p of existing) {
3032
+ await client.appAssetLibraryPlacements.delete(p.id);
3033
+ result.removed++;
3034
+ }
3035
+ }
3036
+ const kept = options.overwrite ? [] : existing;
3037
+ const keptAssetIds = new Set(kept.map((p) => placementAssetId(p)).filter(Boolean));
3038
+ const ours = [];
3039
+ for (const filePath of slot.files) {
3040
+ const asset = await uploadOnce(filePath, slot.placementType);
3041
+ if (keptAssetIds.has(asset.id)) continue;
3042
+ const placement = await client.appAssetLibraryPlacements.create({
3043
+ placementType: slot.placementType,
3044
+ placementGroup: slot.group,
3045
+ ...asset.kind === "image" ? { imageId: asset.id } : { videoId: asset.id },
3046
+ target
3047
+ });
3048
+ ours.push(placement.id);
3049
+ result.placed++;
3050
+ }
3051
+ log(`${where}: placed ${ours.length}${kept.length ? `, kept ${kept.length}` : ""}`);
3052
+ const orderable = slot.placementType === "APP_SCREENSHOT" || slot.placementType === "APP_PREVIEW" || slot.placementType === "IMESSAGE_APP_SCREENSHOT";
3053
+ if (orderable && ours.length + kept.length > 1) {
3054
+ await client.appAssetLibraryPlacements.order({
3055
+ placementGroup: slot.group,
3056
+ placementIds: [...ours, ...kept.map((p) => p.id)],
3057
+ target
3058
+ });
3059
+ }
3060
+ }
3061
+ }
3062
+ return result;
3063
+ }
3064
+ function placementAssetId(p) {
3065
+ for (const rel of ["image", "video"]) {
3066
+ const data = p.relationships?.[rel]?.data;
3067
+ if (data?.id) return data.id;
3068
+ }
3069
+ return void 0;
3070
+ }
3071
+ var ASSET_FOLDERS, IMAGE_EXTENSIONS, VIDEO_EXTENSIONS, READY_STATES;
3072
+ var init_assets = __esm({
3073
+ "src/deliver/assets.ts"() {
3074
+ ASSET_FOLDERS = {
3075
+ screenshots: "APP_SCREENSHOT",
3076
+ imessage_screenshots: "IMESSAGE_APP_SCREENSHOT",
3077
+ previews: "APP_PREVIEW",
3078
+ header: "PRODUCT_PAGE_HEADER_ASSET",
3079
+ search_results: "APP_STORE_SEARCH_RESULTS_ASSET",
3080
+ event_card: "EVENT_CARD_ASSET",
3081
+ event_details: "EVENT_DETAILS_PAGE_ASSET"
3082
+ };
3083
+ IMAGE_EXTENSIONS = [".png", ".jpg", ".jpeg"];
3084
+ VIDEO_EXTENSIONS = [".mp4", ".mov", ".m4v"];
3085
+ READY_STATES = [
3086
+ "COMPLETE",
3087
+ "PREPARE_FOR_SUBMISSION",
3088
+ "READY_FOR_REVIEW",
3089
+ "WAITING_FOR_REVIEW",
3090
+ "IN_REVIEW",
3091
+ "ACCEPTED",
3092
+ "APPROVED"
3093
+ ];
3094
+ }
3095
+ });
2755
3096
 
2756
3097
  // src/xcode/upload.ts
2757
3098
  var upload_exports = {};
@@ -2817,8 +3158,14 @@ var init_upload = __esm({
2817
3158
  // src/deliver/index.ts
2818
3159
  var deliver_exports = {};
2819
3160
  __export(deliver_exports, {
3161
+ ASSET_FOLDERS: () => ASSET_FOLDERS,
3162
+ categoryFor: () => categoryFor,
2820
3163
  loadMetadata: () => loadMetadata,
3164
+ readImageSize: () => readImageSize,
3165
+ resolveImageGroups: () => resolveImageGroups,
2821
3166
  submitForReview: () => submitForReview,
3167
+ uploadAssetLibrary: () => uploadAssetLibrary,
3168
+ uploadLibraryAsset: () => uploadLibraryAsset,
2822
3169
  uploadMetadata: () => uploadMetadata,
2823
3170
  uploadScreenshots: () => uploadScreenshots,
2824
3171
  uploadToAppStore: () => uploadToAppStore
@@ -2857,11 +3204,36 @@ async function uploadToAppStore(options) {
2857
3204
  copyright: options.copyright
2858
3205
  });
2859
3206
  }
3207
+ const log = (line) => options.onOutput?.(`${line}
3208
+ `);
2860
3209
  if (!options.skipScreenshots && options.screenshotsPath && existsSync(options.screenshotsPath)) {
2861
- await uploadScreenshots({
3210
+ if (options.useAssetLibrary) {
3211
+ await uploadAssetLibrary({
3212
+ client: options.client,
3213
+ appId: app.id,
3214
+ versionId: version.id,
3215
+ assetsPath: options.screenshotsPath,
3216
+ folders: {},
3217
+ rootPlacementType: "APP_SCREENSHOT",
3218
+ overwrite: options.overwriteAssets,
3219
+ log
3220
+ });
3221
+ } else {
3222
+ await uploadScreenshots({
3223
+ client: options.client,
3224
+ versionId: version.id,
3225
+ screenshotsPath: options.screenshotsPath
3226
+ });
3227
+ }
3228
+ }
3229
+ if (!options.skipAssets && options.assetsPath && existsSync(options.assetsPath)) {
3230
+ await uploadAssetLibrary({
2862
3231
  client: options.client,
3232
+ appId: app.id,
2863
3233
  versionId: version.id,
2864
- screenshotsPath: options.screenshotsPath
3234
+ assetsPath: options.assetsPath,
3235
+ overwrite: options.overwriteAssets,
3236
+ log
2865
3237
  });
2866
3238
  }
2867
3239
  if (!options.skipBinaryUpload) {
@@ -2946,17 +3318,7 @@ async function uploadScreenshots(options) {
2946
3318
  fileName: file,
2947
3319
  fileSize: fileData.length
2948
3320
  });
2949
- const ops = reserved.attributes.uploadOperations;
2950
- if (ops) {
2951
- for (const op of ops) {
2952
- const chunk = fileData.subarray(op.offset, op.offset + op.length);
2953
- const headers = {};
2954
- for (const h of op.requestHeaders) {
2955
- headers[h.name] = h.value;
2956
- }
2957
- await fetch(op.url, { method: op.method, headers, body: chunk });
2958
- }
2959
- }
3321
+ await runUploadOperations(reserved.attributes.uploadOperations ?? [], fileData);
2960
3322
  await options.client.appScreenshots.commitScreenshot(reserved.id, {
2961
3323
  sourceFileChecksum: checksum,
2962
3324
  uploaded: true
@@ -2976,7 +3338,9 @@ function detectDisplayType(filename) {
2976
3338
  var init_deliver = __esm({
2977
3339
  "src/deliver/index.ts"() {
2978
3340
  init_metadata();
3341
+ init_assets();
2979
3342
  init_metadata();
3343
+ init_assets();
2980
3344
  }
2981
3345
  });
2982
3346
 
@@ -2985,6 +3349,7 @@ var produce_exports = {};
2985
3349
  __export(produce_exports, {
2986
3350
  SERVICE_NAME_TO_CAPABILITY: () => SERVICE_NAME_TO_CAPABILITY,
2987
3351
  addCapability: () => addCapability,
3352
+ associateAppGroups: () => associateAppGroups,
2988
3353
  buildCapabilitySettings: () => buildCapabilitySettings,
2989
3354
  createApp: () => createApp,
2990
3355
  enableServices: () => enableServices,
@@ -3079,6 +3444,30 @@ async function enableServices(options) {
3079
3444
  });
3080
3445
  }
3081
3446
  }
3447
+ async function associateAppGroups(options) {
3448
+ if (!options.appGroups?.length) return;
3449
+ await options.portalClient.enableAppGroups(options.bundleIdId).catch(() => {
3450
+ });
3451
+ const existing = await options.portalClient.listAppGroups();
3452
+ const appGroupIds = [];
3453
+ for (const group of options.appGroups) {
3454
+ let record = existing.find((ag) => ag.identifier === group.identifier);
3455
+ if (!record) {
3456
+ record = await options.portalClient.createAppGroup({
3457
+ name: group.name,
3458
+ identifier: group.identifier
3459
+ });
3460
+ }
3461
+ const id = record?.applicationGroup ?? record?.applicationGroupId ?? record?.appGroupId ?? record?.id;
3462
+ if (id) appGroupIds.push(id);
3463
+ }
3464
+ if (appGroupIds.length) {
3465
+ await options.portalClient.assignAppGroups({
3466
+ appIdId: options.bundleIdId,
3467
+ appGroupIds
3468
+ });
3469
+ }
3470
+ }
3082
3471
  async function createApp(options) {
3083
3472
  const existing = await options.client.apps.findByBundleId(options.bundleIdentifier);
3084
3473
  if (existing) return existing;
@@ -3097,6 +3486,13 @@ async function createApp(options) {
3097
3486
  });
3098
3487
  }
3099
3488
  }
3489
+ if (options.appGroups?.length && options.portalClient) {
3490
+ await associateAppGroups({
3491
+ portalClient: options.portalClient,
3492
+ bundleIdId: bundleId.id,
3493
+ appGroups: options.appGroups
3494
+ });
3495
+ }
3100
3496
  return options.client.apps.create({
3101
3497
  bundleIdentifier: options.bundleIdentifier,
3102
3498
  bundleIdId: bundleId.id,
@@ -3906,10 +4302,10 @@ async function parseTestResults(options) {
3906
4302
  } else if (inputPath.endsWith(".plist")) {
3907
4303
  testSuites = parsePlistResults(inputPath);
3908
4304
  } else {
3909
- const stat6 = await import('fs').then(
4305
+ const stat7 = await import('fs').then(
3910
4306
  (fs) => fs.statSync(inputPath)
3911
4307
  );
3912
- if (stat6.isDirectory()) {
4308
+ if (stat7.isDirectory()) {
3913
4309
  requireMacOS("xcresult parsing");
3914
4310
  testSuites = await parseXCResult(inputPath);
3915
4311
  } else {
@@ -6086,9 +6482,22 @@ var init_pre_release_versions = __esm({
6086
6482
  });
6087
6483
 
6088
6484
  // src/resources/review-submission-items.ts
6089
- var ReviewSubmissionItemsResource;
6485
+ var ITEM_RELATIONSHIPS, ReviewSubmissionItemsResource;
6090
6486
  var init_review_submission_items = __esm({
6091
6487
  "src/resources/review-submission-items.ts"() {
6488
+ ITEM_RELATIONSHIPS = {
6489
+ appStoreVersionId: ["appStoreVersion", "appStoreVersions"],
6490
+ appCustomProductPageVersionId: ["appCustomProductPageVersion", "appCustomProductPageVersions"],
6491
+ appStoreVersionExperimentId: ["appStoreVersionExperiment", "appStoreVersionExperiments"],
6492
+ appStoreVersionExperimentV2Id: ["appStoreVersionExperimentV2", "appStoreVersionExperiments"],
6493
+ appEventId: ["appEvent", "appEvents"],
6494
+ appAssetLibraryImageId: ["appAssetLibraryImage", "appAssetLibraryImages"],
6495
+ appAssetLibraryVideoId: ["appAssetLibraryVideo", "appAssetLibraryVideos"],
6496
+ backgroundAssetVersionId: ["backgroundAssetVersion", "backgroundAssetVersions"],
6497
+ inAppPurchaseVersionId: ["inAppPurchaseVersion", "inAppPurchaseVersions"],
6498
+ subscriptionVersionId: ["subscriptionVersion", "subscriptionVersions"],
6499
+ subscriptionGroupVersionId: ["subscriptionGroupVersion", "subscriptionGroupVersions"]
6500
+ };
6092
6501
  ReviewSubmissionItemsResource = class {
6093
6502
  constructor(http) {
6094
6503
  this.http = http;
@@ -6110,32 +6519,20 @@ var init_review_submission_items = __esm({
6110
6519
  );
6111
6520
  return response.data;
6112
6521
  }
6522
+ /**
6523
+ * Add one item to a review submission. Besides versions, asset library
6524
+ * images and videos can be submitted on their own (ASC API 4.5.1) so
6525
+ * creative assets are approved ahead of a campaign.
6526
+ */
6113
6527
  async create(params) {
6114
6528
  const relationships = {
6115
6529
  reviewSubmission: {
6116
6530
  data: { type: "reviewSubmissions", id: params.reviewSubmissionId }
6117
6531
  }
6118
6532
  };
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
- };
6533
+ for (const [param, [relationship, type]] of Object.entries(ITEM_RELATIONSHIPS)) {
6534
+ const id = params[param];
6535
+ if (id) relationships[relationship] = { data: { type, id } };
6139
6536
  }
6140
6537
  const response = await this.http.post(
6141
6538
  "/v1/reviewSubmissionItems",
@@ -6366,6 +6763,288 @@ var init_xcode_metrics = __esm({
6366
6763
  }
6367
6764
  });
6368
6765
 
6766
+ // src/resources/app-asset-libraries.ts
6767
+ function mergeRefData(parts) {
6768
+ const merged = {
6769
+ features: [],
6770
+ placementProfileGroups: [],
6771
+ imageSpecs: [],
6772
+ videoSpecs: [],
6773
+ placementTypes: [],
6774
+ displayClasses: []
6775
+ };
6776
+ for (const part of parts) {
6777
+ for (const key of Object.keys(merged)) {
6778
+ merged[key].push(...part[key] ?? []);
6779
+ }
6780
+ }
6781
+ return merged;
6782
+ }
6783
+ var AppAssetLibrariesResource;
6784
+ var init_app_asset_libraries = __esm({
6785
+ "src/resources/app-asset-libraries.ts"() {
6786
+ AppAssetLibrariesResource = class {
6787
+ constructor(http) {
6788
+ this.http = http;
6789
+ }
6790
+ http;
6791
+ async getForApp(appId) {
6792
+ const response = await this.http.get(
6793
+ `/v1/apps/${appId}/assetLibrary`
6794
+ );
6795
+ return response.data;
6796
+ }
6797
+ async listImages(libraryId, params = {}) {
6798
+ return this.http.all(`/v1/appAssetLibraries/${libraryId}/images`, params);
6799
+ }
6800
+ async listVideos(libraryId, params = {}) {
6801
+ return this.http.all(`/v1/appAssetLibraries/${libraryId}/videos`, params);
6802
+ }
6803
+ /**
6804
+ * Specs, placement groups and limits. Apple adds placement groups (new
6805
+ * devices) without an API version bump, so read them from here rather than
6806
+ * hard-coding display types.
6807
+ */
6808
+ async refData(params = {}) {
6809
+ const filter = {};
6810
+ if (params.features?.length) filter.features = params.features;
6811
+ if (params.placementTypes?.length) filter.placementTypes = params.placementTypes;
6812
+ if (params.placementProfileGroups?.length) filter.placementProfileGroups = params.placementProfileGroups;
6813
+ const data = await this.http.all("/v1/appAssetLibraryRefData", { filter });
6814
+ return mergeRefData(data.map((d) => d.attributes));
6815
+ }
6816
+ };
6817
+ }
6818
+ });
6819
+
6820
+ // src/resources/app-asset-library-assets.ts
6821
+ var AssetLibraryMediaResource, AppAssetLibraryImagesResource, AppAssetLibraryVideosResource;
6822
+ var init_app_asset_library_assets = __esm({
6823
+ "src/resources/app-asset-library-assets.ts"() {
6824
+ AssetLibraryMediaResource = class {
6825
+ constructor(http) {
6826
+ this.http = http;
6827
+ }
6828
+ http;
6829
+ async get(id) {
6830
+ const response = await this.http.get(`/v1/${this.type}/${id}`);
6831
+ return response.data;
6832
+ }
6833
+ async reserve(params, extra = {}) {
6834
+ const { libraryId, ...attributes } = params;
6835
+ const response = await this.http.post(`/v1/${this.type}`, {
6836
+ data: {
6837
+ type: this.type,
6838
+ attributes: { ...attributes, ...extra },
6839
+ relationships: {
6840
+ assetLibrary: { data: { type: "appAssetLibraries", id: libraryId } }
6841
+ }
6842
+ }
6843
+ });
6844
+ return response.data;
6845
+ }
6846
+ async patch(id, attributes) {
6847
+ const response = await this.http.patch(`/v1/${this.type}/${id}`, {
6848
+ data: { type: this.type, id, attributes }
6849
+ });
6850
+ return response.data;
6851
+ }
6852
+ /** Fails with 409 STATE_ERROR.ASSET_HAS_PLACEMENTS while the asset is still placed. */
6853
+ async delete(id) {
6854
+ await this.http.delete(`/v1/${this.type}/${id}`);
6855
+ }
6856
+ async listPlacements(id, params = {}) {
6857
+ return this.http.all(`/v1/${this.type}/${id}/placements`, params);
6858
+ }
6859
+ };
6860
+ AppAssetLibraryImagesResource = class extends AssetLibraryMediaResource {
6861
+ type = "appAssetLibraryImages";
6862
+ async create(params) {
6863
+ return this.reserve(params);
6864
+ }
6865
+ async update(id, attributes) {
6866
+ return this.patch(id, { ...attributes });
6867
+ }
6868
+ };
6869
+ AppAssetLibraryVideosResource = class extends AssetLibraryMediaResource {
6870
+ type = "appAssetLibraryVideos";
6871
+ async create(params) {
6872
+ const { previewFrameTimeCode, ...rest } = params;
6873
+ return this.reserve(rest, previewFrameTimeCode ? { previewFrameTimeCode } : {});
6874
+ }
6875
+ async update(id, attributes) {
6876
+ return this.patch(id, { ...attributes });
6877
+ }
6878
+ };
6879
+ }
6880
+ });
6881
+
6882
+ // src/resources/app-asset-library-placements.ts
6883
+ function targetRelationship(target) {
6884
+ return { [target.kind]: { data: { type: TARGET_TYPES[target.kind], id: target.id } } };
6885
+ }
6886
+ var TARGET_TYPES, AppAssetLibraryPlacementsResource;
6887
+ var init_app_asset_library_placements = __esm({
6888
+ "src/resources/app-asset-library-placements.ts"() {
6889
+ TARGET_TYPES = {
6890
+ appStoreVersionLocalization: "appStoreVersionLocalizations",
6891
+ appCustomProductPageLocalization: "appCustomProductPageLocalizations",
6892
+ appEventLocalization: "appEventLocalizations",
6893
+ appStoreVersionExperimentTreatmentLocalization: "appStoreVersionExperimentTreatmentLocalizations"
6894
+ };
6895
+ AppAssetLibraryPlacementsResource = class {
6896
+ constructor(http) {
6897
+ this.http = http;
6898
+ }
6899
+ http;
6900
+ /**
6901
+ * Place an image or video on one localization. Placements cannot be edited —
6902
+ * delete and recreate. The parent version/page must be editable.
6903
+ */
6904
+ async create(params) {
6905
+ if (!params.imageId === !params.videoId) {
6906
+ throw new Error("A placement needs exactly one of imageId or videoId");
6907
+ }
6908
+ const media = params.imageId ? { image: { data: { type: "appAssetLibraryImages", id: params.imageId } } } : { video: { data: { type: "appAssetLibraryVideos", id: params.videoId } } };
6909
+ const response = await this.http.post(
6910
+ "/v1/appAssetLibraryPlacements",
6911
+ {
6912
+ data: {
6913
+ type: "appAssetLibraryPlacements",
6914
+ attributes: { placementType: params.placementType, placementGroup: params.placementGroup },
6915
+ relationships: { ...media, ...targetRelationship(params.target) }
6916
+ }
6917
+ }
6918
+ );
6919
+ return response.data;
6920
+ }
6921
+ async get(id, params = {}) {
6922
+ const response = await this.http.get(
6923
+ `/v1/appAssetLibraryPlacements/${id}`,
6924
+ params.include ? { include: params.include } : void 0
6925
+ );
6926
+ return response.data;
6927
+ }
6928
+ async delete(id) {
6929
+ await this.http.delete(`/v1/appAssetLibraryPlacements/${id}`);
6930
+ }
6931
+ /** Placements on one localization, in display order by default. */
6932
+ async listForTarget(target, params = {}) {
6933
+ const filter = {};
6934
+ if (params.placementType) filter.placementType = params.placementType;
6935
+ if (params.placementGroup) filter.placementGroup = params.placementGroup;
6936
+ const listParams = { filter, include: params.include, sort: "placementGroupPosition" };
6937
+ return this.http.all(
6938
+ `/v1/${TARGET_TYPES[target.kind]}/${target.id}/placements`,
6939
+ listParams
6940
+ );
6941
+ }
6942
+ /** Set the display order of a placement group. In-app event localizations cannot be ordered. */
6943
+ async order(params) {
6944
+ if (params.target.kind === "appEventLocalization") {
6945
+ throw new Error("In-app event placements cannot be ordered");
6946
+ }
6947
+ await this.http.post("/v1/appAssetLibraryPlacementOrderingRequests", {
6948
+ data: {
6949
+ type: "appAssetLibraryPlacementOrderingRequests",
6950
+ attributes: { placementGroup: params.placementGroup },
6951
+ relationships: {
6952
+ orderedPlacements: {
6953
+ data: params.placementIds.map((id) => ({ type: "appAssetLibraryPlacements", id }))
6954
+ },
6955
+ ...targetRelationship(params.target)
6956
+ }
6957
+ }
6958
+ });
6959
+ }
6960
+ };
6961
+ }
6962
+ });
6963
+
6964
+ // src/client/session-store.ts
6965
+ var session_store_exports = {};
6966
+ __export(session_store_exports, {
6967
+ clearAppleSession: () => clearAppleSession,
6968
+ lastAppleId: () => lastAppleId,
6969
+ loadAppleSession: () => loadAppleSession,
6970
+ saveAppleSession: () => saveAppleSession,
6971
+ sessionDir: () => sessionDir,
6972
+ signInWithAppleId: () => signInWithAppleId
6973
+ });
6974
+ function sessionDir() {
6975
+ return process.env.FASTNODE_SESSION_DIR || join(homedir(), ".fastnode", "sessions");
6976
+ }
6977
+ function fileFor(email) {
6978
+ const key = createHash("sha256").update(email.trim().toLowerCase()).digest("hex").slice(0, 16);
6979
+ return join(sessionDir(), `${key}.json`);
6980
+ }
6981
+ function ensureDir() {
6982
+ const dir = sessionDir();
6983
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 448 });
6984
+ chmodSync(dir, 448);
6985
+ }
6986
+ function saveAppleSession(session) {
6987
+ ensureDir();
6988
+ const stored = { email: session.userEmail, savedAt: (/* @__PURE__ */ new Date()).toISOString(), session };
6989
+ const path = fileFor(session.userEmail);
6990
+ writeFileSync(path, JSON.stringify(stored), { mode: 384 });
6991
+ chmodSync(path, 384);
6992
+ writeFileSync(join(sessionDir(), "last-apple-id"), session.userEmail, { mode: 384 });
6993
+ }
6994
+ function clearAppleSession(email) {
6995
+ const path = fileFor(email);
6996
+ if (existsSync(path)) unlinkSync(path);
6997
+ }
6998
+ function lastAppleId() {
6999
+ try {
7000
+ return readFileSync(join(sessionDir(), "last-apple-id"), "utf8").trim() || null;
7001
+ } catch {
7002
+ return null;
7003
+ }
7004
+ }
7005
+ async function loadAppleSession(email, fetchImpl = fetch) {
7006
+ const path = fileFor(email);
7007
+ if (!existsSync(path)) return null;
7008
+ let stored;
7009
+ try {
7010
+ stored = JSON.parse(readFileSync(path, "utf8"));
7011
+ } catch {
7012
+ return null;
7013
+ }
7014
+ const cookie = Object.entries(stored.session.cookies).map(([k, v]) => `${k}=${v}`).join("; ");
7015
+ try {
7016
+ const res = await fetchImpl(OLYMPUS_SESSION2, { headers: { Cookie: cookie, Accept: "application/json" } });
7017
+ if (!res.ok) {
7018
+ clearAppleSession(email);
7019
+ return null;
7020
+ }
7021
+ } catch {
7022
+ return null;
7023
+ }
7024
+ return stored.session;
7025
+ }
7026
+ async function signInWithAppleId(options) {
7027
+ const email = options.email || lastAppleId() || await options.askEmail();
7028
+ if (!options.fresh) {
7029
+ const saved = await loadAppleSession(email);
7030
+ if (saved) {
7031
+ options.onLog?.(`Using saved Apple ID session for ${email}`);
7032
+ return saved;
7033
+ }
7034
+ }
7035
+ const password = await options.askPassword(email);
7036
+ const session = await loginWithAppleId({ email, password, onLog: options.onLog });
7037
+ saveAppleSession(session);
7038
+ return session;
7039
+ }
7040
+ var OLYMPUS_SESSION2;
7041
+ var init_session_store = __esm({
7042
+ "src/client/session-store.ts"() {
7043
+ init_apple_session();
7044
+ OLYMPUS_SESSION2 = "https://appstoreconnect.apple.com/olympus/v1/session";
7045
+ }
7046
+ });
7047
+
6369
7048
  // src/client/portal.ts
6370
7049
  function pickUniqueKeyName(base, taken) {
6371
7050
  if (!taken.has(base)) return base;
@@ -6705,7 +7384,10 @@ var init_portal = __esm({
6705
7384
  }
6706
7385
  // ── Teams ──────────────────────────────────────────────────────────────
6707
7386
  async listTeams() {
6708
- const data = await this.post("account/listTeams.action", {});
7387
+ const data = await this.post("account/listTeams.action", {
7388
+ pageSize: "500",
7389
+ pageNumber: "1"
7390
+ });
6709
7391
  return data.teams ?? [];
6710
7392
  }
6711
7393
  // ── App IDs / Bundle IDs ───────────────────────────────────────────────
@@ -6719,11 +7401,12 @@ var init_portal = __esm({
6719
7401
  return data.appIds ?? [];
6720
7402
  }
6721
7403
  async createAppId(params) {
7404
+ const safeName = params.name.replace(/[^0-9A-Za-z\s]/g, " ").replace(/\s+/g, " ").trim() || "App";
6722
7405
  const reqParams = {
6723
7406
  teamId: this.teamId,
6724
7407
  type: "explicit",
6725
7408
  identifier: params.identifier,
6726
- name: params.name
7409
+ name: safeName
6727
7410
  };
6728
7411
  if (params.enablePush) reqParams.push = "on";
6729
7412
  if (params.enableIAP !== false) reqParams.inAppPurchase = "on";
@@ -6758,6 +7441,191 @@ var init_portal = __esm({
6758
7441
  featureValue: params.enabled ? "on" : "off"
6759
7442
  });
6760
7443
  }
7444
+ // ── Application Groups ──────────────────────────────────────────────────
7445
+ //
7446
+ // App Groups (e.g. `group.com.playspina.com`) are the only clean way to
7447
+ // share data — an auth token, in our case — between a host app and an
7448
+ // embedded extension (Messages / Notification Service / etc.). App Store
7449
+ // Connect API keys (.p8) genuinely cannot manage them; Apple only exposes
7450
+ // group create/associate through this legacy session-cookie portal API,
7451
+ // which is why they live on PortalClient (Apple ID session) and not on the
7452
+ // ASC REST client. Endpoint shapes ported 1:1 from Spaceship's
7453
+ // portal_client.rb (listApplicationGroups / addApplicationGroup /
7454
+ // assignApplicationGroupToAppId).
7455
+ /** Apple's internal feature code for the App Groups capability. */
7456
+ static APP_GROUPS_FEATURE = "APG3427HIY";
7457
+ /** List all app groups in the team. */
7458
+ async listAppGroups() {
7459
+ const data = await this.post("account/ios/identifiers/listApplicationGroups.action", {
7460
+ teamId: this.teamId,
7461
+ pageNumber: "1",
7462
+ pageSize: "500",
7463
+ sort: "name=asc"
7464
+ });
7465
+ return data.applicationGroupList ?? [];
7466
+ }
7467
+ /**
7468
+ * Create an app group, returning the raw group record. Idempotent —
7469
+ * Apple rejects a duplicate group identifier ("Multiple App Groups may
7470
+ * not have the same identifier"), so we list first and reuse an existing
7471
+ * group when the identifier already exists in the team.
7472
+ *
7473
+ * Note the two ids on the returned record: `identifier` is the
7474
+ * `group.com.…` string you type, while `applicationGroup` is Apple's
7475
+ * opaque internal handle that association actually requires — do NOT pass
7476
+ * the human identifier to assignAppGroups.
7477
+ */
7478
+ async createAppGroup(params) {
7479
+ const existing = await this.listAppGroups();
7480
+ const hit = existing.find((g) => g.identifier === params.identifier);
7481
+ if (hit) return hit;
7482
+ const safeName = params.name.replace(/[^0-9A-Za-z\s]/g, " ").replace(/\s+/g, " ").trim() || "App Group";
7483
+ const data = await this.post("account/ios/identifiers/addApplicationGroup.action", {
7484
+ teamId: this.teamId,
7485
+ identifier: params.identifier,
7486
+ name: safeName
7487
+ });
7488
+ return data.applicationGroup;
7489
+ }
7490
+ /**
7491
+ * Enable the App Groups capability on an App ID. Required before an
7492
+ * App ID can have a group associated with it — mirrors Spaceship
7493
+ * enabling `AppService::AppGroup` before `associate_groups`.
7494
+ */
7495
+ async enableAppGroups(appIdId) {
7496
+ await this.post("account/ios/identifiers/updateService.action", {
7497
+ teamId: this.teamId,
7498
+ displayId: appIdId,
7499
+ featureType: _PortalClient.APP_GROUPS_FEATURE,
7500
+ featureValue: "true"
7501
+ });
7502
+ }
7503
+ /**
7504
+ * Associate one or more app groups with an App ID. `appGroupIds` are the
7505
+ * internal ids (Spaceship's `app_group_id`), NOT the `group.com.…`
7506
+ * identifiers. Apple wants both `appIdId` and `displayId` set to the
7507
+ * App ID's internal id, per Spaceship's associate_groups_with_app.
7508
+ */
7509
+ async assignAppGroups(params) {
7510
+ await this.post("account/ios/identifiers/assignApplicationGroupToAppId.action", {
7511
+ teamId: this.teamId,
7512
+ appIdId: params.appIdId,
7513
+ displayId: params.appIdId,
7514
+ applicationGroups: params.appGroupIds
7515
+ });
7516
+ }
7517
+ // ── Apple Pay merchants (OMCs) ─────────────────────────────────────────
7518
+ //
7519
+ // Merchant IDs are team-level identifiers (merchant.com.…) that an App ID's
7520
+ // Apple Pay capability must be LINKED to — enabling the capability alone
7521
+ // produces profiles that fail with "doesn't support the … Merchant ID".
7522
+ // Like App Groups, merchants are only manageable through the legacy
7523
+ // session-cookie portal API. Endpoint shapes ported 1:1 from Spaceship's
7524
+ // portal_client.rb (merchants / create_merchant! /
7525
+ // associate_merchants_with_app).
7526
+ /** Apple's internal feature code for the Apple Pay capability. */
7527
+ static APPLE_PAY_FEATURE = "OM633U5T5G";
7528
+ /**
7529
+ * List all Apple Pay merchant IDs in the team. Each record maps
7530
+ * (per Spaceship's merchant.rb attr_mapping): `omcId` = internal id,
7531
+ * `identifier` = the human `merchant.com.…` string, `name` = display name.
7532
+ */
7533
+ async listMerchants() {
7534
+ const data = await this.post("account/ios/identifiers/listOMCs.action", {
7535
+ teamId: this.teamId,
7536
+ pageNumber: "1",
7537
+ pageSize: "500",
7538
+ sort: "name=asc"
7539
+ });
7540
+ return data.identifierList ?? [];
7541
+ }
7542
+ /**
7543
+ * Register (or reuse) a merchant ID, returning its INTERNAL id (`omcId`) —
7544
+ * Apple's opaque handle that association requires, NOT the human
7545
+ * `merchant.com.…` identifier. Idempotent: lists first and reuses an
7546
+ * existing merchant with the same identifier. Endpoint + params ported 1:1
7547
+ * from Spaceship's create_merchant! (addOMC.action).
7548
+ */
7549
+ async createMerchant(params) {
7550
+ const existing = await this.listMerchants();
7551
+ const hit = existing.find((m) => m.identifier === params.identifier);
7552
+ if (hit?.omcId) return String(hit.omcId);
7553
+ const safeName = params.name.replace(/[^0-9A-Za-z\s]/g, " ").replace(/\s+/g, " ").trim() || "Merchant";
7554
+ const data = await this.post("account/ios/identifiers/addOMC.action", {
7555
+ name: safeName,
7556
+ identifier: params.identifier,
7557
+ teamId: this.teamId
7558
+ });
7559
+ const rec = data.omcId ?? data;
7560
+ const id = typeof rec === "string" ? rec : rec?.omcId ?? rec?.merchantId;
7561
+ if (!id) {
7562
+ throw new Error("Apple returned no internal merchant id (omcId) for the registered merchant.");
7563
+ }
7564
+ return String(id);
7565
+ }
7566
+ /**
7567
+ * Enable the Apple Pay capability on an App ID. Apple Pay is a standard
7568
+ * AppService (service_id OM633U5T5G) whose service_uri is the default
7569
+ * `updateService.action` — confirmed in Spaceship's app_service.rb.
7570
+ */
7571
+ async enableApplePay(appIdId) {
7572
+ await this.post("account/ios/identifiers/updateService.action", {
7573
+ teamId: this.teamId,
7574
+ displayId: appIdId,
7575
+ featureType: _PortalClient.APPLE_PAY_FEATURE,
7576
+ featureValue: "true"
7577
+ });
7578
+ }
7579
+ /**
7580
+ * Associate merchant IDs with an App ID's Apple Pay capability. `omcIds`
7581
+ * are the internal ids (each merchant record's `omcId`), NOT the
7582
+ * `merchant.com.…` identifiers. Endpoint + params ported 1:1 from
7583
+ * Spaceship's associate_merchants_with_app (assignOMCToAppId.action — note
7584
+ * the singular "OMC", and NO displayId param).
7585
+ */
7586
+ async assignMerchants(params) {
7587
+ await this.post("account/ios/identifiers/assignOMCToAppId.action", {
7588
+ teamId: this.teamId,
7589
+ appIdId: params.appIdId,
7590
+ omcIds: params.omcIds
7591
+ });
7592
+ }
7593
+ /**
7594
+ * Create an Apple Pay **Payment Processing Certificate** on a merchant ID
7595
+ * from a CSR — the certificate a PSP (e.g. Stripe) hands you as a CSR, which
7596
+ * must be uploaded to the Merchant ID in Apple Developer, then the resulting
7597
+ * `.cer` downloaded and given back to the PSP so it can decrypt payment
7598
+ * tokens. This is the one manual Apple-portal step in wiring up Apple Pay.
7599
+ *
7600
+ * Ported 1:1 from Spaceship's create_certificate! (submitCertificateRequest
7601
+ * .action): the merchant's INTERNAL id (`omcId`) is passed as BOTH `appIdId`
7602
+ * and `specialIdentifierDisplayId` — the latter is how the portal scopes a
7603
+ * cert to a merchant/special identifier rather than an App ID. The cert type
7604
+ * is APPLE_PAY (`4APLUP237T`). Returns the created certRequest (its
7605
+ * `certificateId` feeds `downloadApplePayCertificate`).
7606
+ *
7607
+ * @param omcId the merchant's internal id (from createMerchant/listMerchants)
7608
+ * @param csrContent the PEM CSR string (Stripe's "Apple Pay CSR")
7609
+ */
7610
+ async createApplePayCertificate(params) {
7611
+ const data = await this.post("account/ios/certificate/submitCertificateRequest.action", {
7612
+ teamId: this.teamId,
7613
+ type: _PortalClient.CERT_TYPES.APPLE_PAY,
7614
+ csrContent: params.csrContent,
7615
+ appIdId: params.omcId,
7616
+ specialIdentifierDisplayId: params.omcId
7617
+ });
7618
+ return data.certRequest;
7619
+ }
7620
+ /**
7621
+ * Download an Apple Pay Payment Processing Certificate's raw bytes by its
7622
+ * certificate id (from `createApplePayCertificate`). Thin wrapper over
7623
+ * `downloadCertificate` pinned to the APPLE_PAY type — the DER `.cer` you
7624
+ * upload back into Stripe.
7625
+ */
7626
+ async downloadApplePayCertificate(certificateId) {
7627
+ return this.downloadCertificate(certificateId, _PortalClient.CERT_TYPES.APPLE_PAY);
7628
+ }
6761
7629
  // ── Certificates ───────────────────────────────────────────────────────
6762
7630
  async listCertificates(types) {
6763
7631
  const iosTypes = types ?? [
@@ -7254,8 +8122,14 @@ __export(client_exports, {
7254
8122
  TokenManager: () => TokenManager,
7255
8123
  beginLogin: () => beginLogin,
7256
8124
  checkPortalResponse: () => checkPortalResponse,
8125
+ clearAppleSession: () => clearAppleSession,
8126
+ lastAppleId: () => lastAppleId,
8127
+ loadAppleSession: () => loadAppleSession,
7257
8128
  loginWithAppleId: () => loginWithAppleId,
7258
8129
  requestSmsCode: () => requestSmsCode,
8130
+ saveAppleSession: () => saveAppleSession,
8131
+ sessionDir: () => sessionDir,
8132
+ signInWithAppleId: () => signInWithAppleId,
7259
8133
  submitTwoFactorCode: () => submitTwoFactorCode
7260
8134
  });
7261
8135
  var AppStoreConnectClient;
@@ -7297,9 +8171,13 @@ var init_client = __esm({
7297
8171
  init_in_app_purchases();
7298
8172
  init_game_center_detail();
7299
8173
  init_xcode_metrics();
8174
+ init_app_asset_libraries();
8175
+ init_app_asset_library_assets();
8176
+ init_app_asset_library_placements();
7300
8177
  init_auth();
7301
8178
  init_request();
7302
8179
  init_apple_session();
8180
+ init_session_store();
7303
8181
  init_portal();
7304
8182
  init_iris();
7305
8183
  AppStoreConnectClient = class {
@@ -7338,6 +8216,10 @@ var init_client = __esm({
7338
8216
  inAppPurchases;
7339
8217
  gameCenterDetail;
7340
8218
  xcodeMetrics;
8219
+ appAssetLibraries;
8220
+ appAssetLibraryImages;
8221
+ appAssetLibraryVideos;
8222
+ appAssetLibraryPlacements;
7341
8223
  constructor(config) {
7342
8224
  const tokenManager = new TokenManager({
7343
8225
  keyId: config.keyId,
@@ -7381,6 +8263,10 @@ var init_client = __esm({
7381
8263
  this.inAppPurchases = new InAppPurchasesResource(this.http);
7382
8264
  this.gameCenterDetail = new GameCenterDetailResource(this.http);
7383
8265
  this.xcodeMetrics = new XcodeMetricsResource(this.http);
8266
+ this.appAssetLibraries = new AppAssetLibrariesResource(this.http);
8267
+ this.appAssetLibraryImages = new AppAssetLibraryImagesResource(this.http);
8268
+ this.appAssetLibraryVideos = new AppAssetLibraryVideosResource(this.http);
8269
+ this.appAssetLibraryPlacements = new AppAssetLibraryPlacementsResource(this.http);
7384
8270
  }
7385
8271
  async request(path, options) {
7386
8272
  return this.http.request(path, options);
@@ -8148,8 +9034,8 @@ var init_utilities = __esm({
8148
9034
  description: "Ask the user for input",
8149
9035
  async run(params) {
8150
9036
  const text = params.text ?? "Enter a value:";
8151
- const { createInterface: createInterface2 } = await import('readline');
8152
- const rl = createInterface2({ input: process.stdin, output: process.stdout });
9037
+ const { createInterface: createInterface4 } = await import('readline');
9038
+ const rl = createInterface4({ input: process.stdin, output: process.stdout });
8153
9039
  return new Promise((resolve4) => {
8154
9040
  rl.question(text + " ", (answer) => {
8155
9041
  rl.close();
@@ -8541,6 +9427,308 @@ var init_options = __esm({
8541
9427
  init_client();
8542
9428
  }
8543
9429
  });
9430
+
9431
+ // src/privacy/index.ts
9432
+ var privacy_exports = {};
9433
+ __export(privacy_exports, {
9434
+ DATA_PROTECTIONS: () => DATA_PROTECTIONS,
9435
+ DATA_USAGE_CATEGORIES: () => DATA_USAGE_CATEGORIES,
9436
+ DATA_USAGE_PURPOSES: () => DATA_USAGE_PURPOSES,
9437
+ createAppDataUsage: () => createAppDataUsage,
9438
+ deleteAppDataUsage: () => deleteAppDataUsage,
9439
+ getAppDataUsagesPublishState: () => getAppDataUsagesPublishState,
9440
+ listAppDataUsages: () => listAppDataUsages,
9441
+ publishAppDataUsages: () => publishAppDataUsages,
9442
+ selectProviderForApp: () => selectProviderForApp,
9443
+ uploadAppPrivacyDetails: () => uploadAppPrivacyDetails,
9444
+ validateUsages: () => validateUsages
9445
+ });
9446
+ async function iris(session, method, path, body) {
9447
+ const res = await fetch(path.startsWith("http") ? path : `${IRIS_BASE2}${path}`, {
9448
+ method,
9449
+ headers: {
9450
+ Cookie: cookieHeader(session),
9451
+ Accept: "application/vnd.api+json, application/json",
9452
+ "Content-Type": "application/vnd.api+json",
9453
+ "X-Requested-With": "XMLHttpRequest"
9454
+ },
9455
+ body: body ? JSON.stringify(body) : void 0
9456
+ });
9457
+ absorbCookies(session, res);
9458
+ const text = await res.text();
9459
+ let parsed = null;
9460
+ try {
9461
+ parsed = text ? JSON.parse(text) : null;
9462
+ } catch {
9463
+ }
9464
+ return { status: res.status, body: parsed, text };
9465
+ }
9466
+ function cookieHeader(session) {
9467
+ return Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join("; ");
9468
+ }
9469
+ function absorbCookies(session, res) {
9470
+ for (const raw of res.headers.getSetCookie?.() ?? []) {
9471
+ const m = raw.match(/^([^=]+)=([^;]*)/);
9472
+ if (m) session.cookies[m[1]] = m[2];
9473
+ }
9474
+ }
9475
+ function fail(what, r) {
9476
+ const detail = r.body?.errors?.map((e) => e.detail ?? e.title).join("; ") || r.text.slice(0, 300);
9477
+ throw new Error(`${what} failed (HTTP ${r.status}): ${detail}`);
9478
+ }
9479
+ async function selectProviderForApp(session, appId, onLog) {
9480
+ const owns = async () => (await iris(session, "GET", `/apps/${appId}?fields[apps]=name`)).status === 200;
9481
+ if (await owns()) return;
9482
+ const res = await fetch(OLYMPUS_SESSION3, { headers: { Cookie: cookieHeader(session), Accept: "application/json" } });
9483
+ absorbCookies(session, res);
9484
+ const info = await res.json().catch(() => null);
9485
+ const providers = info?.availableProviders ?? [];
9486
+ for (const p of providers) {
9487
+ const sw = await fetch(OLYMPUS_SESSION3, {
9488
+ method: "POST",
9489
+ headers: { Cookie: cookieHeader(session), "Content-Type": "application/json", Accept: "application/json" },
9490
+ body: JSON.stringify({ provider: { providerId: p.providerId } })
9491
+ });
9492
+ absorbCookies(session, sw);
9493
+ if (sw.ok && await owns()) {
9494
+ onLog?.(`Switched to team "${p.name}"`);
9495
+ session.teamId = String(p.providerId);
9496
+ return;
9497
+ }
9498
+ }
9499
+ throw new Error(`None of this Apple ID's ${providers.length} teams can see app ${appId}.`);
9500
+ }
9501
+ async function listAppDataUsages(session, appId) {
9502
+ const all = [];
9503
+ let next = `/apps/${appId}/dataUsages?include=category,grouping,purpose,dataProtection&limit=500`;
9504
+ while (next) {
9505
+ const r = await iris(session, "GET", next);
9506
+ if (r.status !== 200) fail("List app data usages", r);
9507
+ all.push(...(r.body?.data ?? []).map((d) => ({ id: d.id })));
9508
+ next = r.body?.links?.next ?? null;
9509
+ }
9510
+ return all;
9511
+ }
9512
+ async function createAppDataUsage(session, input) {
9513
+ const relationships = { app: { data: { type: "apps", id: input.appId } } };
9514
+ if (input.category) relationships.category = { data: { type: "appDataUsageCategories", id: input.category } };
9515
+ if (input.purpose) relationships.purpose = { data: { type: "appDataUsagePurposes", id: input.purpose } };
9516
+ relationships.dataProtection = { data: { type: "appDataUsageDataProtections", id: input.dataProtection } };
9517
+ const r = await iris(session, "POST", "/appDataUsages", { data: { type: "appDataUsages", relationships } });
9518
+ if (r.status !== 201 && r.status !== 200) fail(`Create ${input.category ?? ""} ${input.purpose ?? ""} ${input.dataProtection}`.replace(/\s+/g, " "), r);
9519
+ return r.body?.data?.id;
9520
+ }
9521
+ async function deleteAppDataUsage(session, id) {
9522
+ const r = await iris(session, "DELETE", `/appDataUsages/${id}`);
9523
+ if (r.status !== 204 && r.status !== 200) fail(`Delete app data usage ${id}`, r);
9524
+ }
9525
+ async function getAppDataUsagesPublishState(session, appId) {
9526
+ const r = await iris(session, "GET", `/apps/${appId}/dataUsagePublishState`);
9527
+ if (r.status !== 200) fail("Read privacy publish state", r);
9528
+ return { id: r.body.data.id, published: Boolean(r.body.data.attributes?.published) };
9529
+ }
9530
+ async function publishAppDataUsages(session, publishStateId) {
9531
+ const r = await iris(session, "PATCH", `/appDataUsagesPublishState/${publishStateId}`, {
9532
+ data: { type: "appDataUsagesPublishState", id: publishStateId, attributes: { published: true } }
9533
+ });
9534
+ if (r.status !== 200) fail("Publish privacy details", r);
9535
+ }
9536
+ async function uploadAppPrivacyDetails(opts) {
9537
+ const { session, appId, usages, skipPublish, onLog = () => {
9538
+ } } = opts;
9539
+ validateUsages(usages);
9540
+ await selectProviderForApp(session, appId, onLog);
9541
+ const existing = await listAppDataUsages(session, appId);
9542
+ onLog(`Deleting ${existing.length} existing answer${existing.length === 1 ? "" : "s"}`);
9543
+ for (const u of existing) await deleteAppDataUsage(session, u.id);
9544
+ let created = 0;
9545
+ for (const usage of usages) {
9546
+ const purposes = usage.purposes?.length ? usage.purposes : [null];
9547
+ for (const purpose of purposes) {
9548
+ for (const protection of usage.data_protections) {
9549
+ onLog(protection === "DATA_NOT_COLLECTED" ? `Setting ${protection}` : `Setting ${usage.category} and ${purpose} to ${protection}`);
9550
+ await createAppDataUsage(session, { appId, category: usage.category ?? null, purpose, dataProtection: protection });
9551
+ created++;
9552
+ }
9553
+ }
9554
+ }
9555
+ if (skipPublish) {
9556
+ onLog("Skipping publish");
9557
+ return { deleted: existing.length, created, published: false };
9558
+ }
9559
+ const state = await getAppDataUsagesPublishState(session, appId);
9560
+ await publishAppDataUsages(session, state.id);
9561
+ onLog("Published");
9562
+ return { deleted: existing.length, created, published: true };
9563
+ }
9564
+ function validateUsages(usages) {
9565
+ if (!Array.isArray(usages) || usages.length === 0) throw new Error("No privacy usages given.");
9566
+ const notCollected = usages.some((u) => u.data_protections?.includes("DATA_NOT_COLLECTED"));
9567
+ if (notCollected && usages.length > 1) {
9568
+ throw new Error("DATA_NOT_COLLECTED must be the only entry: an app cannot both collect nothing and collect something.");
9569
+ }
9570
+ const known = (list, v, what) => {
9571
+ if (!list.includes(v)) throw new Error(`"${v}" is not an App Store Connect ${what}. Use one of: ${list.join(", ")}.`);
9572
+ };
9573
+ for (const u of usages) {
9574
+ if (!u.data_protections?.length) throw new Error(`${u.category ?? "An entry"} has no data_protections.`);
9575
+ if (u.category) known(DATA_USAGE_CATEGORIES, u.category, "data category");
9576
+ for (const p of u.purposes ?? []) known(DATA_USAGE_PURPOSES, p, "purpose");
9577
+ for (const d of u.data_protections) known(DATA_PROTECTIONS, d, "data protection");
9578
+ if (u.data_protections.includes("DATA_NOT_COLLECTED")) continue;
9579
+ if (!u.category) throw new Error("Every collected entry needs a category.");
9580
+ if (!u.purposes?.length) throw new Error(`${u.category} needs at least one purpose.`);
9581
+ const linked = u.data_protections.includes("DATA_LINKED_TO_YOU");
9582
+ const unlinked = u.data_protections.includes("DATA_NOT_LINKED_TO_YOU");
9583
+ if (linked && unlinked) throw new Error(`${u.category} cannot be both linked and not linked to the user.`);
9584
+ }
9585
+ }
9586
+ var IRIS_BASE2, OLYMPUS_SESSION3, DATA_USAGE_CATEGORIES, DATA_USAGE_PURPOSES, DATA_PROTECTIONS;
9587
+ var init_privacy = __esm({
9588
+ "src/privacy/index.ts"() {
9589
+ IRIS_BASE2 = "https://appstoreconnect.apple.com/iris/v1";
9590
+ OLYMPUS_SESSION3 = "https://appstoreconnect.apple.com/olympus/v1/session";
9591
+ DATA_USAGE_CATEGORIES = [
9592
+ "PAYMENT_INFORMATION",
9593
+ "CREDIT_AND_FRAUD",
9594
+ "OTHER_FINANCIAL_INFO",
9595
+ "PRECISE_LOCATION",
9596
+ "COARSE_LOCATION",
9597
+ "SENSITIVE_INFO",
9598
+ "PHYSICAL_ADDRESS",
9599
+ "EMAIL_ADDRESS",
9600
+ "NAME",
9601
+ "PHONE_NUMBER",
9602
+ "OTHER_CONTACT_INFO",
9603
+ "CONTACTS",
9604
+ "EMAILS_OR_TEXT_MESSAGES",
9605
+ "PHOTOS_OR_VIDEOS",
9606
+ "AUDIO",
9607
+ "GAMEPLAY_CONTENT",
9608
+ "CUSTOMER_SUPPORT",
9609
+ "OTHER_USER_CONTENT",
9610
+ "BROWSING_HISTORY",
9611
+ "SEARCH_HISTORY",
9612
+ "USER_ID",
9613
+ "DEVICE_ID",
9614
+ "PURCHASE_HISTORY",
9615
+ "PRODUCT_INTERACTION",
9616
+ "ADVERTISING_DATA",
9617
+ "OTHER_USAGE_DATA",
9618
+ "CRASH_DATA",
9619
+ "PERFORMANCE_DATA",
9620
+ "OTHER_DIAGNOSTIC_DATA",
9621
+ "OTHER_DATA",
9622
+ "HEALTH",
9623
+ "FITNESS"
9624
+ ];
9625
+ DATA_USAGE_PURPOSES = [
9626
+ "THIRD_PARTY_ADVERTISING",
9627
+ "DEVELOPERS_ADVERTISING",
9628
+ "ANALYTICS",
9629
+ "PRODUCT_PERSONALIZATION",
9630
+ "APP_FUNCTIONALITY",
9631
+ "OTHER_PURPOSES"
9632
+ ];
9633
+ DATA_PROTECTIONS = [
9634
+ "DATA_USED_TO_TRACK_YOU",
9635
+ "DATA_LINKED_TO_YOU",
9636
+ "DATA_NOT_LINKED_TO_YOU",
9637
+ "DATA_NOT_COLLECTED"
9638
+ ];
9639
+ }
9640
+ });
9641
+
9642
+ // src/review/index.ts
9643
+ var review_exports = {};
9644
+ __export(review_exports, {
9645
+ getReviewFeedback: () => getReviewFeedback,
9646
+ getThreadMessages: () => getThreadMessages,
9647
+ htmlToText: () => htmlToText
9648
+ });
9649
+ async function iris2(session, path) {
9650
+ const res = await fetch(`${IRIS_BASE3}${path}`, {
9651
+ headers: {
9652
+ Cookie: Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join("; "),
9653
+ Accept: "application/vnd.api+json, application/json",
9654
+ "X-Requested-With": "XMLHttpRequest"
9655
+ }
9656
+ });
9657
+ for (const raw of res.headers.getSetCookie?.() ?? []) {
9658
+ const m = raw.match(/^([^=]+)=([^;]*)/);
9659
+ if (m) session.cookies[m[1]] = m[2];
9660
+ }
9661
+ const text = await res.text();
9662
+ let body = null;
9663
+ try {
9664
+ body = text ? JSON.parse(text) : null;
9665
+ } catch {
9666
+ }
9667
+ if (!res.ok) {
9668
+ const detail = body?.errors?.map((e) => e.detail ?? e.title).join("; ") || text.slice(0, 300);
9669
+ throw new Error(`GET ${path} failed (HTTP ${res.status}): ${detail}`);
9670
+ }
9671
+ return body;
9672
+ }
9673
+ function htmlToText(html) {
9674
+ if (!html) return "";
9675
+ 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();
9676
+ }
9677
+ function reasonsFrom(rejection) {
9678
+ const reasons = rejection?.attributes?.reasons ?? [];
9679
+ return (Array.isArray(reasons) ? reasons : []).map((r) => ({
9680
+ section: r.reasonSection ?? r.section,
9681
+ description: r.reasonDescription ?? r.description,
9682
+ code: r.reasonCode ?? r.code
9683
+ }));
9684
+ }
9685
+ async function getThreadMessages(session, threadId) {
9686
+ const body = await iris2(session, `/resolutionCenterThreads/${threadId}/resolutionCenterMessages?include=fromActor,rejections`);
9687
+ const included = new Map((body?.included ?? []).map((i) => [`${i.type}:${i.id}`, i]));
9688
+ return (body?.data ?? []).map((m) => {
9689
+ const actor = m.relationships?.fromActor?.data;
9690
+ const actorRow = actor ? included.get(`${actor.type}:${actor.id}`) : null;
9691
+ const rejections = (m.relationships?.rejections?.data ?? []).map((r) => included.get(`${r.type}:${r.id}`)).filter(Boolean);
9692
+ return {
9693
+ id: m.id,
9694
+ createdDate: m.attributes?.createdDate ?? null,
9695
+ from: actorRow?.attributes?.actorType ?? actorRow?.attributes?.userFirstName ?? null,
9696
+ body: htmlToText(m.attributes?.messageBody),
9697
+ reasons: rejections.flatMap(reasonsFrom)
9698
+ };
9699
+ });
9700
+ }
9701
+ async function getReviewFeedback(session, appId, opts = {}) {
9702
+ await selectProviderForApp(session, appId, opts.onLog);
9703
+ const subs = await iris2(session, `/apps/${appId}/reviewSubmissions?limit=10`);
9704
+ const out = [];
9705
+ for (const s of subs?.data ?? []) {
9706
+ const state = s.attributes?.state ?? "";
9707
+ if (opts.onlyOpen && state !== "UNRESOLVED_ISSUES") continue;
9708
+ const threadsBody = await iris2(session, `/resolutionCenterThreads?filter[reviewSubmission]=${s.id}&include=reviewSubmission`);
9709
+ const threads = [];
9710
+ for (const t of threadsBody?.data ?? []) {
9711
+ threads.push({
9712
+ id: t.id,
9713
+ threadType: t.attributes?.threadType ?? null,
9714
+ state: t.attributes?.state ?? null,
9715
+ createdDate: t.attributes?.createdDate ?? null,
9716
+ messages: await getThreadMessages(session, t.id)
9717
+ });
9718
+ }
9719
+ if (threads.length || !opts.onlyOpen) {
9720
+ out.push({ submissionId: s.id, state, submittedDate: s.attributes?.submittedDate ?? null, threads });
9721
+ }
9722
+ }
9723
+ return out;
9724
+ }
9725
+ var IRIS_BASE3;
9726
+ var init_review = __esm({
9727
+ "src/review/index.ts"() {
9728
+ init_privacy();
9729
+ IRIS_BASE3 = "https://appstoreconnect.apple.com/iris/v1";
9730
+ }
9731
+ });
8544
9732
  function createRunCommand() {
8545
9733
  const cmd = new 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
9734
  const { loadFastfile: loadFastfile2 } = await Promise.resolve().then(() => (init_fastfile(), fastfile_exports));
@@ -8661,10 +9849,10 @@ function createPilotCommand() {
8661
9849
  const cmd = new Command("pilot").description("Manage TestFlight builds and testers");
8662
9850
  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
9851
  const { uploadToTestFlight: uploadToTestFlight2 } = await Promise.resolve().then(() => (init_pilot(), pilot_exports));
8664
- const { readFile: readFile8 } = await import('fs/promises');
9852
+ const { readFile: readFile9 } = await import('fs/promises');
8665
9853
  let apiPrivateKey = "";
8666
9854
  if (opts.apiKey) {
8667
- apiPrivateKey = await readFile8(opts.apiKey, "utf-8");
9855
+ apiPrivateKey = await readFile9(opts.apiKey, "utf-8");
8668
9856
  }
8669
9857
  console.log(`Uploading ${opts.ipa}...`);
8670
9858
  const result = await uploadToTestFlight2({
@@ -8696,7 +9884,10 @@ function createPilotCommand() {
8696
9884
  return cmd;
8697
9885
  }
8698
9886
  function createDeliverCommand() {
8699
- return new 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) => {
9887
+ return new 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(
9888
+ "--assets-path <path>",
9889
+ "App Asset Library folder: <locale>/{screenshots,previews,header,search_results}/"
9890
+ ).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
9891
  const { resolveClientFromEnv: resolveClientFromEnv2 } = await Promise.resolve().then(() => (init_options(), options_exports));
8701
9892
  const { uploadToAppStore: uploadToAppStore2 } = await Promise.resolve().then(() => (init_deliver(), deliver_exports));
8702
9893
  const client = await resolveClientFromEnv2();
@@ -8715,7 +9906,12 @@ function createDeliverCommand() {
8715
9906
  submitForReview: opts.submitForReview,
8716
9907
  skipBinaryUpload: opts.skipBinaryUpload,
8717
9908
  skipMetadata: opts.skipMetadata,
8718
- skipScreenshots: opts.skipScreenshots
9909
+ skipScreenshots: opts.skipScreenshots,
9910
+ assetsPath: opts.assetsPath,
9911
+ useAssetLibrary: opts.assetLibrary,
9912
+ overwriteAssets: opts.overwriteAssets,
9913
+ skipAssets: opts.skipAssets,
9914
+ onOutput: (chunk) => process.stdout.write(chunk)
8719
9915
  });
8720
9916
  console.log(`App: ${result.appId}`);
8721
9917
  console.log(`Version: ${result.versionId}`);
@@ -8819,6 +10015,155 @@ function createLanesCommand() {
8819
10015
  }
8820
10016
  });
8821
10017
  }
10018
+ function createPrivacyCommand() {
10019
+ return new 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) => {
10020
+ const { signInWithAppleId: signInWithAppleId2, saveAppleSession: saveAppleSession2 } = await Promise.resolve().then(() => (init_session_store(), session_store_exports));
10021
+ const { uploadAppPrivacyDetails: uploadAppPrivacyDetails2, validateUsages: validateUsages2 } = await Promise.resolve().then(() => (init_privacy(), privacy_exports));
10022
+ const usages = JSON.parse(readFileSync(opts.jsonPath, "utf8"));
10023
+ validateUsages2(usages);
10024
+ const session = await signInWithAppleId2({
10025
+ email: opts.username,
10026
+ askEmail: () => ask("Apple ID: "),
10027
+ askPassword: async (email) => process.env.FASTNODE_PASSWORD ?? process.env.FASTLANE_PASSWORD ?? await ask(`Password for ${email}: `, true),
10028
+ fresh: opts.freshLogin,
10029
+ onLog: (m) => console.log(m)
10030
+ });
10031
+ const result = await uploadAppPrivacyDetails2({
10032
+ session,
10033
+ appId: opts.appId,
10034
+ usages,
10035
+ skipPublish: opts.skipPublish,
10036
+ onLog: (m) => console.log(m)
10037
+ });
10038
+ saveAppleSession2(session);
10039
+ console.log(`Done: ${result.created} answers${result.published ? ", published" : ""}.`);
10040
+ });
10041
+ }
10042
+ function ask(question, secret = false) {
10043
+ let muted = false;
10044
+ const output = new Writable({
10045
+ write(chunk, _enc, cb) {
10046
+ if (!muted) process.stdout.write(chunk);
10047
+ cb();
10048
+ }
10049
+ });
10050
+ const rl = createInterface({ input: process.stdin, output, terminal: true });
10051
+ return new Promise((resolve4) => {
10052
+ rl.question(question, (answer) => {
10053
+ rl.close();
10054
+ if (secret) process.stdout.write("\n");
10055
+ resolve4(answer.trim());
10056
+ });
10057
+ muted = secret;
10058
+ });
10059
+ }
10060
+ function createRejectionsCommand() {
10061
+ return new 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) => {
10062
+ const { signInWithAppleId: signInWithAppleId2, saveAppleSession: saveAppleSession2 } = await Promise.resolve().then(() => (init_session_store(), session_store_exports));
10063
+ const { getReviewFeedback: getReviewFeedback2 } = await Promise.resolve().then(() => (init_review(), review_exports));
10064
+ const session = await signInWithAppleId2({
10065
+ email: opts.username,
10066
+ askEmail: () => ask2("Apple ID: "),
10067
+ askPassword: async (email) => process.env.FASTNODE_PASSWORD ?? process.env.FASTLANE_PASSWORD ?? await ask2(`Password for ${email}: `, true),
10068
+ fresh: opts.freshLogin,
10069
+ onLog: (m) => console.error(m)
10070
+ });
10071
+ const all = {};
10072
+ for (const appId of opts.appId) {
10073
+ const feedback = await getReviewFeedback2(session, appId, { onlyOpen: !opts.all, onLog: (m) => console.error(m) });
10074
+ all[appId] = feedback;
10075
+ if (opts.json) continue;
10076
+ console.log(`
10077
+ \u2550\u2550 App ${appId} \u2550\u2550`);
10078
+ if (!feedback.length) console.log("No open App Review feedback.");
10079
+ for (const s of feedback) {
10080
+ console.log(`
10081
+ Submission ${s.submissionId} \u2014 ${s.state} (submitted ${s.submittedDate ?? "?"})`);
10082
+ for (const t of s.threads) {
10083
+ console.log(` Thread ${t.threadType ?? "?"} \xB7 ${t.state ?? "?"} \xB7 ${t.createdDate ?? ""}`);
10084
+ for (const m of t.messages) {
10085
+ console.log(`
10086
+ \u2500\u2500 ${m.from ?? "App Review"} \xB7 ${m.createdDate ?? ""}`);
10087
+ for (const r of m.reasons) console.log(` \u25B8 ${[r.section, r.description].filter(Boolean).join(" \u2014 ")}${r.code ? ` [${r.code}]` : ""}`);
10088
+ console.log(m.body.split("\n").map((l) => ` ${l}`).join("\n"));
10089
+ }
10090
+ }
10091
+ }
10092
+ }
10093
+ saveAppleSession2(session);
10094
+ if (opts.json) console.log(JSON.stringify(all, null, 2));
10095
+ });
10096
+ }
10097
+ function ask2(question, secret = false) {
10098
+ let muted = false;
10099
+ const output = new Writable({ write(chunk, _enc, cb) {
10100
+ if (!muted) process.stdout.write(chunk);
10101
+ cb();
10102
+ } });
10103
+ const rl = createInterface({ input: process.stdin, output, terminal: true });
10104
+ return new Promise((resolve4) => {
10105
+ rl.question(question, (a) => {
10106
+ rl.close();
10107
+ if (secret) process.stdout.write("\n");
10108
+ resolve4(a.trim());
10109
+ });
10110
+ muted = secret;
10111
+ });
10112
+ }
10113
+ function createAssetsCommand() {
10114
+ const assets = new Command("assets").description(
10115
+ "App Store asset library \u2014 screenshots, previews, product page headers, search results assets"
10116
+ );
10117
+ 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) => {
10118
+ const { resolveClientFromEnv: resolveClientFromEnv2 } = await Promise.resolve().then(() => (init_options(), options_exports));
10119
+ const client = await resolveClientFromEnv2();
10120
+ const feature = opts.feature;
10121
+ const refData = await client.appAssetLibraries.refData({
10122
+ features: [feature],
10123
+ placementTypes: opts.placementType ? [opts.placementType] : void 0
10124
+ });
10125
+ if (opts.json) {
10126
+ console.log(JSON.stringify(refData, null, 2));
10127
+ return;
10128
+ }
10129
+ const policies = refData.features?.find((f) => f.featureId === feature)?.placementPolicies ?? [];
10130
+ const specs = new Map([...refData.imageSpecs ?? [], ...refData.videoSpecs ?? []].map((s) => [s.specId, s]));
10131
+ for (const type of refData.placementTypes ?? []) {
10132
+ if (opts.placementType && type.placementTypeId !== opts.placementType) continue;
10133
+ const policy = policies.find((p) => p.placementType === type.placementTypeId);
10134
+ if (policies.length && !policy) continue;
10135
+ console.log(`
10136
+ ${type.placementTypeId} (${(type.acceptsAssetCategories ?? []).join(", ")})`);
10137
+ for (const mapping of type.specMappings ?? []) {
10138
+ const max = policy?.groupLimits?.find((l) => l.groupIds.includes(mapping.placementGroupId))?.maxCount;
10139
+ const sizes = mapping.specs.map((id) => specs.get(id)).filter((s) => Boolean(s)).map((s) => {
10140
+ const d = s.dimensions;
10141
+ const w = d.minWidth === d.maxWidth ? `${d.minWidth}` : `${d.minWidth}-${d.maxWidth}`;
10142
+ const h = d.minHeight === d.maxHeight ? `${d.minHeight}` : `${d.minHeight}-${d.maxHeight}`;
10143
+ return `${w}\xD7${h}`;
10144
+ });
10145
+ console.log(` ${mapping.placementGroupId.padEnd(44)} ${max ? `max ${max} ` : ""}${sizes.join(", ")}`);
10146
+ }
10147
+ }
10148
+ });
10149
+ assets.command("list").description("List images and videos in an app's asset library").requiredOption("-a, --app-identifier <id>", "Bundle identifier").action(async (opts) => {
10150
+ const { resolveClientFromEnv: resolveClientFromEnv2 } = await Promise.resolve().then(() => (init_options(), options_exports));
10151
+ const client = await resolveClientFromEnv2();
10152
+ const app = await client.apps.findByBundleId(opts.appIdentifier);
10153
+ if (!app) throw new Error(`App not found: ${opts.appIdentifier}`);
10154
+ const library = await client.appAssetLibraries.getForApp(app.id);
10155
+ const [images, videos] = await Promise.all([
10156
+ client.appAssetLibraries.listImages(library.id),
10157
+ client.appAssetLibraries.listVideos(library.id)
10158
+ ]);
10159
+ for (const a of [...images, ...videos]) {
10160
+ const at = a.attributes;
10161
+ console.log(`${a.type === "appAssetLibraryImages" ? "image" : "video"} ${a.id} ${String(at.state).padEnd(22)} ${at.category} ${at.referenceName ?? at.fileName}`);
10162
+ }
10163
+ if (!images.length && !videos.length) console.log("Asset library is empty");
10164
+ });
10165
+ return assets;
10166
+ }
8822
10167
 
8823
10168
  // src/cli/index.ts
8824
10169
  init_options();
@@ -8837,6 +10182,9 @@ function createCli() {
8837
10182
  program.addCommand(createMatchCommand());
8838
10183
  program.addCommand(createActionsCommand());
8839
10184
  program.addCommand(createLanesCommand());
10185
+ program.addCommand(createPrivacyCommand());
10186
+ program.addCommand(createRejectionsCommand());
10187
+ program.addCommand(createAssetsCommand());
8840
10188
  return program;
8841
10189
  }
8842
10190