@sitepong/fastnode 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -1
- package/dist/actions/index.cjs +891 -72
- package/dist/actions/index.cjs.map +1 -1
- package/dist/actions/index.mjs +892 -73
- 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 +17868 -24
- 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 +17853 -7
- package/dist/cert/index.mjs.map +1 -1
- package/dist/{cert-e5GAgAPb.d.ts → cert-RvXmdMke.d.ts} +2 -2
- package/dist/{cert-uLLdb5a7.d.cts → cert-eOoxGMN1.d.cts} +2 -2
- package/dist/cli/index.cjs +1364 -78
- 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 +1365 -79
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/index.cjs +480 -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 +480 -31
- 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-DrUpRXRG.d.cts → index-B6-MJpO4.d.cts} +1 -1
- package/dist/{index-Clyf4-lu.d.cts → index-BE7bwNff.d.cts} +1 -1
- package/dist/index-BGhV0gS-.d.ts +65 -0
- package/dist/index-BaNcf7jt.d.cts +201 -0
- package/dist/{index-Di4Ic0EE.d.ts → index-BvyZ4uiw.d.ts} +3 -3
- package/dist/{index-CEldrbyz.d.ts → index-C01Rkrir.d.ts} +2 -2
- package/dist/{index-jeI44Q5G.d.ts → index-C9mHBJED.d.ts} +1 -1
- package/dist/{index-BgHagzj1.d.cts → index-CAEiXVWd.d.ts} +9 -1
- package/dist/index-CBf0-NUQ.d.ts +201 -0
- package/dist/index-CXJlvmB4.d.cts +63 -0
- package/dist/index-CXJlvmB4.d.ts +63 -0
- package/dist/{index-BNP7Bpu5.d.ts → index-D1GHEM3c.d.ts} +32 -3
- package/dist/index-DFstwgGK.d.cts +65 -0
- package/dist/{index-DrCOh0Vh.d.cts → index-DGkgRnZp.d.cts} +2 -2
- package/dist/{index-CtydPNxA.d.cts → index-DLJso7Vs.d.cts} +32 -3
- package/dist/{index-BN0iWhuw.d.cts → index-DWOLZNlz.d.cts} +2 -2
- package/dist/{index-BjkOUQcQ.d.cts → index-DfkIz7i0.d.cts} +18 -4
- package/dist/{index-EKU_G1G5.d.ts → index-DjC_gmnL.d.ts} +18 -4
- package/dist/index-DnHQ2uT2.d.cts +104 -0
- package/dist/{index-CdfgEZwh.d.cts → index-DrmS802s.d.cts} +356 -130
- package/dist/index-Dv-Mek07.d.cts +84 -0
- package/dist/index-Dv-Mek07.d.ts +84 -0
- package/dist/{index-DHxi-QPC.d.cts → index-icIH5e5V.d.cts} +3 -3
- package/dist/{index-mddpvrRX.d.ts → index-laE3_wm9.d.cts} +9 -1
- package/dist/{index-BwwpAlk2.d.ts → index-lsuS2R1T.d.ts} +356 -130
- package/dist/{index-DImN9PWc.d.ts → index-mEEB7An6.d.ts} +1 -1
- package/dist/{index-C2YLoFVl.d.ts → index-rTZOdMRE.d.ts} +2 -2
- package/dist/index.cjs +20496 -217
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +278 -22
- package/dist/index.d.ts +278 -22
- package/dist/index.mjs +20489 -220
- package/dist/index.mjs.map +1 -1
- package/dist/lane/index.cjs +891 -72
- package/dist/lane/index.cjs.map +1 -1
- package/dist/lane/index.mjs +892 -73
- 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 +38 -18
- 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 +38 -19
- 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-BbY_H_f6.d.ts +0 -99
- package/dist/index-XsMsVc3O.d.cts +0 -99
package/dist/cli/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import * as x509 from '@peculiar/x509';
|
|
|
2
2
|
import { randomUUID, createHash, webcrypto, randomBytes, pbkdf2Sync, createCipheriv, createDecipheriv } from 'crypto';
|
|
3
3
|
import { spawn, execFile, execFileSync } from 'child_process';
|
|
4
4
|
import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, copyFileSync, mkdtempSync, rmSync, chmodSync } from 'fs';
|
|
5
|
-
import { join, extname, resolve, basename } from 'path';
|
|
5
|
+
import { join, extname, resolve, basename, relative } from 'path';
|
|
6
6
|
import { homedir, tmpdir } from 'os';
|
|
7
7
|
import { promisify } from 'util';
|
|
8
8
|
import { mkdir, writeFile, readFile, readdir, unlink, stat, rm, mkdtemp } from 'fs/promises';
|
|
@@ -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,34 @@ var init_srp = __esm({
|
|
|
1754
1755
|
};
|
|
1755
1756
|
}
|
|
1756
1757
|
});
|
|
1758
|
+
|
|
1759
|
+
// src/client/apple-session.ts
|
|
1760
|
+
var apple_session_exports = {};
|
|
1761
|
+
__export(apple_session_exports, {
|
|
1762
|
+
ProgramLicenseAgreementError: () => ProgramLicenseAgreementError,
|
|
1763
|
+
appleJson: () => appleJson,
|
|
1764
|
+
beginLogin: () => beginLogin,
|
|
1765
|
+
checkPortalResponse: () => checkPortalResponse,
|
|
1766
|
+
fetchServiceKey: () => fetchServiceKey,
|
|
1767
|
+
loginWithAppleId: () => loginWithAppleId,
|
|
1768
|
+
requestSmsCode: () => requestSmsCode,
|
|
1769
|
+
serviceKeyFromLocation: () => serviceKeyFromLocation,
|
|
1770
|
+
submitTwoFactorCode: () => submitTwoFactorCode
|
|
1771
|
+
});
|
|
1772
|
+
async function appleJson(res, what) {
|
|
1773
|
+
const text = await res.text();
|
|
1774
|
+
try {
|
|
1775
|
+
return JSON.parse(text);
|
|
1776
|
+
} catch {
|
|
1777
|
+
const contentType = (res.headers.get("content-type") ?? "").split(";")[0].trim();
|
|
1778
|
+
const title = /<title>([^<]{1,120})<\/title>/i.exec(text)?.[1]?.trim();
|
|
1779
|
+
const looksHtml = /^\s*<(!doctype|html)/i.test(text) || contentType === "text/html";
|
|
1780
|
+
const page = looksHtml ? "an HTML page" : contentType ? `a ${contentType} response` : "a non-JSON response";
|
|
1781
|
+
throw new Error(
|
|
1782
|
+
`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.`
|
|
1783
|
+
);
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1757
1786
|
function checkPortalResponse(body, session) {
|
|
1758
1787
|
if (!body || typeof body !== "object") return;
|
|
1759
1788
|
if (body.resultCode === 4100) {
|
|
@@ -1830,7 +1859,7 @@ async function beginLogin(config) {
|
|
|
1830
1859
|
}
|
|
1831
1860
|
async function fetchTwoFactorChallenge(pending) {
|
|
1832
1861
|
const r = await fetch(`${AUTH_BASE}`, { headers: buildAuthHeadersFromPending(pending) });
|
|
1833
|
-
const body = await r
|
|
1862
|
+
const body = await appleJson(r, "two-factor challenge");
|
|
1834
1863
|
const trustedPhoneNumbers = (body.trustedPhoneNumbers ?? []).map((p) => ({
|
|
1835
1864
|
id: p.id,
|
|
1836
1865
|
numberWithDialCode: p.numberWithDialCode,
|
|
@@ -1851,7 +1880,8 @@ async function requestSmsCode(input) {
|
|
|
1851
1880
|
body: JSON.stringify({ phoneNumber: { id: input.phoneId }, mode: input.mode ?? "sms" })
|
|
1852
1881
|
});
|
|
1853
1882
|
}
|
|
1854
|
-
async function submitTwoFactorCode(
|
|
1883
|
+
async function submitTwoFactorCode(rawInput) {
|
|
1884
|
+
const input = { ...rawInput, code: rawInput.code.replace(/[\s\u00a0]+/g, "") };
|
|
1855
1885
|
const log = input.onLog ?? (() => {
|
|
1856
1886
|
});
|
|
1857
1887
|
let path;
|
|
@@ -1963,9 +1993,12 @@ async function doSirp(user, password, serviceKey, cookieStr, hashcash) {
|
|
|
1963
1993
|
body: JSON.stringify({ a: aBase64, accountName: user, protocols: ["s2k", "s2k_fo"] }),
|
|
1964
1994
|
redirect: "manual"
|
|
1965
1995
|
});
|
|
1966
|
-
const initBody = await initRes
|
|
1996
|
+
const initBody = await appleJson(initRes, "sign-in init");
|
|
1967
1997
|
if (initBody.serviceErrors) throw new Error(`Apple auth error: ${JSON.stringify(initBody.serviceErrors)}`);
|
|
1968
1998
|
const { iteration: iterations, salt: saltB64, b: bB64, c, protocol } = initBody;
|
|
1999
|
+
if (!iterations || !saltB64 || !bB64 || !c || !protocol) {
|
|
2000
|
+
throw new Error(`Apple's sign-in init answered without a challenge (HTTP ${initRes.status}): ${JSON.stringify(initBody).slice(0, 300)}`);
|
|
2001
|
+
}
|
|
1969
2002
|
const salt = Buffer.from(saltB64, "base64");
|
|
1970
2003
|
const b = Buffer.from(bB64, "base64");
|
|
1971
2004
|
const encryptedPassword = applePbkdf2(password, salt, iterations, 32, protocol);
|
|
@@ -2008,7 +2041,7 @@ async function fetchOlympusSession(session) {
|
|
|
2008
2041
|
const cookieStr = Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join("; ");
|
|
2009
2042
|
const res = await fetch(OLYMPUS_SESSION, { headers: { Cookie: cookieStr, Accept: "application/json" } });
|
|
2010
2043
|
if (res.ok) {
|
|
2011
|
-
const body = await res
|
|
2044
|
+
const body = await appleJson(res, "session lookup");
|
|
2012
2045
|
if (body?.user?.emailAddress) session.userEmail = body.user.emailAddress;
|
|
2013
2046
|
if (body?.provider?.providerId) session.teamId = String(body.provider.providerId);
|
|
2014
2047
|
}
|
|
@@ -2020,7 +2053,7 @@ async function fetchContractMessages(session) {
|
|
|
2020
2053
|
headers: { Cookie: cookieStr, Accept: "application/json" }
|
|
2021
2054
|
});
|
|
2022
2055
|
if (!res.ok) return [];
|
|
2023
|
-
const body = await res
|
|
2056
|
+
const body = await appleJson(res, "agreements lookup");
|
|
2024
2057
|
return (body ?? []).map((m) => ({
|
|
2025
2058
|
id: m.id ?? "",
|
|
2026
2059
|
group: m.group ?? "",
|
|
@@ -2031,10 +2064,22 @@ async function fetchContractMessages(session) {
|
|
|
2031
2064
|
return [];
|
|
2032
2065
|
}
|
|
2033
2066
|
}
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2067
|
+
function serviceKeyFromLocation(location) {
|
|
2068
|
+
if (!location) return null;
|
|
2069
|
+
try {
|
|
2070
|
+
const key = new URL(location).searchParams.get("widgetKey")?.trim();
|
|
2071
|
+
return key && /^[0-9a-f]{16,}$/i.test(key) ? key : null;
|
|
2072
|
+
} catch {
|
|
2073
|
+
return null;
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
async function fetchServiceKey(fetchImpl = fetch) {
|
|
2077
|
+
const head = await fetchImpl(LOGOUT_URL, { method: "HEAD", redirect: "manual" });
|
|
2078
|
+
const fromRedirect = serviceKeyFromLocation(head.headers.get("location"));
|
|
2079
|
+
if (fromRedirect) return fromRedirect;
|
|
2080
|
+
const res = await fetchImpl(WIDGET_KEY_URL);
|
|
2081
|
+
const body = await appleJson(res, "service key request");
|
|
2082
|
+
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
2083
|
return body.authServiceKey;
|
|
2039
2084
|
}
|
|
2040
2085
|
function buildAuthHeaders(session) {
|
|
@@ -2082,7 +2127,7 @@ async function defaultPrompt(message) {
|
|
|
2082
2127
|
});
|
|
2083
2128
|
});
|
|
2084
2129
|
}
|
|
2085
|
-
var AUTH_BASE, OLYMPUS_SESSION, WIDGET_KEY_URL, ProgramLicenseAgreementError;
|
|
2130
|
+
var AUTH_BASE, OLYMPUS_SESSION, WIDGET_KEY_URL, ProgramLicenseAgreementError, LOGOUT_URL;
|
|
2086
2131
|
var init_apple_session = __esm({
|
|
2087
2132
|
"src/client/apple-session.ts"() {
|
|
2088
2133
|
init_srp();
|
|
@@ -2097,6 +2142,7 @@ var init_apple_session = __esm({
|
|
|
2097
2142
|
}
|
|
2098
2143
|
agreements;
|
|
2099
2144
|
};
|
|
2145
|
+
LOGOUT_URL = "https://appstoreconnect.apple.com/logout";
|
|
2100
2146
|
}
|
|
2101
2147
|
});
|
|
2102
2148
|
|
|
@@ -2413,9 +2459,10 @@ async function uploadWithTransporter(options) {
|
|
|
2413
2459
|
const transporterBin = await resolveTransporter();
|
|
2414
2460
|
const platform = options.platform ?? "ios";
|
|
2415
2461
|
const timeout = options.timeout ?? 18e5;
|
|
2416
|
-
const keyDir =
|
|
2462
|
+
const keyDir = join(homedir(), ".appstoreconnect", "private_keys");
|
|
2463
|
+
await mkdir(keyDir, { recursive: true });
|
|
2417
2464
|
const keyPath = join(keyDir, `AuthKey_${options.apiKeyId}.p8`);
|
|
2418
|
-
await writeFile(keyPath, options.apiPrivateKey);
|
|
2465
|
+
await writeFile(keyPath, options.apiPrivateKey, { mode: 384 });
|
|
2419
2466
|
return new Promise((resolve4) => {
|
|
2420
2467
|
const args = [
|
|
2421
2468
|
"-m",
|
|
@@ -2431,6 +2478,9 @@ async function uploadWithTransporter(options) {
|
|
|
2431
2478
|
"-WONoPause",
|
|
2432
2479
|
"true"
|
|
2433
2480
|
];
|
|
2481
|
+
if (options.assetDescriptionPath) {
|
|
2482
|
+
args.push("-assetDescription", options.assetDescriptionPath);
|
|
2483
|
+
}
|
|
2434
2484
|
if (platform !== "ios") {
|
|
2435
2485
|
args.push("-bundleType", platform);
|
|
2436
2486
|
}
|
|
@@ -2454,13 +2504,13 @@ async function uploadWithTransporter(options) {
|
|
|
2454
2504
|
child.stderr.on("data", onData);
|
|
2455
2505
|
child.on("close", async (code) => {
|
|
2456
2506
|
clearTimeout(timer);
|
|
2457
|
-
await rm(
|
|
2507
|
+
await rm(keyPath, { force: true }).catch(() => {
|
|
2458
2508
|
});
|
|
2459
2509
|
resolve4({ success: code === 0, output, backend: "transporter" });
|
|
2460
2510
|
});
|
|
2461
2511
|
child.on("error", async (err) => {
|
|
2462
2512
|
clearTimeout(timer);
|
|
2463
|
-
await rm(
|
|
2513
|
+
await rm(keyPath, { force: true }).catch(() => {
|
|
2464
2514
|
});
|
|
2465
2515
|
resolve4({
|
|
2466
2516
|
success: false,
|
|
@@ -2752,6 +2802,311 @@ var init_metadata = __esm({
|
|
|
2752
2802
|
};
|
|
2753
2803
|
}
|
|
2754
2804
|
});
|
|
2805
|
+
function categoryFor(placementType) {
|
|
2806
|
+
return placementType === "APP_SCREENSHOT" || placementType === "IMESSAGE_APP_SCREENSHOT" || placementType === "APP_PREVIEW" ? "APP_SCREENSHOTS_AND_PREVIEWS" : "CREATIVE_ASSETS";
|
|
2807
|
+
}
|
|
2808
|
+
function readImageSize(data) {
|
|
2809
|
+
if (data.length >= 26 && data.readUInt32BE(0) === 2303741511) {
|
|
2810
|
+
const colorType = data[25];
|
|
2811
|
+
return {
|
|
2812
|
+
width: data.readUInt32BE(16),
|
|
2813
|
+
height: data.readUInt32BE(20),
|
|
2814
|
+
alpha: colorType === 4 || colorType === 6
|
|
2815
|
+
};
|
|
2816
|
+
}
|
|
2817
|
+
if (data.length >= 4 && data[0] === 255 && data[1] === 216) {
|
|
2818
|
+
let offset = 2;
|
|
2819
|
+
while (offset + 9 < data.length) {
|
|
2820
|
+
if (data[offset] !== 255) return null;
|
|
2821
|
+
const marker = data[offset + 1];
|
|
2822
|
+
const length = data.readUInt16BE(offset + 2);
|
|
2823
|
+
const isSof = marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204;
|
|
2824
|
+
if (isSof) {
|
|
2825
|
+
return { height: data.readUInt16BE(offset + 5), width: data.readUInt16BE(offset + 7), alpha: false };
|
|
2826
|
+
}
|
|
2827
|
+
offset += 2 + length;
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
return null;
|
|
2831
|
+
}
|
|
2832
|
+
function fits(spec, width, height) {
|
|
2833
|
+
const d = spec.dimensions;
|
|
2834
|
+
return width >= d.minWidth && width <= d.maxWidth && height >= d.minHeight && height <= d.maxHeight;
|
|
2835
|
+
}
|
|
2836
|
+
function groupsForFeature(refData, feature, placementType) {
|
|
2837
|
+
const mappings = refData.placementTypes?.find((p) => p.placementTypeId === placementType)?.specMappings ?? [];
|
|
2838
|
+
const all = mappings.map((m) => m.placementGroupId);
|
|
2839
|
+
const policy = refData.features?.find((f) => f.featureId === feature)?.placementPolicies?.find((p) => p.placementType === placementType);
|
|
2840
|
+
const limited = policy?.groupLimits?.flatMap((l) => l.groupIds);
|
|
2841
|
+
return limited?.length ? all.filter((g) => limited.includes(g)) : all;
|
|
2842
|
+
}
|
|
2843
|
+
function maxCountFor(refData, feature, placementType, placementGroup) {
|
|
2844
|
+
return refData.features?.find((f) => f.featureId === feature)?.placementPolicies?.find((p) => p.placementType === placementType)?.groupLimits?.find((l) => l.groupIds.includes(placementGroup))?.maxCount;
|
|
2845
|
+
}
|
|
2846
|
+
function resolveImageGroups(refData, feature, placementType, size) {
|
|
2847
|
+
const specs = (refData.imageSpecs ?? []).filter(
|
|
2848
|
+
(s) => s.compatiblePlacementTypes.includes(placementType) && fits(s, size.width, size.height)
|
|
2849
|
+
);
|
|
2850
|
+
const specIds = new Set(specs.map((s) => s.specId));
|
|
2851
|
+
const allowed = new Set(groupsForFeature(refData, feature, placementType));
|
|
2852
|
+
const mappings = refData.placementTypes?.find((p) => p.placementTypeId === placementType)?.specMappings ?? [];
|
|
2853
|
+
const groups = mappings.filter((m) => allowed.has(m.placementGroupId) && m.specs.some((id) => specIds.has(id))).map((m) => m.placementGroupId);
|
|
2854
|
+
return { groups: [...new Set(groups)], spec: specs[0] };
|
|
2855
|
+
}
|
|
2856
|
+
async function runUploadOperations(ops, data) {
|
|
2857
|
+
for (const op of ops) {
|
|
2858
|
+
const headers = {};
|
|
2859
|
+
for (const h of op.requestHeaders) headers[h.name] = h.value;
|
|
2860
|
+
const res = await fetch(op.url, {
|
|
2861
|
+
method: op.method,
|
|
2862
|
+
headers,
|
|
2863
|
+
body: data.subarray(op.offset, op.offset + op.length)
|
|
2864
|
+
});
|
|
2865
|
+
if (!res.ok) {
|
|
2866
|
+
throw new Error(`Upload operation failed: ${op.method} ${new URL(op.url).host} \u2192 HTTP ${res.status}`);
|
|
2867
|
+
}
|
|
2868
|
+
}
|
|
2869
|
+
}
|
|
2870
|
+
async function uploadLibraryAsset(options) {
|
|
2871
|
+
const { client, libraryId, filePath, category } = options;
|
|
2872
|
+
const data = await readFile(filePath);
|
|
2873
|
+
const fileName = basename(filePath);
|
|
2874
|
+
const kind = VIDEO_EXTENSIONS.includes(extname(filePath).toLowerCase()) ? "video" : "image";
|
|
2875
|
+
const hash = createHash("sha256").update(data).digest("hex").slice(0, 12);
|
|
2876
|
+
const referenceName = `${options.referenceName ?? fileName} #${hash}`;
|
|
2877
|
+
const media = kind === "image" ? client.appAssetLibraryImages : client.appAssetLibraryVideos;
|
|
2878
|
+
const filter = { filter: { referenceName } };
|
|
2879
|
+
const existing = kind === "image" ? await client.appAssetLibraries.listImages(libraryId, filter) : await client.appAssetLibraries.listVideos(libraryId, filter);
|
|
2880
|
+
const reusable = existing.find(
|
|
2881
|
+
(a) => a.attributes.category === category && a.attributes.state && READY_STATES.includes(a.attributes.state)
|
|
2882
|
+
);
|
|
2883
|
+
if (reusable) {
|
|
2884
|
+
return { kind, id: reusable.id, state: reusable.attributes.state, reused: true };
|
|
2885
|
+
}
|
|
2886
|
+
const reserved = kind === "image" ? await client.appAssetLibraryImages.create({ libraryId, fileName, fileSize: data.length, category, referenceName }) : await client.appAssetLibraryVideos.create({
|
|
2887
|
+
libraryId,
|
|
2888
|
+
fileName,
|
|
2889
|
+
fileSize: data.length,
|
|
2890
|
+
category,
|
|
2891
|
+
referenceName,
|
|
2892
|
+
previewFrameTimeCode: options.previewFrameTimeCode
|
|
2893
|
+
});
|
|
2894
|
+
await runUploadOperations(reserved.attributes.uploadOperations ?? [], data);
|
|
2895
|
+
await media.update(reserved.id, { uploaded: true });
|
|
2896
|
+
options.log?.(`uploaded ${fileName} (${kind}, ${data.length} bytes)`);
|
|
2897
|
+
const state = await waitForAssetReady(
|
|
2898
|
+
() => media.get(reserved.id),
|
|
2899
|
+
options.pollIntervalMs ?? 3e3,
|
|
2900
|
+
options.timeoutMs ?? (kind === "video" ? 30 * 6e4 : 5 * 6e4)
|
|
2901
|
+
);
|
|
2902
|
+
return { kind, id: reserved.id, state, reused: false };
|
|
2903
|
+
}
|
|
2904
|
+
async function waitForAssetReady(get, intervalMs, timeoutMs) {
|
|
2905
|
+
const deadline = Date.now() + timeoutMs;
|
|
2906
|
+
for (; ; ) {
|
|
2907
|
+
const asset = await get();
|
|
2908
|
+
const state = asset.attributes.state;
|
|
2909
|
+
if (state === "FAILED") {
|
|
2910
|
+
const why = (asset.attributes.stateDetails ?? []).map((d) => d.message ?? d.code).filter(Boolean).join("; ");
|
|
2911
|
+
throw new Error(`Asset processing failed${why ? `: ${why}` : ""}`);
|
|
2912
|
+
}
|
|
2913
|
+
if (state && READY_STATES.includes(state)) return state;
|
|
2914
|
+
if (Date.now() > deadline) throw new Error(`Asset still ${state ?? "unknown"} after ${timeoutMs / 1e3}s`);
|
|
2915
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
async function listFiles(dir) {
|
|
2919
|
+
return (await readdir(dir)).filter((f) => !f.startsWith(".")).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }));
|
|
2920
|
+
}
|
|
2921
|
+
async function isDir(path) {
|
|
2922
|
+
return (await stat(path)).isDirectory();
|
|
2923
|
+
}
|
|
2924
|
+
async function planLocale(localeDir, refData, feature, folders, warn) {
|
|
2925
|
+
const planned = [];
|
|
2926
|
+
for (const [folder, placementType] of Object.entries(folders)) {
|
|
2927
|
+
const typeDir = join(localeDir, folder);
|
|
2928
|
+
if (!existsSync(typeDir)) continue;
|
|
2929
|
+
const known = new Set(groupsForFeature(refData, feature, placementType));
|
|
2930
|
+
const entries = [];
|
|
2931
|
+
for (const entry of await listFiles(typeDir)) {
|
|
2932
|
+
const full = join(typeDir, entry);
|
|
2933
|
+
if (await isDir(full)) {
|
|
2934
|
+
if (!known.has(entry)) {
|
|
2935
|
+
warn(`${relative(localeDir, full)}: "${entry}" is not a ${placementType} placement group for ${feature} \u2014 skipped`);
|
|
2936
|
+
continue;
|
|
2937
|
+
}
|
|
2938
|
+
for (const f of await listFiles(full)) entries.push({ filePath: join(full, f), group: entry });
|
|
2939
|
+
} else {
|
|
2940
|
+
entries.push({ filePath: full });
|
|
2941
|
+
}
|
|
2942
|
+
}
|
|
2943
|
+
for (const { filePath, group } of entries) {
|
|
2944
|
+
const ext = extname(filePath).toLowerCase();
|
|
2945
|
+
const isVideo = VIDEO_EXTENSIONS.includes(ext);
|
|
2946
|
+
if (!isVideo && !IMAGE_EXTENSIONS.includes(ext)) continue;
|
|
2947
|
+
const needsVideo = placementType === "APP_PREVIEW";
|
|
2948
|
+
const needsImage = placementType === "APP_SCREENSHOT" || placementType === "IMESSAGE_APP_SCREENSHOT";
|
|
2949
|
+
if (needsVideo && !isVideo || needsImage && isVideo) {
|
|
2950
|
+
warn(`${relative(localeDir, filePath)}: ${isVideo ? "video" : "image"} cannot be a ${placementType} \u2014 skipped`);
|
|
2951
|
+
continue;
|
|
2952
|
+
}
|
|
2953
|
+
if (group) {
|
|
2954
|
+
planned.push({ filePath, placementType, groups: [group] });
|
|
2955
|
+
continue;
|
|
2956
|
+
}
|
|
2957
|
+
if (isVideo) {
|
|
2958
|
+
if (known.size === 1) planned.push({ filePath, placementType, groups: [...known] });
|
|
2959
|
+
else warn(`${relative(localeDir, filePath)}: put videos in a placement-group folder (one of ${[...known].join(", ")}) \u2014 skipped`);
|
|
2960
|
+
continue;
|
|
2961
|
+
}
|
|
2962
|
+
const size = readImageSize(await readFile(filePath));
|
|
2963
|
+
if (!size) {
|
|
2964
|
+
warn(`${relative(localeDir, filePath)}: could not read image size \u2014 skipped`);
|
|
2965
|
+
continue;
|
|
2966
|
+
}
|
|
2967
|
+
const { groups, spec } = resolveImageGroups(refData, feature, placementType, size);
|
|
2968
|
+
if (!groups.length) {
|
|
2969
|
+
warn(`${relative(localeDir, filePath)}: ${size.width}\xD7${size.height} matches no ${placementType} spec \u2014 skipped`);
|
|
2970
|
+
continue;
|
|
2971
|
+
}
|
|
2972
|
+
if (size.alpha && spec && spec.alphaAllowed === false) {
|
|
2973
|
+
warn(`${relative(localeDir, filePath)}: has an alpha channel, which Apple rejects for this spec`);
|
|
2974
|
+
}
|
|
2975
|
+
planned.push({ filePath, placementType, groups: groups.slice(0, 1) });
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
return planned;
|
|
2979
|
+
}
|
|
2980
|
+
async function uploadAssetLibrary(options) {
|
|
2981
|
+
const { client } = options;
|
|
2982
|
+
const log = options.log ?? (() => {
|
|
2983
|
+
});
|
|
2984
|
+
const feature = "APP_STORE_VERSIONS";
|
|
2985
|
+
const result = { uploaded: 0, reused: 0, placed: 0, removed: 0, warnings: [] };
|
|
2986
|
+
const warn = (line) => {
|
|
2987
|
+
result.warnings.push(line);
|
|
2988
|
+
log(`warning: ${line}`);
|
|
2989
|
+
};
|
|
2990
|
+
const [library, refData, localizations] = await Promise.all([
|
|
2991
|
+
client.appAssetLibraries.getForApp(options.appId),
|
|
2992
|
+
client.appAssetLibraries.refData({ features: [feature] }),
|
|
2993
|
+
client.appStoreVersions.getLocalizations(options.versionId)
|
|
2994
|
+
]);
|
|
2995
|
+
const folders = { ...options.folders ?? ASSET_FOLDERS };
|
|
2996
|
+
if (options.rootPlacementType) folders["."] = options.rootPlacementType;
|
|
2997
|
+
const uploads = /* @__PURE__ */ new Map();
|
|
2998
|
+
const uploadOnce = (filePath, placementType) => {
|
|
2999
|
+
const key = `${filePath}|${categoryFor(placementType)}`;
|
|
3000
|
+
if (!uploads.has(key)) {
|
|
3001
|
+
uploads.set(
|
|
3002
|
+
key,
|
|
3003
|
+
uploadLibraryAsset({
|
|
3004
|
+
client,
|
|
3005
|
+
libraryId: library.id,
|
|
3006
|
+
filePath,
|
|
3007
|
+
category: categoryFor(placementType),
|
|
3008
|
+
referenceName: relative(options.assetsPath, filePath),
|
|
3009
|
+
log
|
|
3010
|
+
}).then((a) => {
|
|
3011
|
+
if (a.reused) result.reused++;
|
|
3012
|
+
else result.uploaded++;
|
|
3013
|
+
return a;
|
|
3014
|
+
})
|
|
3015
|
+
);
|
|
3016
|
+
}
|
|
3017
|
+
return uploads.get(key);
|
|
3018
|
+
};
|
|
3019
|
+
for (const loc of localizations) {
|
|
3020
|
+
const localeDir = join(options.assetsPath, loc.attributes.locale);
|
|
3021
|
+
if (!existsSync(localeDir)) continue;
|
|
3022
|
+
const target = { kind: "appStoreVersionLocalization", id: loc.id };
|
|
3023
|
+
const plan = await planLocale(localeDir, refData, feature, folders, warn);
|
|
3024
|
+
const slots = /* @__PURE__ */ new Map();
|
|
3025
|
+
for (const p of plan) {
|
|
3026
|
+
for (const group of p.groups) {
|
|
3027
|
+
const key = `${p.placementType}|${group}`;
|
|
3028
|
+
if (!slots.has(key)) slots.set(key, { placementType: p.placementType, group, files: [] });
|
|
3029
|
+
slots.get(key).files.push(p.filePath);
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
3032
|
+
for (const slot of slots.values()) {
|
|
3033
|
+
const where = `${loc.attributes.locale} ${slot.placementType} ${slot.group}`;
|
|
3034
|
+
const max = maxCountFor(refData, feature, slot.placementType, slot.group);
|
|
3035
|
+
if (max !== void 0 && slot.files.length > max) {
|
|
3036
|
+
warn(`${where}: ${slot.files.length} files but Apple allows ${max} \u2014 keeping the first ${max}`);
|
|
3037
|
+
slot.files = slot.files.slice(0, max);
|
|
3038
|
+
}
|
|
3039
|
+
const existing = await client.appAssetLibraryPlacements.listForTarget(target, {
|
|
3040
|
+
placementType: slot.placementType,
|
|
3041
|
+
placementGroup: slot.group,
|
|
3042
|
+
include: "image,video"
|
|
3043
|
+
});
|
|
3044
|
+
if (options.overwrite) {
|
|
3045
|
+
for (const p of existing) {
|
|
3046
|
+
await client.appAssetLibraryPlacements.delete(p.id);
|
|
3047
|
+
result.removed++;
|
|
3048
|
+
}
|
|
3049
|
+
}
|
|
3050
|
+
const kept = options.overwrite ? [] : existing;
|
|
3051
|
+
const keptAssetIds = new Set(kept.map((p) => placementAssetId(p)).filter(Boolean));
|
|
3052
|
+
const ours = [];
|
|
3053
|
+
for (const filePath of slot.files) {
|
|
3054
|
+
const asset = await uploadOnce(filePath, slot.placementType);
|
|
3055
|
+
if (keptAssetIds.has(asset.id)) continue;
|
|
3056
|
+
const placement = await client.appAssetLibraryPlacements.create({
|
|
3057
|
+
placementType: slot.placementType,
|
|
3058
|
+
placementGroup: slot.group,
|
|
3059
|
+
...asset.kind === "image" ? { imageId: asset.id } : { videoId: asset.id },
|
|
3060
|
+
target
|
|
3061
|
+
});
|
|
3062
|
+
ours.push(placement.id);
|
|
3063
|
+
result.placed++;
|
|
3064
|
+
}
|
|
3065
|
+
log(`${where}: placed ${ours.length}${kept.length ? `, kept ${kept.length}` : ""}`);
|
|
3066
|
+
const orderable = slot.placementType === "APP_SCREENSHOT" || slot.placementType === "APP_PREVIEW" || slot.placementType === "IMESSAGE_APP_SCREENSHOT";
|
|
3067
|
+
if (orderable && ours.length + kept.length > 1) {
|
|
3068
|
+
await client.appAssetLibraryPlacements.order({
|
|
3069
|
+
placementGroup: slot.group,
|
|
3070
|
+
placementIds: [...ours, ...kept.map((p) => p.id)],
|
|
3071
|
+
target
|
|
3072
|
+
});
|
|
3073
|
+
}
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
return result;
|
|
3077
|
+
}
|
|
3078
|
+
function placementAssetId(p) {
|
|
3079
|
+
for (const rel of ["image", "video"]) {
|
|
3080
|
+
const data = p.relationships?.[rel]?.data;
|
|
3081
|
+
if (data?.id) return data.id;
|
|
3082
|
+
}
|
|
3083
|
+
return void 0;
|
|
3084
|
+
}
|
|
3085
|
+
var ASSET_FOLDERS, IMAGE_EXTENSIONS, VIDEO_EXTENSIONS, READY_STATES;
|
|
3086
|
+
var init_assets = __esm({
|
|
3087
|
+
"src/deliver/assets.ts"() {
|
|
3088
|
+
ASSET_FOLDERS = {
|
|
3089
|
+
screenshots: "APP_SCREENSHOT",
|
|
3090
|
+
imessage_screenshots: "IMESSAGE_APP_SCREENSHOT",
|
|
3091
|
+
previews: "APP_PREVIEW",
|
|
3092
|
+
header: "PRODUCT_PAGE_HEADER_ASSET",
|
|
3093
|
+
search_results: "APP_STORE_SEARCH_RESULTS_ASSET",
|
|
3094
|
+
event_card: "EVENT_CARD_ASSET",
|
|
3095
|
+
event_details: "EVENT_DETAILS_PAGE_ASSET"
|
|
3096
|
+
};
|
|
3097
|
+
IMAGE_EXTENSIONS = [".png", ".jpg", ".jpeg"];
|
|
3098
|
+
VIDEO_EXTENSIONS = [".mp4", ".mov", ".m4v"];
|
|
3099
|
+
READY_STATES = [
|
|
3100
|
+
"COMPLETE",
|
|
3101
|
+
"PREPARE_FOR_SUBMISSION",
|
|
3102
|
+
"READY_FOR_REVIEW",
|
|
3103
|
+
"WAITING_FOR_REVIEW",
|
|
3104
|
+
"IN_REVIEW",
|
|
3105
|
+
"ACCEPTED",
|
|
3106
|
+
"APPROVED"
|
|
3107
|
+
];
|
|
3108
|
+
}
|
|
3109
|
+
});
|
|
2755
3110
|
|
|
2756
3111
|
// src/xcode/upload.ts
|
|
2757
3112
|
var upload_exports = {};
|
|
@@ -2817,8 +3172,14 @@ var init_upload = __esm({
|
|
|
2817
3172
|
// src/deliver/index.ts
|
|
2818
3173
|
var deliver_exports = {};
|
|
2819
3174
|
__export(deliver_exports, {
|
|
3175
|
+
ASSET_FOLDERS: () => ASSET_FOLDERS,
|
|
3176
|
+
categoryFor: () => categoryFor,
|
|
2820
3177
|
loadMetadata: () => loadMetadata,
|
|
3178
|
+
readImageSize: () => readImageSize,
|
|
3179
|
+
resolveImageGroups: () => resolveImageGroups,
|
|
2821
3180
|
submitForReview: () => submitForReview,
|
|
3181
|
+
uploadAssetLibrary: () => uploadAssetLibrary,
|
|
3182
|
+
uploadLibraryAsset: () => uploadLibraryAsset,
|
|
2822
3183
|
uploadMetadata: () => uploadMetadata,
|
|
2823
3184
|
uploadScreenshots: () => uploadScreenshots,
|
|
2824
3185
|
uploadToAppStore: () => uploadToAppStore
|
|
@@ -2857,11 +3218,36 @@ async function uploadToAppStore(options) {
|
|
|
2857
3218
|
copyright: options.copyright
|
|
2858
3219
|
});
|
|
2859
3220
|
}
|
|
3221
|
+
const log = (line) => options.onOutput?.(`${line}
|
|
3222
|
+
`);
|
|
2860
3223
|
if (!options.skipScreenshots && options.screenshotsPath && existsSync(options.screenshotsPath)) {
|
|
2861
|
-
|
|
3224
|
+
if (options.useAssetLibrary) {
|
|
3225
|
+
await uploadAssetLibrary({
|
|
3226
|
+
client: options.client,
|
|
3227
|
+
appId: app.id,
|
|
3228
|
+
versionId: version.id,
|
|
3229
|
+
assetsPath: options.screenshotsPath,
|
|
3230
|
+
folders: {},
|
|
3231
|
+
rootPlacementType: "APP_SCREENSHOT",
|
|
3232
|
+
overwrite: options.overwriteAssets,
|
|
3233
|
+
log
|
|
3234
|
+
});
|
|
3235
|
+
} else {
|
|
3236
|
+
await uploadScreenshots({
|
|
3237
|
+
client: options.client,
|
|
3238
|
+
versionId: version.id,
|
|
3239
|
+
screenshotsPath: options.screenshotsPath
|
|
3240
|
+
});
|
|
3241
|
+
}
|
|
3242
|
+
}
|
|
3243
|
+
if (!options.skipAssets && options.assetsPath && existsSync(options.assetsPath)) {
|
|
3244
|
+
await uploadAssetLibrary({
|
|
2862
3245
|
client: options.client,
|
|
3246
|
+
appId: app.id,
|
|
2863
3247
|
versionId: version.id,
|
|
2864
|
-
|
|
3248
|
+
assetsPath: options.assetsPath,
|
|
3249
|
+
overwrite: options.overwriteAssets,
|
|
3250
|
+
log
|
|
2865
3251
|
});
|
|
2866
3252
|
}
|
|
2867
3253
|
if (!options.skipBinaryUpload) {
|
|
@@ -2946,17 +3332,7 @@ async function uploadScreenshots(options) {
|
|
|
2946
3332
|
fileName: file,
|
|
2947
3333
|
fileSize: fileData.length
|
|
2948
3334
|
});
|
|
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
|
-
}
|
|
3335
|
+
await runUploadOperations(reserved.attributes.uploadOperations ?? [], fileData);
|
|
2960
3336
|
await options.client.appScreenshots.commitScreenshot(reserved.id, {
|
|
2961
3337
|
sourceFileChecksum: checksum,
|
|
2962
3338
|
uploaded: true
|
|
@@ -2976,7 +3352,9 @@ function detectDisplayType(filename) {
|
|
|
2976
3352
|
var init_deliver = __esm({
|
|
2977
3353
|
"src/deliver/index.ts"() {
|
|
2978
3354
|
init_metadata();
|
|
3355
|
+
init_assets();
|
|
2979
3356
|
init_metadata();
|
|
3357
|
+
init_assets();
|
|
2980
3358
|
}
|
|
2981
3359
|
});
|
|
2982
3360
|
|
|
@@ -2985,6 +3363,7 @@ var produce_exports = {};
|
|
|
2985
3363
|
__export(produce_exports, {
|
|
2986
3364
|
SERVICE_NAME_TO_CAPABILITY: () => SERVICE_NAME_TO_CAPABILITY,
|
|
2987
3365
|
addCapability: () => addCapability,
|
|
3366
|
+
associateAppGroups: () => associateAppGroups,
|
|
2988
3367
|
buildCapabilitySettings: () => buildCapabilitySettings,
|
|
2989
3368
|
createApp: () => createApp,
|
|
2990
3369
|
enableServices: () => enableServices,
|
|
@@ -3079,6 +3458,30 @@ async function enableServices(options) {
|
|
|
3079
3458
|
});
|
|
3080
3459
|
}
|
|
3081
3460
|
}
|
|
3461
|
+
async function associateAppGroups(options) {
|
|
3462
|
+
if (!options.appGroups?.length) return;
|
|
3463
|
+
await options.portalClient.enableAppGroups(options.bundleIdId).catch(() => {
|
|
3464
|
+
});
|
|
3465
|
+
const existing = await options.portalClient.listAppGroups();
|
|
3466
|
+
const appGroupIds = [];
|
|
3467
|
+
for (const group of options.appGroups) {
|
|
3468
|
+
let record = existing.find((ag) => ag.identifier === group.identifier);
|
|
3469
|
+
if (!record) {
|
|
3470
|
+
record = await options.portalClient.createAppGroup({
|
|
3471
|
+
name: group.name,
|
|
3472
|
+
identifier: group.identifier
|
|
3473
|
+
});
|
|
3474
|
+
}
|
|
3475
|
+
const id = record?.applicationGroup ?? record?.applicationGroupId ?? record?.appGroupId ?? record?.id;
|
|
3476
|
+
if (id) appGroupIds.push(id);
|
|
3477
|
+
}
|
|
3478
|
+
if (appGroupIds.length) {
|
|
3479
|
+
await options.portalClient.assignAppGroups({
|
|
3480
|
+
appIdId: options.bundleIdId,
|
|
3481
|
+
appGroupIds
|
|
3482
|
+
});
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3082
3485
|
async function createApp(options) {
|
|
3083
3486
|
const existing = await options.client.apps.findByBundleId(options.bundleIdentifier);
|
|
3084
3487
|
if (existing) return existing;
|
|
@@ -3097,25 +3500,20 @@ async function createApp(options) {
|
|
|
3097
3500
|
});
|
|
3098
3501
|
}
|
|
3099
3502
|
}
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
data: { type: "bundleIds", id: bundleId.id }
|
|
3114
|
-
}
|
|
3115
|
-
}
|
|
3116
|
-
}
|
|
3503
|
+
if (options.appGroups?.length && options.portalClient) {
|
|
3504
|
+
await associateAppGroups({
|
|
3505
|
+
portalClient: options.portalClient,
|
|
3506
|
+
bundleIdId: bundleId.id,
|
|
3507
|
+
appGroups: options.appGroups
|
|
3508
|
+
});
|
|
3509
|
+
}
|
|
3510
|
+
return options.client.apps.create({
|
|
3511
|
+
bundleIdentifier: options.bundleIdentifier,
|
|
3512
|
+
bundleIdId: bundleId.id,
|
|
3513
|
+
appName: options.appName,
|
|
3514
|
+
sku: options.sku ?? `${options.bundleIdentifier}.${Date.now()}`,
|
|
3515
|
+
primaryLocale: options.primaryLocale
|
|
3117
3516
|
});
|
|
3118
|
-
return response.data;
|
|
3119
3517
|
}
|
|
3120
3518
|
var SERVICE_NAME_TO_CAPABILITY;
|
|
3121
3519
|
var init_produce = __esm({
|
|
@@ -3918,10 +4316,10 @@ async function parseTestResults(options) {
|
|
|
3918
4316
|
} else if (inputPath.endsWith(".plist")) {
|
|
3919
4317
|
testSuites = parsePlistResults(inputPath);
|
|
3920
4318
|
} else {
|
|
3921
|
-
const
|
|
4319
|
+
const stat7 = await import('fs').then(
|
|
3922
4320
|
(fs) => fs.statSync(inputPath)
|
|
3923
4321
|
);
|
|
3924
|
-
if (
|
|
4322
|
+
if (stat7.isDirectory()) {
|
|
3925
4323
|
requireMacOS("xcresult parsing");
|
|
3926
4324
|
testSuites = await parseXCResult(inputPath);
|
|
3927
4325
|
} else {
|
|
@@ -4909,6 +5307,43 @@ var init_apps = __esm({
|
|
|
4909
5307
|
});
|
|
4910
5308
|
return results[0] ?? null;
|
|
4911
5309
|
}
|
|
5310
|
+
/**
|
|
5311
|
+
* Create an App Store Connect **app record** (the thing that shows up
|
|
5312
|
+
* under "My Apps" and that TestFlight / App Store uploads require).
|
|
5313
|
+
*
|
|
5314
|
+
* This is distinct from registering an App ID in the Developer Portal —
|
|
5315
|
+
* you need BOTH. The Developer Portal App ID unlocks provisioning
|
|
5316
|
+
* profiles and entitlements; the App Store Connect app record unlocks
|
|
5317
|
+
* uploads. `upload_to_testflight` fails with "Couldn't find app
|
|
5318
|
+
* 'com.foo.bar' on the account…" when this record is missing.
|
|
5319
|
+
*
|
|
5320
|
+
* `bundleIdId` is the REST API primary key of the bundle id resource
|
|
5321
|
+
* (not the identifier string) — fetch it via `bundleIds.find(identifier)`
|
|
5322
|
+
* or from the response of `bundleIds.create()`.
|
|
5323
|
+
*
|
|
5324
|
+
* `sku` is an arbitrary unique string Apple uses to identify the app in
|
|
5325
|
+
* their internal records. If you don't have a meaningful one, pass the
|
|
5326
|
+
* bundle identifier itself or append a timestamp for uniqueness.
|
|
5327
|
+
*/
|
|
5328
|
+
async create(params) {
|
|
5329
|
+
const response = await this.http.post("/v1/apps", {
|
|
5330
|
+
data: {
|
|
5331
|
+
type: "apps",
|
|
5332
|
+
attributes: {
|
|
5333
|
+
bundleId: params.bundleIdentifier,
|
|
5334
|
+
name: params.appName,
|
|
5335
|
+
sku: params.sku,
|
|
5336
|
+
primaryLocale: params.primaryLocale ?? "en-US"
|
|
5337
|
+
},
|
|
5338
|
+
relationships: {
|
|
5339
|
+
bundleId: {
|
|
5340
|
+
data: { type: "bundleIds", id: params.bundleIdId }
|
|
5341
|
+
}
|
|
5342
|
+
}
|
|
5343
|
+
}
|
|
5344
|
+
});
|
|
5345
|
+
return response.data;
|
|
5346
|
+
}
|
|
4912
5347
|
};
|
|
4913
5348
|
}
|
|
4914
5349
|
});
|
|
@@ -6061,9 +6496,22 @@ var init_pre_release_versions = __esm({
|
|
|
6061
6496
|
});
|
|
6062
6497
|
|
|
6063
6498
|
// src/resources/review-submission-items.ts
|
|
6064
|
-
var ReviewSubmissionItemsResource;
|
|
6499
|
+
var ITEM_RELATIONSHIPS, ReviewSubmissionItemsResource;
|
|
6065
6500
|
var init_review_submission_items = __esm({
|
|
6066
6501
|
"src/resources/review-submission-items.ts"() {
|
|
6502
|
+
ITEM_RELATIONSHIPS = {
|
|
6503
|
+
appStoreVersionId: ["appStoreVersion", "appStoreVersions"],
|
|
6504
|
+
appCustomProductPageVersionId: ["appCustomProductPageVersion", "appCustomProductPageVersions"],
|
|
6505
|
+
appStoreVersionExperimentId: ["appStoreVersionExperiment", "appStoreVersionExperiments"],
|
|
6506
|
+
appStoreVersionExperimentV2Id: ["appStoreVersionExperimentV2", "appStoreVersionExperiments"],
|
|
6507
|
+
appEventId: ["appEvent", "appEvents"],
|
|
6508
|
+
appAssetLibraryImageId: ["appAssetLibraryImage", "appAssetLibraryImages"],
|
|
6509
|
+
appAssetLibraryVideoId: ["appAssetLibraryVideo", "appAssetLibraryVideos"],
|
|
6510
|
+
backgroundAssetVersionId: ["backgroundAssetVersion", "backgroundAssetVersions"],
|
|
6511
|
+
inAppPurchaseVersionId: ["inAppPurchaseVersion", "inAppPurchaseVersions"],
|
|
6512
|
+
subscriptionVersionId: ["subscriptionVersion", "subscriptionVersions"],
|
|
6513
|
+
subscriptionGroupVersionId: ["subscriptionGroupVersion", "subscriptionGroupVersions"]
|
|
6514
|
+
};
|
|
6067
6515
|
ReviewSubmissionItemsResource = class {
|
|
6068
6516
|
constructor(http) {
|
|
6069
6517
|
this.http = http;
|
|
@@ -6085,32 +6533,20 @@ var init_review_submission_items = __esm({
|
|
|
6085
6533
|
);
|
|
6086
6534
|
return response.data;
|
|
6087
6535
|
}
|
|
6536
|
+
/**
|
|
6537
|
+
* Add one item to a review submission. Besides versions, asset library
|
|
6538
|
+
* images and videos can be submitted on their own (ASC API 4.5.1) so
|
|
6539
|
+
* creative assets are approved ahead of a campaign.
|
|
6540
|
+
*/
|
|
6088
6541
|
async create(params) {
|
|
6089
6542
|
const relationships = {
|
|
6090
6543
|
reviewSubmission: {
|
|
6091
6544
|
data: { type: "reviewSubmissions", id: params.reviewSubmissionId }
|
|
6092
6545
|
}
|
|
6093
6546
|
};
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
};
|
|
6098
|
-
}
|
|
6099
|
-
if (params.appCustomProductPageVersionId) {
|
|
6100
|
-
relationships.appCustomProductPageVersion = {
|
|
6101
|
-
data: {
|
|
6102
|
-
type: "appCustomProductPageVersions",
|
|
6103
|
-
id: params.appCustomProductPageVersionId
|
|
6104
|
-
}
|
|
6105
|
-
};
|
|
6106
|
-
}
|
|
6107
|
-
if (params.appStoreVersionExperimentId) {
|
|
6108
|
-
relationships.appStoreVersionExperiment = {
|
|
6109
|
-
data: {
|
|
6110
|
-
type: "appStoreVersionExperiments",
|
|
6111
|
-
id: params.appStoreVersionExperimentId
|
|
6112
|
-
}
|
|
6113
|
-
};
|
|
6547
|
+
for (const [param, [relationship, type]] of Object.entries(ITEM_RELATIONSHIPS)) {
|
|
6548
|
+
const id = params[param];
|
|
6549
|
+
if (id) relationships[relationship] = { data: { type, id } };
|
|
6114
6550
|
}
|
|
6115
6551
|
const response = await this.http.post(
|
|
6116
6552
|
"/v1/reviewSubmissionItems",
|
|
@@ -6341,6 +6777,204 @@ var init_xcode_metrics = __esm({
|
|
|
6341
6777
|
}
|
|
6342
6778
|
});
|
|
6343
6779
|
|
|
6780
|
+
// src/resources/app-asset-libraries.ts
|
|
6781
|
+
function mergeRefData(parts) {
|
|
6782
|
+
const merged = {
|
|
6783
|
+
features: [],
|
|
6784
|
+
placementProfileGroups: [],
|
|
6785
|
+
imageSpecs: [],
|
|
6786
|
+
videoSpecs: [],
|
|
6787
|
+
placementTypes: [],
|
|
6788
|
+
displayClasses: []
|
|
6789
|
+
};
|
|
6790
|
+
for (const part of parts) {
|
|
6791
|
+
for (const key of Object.keys(merged)) {
|
|
6792
|
+
merged[key].push(...part[key] ?? []);
|
|
6793
|
+
}
|
|
6794
|
+
}
|
|
6795
|
+
return merged;
|
|
6796
|
+
}
|
|
6797
|
+
var AppAssetLibrariesResource;
|
|
6798
|
+
var init_app_asset_libraries = __esm({
|
|
6799
|
+
"src/resources/app-asset-libraries.ts"() {
|
|
6800
|
+
AppAssetLibrariesResource = class {
|
|
6801
|
+
constructor(http) {
|
|
6802
|
+
this.http = http;
|
|
6803
|
+
}
|
|
6804
|
+
http;
|
|
6805
|
+
async getForApp(appId) {
|
|
6806
|
+
const response = await this.http.get(
|
|
6807
|
+
`/v1/apps/${appId}/assetLibrary`
|
|
6808
|
+
);
|
|
6809
|
+
return response.data;
|
|
6810
|
+
}
|
|
6811
|
+
async listImages(libraryId, params = {}) {
|
|
6812
|
+
return this.http.all(`/v1/appAssetLibraries/${libraryId}/images`, params);
|
|
6813
|
+
}
|
|
6814
|
+
async listVideos(libraryId, params = {}) {
|
|
6815
|
+
return this.http.all(`/v1/appAssetLibraries/${libraryId}/videos`, params);
|
|
6816
|
+
}
|
|
6817
|
+
/**
|
|
6818
|
+
* Specs, placement groups and limits. Apple adds placement groups (new
|
|
6819
|
+
* devices) without an API version bump, so read them from here rather than
|
|
6820
|
+
* hard-coding display types.
|
|
6821
|
+
*/
|
|
6822
|
+
async refData(params = {}) {
|
|
6823
|
+
const filter = {};
|
|
6824
|
+
if (params.features?.length) filter.features = params.features;
|
|
6825
|
+
if (params.placementTypes?.length) filter.placementTypes = params.placementTypes;
|
|
6826
|
+
if (params.placementProfileGroups?.length) filter.placementProfileGroups = params.placementProfileGroups;
|
|
6827
|
+
const data = await this.http.all("/v1/appAssetLibraryRefData", { filter });
|
|
6828
|
+
return mergeRefData(data.map((d) => d.attributes));
|
|
6829
|
+
}
|
|
6830
|
+
};
|
|
6831
|
+
}
|
|
6832
|
+
});
|
|
6833
|
+
|
|
6834
|
+
// src/resources/app-asset-library-assets.ts
|
|
6835
|
+
var AssetLibraryMediaResource, AppAssetLibraryImagesResource, AppAssetLibraryVideosResource;
|
|
6836
|
+
var init_app_asset_library_assets = __esm({
|
|
6837
|
+
"src/resources/app-asset-library-assets.ts"() {
|
|
6838
|
+
AssetLibraryMediaResource = class {
|
|
6839
|
+
constructor(http) {
|
|
6840
|
+
this.http = http;
|
|
6841
|
+
}
|
|
6842
|
+
http;
|
|
6843
|
+
async get(id) {
|
|
6844
|
+
const response = await this.http.get(`/v1/${this.type}/${id}`);
|
|
6845
|
+
return response.data;
|
|
6846
|
+
}
|
|
6847
|
+
async reserve(params, extra = {}) {
|
|
6848
|
+
const { libraryId, ...attributes } = params;
|
|
6849
|
+
const response = await this.http.post(`/v1/${this.type}`, {
|
|
6850
|
+
data: {
|
|
6851
|
+
type: this.type,
|
|
6852
|
+
attributes: { ...attributes, ...extra },
|
|
6853
|
+
relationships: {
|
|
6854
|
+
assetLibrary: { data: { type: "appAssetLibraries", id: libraryId } }
|
|
6855
|
+
}
|
|
6856
|
+
}
|
|
6857
|
+
});
|
|
6858
|
+
return response.data;
|
|
6859
|
+
}
|
|
6860
|
+
async patch(id, attributes) {
|
|
6861
|
+
const response = await this.http.patch(`/v1/${this.type}/${id}`, {
|
|
6862
|
+
data: { type: this.type, id, attributes }
|
|
6863
|
+
});
|
|
6864
|
+
return response.data;
|
|
6865
|
+
}
|
|
6866
|
+
/** Fails with 409 STATE_ERROR.ASSET_HAS_PLACEMENTS while the asset is still placed. */
|
|
6867
|
+
async delete(id) {
|
|
6868
|
+
await this.http.delete(`/v1/${this.type}/${id}`);
|
|
6869
|
+
}
|
|
6870
|
+
async listPlacements(id, params = {}) {
|
|
6871
|
+
return this.http.all(`/v1/${this.type}/${id}/placements`, params);
|
|
6872
|
+
}
|
|
6873
|
+
};
|
|
6874
|
+
AppAssetLibraryImagesResource = class extends AssetLibraryMediaResource {
|
|
6875
|
+
type = "appAssetLibraryImages";
|
|
6876
|
+
async create(params) {
|
|
6877
|
+
return this.reserve(params);
|
|
6878
|
+
}
|
|
6879
|
+
async update(id, attributes) {
|
|
6880
|
+
return this.patch(id, { ...attributes });
|
|
6881
|
+
}
|
|
6882
|
+
};
|
|
6883
|
+
AppAssetLibraryVideosResource = class extends AssetLibraryMediaResource {
|
|
6884
|
+
type = "appAssetLibraryVideos";
|
|
6885
|
+
async create(params) {
|
|
6886
|
+
const { previewFrameTimeCode, ...rest } = params;
|
|
6887
|
+
return this.reserve(rest, previewFrameTimeCode ? { previewFrameTimeCode } : {});
|
|
6888
|
+
}
|
|
6889
|
+
async update(id, attributes) {
|
|
6890
|
+
return this.patch(id, { ...attributes });
|
|
6891
|
+
}
|
|
6892
|
+
};
|
|
6893
|
+
}
|
|
6894
|
+
});
|
|
6895
|
+
|
|
6896
|
+
// src/resources/app-asset-library-placements.ts
|
|
6897
|
+
function targetRelationship(target) {
|
|
6898
|
+
return { [target.kind]: { data: { type: TARGET_TYPES[target.kind], id: target.id } } };
|
|
6899
|
+
}
|
|
6900
|
+
var TARGET_TYPES, AppAssetLibraryPlacementsResource;
|
|
6901
|
+
var init_app_asset_library_placements = __esm({
|
|
6902
|
+
"src/resources/app-asset-library-placements.ts"() {
|
|
6903
|
+
TARGET_TYPES = {
|
|
6904
|
+
appStoreVersionLocalization: "appStoreVersionLocalizations",
|
|
6905
|
+
appCustomProductPageLocalization: "appCustomProductPageLocalizations",
|
|
6906
|
+
appEventLocalization: "appEventLocalizations",
|
|
6907
|
+
appStoreVersionExperimentTreatmentLocalization: "appStoreVersionExperimentTreatmentLocalizations"
|
|
6908
|
+
};
|
|
6909
|
+
AppAssetLibraryPlacementsResource = class {
|
|
6910
|
+
constructor(http) {
|
|
6911
|
+
this.http = http;
|
|
6912
|
+
}
|
|
6913
|
+
http;
|
|
6914
|
+
/**
|
|
6915
|
+
* Place an image or video on one localization. Placements cannot be edited —
|
|
6916
|
+
* delete and recreate. The parent version/page must be editable.
|
|
6917
|
+
*/
|
|
6918
|
+
async create(params) {
|
|
6919
|
+
if (!params.imageId === !params.videoId) {
|
|
6920
|
+
throw new Error("A placement needs exactly one of imageId or videoId");
|
|
6921
|
+
}
|
|
6922
|
+
const media = params.imageId ? { image: { data: { type: "appAssetLibraryImages", id: params.imageId } } } : { video: { data: { type: "appAssetLibraryVideos", id: params.videoId } } };
|
|
6923
|
+
const response = await this.http.post(
|
|
6924
|
+
"/v1/appAssetLibraryPlacements",
|
|
6925
|
+
{
|
|
6926
|
+
data: {
|
|
6927
|
+
type: "appAssetLibraryPlacements",
|
|
6928
|
+
attributes: { placementType: params.placementType, placementGroup: params.placementGroup },
|
|
6929
|
+
relationships: { ...media, ...targetRelationship(params.target) }
|
|
6930
|
+
}
|
|
6931
|
+
}
|
|
6932
|
+
);
|
|
6933
|
+
return response.data;
|
|
6934
|
+
}
|
|
6935
|
+
async get(id, params = {}) {
|
|
6936
|
+
const response = await this.http.get(
|
|
6937
|
+
`/v1/appAssetLibraryPlacements/${id}`,
|
|
6938
|
+
params.include ? { include: params.include } : void 0
|
|
6939
|
+
);
|
|
6940
|
+
return response.data;
|
|
6941
|
+
}
|
|
6942
|
+
async delete(id) {
|
|
6943
|
+
await this.http.delete(`/v1/appAssetLibraryPlacements/${id}`);
|
|
6944
|
+
}
|
|
6945
|
+
/** Placements on one localization, in display order by default. */
|
|
6946
|
+
async listForTarget(target, params = {}) {
|
|
6947
|
+
const filter = {};
|
|
6948
|
+
if (params.placementType) filter.placementType = params.placementType;
|
|
6949
|
+
if (params.placementGroup) filter.placementGroup = params.placementGroup;
|
|
6950
|
+
const listParams = { filter, include: params.include, sort: "placementGroupPosition" };
|
|
6951
|
+
return this.http.all(
|
|
6952
|
+
`/v1/${TARGET_TYPES[target.kind]}/${target.id}/placements`,
|
|
6953
|
+
listParams
|
|
6954
|
+
);
|
|
6955
|
+
}
|
|
6956
|
+
/** Set the display order of a placement group. In-app event localizations cannot be ordered. */
|
|
6957
|
+
async order(params) {
|
|
6958
|
+
if (params.target.kind === "appEventLocalization") {
|
|
6959
|
+
throw new Error("In-app event placements cannot be ordered");
|
|
6960
|
+
}
|
|
6961
|
+
await this.http.post("/v1/appAssetLibraryPlacementOrderingRequests", {
|
|
6962
|
+
data: {
|
|
6963
|
+
type: "appAssetLibraryPlacementOrderingRequests",
|
|
6964
|
+
attributes: { placementGroup: params.placementGroup },
|
|
6965
|
+
relationships: {
|
|
6966
|
+
orderedPlacements: {
|
|
6967
|
+
data: params.placementIds.map((id) => ({ type: "appAssetLibraryPlacements", id }))
|
|
6968
|
+
},
|
|
6969
|
+
...targetRelationship(params.target)
|
|
6970
|
+
}
|
|
6971
|
+
}
|
|
6972
|
+
});
|
|
6973
|
+
}
|
|
6974
|
+
};
|
|
6975
|
+
}
|
|
6976
|
+
});
|
|
6977
|
+
|
|
6344
6978
|
// src/client/portal.ts
|
|
6345
6979
|
function pickUniqueKeyName(base, taken) {
|
|
6346
6980
|
if (!taken.has(base)) return base;
|
|
@@ -6680,7 +7314,10 @@ var init_portal = __esm({
|
|
|
6680
7314
|
}
|
|
6681
7315
|
// ── Teams ──────────────────────────────────────────────────────────────
|
|
6682
7316
|
async listTeams() {
|
|
6683
|
-
const data = await this.post("account/listTeams.action", {
|
|
7317
|
+
const data = await this.post("account/listTeams.action", {
|
|
7318
|
+
pageSize: "500",
|
|
7319
|
+
pageNumber: "1"
|
|
7320
|
+
});
|
|
6684
7321
|
return data.teams ?? [];
|
|
6685
7322
|
}
|
|
6686
7323
|
// ── App IDs / Bundle IDs ───────────────────────────────────────────────
|
|
@@ -6694,11 +7331,12 @@ var init_portal = __esm({
|
|
|
6694
7331
|
return data.appIds ?? [];
|
|
6695
7332
|
}
|
|
6696
7333
|
async createAppId(params) {
|
|
7334
|
+
const safeName = params.name.replace(/[^0-9A-Za-z\s]/g, " ").replace(/\s+/g, " ").trim() || "App";
|
|
6697
7335
|
const reqParams = {
|
|
6698
7336
|
teamId: this.teamId,
|
|
6699
7337
|
type: "explicit",
|
|
6700
7338
|
identifier: params.identifier,
|
|
6701
|
-
name:
|
|
7339
|
+
name: safeName
|
|
6702
7340
|
};
|
|
6703
7341
|
if (params.enablePush) reqParams.push = "on";
|
|
6704
7342
|
if (params.enableIAP !== false) reqParams.inAppPurchase = "on";
|
|
@@ -6733,6 +7371,191 @@ var init_portal = __esm({
|
|
|
6733
7371
|
featureValue: params.enabled ? "on" : "off"
|
|
6734
7372
|
});
|
|
6735
7373
|
}
|
|
7374
|
+
// ── Application Groups ──────────────────────────────────────────────────
|
|
7375
|
+
//
|
|
7376
|
+
// App Groups (e.g. `group.com.playspina.com`) are the only clean way to
|
|
7377
|
+
// share data — an auth token, in our case — between a host app and an
|
|
7378
|
+
// embedded extension (Messages / Notification Service / etc.). App Store
|
|
7379
|
+
// Connect API keys (.p8) genuinely cannot manage them; Apple only exposes
|
|
7380
|
+
// group create/associate through this legacy session-cookie portal API,
|
|
7381
|
+
// which is why they live on PortalClient (Apple ID session) and not on the
|
|
7382
|
+
// ASC REST client. Endpoint shapes ported 1:1 from Spaceship's
|
|
7383
|
+
// portal_client.rb (listApplicationGroups / addApplicationGroup /
|
|
7384
|
+
// assignApplicationGroupToAppId).
|
|
7385
|
+
/** Apple's internal feature code for the App Groups capability. */
|
|
7386
|
+
static APP_GROUPS_FEATURE = "APG3427HIY";
|
|
7387
|
+
/** List all app groups in the team. */
|
|
7388
|
+
async listAppGroups() {
|
|
7389
|
+
const data = await this.post("account/ios/identifiers/listApplicationGroups.action", {
|
|
7390
|
+
teamId: this.teamId,
|
|
7391
|
+
pageNumber: "1",
|
|
7392
|
+
pageSize: "500",
|
|
7393
|
+
sort: "name=asc"
|
|
7394
|
+
});
|
|
7395
|
+
return data.applicationGroupList ?? [];
|
|
7396
|
+
}
|
|
7397
|
+
/**
|
|
7398
|
+
* Create an app group, returning the raw group record. Idempotent —
|
|
7399
|
+
* Apple rejects a duplicate group identifier ("Multiple App Groups may
|
|
7400
|
+
* not have the same identifier"), so we list first and reuse an existing
|
|
7401
|
+
* group when the identifier already exists in the team.
|
|
7402
|
+
*
|
|
7403
|
+
* Note the two ids on the returned record: `identifier` is the
|
|
7404
|
+
* `group.com.…` string you type, while `applicationGroup` is Apple's
|
|
7405
|
+
* opaque internal handle that association actually requires — do NOT pass
|
|
7406
|
+
* the human identifier to assignAppGroups.
|
|
7407
|
+
*/
|
|
7408
|
+
async createAppGroup(params) {
|
|
7409
|
+
const existing = await this.listAppGroups();
|
|
7410
|
+
const hit = existing.find((g) => g.identifier === params.identifier);
|
|
7411
|
+
if (hit) return hit;
|
|
7412
|
+
const safeName = params.name.replace(/[^0-9A-Za-z\s]/g, " ").replace(/\s+/g, " ").trim() || "App Group";
|
|
7413
|
+
const data = await this.post("account/ios/identifiers/addApplicationGroup.action", {
|
|
7414
|
+
teamId: this.teamId,
|
|
7415
|
+
identifier: params.identifier,
|
|
7416
|
+
name: safeName
|
|
7417
|
+
});
|
|
7418
|
+
return data.applicationGroup;
|
|
7419
|
+
}
|
|
7420
|
+
/**
|
|
7421
|
+
* Enable the App Groups capability on an App ID. Required before an
|
|
7422
|
+
* App ID can have a group associated with it — mirrors Spaceship
|
|
7423
|
+
* enabling `AppService::AppGroup` before `associate_groups`.
|
|
7424
|
+
*/
|
|
7425
|
+
async enableAppGroups(appIdId) {
|
|
7426
|
+
await this.post("account/ios/identifiers/updateService.action", {
|
|
7427
|
+
teamId: this.teamId,
|
|
7428
|
+
displayId: appIdId,
|
|
7429
|
+
featureType: _PortalClient.APP_GROUPS_FEATURE,
|
|
7430
|
+
featureValue: "true"
|
|
7431
|
+
});
|
|
7432
|
+
}
|
|
7433
|
+
/**
|
|
7434
|
+
* Associate one or more app groups with an App ID. `appGroupIds` are the
|
|
7435
|
+
* internal ids (Spaceship's `app_group_id`), NOT the `group.com.…`
|
|
7436
|
+
* identifiers. Apple wants both `appIdId` and `displayId` set to the
|
|
7437
|
+
* App ID's internal id, per Spaceship's associate_groups_with_app.
|
|
7438
|
+
*/
|
|
7439
|
+
async assignAppGroups(params) {
|
|
7440
|
+
await this.post("account/ios/identifiers/assignApplicationGroupToAppId.action", {
|
|
7441
|
+
teamId: this.teamId,
|
|
7442
|
+
appIdId: params.appIdId,
|
|
7443
|
+
displayId: params.appIdId,
|
|
7444
|
+
applicationGroups: params.appGroupIds
|
|
7445
|
+
});
|
|
7446
|
+
}
|
|
7447
|
+
// ── Apple Pay merchants (OMCs) ─────────────────────────────────────────
|
|
7448
|
+
//
|
|
7449
|
+
// Merchant IDs are team-level identifiers (merchant.com.…) that an App ID's
|
|
7450
|
+
// Apple Pay capability must be LINKED to — enabling the capability alone
|
|
7451
|
+
// produces profiles that fail with "doesn't support the … Merchant ID".
|
|
7452
|
+
// Like App Groups, merchants are only manageable through the legacy
|
|
7453
|
+
// session-cookie portal API. Endpoint shapes ported 1:1 from Spaceship's
|
|
7454
|
+
// portal_client.rb (merchants / create_merchant! /
|
|
7455
|
+
// associate_merchants_with_app).
|
|
7456
|
+
/** Apple's internal feature code for the Apple Pay capability. */
|
|
7457
|
+
static APPLE_PAY_FEATURE = "OM633U5T5G";
|
|
7458
|
+
/**
|
|
7459
|
+
* List all Apple Pay merchant IDs in the team. Each record maps
|
|
7460
|
+
* (per Spaceship's merchant.rb attr_mapping): `omcId` = internal id,
|
|
7461
|
+
* `identifier` = the human `merchant.com.…` string, `name` = display name.
|
|
7462
|
+
*/
|
|
7463
|
+
async listMerchants() {
|
|
7464
|
+
const data = await this.post("account/ios/identifiers/listOMCs.action", {
|
|
7465
|
+
teamId: this.teamId,
|
|
7466
|
+
pageNumber: "1",
|
|
7467
|
+
pageSize: "500",
|
|
7468
|
+
sort: "name=asc"
|
|
7469
|
+
});
|
|
7470
|
+
return data.identifierList ?? [];
|
|
7471
|
+
}
|
|
7472
|
+
/**
|
|
7473
|
+
* Register (or reuse) a merchant ID, returning its INTERNAL id (`omcId`) —
|
|
7474
|
+
* Apple's opaque handle that association requires, NOT the human
|
|
7475
|
+
* `merchant.com.…` identifier. Idempotent: lists first and reuses an
|
|
7476
|
+
* existing merchant with the same identifier. Endpoint + params ported 1:1
|
|
7477
|
+
* from Spaceship's create_merchant! (addOMC.action).
|
|
7478
|
+
*/
|
|
7479
|
+
async createMerchant(params) {
|
|
7480
|
+
const existing = await this.listMerchants();
|
|
7481
|
+
const hit = existing.find((m) => m.identifier === params.identifier);
|
|
7482
|
+
if (hit?.omcId) return String(hit.omcId);
|
|
7483
|
+
const safeName = params.name.replace(/[^0-9A-Za-z\s]/g, " ").replace(/\s+/g, " ").trim() || "Merchant";
|
|
7484
|
+
const data = await this.post("account/ios/identifiers/addOMC.action", {
|
|
7485
|
+
name: safeName,
|
|
7486
|
+
identifier: params.identifier,
|
|
7487
|
+
teamId: this.teamId
|
|
7488
|
+
});
|
|
7489
|
+
const rec = data.omcId ?? data;
|
|
7490
|
+
const id = typeof rec === "string" ? rec : rec?.omcId ?? rec?.merchantId;
|
|
7491
|
+
if (!id) {
|
|
7492
|
+
throw new Error("Apple returned no internal merchant id (omcId) for the registered merchant.");
|
|
7493
|
+
}
|
|
7494
|
+
return String(id);
|
|
7495
|
+
}
|
|
7496
|
+
/**
|
|
7497
|
+
* Enable the Apple Pay capability on an App ID. Apple Pay is a standard
|
|
7498
|
+
* AppService (service_id OM633U5T5G) whose service_uri is the default
|
|
7499
|
+
* `updateService.action` — confirmed in Spaceship's app_service.rb.
|
|
7500
|
+
*/
|
|
7501
|
+
async enableApplePay(appIdId) {
|
|
7502
|
+
await this.post("account/ios/identifiers/updateService.action", {
|
|
7503
|
+
teamId: this.teamId,
|
|
7504
|
+
displayId: appIdId,
|
|
7505
|
+
featureType: _PortalClient.APPLE_PAY_FEATURE,
|
|
7506
|
+
featureValue: "true"
|
|
7507
|
+
});
|
|
7508
|
+
}
|
|
7509
|
+
/**
|
|
7510
|
+
* Associate merchant IDs with an App ID's Apple Pay capability. `omcIds`
|
|
7511
|
+
* are the internal ids (each merchant record's `omcId`), NOT the
|
|
7512
|
+
* `merchant.com.…` identifiers. Endpoint + params ported 1:1 from
|
|
7513
|
+
* Spaceship's associate_merchants_with_app (assignOMCToAppId.action — note
|
|
7514
|
+
* the singular "OMC", and NO displayId param).
|
|
7515
|
+
*/
|
|
7516
|
+
async assignMerchants(params) {
|
|
7517
|
+
await this.post("account/ios/identifiers/assignOMCToAppId.action", {
|
|
7518
|
+
teamId: this.teamId,
|
|
7519
|
+
appIdId: params.appIdId,
|
|
7520
|
+
omcIds: params.omcIds
|
|
7521
|
+
});
|
|
7522
|
+
}
|
|
7523
|
+
/**
|
|
7524
|
+
* Create an Apple Pay **Payment Processing Certificate** on a merchant ID
|
|
7525
|
+
* from a CSR — the certificate a PSP (e.g. Stripe) hands you as a CSR, which
|
|
7526
|
+
* must be uploaded to the Merchant ID in Apple Developer, then the resulting
|
|
7527
|
+
* `.cer` downloaded and given back to the PSP so it can decrypt payment
|
|
7528
|
+
* tokens. This is the one manual Apple-portal step in wiring up Apple Pay.
|
|
7529
|
+
*
|
|
7530
|
+
* Ported 1:1 from Spaceship's create_certificate! (submitCertificateRequest
|
|
7531
|
+
* .action): the merchant's INTERNAL id (`omcId`) is passed as BOTH `appIdId`
|
|
7532
|
+
* and `specialIdentifierDisplayId` — the latter is how the portal scopes a
|
|
7533
|
+
* cert to a merchant/special identifier rather than an App ID. The cert type
|
|
7534
|
+
* is APPLE_PAY (`4APLUP237T`). Returns the created certRequest (its
|
|
7535
|
+
* `certificateId` feeds `downloadApplePayCertificate`).
|
|
7536
|
+
*
|
|
7537
|
+
* @param omcId the merchant's internal id (from createMerchant/listMerchants)
|
|
7538
|
+
* @param csrContent the PEM CSR string (Stripe's "Apple Pay CSR")
|
|
7539
|
+
*/
|
|
7540
|
+
async createApplePayCertificate(params) {
|
|
7541
|
+
const data = await this.post("account/ios/certificate/submitCertificateRequest.action", {
|
|
7542
|
+
teamId: this.teamId,
|
|
7543
|
+
type: _PortalClient.CERT_TYPES.APPLE_PAY,
|
|
7544
|
+
csrContent: params.csrContent,
|
|
7545
|
+
appIdId: params.omcId,
|
|
7546
|
+
specialIdentifierDisplayId: params.omcId
|
|
7547
|
+
});
|
|
7548
|
+
return data.certRequest;
|
|
7549
|
+
}
|
|
7550
|
+
/**
|
|
7551
|
+
* Download an Apple Pay Payment Processing Certificate's raw bytes by its
|
|
7552
|
+
* certificate id (from `createApplePayCertificate`). Thin wrapper over
|
|
7553
|
+
* `downloadCertificate` pinned to the APPLE_PAY type — the DER `.cer` you
|
|
7554
|
+
* upload back into Stripe.
|
|
7555
|
+
*/
|
|
7556
|
+
async downloadApplePayCertificate(certificateId) {
|
|
7557
|
+
return this.downloadCertificate(certificateId, _PortalClient.CERT_TYPES.APPLE_PAY);
|
|
7558
|
+
}
|
|
6736
7559
|
// ── Certificates ───────────────────────────────────────────────────────
|
|
6737
7560
|
async listCertificates(types) {
|
|
6738
7561
|
const iosTypes = types ?? [
|
|
@@ -7272,6 +8095,9 @@ var init_client = __esm({
|
|
|
7272
8095
|
init_in_app_purchases();
|
|
7273
8096
|
init_game_center_detail();
|
|
7274
8097
|
init_xcode_metrics();
|
|
8098
|
+
init_app_asset_libraries();
|
|
8099
|
+
init_app_asset_library_assets();
|
|
8100
|
+
init_app_asset_library_placements();
|
|
7275
8101
|
init_auth();
|
|
7276
8102
|
init_request();
|
|
7277
8103
|
init_apple_session();
|
|
@@ -7313,6 +8139,10 @@ var init_client = __esm({
|
|
|
7313
8139
|
inAppPurchases;
|
|
7314
8140
|
gameCenterDetail;
|
|
7315
8141
|
xcodeMetrics;
|
|
8142
|
+
appAssetLibraries;
|
|
8143
|
+
appAssetLibraryImages;
|
|
8144
|
+
appAssetLibraryVideos;
|
|
8145
|
+
appAssetLibraryPlacements;
|
|
7316
8146
|
constructor(config) {
|
|
7317
8147
|
const tokenManager = new TokenManager({
|
|
7318
8148
|
keyId: config.keyId,
|
|
@@ -7356,6 +8186,10 @@ var init_client = __esm({
|
|
|
7356
8186
|
this.inAppPurchases = new InAppPurchasesResource(this.http);
|
|
7357
8187
|
this.gameCenterDetail = new GameCenterDetailResource(this.http);
|
|
7358
8188
|
this.xcodeMetrics = new XcodeMetricsResource(this.http);
|
|
8189
|
+
this.appAssetLibraries = new AppAssetLibrariesResource(this.http);
|
|
8190
|
+
this.appAssetLibraryImages = new AppAssetLibraryImagesResource(this.http);
|
|
8191
|
+
this.appAssetLibraryVideos = new AppAssetLibraryVideosResource(this.http);
|
|
8192
|
+
this.appAssetLibraryPlacements = new AppAssetLibraryPlacementsResource(this.http);
|
|
7359
8193
|
}
|
|
7360
8194
|
async request(path, options) {
|
|
7361
8195
|
return this.http.request(path, options);
|
|
@@ -8123,8 +8957,8 @@ var init_utilities = __esm({
|
|
|
8123
8957
|
description: "Ask the user for input",
|
|
8124
8958
|
async run(params) {
|
|
8125
8959
|
const text = params.text ?? "Enter a value:";
|
|
8126
|
-
const { createInterface:
|
|
8127
|
-
const rl =
|
|
8960
|
+
const { createInterface: createInterface4 } = await import('readline');
|
|
8961
|
+
const rl = createInterface4({ input: process.stdin, output: process.stdout });
|
|
8128
8962
|
return new Promise((resolve4) => {
|
|
8129
8963
|
rl.question(text + " ", (answer) => {
|
|
8130
8964
|
rl.close();
|
|
@@ -8516,6 +9350,308 @@ var init_options = __esm({
|
|
|
8516
9350
|
init_client();
|
|
8517
9351
|
}
|
|
8518
9352
|
});
|
|
9353
|
+
|
|
9354
|
+
// src/privacy/index.ts
|
|
9355
|
+
var privacy_exports = {};
|
|
9356
|
+
__export(privacy_exports, {
|
|
9357
|
+
DATA_PROTECTIONS: () => DATA_PROTECTIONS,
|
|
9358
|
+
DATA_USAGE_CATEGORIES: () => DATA_USAGE_CATEGORIES,
|
|
9359
|
+
DATA_USAGE_PURPOSES: () => DATA_USAGE_PURPOSES,
|
|
9360
|
+
createAppDataUsage: () => createAppDataUsage,
|
|
9361
|
+
deleteAppDataUsage: () => deleteAppDataUsage,
|
|
9362
|
+
getAppDataUsagesPublishState: () => getAppDataUsagesPublishState,
|
|
9363
|
+
listAppDataUsages: () => listAppDataUsages,
|
|
9364
|
+
publishAppDataUsages: () => publishAppDataUsages,
|
|
9365
|
+
selectProviderForApp: () => selectProviderForApp,
|
|
9366
|
+
uploadAppPrivacyDetails: () => uploadAppPrivacyDetails,
|
|
9367
|
+
validateUsages: () => validateUsages
|
|
9368
|
+
});
|
|
9369
|
+
async function iris(session, method, path, body) {
|
|
9370
|
+
const res = await fetch(path.startsWith("http") ? path : `${IRIS_BASE2}${path}`, {
|
|
9371
|
+
method,
|
|
9372
|
+
headers: {
|
|
9373
|
+
Cookie: cookieHeader(session),
|
|
9374
|
+
Accept: "application/vnd.api+json, application/json",
|
|
9375
|
+
"Content-Type": "application/vnd.api+json",
|
|
9376
|
+
"X-Requested-With": "XMLHttpRequest"
|
|
9377
|
+
},
|
|
9378
|
+
body: body ? JSON.stringify(body) : void 0
|
|
9379
|
+
});
|
|
9380
|
+
absorbCookies(session, res);
|
|
9381
|
+
const text = await res.text();
|
|
9382
|
+
let parsed = null;
|
|
9383
|
+
try {
|
|
9384
|
+
parsed = text ? JSON.parse(text) : null;
|
|
9385
|
+
} catch {
|
|
9386
|
+
}
|
|
9387
|
+
return { status: res.status, body: parsed, text };
|
|
9388
|
+
}
|
|
9389
|
+
function cookieHeader(session) {
|
|
9390
|
+
return Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join("; ");
|
|
9391
|
+
}
|
|
9392
|
+
function absorbCookies(session, res) {
|
|
9393
|
+
for (const raw of res.headers.getSetCookie?.() ?? []) {
|
|
9394
|
+
const m = raw.match(/^([^=]+)=([^;]*)/);
|
|
9395
|
+
if (m) session.cookies[m[1]] = m[2];
|
|
9396
|
+
}
|
|
9397
|
+
}
|
|
9398
|
+
function fail(what, r) {
|
|
9399
|
+
const detail = r.body?.errors?.map((e) => e.detail ?? e.title).join("; ") || r.text.slice(0, 300);
|
|
9400
|
+
throw new Error(`${what} failed (HTTP ${r.status}): ${detail}`);
|
|
9401
|
+
}
|
|
9402
|
+
async function selectProviderForApp(session, appId, onLog) {
|
|
9403
|
+
const owns = async () => (await iris(session, "GET", `/apps/${appId}?fields[apps]=name`)).status === 200;
|
|
9404
|
+
if (await owns()) return;
|
|
9405
|
+
const res = await fetch(OLYMPUS_SESSION2, { headers: { Cookie: cookieHeader(session), Accept: "application/json" } });
|
|
9406
|
+
absorbCookies(session, res);
|
|
9407
|
+
const info = await res.json().catch(() => null);
|
|
9408
|
+
const providers = info?.availableProviders ?? [];
|
|
9409
|
+
for (const p of providers) {
|
|
9410
|
+
const sw = await fetch(OLYMPUS_SESSION2, {
|
|
9411
|
+
method: "POST",
|
|
9412
|
+
headers: { Cookie: cookieHeader(session), "Content-Type": "application/json", Accept: "application/json" },
|
|
9413
|
+
body: JSON.stringify({ provider: { providerId: p.providerId } })
|
|
9414
|
+
});
|
|
9415
|
+
absorbCookies(session, sw);
|
|
9416
|
+
if (sw.ok && await owns()) {
|
|
9417
|
+
onLog?.(`Switched to team "${p.name}"`);
|
|
9418
|
+
session.teamId = String(p.providerId);
|
|
9419
|
+
return;
|
|
9420
|
+
}
|
|
9421
|
+
}
|
|
9422
|
+
throw new Error(`None of this Apple ID's ${providers.length} teams can see app ${appId}.`);
|
|
9423
|
+
}
|
|
9424
|
+
async function listAppDataUsages(session, appId) {
|
|
9425
|
+
const all = [];
|
|
9426
|
+
let next = `/apps/${appId}/dataUsages?include=category,grouping,purpose,dataProtection&limit=500`;
|
|
9427
|
+
while (next) {
|
|
9428
|
+
const r = await iris(session, "GET", next);
|
|
9429
|
+
if (r.status !== 200) fail("List app data usages", r);
|
|
9430
|
+
all.push(...(r.body?.data ?? []).map((d) => ({ id: d.id })));
|
|
9431
|
+
next = r.body?.links?.next ?? null;
|
|
9432
|
+
}
|
|
9433
|
+
return all;
|
|
9434
|
+
}
|
|
9435
|
+
async function createAppDataUsage(session, input) {
|
|
9436
|
+
const relationships = { app: { data: { type: "apps", id: input.appId } } };
|
|
9437
|
+
if (input.category) relationships.category = { data: { type: "appDataUsageCategories", id: input.category } };
|
|
9438
|
+
if (input.purpose) relationships.purpose = { data: { type: "appDataUsagePurposes", id: input.purpose } };
|
|
9439
|
+
relationships.dataProtection = { data: { type: "appDataUsageDataProtections", id: input.dataProtection } };
|
|
9440
|
+
const r = await iris(session, "POST", "/appDataUsages", { data: { type: "appDataUsages", relationships } });
|
|
9441
|
+
if (r.status !== 201 && r.status !== 200) fail(`Create ${input.category ?? ""} ${input.purpose ?? ""} ${input.dataProtection}`.replace(/\s+/g, " "), r);
|
|
9442
|
+
return r.body?.data?.id;
|
|
9443
|
+
}
|
|
9444
|
+
async function deleteAppDataUsage(session, id) {
|
|
9445
|
+
const r = await iris(session, "DELETE", `/appDataUsages/${id}`);
|
|
9446
|
+
if (r.status !== 204 && r.status !== 200) fail(`Delete app data usage ${id}`, r);
|
|
9447
|
+
}
|
|
9448
|
+
async function getAppDataUsagesPublishState(session, appId) {
|
|
9449
|
+
const r = await iris(session, "GET", `/apps/${appId}/dataUsagePublishState`);
|
|
9450
|
+
if (r.status !== 200) fail("Read privacy publish state", r);
|
|
9451
|
+
return { id: r.body.data.id, published: Boolean(r.body.data.attributes?.published) };
|
|
9452
|
+
}
|
|
9453
|
+
async function publishAppDataUsages(session, publishStateId) {
|
|
9454
|
+
const r = await iris(session, "PATCH", `/appDataUsagesPublishState/${publishStateId}`, {
|
|
9455
|
+
data: { type: "appDataUsagesPublishState", id: publishStateId, attributes: { published: true } }
|
|
9456
|
+
});
|
|
9457
|
+
if (r.status !== 200) fail("Publish privacy details", r);
|
|
9458
|
+
}
|
|
9459
|
+
async function uploadAppPrivacyDetails(opts) {
|
|
9460
|
+
const { session, appId, usages, skipPublish, onLog = () => {
|
|
9461
|
+
} } = opts;
|
|
9462
|
+
validateUsages(usages);
|
|
9463
|
+
await selectProviderForApp(session, appId, onLog);
|
|
9464
|
+
const existing = await listAppDataUsages(session, appId);
|
|
9465
|
+
onLog(`Deleting ${existing.length} existing answer${existing.length === 1 ? "" : "s"}`);
|
|
9466
|
+
for (const u of existing) await deleteAppDataUsage(session, u.id);
|
|
9467
|
+
let created = 0;
|
|
9468
|
+
for (const usage of usages) {
|
|
9469
|
+
const purposes = usage.purposes?.length ? usage.purposes : [null];
|
|
9470
|
+
for (const purpose of purposes) {
|
|
9471
|
+
for (const protection of usage.data_protections) {
|
|
9472
|
+
onLog(protection === "DATA_NOT_COLLECTED" ? `Setting ${protection}` : `Setting ${usage.category} and ${purpose} to ${protection}`);
|
|
9473
|
+
await createAppDataUsage(session, { appId, category: usage.category ?? null, purpose, dataProtection: protection });
|
|
9474
|
+
created++;
|
|
9475
|
+
}
|
|
9476
|
+
}
|
|
9477
|
+
}
|
|
9478
|
+
if (skipPublish) {
|
|
9479
|
+
onLog("Skipping publish");
|
|
9480
|
+
return { deleted: existing.length, created, published: false };
|
|
9481
|
+
}
|
|
9482
|
+
const state = await getAppDataUsagesPublishState(session, appId);
|
|
9483
|
+
await publishAppDataUsages(session, state.id);
|
|
9484
|
+
onLog("Published");
|
|
9485
|
+
return { deleted: existing.length, created, published: true };
|
|
9486
|
+
}
|
|
9487
|
+
function validateUsages(usages) {
|
|
9488
|
+
if (!Array.isArray(usages) || usages.length === 0) throw new Error("No privacy usages given.");
|
|
9489
|
+
const notCollected = usages.some((u) => u.data_protections?.includes("DATA_NOT_COLLECTED"));
|
|
9490
|
+
if (notCollected && usages.length > 1) {
|
|
9491
|
+
throw new Error("DATA_NOT_COLLECTED must be the only entry: an app cannot both collect nothing and collect something.");
|
|
9492
|
+
}
|
|
9493
|
+
const known = (list, v, what) => {
|
|
9494
|
+
if (!list.includes(v)) throw new Error(`"${v}" is not an App Store Connect ${what}. Use one of: ${list.join(", ")}.`);
|
|
9495
|
+
};
|
|
9496
|
+
for (const u of usages) {
|
|
9497
|
+
if (!u.data_protections?.length) throw new Error(`${u.category ?? "An entry"} has no data_protections.`);
|
|
9498
|
+
if (u.category) known(DATA_USAGE_CATEGORIES, u.category, "data category");
|
|
9499
|
+
for (const p of u.purposes ?? []) known(DATA_USAGE_PURPOSES, p, "purpose");
|
|
9500
|
+
for (const d of u.data_protections) known(DATA_PROTECTIONS, d, "data protection");
|
|
9501
|
+
if (u.data_protections.includes("DATA_NOT_COLLECTED")) continue;
|
|
9502
|
+
if (!u.category) throw new Error("Every collected entry needs a category.");
|
|
9503
|
+
if (!u.purposes?.length) throw new Error(`${u.category} needs at least one purpose.`);
|
|
9504
|
+
const linked = u.data_protections.includes("DATA_LINKED_TO_YOU");
|
|
9505
|
+
const unlinked = u.data_protections.includes("DATA_NOT_LINKED_TO_YOU");
|
|
9506
|
+
if (linked && unlinked) throw new Error(`${u.category} cannot be both linked and not linked to the user.`);
|
|
9507
|
+
}
|
|
9508
|
+
}
|
|
9509
|
+
var IRIS_BASE2, OLYMPUS_SESSION2, DATA_USAGE_CATEGORIES, DATA_USAGE_PURPOSES, DATA_PROTECTIONS;
|
|
9510
|
+
var init_privacy = __esm({
|
|
9511
|
+
"src/privacy/index.ts"() {
|
|
9512
|
+
IRIS_BASE2 = "https://appstoreconnect.apple.com/iris/v1";
|
|
9513
|
+
OLYMPUS_SESSION2 = "https://appstoreconnect.apple.com/olympus/v1/session";
|
|
9514
|
+
DATA_USAGE_CATEGORIES = [
|
|
9515
|
+
"PAYMENT_INFORMATION",
|
|
9516
|
+
"CREDIT_AND_FRAUD",
|
|
9517
|
+
"OTHER_FINANCIAL_INFO",
|
|
9518
|
+
"PRECISE_LOCATION",
|
|
9519
|
+
"COARSE_LOCATION",
|
|
9520
|
+
"SENSITIVE_INFO",
|
|
9521
|
+
"PHYSICAL_ADDRESS",
|
|
9522
|
+
"EMAIL_ADDRESS",
|
|
9523
|
+
"NAME",
|
|
9524
|
+
"PHONE_NUMBER",
|
|
9525
|
+
"OTHER_CONTACT_INFO",
|
|
9526
|
+
"CONTACTS",
|
|
9527
|
+
"EMAILS_OR_TEXT_MESSAGES",
|
|
9528
|
+
"PHOTOS_OR_VIDEOS",
|
|
9529
|
+
"AUDIO",
|
|
9530
|
+
"GAMEPLAY_CONTENT",
|
|
9531
|
+
"CUSTOMER_SUPPORT",
|
|
9532
|
+
"OTHER_USER_CONTENT",
|
|
9533
|
+
"BROWSING_HISTORY",
|
|
9534
|
+
"SEARCH_HISTORY",
|
|
9535
|
+
"USER_ID",
|
|
9536
|
+
"DEVICE_ID",
|
|
9537
|
+
"PURCHASE_HISTORY",
|
|
9538
|
+
"PRODUCT_INTERACTION",
|
|
9539
|
+
"ADVERTISING_DATA",
|
|
9540
|
+
"OTHER_USAGE_DATA",
|
|
9541
|
+
"CRASH_DATA",
|
|
9542
|
+
"PERFORMANCE_DATA",
|
|
9543
|
+
"OTHER_DIAGNOSTIC_DATA",
|
|
9544
|
+
"OTHER_DATA",
|
|
9545
|
+
"HEALTH",
|
|
9546
|
+
"FITNESS"
|
|
9547
|
+
];
|
|
9548
|
+
DATA_USAGE_PURPOSES = [
|
|
9549
|
+
"THIRD_PARTY_ADVERTISING",
|
|
9550
|
+
"DEVELOPERS_ADVERTISING",
|
|
9551
|
+
"ANALYTICS",
|
|
9552
|
+
"PRODUCT_PERSONALIZATION",
|
|
9553
|
+
"APP_FUNCTIONALITY",
|
|
9554
|
+
"OTHER_PURPOSES"
|
|
9555
|
+
];
|
|
9556
|
+
DATA_PROTECTIONS = [
|
|
9557
|
+
"DATA_USED_TO_TRACK_YOU",
|
|
9558
|
+
"DATA_LINKED_TO_YOU",
|
|
9559
|
+
"DATA_NOT_LINKED_TO_YOU",
|
|
9560
|
+
"DATA_NOT_COLLECTED"
|
|
9561
|
+
];
|
|
9562
|
+
}
|
|
9563
|
+
});
|
|
9564
|
+
|
|
9565
|
+
// src/review/index.ts
|
|
9566
|
+
var review_exports = {};
|
|
9567
|
+
__export(review_exports, {
|
|
9568
|
+
getReviewFeedback: () => getReviewFeedback,
|
|
9569
|
+
getThreadMessages: () => getThreadMessages,
|
|
9570
|
+
htmlToText: () => htmlToText
|
|
9571
|
+
});
|
|
9572
|
+
async function iris2(session, path) {
|
|
9573
|
+
const res = await fetch(`${IRIS_BASE3}${path}`, {
|
|
9574
|
+
headers: {
|
|
9575
|
+
Cookie: Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join("; "),
|
|
9576
|
+
Accept: "application/vnd.api+json, application/json",
|
|
9577
|
+
"X-Requested-With": "XMLHttpRequest"
|
|
9578
|
+
}
|
|
9579
|
+
});
|
|
9580
|
+
for (const raw of res.headers.getSetCookie?.() ?? []) {
|
|
9581
|
+
const m = raw.match(/^([^=]+)=([^;]*)/);
|
|
9582
|
+
if (m) session.cookies[m[1]] = m[2];
|
|
9583
|
+
}
|
|
9584
|
+
const text = await res.text();
|
|
9585
|
+
let body = null;
|
|
9586
|
+
try {
|
|
9587
|
+
body = text ? JSON.parse(text) : null;
|
|
9588
|
+
} catch {
|
|
9589
|
+
}
|
|
9590
|
+
if (!res.ok) {
|
|
9591
|
+
const detail = body?.errors?.map((e) => e.detail ?? e.title).join("; ") || text.slice(0, 300);
|
|
9592
|
+
throw new Error(`GET ${path} failed (HTTP ${res.status}): ${detail}`);
|
|
9593
|
+
}
|
|
9594
|
+
return body;
|
|
9595
|
+
}
|
|
9596
|
+
function htmlToText(html) {
|
|
9597
|
+
if (!html) return "";
|
|
9598
|
+
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();
|
|
9599
|
+
}
|
|
9600
|
+
function reasonsFrom(rejection) {
|
|
9601
|
+
const reasons = rejection?.attributes?.reasons ?? [];
|
|
9602
|
+
return (Array.isArray(reasons) ? reasons : []).map((r) => ({
|
|
9603
|
+
section: r.reasonSection ?? r.section,
|
|
9604
|
+
description: r.reasonDescription ?? r.description,
|
|
9605
|
+
code: r.reasonCode ?? r.code
|
|
9606
|
+
}));
|
|
9607
|
+
}
|
|
9608
|
+
async function getThreadMessages(session, threadId) {
|
|
9609
|
+
const body = await iris2(session, `/resolutionCenterThreads/${threadId}/resolutionCenterMessages?include=fromActor,rejections`);
|
|
9610
|
+
const included = new Map((body?.included ?? []).map((i) => [`${i.type}:${i.id}`, i]));
|
|
9611
|
+
return (body?.data ?? []).map((m) => {
|
|
9612
|
+
const actor = m.relationships?.fromActor?.data;
|
|
9613
|
+
const actorRow = actor ? included.get(`${actor.type}:${actor.id}`) : null;
|
|
9614
|
+
const rejections = (m.relationships?.rejections?.data ?? []).map((r) => included.get(`${r.type}:${r.id}`)).filter(Boolean);
|
|
9615
|
+
return {
|
|
9616
|
+
id: m.id,
|
|
9617
|
+
createdDate: m.attributes?.createdDate ?? null,
|
|
9618
|
+
from: actorRow?.attributes?.actorType ?? actorRow?.attributes?.userFirstName ?? null,
|
|
9619
|
+
body: htmlToText(m.attributes?.messageBody),
|
|
9620
|
+
reasons: rejections.flatMap(reasonsFrom)
|
|
9621
|
+
};
|
|
9622
|
+
});
|
|
9623
|
+
}
|
|
9624
|
+
async function getReviewFeedback(session, appId, opts = {}) {
|
|
9625
|
+
await selectProviderForApp(session, appId, opts.onLog);
|
|
9626
|
+
const subs = await iris2(session, `/apps/${appId}/reviewSubmissions?limit=10`);
|
|
9627
|
+
const out = [];
|
|
9628
|
+
for (const s of subs?.data ?? []) {
|
|
9629
|
+
const state = s.attributes?.state ?? "";
|
|
9630
|
+
if (opts.onlyOpen && state !== "UNRESOLVED_ISSUES") continue;
|
|
9631
|
+
const threadsBody = await iris2(session, `/resolutionCenterThreads?filter[reviewSubmission]=${s.id}&include=reviewSubmission`);
|
|
9632
|
+
const threads = [];
|
|
9633
|
+
for (const t of threadsBody?.data ?? []) {
|
|
9634
|
+
threads.push({
|
|
9635
|
+
id: t.id,
|
|
9636
|
+
threadType: t.attributes?.threadType ?? null,
|
|
9637
|
+
state: t.attributes?.state ?? null,
|
|
9638
|
+
createdDate: t.attributes?.createdDate ?? null,
|
|
9639
|
+
messages: await getThreadMessages(session, t.id)
|
|
9640
|
+
});
|
|
9641
|
+
}
|
|
9642
|
+
if (threads.length || !opts.onlyOpen) {
|
|
9643
|
+
out.push({ submissionId: s.id, state, submittedDate: s.attributes?.submittedDate ?? null, threads });
|
|
9644
|
+
}
|
|
9645
|
+
}
|
|
9646
|
+
return out;
|
|
9647
|
+
}
|
|
9648
|
+
var IRIS_BASE3;
|
|
9649
|
+
var init_review = __esm({
|
|
9650
|
+
"src/review/index.ts"() {
|
|
9651
|
+
init_privacy();
|
|
9652
|
+
IRIS_BASE3 = "https://appstoreconnect.apple.com/iris/v1";
|
|
9653
|
+
}
|
|
9654
|
+
});
|
|
8519
9655
|
function createRunCommand() {
|
|
8520
9656
|
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) => {
|
|
8521
9657
|
const { loadFastfile: loadFastfile2 } = await Promise.resolve().then(() => (init_fastfile(), fastfile_exports));
|
|
@@ -8636,10 +9772,10 @@ function createPilotCommand() {
|
|
|
8636
9772
|
const cmd = new Command("pilot").description("Manage TestFlight builds and testers");
|
|
8637
9773
|
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) => {
|
|
8638
9774
|
const { uploadToTestFlight: uploadToTestFlight2 } = await Promise.resolve().then(() => (init_pilot(), pilot_exports));
|
|
8639
|
-
const { readFile:
|
|
9775
|
+
const { readFile: readFile9 } = await import('fs/promises');
|
|
8640
9776
|
let apiPrivateKey = "";
|
|
8641
9777
|
if (opts.apiKey) {
|
|
8642
|
-
apiPrivateKey = await
|
|
9778
|
+
apiPrivateKey = await readFile9(opts.apiKey, "utf-8");
|
|
8643
9779
|
}
|
|
8644
9780
|
console.log(`Uploading ${opts.ipa}...`);
|
|
8645
9781
|
const result = await uploadToTestFlight2({
|
|
@@ -8671,7 +9807,10 @@ function createPilotCommand() {
|
|
|
8671
9807
|
return cmd;
|
|
8672
9808
|
}
|
|
8673
9809
|
function createDeliverCommand() {
|
|
8674
|
-
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(
|
|
9810
|
+
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(
|
|
9811
|
+
"--assets-path <path>",
|
|
9812
|
+
"App Asset Library folder: <locale>/{screenshots,previews,header,search_results}/"
|
|
9813
|
+
).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) => {
|
|
8675
9814
|
const { resolveClientFromEnv: resolveClientFromEnv2 } = await Promise.resolve().then(() => (init_options(), options_exports));
|
|
8676
9815
|
const { uploadToAppStore: uploadToAppStore2 } = await Promise.resolve().then(() => (init_deliver(), deliver_exports));
|
|
8677
9816
|
const client = await resolveClientFromEnv2();
|
|
@@ -8690,7 +9829,12 @@ function createDeliverCommand() {
|
|
|
8690
9829
|
submitForReview: opts.submitForReview,
|
|
8691
9830
|
skipBinaryUpload: opts.skipBinaryUpload,
|
|
8692
9831
|
skipMetadata: opts.skipMetadata,
|
|
8693
|
-
skipScreenshots: opts.skipScreenshots
|
|
9832
|
+
skipScreenshots: opts.skipScreenshots,
|
|
9833
|
+
assetsPath: opts.assetsPath,
|
|
9834
|
+
useAssetLibrary: opts.assetLibrary,
|
|
9835
|
+
overwriteAssets: opts.overwriteAssets,
|
|
9836
|
+
skipAssets: opts.skipAssets,
|
|
9837
|
+
onOutput: (chunk) => process.stdout.write(chunk)
|
|
8694
9838
|
});
|
|
8695
9839
|
console.log(`App: ${result.appId}`);
|
|
8696
9840
|
console.log(`Version: ${result.versionId}`);
|
|
@@ -8794,6 +9938,145 @@ function createLanesCommand() {
|
|
|
8794
9938
|
}
|
|
8795
9939
|
});
|
|
8796
9940
|
}
|
|
9941
|
+
function createPrivacyCommand() {
|
|
9942
|
+
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", process.env.FASTNODE_USER ?? process.env.FASTLANE_USER).option("--skip-publish", "Save the answers without publishing them", false).action(async (opts) => {
|
|
9943
|
+
const { loginWithAppleId: loginWithAppleId2 } = await Promise.resolve().then(() => (init_apple_session(), apple_session_exports));
|
|
9944
|
+
const { uploadAppPrivacyDetails: uploadAppPrivacyDetails2, validateUsages: validateUsages2 } = await Promise.resolve().then(() => (init_privacy(), privacy_exports));
|
|
9945
|
+
const usages = JSON.parse(readFileSync(opts.jsonPath, "utf8"));
|
|
9946
|
+
validateUsages2(usages);
|
|
9947
|
+
const email = opts.username || await ask("Apple ID: ");
|
|
9948
|
+
const password = process.env.FASTNODE_PASSWORD ?? process.env.FASTLANE_PASSWORD ?? await ask(`Password for ${email}: `, true);
|
|
9949
|
+
const session = await loginWithAppleId2({ email, password, onLog: (m) => console.log(m) });
|
|
9950
|
+
const result = await uploadAppPrivacyDetails2({
|
|
9951
|
+
session,
|
|
9952
|
+
appId: opts.appId,
|
|
9953
|
+
usages,
|
|
9954
|
+
skipPublish: opts.skipPublish,
|
|
9955
|
+
onLog: (m) => console.log(m)
|
|
9956
|
+
});
|
|
9957
|
+
console.log(`Done: ${result.created} answers${result.published ? ", published" : ""}.`);
|
|
9958
|
+
});
|
|
9959
|
+
}
|
|
9960
|
+
function ask(question, secret = false) {
|
|
9961
|
+
let muted = false;
|
|
9962
|
+
const output = new Writable({
|
|
9963
|
+
write(chunk, _enc, cb) {
|
|
9964
|
+
if (!muted) process.stdout.write(chunk);
|
|
9965
|
+
cb();
|
|
9966
|
+
}
|
|
9967
|
+
});
|
|
9968
|
+
const rl = createInterface({ input: process.stdin, output, terminal: true });
|
|
9969
|
+
return new Promise((resolve4) => {
|
|
9970
|
+
rl.question(question, (answer) => {
|
|
9971
|
+
rl.close();
|
|
9972
|
+
if (secret) process.stdout.write("\n");
|
|
9973
|
+
resolve4(answer.trim());
|
|
9974
|
+
});
|
|
9975
|
+
muted = secret;
|
|
9976
|
+
});
|
|
9977
|
+
}
|
|
9978
|
+
function createRejectionsCommand() {
|
|
9979
|
+
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", process.env.FASTNODE_USER ?? process.env.FASTLANE_USER).option("--all", "Include resolved submissions, not just ones awaiting you", false).option("--json", "Print JSON instead of text", false).action(async (opts) => {
|
|
9980
|
+
const { loginWithAppleId: loginWithAppleId2 } = await Promise.resolve().then(() => (init_apple_session(), apple_session_exports));
|
|
9981
|
+
const { getReviewFeedback: getReviewFeedback2 } = await Promise.resolve().then(() => (init_review(), review_exports));
|
|
9982
|
+
const email = opts.username || await ask2("Apple ID: ");
|
|
9983
|
+
const password = process.env.FASTNODE_PASSWORD ?? process.env.FASTLANE_PASSWORD ?? await ask2(`Password for ${email}: `, true);
|
|
9984
|
+
const session = await loginWithAppleId2({ email, password, onLog: (m) => console.error(m) });
|
|
9985
|
+
const all = {};
|
|
9986
|
+
for (const appId of opts.appId) {
|
|
9987
|
+
const feedback = await getReviewFeedback2(session, appId, { onlyOpen: !opts.all, onLog: (m) => console.error(m) });
|
|
9988
|
+
all[appId] = feedback;
|
|
9989
|
+
if (opts.json) continue;
|
|
9990
|
+
console.log(`
|
|
9991
|
+
\u2550\u2550 App ${appId} \u2550\u2550`);
|
|
9992
|
+
if (!feedback.length) console.log("No open App Review feedback.");
|
|
9993
|
+
for (const s of feedback) {
|
|
9994
|
+
console.log(`
|
|
9995
|
+
Submission ${s.submissionId} \u2014 ${s.state} (submitted ${s.submittedDate ?? "?"})`);
|
|
9996
|
+
for (const t of s.threads) {
|
|
9997
|
+
console.log(` Thread ${t.threadType ?? "?"} \xB7 ${t.state ?? "?"} \xB7 ${t.createdDate ?? ""}`);
|
|
9998
|
+
for (const m of t.messages) {
|
|
9999
|
+
console.log(`
|
|
10000
|
+
\u2500\u2500 ${m.from ?? "App Review"} \xB7 ${m.createdDate ?? ""}`);
|
|
10001
|
+
for (const r of m.reasons) console.log(` \u25B8 ${[r.section, r.description].filter(Boolean).join(" \u2014 ")}${r.code ? ` [${r.code}]` : ""}`);
|
|
10002
|
+
console.log(m.body.split("\n").map((l) => ` ${l}`).join("\n"));
|
|
10003
|
+
}
|
|
10004
|
+
}
|
|
10005
|
+
}
|
|
10006
|
+
}
|
|
10007
|
+
if (opts.json) console.log(JSON.stringify(all, null, 2));
|
|
10008
|
+
});
|
|
10009
|
+
}
|
|
10010
|
+
function ask2(question, secret = false) {
|
|
10011
|
+
let muted = false;
|
|
10012
|
+
const output = new Writable({ write(chunk, _enc, cb) {
|
|
10013
|
+
if (!muted) process.stdout.write(chunk);
|
|
10014
|
+
cb();
|
|
10015
|
+
} });
|
|
10016
|
+
const rl = createInterface({ input: process.stdin, output, terminal: true });
|
|
10017
|
+
return new Promise((resolve4) => {
|
|
10018
|
+
rl.question(question, (a) => {
|
|
10019
|
+
rl.close();
|
|
10020
|
+
if (secret) process.stdout.write("\n");
|
|
10021
|
+
resolve4(a.trim());
|
|
10022
|
+
});
|
|
10023
|
+
muted = secret;
|
|
10024
|
+
});
|
|
10025
|
+
}
|
|
10026
|
+
function createAssetsCommand() {
|
|
10027
|
+
const assets = new Command("assets").description(
|
|
10028
|
+
"App Store asset library \u2014 screenshots, previews, product page headers, search results assets"
|
|
10029
|
+
);
|
|
10030
|
+
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) => {
|
|
10031
|
+
const { resolveClientFromEnv: resolveClientFromEnv2 } = await Promise.resolve().then(() => (init_options(), options_exports));
|
|
10032
|
+
const client = await resolveClientFromEnv2();
|
|
10033
|
+
const feature = opts.feature;
|
|
10034
|
+
const refData = await client.appAssetLibraries.refData({
|
|
10035
|
+
features: [feature],
|
|
10036
|
+
placementTypes: opts.placementType ? [opts.placementType] : void 0
|
|
10037
|
+
});
|
|
10038
|
+
if (opts.json) {
|
|
10039
|
+
console.log(JSON.stringify(refData, null, 2));
|
|
10040
|
+
return;
|
|
10041
|
+
}
|
|
10042
|
+
const policies = refData.features?.find((f) => f.featureId === feature)?.placementPolicies ?? [];
|
|
10043
|
+
const specs = new Map([...refData.imageSpecs ?? [], ...refData.videoSpecs ?? []].map((s) => [s.specId, s]));
|
|
10044
|
+
for (const type of refData.placementTypes ?? []) {
|
|
10045
|
+
if (opts.placementType && type.placementTypeId !== opts.placementType) continue;
|
|
10046
|
+
const policy = policies.find((p) => p.placementType === type.placementTypeId);
|
|
10047
|
+
if (policies.length && !policy) continue;
|
|
10048
|
+
console.log(`
|
|
10049
|
+
${type.placementTypeId} (${(type.acceptsAssetCategories ?? []).join(", ")})`);
|
|
10050
|
+
for (const mapping of type.specMappings ?? []) {
|
|
10051
|
+
const max = policy?.groupLimits?.find((l) => l.groupIds.includes(mapping.placementGroupId))?.maxCount;
|
|
10052
|
+
const sizes = mapping.specs.map((id) => specs.get(id)).filter((s) => Boolean(s)).map((s) => {
|
|
10053
|
+
const d = s.dimensions;
|
|
10054
|
+
const w = d.minWidth === d.maxWidth ? `${d.minWidth}` : `${d.minWidth}-${d.maxWidth}`;
|
|
10055
|
+
const h = d.minHeight === d.maxHeight ? `${d.minHeight}` : `${d.minHeight}-${d.maxHeight}`;
|
|
10056
|
+
return `${w}\xD7${h}`;
|
|
10057
|
+
});
|
|
10058
|
+
console.log(` ${mapping.placementGroupId.padEnd(44)} ${max ? `max ${max} ` : ""}${sizes.join(", ")}`);
|
|
10059
|
+
}
|
|
10060
|
+
}
|
|
10061
|
+
});
|
|
10062
|
+
assets.command("list").description("List images and videos in an app's asset library").requiredOption("-a, --app-identifier <id>", "Bundle identifier").action(async (opts) => {
|
|
10063
|
+
const { resolveClientFromEnv: resolveClientFromEnv2 } = await Promise.resolve().then(() => (init_options(), options_exports));
|
|
10064
|
+
const client = await resolveClientFromEnv2();
|
|
10065
|
+
const app = await client.apps.findByBundleId(opts.appIdentifier);
|
|
10066
|
+
if (!app) throw new Error(`App not found: ${opts.appIdentifier}`);
|
|
10067
|
+
const library = await client.appAssetLibraries.getForApp(app.id);
|
|
10068
|
+
const [images, videos] = await Promise.all([
|
|
10069
|
+
client.appAssetLibraries.listImages(library.id),
|
|
10070
|
+
client.appAssetLibraries.listVideos(library.id)
|
|
10071
|
+
]);
|
|
10072
|
+
for (const a of [...images, ...videos]) {
|
|
10073
|
+
const at = a.attributes;
|
|
10074
|
+
console.log(`${a.type === "appAssetLibraryImages" ? "image" : "video"} ${a.id} ${String(at.state).padEnd(22)} ${at.category} ${at.referenceName ?? at.fileName}`);
|
|
10075
|
+
}
|
|
10076
|
+
if (!images.length && !videos.length) console.log("Asset library is empty");
|
|
10077
|
+
});
|
|
10078
|
+
return assets;
|
|
10079
|
+
}
|
|
8797
10080
|
|
|
8798
10081
|
// src/cli/index.ts
|
|
8799
10082
|
init_options();
|
|
@@ -8812,6 +10095,9 @@ function createCli() {
|
|
|
8812
10095
|
program.addCommand(createMatchCommand());
|
|
8813
10096
|
program.addCommand(createActionsCommand());
|
|
8814
10097
|
program.addCommand(createLanesCommand());
|
|
10098
|
+
program.addCommand(createPrivacyCommand());
|
|
10099
|
+
program.addCommand(createRejectionsCommand());
|
|
10100
|
+
program.addCommand(createAssetsCommand());
|
|
8815
10101
|
return program;
|
|
8816
10102
|
}
|
|
8817
10103
|
|