@maoyugames/phaser-framework 1.0.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/LICENSE +21 -0
- package/README.md +140 -0
- package/dist/BasePlatform-BCOkvvDW.d.ts +47 -0
- package/dist/chunk-H5CUVYCN.js +229 -0
- package/dist/chunk-II3JM4R3.js +9 -0
- package/dist/chunk-PKBMQBKP.js +5 -0
- package/dist/cli/index.d.ts +20 -0
- package/dist/cli/index.js +1219 -0
- package/dist/index.d.ts +1778 -0
- package/dist/index.js +3790 -0
- package/dist/platform/capacitor.d.ts +34 -0
- package/dist/platform/capacitor.js +145 -0
- package/dist/platform/facebook.d.ts +33 -0
- package/dist/platform/facebook.js +256 -0
- package/dist/platform/tiktok.d.ts +30 -0
- package/dist/platform/tiktok.js +170 -0
- package/dist/platform/web.d.ts +22 -0
- package/dist/platform/web.js +67 -0
- package/dist/platform/wechat.d.ts +37 -0
- package/dist/platform/wechat.js +436 -0
- package/dist/types-DtcRFbM0.d.ts +239 -0
- package/global.d.ts +8 -0
- package/package.json +83 -0
|
@@ -0,0 +1,1219 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import '../chunk-PKBMQBKP.js';
|
|
3
|
+
import pc4 from 'picocolors';
|
|
4
|
+
import { resolve, join, relative, basename, dirname } from 'path';
|
|
5
|
+
import { randomBytes } from 'crypto';
|
|
6
|
+
import fse4 from 'fs-extra';
|
|
7
|
+
import { createRequire } from 'module';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
import { existsSync, readFileSync } from 'fs';
|
|
10
|
+
import { spawnSync } from 'child_process';
|
|
11
|
+
|
|
12
|
+
// src/cli/platforms-meta.ts
|
|
13
|
+
var ALL_PLATFORMS = ["web", "tiktok", "wechat", "facebook", "capacitor"];
|
|
14
|
+
var ISOLATION_RULES = {
|
|
15
|
+
web: {
|
|
16
|
+
// 标准 H5,不该含任何小游戏 SDK 适配
|
|
17
|
+
forbidden: [
|
|
18
|
+
"TTMinis.game",
|
|
19
|
+
"FBInstant.player",
|
|
20
|
+
"FBInstant.initializeAsync",
|
|
21
|
+
"wx.connectSocket",
|
|
22
|
+
"wx.request",
|
|
23
|
+
"wx.setStorageSync",
|
|
24
|
+
"wx.requestMidasPayment"
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
tiktok: {
|
|
28
|
+
// TikTok 包不得含微信 / Facebook
|
|
29
|
+
forbidden: [
|
|
30
|
+
"FBInstant.player",
|
|
31
|
+
"FBInstant.initializeAsync",
|
|
32
|
+
"wx.connectSocket",
|
|
33
|
+
"wx.request",
|
|
34
|
+
"wx.setStorageSync",
|
|
35
|
+
"wx.requestMidasPayment"
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
wechat: {
|
|
39
|
+
// 微信包不得含 TikTok / Facebook
|
|
40
|
+
forbidden: ["TTMinis.game", "FBInstant.player", "FBInstant.initializeAsync", "FBInstant.payments"]
|
|
41
|
+
},
|
|
42
|
+
facebook: {
|
|
43
|
+
// Facebook 包不得含 TikTok / 微信
|
|
44
|
+
forbidden: ["TTMinis.game", "wx.connectSocket", "wx.request", "wx.setStorageSync", "wx.requestMidasPayment"]
|
|
45
|
+
},
|
|
46
|
+
capacitor: {
|
|
47
|
+
// 原生 App 包不得含任何小游戏 SDK 适配
|
|
48
|
+
forbidden: [
|
|
49
|
+
"TTMinis.game",
|
|
50
|
+
"FBInstant.player",
|
|
51
|
+
"FBInstant.initializeAsync",
|
|
52
|
+
"wx.connectSocket",
|
|
53
|
+
"wx.request",
|
|
54
|
+
"wx.setStorageSync",
|
|
55
|
+
"wx.requestMidasPayment"
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
var MB = 1024 * 1024;
|
|
60
|
+
var SIZE_LIMITS = {
|
|
61
|
+
// TikTok 整包硬上限 50MB
|
|
62
|
+
tiktok: { totalHardLimit: 50 * MB },
|
|
63
|
+
// 微信主包建议 ≤ 4MB(分包另算,给提示而非硬失败)
|
|
64
|
+
wechat: { mainBundleSoftLimit: 4 * MB }
|
|
65
|
+
};
|
|
66
|
+
function formatBytes(bytes) {
|
|
67
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
68
|
+
if (bytes < MB) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
69
|
+
return `${(bytes / MB).toFixed(2)} MB`;
|
|
70
|
+
}
|
|
71
|
+
function isPlatform(value) {
|
|
72
|
+
return ALL_PLATFORMS.includes(value);
|
|
73
|
+
}
|
|
74
|
+
var FRAMEWORK_PKG = "@maoyugames/phaser-framework";
|
|
75
|
+
function findProjectRoot(startDir = process.cwd()) {
|
|
76
|
+
let dir = resolve(startDir);
|
|
77
|
+
while (true) {
|
|
78
|
+
const pkgPath = join(dir, "package.json");
|
|
79
|
+
if (existsSync(pkgPath)) {
|
|
80
|
+
try {
|
|
81
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
82
|
+
const deps = {
|
|
83
|
+
...pkg.dependencies,
|
|
84
|
+
...pkg.devDependencies,
|
|
85
|
+
...pkg.peerDependencies
|
|
86
|
+
};
|
|
87
|
+
if (deps[FRAMEWORK_PKG]) {
|
|
88
|
+
return dir;
|
|
89
|
+
}
|
|
90
|
+
} catch {
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const parent = dirname(dir);
|
|
94
|
+
if (parent === dir) break;
|
|
95
|
+
dir = parent;
|
|
96
|
+
}
|
|
97
|
+
throw new Error(
|
|
98
|
+
`\u672A\u627E\u5230\u7528\u6237\u9879\u76EE\u6839:\u4ECE ${startDir} \u5411\u4E0A\u672A\u627E\u5230\u4F9D\u8D56 ${FRAMEWORK_PKG} \u7684 package.json\u3002
|
|
99
|
+
\u8BF7\u5728\u542B\u8BE5\u4F9D\u8D56\u7684\u9879\u76EE\u76EE\u5F55\u5185\u8FD0\u884C pf\u3002`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
function readFrameworkVersion() {
|
|
103
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
104
|
+
while (true) {
|
|
105
|
+
const pkgPath = join(dir, "package.json");
|
|
106
|
+
if (existsSync(pkgPath)) {
|
|
107
|
+
try {
|
|
108
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
109
|
+
if (pkg.name === FRAMEWORK_PKG && pkg.version) {
|
|
110
|
+
return pkg.version;
|
|
111
|
+
}
|
|
112
|
+
} catch {
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const parent = dirname(dir);
|
|
116
|
+
if (parent === dir) break;
|
|
117
|
+
dir = parent;
|
|
118
|
+
}
|
|
119
|
+
return "0.0.0";
|
|
120
|
+
}
|
|
121
|
+
function resolveProject(startDir = process.cwd()) {
|
|
122
|
+
const root = findProjectRoot(startDir);
|
|
123
|
+
const gameConfigPath = join(root, "game.config.ts");
|
|
124
|
+
const platformConfigPath = join(root, "platform.config.ts");
|
|
125
|
+
if (!existsSync(gameConfigPath)) {
|
|
126
|
+
throw new Error(`\u7F3A\u5C11\u4E1A\u52A1\u914D\u7F6E:${gameConfigPath}
|
|
127
|
+
\u8BF7\u5728\u9879\u76EE\u6839\u521B\u5EFA game.config.ts \u5E76\u5BFC\u51FA gameConfig\u3002`);
|
|
128
|
+
}
|
|
129
|
+
if (!existsSync(platformConfigPath)) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
`\u7F3A\u5C11\u5E73\u53F0\u914D\u7F6E:${platformConfigPath}
|
|
132
|
+
\u8BF7\u5728\u9879\u76EE\u6839\u521B\u5EFA platform.config.ts \u5E76 export default { ... } satisfies PlatformConfig\u3002`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
root,
|
|
137
|
+
gameConfigPath,
|
|
138
|
+
platformConfigPath,
|
|
139
|
+
frameworkVersion: readFrameworkVersion()
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
async function loadViteFromRoot(root) {
|
|
143
|
+
const requireFromRoot = createRequire(join(root, "package.json"));
|
|
144
|
+
const viteEntry = requireFromRoot.resolve("vite");
|
|
145
|
+
const mod = await import(pathToFileUrl(viteEntry));
|
|
146
|
+
const ns = mod && typeof mod.createServer === "function" ? mod : mod.default ?? mod;
|
|
147
|
+
if (typeof ns.build !== "function") {
|
|
148
|
+
throw new Error("\u65E0\u6CD5\u4ECE\u7528\u6237\u9879\u76EE\u52A0\u8F7D Vite(createServer/build \u4E0D\u53EF\u7528)\u3002\u8BF7\u786E\u8BA4\u5DF2\u5B89\u88C5 vite \u4F9D\u8D56\u3002");
|
|
149
|
+
}
|
|
150
|
+
return ns;
|
|
151
|
+
}
|
|
152
|
+
async function loadConfigModule(root, filePath) {
|
|
153
|
+
const vite = await loadViteFromRoot(root);
|
|
154
|
+
const server = await vite.createServer({
|
|
155
|
+
root,
|
|
156
|
+
// 仅用于 SSR 加载配置,不需要监听端口
|
|
157
|
+
server: { middlewareMode: true, hmr: false },
|
|
158
|
+
appType: "custom",
|
|
159
|
+
logLevel: "silent",
|
|
160
|
+
// 配置文件加载阶段不需要用户项目的 vite.config(若有也无妨,这里关闭以隔离副作用)
|
|
161
|
+
configFile: false
|
|
162
|
+
});
|
|
163
|
+
try {
|
|
164
|
+
const mod = await server.ssrLoadModule(filePath);
|
|
165
|
+
return mod;
|
|
166
|
+
} finally {
|
|
167
|
+
await server.close();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
async function loadPlatformConfig(ctx) {
|
|
171
|
+
const mod = await loadConfigModule(ctx.root, ctx.platformConfigPath);
|
|
172
|
+
const cfg = mod.default ?? mod;
|
|
173
|
+
if (!cfg || typeof cfg !== "object") {
|
|
174
|
+
throw new Error(`platform.config.ts \u672A\u9ED8\u8BA4\u5BFC\u51FA PlatformConfig \u5BF9\u8C61:${ctx.platformConfigPath}`);
|
|
175
|
+
}
|
|
176
|
+
return cfg;
|
|
177
|
+
}
|
|
178
|
+
function pathToFileUrl(p) {
|
|
179
|
+
const resolved = resolve(p);
|
|
180
|
+
const normalized = resolved.replace(/\\/g, "/");
|
|
181
|
+
return normalized.startsWith("/") ? `file://${normalized}` : `file:///${normalized}`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// src/cli/entry-template.ts
|
|
185
|
+
var PLATFORM_CLASS = {
|
|
186
|
+
web: "WebPlatform",
|
|
187
|
+
tiktok: "TikTokPlatform",
|
|
188
|
+
wechat: "WeChatPlatform",
|
|
189
|
+
facebook: "FacebookPlatform",
|
|
190
|
+
capacitor: "CapacitorPlatform"
|
|
191
|
+
};
|
|
192
|
+
function renderEntry(opts) {
|
|
193
|
+
const { platform, projectRoot } = opts;
|
|
194
|
+
const cls = PLATFORM_CLASS[platform];
|
|
195
|
+
const gameConfigImport = JSON.stringify(toImportPath(`${projectRoot}/game.config`));
|
|
196
|
+
return `/**
|
|
197
|
+
* pf \u81EA\u52A8\u751F\u6210\u7684\u4E34\u65F6\u5355\u5E73\u53F0\u5165\u53E3(${platform})\u3002
|
|
198
|
+
* \u7531 CLI \u5728\u6784\u5EFA/dev \u65F6\u751F\u6210\u4E8E node_modules/.cache/pf/,\u6784\u5EFA\u540E\u81EA\u52A8\u6E05\u7406,\u8BF7\u52FF\u624B\u6539\u3002
|
|
199
|
+
*/
|
|
200
|
+
import { startGame } from ${JSON.stringify(FRAMEWORK_PKG)};
|
|
201
|
+
import { ${cls} } from ${JSON.stringify(`${FRAMEWORK_PKG}/platform/${platform}`)};
|
|
202
|
+
import { gameConfig } from ${gameConfigImport};
|
|
203
|
+
import { scenes } from '@game/scenes';
|
|
204
|
+
import { setupGame } from '@game/setup';
|
|
205
|
+
|
|
206
|
+
void startGame({ platform: new ${cls}(), config: gameConfig, scenes, setup: setupGame });
|
|
207
|
+
`;
|
|
208
|
+
}
|
|
209
|
+
function toImportPath(p) {
|
|
210
|
+
return p.replace(/\\/g, "/");
|
|
211
|
+
}
|
|
212
|
+
function createAlias(projectRoot) {
|
|
213
|
+
return [
|
|
214
|
+
// @game(整词)→ src/game/index?(用户通常用 @game/xxx,这里整词兜底到 src/game 目录)
|
|
215
|
+
{ find: /^@game$/, replacement: resolve(projectRoot, "src/game") },
|
|
216
|
+
// @game/xxx → src/game/xxx
|
|
217
|
+
{ find: /^@game\//, replacement: resolve(projectRoot, "src/game") + "/" }
|
|
218
|
+
];
|
|
219
|
+
}
|
|
220
|
+
function createPlatformConfig(platform, opts) {
|
|
221
|
+
const { projectRoot, entryFile, frameworkVersion } = opts;
|
|
222
|
+
const dev = opts.dev ?? false;
|
|
223
|
+
const outDir = resolve(projectRoot, "dist", platform);
|
|
224
|
+
const publicDir = resolve(projectRoot, "src/game/public");
|
|
225
|
+
const base = {
|
|
226
|
+
root: opts.viteRoot,
|
|
227
|
+
// 资源用相对路径,适配 webview / file:// / 小游戏本地包加载;web 可被 opts.base 覆盖
|
|
228
|
+
base: opts.base ?? "./",
|
|
229
|
+
// 业务静态资源(配置表 JSON 等)整目录复制到产物根;wechat 在下面关闭并由 inject 手动拷
|
|
230
|
+
publicDir,
|
|
231
|
+
configFile: false,
|
|
232
|
+
logLevel: dev ? "info" : "warn",
|
|
233
|
+
define: {
|
|
234
|
+
__PLATFORM__: JSON.stringify(platform),
|
|
235
|
+
__DEV__: JSON.stringify(dev),
|
|
236
|
+
__FRAMEWORK_VERSION__: JSON.stringify(frameworkVersion)
|
|
237
|
+
},
|
|
238
|
+
resolve: {
|
|
239
|
+
alias: createAlias(projectRoot)
|
|
240
|
+
},
|
|
241
|
+
esbuild: {
|
|
242
|
+
// 兼容低端 webview / 小游戏 JS 引擎
|
|
243
|
+
target: "es2019"
|
|
244
|
+
},
|
|
245
|
+
envPrefix: ["VITE_"],
|
|
246
|
+
build: {
|
|
247
|
+
target: "es2019",
|
|
248
|
+
outDir,
|
|
249
|
+
emptyOutDir: !dev,
|
|
250
|
+
// 生产统一用 terser:压缩比稳定、可去 console/debugger,便于体积控制
|
|
251
|
+
minify: dev ? false : "terser",
|
|
252
|
+
terserOptions: {
|
|
253
|
+
compress: {
|
|
254
|
+
drop_console: !dev,
|
|
255
|
+
drop_debugger: !dev
|
|
256
|
+
},
|
|
257
|
+
format: {
|
|
258
|
+
comments: false
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
// 资源内联阈值:小图标内联减少请求,大资源走文件
|
|
262
|
+
assetsInlineLimit: 4096,
|
|
263
|
+
// phaser 较大,放宽 chunk 体积警告阈值(单位 KB)
|
|
264
|
+
chunkSizeWarningLimit: 4096,
|
|
265
|
+
rollupOptions: {
|
|
266
|
+
input: entryFile
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
if (platform === "wechat") {
|
|
271
|
+
return applyWechatOverrides(base, entryFile, outDir);
|
|
272
|
+
}
|
|
273
|
+
return base;
|
|
274
|
+
}
|
|
275
|
+
function applyWechatOverrides(base, entryFile, outDir) {
|
|
276
|
+
return {
|
|
277
|
+
...base,
|
|
278
|
+
publicDir: false,
|
|
279
|
+
build: {
|
|
280
|
+
...base.build,
|
|
281
|
+
outDir,
|
|
282
|
+
emptyOutDir: base.build?.emptyOutDir,
|
|
283
|
+
lib: {
|
|
284
|
+
entry: entryFile,
|
|
285
|
+
// CommonJS,供 game.js 用 require 加载
|
|
286
|
+
formats: ["cjs"],
|
|
287
|
+
fileName: () => "game.bundle.js"
|
|
288
|
+
},
|
|
289
|
+
rollupOptions: {
|
|
290
|
+
// 不 external 任何依赖:phaser 等全部打进 bundle
|
|
291
|
+
external: [],
|
|
292
|
+
output: {
|
|
293
|
+
// 强制单 chunk,杜绝动态 import 拆包(小游戏主包加载单文件)
|
|
294
|
+
inlineDynamicImports: true,
|
|
295
|
+
manualChunks: void 0,
|
|
296
|
+
entryFileNames: "game.bundle.js"
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// src/cli/shells/html-util.ts
|
|
304
|
+
function escapeHtml(s) {
|
|
305
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
306
|
+
}
|
|
307
|
+
function escapeAttr(s) {
|
|
308
|
+
return s.replace(/&/g, "&").replace(/"/g, """);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// src/cli/shells/facebook.ts
|
|
312
|
+
var DEFAULT_FB_SDK_VERSION = "7.1";
|
|
313
|
+
function renderFacebookHtml(cfg, scriptSrc) {
|
|
314
|
+
const title = escapeHtml(cfg?.appName ?? "Facebook Instant Game");
|
|
315
|
+
const sdkVersion = (cfg?.fbSdkVersion ?? DEFAULT_FB_SDK_VERSION).trim();
|
|
316
|
+
const sdkSrc = `https://connect.facebook.net/en_US/fbinstant.${sdkVersion}.js`;
|
|
317
|
+
return `<!doctype html>
|
|
318
|
+
<html lang="en">
|
|
319
|
+
<head>
|
|
320
|
+
<meta charset="UTF-8" />
|
|
321
|
+
<meta
|
|
322
|
+
name="viewport"
|
|
323
|
+
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
|
|
324
|
+
/>
|
|
325
|
+
<title>${title}</title>
|
|
326
|
+
<style>
|
|
327
|
+
html,
|
|
328
|
+
body {
|
|
329
|
+
margin: 0;
|
|
330
|
+
padding: 0;
|
|
331
|
+
width: 100%;
|
|
332
|
+
height: 100%;
|
|
333
|
+
background: #000;
|
|
334
|
+
overflow: hidden;
|
|
335
|
+
box-sizing: border-box;
|
|
336
|
+
}
|
|
337
|
+
#game-root {
|
|
338
|
+
width: 100%;
|
|
339
|
+
height: 100%;
|
|
340
|
+
}
|
|
341
|
+
#game-root canvas {
|
|
342
|
+
display: block;
|
|
343
|
+
margin: 0 auto;
|
|
344
|
+
}
|
|
345
|
+
</style>
|
|
346
|
+
</head>
|
|
347
|
+
<body>
|
|
348
|
+
<!--
|
|
349
|
+
Facebook Instant Games SDK \u6CE8\u5165(\u7248\u672C\u6765\u81EA platform.config.facebook.fbSdkVersion)\u3002
|
|
350
|
+
\u5FC5\u987B\u5728\u6E38\u620F\u811A\u672C\u4E4B\u524D\u52A0\u8F7D,\u8FD0\u884C\u65F6\u901A\u8FC7\u5168\u5C40 FBInstant.* \u8C03\u7528\u6865\u63A5\u80FD\u529B\u3002
|
|
351
|
+
-->
|
|
352
|
+
<script src="${escapeAttr(sdkSrc)}"></script>
|
|
353
|
+
|
|
354
|
+
<!-- \u6E38\u620F\u753B\u5E03\u6302\u8F7D\u70B9 -->
|
|
355
|
+
<div id="game-root"></div>
|
|
356
|
+
<!-- Facebook \u5E73\u53F0\u5165\u53E3:\u4EC5\u9759\u6001 import Facebook \u9002\u914D\u5668 -->
|
|
357
|
+
<script type="module" src="${scriptSrc}"></script>
|
|
358
|
+
</body>
|
|
359
|
+
</html>
|
|
360
|
+
`;
|
|
361
|
+
}
|
|
362
|
+
function renderFacebookAppConfig(cfg) {
|
|
363
|
+
const orientation = (cfg?.orientation ?? "portrait").toUpperCase();
|
|
364
|
+
const obj = {
|
|
365
|
+
instant_games: {
|
|
366
|
+
platform_version: "RICH_GAMEPLAY",
|
|
367
|
+
orientation
|
|
368
|
+
},
|
|
369
|
+
app_id: cfg?.appId ?? "YOUR_FB_APP_ID",
|
|
370
|
+
app_name: cfg?.appName ?? "Phaser Instant Game"
|
|
371
|
+
};
|
|
372
|
+
return {
|
|
373
|
+
relPath: "fbapp-config.json",
|
|
374
|
+
content: JSON.stringify(obj, null, 2) + "\n"
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// src/cli/shells/capacitor.ts
|
|
379
|
+
function renderCapacitorHtml(cfg, scriptSrc) {
|
|
380
|
+
const title = escapeHtml(cfg?.appName ?? "Game App");
|
|
381
|
+
return `<!doctype html>
|
|
382
|
+
<html lang="zh-CN">
|
|
383
|
+
<head>
|
|
384
|
+
<meta charset="UTF-8" />
|
|
385
|
+
<meta
|
|
386
|
+
name="viewport"
|
|
387
|
+
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
|
|
388
|
+
/>
|
|
389
|
+
<title>${title}</title>
|
|
390
|
+
<style>
|
|
391
|
+
html,
|
|
392
|
+
body {
|
|
393
|
+
margin: 0;
|
|
394
|
+
padding: 0;
|
|
395
|
+
width: 100%;
|
|
396
|
+
height: 100%;
|
|
397
|
+
background: #000;
|
|
398
|
+
overflow: hidden;
|
|
399
|
+
/* \u539F\u751F App webview \u5B89\u5168\u533A\u9002\u914D */
|
|
400
|
+
padding: env(safe-area-inset-top) env(safe-area-inset-right)
|
|
401
|
+
env(safe-area-inset-bottom) env(safe-area-inset-left);
|
|
402
|
+
box-sizing: border-box;
|
|
403
|
+
}
|
|
404
|
+
#game-root {
|
|
405
|
+
width: 100%;
|
|
406
|
+
height: 100%;
|
|
407
|
+
}
|
|
408
|
+
#game-root canvas {
|
|
409
|
+
display: block;
|
|
410
|
+
margin: 0 auto;
|
|
411
|
+
}
|
|
412
|
+
</style>
|
|
413
|
+
</head>
|
|
414
|
+
<body>
|
|
415
|
+
<!--
|
|
416
|
+
Capacitor \u539F\u751F App(iOS + Android)\u5916\u58F3\u3002
|
|
417
|
+
Capacitor \u8FD0\u884C\u65F6\u7531\u539F\u751F\u5BB9\u5668\u5728 webview \u6CE8\u5165\u5168\u5C40 window.Capacitor \u4E0E\u5DF2\u5B89\u88C5\u63D2\u4EF6;
|
|
418
|
+
\u6B64\u5904\u65E0\u9700 <script> \u6CE8\u5165 SDK,\u9002\u914D\u5668\u8FD0\u884C\u65F6\u63A2\u6D4B Capacitor \u5BF9\u8C61,\u7F3A\u5931\u5219\u4F18\u96C5\u964D\u7EA7\u3002
|
|
419
|
+
-->
|
|
420
|
+
<div id="game-root"></div>
|
|
421
|
+
<!-- Capacitor \u5E73\u53F0\u5165\u53E3:\u4EC5\u9759\u6001 import Capacitor \u9002\u914D\u5668 -->
|
|
422
|
+
<script type="module" src="${scriptSrc}"></script>
|
|
423
|
+
</body>
|
|
424
|
+
</html>
|
|
425
|
+
`;
|
|
426
|
+
}
|
|
427
|
+
function renderCapacitorConfig(cfg) {
|
|
428
|
+
const appId = cfg?.appId ?? "com.example.app";
|
|
429
|
+
const appName = cfg?.appName ?? "App";
|
|
430
|
+
return `/**
|
|
431
|
+
* Capacitor \u914D\u7F6E(\u7531 pf \u4ECE platform.config.capacitor \u81EA\u52A8\u751F\u6210)\u3002
|
|
432
|
+
*
|
|
433
|
+
* - appId / appName \u6765\u81EA\u9879\u76EE\u6839 platform.config.ts \u7684 capacitor \u6BB5\u3002
|
|
434
|
+
* - webDir \u6307\u5411 Vite \u6784\u5EFA\u4EA7\u7269 dist/capacitor:\`pf build capacitor\` \u4EA7\u51FA\u540E,
|
|
435
|
+
* \`pf cap sync\` \u4F1A\u628A\u5B83\u540C\u6B65\u8FDB android / ios \u539F\u751F\u5DE5\u7A0B\u3002
|
|
436
|
+
*
|
|
437
|
+
* \u672C\u6587\u4EF6\u7531 CLI \u751F\u6210,\u624B\u6539\u4F1A\u5728\u4E0B\u6B21 build \u65F6\u88AB\u8986\u76D6;\u5982\u9700\u5B9A\u5236\u8BF7\u6539 platform.config.ts\u3002
|
|
438
|
+
*/
|
|
439
|
+
import type { CapacitorConfig } from '@capacitor/cli';
|
|
440
|
+
|
|
441
|
+
const config: CapacitorConfig = {
|
|
442
|
+
appId: ${JSON.stringify(appId)},
|
|
443
|
+
appName: ${JSON.stringify(appName)},
|
|
444
|
+
// \u76F8\u5BF9\u9879\u76EE\u6839:Vite \u628A capacitor \u4EA7\u7269\u8F93\u51FA\u5230 dist/capacitor
|
|
445
|
+
webDir: 'dist/capacitor',
|
|
446
|
+
server: {
|
|
447
|
+
androidScheme: 'https',
|
|
448
|
+
},
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
export default config;
|
|
452
|
+
`;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// src/cli/shells/wechat.ts
|
|
456
|
+
function renderGameJs() {
|
|
457
|
+
return `/**
|
|
458
|
+
* \u5FAE\u4FE1\u5C0F\u6E38\u620F\u5165\u53E3(\u4F4D\u4E8E dist/wechat/game.js,\u7531 pf \u751F\u6210)\u3002
|
|
459
|
+
*
|
|
460
|
+
* \u52A0\u8F7D\u987A\u5E8F\u81F3\u5173\u91CD\u8981:
|
|
461
|
+
* 1. \u5148 require('./weapp-adapter.js'):\u5FAE\u4FE1\u5B98\u65B9\u9002\u914D\u5668,\u5728\u65E0 DOM \u7684\u5C0F\u6E38\u620F\u8FD0\u884C\u65F6\u91CC
|
|
462
|
+
* \u6CE8\u5165 window / document / canvas / Image / localStorage \u7B49\u6D4F\u89C8\u5668 shim\u3002
|
|
463
|
+
* 2. \u518D require('./game.bundle.js'):Vite lib \u6A21\u5F0F\u6253\u51FA\u7684\u6E38\u620F\u4E3B bundle(CommonJS,
|
|
464
|
+
* \u4EC5\u542B\u5FAE\u4FE1\u9002\u914D\u5668)\u3002
|
|
465
|
+
*
|
|
466
|
+
* weapp-adapter.js / symbol.js \u7531 pf \u5728\u6784\u5EFA\u65F6\u4ECE\u7528\u6237\u9879\u76EE wechat vendor \u76EE\u5F55\u62F7\u5165;
|
|
467
|
+
* \u7F3A\u5931\u65F6 CLI \u4F1A\u9192\u76EE\u8B66\u544A(\u5FAE\u4FE1\u8FD0\u884C\u65F6\u627E\u4E0D\u5230\u8BE5\u6587\u4EF6\u4F1A\u542F\u52A8\u5373\u62A5\u9519)\u3002
|
|
468
|
+
*/
|
|
469
|
+
require('./weapp-adapter.js');
|
|
470
|
+
require('./game.bundle.js');
|
|
471
|
+
`;
|
|
472
|
+
}
|
|
473
|
+
function renderGameJson(cfg) {
|
|
474
|
+
const orientation = cfg?.orientation ?? "portrait";
|
|
475
|
+
const obj = {
|
|
476
|
+
deviceOrientation: orientation,
|
|
477
|
+
showStatusBar: false,
|
|
478
|
+
networkTimeout: {
|
|
479
|
+
request: 1e4,
|
|
480
|
+
connectSocket: 1e4,
|
|
481
|
+
uploadFile: 1e4,
|
|
482
|
+
downloadFile: 1e4
|
|
483
|
+
},
|
|
484
|
+
workerSize: 1,
|
|
485
|
+
subpackages: [],
|
|
486
|
+
plugins: {},
|
|
487
|
+
openDataContext: ""
|
|
488
|
+
};
|
|
489
|
+
return JSON.stringify(obj, null, 2) + "\n";
|
|
490
|
+
}
|
|
491
|
+
function renderProjectConfig(cfg) {
|
|
492
|
+
const obj = {
|
|
493
|
+
appid: cfg?.appid ?? "wxYOUR_APPID",
|
|
494
|
+
projectname: cfg?.projectname ?? "phaser-wechat-minigame",
|
|
495
|
+
compileType: "game",
|
|
496
|
+
setting: {
|
|
497
|
+
es6: false,
|
|
498
|
+
enhance: false,
|
|
499
|
+
postcss: false,
|
|
500
|
+
minified: false,
|
|
501
|
+
uglifyFileName: false,
|
|
502
|
+
uploadWithSourceMap: true,
|
|
503
|
+
checkSiteMap: false,
|
|
504
|
+
babelSetting: {
|
|
505
|
+
ignore: [],
|
|
506
|
+
disablePlugins: [],
|
|
507
|
+
outputPath: ""
|
|
508
|
+
}
|
|
509
|
+
},
|
|
510
|
+
compileLifecycle: {},
|
|
511
|
+
libVersion: "latest",
|
|
512
|
+
condition: {}
|
|
513
|
+
};
|
|
514
|
+
return JSON.stringify(obj, null, 2) + "\n";
|
|
515
|
+
}
|
|
516
|
+
function renderWeChatShells(cfg) {
|
|
517
|
+
return [
|
|
518
|
+
{ relPath: "game.js", content: renderGameJs() },
|
|
519
|
+
{ relPath: "game.json", content: renderGameJson(cfg) },
|
|
520
|
+
{ relPath: "project.config.json", content: renderProjectConfig(cfg) }
|
|
521
|
+
];
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// src/cli/inject.ts
|
|
525
|
+
function wechatVendorDirs(projectRoot) {
|
|
526
|
+
return [resolve(projectRoot, "wechat/vendor"), resolve(projectRoot, "platforms/wechat/vendor")];
|
|
527
|
+
}
|
|
528
|
+
function writeShellFiles(distDir, projectRoot, files) {
|
|
529
|
+
for (const f of files) {
|
|
530
|
+
const dst = join(distDir, f.relPath);
|
|
531
|
+
fse4.ensureDirSync(resolve(dst, ".."));
|
|
532
|
+
fse4.writeFileSync(dst, f.content, "utf-8");
|
|
533
|
+
console.log(pc4.green(` \u2713 ${f.relPath} \u2192 ${relative(projectRoot, dst)}`));
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
function injectShell(opts) {
|
|
537
|
+
const { platform, projectRoot, distDir, config } = opts;
|
|
538
|
+
switch (platform) {
|
|
539
|
+
case "web":
|
|
540
|
+
case "tiktok":
|
|
541
|
+
return true;
|
|
542
|
+
case "facebook": {
|
|
543
|
+
console.log(pc4.cyan(` \u25B6 [facebook] \u6CE8\u5165\u5916\u58F3\u6587\u4EF6`));
|
|
544
|
+
writeShellFiles(distDir, projectRoot, [renderFacebookAppConfig(config.facebook)]);
|
|
545
|
+
return true;
|
|
546
|
+
}
|
|
547
|
+
case "capacitor": {
|
|
548
|
+
console.log(pc4.cyan(` \u25B6 [capacitor] \u751F\u6210 capacitor.config.ts(\u9879\u76EE\u6839)`));
|
|
549
|
+
const capCfgPath = resolve(projectRoot, "capacitor.config.ts");
|
|
550
|
+
fse4.writeFileSync(capCfgPath, renderCapacitorConfig(config.capacitor), "utf-8");
|
|
551
|
+
console.log(pc4.green(` \u2713 capacitor.config.ts \u2192 ${relative(projectRoot, capCfgPath)}`));
|
|
552
|
+
return true;
|
|
553
|
+
}
|
|
554
|
+
case "wechat": {
|
|
555
|
+
console.log(pc4.cyan(` \u25B6 [wechat] \u6CE8\u5165\u5916\u58F3\u6587\u4EF6`));
|
|
556
|
+
writeShellFiles(distDir, projectRoot, renderWeChatShells(config.wechat));
|
|
557
|
+
copyPublicAssetsToWechat(projectRoot, distDir);
|
|
558
|
+
copyWechatVendor(projectRoot, distDir);
|
|
559
|
+
return true;
|
|
560
|
+
}
|
|
561
|
+
default:
|
|
562
|
+
return true;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
function copyPublicAssetsToWechat(projectRoot, distDir) {
|
|
566
|
+
const srcPublic = resolve(projectRoot, "src/game/public");
|
|
567
|
+
if (!fse4.existsSync(srcPublic)) return;
|
|
568
|
+
console.log(pc4.cyan(" \u25B6 [wechat] \u62F7\u8D1D\u4E1A\u52A1\u9759\u6001\u8D44\u6E90(src/game/public)"));
|
|
569
|
+
fse4.copySync(srcPublic, distDir);
|
|
570
|
+
console.log(pc4.green(" \u2713 src/game/public/* \u2192 dist/wechat/"));
|
|
571
|
+
}
|
|
572
|
+
function copyWechatVendor(projectRoot, distDir) {
|
|
573
|
+
console.log(pc4.cyan(" \u25B6 [wechat] \u68C0\u6D4B\u5FAE\u4FE1\u9002\u914D\u5668(vendor)"));
|
|
574
|
+
const dirs = wechatVendorDirs(projectRoot);
|
|
575
|
+
const adapter = dirs.map((d) => join(d, "weapp-adapter.js")).find((p) => fse4.existsSync(p));
|
|
576
|
+
if (adapter) {
|
|
577
|
+
fse4.copyFileSync(adapter, join(distDir, "weapp-adapter.js"));
|
|
578
|
+
console.log(pc4.green(" \u2713 weapp-adapter.js \u2192 dist/wechat/weapp-adapter.js"));
|
|
579
|
+
const vendorDir = resolve(adapter, "..");
|
|
580
|
+
const symbol = join(vendorDir, "symbol.js");
|
|
581
|
+
if (fse4.existsSync(symbol)) {
|
|
582
|
+
fse4.copyFileSync(symbol, join(distDir, "symbol.js"));
|
|
583
|
+
console.log(pc4.green(" \u2713 symbol.js \u2192 dist/wechat/symbol.js"));
|
|
584
|
+
}
|
|
585
|
+
return true;
|
|
586
|
+
}
|
|
587
|
+
console.log(pc4.yellow(pc4.bold(" \u26A0 \u7F3A\u5C11\u5FAE\u4FE1\u9002\u914D\u5668 weapp-adapter.js")));
|
|
588
|
+
console.log(
|
|
589
|
+
pc4.yellow(
|
|
590
|
+
" \u5FAE\u4FE1\u5C0F\u6E38\u620F\u65E0 DOM,\u9700\u6B64\u5B98\u65B9\u9002\u914D\u5668\u6CE8\u5165 canvas/document shim,\u5426\u5219 game.js \u542F\u52A8\u5373\u62A5\u9519\u3002"
|
|
591
|
+
)
|
|
592
|
+
);
|
|
593
|
+
console.log(pc4.yellow(` \u8BF7\u628A\u5B98\u65B9 weapp-adapter.js(\u53CA\u53EF\u9009 symbol.js)\u653E\u5230\u4EE5\u4E0B\u4EFB\u4E00\u76EE\u5F55\u540E\u91CD\u65B0\u6784\u5EFA:`));
|
|
594
|
+
for (const d of wechatVendorDirs(projectRoot)) {
|
|
595
|
+
console.log(pc4.yellow(` - ${relative(projectRoot, d)}/weapp-adapter.js`));
|
|
596
|
+
}
|
|
597
|
+
console.log(
|
|
598
|
+
pc4.yellow(" \u83B7\u53D6\u65B9\u5F0F:\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u65B0\u5EFA\u300C\u5C0F\u6E38\u620F\u300D\u9879\u76EE\u6A21\u677F\u91CC\u9644\u5E26 weapp-adapter.js\u3002")
|
|
599
|
+
);
|
|
600
|
+
return false;
|
|
601
|
+
}
|
|
602
|
+
function listFiles(dir) {
|
|
603
|
+
const out = [];
|
|
604
|
+
if (!fse4.existsSync(dir)) return out;
|
|
605
|
+
for (const name of fse4.readdirSync(dir)) {
|
|
606
|
+
const full = join(dir, name);
|
|
607
|
+
const st = fse4.statSync(full);
|
|
608
|
+
if (st.isDirectory()) out.push(...listFiles(full));
|
|
609
|
+
else out.push({ path: full, size: st.size });
|
|
610
|
+
}
|
|
611
|
+
return out;
|
|
612
|
+
}
|
|
613
|
+
function findMainBundle(files) {
|
|
614
|
+
const wechatBundle = files.find((f) => basename(f.path) === "game.bundle.js");
|
|
615
|
+
if (wechatBundle) return wechatBundle;
|
|
616
|
+
const jsFiles = files.filter((f) => /\.js$/i.test(f.path));
|
|
617
|
+
if (jsFiles.length === 0) return void 0;
|
|
618
|
+
return jsFiles.reduce((max, f) => f.size > max.size ? f : max, jsFiles[0]);
|
|
619
|
+
}
|
|
620
|
+
function checkPlatform(platform, distDir) {
|
|
621
|
+
if (!fse4.existsSync(distDir)) {
|
|
622
|
+
console.log(pc4.gray(` [${platform}] \u672A\u6784\u5EFA,\u8DF3\u8FC7`));
|
|
623
|
+
return true;
|
|
624
|
+
}
|
|
625
|
+
const files = listFiles(distDir);
|
|
626
|
+
const total = files.reduce((s, f) => s + f.size, 0);
|
|
627
|
+
const main2 = findMainBundle(files);
|
|
628
|
+
const limits = SIZE_LIMITS[platform];
|
|
629
|
+
console.log(pc4.cyan(` [${platform}]`));
|
|
630
|
+
console.log(` \u603B\u4F53\u79EF:${pc4.bold(formatBytes(total))}(${files.length} \u4E2A\u6587\u4EF6)`);
|
|
631
|
+
if (main2) {
|
|
632
|
+
console.log(` \u4E3B bundle:${basename(main2.path)} = ${pc4.bold(formatBytes(main2.size))}`);
|
|
633
|
+
}
|
|
634
|
+
let ok = true;
|
|
635
|
+
if (limits?.totalHardLimit) {
|
|
636
|
+
if (total > limits.totalHardLimit) {
|
|
637
|
+
console.log(
|
|
638
|
+
pc4.red(
|
|
639
|
+
` \u2717 \u8D85\u51FA TikTok \u6574\u5305\u786C\u4E0A\u9650 ${formatBytes(limits.totalHardLimit)}(\u8D85 ${formatBytes(total - limits.totalHardLimit)})`
|
|
640
|
+
)
|
|
641
|
+
);
|
|
642
|
+
ok = false;
|
|
643
|
+
} else {
|
|
644
|
+
console.log(
|
|
645
|
+
pc4.green(` \u2713 \u5728 TikTok 50MB \u6574\u5305\u4E0A\u9650\u5185(\u4F59 ${formatBytes(limits.totalHardLimit - total)})`)
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
if (limits?.mainBundleSoftLimit && main2) {
|
|
650
|
+
if (main2.size > limits.mainBundleSoftLimit) {
|
|
651
|
+
console.log(
|
|
652
|
+
pc4.yellow(
|
|
653
|
+
` \u26A0 \u5FAE\u4FE1\u4E3B\u5305\u8D85\u8FC7\u5EFA\u8BAE ${formatBytes(limits.mainBundleSoftLimit)}(\u5F53\u524D ${formatBytes(main2.size)});\u5EFA\u8BAE\u62C6\u5206\u5305\u6216\u88C1\u526A\u8D44\u6E90,\u4F46\u4E0D\u963B\u65AD\u6784\u5EFA`
|
|
654
|
+
)
|
|
655
|
+
);
|
|
656
|
+
} else {
|
|
657
|
+
console.log(
|
|
658
|
+
pc4.green(` \u2713 \u5FAE\u4FE1\u4E3B\u5305\u5728 4MB \u5EFA\u8BAE\u503C\u5185(\u4F59 ${formatBytes(limits.mainBundleSoftLimit - main2.size)})`)
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
return ok;
|
|
663
|
+
}
|
|
664
|
+
function runSizeCheck(target) {
|
|
665
|
+
const ctx = resolveProject();
|
|
666
|
+
const targets = target ? isPlatform(target) ? [target] : invalidTarget(target) : ALL_PLATFORMS;
|
|
667
|
+
console.log(pc4.cyan(pc4.bold("\u25B6 \u4EA7\u7269\u4F53\u79EF\u68C0\u67E5")));
|
|
668
|
+
let allOk = true;
|
|
669
|
+
for (const p of targets) {
|
|
670
|
+
const distDir = join(ctx.root, "dist", p);
|
|
671
|
+
if (!checkPlatform(p, distDir)) allOk = false;
|
|
672
|
+
}
|
|
673
|
+
if (!allOk) {
|
|
674
|
+
console.log(pc4.red(pc4.bold("\n\u2717 \u4F53\u79EF\u68C0\u67E5\u672A\u901A\u8FC7(\u5B58\u5728\u8D85\u786C\u4E0A\u9650\u7684\u5E73\u53F0)")));
|
|
675
|
+
return false;
|
|
676
|
+
}
|
|
677
|
+
console.log(pc4.green(pc4.bold("\n\u2713 \u4F53\u79EF\u68C0\u67E5\u5B8C\u6210")));
|
|
678
|
+
return true;
|
|
679
|
+
}
|
|
680
|
+
function sizeCheckPlatform(platform, distDir) {
|
|
681
|
+
console.log(pc4.cyan(pc4.bold("\u25B6 \u4EA7\u7269\u4F53\u79EF\u68C0\u67E5")));
|
|
682
|
+
const ok = checkPlatform(platform, distDir);
|
|
683
|
+
if (ok) console.log(pc4.green(pc4.bold("\u2713 \u4F53\u79EF\u68C0\u67E5\u5B8C\u6210")));
|
|
684
|
+
else console.log(pc4.red(pc4.bold("\u2717 \u4F53\u79EF\u68C0\u67E5\u672A\u901A\u8FC7")));
|
|
685
|
+
return ok;
|
|
686
|
+
}
|
|
687
|
+
function invalidTarget(target) {
|
|
688
|
+
console.log(pc4.red(`\u672A\u77E5\u5E73\u53F0:${target}`));
|
|
689
|
+
console.log(pc4.gray(`\u53EF\u9009:${ALL_PLATFORMS.join(" / ")}`));
|
|
690
|
+
process.exit(1);
|
|
691
|
+
}
|
|
692
|
+
function collectJsFiles(dir) {
|
|
693
|
+
const out = [];
|
|
694
|
+
if (!fse4.existsSync(dir)) return out;
|
|
695
|
+
for (const name of fse4.readdirSync(dir)) {
|
|
696
|
+
const full = join(dir, name);
|
|
697
|
+
const st = fse4.statSync(full);
|
|
698
|
+
if (st.isDirectory()) out.push(...collectJsFiles(full));
|
|
699
|
+
else if (/\.(js|mjs|cjs)$/i.test(name)) out.push(full);
|
|
700
|
+
}
|
|
701
|
+
return out;
|
|
702
|
+
}
|
|
703
|
+
function snippetAround(text, index, span = 60) {
|
|
704
|
+
const start = Math.max(0, index - span);
|
|
705
|
+
const end = Math.min(text.length, index + span);
|
|
706
|
+
return text.slice(start, end).replace(/\s+/g, " ").trim();
|
|
707
|
+
}
|
|
708
|
+
function verifyPlatform(platform, distDir, projectRoot) {
|
|
709
|
+
const rules = ISOLATION_RULES[platform];
|
|
710
|
+
if (!fse4.existsSync(distDir)) {
|
|
711
|
+
console.log(pc4.gray(` [${platform}] \u672A\u6784\u5EFA(${relative(projectRoot, distDir)} \u4E0D\u5B58\u5728),\u8DF3\u8FC7`));
|
|
712
|
+
return 0;
|
|
713
|
+
}
|
|
714
|
+
const files = collectJsFiles(distDir);
|
|
715
|
+
if (files.length === 0) {
|
|
716
|
+
console.log(pc4.yellow(` [${platform}] \u672A\u53D1\u73B0 JS \u4EA7\u7269,\u8DF3\u8FC7`));
|
|
717
|
+
return 0;
|
|
718
|
+
}
|
|
719
|
+
let hits = 0;
|
|
720
|
+
for (const file of files) {
|
|
721
|
+
const text = fse4.readFileSync(file, "utf-8");
|
|
722
|
+
for (const sig of rules.forbidden) {
|
|
723
|
+
let from = 0;
|
|
724
|
+
let idx = text.indexOf(sig, from);
|
|
725
|
+
let perSig = 0;
|
|
726
|
+
while (idx !== -1) {
|
|
727
|
+
hits++;
|
|
728
|
+
perSig++;
|
|
729
|
+
const rel = relative(projectRoot, file);
|
|
730
|
+
console.log(pc4.red(` \u2717 [${platform}] \u547D\u4E2D\u7981\u7528 SDK \u7B7E\u540D ${pc4.bold(sig)}`));
|
|
731
|
+
console.log(pc4.gray(` \u6587\u4EF6:${rel}`));
|
|
732
|
+
console.log(pc4.gray(` \u7247\u6BB5:\u2026${snippetAround(text, idx)}\u2026`));
|
|
733
|
+
from = idx + sig.length;
|
|
734
|
+
idx = text.indexOf(sig, from);
|
|
735
|
+
if (perSig >= 3 && idx !== -1) {
|
|
736
|
+
console.log(pc4.gray(` (\u8BE5\u7B7E\u540D\u5728\u672C\u6587\u4EF6\u8FD8\u6709\u66F4\u591A\u547D\u4E2D,\u5DF2\u6298\u53E0)`));
|
|
737
|
+
break;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
if (hits === 0) {
|
|
743
|
+
console.log(
|
|
744
|
+
pc4.green(` \u2713 [${platform}] \u9694\u79BB\u901A\u8FC7(\u626B\u63CF ${files.length} \u4E2A JS \u6587\u4EF6,\u65E0\u5176\u5B83\u5E73\u53F0 SDK \u7B7E\u540D)`)
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
return hits;
|
|
748
|
+
}
|
|
749
|
+
function runVerifyIsolation(target) {
|
|
750
|
+
const ctx = resolveProject();
|
|
751
|
+
const targets = target ? isPlatform(target) ? [target] : invalidTarget2(target) : ALL_PLATFORMS;
|
|
752
|
+
console.log(pc4.cyan(pc4.bold("\u25B6 SDK \u9694\u79BB\u6821\u9A8C")));
|
|
753
|
+
let totalHits = 0;
|
|
754
|
+
for (const p of targets) {
|
|
755
|
+
totalHits += verifyPlatform(p, join(ctx.root, "dist", p), ctx.root);
|
|
756
|
+
}
|
|
757
|
+
if (totalHits > 0) {
|
|
758
|
+
console.log(pc4.red(pc4.bold(`
|
|
759
|
+
\u2717 \u9694\u79BB\u6821\u9A8C\u5931\u8D25:\u5171\u53D1\u73B0 ${totalHits} \u5904\u8DE8\u5E73\u53F0 SDK \u7B7E\u540D\u6CC4\u6F0F`)));
|
|
760
|
+
return false;
|
|
761
|
+
}
|
|
762
|
+
console.log(pc4.green(pc4.bold("\n\u2713 \u9694\u79BB\u6821\u9A8C\u5168\u90E8\u901A\u8FC7")));
|
|
763
|
+
return true;
|
|
764
|
+
}
|
|
765
|
+
function verifyIsolationPlatform(platform, distDir, projectRoot) {
|
|
766
|
+
console.log(pc4.cyan(pc4.bold("\u25B6 SDK \u9694\u79BB\u6821\u9A8C")));
|
|
767
|
+
const hits = verifyPlatform(platform, distDir, projectRoot);
|
|
768
|
+
if (hits > 0) {
|
|
769
|
+
console.log(pc4.red(pc4.bold(`\u2717 \u9694\u79BB\u6821\u9A8C\u5931\u8D25:${hits} \u5904\u8DE8\u5E73\u53F0 SDK \u7B7E\u540D\u6CC4\u6F0F`)));
|
|
770
|
+
return false;
|
|
771
|
+
}
|
|
772
|
+
console.log(pc4.green(pc4.bold("\u2713 \u9694\u79BB\u6821\u9A8C\u901A\u8FC7")));
|
|
773
|
+
return true;
|
|
774
|
+
}
|
|
775
|
+
function invalidTarget2(target) {
|
|
776
|
+
console.log(pc4.red(`\u672A\u77E5\u5E73\u53F0:${target}`));
|
|
777
|
+
console.log(pc4.gray(`\u53EF\u9009:${ALL_PLATFORMS.join(" / ")}`));
|
|
778
|
+
process.exit(1);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// src/cli/shells/web.ts
|
|
782
|
+
function renderWebHtml(cfg, scriptSrc) {
|
|
783
|
+
const title = escapeHtml(cfg?.title ?? "Phaser Game");
|
|
784
|
+
return `<!doctype html>
|
|
785
|
+
<html lang="zh-CN">
|
|
786
|
+
<head>
|
|
787
|
+
<meta charset="UTF-8" />
|
|
788
|
+
<meta
|
|
789
|
+
name="viewport"
|
|
790
|
+
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
|
|
791
|
+
/>
|
|
792
|
+
<title>${title}</title>
|
|
793
|
+
<style>
|
|
794
|
+
html,
|
|
795
|
+
body {
|
|
796
|
+
margin: 0;
|
|
797
|
+
padding: 0;
|
|
798
|
+
width: 100%;
|
|
799
|
+
height: 100%;
|
|
800
|
+
background: #000;
|
|
801
|
+
overflow: hidden;
|
|
802
|
+
/* \u5B89\u5168\u533A:\u5218\u6D77\u5C4F / \u5706\u89D2\u5C4F\u9002\u914D */
|
|
803
|
+
padding: env(safe-area-inset-top) env(safe-area-inset-right)
|
|
804
|
+
env(safe-area-inset-bottom) env(safe-area-inset-left);
|
|
805
|
+
box-sizing: border-box;
|
|
806
|
+
}
|
|
807
|
+
#game-root {
|
|
808
|
+
width: 100%;
|
|
809
|
+
height: 100%;
|
|
810
|
+
}
|
|
811
|
+
#game-root canvas {
|
|
812
|
+
display: block;
|
|
813
|
+
margin: 0 auto;
|
|
814
|
+
}
|
|
815
|
+
</style>
|
|
816
|
+
</head>
|
|
817
|
+
<body>
|
|
818
|
+
<!-- \u6E38\u620F\u753B\u5E03\u6302\u8F7D\u70B9 -->
|
|
819
|
+
<div id="game-root"></div>
|
|
820
|
+
<!-- Web \u5E73\u53F0\u5165\u53E3:\u4EC5\u9759\u6001 import Web \u9002\u914D\u5668,\u6784\u5EFA\u540E\u88AB\u66FF\u6362\u4E3A\u6253\u5305\u811A\u672C -->
|
|
821
|
+
<script type="module" src="${scriptSrc}"></script>
|
|
822
|
+
</body>
|
|
823
|
+
</html>
|
|
824
|
+
`;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// src/cli/shells/tiktok.ts
|
|
828
|
+
var DEFAULT_SDK_URL = "https://developers.tiktok.com/js/minis.js";
|
|
829
|
+
function renderTikTokHtml(cfg, scriptSrc) {
|
|
830
|
+
const title = escapeHtml(cfg?.title ?? "TikTok Mini Game");
|
|
831
|
+
const sdkUrl = cfg?.sdkUrl ?? DEFAULT_SDK_URL;
|
|
832
|
+
const sdkTag = sdkUrl ? `<!--
|
|
833
|
+
TikTok Mini Games(Minis)SDK \u6CE8\u5165(\u5730\u5740\u6765\u81EA platform.config.tiktok.sdkUrl)\u3002
|
|
834
|
+
\u5FC5\u987B\u5728\u6E38\u620F\u811A\u672C\u4E4B\u524D\u52A0\u8F7D,\u8FD0\u884C\u65F6\u901A\u8FC7\u5168\u5C40 TTMinis.game.* \u8C03\u7528\u6865\u63A5\u80FD\u529B\u3002
|
|
835
|
+
-->
|
|
836
|
+
<script src="${escapeAttr(sdkUrl)}"></script>` : "";
|
|
837
|
+
return `<!doctype html>
|
|
838
|
+
<html lang="en">
|
|
839
|
+
<head>
|
|
840
|
+
<meta charset="UTF-8" />
|
|
841
|
+
<meta
|
|
842
|
+
name="viewport"
|
|
843
|
+
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
|
|
844
|
+
/>
|
|
845
|
+
<title>${title}</title>
|
|
846
|
+
<style>
|
|
847
|
+
html,
|
|
848
|
+
body {
|
|
849
|
+
margin: 0;
|
|
850
|
+
padding: 0;
|
|
851
|
+
width: 100%;
|
|
852
|
+
height: 100%;
|
|
853
|
+
background: #000;
|
|
854
|
+
overflow: hidden;
|
|
855
|
+
box-sizing: border-box;
|
|
856
|
+
}
|
|
857
|
+
#game-root {
|
|
858
|
+
width: 100%;
|
|
859
|
+
height: 100%;
|
|
860
|
+
}
|
|
861
|
+
#game-root canvas {
|
|
862
|
+
display: block;
|
|
863
|
+
margin: 0 auto;
|
|
864
|
+
}
|
|
865
|
+
</style>
|
|
866
|
+
</head>
|
|
867
|
+
<body>
|
|
868
|
+
${sdkTag}
|
|
869
|
+
|
|
870
|
+
<!-- \u6E38\u620F\u753B\u5E03\u6302\u8F7D\u70B9 -->
|
|
871
|
+
<div id="game-root"></div>
|
|
872
|
+
<!-- TikTok \u5E73\u53F0\u5165\u53E3:\u4EC5\u9759\u6001 import TikTok \u9002\u914D\u5668 -->
|
|
873
|
+
<script type="module" src="${scriptSrc}"></script>
|
|
874
|
+
</body>
|
|
875
|
+
</html>
|
|
876
|
+
`;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// src/cli/commands/build.ts
|
|
880
|
+
function cacheDir(projectRoot) {
|
|
881
|
+
return resolve(projectRoot, "node_modules/.cache/pf");
|
|
882
|
+
}
|
|
883
|
+
function renderHtmlShell(platform, config, scriptSrc) {
|
|
884
|
+
switch (platform) {
|
|
885
|
+
case "web":
|
|
886
|
+
return renderWebHtml(config.web, scriptSrc);
|
|
887
|
+
case "tiktok":
|
|
888
|
+
return renderTikTokHtml(config.tiktok, scriptSrc);
|
|
889
|
+
case "facebook":
|
|
890
|
+
return renderFacebookHtml(config.facebook, scriptSrc);
|
|
891
|
+
case "capacitor":
|
|
892
|
+
return renderCapacitorHtml(config.capacitor, scriptSrc);
|
|
893
|
+
default:
|
|
894
|
+
throw new Error(`renderHtmlShell \u4E0D\u652F\u6301\u5E73\u53F0:${platform}`);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
function normalizeHtmlOutput(distDir) {
|
|
898
|
+
const finalHtml = join(distDir, "index.html");
|
|
899
|
+
if (fse4.existsSync(finalHtml)) return;
|
|
900
|
+
const found = findFirstHtml(distDir);
|
|
901
|
+
if (found) {
|
|
902
|
+
fse4.moveSync(found, finalHtml, { overwrite: true });
|
|
903
|
+
}
|
|
904
|
+
const nm = join(distDir, "node_modules");
|
|
905
|
+
if (fse4.existsSync(nm)) {
|
|
906
|
+
fse4.removeSync(nm);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
function findFirstHtml(dir) {
|
|
910
|
+
for (const name of fse4.readdirSync(dir)) {
|
|
911
|
+
const full = join(dir, name);
|
|
912
|
+
const st = fse4.statSync(full);
|
|
913
|
+
if (st.isDirectory()) {
|
|
914
|
+
const inner = findFirstHtml(full);
|
|
915
|
+
if (inner) return inner;
|
|
916
|
+
} else if (/\.html$/i.test(name)) {
|
|
917
|
+
return full;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
return void 0;
|
|
921
|
+
}
|
|
922
|
+
async function buildPlatform(platform, ctx, config, vite) {
|
|
923
|
+
console.log(pc4.bgCyan(pc4.black(` \u6784\u5EFA\u5E73\u53F0:${platform} `)));
|
|
924
|
+
const cache = cacheDir(ctx.root);
|
|
925
|
+
fse4.ensureDirSync(cache);
|
|
926
|
+
const hash = randomBytes(4).toString("hex");
|
|
927
|
+
const entryFileName = `main.${platform}.${hash}.ts`;
|
|
928
|
+
const entryPath = join(cache, entryFileName);
|
|
929
|
+
fse4.writeFileSync(entryPath, renderEntry({ platform, projectRoot: ctx.root }), "utf-8");
|
|
930
|
+
const tempFiles = [entryPath];
|
|
931
|
+
const distDir = resolve(ctx.root, "dist", platform);
|
|
932
|
+
try {
|
|
933
|
+
let inputFile;
|
|
934
|
+
if (platform === "wechat") {
|
|
935
|
+
inputFile = entryPath;
|
|
936
|
+
} else {
|
|
937
|
+
const htmlName = `index.${platform}.${hash}.html`;
|
|
938
|
+
const htmlPath = join(cache, htmlName);
|
|
939
|
+
fse4.writeFileSync(htmlPath, renderHtmlShell(platform, config, `./${entryFileName}`), "utf-8");
|
|
940
|
+
tempFiles.push(htmlPath);
|
|
941
|
+
inputFile = htmlPath;
|
|
942
|
+
}
|
|
943
|
+
console.log(pc4.cyan(` \u25B6 [${platform}] vite build`));
|
|
944
|
+
const inlineConfig = createPlatformConfig(platform, {
|
|
945
|
+
projectRoot: ctx.root,
|
|
946
|
+
viteRoot: platform === "wechat" ? ctx.root : cache,
|
|
947
|
+
entryFile: inputFile,
|
|
948
|
+
frameworkVersion: ctx.frameworkVersion,
|
|
949
|
+
dev: false,
|
|
950
|
+
base: platform === "web" ? config.web?.base : void 0
|
|
951
|
+
});
|
|
952
|
+
await vite.build(inlineConfig);
|
|
953
|
+
if (platform !== "wechat") {
|
|
954
|
+
normalizeHtmlOutput(distDir);
|
|
955
|
+
}
|
|
956
|
+
console.log(pc4.green(` \u2713 [${platform}] vite build \u5B8C\u6210 \u2192 ${relative(ctx.root, distDir)}`));
|
|
957
|
+
const injectOk = injectShell({ platform, projectRoot: ctx.root, distDir, config });
|
|
958
|
+
const sizeOk = sizeCheckPlatform(platform, distDir);
|
|
959
|
+
const isoOk = verifyIsolationPlatform(platform, distDir, ctx.root);
|
|
960
|
+
const allOk = injectOk && sizeOk && isoOk;
|
|
961
|
+
if (allOk) {
|
|
962
|
+
console.log(pc4.green(pc4.bold(`\u2713 [${platform}] \u6784\u5EFA + \u6821\u9A8C\u5B8C\u6210 \u2192 ${relative(ctx.root, distDir)}
|
|
963
|
+
`)));
|
|
964
|
+
} else {
|
|
965
|
+
console.log(pc4.red(pc4.bold(`\u2717 [${platform}] \u6821\u9A8C/\u6CE8\u5165\u672A\u5168\u90E8\u901A\u8FC7
|
|
966
|
+
`)));
|
|
967
|
+
}
|
|
968
|
+
return allOk;
|
|
969
|
+
} catch (e) {
|
|
970
|
+
console.log(pc4.red(` \u2717 [${platform}] vite build \u5931\u8D25:${e.message}`));
|
|
971
|
+
if (e.stack) console.log(pc4.gray(e.stack ?? ""));
|
|
972
|
+
return false;
|
|
973
|
+
} finally {
|
|
974
|
+
for (const f of tempFiles) {
|
|
975
|
+
try {
|
|
976
|
+
fse4.removeSync(f);
|
|
977
|
+
} catch {
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
async function runBuild(target) {
|
|
983
|
+
if (!target) {
|
|
984
|
+
console.log(pc4.red("\u7528\u6CD5:pf build <web|tiktok|wechat|facebook|capacitor|all>"));
|
|
985
|
+
return false;
|
|
986
|
+
}
|
|
987
|
+
const platforms = target === "all" ? ALL_PLATFORMS : isPlatform(target) ? [target] : null;
|
|
988
|
+
if (!platforms) {
|
|
989
|
+
console.log(pc4.red(`\u672A\u77E5\u5E73\u53F0:${target}`));
|
|
990
|
+
console.log(pc4.gray(`\u53EF\u9009:${ALL_PLATFORMS.join(" / ")} / all`));
|
|
991
|
+
return false;
|
|
992
|
+
}
|
|
993
|
+
const ctx = resolveProject();
|
|
994
|
+
const config = await loadPlatformConfig(ctx);
|
|
995
|
+
const vite = await loadViteFromRoot(ctx.root);
|
|
996
|
+
console.log(pc4.cyan(pc4.bold(`\u25B6 \u5F00\u59CB\u6784\u5EFA:${platforms.join(", ")}(\u9879\u76EE:${ctx.root})
|
|
997
|
+
`)));
|
|
998
|
+
const results = [];
|
|
999
|
+
for (const p of platforms) {
|
|
1000
|
+
results.push({ platform: p, ok: await buildPlatform(p, ctx, config, vite) });
|
|
1001
|
+
}
|
|
1002
|
+
console.log(pc4.cyan(pc4.bold("\u25B6 \u6784\u5EFA\u6C47\u603B")));
|
|
1003
|
+
let allOk = true;
|
|
1004
|
+
for (const r of results) {
|
|
1005
|
+
if (r.ok) console.log(pc4.green(` \u2713 ${r.platform}`));
|
|
1006
|
+
else {
|
|
1007
|
+
console.log(pc4.red(` \u2717 ${r.platform}`));
|
|
1008
|
+
allOk = false;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
if (!allOk) {
|
|
1012
|
+
console.log(pc4.red(pc4.bold("\n\u2717 \u5B58\u5728\u6784\u5EFA/\u6821\u9A8C\u5931\u8D25\u7684\u5E73\u53F0")));
|
|
1013
|
+
} else {
|
|
1014
|
+
console.log(pc4.green(pc4.bold("\n\u2713 \u5168\u90E8\u5B8C\u6210")));
|
|
1015
|
+
}
|
|
1016
|
+
return allOk;
|
|
1017
|
+
}
|
|
1018
|
+
function cacheDir2(projectRoot) {
|
|
1019
|
+
return resolve(projectRoot, "node_modules/.cache/pf");
|
|
1020
|
+
}
|
|
1021
|
+
function renderHtmlShell2(platform, config, scriptSrc) {
|
|
1022
|
+
switch (platform) {
|
|
1023
|
+
case "web":
|
|
1024
|
+
return renderWebHtml(config.web, scriptSrc);
|
|
1025
|
+
case "tiktok":
|
|
1026
|
+
return renderTikTokHtml(config.tiktok, scriptSrc);
|
|
1027
|
+
case "facebook":
|
|
1028
|
+
return renderFacebookHtml(config.facebook, scriptSrc);
|
|
1029
|
+
case "capacitor":
|
|
1030
|
+
return renderCapacitorHtml(config.capacitor, scriptSrc);
|
|
1031
|
+
default:
|
|
1032
|
+
throw new Error(`dev \u4E0D\u652F\u6301\u5E73\u53F0:${platform}`);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
async function runDev(target) {
|
|
1036
|
+
const platform = target ? isPlatform(target) ? target : invalidTarget3(target) : "web";
|
|
1037
|
+
if (platform === "wechat") {
|
|
1038
|
+
console.log(pc4.red("\u2717 wechat \u65E0 HTML dev server \u5F62\u6001\u3002"));
|
|
1039
|
+
console.log(pc4.yellow(" \u8BF7\u7528 `pf build wechat` \u4EA7\u51FA dist/wechat,\u518D\u7528\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u6253\u5F00\u9884\u89C8\u3002"));
|
|
1040
|
+
return false;
|
|
1041
|
+
}
|
|
1042
|
+
const ctx = resolveProject();
|
|
1043
|
+
const config = await loadPlatformConfig(ctx);
|
|
1044
|
+
const vite = await loadViteFromRoot(ctx.root);
|
|
1045
|
+
const cache = cacheDir2(ctx.root);
|
|
1046
|
+
fse4.ensureDirSync(cache);
|
|
1047
|
+
const hash = randomBytes(4).toString("hex");
|
|
1048
|
+
const entryFileName = `main.${platform}.${hash}.ts`;
|
|
1049
|
+
const entryPath = join(cache, entryFileName);
|
|
1050
|
+
const htmlName = `index.${platform}.${hash}.html`;
|
|
1051
|
+
const htmlPath = join(cache, htmlName);
|
|
1052
|
+
fse4.writeFileSync(entryPath, renderEntry({ platform, projectRoot: ctx.root }), "utf-8");
|
|
1053
|
+
fse4.writeFileSync(htmlPath, renderHtmlShell2(platform, config, `./${entryFileName}`), "utf-8");
|
|
1054
|
+
const cleanup = () => {
|
|
1055
|
+
for (const f of [entryPath, htmlPath]) {
|
|
1056
|
+
try {
|
|
1057
|
+
fse4.removeSync(f);
|
|
1058
|
+
} catch {
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
};
|
|
1062
|
+
try {
|
|
1063
|
+
const server = await vite.createServer({
|
|
1064
|
+
// root 设为缓存目录(临时 HTML 所在),入口页直接是 /index.<p>.<hash>.html;
|
|
1065
|
+
// alias/publicDir 用绝对路径不受影响
|
|
1066
|
+
root: cache,
|
|
1067
|
+
base: "/",
|
|
1068
|
+
configFile: false,
|
|
1069
|
+
define: {
|
|
1070
|
+
__PLATFORM__: JSON.stringify(platform),
|
|
1071
|
+
__DEV__: JSON.stringify(true),
|
|
1072
|
+
__FRAMEWORK_VERSION__: JSON.stringify(ctx.frameworkVersion)
|
|
1073
|
+
},
|
|
1074
|
+
resolve: {
|
|
1075
|
+
alias: [
|
|
1076
|
+
{ find: /^@game$/, replacement: resolve(ctx.root, "src/game") },
|
|
1077
|
+
{ find: /^@game\//, replacement: resolve(ctx.root, "src/game") + "/" }
|
|
1078
|
+
]
|
|
1079
|
+
},
|
|
1080
|
+
publicDir: resolve(ctx.root, "src/game/public"),
|
|
1081
|
+
server: { open: false }
|
|
1082
|
+
});
|
|
1083
|
+
await server.listen();
|
|
1084
|
+
const info = server.resolvedUrls;
|
|
1085
|
+
const url = info?.local?.[0] ?? "http://localhost:5173/";
|
|
1086
|
+
console.log(pc4.green(pc4.bold(`\u25B6 [${platform}] dev server \u5C31\u7EEA`)));
|
|
1087
|
+
console.log(` \u6253\u5F00:${pc4.cyan(url + htmlName)}`);
|
|
1088
|
+
console.log(pc4.gray(" (Ctrl+C \u9000\u51FA,\u4E34\u65F6\u5165\u53E3\u4F1A\u81EA\u52A8\u6E05\u7406)"));
|
|
1089
|
+
const onExit = async () => {
|
|
1090
|
+
cleanup();
|
|
1091
|
+
await server.close();
|
|
1092
|
+
process.exit(0);
|
|
1093
|
+
};
|
|
1094
|
+
process.on("SIGINT", onExit);
|
|
1095
|
+
process.on("SIGTERM", onExit);
|
|
1096
|
+
return true;
|
|
1097
|
+
} catch (e) {
|
|
1098
|
+
cleanup();
|
|
1099
|
+
console.log(pc4.red(`\u2717 dev server \u542F\u52A8\u5931\u8D25:${e.message}`));
|
|
1100
|
+
return false;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
function invalidTarget3(target) {
|
|
1104
|
+
console.log(pc4.red(`\u672A\u77E5\u5E73\u53F0:${target}`));
|
|
1105
|
+
process.exit(1);
|
|
1106
|
+
}
|
|
1107
|
+
function spawnCap(projectRoot, args) {
|
|
1108
|
+
const npx = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
1109
|
+
console.log(pc4.gray(` $ ${npx} cap ${args.join(" ")}`));
|
|
1110
|
+
const res = spawnSync(npx, ["cap", ...args], {
|
|
1111
|
+
cwd: projectRoot,
|
|
1112
|
+
stdio: "inherit",
|
|
1113
|
+
// Windows 下 .cmd 需要 shell:true 才能被解析
|
|
1114
|
+
shell: process.platform === "win32"
|
|
1115
|
+
});
|
|
1116
|
+
if (res.error) {
|
|
1117
|
+
console.log(pc4.red(` \u2717 \u6267\u884C\u5931\u8D25:${res.error.message}`));
|
|
1118
|
+
console.log(
|
|
1119
|
+
pc4.yellow(
|
|
1120
|
+
" \u8BF7\u786E\u8BA4\u5DF2\u5B89\u88C5 Capacitor CLI:npm i -D @capacitor/cli @capacitor/android @capacitor/ios"
|
|
1121
|
+
)
|
|
1122
|
+
);
|
|
1123
|
+
return 1;
|
|
1124
|
+
}
|
|
1125
|
+
return res.status ?? 1;
|
|
1126
|
+
}
|
|
1127
|
+
function runCap(action, platform) {
|
|
1128
|
+
const ctx = resolveProject();
|
|
1129
|
+
if (action === "sync") {
|
|
1130
|
+
console.log(pc4.cyan(pc4.bold("\u25B6 Capacitor sync")));
|
|
1131
|
+
const distDir = resolve(ctx.root, "dist", "capacitor");
|
|
1132
|
+
if (!fse4.existsSync(distDir)) {
|
|
1133
|
+
console.log(pc4.red("\u2717 dist/capacitor \u4E0D\u5B58\u5728"));
|
|
1134
|
+
console.log(pc4.yellow(" \u8BF7\u5148\u6784\u5EFA:pf build capacitor"));
|
|
1135
|
+
return false;
|
|
1136
|
+
}
|
|
1137
|
+
const args = platform ? ["sync", platform] : ["sync"];
|
|
1138
|
+
return capExec(ctx.root, args);
|
|
1139
|
+
}
|
|
1140
|
+
if (action === "open") {
|
|
1141
|
+
if (platform !== "android" && platform !== "ios") {
|
|
1142
|
+
console.log(pc4.red("\u2717 open \u9700\u6307\u5B9A\u5E73\u53F0:pf cap open <android|ios>"));
|
|
1143
|
+
return false;
|
|
1144
|
+
}
|
|
1145
|
+
console.log(pc4.cyan(pc4.bold(`\u25B6 Capacitor open ${platform}`)));
|
|
1146
|
+
return capExec(ctx.root, ["open", platform]);
|
|
1147
|
+
}
|
|
1148
|
+
console.log(pc4.red("\u7528\u6CD5:"));
|
|
1149
|
+
console.log(" pf cap sync [android|ios]");
|
|
1150
|
+
console.log(" pf cap open <android|ios>");
|
|
1151
|
+
return false;
|
|
1152
|
+
}
|
|
1153
|
+
function capExec(projectRoot, args) {
|
|
1154
|
+
const code = spawnCap(projectRoot, args);
|
|
1155
|
+
return code === 0;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
// src/cli/index.ts
|
|
1159
|
+
async function main(argv = process.argv.slice(2)) {
|
|
1160
|
+
const [command, ...rest] = argv;
|
|
1161
|
+
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
1162
|
+
printHelp();
|
|
1163
|
+
return;
|
|
1164
|
+
}
|
|
1165
|
+
try {
|
|
1166
|
+
switch (command) {
|
|
1167
|
+
case "build": {
|
|
1168
|
+
const ok = await runBuild(rest[0]);
|
|
1169
|
+
process.exitCode = ok ? 0 : 1;
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
case "dev": {
|
|
1173
|
+
const ok = await runDev(rest[0]);
|
|
1174
|
+
if (!ok) process.exitCode = 1;
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
case "size-check": {
|
|
1178
|
+
const ok = runSizeCheck(rest[0]);
|
|
1179
|
+
process.exitCode = ok ? 0 : 1;
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
case "verify-isolation": {
|
|
1183
|
+
const ok = runVerifyIsolation(rest[0]);
|
|
1184
|
+
process.exitCode = ok ? 0 : 1;
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
case "cap": {
|
|
1188
|
+
const ok = runCap(rest[0], rest[1]);
|
|
1189
|
+
process.exitCode = ok ? 0 : 1;
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
default: {
|
|
1193
|
+
console.log(pc4.yellow(`[pf] \u672A\u77E5\u547D\u4EE4 "${command}"\u3002`));
|
|
1194
|
+
console.log(pc4.dim("\u8FD0\u884C `pf help` \u67E5\u770B\u53EF\u7528\u547D\u4EE4\u3002"));
|
|
1195
|
+
process.exitCode = 1;
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
} catch (e) {
|
|
1199
|
+
console.log(pc4.red(`[pf] \u6267\u884C "${command}" \u51FA\u9519:${e.message}`));
|
|
1200
|
+
if (e.stack) console.log(pc4.gray(e.stack ?? ""));
|
|
1201
|
+
process.exitCode = 1;
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
function printHelp() {
|
|
1205
|
+
console.log(pc4.bold("@maoyugames/phaser-framework CLI (pf)"));
|
|
1206
|
+
console.log(pc4.dim("\u591A\u5E73\u53F0 Phaser \u6846\u67B6\u6784\u5EFA\u5DE5\u5177\u3002"));
|
|
1207
|
+
console.log("");
|
|
1208
|
+
console.log("\u547D\u4EE4:");
|
|
1209
|
+
console.log(` ${pc4.cyan("pf dev [platform]")} \u542F\u52A8\u67D0\u5E73\u53F0 dev server(\u9ED8\u8BA4 web)`);
|
|
1210
|
+
console.log(` ${pc4.cyan("pf build <platform|all>")} \u6784\u5EFA\u67D0\u5E73\u53F0/\u5168\u90E8\u5E73\u53F0`);
|
|
1211
|
+
console.log(` ${pc4.cyan("pf size-check [platform]")} \u4EA7\u7269\u4F53\u79EF\u68C0\u67E5`);
|
|
1212
|
+
console.log(` ${pc4.cyan("pf verify-isolation [platform]")} SDK \u9694\u79BB\u6821\u9A8C`);
|
|
1213
|
+
console.log(` ${pc4.cyan("pf cap <sync|open> [android|ios]")} Capacitor \u539F\u751F\u5DE5\u7A0B\u64CD\u4F5C`);
|
|
1214
|
+
console.log("");
|
|
1215
|
+
console.log(pc4.dim("\u5E73\u53F0:web / tiktok / wechat / facebook / capacitor"));
|
|
1216
|
+
}
|
|
1217
|
+
void main();
|
|
1218
|
+
|
|
1219
|
+
export { main };
|