@sitepong/fastnode 0.2.2 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -1
- package/dist/actions/index.cjs +928 -54
- package/dist/actions/index.cjs.map +1 -1
- package/dist/actions/index.mjs +930 -56
- package/dist/actions/index.mjs.map +1 -1
- package/dist/apple-session-jUzBrql5.d.cts +122 -0
- package/dist/apple-session-jUzBrql5.d.ts +122 -0
- package/dist/cert/index.cjs +782 -736
- package/dist/cert/index.cjs.map +1 -1
- package/dist/cert/index.d.cts +5 -4
- package/dist/cert/index.d.ts +5 -4
- package/dist/cert/index.mjs +782 -737
- package/dist/cert/index.mjs.map +1 -1
- package/dist/{cert-Ddl7FpFT.d.ts → cert-ClacRx9y.d.ts} +2 -2
- package/dist/{cert-FehieKeb.d.cts → cert-DffLKURp.d.cts} +2 -2
- package/dist/cli/index.cjs +1408 -60
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.d.cts +3 -2
- package/dist/cli/index.d.ts +3 -2
- package/dist/cli/index.mjs +1410 -62
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/index.cjs +519 -31
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +3 -2
- package/dist/client/index.d.ts +3 -2
- package/dist/client/index.mjs +514 -32
- package/dist/client/index.mjs.map +1 -1
- package/dist/deliver/index.cjs +334 -13
- package/dist/deliver/index.cjs.map +1 -1
- package/dist/deliver/index.d.cts +4 -3
- package/dist/deliver/index.d.ts +4 -3
- package/dist/deliver/index.mjs +330 -15
- package/dist/deliver/index.mjs.map +1 -1
- package/dist/{enums-B609j2rb.d.cts → enums-yXNVh3UI.d.cts} +8 -1
- package/dist/{enums-B609j2rb.d.ts → enums-yXNVh3UI.d.ts} +8 -1
- package/dist/gradle/index.cjs +222 -0
- package/dist/gradle/index.cjs.map +1 -0
- package/dist/gradle/index.d.cts +1 -0
- package/dist/gradle/index.d.ts +1 -0
- package/dist/gradle/index.mjs +217 -0
- package/dist/gradle/index.mjs.map +1 -0
- package/dist/gym/index.d.cts +2 -2
- package/dist/gym/index.d.ts +2 -2
- package/dist/index-8djTPW8O.d.ts +104 -0
- package/dist/{index-Bk1Gd-qE.d.cts → index-925_Ov7X.d.cts} +2 -2
- package/dist/{index-DrUpRXRG.d.cts → index-B6-MJpO4.d.cts} +1 -1
- package/dist/index-BGhV0gS-.d.ts +65 -0
- package/dist/{index-OelCoOth.d.cts → index-BbVQzSV1.d.cts} +18 -4
- package/dist/index-BhGaVlK1.d.cts +201 -0
- package/dist/{index-CtPyeMLH.d.ts → index-C-EEYXSE.d.ts} +18 -4
- package/dist/{index-CNHf7I4v.d.cts → index-C18cijCd.d.ts} +32 -3
- package/dist/{index-DFni8oZt.d.ts → index-CBM5XZKC.d.ts} +2 -2
- package/dist/index-CXJlvmB4.d.cts +63 -0
- package/dist/index-CXJlvmB4.d.ts +63 -0
- package/dist/{index-BKUapQP2.d.cts → index-CemR3UI9.d.ts} +356 -124
- package/dist/{index-DI1ZHr-3.d.ts → index-CmcFPn_G.d.cts} +9 -1
- package/dist/{index-CCtYhPN9.d.ts → index-CpGV1f0L.d.cts} +356 -124
- package/dist/{index-B8FOfxOw.d.ts → index-D9YQQ3FM.d.ts} +2 -2
- package/dist/index-DFstwgGK.d.cts +65 -0
- package/dist/{index-YcKz4aRS.d.ts → index-Dkf5sH1E.d.ts} +1 -1
- package/dist/index-DnHQ2uT2.d.cts +104 -0
- package/dist/index-Dv-Mek07.d.cts +84 -0
- package/dist/index-Dv-Mek07.d.ts +84 -0
- package/dist/{index-BICa8odo.d.cts → index-N6eV5yym.d.ts} +9 -1
- package/dist/{index-oVyLLIEK.d.cts → index-O3EMrd5s.d.cts} +3 -3
- package/dist/{index-DZoPGCbE.d.ts → index-Ol0RtbQA.d.ts} +3 -3
- package/dist/{index-CplRWSUc.d.ts → index-_24DRp3a.d.cts} +32 -3
- package/dist/{index-BZWmOTWE.d.cts → index-isT08RuY.d.cts} +2 -2
- package/dist/{index-DImN9PWc.d.ts → index-mEEB7An6.d.ts} +1 -1
- package/dist/{index-D13sn8DO.d.cts → index-on573dnO.d.cts} +1 -1
- package/dist/index-s2PigtDM.d.ts +201 -0
- package/dist/index.cjs +3425 -955
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +237 -14
- package/dist/index.d.ts +237 -14
- package/dist/index.mjs +3417 -958
- package/dist/index.mjs.map +1 -1
- package/dist/lane/index.cjs +928 -54
- package/dist/lane/index.cjs.map +1 -1
- package/dist/lane/index.mjs +930 -56
- package/dist/lane/index.mjs.map +1 -1
- package/dist/match/index.d.cts +6 -5
- package/dist/match/index.d.ts +6 -5
- package/dist/pem/index.d.cts +4 -3
- package/dist/pem/index.d.ts +4 -3
- package/dist/pilot/index.cjs +8 -4
- package/dist/pilot/index.cjs.map +1 -1
- package/dist/pilot/index.d.cts +4 -3
- package/dist/pilot/index.d.ts +4 -3
- package/dist/pilot/index.mjs +10 -6
- package/dist/pilot/index.mjs.map +1 -1
- package/dist/playstore/index.cjs +207 -0
- package/dist/playstore/index.cjs.map +1 -0
- package/dist/playstore/index.d.cts +1 -0
- package/dist/playstore/index.d.ts +1 -0
- package/dist/playstore/index.mjs +203 -0
- package/dist/playstore/index.mjs.map +1 -0
- package/dist/precheck/index.d.cts +4 -3
- package/dist/precheck/index.d.ts +4 -3
- package/dist/privacy/index.cjs +207 -0
- package/dist/privacy/index.cjs.map +1 -0
- package/dist/privacy/index.d.cts +2 -0
- package/dist/privacy/index.d.ts +2 -0
- package/dist/privacy/index.mjs +195 -0
- package/dist/privacy/index.mjs.map +1 -0
- package/dist/produce/index.cjs +32 -0
- package/dist/produce/index.cjs.map +1 -1
- package/dist/produce/index.d.cts +4 -3
- package/dist/produce/index.d.ts +4 -3
- package/dist/produce/index.mjs +32 -1
- package/dist/produce/index.mjs.map +1 -1
- package/dist/review/index.cjs +141 -0
- package/dist/review/index.cjs.map +1 -0
- package/dist/review/index.d.cts +2 -0
- package/dist/review/index.d.ts +2 -0
- package/dist/review/index.mjs +137 -0
- package/dist/review/index.mjs.map +1 -0
- package/dist/sigh/index.d.cts +4 -3
- package/dist/sigh/index.d.ts +4 -3
- package/dist/xcode/index.cjs +1 -1
- package/dist/xcode/index.cjs.map +1 -1
- package/dist/xcode/index.mjs +1 -1
- package/dist/xcode/index.mjs.map +1 -1
- package/package.json +27 -1
- package/dist/index-B9QeTpQK.d.cts +0 -99
- package/dist/index-xykpq6Lz.d.ts +0 -99
package/dist/cli/index.cjs
CHANGED
|
@@ -13,6 +13,7 @@ var readline = require('readline');
|
|
|
13
13
|
var emojiRegex = require('emoji-regex');
|
|
14
14
|
var jose = require('jose');
|
|
15
15
|
var commander = require('commander');
|
|
16
|
+
var stream = require('stream');
|
|
16
17
|
|
|
17
18
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
18
19
|
|
|
@@ -209,8 +210,8 @@ async function revokeExpiredCertificates(options) {
|
|
|
209
210
|
return count;
|
|
210
211
|
}
|
|
211
212
|
async function importToKeychain(certData, keychainPath, keychainPassword) {
|
|
212
|
-
const { tmpdir:
|
|
213
|
-
const tempCertPath = path.join(
|
|
213
|
+
const { tmpdir: tmpdir7 } = await import('os');
|
|
214
|
+
const tempCertPath = path.join(tmpdir7(), `fastnode-cert-${Date.now()}.cer`);
|
|
214
215
|
await promises.writeFile(tempCertPath, certData);
|
|
215
216
|
try {
|
|
216
217
|
const args = [
|
|
@@ -1779,6 +1780,20 @@ var init_srp = __esm({
|
|
|
1779
1780
|
};
|
|
1780
1781
|
}
|
|
1781
1782
|
});
|
|
1783
|
+
async function appleJson(res, what) {
|
|
1784
|
+
const text = await res.text();
|
|
1785
|
+
try {
|
|
1786
|
+
return JSON.parse(text);
|
|
1787
|
+
} catch {
|
|
1788
|
+
const contentType = (res.headers.get("content-type") ?? "").split(";")[0].trim();
|
|
1789
|
+
const title = /<title>([^<]{1,120})<\/title>/i.exec(text)?.[1]?.trim();
|
|
1790
|
+
const looksHtml = /^\s*<(!doctype|html)/i.test(text) || contentType === "text/html";
|
|
1791
|
+
const page = looksHtml ? "an HTML page" : contentType ? `a ${contentType} response` : "a non-JSON response";
|
|
1792
|
+
throw new Error(
|
|
1793
|
+
`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.`
|
|
1794
|
+
);
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1782
1797
|
function checkPortalResponse(body, session) {
|
|
1783
1798
|
if (!body || typeof body !== "object") return;
|
|
1784
1799
|
if (body.resultCode === 4100) {
|
|
@@ -1855,7 +1870,7 @@ async function beginLogin(config) {
|
|
|
1855
1870
|
}
|
|
1856
1871
|
async function fetchTwoFactorChallenge(pending) {
|
|
1857
1872
|
const r = await fetch(`${AUTH_BASE}`, { headers: buildAuthHeadersFromPending(pending) });
|
|
1858
|
-
const body = await r
|
|
1873
|
+
const body = await appleJson(r, "two-factor challenge");
|
|
1859
1874
|
const trustedPhoneNumbers = (body.trustedPhoneNumbers ?? []).map((p) => ({
|
|
1860
1875
|
id: p.id,
|
|
1861
1876
|
numberWithDialCode: p.numberWithDialCode,
|
|
@@ -1876,7 +1891,8 @@ async function requestSmsCode(input) {
|
|
|
1876
1891
|
body: JSON.stringify({ phoneNumber: { id: input.phoneId }, mode: input.mode ?? "sms" })
|
|
1877
1892
|
});
|
|
1878
1893
|
}
|
|
1879
|
-
async function submitTwoFactorCode(
|
|
1894
|
+
async function submitTwoFactorCode(rawInput) {
|
|
1895
|
+
const input = { ...rawInput, code: rawInput.code.replace(/[\s\u00a0]+/g, "") };
|
|
1880
1896
|
const log = input.onLog ?? (() => {
|
|
1881
1897
|
});
|
|
1882
1898
|
let path;
|
|
@@ -1988,9 +2004,12 @@ async function doSirp(user, password, serviceKey, cookieStr, hashcash) {
|
|
|
1988
2004
|
body: JSON.stringify({ a: aBase64, accountName: user, protocols: ["s2k", "s2k_fo"] }),
|
|
1989
2005
|
redirect: "manual"
|
|
1990
2006
|
});
|
|
1991
|
-
const initBody = await initRes
|
|
2007
|
+
const initBody = await appleJson(initRes, "sign-in init");
|
|
1992
2008
|
if (initBody.serviceErrors) throw new Error(`Apple auth error: ${JSON.stringify(initBody.serviceErrors)}`);
|
|
1993
2009
|
const { iteration: iterations, salt: saltB64, b: bB64, c, protocol } = initBody;
|
|
2010
|
+
if (!iterations || !saltB64 || !bB64 || !c || !protocol) {
|
|
2011
|
+
throw new Error(`Apple's sign-in init answered without a challenge (HTTP ${initRes.status}): ${JSON.stringify(initBody).slice(0, 300)}`);
|
|
2012
|
+
}
|
|
1994
2013
|
const salt = Buffer.from(saltB64, "base64");
|
|
1995
2014
|
const b = Buffer.from(bB64, "base64");
|
|
1996
2015
|
const encryptedPassword = applePbkdf2(password, salt, iterations, 32, protocol);
|
|
@@ -2033,7 +2052,7 @@ async function fetchOlympusSession(session) {
|
|
|
2033
2052
|
const cookieStr = Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join("; ");
|
|
2034
2053
|
const res = await fetch(OLYMPUS_SESSION, { headers: { Cookie: cookieStr, Accept: "application/json" } });
|
|
2035
2054
|
if (res.ok) {
|
|
2036
|
-
const body = await res
|
|
2055
|
+
const body = await appleJson(res, "session lookup");
|
|
2037
2056
|
if (body?.user?.emailAddress) session.userEmail = body.user.emailAddress;
|
|
2038
2057
|
if (body?.provider?.providerId) session.teamId = String(body.provider.providerId);
|
|
2039
2058
|
}
|
|
@@ -2045,7 +2064,7 @@ async function fetchContractMessages(session) {
|
|
|
2045
2064
|
headers: { Cookie: cookieStr, Accept: "application/json" }
|
|
2046
2065
|
});
|
|
2047
2066
|
if (!res.ok) return [];
|
|
2048
|
-
const body = await res
|
|
2067
|
+
const body = await appleJson(res, "agreements lookup");
|
|
2049
2068
|
return (body ?? []).map((m) => ({
|
|
2050
2069
|
id: m.id ?? "",
|
|
2051
2070
|
group: m.group ?? "",
|
|
@@ -2056,10 +2075,22 @@ async function fetchContractMessages(session) {
|
|
|
2056
2075
|
return [];
|
|
2057
2076
|
}
|
|
2058
2077
|
}
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2078
|
+
function serviceKeyFromLocation(location) {
|
|
2079
|
+
if (!location) return null;
|
|
2080
|
+
try {
|
|
2081
|
+
const key = new URL(location).searchParams.get("widgetKey")?.trim();
|
|
2082
|
+
return key && /^[0-9a-f]{16,}$/i.test(key) ? key : null;
|
|
2083
|
+
} catch {
|
|
2084
|
+
return null;
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
async function fetchServiceKey(fetchImpl = fetch) {
|
|
2088
|
+
const head = await fetchImpl(LOGOUT_URL, { method: "HEAD", redirect: "manual" });
|
|
2089
|
+
const fromRedirect = serviceKeyFromLocation(head.headers.get("location"));
|
|
2090
|
+
if (fromRedirect) return fromRedirect;
|
|
2091
|
+
const res = await fetchImpl(WIDGET_KEY_URL);
|
|
2092
|
+
const body = await appleJson(res, "service key request");
|
|
2093
|
+
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.");
|
|
2063
2094
|
return body.authServiceKey;
|
|
2064
2095
|
}
|
|
2065
2096
|
function buildAuthHeaders(session) {
|
|
@@ -2107,7 +2138,7 @@ async function defaultPrompt(message) {
|
|
|
2107
2138
|
});
|
|
2108
2139
|
});
|
|
2109
2140
|
}
|
|
2110
|
-
var AUTH_BASE, OLYMPUS_SESSION, WIDGET_KEY_URL, ProgramLicenseAgreementError;
|
|
2141
|
+
var AUTH_BASE, OLYMPUS_SESSION, WIDGET_KEY_URL, ProgramLicenseAgreementError, LOGOUT_URL;
|
|
2111
2142
|
var init_apple_session = __esm({
|
|
2112
2143
|
"src/client/apple-session.ts"() {
|
|
2113
2144
|
init_srp();
|
|
@@ -2122,6 +2153,7 @@ var init_apple_session = __esm({
|
|
|
2122
2153
|
}
|
|
2123
2154
|
agreements;
|
|
2124
2155
|
};
|
|
2156
|
+
LOGOUT_URL = "https://appstoreconnect.apple.com/logout";
|
|
2125
2157
|
}
|
|
2126
2158
|
});
|
|
2127
2159
|
|
|
@@ -2438,9 +2470,10 @@ async function uploadWithTransporter(options) {
|
|
|
2438
2470
|
const transporterBin = await resolveTransporter();
|
|
2439
2471
|
const platform = options.platform ?? "ios";
|
|
2440
2472
|
const timeout = options.timeout ?? 18e5;
|
|
2441
|
-
const keyDir =
|
|
2473
|
+
const keyDir = path.join(os.homedir(), ".appstoreconnect", "private_keys");
|
|
2474
|
+
await promises.mkdir(keyDir, { recursive: true });
|
|
2442
2475
|
const keyPath = path.join(keyDir, `AuthKey_${options.apiKeyId}.p8`);
|
|
2443
|
-
await promises.writeFile(keyPath, options.apiPrivateKey);
|
|
2476
|
+
await promises.writeFile(keyPath, options.apiPrivateKey, { mode: 384 });
|
|
2444
2477
|
return new Promise((resolve4) => {
|
|
2445
2478
|
const args = [
|
|
2446
2479
|
"-m",
|
|
@@ -2456,6 +2489,9 @@ async function uploadWithTransporter(options) {
|
|
|
2456
2489
|
"-WONoPause",
|
|
2457
2490
|
"true"
|
|
2458
2491
|
];
|
|
2492
|
+
if (options.assetDescriptionPath) {
|
|
2493
|
+
args.push("-assetDescription", options.assetDescriptionPath);
|
|
2494
|
+
}
|
|
2459
2495
|
if (platform !== "ios") {
|
|
2460
2496
|
args.push("-bundleType", platform);
|
|
2461
2497
|
}
|
|
@@ -2479,13 +2515,13 @@ async function uploadWithTransporter(options) {
|
|
|
2479
2515
|
child.stderr.on("data", onData);
|
|
2480
2516
|
child.on("close", async (code) => {
|
|
2481
2517
|
clearTimeout(timer);
|
|
2482
|
-
await promises.rm(
|
|
2518
|
+
await promises.rm(keyPath, { force: true }).catch(() => {
|
|
2483
2519
|
});
|
|
2484
2520
|
resolve4({ success: code === 0, output, backend: "transporter" });
|
|
2485
2521
|
});
|
|
2486
2522
|
child.on("error", async (err) => {
|
|
2487
2523
|
clearTimeout(timer);
|
|
2488
|
-
await promises.rm(
|
|
2524
|
+
await promises.rm(keyPath, { force: true }).catch(() => {
|
|
2489
2525
|
});
|
|
2490
2526
|
resolve4({
|
|
2491
2527
|
success: false,
|
|
@@ -2777,6 +2813,311 @@ var init_metadata = __esm({
|
|
|
2777
2813
|
};
|
|
2778
2814
|
}
|
|
2779
2815
|
});
|
|
2816
|
+
function categoryFor(placementType) {
|
|
2817
|
+
return placementType === "APP_SCREENSHOT" || placementType === "IMESSAGE_APP_SCREENSHOT" || placementType === "APP_PREVIEW" ? "APP_SCREENSHOTS_AND_PREVIEWS" : "CREATIVE_ASSETS";
|
|
2818
|
+
}
|
|
2819
|
+
function readImageSize(data) {
|
|
2820
|
+
if (data.length >= 26 && data.readUInt32BE(0) === 2303741511) {
|
|
2821
|
+
const colorType = data[25];
|
|
2822
|
+
return {
|
|
2823
|
+
width: data.readUInt32BE(16),
|
|
2824
|
+
height: data.readUInt32BE(20),
|
|
2825
|
+
alpha: colorType === 4 || colorType === 6
|
|
2826
|
+
};
|
|
2827
|
+
}
|
|
2828
|
+
if (data.length >= 4 && data[0] === 255 && data[1] === 216) {
|
|
2829
|
+
let offset = 2;
|
|
2830
|
+
while (offset + 9 < data.length) {
|
|
2831
|
+
if (data[offset] !== 255) return null;
|
|
2832
|
+
const marker = data[offset + 1];
|
|
2833
|
+
const length = data.readUInt16BE(offset + 2);
|
|
2834
|
+
const isSof = marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204;
|
|
2835
|
+
if (isSof) {
|
|
2836
|
+
return { height: data.readUInt16BE(offset + 5), width: data.readUInt16BE(offset + 7), alpha: false };
|
|
2837
|
+
}
|
|
2838
|
+
offset += 2 + length;
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2841
|
+
return null;
|
|
2842
|
+
}
|
|
2843
|
+
function fits(spec, width, height) {
|
|
2844
|
+
const d = spec.dimensions;
|
|
2845
|
+
return width >= d.minWidth && width <= d.maxWidth && height >= d.minHeight && height <= d.maxHeight;
|
|
2846
|
+
}
|
|
2847
|
+
function groupsForFeature(refData, feature, placementType) {
|
|
2848
|
+
const mappings = refData.placementTypes?.find((p) => p.placementTypeId === placementType)?.specMappings ?? [];
|
|
2849
|
+
const all = mappings.map((m) => m.placementGroupId);
|
|
2850
|
+
const policy = refData.features?.find((f) => f.featureId === feature)?.placementPolicies?.find((p) => p.placementType === placementType);
|
|
2851
|
+
const limited = policy?.groupLimits?.flatMap((l) => l.groupIds);
|
|
2852
|
+
return limited?.length ? all.filter((g) => limited.includes(g)) : all;
|
|
2853
|
+
}
|
|
2854
|
+
function maxCountFor(refData, feature, placementType, placementGroup) {
|
|
2855
|
+
return refData.features?.find((f) => f.featureId === feature)?.placementPolicies?.find((p) => p.placementType === placementType)?.groupLimits?.find((l) => l.groupIds.includes(placementGroup))?.maxCount;
|
|
2856
|
+
}
|
|
2857
|
+
function resolveImageGroups(refData, feature, placementType, size) {
|
|
2858
|
+
const specs = (refData.imageSpecs ?? []).filter(
|
|
2859
|
+
(s) => s.compatiblePlacementTypes.includes(placementType) && fits(s, size.width, size.height)
|
|
2860
|
+
);
|
|
2861
|
+
const specIds = new Set(specs.map((s) => s.specId));
|
|
2862
|
+
const allowed = new Set(groupsForFeature(refData, feature, placementType));
|
|
2863
|
+
const mappings = refData.placementTypes?.find((p) => p.placementTypeId === placementType)?.specMappings ?? [];
|
|
2864
|
+
const groups = mappings.filter((m) => allowed.has(m.placementGroupId) && m.specs.some((id) => specIds.has(id))).map((m) => m.placementGroupId);
|
|
2865
|
+
return { groups: [...new Set(groups)], spec: specs[0] };
|
|
2866
|
+
}
|
|
2867
|
+
async function runUploadOperations(ops, data) {
|
|
2868
|
+
for (const op of ops) {
|
|
2869
|
+
const headers = {};
|
|
2870
|
+
for (const h of op.requestHeaders) headers[h.name] = h.value;
|
|
2871
|
+
const res = await fetch(op.url, {
|
|
2872
|
+
method: op.method,
|
|
2873
|
+
headers,
|
|
2874
|
+
body: data.subarray(op.offset, op.offset + op.length)
|
|
2875
|
+
});
|
|
2876
|
+
if (!res.ok) {
|
|
2877
|
+
throw new Error(`Upload operation failed: ${op.method} ${new URL(op.url).host} \u2192 HTTP ${res.status}`);
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
async function uploadLibraryAsset(options) {
|
|
2882
|
+
const { client, libraryId, filePath, category } = options;
|
|
2883
|
+
const data = await promises.readFile(filePath);
|
|
2884
|
+
const fileName = path.basename(filePath);
|
|
2885
|
+
const kind = VIDEO_EXTENSIONS.includes(path.extname(filePath).toLowerCase()) ? "video" : "image";
|
|
2886
|
+
const hash = crypto.createHash("sha256").update(data).digest("hex").slice(0, 12);
|
|
2887
|
+
const referenceName = `${options.referenceName ?? fileName} #${hash}`;
|
|
2888
|
+
const media = kind === "image" ? client.appAssetLibraryImages : client.appAssetLibraryVideos;
|
|
2889
|
+
const filter = { filter: { referenceName } };
|
|
2890
|
+
const existing = kind === "image" ? await client.appAssetLibraries.listImages(libraryId, filter) : await client.appAssetLibraries.listVideos(libraryId, filter);
|
|
2891
|
+
const reusable = existing.find(
|
|
2892
|
+
(a) => a.attributes.category === category && a.attributes.state && READY_STATES.includes(a.attributes.state)
|
|
2893
|
+
);
|
|
2894
|
+
if (reusable) {
|
|
2895
|
+
return { kind, id: reusable.id, state: reusable.attributes.state, reused: true };
|
|
2896
|
+
}
|
|
2897
|
+
const reserved = kind === "image" ? await client.appAssetLibraryImages.create({ libraryId, fileName, fileSize: data.length, category, referenceName }) : await client.appAssetLibraryVideos.create({
|
|
2898
|
+
libraryId,
|
|
2899
|
+
fileName,
|
|
2900
|
+
fileSize: data.length,
|
|
2901
|
+
category,
|
|
2902
|
+
referenceName,
|
|
2903
|
+
previewFrameTimeCode: options.previewFrameTimeCode
|
|
2904
|
+
});
|
|
2905
|
+
await runUploadOperations(reserved.attributes.uploadOperations ?? [], data);
|
|
2906
|
+
await media.update(reserved.id, { uploaded: true });
|
|
2907
|
+
options.log?.(`uploaded ${fileName} (${kind}, ${data.length} bytes)`);
|
|
2908
|
+
const state = await waitForAssetReady(
|
|
2909
|
+
() => media.get(reserved.id),
|
|
2910
|
+
options.pollIntervalMs ?? 3e3,
|
|
2911
|
+
options.timeoutMs ?? (kind === "video" ? 30 * 6e4 : 5 * 6e4)
|
|
2912
|
+
);
|
|
2913
|
+
return { kind, id: reserved.id, state, reused: false };
|
|
2914
|
+
}
|
|
2915
|
+
async function waitForAssetReady(get, intervalMs, timeoutMs) {
|
|
2916
|
+
const deadline = Date.now() + timeoutMs;
|
|
2917
|
+
for (; ; ) {
|
|
2918
|
+
const asset = await get();
|
|
2919
|
+
const state = asset.attributes.state;
|
|
2920
|
+
if (state === "FAILED") {
|
|
2921
|
+
const why = (asset.attributes.stateDetails ?? []).map((d) => d.message ?? d.code).filter(Boolean).join("; ");
|
|
2922
|
+
throw new Error(`Asset processing failed${why ? `: ${why}` : ""}`);
|
|
2923
|
+
}
|
|
2924
|
+
if (state && READY_STATES.includes(state)) return state;
|
|
2925
|
+
if (Date.now() > deadline) throw new Error(`Asset still ${state ?? "unknown"} after ${timeoutMs / 1e3}s`);
|
|
2926
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
async function listFiles(dir) {
|
|
2930
|
+
return (await promises.readdir(dir)).filter((f) => !f.startsWith(".")).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }));
|
|
2931
|
+
}
|
|
2932
|
+
async function isDir(path) {
|
|
2933
|
+
return (await promises.stat(path)).isDirectory();
|
|
2934
|
+
}
|
|
2935
|
+
async function planLocale(localeDir, refData, feature, folders, warn) {
|
|
2936
|
+
const planned = [];
|
|
2937
|
+
for (const [folder, placementType] of Object.entries(folders)) {
|
|
2938
|
+
const typeDir = path.join(localeDir, folder);
|
|
2939
|
+
if (!fs.existsSync(typeDir)) continue;
|
|
2940
|
+
const known = new Set(groupsForFeature(refData, feature, placementType));
|
|
2941
|
+
const entries = [];
|
|
2942
|
+
for (const entry of await listFiles(typeDir)) {
|
|
2943
|
+
const full = path.join(typeDir, entry);
|
|
2944
|
+
if (await isDir(full)) {
|
|
2945
|
+
if (!known.has(entry)) {
|
|
2946
|
+
warn(`${path.relative(localeDir, full)}: "${entry}" is not a ${placementType} placement group for ${feature} \u2014 skipped`);
|
|
2947
|
+
continue;
|
|
2948
|
+
}
|
|
2949
|
+
for (const f of await listFiles(full)) entries.push({ filePath: path.join(full, f), group: entry });
|
|
2950
|
+
} else {
|
|
2951
|
+
entries.push({ filePath: full });
|
|
2952
|
+
}
|
|
2953
|
+
}
|
|
2954
|
+
for (const { filePath, group } of entries) {
|
|
2955
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
2956
|
+
const isVideo = VIDEO_EXTENSIONS.includes(ext);
|
|
2957
|
+
if (!isVideo && !IMAGE_EXTENSIONS.includes(ext)) continue;
|
|
2958
|
+
const needsVideo = placementType === "APP_PREVIEW";
|
|
2959
|
+
const needsImage = placementType === "APP_SCREENSHOT" || placementType === "IMESSAGE_APP_SCREENSHOT";
|
|
2960
|
+
if (needsVideo && !isVideo || needsImage && isVideo) {
|
|
2961
|
+
warn(`${path.relative(localeDir, filePath)}: ${isVideo ? "video" : "image"} cannot be a ${placementType} \u2014 skipped`);
|
|
2962
|
+
continue;
|
|
2963
|
+
}
|
|
2964
|
+
if (group) {
|
|
2965
|
+
planned.push({ filePath, placementType, groups: [group] });
|
|
2966
|
+
continue;
|
|
2967
|
+
}
|
|
2968
|
+
if (isVideo) {
|
|
2969
|
+
if (known.size === 1) planned.push({ filePath, placementType, groups: [...known] });
|
|
2970
|
+
else warn(`${path.relative(localeDir, filePath)}: put videos in a placement-group folder (one of ${[...known].join(", ")}) \u2014 skipped`);
|
|
2971
|
+
continue;
|
|
2972
|
+
}
|
|
2973
|
+
const size = readImageSize(await promises.readFile(filePath));
|
|
2974
|
+
if (!size) {
|
|
2975
|
+
warn(`${path.relative(localeDir, filePath)}: could not read image size \u2014 skipped`);
|
|
2976
|
+
continue;
|
|
2977
|
+
}
|
|
2978
|
+
const { groups, spec } = resolveImageGroups(refData, feature, placementType, size);
|
|
2979
|
+
if (!groups.length) {
|
|
2980
|
+
warn(`${path.relative(localeDir, filePath)}: ${size.width}\xD7${size.height} matches no ${placementType} spec \u2014 skipped`);
|
|
2981
|
+
continue;
|
|
2982
|
+
}
|
|
2983
|
+
if (size.alpha && spec && spec.alphaAllowed === false) {
|
|
2984
|
+
warn(`${path.relative(localeDir, filePath)}: has an alpha channel, which Apple rejects for this spec`);
|
|
2985
|
+
}
|
|
2986
|
+
planned.push({ filePath, placementType, groups: groups.slice(0, 1) });
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
return planned;
|
|
2990
|
+
}
|
|
2991
|
+
async function uploadAssetLibrary(options) {
|
|
2992
|
+
const { client } = options;
|
|
2993
|
+
const log = options.log ?? (() => {
|
|
2994
|
+
});
|
|
2995
|
+
const feature = "APP_STORE_VERSIONS";
|
|
2996
|
+
const result = { uploaded: 0, reused: 0, placed: 0, removed: 0, warnings: [] };
|
|
2997
|
+
const warn = (line) => {
|
|
2998
|
+
result.warnings.push(line);
|
|
2999
|
+
log(`warning: ${line}`);
|
|
3000
|
+
};
|
|
3001
|
+
const [library, refData, localizations] = await Promise.all([
|
|
3002
|
+
client.appAssetLibraries.getForApp(options.appId),
|
|
3003
|
+
client.appAssetLibraries.refData({ features: [feature] }),
|
|
3004
|
+
client.appStoreVersions.getLocalizations(options.versionId)
|
|
3005
|
+
]);
|
|
3006
|
+
const folders = { ...options.folders ?? ASSET_FOLDERS };
|
|
3007
|
+
if (options.rootPlacementType) folders["."] = options.rootPlacementType;
|
|
3008
|
+
const uploads = /* @__PURE__ */ new Map();
|
|
3009
|
+
const uploadOnce = (filePath, placementType) => {
|
|
3010
|
+
const key = `${filePath}|${categoryFor(placementType)}`;
|
|
3011
|
+
if (!uploads.has(key)) {
|
|
3012
|
+
uploads.set(
|
|
3013
|
+
key,
|
|
3014
|
+
uploadLibraryAsset({
|
|
3015
|
+
client,
|
|
3016
|
+
libraryId: library.id,
|
|
3017
|
+
filePath,
|
|
3018
|
+
category: categoryFor(placementType),
|
|
3019
|
+
referenceName: path.relative(options.assetsPath, filePath),
|
|
3020
|
+
log
|
|
3021
|
+
}).then((a) => {
|
|
3022
|
+
if (a.reused) result.reused++;
|
|
3023
|
+
else result.uploaded++;
|
|
3024
|
+
return a;
|
|
3025
|
+
})
|
|
3026
|
+
);
|
|
3027
|
+
}
|
|
3028
|
+
return uploads.get(key);
|
|
3029
|
+
};
|
|
3030
|
+
for (const loc of localizations) {
|
|
3031
|
+
const localeDir = path.join(options.assetsPath, loc.attributes.locale);
|
|
3032
|
+
if (!fs.existsSync(localeDir)) continue;
|
|
3033
|
+
const target = { kind: "appStoreVersionLocalization", id: loc.id };
|
|
3034
|
+
const plan = await planLocale(localeDir, refData, feature, folders, warn);
|
|
3035
|
+
const slots = /* @__PURE__ */ new Map();
|
|
3036
|
+
for (const p of plan) {
|
|
3037
|
+
for (const group of p.groups) {
|
|
3038
|
+
const key = `${p.placementType}|${group}`;
|
|
3039
|
+
if (!slots.has(key)) slots.set(key, { placementType: p.placementType, group, files: [] });
|
|
3040
|
+
slots.get(key).files.push(p.filePath);
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
3043
|
+
for (const slot of slots.values()) {
|
|
3044
|
+
const where = `${loc.attributes.locale} ${slot.placementType} ${slot.group}`;
|
|
3045
|
+
const max = maxCountFor(refData, feature, slot.placementType, slot.group);
|
|
3046
|
+
if (max !== void 0 && slot.files.length > max) {
|
|
3047
|
+
warn(`${where}: ${slot.files.length} files but Apple allows ${max} \u2014 keeping the first ${max}`);
|
|
3048
|
+
slot.files = slot.files.slice(0, max);
|
|
3049
|
+
}
|
|
3050
|
+
const existing = await client.appAssetLibraryPlacements.listForTarget(target, {
|
|
3051
|
+
placementType: slot.placementType,
|
|
3052
|
+
placementGroup: slot.group,
|
|
3053
|
+
include: "image,video"
|
|
3054
|
+
});
|
|
3055
|
+
if (options.overwrite) {
|
|
3056
|
+
for (const p of existing) {
|
|
3057
|
+
await client.appAssetLibraryPlacements.delete(p.id);
|
|
3058
|
+
result.removed++;
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
const kept = options.overwrite ? [] : existing;
|
|
3062
|
+
const keptAssetIds = new Set(kept.map((p) => placementAssetId(p)).filter(Boolean));
|
|
3063
|
+
const ours = [];
|
|
3064
|
+
for (const filePath of slot.files) {
|
|
3065
|
+
const asset = await uploadOnce(filePath, slot.placementType);
|
|
3066
|
+
if (keptAssetIds.has(asset.id)) continue;
|
|
3067
|
+
const placement = await client.appAssetLibraryPlacements.create({
|
|
3068
|
+
placementType: slot.placementType,
|
|
3069
|
+
placementGroup: slot.group,
|
|
3070
|
+
...asset.kind === "image" ? { imageId: asset.id } : { videoId: asset.id },
|
|
3071
|
+
target
|
|
3072
|
+
});
|
|
3073
|
+
ours.push(placement.id);
|
|
3074
|
+
result.placed++;
|
|
3075
|
+
}
|
|
3076
|
+
log(`${where}: placed ${ours.length}${kept.length ? `, kept ${kept.length}` : ""}`);
|
|
3077
|
+
const orderable = slot.placementType === "APP_SCREENSHOT" || slot.placementType === "APP_PREVIEW" || slot.placementType === "IMESSAGE_APP_SCREENSHOT";
|
|
3078
|
+
if (orderable && ours.length + kept.length > 1) {
|
|
3079
|
+
await client.appAssetLibraryPlacements.order({
|
|
3080
|
+
placementGroup: slot.group,
|
|
3081
|
+
placementIds: [...ours, ...kept.map((p) => p.id)],
|
|
3082
|
+
target
|
|
3083
|
+
});
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
return result;
|
|
3088
|
+
}
|
|
3089
|
+
function placementAssetId(p) {
|
|
3090
|
+
for (const rel of ["image", "video"]) {
|
|
3091
|
+
const data = p.relationships?.[rel]?.data;
|
|
3092
|
+
if (data?.id) return data.id;
|
|
3093
|
+
}
|
|
3094
|
+
return void 0;
|
|
3095
|
+
}
|
|
3096
|
+
var ASSET_FOLDERS, IMAGE_EXTENSIONS, VIDEO_EXTENSIONS, READY_STATES;
|
|
3097
|
+
var init_assets = __esm({
|
|
3098
|
+
"src/deliver/assets.ts"() {
|
|
3099
|
+
ASSET_FOLDERS = {
|
|
3100
|
+
screenshots: "APP_SCREENSHOT",
|
|
3101
|
+
imessage_screenshots: "IMESSAGE_APP_SCREENSHOT",
|
|
3102
|
+
previews: "APP_PREVIEW",
|
|
3103
|
+
header: "PRODUCT_PAGE_HEADER_ASSET",
|
|
3104
|
+
search_results: "APP_STORE_SEARCH_RESULTS_ASSET",
|
|
3105
|
+
event_card: "EVENT_CARD_ASSET",
|
|
3106
|
+
event_details: "EVENT_DETAILS_PAGE_ASSET"
|
|
3107
|
+
};
|
|
3108
|
+
IMAGE_EXTENSIONS = [".png", ".jpg", ".jpeg"];
|
|
3109
|
+
VIDEO_EXTENSIONS = [".mp4", ".mov", ".m4v"];
|
|
3110
|
+
READY_STATES = [
|
|
3111
|
+
"COMPLETE",
|
|
3112
|
+
"PREPARE_FOR_SUBMISSION",
|
|
3113
|
+
"READY_FOR_REVIEW",
|
|
3114
|
+
"WAITING_FOR_REVIEW",
|
|
3115
|
+
"IN_REVIEW",
|
|
3116
|
+
"ACCEPTED",
|
|
3117
|
+
"APPROVED"
|
|
3118
|
+
];
|
|
3119
|
+
}
|
|
3120
|
+
});
|
|
2780
3121
|
|
|
2781
3122
|
// src/xcode/upload.ts
|
|
2782
3123
|
var upload_exports = {};
|
|
@@ -2842,8 +3183,14 @@ var init_upload = __esm({
|
|
|
2842
3183
|
// src/deliver/index.ts
|
|
2843
3184
|
var deliver_exports = {};
|
|
2844
3185
|
__export(deliver_exports, {
|
|
3186
|
+
ASSET_FOLDERS: () => ASSET_FOLDERS,
|
|
3187
|
+
categoryFor: () => categoryFor,
|
|
2845
3188
|
loadMetadata: () => loadMetadata,
|
|
3189
|
+
readImageSize: () => readImageSize,
|
|
3190
|
+
resolveImageGroups: () => resolveImageGroups,
|
|
2846
3191
|
submitForReview: () => submitForReview,
|
|
3192
|
+
uploadAssetLibrary: () => uploadAssetLibrary,
|
|
3193
|
+
uploadLibraryAsset: () => uploadLibraryAsset,
|
|
2847
3194
|
uploadMetadata: () => uploadMetadata,
|
|
2848
3195
|
uploadScreenshots: () => uploadScreenshots,
|
|
2849
3196
|
uploadToAppStore: () => uploadToAppStore
|
|
@@ -2882,11 +3229,36 @@ async function uploadToAppStore(options) {
|
|
|
2882
3229
|
copyright: options.copyright
|
|
2883
3230
|
});
|
|
2884
3231
|
}
|
|
3232
|
+
const log = (line) => options.onOutput?.(`${line}
|
|
3233
|
+
`);
|
|
2885
3234
|
if (!options.skipScreenshots && options.screenshotsPath && fs.existsSync(options.screenshotsPath)) {
|
|
2886
|
-
|
|
3235
|
+
if (options.useAssetLibrary) {
|
|
3236
|
+
await uploadAssetLibrary({
|
|
3237
|
+
client: options.client,
|
|
3238
|
+
appId: app.id,
|
|
3239
|
+
versionId: version.id,
|
|
3240
|
+
assetsPath: options.screenshotsPath,
|
|
3241
|
+
folders: {},
|
|
3242
|
+
rootPlacementType: "APP_SCREENSHOT",
|
|
3243
|
+
overwrite: options.overwriteAssets,
|
|
3244
|
+
log
|
|
3245
|
+
});
|
|
3246
|
+
} else {
|
|
3247
|
+
await uploadScreenshots({
|
|
3248
|
+
client: options.client,
|
|
3249
|
+
versionId: version.id,
|
|
3250
|
+
screenshotsPath: options.screenshotsPath
|
|
3251
|
+
});
|
|
3252
|
+
}
|
|
3253
|
+
}
|
|
3254
|
+
if (!options.skipAssets && options.assetsPath && fs.existsSync(options.assetsPath)) {
|
|
3255
|
+
await uploadAssetLibrary({
|
|
2887
3256
|
client: options.client,
|
|
3257
|
+
appId: app.id,
|
|
2888
3258
|
versionId: version.id,
|
|
2889
|
-
|
|
3259
|
+
assetsPath: options.assetsPath,
|
|
3260
|
+
overwrite: options.overwriteAssets,
|
|
3261
|
+
log
|
|
2890
3262
|
});
|
|
2891
3263
|
}
|
|
2892
3264
|
if (!options.skipBinaryUpload) {
|
|
@@ -2971,17 +3343,7 @@ async function uploadScreenshots(options) {
|
|
|
2971
3343
|
fileName: file,
|
|
2972
3344
|
fileSize: fileData.length
|
|
2973
3345
|
});
|
|
2974
|
-
|
|
2975
|
-
if (ops) {
|
|
2976
|
-
for (const op of ops) {
|
|
2977
|
-
const chunk = fileData.subarray(op.offset, op.offset + op.length);
|
|
2978
|
-
const headers = {};
|
|
2979
|
-
for (const h of op.requestHeaders) {
|
|
2980
|
-
headers[h.name] = h.value;
|
|
2981
|
-
}
|
|
2982
|
-
await fetch(op.url, { method: op.method, headers, body: chunk });
|
|
2983
|
-
}
|
|
2984
|
-
}
|
|
3346
|
+
await runUploadOperations(reserved.attributes.uploadOperations ?? [], fileData);
|
|
2985
3347
|
await options.client.appScreenshots.commitScreenshot(reserved.id, {
|
|
2986
3348
|
sourceFileChecksum: checksum,
|
|
2987
3349
|
uploaded: true
|
|
@@ -3001,7 +3363,9 @@ function detectDisplayType(filename) {
|
|
|
3001
3363
|
var init_deliver = __esm({
|
|
3002
3364
|
"src/deliver/index.ts"() {
|
|
3003
3365
|
init_metadata();
|
|
3366
|
+
init_assets();
|
|
3004
3367
|
init_metadata();
|
|
3368
|
+
init_assets();
|
|
3005
3369
|
}
|
|
3006
3370
|
});
|
|
3007
3371
|
|
|
@@ -3010,6 +3374,7 @@ var produce_exports = {};
|
|
|
3010
3374
|
__export(produce_exports, {
|
|
3011
3375
|
SERVICE_NAME_TO_CAPABILITY: () => SERVICE_NAME_TO_CAPABILITY,
|
|
3012
3376
|
addCapability: () => addCapability,
|
|
3377
|
+
associateAppGroups: () => associateAppGroups,
|
|
3013
3378
|
buildCapabilitySettings: () => buildCapabilitySettings,
|
|
3014
3379
|
createApp: () => createApp,
|
|
3015
3380
|
enableServices: () => enableServices,
|
|
@@ -3104,6 +3469,30 @@ async function enableServices(options) {
|
|
|
3104
3469
|
});
|
|
3105
3470
|
}
|
|
3106
3471
|
}
|
|
3472
|
+
async function associateAppGroups(options) {
|
|
3473
|
+
if (!options.appGroups?.length) return;
|
|
3474
|
+
await options.portalClient.enableAppGroups(options.bundleIdId).catch(() => {
|
|
3475
|
+
});
|
|
3476
|
+
const existing = await options.portalClient.listAppGroups();
|
|
3477
|
+
const appGroupIds = [];
|
|
3478
|
+
for (const group of options.appGroups) {
|
|
3479
|
+
let record = existing.find((ag) => ag.identifier === group.identifier);
|
|
3480
|
+
if (!record) {
|
|
3481
|
+
record = await options.portalClient.createAppGroup({
|
|
3482
|
+
name: group.name,
|
|
3483
|
+
identifier: group.identifier
|
|
3484
|
+
});
|
|
3485
|
+
}
|
|
3486
|
+
const id = record?.applicationGroup ?? record?.applicationGroupId ?? record?.appGroupId ?? record?.id;
|
|
3487
|
+
if (id) appGroupIds.push(id);
|
|
3488
|
+
}
|
|
3489
|
+
if (appGroupIds.length) {
|
|
3490
|
+
await options.portalClient.assignAppGroups({
|
|
3491
|
+
appIdId: options.bundleIdId,
|
|
3492
|
+
appGroupIds
|
|
3493
|
+
});
|
|
3494
|
+
}
|
|
3495
|
+
}
|
|
3107
3496
|
async function createApp(options) {
|
|
3108
3497
|
const existing = await options.client.apps.findByBundleId(options.bundleIdentifier);
|
|
3109
3498
|
if (existing) return existing;
|
|
@@ -3122,6 +3511,13 @@ async function createApp(options) {
|
|
|
3122
3511
|
});
|
|
3123
3512
|
}
|
|
3124
3513
|
}
|
|
3514
|
+
if (options.appGroups?.length && options.portalClient) {
|
|
3515
|
+
await associateAppGroups({
|
|
3516
|
+
portalClient: options.portalClient,
|
|
3517
|
+
bundleIdId: bundleId.id,
|
|
3518
|
+
appGroups: options.appGroups
|
|
3519
|
+
});
|
|
3520
|
+
}
|
|
3125
3521
|
return options.client.apps.create({
|
|
3126
3522
|
bundleIdentifier: options.bundleIdentifier,
|
|
3127
3523
|
bundleIdId: bundleId.id,
|
|
@@ -3931,10 +4327,10 @@ async function parseTestResults(options) {
|
|
|
3931
4327
|
} else if (inputPath.endsWith(".plist")) {
|
|
3932
4328
|
testSuites = parsePlistResults(inputPath);
|
|
3933
4329
|
} else {
|
|
3934
|
-
const
|
|
4330
|
+
const stat7 = await import('fs').then(
|
|
3935
4331
|
(fs) => fs.statSync(inputPath)
|
|
3936
4332
|
);
|
|
3937
|
-
if (
|
|
4333
|
+
if (stat7.isDirectory()) {
|
|
3938
4334
|
requireMacOS("xcresult parsing");
|
|
3939
4335
|
testSuites = await parseXCResult(inputPath);
|
|
3940
4336
|
} else {
|
|
@@ -6111,9 +6507,22 @@ var init_pre_release_versions = __esm({
|
|
|
6111
6507
|
});
|
|
6112
6508
|
|
|
6113
6509
|
// src/resources/review-submission-items.ts
|
|
6114
|
-
var ReviewSubmissionItemsResource;
|
|
6510
|
+
var ITEM_RELATIONSHIPS, ReviewSubmissionItemsResource;
|
|
6115
6511
|
var init_review_submission_items = __esm({
|
|
6116
6512
|
"src/resources/review-submission-items.ts"() {
|
|
6513
|
+
ITEM_RELATIONSHIPS = {
|
|
6514
|
+
appStoreVersionId: ["appStoreVersion", "appStoreVersions"],
|
|
6515
|
+
appCustomProductPageVersionId: ["appCustomProductPageVersion", "appCustomProductPageVersions"],
|
|
6516
|
+
appStoreVersionExperimentId: ["appStoreVersionExperiment", "appStoreVersionExperiments"],
|
|
6517
|
+
appStoreVersionExperimentV2Id: ["appStoreVersionExperimentV2", "appStoreVersionExperiments"],
|
|
6518
|
+
appEventId: ["appEvent", "appEvents"],
|
|
6519
|
+
appAssetLibraryImageId: ["appAssetLibraryImage", "appAssetLibraryImages"],
|
|
6520
|
+
appAssetLibraryVideoId: ["appAssetLibraryVideo", "appAssetLibraryVideos"],
|
|
6521
|
+
backgroundAssetVersionId: ["backgroundAssetVersion", "backgroundAssetVersions"],
|
|
6522
|
+
inAppPurchaseVersionId: ["inAppPurchaseVersion", "inAppPurchaseVersions"],
|
|
6523
|
+
subscriptionVersionId: ["subscriptionVersion", "subscriptionVersions"],
|
|
6524
|
+
subscriptionGroupVersionId: ["subscriptionGroupVersion", "subscriptionGroupVersions"]
|
|
6525
|
+
};
|
|
6117
6526
|
ReviewSubmissionItemsResource = class {
|
|
6118
6527
|
constructor(http) {
|
|
6119
6528
|
this.http = http;
|
|
@@ -6135,32 +6544,20 @@ var init_review_submission_items = __esm({
|
|
|
6135
6544
|
);
|
|
6136
6545
|
return response.data;
|
|
6137
6546
|
}
|
|
6547
|
+
/**
|
|
6548
|
+
* Add one item to a review submission. Besides versions, asset library
|
|
6549
|
+
* images and videos can be submitted on their own (ASC API 4.5.1) so
|
|
6550
|
+
* creative assets are approved ahead of a campaign.
|
|
6551
|
+
*/
|
|
6138
6552
|
async create(params) {
|
|
6139
6553
|
const relationships = {
|
|
6140
6554
|
reviewSubmission: {
|
|
6141
6555
|
data: { type: "reviewSubmissions", id: params.reviewSubmissionId }
|
|
6142
6556
|
}
|
|
6143
6557
|
};
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
};
|
|
6148
|
-
}
|
|
6149
|
-
if (params.appCustomProductPageVersionId) {
|
|
6150
|
-
relationships.appCustomProductPageVersion = {
|
|
6151
|
-
data: {
|
|
6152
|
-
type: "appCustomProductPageVersions",
|
|
6153
|
-
id: params.appCustomProductPageVersionId
|
|
6154
|
-
}
|
|
6155
|
-
};
|
|
6156
|
-
}
|
|
6157
|
-
if (params.appStoreVersionExperimentId) {
|
|
6158
|
-
relationships.appStoreVersionExperiment = {
|
|
6159
|
-
data: {
|
|
6160
|
-
type: "appStoreVersionExperiments",
|
|
6161
|
-
id: params.appStoreVersionExperimentId
|
|
6162
|
-
}
|
|
6163
|
-
};
|
|
6558
|
+
for (const [param, [relationship, type]] of Object.entries(ITEM_RELATIONSHIPS)) {
|
|
6559
|
+
const id = params[param];
|
|
6560
|
+
if (id) relationships[relationship] = { data: { type, id } };
|
|
6164
6561
|
}
|
|
6165
6562
|
const response = await this.http.post(
|
|
6166
6563
|
"/v1/reviewSubmissionItems",
|
|
@@ -6391,6 +6788,288 @@ var init_xcode_metrics = __esm({
|
|
|
6391
6788
|
}
|
|
6392
6789
|
});
|
|
6393
6790
|
|
|
6791
|
+
// src/resources/app-asset-libraries.ts
|
|
6792
|
+
function mergeRefData(parts) {
|
|
6793
|
+
const merged = {
|
|
6794
|
+
features: [],
|
|
6795
|
+
placementProfileGroups: [],
|
|
6796
|
+
imageSpecs: [],
|
|
6797
|
+
videoSpecs: [],
|
|
6798
|
+
placementTypes: [],
|
|
6799
|
+
displayClasses: []
|
|
6800
|
+
};
|
|
6801
|
+
for (const part of parts) {
|
|
6802
|
+
for (const key of Object.keys(merged)) {
|
|
6803
|
+
merged[key].push(...part[key] ?? []);
|
|
6804
|
+
}
|
|
6805
|
+
}
|
|
6806
|
+
return merged;
|
|
6807
|
+
}
|
|
6808
|
+
var AppAssetLibrariesResource;
|
|
6809
|
+
var init_app_asset_libraries = __esm({
|
|
6810
|
+
"src/resources/app-asset-libraries.ts"() {
|
|
6811
|
+
AppAssetLibrariesResource = class {
|
|
6812
|
+
constructor(http) {
|
|
6813
|
+
this.http = http;
|
|
6814
|
+
}
|
|
6815
|
+
http;
|
|
6816
|
+
async getForApp(appId) {
|
|
6817
|
+
const response = await this.http.get(
|
|
6818
|
+
`/v1/apps/${appId}/assetLibrary`
|
|
6819
|
+
);
|
|
6820
|
+
return response.data;
|
|
6821
|
+
}
|
|
6822
|
+
async listImages(libraryId, params = {}) {
|
|
6823
|
+
return this.http.all(`/v1/appAssetLibraries/${libraryId}/images`, params);
|
|
6824
|
+
}
|
|
6825
|
+
async listVideos(libraryId, params = {}) {
|
|
6826
|
+
return this.http.all(`/v1/appAssetLibraries/${libraryId}/videos`, params);
|
|
6827
|
+
}
|
|
6828
|
+
/**
|
|
6829
|
+
* Specs, placement groups and limits. Apple adds placement groups (new
|
|
6830
|
+
* devices) without an API version bump, so read them from here rather than
|
|
6831
|
+
* hard-coding display types.
|
|
6832
|
+
*/
|
|
6833
|
+
async refData(params = {}) {
|
|
6834
|
+
const filter = {};
|
|
6835
|
+
if (params.features?.length) filter.features = params.features;
|
|
6836
|
+
if (params.placementTypes?.length) filter.placementTypes = params.placementTypes;
|
|
6837
|
+
if (params.placementProfileGroups?.length) filter.placementProfileGroups = params.placementProfileGroups;
|
|
6838
|
+
const data = await this.http.all("/v1/appAssetLibraryRefData", { filter });
|
|
6839
|
+
return mergeRefData(data.map((d) => d.attributes));
|
|
6840
|
+
}
|
|
6841
|
+
};
|
|
6842
|
+
}
|
|
6843
|
+
});
|
|
6844
|
+
|
|
6845
|
+
// src/resources/app-asset-library-assets.ts
|
|
6846
|
+
var AssetLibraryMediaResource, AppAssetLibraryImagesResource, AppAssetLibraryVideosResource;
|
|
6847
|
+
var init_app_asset_library_assets = __esm({
|
|
6848
|
+
"src/resources/app-asset-library-assets.ts"() {
|
|
6849
|
+
AssetLibraryMediaResource = class {
|
|
6850
|
+
constructor(http) {
|
|
6851
|
+
this.http = http;
|
|
6852
|
+
}
|
|
6853
|
+
http;
|
|
6854
|
+
async get(id) {
|
|
6855
|
+
const response = await this.http.get(`/v1/${this.type}/${id}`);
|
|
6856
|
+
return response.data;
|
|
6857
|
+
}
|
|
6858
|
+
async reserve(params, extra = {}) {
|
|
6859
|
+
const { libraryId, ...attributes } = params;
|
|
6860
|
+
const response = await this.http.post(`/v1/${this.type}`, {
|
|
6861
|
+
data: {
|
|
6862
|
+
type: this.type,
|
|
6863
|
+
attributes: { ...attributes, ...extra },
|
|
6864
|
+
relationships: {
|
|
6865
|
+
assetLibrary: { data: { type: "appAssetLibraries", id: libraryId } }
|
|
6866
|
+
}
|
|
6867
|
+
}
|
|
6868
|
+
});
|
|
6869
|
+
return response.data;
|
|
6870
|
+
}
|
|
6871
|
+
async patch(id, attributes) {
|
|
6872
|
+
const response = await this.http.patch(`/v1/${this.type}/${id}`, {
|
|
6873
|
+
data: { type: this.type, id, attributes }
|
|
6874
|
+
});
|
|
6875
|
+
return response.data;
|
|
6876
|
+
}
|
|
6877
|
+
/** Fails with 409 STATE_ERROR.ASSET_HAS_PLACEMENTS while the asset is still placed. */
|
|
6878
|
+
async delete(id) {
|
|
6879
|
+
await this.http.delete(`/v1/${this.type}/${id}`);
|
|
6880
|
+
}
|
|
6881
|
+
async listPlacements(id, params = {}) {
|
|
6882
|
+
return this.http.all(`/v1/${this.type}/${id}/placements`, params);
|
|
6883
|
+
}
|
|
6884
|
+
};
|
|
6885
|
+
AppAssetLibraryImagesResource = class extends AssetLibraryMediaResource {
|
|
6886
|
+
type = "appAssetLibraryImages";
|
|
6887
|
+
async create(params) {
|
|
6888
|
+
return this.reserve(params);
|
|
6889
|
+
}
|
|
6890
|
+
async update(id, attributes) {
|
|
6891
|
+
return this.patch(id, { ...attributes });
|
|
6892
|
+
}
|
|
6893
|
+
};
|
|
6894
|
+
AppAssetLibraryVideosResource = class extends AssetLibraryMediaResource {
|
|
6895
|
+
type = "appAssetLibraryVideos";
|
|
6896
|
+
async create(params) {
|
|
6897
|
+
const { previewFrameTimeCode, ...rest } = params;
|
|
6898
|
+
return this.reserve(rest, previewFrameTimeCode ? { previewFrameTimeCode } : {});
|
|
6899
|
+
}
|
|
6900
|
+
async update(id, attributes) {
|
|
6901
|
+
return this.patch(id, { ...attributes });
|
|
6902
|
+
}
|
|
6903
|
+
};
|
|
6904
|
+
}
|
|
6905
|
+
});
|
|
6906
|
+
|
|
6907
|
+
// src/resources/app-asset-library-placements.ts
|
|
6908
|
+
function targetRelationship(target) {
|
|
6909
|
+
return { [target.kind]: { data: { type: TARGET_TYPES[target.kind], id: target.id } } };
|
|
6910
|
+
}
|
|
6911
|
+
var TARGET_TYPES, AppAssetLibraryPlacementsResource;
|
|
6912
|
+
var init_app_asset_library_placements = __esm({
|
|
6913
|
+
"src/resources/app-asset-library-placements.ts"() {
|
|
6914
|
+
TARGET_TYPES = {
|
|
6915
|
+
appStoreVersionLocalization: "appStoreVersionLocalizations",
|
|
6916
|
+
appCustomProductPageLocalization: "appCustomProductPageLocalizations",
|
|
6917
|
+
appEventLocalization: "appEventLocalizations",
|
|
6918
|
+
appStoreVersionExperimentTreatmentLocalization: "appStoreVersionExperimentTreatmentLocalizations"
|
|
6919
|
+
};
|
|
6920
|
+
AppAssetLibraryPlacementsResource = class {
|
|
6921
|
+
constructor(http) {
|
|
6922
|
+
this.http = http;
|
|
6923
|
+
}
|
|
6924
|
+
http;
|
|
6925
|
+
/**
|
|
6926
|
+
* Place an image or video on one localization. Placements cannot be edited —
|
|
6927
|
+
* delete and recreate. The parent version/page must be editable.
|
|
6928
|
+
*/
|
|
6929
|
+
async create(params) {
|
|
6930
|
+
if (!params.imageId === !params.videoId) {
|
|
6931
|
+
throw new Error("A placement needs exactly one of imageId or videoId");
|
|
6932
|
+
}
|
|
6933
|
+
const media = params.imageId ? { image: { data: { type: "appAssetLibraryImages", id: params.imageId } } } : { video: { data: { type: "appAssetLibraryVideos", id: params.videoId } } };
|
|
6934
|
+
const response = await this.http.post(
|
|
6935
|
+
"/v1/appAssetLibraryPlacements",
|
|
6936
|
+
{
|
|
6937
|
+
data: {
|
|
6938
|
+
type: "appAssetLibraryPlacements",
|
|
6939
|
+
attributes: { placementType: params.placementType, placementGroup: params.placementGroup },
|
|
6940
|
+
relationships: { ...media, ...targetRelationship(params.target) }
|
|
6941
|
+
}
|
|
6942
|
+
}
|
|
6943
|
+
);
|
|
6944
|
+
return response.data;
|
|
6945
|
+
}
|
|
6946
|
+
async get(id, params = {}) {
|
|
6947
|
+
const response = await this.http.get(
|
|
6948
|
+
`/v1/appAssetLibraryPlacements/${id}`,
|
|
6949
|
+
params.include ? { include: params.include } : void 0
|
|
6950
|
+
);
|
|
6951
|
+
return response.data;
|
|
6952
|
+
}
|
|
6953
|
+
async delete(id) {
|
|
6954
|
+
await this.http.delete(`/v1/appAssetLibraryPlacements/${id}`);
|
|
6955
|
+
}
|
|
6956
|
+
/** Placements on one localization, in display order by default. */
|
|
6957
|
+
async listForTarget(target, params = {}) {
|
|
6958
|
+
const filter = {};
|
|
6959
|
+
if (params.placementType) filter.placementType = params.placementType;
|
|
6960
|
+
if (params.placementGroup) filter.placementGroup = params.placementGroup;
|
|
6961
|
+
const listParams = { filter, include: params.include, sort: "placementGroupPosition" };
|
|
6962
|
+
return this.http.all(
|
|
6963
|
+
`/v1/${TARGET_TYPES[target.kind]}/${target.id}/placements`,
|
|
6964
|
+
listParams
|
|
6965
|
+
);
|
|
6966
|
+
}
|
|
6967
|
+
/** Set the display order of a placement group. In-app event localizations cannot be ordered. */
|
|
6968
|
+
async order(params) {
|
|
6969
|
+
if (params.target.kind === "appEventLocalization") {
|
|
6970
|
+
throw new Error("In-app event placements cannot be ordered");
|
|
6971
|
+
}
|
|
6972
|
+
await this.http.post("/v1/appAssetLibraryPlacementOrderingRequests", {
|
|
6973
|
+
data: {
|
|
6974
|
+
type: "appAssetLibraryPlacementOrderingRequests",
|
|
6975
|
+
attributes: { placementGroup: params.placementGroup },
|
|
6976
|
+
relationships: {
|
|
6977
|
+
orderedPlacements: {
|
|
6978
|
+
data: params.placementIds.map((id) => ({ type: "appAssetLibraryPlacements", id }))
|
|
6979
|
+
},
|
|
6980
|
+
...targetRelationship(params.target)
|
|
6981
|
+
}
|
|
6982
|
+
}
|
|
6983
|
+
});
|
|
6984
|
+
}
|
|
6985
|
+
};
|
|
6986
|
+
}
|
|
6987
|
+
});
|
|
6988
|
+
|
|
6989
|
+
// src/client/session-store.ts
|
|
6990
|
+
var session_store_exports = {};
|
|
6991
|
+
__export(session_store_exports, {
|
|
6992
|
+
clearAppleSession: () => clearAppleSession,
|
|
6993
|
+
lastAppleId: () => lastAppleId,
|
|
6994
|
+
loadAppleSession: () => loadAppleSession,
|
|
6995
|
+
saveAppleSession: () => saveAppleSession,
|
|
6996
|
+
sessionDir: () => sessionDir,
|
|
6997
|
+
signInWithAppleId: () => signInWithAppleId
|
|
6998
|
+
});
|
|
6999
|
+
function sessionDir() {
|
|
7000
|
+
return process.env.FASTNODE_SESSION_DIR || path.join(os.homedir(), ".fastnode", "sessions");
|
|
7001
|
+
}
|
|
7002
|
+
function fileFor(email) {
|
|
7003
|
+
const key = crypto.createHash("sha256").update(email.trim().toLowerCase()).digest("hex").slice(0, 16);
|
|
7004
|
+
return path.join(sessionDir(), `${key}.json`);
|
|
7005
|
+
}
|
|
7006
|
+
function ensureDir() {
|
|
7007
|
+
const dir = sessionDir();
|
|
7008
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
7009
|
+
fs.chmodSync(dir, 448);
|
|
7010
|
+
}
|
|
7011
|
+
function saveAppleSession(session) {
|
|
7012
|
+
ensureDir();
|
|
7013
|
+
const stored = { email: session.userEmail, savedAt: (/* @__PURE__ */ new Date()).toISOString(), session };
|
|
7014
|
+
const path$1 = fileFor(session.userEmail);
|
|
7015
|
+
fs.writeFileSync(path$1, JSON.stringify(stored), { mode: 384 });
|
|
7016
|
+
fs.chmodSync(path$1, 384);
|
|
7017
|
+
fs.writeFileSync(path.join(sessionDir(), "last-apple-id"), session.userEmail, { mode: 384 });
|
|
7018
|
+
}
|
|
7019
|
+
function clearAppleSession(email) {
|
|
7020
|
+
const path = fileFor(email);
|
|
7021
|
+
if (fs.existsSync(path)) fs.unlinkSync(path);
|
|
7022
|
+
}
|
|
7023
|
+
function lastAppleId() {
|
|
7024
|
+
try {
|
|
7025
|
+
return fs.readFileSync(path.join(sessionDir(), "last-apple-id"), "utf8").trim() || null;
|
|
7026
|
+
} catch {
|
|
7027
|
+
return null;
|
|
7028
|
+
}
|
|
7029
|
+
}
|
|
7030
|
+
async function loadAppleSession(email, fetchImpl = fetch) {
|
|
7031
|
+
const path = fileFor(email);
|
|
7032
|
+
if (!fs.existsSync(path)) return null;
|
|
7033
|
+
let stored;
|
|
7034
|
+
try {
|
|
7035
|
+
stored = JSON.parse(fs.readFileSync(path, "utf8"));
|
|
7036
|
+
} catch {
|
|
7037
|
+
return null;
|
|
7038
|
+
}
|
|
7039
|
+
const cookie = Object.entries(stored.session.cookies).map(([k, v]) => `${k}=${v}`).join("; ");
|
|
7040
|
+
try {
|
|
7041
|
+
const res = await fetchImpl(OLYMPUS_SESSION2, { headers: { Cookie: cookie, Accept: "application/json" } });
|
|
7042
|
+
if (!res.ok) {
|
|
7043
|
+
clearAppleSession(email);
|
|
7044
|
+
return null;
|
|
7045
|
+
}
|
|
7046
|
+
} catch {
|
|
7047
|
+
return null;
|
|
7048
|
+
}
|
|
7049
|
+
return stored.session;
|
|
7050
|
+
}
|
|
7051
|
+
async function signInWithAppleId(options) {
|
|
7052
|
+
const email = options.email || lastAppleId() || await options.askEmail();
|
|
7053
|
+
if (!options.fresh) {
|
|
7054
|
+
const saved = await loadAppleSession(email);
|
|
7055
|
+
if (saved) {
|
|
7056
|
+
options.onLog?.(`Using saved Apple ID session for ${email}`);
|
|
7057
|
+
return saved;
|
|
7058
|
+
}
|
|
7059
|
+
}
|
|
7060
|
+
const password = await options.askPassword(email);
|
|
7061
|
+
const session = await loginWithAppleId({ email, password, onLog: options.onLog });
|
|
7062
|
+
saveAppleSession(session);
|
|
7063
|
+
return session;
|
|
7064
|
+
}
|
|
7065
|
+
var OLYMPUS_SESSION2;
|
|
7066
|
+
var init_session_store = __esm({
|
|
7067
|
+
"src/client/session-store.ts"() {
|
|
7068
|
+
init_apple_session();
|
|
7069
|
+
OLYMPUS_SESSION2 = "https://appstoreconnect.apple.com/olympus/v1/session";
|
|
7070
|
+
}
|
|
7071
|
+
});
|
|
7072
|
+
|
|
6394
7073
|
// src/client/portal.ts
|
|
6395
7074
|
function pickUniqueKeyName(base, taken) {
|
|
6396
7075
|
if (!taken.has(base)) return base;
|
|
@@ -6730,7 +7409,10 @@ var init_portal = __esm({
|
|
|
6730
7409
|
}
|
|
6731
7410
|
// ── Teams ──────────────────────────────────────────────────────────────
|
|
6732
7411
|
async listTeams() {
|
|
6733
|
-
const data = await this.post("account/listTeams.action", {
|
|
7412
|
+
const data = await this.post("account/listTeams.action", {
|
|
7413
|
+
pageSize: "500",
|
|
7414
|
+
pageNumber: "1"
|
|
7415
|
+
});
|
|
6734
7416
|
return data.teams ?? [];
|
|
6735
7417
|
}
|
|
6736
7418
|
// ── App IDs / Bundle IDs ───────────────────────────────────────────────
|
|
@@ -6744,11 +7426,12 @@ var init_portal = __esm({
|
|
|
6744
7426
|
return data.appIds ?? [];
|
|
6745
7427
|
}
|
|
6746
7428
|
async createAppId(params) {
|
|
7429
|
+
const safeName = params.name.replace(/[^0-9A-Za-z\s]/g, " ").replace(/\s+/g, " ").trim() || "App";
|
|
6747
7430
|
const reqParams = {
|
|
6748
7431
|
teamId: this.teamId,
|
|
6749
7432
|
type: "explicit",
|
|
6750
7433
|
identifier: params.identifier,
|
|
6751
|
-
name:
|
|
7434
|
+
name: safeName
|
|
6752
7435
|
};
|
|
6753
7436
|
if (params.enablePush) reqParams.push = "on";
|
|
6754
7437
|
if (params.enableIAP !== false) reqParams.inAppPurchase = "on";
|
|
@@ -6783,6 +7466,191 @@ var init_portal = __esm({
|
|
|
6783
7466
|
featureValue: params.enabled ? "on" : "off"
|
|
6784
7467
|
});
|
|
6785
7468
|
}
|
|
7469
|
+
// ── Application Groups ──────────────────────────────────────────────────
|
|
7470
|
+
//
|
|
7471
|
+
// App Groups (e.g. `group.com.playspina.com`) are the only clean way to
|
|
7472
|
+
// share data — an auth token, in our case — between a host app and an
|
|
7473
|
+
// embedded extension (Messages / Notification Service / etc.). App Store
|
|
7474
|
+
// Connect API keys (.p8) genuinely cannot manage them; Apple only exposes
|
|
7475
|
+
// group create/associate through this legacy session-cookie portal API,
|
|
7476
|
+
// which is why they live on PortalClient (Apple ID session) and not on the
|
|
7477
|
+
// ASC REST client. Endpoint shapes ported 1:1 from Spaceship's
|
|
7478
|
+
// portal_client.rb (listApplicationGroups / addApplicationGroup /
|
|
7479
|
+
// assignApplicationGroupToAppId).
|
|
7480
|
+
/** Apple's internal feature code for the App Groups capability. */
|
|
7481
|
+
static APP_GROUPS_FEATURE = "APG3427HIY";
|
|
7482
|
+
/** List all app groups in the team. */
|
|
7483
|
+
async listAppGroups() {
|
|
7484
|
+
const data = await this.post("account/ios/identifiers/listApplicationGroups.action", {
|
|
7485
|
+
teamId: this.teamId,
|
|
7486
|
+
pageNumber: "1",
|
|
7487
|
+
pageSize: "500",
|
|
7488
|
+
sort: "name=asc"
|
|
7489
|
+
});
|
|
7490
|
+
return data.applicationGroupList ?? [];
|
|
7491
|
+
}
|
|
7492
|
+
/**
|
|
7493
|
+
* Create an app group, returning the raw group record. Idempotent —
|
|
7494
|
+
* Apple rejects a duplicate group identifier ("Multiple App Groups may
|
|
7495
|
+
* not have the same identifier"), so we list first and reuse an existing
|
|
7496
|
+
* group when the identifier already exists in the team.
|
|
7497
|
+
*
|
|
7498
|
+
* Note the two ids on the returned record: `identifier` is the
|
|
7499
|
+
* `group.com.…` string you type, while `applicationGroup` is Apple's
|
|
7500
|
+
* opaque internal handle that association actually requires — do NOT pass
|
|
7501
|
+
* the human identifier to assignAppGroups.
|
|
7502
|
+
*/
|
|
7503
|
+
async createAppGroup(params) {
|
|
7504
|
+
const existing = await this.listAppGroups();
|
|
7505
|
+
const hit = existing.find((g) => g.identifier === params.identifier);
|
|
7506
|
+
if (hit) return hit;
|
|
7507
|
+
const safeName = params.name.replace(/[^0-9A-Za-z\s]/g, " ").replace(/\s+/g, " ").trim() || "App Group";
|
|
7508
|
+
const data = await this.post("account/ios/identifiers/addApplicationGroup.action", {
|
|
7509
|
+
teamId: this.teamId,
|
|
7510
|
+
identifier: params.identifier,
|
|
7511
|
+
name: safeName
|
|
7512
|
+
});
|
|
7513
|
+
return data.applicationGroup;
|
|
7514
|
+
}
|
|
7515
|
+
/**
|
|
7516
|
+
* Enable the App Groups capability on an App ID. Required before an
|
|
7517
|
+
* App ID can have a group associated with it — mirrors Spaceship
|
|
7518
|
+
* enabling `AppService::AppGroup` before `associate_groups`.
|
|
7519
|
+
*/
|
|
7520
|
+
async enableAppGroups(appIdId) {
|
|
7521
|
+
await this.post("account/ios/identifiers/updateService.action", {
|
|
7522
|
+
teamId: this.teamId,
|
|
7523
|
+
displayId: appIdId,
|
|
7524
|
+
featureType: _PortalClient.APP_GROUPS_FEATURE,
|
|
7525
|
+
featureValue: "true"
|
|
7526
|
+
});
|
|
7527
|
+
}
|
|
7528
|
+
/**
|
|
7529
|
+
* Associate one or more app groups with an App ID. `appGroupIds` are the
|
|
7530
|
+
* internal ids (Spaceship's `app_group_id`), NOT the `group.com.…`
|
|
7531
|
+
* identifiers. Apple wants both `appIdId` and `displayId` set to the
|
|
7532
|
+
* App ID's internal id, per Spaceship's associate_groups_with_app.
|
|
7533
|
+
*/
|
|
7534
|
+
async assignAppGroups(params) {
|
|
7535
|
+
await this.post("account/ios/identifiers/assignApplicationGroupToAppId.action", {
|
|
7536
|
+
teamId: this.teamId,
|
|
7537
|
+
appIdId: params.appIdId,
|
|
7538
|
+
displayId: params.appIdId,
|
|
7539
|
+
applicationGroups: params.appGroupIds
|
|
7540
|
+
});
|
|
7541
|
+
}
|
|
7542
|
+
// ── Apple Pay merchants (OMCs) ─────────────────────────────────────────
|
|
7543
|
+
//
|
|
7544
|
+
// Merchant IDs are team-level identifiers (merchant.com.…) that an App ID's
|
|
7545
|
+
// Apple Pay capability must be LINKED to — enabling the capability alone
|
|
7546
|
+
// produces profiles that fail with "doesn't support the … Merchant ID".
|
|
7547
|
+
// Like App Groups, merchants are only manageable through the legacy
|
|
7548
|
+
// session-cookie portal API. Endpoint shapes ported 1:1 from Spaceship's
|
|
7549
|
+
// portal_client.rb (merchants / create_merchant! /
|
|
7550
|
+
// associate_merchants_with_app).
|
|
7551
|
+
/** Apple's internal feature code for the Apple Pay capability. */
|
|
7552
|
+
static APPLE_PAY_FEATURE = "OM633U5T5G";
|
|
7553
|
+
/**
|
|
7554
|
+
* List all Apple Pay merchant IDs in the team. Each record maps
|
|
7555
|
+
* (per Spaceship's merchant.rb attr_mapping): `omcId` = internal id,
|
|
7556
|
+
* `identifier` = the human `merchant.com.…` string, `name` = display name.
|
|
7557
|
+
*/
|
|
7558
|
+
async listMerchants() {
|
|
7559
|
+
const data = await this.post("account/ios/identifiers/listOMCs.action", {
|
|
7560
|
+
teamId: this.teamId,
|
|
7561
|
+
pageNumber: "1",
|
|
7562
|
+
pageSize: "500",
|
|
7563
|
+
sort: "name=asc"
|
|
7564
|
+
});
|
|
7565
|
+
return data.identifierList ?? [];
|
|
7566
|
+
}
|
|
7567
|
+
/**
|
|
7568
|
+
* Register (or reuse) a merchant ID, returning its INTERNAL id (`omcId`) —
|
|
7569
|
+
* Apple's opaque handle that association requires, NOT the human
|
|
7570
|
+
* `merchant.com.…` identifier. Idempotent: lists first and reuses an
|
|
7571
|
+
* existing merchant with the same identifier. Endpoint + params ported 1:1
|
|
7572
|
+
* from Spaceship's create_merchant! (addOMC.action).
|
|
7573
|
+
*/
|
|
7574
|
+
async createMerchant(params) {
|
|
7575
|
+
const existing = await this.listMerchants();
|
|
7576
|
+
const hit = existing.find((m) => m.identifier === params.identifier);
|
|
7577
|
+
if (hit?.omcId) return String(hit.omcId);
|
|
7578
|
+
const safeName = params.name.replace(/[^0-9A-Za-z\s]/g, " ").replace(/\s+/g, " ").trim() || "Merchant";
|
|
7579
|
+
const data = await this.post("account/ios/identifiers/addOMC.action", {
|
|
7580
|
+
name: safeName,
|
|
7581
|
+
identifier: params.identifier,
|
|
7582
|
+
teamId: this.teamId
|
|
7583
|
+
});
|
|
7584
|
+
const rec = data.omcId ?? data;
|
|
7585
|
+
const id = typeof rec === "string" ? rec : rec?.omcId ?? rec?.merchantId;
|
|
7586
|
+
if (!id) {
|
|
7587
|
+
throw new Error("Apple returned no internal merchant id (omcId) for the registered merchant.");
|
|
7588
|
+
}
|
|
7589
|
+
return String(id);
|
|
7590
|
+
}
|
|
7591
|
+
/**
|
|
7592
|
+
* Enable the Apple Pay capability on an App ID. Apple Pay is a standard
|
|
7593
|
+
* AppService (service_id OM633U5T5G) whose service_uri is the default
|
|
7594
|
+
* `updateService.action` — confirmed in Spaceship's app_service.rb.
|
|
7595
|
+
*/
|
|
7596
|
+
async enableApplePay(appIdId) {
|
|
7597
|
+
await this.post("account/ios/identifiers/updateService.action", {
|
|
7598
|
+
teamId: this.teamId,
|
|
7599
|
+
displayId: appIdId,
|
|
7600
|
+
featureType: _PortalClient.APPLE_PAY_FEATURE,
|
|
7601
|
+
featureValue: "true"
|
|
7602
|
+
});
|
|
7603
|
+
}
|
|
7604
|
+
/**
|
|
7605
|
+
* Associate merchant IDs with an App ID's Apple Pay capability. `omcIds`
|
|
7606
|
+
* are the internal ids (each merchant record's `omcId`), NOT the
|
|
7607
|
+
* `merchant.com.…` identifiers. Endpoint + params ported 1:1 from
|
|
7608
|
+
* Spaceship's associate_merchants_with_app (assignOMCToAppId.action — note
|
|
7609
|
+
* the singular "OMC", and NO displayId param).
|
|
7610
|
+
*/
|
|
7611
|
+
async assignMerchants(params) {
|
|
7612
|
+
await this.post("account/ios/identifiers/assignOMCToAppId.action", {
|
|
7613
|
+
teamId: this.teamId,
|
|
7614
|
+
appIdId: params.appIdId,
|
|
7615
|
+
omcIds: params.omcIds
|
|
7616
|
+
});
|
|
7617
|
+
}
|
|
7618
|
+
/**
|
|
7619
|
+
* Create an Apple Pay **Payment Processing Certificate** on a merchant ID
|
|
7620
|
+
* from a CSR — the certificate a PSP (e.g. Stripe) hands you as a CSR, which
|
|
7621
|
+
* must be uploaded to the Merchant ID in Apple Developer, then the resulting
|
|
7622
|
+
* `.cer` downloaded and given back to the PSP so it can decrypt payment
|
|
7623
|
+
* tokens. This is the one manual Apple-portal step in wiring up Apple Pay.
|
|
7624
|
+
*
|
|
7625
|
+
* Ported 1:1 from Spaceship's create_certificate! (submitCertificateRequest
|
|
7626
|
+
* .action): the merchant's INTERNAL id (`omcId`) is passed as BOTH `appIdId`
|
|
7627
|
+
* and `specialIdentifierDisplayId` — the latter is how the portal scopes a
|
|
7628
|
+
* cert to a merchant/special identifier rather than an App ID. The cert type
|
|
7629
|
+
* is APPLE_PAY (`4APLUP237T`). Returns the created certRequest (its
|
|
7630
|
+
* `certificateId` feeds `downloadApplePayCertificate`).
|
|
7631
|
+
*
|
|
7632
|
+
* @param omcId the merchant's internal id (from createMerchant/listMerchants)
|
|
7633
|
+
* @param csrContent the PEM CSR string (Stripe's "Apple Pay CSR")
|
|
7634
|
+
*/
|
|
7635
|
+
async createApplePayCertificate(params) {
|
|
7636
|
+
const data = await this.post("account/ios/certificate/submitCertificateRequest.action", {
|
|
7637
|
+
teamId: this.teamId,
|
|
7638
|
+
type: _PortalClient.CERT_TYPES.APPLE_PAY,
|
|
7639
|
+
csrContent: params.csrContent,
|
|
7640
|
+
appIdId: params.omcId,
|
|
7641
|
+
specialIdentifierDisplayId: params.omcId
|
|
7642
|
+
});
|
|
7643
|
+
return data.certRequest;
|
|
7644
|
+
}
|
|
7645
|
+
/**
|
|
7646
|
+
* Download an Apple Pay Payment Processing Certificate's raw bytes by its
|
|
7647
|
+
* certificate id (from `createApplePayCertificate`). Thin wrapper over
|
|
7648
|
+
* `downloadCertificate` pinned to the APPLE_PAY type — the DER `.cer` you
|
|
7649
|
+
* upload back into Stripe.
|
|
7650
|
+
*/
|
|
7651
|
+
async downloadApplePayCertificate(certificateId) {
|
|
7652
|
+
return this.downloadCertificate(certificateId, _PortalClient.CERT_TYPES.APPLE_PAY);
|
|
7653
|
+
}
|
|
6786
7654
|
// ── Certificates ───────────────────────────────────────────────────────
|
|
6787
7655
|
async listCertificates(types) {
|
|
6788
7656
|
const iosTypes = types ?? [
|
|
@@ -7279,8 +8147,14 @@ __export(client_exports, {
|
|
|
7279
8147
|
TokenManager: () => TokenManager,
|
|
7280
8148
|
beginLogin: () => beginLogin,
|
|
7281
8149
|
checkPortalResponse: () => checkPortalResponse,
|
|
8150
|
+
clearAppleSession: () => clearAppleSession,
|
|
8151
|
+
lastAppleId: () => lastAppleId,
|
|
8152
|
+
loadAppleSession: () => loadAppleSession,
|
|
7282
8153
|
loginWithAppleId: () => loginWithAppleId,
|
|
7283
8154
|
requestSmsCode: () => requestSmsCode,
|
|
8155
|
+
saveAppleSession: () => saveAppleSession,
|
|
8156
|
+
sessionDir: () => sessionDir,
|
|
8157
|
+
signInWithAppleId: () => signInWithAppleId,
|
|
7284
8158
|
submitTwoFactorCode: () => submitTwoFactorCode
|
|
7285
8159
|
});
|
|
7286
8160
|
var AppStoreConnectClient;
|
|
@@ -7322,9 +8196,13 @@ var init_client = __esm({
|
|
|
7322
8196
|
init_in_app_purchases();
|
|
7323
8197
|
init_game_center_detail();
|
|
7324
8198
|
init_xcode_metrics();
|
|
8199
|
+
init_app_asset_libraries();
|
|
8200
|
+
init_app_asset_library_assets();
|
|
8201
|
+
init_app_asset_library_placements();
|
|
7325
8202
|
init_auth();
|
|
7326
8203
|
init_request();
|
|
7327
8204
|
init_apple_session();
|
|
8205
|
+
init_session_store();
|
|
7328
8206
|
init_portal();
|
|
7329
8207
|
init_iris();
|
|
7330
8208
|
AppStoreConnectClient = class {
|
|
@@ -7363,6 +8241,10 @@ var init_client = __esm({
|
|
|
7363
8241
|
inAppPurchases;
|
|
7364
8242
|
gameCenterDetail;
|
|
7365
8243
|
xcodeMetrics;
|
|
8244
|
+
appAssetLibraries;
|
|
8245
|
+
appAssetLibraryImages;
|
|
8246
|
+
appAssetLibraryVideos;
|
|
8247
|
+
appAssetLibraryPlacements;
|
|
7366
8248
|
constructor(config) {
|
|
7367
8249
|
const tokenManager = new TokenManager({
|
|
7368
8250
|
keyId: config.keyId,
|
|
@@ -7406,6 +8288,10 @@ var init_client = __esm({
|
|
|
7406
8288
|
this.inAppPurchases = new InAppPurchasesResource(this.http);
|
|
7407
8289
|
this.gameCenterDetail = new GameCenterDetailResource(this.http);
|
|
7408
8290
|
this.xcodeMetrics = new XcodeMetricsResource(this.http);
|
|
8291
|
+
this.appAssetLibraries = new AppAssetLibrariesResource(this.http);
|
|
8292
|
+
this.appAssetLibraryImages = new AppAssetLibraryImagesResource(this.http);
|
|
8293
|
+
this.appAssetLibraryVideos = new AppAssetLibraryVideosResource(this.http);
|
|
8294
|
+
this.appAssetLibraryPlacements = new AppAssetLibraryPlacementsResource(this.http);
|
|
7409
8295
|
}
|
|
7410
8296
|
async request(path, options) {
|
|
7411
8297
|
return this.http.request(path, options);
|
|
@@ -8173,8 +9059,8 @@ var init_utilities = __esm({
|
|
|
8173
9059
|
description: "Ask the user for input",
|
|
8174
9060
|
async run(params) {
|
|
8175
9061
|
const text = params.text ?? "Enter a value:";
|
|
8176
|
-
const { createInterface:
|
|
8177
|
-
const rl =
|
|
9062
|
+
const { createInterface: createInterface4 } = await import('readline');
|
|
9063
|
+
const rl = createInterface4({ input: process.stdin, output: process.stdout });
|
|
8178
9064
|
return new Promise((resolve4) => {
|
|
8179
9065
|
rl.question(text + " ", (answer) => {
|
|
8180
9066
|
rl.close();
|
|
@@ -8566,6 +9452,308 @@ var init_options = __esm({
|
|
|
8566
9452
|
init_client();
|
|
8567
9453
|
}
|
|
8568
9454
|
});
|
|
9455
|
+
|
|
9456
|
+
// src/privacy/index.ts
|
|
9457
|
+
var privacy_exports = {};
|
|
9458
|
+
__export(privacy_exports, {
|
|
9459
|
+
DATA_PROTECTIONS: () => DATA_PROTECTIONS,
|
|
9460
|
+
DATA_USAGE_CATEGORIES: () => DATA_USAGE_CATEGORIES,
|
|
9461
|
+
DATA_USAGE_PURPOSES: () => DATA_USAGE_PURPOSES,
|
|
9462
|
+
createAppDataUsage: () => createAppDataUsage,
|
|
9463
|
+
deleteAppDataUsage: () => deleteAppDataUsage,
|
|
9464
|
+
getAppDataUsagesPublishState: () => getAppDataUsagesPublishState,
|
|
9465
|
+
listAppDataUsages: () => listAppDataUsages,
|
|
9466
|
+
publishAppDataUsages: () => publishAppDataUsages,
|
|
9467
|
+
selectProviderForApp: () => selectProviderForApp,
|
|
9468
|
+
uploadAppPrivacyDetails: () => uploadAppPrivacyDetails,
|
|
9469
|
+
validateUsages: () => validateUsages
|
|
9470
|
+
});
|
|
9471
|
+
async function iris(session, method, path, body) {
|
|
9472
|
+
const res = await fetch(path.startsWith("http") ? path : `${IRIS_BASE2}${path}`, {
|
|
9473
|
+
method,
|
|
9474
|
+
headers: {
|
|
9475
|
+
Cookie: cookieHeader(session),
|
|
9476
|
+
Accept: "application/vnd.api+json, application/json",
|
|
9477
|
+
"Content-Type": "application/vnd.api+json",
|
|
9478
|
+
"X-Requested-With": "XMLHttpRequest"
|
|
9479
|
+
},
|
|
9480
|
+
body: body ? JSON.stringify(body) : void 0
|
|
9481
|
+
});
|
|
9482
|
+
absorbCookies(session, res);
|
|
9483
|
+
const text = await res.text();
|
|
9484
|
+
let parsed = null;
|
|
9485
|
+
try {
|
|
9486
|
+
parsed = text ? JSON.parse(text) : null;
|
|
9487
|
+
} catch {
|
|
9488
|
+
}
|
|
9489
|
+
return { status: res.status, body: parsed, text };
|
|
9490
|
+
}
|
|
9491
|
+
function cookieHeader(session) {
|
|
9492
|
+
return Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join("; ");
|
|
9493
|
+
}
|
|
9494
|
+
function absorbCookies(session, res) {
|
|
9495
|
+
for (const raw of res.headers.getSetCookie?.() ?? []) {
|
|
9496
|
+
const m = raw.match(/^([^=]+)=([^;]*)/);
|
|
9497
|
+
if (m) session.cookies[m[1]] = m[2];
|
|
9498
|
+
}
|
|
9499
|
+
}
|
|
9500
|
+
function fail(what, r) {
|
|
9501
|
+
const detail = r.body?.errors?.map((e) => e.detail ?? e.title).join("; ") || r.text.slice(0, 300);
|
|
9502
|
+
throw new Error(`${what} failed (HTTP ${r.status}): ${detail}`);
|
|
9503
|
+
}
|
|
9504
|
+
async function selectProviderForApp(session, appId, onLog) {
|
|
9505
|
+
const owns = async () => (await iris(session, "GET", `/apps/${appId}?fields[apps]=name`)).status === 200;
|
|
9506
|
+
if (await owns()) return;
|
|
9507
|
+
const res = await fetch(OLYMPUS_SESSION3, { headers: { Cookie: cookieHeader(session), Accept: "application/json" } });
|
|
9508
|
+
absorbCookies(session, res);
|
|
9509
|
+
const info = await res.json().catch(() => null);
|
|
9510
|
+
const providers = info?.availableProviders ?? [];
|
|
9511
|
+
for (const p of providers) {
|
|
9512
|
+
const sw = await fetch(OLYMPUS_SESSION3, {
|
|
9513
|
+
method: "POST",
|
|
9514
|
+
headers: { Cookie: cookieHeader(session), "Content-Type": "application/json", Accept: "application/json" },
|
|
9515
|
+
body: JSON.stringify({ provider: { providerId: p.providerId } })
|
|
9516
|
+
});
|
|
9517
|
+
absorbCookies(session, sw);
|
|
9518
|
+
if (sw.ok && await owns()) {
|
|
9519
|
+
onLog?.(`Switched to team "${p.name}"`);
|
|
9520
|
+
session.teamId = String(p.providerId);
|
|
9521
|
+
return;
|
|
9522
|
+
}
|
|
9523
|
+
}
|
|
9524
|
+
throw new Error(`None of this Apple ID's ${providers.length} teams can see app ${appId}.`);
|
|
9525
|
+
}
|
|
9526
|
+
async function listAppDataUsages(session, appId) {
|
|
9527
|
+
const all = [];
|
|
9528
|
+
let next = `/apps/${appId}/dataUsages?include=category,grouping,purpose,dataProtection&limit=500`;
|
|
9529
|
+
while (next) {
|
|
9530
|
+
const r = await iris(session, "GET", next);
|
|
9531
|
+
if (r.status !== 200) fail("List app data usages", r);
|
|
9532
|
+
all.push(...(r.body?.data ?? []).map((d) => ({ id: d.id })));
|
|
9533
|
+
next = r.body?.links?.next ?? null;
|
|
9534
|
+
}
|
|
9535
|
+
return all;
|
|
9536
|
+
}
|
|
9537
|
+
async function createAppDataUsage(session, input) {
|
|
9538
|
+
const relationships = { app: { data: { type: "apps", id: input.appId } } };
|
|
9539
|
+
if (input.category) relationships.category = { data: { type: "appDataUsageCategories", id: input.category } };
|
|
9540
|
+
if (input.purpose) relationships.purpose = { data: { type: "appDataUsagePurposes", id: input.purpose } };
|
|
9541
|
+
relationships.dataProtection = { data: { type: "appDataUsageDataProtections", id: input.dataProtection } };
|
|
9542
|
+
const r = await iris(session, "POST", "/appDataUsages", { data: { type: "appDataUsages", relationships } });
|
|
9543
|
+
if (r.status !== 201 && r.status !== 200) fail(`Create ${input.category ?? ""} ${input.purpose ?? ""} ${input.dataProtection}`.replace(/\s+/g, " "), r);
|
|
9544
|
+
return r.body?.data?.id;
|
|
9545
|
+
}
|
|
9546
|
+
async function deleteAppDataUsage(session, id) {
|
|
9547
|
+
const r = await iris(session, "DELETE", `/appDataUsages/${id}`);
|
|
9548
|
+
if (r.status !== 204 && r.status !== 200) fail(`Delete app data usage ${id}`, r);
|
|
9549
|
+
}
|
|
9550
|
+
async function getAppDataUsagesPublishState(session, appId) {
|
|
9551
|
+
const r = await iris(session, "GET", `/apps/${appId}/dataUsagePublishState`);
|
|
9552
|
+
if (r.status !== 200) fail("Read privacy publish state", r);
|
|
9553
|
+
return { id: r.body.data.id, published: Boolean(r.body.data.attributes?.published) };
|
|
9554
|
+
}
|
|
9555
|
+
async function publishAppDataUsages(session, publishStateId) {
|
|
9556
|
+
const r = await iris(session, "PATCH", `/appDataUsagesPublishState/${publishStateId}`, {
|
|
9557
|
+
data: { type: "appDataUsagesPublishState", id: publishStateId, attributes: { published: true } }
|
|
9558
|
+
});
|
|
9559
|
+
if (r.status !== 200) fail("Publish privacy details", r);
|
|
9560
|
+
}
|
|
9561
|
+
async function uploadAppPrivacyDetails(opts) {
|
|
9562
|
+
const { session, appId, usages, skipPublish, onLog = () => {
|
|
9563
|
+
} } = opts;
|
|
9564
|
+
validateUsages(usages);
|
|
9565
|
+
await selectProviderForApp(session, appId, onLog);
|
|
9566
|
+
const existing = await listAppDataUsages(session, appId);
|
|
9567
|
+
onLog(`Deleting ${existing.length} existing answer${existing.length === 1 ? "" : "s"}`);
|
|
9568
|
+
for (const u of existing) await deleteAppDataUsage(session, u.id);
|
|
9569
|
+
let created = 0;
|
|
9570
|
+
for (const usage of usages) {
|
|
9571
|
+
const purposes = usage.purposes?.length ? usage.purposes : [null];
|
|
9572
|
+
for (const purpose of purposes) {
|
|
9573
|
+
for (const protection of usage.data_protections) {
|
|
9574
|
+
onLog(protection === "DATA_NOT_COLLECTED" ? `Setting ${protection}` : `Setting ${usage.category} and ${purpose} to ${protection}`);
|
|
9575
|
+
await createAppDataUsage(session, { appId, category: usage.category ?? null, purpose, dataProtection: protection });
|
|
9576
|
+
created++;
|
|
9577
|
+
}
|
|
9578
|
+
}
|
|
9579
|
+
}
|
|
9580
|
+
if (skipPublish) {
|
|
9581
|
+
onLog("Skipping publish");
|
|
9582
|
+
return { deleted: existing.length, created, published: false };
|
|
9583
|
+
}
|
|
9584
|
+
const state = await getAppDataUsagesPublishState(session, appId);
|
|
9585
|
+
await publishAppDataUsages(session, state.id);
|
|
9586
|
+
onLog("Published");
|
|
9587
|
+
return { deleted: existing.length, created, published: true };
|
|
9588
|
+
}
|
|
9589
|
+
function validateUsages(usages) {
|
|
9590
|
+
if (!Array.isArray(usages) || usages.length === 0) throw new Error("No privacy usages given.");
|
|
9591
|
+
const notCollected = usages.some((u) => u.data_protections?.includes("DATA_NOT_COLLECTED"));
|
|
9592
|
+
if (notCollected && usages.length > 1) {
|
|
9593
|
+
throw new Error("DATA_NOT_COLLECTED must be the only entry: an app cannot both collect nothing and collect something.");
|
|
9594
|
+
}
|
|
9595
|
+
const known = (list, v, what) => {
|
|
9596
|
+
if (!list.includes(v)) throw new Error(`"${v}" is not an App Store Connect ${what}. Use one of: ${list.join(", ")}.`);
|
|
9597
|
+
};
|
|
9598
|
+
for (const u of usages) {
|
|
9599
|
+
if (!u.data_protections?.length) throw new Error(`${u.category ?? "An entry"} has no data_protections.`);
|
|
9600
|
+
if (u.category) known(DATA_USAGE_CATEGORIES, u.category, "data category");
|
|
9601
|
+
for (const p of u.purposes ?? []) known(DATA_USAGE_PURPOSES, p, "purpose");
|
|
9602
|
+
for (const d of u.data_protections) known(DATA_PROTECTIONS, d, "data protection");
|
|
9603
|
+
if (u.data_protections.includes("DATA_NOT_COLLECTED")) continue;
|
|
9604
|
+
if (!u.category) throw new Error("Every collected entry needs a category.");
|
|
9605
|
+
if (!u.purposes?.length) throw new Error(`${u.category} needs at least one purpose.`);
|
|
9606
|
+
const linked = u.data_protections.includes("DATA_LINKED_TO_YOU");
|
|
9607
|
+
const unlinked = u.data_protections.includes("DATA_NOT_LINKED_TO_YOU");
|
|
9608
|
+
if (linked && unlinked) throw new Error(`${u.category} cannot be both linked and not linked to the user.`);
|
|
9609
|
+
}
|
|
9610
|
+
}
|
|
9611
|
+
var IRIS_BASE2, OLYMPUS_SESSION3, DATA_USAGE_CATEGORIES, DATA_USAGE_PURPOSES, DATA_PROTECTIONS;
|
|
9612
|
+
var init_privacy = __esm({
|
|
9613
|
+
"src/privacy/index.ts"() {
|
|
9614
|
+
IRIS_BASE2 = "https://appstoreconnect.apple.com/iris/v1";
|
|
9615
|
+
OLYMPUS_SESSION3 = "https://appstoreconnect.apple.com/olympus/v1/session";
|
|
9616
|
+
DATA_USAGE_CATEGORIES = [
|
|
9617
|
+
"PAYMENT_INFORMATION",
|
|
9618
|
+
"CREDIT_AND_FRAUD",
|
|
9619
|
+
"OTHER_FINANCIAL_INFO",
|
|
9620
|
+
"PRECISE_LOCATION",
|
|
9621
|
+
"COARSE_LOCATION",
|
|
9622
|
+
"SENSITIVE_INFO",
|
|
9623
|
+
"PHYSICAL_ADDRESS",
|
|
9624
|
+
"EMAIL_ADDRESS",
|
|
9625
|
+
"NAME",
|
|
9626
|
+
"PHONE_NUMBER",
|
|
9627
|
+
"OTHER_CONTACT_INFO",
|
|
9628
|
+
"CONTACTS",
|
|
9629
|
+
"EMAILS_OR_TEXT_MESSAGES",
|
|
9630
|
+
"PHOTOS_OR_VIDEOS",
|
|
9631
|
+
"AUDIO",
|
|
9632
|
+
"GAMEPLAY_CONTENT",
|
|
9633
|
+
"CUSTOMER_SUPPORT",
|
|
9634
|
+
"OTHER_USER_CONTENT",
|
|
9635
|
+
"BROWSING_HISTORY",
|
|
9636
|
+
"SEARCH_HISTORY",
|
|
9637
|
+
"USER_ID",
|
|
9638
|
+
"DEVICE_ID",
|
|
9639
|
+
"PURCHASE_HISTORY",
|
|
9640
|
+
"PRODUCT_INTERACTION",
|
|
9641
|
+
"ADVERTISING_DATA",
|
|
9642
|
+
"OTHER_USAGE_DATA",
|
|
9643
|
+
"CRASH_DATA",
|
|
9644
|
+
"PERFORMANCE_DATA",
|
|
9645
|
+
"OTHER_DIAGNOSTIC_DATA",
|
|
9646
|
+
"OTHER_DATA",
|
|
9647
|
+
"HEALTH",
|
|
9648
|
+
"FITNESS"
|
|
9649
|
+
];
|
|
9650
|
+
DATA_USAGE_PURPOSES = [
|
|
9651
|
+
"THIRD_PARTY_ADVERTISING",
|
|
9652
|
+
"DEVELOPERS_ADVERTISING",
|
|
9653
|
+
"ANALYTICS",
|
|
9654
|
+
"PRODUCT_PERSONALIZATION",
|
|
9655
|
+
"APP_FUNCTIONALITY",
|
|
9656
|
+
"OTHER_PURPOSES"
|
|
9657
|
+
];
|
|
9658
|
+
DATA_PROTECTIONS = [
|
|
9659
|
+
"DATA_USED_TO_TRACK_YOU",
|
|
9660
|
+
"DATA_LINKED_TO_YOU",
|
|
9661
|
+
"DATA_NOT_LINKED_TO_YOU",
|
|
9662
|
+
"DATA_NOT_COLLECTED"
|
|
9663
|
+
];
|
|
9664
|
+
}
|
|
9665
|
+
});
|
|
9666
|
+
|
|
9667
|
+
// src/review/index.ts
|
|
9668
|
+
var review_exports = {};
|
|
9669
|
+
__export(review_exports, {
|
|
9670
|
+
getReviewFeedback: () => getReviewFeedback,
|
|
9671
|
+
getThreadMessages: () => getThreadMessages,
|
|
9672
|
+
htmlToText: () => htmlToText
|
|
9673
|
+
});
|
|
9674
|
+
async function iris2(session, path) {
|
|
9675
|
+
const res = await fetch(`${IRIS_BASE3}${path}`, {
|
|
9676
|
+
headers: {
|
|
9677
|
+
Cookie: Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join("; "),
|
|
9678
|
+
Accept: "application/vnd.api+json, application/json",
|
|
9679
|
+
"X-Requested-With": "XMLHttpRequest"
|
|
9680
|
+
}
|
|
9681
|
+
});
|
|
9682
|
+
for (const raw of res.headers.getSetCookie?.() ?? []) {
|
|
9683
|
+
const m = raw.match(/^([^=]+)=([^;]*)/);
|
|
9684
|
+
if (m) session.cookies[m[1]] = m[2];
|
|
9685
|
+
}
|
|
9686
|
+
const text = await res.text();
|
|
9687
|
+
let body = null;
|
|
9688
|
+
try {
|
|
9689
|
+
body = text ? JSON.parse(text) : null;
|
|
9690
|
+
} catch {
|
|
9691
|
+
}
|
|
9692
|
+
if (!res.ok) {
|
|
9693
|
+
const detail = body?.errors?.map((e) => e.detail ?? e.title).join("; ") || text.slice(0, 300);
|
|
9694
|
+
throw new Error(`GET ${path} failed (HTTP ${res.status}): ${detail}`);
|
|
9695
|
+
}
|
|
9696
|
+
return body;
|
|
9697
|
+
}
|
|
9698
|
+
function htmlToText(html) {
|
|
9699
|
+
if (!html) return "";
|
|
9700
|
+
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();
|
|
9701
|
+
}
|
|
9702
|
+
function reasonsFrom(rejection) {
|
|
9703
|
+
const reasons = rejection?.attributes?.reasons ?? [];
|
|
9704
|
+
return (Array.isArray(reasons) ? reasons : []).map((r) => ({
|
|
9705
|
+
section: r.reasonSection ?? r.section,
|
|
9706
|
+
description: r.reasonDescription ?? r.description,
|
|
9707
|
+
code: r.reasonCode ?? r.code
|
|
9708
|
+
}));
|
|
9709
|
+
}
|
|
9710
|
+
async function getThreadMessages(session, threadId) {
|
|
9711
|
+
const body = await iris2(session, `/resolutionCenterThreads/${threadId}/resolutionCenterMessages?include=fromActor,rejections`);
|
|
9712
|
+
const included = new Map((body?.included ?? []).map((i) => [`${i.type}:${i.id}`, i]));
|
|
9713
|
+
return (body?.data ?? []).map((m) => {
|
|
9714
|
+
const actor = m.relationships?.fromActor?.data;
|
|
9715
|
+
const actorRow = actor ? included.get(`${actor.type}:${actor.id}`) : null;
|
|
9716
|
+
const rejections = (m.relationships?.rejections?.data ?? []).map((r) => included.get(`${r.type}:${r.id}`)).filter(Boolean);
|
|
9717
|
+
return {
|
|
9718
|
+
id: m.id,
|
|
9719
|
+
createdDate: m.attributes?.createdDate ?? null,
|
|
9720
|
+
from: actorRow?.attributes?.actorType ?? actorRow?.attributes?.userFirstName ?? null,
|
|
9721
|
+
body: htmlToText(m.attributes?.messageBody),
|
|
9722
|
+
reasons: rejections.flatMap(reasonsFrom)
|
|
9723
|
+
};
|
|
9724
|
+
});
|
|
9725
|
+
}
|
|
9726
|
+
async function getReviewFeedback(session, appId, opts = {}) {
|
|
9727
|
+
await selectProviderForApp(session, appId, opts.onLog);
|
|
9728
|
+
const subs = await iris2(session, `/apps/${appId}/reviewSubmissions?limit=10`);
|
|
9729
|
+
const out = [];
|
|
9730
|
+
for (const s of subs?.data ?? []) {
|
|
9731
|
+
const state = s.attributes?.state ?? "";
|
|
9732
|
+
if (opts.onlyOpen && state !== "UNRESOLVED_ISSUES") continue;
|
|
9733
|
+
const threadsBody = await iris2(session, `/resolutionCenterThreads?filter[reviewSubmission]=${s.id}&include=reviewSubmission`);
|
|
9734
|
+
const threads = [];
|
|
9735
|
+
for (const t of threadsBody?.data ?? []) {
|
|
9736
|
+
threads.push({
|
|
9737
|
+
id: t.id,
|
|
9738
|
+
threadType: t.attributes?.threadType ?? null,
|
|
9739
|
+
state: t.attributes?.state ?? null,
|
|
9740
|
+
createdDate: t.attributes?.createdDate ?? null,
|
|
9741
|
+
messages: await getThreadMessages(session, t.id)
|
|
9742
|
+
});
|
|
9743
|
+
}
|
|
9744
|
+
if (threads.length || !opts.onlyOpen) {
|
|
9745
|
+
out.push({ submissionId: s.id, state, submittedDate: s.attributes?.submittedDate ?? null, threads });
|
|
9746
|
+
}
|
|
9747
|
+
}
|
|
9748
|
+
return out;
|
|
9749
|
+
}
|
|
9750
|
+
var IRIS_BASE3;
|
|
9751
|
+
var init_review = __esm({
|
|
9752
|
+
"src/review/index.ts"() {
|
|
9753
|
+
init_privacy();
|
|
9754
|
+
IRIS_BASE3 = "https://appstoreconnect.apple.com/iris/v1";
|
|
9755
|
+
}
|
|
9756
|
+
});
|
|
8569
9757
|
function createRunCommand() {
|
|
8570
9758
|
const cmd = new commander.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) => {
|
|
8571
9759
|
const { loadFastfile: loadFastfile2 } = await Promise.resolve().then(() => (init_fastfile(), fastfile_exports));
|
|
@@ -8686,10 +9874,10 @@ function createPilotCommand() {
|
|
|
8686
9874
|
const cmd = new commander.Command("pilot").description("Manage TestFlight builds and testers");
|
|
8687
9875
|
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) => {
|
|
8688
9876
|
const { uploadToTestFlight: uploadToTestFlight2 } = await Promise.resolve().then(() => (init_pilot(), pilot_exports));
|
|
8689
|
-
const { readFile:
|
|
9877
|
+
const { readFile: readFile9 } = await import('fs/promises');
|
|
8690
9878
|
let apiPrivateKey = "";
|
|
8691
9879
|
if (opts.apiKey) {
|
|
8692
|
-
apiPrivateKey = await
|
|
9880
|
+
apiPrivateKey = await readFile9(opts.apiKey, "utf-8");
|
|
8693
9881
|
}
|
|
8694
9882
|
console.log(`Uploading ${opts.ipa}...`);
|
|
8695
9883
|
const result = await uploadToTestFlight2({
|
|
@@ -8721,7 +9909,10 @@ function createPilotCommand() {
|
|
|
8721
9909
|
return cmd;
|
|
8722
9910
|
}
|
|
8723
9911
|
function createDeliverCommand() {
|
|
8724
|
-
return new commander.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(
|
|
9912
|
+
return new commander.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(
|
|
9913
|
+
"--assets-path <path>",
|
|
9914
|
+
"App Asset Library folder: <locale>/{screenshots,previews,header,search_results}/"
|
|
9915
|
+
).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) => {
|
|
8725
9916
|
const { resolveClientFromEnv: resolveClientFromEnv2 } = await Promise.resolve().then(() => (init_options(), options_exports));
|
|
8726
9917
|
const { uploadToAppStore: uploadToAppStore2 } = await Promise.resolve().then(() => (init_deliver(), deliver_exports));
|
|
8727
9918
|
const client = await resolveClientFromEnv2();
|
|
@@ -8740,7 +9931,12 @@ function createDeliverCommand() {
|
|
|
8740
9931
|
submitForReview: opts.submitForReview,
|
|
8741
9932
|
skipBinaryUpload: opts.skipBinaryUpload,
|
|
8742
9933
|
skipMetadata: opts.skipMetadata,
|
|
8743
|
-
skipScreenshots: opts.skipScreenshots
|
|
9934
|
+
skipScreenshots: opts.skipScreenshots,
|
|
9935
|
+
assetsPath: opts.assetsPath,
|
|
9936
|
+
useAssetLibrary: opts.assetLibrary,
|
|
9937
|
+
overwriteAssets: opts.overwriteAssets,
|
|
9938
|
+
skipAssets: opts.skipAssets,
|
|
9939
|
+
onOutput: (chunk) => process.stdout.write(chunk)
|
|
8744
9940
|
});
|
|
8745
9941
|
console.log(`App: ${result.appId}`);
|
|
8746
9942
|
console.log(`Version: ${result.versionId}`);
|
|
@@ -8844,6 +10040,155 @@ function createLanesCommand() {
|
|
|
8844
10040
|
}
|
|
8845
10041
|
});
|
|
8846
10042
|
}
|
|
10043
|
+
function createPrivacyCommand() {
|
|
10044
|
+
return new commander.Command("privacy").description("Upload App Privacy details (nutrition labels) to App Store Connect").requiredOption("--app-id <id>", "App Store Connect app id (numeric Apple ID)").requiredOption("-j, --json-path <path>", "Fastlane-format app_privacy_details.json").option("-u, --username <email>", "Apple ID (defaults to the last one signed in)", process.env.FASTNODE_USER ?? process.env.FASTLANE_USER).option("--fresh-login", "Ignore the saved Apple ID session and sign in again", false).option("--skip-publish", "Save the answers without publishing them", false).action(async (opts) => {
|
|
10045
|
+
const { signInWithAppleId: signInWithAppleId2, saveAppleSession: saveAppleSession2 } = await Promise.resolve().then(() => (init_session_store(), session_store_exports));
|
|
10046
|
+
const { uploadAppPrivacyDetails: uploadAppPrivacyDetails2, validateUsages: validateUsages2 } = await Promise.resolve().then(() => (init_privacy(), privacy_exports));
|
|
10047
|
+
const usages = JSON.parse(fs.readFileSync(opts.jsonPath, "utf8"));
|
|
10048
|
+
validateUsages2(usages);
|
|
10049
|
+
const session = await signInWithAppleId2({
|
|
10050
|
+
email: opts.username,
|
|
10051
|
+
askEmail: () => ask("Apple ID: "),
|
|
10052
|
+
askPassword: async (email) => process.env.FASTNODE_PASSWORD ?? process.env.FASTLANE_PASSWORD ?? await ask(`Password for ${email}: `, true),
|
|
10053
|
+
fresh: opts.freshLogin,
|
|
10054
|
+
onLog: (m) => console.log(m)
|
|
10055
|
+
});
|
|
10056
|
+
const result = await uploadAppPrivacyDetails2({
|
|
10057
|
+
session,
|
|
10058
|
+
appId: opts.appId,
|
|
10059
|
+
usages,
|
|
10060
|
+
skipPublish: opts.skipPublish,
|
|
10061
|
+
onLog: (m) => console.log(m)
|
|
10062
|
+
});
|
|
10063
|
+
saveAppleSession2(session);
|
|
10064
|
+
console.log(`Done: ${result.created} answers${result.published ? ", published" : ""}.`);
|
|
10065
|
+
});
|
|
10066
|
+
}
|
|
10067
|
+
function ask(question, secret = false) {
|
|
10068
|
+
let muted = false;
|
|
10069
|
+
const output = new stream.Writable({
|
|
10070
|
+
write(chunk, _enc, cb) {
|
|
10071
|
+
if (!muted) process.stdout.write(chunk);
|
|
10072
|
+
cb();
|
|
10073
|
+
}
|
|
10074
|
+
});
|
|
10075
|
+
const rl = readline.createInterface({ input: process.stdin, output, terminal: true });
|
|
10076
|
+
return new Promise((resolve4) => {
|
|
10077
|
+
rl.question(question, (answer) => {
|
|
10078
|
+
rl.close();
|
|
10079
|
+
if (secret) process.stdout.write("\n");
|
|
10080
|
+
resolve4(answer.trim());
|
|
10081
|
+
});
|
|
10082
|
+
muted = secret;
|
|
10083
|
+
});
|
|
10084
|
+
}
|
|
10085
|
+
function createRejectionsCommand() {
|
|
10086
|
+
return new commander.Command("rejections").description("Show App Review's Resolution Center messages and rejection reasons").requiredOption("--app-id <ids...>", "App Store Connect app id(s) (numeric Apple ID)").option("-u, --username <email>", "Apple ID (defaults to the last one signed in)", process.env.FASTNODE_USER ?? process.env.FASTLANE_USER).option("--fresh-login", "Ignore the saved Apple ID session and sign in again", false).option("--all", "Include resolved submissions, not just ones awaiting you", false).option("--json", "Print JSON instead of text", false).action(async (opts) => {
|
|
10087
|
+
const { signInWithAppleId: signInWithAppleId2, saveAppleSession: saveAppleSession2 } = await Promise.resolve().then(() => (init_session_store(), session_store_exports));
|
|
10088
|
+
const { getReviewFeedback: getReviewFeedback2 } = await Promise.resolve().then(() => (init_review(), review_exports));
|
|
10089
|
+
const session = await signInWithAppleId2({
|
|
10090
|
+
email: opts.username,
|
|
10091
|
+
askEmail: () => ask2("Apple ID: "),
|
|
10092
|
+
askPassword: async (email) => process.env.FASTNODE_PASSWORD ?? process.env.FASTLANE_PASSWORD ?? await ask2(`Password for ${email}: `, true),
|
|
10093
|
+
fresh: opts.freshLogin,
|
|
10094
|
+
onLog: (m) => console.error(m)
|
|
10095
|
+
});
|
|
10096
|
+
const all = {};
|
|
10097
|
+
for (const appId of opts.appId) {
|
|
10098
|
+
const feedback = await getReviewFeedback2(session, appId, { onlyOpen: !opts.all, onLog: (m) => console.error(m) });
|
|
10099
|
+
all[appId] = feedback;
|
|
10100
|
+
if (opts.json) continue;
|
|
10101
|
+
console.log(`
|
|
10102
|
+
\u2550\u2550 App ${appId} \u2550\u2550`);
|
|
10103
|
+
if (!feedback.length) console.log("No open App Review feedback.");
|
|
10104
|
+
for (const s of feedback) {
|
|
10105
|
+
console.log(`
|
|
10106
|
+
Submission ${s.submissionId} \u2014 ${s.state} (submitted ${s.submittedDate ?? "?"})`);
|
|
10107
|
+
for (const t of s.threads) {
|
|
10108
|
+
console.log(` Thread ${t.threadType ?? "?"} \xB7 ${t.state ?? "?"} \xB7 ${t.createdDate ?? ""}`);
|
|
10109
|
+
for (const m of t.messages) {
|
|
10110
|
+
console.log(`
|
|
10111
|
+
\u2500\u2500 ${m.from ?? "App Review"} \xB7 ${m.createdDate ?? ""}`);
|
|
10112
|
+
for (const r of m.reasons) console.log(` \u25B8 ${[r.section, r.description].filter(Boolean).join(" \u2014 ")}${r.code ? ` [${r.code}]` : ""}`);
|
|
10113
|
+
console.log(m.body.split("\n").map((l) => ` ${l}`).join("\n"));
|
|
10114
|
+
}
|
|
10115
|
+
}
|
|
10116
|
+
}
|
|
10117
|
+
}
|
|
10118
|
+
saveAppleSession2(session);
|
|
10119
|
+
if (opts.json) console.log(JSON.stringify(all, null, 2));
|
|
10120
|
+
});
|
|
10121
|
+
}
|
|
10122
|
+
function ask2(question, secret = false) {
|
|
10123
|
+
let muted = false;
|
|
10124
|
+
const output = new stream.Writable({ write(chunk, _enc, cb) {
|
|
10125
|
+
if (!muted) process.stdout.write(chunk);
|
|
10126
|
+
cb();
|
|
10127
|
+
} });
|
|
10128
|
+
const rl = readline.createInterface({ input: process.stdin, output, terminal: true });
|
|
10129
|
+
return new Promise((resolve4) => {
|
|
10130
|
+
rl.question(question, (a) => {
|
|
10131
|
+
rl.close();
|
|
10132
|
+
if (secret) process.stdout.write("\n");
|
|
10133
|
+
resolve4(a.trim());
|
|
10134
|
+
});
|
|
10135
|
+
muted = secret;
|
|
10136
|
+
});
|
|
10137
|
+
}
|
|
10138
|
+
function createAssetsCommand() {
|
|
10139
|
+
const assets = new commander.Command("assets").description(
|
|
10140
|
+
"App Store asset library \u2014 screenshots, previews, product page headers, search results assets"
|
|
10141
|
+
);
|
|
10142
|
+
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) => {
|
|
10143
|
+
const { resolveClientFromEnv: resolveClientFromEnv2 } = await Promise.resolve().then(() => (init_options(), options_exports));
|
|
10144
|
+
const client = await resolveClientFromEnv2();
|
|
10145
|
+
const feature = opts.feature;
|
|
10146
|
+
const refData = await client.appAssetLibraries.refData({
|
|
10147
|
+
features: [feature],
|
|
10148
|
+
placementTypes: opts.placementType ? [opts.placementType] : void 0
|
|
10149
|
+
});
|
|
10150
|
+
if (opts.json) {
|
|
10151
|
+
console.log(JSON.stringify(refData, null, 2));
|
|
10152
|
+
return;
|
|
10153
|
+
}
|
|
10154
|
+
const policies = refData.features?.find((f) => f.featureId === feature)?.placementPolicies ?? [];
|
|
10155
|
+
const specs = new Map([...refData.imageSpecs ?? [], ...refData.videoSpecs ?? []].map((s) => [s.specId, s]));
|
|
10156
|
+
for (const type of refData.placementTypes ?? []) {
|
|
10157
|
+
if (opts.placementType && type.placementTypeId !== opts.placementType) continue;
|
|
10158
|
+
const policy = policies.find((p) => p.placementType === type.placementTypeId);
|
|
10159
|
+
if (policies.length && !policy) continue;
|
|
10160
|
+
console.log(`
|
|
10161
|
+
${type.placementTypeId} (${(type.acceptsAssetCategories ?? []).join(", ")})`);
|
|
10162
|
+
for (const mapping of type.specMappings ?? []) {
|
|
10163
|
+
const max = policy?.groupLimits?.find((l) => l.groupIds.includes(mapping.placementGroupId))?.maxCount;
|
|
10164
|
+
const sizes = mapping.specs.map((id) => specs.get(id)).filter((s) => Boolean(s)).map((s) => {
|
|
10165
|
+
const d = s.dimensions;
|
|
10166
|
+
const w = d.minWidth === d.maxWidth ? `${d.minWidth}` : `${d.minWidth}-${d.maxWidth}`;
|
|
10167
|
+
const h = d.minHeight === d.maxHeight ? `${d.minHeight}` : `${d.minHeight}-${d.maxHeight}`;
|
|
10168
|
+
return `${w}\xD7${h}`;
|
|
10169
|
+
});
|
|
10170
|
+
console.log(` ${mapping.placementGroupId.padEnd(44)} ${max ? `max ${max} ` : ""}${sizes.join(", ")}`);
|
|
10171
|
+
}
|
|
10172
|
+
}
|
|
10173
|
+
});
|
|
10174
|
+
assets.command("list").description("List images and videos in an app's asset library").requiredOption("-a, --app-identifier <id>", "Bundle identifier").action(async (opts) => {
|
|
10175
|
+
const { resolveClientFromEnv: resolveClientFromEnv2 } = await Promise.resolve().then(() => (init_options(), options_exports));
|
|
10176
|
+
const client = await resolveClientFromEnv2();
|
|
10177
|
+
const app = await client.apps.findByBundleId(opts.appIdentifier);
|
|
10178
|
+
if (!app) throw new Error(`App not found: ${opts.appIdentifier}`);
|
|
10179
|
+
const library = await client.appAssetLibraries.getForApp(app.id);
|
|
10180
|
+
const [images, videos] = await Promise.all([
|
|
10181
|
+
client.appAssetLibraries.listImages(library.id),
|
|
10182
|
+
client.appAssetLibraries.listVideos(library.id)
|
|
10183
|
+
]);
|
|
10184
|
+
for (const a of [...images, ...videos]) {
|
|
10185
|
+
const at = a.attributes;
|
|
10186
|
+
console.log(`${a.type === "appAssetLibraryImages" ? "image" : "video"} ${a.id} ${String(at.state).padEnd(22)} ${at.category} ${at.referenceName ?? at.fileName}`);
|
|
10187
|
+
}
|
|
10188
|
+
if (!images.length && !videos.length) console.log("Asset library is empty");
|
|
10189
|
+
});
|
|
10190
|
+
return assets;
|
|
10191
|
+
}
|
|
8847
10192
|
|
|
8848
10193
|
// src/cli/index.ts
|
|
8849
10194
|
init_options();
|
|
@@ -8862,6 +10207,9 @@ function createCli() {
|
|
|
8862
10207
|
program.addCommand(createMatchCommand());
|
|
8863
10208
|
program.addCommand(createActionsCommand());
|
|
8864
10209
|
program.addCommand(createLanesCommand());
|
|
10210
|
+
program.addCommand(createPrivacyCommand());
|
|
10211
|
+
program.addCommand(createRejectionsCommand());
|
|
10212
|
+
program.addCommand(createAssetsCommand());
|
|
8865
10213
|
return program;
|
|
8866
10214
|
}
|
|
8867
10215
|
|