@gpc-cli/core 0.9.36 → 0.9.38

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, 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';
4
+ import { PlayApiClient, Track, ExternallyHostedApk, ExternallyHostedApkResponse, UploadProgressEvent, ResumableUploadOptions, Listing, ImageType, CountryAvailability, Image, AppDetails, Review, ReviewReplyResponse, Subscription, SubscriptionOffer, OffersListResponse, BasePlanMigratePricesRequest, InAppProduct, Order, SubscriptionDeferResponse, SubscriptionsV2DeferResponse, ProductPurchase, ProductPurchaseV2, 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;
@@ -541,6 +541,11 @@ declare function refundOrder(client: PlayApiClient, packageName: string, orderId
541
541
  fullRefund?: boolean;
542
542
  proratedRefund?: boolean;
543
543
  }): Promise<void>;
544
+ declare function getOrderDetails(client: PlayApiClient, packageName: string, orderId: string): Promise<Order>;
545
+ declare function batchGetOrders(client: PlayApiClient, packageName: string, orderIds: string[]): Promise<Order[]>;
546
+ declare function getProductPurchaseV2(client: PlayApiClient, packageName: string, token: string): Promise<ProductPurchaseV2>;
547
+ declare function cancelSubscriptionV2(client: PlayApiClient, packageName: string, token: string, cancellationType?: string): Promise<void>;
548
+ declare function deferSubscriptionV2(client: PlayApiClient, packageName: string, token: string, desiredExpiryTime: string): Promise<SubscriptionsV2DeferResponse>;
544
549
 
545
550
  declare const PERMISSION_PROPAGATION_WARNING = "Note: Permission changes may take up to 48 hours to propagate.";
546
551
  interface ListUsersOptions {
@@ -885,6 +890,8 @@ interface ParsedManifest {
885
890
  services: ManifestComponent[];
886
891
  receivers: ManifestComponent[];
887
892
  providers: ManifestComponent[];
893
+ /** Set when the manifest could not be fully parsed — manifest-dependent scanners should skip. */
894
+ _parseError?: string;
888
895
  }
889
896
  interface ManifestFeature {
890
897
  name: string;
@@ -1174,4 +1181,26 @@ declare function trackBreachState(packageName: string, isBreaching: boolean): Pr
1174
1181
  declare function sendNotification(title: string, body: string): void;
1175
1182
  declare function statusHasBreach(status: AppStatus): boolean;
1176
1183
 
1177
- export { ApiError, type AppInfo, type AppStatus, type AuditEntry, type BatchSyncResult, type BundleAnalysis, type BundleComparison, type BundleEntry, type BundleSizeCheckResult, type BundleSizeConfig, type CommandContext, ConfigError, DEFAULT_LIMITS, DEFAULT_PREFLIGHT_CONFIG, type DiffToken, type DiscoverPluginsOptions, type DryRunPublishResult, type DryRunResult, type DryRunUploadResult, type ExportImagesOptions, type ExportImagesSummary, type FastlaneDetection, type FastlaneLane, type FieldLintResult, type FileValidationResult, type FindingSeverity, GOOGLE_PLAY_LANGUAGES, type GetAppStatusOptions, type GitNotesOptions, type GitReleaseNotes, GpcError, type ImageValidationResult, type InitOptions, type InitResult, type InternalSharingUploadResult, type ListIapOptions, type ListSubscriptionsOptions, type ListUsersOptions, type ListVoidedOptions, type ListingDiff, type ListingFieldLimits, type ListingLintResult, type ListingsResult, type LoadedPlugin, type MigrationResult, NetworkError, type OneTimeProductDiff, PERMISSION_PROPAGATION_WARNING, type ParsedManifest, type ParsedMonth, PluginManager, type PreflightConfig, type PreflightFinding, type PreflightOptions, type PreflightResult, type PreflightScanner, type PublishOptions, type PublishResult, type PushResult, type QuotaUsage, type ReleaseDiff, type ReleaseNotesValidation, type ReleaseStatusResult, type ReviewAnalysis, type ReviewExportOptions, type ReviewsFilterOptions, SENSITIVE_ARG_KEYS, SENSITIVE_KEYS, SEVERITY_ORDER, type ScaffoldOptions, type ScaffoldResult, type Spinner, type StatusDiff, type StatusRelease, type StatusReviews, type StatusVitalMetric, type SubscriptionAnalytics, type SubscriptionDiff, type SyncResult, type ThresholdResult, type TrainConfig, type TrainState, type UploadResult, type ValidateCheck, type ValidateOptions, type ValidateResult, type VersionVitalsComparison, type VersionVitalsRow, type VitalsOverview, type VitalsQueryOptions, type VitalsTrendComparison, type WatchOptions, type WatchVitalsOptions, type WebhookPayload, abortTrain, acknowledgeProductPurchase, activateBasePlan, activateOffer, activatePurchaseOption, addRecoveryTargeting, addTesters, advanceTrain, analyzeBundle, analyzeRemoteListings, analyzeReviews, batchSyncInAppProducts, cancelRecoveryAction, cancelSubscriptionPurchase, checkBundleSize, checkThreshold, clearAuditLog, compareBundles, compareVersionVitals, compareVitalsTrend, computeStatusDiff, consumeProductPurchase, convertRegionPrices, createAuditEntry, createDeviceTier, createEnterpriseApp, createExternalTransaction, createGrant, createInAppProduct, createOffer, createOneTimeOffer, createOneTimeProduct, createPurchaseOption, createRecoveryAction, createSpinner, createSubscription, createTrack, deactivateBasePlan, deactivateOffer, deactivatePurchaseOption, deferSubscriptionPurchase, deleteBasePlan, deleteGrant, deleteImage, deleteInAppProduct, deleteListing, deleteOffer, deleteOneTimeOffer, deleteOneTimeProduct, deleteSubscription, deployRecoveryAction, detectFastlane, detectOutputFormat, diffListings, diffListingsCommand, diffListingsEnhanced, diffOneTimeProduct, diffReleases, diffSubscription, discoverPlugins, downloadGeneratedApk, downloadReport, exportDataSafety, exportImages, exportReviews, fetchReleaseNotes, formatCustomPayload, formatDiscordPayload, formatJunit, formatOutput, formatSlackPayload, formatStatusDiff, formatStatusSummary, formatStatusTable, formatWordDiff, generateMigrationPlan, generateNotesFromGit, getAllScannerNames, getAppInfo, getAppStatus, getCountryAvailability, getDataSafety, getDeviceTier, getExternalTransaction, getInAppProduct, getListings, getOffer, getOneTimeOffer, getOneTimeProduct, getProductPurchase, getPurchaseOption, getQuotaUsage, getReleasesStatus, getReview, getSubscription, getSubscriptionAnalytics, getSubscriptionPurchase, getTrainStatus, getUser, getVitalsAnomalies, getVitalsAnr, getVitalsBattery, getVitalsCrashes, getVitalsLmk, getVitalsMemory, getVitalsOverview, getVitalsRendering, getVitalsStartup, importDataSafety, importTestersFromCsv, initAudit, initProject, inviteUser, isFinancialReportType, isStatsReportType, isValidBcp47, isValidReportType, isValidStatsDimension, lintListing, lintListings, lintLocalListings, listAchievements, listAuditEvents, listDeviceTiers, listEnterpriseApps, listEvents, listGeneratedApks, listGrants, listImages, listInAppProducts, listLeaderboards, listOffers, listOneTimeOffers, listOneTimeProducts, listPurchaseOptions, listRecoveryActions, listReports, listReviews, listSubscriptions, listTesters, listTracks, listUsers, listVoidedPurchases, loadPreflightConfig, loadStatusCache, maybePaginate, migratePrices, parseAppfile, parseFastfile, parseGrantArg, parseMonth, pauseTrain, promoteRelease, publish, pullListings, pushListings, readListingsFromDir, readReleaseNotesFromDir, redactAuditArgs, redactSensitive, refundExternalTransaction, refundOrder, refundSubscriptionV2, removeTesters, removeUser, replyToReview, revokeSubscriptionPurchase, runPreflight, runWatchLoop, safePath, safePathWithin, saveStatusCache, scaffoldPlugin, searchAuditEvents, searchVitalsErrors, sendNotification, sendWebhook, sortResults, startTrain, statusHasBreach, syncInAppProducts, topFiles, trackBreachState, updateAppDetails, updateDataSafety, updateGrant, updateInAppProduct, updateListing, updateOffer, updateOneTimeOffer, updateOneTimeProduct, updateRollout, updateSubscription, updateTrackConfig, updateUser, uploadExternallyHosted, uploadImage, uploadInternalSharing, uploadRelease, validateImage, validateLanguageCode, validatePackageName, validatePreSubmission, validateReleaseNotes, validateSku, validateTrackName, validateUploadFile, validateVersionCode, watchVitalsWithAutoHalt, wordDiff, writeAuditLog, writeListingsToDir, writeMigrationOutput };
1184
+ interface ChangelogEntry {
1185
+ version: string;
1186
+ title: string;
1187
+ date: string;
1188
+ body: string;
1189
+ url: string;
1190
+ }
1191
+ interface FetchChangelogOptions {
1192
+ limit?: number;
1193
+ version?: string;
1194
+ }
1195
+ /**
1196
+ * Fetch release history from GitHub Releases API.
1197
+ * Public endpoint — no auth required (60 req/hour rate limit).
1198
+ */
1199
+ declare function fetchChangelog(options?: FetchChangelogOptions): Promise<ChangelogEntry[]>;
1200
+ /**
1201
+ * Format a changelog entry as readable terminal text.
1202
+ * Strips markdown formatting for clean terminal output.
1203
+ */
1204
+ declare function formatChangelogEntry(entry: ChangelogEntry): string;
1205
+
1206
+ export { ApiError, type AppInfo, type AppStatus, type AuditEntry, type BatchSyncResult, type BundleAnalysis, type BundleComparison, type BundleEntry, type BundleSizeCheckResult, type BundleSizeConfig, type ChangelogEntry, type CommandContext, ConfigError, DEFAULT_LIMITS, DEFAULT_PREFLIGHT_CONFIG, type DiffToken, type DiscoverPluginsOptions, type DryRunPublishResult, type DryRunResult, type DryRunUploadResult, type ExportImagesOptions, type ExportImagesSummary, type FastlaneDetection, type FastlaneLane, type FetchChangelogOptions, type FieldLintResult, type FileValidationResult, type FindingSeverity, GOOGLE_PLAY_LANGUAGES, type GetAppStatusOptions, type GitNotesOptions, type GitReleaseNotes, GpcError, type ImageValidationResult, type InitOptions, type InitResult, type InternalSharingUploadResult, type ListIapOptions, type ListSubscriptionsOptions, type ListUsersOptions, type ListVoidedOptions, type ListingDiff, type ListingFieldLimits, type ListingLintResult, type ListingsResult, type LoadedPlugin, type MigrationResult, NetworkError, type OneTimeProductDiff, PERMISSION_PROPAGATION_WARNING, type ParsedManifest, type ParsedMonth, PluginManager, type PreflightConfig, type PreflightFinding, type PreflightOptions, type PreflightResult, type PreflightScanner, type PublishOptions, type PublishResult, type PushResult, type QuotaUsage, type ReleaseDiff, type ReleaseNotesValidation, type ReleaseStatusResult, type ReviewAnalysis, type ReviewExportOptions, type ReviewsFilterOptions, SENSITIVE_ARG_KEYS, SENSITIVE_KEYS, SEVERITY_ORDER, type ScaffoldOptions, type ScaffoldResult, type Spinner, type StatusDiff, type StatusRelease, type StatusReviews, type StatusVitalMetric, type SubscriptionAnalytics, type SubscriptionDiff, type SyncResult, type ThresholdResult, type TrainConfig, type TrainState, type UploadResult, type ValidateCheck, type ValidateOptions, type ValidateResult, type VersionVitalsComparison, type VersionVitalsRow, type VitalsOverview, type VitalsQueryOptions, type VitalsTrendComparison, type WatchOptions, type WatchVitalsOptions, type WebhookPayload, abortTrain, acknowledgeProductPurchase, activateBasePlan, activateOffer, activatePurchaseOption, addRecoveryTargeting, addTesters, advanceTrain, analyzeBundle, analyzeRemoteListings, analyzeReviews, batchGetOrders, batchSyncInAppProducts, cancelRecoveryAction, cancelSubscriptionPurchase, cancelSubscriptionV2, checkBundleSize, checkThreshold, clearAuditLog, compareBundles, compareVersionVitals, compareVitalsTrend, computeStatusDiff, consumeProductPurchase, convertRegionPrices, createAuditEntry, createDeviceTier, createEnterpriseApp, createExternalTransaction, createGrant, createInAppProduct, createOffer, createOneTimeOffer, createOneTimeProduct, createPurchaseOption, createRecoveryAction, createSpinner, createSubscription, createTrack, deactivateBasePlan, deactivateOffer, deactivatePurchaseOption, deferSubscriptionPurchase, deferSubscriptionV2, deleteBasePlan, deleteGrant, deleteImage, deleteInAppProduct, deleteListing, deleteOffer, deleteOneTimeOffer, deleteOneTimeProduct, deleteSubscription, deployRecoveryAction, detectFastlane, detectOutputFormat, diffListings, diffListingsCommand, diffListingsEnhanced, diffOneTimeProduct, diffReleases, diffSubscription, discoverPlugins, downloadGeneratedApk, downloadReport, exportDataSafety, exportImages, exportReviews, fetchChangelog, fetchReleaseNotes, formatChangelogEntry, formatCustomPayload, formatDiscordPayload, formatJunit, formatOutput, formatSlackPayload, formatStatusDiff, formatStatusSummary, formatStatusTable, formatWordDiff, generateMigrationPlan, generateNotesFromGit, getAllScannerNames, getAppInfo, getAppStatus, getCountryAvailability, getDataSafety, getDeviceTier, getExternalTransaction, getInAppProduct, getListings, getOffer, getOneTimeOffer, getOneTimeProduct, getOrderDetails, getProductPurchase, getProductPurchaseV2, getPurchaseOption, getQuotaUsage, getReleasesStatus, getReview, getSubscription, getSubscriptionAnalytics, getSubscriptionPurchase, getTrainStatus, getUser, getVitalsAnomalies, getVitalsAnr, getVitalsBattery, getVitalsCrashes, getVitalsLmk, getVitalsMemory, getVitalsOverview, getVitalsRendering, getVitalsStartup, importDataSafety, importTestersFromCsv, initAudit, initProject, inviteUser, isFinancialReportType, isStatsReportType, isValidBcp47, isValidReportType, isValidStatsDimension, lintListing, lintListings, lintLocalListings, listAchievements, listAuditEvents, listDeviceTiers, listEnterpriseApps, listEvents, listGeneratedApks, listGrants, listImages, listInAppProducts, listLeaderboards, listOffers, listOneTimeOffers, listOneTimeProducts, listPurchaseOptions, listRecoveryActions, listReports, listReviews, listSubscriptions, listTesters, listTracks, listUsers, listVoidedPurchases, loadPreflightConfig, loadStatusCache, maybePaginate, migratePrices, parseAppfile, parseFastfile, parseGrantArg, parseMonth, pauseTrain, promoteRelease, publish, pullListings, pushListings, readListingsFromDir, readReleaseNotesFromDir, redactAuditArgs, redactSensitive, refundExternalTransaction, refundOrder, refundSubscriptionV2, removeTesters, removeUser, replyToReview, revokeSubscriptionPurchase, runPreflight, runWatchLoop, safePath, safePathWithin, saveStatusCache, scaffoldPlugin, searchAuditEvents, searchVitalsErrors, sendNotification, sendWebhook, sortResults, startTrain, statusHasBreach, syncInAppProducts, topFiles, trackBreachState, updateAppDetails, updateDataSafety, updateGrant, updateInAppProduct, updateListing, updateOffer, updateOneTimeOffer, updateOneTimeProduct, updateRollout, updateSubscription, updateTrackConfig, updateUser, uploadExternallyHosted, uploadImage, uploadInternalSharing, uploadRelease, validateImage, validateLanguageCode, validatePackageName, validatePreSubmission, validateReleaseNotes, validateSku, validateTrackName, validateUploadFile, validateVersionCode, watchVitalsWithAutoHalt, wordDiff, writeAuditLog, writeListingsToDir, writeMigrationOutput };
package/dist/index.js CHANGED
@@ -115,7 +115,7 @@ function redactSensitive(data) {
115
115
  if (typeof data === "object") {
116
116
  const result = {};
117
117
  for (const [key, value] of Object.entries(data)) {
118
- if (SENSITIVE_KEYS.has(key) && typeof value === "string") {
118
+ if (SENSITIVE_KEYS.has(key)) {
119
119
  result[key] = REDACTED;
120
120
  } else {
121
121
  result[key] = redactSensitive(value);
@@ -363,6 +363,10 @@ function formatJunit(data, commandName = "command") {
363
363
  }
364
364
 
365
365
  // src/plugins.ts
366
+ var FIRST_PARTY_PLUGINS = /* @__PURE__ */ new Set([
367
+ "@gpc-cli/plugin-ci",
368
+ "@gpc-cli/plugin-sdk"
369
+ ]);
366
370
  var PluginManager = class {
367
371
  plugins = [];
368
372
  beforeHandlers = [];
@@ -373,7 +377,7 @@ var PluginManager = class {
373
377
  registeredCommands = [];
374
378
  /** Load and register a plugin */
375
379
  async load(plugin, manifest) {
376
- const isTrusted = manifest?.trusted ?? plugin.name.startsWith("@gpc-cli/");
380
+ const isTrusted = manifest?.trusted ?? FIRST_PARTY_PLUGINS.has(plugin.name);
377
381
  if (!isTrusted && manifest?.permissions) {
378
382
  validatePermissions(manifest.permissions);
379
383
  }
@@ -1658,6 +1662,14 @@ async function exportImages(client, packageName, dir, options) {
1658
1662
  const dirPath = join12(dir, task.language, task.imageType);
1659
1663
  await mkdir8(dirPath, { recursive: true });
1660
1664
  const response = await fetch(task.url);
1665
+ if (!response.ok) {
1666
+ throw new GpcError(
1667
+ `Failed to download image: HTTP ${response.status} for ${task.imageType} (${task.language})`,
1668
+ "LISTINGS_IMAGE_DOWNLOAD_FAILED",
1669
+ 4,
1670
+ "Check that the image URL is still valid. Re-run the export to retry."
1671
+ );
1672
+ }
1661
1673
  const buffer = Buffer.from(await response.arrayBuffer());
1662
1674
  const filePath = join12(dirPath, `${task.index}.png`);
1663
1675
  await writeFile10(filePath, buffer);
@@ -3292,6 +3304,35 @@ async function refundOrder(client, packageName, orderId, options) {
3292
3304
  validatePackageName(packageName);
3293
3305
  return client.orders.refund(packageName, orderId, options);
3294
3306
  }
3307
+ async function getOrderDetails(client, packageName, orderId) {
3308
+ validatePackageName(packageName);
3309
+ return client.orders.get(packageName, orderId);
3310
+ }
3311
+ async function batchGetOrders(client, packageName, orderIds) {
3312
+ validatePackageName(packageName);
3313
+ if (orderIds.length === 0) {
3314
+ throw new GpcError("No order IDs provided", "ORDERS_BATCH_EMPTY", 2, "Pass at least one order ID with --ids");
3315
+ }
3316
+ if (orderIds.length > 1e3) {
3317
+ throw new GpcError(`Too many order IDs (${orderIds.length}). Maximum is 1000.`, "ORDERS_BATCH_LIMIT", 2, "Split into multiple requests of 1000 or fewer");
3318
+ }
3319
+ return client.orders.batchGet(packageName, orderIds);
3320
+ }
3321
+ async function getProductPurchaseV2(client, packageName, token) {
3322
+ validatePackageName(packageName);
3323
+ return client.purchases.getProductV2(packageName, token);
3324
+ }
3325
+ async function cancelSubscriptionV2(client, packageName, token, cancellationType) {
3326
+ validatePackageName(packageName);
3327
+ const body = cancellationType ? { cancellationType } : void 0;
3328
+ return client.purchases.cancelSubscriptionV2(packageName, token, body);
3329
+ }
3330
+ async function deferSubscriptionV2(client, packageName, token, desiredExpiryTime) {
3331
+ validatePackageName(packageName);
3332
+ return client.purchases.deferSubscriptionV2(packageName, token, {
3333
+ deferralInfo: { desiredExpiryTime }
3334
+ });
3335
+ }
3295
3336
 
3296
3337
  // src/commands/pricing.ts
3297
3338
  async function convertRegionPrices(client, packageName, currencyCode, amount) {
@@ -3970,7 +4011,7 @@ function createSpinner(message) {
3970
4011
 
3971
4012
  // src/utils/train-state.ts
3972
4013
  import { mkdir as mkdir3, readFile as readFile7, writeFile as writeFile4 } from "fs/promises";
3973
- import { join as join5 } from "path";
4014
+ import { dirname, join as join5 } from "path";
3974
4015
  import { getCacheDir } from "@gpc-cli/config";
3975
4016
  function stateFile(packageName) {
3976
4017
  return join5(getCacheDir(), `train-${packageName}.json`);
@@ -3986,7 +4027,7 @@ async function readTrainState(packageName) {
3986
4027
  }
3987
4028
  async function writeTrainState(packageName, state) {
3988
4029
  const path = stateFile(packageName);
3989
- const dir = path.substring(0, path.lastIndexOf("/"));
4030
+ const dir = dirname(path);
3990
4031
  await mkdir3(dir, { recursive: true });
3991
4032
  await writeFile4(path, JSON.stringify(state, null, 2), "utf-8");
3992
4033
  }
@@ -4085,8 +4126,11 @@ async function advanceTrain(apiClient, reportingClient, packageName) {
4085
4126
  state.status = "paused";
4086
4127
  state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
4087
4128
  await writeTrainState(packageName, state);
4088
- throw new Error(
4089
- `Crash gate failed: ${(value * 100).toFixed(3)}% > max ${state.gates.crashes.max}%. Train paused.`
4129
+ throw new GpcError(
4130
+ `Crash gate failed: ${(value * 100).toFixed(3)}% > max ${state.gates.crashes.max}%. Train paused.`,
4131
+ "TRAIN_CRASH_GATE_FAILED",
4132
+ 6,
4133
+ "Review crash data with: gpc vitals crashes --days 1"
4090
4134
  );
4091
4135
  }
4092
4136
  }
@@ -4099,8 +4143,11 @@ async function advanceTrain(apiClient, reportingClient, packageName) {
4099
4143
  state.status = "paused";
4100
4144
  state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
4101
4145
  await writeTrainState(packageName, state);
4102
- throw new Error(
4103
- `ANR gate failed: ${(value * 100).toFixed(3)}% > max ${state.gates.anr.max}%. Train paused.`
4146
+ throw new GpcError(
4147
+ `ANR gate failed: ${(value * 100).toFixed(3)}% > max ${state.gates.anr.max}%. Train paused.`,
4148
+ "TRAIN_ANR_GATE_FAILED",
4149
+ 6,
4150
+ "Review ANR data with: gpc vitals anr --days 1"
4104
4151
  );
4105
4152
  }
4106
4153
  }
@@ -4110,7 +4157,7 @@ async function advanceTrain(apiClient, reportingClient, packageName) {
4110
4157
  }
4111
4158
  async function executeStage(apiClient, packageName, state, stageIndex) {
4112
4159
  const stage = state.stages[stageIndex];
4113
- if (!stage) throw new Error(`Stage ${stageIndex} not found`);
4160
+ if (!stage) throw new GpcError(`Stage ${stageIndex} not found`, "TRAIN_STAGE_NOT_FOUND", 1, "Check your release train configuration.");
4114
4161
  const rolloutFraction = stage.rollout / 100;
4115
4162
  await updateRollout(apiClient, packageName, stage.track, "increase", rolloutFraction);
4116
4163
  stage.executedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -4556,7 +4603,7 @@ async function loadPreflightConfig(configPath) {
4556
4603
  import { open as yauzlOpen } from "yauzl";
4557
4604
 
4558
4605
  // src/preflight/manifest-parser.ts
4559
- import * as protobuf from "protobufjs";
4606
+ import protobuf from "protobufjs";
4560
4607
  var RESOURCE_IDS = {
4561
4608
  16842752: "theme",
4562
4609
  16842753: "label",
@@ -4717,9 +4764,35 @@ async function readAab(aabPath) {
4717
4764
  `AAB is missing ${MANIFEST_PATH}. This does not appear to be a valid Android App Bundle.`
4718
4765
  );
4719
4766
  }
4720
- const manifest = decodeManifest(manifestBuf);
4767
+ let manifest;
4768
+ try {
4769
+ manifest = decodeManifest(manifestBuf);
4770
+ } catch (err) {
4771
+ const errMsg = err instanceof Error ? err.message : String(err);
4772
+ manifest = createFallbackManifest();
4773
+ manifest._parseError = `Manifest could not be fully parsed: ${errMsg}. Manifest-dependent checks will be skipped.`;
4774
+ }
4721
4775
  return { manifest, entries };
4722
4776
  }
4777
+ function createFallbackManifest() {
4778
+ return {
4779
+ packageName: "",
4780
+ versionCode: 0,
4781
+ versionName: "",
4782
+ minSdk: 0,
4783
+ targetSdk: 0,
4784
+ debuggable: false,
4785
+ testOnly: false,
4786
+ usesCleartextTraffic: false,
4787
+ extractNativeLibs: true,
4788
+ permissions: [],
4789
+ features: [],
4790
+ activities: [],
4791
+ services: [],
4792
+ receivers: [],
4793
+ providers: []
4794
+ };
4795
+ }
4723
4796
  function openAndScan(aabPath) {
4724
4797
  return new Promise((resolve2, reject) => {
4725
4798
  yauzlOpen(aabPath, { lazyEntries: true, autoClose: false }, (err, zipfile) => {
@@ -5868,11 +5941,23 @@ async function runPreflight(options) {
5868
5941
  failOn: options.failOn ?? fileConfig.failOn ?? DEFAULT_PREFLIGHT_CONFIG.failOn
5869
5942
  };
5870
5943
  const ctx = { config };
5944
+ const earlyFindings = [];
5871
5945
  if (options.aabPath) {
5872
5946
  ctx.aabPath = options.aabPath;
5873
5947
  const aab = await readAab(options.aabPath);
5874
5948
  ctx.manifest = aab.manifest;
5875
5949
  ctx.zipEntries = aab.entries;
5950
+ if (aab.manifest._parseError) {
5951
+ earlyFindings.push({
5952
+ scanner: "manifest-parser",
5953
+ ruleId: "manifest-parse-error",
5954
+ severity: "warning",
5955
+ title: "Manifest could not be fully parsed",
5956
+ message: aab.manifest._parseError,
5957
+ suggestion: "Manifest-dependent scanners (manifest, permissions, policy, privacy) were skipped. Other scanners (native-libs, size, secrets, billing) still ran."
5958
+ });
5959
+ ctx.manifest = void 0;
5960
+ }
5876
5961
  }
5877
5962
  if (options.metadataDir) ctx.metadataDir = options.metadataDir;
5878
5963
  if (options.sourceDir) ctx.sourceDir = options.sourceDir;
@@ -5888,7 +5973,7 @@ async function runPreflight(options) {
5888
5973
  return true;
5889
5974
  });
5890
5975
  const settled = await Promise.allSettled(applicableScanners.map((scanner) => scanner.scan(ctx)));
5891
- let findings = [];
5976
+ let findings = [...earlyFindings];
5892
5977
  for (let i = 0; i < settled.length; i++) {
5893
5978
  const result = settled[i];
5894
5979
  if (result.status === "fulfilled") {
@@ -6990,6 +7075,62 @@ function sendNotification(title, body) {
6990
7075
  function statusHasBreach(status) {
6991
7076
  return status.vitals.crashes.status === "breach" || status.vitals.anr.status === "breach" || status.vitals.slowStarts.status === "breach" || status.vitals.slowRender.status === "breach";
6992
7077
  }
7078
+
7079
+ // src/commands/changelog.ts
7080
+ var GITHUB_RELEASES_URL = "https://api.github.com/repos/yasserstudio/gpc/releases";
7081
+ var DOCS_CHANGELOG_URL = "https://yasserstudio.github.io/gpc/reference/changelog";
7082
+ async function fetchChangelog(options) {
7083
+ const limit = options?.limit ?? 5;
7084
+ const url = options?.version ? `${GITHUB_RELEASES_URL}/tags/${options.version.startsWith("v") ? options.version : `v${options.version}`}` : `${GITHUB_RELEASES_URL}?per_page=${Math.min(limit, 100)}`;
7085
+ const controller = new AbortController();
7086
+ const timer = setTimeout(() => controller.abort(), 1e4);
7087
+ let response;
7088
+ try {
7089
+ response = await fetch(url, {
7090
+ headers: {
7091
+ Accept: "application/vnd.github+json",
7092
+ "User-Agent": "gpc-cli"
7093
+ },
7094
+ signal: controller.signal
7095
+ });
7096
+ } catch {
7097
+ throw new Error(
7098
+ `Could not fetch changelog. View online: ${DOCS_CHANGELOG_URL}`
7099
+ );
7100
+ } finally {
7101
+ clearTimeout(timer);
7102
+ }
7103
+ if (!response.ok) {
7104
+ if (response.status === 404 && options?.version) {
7105
+ throw new Error(
7106
+ `Version ${options.version} not found. Run: gpc changelog --limit 10`
7107
+ );
7108
+ }
7109
+ throw new Error(
7110
+ `GitHub API returned ${response.status}. View online: ${DOCS_CHANGELOG_URL}`
7111
+ );
7112
+ }
7113
+ const data = await response.json();
7114
+ const releases = Array.isArray(data) ? data : [data];
7115
+ return releases.filter((r) => r.tag_name.startsWith("v")).map((r) => ({
7116
+ version: r.tag_name,
7117
+ title: r.name || r.tag_name,
7118
+ date: r.published_at ? r.published_at.split("T")[0] : "unknown",
7119
+ body: r.body || "No release notes.",
7120
+ url: r.html_url
7121
+ }));
7122
+ }
7123
+ function formatChangelogEntry(entry) {
7124
+ const lines = [];
7125
+ lines.push(`${entry.version} \u2014 ${entry.title}`);
7126
+ lines.push(`Released: ${entry.date}`);
7127
+ lines.push("");
7128
+ const body = entry.body.replace(/^### /gm, "").replace(/^## /gm, "").replace(/^# /gm, "").replace(/\*\*(.*?)\*\*/g, "$1").replace(/`([^`]+)`/g, "$1").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").trim();
7129
+ lines.push(body);
7130
+ lines.push("");
7131
+ lines.push(`Full notes: ${entry.url}`);
7132
+ return lines.join("\n");
7133
+ }
6993
7134
  export {
6994
7135
  ApiError,
6995
7136
  ConfigError,
@@ -7014,9 +7155,11 @@ export {
7014
7155
  analyzeBundle,
7015
7156
  analyzeRemoteListings,
7016
7157
  analyzeReviews2 as analyzeReviews,
7158
+ batchGetOrders,
7017
7159
  batchSyncInAppProducts,
7018
7160
  cancelRecoveryAction,
7019
7161
  cancelSubscriptionPurchase,
7162
+ cancelSubscriptionV2,
7020
7163
  checkBundleSize,
7021
7164
  checkThreshold,
7022
7165
  clearAuditLog,
@@ -7044,6 +7187,7 @@ export {
7044
7187
  deactivateOffer,
7045
7188
  deactivatePurchaseOption,
7046
7189
  deferSubscriptionPurchase,
7190
+ deferSubscriptionV2,
7047
7191
  deleteBasePlan,
7048
7192
  deleteGrant,
7049
7193
  deleteImage,
@@ -7068,7 +7212,9 @@ export {
7068
7212
  exportDataSafety,
7069
7213
  exportImages,
7070
7214
  exportReviews,
7215
+ fetchChangelog,
7071
7216
  fetchReleaseNotes,
7217
+ formatChangelogEntry,
7072
7218
  formatCustomPayload,
7073
7219
  formatDiscordPayload,
7074
7220
  formatJunit,
@@ -7092,7 +7238,9 @@ export {
7092
7238
  getOffer,
7093
7239
  getOneTimeOffer,
7094
7240
  getOneTimeProduct,
7241
+ getOrderDetails,
7095
7242
  getProductPurchase,
7243
+ getProductPurchaseV2,
7096
7244
  getPurchaseOption,
7097
7245
  getQuotaUsage,
7098
7246
  getReleasesStatus,