@mpgd/cli 0.6.0 → 0.7.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 (41) hide show
  1. package/dist/game-acceptance.d.ts +65 -0
  2. package/dist/game-acceptance.js +245 -0
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.js +404 -3
  5. package/dist/production-target-readiness.d.ts +8 -0
  6. package/dist/production-target-readiness.js +207 -0
  7. package/package.json +11 -11
  8. package/templates/phaser-game/README.md +71 -1
  9. package/templates/phaser-game/agent/acceptance.md +12 -6
  10. package/templates/phaser-game/agent/brief.md +3 -1
  11. package/templates/phaser-game/agent/game-manifest.json +7 -0
  12. package/templates/phaser-game/apps/target-devvit/README.md +30 -0
  13. package/templates/phaser-game/apps/target-devvit/devvit.json +7 -0
  14. package/templates/phaser-game/apps/target-devvit/package.json +8 -5
  15. package/templates/phaser-game/apps/target-devvit/src/server/postOperationStore.ts +9 -0
  16. package/templates/phaser-game/game.html +13 -0
  17. package/templates/phaser-game/gitignore +2 -0
  18. package/templates/phaser-game/index.html +2 -2
  19. package/templates/phaser-game/mpgd.game.json +8 -0
  20. package/templates/phaser-game/mpgd.targets.json +6 -0
  21. package/templates/phaser-game/package.json +7 -1
  22. package/templates/phaser-game/public/manifest.webmanifest +1 -0
  23. package/templates/phaser-game/src/entry.ts +7 -0
  24. package/templates/phaser-game/src/env.d.ts +3 -0
  25. package/templates/phaser-game/src/gameEntry.ts +3 -0
  26. package/templates/phaser-game/src/main.ts +11 -2
  27. package/templates/phaser-game/src/platform/buildGatewayModule.ts +5 -0
  28. package/templates/phaser-game/src/platform/buildGateways/ait.ts +11 -0
  29. package/templates/phaser-game/src/platform/buildGateways/aitSandbox.ts +12 -0
  30. package/templates/phaser-game/src/platform/buildGateways/browser.ts +8 -0
  31. package/templates/phaser-game/src/platform/buildGateways/capacitorAndroid.ts +12 -0
  32. package/templates/phaser-game/src/platform/buildGateways/capacitorIos.ts +12 -0
  33. package/templates/phaser-game/src/platform/buildGateways/reddit.ts +11 -0
  34. package/templates/phaser-game/src/platform/buildGateways/redditSandbox.ts +15 -0
  35. package/templates/phaser-game/src/platform/devvitEntrypoint.ts +57 -0
  36. package/templates/phaser-game/src/platform/devvitInlinePreview.css +75 -0
  37. package/templates/phaser-game/src/platform/gameServices.ts +18 -61
  38. package/templates/phaser-game/src/platform/installPlatform.ts +6 -50
  39. package/templates/phaser-game/src/platform/microsoftStorePwa.ts +99 -0
  40. package/templates/phaser-game/tools/run-game-acceptance.mjs +22 -0
  41. package/templates/phaser-game/vite.config.ts +106 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mpgd/cli",
3
- "version": "0.6.0",
3
+ "version": "0.7.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-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",
52
+ "@mpgd/adapter-ait": "0.4.1",
53
+ "@mpgd/adapter-devvit": "0.5.0",
54
+ "@mpgd/analytics": "0.3.4",
55
+ "@mpgd/adapter-browser": "0.4.1",
56
+ "@mpgd/adapter-capacitor": "0.4.1",
57
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",
58
+ "@mpgd/catalog": "0.3.4",
59
+ "@mpgd/game-services": "0.5.0",
60
+ "@mpgd/i18n": "0.5.0",
61
+ "@mpgd/platform": "0.5.0",
61
62
  "@mpgd/phaser-assets": "0.4.0",
62
- "@mpgd/platform": "0.4.0",
63
- "@mpgd/target-config": "0.5.0"
63
+ "@mpgd/target-config": "0.6.0"
64
64
  },
65
65
  "main": "./dist/index.js",
66
66
  "types": "./dist/index.d.ts",
@@ -32,6 +32,7 @@ pnpm exec mpgd target build-all \
32
32
  --targets-file ./mpgd.targets.json \
33
33
  --kit-path ../mpgd-kit \
34
34
  --targets web,microsoft-store,ait,reddit \
35
+ --profile staging \
35
36
  --ait-variant wrapper
36
37
  pnpm exec mpgd target smoke-all \
37
38
  --targets-file ./mpgd.targets.json \
@@ -51,6 +52,7 @@ pnpm --dir ../mpgd-kit mpgd target build-all \
51
52
  --targets-file "$PWD/mpgd.targets.json" \
52
53
  --kit-path ../mpgd-kit \
53
54
  --targets web,microsoft-store,ait,reddit \
55
+ --profile staging \
54
56
  --ait-variant wrapper
55
57
  ```
56
58
 
@@ -58,10 +60,37 @@ pnpm --dir ../mpgd-kit mpgd target build-all \
58
60
  into `.mpgd.targets.generated.json` before calling the kit's existing target
59
61
  build and smoke scripts.
60
62
 
63
+ The starter's Apps in Toss and Capacitor entries intentionally point at kit
64
+ reference wrappers, so their reusable validation builds use the `staging`
65
+ profile. Before an AIT, Android, or iOS production build, copy or create the
66
+ wrapper/shell under this game directory, update `mpgd.targets.json`, and set
67
+ `VITE_MPGD_GAME_SERVICES_URL` to a public HTTPS endpoint without embedded
68
+ credentials. Production preflight resolves real paths, rejects wrappers or
69
+ shells outside the game root (including symbolic-link escapes), and blocks
70
+ localhost plus literal private or reserved IP addresses.
71
+
61
72
  `mpgd target doctor` verifies that release manifest artifact paths stay under
62
73
  this game directory, even when a build used the kit reference Apps in Toss or
63
74
  Capacitor wrappers.
64
75
 
76
+ ## Acceptance Handoff
77
+
78
+ Run the reusable acceptance workflow before handing the game off:
79
+
80
+ ```sh
81
+ pnpm accept
82
+ ```
83
+
84
+ The command runs `check`, an optional `test` script, `build`, the kit's ttsc
85
+ graph preflight, an optional game-owned `playtest` script, and the configured
86
+ target build/smoke matrix. Results are written as JSON and Markdown under
87
+ `artifacts/acceptance`. Add a non-interactive `playtest` package script when the
88
+ game has an automated browser scenario; interactive platform playtests remain
89
+ separate operator workflows.
90
+ Each command has a 30-minute timeout by default. Pass `--timeout-ms` to the
91
+ underlying `mpgd game accept` command when a target matrix needs a different
92
+ per-step limit.
93
+
65
94
  ## Assets
66
95
 
67
96
  Static assets live under `src/assets` and are declared in
@@ -116,6 +145,24 @@ soft rotate prompt on mismatch. The generated PWA manifest also declares
116
145
  that manifest value aligned with the runtime policy if you make a portrait-first
117
146
  game.
118
147
 
148
+ ## App Icons
149
+
150
+ `mpgd.game.json` owns one canonical PNG or SVG app icon source. Target profiles
151
+ derive each platform's required sizes, safe zones, alpha policy, and native or
152
+ wrapper staging files.
153
+
154
+ ```sh
155
+ pnpm icons:generate
156
+ pnpm icons:verify
157
+ pnpm icons:inspect
158
+ ```
159
+
160
+ Use each target's optional `icon` object in `mpgd.targets.json` for a profile,
161
+ background, source, or variant override. The release evidence still records the
162
+ shared canonical source separately from an override render source. Before an
163
+ Apps in Toss production package build, upload the generated 600x600 icon in the
164
+ console and configure the returned HTTPS URL as `ait.icon.externalUrl`.
165
+
119
166
  ## Reddit Devvit
120
167
 
121
168
  This starter owns its Devvit app root in `apps/target-devvit`. That directory
@@ -126,10 +173,17 @@ The generated `devvit.json` app name is a Devvit-safe slug derived from the game
126
173
  directory name; edit it before `devvit:init` if you need a different Reddit app
127
174
  name.
128
175
 
176
+ The default post entry builds a lightweight inline preview without importing
177
+ Phaser. Its Play button requests the separate `game` entrypoint, whose
178
+ `game.html` document starts the Phaser bundle directly. Keep these physical
179
+ entries separate when customizing the preview so Reddit cards do not pay the
180
+ game runtime cost before expansion.
181
+
129
182
  First-time Devvit setup:
130
183
 
131
184
  ```sh
132
185
  pnpm install -w
186
+ pnpm icons:generate:devvit:production
133
187
  pnpm devvit:login
134
188
  pnpm devvit:whoami
135
189
  pnpm devvit:init
@@ -218,7 +272,14 @@ submission.
218
272
  - Microsoft Store is a game-owned PWA target that reuses the browser adapter and
219
273
  writes to `artifacts/microsoft-store` with `manifest.webmanifest` for
220
274
  PWABuilder packaging. Replace the starter icon and manifest metadata before
221
- Store submission.
275
+ Store submission. Production builds also emit `pwa-release.json` and an
276
+ atomic offline `service-worker.js`; its deterministic revision covers every
277
+ precached artifact plus the game and kit Git provenance. Updates intentionally
278
+ wait while any old app window remains open. Listen for
279
+ `mpgd:pwa-update-ready` to tell players to close every window and reopen the
280
+ app; do not force activation with `skipWaiting()`. Keep the web manifest `id`
281
+ game-specific because the cache namespace uses it to isolate apps sharing an
282
+ origin.
222
283
  - Reddit Devvit is game-owned in `apps/target-devvit`.
223
284
  - Apps in Toss currently uses the kit reference wrapper at
224
285
  `${MPGD_KIT_PATH}/apps/target-ait` for smoke packaging. Before a real Toss
@@ -242,3 +303,12 @@ VITE_MPGD_GAME_SERVICES_TRANSPORT=http
242
303
 
243
304
  Use `VITE_MPGD_GAME_SERVICES_TRANSPORT=orpc` with a `/rpc` URL to test the oRPC
244
305
  client path.
306
+
307
+ The generated starter uses `createGameServicesRuntime` for backend selection
308
+ and treats only the exact Vite `production` profile as production authority.
309
+ Production fails closed when the URL is absent or is not public HTTPS without
310
+ credentials: Game Services reports `missing_authoritative_backend` or
311
+ `invalid_authoritative_backend` and does not create a client. Process-local
312
+ backends require both a non-production authority mode and an explicit
313
+ `allowLocalBackend` opt-in, so they cannot manufacture successful production
314
+ purchase, rewarded-ad, or leaderboard ledger results.
@@ -3,22 +3,28 @@
3
3
  Run these checks before handing off a generated game starter:
4
4
 
5
5
  ```sh
6
- pnpm check
7
- pnpm build
8
- pnpm --dir ../mpgd-kit mpgd target build-all --targets-file "$PWD/mpgd.targets.json" --targets web,microsoft-store,ait,reddit --ait-variant wrapper
9
- pnpm --dir ../mpgd-kit mpgd target smoke-all --targets-file "$PWD/mpgd.targets.json" --targets web,microsoft-store,ait,reddit
10
- pnpm --dir ../mpgd-kit mpgd target doctor --targets-file "$PWD/mpgd.targets.json" --targets web,microsoft-store,ait,reddit
6
+ pnpm icons:generate
7
+ pnpm icons:verify
8
+ pnpm accept
11
9
  ```
12
10
 
11
+ `pnpm accept` runs the configured game check, optional test and playtest scripts,
12
+ game build, kit TypeScript graph preflight, and the reusable target build/smoke
13
+ matrix. It writes `artifacts/acceptance/acceptance-report.json` and
14
+ `acceptance-report.md` for handoff. Missing optional `test` or `playtest` scripts
15
+ are recorded as skipped; check and build remain required.
16
+
13
17
  For Apps in Toss changes, use the apps-in-toss MCP before implementation and
14
18
  keep SDK calls inside adapters or target wrappers.
15
19
  Resolve identity and launch intents during bootstrap, treat inbound share data as
16
20
  untrusted, and keep notification delivery on the server.
17
21
 
18
- For Reddit Devvit changes, keep Devvit SDK calls inside the target wrapper and
22
+ For Reddit Devvit changes, keep Devvit SDK calls inside the adapter or target wrapper and
19
23
  continue to expose game-facing behavior through `PlatformGateway`. The Devvit
20
24
  app root is game-owned in `apps/target-devvit`; run `pnpm devvit:init` once
21
25
  after login before upload or playtest.
26
+ Verify the default post entry renders only the lightweight inline preview and
27
+ the `game` entry opens the separate expanded Phaser document.
22
28
 
23
29
  For Microsoft Store changes, keep the first pass as a PWA/web target that uses
24
30
  the browser adapter. Add a dedicated Store commerce adapter only when wiring
@@ -18,7 +18,9 @@ Useful commands:
18
18
  pnpm dev
19
19
  pnpm check
20
20
  pnpm build
21
- pnpm exec mpgd target build-all --targets-file ./mpgd.targets.json --kit-path ../mpgd-kit --targets web,microsoft-store,ait,reddit --ait-variant wrapper
21
+ pnpm icons:generate
22
+ pnpm icons:verify
23
+ pnpm exec mpgd target build-all --targets-file ./mpgd.targets.json --kit-path ../mpgd-kit --targets web,microsoft-store,ait,reddit --profile staging --ait-variant wrapper
22
24
  pnpm devvit:login
23
25
  pnpm devvit:init
24
26
  pnpm devvit:playtest
@@ -37,6 +37,10 @@
37
37
  "id": "platform.devvit.app-root",
38
38
  "owner": "apps/target-devvit"
39
39
  },
40
+ {
41
+ "id": "platform.devvit.surface-split",
42
+ "owner": "src/platform/devvitEntrypoint.ts"
43
+ },
40
44
  {
41
45
  "id": "services.backend.game-services",
42
46
  "owner": "src/platform/gameServices.ts"
@@ -51,6 +55,9 @@
51
55
  }
52
56
  ],
53
57
  "acceptance": [
58
+ "pnpm accept",
59
+ "pnpm icons:generate",
60
+ "pnpm icons:verify",
54
61
  "pnpm check",
55
62
  "pnpm build"
56
63
  ]
@@ -29,3 +29,33 @@ The client bundle is copied into `dist/client` by the mpgd target build. The ser
29
29
  compiled to `dist/server/index.cjs` and keeps Devvit SDK imports out of Phaser scenes.
30
30
  The game root pins `@mpgd/cli`, so these commands use the same CLI version as the generated
31
31
  starter's other `@mpgd/*` dependencies.
32
+
33
+ The default post entry uses `index.html` for a lightweight inline preview. Its
34
+ Play button requests the `game` entrypoint, which loads the separate
35
+ `game.html` Phaser document. Keep inline UI free of game runtime imports so the
36
+ card remains lightweight before expansion.
37
+
38
+ ## Durable Post Operations
39
+
40
+ Use `src/server/postOperationStore.ts` with `@mpgd/adapter-devvit/server` for a
41
+ server action or scheduler that may receive the same custom-post operation more
42
+ than once. Use `{ appScope, subredditId }` as the scope, a stable `operationType`
43
+ in the definition, and an `operationId` for each logical publication slot. Validate
44
+ the public payload and launch parameters, then inject the Redis-backed store into
45
+ `createDevvitPostOperationCoordinator`.
46
+
47
+ The contract is duplicate-safe and ambiguity-safe, not exactly-once. It persists
48
+ the attempt before invoking Reddit. If Reddit accepts the post but its response is
49
+ lost, or the process stops before the receipt is saved, repeating `execute` returns
50
+ `reconciliation-required` and never blindly calls the submit function again. An
51
+ explicit recovery endpoint or scheduler calls `reconcile`, which requires an exact
52
+ match of the full canonical envelope. A missing match from a bounded listing
53
+ remains unresolved rather than restoring submit permission.
54
+
55
+ Keep operation definitions and `@devvit/web/server` imports inside this target
56
+ app. The canonical `{ mpgd, launch, payload }` envelope is public and untrusted;
57
+ keep private content and authoritative records in server-side storage. Exercise
58
+ first success, duplicate calls, interruption,
59
+ response loss, lease expiry with concurrent reconciliation, malformed durable
60
+ state, invalid launch metadata, and cross-scope key isolation before enabling a
61
+ scheduled or user-triggered publication flow.
@@ -1,12 +1,19 @@
1
1
  {
2
2
  "$schema": "https://developers.reddit.com/schema/config-file.v1.json",
3
3
  "name": "__DEVVIT_APP_NAME__",
4
+ "marketingAssets": {
5
+ "icon": "generated/marketing-icon.png"
6
+ },
4
7
  "post": {
5
8
  "dir": "dist/client",
6
9
  "entrypoints": {
7
10
  "default": {
8
11
  "entry": "index.html",
9
12
  "height": "tall"
13
+ },
14
+ "game": {
15
+ "entry": "game.html",
16
+ "height": "tall"
10
17
  }
11
18
  }
12
19
  },
@@ -4,9 +4,11 @@
4
4
  "version": "0.0.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
- "init": "devvit upload --config devvit.json",
8
- "init:copy-paste": "devvit upload --copy-paste --config devvit.json",
9
- "dev": "devvit playtest --config devvit.json",
7
+ "prepare:icon": "pnpm --dir ../.. icons:generate:devvit",
8
+ "prepare:icon:production": "pnpm --dir ../.. icons:generate:devvit:production",
9
+ "init": "pnpm run prepare:icon:production && devvit upload --config devvit.json",
10
+ "init:copy-paste": "pnpm run prepare:icon:production && devvit upload --copy-paste --config devvit.json",
11
+ "dev": "pnpm run prepare:icon && devvit playtest --config devvit.json",
10
12
  "dev:prepare": "pnpm --dir ../.. build:devvit:staging",
11
13
  "dev:client": "APP_TARGET=reddit APP_VERSION=0.0.0-dev BUILD_ID=devvit-watch pnpm --dir ../.. exec vite build --mode staging --watch --outDir apps/target-devvit/dist/client",
12
14
  "dev:server": "vite build --config vite.server.config.ts --watch",
@@ -14,8 +16,8 @@
14
16
  "build": "pnpm --dir ../.. build:devvit",
15
17
  "build:server": "vite build --config vite.server.config.ts",
16
18
  "whoami": "devvit whoami",
17
- "upload": "devvit upload --config devvit.json",
18
- "publish": "devvit publish --config devvit.json",
19
+ "upload": "pnpm run prepare:icon:production && devvit upload --config devvit.json",
20
+ "publish": "pnpm run prepare:icon:production && devvit publish --config devvit.json",
19
21
  "check": "ttsc --noEmit -p tsconfig.json",
20
22
  "lint": "ttsc --noEmit -p tsconfig.json",
21
23
  "format": "ttsc format",
@@ -23,6 +25,7 @@
23
25
  },
24
26
  "dependencies": {
25
27
  "@devvit/web": "0.13.6",
28
+ "@mpgd/adapter-devvit": "__MPGD_DEPENDENCY_VERSION_ADAPTER_DEVVIT__",
26
29
  "@mpgd/bridge": "__MPGD_DEPENDENCY_VERSION_BRIDGE__",
27
30
  "express": "^5.2.1",
28
31
  "helmet": "^8.1.0"
@@ -0,0 +1,9 @@
1
+ import { redis } from '@devvit/web/server';
2
+ import {
3
+ createDevvitRedisPostOperationStore,
4
+ type DevvitDurableOperationStore,
5
+ } from '@mpgd/adapter-devvit/server';
6
+
7
+ export function createPostOperationStore(): DevvitDurableOperationStore {
8
+ return createDevvitRedisPostOperationStore(redis);
9
+ }
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta name="theme-color" content="#0f172a" />
7
+ <title>__GAME_TITLE__</title>
8
+ </head>
9
+ <body>
10
+ <div id="game"></div>
11
+ <script type="module" src="/src/gameEntry.ts"></script>
12
+ </body>
13
+ </html>
@@ -1,9 +1,11 @@
1
1
  node_modules/
2
2
  dist/
3
3
  apps/*/dist/
4
+ apps/target-devvit/generated/
4
5
  artifacts/
5
6
  release-output/
6
7
  .mpgd.targets.generated.json
8
+ .mpgd/
7
9
  .env
8
10
  .env.*
9
11
  apps/*/.env
@@ -4,11 +4,11 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <meta name="theme-color" content="#0f172a" />
7
- <link rel="manifest" href="/manifest.webmanifest" />
7
+ <link rel="manifest" href="./manifest.webmanifest" />
8
8
  <title>__GAME_TITLE__</title>
9
9
  </head>
10
10
  <body>
11
11
  <div id="game"></div>
12
- <script type="module" src="/src/main.ts"></script>
12
+ <script type="module" src="/src/entry.ts"></script>
13
13
  </body>
14
14
  </html>
@@ -0,0 +1,8 @@
1
+ {
2
+ "brand": {
3
+ "appIcon": {
4
+ "source": "public/icon.svg",
5
+ "backgroundColor": "#0f172a"
6
+ }
7
+ }
8
+ }
@@ -4,12 +4,14 @@
4
4
  "kind": "web",
5
5
  "gameApp": ".",
6
6
  "adapter": "browser",
7
+ "icon": { "profile": "web-preview" },
7
8
  "output": "artifacts/web-preview"
8
9
  },
9
10
  "microsoft-store": {
10
11
  "kind": "web",
11
12
  "gameApp": ".",
12
13
  "adapter": "browser",
14
+ "icon": { "profile": "microsoft-pwa" },
13
15
  "output": "artifacts/microsoft-store"
14
16
  },
15
17
  "android": {
@@ -17,6 +19,7 @@
17
19
  "gameApp": ".",
18
20
  "shellApp": "${MPGD_KIT_PATH}/apps/mobile-capacitor",
19
21
  "adapter": "capacitor",
22
+ "icon": { "profile": "android" },
20
23
  "webDir": "${MPGD_KIT_PATH}/apps/mobile-capacitor/www",
21
24
  "artifact": "aab"
22
25
  },
@@ -25,6 +28,7 @@
25
28
  "gameApp": ".",
26
29
  "shellApp": "${MPGD_KIT_PATH}/apps/mobile-capacitor",
27
30
  "adapter": "capacitor",
31
+ "icon": { "profile": "ios" },
28
32
  "webDir": "${MPGD_KIT_PATH}/apps/mobile-capacitor/www",
29
33
  "artifact": "ipa"
30
34
  },
@@ -33,6 +37,7 @@
33
37
  "gameApp": ".",
34
38
  "wrapperApp": "${MPGD_KIT_PATH}/apps/target-ait",
35
39
  "adapter": "ait",
40
+ "icon": { "profile": "ait" },
36
41
  "webDir": "${MPGD_KIT_PATH}/apps/target-ait/public/game",
37
42
  "artifact": ".ait"
38
43
  },
@@ -41,6 +46,7 @@
41
46
  "gameApp": ".",
42
47
  "wrapperApp": "apps/target-devvit",
43
48
  "adapter": "devvit",
49
+ "icon": { "profile": "devvit" },
44
50
  "webDir": "apps/target-devvit/dist/client",
45
51
  "artifact": "devvit"
46
52
  }
@@ -8,7 +8,13 @@
8
8
  "dev": "__WORKSPACE_I18N_BUILD_PREFIX__vite --host 0.0.0.0",
9
9
  "dev:ait": "APP_TARGET=ait APP_VERSION=0.0.0-dev BUILD_ID=ait-sandbox sh -c '__WORKSPACE_I18N_BUILD_PREFIX__vite --host 0.0.0.0'",
10
10
  "build": "__WORKSPACE_I18N_BUILD_PREFIX__vite build",
11
- "build:ait": "pnpm exec mpgd target build ait production --variant wrapper --targets-file ./mpgd.targets.json --kit-path ${MPGD_KIT_PATH:-__DEFAULT_KIT_PATH__}",
11
+ "accept": "node ./tools/run-game-acceptance.mjs",
12
+ "icons:generate": "pnpm exec mpgd game icons generate . --kit-path ${MPGD_KIT_PATH:-__DEFAULT_KIT_PATH__}",
13
+ "icons:generate:devvit": "pnpm exec mpgd game icons generate . --targets reddit --profile development --kit-path ${MPGD_KIT_PATH:-__DEFAULT_KIT_PATH__}",
14
+ "icons:generate:devvit:production": "pnpm exec mpgd game icons generate . --targets reddit --profile production --kit-path ${MPGD_KIT_PATH:-__DEFAULT_KIT_PATH__}",
15
+ "icons:verify": "pnpm exec mpgd game icons verify . --kit-path ${MPGD_KIT_PATH:-__DEFAULT_KIT_PATH__}",
16
+ "icons:inspect": "pnpm exec mpgd game icons inspect . --kit-path ${MPGD_KIT_PATH:-__DEFAULT_KIT_PATH__}",
17
+ "build:ait": "pnpm exec mpgd target build ait staging --variant wrapper --targets-file ./mpgd.targets.json --kit-path ${MPGD_KIT_PATH:-__DEFAULT_KIT_PATH__}",
12
18
  "build:ait:package": "pnpm exec mpgd target build ait production --targets-file ./mpgd.targets.json --kit-path ${MPGD_KIT_PATH:-__DEFAULT_KIT_PATH__}",
13
19
  "smoke:ait": "pnpm exec mpgd target smoke ait --targets-file ./mpgd.targets.json --kit-path ${MPGD_KIT_PATH:-__DEFAULT_KIT_PATH__}",
14
20
  "ait:wrapper:dev": "pnpm --dir ${MPGD_KIT_PATH:-__DEFAULT_KIT_PATH__}/apps/target-ait run dev",
@@ -3,6 +3,7 @@
3
3
  "name": "__GAME_TITLE__",
4
4
  "short_name": "__GAME_NAME__",
5
5
  "description": "__GAME_TITLE__ built with mpgd-kit.",
6
+ "id": "./__GAME_NAME__",
6
7
  "start_url": "./",
7
8
  "scope": "./",
8
9
  "display": "standalone",
@@ -0,0 +1,7 @@
1
+ if (__APP_TARGET__ === 'reddit') {
2
+ await import('./platform/devvitEntrypoint');
3
+ } else {
4
+ await import('./main');
5
+ }
6
+
7
+ export {};
@@ -2,6 +2,9 @@
2
2
 
3
3
  declare const __APP_TARGET__: string;
4
4
  declare const __MPGD_CONFIG_TARGET__: string;
5
+ declare const __MPGD_PLATFORM_TARGET__:
6
+ | import('@mpgd/target-config').EffectivePlatformTargetMetadata
7
+ | undefined;
5
8
  declare const __APP_VERSION__: string;
6
9
  declare const __BUILD_ID__: string;
7
10
  declare const __SOURCE_GIT_SHA__: string;
@@ -0,0 +1,3 @@
1
+ await import('./main');
2
+
3
+ export {};
@@ -1,7 +1,7 @@
1
1
  import './styles.css';
2
2
 
3
3
  import { createAnalyticsReporter, createBufferedAnalyticsSink } from '@mpgd/analytics';
4
- import { resolveMpgdLocale, type Locale } from '@mpgd/i18n';
4
+ import { resolveTargetMpgdLocale, type Locale } from '@mpgd/i18n';
5
5
  import type { IdentitySession, LaunchIntent, PlatformGateway } from '@mpgd/platform';
6
6
  import {
7
7
  resolveTargetViewportPlan,
@@ -14,11 +14,13 @@ import { createStarterGame } from './runtime/createGame';
14
14
  import { detectRuntime } from './platform/runtimeDetector';
15
15
  import { createStarterGameServices } from './platform/gameServices';
16
16
  import { installPlatform } from './platform/installPlatform';
17
+ import { installMicrosoftStorePwa } from './platform/microsoftStorePwa';
17
18
 
18
19
  await bootstrap();
19
20
 
20
21
  async function bootstrap(): Promise<void> {
21
22
  let locale: Locale = 'en';
23
+ let disposeMicrosoftStorePwa: (() => void) | undefined;
22
24
 
23
25
  try {
24
26
  if (__APP_TARGET__ === 'ait') {
@@ -27,6 +29,7 @@ async function bootstrap(): Promise<void> {
27
29
  }
28
30
 
29
31
  const runtimeConfig = detectRuntime();
32
+ disposeMicrosoftStorePwa = installMicrosoftStorePwa(runtimeConfig);
30
33
  const platform = await installPlatform(runtimeConfig);
31
34
  const runtime = await platform.getTargetRuntime();
32
35
  const orientationPolicy = {
@@ -46,7 +49,12 @@ async function bootstrap(): Promise<void> {
46
49
  resolveIdentitySession(platform, player.playerId),
47
50
  resolveLaunchIntent(platform),
48
51
  ]);
49
- locale = resolveMpgdLocale(runtime.capabilities);
52
+ locale = resolveTargetMpgdLocale({
53
+ capabilities: runtime.capabilities,
54
+ fallbackLocale:
55
+ runtime.effectiveConfig?.localization.fallbackLocale
56
+ ?? runtime.config.localization.fallbackLocale,
57
+ });
50
58
  const analyticsSink = createBufferedAnalyticsSink();
51
59
  const analytics = createAnalyticsReporter({
52
60
  target: platform.target,
@@ -82,6 +90,7 @@ async function bootstrap(): Promise<void> {
82
90
  },
83
91
  });
84
92
  } catch (error) {
93
+ disposeMicrosoftStorePwa?.();
85
94
  renderBootstrapError(error, locale);
86
95
  console.error('[bootstrap]', error);
87
96
  }
@@ -0,0 +1,5 @@
1
+ declare module '#mpgd-platform-gateway' {
2
+ export function createBuildGateway(
3
+ runtime: import('./runtimeDetector').RuntimeConfig,
4
+ ): Promise<import('@mpgd/platform').PlatformGateway>;
5
+ }
@@ -0,0 +1,11 @@
1
+ import { createAitPlatformGateway } from '@mpgd/adapter-ait';
2
+ import type { PlatformGateway } from '@mpgd/platform';
3
+
4
+ import type { RuntimeConfig } from '../runtimeDetector';
5
+
6
+ export async function createBuildGateway(runtime: RuntimeConfig): Promise<PlatformGateway> {
7
+ return createAitPlatformGateway({
8
+ appVersion: runtime.appVersion,
9
+ buildId: runtime.buildId,
10
+ });
11
+ }
@@ -0,0 +1,12 @@
1
+ import { createAitPlatformGateway, createAitSandboxBridge } from '@mpgd/adapter-ait';
2
+ import type { PlatformGateway } from '@mpgd/platform';
3
+
4
+ import type { RuntimeConfig } from '../runtimeDetector';
5
+
6
+ export async function createBuildGateway(runtime: RuntimeConfig): Promise<PlatformGateway> {
7
+ return createAitPlatformGateway({
8
+ appVersion: runtime.appVersion,
9
+ buildId: runtime.buildId,
10
+ fallbackBridge: createAitSandboxBridge(),
11
+ });
12
+ }
@@ -0,0 +1,8 @@
1
+ import { createBrowserPlatformGateway } from '@mpgd/adapter-browser';
2
+ import type { PlatformGateway } from '@mpgd/platform';
3
+
4
+ import type { RuntimeConfig } from '../runtimeDetector';
5
+
6
+ export async function createBuildGateway(_runtime: RuntimeConfig): Promise<PlatformGateway> {
7
+ return createBrowserPlatformGateway();
8
+ }
@@ -0,0 +1,12 @@
1
+ import { createCapacitorPlatformGateway } from '@mpgd/adapter-capacitor';
2
+ import type { PlatformGateway } from '@mpgd/platform';
3
+
4
+ import type { RuntimeConfig } from '../runtimeDetector';
5
+
6
+ export async function createBuildGateway(runtime: RuntimeConfig): Promise<PlatformGateway> {
7
+ return createCapacitorPlatformGateway({
8
+ target: 'android',
9
+ appVersion: runtime.appVersion,
10
+ buildId: runtime.buildId,
11
+ });
12
+ }
@@ -0,0 +1,12 @@
1
+ import { createCapacitorPlatformGateway } from '@mpgd/adapter-capacitor';
2
+ import type { PlatformGateway } from '@mpgd/platform';
3
+
4
+ import type { RuntimeConfig } from '../runtimeDetector';
5
+
6
+ export async function createBuildGateway(runtime: RuntimeConfig): Promise<PlatformGateway> {
7
+ return createCapacitorPlatformGateway({
8
+ target: 'ios',
9
+ appVersion: runtime.appVersion,
10
+ buildId: runtime.buildId,
11
+ });
12
+ }
@@ -0,0 +1,11 @@
1
+ import { createDevvitPlatformGateway } from '@mpgd/adapter-devvit';
2
+ import type { PlatformGateway } from '@mpgd/platform';
3
+
4
+ import type { RuntimeConfig } from '../runtimeDetector';
5
+
6
+ export async function createBuildGateway(runtime: RuntimeConfig): Promise<PlatformGateway> {
7
+ return createDevvitPlatformGateway({
8
+ appVersion: runtime.appVersion,
9
+ buildId: runtime.buildId,
10
+ });
11
+ }
@@ -0,0 +1,15 @@
1
+ import {
2
+ createDevvitPlatformGateway,
3
+ createDevvitSandboxBridge,
4
+ } from '@mpgd/adapter-devvit';
5
+ import type { PlatformGateway } from '@mpgd/platform';
6
+
7
+ import type { RuntimeConfig } from '../runtimeDetector';
8
+
9
+ export async function createBuildGateway(runtime: RuntimeConfig): Promise<PlatformGateway> {
10
+ return createDevvitPlatformGateway({
11
+ appVersion: runtime.appVersion,
12
+ buildId: runtime.buildId,
13
+ fallbackBridge: createDevvitSandboxBridge(),
14
+ });
15
+ }