@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 +66 -0
- package/package.json +12 -12
- package/templates/phaser-game/agent/acceptance.md +2 -0
- package/templates/phaser-game/agent/game-manifest.json +4 -0
- package/templates/phaser-game/apps/target-devvit/src/server/index.ts +46 -1
- package/templates/phaser-game/apps/target-devvit/vite.server.config.ts +2 -1
- package/templates/phaser-game/src/env.d.ts +1 -0
- package/templates/phaser-game/src/main.ts +37 -1
- package/templates/phaser-game/src/runtime/gameContext.ts +3 -1
- package/templates/phaser-game/vite.config.ts +57 -0
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.
|
|
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-
|
|
53
|
-
"@mpgd/adapter-
|
|
54
|
-
"@mpgd/adapter-capacitor": "0.
|
|
55
|
-
"@mpgd/
|
|
56
|
-
"@mpgd/
|
|
57
|
-
"@mpgd/bridge": "0.
|
|
58
|
-
"@mpgd/catalog": "0.3.
|
|
59
|
-
"@mpgd/
|
|
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/
|
|
62
|
-
"@mpgd/target-config": "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
|
|
@@ -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:
|
|
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
|
-
|
|
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
|
+
}
|