@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 +3 -3
- package/dist/index.d.ts +83 -16
- package/dist/index.js +94 -18
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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
|
-
|
|
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
|
-
##
|
|
195
|
+
## Licensing
|
|
196
196
|
|
|
197
|
-
|
|
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
|
|
554
|
-
offerPhase?:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1453
|
-
|
|
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
|
-
|
|
1459
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
2294
|
+
const http = createHttpClient(options);
|
|
2224
2295
|
return {
|
|
2225
2296
|
apps: {
|
|
2226
|
-
async create(
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
const { data } = await http.
|
|
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
|
}
|