@mpgd/cli 0.5.0 → 0.6.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.
package/dist/index.js CHANGED
@@ -949,6 +949,7 @@ function toTemplatePath(value) {
949
949
  }
950
950
  function createTargetCommandEnv(values) {
951
951
  const targetsFile = path.resolve(readOptionalString(values['targets-file']) ?? 'mpgd.targets.json');
952
+ const gameRoot = path.dirname(targetsFile);
952
953
  const kitPath = resolveKitPathForTarget(values);
953
954
  const resolvedTargetsFile = readOptionalString(values['resolved-targets-file']);
954
955
  const preparedTargetsFile = preparePlatformTargetsFile({
@@ -960,10 +961,75 @@ function createTargetCommandEnv(values) {
960
961
  });
961
962
  return {
962
963
  ...process.env,
964
+ ...createGameOwnedReleaseEnv(gameRoot, process.env),
963
965
  MPGD_KIT_PATH: kitPath,
964
966
  MPGD_PLATFORM_TARGETS_FILE: preparedTargetsFile,
965
967
  };
966
968
  }
969
+ function createGameOwnedReleaseEnv(gameRoot, baseEnv) {
970
+ const monetizationFiles = resolveGameOwnedMonetizationFiles(gameRoot, baseEnv);
971
+ const sourceGitSha = readOptionalString(baseEnv.MPGD_SOURCE_GIT_SHA)
972
+ ?? readSourceGitSha(gameRoot);
973
+ const appVersion = readOptionalString(baseEnv.APP_VERSION)
974
+ ?? readGamePackageVersion(gameRoot);
975
+ return {
976
+ ...monetizationFiles,
977
+ MPGD_SOURCE_GIT_SHA: sourceGitSha,
978
+ ...(appVersion === undefined ? {} : { APP_VERSION: appVersion }),
979
+ };
980
+ }
981
+ function resolveGameOwnedMonetizationFiles(gameRoot, baseEnv) {
982
+ const configuredCatalog = readConfiguredPath(baseEnv.MPGD_PRODUCT_CATALOG_FILE);
983
+ const configuredPlacements = readConfiguredPath(baseEnv.MPGD_AD_PLACEMENTS_FILE);
984
+ if ((configuredCatalog === undefined) !== (configuredPlacements === undefined)) {
985
+ throw new Error('MPGD_PRODUCT_CATALOG_FILE and MPGD_AD_PLACEMENTS_FILE must be configured together.');
986
+ }
987
+ if (configuredCatalog !== undefined && configuredPlacements !== undefined) {
988
+ return {
989
+ MPGD_PRODUCT_CATALOG_FILE: path.resolve(gameRoot, configuredCatalog),
990
+ MPGD_AD_PLACEMENTS_FILE: path.resolve(gameRoot, configuredPlacements),
991
+ };
992
+ }
993
+ const catalogFile = path.join(gameRoot, 'mpgd.catalog.json');
994
+ const placementsFile = path.join(gameRoot, 'mpgd.ad-placements.json');
995
+ const hasCatalog = existsSync(catalogFile);
996
+ const hasPlacements = existsSync(placementsFile);
997
+ if (hasCatalog !== hasPlacements) {
998
+ throw new Error(`Game target builds must provide both mpgd.catalog.json and mpgd.ad-placements.json: ${gameRoot}`);
999
+ }
1000
+ if (!hasCatalog) {
1001
+ return {};
1002
+ }
1003
+ return {
1004
+ MPGD_PRODUCT_CATALOG_FILE: catalogFile,
1005
+ MPGD_AD_PLACEMENTS_FILE: placementsFile,
1006
+ };
1007
+ }
1008
+ function readConfiguredPath(value) {
1009
+ const normalized = value?.trim();
1010
+ return normalized === undefined || normalized.length === 0 ? undefined : normalized;
1011
+ }
1012
+ function readSourceGitSha(gameRoot) {
1013
+ const result = spawnSync('git', ['-C', gameRoot, 'rev-parse', '--short', 'HEAD'], {
1014
+ encoding: 'utf8',
1015
+ stdio: ['ignore', 'pipe', 'ignore'],
1016
+ });
1017
+ if (result.error !== undefined || result.status !== 0) {
1018
+ return 'uncommitted';
1019
+ }
1020
+ return result.stdout.trim() || 'uncommitted';
1021
+ }
1022
+ function readGamePackageVersion(gameRoot) {
1023
+ const packageFile = path.join(gameRoot, 'package.json');
1024
+ if (!existsSync(packageFile)) {
1025
+ return undefined;
1026
+ }
1027
+ const packageJson = readJsonForCli(packageFile);
1028
+ if (typeof packageJson !== 'object' || packageJson === null || Array.isArray(packageJson)) {
1029
+ return undefined;
1030
+ }
1031
+ return readOptionalString(packageJson.version);
1032
+ }
967
1033
  function preparePlatformTargetsFile(input) {
968
1034
  const parsed = readJsonForCli(input.targetsFile);
969
1035
  const gameRoot = path.dirname(input.targetsFile);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mpgd/cli",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Gunshi CLI for mpgd-kit starter generation and target workflow orchestration.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -49,18 +49,18 @@
49
49
  "@types/node": "^24.0.0",
50
50
  "ttsc": "0.16.9",
51
51
  "typescript": "7.0.1-rc",
52
- "@mpgd/adapter-ait": "0.3.3",
53
- "@mpgd/adapter-browser": "0.3.2",
54
- "@mpgd/adapter-capacitor": "0.3.3",
55
- "@mpgd/adapter-devvit": "0.3.3",
56
- "@mpgd/analytics": "0.3.2",
57
- "@mpgd/bridge": "0.4.0",
58
- "@mpgd/catalog": "0.3.2",
59
- "@mpgd/game-services": "0.3.3",
52
+ "@mpgd/adapter-browser": "0.4.0",
53
+ "@mpgd/adapter-devvit": "0.4.0",
54
+ "@mpgd/adapter-capacitor": "0.4.0",
55
+ "@mpgd/analytics": "0.3.3",
56
+ "@mpgd/adapter-ait": "0.4.0",
57
+ "@mpgd/bridge": "0.5.0",
58
+ "@mpgd/catalog": "0.3.3",
59
+ "@mpgd/i18n": "0.4.1",
60
+ "@mpgd/game-services": "0.4.0",
60
61
  "@mpgd/phaser-assets": "0.4.0",
61
- "@mpgd/i18n": "0.4.0",
62
- "@mpgd/target-config": "0.4.0",
63
- "@mpgd/platform": "0.3.2"
62
+ "@mpgd/platform": "0.4.0",
63
+ "@mpgd/target-config": "0.5.0"
64
64
  },
65
65
  "main": "./dist/index.js",
66
66
  "types": "./dist/index.d.ts",
@@ -12,6 +12,8 @@ pnpm --dir ../mpgd-kit mpgd target doctor --targets-file "$PWD/mpgd.targets.json
12
12
 
13
13
  For Apps in Toss changes, use the apps-in-toss MCP before implementation and
14
14
  keep SDK calls inside adapters or target wrappers.
15
+ Resolve identity and launch intents during bootstrap, treat inbound share data as
16
+ untrusted, and keep notification delivery on the server.
15
17
 
16
18
  For Reddit Devvit changes, keep Devvit SDK calls inside the target wrapper and
17
19
  continue to expose game-facing behavior through `PlatformGateway`. The Devvit
@@ -25,6 +25,10 @@
25
25
  "id": "runtime.platform.gateway",
26
26
  "owner": "src/platform/installPlatform.ts"
27
27
  },
28
+ {
29
+ "id": "platform.gateway.bootstrap-intents",
30
+ "owner": "src/main.ts"
31
+ },
28
32
  {
29
33
  "id": "runtime.viewport.orientation-policy",
30
34
  "owner": "src/main.ts"
@@ -168,7 +168,7 @@ async function handleBridgeRequest(input: BridgeRequest): Promise<BridgeResponse
168
168
  nativeLeaderboard: true,
169
169
  achievements: false,
170
170
  cloudSave: true,
171
- socialShare: true,
171
+ socialShare: false,
172
172
  haptics: false,
173
173
  localizedContent: true,
174
174
  });
@@ -186,6 +186,51 @@ async function handleBridgeRequest(input: BridgeRequest): Promise<BridgeResponse
186
186
  });
187
187
  }
188
188
 
189
+ case 'identity.getSession': {
190
+ const playerId = currentPlayerId();
191
+
192
+ return ok(
193
+ input,
194
+ playerId === undefined
195
+ ? {
196
+ identityLevel: 'guest',
197
+ trustLevel: 'local',
198
+ }
199
+ : {
200
+ identityLevel: 'authenticated',
201
+ playerId,
202
+ trustLevel: 'server-verified',
203
+ },
204
+ );
205
+ }
206
+
207
+ case 'identity.requestUpgrade': {
208
+ const authenticated = currentPlayerId() !== undefined;
209
+
210
+ return ok(input, {
211
+ status: authenticated ? 'completed' : 'unavailable',
212
+ reloadExpected: false,
213
+ });
214
+ }
215
+
216
+ case 'presentation.getLaunchIntent':
217
+ return ok(input, { entry: 'home' });
218
+
219
+ case 'presentation.requestGameSurface':
220
+ return ok(input, 'unavailable');
221
+
222
+ case 'share.share':
223
+ return ok(input, { status: 'unavailable' });
224
+
225
+ case 'share.readInboundShare':
226
+ return ok(input, null);
227
+
228
+ case 'notifications.getStatus':
229
+ return ok(input, 'approval-required');
230
+
231
+ case 'notifications.requestSubscription':
232
+ return ok(input, 'unavailable');
233
+
189
234
  case 'commerce.getProducts':
190
235
  case 'commerce.getEntitlements':
191
236
  return ok(input, []);
@@ -22,6 +22,7 @@ export default defineConfig({
22
22
  ssr: 'src/server/index.ts',
23
23
  outDir: 'dist/server',
24
24
  target: 'node22',
25
+ minify: 'esbuild',
25
26
  sourcemap: false,
26
27
  emptyOutDir: true,
27
28
  rollupOptions: {
@@ -29,7 +30,7 @@ export default defineConfig({
29
30
  output: {
30
31
  format: 'cjs',
31
32
  entryFileNames: 'index.cjs',
32
- inlineDynamicImports: true,
33
+ codeSplitting: false,
33
34
  },
34
35
  },
35
36
  },
@@ -4,6 +4,7 @@ declare const __APP_TARGET__: string;
4
4
  declare const __MPGD_CONFIG_TARGET__: string;
5
5
  declare const __APP_VERSION__: string;
6
6
  declare const __BUILD_ID__: string;
7
+ declare const __SOURCE_GIT_SHA__: string;
7
8
  declare const __DEBUG_BUILD__: boolean;
8
9
 
9
10
  interface ImportMetaEnv {
@@ -2,6 +2,7 @@ import './styles.css';
2
2
 
3
3
  import { createAnalyticsReporter, createBufferedAnalyticsSink } from '@mpgd/analytics';
4
4
  import { resolveMpgdLocale, type Locale } from '@mpgd/i18n';
5
+ import type { IdentitySession, LaunchIntent, PlatformGateway } from '@mpgd/platform';
5
6
  import {
6
7
  resolveTargetViewportPlan,
7
8
  type TargetViewportOrientationPolicy,
@@ -41,6 +42,10 @@ async function bootstrap(): Promise<void> {
41
42
  playerId: 'local-player',
42
43
  displayName: 'Local Player',
43
44
  };
45
+ const [identitySession, launchIntent] = await Promise.all([
46
+ resolveIdentitySession(platform, player.playerId),
47
+ resolveLaunchIntent(platform),
48
+ ]);
44
49
  locale = resolveMpgdLocale(runtime.capabilities);
45
50
  const analyticsSink = createBufferedAnalyticsSink();
46
51
  const analytics = createAnalyticsReporter({
@@ -50,7 +55,7 @@ async function bootstrap(): Promise<void> {
50
55
  });
51
56
  const gameServices = createStarterGameServices({
52
57
  gateway: platform,
53
- playerId: player.playerId,
58
+ playerId: identitySession.playerId ?? player.playerId,
54
59
  });
55
60
 
56
61
  await analytics.track({
@@ -68,6 +73,8 @@ async function bootstrap(): Promise<void> {
68
73
  runtime,
69
74
  viewport,
70
75
  player,
76
+ identitySession,
77
+ launchIntent,
71
78
  locale,
72
79
  gameServices,
73
80
  analytics,
@@ -80,6 +87,35 @@ async function bootstrap(): Promise<void> {
80
87
  }
81
88
  }
82
89
 
90
+ async function resolveIdentitySession(
91
+ platform: PlatformGateway,
92
+ fallbackPlayerId: string,
93
+ ): Promise<IdentitySession> {
94
+ try {
95
+ return (await platform.identity.getSession?.()) ?? createGuestSession(fallbackPlayerId);
96
+ } catch (error) {
97
+ console.warn('[platform] identity session unavailable; using guest fallback.', error);
98
+ return createGuestSession(fallbackPlayerId);
99
+ }
100
+ }
101
+
102
+ async function resolveLaunchIntent(platform: PlatformGateway): Promise<LaunchIntent> {
103
+ try {
104
+ return (await platform.presentation?.getLaunchIntent()) ?? { entry: 'home' };
105
+ } catch (error) {
106
+ console.warn('[platform] launch intent unavailable; using home fallback.', error);
107
+ return { entry: 'home' };
108
+ }
109
+ }
110
+
111
+ function createGuestSession(playerId: string): IdentitySession {
112
+ return {
113
+ identityLevel: 'guest',
114
+ playerId,
115
+ trustLevel: 'local',
116
+ };
117
+ }
118
+
83
119
  function renderBootstrapError(error: unknown, locale: Locale): void {
84
120
  const message = error instanceof Error ? error.message : String(error);
85
121
  const root = document.querySelector<HTMLDivElement>('#game');
@@ -1,6 +1,6 @@
1
1
  import type { AnalyticsReporter, BufferedAnalyticsSink } from '@mpgd/analytics';
2
2
  import type { Locale } from '@mpgd/i18n';
3
- import type { PlayerIdentity } from '@mpgd/platform';
3
+ import type { IdentitySession, LaunchIntent, PlayerIdentity } from '@mpgd/platform';
4
4
  import type {
5
5
  TargetConfiguredGateway,
6
6
  TargetRuntimeSnapshot,
@@ -16,6 +16,8 @@ export interface StarterContext {
16
16
  readonly runtime: TargetRuntimeSnapshot;
17
17
  readonly viewport: TargetViewportPlan;
18
18
  readonly player: PlayerIdentity;
19
+ readonly identitySession: IdentitySession;
20
+ readonly launchIntent: LaunchIntent;
19
21
  readonly locale: Locale;
20
22
  readonly gameServices: StarterGameServices;
21
23
  readonly analytics: AnalyticsReporter;
@@ -1,3 +1,6 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+
1
4
  import ttsc from '@ttsc/unplugin/vite';
2
5
  import { defineConfig } from 'vite';
3
6
 
@@ -12,11 +15,15 @@ export default defineConfig(({ mode }) => {
12
15
  plugins: false,
13
16
  }),
14
17
  ],
18
+ resolve: {
19
+ alias: createCatalogAliases(),
20
+ },
15
21
  define: {
16
22
  __APP_TARGET__: JSON.stringify(process.env.APP_TARGET ?? 'browser'),
17
23
  __MPGD_CONFIG_TARGET__: JSON.stringify(process.env.MPGD_CONFIG_TARGET ?? ''),
18
24
  __APP_VERSION__: JSON.stringify(process.env.APP_VERSION ?? '0.0.0-dev'),
19
25
  __BUILD_ID__: JSON.stringify(process.env.BUILD_ID ?? 'local'),
26
+ __SOURCE_GIT_SHA__: JSON.stringify(process.env.MPGD_SOURCE_GIT_SHA ?? 'uncommitted'),
20
27
  __DEBUG_BUILD__: JSON.stringify(!isProduction),
21
28
  },
22
29
  build: {
@@ -35,3 +42,53 @@ export default defineConfig(({ mode }) => {
35
42
  },
36
43
  };
37
44
  });
45
+
46
+ function createCatalogAliases(): Record<string, string> {
47
+ const productCatalogFile = readConfiguredPath(process.env.MPGD_PRODUCT_CATALOG_FILE);
48
+ const adPlacementsFile = readConfiguredPath(process.env.MPGD_AD_PLACEMENTS_FILE);
49
+
50
+ if ((productCatalogFile === undefined) !== (adPlacementsFile === undefined)) {
51
+ throw new Error(
52
+ 'MPGD_PRODUCT_CATALOG_FILE and MPGD_AD_PLACEMENTS_FILE must be configured together.',
53
+ );
54
+ }
55
+
56
+ if (productCatalogFile === undefined || adPlacementsFile === undefined) {
57
+ return {};
58
+ }
59
+
60
+ return {
61
+ '@mpgd/catalog/catalog.json': resolveCatalogPath(productCatalogFile, adPlacementsFile),
62
+ '@mpgd/catalog/placements.json': resolveCatalogPath(adPlacementsFile, productCatalogFile),
63
+ };
64
+ }
65
+
66
+ function readConfiguredPath(value: string | undefined): string | undefined {
67
+ const normalized = value?.trim();
68
+ return normalized === undefined || normalized.length === 0 ? undefined : normalized;
69
+ }
70
+
71
+ function resolveCatalogPath(path: string, pairedPath: string): string {
72
+ return resolve(resolveCatalogBaseDir(path, pairedPath), path);
73
+ }
74
+
75
+ function resolveCatalogBaseDir(path: string, pairedPath: string): string {
76
+ const fallbackBaseDir = process.cwd();
77
+ const candidates = [
78
+ fallbackBaseDir,
79
+ readConfiguredPath(process.env.INIT_CWD),
80
+ readConfiguredPath(process.env.PWD),
81
+ ];
82
+
83
+ for (const candidate of candidates) {
84
+ if (
85
+ candidate !== undefined
86
+ && existsSync(resolve(candidate, path))
87
+ && existsSync(resolve(candidate, pairedPath))
88
+ ) {
89
+ return candidate;
90
+ }
91
+ }
92
+
93
+ return fallbackBaseDir;
94
+ }