@gpc-cli/core 0.9.31 → 0.9.32

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/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { OutputFormat, ResolvedConfig, WebhookConfig } from '@gpc-cli/config';
2
2
  import { AuthClient } from '@gpc-cli/auth';
3
3
  import { GpcPlugin, PluginManifest, CommandEvent, CommandResult, PluginError, RequestEvent, ResponseEvent, PluginCommand } from '@gpc-cli/plugin-sdk';
4
- import { PlayApiClient, Track, ExternallyHostedApk, ExternallyHostedApkResponse, Listing, ImageType, CountryAvailability, Image, AppDetails, Review, ReviewReplyResponse, Subscription, SubscriptionOffer, OffersListResponse, BasePlanMigratePricesRequest, InAppProduct, SubscriptionDeferResponse, ProductPurchase, SubscriptionPurchaseV2, VoidedPurchase, UsersApiClient, User, DeveloperPermission, Grant, MetricRow, ReportingDimension, ReportingAggregation, VitalsMetricSet, ReportingApiClient, AnomalyDetectionResponse, MetricSetResponse, ErrorIssuesResponse, ConvertRegionPricesResponse, ReportType, StatsDimension, ReportBucket, Testers, AppRecoveryTargeting, AppRecoveryAction, CreateAppRecoveryActionRequest, DataSafety, ExternalTransaction, ExternalTransactionRefund, DeviceTierConfig, OneTimeOffer, OneTimeProduct, OneTimeOffersListResponse, OneTimeProductsListResponse, GamesApiClient, Achievement, GameEvent, Leaderboard, EnterpriseApiClient, CustomApp, GeneratedApk, PurchaseOption, PurchaseOptionsListResponse } from '@gpc-cli/api';
4
+ import { PlayApiClient, Track, ExternallyHostedApk, ExternallyHostedApkResponse, UploadProgressEvent, ResumableUploadOptions, Listing, ImageType, CountryAvailability, Image, AppDetails, Review, ReviewReplyResponse, Subscription, SubscriptionOffer, OffersListResponse, BasePlanMigratePricesRequest, InAppProduct, SubscriptionDeferResponse, ProductPurchase, SubscriptionPurchaseV2, VoidedPurchase, UsersApiClient, User, DeveloperPermission, Grant, MetricRow, ReportingDimension, ReportingAggregation, VitalsMetricSet, ReportingApiClient, AnomalyDetectionResponse, MetricSetResponse, ErrorIssuesResponse, ConvertRegionPricesResponse, ReportType, StatsDimension, ReportBucket, Testers, AppRecoveryTargeting, AppRecoveryAction, CreateAppRecoveryActionRequest, DataSafety, ExternalTransaction, ExternalTransactionRefund, DeviceTierConfig, OneTimeOffer, OneTimeProduct, OneTimeOffersListResponse, OneTimeProductsListResponse, GamesApiClient, Achievement, GameEvent, Leaderboard, EnterpriseApiClient, CustomApp, GeneratedApk, PurchaseOption, PurchaseOptionsListResponse } from '@gpc-cli/api';
5
5
 
6
6
  declare class GpcError extends Error {
7
7
  readonly code: string;
@@ -155,6 +155,8 @@ declare function uploadRelease(client: PlayApiClient, packageName: string, fileP
155
155
  mappingFile?: string;
156
156
  dryRun?: boolean;
157
157
  onProgress?: (uploaded: number, total: number) => void;
158
+ onUploadProgress?: (event: UploadProgressEvent) => void;
159
+ uploadOptions?: Pick<ResumableUploadOptions, "chunkSize" | "resumeSessionUri" | "maxResumeAttempts">;
158
160
  }): Promise<UploadResult | DryRunUploadResult>;
159
161
  declare function getReleasesStatus(client: PlayApiClient, packageName: string, trackFilter?: string): Promise<ReleaseStatusResult[]>;
160
162
  declare function promoteRelease(client: PlayApiClient, packageName: string, fromTrack: string, toTrack: string, options?: {
package/dist/index.js CHANGED
@@ -563,11 +563,11 @@ import { stat as stat2 } from "fs/promises";
563
563
  import { PlayApiError } from "@gpc-cli/api";
564
564
 
565
565
  // src/utils/file-validation.ts
566
- import { readFile, stat } from "fs/promises";
566
+ import { open, stat } from "fs/promises";
567
567
  import { extname } from "path";
568
568
  var ZIP_MAGIC = Buffer.from([80, 75, 3, 4]);
569
- var MAX_APK_SIZE = 150 * 1024 * 1024;
570
- var MAX_AAB_SIZE = 500 * 1024 * 1024;
569
+ var MAX_APK_SIZE = 1024 * 1024 * 1024;
570
+ var MAX_AAB_SIZE = 2 * 1024 * 1024 * 1024;
571
571
  var LARGE_FILE_THRESHOLD = 100 * 1024 * 1024;
572
572
  async function validateUploadFile(filePath) {
573
573
  const errors = [];
@@ -594,11 +594,11 @@ async function validateUploadFile(filePath) {
594
594
  }
595
595
  if (fileType === "apk" && sizeBytes > MAX_APK_SIZE) {
596
596
  errors.push(
597
- `APK exceeds 150 MB limit (${formatSize(sizeBytes)}). Consider using AAB format instead.`
597
+ `APK exceeds 1 GB limit (${formatSize(sizeBytes)}). Consider using AAB format instead.`
598
598
  );
599
599
  }
600
600
  if (fileType === "aab" && sizeBytes > MAX_AAB_SIZE) {
601
- errors.push(`AAB exceeds 500 MB limit (${formatSize(sizeBytes)}).`);
601
+ errors.push(`AAB exceeds 2 GB limit (${formatSize(sizeBytes)}).`);
602
602
  }
603
603
  if (sizeBytes > LARGE_FILE_THRESHOLD && errors.length === 0) {
604
604
  warnings.push(
@@ -606,16 +606,20 @@ async function validateUploadFile(filePath) {
606
606
  );
607
607
  }
608
608
  if (sizeBytes > 0) {
609
+ let fh;
609
610
  try {
610
- const fd = await readFile(filePath, { flag: "r" });
611
- const header = fd.subarray(0, 4);
612
- if (!header.equals(ZIP_MAGIC)) {
611
+ fh = await open(filePath, "r");
612
+ const buf = Buffer.alloc(4);
613
+ await fh.read(buf, 0, 4, 0);
614
+ if (!buf.equals(ZIP_MAGIC)) {
613
615
  errors.push(
614
616
  "File does not have valid ZIP magic bytes (PK\\x03\\x04). Both AAB and APK files must be valid ZIP archives."
615
617
  );
616
618
  }
617
619
  } catch {
618
620
  errors.push("Unable to read file header for validation");
621
+ } finally {
622
+ await fh?.close();
619
623
  }
620
624
  }
621
625
  return {
@@ -640,6 +644,18 @@ function formatSize(bytes) {
640
644
  }
641
645
 
642
646
  // src/commands/releases.ts
647
+ function warnIfEditExpiring(edit) {
648
+ if (!edit.expiryTimeSeconds) return;
649
+ const expiryMs = Number(edit.expiryTimeSeconds) * 1e3;
650
+ const remainingMs = expiryMs - Date.now();
651
+ if (remainingMs < 5 * 60 * 1e3 && remainingMs > 0) {
652
+ const minutes = Math.round(remainingMs / 6e4);
653
+ process.emitWarning?.(
654
+ `Edit session expires in ~${minutes} minute${minutes !== 1 ? "s" : ""}. Long uploads may fail. Consider starting a fresh operation.`,
655
+ "EditExpiryWarning"
656
+ );
657
+ }
658
+ }
643
659
  async function uploadRelease(client, packageName, filePath, options) {
644
660
  const validation = await validateUploadFile(filePath);
645
661
  if (options.dryRun) {
@@ -691,9 +707,15 @@ ${validation.errors.join("\n")}`,
691
707
  }
692
708
  if (options.onProgress) options.onProgress(0, fileSize);
693
709
  const edit = await client.edits.insert(packageName);
710
+ warnIfEditExpiring(edit);
694
711
  try {
695
- const bundle = await client.bundles.upload(packageName, edit.id, filePath);
696
- if (options.onProgress) options.onProgress(fileSize, fileSize);
712
+ const bundle = await client.bundles.upload(packageName, edit.id, filePath, {
713
+ ...options.uploadOptions,
714
+ onProgress: (event) => {
715
+ if (options.onProgress) options.onProgress(event.bytesUploaded, event.totalBytes);
716
+ if (options.onUploadProgress) options.onUploadProgress(event);
717
+ }
718
+ });
697
719
  if (options.mappingFile) {
698
720
  await client.deobfuscation.upload(
699
721
  packageName,
@@ -1132,7 +1154,7 @@ function formatSize2(bytes) {
1132
1154
  }
1133
1155
 
1134
1156
  // src/utils/fastlane.ts
1135
- import { readFile as readFile2, writeFile, mkdir, readdir, stat as stat4 } from "fs/promises";
1157
+ import { readFile, writeFile, mkdir, readdir, stat as stat4 } from "fs/promises";
1136
1158
  import { join } from "path";
1137
1159
  var FILE_MAP = {
1138
1160
  "title.txt": "title",
@@ -1170,7 +1192,7 @@ async function readListingsFromDir(dir) {
1170
1192
  for (const [fileName, field] of Object.entries(FILE_MAP)) {
1171
1193
  const filePath = join(langDir, fileName);
1172
1194
  if (await exists(filePath)) {
1173
- const content = await readFile2(filePath, "utf-8");
1195
+ const content = await readFile(filePath, "utf-8");
1174
1196
  listing[field] = content.trimEnd();
1175
1197
  }
1176
1198
  }
@@ -1639,7 +1661,7 @@ async function updateAppDetails(client, packageName, details) {
1639
1661
  }
1640
1662
 
1641
1663
  // src/commands/migrate.ts
1642
- import { readdir as readdir2, readFile as readFile3, writeFile as writeFile2, mkdir as mkdir2, access } from "fs/promises";
1664
+ import { readdir as readdir2, readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, access } from "fs/promises";
1643
1665
  import { join as join2 } from "path";
1644
1666
  var COMPLEX_RUBY_RE = /\b(begin|rescue|ensure|if |unless |case |while |until |for )\b/;
1645
1667
  async function fileExists(path) {
@@ -1679,7 +1701,7 @@ async function detectFastlane(cwd) {
1679
1701
  }
1680
1702
  if (result.hasFastfile) {
1681
1703
  try {
1682
- const content = await readFile3(fastfilePath, "utf-8");
1704
+ const content = await readFile2(fastfilePath, "utf-8");
1683
1705
  result.lanes = parseFastfile(content);
1684
1706
  if (COMPLEX_RUBY_RE.test(content)) {
1685
1707
  result.parseWarnings.push(
@@ -1692,7 +1714,7 @@ async function detectFastlane(cwd) {
1692
1714
  }
1693
1715
  if (result.hasAppfile) {
1694
1716
  try {
1695
- const content = await readFile3(appfilePath, "utf-8");
1717
+ const content = await readFile2(appfilePath, "utf-8");
1696
1718
  const parsed = parseAppfile(content);
1697
1719
  result.packageName = parsed.packageName;
1698
1720
  result.jsonKeyPath = parsed.jsonKeyPath;
@@ -1923,7 +1945,7 @@ function validateSku(sku) {
1923
1945
  }
1924
1946
 
1925
1947
  // src/utils/release-notes.ts
1926
- import { readdir as readdir3, readFile as readFile4, stat as stat5 } from "fs/promises";
1948
+ import { readdir as readdir3, readFile as readFile3, stat as stat5 } from "fs/promises";
1927
1949
  import { extname as extname3, basename, join as join3 } from "path";
1928
1950
  var MAX_NOTES_LENGTH = 500;
1929
1951
  async function readReleaseNotesFromDir(dir) {
@@ -1945,7 +1967,7 @@ async function readReleaseNotesFromDir(dir) {
1945
1967
  const filePath = join3(dir, entry);
1946
1968
  const stats = await stat5(filePath);
1947
1969
  if (!stats.isFile()) continue;
1948
- const text = (await readFile4(filePath, "utf-8")).trim();
1970
+ const text = (await readFile3(filePath, "utf-8")).trim();
1949
1971
  if (text.length === 0) continue;
1950
1972
  notes.push({ language, text });
1951
1973
  }
@@ -2770,8 +2792,8 @@ var METRIC_SET_METRICS = {
2770
2792
  slowStartRateMetricSet: ["slowStartRate", "distinctUsers"],
2771
2793
  slowRenderingRateMetricSet: ["slowRenderingRate", "distinctUsers"],
2772
2794
  excessiveWakeupRateMetricSet: ["excessiveWakeupRate", "distinctUsers"],
2773
- // API requires the weighted variants — base `stuckBackgroundWakelockRate` is not a valid metric
2774
2795
  stuckBackgroundWakelockRateMetricSet: [
2796
+ "stuckBackgroundWakelockRate",
2775
2797
  "stuckBackgroundWakelockRate7dUserWeighted",
2776
2798
  "stuckBackgroundWakelockRate28dUserWeighted",
2777
2799
  "distinctUsers"
@@ -2841,7 +2863,8 @@ async function getVitalsAnr(reporting, packageName, options) {
2841
2863
  return queryMetric(reporting, packageName, "anrRateMetricSet", options);
2842
2864
  }
2843
2865
  async function getVitalsStartup(reporting, packageName, options) {
2844
- return queryMetric(reporting, packageName, "slowStartRateMetricSet", options);
2866
+ const opts = options?.dimension ? options : { ...options, dimension: "startType" };
2867
+ return queryMetric(reporting, packageName, "slowStartRateMetricSet", opts);
2845
2868
  }
2846
2869
  async function getVitalsRendering(reporting, packageName, options) {
2847
2870
  return queryMetric(reporting, packageName, "slowRenderingRateMetricSet", options);
@@ -3013,7 +3036,7 @@ function watchVitalsWithAutoHalt(reporting, packageName, options) {
3013
3036
  }
3014
3037
 
3015
3038
  // src/commands/iap.ts
3016
- import { readdir as readdir4, readFile as readFile5 } from "fs/promises";
3039
+ import { readdir as readdir4, readFile as readFile4 } from "fs/promises";
3017
3040
  import { join as join4 } from "path";
3018
3041
  import { paginateAll as paginateAll3 } from "@gpc-cli/api";
3019
3042
  async function listInAppProducts(client, packageName, options) {
@@ -3103,7 +3126,7 @@ async function readProductsFromDir(dir) {
3103
3126
  const jsonFiles = files.filter((f) => f.endsWith(".json"));
3104
3127
  const localProducts = [];
3105
3128
  for (const file of jsonFiles) {
3106
- const content = await readFile5(join4(dir, file), "utf-8");
3129
+ const content = await readFile4(join4(dir, file), "utf-8");
3107
3130
  try {
3108
3131
  localProducts.push(JSON.parse(content));
3109
3132
  } catch {
@@ -3409,7 +3432,7 @@ async function deleteGrant(client, developerId, email, packageName) {
3409
3432
  }
3410
3433
 
3411
3434
  // src/commands/testers.ts
3412
- import { readFile as readFile6 } from "fs/promises";
3435
+ import { readFile as readFile5 } from "fs/promises";
3413
3436
  async function listTesters(client, packageName, track) {
3414
3437
  const edit = await client.edits.insert(packageName);
3415
3438
  try {
@@ -3458,7 +3481,7 @@ async function removeTesters(client, packageName, track, groupEmails) {
3458
3481
  }
3459
3482
  }
3460
3483
  async function importTestersFromCsv(client, packageName, track, csvPath) {
3461
- const content = await readFile6(csvPath, "utf-8");
3484
+ const content = await readFile5(csvPath, "utf-8");
3462
3485
  const emails = content.split(/[,\n\r]+/).map((e) => e.trim()).filter((e) => e.length > 0 && e.includes("@"));
3463
3486
  if (emails.length === 0) {
3464
3487
  throw new GpcError(
@@ -3601,7 +3624,7 @@ async function addRecoveryTargeting(client, packageName, actionId, targeting) {
3601
3624
  }
3602
3625
 
3603
3626
  // src/commands/data-safety.ts
3604
- import { readFile as readFile7, writeFile as writeFile3 } from "fs/promises";
3627
+ import { readFile as readFile6, writeFile as writeFile3 } from "fs/promises";
3605
3628
  async function getDataSafety(client, packageName) {
3606
3629
  return client.dataSafety.get(packageName);
3607
3630
  }
@@ -3614,7 +3637,7 @@ async function exportDataSafety(client, packageName, outputPath) {
3614
3637
  return dataSafety;
3615
3638
  }
3616
3639
  async function importDataSafety(client, packageName, filePath) {
3617
- const content = await readFile7(filePath, "utf-8");
3640
+ const content = await readFile6(filePath, "utf-8");
3618
3641
  let data;
3619
3642
  try {
3620
3643
  data = JSON.parse(content);
@@ -3911,7 +3934,7 @@ function createSpinner(message) {
3911
3934
  }
3912
3935
 
3913
3936
  // src/utils/train-state.ts
3914
- import { mkdir as mkdir3, readFile as readFile8, writeFile as writeFile4 } from "fs/promises";
3937
+ import { mkdir as mkdir3, readFile as readFile7, writeFile as writeFile4 } from "fs/promises";
3915
3938
  import { join as join5 } from "path";
3916
3939
  import { getCacheDir } from "@gpc-cli/config";
3917
3940
  function stateFile(packageName) {
@@ -3920,7 +3943,7 @@ function stateFile(packageName) {
3920
3943
  async function readTrainState(packageName) {
3921
3944
  const path = stateFile(packageName);
3922
3945
  try {
3923
- const raw = await readFile8(path, "utf-8");
3946
+ const raw = await readFile7(path, "utf-8");
3924
3947
  return JSON.parse(raw);
3925
3948
  } catch {
3926
3949
  return null;
@@ -4085,7 +4108,7 @@ async function createEnterpriseApp(client, organizationId, app) {
4085
4108
  }
4086
4109
 
4087
4110
  // src/audit.ts
4088
- import { appendFile, chmod, mkdir as mkdir4, readFile as readFile9, writeFile as writeFile5 } from "fs/promises";
4111
+ import { appendFile, chmod, mkdir as mkdir4, readFile as readFile8, writeFile as writeFile5 } from "fs/promises";
4089
4112
  import { join as join6 } from "path";
4090
4113
  var auditDir = null;
4091
4114
  function initAudit(configDir) {
@@ -4153,7 +4176,7 @@ async function listAuditEvents(options) {
4153
4176
  const logPath = join6(auditDir, "audit.log");
4154
4177
  let content;
4155
4178
  try {
4156
- content = await readFile9(logPath, "utf-8");
4179
+ content = await readFile8(logPath, "utf-8");
4157
4180
  } catch {
4158
4181
  return [];
4159
4182
  }
@@ -4191,7 +4214,7 @@ async function clearAuditLog(options) {
4191
4214
  const logPath = join6(auditDir, "audit.log");
4192
4215
  let content;
4193
4216
  try {
4194
- content = await readFile9(logPath, "utf-8");
4217
+ content = await readFile8(logPath, "utf-8");
4195
4218
  } catch {
4196
4219
  return { deleted: 0, remaining: 0 };
4197
4220
  }
@@ -4684,7 +4707,7 @@ async function deactivatePurchaseOption(client, packageName, purchaseOptionId) {
4684
4707
  }
4685
4708
 
4686
4709
  // src/commands/bundle-analysis.ts
4687
- import { readFile as readFile10, stat as stat7 } from "fs/promises";
4710
+ import { readFile as readFile9, stat as stat7 } from "fs/promises";
4688
4711
  var EOCD_SIGNATURE = 101010256;
4689
4712
  var CD_SIGNATURE = 33639248;
4690
4713
  var MODULE_SUBDIRS = /* @__PURE__ */ new Set(["dex", "manifest", "res", "assets", "lib", "resources.pb", "root"]);
@@ -4764,7 +4787,7 @@ async function analyzeBundle(filePath) {
4764
4787
  if (!fileInfo || !fileInfo.isFile()) {
4765
4788
  throw new Error(`File not found: ${filePath}`);
4766
4789
  }
4767
- const buf = await readFile10(filePath);
4790
+ const buf = await readFile9(filePath);
4768
4791
  const cdEntries = parseCentralDirectory(buf);
4769
4792
  const fileType = detectFileType2(filePath);
4770
4793
  const isAab = fileType === "aab";
@@ -4850,7 +4873,7 @@ function topFiles(analysis, n = 20) {
4850
4873
  async function checkBundleSize(analysis, configPath = ".bundlesize.json") {
4851
4874
  let config;
4852
4875
  try {
4853
- const raw = await readFile10(configPath, "utf-8");
4876
+ const raw = await readFile9(configPath, "utf-8");
4854
4877
  config = JSON.parse(raw);
4855
4878
  } catch {
4856
4879
  return { passed: true, violations: [] };
@@ -4879,7 +4902,7 @@ async function checkBundleSize(analysis, configPath = ".bundlesize.json") {
4879
4902
  }
4880
4903
 
4881
4904
  // src/commands/status.ts
4882
- import { mkdir as mkdir6, readFile as readFile11, writeFile as writeFile8 } from "fs/promises";
4905
+ import { mkdir as mkdir6, readFile as readFile10, writeFile as writeFile8 } from "fs/promises";
4883
4906
  import { execFile as execFile2 } from "child_process";
4884
4907
  import { join as join8 } from "path";
4885
4908
  import { getCacheDir as getCacheDir2 } from "@gpc-cli/config";
@@ -4889,7 +4912,7 @@ function cacheFilePath(packageName) {
4889
4912
  }
4890
4913
  async function loadStatusCache(packageName, ttlSeconds = DEFAULT_TTL_SECONDS) {
4891
4914
  try {
4892
- const raw = await readFile11(cacheFilePath(packageName), "utf-8");
4915
+ const raw = await readFile10(cacheFilePath(packageName), "utf-8");
4893
4916
  const entry = JSON.parse(raw);
4894
4917
  const age = (Date.now() - new Date(entry.fetchedAt).getTime()) / 1e3;
4895
4918
  if (age > (entry.ttl ?? ttlSeconds)) return null;
@@ -5290,7 +5313,7 @@ async function trackBreachState(packageName, isBreaching) {
5290
5313
  const filePath = breachStateFilePath(packageName);
5291
5314
  let prevBreaching = false;
5292
5315
  try {
5293
- const raw = await readFile11(filePath, "utf-8");
5316
+ const raw = await readFile10(filePath, "utf-8");
5294
5317
  prevBreaching = JSON.parse(raw).breaching;
5295
5318
  } catch {
5296
5319
  }