@gpc-cli/api 1.0.30 → 1.0.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Typed Google Play Developer API v3 client for TypeScript. Part of [GPC](https://github.com/yasserstudio/gpc).
4
4
 
5
- 215 endpoints across edits, releases, tracks, listings, subscriptions, in-app products, purchases, reviews, vitals, reports, users, and testers. Built-in rate limiting, retry logic, and pagination.
5
+ 217 endpoints across edits, releases, tracks, listings, subscriptions, in-app products, purchases, reviews, vitals, reports, users, testers, and **Managed Google Play private app publishing** (Play Custom App Publishing API, v0.9.56+ — first Android publishing SDK to support this). Built-in rate limiting, retry logic, resumable uploads, and pagination.
6
6
 
7
7
  ## Install
8
8
 
@@ -192,6 +192,6 @@ import type {
192
192
  - [SDK usage guide](https://yasserstudio.github.io/gpc/advanced/sdk-usage)
193
193
  - [API coverage map](https://yasserstudio.github.io/gpc/reference/api-coverage)
194
194
 
195
- ## License
195
+ ## Licensing
196
196
 
197
- MIT
197
+ Free to use. Source code is on GitHub at [yasserstudio/gpc](https://github.com/yasserstudio/gpc).
package/dist/index.d.ts CHANGED
@@ -471,12 +471,13 @@ interface ProductPurchase {
471
471
  acknowledgementState: number;
472
472
  regionCode?: string;
473
473
  }
474
+ type SubscriptionState = "SUBSCRIPTION_STATE_UNSPECIFIED" | "SUBSCRIPTION_STATE_PENDING" | "SUBSCRIPTION_STATE_ACTIVE" | "SUBSCRIPTION_STATE_PAUSED" | "SUBSCRIPTION_STATE_IN_GRACE_PERIOD" | "SUBSCRIPTION_STATE_ON_HOLD" | "SUBSCRIPTION_STATE_CANCELED" | "SUBSCRIPTION_STATE_EXPIRED" | "SUBSCRIPTION_STATE_PENDING_PURCHASE_CANCELED";
474
475
  interface SubscriptionPurchaseV2 {
475
476
  kind: string;
476
477
  regionCode?: string;
477
478
  lineItems: SubscriptionPurchaseLineItem[];
478
479
  startTime?: string;
479
- subscriptionState: string;
480
+ subscriptionState: SubscriptionState;
480
481
  acknowledgementState?: string;
481
482
  linkedPurchaseToken?: string;
482
483
  /** Resubscription context when purchase originates from Play Store. (Nov 2025) */
@@ -723,15 +724,18 @@ interface ReportBucket {
723
724
  interface ReportsListResponse {
724
725
  reports: ReportBucket[];
725
726
  }
726
- type DeveloperPermission = "ADMIN" | "CAN_MANAGE_PERMISSIONS" | "CAN_MANAGE_PUBLIC_APKS" | "CAN_MANAGE_TRACK_USERS" | "CAN_MANAGE_TRACK_CONFIGURATION" | "CAN_VIEW_FINANCIAL_DATA" | "CAN_REPLY_TO_REVIEWS" | "CAN_MANAGE_PUBLIC_LISTING" | "CAN_MANAGE_DRAFT_APPS" | "CAN_MANAGE_ORDERS";
727
+ type DeveloperLevelPermission = "DEVELOPER_LEVEL_PERMISSION_UNSPECIFIED" | "CAN_SEE_ALL_APPS" | "CAN_VIEW_FINANCIAL_DATA_GLOBAL" | "CAN_MANAGE_PERMISSIONS_GLOBAL" | "CAN_EDIT_GAMES_GLOBAL" | "CAN_PUBLISH_GAMES_GLOBAL" | "CAN_REPLY_TO_REVIEWS_GLOBAL" | "CAN_MANAGE_PUBLIC_APKS_GLOBAL" | "CAN_MANAGE_TRACK_APKS_GLOBAL" | "CAN_MANAGE_TRACK_USERS_GLOBAL" | "CAN_MANAGE_PUBLIC_LISTING_GLOBAL" | "CAN_MANAGE_DRAFT_APPS_GLOBAL" | "CAN_CREATE_MANAGED_PLAY_APPS_GLOBAL" | "CAN_CHANGE_MANAGED_PLAY_SETTING_GLOBAL" | "CAN_MANAGE_ORDERS_GLOBAL" | "CAN_MANAGE_APP_CONTENT_GLOBAL" | "CAN_VIEW_NON_FINANCIAL_DATA_GLOBAL" | "CAN_VIEW_APP_QUALITY_GLOBAL" | "CAN_MANAGE_DEEPLINKS_GLOBAL";
728
+ type AppLevelPermission = "APP_LEVEL_PERMISSION_UNSPECIFIED" | "CAN_ACCESS_APP" | "CAN_VIEW_FINANCIAL_DATA" | "CAN_MANAGE_PERMISSIONS" | "CAN_REPLY_TO_REVIEWS" | "CAN_MANAGE_PUBLIC_APKS" | "CAN_MANAGE_TRACK_APKS" | "CAN_MANAGE_TRACK_USERS" | "CAN_MANAGE_PUBLIC_LISTING" | "CAN_MANAGE_DRAFT_APPS" | "CAN_MANAGE_ORDERS" | "CAN_MANAGE_APP_CONTENT" | "CAN_VIEW_NON_FINANCIAL_DATA" | "CAN_VIEW_APP_QUALITY" | "CAN_MANAGE_DEEPLINKS";
729
+ /** @deprecated Use DeveloperLevelPermission instead */
730
+ type DeveloperPermission = DeveloperLevelPermission;
727
731
  interface Grant {
728
732
  packageName: string;
729
- appLevelPermissions: DeveloperPermission[];
733
+ appLevelPermissions: AppLevelPermission[];
730
734
  }
731
735
  interface User {
732
736
  email: string;
733
737
  name?: string;
734
- developerAccountPermission?: DeveloperPermission[];
738
+ developerAccountPermission?: DeveloperLevelPermission[];
735
739
  grants?: Grant[];
736
740
  expirationTime?: string;
737
741
  }
@@ -939,12 +943,14 @@ interface TaxAndComplianceSettings {
939
943
  }>;
940
944
  isTokenizedDigitalAsset?: boolean;
941
945
  }
946
+ type OneTimeOfferState = "STATE_UNSPECIFIED" | "DRAFT" | "ACTIVE" | "INACTIVE" | "INACTIVE_PUBLISHED" | "CANCELLED";
947
+ type OneTimeProductAvailability = "AVAILABILITY_UNSPECIFIED" | "AVAILABLE" | "NO_LONGER_AVAILABLE" | "AVAILABLE_IF_RELEASED" | "AVAILABLE_FOR_OFFERS_ONLY";
942
948
  interface OneTimeOffer {
943
949
  packageName: string;
944
950
  productId: string;
945
951
  purchaseOptionId: string;
946
952
  offerId: string;
947
- state?: "DRAFT" | "ACTIVE" | "CANCELLED" | "INACTIVE";
953
+ state?: OneTimeOfferState;
948
954
  regionalPricingAndAvailabilityConfigs?: Record<string, OneTimeOfferRegionalConfig>;
949
955
  /** @deprecated Use regionalPricingAndAvailabilityConfigs instead */
950
956
  regionalConfigs?: Record<string, OneTimeOfferRegionalConfig>;
@@ -1031,6 +1037,14 @@ interface ResumableUploadOptions {
1031
1037
  resumeSessionUri?: string;
1032
1038
  /** Maximum resume attempts per chunk before giving up. Default: 5 */
1033
1039
  maxResumeAttempts?: number;
1040
+ /**
1041
+ * Optional JSON metadata to include in the initial session-initiation POST.
1042
+ * When present, the initial request uses `Content-Type: application/json; charset=UTF-8`
1043
+ * and the serialized body. Used by APIs like Play Custom App Publishing that
1044
+ * accept resource metadata alongside the media upload (CustomApp.title, etc.).
1045
+ * When omitted, the initial request sends an empty body (default Publisher API behavior).
1046
+ */
1047
+ initialMetadata?: object;
1034
1048
  }
1035
1049
  interface ReleaseSummary {
1036
1050
  releaseName?: string;
@@ -1128,7 +1142,6 @@ interface PlayApiClient {
1128
1142
  get(packageName: string, editId: string, track: string): Promise<CountryAvailability>;
1129
1143
  };
1130
1144
  dataSafety: {
1131
- get(packageName: string): Promise<DataSafety>;
1132
1145
  update(packageName: string, data: DataSafety): Promise<DataSafety>;
1133
1146
  };
1134
1147
  reviews: {
@@ -1329,6 +1342,8 @@ interface PlayApiClient {
1329
1342
  }>): Promise<{
1330
1343
  oneTimeProducts: OneTimeProduct[];
1331
1344
  }>;
1345
+ activateOffer(packageName: string, productId: string, purchaseOptionId: string, offerId: string): Promise<OneTimeOffer>;
1346
+ deactivateOffer(packageName: string, productId: string, purchaseOptionId: string, offerId: string): Promise<OneTimeOffer>;
1332
1347
  cancelOffer(packageName: string, productId: string, purchaseOptionId: string, offerId: string, latencyTolerance?: string): Promise<OneTimeOffer>;
1333
1348
  batchGetOffers(packageName: string, productId: string, purchaseOptionId: string, requests: Array<{
1334
1349
  packageName: string;
@@ -1471,23 +1486,44 @@ interface GamesApiClient {
1471
1486
  }
1472
1487
  declare function createGamesClient(options: ApiClientOptions): GamesApiClient;
1473
1488
 
1489
+ /**
1490
+ * A private ("custom") app published via the Google Play Custom App Publishing API.
1491
+ * Once created, these apps are PERMANENTLY private — they cannot be made public.
1492
+ *
1493
+ * Source: `https://playcustomapp.googleapis.com/$discovery/rest?version=v1`
1494
+ */
1474
1495
  interface CustomApp {
1475
- packageName?: string;
1496
+ /** Output only. Package name Google assigns to the created custom app. */
1497
+ readonly packageName?: string;
1498
+ /** Title for the Android app. Required in practice. */
1476
1499
  title: string;
1500
+ /** Default listing language in BCP 47 format (e.g. "en_US"). */
1477
1501
  languageCode?: string;
1502
+ /** Organizations to which the custom app should be made available. */
1478
1503
  organizations?: Array<{
1504
+ /** ID of the enterprise organization (required when included). */
1479
1505
  organizationId: string;
1506
+ /** Human-readable organization name (optional). */
1480
1507
  organizationName?: string;
1481
1508
  }>;
1482
1509
  }
1483
- interface CustomAppsListResponse {
1484
- customApps?: CustomApp[];
1485
- nextPageToken?: string;
1486
- }
1510
+ /** Input metadata for `apps.create` — `packageName` is output-only and excluded. */
1511
+ type CustomAppCreateMetadata = Omit<CustomApp, "packageName">;
1487
1512
  interface EnterpriseApiClient {
1488
1513
  apps: {
1489
- create(organizationId: string, app: Partial<CustomApp>): Promise<CustomApp>;
1490
- list(organizationId: string): Promise<CustomAppsListResponse>;
1514
+ /**
1515
+ * Create and publish a new private custom app. Performs a resumable
1516
+ * upload: the initial POST carries the metadata JSON, subsequent chunks
1517
+ * stream the bundle binary. Returns the created `CustomApp` with the
1518
+ * assigned `packageName`.
1519
+ *
1520
+ * The created app is permanently private and cannot be made public.
1521
+ *
1522
+ * @param accountId - Developer account ID (int64, from Play Console URL)
1523
+ * @param bundlePath - Path to the AAB or APK to upload
1524
+ * @param metadata - CustomApp metadata (title, languageCode, organizations)
1525
+ */
1526
+ create(accountId: string, bundlePath: string, metadata: CustomAppCreateMetadata): Promise<CustomApp>;
1491
1527
  };
1492
1528
  }
1493
1529
  declare function createEnterpriseClient(options: ApiClientOptions): EnterpriseApiClient;
@@ -1501,6 +1537,13 @@ interface HttpClient {
1501
1537
  upload<T>(path: string, filePath: string, contentType: string): Promise<ApiResponse<T>>;
1502
1538
  uploadResumable<T>(path: string, filePath: string, contentType: string, options?: ResumableUploadOptions): Promise<ApiResponse<T>>;
1503
1539
  uploadInternal<T>(path: string, filePath: string, contentType: string): Promise<ApiResponse<T>>;
1540
+ /**
1541
+ * Play Custom App Publishing upload. Sends the media body AND a JSON metadata
1542
+ * object in a single resumable session — the initial session-initiation POST
1543
+ * carries the metadata (CustomApp shape), and subsequent chunks stream the
1544
+ * bundle binary. Used by the Play Custom App API which requires both parts.
1545
+ */
1546
+ uploadCustomApp<T>(path: string, filePath: string, metadata: object, contentType: string): Promise<ApiResponse<T>>;
1504
1547
  download(path: string): Promise<ArrayBuffer>;
1505
1548
  }
1506
1549
  declare function createHttpClient(options: ApiClientOptions): HttpClient;
@@ -1556,4 +1599,4 @@ declare class PlayApiError extends Error {
1556
1599
  /** Files below this threshold use simple upload instead. */
1557
1600
  declare const RESUMABLE_THRESHOLD: number;
1558
1601
 
1559
- export { type Achievement, type AcknowledgeSubscriptionRequest, type Anomaly, type AnomalyDetectionResponse, type ApiClientOptions, type ApiResponse, type ApkInfo, type ApksListResponse, type AppDetails, type AppEdit, type AppRecoveriesListResponse, type AppRecoveryAction, type AppRecoveryTargeting, type BasePlan, type BasePlanMigratePricesRequest, type BatchGetOrdersResponse, type BatchMigratePricesRequest, type BatchMigratePricesResponse, type Bundle, type BundleListResponse, type ChangesInReviewBehavior, type ConvertRegionPricesRequest, type ConvertRegionPricesResponse, type ConvertedRegionPrice, type CountryAvailability, type CreateAppRecoveryActionRequest, type CustomApp, type CustomAppsListResponse, type DataSafety, type DataSafetyDataType, type DataSafetyPurpose, type DeobfuscationFile, type DeobfuscationFileType, type DeobfuscationUploadResponse, type DeveloperComment, type DeveloperPermission, type DeviceGroup, type DeviceSelector, type DeviceTier, type DeviceTierConfig, type DeviceTierConfigsListResponse, type EditCommitOptions, type EnterpriseApiClient, type ErrorIssue, type ErrorIssuesResponse, type ErrorReport, type ErrorReportsResponse, type ExpansionFile, type ExpansionFileType, type ExternalTransaction, type ExternalTransactionAmount, type ExternalTransactionRefund, type ExternallyHostedApk, type ExternallyHostedApkResponse, type GameEvent, type GamesApiClient, type GeneratedApk, type GeneratedApksPerVersion, type Grant, type GrantsListResponse, type HttpClient, type Image, type ImageType, type ImageUploadResponse, type ImagesDeleteAllResponse, type ImagesListResponse, type InAppProduct, type InAppProductListing, type InAppProductsBatchDeleteRequest, type InAppProductsBatchGetRequest, type InAppProductsBatchUpdateRequest, type InAppProductsBatchUpdateResponse, type InAppProductsListResponse, type InternalAppSharingArtifact, type Leaderboard, type LeaderboardScore, type Listing, type ListingsListResponse, type MetricRow, type MetricSetQuery, type MetricSetResponse, type Money, type MutationOptions, type OffersListResponse, type OneTimeOffer, type OneTimeOfferRegionalConfig, type OneTimeOffersListResponse, type OneTimeProduct, type OneTimeProductListing, type OneTimeProductsListResponse, type Order, type OrderLineItem, type PagedResponse, type PaginateOptions, type PlayApiClient, PlayApiError, type ProductPurchase, type ProductPurchaseLineItem, type ProductPurchaseV2, type ProductUpdateLatencyTolerance, RATE_LIMIT_BUCKETS, RESUMABLE_THRESHOLD, type RateLimitBucket, type RateLimiter, type RegionalBasePlanConfig, type Release, type ReleaseNote, type ReleaseStatus, type ReleaseSummary, type ReleasesListResponse, type ReportBucket, type ReportType, type ReportingAggregation, type ReportingApiClient, type ReportingDimension, type ReportsListResponse, type ResumableUploadOptions, type RetryLogEntry, type Review, type ReviewComment, type ReviewReplyRequest, type ReviewReplyResponse, type ReviewsListOptions, type ReviewsListResponse, type RevokeSubscriptionV2Request, type StatsDimension, type Subscription, type SubscriptionDeferRequest, type SubscriptionDeferResponse, type SubscriptionListing, type SubscriptionOffer, type SubscriptionOfferPhase, type SubscriptionPurchase, type SubscriptionPurchaseLineItem, type SubscriptionPurchaseV2, type SubscriptionsBatchGetRequest, type SubscriptionsBatchGetResponse, type SubscriptionsBatchUpdateRequest, type SubscriptionsBatchUpdateResponse, type SubscriptionsListResponse, type SubscriptionsV2CancelRequest, type SubscriptionsV2DeferRequest, type SubscriptionsV2DeferResponse, type SystemApkDeviceSpec, type SystemApkOptions, type SystemApkVariant, type TaxAndComplianceSettings, type Testers, type TokenPagination, type Track, type TrackListResponse, type UploadProgressEvent, type UploadResponse, type User, type UserComment, type UsersApiClient, type UsersListResponse, type VitalsMetricSet, type VoidedPurchase, type VoidedPurchasesListResponse, createApiClient, createEnterpriseClient, createGamesClient, createHttpClient, createRateLimiter, createReportingClient, createUsersClient, paginate, paginateAll, paginateParallel, resolveBucket };
1602
+ export { type Achievement, type AcknowledgeSubscriptionRequest, type Anomaly, type AnomalyDetectionResponse, type ApiClientOptions, type ApiResponse, type ApkInfo, type ApksListResponse, type AppDetails, type AppEdit, type AppLevelPermission, type AppRecoveriesListResponse, type AppRecoveryAction, type AppRecoveryTargeting, type BasePlan, type BasePlanMigratePricesRequest, type BatchGetOrdersResponse, type BatchMigratePricesRequest, type BatchMigratePricesResponse, type Bundle, type BundleListResponse, type ChangesInReviewBehavior, type ConvertRegionPricesRequest, type ConvertRegionPricesResponse, type ConvertedRegionPrice, type CountryAvailability, type CreateAppRecoveryActionRequest, type CustomApp, type CustomAppCreateMetadata, type DataSafety, type DataSafetyDataType, type DataSafetyPurpose, type DeobfuscationFile, type DeobfuscationFileType, type DeobfuscationUploadResponse, type DeveloperComment, type DeveloperLevelPermission, type DeveloperPermission, type DeviceGroup, type DeviceSelector, type DeviceTier, type DeviceTierConfig, type DeviceTierConfigsListResponse, type EditCommitOptions, type EnterpriseApiClient, type ErrorIssue, type ErrorIssuesResponse, type ErrorReport, type ErrorReportsResponse, type ExpansionFile, type ExpansionFileType, type ExternalTransaction, type ExternalTransactionAmount, type ExternalTransactionRefund, type ExternallyHostedApk, type ExternallyHostedApkResponse, type GameEvent, type GamesApiClient, type GeneratedApk, type GeneratedApksPerVersion, type Grant, type GrantsListResponse, type HttpClient, type Image, type ImageType, type ImageUploadResponse, type ImagesDeleteAllResponse, type ImagesListResponse, type InAppProduct, type InAppProductListing, type InAppProductsBatchDeleteRequest, type InAppProductsBatchGetRequest, type InAppProductsBatchUpdateRequest, type InAppProductsBatchUpdateResponse, type InAppProductsListResponse, type InternalAppSharingArtifact, type Leaderboard, type LeaderboardScore, type Listing, type ListingsListResponse, type MetricRow, type MetricSetQuery, type MetricSetResponse, type Money, type MutationOptions, type OffersListResponse, type OneTimeOffer, type OneTimeOfferRegionalConfig, type OneTimeOfferState, type OneTimeOffersListResponse, type OneTimeProduct, type OneTimeProductAvailability, type OneTimeProductListing, type OneTimeProductsListResponse, type Order, type OrderLineItem, type PagedResponse, type PaginateOptions, type PlayApiClient, PlayApiError, type ProductPurchase, type ProductPurchaseLineItem, type ProductPurchaseV2, type ProductUpdateLatencyTolerance, RATE_LIMIT_BUCKETS, RESUMABLE_THRESHOLD, type RateLimitBucket, type RateLimiter, type RegionalBasePlanConfig, type Release, type ReleaseNote, type ReleaseStatus, type ReleaseSummary, type ReleasesListResponse, type ReportBucket, type ReportType, type ReportingAggregation, type ReportingApiClient, type ReportingDimension, type ReportsListResponse, type ResumableUploadOptions, type RetryLogEntry, type Review, type ReviewComment, type ReviewReplyRequest, type ReviewReplyResponse, type ReviewsListOptions, type ReviewsListResponse, type RevokeSubscriptionV2Request, type StatsDimension, type Subscription, type SubscriptionDeferRequest, type SubscriptionDeferResponse, type SubscriptionListing, type SubscriptionOffer, type SubscriptionOfferPhase, type SubscriptionPurchase, type SubscriptionPurchaseLineItem, type SubscriptionPurchaseV2, type SubscriptionState, type SubscriptionsBatchGetRequest, type SubscriptionsBatchGetResponse, type SubscriptionsBatchUpdateRequest, type SubscriptionsBatchUpdateResponse, type SubscriptionsListResponse, type SubscriptionsV2CancelRequest, type SubscriptionsV2DeferRequest, type SubscriptionsV2DeferResponse, type SystemApkDeviceSpec, type SystemApkOptions, type SystemApkVariant, type TaxAndComplianceSettings, type Testers, type TokenPagination, type Track, type TrackListResponse, type UploadProgressEvent, type UploadResponse, type User, type UserComment, type UsersApiClient, type UsersListResponse, type VitalsMetricSet, type VoidedPurchase, type VoidedPurchasesListResponse, createApiClient, createEnterpriseClient, createGamesClient, createHttpClient, createRateLimiter, createReportingClient, createUsersClient, paginate, paginateAll, paginateParallel, resolveBucket };
package/dist/index.js CHANGED
@@ -7,6 +7,9 @@ var PlayApiError = class extends Error {
7
7
  this.suggestion = suggestion;
8
8
  this.name = "PlayApiError";
9
9
  }
10
+ code;
11
+ statusCode;
12
+ suggestion;
10
13
  exitCode = 4;
11
14
  toJSON() {
12
15
  return {
@@ -64,7 +67,13 @@ async function resumableUpload(uploadUrl, filePath, contentType, ctx, options) {
64
67
  const totalBytes = fileStats.size;
65
68
  let sessionUri = options?.resumeSessionUri;
66
69
  if (!sessionUri) {
67
- sessionUri = await initiateSession(uploadUrl, contentType, totalBytes, ctx);
70
+ sessionUri = await initiateSession(
71
+ uploadUrl,
72
+ contentType,
73
+ totalBytes,
74
+ ctx,
75
+ options?.initialMetadata
76
+ );
68
77
  }
69
78
  const startTime = Date.now();
70
79
  let offset = 0;
@@ -170,21 +179,29 @@ async function resumableUpload(uploadUrl, filePath, contentType, ctx, options) {
170
179
  await fh?.close();
171
180
  }
172
181
  }
173
- async function initiateSession(uploadUrl, contentType, totalBytes, ctx) {
182
+ async function initiateSession(uploadUrl, contentType, totalBytes, ctx, initialMetadata) {
174
183
  const token = await ctx.getAccessToken();
175
184
  const url = uploadUrl.includes("?") ? `${uploadUrl}&uploadType=resumable` : `${uploadUrl}?uploadType=resumable`;
185
+ const hasMetadata = initialMetadata !== void 0;
186
+ const metadataBody = hasMetadata ? JSON.stringify(initialMetadata) : "";
187
+ const metadataBodyBytes = hasMetadata ? Buffer.byteLength(metadataBody, "utf8") : 0;
188
+ const headers = {
189
+ Authorization: `Bearer ${token}`,
190
+ "X-Upload-Content-Type": contentType,
191
+ "X-Upload-Content-Length": String(totalBytes),
192
+ "Content-Length": String(metadataBodyBytes)
193
+ };
194
+ if (hasMetadata) {
195
+ headers["Content-Type"] = "application/json; charset=UTF-8";
196
+ }
176
197
  const controller = new AbortController();
177
198
  const timer = setTimeout(() => controller.abort(), 6e4);
178
199
  let response;
179
200
  try {
180
201
  response = await fetch(url, {
181
202
  method: "POST",
182
- headers: {
183
- Authorization: `Bearer ${token}`,
184
- "X-Upload-Content-Type": contentType,
185
- "X-Upload-Content-Length": String(totalBytes),
186
- "Content-Length": "0"
187
- },
203
+ headers,
204
+ body: hasMetadata ? metadataBody : void 0,
188
205
  signal: controller.signal
189
206
  });
190
207
  } finally {
@@ -405,6 +422,7 @@ function validateFilePath(filePath) {
405
422
  var BASE_URL = "https://androidpublisher.googleapis.com/androidpublisher/v3/applications";
406
423
  var UPLOAD_BASE_URL = "https://androidpublisher.googleapis.com/upload/androidpublisher/v3/applications";
407
424
  var INTERNAL_SHARING_UPLOAD_BASE_URL = "https://androidpublisher.googleapis.com/upload/internalappsharing/v3/applications";
425
+ var CUSTOM_APP_UPLOAD_BASE_URL = "https://playcustomapp.googleapis.com/upload/playcustomapp/v1/accounts";
408
426
  function envInt2(name) {
409
427
  const val = process.env[name];
410
428
  if (val === void 0) return void 0;
@@ -938,6 +956,23 @@ function createHttpClient(options) {
938
956
  uploadInternal(path, filePath, contentType) {
939
957
  return uploadRequest(path, filePath, contentType, INTERNAL_SHARING_UPLOAD_BASE_URL);
940
958
  },
959
+ async uploadCustomApp(path, filePath, metadata, contentType) {
960
+ const safeFilePath = validateFilePath(filePath);
961
+ const uploadUrl = `${CUSTOM_APP_UPLOAD_BASE_URL}${path}`;
962
+ return resumableUpload(
963
+ uploadUrl,
964
+ safeFilePath,
965
+ contentType,
966
+ {
967
+ getAccessToken: () => options.auth.getAccessToken(),
968
+ maxRetries,
969
+ baseDelay,
970
+ maxDelay,
971
+ onRetry
972
+ },
973
+ { initialMetadata: metadata }
974
+ );
975
+ },
941
976
  async download(path) {
942
977
  const url = `${options.baseUrl ?? BASE_URL}${path}`;
943
978
  const token = await options.auth.getAccessToken();
@@ -1345,12 +1380,8 @@ function createApiClient(options) {
1345
1380
  }
1346
1381
  },
1347
1382
  dataSafety: {
1348
- async get(packageName) {
1349
- const { data } = await http.get(`/${packageName}/dataSafety`);
1350
- return data;
1351
- },
1352
1383
  async update(packageName, body) {
1353
- const { data } = await http.put(`/${packageName}/dataSafety`, body);
1384
+ const { data } = await http.post(`/${packageName}/dataSafety`, body);
1354
1385
  return data;
1355
1386
  }
1356
1387
  },
@@ -1788,10 +1819,10 @@ function createApiClient(options) {
1788
1819
  return data.recoveryActions || [];
1789
1820
  },
1790
1821
  async cancel(packageName, appRecoveryId) {
1791
- await http.post(`/${packageName}/appRecovery/${appRecoveryId}:cancel`);
1822
+ await http.post(`/${packageName}/appRecoveries/${appRecoveryId}:cancel`);
1792
1823
  },
1793
1824
  async deploy(packageName, appRecoveryId) {
1794
- await http.post(`/${packageName}/appRecovery/${appRecoveryId}:deploy`);
1825
+ await http.post(`/${packageName}/appRecoveries/${appRecoveryId}:deploy`);
1795
1826
  },
1796
1827
  async create(packageName, request) {
1797
1828
  const { data } = await http.post(
@@ -1963,6 +1994,19 @@ function createApiClient(options) {
1963
1994
  );
1964
1995
  return data;
1965
1996
  },
1997
+ // Offer lifecycle
1998
+ async activateOffer(packageName, productId, purchaseOptionId, offerId) {
1999
+ const { data } = await http.post(
2000
+ `/${packageName}/oneTimeProducts/${productId}/purchaseOptions/${purchaseOptionId}/offers/${offerId}:activate`
2001
+ );
2002
+ return data;
2003
+ },
2004
+ async deactivateOffer(packageName, productId, purchaseOptionId, offerId) {
2005
+ const { data } = await http.post(
2006
+ `/${packageName}/oneTimeProducts/${productId}/purchaseOptions/${purchaseOptionId}/offers/${offerId}:deactivate`
2007
+ );
2008
+ return data;
2009
+ },
1966
2010
  // Offer batch operations
1967
2011
  async cancelOffer(packageName, productId, purchaseOptionId, offerId, latencyTolerance) {
1968
2012
  const body = latencyTolerance ? { latencyTolerance } : {};
@@ -2221,17 +2265,58 @@ function createGamesClient(options) {
2221
2265
  }
2222
2266
 
2223
2267
  // src/enterprise-client.ts
2224
- var ENTERPRISE_BASE_URL = "https://playcustomapp.googleapis.com/playcustomapp/v1/organizations";
2268
+ import { stat as stat3 } from "fs/promises";
2269
+ function assertAccountId(accountId) {
2270
+ if (!/^\d+$/.test(accountId)) {
2271
+ throw new PlayApiError(
2272
+ `Developer account ID must be numeric (got "${accountId}").`,
2273
+ "ENTERPRISE_INVALID_ACCOUNT_ID",
2274
+ void 0,
2275
+ [
2276
+ "Find your developer account ID in the Play Console URL:",
2277
+ " https://play.google.com/console/developers/[ID]",
2278
+ "The ID is a long integer, not your Workspace or Cloud Identity organization ID."
2279
+ ].join("\n")
2280
+ );
2281
+ }
2282
+ }
2283
+ async function assertBundleExists(bundlePath) {
2284
+ try {
2285
+ await stat3(bundlePath);
2286
+ } catch {
2287
+ throw new PlayApiError(
2288
+ `Bundle file not found: ${bundlePath}`,
2289
+ "ENTERPRISE_BUNDLE_NOT_FOUND",
2290
+ void 0,
2291
+ "Verify the path to your AAB or APK is correct."
2292
+ );
2293
+ }
2294
+ }
2295
+ function detectContentType(bundlePath) {
2296
+ const lower = bundlePath.toLowerCase();
2297
+ if (lower.endsWith(".aab")) {
2298
+ return "application/octet-stream";
2299
+ }
2300
+ if (lower.endsWith(".apk")) {
2301
+ return "application/vnd.android.package-archive";
2302
+ }
2303
+ return "application/octet-stream";
2304
+ }
2225
2305
  function createEnterpriseClient(options) {
2226
- const http = createHttpClient({ ...options, baseUrl: ENTERPRISE_BASE_URL });
2306
+ const http = createHttpClient(options);
2227
2307
  return {
2228
2308
  apps: {
2229
- async create(organizationId, app) {
2230
- const { data } = await http.post(`/${organizationId}/apps`, app);
2231
- return data;
2232
- },
2233
- async list(organizationId) {
2234
- const { data } = await http.get(`/${organizationId}/apps`);
2309
+ async create(accountId, bundlePath, metadata) {
2310
+ assertAccountId(accountId);
2311
+ await assertBundleExists(bundlePath);
2312
+ const contentType = detectContentType(bundlePath);
2313
+ const path = `/${accountId}/customApps`;
2314
+ const { data } = await http.uploadCustomApp(
2315
+ path,
2316
+ bundlePath,
2317
+ metadata,
2318
+ contentType
2319
+ );
2235
2320
  return data;
2236
2321
  }
2237
2322
  }