@ttmg/cli 0.3.8 → 0.3.9-beta.2
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 +2155 -1293
- package/dist/index.js.map +1 -1
- package/dist/package.json +7 -2
- package/dist/public/assets/Card-CTv745_w.js +1 -0
- package/dist/public/assets/Detail-DgPwDVds.js +1 -0
- package/dist/public/assets/Detail-DgPwDVds.js.br +0 -0
- package/dist/public/assets/MonetizationMode-CqG7VmZX.js +1 -0
- package/dist/public/assets/MonetizationModeSummary-BSmi2Vjs.js +1 -0
- package/dist/public/assets/SectionHeader-BFCjOsAZ.js +1 -0
- package/dist/public/assets/Tag-Cwc1Bal2.js +1 -0
- package/dist/public/assets/arrow-left-DrT44p81.js +1 -0
- package/dist/public/assets/baseForm-DY0XNYDE.js +10 -0
- package/dist/public/assets/baseForm-DY0XNYDE.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-DzleLIdl.js +1 -0
- package/dist/public/assets/compass-BW4vGO0x.js +1 -0
- package/dist/public/assets/index-4NS2mMuQ.css +1 -0
- package/dist/public/assets/index-B-AeuNlL.css +1 -0
- package/dist/public/assets/index-B-AeuNlL.css.br +0 -0
- package/dist/public/assets/index-B3e59WZW.js +1 -0
- package/dist/public/assets/{index-tOg_vZEc.js → index-B6NHbQwP.js} +1 -1
- package/dist/public/assets/index-B6RZ7AdI.js +1 -0
- package/dist/public/assets/index-B6RZ7AdI.js.br +0 -0
- package/dist/public/assets/index-BAud2cRu.css +1 -0
- package/dist/public/assets/index-BBuCNszs.js +1 -0
- package/dist/public/assets/index-BJ1WI0xW.js +14 -0
- package/dist/public/assets/index-BJ1WI0xW.js.br +0 -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-BQiwR45B.js +1 -0
- package/dist/public/assets/index-BR2iFF5J.js +1 -0
- package/dist/public/assets/index-BR2iFF5J.js.br +0 -0
- package/dist/public/assets/index-BbSY5AoV.js +1 -0
- package/dist/public/assets/index-BmP2LC8F.js +1 -0
- package/dist/public/assets/index-Bmw61rl1.css +1 -0
- package/dist/public/assets/index-Bmw61rl1.css.br +0 -0
- package/dist/public/assets/index-BqUsIqrD.js +1 -0
- package/dist/public/assets/index-BqUsIqrD.js.br +0 -0
- package/dist/public/assets/index-BrC0aWmM.js +1 -0
- package/dist/public/assets/index-BsEJ06u7.js +1 -0
- package/dist/public/assets/index-C-tTmNa4.css +1 -0
- package/dist/public/assets/index-C0slAX90.js +1 -0
- package/dist/public/assets/index-C0slAX90.js.br +0 -0
- package/dist/public/assets/index-CBlaFQAe.js +1 -0
- package/dist/public/assets/index-CBlaFQAe.js.br +0 -0
- package/dist/public/assets/index-CCIR5x04.js +1 -0
- package/dist/public/assets/index-CVrG-aCP.js +1 -0
- package/dist/public/assets/index-CYONSh3E.js +1 -0
- package/dist/public/assets/index-Cc1ilXmc.css +1 -0
- package/dist/public/assets/index-CmVUh50W.css +1 -0
- package/dist/public/assets/index-Crx61Qjc.css +1 -0
- package/dist/public/assets/index-D-GbEkoB.css +1 -0
- package/dist/public/assets/index-DYNaiQIB.js +1 -0
- package/dist/public/assets/index-Dvg_oNs7.css +1 -0
- package/dist/public/assets/{index-D2LsTDVa.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-TElbHITb.js +1 -0
- package/dist/public/assets/index-Yv0czelv.js +1 -0
- package/dist/public/assets/index-ZYC8SS17.js +1 -0
- package/dist/public/assets/index-faRHENEQ.css +1 -0
- package/dist/public/assets/sparkles-KMi7bfJ3.js +1 -0
- package/dist/public/assets/zap-BlvSLoGD.js +1 -0
- package/dist/public/index.html +13 -3
- package/dist/scripts/dev-debug.js +191 -0
- package/package.json +7 -2
- package/CHANGELOG.md +0 -228
- package/dist/public/assets/Detail-DuoflIDB.js +0 -1
- package/dist/public/assets/Detail-DuoflIDB.js.br +0 -0
- package/dist/public/assets/MonetizationMode-C6-xgErM.js +0 -1
- package/dist/public/assets/MonetizationModeSummary-v1a9_YaN.js +0 -1
- package/dist/public/assets/baseForm-CB6KCNqW.css +0 -1
- package/dist/public/assets/baseForm-CB6KCNqW.css.br +0 -0
- package/dist/public/assets/baseForm-DqyLTNAx.js +0 -10
- package/dist/public/assets/baseForm-DqyLTNAx.js.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-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-BmcEpXdh.js +0 -1
- package/dist/public/assets/index-BmcEpXdh.js.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-Bpba_DWs.js +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-CLgcHgzd.css +0 -1
- package/dist/public/assets/index-CPJp_0l2.js +0 -1
- package/dist/public/assets/index-ClpFHzCy.js +0 -1
- package/dist/public/assets/index-Crf11eTs.js +0 -1
- package/dist/public/assets/index-Crf11eTs.js.br +0 -0
- package/dist/public/assets/index-CwihbwhD.js +0 -1
- package/dist/public/assets/index-D2LsTDVa.css.br +0 -0
- package/dist/public/assets/index-DJTcocTh.js +0 -1
- package/dist/public/assets/index-DNsJSSmy.css +0 -1
- package/dist/public/assets/index-DO22Abni.js +0 -1
- package/dist/public/assets/index-DPSts5Re.css +0 -1
- package/dist/public/assets/index-DPSts5Re.css.br +0 -0
- package/dist/public/assets/index-DWw-gYwj.js +0 -1
- package/dist/public/assets/index-DWw-gYwj.js.br +0 -0
- package/dist/public/assets/index-Dc800O6M.js +0 -1
- package/dist/public/assets/index-De7XyxJU.js +0 -1
- package/dist/public/assets/index-Pfoc2Cwl.js +0 -14
- package/dist/public/assets/index-Pfoc2Cwl.js.br +0 -0
- package/dist/public/assets/index-SOEihm3l.js +0 -1
- package/dist/public/assets/index-dSgab9d_.js +0 -1
- package/dist/public/assets/index-dSgab9d_.js.br +0 -0
- package/dist/public/assets/index-iUN2bzlN.js +0 -1
- package/dist/public/assets/index-yXoFuCJJ.js +0 -1
- package/dist/public/assets/index-yXoFuCJJ.js.br +0 -0
- package/dist/public/assets/times-BsA3kTAU.js +0 -1
- package/dist/public/assets/times-C7C5ulLg.css +0 -1
package/dist/index.js
CHANGED
|
@@ -38,9 +38,12 @@ var FormData$1 = require('form-data');
|
|
|
38
38
|
var ttmgPack = require('ttmg-pack');
|
|
39
39
|
var expressStaticGzip = require('express-static-gzip');
|
|
40
40
|
var fileUpload = require('express-fileupload');
|
|
41
|
+
var ttmgWasmtool = require('@anrans001/ttmg-wasmtool');
|
|
42
|
+
var crypto$1 = require('node:crypto');
|
|
41
43
|
var fs$1 = require('node:fs');
|
|
42
44
|
var path$1 = require('node:path');
|
|
43
45
|
var zlib = require('zlib');
|
|
46
|
+
var crypto = require('crypto');
|
|
44
47
|
var promises = require('fs/promises');
|
|
45
48
|
var qs = require('qs');
|
|
46
49
|
|
|
@@ -69,6 +72,8 @@ var http__namespace = /*#__PURE__*/_interopNamespaceDefault(http);
|
|
|
69
72
|
var dns__namespace = /*#__PURE__*/_interopNamespaceDefault(dns);
|
|
70
73
|
var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
|
|
71
74
|
var glob__namespace = /*#__PURE__*/_interopNamespaceDefault(glob);
|
|
75
|
+
var fs__namespace$1 = /*#__PURE__*/_interopNamespaceDefault(fs$1);
|
|
76
|
+
var path__namespace$1 = /*#__PURE__*/_interopNamespaceDefault(path$1);
|
|
72
77
|
|
|
73
78
|
async function openUrl(url) {
|
|
74
79
|
const { launch } = await import('chrome-launcher');
|
|
@@ -584,17 +589,17 @@ var hasRequiredBrowser;
|
|
|
584
589
|
function requireBrowser () {
|
|
585
590
|
if (hasRequiredBrowser) return browser.exports;
|
|
586
591
|
hasRequiredBrowser = 1;
|
|
587
|
-
(function (module, exports) {
|
|
592
|
+
(function (module, exports$1) {
|
|
588
593
|
/**
|
|
589
594
|
* This is the web browser implementation of `debug()`.
|
|
590
595
|
*/
|
|
591
596
|
|
|
592
|
-
exports.formatArgs = formatArgs;
|
|
593
|
-
exports.save = save;
|
|
594
|
-
exports.load = load;
|
|
595
|
-
exports.useColors = useColors;
|
|
596
|
-
exports.storage = localstorage();
|
|
597
|
-
exports.destroy = (() => {
|
|
597
|
+
exports$1.formatArgs = formatArgs;
|
|
598
|
+
exports$1.save = save;
|
|
599
|
+
exports$1.load = load;
|
|
600
|
+
exports$1.useColors = useColors;
|
|
601
|
+
exports$1.storage = localstorage();
|
|
602
|
+
exports$1.destroy = (() => {
|
|
598
603
|
let warned = false;
|
|
599
604
|
|
|
600
605
|
return () => {
|
|
@@ -609,7 +614,7 @@ function requireBrowser () {
|
|
|
609
614
|
* Colors.
|
|
610
615
|
*/
|
|
611
616
|
|
|
612
|
-
exports.colors = [
|
|
617
|
+
exports$1.colors = [
|
|
613
618
|
'#0000CC',
|
|
614
619
|
'#0000FF',
|
|
615
620
|
'#0033CC',
|
|
@@ -774,7 +779,7 @@ function requireBrowser () {
|
|
|
774
779
|
*
|
|
775
780
|
* @api public
|
|
776
781
|
*/
|
|
777
|
-
exports.log = console.debug || console.log || (() => {});
|
|
782
|
+
exports$1.log = console.debug || console.log || (() => {});
|
|
778
783
|
|
|
779
784
|
/**
|
|
780
785
|
* Save `namespaces`.
|
|
@@ -785,9 +790,9 @@ function requireBrowser () {
|
|
|
785
790
|
function save(namespaces) {
|
|
786
791
|
try {
|
|
787
792
|
if (namespaces) {
|
|
788
|
-
exports.storage.setItem('debug', namespaces);
|
|
793
|
+
exports$1.storage.setItem('debug', namespaces);
|
|
789
794
|
} else {
|
|
790
|
-
exports.storage.removeItem('debug');
|
|
795
|
+
exports$1.storage.removeItem('debug');
|
|
791
796
|
}
|
|
792
797
|
} catch (error) {
|
|
793
798
|
// Swallow
|
|
@@ -804,7 +809,7 @@ function requireBrowser () {
|
|
|
804
809
|
function load() {
|
|
805
810
|
let r;
|
|
806
811
|
try {
|
|
807
|
-
r = exports.storage.getItem('debug') || exports.storage.getItem('DEBUG') ;
|
|
812
|
+
r = exports$1.storage.getItem('debug') || exports$1.storage.getItem('DEBUG') ;
|
|
808
813
|
} catch (error) {
|
|
809
814
|
// Swallow
|
|
810
815
|
// XXX (@Qix-) should we be logging these?
|
|
@@ -840,7 +845,7 @@ function requireBrowser () {
|
|
|
840
845
|
}
|
|
841
846
|
}
|
|
842
847
|
|
|
843
|
-
module.exports = requireCommon$1()(exports);
|
|
848
|
+
module.exports = requireCommon$1()(exports$1);
|
|
844
849
|
|
|
845
850
|
const {formatters} = module.exports;
|
|
846
851
|
|
|
@@ -1029,7 +1034,7 @@ var hasRequiredNode;
|
|
|
1029
1034
|
function requireNode () {
|
|
1030
1035
|
if (hasRequiredNode) return node.exports;
|
|
1031
1036
|
hasRequiredNode = 1;
|
|
1032
|
-
(function (module, exports) {
|
|
1037
|
+
(function (module, exports$1) {
|
|
1033
1038
|
const tty = require$$1;
|
|
1034
1039
|
const util = require$$1$1;
|
|
1035
1040
|
|
|
@@ -1037,13 +1042,13 @@ function requireNode () {
|
|
|
1037
1042
|
* This is the Node.js implementation of `debug()`.
|
|
1038
1043
|
*/
|
|
1039
1044
|
|
|
1040
|
-
exports.init = init;
|
|
1041
|
-
exports.log = log;
|
|
1042
|
-
exports.formatArgs = formatArgs;
|
|
1043
|
-
exports.save = save;
|
|
1044
|
-
exports.load = load;
|
|
1045
|
-
exports.useColors = useColors;
|
|
1046
|
-
exports.destroy = util.deprecate(
|
|
1045
|
+
exports$1.init = init;
|
|
1046
|
+
exports$1.log = log;
|
|
1047
|
+
exports$1.formatArgs = formatArgs;
|
|
1048
|
+
exports$1.save = save;
|
|
1049
|
+
exports$1.load = load;
|
|
1050
|
+
exports$1.useColors = useColors;
|
|
1051
|
+
exports$1.destroy = util.deprecate(
|
|
1047
1052
|
() => {},
|
|
1048
1053
|
'Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.'
|
|
1049
1054
|
);
|
|
@@ -1052,7 +1057,7 @@ function requireNode () {
|
|
|
1052
1057
|
* Colors.
|
|
1053
1058
|
*/
|
|
1054
1059
|
|
|
1055
|
-
exports.colors = [6, 2, 3, 4, 5, 1];
|
|
1060
|
+
exports$1.colors = [6, 2, 3, 4, 5, 1];
|
|
1056
1061
|
|
|
1057
1062
|
try {
|
|
1058
1063
|
// Optional dependency (as in, doesn't need to be installed, NOT like optionalDependencies in package.json)
|
|
@@ -1060,7 +1065,7 @@ function requireNode () {
|
|
|
1060
1065
|
const supportsColor = requireSupportsColor();
|
|
1061
1066
|
|
|
1062
1067
|
if (supportsColor && (supportsColor.stderr || supportsColor).level >= 2) {
|
|
1063
|
-
exports.colors = [
|
|
1068
|
+
exports$1.colors = [
|
|
1064
1069
|
20,
|
|
1065
1070
|
21,
|
|
1066
1071
|
26,
|
|
@@ -1149,7 +1154,7 @@ function requireNode () {
|
|
|
1149
1154
|
* $ DEBUG_COLORS=no DEBUG_DEPTH=10 DEBUG_SHOW_HIDDEN=enabled node script.js
|
|
1150
1155
|
*/
|
|
1151
1156
|
|
|
1152
|
-
exports.inspectOpts = Object.keys(process.env).filter(key => {
|
|
1157
|
+
exports$1.inspectOpts = Object.keys(process.env).filter(key => {
|
|
1153
1158
|
return /^debug_/i.test(key);
|
|
1154
1159
|
}).reduce((obj, key) => {
|
|
1155
1160
|
// Camel-case
|
|
@@ -1181,8 +1186,8 @@ function requireNode () {
|
|
|
1181
1186
|
*/
|
|
1182
1187
|
|
|
1183
1188
|
function useColors() {
|
|
1184
|
-
return 'colors' in exports.inspectOpts ?
|
|
1185
|
-
Boolean(exports.inspectOpts.colors) :
|
|
1189
|
+
return 'colors' in exports$1.inspectOpts ?
|
|
1190
|
+
Boolean(exports$1.inspectOpts.colors) :
|
|
1186
1191
|
tty.isatty(process.stderr.fd);
|
|
1187
1192
|
}
|
|
1188
1193
|
|
|
@@ -1208,7 +1213,7 @@ function requireNode () {
|
|
|
1208
1213
|
}
|
|
1209
1214
|
|
|
1210
1215
|
function getDate() {
|
|
1211
|
-
if (exports.inspectOpts.hideDate) {
|
|
1216
|
+
if (exports$1.inspectOpts.hideDate) {
|
|
1212
1217
|
return '';
|
|
1213
1218
|
}
|
|
1214
1219
|
return new Date().toISOString() + ' ';
|
|
@@ -1219,7 +1224,7 @@ function requireNode () {
|
|
|
1219
1224
|
*/
|
|
1220
1225
|
|
|
1221
1226
|
function log(...args) {
|
|
1222
|
-
return process.stderr.write(util.formatWithOptions(exports.inspectOpts, ...args) + '\n');
|
|
1227
|
+
return process.stderr.write(util.formatWithOptions(exports$1.inspectOpts, ...args) + '\n');
|
|
1223
1228
|
}
|
|
1224
1229
|
|
|
1225
1230
|
/**
|
|
@@ -1259,13 +1264,13 @@ function requireNode () {
|
|
|
1259
1264
|
function init(debug) {
|
|
1260
1265
|
debug.inspectOpts = {};
|
|
1261
1266
|
|
|
1262
|
-
const keys = Object.keys(exports.inspectOpts);
|
|
1267
|
+
const keys = Object.keys(exports$1.inspectOpts);
|
|
1263
1268
|
for (let i = 0; i < keys.length; i++) {
|
|
1264
|
-
debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]];
|
|
1269
|
+
debug.inspectOpts[keys[i]] = exports$1.inspectOpts[keys[i]];
|
|
1265
1270
|
}
|
|
1266
1271
|
}
|
|
1267
1272
|
|
|
1268
|
-
module.exports = requireCommon$1()(exports);
|
|
1273
|
+
module.exports = requireCommon$1()(exports$1);
|
|
1269
1274
|
|
|
1270
1275
|
const {formatters} = module.exports;
|
|
1271
1276
|
|
|
@@ -3354,27 +3359,6 @@ var ipv4 = {};
|
|
|
3354
3359
|
|
|
3355
3360
|
var common = {};
|
|
3356
3361
|
|
|
3357
|
-
var addressError = {};
|
|
3358
|
-
|
|
3359
|
-
var hasRequiredAddressError;
|
|
3360
|
-
|
|
3361
|
-
function requireAddressError () {
|
|
3362
|
-
if (hasRequiredAddressError) return addressError;
|
|
3363
|
-
hasRequiredAddressError = 1;
|
|
3364
|
-
Object.defineProperty(addressError, "__esModule", { value: true });
|
|
3365
|
-
addressError.AddressError = void 0;
|
|
3366
|
-
class AddressError extends Error {
|
|
3367
|
-
constructor(message, parseMessage) {
|
|
3368
|
-
super(message);
|
|
3369
|
-
this.name = 'AddressError';
|
|
3370
|
-
this.parseMessage = parseMessage;
|
|
3371
|
-
}
|
|
3372
|
-
}
|
|
3373
|
-
addressError.AddressError = AddressError;
|
|
3374
|
-
|
|
3375
|
-
return addressError;
|
|
3376
|
-
}
|
|
3377
|
-
|
|
3378
3362
|
var hasRequiredCommon;
|
|
3379
3363
|
|
|
3380
3364
|
function requireCommon () {
|
|
@@ -3383,11 +3367,9 @@ function requireCommon () {
|
|
|
3383
3367
|
Object.defineProperty(common, "__esModule", { value: true });
|
|
3384
3368
|
common.isInSubnet = isInSubnet;
|
|
3385
3369
|
common.isCorrect = isCorrect;
|
|
3386
|
-
common.prefixLengthFromMask = prefixLengthFromMask;
|
|
3387
3370
|
common.numberToPaddedHex = numberToPaddedHex;
|
|
3388
3371
|
common.stringToPaddedHex = stringToPaddedHex;
|
|
3389
3372
|
common.testBit = testBit;
|
|
3390
|
-
const address_error_1 = /*@__PURE__*/ requireAddressError();
|
|
3391
3373
|
function isInSubnet(address) {
|
|
3392
3374
|
if (this.subnetMask < address.subnetMask) {
|
|
3393
3375
|
return false;
|
|
@@ -3408,25 +3390,6 @@ function requireCommon () {
|
|
|
3408
3390
|
return this.parsedSubnet === String(this.subnetMask);
|
|
3409
3391
|
};
|
|
3410
3392
|
}
|
|
3411
|
-
/**
|
|
3412
|
-
* Returns the prefix length (number of leading 1 bits) of a contiguous
|
|
3413
|
-
* subnet mask. Throws `AddressError` if the mask is non-contiguous (e.g.
|
|
3414
|
-
* `255.0.255.0`).
|
|
3415
|
-
*/
|
|
3416
|
-
function prefixLengthFromMask(value, totalBits) {
|
|
3417
|
-
const binary = value.toString(2).padStart(totalBits, '0');
|
|
3418
|
-
if (binary.length > totalBits) {
|
|
3419
|
-
throw new address_error_1.AddressError('Invalid subnet mask.');
|
|
3420
|
-
}
|
|
3421
|
-
const firstZero = binary.indexOf('0');
|
|
3422
|
-
if (firstZero === -1) {
|
|
3423
|
-
return totalBits;
|
|
3424
|
-
}
|
|
3425
|
-
if (binary.slice(firstZero).includes('1')) {
|
|
3426
|
-
throw new address_error_1.AddressError('Invalid subnet mask.');
|
|
3427
|
-
}
|
|
3428
|
-
return firstZero;
|
|
3429
|
-
}
|
|
3430
3393
|
function numberToPaddedHex(number) {
|
|
3431
3394
|
return number.toString(16).padStart(2, '0');
|
|
3432
3395
|
}
|
|
@@ -3466,6 +3429,27 @@ function requireConstants$1 () {
|
|
|
3466
3429
|
return constants$1;
|
|
3467
3430
|
}
|
|
3468
3431
|
|
|
3432
|
+
var addressError = {};
|
|
3433
|
+
|
|
3434
|
+
var hasRequiredAddressError;
|
|
3435
|
+
|
|
3436
|
+
function requireAddressError () {
|
|
3437
|
+
if (hasRequiredAddressError) return addressError;
|
|
3438
|
+
hasRequiredAddressError = 1;
|
|
3439
|
+
Object.defineProperty(addressError, "__esModule", { value: true });
|
|
3440
|
+
addressError.AddressError = void 0;
|
|
3441
|
+
class AddressError extends Error {
|
|
3442
|
+
constructor(message, parseMessage) {
|
|
3443
|
+
super(message);
|
|
3444
|
+
this.name = 'AddressError';
|
|
3445
|
+
this.parseMessage = parseMessage;
|
|
3446
|
+
}
|
|
3447
|
+
}
|
|
3448
|
+
addressError.AddressError = AddressError;
|
|
3449
|
+
|
|
3450
|
+
return addressError;
|
|
3451
|
+
}
|
|
3452
|
+
|
|
3469
3453
|
var hasRequiredIpv4;
|
|
3470
3454
|
|
|
3471
3455
|
function requireIpv4 () {
|
|
@@ -3497,12 +3481,12 @@ function requireIpv4 () {
|
|
|
3497
3481
|
};
|
|
3498
3482
|
Object.defineProperty(ipv4, "__esModule", { value: true });
|
|
3499
3483
|
ipv4.Address4 = void 0;
|
|
3500
|
-
const common = __importStar(
|
|
3501
|
-
const constants = __importStar(
|
|
3502
|
-
const address_error_1 =
|
|
3503
|
-
const isCorrect4 = common.isCorrect(constants.BITS);
|
|
3484
|
+
const common = __importStar(requireCommon());
|
|
3485
|
+
const constants = __importStar(requireConstants$1());
|
|
3486
|
+
const address_error_1 = requireAddressError();
|
|
3504
3487
|
/**
|
|
3505
3488
|
* Represents an IPv4 address
|
|
3489
|
+
* @class Address4
|
|
3506
3490
|
* @param {string} address - An IPv4 address string
|
|
3507
3491
|
*/
|
|
3508
3492
|
class Address4 {
|
|
@@ -3515,11 +3499,15 @@ function requireIpv4 () {
|
|
|
3515
3499
|
this.v4 = true;
|
|
3516
3500
|
/**
|
|
3517
3501
|
* Returns true if the address is correct, false otherwise
|
|
3502
|
+
* @memberof Address4
|
|
3503
|
+
* @instance
|
|
3518
3504
|
* @returns {Boolean}
|
|
3519
3505
|
*/
|
|
3520
|
-
this.isCorrect =
|
|
3506
|
+
this.isCorrect = common.isCorrect(constants.BITS);
|
|
3521
3507
|
/**
|
|
3522
3508
|
* Returns true if the given address is in the subnet of the current address
|
|
3509
|
+
* @memberof Address4
|
|
3510
|
+
* @instance
|
|
3523
3511
|
* @returns {boolean}
|
|
3524
3512
|
*/
|
|
3525
3513
|
this.isInSubnet = common.isInSubnet;
|
|
@@ -3537,13 +3525,6 @@ function requireIpv4 () {
|
|
|
3537
3525
|
this.addressMinusSuffix = address;
|
|
3538
3526
|
this.parsedAddress = this.parse(address);
|
|
3539
3527
|
}
|
|
3540
|
-
/**
|
|
3541
|
-
* Returns true if the given string is a valid IPv4 address (with optional
|
|
3542
|
-
* CIDR subnet), false otherwise. Host bits in the subnet portion are
|
|
3543
|
-
* allowed (e.g. `192.168.1.5/24` is valid); for strict network-address
|
|
3544
|
-
* validation compare `correctForm()` to `startAddress().correctForm()`,
|
|
3545
|
-
* or use `networkForm()`.
|
|
3546
|
-
*/
|
|
3547
3528
|
static isValid(address) {
|
|
3548
3529
|
try {
|
|
3549
3530
|
// eslint-disable-next-line no-new
|
|
@@ -3554,11 +3535,8 @@ function requireIpv4 () {
|
|
|
3554
3535
|
return false;
|
|
3555
3536
|
}
|
|
3556
3537
|
}
|
|
3557
|
-
|
|
3558
|
-
* Parses
|
|
3559
|
-
* result on `this.parsedAddress`. Called automatically by the constructor;
|
|
3560
|
-
* you typically don't need to call it directly. Throws `AddressError` if
|
|
3561
|
-
* the input is not a valid IPv4 address.
|
|
3538
|
+
/*
|
|
3539
|
+
* Parses a v4 address
|
|
3562
3540
|
*/
|
|
3563
3541
|
parse(address) {
|
|
3564
3542
|
const groups = address.split('.');
|
|
@@ -3568,110 +3546,45 @@ function requireIpv4 () {
|
|
|
3568
3546
|
return groups;
|
|
3569
3547
|
}
|
|
3570
3548
|
/**
|
|
3571
|
-
* Returns the
|
|
3572
|
-
*
|
|
3573
|
-
*
|
|
3549
|
+
* Returns the correct form of an address
|
|
3550
|
+
* @memberof Address4
|
|
3551
|
+
* @instance
|
|
3552
|
+
* @returns {String}
|
|
3574
3553
|
*/
|
|
3575
3554
|
correctForm() {
|
|
3576
3555
|
return this.parsedAddress.map((part) => parseInt(part, 10)).join('.');
|
|
3577
3556
|
}
|
|
3578
3557
|
/**
|
|
3579
|
-
*
|
|
3580
|
-
*
|
|
3581
|
-
*
|
|
3582
|
-
* non-contiguous (e.g. `255.0.255.0`).
|
|
3583
|
-
* @example
|
|
3584
|
-
* var address = Address4.fromAddressAndMask('192.168.1.1', '255.255.255.0');
|
|
3585
|
-
* address.subnetMask; // 24
|
|
3586
|
-
*/
|
|
3587
|
-
static fromAddressAndMask(address, mask) {
|
|
3588
|
-
const bits = common.prefixLengthFromMask(new Address4(mask).bigInt(), constants.BITS);
|
|
3589
|
-
return new Address4(`${address}/${bits}`);
|
|
3590
|
-
}
|
|
3591
|
-
/**
|
|
3592
|
-
* Construct an `Address4` from an address and a Cisco-style wildcard mask
|
|
3593
|
-
* given as separate strings (e.g. `0.0.0.255` for a `/24`). The wildcard
|
|
3594
|
-
* mask is the bitwise inverse of the subnet mask. Throws `AddressError`
|
|
3595
|
-
* if the mask is non-contiguous (e.g. `0.255.0.255`).
|
|
3596
|
-
* @example
|
|
3597
|
-
* var address = Address4.fromAddressAndWildcardMask('10.0.0.1', '0.0.0.255');
|
|
3598
|
-
* address.subnetMask; // 24
|
|
3599
|
-
*/
|
|
3600
|
-
static fromAddressAndWildcardMask(address, wildcardMask) {
|
|
3601
|
-
const wildcard = new Address4(wildcardMask).bigInt();
|
|
3602
|
-
const allOnes = (BigInt(1) << BigInt(constants.BITS)) - BigInt(1);
|
|
3603
|
-
// eslint-disable-next-line no-bitwise
|
|
3604
|
-
const mask = wildcard ^ allOnes;
|
|
3605
|
-
const bits = common.prefixLengthFromMask(mask, constants.BITS);
|
|
3606
|
-
return new Address4(`${address}/${bits}`);
|
|
3607
|
-
}
|
|
3608
|
-
/**
|
|
3609
|
-
* Construct an `Address4` from a wildcard pattern with trailing `*`
|
|
3610
|
-
* octets. The number of trailing wildcards determines the prefix
|
|
3611
|
-
* length: each `*` represents 8 bits.
|
|
3612
|
-
*
|
|
3613
|
-
* Only trailing whole-octet wildcards are supported. Partial-octet
|
|
3614
|
-
* wildcards (e.g. `192.168.0.1*`) and interior wildcards (e.g.
|
|
3615
|
-
* `192.*.0.1`) throw `AddressError`.
|
|
3616
|
-
* @example
|
|
3617
|
-
* Address4.fromWildcard('192.168.0.*').subnet; // '/24'
|
|
3618
|
-
* Address4.fromWildcard('192.168.*.*').subnet; // '/16'
|
|
3619
|
-
* Address4.fromWildcard('*.*.*.*').subnet; // '/0'
|
|
3620
|
-
*/
|
|
3621
|
-
static fromWildcard(input) {
|
|
3622
|
-
const groups = input.split('.');
|
|
3623
|
-
if (groups.length !== constants.GROUPS) {
|
|
3624
|
-
throw new address_error_1.AddressError('Wildcard pattern must have 4 octets');
|
|
3625
|
-
}
|
|
3626
|
-
let firstWildcard = -1;
|
|
3627
|
-
for (let i = 0; i < groups.length; i++) {
|
|
3628
|
-
if (groups[i] === '*') {
|
|
3629
|
-
if (firstWildcard === -1) {
|
|
3630
|
-
firstWildcard = i;
|
|
3631
|
-
}
|
|
3632
|
-
}
|
|
3633
|
-
else if (firstWildcard !== -1) {
|
|
3634
|
-
throw new address_error_1.AddressError('Wildcard `*` must only appear in trailing octets (e.g. `192.168.0.*`)');
|
|
3635
|
-
}
|
|
3636
|
-
}
|
|
3637
|
-
const trailing = firstWildcard === -1 ? 0 : groups.length - firstWildcard;
|
|
3638
|
-
const replaced = groups.map((g) => (g === '*' ? '0' : g));
|
|
3639
|
-
const subnetBits = constants.BITS - trailing * 8;
|
|
3640
|
-
return new Address4(`${replaced.join('.')}/${subnetBits}`);
|
|
3641
|
-
}
|
|
3642
|
-
/**
|
|
3643
|
-
* Converts a hex string to an IPv4 address object. Accepts 8 hex digits
|
|
3644
|
-
* with optional `:` separators (e.g. `'7f000001'` or `'7f:00:00:01'`).
|
|
3645
|
-
* Throws `AddressError` for any other length or for non-hex characters.
|
|
3558
|
+
* Converts a hex string to an IPv4 address object
|
|
3559
|
+
* @memberof Address4
|
|
3560
|
+
* @static
|
|
3646
3561
|
* @param {string} hex - a hex string to convert
|
|
3647
3562
|
* @returns {Address4}
|
|
3648
3563
|
*/
|
|
3649
3564
|
static fromHex(hex) {
|
|
3650
|
-
const
|
|
3651
|
-
if (!/^[0-9a-fA-F]{8}$/.test(stripped)) {
|
|
3652
|
-
throw new address_error_1.AddressError('IPv4 hex must be exactly 8 hex digits');
|
|
3653
|
-
}
|
|
3565
|
+
const padded = hex.replace(/:/g, '').padStart(8, '0');
|
|
3654
3566
|
const groups = [];
|
|
3655
|
-
|
|
3656
|
-
|
|
3567
|
+
let i;
|
|
3568
|
+
for (i = 0; i < 8; i += 2) {
|
|
3569
|
+
const h = padded.slice(i, i + 2);
|
|
3570
|
+
groups.push(parseInt(h, 16));
|
|
3657
3571
|
}
|
|
3658
3572
|
return new Address4(groups.join('.'));
|
|
3659
3573
|
}
|
|
3660
3574
|
/**
|
|
3661
|
-
* Converts an integer into a IPv4 address object
|
|
3662
|
-
*
|
|
3663
|
-
*
|
|
3575
|
+
* Converts an integer into a IPv4 address object
|
|
3576
|
+
* @memberof Address4
|
|
3577
|
+
* @static
|
|
3664
3578
|
* @param {integer} integer - a number to convert
|
|
3665
3579
|
* @returns {Address4}
|
|
3666
3580
|
*/
|
|
3667
3581
|
static fromInteger(integer) {
|
|
3668
|
-
|
|
3669
|
-
throw new address_error_1.AddressError('IPv4 integer must be in the range 0 to 2**32 - 1');
|
|
3670
|
-
}
|
|
3671
|
-
return Address4.fromHex(integer.toString(16).padStart(8, '0'));
|
|
3582
|
+
return Address4.fromHex(integer.toString(16));
|
|
3672
3583
|
}
|
|
3673
3584
|
/**
|
|
3674
3585
|
* Return an address from in-addr.arpa form
|
|
3586
|
+
* @memberof Address4
|
|
3587
|
+
* @static
|
|
3675
3588
|
* @param {string} arpaFormAddress - an 'in-addr.arpa' form ipv4 address
|
|
3676
3589
|
* @returns {Adress4}
|
|
3677
3590
|
* @example
|
|
@@ -3686,15 +3599,17 @@ function requireIpv4 () {
|
|
|
3686
3599
|
}
|
|
3687
3600
|
/**
|
|
3688
3601
|
* Converts an IPv4 address object to a hex string
|
|
3602
|
+
* @memberof Address4
|
|
3603
|
+
* @instance
|
|
3689
3604
|
* @returns {String}
|
|
3690
3605
|
*/
|
|
3691
3606
|
toHex() {
|
|
3692
3607
|
return this.parsedAddress.map((part) => common.stringToPaddedHex(part)).join(':');
|
|
3693
3608
|
}
|
|
3694
3609
|
/**
|
|
3695
|
-
* Converts an IPv4 address object to an array of bytes
|
|
3696
|
-
*
|
|
3697
|
-
*
|
|
3610
|
+
* Converts an IPv4 address object to an array of bytes
|
|
3611
|
+
* @memberof Address4
|
|
3612
|
+
* @instance
|
|
3698
3613
|
* @returns {Array}
|
|
3699
3614
|
*/
|
|
3700
3615
|
toArray() {
|
|
@@ -3702,6 +3617,8 @@ function requireIpv4 () {
|
|
|
3702
3617
|
}
|
|
3703
3618
|
/**
|
|
3704
3619
|
* Converts an IPv4 address object to an IPv6 address group
|
|
3620
|
+
* @memberof Address4
|
|
3621
|
+
* @instance
|
|
3705
3622
|
* @returns {String}
|
|
3706
3623
|
*/
|
|
3707
3624
|
toGroup6() {
|
|
@@ -3714,6 +3631,8 @@ function requireIpv4 () {
|
|
|
3714
3631
|
}
|
|
3715
3632
|
/**
|
|
3716
3633
|
* Returns the address as a `bigint`
|
|
3634
|
+
* @memberof Address4
|
|
3635
|
+
* @instance
|
|
3717
3636
|
* @returns {bigint}
|
|
3718
3637
|
*/
|
|
3719
3638
|
bigInt() {
|
|
@@ -3721,6 +3640,8 @@ function requireIpv4 () {
|
|
|
3721
3640
|
}
|
|
3722
3641
|
/**
|
|
3723
3642
|
* Helper function getting start address.
|
|
3643
|
+
* @memberof Address4
|
|
3644
|
+
* @instance
|
|
3724
3645
|
* @returns {bigint}
|
|
3725
3646
|
*/
|
|
3726
3647
|
_startAddress() {
|
|
@@ -3729,6 +3650,8 @@ function requireIpv4 () {
|
|
|
3729
3650
|
/**
|
|
3730
3651
|
* The first address in the range given by this address' subnet.
|
|
3731
3652
|
* Often referred to as the Network Address.
|
|
3653
|
+
* @memberof Address4
|
|
3654
|
+
* @instance
|
|
3732
3655
|
* @returns {Address4}
|
|
3733
3656
|
*/
|
|
3734
3657
|
startAddress() {
|
|
@@ -3737,6 +3660,8 @@ function requireIpv4 () {
|
|
|
3737
3660
|
/**
|
|
3738
3661
|
* The first host address in the range given by this address's subnet ie
|
|
3739
3662
|
* the first address after the Network Address
|
|
3663
|
+
* @memberof Address4
|
|
3664
|
+
* @instance
|
|
3740
3665
|
* @returns {Address4}
|
|
3741
3666
|
*/
|
|
3742
3667
|
startAddressExclusive() {
|
|
@@ -3745,6 +3670,8 @@ function requireIpv4 () {
|
|
|
3745
3670
|
}
|
|
3746
3671
|
/**
|
|
3747
3672
|
* Helper function getting end address.
|
|
3673
|
+
* @memberof Address4
|
|
3674
|
+
* @instance
|
|
3748
3675
|
* @returns {bigint}
|
|
3749
3676
|
*/
|
|
3750
3677
|
_endAddress() {
|
|
@@ -3753,6 +3680,8 @@ function requireIpv4 () {
|
|
|
3753
3680
|
/**
|
|
3754
3681
|
* The last address in the range given by this address' subnet
|
|
3755
3682
|
* Often referred to as the Broadcast
|
|
3683
|
+
* @memberof Address4
|
|
3684
|
+
* @instance
|
|
3756
3685
|
* @returns {Address4}
|
|
3757
3686
|
*/
|
|
3758
3687
|
endAddress() {
|
|
@@ -3761,6 +3690,8 @@ function requireIpv4 () {
|
|
|
3761
3690
|
/**
|
|
3762
3691
|
* The last host address in the range given by this address's subnet ie
|
|
3763
3692
|
* the last address prior to the Broadcast Address
|
|
3693
|
+
* @memberof Address4
|
|
3694
|
+
* @instance
|
|
3764
3695
|
* @returns {Address4}
|
|
3765
3696
|
*/
|
|
3766
3697
|
endAddressExclusive() {
|
|
@@ -3768,47 +3699,19 @@ function requireIpv4 () {
|
|
|
3768
3699
|
return Address4.fromBigInt(this._endAddress() - adjust);
|
|
3769
3700
|
}
|
|
3770
3701
|
/**
|
|
3771
|
-
*
|
|
3772
|
-
*
|
|
3773
|
-
* @
|
|
3774
|
-
*/
|
|
3775
|
-
subnetMaskAddress() {
|
|
3776
|
-
return Address4.fromBigInt(BigInt(`0b${'1'.repeat(this.subnetMask)}${'0'.repeat(constants.BITS - this.subnetMask)}`));
|
|
3777
|
-
}
|
|
3778
|
-
/**
|
|
3779
|
-
* The Cisco-style wildcard mask, e.g. `0.0.0.255` for a `/24`. This is
|
|
3780
|
-
* the bitwise inverse of `subnetMaskAddress()`. Returns an `Address4`;
|
|
3781
|
-
* call `.correctForm()` for the string.
|
|
3782
|
-
* @returns {Address4}
|
|
3783
|
-
*/
|
|
3784
|
-
wildcardMask() {
|
|
3785
|
-
return Address4.fromBigInt(BigInt(`0b${'0'.repeat(this.subnetMask)}${'1'.repeat(constants.BITS - this.subnetMask)}`));
|
|
3786
|
-
}
|
|
3787
|
-
/**
|
|
3788
|
-
* The network address in CIDR string form, e.g. `192.168.1.0/24` for
|
|
3789
|
-
* `192.168.1.5/24`. For an address with no explicit subnet the prefix is
|
|
3790
|
-
* `/32`, e.g. `networkForm()` on `192.168.1.5` returns `192.168.1.5/32`.
|
|
3791
|
-
* @returns {string}
|
|
3792
|
-
*/
|
|
3793
|
-
networkForm() {
|
|
3794
|
-
return `${this.startAddress().correctForm()}/${this.subnetMask}`;
|
|
3795
|
-
}
|
|
3796
|
-
/**
|
|
3797
|
-
* Converts a BigInt to a v4 address object. The value must be in the
|
|
3798
|
-
* range `[0, 2**32 - 1]`; otherwise `AddressError` is thrown.
|
|
3702
|
+
* Converts a BigInt to a v4 address object
|
|
3703
|
+
* @memberof Address4
|
|
3704
|
+
* @static
|
|
3799
3705
|
* @param {bigint} bigInt - a BigInt to convert
|
|
3800
3706
|
* @returns {Address4}
|
|
3801
3707
|
*/
|
|
3802
3708
|
static fromBigInt(bigInt) {
|
|
3803
|
-
|
|
3804
|
-
throw new address_error_1.AddressError('IPv4 BigInt must be in the range 0 to 2**32 - 1');
|
|
3805
|
-
}
|
|
3806
|
-
return Address4.fromHex(bigInt.toString(16).padStart(8, '0'));
|
|
3709
|
+
return Address4.fromHex(bigInt.toString(16));
|
|
3807
3710
|
}
|
|
3808
3711
|
/**
|
|
3809
|
-
* Convert a byte array to an Address4 object
|
|
3810
|
-
*
|
|
3811
|
-
*
|
|
3712
|
+
* Convert a byte array to an Address4 object
|
|
3713
|
+
* @memberof Address4
|
|
3714
|
+
* @static
|
|
3812
3715
|
* @param {Array<number>} bytes - an array of 4 bytes (0-255)
|
|
3813
3716
|
* @returns {Address4}
|
|
3814
3717
|
*/
|
|
@@ -3826,6 +3729,8 @@ function requireIpv4 () {
|
|
|
3826
3729
|
}
|
|
3827
3730
|
/**
|
|
3828
3731
|
* Convert an unsigned byte array to an Address4 object
|
|
3732
|
+
* @memberof Address4
|
|
3733
|
+
* @static
|
|
3829
3734
|
* @param {Array<number>} bytes - an array of 4 unsigned bytes (0-255)
|
|
3830
3735
|
* @returns {Address4}
|
|
3831
3736
|
*/
|
|
@@ -3839,6 +3744,8 @@ function requireIpv4 () {
|
|
|
3839
3744
|
/**
|
|
3840
3745
|
* Returns the first n bits of the address, defaulting to the
|
|
3841
3746
|
* subnet mask
|
|
3747
|
+
* @memberof Address4
|
|
3748
|
+
* @instance
|
|
3842
3749
|
* @returns {String}
|
|
3843
3750
|
*/
|
|
3844
3751
|
mask(mask) {
|
|
@@ -3849,6 +3756,8 @@ function requireIpv4 () {
|
|
|
3849
3756
|
}
|
|
3850
3757
|
/**
|
|
3851
3758
|
* Returns the bits in the given range as a base-2 string
|
|
3759
|
+
* @memberof Address4
|
|
3760
|
+
* @instance
|
|
3852
3761
|
* @returns {string}
|
|
3853
3762
|
*/
|
|
3854
3763
|
getBitsBase2(start, end) {
|
|
@@ -3856,8 +3765,10 @@ function requireIpv4 () {
|
|
|
3856
3765
|
}
|
|
3857
3766
|
/**
|
|
3858
3767
|
* Return the reversed ip6.arpa form of the address
|
|
3768
|
+
* @memberof Address4
|
|
3859
3769
|
* @param {Object} options
|
|
3860
3770
|
* @param {boolean} options.omitSuffix - omit the "in-addr.arpa" suffix
|
|
3771
|
+
* @instance
|
|
3861
3772
|
* @returns {String}
|
|
3862
3773
|
*/
|
|
3863
3774
|
reverseForm(options) {
|
|
@@ -3872,62 +3783,21 @@ function requireIpv4 () {
|
|
|
3872
3783
|
}
|
|
3873
3784
|
/**
|
|
3874
3785
|
* Returns true if the given address is a multicast address
|
|
3786
|
+
* @memberof Address4
|
|
3787
|
+
* @instance
|
|
3875
3788
|
* @returns {boolean}
|
|
3876
3789
|
*/
|
|
3877
3790
|
isMulticast() {
|
|
3878
|
-
return this.isInSubnet(
|
|
3879
|
-
}
|
|
3880
|
-
/**
|
|
3881
|
-
* Returns true if the address is in one of the [RFC 1918](https://datatracker.ietf.org/doc/html/rfc1918) private address ranges (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`).
|
|
3882
|
-
* @returns {boolean}
|
|
3883
|
-
*/
|
|
3884
|
-
isPrivate() {
|
|
3885
|
-
return PRIVATE_V4.some((subnet) => this.isInSubnet(subnet));
|
|
3886
|
-
}
|
|
3887
|
-
/**
|
|
3888
|
-
* Returns true if the address is in the loopback range `127.0.0.0/8` ([RFC 1122](https://datatracker.ietf.org/doc/html/rfc1122)).
|
|
3889
|
-
* @returns {boolean}
|
|
3890
|
-
*/
|
|
3891
|
-
isLoopback() {
|
|
3892
|
-
return this.isInSubnet(LOOPBACK_V4);
|
|
3893
|
-
}
|
|
3894
|
-
/**
|
|
3895
|
-
* Returns true if the address is in the link-local range `169.254.0.0/16` ([RFC 3927](https://datatracker.ietf.org/doc/html/rfc3927)).
|
|
3896
|
-
* @returns {boolean}
|
|
3897
|
-
*/
|
|
3898
|
-
isLinkLocal() {
|
|
3899
|
-
return this.isInSubnet(LINK_LOCAL_V4);
|
|
3900
|
-
}
|
|
3901
|
-
/**
|
|
3902
|
-
* Returns true if the address is the unspecified address `0.0.0.0`.
|
|
3903
|
-
* @returns {boolean}
|
|
3904
|
-
*/
|
|
3905
|
-
isUnspecified() {
|
|
3906
|
-
return this.isInSubnet(UNSPECIFIED_V4);
|
|
3907
|
-
}
|
|
3908
|
-
/**
|
|
3909
|
-
* Returns true if the address is the limited broadcast address `255.255.255.255` ([RFC 919](https://datatracker.ietf.org/doc/html/rfc919)).
|
|
3910
|
-
* @returns {boolean}
|
|
3911
|
-
*/
|
|
3912
|
-
isBroadcast() {
|
|
3913
|
-
return this.isInSubnet(BROADCAST_V4);
|
|
3914
|
-
}
|
|
3915
|
-
/**
|
|
3916
|
-
* Returns true if the address is in the carrier-grade NAT range `100.64.0.0/10` ([RFC 6598](https://datatracker.ietf.org/doc/html/rfc6598)).
|
|
3917
|
-
* @returns {boolean}
|
|
3918
|
-
*/
|
|
3919
|
-
isCGNAT() {
|
|
3920
|
-
return this.isInSubnet(CGNAT_V4);
|
|
3791
|
+
return this.isInSubnet(new Address4('224.0.0.0/4'));
|
|
3921
3792
|
}
|
|
3922
3793
|
/**
|
|
3923
3794
|
* Returns a zero-padded base-2 string representation of the address
|
|
3795
|
+
* @memberof Address4
|
|
3796
|
+
* @instance
|
|
3924
3797
|
* @returns {string}
|
|
3925
3798
|
*/
|
|
3926
3799
|
binaryZeroPad() {
|
|
3927
|
-
|
|
3928
|
-
this._binaryZeroPad = this.bigInt().toString(2).padStart(constants.BITS, '0');
|
|
3929
|
-
}
|
|
3930
|
-
return this._binaryZeroPad;
|
|
3800
|
+
return this.bigInt().toString(2).padStart(constants.BITS, '0');
|
|
3931
3801
|
}
|
|
3932
3802
|
/**
|
|
3933
3803
|
* Groups an IPv4 address for inclusion at the end of an IPv6 address
|
|
@@ -3943,17 +3813,6 @@ function requireIpv4 () {
|
|
|
3943
3813
|
}
|
|
3944
3814
|
}
|
|
3945
3815
|
ipv4.Address4 = Address4;
|
|
3946
|
-
const MULTICAST_V4 = new Address4('224.0.0.0/4');
|
|
3947
|
-
const PRIVATE_V4 = [
|
|
3948
|
-
new Address4('10.0.0.0/8'),
|
|
3949
|
-
new Address4('172.16.0.0/12'),
|
|
3950
|
-
new Address4('192.168.0.0/16'),
|
|
3951
|
-
];
|
|
3952
|
-
const LOOPBACK_V4 = new Address4('127.0.0.0/8');
|
|
3953
|
-
const LINK_LOCAL_V4 = new Address4('169.254.0.0/16');
|
|
3954
|
-
const UNSPECIFIED_V4 = new Address4('0.0.0.0/32');
|
|
3955
|
-
const BROADCAST_V4 = new Address4('255.255.255.255/32');
|
|
3956
|
-
const CGNAT_V4 = new Address4('100.64.0.0/10');
|
|
3957
3816
|
|
|
3958
3817
|
return ipv4;
|
|
3959
3818
|
}
|
|
@@ -4014,11 +3873,6 @@ function requireConstants () {
|
|
|
4014
3873
|
'::1/128': 'Loopback',
|
|
4015
3874
|
'ff00::/8': 'Multicast',
|
|
4016
3875
|
'fe80::/10': 'Link-local unicast',
|
|
4017
|
-
'fc00::/7': 'Unique local',
|
|
4018
|
-
'2002::/16': '6to4',
|
|
4019
|
-
'2001:db8::/32': 'Documentation',
|
|
4020
|
-
'64:ff9b::/96': 'NAT64 (well-known)',
|
|
4021
|
-
'64:ff9b:1::/48': 'NAT64 (local-use)',
|
|
4022
3876
|
};
|
|
4023
3877
|
/**
|
|
4024
3878
|
* A regular expression that matches bad characters in an IPv6 address
|
|
@@ -4058,24 +3912,15 @@ function requireHelpers$1 () {
|
|
|
4058
3912
|
if (hasRequiredHelpers$1) return helpers;
|
|
4059
3913
|
hasRequiredHelpers$1 = 1;
|
|
4060
3914
|
Object.defineProperty(helpers, "__esModule", { value: true });
|
|
4061
|
-
helpers.escapeHtml = escapeHtml;
|
|
4062
3915
|
helpers.spanAllZeroes = spanAllZeroes;
|
|
4063
3916
|
helpers.spanAll = spanAll;
|
|
4064
3917
|
helpers.spanLeadingZeroes = spanLeadingZeroes;
|
|
4065
3918
|
helpers.simpleGroup = simpleGroup;
|
|
4066
|
-
function escapeHtml(s) {
|
|
4067
|
-
return s
|
|
4068
|
-
.replace(/&/g, '&')
|
|
4069
|
-
.replace(/</g, '<')
|
|
4070
|
-
.replace(/>/g, '>')
|
|
4071
|
-
.replace(/"/g, '"')
|
|
4072
|
-
.replace(/'/g, ''');
|
|
4073
|
-
}
|
|
4074
3919
|
/**
|
|
4075
3920
|
* @returns {String} the string with all zeroes contained in a <span>
|
|
4076
3921
|
*/
|
|
4077
3922
|
function spanAllZeroes(s) {
|
|
4078
|
-
return
|
|
3923
|
+
return s.replace(/(0+)/g, '<span class="zero">$1</span>');
|
|
4079
3924
|
}
|
|
4080
3925
|
/**
|
|
4081
3926
|
* @returns {String} the string with each character contained in a <span>
|
|
@@ -4083,11 +3928,11 @@ function requireHelpers$1 () {
|
|
|
4083
3928
|
function spanAll(s, offset = 0) {
|
|
4084
3929
|
const letters = s.split('');
|
|
4085
3930
|
return letters
|
|
4086
|
-
.map((n, i) => `<span class="digit value-${
|
|
3931
|
+
.map((n, i) => `<span class="digit value-${n} position-${i + offset}">${spanAllZeroes(n)}</span>`)
|
|
4087
3932
|
.join('');
|
|
4088
3933
|
}
|
|
4089
3934
|
function spanLeadingZeroesSimple(group) {
|
|
4090
|
-
return
|
|
3935
|
+
return group.replace(/^(0+)/, '<span class="zero">$1</span>');
|
|
4091
3936
|
}
|
|
4092
3937
|
/**
|
|
4093
3938
|
* @returns {String} the string with leading zeroes contained in a <span>
|
|
@@ -4149,7 +3994,7 @@ function requireRegularExpressions () {
|
|
|
4149
3994
|
regularExpressions.padGroup = padGroup;
|
|
4150
3995
|
regularExpressions.simpleRegularExpression = simpleRegularExpression;
|
|
4151
3996
|
regularExpressions.possibleElisions = possibleElisions;
|
|
4152
|
-
const v6 = __importStar(
|
|
3997
|
+
const v6 = __importStar(requireConstants());
|
|
4153
3998
|
function groupPossibilities(possibilities) {
|
|
4154
3999
|
return `(${possibilities.join('|')})`;
|
|
4155
4000
|
}
|
|
@@ -4249,15 +4094,14 @@ function requireIpv6 () {
|
|
|
4249
4094
|
};
|
|
4250
4095
|
Object.defineProperty(ipv6, "__esModule", { value: true });
|
|
4251
4096
|
ipv6.Address6 = void 0;
|
|
4252
|
-
const common = __importStar(
|
|
4253
|
-
const constants4 = __importStar(
|
|
4254
|
-
const constants6 = __importStar(
|
|
4255
|
-
const helpers = __importStar(
|
|
4256
|
-
const ipv4_1 =
|
|
4257
|
-
const regular_expressions_1 =
|
|
4258
|
-
const address_error_1 =
|
|
4259
|
-
const common_1 =
|
|
4260
|
-
const isCorrect6 = common.isCorrect(constants6.BITS);
|
|
4097
|
+
const common = __importStar(requireCommon());
|
|
4098
|
+
const constants4 = __importStar(requireConstants$1());
|
|
4099
|
+
const constants6 = __importStar(requireConstants());
|
|
4100
|
+
const helpers = __importStar(requireHelpers$1());
|
|
4101
|
+
const ipv4_1 = requireIpv4();
|
|
4102
|
+
const regular_expressions_1 = requireRegularExpressions();
|
|
4103
|
+
const address_error_1 = requireAddressError();
|
|
4104
|
+
const common_1 = requireCommon();
|
|
4261
4105
|
function assert(condition) {
|
|
4262
4106
|
if (!condition) {
|
|
4263
4107
|
throw new Error('Assertion failed.');
|
|
@@ -4301,6 +4145,7 @@ function requireIpv6 () {
|
|
|
4301
4145
|
}
|
|
4302
4146
|
/**
|
|
4303
4147
|
* Represents an IPv6 address
|
|
4148
|
+
* @class Address6
|
|
4304
4149
|
* @param {string} address - An IPv6 address string
|
|
4305
4150
|
* @param {number} [groups=8] - How many octets to parse
|
|
4306
4151
|
* @example
|
|
@@ -4317,14 +4162,18 @@ function requireIpv6 () {
|
|
|
4317
4162
|
// #region Attributes
|
|
4318
4163
|
/**
|
|
4319
4164
|
* Returns true if the given address is in the subnet of the current address
|
|
4165
|
+
* @memberof Address6
|
|
4166
|
+
* @instance
|
|
4320
4167
|
* @returns {boolean}
|
|
4321
4168
|
*/
|
|
4322
4169
|
this.isInSubnet = common.isInSubnet;
|
|
4323
4170
|
/**
|
|
4324
4171
|
* Returns true if the address is correct, false otherwise
|
|
4172
|
+
* @memberof Address6
|
|
4173
|
+
* @instance
|
|
4325
4174
|
* @returns {boolean}
|
|
4326
4175
|
*/
|
|
4327
|
-
this.isCorrect =
|
|
4176
|
+
this.isCorrect = common.isCorrect(constants6.BITS);
|
|
4328
4177
|
if (optionalGroups === undefined) {
|
|
4329
4178
|
this.groups = constants6.GROUPS;
|
|
4330
4179
|
}
|
|
@@ -4355,13 +4204,6 @@ function requireIpv6 () {
|
|
|
4355
4204
|
this.addressMinusSuffix = address;
|
|
4356
4205
|
this.parsedAddress = this.parse(this.addressMinusSuffix);
|
|
4357
4206
|
}
|
|
4358
|
-
/**
|
|
4359
|
-
* Returns true if the given string is a valid IPv6 address (with optional
|
|
4360
|
-
* CIDR subnet and zone identifier), false otherwise. Host bits in the
|
|
4361
|
-
* subnet portion are allowed (e.g. `2001:db8::1/32` is valid); for strict
|
|
4362
|
-
* network-address validation compare `correctForm()` to
|
|
4363
|
-
* `startAddress().correctForm()`, or use `networkForm()`.
|
|
4364
|
-
*/
|
|
4365
4207
|
static isValid(address) {
|
|
4366
4208
|
try {
|
|
4367
4209
|
// eslint-disable-next-line no-new
|
|
@@ -4373,8 +4215,9 @@ function requireIpv6 () {
|
|
|
4373
4215
|
}
|
|
4374
4216
|
}
|
|
4375
4217
|
/**
|
|
4376
|
-
* Convert a BigInt to a v6 address object
|
|
4377
|
-
*
|
|
4218
|
+
* Convert a BigInt to a v6 address object
|
|
4219
|
+
* @memberof Address6
|
|
4220
|
+
* @static
|
|
4378
4221
|
* @param {bigint} bigInt - a BigInt to convert
|
|
4379
4222
|
* @returns {Address6}
|
|
4380
4223
|
* @example
|
|
@@ -4383,21 +4226,19 @@ function requireIpv6 () {
|
|
|
4383
4226
|
* address.correctForm(); // '::e8:d4a5:1000'
|
|
4384
4227
|
*/
|
|
4385
4228
|
static fromBigInt(bigInt) {
|
|
4386
|
-
if (bigInt < 0n || bigInt > (1n << BigInt(constants6.BITS)) - 1n) {
|
|
4387
|
-
throw new address_error_1.AddressError('IPv6 BigInt must be in the range 0 to 2**128 - 1');
|
|
4388
|
-
}
|
|
4389
4229
|
const hex = bigInt.toString(16).padStart(32, '0');
|
|
4390
4230
|
const groups = [];
|
|
4391
|
-
|
|
4231
|
+
let i;
|
|
4232
|
+
for (i = 0; i < constants6.GROUPS; i++) {
|
|
4392
4233
|
groups.push(hex.slice(i * 4, (i + 1) * 4));
|
|
4393
4234
|
}
|
|
4394
4235
|
return new Address6(groups.join(':'));
|
|
4395
4236
|
}
|
|
4396
4237
|
/**
|
|
4397
|
-
*
|
|
4398
|
-
*
|
|
4399
|
-
*
|
|
4400
|
-
*
|
|
4238
|
+
* Convert a URL (with optional port number) to an address object
|
|
4239
|
+
* @memberof Address6
|
|
4240
|
+
* @static
|
|
4241
|
+
* @param {string} url - a URL with optional port number
|
|
4401
4242
|
* @example
|
|
4402
4243
|
* var addressAndPort = Address6.fromURL('http://[ffff::]:8080/foo/');
|
|
4403
4244
|
* addressAndPort.address.correctForm(); // 'ffff::'
|
|
@@ -4456,92 +4297,10 @@ function requireIpv6 () {
|
|
|
4456
4297
|
port,
|
|
4457
4298
|
};
|
|
4458
4299
|
}
|
|
4459
|
-
/**
|
|
4460
|
-
* Construct an `Address6` from an address and a hex subnet mask given as
|
|
4461
|
-
* separate strings (e.g. as returned by Node's `os.networkInterfaces()`).
|
|
4462
|
-
* Throws `AddressError` if the mask is non-contiguous (e.g.
|
|
4463
|
-
* `ffff::ffff`).
|
|
4464
|
-
* @example
|
|
4465
|
-
* var address = Address6.fromAddressAndMask('fe80::1', 'ffff:ffff:ffff:ffff::');
|
|
4466
|
-
* address.subnetMask; // 64
|
|
4467
|
-
*/
|
|
4468
|
-
static fromAddressAndMask(address, mask) {
|
|
4469
|
-
const bits = common.prefixLengthFromMask(new Address6(mask).bigInt(), constants6.BITS);
|
|
4470
|
-
return new Address6(`${address}/${bits}`);
|
|
4471
|
-
}
|
|
4472
|
-
/**
|
|
4473
|
-
* Construct an `Address6` from an address and a Cisco-style wildcard mask
|
|
4474
|
-
* given as separate strings (e.g. `::ffff:ffff:ffff:ffff` for a `/64`).
|
|
4475
|
-
* The wildcard mask is the bitwise inverse of the subnet mask. Throws
|
|
4476
|
-
* `AddressError` if the mask is non-contiguous.
|
|
4477
|
-
* @example
|
|
4478
|
-
* var address = Address6.fromAddressAndWildcardMask('fe80::1', '::ffff:ffff:ffff:ffff');
|
|
4479
|
-
* address.subnetMask; // 64
|
|
4480
|
-
*/
|
|
4481
|
-
static fromAddressAndWildcardMask(address, wildcardMask) {
|
|
4482
|
-
const wildcard = new Address6(wildcardMask).bigInt();
|
|
4483
|
-
const allOnes = (BigInt(1) << BigInt(constants6.BITS)) - BigInt(1);
|
|
4484
|
-
// eslint-disable-next-line no-bitwise
|
|
4485
|
-
const mask = wildcard ^ allOnes;
|
|
4486
|
-
const bits = common.prefixLengthFromMask(mask, constants6.BITS);
|
|
4487
|
-
return new Address6(`${address}/${bits}`);
|
|
4488
|
-
}
|
|
4489
|
-
/**
|
|
4490
|
-
* Construct an `Address6` from a wildcard pattern with trailing `*`
|
|
4491
|
-
* groups. The number of trailing wildcards determines the prefix
|
|
4492
|
-
* length: each `*` represents 16 bits. `::` is expanded to zero groups
|
|
4493
|
-
* (not wildcards) before evaluating trailing wildcards.
|
|
4494
|
-
*
|
|
4495
|
-
* Only trailing whole-group wildcards are supported. Partial-group
|
|
4496
|
-
* wildcards (e.g. `2001:db8::0*`) and interior wildcards (e.g.
|
|
4497
|
-
* `*::1`) throw `AddressError`.
|
|
4498
|
-
* @example
|
|
4499
|
-
* Address6.fromWildcard('2001:db8:*:*:*:*:*:*').subnet; // '/32'
|
|
4500
|
-
* Address6.fromWildcard('2001:db8::*').subnet; // '/112'
|
|
4501
|
-
* Address6.fromWildcard('*:*:*:*:*:*:*:*').subnet; // '/0'
|
|
4502
|
-
*/
|
|
4503
|
-
static fromWildcard(input) {
|
|
4504
|
-
if (input.includes('%') || input.includes('/')) {
|
|
4505
|
-
throw new address_error_1.AddressError('Wildcard pattern must not include a zone or CIDR suffix');
|
|
4506
|
-
}
|
|
4507
|
-
const halves = input.split('::');
|
|
4508
|
-
if (halves.length > 2) {
|
|
4509
|
-
throw new address_error_1.AddressError("Wildcard pattern cannot contain more than one '::'");
|
|
4510
|
-
}
|
|
4511
|
-
let groups;
|
|
4512
|
-
if (halves.length === 2) {
|
|
4513
|
-
const left = halves[0] === '' ? [] : halves[0].split(':');
|
|
4514
|
-
const right = halves[1] === '' ? [] : halves[1].split(':');
|
|
4515
|
-
const remaining = constants6.GROUPS - left.length - right.length;
|
|
4516
|
-
if (remaining < 1) {
|
|
4517
|
-
throw new address_error_1.AddressError("Wildcard pattern with '::' has too many groups");
|
|
4518
|
-
}
|
|
4519
|
-
groups = [...left, ...new Array(remaining).fill('0'), ...right];
|
|
4520
|
-
}
|
|
4521
|
-
else {
|
|
4522
|
-
groups = input.split(':');
|
|
4523
|
-
}
|
|
4524
|
-
if (groups.length !== constants6.GROUPS) {
|
|
4525
|
-
throw new address_error_1.AddressError('Wildcard pattern must have 8 groups');
|
|
4526
|
-
}
|
|
4527
|
-
let firstWildcard = -1;
|
|
4528
|
-
for (let i = 0; i < groups.length; i++) {
|
|
4529
|
-
if (groups[i] === '*') {
|
|
4530
|
-
if (firstWildcard === -1) {
|
|
4531
|
-
firstWildcard = i;
|
|
4532
|
-
}
|
|
4533
|
-
}
|
|
4534
|
-
else if (firstWildcard !== -1) {
|
|
4535
|
-
throw new address_error_1.AddressError('Wildcard `*` must only appear in trailing groups (e.g. `2001:db8:*:*:*:*:*:*`)');
|
|
4536
|
-
}
|
|
4537
|
-
}
|
|
4538
|
-
const trailing = firstWildcard === -1 ? 0 : groups.length - firstWildcard;
|
|
4539
|
-
const replaced = groups.map((g) => (g === '*' ? '0' : g));
|
|
4540
|
-
const subnetBits = constants6.BITS - trailing * 16;
|
|
4541
|
-
return new Address6(`${replaced.join(':')}/${subnetBits}`);
|
|
4542
|
-
}
|
|
4543
4300
|
/**
|
|
4544
4301
|
* Create an IPv6-mapped address given an IPv4 address
|
|
4302
|
+
* @memberof Address6
|
|
4303
|
+
* @static
|
|
4545
4304
|
* @param {string} address - An IPv4 address string
|
|
4546
4305
|
* @returns {Address6}
|
|
4547
4306
|
* @example
|
|
@@ -4556,6 +4315,8 @@ function requireIpv6 () {
|
|
|
4556
4315
|
}
|
|
4557
4316
|
/**
|
|
4558
4317
|
* Return an address from ip6.arpa form
|
|
4318
|
+
* @memberof Address6
|
|
4319
|
+
* @static
|
|
4559
4320
|
* @param {string} arpaFormAddress - an 'ip6.arpa' form address
|
|
4560
4321
|
* @returns {Adress6}
|
|
4561
4322
|
* @example
|
|
@@ -4580,6 +4341,8 @@ function requireIpv6 () {
|
|
|
4580
4341
|
}
|
|
4581
4342
|
/**
|
|
4582
4343
|
* Return the Microsoft UNC transcription of the address
|
|
4344
|
+
* @memberof Address6
|
|
4345
|
+
* @instance
|
|
4583
4346
|
* @returns {String} the Microsoft UNC transcription of the address
|
|
4584
4347
|
*/
|
|
4585
4348
|
microsoftTranscription() {
|
|
@@ -4587,6 +4350,8 @@ function requireIpv6 () {
|
|
|
4587
4350
|
}
|
|
4588
4351
|
/**
|
|
4589
4352
|
* Return the first n bits of the address, defaulting to the subnet mask
|
|
4353
|
+
* @memberof Address6
|
|
4354
|
+
* @instance
|
|
4590
4355
|
* @param {number} [mask=subnet] - the number of bits to mask
|
|
4591
4356
|
* @returns {String} the first n bits of the address as a string
|
|
4592
4357
|
*/
|
|
@@ -4595,6 +4360,8 @@ function requireIpv6 () {
|
|
|
4595
4360
|
}
|
|
4596
4361
|
/**
|
|
4597
4362
|
* Return the number of possible subnets of a given size in the address
|
|
4363
|
+
* @memberof Address6
|
|
4364
|
+
* @instance
|
|
4598
4365
|
* @param {number} [subnetSize=128] - the subnet size
|
|
4599
4366
|
* @returns {String}
|
|
4600
4367
|
*/
|
|
@@ -4610,6 +4377,8 @@ function requireIpv6 () {
|
|
|
4610
4377
|
}
|
|
4611
4378
|
/**
|
|
4612
4379
|
* Helper function getting start address.
|
|
4380
|
+
* @memberof Address6
|
|
4381
|
+
* @instance
|
|
4613
4382
|
* @returns {bigint}
|
|
4614
4383
|
*/
|
|
4615
4384
|
_startAddress() {
|
|
@@ -4618,6 +4387,8 @@ function requireIpv6 () {
|
|
|
4618
4387
|
/**
|
|
4619
4388
|
* The first address in the range given by this address' subnet
|
|
4620
4389
|
* Often referred to as the Network Address.
|
|
4390
|
+
* @memberof Address6
|
|
4391
|
+
* @instance
|
|
4621
4392
|
* @returns {Address6}
|
|
4622
4393
|
*/
|
|
4623
4394
|
startAddress() {
|
|
@@ -4626,6 +4397,8 @@ function requireIpv6 () {
|
|
|
4626
4397
|
/**
|
|
4627
4398
|
* The first host address in the range given by this address's subnet ie
|
|
4628
4399
|
* the first address after the Network Address
|
|
4400
|
+
* @memberof Address6
|
|
4401
|
+
* @instance
|
|
4629
4402
|
* @returns {Address6}
|
|
4630
4403
|
*/
|
|
4631
4404
|
startAddressExclusive() {
|
|
@@ -4634,6 +4407,8 @@ function requireIpv6 () {
|
|
|
4634
4407
|
}
|
|
4635
4408
|
/**
|
|
4636
4409
|
* Helper function getting end address.
|
|
4410
|
+
* @memberof Address6
|
|
4411
|
+
* @instance
|
|
4637
4412
|
* @returns {bigint}
|
|
4638
4413
|
*/
|
|
4639
4414
|
_endAddress() {
|
|
@@ -4642,6 +4417,8 @@ function requireIpv6 () {
|
|
|
4642
4417
|
/**
|
|
4643
4418
|
* The last address in the range given by this address' subnet
|
|
4644
4419
|
* Often referred to as the Broadcast
|
|
4420
|
+
* @memberof Address6
|
|
4421
|
+
* @instance
|
|
4645
4422
|
* @returns {Address6}
|
|
4646
4423
|
*/
|
|
4647
4424
|
endAddress() {
|
|
@@ -4650,6 +4427,8 @@ function requireIpv6 () {
|
|
|
4650
4427
|
/**
|
|
4651
4428
|
* The last host address in the range given by this address's subnet ie
|
|
4652
4429
|
* the last address prior to the Broadcast Address
|
|
4430
|
+
* @memberof Address6
|
|
4431
|
+
* @instance
|
|
4653
4432
|
* @returns {Address6}
|
|
4654
4433
|
*/
|
|
4655
4434
|
endAddressExclusive() {
|
|
@@ -4657,73 +4436,36 @@ function requireIpv6 () {
|
|
|
4657
4436
|
return Address6.fromBigInt(this._endAddress() - adjust);
|
|
4658
4437
|
}
|
|
4659
4438
|
/**
|
|
4660
|
-
*
|
|
4661
|
-
*
|
|
4662
|
-
* @
|
|
4663
|
-
*/
|
|
4664
|
-
subnetMaskAddress() {
|
|
4665
|
-
return Address6.fromBigInt(BigInt(`0b${'1'.repeat(this.subnetMask)}${'0'.repeat(constants6.BITS - this.subnetMask)}`));
|
|
4666
|
-
}
|
|
4667
|
-
/**
|
|
4668
|
-
* The Cisco-style wildcard mask, e.g. `::ffff:ffff:ffff:ffff` for a
|
|
4669
|
-
* `/64`. This is the bitwise inverse of `subnetMaskAddress()`. Returns
|
|
4670
|
-
* an `Address6`; call `.correctForm()` for the string.
|
|
4671
|
-
* @returns {Address6}
|
|
4672
|
-
*/
|
|
4673
|
-
wildcardMask() {
|
|
4674
|
-
return Address6.fromBigInt(BigInt(`0b${'0'.repeat(this.subnetMask)}${'1'.repeat(constants6.BITS - this.subnetMask)}`));
|
|
4675
|
-
}
|
|
4676
|
-
/**
|
|
4677
|
-
* The network address in CIDR string form, e.g. `2001:db8::/32` for
|
|
4678
|
-
* `2001:db8::1/32`. For an address with no explicit subnet the prefix
|
|
4679
|
-
* is `/128`, e.g. `networkForm()` on `2001:db8::1` returns
|
|
4680
|
-
* `2001:db8::1/128`.
|
|
4681
|
-
* @returns {string}
|
|
4682
|
-
*/
|
|
4683
|
-
networkForm() {
|
|
4684
|
-
return `${this.startAddress().correctForm()}/${this.subnetMask}`;
|
|
4685
|
-
}
|
|
4686
|
-
/**
|
|
4687
|
-
* Return the scope of the address. The 4-bit scope field
|
|
4688
|
-
* ([RFC 4291 §2.7](https://datatracker.ietf.org/doc/html/rfc4291#section-2.7))
|
|
4689
|
-
* is only defined for multicast addresses; for unicast addresses the scope
|
|
4690
|
-
* is derived from the address type per
|
|
4691
|
-
* [RFC 4007 §6](https://datatracker.ietf.org/doc/html/rfc4007#section-6).
|
|
4439
|
+
* Return the scope of the address
|
|
4440
|
+
* @memberof Address6
|
|
4441
|
+
* @instance
|
|
4692
4442
|
* @returns {String}
|
|
4693
4443
|
*/
|
|
4694
4444
|
getScope() {
|
|
4695
|
-
|
|
4696
|
-
if (
|
|
4697
|
-
|
|
4698
|
-
return scope || 'Unknown';
|
|
4699
|
-
}
|
|
4700
|
-
// RFC 4291 §2.5.3: the loopback address is treated as having Link-Local
|
|
4701
|
-
// scope. (Multicast scope 1, "Interface-Local", is a different concept
|
|
4702
|
-
// used only for loopback transmission of multicast.)
|
|
4703
|
-
if (type === 'Link-local unicast' || type === 'Loopback') {
|
|
4704
|
-
return 'Link local';
|
|
4445
|
+
let scope = constants6.SCOPES[parseInt(this.getBits(12, 16).toString(10), 10)];
|
|
4446
|
+
if (this.getType() === 'Global unicast' && scope !== 'Link local') {
|
|
4447
|
+
scope = 'Global';
|
|
4705
4448
|
}
|
|
4706
|
-
|
|
4707
|
-
if (type === 'Unspecified') {
|
|
4708
|
-
return 'Unknown';
|
|
4709
|
-
}
|
|
4710
|
-
return 'Global';
|
|
4449
|
+
return scope || 'Unknown';
|
|
4711
4450
|
}
|
|
4712
4451
|
/**
|
|
4713
4452
|
* Return the type of the address
|
|
4453
|
+
* @memberof Address6
|
|
4454
|
+
* @instance
|
|
4714
4455
|
* @returns {String}
|
|
4715
4456
|
*/
|
|
4716
4457
|
getType() {
|
|
4717
|
-
for (
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
return entry[1];
|
|
4458
|
+
for (const subnet of Object.keys(constants6.TYPES)) {
|
|
4459
|
+
if (this.isInSubnet(new Address6(subnet))) {
|
|
4460
|
+
return constants6.TYPES[subnet];
|
|
4721
4461
|
}
|
|
4722
4462
|
}
|
|
4723
4463
|
return 'Global unicast';
|
|
4724
4464
|
}
|
|
4725
4465
|
/**
|
|
4726
4466
|
* Return the bits in the given range as a BigInt
|
|
4467
|
+
* @memberof Address6
|
|
4468
|
+
* @instance
|
|
4727
4469
|
* @returns {bigint}
|
|
4728
4470
|
*/
|
|
4729
4471
|
getBits(start, end) {
|
|
@@ -4731,6 +4473,8 @@ function requireIpv6 () {
|
|
|
4731
4473
|
}
|
|
4732
4474
|
/**
|
|
4733
4475
|
* Return the bits in the given range as a base-2 string
|
|
4476
|
+
* @memberof Address6
|
|
4477
|
+
* @instance
|
|
4734
4478
|
* @returns {String}
|
|
4735
4479
|
*/
|
|
4736
4480
|
getBitsBase2(start, end) {
|
|
@@ -4738,6 +4482,8 @@ function requireIpv6 () {
|
|
|
4738
4482
|
}
|
|
4739
4483
|
/**
|
|
4740
4484
|
* Return the bits in the given range as a base-16 string
|
|
4485
|
+
* @memberof Address6
|
|
4486
|
+
* @instance
|
|
4741
4487
|
* @returns {String}
|
|
4742
4488
|
*/
|
|
4743
4489
|
getBitsBase16(start, end) {
|
|
@@ -4751,6 +4497,8 @@ function requireIpv6 () {
|
|
|
4751
4497
|
}
|
|
4752
4498
|
/**
|
|
4753
4499
|
* Return the bits that are set past the subnet mask length
|
|
4500
|
+
* @memberof Address6
|
|
4501
|
+
* @instance
|
|
4754
4502
|
* @returns {String}
|
|
4755
4503
|
*/
|
|
4756
4504
|
getBitsPastSubnet() {
|
|
@@ -4758,8 +4506,10 @@ function requireIpv6 () {
|
|
|
4758
4506
|
}
|
|
4759
4507
|
/**
|
|
4760
4508
|
* Return the reversed ip6.arpa form of the address
|
|
4509
|
+
* @memberof Address6
|
|
4761
4510
|
* @param {Object} options
|
|
4762
4511
|
* @param {boolean} options.omitSuffix - omit the "ip6.arpa" suffix
|
|
4512
|
+
* @instance
|
|
4763
4513
|
* @returns {String}
|
|
4764
4514
|
*/
|
|
4765
4515
|
reverseForm(options) {
|
|
@@ -4785,10 +4535,10 @@ function requireIpv6 () {
|
|
|
4785
4535
|
return 'ip6.arpa.';
|
|
4786
4536
|
}
|
|
4787
4537
|
/**
|
|
4788
|
-
*
|
|
4789
|
-
*
|
|
4790
|
-
*
|
|
4791
|
-
*
|
|
4538
|
+
* Return the correct form of the address
|
|
4539
|
+
* @memberof Address6
|
|
4540
|
+
* @instance
|
|
4541
|
+
* @returns {String}
|
|
4792
4542
|
*/
|
|
4793
4543
|
correctForm() {
|
|
4794
4544
|
let i;
|
|
@@ -4832,6 +4582,8 @@ function requireIpv6 () {
|
|
|
4832
4582
|
}
|
|
4833
4583
|
/**
|
|
4834
4584
|
* Return a zero-padded base-2 string representation of the address
|
|
4585
|
+
* @memberof Address6
|
|
4586
|
+
* @instance
|
|
4835
4587
|
* @returns {String}
|
|
4836
4588
|
* @example
|
|
4837
4589
|
* var address = new Address6('2001:4860:4001:803::1011');
|
|
@@ -4840,22 +4592,10 @@ function requireIpv6 () {
|
|
|
4840
4592
|
* // 0000000000000000000000000000000000000000000000000001000000010001'
|
|
4841
4593
|
*/
|
|
4842
4594
|
binaryZeroPad() {
|
|
4843
|
-
|
|
4844
|
-
this._binaryZeroPad = this.bigInt().toString(2).padStart(constants6.BITS, '0');
|
|
4845
|
-
}
|
|
4846
|
-
return this._binaryZeroPad;
|
|
4595
|
+
return this.bigInt().toString(2).padStart(constants6.BITS, '0');
|
|
4847
4596
|
}
|
|
4848
|
-
/**
|
|
4849
|
-
* Parses a v4-in-v6 string (e.g. `::ffff:192.168.0.1`) by extracting the
|
|
4850
|
-
* trailing IPv4 address into `this.address4` / `this.parsedAddress4` and
|
|
4851
|
-
* returning the address with the v4 portion converted to two v6 groups.
|
|
4852
|
-
* Used internally by `parse()`.
|
|
4853
|
-
*/
|
|
4854
4597
|
// TODO: Improve the semantics of this helper function
|
|
4855
4598
|
parse4in6(address) {
|
|
4856
|
-
if (address.indexOf('.') === -1) {
|
|
4857
|
-
return address;
|
|
4858
|
-
}
|
|
4859
4599
|
const groups = address.split(':');
|
|
4860
4600
|
const lastGroup = groups.slice(-1)[0];
|
|
4861
4601
|
const address4 = lastGroup.match(constants4.RE_ADDRESS);
|
|
@@ -4864,12 +4604,7 @@ function requireIpv6 () {
|
|
|
4864
4604
|
this.address4 = new ipv4_1.Address4(this.parsedAddress4);
|
|
4865
4605
|
for (let i = 0; i < this.address4.groups; i++) {
|
|
4866
4606
|
if (/^0[0-9]+/.test(this.address4.parsedAddress[i])) {
|
|
4867
|
-
|
|
4868
|
-
// yet, so escape them before including in the error HTML.
|
|
4869
|
-
const highlighted = this.address4.parsedAddress.map(spanLeadingZeroes4).join('.');
|
|
4870
|
-
const prefix = groups.slice(0, -1).map(helpers.escapeHtml).join(':');
|
|
4871
|
-
const separator = groups.length > 1 ? ':' : '';
|
|
4872
|
-
throw new address_error_1.AddressError("IPv4 addresses can't have leading zeroes.", `${prefix}${separator}${highlighted}`);
|
|
4607
|
+
throw new address_error_1.AddressError("IPv4 addresses can't have leading zeroes.", address.replace(constants4.RE_ADDRESS, this.address4.parsedAddress.map(spanLeadingZeroes4).join('.')));
|
|
4873
4608
|
}
|
|
4874
4609
|
}
|
|
4875
4610
|
this.v4 = true;
|
|
@@ -4878,13 +4613,6 @@ function requireIpv6 () {
|
|
|
4878
4613
|
}
|
|
4879
4614
|
return address;
|
|
4880
4615
|
}
|
|
4881
|
-
/**
|
|
4882
|
-
* Parses an IPv6 address string into its 8 hexadecimal groups (expanding
|
|
4883
|
-
* any `::` elision and any trailing v4-in-v6 portion) and stores the result
|
|
4884
|
-
* on `this.parsedAddress`. Called automatically by the constructor; you
|
|
4885
|
-
* typically don't need to call it directly. Throws `AddressError` if the
|
|
4886
|
-
* input is malformed.
|
|
4887
|
-
*/
|
|
4888
4616
|
// TODO: Make private?
|
|
4889
4617
|
parse(address) {
|
|
4890
4618
|
address = this.parse4in6(address);
|
|
@@ -4934,16 +4662,18 @@ function requireIpv6 () {
|
|
|
4934
4662
|
return groups;
|
|
4935
4663
|
}
|
|
4936
4664
|
/**
|
|
4937
|
-
*
|
|
4938
|
-
*
|
|
4939
|
-
*
|
|
4940
|
-
*
|
|
4665
|
+
* Return the canonical form of the address
|
|
4666
|
+
* @memberof Address6
|
|
4667
|
+
* @instance
|
|
4668
|
+
* @returns {String}
|
|
4941
4669
|
*/
|
|
4942
4670
|
canonicalForm() {
|
|
4943
4671
|
return this.parsedAddress.map(paddedHex).join(':');
|
|
4944
4672
|
}
|
|
4945
4673
|
/**
|
|
4946
4674
|
* Return the decimal form of the address
|
|
4675
|
+
* @memberof Address6
|
|
4676
|
+
* @instance
|
|
4947
4677
|
* @returns {String}
|
|
4948
4678
|
*/
|
|
4949
4679
|
decimal() {
|
|
@@ -4951,6 +4681,8 @@ function requireIpv6 () {
|
|
|
4951
4681
|
}
|
|
4952
4682
|
/**
|
|
4953
4683
|
* Return the address as a BigInt
|
|
4684
|
+
* @memberof Address6
|
|
4685
|
+
* @instance
|
|
4954
4686
|
* @returns {bigint}
|
|
4955
4687
|
*/
|
|
4956
4688
|
bigInt() {
|
|
@@ -4958,6 +4690,8 @@ function requireIpv6 () {
|
|
|
4958
4690
|
}
|
|
4959
4691
|
/**
|
|
4960
4692
|
* Return the last two groups of this address as an IPv4 address string
|
|
4693
|
+
* @memberof Address6
|
|
4694
|
+
* @instance
|
|
4961
4695
|
* @returns {Address4}
|
|
4962
4696
|
* @example
|
|
4963
4697
|
* var address = new Address6('2001:4860:4001::1825:bf11');
|
|
@@ -4965,10 +4699,12 @@ function requireIpv6 () {
|
|
|
4965
4699
|
*/
|
|
4966
4700
|
to4() {
|
|
4967
4701
|
const binary = this.binaryZeroPad().split('');
|
|
4968
|
-
return ipv4_1.Address4.fromHex(BigInt(`0b${binary.slice(96, 128).join('')}`).toString(16)
|
|
4702
|
+
return ipv4_1.Address4.fromHex(BigInt(`0b${binary.slice(96, 128).join('')}`).toString(16));
|
|
4969
4703
|
}
|
|
4970
4704
|
/**
|
|
4971
4705
|
* Return the v4-in-v6 form of the address
|
|
4706
|
+
* @memberof Address6
|
|
4707
|
+
* @instance
|
|
4972
4708
|
* @returns {String}
|
|
4973
4709
|
*/
|
|
4974
4710
|
to4in6() {
|
|
@@ -4982,10 +4718,10 @@ function requireIpv6 () {
|
|
|
4982
4718
|
return correct + infix + address4.address;
|
|
4983
4719
|
}
|
|
4984
4720
|
/**
|
|
4985
|
-
*
|
|
4986
|
-
*
|
|
4987
|
-
*
|
|
4988
|
-
*
|
|
4721
|
+
* Return an object containing the Teredo properties of the address
|
|
4722
|
+
* @memberof Address6
|
|
4723
|
+
* @instance
|
|
4724
|
+
* @returns {Object}
|
|
4989
4725
|
*/
|
|
4990
4726
|
inspectTeredo() {
|
|
4991
4727
|
/*
|
|
@@ -5016,7 +4752,7 @@ function requireIpv6 () {
|
|
|
5016
4752
|
const server4 = ipv4_1.Address4.fromHex(this.getBitsBase16(32, 64));
|
|
5017
4753
|
const bitsForClient4 = this.getBits(96, 128);
|
|
5018
4754
|
// eslint-disable-next-line no-bitwise
|
|
5019
|
-
const client4 = ipv4_1.Address4.fromHex((bitsForClient4 ^ BigInt('0xffffffff')).toString(16)
|
|
4755
|
+
const client4 = ipv4_1.Address4.fromHex((bitsForClient4 ^ BigInt('0xffffffff')).toString(16));
|
|
5020
4756
|
const flagsBase2 = this.getBitsBase2(64, 80);
|
|
5021
4757
|
const coneNat = (0, common_1.testBit)(flagsBase2, 15);
|
|
5022
4758
|
const reserved = (0, common_1.testBit)(flagsBase2, 14);
|
|
@@ -5039,9 +4775,10 @@ function requireIpv6 () {
|
|
|
5039
4775
|
};
|
|
5040
4776
|
}
|
|
5041
4777
|
/**
|
|
5042
|
-
*
|
|
5043
|
-
*
|
|
5044
|
-
*
|
|
4778
|
+
* Return an object containing the 6to4 properties of the address
|
|
4779
|
+
* @memberof Address6
|
|
4780
|
+
* @instance
|
|
4781
|
+
* @returns {Object}
|
|
5045
4782
|
*/
|
|
5046
4783
|
inspect6to4() {
|
|
5047
4784
|
/*
|
|
@@ -5057,6 +4794,8 @@ function requireIpv6 () {
|
|
|
5057
4794
|
}
|
|
5058
4795
|
/**
|
|
5059
4796
|
* Return a v6 6to4 address from a v6 v4inv6 address
|
|
4797
|
+
* @memberof Address6
|
|
4798
|
+
* @instance
|
|
5060
4799
|
* @returns {Address6}
|
|
5061
4800
|
*/
|
|
5062
4801
|
to6to4() {
|
|
@@ -5073,80 +4812,9 @@ function requireIpv6 () {
|
|
|
5073
4812
|
return new Address6(addr6to4);
|
|
5074
4813
|
}
|
|
5075
4814
|
/**
|
|
5076
|
-
*
|
|
5077
|
-
*
|
|
5078
|
-
*
|
|
5079
|
-
* length must be one of 32, 40, 48, 56, 64, or 96; for prefixes shorter
|
|
5080
|
-
* than /64 the IPv4 octets are split around the reserved bits 64–71.
|
|
5081
|
-
* @example
|
|
5082
|
-
* Address6.fromAddress4Nat64('192.0.2.33').correctForm(); // '64:ff9b::c000:221'
|
|
5083
|
-
* Address6.fromAddress4Nat64('192.0.2.33', '2001:db8::/32').correctForm(); // '2001:db8:c000:221::'
|
|
5084
|
-
*/
|
|
5085
|
-
static fromAddress4Nat64(address, prefix = '64:ff9b::/96') {
|
|
5086
|
-
const v4 = new ipv4_1.Address4(address);
|
|
5087
|
-
const prefix6 = new Address6(prefix);
|
|
5088
|
-
const pl = prefix6.subnetMask;
|
|
5089
|
-
if (pl !== 32 && pl !== 40 && pl !== 48 && pl !== 56 && pl !== 64 && pl !== 96) {
|
|
5090
|
-
throw new address_error_1.AddressError('NAT64 prefix length must be 32, 40, 48, 56, 64, or 96');
|
|
5091
|
-
}
|
|
5092
|
-
const prefixBits = prefix6.binaryZeroPad();
|
|
5093
|
-
const v4Bits = v4.binaryZeroPad();
|
|
5094
|
-
let bits;
|
|
5095
|
-
if (pl === 96) {
|
|
5096
|
-
bits = prefixBits.slice(0, 96) + v4Bits;
|
|
5097
|
-
}
|
|
5098
|
-
else {
|
|
5099
|
-
const beforeU = 64 - pl;
|
|
5100
|
-
bits =
|
|
5101
|
-
prefixBits.slice(0, pl) +
|
|
5102
|
-
v4Bits.slice(0, beforeU) +
|
|
5103
|
-
'00000000' +
|
|
5104
|
-
v4Bits.slice(beforeU) +
|
|
5105
|
-
'0'.repeat(128 - 72 - (32 - beforeU));
|
|
5106
|
-
}
|
|
5107
|
-
const hex = BigInt(`0b${bits}`).toString(16).padStart(32, '0');
|
|
5108
|
-
const groups = [];
|
|
5109
|
-
for (let i = 0; i < 8; i++) {
|
|
5110
|
-
groups.push(hex.slice(i * 4, (i + 1) * 4));
|
|
5111
|
-
}
|
|
5112
|
-
return new Address6(groups.join(':'));
|
|
5113
|
-
}
|
|
5114
|
-
/**
|
|
5115
|
-
* Extract the embedded IPv4 address from a NAT64 IPv6 address using the
|
|
5116
|
-
* encoding defined by [RFC 6052](https://datatracker.ietf.org/doc/html/rfc6052).
|
|
5117
|
-
* The default prefix is the well-known prefix `64:ff9b::/96`. Returns
|
|
5118
|
-
* `null` if this address is not contained within the given prefix.
|
|
5119
|
-
* @example
|
|
5120
|
-
* new Address6('64:ff9b::c000:221').toAddress4Nat64()!.correctForm(); // '192.0.2.33'
|
|
5121
|
-
*/
|
|
5122
|
-
toAddress4Nat64(prefix = '64:ff9b::/96') {
|
|
5123
|
-
const prefix6 = new Address6(prefix);
|
|
5124
|
-
const pl = prefix6.subnetMask;
|
|
5125
|
-
if (pl !== 32 && pl !== 40 && pl !== 48 && pl !== 56 && pl !== 64 && pl !== 96) {
|
|
5126
|
-
throw new address_error_1.AddressError('NAT64 prefix length must be 32, 40, 48, 56, 64, or 96');
|
|
5127
|
-
}
|
|
5128
|
-
if (!this.isInSubnet(prefix6)) {
|
|
5129
|
-
return null;
|
|
5130
|
-
}
|
|
5131
|
-
const bits = this.binaryZeroPad();
|
|
5132
|
-
let v4Bits;
|
|
5133
|
-
if (pl === 96) {
|
|
5134
|
-
v4Bits = bits.slice(96, 128);
|
|
5135
|
-
}
|
|
5136
|
-
else {
|
|
5137
|
-
const beforeU = 64 - pl;
|
|
5138
|
-
v4Bits = bits.slice(pl, pl + beforeU) + bits.slice(72, 72 + (32 - beforeU));
|
|
5139
|
-
}
|
|
5140
|
-
const octets = [];
|
|
5141
|
-
for (let i = 0; i < 4; i++) {
|
|
5142
|
-
octets.push(parseInt(v4Bits.slice(i * 8, (i + 1) * 8), 2).toString());
|
|
5143
|
-
}
|
|
5144
|
-
return new ipv4_1.Address4(octets.join('.'));
|
|
5145
|
-
}
|
|
5146
|
-
/**
|
|
5147
|
-
* Return a byte array.
|
|
5148
|
-
*
|
|
5149
|
-
* To get a Node.js `Buffer`, wrap the result: `Buffer.from(address.toByteArray())`.
|
|
4815
|
+
* Return a byte array
|
|
4816
|
+
* @memberof Address6
|
|
4817
|
+
* @instance
|
|
5150
4818
|
* @returns {Array}
|
|
5151
4819
|
*/
|
|
5152
4820
|
toByteArray() {
|
|
@@ -5160,27 +4828,27 @@ function requireIpv6 () {
|
|
|
5160
4828
|
return bytes;
|
|
5161
4829
|
}
|
|
5162
4830
|
/**
|
|
5163
|
-
* Return an unsigned byte array
|
|
5164
|
-
*
|
|
5165
|
-
*
|
|
4831
|
+
* Return an unsigned byte array
|
|
4832
|
+
* @memberof Address6
|
|
4833
|
+
* @instance
|
|
5166
4834
|
* @returns {Array}
|
|
5167
4835
|
*/
|
|
5168
4836
|
toUnsignedByteArray() {
|
|
5169
4837
|
return this.toByteArray().map(unsignByte);
|
|
5170
4838
|
}
|
|
5171
4839
|
/**
|
|
5172
|
-
* Convert a byte array to an Address6 object
|
|
5173
|
-
*
|
|
5174
|
-
*
|
|
4840
|
+
* Convert a byte array to an Address6 object
|
|
4841
|
+
* @memberof Address6
|
|
4842
|
+
* @static
|
|
5175
4843
|
* @returns {Address6}
|
|
5176
4844
|
*/
|
|
5177
4845
|
static fromByteArray(bytes) {
|
|
5178
4846
|
return this.fromUnsignedByteArray(bytes.map(unsignByte));
|
|
5179
4847
|
}
|
|
5180
4848
|
/**
|
|
5181
|
-
* Convert an unsigned byte array to an Address6 object
|
|
5182
|
-
*
|
|
5183
|
-
*
|
|
4849
|
+
* Convert an unsigned byte array to an Address6 object
|
|
4850
|
+
* @memberof Address6
|
|
4851
|
+
* @static
|
|
5184
4852
|
* @returns {Address6}
|
|
5185
4853
|
*/
|
|
5186
4854
|
static fromUnsignedByteArray(bytes) {
|
|
@@ -5195,6 +4863,8 @@ function requireIpv6 () {
|
|
|
5195
4863
|
}
|
|
5196
4864
|
/**
|
|
5197
4865
|
* Returns true if the address is in the canonical form, false otherwise
|
|
4866
|
+
* @memberof Address6
|
|
4867
|
+
* @instance
|
|
5198
4868
|
* @returns {boolean}
|
|
5199
4869
|
*/
|
|
5200
4870
|
isCanonical() {
|
|
@@ -5202,6 +4872,8 @@ function requireIpv6 () {
|
|
|
5202
4872
|
}
|
|
5203
4873
|
/**
|
|
5204
4874
|
* Returns true if the address is a link local address, false otherwise
|
|
4875
|
+
* @memberof Address6
|
|
4876
|
+
* @instance
|
|
5205
4877
|
* @returns {boolean}
|
|
5206
4878
|
*/
|
|
5207
4879
|
isLinkLocal() {
|
|
@@ -5214,81 +4886,53 @@ function requireIpv6 () {
|
|
|
5214
4886
|
}
|
|
5215
4887
|
/**
|
|
5216
4888
|
* Returns true if the address is a multicast address, false otherwise
|
|
4889
|
+
* @memberof Address6
|
|
4890
|
+
* @instance
|
|
5217
4891
|
* @returns {boolean}
|
|
5218
4892
|
*/
|
|
5219
4893
|
isMulticast() {
|
|
5220
|
-
|
|
5221
|
-
return type === 'Multicast' || type.startsWith('Multicast ');
|
|
4894
|
+
return this.getType() === 'Multicast';
|
|
5222
4895
|
}
|
|
5223
4896
|
/**
|
|
5224
|
-
* Returns true if the address
|
|
5225
|
-
*
|
|
5226
|
-
*
|
|
5227
|
-
* (`::ffff:0:0/96`) subnet — for that, see {@link isMapped4}.
|
|
4897
|
+
* Returns true if the address is a v4-in-v6 address, false otherwise
|
|
4898
|
+
* @memberof Address6
|
|
4899
|
+
* @instance
|
|
5228
4900
|
* @returns {boolean}
|
|
5229
4901
|
*/
|
|
5230
4902
|
is4() {
|
|
5231
4903
|
return this.v4;
|
|
5232
4904
|
}
|
|
5233
|
-
/**
|
|
5234
|
-
* Returns true if the address is an IPv4-mapped IPv6 address in
|
|
5235
|
-
* `::ffff:0:0/96` ([RFC 4291 §2.5.5.2](https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.2)),
|
|
5236
|
-
* false otherwise. Unlike {@link is4}, this checks the underlying address
|
|
5237
|
-
* bits rather than the textual notation, so `::ffff:127.0.0.1` and
|
|
5238
|
-
* `::ffff:7f00:1` both return true.
|
|
5239
|
-
* @returns {boolean}
|
|
5240
|
-
*/
|
|
5241
|
-
isMapped4() {
|
|
5242
|
-
return this.isInSubnet(IPV4_MAPPED_SUBNET);
|
|
5243
|
-
}
|
|
5244
4905
|
/**
|
|
5245
4906
|
* Returns true if the address is a Teredo address, false otherwise
|
|
4907
|
+
* @memberof Address6
|
|
4908
|
+
* @instance
|
|
5246
4909
|
* @returns {boolean}
|
|
5247
4910
|
*/
|
|
5248
4911
|
isTeredo() {
|
|
5249
|
-
return this.isInSubnet(
|
|
4912
|
+
return this.isInSubnet(new Address6('2001::/32'));
|
|
5250
4913
|
}
|
|
5251
4914
|
/**
|
|
5252
4915
|
* Returns true if the address is a 6to4 address, false otherwise
|
|
4916
|
+
* @memberof Address6
|
|
4917
|
+
* @instance
|
|
5253
4918
|
* @returns {boolean}
|
|
5254
4919
|
*/
|
|
5255
4920
|
is6to4() {
|
|
5256
|
-
return this.isInSubnet(
|
|
4921
|
+
return this.isInSubnet(new Address6('2002::/16'));
|
|
5257
4922
|
}
|
|
5258
4923
|
/**
|
|
5259
4924
|
* Returns true if the address is a loopback address, false otherwise
|
|
4925
|
+
* @memberof Address6
|
|
4926
|
+
* @instance
|
|
5260
4927
|
* @returns {boolean}
|
|
5261
4928
|
*/
|
|
5262
4929
|
isLoopback() {
|
|
5263
4930
|
return this.getType() === 'Loopback';
|
|
5264
4931
|
}
|
|
5265
|
-
/**
|
|
5266
|
-
* Returns true if the address is a Unique Local Address in `fc00::/7` ([RFC 4193](https://datatracker.ietf.org/doc/html/rfc4193)). ULAs are the IPv6 equivalent of IPv4 [RFC 1918](https://datatracker.ietf.org/doc/html/rfc1918) private addresses.
|
|
5267
|
-
* @returns {boolean}
|
|
5268
|
-
*/
|
|
5269
|
-
isULA() {
|
|
5270
|
-
return this.isInSubnet(ULA_SUBNET);
|
|
5271
|
-
}
|
|
5272
|
-
/**
|
|
5273
|
-
* Returns true if the address is the unspecified address `::`.
|
|
5274
|
-
* @returns {boolean}
|
|
5275
|
-
*/
|
|
5276
|
-
isUnspecified() {
|
|
5277
|
-
return this.getType() === 'Unspecified';
|
|
5278
|
-
}
|
|
5279
|
-
/**
|
|
5280
|
-
* Returns true if the address is in the documentation prefix `2001:db8::/32` ([RFC 3849](https://datatracker.ietf.org/doc/html/rfc3849)).
|
|
5281
|
-
* @returns {boolean}
|
|
5282
|
-
*/
|
|
5283
|
-
isDocumentation() {
|
|
5284
|
-
return this.isInSubnet(DOCUMENTATION_SUBNET);
|
|
5285
|
-
}
|
|
5286
4932
|
// #endregion
|
|
5287
4933
|
// #region HTML
|
|
5288
4934
|
/**
|
|
5289
|
-
*
|
|
5290
|
-
* `http://[2001:db8::1]/`. If `optionalPort` is provided it is appended,
|
|
5291
|
-
* e.g. `http://[2001:db8::1]:8080/`.
|
|
4935
|
+
* @returns {String} the address in link form with a default port of 80
|
|
5292
4936
|
*/
|
|
5293
4937
|
href(optionalPort) {
|
|
5294
4938
|
if (optionalPort === undefined) {
|
|
@@ -5300,12 +4944,7 @@ function requireIpv6 () {
|
|
|
5300
4944
|
return `http://[${this.correctForm()}]${optionalPort}/`;
|
|
5301
4945
|
}
|
|
5302
4946
|
/**
|
|
5303
|
-
*
|
|
5304
|
-
* hash fragment (default prefix `/#address=`). Useful for linking between
|
|
5305
|
-
* pages of an address-inspector UI.
|
|
5306
|
-
* @param options.className - CSS class for the rendered `<a>` element
|
|
5307
|
-
* @param options.prefix - hash prefix prepended to the address (default `/#address=`)
|
|
5308
|
-
* @param options.v4 - when true, render the address in v4-in-v6 form
|
|
4947
|
+
* @returns {String} a link suitable for conveying the address via a URL hash
|
|
5309
4948
|
*/
|
|
5310
4949
|
link(options) {
|
|
5311
4950
|
if (!options) {
|
|
@@ -5325,13 +4964,10 @@ function requireIpv6 () {
|
|
|
5325
4964
|
formFunction = this.to4in6;
|
|
5326
4965
|
}
|
|
5327
4966
|
const form = formFunction.call(this);
|
|
5328
|
-
const safeHref = helpers.escapeHtml(`${options.prefix}${form}`);
|
|
5329
|
-
const safeForm = helpers.escapeHtml(form);
|
|
5330
4967
|
if (options.className) {
|
|
5331
|
-
|
|
5332
|
-
return `<a href="${safeHref}" class="${safeClass}">${safeForm}</a>`;
|
|
4968
|
+
return `<a href="${options.prefix}${form}" class="${options.className}">${form}</a>`;
|
|
5333
4969
|
}
|
|
5334
|
-
return `<a href="${
|
|
4970
|
+
return `<a href="${options.prefix}${form}">${form}</a>`;
|
|
5335
4971
|
}
|
|
5336
4972
|
/**
|
|
5337
4973
|
* Groups an address
|
|
@@ -5340,13 +4976,13 @@ function requireIpv6 () {
|
|
|
5340
4976
|
group() {
|
|
5341
4977
|
if (this.elidedGroups === 0) {
|
|
5342
4978
|
// The simple case
|
|
5343
|
-
return helpers.simpleGroup(this.
|
|
4979
|
+
return helpers.simpleGroup(this.address).join(':');
|
|
5344
4980
|
}
|
|
5345
4981
|
assert(typeof this.elidedGroups === 'number');
|
|
5346
4982
|
assert(typeof this.elisionBegin === 'number');
|
|
5347
4983
|
// The elided case
|
|
5348
4984
|
const output = [];
|
|
5349
|
-
const [left, right] = this.
|
|
4985
|
+
const [left, right] = this.address.split('::');
|
|
5350
4986
|
if (left.length) {
|
|
5351
4987
|
output.push(...helpers.simpleGroup(left));
|
|
5352
4988
|
}
|
|
@@ -5376,6 +5012,8 @@ function requireIpv6 () {
|
|
|
5376
5012
|
/**
|
|
5377
5013
|
* Generate a regular expression string that can be used to find or validate
|
|
5378
5014
|
* all variations of this address
|
|
5015
|
+
* @memberof Address6
|
|
5016
|
+
* @instance
|
|
5379
5017
|
* @param {boolean} substringSearch
|
|
5380
5018
|
* @returns {string}
|
|
5381
5019
|
*/
|
|
@@ -5420,6 +5058,8 @@ function requireIpv6 () {
|
|
|
5420
5058
|
/**
|
|
5421
5059
|
* Generate a regular expression that can be used to find or validate all
|
|
5422
5060
|
* variations of this address.
|
|
5061
|
+
* @memberof Address6
|
|
5062
|
+
* @instance
|
|
5423
5063
|
* @param {boolean} substringSearch
|
|
5424
5064
|
* @returns {RegExp}
|
|
5425
5065
|
*/
|
|
@@ -5428,15 +5068,6 @@ function requireIpv6 () {
|
|
|
5428
5068
|
}
|
|
5429
5069
|
}
|
|
5430
5070
|
ipv6.Address6 = Address6;
|
|
5431
|
-
const TYPE_SUBNETS = Object.keys(constants6.TYPES).map((subnet) => [
|
|
5432
|
-
new Address6(subnet),
|
|
5433
|
-
constants6.TYPES[subnet],
|
|
5434
|
-
]);
|
|
5435
|
-
const TEREDO_SUBNET = new Address6('2001::/32');
|
|
5436
|
-
const SIX_TO_FOUR_SUBNET = new Address6('2002::/16');
|
|
5437
|
-
const ULA_SUBNET = new Address6('fc00::/7');
|
|
5438
|
-
const DOCUMENTATION_SUBNET = new Address6('2001:db8::/32');
|
|
5439
|
-
const IPV4_MAPPED_SUBNET = new Address6('::ffff:0:0/96');
|
|
5440
5071
|
|
|
5441
5072
|
return ipv6;
|
|
5442
5073
|
}
|
|
@@ -5446,7 +5077,7 @@ var hasRequiredIpAddress;
|
|
|
5446
5077
|
function requireIpAddress () {
|
|
5447
5078
|
if (hasRequiredIpAddress) return ipAddress;
|
|
5448
5079
|
hasRequiredIpAddress = 1;
|
|
5449
|
-
(function (exports) {
|
|
5080
|
+
(function (exports$1) {
|
|
5450
5081
|
var __createBinding = (ipAddress && ipAddress.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
5451
5082
|
if (k2 === undefined) k2 = k;
|
|
5452
5083
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -5470,16 +5101,16 @@ function requireIpAddress () {
|
|
|
5470
5101
|
__setModuleDefault(result, mod);
|
|
5471
5102
|
return result;
|
|
5472
5103
|
};
|
|
5473
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5474
|
-
exports.v6 = exports.AddressError = exports.Address6 = exports.Address4 = void 0;
|
|
5475
|
-
var ipv4_1 =
|
|
5476
|
-
Object.defineProperty(exports, "Address4", { enumerable: true, get: function () { return ipv4_1.Address4; } });
|
|
5477
|
-
var ipv6_1 =
|
|
5478
|
-
Object.defineProperty(exports, "Address6", { enumerable: true, get: function () { return ipv6_1.Address6; } });
|
|
5479
|
-
var address_error_1 =
|
|
5480
|
-
Object.defineProperty(exports, "AddressError", { enumerable: true, get: function () { return address_error_1.AddressError; } });
|
|
5481
|
-
const helpers = __importStar(
|
|
5482
|
-
exports.v6 = { helpers };
|
|
5104
|
+
Object.defineProperty(exports$1, "__esModule", { value: true });
|
|
5105
|
+
exports$1.v6 = exports$1.AddressError = exports$1.Address6 = exports$1.Address4 = void 0;
|
|
5106
|
+
var ipv4_1 = requireIpv4();
|
|
5107
|
+
Object.defineProperty(exports$1, "Address4", { enumerable: true, get: function () { return ipv4_1.Address4; } });
|
|
5108
|
+
var ipv6_1 = requireIpv6();
|
|
5109
|
+
Object.defineProperty(exports$1, "Address6", { enumerable: true, get: function () { return ipv6_1.Address6; } });
|
|
5110
|
+
var address_error_1 = requireAddressError();
|
|
5111
|
+
Object.defineProperty(exports$1, "AddressError", { enumerable: true, get: function () { return address_error_1.AddressError; } });
|
|
5112
|
+
const helpers = __importStar(requireHelpers$1());
|
|
5113
|
+
exports$1.v6 = { helpers };
|
|
5483
5114
|
|
|
5484
5115
|
} (ipAddress));
|
|
5485
5116
|
return ipAddress;
|
|
@@ -5495,7 +5126,7 @@ function requireHelpers () {
|
|
|
5495
5126
|
const util_1 = requireUtil();
|
|
5496
5127
|
const constants_1 = requireConstants$2();
|
|
5497
5128
|
const stream = require$$2$1;
|
|
5498
|
-
const ip_address_1 =
|
|
5129
|
+
const ip_address_1 = requireIpAddress();
|
|
5499
5130
|
const net$1 = net;
|
|
5500
5131
|
/**
|
|
5501
5132
|
* Validates the provided SocksClientOptions
|
|
@@ -5716,7 +5347,7 @@ var hasRequiredSocksclient;
|
|
|
5716
5347
|
function requireSocksclient () {
|
|
5717
5348
|
if (hasRequiredSocksclient) return socksclient;
|
|
5718
5349
|
hasRequiredSocksclient = 1;
|
|
5719
|
-
(function (exports) {
|
|
5350
|
+
(function (exports$1) {
|
|
5720
5351
|
var __awaiter = (socksclient && socksclient.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
5721
5352
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
5722
5353
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -5726,8 +5357,8 @@ function requireSocksclient () {
|
|
|
5726
5357
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
5727
5358
|
});
|
|
5728
5359
|
};
|
|
5729
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5730
|
-
exports.SocksClientError = exports.SocksClient = void 0;
|
|
5360
|
+
Object.defineProperty(exports$1, "__esModule", { value: true });
|
|
5361
|
+
exports$1.SocksClientError = exports$1.SocksClient = void 0;
|
|
5731
5362
|
const events_1 = require$$0;
|
|
5732
5363
|
const net$1 = net;
|
|
5733
5364
|
const smart_buffer_1 = requireSmartbuffer();
|
|
@@ -5735,8 +5366,8 @@ function requireSocksclient () {
|
|
|
5735
5366
|
const helpers_1 = requireHelpers();
|
|
5736
5367
|
const receivebuffer_1 = requireReceivebuffer();
|
|
5737
5368
|
const util_1 = requireUtil();
|
|
5738
|
-
Object.defineProperty(exports, "SocksClientError", { enumerable: true, get: function () { return util_1.SocksClientError; } });
|
|
5739
|
-
const ip_address_1 =
|
|
5369
|
+
Object.defineProperty(exports$1, "SocksClientError", { enumerable: true, get: function () { return util_1.SocksClientError; } });
|
|
5370
|
+
const ip_address_1 = requireIpAddress();
|
|
5740
5371
|
class SocksClient extends events_1.EventEmitter {
|
|
5741
5372
|
constructor(options) {
|
|
5742
5373
|
super();
|
|
@@ -6507,7 +6138,7 @@ function requireSocksclient () {
|
|
|
6507
6138
|
return Object.assign({}, this.options);
|
|
6508
6139
|
}
|
|
6509
6140
|
}
|
|
6510
|
-
exports.SocksClient = SocksClient;
|
|
6141
|
+
exports$1.SocksClient = SocksClient;
|
|
6511
6142
|
|
|
6512
6143
|
} (socksclient));
|
|
6513
6144
|
return socksclient;
|
|
@@ -6518,7 +6149,7 @@ var hasRequiredBuild;
|
|
|
6518
6149
|
function requireBuild () {
|
|
6519
6150
|
if (hasRequiredBuild) return build$1;
|
|
6520
6151
|
hasRequiredBuild = 1;
|
|
6521
|
-
(function (exports) {
|
|
6152
|
+
(function (exports$1) {
|
|
6522
6153
|
var __createBinding = (build$1 && build$1.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6523
6154
|
if (k2 === undefined) k2 = k;
|
|
6524
6155
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -6530,11 +6161,11 @@ function requireBuild () {
|
|
|
6530
6161
|
if (k2 === undefined) k2 = k;
|
|
6531
6162
|
o[k2] = m[k];
|
|
6532
6163
|
}));
|
|
6533
|
-
var __exportStar = (build$1 && build$1.__exportStar) || function(m, exports) {
|
|
6534
|
-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
6164
|
+
var __exportStar = (build$1 && build$1.__exportStar) || function(m, exports$1) {
|
|
6165
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports$1, p)) __createBinding(exports$1, m, p);
|
|
6535
6166
|
};
|
|
6536
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6537
|
-
__exportStar(requireSocksclient(), exports);
|
|
6167
|
+
Object.defineProperty(exports$1, "__esModule", { value: true });
|
|
6168
|
+
__exportStar(requireSocksclient(), exports$1);
|
|
6538
6169
|
|
|
6539
6170
|
} (build$1));
|
|
6540
6171
|
return build$1;
|
|
@@ -6957,10 +6588,44 @@ function buildCookieHeaderFromSetCookies(setCookies) {
|
|
|
6957
6588
|
return sanitizeCookieHeader(setCookies.join('; '));
|
|
6958
6589
|
}
|
|
6959
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
|
+
*/
|
|
6960
6603
|
function isVerboseEnabled() {
|
|
6961
6604
|
return process.argv.includes('--verbose');
|
|
6962
6605
|
}
|
|
6963
|
-
|
|
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
|
+
|
|
6615
|
+
/**
|
|
6616
|
+
* PPE / 测试环境开关。
|
|
6617
|
+
*
|
|
6618
|
+
* 所有 CLI 接口(`stark_wasm/v4/*`, `wasm-collect/v1/*`, portal 鉴权等)
|
|
6619
|
+
* 都共用本文件的 `request()`,所以在这里统一注入 PPE header 就能覆盖全量。
|
|
6620
|
+
* 不在 `DEV_HEADERS` 里改是因为那份常量只被 `remotePipeline.ts` 里的老
|
|
6621
|
+
* 远程分包接口 spread 用;新加的 `startSession.ts` / `finishSession.ts` /
|
|
6622
|
+
* `getCollectedFuncIds.ts` 都没 spread,漏一处就会绕开 PPE。
|
|
6623
|
+
*
|
|
6624
|
+
* 要切回线上环境,直接把 `CLI_PPE_ENV` 设成空串即可(下方条件 spread 会
|
|
6625
|
+
* 自动不带这两个 header)。
|
|
6626
|
+
*/
|
|
6627
|
+
// const CLI_PPE_ENV: string = 'ppe_wasm_test'; // 走 PPE 测试环境
|
|
6628
|
+
const CLI_PPE_ENV = ''; // 走线上服务(空串 => 下方条件 spread 不注入 PPE header)
|
|
6964
6629
|
function getAxiosProxyConfig() {
|
|
6965
6630
|
const config = getTTMGRC();
|
|
6966
6631
|
// 优先级: http-proxy > socks-proxy > proxy (老字段兼容)
|
|
@@ -7105,7 +6770,7 @@ function printApiResponseLog(title, result) {
|
|
|
7105
6770
|
['Return Value', result.data],
|
|
7106
6771
|
]);
|
|
7107
6772
|
}
|
|
7108
|
-
async function request({ url, method, data, headers, params, }) {
|
|
6773
|
+
async function request({ url, method, data, headers, params, logRequestBody = true, }) {
|
|
7109
6774
|
const config = getTTMGRC();
|
|
7110
6775
|
const cookie = sanitizeCookieHeader(config?.cookie);
|
|
7111
6776
|
const proxyConfig = getAxiosProxyConfig();
|
|
@@ -7113,7 +6778,9 @@ async function request({ url, method, data, headers, params, }) {
|
|
|
7113
6778
|
printApiRequestLog({
|
|
7114
6779
|
url,
|
|
7115
6780
|
method,
|
|
7116
|
-
params:
|
|
6781
|
+
params: logRequestBody
|
|
6782
|
+
? getRequestParams(params, data)
|
|
6783
|
+
: '[omitted: large request body]',
|
|
7117
6784
|
});
|
|
7118
6785
|
}
|
|
7119
6786
|
try {
|
|
@@ -7124,8 +6791,11 @@ async function request({ url, method, data, headers, params, }) {
|
|
|
7124
6791
|
params,
|
|
7125
6792
|
headers: {
|
|
7126
6793
|
Cookie: cookie,
|
|
7127
|
-
//
|
|
7128
|
-
//
|
|
6794
|
+
// 注入 PPE header — 放在 caller headers 之前,允许单个调用点通过
|
|
6795
|
+
// 显式传 `x-tt-env` 来覆盖本次请求(例如某个接口还没在 PPE 上发布)。
|
|
6796
|
+
...(CLI_PPE_ENV
|
|
6797
|
+
? { 'x-use-ppe': '1', 'x-tt-env': CLI_PPE_ENV }
|
|
6798
|
+
: {}),
|
|
7129
6799
|
...(headers || {}),
|
|
7130
6800
|
},
|
|
7131
6801
|
...proxyConfig,
|
|
@@ -7167,7 +6837,6 @@ async function request({ url, method, data, headers, params, }) {
|
|
|
7167
6837
|
}
|
|
7168
6838
|
}
|
|
7169
6839
|
async function download(url, filePath) {
|
|
7170
|
-
// 清理旧文件
|
|
7171
6840
|
if (fs.existsSync(filePath)) {
|
|
7172
6841
|
try {
|
|
7173
6842
|
fs.unlinkSync(filePath);
|
|
@@ -7175,16 +6844,31 @@ async function download(url, filePath) {
|
|
|
7175
6844
|
catch { }
|
|
7176
6845
|
}
|
|
7177
6846
|
const proxyConfig = getAxiosProxyConfig();
|
|
6847
|
+
verboseLog('[download] start', { url: url.slice(0, 120), filePath, hasProxy: !!proxyConfig.httpsAgent });
|
|
7178
6848
|
try {
|
|
7179
6849
|
const res = await axios.get(url, {
|
|
7180
6850
|
responseType: 'stream',
|
|
7181
|
-
// 让非 2xx 进入 catch
|
|
7182
6851
|
validateStatus: s => s >= 200 && s < 300,
|
|
6852
|
+
// Bail out if the server doesn't start responding within 30s instead of
|
|
6853
|
+
// hanging forever (e.g. proxy misrouting a CDN signed URL).
|
|
6854
|
+
timeout: 30000,
|
|
7183
6855
|
...proxyConfig,
|
|
7184
6856
|
});
|
|
7185
|
-
|
|
6857
|
+
const total = Number(res.headers['content-length'] || 0);
|
|
6858
|
+
let received = 0;
|
|
6859
|
+
let lastLoggedPct = -1;
|
|
6860
|
+
const startedAt = Date.now();
|
|
7186
6861
|
await new Promise((resolve, reject) => {
|
|
7187
6862
|
const writer = fs.createWriteStream(filePath);
|
|
6863
|
+
// Inactivity watchdog: if no bytes arrive for 60s mid-stream, abort.
|
|
6864
|
+
let inactivityTimer = null;
|
|
6865
|
+
const resetInactivity = () => {
|
|
6866
|
+
if (inactivityTimer)
|
|
6867
|
+
clearTimeout(inactivityTimer);
|
|
6868
|
+
inactivityTimer = setTimeout(() => {
|
|
6869
|
+
onError(new Error('download stalled: no data for 60s'));
|
|
6870
|
+
}, 60000);
|
|
6871
|
+
};
|
|
7188
6872
|
const onError = (e) => {
|
|
7189
6873
|
cleanup();
|
|
7190
6874
|
try {
|
|
@@ -7196,28 +6880,42 @@ async function download(url, filePath) {
|
|
|
7196
6880
|
};
|
|
7197
6881
|
const onClose = () => {
|
|
7198
6882
|
cleanup();
|
|
6883
|
+
verboseLog(`[download] done: ${received} bytes in ${Date.now() - startedAt}ms`);
|
|
7199
6884
|
resolve();
|
|
7200
6885
|
};
|
|
7201
6886
|
const cleanup = () => {
|
|
6887
|
+
if (inactivityTimer)
|
|
6888
|
+
clearTimeout(inactivityTimer);
|
|
7202
6889
|
writer.off('error', onError);
|
|
7203
6890
|
writer.off('close', onClose);
|
|
7204
6891
|
res.data.off('error', onError);
|
|
6892
|
+
res.data.off('data', onData);
|
|
6893
|
+
};
|
|
6894
|
+
const onData = (chunk) => {
|
|
6895
|
+
received += chunk.length;
|
|
6896
|
+
resetInactivity();
|
|
6897
|
+
if (total > 0) {
|
|
6898
|
+
const pct = Math.floor((received / total) * 10) * 10;
|
|
6899
|
+
if (pct !== lastLoggedPct) {
|
|
6900
|
+
lastLoggedPct = pct;
|
|
6901
|
+
verboseLog(`[download] ${pct}% (${received}/${total})`);
|
|
6902
|
+
}
|
|
6903
|
+
}
|
|
7205
6904
|
};
|
|
7206
6905
|
res.data.on('error', onError);
|
|
6906
|
+
res.data.on('data', onData);
|
|
7207
6907
|
writer.on('error', onError);
|
|
7208
6908
|
writer.on('close', onClose);
|
|
6909
|
+
resetInactivity();
|
|
7209
6910
|
res.data.pipe(writer);
|
|
7210
6911
|
});
|
|
7211
|
-
// 成功
|
|
7212
6912
|
return { ok: true };
|
|
7213
6913
|
}
|
|
7214
6914
|
catch (err) {
|
|
7215
|
-
|
|
6915
|
+
verboseLog('[download] failed:', err?.message);
|
|
7216
6916
|
if (isAxiosError(err) && err.response?.status === 403) {
|
|
7217
|
-
// 不抛出,让上层自行决定
|
|
7218
6917
|
throw new Error('下载链接已过期,请重新进行分包后重试');
|
|
7219
6918
|
}
|
|
7220
|
-
// 其他错误抛出或返回
|
|
7221
6919
|
throw err;
|
|
7222
6920
|
}
|
|
7223
6921
|
}
|
|
@@ -9212,11 +8910,19 @@ const zipCwdToBuffer = (customIgnores = [], targetDir = process.cwd()) => {
|
|
|
9212
8910
|
});
|
|
9213
8911
|
archive.pipe(output);
|
|
9214
8912
|
// 1. 基础忽略列表 (建议保留这些基础规则,防止包过大)
|
|
8913
|
+
//
|
|
8914
|
+
// 注意 `${TTMG_TEMP_DIR}/**` 必须显式写出:archiver 用的 picomatch 在
|
|
8915
|
+
// 没有 `/**` 后缀时只匹配根目录下的同名条目,不会递归匹配目录内容。
|
|
8916
|
+
// 历史 bug:只写 `__TTMG_TEMP__` 时,`__TTMG_TEMP__/wasmcode/<basename>.br`
|
|
8917
|
+
//(prepare 阶段缓存的「原始未插桩 wasm 备份」)等内部文件全部被打进 zip
|
|
8918
|
+
// 推到设备,体积虚胖外,host 在 fallback 路径下还可能错误命中未插桩 wasm。
|
|
8919
|
+
// 同时保留裸名 `TTMG_TEMP_DIR` 以兜底空目录场景。
|
|
9215
8920
|
const defaultIgnores = [
|
|
9216
8921
|
'node_modules/**',
|
|
9217
8922
|
'.git/**',
|
|
9218
8923
|
'.DS_Store',
|
|
9219
8924
|
ttmgPack.TTMG_TEMP_DIR,
|
|
8925
|
+
`${ttmgPack.TTMG_TEMP_DIR}/**`,
|
|
9220
8926
|
'*.zip', // 忽略自身生成的 zip
|
|
9221
8927
|
];
|
|
9222
8928
|
// 2. 合并自定义规则
|
|
@@ -9650,55 +9356,117 @@ const gameCheckRoute = {
|
|
|
9650
9356
|
|
|
9651
9357
|
const changelog = [
|
|
9652
9358
|
{
|
|
9653
|
-
title: '0.3.
|
|
9359
|
+
title: '0.3.9',
|
|
9654
9360
|
target: {
|
|
9655
9361
|
iOS: '>=43.1',
|
|
9656
9362
|
Android: '>=43.1',
|
|
9657
9363
|
},
|
|
9658
9364
|
changes: {
|
|
9659
|
-
|
|
9365
|
+
optimize: [
|
|
9660
9366
|
{
|
|
9661
9367
|
desc: {
|
|
9662
|
-
'zh-CN': '
|
|
9663
|
-
'en-US': '
|
|
9368
|
+
'zh-CN': '调试 IDE 默认使用浅色主题,暗色模式改为手动开启并记忆选择,避免跟随系统强制进入暗色导致不适应',
|
|
9369
|
+
'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.',
|
|
9664
9370
|
},
|
|
9665
|
-
module: '
|
|
9371
|
+
module: 'new',
|
|
9666
9372
|
},
|
|
9667
|
-
],
|
|
9668
|
-
},
|
|
9669
|
-
},
|
|
9670
|
-
{
|
|
9671
|
-
title: '0.3.7',
|
|
9672
|
-
target: {
|
|
9673
|
-
iOS: '>=43.1',
|
|
9674
|
-
Android: '>=43.1',
|
|
9675
|
-
},
|
|
9676
|
-
changes: {
|
|
9677
|
-
feature: [
|
|
9678
9373
|
{
|
|
9679
9374
|
desc: {
|
|
9680
|
-
'zh-CN': '
|
|
9681
|
-
'en-US': '
|
|
9375
|
+
'zh-CN': '优化暗色模式下二维码显示,调整背景避免过亮刺眼,同时保证扫码识别率',
|
|
9376
|
+
'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.',
|
|
9682
9377
|
},
|
|
9683
|
-
module: '
|
|
9378
|
+
module: 'scanQrcode',
|
|
9684
9379
|
},
|
|
9685
|
-
],
|
|
9686
|
-
optimize: [
|
|
9687
9380
|
{
|
|
9688
9381
|
desc: {
|
|
9689
|
-
'zh-CN': '
|
|
9690
|
-
'en-US': '
|
|
9382
|
+
'zh-CN': 'Wasm 分包页面及相关组件补齐多语言,修复页面文案中英文混排的问题',
|
|
9383
|
+
'en-US': 'Complete localization for the Wasm Code Split page and its components, fixing the mixed Chinese/English text.',
|
|
9691
9384
|
},
|
|
9692
|
-
module: '
|
|
9385
|
+
module: 'check',
|
|
9693
9386
|
},
|
|
9694
9387
|
{
|
|
9695
9388
|
desc: {
|
|
9696
|
-
'zh-CN': '
|
|
9697
|
-
'en-US': '
|
|
9389
|
+
'zh-CN': '能力接入助手页面视觉升级:变现模式卡片新增图标与强调色,关键能力标签按「必需 / 可选 / 实验」分级展示,信息层级更清晰',
|
|
9390
|
+
'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.',
|
|
9698
9391
|
},
|
|
9699
|
-
module: '
|
|
9392
|
+
module: 'new',
|
|
9700
9393
|
},
|
|
9701
|
-
|
|
9394
|
+
{
|
|
9395
|
+
desc: {
|
|
9396
|
+
'zh-CN': '完成扫码连接后,首页标题与说明文案会切换为「已连接」状态,状态反馈更明确',
|
|
9397
|
+
'en-US': 'After scanning to connect, the home page title and subtitle switch to a "connected" state for clearer feedback.',
|
|
9398
|
+
},
|
|
9399
|
+
module: 'scanQrcode',
|
|
9400
|
+
},
|
|
9401
|
+
{
|
|
9402
|
+
desc: {
|
|
9403
|
+
'zh-CN': '新版本提示由红点改为「有更新」文字标签,含义更直观;上传游戏包按钮配色与产品主题色统一',
|
|
9404
|
+
'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.',
|
|
9405
|
+
},
|
|
9406
|
+
module: 'new',
|
|
9407
|
+
},
|
|
9408
|
+
],
|
|
9409
|
+
bugfix: [
|
|
9410
|
+
{
|
|
9411
|
+
desc: {
|
|
9412
|
+
'zh-CN': '修复多个页面中区块标题与正文内容未对齐的问题,整体排版更整齐',
|
|
9413
|
+
'en-US': 'Fix section titles that were misaligned with their body content across multiple pages for a tidier layout.',
|
|
9414
|
+
},
|
|
9415
|
+
module: 'new',
|
|
9416
|
+
},
|
|
9417
|
+
],
|
|
9418
|
+
},
|
|
9419
|
+
},
|
|
9420
|
+
{
|
|
9421
|
+
title: '0.3.8',
|
|
9422
|
+
target: {
|
|
9423
|
+
iOS: '>=43.1',
|
|
9424
|
+
Android: '>=43.1',
|
|
9425
|
+
},
|
|
9426
|
+
changes: {
|
|
9427
|
+
bugfix: [
|
|
9428
|
+
{
|
|
9429
|
+
desc: {
|
|
9430
|
+
'zh-CN': '修复 ttmg login 后 Cookie 中混入 Set-Cookie 属性(Path/Domain/Max-Age 等)且 Max-Age=0 的失效同名 cookie 污染有效值的问题,导致 CLI 请求接口鉴权异常;现已与浏览器行为对齐,剥离属性并过滤过期 cookie,已登录用户无需重新登录',
|
|
9431
|
+
'en-US': 'Fix ttmg login storing raw Set-Cookie attributes (Path/Domain/Max-Age, etc.) into the Cookie field, where same-name cookies with Max-Age=0 polluted valid ones and caused auth failures in CLI requests. Now aligned with browser behavior: strip attributes and drop expired cookies. No re-login required.',
|
|
9432
|
+
},
|
|
9433
|
+
module: 'login',
|
|
9434
|
+
},
|
|
9435
|
+
],
|
|
9436
|
+
},
|
|
9437
|
+
},
|
|
9438
|
+
{
|
|
9439
|
+
title: '0.3.7',
|
|
9440
|
+
target: {
|
|
9441
|
+
iOS: '>=43.1',
|
|
9442
|
+
Android: '>=43.1',
|
|
9443
|
+
},
|
|
9444
|
+
changes: {
|
|
9445
|
+
feature: [
|
|
9446
|
+
{
|
|
9447
|
+
desc: {
|
|
9448
|
+
'zh-CN': '新增能力接入助手,帮助开发者基于小游戏变现类型获取关键能力的接入引导',
|
|
9449
|
+
'en-US': 'Add Capability Integration Assistant to guide developers through key capability integration based on the game monetization model',
|
|
9450
|
+
},
|
|
9451
|
+
module: 'new',
|
|
9452
|
+
},
|
|
9453
|
+
],
|
|
9454
|
+
optimize: [
|
|
9455
|
+
{
|
|
9456
|
+
desc: {
|
|
9457
|
+
'zh-CN': '优化上传发布环节,将代码预检与代码上传整合为完整的上传发布流程',
|
|
9458
|
+
'en-US': 'Optimize Upload & Publish by combining code precheck and code upload into a complete publishing flow',
|
|
9459
|
+
},
|
|
9460
|
+
module: 'upload',
|
|
9461
|
+
},
|
|
9462
|
+
{
|
|
9463
|
+
desc: {
|
|
9464
|
+
'zh-CN': '扩充包体大小预检说明,补充整包、主包、独立分包限制规则与详细文档入口',
|
|
9465
|
+
'en-US': 'Expand package size precheck guidance with project, main package, and independent package limits plus a detailed documentation entry',
|
|
9466
|
+
},
|
|
9467
|
+
module: 'check',
|
|
9468
|
+
},
|
|
9469
|
+
],
|
|
9702
9470
|
},
|
|
9703
9471
|
},
|
|
9704
9472
|
{
|
|
@@ -10482,7 +10250,12 @@ const gameUploadRoute = {
|
|
|
10482
10250
|
},
|
|
10483
10251
|
};
|
|
10484
10252
|
|
|
10253
|
+
// 历史遗留:`remotePipeline.ts` 里的老远程分包接口 spread 了 `DEV_HEADERS`。
|
|
10254
|
+
// 目前全局 PPE 走的是 `libs/api/request.ts` 里的 `CLI_PPE_ENV`,这里保持
|
|
10255
|
+
// 相同的值是为了:一旦以后需要按接口粒度覆盖 PPE(例如远程走 PPE、本地
|
|
10256
|
+
// 走线上),只需在这里填回 header、两处值天然一致。
|
|
10485
10257
|
const BASE_URL = 'https://developers.tiktok.com';
|
|
10258
|
+
const WASM_COLLECT_BASE_URL = `${BASE_URL}/api/wasm-collect/v1`;
|
|
10486
10259
|
const DEV_HEADERS = {
|
|
10487
10260
|
// 'x-use-ppe': '1',
|
|
10488
10261
|
// 'x-tt-env': UNITY_PPE_ENV,
|
|
@@ -10505,6 +10278,8 @@ const UNITY_WASM_SPLIT_CONFIG_FIELD_SCHEME = {
|
|
|
10505
10278
|
WASMSPLITVERSION: `"$WASMSPLITVERSION"`,
|
|
10506
10279
|
ENABLEWASMSPLIT: `"$ENABLEWASMSPLIT"`,
|
|
10507
10280
|
IOS_SUB_JS_FILE_CONFIG: `"$IOS_SUB_JS_FILE_CONFIG"`,
|
|
10281
|
+
ENABLEARCHIVEMODE: `"$ENABLEARCHIVEMODE"`,
|
|
10282
|
+
ARCHIVE_CODE_FILE_MD5: `$ARCHIVE_CODE_FILE_MD5`,
|
|
10508
10283
|
};
|
|
10509
10284
|
|
|
10510
10285
|
const DIR_SPLIT = 'split';
|
|
@@ -10532,7 +10307,34 @@ const WASM_SPLIT_SUBPACKAGE_CONFIG = {
|
|
|
10532
10307
|
name: 'wasmcode1-ios',
|
|
10533
10308
|
root: 'wasmcode1-ios/',
|
|
10534
10309
|
},
|
|
10310
|
+
// archive 模式 split 产物:wasmcode/(主包,包含原文件名以外的 main wasm)+
|
|
10311
|
+
// 下面这两个独立子包目录。回滚 / 取消时必须把它们一起清掉,否则下次构建仍会
|
|
10312
|
+
// 把旧的 sub/archive 文件打进 zip 推到设备。
|
|
10313
|
+
archiveSub: {
|
|
10314
|
+
name: 'wasmcode1',
|
|
10315
|
+
root: 'wasmcode1/',
|
|
10316
|
+
},
|
|
10317
|
+
archiveCode: {
|
|
10318
|
+
name: 'wasmcode-archive',
|
|
10319
|
+
root: 'wasmcode-archive/',
|
|
10320
|
+
},
|
|
10535
10321
|
};
|
|
10322
|
+
/**
|
|
10323
|
+
* 所有由 split 流程生成、应在 cancel / reset 时被整目录删除的产物目录。
|
|
10324
|
+
* 注意不包含 `origin`(wasmcode/)—— 那是 Unity 原生输出目录,回滚时只能
|
|
10325
|
+
* 把里面的 split 产物 .br 清掉、再把原始 wasm 拷回去,整个删掉会破坏工程。
|
|
10326
|
+
*
|
|
10327
|
+
* 单一事实来源:`restoreFromCache` / `resetWasmSplit` / `cancelSplit` 都从
|
|
10328
|
+
* 这里读取,避免新增模式时漏改某一处导致旧产物泄漏到 zip。
|
|
10329
|
+
*/
|
|
10330
|
+
const SPLIT_OUTPUT_DIRS = [
|
|
10331
|
+
WASM_SPLIT_SUBPACKAGE_CONFIG.androidMain.root,
|
|
10332
|
+
WASM_SPLIT_SUBPACKAGE_CONFIG.androidSub.root,
|
|
10333
|
+
WASM_SPLIT_SUBPACKAGE_CONFIG.ios.root,
|
|
10334
|
+
WASM_SPLIT_SUBPACKAGE_CONFIG.iosSub.root,
|
|
10335
|
+
WASM_SPLIT_SUBPACKAGE_CONFIG.archiveSub.root,
|
|
10336
|
+
WASM_SPLIT_SUBPACKAGE_CONFIG.archiveCode.root,
|
|
10337
|
+
];
|
|
10536
10338
|
const WASM_FILENAME_SUFFIX = '.webgl.wasm.code.unityweb.wasm';
|
|
10537
10339
|
const BR_SUFFIX = '.br';
|
|
10538
10340
|
// 输出 JSON 格式
|
|
@@ -10543,69 +10345,6 @@ const CONCURRENCY_LIMIT = 2;
|
|
|
10543
10345
|
const DOWNLOAD_RETRY = 3;
|
|
10544
10346
|
const WASM_SPLIT_CONFIG_FILE_NAME = 'webgl-wasm-split.js';
|
|
10545
10347
|
|
|
10546
|
-
// prepare.ts
|
|
10547
|
-
// 若你的 request 是 axios:你可以添加 maxBodyLength/ maxContentLength 等参数
|
|
10548
|
-
// 若是 got:可直接传 form 实例
|
|
10549
|
-
async function startPrepare(params) {
|
|
10550
|
-
const form = new FormData$1();
|
|
10551
|
-
form.append('desc', params.desc);
|
|
10552
|
-
form.append('wasm_md5', params.wasm_md5);
|
|
10553
|
-
form.append('with_ios', 'true');
|
|
10554
|
-
// 二进制字段:用 ReadStream(推荐)或 Buffer
|
|
10555
|
-
form.append('wasm_file', fs$1.createReadStream(path$1.join(process.cwd(), params.wasm_file_path)), {
|
|
10556
|
-
filename: path$1.basename(params.wasm_file_path),
|
|
10557
|
-
// 部分后端会依赖 content-type;如果不确定就用 application/octet-stream
|
|
10558
|
-
contentType: 'application/wasm',
|
|
10559
|
-
});
|
|
10560
|
-
/**
|
|
10561
|
-
* 兼容 WASM_SYMBOL_FILE_NAME
|
|
10562
|
-
* case 1:项目根目录有 webgl.symbols.json 文件
|
|
10563
|
-
* case 2:项目根目录没有 webgl.symbols.json 文件,但 TTMG_TEMP_DIR 有
|
|
10564
|
-
* 优先读 2
|
|
10565
|
-
*/
|
|
10566
|
-
let symbolFilePath = path$1.join(process.cwd(), TTMG_TEMP_DIR, WASM_SYMBOL_FILE_NAME);
|
|
10567
|
-
if (!fs$1.existsSync(symbolFilePath)) {
|
|
10568
|
-
symbolFilePath = path$1.join(process.cwd(), WASM_SYMBOL_FILE_NAME);
|
|
10569
|
-
}
|
|
10570
|
-
/**
|
|
10571
|
-
* 判断是否有 symbol 文件,有则上传,没有直接接口报错
|
|
10572
|
-
*/
|
|
10573
|
-
if (!fs$1.existsSync(symbolFilePath)) {
|
|
10574
|
-
return {
|
|
10575
|
-
error: {
|
|
10576
|
-
code: 400,
|
|
10577
|
-
message: `${WASM_SYMBOL_FILE_NAME} not found at ${path$1.join(process.cwd())},use unity plugin to rebuild`,
|
|
10578
|
-
client_key: params.client_key,
|
|
10579
|
-
},
|
|
10580
|
-
data: null,
|
|
10581
|
-
ctx: {
|
|
10582
|
-
logid: '',
|
|
10583
|
-
httpStatusCode: 400,
|
|
10584
|
-
},
|
|
10585
|
-
};
|
|
10586
|
-
}
|
|
10587
|
-
form.append('wasm_symbol_file', fs$1.createReadStream(symbolFilePath), {
|
|
10588
|
-
filename: WASM_SYMBOL_FILE_NAME,
|
|
10589
|
-
contentType: 'application/octet-stream',
|
|
10590
|
-
});
|
|
10591
|
-
// 关键:用 form.getHeaders() 获取带 boundary 的 Content-Type
|
|
10592
|
-
const formHeaders = form.getHeaders();
|
|
10593
|
-
return request({
|
|
10594
|
-
url: `${BASE_URL}/api/stark_wasm/v4/post/prepare`,
|
|
10595
|
-
method: 'POST',
|
|
10596
|
-
headers: {
|
|
10597
|
-
...DEV_HEADERS,
|
|
10598
|
-
...formHeaders, // 包含正确的 multipart/form-data; boundary=...
|
|
10599
|
-
},
|
|
10600
|
-
params: {
|
|
10601
|
-
client_key: params.client_key,
|
|
10602
|
-
with_ios: true,
|
|
10603
|
-
},
|
|
10604
|
-
data: form,
|
|
10605
|
-
// 若 request 基于 axios,建议加上以下两项以支持大文件:
|
|
10606
|
-
});
|
|
10607
|
-
}
|
|
10608
|
-
|
|
10609
10348
|
async function withRetry(fn, retries = 3) {
|
|
10610
10349
|
let lastErr;
|
|
10611
10350
|
for (let i = 0; i < retries; i++) {
|
|
@@ -10622,18 +10361,26 @@ async function withRetry(fn, retries = 3) {
|
|
|
10622
10361
|
}
|
|
10623
10362
|
|
|
10624
10363
|
function updateWasmSplitConfig(fields) {
|
|
10364
|
+
const configFilePath = path.join(process.cwd(), WASM_SPLIT_CONFIG_FILE_NAME);
|
|
10365
|
+
let config = fs.readFileSync(configFilePath, 'utf-8');
|
|
10625
10366
|
for (const field in fields) {
|
|
10626
10367
|
const value = fields[field];
|
|
10627
|
-
const isString = typeof value === 'string';
|
|
10628
|
-
const valueStr = isString ? value : String(value);
|
|
10629
|
-
const configFilePath = path.join(process.cwd(), WASM_SPLIT_CONFIG_FILE_NAME);
|
|
10630
10368
|
const placeholder = UNITY_WASM_SPLIT_CONFIG_FIELD_SCHEME[field];
|
|
10631
|
-
|
|
10632
|
-
|
|
10633
|
-
|
|
10634
|
-
|
|
10635
|
-
|
|
10369
|
+
if (!placeholder)
|
|
10370
|
+
continue;
|
|
10371
|
+
let replacement;
|
|
10372
|
+
if (typeof value === 'boolean' || typeof value === 'number') {
|
|
10373
|
+
replacement = String(value);
|
|
10374
|
+
}
|
|
10375
|
+
else if (typeof value === 'string') {
|
|
10376
|
+
replacement = value;
|
|
10377
|
+
}
|
|
10378
|
+
else {
|
|
10379
|
+
replacement = String(value);
|
|
10380
|
+
}
|
|
10381
|
+
config = config.replace(placeholder, replacement);
|
|
10636
10382
|
}
|
|
10383
|
+
fs.writeFileSync(configFilePath, config, 'utf-8');
|
|
10637
10384
|
}
|
|
10638
10385
|
|
|
10639
10386
|
async function compressWasmFile(wasmFilePath, compressedFilePath) {
|
|
@@ -10645,7 +10392,11 @@ async function compressWasmFile(wasmFilePath, compressedFilePath) {
|
|
|
10645
10392
|
}
|
|
10646
10393
|
function compressArrayBuffer(arrayBuffer) {
|
|
10647
10394
|
return new Promise((resolve, reject) => {
|
|
10648
|
-
const compressStream = zlib.createBrotliCompress(
|
|
10395
|
+
const compressStream = zlib.createBrotliCompress({
|
|
10396
|
+
params: {
|
|
10397
|
+
[zlib.constants.BROTLI_PARAM_QUALITY]: 9,
|
|
10398
|
+
},
|
|
10399
|
+
});
|
|
10649
10400
|
compressStream.write(Buffer.from(arrayBuffer));
|
|
10650
10401
|
compressStream.end();
|
|
10651
10402
|
const compressedChunks = [];
|
|
@@ -10676,189 +10427,982 @@ function keepCacheSync({ entryDir, originalWasmPath, originalSplitConfigPath, })
|
|
|
10676
10427
|
if (!fs__namespace.existsSync(gameJsonCachePath)) {
|
|
10677
10428
|
fs__namespace.copyFileSync(gameJsonPath, gameJsonCachePath);
|
|
10678
10429
|
}
|
|
10430
|
+
/**
|
|
10431
|
+
* 保存 wasmcode/game.js(如果存在)
|
|
10432
|
+
*
|
|
10433
|
+
* 抖音 / 微信 mini-game 平台对 game.json.subpackages 里声明的每个子包
|
|
10434
|
+
* 都要求根目录有 `game.js`(哪怕是空文件)作为 `tt.loadSubpackage` 的
|
|
10435
|
+
* 入口锚点,否则子包加载直接失败。很多 Unity 项目把 wasmcode 当成
|
|
10436
|
+
* 一个内置子包(参考真实工程的 game.json),原始 wasmcode/ 里就有
|
|
10437
|
+
* 一个空 game.js。split 阶段虽然也会重写它,但回退时如果不把这个
|
|
10438
|
+
* 占位文件还原回来,mini-game 启动会因为找不到 wasmcode/game.js
|
|
10439
|
+
* 而崩。
|
|
10440
|
+
*
|
|
10441
|
+
* 这里只在源文件存在时才备份,避免给"原始就没有 game.js"的工程
|
|
10442
|
+
* 偷偷塞一个空文件污染回退状态。
|
|
10443
|
+
*/
|
|
10444
|
+
const originGameJsPath = path__namespace.join(entryDir, WASM_SPLIT_SUBPACKAGE_CONFIG.origin.root, 'game.js');
|
|
10445
|
+
const originGameJsCachePath = path__namespace.join(cacheDir, 'wasmcode-game.js');
|
|
10446
|
+
if (fs__namespace.existsSync(originGameJsPath) &&
|
|
10447
|
+
!fs__namespace.existsSync(originGameJsCachePath)) {
|
|
10448
|
+
fs__namespace.copyFileSync(originGameJsPath, originGameJsCachePath);
|
|
10449
|
+
}
|
|
10679
10450
|
return {
|
|
10680
10451
|
cacheDir,
|
|
10681
10452
|
};
|
|
10682
10453
|
}
|
|
10683
10454
|
|
|
10684
|
-
|
|
10685
|
-
|
|
10686
|
-
|
|
10687
|
-
|
|
10688
|
-
|
|
10689
|
-
|
|
10690
|
-
|
|
10691
|
-
|
|
10692
|
-
|
|
10693
|
-
|
|
10694
|
-
|
|
10695
|
-
|
|
10696
|
-
|
|
10697
|
-
|
|
10698
|
-
|
|
10699
|
-
|
|
10700
|
-
|
|
10701
|
-
|
|
10702
|
-
|
|
10703
|
-
|
|
10704
|
-
|
|
10705
|
-
|
|
10706
|
-
|
|
10707
|
-
|
|
10708
|
-
|
|
10709
|
-
|
|
10710
|
-
|
|
10711
|
-
|
|
10712
|
-
|
|
10713
|
-
|
|
10714
|
-
|
|
10715
|
-
|
|
10716
|
-
|
|
10717
|
-
|
|
10718
|
-
|
|
10719
|
-
|
|
10720
|
-
|
|
10721
|
-
|
|
10722
|
-
|
|
10723
|
-
|
|
10724
|
-
|
|
10725
|
-
|
|
10726
|
-
|
|
10727
|
-
|
|
10728
|
-
|
|
10729
|
-
|
|
10730
|
-
|
|
10731
|
-
|
|
10732
|
-
|
|
10733
|
-
|
|
10734
|
-
|
|
10735
|
-
*/
|
|
10736
|
-
wsServer.sendUnitySplitStatus({
|
|
10737
|
-
status: 'start_compress_prepared_wasm',
|
|
10738
|
-
});
|
|
10739
|
-
await compressWasmFile(tempWasmPath, willReplaceWasmPath);
|
|
10740
|
-
wsServer.sendUnitySplitStatus({
|
|
10741
|
-
status: 'compress_prepared_wasm_done',
|
|
10742
|
-
url: downloadUrl,
|
|
10743
|
-
});
|
|
10744
|
-
wsServer.sendUnitySplitStatus({
|
|
10745
|
-
status: 'write_compress_prepared_wasm_done',
|
|
10746
|
-
});
|
|
10455
|
+
/**
|
|
10456
|
+
* Restore webgl-wasm-split.js from the cached original (with placeholders).
|
|
10457
|
+
* Called at the start of each prepare so that pipeline-specific values can be
|
|
10458
|
+
* applied deterministically, regardless of previous runs.
|
|
10459
|
+
* No-op if the cache does not yet exist (first run).
|
|
10460
|
+
*/
|
|
10461
|
+
function restoreSplitConfigFromCache(entryDir = process.cwd()) {
|
|
10462
|
+
const cachedConfigPath = path.join(entryDir, WASM_SPLIT_CACHE_DIR, path.basename(WASM_SPLIT_CONFIG_FILE_NAME));
|
|
10463
|
+
const targetConfigPath = path.join(entryDir, WASM_SPLIT_CONFIG_FILE_NAME);
|
|
10464
|
+
if (fs.existsSync(cachedConfigPath)) {
|
|
10465
|
+
fs.copyFileSync(cachedConfigPath, targetConfigPath);
|
|
10466
|
+
}
|
|
10467
|
+
}
|
|
10468
|
+
|
|
10469
|
+
/**
|
|
10470
|
+
* Restore the project from the backup cache:
|
|
10471
|
+
* - original (unmodified) wasm file back into its `wasmcode/<file>.br` location
|
|
10472
|
+
* - webgl-wasm-split.js back to its template (with placeholders)
|
|
10473
|
+
* - game.json back to its pre-split version
|
|
10474
|
+
* - wasmcode/game.js — restore from cache if backed up, otherwise write empty
|
|
10475
|
+
* (mini-game runtime requires every subpackage root to contain `game.js` as
|
|
10476
|
+
* a `tt.loadSubpackage` entry anchor; many Unity projects ship with
|
|
10477
|
+
* wasmcode declared as a subpackage in game.json, so a missing wasmcode/game.js
|
|
10478
|
+
* after rollback breaks subpackage loading at boot)
|
|
10479
|
+
* - remove ALL generated sub-package directories (see SPLIT_OUTPUT_DIRS) —
|
|
10480
|
+
* includes both legacy (wasmcode-android / wasmcode1-android / wasmcode-ios /
|
|
10481
|
+
* wasmcode1-ios) and archive-mode (wasmcode1 / wasmcode-archive) outputs
|
|
10482
|
+
* - clean stale split-produced .br files inside wasmcode/ (split phase writes
|
|
10483
|
+
* `${main_wasm_md5}.webgl...br` next to the original `${orig_md5}.webgl...br`;
|
|
10484
|
+
* we wipe everything in there and re-copy the cached original so the dir
|
|
10485
|
+
* ends up byte-identical to the pre-prepare state)
|
|
10486
|
+
*
|
|
10487
|
+
* Shared by both local and remote reset/rollback flows. Single source of
|
|
10488
|
+
* truth for "what does cancel actually undo" — adding a new split-output
|
|
10489
|
+
* dir means appending it to SPLIT_OUTPUT_DIRS, no other call site needs
|
|
10490
|
+
* to change.
|
|
10491
|
+
*/
|
|
10492
|
+
function restoreFromCache(entryDir = process.cwd()) {
|
|
10493
|
+
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
|
+
const originDir = path.join(entryDir, WASM_SPLIT_SUBPACKAGE_CONFIG.origin.root);
|
|
10498
|
+
if (fs.existsSync(originDir)) {
|
|
10499
|
+
for (const entry of fs.readdirSync(originDir)) {
|
|
10500
|
+
// Only clean files split is known to write — `.br` (main wasm) and
|
|
10501
|
+
// the empty `game.js` placeholder. Touching anything else risks
|
|
10502
|
+
// nuking developer-authored content that happens to live in
|
|
10503
|
+
// wasmcode/ for unrelated reasons.
|
|
10504
|
+
if (entry.endsWith('.br') || entry === 'game.js') {
|
|
10505
|
+
fs.rmSync(path.join(originDir, entry), { force: true });
|
|
10747
10506
|
}
|
|
10748
|
-
|
|
10749
|
-
|
|
10750
|
-
|
|
10751
|
-
|
|
10752
|
-
|
|
10753
|
-
|
|
10754
|
-
|
|
10755
|
-
|
|
10756
|
-
|
|
10757
|
-
|
|
10758
|
-
|
|
10759
|
-
|
|
10760
|
-
|
|
10761
|
-
|
|
10762
|
-
|
|
10763
|
-
|
|
10507
|
+
}
|
|
10508
|
+
}
|
|
10509
|
+
if (fs.existsSync(cacheDir)) {
|
|
10510
|
+
const targetWasmBrPath = fs
|
|
10511
|
+
.readdirSync(cacheDir)
|
|
10512
|
+
.find(item => item.endsWith('.br'));
|
|
10513
|
+
if (targetWasmBrPath) {
|
|
10514
|
+
const destWasmBrPath = path.join(entryDir, WASM_SPLIT_SUBPACKAGE_CONFIG.origin.root, path.basename(targetWasmBrPath));
|
|
10515
|
+
ensureDirSync(path.dirname(destWasmBrPath));
|
|
10516
|
+
fs.copyFileSync(path.join(cacheDir, targetWasmBrPath), destWasmBrPath);
|
|
10517
|
+
}
|
|
10518
|
+
}
|
|
10519
|
+
const splitConfigCachePath = path.join(cacheDir, WASM_SPLIT_CONFIG_FILE_NAME);
|
|
10520
|
+
if (fs.existsSync(splitConfigCachePath)) {
|
|
10521
|
+
fs.copyFileSync(splitConfigCachePath, path.join(entryDir, WASM_SPLIT_CONFIG_FILE_NAME));
|
|
10522
|
+
}
|
|
10523
|
+
const gameJsonCachePath = path.join(cacheDir, 'game.json');
|
|
10524
|
+
if (fs.existsSync(gameJsonCachePath)) {
|
|
10525
|
+
fs.copyFileSync(gameJsonCachePath, path.join(entryDir, 'game.json'));
|
|
10526
|
+
}
|
|
10527
|
+
// Restore wasmcode/game.js. We just deleted whatever was there in step 1,
|
|
10528
|
+
// so we always need to put something back when wasmcode is a subpackage.
|
|
10529
|
+
// Strategy:
|
|
10530
|
+
// - Prefer the cache (keepCacheSync stashes pre-split contents to
|
|
10531
|
+
// `__unity_cache__/wasmcode-game.js` when the original existed)
|
|
10532
|
+
// - Fall back to writing an empty file. Rationale: if we got here the
|
|
10533
|
+
// dir exists, the .br is in place, and the project's game.json — now
|
|
10534
|
+
// restored above — likely still lists wasmcode as a subpackage (true
|
|
10535
|
+
// for every Unity template we ship). An empty game.js is exactly what
|
|
10536
|
+
// downloadSplited.ts also writes; it satisfies the platform requirement
|
|
10537
|
+
// without changing semantics for projects that don't use wasmcode as
|
|
10538
|
+
// a subpackage (the file is harmless empty).
|
|
10539
|
+
const originGameJsCachePath = path.join(cacheDir, 'wasmcode-game.js');
|
|
10540
|
+
const originGameJsDestPath = path.join(originDir, 'game.js');
|
|
10541
|
+
if (fs.existsSync(originDir)) {
|
|
10542
|
+
if (fs.existsSync(originGameJsCachePath)) {
|
|
10543
|
+
fs.copyFileSync(originGameJsCachePath, originGameJsDestPath);
|
|
10764
10544
|
}
|
|
10765
10545
|
else {
|
|
10766
|
-
|
|
10767
|
-
isSuccess: false,
|
|
10768
|
-
error: {
|
|
10769
|
-
code: res.data?.code,
|
|
10770
|
-
message: res.data?.message,
|
|
10771
|
-
},
|
|
10772
|
-
ctx: res?.ctx,
|
|
10773
|
-
};
|
|
10546
|
+
fs.writeFileSync(originGameJsDestPath, '', 'utf-8');
|
|
10774
10547
|
}
|
|
10775
10548
|
}
|
|
10776
|
-
|
|
10777
|
-
|
|
10778
|
-
|
|
10779
|
-
|
|
10780
|
-
|
|
10781
|
-
message: error.message,
|
|
10782
|
-
},
|
|
10783
|
-
ctx: res?.ctx,
|
|
10784
|
-
};
|
|
10549
|
+
for (const subDir of SPLIT_OUTPUT_DIRS) {
|
|
10550
|
+
const full = path.join(entryDir, subDir);
|
|
10551
|
+
if (fs.existsSync(full)) {
|
|
10552
|
+
fs.rmSync(full, { recursive: true, force: true });
|
|
10553
|
+
}
|
|
10785
10554
|
}
|
|
10786
10555
|
}
|
|
10787
10556
|
|
|
10788
|
-
async function
|
|
10789
|
-
|
|
10790
|
-
|
|
10791
|
-
|
|
10792
|
-
|
|
10793
|
-
|
|
10794
|
-
|
|
10795
|
-
|
|
10796
|
-
}
|
|
10557
|
+
async function decompressWasmFile(inputPath, outputPath) {
|
|
10558
|
+
const compressed = await fs.promises.readFile(inputPath);
|
|
10559
|
+
const decompressed = await new Promise((resolve, reject) => {
|
|
10560
|
+
zlib.brotliDecompress(compressed, (err, result) => {
|
|
10561
|
+
if (err)
|
|
10562
|
+
reject(err);
|
|
10563
|
+
else
|
|
10564
|
+
resolve(result);
|
|
10565
|
+
});
|
|
10797
10566
|
});
|
|
10567
|
+
await fs.promises.writeFile(outputPath, decompressed);
|
|
10798
10568
|
}
|
|
10799
10569
|
|
|
10800
|
-
|
|
10801
|
-
|
|
10802
|
-
|
|
10803
|
-
method: 'POST',
|
|
10804
|
-
data: {
|
|
10805
|
-
client_key,
|
|
10806
|
-
wasm_md5,
|
|
10807
|
-
},
|
|
10808
|
-
headers: DEV_HEADERS,
|
|
10809
|
-
});
|
|
10570
|
+
function computeFileMd5Sync(filePath) {
|
|
10571
|
+
const content = fs.readFileSync(filePath);
|
|
10572
|
+
return crypto.createHash('md5').update(content).digest('hex');
|
|
10810
10573
|
}
|
|
10811
10574
|
|
|
10812
|
-
|
|
10813
|
-
|
|
10814
|
-
|
|
10815
|
-
|
|
10816
|
-
|
|
10817
|
-
|
|
10818
|
-
|
|
10819
|
-
|
|
10820
|
-
}
|
|
10821
|
-
|
|
10575
|
+
let cached = null;
|
|
10576
|
+
function getGameJson() {
|
|
10577
|
+
if (cached)
|
|
10578
|
+
return cached;
|
|
10579
|
+
const filePath = path$1.join(process.cwd(), 'game.json');
|
|
10580
|
+
if (fs$1.existsSync(filePath)) {
|
|
10581
|
+
try {
|
|
10582
|
+
cached = JSON.parse(fs$1.readFileSync(filePath, 'utf-8'));
|
|
10583
|
+
}
|
|
10584
|
+
catch {
|
|
10585
|
+
cached = {};
|
|
10586
|
+
}
|
|
10587
|
+
}
|
|
10588
|
+
else {
|
|
10589
|
+
cached = {};
|
|
10590
|
+
}
|
|
10591
|
+
return cached;
|
|
10822
10592
|
}
|
|
10823
10593
|
|
|
10824
|
-
|
|
10825
|
-
|
|
10826
|
-
return request({
|
|
10827
|
-
url: `${BASE_URL}/api/stark_wasm/v4/post/split`,
|
|
10828
|
-
method: 'POST',
|
|
10829
|
-
headers: {
|
|
10830
|
-
...DEV_HEADERS,
|
|
10831
|
-
},
|
|
10832
|
-
data: {
|
|
10833
|
-
client_key,
|
|
10834
|
-
wasm_md5,
|
|
10835
|
-
},
|
|
10836
|
-
});
|
|
10594
|
+
function metaFilePath(entryDir = process.cwd()) {
|
|
10595
|
+
return path__namespace$1.join(entryDir, TTMG_TEMP_DIR, 'prepared-meta.json');
|
|
10837
10596
|
}
|
|
10838
|
-
|
|
10839
|
-
|
|
10840
|
-
|
|
10841
|
-
|
|
10842
|
-
|
|
10843
|
-
|
|
10844
|
-
|
|
10845
|
-
|
|
10846
|
-
next;
|
|
10847
|
-
|
|
10848
|
-
constructor(value) {
|
|
10849
|
-
this.value = value;
|
|
10850
|
-
}
|
|
10597
|
+
function writePreparedMeta(meta, entryDir = process.cwd()) {
|
|
10598
|
+
const target = metaFilePath(entryDir);
|
|
10599
|
+
fs__namespace$1.mkdirSync(path__namespace$1.dirname(target), { recursive: true });
|
|
10600
|
+
const payload = {
|
|
10601
|
+
...meta,
|
|
10602
|
+
preparedAt: new Date().toISOString(),
|
|
10603
|
+
};
|
|
10604
|
+
fs__namespace$1.writeFileSync(target, JSON.stringify(payload, null, 2), 'utf-8');
|
|
10851
10605
|
}
|
|
10852
|
-
|
|
10853
|
-
|
|
10854
|
-
|
|
10855
|
-
|
|
10856
|
-
|
|
10857
|
-
|
|
10858
|
-
|
|
10859
|
-
|
|
10860
|
-
|
|
10861
|
-
|
|
10606
|
+
function readPreparedMeta(entryDir = process.cwd()) {
|
|
10607
|
+
const target = metaFilePath(entryDir);
|
|
10608
|
+
if (!fs__namespace$1.existsSync(target))
|
|
10609
|
+
return null;
|
|
10610
|
+
try {
|
|
10611
|
+
const raw = fs__namespace$1.readFileSync(target, 'utf-8');
|
|
10612
|
+
const parsed = JSON.parse(raw);
|
|
10613
|
+
if (typeof parsed?.preparedWasmMd5 === 'string' &&
|
|
10614
|
+
typeof parsed?.codePath === 'string' &&
|
|
10615
|
+
parsed.preparedWasmMd5.length === 32) {
|
|
10616
|
+
return parsed;
|
|
10617
|
+
}
|
|
10618
|
+
return null;
|
|
10619
|
+
}
|
|
10620
|
+
catch {
|
|
10621
|
+
return null;
|
|
10622
|
+
}
|
|
10623
|
+
}
|
|
10624
|
+
/**
|
|
10625
|
+
* Return the current md5 of the wasm file referenced by `prepared-meta.json`
|
|
10626
|
+
* or null if the file is missing / meta isn't present. Caller compares the
|
|
10627
|
+
* result to `meta.preparedWasmMd5` — mismatch means the project's wasm
|
|
10628
|
+
* has drifted from the prepared output (Unity re-build etc.) and the
|
|
10629
|
+
* project should be walked back through the prepare step before collect
|
|
10630
|
+
* can produce useful data.
|
|
10631
|
+
*/
|
|
10632
|
+
function computeCurrentProjectWasmMd5(entryDir = process.cwd()) {
|
|
10633
|
+
const meta = readPreparedMeta(entryDir);
|
|
10634
|
+
if (!meta)
|
|
10635
|
+
return null;
|
|
10636
|
+
const absolutePath = path__namespace$1.join(entryDir, meta.codePath);
|
|
10637
|
+
if (!fs__namespace$1.existsSync(absolutePath))
|
|
10638
|
+
return null;
|
|
10639
|
+
const currentMd5 = crypto$1
|
|
10640
|
+
.createHash('md5')
|
|
10641
|
+
.update(fs__namespace$1.readFileSync(absolutePath))
|
|
10642
|
+
.digest('hex');
|
|
10643
|
+
return { meta, currentMd5 };
|
|
10644
|
+
}
|
|
10645
|
+
|
|
10646
|
+
const state = {
|
|
10647
|
+
pipelineMode: 'local',
|
|
10648
|
+
originalWasmPath: '',
|
|
10649
|
+
preparedWasmPath: '',
|
|
10650
|
+
codePath: '',
|
|
10651
|
+
splitOutputDir: '',
|
|
10652
|
+
splitMeta: null,
|
|
10653
|
+
totalWasmFuncCount: 0,
|
|
10654
|
+
wasmSize: 0,
|
|
10655
|
+
isArchiveMode: true,
|
|
10656
|
+
};
|
|
10657
|
+
function getLocalState() {
|
|
10658
|
+
return state;
|
|
10659
|
+
}
|
|
10660
|
+
function setLocalState(partial) {
|
|
10661
|
+
Object.assign(state, partial);
|
|
10662
|
+
}
|
|
10663
|
+
|
|
10664
|
+
async function startPrepare$1(params) {
|
|
10665
|
+
const tempDir = path$1.join(process.cwd(), TTMG_TEMP_DIR);
|
|
10666
|
+
ensureDirSync(tempDir);
|
|
10667
|
+
const inputPath = path$1.join(process.cwd(), params.wasm_file_path);
|
|
10668
|
+
let rawWasmPath = path$1.join(tempDir, 'original.wasm');
|
|
10669
|
+
if (inputPath.endsWith('.br')) {
|
|
10670
|
+
await decompressWasmFile(inputPath, rawWasmPath);
|
|
10671
|
+
}
|
|
10672
|
+
else {
|
|
10673
|
+
fs$1.copyFileSync(inputPath, rawWasmPath);
|
|
10674
|
+
}
|
|
10675
|
+
const preparedWasmPath = path$1.join(tempDir, 'prepared.wasm');
|
|
10676
|
+
try {
|
|
10677
|
+
const result = ttmgWasmtool.prepare(rawWasmPath, preparedWasmPath);
|
|
10678
|
+
verboseLog(`[wasmtool] prepare done: ${result.outputSize} bytes, ${result.timeCost}s`);
|
|
10679
|
+
const gameJson = getGameJson();
|
|
10680
|
+
const totalWasmFuncCount = gameJson.wasmFuncCount ?? 0;
|
|
10681
|
+
const wasmSize = fs$1.existsSync(inputPath)
|
|
10682
|
+
? fs$1.statSync(inputPath).size
|
|
10683
|
+
: 0;
|
|
10684
|
+
setLocalState({
|
|
10685
|
+
originalWasmPath: rawWasmPath,
|
|
10686
|
+
preparedWasmPath,
|
|
10687
|
+
codePath: params.wasm_file_path,
|
|
10688
|
+
totalWasmFuncCount,
|
|
10689
|
+
wasmSize,
|
|
10690
|
+
});
|
|
10691
|
+
keepCacheSync({
|
|
10692
|
+
entryDir: process.cwd(),
|
|
10693
|
+
originalWasmPath: params.wasm_file_path,
|
|
10694
|
+
originalSplitConfigPath: WASM_SPLIT_CONFIG_FILE_NAME,
|
|
10695
|
+
});
|
|
10696
|
+
// Start from cached (placeholder) config so pipeline switching is deterministic
|
|
10697
|
+
restoreSplitConfigFromCache();
|
|
10698
|
+
const willReplaceWasmPath = path$1.join(process.cwd(), params.wasm_file_path);
|
|
10699
|
+
// Diagnostic: prove prepare actually produced a different binary
|
|
10700
|
+
// (size should grow noticeably because every function body is prefixed
|
|
10701
|
+
// with a scwebgl.logCall(funcIndex) call).
|
|
10702
|
+
const rawSize = fs$1.existsSync(rawWasmPath) ? fs$1.statSync(rawWasmPath).size : 0;
|
|
10703
|
+
const preparedSize = fs$1.existsSync(preparedWasmPath)
|
|
10704
|
+
? fs$1.statSync(preparedWasmPath).size
|
|
10705
|
+
: 0;
|
|
10706
|
+
const rawMd5 = fs$1.existsSync(rawWasmPath)
|
|
10707
|
+
? crypto$1.createHash('md5').update(fs$1.readFileSync(rawWasmPath)).digest('hex')
|
|
10708
|
+
: '<missing>';
|
|
10709
|
+
const preparedMd5 = fs$1.existsSync(preparedWasmPath)
|
|
10710
|
+
? crypto$1.createHash('md5').update(fs$1.readFileSync(preparedWasmPath)).digest('hex')
|
|
10711
|
+
: '<missing>';
|
|
10712
|
+
verboseLog(`[wasmtool] prepare sanity: raw(size=${rawSize} md5=${rawMd5}) -> prepared(size=${preparedSize} md5=${preparedMd5}) delta=${preparedSize - rawSize}`);
|
|
10713
|
+
if (preparedSize <= rawSize || preparedMd5 === rawMd5) {
|
|
10714
|
+
verboseWarn('[wasmtool] WARNING: prepared wasm is not larger / md5 is unchanged vs raw wasm. Instrumentation likely did not happen.');
|
|
10715
|
+
}
|
|
10716
|
+
verboseLog('[wasmtool] compressing prepared wasm (quality=9)...');
|
|
10717
|
+
await compressWasmFile(preparedWasmPath, willReplaceWasmPath);
|
|
10718
|
+
verboseLog('[wasmtool] compressed and written to project');
|
|
10719
|
+
// Diagnostic: confirm the file the client actually fetches was overwritten,
|
|
10720
|
+
// and compare to the cached original brotli so we can prove on-disk replacement.
|
|
10721
|
+
const replacedSize = fs$1.existsSync(willReplaceWasmPath)
|
|
10722
|
+
? fs$1.statSync(willReplaceWasmPath).size
|
|
10723
|
+
: 0;
|
|
10724
|
+
const replacedMd5 = fs$1.existsSync(willReplaceWasmPath)
|
|
10725
|
+
? crypto$1.createHash('md5').update(fs$1.readFileSync(willReplaceWasmPath)).digest('hex')
|
|
10726
|
+
: '<missing>';
|
|
10727
|
+
const cachedOriginalBr = path$1.join(process.cwd(), TTMG_TEMP_DIR, 'wasmcode', path$1.basename(params.wasm_file_path));
|
|
10728
|
+
const cachedOriginalSize = fs$1.existsSync(cachedOriginalBr)
|
|
10729
|
+
? fs$1.statSync(cachedOriginalBr).size
|
|
10730
|
+
: 0;
|
|
10731
|
+
const cachedOriginalMd5 = fs$1.existsSync(cachedOriginalBr)
|
|
10732
|
+
? crypto$1
|
|
10733
|
+
.createHash('md5')
|
|
10734
|
+
.update(fs$1.readFileSync(cachedOriginalBr))
|
|
10735
|
+
.digest('hex')
|
|
10736
|
+
: '<missing>';
|
|
10737
|
+
verboseLog(`[wasmtool] on-disk replace check: project=${params.wasm_file_path} size=${replacedSize} md5=${replacedMd5} | cached-original size=${cachedOriginalSize} md5=${cachedOriginalMd5}`);
|
|
10738
|
+
if (replacedMd5 === cachedOriginalMd5) {
|
|
10739
|
+
verboseWarn('[wasmtool] WARNING: project wasm md5 matches cached-original md5. The file was not actually replaced with the instrumented build.');
|
|
10740
|
+
}
|
|
10741
|
+
else {
|
|
10742
|
+
verboseLog('[wasmtool] OK: project wasm differs from cached-original — instrumented wasm is on disk.');
|
|
10743
|
+
}
|
|
10744
|
+
// Local pipeline uses the new wasm-collect/v1/report API + archive sub-wasm.
|
|
10745
|
+
// ORIGINALWASMMD5 must be set now (not only at split time) so the plugin
|
|
10746
|
+
// reports the correct wasm_md5 during the collect phase.
|
|
10747
|
+
updateWasmSplitConfig({
|
|
10748
|
+
ENABLEWASMCOLLECT: true,
|
|
10749
|
+
ENABLEARCHIVEMODE: true,
|
|
10750
|
+
ORIGINALWASMMD5: params.wasm_md5,
|
|
10751
|
+
});
|
|
10752
|
+
verboseLog('[wasmtool] wasm split config updated (local pipeline: archive=true)');
|
|
10753
|
+
// Disk-persisted anchor for "wasm drift" detection in
|
|
10754
|
+
// `game-wasm-split-config` route. Stores the md5 that prepare just
|
|
10755
|
+
// wrote into the project alongside the project-relative path. The
|
|
10756
|
+
// route reads this back on every Modal open, recomputes the md5 of
|
|
10757
|
+
// the file on disk, and if they differ (Unity re-build, git
|
|
10758
|
+
// checkout, etc.) suppresses `enableWasmCollect=true` in the
|
|
10759
|
+
// response so the IDE goes back through prepare instead of dropping
|
|
10760
|
+
// the user straight into Collect with an un-instrumented wasm on
|
|
10761
|
+
// the device. See `preparedMeta.ts` for full rationale.
|
|
10762
|
+
writePreparedMeta({
|
|
10763
|
+
preparedWasmMd5: replacedMd5,
|
|
10764
|
+
codePath: params.wasm_file_path,
|
|
10765
|
+
});
|
|
10766
|
+
verboseLog(`[wasmtool] prepared-meta written: md5=${replacedMd5} codePath=${params.wasm_file_path}`);
|
|
10767
|
+
return {
|
|
10768
|
+
data: {
|
|
10769
|
+
code: 0,
|
|
10770
|
+
message: 'success',
|
|
10771
|
+
result: { md5: params.wasm_md5 },
|
|
10772
|
+
},
|
|
10773
|
+
error: null,
|
|
10774
|
+
ctx: { logid: 'local', httpStatusCode: 200 },
|
|
10775
|
+
};
|
|
10776
|
+
}
|
|
10777
|
+
catch (err) {
|
|
10778
|
+
return {
|
|
10779
|
+
data: null,
|
|
10780
|
+
error: {
|
|
10781
|
+
code: 500,
|
|
10782
|
+
message: err instanceof Error ? err.message : String(err),
|
|
10783
|
+
},
|
|
10784
|
+
ctx: { logid: 'local', httpStatusCode: 500 },
|
|
10785
|
+
};
|
|
10786
|
+
}
|
|
10787
|
+
}
|
|
10788
|
+
|
|
10789
|
+
/**
|
|
10790
|
+
* Local pipeline: startPrepareLocal already compressed/replaced the wasm and
|
|
10791
|
+
* updated webgl-wasm-split.js, so this step is a no-op that just emits UI
|
|
10792
|
+
* status events for parity with the remote flow.
|
|
10793
|
+
*/
|
|
10794
|
+
async function downloadPrepared$1(_data) {
|
|
10795
|
+
const { preparedWasmPath } = getLocalState();
|
|
10796
|
+
if (!preparedWasmPath) {
|
|
10797
|
+
return {
|
|
10798
|
+
isSuccess: false,
|
|
10799
|
+
error: { code: 404, message: 'Prepared wasm not found. Run prepare first.' },
|
|
10800
|
+
};
|
|
10801
|
+
}
|
|
10802
|
+
wsServer.sendUnitySplitStatus({ status: 'update_wasm_split_config_done' });
|
|
10803
|
+
return { isSuccess: true, ctx: { logid: 'local' } };
|
|
10804
|
+
}
|
|
10805
|
+
|
|
10806
|
+
async function getCollectedFuncIds$1({ client_key, wasm_md5, }) {
|
|
10807
|
+
const res = await request({
|
|
10808
|
+
url: `${WASM_COLLECT_BASE_URL}/progress`,
|
|
10809
|
+
method: 'GET',
|
|
10810
|
+
params: {
|
|
10811
|
+
app_id: client_key,
|
|
10812
|
+
wasm_md5,
|
|
10813
|
+
},
|
|
10814
|
+
});
|
|
10815
|
+
const funcCount = res?.data?.func_count ?? 0;
|
|
10816
|
+
return {
|
|
10817
|
+
data: {
|
|
10818
|
+
code: res?.data?.code ?? 0,
|
|
10819
|
+
message: 'success',
|
|
10820
|
+
result: {
|
|
10821
|
+
collected_func_count: funcCount,
|
|
10822
|
+
data_size: funcCount,
|
|
10823
|
+
real_data_size: funcCount,
|
|
10824
|
+
collect_state: res?.data?.collect_state,
|
|
10825
|
+
},
|
|
10826
|
+
},
|
|
10827
|
+
error: res.error,
|
|
10828
|
+
ctx: res.ctx,
|
|
10829
|
+
};
|
|
10830
|
+
}
|
|
10831
|
+
|
|
10832
|
+
/**
|
|
10833
|
+
* POST /start — opens a collect session (Portal-authenticated).
|
|
10834
|
+
*
|
|
10835
|
+
* Idempotent on the server: re-opening an already-open session just refreshes
|
|
10836
|
+
* `started_at`; only `reset: true` wipes history.
|
|
10837
|
+
*
|
|
10838
|
+
* Default `reset` is `false` to mirror the server-side default documented in
|
|
10839
|
+
* `wasm_api.md` §5.1 — "页面刷新 / 恢复" must NOT silently destroy data. The
|
|
10840
|
+
* "fresh run" semantic (e.g. user clicks "重新开始分包") is the responsibility
|
|
10841
|
+
* of the caller, which must explicitly pass `reset: true`. See `setCollect`
|
|
10842
|
+
* for the CLI-level wiring of those two paths.
|
|
10843
|
+
*
|
|
10844
|
+
* NOTE on naming: the server route is flat (`/start`, not `/session/start`).
|
|
10845
|
+
* Our local symbol stays `startWasmSession` because it's the "start collect
|
|
10846
|
+
* session" lifecycle primitive from the IDE's perspective.
|
|
10847
|
+
*/
|
|
10848
|
+
async function startWasmSession({ client_key, wasm_md5, reset, }) {
|
|
10849
|
+
const res = await request({
|
|
10850
|
+
url: `${WASM_COLLECT_BASE_URL}/start`,
|
|
10851
|
+
method: 'POST',
|
|
10852
|
+
data: {
|
|
10853
|
+
app_id: client_key,
|
|
10854
|
+
wasm_md5,
|
|
10855
|
+
reset: reset ?? false,
|
|
10856
|
+
},
|
|
10857
|
+
});
|
|
10858
|
+
return {
|
|
10859
|
+
data: res.data
|
|
10860
|
+
? {
|
|
10861
|
+
code: res.data.code ?? 0,
|
|
10862
|
+
message: res.data.message || 'success',
|
|
10863
|
+
result: {
|
|
10864
|
+
collect_state: res.data.collect_state,
|
|
10865
|
+
started_at: res.data.started_at,
|
|
10866
|
+
},
|
|
10867
|
+
}
|
|
10868
|
+
: null,
|
|
10869
|
+
error: res.error,
|
|
10870
|
+
ctx: res.ctx,
|
|
10871
|
+
};
|
|
10872
|
+
}
|
|
10873
|
+
|
|
10874
|
+
/**
|
|
10875
|
+
* 打开 server 端 collect session(本地 pipeline 的 `/start`)。
|
|
10876
|
+
*
|
|
10877
|
+
* 这是**鉴权门**:`/start` 走 Portal 鉴权中间件,登录态失效会返回 `-401`
|
|
10878
|
+
* (或带登录关键字的 `-1`)。鉴权失败必须立即把错误回给 IDE 并中止——
|
|
10879
|
+
* 否则用户进了"正在收集",但 plugin 之后所有 `/report` 都会被 fail-close
|
|
10880
|
+
* 丢弃,函数数量永远 0,且很难归因。
|
|
10881
|
+
*
|
|
10882
|
+
* IDE 通过独立路由 `/game/wasm-collect-start` 调用本函数,所以它在浏览器
|
|
10883
|
+
* Network 里是一条可见且**明确叫 start** 的请求——出鉴权问题时一眼能定位到
|
|
10884
|
+
* 是开 session 这步失败,而不是被误认为 collect 轮询接口的问题。symbols
|
|
10885
|
+
* 上传是另一步(`uploadCollectSymbols`),与开 session 解耦。
|
|
10886
|
+
*
|
|
10887
|
+
* 两种语义(与 `wasm_api.md` §5.1 对齐):
|
|
10888
|
+
* - 默认(`resume` 缺省 / false)→ `reset: true`,服务端清空历史。
|
|
10889
|
+
* - `resume: true` → `reset: false`,幂等打开、保留已有 func_ids。
|
|
10890
|
+
*/
|
|
10891
|
+
async function openCollectSession$1({ client_key, wasm_md5, resume, }) {
|
|
10892
|
+
const startRes = await startWasmSession({
|
|
10893
|
+
client_key,
|
|
10894
|
+
wasm_md5,
|
|
10895
|
+
reset: !resume,
|
|
10896
|
+
});
|
|
10897
|
+
if (startRes.error || !startRes.data || startRes.data.code !== 0) {
|
|
10898
|
+
// 结构化日志带 logid——找后端排查鉴权/会话问题时最有用的字段。
|
|
10899
|
+
const code = startRes.error?.code ?? startRes.data?.code ?? -1;
|
|
10900
|
+
const message = startRes.error?.message ||
|
|
10901
|
+
startRes.data?.message ||
|
|
10902
|
+
'Open collect session failed';
|
|
10903
|
+
const logid = startRes.ctx?.logid || 'n/a';
|
|
10904
|
+
console.error(`[wasm-collect] /start failed: code=${code} message=${message} logid=${logid}`);
|
|
10905
|
+
return {
|
|
10906
|
+
data: startRes.data ?? null,
|
|
10907
|
+
error: startRes.error ?? { code, message },
|
|
10908
|
+
ctx: startRes.ctx,
|
|
10909
|
+
};
|
|
10910
|
+
}
|
|
10911
|
+
return {
|
|
10912
|
+
data: { code: 0, message: 'success', result: startRes.data.result },
|
|
10913
|
+
error: null,
|
|
10914
|
+
ctx: startRes.ctx,
|
|
10915
|
+
};
|
|
10916
|
+
}
|
|
10917
|
+
/**
|
|
10918
|
+
* 上传符号表(`/symbols`)。只在 session 已打开后调用。
|
|
10919
|
+
*
|
|
10920
|
+
* 符号表只是给 server 端后续调试用的 debug 信息,丢了不影响分包主链路,
|
|
10921
|
+
* 所以这里故意不 await、失败仅 warn,且总是返回 `{code: 0}`——它不应该
|
|
10922
|
+
* 阻塞或失败掉"开始收集"流程。
|
|
10923
|
+
*/
|
|
10924
|
+
async function uploadCollectSymbols$1({ client_key, wasm_md5, }) {
|
|
10925
|
+
let symbolPath = path$1.join(process.cwd(), WASM_SYMBOL_FILE_NAME);
|
|
10926
|
+
if (!fs$1.existsSync(symbolPath)) {
|
|
10927
|
+
symbolPath = path$1.join(process.cwd(), TTMG_TEMP_DIR, WASM_SYMBOL_FILE_NAME);
|
|
10928
|
+
}
|
|
10929
|
+
if (fs$1.existsSync(symbolPath)) {
|
|
10930
|
+
const symbols = fs$1.readFileSync(symbolPath, 'utf-8');
|
|
10931
|
+
request({
|
|
10932
|
+
url: `${WASM_COLLECT_BASE_URL}/symbols`,
|
|
10933
|
+
method: 'POST',
|
|
10934
|
+
// symbols 是整张符号表,verbose 下打出来会刷屏,关掉它的入参日志。
|
|
10935
|
+
logRequestBody: false,
|
|
10936
|
+
data: {
|
|
10937
|
+
app_id: client_key,
|
|
10938
|
+
wasm_md5,
|
|
10939
|
+
symbols,
|
|
10940
|
+
},
|
|
10941
|
+
}).catch(err => {
|
|
10942
|
+
verboseWarn('[wasmtool] Failed to upload symbols:', err);
|
|
10943
|
+
});
|
|
10944
|
+
}
|
|
10945
|
+
return {
|
|
10946
|
+
data: { code: 0, message: 'success', result: {} },
|
|
10947
|
+
error: null,
|
|
10948
|
+
ctx: { logid: 'local', httpStatusCode: 200 },
|
|
10949
|
+
};
|
|
10950
|
+
}
|
|
10951
|
+
|
|
10952
|
+
async function getCollecttingInfo$1({ client_key, wasm_md5, }) {
|
|
10953
|
+
const res = await request({
|
|
10954
|
+
url: `${WASM_COLLECT_BASE_URL}/progress`,
|
|
10955
|
+
method: 'GET',
|
|
10956
|
+
params: {
|
|
10957
|
+
app_id: client_key,
|
|
10958
|
+
wasm_md5,
|
|
10959
|
+
},
|
|
10960
|
+
});
|
|
10961
|
+
const { totalWasmFuncCount } = getLocalState();
|
|
10962
|
+
// Fall back to game.json.wasmFuncCount so the total survives CLI restarts.
|
|
10963
|
+
const gameJsonFuncCount = Number(getGameJson()?.wasmFuncCount) || 0;
|
|
10964
|
+
return {
|
|
10965
|
+
data: {
|
|
10966
|
+
code: res?.data?.code ?? 0,
|
|
10967
|
+
message: 'success',
|
|
10968
|
+
result: {
|
|
10969
|
+
app_id: client_key,
|
|
10970
|
+
wasm_md5,
|
|
10971
|
+
collected_func_count: res?.data?.func_count ?? 0,
|
|
10972
|
+
total_wasm_func_count: gameJsonFuncCount || totalWasmFuncCount || 0,
|
|
10973
|
+
collect_state: res?.data?.collect_state,
|
|
10974
|
+
},
|
|
10975
|
+
},
|
|
10976
|
+
error: res.error,
|
|
10977
|
+
ctx: res.ctx,
|
|
10978
|
+
};
|
|
10979
|
+
}
|
|
10980
|
+
|
|
10981
|
+
/**
|
|
10982
|
+
* POST /finish — closes a collect session and returns the final `func_count`
|
|
10983
|
+
* so the IDE can surface "本次共收集 N 个函数" in the success dialog.
|
|
10984
|
+
* Idempotent on the server.
|
|
10985
|
+
*
|
|
10986
|
+
* NOTE on naming: the server route is flat (`/finish`, not `/session/finish`).
|
|
10987
|
+
* The local symbol keeps `finishWasmSession` for symmetry with `startWasmSession`.
|
|
10988
|
+
*/
|
|
10989
|
+
async function finishWasmSession({ client_key, wasm_md5, }) {
|
|
10990
|
+
const res = await request({
|
|
10991
|
+
url: `${WASM_COLLECT_BASE_URL}/finish`,
|
|
10992
|
+
method: 'POST',
|
|
10993
|
+
data: {
|
|
10994
|
+
app_id: client_key,
|
|
10995
|
+
wasm_md5,
|
|
10996
|
+
},
|
|
10997
|
+
});
|
|
10998
|
+
return {
|
|
10999
|
+
data: res.data
|
|
11000
|
+
? {
|
|
11001
|
+
code: res.data.code ?? 0,
|
|
11002
|
+
message: res.data.message || 'success',
|
|
11003
|
+
result: {
|
|
11004
|
+
collect_state: res.data.collect_state,
|
|
11005
|
+
func_count: res.data.func_count ?? 0,
|
|
11006
|
+
finished_at: res.data.finished_at,
|
|
11007
|
+
},
|
|
11008
|
+
}
|
|
11009
|
+
: null,
|
|
11010
|
+
error: res.error,
|
|
11011
|
+
ctx: res.ctx,
|
|
11012
|
+
};
|
|
11013
|
+
}
|
|
11014
|
+
|
|
11015
|
+
async function startSplit$1({ client_key, wasm_md5, }) {
|
|
11016
|
+
const tempDir = path$1.join(process.cwd(), TTMG_TEMP_DIR);
|
|
11017
|
+
const splitOutputDir = path$1.join(tempDir, 'split-output');
|
|
11018
|
+
if (fs$1.existsSync(splitOutputDir)) {
|
|
11019
|
+
fs$1.rmSync(splitOutputDir, { recursive: true, force: true });
|
|
11020
|
+
}
|
|
11021
|
+
ensureDirSync(splitOutputDir);
|
|
11022
|
+
const { originalWasmPath, isArchiveMode: archive } = getLocalState();
|
|
11023
|
+
const rawWasmPath = originalWasmPath || path$1.join(tempDir, 'original.wasm');
|
|
11024
|
+
if (!fs$1.existsSync(rawWasmPath)) {
|
|
11025
|
+
return {
|
|
11026
|
+
data: null,
|
|
11027
|
+
error: {
|
|
11028
|
+
code: 404,
|
|
11029
|
+
message: 'Original wasm not found. Run prepare first.',
|
|
11030
|
+
},
|
|
11031
|
+
ctx: { logid: 'local', httpStatusCode: 404 },
|
|
11032
|
+
};
|
|
11033
|
+
}
|
|
11034
|
+
const exportRes = await request({
|
|
11035
|
+
url: `${WASM_COLLECT_BASE_URL}/export`,
|
|
11036
|
+
method: 'GET',
|
|
11037
|
+
params: {
|
|
11038
|
+
app_id: client_key,
|
|
11039
|
+
wasm_md5,
|
|
11040
|
+
strategy: 'union',
|
|
11041
|
+
},
|
|
11042
|
+
});
|
|
11043
|
+
const funcIds = exportRes?.data?.func_ids;
|
|
11044
|
+
const bootFuncIds = exportRes?.data?.boot_func_ids ?? [];
|
|
11045
|
+
if (!funcIds?.length) {
|
|
11046
|
+
return {
|
|
11047
|
+
data: null,
|
|
11048
|
+
error: {
|
|
11049
|
+
code: 400,
|
|
11050
|
+
message: 'No collected func IDs found.',
|
|
11051
|
+
},
|
|
11052
|
+
ctx: { logid: 'local', httpStatusCode: 400 },
|
|
11053
|
+
};
|
|
11054
|
+
}
|
|
11055
|
+
verboseLog(`[wasmtool] splitting with ${funcIds.length} func IDs` +
|
|
11056
|
+
(bootFuncIds.length > 0
|
|
11057
|
+
? `, ${bootFuncIds.length} boot-phase func IDs (→ alwaysInclude)`
|
|
11058
|
+
: ', no boot-phase info (legacy server, falling back to callClosure only)') +
|
|
11059
|
+
`, archive=${archive}`);
|
|
11060
|
+
try {
|
|
11061
|
+
const result = ttmgWasmtool.split({
|
|
11062
|
+
input: rawWasmPath,
|
|
11063
|
+
funcIds,
|
|
11064
|
+
// Boot-phase func ids → `alwaysInclude`. They are a subset of
|
|
11065
|
+
// `funcIds` so this doesn't grow `collect_count`, but it DOES seed
|
|
11066
|
+
// the direct-call closure BFS with the exact set needed for first
|
|
11067
|
+
// frame, and the split tool's `alwaysIncludeAdded` counter is the
|
|
11068
|
+
// observability signal when zero (= server didn't return boot info).
|
|
11069
|
+
alwaysInclude: bootFuncIds.length > 0 ? bootFuncIds : undefined,
|
|
11070
|
+
// Always-on direct-call closure over (collect ∪ alwaysInclude ∪
|
|
11071
|
+
// start_func). Folds in func ids that collect missed (untaken
|
|
11072
|
+
// branches, race conditions during collect) so first-screen code
|
|
11073
|
+
// paths don't trap on archive trampolines. See the split tool's
|
|
11074
|
+
// `closure_added` counter for the per-build size impact.
|
|
11075
|
+
callClosure: true,
|
|
11076
|
+
// Always-on indirect-call type-closure scoped to the boot subset.
|
|
11077
|
+
// Catches IL2CPP virtual / interface / delegate dispatch which is
|
|
11078
|
+
// the dominant source of remaining `firstFrame=BEFORE` archive
|
|
11079
|
+
// trampoline hits after the runtime collect + direct closure
|
|
11080
|
+
// passes (see `indirectClosureAdded` for the per-build size
|
|
11081
|
+
// impact). Defaults to `true` in the wasmtool but we set it
|
|
11082
|
+
// explicitly so a future tool default change can't silently turn
|
|
11083
|
+
// it off in our pipeline.
|
|
11084
|
+
callIndirectClosure: true,
|
|
11085
|
+
outputDir: splitOutputDir,
|
|
11086
|
+
archive,
|
|
11087
|
+
compress: true,
|
|
11088
|
+
quality: 9,
|
|
11089
|
+
});
|
|
11090
|
+
if (result.code !== 0) {
|
|
11091
|
+
return {
|
|
11092
|
+
data: null,
|
|
11093
|
+
error: { code: result.code, message: result.errMsg },
|
|
11094
|
+
ctx: { logid: 'local', httpStatusCode: 500 },
|
|
11095
|
+
};
|
|
11096
|
+
}
|
|
11097
|
+
const mainBrPath = result.mainWasmPath + '.br';
|
|
11098
|
+
const actualMainPath = fs$1.existsSync(mainBrPath)
|
|
11099
|
+
? mainBrPath
|
|
11100
|
+
: result.mainWasmPath;
|
|
11101
|
+
const mainWasmMd5 = computeFileMd5Sync(actualMainPath);
|
|
11102
|
+
const subBrPath = result.subWasmPath ? result.subWasmPath + '.br' : '';
|
|
11103
|
+
const actualSubPath = subBrPath && fs$1.existsSync(subBrPath)
|
|
11104
|
+
? subBrPath
|
|
11105
|
+
: result.subWasmPath;
|
|
11106
|
+
const subWasmMd5 = actualSubPath
|
|
11107
|
+
? computeFileMd5Sync(actualSubPath)
|
|
11108
|
+
: '';
|
|
11109
|
+
let archiveMd5 = '';
|
|
11110
|
+
if (archive && result.archivePath) {
|
|
11111
|
+
const archiveBrPath = result.archivePath + '.br';
|
|
11112
|
+
const actualArchivePath = fs$1.existsSync(archiveBrPath)
|
|
11113
|
+
? archiveBrPath
|
|
11114
|
+
: result.archivePath;
|
|
11115
|
+
verboseLog(`[wasmtool] archivePath=${result.archivePath}, brExists=${fs$1.existsSync(archiveBrPath)}, actualExists=${fs$1.existsSync(actualArchivePath)}`);
|
|
11116
|
+
if (fs$1.existsSync(actualArchivePath)) {
|
|
11117
|
+
archiveMd5 = computeFileMd5Sync(actualArchivePath);
|
|
11118
|
+
verboseLog(`[wasmtool] archive_md5=${archiveMd5}`);
|
|
11119
|
+
}
|
|
11120
|
+
}
|
|
11121
|
+
else {
|
|
11122
|
+
verboseLog(`[wasmtool] skip archive md5: archive=${archive}, archivePath=${result.archivePath}`);
|
|
11123
|
+
}
|
|
11124
|
+
const globalVarList = result.globalVarList
|
|
11125
|
+
.split(';')
|
|
11126
|
+
.filter(Boolean)
|
|
11127
|
+
.map((entry) => {
|
|
11128
|
+
const [name, type, mutable] = entry.trim().split(',');
|
|
11129
|
+
return { name, type, mutable: mutable === '1' };
|
|
11130
|
+
});
|
|
11131
|
+
const splitMeta = {
|
|
11132
|
+
original_wasm_md5: wasm_md5,
|
|
11133
|
+
main_wasm_md5: mainWasmMd5,
|
|
11134
|
+
main_wasm_h5_md5: mainWasmMd5,
|
|
11135
|
+
sub_wasm_md5: subWasmMd5,
|
|
11136
|
+
archive_md5: archiveMd5,
|
|
11137
|
+
table_size: result.tableSize,
|
|
11138
|
+
global_var_list: globalVarList,
|
|
11139
|
+
version: Date.now(),
|
|
11140
|
+
total_wasm_count: result.totalWasmCount,
|
|
11141
|
+
main_wasm_count: result.mainWasmCount,
|
|
11142
|
+
time_cost: result.timeCost,
|
|
11143
|
+
archive,
|
|
11144
|
+
local_main_wasm_path: result.mainWasmPath,
|
|
11145
|
+
local_sub_wasm_path: result.subWasmPath,
|
|
11146
|
+
local_func_meta_path: result.funcMetaPath,
|
|
11147
|
+
local_archive_path: result.archivePath,
|
|
11148
|
+
// Composition breakdown of main_funcs — the single most useful
|
|
11149
|
+
// piece of information when triaging "why is my main package X MB"
|
|
11150
|
+
// (or, conversely, "why are first-screen sub-package batches still
|
|
11151
|
+
// loading"). collect = runtime-observed, always_include =
|
|
11152
|
+
// boot_func_ids, closure = BFS direct callees, indirect_closure =
|
|
11153
|
+
// type-matching pass scoped to boot funcs (covers IL2CPP virtual
|
|
11154
|
+
// dispatch), export = wasm exports. These sum with imports to
|
|
11155
|
+
// main_wasm_count.
|
|
11156
|
+
collect_func_count: result.collectFuncCount,
|
|
11157
|
+
always_include_added: result.alwaysIncludeAdded,
|
|
11158
|
+
closure_added: result.closureAdded,
|
|
11159
|
+
indirect_closure_added: result.indirectClosureAdded,
|
|
11160
|
+
indirect_closure_types: result.indirectClosureTypes,
|
|
11161
|
+
export_added: result.exportAdded,
|
|
11162
|
+
};
|
|
11163
|
+
setLocalState({ splitOutputDir, splitMeta });
|
|
11164
|
+
verboseLog(`[wasmtool] split done: total=${result.totalWasmCount}, main=${result.mainWasmCount} ` +
|
|
11165
|
+
`(collect=${result.collectFuncCount}, +alwaysInclude=${result.alwaysIncludeAdded}, ` +
|
|
11166
|
+
`+closure=${result.closureAdded}, +indirectClosure=${result.indirectClosureAdded}` +
|
|
11167
|
+
`[types=${result.indirectClosureTypes}], +exports=${result.exportAdded}), ` +
|
|
11168
|
+
`time=${result.timeCost}s`);
|
|
11169
|
+
// Split landed — close the collect session so the plugin stops reporting.
|
|
11170
|
+
// Awaited (not fire-and-forget) so IDE can rely on "wasm-split returned
|
|
11171
|
+
// success" meaning "session definitively closed". If /finish itself
|
|
11172
|
+
// fails (e.g. portal cookie expired mid-run) we still return split
|
|
11173
|
+
// success to the IDE — the plugin already has the MD5-bound session
|
|
11174
|
+
// state from the earlier /report responses and will time out on TTL
|
|
11175
|
+
// anyway; failing split for a finalizer hiccup would be worse UX.
|
|
11176
|
+
let funcCount;
|
|
11177
|
+
try {
|
|
11178
|
+
const finishRes = await finishWasmSession({ client_key, wasm_md5 });
|
|
11179
|
+
if (finishRes.error || !finishRes.data || finishRes.data.code !== 0) {
|
|
11180
|
+
// Soft failure: split already succeeded from the user's POV, but
|
|
11181
|
+
// this is the main diagnostic breadcrumb if someone later reports
|
|
11182
|
+
// "plugin kept uploading after 分包完成". Always include logid so
|
|
11183
|
+
// backend can cross-reference without having to know our build.
|
|
11184
|
+
const code = finishRes.error?.code ?? finishRes.data?.code ?? -1;
|
|
11185
|
+
const message = finishRes.error?.message ||
|
|
11186
|
+
finishRes.data?.message ||
|
|
11187
|
+
'finish session non-success';
|
|
11188
|
+
const logid = finishRes.ctx?.logid || 'n/a';
|
|
11189
|
+
console.error(`[wasm-split] /finish failed (split still succeeded): code=${code} message=${message} logid=${logid}`);
|
|
11190
|
+
}
|
|
11191
|
+
else {
|
|
11192
|
+
funcCount = finishRes.data.result?.func_count;
|
|
11193
|
+
}
|
|
11194
|
+
}
|
|
11195
|
+
catch (e) {
|
|
11196
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
11197
|
+
console.error(`[wasm-split] /finish threw (split still succeeded): ${msg}`);
|
|
11198
|
+
}
|
|
11199
|
+
return {
|
|
11200
|
+
data: { code: 0, message: 'success', func_count: funcCount },
|
|
11201
|
+
error: null,
|
|
11202
|
+
ctx: { logid: 'local', httpStatusCode: 200 },
|
|
11203
|
+
};
|
|
11204
|
+
}
|
|
11205
|
+
catch (err) {
|
|
11206
|
+
return {
|
|
11207
|
+
data: null,
|
|
11208
|
+
error: {
|
|
11209
|
+
code: 500,
|
|
11210
|
+
message: err instanceof Error ? err.message : String(err),
|
|
11211
|
+
},
|
|
11212
|
+
ctx: { logid: 'local', httpStatusCode: 500 },
|
|
11213
|
+
};
|
|
11214
|
+
}
|
|
11215
|
+
}
|
|
11216
|
+
|
|
11217
|
+
const ARCHIVE_SUBPACKAGE_CONFIG = [
|
|
11218
|
+
WASM_SPLIT_SUBPACKAGE_CONFIG.origin,
|
|
11219
|
+
WASM_SPLIT_SUBPACKAGE_CONFIG.archiveSub,
|
|
11220
|
+
WASM_SPLIT_SUBPACKAGE_CONFIG.archiveCode,
|
|
11221
|
+
];
|
|
11222
|
+
function updateSubpackageConfigSync(archive = false) {
|
|
11223
|
+
const gameJsonPath = path__namespace.join(process.cwd(), SUBPACKAGE_CONFIG_FILE_NAME);
|
|
11224
|
+
const raw = fs__namespace.readFileSync(gameJsonPath, 'utf-8');
|
|
11225
|
+
const gameJson = JSON.parse(raw);
|
|
11226
|
+
delete gameJson.wasmFuncCount;
|
|
11227
|
+
const fieldName = SUBPACKAGE_FIELD_NAMES.find(k => k in gameJson) ??
|
|
11228
|
+
SUBPACKAGE_FIELD_NAMES[0];
|
|
11229
|
+
if (!gameJson[fieldName])
|
|
11230
|
+
gameJson[fieldName] = [];
|
|
11231
|
+
const subpackages = gameJson[fieldName];
|
|
11232
|
+
const filtered = subpackages.filter(s => s.name !== WASM_SPLIT_SUBPACKAGE_CONFIG.origin.name);
|
|
11233
|
+
if (archive) {
|
|
11234
|
+
ARCHIVE_SUBPACKAGE_CONFIG.forEach(pkg => filtered.push(pkg));
|
|
11235
|
+
}
|
|
11236
|
+
else {
|
|
11237
|
+
filtered.push(WASM_SPLIT_SUBPACKAGE_CONFIG.androidMain);
|
|
11238
|
+
filtered.push(WASM_SPLIT_SUBPACKAGE_CONFIG.androidSub);
|
|
11239
|
+
filtered.push(WASM_SPLIT_SUBPACKAGE_CONFIG.iosMain);
|
|
11240
|
+
filtered.push(WASM_SPLIT_SUBPACKAGE_CONFIG.iosSub);
|
|
11241
|
+
}
|
|
11242
|
+
const map = new Map(filtered.map(s => [s.name, s]));
|
|
11243
|
+
gameJson[fieldName] = Array.from(map.values());
|
|
11244
|
+
fs__namespace.writeFileSync(gameJsonPath, JSON.stringify(gameJson, null, JSON_INDENT) + JSON_EOL);
|
|
11245
|
+
}
|
|
11246
|
+
|
|
11247
|
+
async function downloadSplited$1(_context) {
|
|
11248
|
+
const cwd = process.cwd();
|
|
11249
|
+
const { splitMeta } = getLocalState();
|
|
11250
|
+
if (!splitMeta) {
|
|
11251
|
+
return {
|
|
11252
|
+
data: { isSuccess: false },
|
|
11253
|
+
error: { message: 'No local split result found. Run split first.' },
|
|
11254
|
+
ctx: _context,
|
|
11255
|
+
};
|
|
11256
|
+
}
|
|
11257
|
+
const splitTempDir = path.join(cwd, WASM_SPLIT_CACHE_DIR, DIR_SPLIT);
|
|
11258
|
+
ensureDirSync(splitTempDir);
|
|
11259
|
+
const isArchive = splitMeta.archive;
|
|
11260
|
+
const mainAndroidDir = path.join(splitTempDir, isArchive ? 'wasmcode' : WASM_SPLIT_SUBPACKAGE_CONFIG.androidMain.root);
|
|
11261
|
+
const subAndroidDir = path.join(splitTempDir, isArchive ? 'wasmcode1' : WASM_SPLIT_SUBPACKAGE_CONFIG.androidSub.root);
|
|
11262
|
+
const mainIosDir = isArchive
|
|
11263
|
+
? mainAndroidDir
|
|
11264
|
+
: path.join(splitTempDir, WASM_SPLIT_SUBPACKAGE_CONFIG.iosMain.root);
|
|
11265
|
+
const subIosDir = path.join(splitTempDir, isArchive ? 'wasmcode-archive' : WASM_SPLIT_SUBPACKAGE_CONFIG.iosSub.root);
|
|
11266
|
+
const dirs = [...new Set([mainAndroidDir, subAndroidDir, mainIosDir, subIosDir])];
|
|
11267
|
+
dirs.forEach(ensureDirSync);
|
|
11268
|
+
try {
|
|
11269
|
+
verboseLog('[wasmtool] organizing split output...');
|
|
11270
|
+
const mainWasmMd5 = splitMeta.main_wasm_md5;
|
|
11271
|
+
const subWasmMd5 = splitMeta.sub_wasm_md5;
|
|
11272
|
+
const mainWasmH5Md5 = splitMeta.main_wasm_h5_md5;
|
|
11273
|
+
const localMainPath = splitMeta.local_main_wasm_path;
|
|
11274
|
+
const mainBrPath = localMainPath + BR_SUFFIX;
|
|
11275
|
+
const actualMainPath = fs.existsSync(mainBrPath) ? mainBrPath : localMainPath;
|
|
11276
|
+
if (actualMainPath && fs.existsSync(actualMainPath)) {
|
|
11277
|
+
const isBr = actualMainPath.endsWith(BR_SUFFIX);
|
|
11278
|
+
const ext = isBr
|
|
11279
|
+
? `${WASM_FILENAME_SUFFIX}${BR_SUFFIX}`
|
|
11280
|
+
: WASM_FILENAME_SUFFIX;
|
|
11281
|
+
const mainAndroidDest = path.join(mainAndroidDir, `${mainWasmMd5}${ext}`);
|
|
11282
|
+
fs.copyFileSync(actualMainPath, mainAndroidDest);
|
|
11283
|
+
wsServer.sendUnitySplitStatus({
|
|
11284
|
+
status: 'download_android_main_wasm_done',
|
|
11285
|
+
});
|
|
11286
|
+
if (mainIosDir !== mainAndroidDir) {
|
|
11287
|
+
const mainIosDest = path.join(mainIosDir, `${mainWasmH5Md5}${ext}`);
|
|
11288
|
+
fs.copyFileSync(actualMainPath, mainIosDest);
|
|
11289
|
+
}
|
|
11290
|
+
wsServer.sendUnitySplitStatus({
|
|
11291
|
+
status: 'download_ios_main_wasm_done',
|
|
11292
|
+
});
|
|
11293
|
+
}
|
|
11294
|
+
const localSubPath = splitMeta.local_sub_wasm_path;
|
|
11295
|
+
const subBrPath = localSubPath + BR_SUFFIX;
|
|
11296
|
+
const actualSubPath = fs.existsSync(subBrPath) ? subBrPath : localSubPath;
|
|
11297
|
+
if (actualSubPath && fs.existsSync(actualSubPath)) {
|
|
11298
|
+
const isBr = actualSubPath.endsWith(BR_SUFFIX);
|
|
11299
|
+
const ext = isBr
|
|
11300
|
+
? `${WASM_FILENAME_SUFFIX}${BR_SUFFIX}`
|
|
11301
|
+
: WASM_FILENAME_SUFFIX;
|
|
11302
|
+
const subAndroidDest = path.join(subAndroidDir, `${subWasmMd5}${ext}`);
|
|
11303
|
+
fs.copyFileSync(actualSubPath, subAndroidDest);
|
|
11304
|
+
wsServer.sendUnitySplitStatus({
|
|
11305
|
+
status: 'download_android_sub_wasm_code_done',
|
|
11306
|
+
});
|
|
11307
|
+
}
|
|
11308
|
+
const localArchivePath = splitMeta.local_archive_path;
|
|
11309
|
+
if (isArchive && localArchivePath) {
|
|
11310
|
+
const archiveBrPath = localArchivePath + BR_SUFFIX;
|
|
11311
|
+
const actualArchivePath = fs.existsSync(archiveBrPath) ? archiveBrPath : localArchivePath;
|
|
11312
|
+
verboseLog(`[wasmtool] archive copy: archive_md5=${splitMeta.archive_md5}, localPath=${localArchivePath}, brExists=${fs.existsSync(archiveBrPath)}, actual=${actualArchivePath}`);
|
|
11313
|
+
if (fs.existsSync(actualArchivePath)) {
|
|
11314
|
+
const archiveMd5 = splitMeta.archive_md5 || '';
|
|
11315
|
+
const archiveBaseName = path.basename(actualArchivePath);
|
|
11316
|
+
const destName = archiveMd5 ? `${archiveMd5}.${archiveBaseName}` : archiveBaseName;
|
|
11317
|
+
const archiveDest = path.join(subIosDir, destName);
|
|
11318
|
+
verboseLog(`[wasmtool] archive dest: ${archiveDest}`);
|
|
11319
|
+
fs.copyFileSync(actualArchivePath, archiveDest);
|
|
11320
|
+
}
|
|
11321
|
+
}
|
|
11322
|
+
dirs.forEach((dir) => {
|
|
11323
|
+
fs.writeFileSync(path.join(dir, 'game.js'), '', { encoding: 'utf-8' });
|
|
11324
|
+
});
|
|
11325
|
+
verboseLog('[wasmtool] copy split output to root...');
|
|
11326
|
+
wsServer.sendUnitySplitStatus({ status: 'start_write_splited_wasm_br' });
|
|
11327
|
+
for (const file of fs.readdirSync(splitTempDir)) {
|
|
11328
|
+
const srcPath = path.join(splitTempDir, file);
|
|
11329
|
+
const destPath = path.join(cwd, file);
|
|
11330
|
+
if (fs.existsSync(destPath)) {
|
|
11331
|
+
await promises.rm(destPath, { recursive: true, force: true });
|
|
11332
|
+
}
|
|
11333
|
+
await promises.cp(srcPath, destPath, { recursive: true, force: true });
|
|
11334
|
+
}
|
|
11335
|
+
wsServer.sendUnitySplitStatus({ status: 'write_splited_wasm_done' });
|
|
11336
|
+
verboseLog('[wasmtool] updating subpackage config...');
|
|
11337
|
+
updateSubpackageConfigSync(isArchive);
|
|
11338
|
+
verboseLog('[wasmtool] updating wasm split config...');
|
|
11339
|
+
wsServer.sendUnitySplitStatus({ status: 'start_update_wasm_split_config' });
|
|
11340
|
+
updateWasmSplitConfig({
|
|
11341
|
+
ENABLEWASMCOLLECT: true,
|
|
11342
|
+
ORIGINALWASMMD5: `${splitMeta.original_wasm_md5}`,
|
|
11343
|
+
WASMTABLESIZE: splitMeta.table_size,
|
|
11344
|
+
GLOBALVARLIST: JSON.stringify(splitMeta.global_var_list ?? []),
|
|
11345
|
+
SUBJSURL: '',
|
|
11346
|
+
IOS_CODE_FILE_MD5: `${splitMeta.main_wasm_h5_md5}`,
|
|
11347
|
+
ANDROID_CODE_FILE_MD5: `${splitMeta.main_wasm_md5}`,
|
|
11348
|
+
ANDROID_SUB_CODE_FILE_MD5: `${splitMeta.sub_wasm_md5}`,
|
|
11349
|
+
ARCHIVE_CODE_FILE_MD5: `${splitMeta.archive_md5 || ''}`,
|
|
11350
|
+
WASMSPLITVERSION: `${splitMeta.version}`,
|
|
11351
|
+
USINGWASMH5: Boolean(splitMeta.main_wasm_h5_md5),
|
|
11352
|
+
ENABLEWASMSPLIT: true,
|
|
11353
|
+
ENABLEARCHIVEMODE: isArchive,
|
|
11354
|
+
});
|
|
11355
|
+
wsServer.sendUnitySplitStatus({ status: 'update_wasm_split_config_done' });
|
|
11356
|
+
return {
|
|
11357
|
+
data: { isSuccess: true },
|
|
11358
|
+
ctx: splitMeta,
|
|
11359
|
+
};
|
|
11360
|
+
}
|
|
11361
|
+
catch (err) {
|
|
11362
|
+
wsServer.sendUnitySplitStatus({
|
|
11363
|
+
status: 'wasm_split_failed',
|
|
11364
|
+
errorMsg: err instanceof Error ? err.message : String(err),
|
|
11365
|
+
});
|
|
11366
|
+
return {
|
|
11367
|
+
data: { isSuccess: false },
|
|
11368
|
+
error: { message: err instanceof Error ? err.message : String(err) },
|
|
11369
|
+
ctx: splitMeta,
|
|
11370
|
+
};
|
|
11371
|
+
}
|
|
11372
|
+
finally {
|
|
11373
|
+
await promises.rm(splitTempDir, { recursive: true, force: true });
|
|
11374
|
+
if (!isArchive) {
|
|
11375
|
+
await promises.rm(path.join(cwd, WASM_SPLIT_SUBPACKAGE_CONFIG.origin.root), {
|
|
11376
|
+
recursive: true,
|
|
11377
|
+
force: true,
|
|
11378
|
+
});
|
|
11379
|
+
}
|
|
11380
|
+
}
|
|
11381
|
+
}
|
|
11382
|
+
|
|
11383
|
+
/*
|
|
11384
|
+
How it works:
|
|
11385
|
+
`this.#head` is an instance of `Node` which keeps track of its current value and nests another instance of `Node` that keeps the value that comes after it. When a value is provided to `.enqueue()`, the code needs to iterate through `this.#head`, going deeper and deeper to find the last value. However, iterating through every single item is slow. This problem is solved by saving a reference to the last value as `this.#tail` so that it can reference it to add a new value.
|
|
11386
|
+
*/
|
|
11387
|
+
|
|
11388
|
+
class Node {
|
|
11389
|
+
value;
|
|
11390
|
+
next;
|
|
11391
|
+
|
|
11392
|
+
constructor(value) {
|
|
11393
|
+
this.value = value;
|
|
11394
|
+
}
|
|
11395
|
+
}
|
|
11396
|
+
|
|
11397
|
+
class Queue {
|
|
11398
|
+
#head;
|
|
11399
|
+
#tail;
|
|
11400
|
+
#size;
|
|
11401
|
+
|
|
11402
|
+
constructor() {
|
|
11403
|
+
this.clear();
|
|
11404
|
+
}
|
|
11405
|
+
|
|
10862
11406
|
enqueue(value) {
|
|
10863
11407
|
const node = new Node(value);
|
|
10864
11408
|
|
|
@@ -11002,75 +11546,28 @@ function pLimit(concurrency) {
|
|
|
11002
11546
|
return generator;
|
|
11003
11547
|
}
|
|
11004
11548
|
|
|
11005
|
-
function
|
|
11006
|
-
const
|
|
11007
|
-
const raw = fs__namespace.readFileSync(gameJsonPath, 'utf-8');
|
|
11008
|
-
const gameJson = JSON.parse(raw);
|
|
11009
|
-
/**
|
|
11010
|
-
* wasm 分包完整流程完成后,删除一次性校验字段,
|
|
11011
|
-
* 避免后续调试阶段继续触发 wasmFuncCount 的提示。
|
|
11012
|
-
*/
|
|
11013
|
-
delete gameJson.wasmFuncCount;
|
|
11014
|
-
const fieldName = SUBPACKAGE_FIELD_NAMES.find(k => k in gameJson) ??
|
|
11015
|
-
SUBPACKAGE_FIELD_NAMES[0];
|
|
11016
|
-
if (!gameJson[fieldName])
|
|
11017
|
-
gameJson[fieldName] = [];
|
|
11018
|
-
const subpackages = gameJson[fieldName];
|
|
11019
|
-
// 删除老的 'wasmcode'
|
|
11020
|
-
const filtered = subpackages.filter(s => s.name !== WASM_SPLIT_SUBPACKAGE_CONFIG.origin.name);
|
|
11021
|
-
/**
|
|
11022
|
-
* 基于 SUBPACKAGE_CONFIG_FILE_NAME 更新 subpackages
|
|
11023
|
-
*/
|
|
11024
|
-
filtered.push(WASM_SPLIT_SUBPACKAGE_CONFIG.androidMain);
|
|
11025
|
-
filtered.push(WASM_SPLIT_SUBPACKAGE_CONFIG.androidSub);
|
|
11026
|
-
filtered.push(WASM_SPLIT_SUBPACKAGE_CONFIG.iosMain);
|
|
11027
|
-
filtered.push(WASM_SPLIT_SUBPACKAGE_CONFIG.iosSub);
|
|
11028
|
-
// 合并去重:存在则更新 root,不存在则新增
|
|
11029
|
-
const map = new Map(filtered.map(s => [s.name, s]));
|
|
11030
|
-
gameJson[fieldName] = Array.from(map.values());
|
|
11031
|
-
fs__namespace.writeFileSync(gameJsonPath, JSON.stringify(gameJson, null, JSON_INDENT) + JSON_EOL);
|
|
11032
|
-
}
|
|
11033
|
-
|
|
11034
|
-
async function downloadAndCompress(opts) {
|
|
11035
|
-
const { startDownloadStatus, downloadDoneStatus, startCompressStatus, compressDoneStatus, url, out, enableCompress = false, } = opts;
|
|
11549
|
+
async function downloadOne(opts) {
|
|
11550
|
+
const { startStatus, doneStatus, url, out } = opts;
|
|
11036
11551
|
if (!url)
|
|
11037
11552
|
return;
|
|
11038
11553
|
const willDownloadedFileIsBr = url.includes(BR_SUFFIX);
|
|
11039
|
-
const
|
|
11040
|
-
|
|
11041
|
-
|
|
11042
|
-
console.log(`download url: ${url}`);
|
|
11554
|
+
const finalOut = willDownloadedFileIsBr && !out.endsWith(BR_SUFFIX) ? out + BR_SUFFIX : out;
|
|
11555
|
+
wsServer.sendUnitySplitStatus({ status: startStatus });
|
|
11556
|
+
verboseLog(`[remote-split-download] fetching -> ${finalOut}`);
|
|
11043
11557
|
const t0 = Date.now();
|
|
11044
|
-
await withRetry(() => download(url,
|
|
11045
|
-
|
|
11046
|
-
|
|
11047
|
-
|
|
11048
|
-
|
|
11049
|
-
|
|
11050
|
-
|
|
11051
|
-
}
|
|
11052
|
-
|
|
11053
|
-
|
|
11054
|
-
|
|
11055
|
-
|
|
11056
|
-
|
|
11057
|
-
console.log(`compress start: ${path.basename(out)}${BR_SUFFIX}`);
|
|
11058
|
-
// 压缩
|
|
11059
|
-
wsServer.sendUnitySplitStatus({ status: startCompressStatus });
|
|
11060
|
-
const t1 = Date.now();
|
|
11061
|
-
await compressWasmFile(out, wasmBrOutName);
|
|
11062
|
-
wsServer.sendUnitySplitStatus({ status: compressDoneStatus });
|
|
11063
|
-
console.log(`compress done: ${path.basename(wasmBrOutName)} time=${Date.now() - t1}ms`);
|
|
11064
|
-
}
|
|
11065
|
-
/**
|
|
11066
|
-
* 在当前文件所在目录下写入一个空的 game.js
|
|
11067
|
-
*/
|
|
11068
|
-
fs__namespace.writeFileSync(path.join(path.dirname(out), 'game.js'), '', {
|
|
11069
|
-
encoding: 'utf-8',
|
|
11070
|
-
});
|
|
11071
|
-
}
|
|
11072
|
-
|
|
11073
|
-
async function downloadSplited(context) {
|
|
11558
|
+
await withRetry(() => download(url, finalOut), DOWNLOAD_RETRY);
|
|
11559
|
+
const st = await promises.stat(finalOut);
|
|
11560
|
+
if (!st.size) {
|
|
11561
|
+
await promises.rm(finalOut, { force: true });
|
|
11562
|
+
throw new Error(`Empty download: ${finalOut}`);
|
|
11563
|
+
}
|
|
11564
|
+
verboseLog(`[remote-split-download] done: ${path.basename(finalOut)} size=${st.size}B time=${Date.now() - t0}ms`);
|
|
11565
|
+
wsServer.sendUnitySplitStatus({ status: doneStatus, url });
|
|
11566
|
+
// Legacy behaviour: write an empty game.js next to each downloaded artifact
|
|
11567
|
+
// so the subpackage loader doesn't complain about missing js entries.
|
|
11568
|
+
fs.writeFileSync(path.join(path.dirname(out), 'game.js'), '', 'utf-8');
|
|
11569
|
+
}
|
|
11570
|
+
async function downloadSplitedRemote(context) {
|
|
11074
11571
|
const cwd = process.cwd();
|
|
11075
11572
|
const splitTempDir = path.join(cwd, WASM_SPLIT_CACHE_DIR, DIR_SPLIT);
|
|
11076
11573
|
ensureDirSync(splitTempDir);
|
|
@@ -11079,138 +11576,385 @@ async function downloadSplited(context) {
|
|
|
11079
11576
|
const mainIosDir = path.join(splitTempDir, WASM_SPLIT_SUBPACKAGE_CONFIG.iosMain.root);
|
|
11080
11577
|
const subIosDir = path.join(splitTempDir, WASM_SPLIT_SUBPACKAGE_CONFIG.iosSub.root);
|
|
11081
11578
|
[mainAndroidDir, subAndroidDir, mainIosDir, subIosDir].forEach(ensureDirSync);
|
|
11082
|
-
const
|
|
11083
|
-
const
|
|
11084
|
-
const
|
|
11579
|
+
const mainAndroidOut = path.join(mainAndroidDir, `${context.main_wasm_md5}${WASM_FILENAME_SUFFIX}`);
|
|
11580
|
+
const subAndroidOut = path.join(subAndroidDir, `${context.sub_wasm_md5}${WASM_FILENAME_SUFFIX}`);
|
|
11581
|
+
const mainIosOut = path.join(mainIosDir, `${context.main_wasm_h5_md5}${WASM_FILENAME_SUFFIX}`);
|
|
11085
11582
|
const limit = pLimit(CONCURRENCY_LIMIT);
|
|
11086
11583
|
try {
|
|
11087
|
-
|
|
11088
|
-
|
|
11089
|
-
|
|
11090
|
-
|
|
11584
|
+
verboseLog('[remote-split-download] start', {
|
|
11585
|
+
original_wasm_md5: context.original_wasm_md5,
|
|
11586
|
+
main_wasm_md5: context.main_wasm_md5,
|
|
11587
|
+
sub_wasm_md5: context.sub_wasm_md5,
|
|
11588
|
+
main_wasm_h5_md5: context.main_wasm_h5_md5,
|
|
11091
11589
|
});
|
|
11092
|
-
wsServer.sendUnitySplitStatus({
|
|
11093
|
-
status: 'start_download_android_sub_wasm_code',
|
|
11094
|
-
});
|
|
11095
|
-
wsServer.sendUnitySplitStatus({ status: 'start_download_ios_main_wasm' });
|
|
11096
|
-
/**
|
|
11097
|
-
* 需要做个保护,只有 有 URL 时才下载
|
|
11098
|
-
*/
|
|
11099
|
-
// 并发下载 + 压缩(带重试)
|
|
11100
11590
|
await Promise.all([
|
|
11101
|
-
limit(() =>
|
|
11102
|
-
|
|
11103
|
-
|
|
11104
|
-
startCompressStatus: 'start_compress_android_main_wasm',
|
|
11105
|
-
compressDoneStatus: 'compress_android_main_wasm_done',
|
|
11591
|
+
limit(() => downloadOne({
|
|
11592
|
+
startStatus: 'start_download_android_main_wasm',
|
|
11593
|
+
doneStatus: 'download_android_main_wasm_done',
|
|
11106
11594
|
url: context.main_wasm_download_url,
|
|
11107
|
-
out:
|
|
11595
|
+
out: mainAndroidOut,
|
|
11108
11596
|
})),
|
|
11109
|
-
limit(() =>
|
|
11110
|
-
|
|
11111
|
-
|
|
11112
|
-
startCompressStatus: 'start_compress_android_sub_wasm_code',
|
|
11113
|
-
compressDoneStatus: 'compress_android_sub_wasm_code_done',
|
|
11597
|
+
limit(() => downloadOne({
|
|
11598
|
+
startStatus: 'start_download_android_sub_wasm_code',
|
|
11599
|
+
doneStatus: 'download_android_sub_wasm_code_done',
|
|
11114
11600
|
url: context.sub_wasm_download_url,
|
|
11115
|
-
out:
|
|
11601
|
+
out: subAndroidOut,
|
|
11116
11602
|
})),
|
|
11117
|
-
limit(() =>
|
|
11118
|
-
|
|
11119
|
-
|
|
11120
|
-
startCompressStatus: 'start_compress_ios_main_wasm',
|
|
11121
|
-
compressDoneStatus: 'compress_ios_main_wasm_done',
|
|
11603
|
+
limit(() => downloadOne({
|
|
11604
|
+
startStatus: 'start_download_ios_main_wasm',
|
|
11605
|
+
doneStatus: 'download_ios_main_wasm_done',
|
|
11122
11606
|
url: context.main_wasm_h5_download_url,
|
|
11123
|
-
out:
|
|
11607
|
+
out: mainIosOut,
|
|
11124
11608
|
})),
|
|
11125
|
-
|
|
11126
|
-
|
|
11127
|
-
|
|
11128
|
-
downloadDoneStatus: 'download_ios_range_json_done',
|
|
11609
|
+
limit(() => downloadOne({
|
|
11610
|
+
startStatus: 'start_download_ios_range_json',
|
|
11611
|
+
doneStatus: 'download_ios_range_json_done',
|
|
11129
11612
|
url: context.sub_js_range_download_url,
|
|
11130
11613
|
out: path.join(subIosDir, 'func_bytes_range.json'),
|
|
11131
11614
|
})),
|
|
11132
|
-
|
|
11133
|
-
|
|
11134
|
-
|
|
11135
|
-
downloadDoneStatus: 'download_ios_js_data_br_done',
|
|
11615
|
+
limit(() => downloadOne({
|
|
11616
|
+
startStatus: 'start_download_ios_js_data_br',
|
|
11617
|
+
doneStatus: 'download_ios_js_data_br_done',
|
|
11136
11618
|
url: context.sub_js_data_download_url,
|
|
11137
11619
|
out: path.join(subIosDir, 'subjs.data'),
|
|
11138
11620
|
})),
|
|
11139
11621
|
]);
|
|
11140
|
-
|
|
11141
|
-
console.log('copy splitTempDir to root start');
|
|
11622
|
+
verboseLog('[remote-split-download] copying split output to project root...');
|
|
11142
11623
|
wsServer.sendUnitySplitStatus({ status: 'start_write_splited_wasm_br' });
|
|
11143
11624
|
for (const file of fs.readdirSync(splitTempDir)) {
|
|
11144
11625
|
const srcPath = path.join(splitTempDir, file);
|
|
11145
11626
|
const destPath = path.join(cwd, file);
|
|
11146
|
-
// 如果目标路径有文件或目录,先删除
|
|
11147
11627
|
if (fs.existsSync(destPath)) {
|
|
11148
11628
|
await promises.rm(destPath, { recursive: true, force: true });
|
|
11149
11629
|
}
|
|
11150
11630
|
await promises.cp(srcPath, destPath, { recursive: true, force: true });
|
|
11151
11631
|
}
|
|
11152
11632
|
wsServer.sendUnitySplitStatus({ status: 'write_splited_wasm_done' });
|
|
11153
|
-
|
|
11154
|
-
|
|
11155
|
-
|
|
11156
|
-
updateSubpackageConfigSync();
|
|
11157
|
-
console.log('updateSubpackageConfigSync end');
|
|
11158
|
-
// 更新 wasm split 配置(保持原始状态文案)
|
|
11159
|
-
console.log('updateWasmSplitConfig start');
|
|
11633
|
+
verboseLog('[remote-split-download] updating subpackage config...');
|
|
11634
|
+
updateSubpackageConfigSync(false);
|
|
11635
|
+
verboseLog('[remote-split-download] updating webgl-wasm-split.js...');
|
|
11160
11636
|
wsServer.sendUnitySplitStatus({ status: 'start_update_wasm_split_config' });
|
|
11161
11637
|
updateWasmSplitConfig({
|
|
11162
11638
|
ENABLEWASMCOLLECT: true,
|
|
11163
|
-
|
|
11639
|
+
ENABLEWASMSPLIT: true,
|
|
11640
|
+
ENABLEARCHIVEMODE: false,
|
|
11641
|
+
ORIGINALWASMMD5: `${context.original_wasm_md5 ?? ''}`,
|
|
11164
11642
|
WASMTABLESIZE: context.table_size,
|
|
11165
11643
|
GLOBALVARLIST: JSON.stringify(context.global_var_list ?? []),
|
|
11166
|
-
SUBJSURL: `${context.sub_js_download_url}`,
|
|
11167
|
-
IOS_CODE_FILE_MD5: `${context.main_wasm_h5_md5}`,
|
|
11168
|
-
ANDROID_CODE_FILE_MD5: `${context.main_wasm_md5}`,
|
|
11169
|
-
ANDROID_SUB_CODE_FILE_MD5: `${context.sub_wasm_md5}`,
|
|
11170
|
-
WASMSPLITVERSION: `${context.version}`,
|
|
11644
|
+
SUBJSURL: `${context.sub_js_download_url ?? ''}`,
|
|
11645
|
+
IOS_CODE_FILE_MD5: `${context.main_wasm_h5_md5 ?? ''}`,
|
|
11646
|
+
ANDROID_CODE_FILE_MD5: `${context.main_wasm_md5 ?? ''}`,
|
|
11647
|
+
ANDROID_SUB_CODE_FILE_MD5: `${context.sub_wasm_md5 ?? ''}`,
|
|
11648
|
+
WASMSPLITVERSION: `${context.version ?? ''}`,
|
|
11171
11649
|
USINGWASMH5: Boolean(context.main_wasm_h5_md5),
|
|
11172
|
-
ENABLEWASMSPLIT: true,
|
|
11173
|
-
// IOS_SUB_JS_FILE_CONFIG: JSON.stringify(context.merged_js ?? {}),
|
|
11174
11650
|
});
|
|
11175
11651
|
wsServer.sendUnitySplitStatus({ status: 'update_wasm_split_config_done' });
|
|
11176
|
-
|
|
11177
|
-
return {
|
|
11178
|
-
data: {
|
|
11179
|
-
isSuccess: true,
|
|
11180
|
-
},
|
|
11181
|
-
ctx: context,
|
|
11182
|
-
};
|
|
11652
|
+
verboseLog('[remote-split-download] all done');
|
|
11653
|
+
return { data: { isSuccess: true }, ctx: context };
|
|
11183
11654
|
}
|
|
11184
11655
|
catch (err) {
|
|
11185
|
-
|
|
11186
|
-
|
|
11187
|
-
|
|
11188
|
-
});
|
|
11656
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
11657
|
+
verboseLog('[remote-split-download] failed:', message);
|
|
11658
|
+
wsServer.sendUnitySplitStatus({ status: 'wasm_split_failed', errorMsg: message });
|
|
11189
11659
|
return {
|
|
11190
|
-
data: {
|
|
11191
|
-
|
|
11192
|
-
},
|
|
11193
|
-
error: {
|
|
11194
|
-
message: err instanceof Error ? err.message : String(err),
|
|
11195
|
-
},
|
|
11660
|
+
data: { isSuccess: false },
|
|
11661
|
+
error: { message },
|
|
11196
11662
|
ctx: context,
|
|
11197
11663
|
};
|
|
11198
11664
|
}
|
|
11199
11665
|
finally {
|
|
11200
|
-
// 清理临时目录与旧 wasmcode 目录
|
|
11201
|
-
console.log('delete splitTempDir start');
|
|
11202
11666
|
await promises.rm(splitTempDir, { recursive: true, force: true });
|
|
11203
|
-
|
|
11204
|
-
|
|
11667
|
+
// Legacy flow: the server-produced `wasmcode/` placeholder at the project
|
|
11668
|
+
// root is no longer needed once we've laid down the 4 platform dirs.
|
|
11205
11669
|
await promises.rm(path.join(cwd, WASM_SPLIT_SUBPACKAGE_CONFIG.origin.root), {
|
|
11206
11670
|
recursive: true,
|
|
11207
11671
|
force: true,
|
|
11208
11672
|
});
|
|
11209
|
-
console.log('delete wasmcode end');
|
|
11210
11673
|
}
|
|
11211
11674
|
}
|
|
11212
|
-
|
|
11213
|
-
async function getSplitResult(
|
|
11675
|
+
|
|
11676
|
+
async function getSplitResult$1(_params) {
|
|
11677
|
+
const { splitMeta } = getLocalState();
|
|
11678
|
+
if (!splitMeta) {
|
|
11679
|
+
return {
|
|
11680
|
+
data: null,
|
|
11681
|
+
error: {
|
|
11682
|
+
code: 404,
|
|
11683
|
+
message: 'No local split result found. Run split first.',
|
|
11684
|
+
},
|
|
11685
|
+
ctx: { logid: 'local', httpStatusCode: 404 },
|
|
11686
|
+
};
|
|
11687
|
+
}
|
|
11688
|
+
return {
|
|
11689
|
+
data: {
|
|
11690
|
+
code: 0,
|
|
11691
|
+
message: 'success',
|
|
11692
|
+
result: splitMeta,
|
|
11693
|
+
},
|
|
11694
|
+
error: null,
|
|
11695
|
+
ctx: { logid: 'local', httpStatusCode: 200 },
|
|
11696
|
+
};
|
|
11697
|
+
}
|
|
11698
|
+
|
|
11699
|
+
/**
|
|
11700
|
+
* Local-only undo of a prepare run. Routes through the shared
|
|
11701
|
+
* `restoreFromCache` helper so this stays in sync with `resetWasmSplit`
|
|
11702
|
+
* and never falls behind when new split-output dirs are added.
|
|
11703
|
+
*
|
|
11704
|
+
* `wasmCodePath` is kept on the signature for backward-compat with the
|
|
11705
|
+
* existing /game/wasm-cancel route shape, but it's no longer needed —
|
|
11706
|
+
* `restoreFromCache` already resolves the wasm path from the cache
|
|
11707
|
+
* directory contents (whatever `keepCacheSync` recorded as the original).
|
|
11708
|
+
*/
|
|
11709
|
+
function cancelSplit(_params) {
|
|
11710
|
+
restoreFromCache();
|
|
11711
|
+
// Drop the prepared-meta anchor for the same reason resetWasmSplit
|
|
11712
|
+
// does: cancel rolled the wasm back to its un-instrumented state, so
|
|
11713
|
+
// the recorded "preparedWasmMd5" is no longer accurate. Leaving it
|
|
11714
|
+
// would trip the split-config drift guard on the very next Modal open.
|
|
11715
|
+
const preparedMetaPath = path__namespace.join(process.cwd(), TTMG_TEMP_DIR, 'prepared-meta.json');
|
|
11716
|
+
if (fs__namespace.existsSync(preparedMetaPath)) {
|
|
11717
|
+
fs__namespace.rmSync(preparedMetaPath, { force: true });
|
|
11718
|
+
}
|
|
11719
|
+
}
|
|
11720
|
+
|
|
11721
|
+
async function resetWasmSplit$1(data) {
|
|
11722
|
+
const res = await request({
|
|
11723
|
+
url: `${WASM_COLLECT_BASE_URL}/reset`,
|
|
11724
|
+
method: 'POST',
|
|
11725
|
+
data: {
|
|
11726
|
+
app_id: data.clientkey,
|
|
11727
|
+
wasm_md5: data.wasmMd5,
|
|
11728
|
+
},
|
|
11729
|
+
});
|
|
11730
|
+
// Reuse the shared restore helper so local + remote rollback cleans
|
|
11731
|
+
// exactly the same set of files. Previously this function had its own
|
|
11732
|
+
// inline copy that only removed wasmcode-android / wasmcode1-android /
|
|
11733
|
+
// wasmcode-ios — missing wasmcode1 / wasmcode-archive (archive mode)
|
|
11734
|
+
// and wasmcode1-ios (legacy iOS sub), which left stale split outputs
|
|
11735
|
+
// on disk and caused them to be re-uploaded on the next build.
|
|
11736
|
+
restoreFromCache();
|
|
11737
|
+
// Drop the prepared-meta anchor as well — rollback restores the
|
|
11738
|
+
// original wasm into the project, so any md5 we previously recorded
|
|
11739
|
+
// for the prepared build is no longer valid. Leaving it behind would
|
|
11740
|
+
// make the split-config drift guard fire on the very next Modal open
|
|
11741
|
+
// and force the user through a redundant prepare cycle.
|
|
11742
|
+
const preparedMetaPath = path.join(process.cwd(), TTMG_TEMP_DIR, 'prepared-meta.json');
|
|
11743
|
+
if (fs.existsSync(preparedMetaPath)) {
|
|
11744
|
+
fs.rmSync(preparedMetaPath, { force: true });
|
|
11745
|
+
}
|
|
11746
|
+
return res;
|
|
11747
|
+
}
|
|
11748
|
+
|
|
11749
|
+
function getSplitConfig() {
|
|
11750
|
+
const configFilePath = path__namespace.join(process.cwd(), WASM_SPLIT_CONFIG_FILE_NAME);
|
|
11751
|
+
try {
|
|
11752
|
+
// 1. 检查文件是否存在
|
|
11753
|
+
if (!fs__namespace.existsSync(configFilePath)) {
|
|
11754
|
+
console.error(`Config file not found at: ${configFilePath}`);
|
|
11755
|
+
return null;
|
|
11756
|
+
}
|
|
11757
|
+
// 2. 同步读取文件内容为字符串
|
|
11758
|
+
const fileContent = fs__namespace.readFileSync(configFilePath, 'utf-8');
|
|
11759
|
+
// 3. 构造一个函数来执行并返回 module.exports 的内容
|
|
11760
|
+
// 这是一种比直接用 eval() 更安全的方式,因为它限制了代码的执行作用域
|
|
11761
|
+
const evaluateModule = new Function('module', `${fileContent}; return module.exports;`);
|
|
11762
|
+
// 准备一个临时的 module 对象
|
|
11763
|
+
const tempModule = { exports: {} };
|
|
11764
|
+
// 4. 执行函数,并将配置赋值给 config 变量
|
|
11765
|
+
const config = evaluateModule(tempModule);
|
|
11766
|
+
// 5. 检查是否成功获取了配置
|
|
11767
|
+
if (typeof config === 'object' && config !== null) {
|
|
11768
|
+
return config;
|
|
11769
|
+
}
|
|
11770
|
+
else {
|
|
11771
|
+
console.error('Failed to extract a valid config object from the file.');
|
|
11772
|
+
return null;
|
|
11773
|
+
}
|
|
11774
|
+
}
|
|
11775
|
+
catch (error) {
|
|
11776
|
+
console.error('Error reading or evaluating split config file:', error);
|
|
11777
|
+
return null;
|
|
11778
|
+
}
|
|
11779
|
+
}
|
|
11780
|
+
|
|
11781
|
+
var WasmStatus;
|
|
11782
|
+
(function (WasmStatus) {
|
|
11783
|
+
WasmStatus[WasmStatus["IdleStatus"] = 0] = "IdleStatus";
|
|
11784
|
+
WasmStatus[WasmStatus["WasmPreparingStatus"] = 1] = "WasmPreparingStatus";
|
|
11785
|
+
WasmStatus[WasmStatus["WasmPreparedStatus"] = 2] = "WasmPreparedStatus";
|
|
11786
|
+
WasmStatus[WasmStatus["WasmSplitStatus"] = 3] = "WasmSplitStatus";
|
|
11787
|
+
WasmStatus[WasmStatus["WasmSplittingStatus"] = 4] = "WasmSplittingStatus";
|
|
11788
|
+
WasmStatus[WasmStatus["WasmSplitDoneStatus"] = 5] = "WasmSplitDoneStatus";
|
|
11789
|
+
WasmStatus[WasmStatus["WasmSplitReadyToPrepareStatus"] = 6] = "WasmSplitReadyToPrepareStatus";
|
|
11790
|
+
WasmStatus[WasmStatus["WasmSplitPreparingStatus"] = 7] = "WasmSplitPreparingStatus";
|
|
11791
|
+
WasmStatus[WasmStatus["WasmSplitPreparedStatus"] = 8] = "WasmSplitPreparedStatus";
|
|
11792
|
+
WasmStatus[WasmStatus["WasmCollectingStatus"] = 9] = "WasmCollectingStatus";
|
|
11793
|
+
WasmStatus[WasmStatus["WasmUploadFailStatus"] = -1] = "WasmUploadFailStatus";
|
|
11794
|
+
WasmStatus[WasmStatus["WasmDownloadFailStatus"] = -2] = "WasmDownloadFailStatus";
|
|
11795
|
+
WasmStatus[WasmStatus["WasmFileNotExistStatus"] = -3] = "WasmFileNotExistStatus";
|
|
11796
|
+
WasmStatus[WasmStatus["WasmSplitFailStatus"] = -4] = "WasmSplitFailStatus";
|
|
11797
|
+
WasmStatus[WasmStatus["WasmSplitUpdateDBFailedStatus"] = -5] = "WasmSplitUpdateDBFailedStatus";
|
|
11798
|
+
WasmStatus[WasmStatus["WasmSplitPrepareFailedStatus"] = -6] = "WasmSplitPrepareFailedStatus";
|
|
11799
|
+
})(WasmStatus || (WasmStatus = {}));
|
|
11800
|
+
|
|
11801
|
+
const getTaskStatus$1 = async (params) => {
|
|
11802
|
+
const { preparedWasmPath, splitMeta } = getLocalState();
|
|
11803
|
+
let status = WasmStatus.IdleStatus;
|
|
11804
|
+
if (splitMeta) {
|
|
11805
|
+
status = WasmStatus.WasmSplitDoneStatus;
|
|
11806
|
+
}
|
|
11807
|
+
else if (preparedWasmPath) {
|
|
11808
|
+
status = WasmStatus.WasmSplitPreparedStatus;
|
|
11809
|
+
}
|
|
11810
|
+
return {
|
|
11811
|
+
data: {
|
|
11812
|
+
code: 0,
|
|
11813
|
+
message: 'success',
|
|
11814
|
+
result: {
|
|
11815
|
+
status,
|
|
11816
|
+
wasm_md5: params.wasm_md5,
|
|
11817
|
+
},
|
|
11818
|
+
},
|
|
11819
|
+
error: null,
|
|
11820
|
+
ctx: { logid: 'local', httpStatusCode: 200 },
|
|
11821
|
+
};
|
|
11822
|
+
};
|
|
11823
|
+
|
|
11824
|
+
const getTaskInfo$1 = async (params) => {
|
|
11825
|
+
const res = await request({
|
|
11826
|
+
url: `${WASM_COLLECT_BASE_URL}/progress`,
|
|
11827
|
+
method: 'GET',
|
|
11828
|
+
params: {
|
|
11829
|
+
app_id: params.client_key,
|
|
11830
|
+
wasm_md5: params.wasm_md5,
|
|
11831
|
+
},
|
|
11832
|
+
});
|
|
11833
|
+
const { totalWasmFuncCount, preparedWasmPath, wasmSize } = getLocalState();
|
|
11834
|
+
// Prefer game.json as the source of truth so wasm_size / total_wasm_func_count
|
|
11835
|
+
// survive CLI restarts. localState values are only populated during the
|
|
11836
|
+
// prepare step of the current session; after a restart they default to 0.
|
|
11837
|
+
// game.json carries wasmCodeSize/wasmFuncCount emitted at build time, so
|
|
11838
|
+
// re-entering the collect step still shows the correct totals.
|
|
11839
|
+
const gameJson = getGameJson();
|
|
11840
|
+
const gameJsonWasmSize = Number(gameJson?.wasmCodeSize) || 0;
|
|
11841
|
+
const gameJsonFuncCount = Number(gameJson?.wasmFuncCount) || 0;
|
|
11842
|
+
return {
|
|
11843
|
+
data: {
|
|
11844
|
+
code: res?.data?.code ?? 0,
|
|
11845
|
+
message: 'success',
|
|
11846
|
+
result: {
|
|
11847
|
+
app_id: params.client_key,
|
|
11848
|
+
wasm_md5: params.wasm_md5,
|
|
11849
|
+
is_prepared: Boolean(preparedWasmPath),
|
|
11850
|
+
collected_func_count: res?.data?.func_count ?? 0,
|
|
11851
|
+
total_wasm_func_count: gameJsonFuncCount || totalWasmFuncCount || 0,
|
|
11852
|
+
wasm_size: gameJsonWasmSize || wasmSize || 0,
|
|
11853
|
+
},
|
|
11854
|
+
},
|
|
11855
|
+
error: res.error,
|
|
11856
|
+
ctx: res.ctx,
|
|
11857
|
+
};
|
|
11858
|
+
};
|
|
11859
|
+
|
|
11860
|
+
async function startPrepareRemote(params) {
|
|
11861
|
+
// Back up the original wasm + split config on the first run so cancel/rollback
|
|
11862
|
+
// works even if the user aborts before the server-side prepare finishes.
|
|
11863
|
+
keepCacheSync({
|
|
11864
|
+
entryDir: process.cwd(),
|
|
11865
|
+
originalWasmPath: params.wasm_file_path,
|
|
11866
|
+
originalSplitConfigPath: WASM_SPLIT_CONFIG_FILE_NAME,
|
|
11867
|
+
});
|
|
11868
|
+
const form = new FormData$1();
|
|
11869
|
+
form.append('desc', params.desc);
|
|
11870
|
+
form.append('wasm_md5', params.wasm_md5);
|
|
11871
|
+
form.append('with_ios', 'true');
|
|
11872
|
+
form.append('wasm_file', fs$1.createReadStream(path$1.join(process.cwd(), params.wasm_file_path)), { filename: path$1.basename(params.wasm_file_path), contentType: 'application/wasm' });
|
|
11873
|
+
let symbolFilePath = path$1.join(process.cwd(), TTMG_TEMP_DIR, WASM_SYMBOL_FILE_NAME);
|
|
11874
|
+
if (!fs$1.existsSync(symbolFilePath)) {
|
|
11875
|
+
symbolFilePath = path$1.join(process.cwd(), WASM_SYMBOL_FILE_NAME);
|
|
11876
|
+
}
|
|
11877
|
+
if (!fs$1.existsSync(symbolFilePath)) {
|
|
11878
|
+
return {
|
|
11879
|
+
error: { code: 400, message: `${WASM_SYMBOL_FILE_NAME} not found`, client_key: params.client_key },
|
|
11880
|
+
data: null,
|
|
11881
|
+
ctx: { logid: '', httpStatusCode: 400 },
|
|
11882
|
+
};
|
|
11883
|
+
}
|
|
11884
|
+
form.append('wasm_symbol_file', fs$1.createReadStream(symbolFilePath), {
|
|
11885
|
+
filename: WASM_SYMBOL_FILE_NAME,
|
|
11886
|
+
contentType: 'application/octet-stream',
|
|
11887
|
+
});
|
|
11888
|
+
const formHeaders = form.getHeaders();
|
|
11889
|
+
return request({
|
|
11890
|
+
url: `${BASE_URL}/api/stark_wasm/v4/post/prepare`,
|
|
11891
|
+
method: 'POST',
|
|
11892
|
+
headers: { ...DEV_HEADERS, ...formHeaders },
|
|
11893
|
+
params: { client_key: params.client_key, with_ios: true },
|
|
11894
|
+
data: form,
|
|
11895
|
+
});
|
|
11896
|
+
}
|
|
11897
|
+
async function setCollectRemote({ client_key, wasm_md5 }) {
|
|
11898
|
+
return request({
|
|
11899
|
+
url: `${BASE_URL}/api/stark_wasm/v4/post/set_collecting`,
|
|
11900
|
+
method: 'POST',
|
|
11901
|
+
data: { client_key, wasm_md5 },
|
|
11902
|
+
headers: DEV_HEADERS,
|
|
11903
|
+
});
|
|
11904
|
+
}
|
|
11905
|
+
async function getCollectedFuncIdsRemote({ client_key, wasm_md5 }) {
|
|
11906
|
+
return request({
|
|
11907
|
+
url: `${BASE_URL}/api/stark_wasm/v4/get/collectedfuncids`,
|
|
11908
|
+
method: 'GET',
|
|
11909
|
+
headers: DEV_HEADERS,
|
|
11910
|
+
params: { client_key, wasm_md5 },
|
|
11911
|
+
});
|
|
11912
|
+
}
|
|
11913
|
+
async function getCollecttingInfoRemote({ client_key, wasm_md5 }) {
|
|
11914
|
+
return request({
|
|
11915
|
+
url: `${BASE_URL}/api/stark_wasm/v4/get/funccollect`,
|
|
11916
|
+
method: 'GET',
|
|
11917
|
+
headers: DEV_HEADERS,
|
|
11918
|
+
params: { client_key, wasm_md5 },
|
|
11919
|
+
});
|
|
11920
|
+
}
|
|
11921
|
+
async function startSplitRemote({ client_key, wasm_md5 }) {
|
|
11922
|
+
return request({
|
|
11923
|
+
url: `${BASE_URL}/api/stark_wasm/v4/post/split`,
|
|
11924
|
+
method: 'POST',
|
|
11925
|
+
headers: { ...DEV_HEADERS },
|
|
11926
|
+
data: { client_key, wasm_md5 },
|
|
11927
|
+
});
|
|
11928
|
+
}
|
|
11929
|
+
async function getTaskInfoRemote(params) {
|
|
11930
|
+
return request({
|
|
11931
|
+
url: `${BASE_URL}/api/stark_wasm/v4/get/taskinfo`,
|
|
11932
|
+
method: 'GET',
|
|
11933
|
+
headers: DEV_HEADERS,
|
|
11934
|
+
params,
|
|
11935
|
+
});
|
|
11936
|
+
}
|
|
11937
|
+
async function getTaskStatusRemote(params) {
|
|
11938
|
+
return request({
|
|
11939
|
+
url: `${BASE_URL}/api/stark_wasm/v4/get/status`,
|
|
11940
|
+
method: 'GET',
|
|
11941
|
+
headers: DEV_HEADERS,
|
|
11942
|
+
params,
|
|
11943
|
+
});
|
|
11944
|
+
}
|
|
11945
|
+
async function resetWasmSplitRemote(data) {
|
|
11946
|
+
const res = await request({
|
|
11947
|
+
url: `${BASE_URL}/api/stark_wasm/v4/post/reset`,
|
|
11948
|
+
method: 'POST',
|
|
11949
|
+
headers: { ...DEV_HEADERS },
|
|
11950
|
+
data: { client_key: data.clientkey, wasm_md5: data.wasmMd5 },
|
|
11951
|
+
});
|
|
11952
|
+
// Restore project files (wasm / webgl-wasm-split.js / game.json) so the
|
|
11953
|
+
// next prepare starts from the original placeholders.
|
|
11954
|
+
restoreFromCache();
|
|
11955
|
+
return res;
|
|
11956
|
+
}
|
|
11957
|
+
async function getSplitResultRemote({ client_key, wasm_md5, wasm_path }) {
|
|
11214
11958
|
return request({
|
|
11215
11959
|
url: `${BASE_URL}/api/stark_wasm/v4/post/download`,
|
|
11216
11960
|
method: 'POST',
|
|
@@ -11218,162 +11962,121 @@ async function getSplitResult({ client_key, wasm_md5, wasm_path, }) {
|
|
|
11218
11962
|
data: { client_key, wasm_md5, wasm_path },
|
|
11219
11963
|
});
|
|
11220
11964
|
}
|
|
11221
|
-
|
|
11222
|
-
|
|
11223
|
-
|
|
11224
|
-
|
|
11225
|
-
|
|
11226
|
-
|
|
11227
|
-
|
|
11228
|
-
/**
|
|
11229
|
-
* 恢复 br 文件
|
|
11230
|
-
*/
|
|
11231
|
-
if (fs__namespace.existsSync(cacheDir)) {
|
|
11232
|
-
/**
|
|
11233
|
-
* 判断是否有缓存的 br 文件
|
|
11234
|
-
*/
|
|
11235
|
-
const targetWasmBrPath = path__namespace.join(cacheDir, path__namespace.basename(wasmCodePath));
|
|
11236
|
-
if (fs__namespace.existsSync(targetWasmBrPath)) {
|
|
11237
|
-
const destWasmBrPath = path__namespace.join(process.cwd(), wasmCodePath);
|
|
11238
|
-
// 规避没有文件夹的情况
|
|
11239
|
-
ensureDirSync(path__namespace.dirname(destWasmBrPath));
|
|
11240
|
-
fs__namespace.copyFileSync(targetWasmBrPath, destWasmBrPath);
|
|
11241
|
-
}
|
|
11242
|
-
}
|
|
11243
|
-
/**
|
|
11244
|
-
* 恢复 webgl-wasm-split.js 文件
|
|
11245
|
-
*/
|
|
11246
|
-
const splitConfigCachePath = path__namespace.join(cacheDir, WASM_SPLIT_CONFIG_FILE_NAME);
|
|
11247
|
-
if (fs__namespace.existsSync(splitConfigCachePath)) {
|
|
11248
|
-
fs__namespace.copyFileSync(splitConfigCachePath, path__namespace.join(process.cwd(), WASM_SPLIT_CONFIG_FILE_NAME));
|
|
11249
|
-
}
|
|
11250
|
-
/**
|
|
11251
|
-
* 恢复 game.json 文件
|
|
11252
|
-
*/
|
|
11253
|
-
const gameJsonCachePath = path__namespace.join(cacheDir, 'game.json');
|
|
11254
|
-
if (fs__namespace.existsSync(gameJsonCachePath)) {
|
|
11255
|
-
fs__namespace.copyFileSync(gameJsonCachePath, path__namespace.join(process.cwd(), 'game.json'));
|
|
11256
|
-
}
|
|
11257
|
-
}
|
|
11258
|
-
|
|
11259
|
-
async function resetWasmSplit(data) {
|
|
11965
|
+
/**
|
|
11966
|
+
* Remote pipeline: after the server finishes preparing (instrumenting) the wasm,
|
|
11967
|
+
* fetch the download URL, download the prepared wasm, replace the project file,
|
|
11968
|
+
* and update webgl-wasm-split.js for the LEGACY reporting flow.
|
|
11969
|
+
*/
|
|
11970
|
+
async function downloadPreparedRemote(data) {
|
|
11971
|
+
wsServer.sendUnitySplitStatus({ status: 'star_fetch_prepared_wasm_url' });
|
|
11260
11972
|
const res = await request({
|
|
11261
|
-
url: `${BASE_URL}/api/stark_wasm/v4/post/
|
|
11973
|
+
url: `${BASE_URL}/api/stark_wasm/v4/post/download_prepared`,
|
|
11262
11974
|
method: 'POST',
|
|
11263
|
-
headers:
|
|
11264
|
-
|
|
11265
|
-
},
|
|
11266
|
-
data: {
|
|
11267
|
-
client_key: data.clientkey,
|
|
11268
|
-
wasm_md5: data.wasmMd5,
|
|
11269
|
-
},
|
|
11975
|
+
headers: DEV_HEADERS,
|
|
11976
|
+
data,
|
|
11270
11977
|
});
|
|
11271
|
-
|
|
11272
|
-
* 把— __TTMG_TEMP__/wasmcode/ 目录下的所有文件恢复到原本的位置,进行重置
|
|
11273
|
-
*/
|
|
11274
|
-
const cacheDir = path.join(process.cwd(), WASM_SPLIT_CACHE_DIR);
|
|
11275
|
-
/**
|
|
11276
|
-
* 恢复 br 文件
|
|
11277
|
-
*/
|
|
11278
|
-
if (fs.existsSync(cacheDir)) {
|
|
11279
|
-
/**
|
|
11280
|
-
* 判断是否有缓存的 br 文件
|
|
11281
|
-
*/
|
|
11282
|
-
/**
|
|
11283
|
-
* 判断 cache 文件夹下有没有 .br 文件
|
|
11284
|
-
*
|
|
11285
|
-
*/
|
|
11286
|
-
const targetWasmBrPath = fs
|
|
11287
|
-
.readdirSync(cacheDir)
|
|
11288
|
-
.find(item => item.endsWith('.br'));
|
|
11289
|
-
if (targetWasmBrPath) {
|
|
11290
|
-
const destWasmBrPath = path.join(process.cwd(), WASM_SPLIT_SUBPACKAGE_CONFIG.origin.root, path.basename(targetWasmBrPath));
|
|
11291
|
-
// 规避没有文件夹的情况
|
|
11292
|
-
ensureDirSync(path.dirname(destWasmBrPath));
|
|
11293
|
-
fs.copyFileSync(path.join(cacheDir, targetWasmBrPath), destWasmBrPath);
|
|
11294
|
-
}
|
|
11295
|
-
}
|
|
11296
|
-
/**
|
|
11297
|
-
* 恢复 webgl-wasm-split.js 文件
|
|
11298
|
-
*/
|
|
11299
|
-
const splitConfigCachePath = path.join(cacheDir, WASM_SPLIT_CONFIG_FILE_NAME);
|
|
11300
|
-
if (fs.existsSync(splitConfigCachePath)) {
|
|
11301
|
-
fs.copyFileSync(splitConfigCachePath, path.join(process.cwd(), WASM_SPLIT_CONFIG_FILE_NAME));
|
|
11302
|
-
}
|
|
11303
|
-
/**
|
|
11304
|
-
* 恢复 game.json 文件
|
|
11305
|
-
*/
|
|
11306
|
-
const gameJsonCachePath = path.join(cacheDir, 'game.json');
|
|
11307
|
-
if (fs.existsSync(gameJsonCachePath)) {
|
|
11308
|
-
fs.copyFileSync(gameJsonCachePath, path.join(process.cwd(), 'game.json'));
|
|
11309
|
-
}
|
|
11310
|
-
/**
|
|
11311
|
-
* 删除历史分包产物
|
|
11312
|
-
*/
|
|
11313
|
-
const androidSubpackageDir = path.join(process.cwd(), WASM_SPLIT_SUBPACKAGE_CONFIG.androidMain.root);
|
|
11314
|
-
if (fs.existsSync(androidSubpackageDir)) {
|
|
11315
|
-
fs.rmSync(androidSubpackageDir, { recursive: true });
|
|
11316
|
-
}
|
|
11317
|
-
const androidSubpackageSubDir = path.join(process.cwd(), WASM_SPLIT_SUBPACKAGE_CONFIG.androidSub.root);
|
|
11318
|
-
if (fs.existsSync(androidSubpackageSubDir)) {
|
|
11319
|
-
fs.rmSync(androidSubpackageSubDir, { recursive: true });
|
|
11320
|
-
}
|
|
11321
|
-
const iosSubpackageDir = path.join(process.cwd(), WASM_SPLIT_SUBPACKAGE_CONFIG.ios.root);
|
|
11322
|
-
if (fs.existsSync(iosSubpackageDir)) {
|
|
11323
|
-
fs.rmSync(iosSubpackageDir, { recursive: true });
|
|
11324
|
-
}
|
|
11325
|
-
return res;
|
|
11326
|
-
}
|
|
11327
|
-
|
|
11328
|
-
function getSplitConfig() {
|
|
11329
|
-
const configFilePath = path__namespace.join(process.cwd(), WASM_SPLIT_CONFIG_FILE_NAME);
|
|
11978
|
+
wsServer.sendUnitySplitStatus({ status: 'fetch_prepared_wasm_url_done' });
|
|
11330
11979
|
try {
|
|
11331
|
-
|
|
11332
|
-
if (!
|
|
11333
|
-
|
|
11334
|
-
return
|
|
11980
|
+
const downloadUrl = res?.data?.result?.download_url;
|
|
11981
|
+
if (!downloadUrl) {
|
|
11982
|
+
verboseLog('[remote-download-prepared] no download_url in response');
|
|
11983
|
+
return {
|
|
11984
|
+
isSuccess: false,
|
|
11985
|
+
error: { code: res.data?.code, message: res.data?.message || 'No download_url returned' },
|
|
11986
|
+
ctx: res?.ctx,
|
|
11987
|
+
};
|
|
11335
11988
|
}
|
|
11336
|
-
|
|
11337
|
-
const
|
|
11338
|
-
|
|
11339
|
-
|
|
11340
|
-
|
|
11341
|
-
|
|
11342
|
-
|
|
11343
|
-
|
|
11344
|
-
|
|
11345
|
-
|
|
11346
|
-
|
|
11347
|
-
|
|
11989
|
+
const willReplaceWasmPath = path$1.join(process.cwd(), data.wasm_path);
|
|
11990
|
+
const { cacheDir } = keepCacheSync({
|
|
11991
|
+
entryDir: process.cwd(),
|
|
11992
|
+
originalWasmPath: data.wasm_path,
|
|
11993
|
+
originalSplitConfigPath: WASM_SPLIT_CONFIG_FILE_NAME,
|
|
11994
|
+
});
|
|
11995
|
+
verboseLog(`[remote-download-prepared] target=${willReplaceWasmPath}`);
|
|
11996
|
+
if (downloadUrl.includes('.br')) {
|
|
11997
|
+
const tempWasmPath = path$1.join(cacheDir, '__temp__.wasm.br');
|
|
11998
|
+
verboseLog('[remote-download-prepared] downloading (br) ->', tempWasmPath);
|
|
11999
|
+
wsServer.sendUnitySplitStatus({ status: 'start_download_prepared_wasm', url: downloadUrl });
|
|
12000
|
+
const startedAt = Date.now();
|
|
12001
|
+
await download(downloadUrl, tempWasmPath);
|
|
12002
|
+
verboseLog(`[remote-download-prepared] download done in ${Date.now() - startedAt}ms, size=${fs$1.statSync(tempWasmPath).size}`);
|
|
12003
|
+
fs$1.copyFileSync(tempWasmPath, willReplaceWasmPath);
|
|
12004
|
+
wsServer.sendUnitySplitStatus({ status: 'download_prepared_wasm_done', url: downloadUrl });
|
|
11348
12005
|
}
|
|
11349
12006
|
else {
|
|
11350
|
-
|
|
11351
|
-
|
|
12007
|
+
const tempWasmPath = path$1.join(cacheDir, '__temp__.wasm');
|
|
12008
|
+
verboseLog('[remote-download-prepared] downloading (raw) ->', tempWasmPath);
|
|
12009
|
+
wsServer.sendUnitySplitStatus({ status: 'start_download_prepared_wasm', url: downloadUrl });
|
|
12010
|
+
const startedAt = Date.now();
|
|
12011
|
+
await download(downloadUrl, tempWasmPath);
|
|
12012
|
+
verboseLog(`[remote-download-prepared] download done in ${Date.now() - startedAt}ms, size=${fs$1.statSync(tempWasmPath).size}`);
|
|
12013
|
+
wsServer.sendUnitySplitStatus({ status: 'download_prepared_wasm_done', url: downloadUrl });
|
|
12014
|
+
wsServer.sendUnitySplitStatus({ status: 'start_compress_prepared_wasm' });
|
|
12015
|
+
await compressWasmFile(tempWasmPath, willReplaceWasmPath);
|
|
12016
|
+
verboseLog('[remote-download-prepared] compressed and written to project');
|
|
12017
|
+
wsServer.sendUnitySplitStatus({ status: 'compress_prepared_wasm_done', url: downloadUrl });
|
|
12018
|
+
wsServer.sendUnitySplitStatus({ status: 'write_compress_prepared_wasm_done' });
|
|
11352
12019
|
}
|
|
12020
|
+
wsServer.sendUnitySplitStatus({ status: 'start_update_wasm_split_config' });
|
|
12021
|
+
// Remote (legacy) pipeline: enable collect but disable archive mode so the
|
|
12022
|
+
// plugin reports to the legacy stark_wasm/v4 collect API.
|
|
12023
|
+
// ORIGINALWASMMD5 must be set now (not only at split time) so the plugin
|
|
12024
|
+
// sends the correct wasm_md5 in every collect report.
|
|
12025
|
+
restoreSplitConfigFromCache();
|
|
12026
|
+
updateWasmSplitConfig({
|
|
12027
|
+
ENABLEWASMCOLLECT: true,
|
|
12028
|
+
ENABLEARCHIVEMODE: false,
|
|
12029
|
+
ORIGINALWASMMD5: res?.data?.result?.original_wasm_md5 ||
|
|
12030
|
+
res?.data?.result?.md5 ||
|
|
12031
|
+
data.wasm_md5,
|
|
12032
|
+
});
|
|
12033
|
+
wsServer.sendUnitySplitStatus({ status: 'update_wasm_split_config_done' });
|
|
12034
|
+
verboseLog('[remote-download-prepared] split config updated, returning success');
|
|
12035
|
+
return { isSuccess: true, ctx: res?.ctx };
|
|
11353
12036
|
}
|
|
11354
12037
|
catch (error) {
|
|
11355
|
-
|
|
11356
|
-
return
|
|
12038
|
+
verboseLog('[remote-download-prepared] error:', error);
|
|
12039
|
+
return {
|
|
12040
|
+
isSuccess: false,
|
|
12041
|
+
error: { code: res.data?.code, message: error instanceof Error ? error.message : String(error) },
|
|
12042
|
+
ctx: res?.ctx,
|
|
12043
|
+
};
|
|
11357
12044
|
}
|
|
11358
12045
|
}
|
|
11359
12046
|
|
|
11360
|
-
|
|
11361
|
-
return
|
|
11362
|
-
|
|
11363
|
-
|
|
11364
|
-
|
|
11365
|
-
|
|
11366
|
-
|
|
11367
|
-
|
|
11368
|
-
|
|
11369
|
-
const
|
|
11370
|
-
|
|
11371
|
-
|
|
11372
|
-
|
|
11373
|
-
|
|
11374
|
-
|
|
12047
|
+
function isLocal() {
|
|
12048
|
+
return getLocalState().pipelineMode === 'local';
|
|
12049
|
+
}
|
|
12050
|
+
const startPrepare = (params) => isLocal() ? startPrepare$1(params) : startPrepareRemote(params);
|
|
12051
|
+
const downloadPrepared = (params) => isLocal() ? downloadPrepared$1() : downloadPreparedRemote(params);
|
|
12052
|
+
// 开 collect session(鉴权门)。IDE 经 `/game/wasm-collect-start` 调用,
|
|
12053
|
+
// 所以它在浏览器 Network 里是一条独立可见、明确叫 start 的请求。
|
|
12054
|
+
// - 本地 → `/start`
|
|
12055
|
+
// - 远程 → 老的 `set_collecting`(远程没有独立 /start,set_collecting 即开窗)
|
|
12056
|
+
const openCollectSession = (params) => isLocal() ? openCollectSession$1(params) : setCollectRemote(params);
|
|
12057
|
+
// 上传符号表。本地走 `/symbols`(非阻塞、失败不致命);远程在 prepare 阶段
|
|
12058
|
+
// 已随表单上传过 symbol 文件,这里是 no-op,直接返回成功保持两条 pipeline
|
|
12059
|
+
// 在 IDE 层对称。
|
|
12060
|
+
const uploadCollectSymbols = (params) => isLocal()
|
|
12061
|
+
? uploadCollectSymbols$1(params)
|
|
12062
|
+
: Promise.resolve({
|
|
12063
|
+
data: { code: 0, message: 'success', result: {} },
|
|
12064
|
+
error: null,
|
|
12065
|
+
ctx: { logid: 'remote-noop', httpStatusCode: 200 },
|
|
11375
12066
|
});
|
|
11376
|
-
|
|
12067
|
+
const getCollectedFuncIds = (params) => isLocal() ? getCollectedFuncIds$1(params) : getCollectedFuncIdsRemote(params);
|
|
12068
|
+
const getCollecttingInfo = (params) => isLocal() ? getCollecttingInfo$1(params) : getCollecttingInfoRemote(params);
|
|
12069
|
+
const startSplit = (params) => isLocal() ? startSplit$1(params) : startSplitRemote(params);
|
|
12070
|
+
const downloadSplited = (context) => isLocal() ? downloadSplited$1(context) : downloadSplitedRemote(context);
|
|
12071
|
+
const getSplitResult = (params) => isLocal() ? getSplitResult$1() : getSplitResultRemote(params);
|
|
12072
|
+
const getTaskInfo = (params) => isLocal() ? getTaskInfo$1(params) : getTaskInfoRemote(params);
|
|
12073
|
+
const getTaskStatus = (params) => isLocal() ? getTaskStatus$1(params) : getTaskStatusRemote(params);
|
|
12074
|
+
const resetWasmSplit = (data) => isLocal() ? resetWasmSplit$1(data) : resetWasmSplitRemote(data);
|
|
12075
|
+
// Collect session (`/start` / `/finish`) is an implementation detail of the
|
|
12076
|
+
// local `wasm-collect/v1` pipeline — it's invoked inside `setCollectLocal`
|
|
12077
|
+
// and `startSplitLocal` respectively. The remote `stark_wasm/v4` pipeline
|
|
12078
|
+
// has no session concept. IDE never calls these directly, so there is no
|
|
12079
|
+
// dispatcher exposed here.
|
|
11377
12080
|
|
|
11378
12081
|
const gameWasmCancelRoute = {
|
|
11379
12082
|
method: 'post',
|
|
@@ -11381,9 +12084,7 @@ const gameWasmCancelRoute = {
|
|
|
11381
12084
|
handler: async (req, res) => {
|
|
11382
12085
|
const { codePath } = req.body;
|
|
11383
12086
|
console.log('wasm-cancel', req.body);
|
|
11384
|
-
await cancelSplit(
|
|
11385
|
-
wasmCodePath: codePath,
|
|
11386
|
-
});
|
|
12087
|
+
await cancelSplit();
|
|
11387
12088
|
res.send({
|
|
11388
12089
|
code: successCode,
|
|
11389
12090
|
msg: 'cancel success',
|
|
@@ -11477,26 +12178,35 @@ const gameWasmPrepareResultRoute = {
|
|
|
11477
12178
|
method: 'post',
|
|
11478
12179
|
path: '/game/wasm-prepare-result',
|
|
11479
12180
|
handler: async (req, res) => {
|
|
11480
|
-
|
|
11481
|
-
|
|
11482
|
-
const response = await getTaskStatus({
|
|
11483
|
-
client_key: clientKey,
|
|
11484
|
-
wasm_md5: codeMd5,
|
|
11485
|
-
});
|
|
11486
|
-
if (response.error) {
|
|
11487
|
-
res.send({
|
|
11488
|
-
code: errorCode,
|
|
11489
|
-
error: response.error,
|
|
11490
|
-
ctx: response.ctx,
|
|
11491
|
-
});
|
|
11492
|
-
}
|
|
11493
|
-
else {
|
|
12181
|
+
const { pipelineMode } = getLocalState();
|
|
12182
|
+
if (pipelineMode === 'local') {
|
|
11494
12183
|
res.send({
|
|
11495
12184
|
code: successCode,
|
|
11496
|
-
data:
|
|
11497
|
-
ctx:
|
|
12185
|
+
data: { status: WasmStatus.WasmSplitPreparedStatus },
|
|
12186
|
+
ctx: { logid: 'local' },
|
|
11498
12187
|
});
|
|
12188
|
+
return;
|
|
12189
|
+
}
|
|
12190
|
+
const { codeMd5, clientKey } = req.body;
|
|
12191
|
+
const result = await getTaskStatus({
|
|
12192
|
+
client_key: clientKey,
|
|
12193
|
+
wasm_md5: codeMd5,
|
|
12194
|
+
});
|
|
12195
|
+
// For the remote pipeline, forward the full `result` payload so the IDE
|
|
12196
|
+
// gets both `status` and the accompanying `package` info, matching the
|
|
12197
|
+
// legacy behaviour that the UI was written against.
|
|
12198
|
+
if (result?.error) {
|
|
12199
|
+
console.log('[wasm-prepare-result] remote error', result.error);
|
|
12200
|
+
res.send({ code: errorCode, error: result.error, ctx: result?.ctx });
|
|
12201
|
+
return;
|
|
11499
12202
|
}
|
|
12203
|
+
const data = result?.data?.result ?? { status: WasmStatus.IdleStatus };
|
|
12204
|
+
console.log(`[wasm-prepare-result] remote status=${data?.status}`);
|
|
12205
|
+
res.send({
|
|
12206
|
+
code: successCode,
|
|
12207
|
+
data,
|
|
12208
|
+
ctx: result?.ctx ?? { logid: 'remote' },
|
|
12209
|
+
});
|
|
11500
12210
|
},
|
|
11501
12211
|
};
|
|
11502
12212
|
|
|
@@ -11505,7 +12215,8 @@ const gameWasmPrepareRoute = {
|
|
|
11505
12215
|
path: '/game/wasm-prepare',
|
|
11506
12216
|
handler: async (req, res) => {
|
|
11507
12217
|
const { codePath, desc, codeMd5, clientKey } = req.body;
|
|
11508
|
-
|
|
12218
|
+
const { pipelineMode } = getLocalState();
|
|
12219
|
+
console.log(`wasm-prepare-start [mode=${pipelineMode}]`, req.body);
|
|
11509
12220
|
const result = await startPrepare({
|
|
11510
12221
|
client_key: clientKey,
|
|
11511
12222
|
desc,
|
|
@@ -11540,13 +12251,20 @@ const gameWasmPrepareRoute = {
|
|
|
11540
12251
|
},
|
|
11541
12252
|
};
|
|
11542
12253
|
|
|
12254
|
+
/**
|
|
12255
|
+
* 上传符号表(`/symbols`)。只在 session 已通过 `/game/wasm-collect-start`
|
|
12256
|
+
* 打开后调用,与"开 session"解耦:
|
|
12257
|
+
* - 鉴权门是 `/game/wasm-collect-start`(→ `/start`),不在这条。
|
|
12258
|
+
* - 这步是非关键的 debug 信息上传,本地非阻塞/失败不致命,远程为 no-op。
|
|
12259
|
+
* 因此即便失败也只回 errorCode 让调用方静默处理,不应阻断"开始收集"。
|
|
12260
|
+
*/
|
|
11543
12261
|
const gameWasmSetCollectRoute = {
|
|
11544
12262
|
method: 'post',
|
|
11545
12263
|
path: '/game/wasm-set-collect',
|
|
11546
12264
|
handler: async (req, res) => {
|
|
11547
12265
|
const { clientKey, codeMd5 } = req.body;
|
|
11548
|
-
console.log('wasm-set-collect', req.body);
|
|
11549
|
-
const response = await
|
|
12266
|
+
console.log('wasm-set-collect (upload symbols)', req.body);
|
|
12267
|
+
const response = await uploadCollectSymbols({
|
|
11550
12268
|
client_key: clientKey,
|
|
11551
12269
|
wasm_md5: codeMd5,
|
|
11552
12270
|
});
|
|
@@ -11567,16 +12285,27 @@ const gameWasmSetCollectRoute = {
|
|
|
11567
12285
|
},
|
|
11568
12286
|
};
|
|
11569
12287
|
|
|
11570
|
-
|
|
12288
|
+
/**
|
|
12289
|
+
* 打开 collect session —— 对应后端 `/start`(本地)/ `set_collecting`(远程)。
|
|
12290
|
+
*
|
|
12291
|
+
* 单独成一条 IDE 可见路由(而不是埋在 set-collect 里),是为了让浏览器
|
|
12292
|
+
* Network 里有一条**明确叫 start** 的请求:`/start` 走 Portal 鉴权中间件,
|
|
12293
|
+
* 鉴权失败(`-401`)时这条请求会带着透传上来的 `error.code:-401` 返回,
|
|
12294
|
+
* IDE 全局拦截器据此弹"登录态失效"toast,且排查时能一眼定位到是开 session
|
|
12295
|
+
* 这步报的鉴权,而非 collect 轮询接口。
|
|
12296
|
+
*
|
|
12297
|
+
* `resume` 可选:仅本地 pipeline 有意义,缺省 → `reset:true`(重新开始)。
|
|
12298
|
+
*/
|
|
12299
|
+
const gameWasmCollectStartRoute = {
|
|
11571
12300
|
method: 'post',
|
|
11572
|
-
path: '/game/wasm-
|
|
12301
|
+
path: '/game/wasm-collect-start',
|
|
11573
12302
|
handler: async (req, res) => {
|
|
11574
|
-
const { clientKey, codeMd5,
|
|
11575
|
-
console.log('
|
|
11576
|
-
const response = await
|
|
12303
|
+
const { clientKey, codeMd5, resume } = req.body;
|
|
12304
|
+
console.log('wasm-collect-start', req.body);
|
|
12305
|
+
const response = await openCollectSession({
|
|
11577
12306
|
client_key: clientKey,
|
|
11578
12307
|
wasm_md5: codeMd5,
|
|
11579
|
-
|
|
12308
|
+
resume,
|
|
11580
12309
|
});
|
|
11581
12310
|
if (response.error) {
|
|
11582
12311
|
res.send({
|
|
@@ -11586,35 +12315,55 @@ const gameWasmSplitDownloadResultRoute = {
|
|
|
11586
12315
|
});
|
|
11587
12316
|
}
|
|
11588
12317
|
else {
|
|
11589
|
-
|
|
11590
|
-
|
|
11591
|
-
|
|
11592
|
-
|
|
11593
|
-
// 'sub_wasm_download_url',
|
|
11594
|
-
// 'sub_js_download_url',
|
|
11595
|
-
// 'sub_js_data_download_url',
|
|
11596
|
-
// 'sub_js_range_download_url',
|
|
11597
|
-
];
|
|
11598
|
-
const missingFields = requiredDownloadFields.filter(field => {
|
|
11599
|
-
const value = splitResult[field];
|
|
11600
|
-
return typeof value !== 'string' || value.trim() === '';
|
|
12318
|
+
res.send({
|
|
12319
|
+
code: successCode,
|
|
12320
|
+
data: response.data || {},
|
|
12321
|
+
ctx: response.ctx,
|
|
11601
12322
|
});
|
|
11602
|
-
|
|
12323
|
+
}
|
|
12324
|
+
},
|
|
12325
|
+
};
|
|
12326
|
+
|
|
12327
|
+
const gameWasmSplitDownloadResultRoute = {
|
|
12328
|
+
method: 'post',
|
|
12329
|
+
path: '/game/wasm-split-download-result',
|
|
12330
|
+
handler: async (req, res) => {
|
|
12331
|
+
const { pipelineMode, splitMeta } = getLocalState();
|
|
12332
|
+
if (pipelineMode === 'local') {
|
|
12333
|
+
if (!splitMeta) {
|
|
11603
12334
|
res.send({
|
|
11604
12335
|
code: errorCode,
|
|
11605
|
-
error: {
|
|
11606
|
-
message: `Missing required wasm split fields: ${missingFields.join(', ')}`,
|
|
11607
|
-
},
|
|
11608
|
-
data: response.data || {},
|
|
11609
|
-
ctx: response.ctx,
|
|
12336
|
+
error: { message: 'No local split result found. Run split first.' },
|
|
11610
12337
|
});
|
|
11611
12338
|
return;
|
|
11612
12339
|
}
|
|
11613
12340
|
res.send({
|
|
11614
12341
|
code: successCode,
|
|
11615
|
-
data:
|
|
12342
|
+
data: { result: splitMeta },
|
|
11616
12343
|
msg: 'download success',
|
|
11617
|
-
ctx:
|
|
12344
|
+
ctx: { logid: 'local' },
|
|
12345
|
+
});
|
|
12346
|
+
return;
|
|
12347
|
+
}
|
|
12348
|
+
const { clientKey, codeMd5, codePath } = req.body;
|
|
12349
|
+
const result = await getSplitResult({
|
|
12350
|
+
client_key: clientKey,
|
|
12351
|
+
wasm_md5: codeMd5,
|
|
12352
|
+
wasm_path: codePath,
|
|
12353
|
+
});
|
|
12354
|
+
if (result.error) {
|
|
12355
|
+
res.send({
|
|
12356
|
+
code: errorCode,
|
|
12357
|
+
error: result.error,
|
|
12358
|
+
ctx: result.ctx,
|
|
12359
|
+
});
|
|
12360
|
+
}
|
|
12361
|
+
else {
|
|
12362
|
+
res.send({
|
|
12363
|
+
code: successCode,
|
|
12364
|
+
data: result.data || {},
|
|
12365
|
+
msg: 'download success',
|
|
12366
|
+
ctx: result.ctx,
|
|
11618
12367
|
});
|
|
11619
12368
|
}
|
|
11620
12369
|
},
|
|
@@ -11703,26 +12452,32 @@ const gameWasmSplitResultRoute = {
|
|
|
11703
12452
|
method: 'post',
|
|
11704
12453
|
path: '/game/wasm-split-result',
|
|
11705
12454
|
handler: async (req, res) => {
|
|
11706
|
-
const {
|
|
11707
|
-
|
|
11708
|
-
const response = await getTaskStatus({
|
|
11709
|
-
client_key: clientKey,
|
|
11710
|
-
wasm_md5: codeMd5,
|
|
11711
|
-
});
|
|
11712
|
-
if (response.error) {
|
|
11713
|
-
res.send({
|
|
11714
|
-
code: errorCode,
|
|
11715
|
-
error: response.error,
|
|
11716
|
-
ctx: response.ctx,
|
|
11717
|
-
});
|
|
11718
|
-
}
|
|
11719
|
-
else {
|
|
12455
|
+
const { pipelineMode } = getLocalState();
|
|
12456
|
+
if (pipelineMode === 'local') {
|
|
11720
12457
|
res.send({
|
|
11721
12458
|
code: successCode,
|
|
11722
|
-
data:
|
|
11723
|
-
ctx:
|
|
12459
|
+
data: { status: WasmStatus.WasmSplitDoneStatus },
|
|
12460
|
+
ctx: { logid: 'local' },
|
|
11724
12461
|
});
|
|
12462
|
+
return;
|
|
11725
12463
|
}
|
|
12464
|
+
const { clientKey, codeMd5 } = req.body;
|
|
12465
|
+
const result = await getTaskStatus({
|
|
12466
|
+
client_key: clientKey,
|
|
12467
|
+
wasm_md5: codeMd5,
|
|
12468
|
+
});
|
|
12469
|
+
if (result?.error) {
|
|
12470
|
+
console.log('[wasm-split-result] remote error', result.error);
|
|
12471
|
+
res.send({ code: errorCode, error: result.error, ctx: result?.ctx });
|
|
12472
|
+
return;
|
|
12473
|
+
}
|
|
12474
|
+
const data = result?.data?.result ?? { status: WasmStatus.IdleStatus };
|
|
12475
|
+
console.log(`[wasm-split-result] remote status=${data?.status}`);
|
|
12476
|
+
res.send({
|
|
12477
|
+
code: successCode,
|
|
12478
|
+
data,
|
|
12479
|
+
ctx: result?.ctx ?? { logid: 'remote' },
|
|
12480
|
+
});
|
|
11726
12481
|
},
|
|
11727
12482
|
};
|
|
11728
12483
|
|
|
@@ -11733,10 +12488,52 @@ const gameWasmSplitConfigRoute = {
|
|
|
11733
12488
|
const config = getSplitConfig();
|
|
11734
12489
|
if (!config) {
|
|
11735
12490
|
res.send({ code: errorCode, data: 'Failed to parse split config' });
|
|
12491
|
+
return;
|
|
11736
12492
|
}
|
|
11737
|
-
|
|
11738
|
-
|
|
12493
|
+
// When the CLI is restarted mid-session, localState.pipelineMode resets to
|
|
12494
|
+
// 'local' even though the project on disk may have been prepared in remote
|
|
12495
|
+
// mode. Re-infer it from the persisted split config so subsequent
|
|
12496
|
+
// dispatches (taskinfo / collect / split download) pick the right backend.
|
|
12497
|
+
// Heuristic: the local pipeline always writes enableArchiveMode=true, the
|
|
12498
|
+
// legacy remote pipeline always writes enableArchiveMode=false.
|
|
12499
|
+
if (config.enableWasmCollect) {
|
|
12500
|
+
const inferredMode = config.enableArchiveMode === true ? 'local' : 'remote';
|
|
12501
|
+
const current = getLocalState().pipelineMode;
|
|
12502
|
+
if (current !== inferredMode) {
|
|
12503
|
+
setLocalState({ pipelineMode: inferredMode });
|
|
12504
|
+
console.log(`[pipeline] inferred mode=${inferredMode} from webgl-wasm-split.js (was ${current})`);
|
|
12505
|
+
}
|
|
12506
|
+
}
|
|
12507
|
+
// ── wasm drift guard ─────────────────────────────────────────────
|
|
12508
|
+
//
|
|
12509
|
+
// `webgl-wasm-split.js` is persisted state about "which stage was
|
|
12510
|
+
// last completed", but it can desync from reality: the user's Unity
|
|
12511
|
+
// build re-emits `wasmcode/<file>.br` with a fresh, un-instrumented
|
|
12512
|
+
// binary while the config still claims `enableWasmCollect=true`. The
|
|
12513
|
+
// IDE's `canCollect()` then returns true, the prepare step gets
|
|
12514
|
+
// skipped, and the device loads a wasm that has no `scwebgl.logCall`
|
|
12515
|
+
// import — the `[wasmcollect] FATAL: no scwebgl.logCall import`
|
|
12516
|
+
// failure.
|
|
12517
|
+
//
|
|
12518
|
+
// To guard: compare the wasm file currently on disk to the md5 that
|
|
12519
|
+
// startPrepare wrote into `.ttmg-temp/prepared-meta.json`. If they
|
|
12520
|
+
// differ, demote `enableWasmCollect` back to its placeholder string
|
|
12521
|
+
// in the response so `canCollect()` → false and the IDE walks the
|
|
12522
|
+
// user through prepare again. We never touch the real config file
|
|
12523
|
+
// on disk — this is a transient correction at the read boundary, so
|
|
12524
|
+
// the next successful prepare seamlessly re-aligns everything.
|
|
12525
|
+
if (config.enableWasmCollect === true) {
|
|
12526
|
+
const wasmMeta = computeCurrentProjectWasmMd5();
|
|
12527
|
+
if (wasmMeta && wasmMeta.currentMd5 !== wasmMeta.meta.preparedWasmMd5) {
|
|
12528
|
+
console.warn(`[wasmtool] wasm drift detected: project wasm md5=${wasmMeta.currentMd5} but prepared meta expected ${wasmMeta.meta.preparedWasmMd5} (path=${wasmMeta.meta.codePath}). Forcing IDE back to prepare.`);
|
|
12529
|
+
// Mirror the string-placeholder shape the template uses before
|
|
12530
|
+
// prepare writes a real boolean — matches what `canCollect`
|
|
12531
|
+
// expects and is indistinguishable from "never prepared" from
|
|
12532
|
+
// the IDE's perspective.
|
|
12533
|
+
config.enableWasmCollect = '$ENABLEWASMCOLLECT';
|
|
12534
|
+
}
|
|
11739
12535
|
}
|
|
12536
|
+
res.send({ code: successCode, data: config });
|
|
11740
12537
|
},
|
|
11741
12538
|
};
|
|
11742
12539
|
|
|
@@ -11804,6 +12601,59 @@ function getGameFallbackRoute(publicPath) {
|
|
|
11804
12601
|
};
|
|
11805
12602
|
}
|
|
11806
12603
|
|
|
12604
|
+
/**
|
|
12605
|
+
* Explicit "user clicked a lang toggle in the IDE" endpoint. Unlike
|
|
12606
|
+
* `/game/config-fillback` — which intentionally no-ops when the CLI
|
|
12607
|
+
* already has a lang configured — this route always writes the incoming
|
|
12608
|
+
* lang to TTMGRC so the next IDE bootstrap's `setCurrentLang(cliLang)`
|
|
12609
|
+
* call won't stomp the user's fresh choice.
|
|
12610
|
+
*/
|
|
12611
|
+
const gameLanguageRoute = {
|
|
12612
|
+
method: 'post',
|
|
12613
|
+
path: '/game/language',
|
|
12614
|
+
handler: async (req, res) => {
|
|
12615
|
+
const incomingLang = req.body?.lang;
|
|
12616
|
+
const nextLang = resolveSupportedLanguage(incomingLang);
|
|
12617
|
+
if (!nextLang) {
|
|
12618
|
+
res.send({
|
|
12619
|
+
code: successCode,
|
|
12620
|
+
data: {
|
|
12621
|
+
lang: null,
|
|
12622
|
+
updated: false,
|
|
12623
|
+
},
|
|
12624
|
+
});
|
|
12625
|
+
return;
|
|
12626
|
+
}
|
|
12627
|
+
setTTMGRC({ lang: nextLang });
|
|
12628
|
+
res.send({
|
|
12629
|
+
code: successCode,
|
|
12630
|
+
data: {
|
|
12631
|
+
lang: nextLang,
|
|
12632
|
+
updated: true,
|
|
12633
|
+
},
|
|
12634
|
+
});
|
|
12635
|
+
},
|
|
12636
|
+
};
|
|
12637
|
+
|
|
12638
|
+
const gamePipelineModeRoute = {
|
|
12639
|
+
method: 'post',
|
|
12640
|
+
path: '/game/pipeline-mode',
|
|
12641
|
+
handler: (req, res) => {
|
|
12642
|
+
const { mode } = req.body;
|
|
12643
|
+
setLocalState({ pipelineMode: mode });
|
|
12644
|
+
console.log(`[pipeline] mode set to: ${mode}`);
|
|
12645
|
+
res.send({ code: successCode, data: { mode } });
|
|
12646
|
+
},
|
|
12647
|
+
};
|
|
12648
|
+
const gamePipelineModeGetRoute = {
|
|
12649
|
+
method: 'get',
|
|
12650
|
+
path: '/game/pipeline-mode',
|
|
12651
|
+
handler: (_req, res) => {
|
|
12652
|
+
const { pipelineMode } = getLocalState();
|
|
12653
|
+
res.send({ code: successCode, data: { mode: pipelineMode } });
|
|
12654
|
+
},
|
|
12655
|
+
};
|
|
12656
|
+
|
|
11807
12657
|
const routes = [
|
|
11808
12658
|
gameAssetPreviewUrlRoute,
|
|
11809
12659
|
gameAssetsRoute,
|
|
@@ -11824,6 +12674,7 @@ const routes = [
|
|
|
11824
12674
|
gameWasmPrepareDownloadRoute,
|
|
11825
12675
|
gameWasmSplitRoute,
|
|
11826
12676
|
gameWasmTaskinfoRoute,
|
|
12677
|
+
gameWasmCollectStartRoute,
|
|
11827
12678
|
gameWasmSetCollectRoute,
|
|
11828
12679
|
gameWasmCollectFuncidsRoute,
|
|
11829
12680
|
gameWasmCollectInfoRoute,
|
|
@@ -11832,6 +12683,9 @@ const routes = [
|
|
|
11832
12683
|
gameWasmSplitDownloadRoute,
|
|
11833
12684
|
gameWasmCancelRoute,
|
|
11834
12685
|
gameWasmSplitResetRoute,
|
|
12686
|
+
gamePipelineModeRoute,
|
|
12687
|
+
gamePipelineModeGetRoute,
|
|
12688
|
+
gameLanguageRoute,
|
|
11835
12689
|
];
|
|
11836
12690
|
function registerRoutes(app, options) {
|
|
11837
12691
|
const allRoutes = [...routes, getGameFallbackRoute(options.publicPath)];
|
|
@@ -11855,6 +12709,14 @@ async function start() {
|
|
|
11855
12709
|
const { url, version } = await listen(app, { maxRetries: 20 });
|
|
11856
12710
|
console.log(chalk.green.bold(`TTMG`), chalk.green(`v${version}`), chalk.gray(t('native.server.readyIn')), chalk.bold(`${Date.now() - startTime}ms`));
|
|
11857
12711
|
showTips({ server: url });
|
|
12712
|
+
// 联调场景下(root `dev:debug` 脚本)我们用 vite dev server 提供 IDE,
|
|
12713
|
+
// 不需要 CLI 自己再开浏览器到 dist/public 里那份打包后的旧 IDE。设置
|
|
12714
|
+
// `TTMG_DEV_NO_OPEN=1` 抑制 openUrl,由调度脚本统一负责开 5173。
|
|
12715
|
+
// 普通用户场景(npm 安装 ttmg)不会设置这个 env var,行为不变。
|
|
12716
|
+
if (process.env.TTMG_DEV_NO_OPEN === '1') {
|
|
12717
|
+
console.log(chalk.gray(`[dev-debug] TTMG_DEV_NO_OPEN=1 detected, skipping browser auto-open. Use vite dev server (likely http://localhost:5173) instead.`));
|
|
12718
|
+
return;
|
|
12719
|
+
}
|
|
11858
12720
|
openUrl(url);
|
|
11859
12721
|
}
|
|
11860
12722
|
|
|
@@ -12277,7 +13139,7 @@ async function upload({ clientKey, note = '--', dir, }) {
|
|
|
12277
13139
|
}
|
|
12278
13140
|
}
|
|
12279
13141
|
|
|
12280
|
-
var version = "0.3.
|
|
13142
|
+
var version = "0.3.9-beta.2";
|
|
12281
13143
|
var pkg = {
|
|
12282
13144
|
version: version};
|
|
12283
13145
|
|