@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.
- package/dist/game-acceptance.d.ts +65 -0
- package/dist/game-acceptance.js +245 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +404 -3
- package/dist/production-target-readiness.d.ts +8 -0
- package/dist/production-target-readiness.js +207 -0
- package/package.json +11 -11
- package/templates/phaser-game/README.md +71 -1
- package/templates/phaser-game/agent/acceptance.md +12 -6
- package/templates/phaser-game/agent/brief.md +3 -1
- package/templates/phaser-game/agent/game-manifest.json +7 -0
- package/templates/phaser-game/apps/target-devvit/README.md +30 -0
- package/templates/phaser-game/apps/target-devvit/devvit.json +7 -0
- package/templates/phaser-game/apps/target-devvit/package.json +8 -5
- package/templates/phaser-game/apps/target-devvit/src/server/postOperationStore.ts +9 -0
- package/templates/phaser-game/game.html +13 -0
- package/templates/phaser-game/gitignore +2 -0
- package/templates/phaser-game/index.html +2 -2
- package/templates/phaser-game/mpgd.game.json +8 -0
- package/templates/phaser-game/mpgd.targets.json +6 -0
- package/templates/phaser-game/package.json +7 -1
- package/templates/phaser-game/public/manifest.webmanifest +1 -0
- package/templates/phaser-game/src/entry.ts +7 -0
- package/templates/phaser-game/src/env.d.ts +3 -0
- package/templates/phaser-game/src/gameEntry.ts +3 -0
- package/templates/phaser-game/src/main.ts +11 -2
- package/templates/phaser-game/src/platform/buildGatewayModule.ts +5 -0
- package/templates/phaser-game/src/platform/buildGateways/ait.ts +11 -0
- package/templates/phaser-game/src/platform/buildGateways/aitSandbox.ts +12 -0
- package/templates/phaser-game/src/platform/buildGateways/browser.ts +8 -0
- package/templates/phaser-game/src/platform/buildGateways/capacitorAndroid.ts +12 -0
- package/templates/phaser-game/src/platform/buildGateways/capacitorIos.ts +12 -0
- package/templates/phaser-game/src/platform/buildGateways/reddit.ts +11 -0
- package/templates/phaser-game/src/platform/buildGateways/redditSandbox.ts +15 -0
- package/templates/phaser-game/src/platform/devvitEntrypoint.ts +57 -0
- package/templates/phaser-game/src/platform/devvitInlinePreview.css +75 -0
- package/templates/phaser-game/src/platform/gameServices.ts +18 -61
- package/templates/phaser-game/src/platform/installPlatform.ts +6 -50
- package/templates/phaser-game/src/platform/microsoftStorePwa.ts +99 -0
- package/templates/phaser-game/tools/run-game-acceptance.mjs +22 -0
- package/templates/phaser-game/vite.config.ts +106 -4
|
@@ -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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
8
|
+
import type { PlatformGateway } from '@mpgd/platform';
|
|
11
9
|
|
|
12
|
-
export type StarterBackendMode =
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
import.meta.env.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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
|
+
]);
|
|
@@ -1,11 +1,25 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
3
|
|
|
4
4
|
import ttsc from '@ttsc/unplugin/vite';
|
|
5
5
|
import { defineConfig } from 'vite';
|
|
6
6
|
|
|
7
|
+
interface RuntimePlatformTargetMetadata {
|
|
8
|
+
readonly kind: string;
|
|
9
|
+
readonly adapter: string;
|
|
10
|
+
readonly integrations?: Record<string, unknown>;
|
|
11
|
+
}
|
|
12
|
+
|
|
7
13
|
export default defineConfig(({ mode }) => {
|
|
8
14
|
const isProduction = mode === 'production';
|
|
15
|
+
const platformTarget = readRuntimePlatformTarget();
|
|
16
|
+
const appTarget = process.env.APP_TARGET ?? 'browser';
|
|
17
|
+
const isDevvitBuild = appTarget === 'reddit';
|
|
18
|
+
const buildGatewayModule = resolveBuildGatewayModule({
|
|
19
|
+
target: appTarget,
|
|
20
|
+
debug: !isProduction,
|
|
21
|
+
buildId: process.env.BUILD_ID ?? 'local',
|
|
22
|
+
});
|
|
9
23
|
|
|
10
24
|
return {
|
|
11
25
|
base: './',
|
|
@@ -16,11 +30,16 @@ export default defineConfig(({ mode }) => {
|
|
|
16
30
|
}),
|
|
17
31
|
],
|
|
18
32
|
resolve: {
|
|
19
|
-
alias:
|
|
33
|
+
alias: {
|
|
34
|
+
...createCatalogAliases(),
|
|
35
|
+
'#mpgd-platform-gateway': resolve(buildGatewayModule),
|
|
36
|
+
},
|
|
20
37
|
},
|
|
21
38
|
define: {
|
|
22
|
-
__APP_TARGET__: JSON.stringify(
|
|
39
|
+
__APP_TARGET__: JSON.stringify(appTarget),
|
|
23
40
|
__MPGD_CONFIG_TARGET__: JSON.stringify(process.env.MPGD_CONFIG_TARGET ?? ''),
|
|
41
|
+
__MPGD_PLATFORM_TARGET__:
|
|
42
|
+
platformTarget === undefined ? 'undefined' : JSON.stringify(platformTarget),
|
|
24
43
|
__APP_VERSION__: JSON.stringify(process.env.APP_VERSION ?? '0.0.0-dev'),
|
|
25
44
|
__BUILD_ID__: JSON.stringify(process.env.BUILD_ID ?? 'local'),
|
|
26
45
|
__SOURCE_GIT_SHA__: JSON.stringify(process.env.MPGD_SOURCE_GIT_SHA ?? 'uncommitted'),
|
|
@@ -33,8 +52,16 @@ export default defineConfig(({ mode }) => {
|
|
|
33
52
|
assetsDir: 'assets',
|
|
34
53
|
emptyOutDir: true,
|
|
35
54
|
rolldownOptions: {
|
|
55
|
+
...(isDevvitBuild
|
|
56
|
+
? {
|
|
57
|
+
input: {
|
|
58
|
+
preview: resolve('index.html'),
|
|
59
|
+
game: resolve('game.html'),
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
: {}),
|
|
36
63
|
output: {
|
|
37
|
-
entryFileNames: 'assets/game.js',
|
|
64
|
+
entryFileNames: isDevvitBuild ? 'assets/[name].js' : 'assets/game.js',
|
|
38
65
|
chunkFileNames: 'assets/[name].js',
|
|
39
66
|
assetFileNames: 'assets/[name][extname]',
|
|
40
67
|
},
|
|
@@ -43,6 +70,29 @@ export default defineConfig(({ mode }) => {
|
|
|
43
70
|
};
|
|
44
71
|
});
|
|
45
72
|
|
|
73
|
+
export function resolveBuildGatewayModule(input: {
|
|
74
|
+
readonly target: string;
|
|
75
|
+
readonly debug: boolean;
|
|
76
|
+
readonly buildId: string;
|
|
77
|
+
}): string {
|
|
78
|
+
switch (input.target) {
|
|
79
|
+
case 'android':
|
|
80
|
+
return 'src/platform/buildGateways/capacitorAndroid.ts';
|
|
81
|
+
case 'ios':
|
|
82
|
+
return 'src/platform/buildGateways/capacitorIos.ts';
|
|
83
|
+
case 'ait':
|
|
84
|
+
return input.debug
|
|
85
|
+
? 'src/platform/buildGateways/aitSandbox.ts'
|
|
86
|
+
: 'src/platform/buildGateways/ait.ts';
|
|
87
|
+
case 'reddit':
|
|
88
|
+
return input.debug && input.buildId === 'devvit-sandbox'
|
|
89
|
+
? 'src/platform/buildGateways/redditSandbox.ts'
|
|
90
|
+
: 'src/platform/buildGateways/reddit.ts';
|
|
91
|
+
default:
|
|
92
|
+
return 'src/platform/buildGateways/browser.ts';
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
46
96
|
function createCatalogAliases(): Record<string, string> {
|
|
47
97
|
const productCatalogFile = readConfiguredPath(process.env.MPGD_PRODUCT_CATALOG_FILE);
|
|
48
98
|
const adPlacementsFile = readConfiguredPath(process.env.MPGD_AD_PLACEMENTS_FILE);
|
|
@@ -68,6 +118,58 @@ function readConfiguredPath(value: string | undefined): string | undefined {
|
|
|
68
118
|
return normalized === undefined || normalized.length === 0 ? undefined : normalized;
|
|
69
119
|
}
|
|
70
120
|
|
|
121
|
+
function readRuntimePlatformTarget(): RuntimePlatformTargetMetadata | undefined {
|
|
122
|
+
const targetsFile = readConfiguredPath(process.env.MPGD_PLATFORM_TARGETS_FILE);
|
|
123
|
+
const configTarget = readConfiguredPath(process.env.MPGD_CONFIG_TARGET);
|
|
124
|
+
|
|
125
|
+
if (targetsFile === undefined || configTarget === undefined) {
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const resolvedTargetsFile = resolve(targetsFile);
|
|
130
|
+
let parsed: unknown;
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
parsed = JSON.parse(readFileSync(resolvedTargetsFile, 'utf8'));
|
|
134
|
+
} catch (error) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
`Failed to read or parse MPGD_PLATFORM_TARGETS_FILE at ${resolvedTargetsFile}: ${formatError(error)}`,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (!isRecord(parsed) || !isRecord(parsed.targets)) {
|
|
141
|
+
throw new Error('MPGD_PLATFORM_TARGETS_FILE must contain a targets object.');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const target = parsed.targets[configTarget];
|
|
145
|
+
|
|
146
|
+
if (!isRecord(target)) {
|
|
147
|
+
throw new Error(`Missing platform target metadata for ${configTarget}.`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (typeof target.kind !== 'string' || typeof target.adapter !== 'string') {
|
|
151
|
+
throw new Error(`Platform target ${configTarget} must define kind and adapter.`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (target.integrations !== undefined && !isRecord(target.integrations)) {
|
|
155
|
+
throw new Error(`Platform target ${configTarget} integrations must be an object.`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
kind: target.kind,
|
|
160
|
+
adapter: target.adapter,
|
|
161
|
+
...(target.integrations === undefined ? {} : { integrations: target.integrations }),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function isRecord(input: unknown): input is Record<string, unknown> {
|
|
166
|
+
return typeof input === 'object' && input !== null && !Array.isArray(input);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function formatError(error: unknown): string {
|
|
170
|
+
return error instanceof Error ? error.message : String(error);
|
|
171
|
+
}
|
|
172
|
+
|
|
71
173
|
function resolveCatalogPath(path: string, pairedPath: string): string {
|
|
72
174
|
return resolve(resolveCatalogBaseDir(path, pairedPath), path);
|
|
73
175
|
}
|