@mpgd/cli 0.21.0 → 0.22.1
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.d.ts +2 -1
- package/dist/index.js +92 -8
- package/dist/microsoft-store-package-generation-contract.d.ts +5 -0
- package/dist/microsoft-store-package-generation-input.js +55 -0
- package/dist/microsoft-store-starter.d.ts +7 -0
- package/dist/microsoft-store-starter.js +176 -4
- package/dist/microsoft-store-submission.d.ts +24 -3
- package/dist/microsoft-store-submission.js +131 -4
- package/dist/offline-playtest.d.ts +31 -0
- package/dist/offline-playtest.js +5718 -0
- package/dist/production-target-readiness.js +38 -19
- package/package.json +15 -13
- package/templates/phaser-game/README.md +57 -33
- package/templates/phaser-game/apps/target-ait/apps-in-toss.config.ts +12 -0
- package/templates/phaser-game/apps/target-ait/package.json +5 -8
- package/templates/phaser-game/apps/target-ait/vite.config.ts +6 -15
- package/templates/phaser-game/apps/target-cloudflare-pages/package.json +1 -1
- package/templates/phaser-game/apps/target-devvit/README.md +2 -1
- package/templates/phaser-game/apps/target-devvit/package.json +1 -1
- package/templates/phaser-game/apps/target-devvit/src/server/index.ts +1 -0
- package/templates/phaser-game/docs/MPGD_KIT_WORKFLOWS.md +11 -0
- package/templates/phaser-game/mpgd.targets.json +8 -1
- package/templates/phaser-game/package.json +2 -5
- package/templates/phaser-game/pnpm-workspace.yaml +3 -2
- package/templates/phaser-game/src/entry.ts +10 -4
- package/templates/phaser-game/src/gameEntry.ts +7 -1
- package/templates/phaser-game/src/platform/buildGateways/microsoftStore.ts +37 -0
- package/templates/phaser-game/src/platform/runtimeDetector.ts +1 -0
- package/templates/phaser-game/src/runtime/renderEntryFailure.ts +24 -0
- package/templates/phaser-game/vite.shared.ts +6 -2
|
@@ -19,31 +19,30 @@ export function assertProductionTargetReadiness(input) {
|
|
|
19
19
|
if (ownerPathKey === undefined && input.targetPolicy === undefined) {
|
|
20
20
|
throw new Error(`Production target ${input.target} requires a target policy.`);
|
|
21
21
|
}
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
||
|
|
22
|
+
const targetConfig = readTargets(input.targetsFile)[input.target];
|
|
23
|
+
if (!isRecord(targetConfig)) {
|
|
24
|
+
throw new Error(`Missing target configuration for ${input.target}.`);
|
|
25
|
+
}
|
|
26
|
+
const targetPolicy = targetConfig.authoritativeGameServices === false
|
|
27
|
+
? disableMonetization(input.targetPolicy)
|
|
28
|
+
: input.targetPolicy;
|
|
29
|
+
const requiresVerse8RewardAuthority = targetPolicy?.runtime === 'verse8-web'
|
|
30
|
+
&& (targetPolicy.features.rewardedAds
|
|
31
|
+
|| targetPolicy.monetization.rewardedAds);
|
|
32
|
+
const requiresWebMonetizationAuthority = (targetPolicy?.runtime === 'web'
|
|
33
|
+
|| targetPolicy?.runtime === 'microsoft-store-pwa')
|
|
34
|
+
&& (targetPolicy.features.iap
|
|
35
|
+
|| targetPolicy.features.rewardedAds
|
|
36
|
+
|| targetPolicy.features.interstitialAds
|
|
37
|
+
|| targetPolicy.monetization.iap
|
|
38
|
+
|| targetPolicy.monetization.rewardedAds
|
|
39
|
+
|| targetPolicy.monetization.interstitialAds);
|
|
32
40
|
const requiresMonetizationAuthority = requiresVerse8RewardAuthority
|
|
33
41
|
|| requiresWebMonetizationAuthority;
|
|
34
42
|
if (ownerPathKey === undefined && !requiresMonetizationAuthority) {
|
|
35
43
|
return;
|
|
36
44
|
}
|
|
37
|
-
const targetConfig = readTargets(input.targetsFile)[input.target];
|
|
38
|
-
if (!isRecord(targetConfig)) {
|
|
39
|
-
throw new Error(`Missing target configuration for ${input.target}.`);
|
|
40
|
-
}
|
|
41
45
|
if (ownerPathKey === undefined) {
|
|
42
|
-
if (requiresMonetizationAuthority
|
|
43
|
-
&& targetConfig.authoritativeGameServices === false) {
|
|
44
|
-
throw new Error(`Production target ${input.target} cannot enable monetization without `
|
|
45
|
-
+ 'authoritative game services.');
|
|
46
|
-
}
|
|
47
46
|
if (requiresMonetizationAuthority || targetConfig.authoritativeGameServices !== false) {
|
|
48
47
|
assertAuthoritativeGameServicesUrl(input.gameServicesUrl, input.target);
|
|
49
48
|
}
|
|
@@ -82,6 +81,26 @@ export function assertProductionTargetReadiness(input) {
|
|
|
82
81
|
assertAuthoritativeGameServicesUrl(input.gameServicesUrl, input.target);
|
|
83
82
|
}
|
|
84
83
|
}
|
|
84
|
+
function disableMonetization(policy) {
|
|
85
|
+
if (policy === undefined) {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
...policy,
|
|
90
|
+
features: {
|
|
91
|
+
...policy.features,
|
|
92
|
+
iap: false,
|
|
93
|
+
rewardedAds: false,
|
|
94
|
+
interstitialAds: false,
|
|
95
|
+
},
|
|
96
|
+
monetization: {
|
|
97
|
+
...policy.monetization,
|
|
98
|
+
iap: false,
|
|
99
|
+
rewardedAds: false,
|
|
100
|
+
interstitialAds: false,
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
85
104
|
function readTargets(targetsFile) {
|
|
86
105
|
let parsed;
|
|
87
106
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mpgd/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.1",
|
|
4
4
|
"description": "Gunshi CLI for mpgd-kit starter generation and target workflow orchestration.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -43,6 +43,8 @@
|
|
|
43
43
|
"@gunshi/plugin-completion": "^0.35.1",
|
|
44
44
|
"@gunshi/plugin-i18n": "^0.35.1",
|
|
45
45
|
"@gunshi/resources": "^0.35.1",
|
|
46
|
+
"entities": "^8.0.0",
|
|
47
|
+
"esbuild": "^0.28.1",
|
|
46
48
|
"gunshi": "^0.35.1",
|
|
47
49
|
"undici": "^7.28.0"
|
|
48
50
|
},
|
|
@@ -50,19 +52,19 @@
|
|
|
50
52
|
"@types/node": "^24.0.0",
|
|
51
53
|
"ttsc": "0.18.4",
|
|
52
54
|
"typescript": "7.0.2",
|
|
53
|
-
"@mpgd/adapter-
|
|
54
|
-
"@mpgd/adapter-
|
|
55
|
-
"@mpgd/adapter-
|
|
56
|
-
"@mpgd/adapter-
|
|
57
|
-
"@mpgd/adapter-verse8": "0.
|
|
58
|
-
"@mpgd/
|
|
59
|
-
"@mpgd/bridge": "0.
|
|
60
|
-
"@mpgd/game-services": "0.
|
|
61
|
-
"@mpgd/i18n": "0.5.
|
|
62
|
-
"@mpgd/catalog": "0.5.2",
|
|
55
|
+
"@mpgd/adapter-capacitor": "0.4.6",
|
|
56
|
+
"@mpgd/adapter-ait": "0.9.0",
|
|
57
|
+
"@mpgd/adapter-devvit": "0.9.0",
|
|
58
|
+
"@mpgd/adapter-browser": "0.6.0",
|
|
59
|
+
"@mpgd/adapter-verse8": "0.3.0",
|
|
60
|
+
"@mpgd/catalog": "0.6.0",
|
|
61
|
+
"@mpgd/bridge": "0.8.0",
|
|
62
|
+
"@mpgd/game-services": "0.12.0",
|
|
63
|
+
"@mpgd/i18n": "0.5.5",
|
|
63
64
|
"@mpgd/phaser-assets": "0.4.1",
|
|
64
|
-
"@mpgd/
|
|
65
|
-
"@mpgd/
|
|
65
|
+
"@mpgd/analytics": "0.3.9",
|
|
66
|
+
"@mpgd/platform": "0.9.0",
|
|
67
|
+
"@mpgd/target-config": "0.12.0"
|
|
66
68
|
},
|
|
67
69
|
"main": "./dist/index.js",
|
|
68
70
|
"types": "./dist/index.d.ts",
|
|
@@ -49,21 +49,36 @@ pnpm exec mpgd target doctor \
|
|
|
49
49
|
--targets __RECOMMENDED_MATRIX_TARGETS__
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
`mpgd.targets.json` contains `${MPGD_KIT_PATH}` tokens. The CLI resolves them
|
|
53
|
+
into `.mpgd.targets.generated.json` before calling the kit's existing target
|
|
54
|
+
build and smoke scripts.
|
|
55
|
+
|
|
56
|
+
### Offline test-play copy
|
|
57
|
+
|
|
58
|
+
When a tester needs to open one local file without a server, package the
|
|
59
|
+
existing browser preview:
|
|
54
60
|
|
|
55
61
|
```sh
|
|
56
|
-
pnpm
|
|
57
|
-
--targets-file
|
|
58
|
-
--kit-path
|
|
59
|
-
|
|
60
|
-
--profile staging \
|
|
61
|
-
--ait-variant wrapper
|
|
62
|
+
pnpm exec mpgd target build web-preview staging \
|
|
63
|
+
--targets-file ./mpgd.targets.json \
|
|
64
|
+
--kit-path "${MPGD_KIT_PATH:-../mpgd-kit}"
|
|
65
|
+
pnpm exec mpgd game offline-playtest .
|
|
62
66
|
```
|
|
63
67
|
|
|
64
|
-
`
|
|
65
|
-
|
|
66
|
-
|
|
68
|
+
Open `artifacts/offline-playtest/index.html` directly. This is a
|
|
69
|
+
network-blocked, test-play-only copy—not a target in `mpgd.targets.json`, PWA,
|
|
70
|
+
deployment artifact, release-manifest entry, or store-submission package.
|
|
71
|
+
Custom output directories must remain below this game's `artifacts` directory.
|
|
72
|
+
`README.txt` and `offline-playtest.json` preserve that boundary and record the
|
|
73
|
+
result hash. Server-backed login, purchases, ads, rewards, leaderboards, and
|
|
74
|
+
cloud saves do not work in this copy. Workers, service workers, WebAssembly
|
|
75
|
+
streaming, dynamic imports, import maps, HTML base elements, CSS `@import`,
|
|
76
|
+
runtime-computed asset URLs, retained inline-module imports, script-driven
|
|
77
|
+
navigation, and browser-dependent `file://` storage are outside this helper's
|
|
78
|
+
contract. Meta-refresh navigation is removed from the copy. glTF files must
|
|
79
|
+
embed their dependencies as data URIs; use GLB for models with external buffers
|
|
80
|
+
or textures. A successful package does not prove that every game path works
|
|
81
|
+
offline; keep unavailable capability fallbacks in the game.
|
|
67
82
|
|
|
68
83
|
The starter owns its Apps in Toss wrapper under `apps/target-ait`. With
|
|
69
84
|
`authoritativeGameServices: false`, production keeps native identity, storage,
|
|
@@ -96,7 +111,9 @@ Optional targets can add their own config, scripts, runtime wiring, and release
|
|
|
96
111
|
skill. To enable Microsoft Store after creation, run
|
|
97
112
|
`pnpm exec mpgd target init microsoft-store --game . --kit-path ../mpgd-kit`.
|
|
98
113
|
The initializer is idempotent and stops before writing when it finds conflicting
|
|
99
|
-
game-owned values.
|
|
114
|
+
game-owned values. It requires this generated starter's existing
|
|
115
|
+
`src/platform/runtimeDetector.ts` and `vite.shared.ts` files; custom layouts must
|
|
116
|
+
add equivalent runtime and Vite gateway routing manually.
|
|
100
117
|
|
|
101
118
|
## Acceptance Handoff
|
|
102
119
|
|
|
@@ -250,29 +267,19 @@ pnpm build:ait
|
|
|
250
267
|
pnpm smoke:ait
|
|
251
268
|
```
|
|
252
269
|
|
|
253
|
-
The game-owned wrapper
|
|
254
|
-
|
|
270
|
+
The game-owned wrapper provides an explicit local SDK mock for browser layout
|
|
271
|
+
and non-native flow checks:
|
|
255
272
|
|
|
256
273
|
```sh
|
|
257
274
|
pnpm ait:wrapper:dev
|
|
258
275
|
pnpm ait:wrapper:dev:plain
|
|
259
|
-
pnpm ait:wrapper:dev:phone
|
|
260
|
-
pnpm ait:devtools:mcp
|
|
261
|
-
pnpm ait:devtools:mcp:mobile
|
|
262
276
|
```
|
|
263
277
|
|
|
264
|
-
`ait:wrapper:dev`
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
for phone sandbox checks, and `ait:devtools:mcp:mobile` starts the community MCP
|
|
270
|
-
bridge in the phone tunnel mode after the wrapper prints the tunnel URLs. Use
|
|
271
|
-
`ait:devtools:mcp` for the default local or relay debugging mode. The kit
|
|
272
|
-
intentionally leaves
|
|
273
|
-
`cloudflared` postinstall disabled; the first phone tunnel session downloads the
|
|
274
|
-
binary lazily through the community devtools package instead of during ordinary
|
|
275
|
-
workspace install.
|
|
278
|
+
`ait:wrapper:dev` loads the last game bundle copied by `pnpm build:ait` from the
|
|
279
|
+
wrapper's `public/game` directory, so run `pnpm build:ait` again after game
|
|
280
|
+
changes before opening it. The local mock deliberately keeps ads, promotions,
|
|
281
|
+
and IAP unavailable; test those flows from the Apps in Toss console QR sandbox
|
|
282
|
+
or a real device after uploading the `.ait` bundle.
|
|
276
283
|
|
|
277
284
|
The starter includes `@ait-co/console-cli`, the Apps in Toss community console
|
|
278
285
|
CLI, as a dev dependency for project-local console automation:
|
|
@@ -301,6 +308,13 @@ Apps in Toss console, while the file owns `appName`, `brand.primaryColor`,
|
|
|
301
308
|
permissions, `webView`, and `webBundleDir`. Do not restore the removed SDK 2
|
|
302
309
|
`granite.config.ts` fields.
|
|
303
310
|
|
|
311
|
+
Apps in Toss navigation chrome is target-owned. Configure the `ait` target's
|
|
312
|
+
`navigationBar` in `mpgd.targets.json`; `mpgd target build ait` validates the
|
|
313
|
+
supported appearance options and forwards them to `apps-in-toss.config.ts` at
|
|
314
|
+
build time. The Phaser starter uses a transparent dark navigation bar without
|
|
315
|
+
back or home chrome so game content can continue underneath it. Keep
|
|
316
|
+
the game's own safe-area padding clear of Toss controls.
|
|
317
|
+
|
|
304
318
|
Before the first SDK 3 release, allow both of these exact origins on every API
|
|
305
319
|
called by the mini-app:
|
|
306
320
|
|
|
@@ -334,7 +348,7 @@ HTTPS game-services backend that verifies purchase or reward evidence.
|
|
|
334
348
|
|
|
335
349
|
- Browser preview is fully game-owned and writes to `artifacts/web-preview`.
|
|
336
350
|
<!-- mpgd:microsoft-store:start -->
|
|
337
|
-
- Microsoft Store is a game-owned PWA target
|
|
351
|
+
- Microsoft Store is a game-owned PWA target with a dedicated Digital Goods adapter and
|
|
338
352
|
writes to `artifacts/microsoft-store` with `manifest.webmanifest` for
|
|
339
353
|
PWABuilder packaging. Replace the starter icon and manifest metadata before
|
|
340
354
|
Store submission. Production builds also emit `pwa-release.json` and an
|
|
@@ -345,6 +359,16 @@ HTTPS game-services backend that verifies purchase or reward evidence.
|
|
|
345
359
|
app; do not force activation with `skipWaiting()`. Keep the web manifest `id`
|
|
346
360
|
game-specific because the cache namespace uses it to isolate apps sharing an
|
|
347
361
|
origin.
|
|
362
|
+
The generated target starts with `authoritativeGameServices: false` while
|
|
363
|
+
`mpgd.microsoft-store.json` uses `commerce.mode: "disabled"`, so its effective
|
|
364
|
+
target contains no enabled IAP products. Switch both settings to their enabled
|
|
365
|
+
values only after the game supplies consumable
|
|
366
|
+
`InAppOfferToken` mappings plus an authoritative HTTPS backend. The backend
|
|
367
|
+
must resolve the renewable User Store ID and a separate stable account-link
|
|
368
|
+
ID from trusted player identity, query Collections v9, record the grant
|
|
369
|
+
idempotently, and consume through Collections v8. Never derive the account-link
|
|
370
|
+
ID from the User Store ID or treat the browser `purchaseToken` as a unique
|
|
371
|
+
transaction ID.
|
|
348
372
|
Before Partner Center submission, replace every placeholder in
|
|
349
373
|
`mpgd.microsoft-store.json`, add at least one real PNG screenshot per listing
|
|
350
374
|
locale (maximum 50 MB and at least 1366 x 768 in either orientation), and run
|
|
@@ -405,9 +429,9 @@ HTTPS game-services backend that verifies purchase or reward evidence.
|
|
|
405
429
|
bridge fails closed on identity, provider, serialization, and quota errors;
|
|
406
430
|
it never switches progress to browser `localStorage`.
|
|
407
431
|
- Apps in Toss is game-owned in `apps/target-ait`. The wrapper keeps the app
|
|
408
|
-
name, console state, review metadata, and
|
|
409
|
-
|
|
410
|
-
|
|
432
|
+
name, console state, review metadata, and local SDK mock configuration in the
|
|
433
|
+
game while reusing `@mpgd/adapter-ait` for the production bridge and
|
|
434
|
+
game-bundle loader.
|
|
411
435
|
- Android and iOS currently use the kit reference Capacitor shell at
|
|
412
436
|
`${MPGD_KIT_PATH}/apps/mobile-capacitor`. That is useful for local artifact
|
|
413
437
|
checks, and final artifacts are copied back to this game's `release-output/`
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { defineConfig, type AppsInTossConfig } from '@apps-in-toss/web-framework/config';
|
|
2
2
|
|
|
3
|
+
const navigationBar = readNavigationBar(process.env.MPGD_AIT_NAVIGATION_BAR);
|
|
4
|
+
|
|
3
5
|
const config: AppsInTossConfig = {
|
|
4
6
|
appName: readEnvString(process.env.MPGD_AIT_APP_NAME) ?? '__GAME_NAME__',
|
|
5
7
|
brand: {
|
|
6
8
|
primaryColor: readEnvString(process.env.MPGD_AIT_PRIMARY_COLOR) ?? '#101820',
|
|
7
9
|
},
|
|
8
10
|
permissions: [],
|
|
11
|
+
...(navigationBar === undefined ? {} : { navigationBar }),
|
|
9
12
|
webView: {
|
|
10
13
|
bounces: false,
|
|
11
14
|
pullToRefreshEnabled: false,
|
|
@@ -24,3 +27,12 @@ function readEnvString(input: string | undefined): string | undefined {
|
|
|
24
27
|
const trimmed = input?.trim();
|
|
25
28
|
return trimmed === undefined || trimmed.length === 0 ? undefined : trimmed;
|
|
26
29
|
}
|
|
30
|
+
|
|
31
|
+
function readNavigationBar(
|
|
32
|
+
input: string | undefined,
|
|
33
|
+
): AppsInTossConfig['navigationBar'] | undefined {
|
|
34
|
+
const encoded = readEnvString(input);
|
|
35
|
+
return encoded === undefined
|
|
36
|
+
? undefined
|
|
37
|
+
: JSON.parse(encoded) as AppsInTossConfig['navigationBar'];
|
|
38
|
+
}
|
|
@@ -4,30 +4,27 @@
|
|
|
4
4
|
"version": "0.0.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"dev": "vite --host 0.0.0.0",
|
|
8
|
-
"dev:plain": "
|
|
9
|
-
"dev:phone": "AIT_TUNNEL=1 AIT_TUNNEL_CDP=1 vite --host 0.0.0.0",
|
|
7
|
+
"dev": "MPGD_AIT_LOCAL_MOCK=1 VITE_MPGD_AIT_MOCK_IDENTITY=1 vite --host 0.0.0.0",
|
|
8
|
+
"dev:plain": "pnpm dev",
|
|
10
9
|
"build": "vite build && ait build",
|
|
11
10
|
"ait:build": "ait build",
|
|
12
11
|
"ait:deploy": "ait deploy --api-key \"$AIT_API_KEY\"",
|
|
13
|
-
"devtools:mcp": "pnpm exec devtools-mcp",
|
|
14
|
-
"devtools:mcp:mobile": "pnpm exec devtools-mcp --target=mobile",
|
|
15
12
|
"check": "ttsc --noEmit -p tsconfig.json",
|
|
16
13
|
"lint": "ttsc --noEmit -p tsconfig.json",
|
|
17
14
|
"format": "ttsc format",
|
|
18
15
|
"fix": "ttsc fix"
|
|
19
16
|
},
|
|
20
17
|
"dependencies": {
|
|
21
|
-
"@apps-in-toss/web-framework": "3.0.
|
|
18
|
+
"@apps-in-toss/web-framework": "3.0.2",
|
|
22
19
|
"@mpgd/adapter-ait": "__MPGD_DEPENDENCY_VERSION_ADAPTER_AIT__"
|
|
23
20
|
},
|
|
24
21
|
"devDependencies": {
|
|
25
22
|
"@ait-co/devtools": "^0.1.140",
|
|
26
|
-
"@apps-in-toss/cli": "3.0.
|
|
23
|
+
"@apps-in-toss/cli": "3.0.2",
|
|
27
24
|
"@ttsc/unplugin": "0.18.4",
|
|
28
25
|
"@types/node": "^24.0.0",
|
|
29
26
|
"ttsc": "0.18.4",
|
|
30
27
|
"typescript": "7.0.2",
|
|
31
|
-
"vite": "
|
|
28
|
+
"vite": "8.1.3"
|
|
32
29
|
}
|
|
33
30
|
}
|
|
@@ -1,22 +1,16 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
|
|
3
|
-
import aitDevtools from '@ait-co/devtools/unplugin';
|
|
4
3
|
import { extractAitAdBridgeConfig } from '@mpgd/adapter-ait/ad-config';
|
|
5
4
|
import ttsc from '@ttsc/unplugin/vite';
|
|
6
5
|
import { defineConfig } from 'vite';
|
|
7
6
|
|
|
8
|
-
const isTruthyEnv = (value: string | undefined): boolean =>
|
|
9
|
-
value !== undefined && value !== '' && value !== '0' && value.toLowerCase() !== 'false';
|
|
10
|
-
const aitDevtoolsTunnel = isTruthyEnv(process.env.AIT_TUNNEL)
|
|
11
|
-
? { cdp: isTruthyEnv(process.env.AIT_TUNNEL_CDP) }
|
|
12
|
-
: false;
|
|
13
7
|
const aitAppName = process.env.MPGD_AIT_APP_NAME?.trim() || '__GAME_NAME__';
|
|
14
8
|
const aitAdConfig = readAitAdConfig(process.env.MPGD_AD_PLACEMENTS_FILE);
|
|
15
9
|
|
|
16
10
|
export default defineConfig(({ command, isPreview }) => {
|
|
17
|
-
const
|
|
11
|
+
const enableLocalAitMock = command === 'serve'
|
|
18
12
|
&& !isPreview
|
|
19
|
-
&& process.env.
|
|
13
|
+
&& process.env.MPGD_AIT_LOCAL_MOCK !== '0';
|
|
20
14
|
|
|
21
15
|
return {
|
|
22
16
|
define: {
|
|
@@ -24,21 +18,18 @@ export default defineConfig(({ command, isPreview }) => {
|
|
|
24
18
|
__MPGD_AIT_AD_GROUP_IDS__: JSON.stringify(aitAdConfig.adGroupIds),
|
|
25
19
|
__MPGD_AIT_AD_PLACEMENT_TYPES__: JSON.stringify(aitAdConfig.adPlacementTypes),
|
|
26
20
|
},
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
...(
|
|
21
|
+
// Browser-only local development must not pretend that native rewards or
|
|
22
|
+
// purchases are available. Production and console builds use the real SDK.
|
|
23
|
+
...(enableLocalAitMock
|
|
30
24
|
? {
|
|
31
25
|
resolve: {
|
|
32
26
|
alias: {
|
|
33
|
-
'@apps-in-toss/web-framework': '@ait-
|
|
27
|
+
'@apps-in-toss/web-framework': '@mpgd/adapter-ait/local-mock',
|
|
34
28
|
},
|
|
35
29
|
},
|
|
36
30
|
}
|
|
37
31
|
: {}),
|
|
38
32
|
plugins: [
|
|
39
|
-
...(enableAitDevtools
|
|
40
|
-
? [aitDevtools.vite({ mcp: true, tunnel: aitDevtoolsTunnel })]
|
|
41
|
-
: []),
|
|
42
33
|
ttsc({ project: 'tsconfig.json', plugins: false }),
|
|
43
34
|
],
|
|
44
35
|
build: {
|
|
@@ -52,7 +52,8 @@ leaderboard. Devvit ranking should be owned by a server completion handler that
|
|
|
52
52
|
validates the game-specific attempt, records it through the verified leaderboard
|
|
53
53
|
provider from `@mpgd/adapter-devvit/server`, and exposes only an authenticated
|
|
54
54
|
game-scoped snapshot route. Do not enable `nativeLeaderboard` or trust a raw
|
|
55
|
-
client score submission for that flow.
|
|
55
|
+
client score submission for that flow. Keep `remoteLeaderboard` disabled too;
|
|
56
|
+
the verified completion route is not the generic `PlatformGateway` leaderboard.
|
|
56
57
|
|
|
57
58
|
`devvit playtest` runs the official unified Vite build in watch mode, so a
|
|
58
59
|
separate client/server watcher or staging prebuild is not required.
|
|
@@ -141,6 +141,7 @@ async function handleBridgeRequest(input: BridgeRequest): Promise<BridgeResponse
|
|
|
141
141
|
rewardedAds: false,
|
|
142
142
|
interstitialAds: false,
|
|
143
143
|
nativeLeaderboard: false,
|
|
144
|
+
remoteLeaderboard: false,
|
|
144
145
|
achievements: false,
|
|
145
146
|
cloudSave: true,
|
|
146
147
|
socialShare: false,
|
|
@@ -74,6 +74,17 @@ Use the browser adapter for local and hosted web behavior. Verify installed-PWA
|
|
|
74
74
|
updates without forcing service-worker activation while old windows remain
|
|
75
75
|
open.
|
|
76
76
|
|
|
77
|
+
For direct-file test play, build `web-preview` and run
|
|
78
|
+
`pnpm exec mpgd game offline-playtest .`. Open
|
|
79
|
+
`artifacts/offline-playtest/index.html`. This output is deliberately a
|
|
80
|
+
network-blocked test copy, not another release target, deployable PWA,
|
|
81
|
+
release-manifest entry, or store package. Its README and JSON evidence describe
|
|
82
|
+
server-feature, Worker, dynamic-import, import-map, HTML-base, CSS `@import`,
|
|
83
|
+
dynamic-asset, inline-module-import, script-navigation, and `file://` storage
|
|
84
|
+
limitations. Meta-refresh navigation is removed. Keep server calls behind
|
|
85
|
+
platform or game-service helpers and expose a disabled or local fallback when
|
|
86
|
+
those capabilities are unavailable.
|
|
87
|
+
|
|
77
88
|
### Microsoft Store
|
|
78
89
|
|
|
79
90
|
This target is optional. Enable it in an existing generated game with:
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"microsoft-store": {
|
|
11
11
|
"kind": "web",
|
|
12
12
|
"gameApp": ".",
|
|
13
|
-
"adapter": "
|
|
13
|
+
"adapter": "microsoft-store",
|
|
14
|
+
"authoritativeGameServices": false,
|
|
14
15
|
"icon": { "profile": "microsoft-pwa" },
|
|
15
16
|
"output": "artifacts/microsoft-store"
|
|
16
17
|
},
|
|
@@ -51,6 +52,12 @@
|
|
|
51
52
|
"primaryColor": "#101820",
|
|
52
53
|
"sdkMajor": 3
|
|
53
54
|
},
|
|
55
|
+
"navigationBar": {
|
|
56
|
+
"withBackButton": false,
|
|
57
|
+
"withHomeButton": false,
|
|
58
|
+
"transparentBackground": true,
|
|
59
|
+
"theme": "dark"
|
|
60
|
+
},
|
|
54
61
|
"icon": { "profile": "ait" },
|
|
55
62
|
"webDir": "apps/target-ait/public/game",
|
|
56
63
|
"artifact": ".ait"
|
|
@@ -19,9 +19,6 @@
|
|
|
19
19
|
"smoke:ait": "pnpm exec mpgd target smoke ait --targets-file ./mpgd.targets.json --kit-path ${MPGD_KIT_PATH:-__DEFAULT_KIT_PATH__}",
|
|
20
20
|
"ait:wrapper:dev": "pnpm --dir apps/target-ait run dev",
|
|
21
21
|
"ait:wrapper:dev:plain": "pnpm --dir apps/target-ait run dev:plain",
|
|
22
|
-
"ait:wrapper:dev:phone": "pnpm --dir apps/target-ait run dev:phone",
|
|
23
|
-
"ait:devtools:mcp": "pnpm --dir apps/target-ait run devtools:mcp",
|
|
24
|
-
"ait:devtools:mcp:mobile": "pnpm --dir apps/target-ait run devtools:mcp:mobile",
|
|
25
22
|
"ait:console:login": "aitcc login",
|
|
26
23
|
"ait:console:whoami": "aitcc whoami",
|
|
27
24
|
"ait:console:init": "aitcc app init",
|
|
@@ -58,7 +55,7 @@
|
|
|
58
55
|
},
|
|
59
56
|
"dependencies": {
|
|
60
57
|
"@ait-co/polyfill": "^0.1.17",
|
|
61
|
-
"@apps-in-toss/web-framework": "3.0.
|
|
58
|
+
"@apps-in-toss/web-framework": "3.0.2",
|
|
62
59
|
"@mpgd/adapter-ait": "__MPGD_DEPENDENCY_VERSION_ADAPTER_AIT__",
|
|
63
60
|
"@mpgd/adapter-browser": "__MPGD_DEPENDENCY_VERSION_ADAPTER_BROWSER__",
|
|
64
61
|
"@mpgd/adapter-capacitor": "__MPGD_DEPENDENCY_VERSION_ADAPTER_CAPACITOR__",
|
|
@@ -82,7 +79,7 @@
|
|
|
82
79
|
"@types/node": "^24.0.0",
|
|
83
80
|
"ttsc": "0.18.4",
|
|
84
81
|
"typescript": "7.0.2",
|
|
85
|
-
"vite": "
|
|
82
|
+
"vite": "8.1.3",
|
|
86
83
|
"wrangler": "^4.107.0"
|
|
87
84
|
}
|
|
88
85
|
}
|
|
@@ -5,10 +5,11 @@ __PNPM_WORKSPACE_KIT_PACKAGES__
|
|
|
5
5
|
allowBuilds:
|
|
6
6
|
'@sentry/cli': true
|
|
7
7
|
'@swc/core': true
|
|
8
|
-
# @ait-co/devtools lazily installs cloudflared when the phone tunnel starts;
|
|
9
|
-
# keep default workspace installs free of external binary downloads.
|
|
10
8
|
cloudflared: false
|
|
11
9
|
esbuild: true
|
|
12
10
|
protobufjs: true
|
|
13
11
|
sharp: true
|
|
14
12
|
workerd: true
|
|
13
|
+
peerDependencyRules:
|
|
14
|
+
allowedVersions:
|
|
15
|
+
'@ait-co/devtools>@apps-in-toss/web-framework': '3.0.2'
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { renderEntryFailure } from './runtime/renderEntryFailure';
|
|
2
|
+
|
|
3
|
+
try {
|
|
4
|
+
if (__APP_TARGET__ === 'reddit') {
|
|
5
|
+
await import('./platform/devvitEntrypoint');
|
|
6
|
+
} else {
|
|
7
|
+
await import('./main');
|
|
8
|
+
}
|
|
9
|
+
} catch (error) {
|
|
10
|
+
renderEntryFailure(error);
|
|
5
11
|
}
|
|
6
12
|
|
|
7
13
|
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { createBrowserPlatformGateway } from '@mpgd/adapter-browser';
|
|
2
|
+
import {
|
|
3
|
+
createMicrosoftStoreCommerceAdapter,
|
|
4
|
+
withMicrosoftStoreCommerceAdapter,
|
|
5
|
+
} from '@mpgd/adapter-browser/microsoft-store';
|
|
6
|
+
import type { PlatformGateway } from '@mpgd/platform';
|
|
7
|
+
|
|
8
|
+
import type { RuntimeConfig } from '../runtimeDetector';
|
|
9
|
+
|
|
10
|
+
const browser = createBrowserPlatformGateway();
|
|
11
|
+
const base: PlatformGateway = { ...browser, target: 'microsoft-store' };
|
|
12
|
+
const commerce = createMicrosoftStoreCommerceAdapter({
|
|
13
|
+
getRecoveryScope: () => 'configuration-required',
|
|
14
|
+
products: [],
|
|
15
|
+
authority: {
|
|
16
|
+
async getAvailability() {
|
|
17
|
+
return 'configuration-required';
|
|
18
|
+
},
|
|
19
|
+
async claimRecoveryOwnership() {
|
|
20
|
+
return { status: 'denied' } as const;
|
|
21
|
+
},
|
|
22
|
+
async hasRecoveryOwnership() {
|
|
23
|
+
return { status: 'denied' } as const;
|
|
24
|
+
},
|
|
25
|
+
async verifyAndGrant() {
|
|
26
|
+
return { status: 'failed' };
|
|
27
|
+
},
|
|
28
|
+
async getEntitlements() {
|
|
29
|
+
return [];
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/** Configure Store products, authenticated recovery scope, and authority before checkout. */
|
|
35
|
+
export async function createBuildGateway(_runtime: RuntimeConfig): Promise<PlatformGateway> {
|
|
36
|
+
return withMicrosoftStoreCommerceAdapter(base, commerce);
|
|
37
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function renderEntryFailure(error: unknown): void {
|
|
2
|
+
console.error('[entry] Failed to load the game module.', error);
|
|
3
|
+
|
|
4
|
+
const root = document.querySelector<HTMLElement>('#game');
|
|
5
|
+
|
|
6
|
+
if (root === null) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
root.replaceChildren();
|
|
11
|
+
const panel = document.createElement('div');
|
|
12
|
+
panel.setAttribute('role', 'alert');
|
|
13
|
+
panel.textContent = 'Failed to load the game. Please refresh and try again.';
|
|
14
|
+
panel.style.boxSizing = 'border-box';
|
|
15
|
+
panel.style.width = 'min(720px, calc(100% - 32px))';
|
|
16
|
+
panel.style.margin = '20dvh auto 0';
|
|
17
|
+
panel.style.padding = '20px';
|
|
18
|
+
panel.style.border = '1px solid #dc2626';
|
|
19
|
+
panel.style.borderRadius = '8px';
|
|
20
|
+
panel.style.background = '#1f1111';
|
|
21
|
+
panel.style.color = '#fecaca';
|
|
22
|
+
panel.style.font = '16px/1.5 Arial, sans-serif';
|
|
23
|
+
root.append(panel);
|
|
24
|
+
}
|
|
@@ -2,7 +2,7 @@ import { existsSync, readFileSync } from 'node:fs';
|
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
3
|
|
|
4
4
|
import ttsc from '@ttsc/unplugin/vite';
|
|
5
|
-
import type { UserConfig } from 'vite';
|
|
5
|
+
import type { PluginOption, UserConfig } from 'vite';
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
assertRuntimeTargetConfigMatrix,
|
|
@@ -51,10 +51,12 @@ export function createGameViteSharedConfig(
|
|
|
51
51
|
return {
|
|
52
52
|
base: './',
|
|
53
53
|
plugins: [
|
|
54
|
+
// The unplugin runtime is Vite-compatible, but its recursive generic
|
|
55
|
+
// plugin type can exceed TypeScript's comparison depth in a monorepo.
|
|
54
56
|
ttsc({
|
|
55
57
|
project: input.project,
|
|
56
58
|
plugins: false,
|
|
57
|
-
}),
|
|
59
|
+
}) as unknown as PluginOption,
|
|
58
60
|
],
|
|
59
61
|
resolve: {
|
|
60
62
|
alias: {
|
|
@@ -124,6 +126,8 @@ export function resolveBuildGatewayModule(input: {
|
|
|
124
126
|
: 'src/platform/buildGateways/reddit.ts';
|
|
125
127
|
case 'verse8':
|
|
126
128
|
return 'src/platform/buildGateways/verse8.ts';
|
|
129
|
+
case 'microsoft-store':
|
|
130
|
+
return 'src/platform/buildGateways/microsoftStore.ts';
|
|
127
131
|
default:
|
|
128
132
|
return 'src/platform/buildGateways/browser.ts';
|
|
129
133
|
}
|