@mpgd/cli 0.6.0 → 0.8.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 (51) 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 +398 -32
  5. package/dist/production-target-readiness.d.ts +8 -0
  6. package/dist/production-target-readiness.js +207 -0
  7. package/package.json +15 -15
  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-cloudflare-pages/package.json +3 -3
  13. package/templates/phaser-game/apps/target-devvit/README.md +70 -4
  14. package/templates/phaser-game/apps/target-devvit/devvit.json +8 -1
  15. package/templates/phaser-game/apps/target-devvit/package.json +15 -20
  16. package/templates/phaser-game/apps/target-devvit/src/client/game.html +13 -0
  17. package/templates/phaser-game/apps/target-devvit/src/client/game.ts +3 -0
  18. package/templates/phaser-game/apps/target-devvit/src/client/index.html +13 -0
  19. package/templates/phaser-game/apps/target-devvit/src/client/preview.ts +3 -0
  20. package/templates/phaser-game/apps/target-devvit/src/server/index.ts +228 -116
  21. package/templates/phaser-game/apps/target-devvit/src/server/postOperationStore.ts +9 -0
  22. package/templates/phaser-game/apps/target-devvit/tsconfig.json +7 -2
  23. package/templates/phaser-game/apps/target-devvit/vite.config.ts +41 -0
  24. package/templates/phaser-game/game.html +13 -0
  25. package/templates/phaser-game/gitignore +2 -0
  26. package/templates/phaser-game/index.html +2 -2
  27. package/templates/phaser-game/mpgd.game.json +8 -0
  28. package/templates/phaser-game/mpgd.targets.json +7 -0
  29. package/templates/phaser-game/package.json +10 -4
  30. package/templates/phaser-game/public/manifest.webmanifest +1 -0
  31. package/templates/phaser-game/src/entry.ts +7 -0
  32. package/templates/phaser-game/src/env.d.ts +3 -0
  33. package/templates/phaser-game/src/gameEntry.ts +3 -0
  34. package/templates/phaser-game/src/main.ts +11 -2
  35. package/templates/phaser-game/src/platform/buildGatewayModule.ts +5 -0
  36. package/templates/phaser-game/src/platform/buildGateways/ait.ts +11 -0
  37. package/templates/phaser-game/src/platform/buildGateways/aitSandbox.ts +12 -0
  38. package/templates/phaser-game/src/platform/buildGateways/browser.ts +8 -0
  39. package/templates/phaser-game/src/platform/buildGateways/capacitorAndroid.ts +12 -0
  40. package/templates/phaser-game/src/platform/buildGateways/capacitorIos.ts +12 -0
  41. package/templates/phaser-game/src/platform/buildGateways/reddit.ts +11 -0
  42. package/templates/phaser-game/src/platform/buildGateways/redditSandbox.ts +15 -0
  43. package/templates/phaser-game/src/platform/devvitEntrypoint.ts +57 -0
  44. package/templates/phaser-game/src/platform/devvitInlinePreview.css +75 -0
  45. package/templates/phaser-game/src/platform/gameServices.ts +18 -61
  46. package/templates/phaser-game/src/platform/installPlatform.ts +6 -50
  47. package/templates/phaser-game/src/platform/microsoftStorePwa.ts +99 -0
  48. package/templates/phaser-game/tools/run-game-acceptance.mjs +22 -0
  49. package/templates/phaser-game/vite.config.ts +24 -73
  50. package/templates/phaser-game/vite.shared.ts +217 -0
  51. package/templates/phaser-game/apps/target-devvit/vite.server.config.ts +0 -37
@@ -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
+ }
@@ -0,0 +1,57 @@
1
+ import {
2
+ requestDevvitExpandedMode,
3
+ startDevvitWebSurface,
4
+ } from '@mpgd/adapter-devvit/web';
5
+
6
+ await startDevvitWebSurface({
7
+ async mountInlinePreview() {
8
+ await import('./devvitInlinePreview.css');
9
+ renderInlinePreview();
10
+ },
11
+ async loadExpandedGame() {
12
+ await import('../main');
13
+ },
14
+ onModeUnavailable(error) {
15
+ if (!(error instanceof ReferenceError)) {
16
+ console.warn('[devvit] web view mode unavailable; loading the game surface.', error);
17
+ }
18
+ },
19
+ });
20
+
21
+ function renderInlinePreview(): void {
22
+ const preview = document.createElement('main');
23
+ preview.className = 'devvit-preview';
24
+
25
+ const eyebrow = document.createElement('p');
26
+ eyebrow.className = 'devvit-preview__eyebrow';
27
+ eyebrow.textContent = 'Ready to play';
28
+
29
+ const title = document.createElement('h1');
30
+ title.textContent = '__GAME_TITLE__';
31
+
32
+ const description = document.createElement('p');
33
+ description.className = 'devvit-preview__description';
34
+ description.textContent = 'Open the expanded view to start the game.';
35
+
36
+ const button = document.createElement('button');
37
+ button.className = 'devvit-preview__button';
38
+ button.type = 'button';
39
+ button.textContent = 'Play';
40
+ button.addEventListener('click', async (event) => {
41
+ try {
42
+ await requestDevvitExpandedMode(event, 'game');
43
+ } catch (error) {
44
+ console.error('[devvit] expanded game surface request failed.', error);
45
+ }
46
+ });
47
+
48
+ preview.append(eyebrow, title, description, button);
49
+ const body = document.body;
50
+
51
+ if (body === null) {
52
+ throw new Error('Devvit inline preview requires a document body.');
53
+ }
54
+
55
+ body.classList.add('devvit-preview-host');
56
+ body.replaceChildren(preview);
57
+ }
@@ -0,0 +1,75 @@
1
+ body.devvit-preview-host {
2
+ min-width: 320px;
3
+ min-height: 100vh;
4
+ margin: 0;
5
+ background: #0f172a;
6
+ }
7
+
8
+ .devvit-preview,
9
+ .devvit-preview * {
10
+ box-sizing: border-box;
11
+ }
12
+
13
+ .devvit-preview {
14
+ min-height: 100vh;
15
+ display: grid;
16
+ place-content: center;
17
+ justify-items: start;
18
+ gap: 0.75rem;
19
+ padding: clamp(1.25rem, 5vw, 3rem);
20
+ background:
21
+ radial-gradient(circle at 85% 15%, rgb(56 189 248 / 0.2), transparent 38%),
22
+ linear-gradient(145deg, #0f172a, #172554);
23
+ color: #f8fafc;
24
+ font-family: Inter, ui-sans-serif, system-ui, sans-serif;
25
+ }
26
+
27
+ .devvit-preview__eyebrow,
28
+ .devvit-preview__description,
29
+ .devvit-preview h1 {
30
+ margin: 0;
31
+ }
32
+
33
+ .devvit-preview__eyebrow {
34
+ color: #7dd3fc;
35
+ font-size: 0.75rem;
36
+ font-weight: 700;
37
+ letter-spacing: 0.12em;
38
+ text-transform: uppercase;
39
+ }
40
+
41
+ .devvit-preview h1 {
42
+ max-width: 18ch;
43
+ font-size: clamp(2rem, 9vw, 4.5rem);
44
+ line-height: 0.95;
45
+ }
46
+
47
+ .devvit-preview__description {
48
+ max-width: 36rem;
49
+ color: #cbd5e1;
50
+ line-height: 1.5;
51
+ }
52
+
53
+ .devvit-preview__button {
54
+ min-height: 2.75rem;
55
+ margin-top: 0.5rem;
56
+ padding: 0.65rem 1.25rem;
57
+ border: 0;
58
+ border-radius: 999px;
59
+ color: #082f49;
60
+ background: #7dd3fc;
61
+ font: inherit;
62
+ font-weight: 800;
63
+ cursor: pointer;
64
+ }
65
+
66
+ .devvit-preview__button:focus-visible {
67
+ outline: 3px solid #f8fafc;
68
+ outline-offset: 3px;
69
+ }
70
+
71
+ @media (hover: hover) {
72
+ .devvit-preview__button:hover {
73
+ filter: brightness(1.1);
74
+ }
75
+ }
@@ -1,70 +1,27 @@
1
1
  import {
2
- createGameServicesClient,
3
- createGameServicesFetchBackendTransport,
4
- createGameServicesHttpBackendApi,
5
- createGameServicesOrpcBackendApi,
6
- createGameServicesOrpcClient,
7
- type GameServicesClient,
8
- type GameServicesStoreTarget,
2
+ createGameServicesRuntime,
3
+ resolveGameServicesAuthorityMode,
4
+ resolveGameServicesTransport,
5
+ type GameServicesRuntime,
6
+ type GameServicesRuntimeMode,
9
7
  } from '@mpgd/game-services';
10
- import type { PlatformGateway, PlatformTarget } from '@mpgd/platform';
8
+ import type { PlatformGateway } from '@mpgd/platform';
11
9
 
12
- export type StarterBackendMode = 'disabled' | 'http' | 'orpc';
13
-
14
- export interface StarterGameServices {
15
- readonly mode: StarterBackendMode;
16
- readonly baseUrl?: string;
17
- readonly target?: GameServicesStoreTarget;
18
- readonly client?: GameServicesClient;
19
- }
10
+ export type StarterBackendMode = GameServicesRuntimeMode;
11
+ export type StarterGameServices = GameServicesRuntime;
20
12
 
21
13
  export function createStarterGameServices(input: {
22
14
  readonly gateway: PlatformGateway;
23
15
  readonly playerId: string;
24
16
  }): StarterGameServices {
25
- const baseUrl = import.meta.env.VITE_MPGD_GAME_SERVICES_URL;
26
- const requestedMode = import.meta.env.VITE_MPGD_GAME_SERVICES_TRANSPORT;
27
- const storeTarget = readStoreTarget(
28
- import.meta.env.VITE_MPGD_GAME_SERVICES_TARGET ?? input.gateway.target,
29
- );
30
-
31
- if (baseUrl === undefined || baseUrl.length === 0 || storeTarget === null) {
32
- return {
33
- mode: 'disabled',
34
- };
35
- }
36
-
37
- const mode = requestedMode === 'orpc' ? 'orpc' : 'http';
38
- const backend =
39
- mode === 'orpc'
40
- ? createGameServicesOrpcBackendApi(
41
- createGameServicesOrpcClient({
42
- url: baseUrl,
43
- }),
44
- )
45
- : createGameServicesHttpBackendApi({
46
- transport: createGameServicesFetchBackendTransport({
47
- baseUrl,
48
- }),
49
- });
50
-
51
- return {
52
- mode,
53
- baseUrl,
54
- target: storeTarget,
55
- client: createGameServicesClient({
56
- gateway: input.gateway,
57
- backend,
58
- playerId: input.playerId,
59
- target: storeTarget,
60
- }),
61
- };
62
- }
63
-
64
- function readStoreTarget(target: PlatformTarget | string): GameServicesStoreTarget | null {
65
- if (target === 'android' || target === 'ios' || target === 'ait') {
66
- return target;
67
- }
68
-
69
- return null;
17
+ return createGameServicesRuntime({
18
+ gateway: input.gateway,
19
+ playerId: input.playerId,
20
+ authorityMode: resolveGameServicesAuthorityMode(import.meta.env.MODE),
21
+ target: import.meta.env.VITE_MPGD_GAME_SERVICES_TARGET ?? input.gateway.target,
22
+ ...(import.meta.env.VITE_MPGD_GAME_SERVICES_URL === undefined
23
+ ? {}
24
+ : { baseUrl: import.meta.env.VITE_MPGD_GAME_SERVICES_URL }),
25
+ transport: resolveGameServicesTransport(import.meta.env.VITE_MPGD_GAME_SERVICES_TRANSPORT),
26
+ });
70
27
  }
@@ -1,7 +1,8 @@
1
+ import { createBuildGateway } from '#mpgd-platform-gateway';
2
+
1
3
  import type { AdPlacements, ProductCatalog } from '@mpgd/catalog';
2
4
  import productCatalogJson from '@mpgd/catalog/catalog.json';
3
5
  import adPlacementsJson from '@mpgd/catalog/placements.json';
4
- import type { PlatformGateway } from '@mpgd/platform';
5
6
  import {
6
7
  createEffectiveTargetConfig,
7
8
  getTargetConfig,
@@ -14,7 +15,6 @@ import targetConfigMatrixJson from '@mpgd/target-config/targets.json';
14
15
 
15
16
  import type { RuntimeConfig } from './runtimeDetector';
16
17
 
17
- const devvitSandboxBuildId = 'devvit-sandbox';
18
18
  const targetConfigMatrix = targetConfigMatrixJson as TargetConfigMatrix;
19
19
  const productCatalog = productCatalogJson as ProductCatalog;
20
20
  const adPlacements = adPlacementsJson as AdPlacements;
@@ -27,50 +27,7 @@ const adPlacementTypes = new Map<string, 'rewarded' | 'interstitial'>(
27
27
  );
28
28
 
29
29
  export async function installPlatform(runtime: RuntimeConfig): Promise<TargetConfiguredGateway> {
30
- let gateway: PlatformGateway;
31
-
32
- switch (runtime.target) {
33
- case 'android':
34
- case 'ios': {
35
- const { createCapacitorPlatformGateway } = await import('@mpgd/adapter-capacitor');
36
- gateway = createCapacitorPlatformGateway({
37
- target: runtime.target,
38
- appVersion: runtime.appVersion,
39
- buildId: runtime.buildId,
40
- });
41
- break;
42
- }
43
-
44
- case 'ait': {
45
- const { createAitPlatformGateway, createAitSandboxBridge } = await import(
46
- '@mpgd/adapter-ait'
47
- );
48
- gateway = createAitPlatformGateway({
49
- appVersion: runtime.appVersion,
50
- buildId: runtime.buildId,
51
- ...(runtime.debug ? { fallbackBridge: createAitSandboxBridge() } : {}),
52
- });
53
- break;
54
- }
55
-
56
- case 'reddit': {
57
- const { createDevvitPlatformGateway, createDevvitSandboxBridge } = await import(
58
- '@mpgd/adapter-devvit'
59
- );
60
- gateway = createDevvitPlatformGateway({
61
- appVersion: runtime.appVersion,
62
- buildId: runtime.buildId,
63
- ...(shouldUseDevvitSandbox(runtime) ? { fallbackBridge: createDevvitSandboxBridge() } : {}),
64
- });
65
- break;
66
- }
67
-
68
- default: {
69
- const { createBrowserPlatformGateway } = await import('@mpgd/adapter-browser');
70
- gateway = createBrowserPlatformGateway();
71
- }
72
- }
73
-
30
+ const gateway = await createBuildGateway(runtime);
74
31
  const configTarget = runtime.configTarget || targetConfigKeyForPlatform(runtime.target);
75
32
  const targetConfig = getTargetConfig(targetConfigMatrix, configTarget);
76
33
  const effectiveConfig = createEffectiveTargetConfig({
@@ -79,6 +36,9 @@ export async function installPlatform(runtime: RuntimeConfig): Promise<TargetCon
79
36
  config: targetConfig,
80
37
  catalog: productCatalog,
81
38
  adPlacements,
39
+ ...(__MPGD_PLATFORM_TARGET__ === undefined
40
+ ? {}
41
+ : { platformTarget: __MPGD_PLATFORM_TARGET__ }),
82
42
  });
83
43
 
84
44
  return withTargetAvailability(gateway, targetConfig, {
@@ -90,7 +50,3 @@ export async function installPlatform(runtime: RuntimeConfig): Promise<TargetCon
90
50
  },
91
51
  });
92
52
  }
93
-
94
- function shouldUseDevvitSandbox(runtime: RuntimeConfig): boolean {
95
- return runtime.debug && runtime.buildId === devvitSandboxBuildId;
96
- }
@@ -0,0 +1,99 @@
1
+ export const microsoftStorePwaUpdateReadyEvent = 'mpgd:pwa-update-ready';
2
+
3
+ export interface MicrosoftStorePwaUpdateReadyDetail {
4
+ readonly registration: ServiceWorkerRegistration;
5
+ }
6
+
7
+ export function shouldInstallMicrosoftStorePwa(input: {
8
+ readonly configTarget: string;
9
+ readonly debug: boolean;
10
+ }): boolean {
11
+ return input.configTarget === 'microsoft-store' && !input.debug;
12
+ }
13
+
14
+ export function installMicrosoftStorePwa(input: {
15
+ readonly configTarget: string;
16
+ readonly debug: boolean;
17
+ readonly onUpdateReady?: (detail: MicrosoftStorePwaUpdateReadyDetail) => void;
18
+ readonly onRegistrationError?: (error: unknown) => void;
19
+ }): () => void {
20
+ if (
21
+ !shouldInstallMicrosoftStorePwa(input)
22
+ || typeof navigator === 'undefined'
23
+ || !('serviceWorker' in navigator)
24
+ || typeof document === 'undefined'
25
+ ) {
26
+ return () => {};
27
+ }
28
+
29
+ let disposed = false;
30
+ let registration: ServiceWorkerRegistration | null = null;
31
+ let installingWorker: ServiceWorker | null = null;
32
+ const announceWaitingUpdate = () => {
33
+ if (
34
+ disposed
35
+ || registration === null
36
+ || registration.waiting === null
37
+ || navigator.serviceWorker.controller === null
38
+ ) {
39
+ return;
40
+ }
41
+
42
+ const detail = { registration } satisfies MicrosoftStorePwaUpdateReadyDetail;
43
+
44
+ if (input.onUpdateReady !== undefined) {
45
+ input.onUpdateReady(detail);
46
+ return;
47
+ }
48
+
49
+ globalThis.dispatchEvent(new CustomEvent(microsoftStorePwaUpdateReadyEvent, { detail }));
50
+ };
51
+ const handleInstallingStateChange = () => {
52
+ if (installingWorker?.state === 'installed') {
53
+ announceWaitingUpdate();
54
+ }
55
+ };
56
+ const handleUpdateFound = () => {
57
+ installingWorker?.removeEventListener('statechange', handleInstallingStateChange);
58
+ installingWorker = registration?.installing ?? null;
59
+ installingWorker?.addEventListener('statechange', handleInstallingStateChange);
60
+ };
61
+ const register = async () => {
62
+ try {
63
+ registration = await navigator.serviceWorker.register('./service-worker.js', {
64
+ scope: './',
65
+ updateViaCache: 'none',
66
+ });
67
+
68
+ if (disposed) {
69
+ return;
70
+ }
71
+
72
+ registration.addEventListener('updatefound', handleUpdateFound);
73
+ handleUpdateFound();
74
+ announceWaitingUpdate();
75
+ } catch (error) {
76
+ if (input.onRegistrationError !== undefined) {
77
+ input.onRegistrationError(error);
78
+ } else {
79
+ console.warn('[mpgd] Microsoft Store service worker registration failed.', error);
80
+ }
81
+ }
82
+ };
83
+ const handleWindowLoad = () => {
84
+ void register();
85
+ };
86
+
87
+ if (document.readyState === 'complete') {
88
+ void register();
89
+ } else {
90
+ globalThis.addEventListener('load', handleWindowLoad, { once: true });
91
+ }
92
+
93
+ return () => {
94
+ disposed = true;
95
+ globalThis.removeEventListener('load', handleWindowLoad);
96
+ installingWorker?.removeEventListener('statechange', handleInstallingStateChange);
97
+ registration?.removeEventListener('updatefound', handleUpdateFound);
98
+ };
99
+ }
@@ -0,0 +1,22 @@
1
+ import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+
4
+ import { runMpgdCli } from '@mpgd/cli';
5
+
6
+ const gameRoot = fileURLToPath(new URL('../', import.meta.url));
7
+ const configuredKitPath = process.env.MPGD_KIT_PATH;
8
+ const kitPath = path.resolve(
9
+ gameRoot,
10
+ configuredKitPath === undefined || configuredKitPath.length === 0
11
+ ? '__DEFAULT_KIT_PATH__'
12
+ : configuredKitPath,
13
+ );
14
+
15
+ await runMpgdCli([
16
+ 'game',
17
+ 'accept',
18
+ gameRoot,
19
+ '--kit-path',
20
+ kitPath,
21
+ ...process.argv.slice(2),
22
+ ]);