@mpgd/cli 0.20.1 → 0.22.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.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.d.ts +4 -0
- package/dist/production-target-readiness.js +40 -12
- package/package.json +15 -13
- package/templates/phaser-game/README.md +62 -22
- 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.runtime-target-config.ts +2 -0
- package/templates/phaser-game/vite.shared.ts +6 -2
|
@@ -9,10 +9,14 @@ export interface ProductionTargetReadinessInput {
|
|
|
9
9
|
export interface ProductionTargetPolicy {
|
|
10
10
|
readonly runtime: string;
|
|
11
11
|
readonly features: {
|
|
12
|
+
readonly iap: boolean;
|
|
12
13
|
readonly rewardedAds: boolean;
|
|
14
|
+
readonly interstitialAds: boolean;
|
|
13
15
|
};
|
|
14
16
|
readonly monetization: {
|
|
17
|
+
readonly iap: boolean;
|
|
15
18
|
readonly rewardedAds: boolean;
|
|
19
|
+
readonly interstitialAds: boolean;
|
|
16
20
|
};
|
|
17
21
|
}
|
|
18
22
|
export declare function assertProductionTargetReadiness(input: ProductionTargetReadinessInput): void;
|
|
@@ -19,23 +19,31 @@ 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 requiresVerse8RewardAuthority = input.targetPolicy?.runtime === 'verse8-web'
|
|
23
|
-
&& (input.targetPolicy.features.rewardedAds
|
|
24
|
-
|| input.targetPolicy.monetization.rewardedAds);
|
|
25
|
-
if (ownerPathKey === undefined && !requiresVerse8RewardAuthority) {
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
22
|
const targetConfig = readTargets(input.targetsFile)[input.target];
|
|
29
23
|
if (!isRecord(targetConfig)) {
|
|
30
24
|
throw new Error(`Missing target configuration for ${input.target}.`);
|
|
31
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);
|
|
40
|
+
const requiresMonetizationAuthority = requiresVerse8RewardAuthority
|
|
41
|
+
|| requiresWebMonetizationAuthority;
|
|
42
|
+
if (ownerPathKey === undefined && !requiresMonetizationAuthority) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
32
45
|
if (ownerPathKey === undefined) {
|
|
33
|
-
if (
|
|
34
|
-
&& targetConfig.authoritativeGameServices === false) {
|
|
35
|
-
throw new Error(`Production target ${input.target} cannot enable rewarded ads without `
|
|
36
|
-
+ 'authoritative game services.');
|
|
37
|
-
}
|
|
38
|
-
if (requiresVerse8RewardAuthority || targetConfig.authoritativeGameServices !== false) {
|
|
46
|
+
if (requiresMonetizationAuthority || targetConfig.authoritativeGameServices !== false) {
|
|
39
47
|
assertAuthoritativeGameServicesUrl(input.gameServicesUrl, input.target);
|
|
40
48
|
}
|
|
41
49
|
return;
|
|
@@ -73,6 +81,26 @@ export function assertProductionTargetReadiness(input) {
|
|
|
73
81
|
assertAuthoritativeGameServicesUrl(input.gameServicesUrl, input.target);
|
|
74
82
|
}
|
|
75
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
|
+
}
|
|
76
104
|
function readTargets(targetsFile) {
|
|
77
105
|
let parsed;
|
|
78
106
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mpgd/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
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-ait": "0.
|
|
54
|
-
"@mpgd/adapter-
|
|
55
|
-
"@mpgd/adapter-
|
|
56
|
-
"@mpgd/adapter-
|
|
57
|
-
"@mpgd/
|
|
58
|
-
"@mpgd/
|
|
59
|
-
"@mpgd/
|
|
60
|
-
"@mpgd/
|
|
61
|
-
"@mpgd/
|
|
62
|
-
"@mpgd/i18n": "0.5.
|
|
55
|
+
"@mpgd/adapter-ait": "0.9.0",
|
|
56
|
+
"@mpgd/adapter-browser": "0.6.0",
|
|
57
|
+
"@mpgd/adapter-capacitor": "0.4.6",
|
|
58
|
+
"@mpgd/adapter-devvit": "0.9.0",
|
|
59
|
+
"@mpgd/adapter-verse8": "0.3.0",
|
|
60
|
+
"@mpgd/bridge": "0.8.0",
|
|
61
|
+
"@mpgd/analytics": "0.3.9",
|
|
62
|
+
"@mpgd/game-services": "0.12.0",
|
|
63
|
+
"@mpgd/catalog": "0.6.0",
|
|
64
|
+
"@mpgd/i18n": "0.5.5",
|
|
65
|
+
"@mpgd/target-config": "0.12.0",
|
|
63
66
|
"@mpgd/phaser-assets": "0.4.1",
|
|
64
|
-
"@mpgd/platform": "0.
|
|
65
|
-
"@mpgd/target-config": "0.10.1"
|
|
67
|
+
"@mpgd/platform": "0.9.0"
|
|
66
68
|
},
|
|
67
69
|
"main": "./dist/index.js",
|
|
68
70
|
"types": "./dist/index.d.ts",
|
|
@@ -65,6 +65,33 @@ pnpm --dir ../mpgd-kit mpgd target build-all \
|
|
|
65
65
|
into `.mpgd.targets.generated.json` before calling the kit's existing target
|
|
66
66
|
build and smoke scripts.
|
|
67
67
|
|
|
68
|
+
### Offline test-play copy
|
|
69
|
+
|
|
70
|
+
When a tester needs to open one local file without a server, package the
|
|
71
|
+
existing browser preview:
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
pnpm exec mpgd target build web-preview staging \
|
|
75
|
+
--targets-file ./mpgd.targets.json \
|
|
76
|
+
--kit-path "${MPGD_KIT_PATH:-../mpgd-kit}"
|
|
77
|
+
pnpm exec mpgd game offline-playtest .
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Open `artifacts/offline-playtest/index.html` directly. This is a
|
|
81
|
+
network-blocked, test-play-only copy—not a target in `mpgd.targets.json`, PWA,
|
|
82
|
+
deployment artifact, release-manifest entry, or store-submission package.
|
|
83
|
+
Custom output directories must remain below this game's `artifacts` directory.
|
|
84
|
+
`README.txt` and `offline-playtest.json` preserve that boundary and record the
|
|
85
|
+
result hash. Server-backed login, purchases, ads, rewards, leaderboards, and
|
|
86
|
+
cloud saves do not work in this copy. Workers, service workers, WebAssembly
|
|
87
|
+
streaming, dynamic imports, import maps, HTML base elements, CSS `@import`,
|
|
88
|
+
runtime-computed asset URLs, retained inline-module imports, script-driven
|
|
89
|
+
navigation, and browser-dependent `file://` storage are outside this helper's
|
|
90
|
+
contract. Meta-refresh navigation is removed from the copy. glTF files must
|
|
91
|
+
embed their dependencies as data URIs; use GLB for models with external buffers
|
|
92
|
+
or textures. A successful package does not prove that every game path works
|
|
93
|
+
offline; keep unavailable capability fallbacks in the game.
|
|
94
|
+
|
|
68
95
|
The starter owns its Apps in Toss wrapper under `apps/target-ait`. With
|
|
69
96
|
`authoritativeGameServices: false`, production keeps native identity, storage,
|
|
70
97
|
sharing, and Game Center while disabling IAP and ads; no game-services URL is
|
|
@@ -73,6 +100,10 @@ set the authority flag to `true`, add app-owned product or ad IDs, and configure
|
|
|
73
100
|
`VITE_MPGD_GAME_SERVICES_URL` to a public HTTPS verifier without embedded
|
|
74
101
|
credentials.
|
|
75
102
|
|
|
103
|
+
The same verifier URL is required when a deployed custom `web` runtime enables
|
|
104
|
+
IAP or ads. This production gate prevents client-only grants even when the
|
|
105
|
+
browser adapter itself is game-owned.
|
|
106
|
+
|
|
76
107
|
The Capacitor entries still intentionally point at kit reference shells, so
|
|
77
108
|
their reusable validation builds use the `staging` profile. Before an Android
|
|
78
109
|
or iOS production build, copy or create the shell under this game directory and
|
|
@@ -92,7 +123,9 @@ Optional targets can add their own config, scripts, runtime wiring, and release
|
|
|
92
123
|
skill. To enable Microsoft Store after creation, run
|
|
93
124
|
`pnpm exec mpgd target init microsoft-store --game . --kit-path ../mpgd-kit`.
|
|
94
125
|
The initializer is idempotent and stops before writing when it finds conflicting
|
|
95
|
-
game-owned values.
|
|
126
|
+
game-owned values. It requires this generated starter's existing
|
|
127
|
+
`src/platform/runtimeDetector.ts` and `vite.shared.ts` files; custom layouts must
|
|
128
|
+
add equivalent runtime and Vite gateway routing manually.
|
|
96
129
|
|
|
97
130
|
## Acceptance Handoff
|
|
98
131
|
|
|
@@ -246,29 +279,19 @@ pnpm build:ait
|
|
|
246
279
|
pnpm smoke:ait
|
|
247
280
|
```
|
|
248
281
|
|
|
249
|
-
The game-owned wrapper
|
|
250
|
-
|
|
282
|
+
The game-owned wrapper provides an explicit local SDK mock for browser layout
|
|
283
|
+
and non-native flow checks:
|
|
251
284
|
|
|
252
285
|
```sh
|
|
253
286
|
pnpm ait:wrapper:dev
|
|
254
287
|
pnpm ait:wrapper:dev:plain
|
|
255
|
-
pnpm ait:wrapper:dev:phone
|
|
256
|
-
pnpm ait:devtools:mcp
|
|
257
|
-
pnpm ait:devtools:mcp:mobile
|
|
258
288
|
```
|
|
259
289
|
|
|
260
|
-
`ait:wrapper:dev`
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
for phone sandbox checks, and `ait:devtools:mcp:mobile` starts the community MCP
|
|
266
|
-
bridge in the phone tunnel mode after the wrapper prints the tunnel URLs. Use
|
|
267
|
-
`ait:devtools:mcp` for the default local or relay debugging mode. The kit
|
|
268
|
-
intentionally leaves
|
|
269
|
-
`cloudflared` postinstall disabled; the first phone tunnel session downloads the
|
|
270
|
-
binary lazily through the community devtools package instead of during ordinary
|
|
271
|
-
workspace install.
|
|
290
|
+
`ait:wrapper:dev` loads the last game bundle copied by `pnpm build:ait` from the
|
|
291
|
+
wrapper's `public/game` directory, so run `pnpm build:ait` again after game
|
|
292
|
+
changes before opening it. The local mock deliberately keeps ads, promotions,
|
|
293
|
+
and IAP unavailable; test those flows from the Apps in Toss console QR sandbox
|
|
294
|
+
or a real device after uploading the `.ait` bundle.
|
|
272
295
|
|
|
273
296
|
The starter includes `@ait-co/console-cli`, the Apps in Toss community console
|
|
274
297
|
CLI, as a dev dependency for project-local console automation:
|
|
@@ -297,6 +320,13 @@ Apps in Toss console, while the file owns `appName`, `brand.primaryColor`,
|
|
|
297
320
|
permissions, `webView`, and `webBundleDir`. Do not restore the removed SDK 2
|
|
298
321
|
`granite.config.ts` fields.
|
|
299
322
|
|
|
323
|
+
Apps in Toss navigation chrome is target-owned. Configure the `ait` target's
|
|
324
|
+
`navigationBar` in `mpgd.targets.json`; `mpgd target build ait` validates the
|
|
325
|
+
supported appearance options and forwards them to `apps-in-toss.config.ts` at
|
|
326
|
+
build time. The Phaser starter uses a transparent dark navigation bar without
|
|
327
|
+
back or home chrome so game content can continue underneath it. Keep
|
|
328
|
+
the game's own safe-area padding clear of Toss controls.
|
|
329
|
+
|
|
300
330
|
Before the first SDK 3 release, allow both of these exact origins on every API
|
|
301
331
|
called by the mini-app:
|
|
302
332
|
|
|
@@ -330,7 +360,7 @@ HTTPS game-services backend that verifies purchase or reward evidence.
|
|
|
330
360
|
|
|
331
361
|
- Browser preview is fully game-owned and writes to `artifacts/web-preview`.
|
|
332
362
|
<!-- mpgd:microsoft-store:start -->
|
|
333
|
-
- Microsoft Store is a game-owned PWA target
|
|
363
|
+
- Microsoft Store is a game-owned PWA target with a dedicated Digital Goods adapter and
|
|
334
364
|
writes to `artifacts/microsoft-store` with `manifest.webmanifest` for
|
|
335
365
|
PWABuilder packaging. Replace the starter icon and manifest metadata before
|
|
336
366
|
Store submission. Production builds also emit `pwa-release.json` and an
|
|
@@ -341,6 +371,16 @@ HTTPS game-services backend that verifies purchase or reward evidence.
|
|
|
341
371
|
app; do not force activation with `skipWaiting()`. Keep the web manifest `id`
|
|
342
372
|
game-specific because the cache namespace uses it to isolate apps sharing an
|
|
343
373
|
origin.
|
|
374
|
+
The generated target starts with `authoritativeGameServices: false` while
|
|
375
|
+
`mpgd.microsoft-store.json` uses `commerce.mode: "disabled"`, so its effective
|
|
376
|
+
target contains no enabled IAP products. Switch both settings to their enabled
|
|
377
|
+
values only after the game supplies consumable
|
|
378
|
+
`InAppOfferToken` mappings plus an authoritative HTTPS backend. The backend
|
|
379
|
+
must resolve the renewable User Store ID and a separate stable account-link
|
|
380
|
+
ID from trusted player identity, query Collections v9, record the grant
|
|
381
|
+
idempotently, and consume through Collections v8. Never derive the account-link
|
|
382
|
+
ID from the User Store ID or treat the browser `purchaseToken` as a unique
|
|
383
|
+
transaction ID.
|
|
344
384
|
Before Partner Center submission, replace every placeholder in
|
|
345
385
|
`mpgd.microsoft-store.json`, add at least one real PNG screenshot per listing
|
|
346
386
|
locale (maximum 50 MB and at least 1366 x 768 in either orientation), and run
|
|
@@ -401,9 +441,9 @@ HTTPS game-services backend that verifies purchase or reward evidence.
|
|
|
401
441
|
bridge fails closed on identity, provider, serialization, and quota errors;
|
|
402
442
|
it never switches progress to browser `localStorage`.
|
|
403
443
|
- Apps in Toss is game-owned in `apps/target-ait`. The wrapper keeps the app
|
|
404
|
-
name, console state, review metadata, and
|
|
405
|
-
|
|
406
|
-
|
|
444
|
+
name, console state, review metadata, and local SDK mock configuration in the
|
|
445
|
+
game while reusing `@mpgd/adapter-ait` for the production bridge and
|
|
446
|
+
game-bundle loader.
|
|
407
447
|
- Android and iOS currently use the kit reference Capacitor shell at
|
|
408
448
|
`${MPGD_KIT_PATH}/apps/mobile-capacitor`. That is useful for local artifact
|
|
409
449
|
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
|
+
}
|
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
export type { TargetConfigMatrix } from '@mpgd/target-config';
|
|
13
13
|
|
|
14
14
|
const runtimeKinds = valueSet({
|
|
15
|
+
web: true,
|
|
15
16
|
'web-preview': true,
|
|
16
17
|
'microsoft-store-pwa': true,
|
|
17
18
|
'capacitor-android': true,
|
|
@@ -21,6 +22,7 @@ const runtimeKinds = valueSet({
|
|
|
21
22
|
'verse8-web': true,
|
|
22
23
|
} satisfies Record<TargetRuntimeKind, true>);
|
|
23
24
|
const releaseProfiles = valueSet({
|
|
25
|
+
web: true,
|
|
24
26
|
'web-preview': true,
|
|
25
27
|
'microsoft-store': true,
|
|
26
28
|
'google-play': true,
|