@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
@@ -178,8 +178,8 @@ async function revokeExpiredCertificates(options) {
178
178
  return count;
179
179
  }
180
180
  async function importToKeychain(certData, keychainPath, keychainPassword) {
181
- const { tmpdir: tmpdir8 } = await import('os');
182
- const tempCertPath = path.join(tmpdir8(), `fastnode-cert-${Date.now()}.cer`);
181
+ const { tmpdir: tmpdir7 } = await import('os');
182
+ const tempCertPath = path.join(tmpdir7(), `fastnode-cert-${Date.now()}.cer`);
183
183
  await promises.writeFile(tempCertPath, certData);
184
184
  try {
185
185
  const args = [
@@ -1748,6 +1748,20 @@ var init_srp = __esm({
1748
1748
  };
1749
1749
  }
1750
1750
  });
1751
+ async function appleJson(res, what) {
1752
+ const text = await res.text();
1753
+ try {
1754
+ return JSON.parse(text);
1755
+ } catch {
1756
+ const contentType = (res.headers.get("content-type") ?? "").split(";")[0].trim();
1757
+ const title = /<title>([^<]{1,120})<\/title>/i.exec(text)?.[1]?.trim();
1758
+ const looksHtml = /^\s*<(!doctype|html)/i.test(text) || contentType === "text/html";
1759
+ const page = looksHtml ? "an HTML page" : contentType ? `a ${contentType} response` : "a non-JSON response";
1760
+ throw new Error(
1761
+ `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.`
1762
+ );
1763
+ }
1764
+ }
1751
1765
  function checkPortalResponse(body, session) {
1752
1766
  if (!body || typeof body !== "object") return;
1753
1767
  if (body.resultCode === 4100) {
@@ -1824,7 +1838,7 @@ async function beginLogin(config) {
1824
1838
  }
1825
1839
  async function fetchTwoFactorChallenge(pending) {
1826
1840
  const r = await fetch(`${AUTH_BASE}`, { headers: buildAuthHeadersFromPending(pending) });
1827
- const body = await r.json();
1841
+ const body = await appleJson(r, "two-factor challenge");
1828
1842
  const trustedPhoneNumbers = (body.trustedPhoneNumbers ?? []).map((p) => ({
1829
1843
  id: p.id,
1830
1844
  numberWithDialCode: p.numberWithDialCode,
@@ -1845,7 +1859,8 @@ async function requestSmsCode(input) {
1845
1859
  body: JSON.stringify({ phoneNumber: { id: input.phoneId }, mode: input.mode ?? "sms" })
1846
1860
  });
1847
1861
  }
1848
- async function submitTwoFactorCode(input) {
1862
+ async function submitTwoFactorCode(rawInput) {
1863
+ const input = { ...rawInput, code: rawInput.code.replace(/[\s\u00a0]+/g, "") };
1849
1864
  const log = input.onLog ?? (() => {
1850
1865
  });
1851
1866
  let path;
@@ -1957,9 +1972,12 @@ async function doSirp(user, password, serviceKey, cookieStr, hashcash) {
1957
1972
  body: JSON.stringify({ a: aBase64, accountName: user, protocols: ["s2k", "s2k_fo"] }),
1958
1973
  redirect: "manual"
1959
1974
  });
1960
- const initBody = await initRes.json();
1975
+ const initBody = await appleJson(initRes, "sign-in init");
1961
1976
  if (initBody.serviceErrors) throw new Error(`Apple auth error: ${JSON.stringify(initBody.serviceErrors)}`);
1962
1977
  const { iteration: iterations, salt: saltB64, b: bB64, c, protocol } = initBody;
1978
+ if (!iterations || !saltB64 || !bB64 || !c || !protocol) {
1979
+ throw new Error(`Apple's sign-in init answered without a challenge (HTTP ${initRes.status}): ${JSON.stringify(initBody).slice(0, 300)}`);
1980
+ }
1963
1981
  const salt = Buffer.from(saltB64, "base64");
1964
1982
  const b = Buffer.from(bB64, "base64");
1965
1983
  const encryptedPassword = applePbkdf2(password, salt, iterations, 32, protocol);
@@ -2002,7 +2020,7 @@ async function fetchOlympusSession(session) {
2002
2020
  const cookieStr = Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join("; ");
2003
2021
  const res = await fetch(OLYMPUS_SESSION, { headers: { Cookie: cookieStr, Accept: "application/json" } });
2004
2022
  if (res.ok) {
2005
- const body = await res.json();
2023
+ const body = await appleJson(res, "session lookup");
2006
2024
  if (body?.user?.emailAddress) session.userEmail = body.user.emailAddress;
2007
2025
  if (body?.provider?.providerId) session.teamId = String(body.provider.providerId);
2008
2026
  }
@@ -2014,7 +2032,7 @@ async function fetchContractMessages(session) {
2014
2032
  headers: { Cookie: cookieStr, Accept: "application/json" }
2015
2033
  });
2016
2034
  if (!res.ok) return [];
2017
- const body = await res.json();
2035
+ const body = await appleJson(res, "agreements lookup");
2018
2036
  return (body ?? []).map((m) => ({
2019
2037
  id: m.id ?? "",
2020
2038
  group: m.group ?? "",
@@ -2025,10 +2043,22 @@ async function fetchContractMessages(session) {
2025
2043
  return [];
2026
2044
  }
2027
2045
  }
2028
- async function fetchServiceKey() {
2029
- const res = await fetch(WIDGET_KEY_URL);
2030
- const body = await res.json();
2031
- if (!body?.authServiceKey) throw new Error("Could not fetch Apple service key");
2046
+ function serviceKeyFromLocation(location) {
2047
+ if (!location) return null;
2048
+ try {
2049
+ const key = new URL(location).searchParams.get("widgetKey")?.trim();
2050
+ return key && /^[0-9a-f]{16,}$/i.test(key) ? key : null;
2051
+ } catch {
2052
+ return null;
2053
+ }
2054
+ }
2055
+ async function fetchServiceKey(fetchImpl = fetch) {
2056
+ const head = await fetchImpl(LOGOUT_URL, { method: "HEAD", redirect: "manual" });
2057
+ const fromRedirect = serviceKeyFromLocation(head.headers.get("location"));
2058
+ if (fromRedirect) return fromRedirect;
2059
+ const res = await fetchImpl(WIDGET_KEY_URL);
2060
+ const body = await appleJson(res, "service key request");
2061
+ 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.");
2032
2062
  return body.authServiceKey;
2033
2063
  }
2034
2064
  function buildAuthHeaders(session) {
@@ -2076,7 +2106,7 @@ async function defaultPrompt(message) {
2076
2106
  });
2077
2107
  });
2078
2108
  }
2079
- var AUTH_BASE, OLYMPUS_SESSION, WIDGET_KEY_URL, ProgramLicenseAgreementError;
2109
+ var AUTH_BASE, OLYMPUS_SESSION, WIDGET_KEY_URL, ProgramLicenseAgreementError, LOGOUT_URL;
2080
2110
  var init_apple_session = __esm({
2081
2111
  "src/client/apple-session.ts"() {
2082
2112
  init_srp();
@@ -2091,6 +2121,7 @@ var init_apple_session = __esm({
2091
2121
  }
2092
2122
  agreements;
2093
2123
  };
2124
+ LOGOUT_URL = "https://appstoreconnect.apple.com/logout";
2094
2125
  }
2095
2126
  });
2096
2127
 
@@ -2407,9 +2438,10 @@ async function uploadWithTransporter(options) {
2407
2438
  const transporterBin = await resolveTransporter();
2408
2439
  const platform = options.platform ?? "ios";
2409
2440
  const timeout = options.timeout ?? 18e5;
2410
- const keyDir = await promises.mkdtemp(path.join(os.tmpdir(), "fastnode-itms-"));
2441
+ const keyDir = path.join(os.homedir(), ".appstoreconnect", "private_keys");
2442
+ await promises.mkdir(keyDir, { recursive: true });
2411
2443
  const keyPath = path.join(keyDir, `AuthKey_${options.apiKeyId}.p8`);
2412
- await promises.writeFile(keyPath, options.apiPrivateKey);
2444
+ await promises.writeFile(keyPath, options.apiPrivateKey, { mode: 384 });
2413
2445
  return new Promise((resolve4) => {
2414
2446
  const args = [
2415
2447
  "-m",
@@ -2425,6 +2457,9 @@ async function uploadWithTransporter(options) {
2425
2457
  "-WONoPause",
2426
2458
  "true"
2427
2459
  ];
2460
+ if (options.assetDescriptionPath) {
2461
+ args.push("-assetDescription", options.assetDescriptionPath);
2462
+ }
2428
2463
  if (platform !== "ios") {
2429
2464
  args.push("-bundleType", platform);
2430
2465
  }
@@ -2448,13 +2483,13 @@ async function uploadWithTransporter(options) {
2448
2483
  child.stderr.on("data", onData);
2449
2484
  child.on("close", async (code) => {
2450
2485
  clearTimeout(timer);
2451
- await promises.rm(keyDir, { recursive: true, force: true }).catch(() => {
2486
+ await promises.rm(keyPath, { force: true }).catch(() => {
2452
2487
  });
2453
2488
  resolve4({ success: code === 0, output, backend: "transporter" });
2454
2489
  });
2455
2490
  child.on("error", async (err) => {
2456
2491
  clearTimeout(timer);
2457
- await promises.rm(keyDir, { recursive: true, force: true }).catch(() => {
2492
+ await promises.rm(keyPath, { force: true }).catch(() => {
2458
2493
  });
2459
2494
  resolve4({
2460
2495
  success: false,
@@ -2746,6 +2781,311 @@ var init_metadata = __esm({
2746
2781
  };
2747
2782
  }
2748
2783
  });
2784
+ function categoryFor(placementType) {
2785
+ return placementType === "APP_SCREENSHOT" || placementType === "IMESSAGE_APP_SCREENSHOT" || placementType === "APP_PREVIEW" ? "APP_SCREENSHOTS_AND_PREVIEWS" : "CREATIVE_ASSETS";
2786
+ }
2787
+ function readImageSize(data) {
2788
+ if (data.length >= 26 && data.readUInt32BE(0) === 2303741511) {
2789
+ const colorType = data[25];
2790
+ return {
2791
+ width: data.readUInt32BE(16),
2792
+ height: data.readUInt32BE(20),
2793
+ alpha: colorType === 4 || colorType === 6
2794
+ };
2795
+ }
2796
+ if (data.length >= 4 && data[0] === 255 && data[1] === 216) {
2797
+ let offset = 2;
2798
+ while (offset + 9 < data.length) {
2799
+ if (data[offset] !== 255) return null;
2800
+ const marker = data[offset + 1];
2801
+ const length = data.readUInt16BE(offset + 2);
2802
+ const isSof = marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204;
2803
+ if (isSof) {
2804
+ return { height: data.readUInt16BE(offset + 5), width: data.readUInt16BE(offset + 7), alpha: false };
2805
+ }
2806
+ offset += 2 + length;
2807
+ }
2808
+ }
2809
+ return null;
2810
+ }
2811
+ function fits(spec, width, height) {
2812
+ const d = spec.dimensions;
2813
+ return width >= d.minWidth && width <= d.maxWidth && height >= d.minHeight && height <= d.maxHeight;
2814
+ }
2815
+ function groupsForFeature(refData, feature, placementType) {
2816
+ const mappings = refData.placementTypes?.find((p) => p.placementTypeId === placementType)?.specMappings ?? [];
2817
+ const all = mappings.map((m) => m.placementGroupId);
2818
+ const policy = refData.features?.find((f) => f.featureId === feature)?.placementPolicies?.find((p) => p.placementType === placementType);
2819
+ const limited = policy?.groupLimits?.flatMap((l) => l.groupIds);
2820
+ return limited?.length ? all.filter((g) => limited.includes(g)) : all;
2821
+ }
2822
+ function maxCountFor(refData, feature, placementType, placementGroup) {
2823
+ return refData.features?.find((f) => f.featureId === feature)?.placementPolicies?.find((p) => p.placementType === placementType)?.groupLimits?.find((l) => l.groupIds.includes(placementGroup))?.maxCount;
2824
+ }
2825
+ function resolveImageGroups(refData, feature, placementType, size) {
2826
+ const specs = (refData.imageSpecs ?? []).filter(
2827
+ (s) => s.compatiblePlacementTypes.includes(placementType) && fits(s, size.width, size.height)
2828
+ );
2829
+ const specIds = new Set(specs.map((s) => s.specId));
2830
+ const allowed = new Set(groupsForFeature(refData, feature, placementType));
2831
+ const mappings = refData.placementTypes?.find((p) => p.placementTypeId === placementType)?.specMappings ?? [];
2832
+ const groups = mappings.filter((m) => allowed.has(m.placementGroupId) && m.specs.some((id) => specIds.has(id))).map((m) => m.placementGroupId);
2833
+ return { groups: [...new Set(groups)], spec: specs[0] };
2834
+ }
2835
+ async function runUploadOperations(ops, data) {
2836
+ for (const op of ops) {
2837
+ const headers = {};
2838
+ for (const h of op.requestHeaders) headers[h.name] = h.value;
2839
+ const res = await fetch(op.url, {
2840
+ method: op.method,
2841
+ headers,
2842
+ body: data.subarray(op.offset, op.offset + op.length)
2843
+ });
2844
+ if (!res.ok) {
2845
+ throw new Error(`Upload operation failed: ${op.method} ${new URL(op.url).host} \u2192 HTTP ${res.status}`);
2846
+ }
2847
+ }
2848
+ }
2849
+ async function uploadLibraryAsset(options) {
2850
+ const { client, libraryId, filePath, category } = options;
2851
+ const data = await promises.readFile(filePath);
2852
+ const fileName = path.basename(filePath);
2853
+ const kind = VIDEO_EXTENSIONS.includes(path.extname(filePath).toLowerCase()) ? "video" : "image";
2854
+ const hash = crypto.createHash("sha256").update(data).digest("hex").slice(0, 12);
2855
+ const referenceName = `${options.referenceName ?? fileName} #${hash}`;
2856
+ const media = kind === "image" ? client.appAssetLibraryImages : client.appAssetLibraryVideos;
2857
+ const filter = { filter: { referenceName } };
2858
+ const existing = kind === "image" ? await client.appAssetLibraries.listImages(libraryId, filter) : await client.appAssetLibraries.listVideos(libraryId, filter);
2859
+ const reusable = existing.find(
2860
+ (a) => a.attributes.category === category && a.attributes.state && READY_STATES.includes(a.attributes.state)
2861
+ );
2862
+ if (reusable) {
2863
+ return { kind, id: reusable.id, state: reusable.attributes.state, reused: true };
2864
+ }
2865
+ const reserved = kind === "image" ? await client.appAssetLibraryImages.create({ libraryId, fileName, fileSize: data.length, category, referenceName }) : await client.appAssetLibraryVideos.create({
2866
+ libraryId,
2867
+ fileName,
2868
+ fileSize: data.length,
2869
+ category,
2870
+ referenceName,
2871
+ previewFrameTimeCode: options.previewFrameTimeCode
2872
+ });
2873
+ await runUploadOperations(reserved.attributes.uploadOperations ?? [], data);
2874
+ await media.update(reserved.id, { uploaded: true });
2875
+ options.log?.(`uploaded ${fileName} (${kind}, ${data.length} bytes)`);
2876
+ const state = await waitForAssetReady(
2877
+ () => media.get(reserved.id),
2878
+ options.pollIntervalMs ?? 3e3,
2879
+ options.timeoutMs ?? (kind === "video" ? 30 * 6e4 : 5 * 6e4)
2880
+ );
2881
+ return { kind, id: reserved.id, state, reused: false };
2882
+ }
2883
+ async function waitForAssetReady(get, intervalMs, timeoutMs) {
2884
+ const deadline = Date.now() + timeoutMs;
2885
+ for (; ; ) {
2886
+ const asset = await get();
2887
+ const state = asset.attributes.state;
2888
+ if (state === "FAILED") {
2889
+ const why = (asset.attributes.stateDetails ?? []).map((d) => d.message ?? d.code).filter(Boolean).join("; ");
2890
+ throw new Error(`Asset processing failed${why ? `: ${why}` : ""}`);
2891
+ }
2892
+ if (state && READY_STATES.includes(state)) return state;
2893
+ if (Date.now() > deadline) throw new Error(`Asset still ${state ?? "unknown"} after ${timeoutMs / 1e3}s`);
2894
+ await new Promise((r) => setTimeout(r, intervalMs));
2895
+ }
2896
+ }
2897
+ async function listFiles(dir) {
2898
+ return (await promises.readdir(dir)).filter((f) => !f.startsWith(".")).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }));
2899
+ }
2900
+ async function isDir(path) {
2901
+ return (await promises.stat(path)).isDirectory();
2902
+ }
2903
+ async function planLocale(localeDir, refData, feature, folders, warn) {
2904
+ const planned = [];
2905
+ for (const [folder, placementType] of Object.entries(folders)) {
2906
+ const typeDir = path.join(localeDir, folder);
2907
+ if (!fs.existsSync(typeDir)) continue;
2908
+ const known = new Set(groupsForFeature(refData, feature, placementType));
2909
+ const entries = [];
2910
+ for (const entry of await listFiles(typeDir)) {
2911
+ const full = path.join(typeDir, entry);
2912
+ if (await isDir(full)) {
2913
+ if (!known.has(entry)) {
2914
+ warn(`${path.relative(localeDir, full)}: "${entry}" is not a ${placementType} placement group for ${feature} \u2014 skipped`);
2915
+ continue;
2916
+ }
2917
+ for (const f of await listFiles(full)) entries.push({ filePath: path.join(full, f), group: entry });
2918
+ } else {
2919
+ entries.push({ filePath: full });
2920
+ }
2921
+ }
2922
+ for (const { filePath, group } of entries) {
2923
+ const ext = path.extname(filePath).toLowerCase();
2924
+ const isVideo = VIDEO_EXTENSIONS.includes(ext);
2925
+ if (!isVideo && !IMAGE_EXTENSIONS.includes(ext)) continue;
2926
+ const needsVideo = placementType === "APP_PREVIEW";
2927
+ const needsImage = placementType === "APP_SCREENSHOT" || placementType === "IMESSAGE_APP_SCREENSHOT";
2928
+ if (needsVideo && !isVideo || needsImage && isVideo) {
2929
+ warn(`${path.relative(localeDir, filePath)}: ${isVideo ? "video" : "image"} cannot be a ${placementType} \u2014 skipped`);
2930
+ continue;
2931
+ }
2932
+ if (group) {
2933
+ planned.push({ filePath, placementType, groups: [group] });
2934
+ continue;
2935
+ }
2936
+ if (isVideo) {
2937
+ if (known.size === 1) planned.push({ filePath, placementType, groups: [...known] });
2938
+ else warn(`${path.relative(localeDir, filePath)}: put videos in a placement-group folder (one of ${[...known].join(", ")}) \u2014 skipped`);
2939
+ continue;
2940
+ }
2941
+ const size = readImageSize(await promises.readFile(filePath));
2942
+ if (!size) {
2943
+ warn(`${path.relative(localeDir, filePath)}: could not read image size \u2014 skipped`);
2944
+ continue;
2945
+ }
2946
+ const { groups, spec } = resolveImageGroups(refData, feature, placementType, size);
2947
+ if (!groups.length) {
2948
+ warn(`${path.relative(localeDir, filePath)}: ${size.width}\xD7${size.height} matches no ${placementType} spec \u2014 skipped`);
2949
+ continue;
2950
+ }
2951
+ if (size.alpha && spec && spec.alphaAllowed === false) {
2952
+ warn(`${path.relative(localeDir, filePath)}: has an alpha channel, which Apple rejects for this spec`);
2953
+ }
2954
+ planned.push({ filePath, placementType, groups: groups.slice(0, 1) });
2955
+ }
2956
+ }
2957
+ return planned;
2958
+ }
2959
+ async function uploadAssetLibrary(options) {
2960
+ const { client } = options;
2961
+ const log = options.log ?? (() => {
2962
+ });
2963
+ const feature = "APP_STORE_VERSIONS";
2964
+ const result = { uploaded: 0, reused: 0, placed: 0, removed: 0, warnings: [] };
2965
+ const warn = (line) => {
2966
+ result.warnings.push(line);
2967
+ log(`warning: ${line}`);
2968
+ };
2969
+ const [library, refData, localizations] = await Promise.all([
2970
+ client.appAssetLibraries.getForApp(options.appId),
2971
+ client.appAssetLibraries.refData({ features: [feature] }),
2972
+ client.appStoreVersions.getLocalizations(options.versionId)
2973
+ ]);
2974
+ const folders = { ...options.folders ?? ASSET_FOLDERS };
2975
+ if (options.rootPlacementType) folders["."] = options.rootPlacementType;
2976
+ const uploads = /* @__PURE__ */ new Map();
2977
+ const uploadOnce = (filePath, placementType) => {
2978
+ const key = `${filePath}|${categoryFor(placementType)}`;
2979
+ if (!uploads.has(key)) {
2980
+ uploads.set(
2981
+ key,
2982
+ uploadLibraryAsset({
2983
+ client,
2984
+ libraryId: library.id,
2985
+ filePath,
2986
+ category: categoryFor(placementType),
2987
+ referenceName: path.relative(options.assetsPath, filePath),
2988
+ log
2989
+ }).then((a) => {
2990
+ if (a.reused) result.reused++;
2991
+ else result.uploaded++;
2992
+ return a;
2993
+ })
2994
+ );
2995
+ }
2996
+ return uploads.get(key);
2997
+ };
2998
+ for (const loc of localizations) {
2999
+ const localeDir = path.join(options.assetsPath, loc.attributes.locale);
3000
+ if (!fs.existsSync(localeDir)) continue;
3001
+ const target = { kind: "appStoreVersionLocalization", id: loc.id };
3002
+ const plan = await planLocale(localeDir, refData, feature, folders, warn);
3003
+ const slots = /* @__PURE__ */ new Map();
3004
+ for (const p of plan) {
3005
+ for (const group of p.groups) {
3006
+ const key = `${p.placementType}|${group}`;
3007
+ if (!slots.has(key)) slots.set(key, { placementType: p.placementType, group, files: [] });
3008
+ slots.get(key).files.push(p.filePath);
3009
+ }
3010
+ }
3011
+ for (const slot of slots.values()) {
3012
+ const where = `${loc.attributes.locale} ${slot.placementType} ${slot.group}`;
3013
+ const max = maxCountFor(refData, feature, slot.placementType, slot.group);
3014
+ if (max !== void 0 && slot.files.length > max) {
3015
+ warn(`${where}: ${slot.files.length} files but Apple allows ${max} \u2014 keeping the first ${max}`);
3016
+ slot.files = slot.files.slice(0, max);
3017
+ }
3018
+ const existing = await client.appAssetLibraryPlacements.listForTarget(target, {
3019
+ placementType: slot.placementType,
3020
+ placementGroup: slot.group,
3021
+ include: "image,video"
3022
+ });
3023
+ if (options.overwrite) {
3024
+ for (const p of existing) {
3025
+ await client.appAssetLibraryPlacements.delete(p.id);
3026
+ result.removed++;
3027
+ }
3028
+ }
3029
+ const kept = options.overwrite ? [] : existing;
3030
+ const keptAssetIds = new Set(kept.map((p) => placementAssetId(p)).filter(Boolean));
3031
+ const ours = [];
3032
+ for (const filePath of slot.files) {
3033
+ const asset = await uploadOnce(filePath, slot.placementType);
3034
+ if (keptAssetIds.has(asset.id)) continue;
3035
+ const placement = await client.appAssetLibraryPlacements.create({
3036
+ placementType: slot.placementType,
3037
+ placementGroup: slot.group,
3038
+ ...asset.kind === "image" ? { imageId: asset.id } : { videoId: asset.id },
3039
+ target
3040
+ });
3041
+ ours.push(placement.id);
3042
+ result.placed++;
3043
+ }
3044
+ log(`${where}: placed ${ours.length}${kept.length ? `, kept ${kept.length}` : ""}`);
3045
+ const orderable = slot.placementType === "APP_SCREENSHOT" || slot.placementType === "APP_PREVIEW" || slot.placementType === "IMESSAGE_APP_SCREENSHOT";
3046
+ if (orderable && ours.length + kept.length > 1) {
3047
+ await client.appAssetLibraryPlacements.order({
3048
+ placementGroup: slot.group,
3049
+ placementIds: [...ours, ...kept.map((p) => p.id)],
3050
+ target
3051
+ });
3052
+ }
3053
+ }
3054
+ }
3055
+ return result;
3056
+ }
3057
+ function placementAssetId(p) {
3058
+ for (const rel of ["image", "video"]) {
3059
+ const data = p.relationships?.[rel]?.data;
3060
+ if (data?.id) return data.id;
3061
+ }
3062
+ return void 0;
3063
+ }
3064
+ var ASSET_FOLDERS, IMAGE_EXTENSIONS, VIDEO_EXTENSIONS, READY_STATES;
3065
+ var init_assets = __esm({
3066
+ "src/deliver/assets.ts"() {
3067
+ ASSET_FOLDERS = {
3068
+ screenshots: "APP_SCREENSHOT",
3069
+ imessage_screenshots: "IMESSAGE_APP_SCREENSHOT",
3070
+ previews: "APP_PREVIEW",
3071
+ header: "PRODUCT_PAGE_HEADER_ASSET",
3072
+ search_results: "APP_STORE_SEARCH_RESULTS_ASSET",
3073
+ event_card: "EVENT_CARD_ASSET",
3074
+ event_details: "EVENT_DETAILS_PAGE_ASSET"
3075
+ };
3076
+ IMAGE_EXTENSIONS = [".png", ".jpg", ".jpeg"];
3077
+ VIDEO_EXTENSIONS = [".mp4", ".mov", ".m4v"];
3078
+ READY_STATES = [
3079
+ "COMPLETE",
3080
+ "PREPARE_FOR_SUBMISSION",
3081
+ "READY_FOR_REVIEW",
3082
+ "WAITING_FOR_REVIEW",
3083
+ "IN_REVIEW",
3084
+ "ACCEPTED",
3085
+ "APPROVED"
3086
+ ];
3087
+ }
3088
+ });
2749
3089
 
2750
3090
  // src/xcode/upload.ts
2751
3091
  var upload_exports = {};
@@ -2811,8 +3151,14 @@ var init_upload = __esm({
2811
3151
  // src/deliver/index.ts
2812
3152
  var deliver_exports = {};
2813
3153
  __export(deliver_exports, {
3154
+ ASSET_FOLDERS: () => ASSET_FOLDERS,
3155
+ categoryFor: () => categoryFor,
2814
3156
  loadMetadata: () => loadMetadata,
3157
+ readImageSize: () => readImageSize,
3158
+ resolveImageGroups: () => resolveImageGroups,
2815
3159
  submitForReview: () => submitForReview,
3160
+ uploadAssetLibrary: () => uploadAssetLibrary,
3161
+ uploadLibraryAsset: () => uploadLibraryAsset,
2816
3162
  uploadMetadata: () => uploadMetadata,
2817
3163
  uploadScreenshots: () => uploadScreenshots,
2818
3164
  uploadToAppStore: () => uploadToAppStore
@@ -2851,11 +3197,36 @@ async function uploadToAppStore(options) {
2851
3197
  copyright: options.copyright
2852
3198
  });
2853
3199
  }
3200
+ const log = (line) => options.onOutput?.(`${line}
3201
+ `);
2854
3202
  if (!options.skipScreenshots && options.screenshotsPath && fs.existsSync(options.screenshotsPath)) {
2855
- await uploadScreenshots({
3203
+ if (options.useAssetLibrary) {
3204
+ await uploadAssetLibrary({
3205
+ client: options.client,
3206
+ appId: app.id,
3207
+ versionId: version.id,
3208
+ assetsPath: options.screenshotsPath,
3209
+ folders: {},
3210
+ rootPlacementType: "APP_SCREENSHOT",
3211
+ overwrite: options.overwriteAssets,
3212
+ log
3213
+ });
3214
+ } else {
3215
+ await uploadScreenshots({
3216
+ client: options.client,
3217
+ versionId: version.id,
3218
+ screenshotsPath: options.screenshotsPath
3219
+ });
3220
+ }
3221
+ }
3222
+ if (!options.skipAssets && options.assetsPath && fs.existsSync(options.assetsPath)) {
3223
+ await uploadAssetLibrary({
2856
3224
  client: options.client,
3225
+ appId: app.id,
2857
3226
  versionId: version.id,
2858
- screenshotsPath: options.screenshotsPath
3227
+ assetsPath: options.assetsPath,
3228
+ overwrite: options.overwriteAssets,
3229
+ log
2859
3230
  });
2860
3231
  }
2861
3232
  if (!options.skipBinaryUpload) {
@@ -2940,17 +3311,7 @@ async function uploadScreenshots(options) {
2940
3311
  fileName: file,
2941
3312
  fileSize: fileData.length
2942
3313
  });
2943
- const ops = reserved.attributes.uploadOperations;
2944
- if (ops) {
2945
- for (const op of ops) {
2946
- const chunk = fileData.subarray(op.offset, op.offset + op.length);
2947
- const headers = {};
2948
- for (const h of op.requestHeaders) {
2949
- headers[h.name] = h.value;
2950
- }
2951
- await fetch(op.url, { method: op.method, headers, body: chunk });
2952
- }
2953
- }
3314
+ await runUploadOperations(reserved.attributes.uploadOperations ?? [], fileData);
2954
3315
  await options.client.appScreenshots.commitScreenshot(reserved.id, {
2955
3316
  sourceFileChecksum: checksum,
2956
3317
  uploaded: true
@@ -2970,7 +3331,9 @@ function detectDisplayType(filename) {
2970
3331
  var init_deliver = __esm({
2971
3332
  "src/deliver/index.ts"() {
2972
3333
  init_metadata();
3334
+ init_assets();
2973
3335
  init_metadata();
3336
+ init_assets();
2974
3337
  }
2975
3338
  });
2976
3339
 
@@ -2979,6 +3342,7 @@ var produce_exports = {};
2979
3342
  __export(produce_exports, {
2980
3343
  SERVICE_NAME_TO_CAPABILITY: () => SERVICE_NAME_TO_CAPABILITY,
2981
3344
  addCapability: () => addCapability,
3345
+ associateAppGroups: () => associateAppGroups,
2982
3346
  buildCapabilitySettings: () => buildCapabilitySettings,
2983
3347
  createApp: () => createApp,
2984
3348
  enableServices: () => enableServices,
@@ -3073,6 +3437,30 @@ async function enableServices(options) {
3073
3437
  });
3074
3438
  }
3075
3439
  }
3440
+ async function associateAppGroups(options) {
3441
+ if (!options.appGroups?.length) return;
3442
+ await options.portalClient.enableAppGroups(options.bundleIdId).catch(() => {
3443
+ });
3444
+ const existing = await options.portalClient.listAppGroups();
3445
+ const appGroupIds = [];
3446
+ for (const group of options.appGroups) {
3447
+ let record = existing.find((ag) => ag.identifier === group.identifier);
3448
+ if (!record) {
3449
+ record = await options.portalClient.createAppGroup({
3450
+ name: group.name,
3451
+ identifier: group.identifier
3452
+ });
3453
+ }
3454
+ const id = record?.applicationGroup ?? record?.applicationGroupId ?? record?.appGroupId ?? record?.id;
3455
+ if (id) appGroupIds.push(id);
3456
+ }
3457
+ if (appGroupIds.length) {
3458
+ await options.portalClient.assignAppGroups({
3459
+ appIdId: options.bundleIdId,
3460
+ appGroupIds
3461
+ });
3462
+ }
3463
+ }
3076
3464
  async function createApp(options) {
3077
3465
  const existing = await options.client.apps.findByBundleId(options.bundleIdentifier);
3078
3466
  if (existing) return existing;
@@ -3091,6 +3479,13 @@ async function createApp(options) {
3091
3479
  });
3092
3480
  }
3093
3481
  }
3482
+ if (options.appGroups?.length && options.portalClient) {
3483
+ await associateAppGroups({
3484
+ portalClient: options.portalClient,
3485
+ bundleIdId: bundleId.id,
3486
+ appGroups: options.appGroups
3487
+ });
3488
+ }
3094
3489
  return options.client.apps.create({
3095
3490
  bundleIdentifier: options.bundleIdentifier,
3096
3491
  bundleIdId: bundleId.id,
@@ -3900,10 +4295,10 @@ async function parseTestResults(options) {
3900
4295
  } else if (inputPath.endsWith(".plist")) {
3901
4296
  testSuites = parsePlistResults(inputPath);
3902
4297
  } else {
3903
- const stat6 = await import('fs').then(
4298
+ const stat7 = await import('fs').then(
3904
4299
  (fs) => fs.statSync(inputPath)
3905
4300
  );
3906
- if (stat6.isDirectory()) {
4301
+ if (stat7.isDirectory()) {
3907
4302
  requireMacOS("xcresult parsing");
3908
4303
  testSuites = await parseXCResult(inputPath);
3909
4304
  } else {
@@ -6080,9 +6475,22 @@ var init_pre_release_versions = __esm({
6080
6475
  });
6081
6476
 
6082
6477
  // src/resources/review-submission-items.ts
6083
- var ReviewSubmissionItemsResource;
6478
+ var ITEM_RELATIONSHIPS, ReviewSubmissionItemsResource;
6084
6479
  var init_review_submission_items = __esm({
6085
6480
  "src/resources/review-submission-items.ts"() {
6481
+ ITEM_RELATIONSHIPS = {
6482
+ appStoreVersionId: ["appStoreVersion", "appStoreVersions"],
6483
+ appCustomProductPageVersionId: ["appCustomProductPageVersion", "appCustomProductPageVersions"],
6484
+ appStoreVersionExperimentId: ["appStoreVersionExperiment", "appStoreVersionExperiments"],
6485
+ appStoreVersionExperimentV2Id: ["appStoreVersionExperimentV2", "appStoreVersionExperiments"],
6486
+ appEventId: ["appEvent", "appEvents"],
6487
+ appAssetLibraryImageId: ["appAssetLibraryImage", "appAssetLibraryImages"],
6488
+ appAssetLibraryVideoId: ["appAssetLibraryVideo", "appAssetLibraryVideos"],
6489
+ backgroundAssetVersionId: ["backgroundAssetVersion", "backgroundAssetVersions"],
6490
+ inAppPurchaseVersionId: ["inAppPurchaseVersion", "inAppPurchaseVersions"],
6491
+ subscriptionVersionId: ["subscriptionVersion", "subscriptionVersions"],
6492
+ subscriptionGroupVersionId: ["subscriptionGroupVersion", "subscriptionGroupVersions"]
6493
+ };
6086
6494
  ReviewSubmissionItemsResource = class {
6087
6495
  constructor(http) {
6088
6496
  this.http = http;
@@ -6104,32 +6512,20 @@ var init_review_submission_items = __esm({
6104
6512
  );
6105
6513
  return response.data;
6106
6514
  }
6515
+ /**
6516
+ * Add one item to a review submission. Besides versions, asset library
6517
+ * images and videos can be submitted on their own (ASC API 4.5.1) so
6518
+ * creative assets are approved ahead of a campaign.
6519
+ */
6107
6520
  async create(params) {
6108
6521
  const relationships = {
6109
6522
  reviewSubmission: {
6110
6523
  data: { type: "reviewSubmissions", id: params.reviewSubmissionId }
6111
6524
  }
6112
6525
  };
6113
- if (params.appStoreVersionId) {
6114
- relationships.appStoreVersion = {
6115
- data: { type: "appStoreVersions", id: params.appStoreVersionId }
6116
- };
6117
- }
6118
- if (params.appCustomProductPageVersionId) {
6119
- relationships.appCustomProductPageVersion = {
6120
- data: {
6121
- type: "appCustomProductPageVersions",
6122
- id: params.appCustomProductPageVersionId
6123
- }
6124
- };
6125
- }
6126
- if (params.appStoreVersionExperimentId) {
6127
- relationships.appStoreVersionExperiment = {
6128
- data: {
6129
- type: "appStoreVersionExperiments",
6130
- id: params.appStoreVersionExperimentId
6131
- }
6132
- };
6526
+ for (const [param, [relationship, type]] of Object.entries(ITEM_RELATIONSHIPS)) {
6527
+ const id = params[param];
6528
+ if (id) relationships[relationship] = { data: { type, id } };
6133
6529
  }
6134
6530
  const response = await this.http.post(
6135
6531
  "/v1/reviewSubmissionItems",
@@ -6360,6 +6756,277 @@ var init_xcode_metrics = __esm({
6360
6756
  }
6361
6757
  });
6362
6758
 
6759
+ // src/resources/app-asset-libraries.ts
6760
+ function mergeRefData(parts) {
6761
+ const merged = {
6762
+ features: [],
6763
+ placementProfileGroups: [],
6764
+ imageSpecs: [],
6765
+ videoSpecs: [],
6766
+ placementTypes: [],
6767
+ displayClasses: []
6768
+ };
6769
+ for (const part of parts) {
6770
+ for (const key of Object.keys(merged)) {
6771
+ merged[key].push(...part[key] ?? []);
6772
+ }
6773
+ }
6774
+ return merged;
6775
+ }
6776
+ var AppAssetLibrariesResource;
6777
+ var init_app_asset_libraries = __esm({
6778
+ "src/resources/app-asset-libraries.ts"() {
6779
+ AppAssetLibrariesResource = class {
6780
+ constructor(http) {
6781
+ this.http = http;
6782
+ }
6783
+ http;
6784
+ async getForApp(appId) {
6785
+ const response = await this.http.get(
6786
+ `/v1/apps/${appId}/assetLibrary`
6787
+ );
6788
+ return response.data;
6789
+ }
6790
+ async listImages(libraryId, params = {}) {
6791
+ return this.http.all(`/v1/appAssetLibraries/${libraryId}/images`, params);
6792
+ }
6793
+ async listVideos(libraryId, params = {}) {
6794
+ return this.http.all(`/v1/appAssetLibraries/${libraryId}/videos`, params);
6795
+ }
6796
+ /**
6797
+ * Specs, placement groups and limits. Apple adds placement groups (new
6798
+ * devices) without an API version bump, so read them from here rather than
6799
+ * hard-coding display types.
6800
+ */
6801
+ async refData(params = {}) {
6802
+ const filter = {};
6803
+ if (params.features?.length) filter.features = params.features;
6804
+ if (params.placementTypes?.length) filter.placementTypes = params.placementTypes;
6805
+ if (params.placementProfileGroups?.length) filter.placementProfileGroups = params.placementProfileGroups;
6806
+ const data = await this.http.all("/v1/appAssetLibraryRefData", { filter });
6807
+ return mergeRefData(data.map((d) => d.attributes));
6808
+ }
6809
+ };
6810
+ }
6811
+ });
6812
+
6813
+ // src/resources/app-asset-library-assets.ts
6814
+ var AssetLibraryMediaResource, AppAssetLibraryImagesResource, AppAssetLibraryVideosResource;
6815
+ var init_app_asset_library_assets = __esm({
6816
+ "src/resources/app-asset-library-assets.ts"() {
6817
+ AssetLibraryMediaResource = class {
6818
+ constructor(http) {
6819
+ this.http = http;
6820
+ }
6821
+ http;
6822
+ async get(id) {
6823
+ const response = await this.http.get(`/v1/${this.type}/${id}`);
6824
+ return response.data;
6825
+ }
6826
+ async reserve(params, extra = {}) {
6827
+ const { libraryId, ...attributes } = params;
6828
+ const response = await this.http.post(`/v1/${this.type}`, {
6829
+ data: {
6830
+ type: this.type,
6831
+ attributes: { ...attributes, ...extra },
6832
+ relationships: {
6833
+ assetLibrary: { data: { type: "appAssetLibraries", id: libraryId } }
6834
+ }
6835
+ }
6836
+ });
6837
+ return response.data;
6838
+ }
6839
+ async patch(id, attributes) {
6840
+ const response = await this.http.patch(`/v1/${this.type}/${id}`, {
6841
+ data: { type: this.type, id, attributes }
6842
+ });
6843
+ return response.data;
6844
+ }
6845
+ /** Fails with 409 STATE_ERROR.ASSET_HAS_PLACEMENTS while the asset is still placed. */
6846
+ async delete(id) {
6847
+ await this.http.delete(`/v1/${this.type}/${id}`);
6848
+ }
6849
+ async listPlacements(id, params = {}) {
6850
+ return this.http.all(`/v1/${this.type}/${id}/placements`, params);
6851
+ }
6852
+ };
6853
+ AppAssetLibraryImagesResource = class extends AssetLibraryMediaResource {
6854
+ type = "appAssetLibraryImages";
6855
+ async create(params) {
6856
+ return this.reserve(params);
6857
+ }
6858
+ async update(id, attributes) {
6859
+ return this.patch(id, { ...attributes });
6860
+ }
6861
+ };
6862
+ AppAssetLibraryVideosResource = class extends AssetLibraryMediaResource {
6863
+ type = "appAssetLibraryVideos";
6864
+ async create(params) {
6865
+ const { previewFrameTimeCode, ...rest } = params;
6866
+ return this.reserve(rest, previewFrameTimeCode ? { previewFrameTimeCode } : {});
6867
+ }
6868
+ async update(id, attributes) {
6869
+ return this.patch(id, { ...attributes });
6870
+ }
6871
+ };
6872
+ }
6873
+ });
6874
+
6875
+ // src/resources/app-asset-library-placements.ts
6876
+ function targetRelationship(target) {
6877
+ return { [target.kind]: { data: { type: TARGET_TYPES[target.kind], id: target.id } } };
6878
+ }
6879
+ var TARGET_TYPES, AppAssetLibraryPlacementsResource;
6880
+ var init_app_asset_library_placements = __esm({
6881
+ "src/resources/app-asset-library-placements.ts"() {
6882
+ TARGET_TYPES = {
6883
+ appStoreVersionLocalization: "appStoreVersionLocalizations",
6884
+ appCustomProductPageLocalization: "appCustomProductPageLocalizations",
6885
+ appEventLocalization: "appEventLocalizations",
6886
+ appStoreVersionExperimentTreatmentLocalization: "appStoreVersionExperimentTreatmentLocalizations"
6887
+ };
6888
+ AppAssetLibraryPlacementsResource = class {
6889
+ constructor(http) {
6890
+ this.http = http;
6891
+ }
6892
+ http;
6893
+ /**
6894
+ * Place an image or video on one localization. Placements cannot be edited —
6895
+ * delete and recreate. The parent version/page must be editable.
6896
+ */
6897
+ async create(params) {
6898
+ if (!params.imageId === !params.videoId) {
6899
+ throw new Error("A placement needs exactly one of imageId or videoId");
6900
+ }
6901
+ const media = params.imageId ? { image: { data: { type: "appAssetLibraryImages", id: params.imageId } } } : { video: { data: { type: "appAssetLibraryVideos", id: params.videoId } } };
6902
+ const response = await this.http.post(
6903
+ "/v1/appAssetLibraryPlacements",
6904
+ {
6905
+ data: {
6906
+ type: "appAssetLibraryPlacements",
6907
+ attributes: { placementType: params.placementType, placementGroup: params.placementGroup },
6908
+ relationships: { ...media, ...targetRelationship(params.target) }
6909
+ }
6910
+ }
6911
+ );
6912
+ return response.data;
6913
+ }
6914
+ async get(id, params = {}) {
6915
+ const response = await this.http.get(
6916
+ `/v1/appAssetLibraryPlacements/${id}`,
6917
+ params.include ? { include: params.include } : void 0
6918
+ );
6919
+ return response.data;
6920
+ }
6921
+ async delete(id) {
6922
+ await this.http.delete(`/v1/appAssetLibraryPlacements/${id}`);
6923
+ }
6924
+ /** Placements on one localization, in display order by default. */
6925
+ async listForTarget(target, params = {}) {
6926
+ const filter = {};
6927
+ if (params.placementType) filter.placementType = params.placementType;
6928
+ if (params.placementGroup) filter.placementGroup = params.placementGroup;
6929
+ const listParams = { filter, include: params.include, sort: "placementGroupPosition" };
6930
+ return this.http.all(
6931
+ `/v1/${TARGET_TYPES[target.kind]}/${target.id}/placements`,
6932
+ listParams
6933
+ );
6934
+ }
6935
+ /** Set the display order of a placement group. In-app event localizations cannot be ordered. */
6936
+ async order(params) {
6937
+ if (params.target.kind === "appEventLocalization") {
6938
+ throw new Error("In-app event placements cannot be ordered");
6939
+ }
6940
+ await this.http.post("/v1/appAssetLibraryPlacementOrderingRequests", {
6941
+ data: {
6942
+ type: "appAssetLibraryPlacementOrderingRequests",
6943
+ attributes: { placementGroup: params.placementGroup },
6944
+ relationships: {
6945
+ orderedPlacements: {
6946
+ data: params.placementIds.map((id) => ({ type: "appAssetLibraryPlacements", id }))
6947
+ },
6948
+ ...targetRelationship(params.target)
6949
+ }
6950
+ }
6951
+ });
6952
+ }
6953
+ };
6954
+ }
6955
+ });
6956
+ function sessionDir() {
6957
+ return process.env.FASTNODE_SESSION_DIR || path.join(os.homedir(), ".fastnode", "sessions");
6958
+ }
6959
+ function fileFor(email) {
6960
+ const key = crypto.createHash("sha256").update(email.trim().toLowerCase()).digest("hex").slice(0, 16);
6961
+ return path.join(sessionDir(), `${key}.json`);
6962
+ }
6963
+ function ensureDir() {
6964
+ const dir = sessionDir();
6965
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true, mode: 448 });
6966
+ fs.chmodSync(dir, 448);
6967
+ }
6968
+ function saveAppleSession(session) {
6969
+ ensureDir();
6970
+ const stored = { email: session.userEmail, savedAt: (/* @__PURE__ */ new Date()).toISOString(), session };
6971
+ const path$1 = fileFor(session.userEmail);
6972
+ fs.writeFileSync(path$1, JSON.stringify(stored), { mode: 384 });
6973
+ fs.chmodSync(path$1, 384);
6974
+ fs.writeFileSync(path.join(sessionDir(), "last-apple-id"), session.userEmail, { mode: 384 });
6975
+ }
6976
+ function clearAppleSession(email) {
6977
+ const path = fileFor(email);
6978
+ if (fs.existsSync(path)) fs.unlinkSync(path);
6979
+ }
6980
+ function lastAppleId() {
6981
+ try {
6982
+ return fs.readFileSync(path.join(sessionDir(), "last-apple-id"), "utf8").trim() || null;
6983
+ } catch {
6984
+ return null;
6985
+ }
6986
+ }
6987
+ async function loadAppleSession(email, fetchImpl = fetch) {
6988
+ const path = fileFor(email);
6989
+ if (!fs.existsSync(path)) return null;
6990
+ let stored;
6991
+ try {
6992
+ stored = JSON.parse(fs.readFileSync(path, "utf8"));
6993
+ } catch {
6994
+ return null;
6995
+ }
6996
+ const cookie = Object.entries(stored.session.cookies).map(([k, v]) => `${k}=${v}`).join("; ");
6997
+ try {
6998
+ const res = await fetchImpl(OLYMPUS_SESSION2, { headers: { Cookie: cookie, Accept: "application/json" } });
6999
+ if (!res.ok) {
7000
+ clearAppleSession(email);
7001
+ return null;
7002
+ }
7003
+ } catch {
7004
+ return null;
7005
+ }
7006
+ return stored.session;
7007
+ }
7008
+ async function signInWithAppleId(options) {
7009
+ const email = options.email || lastAppleId() || await options.askEmail();
7010
+ if (!options.fresh) {
7011
+ const saved = await loadAppleSession(email);
7012
+ if (saved) {
7013
+ options.onLog?.(`Using saved Apple ID session for ${email}`);
7014
+ return saved;
7015
+ }
7016
+ }
7017
+ const password = await options.askPassword(email);
7018
+ const session = await loginWithAppleId({ email, password, onLog: options.onLog });
7019
+ saveAppleSession(session);
7020
+ return session;
7021
+ }
7022
+ var OLYMPUS_SESSION2;
7023
+ var init_session_store = __esm({
7024
+ "src/client/session-store.ts"() {
7025
+ init_apple_session();
7026
+ OLYMPUS_SESSION2 = "https://appstoreconnect.apple.com/olympus/v1/session";
7027
+ }
7028
+ });
7029
+
6363
7030
  // src/client/portal.ts
6364
7031
  function pickUniqueKeyName(base, taken) {
6365
7032
  if (!taken.has(base)) return base;
@@ -6699,7 +7366,10 @@ var init_portal = __esm({
6699
7366
  }
6700
7367
  // ── Teams ──────────────────────────────────────────────────────────────
6701
7368
  async listTeams() {
6702
- const data = await this.post("account/listTeams.action", {});
7369
+ const data = await this.post("account/listTeams.action", {
7370
+ pageSize: "500",
7371
+ pageNumber: "1"
7372
+ });
6703
7373
  return data.teams ?? [];
6704
7374
  }
6705
7375
  // ── App IDs / Bundle IDs ───────────────────────────────────────────────
@@ -6713,11 +7383,12 @@ var init_portal = __esm({
6713
7383
  return data.appIds ?? [];
6714
7384
  }
6715
7385
  async createAppId(params) {
7386
+ const safeName = params.name.replace(/[^0-9A-Za-z\s]/g, " ").replace(/\s+/g, " ").trim() || "App";
6716
7387
  const reqParams = {
6717
7388
  teamId: this.teamId,
6718
7389
  type: "explicit",
6719
7390
  identifier: params.identifier,
6720
- name: params.name
7391
+ name: safeName
6721
7392
  };
6722
7393
  if (params.enablePush) reqParams.push = "on";
6723
7394
  if (params.enableIAP !== false) reqParams.inAppPurchase = "on";
@@ -6752,6 +7423,191 @@ var init_portal = __esm({
6752
7423
  featureValue: params.enabled ? "on" : "off"
6753
7424
  });
6754
7425
  }
7426
+ // ── Application Groups ──────────────────────────────────────────────────
7427
+ //
7428
+ // App Groups (e.g. `group.com.playspina.com`) are the only clean way to
7429
+ // share data — an auth token, in our case — between a host app and an
7430
+ // embedded extension (Messages / Notification Service / etc.). App Store
7431
+ // Connect API keys (.p8) genuinely cannot manage them; Apple only exposes
7432
+ // group create/associate through this legacy session-cookie portal API,
7433
+ // which is why they live on PortalClient (Apple ID session) and not on the
7434
+ // ASC REST client. Endpoint shapes ported 1:1 from Spaceship's
7435
+ // portal_client.rb (listApplicationGroups / addApplicationGroup /
7436
+ // assignApplicationGroupToAppId).
7437
+ /** Apple's internal feature code for the App Groups capability. */
7438
+ static APP_GROUPS_FEATURE = "APG3427HIY";
7439
+ /** List all app groups in the team. */
7440
+ async listAppGroups() {
7441
+ const data = await this.post("account/ios/identifiers/listApplicationGroups.action", {
7442
+ teamId: this.teamId,
7443
+ pageNumber: "1",
7444
+ pageSize: "500",
7445
+ sort: "name=asc"
7446
+ });
7447
+ return data.applicationGroupList ?? [];
7448
+ }
7449
+ /**
7450
+ * Create an app group, returning the raw group record. Idempotent —
7451
+ * Apple rejects a duplicate group identifier ("Multiple App Groups may
7452
+ * not have the same identifier"), so we list first and reuse an existing
7453
+ * group when the identifier already exists in the team.
7454
+ *
7455
+ * Note the two ids on the returned record: `identifier` is the
7456
+ * `group.com.…` string you type, while `applicationGroup` is Apple's
7457
+ * opaque internal handle that association actually requires — do NOT pass
7458
+ * the human identifier to assignAppGroups.
7459
+ */
7460
+ async createAppGroup(params) {
7461
+ const existing = await this.listAppGroups();
7462
+ const hit = existing.find((g) => g.identifier === params.identifier);
7463
+ if (hit) return hit;
7464
+ const safeName = params.name.replace(/[^0-9A-Za-z\s]/g, " ").replace(/\s+/g, " ").trim() || "App Group";
7465
+ const data = await this.post("account/ios/identifiers/addApplicationGroup.action", {
7466
+ teamId: this.teamId,
7467
+ identifier: params.identifier,
7468
+ name: safeName
7469
+ });
7470
+ return data.applicationGroup;
7471
+ }
7472
+ /**
7473
+ * Enable the App Groups capability on an App ID. Required before an
7474
+ * App ID can have a group associated with it — mirrors Spaceship
7475
+ * enabling `AppService::AppGroup` before `associate_groups`.
7476
+ */
7477
+ async enableAppGroups(appIdId) {
7478
+ await this.post("account/ios/identifiers/updateService.action", {
7479
+ teamId: this.teamId,
7480
+ displayId: appIdId,
7481
+ featureType: _PortalClient.APP_GROUPS_FEATURE,
7482
+ featureValue: "true"
7483
+ });
7484
+ }
7485
+ /**
7486
+ * Associate one or more app groups with an App ID. `appGroupIds` are the
7487
+ * internal ids (Spaceship's `app_group_id`), NOT the `group.com.…`
7488
+ * identifiers. Apple wants both `appIdId` and `displayId` set to the
7489
+ * App ID's internal id, per Spaceship's associate_groups_with_app.
7490
+ */
7491
+ async assignAppGroups(params) {
7492
+ await this.post("account/ios/identifiers/assignApplicationGroupToAppId.action", {
7493
+ teamId: this.teamId,
7494
+ appIdId: params.appIdId,
7495
+ displayId: params.appIdId,
7496
+ applicationGroups: params.appGroupIds
7497
+ });
7498
+ }
7499
+ // ── Apple Pay merchants (OMCs) ─────────────────────────────────────────
7500
+ //
7501
+ // Merchant IDs are team-level identifiers (merchant.com.…) that an App ID's
7502
+ // Apple Pay capability must be LINKED to — enabling the capability alone
7503
+ // produces profiles that fail with "doesn't support the … Merchant ID".
7504
+ // Like App Groups, merchants are only manageable through the legacy
7505
+ // session-cookie portal API. Endpoint shapes ported 1:1 from Spaceship's
7506
+ // portal_client.rb (merchants / create_merchant! /
7507
+ // associate_merchants_with_app).
7508
+ /** Apple's internal feature code for the Apple Pay capability. */
7509
+ static APPLE_PAY_FEATURE = "OM633U5T5G";
7510
+ /**
7511
+ * List all Apple Pay merchant IDs in the team. Each record maps
7512
+ * (per Spaceship's merchant.rb attr_mapping): `omcId` = internal id,
7513
+ * `identifier` = the human `merchant.com.…` string, `name` = display name.
7514
+ */
7515
+ async listMerchants() {
7516
+ const data = await this.post("account/ios/identifiers/listOMCs.action", {
7517
+ teamId: this.teamId,
7518
+ pageNumber: "1",
7519
+ pageSize: "500",
7520
+ sort: "name=asc"
7521
+ });
7522
+ return data.identifierList ?? [];
7523
+ }
7524
+ /**
7525
+ * Register (or reuse) a merchant ID, returning its INTERNAL id (`omcId`) —
7526
+ * Apple's opaque handle that association requires, NOT the human
7527
+ * `merchant.com.…` identifier. Idempotent: lists first and reuses an
7528
+ * existing merchant with the same identifier. Endpoint + params ported 1:1
7529
+ * from Spaceship's create_merchant! (addOMC.action).
7530
+ */
7531
+ async createMerchant(params) {
7532
+ const existing = await this.listMerchants();
7533
+ const hit = existing.find((m) => m.identifier === params.identifier);
7534
+ if (hit?.omcId) return String(hit.omcId);
7535
+ const safeName = params.name.replace(/[^0-9A-Za-z\s]/g, " ").replace(/\s+/g, " ").trim() || "Merchant";
7536
+ const data = await this.post("account/ios/identifiers/addOMC.action", {
7537
+ name: safeName,
7538
+ identifier: params.identifier,
7539
+ teamId: this.teamId
7540
+ });
7541
+ const rec = data.omcId ?? data;
7542
+ const id = typeof rec === "string" ? rec : rec?.omcId ?? rec?.merchantId;
7543
+ if (!id) {
7544
+ throw new Error("Apple returned no internal merchant id (omcId) for the registered merchant.");
7545
+ }
7546
+ return String(id);
7547
+ }
7548
+ /**
7549
+ * Enable the Apple Pay capability on an App ID. Apple Pay is a standard
7550
+ * AppService (service_id OM633U5T5G) whose service_uri is the default
7551
+ * `updateService.action` — confirmed in Spaceship's app_service.rb.
7552
+ */
7553
+ async enableApplePay(appIdId) {
7554
+ await this.post("account/ios/identifiers/updateService.action", {
7555
+ teamId: this.teamId,
7556
+ displayId: appIdId,
7557
+ featureType: _PortalClient.APPLE_PAY_FEATURE,
7558
+ featureValue: "true"
7559
+ });
7560
+ }
7561
+ /**
7562
+ * Associate merchant IDs with an App ID's Apple Pay capability. `omcIds`
7563
+ * are the internal ids (each merchant record's `omcId`), NOT the
7564
+ * `merchant.com.…` identifiers. Endpoint + params ported 1:1 from
7565
+ * Spaceship's associate_merchants_with_app (assignOMCToAppId.action — note
7566
+ * the singular "OMC", and NO displayId param).
7567
+ */
7568
+ async assignMerchants(params) {
7569
+ await this.post("account/ios/identifiers/assignOMCToAppId.action", {
7570
+ teamId: this.teamId,
7571
+ appIdId: params.appIdId,
7572
+ omcIds: params.omcIds
7573
+ });
7574
+ }
7575
+ /**
7576
+ * Create an Apple Pay **Payment Processing Certificate** on a merchant ID
7577
+ * from a CSR — the certificate a PSP (e.g. Stripe) hands you as a CSR, which
7578
+ * must be uploaded to the Merchant ID in Apple Developer, then the resulting
7579
+ * `.cer` downloaded and given back to the PSP so it can decrypt payment
7580
+ * tokens. This is the one manual Apple-portal step in wiring up Apple Pay.
7581
+ *
7582
+ * Ported 1:1 from Spaceship's create_certificate! (submitCertificateRequest
7583
+ * .action): the merchant's INTERNAL id (`omcId`) is passed as BOTH `appIdId`
7584
+ * and `specialIdentifierDisplayId` — the latter is how the portal scopes a
7585
+ * cert to a merchant/special identifier rather than an App ID. The cert type
7586
+ * is APPLE_PAY (`4APLUP237T`). Returns the created certRequest (its
7587
+ * `certificateId` feeds `downloadApplePayCertificate`).
7588
+ *
7589
+ * @param omcId the merchant's internal id (from createMerchant/listMerchants)
7590
+ * @param csrContent the PEM CSR string (Stripe's "Apple Pay CSR")
7591
+ */
7592
+ async createApplePayCertificate(params) {
7593
+ const data = await this.post("account/ios/certificate/submitCertificateRequest.action", {
7594
+ teamId: this.teamId,
7595
+ type: _PortalClient.CERT_TYPES.APPLE_PAY,
7596
+ csrContent: params.csrContent,
7597
+ appIdId: params.omcId,
7598
+ specialIdentifierDisplayId: params.omcId
7599
+ });
7600
+ return data.certRequest;
7601
+ }
7602
+ /**
7603
+ * Download an Apple Pay Payment Processing Certificate's raw bytes by its
7604
+ * certificate id (from `createApplePayCertificate`). Thin wrapper over
7605
+ * `downloadCertificate` pinned to the APPLE_PAY type — the DER `.cer` you
7606
+ * upload back into Stripe.
7607
+ */
7608
+ async downloadApplePayCertificate(certificateId) {
7609
+ return this.downloadCertificate(certificateId, _PortalClient.CERT_TYPES.APPLE_PAY);
7610
+ }
6755
7611
  // ── Certificates ───────────────────────────────────────────────────────
6756
7612
  async listCertificates(types) {
6757
7613
  const iosTypes = types ?? [
@@ -7248,8 +8104,14 @@ __export(client_exports, {
7248
8104
  TokenManager: () => TokenManager,
7249
8105
  beginLogin: () => beginLogin,
7250
8106
  checkPortalResponse: () => checkPortalResponse,
8107
+ clearAppleSession: () => clearAppleSession,
8108
+ lastAppleId: () => lastAppleId,
8109
+ loadAppleSession: () => loadAppleSession,
7251
8110
  loginWithAppleId: () => loginWithAppleId,
7252
8111
  requestSmsCode: () => requestSmsCode,
8112
+ saveAppleSession: () => saveAppleSession,
8113
+ sessionDir: () => sessionDir,
8114
+ signInWithAppleId: () => signInWithAppleId,
7253
8115
  submitTwoFactorCode: () => submitTwoFactorCode
7254
8116
  });
7255
8117
  var AppStoreConnectClient;
@@ -7291,9 +8153,13 @@ var init_client = __esm({
7291
8153
  init_in_app_purchases();
7292
8154
  init_game_center_detail();
7293
8155
  init_xcode_metrics();
8156
+ init_app_asset_libraries();
8157
+ init_app_asset_library_assets();
8158
+ init_app_asset_library_placements();
7294
8159
  init_auth();
7295
8160
  init_request();
7296
8161
  init_apple_session();
8162
+ init_session_store();
7297
8163
  init_portal();
7298
8164
  init_iris();
7299
8165
  AppStoreConnectClient = class {
@@ -7332,6 +8198,10 @@ var init_client = __esm({
7332
8198
  inAppPurchases;
7333
8199
  gameCenterDetail;
7334
8200
  xcodeMetrics;
8201
+ appAssetLibraries;
8202
+ appAssetLibraryImages;
8203
+ appAssetLibraryVideos;
8204
+ appAssetLibraryPlacements;
7335
8205
  constructor(config) {
7336
8206
  const tokenManager = new TokenManager({
7337
8207
  keyId: config.keyId,
@@ -7375,6 +8245,10 @@ var init_client = __esm({
7375
8245
  this.inAppPurchases = new InAppPurchasesResource(this.http);
7376
8246
  this.gameCenterDetail = new GameCenterDetailResource(this.http);
7377
8247
  this.xcodeMetrics = new XcodeMetricsResource(this.http);
8248
+ this.appAssetLibraries = new AppAssetLibrariesResource(this.http);
8249
+ this.appAssetLibraryImages = new AppAssetLibraryImagesResource(this.http);
8250
+ this.appAssetLibraryVideos = new AppAssetLibraryVideosResource(this.http);
8251
+ this.appAssetLibraryPlacements = new AppAssetLibraryPlacementsResource(this.http);
7378
8252
  }
7379
8253
  async request(path, options) {
7380
8254
  return this.http.request(path, options);