@sitepong/fastnode 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -1
- package/dist/actions/index.cjs +891 -72
- package/dist/actions/index.cjs.map +1 -1
- package/dist/actions/index.mjs +892 -73
- package/dist/actions/index.mjs.map +1 -1
- package/dist/apple-session-jUzBrql5.d.cts +122 -0
- package/dist/apple-session-jUzBrql5.d.ts +122 -0
- package/dist/cert/index.cjs +17868 -24
- package/dist/cert/index.cjs.map +1 -1
- package/dist/cert/index.d.cts +5 -4
- package/dist/cert/index.d.ts +5 -4
- package/dist/cert/index.mjs +17853 -7
- package/dist/cert/index.mjs.map +1 -1
- package/dist/{cert-e5GAgAPb.d.ts → cert-RvXmdMke.d.ts} +2 -2
- package/dist/{cert-uLLdb5a7.d.cts → cert-eOoxGMN1.d.cts} +2 -2
- package/dist/cli/index.cjs +1364 -78
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.d.cts +3 -2
- package/dist/cli/index.d.ts +3 -2
- package/dist/cli/index.mjs +1365 -79
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/index.cjs +480 -31
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +3 -2
- package/dist/client/index.d.ts +3 -2
- package/dist/client/index.mjs +480 -31
- package/dist/client/index.mjs.map +1 -1
- package/dist/deliver/index.cjs +334 -13
- package/dist/deliver/index.cjs.map +1 -1
- package/dist/deliver/index.d.cts +4 -3
- package/dist/deliver/index.d.ts +4 -3
- package/dist/deliver/index.mjs +330 -15
- package/dist/deliver/index.mjs.map +1 -1
- package/dist/{enums-B609j2rb.d.cts → enums-yXNVh3UI.d.cts} +8 -1
- package/dist/{enums-B609j2rb.d.ts → enums-yXNVh3UI.d.ts} +8 -1
- package/dist/gradle/index.cjs +222 -0
- package/dist/gradle/index.cjs.map +1 -0
- package/dist/gradle/index.d.cts +1 -0
- package/dist/gradle/index.d.ts +1 -0
- package/dist/gradle/index.mjs +217 -0
- package/dist/gradle/index.mjs.map +1 -0
- package/dist/gym/index.d.cts +2 -2
- package/dist/gym/index.d.ts +2 -2
- package/dist/index-8djTPW8O.d.ts +104 -0
- package/dist/{index-DrUpRXRG.d.cts → index-B6-MJpO4.d.cts} +1 -1
- package/dist/{index-Clyf4-lu.d.cts → index-BE7bwNff.d.cts} +1 -1
- package/dist/index-BGhV0gS-.d.ts +65 -0
- package/dist/index-BaNcf7jt.d.cts +201 -0
- package/dist/{index-Di4Ic0EE.d.ts → index-BvyZ4uiw.d.ts} +3 -3
- package/dist/{index-CEldrbyz.d.ts → index-C01Rkrir.d.ts} +2 -2
- package/dist/{index-jeI44Q5G.d.ts → index-C9mHBJED.d.ts} +1 -1
- package/dist/{index-BgHagzj1.d.cts → index-CAEiXVWd.d.ts} +9 -1
- package/dist/index-CBf0-NUQ.d.ts +201 -0
- package/dist/index-CXJlvmB4.d.cts +63 -0
- package/dist/index-CXJlvmB4.d.ts +63 -0
- package/dist/{index-BNP7Bpu5.d.ts → index-D1GHEM3c.d.ts} +32 -3
- package/dist/index-DFstwgGK.d.cts +65 -0
- package/dist/{index-DrCOh0Vh.d.cts → index-DGkgRnZp.d.cts} +2 -2
- package/dist/{index-CtydPNxA.d.cts → index-DLJso7Vs.d.cts} +32 -3
- package/dist/{index-BN0iWhuw.d.cts → index-DWOLZNlz.d.cts} +2 -2
- package/dist/{index-BjkOUQcQ.d.cts → index-DfkIz7i0.d.cts} +18 -4
- package/dist/{index-EKU_G1G5.d.ts → index-DjC_gmnL.d.ts} +18 -4
- package/dist/index-DnHQ2uT2.d.cts +104 -0
- package/dist/{index-CdfgEZwh.d.cts → index-DrmS802s.d.cts} +356 -130
- package/dist/index-Dv-Mek07.d.cts +84 -0
- package/dist/index-Dv-Mek07.d.ts +84 -0
- package/dist/{index-DHxi-QPC.d.cts → index-icIH5e5V.d.cts} +3 -3
- package/dist/{index-mddpvrRX.d.ts → index-laE3_wm9.d.cts} +9 -1
- package/dist/{index-BwwpAlk2.d.ts → index-lsuS2R1T.d.ts} +356 -130
- package/dist/{index-DImN9PWc.d.ts → index-mEEB7An6.d.ts} +1 -1
- package/dist/{index-C2YLoFVl.d.ts → index-rTZOdMRE.d.ts} +2 -2
- package/dist/index.cjs +20496 -217
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +278 -22
- package/dist/index.d.ts +278 -22
- package/dist/index.mjs +20489 -220
- package/dist/index.mjs.map +1 -1
- package/dist/lane/index.cjs +891 -72
- package/dist/lane/index.cjs.map +1 -1
- package/dist/lane/index.mjs +892 -73
- package/dist/lane/index.mjs.map +1 -1
- package/dist/match/index.d.cts +6 -5
- package/dist/match/index.d.ts +6 -5
- package/dist/pem/index.d.cts +4 -3
- package/dist/pem/index.d.ts +4 -3
- package/dist/pilot/index.cjs +8 -4
- package/dist/pilot/index.cjs.map +1 -1
- package/dist/pilot/index.d.cts +4 -3
- package/dist/pilot/index.d.ts +4 -3
- package/dist/pilot/index.mjs +10 -6
- package/dist/pilot/index.mjs.map +1 -1
- package/dist/playstore/index.cjs +207 -0
- package/dist/playstore/index.cjs.map +1 -0
- package/dist/playstore/index.d.cts +1 -0
- package/dist/playstore/index.d.ts +1 -0
- package/dist/playstore/index.mjs +203 -0
- package/dist/playstore/index.mjs.map +1 -0
- package/dist/precheck/index.d.cts +4 -3
- package/dist/precheck/index.d.ts +4 -3
- package/dist/privacy/index.cjs +207 -0
- package/dist/privacy/index.cjs.map +1 -0
- package/dist/privacy/index.d.cts +2 -0
- package/dist/privacy/index.d.ts +2 -0
- package/dist/privacy/index.mjs +195 -0
- package/dist/privacy/index.mjs.map +1 -0
- package/dist/produce/index.cjs +38 -18
- package/dist/produce/index.cjs.map +1 -1
- package/dist/produce/index.d.cts +4 -3
- package/dist/produce/index.d.ts +4 -3
- package/dist/produce/index.mjs +38 -19
- package/dist/produce/index.mjs.map +1 -1
- package/dist/review/index.cjs +141 -0
- package/dist/review/index.cjs.map +1 -0
- package/dist/review/index.d.cts +2 -0
- package/dist/review/index.d.ts +2 -0
- package/dist/review/index.mjs +137 -0
- package/dist/review/index.mjs.map +1 -0
- package/dist/sigh/index.d.cts +4 -3
- package/dist/sigh/index.d.ts +4 -3
- package/dist/xcode/index.cjs +1 -1
- package/dist/xcode/index.cjs.map +1 -1
- package/dist/xcode/index.mjs +1 -1
- package/dist/xcode/index.mjs.map +1 -1
- package/package.json +27 -1
- package/dist/index-BbY_H_f6.d.ts +0 -99
- package/dist/index-XsMsVc3O.d.cts +0 -99
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/privacy/index.ts
|
|
4
|
+
var IRIS_BASE = "https://appstoreconnect.apple.com/iris/v1";
|
|
5
|
+
var OLYMPUS_SESSION = "https://appstoreconnect.apple.com/olympus/v1/session";
|
|
6
|
+
async function iris(session, method, path, body) {
|
|
7
|
+
const res = await fetch(path.startsWith("http") ? path : `${IRIS_BASE}${path}`, {
|
|
8
|
+
method,
|
|
9
|
+
headers: {
|
|
10
|
+
Cookie: cookieHeader(session),
|
|
11
|
+
Accept: "application/vnd.api+json, application/json",
|
|
12
|
+
"Content-Type": "application/vnd.api+json",
|
|
13
|
+
"X-Requested-With": "XMLHttpRequest"
|
|
14
|
+
},
|
|
15
|
+
body: void 0
|
|
16
|
+
});
|
|
17
|
+
absorbCookies(session, res);
|
|
18
|
+
const text = await res.text();
|
|
19
|
+
let parsed = null;
|
|
20
|
+
try {
|
|
21
|
+
parsed = text ? JSON.parse(text) : null;
|
|
22
|
+
} catch {
|
|
23
|
+
}
|
|
24
|
+
return { status: res.status, body: parsed, text };
|
|
25
|
+
}
|
|
26
|
+
function cookieHeader(session) {
|
|
27
|
+
return Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join("; ");
|
|
28
|
+
}
|
|
29
|
+
function absorbCookies(session, res) {
|
|
30
|
+
for (const raw of res.headers.getSetCookie?.() ?? []) {
|
|
31
|
+
const m = raw.match(/^([^=]+)=([^;]*)/);
|
|
32
|
+
if (m) session.cookies[m[1]] = m[2];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function selectProviderForApp(session, appId, onLog) {
|
|
36
|
+
const owns = async () => (await iris(session, "GET", `/apps/${appId}?fields[apps]=name`)).status === 200;
|
|
37
|
+
if (await owns()) return;
|
|
38
|
+
const res = await fetch(OLYMPUS_SESSION, { headers: { Cookie: cookieHeader(session), Accept: "application/json" } });
|
|
39
|
+
absorbCookies(session, res);
|
|
40
|
+
const info = await res.json().catch(() => null);
|
|
41
|
+
const providers = info?.availableProviders ?? [];
|
|
42
|
+
for (const p of providers) {
|
|
43
|
+
const sw = await fetch(OLYMPUS_SESSION, {
|
|
44
|
+
method: "POST",
|
|
45
|
+
headers: { Cookie: cookieHeader(session), "Content-Type": "application/json", Accept: "application/json" },
|
|
46
|
+
body: JSON.stringify({ provider: { providerId: p.providerId } })
|
|
47
|
+
});
|
|
48
|
+
absorbCookies(session, sw);
|
|
49
|
+
if (sw.ok && await owns()) {
|
|
50
|
+
onLog?.(`Switched to team "${p.name}"`);
|
|
51
|
+
session.teamId = String(p.providerId);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
throw new Error(`None of this Apple ID's ${providers.length} teams can see app ${appId}.`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// src/review/index.ts
|
|
59
|
+
var IRIS_BASE2 = "https://appstoreconnect.apple.com/iris/v1";
|
|
60
|
+
async function iris2(session, path) {
|
|
61
|
+
const res = await fetch(`${IRIS_BASE2}${path}`, {
|
|
62
|
+
headers: {
|
|
63
|
+
Cookie: Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join("; "),
|
|
64
|
+
Accept: "application/vnd.api+json, application/json",
|
|
65
|
+
"X-Requested-With": "XMLHttpRequest"
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
for (const raw of res.headers.getSetCookie?.() ?? []) {
|
|
69
|
+
const m = raw.match(/^([^=]+)=([^;]*)/);
|
|
70
|
+
if (m) session.cookies[m[1]] = m[2];
|
|
71
|
+
}
|
|
72
|
+
const text = await res.text();
|
|
73
|
+
let body = null;
|
|
74
|
+
try {
|
|
75
|
+
body = text ? JSON.parse(text) : null;
|
|
76
|
+
} catch {
|
|
77
|
+
}
|
|
78
|
+
if (!res.ok) {
|
|
79
|
+
const detail = body?.errors?.map((e) => e.detail ?? e.title).join("; ") || text.slice(0, 300);
|
|
80
|
+
throw new Error(`GET ${path} failed (HTTP ${res.status}): ${detail}`);
|
|
81
|
+
}
|
|
82
|
+
return body;
|
|
83
|
+
}
|
|
84
|
+
function htmlToText(html) {
|
|
85
|
+
if (!html) return "";
|
|
86
|
+
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();
|
|
87
|
+
}
|
|
88
|
+
function reasonsFrom(rejection) {
|
|
89
|
+
const reasons = rejection?.attributes?.reasons ?? [];
|
|
90
|
+
return (Array.isArray(reasons) ? reasons : []).map((r) => ({
|
|
91
|
+
section: r.reasonSection ?? r.section,
|
|
92
|
+
description: r.reasonDescription ?? r.description,
|
|
93
|
+
code: r.reasonCode ?? r.code
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
96
|
+
async function getThreadMessages(session, threadId) {
|
|
97
|
+
const body = await iris2(session, `/resolutionCenterThreads/${threadId}/resolutionCenterMessages?include=fromActor,rejections`);
|
|
98
|
+
const included = new Map((body?.included ?? []).map((i) => [`${i.type}:${i.id}`, i]));
|
|
99
|
+
return (body?.data ?? []).map((m) => {
|
|
100
|
+
const actor = m.relationships?.fromActor?.data;
|
|
101
|
+
const actorRow = actor ? included.get(`${actor.type}:${actor.id}`) : null;
|
|
102
|
+
const rejections = (m.relationships?.rejections?.data ?? []).map((r) => included.get(`${r.type}:${r.id}`)).filter(Boolean);
|
|
103
|
+
return {
|
|
104
|
+
id: m.id,
|
|
105
|
+
createdDate: m.attributes?.createdDate ?? null,
|
|
106
|
+
from: actorRow?.attributes?.actorType ?? actorRow?.attributes?.userFirstName ?? null,
|
|
107
|
+
body: htmlToText(m.attributes?.messageBody),
|
|
108
|
+
reasons: rejections.flatMap(reasonsFrom)
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
async function getReviewFeedback(session, appId, opts = {}) {
|
|
113
|
+
await selectProviderForApp(session, appId, opts.onLog);
|
|
114
|
+
const subs = await iris2(session, `/apps/${appId}/reviewSubmissions?limit=10`);
|
|
115
|
+
const out = [];
|
|
116
|
+
for (const s of subs?.data ?? []) {
|
|
117
|
+
const state = s.attributes?.state ?? "";
|
|
118
|
+
if (opts.onlyOpen && state !== "UNRESOLVED_ISSUES") continue;
|
|
119
|
+
const threadsBody = await iris2(session, `/resolutionCenterThreads?filter[reviewSubmission]=${s.id}&include=reviewSubmission`);
|
|
120
|
+
const threads = [];
|
|
121
|
+
for (const t of threadsBody?.data ?? []) {
|
|
122
|
+
threads.push({
|
|
123
|
+
id: t.id,
|
|
124
|
+
threadType: t.attributes?.threadType ?? null,
|
|
125
|
+
state: t.attributes?.state ?? null,
|
|
126
|
+
createdDate: t.attributes?.createdDate ?? null,
|
|
127
|
+
messages: await getThreadMessages(session, t.id)
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
if (threads.length || !opts.onlyOpen) {
|
|
131
|
+
out.push({ submissionId: s.id, state, submittedDate: s.attributes?.submittedDate ?? null, threads });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return out;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
exports.getReviewFeedback = getReviewFeedback;
|
|
138
|
+
exports.getThreadMessages = getThreadMessages;
|
|
139
|
+
exports.htmlToText = htmlToText;
|
|
140
|
+
//# sourceMappingURL=index.cjs.map
|
|
141
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/privacy/index.ts","../../src/review/index.ts"],"names":["IRIS_BASE","iris"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * App Privacy details (\"nutrition labels\") for an app in App Store Connect.\n * 1:1 port of Fastlane's `upload_app_privacy_details_to_app_store` action and\n * Spaceship's AppDataUsage / AppDataUsagesPublishState models.\n *\n * The public App Store Connect API has no endpoint for privacy details — an\n * API key cannot set them. Fastlane goes through the iris API that App Store\n * Connect's own web UI uses, authenticated with an Apple ID session, and so\n * does this: pass the AppleSession from `loginWithAppleId` / the App Release\n * sign-in.\n *\n * Usages use Fastlane's JSON format, so an existing `app_privacy_details.json`\n * works unchanged:\n *\n * [\n * { \"category\": \"EMAIL_ADDRESS\", \"purposes\": [\"APP_FUNCTIONALITY\"], \"data_protections\": [\"DATA_LINKED_TO_YOU\"] },\n * { \"data_protections\": [\"DATA_NOT_COLLECTED\"] } // an app that collects nothing\n * ]\n *\n * As in Fastlane, the upload REPLACES everything: existing usages are deleted\n * first, then the new ones are created, then the answers are published.\n */\n\nimport type { AppleSession } from \"../client/apple-session.js\";\n\nconst IRIS_BASE = \"https://appstoreconnect.apple.com/iris/v1\";\nconst OLYMPUS_SESSION = \"https://appstoreconnect.apple.com/olympus/v1/session\";\n\n/** Every category App Store Connect accepts. Anything else is refused before existing answers are deleted. */\nexport const DATA_USAGE_CATEGORIES = [\n \"PAYMENT_INFORMATION\", \"CREDIT_AND_FRAUD\", \"OTHER_FINANCIAL_INFO\",\n \"PRECISE_LOCATION\", \"COARSE_LOCATION\", \"SENSITIVE_INFO\",\n \"PHYSICAL_ADDRESS\", \"EMAIL_ADDRESS\", \"NAME\", \"PHONE_NUMBER\", \"OTHER_CONTACT_INFO\",\n \"CONTACTS\", \"EMAILS_OR_TEXT_MESSAGES\", \"PHOTOS_OR_VIDEOS\", \"AUDIO\", \"GAMEPLAY_CONTENT\",\n \"CUSTOMER_SUPPORT\", \"OTHER_USER_CONTENT\", \"BROWSING_HISTORY\", \"SEARCH_HISTORY\",\n \"USER_ID\", \"DEVICE_ID\", \"PURCHASE_HISTORY\", \"PRODUCT_INTERACTION\", \"ADVERTISING_DATA\",\n \"OTHER_USAGE_DATA\", \"CRASH_DATA\", \"PERFORMANCE_DATA\", \"OTHER_DIAGNOSTIC_DATA\",\n \"OTHER_DATA\", \"HEALTH\", \"FITNESS\",\n] as const;\nexport const DATA_USAGE_PURPOSES = [\n \"THIRD_PARTY_ADVERTISING\", \"DEVELOPERS_ADVERTISING\", \"ANALYTICS\",\n \"PRODUCT_PERSONALIZATION\", \"APP_FUNCTIONALITY\", \"OTHER_PURPOSES\",\n] as const;\nexport const DATA_PROTECTIONS = [\n \"DATA_USED_TO_TRACK_YOU\", \"DATA_LINKED_TO_YOU\", \"DATA_NOT_LINKED_TO_YOU\", \"DATA_NOT_COLLECTED\",\n] as const;\n\nexport type DataUsageCategory =\n | \"PAYMENT_INFORMATION\" | \"CREDIT_AND_FRAUD\" | \"OTHER_FINANCIAL_INFO\"\n | \"PRECISE_LOCATION\" | \"COARSE_LOCATION\" | \"SENSITIVE_INFO\"\n | \"PHYSICAL_ADDRESS\" | \"EMAIL_ADDRESS\" | \"NAME\" | \"PHONE_NUMBER\" | \"OTHER_CONTACT_INFO\"\n | \"CONTACTS\" | \"EMAILS_OR_TEXT_MESSAGES\" | \"PHOTOS_OR_VIDEOS\" | \"AUDIO\" | \"GAMEPLAY_CONTENT\"\n | \"CUSTOMER_SUPPORT\" | \"OTHER_USER_CONTENT\" | \"BROWSING_HISTORY\" | \"SEARCH_HISTORY\"\n | \"USER_ID\" | \"DEVICE_ID\" | \"PURCHASE_HISTORY\" | \"PRODUCT_INTERACTION\" | \"ADVERTISING_DATA\"\n | \"OTHER_USAGE_DATA\" | \"CRASH_DATA\" | \"PERFORMANCE_DATA\" | \"OTHER_DIAGNOSTIC_DATA\"\n | \"OTHER_DATA\" | \"HEALTH\" | \"FITNESS\";\n\nexport type DataUsagePurpose =\n | \"THIRD_PARTY_ADVERTISING\" | \"DEVELOPERS_ADVERTISING\" | \"ANALYTICS\"\n | \"PRODUCT_PERSONALIZATION\" | \"APP_FUNCTIONALITY\" | \"OTHER_PURPOSES\";\n\nexport type DataProtection =\n | \"DATA_USED_TO_TRACK_YOU\" | \"DATA_LINKED_TO_YOU\" | \"DATA_NOT_LINKED_TO_YOU\" | \"DATA_NOT_COLLECTED\";\n\n/** One entry of Fastlane's app_privacy_details.json. */\nexport interface AppPrivacyUsage {\n category?: DataUsageCategory;\n purposes?: DataUsagePurpose[];\n data_protections: DataProtection[];\n}\n\nexport interface UploadAppPrivacyOptions {\n session: AppleSession;\n /** App Store Connect app id (the numeric Apple ID). */\n appId: string;\n usages: AppPrivacyUsage[];\n /** Leave the answers saved but unpublished (Fastlane's skip_publish). */\n skipPublish?: boolean;\n onLog?: (msg: string) => void;\n}\n\nexport interface UploadAppPrivacyResult {\n deleted: number;\n created: number;\n published: boolean;\n}\n\ninterface IrisResponse { status: number; body: any; text: string }\n\nasync function iris(session: AppleSession, method: string, path: string, body?: unknown): Promise<IrisResponse> {\n const res = await fetch(path.startsWith(\"http\") ? path : `${IRIS_BASE}${path}`, {\n method,\n headers: {\n Cookie: cookieHeader(session),\n Accept: \"application/vnd.api+json, application/json\",\n \"Content-Type\": \"application/vnd.api+json\",\n \"X-Requested-With\": \"XMLHttpRequest\",\n },\n body: body ? JSON.stringify(body) : undefined,\n });\n absorbCookies(session, res);\n const text = await res.text();\n let parsed: any = null;\n try { parsed = text ? JSON.parse(text) : null; } catch { /* not JSON */ }\n return { status: res.status, body: parsed, text };\n}\n\nfunction cookieHeader(session: AppleSession): string {\n return Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join(\"; \");\n}\n\nfunction absorbCookies(session: AppleSession, res: Response): void {\n for (const raw of res.headers.getSetCookie?.() ?? []) {\n const m = raw.match(/^([^=]+)=([^;]*)/);\n if (m) session.cookies[m[1]] = m[2];\n }\n}\n\nfunction fail(what: string, r: IrisResponse): never {\n const detail = r.body?.errors?.map((e: any) => e.detail ?? e.title).join(\"; \") || r.text.slice(0, 300);\n throw new Error(`${what} failed (HTTP ${r.status}): ${detail}`);\n}\n\n/**\n * Point the session at the App Store Connect provider (team) that owns the app.\n *\n * An Apple ID in several teams signs in to whichever one it used last, and\n * iris answers 404 for another team's app. Spaceship's `select_team` does the\n * same switch: POST the provider id to the olympus session.\n */\nexport async function selectProviderForApp(session: AppleSession, appId: string, onLog?: (m: string) => void): Promise<void> {\n const owns = async () => (await iris(session, \"GET\", `/apps/${appId}?fields[apps]=name`)).status === 200;\n if (await owns()) return;\n\n const res = await fetch(OLYMPUS_SESSION, { headers: { Cookie: cookieHeader(session), Accept: \"application/json\" } });\n absorbCookies(session, res);\n const info: any = await res.json().catch(() => null);\n const providers: Array<{ providerId: number; name: string }> = info?.availableProviders ?? [];\n for (const p of providers) {\n const sw = await fetch(OLYMPUS_SESSION, {\n method: \"POST\",\n headers: { Cookie: cookieHeader(session), \"Content-Type\": \"application/json\", Accept: \"application/json\" },\n body: JSON.stringify({ provider: { providerId: p.providerId } }),\n });\n absorbCookies(session, sw);\n if (sw.ok && (await owns())) {\n onLog?.(`Switched to team \"${p.name}\"`);\n session.teamId = String(p.providerId);\n return;\n }\n }\n throw new Error(`None of this Apple ID's ${providers.length} teams can see app ${appId}.`);\n}\n\n/** Existing privacy answers for an app. */\nexport async function listAppDataUsages(session: AppleSession, appId: string): Promise<Array<{ id: string }>> {\n const all: Array<{ id: string }> = [];\n let next: string | null = `/apps/${appId}/dataUsages?include=category,grouping,purpose,dataProtection&limit=500`;\n while (next) {\n const r = await iris(session, \"GET\", next);\n if (r.status !== 200) fail(\"List app data usages\", r);\n all.push(...(r.body?.data ?? []).map((d: any) => ({ id: d.id })));\n next = r.body?.links?.next ?? null;\n }\n return all;\n}\n\n/** Spaceship AppDataUsage.create — relationships are sent only when given. */\nexport async function createAppDataUsage(\n session: AppleSession,\n input: { appId: string; category?: string | null; purpose?: string | null; dataProtection: string },\n): Promise<string> {\n const relationships: Record<string, unknown> = { app: { data: { type: \"apps\", id: input.appId } } };\n if (input.category) relationships.category = { data: { type: \"appDataUsageCategories\", id: input.category } };\n if (input.purpose) relationships.purpose = { data: { type: \"appDataUsagePurposes\", id: input.purpose } };\n relationships.dataProtection = { data: { type: \"appDataUsageDataProtections\", id: input.dataProtection } };\n const r = await iris(session, \"POST\", \"/appDataUsages\", { data: { type: \"appDataUsages\", relationships } });\n if (r.status !== 201 && r.status !== 200) fail(`Create ${input.category ?? \"\"} ${input.purpose ?? \"\"} ${input.dataProtection}`.replace(/\\s+/g, \" \"), r);\n return r.body?.data?.id;\n}\n\nexport async function deleteAppDataUsage(session: AppleSession, id: string): Promise<void> {\n const r = await iris(session, \"DELETE\", `/appDataUsages/${id}`);\n if (r.status !== 204 && r.status !== 200) fail(`Delete app data usage ${id}`, r);\n}\n\nexport async function getAppDataUsagesPublishState(session: AppleSession, appId: string): Promise<{ id: string; published: boolean }> {\n const r = await iris(session, \"GET\", `/apps/${appId}/dataUsagePublishState`);\n if (r.status !== 200) fail(\"Read privacy publish state\", r);\n return { id: r.body.data.id, published: Boolean(r.body.data.attributes?.published) };\n}\n\nexport async function publishAppDataUsages(session: AppleSession, publishStateId: string): Promise<void> {\n const r = await iris(session, \"PATCH\", `/appDataUsagesPublishState/${publishStateId}`, {\n data: { type: \"appDataUsagesPublishState\", id: publishStateId, attributes: { published: true } },\n });\n if (r.status !== 200) fail(\"Publish privacy details\", r);\n}\n\n/** Fastlane's `upload_app_privacy_details_to_app_store`. Replaces all answers, then publishes. */\nexport async function uploadAppPrivacyDetails(opts: UploadAppPrivacyOptions): Promise<UploadAppPrivacyResult> {\n const { session, appId, usages, skipPublish, onLog = () => {} } = opts;\n validateUsages(usages);\n await selectProviderForApp(session, appId, onLog);\n\n const existing = await listAppDataUsages(session, appId);\n onLog(`Deleting ${existing.length} existing answer${existing.length === 1 ? \"\" : \"s\"}`);\n for (const u of existing) await deleteAppDataUsage(session, u.id);\n\n let created = 0;\n for (const usage of usages) {\n // \"Not collected\" has no purposes but still needs one AppDataUsage, so\n // iterate over [null] — the same trick Fastlane uses.\n const purposes: Array<string | null> = usage.purposes?.length ? usage.purposes : [null];\n for (const purpose of purposes) {\n for (const protection of usage.data_protections) {\n onLog(protection === \"DATA_NOT_COLLECTED\"\n ? `Setting ${protection}`\n : `Setting ${usage.category} and ${purpose} to ${protection}`);\n await createAppDataUsage(session, { appId, category: usage.category ?? null, purpose, dataProtection: protection });\n created++;\n }\n }\n }\n\n if (skipPublish) {\n onLog(\"Skipping publish\");\n return { deleted: existing.length, created, published: false };\n }\n const state = await getAppDataUsagesPublishState(session, appId);\n await publishAppDataUsages(session, state.id);\n onLog(\"Published\");\n return { deleted: existing.length, created, published: true };\n}\n\n/** Catch the answers App Store Connect would refuse, before deleting what is there. */\nexport function validateUsages(usages: AppPrivacyUsage[]): void {\n if (!Array.isArray(usages) || usages.length === 0) throw new Error(\"No privacy usages given.\");\n const notCollected = usages.some((u) => u.data_protections?.includes(\"DATA_NOT_COLLECTED\"));\n if (notCollected && usages.length > 1) {\n throw new Error(\"DATA_NOT_COLLECTED must be the only entry: an app cannot both collect nothing and collect something.\");\n }\n const known = (list: readonly string[], v: string, what: string) => {\n if (!list.includes(v)) throw new Error(`\"${v}\" is not an App Store Connect ${what}. Use one of: ${list.join(\", \")}.`);\n };\n for (const u of usages) {\n if (!u.data_protections?.length) throw new Error(`${u.category ?? \"An entry\"} has no data_protections.`);\n // Checked here, before anything is deleted: App Store Connect refuses an\n // unknown id only when that one answer is created, halfway through.\n if (u.category) known(DATA_USAGE_CATEGORIES, u.category, \"data category\");\n for (const p of u.purposes ?? []) known(DATA_USAGE_PURPOSES, p, \"purpose\");\n for (const d of u.data_protections) known(DATA_PROTECTIONS, d, \"data protection\");\n if (u.data_protections.includes(\"DATA_NOT_COLLECTED\")) continue;\n if (!u.category) throw new Error(\"Every collected entry needs a category.\");\n if (!u.purposes?.length) throw new Error(`${u.category} needs at least one purpose.`);\n const linked = u.data_protections.includes(\"DATA_LINKED_TO_YOU\");\n const unlinked = u.data_protections.includes(\"DATA_NOT_LINKED_TO_YOU\");\n if (linked && unlinked) throw new Error(`${u.category} cannot be both linked and not linked to the user.`);\n }\n}\n","/**\n * App Review rejections: what App Review said, from the Resolution Center.\n * Port of Spaceship's ResolutionCenterThread / ResolutionCenterMessage /\n * ReviewRejection models (connect_api/tunes).\n *\n * Like App Privacy, this is not in the public App Store Connect API — with an\n * API key, /v1/resolutionCenterThreads answers \"resource does not exist\".\n * Spaceship reaches it through its Tunes client, whose host is\n * appstoreconnect.apple.com/iris/, i.e. an Apple ID session. So does this.\n */\n\nimport type { AppleSession } from \"../client/apple-session.js\";\nimport { selectProviderForApp } from \"../privacy/index.js\";\n\nconst IRIS_BASE = \"https://appstoreconnect.apple.com/iris/v1\";\n\nexport interface RejectionReason {\n section?: string;\n description?: string;\n code?: string;\n}\n\nexport interface ResolutionMessage {\n id: string;\n createdDate: string | null;\n from: string | null;\n /** The message as App Review wrote it, HTML reduced to text. */\n body: string;\n reasons: RejectionReason[];\n}\n\nexport interface ResolutionThread {\n id: string;\n threadType: string | null;\n state: string | null;\n createdDate: string | null;\n messages: ResolutionMessage[];\n}\n\nexport interface SubmissionReview {\n submissionId: string;\n state: string;\n submittedDate: string | null;\n threads: ResolutionThread[];\n}\n\nasync function iris(session: AppleSession, path: string): Promise<any> {\n const res = await fetch(`${IRIS_BASE}${path}`, {\n headers: {\n Cookie: Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join(\"; \"),\n Accept: \"application/vnd.api+json, application/json\",\n \"X-Requested-With\": \"XMLHttpRequest\",\n },\n });\n for (const raw of res.headers.getSetCookie?.() ?? []) {\n const m = raw.match(/^([^=]+)=([^;]*)/);\n if (m) session.cookies[m[1]] = m[2];\n }\n const text = await res.text();\n let body: any = null;\n try { body = text ? JSON.parse(text) : null; } catch { /* not JSON */ }\n if (!res.ok) {\n const detail = body?.errors?.map((e: any) => e.detail ?? e.title).join(\"; \") || text.slice(0, 300);\n throw new Error(`GET ${path} failed (HTTP ${res.status}): ${detail}`);\n }\n return body;\n}\n\n/** App Review writes HTML; keep paragraphs and list items, drop the markup. */\nexport function htmlToText(html: string | null | undefined): string {\n if (!html) return \"\";\n return html\n .replace(/<br\\s*\\/?>/gi, \"\\n\")\n .replace(/<\\/(p|div|li|h\\d)>/gi, \"\\n\")\n .replace(/<li[^>]*>/gi, \"- \")\n .replace(/<[^>]+>/g, \"\")\n .replace(/ /g, \" \").replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\")\n .replace(/"/g, '\"').replace(/'/g, \"'\")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim();\n}\n\nfunction reasonsFrom(rejection: any): RejectionReason[] {\n const reasons = rejection?.attributes?.reasons ?? [];\n return (Array.isArray(reasons) ? reasons : []).map((r: any) => ({\n section: r.reasonSection ?? r.section,\n description: r.reasonDescription ?? r.description,\n code: r.reasonCode ?? r.code,\n }));\n}\n\n/** The messages in one thread, each with the guideline reasons App Review attached. */\nexport async function getThreadMessages(session: AppleSession, threadId: string): Promise<ResolutionMessage[]> {\n const body = await iris(session, `/resolutionCenterThreads/${threadId}/resolutionCenterMessages?include=fromActor,rejections`);\n const included = new Map<string, any>((body?.included ?? []).map((i: any) => [`${i.type}:${i.id}`, i]));\n return (body?.data ?? []).map((m: any) => {\n const actor = m.relationships?.fromActor?.data;\n const actorRow = actor ? included.get(`${actor.type}:${actor.id}`) : null;\n const rejections = (m.relationships?.rejections?.data ?? []).map((r: any) => included.get(`${r.type}:${r.id}`)).filter(Boolean);\n return {\n id: m.id,\n createdDate: m.attributes?.createdDate ?? null,\n from: actorRow?.attributes?.actorType ?? actorRow?.attributes?.userFirstName ?? null,\n body: htmlToText(m.attributes?.messageBody),\n reasons: rejections.flatMap(reasonsFrom),\n };\n });\n}\n\n/**\n * Every review submission for the app that App Review has answered, with its\n * Resolution Center threads and messages. `onlyOpen` keeps the ones still\n * waiting on the developer (UNRESOLVED_ISSUES).\n */\nexport async function getReviewFeedback(\n session: AppleSession,\n appId: string,\n opts: { onlyOpen?: boolean; onLog?: (m: string) => void } = {},\n): Promise<SubmissionReview[]> {\n await selectProviderForApp(session, appId, opts.onLog);\n const subs = await iris(session, `/apps/${appId}/reviewSubmissions?limit=10`);\n const out: SubmissionReview[] = [];\n for (const s of subs?.data ?? []) {\n const state = s.attributes?.state ?? \"\";\n if (opts.onlyOpen && state !== \"UNRESOLVED_ISSUES\") continue;\n const threadsBody = await iris(session, `/resolutionCenterThreads?filter[reviewSubmission]=${s.id}&include=reviewSubmission`);\n const threads: ResolutionThread[] = [];\n for (const t of threadsBody?.data ?? []) {\n threads.push({\n id: t.id,\n threadType: t.attributes?.threadType ?? null,\n state: t.attributes?.state ?? null,\n createdDate: t.attributes?.createdDate ?? null,\n messages: await getThreadMessages(session, t.id),\n });\n }\n if (threads.length || !opts.onlyOpen) {\n out.push({ submissionId: s.id, state, submittedDate: s.attributes?.submittedDate ?? null, threads });\n }\n }\n return out;\n}\n"]}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// src/privacy/index.ts
|
|
2
|
+
var IRIS_BASE = "https://appstoreconnect.apple.com/iris/v1";
|
|
3
|
+
var OLYMPUS_SESSION = "https://appstoreconnect.apple.com/olympus/v1/session";
|
|
4
|
+
async function iris(session, method, path, body) {
|
|
5
|
+
const res = await fetch(path.startsWith("http") ? path : `${IRIS_BASE}${path}`, {
|
|
6
|
+
method,
|
|
7
|
+
headers: {
|
|
8
|
+
Cookie: cookieHeader(session),
|
|
9
|
+
Accept: "application/vnd.api+json, application/json",
|
|
10
|
+
"Content-Type": "application/vnd.api+json",
|
|
11
|
+
"X-Requested-With": "XMLHttpRequest"
|
|
12
|
+
},
|
|
13
|
+
body: void 0
|
|
14
|
+
});
|
|
15
|
+
absorbCookies(session, res);
|
|
16
|
+
const text = await res.text();
|
|
17
|
+
let parsed = null;
|
|
18
|
+
try {
|
|
19
|
+
parsed = text ? JSON.parse(text) : null;
|
|
20
|
+
} catch {
|
|
21
|
+
}
|
|
22
|
+
return { status: res.status, body: parsed, text };
|
|
23
|
+
}
|
|
24
|
+
function cookieHeader(session) {
|
|
25
|
+
return Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join("; ");
|
|
26
|
+
}
|
|
27
|
+
function absorbCookies(session, res) {
|
|
28
|
+
for (const raw of res.headers.getSetCookie?.() ?? []) {
|
|
29
|
+
const m = raw.match(/^([^=]+)=([^;]*)/);
|
|
30
|
+
if (m) session.cookies[m[1]] = m[2];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function selectProviderForApp(session, appId, onLog) {
|
|
34
|
+
const owns = async () => (await iris(session, "GET", `/apps/${appId}?fields[apps]=name`)).status === 200;
|
|
35
|
+
if (await owns()) return;
|
|
36
|
+
const res = await fetch(OLYMPUS_SESSION, { headers: { Cookie: cookieHeader(session), Accept: "application/json" } });
|
|
37
|
+
absorbCookies(session, res);
|
|
38
|
+
const info = await res.json().catch(() => null);
|
|
39
|
+
const providers = info?.availableProviders ?? [];
|
|
40
|
+
for (const p of providers) {
|
|
41
|
+
const sw = await fetch(OLYMPUS_SESSION, {
|
|
42
|
+
method: "POST",
|
|
43
|
+
headers: { Cookie: cookieHeader(session), "Content-Type": "application/json", Accept: "application/json" },
|
|
44
|
+
body: JSON.stringify({ provider: { providerId: p.providerId } })
|
|
45
|
+
});
|
|
46
|
+
absorbCookies(session, sw);
|
|
47
|
+
if (sw.ok && await owns()) {
|
|
48
|
+
onLog?.(`Switched to team "${p.name}"`);
|
|
49
|
+
session.teamId = String(p.providerId);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
throw new Error(`None of this Apple ID's ${providers.length} teams can see app ${appId}.`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/review/index.ts
|
|
57
|
+
var IRIS_BASE2 = "https://appstoreconnect.apple.com/iris/v1";
|
|
58
|
+
async function iris2(session, path) {
|
|
59
|
+
const res = await fetch(`${IRIS_BASE2}${path}`, {
|
|
60
|
+
headers: {
|
|
61
|
+
Cookie: Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join("; "),
|
|
62
|
+
Accept: "application/vnd.api+json, application/json",
|
|
63
|
+
"X-Requested-With": "XMLHttpRequest"
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
for (const raw of res.headers.getSetCookie?.() ?? []) {
|
|
67
|
+
const m = raw.match(/^([^=]+)=([^;]*)/);
|
|
68
|
+
if (m) session.cookies[m[1]] = m[2];
|
|
69
|
+
}
|
|
70
|
+
const text = await res.text();
|
|
71
|
+
let body = null;
|
|
72
|
+
try {
|
|
73
|
+
body = text ? JSON.parse(text) : null;
|
|
74
|
+
} catch {
|
|
75
|
+
}
|
|
76
|
+
if (!res.ok) {
|
|
77
|
+
const detail = body?.errors?.map((e) => e.detail ?? e.title).join("; ") || text.slice(0, 300);
|
|
78
|
+
throw new Error(`GET ${path} failed (HTTP ${res.status}): ${detail}`);
|
|
79
|
+
}
|
|
80
|
+
return body;
|
|
81
|
+
}
|
|
82
|
+
function htmlToText(html) {
|
|
83
|
+
if (!html) return "";
|
|
84
|
+
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();
|
|
85
|
+
}
|
|
86
|
+
function reasonsFrom(rejection) {
|
|
87
|
+
const reasons = rejection?.attributes?.reasons ?? [];
|
|
88
|
+
return (Array.isArray(reasons) ? reasons : []).map((r) => ({
|
|
89
|
+
section: r.reasonSection ?? r.section,
|
|
90
|
+
description: r.reasonDescription ?? r.description,
|
|
91
|
+
code: r.reasonCode ?? r.code
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
async function getThreadMessages(session, threadId) {
|
|
95
|
+
const body = await iris2(session, `/resolutionCenterThreads/${threadId}/resolutionCenterMessages?include=fromActor,rejections`);
|
|
96
|
+
const included = new Map((body?.included ?? []).map((i) => [`${i.type}:${i.id}`, i]));
|
|
97
|
+
return (body?.data ?? []).map((m) => {
|
|
98
|
+
const actor = m.relationships?.fromActor?.data;
|
|
99
|
+
const actorRow = actor ? included.get(`${actor.type}:${actor.id}`) : null;
|
|
100
|
+
const rejections = (m.relationships?.rejections?.data ?? []).map((r) => included.get(`${r.type}:${r.id}`)).filter(Boolean);
|
|
101
|
+
return {
|
|
102
|
+
id: m.id,
|
|
103
|
+
createdDate: m.attributes?.createdDate ?? null,
|
|
104
|
+
from: actorRow?.attributes?.actorType ?? actorRow?.attributes?.userFirstName ?? null,
|
|
105
|
+
body: htmlToText(m.attributes?.messageBody),
|
|
106
|
+
reasons: rejections.flatMap(reasonsFrom)
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
async function getReviewFeedback(session, appId, opts = {}) {
|
|
111
|
+
await selectProviderForApp(session, appId, opts.onLog);
|
|
112
|
+
const subs = await iris2(session, `/apps/${appId}/reviewSubmissions?limit=10`);
|
|
113
|
+
const out = [];
|
|
114
|
+
for (const s of subs?.data ?? []) {
|
|
115
|
+
const state = s.attributes?.state ?? "";
|
|
116
|
+
if (opts.onlyOpen && state !== "UNRESOLVED_ISSUES") continue;
|
|
117
|
+
const threadsBody = await iris2(session, `/resolutionCenterThreads?filter[reviewSubmission]=${s.id}&include=reviewSubmission`);
|
|
118
|
+
const threads = [];
|
|
119
|
+
for (const t of threadsBody?.data ?? []) {
|
|
120
|
+
threads.push({
|
|
121
|
+
id: t.id,
|
|
122
|
+
threadType: t.attributes?.threadType ?? null,
|
|
123
|
+
state: t.attributes?.state ?? null,
|
|
124
|
+
createdDate: t.attributes?.createdDate ?? null,
|
|
125
|
+
messages: await getThreadMessages(session, t.id)
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
if (threads.length || !opts.onlyOpen) {
|
|
129
|
+
out.push({ submissionId: s.id, state, submittedDate: s.attributes?.submittedDate ?? null, threads });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export { getReviewFeedback, getThreadMessages, htmlToText };
|
|
136
|
+
//# sourceMappingURL=index.mjs.map
|
|
137
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/privacy/index.ts","../../src/review/index.ts"],"names":["IRIS_BASE","iris"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * App Privacy details (\"nutrition labels\") for an app in App Store Connect.\n * 1:1 port of Fastlane's `upload_app_privacy_details_to_app_store` action and\n * Spaceship's AppDataUsage / AppDataUsagesPublishState models.\n *\n * The public App Store Connect API has no endpoint for privacy details — an\n * API key cannot set them. Fastlane goes through the iris API that App Store\n * Connect's own web UI uses, authenticated with an Apple ID session, and so\n * does this: pass the AppleSession from `loginWithAppleId` / the App Release\n * sign-in.\n *\n * Usages use Fastlane's JSON format, so an existing `app_privacy_details.json`\n * works unchanged:\n *\n * [\n * { \"category\": \"EMAIL_ADDRESS\", \"purposes\": [\"APP_FUNCTIONALITY\"], \"data_protections\": [\"DATA_LINKED_TO_YOU\"] },\n * { \"data_protections\": [\"DATA_NOT_COLLECTED\"] } // an app that collects nothing\n * ]\n *\n * As in Fastlane, the upload REPLACES everything: existing usages are deleted\n * first, then the new ones are created, then the answers are published.\n */\n\nimport type { AppleSession } from \"../client/apple-session.js\";\n\nconst IRIS_BASE = \"https://appstoreconnect.apple.com/iris/v1\";\nconst OLYMPUS_SESSION = \"https://appstoreconnect.apple.com/olympus/v1/session\";\n\n/** Every category App Store Connect accepts. Anything else is refused before existing answers are deleted. */\nexport const DATA_USAGE_CATEGORIES = [\n \"PAYMENT_INFORMATION\", \"CREDIT_AND_FRAUD\", \"OTHER_FINANCIAL_INFO\",\n \"PRECISE_LOCATION\", \"COARSE_LOCATION\", \"SENSITIVE_INFO\",\n \"PHYSICAL_ADDRESS\", \"EMAIL_ADDRESS\", \"NAME\", \"PHONE_NUMBER\", \"OTHER_CONTACT_INFO\",\n \"CONTACTS\", \"EMAILS_OR_TEXT_MESSAGES\", \"PHOTOS_OR_VIDEOS\", \"AUDIO\", \"GAMEPLAY_CONTENT\",\n \"CUSTOMER_SUPPORT\", \"OTHER_USER_CONTENT\", \"BROWSING_HISTORY\", \"SEARCH_HISTORY\",\n \"USER_ID\", \"DEVICE_ID\", \"PURCHASE_HISTORY\", \"PRODUCT_INTERACTION\", \"ADVERTISING_DATA\",\n \"OTHER_USAGE_DATA\", \"CRASH_DATA\", \"PERFORMANCE_DATA\", \"OTHER_DIAGNOSTIC_DATA\",\n \"OTHER_DATA\", \"HEALTH\", \"FITNESS\",\n] as const;\nexport const DATA_USAGE_PURPOSES = [\n \"THIRD_PARTY_ADVERTISING\", \"DEVELOPERS_ADVERTISING\", \"ANALYTICS\",\n \"PRODUCT_PERSONALIZATION\", \"APP_FUNCTIONALITY\", \"OTHER_PURPOSES\",\n] as const;\nexport const DATA_PROTECTIONS = [\n \"DATA_USED_TO_TRACK_YOU\", \"DATA_LINKED_TO_YOU\", \"DATA_NOT_LINKED_TO_YOU\", \"DATA_NOT_COLLECTED\",\n] as const;\n\nexport type DataUsageCategory =\n | \"PAYMENT_INFORMATION\" | \"CREDIT_AND_FRAUD\" | \"OTHER_FINANCIAL_INFO\"\n | \"PRECISE_LOCATION\" | \"COARSE_LOCATION\" | \"SENSITIVE_INFO\"\n | \"PHYSICAL_ADDRESS\" | \"EMAIL_ADDRESS\" | \"NAME\" | \"PHONE_NUMBER\" | \"OTHER_CONTACT_INFO\"\n | \"CONTACTS\" | \"EMAILS_OR_TEXT_MESSAGES\" | \"PHOTOS_OR_VIDEOS\" | \"AUDIO\" | \"GAMEPLAY_CONTENT\"\n | \"CUSTOMER_SUPPORT\" | \"OTHER_USER_CONTENT\" | \"BROWSING_HISTORY\" | \"SEARCH_HISTORY\"\n | \"USER_ID\" | \"DEVICE_ID\" | \"PURCHASE_HISTORY\" | \"PRODUCT_INTERACTION\" | \"ADVERTISING_DATA\"\n | \"OTHER_USAGE_DATA\" | \"CRASH_DATA\" | \"PERFORMANCE_DATA\" | \"OTHER_DIAGNOSTIC_DATA\"\n | \"OTHER_DATA\" | \"HEALTH\" | \"FITNESS\";\n\nexport type DataUsagePurpose =\n | \"THIRD_PARTY_ADVERTISING\" | \"DEVELOPERS_ADVERTISING\" | \"ANALYTICS\"\n | \"PRODUCT_PERSONALIZATION\" | \"APP_FUNCTIONALITY\" | \"OTHER_PURPOSES\";\n\nexport type DataProtection =\n | \"DATA_USED_TO_TRACK_YOU\" | \"DATA_LINKED_TO_YOU\" | \"DATA_NOT_LINKED_TO_YOU\" | \"DATA_NOT_COLLECTED\";\n\n/** One entry of Fastlane's app_privacy_details.json. */\nexport interface AppPrivacyUsage {\n category?: DataUsageCategory;\n purposes?: DataUsagePurpose[];\n data_protections: DataProtection[];\n}\n\nexport interface UploadAppPrivacyOptions {\n session: AppleSession;\n /** App Store Connect app id (the numeric Apple ID). */\n appId: string;\n usages: AppPrivacyUsage[];\n /** Leave the answers saved but unpublished (Fastlane's skip_publish). */\n skipPublish?: boolean;\n onLog?: (msg: string) => void;\n}\n\nexport interface UploadAppPrivacyResult {\n deleted: number;\n created: number;\n published: boolean;\n}\n\ninterface IrisResponse { status: number; body: any; text: string }\n\nasync function iris(session: AppleSession, method: string, path: string, body?: unknown): Promise<IrisResponse> {\n const res = await fetch(path.startsWith(\"http\") ? path : `${IRIS_BASE}${path}`, {\n method,\n headers: {\n Cookie: cookieHeader(session),\n Accept: \"application/vnd.api+json, application/json\",\n \"Content-Type\": \"application/vnd.api+json\",\n \"X-Requested-With\": \"XMLHttpRequest\",\n },\n body: body ? JSON.stringify(body) : undefined,\n });\n absorbCookies(session, res);\n const text = await res.text();\n let parsed: any = null;\n try { parsed = text ? JSON.parse(text) : null; } catch { /* not JSON */ }\n return { status: res.status, body: parsed, text };\n}\n\nfunction cookieHeader(session: AppleSession): string {\n return Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join(\"; \");\n}\n\nfunction absorbCookies(session: AppleSession, res: Response): void {\n for (const raw of res.headers.getSetCookie?.() ?? []) {\n const m = raw.match(/^([^=]+)=([^;]*)/);\n if (m) session.cookies[m[1]] = m[2];\n }\n}\n\nfunction fail(what: string, r: IrisResponse): never {\n const detail = r.body?.errors?.map((e: any) => e.detail ?? e.title).join(\"; \") || r.text.slice(0, 300);\n throw new Error(`${what} failed (HTTP ${r.status}): ${detail}`);\n}\n\n/**\n * Point the session at the App Store Connect provider (team) that owns the app.\n *\n * An Apple ID in several teams signs in to whichever one it used last, and\n * iris answers 404 for another team's app. Spaceship's `select_team` does the\n * same switch: POST the provider id to the olympus session.\n */\nexport async function selectProviderForApp(session: AppleSession, appId: string, onLog?: (m: string) => void): Promise<void> {\n const owns = async () => (await iris(session, \"GET\", `/apps/${appId}?fields[apps]=name`)).status === 200;\n if (await owns()) return;\n\n const res = await fetch(OLYMPUS_SESSION, { headers: { Cookie: cookieHeader(session), Accept: \"application/json\" } });\n absorbCookies(session, res);\n const info: any = await res.json().catch(() => null);\n const providers: Array<{ providerId: number; name: string }> = info?.availableProviders ?? [];\n for (const p of providers) {\n const sw = await fetch(OLYMPUS_SESSION, {\n method: \"POST\",\n headers: { Cookie: cookieHeader(session), \"Content-Type\": \"application/json\", Accept: \"application/json\" },\n body: JSON.stringify({ provider: { providerId: p.providerId } }),\n });\n absorbCookies(session, sw);\n if (sw.ok && (await owns())) {\n onLog?.(`Switched to team \"${p.name}\"`);\n session.teamId = String(p.providerId);\n return;\n }\n }\n throw new Error(`None of this Apple ID's ${providers.length} teams can see app ${appId}.`);\n}\n\n/** Existing privacy answers for an app. */\nexport async function listAppDataUsages(session: AppleSession, appId: string): Promise<Array<{ id: string }>> {\n const all: Array<{ id: string }> = [];\n let next: string | null = `/apps/${appId}/dataUsages?include=category,grouping,purpose,dataProtection&limit=500`;\n while (next) {\n const r = await iris(session, \"GET\", next);\n if (r.status !== 200) fail(\"List app data usages\", r);\n all.push(...(r.body?.data ?? []).map((d: any) => ({ id: d.id })));\n next = r.body?.links?.next ?? null;\n }\n return all;\n}\n\n/** Spaceship AppDataUsage.create — relationships are sent only when given. */\nexport async function createAppDataUsage(\n session: AppleSession,\n input: { appId: string; category?: string | null; purpose?: string | null; dataProtection: string },\n): Promise<string> {\n const relationships: Record<string, unknown> = { app: { data: { type: \"apps\", id: input.appId } } };\n if (input.category) relationships.category = { data: { type: \"appDataUsageCategories\", id: input.category } };\n if (input.purpose) relationships.purpose = { data: { type: \"appDataUsagePurposes\", id: input.purpose } };\n relationships.dataProtection = { data: { type: \"appDataUsageDataProtections\", id: input.dataProtection } };\n const r = await iris(session, \"POST\", \"/appDataUsages\", { data: { type: \"appDataUsages\", relationships } });\n if (r.status !== 201 && r.status !== 200) fail(`Create ${input.category ?? \"\"} ${input.purpose ?? \"\"} ${input.dataProtection}`.replace(/\\s+/g, \" \"), r);\n return r.body?.data?.id;\n}\n\nexport async function deleteAppDataUsage(session: AppleSession, id: string): Promise<void> {\n const r = await iris(session, \"DELETE\", `/appDataUsages/${id}`);\n if (r.status !== 204 && r.status !== 200) fail(`Delete app data usage ${id}`, r);\n}\n\nexport async function getAppDataUsagesPublishState(session: AppleSession, appId: string): Promise<{ id: string; published: boolean }> {\n const r = await iris(session, \"GET\", `/apps/${appId}/dataUsagePublishState`);\n if (r.status !== 200) fail(\"Read privacy publish state\", r);\n return { id: r.body.data.id, published: Boolean(r.body.data.attributes?.published) };\n}\n\nexport async function publishAppDataUsages(session: AppleSession, publishStateId: string): Promise<void> {\n const r = await iris(session, \"PATCH\", `/appDataUsagesPublishState/${publishStateId}`, {\n data: { type: \"appDataUsagesPublishState\", id: publishStateId, attributes: { published: true } },\n });\n if (r.status !== 200) fail(\"Publish privacy details\", r);\n}\n\n/** Fastlane's `upload_app_privacy_details_to_app_store`. Replaces all answers, then publishes. */\nexport async function uploadAppPrivacyDetails(opts: UploadAppPrivacyOptions): Promise<UploadAppPrivacyResult> {\n const { session, appId, usages, skipPublish, onLog = () => {} } = opts;\n validateUsages(usages);\n await selectProviderForApp(session, appId, onLog);\n\n const existing = await listAppDataUsages(session, appId);\n onLog(`Deleting ${existing.length} existing answer${existing.length === 1 ? \"\" : \"s\"}`);\n for (const u of existing) await deleteAppDataUsage(session, u.id);\n\n let created = 0;\n for (const usage of usages) {\n // \"Not collected\" has no purposes but still needs one AppDataUsage, so\n // iterate over [null] — the same trick Fastlane uses.\n const purposes: Array<string | null> = usage.purposes?.length ? usage.purposes : [null];\n for (const purpose of purposes) {\n for (const protection of usage.data_protections) {\n onLog(protection === \"DATA_NOT_COLLECTED\"\n ? `Setting ${protection}`\n : `Setting ${usage.category} and ${purpose} to ${protection}`);\n await createAppDataUsage(session, { appId, category: usage.category ?? null, purpose, dataProtection: protection });\n created++;\n }\n }\n }\n\n if (skipPublish) {\n onLog(\"Skipping publish\");\n return { deleted: existing.length, created, published: false };\n }\n const state = await getAppDataUsagesPublishState(session, appId);\n await publishAppDataUsages(session, state.id);\n onLog(\"Published\");\n return { deleted: existing.length, created, published: true };\n}\n\n/** Catch the answers App Store Connect would refuse, before deleting what is there. */\nexport function validateUsages(usages: AppPrivacyUsage[]): void {\n if (!Array.isArray(usages) || usages.length === 0) throw new Error(\"No privacy usages given.\");\n const notCollected = usages.some((u) => u.data_protections?.includes(\"DATA_NOT_COLLECTED\"));\n if (notCollected && usages.length > 1) {\n throw new Error(\"DATA_NOT_COLLECTED must be the only entry: an app cannot both collect nothing and collect something.\");\n }\n const known = (list: readonly string[], v: string, what: string) => {\n if (!list.includes(v)) throw new Error(`\"${v}\" is not an App Store Connect ${what}. Use one of: ${list.join(\", \")}.`);\n };\n for (const u of usages) {\n if (!u.data_protections?.length) throw new Error(`${u.category ?? \"An entry\"} has no data_protections.`);\n // Checked here, before anything is deleted: App Store Connect refuses an\n // unknown id only when that one answer is created, halfway through.\n if (u.category) known(DATA_USAGE_CATEGORIES, u.category, \"data category\");\n for (const p of u.purposes ?? []) known(DATA_USAGE_PURPOSES, p, \"purpose\");\n for (const d of u.data_protections) known(DATA_PROTECTIONS, d, \"data protection\");\n if (u.data_protections.includes(\"DATA_NOT_COLLECTED\")) continue;\n if (!u.category) throw new Error(\"Every collected entry needs a category.\");\n if (!u.purposes?.length) throw new Error(`${u.category} needs at least one purpose.`);\n const linked = u.data_protections.includes(\"DATA_LINKED_TO_YOU\");\n const unlinked = u.data_protections.includes(\"DATA_NOT_LINKED_TO_YOU\");\n if (linked && unlinked) throw new Error(`${u.category} cannot be both linked and not linked to the user.`);\n }\n}\n","/**\n * App Review rejections: what App Review said, from the Resolution Center.\n * Port of Spaceship's ResolutionCenterThread / ResolutionCenterMessage /\n * ReviewRejection models (connect_api/tunes).\n *\n * Like App Privacy, this is not in the public App Store Connect API — with an\n * API key, /v1/resolutionCenterThreads answers \"resource does not exist\".\n * Spaceship reaches it through its Tunes client, whose host is\n * appstoreconnect.apple.com/iris/, i.e. an Apple ID session. So does this.\n */\n\nimport type { AppleSession } from \"../client/apple-session.js\";\nimport { selectProviderForApp } from \"../privacy/index.js\";\n\nconst IRIS_BASE = \"https://appstoreconnect.apple.com/iris/v1\";\n\nexport interface RejectionReason {\n section?: string;\n description?: string;\n code?: string;\n}\n\nexport interface ResolutionMessage {\n id: string;\n createdDate: string | null;\n from: string | null;\n /** The message as App Review wrote it, HTML reduced to text. */\n body: string;\n reasons: RejectionReason[];\n}\n\nexport interface ResolutionThread {\n id: string;\n threadType: string | null;\n state: string | null;\n createdDate: string | null;\n messages: ResolutionMessage[];\n}\n\nexport interface SubmissionReview {\n submissionId: string;\n state: string;\n submittedDate: string | null;\n threads: ResolutionThread[];\n}\n\nasync function iris(session: AppleSession, path: string): Promise<any> {\n const res = await fetch(`${IRIS_BASE}${path}`, {\n headers: {\n Cookie: Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join(\"; \"),\n Accept: \"application/vnd.api+json, application/json\",\n \"X-Requested-With\": \"XMLHttpRequest\",\n },\n });\n for (const raw of res.headers.getSetCookie?.() ?? []) {\n const m = raw.match(/^([^=]+)=([^;]*)/);\n if (m) session.cookies[m[1]] = m[2];\n }\n const text = await res.text();\n let body: any = null;\n try { body = text ? JSON.parse(text) : null; } catch { /* not JSON */ }\n if (!res.ok) {\n const detail = body?.errors?.map((e: any) => e.detail ?? e.title).join(\"; \") || text.slice(0, 300);\n throw new Error(`GET ${path} failed (HTTP ${res.status}): ${detail}`);\n }\n return body;\n}\n\n/** App Review writes HTML; keep paragraphs and list items, drop the markup. */\nexport function htmlToText(html: string | null | undefined): string {\n if (!html) return \"\";\n return html\n .replace(/<br\\s*\\/?>/gi, \"\\n\")\n .replace(/<\\/(p|div|li|h\\d)>/gi, \"\\n\")\n .replace(/<li[^>]*>/gi, \"- \")\n .replace(/<[^>]+>/g, \"\")\n .replace(/ /g, \" \").replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\")\n .replace(/"/g, '\"').replace(/'/g, \"'\")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim();\n}\n\nfunction reasonsFrom(rejection: any): RejectionReason[] {\n const reasons = rejection?.attributes?.reasons ?? [];\n return (Array.isArray(reasons) ? reasons : []).map((r: any) => ({\n section: r.reasonSection ?? r.section,\n description: r.reasonDescription ?? r.description,\n code: r.reasonCode ?? r.code,\n }));\n}\n\n/** The messages in one thread, each with the guideline reasons App Review attached. */\nexport async function getThreadMessages(session: AppleSession, threadId: string): Promise<ResolutionMessage[]> {\n const body = await iris(session, `/resolutionCenterThreads/${threadId}/resolutionCenterMessages?include=fromActor,rejections`);\n const included = new Map<string, any>((body?.included ?? []).map((i: any) => [`${i.type}:${i.id}`, i]));\n return (body?.data ?? []).map((m: any) => {\n const actor = m.relationships?.fromActor?.data;\n const actorRow = actor ? included.get(`${actor.type}:${actor.id}`) : null;\n const rejections = (m.relationships?.rejections?.data ?? []).map((r: any) => included.get(`${r.type}:${r.id}`)).filter(Boolean);\n return {\n id: m.id,\n createdDate: m.attributes?.createdDate ?? null,\n from: actorRow?.attributes?.actorType ?? actorRow?.attributes?.userFirstName ?? null,\n body: htmlToText(m.attributes?.messageBody),\n reasons: rejections.flatMap(reasonsFrom),\n };\n });\n}\n\n/**\n * Every review submission for the app that App Review has answered, with its\n * Resolution Center threads and messages. `onlyOpen` keeps the ones still\n * waiting on the developer (UNRESOLVED_ISSUES).\n */\nexport async function getReviewFeedback(\n session: AppleSession,\n appId: string,\n opts: { onlyOpen?: boolean; onLog?: (m: string) => void } = {},\n): Promise<SubmissionReview[]> {\n await selectProviderForApp(session, appId, opts.onLog);\n const subs = await iris(session, `/apps/${appId}/reviewSubmissions?limit=10`);\n const out: SubmissionReview[] = [];\n for (const s of subs?.data ?? []) {\n const state = s.attributes?.state ?? \"\";\n if (opts.onlyOpen && state !== \"UNRESOLVED_ISSUES\") continue;\n const threadsBody = await iris(session, `/resolutionCenterThreads?filter[reviewSubmission]=${s.id}&include=reviewSubmission`);\n const threads: ResolutionThread[] = [];\n for (const t of threadsBody?.data ?? []) {\n threads.push({\n id: t.id,\n threadType: t.attributes?.threadType ?? null,\n state: t.attributes?.state ?? null,\n createdDate: t.attributes?.createdDate ?? null,\n messages: await getThreadMessages(session, t.id),\n });\n }\n if (threads.length || !opts.onlyOpen) {\n out.push({ submissionId: s.id, state, submittedDate: s.attributes?.submittedDate ?? null, threads });\n }\n }\n return out;\n}\n"]}
|
package/dist/sigh/index.d.cts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import '../index-
|
|
2
|
-
import '../enums-
|
|
3
|
-
export { a as SighOptions, S as SighResult, c as createProfile, d as downloadProfile, f as findMatchingProfile, b as installProfile } from '../index-
|
|
1
|
+
import '../index-DrmS802s.cjs';
|
|
2
|
+
import '../enums-yXNVh3UI.cjs';
|
|
3
|
+
export { a as SighOptions, S as SighResult, c as createProfile, d as downloadProfile, f as findMatchingProfile, b as installProfile } from '../index-DWOLZNlz.cjs';
|
|
4
|
+
import '../apple-session-jUzBrql5.cjs';
|
package/dist/sigh/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import '../index-
|
|
2
|
-
import '../enums-
|
|
3
|
-
export { a as SighOptions, S as SighResult, c as createProfile, d as downloadProfile, f as findMatchingProfile, b as installProfile } from '../index-
|
|
1
|
+
import '../index-lsuS2R1T.js';
|
|
2
|
+
import '../enums-yXNVh3UI.js';
|
|
3
|
+
export { a as SighOptions, S as SighResult, c as createProfile, d as downloadProfile, f as findMatchingProfile, b as installProfile } from '../index-C01Rkrir.js';
|
|
4
|
+
import '../apple-session-jUzBrql5.js';
|
package/dist/xcode/index.cjs
CHANGED
|
@@ -25,7 +25,7 @@ async function createKeychain(options) {
|
|
|
25
25
|
const keychainName = `${options.name}.keychain-db`;
|
|
26
26
|
const keychainPath = path.join(os.homedir(), "Library", "Keychains", keychainName);
|
|
27
27
|
await execFileAsync("security", ["create-keychain", "-p", password, keychainPath]);
|
|
28
|
-
await execFileAsync("security", ["set-keychain-settings",
|
|
28
|
+
await execFileAsync("security", ["set-keychain-settings", keychainPath]);
|
|
29
29
|
await execFileAsync("security", ["unlock-keychain", "-p", password, keychainPath]);
|
|
30
30
|
const { stdout: currentList } = await execFileAsync("security", ["list-keychains", "-d", "user"]);
|
|
31
31
|
const keychains = currentList.split("\n").map((k) => k.trim().replace(/"/g, "")).filter(Boolean);
|
package/dist/xcode/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/xcode/platform.ts","../../src/xcode/keychain.ts","../../src/xcode/project.ts","../../src/xcode/upload.ts"],"names":["promisify","execFile","randomBytes","join","homedir","mkdir","copyFile","execFileAsync","readFile","writeFile","mkdtemp","tmpdir","spawn","unlink"],"mappings":"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","file":"index.cjs","sourcesContent":["export function requireMacOS(feature: string): void {\n if (process.platform !== \"darwin\") {\n throw new Error(\n `${feature} requires macOS. This operation uses Xcode command-line tools ` +\n `which are not available on ${process.platform}.`\n );\n }\n}\n\nexport function isMacOS(): boolean {\n return process.platform === \"darwin\";\n}\n","import { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport { copyFile, mkdir, unlink } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { randomBytes } from \"node:crypto\";\nimport { requireMacOS } from \"./platform.js\";\n\nconst execFileAsync = promisify(execFile);\n\nexport interface KeychainHandle {\n path: string;\n password: string;\n cleanup(): Promise<void>;\n}\n\nexport async function createKeychain(options: {\n name: string;\n password?: string;\n}): Promise<KeychainHandle> {\n requireMacOS(\"keychain\");\n\n const password = options.password ?? randomBytes(16).toString(\"hex\");\n const keychainName = `${options.name}.keychain-db`;\n const keychainPath = join(homedir(), \"Library\", \"Keychains\", keychainName);\n\n await execFileAsync(\"security\", [\"create-keychain\", \"-p\", password, keychainPath]);\n await execFileAsync(\"security\", [\"set-keychain-settings\", \"-lut\", \"21600\", keychainPath]);\n await execFileAsync(\"security\", [\"unlock-keychain\", \"-p\", password, keychainPath]);\n\n // Add to search list\n const { stdout: currentList } = await execFileAsync(\"security\", [\"list-keychains\", \"-d\", \"user\"]);\n const keychains = currentList.split(\"\\n\").map((k) => k.trim().replace(/\"/g, \"\")).filter(Boolean);\n const searchList = [keychainPath, ...keychains];\n if (!searchList.includes(\"/Library/Keychains/System.keychain\")) {\n searchList.push(\"/Library/Keychains/System.keychain\");\n }\n await execFileAsync(\"security\", [\"list-keychains\", \"-d\", \"user\", \"-s\", ...searchList]);\n\n // Download and import WWDR cert\n try {\n const wwdrPath = \"/tmp/AppleWWDRCAG3.cer\";\n await execFileAsync(\"curl\", [\"-sfo\", wwdrPath, \"https://www.apple.com/certificateauthority/AppleWWDRCAG3.cer\"]);\n await execFileAsync(\"security\", [\"import\", wwdrPath, \"-k\", keychainPath, \"-T\", \"/usr/bin/codesign\"]);\n } catch {\n // Non-fatal\n }\n\n return {\n path: keychainPath,\n password,\n cleanup: () => cleanupKeychain({ path: keychainPath, password, cleanup: async () => {} }),\n };\n}\n\nexport async function importCertificate(\n keychainPath: string,\n certPath: string,\n password?: string\n): Promise<void> {\n requireMacOS(\"keychain\");\n\n const isCer = certPath.endsWith(\".cer\") || certPath.endsWith(\".pem\");\n const args = isCer\n ? [\"import\", certPath, \"-k\", keychainPath, \"-T\", \"/usr/bin/codesign\", \"-T\", \"/usr/bin/security\"]\n : [\"import\", certPath, \"-k\", keychainPath, \"-P\", password ?? \"\", \"-T\", \"/usr/bin/codesign\", \"-T\", \"/usr/bin/security\"];\n\n try {\n await execFileAsync(\"security\", args);\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n if (!msg.includes(\"already exists\")) throw err;\n }\n}\n\nexport async function installProfile(\n profilePath: string,\n uuid: string\n): Promise<string> {\n const profilesDir = join(homedir(), \"Library\", \"MobileDevice\", \"Provisioning Profiles\");\n await mkdir(profilesDir, { recursive: true });\n const dest = join(profilesDir, `${uuid}.mobileprovision`);\n await copyFile(profilePath, dest);\n return dest;\n}\n\nexport async function cleanupKeychain(handle: KeychainHandle): Promise<void> {\n try {\n await execFileAsync(\"security\", [\"delete-keychain\", handle.path]);\n } catch {\n // Keychain may already be deleted\n }\n}\n","import { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport { requireMacOS } from \"./platform.js\";\n\nconst execFileAsync = promisify(execFile);\n\nexport async function updateSigningSettings(options: {\n projectPath: string;\n teamId: string;\n bundleIdentifier: string;\n codeSignIdentity: string;\n targets?: string[];\n profileName?: string;\n profileUuid?: string;\n automaticSigning?: boolean;\n}): Promise<void> {\n const pbxprojPath = `${options.projectPath}/project.pbxproj`;\n let content = await readFile(pbxprojPath, \"utf-8\");\n\n const style = options.automaticSigning ? \"Automatic\" : \"Manual\";\n\n // Replace build settings across the file\n content = replaceBuildSetting(content, \"CODE_SIGN_STYLE\", style);\n content = replaceBuildSetting(content, \"DEVELOPMENT_TEAM\", options.teamId);\n content = replaceBuildSetting(content, \"CODE_SIGN_IDENTITY\", options.codeSignIdentity);\n\n if (options.profileName) {\n content = replaceBuildSetting(content, \"PROVISIONING_PROFILE_SPECIFIER\", options.profileName);\n }\n if (options.profileUuid) {\n content = replaceBuildSetting(content, \"PROVISIONING_PROFILE\", options.profileUuid);\n }\n\n await writeFile(pbxprojPath, content);\n}\n\nexport async function setBuildNumber(\n projectPath: string,\n buildNumber: string\n): Promise<void> {\n const pbxprojPath = `${projectPath}/project.pbxproj`;\n let content = await readFile(pbxprojPath, \"utf-8\");\n content = replaceBuildSetting(content, \"CURRENT_PROJECT_VERSION\", buildNumber);\n await writeFile(pbxprojPath, content);\n}\n\nexport async function setVersionNumber(\n projectPath: string,\n version: string\n): Promise<void> {\n const pbxprojPath = `${projectPath}/project.pbxproj`;\n let content = await readFile(pbxprojPath, \"utf-8\");\n content = replaceBuildSetting(content, \"MARKETING_VERSION\", version);\n await writeFile(pbxprojPath, content);\n}\n\nexport async function readBuildSettings(options: {\n projectPath?: string;\n workspacePath?: string;\n scheme: string;\n configuration?: string;\n}): Promise<Record<string, string>> {\n requireMacOS(\"readBuildSettings\");\n\n const args = [\"-showBuildSettings\"];\n if (options.workspacePath) args.push(\"-workspace\", options.workspacePath);\n else if (options.projectPath) args.push(\"-project\", options.projectPath);\n args.push(\"-scheme\", options.scheme);\n if (options.configuration) args.push(\"-configuration\", options.configuration);\n\n const { stdout } = await execFileAsync(\"xcodebuild\", args);\n const settings: Record<string, string> = {};\n\n for (const line of stdout.split(\"\\n\")) {\n const match = line.match(/^\\s+(\\w+)\\s*=\\s*(.*)$/);\n if (match) {\n settings[match[1]] = match[2].trim();\n }\n }\n\n return settings;\n}\n\nfunction replaceBuildSetting(content: string, key: string, value: string): string {\n // Match: KEY = \"value\"; or KEY = value;\n // Use negative lookbehind to avoid partial matches (e.g., PROVISIONING_PROFILE vs _SPECIFIER)\n const regex = new RegExp(`(?<![A-Z_])(${key})\\\\s*=\\\\s*(\"[^\"]*\"|[^;]*);`, \"g\");\n // Quote value if it contains non-alphanumeric chars or if the original was quoted\n const needsQuotes = /[^A-Za-z0-9]/.test(value) || /\\s/.test(value);\n const quoted = needsQuotes ? `\"${value}\"` : value;\n return content.replace(regex, `$1 = ${quoted};`);\n}\n","import { spawn } from \"node:child_process\";\nimport { writeFile, unlink, mkdtemp } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { tmpdir } from \"node:os\";\nimport { requireMacOS } from \"./platform.js\";\n\nexport async function uploadWithAltool(options: {\n filePath: string;\n apiKeyId: string;\n apiIssuerId: string;\n apiPrivateKey?: string;\n apiKeyPath?: string;\n platform?: string;\n onOutput?: (chunk: string) => void;\n}): Promise<{ success: boolean; output: string }> {\n requireMacOS(\"altool upload\");\n\n // Write temp .p8 if private key content provided\n let keyDir: string | undefined;\n if (options.apiPrivateKey && !options.apiKeyPath) {\n keyDir = await mkdtemp(join(tmpdir(), \"fastnode-altool-\"));\n const keyPath = join(keyDir, `AuthKey_${options.apiKeyId}.p8`);\n await writeFile(keyPath, options.apiPrivateKey);\n }\n\n const args = [\n \"altool\",\n \"--upload-app\",\n \"-f\", options.filePath,\n \"-t\", options.platform ?? \"ios\",\n \"--apiKey\", options.apiKeyId,\n \"--apiIssuer\", options.apiIssuerId,\n ];\n\n const env = { ...process.env };\n if (keyDir) {\n env.API_PRIVATE_KEYS_DIR = keyDir;\n }\n\n return new Promise<{ success: boolean; output: string }>((resolve) => {\n const child = spawn(\"xcrun\", args, {\n env,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let output = \"\";\n\n child.stdout.on(\"data\", (data: Buffer) => {\n const chunk = data.toString();\n output += chunk;\n options.onOutput?.(chunk);\n });\n\n child.stderr.on(\"data\", (data: Buffer) => {\n const chunk = data.toString();\n output += chunk;\n options.onOutput?.(chunk);\n });\n\n child.on(\"close\", async (code) => {\n if (keyDir) {\n const keyPath = join(keyDir, `AuthKey_${options.apiKeyId}.p8`);\n await unlink(keyPath).catch(() => {});\n }\n resolve({ success: code === 0, output });\n });\n });\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/xcode/platform.ts","../../src/xcode/keychain.ts","../../src/xcode/project.ts","../../src/xcode/upload.ts"],"names":["promisify","execFile","randomBytes","join","homedir","mkdir","copyFile","execFileAsync","readFile","writeFile","mkdtemp","tmpdir","spawn","unlink"],"mappings":"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","file":"index.cjs","sourcesContent":["export function requireMacOS(feature: string): void {\n if (process.platform !== \"darwin\") {\n throw new Error(\n `${feature} requires macOS. This operation uses Xcode command-line tools ` +\n `which are not available on ${process.platform}.`\n );\n }\n}\n\nexport function isMacOS(): boolean {\n return process.platform === \"darwin\";\n}\n","import { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport { copyFile, mkdir, unlink } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { randomBytes } from \"node:crypto\";\nimport { requireMacOS } from \"./platform.js\";\n\nconst execFileAsync = promisify(execFile);\n\nexport interface KeychainHandle {\n path: string;\n password: string;\n cleanup(): Promise<void>;\n}\n\nexport async function createKeychain(options: {\n name: string;\n password?: string;\n}): Promise<KeychainHandle> {\n requireMacOS(\"keychain\");\n\n const password = options.password ?? randomBytes(16).toString(\"hex\");\n const keychainName = `${options.name}.keychain-db`;\n const keychainPath = join(homedir(), \"Library\", \"Keychains\", keychainName);\n\n await execFileAsync(\"security\", [\"create-keychain\", \"-p\", password, keychainPath]);\n // No flags: clears both the sleep lock and the idle timeout.\n //\n // \"-lut 21600\" locks the keychain when the system sleeps, which on a laptop\n // build host means any sleep part way through a build makes every codesign\n // after it fail with errSecInternalComponent — a failure that reads as a\n // certificate problem and is not one. A keychain created per build and torn\n // down after has nothing to gain from locking while that build still runs.\n // servers/build/src/keychain.ts carried the same line; this is the same fix.\n await execFileAsync(\"security\", [\"set-keychain-settings\", keychainPath]);\n await execFileAsync(\"security\", [\"unlock-keychain\", \"-p\", password, keychainPath]);\n\n // Add to search list\n const { stdout: currentList } = await execFileAsync(\"security\", [\"list-keychains\", \"-d\", \"user\"]);\n const keychains = currentList.split(\"\\n\").map((k) => k.trim().replace(/\"/g, \"\")).filter(Boolean);\n const searchList = [keychainPath, ...keychains];\n if (!searchList.includes(\"/Library/Keychains/System.keychain\")) {\n searchList.push(\"/Library/Keychains/System.keychain\");\n }\n await execFileAsync(\"security\", [\"list-keychains\", \"-d\", \"user\", \"-s\", ...searchList]);\n\n // Download and import WWDR cert\n try {\n const wwdrPath = \"/tmp/AppleWWDRCAG3.cer\";\n await execFileAsync(\"curl\", [\"-sfo\", wwdrPath, \"https://www.apple.com/certificateauthority/AppleWWDRCAG3.cer\"]);\n await execFileAsync(\"security\", [\"import\", wwdrPath, \"-k\", keychainPath, \"-T\", \"/usr/bin/codesign\"]);\n } catch {\n // Non-fatal\n }\n\n return {\n path: keychainPath,\n password,\n cleanup: () => cleanupKeychain({ path: keychainPath, password, cleanup: async () => {} }),\n };\n}\n\nexport async function importCertificate(\n keychainPath: string,\n certPath: string,\n password?: string\n): Promise<void> {\n requireMacOS(\"keychain\");\n\n const isCer = certPath.endsWith(\".cer\") || certPath.endsWith(\".pem\");\n const args = isCer\n ? [\"import\", certPath, \"-k\", keychainPath, \"-T\", \"/usr/bin/codesign\", \"-T\", \"/usr/bin/security\"]\n : [\"import\", certPath, \"-k\", keychainPath, \"-P\", password ?? \"\", \"-T\", \"/usr/bin/codesign\", \"-T\", \"/usr/bin/security\"];\n\n try {\n await execFileAsync(\"security\", args);\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n if (!msg.includes(\"already exists\")) throw err;\n }\n}\n\nexport async function installProfile(\n profilePath: string,\n uuid: string\n): Promise<string> {\n const profilesDir = join(homedir(), \"Library\", \"MobileDevice\", \"Provisioning Profiles\");\n await mkdir(profilesDir, { recursive: true });\n const dest = join(profilesDir, `${uuid}.mobileprovision`);\n await copyFile(profilePath, dest);\n return dest;\n}\n\nexport async function cleanupKeychain(handle: KeychainHandle): Promise<void> {\n try {\n await execFileAsync(\"security\", [\"delete-keychain\", handle.path]);\n } catch {\n // Keychain may already be deleted\n }\n}\n","import { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport { requireMacOS } from \"./platform.js\";\n\nconst execFileAsync = promisify(execFile);\n\nexport async function updateSigningSettings(options: {\n projectPath: string;\n teamId: string;\n bundleIdentifier: string;\n codeSignIdentity: string;\n targets?: string[];\n profileName?: string;\n profileUuid?: string;\n automaticSigning?: boolean;\n}): Promise<void> {\n const pbxprojPath = `${options.projectPath}/project.pbxproj`;\n let content = await readFile(pbxprojPath, \"utf-8\");\n\n const style = options.automaticSigning ? \"Automatic\" : \"Manual\";\n\n // Replace build settings across the file\n content = replaceBuildSetting(content, \"CODE_SIGN_STYLE\", style);\n content = replaceBuildSetting(content, \"DEVELOPMENT_TEAM\", options.teamId);\n content = replaceBuildSetting(content, \"CODE_SIGN_IDENTITY\", options.codeSignIdentity);\n\n if (options.profileName) {\n content = replaceBuildSetting(content, \"PROVISIONING_PROFILE_SPECIFIER\", options.profileName);\n }\n if (options.profileUuid) {\n content = replaceBuildSetting(content, \"PROVISIONING_PROFILE\", options.profileUuid);\n }\n\n await writeFile(pbxprojPath, content);\n}\n\nexport async function setBuildNumber(\n projectPath: string,\n buildNumber: string\n): Promise<void> {\n const pbxprojPath = `${projectPath}/project.pbxproj`;\n let content = await readFile(pbxprojPath, \"utf-8\");\n content = replaceBuildSetting(content, \"CURRENT_PROJECT_VERSION\", buildNumber);\n await writeFile(pbxprojPath, content);\n}\n\nexport async function setVersionNumber(\n projectPath: string,\n version: string\n): Promise<void> {\n const pbxprojPath = `${projectPath}/project.pbxproj`;\n let content = await readFile(pbxprojPath, \"utf-8\");\n content = replaceBuildSetting(content, \"MARKETING_VERSION\", version);\n await writeFile(pbxprojPath, content);\n}\n\nexport async function readBuildSettings(options: {\n projectPath?: string;\n workspacePath?: string;\n scheme: string;\n configuration?: string;\n}): Promise<Record<string, string>> {\n requireMacOS(\"readBuildSettings\");\n\n const args = [\"-showBuildSettings\"];\n if (options.workspacePath) args.push(\"-workspace\", options.workspacePath);\n else if (options.projectPath) args.push(\"-project\", options.projectPath);\n args.push(\"-scheme\", options.scheme);\n if (options.configuration) args.push(\"-configuration\", options.configuration);\n\n const { stdout } = await execFileAsync(\"xcodebuild\", args);\n const settings: Record<string, string> = {};\n\n for (const line of stdout.split(\"\\n\")) {\n const match = line.match(/^\\s+(\\w+)\\s*=\\s*(.*)$/);\n if (match) {\n settings[match[1]] = match[2].trim();\n }\n }\n\n return settings;\n}\n\nfunction replaceBuildSetting(content: string, key: string, value: string): string {\n // Match: KEY = \"value\"; or KEY = value;\n // Use negative lookbehind to avoid partial matches (e.g., PROVISIONING_PROFILE vs _SPECIFIER)\n const regex = new RegExp(`(?<![A-Z_])(${key})\\\\s*=\\\\s*(\"[^\"]*\"|[^;]*);`, \"g\");\n // Quote value if it contains non-alphanumeric chars or if the original was quoted\n const needsQuotes = /[^A-Za-z0-9]/.test(value) || /\\s/.test(value);\n const quoted = needsQuotes ? `\"${value}\"` : value;\n return content.replace(regex, `$1 = ${quoted};`);\n}\n","import { spawn } from \"node:child_process\";\nimport { writeFile, unlink, mkdtemp } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { tmpdir } from \"node:os\";\nimport { requireMacOS } from \"./platform.js\";\n\nexport async function uploadWithAltool(options: {\n filePath: string;\n apiKeyId: string;\n apiIssuerId: string;\n apiPrivateKey?: string;\n apiKeyPath?: string;\n platform?: string;\n onOutput?: (chunk: string) => void;\n}): Promise<{ success: boolean; output: string }> {\n requireMacOS(\"altool upload\");\n\n // Write temp .p8 if private key content provided\n let keyDir: string | undefined;\n if (options.apiPrivateKey && !options.apiKeyPath) {\n keyDir = await mkdtemp(join(tmpdir(), \"fastnode-altool-\"));\n const keyPath = join(keyDir, `AuthKey_${options.apiKeyId}.p8`);\n await writeFile(keyPath, options.apiPrivateKey);\n }\n\n const args = [\n \"altool\",\n \"--upload-app\",\n \"-f\", options.filePath,\n \"-t\", options.platform ?? \"ios\",\n \"--apiKey\", options.apiKeyId,\n \"--apiIssuer\", options.apiIssuerId,\n ];\n\n const env = { ...process.env };\n if (keyDir) {\n env.API_PRIVATE_KEYS_DIR = keyDir;\n }\n\n return new Promise<{ success: boolean; output: string }>((resolve) => {\n const child = spawn(\"xcrun\", args, {\n env,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let output = \"\";\n\n child.stdout.on(\"data\", (data: Buffer) => {\n const chunk = data.toString();\n output += chunk;\n options.onOutput?.(chunk);\n });\n\n child.stderr.on(\"data\", (data: Buffer) => {\n const chunk = data.toString();\n output += chunk;\n options.onOutput?.(chunk);\n });\n\n child.on(\"close\", async (code) => {\n if (keyDir) {\n const keyPath = join(keyDir, `AuthKey_${options.apiKeyId}.p8`);\n await unlink(keyPath).catch(() => {});\n }\n resolve({ success: code === 0, output });\n });\n });\n}\n"]}
|
package/dist/xcode/index.mjs
CHANGED
|
@@ -23,7 +23,7 @@ async function createKeychain(options) {
|
|
|
23
23
|
const keychainName = `${options.name}.keychain-db`;
|
|
24
24
|
const keychainPath = join(homedir(), "Library", "Keychains", keychainName);
|
|
25
25
|
await execFileAsync("security", ["create-keychain", "-p", password, keychainPath]);
|
|
26
|
-
await execFileAsync("security", ["set-keychain-settings",
|
|
26
|
+
await execFileAsync("security", ["set-keychain-settings", keychainPath]);
|
|
27
27
|
await execFileAsync("security", ["unlock-keychain", "-p", password, keychainPath]);
|
|
28
28
|
const { stdout: currentList } = await execFileAsync("security", ["list-keychains", "-d", "user"]);
|
|
29
29
|
const keychains = currentList.split("\n").map((k) => k.trim().replace(/"/g, "")).filter(Boolean);
|