@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.
Files changed (126) hide show
  1. package/README.md +58 -1
  2. package/dist/actions/index.cjs +848 -54
  3. package/dist/actions/index.cjs.map +1 -1
  4. package/dist/actions/index.mjs +849 -55
  5. package/dist/actions/index.mjs.map +1 -1
  6. package/dist/apple-session-jUzBrql5.d.cts +122 -0
  7. package/dist/apple-session-jUzBrql5.d.ts +122 -0
  8. package/dist/cert/index.cjs +782 -736
  9. package/dist/cert/index.cjs.map +1 -1
  10. package/dist/cert/index.d.cts +5 -4
  11. package/dist/cert/index.d.ts +5 -4
  12. package/dist/cert/index.mjs +782 -737
  13. package/dist/cert/index.mjs.map +1 -1
  14. package/dist/{cert-Ddl7FpFT.d.ts → cert-RvXmdMke.d.ts} +2 -2
  15. package/dist/{cert-FehieKeb.d.cts → cert-eOoxGMN1.d.cts} +2 -2
  16. package/dist/cli/index.cjs +1321 -60
  17. package/dist/cli/index.cjs.map +1 -1
  18. package/dist/cli/index.d.cts +3 -2
  19. package/dist/cli/index.d.ts +3 -2
  20. package/dist/cli/index.mjs +1322 -61
  21. package/dist/cli/index.mjs.map +1 -1
  22. package/dist/client/index.cjs +443 -31
  23. package/dist/client/index.cjs.map +1 -1
  24. package/dist/client/index.d.cts +3 -2
  25. package/dist/client/index.d.ts +3 -2
  26. package/dist/client/index.mjs +443 -31
  27. package/dist/client/index.mjs.map +1 -1
  28. package/dist/deliver/index.cjs +334 -13
  29. package/dist/deliver/index.cjs.map +1 -1
  30. package/dist/deliver/index.d.cts +4 -3
  31. package/dist/deliver/index.d.ts +4 -3
  32. package/dist/deliver/index.mjs +330 -15
  33. package/dist/deliver/index.mjs.map +1 -1
  34. package/dist/{enums-B609j2rb.d.cts → enums-yXNVh3UI.d.cts} +8 -1
  35. package/dist/{enums-B609j2rb.d.ts → enums-yXNVh3UI.d.ts} +8 -1
  36. package/dist/gradle/index.cjs +222 -0
  37. package/dist/gradle/index.cjs.map +1 -0
  38. package/dist/gradle/index.d.cts +1 -0
  39. package/dist/gradle/index.d.ts +1 -0
  40. package/dist/gradle/index.mjs +217 -0
  41. package/dist/gradle/index.mjs.map +1 -0
  42. package/dist/gym/index.d.cts +2 -2
  43. package/dist/gym/index.d.ts +2 -2
  44. package/dist/index-8djTPW8O.d.ts +104 -0
  45. package/dist/{index-DrUpRXRG.d.cts → index-B6-MJpO4.d.cts} +1 -1
  46. package/dist/{index-D13sn8DO.d.cts → index-BE7bwNff.d.cts} +1 -1
  47. package/dist/index-BGhV0gS-.d.ts +65 -0
  48. package/dist/index-BaNcf7jt.d.cts +201 -0
  49. package/dist/{index-DZoPGCbE.d.ts → index-BvyZ4uiw.d.ts} +3 -3
  50. package/dist/{index-B8FOfxOw.d.ts → index-C01Rkrir.d.ts} +2 -2
  51. package/dist/{index-YcKz4aRS.d.ts → index-C9mHBJED.d.ts} +1 -1
  52. package/dist/{index-BICa8odo.d.cts → index-CAEiXVWd.d.ts} +9 -1
  53. package/dist/index-CBf0-NUQ.d.ts +201 -0
  54. package/dist/index-CXJlvmB4.d.cts +63 -0
  55. package/dist/index-CXJlvmB4.d.ts +63 -0
  56. package/dist/{index-CNHf7I4v.d.cts → index-D1GHEM3c.d.ts} +32 -3
  57. package/dist/index-DFstwgGK.d.cts +65 -0
  58. package/dist/{index-Bk1Gd-qE.d.cts → index-DGkgRnZp.d.cts} +2 -2
  59. package/dist/{index-CplRWSUc.d.ts → index-DLJso7Vs.d.cts} +32 -3
  60. package/dist/{index-BZWmOTWE.d.cts → index-DWOLZNlz.d.cts} +2 -2
  61. package/dist/{index-OelCoOth.d.cts → index-DfkIz7i0.d.cts} +18 -4
  62. package/dist/{index-CtPyeMLH.d.ts → index-DjC_gmnL.d.ts} +18 -4
  63. package/dist/index-DnHQ2uT2.d.cts +104 -0
  64. package/dist/{index-CCtYhPN9.d.ts → index-DrmS802s.d.cts} +331 -130
  65. package/dist/index-Dv-Mek07.d.cts +84 -0
  66. package/dist/index-Dv-Mek07.d.ts +84 -0
  67. package/dist/{index-oVyLLIEK.d.cts → index-icIH5e5V.d.cts} +3 -3
  68. package/dist/{index-DI1ZHr-3.d.ts → index-laE3_wm9.d.cts} +9 -1
  69. package/dist/{index-BKUapQP2.d.cts → index-lsuS2R1T.d.ts} +331 -130
  70. package/dist/{index-DImN9PWc.d.ts → index-mEEB7An6.d.ts} +1 -1
  71. package/dist/{index-DFni8oZt.d.ts → index-rTZOdMRE.d.ts} +2 -2
  72. package/dist/index.cjs +3342 -959
  73. package/dist/index.cjs.map +1 -1
  74. package/dist/index.d.cts +237 -14
  75. package/dist/index.d.ts +237 -14
  76. package/dist/index.mjs +3335 -963
  77. package/dist/index.mjs.map +1 -1
  78. package/dist/lane/index.cjs +848 -54
  79. package/dist/lane/index.cjs.map +1 -1
  80. package/dist/lane/index.mjs +849 -55
  81. package/dist/lane/index.mjs.map +1 -1
  82. package/dist/match/index.d.cts +6 -5
  83. package/dist/match/index.d.ts +6 -5
  84. package/dist/pem/index.d.cts +4 -3
  85. package/dist/pem/index.d.ts +4 -3
  86. package/dist/pilot/index.cjs +8 -4
  87. package/dist/pilot/index.cjs.map +1 -1
  88. package/dist/pilot/index.d.cts +4 -3
  89. package/dist/pilot/index.d.ts +4 -3
  90. package/dist/pilot/index.mjs +10 -6
  91. package/dist/pilot/index.mjs.map +1 -1
  92. package/dist/playstore/index.cjs +207 -0
  93. package/dist/playstore/index.cjs.map +1 -0
  94. package/dist/playstore/index.d.cts +1 -0
  95. package/dist/playstore/index.d.ts +1 -0
  96. package/dist/playstore/index.mjs +203 -0
  97. package/dist/playstore/index.mjs.map +1 -0
  98. package/dist/precheck/index.d.cts +4 -3
  99. package/dist/precheck/index.d.ts +4 -3
  100. package/dist/privacy/index.cjs +207 -0
  101. package/dist/privacy/index.cjs.map +1 -0
  102. package/dist/privacy/index.d.cts +2 -0
  103. package/dist/privacy/index.d.ts +2 -0
  104. package/dist/privacy/index.mjs +195 -0
  105. package/dist/privacy/index.mjs.map +1 -0
  106. package/dist/produce/index.cjs +32 -0
  107. package/dist/produce/index.cjs.map +1 -1
  108. package/dist/produce/index.d.cts +4 -3
  109. package/dist/produce/index.d.ts +4 -3
  110. package/dist/produce/index.mjs +32 -1
  111. package/dist/produce/index.mjs.map +1 -1
  112. package/dist/review/index.cjs +141 -0
  113. package/dist/review/index.cjs.map +1 -0
  114. package/dist/review/index.d.cts +2 -0
  115. package/dist/review/index.d.ts +2 -0
  116. package/dist/review/index.mjs +137 -0
  117. package/dist/review/index.mjs.map +1 -0
  118. package/dist/sigh/index.d.cts +4 -3
  119. package/dist/sigh/index.d.ts +4 -3
  120. package/dist/xcode/index.cjs +1 -1
  121. package/dist/xcode/index.cjs.map +1 -1
  122. package/dist/xcode/index.mjs +1 -1
  123. package/dist/xcode/index.mjs.map +1 -1
  124. package/package.json +27 -1
  125. package/dist/index-B9QeTpQK.d.cts +0 -99
  126. package/dist/index-xykpq6Lz.d.ts +0 -99
@@ -0,0 +1,203 @@
1
+ import { readFile } from 'fs/promises';
2
+ import { basename } from 'path';
3
+ import { importPKCS8, SignJWT } from 'jose';
4
+
5
+ // src/playstore/index.ts
6
+ var ANDROIDPUBLISHER_SCOPE = "https://www.googleapis.com/auth/androidpublisher";
7
+ var OAUTH_TOKEN_URL = "https://oauth2.googleapis.com/token";
8
+ var API_BASE = "https://androidpublisher.googleapis.com/androidpublisher/v3";
9
+ var PlayApiError = class extends Error {
10
+ status;
11
+ constructor(message, status) {
12
+ super(message);
13
+ this.name = "PlayApiError";
14
+ this.status = status;
15
+ }
16
+ };
17
+ async function getAccessToken(account) {
18
+ const now = Math.floor(Date.now() / 1e3);
19
+ const privateKey = await importPKCS8(account.private_key, "RS256");
20
+ const assertion = await new SignJWT({ scope: ANDROIDPUBLISHER_SCOPE }).setProtectedHeader({ alg: "RS256", typ: "JWT" }).setIssuer(account.client_email).setAudience(OAUTH_TOKEN_URL).setIssuedAt(now).setExpirationTime(now + 3600).sign(privateKey);
21
+ const response = await fetch(OAUTH_TOKEN_URL, {
22
+ method: "POST",
23
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
24
+ body: new URLSearchParams({
25
+ grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
26
+ assertion
27
+ })
28
+ });
29
+ if (!response.ok) {
30
+ const err = await response.text().catch(() => "");
31
+ throw new PlayApiError(
32
+ `OAuth token request failed: ${response.status} - ${err}`,
33
+ response.status
34
+ );
35
+ }
36
+ const data = await response.json();
37
+ return data.access_token;
38
+ }
39
+ async function readErrorBody(response) {
40
+ const text = await response.text().catch(() => "");
41
+ return text;
42
+ }
43
+ async function uploadToPlay(options) {
44
+ const progress = (msg) => options.onProgress?.(msg);
45
+ let account;
46
+ try {
47
+ account = JSON.parse(options.serviceAccountJson);
48
+ } catch {
49
+ throw new PlayApiError("Invalid service account JSON");
50
+ }
51
+ if (!account.client_email || !account.private_key) {
52
+ throw new PlayApiError(
53
+ "Service account JSON missing client_email or private_key"
54
+ );
55
+ }
56
+ const isAab = options.artifactPath.toLowerCase().endsWith(".aab");
57
+ const isApk = options.artifactPath.toLowerCase().endsWith(".apk");
58
+ if (!isAab && !isApk) {
59
+ throw new PlayApiError(
60
+ `Unsupported artifact extension: ${basename(options.artifactPath)} (expected .aab or .apk)`
61
+ );
62
+ }
63
+ progress("Reading artifact\u2026");
64
+ const artifact = await readFile(options.artifactPath);
65
+ progress("Authenticating with Google\u2026");
66
+ const accessToken = await getAccessToken(account);
67
+ const authHeader = { Authorization: `Bearer ${accessToken}` };
68
+ const editsBase = `${API_BASE}/applications/${encodeURIComponent(
69
+ options.packageName
70
+ )}/edits`;
71
+ progress("Creating edit\u2026");
72
+ const createRes = await fetch(editsBase, {
73
+ method: "POST",
74
+ headers: { ...authHeader, "Content-Type": "application/json" },
75
+ body: "{}"
76
+ });
77
+ if (!createRes.ok) {
78
+ throw new PlayApiError(
79
+ `Failed to create edit: ${createRes.status} - ${await readErrorBody(
80
+ createRes
81
+ )}`,
82
+ createRes.status
83
+ );
84
+ }
85
+ const edit = await createRes.json();
86
+ const editId = edit.id;
87
+ const deleteEdit = async () => {
88
+ await fetch(`${editsBase}/${editId}`, {
89
+ method: "DELETE",
90
+ headers: authHeader
91
+ }).catch(() => {
92
+ });
93
+ };
94
+ try {
95
+ const uploadKind = isAab ? "bundles" : "apks";
96
+ progress(`Uploading ${isAab ? "bundle" : "apk"}\u2026`);
97
+ const uploadUrl = `https://androidpublisher.googleapis.com/upload/androidpublisher/v3/applications/${encodeURIComponent(
98
+ options.packageName
99
+ )}/edits/${editId}/${uploadKind}?uploadType=media`;
100
+ const uploadRes = await fetch(uploadUrl, {
101
+ method: "POST",
102
+ headers: {
103
+ ...authHeader,
104
+ "Content-Type": "application/octet-stream"
105
+ },
106
+ body: artifact
107
+ });
108
+ if (!uploadRes.ok) {
109
+ throw new PlayApiError(
110
+ `Failed to upload artifact: ${uploadRes.status} - ${await readErrorBody(
111
+ uploadRes
112
+ )}`,
113
+ uploadRes.status
114
+ );
115
+ }
116
+ const uploaded = await uploadRes.json();
117
+ const versionCode = uploaded.versionCode;
118
+ progress(`Uploaded versionCode ${versionCode}`);
119
+ progress(`Assigning to ${options.track} track\u2026`);
120
+ const release = {
121
+ versionCodes: [versionCode],
122
+ status: "completed"
123
+ };
124
+ if (options.releaseNotes) {
125
+ release.releaseNotes = [
126
+ { language: "en-US", text: options.releaseNotes }
127
+ ];
128
+ }
129
+ if (options.releaseName) {
130
+ release.name = options.releaseName;
131
+ }
132
+ const trackRes = await fetch(
133
+ `${editsBase}/${editId}/tracks/${options.track}`,
134
+ {
135
+ method: "PUT",
136
+ headers: { ...authHeader, "Content-Type": "application/json" },
137
+ body: JSON.stringify({
138
+ track: options.track,
139
+ releases: [release]
140
+ })
141
+ }
142
+ );
143
+ if (!trackRes.ok) {
144
+ throw new PlayApiError(
145
+ `Failed to update track: ${trackRes.status} - ${await readErrorBody(
146
+ trackRes
147
+ )}`,
148
+ trackRes.status
149
+ );
150
+ }
151
+ progress("Committing edit\u2026");
152
+ const commitRes = await fetch(`${editsBase}/${editId}:commit`, {
153
+ method: "POST",
154
+ headers: authHeader
155
+ });
156
+ if (!commitRes.ok) {
157
+ throw new PlayApiError(
158
+ `Failed to commit edit: ${commitRes.status} - ${await readErrorBody(
159
+ commitRes
160
+ )}`,
161
+ commitRes.status
162
+ );
163
+ }
164
+ progress("Done.");
165
+ return { success: true, versionCode, track: options.track };
166
+ } catch (err) {
167
+ await deleteEdit();
168
+ throw err;
169
+ }
170
+ }
171
+ function classifyPlayError(err) {
172
+ if (err instanceof PlayApiError && typeof err.status === "number") {
173
+ const status = err.status;
174
+ if (status === 429 || status >= 500) return { transient: true };
175
+ if (status >= 400) return { transient: false };
176
+ }
177
+ const message = err instanceof Error ? err.message : String(err);
178
+ if (/\b429\b/.test(message) || /status code 5\d\d/i.test(message)) {
179
+ return { transient: true };
180
+ }
181
+ if (/\b4\d\d\b/.test(message)) {
182
+ return { transient: false };
183
+ }
184
+ const networkPatterns = [
185
+ /ECONNRESET/i,
186
+ /ECONNREFUSED/i,
187
+ /ETIMEDOUT/i,
188
+ /EAI_AGAIN/i,
189
+ /ENOTFOUND/i,
190
+ /socket hang up/i,
191
+ /network/i,
192
+ /timed out/i,
193
+ /fetch failed/i
194
+ ];
195
+ if (networkPatterns.some((p) => p.test(message))) {
196
+ return { transient: true };
197
+ }
198
+ return { transient: false };
199
+ }
200
+
201
+ export { PlayApiError, classifyPlayError, uploadToPlay };
202
+ //# sourceMappingURL=index.mjs.map
203
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/playstore/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Google Play Developer API v3 uploader — the Android-side equivalent of\n * `pilot` (TestFlight). Uses a service-account JSON to mint an OAuth2 access\n * token (scope androidpublisher), then drives the Edits → upload → track →\n * commit flow against the Android Publisher REST API.\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { basename } from \"node:path\";\n\nimport { SignJWT, importPKCS8 } from \"jose\";\n\n// ── Constants ──────────────────────────────────────────────────────────────\n\nconst ANDROIDPUBLISHER_SCOPE =\n \"https://www.googleapis.com/auth/androidpublisher\";\nconst OAUTH_TOKEN_URL = \"https://oauth2.googleapis.com/token\";\nconst API_BASE = \"https://androidpublisher.googleapis.com/androidpublisher/v3\";\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\nexport type PlayTrack = \"internal\" | \"alpha\" | \"beta\" | \"production\";\n\nexport interface PlayUploadOptions {\n packageName: string;\n /** Raw service-account JSON string (with client_email + private_key). */\n serviceAccountJson: string;\n /** Path to the .aab or .apk artifact to upload. */\n artifactPath: string;\n track: PlayTrack;\n releaseNotes?: string;\n releaseName?: string;\n onProgress?: (msg: string) => void;\n}\n\nexport interface PlayUploadResult {\n success: true;\n versionCode: number;\n track: PlayTrack;\n}\n\ninterface ServiceAccount {\n client_email: string;\n private_key: string;\n}\n\n/**\n * Error thrown for any Play upload failure, carrying the HTTP status (if any)\n * so callers/`classifyPlayError` can decide whether to retry.\n */\nexport class PlayApiError extends Error {\n readonly status?: number;\n\n constructor(message: string, status?: number) {\n super(message);\n this.name = \"PlayApiError\";\n this.status = status;\n }\n}\n\n// ── Auth ─────────────────────────────────────────────────────────────────────\n\n/**\n * Mint an OAuth2 access token from a service account, same flow as the FCM\n * client: jose SignJWT (RS256) → POST oauth2.googleapis.com/token.\n */\nasync function getAccessToken(account: ServiceAccount): Promise<string> {\n const now = Math.floor(Date.now() / 1000);\n const privateKey = await importPKCS8(account.private_key, \"RS256\");\n\n const assertion = await new SignJWT({ scope: ANDROIDPUBLISHER_SCOPE })\n .setProtectedHeader({ alg: \"RS256\", typ: \"JWT\" })\n .setIssuer(account.client_email)\n .setAudience(OAUTH_TOKEN_URL)\n .setIssuedAt(now)\n .setExpirationTime(now + 3600)\n .sign(privateKey);\n\n const response = await fetch(OAUTH_TOKEN_URL, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({\n grant_type: \"urn:ietf:params:oauth:grant-type:jwt-bearer\",\n assertion,\n }),\n });\n\n if (!response.ok) {\n const err = await response.text().catch(() => \"\");\n throw new PlayApiError(\n `OAuth token request failed: ${response.status} - ${err}`,\n response.status,\n );\n }\n\n const data = (await response.json()) as { access_token: string };\n return data.access_token;\n}\n\n// ── HTTP helpers ─────────────────────────────────────────────────────────────\n\nasync function readErrorBody(response: Response): Promise<string> {\n const text = await response.text().catch(() => \"\");\n return text;\n}\n\n// ── Main entry point ─────────────────────────────────────────────────────────\n\n/**\n * Upload an artifact (.aab or .apk) to a Google Play track and roll it out.\n *\n * Flow:\n * 1. Create an edit.\n * 2. Upload the bundle/apk bytes, capturing the returned versionCode.\n * 3. Assign that versionCode to the requested track (status \"completed\").\n * 4. Commit the edit.\n * 5. On any failure, delete the edit (best effort) and throw.\n */\nexport async function uploadToPlay(\n options: PlayUploadOptions,\n): Promise<PlayUploadResult> {\n const progress = (msg: string) => options.onProgress?.(msg);\n\n let account: ServiceAccount;\n try {\n account = JSON.parse(options.serviceAccountJson) as ServiceAccount;\n } catch {\n throw new PlayApiError(\"Invalid service account JSON\");\n }\n if (!account.client_email || !account.private_key) {\n throw new PlayApiError(\n \"Service account JSON missing client_email or private_key\",\n );\n }\n\n const isAab = options.artifactPath.toLowerCase().endsWith(\".aab\");\n const isApk = options.artifactPath.toLowerCase().endsWith(\".apk\");\n if (!isAab && !isApk) {\n throw new PlayApiError(\n `Unsupported artifact extension: ${basename(options.artifactPath)} ` +\n `(expected .aab or .apk)`,\n );\n }\n\n progress(\"Reading artifact…\");\n const artifact = await readFile(options.artifactPath);\n\n progress(\"Authenticating with Google…\");\n const accessToken = await getAccessToken(account);\n\n const authHeader = { Authorization: `Bearer ${accessToken}` };\n const editsBase = `${API_BASE}/applications/${encodeURIComponent(\n options.packageName,\n )}/edits`;\n\n // ── 1. Create edit ────────────────────────────────────────────────────\n progress(\"Creating edit…\");\n const createRes = await fetch(editsBase, {\n method: \"POST\",\n headers: { ...authHeader, \"Content-Type\": \"application/json\" },\n body: \"{}\",\n });\n if (!createRes.ok) {\n throw new PlayApiError(\n `Failed to create edit: ${createRes.status} - ${await readErrorBody(\n createRes,\n )}`,\n createRes.status,\n );\n }\n const edit = (await createRes.json()) as { id: string };\n const editId = edit.id;\n\n const deleteEdit = async () => {\n await fetch(`${editsBase}/${editId}`, {\n method: \"DELETE\",\n headers: authHeader,\n }).catch(() => {});\n };\n\n try {\n // ── 2. Upload artifact ──────────────────────────────────────────────\n const uploadKind = isAab ? \"bundles\" : \"apks\";\n progress(`Uploading ${isAab ? \"bundle\" : \"apk\"}…`);\n const uploadUrl = `https://androidpublisher.googleapis.com/upload/androidpublisher/v3/applications/${encodeURIComponent(\n options.packageName,\n )}/edits/${editId}/${uploadKind}?uploadType=media`;\n\n const uploadRes = await fetch(uploadUrl, {\n method: \"POST\",\n headers: {\n ...authHeader,\n \"Content-Type\": \"application/octet-stream\",\n },\n body: artifact,\n });\n if (!uploadRes.ok) {\n throw new PlayApiError(\n `Failed to upload artifact: ${uploadRes.status} - ${await readErrorBody(\n uploadRes,\n )}`,\n uploadRes.status,\n );\n }\n const uploaded = (await uploadRes.json()) as { versionCode: number };\n const versionCode = uploaded.versionCode;\n progress(`Uploaded versionCode ${versionCode}`);\n\n // ── 3. Assign to track ──────────────────────────────────────────────\n progress(`Assigning to ${options.track} track…`);\n const release: Record<string, unknown> = {\n versionCodes: [versionCode],\n status: \"completed\",\n };\n if (options.releaseNotes) {\n release.releaseNotes = [\n { language: \"en-US\", text: options.releaseNotes },\n ];\n }\n if (options.releaseName) {\n release.name = options.releaseName;\n }\n\n const trackRes = await fetch(\n `${editsBase}/${editId}/tracks/${options.track}`,\n {\n method: \"PUT\",\n headers: { ...authHeader, \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n track: options.track,\n releases: [release],\n }),\n },\n );\n if (!trackRes.ok) {\n throw new PlayApiError(\n `Failed to update track: ${trackRes.status} - ${await readErrorBody(\n trackRes,\n )}`,\n trackRes.status,\n );\n }\n\n // ── 4. Commit edit ──────────────────────────────────────────────────\n progress(\"Committing edit…\");\n const commitRes = await fetch(`${editsBase}/${editId}:commit`, {\n method: \"POST\",\n headers: authHeader,\n });\n if (!commitRes.ok) {\n throw new PlayApiError(\n `Failed to commit edit: ${commitRes.status} - ${await readErrorBody(\n commitRes,\n )}`,\n commitRes.status,\n );\n }\n\n progress(\"Done.\");\n return { success: true, versionCode, track: options.track };\n } catch (err) {\n // Best-effort cleanup so a half-finished edit doesn't linger.\n await deleteEdit();\n throw err;\n }\n}\n\n// ── Error classification ─────────────────────────────────────────────────────\n\n/**\n * Classify a Play upload failure into transient vs. fatal — mirrors\n * pilot/classify.ts. HTTP 429/5xx and network-level errors are transient\n * (safe to retry); 4xx (auth, invalid bundle, version conflict) are fatal.\n */\nexport function classifyPlayError(err: unknown): { transient: boolean } {\n if (err instanceof PlayApiError && typeof err.status === \"number\") {\n const status = err.status;\n if (status === 429 || status >= 500) return { transient: true };\n if (status >= 400) return { transient: false };\n }\n\n const message = err instanceof Error ? err.message : String(err);\n\n // Explicit HTTP status mentioned in the message (e.g. wrapped errors).\n if (/\\b429\\b/.test(message) || /status code 5\\d\\d/i.test(message)) {\n return { transient: true };\n }\n if (/\\b4\\d\\d\\b/.test(message)) {\n return { transient: false };\n }\n\n // Network-level failures from fetch/undici are transient.\n const networkPatterns = [\n /ECONNRESET/i,\n /ECONNREFUSED/i,\n /ETIMEDOUT/i,\n /EAI_AGAIN/i,\n /ENOTFOUND/i,\n /socket hang up/i,\n /network/i,\n /timed out/i,\n /fetch failed/i,\n ];\n if (networkPatterns.some((p) => p.test(message))) {\n return { transient: true };\n }\n\n // Default: treat unknown errors as fatal so we don't retry forever.\n return { transient: false };\n}\n"]}
@@ -1,3 +1,4 @@
1
- import '../index-BKUapQP2.cjs';
2
- export { A as ALL_RULES, P as PrecheckOptions, a as PrecheckRule, R as Rule, c as RuleResult, b as ValidationIssue, V as ValidationResult, v as validateMetadata } from '../index-D13sn8DO.cjs';
3
- import '../enums-B609j2rb.cjs';
1
+ import '../index-DrmS802s.cjs';
2
+ export { A as ALL_RULES, P as PrecheckOptions, a as PrecheckRule, R as Rule, c as RuleResult, b as ValidationIssue, V as ValidationResult, v as validateMetadata } from '../index-BE7bwNff.cjs';
3
+ import '../enums-yXNVh3UI.cjs';
4
+ import '../apple-session-jUzBrql5.cjs';
@@ -1,3 +1,4 @@
1
- import '../index-CCtYhPN9.js';
2
- export { A as ALL_RULES, P as PrecheckOptions, a as PrecheckRule, R as Rule, c as RuleResult, b as ValidationIssue, V as ValidationResult, v as validateMetadata } from '../index-YcKz4aRS.js';
3
- import '../enums-B609j2rb.js';
1
+ import '../index-lsuS2R1T.js';
2
+ export { A as ALL_RULES, P as PrecheckOptions, a as PrecheckRule, R as Rule, c as RuleResult, b as ValidationIssue, V as ValidationResult, v as validateMetadata } from '../index-C9mHBJED.js';
3
+ import '../enums-yXNVh3UI.js';
4
+ import '../apple-session-jUzBrql5.js';
@@ -0,0 +1,207 @@
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
+ var DATA_USAGE_CATEGORIES = [
7
+ "PAYMENT_INFORMATION",
8
+ "CREDIT_AND_FRAUD",
9
+ "OTHER_FINANCIAL_INFO",
10
+ "PRECISE_LOCATION",
11
+ "COARSE_LOCATION",
12
+ "SENSITIVE_INFO",
13
+ "PHYSICAL_ADDRESS",
14
+ "EMAIL_ADDRESS",
15
+ "NAME",
16
+ "PHONE_NUMBER",
17
+ "OTHER_CONTACT_INFO",
18
+ "CONTACTS",
19
+ "EMAILS_OR_TEXT_MESSAGES",
20
+ "PHOTOS_OR_VIDEOS",
21
+ "AUDIO",
22
+ "GAMEPLAY_CONTENT",
23
+ "CUSTOMER_SUPPORT",
24
+ "OTHER_USER_CONTENT",
25
+ "BROWSING_HISTORY",
26
+ "SEARCH_HISTORY",
27
+ "USER_ID",
28
+ "DEVICE_ID",
29
+ "PURCHASE_HISTORY",
30
+ "PRODUCT_INTERACTION",
31
+ "ADVERTISING_DATA",
32
+ "OTHER_USAGE_DATA",
33
+ "CRASH_DATA",
34
+ "PERFORMANCE_DATA",
35
+ "OTHER_DIAGNOSTIC_DATA",
36
+ "OTHER_DATA",
37
+ "HEALTH",
38
+ "FITNESS"
39
+ ];
40
+ var DATA_USAGE_PURPOSES = [
41
+ "THIRD_PARTY_ADVERTISING",
42
+ "DEVELOPERS_ADVERTISING",
43
+ "ANALYTICS",
44
+ "PRODUCT_PERSONALIZATION",
45
+ "APP_FUNCTIONALITY",
46
+ "OTHER_PURPOSES"
47
+ ];
48
+ var DATA_PROTECTIONS = [
49
+ "DATA_USED_TO_TRACK_YOU",
50
+ "DATA_LINKED_TO_YOU",
51
+ "DATA_NOT_LINKED_TO_YOU",
52
+ "DATA_NOT_COLLECTED"
53
+ ];
54
+ async function iris(session, method, path, body) {
55
+ const res = await fetch(path.startsWith("http") ? path : `${IRIS_BASE}${path}`, {
56
+ method,
57
+ headers: {
58
+ Cookie: cookieHeader(session),
59
+ Accept: "application/vnd.api+json, application/json",
60
+ "Content-Type": "application/vnd.api+json",
61
+ "X-Requested-With": "XMLHttpRequest"
62
+ },
63
+ body: body ? JSON.stringify(body) : void 0
64
+ });
65
+ absorbCookies(session, res);
66
+ const text = await res.text();
67
+ let parsed = null;
68
+ try {
69
+ parsed = text ? JSON.parse(text) : null;
70
+ } catch {
71
+ }
72
+ return { status: res.status, body: parsed, text };
73
+ }
74
+ function cookieHeader(session) {
75
+ return Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join("; ");
76
+ }
77
+ function absorbCookies(session, res) {
78
+ for (const raw of res.headers.getSetCookie?.() ?? []) {
79
+ const m = raw.match(/^([^=]+)=([^;]*)/);
80
+ if (m) session.cookies[m[1]] = m[2];
81
+ }
82
+ }
83
+ function fail(what, r) {
84
+ const detail = r.body?.errors?.map((e) => e.detail ?? e.title).join("; ") || r.text.slice(0, 300);
85
+ throw new Error(`${what} failed (HTTP ${r.status}): ${detail}`);
86
+ }
87
+ async function selectProviderForApp(session, appId, onLog) {
88
+ const owns = async () => (await iris(session, "GET", `/apps/${appId}?fields[apps]=name`)).status === 200;
89
+ if (await owns()) return;
90
+ const res = await fetch(OLYMPUS_SESSION, { headers: { Cookie: cookieHeader(session), Accept: "application/json" } });
91
+ absorbCookies(session, res);
92
+ const info = await res.json().catch(() => null);
93
+ const providers = info?.availableProviders ?? [];
94
+ for (const p of providers) {
95
+ const sw = await fetch(OLYMPUS_SESSION, {
96
+ method: "POST",
97
+ headers: { Cookie: cookieHeader(session), "Content-Type": "application/json", Accept: "application/json" },
98
+ body: JSON.stringify({ provider: { providerId: p.providerId } })
99
+ });
100
+ absorbCookies(session, sw);
101
+ if (sw.ok && await owns()) {
102
+ onLog?.(`Switched to team "${p.name}"`);
103
+ session.teamId = String(p.providerId);
104
+ return;
105
+ }
106
+ }
107
+ throw new Error(`None of this Apple ID's ${providers.length} teams can see app ${appId}.`);
108
+ }
109
+ async function listAppDataUsages(session, appId) {
110
+ const all = [];
111
+ let next = `/apps/${appId}/dataUsages?include=category,grouping,purpose,dataProtection&limit=500`;
112
+ while (next) {
113
+ const r = await iris(session, "GET", next);
114
+ if (r.status !== 200) fail("List app data usages", r);
115
+ all.push(...(r.body?.data ?? []).map((d) => ({ id: d.id })));
116
+ next = r.body?.links?.next ?? null;
117
+ }
118
+ return all;
119
+ }
120
+ async function createAppDataUsage(session, input) {
121
+ const relationships = { app: { data: { type: "apps", id: input.appId } } };
122
+ if (input.category) relationships.category = { data: { type: "appDataUsageCategories", id: input.category } };
123
+ if (input.purpose) relationships.purpose = { data: { type: "appDataUsagePurposes", id: input.purpose } };
124
+ relationships.dataProtection = { data: { type: "appDataUsageDataProtections", id: input.dataProtection } };
125
+ const r = await iris(session, "POST", "/appDataUsages", { data: { type: "appDataUsages", relationships } });
126
+ if (r.status !== 201 && r.status !== 200) fail(`Create ${input.category ?? ""} ${input.purpose ?? ""} ${input.dataProtection}`.replace(/\s+/g, " "), r);
127
+ return r.body?.data?.id;
128
+ }
129
+ async function deleteAppDataUsage(session, id) {
130
+ const r = await iris(session, "DELETE", `/appDataUsages/${id}`);
131
+ if (r.status !== 204 && r.status !== 200) fail(`Delete app data usage ${id}`, r);
132
+ }
133
+ async function getAppDataUsagesPublishState(session, appId) {
134
+ const r = await iris(session, "GET", `/apps/${appId}/dataUsagePublishState`);
135
+ if (r.status !== 200) fail("Read privacy publish state", r);
136
+ return { id: r.body.data.id, published: Boolean(r.body.data.attributes?.published) };
137
+ }
138
+ async function publishAppDataUsages(session, publishStateId) {
139
+ const r = await iris(session, "PATCH", `/appDataUsagesPublishState/${publishStateId}`, {
140
+ data: { type: "appDataUsagesPublishState", id: publishStateId, attributes: { published: true } }
141
+ });
142
+ if (r.status !== 200) fail("Publish privacy details", r);
143
+ }
144
+ async function uploadAppPrivacyDetails(opts) {
145
+ const { session, appId, usages, skipPublish, onLog = () => {
146
+ } } = opts;
147
+ validateUsages(usages);
148
+ await selectProviderForApp(session, appId, onLog);
149
+ const existing = await listAppDataUsages(session, appId);
150
+ onLog(`Deleting ${existing.length} existing answer${existing.length === 1 ? "" : "s"}`);
151
+ for (const u of existing) await deleteAppDataUsage(session, u.id);
152
+ let created = 0;
153
+ for (const usage of usages) {
154
+ const purposes = usage.purposes?.length ? usage.purposes : [null];
155
+ for (const purpose of purposes) {
156
+ for (const protection of usage.data_protections) {
157
+ onLog(protection === "DATA_NOT_COLLECTED" ? `Setting ${protection}` : `Setting ${usage.category} and ${purpose} to ${protection}`);
158
+ await createAppDataUsage(session, { appId, category: usage.category ?? null, purpose, dataProtection: protection });
159
+ created++;
160
+ }
161
+ }
162
+ }
163
+ if (skipPublish) {
164
+ onLog("Skipping publish");
165
+ return { deleted: existing.length, created, published: false };
166
+ }
167
+ const state = await getAppDataUsagesPublishState(session, appId);
168
+ await publishAppDataUsages(session, state.id);
169
+ onLog("Published");
170
+ return { deleted: existing.length, created, published: true };
171
+ }
172
+ function validateUsages(usages) {
173
+ if (!Array.isArray(usages) || usages.length === 0) throw new Error("No privacy usages given.");
174
+ const notCollected = usages.some((u) => u.data_protections?.includes("DATA_NOT_COLLECTED"));
175
+ if (notCollected && usages.length > 1) {
176
+ throw new Error("DATA_NOT_COLLECTED must be the only entry: an app cannot both collect nothing and collect something.");
177
+ }
178
+ const known = (list, v, what) => {
179
+ if (!list.includes(v)) throw new Error(`"${v}" is not an App Store Connect ${what}. Use one of: ${list.join(", ")}.`);
180
+ };
181
+ for (const u of usages) {
182
+ if (!u.data_protections?.length) throw new Error(`${u.category ?? "An entry"} has no data_protections.`);
183
+ if (u.category) known(DATA_USAGE_CATEGORIES, u.category, "data category");
184
+ for (const p of u.purposes ?? []) known(DATA_USAGE_PURPOSES, p, "purpose");
185
+ for (const d of u.data_protections) known(DATA_PROTECTIONS, d, "data protection");
186
+ if (u.data_protections.includes("DATA_NOT_COLLECTED")) continue;
187
+ if (!u.category) throw new Error("Every collected entry needs a category.");
188
+ if (!u.purposes?.length) throw new Error(`${u.category} needs at least one purpose.`);
189
+ const linked = u.data_protections.includes("DATA_LINKED_TO_YOU");
190
+ const unlinked = u.data_protections.includes("DATA_NOT_LINKED_TO_YOU");
191
+ if (linked && unlinked) throw new Error(`${u.category} cannot be both linked and not linked to the user.`);
192
+ }
193
+ }
194
+
195
+ exports.DATA_PROTECTIONS = DATA_PROTECTIONS;
196
+ exports.DATA_USAGE_CATEGORIES = DATA_USAGE_CATEGORIES;
197
+ exports.DATA_USAGE_PURPOSES = DATA_USAGE_PURPOSES;
198
+ exports.createAppDataUsage = createAppDataUsage;
199
+ exports.deleteAppDataUsage = deleteAppDataUsage;
200
+ exports.getAppDataUsagesPublishState = getAppDataUsagesPublishState;
201
+ exports.listAppDataUsages = listAppDataUsages;
202
+ exports.publishAppDataUsages = publishAppDataUsages;
203
+ exports.selectProviderForApp = selectProviderForApp;
204
+ exports.uploadAppPrivacyDetails = uploadAppPrivacyDetails;
205
+ exports.validateUsages = validateUsages;
206
+ //# sourceMappingURL=index.cjs.map
207
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/privacy/index.ts"],"names":[],"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"]}
@@ -0,0 +1,2 @@
1
+ import '../apple-session-jUzBrql5.cjs';
2
+ export { A as AppPrivacyUsage, b as DATA_PROTECTIONS, D as DATA_USAGE_CATEGORIES, a as DATA_USAGE_PURPOSES, e as DataProtection, c as DataUsageCategory, d as DataUsagePurpose, U as UploadAppPrivacyOptions, f as UploadAppPrivacyResult, g as createAppDataUsage, h as deleteAppDataUsage, j as getAppDataUsagesPublishState, l as listAppDataUsages, p as publishAppDataUsages, s as selectProviderForApp, u as uploadAppPrivacyDetails, v as validateUsages } from '../index-DnHQ2uT2.cjs';
@@ -0,0 +1,2 @@
1
+ import '../apple-session-jUzBrql5.js';
2
+ export { A as AppPrivacyUsage, b as DATA_PROTECTIONS, D as DATA_USAGE_CATEGORIES, a as DATA_USAGE_PURPOSES, e as DataProtection, c as DataUsageCategory, d as DataUsagePurpose, U as UploadAppPrivacyOptions, f as UploadAppPrivacyResult, g as createAppDataUsage, h as deleteAppDataUsage, j as getAppDataUsagesPublishState, l as listAppDataUsages, p as publishAppDataUsages, s as selectProviderForApp, u as uploadAppPrivacyDetails, v as validateUsages } from '../index-8djTPW8O.js';