@sitepong/fastnode 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -1
- package/dist/actions/index.cjs +891 -72
- package/dist/actions/index.cjs.map +1 -1
- package/dist/actions/index.mjs +892 -73
- package/dist/actions/index.mjs.map +1 -1
- package/dist/apple-session-jUzBrql5.d.cts +122 -0
- package/dist/apple-session-jUzBrql5.d.ts +122 -0
- package/dist/cert/index.cjs +17868 -24
- package/dist/cert/index.cjs.map +1 -1
- package/dist/cert/index.d.cts +5 -4
- package/dist/cert/index.d.ts +5 -4
- package/dist/cert/index.mjs +17853 -7
- package/dist/cert/index.mjs.map +1 -1
- package/dist/{cert-e5GAgAPb.d.ts → cert-RvXmdMke.d.ts} +2 -2
- package/dist/{cert-uLLdb5a7.d.cts → cert-eOoxGMN1.d.cts} +2 -2
- package/dist/cli/index.cjs +1364 -78
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.d.cts +3 -2
- package/dist/cli/index.d.ts +3 -2
- package/dist/cli/index.mjs +1365 -79
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/index.cjs +480 -31
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +3 -2
- package/dist/client/index.d.ts +3 -2
- package/dist/client/index.mjs +480 -31
- package/dist/client/index.mjs.map +1 -1
- package/dist/deliver/index.cjs +334 -13
- package/dist/deliver/index.cjs.map +1 -1
- package/dist/deliver/index.d.cts +4 -3
- package/dist/deliver/index.d.ts +4 -3
- package/dist/deliver/index.mjs +330 -15
- package/dist/deliver/index.mjs.map +1 -1
- package/dist/{enums-B609j2rb.d.cts → enums-yXNVh3UI.d.cts} +8 -1
- package/dist/{enums-B609j2rb.d.ts → enums-yXNVh3UI.d.ts} +8 -1
- package/dist/gradle/index.cjs +222 -0
- package/dist/gradle/index.cjs.map +1 -0
- package/dist/gradle/index.d.cts +1 -0
- package/dist/gradle/index.d.ts +1 -0
- package/dist/gradle/index.mjs +217 -0
- package/dist/gradle/index.mjs.map +1 -0
- package/dist/gym/index.d.cts +2 -2
- package/dist/gym/index.d.ts +2 -2
- package/dist/index-8djTPW8O.d.ts +104 -0
- package/dist/{index-DrUpRXRG.d.cts → index-B6-MJpO4.d.cts} +1 -1
- package/dist/{index-Clyf4-lu.d.cts → index-BE7bwNff.d.cts} +1 -1
- package/dist/index-BGhV0gS-.d.ts +65 -0
- package/dist/index-BaNcf7jt.d.cts +201 -0
- package/dist/{index-Di4Ic0EE.d.ts → index-BvyZ4uiw.d.ts} +3 -3
- package/dist/{index-CEldrbyz.d.ts → index-C01Rkrir.d.ts} +2 -2
- package/dist/{index-jeI44Q5G.d.ts → index-C9mHBJED.d.ts} +1 -1
- package/dist/{index-BgHagzj1.d.cts → index-CAEiXVWd.d.ts} +9 -1
- package/dist/index-CBf0-NUQ.d.ts +201 -0
- package/dist/index-CXJlvmB4.d.cts +63 -0
- package/dist/index-CXJlvmB4.d.ts +63 -0
- package/dist/{index-BNP7Bpu5.d.ts → index-D1GHEM3c.d.ts} +32 -3
- package/dist/index-DFstwgGK.d.cts +65 -0
- package/dist/{index-DrCOh0Vh.d.cts → index-DGkgRnZp.d.cts} +2 -2
- package/dist/{index-CtydPNxA.d.cts → index-DLJso7Vs.d.cts} +32 -3
- package/dist/{index-BN0iWhuw.d.cts → index-DWOLZNlz.d.cts} +2 -2
- package/dist/{index-BjkOUQcQ.d.cts → index-DfkIz7i0.d.cts} +18 -4
- package/dist/{index-EKU_G1G5.d.ts → index-DjC_gmnL.d.ts} +18 -4
- package/dist/index-DnHQ2uT2.d.cts +104 -0
- package/dist/{index-CdfgEZwh.d.cts → index-DrmS802s.d.cts} +356 -130
- package/dist/index-Dv-Mek07.d.cts +84 -0
- package/dist/index-Dv-Mek07.d.ts +84 -0
- package/dist/{index-DHxi-QPC.d.cts → index-icIH5e5V.d.cts} +3 -3
- package/dist/{index-mddpvrRX.d.ts → index-laE3_wm9.d.cts} +9 -1
- package/dist/{index-BwwpAlk2.d.ts → index-lsuS2R1T.d.ts} +356 -130
- package/dist/{index-DImN9PWc.d.ts → index-mEEB7An6.d.ts} +1 -1
- package/dist/{index-C2YLoFVl.d.ts → index-rTZOdMRE.d.ts} +2 -2
- package/dist/index.cjs +20496 -217
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +278 -22
- package/dist/index.d.ts +278 -22
- package/dist/index.mjs +20489 -220
- package/dist/index.mjs.map +1 -1
- package/dist/lane/index.cjs +891 -72
- package/dist/lane/index.cjs.map +1 -1
- package/dist/lane/index.mjs +892 -73
- package/dist/lane/index.mjs.map +1 -1
- package/dist/match/index.d.cts +6 -5
- package/dist/match/index.d.ts +6 -5
- package/dist/pem/index.d.cts +4 -3
- package/dist/pem/index.d.ts +4 -3
- package/dist/pilot/index.cjs +8 -4
- package/dist/pilot/index.cjs.map +1 -1
- package/dist/pilot/index.d.cts +4 -3
- package/dist/pilot/index.d.ts +4 -3
- package/dist/pilot/index.mjs +10 -6
- package/dist/pilot/index.mjs.map +1 -1
- package/dist/playstore/index.cjs +207 -0
- package/dist/playstore/index.cjs.map +1 -0
- package/dist/playstore/index.d.cts +1 -0
- package/dist/playstore/index.d.ts +1 -0
- package/dist/playstore/index.mjs +203 -0
- package/dist/playstore/index.mjs.map +1 -0
- package/dist/precheck/index.d.cts +4 -3
- package/dist/precheck/index.d.ts +4 -3
- package/dist/privacy/index.cjs +207 -0
- package/dist/privacy/index.cjs.map +1 -0
- package/dist/privacy/index.d.cts +2 -0
- package/dist/privacy/index.d.ts +2 -0
- package/dist/privacy/index.mjs +195 -0
- package/dist/privacy/index.mjs.map +1 -0
- package/dist/produce/index.cjs +38 -18
- package/dist/produce/index.cjs.map +1 -1
- package/dist/produce/index.d.cts +4 -3
- package/dist/produce/index.d.ts +4 -3
- package/dist/produce/index.mjs +38 -19
- package/dist/produce/index.mjs.map +1 -1
- package/dist/review/index.cjs +141 -0
- package/dist/review/index.cjs.map +1 -0
- package/dist/review/index.d.cts +2 -0
- package/dist/review/index.d.ts +2 -0
- package/dist/review/index.mjs +137 -0
- package/dist/review/index.mjs.map +1 -0
- package/dist/sigh/index.d.cts +4 -3
- package/dist/sigh/index.d.ts +4 -3
- package/dist/xcode/index.cjs +1 -1
- package/dist/xcode/index.cjs.map +1 -1
- package/dist/xcode/index.mjs +1 -1
- package/dist/xcode/index.mjs.map +1 -1
- package/package.json +27 -1
- package/dist/index-BbY_H_f6.d.ts +0 -99
- package/dist/index-XsMsVc3O.d.cts +0 -99
package/dist/actions/index.cjs
CHANGED
|
@@ -178,8 +178,8 @@ async function revokeExpiredCertificates(options) {
|
|
|
178
178
|
return count;
|
|
179
179
|
}
|
|
180
180
|
async function importToKeychain(certData, keychainPath, keychainPassword) {
|
|
181
|
-
const { tmpdir:
|
|
182
|
-
const tempCertPath = path.join(
|
|
181
|
+
const { tmpdir: tmpdir7 } = await import('os');
|
|
182
|
+
const tempCertPath = path.join(tmpdir7(), `fastnode-cert-${Date.now()}.cer`);
|
|
183
183
|
await promises.writeFile(tempCertPath, certData);
|
|
184
184
|
try {
|
|
185
185
|
const args = [
|
|
@@ -1748,6 +1748,20 @@ var init_srp = __esm({
|
|
|
1748
1748
|
};
|
|
1749
1749
|
}
|
|
1750
1750
|
});
|
|
1751
|
+
async function appleJson(res, what) {
|
|
1752
|
+
const text = await res.text();
|
|
1753
|
+
try {
|
|
1754
|
+
return JSON.parse(text);
|
|
1755
|
+
} catch {
|
|
1756
|
+
const contentType = (res.headers.get("content-type") ?? "").split(";")[0].trim();
|
|
1757
|
+
const title = /<title>([^<]{1,120})<\/title>/i.exec(text)?.[1]?.trim();
|
|
1758
|
+
const looksHtml = /^\s*<(!doctype|html)/i.test(text) || contentType === "text/html";
|
|
1759
|
+
const page = looksHtml ? "an HTML page" : contentType ? `a ${contentType} response` : "a non-JSON response";
|
|
1760
|
+
throw new Error(
|
|
1761
|
+
`Apple answered the ${what} with ${page} (HTTP ${res.status}${title ? `, "${title}"` : ""}) instead of JSON. Apple is refusing or rate-limiting sign-ins from this server right now; wait a few minutes and try again. If it keeps happening, sign in once at appleid.apple.com to clear any hold on the account.`
|
|
1762
|
+
);
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1751
1765
|
function checkPortalResponse(body, session) {
|
|
1752
1766
|
if (!body || typeof body !== "object") return;
|
|
1753
1767
|
if (body.resultCode === 4100) {
|
|
@@ -1824,7 +1838,7 @@ async function beginLogin(config) {
|
|
|
1824
1838
|
}
|
|
1825
1839
|
async function fetchTwoFactorChallenge(pending) {
|
|
1826
1840
|
const r = await fetch(`${AUTH_BASE}`, { headers: buildAuthHeadersFromPending(pending) });
|
|
1827
|
-
const body = await r
|
|
1841
|
+
const body = await appleJson(r, "two-factor challenge");
|
|
1828
1842
|
const trustedPhoneNumbers = (body.trustedPhoneNumbers ?? []).map((p) => ({
|
|
1829
1843
|
id: p.id,
|
|
1830
1844
|
numberWithDialCode: p.numberWithDialCode,
|
|
@@ -1845,7 +1859,8 @@ async function requestSmsCode(input) {
|
|
|
1845
1859
|
body: JSON.stringify({ phoneNumber: { id: input.phoneId }, mode: input.mode ?? "sms" })
|
|
1846
1860
|
});
|
|
1847
1861
|
}
|
|
1848
|
-
async function submitTwoFactorCode(
|
|
1862
|
+
async function submitTwoFactorCode(rawInput) {
|
|
1863
|
+
const input = { ...rawInput, code: rawInput.code.replace(/[\s\u00a0]+/g, "") };
|
|
1849
1864
|
const log = input.onLog ?? (() => {
|
|
1850
1865
|
});
|
|
1851
1866
|
let path;
|
|
@@ -1957,9 +1972,12 @@ async function doSirp(user, password, serviceKey, cookieStr, hashcash) {
|
|
|
1957
1972
|
body: JSON.stringify({ a: aBase64, accountName: user, protocols: ["s2k", "s2k_fo"] }),
|
|
1958
1973
|
redirect: "manual"
|
|
1959
1974
|
});
|
|
1960
|
-
const initBody = await initRes
|
|
1975
|
+
const initBody = await appleJson(initRes, "sign-in init");
|
|
1961
1976
|
if (initBody.serviceErrors) throw new Error(`Apple auth error: ${JSON.stringify(initBody.serviceErrors)}`);
|
|
1962
1977
|
const { iteration: iterations, salt: saltB64, b: bB64, c, protocol } = initBody;
|
|
1978
|
+
if (!iterations || !saltB64 || !bB64 || !c || !protocol) {
|
|
1979
|
+
throw new Error(`Apple's sign-in init answered without a challenge (HTTP ${initRes.status}): ${JSON.stringify(initBody).slice(0, 300)}`);
|
|
1980
|
+
}
|
|
1963
1981
|
const salt = Buffer.from(saltB64, "base64");
|
|
1964
1982
|
const b = Buffer.from(bB64, "base64");
|
|
1965
1983
|
const encryptedPassword = applePbkdf2(password, salt, iterations, 32, protocol);
|
|
@@ -2002,7 +2020,7 @@ async function fetchOlympusSession(session) {
|
|
|
2002
2020
|
const cookieStr = Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join("; ");
|
|
2003
2021
|
const res = await fetch(OLYMPUS_SESSION, { headers: { Cookie: cookieStr, Accept: "application/json" } });
|
|
2004
2022
|
if (res.ok) {
|
|
2005
|
-
const body = await res
|
|
2023
|
+
const body = await appleJson(res, "session lookup");
|
|
2006
2024
|
if (body?.user?.emailAddress) session.userEmail = body.user.emailAddress;
|
|
2007
2025
|
if (body?.provider?.providerId) session.teamId = String(body.provider.providerId);
|
|
2008
2026
|
}
|
|
@@ -2014,7 +2032,7 @@ async function fetchContractMessages(session) {
|
|
|
2014
2032
|
headers: { Cookie: cookieStr, Accept: "application/json" }
|
|
2015
2033
|
});
|
|
2016
2034
|
if (!res.ok) return [];
|
|
2017
|
-
const body = await res
|
|
2035
|
+
const body = await appleJson(res, "agreements lookup");
|
|
2018
2036
|
return (body ?? []).map((m) => ({
|
|
2019
2037
|
id: m.id ?? "",
|
|
2020
2038
|
group: m.group ?? "",
|
|
@@ -2025,10 +2043,22 @@ async function fetchContractMessages(session) {
|
|
|
2025
2043
|
return [];
|
|
2026
2044
|
}
|
|
2027
2045
|
}
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2046
|
+
function serviceKeyFromLocation(location) {
|
|
2047
|
+
if (!location) return null;
|
|
2048
|
+
try {
|
|
2049
|
+
const key = new URL(location).searchParams.get("widgetKey")?.trim();
|
|
2050
|
+
return key && /^[0-9a-f]{16,}$/i.test(key) ? key : null;
|
|
2051
|
+
} catch {
|
|
2052
|
+
return null;
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
async function fetchServiceKey(fetchImpl = fetch) {
|
|
2056
|
+
const head = await fetchImpl(LOGOUT_URL, { method: "HEAD", redirect: "manual" });
|
|
2057
|
+
const fromRedirect = serviceKeyFromLocation(head.headers.get("location"));
|
|
2058
|
+
if (fromRedirect) return fromRedirect;
|
|
2059
|
+
const res = await fetchImpl(WIDGET_KEY_URL);
|
|
2060
|
+
const body = await appleJson(res, "service key request");
|
|
2061
|
+
if (!body?.authServiceKey) throw new Error("Could not fetch Apple service key: the sign-out redirect carried no widgetKey and the config endpoint had no authServiceKey.");
|
|
2032
2062
|
return body.authServiceKey;
|
|
2033
2063
|
}
|
|
2034
2064
|
function buildAuthHeaders(session) {
|
|
@@ -2076,7 +2106,7 @@ async function defaultPrompt(message) {
|
|
|
2076
2106
|
});
|
|
2077
2107
|
});
|
|
2078
2108
|
}
|
|
2079
|
-
var AUTH_BASE, OLYMPUS_SESSION, WIDGET_KEY_URL, ProgramLicenseAgreementError;
|
|
2109
|
+
var AUTH_BASE, OLYMPUS_SESSION, WIDGET_KEY_URL, ProgramLicenseAgreementError, LOGOUT_URL;
|
|
2080
2110
|
var init_apple_session = __esm({
|
|
2081
2111
|
"src/client/apple-session.ts"() {
|
|
2082
2112
|
init_srp();
|
|
@@ -2091,6 +2121,7 @@ var init_apple_session = __esm({
|
|
|
2091
2121
|
}
|
|
2092
2122
|
agreements;
|
|
2093
2123
|
};
|
|
2124
|
+
LOGOUT_URL = "https://appstoreconnect.apple.com/logout";
|
|
2094
2125
|
}
|
|
2095
2126
|
});
|
|
2096
2127
|
|
|
@@ -2407,9 +2438,10 @@ async function uploadWithTransporter(options) {
|
|
|
2407
2438
|
const transporterBin = await resolveTransporter();
|
|
2408
2439
|
const platform = options.platform ?? "ios";
|
|
2409
2440
|
const timeout = options.timeout ?? 18e5;
|
|
2410
|
-
const keyDir =
|
|
2441
|
+
const keyDir = path.join(os.homedir(), ".appstoreconnect", "private_keys");
|
|
2442
|
+
await promises.mkdir(keyDir, { recursive: true });
|
|
2411
2443
|
const keyPath = path.join(keyDir, `AuthKey_${options.apiKeyId}.p8`);
|
|
2412
|
-
await promises.writeFile(keyPath, options.apiPrivateKey);
|
|
2444
|
+
await promises.writeFile(keyPath, options.apiPrivateKey, { mode: 384 });
|
|
2413
2445
|
return new Promise((resolve3) => {
|
|
2414
2446
|
const args = [
|
|
2415
2447
|
"-m",
|
|
@@ -2425,6 +2457,9 @@ async function uploadWithTransporter(options) {
|
|
|
2425
2457
|
"-WONoPause",
|
|
2426
2458
|
"true"
|
|
2427
2459
|
];
|
|
2460
|
+
if (options.assetDescriptionPath) {
|
|
2461
|
+
args.push("-assetDescription", options.assetDescriptionPath);
|
|
2462
|
+
}
|
|
2428
2463
|
if (platform !== "ios") {
|
|
2429
2464
|
args.push("-bundleType", platform);
|
|
2430
2465
|
}
|
|
@@ -2448,13 +2483,13 @@ async function uploadWithTransporter(options) {
|
|
|
2448
2483
|
child.stderr.on("data", onData);
|
|
2449
2484
|
child.on("close", async (code) => {
|
|
2450
2485
|
clearTimeout(timer);
|
|
2451
|
-
await promises.rm(
|
|
2486
|
+
await promises.rm(keyPath, { force: true }).catch(() => {
|
|
2452
2487
|
});
|
|
2453
2488
|
resolve3({ success: code === 0, output, backend: "transporter" });
|
|
2454
2489
|
});
|
|
2455
2490
|
child.on("error", async (err) => {
|
|
2456
2491
|
clearTimeout(timer);
|
|
2457
|
-
await promises.rm(
|
|
2492
|
+
await promises.rm(keyPath, { force: true }).catch(() => {
|
|
2458
2493
|
});
|
|
2459
2494
|
resolve3({
|
|
2460
2495
|
success: false,
|
|
@@ -2746,6 +2781,311 @@ var init_metadata = __esm({
|
|
|
2746
2781
|
};
|
|
2747
2782
|
}
|
|
2748
2783
|
});
|
|
2784
|
+
function categoryFor(placementType) {
|
|
2785
|
+
return placementType === "APP_SCREENSHOT" || placementType === "IMESSAGE_APP_SCREENSHOT" || placementType === "APP_PREVIEW" ? "APP_SCREENSHOTS_AND_PREVIEWS" : "CREATIVE_ASSETS";
|
|
2786
|
+
}
|
|
2787
|
+
function readImageSize(data) {
|
|
2788
|
+
if (data.length >= 26 && data.readUInt32BE(0) === 2303741511) {
|
|
2789
|
+
const colorType = data[25];
|
|
2790
|
+
return {
|
|
2791
|
+
width: data.readUInt32BE(16),
|
|
2792
|
+
height: data.readUInt32BE(20),
|
|
2793
|
+
alpha: colorType === 4 || colorType === 6
|
|
2794
|
+
};
|
|
2795
|
+
}
|
|
2796
|
+
if (data.length >= 4 && data[0] === 255 && data[1] === 216) {
|
|
2797
|
+
let offset = 2;
|
|
2798
|
+
while (offset + 9 < data.length) {
|
|
2799
|
+
if (data[offset] !== 255) return null;
|
|
2800
|
+
const marker = data[offset + 1];
|
|
2801
|
+
const length = data.readUInt16BE(offset + 2);
|
|
2802
|
+
const isSof = marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204;
|
|
2803
|
+
if (isSof) {
|
|
2804
|
+
return { height: data.readUInt16BE(offset + 5), width: data.readUInt16BE(offset + 7), alpha: false };
|
|
2805
|
+
}
|
|
2806
|
+
offset += 2 + length;
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
return null;
|
|
2810
|
+
}
|
|
2811
|
+
function fits(spec, width, height) {
|
|
2812
|
+
const d = spec.dimensions;
|
|
2813
|
+
return width >= d.minWidth && width <= d.maxWidth && height >= d.minHeight && height <= d.maxHeight;
|
|
2814
|
+
}
|
|
2815
|
+
function groupsForFeature(refData, feature, placementType) {
|
|
2816
|
+
const mappings = refData.placementTypes?.find((p) => p.placementTypeId === placementType)?.specMappings ?? [];
|
|
2817
|
+
const all = mappings.map((m) => m.placementGroupId);
|
|
2818
|
+
const policy = refData.features?.find((f) => f.featureId === feature)?.placementPolicies?.find((p) => p.placementType === placementType);
|
|
2819
|
+
const limited = policy?.groupLimits?.flatMap((l) => l.groupIds);
|
|
2820
|
+
return limited?.length ? all.filter((g) => limited.includes(g)) : all;
|
|
2821
|
+
}
|
|
2822
|
+
function maxCountFor(refData, feature, placementType, placementGroup) {
|
|
2823
|
+
return refData.features?.find((f) => f.featureId === feature)?.placementPolicies?.find((p) => p.placementType === placementType)?.groupLimits?.find((l) => l.groupIds.includes(placementGroup))?.maxCount;
|
|
2824
|
+
}
|
|
2825
|
+
function resolveImageGroups(refData, feature, placementType, size) {
|
|
2826
|
+
const specs = (refData.imageSpecs ?? []).filter(
|
|
2827
|
+
(s) => s.compatiblePlacementTypes.includes(placementType) && fits(s, size.width, size.height)
|
|
2828
|
+
);
|
|
2829
|
+
const specIds = new Set(specs.map((s) => s.specId));
|
|
2830
|
+
const allowed = new Set(groupsForFeature(refData, feature, placementType));
|
|
2831
|
+
const mappings = refData.placementTypes?.find((p) => p.placementTypeId === placementType)?.specMappings ?? [];
|
|
2832
|
+
const groups = mappings.filter((m) => allowed.has(m.placementGroupId) && m.specs.some((id) => specIds.has(id))).map((m) => m.placementGroupId);
|
|
2833
|
+
return { groups: [...new Set(groups)], spec: specs[0] };
|
|
2834
|
+
}
|
|
2835
|
+
async function runUploadOperations(ops, data) {
|
|
2836
|
+
for (const op of ops) {
|
|
2837
|
+
const headers = {};
|
|
2838
|
+
for (const h of op.requestHeaders) headers[h.name] = h.value;
|
|
2839
|
+
const res = await fetch(op.url, {
|
|
2840
|
+
method: op.method,
|
|
2841
|
+
headers,
|
|
2842
|
+
body: data.subarray(op.offset, op.offset + op.length)
|
|
2843
|
+
});
|
|
2844
|
+
if (!res.ok) {
|
|
2845
|
+
throw new Error(`Upload operation failed: ${op.method} ${new URL(op.url).host} \u2192 HTTP ${res.status}`);
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
async function uploadLibraryAsset(options) {
|
|
2850
|
+
const { client, libraryId, filePath, category } = options;
|
|
2851
|
+
const data = await promises.readFile(filePath);
|
|
2852
|
+
const fileName = path.basename(filePath);
|
|
2853
|
+
const kind = VIDEO_EXTENSIONS.includes(path.extname(filePath).toLowerCase()) ? "video" : "image";
|
|
2854
|
+
const hash = crypto.createHash("sha256").update(data).digest("hex").slice(0, 12);
|
|
2855
|
+
const referenceName = `${options.referenceName ?? fileName} #${hash}`;
|
|
2856
|
+
const media = kind === "image" ? client.appAssetLibraryImages : client.appAssetLibraryVideos;
|
|
2857
|
+
const filter = { filter: { referenceName } };
|
|
2858
|
+
const existing = kind === "image" ? await client.appAssetLibraries.listImages(libraryId, filter) : await client.appAssetLibraries.listVideos(libraryId, filter);
|
|
2859
|
+
const reusable = existing.find(
|
|
2860
|
+
(a) => a.attributes.category === category && a.attributes.state && READY_STATES.includes(a.attributes.state)
|
|
2861
|
+
);
|
|
2862
|
+
if (reusable) {
|
|
2863
|
+
return { kind, id: reusable.id, state: reusable.attributes.state, reused: true };
|
|
2864
|
+
}
|
|
2865
|
+
const reserved = kind === "image" ? await client.appAssetLibraryImages.create({ libraryId, fileName, fileSize: data.length, category, referenceName }) : await client.appAssetLibraryVideos.create({
|
|
2866
|
+
libraryId,
|
|
2867
|
+
fileName,
|
|
2868
|
+
fileSize: data.length,
|
|
2869
|
+
category,
|
|
2870
|
+
referenceName,
|
|
2871
|
+
previewFrameTimeCode: options.previewFrameTimeCode
|
|
2872
|
+
});
|
|
2873
|
+
await runUploadOperations(reserved.attributes.uploadOperations ?? [], data);
|
|
2874
|
+
await media.update(reserved.id, { uploaded: true });
|
|
2875
|
+
options.log?.(`uploaded ${fileName} (${kind}, ${data.length} bytes)`);
|
|
2876
|
+
const state = await waitForAssetReady(
|
|
2877
|
+
() => media.get(reserved.id),
|
|
2878
|
+
options.pollIntervalMs ?? 3e3,
|
|
2879
|
+
options.timeoutMs ?? (kind === "video" ? 30 * 6e4 : 5 * 6e4)
|
|
2880
|
+
);
|
|
2881
|
+
return { kind, id: reserved.id, state, reused: false };
|
|
2882
|
+
}
|
|
2883
|
+
async function waitForAssetReady(get, intervalMs, timeoutMs) {
|
|
2884
|
+
const deadline = Date.now() + timeoutMs;
|
|
2885
|
+
for (; ; ) {
|
|
2886
|
+
const asset = await get();
|
|
2887
|
+
const state = asset.attributes.state;
|
|
2888
|
+
if (state === "FAILED") {
|
|
2889
|
+
const why = (asset.attributes.stateDetails ?? []).map((d) => d.message ?? d.code).filter(Boolean).join("; ");
|
|
2890
|
+
throw new Error(`Asset processing failed${why ? `: ${why}` : ""}`);
|
|
2891
|
+
}
|
|
2892
|
+
if (state && READY_STATES.includes(state)) return state;
|
|
2893
|
+
if (Date.now() > deadline) throw new Error(`Asset still ${state ?? "unknown"} after ${timeoutMs / 1e3}s`);
|
|
2894
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2897
|
+
async function listFiles(dir) {
|
|
2898
|
+
return (await promises.readdir(dir)).filter((f) => !f.startsWith(".")).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }));
|
|
2899
|
+
}
|
|
2900
|
+
async function isDir(path) {
|
|
2901
|
+
return (await promises.stat(path)).isDirectory();
|
|
2902
|
+
}
|
|
2903
|
+
async function planLocale(localeDir, refData, feature, folders, warn) {
|
|
2904
|
+
const planned = [];
|
|
2905
|
+
for (const [folder, placementType] of Object.entries(folders)) {
|
|
2906
|
+
const typeDir = path.join(localeDir, folder);
|
|
2907
|
+
if (!fs.existsSync(typeDir)) continue;
|
|
2908
|
+
const known = new Set(groupsForFeature(refData, feature, placementType));
|
|
2909
|
+
const entries = [];
|
|
2910
|
+
for (const entry of await listFiles(typeDir)) {
|
|
2911
|
+
const full = path.join(typeDir, entry);
|
|
2912
|
+
if (await isDir(full)) {
|
|
2913
|
+
if (!known.has(entry)) {
|
|
2914
|
+
warn(`${path.relative(localeDir, full)}: "${entry}" is not a ${placementType} placement group for ${feature} \u2014 skipped`);
|
|
2915
|
+
continue;
|
|
2916
|
+
}
|
|
2917
|
+
for (const f of await listFiles(full)) entries.push({ filePath: path.join(full, f), group: entry });
|
|
2918
|
+
} else {
|
|
2919
|
+
entries.push({ filePath: full });
|
|
2920
|
+
}
|
|
2921
|
+
}
|
|
2922
|
+
for (const { filePath, group } of entries) {
|
|
2923
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
2924
|
+
const isVideo = VIDEO_EXTENSIONS.includes(ext);
|
|
2925
|
+
if (!isVideo && !IMAGE_EXTENSIONS.includes(ext)) continue;
|
|
2926
|
+
const needsVideo = placementType === "APP_PREVIEW";
|
|
2927
|
+
const needsImage = placementType === "APP_SCREENSHOT" || placementType === "IMESSAGE_APP_SCREENSHOT";
|
|
2928
|
+
if (needsVideo && !isVideo || needsImage && isVideo) {
|
|
2929
|
+
warn(`${path.relative(localeDir, filePath)}: ${isVideo ? "video" : "image"} cannot be a ${placementType} \u2014 skipped`);
|
|
2930
|
+
continue;
|
|
2931
|
+
}
|
|
2932
|
+
if (group) {
|
|
2933
|
+
planned.push({ filePath, placementType, groups: [group] });
|
|
2934
|
+
continue;
|
|
2935
|
+
}
|
|
2936
|
+
if (isVideo) {
|
|
2937
|
+
if (known.size === 1) planned.push({ filePath, placementType, groups: [...known] });
|
|
2938
|
+
else warn(`${path.relative(localeDir, filePath)}: put videos in a placement-group folder (one of ${[...known].join(", ")}) \u2014 skipped`);
|
|
2939
|
+
continue;
|
|
2940
|
+
}
|
|
2941
|
+
const size = readImageSize(await promises.readFile(filePath));
|
|
2942
|
+
if (!size) {
|
|
2943
|
+
warn(`${path.relative(localeDir, filePath)}: could not read image size \u2014 skipped`);
|
|
2944
|
+
continue;
|
|
2945
|
+
}
|
|
2946
|
+
const { groups, spec } = resolveImageGroups(refData, feature, placementType, size);
|
|
2947
|
+
if (!groups.length) {
|
|
2948
|
+
warn(`${path.relative(localeDir, filePath)}: ${size.width}\xD7${size.height} matches no ${placementType} spec \u2014 skipped`);
|
|
2949
|
+
continue;
|
|
2950
|
+
}
|
|
2951
|
+
if (size.alpha && spec && spec.alphaAllowed === false) {
|
|
2952
|
+
warn(`${path.relative(localeDir, filePath)}: has an alpha channel, which Apple rejects for this spec`);
|
|
2953
|
+
}
|
|
2954
|
+
planned.push({ filePath, placementType, groups: groups.slice(0, 1) });
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
return planned;
|
|
2958
|
+
}
|
|
2959
|
+
async function uploadAssetLibrary(options) {
|
|
2960
|
+
const { client } = options;
|
|
2961
|
+
const log = options.log ?? (() => {
|
|
2962
|
+
});
|
|
2963
|
+
const feature = "APP_STORE_VERSIONS";
|
|
2964
|
+
const result = { uploaded: 0, reused: 0, placed: 0, removed: 0, warnings: [] };
|
|
2965
|
+
const warn = (line) => {
|
|
2966
|
+
result.warnings.push(line);
|
|
2967
|
+
log(`warning: ${line}`);
|
|
2968
|
+
};
|
|
2969
|
+
const [library, refData, localizations] = await Promise.all([
|
|
2970
|
+
client.appAssetLibraries.getForApp(options.appId),
|
|
2971
|
+
client.appAssetLibraries.refData({ features: [feature] }),
|
|
2972
|
+
client.appStoreVersions.getLocalizations(options.versionId)
|
|
2973
|
+
]);
|
|
2974
|
+
const folders = { ...options.folders ?? ASSET_FOLDERS };
|
|
2975
|
+
if (options.rootPlacementType) folders["."] = options.rootPlacementType;
|
|
2976
|
+
const uploads = /* @__PURE__ */ new Map();
|
|
2977
|
+
const uploadOnce = (filePath, placementType) => {
|
|
2978
|
+
const key = `${filePath}|${categoryFor(placementType)}`;
|
|
2979
|
+
if (!uploads.has(key)) {
|
|
2980
|
+
uploads.set(
|
|
2981
|
+
key,
|
|
2982
|
+
uploadLibraryAsset({
|
|
2983
|
+
client,
|
|
2984
|
+
libraryId: library.id,
|
|
2985
|
+
filePath,
|
|
2986
|
+
category: categoryFor(placementType),
|
|
2987
|
+
referenceName: path.relative(options.assetsPath, filePath),
|
|
2988
|
+
log
|
|
2989
|
+
}).then((a) => {
|
|
2990
|
+
if (a.reused) result.reused++;
|
|
2991
|
+
else result.uploaded++;
|
|
2992
|
+
return a;
|
|
2993
|
+
})
|
|
2994
|
+
);
|
|
2995
|
+
}
|
|
2996
|
+
return uploads.get(key);
|
|
2997
|
+
};
|
|
2998
|
+
for (const loc of localizations) {
|
|
2999
|
+
const localeDir = path.join(options.assetsPath, loc.attributes.locale);
|
|
3000
|
+
if (!fs.existsSync(localeDir)) continue;
|
|
3001
|
+
const target = { kind: "appStoreVersionLocalization", id: loc.id };
|
|
3002
|
+
const plan = await planLocale(localeDir, refData, feature, folders, warn);
|
|
3003
|
+
const slots = /* @__PURE__ */ new Map();
|
|
3004
|
+
for (const p of plan) {
|
|
3005
|
+
for (const group of p.groups) {
|
|
3006
|
+
const key = `${p.placementType}|${group}`;
|
|
3007
|
+
if (!slots.has(key)) slots.set(key, { placementType: p.placementType, group, files: [] });
|
|
3008
|
+
slots.get(key).files.push(p.filePath);
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
for (const slot of slots.values()) {
|
|
3012
|
+
const where = `${loc.attributes.locale} ${slot.placementType} ${slot.group}`;
|
|
3013
|
+
const max = maxCountFor(refData, feature, slot.placementType, slot.group);
|
|
3014
|
+
if (max !== void 0 && slot.files.length > max) {
|
|
3015
|
+
warn(`${where}: ${slot.files.length} files but Apple allows ${max} \u2014 keeping the first ${max}`);
|
|
3016
|
+
slot.files = slot.files.slice(0, max);
|
|
3017
|
+
}
|
|
3018
|
+
const existing = await client.appAssetLibraryPlacements.listForTarget(target, {
|
|
3019
|
+
placementType: slot.placementType,
|
|
3020
|
+
placementGroup: slot.group,
|
|
3021
|
+
include: "image,video"
|
|
3022
|
+
});
|
|
3023
|
+
if (options.overwrite) {
|
|
3024
|
+
for (const p of existing) {
|
|
3025
|
+
await client.appAssetLibraryPlacements.delete(p.id);
|
|
3026
|
+
result.removed++;
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
3029
|
+
const kept = options.overwrite ? [] : existing;
|
|
3030
|
+
const keptAssetIds = new Set(kept.map((p) => placementAssetId(p)).filter(Boolean));
|
|
3031
|
+
const ours = [];
|
|
3032
|
+
for (const filePath of slot.files) {
|
|
3033
|
+
const asset = await uploadOnce(filePath, slot.placementType);
|
|
3034
|
+
if (keptAssetIds.has(asset.id)) continue;
|
|
3035
|
+
const placement = await client.appAssetLibraryPlacements.create({
|
|
3036
|
+
placementType: slot.placementType,
|
|
3037
|
+
placementGroup: slot.group,
|
|
3038
|
+
...asset.kind === "image" ? { imageId: asset.id } : { videoId: asset.id },
|
|
3039
|
+
target
|
|
3040
|
+
});
|
|
3041
|
+
ours.push(placement.id);
|
|
3042
|
+
result.placed++;
|
|
3043
|
+
}
|
|
3044
|
+
log(`${where}: placed ${ours.length}${kept.length ? `, kept ${kept.length}` : ""}`);
|
|
3045
|
+
const orderable = slot.placementType === "APP_SCREENSHOT" || slot.placementType === "APP_PREVIEW" || slot.placementType === "IMESSAGE_APP_SCREENSHOT";
|
|
3046
|
+
if (orderable && ours.length + kept.length > 1) {
|
|
3047
|
+
await client.appAssetLibraryPlacements.order({
|
|
3048
|
+
placementGroup: slot.group,
|
|
3049
|
+
placementIds: [...ours, ...kept.map((p) => p.id)],
|
|
3050
|
+
target
|
|
3051
|
+
});
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
return result;
|
|
3056
|
+
}
|
|
3057
|
+
function placementAssetId(p) {
|
|
3058
|
+
for (const rel of ["image", "video"]) {
|
|
3059
|
+
const data = p.relationships?.[rel]?.data;
|
|
3060
|
+
if (data?.id) return data.id;
|
|
3061
|
+
}
|
|
3062
|
+
return void 0;
|
|
3063
|
+
}
|
|
3064
|
+
var ASSET_FOLDERS, IMAGE_EXTENSIONS, VIDEO_EXTENSIONS, READY_STATES;
|
|
3065
|
+
var init_assets = __esm({
|
|
3066
|
+
"src/deliver/assets.ts"() {
|
|
3067
|
+
ASSET_FOLDERS = {
|
|
3068
|
+
screenshots: "APP_SCREENSHOT",
|
|
3069
|
+
imessage_screenshots: "IMESSAGE_APP_SCREENSHOT",
|
|
3070
|
+
previews: "APP_PREVIEW",
|
|
3071
|
+
header: "PRODUCT_PAGE_HEADER_ASSET",
|
|
3072
|
+
search_results: "APP_STORE_SEARCH_RESULTS_ASSET",
|
|
3073
|
+
event_card: "EVENT_CARD_ASSET",
|
|
3074
|
+
event_details: "EVENT_DETAILS_PAGE_ASSET"
|
|
3075
|
+
};
|
|
3076
|
+
IMAGE_EXTENSIONS = [".png", ".jpg", ".jpeg"];
|
|
3077
|
+
VIDEO_EXTENSIONS = [".mp4", ".mov", ".m4v"];
|
|
3078
|
+
READY_STATES = [
|
|
3079
|
+
"COMPLETE",
|
|
3080
|
+
"PREPARE_FOR_SUBMISSION",
|
|
3081
|
+
"READY_FOR_REVIEW",
|
|
3082
|
+
"WAITING_FOR_REVIEW",
|
|
3083
|
+
"IN_REVIEW",
|
|
3084
|
+
"ACCEPTED",
|
|
3085
|
+
"APPROVED"
|
|
3086
|
+
];
|
|
3087
|
+
}
|
|
3088
|
+
});
|
|
2749
3089
|
|
|
2750
3090
|
// src/xcode/upload.ts
|
|
2751
3091
|
var upload_exports = {};
|
|
@@ -2811,8 +3151,14 @@ var init_upload = __esm({
|
|
|
2811
3151
|
// src/deliver/index.ts
|
|
2812
3152
|
var deliver_exports = {};
|
|
2813
3153
|
__export(deliver_exports, {
|
|
3154
|
+
ASSET_FOLDERS: () => ASSET_FOLDERS,
|
|
3155
|
+
categoryFor: () => categoryFor,
|
|
2814
3156
|
loadMetadata: () => loadMetadata,
|
|
3157
|
+
readImageSize: () => readImageSize,
|
|
3158
|
+
resolveImageGroups: () => resolveImageGroups,
|
|
2815
3159
|
submitForReview: () => submitForReview,
|
|
3160
|
+
uploadAssetLibrary: () => uploadAssetLibrary,
|
|
3161
|
+
uploadLibraryAsset: () => uploadLibraryAsset,
|
|
2816
3162
|
uploadMetadata: () => uploadMetadata,
|
|
2817
3163
|
uploadScreenshots: () => uploadScreenshots,
|
|
2818
3164
|
uploadToAppStore: () => uploadToAppStore
|
|
@@ -2851,11 +3197,36 @@ async function uploadToAppStore(options) {
|
|
|
2851
3197
|
copyright: options.copyright
|
|
2852
3198
|
});
|
|
2853
3199
|
}
|
|
3200
|
+
const log = (line) => options.onOutput?.(`${line}
|
|
3201
|
+
`);
|
|
2854
3202
|
if (!options.skipScreenshots && options.screenshotsPath && fs.existsSync(options.screenshotsPath)) {
|
|
2855
|
-
|
|
3203
|
+
if (options.useAssetLibrary) {
|
|
3204
|
+
await uploadAssetLibrary({
|
|
3205
|
+
client: options.client,
|
|
3206
|
+
appId: app.id,
|
|
3207
|
+
versionId: version.id,
|
|
3208
|
+
assetsPath: options.screenshotsPath,
|
|
3209
|
+
folders: {},
|
|
3210
|
+
rootPlacementType: "APP_SCREENSHOT",
|
|
3211
|
+
overwrite: options.overwriteAssets,
|
|
3212
|
+
log
|
|
3213
|
+
});
|
|
3214
|
+
} else {
|
|
3215
|
+
await uploadScreenshots({
|
|
3216
|
+
client: options.client,
|
|
3217
|
+
versionId: version.id,
|
|
3218
|
+
screenshotsPath: options.screenshotsPath
|
|
3219
|
+
});
|
|
3220
|
+
}
|
|
3221
|
+
}
|
|
3222
|
+
if (!options.skipAssets && options.assetsPath && fs.existsSync(options.assetsPath)) {
|
|
3223
|
+
await uploadAssetLibrary({
|
|
2856
3224
|
client: options.client,
|
|
3225
|
+
appId: app.id,
|
|
2857
3226
|
versionId: version.id,
|
|
2858
|
-
|
|
3227
|
+
assetsPath: options.assetsPath,
|
|
3228
|
+
overwrite: options.overwriteAssets,
|
|
3229
|
+
log
|
|
2859
3230
|
});
|
|
2860
3231
|
}
|
|
2861
3232
|
if (!options.skipBinaryUpload) {
|
|
@@ -2940,17 +3311,7 @@ async function uploadScreenshots(options) {
|
|
|
2940
3311
|
fileName: file,
|
|
2941
3312
|
fileSize: fileData.length
|
|
2942
3313
|
});
|
|
2943
|
-
|
|
2944
|
-
if (ops) {
|
|
2945
|
-
for (const op of ops) {
|
|
2946
|
-
const chunk = fileData.subarray(op.offset, op.offset + op.length);
|
|
2947
|
-
const headers = {};
|
|
2948
|
-
for (const h of op.requestHeaders) {
|
|
2949
|
-
headers[h.name] = h.value;
|
|
2950
|
-
}
|
|
2951
|
-
await fetch(op.url, { method: op.method, headers, body: chunk });
|
|
2952
|
-
}
|
|
2953
|
-
}
|
|
3314
|
+
await runUploadOperations(reserved.attributes.uploadOperations ?? [], fileData);
|
|
2954
3315
|
await options.client.appScreenshots.commitScreenshot(reserved.id, {
|
|
2955
3316
|
sourceFileChecksum: checksum,
|
|
2956
3317
|
uploaded: true
|
|
@@ -2970,7 +3331,9 @@ function detectDisplayType(filename) {
|
|
|
2970
3331
|
var init_deliver = __esm({
|
|
2971
3332
|
"src/deliver/index.ts"() {
|
|
2972
3333
|
init_metadata();
|
|
3334
|
+
init_assets();
|
|
2973
3335
|
init_metadata();
|
|
3336
|
+
init_assets();
|
|
2974
3337
|
}
|
|
2975
3338
|
});
|
|
2976
3339
|
|
|
@@ -2979,6 +3342,7 @@ var produce_exports = {};
|
|
|
2979
3342
|
__export(produce_exports, {
|
|
2980
3343
|
SERVICE_NAME_TO_CAPABILITY: () => SERVICE_NAME_TO_CAPABILITY,
|
|
2981
3344
|
addCapability: () => addCapability,
|
|
3345
|
+
associateAppGroups: () => associateAppGroups,
|
|
2982
3346
|
buildCapabilitySettings: () => buildCapabilitySettings,
|
|
2983
3347
|
createApp: () => createApp,
|
|
2984
3348
|
enableServices: () => enableServices,
|
|
@@ -3073,6 +3437,30 @@ async function enableServices(options) {
|
|
|
3073
3437
|
});
|
|
3074
3438
|
}
|
|
3075
3439
|
}
|
|
3440
|
+
async function associateAppGroups(options) {
|
|
3441
|
+
if (!options.appGroups?.length) return;
|
|
3442
|
+
await options.portalClient.enableAppGroups(options.bundleIdId).catch(() => {
|
|
3443
|
+
});
|
|
3444
|
+
const existing = await options.portalClient.listAppGroups();
|
|
3445
|
+
const appGroupIds = [];
|
|
3446
|
+
for (const group of options.appGroups) {
|
|
3447
|
+
let record = existing.find((ag) => ag.identifier === group.identifier);
|
|
3448
|
+
if (!record) {
|
|
3449
|
+
record = await options.portalClient.createAppGroup({
|
|
3450
|
+
name: group.name,
|
|
3451
|
+
identifier: group.identifier
|
|
3452
|
+
});
|
|
3453
|
+
}
|
|
3454
|
+
const id = record?.applicationGroup ?? record?.applicationGroupId ?? record?.appGroupId ?? record?.id;
|
|
3455
|
+
if (id) appGroupIds.push(id);
|
|
3456
|
+
}
|
|
3457
|
+
if (appGroupIds.length) {
|
|
3458
|
+
await options.portalClient.assignAppGroups({
|
|
3459
|
+
appIdId: options.bundleIdId,
|
|
3460
|
+
appGroupIds
|
|
3461
|
+
});
|
|
3462
|
+
}
|
|
3463
|
+
}
|
|
3076
3464
|
async function createApp(options) {
|
|
3077
3465
|
const existing = await options.client.apps.findByBundleId(options.bundleIdentifier);
|
|
3078
3466
|
if (existing) return existing;
|
|
@@ -3091,25 +3479,20 @@ async function createApp(options) {
|
|
|
3091
3479
|
});
|
|
3092
3480
|
}
|
|
3093
3481
|
}
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
data: { type: "bundleIds", id: bundleId.id }
|
|
3108
|
-
}
|
|
3109
|
-
}
|
|
3110
|
-
}
|
|
3482
|
+
if (options.appGroups?.length && options.portalClient) {
|
|
3483
|
+
await associateAppGroups({
|
|
3484
|
+
portalClient: options.portalClient,
|
|
3485
|
+
bundleIdId: bundleId.id,
|
|
3486
|
+
appGroups: options.appGroups
|
|
3487
|
+
});
|
|
3488
|
+
}
|
|
3489
|
+
return options.client.apps.create({
|
|
3490
|
+
bundleIdentifier: options.bundleIdentifier,
|
|
3491
|
+
bundleIdId: bundleId.id,
|
|
3492
|
+
appName: options.appName,
|
|
3493
|
+
sku: options.sku ?? `${options.bundleIdentifier}.${Date.now()}`,
|
|
3494
|
+
primaryLocale: options.primaryLocale
|
|
3111
3495
|
});
|
|
3112
|
-
return response.data;
|
|
3113
3496
|
}
|
|
3114
3497
|
var SERVICE_NAME_TO_CAPABILITY;
|
|
3115
3498
|
var init_produce = __esm({
|
|
@@ -3912,10 +4295,10 @@ async function parseTestResults(options) {
|
|
|
3912
4295
|
} else if (inputPath.endsWith(".plist")) {
|
|
3913
4296
|
testSuites = parsePlistResults(inputPath);
|
|
3914
4297
|
} else {
|
|
3915
|
-
const
|
|
4298
|
+
const stat7 = await import('fs').then(
|
|
3916
4299
|
(fs) => fs.statSync(inputPath)
|
|
3917
4300
|
);
|
|
3918
|
-
if (
|
|
4301
|
+
if (stat7.isDirectory()) {
|
|
3919
4302
|
requireMacOS("xcresult parsing");
|
|
3920
4303
|
testSuites = await parseXCResult(inputPath);
|
|
3921
4304
|
} else {
|
|
@@ -4903,6 +5286,43 @@ var init_apps = __esm({
|
|
|
4903
5286
|
});
|
|
4904
5287
|
return results[0] ?? null;
|
|
4905
5288
|
}
|
|
5289
|
+
/**
|
|
5290
|
+
* Create an App Store Connect **app record** (the thing that shows up
|
|
5291
|
+
* under "My Apps" and that TestFlight / App Store uploads require).
|
|
5292
|
+
*
|
|
5293
|
+
* This is distinct from registering an App ID in the Developer Portal —
|
|
5294
|
+
* you need BOTH. The Developer Portal App ID unlocks provisioning
|
|
5295
|
+
* profiles and entitlements; the App Store Connect app record unlocks
|
|
5296
|
+
* uploads. `upload_to_testflight` fails with "Couldn't find app
|
|
5297
|
+
* 'com.foo.bar' on the account…" when this record is missing.
|
|
5298
|
+
*
|
|
5299
|
+
* `bundleIdId` is the REST API primary key of the bundle id resource
|
|
5300
|
+
* (not the identifier string) — fetch it via `bundleIds.find(identifier)`
|
|
5301
|
+
* or from the response of `bundleIds.create()`.
|
|
5302
|
+
*
|
|
5303
|
+
* `sku` is an arbitrary unique string Apple uses to identify the app in
|
|
5304
|
+
* their internal records. If you don't have a meaningful one, pass the
|
|
5305
|
+
* bundle identifier itself or append a timestamp for uniqueness.
|
|
5306
|
+
*/
|
|
5307
|
+
async create(params) {
|
|
5308
|
+
const response = await this.http.post("/v1/apps", {
|
|
5309
|
+
data: {
|
|
5310
|
+
type: "apps",
|
|
5311
|
+
attributes: {
|
|
5312
|
+
bundleId: params.bundleIdentifier,
|
|
5313
|
+
name: params.appName,
|
|
5314
|
+
sku: params.sku,
|
|
5315
|
+
primaryLocale: params.primaryLocale ?? "en-US"
|
|
5316
|
+
},
|
|
5317
|
+
relationships: {
|
|
5318
|
+
bundleId: {
|
|
5319
|
+
data: { type: "bundleIds", id: params.bundleIdId }
|
|
5320
|
+
}
|
|
5321
|
+
}
|
|
5322
|
+
}
|
|
5323
|
+
});
|
|
5324
|
+
return response.data;
|
|
5325
|
+
}
|
|
4906
5326
|
};
|
|
4907
5327
|
}
|
|
4908
5328
|
});
|
|
@@ -6055,9 +6475,22 @@ var init_pre_release_versions = __esm({
|
|
|
6055
6475
|
});
|
|
6056
6476
|
|
|
6057
6477
|
// src/resources/review-submission-items.ts
|
|
6058
|
-
var ReviewSubmissionItemsResource;
|
|
6478
|
+
var ITEM_RELATIONSHIPS, ReviewSubmissionItemsResource;
|
|
6059
6479
|
var init_review_submission_items = __esm({
|
|
6060
6480
|
"src/resources/review-submission-items.ts"() {
|
|
6481
|
+
ITEM_RELATIONSHIPS = {
|
|
6482
|
+
appStoreVersionId: ["appStoreVersion", "appStoreVersions"],
|
|
6483
|
+
appCustomProductPageVersionId: ["appCustomProductPageVersion", "appCustomProductPageVersions"],
|
|
6484
|
+
appStoreVersionExperimentId: ["appStoreVersionExperiment", "appStoreVersionExperiments"],
|
|
6485
|
+
appStoreVersionExperimentV2Id: ["appStoreVersionExperimentV2", "appStoreVersionExperiments"],
|
|
6486
|
+
appEventId: ["appEvent", "appEvents"],
|
|
6487
|
+
appAssetLibraryImageId: ["appAssetLibraryImage", "appAssetLibraryImages"],
|
|
6488
|
+
appAssetLibraryVideoId: ["appAssetLibraryVideo", "appAssetLibraryVideos"],
|
|
6489
|
+
backgroundAssetVersionId: ["backgroundAssetVersion", "backgroundAssetVersions"],
|
|
6490
|
+
inAppPurchaseVersionId: ["inAppPurchaseVersion", "inAppPurchaseVersions"],
|
|
6491
|
+
subscriptionVersionId: ["subscriptionVersion", "subscriptionVersions"],
|
|
6492
|
+
subscriptionGroupVersionId: ["subscriptionGroupVersion", "subscriptionGroupVersions"]
|
|
6493
|
+
};
|
|
6061
6494
|
ReviewSubmissionItemsResource = class {
|
|
6062
6495
|
constructor(http) {
|
|
6063
6496
|
this.http = http;
|
|
@@ -6079,32 +6512,20 @@ var init_review_submission_items = __esm({
|
|
|
6079
6512
|
);
|
|
6080
6513
|
return response.data;
|
|
6081
6514
|
}
|
|
6515
|
+
/**
|
|
6516
|
+
* Add one item to a review submission. Besides versions, asset library
|
|
6517
|
+
* images and videos can be submitted on their own (ASC API 4.5.1) so
|
|
6518
|
+
* creative assets are approved ahead of a campaign.
|
|
6519
|
+
*/
|
|
6082
6520
|
async create(params) {
|
|
6083
6521
|
const relationships = {
|
|
6084
6522
|
reviewSubmission: {
|
|
6085
6523
|
data: { type: "reviewSubmissions", id: params.reviewSubmissionId }
|
|
6086
6524
|
}
|
|
6087
6525
|
};
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
};
|
|
6092
|
-
}
|
|
6093
|
-
if (params.appCustomProductPageVersionId) {
|
|
6094
|
-
relationships.appCustomProductPageVersion = {
|
|
6095
|
-
data: {
|
|
6096
|
-
type: "appCustomProductPageVersions",
|
|
6097
|
-
id: params.appCustomProductPageVersionId
|
|
6098
|
-
}
|
|
6099
|
-
};
|
|
6100
|
-
}
|
|
6101
|
-
if (params.appStoreVersionExperimentId) {
|
|
6102
|
-
relationships.appStoreVersionExperiment = {
|
|
6103
|
-
data: {
|
|
6104
|
-
type: "appStoreVersionExperiments",
|
|
6105
|
-
id: params.appStoreVersionExperimentId
|
|
6106
|
-
}
|
|
6107
|
-
};
|
|
6526
|
+
for (const [param, [relationship, type]] of Object.entries(ITEM_RELATIONSHIPS)) {
|
|
6527
|
+
const id = params[param];
|
|
6528
|
+
if (id) relationships[relationship] = { data: { type, id } };
|
|
6108
6529
|
}
|
|
6109
6530
|
const response = await this.http.post(
|
|
6110
6531
|
"/v1/reviewSubmissionItems",
|
|
@@ -6335,6 +6756,204 @@ var init_xcode_metrics = __esm({
|
|
|
6335
6756
|
}
|
|
6336
6757
|
});
|
|
6337
6758
|
|
|
6759
|
+
// src/resources/app-asset-libraries.ts
|
|
6760
|
+
function mergeRefData(parts) {
|
|
6761
|
+
const merged = {
|
|
6762
|
+
features: [],
|
|
6763
|
+
placementProfileGroups: [],
|
|
6764
|
+
imageSpecs: [],
|
|
6765
|
+
videoSpecs: [],
|
|
6766
|
+
placementTypes: [],
|
|
6767
|
+
displayClasses: []
|
|
6768
|
+
};
|
|
6769
|
+
for (const part of parts) {
|
|
6770
|
+
for (const key of Object.keys(merged)) {
|
|
6771
|
+
merged[key].push(...part[key] ?? []);
|
|
6772
|
+
}
|
|
6773
|
+
}
|
|
6774
|
+
return merged;
|
|
6775
|
+
}
|
|
6776
|
+
var AppAssetLibrariesResource;
|
|
6777
|
+
var init_app_asset_libraries = __esm({
|
|
6778
|
+
"src/resources/app-asset-libraries.ts"() {
|
|
6779
|
+
AppAssetLibrariesResource = class {
|
|
6780
|
+
constructor(http) {
|
|
6781
|
+
this.http = http;
|
|
6782
|
+
}
|
|
6783
|
+
http;
|
|
6784
|
+
async getForApp(appId) {
|
|
6785
|
+
const response = await this.http.get(
|
|
6786
|
+
`/v1/apps/${appId}/assetLibrary`
|
|
6787
|
+
);
|
|
6788
|
+
return response.data;
|
|
6789
|
+
}
|
|
6790
|
+
async listImages(libraryId, params = {}) {
|
|
6791
|
+
return this.http.all(`/v1/appAssetLibraries/${libraryId}/images`, params);
|
|
6792
|
+
}
|
|
6793
|
+
async listVideos(libraryId, params = {}) {
|
|
6794
|
+
return this.http.all(`/v1/appAssetLibraries/${libraryId}/videos`, params);
|
|
6795
|
+
}
|
|
6796
|
+
/**
|
|
6797
|
+
* Specs, placement groups and limits. Apple adds placement groups (new
|
|
6798
|
+
* devices) without an API version bump, so read them from here rather than
|
|
6799
|
+
* hard-coding display types.
|
|
6800
|
+
*/
|
|
6801
|
+
async refData(params = {}) {
|
|
6802
|
+
const filter = {};
|
|
6803
|
+
if (params.features?.length) filter.features = params.features;
|
|
6804
|
+
if (params.placementTypes?.length) filter.placementTypes = params.placementTypes;
|
|
6805
|
+
if (params.placementProfileGroups?.length) filter.placementProfileGroups = params.placementProfileGroups;
|
|
6806
|
+
const data = await this.http.all("/v1/appAssetLibraryRefData", { filter });
|
|
6807
|
+
return mergeRefData(data.map((d) => d.attributes));
|
|
6808
|
+
}
|
|
6809
|
+
};
|
|
6810
|
+
}
|
|
6811
|
+
});
|
|
6812
|
+
|
|
6813
|
+
// src/resources/app-asset-library-assets.ts
|
|
6814
|
+
var AssetLibraryMediaResource, AppAssetLibraryImagesResource, AppAssetLibraryVideosResource;
|
|
6815
|
+
var init_app_asset_library_assets = __esm({
|
|
6816
|
+
"src/resources/app-asset-library-assets.ts"() {
|
|
6817
|
+
AssetLibraryMediaResource = class {
|
|
6818
|
+
constructor(http) {
|
|
6819
|
+
this.http = http;
|
|
6820
|
+
}
|
|
6821
|
+
http;
|
|
6822
|
+
async get(id) {
|
|
6823
|
+
const response = await this.http.get(`/v1/${this.type}/${id}`);
|
|
6824
|
+
return response.data;
|
|
6825
|
+
}
|
|
6826
|
+
async reserve(params, extra = {}) {
|
|
6827
|
+
const { libraryId, ...attributes } = params;
|
|
6828
|
+
const response = await this.http.post(`/v1/${this.type}`, {
|
|
6829
|
+
data: {
|
|
6830
|
+
type: this.type,
|
|
6831
|
+
attributes: { ...attributes, ...extra },
|
|
6832
|
+
relationships: {
|
|
6833
|
+
assetLibrary: { data: { type: "appAssetLibraries", id: libraryId } }
|
|
6834
|
+
}
|
|
6835
|
+
}
|
|
6836
|
+
});
|
|
6837
|
+
return response.data;
|
|
6838
|
+
}
|
|
6839
|
+
async patch(id, attributes) {
|
|
6840
|
+
const response = await this.http.patch(`/v1/${this.type}/${id}`, {
|
|
6841
|
+
data: { type: this.type, id, attributes }
|
|
6842
|
+
});
|
|
6843
|
+
return response.data;
|
|
6844
|
+
}
|
|
6845
|
+
/** Fails with 409 STATE_ERROR.ASSET_HAS_PLACEMENTS while the asset is still placed. */
|
|
6846
|
+
async delete(id) {
|
|
6847
|
+
await this.http.delete(`/v1/${this.type}/${id}`);
|
|
6848
|
+
}
|
|
6849
|
+
async listPlacements(id, params = {}) {
|
|
6850
|
+
return this.http.all(`/v1/${this.type}/${id}/placements`, params);
|
|
6851
|
+
}
|
|
6852
|
+
};
|
|
6853
|
+
AppAssetLibraryImagesResource = class extends AssetLibraryMediaResource {
|
|
6854
|
+
type = "appAssetLibraryImages";
|
|
6855
|
+
async create(params) {
|
|
6856
|
+
return this.reserve(params);
|
|
6857
|
+
}
|
|
6858
|
+
async update(id, attributes) {
|
|
6859
|
+
return this.patch(id, { ...attributes });
|
|
6860
|
+
}
|
|
6861
|
+
};
|
|
6862
|
+
AppAssetLibraryVideosResource = class extends AssetLibraryMediaResource {
|
|
6863
|
+
type = "appAssetLibraryVideos";
|
|
6864
|
+
async create(params) {
|
|
6865
|
+
const { previewFrameTimeCode, ...rest } = params;
|
|
6866
|
+
return this.reserve(rest, previewFrameTimeCode ? { previewFrameTimeCode } : {});
|
|
6867
|
+
}
|
|
6868
|
+
async update(id, attributes) {
|
|
6869
|
+
return this.patch(id, { ...attributes });
|
|
6870
|
+
}
|
|
6871
|
+
};
|
|
6872
|
+
}
|
|
6873
|
+
});
|
|
6874
|
+
|
|
6875
|
+
// src/resources/app-asset-library-placements.ts
|
|
6876
|
+
function targetRelationship(target) {
|
|
6877
|
+
return { [target.kind]: { data: { type: TARGET_TYPES[target.kind], id: target.id } } };
|
|
6878
|
+
}
|
|
6879
|
+
var TARGET_TYPES, AppAssetLibraryPlacementsResource;
|
|
6880
|
+
var init_app_asset_library_placements = __esm({
|
|
6881
|
+
"src/resources/app-asset-library-placements.ts"() {
|
|
6882
|
+
TARGET_TYPES = {
|
|
6883
|
+
appStoreVersionLocalization: "appStoreVersionLocalizations",
|
|
6884
|
+
appCustomProductPageLocalization: "appCustomProductPageLocalizations",
|
|
6885
|
+
appEventLocalization: "appEventLocalizations",
|
|
6886
|
+
appStoreVersionExperimentTreatmentLocalization: "appStoreVersionExperimentTreatmentLocalizations"
|
|
6887
|
+
};
|
|
6888
|
+
AppAssetLibraryPlacementsResource = class {
|
|
6889
|
+
constructor(http) {
|
|
6890
|
+
this.http = http;
|
|
6891
|
+
}
|
|
6892
|
+
http;
|
|
6893
|
+
/**
|
|
6894
|
+
* Place an image or video on one localization. Placements cannot be edited —
|
|
6895
|
+
* delete and recreate. The parent version/page must be editable.
|
|
6896
|
+
*/
|
|
6897
|
+
async create(params) {
|
|
6898
|
+
if (!params.imageId === !params.videoId) {
|
|
6899
|
+
throw new Error("A placement needs exactly one of imageId or videoId");
|
|
6900
|
+
}
|
|
6901
|
+
const media = params.imageId ? { image: { data: { type: "appAssetLibraryImages", id: params.imageId } } } : { video: { data: { type: "appAssetLibraryVideos", id: params.videoId } } };
|
|
6902
|
+
const response = await this.http.post(
|
|
6903
|
+
"/v1/appAssetLibraryPlacements",
|
|
6904
|
+
{
|
|
6905
|
+
data: {
|
|
6906
|
+
type: "appAssetLibraryPlacements",
|
|
6907
|
+
attributes: { placementType: params.placementType, placementGroup: params.placementGroup },
|
|
6908
|
+
relationships: { ...media, ...targetRelationship(params.target) }
|
|
6909
|
+
}
|
|
6910
|
+
}
|
|
6911
|
+
);
|
|
6912
|
+
return response.data;
|
|
6913
|
+
}
|
|
6914
|
+
async get(id, params = {}) {
|
|
6915
|
+
const response = await this.http.get(
|
|
6916
|
+
`/v1/appAssetLibraryPlacements/${id}`,
|
|
6917
|
+
params.include ? { include: params.include } : void 0
|
|
6918
|
+
);
|
|
6919
|
+
return response.data;
|
|
6920
|
+
}
|
|
6921
|
+
async delete(id) {
|
|
6922
|
+
await this.http.delete(`/v1/appAssetLibraryPlacements/${id}`);
|
|
6923
|
+
}
|
|
6924
|
+
/** Placements on one localization, in display order by default. */
|
|
6925
|
+
async listForTarget(target, params = {}) {
|
|
6926
|
+
const filter = {};
|
|
6927
|
+
if (params.placementType) filter.placementType = params.placementType;
|
|
6928
|
+
if (params.placementGroup) filter.placementGroup = params.placementGroup;
|
|
6929
|
+
const listParams = { filter, include: params.include, sort: "placementGroupPosition" };
|
|
6930
|
+
return this.http.all(
|
|
6931
|
+
`/v1/${TARGET_TYPES[target.kind]}/${target.id}/placements`,
|
|
6932
|
+
listParams
|
|
6933
|
+
);
|
|
6934
|
+
}
|
|
6935
|
+
/** Set the display order of a placement group. In-app event localizations cannot be ordered. */
|
|
6936
|
+
async order(params) {
|
|
6937
|
+
if (params.target.kind === "appEventLocalization") {
|
|
6938
|
+
throw new Error("In-app event placements cannot be ordered");
|
|
6939
|
+
}
|
|
6940
|
+
await this.http.post("/v1/appAssetLibraryPlacementOrderingRequests", {
|
|
6941
|
+
data: {
|
|
6942
|
+
type: "appAssetLibraryPlacementOrderingRequests",
|
|
6943
|
+
attributes: { placementGroup: params.placementGroup },
|
|
6944
|
+
relationships: {
|
|
6945
|
+
orderedPlacements: {
|
|
6946
|
+
data: params.placementIds.map((id) => ({ type: "appAssetLibraryPlacements", id }))
|
|
6947
|
+
},
|
|
6948
|
+
...targetRelationship(params.target)
|
|
6949
|
+
}
|
|
6950
|
+
}
|
|
6951
|
+
});
|
|
6952
|
+
}
|
|
6953
|
+
};
|
|
6954
|
+
}
|
|
6955
|
+
});
|
|
6956
|
+
|
|
6338
6957
|
// src/client/portal.ts
|
|
6339
6958
|
function pickUniqueKeyName(base, taken) {
|
|
6340
6959
|
if (!taken.has(base)) return base;
|
|
@@ -6674,7 +7293,10 @@ var init_portal = __esm({
|
|
|
6674
7293
|
}
|
|
6675
7294
|
// ── Teams ──────────────────────────────────────────────────────────────
|
|
6676
7295
|
async listTeams() {
|
|
6677
|
-
const data = await this.post("account/listTeams.action", {
|
|
7296
|
+
const data = await this.post("account/listTeams.action", {
|
|
7297
|
+
pageSize: "500",
|
|
7298
|
+
pageNumber: "1"
|
|
7299
|
+
});
|
|
6678
7300
|
return data.teams ?? [];
|
|
6679
7301
|
}
|
|
6680
7302
|
// ── App IDs / Bundle IDs ───────────────────────────────────────────────
|
|
@@ -6688,11 +7310,12 @@ var init_portal = __esm({
|
|
|
6688
7310
|
return data.appIds ?? [];
|
|
6689
7311
|
}
|
|
6690
7312
|
async createAppId(params) {
|
|
7313
|
+
const safeName = params.name.replace(/[^0-9A-Za-z\s]/g, " ").replace(/\s+/g, " ").trim() || "App";
|
|
6691
7314
|
const reqParams = {
|
|
6692
7315
|
teamId: this.teamId,
|
|
6693
7316
|
type: "explicit",
|
|
6694
7317
|
identifier: params.identifier,
|
|
6695
|
-
name:
|
|
7318
|
+
name: safeName
|
|
6696
7319
|
};
|
|
6697
7320
|
if (params.enablePush) reqParams.push = "on";
|
|
6698
7321
|
if (params.enableIAP !== false) reqParams.inAppPurchase = "on";
|
|
@@ -6727,6 +7350,191 @@ var init_portal = __esm({
|
|
|
6727
7350
|
featureValue: params.enabled ? "on" : "off"
|
|
6728
7351
|
});
|
|
6729
7352
|
}
|
|
7353
|
+
// ── Application Groups ──────────────────────────────────────────────────
|
|
7354
|
+
//
|
|
7355
|
+
// App Groups (e.g. `group.com.playspina.com`) are the only clean way to
|
|
7356
|
+
// share data — an auth token, in our case — between a host app and an
|
|
7357
|
+
// embedded extension (Messages / Notification Service / etc.). App Store
|
|
7358
|
+
// Connect API keys (.p8) genuinely cannot manage them; Apple only exposes
|
|
7359
|
+
// group create/associate through this legacy session-cookie portal API,
|
|
7360
|
+
// which is why they live on PortalClient (Apple ID session) and not on the
|
|
7361
|
+
// ASC REST client. Endpoint shapes ported 1:1 from Spaceship's
|
|
7362
|
+
// portal_client.rb (listApplicationGroups / addApplicationGroup /
|
|
7363
|
+
// assignApplicationGroupToAppId).
|
|
7364
|
+
/** Apple's internal feature code for the App Groups capability. */
|
|
7365
|
+
static APP_GROUPS_FEATURE = "APG3427HIY";
|
|
7366
|
+
/** List all app groups in the team. */
|
|
7367
|
+
async listAppGroups() {
|
|
7368
|
+
const data = await this.post("account/ios/identifiers/listApplicationGroups.action", {
|
|
7369
|
+
teamId: this.teamId,
|
|
7370
|
+
pageNumber: "1",
|
|
7371
|
+
pageSize: "500",
|
|
7372
|
+
sort: "name=asc"
|
|
7373
|
+
});
|
|
7374
|
+
return data.applicationGroupList ?? [];
|
|
7375
|
+
}
|
|
7376
|
+
/**
|
|
7377
|
+
* Create an app group, returning the raw group record. Idempotent —
|
|
7378
|
+
* Apple rejects a duplicate group identifier ("Multiple App Groups may
|
|
7379
|
+
* not have the same identifier"), so we list first and reuse an existing
|
|
7380
|
+
* group when the identifier already exists in the team.
|
|
7381
|
+
*
|
|
7382
|
+
* Note the two ids on the returned record: `identifier` is the
|
|
7383
|
+
* `group.com.…` string you type, while `applicationGroup` is Apple's
|
|
7384
|
+
* opaque internal handle that association actually requires — do NOT pass
|
|
7385
|
+
* the human identifier to assignAppGroups.
|
|
7386
|
+
*/
|
|
7387
|
+
async createAppGroup(params) {
|
|
7388
|
+
const existing = await this.listAppGroups();
|
|
7389
|
+
const hit = existing.find((g) => g.identifier === params.identifier);
|
|
7390
|
+
if (hit) return hit;
|
|
7391
|
+
const safeName = params.name.replace(/[^0-9A-Za-z\s]/g, " ").replace(/\s+/g, " ").trim() || "App Group";
|
|
7392
|
+
const data = await this.post("account/ios/identifiers/addApplicationGroup.action", {
|
|
7393
|
+
teamId: this.teamId,
|
|
7394
|
+
identifier: params.identifier,
|
|
7395
|
+
name: safeName
|
|
7396
|
+
});
|
|
7397
|
+
return data.applicationGroup;
|
|
7398
|
+
}
|
|
7399
|
+
/**
|
|
7400
|
+
* Enable the App Groups capability on an App ID. Required before an
|
|
7401
|
+
* App ID can have a group associated with it — mirrors Spaceship
|
|
7402
|
+
* enabling `AppService::AppGroup` before `associate_groups`.
|
|
7403
|
+
*/
|
|
7404
|
+
async enableAppGroups(appIdId) {
|
|
7405
|
+
await this.post("account/ios/identifiers/updateService.action", {
|
|
7406
|
+
teamId: this.teamId,
|
|
7407
|
+
displayId: appIdId,
|
|
7408
|
+
featureType: _PortalClient.APP_GROUPS_FEATURE,
|
|
7409
|
+
featureValue: "true"
|
|
7410
|
+
});
|
|
7411
|
+
}
|
|
7412
|
+
/**
|
|
7413
|
+
* Associate one or more app groups with an App ID. `appGroupIds` are the
|
|
7414
|
+
* internal ids (Spaceship's `app_group_id`), NOT the `group.com.…`
|
|
7415
|
+
* identifiers. Apple wants both `appIdId` and `displayId` set to the
|
|
7416
|
+
* App ID's internal id, per Spaceship's associate_groups_with_app.
|
|
7417
|
+
*/
|
|
7418
|
+
async assignAppGroups(params) {
|
|
7419
|
+
await this.post("account/ios/identifiers/assignApplicationGroupToAppId.action", {
|
|
7420
|
+
teamId: this.teamId,
|
|
7421
|
+
appIdId: params.appIdId,
|
|
7422
|
+
displayId: params.appIdId,
|
|
7423
|
+
applicationGroups: params.appGroupIds
|
|
7424
|
+
});
|
|
7425
|
+
}
|
|
7426
|
+
// ── Apple Pay merchants (OMCs) ─────────────────────────────────────────
|
|
7427
|
+
//
|
|
7428
|
+
// Merchant IDs are team-level identifiers (merchant.com.…) that an App ID's
|
|
7429
|
+
// Apple Pay capability must be LINKED to — enabling the capability alone
|
|
7430
|
+
// produces profiles that fail with "doesn't support the … Merchant ID".
|
|
7431
|
+
// Like App Groups, merchants are only manageable through the legacy
|
|
7432
|
+
// session-cookie portal API. Endpoint shapes ported 1:1 from Spaceship's
|
|
7433
|
+
// portal_client.rb (merchants / create_merchant! /
|
|
7434
|
+
// associate_merchants_with_app).
|
|
7435
|
+
/** Apple's internal feature code for the Apple Pay capability. */
|
|
7436
|
+
static APPLE_PAY_FEATURE = "OM633U5T5G";
|
|
7437
|
+
/**
|
|
7438
|
+
* List all Apple Pay merchant IDs in the team. Each record maps
|
|
7439
|
+
* (per Spaceship's merchant.rb attr_mapping): `omcId` = internal id,
|
|
7440
|
+
* `identifier` = the human `merchant.com.…` string, `name` = display name.
|
|
7441
|
+
*/
|
|
7442
|
+
async listMerchants() {
|
|
7443
|
+
const data = await this.post("account/ios/identifiers/listOMCs.action", {
|
|
7444
|
+
teamId: this.teamId,
|
|
7445
|
+
pageNumber: "1",
|
|
7446
|
+
pageSize: "500",
|
|
7447
|
+
sort: "name=asc"
|
|
7448
|
+
});
|
|
7449
|
+
return data.identifierList ?? [];
|
|
7450
|
+
}
|
|
7451
|
+
/**
|
|
7452
|
+
* Register (or reuse) a merchant ID, returning its INTERNAL id (`omcId`) —
|
|
7453
|
+
* Apple's opaque handle that association requires, NOT the human
|
|
7454
|
+
* `merchant.com.…` identifier. Idempotent: lists first and reuses an
|
|
7455
|
+
* existing merchant with the same identifier. Endpoint + params ported 1:1
|
|
7456
|
+
* from Spaceship's create_merchant! (addOMC.action).
|
|
7457
|
+
*/
|
|
7458
|
+
async createMerchant(params) {
|
|
7459
|
+
const existing = await this.listMerchants();
|
|
7460
|
+
const hit = existing.find((m) => m.identifier === params.identifier);
|
|
7461
|
+
if (hit?.omcId) return String(hit.omcId);
|
|
7462
|
+
const safeName = params.name.replace(/[^0-9A-Za-z\s]/g, " ").replace(/\s+/g, " ").trim() || "Merchant";
|
|
7463
|
+
const data = await this.post("account/ios/identifiers/addOMC.action", {
|
|
7464
|
+
name: safeName,
|
|
7465
|
+
identifier: params.identifier,
|
|
7466
|
+
teamId: this.teamId
|
|
7467
|
+
});
|
|
7468
|
+
const rec = data.omcId ?? data;
|
|
7469
|
+
const id = typeof rec === "string" ? rec : rec?.omcId ?? rec?.merchantId;
|
|
7470
|
+
if (!id) {
|
|
7471
|
+
throw new Error("Apple returned no internal merchant id (omcId) for the registered merchant.");
|
|
7472
|
+
}
|
|
7473
|
+
return String(id);
|
|
7474
|
+
}
|
|
7475
|
+
/**
|
|
7476
|
+
* Enable the Apple Pay capability on an App ID. Apple Pay is a standard
|
|
7477
|
+
* AppService (service_id OM633U5T5G) whose service_uri is the default
|
|
7478
|
+
* `updateService.action` — confirmed in Spaceship's app_service.rb.
|
|
7479
|
+
*/
|
|
7480
|
+
async enableApplePay(appIdId) {
|
|
7481
|
+
await this.post("account/ios/identifiers/updateService.action", {
|
|
7482
|
+
teamId: this.teamId,
|
|
7483
|
+
displayId: appIdId,
|
|
7484
|
+
featureType: _PortalClient.APPLE_PAY_FEATURE,
|
|
7485
|
+
featureValue: "true"
|
|
7486
|
+
});
|
|
7487
|
+
}
|
|
7488
|
+
/**
|
|
7489
|
+
* Associate merchant IDs with an App ID's Apple Pay capability. `omcIds`
|
|
7490
|
+
* are the internal ids (each merchant record's `omcId`), NOT the
|
|
7491
|
+
* `merchant.com.…` identifiers. Endpoint + params ported 1:1 from
|
|
7492
|
+
* Spaceship's associate_merchants_with_app (assignOMCToAppId.action — note
|
|
7493
|
+
* the singular "OMC", and NO displayId param).
|
|
7494
|
+
*/
|
|
7495
|
+
async assignMerchants(params) {
|
|
7496
|
+
await this.post("account/ios/identifiers/assignOMCToAppId.action", {
|
|
7497
|
+
teamId: this.teamId,
|
|
7498
|
+
appIdId: params.appIdId,
|
|
7499
|
+
omcIds: params.omcIds
|
|
7500
|
+
});
|
|
7501
|
+
}
|
|
7502
|
+
/**
|
|
7503
|
+
* Create an Apple Pay **Payment Processing Certificate** on a merchant ID
|
|
7504
|
+
* from a CSR — the certificate a PSP (e.g. Stripe) hands you as a CSR, which
|
|
7505
|
+
* must be uploaded to the Merchant ID in Apple Developer, then the resulting
|
|
7506
|
+
* `.cer` downloaded and given back to the PSP so it can decrypt payment
|
|
7507
|
+
* tokens. This is the one manual Apple-portal step in wiring up Apple Pay.
|
|
7508
|
+
*
|
|
7509
|
+
* Ported 1:1 from Spaceship's create_certificate! (submitCertificateRequest
|
|
7510
|
+
* .action): the merchant's INTERNAL id (`omcId`) is passed as BOTH `appIdId`
|
|
7511
|
+
* and `specialIdentifierDisplayId` — the latter is how the portal scopes a
|
|
7512
|
+
* cert to a merchant/special identifier rather than an App ID. The cert type
|
|
7513
|
+
* is APPLE_PAY (`4APLUP237T`). Returns the created certRequest (its
|
|
7514
|
+
* `certificateId` feeds `downloadApplePayCertificate`).
|
|
7515
|
+
*
|
|
7516
|
+
* @param omcId the merchant's internal id (from createMerchant/listMerchants)
|
|
7517
|
+
* @param csrContent the PEM CSR string (Stripe's "Apple Pay CSR")
|
|
7518
|
+
*/
|
|
7519
|
+
async createApplePayCertificate(params) {
|
|
7520
|
+
const data = await this.post("account/ios/certificate/submitCertificateRequest.action", {
|
|
7521
|
+
teamId: this.teamId,
|
|
7522
|
+
type: _PortalClient.CERT_TYPES.APPLE_PAY,
|
|
7523
|
+
csrContent: params.csrContent,
|
|
7524
|
+
appIdId: params.omcId,
|
|
7525
|
+
specialIdentifierDisplayId: params.omcId
|
|
7526
|
+
});
|
|
7527
|
+
return data.certRequest;
|
|
7528
|
+
}
|
|
7529
|
+
/**
|
|
7530
|
+
* Download an Apple Pay Payment Processing Certificate's raw bytes by its
|
|
7531
|
+
* certificate id (from `createApplePayCertificate`). Thin wrapper over
|
|
7532
|
+
* `downloadCertificate` pinned to the APPLE_PAY type — the DER `.cer` you
|
|
7533
|
+
* upload back into Stripe.
|
|
7534
|
+
*/
|
|
7535
|
+
async downloadApplePayCertificate(certificateId) {
|
|
7536
|
+
return this.downloadCertificate(certificateId, _PortalClient.CERT_TYPES.APPLE_PAY);
|
|
7537
|
+
}
|
|
6730
7538
|
// ── Certificates ───────────────────────────────────────────────────────
|
|
6731
7539
|
async listCertificates(types) {
|
|
6732
7540
|
const iosTypes = types ?? [
|
|
@@ -7266,6 +8074,9 @@ var init_client = __esm({
|
|
|
7266
8074
|
init_in_app_purchases();
|
|
7267
8075
|
init_game_center_detail();
|
|
7268
8076
|
init_xcode_metrics();
|
|
8077
|
+
init_app_asset_libraries();
|
|
8078
|
+
init_app_asset_library_assets();
|
|
8079
|
+
init_app_asset_library_placements();
|
|
7269
8080
|
init_auth();
|
|
7270
8081
|
init_request();
|
|
7271
8082
|
init_apple_session();
|
|
@@ -7307,6 +8118,10 @@ var init_client = __esm({
|
|
|
7307
8118
|
inAppPurchases;
|
|
7308
8119
|
gameCenterDetail;
|
|
7309
8120
|
xcodeMetrics;
|
|
8121
|
+
appAssetLibraries;
|
|
8122
|
+
appAssetLibraryImages;
|
|
8123
|
+
appAssetLibraryVideos;
|
|
8124
|
+
appAssetLibraryPlacements;
|
|
7310
8125
|
constructor(config) {
|
|
7311
8126
|
const tokenManager = new TokenManager({
|
|
7312
8127
|
keyId: config.keyId,
|
|
@@ -7350,6 +8165,10 @@ var init_client = __esm({
|
|
|
7350
8165
|
this.inAppPurchases = new InAppPurchasesResource(this.http);
|
|
7351
8166
|
this.gameCenterDetail = new GameCenterDetailResource(this.http);
|
|
7352
8167
|
this.xcodeMetrics = new XcodeMetricsResource(this.http);
|
|
8168
|
+
this.appAssetLibraries = new AppAssetLibrariesResource(this.http);
|
|
8169
|
+
this.appAssetLibraryImages = new AppAssetLibraryImagesResource(this.http);
|
|
8170
|
+
this.appAssetLibraryVideos = new AppAssetLibraryVideosResource(this.http);
|
|
8171
|
+
this.appAssetLibraryPlacements = new AppAssetLibraryPlacementsResource(this.http);
|
|
7353
8172
|
}
|
|
7354
8173
|
async request(path, options) {
|
|
7355
8174
|
return this.http.request(path, options);
|