@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.
@@ -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
- process.emitWarning(
253
- "App has a rejected update \u2014 auto-setting changesNotSentForReview=true",
254
- "AutoRescueWarning"
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
- process.emitWarning(
273
- "App has a rejected update \u2014 auto-setting changesNotSentForReview=true",
274
- "AutoRescueWarning"
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
- } else {
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(client, packageName, edit.id, options?.commitOptions);
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(client, packageName, edit.id, options.commitOptions);
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-4TBNOCVU.js.map
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":";AAAA,SAAS,QAAAA,aAAY;AACrB,SAAS,WAAAC,gBAAe;;;ACDxB,SAAS,SAAS,UAAU,aAAa;AACzC,SAAS,SAAS,UAAU,YAAY;;;ACDjC,IAAM,WAAN,cAAuB,MAAM;AAAA,EAClC,YACE,SACgB,MACA,UACA,YAChB;AACA,UAAM,OAAO;AAJG;AACA;AACA;AAGhB,SAAK,OAAO;AAAA,EACd;AAAA,EANkB;AAAA,EACA;AAAA,EACA;AAAA,EAMlB,SAAS;AACP,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO;AAAA,QACL,MAAM,KAAK;AAAA,QACX,SAAS,KAAK;AAAA,QACd,YAAY,KAAK;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AACF;AAEO,IAAM,cAAN,cAA0B,SAAS;AAAA,EACxC,YAAY,SAAiB,MAAc,YAAqB;AAC9D,UAAM,SAAS,MAAM,GAAG,UAAU;AAClC,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,WAAN,cAAuB,SAAS;AAAA,EACrC,YACE,SACA,MACgB,YAChB,YACA;AACA,UAAM,SAAS,MAAM,GAAG,UAAU;AAHlB;AAIhB,SAAK,OAAO;AAAA,EACd;AAAA,EALkB;AAMpB;AAEO,IAAM,eAAN,cAA2B,SAAS;AAAA,EACzC,YAAY,SAAiB,YAAqB;AAChD,UAAM,SAAS,iBAAiB,GAAG,UAAU;AAC7C,SAAK,OAAO;AAAA,EACd;AACF;;;ADhCA,IAAM,mBAAmB;AAEzB,eAAsB,wBAAwB,KAAqC;AACjF,MAAI;AACJ,MAAI;AACF,cAAU,MAAM,QAAQ,GAAG;AAAA,EAC7B,QAAQ;AACN,UAAM,IAAI;AAAA,MACR,sCAAsC,GAAG;AAAA,MACzC;AAAA,MACA;AAAA,MACA,2FAA2F,GAAG;AAAA,IAChG;AAAA,EACF;AAEA,QAAM,QAAuB,CAAC;AAE9B,aAAW,SAAS,SAAS;AAC3B,QAAI,QAAQ,KAAK,MAAM,OAAQ;AAE/B,UAAM,WAAW,SAAS,OAAO,MAAM;AACvC,UAAM,WAAW,KAAK,KAAK,KAAK;AAEhC,UAAM,QAAQ,MAAM,MAAM,QAAQ;AAClC,QAAI,CAAC,MAAM,OAAO,KAAK,MAAM,eAAe,EAAG;AAE/C,UAAM,QAAQ,MAAM,SAAS,UAAU,OAAO,GAAG,KAAK;AACtD,QAAI,KAAK,WAAW,EAAG;AAEvB,UAAM,KAAK,EAAE,UAAU,KAAK,CAAC;AAAA,EAC/B;AAEA,SAAO;AACT;AAEA,IAAM,iBAAiB;AAEvB,eAAsB,oBAAoB,KAA+B;AACvE,MAAI;AACJ,MAAI;AACF,cAAU,MAAM,QAAQ,GAAG;AAAA,EAC7B,QAAQ;AACN,WAAO;AAAA,EACT;AACA,aAAW,SAAS,SAAS;AAC3B,QAAI,CAAC,eAAe,KAAK,KAAK,EAAG;AACjC,UAAM,YAAY,KAAK,KAAK,KAAK;AACjC,UAAM,QAAQ,MAAM,MAAM,SAAS;AACnC,QAAI,MAAM,YAAY,KAAK,CAAC,MAAM,eAAe,EAAG,QAAO;AAAA,EAC7D;AACA,SAAO;AACT;AAEA,eAAsB,2BACpB,KACA,aACwB;AACxB,MAAI,CAAC,OAAO,UAAU,WAAW,KAAK,eAAe,GAAG;AACtD,UAAM,IAAI;AAAA,MACR,yBAAyB,WAAW;AAAA,MACpC;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,MAAI;AACJ,MAAI;AACF,cAAU,MAAM,QAAQ,GAAG;AAAA,EAC7B,QAAQ;AACN,UAAM,IAAI;AAAA,MACR,sCAAsC,GAAG;AAAA,MACzC;AAAA,MACA;AAAA,MACA,wGAAwG,GAAG;AAAA,IAC7G;AAAA,EACF;AAEA,QAAM,QAAuB,CAAC;AAE9B,aAAW,SAAS,SAAS;AAC3B,UAAM,YAAY,KAAK,KAAK,KAAK;AACjC,UAAM,QAAQ,MAAM,MAAM,SAAS;AACnC,QAAI,CAAC,MAAM,YAAY,KAAK,MAAM,eAAe,EAAG;AAEpD,UAAM,WAAW;AACjB,UAAM,cAAc,KAAK,WAAW,GAAG,WAAW,MAAM;AACxD,UAAM,cAAc,KAAK,WAAW,aAAa;AAEjD,QAAI;AACJ,QAAI;AACF,cAAQ,MAAM,SAAS,aAAa,OAAO,GAAG,KAAK;AAAA,IACrD,SAAS,KAAc;AACrB,UAAI,OAAO,OAAO,QAAQ,YAAY,UAAU,OAAO,IAAI,SAAS,SAAU,OAAM;AACpF,UAAI;AACF,gBAAQ,MAAM,SAAS,aAAa,OAAO,GAAG,KAAK;AAAA,MACrD,SAAS,MAAe;AACtB,YAAI,QAAQ,OAAO,SAAS,YAAY,UAAU,QAAQ,KAAK,SAAS;AACtE,gBAAM;AACR;AAAA,MACF;AAAA,IACF;AAEA,QAAI,QAAQ,KAAK,SAAS,GAAG;AAC3B,YAAM,KAAK,EAAE,UAAU,KAAK,CAAC;AAAA,IAC/B;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,qBAAqB,OAA8C;AACjF,QAAM,SAAmB,CAAC;AAC1B,QAAM,WAAqB,CAAC;AAE5B,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,QAAQ,OAAO;AACxB,QAAI,KAAK,IAAI,KAAK,QAAQ,GAAG;AAC3B,aAAO,KAAK,4BAA4B,KAAK,QAAQ,EAAE;AAAA,IACzD;AACA,SAAK,IAAI,KAAK,QAAQ;AAEtB,QAAI,KAAK,KAAK,SAAS,kBAAkB;AACvC,eAAS;AAAA,QACP,sBAAsB,KAAK,QAAQ,SAAS,KAAK,KAAK,MAAM,eAAe,gBAAgB;AAAA,MAC7F;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,OAAO,OAAO,WAAW,GAAG,QAAQ,SAAS;AACxD;;;ADlIA,SAAS,gBAAAC,qBAAoB;;;AGf7B,SAAS,MAAM,YAAY;AAC3B,SAAS,WAAAC,gBAAe;;;ACDjB,SAAS,WAAW,OAAuB;AAChD,MAAI,SAAS,OAAO,OAAO,MAAM;AAC/B,WAAO,IAAI,SAAS,OAAO,OAAO,OAAO,QAAQ,CAAC,CAAC;AAAA,EACrD;AACA,MAAI,SAAS,OAAO,MAAM;AACxB,WAAO,IAAI,SAAS,OAAO,OAAO,QAAQ,CAAC,CAAC;AAAA,EAC9C;AACA,MAAI,SAAS,MAAM;AACjB,WAAO,IAAI,QAAQ,MAAM,QAAQ,CAAC,CAAC;AAAA,EACrC;AACA,SAAO,GAAG,KAAK;AACjB;;;ADGA,IAAM,YAAY,OAAO,KAAK,CAAC,IAAM,IAAM,GAAM,CAAI,CAAC;AAEtD,IAAM,eAAe,OAAO,OAAO;AACnC,IAAM,eAAe,IAAI,OAAO,OAAO;AACvC,IAAM,uBAAuB,MAAM,OAAO;AAE1C,eAAsB,mBAAmB,UAAiD;AACxF,QAAM,SAAmB,CAAC;AAC1B,QAAM,WAAqB,CAAC;AAG5B,QAAM,MAAMC,SAAQ,QAAQ,EAAE,YAAY;AAC1C,MAAI,WAA6C;AAEjD,MAAI,QAAQ,QAAQ;AAClB,eAAW;AAAA,EACb,WAAW,QAAQ,QAAQ;AACzB,eAAW;AAAA,EACb,OAAO;AACL,WAAO,KAAK,+BAA+B,GAAG,0BAA0B;AAAA,EAC1E;AAGA,MAAI;AACJ,MAAI;AACF,UAAM,QAAQ,MAAM,KAAK,QAAQ;AACjC,gBAAY,MAAM;AAElB,QAAI,cAAc,GAAG;AACnB,aAAO,KAAK,yBAAyB;AAAA,IACvC;AAAA,EACF,QAAQ;AACN,WAAO,KAAK,mBAAmB,QAAQ,EAAE;AACzC,WAAO,EAAE,OAAO,OAAO,UAAU,WAAW,GAAG,QAAQ,SAAS;AAAA,EAClE;AAGA,MAAI,aAAa,SAAS,YAAY,cAAc;AAClD,WAAO;AAAA,MACL,2BAA2B,WAAW,SAAS,CAAC;AAAA,IAClD;AAAA,EACF;AACA,MAAI,aAAa,SAAS,YAAY,cAAc;AAClD,WAAO,KAAK,2BAA2B,WAAW,SAAS,CAAC,IAAI;AAAA,EAClE;AAEA,MAAI,YAAY,wBAAwB,OAAO,WAAW,GAAG;AAC3D,aAAS;AAAA,MACP,eAAe,WAAW,SAAS,CAAC;AAAA,IACtC;AAAA,EACF;AAGA,MAAI,YAAY,GAAG;AACjB,QAAI;AACJ,QAAI;AACF,WAAK,MAAM,KAAK,UAAU,GAAG;AAC7B,YAAM,MAAM,OAAO,MAAM,CAAC;AAC1B,YAAM,GAAG,KAAK,KAAK,GAAG,GAAG,CAAC;AAE1B,UAAI,CAAC,IAAI,OAAO,SAAS,GAAG;AAC1B,eAAO;AAAA,UACL;AAAA,QAEF;AAAA,MACF;AAAA,IACF,QAAQ;AACN,aAAO,KAAK,2CAA2C;AAAA,IACzD,UAAE;AACA,YAAM,IAAI,MAAM;AAAA,IAClB;AAAA,EACF;AAEA,SAAO;AAAA,IACL,OAAO,OAAO,WAAW;AAAA,IACzB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;AE7FA,SAAS,oBAAoB;AAM7B,IAAM,yBACJ;AAGF,eAAsB,iBACpB,QACA,aACA,QACA,eACuB;AACvB,MAAI;AACF,UAAM,OAAO,MAAM,OAAO,aAAa,QAAQ,aAAa;AAC5D,WAAO,EAAE,SAAS,MAAM;AAAA,EAC1B,SAAS,OAAO;AACd,QACE,iBAAiB,gBACjB,MAAM,SAAS,qCACf,CAAC,eAAe,yBAChB;AACA,cAAQ,MAAM;AAAA,aAAgB,sBAAsB;AAAA,CAAI;AACxD,YAAM,OAAO,MAAM,OAAO,aAAa,QAAQ;AAAA,QAC7C,GAAG;AAAA,QACH,yBAAyB;AAAA,MAC3B,CAAC;AACD,aAAO,EAAE,SAAS,KAAK;AAAA,IACzB;AACA,UAAM;AAAA,EACR;AACF;AAEA,eAAsB,kBACpB,QACA,aACA,QACA,eACuB;AACvB,MAAI,sBAAsB;AAC1B,MAAI,CAAC,eAAe,yBAAyB;AAC3C,QAAI;AACF,YAAM,OAAO,MAAM,SAAS,aAAa,MAAM;AAAA,IACjD,SAAS,OAAO;AACd,UAAI,iBAAiB,gBAAgB,MAAM,SAAS,mCAAmC;AACrF,gBAAQ,MAAM;AAAA,aAAgB,sBAAsB;AAAA,CAAI;AACxD,8BAAsB;AAAA,MACxB,OAAO;AACL,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,MAAI,qBAAqB;AACvB,UAAM,OAAO,MAAM,OAAO,aAAa,QAAQ;AAAA,MAC7C,GAAG;AAAA,MACH,yBAAyB;AAAA,IAC3B,CAAC;AACD,WAAO,EAAE,SAAS,KAAK;AAAA,EACzB;AACA,SAAO,iBAAiB,QAAQ,aAAa,QAAQ,aAAa;AACpE;;;AChEA,SAAS,gBAAAC,qBAAoB;AAQ7B,eAAsB,YAAY,QAAuB,aAAwC;AAC/F,QAAM,OAAO,MAAM,OAAO,MAAM,OAAO,WAAW;AAClD,MAAI;AACF,UAAM,UAAU,MAAM,OAAO,QAAQ,KAAK,aAAa,KAAK,EAAE;AAC9D,UAAM,OAAO,MAAM,OAAO,aAAa,KAAK,EAAE,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAC9D,WAAO;AAAA,EACT,SAAS,OAAO;AACd,UAAM,OAAO,MAAM,OAAO,aAAa,KAAK,EAAE,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAC9D,UAAM;AAAA,EACR;AACF;AAEA,eAAsB,WACpB,QACA,aACA,aACwB;AACxB,QAAM,UAAU,MAAM,YAAY,QAAQ,WAAW;AACrD,SAAO,QAAQ,KAAK,CAAC,MAAM,EAAE,gBAAgB,WAAW,KAAK;AAC/D;AAEA,SAAS,iBAAiB,OAAyB;AACjD,MAAI,EAAE,iBAAiBC,eAAe,QAAO;AAC7C,QAAM,SAAS,MAAM;AACrB,SAAO,WAAW,OAAO,WAAW,OAAO,WAAW,OAAO,WAAW;AAC1E;AAEA,eAAsB,cACpB,QACA,aACA,aACA,SACiB;AACjB,QAAM,UAAU,SAAS,WAAW;AACpC,QAAM,WAAW,SAAS,YAAY;AACtC,QAAM,WAAW,KAAK,IAAI,IAAI;AAE9B,SAAO,KAAK,IAAI,IAAI,UAAU;AAC5B,QAAI;AACF,YAAM,UAAU,MAAM,YAAY,QAAQ,WAAW;AACrD,YAAM,QAAQ,QAAQ,KAAK,CAAC,MAAM,EAAE,gBAAgB,WAAW;AAC/D,UAAI,MAAO,QAAO;AAAA,IACpB,SAAS,OAAO;AACd,UAAI,CAAC,iBAAiB,KAAK,EAAG,OAAM;AAAA,IACtC;AAEA,UAAM,YAAY,WAAW,KAAK,IAAI;AACtC,QAAI,aAAa,EAAG;AACpB,UAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,KAAK,IAAI,UAAU,SAAS,CAAC,CAAC;AAAA,EACvE;AAEA,QAAM,IAAI;AAAA,IACR,uBAAuB,WAAW,oBAAoB,KAAK,MAAM,UAAU,GAAI,CAAC;AAAA,IAChF;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;AN7CA,IAAM,sBAAsB,CAAC,KAAO,KAAO,KAAO,KAAO,MAAQ,MAAQ,IAAM;AAC/E,IAAM,2BAA2B;AAEjC,eAAsB,wBACpB,QACA,aACA,QACA,aACA,UAAoB,qBACL;AACf,QAAM,QAAQ,KAAK,IAAI;AACvB,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,UAAM,UAAU,MAAM,OAAO,QAAQ,KAAK,aAAa,MAAM;AAC7D,QAAI,QAAQ,KAAK,CAAC,MAAM,EAAE,gBAAgB,WAAW,EAAG;AACxD,UAAM,UAAU,KAAK,OAAO,KAAK,IAAI,IAAI,SAAS,GAAI;AACtD,YAAQ;AAAA,MACN,wDAAwD,OAAO,cAAc,IAAI,CAAC,IAAI,QAAQ,MAAM;AAAA,IACtG;AACA,UAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,QAAQ,CAAC,CAAC,CAAC;AAAA,EACpD;AACA,QAAM,IAAI;AAAA,IACR,sBAAsB,WAAW,oBAAoB,QAAQ,MAAM,oBAAoB,KAAK,MAAM,QAAQ,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI,GAAI,CAAC;AAAA,IAC5I;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEA,IAAM,yBAAyB;AAC/B,IAAM,uBAAuB,CAAC,MAAQ,KAAQ,IAAM;AAEpD,eAAsB,yBAA4B,IAAkC;AAClF,WAAS,IAAI,GAAG,IAAI,qBAAqB,QAAQ,KAAK;AACpD,QAAI;AACF,aAAO,MAAM,GAAG;AAAA,IAClB,SAAS,OAAO;AACd,YAAM,sBACJ,iBAAiBC,iBAAgB,uBAAuB,KAAK,MAAM,OAAO;AAC5E,UAAI,CAAC,uBAAuB,MAAM,qBAAqB,SAAS,EAAG,OAAM;AACzE,YAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,qBAAqB,CAAC,CAAC,CAAC;AAAA,IACjE;AAAA,EACF;AACA,SAAO,GAAG;AACZ;AAMA,eAAe,oBACb,QACA,aACA,WACY;AACZ,QAAM,OAAO,MAAM,OAAO,MAAM,OAAO,WAAW;AAClD,MAAI;AACF,WAAO,MAAM,UAAU,IAAI;AAAA,EAC7B,SAAS,OAAO;AACd,UAAM,aAAa,iBAAiBA,iBAAgB,MAAM,eAAe;AACzE,QAAI,CAAC,YAAY;AACf,YAAM,OAAO,MAAM,OAAO,aAAa,KAAK,EAAE,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAC9D,YAAM;AAAA,IACR;AAEA,UAAM,OAAO,MAAM,OAAO,aAAa,KAAK,EAAE,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAC9D,UAAM,YAAY,MAAM,OAAO,MAAM,OAAO,WAAW;AACvD,QAAI;AACF,aAAO,MAAM,UAAU,SAAS;AAAA,IAClC,SAAS,YAAY;AACnB,YAAM,OAAO,MAAM,OAAO,aAAa,UAAU,EAAE,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AACnE,YAAM;AAAA,IACR;AAAA,EACF;AACF;AAGA,IAAI,2BAA2B;AAC/B,SAAS,2BAAiC;AACxC,MAAI,yBAA0B;AAC9B,6BAA2B;AAC3B,UAAQ;AAAA,IACN;AAAA,IAEA;AAAA,EACF;AACF;AAEA,SAAS,mBAAmB,MAAqB;AAC/C,MAAI,CAAC,KAAK,kBAAmB;AAC7B,QAAM,WAAW,OAAO,KAAK,iBAAiB,IAAI;AAClD,QAAM,cAAc,WAAW,KAAK,IAAI;AACxC,MAAI,cAAc,IAAI,KAAK,OAAQ,cAAc,GAAG;AAClD,UAAM,UAAU,KAAK,MAAM,cAAc,GAAM;AAC/C,YAAQ;AAAA,MACN,4BAA4B,OAAO,UAAU,YAAY,IAAI,MAAM,EAAE;AAAA,MACrE;AAAA,IACF;AAAA,EACF;AACF;AAOA,eAAsB,cACpB,QACA,aACA,WACY;AACZ,QAAM,OAAO,MAAM,OAAO,MAAM,OAAO,WAAW;AAClD,MAAI;AACF,WAAO,MAAM,UAAU,KAAK,EAAE;AAAA,EAChC,SAAS,OAAO;AACd,QAAI,iBAAiBA,iBAAgB,MAAM,SAAS,oBAAoB;AAEtE,YAAM,OAAO,MAAM,OAAO,aAAa,KAAK,EAAE,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAC9D,YAAM,YAAY,MAAM,OAAO,MAAM,OAAO,WAAW;AACvD,UAAI;AACF,eAAO,MAAM,UAAU,UAAU,EAAE;AAAA,MACrC,SAAS,YAAY;AACnB,cAAM,OAAO,MAAM,OAAO,aAAa,UAAU,EAAE,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AACnE,cAAM;AAAA,MACR;AAAA,IACF;AACA,UAAM,OAAO,MAAM,OAAO,aAAa,KAAK,EAAE,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAC9D,UAAM;AAAA,EACR;AACF;AAiCA,eAAsB,cACpB,QACA,aACA,UACA,SAsB4C;AAE5C,QAAM,aAAa,MAAM,mBAAmB,QAAQ;AAEpD,MAAI,QAAQ,QAAQ;AAClB,UAAM,gBAAgB,QAAQ,WAAW,QAAQ,eAAe,eAAe;AAG/E,QAAI,kBAAyD,CAAC;AAC9D,UAAMC,QAAO,MAAM,OAAO,MAAM,OAAO,WAAW;AAClD,QAAI;AACF,YAAM,YAAY,MAAM,OAAO,OAAO,IAAI,aAAaA,MAAK,IAAI,QAAQ,KAAK;AAC7E,yBAAmB,UAAU,YAAY,CAAC,GAAG,IAAI,CAAC,OAAO;AAAA,QACvD,cAAc,EAAE,gBAAgB,CAAC;AAAA,QACjC,QAAQ,EAAE;AAAA,QACV,GAAI,EAAE,iBAAiB,UAAa,EAAE,cAAc,EAAE,aAAa;AAAA,MACrE,EAAE;AAAA,IACJ,QAAQ;AAAA,IAER,UAAE;AACA,YAAM,OAAO,MAAM,OAAO,aAAaA,MAAK,EAAE,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IAChE;AAEA,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,MAAM;AAAA,QACJ,MAAM;AAAA,QACN,OAAO,WAAW;AAAA,QAClB,QAAQ,WAAW;AAAA,QACnB,UAAU,WAAW;AAAA,MACvB;AAAA,MACA,OAAO,QAAQ;AAAA,MACf;AAAA,MACA,gBAAgB;AAAA,QACd,QAAQ;AAAA,QACR,GAAI,QAAQ,iBAAiB,UAAa,EAAE,cAAc,QAAQ,aAAa;AAAA,MACjF;AAAA,MACA,UAAU;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACA,SAAS,CAAC,iBAAiB,mBAAmB,aAAa;AAAA,IAC7D;AAAA,EACF;AAEA,MAAI,CAAC,WAAW,OAAO;AACrB,UAAM,IAAI;AAAA,MACR;AAAA,EAA4B,WAAW,OAAO,KAAK,IAAI,CAAC;AAAA,MACxD;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAGA,MAAI,WAAW;AACf,MAAI;AACF,UAAM,EAAE,KAAK,IAAI,MAAMC,MAAK,QAAQ;AACpC,eAAW;AAAA,EACb,QAAQ;AAAA,EAER;AAEA,MAAI,QAAQ,WAAY,SAAQ,WAAW,GAAG,QAAQ;AAEtD,QAAM,OAAO,MAAM,OAAO,MAAM,OAAO,WAAW;AAClD,qBAAmB,IAAI;AACvB,2BAAyB;AACzB,MAAI;AAEF,UAAM,QAAQC,SAAQ,QAAQ,EAAE,YAAY,MAAM;AAClD,UAAM,aAAa;AAAA,MACjB,GAAG,QAAQ;AAAA,MACX,YAAY,CAAC,UAA+B;AAC1C,YAAI,QAAQ,WAAY,SAAQ,WAAW,MAAM,eAAe,MAAM,UAAU;AAChF,YAAI,QAAQ,iBAAkB,SAAQ,iBAAiB,KAAK;AAAA,MAC9D;AAAA,IACF;AACA,UAAM,SAAS,QACX,MAAM,OAAO,KAAK,OAAO,aAAa,KAAK,IAAI,UAAU,UAAU,IACnE,MAAM,OAAO,QAAQ;AAAA,MACnB;AAAA,MACA,KAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,IACV;AAMJ,QAAI,CAAC,OAAO;AACV,YAAM,wBAAwB,QAAQ,aAAa,KAAK,IAAI,OAAO,WAAW;AAAA,IAChF;AAGA,QAAI,QAAQ,aAAa;AACvB,YAAM,OAAO,cAAc;AAAA,QACzB;AAAA,QACA,KAAK;AAAA,QACL,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,QAAQ;AAAA,MACV;AAAA,IACF;AAGA,QAAI,eAAe,QAAQ;AAC3B,QAAI,CAAC,gBAAgB,QAAQ,mBAAmB;AAC9C,qBAAe,MAAM;AAAA,QACnB,QAAQ;AAAA,QACR,OAAO;AAAA,MACT;AACA,UAAI,aAAa,WAAW,EAAG,gBAAe;AAAA,IAChD;AAGA,UAAM,eAAe,OAAO,OAAO,WAAW;AAC9C,UAAM,UAAmB;AAAA,MACvB,cAAc;AAAA,QACZ,IAAI,QAAQ,sBAAsB,CAAC,GAAG,OAAO,CAAC,OAAO,OAAO,YAAY;AAAA,QACxE;AAAA,MACF;AAAA,MACA,QAAS,QAAQ,WACd,QAAQ,eAAe,eAAe;AAAA,MACzC,GAAI,QAAQ,gBAAgB,EAAE,cAAc,QAAQ,aAAa;AAAA,MACjE,GAAI,gBAAgB,EAAE,aAAa;AAAA,MACnC,GAAI,QAAQ,eAAe,EAAE,MAAM,QAAQ,YAAY;AAAA,MACvD,GAAI,QAAQ,wBAAwB,UAAa;AAAA,QAC/C,qBAAqB,QAAQ;AAAA,MAC/B;AAAA,IACF;AAEA,UAAM,OAAO,OAAO,OAAO,aAAa,KAAK,IAAI,QAAQ,OAAO,OAAO;AAEvE,QAAI,CAAC,QAAQ,eAAe,yBAAyB;AACnD,YAAM,yBAAyB,MAAM,OAAO,MAAM,SAAS,aAAa,KAAK,EAAE,CAAC;AAAA,IAClF;AAEA,QAAI,QAAQ,cAAc;AACxB,YAAM,OAAO,MAAM,OAAO,aAAa,KAAK,EAAE,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAC9D,aAAO;AAAA,QACL,aAAa,OAAO;AAAA,QACpB,OAAO,QAAQ;AAAA,QACf,QAAQ,QAAQ;AAAA,QAChB,cAAc;AAAA,MAChB;AAAA,IACF;AAEA,UAAM,eAAe,MAAM;AAAA,MAAyB,MAClD,iBAAiB,QAAQ,aAAa,KAAK,IAAI,QAAQ,aAAa;AAAA,IACtE;AAEA,UAAM,aAAa,QAAQ,UAAU;AACrC,QAAI,YAAY;AACd,cAAQ;AAAA,QACN;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,MACL,aAAa,OAAO;AAAA,MACpB,OAAO,QAAQ;AAAA,MACf,QAAQ,QAAQ;AAAA,MAChB,GAAI,cAAc,EAAE,eAAe,KAAK;AAAA,MACxC,GAAI,aAAa,WACf,CAAC,cAAc;AAAA,QACb,eAAe;AAAA,QACf,UAAU;AAAA,MACZ;AAAA,IACJ;AAAA,EACF,SAAS,OAAO;AACd,UAAM,OAAO,MAAM,OAAO,aAAa,KAAK,EAAE,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAC9D,UAAM;AAAA,EACR;AACF;AAEA,eAAsB,kBACpB,QACA,aACA,aACgC;AAChC,QAAM,OAAO,MAAM,OAAO,MAAM,OAAO,WAAW;AAClD,MAAI;AACF,UAAM,SAAS,cACX,CAAC,MAAM,OAAO,OAAO,IAAI,aAAa,KAAK,IAAI,WAAW,CAAC,IAC3D,MAAM,OAAO,OAAO,KAAK,aAAa,KAAK,EAAE;AAEjD,UAAM,OAAO,MAAM,OAAO,aAAa,KAAK,EAAE;AAE9C,UAAM,UAAiC,CAAC;AACxC,eAAW,SAAS,QAAQ;AAC1B,iBAAW,WAAW,MAAM,YAAY,CAAC,GAAG;AAC1C,gBAAQ,KAAK;AAAA,UACX,OAAO,MAAM;AAAA,UACb,QAAQ,QAAQ;AAAA,UAChB,cAAc,QAAQ,gBAAgB,CAAC;AAAA,UACvC,cAAc,QAAQ;AAAA,UACtB,cAAc,QAAQ;AAAA,QACxB,CAAC;AAAA,MACH;AAAA,IACF;AACA,WAAO;AAAA,EACT,SAAS,OAAO;AACd,UAAM,OAAO,MAAM,OAAO,aAAa,KAAK,EAAE,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAC9D,UAAM;AAAA,EACR;AACF;AAEA,eAAsB,eACpB,QACA,aACA,WACA,SACA,SAM8B;AAE9B,MAAI,SAAS,iBAAiB,QAAQ,gBAAgB,KAAK,QAAQ,eAAe,IAAI;AACpF,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,SAAO,oBAAoB,QAAQ,aAAa,OAAO,SAAS;AAE9D,UAAM,cAAc,MAAM,OAAO,OAAO,IAAI,aAAa,KAAK,IAAI,SAAS;AAC3E,UAAM,iBAAiB,YAAY,UAAU;AAAA,MAC3C,CAAC,MAAM,EAAE,WAAW,eAAe,EAAE,WAAW;AAAA,IAClD;AAEA,QAAI,CAAC,gBAAgB;AACnB,YAAM,IAAI;AAAA,QACR,qCAAqC,SAAS;AAAA,QAC9C;AAAA,QACA;AAAA,QACA,8DAA8D,SAAS;AAAA,MACzE;AAAA,IACF;AAEA,UAAM,UAAmB;AAAA,MACvB,cAAc,eAAe;AAAA,MAC7B,QAAS,SAAS,WACf,SAAS,eAAe,eAAe;AAAA,MAC1C,GAAI,SAAS,gBAAgB,EAAE,cAAc,QAAQ,aAAa;AAAA,MAClE,cAAc,SAAS,gBAAgB,eAAe,gBAAgB,CAAC;AAAA,MACvE,GAAI,eAAe,wBAAwB,UAAa;AAAA,QACtD,qBAAqB,eAAe;AAAA,MACtC;AAAA,MACA,GAAI,eAAe,QAAQ,EAAE,MAAM,eAAe,KAAK;AAAA,IACzD;AAEA,UAAM,OAAO,OAAO,OAAO,aAAa,KAAK,IAAI,SAAS,OAAO;AACjE,UAAM,eAAe,MAAM;AAAA,MACzB;AAAA,MACA;AAAA,MACA,KAAK;AAAA,MACL,SAAS;AAAA,IACX;AAEA,WAAO;AAAA,MACL,OAAO;AAAA,MACP,QAAQ,QAAQ;AAAA,MAChB,cAAc,QAAQ;AAAA,MACtB,cAAc,QAAQ;AAAA,MACtB,GAAI,aAAa,WAAW;AAAA,QAC1B,eAAe;AAAA,QACf,UAAU;AAAA,MACZ;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAEA,eAAsB,cACpB,QACA,aACA,aACA,SAW8B;AAC9B,MAAI,QAAQ,iBAAiB,QAAQ,gBAAgB,KAAK,QAAQ,eAAe,IAAI;AACnF,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,SAAS,MAAM,WAAW,QAAQ,aAAa,WAAW;AAChE,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI;AAAA,MACR,gBAAgB,WAAW;AAAA,MAC3B;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,OAAO,OAAO,WAAW;AAC/B,QAAM,UAAmB;AAAA,IACvB,cAAc,CAAC,IAAI,QAAQ,sBAAsB,CAAC,GAAG,OAAO,CAAC,OAAO,OAAO,IAAI,GAAG,IAAI;AAAA,IACtF,QAAS,QAAQ,WACd,QAAQ,eAAe,eAAe;AAAA,IACzC,GAAI,QAAQ,gBAAgB,EAAE,cAAc,QAAQ,aAAa;AAAA,IACjE,GAAI,QAAQ,gBAAgB,EAAE,cAAc,QAAQ,aAAa;AAAA,IACjE,GAAI,QAAQ,eAAe,EAAE,MAAM,QAAQ,YAAY;AAAA,IACvD,GAAI,QAAQ,wBAAwB,UAAa;AAAA,MAC/C,qBAAqB,QAAQ;AAAA,IAC/B;AAAA,EACF;AAEA,MAAI,QAAQ,QAAQ;AAClB,WAAO;AAAA,MACL,OAAO,QAAQ;AAAA,MACf,QAAQ,QAAQ;AAAA,MAChB,cAAc,QAAQ;AAAA,MACtB,cAAc,QAAQ;AAAA,IACxB;AAAA,EACF;AAEA,QAAM,OAAO,MAAM,OAAO,MAAM,OAAO,WAAW;AAClD,MAAI;AACF,UAAM,OAAO,OAAO,OAAO,aAAa,KAAK,IAAI,QAAQ,OAAO,OAAO;AACvE,UAAM,eAAe,MAAM;AAAA,MACzB;AAAA,MACA;AAAA,MACA,KAAK;AAAA,MACL,QAAQ;AAAA,IACV;AAEA,WAAO;AAAA,MACL,OAAO,QAAQ;AAAA,MACf,QAAQ,QAAQ;AAAA,MAChB,cAAc,QAAQ;AAAA,MACtB,cAAc,QAAQ;AAAA,MACtB,GAAI,aAAa,WAAW;AAAA,QAC1B,eAAe;AAAA,QACf,UAAU;AAAA,MACZ;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,UAAM,OAAO,MAAM,OAAO,aAAa,KAAK,EAAE,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAC9D,UAAM;AAAA,EACR;AACF;AAEA,eAAsB,cACpB,QACA,aACA,OACA,QACA,cACA,eAC8B;AAC9B,QAAM,OAAO,MAAM,OAAO,MAAM,OAAO,WAAW;AAClD,MAAI;AACF,UAAM,YAAY,MAAM,OAAO,OAAO,IAAI,aAAa,KAAK,IAAI,KAAK;AACrE,UAAM,iBAAiB,UAAU,UAAU;AAAA,MACzC,CAAC,MAAM,EAAE,WAAW,gBAAgB,EAAE,WAAW;AAAA,IACnD;AAEA,QAAI,CAAC,gBAAgB;AACnB,YAAM,IAAI;AAAA,QACR,qCAAqC,KAAK;AAAA,QAC1C;AAAA,QACA;AAAA,QACA,qDAAqD,KAAK,2EAA2E,KAAK;AAAA,MAC5I;AAAA,IACF;AAEA,QAAI;AACJ,QAAI;AAEJ,YAAQ,QAAQ;AAAA,MACd,KAAK;AACH,YAAI,CAAC;AACH,gBAAM,IAAI;AAAA,YACR;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF;AACF,YAAI,gBAAgB,KAAK,eAAe,GAAG;AACzC,gBAAM,IAAI;AAAA,YACR;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA,QACF;AACA,oBAAY;AACZ,sBAAc;AACd;AAAA,MACF,KAAK;AACH,oBAAY;AACZ,sBAAc,eAAe;AAC7B;AAAA,MACF,KAAK;AACH,oBAAY;AACZ,sBAAc,eAAe;AAC7B;AAAA,MACF,KAAK;AACH,oBAAY;AACZ,sBAAc;AACd;AAAA,IACJ;AAEA,UAAM,UAAmB;AAAA,MACvB,cAAc,eAAe;AAAA,MAC7B,QAAQ;AAAA,MACR,GAAI,gBAAgB,UAAa,EAAE,cAAc,YAAY;AAAA,MAC7D,cAAc,eAAe,gBAAgB,CAAC;AAAA,IAChD;AAEA,UAAM,OAAO,OAAO,OAAO,aAAa,KAAK,IAAI,OAAO,OAAO;AAC/D,UAAM,eAAe,MAAM,kBAAkB,QAAQ,aAAa,KAAK,IAAI,aAAa;AAExF,WAAO;AAAA,MACL;AAAA,MACA,QAAQ;AAAA,MACR,cAAc,QAAQ;AAAA,MACtB,cAAc;AAAA,MACd,GAAI,aAAa,WAAW;AAAA,QAC1B,eAAe;AAAA,QACf,UAAU;AAAA,MACZ;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,UAAM,OAAO,MAAM,OAAO,aAAa,KAAK,EAAE,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAC9D,UAAM;AAAA,EACR;AACF;AAEA,eAAsB,WAAW,QAAuB,aAAuC;AAC7F,QAAM,OAAO,MAAM,OAAO,MAAM,OAAO,WAAW;AAClD,MAAI;AACF,UAAM,SAAS,MAAM,OAAO,OAAO,KAAK,aAAa,KAAK,EAAE;AAC5D,UAAM,OAAO,MAAM,OAAO,aAAa,KAAK,EAAE;AAC9C,WAAO;AAAA,EACT,SAAS,OAAO;AACd,UAAM,OAAO,MAAM,OAAO,aAAa,KAAK,EAAE,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAC9D,UAAM;AAAA,EACR;AACF;AAEA,eAAsB,YACpB,QACA,aACA,WACA,eACgB;AAChB,MAAI,CAAC,aAAa,UAAU,KAAK,EAAE,WAAW,GAAG;AAC/C,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,OAAO,MAAM,OAAO,MAAM,OAAO,WAAW;AAClD,MAAI;AACF,UAAM,QAAQ,MAAM,OAAO,OAAO,OAAO,aAAa,KAAK,IAAI,SAAS;AACxE,UAAM,kBAAkB,QAAQ,aAAa,KAAK,IAAI,aAAa;AACnE,WAAO;AAAA,EACT,SAAS,OAAO;AACd,UAAM,OAAO,MAAM,OAAO,aAAa,KAAK,EAAE,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAC9D,UAAM;AAAA,EACR;AACF;AAEA,eAAsB,kBACpB,QACA,aACA,WACA,QACA,eACgB;AAChB,MAAI,CAAC,aAAa,UAAU,KAAK,EAAE,WAAW,GAAG;AAC/C,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,OAAO,MAAM,OAAO,MAAM,OAAO,WAAW;AAClD,MAAI;AAEF,UAAM,SACJ,MAAM,QAAQ,OAAO,UAAU,CAAC,KAAK,OAAO,UAAU,EAAE,SAAS,IAC5D,OAAO,UAAU,EAAgC,CAAC,IACnD;AACN,UAAM,OAAO,CAAC,QAAgB,SAAS,GAAG,KAAK,OAAO,GAAG;AAEzD,UAAM,WAAW,KAAK,cAAc;AACpC,QAAI,CAAC,YAAY,SAAS,WAAW,GAAG;AACtC,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,UAAM,eAAe,SAAS,IAAI,MAAM;AAExC,UAAM,UAAmB;AAAA,MACvB;AAAA,MACA,QAAU,KAAK,QAAQ,KAAgB;AAAA,IACzC;AACA,QAAI,KAAK,cAAc,MAAM,QAAW;AACtC,cAAQ,eAAe,KAAK,cAAc;AAAA,IAC5C;AACA,QAAI,KAAK,cAAc,GAAG;AACxB,cAAQ,eAAe,KAAK,cAAc;AAAA,IAC5C;AACA,QAAI,KAAK,MAAM,GAAG;AAChB,cAAQ,OAAO,KAAK,MAAM;AAAA,IAC5B;AACA,QAAI,KAAK,qBAAqB,MAAM,QAAW;AAC7C,cAAQ,sBAAsB,KAAK,qBAAqB;AAAA,IAC1D;AAEA,UAAM,QAAQ,MAAM,OAAO,OAAO,OAAO,aAAa,KAAK,IAAI,WAAW,OAAO;AACjF,UAAM,kBAAkB,QAAQ,aAAa,KAAK,IAAI,aAAa;AACnE,WAAO;AAAA,EACT,SAAS,OAAO;AACd,UAAM,OAAO,MAAM,OAAO,aAAa,KAAK,EAAE,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAC9D,UAAM;AAAA,EACR;AACF;AAMA,eAAsB,kBACpB,QACA,aACA,OAC+C;AAC/C,QAAM,OAAO,MAAM,OAAO,MAAM,OAAO,WAAW;AAClD,MAAI;AACF,UAAM,YAAY,MAAM,OAAO,OAAO,IAAI,aAAa,KAAK,IAAI,KAAK;AACrE,UAAM,UACJ,UAAU,UAAU,KAAK,CAAC,MAAM,EAAE,WAAW,eAAe,EAAE,WAAW,YAAY,KACrF,UAAU,WAAW,CAAC;AAExB,QAAI,CAAC,SAAS;AACZ,YAAM,IAAI;AAAA,QACR,8BAA8B,KAAK;AAAA,QACnC;AAAA,QACA;AAAA,QACA,qCAAqC,KAAK;AAAA,MAC5C;AAAA,IACF;AAEA,WAAO,QAAQ,gBAAgB,CAAC;AAAA,EAClC,UAAE;AACA,UAAM,OAAO,MAAM,OAAO,aAAa,KAAK,EAAE,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EAChE;AACF;AASA,eAAsB,kBACpB,QACA,aACA,OACA,cACA,eACkC;AAClC,SAAO,oBAAoB,QAAQ,aAAa,OAAO,SAAS;AAC9D,UAAM,YAAY,MAAM,OAAO,OAAO,IAAI,aAAa,KAAK,IAAI,KAAK;AACrE,UAAM,QAAQ,UAAU,UAAU,KAAK,CAAC,MAAM,EAAE,WAAW,OAAO;AAElE,QAAI,CAAC,OAAO;AACV,YAAM,IAAI;AAAA,QACR,oCAAoC,KAAK;AAAA,QACzC;AAAA,QACA;AAAA,QACA,0FAA0F,KAAK;AAAA,MACjG;AAAA,IACF;AAEA,UAAM,UAAmB;AAAA,MACvB,GAAG;AAAA,MACH;AAAA,IACF;AAEA,UAAM,OAAO,OAAO,OAAO,aAAa,KAAK,IAAI,OAAO,OAAO;AAC/D,UAAM,kBAAkB,QAAQ,aAAa,KAAK,IAAI,aAAa;AAEnE,WAAO;AAAA,MACL;AAAA,MACA,cAAc,MAAM,gBAAgB,CAAC;AAAA,MACrC,aAAa,aAAa;AAAA,MAC1B;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAQA,eAAsB,aACpB,QACA,aACA,WACA,SACuE;AACvE,QAAM,OAAO,MAAM,OAAO,MAAM,OAAO,WAAW;AAClD,MAAI;AACF,UAAM,CAAC,UAAU,MAAM,IAAI,MAAM,QAAQ,IAAI;AAAA,MAC3C,OAAO,OAAO,IAAI,aAAa,KAAK,IAAI,SAAS;AAAA,MACjD,OAAO,OAAO,IAAI,aAAa,KAAK,IAAI,OAAO;AAAA,IACjD,CAAC;AACD,UAAM,OAAO,MAAM,OAAO,aAAa,KAAK,EAAE;AAE9C,UAAM,cAAc,SAAS,WAAW,CAAC;AACzC,UAAM,YAAY,OAAO,WAAW,CAAC;AACrC,UAAM,QAAuB,CAAC;AAE9B,UAAM,SAAS,CAAC,gBAAgB,UAAU,gBAAgB,gBAAgB,MAAM;AAChF,eAAW,SAAS,QAAQ;AAC1B,YAAM,KAAK,cAAc,KAAK,UAAU,YAAY,KAAK,KAAK,IAAI,IAAI;AACtE,YAAM,KAAK,YAAY,KAAK,UAAU,UAAU,KAAK,KAAK,IAAI,IAAI;AAClE,UAAI,OAAO,IAAI;AACb,cAAM,KAAK,EAAE,OAAO,aAAa,IAAI,aAAa,GAAG,CAAC;AAAA,MACxD;AAAA,IACF;AAEA,WAAO,EAAE,WAAW,SAAS,MAAM;AAAA,EACrC,SAAS,OAAO;AACd,UAAM,OAAO,MAAM,OAAO,aAAa,KAAK,EAAE,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAC9D,UAAM;AAAA,EACR;AACF;AAEA,eAAsB,uBACpB,QACA,aACA,MACA,eACsC;AACtC,MAAI,CAAC,KAAK,qBAAqB;AAC7B,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,MAAI,CAAC,KAAK,aAAa;AACrB,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,OAAO,MAAM,OAAO,MAAM,OAAO,WAAW;AAClD,MAAI;AACF,UAAM,SAAS,MAAM,OAAO,KAAK,oBAAoB,aAAa,KAAK,IAAI,IAAI;AAC/E,UAAM,kBAAkB,QAAQ,aAAa,KAAK,IAAI,aAAa;AACnE,WAAO;AAAA,EACT,SAAS,OAAO;AACd,UAAM,OAAO,MAAM,OAAO,aAAa,KAAK,EAAE,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAC9D,UAAM;AAAA,EACR;AACF;","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 256 bytes of each .so file, keyed by ZIP entry path. */
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
- declare function commitWithRescue(client: PlayApiClient, packageName: string, editId: string, commitOptions?: EditCommitOptions): Promise<void>;
1573
- declare function validateAndCommit(client: PlayApiClient, packageName: string, editId: string, commitOptions?: EditCommitOptions): Promise<void>;
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 };