@ttmg/cli 0.3.9 → 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 +416 -81
- package/dist/index.js.map +1 -1
- package/dist/package.json +1 -1
- package/dist/public/assets/{Card-DvUkoCA3.js → Card-C35olIke.js} +1 -1
- 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--L4WcmYS.js → MonetizationModeSummary-D-zuX05a.js} +1 -1
- package/dist/public/assets/{SectionHeader-DfBBWmpa.js → SectionHeader-DRXnax7L.js} +1 -1
- package/dist/public/assets/{Tag-DuP9fCS0.js → Tag-CgPHrk93.js} +1 -1
- package/dist/public/assets/{arrow-left-1V5G0Kw7.js → arrow-left-CNdHGsDz.js} +1 -1
- package/dist/public/assets/{baseForm-2J62W6xr.js → baseForm-C2Ms-wqt.js} +1 -1
- package/dist/public/assets/baseForm-C2Ms-wqt.js.br +0 -0
- package/dist/public/assets/{chevron-right-C5K8xBff.js → chevron-right-CX-Bo3I2.js} +1 -1
- package/dist/public/assets/{compass-DZKQ36UU.js → compass-BUqxZo39.js} +1 -1
- package/dist/public/assets/{index-B13yZ-GH.js → index-8z6tNstf.js} +1 -1
- package/dist/public/assets/index-AGEFeR2m.js +1 -0
- package/dist/public/assets/{index-B-k2MmlV.js → index-B8dmfh3A.js} +1 -1
- package/dist/public/assets/{index-Br2fR8kJ.js → index-B9C7FZes.js} +1 -1
- package/dist/public/assets/{index-DYxQAjNc.js → index-BULJJdeE.js} +1 -1
- 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-fS7DhM40.js → index-BmmpJdOy.js} +1 -1
- package/dist/public/assets/index-BmmpJdOy.js.br +0 -0
- package/dist/public/assets/{index-DqoKSlOn.js → index-BuGJ38RF.js} +1 -1
- package/dist/public/assets/{index-CmVUh50W.css → index-ByX5RuFA.css} +1 -1
- package/dist/public/assets/{index-CgTtqphq.js → index-ByqGLZ4G.js} +1 -1
- package/dist/public/assets/{index-Bmw61rl1.css → index-CFd8iglC.css} +1 -1
- package/dist/public/assets/index-CFd8iglC.css.br +0 -0
- package/dist/public/assets/{index-CwYCdibK.js → index-CIIptoBi.js} +1 -1
- package/dist/public/assets/{index-BWWe1lsP.js → index-CLXkCzG6.js} +1 -1
- package/dist/public/assets/{index-WZnCtUAc.js → index-CUV27PJL.js} +1 -1
- package/dist/public/assets/{index-BttcxiCg.js → index-CxeatYOZ.js} +1 -1
- package/dist/public/assets/{index-BdDJLxXD.js → index-D0DgrJ_K.js} +1 -1
- package/dist/public/assets/{index-BaZPp3HP.js → index-D28D1GWM.js} +1 -1
- 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-DNktXE2W.js → index-EorOvXWq.js} +1 -1
- package/dist/public/assets/{index-B-AeuNlL.css → index-_6n0s04V.css} +1 -1
- package/dist/public/assets/index-_6n0s04V.css.br +0 -0
- package/dist/public/assets/{index-CnReCFYA.js → index-cC0QApVl.js} +8 -8
- package/dist/public/assets/index-cC0QApVl.js.br +0 -0
- package/dist/public/assets/{index-CZ7VVYfd.js → index-l16xoaSk.js} +1 -1
- package/dist/public/assets/{sparkles-tr6K1Npg.js → sparkles-C54nzN2M.js} +1 -1
- package/dist/public/assets/{zap-CuMRVa_B.js → zap-g-acVHwy.js} +1 -1
- package/dist/public/index.html +1 -1
- package/package.json +1 -1
- package/CHANGELOG.md +0 -241
- package/dist/public/assets/Detail-Cbq9dDUe.js +0 -1
- package/dist/public/assets/Detail-Cbq9dDUe.js.br +0 -0
- package/dist/public/assets/MonetizationMode-0taoFj2g.js +0 -1
- package/dist/public/assets/baseForm-2J62W6xr.js.br +0 -0
- package/dist/public/assets/index-B-AeuNlL.css.br +0 -0
- package/dist/public/assets/index-Bmw61rl1.css.br +0 -0
- package/dist/public/assets/index-CcxOfQrE.js +0 -1
- package/dist/public/assets/index-CcxOfQrE.js.br +0 -0
- package/dist/public/assets/index-CnReCFYA.js.br +0 -0
- package/dist/public/assets/index-DYxQAjNc.js.br +0 -0
- package/dist/public/assets/index-YkO-JjYA.js +0 -1
- package/dist/public/assets/index-cPr70sDS.js +0 -1
- package/dist/public/assets/index-fS7DhM40.js.br +0 -0
- package/dist/public/assets/index-qHht7NSU.js +0 -1
- package/dist/public/assets/index-qHht7NSU.js.br +0 -0
- /package/dist/public/assets/{index-B6NHbQwP.js → index-CjInIkcE.js} +0 -0
package/dist/index.js
CHANGED
|
@@ -6924,10 +6924,8 @@ function isAxiosError(e) {
|
|
|
6924
6924
|
}
|
|
6925
6925
|
|
|
6926
6926
|
const CREATE_DIRECT_FEED_CARD_URL = 'https://developers.tiktok.com/tiktok/v4/devportal/mini_game/fyf_card/create_direct_feed_card';
|
|
6927
|
-
const DIRECT_FEED_CARD_HEADERS$
|
|
6927
|
+
const DIRECT_FEED_CARD_HEADERS$3 = {
|
|
6928
6928
|
'Content-Type': 'application/json',
|
|
6929
|
-
'x-use-ppe': '1',
|
|
6930
|
-
'x-tt-env': 'ppe_feed_play',
|
|
6931
6929
|
};
|
|
6932
6930
|
async function createDirectFeedCard({ appId, clientKey, directFeedCard, }) {
|
|
6933
6931
|
const payload = {
|
|
@@ -6938,22 +6936,20 @@ async function createDirectFeedCard({ appId, clientKey, directFeedCard, }) {
|
|
|
6938
6936
|
return request({
|
|
6939
6937
|
url: CREATE_DIRECT_FEED_CARD_URL,
|
|
6940
6938
|
method: 'POST',
|
|
6941
|
-
headers: DIRECT_FEED_CARD_HEADERS$
|
|
6939
|
+
headers: DIRECT_FEED_CARD_HEADERS$3,
|
|
6942
6940
|
data: payload,
|
|
6943
6941
|
});
|
|
6944
6942
|
}
|
|
6945
6943
|
|
|
6946
6944
|
const GET_DIRECT_FEED_SCENARIOS_URL = 'https://developers.tiktok.com/tiktok/v4/devportal/mini_game/fyf_card/get_direct_feed_scenario';
|
|
6947
|
-
const DIRECT_FEED_CARD_HEADERS$
|
|
6945
|
+
const DIRECT_FEED_CARD_HEADERS$2 = {
|
|
6948
6946
|
'Content-Type': 'application/json',
|
|
6949
|
-
'x-use-ppe': '1',
|
|
6950
|
-
'x-tt-env': 'ppe_feed_play',
|
|
6951
6947
|
};
|
|
6952
6948
|
async function fetchDirectFeedScenarios({ appId, clientKey, }) {
|
|
6953
6949
|
return request({
|
|
6954
6950
|
url: GET_DIRECT_FEED_SCENARIOS_URL,
|
|
6955
6951
|
method: 'POST',
|
|
6956
|
-
headers: DIRECT_FEED_CARD_HEADERS$
|
|
6952
|
+
headers: DIRECT_FEED_CARD_HEADERS$2,
|
|
6957
6953
|
data: {
|
|
6958
6954
|
app_id: appId,
|
|
6959
6955
|
client_key: clientKey,
|
|
@@ -6973,16 +6969,14 @@ async function fetchGameAssetPreviewUrl({ assetId, }) {
|
|
|
6973
6969
|
}
|
|
6974
6970
|
|
|
6975
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';
|
|
6976
|
-
const DIRECT_FEED_CARD_HEADERS = {
|
|
6972
|
+
const DIRECT_FEED_CARD_HEADERS$1 = {
|
|
6977
6973
|
'Content-Type': 'application/json',
|
|
6978
|
-
'x-use-ppe': '1',
|
|
6979
|
-
'x-tt-env': 'ppe_feed_play',
|
|
6980
6974
|
};
|
|
6981
6975
|
async function fetchDirectFeedCardAssetPreviewUrl({ assetId, contentId, }) {
|
|
6982
6976
|
return request({
|
|
6983
6977
|
url: GET_DIRECT_FEED_CARD_ASSET_PREVIEW_URL,
|
|
6984
6978
|
method: 'POST',
|
|
6985
|
-
headers: DIRECT_FEED_CARD_HEADERS,
|
|
6979
|
+
headers: DIRECT_FEED_CARD_HEADERS$1,
|
|
6986
6980
|
data: {
|
|
6987
6981
|
asset_id: assetId,
|
|
6988
6982
|
content_id: contentId,
|
|
@@ -7040,8 +7034,6 @@ async function fetchGameInfo(clientKey) {
|
|
|
7040
7034
|
const LIST_DIRECT_FEED_CARD_URL = 'https://developers.tiktok.com/tiktok/v4/devportal/mini_game/fyf_card/list_direct_feed_card';
|
|
7041
7035
|
const LIST_DIRECT_FEED_CARD_HEADERS = {
|
|
7042
7036
|
'Content-Type': 'application/json',
|
|
7043
|
-
'x-use-ppe': '1',
|
|
7044
|
-
'x-tt-env': 'ppe_feed_play',
|
|
7045
7037
|
};
|
|
7046
7038
|
async function listDirectFeedCards({ appId, clientKey, }) {
|
|
7047
7039
|
return request({
|
|
@@ -7055,6 +7047,23 @@ async function listDirectFeedCards({ appId, clientKey, }) {
|
|
|
7055
7047
|
});
|
|
7056
7048
|
}
|
|
7057
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
|
+
|
|
7058
7067
|
async function uploadGameToPlatform({ data, name, clientKey, note = '--', appId, }) {
|
|
7059
7068
|
if (!appId) {
|
|
7060
7069
|
return {
|
|
@@ -8372,8 +8381,81 @@ async function compile(context) {
|
|
|
8372
8381
|
}
|
|
8373
8382
|
|
|
8374
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
|
+
}
|
|
8375
8445
|
async function watch() {
|
|
8376
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
|
+
};
|
|
8377
8459
|
// 监听当前工作目录,排除 node_modules 和 .git
|
|
8378
8460
|
const watcher = chokidar.watch(process.cwd(), {
|
|
8379
8461
|
/**
|
|
@@ -8388,17 +8470,20 @@ async function watch() {
|
|
|
8388
8470
|
});
|
|
8389
8471
|
// 任意文件变化都触发
|
|
8390
8472
|
watcher.on('all', (event, path) => {
|
|
8391
|
-
//
|
|
8392
|
-
|
|
8393
|
-
|
|
8394
|
-
|
|
8395
|
-
|
|
8396
|
-
|
|
8397
|
-
|
|
8398
|
-
|
|
8399
|
-
|
|
8400
|
-
|
|
8401
|
-
|
|
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?.();
|
|
8402
8487
|
});
|
|
8403
8488
|
watcher.on('error', error => {
|
|
8404
8489
|
// console.error(chalk.red('[watch] 监听发生错误:'), error);
|
|
@@ -9836,7 +9921,7 @@ const gameDirectFeedCardAssetPreviewRoute = {
|
|
|
9836
9921
|
},
|
|
9837
9922
|
};
|
|
9838
9923
|
|
|
9839
|
-
async function resolveAppIdentity$
|
|
9924
|
+
async function resolveAppIdentity$3(body) {
|
|
9840
9925
|
const clientKey = body?.clientKey?.trim() ||
|
|
9841
9926
|
body?.client_key?.trim() ||
|
|
9842
9927
|
getClientKey().clientKey?.trim();
|
|
@@ -9903,7 +9988,7 @@ const gameDirectFeedCardCreateRoute = {
|
|
|
9903
9988
|
method: 'post',
|
|
9904
9989
|
path: '/game/direct-feed-card/create',
|
|
9905
9990
|
handler: async (req, res) => {
|
|
9906
|
-
const identity = await resolveAppIdentity$
|
|
9991
|
+
const identity = await resolveAppIdentity$3(req.body);
|
|
9907
9992
|
if (identity.error) {
|
|
9908
9993
|
res.send({
|
|
9909
9994
|
code: errorCode,
|
|
@@ -9959,7 +10044,7 @@ function isUsableDirectFeedCard(card) {
|
|
|
9959
10044
|
}
|
|
9960
10045
|
return true;
|
|
9961
10046
|
}
|
|
9962
|
-
async function resolveAppIdentity$
|
|
10047
|
+
async function resolveAppIdentity$2(body) {
|
|
9963
10048
|
const clientKey = body?.clientKey?.trim() ||
|
|
9964
10049
|
body?.client_key?.trim() ||
|
|
9965
10050
|
getClientKey().clientKey?.trim();
|
|
@@ -10004,7 +10089,7 @@ const gameDirectFeedCardListRoute = {
|
|
|
10004
10089
|
method: 'post',
|
|
10005
10090
|
path: '/game/direct-feed-card/list',
|
|
10006
10091
|
handler: async (req, res) => {
|
|
10007
|
-
const identity = await resolveAppIdentity$
|
|
10092
|
+
const identity = await resolveAppIdentity$2(req.body);
|
|
10008
10093
|
if (identity.error) {
|
|
10009
10094
|
res.send({
|
|
10010
10095
|
code: errorCode,
|
|
@@ -10109,7 +10194,7 @@ function extractScenarioOptions(data) {
|
|
|
10109
10194
|
}
|
|
10110
10195
|
return [];
|
|
10111
10196
|
}
|
|
10112
|
-
async function resolveAppIdentity(body) {
|
|
10197
|
+
async function resolveAppIdentity$1(body) {
|
|
10113
10198
|
const clientKey = body?.clientKey?.trim() ||
|
|
10114
10199
|
body?.client_key?.trim() ||
|
|
10115
10200
|
getClientKey().clientKey?.trim();
|
|
@@ -10154,7 +10239,7 @@ const gameDirectFeedCardScenariosRoute = {
|
|
|
10154
10239
|
method: 'post',
|
|
10155
10240
|
path: '/game/direct-feed-card/scenarios',
|
|
10156
10241
|
handler: async (req, res) => {
|
|
10157
|
-
const identity = await resolveAppIdentity(req.body);
|
|
10242
|
+
const identity = await resolveAppIdentity$1(req.body);
|
|
10158
10243
|
if (identity.error) {
|
|
10159
10244
|
res.send({
|
|
10160
10245
|
code: errorCode,
|
|
@@ -10194,6 +10279,112 @@ const gameDirectFeedCardScenariosRoute = {
|
|
|
10194
10279
|
},
|
|
10195
10280
|
};
|
|
10196
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
|
+
|
|
10197
10388
|
const gameDetailRoute = {
|
|
10198
10389
|
method: 'get',
|
|
10199
10390
|
path: '/game/detail',
|
|
@@ -10490,42 +10681,83 @@ function restoreSplitConfigFromCache(entryDir = process.cwd()) {
|
|
|
10490
10681
|
* to change.
|
|
10491
10682
|
*/
|
|
10492
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) {
|
|
10493
10697
|
const cacheDir = path.join(entryDir, WASM_SPLIT_CACHE_DIR);
|
|
10494
|
-
// 1) Wipe stale split residue inside wasmcode/ first, THEN restore the
|
|
10495
|
-
// original. Order matters: if we restore first then wipe, we'd delete
|
|
10496
|
-
// the very file we just brought back.
|
|
10497
10698
|
const originDir = path.join(entryDir, WASM_SPLIT_SUBPACKAGE_CONFIG.origin.root);
|
|
10498
|
-
|
|
10499
|
-
|
|
10500
|
-
|
|
10501
|
-
|
|
10502
|
-
|
|
10503
|
-
|
|
10504
|
-
|
|
10505
|
-
|
|
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
|
+
}
|
|
10506
10724
|
}
|
|
10507
10725
|
}
|
|
10726
|
+
const destWasmBrPath = path.join(originDir, path.basename(cachedWasmBr));
|
|
10727
|
+
ensureDirSync(path.dirname(destWasmBrPath));
|
|
10728
|
+
fs.copyFileSync(path.join(cacheDir, cachedWasmBr), destWasmBrPath);
|
|
10508
10729
|
}
|
|
10509
|
-
|
|
10510
|
-
|
|
10511
|
-
|
|
10512
|
-
|
|
10513
|
-
|
|
10514
|
-
|
|
10515
|
-
|
|
10516
|
-
|
|
10517
|
-
|
|
10518
|
-
|
|
10519
|
-
|
|
10520
|
-
|
|
10521
|
-
|
|
10522
|
-
|
|
10523
|
-
|
|
10524
|
-
|
|
10525
|
-
|
|
10526
|
-
|
|
10527
|
-
|
|
10528
|
-
|
|
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.
|
|
10529
10761
|
// Strategy:
|
|
10530
10762
|
// - Prefer the cache (keepCacheSync stashes pre-split contents to
|
|
10531
10763
|
// `__unity_cache__/wasmcode-game.js` when the original existed)
|
|
@@ -10536,14 +10768,16 @@ function restoreFromCache(entryDir = process.cwd()) {
|
|
|
10536
10768
|
// downloadSplited.ts also writes; it satisfies the platform requirement
|
|
10537
10769
|
// without changing semantics for projects that don't use wasmcode as
|
|
10538
10770
|
// a subpackage (the file is harmless empty).
|
|
10539
|
-
|
|
10540
|
-
|
|
10541
|
-
|
|
10542
|
-
if (fs.existsSync(
|
|
10543
|
-
fs.
|
|
10544
|
-
|
|
10545
|
-
|
|
10546
|
-
|
|
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
|
+
}
|
|
10547
10781
|
}
|
|
10548
10782
|
}
|
|
10549
10783
|
for (const subDir of SPLIT_OUTPUT_DIRS) {
|
|
@@ -10661,19 +10895,51 @@ function setLocalState(partial) {
|
|
|
10661
10895
|
Object.assign(state, partial);
|
|
10662
10896
|
}
|
|
10663
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
|
+
*/
|
|
10664
10904
|
async function startPrepare$1(params) {
|
|
10905
|
+
return withWatchSuspended(() => startPrepareImpl(params), {
|
|
10906
|
+
emitOnFinish: true,
|
|
10907
|
+
});
|
|
10908
|
+
}
|
|
10909
|
+
async function startPrepareImpl(params) {
|
|
10665
10910
|
const tempDir = path$1.join(process.cwd(), TTMG_TEMP_DIR);
|
|
10666
10911
|
ensureDirSync(tempDir);
|
|
10667
10912
|
const inputPath = path$1.join(process.cwd(), params.wasm_file_path);
|
|
10668
|
-
|
|
10669
|
-
if (inputPath.endsWith('.br')) {
|
|
10670
|
-
await decompressWasmFile(inputPath, rawWasmPath);
|
|
10671
|
-
}
|
|
10672
|
-
else {
|
|
10673
|
-
fs$1.copyFileSync(inputPath, rawWasmPath);
|
|
10674
|
-
}
|
|
10913
|
+
const rawWasmPath = path$1.join(tempDir, 'original.wasm');
|
|
10675
10914
|
const preparedWasmPath = path$1.join(tempDir, 'prepared.wasm');
|
|
10676
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
|
+
}
|
|
10677
10943
|
const result = ttmgWasmtool.prepare(rawWasmPath, preparedWasmPath);
|
|
10678
10944
|
verboseLog(`[wasmtool] prepare done: ${result.outputSize} bytes, ${result.timeCost}s`);
|
|
10679
10945
|
const gameJson = getGameJson();
|
|
@@ -11244,7 +11510,18 @@ function updateSubpackageConfigSync(archive = false) {
|
|
|
11244
11510
|
fs__namespace.writeFileSync(gameJsonPath, JSON.stringify(gameJson, null, JSON_INDENT) + JSON_EOL);
|
|
11245
11511
|
}
|
|
11246
11512
|
|
|
11247
|
-
|
|
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) {
|
|
11248
11525
|
const cwd = process.cwd();
|
|
11249
11526
|
const { splitMeta } = getLocalState();
|
|
11250
11527
|
if (!splitMeta) {
|
|
@@ -11567,7 +11844,17 @@ async function downloadOne(opts) {
|
|
|
11567
11844
|
// so the subpackage loader doesn't complain about missing js entries.
|
|
11568
11845
|
fs.writeFileSync(path.join(path.dirname(out), 'game.js'), '', 'utf-8');
|
|
11569
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
|
+
*/
|
|
11570
11852
|
async function downloadSplitedRemote(context) {
|
|
11853
|
+
return withWatchSuspended(() => downloadSplitedRemoteImpl(context), {
|
|
11854
|
+
emitOnFinish: true,
|
|
11855
|
+
});
|
|
11856
|
+
}
|
|
11857
|
+
async function downloadSplitedRemoteImpl(context) {
|
|
11571
11858
|
const cwd = process.cwd();
|
|
11572
11859
|
const splitTempDir = path.join(cwd, WASM_SPLIT_CACHE_DIR, DIR_SPLIT);
|
|
11573
11860
|
ensureDirSync(splitTempDir);
|
|
@@ -12666,6 +12953,7 @@ const routes = [
|
|
|
12666
12953
|
gameDirectFeedCardCreateRoute,
|
|
12667
12954
|
gameDirectFeedCardListRoute,
|
|
12668
12955
|
gameDirectFeedCardScenariosRoute,
|
|
12956
|
+
gameDirectFeedCardStatusRoute,
|
|
12669
12957
|
gameUploadRoute,
|
|
12670
12958
|
gameWasmSplitConfigRoute,
|
|
12671
12959
|
gameWasmSplitOptionsRoute,
|
|
@@ -12687,14 +12975,61 @@ const routes = [
|
|
|
12687
12975
|
gamePipelineModeGetRoute,
|
|
12688
12976
|
gameLanguageRoute,
|
|
12689
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
|
+
}
|
|
12690
13024
|
function registerRoutes(app, options) {
|
|
12691
13025
|
const allRoutes = [...routes, getGameFallbackRoute(options.publicPath)];
|
|
12692
13026
|
for (const route of allRoutes) {
|
|
13027
|
+
const handler = withErrorBoundary(route.handler);
|
|
12693
13028
|
if (route.method === 'get') {
|
|
12694
|
-
app.get(route.path,
|
|
13029
|
+
app.get(route.path, handler);
|
|
12695
13030
|
}
|
|
12696
13031
|
else if (route.method === 'post') {
|
|
12697
|
-
app.post(route.path,
|
|
13032
|
+
app.post(route.path, handler);
|
|
12698
13033
|
}
|
|
12699
13034
|
}
|
|
12700
13035
|
}
|
|
@@ -13139,7 +13474,7 @@ async function upload({ clientKey, note = '--', dir, }) {
|
|
|
13139
13474
|
}
|
|
13140
13475
|
}
|
|
13141
13476
|
|
|
13142
|
-
var version = "0.
|
|
13477
|
+
var version = "0.4.0-beta.1";
|
|
13143
13478
|
var pkg = {
|
|
13144
13479
|
version: version};
|
|
13145
13480
|
|