@gpc-cli/core 0.9.53 → 0.9.55

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/README.md CHANGED
@@ -45,22 +45,23 @@ const analysis = await analyzeBundle("./app.aab");
45
45
 
46
46
  ## Command Groups
47
47
 
48
- | Group | Functions |
49
- | ----------------- | ------------------------------------------------------------------------------------------------------------------- |
50
- | **Releases** | `uploadRelease`, `promoteRelease`, `updateRollout`, `getReleasesStatus`, `listTracks` |
51
- | **Listings** | `getListings`, `updateListing`, `pullListings`, `pushListings`, `diffListings` |
52
- | **Images** | `listImages`, `uploadImage`, `deleteImage` |
53
- | **Reviews** | `listReviews`, `getReview`, `replyToReview`, `exportReviews` |
54
- | **Vitals** | `getVitalsOverview`, `getVitalsCrashes`, `getVitalsAnr`, `getVitalsStartup`, `compareVitalsTrend`, `checkThreshold` |
55
- | **Subscriptions** | `listSubscriptions`, `createSubscription`, `updateSubscription`, `deleteSubscription`, `listOffers`, `createOffer` |
56
- | **IAP** | `listInAppProducts`, `createInAppProduct`, `syncInAppProducts` |
57
- | **Purchases** | `getProductPurchase`, `acknowledgeProductPurchase`, `refundOrder` |
58
- | **Reports** | `listReports`, `downloadReport` |
59
- | **Users** | `listUsers`, `inviteUser`, `updateUser`, `removeUser` |
60
- | **Testers** | `listTesters`, `addTesters`, `removeTesters`, `importTestersFromCsv` |
61
- | **Bundle** | `analyzeBundle`, `compareBundles` (zero-dependency AAB/APK size analysis) |
62
- | **Publishing** | `publish` (end-to-end: upload + track + notes + commit) |
63
- | **Validation** | `validateUploadFile`, `validateImage`, `validatePreSubmission` |
48
+ | Group | Functions |
49
+ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
50
+ | **Releases** | `uploadRelease`, `promoteRelease`, `updateRollout`, `getReleasesStatus`, `listTracks` |
51
+ | **Listings** | `getListings`, `updateListing`, `pullListings`, `pushListings`, `diffListings` |
52
+ | **Images** | `listImages`, `uploadImage`, `deleteImage` |
53
+ | **Reviews** | `listReviews`, `getReview`, `replyToReview`, `exportReviews` |
54
+ | **Vitals** | `getVitalsOverview`, `getVitalsCrashes`, `getVitalsAnr`, `getVitalsStartup`, `compareVitalsTrend`, `checkThreshold` |
55
+ | **Subscriptions** | `listSubscriptions`, `createSubscription`, `updateSubscription`, `deleteSubscription`, `listOffers`, `createOffer` |
56
+ | **IAP** | `listInAppProducts`, `createInAppProduct`, `syncInAppProducts` |
57
+ | **Purchases** | `getProductPurchase`, `acknowledgeProductPurchase`, `refundOrder` |
58
+ | **Reports** | `listReports`, `downloadReport` |
59
+ | **Users** | `listUsers`, `inviteUser`, `updateUser`, `removeUser` |
60
+ | **Testers** | `listTesters`, `addTesters`, `removeTesters`, `importTestersFromCsv` |
61
+ | **Bundle** | `analyzeBundle`, `compareBundles` (zero-dependency AAB/APK size analysis) |
62
+ | **Publishing** | `publish` (end-to-end: upload + track + notes + commit) |
63
+ | **Changelog** | `generateChangelog`, `fetchChangelog`, `formatChangelogEntry`, `buildLocaleBundle`, `renderPlayStore`, `renderMarkdown`, `renderJson`, `renderPrompt`, `translateBundle`, `resolveLocales` |
64
+ | **Validation** | `validateUploadFile`, `validateImage`, `validatePreSubmission` |
64
65
 
65
66
  ## Utilities
66
67
 
package/dist/index.d.ts CHANGED
@@ -109,6 +109,7 @@ interface AppInfo {
109
109
  }
110
110
  declare function getAppInfo(client: PlayApiClient, packageName: string): Promise<AppInfo>;
111
111
 
112
+ declare function waitForBundleProcessing(client: PlayApiClient, packageName: string, editId: string, versionCode: number, backoff?: number[]): Promise<void>;
112
113
  interface UploadResult {
113
114
  versionCode: number;
114
115
  track: string;
@@ -183,6 +184,19 @@ declare function fetchReleaseNotes(client: PlayApiClient, packageName: string, t
183
184
  language: string;
184
185
  text: string;
185
186
  }[]>;
187
+ interface ApplyReleaseNotesResult {
188
+ track: string;
189
+ versionCodes: string[];
190
+ localeCount: number;
191
+ releaseNotes: {
192
+ language: string;
193
+ text: string;
194
+ }[];
195
+ }
196
+ declare function applyReleaseNotes(client: PlayApiClient, packageName: string, track: string, releaseNotes: {
197
+ language: string;
198
+ text: string;
199
+ }[], commitOptions?: EditCommitOptions): Promise<ApplyReleaseNotesResult>;
186
200
  interface ReleaseDiff {
187
201
  field: string;
188
202
  track1Value: string;
@@ -1398,6 +1412,11 @@ interface TranslatedBundle extends LocaleBundle {
1398
1412
  failures: TranslationFailure[];
1399
1413
  }
1400
1414
  declare function translateBundle(bundle: LocaleBundle, options: TranslateBundleOptions): Promise<TranslatedBundle>;
1415
+ declare function validateBundleForApply(bundle: LocaleBundle): string[];
1416
+ declare function bundleToReleaseNotes(bundle: LocaleBundle): {
1417
+ language: string;
1418
+ text: string;
1419
+ }[];
1401
1420
 
1402
1421
  type Renderer = (g: GeneratedChangelog) => string;
1403
1422
  declare const RENDERERS: Record<OutputMode, Renderer>;
@@ -1453,4 +1472,4 @@ declare function decodeNotification(base64Payload: string): DecodedNotification;
1453
1472
  */
1454
1473
  declare function formatNotification(notification: DecodedNotification): Record<string, unknown>;
1455
1474
 
1456
- export { ApiError, type AppInfo, type AppStatus, type AuditEntry, type BatchSyncResult, type BundleAnalysis, type BundleComparison, type BundleEntry, type BundleSizeCheckResult, type BundleSizeConfig, type ChangelogEntry, type CommandContext, type CommitCluster, ConfigError, type CreateEnterpriseAppParams, DEFAULT_LIMITS, DEFAULT_MODELS, DEFAULT_PREFLIGHT_CONFIG, type DecodedNotification, type DiffToken, type DiscoverPluginsOptions, type DryRunPublishResult, type DryRunResult, type DryRunUploadResult, type ErrorReason, type ExportImagesOptions, type ExportImagesSummary, type FastlaneDetection, type FastlaneLane, type FetchChangelogOptions, type FieldLintResult, type FileValidationResult, type FindingSeverity, GOOGLE_PLAY_LANGUAGES, type GenerateOptions, type GeneratedChangelog, type GetAppStatusOptions, type GitNotesOptions, type GitReleaseNotes, type GitRunner, GpcError, type 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 LocaleBundle, type LocaleEntry, type MigrationResult, NetworkError, type OneTimeProductDiff, type OutputMode, PERMISSION_PROPAGATION_WARNING, PLACEHOLDER_TEXT, PLAY_STORE_LIMIT, PROVIDER_WHITELIST, type ParsedCommit, type ParsedManifest, type ParsedMonth, type PlayStoreFormat, type PlayStoreRenderOptions, PluginManager, type PreflightConfig, type PreflightFinding, type PreflightOptions, type PreflightResult, type PreflightScanner, type Provider, type PublishOptions, type PublishResult, type PushResult, type QuotaUsage, RENDERERS, type RawCommit, type ReleaseDiff, type ReleaseNotesValidation, type ReleaseStatusResult, type Renderer, type ResolveAiConfigOptions, type ResolveLocalesOptions, type ReviewAnalysis, type ReviewExportOptions, type ReviewsFilterOptions, type RtdnStatus, SECTION_ORDER, 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 TranslateBundleOptions, type TranslatedBundle, type TranslationFailure, type TranslationPath, type TranslationResult, type Translator, type TranslatorConfig, 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, addRecoveryTargeting, addTesters, advanceTrain, analyzeBundle, analyzeRemoteListings, analyzeReviews, batchGetOrders, batchSyncInAppProducts, buildLocaleBundle, cancelRecoveryAction, cancelSubscriptionPurchase, cancelSubscriptionV2, checkBundleSize, checkThreshold, classifyError, clearAuditLog, compareBundles, compareVersionVitals, compareVitalsTrend, computeStatusDiff, consumeProductPurchase, convertRegionPrices, createAuditEntry, createDeviceTier, createEnterpriseApp, createExternalTransaction, createGrant, createInAppProduct, createOffer, createOneTimeOffer, createOneTimeProduct, createRecoveryAction, createSpinner, createSubscription, createTrack, createTranslator, deactivateBasePlan, deactivateOffer, decodeNotification, defaultGitRunner, deferSubscriptionPurchase, deferSubscriptionV2, deleteBasePlan, deleteGrant, deleteImage, deleteInAppProduct, deleteListing, deleteOffer, deleteOneTimeOffer, deleteOneTimeProduct, deleteSubscription, deployRecoveryAction, detectFastlane, detectOutputFormat, diffListings, diffListingsCommand, diffListingsEnhanced, diffOneTimeProduct, diffReleases, diffSubscription, discoverPlugins, downloadGeneratedApk, downloadReport, exportImages, exportReviews, fetchAggregateCost, fetchChangelog, fetchReleaseNotes, formatChangelogEntry, formatCustomPayload, formatDiscordPayload, formatJunit, formatNotification, formatOutput, formatPathLabel, formatSlackPayload, formatStatusDiff, formatStatusSummary, formatStatusTable, formatWordDiff, generateChangelog, generateMigrationPlan, generateNotesFromGit, getAllScannerNames, getAppInfo, getAppStatus, getCountryAvailability, getDeviceTier, getExternalTransaction, getInAppProduct, getListings, getOffer, getOneTimeOffer, getOneTimeProduct, getOrderDetails, getProductPurchase, getProductPurchaseV2, getQuotaUsage, getReleasesStatus, getReview, getRtdnStatus, getSubscription, getSubscriptionAnalytics, getSubscriptionPurchase, getTrainStatus, getUser, getVitalsAnomalies, getVitalsAnr, getVitalsBattery, getVitalsCrashes, getVitalsErrorCount, getVitalsLmk, getVitalsMemory, getVitalsOverview, getVitalsRendering, getVitalsStartup, importDataSafety, importTestersFromCsv, initAudit, initProject, inviteUser, isFinancialReportType, isStatsReportType, isValidBcp47, isValidReportType, isValidStatsDimension, lintListing, lintListings, lintLocalListings, listAchievements, listAuditEvents, listDeviceTiers, listEvents, listGeneratedApks, listGrants, listImages, listInAppProducts, listLeaderboards, listOffers, listOneTimeOffers, listOneTimeProducts, listRecoveryActions, listReports, listReviews, listSubscriptions, listTesters, listTracks, listUsers, listVoidedPurchases, loadPreflightConfig, loadStatusCache, maybePaginate, migratePrices, parseAppfile, parseCommit, parseFastfile, parseGrantArg, parseMonth, parseRemoteUrl, pauseTrain, promoteRelease, publish, publishEnterpriseApp, pullListings, pushListings, readListingsFromDir, readReleaseNotesFromDir, redactAuditArgs, redactSensitive, refundExternalTransaction, refundOrder, relativeTime, removeTesters, removeUser, renderJson, renderMarkdown, renderPlayStore, renderPlayStoreJson, renderPlayStoreMd, renderPlayStorePrompt, renderPrompt, replyToReview, resolveAiConfig, resolveLocales, revokeSubscriptionPurchase, runPreflight, runWatchLoop, safePath, safePathWithin, saveStatusCache, scaffoldPlugin, searchAuditEvents, searchVitalsErrors, sendNotification, sendWebhook, sortResults, startTrain, statusHasBreach, syncInAppProducts, topFiles, trackBreachState, translateBundle, 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 };
1475
+ export { ApiError, type AppInfo, type AppStatus, type ApplyReleaseNotesResult, type AuditEntry, type BatchSyncResult, type BundleAnalysis, type BundleComparison, type BundleEntry, type BundleSizeCheckResult, type BundleSizeConfig, type ChangelogEntry, type CommandContext, type CommitCluster, ConfigError, type CreateEnterpriseAppParams, DEFAULT_LIMITS, DEFAULT_MODELS, DEFAULT_PREFLIGHT_CONFIG, type DecodedNotification, type DiffToken, type DiscoverPluginsOptions, type DryRunPublishResult, type DryRunResult, type DryRunUploadResult, type ErrorReason, type ExportImagesOptions, type ExportImagesSummary, type FastlaneDetection, type FastlaneLane, type FetchChangelogOptions, type FieldLintResult, type FileValidationResult, type FindingSeverity, GOOGLE_PLAY_LANGUAGES, type GenerateOptions, type GeneratedChangelog, type GetAppStatusOptions, type GitNotesOptions, type GitReleaseNotes, type GitRunner, GpcError, type 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 LocaleBundle, type LocaleEntry, type MigrationResult, NetworkError, type OneTimeProductDiff, type OutputMode, PERMISSION_PROPAGATION_WARNING, PLACEHOLDER_TEXT, PLAY_STORE_LIMIT, PROVIDER_WHITELIST, type ParsedCommit, type ParsedManifest, type ParsedMonth, type PlayStoreFormat, type PlayStoreRenderOptions, PluginManager, type PreflightConfig, type PreflightFinding, type PreflightOptions, type PreflightResult, type PreflightScanner, type Provider, type PublishOptions, type PublishResult, type PushResult, type QuotaUsage, RENDERERS, type RawCommit, type ReleaseDiff, type ReleaseNotesValidation, type ReleaseStatusResult, type Renderer, type ResolveAiConfigOptions, type ResolveLocalesOptions, type ReviewAnalysis, type ReviewExportOptions, type ReviewsFilterOptions, type RtdnStatus, SECTION_ORDER, 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 TranslateBundleOptions, type TranslatedBundle, type TranslationFailure, type TranslationPath, type TranslationResult, type Translator, type TranslatorConfig, 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, addRecoveryTargeting, addTesters, advanceTrain, analyzeBundle, analyzeRemoteListings, analyzeReviews, applyReleaseNotes, batchGetOrders, batchSyncInAppProducts, buildLocaleBundle, bundleToReleaseNotes, cancelRecoveryAction, cancelSubscriptionPurchase, cancelSubscriptionV2, checkBundleSize, checkThreshold, classifyError, clearAuditLog, compareBundles, compareVersionVitals, compareVitalsTrend, computeStatusDiff, consumeProductPurchase, convertRegionPrices, createAuditEntry, createDeviceTier, createEnterpriseApp, createExternalTransaction, createGrant, createInAppProduct, createOffer, createOneTimeOffer, createOneTimeProduct, createRecoveryAction, createSpinner, createSubscription, createTrack, createTranslator, deactivateBasePlan, deactivateOffer, decodeNotification, defaultGitRunner, deferSubscriptionPurchase, deferSubscriptionV2, deleteBasePlan, deleteGrant, deleteImage, deleteInAppProduct, deleteListing, deleteOffer, deleteOneTimeOffer, deleteOneTimeProduct, deleteSubscription, deployRecoveryAction, detectFastlane, detectOutputFormat, diffListings, diffListingsCommand, diffListingsEnhanced, diffOneTimeProduct, diffReleases, diffSubscription, discoverPlugins, downloadGeneratedApk, downloadReport, exportImages, exportReviews, fetchAggregateCost, fetchChangelog, fetchReleaseNotes, formatChangelogEntry, formatCustomPayload, formatDiscordPayload, formatJunit, formatNotification, formatOutput, formatPathLabel, formatSlackPayload, formatStatusDiff, formatStatusSummary, formatStatusTable, formatWordDiff, generateChangelog, generateMigrationPlan, generateNotesFromGit, getAllScannerNames, getAppInfo, getAppStatus, getCountryAvailability, getDeviceTier, getExternalTransaction, getInAppProduct, getListings, getOffer, getOneTimeOffer, getOneTimeProduct, getOrderDetails, getProductPurchase, getProductPurchaseV2, getQuotaUsage, getReleasesStatus, getReview, getRtdnStatus, getSubscription, getSubscriptionAnalytics, getSubscriptionPurchase, getTrainStatus, getUser, getVitalsAnomalies, getVitalsAnr, getVitalsBattery, getVitalsCrashes, getVitalsErrorCount, getVitalsLmk, getVitalsMemory, getVitalsOverview, getVitalsRendering, getVitalsStartup, importDataSafety, importTestersFromCsv, initAudit, initProject, inviteUser, isFinancialReportType, isStatsReportType, isValidBcp47, isValidReportType, isValidStatsDimension, lintListing, lintListings, lintLocalListings, listAchievements, listAuditEvents, listDeviceTiers, listEvents, listGeneratedApks, listGrants, listImages, listInAppProducts, listLeaderboards, listOffers, listOneTimeOffers, listOneTimeProducts, listRecoveryActions, listReports, listReviews, listSubscriptions, listTesters, listTracks, listUsers, listVoidedPurchases, loadPreflightConfig, loadStatusCache, maybePaginate, migratePrices, parseAppfile, parseCommit, parseFastfile, parseGrantArg, parseMonth, parseRemoteUrl, pauseTrain, promoteRelease, publish, publishEnterpriseApp, pullListings, pushListings, readListingsFromDir, readReleaseNotesFromDir, redactAuditArgs, redactSensitive, refundExternalTransaction, refundOrder, relativeTime, removeTesters, removeUser, renderJson, renderMarkdown, renderPlayStore, renderPlayStoreJson, renderPlayStoreMd, renderPlayStorePrompt, renderPrompt, replyToReview, resolveAiConfig, resolveLocales, revokeSubscriptionPurchase, runPreflight, runWatchLoop, safePath, safePathWithin, saveStatusCache, scaffoldPlugin, searchAuditEvents, searchVitalsErrors, sendNotification, sendWebhook, sortResults, startTrain, statusHasBreach, syncInAppProducts, topFiles, trackBreachState, translateBundle, updateAppDetails, updateDataSafety, updateGrant, updateInAppProduct, updateListing, updateOffer, updateOneTimeOffer, updateOneTimeProduct, updateRollout, updateSubscription, updateTrackConfig, updateUser, uploadExternallyHosted, uploadImage, uploadInternalSharing, uploadRelease, validateBundleForApply, validateImage, validateLanguageCode, validatePackageName, validatePreSubmission, validateReleaseNotes, validateSku, validateTrackName, validateUploadFile, validateVersionCode, waitForBundleProcessing, watchVitalsWithAutoHalt, wordDiff, writeAuditLog, writeListingsToDir, writeMigrationOutput };
package/dist/index.js CHANGED
@@ -650,6 +650,20 @@ function formatSize(bytes) {
650
650
  }
651
651
 
652
652
  // src/commands/releases.ts
653
+ var BUNDLE_POLL_BACKOFF = [2e3, 3e3, 5e3, 8e3, 13e3];
654
+ async function waitForBundleProcessing(client, packageName, editId, versionCode, backoff = BUNDLE_POLL_BACKOFF) {
655
+ for (let i = 0; i < backoff.length; i++) {
656
+ const bundles = await client.bundles.list(packageName, editId);
657
+ if (bundles.some((b) => b.versionCode === versionCode)) return;
658
+ await new Promise((r) => setTimeout(r, backoff[i]));
659
+ }
660
+ throw new GpcError(
661
+ `Bundle versionCode ${versionCode} not ready after ${backoff.length} poll attempts (~${Math.round(backoff.reduce((a, b) => a + b, 0) / 1e3)}s)`,
662
+ "BUNDLE_PROCESSING_TIMEOUT",
663
+ 4,
664
+ "The AAB is still being processed by Google. Retry the upload, or use --status draft and commit later."
665
+ );
666
+ }
653
667
  async function withRetryOnConflict(client, packageName, operation) {
654
668
  const edit = await client.edits.insert(packageName);
655
669
  try {
@@ -763,6 +777,9 @@ ${validation.errors.join("\n")}`,
763
777
  uploadOpts,
764
778
  options.deviceTierConfigId
765
779
  );
780
+ if (!isApk) {
781
+ await waitForBundleProcessing(client, packageName, edit.id, bundle.versionCode);
782
+ }
766
783
  if (options.mappingFile) {
767
784
  await client.deobfuscation.upload(
768
785
  packageName,
@@ -1023,6 +1040,35 @@ async function fetchReleaseNotes(client, packageName, track) {
1023
1040
  });
1024
1041
  }
1025
1042
  }
1043
+ async function applyReleaseNotes(client, packageName, track, releaseNotes, commitOptions) {
1044
+ return withRetryOnConflict(client, packageName, async (edit) => {
1045
+ const trackData = await client.tracks.get(packageName, edit.id, track);
1046
+ const draft = trackData.releases?.find((r) => r.status === "draft");
1047
+ if (!draft) {
1048
+ throw new GpcError(
1049
+ `No draft release found on track "${track}"`,
1050
+ "RELEASE_NO_DRAFT",
1051
+ 1,
1052
+ `Upload an AAB/APK first to create a draft, or check the --track value. Current track: "${track}".`
1053
+ );
1054
+ }
1055
+ const patched = {
1056
+ ...draft,
1057
+ releaseNotes
1058
+ };
1059
+ await client.tracks.update(packageName, edit.id, track, patched);
1060
+ if (!commitOptions?.changesNotSentForReview) {
1061
+ await client.edits.validate(packageName, edit.id);
1062
+ }
1063
+ await client.edits.commit(packageName, edit.id, commitOptions);
1064
+ return {
1065
+ track,
1066
+ versionCodes: draft.versionCodes || [],
1067
+ localeCount: releaseNotes.length,
1068
+ releaseNotes
1069
+ };
1070
+ });
1071
+ }
1026
1072
  async function diffReleases(client, packageName, fromTrack, toTrack) {
1027
1073
  const edit = await client.edits.insert(packageName);
1028
1074
  try {
@@ -5143,6 +5189,28 @@ var manifestScanner = {
5143
5189
  }
5144
5190
  }
5145
5191
  }
5192
+ const hasBackgroundLocation = manifest.permissions.includes(
5193
+ "android.permission.ACCESS_BACKGROUND_LOCATION"
5194
+ );
5195
+ if (hasBackgroundLocation) {
5196
+ for (const service of manifest.services) {
5197
+ const fst = service.foregroundServiceType;
5198
+ if (!fst) continue;
5199
+ const num = Number(fst);
5200
+ const hasLocation = fst.split("|").some((t) => t.trim() === "location") || !isNaN(num) && (num & 8) !== 0;
5201
+ if (hasLocation) {
5202
+ findings.push({
5203
+ scanner: "manifest",
5204
+ ruleId: "geofencing-foreground-service",
5205
+ severity: "warning",
5206
+ title: `Possible geofencing via foreground service "${service.name}"`,
5207
+ message: `Service "${service.name}" uses foregroundServiceType "location" and the app declares ACCESS_BACKGROUND_LOCATION. Google Play no longer approves geofencing as a foreground service use case (April 2026 policy). Compliance deadline: May 15, 2026.`,
5208
+ suggestion: 'If this service performs geofencing, migrate to WorkManager or AlarmManager. If this is legitimate background location tracking (navigation, fitness), suppress this rule via .preflightrc.json: "disabledRules": ["geofencing-foreground-service"].',
5209
+ policyUrl: "https://support.google.com/googleplay/android-developer/answer/16926792"
5210
+ });
5211
+ }
5212
+ }
5213
+ }
5146
5214
  const allComponents = [
5147
5215
  ...manifest.activities,
5148
5216
  ...manifest.services,
@@ -5360,6 +5428,34 @@ var permissionsScanner = {
5360
5428
  });
5361
5429
  }
5362
5430
  }
5431
+ const contactsPerms = [
5432
+ "android.permission.READ_CONTACTS",
5433
+ "android.permission.WRITE_CONTACTS"
5434
+ ].filter((p) => manifest.permissions.includes(p) && !allowed.has(p));
5435
+ if (contactsPerms.length > 0) {
5436
+ const names = contactsPerms.map((p) => p.split(".").pop()).join(", ");
5437
+ findings.push({
5438
+ scanner: "permissions",
5439
+ ruleId: "contacts-permission-broad",
5440
+ severity: "warning",
5441
+ title: "Broad contacts access requires migration to Contact Picker",
5442
+ message: `Your app declares ${names}. Google Play now requires the Android Contact Picker instead of broad contacts access. Compliance deadline: May 15, 2026.`,
5443
+ suggestion: "Migrate to the Android Contact Picker API for user-initiated contact selection. Remove READ_CONTACTS/WRITE_CONTACTS unless your app is a dialer, messaging, or contacts management app.",
5444
+ policyUrl: "https://support.google.com/googleplay/android-developer/answer/16926792"
5445
+ });
5446
+ }
5447
+ const broadHealthPerm = "android.permission.health.READ_ALL_HEALTH_DATA";
5448
+ if (manifest.permissions.includes(broadHealthPerm) && !allowed.has(broadHealthPerm)) {
5449
+ findings.push({
5450
+ scanner: "permissions",
5451
+ ruleId: "health-connect-granular",
5452
+ severity: manifest.targetSdk >= 36 ? "warning" : "info",
5453
+ title: "Broad Health Connect permission \u2014 use granular permissions",
5454
+ message: `Your app declares READ_ALL_HEALTH_DATA. Android 16 requires granular Health Connect permissions for individual data types (e.g., steps, heart rate, sleep).${manifest.targetSdk >= 36 ? " Your targetSdk >= 36 makes this a policy requirement." : " This will become required when you target API 36+."}`,
5455
+ suggestion: "Replace READ_ALL_HEALTH_DATA with individual permissions like health.READ_STEPS, health.READ_HEART_RATE, etc. Only request the data types your app actually uses.",
5456
+ policyUrl: "https://developer.android.com/health-and-fitness/guides/health-connect/plan/data-types"
5457
+ });
5458
+ }
5363
5459
  const dataPermissions = [
5364
5460
  { perm: "android.permission.ACCESS_FINE_LOCATION", data: "precise location" },
5365
5461
  { perm: "android.permission.ACCESS_COARSE_LOCATION", data: "approximate location" },
@@ -7377,7 +7473,16 @@ var KNOWN_TYPES = /* @__PURE__ */ new Set([
7377
7473
  "release"
7378
7474
  ]);
7379
7475
  var FILTERED_TYPES = /* @__PURE__ */ new Set(["chore", "refactor", "test", "build", "style", "merge"]);
7380
- var SECTION_ORDER = ["breaking", "feat", "fix", "perf", "docs", "ci", "release", "other"];
7476
+ var SECTION_ORDER = [
7477
+ "breaking",
7478
+ "feat",
7479
+ "fix",
7480
+ "perf",
7481
+ "docs",
7482
+ "ci",
7483
+ "release",
7484
+ "other"
7485
+ ];
7381
7486
  var FIXUP_PATTERNS = [
7382
7487
  /^wip\b/i,
7383
7488
  /^fix\s+typo\b/i,
@@ -7841,14 +7946,18 @@ function renderPrompt(g) {
7841
7946
  if (g.headlineCandidates.length > 0) {
7842
7947
  lines.push("HEADLINE CANDIDATES (largest first):");
7843
7948
  for (const c of g.headlineCandidates) {
7844
- lines.push(` ${c.label} (weight ${c.weight}, ${c.commits.length} commits, primary ${c.primaryType})`);
7949
+ lines.push(
7950
+ ` ${c.label} (weight ${c.weight}, ${c.commits.length} commits, primary ${c.primaryType})`
7951
+ );
7845
7952
  }
7846
7953
  lines.push("");
7847
7954
  }
7848
7955
  lines.push("CLUSTERED COMMITS:");
7849
7956
  lines.push("");
7850
7957
  for (const cluster of g.clusters) {
7851
- lines.push(`[cluster: ${cluster.label}, weight ${cluster.weight}, ${cluster.commits.length} commits, primary ${cluster.primaryType}]`);
7958
+ lines.push(
7959
+ `[cluster: ${cluster.label}, weight ${cluster.weight}, ${cluster.commits.length} commits, primary ${cluster.primaryType}]`
7960
+ );
7852
7961
  for (const commit of cluster.commits) {
7853
7962
  lines.push(`- ${commit.type}: ${safeLine(commit.subject)} (${commit.sha.slice(0, 7)})`);
7854
7963
  if (commit.files.length > 0) {
@@ -8159,7 +8268,9 @@ function renderPlayStorePrompt(bundle, g) {
8159
8268
  const source = bundle.locales.find((e) => e.language === bundle.sourceLanguage);
8160
8269
  const targets = bundle.locales.filter((e) => e.language !== bundle.sourceLanguage);
8161
8270
  const lines = [];
8162
- lines.push(`You are translating Play Store "What's new" release notes from ${bundle.sourceLanguage}.`);
8271
+ lines.push(
8272
+ `You are translating Play Store "What's new" release notes from ${bundle.sourceLanguage}.`
8273
+ );
8163
8274
  lines.push("");
8164
8275
  lines.push("TARGETS:");
8165
8276
  for (const t of targets) lines.push(` - ${t.language}`);
@@ -8170,7 +8281,9 @@ function renderPlayStorePrompt(bundle, g) {
8170
8281
  lines.push('- Preserve the bullet format (one item per line, starts with "- ")');
8171
8282
  lines.push("- Keep a user-facing tone (no internal jargon)");
8172
8283
  lines.push('- Do not translate technical names (package names, CLI flags, "GPC")');
8173
- lines.push("- Drop the conventional-commit prefix (feat:/fix:) if it feels unnatural in the target language");
8284
+ lines.push(
8285
+ "- Drop the conventional-commit prefix (feat:/fix:) if it feels unnatural in the target language"
8286
+ );
8174
8287
  lines.push("");
8175
8288
  lines.push(`SOURCE (${bundle.sourceLanguage}, ${source?.chars ?? 0}/${bundle.limit} chars):`);
8176
8289
  lines.push("```");
@@ -8290,6 +8403,21 @@ async function translateBundle(bundle, options) {
8290
8403
  }
8291
8404
  return translated;
8292
8405
  }
8406
+ function validateBundleForApply(bundle) {
8407
+ const errors = [];
8408
+ for (const entry of bundle.locales) {
8409
+ if (entry.status === "placeholder") {
8410
+ errors.push(`${entry.language}: untranslated placeholder \u2014 use --ai or remove this locale`);
8411
+ }
8412
+ if (entry.status === "failed") {
8413
+ errors.push(`${entry.language}: translation failed \u2014 retry or remove this locale`);
8414
+ }
8415
+ }
8416
+ return errors;
8417
+ }
8418
+ function bundleToReleaseNotes(bundle) {
8419
+ return bundle.locales.filter((e) => e.status !== "placeholder" && e.status !== "failed").map((e) => ({ language: e.language, text: e.text }));
8420
+ }
8293
8421
 
8294
8422
  // src/commands/changelog-renderers/index.ts
8295
8423
  var RENDERERS = {
@@ -8481,9 +8609,11 @@ export {
8481
8609
  analyzeBundle,
8482
8610
  analyzeRemoteListings,
8483
8611
  analyzeReviews2 as analyzeReviews,
8612
+ applyReleaseNotes,
8484
8613
  batchGetOrders,
8485
8614
  batchSyncInAppProducts,
8486
8615
  buildLocaleBundle,
8616
+ bundleToReleaseNotes,
8487
8617
  cancelRecoveryAction,
8488
8618
  cancelSubscriptionPurchase,
8489
8619
  cancelSubscriptionV2,
@@ -8693,6 +8823,7 @@ export {
8693
8823
  uploadImage,
8694
8824
  uploadInternalSharing,
8695
8825
  uploadRelease,
8826
+ validateBundleForApply,
8696
8827
  validateImage,
8697
8828
  validateLanguageCode,
8698
8829
  validatePackageName,
@@ -8702,6 +8833,7 @@ export {
8702
8833
  validateTrackName,
8703
8834
  validateUploadFile,
8704
8835
  validateVersionCode,
8836
+ waitForBundleProcessing,
8705
8837
  watchVitalsWithAutoHalt,
8706
8838
  wordDiff,
8707
8839
  writeAuditLog,