@sitepong/fastnode 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/README.md +58 -1
  2. package/dist/actions/index.cjs +848 -54
  3. package/dist/actions/index.cjs.map +1 -1
  4. package/dist/actions/index.mjs +849 -55
  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-RvXmdMke.d.ts} +2 -2
  15. package/dist/{cert-FehieKeb.d.cts → cert-eOoxGMN1.d.cts} +2 -2
  16. package/dist/cli/index.cjs +1321 -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 +1322 -61
  21. package/dist/cli/index.mjs.map +1 -1
  22. package/dist/client/index.cjs +443 -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 +443 -31
  27. package/dist/client/index.mjs.map +1 -1
  28. package/dist/deliver/index.cjs +334 -13
  29. package/dist/deliver/index.cjs.map +1 -1
  30. package/dist/deliver/index.d.cts +4 -3
  31. package/dist/deliver/index.d.ts +4 -3
  32. package/dist/deliver/index.mjs +330 -15
  33. package/dist/deliver/index.mjs.map +1 -1
  34. package/dist/{enums-B609j2rb.d.cts → enums-yXNVh3UI.d.cts} +8 -1
  35. package/dist/{enums-B609j2rb.d.ts → enums-yXNVh3UI.d.ts} +8 -1
  36. package/dist/gradle/index.cjs +222 -0
  37. package/dist/gradle/index.cjs.map +1 -0
  38. package/dist/gradle/index.d.cts +1 -0
  39. package/dist/gradle/index.d.ts +1 -0
  40. package/dist/gradle/index.mjs +217 -0
  41. package/dist/gradle/index.mjs.map +1 -0
  42. package/dist/gym/index.d.cts +2 -2
  43. package/dist/gym/index.d.ts +2 -2
  44. package/dist/index-8djTPW8O.d.ts +104 -0
  45. package/dist/{index-DrUpRXRG.d.cts → index-B6-MJpO4.d.cts} +1 -1
  46. package/dist/{index-D13sn8DO.d.cts → index-BE7bwNff.d.cts} +1 -1
  47. package/dist/index-BGhV0gS-.d.ts +65 -0
  48. package/dist/index-BaNcf7jt.d.cts +201 -0
  49. package/dist/{index-DZoPGCbE.d.ts → index-BvyZ4uiw.d.ts} +3 -3
  50. package/dist/{index-B8FOfxOw.d.ts → index-C01Rkrir.d.ts} +2 -2
  51. package/dist/{index-YcKz4aRS.d.ts → index-C9mHBJED.d.ts} +1 -1
  52. package/dist/{index-BICa8odo.d.cts → index-CAEiXVWd.d.ts} +9 -1
  53. package/dist/index-CBf0-NUQ.d.ts +201 -0
  54. package/dist/index-CXJlvmB4.d.cts +63 -0
  55. package/dist/index-CXJlvmB4.d.ts +63 -0
  56. package/dist/{index-CNHf7I4v.d.cts → index-D1GHEM3c.d.ts} +32 -3
  57. package/dist/index-DFstwgGK.d.cts +65 -0
  58. package/dist/{index-Bk1Gd-qE.d.cts → index-DGkgRnZp.d.cts} +2 -2
  59. package/dist/{index-CplRWSUc.d.ts → index-DLJso7Vs.d.cts} +32 -3
  60. package/dist/{index-BZWmOTWE.d.cts → index-DWOLZNlz.d.cts} +2 -2
  61. package/dist/{index-OelCoOth.d.cts → index-DfkIz7i0.d.cts} +18 -4
  62. package/dist/{index-CtPyeMLH.d.ts → index-DjC_gmnL.d.ts} +18 -4
  63. package/dist/index-DnHQ2uT2.d.cts +104 -0
  64. package/dist/{index-CCtYhPN9.d.ts → index-DrmS802s.d.cts} +331 -130
  65. package/dist/index-Dv-Mek07.d.cts +84 -0
  66. package/dist/index-Dv-Mek07.d.ts +84 -0
  67. package/dist/{index-oVyLLIEK.d.cts → index-icIH5e5V.d.cts} +3 -3
  68. package/dist/{index-DI1ZHr-3.d.ts → index-laE3_wm9.d.cts} +9 -1
  69. package/dist/{index-BKUapQP2.d.cts → index-lsuS2R1T.d.ts} +331 -130
  70. package/dist/{index-DImN9PWc.d.ts → index-mEEB7An6.d.ts} +1 -1
  71. package/dist/{index-DFni8oZt.d.ts → index-rTZOdMRE.d.ts} +2 -2
  72. package/dist/index.cjs +3342 -959
  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 +3335 -963
  77. package/dist/index.mjs.map +1 -1
  78. package/dist/lane/index.cjs +848 -54
  79. package/dist/lane/index.cjs.map +1 -1
  80. package/dist/lane/index.mjs +849 -55
  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
@@ -2,7 +2,7 @@ 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
4
  import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, copyFileSync, mkdtempSync, rmSync, chmodSync } from 'fs';
5
- import { join, extname, resolve, basename } from 'path';
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';
@@ -153,8 +153,8 @@ async function revokeExpiredCertificates(options) {
153
153
  return count;
154
154
  }
155
155
  async function importToKeychain(certData, keychainPath, keychainPassword) {
156
- const { tmpdir: tmpdir8 } = await import('os');
157
- const tempCertPath = join(tmpdir8(), `fastnode-cert-${Date.now()}.cer`);
156
+ const { tmpdir: tmpdir7 } = await import('os');
157
+ const tempCertPath = join(tmpdir7(), `fastnode-cert-${Date.now()}.cer`);
158
158
  await writeFile(tempCertPath, certData);
159
159
  try {
160
160
  const args = [
@@ -1723,6 +1723,20 @@ var init_srp = __esm({
1723
1723
  };
1724
1724
  }
1725
1725
  });
1726
+ async function appleJson(res, what) {
1727
+ const text = await res.text();
1728
+ try {
1729
+ return JSON.parse(text);
1730
+ } catch {
1731
+ const contentType = (res.headers.get("content-type") ?? "").split(";")[0].trim();
1732
+ const title = /<title>([^<]{1,120})<\/title>/i.exec(text)?.[1]?.trim();
1733
+ const looksHtml = /^\s*<(!doctype|html)/i.test(text) || contentType === "text/html";
1734
+ const page = looksHtml ? "an HTML page" : contentType ? `a ${contentType} response` : "a non-JSON response";
1735
+ throw new Error(
1736
+ `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.`
1737
+ );
1738
+ }
1739
+ }
1726
1740
  function checkPortalResponse(body, session) {
1727
1741
  if (!body || typeof body !== "object") return;
1728
1742
  if (body.resultCode === 4100) {
@@ -1799,7 +1813,7 @@ async function beginLogin(config) {
1799
1813
  }
1800
1814
  async function fetchTwoFactorChallenge(pending) {
1801
1815
  const r = await fetch(`${AUTH_BASE}`, { headers: buildAuthHeadersFromPending(pending) });
1802
- const body = await r.json();
1816
+ const body = await appleJson(r, "two-factor challenge");
1803
1817
  const trustedPhoneNumbers = (body.trustedPhoneNumbers ?? []).map((p) => ({
1804
1818
  id: p.id,
1805
1819
  numberWithDialCode: p.numberWithDialCode,
@@ -1820,7 +1834,8 @@ async function requestSmsCode(input) {
1820
1834
  body: JSON.stringify({ phoneNumber: { id: input.phoneId }, mode: input.mode ?? "sms" })
1821
1835
  });
1822
1836
  }
1823
- async function submitTwoFactorCode(input) {
1837
+ async function submitTwoFactorCode(rawInput) {
1838
+ const input = { ...rawInput, code: rawInput.code.replace(/[\s\u00a0]+/g, "") };
1824
1839
  const log = input.onLog ?? (() => {
1825
1840
  });
1826
1841
  let path;
@@ -1932,9 +1947,12 @@ async function doSirp(user, password, serviceKey, cookieStr, hashcash) {
1932
1947
  body: JSON.stringify({ a: aBase64, accountName: user, protocols: ["s2k", "s2k_fo"] }),
1933
1948
  redirect: "manual"
1934
1949
  });
1935
- const initBody = await initRes.json();
1950
+ const initBody = await appleJson(initRes, "sign-in init");
1936
1951
  if (initBody.serviceErrors) throw new Error(`Apple auth error: ${JSON.stringify(initBody.serviceErrors)}`);
1937
1952
  const { iteration: iterations, salt: saltB64, b: bB64, c, protocol } = initBody;
1953
+ if (!iterations || !saltB64 || !bB64 || !c || !protocol) {
1954
+ throw new Error(`Apple's sign-in init answered without a challenge (HTTP ${initRes.status}): ${JSON.stringify(initBody).slice(0, 300)}`);
1955
+ }
1938
1956
  const salt = Buffer.from(saltB64, "base64");
1939
1957
  const b = Buffer.from(bB64, "base64");
1940
1958
  const encryptedPassword = applePbkdf2(password, salt, iterations, 32, protocol);
@@ -1977,7 +1995,7 @@ async function fetchOlympusSession(session) {
1977
1995
  const cookieStr = Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join("; ");
1978
1996
  const res = await fetch(OLYMPUS_SESSION, { headers: { Cookie: cookieStr, Accept: "application/json" } });
1979
1997
  if (res.ok) {
1980
- const body = await res.json();
1998
+ const body = await appleJson(res, "session lookup");
1981
1999
  if (body?.user?.emailAddress) session.userEmail = body.user.emailAddress;
1982
2000
  if (body?.provider?.providerId) session.teamId = String(body.provider.providerId);
1983
2001
  }
@@ -1989,7 +2007,7 @@ async function fetchContractMessages(session) {
1989
2007
  headers: { Cookie: cookieStr, Accept: "application/json" }
1990
2008
  });
1991
2009
  if (!res.ok) return [];
1992
- const body = await res.json();
2010
+ const body = await appleJson(res, "agreements lookup");
1993
2011
  return (body ?? []).map((m) => ({
1994
2012
  id: m.id ?? "",
1995
2013
  group: m.group ?? "",
@@ -2000,10 +2018,22 @@ async function fetchContractMessages(session) {
2000
2018
  return [];
2001
2019
  }
2002
2020
  }
2003
- async function fetchServiceKey() {
2004
- const res = await fetch(WIDGET_KEY_URL);
2005
- const body = await res.json();
2006
- if (!body?.authServiceKey) throw new Error("Could not fetch Apple service key");
2021
+ function serviceKeyFromLocation(location) {
2022
+ if (!location) return null;
2023
+ try {
2024
+ const key = new URL(location).searchParams.get("widgetKey")?.trim();
2025
+ return key && /^[0-9a-f]{16,}$/i.test(key) ? key : null;
2026
+ } catch {
2027
+ return null;
2028
+ }
2029
+ }
2030
+ async function fetchServiceKey(fetchImpl = fetch) {
2031
+ const head = await fetchImpl(LOGOUT_URL, { method: "HEAD", redirect: "manual" });
2032
+ const fromRedirect = serviceKeyFromLocation(head.headers.get("location"));
2033
+ if (fromRedirect) return fromRedirect;
2034
+ const res = await fetchImpl(WIDGET_KEY_URL);
2035
+ const body = await appleJson(res, "service key request");
2036
+ 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.");
2007
2037
  return body.authServiceKey;
2008
2038
  }
2009
2039
  function buildAuthHeaders(session) {
@@ -2051,7 +2081,7 @@ async function defaultPrompt(message) {
2051
2081
  });
2052
2082
  });
2053
2083
  }
2054
- var AUTH_BASE, OLYMPUS_SESSION, WIDGET_KEY_URL, ProgramLicenseAgreementError;
2084
+ var AUTH_BASE, OLYMPUS_SESSION, WIDGET_KEY_URL, ProgramLicenseAgreementError, LOGOUT_URL;
2055
2085
  var init_apple_session = __esm({
2056
2086
  "src/client/apple-session.ts"() {
2057
2087
  init_srp();
@@ -2066,6 +2096,7 @@ var init_apple_session = __esm({
2066
2096
  }
2067
2097
  agreements;
2068
2098
  };
2099
+ LOGOUT_URL = "https://appstoreconnect.apple.com/logout";
2069
2100
  }
2070
2101
  });
2071
2102
 
@@ -2382,9 +2413,10 @@ async function uploadWithTransporter(options) {
2382
2413
  const transporterBin = await resolveTransporter();
2383
2414
  const platform = options.platform ?? "ios";
2384
2415
  const timeout = options.timeout ?? 18e5;
2385
- const keyDir = await mkdtemp(join(tmpdir(), "fastnode-itms-"));
2416
+ const keyDir = join(homedir(), ".appstoreconnect", "private_keys");
2417
+ await mkdir(keyDir, { recursive: true });
2386
2418
  const keyPath = join(keyDir, `AuthKey_${options.apiKeyId}.p8`);
2387
- await writeFile(keyPath, options.apiPrivateKey);
2419
+ await writeFile(keyPath, options.apiPrivateKey, { mode: 384 });
2388
2420
  return new Promise((resolve3) => {
2389
2421
  const args = [
2390
2422
  "-m",
@@ -2400,6 +2432,9 @@ async function uploadWithTransporter(options) {
2400
2432
  "-WONoPause",
2401
2433
  "true"
2402
2434
  ];
2435
+ if (options.assetDescriptionPath) {
2436
+ args.push("-assetDescription", options.assetDescriptionPath);
2437
+ }
2403
2438
  if (platform !== "ios") {
2404
2439
  args.push("-bundleType", platform);
2405
2440
  }
@@ -2423,13 +2458,13 @@ async function uploadWithTransporter(options) {
2423
2458
  child.stderr.on("data", onData);
2424
2459
  child.on("close", async (code) => {
2425
2460
  clearTimeout(timer);
2426
- await rm(keyDir, { recursive: true, force: true }).catch(() => {
2461
+ await rm(keyPath, { force: true }).catch(() => {
2427
2462
  });
2428
2463
  resolve3({ success: code === 0, output, backend: "transporter" });
2429
2464
  });
2430
2465
  child.on("error", async (err) => {
2431
2466
  clearTimeout(timer);
2432
- await rm(keyDir, { recursive: true, force: true }).catch(() => {
2467
+ await rm(keyPath, { force: true }).catch(() => {
2433
2468
  });
2434
2469
  resolve3({
2435
2470
  success: false,
@@ -2721,6 +2756,311 @@ var init_metadata = __esm({
2721
2756
  };
2722
2757
  }
2723
2758
  });
2759
+ function categoryFor(placementType) {
2760
+ return placementType === "APP_SCREENSHOT" || placementType === "IMESSAGE_APP_SCREENSHOT" || placementType === "APP_PREVIEW" ? "APP_SCREENSHOTS_AND_PREVIEWS" : "CREATIVE_ASSETS";
2761
+ }
2762
+ function readImageSize(data) {
2763
+ if (data.length >= 26 && data.readUInt32BE(0) === 2303741511) {
2764
+ const colorType = data[25];
2765
+ return {
2766
+ width: data.readUInt32BE(16),
2767
+ height: data.readUInt32BE(20),
2768
+ alpha: colorType === 4 || colorType === 6
2769
+ };
2770
+ }
2771
+ if (data.length >= 4 && data[0] === 255 && data[1] === 216) {
2772
+ let offset = 2;
2773
+ while (offset + 9 < data.length) {
2774
+ if (data[offset] !== 255) return null;
2775
+ const marker = data[offset + 1];
2776
+ const length = data.readUInt16BE(offset + 2);
2777
+ const isSof = marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204;
2778
+ if (isSof) {
2779
+ return { height: data.readUInt16BE(offset + 5), width: data.readUInt16BE(offset + 7), alpha: false };
2780
+ }
2781
+ offset += 2 + length;
2782
+ }
2783
+ }
2784
+ return null;
2785
+ }
2786
+ function fits(spec, width, height) {
2787
+ const d = spec.dimensions;
2788
+ return width >= d.minWidth && width <= d.maxWidth && height >= d.minHeight && height <= d.maxHeight;
2789
+ }
2790
+ function groupsForFeature(refData, feature, placementType) {
2791
+ const mappings = refData.placementTypes?.find((p) => p.placementTypeId === placementType)?.specMappings ?? [];
2792
+ const all = mappings.map((m) => m.placementGroupId);
2793
+ const policy = refData.features?.find((f) => f.featureId === feature)?.placementPolicies?.find((p) => p.placementType === placementType);
2794
+ const limited = policy?.groupLimits?.flatMap((l) => l.groupIds);
2795
+ return limited?.length ? all.filter((g) => limited.includes(g)) : all;
2796
+ }
2797
+ function maxCountFor(refData, feature, placementType, placementGroup) {
2798
+ return refData.features?.find((f) => f.featureId === feature)?.placementPolicies?.find((p) => p.placementType === placementType)?.groupLimits?.find((l) => l.groupIds.includes(placementGroup))?.maxCount;
2799
+ }
2800
+ function resolveImageGroups(refData, feature, placementType, size) {
2801
+ const specs = (refData.imageSpecs ?? []).filter(
2802
+ (s) => s.compatiblePlacementTypes.includes(placementType) && fits(s, size.width, size.height)
2803
+ );
2804
+ const specIds = new Set(specs.map((s) => s.specId));
2805
+ const allowed = new Set(groupsForFeature(refData, feature, placementType));
2806
+ const mappings = refData.placementTypes?.find((p) => p.placementTypeId === placementType)?.specMappings ?? [];
2807
+ const groups = mappings.filter((m) => allowed.has(m.placementGroupId) && m.specs.some((id) => specIds.has(id))).map((m) => m.placementGroupId);
2808
+ return { groups: [...new Set(groups)], spec: specs[0] };
2809
+ }
2810
+ async function runUploadOperations(ops, data) {
2811
+ for (const op of ops) {
2812
+ const headers = {};
2813
+ for (const h of op.requestHeaders) headers[h.name] = h.value;
2814
+ const res = await fetch(op.url, {
2815
+ method: op.method,
2816
+ headers,
2817
+ body: data.subarray(op.offset, op.offset + op.length)
2818
+ });
2819
+ if (!res.ok) {
2820
+ throw new Error(`Upload operation failed: ${op.method} ${new URL(op.url).host} \u2192 HTTP ${res.status}`);
2821
+ }
2822
+ }
2823
+ }
2824
+ async function uploadLibraryAsset(options) {
2825
+ const { client, libraryId, filePath, category } = options;
2826
+ const data = await readFile(filePath);
2827
+ const fileName = basename(filePath);
2828
+ const kind = VIDEO_EXTENSIONS.includes(extname(filePath).toLowerCase()) ? "video" : "image";
2829
+ const hash = createHash("sha256").update(data).digest("hex").slice(0, 12);
2830
+ const referenceName = `${options.referenceName ?? fileName} #${hash}`;
2831
+ const media = kind === "image" ? client.appAssetLibraryImages : client.appAssetLibraryVideos;
2832
+ const filter = { filter: { referenceName } };
2833
+ const existing = kind === "image" ? await client.appAssetLibraries.listImages(libraryId, filter) : await client.appAssetLibraries.listVideos(libraryId, filter);
2834
+ const reusable = existing.find(
2835
+ (a) => a.attributes.category === category && a.attributes.state && READY_STATES.includes(a.attributes.state)
2836
+ );
2837
+ if (reusable) {
2838
+ return { kind, id: reusable.id, state: reusable.attributes.state, reused: true };
2839
+ }
2840
+ const reserved = kind === "image" ? await client.appAssetLibraryImages.create({ libraryId, fileName, fileSize: data.length, category, referenceName }) : await client.appAssetLibraryVideos.create({
2841
+ libraryId,
2842
+ fileName,
2843
+ fileSize: data.length,
2844
+ category,
2845
+ referenceName,
2846
+ previewFrameTimeCode: options.previewFrameTimeCode
2847
+ });
2848
+ await runUploadOperations(reserved.attributes.uploadOperations ?? [], data);
2849
+ await media.update(reserved.id, { uploaded: true });
2850
+ options.log?.(`uploaded ${fileName} (${kind}, ${data.length} bytes)`);
2851
+ const state = await waitForAssetReady(
2852
+ () => media.get(reserved.id),
2853
+ options.pollIntervalMs ?? 3e3,
2854
+ options.timeoutMs ?? (kind === "video" ? 30 * 6e4 : 5 * 6e4)
2855
+ );
2856
+ return { kind, id: reserved.id, state, reused: false };
2857
+ }
2858
+ async function waitForAssetReady(get, intervalMs, timeoutMs) {
2859
+ const deadline = Date.now() + timeoutMs;
2860
+ for (; ; ) {
2861
+ const asset = await get();
2862
+ const state = asset.attributes.state;
2863
+ if (state === "FAILED") {
2864
+ const why = (asset.attributes.stateDetails ?? []).map((d) => d.message ?? d.code).filter(Boolean).join("; ");
2865
+ throw new Error(`Asset processing failed${why ? `: ${why}` : ""}`);
2866
+ }
2867
+ if (state && READY_STATES.includes(state)) return state;
2868
+ if (Date.now() > deadline) throw new Error(`Asset still ${state ?? "unknown"} after ${timeoutMs / 1e3}s`);
2869
+ await new Promise((r) => setTimeout(r, intervalMs));
2870
+ }
2871
+ }
2872
+ async function listFiles(dir) {
2873
+ return (await readdir(dir)).filter((f) => !f.startsWith(".")).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }));
2874
+ }
2875
+ async function isDir(path) {
2876
+ return (await stat(path)).isDirectory();
2877
+ }
2878
+ async function planLocale(localeDir, refData, feature, folders, warn) {
2879
+ const planned = [];
2880
+ for (const [folder, placementType] of Object.entries(folders)) {
2881
+ const typeDir = join(localeDir, folder);
2882
+ if (!existsSync(typeDir)) continue;
2883
+ const known = new Set(groupsForFeature(refData, feature, placementType));
2884
+ const entries = [];
2885
+ for (const entry of await listFiles(typeDir)) {
2886
+ const full = join(typeDir, entry);
2887
+ if (await isDir(full)) {
2888
+ if (!known.has(entry)) {
2889
+ warn(`${relative(localeDir, full)}: "${entry}" is not a ${placementType} placement group for ${feature} \u2014 skipped`);
2890
+ continue;
2891
+ }
2892
+ for (const f of await listFiles(full)) entries.push({ filePath: join(full, f), group: entry });
2893
+ } else {
2894
+ entries.push({ filePath: full });
2895
+ }
2896
+ }
2897
+ for (const { filePath, group } of entries) {
2898
+ const ext = extname(filePath).toLowerCase();
2899
+ const isVideo = VIDEO_EXTENSIONS.includes(ext);
2900
+ if (!isVideo && !IMAGE_EXTENSIONS.includes(ext)) continue;
2901
+ const needsVideo = placementType === "APP_PREVIEW";
2902
+ const needsImage = placementType === "APP_SCREENSHOT" || placementType === "IMESSAGE_APP_SCREENSHOT";
2903
+ if (needsVideo && !isVideo || needsImage && isVideo) {
2904
+ warn(`${relative(localeDir, filePath)}: ${isVideo ? "video" : "image"} cannot be a ${placementType} \u2014 skipped`);
2905
+ continue;
2906
+ }
2907
+ if (group) {
2908
+ planned.push({ filePath, placementType, groups: [group] });
2909
+ continue;
2910
+ }
2911
+ if (isVideo) {
2912
+ if (known.size === 1) planned.push({ filePath, placementType, groups: [...known] });
2913
+ else warn(`${relative(localeDir, filePath)}: put videos in a placement-group folder (one of ${[...known].join(", ")}) \u2014 skipped`);
2914
+ continue;
2915
+ }
2916
+ const size = readImageSize(await readFile(filePath));
2917
+ if (!size) {
2918
+ warn(`${relative(localeDir, filePath)}: could not read image size \u2014 skipped`);
2919
+ continue;
2920
+ }
2921
+ const { groups, spec } = resolveImageGroups(refData, feature, placementType, size);
2922
+ if (!groups.length) {
2923
+ warn(`${relative(localeDir, filePath)}: ${size.width}\xD7${size.height} matches no ${placementType} spec \u2014 skipped`);
2924
+ continue;
2925
+ }
2926
+ if (size.alpha && spec && spec.alphaAllowed === false) {
2927
+ warn(`${relative(localeDir, filePath)}: has an alpha channel, which Apple rejects for this spec`);
2928
+ }
2929
+ planned.push({ filePath, placementType, groups: groups.slice(0, 1) });
2930
+ }
2931
+ }
2932
+ return planned;
2933
+ }
2934
+ async function uploadAssetLibrary(options) {
2935
+ const { client } = options;
2936
+ const log = options.log ?? (() => {
2937
+ });
2938
+ const feature = "APP_STORE_VERSIONS";
2939
+ const result = { uploaded: 0, reused: 0, placed: 0, removed: 0, warnings: [] };
2940
+ const warn = (line) => {
2941
+ result.warnings.push(line);
2942
+ log(`warning: ${line}`);
2943
+ };
2944
+ const [library, refData, localizations] = await Promise.all([
2945
+ client.appAssetLibraries.getForApp(options.appId),
2946
+ client.appAssetLibraries.refData({ features: [feature] }),
2947
+ client.appStoreVersions.getLocalizations(options.versionId)
2948
+ ]);
2949
+ const folders = { ...options.folders ?? ASSET_FOLDERS };
2950
+ if (options.rootPlacementType) folders["."] = options.rootPlacementType;
2951
+ const uploads = /* @__PURE__ */ new Map();
2952
+ const uploadOnce = (filePath, placementType) => {
2953
+ const key = `${filePath}|${categoryFor(placementType)}`;
2954
+ if (!uploads.has(key)) {
2955
+ uploads.set(
2956
+ key,
2957
+ uploadLibraryAsset({
2958
+ client,
2959
+ libraryId: library.id,
2960
+ filePath,
2961
+ category: categoryFor(placementType),
2962
+ referenceName: relative(options.assetsPath, filePath),
2963
+ log
2964
+ }).then((a) => {
2965
+ if (a.reused) result.reused++;
2966
+ else result.uploaded++;
2967
+ return a;
2968
+ })
2969
+ );
2970
+ }
2971
+ return uploads.get(key);
2972
+ };
2973
+ for (const loc of localizations) {
2974
+ const localeDir = join(options.assetsPath, loc.attributes.locale);
2975
+ if (!existsSync(localeDir)) continue;
2976
+ const target = { kind: "appStoreVersionLocalization", id: loc.id };
2977
+ const plan = await planLocale(localeDir, refData, feature, folders, warn);
2978
+ const slots = /* @__PURE__ */ new Map();
2979
+ for (const p of plan) {
2980
+ for (const group of p.groups) {
2981
+ const key = `${p.placementType}|${group}`;
2982
+ if (!slots.has(key)) slots.set(key, { placementType: p.placementType, group, files: [] });
2983
+ slots.get(key).files.push(p.filePath);
2984
+ }
2985
+ }
2986
+ for (const slot of slots.values()) {
2987
+ const where = `${loc.attributes.locale} ${slot.placementType} ${slot.group}`;
2988
+ const max = maxCountFor(refData, feature, slot.placementType, slot.group);
2989
+ if (max !== void 0 && slot.files.length > max) {
2990
+ warn(`${where}: ${slot.files.length} files but Apple allows ${max} \u2014 keeping the first ${max}`);
2991
+ slot.files = slot.files.slice(0, max);
2992
+ }
2993
+ const existing = await client.appAssetLibraryPlacements.listForTarget(target, {
2994
+ placementType: slot.placementType,
2995
+ placementGroup: slot.group,
2996
+ include: "image,video"
2997
+ });
2998
+ if (options.overwrite) {
2999
+ for (const p of existing) {
3000
+ await client.appAssetLibraryPlacements.delete(p.id);
3001
+ result.removed++;
3002
+ }
3003
+ }
3004
+ const kept = options.overwrite ? [] : existing;
3005
+ const keptAssetIds = new Set(kept.map((p) => placementAssetId(p)).filter(Boolean));
3006
+ const ours = [];
3007
+ for (const filePath of slot.files) {
3008
+ const asset = await uploadOnce(filePath, slot.placementType);
3009
+ if (keptAssetIds.has(asset.id)) continue;
3010
+ const placement = await client.appAssetLibraryPlacements.create({
3011
+ placementType: slot.placementType,
3012
+ placementGroup: slot.group,
3013
+ ...asset.kind === "image" ? { imageId: asset.id } : { videoId: asset.id },
3014
+ target
3015
+ });
3016
+ ours.push(placement.id);
3017
+ result.placed++;
3018
+ }
3019
+ log(`${where}: placed ${ours.length}${kept.length ? `, kept ${kept.length}` : ""}`);
3020
+ const orderable = slot.placementType === "APP_SCREENSHOT" || slot.placementType === "APP_PREVIEW" || slot.placementType === "IMESSAGE_APP_SCREENSHOT";
3021
+ if (orderable && ours.length + kept.length > 1) {
3022
+ await client.appAssetLibraryPlacements.order({
3023
+ placementGroup: slot.group,
3024
+ placementIds: [...ours, ...kept.map((p) => p.id)],
3025
+ target
3026
+ });
3027
+ }
3028
+ }
3029
+ }
3030
+ return result;
3031
+ }
3032
+ function placementAssetId(p) {
3033
+ for (const rel of ["image", "video"]) {
3034
+ const data = p.relationships?.[rel]?.data;
3035
+ if (data?.id) return data.id;
3036
+ }
3037
+ return void 0;
3038
+ }
3039
+ var ASSET_FOLDERS, IMAGE_EXTENSIONS, VIDEO_EXTENSIONS, READY_STATES;
3040
+ var init_assets = __esm({
3041
+ "src/deliver/assets.ts"() {
3042
+ ASSET_FOLDERS = {
3043
+ screenshots: "APP_SCREENSHOT",
3044
+ imessage_screenshots: "IMESSAGE_APP_SCREENSHOT",
3045
+ previews: "APP_PREVIEW",
3046
+ header: "PRODUCT_PAGE_HEADER_ASSET",
3047
+ search_results: "APP_STORE_SEARCH_RESULTS_ASSET",
3048
+ event_card: "EVENT_CARD_ASSET",
3049
+ event_details: "EVENT_DETAILS_PAGE_ASSET"
3050
+ };
3051
+ IMAGE_EXTENSIONS = [".png", ".jpg", ".jpeg"];
3052
+ VIDEO_EXTENSIONS = [".mp4", ".mov", ".m4v"];
3053
+ READY_STATES = [
3054
+ "COMPLETE",
3055
+ "PREPARE_FOR_SUBMISSION",
3056
+ "READY_FOR_REVIEW",
3057
+ "WAITING_FOR_REVIEW",
3058
+ "IN_REVIEW",
3059
+ "ACCEPTED",
3060
+ "APPROVED"
3061
+ ];
3062
+ }
3063
+ });
2724
3064
 
2725
3065
  // src/xcode/upload.ts
2726
3066
  var upload_exports = {};
@@ -2786,8 +3126,14 @@ var init_upload = __esm({
2786
3126
  // src/deliver/index.ts
2787
3127
  var deliver_exports = {};
2788
3128
  __export(deliver_exports, {
3129
+ ASSET_FOLDERS: () => ASSET_FOLDERS,
3130
+ categoryFor: () => categoryFor,
2789
3131
  loadMetadata: () => loadMetadata,
3132
+ readImageSize: () => readImageSize,
3133
+ resolveImageGroups: () => resolveImageGroups,
2790
3134
  submitForReview: () => submitForReview,
3135
+ uploadAssetLibrary: () => uploadAssetLibrary,
3136
+ uploadLibraryAsset: () => uploadLibraryAsset,
2791
3137
  uploadMetadata: () => uploadMetadata,
2792
3138
  uploadScreenshots: () => uploadScreenshots,
2793
3139
  uploadToAppStore: () => uploadToAppStore
@@ -2826,11 +3172,36 @@ async function uploadToAppStore(options) {
2826
3172
  copyright: options.copyright
2827
3173
  });
2828
3174
  }
3175
+ const log = (line) => options.onOutput?.(`${line}
3176
+ `);
2829
3177
  if (!options.skipScreenshots && options.screenshotsPath && existsSync(options.screenshotsPath)) {
2830
- await uploadScreenshots({
3178
+ if (options.useAssetLibrary) {
3179
+ await uploadAssetLibrary({
3180
+ client: options.client,
3181
+ appId: app.id,
3182
+ versionId: version.id,
3183
+ assetsPath: options.screenshotsPath,
3184
+ folders: {},
3185
+ rootPlacementType: "APP_SCREENSHOT",
3186
+ overwrite: options.overwriteAssets,
3187
+ log
3188
+ });
3189
+ } else {
3190
+ await uploadScreenshots({
3191
+ client: options.client,
3192
+ versionId: version.id,
3193
+ screenshotsPath: options.screenshotsPath
3194
+ });
3195
+ }
3196
+ }
3197
+ if (!options.skipAssets && options.assetsPath && existsSync(options.assetsPath)) {
3198
+ await uploadAssetLibrary({
2831
3199
  client: options.client,
3200
+ appId: app.id,
2832
3201
  versionId: version.id,
2833
- screenshotsPath: options.screenshotsPath
3202
+ assetsPath: options.assetsPath,
3203
+ overwrite: options.overwriteAssets,
3204
+ log
2834
3205
  });
2835
3206
  }
2836
3207
  if (!options.skipBinaryUpload) {
@@ -2915,17 +3286,7 @@ async function uploadScreenshots(options) {
2915
3286
  fileName: file,
2916
3287
  fileSize: fileData.length
2917
3288
  });
2918
- const ops = reserved.attributes.uploadOperations;
2919
- if (ops) {
2920
- for (const op of ops) {
2921
- const chunk = fileData.subarray(op.offset, op.offset + op.length);
2922
- const headers = {};
2923
- for (const h of op.requestHeaders) {
2924
- headers[h.name] = h.value;
2925
- }
2926
- await fetch(op.url, { method: op.method, headers, body: chunk });
2927
- }
2928
- }
3289
+ await runUploadOperations(reserved.attributes.uploadOperations ?? [], fileData);
2929
3290
  await options.client.appScreenshots.commitScreenshot(reserved.id, {
2930
3291
  sourceFileChecksum: checksum,
2931
3292
  uploaded: true
@@ -2945,7 +3306,9 @@ function detectDisplayType(filename) {
2945
3306
  var init_deliver = __esm({
2946
3307
  "src/deliver/index.ts"() {
2947
3308
  init_metadata();
3309
+ init_assets();
2948
3310
  init_metadata();
3311
+ init_assets();
2949
3312
  }
2950
3313
  });
2951
3314
 
@@ -2954,6 +3317,7 @@ var produce_exports = {};
2954
3317
  __export(produce_exports, {
2955
3318
  SERVICE_NAME_TO_CAPABILITY: () => SERVICE_NAME_TO_CAPABILITY,
2956
3319
  addCapability: () => addCapability,
3320
+ associateAppGroups: () => associateAppGroups,
2957
3321
  buildCapabilitySettings: () => buildCapabilitySettings,
2958
3322
  createApp: () => createApp,
2959
3323
  enableServices: () => enableServices,
@@ -3048,6 +3412,30 @@ async function enableServices(options) {
3048
3412
  });
3049
3413
  }
3050
3414
  }
3415
+ async function associateAppGroups(options) {
3416
+ if (!options.appGroups?.length) return;
3417
+ await options.portalClient.enableAppGroups(options.bundleIdId).catch(() => {
3418
+ });
3419
+ const existing = await options.portalClient.listAppGroups();
3420
+ const appGroupIds = [];
3421
+ for (const group of options.appGroups) {
3422
+ let record = existing.find((ag) => ag.identifier === group.identifier);
3423
+ if (!record) {
3424
+ record = await options.portalClient.createAppGroup({
3425
+ name: group.name,
3426
+ identifier: group.identifier
3427
+ });
3428
+ }
3429
+ const id = record?.applicationGroup ?? record?.applicationGroupId ?? record?.appGroupId ?? record?.id;
3430
+ if (id) appGroupIds.push(id);
3431
+ }
3432
+ if (appGroupIds.length) {
3433
+ await options.portalClient.assignAppGroups({
3434
+ appIdId: options.bundleIdId,
3435
+ appGroupIds
3436
+ });
3437
+ }
3438
+ }
3051
3439
  async function createApp(options) {
3052
3440
  const existing = await options.client.apps.findByBundleId(options.bundleIdentifier);
3053
3441
  if (existing) return existing;
@@ -3066,6 +3454,13 @@ async function createApp(options) {
3066
3454
  });
3067
3455
  }
3068
3456
  }
3457
+ if (options.appGroups?.length && options.portalClient) {
3458
+ await associateAppGroups({
3459
+ portalClient: options.portalClient,
3460
+ bundleIdId: bundleId.id,
3461
+ appGroups: options.appGroups
3462
+ });
3463
+ }
3069
3464
  return options.client.apps.create({
3070
3465
  bundleIdentifier: options.bundleIdentifier,
3071
3466
  bundleIdId: bundleId.id,
@@ -3875,10 +4270,10 @@ async function parseTestResults(options) {
3875
4270
  } else if (inputPath.endsWith(".plist")) {
3876
4271
  testSuites = parsePlistResults(inputPath);
3877
4272
  } else {
3878
- const stat6 = await import('fs').then(
4273
+ const stat7 = await import('fs').then(
3879
4274
  (fs) => fs.statSync(inputPath)
3880
4275
  );
3881
- if (stat6.isDirectory()) {
4276
+ if (stat7.isDirectory()) {
3882
4277
  requireMacOS("xcresult parsing");
3883
4278
  testSuites = await parseXCResult(inputPath);
3884
4279
  } else {
@@ -6055,9 +6450,22 @@ var init_pre_release_versions = __esm({
6055
6450
  });
6056
6451
 
6057
6452
  // src/resources/review-submission-items.ts
6058
- var ReviewSubmissionItemsResource;
6453
+ var ITEM_RELATIONSHIPS, ReviewSubmissionItemsResource;
6059
6454
  var init_review_submission_items = __esm({
6060
6455
  "src/resources/review-submission-items.ts"() {
6456
+ ITEM_RELATIONSHIPS = {
6457
+ appStoreVersionId: ["appStoreVersion", "appStoreVersions"],
6458
+ appCustomProductPageVersionId: ["appCustomProductPageVersion", "appCustomProductPageVersions"],
6459
+ appStoreVersionExperimentId: ["appStoreVersionExperiment", "appStoreVersionExperiments"],
6460
+ appStoreVersionExperimentV2Id: ["appStoreVersionExperimentV2", "appStoreVersionExperiments"],
6461
+ appEventId: ["appEvent", "appEvents"],
6462
+ appAssetLibraryImageId: ["appAssetLibraryImage", "appAssetLibraryImages"],
6463
+ appAssetLibraryVideoId: ["appAssetLibraryVideo", "appAssetLibraryVideos"],
6464
+ backgroundAssetVersionId: ["backgroundAssetVersion", "backgroundAssetVersions"],
6465
+ inAppPurchaseVersionId: ["inAppPurchaseVersion", "inAppPurchaseVersions"],
6466
+ subscriptionVersionId: ["subscriptionVersion", "subscriptionVersions"],
6467
+ subscriptionGroupVersionId: ["subscriptionGroupVersion", "subscriptionGroupVersions"]
6468
+ };
6061
6469
  ReviewSubmissionItemsResource = class {
6062
6470
  constructor(http) {
6063
6471
  this.http = http;
@@ -6079,32 +6487,20 @@ var init_review_submission_items = __esm({
6079
6487
  );
6080
6488
  return response.data;
6081
6489
  }
6490
+ /**
6491
+ * Add one item to a review submission. Besides versions, asset library
6492
+ * images and videos can be submitted on their own (ASC API 4.5.1) so
6493
+ * creative assets are approved ahead of a campaign.
6494
+ */
6082
6495
  async create(params) {
6083
6496
  const relationships = {
6084
6497
  reviewSubmission: {
6085
6498
  data: { type: "reviewSubmissions", id: params.reviewSubmissionId }
6086
6499
  }
6087
6500
  };
6088
- if (params.appStoreVersionId) {
6089
- relationships.appStoreVersion = {
6090
- data: { type: "appStoreVersions", id: params.appStoreVersionId }
6091
- };
6092
- }
6093
- if (params.appCustomProductPageVersionId) {
6094
- relationships.appCustomProductPageVersion = {
6095
- data: {
6096
- type: "appCustomProductPageVersions",
6097
- id: params.appCustomProductPageVersionId
6098
- }
6099
- };
6100
- }
6101
- if (params.appStoreVersionExperimentId) {
6102
- relationships.appStoreVersionExperiment = {
6103
- data: {
6104
- type: "appStoreVersionExperiments",
6105
- id: params.appStoreVersionExperimentId
6106
- }
6107
- };
6501
+ for (const [param, [relationship, type]] of Object.entries(ITEM_RELATIONSHIPS)) {
6502
+ const id = params[param];
6503
+ if (id) relationships[relationship] = { data: { type, id } };
6108
6504
  }
6109
6505
  const response = await this.http.post(
6110
6506
  "/v1/reviewSubmissionItems",
@@ -6335,6 +6731,204 @@ var init_xcode_metrics = __esm({
6335
6731
  }
6336
6732
  });
6337
6733
 
6734
+ // src/resources/app-asset-libraries.ts
6735
+ function mergeRefData(parts) {
6736
+ const merged = {
6737
+ features: [],
6738
+ placementProfileGroups: [],
6739
+ imageSpecs: [],
6740
+ videoSpecs: [],
6741
+ placementTypes: [],
6742
+ displayClasses: []
6743
+ };
6744
+ for (const part of parts) {
6745
+ for (const key of Object.keys(merged)) {
6746
+ merged[key].push(...part[key] ?? []);
6747
+ }
6748
+ }
6749
+ return merged;
6750
+ }
6751
+ var AppAssetLibrariesResource;
6752
+ var init_app_asset_libraries = __esm({
6753
+ "src/resources/app-asset-libraries.ts"() {
6754
+ AppAssetLibrariesResource = class {
6755
+ constructor(http) {
6756
+ this.http = http;
6757
+ }
6758
+ http;
6759
+ async getForApp(appId) {
6760
+ const response = await this.http.get(
6761
+ `/v1/apps/${appId}/assetLibrary`
6762
+ );
6763
+ return response.data;
6764
+ }
6765
+ async listImages(libraryId, params = {}) {
6766
+ return this.http.all(`/v1/appAssetLibraries/${libraryId}/images`, params);
6767
+ }
6768
+ async listVideos(libraryId, params = {}) {
6769
+ return this.http.all(`/v1/appAssetLibraries/${libraryId}/videos`, params);
6770
+ }
6771
+ /**
6772
+ * Specs, placement groups and limits. Apple adds placement groups (new
6773
+ * devices) without an API version bump, so read them from here rather than
6774
+ * hard-coding display types.
6775
+ */
6776
+ async refData(params = {}) {
6777
+ const filter = {};
6778
+ if (params.features?.length) filter.features = params.features;
6779
+ if (params.placementTypes?.length) filter.placementTypes = params.placementTypes;
6780
+ if (params.placementProfileGroups?.length) filter.placementProfileGroups = params.placementProfileGroups;
6781
+ const data = await this.http.all("/v1/appAssetLibraryRefData", { filter });
6782
+ return mergeRefData(data.map((d) => d.attributes));
6783
+ }
6784
+ };
6785
+ }
6786
+ });
6787
+
6788
+ // src/resources/app-asset-library-assets.ts
6789
+ var AssetLibraryMediaResource, AppAssetLibraryImagesResource, AppAssetLibraryVideosResource;
6790
+ var init_app_asset_library_assets = __esm({
6791
+ "src/resources/app-asset-library-assets.ts"() {
6792
+ AssetLibraryMediaResource = class {
6793
+ constructor(http) {
6794
+ this.http = http;
6795
+ }
6796
+ http;
6797
+ async get(id) {
6798
+ const response = await this.http.get(`/v1/${this.type}/${id}`);
6799
+ return response.data;
6800
+ }
6801
+ async reserve(params, extra = {}) {
6802
+ const { libraryId, ...attributes } = params;
6803
+ const response = await this.http.post(`/v1/${this.type}`, {
6804
+ data: {
6805
+ type: this.type,
6806
+ attributes: { ...attributes, ...extra },
6807
+ relationships: {
6808
+ assetLibrary: { data: { type: "appAssetLibraries", id: libraryId } }
6809
+ }
6810
+ }
6811
+ });
6812
+ return response.data;
6813
+ }
6814
+ async patch(id, attributes) {
6815
+ const response = await this.http.patch(`/v1/${this.type}/${id}`, {
6816
+ data: { type: this.type, id, attributes }
6817
+ });
6818
+ return response.data;
6819
+ }
6820
+ /** Fails with 409 STATE_ERROR.ASSET_HAS_PLACEMENTS while the asset is still placed. */
6821
+ async delete(id) {
6822
+ await this.http.delete(`/v1/${this.type}/${id}`);
6823
+ }
6824
+ async listPlacements(id, params = {}) {
6825
+ return this.http.all(`/v1/${this.type}/${id}/placements`, params);
6826
+ }
6827
+ };
6828
+ AppAssetLibraryImagesResource = class extends AssetLibraryMediaResource {
6829
+ type = "appAssetLibraryImages";
6830
+ async create(params) {
6831
+ return this.reserve(params);
6832
+ }
6833
+ async update(id, attributes) {
6834
+ return this.patch(id, { ...attributes });
6835
+ }
6836
+ };
6837
+ AppAssetLibraryVideosResource = class extends AssetLibraryMediaResource {
6838
+ type = "appAssetLibraryVideos";
6839
+ async create(params) {
6840
+ const { previewFrameTimeCode, ...rest } = params;
6841
+ return this.reserve(rest, previewFrameTimeCode ? { previewFrameTimeCode } : {});
6842
+ }
6843
+ async update(id, attributes) {
6844
+ return this.patch(id, { ...attributes });
6845
+ }
6846
+ };
6847
+ }
6848
+ });
6849
+
6850
+ // src/resources/app-asset-library-placements.ts
6851
+ function targetRelationship(target) {
6852
+ return { [target.kind]: { data: { type: TARGET_TYPES[target.kind], id: target.id } } };
6853
+ }
6854
+ var TARGET_TYPES, AppAssetLibraryPlacementsResource;
6855
+ var init_app_asset_library_placements = __esm({
6856
+ "src/resources/app-asset-library-placements.ts"() {
6857
+ TARGET_TYPES = {
6858
+ appStoreVersionLocalization: "appStoreVersionLocalizations",
6859
+ appCustomProductPageLocalization: "appCustomProductPageLocalizations",
6860
+ appEventLocalization: "appEventLocalizations",
6861
+ appStoreVersionExperimentTreatmentLocalization: "appStoreVersionExperimentTreatmentLocalizations"
6862
+ };
6863
+ AppAssetLibraryPlacementsResource = class {
6864
+ constructor(http) {
6865
+ this.http = http;
6866
+ }
6867
+ http;
6868
+ /**
6869
+ * Place an image or video on one localization. Placements cannot be edited —
6870
+ * delete and recreate. The parent version/page must be editable.
6871
+ */
6872
+ async create(params) {
6873
+ if (!params.imageId === !params.videoId) {
6874
+ throw new Error("A placement needs exactly one of imageId or videoId");
6875
+ }
6876
+ const media = params.imageId ? { image: { data: { type: "appAssetLibraryImages", id: params.imageId } } } : { video: { data: { type: "appAssetLibraryVideos", id: params.videoId } } };
6877
+ const response = await this.http.post(
6878
+ "/v1/appAssetLibraryPlacements",
6879
+ {
6880
+ data: {
6881
+ type: "appAssetLibraryPlacements",
6882
+ attributes: { placementType: params.placementType, placementGroup: params.placementGroup },
6883
+ relationships: { ...media, ...targetRelationship(params.target) }
6884
+ }
6885
+ }
6886
+ );
6887
+ return response.data;
6888
+ }
6889
+ async get(id, params = {}) {
6890
+ const response = await this.http.get(
6891
+ `/v1/appAssetLibraryPlacements/${id}`,
6892
+ params.include ? { include: params.include } : void 0
6893
+ );
6894
+ return response.data;
6895
+ }
6896
+ async delete(id) {
6897
+ await this.http.delete(`/v1/appAssetLibraryPlacements/${id}`);
6898
+ }
6899
+ /** Placements on one localization, in display order by default. */
6900
+ async listForTarget(target, params = {}) {
6901
+ const filter = {};
6902
+ if (params.placementType) filter.placementType = params.placementType;
6903
+ if (params.placementGroup) filter.placementGroup = params.placementGroup;
6904
+ const listParams = { filter, include: params.include, sort: "placementGroupPosition" };
6905
+ return this.http.all(
6906
+ `/v1/${TARGET_TYPES[target.kind]}/${target.id}/placements`,
6907
+ listParams
6908
+ );
6909
+ }
6910
+ /** Set the display order of a placement group. In-app event localizations cannot be ordered. */
6911
+ async order(params) {
6912
+ if (params.target.kind === "appEventLocalization") {
6913
+ throw new Error("In-app event placements cannot be ordered");
6914
+ }
6915
+ await this.http.post("/v1/appAssetLibraryPlacementOrderingRequests", {
6916
+ data: {
6917
+ type: "appAssetLibraryPlacementOrderingRequests",
6918
+ attributes: { placementGroup: params.placementGroup },
6919
+ relationships: {
6920
+ orderedPlacements: {
6921
+ data: params.placementIds.map((id) => ({ type: "appAssetLibraryPlacements", id }))
6922
+ },
6923
+ ...targetRelationship(params.target)
6924
+ }
6925
+ }
6926
+ });
6927
+ }
6928
+ };
6929
+ }
6930
+ });
6931
+
6338
6932
  // src/client/portal.ts
6339
6933
  function pickUniqueKeyName(base, taken) {
6340
6934
  if (!taken.has(base)) return base;
@@ -6674,7 +7268,10 @@ var init_portal = __esm({
6674
7268
  }
6675
7269
  // ── Teams ──────────────────────────────────────────────────────────────
6676
7270
  async listTeams() {
6677
- const data = await this.post("account/listTeams.action", {});
7271
+ const data = await this.post("account/listTeams.action", {
7272
+ pageSize: "500",
7273
+ pageNumber: "1"
7274
+ });
6678
7275
  return data.teams ?? [];
6679
7276
  }
6680
7277
  // ── App IDs / Bundle IDs ───────────────────────────────────────────────
@@ -6688,11 +7285,12 @@ var init_portal = __esm({
6688
7285
  return data.appIds ?? [];
6689
7286
  }
6690
7287
  async createAppId(params) {
7288
+ const safeName = params.name.replace(/[^0-9A-Za-z\s]/g, " ").replace(/\s+/g, " ").trim() || "App";
6691
7289
  const reqParams = {
6692
7290
  teamId: this.teamId,
6693
7291
  type: "explicit",
6694
7292
  identifier: params.identifier,
6695
- name: params.name
7293
+ name: safeName
6696
7294
  };
6697
7295
  if (params.enablePush) reqParams.push = "on";
6698
7296
  if (params.enableIAP !== false) reqParams.inAppPurchase = "on";
@@ -6727,6 +7325,191 @@ var init_portal = __esm({
6727
7325
  featureValue: params.enabled ? "on" : "off"
6728
7326
  });
6729
7327
  }
7328
+ // ── Application Groups ──────────────────────────────────────────────────
7329
+ //
7330
+ // App Groups (e.g. `group.com.playspina.com`) are the only clean way to
7331
+ // share data — an auth token, in our case — between a host app and an
7332
+ // embedded extension (Messages / Notification Service / etc.). App Store
7333
+ // Connect API keys (.p8) genuinely cannot manage them; Apple only exposes
7334
+ // group create/associate through this legacy session-cookie portal API,
7335
+ // which is why they live on PortalClient (Apple ID session) and not on the
7336
+ // ASC REST client. Endpoint shapes ported 1:1 from Spaceship's
7337
+ // portal_client.rb (listApplicationGroups / addApplicationGroup /
7338
+ // assignApplicationGroupToAppId).
7339
+ /** Apple's internal feature code for the App Groups capability. */
7340
+ static APP_GROUPS_FEATURE = "APG3427HIY";
7341
+ /** List all app groups in the team. */
7342
+ async listAppGroups() {
7343
+ const data = await this.post("account/ios/identifiers/listApplicationGroups.action", {
7344
+ teamId: this.teamId,
7345
+ pageNumber: "1",
7346
+ pageSize: "500",
7347
+ sort: "name=asc"
7348
+ });
7349
+ return data.applicationGroupList ?? [];
7350
+ }
7351
+ /**
7352
+ * Create an app group, returning the raw group record. Idempotent —
7353
+ * Apple rejects a duplicate group identifier ("Multiple App Groups may
7354
+ * not have the same identifier"), so we list first and reuse an existing
7355
+ * group when the identifier already exists in the team.
7356
+ *
7357
+ * Note the two ids on the returned record: `identifier` is the
7358
+ * `group.com.…` string you type, while `applicationGroup` is Apple's
7359
+ * opaque internal handle that association actually requires — do NOT pass
7360
+ * the human identifier to assignAppGroups.
7361
+ */
7362
+ async createAppGroup(params) {
7363
+ const existing = await this.listAppGroups();
7364
+ const hit = existing.find((g) => g.identifier === params.identifier);
7365
+ if (hit) return hit;
7366
+ const safeName = params.name.replace(/[^0-9A-Za-z\s]/g, " ").replace(/\s+/g, " ").trim() || "App Group";
7367
+ const data = await this.post("account/ios/identifiers/addApplicationGroup.action", {
7368
+ teamId: this.teamId,
7369
+ identifier: params.identifier,
7370
+ name: safeName
7371
+ });
7372
+ return data.applicationGroup;
7373
+ }
7374
+ /**
7375
+ * Enable the App Groups capability on an App ID. Required before an
7376
+ * App ID can have a group associated with it — mirrors Spaceship
7377
+ * enabling `AppService::AppGroup` before `associate_groups`.
7378
+ */
7379
+ async enableAppGroups(appIdId) {
7380
+ await this.post("account/ios/identifiers/updateService.action", {
7381
+ teamId: this.teamId,
7382
+ displayId: appIdId,
7383
+ featureType: _PortalClient.APP_GROUPS_FEATURE,
7384
+ featureValue: "true"
7385
+ });
7386
+ }
7387
+ /**
7388
+ * Associate one or more app groups with an App ID. `appGroupIds` are the
7389
+ * internal ids (Spaceship's `app_group_id`), NOT the `group.com.…`
7390
+ * identifiers. Apple wants both `appIdId` and `displayId` set to the
7391
+ * App ID's internal id, per Spaceship's associate_groups_with_app.
7392
+ */
7393
+ async assignAppGroups(params) {
7394
+ await this.post("account/ios/identifiers/assignApplicationGroupToAppId.action", {
7395
+ teamId: this.teamId,
7396
+ appIdId: params.appIdId,
7397
+ displayId: params.appIdId,
7398
+ applicationGroups: params.appGroupIds
7399
+ });
7400
+ }
7401
+ // ── Apple Pay merchants (OMCs) ─────────────────────────────────────────
7402
+ //
7403
+ // Merchant IDs are team-level identifiers (merchant.com.…) that an App ID's
7404
+ // Apple Pay capability must be LINKED to — enabling the capability alone
7405
+ // produces profiles that fail with "doesn't support the … Merchant ID".
7406
+ // Like App Groups, merchants are only manageable through the legacy
7407
+ // session-cookie portal API. Endpoint shapes ported 1:1 from Spaceship's
7408
+ // portal_client.rb (merchants / create_merchant! /
7409
+ // associate_merchants_with_app).
7410
+ /** Apple's internal feature code for the Apple Pay capability. */
7411
+ static APPLE_PAY_FEATURE = "OM633U5T5G";
7412
+ /**
7413
+ * List all Apple Pay merchant IDs in the team. Each record maps
7414
+ * (per Spaceship's merchant.rb attr_mapping): `omcId` = internal id,
7415
+ * `identifier` = the human `merchant.com.…` string, `name` = display name.
7416
+ */
7417
+ async listMerchants() {
7418
+ const data = await this.post("account/ios/identifiers/listOMCs.action", {
7419
+ teamId: this.teamId,
7420
+ pageNumber: "1",
7421
+ pageSize: "500",
7422
+ sort: "name=asc"
7423
+ });
7424
+ return data.identifierList ?? [];
7425
+ }
7426
+ /**
7427
+ * Register (or reuse) a merchant ID, returning its INTERNAL id (`omcId`) —
7428
+ * Apple's opaque handle that association requires, NOT the human
7429
+ * `merchant.com.…` identifier. Idempotent: lists first and reuses an
7430
+ * existing merchant with the same identifier. Endpoint + params ported 1:1
7431
+ * from Spaceship's create_merchant! (addOMC.action).
7432
+ */
7433
+ async createMerchant(params) {
7434
+ const existing = await this.listMerchants();
7435
+ const hit = existing.find((m) => m.identifier === params.identifier);
7436
+ if (hit?.omcId) return String(hit.omcId);
7437
+ const safeName = params.name.replace(/[^0-9A-Za-z\s]/g, " ").replace(/\s+/g, " ").trim() || "Merchant";
7438
+ const data = await this.post("account/ios/identifiers/addOMC.action", {
7439
+ name: safeName,
7440
+ identifier: params.identifier,
7441
+ teamId: this.teamId
7442
+ });
7443
+ const rec = data.omcId ?? data;
7444
+ const id = typeof rec === "string" ? rec : rec?.omcId ?? rec?.merchantId;
7445
+ if (!id) {
7446
+ throw new Error("Apple returned no internal merchant id (omcId) for the registered merchant.");
7447
+ }
7448
+ return String(id);
7449
+ }
7450
+ /**
7451
+ * Enable the Apple Pay capability on an App ID. Apple Pay is a standard
7452
+ * AppService (service_id OM633U5T5G) whose service_uri is the default
7453
+ * `updateService.action` — confirmed in Spaceship's app_service.rb.
7454
+ */
7455
+ async enableApplePay(appIdId) {
7456
+ await this.post("account/ios/identifiers/updateService.action", {
7457
+ teamId: this.teamId,
7458
+ displayId: appIdId,
7459
+ featureType: _PortalClient.APPLE_PAY_FEATURE,
7460
+ featureValue: "true"
7461
+ });
7462
+ }
7463
+ /**
7464
+ * Associate merchant IDs with an App ID's Apple Pay capability. `omcIds`
7465
+ * are the internal ids (each merchant record's `omcId`), NOT the
7466
+ * `merchant.com.…` identifiers. Endpoint + params ported 1:1 from
7467
+ * Spaceship's associate_merchants_with_app (assignOMCToAppId.action — note
7468
+ * the singular "OMC", and NO displayId param).
7469
+ */
7470
+ async assignMerchants(params) {
7471
+ await this.post("account/ios/identifiers/assignOMCToAppId.action", {
7472
+ teamId: this.teamId,
7473
+ appIdId: params.appIdId,
7474
+ omcIds: params.omcIds
7475
+ });
7476
+ }
7477
+ /**
7478
+ * Create an Apple Pay **Payment Processing Certificate** on a merchant ID
7479
+ * from a CSR — the certificate a PSP (e.g. Stripe) hands you as a CSR, which
7480
+ * must be uploaded to the Merchant ID in Apple Developer, then the resulting
7481
+ * `.cer` downloaded and given back to the PSP so it can decrypt payment
7482
+ * tokens. This is the one manual Apple-portal step in wiring up Apple Pay.
7483
+ *
7484
+ * Ported 1:1 from Spaceship's create_certificate! (submitCertificateRequest
7485
+ * .action): the merchant's INTERNAL id (`omcId`) is passed as BOTH `appIdId`
7486
+ * and `specialIdentifierDisplayId` — the latter is how the portal scopes a
7487
+ * cert to a merchant/special identifier rather than an App ID. The cert type
7488
+ * is APPLE_PAY (`4APLUP237T`). Returns the created certRequest (its
7489
+ * `certificateId` feeds `downloadApplePayCertificate`).
7490
+ *
7491
+ * @param omcId the merchant's internal id (from createMerchant/listMerchants)
7492
+ * @param csrContent the PEM CSR string (Stripe's "Apple Pay CSR")
7493
+ */
7494
+ async createApplePayCertificate(params) {
7495
+ const data = await this.post("account/ios/certificate/submitCertificateRequest.action", {
7496
+ teamId: this.teamId,
7497
+ type: _PortalClient.CERT_TYPES.APPLE_PAY,
7498
+ csrContent: params.csrContent,
7499
+ appIdId: params.omcId,
7500
+ specialIdentifierDisplayId: params.omcId
7501
+ });
7502
+ return data.certRequest;
7503
+ }
7504
+ /**
7505
+ * Download an Apple Pay Payment Processing Certificate's raw bytes by its
7506
+ * certificate id (from `createApplePayCertificate`). Thin wrapper over
7507
+ * `downloadCertificate` pinned to the APPLE_PAY type — the DER `.cer` you
7508
+ * upload back into Stripe.
7509
+ */
7510
+ async downloadApplePayCertificate(certificateId) {
7511
+ return this.downloadCertificate(certificateId, _PortalClient.CERT_TYPES.APPLE_PAY);
7512
+ }
6730
7513
  // ── Certificates ───────────────────────────────────────────────────────
6731
7514
  async listCertificates(types) {
6732
7515
  const iosTypes = types ?? [
@@ -7266,6 +8049,9 @@ var init_client = __esm({
7266
8049
  init_in_app_purchases();
7267
8050
  init_game_center_detail();
7268
8051
  init_xcode_metrics();
8052
+ init_app_asset_libraries();
8053
+ init_app_asset_library_assets();
8054
+ init_app_asset_library_placements();
7269
8055
  init_auth();
7270
8056
  init_request();
7271
8057
  init_apple_session();
@@ -7307,6 +8093,10 @@ var init_client = __esm({
7307
8093
  inAppPurchases;
7308
8094
  gameCenterDetail;
7309
8095
  xcodeMetrics;
8096
+ appAssetLibraries;
8097
+ appAssetLibraryImages;
8098
+ appAssetLibraryVideos;
8099
+ appAssetLibraryPlacements;
7310
8100
  constructor(config) {
7311
8101
  const tokenManager = new TokenManager({
7312
8102
  keyId: config.keyId,
@@ -7350,6 +8140,10 @@ var init_client = __esm({
7350
8140
  this.inAppPurchases = new InAppPurchasesResource(this.http);
7351
8141
  this.gameCenterDetail = new GameCenterDetailResource(this.http);
7352
8142
  this.xcodeMetrics = new XcodeMetricsResource(this.http);
8143
+ this.appAssetLibraries = new AppAssetLibrariesResource(this.http);
8144
+ this.appAssetLibraryImages = new AppAssetLibraryImagesResource(this.http);
8145
+ this.appAssetLibraryVideos = new AppAssetLibraryVideosResource(this.http);
8146
+ this.appAssetLibraryPlacements = new AppAssetLibraryPlacementsResource(this.http);
7353
8147
  }
7354
8148
  async request(path, options) {
7355
8149
  return this.http.request(path, options);