@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.
- 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 +398 -32
- package/dist/production-target-readiness.d.ts +8 -0
- package/dist/production-target-readiness.js +207 -0
- package/package.json +15 -15
- 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-cloudflare-pages/package.json +3 -3
- package/templates/phaser-game/apps/target-devvit/README.md +70 -4
- package/templates/phaser-game/apps/target-devvit/devvit.json +8 -1
- package/templates/phaser-game/apps/target-devvit/package.json +15 -20
- package/templates/phaser-game/apps/target-devvit/src/client/game.html +13 -0
- package/templates/phaser-game/apps/target-devvit/src/client/game.ts +3 -0
- package/templates/phaser-game/apps/target-devvit/src/client/index.html +13 -0
- package/templates/phaser-game/apps/target-devvit/src/client/preview.ts +3 -0
- package/templates/phaser-game/apps/target-devvit/src/server/index.ts +228 -116
- package/templates/phaser-game/apps/target-devvit/src/server/postOperationStore.ts +9 -0
- package/templates/phaser-game/apps/target-devvit/tsconfig.json +7 -2
- package/templates/phaser-game/apps/target-devvit/vite.config.ts +41 -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 +7 -0
- package/templates/phaser-game/package.json +10 -4
- 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 +24 -73
- package/templates/phaser-game/vite.shared.ts +217 -0
- package/templates/phaser-game/apps/target-devvit/vite.server.config.ts +0 -37
|
@@ -1,40 +1,41 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
2
1
|
import { resolve } from 'node:path';
|
|
3
2
|
|
|
4
|
-
import ttsc from '@ttsc/unplugin/vite';
|
|
5
3
|
import { defineConfig } from 'vite';
|
|
6
4
|
|
|
5
|
+
import { createGameViteSharedConfig } from './vite.shared';
|
|
6
|
+
|
|
7
|
+
export { resolveBuildGatewayModule } from './vite.shared';
|
|
8
|
+
|
|
9
|
+
const gameRoot = process.cwd();
|
|
10
|
+
|
|
7
11
|
export default defineConfig(({ mode }) => {
|
|
8
|
-
const
|
|
12
|
+
const appTarget = process.env.APP_TARGET ?? 'browser';
|
|
13
|
+
const isDevvitBuild = appTarget === 'reddit';
|
|
9
14
|
|
|
10
15
|
return {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
],
|
|
18
|
-
resolve: {
|
|
19
|
-
alias: createCatalogAliases(),
|
|
20
|
-
},
|
|
21
|
-
define: {
|
|
22
|
-
__APP_TARGET__: JSON.stringify(process.env.APP_TARGET ?? 'browser'),
|
|
23
|
-
__MPGD_CONFIG_TARGET__: JSON.stringify(process.env.MPGD_CONFIG_TARGET ?? ''),
|
|
24
|
-
__APP_VERSION__: JSON.stringify(process.env.APP_VERSION ?? '0.0.0-dev'),
|
|
25
|
-
__BUILD_ID__: JSON.stringify(process.env.BUILD_ID ?? 'local'),
|
|
26
|
-
__SOURCE_GIT_SHA__: JSON.stringify(process.env.MPGD_SOURCE_GIT_SHA ?? 'uncommitted'),
|
|
27
|
-
__DEBUG_BUILD__: JSON.stringify(!isProduction),
|
|
28
|
-
},
|
|
16
|
+
...createGameViteSharedConfig({
|
|
17
|
+
appTarget,
|
|
18
|
+
gameRoot,
|
|
19
|
+
mode,
|
|
20
|
+
project: resolve(gameRoot, 'tsconfig.json'),
|
|
21
|
+
}),
|
|
29
22
|
build: {
|
|
30
23
|
target: 'es2022',
|
|
31
|
-
sourcemap:
|
|
24
|
+
sourcemap: mode !== 'production',
|
|
32
25
|
outDir: 'dist',
|
|
33
26
|
assetsDir: 'assets',
|
|
34
27
|
emptyOutDir: true,
|
|
35
28
|
rolldownOptions: {
|
|
29
|
+
...(isDevvitBuild
|
|
30
|
+
? {
|
|
31
|
+
input: {
|
|
32
|
+
preview: resolve(gameRoot, 'index.html'),
|
|
33
|
+
game: resolve(gameRoot, 'game.html'),
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
: {}),
|
|
36
37
|
output: {
|
|
37
|
-
entryFileNames: 'assets/game.js',
|
|
38
|
+
entryFileNames: isDevvitBuild ? 'assets/[name].js' : 'assets/game.js',
|
|
38
39
|
chunkFileNames: 'assets/[name].js',
|
|
39
40
|
assetFileNames: 'assets/[name][extname]',
|
|
40
41
|
},
|
|
@@ -42,53 +43,3 @@ export default defineConfig(({ mode }) => {
|
|
|
42
43
|
},
|
|
43
44
|
};
|
|
44
45
|
});
|
|
45
|
-
|
|
46
|
-
function createCatalogAliases(): Record<string, string> {
|
|
47
|
-
const productCatalogFile = readConfiguredPath(process.env.MPGD_PRODUCT_CATALOG_FILE);
|
|
48
|
-
const adPlacementsFile = readConfiguredPath(process.env.MPGD_AD_PLACEMENTS_FILE);
|
|
49
|
-
|
|
50
|
-
if ((productCatalogFile === undefined) !== (adPlacementsFile === undefined)) {
|
|
51
|
-
throw new Error(
|
|
52
|
-
'MPGD_PRODUCT_CATALOG_FILE and MPGD_AD_PLACEMENTS_FILE must be configured together.',
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (productCatalogFile === undefined || adPlacementsFile === undefined) {
|
|
57
|
-
return {};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return {
|
|
61
|
-
'@mpgd/catalog/catalog.json': resolveCatalogPath(productCatalogFile, adPlacementsFile),
|
|
62
|
-
'@mpgd/catalog/placements.json': resolveCatalogPath(adPlacementsFile, productCatalogFile),
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function readConfiguredPath(value: string | undefined): string | undefined {
|
|
67
|
-
const normalized = value?.trim();
|
|
68
|
-
return normalized === undefined || normalized.length === 0 ? undefined : normalized;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function resolveCatalogPath(path: string, pairedPath: string): string {
|
|
72
|
-
return resolve(resolveCatalogBaseDir(path, pairedPath), path);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function resolveCatalogBaseDir(path: string, pairedPath: string): string {
|
|
76
|
-
const fallbackBaseDir = process.cwd();
|
|
77
|
-
const candidates = [
|
|
78
|
-
fallbackBaseDir,
|
|
79
|
-
readConfiguredPath(process.env.INIT_CWD),
|
|
80
|
-
readConfiguredPath(process.env.PWD),
|
|
81
|
-
];
|
|
82
|
-
|
|
83
|
-
for (const candidate of candidates) {
|
|
84
|
-
if (
|
|
85
|
-
candidate !== undefined
|
|
86
|
-
&& existsSync(resolve(candidate, path))
|
|
87
|
-
&& existsSync(resolve(candidate, pairedPath))
|
|
88
|
-
) {
|
|
89
|
-
return candidate;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return fallbackBaseDir;
|
|
94
|
-
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import ttsc from '@ttsc/unplugin/vite';
|
|
5
|
+
import type { UserConfig } from 'vite';
|
|
6
|
+
|
|
7
|
+
interface RuntimePlatformTargetMetadata {
|
|
8
|
+
readonly kind: string;
|
|
9
|
+
readonly adapter: string;
|
|
10
|
+
readonly integrations?: Record<string, unknown>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const devvitSandboxBuildId = 'devvit-sandbox';
|
|
14
|
+
|
|
15
|
+
export interface CreateGameViteSharedConfigInput {
|
|
16
|
+
readonly appTarget?: string;
|
|
17
|
+
readonly configTarget?: string;
|
|
18
|
+
readonly gameRoot: string;
|
|
19
|
+
readonly mode: string;
|
|
20
|
+
readonly platformTargetsFile?: string;
|
|
21
|
+
readonly project: string;
|
|
22
|
+
readonly productCatalogFile?: string;
|
|
23
|
+
readonly adPlacementsFile?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function createGameViteSharedConfig(
|
|
27
|
+
input: CreateGameViteSharedConfigInput,
|
|
28
|
+
): UserConfig {
|
|
29
|
+
const isProduction = input.mode === 'production';
|
|
30
|
+
const appTarget = input.appTarget ?? process.env.APP_TARGET ?? 'browser';
|
|
31
|
+
const configTarget = input.configTarget ?? process.env.MPGD_CONFIG_TARGET ?? '';
|
|
32
|
+
const platformTarget = readRuntimePlatformTarget(
|
|
33
|
+
input.platformTargetsFile ?? process.env.MPGD_PLATFORM_TARGETS_FILE,
|
|
34
|
+
configTarget,
|
|
35
|
+
);
|
|
36
|
+
const buildGatewayModule = resolveBuildGatewayModule({
|
|
37
|
+
target: appTarget,
|
|
38
|
+
debug: !isProduction,
|
|
39
|
+
buildId: process.env.BUILD_ID ?? 'local',
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
base: './',
|
|
44
|
+
plugins: [
|
|
45
|
+
ttsc({
|
|
46
|
+
project: input.project,
|
|
47
|
+
plugins: false,
|
|
48
|
+
}),
|
|
49
|
+
],
|
|
50
|
+
resolve: {
|
|
51
|
+
alias: {
|
|
52
|
+
...createCatalogAliases({
|
|
53
|
+
gameRoot: input.gameRoot,
|
|
54
|
+
...(input.productCatalogFile === undefined
|
|
55
|
+
? {}
|
|
56
|
+
: { productCatalogFile: input.productCatalogFile }),
|
|
57
|
+
...(input.adPlacementsFile === undefined
|
|
58
|
+
? {}
|
|
59
|
+
: { adPlacementsFile: input.adPlacementsFile }),
|
|
60
|
+
}),
|
|
61
|
+
'#mpgd-platform-gateway': resolve(input.gameRoot, buildGatewayModule),
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
define: {
|
|
65
|
+
__APP_TARGET__: JSON.stringify(appTarget),
|
|
66
|
+
__MPGD_CONFIG_TARGET__: JSON.stringify(configTarget),
|
|
67
|
+
__MPGD_PLATFORM_TARGET__:
|
|
68
|
+
platformTarget === undefined ? 'undefined' : JSON.stringify(platformTarget),
|
|
69
|
+
__APP_VERSION__: JSON.stringify(process.env.APP_VERSION ?? '0.0.0-dev'),
|
|
70
|
+
__BUILD_ID__: JSON.stringify(process.env.BUILD_ID ?? 'local'),
|
|
71
|
+
__SOURCE_GIT_SHA__: JSON.stringify(process.env.MPGD_SOURCE_GIT_SHA ?? 'uncommitted'),
|
|
72
|
+
__DEBUG_BUILD__: JSON.stringify(!isProduction),
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function resolveBuildGatewayModule(input: {
|
|
78
|
+
readonly target: string;
|
|
79
|
+
readonly debug: boolean;
|
|
80
|
+
readonly buildId: string;
|
|
81
|
+
}): string {
|
|
82
|
+
switch (input.target) {
|
|
83
|
+
case 'android':
|
|
84
|
+
return 'src/platform/buildGateways/capacitorAndroid.ts';
|
|
85
|
+
case 'ios':
|
|
86
|
+
return 'src/platform/buildGateways/capacitorIos.ts';
|
|
87
|
+
case 'ait':
|
|
88
|
+
return input.debug
|
|
89
|
+
? 'src/platform/buildGateways/aitSandbox.ts'
|
|
90
|
+
: 'src/platform/buildGateways/ait.ts';
|
|
91
|
+
case 'reddit':
|
|
92
|
+
return input.debug && input.buildId === devvitSandboxBuildId
|
|
93
|
+
? 'src/platform/buildGateways/redditSandbox.ts'
|
|
94
|
+
: 'src/platform/buildGateways/reddit.ts';
|
|
95
|
+
default:
|
|
96
|
+
return 'src/platform/buildGateways/browser.ts';
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function createCatalogAliases(input: {
|
|
101
|
+
readonly gameRoot: string;
|
|
102
|
+
readonly productCatalogFile?: string;
|
|
103
|
+
readonly adPlacementsFile?: string;
|
|
104
|
+
}): Record<string, string> {
|
|
105
|
+
const productCatalogFile = readConfiguredPath(
|
|
106
|
+
input.productCatalogFile ?? process.env.MPGD_PRODUCT_CATALOG_FILE,
|
|
107
|
+
);
|
|
108
|
+
const adPlacementsFile = readConfiguredPath(
|
|
109
|
+
input.adPlacementsFile ?? process.env.MPGD_AD_PLACEMENTS_FILE,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
if ((productCatalogFile === undefined) !== (adPlacementsFile === undefined)) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
'productCatalogFile and adPlacementsFile '
|
|
115
|
+
+ '(MPGD_PRODUCT_CATALOG_FILE / MPGD_AD_PLACEMENTS_FILE) must be configured together.',
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (productCatalogFile === undefined || adPlacementsFile === undefined) {
|
|
120
|
+
return {};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const catalogBaseDir = resolveCatalogBaseDir(
|
|
124
|
+
productCatalogFile,
|
|
125
|
+
adPlacementsFile,
|
|
126
|
+
input.gameRoot,
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
'@mpgd/catalog/catalog.json': resolve(catalogBaseDir, productCatalogFile),
|
|
131
|
+
'@mpgd/catalog/placements.json': resolve(catalogBaseDir, adPlacementsFile),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function readConfiguredPath(value: string | undefined): string | undefined {
|
|
136
|
+
const normalized = value?.trim();
|
|
137
|
+
return normalized === undefined || normalized.length === 0 ? undefined : normalized;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function readRuntimePlatformTarget(
|
|
141
|
+
targetsFileInput: string | undefined,
|
|
142
|
+
configTarget: string,
|
|
143
|
+
): RuntimePlatformTargetMetadata | undefined {
|
|
144
|
+
const targetsFile = readConfiguredPath(targetsFileInput);
|
|
145
|
+
|
|
146
|
+
if (targetsFile === undefined || configTarget.length === 0) {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const resolvedTargetsFile = resolve(targetsFile);
|
|
151
|
+
let parsed: unknown;
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
parsed = JSON.parse(readFileSync(resolvedTargetsFile, 'utf8'));
|
|
155
|
+
} catch (error) {
|
|
156
|
+
throw new Error(
|
|
157
|
+
`Failed to read or parse MPGD_PLATFORM_TARGETS_FILE at ${resolvedTargetsFile}: ${formatError(error)}`,
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (!isRecord(parsed) || !isRecord(parsed.targets)) {
|
|
162
|
+
throw new Error('MPGD_PLATFORM_TARGETS_FILE must contain a targets object.');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const target = parsed.targets[configTarget];
|
|
166
|
+
|
|
167
|
+
if (!isRecord(target)) {
|
|
168
|
+
throw new Error(`Missing platform target metadata for ${configTarget}.`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (typeof target.kind !== 'string' || typeof target.adapter !== 'string') {
|
|
172
|
+
throw new Error(`Platform target ${configTarget} must define kind and adapter.`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (target.integrations !== undefined && !isRecord(target.integrations)) {
|
|
176
|
+
throw new Error(`Platform target ${configTarget} integrations must be an object.`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
kind: target.kind,
|
|
181
|
+
adapter: target.adapter,
|
|
182
|
+
...(target.integrations === undefined ? {} : { integrations: target.integrations }),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function isRecord(input: unknown): input is Record<string, unknown> {
|
|
187
|
+
return typeof input === 'object' && input !== null && !Array.isArray(input);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function formatError(error: unknown): string {
|
|
191
|
+
return error instanceof Error ? error.message : String(error);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function resolveCatalogBaseDir(path: string, pairedPath: string, gameRoot: string): string {
|
|
195
|
+
const candidates = [
|
|
196
|
+
gameRoot,
|
|
197
|
+
process.cwd(),
|
|
198
|
+
readConfiguredPath(process.env.INIT_CWD),
|
|
199
|
+
readConfiguredPath(process.env.PWD),
|
|
200
|
+
];
|
|
201
|
+
|
|
202
|
+
for (const candidate of candidates) {
|
|
203
|
+
if (
|
|
204
|
+
candidate !== undefined
|
|
205
|
+
&& existsSync(resolve(candidate, path))
|
|
206
|
+
&& existsSync(resolve(candidate, pairedPath))
|
|
207
|
+
) {
|
|
208
|
+
return candidate;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
console.warn(
|
|
213
|
+
`Could not locate catalog files (${path}, ${pairedPath}) in any expected directory; `
|
|
214
|
+
+ `falling back to game root ${gameRoot}.`,
|
|
215
|
+
);
|
|
216
|
+
return gameRoot;
|
|
217
|
+
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { builtinModules } from 'node:module';
|
|
2
|
-
|
|
3
|
-
import ttsc from '@ttsc/unplugin/vite';
|
|
4
|
-
import { defineConfig } from 'vite';
|
|
5
|
-
|
|
6
|
-
const externalBuiltins = [
|
|
7
|
-
...builtinModules,
|
|
8
|
-
...builtinModules.map((moduleName) => `node:${moduleName}`),
|
|
9
|
-
];
|
|
10
|
-
|
|
11
|
-
export default defineConfig({
|
|
12
|
-
plugins: [
|
|
13
|
-
ttsc({
|
|
14
|
-
project: 'tsconfig.json',
|
|
15
|
-
plugins: false,
|
|
16
|
-
}),
|
|
17
|
-
],
|
|
18
|
-
ssr: {
|
|
19
|
-
noExternal: true,
|
|
20
|
-
},
|
|
21
|
-
build: {
|
|
22
|
-
ssr: 'src/server/index.ts',
|
|
23
|
-
outDir: 'dist/server',
|
|
24
|
-
target: 'node22',
|
|
25
|
-
minify: 'esbuild',
|
|
26
|
-
sourcemap: false,
|
|
27
|
-
emptyOutDir: true,
|
|
28
|
-
rollupOptions: {
|
|
29
|
-
external: externalBuiltins,
|
|
30
|
-
output: {
|
|
31
|
-
format: 'cjs',
|
|
32
|
-
entryFileNames: 'index.cjs',
|
|
33
|
-
codeSplitting: false,
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
});
|