@ttmg/cli 0.3.6-beta.3 → 0.3.6-beta.wasmcode.split
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/CHANGELOG.md +4 -1
- package/dist/index.js +1494 -462
- package/dist/index.js.map +1 -1
- package/dist/package.json +3 -2
- package/dist/public/assets/{baseForm-Dnb99UJB.js → baseForm-CH4B0fQb.js} +4 -4
- package/dist/public/assets/baseForm-CH4B0fQb.js.br +0 -0
- package/dist/public/assets/{index-yh4tKekV.css → index-BLfeu5YF.css} +1 -1
- package/dist/public/assets/{index-CPEErQsP.js → index-BM71H_Ze.js} +1 -1
- package/dist/public/assets/index-BRFS2ZhY.css +1 -0
- package/dist/public/assets/index-BRFS2ZhY.css.br +0 -0
- package/dist/public/assets/index-BSpAncbU.css +1 -0
- package/dist/public/assets/{index-C5pU_dUf.js → index-BUFQDiZR.js} +1 -1
- package/dist/public/assets/index-BUFQDiZR.js.br +0 -0
- package/dist/public/assets/index-BZ2ZRGvx.js +1 -0
- package/dist/public/assets/index-Bbp-fMXD.js +1 -0
- package/dist/public/assets/index-BqGAmMye.css +1 -0
- package/dist/public/assets/{index-CJtCELuI.js → index-Br-Gyi7B.js} +1 -1
- package/dist/public/assets/{index-vPoeqDW1.js → index-C0jTkN7v.js} +1 -1
- package/dist/public/assets/index-C0jTkN7v.js.br +0 -0
- package/dist/public/assets/index-C4KyRN0a.js +1 -0
- package/dist/public/assets/index-C4KyRN0a.js.br +0 -0
- package/dist/public/assets/{index-DHyyb3Gw.js → index-CA2yi7fQ.js} +1 -1
- package/dist/public/assets/index-CA2yi7fQ.js.br +0 -0
- package/dist/public/assets/index-CFcABxBe.css +1 -0
- package/dist/public/assets/index-CY576z43.js +14 -0
- package/dist/public/assets/index-CY576z43.js.br +0 -0
- package/dist/public/assets/index-C_GvGYUB.js +1 -0
- package/dist/public/assets/index-CtaSaIq7.css +1 -0
- package/dist/public/assets/index-D4uugaca.js +1 -0
- package/dist/public/assets/index-D6mgKJkP.js +1 -0
- package/dist/public/assets/{index-UihZn1LL.css → index-DEA7T0Eb.css} +1 -1
- package/dist/public/assets/index-DEA7T0Eb.css.br +0 -0
- package/dist/public/assets/{index-BaXjsvp9.js → index-DP50otHK.js} +1 -1
- package/dist/public/assets/index-DUdfX7io.js +1 -0
- package/dist/public/assets/{index-Bovw-Ai8.js → index-DVqEvrQd.js} +1 -1
- package/dist/public/assets/index-DVqEvrQd.js.br +0 -0
- package/dist/public/assets/index-Dcdc0Lzb.css +1 -0
- package/dist/public/assets/index-DeQT3JAU.css +1 -0
- package/dist/public/assets/index-DqFmR7Qk.css +1 -0
- package/dist/public/assets/index-DsIb6ZUl.css +1 -0
- package/dist/public/assets/index-DuzTbOuu.js +1 -0
- package/dist/public/assets/{index-DKKMC8UM.js → index-DwBUxhm4.js} +1 -1
- package/dist/public/assets/index-FqyT3gyn.js +1 -0
- package/dist/public/assets/{index-CqK4Ymjd.js → index-JoKaDNMa.js} +1 -1
- package/dist/public/assets/{index-BkiB8M4Y.css → index-XY8Gg_WJ.css} +1 -1
- package/dist/public/assets/index-XY8Gg_WJ.css.br +0 -0
- package/dist/public/assets/index-gJcsWhoa.css +1 -0
- package/dist/public/assets/index-tReoEVof.css +1 -0
- package/dist/public/assets/isPlainObject-DMoTnwgw.js +1 -0
- package/dist/public/assets/{times-BsXqTKq4.js → times-DWwvCyLh.js} +1 -1
- package/dist/public/index.html +39 -2
- package/package.json +3 -2
- package/dist/public/assets/baseForm-Dnb99UJB.js.br +0 -0
- package/dist/public/assets/index-B8aFXzhN.js +0 -1
- package/dist/public/assets/index-BB5kLk47.css +0 -1
- package/dist/public/assets/index-BGkIaNEY.js +0 -1
- package/dist/public/assets/index-BedicqfR.css +0 -1
- package/dist/public/assets/index-Bf6aJOeV.css +0 -1
- package/dist/public/assets/index-BkiB8M4Y.css.br +0 -0
- package/dist/public/assets/index-BmSg7a2H.js +0 -14
- package/dist/public/assets/index-BmSg7a2H.js.br +0 -0
- package/dist/public/assets/index-Bovw-Ai8.js.br +0 -0
- package/dist/public/assets/index-Bq6YxKLX.css +0 -1
- package/dist/public/assets/index-BwbPFgZF.css +0 -1
- package/dist/public/assets/index-C5pU_dUf.js.br +0 -0
- package/dist/public/assets/index-C8f539tK.css +0 -1
- package/dist/public/assets/index-CkcOz6VJ.js +0 -1
- package/dist/public/assets/index-D0piMtcL.js +0 -1
- package/dist/public/assets/index-DNZgHncv.js +0 -1
- package/dist/public/assets/index-DPSts5Re.css +0 -1
- package/dist/public/assets/index-DPSts5Re.css.br +0 -0
- package/dist/public/assets/index-DhmPFuxl.css +0 -1
- package/dist/public/assets/index-OPfpZTmc.js +0 -1
- package/dist/public/assets/index-OPfpZTmc.js.br +0 -0
- package/dist/public/assets/index-UihZn1LL.css.br +0 -0
- package/dist/public/assets/index-vPoeqDW1.js.br +0 -0
- package/dist/public/assets/isPlainObject-CKjwQ9bg.js +0 -1
package/dist/index.js
CHANGED
|
@@ -38,9 +38,12 @@ var FormData$1 = require('form-data');
|
|
|
38
38
|
var ttmgPack = require('ttmg-pack');
|
|
39
39
|
var expressStaticGzip = require('express-static-gzip');
|
|
40
40
|
var fileUpload = require('express-fileupload');
|
|
41
|
+
var ttmgWasmtool = require('@anrans001/ttmg-wasmtool');
|
|
42
|
+
var crypto$1 = require('node:crypto');
|
|
41
43
|
var fs$1 = require('node:fs');
|
|
42
44
|
var path$1 = require('node:path');
|
|
43
45
|
var zlib = require('zlib');
|
|
46
|
+
var crypto = require('crypto');
|
|
44
47
|
var promises = require('fs/promises');
|
|
45
48
|
var qs = require('qs');
|
|
46
49
|
|
|
@@ -69,6 +72,8 @@ var http__namespace = /*#__PURE__*/_interopNamespaceDefault(http);
|
|
|
69
72
|
var dns__namespace = /*#__PURE__*/_interopNamespaceDefault(dns);
|
|
70
73
|
var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
|
|
71
74
|
var glob__namespace = /*#__PURE__*/_interopNamespaceDefault(glob);
|
|
75
|
+
var fs__namespace$1 = /*#__PURE__*/_interopNamespaceDefault(fs$1);
|
|
76
|
+
var path__namespace$1 = /*#__PURE__*/_interopNamespaceDefault(path$1);
|
|
72
77
|
|
|
73
78
|
async function openUrl(url) {
|
|
74
79
|
const { launch } = await import('chrome-launcher');
|
|
@@ -6504,7 +6509,19 @@ const getCurrentUser = () => {
|
|
|
6504
6509
|
}
|
|
6505
6510
|
};
|
|
6506
6511
|
|
|
6507
|
-
|
|
6512
|
+
/**
|
|
6513
|
+
* PPE / 测试环境开关。
|
|
6514
|
+
*
|
|
6515
|
+
* 所有 CLI 接口(`stark_wasm/v4/*`, `wasm-collect/v1/*`, portal 鉴权等)
|
|
6516
|
+
* 都共用本文件的 `request()`,所以在这里统一注入 PPE header 就能覆盖全量。
|
|
6517
|
+
* 不在 `DEV_HEADERS` 里改是因为那份常量只被 `remotePipeline.ts` 里的老
|
|
6518
|
+
* 远程分包接口 spread 用;新加的 `startSession.ts` / `finishSession.ts` /
|
|
6519
|
+
* `getCollectedFuncIds.ts` 都没 spread,漏一处就会绕开 PPE。
|
|
6520
|
+
*
|
|
6521
|
+
* 要切回线上环境,直接把 `CLI_PPE_ENV` 设成空串即可(下方条件 spread 会
|
|
6522
|
+
* 自动不带这两个 header)。
|
|
6523
|
+
*/
|
|
6524
|
+
const CLI_PPE_ENV = 'ppe_wasm_test';
|
|
6508
6525
|
function getAxiosProxyConfig() {
|
|
6509
6526
|
const config = getTTMGRC();
|
|
6510
6527
|
// 优先级: http-proxy > socks-proxy > proxy (老字段兼容)
|
|
@@ -6542,6 +6559,8 @@ async function request({ url, method, data, headers, params, }) {
|
|
|
6542
6559
|
console.log('Method:', method);
|
|
6543
6560
|
console.log('Headers:', JSON.stringify({
|
|
6544
6561
|
Cookie: cookie,
|
|
6562
|
+
...({ 'x-use-ppe': '1', 'x-tt-env': CLI_PPE_ENV }
|
|
6563
|
+
),
|
|
6545
6564
|
...(headers || {}),
|
|
6546
6565
|
}, null, 2));
|
|
6547
6566
|
if (params) {
|
|
@@ -6559,8 +6578,11 @@ async function request({ url, method, data, headers, params, }) {
|
|
|
6559
6578
|
params,
|
|
6560
6579
|
headers: {
|
|
6561
6580
|
Cookie: cookie,
|
|
6562
|
-
//
|
|
6563
|
-
//
|
|
6581
|
+
// 注入 PPE header — 放在 caller headers 之前,允许单个调用点通过
|
|
6582
|
+
// 显式传 `x-tt-env` 来覆盖本次请求(例如某个接口还没在 PPE 上发布)。
|
|
6583
|
+
...(CLI_PPE_ENV
|
|
6584
|
+
? { 'x-use-ppe': '1', 'x-tt-env': CLI_PPE_ENV }
|
|
6585
|
+
: {}),
|
|
6564
6586
|
...(headers || {}),
|
|
6565
6587
|
},
|
|
6566
6588
|
...proxyConfig,
|
|
@@ -6600,7 +6622,6 @@ async function request({ url, method, data, headers, params, }) {
|
|
|
6600
6622
|
}
|
|
6601
6623
|
}
|
|
6602
6624
|
async function download(url, filePath) {
|
|
6603
|
-
// 清理旧文件
|
|
6604
6625
|
if (fs.existsSync(filePath)) {
|
|
6605
6626
|
try {
|
|
6606
6627
|
fs.unlinkSync(filePath);
|
|
@@ -6608,16 +6629,31 @@ async function download(url, filePath) {
|
|
|
6608
6629
|
catch { }
|
|
6609
6630
|
}
|
|
6610
6631
|
const proxyConfig = getAxiosProxyConfig();
|
|
6632
|
+
console.log('[download] start', { url: url.slice(0, 120), filePath, hasProxy: !!proxyConfig.httpsAgent });
|
|
6611
6633
|
try {
|
|
6612
6634
|
const res = await axios.get(url, {
|
|
6613
6635
|
responseType: 'stream',
|
|
6614
|
-
// 让非 2xx 进入 catch
|
|
6615
6636
|
validateStatus: s => s >= 200 && s < 300,
|
|
6637
|
+
// Bail out if the server doesn't start responding within 30s instead of
|
|
6638
|
+
// hanging forever (e.g. proxy misrouting a CDN signed URL).
|
|
6639
|
+
timeout: 30000,
|
|
6616
6640
|
...proxyConfig,
|
|
6617
6641
|
});
|
|
6618
|
-
|
|
6642
|
+
const total = Number(res.headers['content-length'] || 0);
|
|
6643
|
+
let received = 0;
|
|
6644
|
+
let lastLoggedPct = -1;
|
|
6645
|
+
const startedAt = Date.now();
|
|
6619
6646
|
await new Promise((resolve, reject) => {
|
|
6620
6647
|
const writer = fs.createWriteStream(filePath);
|
|
6648
|
+
// Inactivity watchdog: if no bytes arrive for 60s mid-stream, abort.
|
|
6649
|
+
let inactivityTimer = null;
|
|
6650
|
+
const resetInactivity = () => {
|
|
6651
|
+
if (inactivityTimer)
|
|
6652
|
+
clearTimeout(inactivityTimer);
|
|
6653
|
+
inactivityTimer = setTimeout(() => {
|
|
6654
|
+
onError(new Error('download stalled: no data for 60s'));
|
|
6655
|
+
}, 60000);
|
|
6656
|
+
};
|
|
6621
6657
|
const onError = (e) => {
|
|
6622
6658
|
cleanup();
|
|
6623
6659
|
try {
|
|
@@ -6629,28 +6665,42 @@ async function download(url, filePath) {
|
|
|
6629
6665
|
};
|
|
6630
6666
|
const onClose = () => {
|
|
6631
6667
|
cleanup();
|
|
6668
|
+
console.log(`[download] done: ${received} bytes in ${Date.now() - startedAt}ms`);
|
|
6632
6669
|
resolve();
|
|
6633
6670
|
};
|
|
6634
6671
|
const cleanup = () => {
|
|
6672
|
+
if (inactivityTimer)
|
|
6673
|
+
clearTimeout(inactivityTimer);
|
|
6635
6674
|
writer.off('error', onError);
|
|
6636
6675
|
writer.off('close', onClose);
|
|
6637
6676
|
res.data.off('error', onError);
|
|
6677
|
+
res.data.off('data', onData);
|
|
6678
|
+
};
|
|
6679
|
+
const onData = (chunk) => {
|
|
6680
|
+
received += chunk.length;
|
|
6681
|
+
resetInactivity();
|
|
6682
|
+
if (total > 0) {
|
|
6683
|
+
const pct = Math.floor((received / total) * 10) * 10;
|
|
6684
|
+
if (pct !== lastLoggedPct) {
|
|
6685
|
+
lastLoggedPct = pct;
|
|
6686
|
+
console.log(`[download] ${pct}% (${received}/${total})`);
|
|
6687
|
+
}
|
|
6688
|
+
}
|
|
6638
6689
|
};
|
|
6639
6690
|
res.data.on('error', onError);
|
|
6691
|
+
res.data.on('data', onData);
|
|
6640
6692
|
writer.on('error', onError);
|
|
6641
6693
|
writer.on('close', onClose);
|
|
6694
|
+
resetInactivity();
|
|
6642
6695
|
res.data.pipe(writer);
|
|
6643
6696
|
});
|
|
6644
|
-
// 成功
|
|
6645
6697
|
return { ok: true };
|
|
6646
6698
|
}
|
|
6647
6699
|
catch (err) {
|
|
6648
|
-
|
|
6700
|
+
console.log('[download] failed:', err?.message);
|
|
6649
6701
|
if (isAxiosError(err) && err.response?.status === 403) {
|
|
6650
|
-
// 不抛出,让上层自行决定
|
|
6651
6702
|
throw new Error('下载链接已过期,请重新进行分包后重试');
|
|
6652
6703
|
}
|
|
6653
|
-
// 其他错误抛出或返回
|
|
6654
6704
|
throw err;
|
|
6655
6705
|
}
|
|
6656
6706
|
}
|
|
@@ -8899,7 +8949,12 @@ const gameUploadRoute = {
|
|
|
8899
8949
|
},
|
|
8900
8950
|
};
|
|
8901
8951
|
|
|
8952
|
+
// 历史遗留:`remotePipeline.ts` 里的老远程分包接口 spread 了 `DEV_HEADERS`。
|
|
8953
|
+
// 目前全局 PPE 走的是 `libs/api/request.ts` 里的 `CLI_PPE_ENV`,这里保持
|
|
8954
|
+
// 相同的值是为了:一旦以后需要按接口粒度覆盖 PPE(例如远程走 PPE、本地
|
|
8955
|
+
// 走线上),只需在这里填回 header、两处值天然一致。
|
|
8902
8956
|
const BASE_URL = 'https://developers.tiktok.com';
|
|
8957
|
+
const WASM_COLLECT_BASE_URL = `${BASE_URL}/api/wasm-collect/v1`;
|
|
8903
8958
|
const DEV_HEADERS = {
|
|
8904
8959
|
// 'x-use-ppe': '1',
|
|
8905
8960
|
// 'x-tt-env': UNITY_PPE_ENV,
|
|
@@ -8922,6 +8977,8 @@ const UNITY_WASM_SPLIT_CONFIG_FIELD_SCHEME = {
|
|
|
8922
8977
|
WASMSPLITVERSION: `"$WASMSPLITVERSION"`,
|
|
8923
8978
|
ENABLEWASMSPLIT: `"$ENABLEWASMSPLIT"`,
|
|
8924
8979
|
IOS_SUB_JS_FILE_CONFIG: `"$IOS_SUB_JS_FILE_CONFIG"`,
|
|
8980
|
+
ENABLEARCHIVEMODE: `"$ENABLEARCHIVEMODE"`,
|
|
8981
|
+
ARCHIVE_CODE_FILE_MD5: `$ARCHIVE_CODE_FILE_MD5`,
|
|
8925
8982
|
};
|
|
8926
8983
|
|
|
8927
8984
|
const DIR_SPLIT = 'split';
|
|
@@ -8960,69 +9017,6 @@ const CONCURRENCY_LIMIT = 2;
|
|
|
8960
9017
|
const DOWNLOAD_RETRY = 3;
|
|
8961
9018
|
const WASM_SPLIT_CONFIG_FILE_NAME = 'webgl-wasm-split.js';
|
|
8962
9019
|
|
|
8963
|
-
// prepare.ts
|
|
8964
|
-
// 若你的 request 是 axios:你可以添加 maxBodyLength/ maxContentLength 等参数
|
|
8965
|
-
// 若是 got:可直接传 form 实例
|
|
8966
|
-
async function startPrepare(params) {
|
|
8967
|
-
const form = new FormData$1();
|
|
8968
|
-
form.append('desc', params.desc);
|
|
8969
|
-
form.append('wasm_md5', params.wasm_md5);
|
|
8970
|
-
form.append('with_ios', 'true');
|
|
8971
|
-
// 二进制字段:用 ReadStream(推荐)或 Buffer
|
|
8972
|
-
form.append('wasm_file', fs$1.createReadStream(path$1.join(process.cwd(), params.wasm_file_path)), {
|
|
8973
|
-
filename: path$1.basename(params.wasm_file_path),
|
|
8974
|
-
// 部分后端会依赖 content-type;如果不确定就用 application/octet-stream
|
|
8975
|
-
contentType: 'application/wasm',
|
|
8976
|
-
});
|
|
8977
|
-
/**
|
|
8978
|
-
* 兼容 WASM_SYMBOL_FILE_NAME
|
|
8979
|
-
* case 1:项目根目录有 webgl.symbols.json 文件
|
|
8980
|
-
* case 2:项目根目录没有 webgl.symbols.json 文件,但 TTMG_TEMP_DIR 有
|
|
8981
|
-
* 优先读 2
|
|
8982
|
-
*/
|
|
8983
|
-
let symbolFilePath = path$1.join(process.cwd(), TTMG_TEMP_DIR, WASM_SYMBOL_FILE_NAME);
|
|
8984
|
-
if (!fs$1.existsSync(symbolFilePath)) {
|
|
8985
|
-
symbolFilePath = path$1.join(process.cwd(), WASM_SYMBOL_FILE_NAME);
|
|
8986
|
-
}
|
|
8987
|
-
/**
|
|
8988
|
-
* 判断是否有 symbol 文件,有则上传,没有直接接口报错
|
|
8989
|
-
*/
|
|
8990
|
-
if (!fs$1.existsSync(symbolFilePath)) {
|
|
8991
|
-
return {
|
|
8992
|
-
error: {
|
|
8993
|
-
code: 400,
|
|
8994
|
-
message: `${WASM_SYMBOL_FILE_NAME} not found at ${path$1.join(process.cwd())},use unity plugin to rebuild`,
|
|
8995
|
-
client_key: params.client_key,
|
|
8996
|
-
},
|
|
8997
|
-
data: null,
|
|
8998
|
-
ctx: {
|
|
8999
|
-
logid: '',
|
|
9000
|
-
httpStatusCode: 400,
|
|
9001
|
-
},
|
|
9002
|
-
};
|
|
9003
|
-
}
|
|
9004
|
-
form.append('wasm_symbol_file', fs$1.createReadStream(symbolFilePath), {
|
|
9005
|
-
filename: WASM_SYMBOL_FILE_NAME,
|
|
9006
|
-
contentType: 'application/octet-stream',
|
|
9007
|
-
});
|
|
9008
|
-
// 关键:用 form.getHeaders() 获取带 boundary 的 Content-Type
|
|
9009
|
-
const formHeaders = form.getHeaders();
|
|
9010
|
-
return request({
|
|
9011
|
-
url: `${BASE_URL}/api/stark_wasm/v4/post/prepare`,
|
|
9012
|
-
method: 'POST',
|
|
9013
|
-
headers: {
|
|
9014
|
-
...DEV_HEADERS,
|
|
9015
|
-
...formHeaders, // 包含正确的 multipart/form-data; boundary=...
|
|
9016
|
-
},
|
|
9017
|
-
params: {
|
|
9018
|
-
client_key: params.client_key,
|
|
9019
|
-
with_ios: true,
|
|
9020
|
-
},
|
|
9021
|
-
data: form,
|
|
9022
|
-
// 若 request 基于 axios,建议加上以下两项以支持大文件:
|
|
9023
|
-
});
|
|
9024
|
-
}
|
|
9025
|
-
|
|
9026
9020
|
async function withRetry(fn, retries = 3) {
|
|
9027
9021
|
let lastErr;
|
|
9028
9022
|
for (let i = 0; i < retries; i++) {
|
|
@@ -9039,18 +9033,26 @@ async function withRetry(fn, retries = 3) {
|
|
|
9039
9033
|
}
|
|
9040
9034
|
|
|
9041
9035
|
function updateWasmSplitConfig(fields) {
|
|
9036
|
+
const configFilePath = path.join(process.cwd(), WASM_SPLIT_CONFIG_FILE_NAME);
|
|
9037
|
+
let config = fs.readFileSync(configFilePath, 'utf-8');
|
|
9042
9038
|
for (const field in fields) {
|
|
9043
9039
|
const value = fields[field];
|
|
9044
|
-
const isString = typeof value === 'string';
|
|
9045
|
-
const valueStr = isString ? value : String(value);
|
|
9046
|
-
const configFilePath = path.join(process.cwd(), WASM_SPLIT_CONFIG_FILE_NAME);
|
|
9047
9040
|
const placeholder = UNITY_WASM_SPLIT_CONFIG_FIELD_SCHEME[field];
|
|
9048
|
-
|
|
9049
|
-
|
|
9050
|
-
|
|
9051
|
-
|
|
9052
|
-
|
|
9041
|
+
if (!placeholder)
|
|
9042
|
+
continue;
|
|
9043
|
+
let replacement;
|
|
9044
|
+
if (typeof value === 'boolean' || typeof value === 'number') {
|
|
9045
|
+
replacement = String(value);
|
|
9046
|
+
}
|
|
9047
|
+
else if (typeof value === 'string') {
|
|
9048
|
+
replacement = value;
|
|
9049
|
+
}
|
|
9050
|
+
else {
|
|
9051
|
+
replacement = String(value);
|
|
9052
|
+
}
|
|
9053
|
+
config = config.replace(placeholder, replacement);
|
|
9053
9054
|
}
|
|
9055
|
+
fs.writeFileSync(configFilePath, config, 'utf-8');
|
|
9054
9056
|
}
|
|
9055
9057
|
|
|
9056
9058
|
async function compressWasmFile(wasmFilePath, compressedFilePath) {
|
|
@@ -9062,7 +9064,11 @@ async function compressWasmFile(wasmFilePath, compressedFilePath) {
|
|
|
9062
9064
|
}
|
|
9063
9065
|
function compressArrayBuffer(arrayBuffer) {
|
|
9064
9066
|
return new Promise((resolve, reject) => {
|
|
9065
|
-
const compressStream = zlib.createBrotliCompress(
|
|
9067
|
+
const compressStream = zlib.createBrotliCompress({
|
|
9068
|
+
params: {
|
|
9069
|
+
[zlib.constants.BROTLI_PARAM_QUALITY]: 9,
|
|
9070
|
+
},
|
|
9071
|
+
});
|
|
9066
9072
|
compressStream.write(Buffer.from(arrayBuffer));
|
|
9067
9073
|
compressStream.end();
|
|
9068
9074
|
const compressedChunks = [];
|
|
@@ -9098,176 +9104,899 @@ function keepCacheSync({ entryDir, originalWasmPath, originalSplitConfigPath, })
|
|
|
9098
9104
|
};
|
|
9099
9105
|
}
|
|
9100
9106
|
|
|
9101
|
-
|
|
9102
|
-
|
|
9103
|
-
|
|
9104
|
-
|
|
9105
|
-
|
|
9106
|
-
|
|
9107
|
-
|
|
9108
|
-
|
|
9109
|
-
|
|
9110
|
-
|
|
9111
|
-
|
|
9112
|
-
|
|
9107
|
+
/**
|
|
9108
|
+
* Restore webgl-wasm-split.js from the cached original (with placeholders).
|
|
9109
|
+
* Called at the start of each prepare so that pipeline-specific values can be
|
|
9110
|
+
* applied deterministically, regardless of previous runs.
|
|
9111
|
+
* No-op if the cache does not yet exist (first run).
|
|
9112
|
+
*/
|
|
9113
|
+
function restoreSplitConfigFromCache(entryDir = process.cwd()) {
|
|
9114
|
+
const cachedConfigPath = path.join(entryDir, WASM_SPLIT_CACHE_DIR, path.basename(WASM_SPLIT_CONFIG_FILE_NAME));
|
|
9115
|
+
const targetConfigPath = path.join(entryDir, WASM_SPLIT_CONFIG_FILE_NAME);
|
|
9116
|
+
if (fs.existsSync(cachedConfigPath)) {
|
|
9117
|
+
fs.copyFileSync(cachedConfigPath, targetConfigPath);
|
|
9118
|
+
}
|
|
9119
|
+
}
|
|
9120
|
+
|
|
9121
|
+
/**
|
|
9122
|
+
* Restore the project from the backup cache:
|
|
9123
|
+
* - original (unmodified) wasm file back into its `wasmcode/<file>.br` location
|
|
9124
|
+
* - webgl-wasm-split.js back to its template (with placeholders)
|
|
9125
|
+
* - game.json back to its pre-split version
|
|
9126
|
+
* - remove generated sub-package directories (wasmcode-android, wasmcode1-android, wasmcode-ios, etc.)
|
|
9127
|
+
*
|
|
9128
|
+
* Shared by both local and remote reset/rollback flows.
|
|
9129
|
+
*/
|
|
9130
|
+
function restoreFromCache(entryDir = process.cwd()) {
|
|
9131
|
+
const cacheDir = path.join(entryDir, WASM_SPLIT_CACHE_DIR);
|
|
9132
|
+
if (fs.existsSync(cacheDir)) {
|
|
9133
|
+
const targetWasmBrPath = fs
|
|
9134
|
+
.readdirSync(cacheDir)
|
|
9135
|
+
.find(item => item.endsWith('.br'));
|
|
9136
|
+
if (targetWasmBrPath) {
|
|
9137
|
+
const destWasmBrPath = path.join(entryDir, WASM_SPLIT_SUBPACKAGE_CONFIG.origin.root, path.basename(targetWasmBrPath));
|
|
9138
|
+
ensureDirSync(path.dirname(destWasmBrPath));
|
|
9139
|
+
fs.copyFileSync(path.join(cacheDir, targetWasmBrPath), destWasmBrPath);
|
|
9140
|
+
}
|
|
9141
|
+
}
|
|
9142
|
+
const splitConfigCachePath = path.join(cacheDir, WASM_SPLIT_CONFIG_FILE_NAME);
|
|
9143
|
+
if (fs.existsSync(splitConfigCachePath)) {
|
|
9144
|
+
fs.copyFileSync(splitConfigCachePath, path.join(entryDir, WASM_SPLIT_CONFIG_FILE_NAME));
|
|
9145
|
+
}
|
|
9146
|
+
const gameJsonCachePath = path.join(cacheDir, 'game.json');
|
|
9147
|
+
if (fs.existsSync(gameJsonCachePath)) {
|
|
9148
|
+
fs.copyFileSync(gameJsonCachePath, path.join(entryDir, 'game.json'));
|
|
9149
|
+
}
|
|
9150
|
+
const subDirs = [
|
|
9151
|
+
WASM_SPLIT_SUBPACKAGE_CONFIG.androidMain.root,
|
|
9152
|
+
WASM_SPLIT_SUBPACKAGE_CONFIG.androidSub.root,
|
|
9153
|
+
WASM_SPLIT_SUBPACKAGE_CONFIG.ios.root,
|
|
9154
|
+
];
|
|
9155
|
+
for (const subDir of subDirs) {
|
|
9156
|
+
const full = path.join(entryDir, subDir);
|
|
9157
|
+
if (fs.existsSync(full)) {
|
|
9158
|
+
fs.rmSync(full, { recursive: true });
|
|
9159
|
+
}
|
|
9160
|
+
}
|
|
9161
|
+
}
|
|
9162
|
+
|
|
9163
|
+
async function decompressWasmFile(inputPath, outputPath) {
|
|
9164
|
+
const compressed = await fs.promises.readFile(inputPath);
|
|
9165
|
+
const decompressed = await new Promise((resolve, reject) => {
|
|
9166
|
+
zlib.brotliDecompress(compressed, (err, result) => {
|
|
9167
|
+
if (err)
|
|
9168
|
+
reject(err);
|
|
9169
|
+
else
|
|
9170
|
+
resolve(result);
|
|
9171
|
+
});
|
|
9113
9172
|
});
|
|
9173
|
+
await fs.promises.writeFile(outputPath, decompressed);
|
|
9174
|
+
}
|
|
9175
|
+
|
|
9176
|
+
function computeFileMd5Sync(filePath) {
|
|
9177
|
+
const content = fs.readFileSync(filePath);
|
|
9178
|
+
return crypto.createHash('md5').update(content).digest('hex');
|
|
9179
|
+
}
|
|
9180
|
+
|
|
9181
|
+
let cached = null;
|
|
9182
|
+
function getGameJson() {
|
|
9183
|
+
if (cached)
|
|
9184
|
+
return cached;
|
|
9185
|
+
const filePath = path$1.join(process.cwd(), 'game.json');
|
|
9186
|
+
if (fs$1.existsSync(filePath)) {
|
|
9187
|
+
try {
|
|
9188
|
+
cached = JSON.parse(fs$1.readFileSync(filePath, 'utf-8'));
|
|
9189
|
+
}
|
|
9190
|
+
catch {
|
|
9191
|
+
cached = {};
|
|
9192
|
+
}
|
|
9193
|
+
}
|
|
9194
|
+
else {
|
|
9195
|
+
cached = {};
|
|
9196
|
+
}
|
|
9197
|
+
return cached;
|
|
9198
|
+
}
|
|
9199
|
+
|
|
9200
|
+
function metaFilePath(entryDir = process.cwd()) {
|
|
9201
|
+
return path__namespace$1.join(entryDir, TTMG_TEMP_DIR, 'prepared-meta.json');
|
|
9202
|
+
}
|
|
9203
|
+
function writePreparedMeta(meta, entryDir = process.cwd()) {
|
|
9204
|
+
const target = metaFilePath(entryDir);
|
|
9205
|
+
fs__namespace$1.mkdirSync(path__namespace$1.dirname(target), { recursive: true });
|
|
9206
|
+
const payload = {
|
|
9207
|
+
...meta,
|
|
9208
|
+
preparedAt: new Date().toISOString(),
|
|
9209
|
+
};
|
|
9210
|
+
fs__namespace$1.writeFileSync(target, JSON.stringify(payload, null, 2), 'utf-8');
|
|
9211
|
+
}
|
|
9212
|
+
function readPreparedMeta(entryDir = process.cwd()) {
|
|
9213
|
+
const target = metaFilePath(entryDir);
|
|
9214
|
+
if (!fs__namespace$1.existsSync(target))
|
|
9215
|
+
return null;
|
|
9114
9216
|
try {
|
|
9115
|
-
const
|
|
9116
|
-
const
|
|
9117
|
-
if (
|
|
9118
|
-
|
|
9119
|
-
|
|
9120
|
-
|
|
9121
|
-
|
|
9122
|
-
|
|
9123
|
-
|
|
9124
|
-
|
|
9125
|
-
|
|
9126
|
-
|
|
9127
|
-
|
|
9128
|
-
|
|
9129
|
-
|
|
9130
|
-
|
|
9131
|
-
|
|
9132
|
-
|
|
9133
|
-
|
|
9134
|
-
|
|
9135
|
-
|
|
9136
|
-
|
|
9137
|
-
|
|
9138
|
-
|
|
9139
|
-
|
|
9140
|
-
|
|
9141
|
-
|
|
9142
|
-
|
|
9143
|
-
|
|
9144
|
-
|
|
9145
|
-
|
|
9146
|
-
|
|
9147
|
-
|
|
9148
|
-
|
|
9149
|
-
|
|
9150
|
-
|
|
9151
|
-
|
|
9152
|
-
|
|
9153
|
-
|
|
9154
|
-
|
|
9155
|
-
|
|
9156
|
-
|
|
9157
|
-
|
|
9158
|
-
|
|
9159
|
-
|
|
9160
|
-
|
|
9161
|
-
|
|
9162
|
-
|
|
9163
|
-
|
|
9164
|
-
|
|
9165
|
-
|
|
9166
|
-
|
|
9167
|
-
|
|
9168
|
-
|
|
9169
|
-
|
|
9170
|
-
|
|
9171
|
-
|
|
9172
|
-
|
|
9173
|
-
|
|
9174
|
-
|
|
9175
|
-
|
|
9176
|
-
|
|
9177
|
-
|
|
9178
|
-
|
|
9179
|
-
|
|
9180
|
-
|
|
9217
|
+
const raw = fs__namespace$1.readFileSync(target, 'utf-8');
|
|
9218
|
+
const parsed = JSON.parse(raw);
|
|
9219
|
+
if (typeof parsed?.preparedWasmMd5 === 'string' &&
|
|
9220
|
+
typeof parsed?.codePath === 'string' &&
|
|
9221
|
+
parsed.preparedWasmMd5.length === 32) {
|
|
9222
|
+
return parsed;
|
|
9223
|
+
}
|
|
9224
|
+
return null;
|
|
9225
|
+
}
|
|
9226
|
+
catch {
|
|
9227
|
+
return null;
|
|
9228
|
+
}
|
|
9229
|
+
}
|
|
9230
|
+
/**
|
|
9231
|
+
* Return the current md5 of the wasm file referenced by `prepared-meta.json`
|
|
9232
|
+
* or null if the file is missing / meta isn't present. Caller compares the
|
|
9233
|
+
* result to `meta.preparedWasmMd5` — mismatch means the project's wasm
|
|
9234
|
+
* has drifted from the prepared output (Unity re-build etc.) and the
|
|
9235
|
+
* project should be walked back through the prepare step before collect
|
|
9236
|
+
* can produce useful data.
|
|
9237
|
+
*/
|
|
9238
|
+
function computeCurrentProjectWasmMd5(entryDir = process.cwd()) {
|
|
9239
|
+
const meta = readPreparedMeta(entryDir);
|
|
9240
|
+
if (!meta)
|
|
9241
|
+
return null;
|
|
9242
|
+
const absolutePath = path__namespace$1.join(entryDir, meta.codePath);
|
|
9243
|
+
if (!fs__namespace$1.existsSync(absolutePath))
|
|
9244
|
+
return null;
|
|
9245
|
+
const currentMd5 = crypto$1
|
|
9246
|
+
.createHash('md5')
|
|
9247
|
+
.update(fs__namespace$1.readFileSync(absolutePath))
|
|
9248
|
+
.digest('hex');
|
|
9249
|
+
return { meta, currentMd5 };
|
|
9250
|
+
}
|
|
9251
|
+
|
|
9252
|
+
const state = {
|
|
9253
|
+
pipelineMode: 'local',
|
|
9254
|
+
originalWasmPath: '',
|
|
9255
|
+
preparedWasmPath: '',
|
|
9256
|
+
codePath: '',
|
|
9257
|
+
splitOutputDir: '',
|
|
9258
|
+
splitMeta: null,
|
|
9259
|
+
totalWasmFuncCount: 0,
|
|
9260
|
+
wasmSize: 0,
|
|
9261
|
+
isArchiveMode: true,
|
|
9262
|
+
};
|
|
9263
|
+
function getLocalState() {
|
|
9264
|
+
return state;
|
|
9265
|
+
}
|
|
9266
|
+
function setLocalState(partial) {
|
|
9267
|
+
Object.assign(state, partial);
|
|
9268
|
+
}
|
|
9269
|
+
|
|
9270
|
+
async function startPrepare$1(params) {
|
|
9271
|
+
const tempDir = path$1.join(process.cwd(), TTMG_TEMP_DIR);
|
|
9272
|
+
ensureDirSync(tempDir);
|
|
9273
|
+
const inputPath = path$1.join(process.cwd(), params.wasm_file_path);
|
|
9274
|
+
let rawWasmPath = path$1.join(tempDir, 'original.wasm');
|
|
9275
|
+
if (inputPath.endsWith('.br')) {
|
|
9276
|
+
await decompressWasmFile(inputPath, rawWasmPath);
|
|
9277
|
+
}
|
|
9278
|
+
else {
|
|
9279
|
+
fs$1.copyFileSync(inputPath, rawWasmPath);
|
|
9280
|
+
}
|
|
9281
|
+
const preparedWasmPath = path$1.join(tempDir, 'prepared.wasm');
|
|
9282
|
+
try {
|
|
9283
|
+
const result = ttmgWasmtool.prepare(rawWasmPath, preparedWasmPath);
|
|
9284
|
+
console.log(`[wasmtool] prepare done: ${result.outputSize} bytes, ${result.timeCost}s`);
|
|
9285
|
+
const gameJson = getGameJson();
|
|
9286
|
+
const totalWasmFuncCount = gameJson.wasmFuncCount ?? 0;
|
|
9287
|
+
const wasmSize = fs$1.existsSync(inputPath)
|
|
9288
|
+
? fs$1.statSync(inputPath).size
|
|
9289
|
+
: 0;
|
|
9290
|
+
setLocalState({
|
|
9291
|
+
originalWasmPath: rawWasmPath,
|
|
9292
|
+
preparedWasmPath,
|
|
9293
|
+
codePath: params.wasm_file_path,
|
|
9294
|
+
totalWasmFuncCount,
|
|
9295
|
+
wasmSize,
|
|
9296
|
+
});
|
|
9297
|
+
keepCacheSync({
|
|
9298
|
+
entryDir: process.cwd(),
|
|
9299
|
+
originalWasmPath: params.wasm_file_path,
|
|
9300
|
+
originalSplitConfigPath: WASM_SPLIT_CONFIG_FILE_NAME,
|
|
9301
|
+
});
|
|
9302
|
+
// Start from cached (placeholder) config so pipeline switching is deterministic
|
|
9303
|
+
restoreSplitConfigFromCache();
|
|
9304
|
+
const willReplaceWasmPath = path$1.join(process.cwd(), params.wasm_file_path);
|
|
9305
|
+
// Diagnostic: prove prepare actually produced a different binary
|
|
9306
|
+
// (size should grow noticeably because every function body is prefixed
|
|
9307
|
+
// with a scwebgl.logCall(funcIndex) call).
|
|
9308
|
+
const rawSize = fs$1.existsSync(rawWasmPath) ? fs$1.statSync(rawWasmPath).size : 0;
|
|
9309
|
+
const preparedSize = fs$1.existsSync(preparedWasmPath)
|
|
9310
|
+
? fs$1.statSync(preparedWasmPath).size
|
|
9311
|
+
: 0;
|
|
9312
|
+
const rawMd5 = fs$1.existsSync(rawWasmPath)
|
|
9313
|
+
? crypto$1.createHash('md5').update(fs$1.readFileSync(rawWasmPath)).digest('hex')
|
|
9314
|
+
: '<missing>';
|
|
9315
|
+
const preparedMd5 = fs$1.existsSync(preparedWasmPath)
|
|
9316
|
+
? crypto$1.createHash('md5').update(fs$1.readFileSync(preparedWasmPath)).digest('hex')
|
|
9317
|
+
: '<missing>';
|
|
9318
|
+
console.log(`[wasmtool] prepare sanity: raw(size=${rawSize} md5=${rawMd5}) -> prepared(size=${preparedSize} md5=${preparedMd5}) delta=${preparedSize - rawSize}`);
|
|
9319
|
+
if (preparedSize <= rawSize || preparedMd5 === rawMd5) {
|
|
9320
|
+
console.warn('[wasmtool] WARNING: prepared wasm is not larger / md5 is unchanged vs raw wasm. Instrumentation likely did not happen.');
|
|
9321
|
+
}
|
|
9322
|
+
console.log('[wasmtool] compressing prepared wasm (quality=9)...');
|
|
9323
|
+
await compressWasmFile(preparedWasmPath, willReplaceWasmPath);
|
|
9324
|
+
console.log('[wasmtool] compressed and written to project');
|
|
9325
|
+
// Diagnostic: confirm the file the client actually fetches was overwritten,
|
|
9326
|
+
// and compare to the cached original brotli so we can prove on-disk replacement.
|
|
9327
|
+
const replacedSize = fs$1.existsSync(willReplaceWasmPath)
|
|
9328
|
+
? fs$1.statSync(willReplaceWasmPath).size
|
|
9329
|
+
: 0;
|
|
9330
|
+
const replacedMd5 = fs$1.existsSync(willReplaceWasmPath)
|
|
9331
|
+
? crypto$1.createHash('md5').update(fs$1.readFileSync(willReplaceWasmPath)).digest('hex')
|
|
9332
|
+
: '<missing>';
|
|
9333
|
+
const cachedOriginalBr = path$1.join(process.cwd(), TTMG_TEMP_DIR, 'wasmcode', path$1.basename(params.wasm_file_path));
|
|
9334
|
+
const cachedOriginalSize = fs$1.existsSync(cachedOriginalBr)
|
|
9335
|
+
? fs$1.statSync(cachedOriginalBr).size
|
|
9336
|
+
: 0;
|
|
9337
|
+
const cachedOriginalMd5 = fs$1.existsSync(cachedOriginalBr)
|
|
9338
|
+
? crypto$1
|
|
9339
|
+
.createHash('md5')
|
|
9340
|
+
.update(fs$1.readFileSync(cachedOriginalBr))
|
|
9341
|
+
.digest('hex')
|
|
9342
|
+
: '<missing>';
|
|
9343
|
+
console.log(`[wasmtool] on-disk replace check: project=${params.wasm_file_path} size=${replacedSize} md5=${replacedMd5} | cached-original size=${cachedOriginalSize} md5=${cachedOriginalMd5}`);
|
|
9344
|
+
if (replacedMd5 === cachedOriginalMd5) {
|
|
9345
|
+
console.warn('[wasmtool] WARNING: project wasm md5 matches cached-original md5. The file was not actually replaced with the instrumented build.');
|
|
9181
9346
|
}
|
|
9182
9347
|
else {
|
|
9183
|
-
|
|
9184
|
-
isSuccess: false,
|
|
9185
|
-
error: {
|
|
9186
|
-
code: res.data?.code,
|
|
9187
|
-
message: res.data?.message,
|
|
9188
|
-
},
|
|
9189
|
-
ctx: res?.ctx,
|
|
9190
|
-
};
|
|
9348
|
+
console.log('[wasmtool] OK: project wasm differs from cached-original — instrumented wasm is on disk.');
|
|
9191
9349
|
}
|
|
9350
|
+
// Local pipeline uses the new wasm-collect/v1/report API + archive sub-wasm.
|
|
9351
|
+
// ORIGINALWASMMD5 must be set now (not only at split time) so the plugin
|
|
9352
|
+
// reports the correct wasm_md5 during the collect phase.
|
|
9353
|
+
updateWasmSplitConfig({
|
|
9354
|
+
ENABLEWASMCOLLECT: true,
|
|
9355
|
+
ENABLEARCHIVEMODE: true,
|
|
9356
|
+
ORIGINALWASMMD5: params.wasm_md5,
|
|
9357
|
+
});
|
|
9358
|
+
console.log('[wasmtool] wasm split config updated (local pipeline: archive=true)');
|
|
9359
|
+
// Disk-persisted anchor for "wasm drift" detection in
|
|
9360
|
+
// `game-wasm-split-config` route. Stores the md5 that prepare just
|
|
9361
|
+
// wrote into the project alongside the project-relative path. The
|
|
9362
|
+
// route reads this back on every Modal open, recomputes the md5 of
|
|
9363
|
+
// the file on disk, and if they differ (Unity re-build, git
|
|
9364
|
+
// checkout, etc.) suppresses `enableWasmCollect=true` in the
|
|
9365
|
+
// response so the IDE goes back through prepare instead of dropping
|
|
9366
|
+
// the user straight into Collect with an un-instrumented wasm on
|
|
9367
|
+
// the device. See `preparedMeta.ts` for full rationale.
|
|
9368
|
+
writePreparedMeta({
|
|
9369
|
+
preparedWasmMd5: replacedMd5,
|
|
9370
|
+
codePath: params.wasm_file_path,
|
|
9371
|
+
});
|
|
9372
|
+
console.log(`[wasmtool] prepared-meta written: md5=${replacedMd5} codePath=${params.wasm_file_path}`);
|
|
9373
|
+
return {
|
|
9374
|
+
data: {
|
|
9375
|
+
code: 0,
|
|
9376
|
+
message: 'success',
|
|
9377
|
+
result: { md5: params.wasm_md5 },
|
|
9378
|
+
},
|
|
9379
|
+
error: null,
|
|
9380
|
+
ctx: { logid: 'local', httpStatusCode: 200 },
|
|
9381
|
+
};
|
|
9192
9382
|
}
|
|
9193
|
-
catch (
|
|
9383
|
+
catch (err) {
|
|
9194
9384
|
return {
|
|
9195
|
-
|
|
9385
|
+
data: null,
|
|
9196
9386
|
error: {
|
|
9197
|
-
code:
|
|
9198
|
-
message:
|
|
9387
|
+
code: 500,
|
|
9388
|
+
message: err instanceof Error ? err.message : String(err),
|
|
9199
9389
|
},
|
|
9200
|
-
ctx:
|
|
9390
|
+
ctx: { logid: 'local', httpStatusCode: 500 },
|
|
9201
9391
|
};
|
|
9202
9392
|
}
|
|
9203
9393
|
}
|
|
9204
9394
|
|
|
9205
|
-
|
|
9206
|
-
|
|
9207
|
-
|
|
9395
|
+
/**
|
|
9396
|
+
* Local pipeline: startPrepareLocal already compressed/replaced the wasm and
|
|
9397
|
+
* updated webgl-wasm-split.js, so this step is a no-op that just emits UI
|
|
9398
|
+
* status events for parity with the remote flow.
|
|
9399
|
+
*/
|
|
9400
|
+
async function downloadPrepared$1(_data) {
|
|
9401
|
+
const { preparedWasmPath } = getLocalState();
|
|
9402
|
+
if (!preparedWasmPath) {
|
|
9403
|
+
return {
|
|
9404
|
+
isSuccess: false,
|
|
9405
|
+
error: { code: 404, message: 'Prepared wasm not found. Run prepare first.' },
|
|
9406
|
+
};
|
|
9407
|
+
}
|
|
9408
|
+
wsServer.sendUnitySplitStatus({ status: 'update_wasm_split_config_done' });
|
|
9409
|
+
return { isSuccess: true, ctx: { logid: 'local' } };
|
|
9410
|
+
}
|
|
9411
|
+
|
|
9412
|
+
async function getCollectedFuncIds$1({ client_key, wasm_md5, }) {
|
|
9413
|
+
const res = await request({
|
|
9414
|
+
url: `${WASM_COLLECT_BASE_URL}/progress`,
|
|
9208
9415
|
method: 'GET',
|
|
9209
|
-
headers: DEV_HEADERS,
|
|
9210
9416
|
params: {
|
|
9211
|
-
client_key,
|
|
9417
|
+
app_id: client_key,
|
|
9212
9418
|
wasm_md5,
|
|
9213
9419
|
},
|
|
9214
9420
|
});
|
|
9421
|
+
const funcCount = res?.data?.func_count ?? 0;
|
|
9422
|
+
return {
|
|
9423
|
+
data: {
|
|
9424
|
+
code: res?.data?.code ?? 0,
|
|
9425
|
+
message: 'success',
|
|
9426
|
+
result: {
|
|
9427
|
+
collected_func_count: funcCount,
|
|
9428
|
+
data_size: funcCount,
|
|
9429
|
+
real_data_size: funcCount,
|
|
9430
|
+
collect_state: res?.data?.collect_state,
|
|
9431
|
+
},
|
|
9432
|
+
},
|
|
9433
|
+
error: res.error,
|
|
9434
|
+
ctx: res.ctx,
|
|
9435
|
+
};
|
|
9215
9436
|
}
|
|
9216
9437
|
|
|
9217
|
-
|
|
9218
|
-
|
|
9219
|
-
|
|
9438
|
+
/**
|
|
9439
|
+
* POST /start — opens a collect session (Portal-authenticated).
|
|
9440
|
+
*
|
|
9441
|
+
* Idempotent on the server: re-opening an already-open session just refreshes
|
|
9442
|
+
* `started_at`; only `reset: true` wipes history.
|
|
9443
|
+
*
|
|
9444
|
+
* Default `reset` is `false` to mirror the server-side default documented in
|
|
9445
|
+
* `wasm_api.md` §5.1 — "页面刷新 / 恢复" must NOT silently destroy data. The
|
|
9446
|
+
* "fresh run" semantic (e.g. user clicks "重新开始分包") is the responsibility
|
|
9447
|
+
* of the caller, which must explicitly pass `reset: true`. See `setCollect`
|
|
9448
|
+
* for the CLI-level wiring of those two paths.
|
|
9449
|
+
*
|
|
9450
|
+
* NOTE on naming: the server route is flat (`/start`, not `/session/start`).
|
|
9451
|
+
* Our local symbol stays `startWasmSession` because it's the "start collect
|
|
9452
|
+
* session" lifecycle primitive from the IDE's perspective.
|
|
9453
|
+
*/
|
|
9454
|
+
async function startWasmSession({ client_key, wasm_md5, reset, }) {
|
|
9455
|
+
const res = await request({
|
|
9456
|
+
url: `${WASM_COLLECT_BASE_URL}/start`,
|
|
9220
9457
|
method: 'POST',
|
|
9221
9458
|
data: {
|
|
9222
|
-
client_key,
|
|
9459
|
+
app_id: client_key,
|
|
9223
9460
|
wasm_md5,
|
|
9461
|
+
reset: reset ?? false,
|
|
9224
9462
|
},
|
|
9225
|
-
headers: DEV_HEADERS,
|
|
9226
9463
|
});
|
|
9464
|
+
return {
|
|
9465
|
+
data: res.data
|
|
9466
|
+
? {
|
|
9467
|
+
code: res.data.code ?? 0,
|
|
9468
|
+
message: res.data.message || 'success',
|
|
9469
|
+
result: {
|
|
9470
|
+
collect_state: res.data.collect_state,
|
|
9471
|
+
started_at: res.data.started_at,
|
|
9472
|
+
},
|
|
9473
|
+
}
|
|
9474
|
+
: null,
|
|
9475
|
+
error: res.error,
|
|
9476
|
+
ctx: res.ctx,
|
|
9477
|
+
};
|
|
9227
9478
|
}
|
|
9228
9479
|
|
|
9229
|
-
|
|
9230
|
-
|
|
9231
|
-
|
|
9480
|
+
/**
|
|
9481
|
+
* "开始收集" 的本地 pipeline 实现。语义上等价于老远程流程的
|
|
9482
|
+
* `stark_wasm/v4/post/set_collecting`:**打开 server 端的 collect 窗口**,
|
|
9483
|
+
* 让 plugin 之后的 `/report` 请求能落库。
|
|
9484
|
+
*
|
|
9485
|
+
* 做三件事(顺序敏感):
|
|
9486
|
+
* 1. `POST /start` 打开 session —— 失败必须立即返回给 IDE,
|
|
9487
|
+
* 否则 UI 会让用户进"正在收集"但实际 plugin 所有上报都会被 fail-close
|
|
9488
|
+
* 丢弃,场面非常悲伤。
|
|
9489
|
+
* 2. 成功后上传符号表(`/symbols`)。这一步故意不 await、错误仅 warn —
|
|
9490
|
+
* 符号表只是给 server 端后续调试用的 debug 信息,丢了也不影响分包主链路。
|
|
9491
|
+
* 3. 返回 `{code: 0}`。
|
|
9492
|
+
*
|
|
9493
|
+
* 两种调用语义(与 `wasm_api.md` §5.1 对齐):
|
|
9494
|
+
* - 默认(`resume` 缺省 / false)—— 用户点"开始收集 / 重新开始分包",
|
|
9495
|
+
* 发 `reset: true`,服务端清空历史。这是历史行为,对应 IDE 上的
|
|
9496
|
+
* "willCollect → startCollect" 主入口。
|
|
9497
|
+
* - `resume: true` —— 页面刷新 / 恢复继续,发 `reset: false`,幂等
|
|
9498
|
+
* 打开 session、保留已有 func_ids。需要这条路径的 caller(如 IDE
|
|
9499
|
+
* 重新挂载组件检测到 server `collect_state: "open"` 想接续)必须
|
|
9500
|
+
* 显式传,避免误清。
|
|
9501
|
+
*
|
|
9502
|
+
* Session 生命周期对前端透明——IDE 只知道"开始收集 / 完成收集"两个动作,
|
|
9503
|
+
* `/start` 和 `/finish` 都被封在本地 dispatcher 内。这样远程 pipeline(没有
|
|
9504
|
+
* session 概念)和本地 pipeline(有 session)在 IDE 层看起来是对称的。
|
|
9505
|
+
*/
|
|
9506
|
+
async function setCollect$1({ client_key, wasm_md5, resume, }) {
|
|
9507
|
+
const startRes = await startWasmSession({
|
|
9508
|
+
client_key,
|
|
9509
|
+
wasm_md5,
|
|
9510
|
+
reset: !resume,
|
|
9511
|
+
});
|
|
9512
|
+
if (startRes.error || !startRes.data || startRes.data.code !== 0) {
|
|
9513
|
+
// /start is invoked internally by setCollect now; IDE only sees this
|
|
9514
|
+
// bubbled up as a generic "开始收集失败" toast, so dump a structured
|
|
9515
|
+
// one-liner here with logid — the single most useful field when
|
|
9516
|
+
// asking backend to look up what happened on their side.
|
|
9517
|
+
const code = startRes.error?.code ?? startRes.data?.code ?? -1;
|
|
9518
|
+
const message = startRes.error?.message ||
|
|
9519
|
+
startRes.data?.message ||
|
|
9520
|
+
'Open collect session failed';
|
|
9521
|
+
const logid = startRes.ctx?.logid || 'n/a';
|
|
9522
|
+
console.error(`[wasm-collect] /start failed: code=${code} message=${message} logid=${logid}`);
|
|
9523
|
+
return {
|
|
9524
|
+
data: startRes.data ?? null,
|
|
9525
|
+
error: startRes.error ?? { code, message },
|
|
9526
|
+
ctx: startRes.ctx,
|
|
9527
|
+
};
|
|
9528
|
+
}
|
|
9529
|
+
let symbolPath = path$1.join(process.cwd(), WASM_SYMBOL_FILE_NAME);
|
|
9530
|
+
if (!fs$1.existsSync(symbolPath)) {
|
|
9531
|
+
symbolPath = path$1.join(process.cwd(), TTMG_TEMP_DIR, WASM_SYMBOL_FILE_NAME);
|
|
9532
|
+
}
|
|
9533
|
+
if (fs$1.existsSync(symbolPath)) {
|
|
9534
|
+
const symbols = fs$1.readFileSync(symbolPath, 'utf-8');
|
|
9535
|
+
request({
|
|
9536
|
+
url: `${WASM_COLLECT_BASE_URL}/symbols`,
|
|
9537
|
+
method: 'POST',
|
|
9538
|
+
data: {
|
|
9539
|
+
app_id: client_key,
|
|
9540
|
+
wasm_md5,
|
|
9541
|
+
symbols,
|
|
9542
|
+
},
|
|
9543
|
+
}).catch(err => {
|
|
9544
|
+
console.warn('[wasmtool] Failed to upload symbols:', err);
|
|
9545
|
+
});
|
|
9546
|
+
}
|
|
9547
|
+
return {
|
|
9548
|
+
data: { code: 0, message: 'success', result: {} },
|
|
9549
|
+
error: null,
|
|
9550
|
+
ctx: { logid: 'local', httpStatusCode: 200 },
|
|
9551
|
+
};
|
|
9552
|
+
}
|
|
9553
|
+
|
|
9554
|
+
async function getCollecttingInfo$1({ client_key, wasm_md5, }) {
|
|
9555
|
+
const res = await request({
|
|
9556
|
+
url: `${WASM_COLLECT_BASE_URL}/progress`,
|
|
9232
9557
|
method: 'GET',
|
|
9233
|
-
headers: DEV_HEADERS,
|
|
9234
9558
|
params: {
|
|
9235
|
-
client_key,
|
|
9559
|
+
app_id: client_key,
|
|
9236
9560
|
wasm_md5,
|
|
9237
9561
|
},
|
|
9238
9562
|
});
|
|
9563
|
+
const { totalWasmFuncCount } = getLocalState();
|
|
9564
|
+
// Fall back to game.json.wasmFuncCount so the total survives CLI restarts.
|
|
9565
|
+
const gameJsonFuncCount = Number(getGameJson()?.wasmFuncCount) || 0;
|
|
9566
|
+
return {
|
|
9567
|
+
data: {
|
|
9568
|
+
code: res?.data?.code ?? 0,
|
|
9569
|
+
message: 'success',
|
|
9570
|
+
result: {
|
|
9571
|
+
app_id: client_key,
|
|
9572
|
+
wasm_md5,
|
|
9573
|
+
collected_func_count: res?.data?.func_count ?? 0,
|
|
9574
|
+
total_wasm_func_count: gameJsonFuncCount || totalWasmFuncCount || 0,
|
|
9575
|
+
collect_state: res?.data?.collect_state,
|
|
9576
|
+
},
|
|
9577
|
+
},
|
|
9578
|
+
error: res.error,
|
|
9579
|
+
ctx: res.ctx,
|
|
9580
|
+
};
|
|
9239
9581
|
}
|
|
9240
9582
|
|
|
9241
|
-
|
|
9242
|
-
|
|
9243
|
-
|
|
9244
|
-
|
|
9583
|
+
/**
|
|
9584
|
+
* POST /finish — closes a collect session and returns the final `func_count`
|
|
9585
|
+
* so the IDE can surface "本次共收集 N 个函数" in the success dialog.
|
|
9586
|
+
* Idempotent on the server.
|
|
9587
|
+
*
|
|
9588
|
+
* NOTE on naming: the server route is flat (`/finish`, not `/session/finish`).
|
|
9589
|
+
* The local symbol keeps `finishWasmSession` for symmetry with `startWasmSession`.
|
|
9590
|
+
*/
|
|
9591
|
+
async function finishWasmSession({ client_key, wasm_md5, }) {
|
|
9592
|
+
const res = await request({
|
|
9593
|
+
url: `${WASM_COLLECT_BASE_URL}/finish`,
|
|
9245
9594
|
method: 'POST',
|
|
9246
|
-
headers: {
|
|
9247
|
-
...DEV_HEADERS,
|
|
9248
|
-
},
|
|
9249
9595
|
data: {
|
|
9250
|
-
client_key,
|
|
9596
|
+
app_id: client_key,
|
|
9251
9597
|
wasm_md5,
|
|
9252
9598
|
},
|
|
9253
9599
|
});
|
|
9600
|
+
return {
|
|
9601
|
+
data: res.data
|
|
9602
|
+
? {
|
|
9603
|
+
code: res.data.code ?? 0,
|
|
9604
|
+
message: res.data.message || 'success',
|
|
9605
|
+
result: {
|
|
9606
|
+
collect_state: res.data.collect_state,
|
|
9607
|
+
func_count: res.data.func_count ?? 0,
|
|
9608
|
+
finished_at: res.data.finished_at,
|
|
9609
|
+
},
|
|
9610
|
+
}
|
|
9611
|
+
: null,
|
|
9612
|
+
error: res.error,
|
|
9613
|
+
ctx: res.ctx,
|
|
9614
|
+
};
|
|
9254
9615
|
}
|
|
9255
9616
|
|
|
9256
|
-
|
|
9257
|
-
|
|
9258
|
-
|
|
9259
|
-
|
|
9260
|
-
|
|
9261
|
-
|
|
9262
|
-
|
|
9263
|
-
|
|
9264
|
-
|
|
9265
|
-
|
|
9266
|
-
|
|
9267
|
-
|
|
9268
|
-
|
|
9269
|
-
|
|
9270
|
-
|
|
9617
|
+
async function startSplit$1({ client_key, wasm_md5, }) {
|
|
9618
|
+
const tempDir = path$1.join(process.cwd(), TTMG_TEMP_DIR);
|
|
9619
|
+
const splitOutputDir = path$1.join(tempDir, 'split-output');
|
|
9620
|
+
if (fs$1.existsSync(splitOutputDir)) {
|
|
9621
|
+
fs$1.rmSync(splitOutputDir, { recursive: true, force: true });
|
|
9622
|
+
}
|
|
9623
|
+
ensureDirSync(splitOutputDir);
|
|
9624
|
+
const { originalWasmPath, isArchiveMode: archive } = getLocalState();
|
|
9625
|
+
const rawWasmPath = originalWasmPath || path$1.join(tempDir, 'original.wasm');
|
|
9626
|
+
if (!fs$1.existsSync(rawWasmPath)) {
|
|
9627
|
+
return {
|
|
9628
|
+
data: null,
|
|
9629
|
+
error: {
|
|
9630
|
+
code: 404,
|
|
9631
|
+
message: 'Original wasm not found. Run prepare first.',
|
|
9632
|
+
},
|
|
9633
|
+
ctx: { logid: 'local', httpStatusCode: 404 },
|
|
9634
|
+
};
|
|
9635
|
+
}
|
|
9636
|
+
const exportRes = await request({
|
|
9637
|
+
url: `${WASM_COLLECT_BASE_URL}/export`,
|
|
9638
|
+
method: 'GET',
|
|
9639
|
+
params: {
|
|
9640
|
+
app_id: client_key,
|
|
9641
|
+
wasm_md5,
|
|
9642
|
+
strategy: 'union',
|
|
9643
|
+
},
|
|
9644
|
+
});
|
|
9645
|
+
const funcIds = exportRes?.data?.func_ids;
|
|
9646
|
+
const bootFuncIds = exportRes?.data?.boot_func_ids ?? [];
|
|
9647
|
+
if (!funcIds?.length) {
|
|
9648
|
+
return {
|
|
9649
|
+
data: null,
|
|
9650
|
+
error: {
|
|
9651
|
+
code: 400,
|
|
9652
|
+
message: 'No collected func IDs found.',
|
|
9653
|
+
},
|
|
9654
|
+
ctx: { logid: 'local', httpStatusCode: 400 },
|
|
9655
|
+
};
|
|
9656
|
+
}
|
|
9657
|
+
console.log(`[wasmtool] splitting with ${funcIds.length} func IDs` +
|
|
9658
|
+
(bootFuncIds.length > 0
|
|
9659
|
+
? `, ${bootFuncIds.length} boot-phase func IDs (→ alwaysInclude)`
|
|
9660
|
+
: ', no boot-phase info (legacy server, falling back to callClosure only)') +
|
|
9661
|
+
`, archive=${archive}`);
|
|
9662
|
+
try {
|
|
9663
|
+
const result = ttmgWasmtool.split({
|
|
9664
|
+
input: rawWasmPath,
|
|
9665
|
+
funcIds,
|
|
9666
|
+
// Boot-phase func ids → `alwaysInclude`. They are a subset of
|
|
9667
|
+
// `funcIds` so this doesn't grow `collect_count`, but it DOES seed
|
|
9668
|
+
// the direct-call closure BFS with the exact set needed for first
|
|
9669
|
+
// frame, and the split tool's `alwaysIncludeAdded` counter is the
|
|
9670
|
+
// observability signal when zero (= server didn't return boot info).
|
|
9671
|
+
alwaysInclude: bootFuncIds.length > 0 ? bootFuncIds : undefined,
|
|
9672
|
+
// Always-on direct-call closure over (collect ∪ alwaysInclude ∪
|
|
9673
|
+
// start_func). Folds in func ids that collect missed (untaken
|
|
9674
|
+
// branches, race conditions during collect) so first-screen code
|
|
9675
|
+
// paths don't trap on archive trampolines. See the split tool's
|
|
9676
|
+
// `closure_added` counter for the per-build size impact.
|
|
9677
|
+
callClosure: true,
|
|
9678
|
+
// Always-on indirect-call type-closure scoped to the boot subset.
|
|
9679
|
+
// Catches IL2CPP virtual / interface / delegate dispatch which is
|
|
9680
|
+
// the dominant source of remaining `firstFrame=BEFORE` archive
|
|
9681
|
+
// trampoline hits after the runtime collect + direct closure
|
|
9682
|
+
// passes (see `indirectClosureAdded` for the per-build size
|
|
9683
|
+
// impact). Defaults to `true` in the wasmtool but we set it
|
|
9684
|
+
// explicitly so a future tool default change can't silently turn
|
|
9685
|
+
// it off in our pipeline.
|
|
9686
|
+
callIndirectClosure: true,
|
|
9687
|
+
outputDir: splitOutputDir,
|
|
9688
|
+
archive,
|
|
9689
|
+
compress: true,
|
|
9690
|
+
quality: 9,
|
|
9691
|
+
});
|
|
9692
|
+
if (result.code !== 0) {
|
|
9693
|
+
return {
|
|
9694
|
+
data: null,
|
|
9695
|
+
error: { code: result.code, message: result.errMsg },
|
|
9696
|
+
ctx: { logid: 'local', httpStatusCode: 500 },
|
|
9697
|
+
};
|
|
9698
|
+
}
|
|
9699
|
+
const mainBrPath = result.mainWasmPath + '.br';
|
|
9700
|
+
const actualMainPath = fs$1.existsSync(mainBrPath)
|
|
9701
|
+
? mainBrPath
|
|
9702
|
+
: result.mainWasmPath;
|
|
9703
|
+
const mainWasmMd5 = computeFileMd5Sync(actualMainPath);
|
|
9704
|
+
const subBrPath = result.subWasmPath ? result.subWasmPath + '.br' : '';
|
|
9705
|
+
const actualSubPath = subBrPath && fs$1.existsSync(subBrPath)
|
|
9706
|
+
? subBrPath
|
|
9707
|
+
: result.subWasmPath;
|
|
9708
|
+
const subWasmMd5 = actualSubPath
|
|
9709
|
+
? computeFileMd5Sync(actualSubPath)
|
|
9710
|
+
: '';
|
|
9711
|
+
let archiveMd5 = '';
|
|
9712
|
+
if (archive && result.archivePath) {
|
|
9713
|
+
const archiveBrPath = result.archivePath + '.br';
|
|
9714
|
+
const actualArchivePath = fs$1.existsSync(archiveBrPath)
|
|
9715
|
+
? archiveBrPath
|
|
9716
|
+
: result.archivePath;
|
|
9717
|
+
console.log(`[wasmtool] archivePath=${result.archivePath}, brExists=${fs$1.existsSync(archiveBrPath)}, actualExists=${fs$1.existsSync(actualArchivePath)}`);
|
|
9718
|
+
if (fs$1.existsSync(actualArchivePath)) {
|
|
9719
|
+
archiveMd5 = computeFileMd5Sync(actualArchivePath);
|
|
9720
|
+
console.log(`[wasmtool] archive_md5=${archiveMd5}`);
|
|
9721
|
+
}
|
|
9722
|
+
}
|
|
9723
|
+
else {
|
|
9724
|
+
console.log(`[wasmtool] skip archive md5: archive=${archive}, archivePath=${result.archivePath}`);
|
|
9725
|
+
}
|
|
9726
|
+
const globalVarList = result.globalVarList
|
|
9727
|
+
.split(';')
|
|
9728
|
+
.filter(Boolean)
|
|
9729
|
+
.map((entry) => {
|
|
9730
|
+
const [name, type, mutable] = entry.trim().split(',');
|
|
9731
|
+
return { name, type, mutable: mutable === '1' };
|
|
9732
|
+
});
|
|
9733
|
+
const splitMeta = {
|
|
9734
|
+
original_wasm_md5: wasm_md5,
|
|
9735
|
+
main_wasm_md5: mainWasmMd5,
|
|
9736
|
+
main_wasm_h5_md5: mainWasmMd5,
|
|
9737
|
+
sub_wasm_md5: subWasmMd5,
|
|
9738
|
+
archive_md5: archiveMd5,
|
|
9739
|
+
table_size: result.tableSize,
|
|
9740
|
+
global_var_list: globalVarList,
|
|
9741
|
+
version: Date.now(),
|
|
9742
|
+
total_wasm_count: result.totalWasmCount,
|
|
9743
|
+
main_wasm_count: result.mainWasmCount,
|
|
9744
|
+
time_cost: result.timeCost,
|
|
9745
|
+
archive,
|
|
9746
|
+
local_main_wasm_path: result.mainWasmPath,
|
|
9747
|
+
local_sub_wasm_path: result.subWasmPath,
|
|
9748
|
+
local_func_meta_path: result.funcMetaPath,
|
|
9749
|
+
local_archive_path: result.archivePath,
|
|
9750
|
+
// Composition breakdown of main_funcs — the single most useful
|
|
9751
|
+
// piece of information when triaging "why is my main package X MB"
|
|
9752
|
+
// (or, conversely, "why are first-screen sub-package batches still
|
|
9753
|
+
// loading"). collect = runtime-observed, always_include =
|
|
9754
|
+
// boot_func_ids, closure = BFS direct callees, indirect_closure =
|
|
9755
|
+
// type-matching pass scoped to boot funcs (covers IL2CPP virtual
|
|
9756
|
+
// dispatch), export = wasm exports. These sum with imports to
|
|
9757
|
+
// main_wasm_count.
|
|
9758
|
+
collect_func_count: result.collectFuncCount,
|
|
9759
|
+
always_include_added: result.alwaysIncludeAdded,
|
|
9760
|
+
closure_added: result.closureAdded,
|
|
9761
|
+
indirect_closure_added: result.indirectClosureAdded,
|
|
9762
|
+
indirect_closure_types: result.indirectClosureTypes,
|
|
9763
|
+
export_added: result.exportAdded,
|
|
9764
|
+
};
|
|
9765
|
+
setLocalState({ splitOutputDir, splitMeta });
|
|
9766
|
+
console.log(`[wasmtool] split done: total=${result.totalWasmCount}, main=${result.mainWasmCount} ` +
|
|
9767
|
+
`(collect=${result.collectFuncCount}, +alwaysInclude=${result.alwaysIncludeAdded}, ` +
|
|
9768
|
+
`+closure=${result.closureAdded}, +indirectClosure=${result.indirectClosureAdded}` +
|
|
9769
|
+
`[types=${result.indirectClosureTypes}], +exports=${result.exportAdded}), ` +
|
|
9770
|
+
`time=${result.timeCost}s`);
|
|
9771
|
+
// Split landed — close the collect session so the plugin stops reporting.
|
|
9772
|
+
// Awaited (not fire-and-forget) so IDE can rely on "wasm-split returned
|
|
9773
|
+
// success" meaning "session definitively closed". If /finish itself
|
|
9774
|
+
// fails (e.g. portal cookie expired mid-run) we still return split
|
|
9775
|
+
// success to the IDE — the plugin already has the MD5-bound session
|
|
9776
|
+
// state from the earlier /report responses and will time out on TTL
|
|
9777
|
+
// anyway; failing split for a finalizer hiccup would be worse UX.
|
|
9778
|
+
let funcCount;
|
|
9779
|
+
try {
|
|
9780
|
+
const finishRes = await finishWasmSession({ client_key, wasm_md5 });
|
|
9781
|
+
if (finishRes.error || !finishRes.data || finishRes.data.code !== 0) {
|
|
9782
|
+
// Soft failure: split already succeeded from the user's POV, but
|
|
9783
|
+
// this is the main diagnostic breadcrumb if someone later reports
|
|
9784
|
+
// "plugin kept uploading after 分包完成". Always include logid so
|
|
9785
|
+
// backend can cross-reference without having to know our build.
|
|
9786
|
+
const code = finishRes.error?.code ?? finishRes.data?.code ?? -1;
|
|
9787
|
+
const message = finishRes.error?.message ||
|
|
9788
|
+
finishRes.data?.message ||
|
|
9789
|
+
'finish session non-success';
|
|
9790
|
+
const logid = finishRes.ctx?.logid || 'n/a';
|
|
9791
|
+
console.error(`[wasm-split] /finish failed (split still succeeded): code=${code} message=${message} logid=${logid}`);
|
|
9792
|
+
}
|
|
9793
|
+
else {
|
|
9794
|
+
funcCount = finishRes.data.result?.func_count;
|
|
9795
|
+
}
|
|
9796
|
+
}
|
|
9797
|
+
catch (e) {
|
|
9798
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
9799
|
+
console.error(`[wasm-split] /finish threw (split still succeeded): ${msg}`);
|
|
9800
|
+
}
|
|
9801
|
+
return {
|
|
9802
|
+
data: { code: 0, message: 'success', func_count: funcCount },
|
|
9803
|
+
error: null,
|
|
9804
|
+
ctx: { logid: 'local', httpStatusCode: 200 },
|
|
9805
|
+
};
|
|
9806
|
+
}
|
|
9807
|
+
catch (err) {
|
|
9808
|
+
return {
|
|
9809
|
+
data: null,
|
|
9810
|
+
error: {
|
|
9811
|
+
code: 500,
|
|
9812
|
+
message: err instanceof Error ? err.message : String(err),
|
|
9813
|
+
},
|
|
9814
|
+
ctx: { logid: 'local', httpStatusCode: 500 },
|
|
9815
|
+
};
|
|
9816
|
+
}
|
|
9817
|
+
}
|
|
9818
|
+
|
|
9819
|
+
const ARCHIVE_SUBPACKAGE_CONFIG = [
|
|
9820
|
+
{ name: 'wasmcode', root: 'wasmcode/' },
|
|
9821
|
+
{ name: 'wasmcode1', root: 'wasmcode1/' },
|
|
9822
|
+
{ name: 'wasmcode-archive', root: 'wasmcode-archive/' },
|
|
9823
|
+
];
|
|
9824
|
+
function updateSubpackageConfigSync(archive = false) {
|
|
9825
|
+
const gameJsonPath = path__namespace.join(process.cwd(), SUBPACKAGE_CONFIG_FILE_NAME);
|
|
9826
|
+
const raw = fs__namespace.readFileSync(gameJsonPath, 'utf-8');
|
|
9827
|
+
const gameJson = JSON.parse(raw);
|
|
9828
|
+
delete gameJson.wasmFuncCount;
|
|
9829
|
+
const fieldName = SUBPACKAGE_FIELD_NAMES.find(k => k in gameJson) ??
|
|
9830
|
+
SUBPACKAGE_FIELD_NAMES[0];
|
|
9831
|
+
if (!gameJson[fieldName])
|
|
9832
|
+
gameJson[fieldName] = [];
|
|
9833
|
+
const subpackages = gameJson[fieldName];
|
|
9834
|
+
const filtered = subpackages.filter(s => s.name !== WASM_SPLIT_SUBPACKAGE_CONFIG.origin.name);
|
|
9835
|
+
if (archive) {
|
|
9836
|
+
ARCHIVE_SUBPACKAGE_CONFIG.forEach(pkg => filtered.push(pkg));
|
|
9837
|
+
}
|
|
9838
|
+
else {
|
|
9839
|
+
filtered.push(WASM_SPLIT_SUBPACKAGE_CONFIG.androidMain);
|
|
9840
|
+
filtered.push(WASM_SPLIT_SUBPACKAGE_CONFIG.androidSub);
|
|
9841
|
+
filtered.push(WASM_SPLIT_SUBPACKAGE_CONFIG.iosMain);
|
|
9842
|
+
filtered.push(WASM_SPLIT_SUBPACKAGE_CONFIG.iosSub);
|
|
9843
|
+
}
|
|
9844
|
+
const map = new Map(filtered.map(s => [s.name, s]));
|
|
9845
|
+
gameJson[fieldName] = Array.from(map.values());
|
|
9846
|
+
fs__namespace.writeFileSync(gameJsonPath, JSON.stringify(gameJson, null, JSON_INDENT) + JSON_EOL);
|
|
9847
|
+
}
|
|
9848
|
+
|
|
9849
|
+
async function downloadSplited$1(_context) {
|
|
9850
|
+
const cwd = process.cwd();
|
|
9851
|
+
const { splitMeta } = getLocalState();
|
|
9852
|
+
if (!splitMeta) {
|
|
9853
|
+
return {
|
|
9854
|
+
data: { isSuccess: false },
|
|
9855
|
+
error: { message: 'No local split result found. Run split first.' },
|
|
9856
|
+
ctx: _context,
|
|
9857
|
+
};
|
|
9858
|
+
}
|
|
9859
|
+
const splitTempDir = path.join(cwd, WASM_SPLIT_CACHE_DIR, DIR_SPLIT);
|
|
9860
|
+
ensureDirSync(splitTempDir);
|
|
9861
|
+
const isArchive = splitMeta.archive;
|
|
9862
|
+
const mainAndroidDir = path.join(splitTempDir, isArchive ? 'wasmcode' : WASM_SPLIT_SUBPACKAGE_CONFIG.androidMain.root);
|
|
9863
|
+
const subAndroidDir = path.join(splitTempDir, isArchive ? 'wasmcode1' : WASM_SPLIT_SUBPACKAGE_CONFIG.androidSub.root);
|
|
9864
|
+
const mainIosDir = isArchive
|
|
9865
|
+
? mainAndroidDir
|
|
9866
|
+
: path.join(splitTempDir, WASM_SPLIT_SUBPACKAGE_CONFIG.iosMain.root);
|
|
9867
|
+
const subIosDir = path.join(splitTempDir, isArchive ? 'wasmcode-archive' : WASM_SPLIT_SUBPACKAGE_CONFIG.iosSub.root);
|
|
9868
|
+
const dirs = [...new Set([mainAndroidDir, subAndroidDir, mainIosDir, subIosDir])];
|
|
9869
|
+
dirs.forEach(ensureDirSync);
|
|
9870
|
+
try {
|
|
9871
|
+
console.log('[wasmtool] organizing split output...');
|
|
9872
|
+
const mainWasmMd5 = splitMeta.main_wasm_md5;
|
|
9873
|
+
const subWasmMd5 = splitMeta.sub_wasm_md5;
|
|
9874
|
+
const mainWasmH5Md5 = splitMeta.main_wasm_h5_md5;
|
|
9875
|
+
const localMainPath = splitMeta.local_main_wasm_path;
|
|
9876
|
+
const mainBrPath = localMainPath + BR_SUFFIX;
|
|
9877
|
+
const actualMainPath = fs.existsSync(mainBrPath) ? mainBrPath : localMainPath;
|
|
9878
|
+
if (actualMainPath && fs.existsSync(actualMainPath)) {
|
|
9879
|
+
const isBr = actualMainPath.endsWith(BR_SUFFIX);
|
|
9880
|
+
const ext = isBr
|
|
9881
|
+
? `${WASM_FILENAME_SUFFIX}${BR_SUFFIX}`
|
|
9882
|
+
: WASM_FILENAME_SUFFIX;
|
|
9883
|
+
const mainAndroidDest = path.join(mainAndroidDir, `${mainWasmMd5}${ext}`);
|
|
9884
|
+
fs.copyFileSync(actualMainPath, mainAndroidDest);
|
|
9885
|
+
wsServer.sendUnitySplitStatus({
|
|
9886
|
+
status: 'download_android_main_wasm_done',
|
|
9887
|
+
});
|
|
9888
|
+
if (mainIosDir !== mainAndroidDir) {
|
|
9889
|
+
const mainIosDest = path.join(mainIosDir, `${mainWasmH5Md5}${ext}`);
|
|
9890
|
+
fs.copyFileSync(actualMainPath, mainIosDest);
|
|
9891
|
+
}
|
|
9892
|
+
wsServer.sendUnitySplitStatus({
|
|
9893
|
+
status: 'download_ios_main_wasm_done',
|
|
9894
|
+
});
|
|
9895
|
+
}
|
|
9896
|
+
const localSubPath = splitMeta.local_sub_wasm_path;
|
|
9897
|
+
const subBrPath = localSubPath + BR_SUFFIX;
|
|
9898
|
+
const actualSubPath = fs.existsSync(subBrPath) ? subBrPath : localSubPath;
|
|
9899
|
+
if (actualSubPath && fs.existsSync(actualSubPath)) {
|
|
9900
|
+
const isBr = actualSubPath.endsWith(BR_SUFFIX);
|
|
9901
|
+
const ext = isBr
|
|
9902
|
+
? `${WASM_FILENAME_SUFFIX}${BR_SUFFIX}`
|
|
9903
|
+
: WASM_FILENAME_SUFFIX;
|
|
9904
|
+
const subAndroidDest = path.join(subAndroidDir, `${subWasmMd5}${ext}`);
|
|
9905
|
+
fs.copyFileSync(actualSubPath, subAndroidDest);
|
|
9906
|
+
wsServer.sendUnitySplitStatus({
|
|
9907
|
+
status: 'download_android_sub_wasm_code_done',
|
|
9908
|
+
});
|
|
9909
|
+
}
|
|
9910
|
+
const localArchivePath = splitMeta.local_archive_path;
|
|
9911
|
+
if (isArchive && localArchivePath) {
|
|
9912
|
+
const archiveBrPath = localArchivePath + BR_SUFFIX;
|
|
9913
|
+
const actualArchivePath = fs.existsSync(archiveBrPath) ? archiveBrPath : localArchivePath;
|
|
9914
|
+
console.log(`[wasmtool] archive copy: archive_md5=${splitMeta.archive_md5}, localPath=${localArchivePath}, brExists=${fs.existsSync(archiveBrPath)}, actual=${actualArchivePath}`);
|
|
9915
|
+
if (fs.existsSync(actualArchivePath)) {
|
|
9916
|
+
const archiveMd5 = splitMeta.archive_md5 || '';
|
|
9917
|
+
const archiveBaseName = path.basename(actualArchivePath);
|
|
9918
|
+
const destName = archiveMd5 ? `${archiveMd5}.${archiveBaseName}` : archiveBaseName;
|
|
9919
|
+
const archiveDest = path.join(subIosDir, destName);
|
|
9920
|
+
console.log(`[wasmtool] archive dest: ${archiveDest}`);
|
|
9921
|
+
fs.copyFileSync(actualArchivePath, archiveDest);
|
|
9922
|
+
}
|
|
9923
|
+
}
|
|
9924
|
+
dirs.forEach((dir) => {
|
|
9925
|
+
fs.writeFileSync(path.join(dir, 'game.js'), '', { encoding: 'utf-8' });
|
|
9926
|
+
});
|
|
9927
|
+
console.log('[wasmtool] copy split output to root...');
|
|
9928
|
+
wsServer.sendUnitySplitStatus({ status: 'start_write_splited_wasm_br' });
|
|
9929
|
+
for (const file of fs.readdirSync(splitTempDir)) {
|
|
9930
|
+
const srcPath = path.join(splitTempDir, file);
|
|
9931
|
+
const destPath = path.join(cwd, file);
|
|
9932
|
+
if (fs.existsSync(destPath)) {
|
|
9933
|
+
await promises.rm(destPath, { recursive: true, force: true });
|
|
9934
|
+
}
|
|
9935
|
+
await promises.cp(srcPath, destPath, { recursive: true, force: true });
|
|
9936
|
+
}
|
|
9937
|
+
wsServer.sendUnitySplitStatus({ status: 'write_splited_wasm_done' });
|
|
9938
|
+
console.log('[wasmtool] updating subpackage config...');
|
|
9939
|
+
updateSubpackageConfigSync(isArchive);
|
|
9940
|
+
console.log('[wasmtool] updating wasm split config...');
|
|
9941
|
+
wsServer.sendUnitySplitStatus({ status: 'start_update_wasm_split_config' });
|
|
9942
|
+
updateWasmSplitConfig({
|
|
9943
|
+
ENABLEWASMCOLLECT: true,
|
|
9944
|
+
ORIGINALWASMMD5: `${splitMeta.original_wasm_md5}`,
|
|
9945
|
+
WASMTABLESIZE: splitMeta.table_size,
|
|
9946
|
+
GLOBALVARLIST: JSON.stringify(splitMeta.global_var_list ?? []),
|
|
9947
|
+
SUBJSURL: '',
|
|
9948
|
+
IOS_CODE_FILE_MD5: `${splitMeta.main_wasm_h5_md5}`,
|
|
9949
|
+
ANDROID_CODE_FILE_MD5: `${splitMeta.main_wasm_md5}`,
|
|
9950
|
+
ANDROID_SUB_CODE_FILE_MD5: `${splitMeta.sub_wasm_md5}`,
|
|
9951
|
+
ARCHIVE_CODE_FILE_MD5: `${splitMeta.archive_md5 || ''}`,
|
|
9952
|
+
WASMSPLITVERSION: `${splitMeta.version}`,
|
|
9953
|
+
USINGWASMH5: Boolean(splitMeta.main_wasm_h5_md5),
|
|
9954
|
+
ENABLEWASMSPLIT: true,
|
|
9955
|
+
ENABLEARCHIVEMODE: isArchive,
|
|
9956
|
+
});
|
|
9957
|
+
wsServer.sendUnitySplitStatus({ status: 'update_wasm_split_config_done' });
|
|
9958
|
+
return {
|
|
9959
|
+
data: { isSuccess: true },
|
|
9960
|
+
ctx: splitMeta,
|
|
9961
|
+
};
|
|
9962
|
+
}
|
|
9963
|
+
catch (err) {
|
|
9964
|
+
wsServer.sendUnitySplitStatus({
|
|
9965
|
+
status: 'wasm_split_failed',
|
|
9966
|
+
errorMsg: err instanceof Error ? err.message : String(err),
|
|
9967
|
+
});
|
|
9968
|
+
return {
|
|
9969
|
+
data: { isSuccess: false },
|
|
9970
|
+
error: { message: err instanceof Error ? err.message : String(err) },
|
|
9971
|
+
ctx: splitMeta,
|
|
9972
|
+
};
|
|
9973
|
+
}
|
|
9974
|
+
finally {
|
|
9975
|
+
await promises.rm(splitTempDir, { recursive: true, force: true });
|
|
9976
|
+
if (!isArchive) {
|
|
9977
|
+
await promises.rm(path.join(cwd, WASM_SPLIT_SUBPACKAGE_CONFIG.origin.root), {
|
|
9978
|
+
recursive: true,
|
|
9979
|
+
force: true,
|
|
9980
|
+
});
|
|
9981
|
+
}
|
|
9982
|
+
}
|
|
9983
|
+
}
|
|
9984
|
+
|
|
9985
|
+
/*
|
|
9986
|
+
How it works:
|
|
9987
|
+
`this.#head` is an instance of `Node` which keeps track of its current value and nests another instance of `Node` that keeps the value that comes after it. When a value is provided to `.enqueue()`, the code needs to iterate through `this.#head`, going deeper and deeper to find the last value. However, iterating through every single item is slow. This problem is solved by saving a reference to the last value as `this.#tail` so that it can reference it to add a new value.
|
|
9988
|
+
*/
|
|
9989
|
+
|
|
9990
|
+
class Node {
|
|
9991
|
+
value;
|
|
9992
|
+
next;
|
|
9993
|
+
|
|
9994
|
+
constructor(value) {
|
|
9995
|
+
this.value = value;
|
|
9996
|
+
}
|
|
9997
|
+
}
|
|
9998
|
+
|
|
9999
|
+
class Queue {
|
|
9271
10000
|
#head;
|
|
9272
10001
|
#tail;
|
|
9273
10002
|
#size;
|
|
@@ -9419,70 +10148,28 @@ function pLimit(concurrency) {
|
|
|
9419
10148
|
return generator;
|
|
9420
10149
|
}
|
|
9421
10150
|
|
|
9422
|
-
function
|
|
9423
|
-
const
|
|
9424
|
-
const raw = fs__namespace.readFileSync(gameJsonPath, 'utf-8');
|
|
9425
|
-
const gameJson = JSON.parse(raw);
|
|
9426
|
-
const fieldName = SUBPACKAGE_FIELD_NAMES.find(k => k in gameJson) ??
|
|
9427
|
-
SUBPACKAGE_FIELD_NAMES[0];
|
|
9428
|
-
if (!gameJson[fieldName])
|
|
9429
|
-
gameJson[fieldName] = [];
|
|
9430
|
-
const subpackages = gameJson[fieldName];
|
|
9431
|
-
// 删除老的 'wasmcode'
|
|
9432
|
-
const filtered = subpackages.filter(s => s.name !== WASM_SPLIT_SUBPACKAGE_CONFIG.origin.name);
|
|
9433
|
-
/**
|
|
9434
|
-
* 基于 SUBPACKAGE_CONFIG_FILE_NAME 更新 subpackages
|
|
9435
|
-
*/
|
|
9436
|
-
filtered.push(WASM_SPLIT_SUBPACKAGE_CONFIG.androidMain);
|
|
9437
|
-
filtered.push(WASM_SPLIT_SUBPACKAGE_CONFIG.androidSub);
|
|
9438
|
-
filtered.push(WASM_SPLIT_SUBPACKAGE_CONFIG.iosMain);
|
|
9439
|
-
filtered.push(WASM_SPLIT_SUBPACKAGE_CONFIG.iosSub);
|
|
9440
|
-
// 合并去重:存在则更新 root,不存在则新增
|
|
9441
|
-
const map = new Map(filtered.map(s => [s.name, s]));
|
|
9442
|
-
gameJson[fieldName] = Array.from(map.values());
|
|
9443
|
-
fs__namespace.writeFileSync(gameJsonPath, JSON.stringify(gameJson, null, JSON_INDENT) + JSON_EOL);
|
|
9444
|
-
}
|
|
9445
|
-
|
|
9446
|
-
async function downloadAndCompress(opts) {
|
|
9447
|
-
const { startDownloadStatus, downloadDoneStatus, startCompressStatus, compressDoneStatus, url, out, enableCompress = false, } = opts;
|
|
10151
|
+
async function downloadOne(opts) {
|
|
10152
|
+
const { startStatus, doneStatus, url, out } = opts;
|
|
9448
10153
|
if (!url)
|
|
9449
10154
|
return;
|
|
9450
10155
|
const willDownloadedFileIsBr = url.includes(BR_SUFFIX);
|
|
9451
|
-
const
|
|
9452
|
-
|
|
9453
|
-
|
|
9454
|
-
console.log(`download url: ${url}`);
|
|
10156
|
+
const finalOut = willDownloadedFileIsBr && !out.endsWith(BR_SUFFIX) ? out + BR_SUFFIX : out;
|
|
10157
|
+
wsServer.sendUnitySplitStatus({ status: startStatus });
|
|
10158
|
+
console.log(`[remote-split-download] fetching -> ${finalOut}`);
|
|
9455
10159
|
const t0 = Date.now();
|
|
9456
|
-
await withRetry(() => download(url,
|
|
9457
|
-
|
|
9458
|
-
|
|
9459
|
-
|
|
9460
|
-
|
|
9461
|
-
|
|
9462
|
-
|
|
9463
|
-
}
|
|
9464
|
-
|
|
9465
|
-
|
|
9466
|
-
|
|
9467
|
-
}
|
|
9468
|
-
if (enableCompress) {
|
|
9469
|
-
console.log(`compress start: ${path.basename(out)}${BR_SUFFIX}`);
|
|
9470
|
-
// 压缩
|
|
9471
|
-
wsServer.sendUnitySplitStatus({ status: startCompressStatus });
|
|
9472
|
-
const t1 = Date.now();
|
|
9473
|
-
await compressWasmFile(out, wasmBrOutName);
|
|
9474
|
-
wsServer.sendUnitySplitStatus({ status: compressDoneStatus });
|
|
9475
|
-
console.log(`compress done: ${path.basename(wasmBrOutName)} time=${Date.now() - t1}ms`);
|
|
9476
|
-
}
|
|
9477
|
-
/**
|
|
9478
|
-
* 在当前文件所在目录下写入一个空的 game.js
|
|
9479
|
-
*/
|
|
9480
|
-
fs__namespace.writeFileSync(path.join(path.dirname(out), 'game.js'), '', {
|
|
9481
|
-
encoding: 'utf-8',
|
|
9482
|
-
});
|
|
10160
|
+
await withRetry(() => download(url, finalOut), DOWNLOAD_RETRY);
|
|
10161
|
+
const st = await promises.stat(finalOut);
|
|
10162
|
+
if (!st.size) {
|
|
10163
|
+
await promises.rm(finalOut, { force: true });
|
|
10164
|
+
throw new Error(`Empty download: ${finalOut}`);
|
|
10165
|
+
}
|
|
10166
|
+
console.log(`[remote-split-download] done: ${path.basename(finalOut)} size=${st.size}B time=${Date.now() - t0}ms`);
|
|
10167
|
+
wsServer.sendUnitySplitStatus({ status: doneStatus, url });
|
|
10168
|
+
// Legacy behaviour: write an empty game.js next to each downloaded artifact
|
|
10169
|
+
// so the subpackage loader doesn't complain about missing js entries.
|
|
10170
|
+
fs.writeFileSync(path.join(path.dirname(out), 'game.js'), '', 'utf-8');
|
|
9483
10171
|
}
|
|
9484
|
-
|
|
9485
|
-
async function downloadSplited(context) {
|
|
10172
|
+
async function downloadSplitedRemote(context) {
|
|
9486
10173
|
const cwd = process.cwd();
|
|
9487
10174
|
const splitTempDir = path.join(cwd, WASM_SPLIT_CACHE_DIR, DIR_SPLIT);
|
|
9488
10175
|
ensureDirSync(splitTempDir);
|
|
@@ -9491,144 +10178,124 @@ async function downloadSplited(context) {
|
|
|
9491
10178
|
const mainIosDir = path.join(splitTempDir, WASM_SPLIT_SUBPACKAGE_CONFIG.iosMain.root);
|
|
9492
10179
|
const subIosDir = path.join(splitTempDir, WASM_SPLIT_SUBPACKAGE_CONFIG.iosSub.root);
|
|
9493
10180
|
[mainAndroidDir, subAndroidDir, mainIosDir, subIosDir].forEach(ensureDirSync);
|
|
9494
|
-
const
|
|
9495
|
-
const
|
|
9496
|
-
const
|
|
10181
|
+
const mainAndroidOut = path.join(mainAndroidDir, `${context.main_wasm_md5}${WASM_FILENAME_SUFFIX}`);
|
|
10182
|
+
const subAndroidOut = path.join(subAndroidDir, `${context.sub_wasm_md5}${WASM_FILENAME_SUFFIX}`);
|
|
10183
|
+
const mainIosOut = path.join(mainIosDir, `${context.main_wasm_h5_md5}${WASM_FILENAME_SUFFIX}`);
|
|
9497
10184
|
const limit = pLimit(CONCURRENCY_LIMIT);
|
|
9498
10185
|
try {
|
|
9499
|
-
console.log('
|
|
9500
|
-
|
|
9501
|
-
|
|
9502
|
-
|
|
9503
|
-
|
|
9504
|
-
wsServer.sendUnitySplitStatus({
|
|
9505
|
-
status: 'start_download_android_sub_wasm_code',
|
|
10186
|
+
console.log('[remote-split-download] start', {
|
|
10187
|
+
original_wasm_md5: context.original_wasm_md5,
|
|
10188
|
+
main_wasm_md5: context.main_wasm_md5,
|
|
10189
|
+
sub_wasm_md5: context.sub_wasm_md5,
|
|
10190
|
+
main_wasm_h5_md5: context.main_wasm_h5_md5,
|
|
9506
10191
|
});
|
|
9507
|
-
wsServer.sendUnitySplitStatus({ status: 'start_download_ios_main_wasm' });
|
|
9508
|
-
/**
|
|
9509
|
-
* 需要做个保护,只有 有 URL 时才下载
|
|
9510
|
-
*/
|
|
9511
|
-
// 并发下载 + 压缩(带重试)
|
|
9512
10192
|
await Promise.all([
|
|
9513
|
-
limit(() =>
|
|
9514
|
-
|
|
9515
|
-
|
|
9516
|
-
startCompressStatus: 'start_compress_android_main_wasm',
|
|
9517
|
-
compressDoneStatus: 'compress_android_main_wasm_done',
|
|
10193
|
+
limit(() => downloadOne({
|
|
10194
|
+
startStatus: 'start_download_android_main_wasm',
|
|
10195
|
+
doneStatus: 'download_android_main_wasm_done',
|
|
9518
10196
|
url: context.main_wasm_download_url,
|
|
9519
|
-
out:
|
|
10197
|
+
out: mainAndroidOut,
|
|
9520
10198
|
})),
|
|
9521
|
-
limit(() =>
|
|
9522
|
-
|
|
9523
|
-
|
|
9524
|
-
startCompressStatus: 'start_compress_android_sub_wasm_code',
|
|
9525
|
-
compressDoneStatus: 'compress_android_sub_wasm_code_done',
|
|
10199
|
+
limit(() => downloadOne({
|
|
10200
|
+
startStatus: 'start_download_android_sub_wasm_code',
|
|
10201
|
+
doneStatus: 'download_android_sub_wasm_code_done',
|
|
9526
10202
|
url: context.sub_wasm_download_url,
|
|
9527
|
-
out:
|
|
10203
|
+
out: subAndroidOut,
|
|
9528
10204
|
})),
|
|
9529
|
-
limit(() =>
|
|
9530
|
-
|
|
9531
|
-
|
|
9532
|
-
startCompressStatus: 'start_compress_ios_main_wasm',
|
|
9533
|
-
compressDoneStatus: 'compress_ios_main_wasm_done',
|
|
10205
|
+
limit(() => downloadOne({
|
|
10206
|
+
startStatus: 'start_download_ios_main_wasm',
|
|
10207
|
+
doneStatus: 'download_ios_main_wasm_done',
|
|
9534
10208
|
url: context.main_wasm_h5_download_url,
|
|
9535
|
-
out:
|
|
10209
|
+
out: mainIosOut,
|
|
9536
10210
|
})),
|
|
9537
|
-
|
|
9538
|
-
|
|
9539
|
-
|
|
9540
|
-
downloadDoneStatus: 'download_ios_range_json_done',
|
|
10211
|
+
limit(() => downloadOne({
|
|
10212
|
+
startStatus: 'start_download_ios_range_json',
|
|
10213
|
+
doneStatus: 'download_ios_range_json_done',
|
|
9541
10214
|
url: context.sub_js_range_download_url,
|
|
9542
10215
|
out: path.join(subIosDir, 'func_bytes_range.json'),
|
|
9543
10216
|
})),
|
|
9544
|
-
|
|
9545
|
-
|
|
9546
|
-
|
|
9547
|
-
downloadDoneStatus: 'download_ios_js_data_br_done',
|
|
10217
|
+
limit(() => downloadOne({
|
|
10218
|
+
startStatus: 'start_download_ios_js_data_br',
|
|
10219
|
+
doneStatus: 'download_ios_js_data_br_done',
|
|
9548
10220
|
url: context.sub_js_data_download_url,
|
|
9549
10221
|
out: path.join(subIosDir, 'subjs.data'),
|
|
9550
10222
|
})),
|
|
9551
10223
|
]);
|
|
9552
|
-
|
|
9553
|
-
console.log('copy splitTempDir to root start');
|
|
10224
|
+
console.log('[remote-split-download] copying split output to project root...');
|
|
9554
10225
|
wsServer.sendUnitySplitStatus({ status: 'start_write_splited_wasm_br' });
|
|
9555
10226
|
for (const file of fs.readdirSync(splitTempDir)) {
|
|
9556
10227
|
const srcPath = path.join(splitTempDir, file);
|
|
9557
10228
|
const destPath = path.join(cwd, file);
|
|
9558
|
-
// 如果目标路径有文件或目录,先删除
|
|
9559
10229
|
if (fs.existsSync(destPath)) {
|
|
9560
10230
|
await promises.rm(destPath, { recursive: true, force: true });
|
|
9561
10231
|
}
|
|
9562
10232
|
await promises.cp(srcPath, destPath, { recursive: true, force: true });
|
|
9563
10233
|
}
|
|
9564
10234
|
wsServer.sendUnitySplitStatus({ status: 'write_splited_wasm_done' });
|
|
9565
|
-
console.log('
|
|
9566
|
-
|
|
9567
|
-
console.log('
|
|
9568
|
-
updateSubpackageConfigSync();
|
|
9569
|
-
console.log('updateSubpackageConfigSync end');
|
|
9570
|
-
// 更新 wasm split 配置(保持原始状态文案)
|
|
9571
|
-
console.log('updateWasmSplitConfig start');
|
|
10235
|
+
console.log('[remote-split-download] updating subpackage config...');
|
|
10236
|
+
updateSubpackageConfigSync(false);
|
|
10237
|
+
console.log('[remote-split-download] updating webgl-wasm-split.js...');
|
|
9572
10238
|
wsServer.sendUnitySplitStatus({ status: 'start_update_wasm_split_config' });
|
|
9573
10239
|
updateWasmSplitConfig({
|
|
9574
10240
|
ENABLEWASMCOLLECT: true,
|
|
9575
|
-
|
|
10241
|
+
ENABLEWASMSPLIT: true,
|
|
10242
|
+
ENABLEARCHIVEMODE: false,
|
|
10243
|
+
ORIGINALWASMMD5: `${context.original_wasm_md5 ?? ''}`,
|
|
9576
10244
|
WASMTABLESIZE: context.table_size,
|
|
9577
10245
|
GLOBALVARLIST: JSON.stringify(context.global_var_list ?? []),
|
|
9578
|
-
SUBJSURL: `${context.sub_js_download_url}`,
|
|
9579
|
-
IOS_CODE_FILE_MD5: `${context.main_wasm_h5_md5}`,
|
|
9580
|
-
ANDROID_CODE_FILE_MD5: `${context.main_wasm_md5}`,
|
|
9581
|
-
ANDROID_SUB_CODE_FILE_MD5: `${context.sub_wasm_md5}`,
|
|
9582
|
-
WASMSPLITVERSION: `${context.version}`,
|
|
10246
|
+
SUBJSURL: `${context.sub_js_download_url ?? ''}`,
|
|
10247
|
+
IOS_CODE_FILE_MD5: `${context.main_wasm_h5_md5 ?? ''}`,
|
|
10248
|
+
ANDROID_CODE_FILE_MD5: `${context.main_wasm_md5 ?? ''}`,
|
|
10249
|
+
ANDROID_SUB_CODE_FILE_MD5: `${context.sub_wasm_md5 ?? ''}`,
|
|
10250
|
+
WASMSPLITVERSION: `${context.version ?? ''}`,
|
|
9583
10251
|
USINGWASMH5: Boolean(context.main_wasm_h5_md5),
|
|
9584
|
-
ENABLEWASMSPLIT: true,
|
|
9585
|
-
// IOS_SUB_JS_FILE_CONFIG: JSON.stringify(context.merged_js ?? {}),
|
|
9586
10252
|
});
|
|
9587
10253
|
wsServer.sendUnitySplitStatus({ status: 'update_wasm_split_config_done' });
|
|
9588
|
-
console.log('
|
|
9589
|
-
return {
|
|
9590
|
-
data: {
|
|
9591
|
-
isSuccess: true,
|
|
9592
|
-
},
|
|
9593
|
-
ctx: context,
|
|
9594
|
-
};
|
|
10254
|
+
console.log('[remote-split-download] all done');
|
|
10255
|
+
return { data: { isSuccess: true }, ctx: context };
|
|
9595
10256
|
}
|
|
9596
10257
|
catch (err) {
|
|
9597
|
-
|
|
9598
|
-
|
|
9599
|
-
|
|
9600
|
-
});
|
|
10258
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
10259
|
+
console.log('[remote-split-download] failed:', message);
|
|
10260
|
+
wsServer.sendUnitySplitStatus({ status: 'wasm_split_failed', errorMsg: message });
|
|
9601
10261
|
return {
|
|
9602
|
-
data: {
|
|
9603
|
-
|
|
9604
|
-
},
|
|
9605
|
-
error: {
|
|
9606
|
-
message: err instanceof Error ? err.message : String(err),
|
|
9607
|
-
},
|
|
10262
|
+
data: { isSuccess: false },
|
|
10263
|
+
error: { message },
|
|
9608
10264
|
ctx: context,
|
|
9609
10265
|
};
|
|
9610
10266
|
}
|
|
9611
10267
|
finally {
|
|
9612
|
-
// 清理临时目录与旧 wasmcode 目录
|
|
9613
|
-
console.log('delete splitTempDir start');
|
|
9614
10268
|
await promises.rm(splitTempDir, { recursive: true, force: true });
|
|
9615
|
-
|
|
9616
|
-
|
|
10269
|
+
// Legacy flow: the server-produced `wasmcode/` placeholder at the project
|
|
10270
|
+
// root is no longer needed once we've laid down the 4 platform dirs.
|
|
9617
10271
|
await promises.rm(path.join(cwd, WASM_SPLIT_SUBPACKAGE_CONFIG.origin.root), {
|
|
9618
10272
|
recursive: true,
|
|
9619
10273
|
force: true,
|
|
9620
10274
|
});
|
|
9621
|
-
console.log('delete wasmcode end');
|
|
9622
10275
|
}
|
|
9623
10276
|
}
|
|
9624
10277
|
|
|
9625
|
-
async function getSplitResult(
|
|
9626
|
-
|
|
9627
|
-
|
|
9628
|
-
|
|
9629
|
-
|
|
9630
|
-
|
|
9631
|
-
|
|
10278
|
+
async function getSplitResult$1(_params) {
|
|
10279
|
+
const { splitMeta } = getLocalState();
|
|
10280
|
+
if (!splitMeta) {
|
|
10281
|
+
return {
|
|
10282
|
+
data: null,
|
|
10283
|
+
error: {
|
|
10284
|
+
code: 404,
|
|
10285
|
+
message: 'No local split result found. Run split first.',
|
|
10286
|
+
},
|
|
10287
|
+
ctx: { logid: 'local', httpStatusCode: 404 },
|
|
10288
|
+
};
|
|
10289
|
+
}
|
|
10290
|
+
return {
|
|
10291
|
+
data: {
|
|
10292
|
+
code: 0,
|
|
10293
|
+
message: 'success',
|
|
10294
|
+
result: splitMeta,
|
|
10295
|
+
},
|
|
10296
|
+
error: null,
|
|
10297
|
+
ctx: { logid: 'local', httpStatusCode: 200 },
|
|
10298
|
+
};
|
|
9632
10299
|
}
|
|
9633
10300
|
|
|
9634
10301
|
function cancelSplit(params) {
|
|
@@ -9668,60 +10335,34 @@ function cancelSplit(params) {
|
|
|
9668
10335
|
}
|
|
9669
10336
|
}
|
|
9670
10337
|
|
|
9671
|
-
async function resetWasmSplit(data) {
|
|
10338
|
+
async function resetWasmSplit$1(data) {
|
|
9672
10339
|
const res = await request({
|
|
9673
|
-
url: `${
|
|
10340
|
+
url: `${WASM_COLLECT_BASE_URL}/reset`,
|
|
9674
10341
|
method: 'POST',
|
|
9675
|
-
headers: {
|
|
9676
|
-
...DEV_HEADERS,
|
|
9677
|
-
},
|
|
9678
10342
|
data: {
|
|
9679
|
-
|
|
10343
|
+
app_id: data.clientkey,
|
|
9680
10344
|
wasm_md5: data.wasmMd5,
|
|
9681
10345
|
},
|
|
9682
10346
|
});
|
|
9683
|
-
/**
|
|
9684
|
-
* 把— __TTMG_TEMP__/wasmcode/ 目录下的所有文件恢复到原本的位置,进行重置
|
|
9685
|
-
*/
|
|
9686
10347
|
const cacheDir = path.join(process.cwd(), WASM_SPLIT_CACHE_DIR);
|
|
9687
|
-
/**
|
|
9688
|
-
* 恢复 br 文件
|
|
9689
|
-
*/
|
|
9690
10348
|
if (fs.existsSync(cacheDir)) {
|
|
9691
|
-
/**
|
|
9692
|
-
* 判断是否有缓存的 br 文件
|
|
9693
|
-
*/
|
|
9694
|
-
/**
|
|
9695
|
-
* 判断 cache 文件夹下有没有 .br 文件
|
|
9696
|
-
*
|
|
9697
|
-
*/
|
|
9698
10349
|
const targetWasmBrPath = fs
|
|
9699
10350
|
.readdirSync(cacheDir)
|
|
9700
10351
|
.find(item => item.endsWith('.br'));
|
|
9701
10352
|
if (targetWasmBrPath) {
|
|
9702
10353
|
const destWasmBrPath = path.join(process.cwd(), WASM_SPLIT_SUBPACKAGE_CONFIG.origin.root, path.basename(targetWasmBrPath));
|
|
9703
|
-
// 规避没有文件夹的情况
|
|
9704
10354
|
ensureDirSync(path.dirname(destWasmBrPath));
|
|
9705
10355
|
fs.copyFileSync(path.join(cacheDir, targetWasmBrPath), destWasmBrPath);
|
|
9706
10356
|
}
|
|
9707
10357
|
}
|
|
9708
|
-
/**
|
|
9709
|
-
* 恢复 webgl-wasm-split.js 文件
|
|
9710
|
-
*/
|
|
9711
10358
|
const splitConfigCachePath = path.join(cacheDir, WASM_SPLIT_CONFIG_FILE_NAME);
|
|
9712
10359
|
if (fs.existsSync(splitConfigCachePath)) {
|
|
9713
10360
|
fs.copyFileSync(splitConfigCachePath, path.join(process.cwd(), WASM_SPLIT_CONFIG_FILE_NAME));
|
|
9714
10361
|
}
|
|
9715
|
-
/**
|
|
9716
|
-
* 恢复 game.json 文件
|
|
9717
|
-
*/
|
|
9718
10362
|
const gameJsonCachePath = path.join(cacheDir, 'game.json');
|
|
9719
10363
|
if (fs.existsSync(gameJsonCachePath)) {
|
|
9720
10364
|
fs.copyFileSync(gameJsonCachePath, path.join(process.cwd(), 'game.json'));
|
|
9721
10365
|
}
|
|
9722
|
-
/**
|
|
9723
|
-
* 删除历史分包产物
|
|
9724
|
-
*/
|
|
9725
10366
|
const androidSubpackageDir = path.join(process.cwd(), WASM_SPLIT_SUBPACKAGE_CONFIG.androidMain.root);
|
|
9726
10367
|
if (fs.existsSync(androidSubpackageDir)) {
|
|
9727
10368
|
fs.rmSync(androidSubpackageDir, { recursive: true });
|
|
@@ -9734,6 +10375,15 @@ async function resetWasmSplit(data) {
|
|
|
9734
10375
|
if (fs.existsSync(iosSubpackageDir)) {
|
|
9735
10376
|
fs.rmSync(iosSubpackageDir, { recursive: true });
|
|
9736
10377
|
}
|
|
10378
|
+
// Drop the prepared-meta anchor as well — rollback restores the
|
|
10379
|
+
// original wasm into the project, so any md5 we previously recorded
|
|
10380
|
+
// for the prepared build is no longer valid. Leaving it behind would
|
|
10381
|
+
// make the split-config drift guard fire on the very next Modal open
|
|
10382
|
+
// and force the user through a redundant prepare cycle.
|
|
10383
|
+
const preparedMetaPath = path.join(process.cwd(), TTMG_TEMP_DIR, 'prepared-meta.json');
|
|
10384
|
+
if (fs.existsSync(preparedMetaPath)) {
|
|
10385
|
+
fs.rmSync(preparedMetaPath, { force: true });
|
|
10386
|
+
}
|
|
9737
10387
|
return res;
|
|
9738
10388
|
}
|
|
9739
10389
|
|
|
@@ -9769,23 +10419,291 @@ function getSplitConfig() {
|
|
|
9769
10419
|
}
|
|
9770
10420
|
}
|
|
9771
10421
|
|
|
9772
|
-
|
|
9773
|
-
|
|
9774
|
-
|
|
10422
|
+
var WasmStatus;
|
|
10423
|
+
(function (WasmStatus) {
|
|
10424
|
+
WasmStatus[WasmStatus["IdleStatus"] = 0] = "IdleStatus";
|
|
10425
|
+
WasmStatus[WasmStatus["WasmPreparingStatus"] = 1] = "WasmPreparingStatus";
|
|
10426
|
+
WasmStatus[WasmStatus["WasmPreparedStatus"] = 2] = "WasmPreparedStatus";
|
|
10427
|
+
WasmStatus[WasmStatus["WasmSplitStatus"] = 3] = "WasmSplitStatus";
|
|
10428
|
+
WasmStatus[WasmStatus["WasmSplittingStatus"] = 4] = "WasmSplittingStatus";
|
|
10429
|
+
WasmStatus[WasmStatus["WasmSplitDoneStatus"] = 5] = "WasmSplitDoneStatus";
|
|
10430
|
+
WasmStatus[WasmStatus["WasmSplitReadyToPrepareStatus"] = 6] = "WasmSplitReadyToPrepareStatus";
|
|
10431
|
+
WasmStatus[WasmStatus["WasmSplitPreparingStatus"] = 7] = "WasmSplitPreparingStatus";
|
|
10432
|
+
WasmStatus[WasmStatus["WasmSplitPreparedStatus"] = 8] = "WasmSplitPreparedStatus";
|
|
10433
|
+
WasmStatus[WasmStatus["WasmCollectingStatus"] = 9] = "WasmCollectingStatus";
|
|
10434
|
+
WasmStatus[WasmStatus["WasmUploadFailStatus"] = -1] = "WasmUploadFailStatus";
|
|
10435
|
+
WasmStatus[WasmStatus["WasmDownloadFailStatus"] = -2] = "WasmDownloadFailStatus";
|
|
10436
|
+
WasmStatus[WasmStatus["WasmFileNotExistStatus"] = -3] = "WasmFileNotExistStatus";
|
|
10437
|
+
WasmStatus[WasmStatus["WasmSplitFailStatus"] = -4] = "WasmSplitFailStatus";
|
|
10438
|
+
WasmStatus[WasmStatus["WasmSplitUpdateDBFailedStatus"] = -5] = "WasmSplitUpdateDBFailedStatus";
|
|
10439
|
+
WasmStatus[WasmStatus["WasmSplitPrepareFailedStatus"] = -6] = "WasmSplitPrepareFailedStatus";
|
|
10440
|
+
})(WasmStatus || (WasmStatus = {}));
|
|
10441
|
+
|
|
10442
|
+
const getTaskStatus$1 = async (params) => {
|
|
10443
|
+
const { preparedWasmPath, splitMeta } = getLocalState();
|
|
10444
|
+
let status = WasmStatus.IdleStatus;
|
|
10445
|
+
if (splitMeta) {
|
|
10446
|
+
status = WasmStatus.WasmSplitDoneStatus;
|
|
10447
|
+
}
|
|
10448
|
+
else if (preparedWasmPath) {
|
|
10449
|
+
status = WasmStatus.WasmSplitPreparedStatus;
|
|
10450
|
+
}
|
|
10451
|
+
return {
|
|
10452
|
+
data: {
|
|
10453
|
+
code: 0,
|
|
10454
|
+
message: 'success',
|
|
10455
|
+
result: {
|
|
10456
|
+
status,
|
|
10457
|
+
wasm_md5: params.wasm_md5,
|
|
10458
|
+
},
|
|
10459
|
+
},
|
|
10460
|
+
error: null,
|
|
10461
|
+
ctx: { logid: 'local', httpStatusCode: 200 },
|
|
10462
|
+
};
|
|
10463
|
+
};
|
|
10464
|
+
|
|
10465
|
+
const getTaskInfo$1 = async (params) => {
|
|
10466
|
+
const res = await request({
|
|
10467
|
+
url: `${WASM_COLLECT_BASE_URL}/progress`,
|
|
9775
10468
|
method: 'GET',
|
|
9776
|
-
|
|
9777
|
-
|
|
10469
|
+
params: {
|
|
10470
|
+
app_id: params.client_key,
|
|
10471
|
+
wasm_md5: params.wasm_md5,
|
|
10472
|
+
},
|
|
9778
10473
|
});
|
|
10474
|
+
const { totalWasmFuncCount, preparedWasmPath, wasmSize } = getLocalState();
|
|
10475
|
+
// Prefer game.json as the source of truth so wasm_size / total_wasm_func_count
|
|
10476
|
+
// survive CLI restarts. localState values are only populated during the
|
|
10477
|
+
// prepare step of the current session; after a restart they default to 0.
|
|
10478
|
+
// game.json carries wasmCodeSize/wasmFuncCount emitted at build time, so
|
|
10479
|
+
// re-entering the collect step still shows the correct totals.
|
|
10480
|
+
const gameJson = getGameJson();
|
|
10481
|
+
const gameJsonWasmSize = Number(gameJson?.wasmCodeSize) || 0;
|
|
10482
|
+
const gameJsonFuncCount = Number(gameJson?.wasmFuncCount) || 0;
|
|
10483
|
+
return {
|
|
10484
|
+
data: {
|
|
10485
|
+
code: res?.data?.code ?? 0,
|
|
10486
|
+
message: 'success',
|
|
10487
|
+
result: {
|
|
10488
|
+
app_id: params.client_key,
|
|
10489
|
+
wasm_md5: params.wasm_md5,
|
|
10490
|
+
is_prepared: Boolean(preparedWasmPath),
|
|
10491
|
+
collected_func_count: res?.data?.func_count ?? 0,
|
|
10492
|
+
total_wasm_func_count: gameJsonFuncCount || totalWasmFuncCount || 0,
|
|
10493
|
+
wasm_size: gameJsonWasmSize || wasmSize || 0,
|
|
10494
|
+
},
|
|
10495
|
+
},
|
|
10496
|
+
error: res.error,
|
|
10497
|
+
ctx: res.ctx,
|
|
10498
|
+
};
|
|
9779
10499
|
};
|
|
9780
10500
|
|
|
9781
|
-
|
|
10501
|
+
async function startPrepareRemote(params) {
|
|
10502
|
+
// Back up the original wasm + split config on the first run so cancel/rollback
|
|
10503
|
+
// works even if the user aborts before the server-side prepare finishes.
|
|
10504
|
+
keepCacheSync({
|
|
10505
|
+
entryDir: process.cwd(),
|
|
10506
|
+
originalWasmPath: params.wasm_file_path,
|
|
10507
|
+
originalSplitConfigPath: WASM_SPLIT_CONFIG_FILE_NAME,
|
|
10508
|
+
});
|
|
10509
|
+
const form = new FormData$1();
|
|
10510
|
+
form.append('desc', params.desc);
|
|
10511
|
+
form.append('wasm_md5', params.wasm_md5);
|
|
10512
|
+
form.append('with_ios', 'true');
|
|
10513
|
+
form.append('wasm_file', fs$1.createReadStream(path$1.join(process.cwd(), params.wasm_file_path)), { filename: path$1.basename(params.wasm_file_path), contentType: 'application/wasm' });
|
|
10514
|
+
let symbolFilePath = path$1.join(process.cwd(), TTMG_TEMP_DIR, WASM_SYMBOL_FILE_NAME);
|
|
10515
|
+
if (!fs$1.existsSync(symbolFilePath)) {
|
|
10516
|
+
symbolFilePath = path$1.join(process.cwd(), WASM_SYMBOL_FILE_NAME);
|
|
10517
|
+
}
|
|
10518
|
+
if (!fs$1.existsSync(symbolFilePath)) {
|
|
10519
|
+
return {
|
|
10520
|
+
error: { code: 400, message: `${WASM_SYMBOL_FILE_NAME} not found`, client_key: params.client_key },
|
|
10521
|
+
data: null,
|
|
10522
|
+
ctx: { logid: '', httpStatusCode: 400 },
|
|
10523
|
+
};
|
|
10524
|
+
}
|
|
10525
|
+
form.append('wasm_symbol_file', fs$1.createReadStream(symbolFilePath), {
|
|
10526
|
+
filename: WASM_SYMBOL_FILE_NAME,
|
|
10527
|
+
contentType: 'application/octet-stream',
|
|
10528
|
+
});
|
|
10529
|
+
const formHeaders = form.getHeaders();
|
|
10530
|
+
return request({
|
|
10531
|
+
url: `${BASE_URL}/api/stark_wasm/v4/post/prepare`,
|
|
10532
|
+
method: 'POST',
|
|
10533
|
+
headers: { ...DEV_HEADERS, ...formHeaders },
|
|
10534
|
+
params: { client_key: params.client_key, with_ios: true },
|
|
10535
|
+
data: form,
|
|
10536
|
+
});
|
|
10537
|
+
}
|
|
10538
|
+
async function setCollectRemote({ client_key, wasm_md5 }) {
|
|
10539
|
+
return request({
|
|
10540
|
+
url: `${BASE_URL}/api/stark_wasm/v4/post/set_collecting`,
|
|
10541
|
+
method: 'POST',
|
|
10542
|
+
data: { client_key, wasm_md5 },
|
|
10543
|
+
headers: DEV_HEADERS,
|
|
10544
|
+
});
|
|
10545
|
+
}
|
|
10546
|
+
async function getCollectedFuncIdsRemote({ client_key, wasm_md5 }) {
|
|
10547
|
+
return request({
|
|
10548
|
+
url: `${BASE_URL}/api/stark_wasm/v4/get/collectedfuncids`,
|
|
10549
|
+
method: 'GET',
|
|
10550
|
+
headers: DEV_HEADERS,
|
|
10551
|
+
params: { client_key, wasm_md5 },
|
|
10552
|
+
});
|
|
10553
|
+
}
|
|
10554
|
+
async function getCollecttingInfoRemote({ client_key, wasm_md5 }) {
|
|
10555
|
+
return request({
|
|
10556
|
+
url: `${BASE_URL}/api/stark_wasm/v4/get/funccollect`,
|
|
10557
|
+
method: 'GET',
|
|
10558
|
+
headers: DEV_HEADERS,
|
|
10559
|
+
params: { client_key, wasm_md5 },
|
|
10560
|
+
});
|
|
10561
|
+
}
|
|
10562
|
+
async function startSplitRemote({ client_key, wasm_md5 }) {
|
|
10563
|
+
return request({
|
|
10564
|
+
url: `${BASE_URL}/api/stark_wasm/v4/post/split`,
|
|
10565
|
+
method: 'POST',
|
|
10566
|
+
headers: { ...DEV_HEADERS },
|
|
10567
|
+
data: { client_key, wasm_md5 },
|
|
10568
|
+
});
|
|
10569
|
+
}
|
|
10570
|
+
async function getTaskInfoRemote(params) {
|
|
9782
10571
|
return request({
|
|
9783
10572
|
url: `${BASE_URL}/api/stark_wasm/v4/get/taskinfo`,
|
|
9784
10573
|
method: 'GET',
|
|
9785
10574
|
headers: DEV_HEADERS,
|
|
9786
10575
|
params,
|
|
9787
10576
|
});
|
|
9788
|
-
}
|
|
10577
|
+
}
|
|
10578
|
+
async function getTaskStatusRemote(params) {
|
|
10579
|
+
return request({
|
|
10580
|
+
url: `${BASE_URL}/api/stark_wasm/v4/get/status`,
|
|
10581
|
+
method: 'GET',
|
|
10582
|
+
headers: DEV_HEADERS,
|
|
10583
|
+
params,
|
|
10584
|
+
});
|
|
10585
|
+
}
|
|
10586
|
+
async function resetWasmSplitRemote(data) {
|
|
10587
|
+
const res = await request({
|
|
10588
|
+
url: `${BASE_URL}/api/stark_wasm/v4/post/reset`,
|
|
10589
|
+
method: 'POST',
|
|
10590
|
+
headers: { ...DEV_HEADERS },
|
|
10591
|
+
data: { client_key: data.clientkey, wasm_md5: data.wasmMd5 },
|
|
10592
|
+
});
|
|
10593
|
+
// Restore project files (wasm / webgl-wasm-split.js / game.json) so the
|
|
10594
|
+
// next prepare starts from the original placeholders.
|
|
10595
|
+
restoreFromCache();
|
|
10596
|
+
return res;
|
|
10597
|
+
}
|
|
10598
|
+
async function getSplitResultRemote({ client_key, wasm_md5, wasm_path }) {
|
|
10599
|
+
return request({
|
|
10600
|
+
url: `${BASE_URL}/api/stark_wasm/v4/post/download`,
|
|
10601
|
+
method: 'POST',
|
|
10602
|
+
headers: { ...DEV_HEADERS },
|
|
10603
|
+
data: { client_key, wasm_md5, wasm_path },
|
|
10604
|
+
});
|
|
10605
|
+
}
|
|
10606
|
+
/**
|
|
10607
|
+
* Remote pipeline: after the server finishes preparing (instrumenting) the wasm,
|
|
10608
|
+
* fetch the download URL, download the prepared wasm, replace the project file,
|
|
10609
|
+
* and update webgl-wasm-split.js for the LEGACY reporting flow.
|
|
10610
|
+
*/
|
|
10611
|
+
async function downloadPreparedRemote(data) {
|
|
10612
|
+
wsServer.sendUnitySplitStatus({ status: 'star_fetch_prepared_wasm_url' });
|
|
10613
|
+
const res = await request({
|
|
10614
|
+
url: `${BASE_URL}/api/stark_wasm/v4/post/download_prepared`,
|
|
10615
|
+
method: 'POST',
|
|
10616
|
+
headers: DEV_HEADERS,
|
|
10617
|
+
data,
|
|
10618
|
+
});
|
|
10619
|
+
wsServer.sendUnitySplitStatus({ status: 'fetch_prepared_wasm_url_done' });
|
|
10620
|
+
try {
|
|
10621
|
+
const downloadUrl = res?.data?.result?.download_url;
|
|
10622
|
+
if (!downloadUrl) {
|
|
10623
|
+
console.log('[remote-download-prepared] no download_url in response');
|
|
10624
|
+
return {
|
|
10625
|
+
isSuccess: false,
|
|
10626
|
+
error: { code: res.data?.code, message: res.data?.message || 'No download_url returned' },
|
|
10627
|
+
ctx: res?.ctx,
|
|
10628
|
+
};
|
|
10629
|
+
}
|
|
10630
|
+
const willReplaceWasmPath = path$1.join(process.cwd(), data.wasm_path);
|
|
10631
|
+
const { cacheDir } = keepCacheSync({
|
|
10632
|
+
entryDir: process.cwd(),
|
|
10633
|
+
originalWasmPath: data.wasm_path,
|
|
10634
|
+
originalSplitConfigPath: WASM_SPLIT_CONFIG_FILE_NAME,
|
|
10635
|
+
});
|
|
10636
|
+
console.log(`[remote-download-prepared] target=${willReplaceWasmPath}`);
|
|
10637
|
+
if (downloadUrl.includes('.br')) {
|
|
10638
|
+
const tempWasmPath = path$1.join(cacheDir, '__temp__.wasm.br');
|
|
10639
|
+
console.log('[remote-download-prepared] downloading (br) ->', tempWasmPath);
|
|
10640
|
+
wsServer.sendUnitySplitStatus({ status: 'start_download_prepared_wasm', url: downloadUrl });
|
|
10641
|
+
const startedAt = Date.now();
|
|
10642
|
+
await download(downloadUrl, tempWasmPath);
|
|
10643
|
+
console.log(`[remote-download-prepared] download done in ${Date.now() - startedAt}ms, size=${fs$1.statSync(tempWasmPath).size}`);
|
|
10644
|
+
fs$1.copyFileSync(tempWasmPath, willReplaceWasmPath);
|
|
10645
|
+
wsServer.sendUnitySplitStatus({ status: 'download_prepared_wasm_done', url: downloadUrl });
|
|
10646
|
+
}
|
|
10647
|
+
else {
|
|
10648
|
+
const tempWasmPath = path$1.join(cacheDir, '__temp__.wasm');
|
|
10649
|
+
console.log('[remote-download-prepared] downloading (raw) ->', tempWasmPath);
|
|
10650
|
+
wsServer.sendUnitySplitStatus({ status: 'start_download_prepared_wasm', url: downloadUrl });
|
|
10651
|
+
const startedAt = Date.now();
|
|
10652
|
+
await download(downloadUrl, tempWasmPath);
|
|
10653
|
+
console.log(`[remote-download-prepared] download done in ${Date.now() - startedAt}ms, size=${fs$1.statSync(tempWasmPath).size}`);
|
|
10654
|
+
wsServer.sendUnitySplitStatus({ status: 'download_prepared_wasm_done', url: downloadUrl });
|
|
10655
|
+
wsServer.sendUnitySplitStatus({ status: 'start_compress_prepared_wasm' });
|
|
10656
|
+
await compressWasmFile(tempWasmPath, willReplaceWasmPath);
|
|
10657
|
+
console.log('[remote-download-prepared] compressed and written to project');
|
|
10658
|
+
wsServer.sendUnitySplitStatus({ status: 'compress_prepared_wasm_done', url: downloadUrl });
|
|
10659
|
+
wsServer.sendUnitySplitStatus({ status: 'write_compress_prepared_wasm_done' });
|
|
10660
|
+
}
|
|
10661
|
+
wsServer.sendUnitySplitStatus({ status: 'start_update_wasm_split_config' });
|
|
10662
|
+
// Remote (legacy) pipeline: enable collect but disable archive mode so the
|
|
10663
|
+
// plugin reports to the legacy stark_wasm/v4 collect API.
|
|
10664
|
+
// ORIGINALWASMMD5 must be set now (not only at split time) so the plugin
|
|
10665
|
+
// sends the correct wasm_md5 in every collect report.
|
|
10666
|
+
restoreSplitConfigFromCache();
|
|
10667
|
+
updateWasmSplitConfig({
|
|
10668
|
+
ENABLEWASMCOLLECT: true,
|
|
10669
|
+
ENABLEARCHIVEMODE: false,
|
|
10670
|
+
ORIGINALWASMMD5: res?.data?.result?.original_wasm_md5 ||
|
|
10671
|
+
res?.data?.result?.md5 ||
|
|
10672
|
+
data.wasm_md5,
|
|
10673
|
+
});
|
|
10674
|
+
wsServer.sendUnitySplitStatus({ status: 'update_wasm_split_config_done' });
|
|
10675
|
+
console.log('[remote-download-prepared] split config updated, returning success');
|
|
10676
|
+
return { isSuccess: true, ctx: res?.ctx };
|
|
10677
|
+
}
|
|
10678
|
+
catch (error) {
|
|
10679
|
+
console.log('[remote-download-prepared] error:', error);
|
|
10680
|
+
return {
|
|
10681
|
+
isSuccess: false,
|
|
10682
|
+
error: { code: res.data?.code, message: error instanceof Error ? error.message : String(error) },
|
|
10683
|
+
ctx: res?.ctx,
|
|
10684
|
+
};
|
|
10685
|
+
}
|
|
10686
|
+
}
|
|
10687
|
+
|
|
10688
|
+
function isLocal() {
|
|
10689
|
+
return getLocalState().pipelineMode === 'local';
|
|
10690
|
+
}
|
|
10691
|
+
const startPrepare = (params) => isLocal() ? startPrepare$1(params) : startPrepareRemote(params);
|
|
10692
|
+
const downloadPrepared = (params) => isLocal() ? downloadPrepared$1() : downloadPreparedRemote(params);
|
|
10693
|
+
const setCollect = (params) => isLocal() ? setCollect$1(params) : setCollectRemote(params);
|
|
10694
|
+
const getCollectedFuncIds = (params) => isLocal() ? getCollectedFuncIds$1(params) : getCollectedFuncIdsRemote(params);
|
|
10695
|
+
const getCollecttingInfo = (params) => isLocal() ? getCollecttingInfo$1(params) : getCollecttingInfoRemote(params);
|
|
10696
|
+
const startSplit = (params) => isLocal() ? startSplit$1(params) : startSplitRemote(params);
|
|
10697
|
+
const downloadSplited = (context) => isLocal() ? downloadSplited$1(context) : downloadSplitedRemote(context);
|
|
10698
|
+
const getSplitResult = (params) => isLocal() ? getSplitResult$1() : getSplitResultRemote(params);
|
|
10699
|
+
const getTaskInfo = (params) => isLocal() ? getTaskInfo$1(params) : getTaskInfoRemote(params);
|
|
10700
|
+
const getTaskStatus = (params) => isLocal() ? getTaskStatus$1(params) : getTaskStatusRemote(params);
|
|
10701
|
+
const resetWasmSplit = (data) => isLocal() ? resetWasmSplit$1(data) : resetWasmSplitRemote(data);
|
|
10702
|
+
// Collect session (`/start` / `/finish`) is an implementation detail of the
|
|
10703
|
+
// local `wasm-collect/v1` pipeline — it's invoked inside `setCollectLocal`
|
|
10704
|
+
// and `startSplitLocal` respectively. The remote `stark_wasm/v4` pipeline
|
|
10705
|
+
// has no session concept. IDE never calls these directly, so there is no
|
|
10706
|
+
// dispatcher exposed here.
|
|
9789
10707
|
|
|
9790
10708
|
const gameWasmCancelRoute = {
|
|
9791
10709
|
method: 'post',
|
|
@@ -9889,26 +10807,35 @@ const gameWasmPrepareResultRoute = {
|
|
|
9889
10807
|
method: 'post',
|
|
9890
10808
|
path: '/game/wasm-prepare-result',
|
|
9891
10809
|
handler: async (req, res) => {
|
|
9892
|
-
|
|
9893
|
-
|
|
9894
|
-
const response = await getTaskStatus({
|
|
9895
|
-
client_key: clientKey,
|
|
9896
|
-
wasm_md5: codeMd5,
|
|
9897
|
-
});
|
|
9898
|
-
if (response.error) {
|
|
9899
|
-
res.send({
|
|
9900
|
-
code: errorCode,
|
|
9901
|
-
error: response.error,
|
|
9902
|
-
ctx: response.ctx,
|
|
9903
|
-
});
|
|
9904
|
-
}
|
|
9905
|
-
else {
|
|
10810
|
+
const { pipelineMode } = getLocalState();
|
|
10811
|
+
if (pipelineMode === 'local') {
|
|
9906
10812
|
res.send({
|
|
9907
10813
|
code: successCode,
|
|
9908
|
-
data:
|
|
9909
|
-
ctx:
|
|
10814
|
+
data: { status: WasmStatus.WasmSplitPreparedStatus },
|
|
10815
|
+
ctx: { logid: 'local' },
|
|
9910
10816
|
});
|
|
10817
|
+
return;
|
|
10818
|
+
}
|
|
10819
|
+
const { codeMd5, clientKey } = req.body;
|
|
10820
|
+
const result = await getTaskStatus({
|
|
10821
|
+
client_key: clientKey,
|
|
10822
|
+
wasm_md5: codeMd5,
|
|
10823
|
+
});
|
|
10824
|
+
// For the remote pipeline, forward the full `result` payload so the IDE
|
|
10825
|
+
// gets both `status` and the accompanying `package` info, matching the
|
|
10826
|
+
// legacy behaviour that the UI was written against.
|
|
10827
|
+
if (result?.error) {
|
|
10828
|
+
console.log('[wasm-prepare-result] remote error', result.error);
|
|
10829
|
+
res.send({ code: errorCode, error: result.error, ctx: result?.ctx });
|
|
10830
|
+
return;
|
|
9911
10831
|
}
|
|
10832
|
+
const data = result?.data?.result ?? { status: WasmStatus.IdleStatus };
|
|
10833
|
+
console.log(`[wasm-prepare-result] remote status=${data?.status}`);
|
|
10834
|
+
res.send({
|
|
10835
|
+
code: successCode,
|
|
10836
|
+
data,
|
|
10837
|
+
ctx: result?.ctx ?? { logid: 'remote' },
|
|
10838
|
+
});
|
|
9912
10839
|
},
|
|
9913
10840
|
};
|
|
9914
10841
|
|
|
@@ -9917,7 +10844,8 @@ const gameWasmPrepareRoute = {
|
|
|
9917
10844
|
path: '/game/wasm-prepare',
|
|
9918
10845
|
handler: async (req, res) => {
|
|
9919
10846
|
const { codePath, desc, codeMd5, clientKey } = req.body;
|
|
9920
|
-
|
|
10847
|
+
const { pipelineMode } = getLocalState();
|
|
10848
|
+
console.log(`wasm-prepare-start [mode=${pipelineMode}]`, req.body);
|
|
9921
10849
|
const result = await startPrepare({
|
|
9922
10850
|
client_key: clientKey,
|
|
9923
10851
|
desc,
|
|
@@ -9956,11 +10884,19 @@ const gameWasmSetCollectRoute = {
|
|
|
9956
10884
|
method: 'post',
|
|
9957
10885
|
path: '/game/wasm-set-collect',
|
|
9958
10886
|
handler: async (req, res) => {
|
|
9959
|
-
|
|
10887
|
+
// `resume` is optional and only meaningful for the local pipeline:
|
|
10888
|
+
// when the IDE detects an existing open session (e.g. user refreshed
|
|
10889
|
+
// the page mid-collect) and wants to "继续收集" without nuking the
|
|
10890
|
+
// already-uploaded func_ids, it POSTs `{ resume: true }`. Default
|
|
10891
|
+
// (omitted / false) keeps the historical "fresh run" behaviour on
|
|
10892
|
+
// /start (server gets `reset: true`). See `setCollect` jsdoc for the
|
|
10893
|
+
// two-path contract; remote pipeline ignores the field outright.
|
|
10894
|
+
const { clientKey, codeMd5, resume } = req.body;
|
|
9960
10895
|
console.log('wasm-set-collect', req.body);
|
|
9961
10896
|
const response = await setCollect({
|
|
9962
10897
|
client_key: clientKey,
|
|
9963
10898
|
wasm_md5: codeMd5,
|
|
10899
|
+
resume,
|
|
9964
10900
|
});
|
|
9965
10901
|
if (response.error) {
|
|
9966
10902
|
res.send({
|
|
@@ -9983,50 +10919,42 @@ const gameWasmSplitDownloadResultRoute = {
|
|
|
9983
10919
|
method: 'post',
|
|
9984
10920
|
path: '/game/wasm-split-download-result',
|
|
9985
10921
|
handler: async (req, res) => {
|
|
10922
|
+
const { pipelineMode, splitMeta } = getLocalState();
|
|
10923
|
+
if (pipelineMode === 'local') {
|
|
10924
|
+
if (!splitMeta) {
|
|
10925
|
+
res.send({
|
|
10926
|
+
code: errorCode,
|
|
10927
|
+
error: { message: 'No local split result found. Run split first.' },
|
|
10928
|
+
});
|
|
10929
|
+
return;
|
|
10930
|
+
}
|
|
10931
|
+
res.send({
|
|
10932
|
+
code: successCode,
|
|
10933
|
+
data: { result: splitMeta },
|
|
10934
|
+
msg: 'download success',
|
|
10935
|
+
ctx: { logid: 'local' },
|
|
10936
|
+
});
|
|
10937
|
+
return;
|
|
10938
|
+
}
|
|
9986
10939
|
const { clientKey, codeMd5, codePath } = req.body;
|
|
9987
|
-
|
|
9988
|
-
const response = await getSplitResult({
|
|
10940
|
+
const result = await getSplitResult({
|
|
9989
10941
|
client_key: clientKey,
|
|
9990
10942
|
wasm_md5: codeMd5,
|
|
9991
10943
|
wasm_path: codePath,
|
|
9992
10944
|
});
|
|
9993
|
-
if (
|
|
10945
|
+
if (result.error) {
|
|
9994
10946
|
res.send({
|
|
9995
10947
|
code: errorCode,
|
|
9996
|
-
error:
|
|
9997
|
-
ctx:
|
|
10948
|
+
error: result.error,
|
|
10949
|
+
ctx: result.ctx,
|
|
9998
10950
|
});
|
|
9999
10951
|
}
|
|
10000
10952
|
else {
|
|
10001
|
-
const splitResult = (response.data?.result || {});
|
|
10002
|
-
const requiredDownloadFields = [
|
|
10003
|
-
'main_wasm_download_url',
|
|
10004
|
-
'main_wasm_h5_download_url',
|
|
10005
|
-
// 'sub_wasm_download_url',
|
|
10006
|
-
// 'sub_js_download_url',
|
|
10007
|
-
// 'sub_js_data_download_url',
|
|
10008
|
-
// 'sub_js_range_download_url',
|
|
10009
|
-
];
|
|
10010
|
-
const missingFields = requiredDownloadFields.filter(field => {
|
|
10011
|
-
const value = splitResult[field];
|
|
10012
|
-
return typeof value !== 'string' || value.trim() === '';
|
|
10013
|
-
});
|
|
10014
|
-
if (missingFields.length > 0) {
|
|
10015
|
-
res.send({
|
|
10016
|
-
code: errorCode,
|
|
10017
|
-
error: {
|
|
10018
|
-
message: `Missing required wasm split fields: ${missingFields.join(', ')}`,
|
|
10019
|
-
},
|
|
10020
|
-
data: response.data || {},
|
|
10021
|
-
ctx: response.ctx,
|
|
10022
|
-
});
|
|
10023
|
-
return;
|
|
10024
|
-
}
|
|
10025
10953
|
res.send({
|
|
10026
10954
|
code: successCode,
|
|
10027
|
-
data:
|
|
10955
|
+
data: result.data || {},
|
|
10028
10956
|
msg: 'download success',
|
|
10029
|
-
ctx:
|
|
10957
|
+
ctx: result.ctx,
|
|
10030
10958
|
});
|
|
10031
10959
|
}
|
|
10032
10960
|
},
|
|
@@ -10115,26 +11043,32 @@ const gameWasmSplitResultRoute = {
|
|
|
10115
11043
|
method: 'post',
|
|
10116
11044
|
path: '/game/wasm-split-result',
|
|
10117
11045
|
handler: async (req, res) => {
|
|
10118
|
-
const {
|
|
10119
|
-
|
|
10120
|
-
const response = await getTaskStatus({
|
|
10121
|
-
client_key: clientKey,
|
|
10122
|
-
wasm_md5: codeMd5,
|
|
10123
|
-
});
|
|
10124
|
-
if (response.error) {
|
|
10125
|
-
res.send({
|
|
10126
|
-
code: errorCode,
|
|
10127
|
-
error: response.error,
|
|
10128
|
-
ctx: response.ctx,
|
|
10129
|
-
});
|
|
10130
|
-
}
|
|
10131
|
-
else {
|
|
11046
|
+
const { pipelineMode } = getLocalState();
|
|
11047
|
+
if (pipelineMode === 'local') {
|
|
10132
11048
|
res.send({
|
|
10133
11049
|
code: successCode,
|
|
10134
|
-
data:
|
|
10135
|
-
ctx:
|
|
11050
|
+
data: { status: WasmStatus.WasmSplitDoneStatus },
|
|
11051
|
+
ctx: { logid: 'local' },
|
|
10136
11052
|
});
|
|
11053
|
+
return;
|
|
10137
11054
|
}
|
|
11055
|
+
const { clientKey, codeMd5 } = req.body;
|
|
11056
|
+
const result = await getTaskStatus({
|
|
11057
|
+
client_key: clientKey,
|
|
11058
|
+
wasm_md5: codeMd5,
|
|
11059
|
+
});
|
|
11060
|
+
if (result?.error) {
|
|
11061
|
+
console.log('[wasm-split-result] remote error', result.error);
|
|
11062
|
+
res.send({ code: errorCode, error: result.error, ctx: result?.ctx });
|
|
11063
|
+
return;
|
|
11064
|
+
}
|
|
11065
|
+
const data = result?.data?.result ?? { status: WasmStatus.IdleStatus };
|
|
11066
|
+
console.log(`[wasm-split-result] remote status=${data?.status}`);
|
|
11067
|
+
res.send({
|
|
11068
|
+
code: successCode,
|
|
11069
|
+
data,
|
|
11070
|
+
ctx: result?.ctx ?? { logid: 'remote' },
|
|
11071
|
+
});
|
|
10138
11072
|
},
|
|
10139
11073
|
};
|
|
10140
11074
|
|
|
@@ -10145,10 +11079,52 @@ const gameWasmSplitConfigRoute = {
|
|
|
10145
11079
|
const config = getSplitConfig();
|
|
10146
11080
|
if (!config) {
|
|
10147
11081
|
res.send({ code: errorCode, data: 'Failed to parse split config' });
|
|
11082
|
+
return;
|
|
10148
11083
|
}
|
|
10149
|
-
|
|
10150
|
-
|
|
11084
|
+
// When the CLI is restarted mid-session, localState.pipelineMode resets to
|
|
11085
|
+
// 'local' even though the project on disk may have been prepared in remote
|
|
11086
|
+
// mode. Re-infer it from the persisted split config so subsequent
|
|
11087
|
+
// dispatches (taskinfo / collect / split download) pick the right backend.
|
|
11088
|
+
// Heuristic: the local pipeline always writes enableArchiveMode=true, the
|
|
11089
|
+
// legacy remote pipeline always writes enableArchiveMode=false.
|
|
11090
|
+
if (config.enableWasmCollect) {
|
|
11091
|
+
const inferredMode = config.enableArchiveMode === true ? 'local' : 'remote';
|
|
11092
|
+
const current = getLocalState().pipelineMode;
|
|
11093
|
+
if (current !== inferredMode) {
|
|
11094
|
+
setLocalState({ pipelineMode: inferredMode });
|
|
11095
|
+
console.log(`[pipeline] inferred mode=${inferredMode} from webgl-wasm-split.js (was ${current})`);
|
|
11096
|
+
}
|
|
10151
11097
|
}
|
|
11098
|
+
// ── wasm drift guard ─────────────────────────────────────────────
|
|
11099
|
+
//
|
|
11100
|
+
// `webgl-wasm-split.js` is persisted state about "which stage was
|
|
11101
|
+
// last completed", but it can desync from reality: the user's Unity
|
|
11102
|
+
// build re-emits `wasmcode/<file>.br` with a fresh, un-instrumented
|
|
11103
|
+
// binary while the config still claims `enableWasmCollect=true`. The
|
|
11104
|
+
// IDE's `canCollect()` then returns true, the prepare step gets
|
|
11105
|
+
// skipped, and the device loads a wasm that has no `scwebgl.logCall`
|
|
11106
|
+
// import — the `[wasmcollect] FATAL: no scwebgl.logCall import`
|
|
11107
|
+
// failure.
|
|
11108
|
+
//
|
|
11109
|
+
// To guard: compare the wasm file currently on disk to the md5 that
|
|
11110
|
+
// startPrepare wrote into `.ttmg-temp/prepared-meta.json`. If they
|
|
11111
|
+
// differ, demote `enableWasmCollect` back to its placeholder string
|
|
11112
|
+
// in the response so `canCollect()` → false and the IDE walks the
|
|
11113
|
+
// user through prepare again. We never touch the real config file
|
|
11114
|
+
// on disk — this is a transient correction at the read boundary, so
|
|
11115
|
+
// the next successful prepare seamlessly re-aligns everything.
|
|
11116
|
+
if (config.enableWasmCollect === true) {
|
|
11117
|
+
const wasmMeta = computeCurrentProjectWasmMd5();
|
|
11118
|
+
if (wasmMeta && wasmMeta.currentMd5 !== wasmMeta.meta.preparedWasmMd5) {
|
|
11119
|
+
console.warn(`[wasmtool] wasm drift detected: project wasm md5=${wasmMeta.currentMd5} but prepared meta expected ${wasmMeta.meta.preparedWasmMd5} (path=${wasmMeta.meta.codePath}). Forcing IDE back to prepare.`);
|
|
11120
|
+
// Mirror the string-placeholder shape the template uses before
|
|
11121
|
+
// prepare writes a real boolean — matches what `canCollect`
|
|
11122
|
+
// expects and is indistinguishable from "never prepared" from
|
|
11123
|
+
// the IDE's perspective.
|
|
11124
|
+
config.enableWasmCollect = '$ENABLEWASMCOLLECT';
|
|
11125
|
+
}
|
|
11126
|
+
}
|
|
11127
|
+
res.send({ code: successCode, data: config });
|
|
10152
11128
|
},
|
|
10153
11129
|
};
|
|
10154
11130
|
|
|
@@ -10216,6 +11192,59 @@ function getGameFallbackRoute(publicPath) {
|
|
|
10216
11192
|
};
|
|
10217
11193
|
}
|
|
10218
11194
|
|
|
11195
|
+
/**
|
|
11196
|
+
* Explicit "user clicked a lang toggle in the IDE" endpoint. Unlike
|
|
11197
|
+
* `/game/config-fillback` — which intentionally no-ops when the CLI
|
|
11198
|
+
* already has a lang configured — this route always writes the incoming
|
|
11199
|
+
* lang to TTMGRC so the next IDE bootstrap's `setCurrentLang(cliLang)`
|
|
11200
|
+
* call won't stomp the user's fresh choice.
|
|
11201
|
+
*/
|
|
11202
|
+
const gameLanguageRoute = {
|
|
11203
|
+
method: 'post',
|
|
11204
|
+
path: '/game/language',
|
|
11205
|
+
handler: async (req, res) => {
|
|
11206
|
+
const incomingLang = req.body?.lang;
|
|
11207
|
+
const nextLang = resolveSupportedLanguage(incomingLang);
|
|
11208
|
+
if (!nextLang) {
|
|
11209
|
+
res.send({
|
|
11210
|
+
code: successCode,
|
|
11211
|
+
data: {
|
|
11212
|
+
lang: null,
|
|
11213
|
+
updated: false,
|
|
11214
|
+
},
|
|
11215
|
+
});
|
|
11216
|
+
return;
|
|
11217
|
+
}
|
|
11218
|
+
setTTMGRC({ lang: nextLang });
|
|
11219
|
+
res.send({
|
|
11220
|
+
code: successCode,
|
|
11221
|
+
data: {
|
|
11222
|
+
lang: nextLang,
|
|
11223
|
+
updated: true,
|
|
11224
|
+
},
|
|
11225
|
+
});
|
|
11226
|
+
},
|
|
11227
|
+
};
|
|
11228
|
+
|
|
11229
|
+
const gamePipelineModeRoute = {
|
|
11230
|
+
method: 'post',
|
|
11231
|
+
path: '/game/pipeline-mode',
|
|
11232
|
+
handler: (req, res) => {
|
|
11233
|
+
const { mode } = req.body;
|
|
11234
|
+
setLocalState({ pipelineMode: mode });
|
|
11235
|
+
console.log(`[pipeline] mode set to: ${mode}`);
|
|
11236
|
+
res.send({ code: successCode, data: { mode } });
|
|
11237
|
+
},
|
|
11238
|
+
};
|
|
11239
|
+
const gamePipelineModeGetRoute = {
|
|
11240
|
+
method: 'get',
|
|
11241
|
+
path: '/game/pipeline-mode',
|
|
11242
|
+
handler: (_req, res) => {
|
|
11243
|
+
const { pipelineMode } = getLocalState();
|
|
11244
|
+
res.send({ code: successCode, data: { mode: pipelineMode } });
|
|
11245
|
+
},
|
|
11246
|
+
};
|
|
11247
|
+
|
|
10219
11248
|
const routes = [
|
|
10220
11249
|
gameConfigRoute,
|
|
10221
11250
|
gameConfigFillbackRoute,
|
|
@@ -10237,6 +11266,9 @@ const routes = [
|
|
|
10237
11266
|
gameWasmSplitDownloadRoute,
|
|
10238
11267
|
gameWasmCancelRoute,
|
|
10239
11268
|
gameWasmSplitResetRoute,
|
|
11269
|
+
gamePipelineModeRoute,
|
|
11270
|
+
gamePipelineModeGetRoute,
|
|
11271
|
+
gameLanguageRoute,
|
|
10240
11272
|
];
|
|
10241
11273
|
function registerRoutes(app, options) {
|
|
10242
11274
|
const allRoutes = [...routes, getGameFallbackRoute(options.publicPath)];
|
|
@@ -10679,7 +11711,7 @@ async function upload({ clientKey, note = '--', dir, }) {
|
|
|
10679
11711
|
}
|
|
10680
11712
|
}
|
|
10681
11713
|
|
|
10682
|
-
var version = "0.3.6-beta.
|
|
11714
|
+
var version = "0.3.6-beta.wasmcode.split";
|
|
10683
11715
|
var pkg = {
|
|
10684
11716
|
version: version};
|
|
10685
11717
|
|