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