@mpgd/cli 0.28.1 → 0.29.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/cloudflare-pages-static.d.ts +53 -0
- package/dist/cloudflare-pages-static.js +236 -0
- package/dist/hosted-pwa-deployment.d.ts +42 -0
- package/dist/hosted-pwa-deployment.js +1087 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +212 -1
- package/dist/microsoft-store-pwa-release.d.ts +58 -0
- package/dist/microsoft-store-pwa-release.js +343 -0
- package/package.json +5 -5
- package/templates/phaser-game/agent/game-manifest.json +4 -0
- package/templates/phaser-game/mpgd.game.json +3 -0
- package/templates/phaser-game/src/entry.ts +10 -0
- package/templates/phaser-game/src/gameEntry.ts +10 -0
- package/templates/phaser-game/src/platform/textSelection.ts +121 -0
- package/templates/phaser-game/vite.shared.ts +14 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mpgd/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.0",
|
|
4
4
|
"description": "Gunshi CLI for mpgd-kit starter generation and target workflow orchestration.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -52,19 +52,19 @@
|
|
|
52
52
|
"@types/node": "^24.0.0",
|
|
53
53
|
"ttsc": "0.18.4",
|
|
54
54
|
"typescript": "7.0.2",
|
|
55
|
+
"@mpgd/adapter-browser": "0.7.3",
|
|
55
56
|
"@mpgd/adapter-ait": "0.12.1",
|
|
56
57
|
"@mpgd/adapter-capacitor": "0.4.10",
|
|
57
58
|
"@mpgd/adapter-devvit": "0.9.6",
|
|
58
59
|
"@mpgd/adapter-verse8": "0.3.4",
|
|
59
|
-
"@mpgd/adapter-browser": "0.7.3",
|
|
60
60
|
"@mpgd/analytics": "0.3.12",
|
|
61
61
|
"@mpgd/bridge": "0.9.0",
|
|
62
62
|
"@mpgd/catalog": "0.7.2",
|
|
63
|
+
"@mpgd/i18n": "0.6.1",
|
|
63
64
|
"@mpgd/game-services": "0.14.0",
|
|
64
|
-
"@mpgd/i18n": "0.6.0",
|
|
65
65
|
"@mpgd/phaser-assets": "0.4.1",
|
|
66
|
-
"@mpgd/
|
|
67
|
-
"@mpgd/
|
|
66
|
+
"@mpgd/platform": "0.12.0",
|
|
67
|
+
"@mpgd/target-config": "0.14.1"
|
|
68
68
|
},
|
|
69
69
|
"main": "./dist/index.js",
|
|
70
70
|
"types": "./dist/index.d.ts",
|
|
@@ -1,6 +1,16 @@
|
|
|
1
|
+
import gameProjectConfig from '../mpgd.game.json';
|
|
2
|
+
|
|
1
3
|
import { renderEntryFailure } from './runtime/renderEntryFailure';
|
|
4
|
+
import {
|
|
5
|
+
installTextSelectionPolicy,
|
|
6
|
+
resolveTextSelectionMode,
|
|
7
|
+
} from './platform/textSelection';
|
|
2
8
|
|
|
3
9
|
try {
|
|
10
|
+
installTextSelectionPolicy(
|
|
11
|
+
resolveTextSelectionMode((gameProjectConfig as { readonly ui?: unknown }).ui),
|
|
12
|
+
);
|
|
13
|
+
|
|
4
14
|
if (__APP_TARGET__ === 'reddit') {
|
|
5
15
|
await import('./platform/devvitEntrypoint');
|
|
6
16
|
} else {
|
|
@@ -1,6 +1,16 @@
|
|
|
1
|
+
import gameProjectConfig from '../mpgd.game.json';
|
|
2
|
+
|
|
1
3
|
import { renderEntryFailure } from './runtime/renderEntryFailure';
|
|
4
|
+
import {
|
|
5
|
+
installTextSelectionPolicy,
|
|
6
|
+
resolveTextSelectionMode,
|
|
7
|
+
} from './platform/textSelection';
|
|
2
8
|
|
|
3
9
|
try {
|
|
10
|
+
installTextSelectionPolicy(
|
|
11
|
+
resolveTextSelectionMode((gameProjectConfig as { readonly ui?: unknown }).ui),
|
|
12
|
+
);
|
|
13
|
+
|
|
4
14
|
await import('./main');
|
|
5
15
|
} catch (error) {
|
|
6
16
|
renderEntryFailure(error);
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Game-wide text selection policy.
|
|
3
|
+
*
|
|
4
|
+
* Games render tap targets, not documents, so text selection is disabled
|
|
5
|
+
* globally by default and re-enabled only for input fields and elements that
|
|
6
|
+
* explicitly opt in through {@link markSelectable}.
|
|
7
|
+
*
|
|
8
|
+
* Under the disabled policy the stylesheet layers, in ascending precedence:
|
|
9
|
+
* default suppression, editable contenteditable hosts (any value except
|
|
10
|
+
* explicit `false`, descendants included), explicit non-editable islands, and
|
|
11
|
+
* finally form fields plus the {@link selectableElementClassName} opt-in.
|
|
12
|
+
* All selectors are :where() normalized, so game CSS can still override the
|
|
13
|
+
* policy for bespoke elements.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export type TextSelectionMode = 'disabled' | 'enabled';
|
|
17
|
+
|
|
18
|
+
/** Class that re-enables text selection on an element and its descendants. */
|
|
19
|
+
export const selectableElementClassName = 'mpgd-selectable';
|
|
20
|
+
|
|
21
|
+
const textSelectionStylesheetId = 'mpgd-text-selection-policy';
|
|
22
|
+
|
|
23
|
+
export interface SelectableElement {
|
|
24
|
+
readonly classList: {
|
|
25
|
+
add(...tokens: string[]): void;
|
|
26
|
+
remove(...tokens: string[]): void;
|
|
27
|
+
contains(token: string): boolean;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Parse the `ui.textSelection` game setting, defaulting to disabled. */
|
|
32
|
+
export function resolveTextSelectionMode(uiConfig: unknown): TextSelectionMode {
|
|
33
|
+
if (uiConfig === undefined) {
|
|
34
|
+
return 'disabled';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (typeof uiConfig !== 'object' || uiConfig === null || Array.isArray(uiConfig)) {
|
|
38
|
+
throw new Error('mpgd.game.json ui must be an object when present.');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
for (const key of Object.keys(uiConfig)) {
|
|
42
|
+
if (key !== 'textSelection') {
|
|
43
|
+
throw new Error(`mpgd.game.json ui.${key} is not supported.`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const textSelection = (uiConfig as Record<string, unknown>).textSelection;
|
|
48
|
+
|
|
49
|
+
if (textSelection === undefined) {
|
|
50
|
+
return 'disabled';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (textSelection !== 'disabled' && textSelection !== 'enabled') {
|
|
54
|
+
throw new Error("mpgd.game.json ui.textSelection must be 'disabled' or 'enabled'.");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return textSelection;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Build the stylesheet that enforces the selection policy. */
|
|
61
|
+
export function buildTextSelectionStylesheet(mode: TextSelectionMode): string {
|
|
62
|
+
if (mode === 'enabled') {
|
|
63
|
+
return '/* mpgd: text selection enabled; browser default applies. */\n';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Every selector is wrapped in :where() so all rules share zero specificity
|
|
67
|
+
// and source order defines the precedence:
|
|
68
|
+
// default suppression < editable hosts < explicit non-editable islands
|
|
69
|
+
// < form fields and the selectable opt-in utility.
|
|
70
|
+
return [
|
|
71
|
+
'*, *::before, *::after {',
|
|
72
|
+
' -webkit-user-select: none;',
|
|
73
|
+
' user-select: none;',
|
|
74
|
+
' -webkit-touch-callout: none;',
|
|
75
|
+
'}',
|
|
76
|
+
":where([contenteditable]:not([contenteditable='false'])),",
|
|
77
|
+
":where([contenteditable]:not([contenteditable='false'])) * {",
|
|
78
|
+
' -webkit-user-select: text;',
|
|
79
|
+
' user-select: text;',
|
|
80
|
+
' -webkit-touch-callout: default;',
|
|
81
|
+
'}',
|
|
82
|
+
":where([contenteditable='false']), :where([contenteditable='false']) * {",
|
|
83
|
+
' -webkit-user-select: none;',
|
|
84
|
+
' user-select: none;',
|
|
85
|
+
' -webkit-touch-callout: none;',
|
|
86
|
+
'}',
|
|
87
|
+
`:where(input, textarea, .${selectableElementClassName}),`,
|
|
88
|
+
`:where(input, textarea, .${selectableElementClassName}) * {`,
|
|
89
|
+
' -webkit-user-select: text;',
|
|
90
|
+
' user-select: text;',
|
|
91
|
+
' -webkit-touch-callout: default;',
|
|
92
|
+
'}',
|
|
93
|
+
].join('\n');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Install (or refresh) the global selection policy stylesheet. */
|
|
97
|
+
export function installTextSelectionPolicy(mode: TextSelectionMode): void {
|
|
98
|
+
if (typeof document === 'undefined') {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let stylesheet = document.getElementById(textSelectionStylesheetId);
|
|
103
|
+
|
|
104
|
+
if (stylesheet === null) {
|
|
105
|
+
stylesheet = document.createElement('style');
|
|
106
|
+
stylesheet.id = textSelectionStylesheetId;
|
|
107
|
+
document.head.append(stylesheet);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
stylesheet.textContent = buildTextSelectionStylesheet(mode);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Re-enable text selection for a specific UI element and its descendants. */
|
|
114
|
+
export function markSelectable(element: SelectableElement): void {
|
|
115
|
+
element.classList.add(selectableElementClassName);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Withdraw a previous {@link markSelectable} opt-in. */
|
|
119
|
+
export function unmarkSelectable(element: SelectableElement): void {
|
|
120
|
+
element.classList.remove(selectableElementClassName);
|
|
121
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
-
import { resolve } from 'node:path';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
3
4
|
|
|
4
5
|
import ttsc from '@ttsc/unplugin/vite';
|
|
5
6
|
import type { PluginOption } from 'vite';
|
|
@@ -8,6 +9,7 @@ import {
|
|
|
8
9
|
assertRuntimeTargetConfigMatrix,
|
|
9
10
|
type TargetConfigMatrix,
|
|
10
11
|
} from './vite.runtime-target-config';
|
|
12
|
+
import { resolveTextSelectionMode } from './src/platform/textSelection';
|
|
11
13
|
|
|
12
14
|
interface RuntimePlatformTargetMetadata {
|
|
13
15
|
readonly kind: string;
|
|
@@ -39,9 +41,20 @@ export interface GameViteSharedConfig<SharedPluginOption> {
|
|
|
39
41
|
readonly define: Record<string, string>;
|
|
40
42
|
}
|
|
41
43
|
|
|
44
|
+
/** Fail ordinary Vite builds when mpgd.game.json declares an invalid ui policy. */
|
|
45
|
+
function readGameTextSelectionPolicy(): 'disabled' | 'enabled' {
|
|
46
|
+
const gameRoot = dirname(fileURLToPath(import.meta.url));
|
|
47
|
+
const gameConfig = JSON.parse(
|
|
48
|
+
readFileSync(resolve(gameRoot, 'mpgd.game.json'), 'utf8'),
|
|
49
|
+
) as { readonly ui?: unknown };
|
|
50
|
+
|
|
51
|
+
return resolveTextSelectionMode(gameConfig.ui);
|
|
52
|
+
}
|
|
53
|
+
|
|
42
54
|
export function createGameViteSharedConfig<SharedPluginOption = PluginOption>(
|
|
43
55
|
input: CreateGameViteSharedConfigInput,
|
|
44
56
|
): GameViteSharedConfig<SharedPluginOption> {
|
|
57
|
+
readGameTextSelectionPolicy();
|
|
45
58
|
const isProduction = input.mode === 'production';
|
|
46
59
|
const appTarget = input.appTarget ?? process.env.APP_TARGET ?? 'browser';
|
|
47
60
|
const configTarget = input.configTarget ?? process.env.MPGD_CONFIG_TARGET ?? '';
|