@mpgd/cli 0.62.0 → 0.64.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.
Files changed (39) hide show
  1. package/dist/app-store-iap-asc.d.ts +14 -0
  2. package/dist/app-store-iap-asc.js +219 -0
  3. package/dist/app-store-iap-port.d.ts +125 -0
  4. package/dist/app-store-iap-port.js +14 -0
  5. package/dist/app-store-presence-asc.d.ts +23 -0
  6. package/dist/app-store-presence-asc.js +162 -0
  7. package/dist/app-store-presence-port.d.ts +101 -0
  8. package/dist/app-store-presence-port.js +13 -0
  9. package/dist/asc-session.d.ts +43 -0
  10. package/dist/asc-session.js +118 -0
  11. package/dist/index.d.ts +10 -0
  12. package/dist/index.js +169 -1
  13. package/dist/native-build-info.json +2 -2
  14. package/dist/native-build-target.js +411 -339
  15. package/dist/play-listing-port.d.ts +88 -0
  16. package/dist/play-listing-port.js +12 -0
  17. package/dist/play-product-port.d.ts +101 -0
  18. package/dist/play-product-port.js +7 -0
  19. package/dist/play-sdk-adapter.js +168 -15
  20. package/dist/store-presence-app-store.d.ts +139 -0
  21. package/dist/store-presence-app-store.js +537 -0
  22. package/dist/store-presence-command.d.ts +50 -0
  23. package/dist/store-presence-command.js +145 -0
  24. package/dist/store-presence-play.d.ts +116 -0
  25. package/dist/store-presence-play.js +495 -0
  26. package/dist/store-presence.d.ts +106 -0
  27. package/dist/store-presence.js +173 -0
  28. package/dist/store-products-app-store.d.ts +144 -0
  29. package/dist/store-products-app-store.js +632 -0
  30. package/dist/store-products-command.d.ts +51 -0
  31. package/dist/store-products-command.js +155 -0
  32. package/dist/store-products-play.d.ts +112 -0
  33. package/dist/store-products-play.js +622 -0
  34. package/dist/store-products.d.ts +38 -0
  35. package/dist/store-products.js +56 -0
  36. package/dist/store-sync.d.ts +50 -0
  37. package/dist/store-sync.js +137 -0
  38. package/dist/testflight-submission.d.ts +1 -1
  39. package/package.json +13 -13
@@ -0,0 +1,56 @@
1
+ import { existsSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { assertProductCatalog, assertStoreListingCatalog, } from '@mpgd/catalog';
4
+ import { readJsonFile, StorePlanStaleError } from './store-sync.js';
5
+ export { canonicalLanguageTag, mapWithConcurrency, stableJson, stableSha256, } from './store-sync.js';
6
+ export const defaultStoreListingFileName = 'mpgd.store-listings.json';
7
+ export const defaultProductCatalogFileName = 'mpgd.catalog.json';
8
+ /**
9
+ * Read and cross-check the game's product catalog and store listing file.
10
+ * Given paths resolve against the current directory, like every other mpgd
11
+ * file flag (`deploy --plan`, `--release-notes`); the defaults are the
12
+ * game's own files.
13
+ */
14
+ export function readStoreProductSources(input) {
15
+ const catalogFile = input.catalogFile === undefined
16
+ ? path.join(path.resolve(input.gameRoot), defaultProductCatalogFileName)
17
+ : path.resolve(input.catalogFile);
18
+ const listingsFile = input.listingsFile === undefined
19
+ ? path.join(path.resolve(input.gameRoot), defaultStoreListingFileName)
20
+ : path.resolve(input.listingsFile);
21
+ for (const file of [catalogFile, listingsFile]) {
22
+ if (!existsSync(file)) {
23
+ throw new Error(`Store product sync needs ${file}.`);
24
+ }
25
+ }
26
+ const catalog = assertProductCatalog(readJsonFile(catalogFile));
27
+ const listings = assertStoreListingCatalog(readJsonFile(listingsFile), catalog);
28
+ return { catalogFile, listingsFile, catalog, listings };
29
+ }
30
+ export class StoreProductPlanStaleError extends StorePlanStaleError {
31
+ constructor(detail) {
32
+ super(detail, 'mpgd store products plan');
33
+ this.name = 'StoreProductPlanStaleError';
34
+ }
35
+ }
36
+ /** Decimal string (`"0.99"`) as whole units and nanos, without floating point. */
37
+ export function decimalToUnitsAndNanos(amount) {
38
+ const match = /^(0|[1-9]\d{0,11})(?:\.(\d{1,9}))?$/u.exec(amount);
39
+ if (match === null) {
40
+ throw new Error(`Price ${amount} is not a plain decimal amount.`);
41
+ }
42
+ return {
43
+ units: match[1] ?? '0',
44
+ nanos: Number((match[2] ?? '').padEnd(9, '0')),
45
+ };
46
+ }
47
+ /** Decimal strings compared by value: `0.99` equals `0.990`. */
48
+ export function sameDecimal(left, right) {
49
+ const normal = (value) => {
50
+ const [whole = '0', fraction = ''] = value.trim().split('.');
51
+ const trimmedFraction = fraction.replace(/0+$/u, '');
52
+ const trimmedWhole = whole.replace(/^0+(?=\d)/u, '');
53
+ return trimmedFraction === '' ? trimmedWhole : `${trimmedWhole}.${trimmedFraction}`;
54
+ };
55
+ return normal(left) === normal(right);
56
+ }
@@ -0,0 +1,50 @@
1
+ import type { AscCredential } from './app-store-iap-asc.js';
2
+ /**
3
+ * What every `mpgd store <kind> plan|apply` shares: the app and credential
4
+ * the deployment profile names, the saved plan file, the stable digests
5
+ * apply compares, and moving a reviewed plan to another checkout.
6
+ */
7
+ export type StoreSyncStore = 'google-play' | 'app-store';
8
+ /** The store app the deployment profile names, with its credential. */
9
+ export type StoreSyncApp = {
10
+ readonly store: 'google-play';
11
+ readonly packageName: string;
12
+ readonly serviceAccountFile: string;
13
+ } | {
14
+ readonly store: 'app-store';
15
+ /** Numeric App Store Connect app ID. */
16
+ readonly appId: string;
17
+ readonly bundleId: string;
18
+ readonly credential: AscCredential;
19
+ };
20
+ /**
21
+ * Resolve the app and credential of `store` from the game's deployment
22
+ * profile, the same ones `deploy submit` uses.
23
+ */
24
+ export declare function resolveStoreSyncApp(options: {
25
+ readonly game: string;
26
+ readonly profile: string;
27
+ readonly store: StoreSyncStore;
28
+ }, environment: NodeJS.ProcessEnv): StoreSyncApp;
29
+ export declare function assertStoreSyncStore(store: string): asserts store is StoreSyncStore;
30
+ /**
31
+ * A file a saved plan read, moved to the game directory apply runs in
32
+ * (`apply --game`). Files outside the saved game directory cannot move.
33
+ */
34
+ export declare function relocateStoreSyncFile(savedGameRoot: string, gameRoot: string | undefined, file: string): string;
35
+ /** JSON with object keys sorted, so equal values always hash the same. */
36
+ export declare function stableJson(value: unknown): string;
37
+ export declare function stableSha256(value: unknown): string;
38
+ /** A new plan file, never overwriting one (the same rule as `deploy plan --out`). */
39
+ export declare function writeStorePlanFile(file: string, plan: unknown): string;
40
+ /** A saved plan's top-level object; the caller checks its fields. */
41
+ export declare function readStorePlanFile(file: string, label: string): Record<string, unknown>;
42
+ /** The store or the game files moved past the reviewed plan. */
43
+ export declare class StorePlanStaleError extends Error {
44
+ constructor(detail: string, planCommand: string);
45
+ }
46
+ /** BCP-47 casing: language lowercase, script title case, region uppercase. */
47
+ export declare function canonicalLanguageTag(tag: string): string;
48
+ /** `map` with at most `limit` calls in flight; results keep input order. */
49
+ export declare function mapWithConcurrency<T, R>(items: readonly T[], limit: number, map: (item: T) => Promise<R>): Promise<R[]>;
50
+ export declare function readJsonFile(file: string): unknown;
@@ -0,0 +1,137 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFileSync, writeFileSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { planNativeDeployment } from './deploy-planning.js';
5
+ import { readStoreCredential } from './native-deploy-run.js';
6
+ /**
7
+ * Resolve the app and credential of `store` from the game's deployment
8
+ * profile, the same ones `deploy submit` uses.
9
+ */
10
+ export function resolveStoreSyncApp(options, environment) {
11
+ assertStoreSyncStore(options.store);
12
+ const target = options.store === 'google-play' ? 'android' : 'ios';
13
+ const deployment = planNativeDeployment({
14
+ game: path.resolve(options.game),
15
+ profile: options.profile,
16
+ targets: [target],
17
+ });
18
+ const planned = deployment.targets.find((entry) => entry.target === target);
19
+ if (planned === undefined) {
20
+ throw new Error(`Deployment profile ${options.profile} has no ${target} target.`);
21
+ }
22
+ const credential = readStoreCredential(deployment, target, environment);
23
+ if (credential.target === 'android') {
24
+ return {
25
+ store: 'google-play',
26
+ packageName: planned.appId,
27
+ serviceAccountFile: credential.serviceAccountFile,
28
+ };
29
+ }
30
+ return {
31
+ store: 'app-store',
32
+ appId: credential.appStoreAppId,
33
+ bundleId: planned.appId,
34
+ credential: {
35
+ ascBinary: credential.ascBinary,
36
+ apiKeyId: credential.apiKeyId,
37
+ apiIssuerId: credential.apiIssuerId,
38
+ apiPrivateKeyBase64: credential.apiPrivateKeyBase64,
39
+ },
40
+ };
41
+ }
42
+ export function assertStoreSyncStore(store) {
43
+ if (store !== 'google-play' && store !== 'app-store') {
44
+ throw new Error('--store must be google-play or app-store.');
45
+ }
46
+ }
47
+ /**
48
+ * A file a saved plan read, moved to the game directory apply runs in
49
+ * (`apply --game`). Files outside the saved game directory cannot move.
50
+ */
51
+ export function relocateStoreSyncFile(savedGameRoot, gameRoot, file) {
52
+ if (gameRoot === undefined) {
53
+ return file;
54
+ }
55
+ const relative = path.relative(savedGameRoot, file);
56
+ if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
57
+ throw new Error(`The saved plan reads ${file}, outside its game directory; --game cannot move it. Plan again.`);
58
+ }
59
+ return path.resolve(gameRoot, relative);
60
+ }
61
+ /** JSON with object keys sorted, so equal values always hash the same. */
62
+ export function stableJson(value) {
63
+ return JSON.stringify(sortKeys(value));
64
+ }
65
+ export function stableSha256(value) {
66
+ return createHash('sha256').update(stableJson(value)).digest('hex');
67
+ }
68
+ /** A new plan file, never overwriting one (the same rule as `deploy plan --out`). */
69
+ export function writeStorePlanFile(file, plan) {
70
+ const resolved = path.resolve(file);
71
+ writeFileSync(resolved, `${JSON.stringify(plan, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
72
+ return resolved;
73
+ }
74
+ /** A saved plan's top-level object; the caller checks its fields. */
75
+ export function readStorePlanFile(file, label) {
76
+ const raw = readJsonFile(path.resolve(file));
77
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
78
+ throw new Error(`Saved ${label} is malformed.`);
79
+ }
80
+ return raw;
81
+ }
82
+ /** The store or the game files moved past the reviewed plan. */
83
+ export class StorePlanStaleError extends Error {
84
+ constructor(detail, planCommand) {
85
+ super(`${detail} Run "${planCommand}" again and review the new plan.`);
86
+ this.name = 'StorePlanStaleError';
87
+ }
88
+ }
89
+ /** BCP-47 casing: language lowercase, script title case, region uppercase. */
90
+ export function canonicalLanguageTag(tag) {
91
+ return tag.split('-').map((part, index) => {
92
+ if (index === 0) {
93
+ return part.toLowerCase();
94
+ }
95
+ if (/^[A-Za-z]{4}$/u.test(part)) {
96
+ return `${part.slice(0, 1).toUpperCase()}${part.slice(1).toLowerCase()}`;
97
+ }
98
+ if (/^[A-Za-z]{2}$/u.test(part)) {
99
+ return part.toUpperCase();
100
+ }
101
+ return part.toLowerCase();
102
+ }).join('-');
103
+ }
104
+ /** `map` with at most `limit` calls in flight; results keep input order. */
105
+ export async function mapWithConcurrency(items, limit, map) {
106
+ const results = new Array(items.length);
107
+ let next = 0;
108
+ const worker = async () => {
109
+ while (next < items.length) {
110
+ const index = next;
111
+ next += 1;
112
+ results[index] = await map(items[index]);
113
+ }
114
+ };
115
+ await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
116
+ return results;
117
+ }
118
+ export function readJsonFile(file) {
119
+ try {
120
+ return JSON.parse(readFileSync(file, 'utf8'));
121
+ }
122
+ catch (error) {
123
+ throw new Error(`${file} is not valid JSON: ${error.message}`);
124
+ }
125
+ }
126
+ function sortKeys(value) {
127
+ if (Array.isArray(value)) {
128
+ return value.map(sortKeys);
129
+ }
130
+ if (typeof value === 'object' && value !== null) {
131
+ return Object.fromEntries(Object.keys(value)
132
+ .sort()
133
+ .filter((key) => value[key] !== undefined)
134
+ .map((key) => [key, sortKeys(value[key])]));
135
+ }
136
+ return value;
137
+ }
@@ -53,6 +53,6 @@ export declare function classifyInternalBuildState(state: string): {
53
53
  readonly status: Extract<IosTestFlightStatus, 'processing' | 'action-required' | 'failed' | 'unknown'>;
54
54
  readonly detail?: string;
55
55
  } | undefined;
56
- export declare function isolatedAscEnvironment(input: IosTestFlightSubmissionInput, directory: string, source?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
56
+ export declare function isolatedAscEnvironment(input: Pick<IosTestFlightSubmissionInput, 'apiKeyId' | 'apiIssuerId' | 'apiPrivateKeyBase64'>, directory: string, source?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
57
57
  /** The package consumes one reviewed asc pin; it does not trust PATH or version text. */
58
58
  export declare function verifyPinnedAscBinary(binary: string, pinFile?: string): Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mpgd/cli",
3
- "version": "0.62.0",
3
+ "version": "0.64.0",
4
4
  "description": "Gunshi CLI for mpgd-kit starter generation and target workflow orchestration.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -70,10 +70,10 @@
70
70
  "parse5": "7.3.0",
71
71
  "postcss": "8.5.28",
72
72
  "postcss-value-parser": "4.2.0",
73
- "@mpgd/catalog": "0.11.3",
74
- "@mpgd/phaser-assets": "0.9.1",
75
- "@mpgd/target-config": "0.24.1",
76
- "@mpgd/platform": "0.32.0"
73
+ "@mpgd/catalog": "0.13.0",
74
+ "@mpgd/platform": "0.32.0",
75
+ "@mpgd/target-config": "0.24.3",
76
+ "@mpgd/phaser-assets": "0.9.1"
77
77
  },
78
78
  "devDependencies": {
79
79
  "@types/node": "^24.0.0",
@@ -81,17 +81,17 @@
81
81
  "@types/yauzl": "3.4.0",
82
82
  "ttsc": "0.32.1",
83
83
  "typescript": "7.0.2",
84
- "@mpgd/adapter-ait": "0.21.3",
84
+ "@mpgd/adapter-ait": "0.21.5",
85
85
  "@mpgd/adapter-browser": "0.12.1",
86
- "@mpgd/adapter-capacitor": "0.16.1",
87
- "@mpgd/adapter-verse8": "0.7.14",
86
+ "@mpgd/adapter-capacitor": "0.16.3",
87
+ "@mpgd/adapter-devvit": "0.14.7",
88
+ "@mpgd/adapter-verse8": "0.7.16",
88
89
  "@mpgd/bridge": "0.16.4",
89
- "@mpgd/capacitor-play-billing": "0.4.3",
90
- "@mpgd/adapter-devvit": "0.14.5",
91
- "@mpgd/capacitor-storekit": "0.4.3",
92
90
  "@mpgd/analytics": "0.5.5",
93
- "@mpgd/game-runtime": "0.8.7",
94
- "@mpgd/game-services": "0.30.3",
91
+ "@mpgd/game-runtime": "0.8.9",
92
+ "@mpgd/capacitor-play-billing": "0.4.5",
93
+ "@mpgd/capacitor-storekit": "0.4.5",
94
+ "@mpgd/game-services": "0.30.5",
95
95
  "@mpgd/i18n": "0.6.26",
96
96
  "@mpgd/lite-shell": "0.1.0"
97
97
  },