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