@gpc-cli/core 0.9.58 → 0.9.60

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.js CHANGED
@@ -4,19 +4,25 @@ import {
4
4
  GpcError,
5
5
  NetworkError,
6
6
  applyReleaseNotes,
7
+ commitWithRescue,
7
8
  createTrack,
8
9
  diffReleases,
9
10
  fetchReleaseNotes,
10
11
  getReleasesStatus,
12
+ isVersionedNotesDir,
11
13
  listTracks,
12
14
  promoteRelease,
15
+ readReleaseNotesForVersion,
16
+ readReleaseNotesFromDir,
13
17
  updateRollout,
14
18
  updateTrackConfig,
15
19
  uploadExternallyHosted,
16
20
  uploadRelease,
21
+ validateAndCommit,
22
+ validateReleaseNotes,
17
23
  validateUploadFile,
18
24
  waitForBundleProcessing
19
- } from "./chunk-QIYAOW6R.js";
25
+ } from "./chunk-3AUJEAXP.js";
20
26
 
21
27
  // src/output.ts
22
28
  import process2 from "process";
@@ -915,10 +921,7 @@ async function updateListing(client, packageName, language, data, commitOptions)
915
921
  const edit = await client.edits.insert(packageName);
916
922
  try {
917
923
  const listing = await client.listings.patch(packageName, edit.id, language, data);
918
- if (!commitOptions?.changesNotSentForReview) {
919
- await client.edits.validate(packageName, edit.id);
920
- }
921
- await client.edits.commit(packageName, edit.id, commitOptions);
924
+ await validateAndCommit(client, packageName, edit.id, commitOptions);
922
925
  return listing;
923
926
  } catch (error) {
924
927
  await client.edits.delete(packageName, edit.id).catch(() => {
@@ -931,10 +934,7 @@ async function deleteListing(client, packageName, language, commitOptions) {
931
934
  const edit = await client.edits.insert(packageName);
932
935
  try {
933
936
  await client.listings.delete(packageName, edit.id, language);
934
- if (!commitOptions?.changesNotSentForReview) {
935
- await client.edits.validate(packageName, edit.id);
936
- }
937
- await client.edits.commit(packageName, edit.id, commitOptions);
937
+ await validateAndCommit(client, packageName, edit.id, commitOptions);
938
938
  } catch (error) {
939
939
  await client.edits.delete(packageName, edit.id).catch(() => {
940
940
  });
@@ -1057,10 +1057,7 @@ ${details}`,
1057
1057
  const { language, ...data } = listing;
1058
1058
  await client.listings.update(packageName, edit.id, language, data);
1059
1059
  }
1060
- if (!options?.commitOptions?.changesNotSentForReview) {
1061
- await client.edits.validate(packageName, edit.id);
1062
- }
1063
- await client.edits.commit(packageName, edit.id, options?.commitOptions);
1060
+ await validateAndCommit(client, packageName, edit.id, options?.commitOptions);
1064
1061
  return {
1065
1062
  updated: localListings.length,
1066
1063
  languages: localListings.map((l) => l.language)
@@ -1101,10 +1098,7 @@ async function uploadImage(client, packageName, language, imageType, filePath, c
1101
1098
  const edit = await client.edits.insert(packageName);
1102
1099
  try {
1103
1100
  const image = await client.images.upload(packageName, edit.id, language, imageType, filePath);
1104
- if (!commitOptions?.changesNotSentForReview) {
1105
- await client.edits.validate(packageName, edit.id);
1106
- }
1107
- await client.edits.commit(packageName, edit.id, commitOptions);
1101
+ await validateAndCommit(client, packageName, edit.id, commitOptions);
1108
1102
  return image;
1109
1103
  } catch (error) {
1110
1104
  await client.edits.delete(packageName, edit.id).catch(() => {
@@ -1117,10 +1111,7 @@ async function deleteImage(client, packageName, language, imageType, imageId, co
1117
1111
  const edit = await client.edits.insert(packageName);
1118
1112
  try {
1119
1113
  await client.images.delete(packageName, edit.id, language, imageType, imageId);
1120
- if (!commitOptions?.changesNotSentForReview) {
1121
- await client.edits.validate(packageName, edit.id);
1122
- }
1123
- await client.edits.commit(packageName, edit.id, commitOptions);
1114
+ await validateAndCommit(client, packageName, edit.id, commitOptions);
1124
1115
  } catch (error) {
1125
1116
  await client.edits.delete(packageName, edit.id).catch(() => {
1126
1117
  });
@@ -1233,10 +1224,7 @@ async function updateAppDetails(client, packageName, details, commitOptions) {
1233
1224
  const edit = await client.edits.insert(packageName);
1234
1225
  try {
1235
1226
  const result = await client.details.patch(packageName, edit.id, details);
1236
- if (!commitOptions?.changesNotSentForReview) {
1237
- await client.edits.validate(packageName, edit.id);
1238
- }
1239
- await client.edits.commit(packageName, edit.id, commitOptions);
1227
+ await validateAndCommit(client, packageName, edit.id, commitOptions);
1240
1228
  return result;
1241
1229
  } catch (error) {
1242
1230
  await client.edits.delete(packageName, edit.id).catch(() => {
@@ -1529,55 +1517,8 @@ function validateSku(sku) {
1529
1517
  }
1530
1518
  }
1531
1519
 
1532
- // src/utils/release-notes.ts
1533
- import { readdir as readdir3, readFile as readFile3, stat as stat3 } from "fs/promises";
1534
- import { extname as extname2, basename, join as join3 } from "path";
1535
- var MAX_NOTES_LENGTH = 500;
1536
- async function readReleaseNotesFromDir(dir) {
1537
- let entries;
1538
- try {
1539
- entries = await readdir3(dir);
1540
- } catch {
1541
- throw new GpcError(
1542
- `Release notes directory not found: ${dir}`,
1543
- "RELEASE_NOTES_DIR_NOT_FOUND",
1544
- 1,
1545
- `Create the directory and add .txt files named by language code (e.g., en-US.txt). Path: ${dir}`
1546
- );
1547
- }
1548
- const notes = [];
1549
- for (const entry of entries) {
1550
- if (extname2(entry) !== ".txt") continue;
1551
- const language = basename(entry, ".txt");
1552
- const filePath = join3(dir, entry);
1553
- const stats = await stat3(filePath);
1554
- if (!stats.isFile()) continue;
1555
- const text = (await readFile3(filePath, "utf-8")).trim();
1556
- if (text.length === 0) continue;
1557
- notes.push({ language, text });
1558
- }
1559
- return notes;
1560
- }
1561
- function validateReleaseNotes(notes) {
1562
- const errors = [];
1563
- const warnings = [];
1564
- const seen = /* @__PURE__ */ new Set();
1565
- for (const note of notes) {
1566
- if (seen.has(note.language)) {
1567
- errors.push(`Duplicate language code: ${note.language}`);
1568
- }
1569
- seen.add(note.language);
1570
- if (note.text.length > MAX_NOTES_LENGTH) {
1571
- warnings.push(
1572
- `Release notes for "${note.language}" are ${note.text.length} chars (max ${MAX_NOTES_LENGTH}) \u2014 Google Play will reject notes exceeding this limit`
1573
- );
1574
- }
1575
- }
1576
- return { valid: errors.length === 0, errors, warnings };
1577
- }
1578
-
1579
1520
  // src/commands/validate.ts
1580
- import { stat as stat4 } from "fs/promises";
1521
+ import { stat as stat3 } from "fs/promises";
1581
1522
  var STANDARD_TRACKS = /* @__PURE__ */ new Set([
1582
1523
  "internal",
1583
1524
  "qa",
@@ -1621,7 +1562,7 @@ async function validatePreSubmission(options) {
1621
1562
  }
1622
1563
  if (options.mappingFile) {
1623
1564
  try {
1624
- const stats = await stat4(options.mappingFile);
1565
+ const stats = await stat3(options.mappingFile);
1625
1566
  checks.push({
1626
1567
  name: "mapping",
1627
1568
  passed: stats.isFile(),
@@ -2420,12 +2361,28 @@ var METRIC_SET_METRICS = {
2420
2361
  ],
2421
2362
  errorCountMetricSet: ["errorReportCount", "distinctUsers"]
2422
2363
  };
2423
- function buildQuery(metricSet, options) {
2364
+ async function getFreshnessEndDate(reporting, packageName, metricSet, aggregation = "DAILY") {
2365
+ try {
2366
+ const info = await reporting.getMetricSetFreshness(packageName, metricSet);
2367
+ const match = info.freshnessInfo?.freshnesses?.find(
2368
+ (f) => f.aggregationPeriod === aggregation
2369
+ );
2370
+ if (match) {
2371
+ return new Date(
2372
+ Date.UTC(match.latestEndTime.year, match.latestEndTime.month - 1, match.latestEndTime.day)
2373
+ );
2374
+ }
2375
+ } catch {
2376
+ }
2377
+ return void 0;
2378
+ }
2379
+ function buildQuery(metricSet, options, freshnessEnd) {
2424
2380
  const metrics = METRIC_SET_METRICS[metricSet] ?? ["errorReportCount", "distinctUsers"];
2425
2381
  const days = options?.days ?? 30;
2426
2382
  const DAY_MS = 24 * 60 * 60 * 1e3;
2427
- const end = new Date(Date.now() - DAY_MS);
2428
- const start = new Date(Date.now() - DAY_MS - days * DAY_MS);
2383
+ const yesterday = new Date(Date.now() - DAY_MS);
2384
+ const end = freshnessEnd && freshnessEnd < yesterday ? freshnessEnd : yesterday;
2385
+ const start = new Date(end.getTime() - days * DAY_MS);
2429
2386
  const query = {
2430
2387
  metrics,
2431
2388
  timelineSpec: {
@@ -2448,7 +2405,13 @@ function buildQuery(metricSet, options) {
2448
2405
  return query;
2449
2406
  }
2450
2407
  async function queryMetric(reporting, packageName, metricSet, options) {
2451
- const query = buildQuery(metricSet, options);
2408
+ const freshnessEnd = await getFreshnessEndDate(
2409
+ reporting,
2410
+ packageName,
2411
+ metricSet,
2412
+ options?.aggregation
2413
+ );
2414
+ const query = buildQuery(metricSet, options, freshnessEnd);
2452
2415
  return reporting.queryMetricSet(packageName, metricSet, query);
2453
2416
  }
2454
2417
  async function getVitalsOverview(reporting, packageName) {
@@ -2460,8 +2423,15 @@ async function getVitalsOverview(reporting, packageName) {
2460
2423
  ["excessiveWakeupRateMetricSet", "excessiveWakeupRate"],
2461
2424
  ["stuckBackgroundWakelockRateMetricSet", "stuckWakelockRate"]
2462
2425
  ];
2426
+ const freshnessResults = await Promise.allSettled(
2427
+ metricSets.map(([metric]) => getFreshnessEndDate(reporting, packageName, metric))
2428
+ );
2463
2429
  const results = await Promise.allSettled(
2464
- metricSets.map(([metric]) => reporting.queryMetricSet(packageName, metric, buildQuery(metric)))
2430
+ metricSets.map(([metric], i) => {
2431
+ const fr = freshnessResults[i];
2432
+ const freshnessEnd = fr?.status === "fulfilled" ? fr.value : void 0;
2433
+ return reporting.queryMetricSet(packageName, metric, buildQuery(metric, void 0, freshnessEnd));
2434
+ })
2465
2435
  );
2466
2436
  const overview = {};
2467
2437
  for (let i = 0; i < metricSets.length; i++) {
@@ -2514,7 +2484,9 @@ async function searchVitalsErrors(reporting, packageName, options) {
2514
2484
  async function compareVitalsTrend(reporting, packageName, metricSet, days = 7) {
2515
2485
  const DAY_MS = 24 * 60 * 60 * 1e3;
2516
2486
  const nowMs = Date.now();
2517
- const baseMs = nowMs - 2 * DAY_MS;
2487
+ const freshnessEnd = await getFreshnessEndDate(reporting, packageName, metricSet);
2488
+ const fallback = nowMs - 2 * DAY_MS;
2489
+ const baseMs = freshnessEnd ? Math.min(freshnessEnd.getTime(), fallback) : fallback;
2518
2490
  const currentEnd = new Date(baseMs);
2519
2491
  const currentStart = new Date(baseMs - days * DAY_MS);
2520
2492
  const previousEnd = new Date(baseMs - days * DAY_MS - DAY_MS);
@@ -2660,17 +2632,17 @@ function watchVitalsWithAutoHalt(reporting, packageName, options) {
2660
2632
  }
2661
2633
 
2662
2634
  // src/commands/status.ts
2663
- import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
2635
+ import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
2664
2636
  import { execFile } from "child_process";
2665
- import { join as join4 } from "path";
2637
+ import { join as join3 } from "path";
2666
2638
  import { getCacheDir } from "@gpc-cli/config";
2667
2639
  var DEFAULT_TTL_SECONDS = 3600;
2668
2640
  function cacheFilePath(packageName) {
2669
- return join4(getCacheDir(), `status-${packageName}.json`);
2641
+ return join3(getCacheDir(), `status-${packageName}.json`);
2670
2642
  }
2671
2643
  async function loadStatusCache(packageName, ttlSeconds = DEFAULT_TTL_SECONDS) {
2672
2644
  try {
2673
- const raw = await readFile4(cacheFilePath(packageName), "utf-8");
2645
+ const raw = await readFile3(cacheFilePath(packageName), "utf-8");
2674
2646
  const entry = JSON.parse(raw);
2675
2647
  const age = (Date.now() - new Date(entry.fetchedAt).getTime()) / 1e3;
2676
2648
  if (age > (entry.ttl ?? ttlSeconds)) return null;
@@ -3079,13 +3051,13 @@ async function runWatchLoop(opts) {
3079
3051
  }
3080
3052
  }
3081
3053
  function breachStateFilePath(packageName) {
3082
- return join4(getCacheDir(), `breach-state-${packageName}.json`);
3054
+ return join3(getCacheDir(), `breach-state-${packageName}.json`);
3083
3055
  }
3084
3056
  async function trackBreachState(packageName, isBreaching) {
3085
3057
  const filePath = breachStateFilePath(packageName);
3086
3058
  let prevBreaching = false;
3087
3059
  try {
3088
- const raw = await readFile4(filePath, "utf-8");
3060
+ const raw = await readFile3(filePath, "utf-8");
3089
3061
  prevBreaching = JSON.parse(raw).breaching;
3090
3062
  } catch {
3091
3063
  }
@@ -3238,7 +3210,7 @@ async function handleBreach(event, config, client) {
3238
3210
  }
3239
3211
  case "halt": {
3240
3212
  try {
3241
- const { updateRollout: updateRollout2 } = await import("./releases-I5MYFNCV.js");
3213
+ const { updateRollout: updateRollout2 } = await import("./releases-S54GLWH3.js");
3242
3214
  await updateRollout2(client, config.packageName, config.track, "halt");
3243
3215
  halted = true;
3244
3216
  } catch {
@@ -3354,8 +3326,8 @@ async function runWatch(client, reporting, config, callbacks) {
3354
3326
  }
3355
3327
 
3356
3328
  // src/commands/iap.ts
3357
- import { readdir as readdir4, readFile as readFile5 } from "fs/promises";
3358
- import { join as join5 } from "path";
3329
+ import { readdir as readdir3, readFile as readFile4 } from "fs/promises";
3330
+ import { join as join4 } from "path";
3359
3331
  import { paginateAll as paginateAll3 } from "@gpc-cli/api";
3360
3332
  async function listInAppProducts(client, packageName, options) {
3361
3333
  if (options?.limit || options?.nextPage) {
@@ -3440,11 +3412,11 @@ async function batchUpdateProducts(client, packageName, products) {
3440
3412
  return results;
3441
3413
  }
3442
3414
  async function readProductsFromDir(dir) {
3443
- const files = await readdir4(dir);
3415
+ const files = await readdir3(dir);
3444
3416
  const jsonFiles = files.filter((f) => f.endsWith(".json"));
3445
3417
  const localProducts = [];
3446
3418
  for (const file of jsonFiles) {
3447
- const content = await readFile5(join5(dir, file), "utf-8");
3419
+ const content = await readFile4(join4(dir, file), "utf-8");
3448
3420
  try {
3449
3421
  localProducts.push(JSON.parse(content));
3450
3422
  } catch {
@@ -3801,7 +3773,7 @@ async function deleteGrant(client, developerId, email, packageName) {
3801
3773
  }
3802
3774
 
3803
3775
  // src/commands/testers.ts
3804
- import { readFile as readFile6 } from "fs/promises";
3776
+ import { readFile as readFile5 } from "fs/promises";
3805
3777
  async function listTesters(client, packageName, track) {
3806
3778
  const edit = await client.edits.insert(packageName);
3807
3779
  try {
@@ -3822,10 +3794,7 @@ async function addTesters(client, packageName, track, groupEmails, commitOptions
3822
3794
  const updated = await client.testers.update(packageName, edit.id, track, {
3823
3795
  googleGroups: [...existing]
3824
3796
  });
3825
- if (!commitOptions?.changesNotSentForReview) {
3826
- await client.edits.validate(packageName, edit.id);
3827
- }
3828
- await client.edits.commit(packageName, edit.id, commitOptions);
3797
+ await validateAndCommit(client, packageName, edit.id, commitOptions);
3829
3798
  return updated;
3830
3799
  } catch (error) {
3831
3800
  await client.edits.delete(packageName, edit.id).catch(() => {
@@ -3842,10 +3811,7 @@ async function removeTesters(client, packageName, track, groupEmails, commitOpti
3842
3811
  const updated = await client.testers.update(packageName, edit.id, track, {
3843
3812
  googleGroups: filtered
3844
3813
  });
3845
- if (!commitOptions?.changesNotSentForReview) {
3846
- await client.edits.validate(packageName, edit.id);
3847
- }
3848
- await client.edits.commit(packageName, edit.id, commitOptions);
3814
+ await validateAndCommit(client, packageName, edit.id, commitOptions);
3849
3815
  return updated;
3850
3816
  } catch (error) {
3851
3817
  await client.edits.delete(packageName, edit.id).catch(() => {
@@ -3854,7 +3820,7 @@ async function removeTesters(client, packageName, track, groupEmails, commitOpti
3854
3820
  }
3855
3821
  }
3856
3822
  async function importTestersFromCsv(client, packageName, track, csvPath, commitOptions) {
3857
- const content = await readFile6(csvPath, "utf-8");
3823
+ const content = await readFile5(csvPath, "utf-8");
3858
3824
  const emails = content.split(/[,\n\r]+/).map((e) => e.trim()).filter((e) => e.length > 0 && e.includes("@"));
3859
3825
  if (emails.length === 0) {
3860
3826
  throw new GpcError(
@@ -3997,12 +3963,12 @@ async function addRecoveryTargeting(client, packageName, actionId, targeting) {
3997
3963
  }
3998
3964
 
3999
3965
  // src/commands/data-safety.ts
4000
- import { readFile as readFile7 } from "fs/promises";
3966
+ import { readFile as readFile6 } from "fs/promises";
4001
3967
  async function updateDataSafety(client, packageName, data) {
4002
3968
  return client.dataSafety.update(packageName, data);
4003
3969
  }
4004
3970
  async function importDataSafety(client, packageName, filePath) {
4005
- const content = await readFile7(filePath, "utf-8");
3971
+ const content = await readFile6(filePath, "utf-8");
4006
3972
  let data;
4007
3973
  try {
4008
3974
  data = JSON.parse(content);
@@ -4306,16 +4272,16 @@ function createSpinner(message) {
4306
4272
  }
4307
4273
 
4308
4274
  // src/utils/train-state.ts
4309
- import { mkdir as mkdir4, readFile as readFile8, writeFile as writeFile4 } from "fs/promises";
4310
- import { dirname, join as join6 } from "path";
4275
+ import { mkdir as mkdir4, readFile as readFile7, writeFile as writeFile4 } from "fs/promises";
4276
+ import { dirname, join as join5 } from "path";
4311
4277
  import { getCacheDir as getCacheDir2 } from "@gpc-cli/config";
4312
4278
  function stateFile(packageName) {
4313
- return join6(getCacheDir2(), `train-${packageName}.json`);
4279
+ return join5(getCacheDir2(), `train-${packageName}.json`);
4314
4280
  }
4315
4281
  async function readTrainState(packageName) {
4316
4282
  const path = stateFile(packageName);
4317
4283
  try {
4318
- const raw = await readFile8(path, "utf-8");
4284
+ const raw = await readFile7(path, "utf-8");
4319
4285
  return JSON.parse(raw);
4320
4286
  } catch {
4321
4287
  return null;
@@ -4495,8 +4461,8 @@ async function publishEnterpriseApp(client, params) {
4495
4461
  }
4496
4462
 
4497
4463
  // src/audit.ts
4498
- import { appendFile, chmod, mkdir as mkdir5, readFile as readFile9, writeFile as writeFile5 } from "fs/promises";
4499
- import { join as join7 } from "path";
4464
+ import { appendFile, chmod, mkdir as mkdir5, readFile as readFile8, writeFile as writeFile5 } from "fs/promises";
4465
+ import { join as join6 } from "path";
4500
4466
  var auditDir = null;
4501
4467
  function initAudit(configDir) {
4502
4468
  auditDir = configDir;
@@ -4505,7 +4471,7 @@ async function writeAuditLog(entry) {
4505
4471
  if (!auditDir) return;
4506
4472
  try {
4507
4473
  await mkdir5(auditDir, { recursive: true, mode: 448 });
4508
- const logPath = join7(auditDir, "audit.log");
4474
+ const logPath = join6(auditDir, "audit.log");
4509
4475
  const redactedEntry = redactAuditArgs(entry);
4510
4476
  const line = JSON.stringify(redactedEntry) + "\n";
4511
4477
  await appendFile(logPath, line, { encoding: "utf-8", mode: 384 });
@@ -4560,10 +4526,10 @@ function createAuditEntry(command, args, app) {
4560
4526
  }
4561
4527
  async function listAuditEvents(options) {
4562
4528
  if (!auditDir) return [];
4563
- const logPath = join7(auditDir, "audit.log");
4529
+ const logPath = join6(auditDir, "audit.log");
4564
4530
  let content;
4565
4531
  try {
4566
- content = await readFile9(logPath, "utf-8");
4532
+ content = await readFile8(logPath, "utf-8");
4567
4533
  } catch {
4568
4534
  return [];
4569
4535
  }
@@ -4598,10 +4564,10 @@ async function searchAuditEvents(query) {
4598
4564
  }
4599
4565
  async function clearAuditLog(options) {
4600
4566
  if (!auditDir) return { deleted: 0, remaining: 0 };
4601
- const logPath = join7(auditDir, "audit.log");
4567
+ const logPath = join6(auditDir, "audit.log");
4602
4568
  let content;
4603
4569
  try {
4604
- content = await readFile9(logPath, "utf-8");
4570
+ content = await readFile8(logPath, "utf-8");
4605
4571
  } catch {
4606
4572
  return { deleted: 0, remaining: 0 };
4607
4573
  }
@@ -4689,11 +4655,11 @@ function safePathWithin(userPath, baseDir) {
4689
4655
  }
4690
4656
 
4691
4657
  // src/commands/init.ts
4692
- import { mkdir as mkdir6, writeFile as writeFile6, stat as stat5 } from "fs/promises";
4693
- import { join as join8 } from "path";
4658
+ import { mkdir as mkdir6, writeFile as writeFile6, stat as stat4 } from "fs/promises";
4659
+ import { join as join7 } from "path";
4694
4660
  async function exists2(path) {
4695
4661
  try {
4696
- await stat5(path);
4662
+ await stat4(path);
4697
4663
  return true;
4698
4664
  } catch {
4699
4665
  return false;
@@ -4725,7 +4691,7 @@ async function initProject(options) {
4725
4691
  null,
4726
4692
  2
4727
4693
  );
4728
- await safeWrite(join8(dir, ".gpcrc.json"), gpcrc + "\n", created, skipped, skipExisting);
4694
+ await safeWrite(join7(dir, ".gpcrc.json"), gpcrc + "\n", created, skipped, skipExisting);
4729
4695
  const preflightrc = JSON.stringify(
4730
4696
  {
4731
4697
  failOn: "error",
@@ -4739,27 +4705,27 @@ async function initProject(options) {
4739
4705
  2
4740
4706
  );
4741
4707
  await safeWrite(
4742
- join8(dir, ".preflightrc.json"),
4708
+ join7(dir, ".preflightrc.json"),
4743
4709
  preflightrc + "\n",
4744
4710
  created,
4745
4711
  skipped,
4746
4712
  skipExisting
4747
4713
  );
4748
- const metaDir = join8(dir, "metadata", "android", "en-US");
4749
- await safeWrite(join8(metaDir, "title.txt"), "", created, skipped, skipExisting);
4750
- await safeWrite(join8(metaDir, "short_description.txt"), "", created, skipped, skipExisting);
4751
- await safeWrite(join8(metaDir, "full_description.txt"), "", created, skipped, skipExisting);
4752
- await safeWrite(join8(metaDir, "video.txt"), "", created, skipped, skipExisting);
4753
- const ssDir = join8(metaDir, "images", "phoneScreenshots");
4714
+ const metaDir = join7(dir, "metadata", "android", "en-US");
4715
+ await safeWrite(join7(metaDir, "title.txt"), "", created, skipped, skipExisting);
4716
+ await safeWrite(join7(metaDir, "short_description.txt"), "", created, skipped, skipExisting);
4717
+ await safeWrite(join7(metaDir, "full_description.txt"), "", created, skipped, skipExisting);
4718
+ await safeWrite(join7(metaDir, "video.txt"), "", created, skipped, skipExisting);
4719
+ const ssDir = join7(metaDir, "images", "phoneScreenshots");
4754
4720
  await mkdir6(ssDir, { recursive: true });
4755
- await safeWrite(join8(ssDir, ".gitkeep"), "", created, skipped, skipExisting);
4721
+ await safeWrite(join7(ssDir, ".gitkeep"), "", created, skipped, skipExisting);
4756
4722
  if (ci === "github") {
4757
4723
  const workflow = githubActionsTemplate(pkg);
4758
- const workflowDir = join8(dir, ".github", "workflows");
4759
- await safeWrite(join8(workflowDir, "gpc-release.yml"), workflow, created, skipped, skipExisting);
4724
+ const workflowDir = join7(dir, ".github", "workflows");
4725
+ await safeWrite(join7(workflowDir, "gpc-release.yml"), workflow, created, skipped, skipExisting);
4760
4726
  } else if (ci === "gitlab") {
4761
4727
  const pipeline = gitlabCiTemplate(pkg);
4762
- await safeWrite(join8(dir, ".gitlab-ci-gpc.yml"), pipeline, created, skipped, skipExisting);
4728
+ await safeWrite(join7(dir, ".gitlab-ci-gpc.yml"), pipeline, created, skipped, skipExisting);
4763
4729
  }
4764
4730
  return { created, skipped };
4765
4731
  }
@@ -4854,13 +4820,13 @@ var DEFAULT_PREFLIGHT_CONFIG = {
4854
4820
  };
4855
4821
 
4856
4822
  // src/preflight/config.ts
4857
- import { readFile as readFile10 } from "fs/promises";
4823
+ import { readFile as readFile9 } from "fs/promises";
4858
4824
  var VALID_SEVERITIES = /* @__PURE__ */ new Set(["critical", "error", "warning", "info"]);
4859
4825
  async function loadPreflightConfig(configPath) {
4860
4826
  const path = configPath || ".preflightrc.json";
4861
4827
  let raw;
4862
4828
  try {
4863
- raw = await readFile10(path, "utf-8");
4829
+ raw = await readFile9(path, "utf-8");
4864
4830
  } catch {
4865
4831
  return { ...DEFAULT_PREFLIGHT_CONFIG };
4866
4832
  }
@@ -5777,8 +5743,8 @@ ${fileList}` + (hasPageSizeCompat ? "\nandroid:pageSizeCompat is set, so the app
5777
5743
  }
5778
5744
 
5779
5745
  // src/preflight/scanners/metadata-scanner.ts
5780
- import { readdir as readdir5, stat as stat6, readFile as readFile11 } from "fs/promises";
5781
- import { join as join9 } from "path";
5746
+ import { readdir as readdir4, stat as stat5, readFile as readFile10 } from "fs/promises";
5747
+ import { join as join8 } from "path";
5782
5748
  var SAFE_LANG = /^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$/;
5783
5749
  var FILE_MAP2 = {
5784
5750
  "title.txt": "title",
@@ -5804,7 +5770,7 @@ var metadataScanner = {
5804
5770
  const findings = [];
5805
5771
  let entries;
5806
5772
  try {
5807
- entries = await readdir5(dir);
5773
+ entries = await readdir4(dir);
5808
5774
  } catch {
5809
5775
  findings.push({
5810
5776
  scanner: "metadata",
@@ -5829,14 +5795,14 @@ var metadataScanner = {
5829
5795
  return findings;
5830
5796
  }
5831
5797
  for (const lang of locales) {
5832
- const langDir = join9(dir, lang);
5833
- const langStat = await stat6(langDir).catch(() => null);
5798
+ const langDir = join8(dir, lang);
5799
+ const langStat = await stat5(langDir).catch(() => null);
5834
5800
  if (!langStat?.isDirectory()) continue;
5835
5801
  const fields = {};
5836
5802
  for (const [fileName, field] of Object.entries(FILE_MAP2)) {
5837
- const filePath = join9(langDir, fileName);
5803
+ const filePath = join8(langDir, fileName);
5838
5804
  try {
5839
- const content = await readFile11(filePath, "utf-8");
5805
+ const content = await readFile10(filePath, "utf-8");
5840
5806
  fields[field] = content.trimEnd();
5841
5807
  } catch {
5842
5808
  }
@@ -5875,9 +5841,9 @@ var metadataScanner = {
5875
5841
  let totalScreenshots = 0;
5876
5842
  let phoneScreenshots = 0;
5877
5843
  for (const ssDir of SCREENSHOT_DIRS) {
5878
- const ssPath = join9(langDir, "images", ssDir);
5844
+ const ssPath = join8(langDir, "images", ssDir);
5879
5845
  try {
5880
- const ssEntries = await readdir5(ssPath);
5846
+ const ssEntries = await readdir4(ssPath);
5881
5847
  const imageFiles = ssEntries.filter((f) => /\.(png|jpe?g|webp)$/i.test(f));
5882
5848
  totalScreenshots += imageFiles.length;
5883
5849
  if (ssDir === "phoneScreenshots") {
@@ -5907,9 +5873,9 @@ var metadataScanner = {
5907
5873
  }
5908
5874
  }
5909
5875
  const defaultLang = locales.includes("en-US") ? "en-US" : locales[0];
5910
- const privacyPath = join9(dir, defaultLang, "privacy_policy_url.txt");
5876
+ const privacyPath = join8(dir, defaultLang, "privacy_policy_url.txt");
5911
5877
  try {
5912
- const url = await readFile11(privacyPath, "utf-8");
5878
+ const url = await readFile10(privacyPath, "utf-8");
5913
5879
  if (!url.trim()) throw new Error("empty");
5914
5880
  } catch {
5915
5881
  findings.push({
@@ -5934,11 +5900,11 @@ var metadataScanner = {
5934
5900
  };
5935
5901
 
5936
5902
  // src/preflight/scanners/secrets-scanner.ts
5937
- import { readFile as readFile12 } from "fs/promises";
5903
+ import { readFile as readFile11 } from "fs/promises";
5938
5904
 
5939
5905
  // src/preflight/scan-files.ts
5940
- import { readdir as readdir6, stat as stat7 } from "fs/promises";
5941
- import { join as join10, extname as extname3 } from "path";
5906
+ import { readdir as readdir5, stat as stat6 } from "fs/promises";
5907
+ import { join as join9, extname as extname2 } from "path";
5942
5908
  var DEFAULT_SKIP_DIRS = /* @__PURE__ */ new Set([
5943
5909
  ".git",
5944
5910
  "node_modules",
@@ -5955,20 +5921,20 @@ async function collectSourceFiles(dir, extensions, skipDirs = DEFAULT_SKIP_DIRS,
5955
5921
  const files = [];
5956
5922
  let entries;
5957
5923
  try {
5958
- entries = await readdir6(dir);
5924
+ entries = await readdir5(dir);
5959
5925
  } catch {
5960
5926
  return files;
5961
5927
  }
5962
5928
  for (const entry of entries) {
5963
5929
  if (skipDirs.has(entry)) continue;
5964
- const fullPath = join10(dir, entry);
5965
- const s = await stat7(fullPath).catch(() => null);
5930
+ const fullPath = join9(dir, entry);
5931
+ const s = await stat6(fullPath).catch(() => null);
5966
5932
  if (!s) continue;
5967
5933
  if (s.isDirectory()) {
5968
5934
  const sub = await collectSourceFiles(fullPath, extensions, skipDirs, maxDepth - 1);
5969
5935
  files.push(...sub);
5970
5936
  } else if (s.isFile()) {
5971
- const ext = extname3(entry).toLowerCase();
5937
+ const ext = extname2(entry).toLowerCase();
5972
5938
  if (extensions.has(ext) || entry.endsWith(".gradle.kts")) {
5973
5939
  files.push(fullPath);
5974
5940
  }
@@ -6054,7 +6020,7 @@ var secretsScanner = {
6054
6020
  for (const filePath of files) {
6055
6021
  let content;
6056
6022
  try {
6057
- content = await readFile12(filePath, "utf-8");
6023
+ content = await readFile11(filePath, "utf-8");
6058
6024
  } catch {
6059
6025
  continue;
6060
6026
  }
@@ -6082,7 +6048,7 @@ var secretsScanner = {
6082
6048
  };
6083
6049
 
6084
6050
  // src/preflight/scanners/billing-scanner.ts
6085
- import { readFile as readFile13 } from "fs/promises";
6051
+ import { readFile as readFile12 } from "fs/promises";
6086
6052
  var BILLING_PATTERNS = [
6087
6053
  {
6088
6054
  ruleId: "billing-stripe-sdk",
@@ -6143,7 +6109,7 @@ var billingScanner = {
6143
6109
  for (const filePath of files) {
6144
6110
  let content;
6145
6111
  try {
6146
- content = await readFile13(filePath, "utf-8");
6112
+ content = await readFile12(filePath, "utf-8");
6147
6113
  } catch {
6148
6114
  continue;
6149
6115
  }
@@ -6169,7 +6135,7 @@ var billingScanner = {
6169
6135
  };
6170
6136
 
6171
6137
  // src/preflight/scanners/privacy-scanner.ts
6172
- import { readFile as readFile14 } from "fs/promises";
6138
+ import { readFile as readFile13 } from "fs/promises";
6173
6139
  var TRACKING_SDKS = [
6174
6140
  {
6175
6141
  name: "Facebook SDK",
@@ -6209,7 +6175,7 @@ var privacyScanner = {
6209
6175
  for (const filePath of files) {
6210
6176
  let content;
6211
6177
  try {
6212
- content = await readFile14(filePath, "utf-8");
6178
+ content = await readFile13(filePath, "utf-8");
6213
6179
  } catch {
6214
6180
  continue;
6215
6181
  }
@@ -6582,13 +6548,13 @@ function sortResults(items, sortSpec) {
6582
6548
 
6583
6549
  // src/commands/plugin-scaffold.ts
6584
6550
  import { mkdir as mkdir7, writeFile as writeFile7 } from "fs/promises";
6585
- import { join as join11 } from "path";
6551
+ import { join as join10 } from "path";
6586
6552
  async function scaffoldPlugin(options) {
6587
6553
  const { name, dir, description = `GPC plugin: ${name}` } = options;
6588
6554
  const pluginName = name.startsWith("gpc-plugin-") ? name : `gpc-plugin-${name}`;
6589
6555
  const shortName = pluginName.replace(/^gpc-plugin-/, "");
6590
- const srcDir = join11(dir, "src");
6591
- const testDir = join11(dir, "tests");
6556
+ const srcDir = join10(dir, "src");
6557
+ const testDir = join10(dir, "tests");
6592
6558
  await mkdir7(srcDir, { recursive: true });
6593
6559
  await mkdir7(testDir, { recursive: true });
6594
6560
  const files = [];
@@ -6624,7 +6590,7 @@ async function scaffoldPlugin(options) {
6624
6590
  vitest: "^3.0.0"
6625
6591
  }
6626
6592
  };
6627
- await writeFile7(join11(dir, "package.json"), JSON.stringify(pkg, null, 2) + "\n");
6593
+ await writeFile7(join10(dir, "package.json"), JSON.stringify(pkg, null, 2) + "\n");
6628
6594
  files.push("package.json");
6629
6595
  const tsconfig = {
6630
6596
  compilerOptions: {
@@ -6640,7 +6606,7 @@ async function scaffoldPlugin(options) {
6640
6606
  },
6641
6607
  include: ["src"]
6642
6608
  };
6643
- await writeFile7(join11(dir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2) + "\n");
6609
+ await writeFile7(join10(dir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2) + "\n");
6644
6610
  files.push("tsconfig.json");
6645
6611
  const srcContent = `import { definePlugin } from "@gpc-cli/plugin-sdk";
6646
6612
  import type { CommandEvent, CommandResult } from "@gpc-cli/plugin-sdk";
@@ -6673,7 +6639,7 @@ export const plugin = definePlugin({
6673
6639
  },
6674
6640
  });
6675
6641
  `;
6676
- await writeFile7(join11(srcDir, "index.ts"), srcContent);
6642
+ await writeFile7(join10(srcDir, "index.ts"), srcContent);
6677
6643
  files.push("src/index.ts");
6678
6644
  const testContent = `import { describe, it, expect, vi } from "vitest";
6679
6645
  import { plugin } from "../src/index";
@@ -6698,7 +6664,7 @@ describe("${pluginName}", () => {
6698
6664
  });
6699
6665
  });
6700
6666
  `;
6701
- await writeFile7(join11(testDir, "plugin.test.ts"), testContent);
6667
+ await writeFile7(join10(testDir, "plugin.test.ts"), testContent);
6702
6668
  files.push("tests/plugin.test.ts");
6703
6669
  return { dir, files };
6704
6670
  }
@@ -6809,7 +6775,7 @@ async function sendWebhook(config, payload, target) {
6809
6775
  }
6810
6776
 
6811
6777
  // src/commands/internal-sharing.ts
6812
- import { extname as extname4 } from "path";
6778
+ import { extname as extname3 } from "path";
6813
6779
  async function uploadInternalSharing(client, packageName, filePath, fileType) {
6814
6780
  const resolvedType = fileType ?? detectFileType(filePath);
6815
6781
  const validation = await validateUploadFile(filePath);
@@ -6836,7 +6802,7 @@ ${validation.errors.join("\n")}`,
6836
6802
  };
6837
6803
  }
6838
6804
  function detectFileType(filePath) {
6839
- const ext = extname4(filePath).toLowerCase();
6805
+ const ext = extname3(filePath).toLowerCase();
6840
6806
  if (ext === ".aab") return "bundle";
6841
6807
  if (ext === ".apk") return "apk";
6842
6808
  throw new GpcError(
@@ -6884,7 +6850,7 @@ async function downloadGeneratedApk(client, packageName, versionCode, apkId, out
6884
6850
  }
6885
6851
 
6886
6852
  // src/commands/bundle-analysis.ts
6887
- import { readFile as readFile15, stat as stat8 } from "fs/promises";
6853
+ import { readFile as readFile14, stat as stat7 } from "fs/promises";
6888
6854
  var EOCD_SIGNATURE = 101010256;
6889
6855
  var CD_SIGNATURE = 33639248;
6890
6856
  var MODULE_SUBDIRS = /* @__PURE__ */ new Set(["dex", "manifest", "res", "assets", "lib", "resources.pb", "root"]);
@@ -6960,11 +6926,11 @@ function detectFileType2(filePath) {
6960
6926
  return "apk";
6961
6927
  }
6962
6928
  async function analyzeBundle(filePath) {
6963
- const fileInfo = await stat8(filePath).catch(() => null);
6929
+ const fileInfo = await stat7(filePath).catch(() => null);
6964
6930
  if (!fileInfo || !fileInfo.isFile()) {
6965
6931
  throw new Error(`File not found: ${filePath}`);
6966
6932
  }
6967
- const buf = await readFile15(filePath);
6933
+ const buf = await readFile14(filePath);
6968
6934
  const cdEntries = parseCentralDirectory(buf);
6969
6935
  const fileType = detectFileType2(filePath);
6970
6936
  const isAab = fileType === "aab";
@@ -7050,7 +7016,7 @@ function topFiles(analysis, n = 20) {
7050
7016
  async function checkBundleSize(analysis, configPath = ".bundlesize.json") {
7051
7017
  let config;
7052
7018
  try {
7053
- const raw = await readFile15(configPath, "utf-8");
7019
+ const raw = await readFile14(configPath, "utf-8");
7054
7020
  config = JSON.parse(raw);
7055
7021
  } catch {
7056
7022
  return { passed: true, violations: [] };
@@ -8371,6 +8337,202 @@ function compareFingerprints(a, b) {
8371
8337
  return normalizeFingerprint(a) === normalizeFingerprint(b);
8372
8338
  }
8373
8339
 
8340
+ // src/utils/hash.ts
8341
+ import { createHash } from "crypto";
8342
+ import { stat as stat8 } from "fs/promises";
8343
+ async function sha256File(filePath) {
8344
+ const hash = createHash("sha256");
8345
+ const { size } = await stat8(filePath);
8346
+ if (size === 0) return hash.digest("hex");
8347
+ const { createReadStream } = await import("fs");
8348
+ const stream = createReadStream(filePath);
8349
+ await new Promise((resolve2, reject) => {
8350
+ stream.on("data", (chunk) => hash.update(chunk));
8351
+ stream.on("end", resolve2);
8352
+ stream.on("error", reject);
8353
+ });
8354
+ return hash.digest("hex");
8355
+ }
8356
+
8357
+ // src/commands/image-sync.ts
8358
+ import { readdir as readdir6 } from "fs/promises";
8359
+ import { join as join11, extname as extname4 } from "path";
8360
+ import { PlayApiError } from "@gpc-cli/api";
8361
+ var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg"]);
8362
+ var ALL_IMAGE_TYPES2 = [
8363
+ "icon",
8364
+ "featureGraphic",
8365
+ "tvBanner",
8366
+ "phoneScreenshots",
8367
+ "sevenInchScreenshots",
8368
+ "tenInchScreenshots",
8369
+ "tvScreenshots",
8370
+ "wearScreenshots"
8371
+ ];
8372
+ async function scanLocalImages(dir) {
8373
+ try {
8374
+ const entries = await readdir6(dir, { withFileTypes: true });
8375
+ return entries.filter((e) => e.isFile() && IMAGE_EXTENSIONS.has(extname4(e.name).toLowerCase())).map((e) => e.name).sort();
8376
+ } catch {
8377
+ return [];
8378
+ }
8379
+ }
8380
+ async function scanLanguages(dir) {
8381
+ try {
8382
+ const entries = await readdir6(dir, { withFileTypes: true });
8383
+ return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
8384
+ } catch {
8385
+ return [];
8386
+ }
8387
+ }
8388
+ async function syncImages(client, packageName, dir, options) {
8389
+ const details = [];
8390
+ let uploaded = 0;
8391
+ let skipped = 0;
8392
+ let deleted = 0;
8393
+ const languages = options?.lang ? [options.lang] : await scanLanguages(dir);
8394
+ if (languages.length === 0) {
8395
+ throw new GpcError(
8396
+ `No language directories found in "${dir}"`,
8397
+ "IMAGE_SYNC_EMPTY",
8398
+ 1,
8399
+ "The directory should contain subdirectories named by language code (e.g., en-US/) with image type subdirectories inside."
8400
+ );
8401
+ }
8402
+ const imageTypes = options?.type ? [options.type] : ALL_IMAGE_TYPES2;
8403
+ const edit = await client.edits.insert(packageName);
8404
+ try {
8405
+ for (const language of languages) {
8406
+ for (const imageType of imageTypes) {
8407
+ const localDir = join11(dir, language, imageType);
8408
+ const localFiles = await scanLocalImages(localDir);
8409
+ let remoteImages;
8410
+ try {
8411
+ remoteImages = await client.images.list(packageName, edit.id, language, imageType);
8412
+ } catch (error) {
8413
+ if (error instanceof PlayApiError && error.statusCode === 404) {
8414
+ remoteImages = [];
8415
+ } else {
8416
+ throw error;
8417
+ }
8418
+ }
8419
+ const remoteSha256Set = new Set(remoteImages.map((img) => img.sha256.toLowerCase()));
8420
+ const localHashes = /* @__PURE__ */ new Map();
8421
+ for (const file of localFiles) {
8422
+ const hash = await sha256File(join11(localDir, file));
8423
+ localHashes.set(file, hash);
8424
+ }
8425
+ const localSha256Set = new Set(localHashes.values());
8426
+ if (options?.delete) {
8427
+ for (const img of remoteImages) {
8428
+ if (!localSha256Set.has(img.sha256.toLowerCase())) {
8429
+ if (!options?.dryRun) {
8430
+ await client.images.delete(packageName, edit.id, language, imageType, img.id);
8431
+ }
8432
+ deleted++;
8433
+ details.push({
8434
+ language,
8435
+ imageType,
8436
+ file: img.id,
8437
+ action: "delete",
8438
+ reason: "not in local"
8439
+ });
8440
+ }
8441
+ }
8442
+ }
8443
+ for (const file of localFiles) {
8444
+ const hash = localHashes.get(file);
8445
+ if (remoteSha256Set.has(hash)) {
8446
+ skipped++;
8447
+ details.push({ language, imageType, file, action: "skip", reason: "sha256 match" });
8448
+ } else {
8449
+ if (!options?.dryRun) {
8450
+ const filePath = join11(localDir, file);
8451
+ const check = await validateImage(filePath, imageType);
8452
+ if (!check.valid) {
8453
+ throw new GpcError(
8454
+ `Image validation failed for ${language}/${imageType}/${file}: ${check.errors.join("; ")}`,
8455
+ "IMAGE_INVALID",
8456
+ 2,
8457
+ "Check image dimensions, file size, and format."
8458
+ );
8459
+ }
8460
+ await client.images.upload(packageName, edit.id, language, imageType, filePath);
8461
+ }
8462
+ uploaded++;
8463
+ details.push({ language, imageType, file, action: "upload", reason: "new or changed" });
8464
+ }
8465
+ }
8466
+ }
8467
+ }
8468
+ if (options?.dryRun || uploaded === 0 && deleted === 0) {
8469
+ await client.edits.delete(packageName, edit.id).catch(() => {
8470
+ });
8471
+ } else {
8472
+ await validateAndCommit(client, packageName, edit.id, options?.commitOptions);
8473
+ }
8474
+ return {
8475
+ uploaded,
8476
+ skipped,
8477
+ deleted,
8478
+ total: uploaded + skipped + deleted,
8479
+ details
8480
+ };
8481
+ } catch (error) {
8482
+ await client.edits.delete(packageName, edit.id).catch(() => {
8483
+ });
8484
+ throw error;
8485
+ }
8486
+ }
8487
+
8488
+ // src/commands/bundles.ts
8489
+ import { PlayApiError as PlayApiError2 } from "@gpc-cli/api";
8490
+ async function listBundles(client, packageName) {
8491
+ const edit = await client.edits.insert(packageName);
8492
+ try {
8493
+ const bundles = await client.bundles.list(packageName, edit.id);
8494
+ await client.edits.delete(packageName, edit.id).catch(() => {
8495
+ });
8496
+ return bundles;
8497
+ } catch (error) {
8498
+ await client.edits.delete(packageName, edit.id).catch(() => {
8499
+ });
8500
+ throw error;
8501
+ }
8502
+ }
8503
+ async function findBundle(client, packageName, versionCode) {
8504
+ const bundles = await listBundles(client, packageName);
8505
+ return bundles.find((b) => b.versionCode === versionCode) ?? null;
8506
+ }
8507
+ function isRetryableError(error) {
8508
+ if (!(error instanceof PlayApiError2)) return false;
8509
+ const status = error.statusCode;
8510
+ return status === 429 || status === 500 || status === 503 || status === 409;
8511
+ }
8512
+ async function waitForBundle(client, packageName, versionCode, options) {
8513
+ const timeout = options?.timeout ?? 6e5;
8514
+ const interval = options?.interval ?? 15e3;
8515
+ const deadline = Date.now() + timeout;
8516
+ while (Date.now() < deadline) {
8517
+ try {
8518
+ const bundles = await listBundles(client, packageName);
8519
+ const match = bundles.find((b) => b.versionCode === versionCode);
8520
+ if (match) return match;
8521
+ } catch (error) {
8522
+ if (!isRetryableError(error)) throw error;
8523
+ }
8524
+ const remaining = deadline - Date.now();
8525
+ if (remaining <= 0) break;
8526
+ await new Promise((r) => setTimeout(r, Math.min(interval, remaining)));
8527
+ }
8528
+ throw new GpcError(
8529
+ `Bundle version code ${versionCode} not found after ${Math.round(timeout / 1e3)}s`,
8530
+ "BUNDLE_WAIT_TIMEOUT",
8531
+ 4,
8532
+ "The bundle may still be processing. Try again with a longer --timeout, or check the Play Console."
8533
+ );
8534
+ }
8535
+
8374
8536
  // src/signing-consistency.ts
8375
8537
  async function checkSigningConsistency(accessToken, packageName, apiHost = "androidpublisher.googleapis.com") {
8376
8538
  const baseUrl = `https://${apiHost}/androidpublisher/v3/applications/${encodeURIComponent(packageName)}`;
@@ -8613,6 +8775,7 @@ export {
8613
8775
  checkThreshold,
8614
8776
  classifyError,
8615
8777
  clearAuditLog,
8778
+ commitWithRescue,
8616
8779
  compareBundles,
8617
8780
  compareFingerprints,
8618
8781
  compareVersionVitals,
@@ -8667,6 +8830,7 @@ export {
8667
8830
  fetchAggregateCost,
8668
8831
  fetchChangelog,
8669
8832
  fetchReleaseNotes,
8833
+ findBundle,
8670
8834
  formatChangelogEntry,
8671
8835
  formatCustomPayload,
8672
8836
  formatDiscordPayload,
@@ -8728,11 +8892,13 @@ export {
8728
8892
  isValidBcp47,
8729
8893
  isValidReportType,
8730
8894
  isValidStatsDimension,
8895
+ isVersionedNotesDir,
8731
8896
  lintListing,
8732
8897
  lintListings,
8733
8898
  lintLocalListings,
8734
8899
  listAchievements,
8735
8900
  listAuditEvents,
8901
+ listBundles,
8736
8902
  listDeviceTiers,
8737
8903
  listEvents,
8738
8904
  listGeneratedApks,
@@ -8770,6 +8936,7 @@ export {
8770
8936
  pullListings,
8771
8937
  pushListings,
8772
8938
  readListingsFromDir,
8939
+ readReleaseNotesForVersion,
8773
8940
  readReleaseNotesFromDir,
8774
8941
  redactAuditArgs,
8775
8942
  redactSensitive,
@@ -8801,9 +8968,11 @@ export {
8801
8968
  searchVitalsErrors,
8802
8969
  sendNotification,
8803
8970
  sendWebhook,
8971
+ sha256File,
8804
8972
  sortResults,
8805
8973
  startTrain,
8806
8974
  statusHasBreach,
8975
+ syncImages,
8807
8976
  syncInAppProducts,
8808
8977
  topFiles,
8809
8978
  trackBreachState,
@@ -8824,6 +8993,7 @@ export {
8824
8993
  uploadImage,
8825
8994
  uploadInternalSharing,
8826
8995
  uploadRelease,
8996
+ validateAndCommit,
8827
8997
  validateBundleForApply,
8828
8998
  validateImage,
8829
8999
  validateLanguageCode,
@@ -8834,6 +9004,7 @@ export {
8834
9004
  validateTrackName,
8835
9005
  validateUploadFile,
8836
9006
  validateVersionCode,
9007
+ waitForBundle,
8837
9008
  waitForBundleProcessing,
8838
9009
  watchVitalsWithAutoHalt,
8839
9010
  wordDiff,