@gpc-cli/core 0.9.65 → 0.9.67
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/dist/{chunk-4TBNOCVU.js → chunk-4KDWLQB3.js} +85 -37
- package/dist/chunk-4KDWLQB3.js.map +1 -0
- package/dist/index.d.ts +17 -4
- package/dist/index.js +119 -42
- package/dist/index.js.map +1 -1
- package/dist/{releases-XF5MYFQD.js → releases-OAKWPEAC.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-4TBNOCVU.js.map +0 -1
- /package/dist/{releases-XF5MYFQD.js.map → releases-OAKWPEAC.js.map} +0 -0
|
@@ -164,6 +164,22 @@ import { PlayApiError as PlayApiError3 } from "@gpc-cli/api";
|
|
|
164
164
|
// src/utils/file-validation.ts
|
|
165
165
|
import { open, stat } from "fs/promises";
|
|
166
166
|
import { extname as extname2 } from "path";
|
|
167
|
+
|
|
168
|
+
// src/utils/format.ts
|
|
169
|
+
function formatSize(bytes) {
|
|
170
|
+
if (bytes >= 1024 * 1024 * 1024) {
|
|
171
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
|
172
|
+
}
|
|
173
|
+
if (bytes >= 1024 * 1024) {
|
|
174
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
175
|
+
}
|
|
176
|
+
if (bytes >= 1024) {
|
|
177
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
178
|
+
}
|
|
179
|
+
return `${bytes} B`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/utils/file-validation.ts
|
|
167
183
|
var ZIP_MAGIC = Buffer.from([80, 75, 3, 4]);
|
|
168
184
|
var MAX_APK_SIZE = 1024 * 1024 * 1024;
|
|
169
185
|
var MAX_AAB_SIZE = 2 * 1024 * 1024 * 1024;
|
|
@@ -229,35 +245,24 @@ async function validateUploadFile(filePath) {
|
|
|
229
245
|
warnings
|
|
230
246
|
};
|
|
231
247
|
}
|
|
232
|
-
function formatSize(bytes) {
|
|
233
|
-
if (bytes >= 1024 * 1024 * 1024) {
|
|
234
|
-
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
|
235
|
-
}
|
|
236
|
-
if (bytes >= 1024 * 1024) {
|
|
237
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
238
|
-
}
|
|
239
|
-
if (bytes >= 1024) {
|
|
240
|
-
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
241
|
-
}
|
|
242
|
-
return `${bytes} B`;
|
|
243
|
-
}
|
|
244
248
|
|
|
245
249
|
// src/utils/edit-helpers.ts
|
|
246
250
|
import { PlayApiError } from "@gpc-cli/api";
|
|
251
|
+
var REVIEW_PENDING_WARNING = "Changes committed but NOT sent for review (app has a rejected update).\nNext step: Open Google Play Console > Publishing overview > Send for review";
|
|
247
252
|
async function commitWithRescue(client, packageName, editId, commitOptions) {
|
|
248
253
|
try {
|
|
249
254
|
await client.edits.commit(packageName, editId, commitOptions);
|
|
255
|
+
return { rescued: false };
|
|
250
256
|
} catch (error) {
|
|
251
257
|
if (error instanceof PlayApiError && error.code === "API_CHANGES_NOT_SENT_FOR_REVIEW" && !commitOptions?.changesNotSentForReview) {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
);
|
|
258
|
+
console.error(`
|
|
259
|
+
WARNING: ${REVIEW_PENDING_WARNING}
|
|
260
|
+
`);
|
|
256
261
|
await client.edits.commit(packageName, editId, {
|
|
257
262
|
...commitOptions,
|
|
258
263
|
changesNotSentForReview: true
|
|
259
264
|
});
|
|
260
|
-
return;
|
|
265
|
+
return { rescued: true };
|
|
261
266
|
}
|
|
262
267
|
throw error;
|
|
263
268
|
}
|
|
@@ -269,10 +274,9 @@ async function validateAndCommit(client, packageName, editId, commitOptions) {
|
|
|
269
274
|
await client.edits.validate(packageName, editId);
|
|
270
275
|
} catch (error) {
|
|
271
276
|
if (error instanceof PlayApiError && error.code === "API_CHANGES_NOT_SENT_FOR_REVIEW") {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
);
|
|
277
|
+
console.error(`
|
|
278
|
+
WARNING: ${REVIEW_PENDING_WARNING}
|
|
279
|
+
`);
|
|
276
280
|
rescuedFromValidate = true;
|
|
277
281
|
} else {
|
|
278
282
|
throw error;
|
|
@@ -284,9 +288,9 @@ async function validateAndCommit(client, packageName, editId, commitOptions) {
|
|
|
284
288
|
...commitOptions,
|
|
285
289
|
changesNotSentForReview: true
|
|
286
290
|
});
|
|
287
|
-
|
|
288
|
-
await commitWithRescue(client, packageName, editId, commitOptions);
|
|
291
|
+
return { rescued: true };
|
|
289
292
|
}
|
|
293
|
+
return commitWithRescue(client, packageName, editId, commitOptions);
|
|
290
294
|
}
|
|
291
295
|
|
|
292
296
|
// src/commands/bundles.ts
|
|
@@ -339,10 +343,16 @@ async function waitForBundle(client, packageName, versionCode, options) {
|
|
|
339
343
|
|
|
340
344
|
// src/commands/releases.ts
|
|
341
345
|
var BUNDLE_POLL_BACKOFF = [2e3, 3e3, 5e3, 8e3, 13e3, 21e3, 34e3];
|
|
346
|
+
var REVIEW_PENDING_NEXT_STEP = "Send for review from Google Play Console > Publishing overview";
|
|
342
347
|
async function waitForBundleProcessing(client, packageName, editId, versionCode, backoff = BUNDLE_POLL_BACKOFF) {
|
|
348
|
+
const start = Date.now();
|
|
343
349
|
for (let i = 0; i < backoff.length; i++) {
|
|
344
350
|
const bundles = await client.bundles.list(packageName, editId);
|
|
345
351
|
if (bundles.some((b) => b.versionCode === versionCode)) return;
|
|
352
|
+
const elapsed = Math.round((Date.now() - start) / 1e3);
|
|
353
|
+
console.error(
|
|
354
|
+
` Waiting for Google to finish processing bundle... (${elapsed}s, attempt ${i + 1}/${backoff.length})`
|
|
355
|
+
);
|
|
346
356
|
await new Promise((r) => setTimeout(r, backoff[i]));
|
|
347
357
|
}
|
|
348
358
|
throw new GpcError(
|
|
@@ -463,7 +473,14 @@ async function uploadRelease(client, packageName, filePath, options) {
|
|
|
463
473
|
plannedRelease: {
|
|
464
474
|
status: plannedStatus,
|
|
465
475
|
...options.userFraction !== void 0 && { userFraction: options.userFraction }
|
|
466
|
-
}
|
|
476
|
+
},
|
|
477
|
+
executed: [
|
|
478
|
+
"Validated file",
|
|
479
|
+
"Opened edit session (read-only)",
|
|
480
|
+
"Fetched current track state",
|
|
481
|
+
"Discarded edit"
|
|
482
|
+
],
|
|
483
|
+
skipped: ["Upload bundle", "Assign to track", "Commit edit"]
|
|
467
484
|
};
|
|
468
485
|
}
|
|
469
486
|
if (!validation.valid) {
|
|
@@ -549,13 +566,24 @@ ${validation.errors.join("\n")}`,
|
|
|
549
566
|
validateOnly: true
|
|
550
567
|
};
|
|
551
568
|
}
|
|
552
|
-
await retryOnUploadNotComplete(
|
|
569
|
+
const commitResult = await retryOnUploadNotComplete(
|
|
553
570
|
() => commitWithRescue(client, packageName, edit.id, options.commitOptions)
|
|
554
571
|
);
|
|
572
|
+
const isInternal = options.track === "internal";
|
|
573
|
+
if (isInternal) {
|
|
574
|
+
console.error(
|
|
575
|
+
" Internal track: no Google review required. Testers will see this build immediately."
|
|
576
|
+
);
|
|
577
|
+
}
|
|
555
578
|
return {
|
|
556
579
|
versionCode: bundle.versionCode,
|
|
557
580
|
track: options.track,
|
|
558
|
-
status: release.status
|
|
581
|
+
status: release.status,
|
|
582
|
+
...isInternal && { reviewSkipped: true },
|
|
583
|
+
...commitResult.rescued && !isInternal && {
|
|
584
|
+
reviewPending: true,
|
|
585
|
+
nextStep: REVIEW_PENDING_NEXT_STEP
|
|
586
|
+
}
|
|
559
587
|
};
|
|
560
588
|
} catch (error) {
|
|
561
589
|
await client.edits.delete(packageName, edit.id).catch(() => {
|
|
@@ -620,12 +648,21 @@ async function promoteRelease(client, packageName, fromTrack, toTrack, options)
|
|
|
620
648
|
...currentRelease.name && { name: currentRelease.name }
|
|
621
649
|
};
|
|
622
650
|
await client.tracks.update(packageName, edit.id, toTrack, release);
|
|
623
|
-
await validateAndCommit(
|
|
651
|
+
const commitResult = await validateAndCommit(
|
|
652
|
+
client,
|
|
653
|
+
packageName,
|
|
654
|
+
edit.id,
|
|
655
|
+
options?.commitOptions
|
|
656
|
+
);
|
|
624
657
|
return {
|
|
625
658
|
track: toTrack,
|
|
626
659
|
status: release.status,
|
|
627
660
|
versionCodes: release.versionCodes,
|
|
628
|
-
userFraction: release.userFraction
|
|
661
|
+
userFraction: release.userFraction,
|
|
662
|
+
...commitResult.rescued && {
|
|
663
|
+
reviewPending: true,
|
|
664
|
+
nextStep: REVIEW_PENDING_NEXT_STEP
|
|
665
|
+
}
|
|
629
666
|
};
|
|
630
667
|
});
|
|
631
668
|
}
|
|
@@ -649,10 +686,7 @@ async function assignRelease(client, packageName, versionCode, options) {
|
|
|
649
686
|
}
|
|
650
687
|
const code = String(versionCode);
|
|
651
688
|
const release = {
|
|
652
|
-
versionCodes: [
|
|
653
|
-
...(options.retainVersionCodes || []).filter((vc) => vc !== code),
|
|
654
|
-
code
|
|
655
|
-
],
|
|
689
|
+
versionCodes: [...(options.retainVersionCodes || []).filter((vc) => vc !== code), code],
|
|
656
690
|
status: options.status || (options.userFraction ? "inProgress" : "completed"),
|
|
657
691
|
...options.userFraction && { userFraction: options.userFraction },
|
|
658
692
|
...options.releaseNotes && { releaseNotes: options.releaseNotes },
|
|
@@ -672,12 +706,21 @@ async function assignRelease(client, packageName, versionCode, options) {
|
|
|
672
706
|
const edit = await client.edits.insert(packageName);
|
|
673
707
|
try {
|
|
674
708
|
await client.tracks.update(packageName, edit.id, options.track, release);
|
|
675
|
-
await validateAndCommit(
|
|
709
|
+
const commitResult = await validateAndCommit(
|
|
710
|
+
client,
|
|
711
|
+
packageName,
|
|
712
|
+
edit.id,
|
|
713
|
+
options.commitOptions
|
|
714
|
+
);
|
|
676
715
|
return {
|
|
677
716
|
track: options.track,
|
|
678
717
|
status: release.status,
|
|
679
718
|
versionCodes: release.versionCodes,
|
|
680
|
-
userFraction: release.userFraction
|
|
719
|
+
userFraction: release.userFraction,
|
|
720
|
+
...commitResult.rescued && {
|
|
721
|
+
reviewPending: true,
|
|
722
|
+
nextStep: REVIEW_PENDING_NEXT_STEP
|
|
723
|
+
}
|
|
681
724
|
};
|
|
682
725
|
} catch (error) {
|
|
683
726
|
await client.edits.delete(packageName, edit.id).catch(() => {
|
|
@@ -742,12 +785,16 @@ async function updateRollout(client, packageName, track, action, userFraction, c
|
|
|
742
785
|
releaseNotes: currentRelease.releaseNotes || []
|
|
743
786
|
};
|
|
744
787
|
await client.tracks.update(packageName, edit.id, track, release);
|
|
745
|
-
await validateAndCommit(client, packageName, edit.id, commitOptions);
|
|
788
|
+
const commitResult = await validateAndCommit(client, packageName, edit.id, commitOptions);
|
|
746
789
|
return {
|
|
747
790
|
track,
|
|
748
791
|
status: newStatus,
|
|
749
792
|
versionCodes: release.versionCodes,
|
|
750
|
-
userFraction: newFraction
|
|
793
|
+
userFraction: newFraction,
|
|
794
|
+
...commitResult.rescued && {
|
|
795
|
+
reviewPending: true,
|
|
796
|
+
nextStep: REVIEW_PENDING_NEXT_STEP
|
|
797
|
+
}
|
|
751
798
|
};
|
|
752
799
|
} catch (error) {
|
|
753
800
|
await client.edits.delete(packageName, edit.id).catch(() => {
|
|
@@ -944,6 +991,7 @@ export {
|
|
|
944
991
|
isVersionedNotesDir,
|
|
945
992
|
readReleaseNotesForVersion,
|
|
946
993
|
validateReleaseNotes,
|
|
994
|
+
formatSize,
|
|
947
995
|
validateUploadFile,
|
|
948
996
|
commitWithRescue,
|
|
949
997
|
validateAndCommit,
|
|
@@ -966,4 +1014,4 @@ export {
|
|
|
966
1014
|
diffReleases,
|
|
967
1015
|
uploadExternallyHosted
|
|
968
1016
|
};
|
|
969
|
-
//# sourceMappingURL=chunk-
|
|
1017
|
+
//# sourceMappingURL=chunk-4KDWLQB3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/releases.ts","../src/utils/release-notes.ts","../src/errors.ts","../src/utils/file-validation.ts","../src/utils/format.ts","../src/utils/edit-helpers.ts","../src/commands/bundles.ts"],"sourcesContent":["import { stat } from \"node:fs/promises\";\nimport { extname } from \"node:path\";\nimport { readReleaseNotesForVersion } from \"../utils/release-notes.js\";\nimport type {\n PlayApiClient,\n Release,\n Track,\n ExternallyHostedApk,\n ExternallyHostedApkResponse,\n UploadProgressEvent,\n ResumableUploadOptions,\n EditCommitOptions,\n DeobfuscationFileType,\n} from \"@gpc-cli/api\";\nimport type { AppEdit } from \"@gpc-cli/api\";\nimport { PlayApiError } from \"@gpc-cli/api\";\nimport { GpcError } from \"../errors.js\";\nimport { validateUploadFile } from \"../utils/file-validation.js\";\nimport { validateAndCommit, commitWithRescue } from \"../utils/edit-helpers.js\";\nimport { findBundle } from \"./bundles.js\";\n\nconst BUNDLE_POLL_BACKOFF = [2_000, 3_000, 5_000, 8_000, 13_000, 21_000, 34_000];\nconst REVIEW_PENDING_NEXT_STEP = \"Send for review from Google Play Console > Publishing overview\";\n\nexport async function waitForBundleProcessing(\n client: PlayApiClient,\n packageName: string,\n editId: string,\n versionCode: number,\n backoff: number[] = BUNDLE_POLL_BACKOFF,\n): Promise<void> {\n const start = Date.now();\n for (let i = 0; i < backoff.length; i++) {\n const bundles = await client.bundles.list(packageName, editId);\n if (bundles.some((b) => b.versionCode === versionCode)) return;\n const elapsed = Math.round((Date.now() - start) / 1000);\n console.error(\n ` Waiting for Google to finish processing bundle... (${elapsed}s, attempt ${i + 1}/${backoff.length})`,\n );\n await new Promise((r) => setTimeout(r, backoff[i]));\n }\n throw new GpcError(\n `Bundle versionCode ${versionCode} not ready after ${backoff.length} poll attempts (~${Math.round(backoff.reduce((a, b) => a + b, 0) / 1000)}s)`,\n \"BUNDLE_PROCESSING_TIMEOUT\",\n 4,\n \"The AAB is still being processed by Google. Retry the upload, or use --status draft and commit later.\",\n );\n}\n\nconst UPLOAD_NOT_COMPLETE_RE = /uploads? (?:is|are) not completed/i;\nconst UPLOAD_RETRY_BACKOFF = [15_000, 30_000, 45_000];\n\nexport async function retryOnUploadNotComplete<T>(fn: () => Promise<T>): Promise<T> {\n for (let i = 0; i < UPLOAD_RETRY_BACKOFF.length; i++) {\n try {\n return await fn();\n } catch (error) {\n const isUploadNotComplete =\n error instanceof PlayApiError && UPLOAD_NOT_COMPLETE_RE.test(error.message);\n if (!isUploadNotComplete || i === UPLOAD_RETRY_BACKOFF.length - 1) throw error;\n await new Promise((r) => setTimeout(r, UPLOAD_RETRY_BACKOFF[i]));\n }\n }\n return fn();\n}\n\n/**\n * Retry an edit-based operation once if it fails with 409 Conflict (stale edit).\n * Automatically discards the stale edit and creates a fresh one on retry.\n */\nasync function withRetryOnConflict<T>(\n client: PlayApiClient,\n packageName: string,\n operation: (edit: AppEdit) => Promise<T>,\n): Promise<T> {\n const edit = await client.edits.insert(packageName);\n try {\n return await operation(edit);\n } catch (error) {\n const isConflict = error instanceof PlayApiError && error.statusCode === 409;\n if (!isConflict) {\n await client.edits.delete(packageName, edit.id).catch(() => {});\n throw error;\n }\n // Discard stale edit, retry with fresh one\n await client.edits.delete(packageName, edit.id).catch(() => {});\n const freshEdit = await client.edits.insert(packageName);\n try {\n return await operation(freshEdit);\n } catch (retryError) {\n await client.edits.delete(packageName, freshEdit.id).catch(() => {});\n throw retryError;\n }\n }\n}\n\n/** Warn if edit is within 5 minutes of expiry. */\nlet _consoleEditWarningShown = false;\nfunction warnAboutConcurrentEdits(): void {\n if (_consoleEditWarningShown) return;\n _consoleEditWarningShown = true;\n process.emitWarning?.(\n \"If the Play Console has pending changes, they may be discarded when this edit is committed. \" +\n \"Avoid making changes in the Play Console while CLI operations are in progress.\",\n \"ConcurrentEditWarning\",\n );\n}\n\nfunction warnIfEditExpiring(edit: AppEdit): void {\n if (!edit.expiryTimeSeconds) return;\n const expiryMs = Number(edit.expiryTimeSeconds) * 1000;\n const remainingMs = expiryMs - Date.now();\n if (remainingMs < 5 * 60 * 1000 && remainingMs > 0) {\n const minutes = Math.round(remainingMs / 60_000);\n process.emitWarning?.(\n `Edit session expires in ~${minutes} minute${minutes !== 1 ? \"s\" : \"\"}. Long uploads may fail. Consider starting a fresh operation.`,\n \"EditExpiryWarning\",\n );\n }\n}\n\n/**\n * Run an edit-lifecycle operation with automatic retry on expired-edit errors.\n * If the API returns API_EDIT_EXPIRED (FAILED_PRECONDITION), the helper opens a\n * fresh edit and retries the operation exactly once.\n */\nexport async function withFreshEdit<T>(\n client: PlayApiClient,\n packageName: string,\n operation: (editId: string) => Promise<T>,\n): Promise<T> {\n const edit = await client.edits.insert(packageName);\n try {\n return await operation(edit.id);\n } catch (error) {\n if (error instanceof PlayApiError && error.code === \"API_EDIT_EXPIRED\") {\n // Discard stale edit (best effort) and retry with a fresh one\n await client.edits.delete(packageName, edit.id).catch(() => {});\n const freshEdit = await client.edits.insert(packageName);\n try {\n return await operation(freshEdit.id);\n } catch (retryError) {\n await client.edits.delete(packageName, freshEdit.id).catch(() => {});\n throw retryError;\n }\n }\n await client.edits.delete(packageName, edit.id).catch(() => {});\n throw error;\n }\n}\n\nexport interface UploadResult {\n versionCode: number;\n track: string;\n status: string;\n validateOnly?: true;\n reviewPending?: boolean;\n reviewSkipped?: boolean;\n nextStep?: string;\n}\n\nexport interface ReleaseStatusResult {\n track: string;\n status: string;\n versionCodes: string[];\n userFraction?: number;\n releaseNotes?: { language: string; text: string }[];\n reviewPending?: boolean;\n reviewSkipped?: boolean;\n nextStep?: string;\n}\n\nexport interface DryRunUploadResult {\n dryRun: true;\n file: { path: string; valid: boolean; errors: string[]; warnings: string[] };\n track: string;\n currentReleases: { versionCodes: string[]; status: string; userFraction?: number }[];\n plannedRelease: { status: string; userFraction?: number };\n executed: string[];\n skipped: string[];\n}\n\nexport async function uploadRelease(\n client: PlayApiClient,\n packageName: string,\n filePath: string,\n options: {\n track: string;\n status?: string;\n userFraction?: number;\n releaseNotes?: { language: string; text: string }[];\n releaseName?: string;\n mappingFile?: string;\n mappingFileType?: DeobfuscationFileType;\n dryRun?: boolean;\n validateOnly?: boolean;\n onProgress?: (uploaded: number, total: number) => void;\n onUploadProgress?: (event: UploadProgressEvent) => void;\n uploadOptions?: Pick<\n ResumableUploadOptions,\n \"chunkSize\" | \"resumeSessionUri\" | \"maxResumeAttempts\"\n >;\n deviceTierConfigId?: string;\n commitOptions?: EditCommitOptions;\n inAppUpdatePriority?: number;\n retainVersionCodes?: string[];\n notesDirVersioned?: string;\n },\n): Promise<UploadResult | DryRunUploadResult> {\n // Validate file before upload\n const validation = await validateUploadFile(filePath);\n\n if (options.dryRun) {\n const plannedStatus = options.status || (options.userFraction ? \"inProgress\" : \"completed\");\n\n // Fetch current track state without modifying anything\n let currentReleases: DryRunUploadResult[\"currentReleases\"] = [];\n const edit = await client.edits.insert(packageName);\n try {\n const trackData = await client.tracks.get(packageName, edit.id, options.track);\n currentReleases = (trackData.releases || []).map((r) => ({\n versionCodes: r.versionCodes || [],\n status: r.status,\n ...(r.userFraction !== undefined && { userFraction: r.userFraction }),\n }));\n } catch {\n // Track may not exist yet — that's fine for dry-run\n } finally {\n await client.edits.delete(packageName, edit.id).catch(() => {});\n }\n\n return {\n dryRun: true,\n file: {\n path: filePath,\n valid: validation.valid,\n errors: validation.errors,\n warnings: validation.warnings,\n },\n track: options.track,\n currentReleases,\n plannedRelease: {\n status: plannedStatus,\n ...(options.userFraction !== undefined && { userFraction: options.userFraction }),\n },\n executed: [\n \"Validated file\",\n \"Opened edit session (read-only)\",\n \"Fetched current track state\",\n \"Discarded edit\",\n ],\n skipped: [\"Upload bundle\", \"Assign to track\", \"Commit edit\"],\n };\n }\n\n if (!validation.valid) {\n throw new GpcError(\n `File validation failed:\\n${validation.errors.join(\"\\n\")}`,\n \"RELEASE_INVALID_FILE\",\n 2,\n \"Check that the file is a valid AAB or APK and is not corrupted.\",\n );\n }\n\n // Get file size for progress reporting\n let fileSize = 0;\n try {\n const { size } = await stat(filePath);\n fileSize = size;\n } catch {\n /* ignore — file was validated above */\n }\n\n if (options.onProgress) options.onProgress(0, fileSize);\n\n const edit = await client.edits.insert(packageName);\n warnIfEditExpiring(edit);\n warnAboutConcurrentEdits();\n try {\n // Upload AAB or APK via the appropriate endpoint\n const isApk = extname(filePath).toLowerCase() === \".apk\";\n const uploadOpts = {\n ...options.uploadOptions,\n onProgress: (event: UploadProgressEvent) => {\n if (options.onProgress) options.onProgress(event.bytesUploaded, event.totalBytes);\n if (options.onUploadProgress) options.onUploadProgress(event);\n },\n };\n const bundle = isApk\n ? await client.apks.upload(packageName, edit.id, filePath, uploadOpts)\n : await client.bundles.upload(\n packageName,\n edit.id,\n filePath,\n uploadOpts,\n options.deviceTierConfigId,\n );\n\n // Wait for server-side AAB processing before proceeding.\n // Google's API returns from bundles.upload before manifest extraction\n // and signature verification finish, causing edits.validate to fail\n // with \"uploads are not completed yet\" on large bundles (~65MB+).\n if (!isApk) {\n await waitForBundleProcessing(client, packageName, edit.id, bundle.versionCode);\n }\n\n // Upload mapping file if provided\n if (options.mappingFile) {\n await client.deobfuscation.upload(\n packageName,\n edit.id,\n bundle.versionCode,\n options.mappingFile,\n options.mappingFileType,\n );\n }\n\n // Resolve versioned release notes now that versionCode is known\n let releaseNotes = options.releaseNotes;\n if (!releaseNotes && options.notesDirVersioned) {\n releaseNotes = await readReleaseNotesForVersion(\n options.notesDirVersioned,\n bundle.versionCode,\n );\n if (releaseNotes.length === 0) releaseNotes = undefined;\n }\n\n // Create release and assign to track\n const uploadedCode = String(bundle.versionCode);\n const release: Release = {\n versionCodes: [\n ...(options.retainVersionCodes || []).filter((vc) => vc !== uploadedCode),\n uploadedCode,\n ],\n status: (options.status ||\n (options.userFraction ? \"inProgress\" : \"completed\")) as Release[\"status\"],\n ...(options.userFraction && { userFraction: options.userFraction }),\n ...(releaseNotes && { releaseNotes }),\n ...(options.releaseName && { name: options.releaseName }),\n ...(options.inAppUpdatePriority !== undefined && {\n inAppUpdatePriority: options.inAppUpdatePriority,\n }),\n };\n\n await client.tracks.update(packageName, edit.id, options.track, release);\n\n if (!options.commitOptions?.changesNotSentForReview) {\n await retryOnUploadNotComplete(() => client.edits.validate(packageName, edit.id));\n }\n\n if (options.validateOnly) {\n await client.edits.delete(packageName, edit.id).catch(() => {});\n return {\n versionCode: bundle.versionCode,\n track: options.track,\n status: release.status,\n validateOnly: true,\n };\n }\n\n const commitResult = await retryOnUploadNotComplete(() =>\n commitWithRescue(client, packageName, edit.id, options.commitOptions),\n );\n\n const isInternal = options.track === \"internal\";\n if (isInternal) {\n console.error(\n \" Internal track: no Google review required. Testers will see this build immediately.\",\n );\n }\n\n return {\n versionCode: bundle.versionCode,\n track: options.track,\n status: release.status,\n ...(isInternal && { reviewSkipped: true }),\n ...(commitResult.rescued &&\n !isInternal && {\n reviewPending: true,\n nextStep: REVIEW_PENDING_NEXT_STEP,\n }),\n };\n } catch (error) {\n await client.edits.delete(packageName, edit.id).catch(() => {});\n throw error;\n }\n}\n\nexport async function getReleasesStatus(\n client: PlayApiClient,\n packageName: string,\n trackFilter?: string,\n): Promise<ReleaseStatusResult[]> {\n const edit = await client.edits.insert(packageName);\n try {\n const tracks = trackFilter\n ? [await client.tracks.get(packageName, edit.id, trackFilter)]\n : await client.tracks.list(packageName, edit.id);\n\n await client.edits.delete(packageName, edit.id);\n\n const results: ReleaseStatusResult[] = [];\n for (const track of tracks) {\n for (const release of track.releases || []) {\n results.push({\n track: track.track,\n status: release.status,\n versionCodes: release.versionCodes || [],\n userFraction: release.userFraction,\n releaseNotes: release.releaseNotes,\n });\n }\n }\n return results;\n } catch (error) {\n await client.edits.delete(packageName, edit.id).catch(() => {});\n throw error;\n }\n}\n\nexport async function promoteRelease(\n client: PlayApiClient,\n packageName: string,\n fromTrack: string,\n toTrack: string,\n options?: {\n status?: string;\n userFraction?: number;\n releaseNotes?: { language: string; text: string }[];\n commitOptions?: EditCommitOptions;\n },\n): Promise<ReleaseStatusResult> {\n // Validate inputs before opening an edit\n if (options?.userFraction && (options.userFraction <= 0 || options.userFraction > 1)) {\n throw new GpcError(\n \"Rollout percentage must be between 0 and 1 (e.g., 0.1 for 10%)\",\n \"RELEASE_INVALID_FRACTION\",\n 2,\n \"Use a decimal value like 0.1 for 10%, 0.5 for 50%, or 1.0 for 100%.\",\n );\n }\n\n return withRetryOnConflict(client, packageName, async (edit) => {\n // Get current release from source track\n const sourceTrack = await client.tracks.get(packageName, edit.id, fromTrack);\n const currentRelease = sourceTrack.releases?.find(\n (r) => r.status === \"completed\" || r.status === \"inProgress\",\n );\n\n if (!currentRelease) {\n throw new GpcError(\n `No active release found on track \"${fromTrack}\"`,\n \"RELEASE_NOT_FOUND\",\n 1,\n `Ensure there is a completed or in-progress release on the \"${fromTrack}\" track before promoting.`,\n );\n }\n\n const release: Release = {\n versionCodes: currentRelease.versionCodes,\n status: (options?.status ||\n (options?.userFraction ? \"inProgress\" : \"completed\")) as Release[\"status\"],\n ...(options?.userFraction && { userFraction: options.userFraction }),\n releaseNotes: options?.releaseNotes || currentRelease.releaseNotes || [],\n ...(currentRelease.inAppUpdatePriority !== undefined && {\n inAppUpdatePriority: currentRelease.inAppUpdatePriority,\n }),\n ...(currentRelease.name && { name: currentRelease.name }),\n };\n\n await client.tracks.update(packageName, edit.id, toTrack, release);\n const commitResult = await validateAndCommit(\n client,\n packageName,\n edit.id,\n options?.commitOptions,\n );\n\n return {\n track: toTrack,\n status: release.status,\n versionCodes: release.versionCodes,\n userFraction: release.userFraction,\n ...(commitResult.rescued && {\n reviewPending: true,\n nextStep: REVIEW_PENDING_NEXT_STEP,\n }),\n };\n });\n}\n\nexport async function assignRelease(\n client: PlayApiClient,\n packageName: string,\n versionCode: number,\n options: {\n track: string;\n status?: string;\n userFraction?: number;\n releaseNotes?: { language: string; text: string }[];\n releaseName?: string;\n inAppUpdatePriority?: number;\n retainVersionCodes?: string[];\n dryRun?: boolean;\n commitOptions?: EditCommitOptions;\n },\n): Promise<ReleaseStatusResult> {\n if (options.userFraction && (options.userFraction <= 0 || options.userFraction > 1)) {\n throw new GpcError(\n \"Rollout percentage must be between 0 and 1 (e.g., 0.1 for 10%)\",\n \"RELEASE_INVALID_FRACTION\",\n 2,\n \"Use a decimal value like 0.1 for 10%, 0.5 for 50%, or 1.0 for 100%.\",\n );\n }\n\n const bundle = await findBundle(client, packageName, versionCode);\n if (!bundle) {\n throw new GpcError(\n `Version code ${versionCode} not found — the bundle must be uploaded before it can be assigned`,\n \"BUNDLE_NOT_FOUND\",\n 4,\n \"Upload the bundle first with 'gpc releases upload', or check the version code with 'gpc bundles list'.\",\n );\n }\n\n const code = String(versionCode);\n const release: Release = {\n versionCodes: [...(options.retainVersionCodes || []).filter((vc) => vc !== code), code],\n status: (options.status ||\n (options.userFraction ? \"inProgress\" : \"completed\")) as Release[\"status\"],\n ...(options.userFraction && { userFraction: options.userFraction }),\n ...(options.releaseNotes && { releaseNotes: options.releaseNotes }),\n ...(options.releaseName && { name: options.releaseName }),\n ...(options.inAppUpdatePriority !== undefined && {\n inAppUpdatePriority: options.inAppUpdatePriority,\n }),\n };\n\n if (options.dryRun) {\n return {\n track: options.track,\n status: release.status,\n versionCodes: release.versionCodes,\n userFraction: release.userFraction,\n };\n }\n\n const edit = await client.edits.insert(packageName);\n try {\n await client.tracks.update(packageName, edit.id, options.track, release);\n const commitResult = await validateAndCommit(\n client,\n packageName,\n edit.id,\n options.commitOptions,\n );\n\n return {\n track: options.track,\n status: release.status,\n versionCodes: release.versionCodes,\n userFraction: release.userFraction,\n ...(commitResult.rescued && {\n reviewPending: true,\n nextStep: REVIEW_PENDING_NEXT_STEP,\n }),\n };\n } catch (error) {\n await client.edits.delete(packageName, edit.id).catch(() => {});\n throw error;\n }\n}\n\nexport async function updateRollout(\n client: PlayApiClient,\n packageName: string,\n track: string,\n action: \"increase\" | \"halt\" | \"resume\" | \"complete\",\n userFraction?: number,\n commitOptions?: EditCommitOptions,\n): Promise<ReleaseStatusResult> {\n const edit = await client.edits.insert(packageName);\n try {\n const trackData = await client.tracks.get(packageName, edit.id, track);\n const currentRelease = trackData.releases?.find(\n (r) => r.status === \"inProgress\" || r.status === \"halted\",\n );\n\n if (!currentRelease) {\n throw new GpcError(\n `No active rollout found on track \"${track}\"`,\n \"ROLLOUT_NOT_FOUND\",\n 1,\n `There is no in-progress or halted rollout on the \"${track}\" track. Start a staged rollout first with: gpc releases upload --track ${track} --status inProgress --fraction 0.1`,\n );\n }\n\n let newStatus: string;\n let newFraction: number | undefined;\n\n switch (action) {\n case \"increase\":\n if (!userFraction)\n throw new GpcError(\n \"--to <percentage> is required for rollout increase\",\n \"ROLLOUT_MISSING_FRACTION\",\n 2,\n \"Specify the target rollout percentage with --to, e.g.: gpc rollout increase --to 0.5\",\n );\n if (userFraction <= 0 || userFraction > 1) {\n throw new GpcError(\n \"Rollout percentage must be between 0 and 1 (e.g., 0.1 for 10%)\",\n \"RELEASE_INVALID_FRACTION\",\n 2,\n \"Use a decimal value like 0.1 for 10%, 0.5 for 50%, or 1.0 for 100%.\",\n );\n }\n newStatus = \"inProgress\";\n newFraction = userFraction;\n break;\n case \"halt\":\n newStatus = \"halted\";\n newFraction = currentRelease.userFraction;\n break;\n case \"resume\":\n newStatus = \"inProgress\";\n newFraction = currentRelease.userFraction;\n break;\n case \"complete\":\n newStatus = \"completed\";\n newFraction = undefined;\n break;\n }\n\n const release: Release = {\n versionCodes: currentRelease.versionCodes,\n status: newStatus as Release[\"status\"],\n ...(newFraction !== undefined && { userFraction: newFraction }),\n releaseNotes: currentRelease.releaseNotes || [],\n };\n\n await client.tracks.update(packageName, edit.id, track, release);\n const commitResult = await validateAndCommit(client, packageName, edit.id, commitOptions);\n\n return {\n track,\n status: newStatus,\n versionCodes: release.versionCodes,\n userFraction: newFraction,\n ...(commitResult.rescued && {\n reviewPending: true,\n nextStep: REVIEW_PENDING_NEXT_STEP,\n }),\n };\n } catch (error) {\n await client.edits.delete(packageName, edit.id).catch(() => {});\n throw error;\n }\n}\n\nexport async function listTracks(client: PlayApiClient, packageName: string): Promise<Track[]> {\n const edit = await client.edits.insert(packageName);\n try {\n const tracks = await client.tracks.list(packageName, edit.id);\n await client.edits.delete(packageName, edit.id);\n return tracks;\n } catch (error) {\n await client.edits.delete(packageName, edit.id).catch(() => {});\n throw error;\n }\n}\n\nexport async function createTrack(\n client: PlayApiClient,\n packageName: string,\n trackName: string,\n commitOptions?: EditCommitOptions,\n): Promise<Track> {\n if (!trackName || trackName.trim().length === 0) {\n throw new GpcError(\n \"Track name must not be empty\",\n \"TRACK_INVALID_NAME\",\n 2,\n \"Provide a valid custom track name, e.g.: gpc tracks create my-qa-track\",\n );\n }\n\n const edit = await client.edits.insert(packageName);\n try {\n const track = await client.tracks.create(packageName, edit.id, trackName);\n await validateAndCommit(client, packageName, edit.id, commitOptions);\n return track;\n } catch (error) {\n await client.edits.delete(packageName, edit.id).catch(() => {});\n throw error;\n }\n}\n\nexport async function updateTrackConfig(\n client: PlayApiClient,\n packageName: string,\n trackName: string,\n config: Record<string, unknown>,\n commitOptions?: EditCommitOptions,\n): Promise<Track> {\n if (!trackName || trackName.trim().length === 0) {\n throw new GpcError(\n \"Track name must not be empty\",\n \"TRACK_INVALID_NAME\",\n 2,\n \"Provide a valid track name.\",\n );\n }\n\n const edit = await client.edits.insert(packageName);\n try {\n // Support both flat config and nested track-shaped JSON (releases[0].*)\n const nested =\n Array.isArray(config[\"releases\"]) && config[\"releases\"].length > 0\n ? (config[\"releases\"] as Record<string, unknown>[])[0]\n : undefined;\n const pick = (key: string) => nested?.[key] ?? config[key];\n\n const rawCodes = pick(\"versionCodes\") as (string | number)[] | undefined;\n if (!rawCodes || rawCodes.length === 0) {\n throw new GpcError(\n \"versionCodes must not be empty — refusing to update track with no versions\",\n \"TRACK_MISSING_VERSION_CODES\",\n 2,\n \"Provide at least one versionCode in the config file.\",\n );\n }\n const versionCodes = rawCodes.map(String);\n\n const release: Release = {\n versionCodes,\n status: ((pick(\"status\") as string) || \"completed\") as Release[\"status\"],\n };\n if (pick(\"userFraction\") !== undefined) {\n release.userFraction = pick(\"userFraction\") as number;\n }\n if (pick(\"releaseNotes\")) {\n release.releaseNotes = pick(\"releaseNotes\") as { language: string; text: string }[];\n }\n if (pick(\"name\")) {\n release.name = pick(\"name\") as string;\n }\n if (pick(\"inAppUpdatePriority\") !== undefined) {\n release.inAppUpdatePriority = pick(\"inAppUpdatePriority\") as number;\n }\n\n const track = await client.tracks.update(packageName, edit.id, trackName, release);\n await validateAndCommit(client, packageName, edit.id, commitOptions);\n return track;\n } catch (error) {\n await client.edits.delete(packageName, edit.id).catch(() => {});\n throw error;\n }\n}\n\n/**\n * Fetch release notes from the latest active release on a given track.\n * Opens and discards an edit — read-only, no mutations.\n */\nexport async function fetchReleaseNotes(\n client: PlayApiClient,\n packageName: string,\n track: string,\n): Promise<{ language: string; text: string }[]> {\n const edit = await client.edits.insert(packageName);\n try {\n const trackData = await client.tracks.get(packageName, edit.id, track);\n const release =\n trackData.releases?.find((r) => r.status === \"completed\" || r.status === \"inProgress\") ??\n trackData.releases?.[0];\n\n if (!release) {\n throw new GpcError(\n `No release found on track \"${track}\" to copy notes from`,\n \"RELEASE_NOT_FOUND\",\n 1,\n `Ensure there is a release on the \"${track}\" track.`,\n );\n }\n\n return release.releaseNotes ?? [];\n } finally {\n await client.edits.delete(packageName, edit.id).catch(() => {});\n }\n}\n\nexport interface ApplyReleaseNotesResult {\n track: string;\n versionCodes: string[];\n localeCount: number;\n releaseNotes: { language: string; text: string }[];\n}\n\nexport async function applyReleaseNotes(\n client: PlayApiClient,\n packageName: string,\n track: string,\n releaseNotes: { language: string; text: string }[],\n commitOptions?: EditCommitOptions,\n): Promise<ApplyReleaseNotesResult> {\n return withRetryOnConflict(client, packageName, async (edit) => {\n const trackData = await client.tracks.get(packageName, edit.id, track);\n const draft = trackData.releases?.find((r) => r.status === \"draft\");\n\n if (!draft) {\n throw new GpcError(\n `No draft release found on track \"${track}\"`,\n \"RELEASE_NO_DRAFT\",\n 1,\n `Upload an AAB/APK first to create a draft, or check the --track value. Current track: \"${track}\".`,\n );\n }\n\n const patched: Release = {\n ...draft,\n releaseNotes,\n };\n\n await client.tracks.update(packageName, edit.id, track, patched);\n await validateAndCommit(client, packageName, edit.id, commitOptions);\n\n return {\n track,\n versionCodes: draft.versionCodes || [],\n localeCount: releaseNotes.length,\n releaseNotes,\n };\n });\n}\n\nexport interface ReleaseDiff {\n field: string;\n track1Value: string;\n track2Value: string;\n}\n\nexport async function diffReleases(\n client: PlayApiClient,\n packageName: string,\n fromTrack: string,\n toTrack: string,\n): Promise<{ fromTrack: string; toTrack: string; diffs: ReleaseDiff[] }> {\n const edit = await client.edits.insert(packageName);\n try {\n const [fromData, toData] = await Promise.all([\n client.tracks.get(packageName, edit.id, fromTrack),\n client.tracks.get(packageName, edit.id, toTrack),\n ]);\n await client.edits.delete(packageName, edit.id);\n\n const fromRelease = fromData.releases?.[0];\n const toRelease = toData.releases?.[0];\n const diffs: ReleaseDiff[] = [];\n\n const fields = [\"versionCodes\", \"status\", \"userFraction\", \"releaseNotes\", \"name\"] as const;\n for (const field of fields) {\n const v1 = fromRelease ? JSON.stringify(fromRelease[field] ?? null) : \"null\";\n const v2 = toRelease ? JSON.stringify(toRelease[field] ?? null) : \"null\";\n if (v1 !== v2) {\n diffs.push({ field, track1Value: v1, track2Value: v2 });\n }\n }\n\n return { fromTrack, toTrack, diffs };\n } catch (error) {\n await client.edits.delete(packageName, edit.id).catch(() => {});\n throw error;\n }\n}\n\nexport async function uploadExternallyHosted(\n client: PlayApiClient,\n packageName: string,\n data: ExternallyHostedApk,\n commitOptions?: EditCommitOptions,\n): Promise<ExternallyHostedApkResponse> {\n if (!data.externallyHostedUrl) {\n throw new GpcError(\n \"externallyHostedUrl is required\",\n \"EXTERNAL_APK_MISSING_URL\",\n 2,\n \"Provide a valid URL for the externally hosted APK.\",\n );\n }\n\n if (!data.packageName) {\n throw new GpcError(\n \"packageName is required in externally hosted APK data\",\n \"EXTERNAL_APK_MISSING_PACKAGE\",\n 2,\n \"Include the packageName field in the APK configuration.\",\n );\n }\n\n const edit = await client.edits.insert(packageName);\n try {\n const result = await client.apks.addExternallyHosted(packageName, edit.id, data);\n await validateAndCommit(client, packageName, edit.id, commitOptions);\n return result;\n } catch (error) {\n await client.edits.delete(packageName, edit.id).catch(() => {});\n throw error;\n }\n}\n","import { readdir, readFile, lstat } from \"node:fs/promises\";\nimport { extname, basename, join } from \"node:path\";\nimport { GpcError } from \"../errors.js\";\n\nexport interface ReleaseNote {\n language: string;\n text: string;\n}\n\nexport interface ReleaseNotesValidation {\n valid: boolean;\n errors: string[];\n warnings: string[];\n}\n\nconst MAX_NOTES_LENGTH = 500;\n\nexport async function readReleaseNotesFromDir(dir: string): Promise<ReleaseNote[]> {\n let entries: string[];\n try {\n entries = await readdir(dir);\n } catch {\n throw new GpcError(\n `Release notes directory not found: ${dir}`,\n \"RELEASE_NOTES_DIR_NOT_FOUND\",\n 1,\n `Create the directory and add .txt files named by language code (e.g., en-US.txt). Path: ${dir}`,\n );\n }\n\n const notes: ReleaseNote[] = [];\n\n for (const entry of entries) {\n if (extname(entry) !== \".txt\") continue;\n\n const language = basename(entry, \".txt\");\n const filePath = join(dir, entry);\n\n const stats = await lstat(filePath);\n if (!stats.isFile() || stats.isSymbolicLink()) continue;\n\n const text = (await readFile(filePath, \"utf-8\")).trim();\n if (text.length === 0) continue;\n\n notes.push({ language, text });\n }\n\n return notes;\n}\n\nconst LOCALE_PATTERN = /^[a-z]{2}(-[A-Z]{2,3})?$/;\n\nexport async function isVersionedNotesDir(dir: string): Promise<boolean> {\n let entries: string[];\n try {\n entries = await readdir(dir);\n } catch {\n return false;\n }\n for (const entry of entries) {\n if (!LOCALE_PATTERN.test(entry)) continue;\n const entryPath = join(dir, entry);\n const stats = await lstat(entryPath);\n if (stats.isDirectory() && !stats.isSymbolicLink()) return true;\n }\n return false;\n}\n\nexport async function readReleaseNotesForVersion(\n dir: string,\n versionCode: number,\n): Promise<ReleaseNote[]> {\n if (!Number.isInteger(versionCode) || versionCode <= 0) {\n throw new GpcError(\n `Invalid version code: ${versionCode}`,\n \"INVALID_VERSION_CODE\",\n 2,\n \"Version code must be a positive integer.\",\n );\n }\n\n let entries: string[];\n try {\n entries = await readdir(dir);\n } catch {\n throw new GpcError(\n `Release notes directory not found: ${dir}`,\n \"RELEASE_NOTES_DIR_NOT_FOUND\",\n 1,\n `Create the directory with language subdirectories containing {versionCode}.txt or default.txt. Path: ${dir}`,\n );\n }\n\n const notes: ReleaseNote[] = [];\n\n for (const entry of entries) {\n const entryPath = join(dir, entry);\n const stats = await lstat(entryPath);\n if (!stats.isDirectory() || stats.isSymbolicLink()) continue;\n\n const language = entry;\n const versionFile = join(entryPath, `${versionCode}.txt`);\n const defaultFile = join(entryPath, \"default.txt\");\n\n let text: string | undefined;\n try {\n text = (await readFile(versionFile, \"utf-8\")).trim();\n } catch (err: unknown) {\n if (err && typeof err === \"object\" && \"code\" in err && err.code !== \"ENOENT\") throw err;\n try {\n text = (await readFile(defaultFile, \"utf-8\")).trim();\n } catch (err2: unknown) {\n if (err2 && typeof err2 === \"object\" && \"code\" in err2 && err2.code !== \"ENOENT\")\n throw err2;\n continue;\n }\n }\n\n if (text && text.length > 0) {\n notes.push({ language, text });\n }\n }\n\n return notes;\n}\n\nexport function validateReleaseNotes(notes: ReleaseNote[]): ReleaseNotesValidation {\n const errors: string[] = [];\n const warnings: string[] = [];\n\n const seen = new Set<string>();\n for (const note of notes) {\n if (seen.has(note.language)) {\n errors.push(`Duplicate language code: ${note.language}`);\n }\n seen.add(note.language);\n\n if (note.text.length > MAX_NOTES_LENGTH) {\n warnings.push(\n `Release notes for \"${note.language}\" are ${note.text.length} chars (max ${MAX_NOTES_LENGTH}) — Google Play will reject notes exceeding this limit`,\n );\n }\n }\n\n return { valid: errors.length === 0, errors, warnings };\n}\n","export class GpcError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n public readonly exitCode: number,\n public readonly suggestion?: string,\n ) {\n super(message);\n this.name = \"GpcError\";\n }\n\n toJSON() {\n return {\n success: false,\n error: {\n code: this.code,\n message: this.message,\n suggestion: this.suggestion,\n },\n };\n }\n}\n\nexport class ConfigError extends GpcError {\n constructor(message: string, code: string, suggestion?: string) {\n super(message, code, 1, suggestion);\n this.name = \"ConfigError\";\n }\n}\n\nexport class ApiError extends GpcError {\n constructor(\n message: string,\n code: string,\n public readonly statusCode?: number,\n suggestion?: string,\n ) {\n super(message, code, 4, suggestion);\n this.name = \"ApiError\";\n }\n}\n\nexport class NetworkError extends GpcError {\n constructor(message: string, suggestion?: string) {\n super(message, \"NETWORK_ERROR\", 5, suggestion);\n this.name = \"NetworkError\";\n }\n}\n","import { open, stat } from \"node:fs/promises\";\nimport { extname } from \"node:path\";\n\nimport { formatSize } from \"./format.js\";\n\nexport interface FileValidationResult {\n valid: boolean;\n fileType: \"aab\" | \"apk\" | \"unknown\";\n sizeBytes: number;\n errors: string[];\n warnings: string[];\n}\n\n// ZIP magic bytes: PK\\x03\\x04\nconst ZIP_MAGIC = Buffer.from([0x50, 0x4b, 0x03, 0x04]);\n\nconst MAX_APK_SIZE = 1024 * 1024 * 1024; // 1 GB (Google Play API limit)\nconst MAX_AAB_SIZE = 2 * 1024 * 1024 * 1024; // 2 GB (Google Play API limit)\nconst LARGE_FILE_THRESHOLD = 100 * 1024 * 1024; // 100 MB — warn about upload time\n\nexport async function validateUploadFile(filePath: string): Promise<FileValidationResult> {\n const errors: string[] = [];\n const warnings: string[] = [];\n\n // Check extension\n const ext = extname(filePath).toLowerCase();\n let fileType: FileValidationResult[\"fileType\"] = \"unknown\";\n\n if (ext === \".aab\") {\n fileType = \"aab\";\n } else if (ext === \".apk\") {\n fileType = \"apk\";\n } else {\n errors.push(`Unsupported file extension \"${ext}\". Expected .aab or .apk`);\n }\n\n // Check file exists and get size\n let sizeBytes: number;\n try {\n const stats = await stat(filePath);\n sizeBytes = stats.size;\n\n if (sizeBytes === 0) {\n errors.push(\"File is empty (0 bytes)\");\n }\n } catch {\n errors.push(`File not found: ${filePath}`);\n return { valid: false, fileType, sizeBytes: 0, errors, warnings };\n }\n\n // Check size limits\n if (fileType === \"apk\" && sizeBytes > MAX_APK_SIZE) {\n errors.push(\n `APK exceeds 1 GB limit (${formatSize(sizeBytes)}). Consider using AAB format instead.`,\n );\n }\n if (fileType === \"aab\" && sizeBytes > MAX_AAB_SIZE) {\n errors.push(`AAB exceeds 2 GB limit (${formatSize(sizeBytes)}).`);\n }\n\n if (sizeBytes > LARGE_FILE_THRESHOLD && errors.length === 0) {\n warnings.push(\n `Large file (${formatSize(sizeBytes)}). Upload may take a while on slow connections.`,\n );\n }\n\n // Check magic bytes — only read first 4 bytes, not the entire file\n if (sizeBytes > 0) {\n let fh;\n try {\n fh = await open(filePath, \"r\");\n const buf = Buffer.alloc(4);\n await fh.read(buf, 0, 4, 0);\n\n if (!buf.equals(ZIP_MAGIC)) {\n errors.push(\n \"File does not have valid ZIP magic bytes (PK\\\\x03\\\\x04). \" +\n \"Both AAB and APK files must be valid ZIP archives.\",\n );\n }\n } catch {\n errors.push(\"Unable to read file header for validation\");\n } finally {\n await fh?.close();\n }\n }\n\n return {\n valid: errors.length === 0,\n fileType,\n sizeBytes,\n errors,\n warnings,\n };\n}\n\n","export function formatSize(bytes: number): string {\n if (bytes >= 1024 * 1024 * 1024) {\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;\n }\n if (bytes >= 1024 * 1024) {\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n }\n if (bytes >= 1024) {\n return `${(bytes / 1024).toFixed(1)} KB`;\n }\n return `${bytes} B`;\n}\n","import type { PlayApiClient, EditCommitOptions } from \"@gpc-cli/api\";\nimport { PlayApiError } from \"@gpc-cli/api\";\n\nexport interface CommitResult {\n rescued: boolean;\n}\n\nconst REVIEW_PENDING_WARNING =\n \"Changes committed but NOT sent for review (app has a rejected update).\\n\" +\n \"Next step: Open Google Play Console > Publishing overview > Send for review\";\n\nexport async function commitWithRescue(\n client: PlayApiClient,\n packageName: string,\n editId: string,\n commitOptions?: EditCommitOptions,\n): Promise<CommitResult> {\n try {\n await client.edits.commit(packageName, editId, commitOptions);\n return { rescued: false };\n } catch (error) {\n if (\n error instanceof PlayApiError &&\n error.code === \"API_CHANGES_NOT_SENT_FOR_REVIEW\" &&\n !commitOptions?.changesNotSentForReview\n ) {\n console.error(`\\n WARNING: ${REVIEW_PENDING_WARNING}\\n`);\n await client.edits.commit(packageName, editId, {\n ...commitOptions,\n changesNotSentForReview: true,\n });\n return { rescued: true };\n }\n throw error;\n }\n}\n\nexport async function validateAndCommit(\n client: PlayApiClient,\n packageName: string,\n editId: string,\n commitOptions?: EditCommitOptions,\n): Promise<CommitResult> {\n let rescuedFromValidate = false;\n if (!commitOptions?.changesNotSentForReview) {\n try {\n await client.edits.validate(packageName, editId);\n } catch (error) {\n if (error instanceof PlayApiError && error.code === \"API_CHANGES_NOT_SENT_FOR_REVIEW\") {\n console.error(`\\n WARNING: ${REVIEW_PENDING_WARNING}\\n`);\n rescuedFromValidate = true;\n } else {\n throw error;\n }\n }\n }\n\n if (rescuedFromValidate) {\n await client.edits.commit(packageName, editId, {\n ...commitOptions,\n changesNotSentForReview: true,\n });\n return { rescued: true };\n }\n return commitWithRescue(client, packageName, editId, commitOptions);\n}\n","import type { PlayApiClient, Bundle } from \"@gpc-cli/api\";\nimport { PlayApiError } from \"@gpc-cli/api\";\nimport { GpcError } from \"../errors.js\";\n\nexport interface BundlesWaitOptions {\n timeout?: number;\n interval?: number;\n}\n\nexport async function listBundles(client: PlayApiClient, packageName: string): Promise<Bundle[]> {\n const edit = await client.edits.insert(packageName);\n try {\n const bundles = await client.bundles.list(packageName, edit.id);\n await client.edits.delete(packageName, edit.id).catch(() => {});\n return bundles;\n } catch (error) {\n await client.edits.delete(packageName, edit.id).catch(() => {});\n throw error;\n }\n}\n\nexport async function findBundle(\n client: PlayApiClient,\n packageName: string,\n versionCode: number,\n): Promise<Bundle | null> {\n const bundles = await listBundles(client, packageName);\n return bundles.find((b) => b.versionCode === versionCode) ?? null;\n}\n\nfunction isRetryableError(error: unknown): boolean {\n if (!(error instanceof PlayApiError)) return false;\n const status = error.statusCode;\n return status === 429 || status === 500 || status === 503 || status === 409;\n}\n\nexport async function waitForBundle(\n client: PlayApiClient,\n packageName: string,\n versionCode: number,\n options?: BundlesWaitOptions,\n): Promise<Bundle> {\n const timeout = options?.timeout ?? 600_000;\n const interval = options?.interval ?? 15_000;\n const deadline = Date.now() + timeout;\n\n while (Date.now() < deadline) {\n try {\n const bundles = await listBundles(client, packageName);\n const match = bundles.find((b) => b.versionCode === versionCode);\n if (match) return match;\n } catch (error) {\n if (!isRetryableError(error)) throw error;\n }\n\n const remaining = deadline - Date.now();\n if (remaining <= 0) break;\n await new Promise((r) => setTimeout(r, Math.min(interval, remaining)));\n }\n\n throw new GpcError(\n `Bundle version code ${versionCode} not found after ${Math.round(timeout / 1000)}s`,\n \"BUNDLE_WAIT_TIMEOUT\",\n 4,\n \"The bundle may still be processing. Try again with a longer --timeout, or check the Play Console.\",\n );\n}\n"],"mappings":"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","names":["stat","extname","PlayApiError","extname","extname","PlayApiError","PlayApiError","PlayApiError","edit","stat","extname"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -117,6 +117,9 @@ interface UploadResult {
|
|
|
117
117
|
track: string;
|
|
118
118
|
status: string;
|
|
119
119
|
validateOnly?: true;
|
|
120
|
+
reviewPending?: boolean;
|
|
121
|
+
reviewSkipped?: boolean;
|
|
122
|
+
nextStep?: string;
|
|
120
123
|
}
|
|
121
124
|
interface ReleaseStatusResult {
|
|
122
125
|
track: string;
|
|
@@ -127,6 +130,9 @@ interface ReleaseStatusResult {
|
|
|
127
130
|
language: string;
|
|
128
131
|
text: string;
|
|
129
132
|
}[];
|
|
133
|
+
reviewPending?: boolean;
|
|
134
|
+
reviewSkipped?: boolean;
|
|
135
|
+
nextStep?: string;
|
|
130
136
|
}
|
|
131
137
|
interface DryRunUploadResult {
|
|
132
138
|
dryRun: true;
|
|
@@ -146,6 +152,8 @@ interface DryRunUploadResult {
|
|
|
146
152
|
status: string;
|
|
147
153
|
userFraction?: number;
|
|
148
154
|
};
|
|
155
|
+
executed: string[];
|
|
156
|
+
skipped: string[];
|
|
149
157
|
}
|
|
150
158
|
declare function uploadRelease(client: PlayApiClient, packageName: string, filePath: string, options: {
|
|
151
159
|
track: string;
|
|
@@ -975,7 +983,7 @@ interface PreflightContext {
|
|
|
975
983
|
aabPath?: string;
|
|
976
984
|
manifest?: ParsedManifest;
|
|
977
985
|
zipEntries?: ZipEntryInfo[];
|
|
978
|
-
/** First
|
|
986
|
+
/** First 4096 bytes of each .so file, keyed by ZIP entry path. */
|
|
979
987
|
nativeLibHeaders?: EntryHeaderMap;
|
|
980
988
|
metadataDir?: string;
|
|
981
989
|
sourceDir?: string;
|
|
@@ -983,6 +991,7 @@ interface PreflightContext {
|
|
|
983
991
|
}
|
|
984
992
|
interface PreflightResult {
|
|
985
993
|
scanners: string[];
|
|
994
|
+
skippedScanners?: string[];
|
|
986
995
|
findings: PreflightFinding[];
|
|
987
996
|
summary: Record<FindingSeverity, number>;
|
|
988
997
|
passed: boolean;
|
|
@@ -1264,6 +1273,7 @@ interface AppStatus {
|
|
|
1264
1273
|
slowRender: StatusVitalMetric;
|
|
1265
1274
|
};
|
|
1266
1275
|
reviews: StatusReviews;
|
|
1276
|
+
errors?: string[];
|
|
1267
1277
|
}
|
|
1268
1278
|
interface StatusDiff {
|
|
1269
1279
|
versionCode: {
|
|
@@ -1569,8 +1579,11 @@ declare function getKeystoreFingerprint(keystorePath: string, storePassword: str
|
|
|
1569
1579
|
declare function getApiSigningFingerprint(accessToken: string, packageName: string, apiHost?: string): Promise<ApiSigningFingerprint | null>;
|
|
1570
1580
|
declare function compareFingerprints(a: string, b: string): boolean;
|
|
1571
1581
|
|
|
1572
|
-
|
|
1573
|
-
|
|
1582
|
+
interface CommitResult {
|
|
1583
|
+
rescued: boolean;
|
|
1584
|
+
}
|
|
1585
|
+
declare function commitWithRescue(client: PlayApiClient, packageName: string, editId: string, commitOptions?: EditCommitOptions): Promise<CommitResult>;
|
|
1586
|
+
declare function validateAndCommit(client: PlayApiClient, packageName: string, editId: string, commitOptions?: EditCommitOptions): Promise<CommitResult>;
|
|
1574
1587
|
|
|
1575
1588
|
declare function sha256File(filePath: string): Promise<string>;
|
|
1576
1589
|
|
|
@@ -1638,4 +1651,4 @@ interface ChecklistInput {
|
|
|
1638
1651
|
declare function buildChecklist(input: ChecklistInput): ChecklistResult;
|
|
1639
1652
|
declare function renderChecklistMarkdown(result: ChecklistResult, accountEmail: string): string;
|
|
1640
1653
|
|
|
1641
|
-
export { ApiError, type ApiSigningFingerprint, type AppInfo, type AppStatus, type ApplyReleaseNotesResult, type AuditEntry, type BatchSyncResult, type BundleAnalysis, type BundleComparison, type BundleEntry, type BundleSizeCheckResult, type BundleSizeConfig, type BundlesWaitOptions, type ChangelogEntry, type ChecklistInput, type ChecklistItem, type ChecklistResult, type CommandContext, type CommitCluster, ConfigError, type CreateEnterpriseAppParams, DEFAULT_LIMITS, DEFAULT_MODELS, DEFAULT_PREFLIGHT_CONFIG, DEFAULT_WATCH_THRESHOLDS, type DecodedNotification, type DiffToken, type DiscoverPluginsOptions, type DryRunPublishResult, type DryRunResult, type DryRunUploadResult, type ErrorReason, type ExportImagesOptions, type ExportImagesSummary, type FastlaneDetection, type FastlaneLane, type FetchChangelogOptions, type FieldLintResult, type FileValidationResult, type FindingSeverity, GOOGLE_PLAY_LANGUAGES, type GenerateOptions, type GeneratedChangelog, type GetAppStatusOptions, type GitNotesOptions, type GitReleaseNotes, type GitRunner, GpcError, type ImageSyncDetail, type ImageSyncOptions, type ImageSyncResult, type ImageValidationResult, type InitOptions, type InitResult, type InternalSharingUploadResult, type KeystoreFingerprint, type ListIapOptions, type ListSubscriptionsOptions, type ListUsersOptions, type ListVoidedOptions, type ListingDiff, type ListingFieldLimits, type ListingLintResult, type ListingsResult, type LoadedPlugin, type LocaleBundle, type LocaleEntry, type MigrationResult, NetworkError, type OneTimeProductDiff, type OutputMode, PERMISSION_PROPAGATION_WARNING, PLACEHOLDER_TEXT, PLAY_STORE_LIMIT, PROVIDER_WHITELIST, type ParsedCommit, type ParsedManifest, type ParsedMonth, type PlayStoreFormat, type PlayStoreRenderOptions, PluginManager, type PreflightConfig, type PreflightFinding, type PreflightOptions, type PreflightResult, type PreflightScanner, type Provider, type PublishOptions, type PublishResult, type PushResult, type QuotaUsage, RENDERERS, type RawCommit, type ReleaseDiff, type ReleaseNotesValidation, type ReleaseStatusResult, type Renderer, type ResolveAiConfigOptions, type ResolveLocalesOptions, type ReviewAnalysis, type ReviewExportOptions, type ReviewsFilterOptions, type RevocationRefundType, type RtdnStatus, SECTION_ORDER, SENSITIVE_ARG_KEYS, SENSITIVE_KEYS, SEVERITY_ORDER, type ScaffoldOptions, type ScaffoldResult, type SigningConsistencyResult, type SigningKeyComparison, type Spinner, type StatusDiff, type StatusRelease, type StatusReviews, type StatusVitalMetric, type SubscriptionAnalytics, type SubscriptionDiff, type SyncResult, type ThresholdResult, type TrainConfig, type TrainState, type TranslateBundleOptions, type TranslatedBundle, type TranslationFailure, type TranslationPath, type TranslationResult, type Translator, type TranslatorConfig, type UploadResult, VALID_WATCH_METRICS, type ValidateCheck, type ValidateOptions, type ValidateResult, type VersionVitalsComparison, type VersionVitalsRow, type VitalsOverview, type VitalsQueryOptions, type VitalsTrendComparison, type WatchAction, type WatchCallbacks, type WatchConfig, type WatchEvent, type WatchMetric, type WatchOptions, type WatchRollout, type WatchSummary, type WatchVitalReading, type WatchVitalsOptions, type WebhookPayload, abortTrain, acknowledgeProductPurchase, activateBasePlan, activateOffer, addRecoveryTargeting, addTesters, advanceTrain, analyzeBundle, analyzeRemoteListings, analyzeReviews, applyReleaseNotes, assignRelease, batchGetOrders, batchSyncInAppProducts, buildChecklist, buildLocaleBundle, bundleToReleaseNotes, cancelRecoveryAction, cancelSubscriptionPurchase, cancelSubscriptionV2, checkBundleSize, checkSigningConsistency, checkThreshold, classifyError, clearAuditLog, commitWithRescue, compareBundles, compareFingerprints, compareVersionVitals, compareVitalsTrend, computeStatusDiff, consumeProductPurchase, convertRegionPrices, createAuditEntry, createDeviceTier, createEnterpriseApp, createExternalTransaction, createGrant, createInAppProduct, createOffer, createOneTimeOffer, createOneTimeProduct, createRecoveryAction, createSpinner, createSubscription, createTrack, createTranslator, deactivateBasePlan, deactivateOffer, decodeNotification, defaultGitRunner, deferSubscriptionPurchase, deferSubscriptionV2, deleteBasePlan, deleteGrant, deleteImage, deleteInAppProduct, deleteListing, deleteOffer, deleteOneTimeOffer, deleteOneTimeProduct, deleteSubscription, deployRecoveryAction, detectFastlane, detectOutputFormat, diffListings, diffListingsCommand, diffListingsEnhanced, diffOneTimeProduct, diffReleases, diffSubscription, discoverPlugins, dispatchWebhook, downloadGeneratedApk, downloadReport, exportImages, exportReviews, fetchAggregateCost, fetchChangelog, fetchReleaseNotes, findBundle, formatChangelogEntry, formatCustomPayload, formatDiscordPayload, formatJunit, formatNotification, formatOutput, formatPathLabel, formatSlackPayload, formatStatusDiff, formatStatusSummary, formatStatusTable, formatWordDiff, generateChangelog, generateMigrationPlan, generateNotesFromGit, getAllScannerNames, getApiSigningFingerprint, getAppInfo, getAppStatus, getCountryAvailability, getDeviceTier, getExternalTransaction, getInAppProduct, getKeystoreFingerprint, getListings, getOffer, getOneTimeOffer, getOneTimeProduct, getOrderDetails, getProductPurchase, getProductPurchaseV2, getQuotaUsage, getReleasesStatus, getReview, getRtdnStatus, getSubscription, getSubscriptionAnalytics, getSubscriptionPurchase, getTrainStatus, getUser, getVitalsAnomalies, getVitalsAnr, getVitalsBattery, getVitalsCrashes, getVitalsErrorCount, getVitalsLmk, getVitalsMemory, getVitalsOverview, getVitalsRendering, getVitalsStartup, handleBreach, importDataSafety, importTestersFromCsv, initAudit, initProject, inviteUser, isFinancialReportType, isStatsReportType, isValidBcp47, isValidReportType, isValidStatsDimension, isVersionedNotesDir, lintListing, lintListings, lintLocalListings, listAchievements, listAuditEvents, listBundles, listDeviceTiers, listEvents, listGeneratedApks, listGrants, listImages, listInAppProducts, listLeaderboards, listOffers, listOneTimeOffers, listOneTimeProducts, listRecoveryActions, listReports, listReviews, listSubscriptions, listTesters, listTracks, listUsers, listVoidedPurchases, loadPreflightConfig, loadStatusCache, maybePaginate, migratePrices, normalizeFingerprint, parseAppfile, parseCommit, parseFastfile, parseGrantArg, parseKeytoolOutput, parseMonth, parseRemoteUrl, pauseTrain, promoteRelease, publish, publishEnterpriseApp, pullListings, pushListings, readListingsFromDir, readReleaseNotesForVersion, readReleaseNotesFromDir, redactAuditArgs, redactSensitive, refundExternalTransaction, refundOrder, relativeTime, removeTesters, removeUser, renderChecklistMarkdown, renderJson, renderMarkdown, renderPlayStore, renderPlayStoreJson, renderPlayStoreMd, renderPlayStorePrompt, renderPrompt, replyToReview, resolveAiConfig, resolveLocales, retryOnUploadNotComplete, revokeSubscriptionPurchase, runPreflight, runWatch, runWatchLoop, safePath, safePathWithin, saveStatusCache, scaffoldPlugin, searchAuditEvents, searchVitalsErrors, sendNotification, sendWebhook, sha256File, sortResults, startTrain, statusHasBreach, syncImages, syncInAppProducts, topFiles, trackBreachState, translateBundle, updateAppDetails, updateDataSafety, updateGrant, updateInAppProduct, updateListing, updateOffer, updateOneTimeOffer, updateOneTimeProduct, updateRollout, updateSubscription, updateTrackConfig, updateUser, uploadExternallyHosted, uploadImage, uploadInternalSharing, uploadRelease, validateAndCommit, validateBundleForApply, validateImage, validateLanguageCode, validatePackageName, validatePreSubmission, validateReleaseNotes, validateSku, validateTrackName, validateUploadFile, validateVersionCode, waitForBundle, waitForBundleProcessing, watchVitalsWithAutoHalt, wordDiff, writeAuditLog, writeListingsToDir, writeMigrationOutput };
|
|
1654
|
+
export { ApiError, type ApiSigningFingerprint, type AppInfo, type AppStatus, type ApplyReleaseNotesResult, type AuditEntry, type BatchSyncResult, type BundleAnalysis, type BundleComparison, type BundleEntry, type BundleSizeCheckResult, type BundleSizeConfig, type BundlesWaitOptions, type ChangelogEntry, type ChecklistInput, type ChecklistItem, type ChecklistResult, type CommandContext, type CommitCluster, type CommitResult, ConfigError, type CreateEnterpriseAppParams, DEFAULT_LIMITS, DEFAULT_MODELS, DEFAULT_PREFLIGHT_CONFIG, DEFAULT_WATCH_THRESHOLDS, type DecodedNotification, type DiffToken, type DiscoverPluginsOptions, type DryRunPublishResult, type DryRunResult, type DryRunUploadResult, type ErrorReason, type ExportImagesOptions, type ExportImagesSummary, type FastlaneDetection, type FastlaneLane, type FetchChangelogOptions, type FieldLintResult, type FileValidationResult, type FindingSeverity, GOOGLE_PLAY_LANGUAGES, type GenerateOptions, type GeneratedChangelog, type GetAppStatusOptions, type GitNotesOptions, type GitReleaseNotes, type GitRunner, GpcError, type ImageSyncDetail, type ImageSyncOptions, type ImageSyncResult, type ImageValidationResult, type InitOptions, type InitResult, type InternalSharingUploadResult, type KeystoreFingerprint, type ListIapOptions, type ListSubscriptionsOptions, type ListUsersOptions, type ListVoidedOptions, type ListingDiff, type ListingFieldLimits, type ListingLintResult, type ListingsResult, type LoadedPlugin, type LocaleBundle, type LocaleEntry, type MigrationResult, NetworkError, type OneTimeProductDiff, type OutputMode, PERMISSION_PROPAGATION_WARNING, PLACEHOLDER_TEXT, PLAY_STORE_LIMIT, PROVIDER_WHITELIST, type ParsedCommit, type ParsedManifest, type ParsedMonth, type PlayStoreFormat, type PlayStoreRenderOptions, PluginManager, type PreflightConfig, type PreflightFinding, type PreflightOptions, type PreflightResult, type PreflightScanner, type Provider, type PublishOptions, type PublishResult, type PushResult, type QuotaUsage, RENDERERS, type RawCommit, type ReleaseDiff, type ReleaseNotesValidation, type ReleaseStatusResult, type Renderer, type ResolveAiConfigOptions, type ResolveLocalesOptions, type ReviewAnalysis, type ReviewExportOptions, type ReviewsFilterOptions, type RevocationRefundType, type RtdnStatus, SECTION_ORDER, SENSITIVE_ARG_KEYS, SENSITIVE_KEYS, SEVERITY_ORDER, type ScaffoldOptions, type ScaffoldResult, type SigningConsistencyResult, type SigningKeyComparison, type Spinner, type StatusDiff, type StatusRelease, type StatusReviews, type StatusVitalMetric, type SubscriptionAnalytics, type SubscriptionDiff, type SyncResult, type ThresholdResult, type TrainConfig, type TrainState, type TranslateBundleOptions, type TranslatedBundle, type TranslationFailure, type TranslationPath, type TranslationResult, type Translator, type TranslatorConfig, type UploadResult, VALID_WATCH_METRICS, type ValidateCheck, type ValidateOptions, type ValidateResult, type VersionVitalsComparison, type VersionVitalsRow, type VitalsOverview, type VitalsQueryOptions, type VitalsTrendComparison, type WatchAction, type WatchCallbacks, type WatchConfig, type WatchEvent, type WatchMetric, type WatchOptions, type WatchRollout, type WatchSummary, type WatchVitalReading, type WatchVitalsOptions, type WebhookPayload, abortTrain, acknowledgeProductPurchase, activateBasePlan, activateOffer, addRecoveryTargeting, addTesters, advanceTrain, analyzeBundle, analyzeRemoteListings, analyzeReviews, applyReleaseNotes, assignRelease, batchGetOrders, batchSyncInAppProducts, buildChecklist, buildLocaleBundle, bundleToReleaseNotes, cancelRecoveryAction, cancelSubscriptionPurchase, cancelSubscriptionV2, checkBundleSize, checkSigningConsistency, checkThreshold, classifyError, clearAuditLog, commitWithRescue, compareBundles, compareFingerprints, compareVersionVitals, compareVitalsTrend, computeStatusDiff, consumeProductPurchase, convertRegionPrices, createAuditEntry, createDeviceTier, createEnterpriseApp, createExternalTransaction, createGrant, createInAppProduct, createOffer, createOneTimeOffer, createOneTimeProduct, createRecoveryAction, createSpinner, createSubscription, createTrack, createTranslator, deactivateBasePlan, deactivateOffer, decodeNotification, defaultGitRunner, deferSubscriptionPurchase, deferSubscriptionV2, deleteBasePlan, deleteGrant, deleteImage, deleteInAppProduct, deleteListing, deleteOffer, deleteOneTimeOffer, deleteOneTimeProduct, deleteSubscription, deployRecoveryAction, detectFastlane, detectOutputFormat, diffListings, diffListingsCommand, diffListingsEnhanced, diffOneTimeProduct, diffReleases, diffSubscription, discoverPlugins, dispatchWebhook, downloadGeneratedApk, downloadReport, exportImages, exportReviews, fetchAggregateCost, fetchChangelog, fetchReleaseNotes, findBundle, formatChangelogEntry, formatCustomPayload, formatDiscordPayload, formatJunit, formatNotification, formatOutput, formatPathLabel, formatSlackPayload, formatStatusDiff, formatStatusSummary, formatStatusTable, formatWordDiff, generateChangelog, generateMigrationPlan, generateNotesFromGit, getAllScannerNames, getApiSigningFingerprint, getAppInfo, getAppStatus, getCountryAvailability, getDeviceTier, getExternalTransaction, getInAppProduct, getKeystoreFingerprint, getListings, getOffer, getOneTimeOffer, getOneTimeProduct, getOrderDetails, getProductPurchase, getProductPurchaseV2, getQuotaUsage, getReleasesStatus, getReview, getRtdnStatus, getSubscription, getSubscriptionAnalytics, getSubscriptionPurchase, getTrainStatus, getUser, getVitalsAnomalies, getVitalsAnr, getVitalsBattery, getVitalsCrashes, getVitalsErrorCount, getVitalsLmk, getVitalsMemory, getVitalsOverview, getVitalsRendering, getVitalsStartup, handleBreach, importDataSafety, importTestersFromCsv, initAudit, initProject, inviteUser, isFinancialReportType, isStatsReportType, isValidBcp47, isValidReportType, isValidStatsDimension, isVersionedNotesDir, lintListing, lintListings, lintLocalListings, listAchievements, listAuditEvents, listBundles, listDeviceTiers, listEvents, listGeneratedApks, listGrants, listImages, listInAppProducts, listLeaderboards, listOffers, listOneTimeOffers, listOneTimeProducts, listRecoveryActions, listReports, listReviews, listSubscriptions, listTesters, listTracks, listUsers, listVoidedPurchases, loadPreflightConfig, loadStatusCache, maybePaginate, migratePrices, normalizeFingerprint, parseAppfile, parseCommit, parseFastfile, parseGrantArg, parseKeytoolOutput, parseMonth, parseRemoteUrl, pauseTrain, promoteRelease, publish, publishEnterpriseApp, pullListings, pushListings, readListingsFromDir, readReleaseNotesForVersion, readReleaseNotesFromDir, redactAuditArgs, redactSensitive, refundExternalTransaction, refundOrder, relativeTime, removeTesters, removeUser, renderChecklistMarkdown, renderJson, renderMarkdown, renderPlayStore, renderPlayStoreJson, renderPlayStoreMd, renderPlayStorePrompt, renderPrompt, replyToReview, resolveAiConfig, resolveLocales, retryOnUploadNotComplete, revokeSubscriptionPurchase, runPreflight, runWatch, runWatchLoop, safePath, safePathWithin, saveStatusCache, scaffoldPlugin, searchAuditEvents, searchVitalsErrors, sendNotification, sendWebhook, sha256File, sortResults, startTrain, statusHasBreach, syncImages, syncInAppProducts, topFiles, trackBreachState, translateBundle, updateAppDetails, updateDataSafety, updateGrant, updateInAppProduct, updateListing, updateOffer, updateOneTimeOffer, updateOneTimeProduct, updateRollout, updateSubscription, updateTrackConfig, updateUser, uploadExternallyHosted, uploadImage, uploadInternalSharing, uploadRelease, validateAndCommit, validateBundleForApply, validateImage, validateLanguageCode, validatePackageName, validatePreSubmission, validateReleaseNotes, validateSku, validateTrackName, validateUploadFile, validateVersionCode, waitForBundle, waitForBundleProcessing, watchVitalsWithAutoHalt, wordDiff, writeAuditLog, writeListingsToDir, writeMigrationOutput };
|