@gpc-cli/core 0.9.59 → 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
@@ -9,16 +9,20 @@ import {
9
9
  diffReleases,
10
10
  fetchReleaseNotes,
11
11
  getReleasesStatus,
12
+ isVersionedNotesDir,
12
13
  listTracks,
13
14
  promoteRelease,
15
+ readReleaseNotesForVersion,
16
+ readReleaseNotesFromDir,
14
17
  updateRollout,
15
18
  updateTrackConfig,
16
19
  uploadExternallyHosted,
17
20
  uploadRelease,
18
21
  validateAndCommit,
22
+ validateReleaseNotes,
19
23
  validateUploadFile,
20
24
  waitForBundleProcessing
21
- } from "./chunk-IZKB6GBS.js";
25
+ } from "./chunk-3AUJEAXP.js";
22
26
 
23
27
  // src/output.ts
24
28
  import process2 from "process";
@@ -1151,7 +1155,7 @@ var ALL_IMAGE_TYPES = [
1151
1155
  ];
1152
1156
  async function exportImages(client, packageName, dir, options) {
1153
1157
  const { mkdir: mkdir8, writeFile: writeFile9 } = await import("fs/promises");
1154
- const { join: join13 } = await import("path");
1158
+ const { join: join12 } = await import("path");
1155
1159
  const edit = await client.edits.insert(packageName);
1156
1160
  try {
1157
1161
  let languages;
@@ -1182,7 +1186,7 @@ async function exportImages(client, packageName, dir, options) {
1182
1186
  const batch = tasks.slice(i, i + concurrency);
1183
1187
  const results = await Promise.all(
1184
1188
  batch.map(async (task) => {
1185
- const dirPath = join13(dir, task.language, task.imageType);
1189
+ const dirPath = join12(dir, task.language, task.imageType);
1186
1190
  await mkdir8(dirPath, { recursive: true });
1187
1191
  const response = await fetch(task.url);
1188
1192
  if (!response.ok) {
@@ -1194,7 +1198,7 @@ async function exportImages(client, packageName, dir, options) {
1194
1198
  );
1195
1199
  }
1196
1200
  const buffer = Buffer.from(await response.arrayBuffer());
1197
- const filePath = join13(dirPath, `${task.index}.png`);
1201
+ const filePath = join12(dirPath, `${task.index}.png`);
1198
1202
  await writeFile9(filePath, buffer);
1199
1203
  return buffer.length;
1200
1204
  })
@@ -1513,55 +1517,8 @@ function validateSku(sku) {
1513
1517
  }
1514
1518
  }
1515
1519
 
1516
- // src/utils/release-notes.ts
1517
- import { readdir as readdir3, readFile as readFile3, stat as stat3 } from "fs/promises";
1518
- import { extname as extname2, basename, join as join3 } from "path";
1519
- var MAX_NOTES_LENGTH = 500;
1520
- async function readReleaseNotesFromDir(dir) {
1521
- let entries;
1522
- try {
1523
- entries = await readdir3(dir);
1524
- } catch {
1525
- throw new GpcError(
1526
- `Release notes directory not found: ${dir}`,
1527
- "RELEASE_NOTES_DIR_NOT_FOUND",
1528
- 1,
1529
- `Create the directory and add .txt files named by language code (e.g., en-US.txt). Path: ${dir}`
1530
- );
1531
- }
1532
- const notes = [];
1533
- for (const entry of entries) {
1534
- if (extname2(entry) !== ".txt") continue;
1535
- const language = basename(entry, ".txt");
1536
- const filePath = join3(dir, entry);
1537
- const stats = await stat3(filePath);
1538
- if (!stats.isFile()) continue;
1539
- const text = (await readFile3(filePath, "utf-8")).trim();
1540
- if (text.length === 0) continue;
1541
- notes.push({ language, text });
1542
- }
1543
- return notes;
1544
- }
1545
- function validateReleaseNotes(notes) {
1546
- const errors = [];
1547
- const warnings = [];
1548
- const seen = /* @__PURE__ */ new Set();
1549
- for (const note of notes) {
1550
- if (seen.has(note.language)) {
1551
- errors.push(`Duplicate language code: ${note.language}`);
1552
- }
1553
- seen.add(note.language);
1554
- if (note.text.length > MAX_NOTES_LENGTH) {
1555
- warnings.push(
1556
- `Release notes for "${note.language}" are ${note.text.length} chars (max ${MAX_NOTES_LENGTH}) \u2014 Google Play will reject notes exceeding this limit`
1557
- );
1558
- }
1559
- }
1560
- return { valid: errors.length === 0, errors, warnings };
1561
- }
1562
-
1563
1520
  // src/commands/validate.ts
1564
- import { stat as stat4 } from "fs/promises";
1521
+ import { stat as stat3 } from "fs/promises";
1565
1522
  var STANDARD_TRACKS = /* @__PURE__ */ new Set([
1566
1523
  "internal",
1567
1524
  "qa",
@@ -1605,7 +1562,7 @@ async function validatePreSubmission(options) {
1605
1562
  }
1606
1563
  if (options.mappingFile) {
1607
1564
  try {
1608
- const stats = await stat4(options.mappingFile);
1565
+ const stats = await stat3(options.mappingFile);
1609
1566
  checks.push({
1610
1567
  name: "mapping",
1611
1568
  passed: stats.isFile(),
@@ -2404,12 +2361,28 @@ var METRIC_SET_METRICS = {
2404
2361
  ],
2405
2362
  errorCountMetricSet: ["errorReportCount", "distinctUsers"]
2406
2363
  };
2407
- 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) {
2408
2380
  const metrics = METRIC_SET_METRICS[metricSet] ?? ["errorReportCount", "distinctUsers"];
2409
2381
  const days = options?.days ?? 30;
2410
2382
  const DAY_MS = 24 * 60 * 60 * 1e3;
2411
- const end = new Date(Date.now() - DAY_MS);
2412
- 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);
2413
2386
  const query = {
2414
2387
  metrics,
2415
2388
  timelineSpec: {
@@ -2432,7 +2405,13 @@ function buildQuery(metricSet, options) {
2432
2405
  return query;
2433
2406
  }
2434
2407
  async function queryMetric(reporting, packageName, metricSet, options) {
2435
- 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);
2436
2415
  return reporting.queryMetricSet(packageName, metricSet, query);
2437
2416
  }
2438
2417
  async function getVitalsOverview(reporting, packageName) {
@@ -2444,8 +2423,15 @@ async function getVitalsOverview(reporting, packageName) {
2444
2423
  ["excessiveWakeupRateMetricSet", "excessiveWakeupRate"],
2445
2424
  ["stuckBackgroundWakelockRateMetricSet", "stuckWakelockRate"]
2446
2425
  ];
2426
+ const freshnessResults = await Promise.allSettled(
2427
+ metricSets.map(([metric]) => getFreshnessEndDate(reporting, packageName, metric))
2428
+ );
2447
2429
  const results = await Promise.allSettled(
2448
- 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
+ })
2449
2435
  );
2450
2436
  const overview = {};
2451
2437
  for (let i = 0; i < metricSets.length; i++) {
@@ -2498,7 +2484,9 @@ async function searchVitalsErrors(reporting, packageName, options) {
2498
2484
  async function compareVitalsTrend(reporting, packageName, metricSet, days = 7) {
2499
2485
  const DAY_MS = 24 * 60 * 60 * 1e3;
2500
2486
  const nowMs = Date.now();
2501
- 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;
2502
2490
  const currentEnd = new Date(baseMs);
2503
2491
  const currentStart = new Date(baseMs - days * DAY_MS);
2504
2492
  const previousEnd = new Date(baseMs - days * DAY_MS - DAY_MS);
@@ -2644,17 +2632,17 @@ function watchVitalsWithAutoHalt(reporting, packageName, options) {
2644
2632
  }
2645
2633
 
2646
2634
  // src/commands/status.ts
2647
- 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";
2648
2636
  import { execFile } from "child_process";
2649
- import { join as join4 } from "path";
2637
+ import { join as join3 } from "path";
2650
2638
  import { getCacheDir } from "@gpc-cli/config";
2651
2639
  var DEFAULT_TTL_SECONDS = 3600;
2652
2640
  function cacheFilePath(packageName) {
2653
- return join4(getCacheDir(), `status-${packageName}.json`);
2641
+ return join3(getCacheDir(), `status-${packageName}.json`);
2654
2642
  }
2655
2643
  async function loadStatusCache(packageName, ttlSeconds = DEFAULT_TTL_SECONDS) {
2656
2644
  try {
2657
- const raw = await readFile4(cacheFilePath(packageName), "utf-8");
2645
+ const raw = await readFile3(cacheFilePath(packageName), "utf-8");
2658
2646
  const entry = JSON.parse(raw);
2659
2647
  const age = (Date.now() - new Date(entry.fetchedAt).getTime()) / 1e3;
2660
2648
  if (age > (entry.ttl ?? ttlSeconds)) return null;
@@ -3063,13 +3051,13 @@ async function runWatchLoop(opts) {
3063
3051
  }
3064
3052
  }
3065
3053
  function breachStateFilePath(packageName) {
3066
- return join4(getCacheDir(), `breach-state-${packageName}.json`);
3054
+ return join3(getCacheDir(), `breach-state-${packageName}.json`);
3067
3055
  }
3068
3056
  async function trackBreachState(packageName, isBreaching) {
3069
3057
  const filePath = breachStateFilePath(packageName);
3070
3058
  let prevBreaching = false;
3071
3059
  try {
3072
- const raw = await readFile4(filePath, "utf-8");
3060
+ const raw = await readFile3(filePath, "utf-8");
3073
3061
  prevBreaching = JSON.parse(raw).breaching;
3074
3062
  } catch {
3075
3063
  }
@@ -3222,7 +3210,7 @@ async function handleBreach(event, config, client) {
3222
3210
  }
3223
3211
  case "halt": {
3224
3212
  try {
3225
- const { updateRollout: updateRollout2 } = await import("./releases-VFDJ6IX2.js");
3213
+ const { updateRollout: updateRollout2 } = await import("./releases-S54GLWH3.js");
3226
3214
  await updateRollout2(client, config.packageName, config.track, "halt");
3227
3215
  halted = true;
3228
3216
  } catch {
@@ -3338,8 +3326,8 @@ async function runWatch(client, reporting, config, callbacks) {
3338
3326
  }
3339
3327
 
3340
3328
  // src/commands/iap.ts
3341
- import { readdir as readdir4, readFile as readFile5 } from "fs/promises";
3342
- import { join as join5 } from "path";
3329
+ import { readdir as readdir3, readFile as readFile4 } from "fs/promises";
3330
+ import { join as join4 } from "path";
3343
3331
  import { paginateAll as paginateAll3 } from "@gpc-cli/api";
3344
3332
  async function listInAppProducts(client, packageName, options) {
3345
3333
  if (options?.limit || options?.nextPage) {
@@ -3424,11 +3412,11 @@ async function batchUpdateProducts(client, packageName, products) {
3424
3412
  return results;
3425
3413
  }
3426
3414
  async function readProductsFromDir(dir) {
3427
- const files = await readdir4(dir);
3415
+ const files = await readdir3(dir);
3428
3416
  const jsonFiles = files.filter((f) => f.endsWith(".json"));
3429
3417
  const localProducts = [];
3430
3418
  for (const file of jsonFiles) {
3431
- const content = await readFile5(join5(dir, file), "utf-8");
3419
+ const content = await readFile4(join4(dir, file), "utf-8");
3432
3420
  try {
3433
3421
  localProducts.push(JSON.parse(content));
3434
3422
  } catch {
@@ -3785,7 +3773,7 @@ async function deleteGrant(client, developerId, email, packageName) {
3785
3773
  }
3786
3774
 
3787
3775
  // src/commands/testers.ts
3788
- import { readFile as readFile6 } from "fs/promises";
3776
+ import { readFile as readFile5 } from "fs/promises";
3789
3777
  async function listTesters(client, packageName, track) {
3790
3778
  const edit = await client.edits.insert(packageName);
3791
3779
  try {
@@ -3832,7 +3820,7 @@ async function removeTesters(client, packageName, track, groupEmails, commitOpti
3832
3820
  }
3833
3821
  }
3834
3822
  async function importTestersFromCsv(client, packageName, track, csvPath, commitOptions) {
3835
- const content = await readFile6(csvPath, "utf-8");
3823
+ const content = await readFile5(csvPath, "utf-8");
3836
3824
  const emails = content.split(/[,\n\r]+/).map((e) => e.trim()).filter((e) => e.length > 0 && e.includes("@"));
3837
3825
  if (emails.length === 0) {
3838
3826
  throw new GpcError(
@@ -3975,12 +3963,12 @@ async function addRecoveryTargeting(client, packageName, actionId, targeting) {
3975
3963
  }
3976
3964
 
3977
3965
  // src/commands/data-safety.ts
3978
- import { readFile as readFile7 } from "fs/promises";
3966
+ import { readFile as readFile6 } from "fs/promises";
3979
3967
  async function updateDataSafety(client, packageName, data) {
3980
3968
  return client.dataSafety.update(packageName, data);
3981
3969
  }
3982
3970
  async function importDataSafety(client, packageName, filePath) {
3983
- const content = await readFile7(filePath, "utf-8");
3971
+ const content = await readFile6(filePath, "utf-8");
3984
3972
  let data;
3985
3973
  try {
3986
3974
  data = JSON.parse(content);
@@ -4284,16 +4272,16 @@ function createSpinner(message) {
4284
4272
  }
4285
4273
 
4286
4274
  // src/utils/train-state.ts
4287
- import { mkdir as mkdir4, readFile as readFile8, writeFile as writeFile4 } from "fs/promises";
4288
- 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";
4289
4277
  import { getCacheDir as getCacheDir2 } from "@gpc-cli/config";
4290
4278
  function stateFile(packageName) {
4291
- return join6(getCacheDir2(), `train-${packageName}.json`);
4279
+ return join5(getCacheDir2(), `train-${packageName}.json`);
4292
4280
  }
4293
4281
  async function readTrainState(packageName) {
4294
4282
  const path = stateFile(packageName);
4295
4283
  try {
4296
- const raw = await readFile8(path, "utf-8");
4284
+ const raw = await readFile7(path, "utf-8");
4297
4285
  return JSON.parse(raw);
4298
4286
  } catch {
4299
4287
  return null;
@@ -4473,8 +4461,8 @@ async function publishEnterpriseApp(client, params) {
4473
4461
  }
4474
4462
 
4475
4463
  // src/audit.ts
4476
- import { appendFile, chmod, mkdir as mkdir5, readFile as readFile9, writeFile as writeFile5 } from "fs/promises";
4477
- 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";
4478
4466
  var auditDir = null;
4479
4467
  function initAudit(configDir) {
4480
4468
  auditDir = configDir;
@@ -4483,7 +4471,7 @@ async function writeAuditLog(entry) {
4483
4471
  if (!auditDir) return;
4484
4472
  try {
4485
4473
  await mkdir5(auditDir, { recursive: true, mode: 448 });
4486
- const logPath = join7(auditDir, "audit.log");
4474
+ const logPath = join6(auditDir, "audit.log");
4487
4475
  const redactedEntry = redactAuditArgs(entry);
4488
4476
  const line = JSON.stringify(redactedEntry) + "\n";
4489
4477
  await appendFile(logPath, line, { encoding: "utf-8", mode: 384 });
@@ -4538,10 +4526,10 @@ function createAuditEntry(command, args, app) {
4538
4526
  }
4539
4527
  async function listAuditEvents(options) {
4540
4528
  if (!auditDir) return [];
4541
- const logPath = join7(auditDir, "audit.log");
4529
+ const logPath = join6(auditDir, "audit.log");
4542
4530
  let content;
4543
4531
  try {
4544
- content = await readFile9(logPath, "utf-8");
4532
+ content = await readFile8(logPath, "utf-8");
4545
4533
  } catch {
4546
4534
  return [];
4547
4535
  }
@@ -4576,10 +4564,10 @@ async function searchAuditEvents(query) {
4576
4564
  }
4577
4565
  async function clearAuditLog(options) {
4578
4566
  if (!auditDir) return { deleted: 0, remaining: 0 };
4579
- const logPath = join7(auditDir, "audit.log");
4567
+ const logPath = join6(auditDir, "audit.log");
4580
4568
  let content;
4581
4569
  try {
4582
- content = await readFile9(logPath, "utf-8");
4570
+ content = await readFile8(logPath, "utf-8");
4583
4571
  } catch {
4584
4572
  return { deleted: 0, remaining: 0 };
4585
4573
  }
@@ -4667,11 +4655,11 @@ function safePathWithin(userPath, baseDir) {
4667
4655
  }
4668
4656
 
4669
4657
  // src/commands/init.ts
4670
- import { mkdir as mkdir6, writeFile as writeFile6, stat as stat5 } from "fs/promises";
4671
- 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";
4672
4660
  async function exists2(path) {
4673
4661
  try {
4674
- await stat5(path);
4662
+ await stat4(path);
4675
4663
  return true;
4676
4664
  } catch {
4677
4665
  return false;
@@ -4703,7 +4691,7 @@ async function initProject(options) {
4703
4691
  null,
4704
4692
  2
4705
4693
  );
4706
- await safeWrite(join8(dir, ".gpcrc.json"), gpcrc + "\n", created, skipped, skipExisting);
4694
+ await safeWrite(join7(dir, ".gpcrc.json"), gpcrc + "\n", created, skipped, skipExisting);
4707
4695
  const preflightrc = JSON.stringify(
4708
4696
  {
4709
4697
  failOn: "error",
@@ -4717,27 +4705,27 @@ async function initProject(options) {
4717
4705
  2
4718
4706
  );
4719
4707
  await safeWrite(
4720
- join8(dir, ".preflightrc.json"),
4708
+ join7(dir, ".preflightrc.json"),
4721
4709
  preflightrc + "\n",
4722
4710
  created,
4723
4711
  skipped,
4724
4712
  skipExisting
4725
4713
  );
4726
- const metaDir = join8(dir, "metadata", "android", "en-US");
4727
- await safeWrite(join8(metaDir, "title.txt"), "", created, skipped, skipExisting);
4728
- await safeWrite(join8(metaDir, "short_description.txt"), "", created, skipped, skipExisting);
4729
- await safeWrite(join8(metaDir, "full_description.txt"), "", created, skipped, skipExisting);
4730
- await safeWrite(join8(metaDir, "video.txt"), "", created, skipped, skipExisting);
4731
- 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");
4732
4720
  await mkdir6(ssDir, { recursive: true });
4733
- await safeWrite(join8(ssDir, ".gitkeep"), "", created, skipped, skipExisting);
4721
+ await safeWrite(join7(ssDir, ".gitkeep"), "", created, skipped, skipExisting);
4734
4722
  if (ci === "github") {
4735
4723
  const workflow = githubActionsTemplate(pkg);
4736
- const workflowDir = join8(dir, ".github", "workflows");
4737
- 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);
4738
4726
  } else if (ci === "gitlab") {
4739
4727
  const pipeline = gitlabCiTemplate(pkg);
4740
- 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);
4741
4729
  }
4742
4730
  return { created, skipped };
4743
4731
  }
@@ -4832,13 +4820,13 @@ var DEFAULT_PREFLIGHT_CONFIG = {
4832
4820
  };
4833
4821
 
4834
4822
  // src/preflight/config.ts
4835
- import { readFile as readFile10 } from "fs/promises";
4823
+ import { readFile as readFile9 } from "fs/promises";
4836
4824
  var VALID_SEVERITIES = /* @__PURE__ */ new Set(["critical", "error", "warning", "info"]);
4837
4825
  async function loadPreflightConfig(configPath) {
4838
4826
  const path = configPath || ".preflightrc.json";
4839
4827
  let raw;
4840
4828
  try {
4841
- raw = await readFile10(path, "utf-8");
4829
+ raw = await readFile9(path, "utf-8");
4842
4830
  } catch {
4843
4831
  return { ...DEFAULT_PREFLIGHT_CONFIG };
4844
4832
  }
@@ -5755,8 +5743,8 @@ ${fileList}` + (hasPageSizeCompat ? "\nandroid:pageSizeCompat is set, so the app
5755
5743
  }
5756
5744
 
5757
5745
  // src/preflight/scanners/metadata-scanner.ts
5758
- import { readdir as readdir5, stat as stat6, readFile as readFile11 } from "fs/promises";
5759
- 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";
5760
5748
  var SAFE_LANG = /^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$/;
5761
5749
  var FILE_MAP2 = {
5762
5750
  "title.txt": "title",
@@ -5782,7 +5770,7 @@ var metadataScanner = {
5782
5770
  const findings = [];
5783
5771
  let entries;
5784
5772
  try {
5785
- entries = await readdir5(dir);
5773
+ entries = await readdir4(dir);
5786
5774
  } catch {
5787
5775
  findings.push({
5788
5776
  scanner: "metadata",
@@ -5807,14 +5795,14 @@ var metadataScanner = {
5807
5795
  return findings;
5808
5796
  }
5809
5797
  for (const lang of locales) {
5810
- const langDir = join9(dir, lang);
5811
- const langStat = await stat6(langDir).catch(() => null);
5798
+ const langDir = join8(dir, lang);
5799
+ const langStat = await stat5(langDir).catch(() => null);
5812
5800
  if (!langStat?.isDirectory()) continue;
5813
5801
  const fields = {};
5814
5802
  for (const [fileName, field] of Object.entries(FILE_MAP2)) {
5815
- const filePath = join9(langDir, fileName);
5803
+ const filePath = join8(langDir, fileName);
5816
5804
  try {
5817
- const content = await readFile11(filePath, "utf-8");
5805
+ const content = await readFile10(filePath, "utf-8");
5818
5806
  fields[field] = content.trimEnd();
5819
5807
  } catch {
5820
5808
  }
@@ -5853,9 +5841,9 @@ var metadataScanner = {
5853
5841
  let totalScreenshots = 0;
5854
5842
  let phoneScreenshots = 0;
5855
5843
  for (const ssDir of SCREENSHOT_DIRS) {
5856
- const ssPath = join9(langDir, "images", ssDir);
5844
+ const ssPath = join8(langDir, "images", ssDir);
5857
5845
  try {
5858
- const ssEntries = await readdir5(ssPath);
5846
+ const ssEntries = await readdir4(ssPath);
5859
5847
  const imageFiles = ssEntries.filter((f) => /\.(png|jpe?g|webp)$/i.test(f));
5860
5848
  totalScreenshots += imageFiles.length;
5861
5849
  if (ssDir === "phoneScreenshots") {
@@ -5885,9 +5873,9 @@ var metadataScanner = {
5885
5873
  }
5886
5874
  }
5887
5875
  const defaultLang = locales.includes("en-US") ? "en-US" : locales[0];
5888
- const privacyPath = join9(dir, defaultLang, "privacy_policy_url.txt");
5876
+ const privacyPath = join8(dir, defaultLang, "privacy_policy_url.txt");
5889
5877
  try {
5890
- const url = await readFile11(privacyPath, "utf-8");
5878
+ const url = await readFile10(privacyPath, "utf-8");
5891
5879
  if (!url.trim()) throw new Error("empty");
5892
5880
  } catch {
5893
5881
  findings.push({
@@ -5912,11 +5900,11 @@ var metadataScanner = {
5912
5900
  };
5913
5901
 
5914
5902
  // src/preflight/scanners/secrets-scanner.ts
5915
- import { readFile as readFile12 } from "fs/promises";
5903
+ import { readFile as readFile11 } from "fs/promises";
5916
5904
 
5917
5905
  // src/preflight/scan-files.ts
5918
- import { readdir as readdir6, stat as stat7 } from "fs/promises";
5919
- 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";
5920
5908
  var DEFAULT_SKIP_DIRS = /* @__PURE__ */ new Set([
5921
5909
  ".git",
5922
5910
  "node_modules",
@@ -5933,20 +5921,20 @@ async function collectSourceFiles(dir, extensions, skipDirs = DEFAULT_SKIP_DIRS,
5933
5921
  const files = [];
5934
5922
  let entries;
5935
5923
  try {
5936
- entries = await readdir6(dir);
5924
+ entries = await readdir5(dir);
5937
5925
  } catch {
5938
5926
  return files;
5939
5927
  }
5940
5928
  for (const entry of entries) {
5941
5929
  if (skipDirs.has(entry)) continue;
5942
- const fullPath = join10(dir, entry);
5943
- const s = await stat7(fullPath).catch(() => null);
5930
+ const fullPath = join9(dir, entry);
5931
+ const s = await stat6(fullPath).catch(() => null);
5944
5932
  if (!s) continue;
5945
5933
  if (s.isDirectory()) {
5946
5934
  const sub = await collectSourceFiles(fullPath, extensions, skipDirs, maxDepth - 1);
5947
5935
  files.push(...sub);
5948
5936
  } else if (s.isFile()) {
5949
- const ext = extname3(entry).toLowerCase();
5937
+ const ext = extname2(entry).toLowerCase();
5950
5938
  if (extensions.has(ext) || entry.endsWith(".gradle.kts")) {
5951
5939
  files.push(fullPath);
5952
5940
  }
@@ -6032,7 +6020,7 @@ var secretsScanner = {
6032
6020
  for (const filePath of files) {
6033
6021
  let content;
6034
6022
  try {
6035
- content = await readFile12(filePath, "utf-8");
6023
+ content = await readFile11(filePath, "utf-8");
6036
6024
  } catch {
6037
6025
  continue;
6038
6026
  }
@@ -6060,7 +6048,7 @@ var secretsScanner = {
6060
6048
  };
6061
6049
 
6062
6050
  // src/preflight/scanners/billing-scanner.ts
6063
- import { readFile as readFile13 } from "fs/promises";
6051
+ import { readFile as readFile12 } from "fs/promises";
6064
6052
  var BILLING_PATTERNS = [
6065
6053
  {
6066
6054
  ruleId: "billing-stripe-sdk",
@@ -6121,7 +6109,7 @@ var billingScanner = {
6121
6109
  for (const filePath of files) {
6122
6110
  let content;
6123
6111
  try {
6124
- content = await readFile13(filePath, "utf-8");
6112
+ content = await readFile12(filePath, "utf-8");
6125
6113
  } catch {
6126
6114
  continue;
6127
6115
  }
@@ -6147,7 +6135,7 @@ var billingScanner = {
6147
6135
  };
6148
6136
 
6149
6137
  // src/preflight/scanners/privacy-scanner.ts
6150
- import { readFile as readFile14 } from "fs/promises";
6138
+ import { readFile as readFile13 } from "fs/promises";
6151
6139
  var TRACKING_SDKS = [
6152
6140
  {
6153
6141
  name: "Facebook SDK",
@@ -6187,7 +6175,7 @@ var privacyScanner = {
6187
6175
  for (const filePath of files) {
6188
6176
  let content;
6189
6177
  try {
6190
- content = await readFile14(filePath, "utf-8");
6178
+ content = await readFile13(filePath, "utf-8");
6191
6179
  } catch {
6192
6180
  continue;
6193
6181
  }
@@ -6560,13 +6548,13 @@ function sortResults(items, sortSpec) {
6560
6548
 
6561
6549
  // src/commands/plugin-scaffold.ts
6562
6550
  import { mkdir as mkdir7, writeFile as writeFile7 } from "fs/promises";
6563
- import { join as join11 } from "path";
6551
+ import { join as join10 } from "path";
6564
6552
  async function scaffoldPlugin(options) {
6565
6553
  const { name, dir, description = `GPC plugin: ${name}` } = options;
6566
6554
  const pluginName = name.startsWith("gpc-plugin-") ? name : `gpc-plugin-${name}`;
6567
6555
  const shortName = pluginName.replace(/^gpc-plugin-/, "");
6568
- const srcDir = join11(dir, "src");
6569
- const testDir = join11(dir, "tests");
6556
+ const srcDir = join10(dir, "src");
6557
+ const testDir = join10(dir, "tests");
6570
6558
  await mkdir7(srcDir, { recursive: true });
6571
6559
  await mkdir7(testDir, { recursive: true });
6572
6560
  const files = [];
@@ -6602,7 +6590,7 @@ async function scaffoldPlugin(options) {
6602
6590
  vitest: "^3.0.0"
6603
6591
  }
6604
6592
  };
6605
- 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");
6606
6594
  files.push("package.json");
6607
6595
  const tsconfig = {
6608
6596
  compilerOptions: {
@@ -6618,7 +6606,7 @@ async function scaffoldPlugin(options) {
6618
6606
  },
6619
6607
  include: ["src"]
6620
6608
  };
6621
- 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");
6622
6610
  files.push("tsconfig.json");
6623
6611
  const srcContent = `import { definePlugin } from "@gpc-cli/plugin-sdk";
6624
6612
  import type { CommandEvent, CommandResult } from "@gpc-cli/plugin-sdk";
@@ -6651,7 +6639,7 @@ export const plugin = definePlugin({
6651
6639
  },
6652
6640
  });
6653
6641
  `;
6654
- await writeFile7(join11(srcDir, "index.ts"), srcContent);
6642
+ await writeFile7(join10(srcDir, "index.ts"), srcContent);
6655
6643
  files.push("src/index.ts");
6656
6644
  const testContent = `import { describe, it, expect, vi } from "vitest";
6657
6645
  import { plugin } from "../src/index";
@@ -6676,7 +6664,7 @@ describe("${pluginName}", () => {
6676
6664
  });
6677
6665
  });
6678
6666
  `;
6679
- await writeFile7(join11(testDir, "plugin.test.ts"), testContent);
6667
+ await writeFile7(join10(testDir, "plugin.test.ts"), testContent);
6680
6668
  files.push("tests/plugin.test.ts");
6681
6669
  return { dir, files };
6682
6670
  }
@@ -6787,7 +6775,7 @@ async function sendWebhook(config, payload, target) {
6787
6775
  }
6788
6776
 
6789
6777
  // src/commands/internal-sharing.ts
6790
- import { extname as extname4 } from "path";
6778
+ import { extname as extname3 } from "path";
6791
6779
  async function uploadInternalSharing(client, packageName, filePath, fileType) {
6792
6780
  const resolvedType = fileType ?? detectFileType(filePath);
6793
6781
  const validation = await validateUploadFile(filePath);
@@ -6814,7 +6802,7 @@ ${validation.errors.join("\n")}`,
6814
6802
  };
6815
6803
  }
6816
6804
  function detectFileType(filePath) {
6817
- const ext = extname4(filePath).toLowerCase();
6805
+ const ext = extname3(filePath).toLowerCase();
6818
6806
  if (ext === ".aab") return "bundle";
6819
6807
  if (ext === ".apk") return "apk";
6820
6808
  throw new GpcError(
@@ -6862,7 +6850,7 @@ async function downloadGeneratedApk(client, packageName, versionCode, apkId, out
6862
6850
  }
6863
6851
 
6864
6852
  // src/commands/bundle-analysis.ts
6865
- import { readFile as readFile15, stat as stat8 } from "fs/promises";
6853
+ import { readFile as readFile14, stat as stat7 } from "fs/promises";
6866
6854
  var EOCD_SIGNATURE = 101010256;
6867
6855
  var CD_SIGNATURE = 33639248;
6868
6856
  var MODULE_SUBDIRS = /* @__PURE__ */ new Set(["dex", "manifest", "res", "assets", "lib", "resources.pb", "root"]);
@@ -6938,11 +6926,11 @@ function detectFileType2(filePath) {
6938
6926
  return "apk";
6939
6927
  }
6940
6928
  async function analyzeBundle(filePath) {
6941
- const fileInfo = await stat8(filePath).catch(() => null);
6929
+ const fileInfo = await stat7(filePath).catch(() => null);
6942
6930
  if (!fileInfo || !fileInfo.isFile()) {
6943
6931
  throw new Error(`File not found: ${filePath}`);
6944
6932
  }
6945
- const buf = await readFile15(filePath);
6933
+ const buf = await readFile14(filePath);
6946
6934
  const cdEntries = parseCentralDirectory(buf);
6947
6935
  const fileType = detectFileType2(filePath);
6948
6936
  const isAab = fileType === "aab";
@@ -7028,7 +7016,7 @@ function topFiles(analysis, n = 20) {
7028
7016
  async function checkBundleSize(analysis, configPath = ".bundlesize.json") {
7029
7017
  let config;
7030
7018
  try {
7031
- const raw = await readFile15(configPath, "utf-8");
7019
+ const raw = await readFile14(configPath, "utf-8");
7032
7020
  config = JSON.parse(raw);
7033
7021
  } catch {
7034
7022
  return { passed: true, violations: [] };
@@ -8351,10 +8339,10 @@ function compareFingerprints(a, b) {
8351
8339
 
8352
8340
  // src/utils/hash.ts
8353
8341
  import { createHash } from "crypto";
8354
- import { stat as stat9 } from "fs/promises";
8342
+ import { stat as stat8 } from "fs/promises";
8355
8343
  async function sha256File(filePath) {
8356
8344
  const hash = createHash("sha256");
8357
- const { size } = await stat9(filePath);
8345
+ const { size } = await stat8(filePath);
8358
8346
  if (size === 0) return hash.digest("hex");
8359
8347
  const { createReadStream } = await import("fs");
8360
8348
  const stream = createReadStream(filePath);
@@ -8367,8 +8355,8 @@ async function sha256File(filePath) {
8367
8355
  }
8368
8356
 
8369
8357
  // src/commands/image-sync.ts
8370
- import { readdir as readdir7 } from "fs/promises";
8371
- import { join as join12, extname as extname5 } from "path";
8358
+ import { readdir as readdir6 } from "fs/promises";
8359
+ import { join as join11, extname as extname4 } from "path";
8372
8360
  import { PlayApiError } from "@gpc-cli/api";
8373
8361
  var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg"]);
8374
8362
  var ALL_IMAGE_TYPES2 = [
@@ -8383,15 +8371,15 @@ var ALL_IMAGE_TYPES2 = [
8383
8371
  ];
8384
8372
  async function scanLocalImages(dir) {
8385
8373
  try {
8386
- const entries = await readdir7(dir, { withFileTypes: true });
8387
- return entries.filter((e) => e.isFile() && IMAGE_EXTENSIONS.has(extname5(e.name).toLowerCase())).map((e) => e.name).sort();
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();
8388
8376
  } catch {
8389
8377
  return [];
8390
8378
  }
8391
8379
  }
8392
8380
  async function scanLanguages(dir) {
8393
8381
  try {
8394
- const entries = await readdir7(dir, { withFileTypes: true });
8382
+ const entries = await readdir6(dir, { withFileTypes: true });
8395
8383
  return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
8396
8384
  } catch {
8397
8385
  return [];
@@ -8416,7 +8404,7 @@ async function syncImages(client, packageName, dir, options) {
8416
8404
  try {
8417
8405
  for (const language of languages) {
8418
8406
  for (const imageType of imageTypes) {
8419
- const localDir = join12(dir, language, imageType);
8407
+ const localDir = join11(dir, language, imageType);
8420
8408
  const localFiles = await scanLocalImages(localDir);
8421
8409
  let remoteImages;
8422
8410
  try {
@@ -8431,7 +8419,7 @@ async function syncImages(client, packageName, dir, options) {
8431
8419
  const remoteSha256Set = new Set(remoteImages.map((img) => img.sha256.toLowerCase()));
8432
8420
  const localHashes = /* @__PURE__ */ new Map();
8433
8421
  for (const file of localFiles) {
8434
- const hash = await sha256File(join12(localDir, file));
8422
+ const hash = await sha256File(join11(localDir, file));
8435
8423
  localHashes.set(file, hash);
8436
8424
  }
8437
8425
  const localSha256Set = new Set(localHashes.values());
@@ -8459,7 +8447,7 @@ async function syncImages(client, packageName, dir, options) {
8459
8447
  details.push({ language, imageType, file, action: "skip", reason: "sha256 match" });
8460
8448
  } else {
8461
8449
  if (!options?.dryRun) {
8462
- const filePath = join12(localDir, file);
8450
+ const filePath = join11(localDir, file);
8463
8451
  const check = await validateImage(filePath, imageType);
8464
8452
  if (!check.valid) {
8465
8453
  throw new GpcError(
@@ -8904,6 +8892,7 @@ export {
8904
8892
  isValidBcp47,
8905
8893
  isValidReportType,
8906
8894
  isValidStatsDimension,
8895
+ isVersionedNotesDir,
8907
8896
  lintListing,
8908
8897
  lintListings,
8909
8898
  lintLocalListings,
@@ -8947,6 +8936,7 @@ export {
8947
8936
  pullListings,
8948
8937
  pushListings,
8949
8938
  readListingsFromDir,
8939
+ readReleaseNotesForVersion,
8950
8940
  readReleaseNotesFromDir,
8951
8941
  redactAuditArgs,
8952
8942
  redactSensitive,