@mpgd/cli 0.21.0 → 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.
Files changed (30) hide show
  1. package/dist/index.d.ts +2 -1
  2. package/dist/index.js +92 -8
  3. package/dist/microsoft-store-package-generation-contract.d.ts +5 -0
  4. package/dist/microsoft-store-package-generation-input.js +55 -0
  5. package/dist/microsoft-store-starter.d.ts +7 -0
  6. package/dist/microsoft-store-starter.js +176 -4
  7. package/dist/microsoft-store-submission.d.ts +24 -3
  8. package/dist/microsoft-store-submission.js +131 -4
  9. package/dist/offline-playtest.d.ts +31 -0
  10. package/dist/offline-playtest.js +5718 -0
  11. package/dist/production-target-readiness.js +38 -19
  12. package/package.json +15 -13
  13. package/templates/phaser-game/README.md +58 -22
  14. package/templates/phaser-game/apps/target-ait/apps-in-toss.config.ts +12 -0
  15. package/templates/phaser-game/apps/target-ait/package.json +5 -8
  16. package/templates/phaser-game/apps/target-ait/vite.config.ts +6 -15
  17. package/templates/phaser-game/apps/target-cloudflare-pages/package.json +1 -1
  18. package/templates/phaser-game/apps/target-devvit/README.md +2 -1
  19. package/templates/phaser-game/apps/target-devvit/package.json +1 -1
  20. package/templates/phaser-game/apps/target-devvit/src/server/index.ts +1 -0
  21. package/templates/phaser-game/docs/MPGD_KIT_WORKFLOWS.md +11 -0
  22. package/templates/phaser-game/mpgd.targets.json +8 -1
  23. package/templates/phaser-game/package.json +2 -5
  24. package/templates/phaser-game/pnpm-workspace.yaml +3 -2
  25. package/templates/phaser-game/src/entry.ts +10 -4
  26. package/templates/phaser-game/src/gameEntry.ts +7 -1
  27. package/templates/phaser-game/src/platform/buildGateways/microsoftStore.ts +37 -0
  28. package/templates/phaser-game/src/platform/runtimeDetector.ts +1 -0
  29. package/templates/phaser-game/src/runtime/renderEntryFailure.ts +24 -0
  30. 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 requiresVerse8RewardAuthority = input.targetPolicy?.runtime === 'verse8-web'
23
- && (input.targetPolicy.features.rewardedAds
24
- || input.targetPolicy.monetization.rewardedAds);
25
- const requiresWebMonetizationAuthority = input.targetPolicy?.runtime === 'web'
26
- && (input.targetPolicy.features.iap
27
- || input.targetPolicy.features.rewardedAds
28
- || input.targetPolicy.features.interstitialAds
29
- || input.targetPolicy.monetization.iap
30
- || input.targetPolicy.monetization.rewardedAds
31
- || input.targetPolicy.monetization.interstitialAds);
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.21.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.8.0",
54
- "@mpgd/adapter-browser": "0.5.1",
55
- "@mpgd/adapter-capacitor": "0.4.5",
56
- "@mpgd/adapter-devvit": "0.8.6",
57
- "@mpgd/adapter-verse8": "0.2.3",
58
- "@mpgd/analytics": "0.3.8",
59
- "@mpgd/bridge": "0.7.0",
60
- "@mpgd/game-services": "0.11.1",
61
- "@mpgd/i18n": "0.5.4",
62
- "@mpgd/catalog": "0.5.2",
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.8.0",
65
- "@mpgd/target-config": "0.11.0"
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
@@ -96,7 +123,9 @@ Optional targets can add their own config, scripts, runtime wiring, and release
96
123
  skill. To enable Microsoft Store after creation, run
97
124
  `pnpm exec mpgd target init microsoft-store --game . --kit-path ../mpgd-kit`.
98
125
  The initializer is idempotent and stops before writing when it finds conflicting
99
- 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.
100
129
 
101
130
  ## Acceptance Handoff
102
131
 
@@ -250,29 +279,19 @@ pnpm build:ait
250
279
  pnpm smoke:ait
251
280
  ```
252
281
 
253
- The game-owned wrapper includes the Apps in Toss community devtools package for
254
- SDK mocks and device debugging:
282
+ The game-owned wrapper provides an explicit local SDK mock for browser layout
283
+ and non-native flow checks:
255
284
 
256
285
  ```sh
257
286
  pnpm ait:wrapper:dev
258
287
  pnpm ait:wrapper:dev:plain
259
- pnpm ait:wrapper:dev:phone
260
- pnpm ait:devtools:mcp
261
- pnpm ait:devtools:mcp:mobile
262
288
  ```
263
289
 
264
- `ait:wrapper:dev` runs `apps/target-ait` with the community Vite plugin enabled.
265
- It loads the last game bundle copied by `pnpm build:ait` from the wrapper's
266
- `public/game` directory, so run `pnpm build:ait` again after game changes before
267
- opening a wrapper devtools session. Use `ait:wrapper:dev:plain` to disable the
268
- devtools plugin. `ait:wrapper:dev:phone` uses the devtools tunnel and CDP relay
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.
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.
276
295
 
277
296
  The starter includes `@ait-co/console-cli`, the Apps in Toss community console
278
297
  CLI, as a dev dependency for project-local console automation:
@@ -301,6 +320,13 @@ Apps in Toss console, while the file owns `appName`, `brand.primaryColor`,
301
320
  permissions, `webView`, and `webBundleDir`. Do not restore the removed SDK 2
302
321
  `granite.config.ts` fields.
303
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
+
304
330
  Before the first SDK 3 release, allow both of these exact origins on every API
305
331
  called by the mini-app:
306
332
 
@@ -334,7 +360,7 @@ HTTPS game-services backend that verifies purchase or reward evidence.
334
360
 
335
361
  - Browser preview is fully game-owned and writes to `artifacts/web-preview`.
336
362
  <!-- mpgd:microsoft-store:start -->
337
- - Microsoft Store is a game-owned PWA target that reuses the browser adapter and
363
+ - Microsoft Store is a game-owned PWA target with a dedicated Digital Goods adapter and
338
364
  writes to `artifacts/microsoft-store` with `manifest.webmanifest` for
339
365
  PWABuilder packaging. Replace the starter icon and manifest metadata before
340
366
  Store submission. Production builds also emit `pwa-release.json` and an
@@ -345,6 +371,16 @@ HTTPS game-services backend that verifies purchase or reward evidence.
345
371
  app; do not force activation with `skipWaiting()`. Keep the web manifest `id`
346
372
  game-specific because the cache namespace uses it to isolate apps sharing an
347
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.
348
384
  Before Partner Center submission, replace every placeholder in
349
385
  `mpgd.microsoft-store.json`, add at least one real PNG screenshot per listing
350
386
  locale (maximum 50 MB and at least 1366 x 768 in either orientation), and run
@@ -405,9 +441,9 @@ HTTPS game-services backend that verifies purchase or reward evidence.
405
441
  bridge fails closed on identity, provider, serialization, and quota errors;
406
442
  it never switches progress to browser `localStorage`.
407
443
  - Apps in Toss is game-owned in `apps/target-ait`. The wrapper keeps the app
408
- name, console state, review metadata, and `@ait-co/devtools` development
409
- integration local to the game while reusing `@mpgd/adapter-ait` for the
410
- production bridge and game-bundle loader.
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.
411
447
  - Android and iOS currently use the kit reference Capacitor shell at
412
448
  `${MPGD_KIT_PATH}/apps/mobile-capacitor`. That is useful for local artifact
413
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": "MPGD_AIT_DEVTOOLS=0 VITE_MPGD_AIT_MOCK_IDENTITY=1 vite --host 0.0.0.0",
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.0-beta.da14818",
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.0-beta.66a479a",
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": "^8.1.3"
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 enableAitDevtools = command === 'serve'
11
+ const enableLocalAitMock = command === 'serve'
18
12
  && !isPreview
19
- && process.env.MPGD_AIT_DEVTOOLS !== '0';
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
- // Vite prebundles the Kit host before plugin resolve hooks run. An explicit development
28
- // alias keeps that optimized host while replacing its transitive native SDK import.
29
- ...(enableAitDevtools
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-co/devtools/mock',
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: {
@@ -22,7 +22,7 @@
22
22
  "@ttsc/unplugin": "0.18.4",
23
23
  "ttsc": "0.18.4",
24
24
  "typescript": "7.0.2",
25
- "vite": "^8.1.3",
25
+ "vite": "8.1.3",
26
26
  "wrangler": "^4.107.0"
27
27
  }
28
28
  }
@@ -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.
@@ -30,6 +30,6 @@
30
30
  "devvit": "0.13.8",
31
31
  "ttsc": "0.18.4",
32
32
  "typescript": "7.0.2",
33
- "vite": "^8.1.3"
33
+ "vite": "8.1.3"
34
34
  }
35
35
  }
@@ -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": "browser",
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.0-beta.da14818",
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": "^8.1.3",
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
- if (__APP_TARGET__ === 'reddit') {
2
- await import('./platform/devvitEntrypoint');
3
- } else {
4
- await import('./main');
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 {};
@@ -1,3 +1,9 @@
1
- await import('./main');
1
+ import { renderEntryFailure } from './runtime/renderEntryFailure';
2
+
3
+ try {
4
+ await import('./main');
5
+ } catch (error) {
6
+ renderEntryFailure(error);
7
+ }
2
8
 
3
9
  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
+ }
@@ -4,6 +4,7 @@ const validTargets = new Set<string>([
4
4
  'android',
5
5
  'ios',
6
6
  'ait',
7
+ 'microsoft-store',
7
8
  'reddit',
8
9
  'verse8',
9
10
  'telegram',
@@ -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
  }