@ttmg/cli 0.3.9-beta.wasm.1 → 0.4.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +639 -162
- package/dist/index.js.map +1 -1
- package/dist/package.json +1 -1
- package/dist/public/assets/Card-C35olIke.js +1 -0
- package/dist/public/assets/Detail-Bw_dl9hw.js +1 -0
- package/dist/public/assets/Detail-Bw_dl9hw.js.br +0 -0
- package/dist/public/assets/MonetizationMode-C7Wyv9Mg.js +1 -0
- package/dist/public/assets/MonetizationModeSummary-D-zuX05a.js +1 -0
- package/dist/public/assets/SectionHeader-DRXnax7L.js +1 -0
- package/dist/public/assets/Tag-CgPHrk93.js +1 -0
- package/dist/public/assets/arrow-left-CNdHGsDz.js +1 -0
- package/dist/public/assets/baseForm-C2Ms-wqt.js +10 -0
- package/dist/public/assets/baseForm-C2Ms-wqt.js.br +0 -0
- package/dist/public/assets/baseForm-Dl4zA6hU.css +1 -0
- package/dist/public/assets/baseForm-Dl4zA6hU.css.br +0 -0
- package/dist/public/assets/chevron-right-CX-Bo3I2.js +1 -0
- package/dist/public/assets/compass-BUqxZo39.js +1 -0
- package/dist/public/assets/index-4NS2mMuQ.css +1 -0
- package/dist/public/assets/{index-BpefkOoB.js → index-8z6tNstf.js} +1 -1
- package/dist/public/assets/index-AGEFeR2m.js +1 -0
- package/dist/public/assets/index-B8dmfh3A.js +1 -0
- package/dist/public/assets/index-B9C7FZes.js +1 -0
- package/dist/public/assets/index-BAud2cRu.css +1 -0
- package/dist/public/assets/index-BOl1-Siv.css +1 -0
- package/dist/public/assets/index-BOl1-Siv.css.br +0 -0
- package/dist/public/assets/index-BULJJdeE.js +1 -0
- package/dist/public/assets/index-BULJJdeE.js.br +0 -0
- package/dist/public/assets/index-B_iiHvzl.js +1 -0
- package/dist/public/assets/index-B_iiHvzl.js.br +0 -0
- package/dist/public/assets/index-BmmpJdOy.js +1 -0
- package/dist/public/assets/index-BmmpJdOy.js.br +0 -0
- package/dist/public/assets/{index-f0PBkQd9.js → index-BuGJ38RF.js} +1 -1
- package/dist/public/assets/index-ByX5RuFA.css +1 -0
- package/dist/public/assets/{index-BJQfbNPd.js → index-ByqGLZ4G.js} +1 -1
- package/dist/public/assets/index-C-tTmNa4.css +1 -0
- package/dist/public/assets/index-CFd8iglC.css +1 -0
- package/dist/public/assets/index-CFd8iglC.css.br +0 -0
- package/dist/public/assets/index-CIIptoBi.js +1 -0
- package/dist/public/assets/index-CLXkCzG6.js +1 -0
- package/dist/public/assets/{index-BUDAPXlB.js → index-CUV27PJL.js} +1 -1
- package/dist/public/assets/index-Cc1ilXmc.css +1 -0
- package/dist/public/assets/{index-tOg_vZEc.js → index-CjInIkcE.js} +1 -1
- package/dist/public/assets/index-Crx61Qjc.css +1 -0
- package/dist/public/assets/{index-Ba1XAQLw.js → index-CxeatYOZ.js} +1 -1
- package/dist/public/assets/index-D-GbEkoB.css +1 -0
- package/dist/public/assets/index-D0DgrJ_K.js +1 -0
- package/dist/public/assets/index-D28D1GWM.js +1 -0
- package/dist/public/assets/index-DCZsvwKh.js +1 -0
- package/dist/public/assets/index-Dd2rc2_1.js +1 -0
- package/dist/public/assets/index-Dd2rc2_1.js.br +0 -0
- package/dist/public/assets/index-Dk9_4hQ4.js +1 -0
- package/dist/public/assets/index-Dvg_oNs7.css +1 -0
- package/dist/public/assets/index-EorOvXWq.js +1 -0
- package/dist/public/assets/{index-CgAbOvxk.css → index-ROKxx4f7.css} +1 -1
- package/dist/public/assets/index-ROKxx4f7.css.br +0 -0
- package/dist/public/assets/index-RYY-l6Oq.css +1 -0
- package/dist/public/assets/index-_6n0s04V.css +1 -0
- package/dist/public/assets/index-_6n0s04V.css.br +0 -0
- package/dist/public/assets/index-cC0QApVl.js +14 -0
- package/dist/public/assets/index-cC0QApVl.js.br +0 -0
- package/dist/public/assets/index-faRHENEQ.css +1 -0
- package/dist/public/assets/index-l16xoaSk.js +1 -0
- package/dist/public/assets/sparkles-C54nzN2M.js +1 -0
- package/dist/public/assets/zap-g-acVHwy.js +1 -0
- package/dist/public/index.html +11 -8
- package/package.json +1 -1
- package/CHANGELOG.md +0 -228
- package/dist/public/assets/Detail-CqXvsvPM.js +0 -1
- package/dist/public/assets/Detail-CqXvsvPM.js.br +0 -0
- package/dist/public/assets/MonetizationMode-2bp9UZWM.js +0 -1
- package/dist/public/assets/MonetizationModeSummary-DBtIQq9Z.js +0 -1
- package/dist/public/assets/baseForm-BFkpAlns.js +0 -10
- package/dist/public/assets/baseForm-BFkpAlns.js.br +0 -0
- package/dist/public/assets/baseForm-CB6KCNqW.css +0 -1
- package/dist/public/assets/baseForm-CB6KCNqW.css.br +0 -0
- package/dist/public/assets/index-88dZ53Te.css +0 -1
- package/dist/public/assets/index-88dZ53Te.css.br +0 -0
- package/dist/public/assets/index-B6ZeUJlM.js +0 -1
- package/dist/public/assets/index-BFePHKNX.js +0 -1
- package/dist/public/assets/index-BFePHKNX.js.br +0 -0
- package/dist/public/assets/index-BPzYPKPA.js +0 -1
- package/dist/public/assets/index-BRFS2ZhY.css +0 -1
- package/dist/public/assets/index-BRFS2ZhY.css.br +0 -0
- package/dist/public/assets/index-BYgShzrj.js +0 -1
- package/dist/public/assets/index-Bf6aJOeV.css +0 -1
- package/dist/public/assets/index-Bf85t01Q.css +0 -1
- package/dist/public/assets/index-Bf85t01Q.css.br +0 -0
- package/dist/public/assets/index-BnU4EHWL.css +0 -1
- package/dist/public/assets/index-BnU4EHWL.css.br +0 -0
- package/dist/public/assets/index-BvxhyFWU.js +0 -14
- package/dist/public/assets/index-BvxhyFWU.js.br +0 -0
- package/dist/public/assets/index-BwbPFgZF.css +0 -1
- package/dist/public/assets/index-C06KDNuj.css +0 -1
- package/dist/public/assets/index-CH7igbHY.css +0 -1
- package/dist/public/assets/index-CL2qDQto.js +0 -1
- package/dist/public/assets/index-CLgcHgzd.css +0 -1
- package/dist/public/assets/index-CMUKwrEm.js +0 -1
- package/dist/public/assets/index-CRAXXzpR.js +0 -1
- package/dist/public/assets/index-CRAXXzpR.js.br +0 -0
- package/dist/public/assets/index-CgAbOvxk.css.br +0 -0
- package/dist/public/assets/index-DNsJSSmy.css +0 -1
- package/dist/public/assets/index-DY13Lo-E.js +0 -1
- package/dist/public/assets/index-DY13Lo-E.js.br +0 -0
- package/dist/public/assets/index-DqFmR7Qk.css +0 -1
- package/dist/public/assets/index-FFfimhRp.js +0 -1
- package/dist/public/assets/index-FFfimhRp.js.br +0 -0
- package/dist/public/assets/index-OLBa9viz.js +0 -1
- package/dist/public/assets/index-OsVhWD4K.js +0 -1
- package/dist/public/assets/index-lKrpiZfQ.js +0 -1
- package/dist/public/assets/index-lKrpiZfQ.js.br +0 -0
- package/dist/public/assets/times-C1GmugF6.js +0 -1
- package/dist/public/assets/times-C7C5ulLg.css +0 -1
package/dist/index.js
CHANGED
|
@@ -6588,9 +6588,30 @@ function buildCookieHeaderFromSetCookies(setCookies) {
|
|
|
6588
6588
|
return sanitizeCookieHeader(setCookies.join('; '));
|
|
6589
6589
|
}
|
|
6590
6590
|
|
|
6591
|
+
/**
|
|
6592
|
+
* Verbose logging helpers.
|
|
6593
|
+
*
|
|
6594
|
+
* The CLI registers a global `--verbose` flag (see `src/index.ts`). Detailed
|
|
6595
|
+
* diagnostic logs — HTTP request/response bodies, the local wasm split
|
|
6596
|
+
* pipeline's `[wasmtool]` / `[wasm-split]` / `[download]` breadcrumbs — should
|
|
6597
|
+
* only surface when the user explicitly opts in, so the default `ttmg dev`
|
|
6598
|
+
* output stays readable.
|
|
6599
|
+
*
|
|
6600
|
+
* Genuine failures must keep using `console.error` directly so they are always
|
|
6601
|
+
* visible regardless of `--verbose`.
|
|
6602
|
+
*/
|
|
6591
6603
|
function isVerboseEnabled() {
|
|
6592
6604
|
return process.argv.includes('--verbose');
|
|
6593
6605
|
}
|
|
6606
|
+
function verboseLog(...args) {
|
|
6607
|
+
if (isVerboseEnabled())
|
|
6608
|
+
console.log(...args);
|
|
6609
|
+
}
|
|
6610
|
+
function verboseWarn(...args) {
|
|
6611
|
+
if (isVerboseEnabled())
|
|
6612
|
+
console.warn(...args);
|
|
6613
|
+
}
|
|
6614
|
+
|
|
6594
6615
|
/**
|
|
6595
6616
|
* PPE / 测试环境开关。
|
|
6596
6617
|
*
|
|
@@ -6749,7 +6770,7 @@ function printApiResponseLog(title, result) {
|
|
|
6749
6770
|
['Return Value', result.data],
|
|
6750
6771
|
]);
|
|
6751
6772
|
}
|
|
6752
|
-
async function request({ url, method, data, headers, params, }) {
|
|
6773
|
+
async function request({ url, method, data, headers, params, logRequestBody = true, }) {
|
|
6753
6774
|
const config = getTTMGRC();
|
|
6754
6775
|
const cookie = sanitizeCookieHeader(config?.cookie);
|
|
6755
6776
|
const proxyConfig = getAxiosProxyConfig();
|
|
@@ -6757,7 +6778,9 @@ async function request({ url, method, data, headers, params, }) {
|
|
|
6757
6778
|
printApiRequestLog({
|
|
6758
6779
|
url,
|
|
6759
6780
|
method,
|
|
6760
|
-
params:
|
|
6781
|
+
params: logRequestBody
|
|
6782
|
+
? getRequestParams(params, data)
|
|
6783
|
+
: '[omitted: large request body]',
|
|
6761
6784
|
});
|
|
6762
6785
|
}
|
|
6763
6786
|
try {
|
|
@@ -6821,7 +6844,7 @@ async function download(url, filePath) {
|
|
|
6821
6844
|
catch { }
|
|
6822
6845
|
}
|
|
6823
6846
|
const proxyConfig = getAxiosProxyConfig();
|
|
6824
|
-
|
|
6847
|
+
verboseLog('[download] start', { url: url.slice(0, 120), filePath, hasProxy: !!proxyConfig.httpsAgent });
|
|
6825
6848
|
try {
|
|
6826
6849
|
const res = await axios.get(url, {
|
|
6827
6850
|
responseType: 'stream',
|
|
@@ -6857,7 +6880,7 @@ async function download(url, filePath) {
|
|
|
6857
6880
|
};
|
|
6858
6881
|
const onClose = () => {
|
|
6859
6882
|
cleanup();
|
|
6860
|
-
|
|
6883
|
+
verboseLog(`[download] done: ${received} bytes in ${Date.now() - startedAt}ms`);
|
|
6861
6884
|
resolve();
|
|
6862
6885
|
};
|
|
6863
6886
|
const cleanup = () => {
|
|
@@ -6875,7 +6898,7 @@ async function download(url, filePath) {
|
|
|
6875
6898
|
const pct = Math.floor((received / total) * 10) * 10;
|
|
6876
6899
|
if (pct !== lastLoggedPct) {
|
|
6877
6900
|
lastLoggedPct = pct;
|
|
6878
|
-
|
|
6901
|
+
verboseLog(`[download] ${pct}% (${received}/${total})`);
|
|
6879
6902
|
}
|
|
6880
6903
|
}
|
|
6881
6904
|
};
|
|
@@ -6889,7 +6912,7 @@ async function download(url, filePath) {
|
|
|
6889
6912
|
return { ok: true };
|
|
6890
6913
|
}
|
|
6891
6914
|
catch (err) {
|
|
6892
|
-
|
|
6915
|
+
verboseLog('[download] failed:', err?.message);
|
|
6893
6916
|
if (isAxiosError(err) && err.response?.status === 403) {
|
|
6894
6917
|
throw new Error('下载链接已过期,请重新进行分包后重试');
|
|
6895
6918
|
}
|
|
@@ -6901,10 +6924,8 @@ function isAxiosError(e) {
|
|
|
6901
6924
|
}
|
|
6902
6925
|
|
|
6903
6926
|
const CREATE_DIRECT_FEED_CARD_URL = 'https://developers.tiktok.com/tiktok/v4/devportal/mini_game/fyf_card/create_direct_feed_card';
|
|
6904
|
-
const DIRECT_FEED_CARD_HEADERS$
|
|
6927
|
+
const DIRECT_FEED_CARD_HEADERS$3 = {
|
|
6905
6928
|
'Content-Type': 'application/json',
|
|
6906
|
-
'x-use-ppe': '1',
|
|
6907
|
-
'x-tt-env': 'ppe_feed_play',
|
|
6908
6929
|
};
|
|
6909
6930
|
async function createDirectFeedCard({ appId, clientKey, directFeedCard, }) {
|
|
6910
6931
|
const payload = {
|
|
@@ -6915,22 +6936,20 @@ async function createDirectFeedCard({ appId, clientKey, directFeedCard, }) {
|
|
|
6915
6936
|
return request({
|
|
6916
6937
|
url: CREATE_DIRECT_FEED_CARD_URL,
|
|
6917
6938
|
method: 'POST',
|
|
6918
|
-
headers: DIRECT_FEED_CARD_HEADERS$
|
|
6939
|
+
headers: DIRECT_FEED_CARD_HEADERS$3,
|
|
6919
6940
|
data: payload,
|
|
6920
6941
|
});
|
|
6921
6942
|
}
|
|
6922
6943
|
|
|
6923
6944
|
const GET_DIRECT_FEED_SCENARIOS_URL = 'https://developers.tiktok.com/tiktok/v4/devportal/mini_game/fyf_card/get_direct_feed_scenario';
|
|
6924
|
-
const DIRECT_FEED_CARD_HEADERS$
|
|
6945
|
+
const DIRECT_FEED_CARD_HEADERS$2 = {
|
|
6925
6946
|
'Content-Type': 'application/json',
|
|
6926
|
-
'x-use-ppe': '1',
|
|
6927
|
-
'x-tt-env': 'ppe_feed_play',
|
|
6928
6947
|
};
|
|
6929
6948
|
async function fetchDirectFeedScenarios({ appId, clientKey, }) {
|
|
6930
6949
|
return request({
|
|
6931
6950
|
url: GET_DIRECT_FEED_SCENARIOS_URL,
|
|
6932
6951
|
method: 'POST',
|
|
6933
|
-
headers: DIRECT_FEED_CARD_HEADERS$
|
|
6952
|
+
headers: DIRECT_FEED_CARD_HEADERS$2,
|
|
6934
6953
|
data: {
|
|
6935
6954
|
app_id: appId,
|
|
6936
6955
|
client_key: clientKey,
|
|
@@ -6950,16 +6969,14 @@ async function fetchGameAssetPreviewUrl({ assetId, }) {
|
|
|
6950
6969
|
}
|
|
6951
6970
|
|
|
6952
6971
|
const GET_DIRECT_FEED_CARD_ASSET_PREVIEW_URL = 'https://developers.tiktok.com/tiktok/v4/devportal/mini_game/fyf_card/get_direct_feed_card_asset_preview_url';
|
|
6953
|
-
const DIRECT_FEED_CARD_HEADERS = {
|
|
6972
|
+
const DIRECT_FEED_CARD_HEADERS$1 = {
|
|
6954
6973
|
'Content-Type': 'application/json',
|
|
6955
|
-
'x-use-ppe': '1',
|
|
6956
|
-
'x-tt-env': 'ppe_feed_play',
|
|
6957
6974
|
};
|
|
6958
6975
|
async function fetchDirectFeedCardAssetPreviewUrl({ assetId, contentId, }) {
|
|
6959
6976
|
return request({
|
|
6960
6977
|
url: GET_DIRECT_FEED_CARD_ASSET_PREVIEW_URL,
|
|
6961
6978
|
method: 'POST',
|
|
6962
|
-
headers: DIRECT_FEED_CARD_HEADERS,
|
|
6979
|
+
headers: DIRECT_FEED_CARD_HEADERS$1,
|
|
6963
6980
|
data: {
|
|
6964
6981
|
asset_id: assetId,
|
|
6965
6982
|
content_id: contentId,
|
|
@@ -7017,8 +7034,6 @@ async function fetchGameInfo(clientKey) {
|
|
|
7017
7034
|
const LIST_DIRECT_FEED_CARD_URL = 'https://developers.tiktok.com/tiktok/v4/devportal/mini_game/fyf_card/list_direct_feed_card';
|
|
7018
7035
|
const LIST_DIRECT_FEED_CARD_HEADERS = {
|
|
7019
7036
|
'Content-Type': 'application/json',
|
|
7020
|
-
'x-use-ppe': '1',
|
|
7021
|
-
'x-tt-env': 'ppe_feed_play',
|
|
7022
7037
|
};
|
|
7023
7038
|
async function listDirectFeedCards({ appId, clientKey, }) {
|
|
7024
7039
|
return request({
|
|
@@ -7032,6 +7047,23 @@ async function listDirectFeedCards({ appId, clientKey, }) {
|
|
|
7032
7047
|
});
|
|
7033
7048
|
}
|
|
7034
7049
|
|
|
7050
|
+
const CHANGE_DIRECT_FEED_CARD_STATUS_URL = 'https://developers.tiktok.com/tiktok/v4/devportal/mini_game/fyf_card/change_direct_feed_card_status';
|
|
7051
|
+
const DIRECT_FEED_CARD_HEADERS = {
|
|
7052
|
+
'Content-Type': 'application/json',
|
|
7053
|
+
};
|
|
7054
|
+
async function changeDirectFeedCardStatus({ appId, clientKey, contentStatus, }) {
|
|
7055
|
+
return request({
|
|
7056
|
+
url: CHANGE_DIRECT_FEED_CARD_STATUS_URL,
|
|
7057
|
+
method: 'POST',
|
|
7058
|
+
headers: DIRECT_FEED_CARD_HEADERS,
|
|
7059
|
+
data: {
|
|
7060
|
+
app_id: appId,
|
|
7061
|
+
client_key: clientKey,
|
|
7062
|
+
content_status: contentStatus,
|
|
7063
|
+
},
|
|
7064
|
+
});
|
|
7065
|
+
}
|
|
7066
|
+
|
|
7035
7067
|
async function uploadGameToPlatform({ data, name, clientKey, note = '--', appId, }) {
|
|
7036
7068
|
if (!appId) {
|
|
7037
7069
|
return {
|
|
@@ -8349,8 +8381,81 @@ async function compile(context) {
|
|
|
8349
8381
|
}
|
|
8350
8382
|
|
|
8351
8383
|
// import { uploadGame } from './uploadGame';
|
|
8384
|
+
/* ---------------------------------------------------------------------------
|
|
8385
|
+
* Watch suspension control
|
|
8386
|
+
*
|
|
8387
|
+
* The wasm-split pipeline (prepare / split download / rollback) intentionally
|
|
8388
|
+
* rewrites a burst of project files — the instrumented wasm, the `wasmcode*`
|
|
8389
|
+
* subpackage dirs, `game.json`, `webgl-wasm-split.js`. Without gating, every
|
|
8390
|
+
* one of those writes trips the watcher below, and each trip runs a full
|
|
8391
|
+
* `compile` + device `resourceChange` reload *in the middle of the pipeline*.
|
|
8392
|
+
* That both wastes cycles and can reload the device onto a half-written /
|
|
8393
|
+
* transient build. We suspend the watcher around those operations and fire
|
|
8394
|
+
* exactly one update after they complete ("完成之后再触发更新").
|
|
8395
|
+
*
|
|
8396
|
+
* `suspendDepth` is a refcount so overlapping / nested operations are safe.
|
|
8397
|
+
* `muteUntil` keeps swallowing events for a short cooldown AFTER resume:
|
|
8398
|
+
* chokidar's `awaitWriteFinish` delays events ~1s past the actual write, so
|
|
8399
|
+
* those late events from our own writes would otherwise land after we've
|
|
8400
|
+
* resumed and re-trigger the very reload we just suppressed.
|
|
8401
|
+
* ------------------------------------------------------------------------- */
|
|
8402
|
+
let suspendDepth = 0;
|
|
8403
|
+
let muteUntil = 0;
|
|
8404
|
+
let pendingWhileSuspended = false;
|
|
8405
|
+
// chokidar awaitWriteFinish stabilityThreshold (1000ms) + headroom, so the
|
|
8406
|
+
// delayed FS events caused by the pipeline's own writes are swallowed.
|
|
8407
|
+
const RESUME_COOLDOWN_MS = 2500;
|
|
8408
|
+
// Wired up inside `watch()` so suspend/resume can reuse the exact same
|
|
8409
|
+
// (debounced) compile + resourceChange action the watcher fires normally.
|
|
8410
|
+
let triggerUpdate = null;
|
|
8411
|
+
function suspendWatch() {
|
|
8412
|
+
suspendDepth += 1;
|
|
8413
|
+
}
|
|
8414
|
+
function resumeWatch(options = {}) {
|
|
8415
|
+
if (suspendDepth > 0) {
|
|
8416
|
+
suspendDepth -= 1;
|
|
8417
|
+
}
|
|
8418
|
+
if (suspendDepth > 0) {
|
|
8419
|
+
return;
|
|
8420
|
+
}
|
|
8421
|
+
// Back to depth 0 — start the cooldown that swallows our own delayed FS
|
|
8422
|
+
// events, then emit a single update if anything changed while suspended
|
|
8423
|
+
// (or the caller forces it).
|
|
8424
|
+
muteUntil = Date.now() + RESUME_COOLDOWN_MS;
|
|
8425
|
+
const shouldEmit = options.emit ?? pendingWhileSuspended;
|
|
8426
|
+
pendingWhileSuspended = false;
|
|
8427
|
+
if (shouldEmit) {
|
|
8428
|
+
triggerUpdate?.();
|
|
8429
|
+
}
|
|
8430
|
+
}
|
|
8431
|
+
/**
|
|
8432
|
+
* Run a project-file-mutating operation with the watcher muted, then fire a
|
|
8433
|
+
* single update once it finishes. Use this around the wasm-split pipeline
|
|
8434
|
+
* steps so their intermediate writes don't each reload the device.
|
|
8435
|
+
*/
|
|
8436
|
+
async function withWatchSuspended(fn, options = {}) {
|
|
8437
|
+
suspendWatch();
|
|
8438
|
+
try {
|
|
8439
|
+
return await fn();
|
|
8440
|
+
}
|
|
8441
|
+
finally {
|
|
8442
|
+
resumeWatch({ emit: options.emitOnFinish ?? true });
|
|
8443
|
+
}
|
|
8444
|
+
}
|
|
8352
8445
|
async function watch() {
|
|
8353
8446
|
let debounceTimer = null;
|
|
8447
|
+
const runUpdate = async () => {
|
|
8448
|
+
await compile({ mode: 'watch' });
|
|
8449
|
+
wsServer.sendResourceChange();
|
|
8450
|
+
};
|
|
8451
|
+
triggerUpdate = () => {
|
|
8452
|
+
if (debounceTimer)
|
|
8453
|
+
clearTimeout(debounceTimer);
|
|
8454
|
+
debounceTimer = setTimeout(() => {
|
|
8455
|
+
runUpdate();
|
|
8456
|
+
debounceTimer = null;
|
|
8457
|
+
}, 2000);
|
|
8458
|
+
};
|
|
8354
8459
|
// 监听当前工作目录,排除 node_modules 和 .git
|
|
8355
8460
|
const watcher = chokidar.watch(process.cwd(), {
|
|
8356
8461
|
/**
|
|
@@ -8365,17 +8470,20 @@ async function watch() {
|
|
|
8365
8470
|
});
|
|
8366
8471
|
// 任意文件变化都触发
|
|
8367
8472
|
watcher.on('all', (event, path) => {
|
|
8368
|
-
//
|
|
8369
|
-
|
|
8370
|
-
|
|
8371
|
-
|
|
8372
|
-
|
|
8373
|
-
|
|
8374
|
-
|
|
8375
|
-
|
|
8376
|
-
|
|
8377
|
-
|
|
8378
|
-
|
|
8473
|
+
// Pipeline is actively rewriting project files. Remember that something
|
|
8474
|
+
// changed so the resume flush can fire one update, and skip the per-write
|
|
8475
|
+
// compile/reload.
|
|
8476
|
+
if (suspendDepth > 0) {
|
|
8477
|
+
pendingWhileSuspended = true;
|
|
8478
|
+
return;
|
|
8479
|
+
}
|
|
8480
|
+
// Cooldown window right after a pipeline finished: these are the delayed
|
|
8481
|
+
// `awaitWriteFinish` events from the pipeline's own writes. We already
|
|
8482
|
+
// emitted one update on resume, so swallow them to avoid a double reload.
|
|
8483
|
+
if (Date.now() < muteUntil) {
|
|
8484
|
+
return;
|
|
8485
|
+
}
|
|
8486
|
+
triggerUpdate?.();
|
|
8379
8487
|
});
|
|
8380
8488
|
watcher.on('error', error => {
|
|
8381
8489
|
// console.error(chalk.red('[watch] 监听发生错误:'), error);
|
|
@@ -9332,6 +9440,68 @@ const gameCheckRoute = {
|
|
|
9332
9440
|
};
|
|
9333
9441
|
|
|
9334
9442
|
const changelog = [
|
|
9443
|
+
{
|
|
9444
|
+
title: '0.3.9',
|
|
9445
|
+
target: {
|
|
9446
|
+
iOS: '>=43.1',
|
|
9447
|
+
Android: '>=43.1',
|
|
9448
|
+
},
|
|
9449
|
+
changes: {
|
|
9450
|
+
optimize: [
|
|
9451
|
+
{
|
|
9452
|
+
desc: {
|
|
9453
|
+
'zh-CN': '调试 IDE 默认使用浅色主题,暗色模式改为手动开启并记忆选择,避免跟随系统强制进入暗色导致不适应',
|
|
9454
|
+
'en-US': 'The debugging IDE now defaults to the light theme. Dark mode is opt-in and your choice is remembered, so it no longer follows the system and forces an unexpected dark UI.',
|
|
9455
|
+
},
|
|
9456
|
+
module: 'new',
|
|
9457
|
+
},
|
|
9458
|
+
{
|
|
9459
|
+
desc: {
|
|
9460
|
+
'zh-CN': '优化暗色模式下二维码显示,调整背景避免过亮刺眼,同时保证扫码识别率',
|
|
9461
|
+
'en-US': 'Improve the QR code in dark mode by toning down the background so it is no longer glaring while staying easy to scan.',
|
|
9462
|
+
},
|
|
9463
|
+
module: 'scanQrcode',
|
|
9464
|
+
},
|
|
9465
|
+
{
|
|
9466
|
+
desc: {
|
|
9467
|
+
'zh-CN': 'Wasm 分包页面及相关组件补齐多语言,修复页面文案中英文混排的问题',
|
|
9468
|
+
'en-US': 'Complete localization for the Wasm Code Split page and its components, fixing the mixed Chinese/English text.',
|
|
9469
|
+
},
|
|
9470
|
+
module: 'check',
|
|
9471
|
+
},
|
|
9472
|
+
{
|
|
9473
|
+
desc: {
|
|
9474
|
+
'zh-CN': '能力接入助手页面视觉升级:变现模式卡片新增图标与强调色,关键能力标签按「必需 / 可选 / 实验」分级展示,信息层级更清晰',
|
|
9475
|
+
'en-US': 'Refresh the Capability Integration Assistant: monetization-mode cards get icons and accent colors, and capability tags are grouped as Required / Optional / Experimental for a clearer hierarchy.',
|
|
9476
|
+
},
|
|
9477
|
+
module: 'new',
|
|
9478
|
+
},
|
|
9479
|
+
{
|
|
9480
|
+
desc: {
|
|
9481
|
+
'zh-CN': '完成扫码连接后,首页标题与说明文案会切换为「已连接」状态,状态反馈更明确',
|
|
9482
|
+
'en-US': 'After scanning to connect, the home page title and subtitle switch to a "connected" state for clearer feedback.',
|
|
9483
|
+
},
|
|
9484
|
+
module: 'scanQrcode',
|
|
9485
|
+
},
|
|
9486
|
+
{
|
|
9487
|
+
desc: {
|
|
9488
|
+
'zh-CN': '新版本提示由红点改为「有更新」文字标签,含义更直观;上传游戏包按钮配色与产品主题色统一',
|
|
9489
|
+
'en-US': 'Replace the ambiguous red dot for new versions with an explicit "Update available" label, and align the upload button color with the product theme.',
|
|
9490
|
+
},
|
|
9491
|
+
module: 'new',
|
|
9492
|
+
},
|
|
9493
|
+
],
|
|
9494
|
+
bugfix: [
|
|
9495
|
+
{
|
|
9496
|
+
desc: {
|
|
9497
|
+
'zh-CN': '修复多个页面中区块标题与正文内容未对齐的问题,整体排版更整齐',
|
|
9498
|
+
'en-US': 'Fix section titles that were misaligned with their body content across multiple pages for a tidier layout.',
|
|
9499
|
+
},
|
|
9500
|
+
module: 'new',
|
|
9501
|
+
},
|
|
9502
|
+
],
|
|
9503
|
+
},
|
|
9504
|
+
},
|
|
9335
9505
|
{
|
|
9336
9506
|
title: '0.3.8',
|
|
9337
9507
|
target: {
|
|
@@ -9751,7 +9921,7 @@ const gameDirectFeedCardAssetPreviewRoute = {
|
|
|
9751
9921
|
},
|
|
9752
9922
|
};
|
|
9753
9923
|
|
|
9754
|
-
async function resolveAppIdentity$
|
|
9924
|
+
async function resolveAppIdentity$3(body) {
|
|
9755
9925
|
const clientKey = body?.clientKey?.trim() ||
|
|
9756
9926
|
body?.client_key?.trim() ||
|
|
9757
9927
|
getClientKey().clientKey?.trim();
|
|
@@ -9818,7 +9988,7 @@ const gameDirectFeedCardCreateRoute = {
|
|
|
9818
9988
|
method: 'post',
|
|
9819
9989
|
path: '/game/direct-feed-card/create',
|
|
9820
9990
|
handler: async (req, res) => {
|
|
9821
|
-
const identity = await resolveAppIdentity$
|
|
9991
|
+
const identity = await resolveAppIdentity$3(req.body);
|
|
9822
9992
|
if (identity.error) {
|
|
9823
9993
|
res.send({
|
|
9824
9994
|
code: errorCode,
|
|
@@ -9874,7 +10044,7 @@ function isUsableDirectFeedCard(card) {
|
|
|
9874
10044
|
}
|
|
9875
10045
|
return true;
|
|
9876
10046
|
}
|
|
9877
|
-
async function resolveAppIdentity$
|
|
10047
|
+
async function resolveAppIdentity$2(body) {
|
|
9878
10048
|
const clientKey = body?.clientKey?.trim() ||
|
|
9879
10049
|
body?.client_key?.trim() ||
|
|
9880
10050
|
getClientKey().clientKey?.trim();
|
|
@@ -9919,7 +10089,7 @@ const gameDirectFeedCardListRoute = {
|
|
|
9919
10089
|
method: 'post',
|
|
9920
10090
|
path: '/game/direct-feed-card/list',
|
|
9921
10091
|
handler: async (req, res) => {
|
|
9922
|
-
const identity = await resolveAppIdentity$
|
|
10092
|
+
const identity = await resolveAppIdentity$2(req.body);
|
|
9923
10093
|
if (identity.error) {
|
|
9924
10094
|
res.send({
|
|
9925
10095
|
code: errorCode,
|
|
@@ -10024,7 +10194,7 @@ function extractScenarioOptions(data) {
|
|
|
10024
10194
|
}
|
|
10025
10195
|
return [];
|
|
10026
10196
|
}
|
|
10027
|
-
async function resolveAppIdentity(body) {
|
|
10197
|
+
async function resolveAppIdentity$1(body) {
|
|
10028
10198
|
const clientKey = body?.clientKey?.trim() ||
|
|
10029
10199
|
body?.client_key?.trim() ||
|
|
10030
10200
|
getClientKey().clientKey?.trim();
|
|
@@ -10069,7 +10239,7 @@ const gameDirectFeedCardScenariosRoute = {
|
|
|
10069
10239
|
method: 'post',
|
|
10070
10240
|
path: '/game/direct-feed-card/scenarios',
|
|
10071
10241
|
handler: async (req, res) => {
|
|
10072
|
-
const identity = await resolveAppIdentity(req.body);
|
|
10242
|
+
const identity = await resolveAppIdentity$1(req.body);
|
|
10073
10243
|
if (identity.error) {
|
|
10074
10244
|
res.send({
|
|
10075
10245
|
code: errorCode,
|
|
@@ -10109,6 +10279,112 @@ const gameDirectFeedCardScenariosRoute = {
|
|
|
10109
10279
|
},
|
|
10110
10280
|
};
|
|
10111
10281
|
|
|
10282
|
+
async function resolveAppIdentity(body) {
|
|
10283
|
+
const clientKey = body?.clientKey?.trim() ||
|
|
10284
|
+
body?.client_key?.trim() ||
|
|
10285
|
+
getClientKey().clientKey?.trim();
|
|
10286
|
+
let appId = body?.appId?.trim() ||
|
|
10287
|
+
body?.app_id?.trim() ||
|
|
10288
|
+
store.getState().appId?.trim();
|
|
10289
|
+
if (!clientKey) {
|
|
10290
|
+
return {
|
|
10291
|
+
clientKey: '',
|
|
10292
|
+
appId,
|
|
10293
|
+
error: 'Missing client key. Please run `ttmg init` or pass `clientKey` from IDE.',
|
|
10294
|
+
};
|
|
10295
|
+
}
|
|
10296
|
+
if (!appId) {
|
|
10297
|
+
const gameInfoResponse = await fetchGameInfo(clientKey);
|
|
10298
|
+
if (gameInfoResponse.error) {
|
|
10299
|
+
return {
|
|
10300
|
+
clientKey,
|
|
10301
|
+
appId: '',
|
|
10302
|
+
error: gameInfoResponse.error,
|
|
10303
|
+
};
|
|
10304
|
+
}
|
|
10305
|
+
appId = gameInfoResponse.data?.app_id?.trim() || '';
|
|
10306
|
+
if (appId) {
|
|
10307
|
+
store.setState({ appId });
|
|
10308
|
+
}
|
|
10309
|
+
}
|
|
10310
|
+
if (!appId) {
|
|
10311
|
+
return {
|
|
10312
|
+
clientKey,
|
|
10313
|
+
appId: '',
|
|
10314
|
+
error: 'Missing app id. Please open project detail first or pass `appId` from IDE.',
|
|
10315
|
+
};
|
|
10316
|
+
}
|
|
10317
|
+
return {
|
|
10318
|
+
clientKey,
|
|
10319
|
+
appId,
|
|
10320
|
+
error: null,
|
|
10321
|
+
};
|
|
10322
|
+
}
|
|
10323
|
+
function normalizeContentStatus(body) {
|
|
10324
|
+
const source = body?.contentStatus || body?.content_status || {};
|
|
10325
|
+
const contentStatus = Object.entries(source).reduce((result, [contentId, status]) => {
|
|
10326
|
+
const normalizedContentId = String(contentId || '').trim();
|
|
10327
|
+
const normalizedStatus = Number(status);
|
|
10328
|
+
if (normalizedContentId && Number.isFinite(normalizedStatus)) {
|
|
10329
|
+
result[normalizedContentId] = normalizedStatus;
|
|
10330
|
+
}
|
|
10331
|
+
return result;
|
|
10332
|
+
}, {});
|
|
10333
|
+
if (Object.keys(contentStatus).length === 0) {
|
|
10334
|
+
return {
|
|
10335
|
+
contentStatus,
|
|
10336
|
+
error: 'Missing direct-play card status. Please pass a non-empty `contentStatus` from IDE.',
|
|
10337
|
+
};
|
|
10338
|
+
}
|
|
10339
|
+
return {
|
|
10340
|
+
contentStatus,
|
|
10341
|
+
error: null,
|
|
10342
|
+
};
|
|
10343
|
+
}
|
|
10344
|
+
const gameDirectFeedCardStatusRoute = {
|
|
10345
|
+
method: 'post',
|
|
10346
|
+
path: '/game/direct-feed-card/status',
|
|
10347
|
+
handler: async (req, res) => {
|
|
10348
|
+
const body = req.body;
|
|
10349
|
+
const identity = await resolveAppIdentity(body);
|
|
10350
|
+
if (identity.error) {
|
|
10351
|
+
res.send({
|
|
10352
|
+
code: errorCode,
|
|
10353
|
+
error: identity.error,
|
|
10354
|
+
data: null,
|
|
10355
|
+
});
|
|
10356
|
+
return;
|
|
10357
|
+
}
|
|
10358
|
+
const normalized = normalizeContentStatus(body);
|
|
10359
|
+
if (normalized.error) {
|
|
10360
|
+
res.send({
|
|
10361
|
+
code: errorCode,
|
|
10362
|
+
error: normalized.error,
|
|
10363
|
+
data: null,
|
|
10364
|
+
});
|
|
10365
|
+
return;
|
|
10366
|
+
}
|
|
10367
|
+
const response = await changeDirectFeedCardStatus({
|
|
10368
|
+
appId: identity.appId,
|
|
10369
|
+
clientKey: identity.clientKey,
|
|
10370
|
+
contentStatus: normalized.contentStatus,
|
|
10371
|
+
});
|
|
10372
|
+
if (response.error) {
|
|
10373
|
+
res.send({
|
|
10374
|
+
code: errorCode,
|
|
10375
|
+
error: response.error,
|
|
10376
|
+
ctx: response.ctx,
|
|
10377
|
+
});
|
|
10378
|
+
return;
|
|
10379
|
+
}
|
|
10380
|
+
res.send({
|
|
10381
|
+
code: successCode,
|
|
10382
|
+
data: response.data,
|
|
10383
|
+
ctx: response.ctx,
|
|
10384
|
+
});
|
|
10385
|
+
},
|
|
10386
|
+
};
|
|
10387
|
+
|
|
10112
10388
|
const gameDetailRoute = {
|
|
10113
10389
|
method: 'get',
|
|
10114
10390
|
path: '/game/detail',
|
|
@@ -10405,42 +10681,83 @@ function restoreSplitConfigFromCache(entryDir = process.cwd()) {
|
|
|
10405
10681
|
* to change.
|
|
10406
10682
|
*/
|
|
10407
10683
|
function restoreFromCache(entryDir = process.cwd()) {
|
|
10684
|
+
// Rollback rewrites a burst of project files (restored wasm, game.json,
|
|
10685
|
+
// split config, removed split-output dirs). Mute the dev-server watcher for
|
|
10686
|
+
// the duration so it doesn't fire a compile + device reload per file, then
|
|
10687
|
+
// emit a single update once the project is back to its restored state.
|
|
10688
|
+
suspendWatch();
|
|
10689
|
+
try {
|
|
10690
|
+
restoreFromCacheInner(entryDir);
|
|
10691
|
+
}
|
|
10692
|
+
finally {
|
|
10693
|
+
resumeWatch({ emit: true });
|
|
10694
|
+
}
|
|
10695
|
+
}
|
|
10696
|
+
function restoreFromCacheInner(entryDir) {
|
|
10408
10697
|
const cacheDir = path.join(entryDir, WASM_SPLIT_CACHE_DIR);
|
|
10409
|
-
// 1) Wipe stale split residue inside wasmcode/ first, THEN restore the
|
|
10410
|
-
// original. Order matters: if we restore first then wipe, we'd delete
|
|
10411
|
-
// the very file we just brought back.
|
|
10412
10698
|
const originDir = path.join(entryDir, WASM_SPLIT_SUBPACKAGE_CONFIG.origin.root);
|
|
10413
|
-
|
|
10414
|
-
|
|
10415
|
-
|
|
10416
|
-
|
|
10417
|
-
|
|
10418
|
-
|
|
10419
|
-
|
|
10420
|
-
|
|
10699
|
+
// Resolve the cached original wasm BEFORE touching anything on disk. The
|
|
10700
|
+
// previous order (wipe `wasmcode/*.br` first, then look for a cached
|
|
10701
|
+
// original to restore) was destructive when the cache had no original to
|
|
10702
|
+
// give back — e.g. the project was duplicated without `__TTMG_TEMP__`, the
|
|
10703
|
+
// temp dir was cleared, or split completed in a session whose cache is gone.
|
|
10704
|
+
// In that case it deleted the project's only wasm and restored nothing,
|
|
10705
|
+
// leaving `wasmcode/` empty while `game.json` still pointed at the now-missing
|
|
10706
|
+
// `<md5>.wasm.br`. The next prepare then crashed the dev server on an ENOENT
|
|
10707
|
+
// decompress. We only clean + restore when we have a confirmed replacement.
|
|
10708
|
+
const cachedWasmBr = fs.existsSync(cacheDir)
|
|
10709
|
+
? fs.readdirSync(cacheDir).find(item => item.endsWith('.br'))
|
|
10710
|
+
: undefined;
|
|
10711
|
+
if (cachedWasmBr) {
|
|
10712
|
+
// Safe to wipe split residue first: we have a confirmed original to put
|
|
10713
|
+
// back. Order matters — restoring first then wiping would delete the file
|
|
10714
|
+
// we just brought back.
|
|
10715
|
+
if (fs.existsSync(originDir)) {
|
|
10716
|
+
for (const entry of fs.readdirSync(originDir)) {
|
|
10717
|
+
// Only clean files split is known to write — `.br` (main wasm) and
|
|
10718
|
+
// the empty `game.js` placeholder. Touching anything else risks
|
|
10719
|
+
// nuking developer-authored content that happens to live in
|
|
10720
|
+
// wasmcode/ for unrelated reasons.
|
|
10721
|
+
if (entry.endsWith('.br') || entry === 'game.js') {
|
|
10722
|
+
fs.rmSync(path.join(originDir, entry), { force: true });
|
|
10723
|
+
}
|
|
10421
10724
|
}
|
|
10422
10725
|
}
|
|
10726
|
+
const destWasmBrPath = path.join(originDir, path.basename(cachedWasmBr));
|
|
10727
|
+
ensureDirSync(path.dirname(destWasmBrPath));
|
|
10728
|
+
fs.copyFileSync(path.join(cacheDir, cachedWasmBr), destWasmBrPath);
|
|
10423
10729
|
}
|
|
10424
|
-
|
|
10425
|
-
|
|
10426
|
-
|
|
10427
|
-
|
|
10428
|
-
|
|
10429
|
-
|
|
10430
|
-
|
|
10431
|
-
|
|
10432
|
-
|
|
10433
|
-
|
|
10434
|
-
|
|
10435
|
-
|
|
10436
|
-
|
|
10437
|
-
|
|
10438
|
-
|
|
10439
|
-
|
|
10440
|
-
|
|
10441
|
-
|
|
10442
|
-
|
|
10443
|
-
|
|
10730
|
+
else {
|
|
10731
|
+
// No cached original. Do NOT delete whatever wasm is currently on disk —
|
|
10732
|
+
// it is all the project has left. Preserve it so the project can still
|
|
10733
|
+
// boot and a fresh prepare can re-seed the cache, rather than wiping it and
|
|
10734
|
+
// crashing the next prepare. The split-output dirs below are still cleaned.
|
|
10735
|
+
console.warn('[wasmtool] restoreFromCache: no cached original wasm found in ' +
|
|
10736
|
+
`${WASM_SPLIT_CACHE_DIR}; skipping wasmcode/*.br cleanup to avoid ` +
|
|
10737
|
+
'leaving the project without a wasm binary.');
|
|
10738
|
+
}
|
|
10739
|
+
// Only roll back the split config + game.json when we actually restored the
|
|
10740
|
+
// original wasm above. Restoring game.json (which points at the original
|
|
10741
|
+
// `<md5>.wasm.br`) while the matching wasm is NOT on disk recreates exactly
|
|
10742
|
+
// the broken state this guard exists to prevent — a game.json referencing a
|
|
10743
|
+
// file that isn't there. keepCacheSync writes the wasm, config and game.json
|
|
10744
|
+
// together, so a cache that has game.json but no `.br` is partial/corrupt and
|
|
10745
|
+
// must not be applied.
|
|
10746
|
+
if (cachedWasmBr) {
|
|
10747
|
+
const splitConfigCachePath = path.join(cacheDir, WASM_SPLIT_CONFIG_FILE_NAME);
|
|
10748
|
+
if (fs.existsSync(splitConfigCachePath)) {
|
|
10749
|
+
fs.copyFileSync(splitConfigCachePath, path.join(entryDir, WASM_SPLIT_CONFIG_FILE_NAME));
|
|
10750
|
+
}
|
|
10751
|
+
const gameJsonCachePath = path.join(cacheDir, 'game.json');
|
|
10752
|
+
if (fs.existsSync(gameJsonCachePath)) {
|
|
10753
|
+
fs.copyFileSync(gameJsonCachePath, path.join(entryDir, 'game.json'));
|
|
10754
|
+
}
|
|
10755
|
+
}
|
|
10756
|
+
// Restore wasmcode/game.js. We just deleted whatever was there in the
|
|
10757
|
+
// cleanup loop above, so we always need to put something back when wasmcode
|
|
10758
|
+
// is a subpackage. Scoped to the same `cachedWasmBr` guard: in the degraded
|
|
10759
|
+
// (lost-cache) path we left the existing wasmcode/ untouched, so we must not
|
|
10760
|
+
// rewrite its game.js either.
|
|
10444
10761
|
// Strategy:
|
|
10445
10762
|
// - Prefer the cache (keepCacheSync stashes pre-split contents to
|
|
10446
10763
|
// `__unity_cache__/wasmcode-game.js` when the original existed)
|
|
@@ -10451,14 +10768,16 @@ function restoreFromCache(entryDir = process.cwd()) {
|
|
|
10451
10768
|
// downloadSplited.ts also writes; it satisfies the platform requirement
|
|
10452
10769
|
// without changing semantics for projects that don't use wasmcode as
|
|
10453
10770
|
// a subpackage (the file is harmless empty).
|
|
10454
|
-
|
|
10455
|
-
|
|
10456
|
-
|
|
10457
|
-
if (fs.existsSync(
|
|
10458
|
-
fs.
|
|
10459
|
-
|
|
10460
|
-
|
|
10461
|
-
|
|
10771
|
+
if (cachedWasmBr) {
|
|
10772
|
+
const originGameJsCachePath = path.join(cacheDir, 'wasmcode-game.js');
|
|
10773
|
+
const originGameJsDestPath = path.join(originDir, 'game.js');
|
|
10774
|
+
if (fs.existsSync(originDir)) {
|
|
10775
|
+
if (fs.existsSync(originGameJsCachePath)) {
|
|
10776
|
+
fs.copyFileSync(originGameJsCachePath, originGameJsDestPath);
|
|
10777
|
+
}
|
|
10778
|
+
else {
|
|
10779
|
+
fs.writeFileSync(originGameJsDestPath, '', 'utf-8');
|
|
10780
|
+
}
|
|
10462
10781
|
}
|
|
10463
10782
|
}
|
|
10464
10783
|
for (const subDir of SPLIT_OUTPUT_DIRS) {
|
|
@@ -10576,21 +10895,53 @@ function setLocalState(partial) {
|
|
|
10576
10895
|
Object.assign(state, partial);
|
|
10577
10896
|
}
|
|
10578
10897
|
|
|
10898
|
+
/**
|
|
10899
|
+
* Prepare overwrites the project wasm in place with the instrumented build.
|
|
10900
|
+
* Mute the dev-server file watcher for the duration so the in-place rewrite
|
|
10901
|
+
* doesn't fire a compile + device reload while the file is still being
|
|
10902
|
+
* written, then emit a single update once the instrumented wasm is on disk.
|
|
10903
|
+
*/
|
|
10579
10904
|
async function startPrepare$1(params) {
|
|
10905
|
+
return withWatchSuspended(() => startPrepareImpl(params), {
|
|
10906
|
+
emitOnFinish: true,
|
|
10907
|
+
});
|
|
10908
|
+
}
|
|
10909
|
+
async function startPrepareImpl(params) {
|
|
10580
10910
|
const tempDir = path$1.join(process.cwd(), TTMG_TEMP_DIR);
|
|
10581
10911
|
ensureDirSync(tempDir);
|
|
10582
10912
|
const inputPath = path$1.join(process.cwd(), params.wasm_file_path);
|
|
10583
|
-
|
|
10584
|
-
if (inputPath.endsWith('.br')) {
|
|
10585
|
-
await decompressWasmFile(inputPath, rawWasmPath);
|
|
10586
|
-
}
|
|
10587
|
-
else {
|
|
10588
|
-
fs$1.copyFileSync(inputPath, rawWasmPath);
|
|
10589
|
-
}
|
|
10913
|
+
const rawWasmPath = path$1.join(tempDir, 'original.wasm');
|
|
10590
10914
|
const preparedWasmPath = path$1.join(tempDir, 'prepared.wasm');
|
|
10591
10915
|
try {
|
|
10916
|
+
// The project wasm referenced by game.json can legitimately be missing on
|
|
10917
|
+
// disk: a previous rollback may have wiped `wasmcode/*.br` without a cached
|
|
10918
|
+
// original to restore, the project may have been duplicated without
|
|
10919
|
+
// `__TTMG_TEMP__`, or Unity was re-exported. Detect it up front and return
|
|
10920
|
+
// a structured error. Previously the `decompressWasmFile` / `copyFileSync`
|
|
10921
|
+
// below sat OUTSIDE this try block, so a missing input threw ENOENT out of
|
|
10922
|
+
// the route handler as an unhandled rejection and crashed the whole
|
|
10923
|
+
// dev-server process instead of surfacing a recoverable error to the IDE.
|
|
10924
|
+
if (!fs$1.existsSync(inputPath)) {
|
|
10925
|
+
return {
|
|
10926
|
+
data: null,
|
|
10927
|
+
error: {
|
|
10928
|
+
code: 404,
|
|
10929
|
+
message: `WASM file not found: ${params.wasm_file_path}. ` +
|
|
10930
|
+
'The original wasm is missing — a previous "放弃分包" may have removed it ' +
|
|
10931
|
+
'without a cached original to restore. Re-export the Unity build (or restore ' +
|
|
10932
|
+
'the original wasm) and try preparing again.',
|
|
10933
|
+
},
|
|
10934
|
+
ctx: { logid: 'local', httpStatusCode: 404 },
|
|
10935
|
+
};
|
|
10936
|
+
}
|
|
10937
|
+
if (inputPath.endsWith('.br')) {
|
|
10938
|
+
await decompressWasmFile(inputPath, rawWasmPath);
|
|
10939
|
+
}
|
|
10940
|
+
else {
|
|
10941
|
+
fs$1.copyFileSync(inputPath, rawWasmPath);
|
|
10942
|
+
}
|
|
10592
10943
|
const result = ttmgWasmtool.prepare(rawWasmPath, preparedWasmPath);
|
|
10593
|
-
|
|
10944
|
+
verboseLog(`[wasmtool] prepare done: ${result.outputSize} bytes, ${result.timeCost}s`);
|
|
10594
10945
|
const gameJson = getGameJson();
|
|
10595
10946
|
const totalWasmFuncCount = gameJson.wasmFuncCount ?? 0;
|
|
10596
10947
|
const wasmSize = fs$1.existsSync(inputPath)
|
|
@@ -10624,13 +10975,13 @@ async function startPrepare$1(params) {
|
|
|
10624
10975
|
const preparedMd5 = fs$1.existsSync(preparedWasmPath)
|
|
10625
10976
|
? crypto$1.createHash('md5').update(fs$1.readFileSync(preparedWasmPath)).digest('hex')
|
|
10626
10977
|
: '<missing>';
|
|
10627
|
-
|
|
10978
|
+
verboseLog(`[wasmtool] prepare sanity: raw(size=${rawSize} md5=${rawMd5}) -> prepared(size=${preparedSize} md5=${preparedMd5}) delta=${preparedSize - rawSize}`);
|
|
10628
10979
|
if (preparedSize <= rawSize || preparedMd5 === rawMd5) {
|
|
10629
|
-
|
|
10980
|
+
verboseWarn('[wasmtool] WARNING: prepared wasm is not larger / md5 is unchanged vs raw wasm. Instrumentation likely did not happen.');
|
|
10630
10981
|
}
|
|
10631
|
-
|
|
10982
|
+
verboseLog('[wasmtool] compressing prepared wasm (quality=9)...');
|
|
10632
10983
|
await compressWasmFile(preparedWasmPath, willReplaceWasmPath);
|
|
10633
|
-
|
|
10984
|
+
verboseLog('[wasmtool] compressed and written to project');
|
|
10634
10985
|
// Diagnostic: confirm the file the client actually fetches was overwritten,
|
|
10635
10986
|
// and compare to the cached original brotli so we can prove on-disk replacement.
|
|
10636
10987
|
const replacedSize = fs$1.existsSync(willReplaceWasmPath)
|
|
@@ -10649,12 +11000,12 @@ async function startPrepare$1(params) {
|
|
|
10649
11000
|
.update(fs$1.readFileSync(cachedOriginalBr))
|
|
10650
11001
|
.digest('hex')
|
|
10651
11002
|
: '<missing>';
|
|
10652
|
-
|
|
11003
|
+
verboseLog(`[wasmtool] on-disk replace check: project=${params.wasm_file_path} size=${replacedSize} md5=${replacedMd5} | cached-original size=${cachedOriginalSize} md5=${cachedOriginalMd5}`);
|
|
10653
11004
|
if (replacedMd5 === cachedOriginalMd5) {
|
|
10654
|
-
|
|
11005
|
+
verboseWarn('[wasmtool] WARNING: project wasm md5 matches cached-original md5. The file was not actually replaced with the instrumented build.');
|
|
10655
11006
|
}
|
|
10656
11007
|
else {
|
|
10657
|
-
|
|
11008
|
+
verboseLog('[wasmtool] OK: project wasm differs from cached-original — instrumented wasm is on disk.');
|
|
10658
11009
|
}
|
|
10659
11010
|
// Local pipeline uses the new wasm-collect/v1/report API + archive sub-wasm.
|
|
10660
11011
|
// ORIGINALWASMMD5 must be set now (not only at split time) so the plugin
|
|
@@ -10664,7 +11015,7 @@ async function startPrepare$1(params) {
|
|
|
10664
11015
|
ENABLEARCHIVEMODE: true,
|
|
10665
11016
|
ORIGINALWASMMD5: params.wasm_md5,
|
|
10666
11017
|
});
|
|
10667
|
-
|
|
11018
|
+
verboseLog('[wasmtool] wasm split config updated (local pipeline: archive=true)');
|
|
10668
11019
|
// Disk-persisted anchor for "wasm drift" detection in
|
|
10669
11020
|
// `game-wasm-split-config` route. Stores the md5 that prepare just
|
|
10670
11021
|
// wrote into the project alongside the project-relative path. The
|
|
@@ -10678,7 +11029,7 @@ async function startPrepare$1(params) {
|
|
|
10678
11029
|
preparedWasmMd5: replacedMd5,
|
|
10679
11030
|
codePath: params.wasm_file_path,
|
|
10680
11031
|
});
|
|
10681
|
-
|
|
11032
|
+
verboseLog(`[wasmtool] prepared-meta written: md5=${replacedMd5} codePath=${params.wasm_file_path}`);
|
|
10682
11033
|
return {
|
|
10683
11034
|
data: {
|
|
10684
11035
|
code: 0,
|
|
@@ -10787,42 +11138,30 @@ async function startWasmSession({ client_key, wasm_md5, reset, }) {
|
|
|
10787
11138
|
}
|
|
10788
11139
|
|
|
10789
11140
|
/**
|
|
10790
|
-
*
|
|
10791
|
-
* `stark_wasm/v4/post/set_collecting`:**打开 server 端的 collect 窗口**,
|
|
10792
|
-
* 让 plugin 之后的 `/report` 请求能落库。
|
|
11141
|
+
* 打开 server 端 collect session(本地 pipeline 的 `/start`)。
|
|
10793
11142
|
*
|
|
10794
|
-
*
|
|
10795
|
-
*
|
|
10796
|
-
*
|
|
10797
|
-
*
|
|
10798
|
-
* 2. 成功后上传符号表(`/symbols`)。这一步故意不 await、错误仅 warn —
|
|
10799
|
-
* 符号表只是给 server 端后续调试用的 debug 信息,丢了也不影响分包主链路。
|
|
10800
|
-
* 3. 返回 `{code: 0}`。
|
|
11143
|
+
* 这是**鉴权门**:`/start` 走 Portal 鉴权中间件,登录态失效会返回 `-401`
|
|
11144
|
+
* (或带登录关键字的 `-1`)。鉴权失败必须立即把错误回给 IDE 并中止——
|
|
11145
|
+
* 否则用户进了"正在收集",但 plugin 之后所有 `/report` 都会被 fail-close
|
|
11146
|
+
* 丢弃,函数数量永远 0,且很难归因。
|
|
10801
11147
|
*
|
|
10802
|
-
*
|
|
10803
|
-
*
|
|
10804
|
-
*
|
|
10805
|
-
*
|
|
10806
|
-
* - `resume: true` —— 页面刷新 / 恢复继续,发 `reset: false`,幂等
|
|
10807
|
-
* 打开 session、保留已有 func_ids。需要这条路径的 caller(如 IDE
|
|
10808
|
-
* 重新挂载组件检测到 server `collect_state: "open"` 想接续)必须
|
|
10809
|
-
* 显式传,避免误清。
|
|
11148
|
+
* IDE 通过独立路由 `/game/wasm-collect-start` 调用本函数,所以它在浏览器
|
|
11149
|
+
* Network 里是一条可见且**明确叫 start** 的请求——出鉴权问题时一眼能定位到
|
|
11150
|
+
* 是开 session 这步失败,而不是被误认为 collect 轮询接口的问题。symbols
|
|
11151
|
+
* 上传是另一步(`uploadCollectSymbols`),与开 session 解耦。
|
|
10810
11152
|
*
|
|
10811
|
-
*
|
|
10812
|
-
*
|
|
10813
|
-
*
|
|
11153
|
+
* 两种语义(与 `wasm_api.md` §5.1 对齐):
|
|
11154
|
+
* - 默认(`resume` 缺省 / false)→ `reset: true`,服务端清空历史。
|
|
11155
|
+
* - `resume: true` → `reset: false`,幂等打开、保留已有 func_ids。
|
|
10814
11156
|
*/
|
|
10815
|
-
async function
|
|
11157
|
+
async function openCollectSession$1({ client_key, wasm_md5, resume, }) {
|
|
10816
11158
|
const startRes = await startWasmSession({
|
|
10817
11159
|
client_key,
|
|
10818
11160
|
wasm_md5,
|
|
10819
11161
|
reset: !resume,
|
|
10820
11162
|
});
|
|
10821
11163
|
if (startRes.error || !startRes.data || startRes.data.code !== 0) {
|
|
10822
|
-
//
|
|
10823
|
-
// bubbled up as a generic "开始收集失败" toast, so dump a structured
|
|
10824
|
-
// one-liner here with logid — the single most useful field when
|
|
10825
|
-
// asking backend to look up what happened on their side.
|
|
11164
|
+
// 结构化日志带 logid——找后端排查鉴权/会话问题时最有用的字段。
|
|
10826
11165
|
const code = startRes.error?.code ?? startRes.data?.code ?? -1;
|
|
10827
11166
|
const message = startRes.error?.message ||
|
|
10828
11167
|
startRes.data?.message ||
|
|
@@ -10835,6 +11174,20 @@ async function setCollect$1({ client_key, wasm_md5, resume, }) {
|
|
|
10835
11174
|
ctx: startRes.ctx,
|
|
10836
11175
|
};
|
|
10837
11176
|
}
|
|
11177
|
+
return {
|
|
11178
|
+
data: { code: 0, message: 'success', result: startRes.data.result },
|
|
11179
|
+
error: null,
|
|
11180
|
+
ctx: startRes.ctx,
|
|
11181
|
+
};
|
|
11182
|
+
}
|
|
11183
|
+
/**
|
|
11184
|
+
* 上传符号表(`/symbols`)。只在 session 已打开后调用。
|
|
11185
|
+
*
|
|
11186
|
+
* 符号表只是给 server 端后续调试用的 debug 信息,丢了不影响分包主链路,
|
|
11187
|
+
* 所以这里故意不 await、失败仅 warn,且总是返回 `{code: 0}`——它不应该
|
|
11188
|
+
* 阻塞或失败掉"开始收集"流程。
|
|
11189
|
+
*/
|
|
11190
|
+
async function uploadCollectSymbols$1({ client_key, wasm_md5, }) {
|
|
10838
11191
|
let symbolPath = path$1.join(process.cwd(), WASM_SYMBOL_FILE_NAME);
|
|
10839
11192
|
if (!fs$1.existsSync(symbolPath)) {
|
|
10840
11193
|
symbolPath = path$1.join(process.cwd(), TTMG_TEMP_DIR, WASM_SYMBOL_FILE_NAME);
|
|
@@ -10844,13 +11197,15 @@ async function setCollect$1({ client_key, wasm_md5, resume, }) {
|
|
|
10844
11197
|
request({
|
|
10845
11198
|
url: `${WASM_COLLECT_BASE_URL}/symbols`,
|
|
10846
11199
|
method: 'POST',
|
|
11200
|
+
// symbols 是整张符号表,verbose 下打出来会刷屏,关掉它的入参日志。
|
|
11201
|
+
logRequestBody: false,
|
|
10847
11202
|
data: {
|
|
10848
11203
|
app_id: client_key,
|
|
10849
11204
|
wasm_md5,
|
|
10850
11205
|
symbols,
|
|
10851
11206
|
},
|
|
10852
11207
|
}).catch(err => {
|
|
10853
|
-
|
|
11208
|
+
verboseWarn('[wasmtool] Failed to upload symbols:', err);
|
|
10854
11209
|
});
|
|
10855
11210
|
}
|
|
10856
11211
|
return {
|
|
@@ -10963,7 +11318,7 @@ async function startSplit$1({ client_key, wasm_md5, }) {
|
|
|
10963
11318
|
ctx: { logid: 'local', httpStatusCode: 400 },
|
|
10964
11319
|
};
|
|
10965
11320
|
}
|
|
10966
|
-
|
|
11321
|
+
verboseLog(`[wasmtool] splitting with ${funcIds.length} func IDs` +
|
|
10967
11322
|
(bootFuncIds.length > 0
|
|
10968
11323
|
? `, ${bootFuncIds.length} boot-phase func IDs (→ alwaysInclude)`
|
|
10969
11324
|
: ', no boot-phase info (legacy server, falling back to callClosure only)') +
|
|
@@ -11023,14 +11378,14 @@ async function startSplit$1({ client_key, wasm_md5, }) {
|
|
|
11023
11378
|
const actualArchivePath = fs$1.existsSync(archiveBrPath)
|
|
11024
11379
|
? archiveBrPath
|
|
11025
11380
|
: result.archivePath;
|
|
11026
|
-
|
|
11381
|
+
verboseLog(`[wasmtool] archivePath=${result.archivePath}, brExists=${fs$1.existsSync(archiveBrPath)}, actualExists=${fs$1.existsSync(actualArchivePath)}`);
|
|
11027
11382
|
if (fs$1.existsSync(actualArchivePath)) {
|
|
11028
11383
|
archiveMd5 = computeFileMd5Sync(actualArchivePath);
|
|
11029
|
-
|
|
11384
|
+
verboseLog(`[wasmtool] archive_md5=${archiveMd5}`);
|
|
11030
11385
|
}
|
|
11031
11386
|
}
|
|
11032
11387
|
else {
|
|
11033
|
-
|
|
11388
|
+
verboseLog(`[wasmtool] skip archive md5: archive=${archive}, archivePath=${result.archivePath}`);
|
|
11034
11389
|
}
|
|
11035
11390
|
const globalVarList = result.globalVarList
|
|
11036
11391
|
.split(';')
|
|
@@ -11072,7 +11427,7 @@ async function startSplit$1({ client_key, wasm_md5, }) {
|
|
|
11072
11427
|
export_added: result.exportAdded,
|
|
11073
11428
|
};
|
|
11074
11429
|
setLocalState({ splitOutputDir, splitMeta });
|
|
11075
|
-
|
|
11430
|
+
verboseLog(`[wasmtool] split done: total=${result.totalWasmCount}, main=${result.mainWasmCount} ` +
|
|
11076
11431
|
`(collect=${result.collectFuncCount}, +alwaysInclude=${result.alwaysIncludeAdded}, ` +
|
|
11077
11432
|
`+closure=${result.closureAdded}, +indirectClosure=${result.indirectClosureAdded}` +
|
|
11078
11433
|
`[types=${result.indirectClosureTypes}], +exports=${result.exportAdded}), ` +
|
|
@@ -11155,7 +11510,18 @@ function updateSubpackageConfigSync(archive = false) {
|
|
|
11155
11510
|
fs__namespace.writeFileSync(gameJsonPath, JSON.stringify(gameJson, null, JSON_INDENT) + JSON_EOL);
|
|
11156
11511
|
}
|
|
11157
11512
|
|
|
11158
|
-
|
|
11513
|
+
/**
|
|
11514
|
+
* Lays down the split outputs (`wasmcode*` dirs, game.json, webgl-wasm-split.js)
|
|
11515
|
+
* into the project root — a burst of file writes. Mute the watcher across the
|
|
11516
|
+
* whole operation so it doesn't reload the device per file mid-write, then fire
|
|
11517
|
+
* one update once the split layout is fully on disk.
|
|
11518
|
+
*/
|
|
11519
|
+
async function downloadSplited$1(context) {
|
|
11520
|
+
return withWatchSuspended(() => downloadSplitedImpl(context), {
|
|
11521
|
+
emitOnFinish: true,
|
|
11522
|
+
});
|
|
11523
|
+
}
|
|
11524
|
+
async function downloadSplitedImpl(_context) {
|
|
11159
11525
|
const cwd = process.cwd();
|
|
11160
11526
|
const { splitMeta } = getLocalState();
|
|
11161
11527
|
if (!splitMeta) {
|
|
@@ -11177,7 +11543,7 @@ async function downloadSplited$1(_context) {
|
|
|
11177
11543
|
const dirs = [...new Set([mainAndroidDir, subAndroidDir, mainIosDir, subIosDir])];
|
|
11178
11544
|
dirs.forEach(ensureDirSync);
|
|
11179
11545
|
try {
|
|
11180
|
-
|
|
11546
|
+
verboseLog('[wasmtool] organizing split output...');
|
|
11181
11547
|
const mainWasmMd5 = splitMeta.main_wasm_md5;
|
|
11182
11548
|
const subWasmMd5 = splitMeta.sub_wasm_md5;
|
|
11183
11549
|
const mainWasmH5Md5 = splitMeta.main_wasm_h5_md5;
|
|
@@ -11220,20 +11586,20 @@ async function downloadSplited$1(_context) {
|
|
|
11220
11586
|
if (isArchive && localArchivePath) {
|
|
11221
11587
|
const archiveBrPath = localArchivePath + BR_SUFFIX;
|
|
11222
11588
|
const actualArchivePath = fs.existsSync(archiveBrPath) ? archiveBrPath : localArchivePath;
|
|
11223
|
-
|
|
11589
|
+
verboseLog(`[wasmtool] archive copy: archive_md5=${splitMeta.archive_md5}, localPath=${localArchivePath}, brExists=${fs.existsSync(archiveBrPath)}, actual=${actualArchivePath}`);
|
|
11224
11590
|
if (fs.existsSync(actualArchivePath)) {
|
|
11225
11591
|
const archiveMd5 = splitMeta.archive_md5 || '';
|
|
11226
11592
|
const archiveBaseName = path.basename(actualArchivePath);
|
|
11227
11593
|
const destName = archiveMd5 ? `${archiveMd5}.${archiveBaseName}` : archiveBaseName;
|
|
11228
11594
|
const archiveDest = path.join(subIosDir, destName);
|
|
11229
|
-
|
|
11595
|
+
verboseLog(`[wasmtool] archive dest: ${archiveDest}`);
|
|
11230
11596
|
fs.copyFileSync(actualArchivePath, archiveDest);
|
|
11231
11597
|
}
|
|
11232
11598
|
}
|
|
11233
11599
|
dirs.forEach((dir) => {
|
|
11234
11600
|
fs.writeFileSync(path.join(dir, 'game.js'), '', { encoding: 'utf-8' });
|
|
11235
11601
|
});
|
|
11236
|
-
|
|
11602
|
+
verboseLog('[wasmtool] copy split output to root...');
|
|
11237
11603
|
wsServer.sendUnitySplitStatus({ status: 'start_write_splited_wasm_br' });
|
|
11238
11604
|
for (const file of fs.readdirSync(splitTempDir)) {
|
|
11239
11605
|
const srcPath = path.join(splitTempDir, file);
|
|
@@ -11244,9 +11610,9 @@ async function downloadSplited$1(_context) {
|
|
|
11244
11610
|
await promises.cp(srcPath, destPath, { recursive: true, force: true });
|
|
11245
11611
|
}
|
|
11246
11612
|
wsServer.sendUnitySplitStatus({ status: 'write_splited_wasm_done' });
|
|
11247
|
-
|
|
11613
|
+
verboseLog('[wasmtool] updating subpackage config...');
|
|
11248
11614
|
updateSubpackageConfigSync(isArchive);
|
|
11249
|
-
|
|
11615
|
+
verboseLog('[wasmtool] updating wasm split config...');
|
|
11250
11616
|
wsServer.sendUnitySplitStatus({ status: 'start_update_wasm_split_config' });
|
|
11251
11617
|
updateWasmSplitConfig({
|
|
11252
11618
|
ENABLEWASMCOLLECT: true,
|
|
@@ -11464,7 +11830,7 @@ async function downloadOne(opts) {
|
|
|
11464
11830
|
const willDownloadedFileIsBr = url.includes(BR_SUFFIX);
|
|
11465
11831
|
const finalOut = willDownloadedFileIsBr && !out.endsWith(BR_SUFFIX) ? out + BR_SUFFIX : out;
|
|
11466
11832
|
wsServer.sendUnitySplitStatus({ status: startStatus });
|
|
11467
|
-
|
|
11833
|
+
verboseLog(`[remote-split-download] fetching -> ${finalOut}`);
|
|
11468
11834
|
const t0 = Date.now();
|
|
11469
11835
|
await withRetry(() => download(url, finalOut), DOWNLOAD_RETRY);
|
|
11470
11836
|
const st = await promises.stat(finalOut);
|
|
@@ -11472,13 +11838,23 @@ async function downloadOne(opts) {
|
|
|
11472
11838
|
await promises.rm(finalOut, { force: true });
|
|
11473
11839
|
throw new Error(`Empty download: ${finalOut}`);
|
|
11474
11840
|
}
|
|
11475
|
-
|
|
11841
|
+
verboseLog(`[remote-split-download] done: ${path.basename(finalOut)} size=${st.size}B time=${Date.now() - t0}ms`);
|
|
11476
11842
|
wsServer.sendUnitySplitStatus({ status: doneStatus, url });
|
|
11477
11843
|
// Legacy behaviour: write an empty game.js next to each downloaded artifact
|
|
11478
11844
|
// so the subpackage loader doesn't complain about missing js entries.
|
|
11479
11845
|
fs.writeFileSync(path.join(path.dirname(out), 'game.js'), '', 'utf-8');
|
|
11480
11846
|
}
|
|
11847
|
+
/**
|
|
11848
|
+
* Remote-mode counterpart of `downloadSplited`: downloads the server-built
|
|
11849
|
+
* split artifacts and copies them into the project root. Same watcher gating
|
|
11850
|
+
* rationale — mute across the burst of writes, emit one update at the end.
|
|
11851
|
+
*/
|
|
11481
11852
|
async function downloadSplitedRemote(context) {
|
|
11853
|
+
return withWatchSuspended(() => downloadSplitedRemoteImpl(context), {
|
|
11854
|
+
emitOnFinish: true,
|
|
11855
|
+
});
|
|
11856
|
+
}
|
|
11857
|
+
async function downloadSplitedRemoteImpl(context) {
|
|
11482
11858
|
const cwd = process.cwd();
|
|
11483
11859
|
const splitTempDir = path.join(cwd, WASM_SPLIT_CACHE_DIR, DIR_SPLIT);
|
|
11484
11860
|
ensureDirSync(splitTempDir);
|
|
@@ -11492,7 +11868,7 @@ async function downloadSplitedRemote(context) {
|
|
|
11492
11868
|
const mainIosOut = path.join(mainIosDir, `${context.main_wasm_h5_md5}${WASM_FILENAME_SUFFIX}`);
|
|
11493
11869
|
const limit = pLimit(CONCURRENCY_LIMIT);
|
|
11494
11870
|
try {
|
|
11495
|
-
|
|
11871
|
+
verboseLog('[remote-split-download] start', {
|
|
11496
11872
|
original_wasm_md5: context.original_wasm_md5,
|
|
11497
11873
|
main_wasm_md5: context.main_wasm_md5,
|
|
11498
11874
|
sub_wasm_md5: context.sub_wasm_md5,
|
|
@@ -11530,7 +11906,7 @@ async function downloadSplitedRemote(context) {
|
|
|
11530
11906
|
out: path.join(subIosDir, 'subjs.data'),
|
|
11531
11907
|
})),
|
|
11532
11908
|
]);
|
|
11533
|
-
|
|
11909
|
+
verboseLog('[remote-split-download] copying split output to project root...');
|
|
11534
11910
|
wsServer.sendUnitySplitStatus({ status: 'start_write_splited_wasm_br' });
|
|
11535
11911
|
for (const file of fs.readdirSync(splitTempDir)) {
|
|
11536
11912
|
const srcPath = path.join(splitTempDir, file);
|
|
@@ -11541,9 +11917,9 @@ async function downloadSplitedRemote(context) {
|
|
|
11541
11917
|
await promises.cp(srcPath, destPath, { recursive: true, force: true });
|
|
11542
11918
|
}
|
|
11543
11919
|
wsServer.sendUnitySplitStatus({ status: 'write_splited_wasm_done' });
|
|
11544
|
-
|
|
11920
|
+
verboseLog('[remote-split-download] updating subpackage config...');
|
|
11545
11921
|
updateSubpackageConfigSync(false);
|
|
11546
|
-
|
|
11922
|
+
verboseLog('[remote-split-download] updating webgl-wasm-split.js...');
|
|
11547
11923
|
wsServer.sendUnitySplitStatus({ status: 'start_update_wasm_split_config' });
|
|
11548
11924
|
updateWasmSplitConfig({
|
|
11549
11925
|
ENABLEWASMCOLLECT: true,
|
|
@@ -11560,12 +11936,12 @@ async function downloadSplitedRemote(context) {
|
|
|
11560
11936
|
USINGWASMH5: Boolean(context.main_wasm_h5_md5),
|
|
11561
11937
|
});
|
|
11562
11938
|
wsServer.sendUnitySplitStatus({ status: 'update_wasm_split_config_done' });
|
|
11563
|
-
|
|
11939
|
+
verboseLog('[remote-split-download] all done');
|
|
11564
11940
|
return { data: { isSuccess: true }, ctx: context };
|
|
11565
11941
|
}
|
|
11566
11942
|
catch (err) {
|
|
11567
11943
|
const message = err instanceof Error ? err.message : String(err);
|
|
11568
|
-
|
|
11944
|
+
verboseLog('[remote-split-download] failed:', message);
|
|
11569
11945
|
wsServer.sendUnitySplitStatus({ status: 'wasm_split_failed', errorMsg: message });
|
|
11570
11946
|
return {
|
|
11571
11947
|
data: { isSuccess: false },
|
|
@@ -11890,7 +12266,7 @@ async function downloadPreparedRemote(data) {
|
|
|
11890
12266
|
try {
|
|
11891
12267
|
const downloadUrl = res?.data?.result?.download_url;
|
|
11892
12268
|
if (!downloadUrl) {
|
|
11893
|
-
|
|
12269
|
+
verboseLog('[remote-download-prepared] no download_url in response');
|
|
11894
12270
|
return {
|
|
11895
12271
|
isSuccess: false,
|
|
11896
12272
|
error: { code: res.data?.code, message: res.data?.message || 'No download_url returned' },
|
|
@@ -11903,28 +12279,28 @@ async function downloadPreparedRemote(data) {
|
|
|
11903
12279
|
originalWasmPath: data.wasm_path,
|
|
11904
12280
|
originalSplitConfigPath: WASM_SPLIT_CONFIG_FILE_NAME,
|
|
11905
12281
|
});
|
|
11906
|
-
|
|
12282
|
+
verboseLog(`[remote-download-prepared] target=${willReplaceWasmPath}`);
|
|
11907
12283
|
if (downloadUrl.includes('.br')) {
|
|
11908
12284
|
const tempWasmPath = path$1.join(cacheDir, '__temp__.wasm.br');
|
|
11909
|
-
|
|
12285
|
+
verboseLog('[remote-download-prepared] downloading (br) ->', tempWasmPath);
|
|
11910
12286
|
wsServer.sendUnitySplitStatus({ status: 'start_download_prepared_wasm', url: downloadUrl });
|
|
11911
12287
|
const startedAt = Date.now();
|
|
11912
12288
|
await download(downloadUrl, tempWasmPath);
|
|
11913
|
-
|
|
12289
|
+
verboseLog(`[remote-download-prepared] download done in ${Date.now() - startedAt}ms, size=${fs$1.statSync(tempWasmPath).size}`);
|
|
11914
12290
|
fs$1.copyFileSync(tempWasmPath, willReplaceWasmPath);
|
|
11915
12291
|
wsServer.sendUnitySplitStatus({ status: 'download_prepared_wasm_done', url: downloadUrl });
|
|
11916
12292
|
}
|
|
11917
12293
|
else {
|
|
11918
12294
|
const tempWasmPath = path$1.join(cacheDir, '__temp__.wasm');
|
|
11919
|
-
|
|
12295
|
+
verboseLog('[remote-download-prepared] downloading (raw) ->', tempWasmPath);
|
|
11920
12296
|
wsServer.sendUnitySplitStatus({ status: 'start_download_prepared_wasm', url: downloadUrl });
|
|
11921
12297
|
const startedAt = Date.now();
|
|
11922
12298
|
await download(downloadUrl, tempWasmPath);
|
|
11923
|
-
|
|
12299
|
+
verboseLog(`[remote-download-prepared] download done in ${Date.now() - startedAt}ms, size=${fs$1.statSync(tempWasmPath).size}`);
|
|
11924
12300
|
wsServer.sendUnitySplitStatus({ status: 'download_prepared_wasm_done', url: downloadUrl });
|
|
11925
12301
|
wsServer.sendUnitySplitStatus({ status: 'start_compress_prepared_wasm' });
|
|
11926
12302
|
await compressWasmFile(tempWasmPath, willReplaceWasmPath);
|
|
11927
|
-
|
|
12303
|
+
verboseLog('[remote-download-prepared] compressed and written to project');
|
|
11928
12304
|
wsServer.sendUnitySplitStatus({ status: 'compress_prepared_wasm_done', url: downloadUrl });
|
|
11929
12305
|
wsServer.sendUnitySplitStatus({ status: 'write_compress_prepared_wasm_done' });
|
|
11930
12306
|
}
|
|
@@ -11942,11 +12318,11 @@ async function downloadPreparedRemote(data) {
|
|
|
11942
12318
|
data.wasm_md5,
|
|
11943
12319
|
});
|
|
11944
12320
|
wsServer.sendUnitySplitStatus({ status: 'update_wasm_split_config_done' });
|
|
11945
|
-
|
|
12321
|
+
verboseLog('[remote-download-prepared] split config updated, returning success');
|
|
11946
12322
|
return { isSuccess: true, ctx: res?.ctx };
|
|
11947
12323
|
}
|
|
11948
12324
|
catch (error) {
|
|
11949
|
-
|
|
12325
|
+
verboseLog('[remote-download-prepared] error:', error);
|
|
11950
12326
|
return {
|
|
11951
12327
|
isSuccess: false,
|
|
11952
12328
|
error: { code: res.data?.code, message: error instanceof Error ? error.message : String(error) },
|
|
@@ -11960,7 +12336,21 @@ function isLocal() {
|
|
|
11960
12336
|
}
|
|
11961
12337
|
const startPrepare = (params) => isLocal() ? startPrepare$1(params) : startPrepareRemote(params);
|
|
11962
12338
|
const downloadPrepared = (params) => isLocal() ? downloadPrepared$1() : downloadPreparedRemote(params);
|
|
11963
|
-
|
|
12339
|
+
// 开 collect session(鉴权门)。IDE 经 `/game/wasm-collect-start` 调用,
|
|
12340
|
+
// 所以它在浏览器 Network 里是一条独立可见、明确叫 start 的请求。
|
|
12341
|
+
// - 本地 → `/start`
|
|
12342
|
+
// - 远程 → 老的 `set_collecting`(远程没有独立 /start,set_collecting 即开窗)
|
|
12343
|
+
const openCollectSession = (params) => isLocal() ? openCollectSession$1(params) : setCollectRemote(params);
|
|
12344
|
+
// 上传符号表。本地走 `/symbols`(非阻塞、失败不致命);远程在 prepare 阶段
|
|
12345
|
+
// 已随表单上传过 symbol 文件,这里是 no-op,直接返回成功保持两条 pipeline
|
|
12346
|
+
// 在 IDE 层对称。
|
|
12347
|
+
const uploadCollectSymbols = (params) => isLocal()
|
|
12348
|
+
? uploadCollectSymbols$1(params)
|
|
12349
|
+
: Promise.resolve({
|
|
12350
|
+
data: { code: 0, message: 'success', result: {} },
|
|
12351
|
+
error: null,
|
|
12352
|
+
ctx: { logid: 'remote-noop', httpStatusCode: 200 },
|
|
12353
|
+
});
|
|
11964
12354
|
const getCollectedFuncIds = (params) => isLocal() ? getCollectedFuncIds$1(params) : getCollectedFuncIdsRemote(params);
|
|
11965
12355
|
const getCollecttingInfo = (params) => isLocal() ? getCollecttingInfo$1(params) : getCollecttingInfoRemote(params);
|
|
11966
12356
|
const startSplit = (params) => isLocal() ? startSplit$1(params) : startSplitRemote(params);
|
|
@@ -12148,20 +12538,58 @@ const gameWasmPrepareRoute = {
|
|
|
12148
12538
|
},
|
|
12149
12539
|
};
|
|
12150
12540
|
|
|
12541
|
+
/**
|
|
12542
|
+
* 上传符号表(`/symbols`)。只在 session 已通过 `/game/wasm-collect-start`
|
|
12543
|
+
* 打开后调用,与"开 session"解耦:
|
|
12544
|
+
* - 鉴权门是 `/game/wasm-collect-start`(→ `/start`),不在这条。
|
|
12545
|
+
* - 这步是非关键的 debug 信息上传,本地非阻塞/失败不致命,远程为 no-op。
|
|
12546
|
+
* 因此即便失败也只回 errorCode 让调用方静默处理,不应阻断"开始收集"。
|
|
12547
|
+
*/
|
|
12151
12548
|
const gameWasmSetCollectRoute = {
|
|
12152
12549
|
method: 'post',
|
|
12153
12550
|
path: '/game/wasm-set-collect',
|
|
12154
12551
|
handler: async (req, res) => {
|
|
12155
|
-
|
|
12156
|
-
|
|
12157
|
-
|
|
12158
|
-
|
|
12159
|
-
|
|
12160
|
-
|
|
12161
|
-
|
|
12552
|
+
const { clientKey, codeMd5 } = req.body;
|
|
12553
|
+
console.log('wasm-set-collect (upload symbols)', req.body);
|
|
12554
|
+
const response = await uploadCollectSymbols({
|
|
12555
|
+
client_key: clientKey,
|
|
12556
|
+
wasm_md5: codeMd5,
|
|
12557
|
+
});
|
|
12558
|
+
if (response.error) {
|
|
12559
|
+
res.send({
|
|
12560
|
+
code: errorCode,
|
|
12561
|
+
error: response.error,
|
|
12562
|
+
ctx: response.ctx,
|
|
12563
|
+
});
|
|
12564
|
+
}
|
|
12565
|
+
else {
|
|
12566
|
+
res.send({
|
|
12567
|
+
code: successCode,
|
|
12568
|
+
data: response.data || {},
|
|
12569
|
+
ctx: response.ctx,
|
|
12570
|
+
});
|
|
12571
|
+
}
|
|
12572
|
+
},
|
|
12573
|
+
};
|
|
12574
|
+
|
|
12575
|
+
/**
|
|
12576
|
+
* 打开 collect session —— 对应后端 `/start`(本地)/ `set_collecting`(远程)。
|
|
12577
|
+
*
|
|
12578
|
+
* 单独成一条 IDE 可见路由(而不是埋在 set-collect 里),是为了让浏览器
|
|
12579
|
+
* Network 里有一条**明确叫 start** 的请求:`/start` 走 Portal 鉴权中间件,
|
|
12580
|
+
* 鉴权失败(`-401`)时这条请求会带着透传上来的 `error.code:-401` 返回,
|
|
12581
|
+
* IDE 全局拦截器据此弹"登录态失效"toast,且排查时能一眼定位到是开 session
|
|
12582
|
+
* 这步报的鉴权,而非 collect 轮询接口。
|
|
12583
|
+
*
|
|
12584
|
+
* `resume` 可选:仅本地 pipeline 有意义,缺省 → `reset:true`(重新开始)。
|
|
12585
|
+
*/
|
|
12586
|
+
const gameWasmCollectStartRoute = {
|
|
12587
|
+
method: 'post',
|
|
12588
|
+
path: '/game/wasm-collect-start',
|
|
12589
|
+
handler: async (req, res) => {
|
|
12162
12590
|
const { clientKey, codeMd5, resume } = req.body;
|
|
12163
|
-
console.log('wasm-
|
|
12164
|
-
const response = await
|
|
12591
|
+
console.log('wasm-collect-start', req.body);
|
|
12592
|
+
const response = await openCollectSession({
|
|
12165
12593
|
client_key: clientKey,
|
|
12166
12594
|
wasm_md5: codeMd5,
|
|
12167
12595
|
resume,
|
|
@@ -12525,6 +12953,7 @@ const routes = [
|
|
|
12525
12953
|
gameDirectFeedCardCreateRoute,
|
|
12526
12954
|
gameDirectFeedCardListRoute,
|
|
12527
12955
|
gameDirectFeedCardScenariosRoute,
|
|
12956
|
+
gameDirectFeedCardStatusRoute,
|
|
12528
12957
|
gameUploadRoute,
|
|
12529
12958
|
gameWasmSplitConfigRoute,
|
|
12530
12959
|
gameWasmSplitOptionsRoute,
|
|
@@ -12533,6 +12962,7 @@ const routes = [
|
|
|
12533
12962
|
gameWasmPrepareDownloadRoute,
|
|
12534
12963
|
gameWasmSplitRoute,
|
|
12535
12964
|
gameWasmTaskinfoRoute,
|
|
12965
|
+
gameWasmCollectStartRoute,
|
|
12536
12966
|
gameWasmSetCollectRoute,
|
|
12537
12967
|
gameWasmCollectFuncidsRoute,
|
|
12538
12968
|
gameWasmCollectInfoRoute,
|
|
@@ -12545,14 +12975,61 @@ const routes = [
|
|
|
12545
12975
|
gamePipelineModeGetRoute,
|
|
12546
12976
|
gameLanguageRoute,
|
|
12547
12977
|
];
|
|
12978
|
+
/**
|
|
12979
|
+
* Express 4 does not catch rejections from async route handlers — an
|
|
12980
|
+
* unhandled rejection in any handler propagates to the process and, under
|
|
12981
|
+
* Node's default `unhandledRejection` behavior, kills the whole dev server.
|
|
12982
|
+
* That's how a single missing-file ENOENT in `wasm-prepare` could take the
|
|
12983
|
+
* CLI down. Wrap every handler so a failing request returns a 500 instead of
|
|
12984
|
+
* crashing the process. Individual routes can still return structured errors;
|
|
12985
|
+
* this is only the last-resort net for unexpected throws.
|
|
12986
|
+
*/
|
|
12987
|
+
function withErrorBoundary(handler) {
|
|
12988
|
+
return (req, res, next) => {
|
|
12989
|
+
try {
|
|
12990
|
+
const result = handler(req, res, next);
|
|
12991
|
+
if (result && typeof result.then === 'function') {
|
|
12992
|
+
result.catch((err) => {
|
|
12993
|
+
console.error(`[dev-server] unhandled error in ${req.method} ${req.path}:`, err);
|
|
12994
|
+
if (!res.headersSent) {
|
|
12995
|
+
res
|
|
12996
|
+
.status(500)
|
|
12997
|
+
.send({
|
|
12998
|
+
code: -1,
|
|
12999
|
+
error: {
|
|
13000
|
+
code: 500,
|
|
13001
|
+
message: err instanceof Error ? err.message : String(err),
|
|
13002
|
+
},
|
|
13003
|
+
});
|
|
13004
|
+
}
|
|
13005
|
+
});
|
|
13006
|
+
}
|
|
13007
|
+
}
|
|
13008
|
+
catch (err) {
|
|
13009
|
+
console.error(`[dev-server] unhandled error in ${req.method} ${req.path}:`, err);
|
|
13010
|
+
if (!res.headersSent) {
|
|
13011
|
+
res
|
|
13012
|
+
.status(500)
|
|
13013
|
+
.send({
|
|
13014
|
+
code: -1,
|
|
13015
|
+
error: {
|
|
13016
|
+
code: 500,
|
|
13017
|
+
message: err instanceof Error ? err.message : String(err),
|
|
13018
|
+
},
|
|
13019
|
+
});
|
|
13020
|
+
}
|
|
13021
|
+
}
|
|
13022
|
+
};
|
|
13023
|
+
}
|
|
12548
13024
|
function registerRoutes(app, options) {
|
|
12549
13025
|
const allRoutes = [...routes, getGameFallbackRoute(options.publicPath)];
|
|
12550
13026
|
for (const route of allRoutes) {
|
|
13027
|
+
const handler = withErrorBoundary(route.handler);
|
|
12551
13028
|
if (route.method === 'get') {
|
|
12552
|
-
app.get(route.path,
|
|
13029
|
+
app.get(route.path, handler);
|
|
12553
13030
|
}
|
|
12554
13031
|
else if (route.method === 'post') {
|
|
12555
|
-
app.post(route.path,
|
|
13032
|
+
app.post(route.path, handler);
|
|
12556
13033
|
}
|
|
12557
13034
|
}
|
|
12558
13035
|
}
|
|
@@ -12997,7 +13474,7 @@ async function upload({ clientKey, note = '--', dir, }) {
|
|
|
12997
13474
|
}
|
|
12998
13475
|
}
|
|
12999
13476
|
|
|
13000
|
-
var version = "0.
|
|
13477
|
+
var version = "0.4.0-beta.1";
|
|
13001
13478
|
var pkg = {
|
|
13002
13479
|
version: version};
|
|
13003
13480
|
|