@mpgd/cli 0.62.0 → 0.63.0

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.
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Google Play one-time product port (Android Publisher v3
3
+ * `monetization.onetimeproducts`). The Google SDK lives under
4
+ * adapters/play-publisher; the CLI sees only these shapes.
5
+ * https://developers.google.com/android-publisher/api-ref/rest/v3/monetization.onetimeproducts
6
+ */
7
+ /** `Money`: whole `units` as a decimal string and `nanos` (10^-9 units). */
8
+ export interface PlayMoney {
9
+ readonly currencyCode?: string | null | undefined;
10
+ readonly units?: string | null | undefined;
11
+ readonly nanos?: number | null | undefined;
12
+ }
13
+ export interface PlayOneTimeProductListing {
14
+ readonly languageCode?: string | null | undefined;
15
+ readonly title?: string | null | undefined;
16
+ readonly description?: string | null | undefined;
17
+ }
18
+ export interface PlayRegionalPricingConfig {
19
+ readonly regionCode?: string | null | undefined;
20
+ readonly price?: PlayMoney | undefined;
21
+ /** `AVAILABLE`, `NO_LONGER_AVAILABLE`, `AVAILABLE_IF_RELEASED`, `AVAILABLE_FOR_OFFERS_ONLY`. */
22
+ readonly availability?: string | null | undefined;
23
+ }
24
+ export interface PlayNewRegionsConfig {
25
+ readonly usdPrice?: PlayMoney | undefined;
26
+ readonly eurPrice?: PlayMoney | undefined;
27
+ readonly availability?: string | null | undefined;
28
+ }
29
+ export interface PlayPurchaseOption {
30
+ readonly purchaseOptionId?: string | null | undefined;
31
+ readonly buyOption?: {
32
+ readonly legacyCompatible?: boolean | null | undefined;
33
+ readonly multiQuantityEnabled?: boolean | null | undefined;
34
+ } | undefined;
35
+ /** A rent option; the sync never manages one. */
36
+ readonly rentOption?: Readonly<Record<string, unknown>> | undefined;
37
+ readonly regionalPricingAndAvailabilityConfigs?: readonly PlayRegionalPricingConfig[] | undefined;
38
+ readonly newRegionsConfig?: PlayNewRegionsConfig | undefined;
39
+ /** Output only: `DRAFT`, `ACTIVE`, `INACTIVE` or `INACTIVE_PUBLISHED`. */
40
+ readonly state?: string | null | undefined;
41
+ readonly [field: string]: unknown;
42
+ }
43
+ export interface PlayOneTimeProduct {
44
+ readonly packageName?: string | null | undefined;
45
+ readonly productId?: string | null | undefined;
46
+ readonly listings?: readonly PlayOneTimeProductListing[] | undefined;
47
+ readonly purchaseOptions?: readonly PlayPurchaseOption[] | undefined;
48
+ readonly taxAndComplianceSettings?: {
49
+ readonly productTaxCategoryCode?: string | null | undefined;
50
+ readonly [field: string]: unknown;
51
+ } | undefined;
52
+ /** Output only. */
53
+ readonly regionsVersion?: {
54
+ readonly version?: string | null | undefined;
55
+ } | undefined;
56
+ readonly [field: string]: unknown;
57
+ }
58
+ /** `monetization.convertRegionPrices` response. */
59
+ export interface PlayConvertedPrices {
60
+ readonly convertedRegionPrices?: Readonly<Record<string, {
61
+ readonly regionCode?: string | null | undefined;
62
+ /** Tax inclusive. */
63
+ readonly price?: PlayMoney | undefined;
64
+ readonly taxAmount?: PlayMoney | undefined;
65
+ }>> | null | undefined;
66
+ readonly convertedOtherRegionsPrice?: {
67
+ readonly usdPrice?: PlayMoney | undefined;
68
+ readonly eurPrice?: PlayMoney | undefined;
69
+ } | undefined;
70
+ readonly regionVersion?: {
71
+ readonly version?: string | null | undefined;
72
+ } | undefined;
73
+ }
74
+ export interface PlayProductCatalogPort {
75
+ /** Every one-time product of the app (`onetimeproducts.list`, all pages). */
76
+ listOneTimeProducts(packageName: string, signal?: AbortSignal): Promise<PlayOneTimeProduct[]>;
77
+ /** Read-only price conversion (`monetization.convertRegionPrices`). */
78
+ convertRegionPrices(input: {
79
+ readonly packageName: string;
80
+ readonly price: PlayMoney;
81
+ readonly productTaxCategoryCode?: string;
82
+ readonly signal?: AbortSignal;
83
+ }): Promise<PlayConvertedPrices>;
84
+ /** `onetimeproducts.patch`; `allowMissing` creates the product. */
85
+ patchOneTimeProduct(input: {
86
+ readonly packageName: string;
87
+ readonly productId: string;
88
+ readonly product: PlayOneTimeProduct;
89
+ readonly updateMask: string;
90
+ readonly allowMissing: boolean;
91
+ readonly regionsVersion: string;
92
+ readonly signal?: AbortSignal;
93
+ }): Promise<PlayOneTimeProduct>;
94
+ /** `onetimeproducts.purchaseOptions.batchUpdateStates` with one activate request. */
95
+ activatePurchaseOption(input: {
96
+ readonly packageName: string;
97
+ readonly productId: string;
98
+ readonly purchaseOptionId: string;
99
+ readonly signal?: AbortSignal;
100
+ }): Promise<void>;
101
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Google Play one-time product port (Android Publisher v3
3
+ * `monetization.onetimeproducts`). The Google SDK lives under
4
+ * adapters/play-publisher; the CLI sees only these shapes.
5
+ * https://developers.google.com/android-publisher/api-ref/rest/v3/monetization.onetimeproducts
6
+ */
7
+ export {};
@@ -95,7 +95,74 @@ function wrapPublisher(publisher) {
95
95
  }
96
96
  };
97
97
  }
98
+ function createServiceAccountProductCatalog(serviceAccountFile) {
99
+ const googleAuth = new auth.GoogleAuth({ keyFile: serviceAccountFile, scopes: [scope] });
100
+ return wrapProductCatalog(androidpublisher({ version: "v3", auth: googleAuth }));
101
+ }
102
+ function createMockProductCatalog(accessToken, rootUrl) {
103
+ const oauth = new auth.OAuth2();
104
+ oauth.setCredentials({ access_token: accessToken });
105
+ return wrapProductCatalog(androidpublisher({ version: "v3", auth: oauth, rootUrl }));
106
+ }
107
+ function wrapProductCatalog(publisher) {
108
+ const products = publisher.monetization.onetimeproducts;
109
+ return {
110
+ async listOneTimeProducts(packageName, signal) {
111
+ const all = [];
112
+ let pageToken;
113
+ do {
114
+ const response = await products.list({
115
+ packageName,
116
+ pageSize: 1e3,
117
+ ...pageToken === void 0 ? {} : { pageToken }
118
+ }, { signal });
119
+ all.push(...response.data.oneTimeProducts ?? []);
120
+ const next = response.data.nextPageToken;
121
+ pageToken = typeof next === "string" && next !== "" ? next : void 0;
122
+ } while (pageToken !== void 0);
123
+ return all;
124
+ },
125
+ async convertRegionPrices(input) {
126
+ const response = await publisher.monetization.convertRegionPrices({
127
+ packageName: input.packageName,
128
+ requestBody: {
129
+ price: input.price,
130
+ ...input.productTaxCategoryCode === void 0 ? {} : { productTaxCategoryCode: input.productTaxCategoryCode }
131
+ }
132
+ }, { signal: input.signal });
133
+ return response.data;
134
+ },
135
+ async patchOneTimeProduct(input) {
136
+ const response = await products.patch({
137
+ packageName: input.packageName,
138
+ productId: input.productId,
139
+ allowMissing: input.allowMissing,
140
+ updateMask: input.updateMask,
141
+ "regionsVersion.version": input.regionsVersion,
142
+ requestBody: input.product
143
+ }, { retry: false, signal: input.signal });
144
+ return response.data;
145
+ },
146
+ async activatePurchaseOption(input) {
147
+ await products.purchaseOptions.batchUpdateStates({
148
+ packageName: input.packageName,
149
+ productId: input.productId,
150
+ requestBody: {
151
+ requests: [{
152
+ activatePurchaseOptionRequest: {
153
+ packageName: input.packageName,
154
+ productId: input.productId,
155
+ purchaseOptionId: input.purchaseOptionId
156
+ }
157
+ }]
158
+ }
159
+ }, { retry: false, signal: input.signal });
160
+ }
161
+ };
162
+ }
98
163
  export {
164
+ createMockProductCatalog,
99
165
  createMockPublisher,
166
+ createServiceAccountProductCatalog,
100
167
  createServiceAccountPublisher
101
168
  };
@@ -0,0 +1,144 @@
1
+ import { type ProductCatalog, type StoreListingCatalog } from '@mpgd/catalog';
2
+ import type { AscIapPort, AscIapType } from './app-store-iap-port.js';
3
+ import { type StoreProductAction } from './store-products.js';
4
+ /**
5
+ * App Store Connect in-app purchase sync: compare the game's catalog and
6
+ * store listing file with App Store Connect and plan the writes. The plan
7
+ * only reads; in-app purchases only on App Store Connect are reported, never
8
+ * deleted; localizations and territories the file does not name are kept.
9
+ * Nothing is submitted for review: an in-app purchase's first review goes
10
+ * with an app version (App Store Connect Help, "Submit an In-App Purchase").
11
+ */
12
+ export type AppStoreProductChange = 'create' | 'reference-name' | 'family-sharing' | 'version' | 'localizations' | 'prices' | 'availability' | 'review-screenshot';
13
+ export interface AppStoreLocalizationEntry {
14
+ readonly locale: string;
15
+ readonly name: string;
16
+ readonly description: string;
17
+ }
18
+ export interface AppStoreProductWrite {
19
+ readonly create?: {
20
+ readonly type: AscIapType;
21
+ readonly referenceName: string;
22
+ readonly familySharable: boolean;
23
+ };
24
+ readonly update?: {
25
+ readonly referenceName?: string;
26
+ readonly familySharable?: true;
27
+ };
28
+ /**
29
+ * Localizations to upsert by locale. `existing` writes to `versionId`;
30
+ * `create` makes a new draft version first (App Store Connect copies the
31
+ * current metadata into it); `resolve` (a new in-app purchase) uses the
32
+ * version App Store Connect provisioned, or creates one.
33
+ */
34
+ readonly localizations?: {
35
+ readonly version: 'existing' | 'create' | 'resolve';
36
+ readonly versionId?: string;
37
+ readonly entries: readonly AppStoreLocalizationEntry[];
38
+ };
39
+ /** A new schedule effective now; the base territory's price comes first. */
40
+ readonly prices?: {
41
+ readonly baseTerritory: string;
42
+ /** `pricePointId` is resolved at plan time for an existing in-app purchase. */
43
+ readonly prices: readonly {
44
+ readonly territory: string;
45
+ readonly customerPrice: string;
46
+ readonly pricePointId?: string;
47
+ }[];
48
+ };
49
+ readonly availability?: {
50
+ readonly territories: readonly string[];
51
+ readonly availableInNewTerritories: boolean;
52
+ };
53
+ readonly reviewScreenshot?: {
54
+ /** Absolute path on this checkout. */
55
+ readonly file: string;
56
+ /** Path relative to the listing file, as the file names it. */
57
+ readonly path: string;
58
+ readonly md5: string;
59
+ /** The completed screenshot to delete first (App Store Connect keeps one). */
60
+ readonly replaceId?: string;
61
+ };
62
+ }
63
+ export interface AppStorePriceChange {
64
+ /** Alpha-3 territory, or `base-territory`. */
65
+ readonly territory: string;
66
+ readonly from?: string;
67
+ readonly to?: string;
68
+ }
69
+ export interface PlannedAppStoreProduct {
70
+ readonly logicalId: string;
71
+ readonly productId: string;
72
+ readonly action: StoreProductAction;
73
+ readonly changes: readonly AppStoreProductChange[];
74
+ readonly iapId?: string;
75
+ readonly state?: string;
76
+ /** `APPROVED`: the in-app purchase is on sale wherever the app is. */
77
+ readonly live: boolean;
78
+ readonly livePriceChange: boolean;
79
+ readonly priceChanges: readonly AppStorePriceChange[];
80
+ readonly notes: readonly string[];
81
+ readonly remoteSha256: string | null;
82
+ readonly desiredSha256: string;
83
+ readonly write?: AppStoreProductWrite;
84
+ }
85
+ export interface AppStoreProductPlan {
86
+ readonly schemaVersion: 1;
87
+ readonly store: 'app-store';
88
+ /** Numeric App Store Connect app ID. */
89
+ readonly appId: string;
90
+ readonly bundleId: string;
91
+ readonly catalogTarget: string;
92
+ readonly keepLivePrices: boolean;
93
+ readonly products: readonly PlannedAppStoreProduct[];
94
+ readonly skipped: readonly {
95
+ readonly logicalId: string;
96
+ readonly productId?: string;
97
+ readonly reason: string;
98
+ }[];
99
+ /** Product IDs on App Store Connect that no catalog product names. Never deleted. */
100
+ readonly remoteOnly: readonly string[];
101
+ /** What the sync cannot do; the reviewer does it in App Store Connect. */
102
+ readonly manual: readonly string[];
103
+ }
104
+ export interface AppStoreProductPlanInput {
105
+ readonly appId: string;
106
+ readonly bundleId: string;
107
+ readonly catalog: ProductCatalog;
108
+ readonly listings: StoreListingCatalog;
109
+ /** Directory of the listing file; review screenshot paths resolve against it. */
110
+ readonly listingsDirectory: string;
111
+ /** Catalog `platformProductIds` key of the App Store; defaults to `ios`. */
112
+ readonly catalogTarget?: string;
113
+ readonly keepLivePrices?: boolean;
114
+ }
115
+ export declare function planAppStoreProducts(input: AppStoreProductPlanInput, port: AscIapPort): Promise<AppStoreProductPlan>;
116
+ export interface AppStoreProductApplyResult {
117
+ readonly appId: string;
118
+ readonly products: readonly {
119
+ readonly productId: string;
120
+ readonly status: 'created' | 'updated' | 'unchanged';
121
+ readonly iapId?: string;
122
+ }[];
123
+ readonly manual: readonly string[];
124
+ }
125
+ export declare class AppStoreProductApplyError extends Error {
126
+ readonly productId: string;
127
+ readonly stage: string;
128
+ readonly done: AppStoreProductApplyResult['products'];
129
+ constructor(productId: string, stage: string, done: AppStoreProductApplyResult['products'], cause: unknown, detail?: string);
130
+ }
131
+ /**
132
+ * Apply a reviewed plan, like the Google Play apply: `current` is a fresh
133
+ * plan, and every product apply would write must still match the reviewed
134
+ * remote state and planned write.
135
+ */
136
+ export declare function applyAppStoreProductPlan(input: {
137
+ readonly saved: AppStoreProductPlan;
138
+ readonly current: AppStoreProductPlan;
139
+ readonly port: AscIapPort;
140
+ readonly approved: boolean;
141
+ readonly confirmPriceChange?: boolean;
142
+ }): Promise<AppStoreProductApplyResult>;
143
+ /** One line per product for the terminal, then the manual steps. */
144
+ export declare function formatAppStoreProductPlan(plan: AppStoreProductPlan): string;