@gpc-cli/api 1.0.29 → 1.0.31

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
+ 216 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
@@ -479,8 +479,6 @@ interface SubscriptionPurchaseV2 {
479
479
  subscriptionState: string;
480
480
  acknowledgementState?: string;
481
481
  linkedPurchaseToken?: string;
482
- /** Current offer phase: free trial, introductory price, proration, or base plan price. (Jan 2026) */
483
- offerPhase?: string;
484
482
  /** Resubscription context when purchase originates from Play Store. (Nov 2025) */
485
483
  outOfAppPurchaseContext?: {
486
484
  externalTransactionToken?: string;
@@ -550,8 +548,13 @@ interface SubscriptionPurchaseLineItem {
550
548
  offerId?: string;
551
549
  };
552
550
  };
553
- /** Current offer phase identifier. (Jan 2026) */
554
- offerPhase?: string;
551
+ /** Current offer phase (union field — exactly one set). (Jan 2026) */
552
+ offerPhase?: {
553
+ basePrice?: Record<string, unknown>;
554
+ freeTrial?: Record<string, unknown>;
555
+ introductoryPrice?: Record<string, unknown>;
556
+ proratedPeriod?: Record<string, unknown>;
557
+ };
555
558
  }
556
559
  interface SubscriptionPurchase {
557
560
  startTimeMillis: string;
@@ -659,9 +662,39 @@ interface ProductPurchaseLineItem {
659
662
  offerTags?: string[];
660
663
  };
661
664
  }
665
+ /**
666
+ * Request body for purchases.subscriptions.acknowledge.
667
+ * externalAccountId is used to associate a resubscription purchase with a
668
+ * user identifier. (Nov 2025)
669
+ */
670
+ interface AcknowledgeSubscriptionRequest {
671
+ developerPayload?: string;
672
+ externalAccountId?: string;
673
+ }
662
674
  interface SubscriptionsV2CancelRequest {
663
675
  cancellationType?: string;
664
676
  }
677
+ /**
678
+ * Request body for purchases.subscriptionsv2.revoke.
679
+ * revocationContext is a union — exactly one of fullRefund, proratedRefund,
680
+ * or itemBasedRefund should be set. itemBasedRefund targets a single add-on
681
+ * product by productId. (May 2025)
682
+ */
683
+ interface RevokeSubscriptionV2Request {
684
+ revocationContext?: {
685
+ fullRefund?: Record<string, never>;
686
+ proratedRefund?: Record<string, never>;
687
+ itemBasedRefund?: {
688
+ productId: string;
689
+ };
690
+ };
691
+ }
692
+ /**
693
+ * Request body for purchases.subscriptionsv2.defer.
694
+ * For subscriptions with add-ons, the billing date of all line items is
695
+ * deferred together server-side — no per-line-item field is required in
696
+ * the request. (Jan 2026)
697
+ */
665
698
  interface SubscriptionsV2DeferRequest {
666
699
  deferralInfo: {
667
700
  desiredExpiryTime: string;
@@ -998,6 +1031,14 @@ interface ResumableUploadOptions {
998
1031
  resumeSessionUri?: string;
999
1032
  /** Maximum resume attempts per chunk before giving up. Default: 5 */
1000
1033
  maxResumeAttempts?: number;
1034
+ /**
1035
+ * Optional JSON metadata to include in the initial session-initiation POST.
1036
+ * When present, the initial request uses `Content-Type: application/json; charset=UTF-8`
1037
+ * and the serialized body. Used by APIs like Play Custom App Publishing that
1038
+ * accept resource metadata alongside the media upload (CustomApp.title, etc.).
1039
+ * When omitted, the initial request sends an empty body (default Publisher API behavior).
1040
+ */
1041
+ initialMetadata?: object;
1001
1042
  }
1002
1043
  interface ReleaseSummary {
1003
1044
  releaseName?: string;
@@ -1191,10 +1232,8 @@ interface PlayApiClient {
1191
1232
  getSubscriptionV1(packageName: string, subscriptionId: string, token: string): Promise<SubscriptionPurchase>;
1192
1233
  cancelSubscription(packageName: string, subscriptionId: string, token: string): Promise<void>;
1193
1234
  deferSubscription(packageName: string, subscriptionId: string, token: string, body: SubscriptionDeferRequest): Promise<SubscriptionDeferResponse>;
1194
- acknowledgeSubscription(packageName: string, subscriptionId: string, token: string, body?: {
1195
- developerPayload?: string;
1196
- }): Promise<void>;
1197
- revokeSubscriptionV2(packageName: string, token: string): Promise<void>;
1235
+ acknowledgeSubscription(packageName: string, subscriptionId: string, token: string, body?: AcknowledgeSubscriptionRequest): Promise<void>;
1236
+ revokeSubscriptionV2(packageName: string, token: string, body?: RevokeSubscriptionV2Request): Promise<void>;
1198
1237
  /** V2 cancel with cancellationType support. (Sep 2025) */
1199
1238
  cancelSubscriptionV2(packageName: string, token: string, body?: SubscriptionsV2CancelRequest): Promise<void>;
1200
1239
  /** V2 defer for subscriptions with add-ons. (Jan 2026) */
@@ -1440,23 +1479,44 @@ interface GamesApiClient {
1440
1479
  }
1441
1480
  declare function createGamesClient(options: ApiClientOptions): GamesApiClient;
1442
1481
 
1482
+ /**
1483
+ * A private ("custom") app published via the Google Play Custom App Publishing API.
1484
+ * Once created, these apps are PERMANENTLY private — they cannot be made public.
1485
+ *
1486
+ * Source: `https://playcustomapp.googleapis.com/$discovery/rest?version=v1`
1487
+ */
1443
1488
  interface CustomApp {
1444
- packageName?: string;
1489
+ /** Output only. Package name Google assigns to the created custom app. */
1490
+ readonly packageName?: string;
1491
+ /** Title for the Android app. Required in practice. */
1445
1492
  title: string;
1493
+ /** Default listing language in BCP 47 format (e.g. "en_US"). */
1446
1494
  languageCode?: string;
1495
+ /** Organizations to which the custom app should be made available. */
1447
1496
  organizations?: Array<{
1497
+ /** ID of the enterprise organization (required when included). */
1448
1498
  organizationId: string;
1499
+ /** Human-readable organization name (optional). */
1449
1500
  organizationName?: string;
1450
1501
  }>;
1451
1502
  }
1452
- interface CustomAppsListResponse {
1453
- customApps?: CustomApp[];
1454
- nextPageToken?: string;
1455
- }
1503
+ /** Input metadata for `apps.create` — `packageName` is output-only and excluded. */
1504
+ type CustomAppCreateMetadata = Omit<CustomApp, "packageName">;
1456
1505
  interface EnterpriseApiClient {
1457
1506
  apps: {
1458
- create(organizationId: string, app: Partial<CustomApp>): Promise<CustomApp>;
1459
- list(organizationId: string): Promise<CustomAppsListResponse>;
1507
+ /**
1508
+ * Create and publish a new private custom app. Performs a resumable
1509
+ * upload: the initial POST carries the metadata JSON, subsequent chunks
1510
+ * stream the bundle binary. Returns the created `CustomApp` with the
1511
+ * assigned `packageName`.
1512
+ *
1513
+ * The created app is permanently private and cannot be made public.
1514
+ *
1515
+ * @param accountId - Developer account ID (int64, from Play Console URL)
1516
+ * @param bundlePath - Path to the AAB or APK to upload
1517
+ * @param metadata - CustomApp metadata (title, languageCode, organizations)
1518
+ */
1519
+ create(accountId: string, bundlePath: string, metadata: CustomAppCreateMetadata): Promise<CustomApp>;
1460
1520
  };
1461
1521
  }
1462
1522
  declare function createEnterpriseClient(options: ApiClientOptions): EnterpriseApiClient;
@@ -1470,6 +1530,13 @@ interface HttpClient {
1470
1530
  upload<T>(path: string, filePath: string, contentType: string): Promise<ApiResponse<T>>;
1471
1531
  uploadResumable<T>(path: string, filePath: string, contentType: string, options?: ResumableUploadOptions): Promise<ApiResponse<T>>;
1472
1532
  uploadInternal<T>(path: string, filePath: string, contentType: string): Promise<ApiResponse<T>>;
1533
+ /**
1534
+ * Play Custom App Publishing upload. Sends the media body AND a JSON metadata
1535
+ * object in a single resumable session — the initial session-initiation POST
1536
+ * carries the metadata (CustomApp shape), and subsequent chunks stream the
1537
+ * bundle binary. Used by the Play Custom App API which requires both parts.
1538
+ */
1539
+ uploadCustomApp<T>(path: string, filePath: string, metadata: object, contentType: string): Promise<ApiResponse<T>>;
1473
1540
  download(path: string): Promise<ArrayBuffer>;
1474
1541
  }
1475
1542
  declare function createHttpClient(options: ApiClientOptions): HttpClient;
@@ -1525,4 +1592,4 @@ declare class PlayApiError extends Error {
1525
1592
  /** Files below this threshold use simple upload instead. */
1526
1593
  declare const RESUMABLE_THRESHOLD: number;
1527
1594
 
1528
- export { type Achievement, 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 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 };
1595
+ 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 CustomAppCreateMetadata, 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 };
package/dist/index.js CHANGED
@@ -64,7 +64,13 @@ async function resumableUpload(uploadUrl, filePath, contentType, ctx, options) {
64
64
  const totalBytes = fileStats.size;
65
65
  let sessionUri = options?.resumeSessionUri;
66
66
  if (!sessionUri) {
67
- sessionUri = await initiateSession(uploadUrl, contentType, totalBytes, ctx);
67
+ sessionUri = await initiateSession(
68
+ uploadUrl,
69
+ contentType,
70
+ totalBytes,
71
+ ctx,
72
+ options?.initialMetadata
73
+ );
68
74
  }
69
75
  const startTime = Date.now();
70
76
  let offset = 0;
@@ -170,21 +176,29 @@ async function resumableUpload(uploadUrl, filePath, contentType, ctx, options) {
170
176
  await fh?.close();
171
177
  }
172
178
  }
173
- async function initiateSession(uploadUrl, contentType, totalBytes, ctx) {
179
+ async function initiateSession(uploadUrl, contentType, totalBytes, ctx, initialMetadata) {
174
180
  const token = await ctx.getAccessToken();
175
181
  const url = uploadUrl.includes("?") ? `${uploadUrl}&uploadType=resumable` : `${uploadUrl}?uploadType=resumable`;
182
+ const hasMetadata = initialMetadata !== void 0;
183
+ const metadataBody = hasMetadata ? JSON.stringify(initialMetadata) : "";
184
+ const metadataBodyBytes = hasMetadata ? Buffer.byteLength(metadataBody, "utf8") : 0;
185
+ const headers = {
186
+ Authorization: `Bearer ${token}`,
187
+ "X-Upload-Content-Type": contentType,
188
+ "X-Upload-Content-Length": String(totalBytes),
189
+ "Content-Length": String(metadataBodyBytes)
190
+ };
191
+ if (hasMetadata) {
192
+ headers["Content-Type"] = "application/json; charset=UTF-8";
193
+ }
176
194
  const controller = new AbortController();
177
195
  const timer = setTimeout(() => controller.abort(), 6e4);
178
196
  let response;
179
197
  try {
180
198
  response = await fetch(url, {
181
199
  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
- },
200
+ headers,
201
+ body: hasMetadata ? metadataBody : void 0,
188
202
  signal: controller.signal
189
203
  });
190
204
  } finally {
@@ -405,6 +419,7 @@ function validateFilePath(filePath) {
405
419
  var BASE_URL = "https://androidpublisher.googleapis.com/androidpublisher/v3/applications";
406
420
  var UPLOAD_BASE_URL = "https://androidpublisher.googleapis.com/upload/androidpublisher/v3/applications";
407
421
  var INTERNAL_SHARING_UPLOAD_BASE_URL = "https://androidpublisher.googleapis.com/upload/internalappsharing/v3/applications";
422
+ var CUSTOM_APP_UPLOAD_BASE_URL = "https://playcustomapp.googleapis.com/upload/playcustomapp/v1/accounts";
408
423
  function envInt2(name) {
409
424
  const val = process.env[name];
410
425
  if (val === void 0) return void 0;
@@ -938,6 +953,23 @@ function createHttpClient(options) {
938
953
  uploadInternal(path, filePath, contentType) {
939
954
  return uploadRequest(path, filePath, contentType, INTERNAL_SHARING_UPLOAD_BASE_URL);
940
955
  },
956
+ async uploadCustomApp(path, filePath, metadata, contentType) {
957
+ const safeFilePath = validateFilePath(filePath);
958
+ const uploadUrl = `${CUSTOM_APP_UPLOAD_BASE_URL}${path}`;
959
+ return resumableUpload(
960
+ uploadUrl,
961
+ safeFilePath,
962
+ contentType,
963
+ {
964
+ getAccessToken: () => options.auth.getAccessToken(),
965
+ maxRetries,
966
+ baseDelay,
967
+ maxDelay,
968
+ onRetry
969
+ },
970
+ { initialMetadata: metadata }
971
+ );
972
+ },
941
973
  async download(path) {
942
974
  const url = `${options.baseUrl ?? BASE_URL}${path}`;
943
975
  const token = await options.auth.getAccessToken();
@@ -1662,8 +1694,11 @@ function createApiClient(options) {
1662
1694
  body ?? {}
1663
1695
  );
1664
1696
  },
1665
- async revokeSubscriptionV2(packageName, token) {
1666
- await http.post(`/${packageName}/purchases/subscriptionsv2/tokens/${token}:revoke`);
1697
+ async revokeSubscriptionV2(packageName, token, body) {
1698
+ await http.post(
1699
+ `/${packageName}/purchases/subscriptionsv2/tokens/${token}:revoke`,
1700
+ body ?? {}
1701
+ );
1667
1702
  },
1668
1703
  async cancelSubscriptionV2(packageName, token, body) {
1669
1704
  await http.post(`/${packageName}/purchases/subscriptionsv2/tokens/${token}:cancel`, body);
@@ -2218,17 +2253,58 @@ function createGamesClient(options) {
2218
2253
  }
2219
2254
 
2220
2255
  // src/enterprise-client.ts
2221
- var ENTERPRISE_BASE_URL = "https://playcustomapp.googleapis.com/playcustomapp/v1/organizations";
2256
+ import { stat as stat3 } from "fs/promises";
2257
+ function assertAccountId(accountId) {
2258
+ if (!/^\d+$/.test(accountId)) {
2259
+ throw new PlayApiError(
2260
+ `Developer account ID must be numeric (got "${accountId}").`,
2261
+ "ENTERPRISE_INVALID_ACCOUNT_ID",
2262
+ void 0,
2263
+ [
2264
+ "Find your developer account ID in the Play Console URL:",
2265
+ " https://play.google.com/console/developers/[ID]",
2266
+ "The ID is a long integer, not your Workspace or Cloud Identity organization ID."
2267
+ ].join("\n")
2268
+ );
2269
+ }
2270
+ }
2271
+ async function assertBundleExists(bundlePath) {
2272
+ try {
2273
+ await stat3(bundlePath);
2274
+ } catch {
2275
+ throw new PlayApiError(
2276
+ `Bundle file not found: ${bundlePath}`,
2277
+ "ENTERPRISE_BUNDLE_NOT_FOUND",
2278
+ void 0,
2279
+ "Verify the path to your AAB or APK is correct."
2280
+ );
2281
+ }
2282
+ }
2283
+ function detectContentType(bundlePath) {
2284
+ const lower = bundlePath.toLowerCase();
2285
+ if (lower.endsWith(".aab")) {
2286
+ return "application/octet-stream";
2287
+ }
2288
+ if (lower.endsWith(".apk")) {
2289
+ return "application/vnd.android.package-archive";
2290
+ }
2291
+ return "application/octet-stream";
2292
+ }
2222
2293
  function createEnterpriseClient(options) {
2223
- const http = createHttpClient({ ...options, baseUrl: ENTERPRISE_BASE_URL });
2294
+ const http = createHttpClient(options);
2224
2295
  return {
2225
2296
  apps: {
2226
- async create(organizationId, app) {
2227
- const { data } = await http.post(`/${organizationId}/apps`, app);
2228
- return data;
2229
- },
2230
- async list(organizationId) {
2231
- const { data } = await http.get(`/${organizationId}/apps`);
2297
+ async create(accountId, bundlePath, metadata) {
2298
+ assertAccountId(accountId);
2299
+ await assertBundleExists(bundlePath);
2300
+ const contentType = detectContentType(bundlePath);
2301
+ const path = `/${accountId}/customApps`;
2302
+ const { data } = await http.uploadCustomApp(
2303
+ path,
2304
+ bundlePath,
2305
+ metadata,
2306
+ contentType
2307
+ );
2232
2308
  return data;
2233
2309
  }
2234
2310
  }