@ttmg/cli 0.3.8 → 0.3.9-beta.wasm.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1921 -1201
- package/dist/index.js.map +1 -1
- package/dist/package.json +7 -2
- package/dist/public/assets/Detail-CqXvsvPM.js +1 -0
- package/dist/public/assets/Detail-CqXvsvPM.js.br +0 -0
- package/dist/public/assets/MonetizationMode-2bp9UZWM.js +1 -0
- package/dist/public/assets/MonetizationModeSummary-DBtIQq9Z.js +1 -0
- package/dist/public/assets/{baseForm-DqyLTNAx.js → baseForm-BFkpAlns.js} +4 -4
- package/dist/public/assets/baseForm-BFkpAlns.js.br +0 -0
- package/dist/public/assets/index-B6ZeUJlM.js +1 -0
- package/dist/public/assets/index-BFePHKNX.js +1 -0
- package/dist/public/assets/index-BFePHKNX.js.br +0 -0
- package/dist/public/assets/index-BJQfbNPd.js +1 -0
- package/dist/public/assets/index-BPzYPKPA.js +1 -0
- package/dist/public/assets/index-BRFS2ZhY.css +1 -0
- package/dist/public/assets/index-BRFS2ZhY.css.br +0 -0
- package/dist/public/assets/index-BUDAPXlB.js +1 -0
- package/dist/public/assets/index-BYgShzrj.js +1 -0
- package/dist/public/assets/index-Ba1XAQLw.js +1 -0
- package/dist/public/assets/index-BpefkOoB.js +1 -0
- package/dist/public/assets/index-BvxhyFWU.js +14 -0
- package/dist/public/assets/index-BvxhyFWU.js.br +0 -0
- package/dist/public/assets/index-BwbPFgZF.css +1 -0
- package/dist/public/assets/index-CL2qDQto.js +1 -0
- package/dist/public/assets/index-CMUKwrEm.js +1 -0
- package/dist/public/assets/index-CRAXXzpR.js +1 -0
- package/dist/public/assets/index-CRAXXzpR.js.br +0 -0
- package/dist/public/assets/{index-D2LsTDVa.css → index-CgAbOvxk.css} +1 -1
- package/dist/public/assets/index-CgAbOvxk.css.br +0 -0
- package/dist/public/assets/index-DY13Lo-E.js +1 -0
- package/dist/public/assets/index-DY13Lo-E.js.br +0 -0
- package/dist/public/assets/index-DqFmR7Qk.css +1 -0
- package/dist/public/assets/index-FFfimhRp.js +1 -0
- package/dist/public/assets/index-FFfimhRp.js.br +0 -0
- package/dist/public/assets/index-OLBa9viz.js +1 -0
- package/dist/public/assets/index-OsVhWD4K.js +1 -0
- package/dist/public/assets/index-f0PBkQd9.js +1 -0
- package/dist/public/assets/index-lKrpiZfQ.js +1 -0
- package/dist/public/assets/index-lKrpiZfQ.js.br +0 -0
- package/dist/public/assets/times-C1GmugF6.js +1 -0
- package/dist/public/index.html +10 -3
- package/dist/scripts/dev-debug.js +191 -0
- package/package.json +7 -2
- 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-DqyLTNAx.js.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-Bpba_DWs.js +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-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/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;
|
|
@@ -6960,7 +6591,20 @@ function buildCookieHeaderFromSetCookies(setCookies) {
|
|
|
6960
6591
|
function isVerboseEnabled() {
|
|
6961
6592
|
return process.argv.includes('--verbose');
|
|
6962
6593
|
}
|
|
6963
|
-
|
|
6594
|
+
/**
|
|
6595
|
+
* PPE / 测试环境开关。
|
|
6596
|
+
*
|
|
6597
|
+
* 所有 CLI 接口(`stark_wasm/v4/*`, `wasm-collect/v1/*`, portal 鉴权等)
|
|
6598
|
+
* 都共用本文件的 `request()`,所以在这里统一注入 PPE header 就能覆盖全量。
|
|
6599
|
+
* 不在 `DEV_HEADERS` 里改是因为那份常量只被 `remotePipeline.ts` 里的老
|
|
6600
|
+
* 远程分包接口 spread 用;新加的 `startSession.ts` / `finishSession.ts` /
|
|
6601
|
+
* `getCollectedFuncIds.ts` 都没 spread,漏一处就会绕开 PPE。
|
|
6602
|
+
*
|
|
6603
|
+
* 要切回线上环境,直接把 `CLI_PPE_ENV` 设成空串即可(下方条件 spread 会
|
|
6604
|
+
* 自动不带这两个 header)。
|
|
6605
|
+
*/
|
|
6606
|
+
// const CLI_PPE_ENV: string = 'ppe_wasm_test'; // 走 PPE 测试环境
|
|
6607
|
+
const CLI_PPE_ENV = ''; // 走线上服务(空串 => 下方条件 spread 不注入 PPE header)
|
|
6964
6608
|
function getAxiosProxyConfig() {
|
|
6965
6609
|
const config = getTTMGRC();
|
|
6966
6610
|
// 优先级: http-proxy > socks-proxy > proxy (老字段兼容)
|
|
@@ -7124,8 +6768,11 @@ async function request({ url, method, data, headers, params, }) {
|
|
|
7124
6768
|
params,
|
|
7125
6769
|
headers: {
|
|
7126
6770
|
Cookie: cookie,
|
|
7127
|
-
//
|
|
7128
|
-
//
|
|
6771
|
+
// 注入 PPE header — 放在 caller headers 之前,允许单个调用点通过
|
|
6772
|
+
// 显式传 `x-tt-env` 来覆盖本次请求(例如某个接口还没在 PPE 上发布)。
|
|
6773
|
+
...(CLI_PPE_ENV
|
|
6774
|
+
? { 'x-use-ppe': '1', 'x-tt-env': CLI_PPE_ENV }
|
|
6775
|
+
: {}),
|
|
7129
6776
|
...(headers || {}),
|
|
7130
6777
|
},
|
|
7131
6778
|
...proxyConfig,
|
|
@@ -7167,7 +6814,6 @@ async function request({ url, method, data, headers, params, }) {
|
|
|
7167
6814
|
}
|
|
7168
6815
|
}
|
|
7169
6816
|
async function download(url, filePath) {
|
|
7170
|
-
// 清理旧文件
|
|
7171
6817
|
if (fs.existsSync(filePath)) {
|
|
7172
6818
|
try {
|
|
7173
6819
|
fs.unlinkSync(filePath);
|
|
@@ -7175,16 +6821,31 @@ async function download(url, filePath) {
|
|
|
7175
6821
|
catch { }
|
|
7176
6822
|
}
|
|
7177
6823
|
const proxyConfig = getAxiosProxyConfig();
|
|
6824
|
+
console.log('[download] start', { url: url.slice(0, 120), filePath, hasProxy: !!proxyConfig.httpsAgent });
|
|
7178
6825
|
try {
|
|
7179
6826
|
const res = await axios.get(url, {
|
|
7180
6827
|
responseType: 'stream',
|
|
7181
|
-
// 让非 2xx 进入 catch
|
|
7182
6828
|
validateStatus: s => s >= 200 && s < 300,
|
|
6829
|
+
// Bail out if the server doesn't start responding within 30s instead of
|
|
6830
|
+
// hanging forever (e.g. proxy misrouting a CDN signed URL).
|
|
6831
|
+
timeout: 30000,
|
|
7183
6832
|
...proxyConfig,
|
|
7184
6833
|
});
|
|
7185
|
-
|
|
6834
|
+
const total = Number(res.headers['content-length'] || 0);
|
|
6835
|
+
let received = 0;
|
|
6836
|
+
let lastLoggedPct = -1;
|
|
6837
|
+
const startedAt = Date.now();
|
|
7186
6838
|
await new Promise((resolve, reject) => {
|
|
7187
6839
|
const writer = fs.createWriteStream(filePath);
|
|
6840
|
+
// Inactivity watchdog: if no bytes arrive for 60s mid-stream, abort.
|
|
6841
|
+
let inactivityTimer = null;
|
|
6842
|
+
const resetInactivity = () => {
|
|
6843
|
+
if (inactivityTimer)
|
|
6844
|
+
clearTimeout(inactivityTimer);
|
|
6845
|
+
inactivityTimer = setTimeout(() => {
|
|
6846
|
+
onError(new Error('download stalled: no data for 60s'));
|
|
6847
|
+
}, 60000);
|
|
6848
|
+
};
|
|
7188
6849
|
const onError = (e) => {
|
|
7189
6850
|
cleanup();
|
|
7190
6851
|
try {
|
|
@@ -7196,28 +6857,42 @@ async function download(url, filePath) {
|
|
|
7196
6857
|
};
|
|
7197
6858
|
const onClose = () => {
|
|
7198
6859
|
cleanup();
|
|
6860
|
+
console.log(`[download] done: ${received} bytes in ${Date.now() - startedAt}ms`);
|
|
7199
6861
|
resolve();
|
|
7200
6862
|
};
|
|
7201
6863
|
const cleanup = () => {
|
|
6864
|
+
if (inactivityTimer)
|
|
6865
|
+
clearTimeout(inactivityTimer);
|
|
7202
6866
|
writer.off('error', onError);
|
|
7203
6867
|
writer.off('close', onClose);
|
|
7204
6868
|
res.data.off('error', onError);
|
|
6869
|
+
res.data.off('data', onData);
|
|
6870
|
+
};
|
|
6871
|
+
const onData = (chunk) => {
|
|
6872
|
+
received += chunk.length;
|
|
6873
|
+
resetInactivity();
|
|
6874
|
+
if (total > 0) {
|
|
6875
|
+
const pct = Math.floor((received / total) * 10) * 10;
|
|
6876
|
+
if (pct !== lastLoggedPct) {
|
|
6877
|
+
lastLoggedPct = pct;
|
|
6878
|
+
console.log(`[download] ${pct}% (${received}/${total})`);
|
|
6879
|
+
}
|
|
6880
|
+
}
|
|
7205
6881
|
};
|
|
7206
6882
|
res.data.on('error', onError);
|
|
6883
|
+
res.data.on('data', onData);
|
|
7207
6884
|
writer.on('error', onError);
|
|
7208
6885
|
writer.on('close', onClose);
|
|
6886
|
+
resetInactivity();
|
|
7209
6887
|
res.data.pipe(writer);
|
|
7210
6888
|
});
|
|
7211
|
-
// 成功
|
|
7212
6889
|
return { ok: true };
|
|
7213
6890
|
}
|
|
7214
6891
|
catch (err) {
|
|
7215
|
-
|
|
6892
|
+
console.log('[download] failed:', err?.message);
|
|
7216
6893
|
if (isAxiosError(err) && err.response?.status === 403) {
|
|
7217
|
-
// 不抛出,让上层自行决定
|
|
7218
6894
|
throw new Error('下载链接已过期,请重新进行分包后重试');
|
|
7219
6895
|
}
|
|
7220
|
-
// 其他错误抛出或返回
|
|
7221
6896
|
throw err;
|
|
7222
6897
|
}
|
|
7223
6898
|
}
|
|
@@ -9212,11 +8887,19 @@ const zipCwdToBuffer = (customIgnores = [], targetDir = process.cwd()) => {
|
|
|
9212
8887
|
});
|
|
9213
8888
|
archive.pipe(output);
|
|
9214
8889
|
// 1. 基础忽略列表 (建议保留这些基础规则,防止包过大)
|
|
8890
|
+
//
|
|
8891
|
+
// 注意 `${TTMG_TEMP_DIR}/**` 必须显式写出:archiver 用的 picomatch 在
|
|
8892
|
+
// 没有 `/**` 后缀时只匹配根目录下的同名条目,不会递归匹配目录内容。
|
|
8893
|
+
// 历史 bug:只写 `__TTMG_TEMP__` 时,`__TTMG_TEMP__/wasmcode/<basename>.br`
|
|
8894
|
+
//(prepare 阶段缓存的「原始未插桩 wasm 备份」)等内部文件全部被打进 zip
|
|
8895
|
+
// 推到设备,体积虚胖外,host 在 fallback 路径下还可能错误命中未插桩 wasm。
|
|
8896
|
+
// 同时保留裸名 `TTMG_TEMP_DIR` 以兜底空目录场景。
|
|
9215
8897
|
const defaultIgnores = [
|
|
9216
8898
|
'node_modules/**',
|
|
9217
8899
|
'.git/**',
|
|
9218
8900
|
'.DS_Store',
|
|
9219
8901
|
ttmgPack.TTMG_TEMP_DIR,
|
|
8902
|
+
`${ttmgPack.TTMG_TEMP_DIR}/**`,
|
|
9220
8903
|
'*.zip', // 忽略自身生成的 zip
|
|
9221
8904
|
];
|
|
9222
8905
|
// 2. 合并自定义规则
|
|
@@ -10482,7 +10165,12 @@ const gameUploadRoute = {
|
|
|
10482
10165
|
},
|
|
10483
10166
|
};
|
|
10484
10167
|
|
|
10168
|
+
// 历史遗留:`remotePipeline.ts` 里的老远程分包接口 spread 了 `DEV_HEADERS`。
|
|
10169
|
+
// 目前全局 PPE 走的是 `libs/api/request.ts` 里的 `CLI_PPE_ENV`,这里保持
|
|
10170
|
+
// 相同的值是为了:一旦以后需要按接口粒度覆盖 PPE(例如远程走 PPE、本地
|
|
10171
|
+
// 走线上),只需在这里填回 header、两处值天然一致。
|
|
10485
10172
|
const BASE_URL = 'https://developers.tiktok.com';
|
|
10173
|
+
const WASM_COLLECT_BASE_URL = `${BASE_URL}/api/wasm-collect/v1`;
|
|
10486
10174
|
const DEV_HEADERS = {
|
|
10487
10175
|
// 'x-use-ppe': '1',
|
|
10488
10176
|
// 'x-tt-env': UNITY_PPE_ENV,
|
|
@@ -10505,6 +10193,8 @@ const UNITY_WASM_SPLIT_CONFIG_FIELD_SCHEME = {
|
|
|
10505
10193
|
WASMSPLITVERSION: `"$WASMSPLITVERSION"`,
|
|
10506
10194
|
ENABLEWASMSPLIT: `"$ENABLEWASMSPLIT"`,
|
|
10507
10195
|
IOS_SUB_JS_FILE_CONFIG: `"$IOS_SUB_JS_FILE_CONFIG"`,
|
|
10196
|
+
ENABLEARCHIVEMODE: `"$ENABLEARCHIVEMODE"`,
|
|
10197
|
+
ARCHIVE_CODE_FILE_MD5: `$ARCHIVE_CODE_FILE_MD5`,
|
|
10508
10198
|
};
|
|
10509
10199
|
|
|
10510
10200
|
const DIR_SPLIT = 'split';
|
|
@@ -10532,7 +10222,34 @@ const WASM_SPLIT_SUBPACKAGE_CONFIG = {
|
|
|
10532
10222
|
name: 'wasmcode1-ios',
|
|
10533
10223
|
root: 'wasmcode1-ios/',
|
|
10534
10224
|
},
|
|
10225
|
+
// archive 模式 split 产物:wasmcode/(主包,包含原文件名以外的 main wasm)+
|
|
10226
|
+
// 下面这两个独立子包目录。回滚 / 取消时必须把它们一起清掉,否则下次构建仍会
|
|
10227
|
+
// 把旧的 sub/archive 文件打进 zip 推到设备。
|
|
10228
|
+
archiveSub: {
|
|
10229
|
+
name: 'wasmcode1',
|
|
10230
|
+
root: 'wasmcode1/',
|
|
10231
|
+
},
|
|
10232
|
+
archiveCode: {
|
|
10233
|
+
name: 'wasmcode-archive',
|
|
10234
|
+
root: 'wasmcode-archive/',
|
|
10235
|
+
},
|
|
10535
10236
|
};
|
|
10237
|
+
/**
|
|
10238
|
+
* 所有由 split 流程生成、应在 cancel / reset 时被整目录删除的产物目录。
|
|
10239
|
+
* 注意不包含 `origin`(wasmcode/)—— 那是 Unity 原生输出目录,回滚时只能
|
|
10240
|
+
* 把里面的 split 产物 .br 清掉、再把原始 wasm 拷回去,整个删掉会破坏工程。
|
|
10241
|
+
*
|
|
10242
|
+
* 单一事实来源:`restoreFromCache` / `resetWasmSplit` / `cancelSplit` 都从
|
|
10243
|
+
* 这里读取,避免新增模式时漏改某一处导致旧产物泄漏到 zip。
|
|
10244
|
+
*/
|
|
10245
|
+
const SPLIT_OUTPUT_DIRS = [
|
|
10246
|
+
WASM_SPLIT_SUBPACKAGE_CONFIG.androidMain.root,
|
|
10247
|
+
WASM_SPLIT_SUBPACKAGE_CONFIG.androidSub.root,
|
|
10248
|
+
WASM_SPLIT_SUBPACKAGE_CONFIG.ios.root,
|
|
10249
|
+
WASM_SPLIT_SUBPACKAGE_CONFIG.iosSub.root,
|
|
10250
|
+
WASM_SPLIT_SUBPACKAGE_CONFIG.archiveSub.root,
|
|
10251
|
+
WASM_SPLIT_SUBPACKAGE_CONFIG.archiveCode.root,
|
|
10252
|
+
];
|
|
10536
10253
|
const WASM_FILENAME_SUFFIX = '.webgl.wasm.code.unityweb.wasm';
|
|
10537
10254
|
const BR_SUFFIX = '.br';
|
|
10538
10255
|
// 输出 JSON 格式
|
|
@@ -10543,97 +10260,42 @@ const CONCURRENCY_LIMIT = 2;
|
|
|
10543
10260
|
const DOWNLOAD_RETRY = 3;
|
|
10544
10261
|
const WASM_SPLIT_CONFIG_FILE_NAME = 'webgl-wasm-split.js';
|
|
10545
10262
|
|
|
10546
|
-
|
|
10547
|
-
|
|
10548
|
-
|
|
10549
|
-
|
|
10550
|
-
|
|
10551
|
-
|
|
10552
|
-
|
|
10553
|
-
|
|
10554
|
-
|
|
10555
|
-
|
|
10556
|
-
|
|
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
|
-
async function withRetry(fn, retries = 3) {
|
|
10610
|
-
let lastErr;
|
|
10611
|
-
for (let i = 0; i < retries; i++) {
|
|
10612
|
-
try {
|
|
10613
|
-
return await fn();
|
|
10614
|
-
}
|
|
10615
|
-
catch (e) {
|
|
10616
|
-
lastErr = e;
|
|
10617
|
-
const delay = 2 ** i * 300;
|
|
10618
|
-
await new Promise(r => setTimeout(r, delay));
|
|
10619
|
-
}
|
|
10263
|
+
async function withRetry(fn, retries = 3) {
|
|
10264
|
+
let lastErr;
|
|
10265
|
+
for (let i = 0; i < retries; i++) {
|
|
10266
|
+
try {
|
|
10267
|
+
return await fn();
|
|
10268
|
+
}
|
|
10269
|
+
catch (e) {
|
|
10270
|
+
lastErr = e;
|
|
10271
|
+
const delay = 2 ** i * 300;
|
|
10272
|
+
await new Promise(r => setTimeout(r, delay));
|
|
10273
|
+
}
|
|
10620
10274
|
}
|
|
10621
10275
|
throw lastErr;
|
|
10622
10276
|
}
|
|
10623
10277
|
|
|
10624
10278
|
function updateWasmSplitConfig(fields) {
|
|
10279
|
+
const configFilePath = path.join(process.cwd(), WASM_SPLIT_CONFIG_FILE_NAME);
|
|
10280
|
+
let config = fs.readFileSync(configFilePath, 'utf-8');
|
|
10625
10281
|
for (const field in fields) {
|
|
10626
10282
|
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
10283
|
const placeholder = UNITY_WASM_SPLIT_CONFIG_FIELD_SCHEME[field];
|
|
10631
|
-
|
|
10632
|
-
|
|
10633
|
-
|
|
10634
|
-
|
|
10635
|
-
|
|
10284
|
+
if (!placeholder)
|
|
10285
|
+
continue;
|
|
10286
|
+
let replacement;
|
|
10287
|
+
if (typeof value === 'boolean' || typeof value === 'number') {
|
|
10288
|
+
replacement = String(value);
|
|
10289
|
+
}
|
|
10290
|
+
else if (typeof value === 'string') {
|
|
10291
|
+
replacement = value;
|
|
10292
|
+
}
|
|
10293
|
+
else {
|
|
10294
|
+
replacement = String(value);
|
|
10295
|
+
}
|
|
10296
|
+
config = config.replace(placeholder, replacement);
|
|
10636
10297
|
}
|
|
10298
|
+
fs.writeFileSync(configFilePath, config, 'utf-8');
|
|
10637
10299
|
}
|
|
10638
10300
|
|
|
10639
10301
|
async function compressWasmFile(wasmFilePath, compressedFilePath) {
|
|
@@ -10645,7 +10307,11 @@ async function compressWasmFile(wasmFilePath, compressedFilePath) {
|
|
|
10645
10307
|
}
|
|
10646
10308
|
function compressArrayBuffer(arrayBuffer) {
|
|
10647
10309
|
return new Promise((resolve, reject) => {
|
|
10648
|
-
const compressStream = zlib.createBrotliCompress(
|
|
10310
|
+
const compressStream = zlib.createBrotliCompress({
|
|
10311
|
+
params: {
|
|
10312
|
+
[zlib.constants.BROTLI_PARAM_QUALITY]: 9,
|
|
10313
|
+
},
|
|
10314
|
+
});
|
|
10649
10315
|
compressStream.write(Buffer.from(arrayBuffer));
|
|
10650
10316
|
compressStream.end();
|
|
10651
10317
|
const compressedChunks = [];
|
|
@@ -10676,164 +10342,953 @@ function keepCacheSync({ entryDir, originalWasmPath, originalSplitConfigPath, })
|
|
|
10676
10342
|
if (!fs__namespace.existsSync(gameJsonCachePath)) {
|
|
10677
10343
|
fs__namespace.copyFileSync(gameJsonPath, gameJsonCachePath);
|
|
10678
10344
|
}
|
|
10345
|
+
/**
|
|
10346
|
+
* 保存 wasmcode/game.js(如果存在)
|
|
10347
|
+
*
|
|
10348
|
+
* 抖音 / 微信 mini-game 平台对 game.json.subpackages 里声明的每个子包
|
|
10349
|
+
* 都要求根目录有 `game.js`(哪怕是空文件)作为 `tt.loadSubpackage` 的
|
|
10350
|
+
* 入口锚点,否则子包加载直接失败。很多 Unity 项目把 wasmcode 当成
|
|
10351
|
+
* 一个内置子包(参考真实工程的 game.json),原始 wasmcode/ 里就有
|
|
10352
|
+
* 一个空 game.js。split 阶段虽然也会重写它,但回退时如果不把这个
|
|
10353
|
+
* 占位文件还原回来,mini-game 启动会因为找不到 wasmcode/game.js
|
|
10354
|
+
* 而崩。
|
|
10355
|
+
*
|
|
10356
|
+
* 这里只在源文件存在时才备份,避免给"原始就没有 game.js"的工程
|
|
10357
|
+
* 偷偷塞一个空文件污染回退状态。
|
|
10358
|
+
*/
|
|
10359
|
+
const originGameJsPath = path__namespace.join(entryDir, WASM_SPLIT_SUBPACKAGE_CONFIG.origin.root, 'game.js');
|
|
10360
|
+
const originGameJsCachePath = path__namespace.join(cacheDir, 'wasmcode-game.js');
|
|
10361
|
+
if (fs__namespace.existsSync(originGameJsPath) &&
|
|
10362
|
+
!fs__namespace.existsSync(originGameJsCachePath)) {
|
|
10363
|
+
fs__namespace.copyFileSync(originGameJsPath, originGameJsCachePath);
|
|
10364
|
+
}
|
|
10679
10365
|
return {
|
|
10680
10366
|
cacheDir,
|
|
10681
10367
|
};
|
|
10682
10368
|
}
|
|
10683
10369
|
|
|
10684
|
-
|
|
10685
|
-
|
|
10686
|
-
|
|
10687
|
-
|
|
10688
|
-
|
|
10689
|
-
|
|
10690
|
-
|
|
10691
|
-
|
|
10692
|
-
|
|
10693
|
-
|
|
10694
|
-
|
|
10695
|
-
|
|
10370
|
+
/**
|
|
10371
|
+
* Restore webgl-wasm-split.js from the cached original (with placeholders).
|
|
10372
|
+
* Called at the start of each prepare so that pipeline-specific values can be
|
|
10373
|
+
* applied deterministically, regardless of previous runs.
|
|
10374
|
+
* No-op if the cache does not yet exist (first run).
|
|
10375
|
+
*/
|
|
10376
|
+
function restoreSplitConfigFromCache(entryDir = process.cwd()) {
|
|
10377
|
+
const cachedConfigPath = path.join(entryDir, WASM_SPLIT_CACHE_DIR, path.basename(WASM_SPLIT_CONFIG_FILE_NAME));
|
|
10378
|
+
const targetConfigPath = path.join(entryDir, WASM_SPLIT_CONFIG_FILE_NAME);
|
|
10379
|
+
if (fs.existsSync(cachedConfigPath)) {
|
|
10380
|
+
fs.copyFileSync(cachedConfigPath, targetConfigPath);
|
|
10381
|
+
}
|
|
10382
|
+
}
|
|
10383
|
+
|
|
10384
|
+
/**
|
|
10385
|
+
* Restore the project from the backup cache:
|
|
10386
|
+
* - original (unmodified) wasm file back into its `wasmcode/<file>.br` location
|
|
10387
|
+
* - webgl-wasm-split.js back to its template (with placeholders)
|
|
10388
|
+
* - game.json back to its pre-split version
|
|
10389
|
+
* - wasmcode/game.js — restore from cache if backed up, otherwise write empty
|
|
10390
|
+
* (mini-game runtime requires every subpackage root to contain `game.js` as
|
|
10391
|
+
* a `tt.loadSubpackage` entry anchor; many Unity projects ship with
|
|
10392
|
+
* wasmcode declared as a subpackage in game.json, so a missing wasmcode/game.js
|
|
10393
|
+
* after rollback breaks subpackage loading at boot)
|
|
10394
|
+
* - remove ALL generated sub-package directories (see SPLIT_OUTPUT_DIRS) —
|
|
10395
|
+
* includes both legacy (wasmcode-android / wasmcode1-android / wasmcode-ios /
|
|
10396
|
+
* wasmcode1-ios) and archive-mode (wasmcode1 / wasmcode-archive) outputs
|
|
10397
|
+
* - clean stale split-produced .br files inside wasmcode/ (split phase writes
|
|
10398
|
+
* `${main_wasm_md5}.webgl...br` next to the original `${orig_md5}.webgl...br`;
|
|
10399
|
+
* we wipe everything in there and re-copy the cached original so the dir
|
|
10400
|
+
* ends up byte-identical to the pre-prepare state)
|
|
10401
|
+
*
|
|
10402
|
+
* Shared by both local and remote reset/rollback flows. Single source of
|
|
10403
|
+
* truth for "what does cancel actually undo" — adding a new split-output
|
|
10404
|
+
* dir means appending it to SPLIT_OUTPUT_DIRS, no other call site needs
|
|
10405
|
+
* to change.
|
|
10406
|
+
*/
|
|
10407
|
+
function restoreFromCache(entryDir = process.cwd()) {
|
|
10408
|
+
const cacheDir = path.join(entryDir, WASM_SPLIT_CACHE_DIR);
|
|
10409
|
+
// 1) Wipe stale split residue inside wasmcode/ first, THEN restore the
|
|
10410
|
+
// original. Order matters: if we restore first then wipe, we'd delete
|
|
10411
|
+
// the very file we just brought back.
|
|
10412
|
+
const originDir = path.join(entryDir, WASM_SPLIT_SUBPACKAGE_CONFIG.origin.root);
|
|
10413
|
+
if (fs.existsSync(originDir)) {
|
|
10414
|
+
for (const entry of fs.readdirSync(originDir)) {
|
|
10415
|
+
// Only clean files split is known to write — `.br` (main wasm) and
|
|
10416
|
+
// the empty `game.js` placeholder. Touching anything else risks
|
|
10417
|
+
// nuking developer-authored content that happens to live in
|
|
10418
|
+
// wasmcode/ for unrelated reasons.
|
|
10419
|
+
if (entry.endsWith('.br') || entry === 'game.js') {
|
|
10420
|
+
fs.rmSync(path.join(originDir, entry), { force: true });
|
|
10421
|
+
}
|
|
10422
|
+
}
|
|
10423
|
+
}
|
|
10424
|
+
if (fs.existsSync(cacheDir)) {
|
|
10425
|
+
const targetWasmBrPath = fs
|
|
10426
|
+
.readdirSync(cacheDir)
|
|
10427
|
+
.find(item => item.endsWith('.br'));
|
|
10428
|
+
if (targetWasmBrPath) {
|
|
10429
|
+
const destWasmBrPath = path.join(entryDir, WASM_SPLIT_SUBPACKAGE_CONFIG.origin.root, path.basename(targetWasmBrPath));
|
|
10430
|
+
ensureDirSync(path.dirname(destWasmBrPath));
|
|
10431
|
+
fs.copyFileSync(path.join(cacheDir, targetWasmBrPath), destWasmBrPath);
|
|
10432
|
+
}
|
|
10433
|
+
}
|
|
10434
|
+
const splitConfigCachePath = path.join(cacheDir, WASM_SPLIT_CONFIG_FILE_NAME);
|
|
10435
|
+
if (fs.existsSync(splitConfigCachePath)) {
|
|
10436
|
+
fs.copyFileSync(splitConfigCachePath, path.join(entryDir, WASM_SPLIT_CONFIG_FILE_NAME));
|
|
10437
|
+
}
|
|
10438
|
+
const gameJsonCachePath = path.join(cacheDir, 'game.json');
|
|
10439
|
+
if (fs.existsSync(gameJsonCachePath)) {
|
|
10440
|
+
fs.copyFileSync(gameJsonCachePath, path.join(entryDir, 'game.json'));
|
|
10441
|
+
}
|
|
10442
|
+
// Restore wasmcode/game.js. We just deleted whatever was there in step 1,
|
|
10443
|
+
// so we always need to put something back when wasmcode is a subpackage.
|
|
10444
|
+
// Strategy:
|
|
10445
|
+
// - Prefer the cache (keepCacheSync stashes pre-split contents to
|
|
10446
|
+
// `__unity_cache__/wasmcode-game.js` when the original existed)
|
|
10447
|
+
// - Fall back to writing an empty file. Rationale: if we got here the
|
|
10448
|
+
// dir exists, the .br is in place, and the project's game.json — now
|
|
10449
|
+
// restored above — likely still lists wasmcode as a subpackage (true
|
|
10450
|
+
// for every Unity template we ship). An empty game.js is exactly what
|
|
10451
|
+
// downloadSplited.ts also writes; it satisfies the platform requirement
|
|
10452
|
+
// without changing semantics for projects that don't use wasmcode as
|
|
10453
|
+
// a subpackage (the file is harmless empty).
|
|
10454
|
+
const originGameJsCachePath = path.join(cacheDir, 'wasmcode-game.js');
|
|
10455
|
+
const originGameJsDestPath = path.join(originDir, 'game.js');
|
|
10456
|
+
if (fs.existsSync(originDir)) {
|
|
10457
|
+
if (fs.existsSync(originGameJsCachePath)) {
|
|
10458
|
+
fs.copyFileSync(originGameJsCachePath, originGameJsDestPath);
|
|
10459
|
+
}
|
|
10460
|
+
else {
|
|
10461
|
+
fs.writeFileSync(originGameJsDestPath, '', 'utf-8');
|
|
10462
|
+
}
|
|
10463
|
+
}
|
|
10464
|
+
for (const subDir of SPLIT_OUTPUT_DIRS) {
|
|
10465
|
+
const full = path.join(entryDir, subDir);
|
|
10466
|
+
if (fs.existsSync(full)) {
|
|
10467
|
+
fs.rmSync(full, { recursive: true, force: true });
|
|
10468
|
+
}
|
|
10469
|
+
}
|
|
10470
|
+
}
|
|
10471
|
+
|
|
10472
|
+
async function decompressWasmFile(inputPath, outputPath) {
|
|
10473
|
+
const compressed = await fs.promises.readFile(inputPath);
|
|
10474
|
+
const decompressed = await new Promise((resolve, reject) => {
|
|
10475
|
+
zlib.brotliDecompress(compressed, (err, result) => {
|
|
10476
|
+
if (err)
|
|
10477
|
+
reject(err);
|
|
10478
|
+
else
|
|
10479
|
+
resolve(result);
|
|
10480
|
+
});
|
|
10696
10481
|
});
|
|
10482
|
+
await fs.promises.writeFile(outputPath, decompressed);
|
|
10483
|
+
}
|
|
10484
|
+
|
|
10485
|
+
function computeFileMd5Sync(filePath) {
|
|
10486
|
+
const content = fs.readFileSync(filePath);
|
|
10487
|
+
return crypto.createHash('md5').update(content).digest('hex');
|
|
10488
|
+
}
|
|
10489
|
+
|
|
10490
|
+
let cached = null;
|
|
10491
|
+
function getGameJson() {
|
|
10492
|
+
if (cached)
|
|
10493
|
+
return cached;
|
|
10494
|
+
const filePath = path$1.join(process.cwd(), 'game.json');
|
|
10495
|
+
if (fs$1.existsSync(filePath)) {
|
|
10496
|
+
try {
|
|
10497
|
+
cached = JSON.parse(fs$1.readFileSync(filePath, 'utf-8'));
|
|
10498
|
+
}
|
|
10499
|
+
catch {
|
|
10500
|
+
cached = {};
|
|
10501
|
+
}
|
|
10502
|
+
}
|
|
10503
|
+
else {
|
|
10504
|
+
cached = {};
|
|
10505
|
+
}
|
|
10506
|
+
return cached;
|
|
10507
|
+
}
|
|
10508
|
+
|
|
10509
|
+
function metaFilePath(entryDir = process.cwd()) {
|
|
10510
|
+
return path__namespace$1.join(entryDir, TTMG_TEMP_DIR, 'prepared-meta.json');
|
|
10511
|
+
}
|
|
10512
|
+
function writePreparedMeta(meta, entryDir = process.cwd()) {
|
|
10513
|
+
const target = metaFilePath(entryDir);
|
|
10514
|
+
fs__namespace$1.mkdirSync(path__namespace$1.dirname(target), { recursive: true });
|
|
10515
|
+
const payload = {
|
|
10516
|
+
...meta,
|
|
10517
|
+
preparedAt: new Date().toISOString(),
|
|
10518
|
+
};
|
|
10519
|
+
fs__namespace$1.writeFileSync(target, JSON.stringify(payload, null, 2), 'utf-8');
|
|
10520
|
+
}
|
|
10521
|
+
function readPreparedMeta(entryDir = process.cwd()) {
|
|
10522
|
+
const target = metaFilePath(entryDir);
|
|
10523
|
+
if (!fs__namespace$1.existsSync(target))
|
|
10524
|
+
return null;
|
|
10697
10525
|
try {
|
|
10698
|
-
const
|
|
10699
|
-
const
|
|
10700
|
-
if (
|
|
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
|
-
|
|
10737
|
-
|
|
10738
|
-
|
|
10739
|
-
|
|
10740
|
-
|
|
10741
|
-
|
|
10742
|
-
|
|
10743
|
-
|
|
10744
|
-
|
|
10745
|
-
|
|
10746
|
-
|
|
10747
|
-
|
|
10748
|
-
|
|
10749
|
-
|
|
10750
|
-
|
|
10751
|
-
|
|
10752
|
-
|
|
10753
|
-
|
|
10754
|
-
|
|
10755
|
-
|
|
10756
|
-
|
|
10757
|
-
|
|
10758
|
-
|
|
10759
|
-
|
|
10760
|
-
|
|
10761
|
-
|
|
10762
|
-
|
|
10763
|
-
|
|
10526
|
+
const raw = fs__namespace$1.readFileSync(target, 'utf-8');
|
|
10527
|
+
const parsed = JSON.parse(raw);
|
|
10528
|
+
if (typeof parsed?.preparedWasmMd5 === 'string' &&
|
|
10529
|
+
typeof parsed?.codePath === 'string' &&
|
|
10530
|
+
parsed.preparedWasmMd5.length === 32) {
|
|
10531
|
+
return parsed;
|
|
10532
|
+
}
|
|
10533
|
+
return null;
|
|
10534
|
+
}
|
|
10535
|
+
catch {
|
|
10536
|
+
return null;
|
|
10537
|
+
}
|
|
10538
|
+
}
|
|
10539
|
+
/**
|
|
10540
|
+
* Return the current md5 of the wasm file referenced by `prepared-meta.json`
|
|
10541
|
+
* or null if the file is missing / meta isn't present. Caller compares the
|
|
10542
|
+
* result to `meta.preparedWasmMd5` — mismatch means the project's wasm
|
|
10543
|
+
* has drifted from the prepared output (Unity re-build etc.) and the
|
|
10544
|
+
* project should be walked back through the prepare step before collect
|
|
10545
|
+
* can produce useful data.
|
|
10546
|
+
*/
|
|
10547
|
+
function computeCurrentProjectWasmMd5(entryDir = process.cwd()) {
|
|
10548
|
+
const meta = readPreparedMeta(entryDir);
|
|
10549
|
+
if (!meta)
|
|
10550
|
+
return null;
|
|
10551
|
+
const absolutePath = path__namespace$1.join(entryDir, meta.codePath);
|
|
10552
|
+
if (!fs__namespace$1.existsSync(absolutePath))
|
|
10553
|
+
return null;
|
|
10554
|
+
const currentMd5 = crypto$1
|
|
10555
|
+
.createHash('md5')
|
|
10556
|
+
.update(fs__namespace$1.readFileSync(absolutePath))
|
|
10557
|
+
.digest('hex');
|
|
10558
|
+
return { meta, currentMd5 };
|
|
10559
|
+
}
|
|
10560
|
+
|
|
10561
|
+
const state = {
|
|
10562
|
+
pipelineMode: 'local',
|
|
10563
|
+
originalWasmPath: '',
|
|
10564
|
+
preparedWasmPath: '',
|
|
10565
|
+
codePath: '',
|
|
10566
|
+
splitOutputDir: '',
|
|
10567
|
+
splitMeta: null,
|
|
10568
|
+
totalWasmFuncCount: 0,
|
|
10569
|
+
wasmSize: 0,
|
|
10570
|
+
isArchiveMode: true,
|
|
10571
|
+
};
|
|
10572
|
+
function getLocalState() {
|
|
10573
|
+
return state;
|
|
10574
|
+
}
|
|
10575
|
+
function setLocalState(partial) {
|
|
10576
|
+
Object.assign(state, partial);
|
|
10577
|
+
}
|
|
10578
|
+
|
|
10579
|
+
async function startPrepare$1(params) {
|
|
10580
|
+
const tempDir = path$1.join(process.cwd(), TTMG_TEMP_DIR);
|
|
10581
|
+
ensureDirSync(tempDir);
|
|
10582
|
+
const inputPath = path$1.join(process.cwd(), params.wasm_file_path);
|
|
10583
|
+
let rawWasmPath = path$1.join(tempDir, 'original.wasm');
|
|
10584
|
+
if (inputPath.endsWith('.br')) {
|
|
10585
|
+
await decompressWasmFile(inputPath, rawWasmPath);
|
|
10586
|
+
}
|
|
10587
|
+
else {
|
|
10588
|
+
fs$1.copyFileSync(inputPath, rawWasmPath);
|
|
10589
|
+
}
|
|
10590
|
+
const preparedWasmPath = path$1.join(tempDir, 'prepared.wasm');
|
|
10591
|
+
try {
|
|
10592
|
+
const result = ttmgWasmtool.prepare(rawWasmPath, preparedWasmPath);
|
|
10593
|
+
console.log(`[wasmtool] prepare done: ${result.outputSize} bytes, ${result.timeCost}s`);
|
|
10594
|
+
const gameJson = getGameJson();
|
|
10595
|
+
const totalWasmFuncCount = gameJson.wasmFuncCount ?? 0;
|
|
10596
|
+
const wasmSize = fs$1.existsSync(inputPath)
|
|
10597
|
+
? fs$1.statSync(inputPath).size
|
|
10598
|
+
: 0;
|
|
10599
|
+
setLocalState({
|
|
10600
|
+
originalWasmPath: rawWasmPath,
|
|
10601
|
+
preparedWasmPath,
|
|
10602
|
+
codePath: params.wasm_file_path,
|
|
10603
|
+
totalWasmFuncCount,
|
|
10604
|
+
wasmSize,
|
|
10605
|
+
});
|
|
10606
|
+
keepCacheSync({
|
|
10607
|
+
entryDir: process.cwd(),
|
|
10608
|
+
originalWasmPath: params.wasm_file_path,
|
|
10609
|
+
originalSplitConfigPath: WASM_SPLIT_CONFIG_FILE_NAME,
|
|
10610
|
+
});
|
|
10611
|
+
// Start from cached (placeholder) config so pipeline switching is deterministic
|
|
10612
|
+
restoreSplitConfigFromCache();
|
|
10613
|
+
const willReplaceWasmPath = path$1.join(process.cwd(), params.wasm_file_path);
|
|
10614
|
+
// Diagnostic: prove prepare actually produced a different binary
|
|
10615
|
+
// (size should grow noticeably because every function body is prefixed
|
|
10616
|
+
// with a scwebgl.logCall(funcIndex) call).
|
|
10617
|
+
const rawSize = fs$1.existsSync(rawWasmPath) ? fs$1.statSync(rawWasmPath).size : 0;
|
|
10618
|
+
const preparedSize = fs$1.existsSync(preparedWasmPath)
|
|
10619
|
+
? fs$1.statSync(preparedWasmPath).size
|
|
10620
|
+
: 0;
|
|
10621
|
+
const rawMd5 = fs$1.existsSync(rawWasmPath)
|
|
10622
|
+
? crypto$1.createHash('md5').update(fs$1.readFileSync(rawWasmPath)).digest('hex')
|
|
10623
|
+
: '<missing>';
|
|
10624
|
+
const preparedMd5 = fs$1.existsSync(preparedWasmPath)
|
|
10625
|
+
? crypto$1.createHash('md5').update(fs$1.readFileSync(preparedWasmPath)).digest('hex')
|
|
10626
|
+
: '<missing>';
|
|
10627
|
+
console.log(`[wasmtool] prepare sanity: raw(size=${rawSize} md5=${rawMd5}) -> prepared(size=${preparedSize} md5=${preparedMd5}) delta=${preparedSize - rawSize}`);
|
|
10628
|
+
if (preparedSize <= rawSize || preparedMd5 === rawMd5) {
|
|
10629
|
+
console.warn('[wasmtool] WARNING: prepared wasm is not larger / md5 is unchanged vs raw wasm. Instrumentation likely did not happen.');
|
|
10630
|
+
}
|
|
10631
|
+
console.log('[wasmtool] compressing prepared wasm (quality=9)...');
|
|
10632
|
+
await compressWasmFile(preparedWasmPath, willReplaceWasmPath);
|
|
10633
|
+
console.log('[wasmtool] compressed and written to project');
|
|
10634
|
+
// Diagnostic: confirm the file the client actually fetches was overwritten,
|
|
10635
|
+
// and compare to the cached original brotli so we can prove on-disk replacement.
|
|
10636
|
+
const replacedSize = fs$1.existsSync(willReplaceWasmPath)
|
|
10637
|
+
? fs$1.statSync(willReplaceWasmPath).size
|
|
10638
|
+
: 0;
|
|
10639
|
+
const replacedMd5 = fs$1.existsSync(willReplaceWasmPath)
|
|
10640
|
+
? crypto$1.createHash('md5').update(fs$1.readFileSync(willReplaceWasmPath)).digest('hex')
|
|
10641
|
+
: '<missing>';
|
|
10642
|
+
const cachedOriginalBr = path$1.join(process.cwd(), TTMG_TEMP_DIR, 'wasmcode', path$1.basename(params.wasm_file_path));
|
|
10643
|
+
const cachedOriginalSize = fs$1.existsSync(cachedOriginalBr)
|
|
10644
|
+
? fs$1.statSync(cachedOriginalBr).size
|
|
10645
|
+
: 0;
|
|
10646
|
+
const cachedOriginalMd5 = fs$1.existsSync(cachedOriginalBr)
|
|
10647
|
+
? crypto$1
|
|
10648
|
+
.createHash('md5')
|
|
10649
|
+
.update(fs$1.readFileSync(cachedOriginalBr))
|
|
10650
|
+
.digest('hex')
|
|
10651
|
+
: '<missing>';
|
|
10652
|
+
console.log(`[wasmtool] on-disk replace check: project=${params.wasm_file_path} size=${replacedSize} md5=${replacedMd5} | cached-original size=${cachedOriginalSize} md5=${cachedOriginalMd5}`);
|
|
10653
|
+
if (replacedMd5 === cachedOriginalMd5) {
|
|
10654
|
+
console.warn('[wasmtool] WARNING: project wasm md5 matches cached-original md5. The file was not actually replaced with the instrumented build.');
|
|
10764
10655
|
}
|
|
10765
10656
|
else {
|
|
10766
|
-
|
|
10767
|
-
isSuccess: false,
|
|
10768
|
-
error: {
|
|
10769
|
-
code: res.data?.code,
|
|
10770
|
-
message: res.data?.message,
|
|
10771
|
-
},
|
|
10772
|
-
ctx: res?.ctx,
|
|
10773
|
-
};
|
|
10657
|
+
console.log('[wasmtool] OK: project wasm differs from cached-original — instrumented wasm is on disk.');
|
|
10774
10658
|
}
|
|
10659
|
+
// Local pipeline uses the new wasm-collect/v1/report API + archive sub-wasm.
|
|
10660
|
+
// ORIGINALWASMMD5 must be set now (not only at split time) so the plugin
|
|
10661
|
+
// reports the correct wasm_md5 during the collect phase.
|
|
10662
|
+
updateWasmSplitConfig({
|
|
10663
|
+
ENABLEWASMCOLLECT: true,
|
|
10664
|
+
ENABLEARCHIVEMODE: true,
|
|
10665
|
+
ORIGINALWASMMD5: params.wasm_md5,
|
|
10666
|
+
});
|
|
10667
|
+
console.log('[wasmtool] wasm split config updated (local pipeline: archive=true)');
|
|
10668
|
+
// Disk-persisted anchor for "wasm drift" detection in
|
|
10669
|
+
// `game-wasm-split-config` route. Stores the md5 that prepare just
|
|
10670
|
+
// wrote into the project alongside the project-relative path. The
|
|
10671
|
+
// route reads this back on every Modal open, recomputes the md5 of
|
|
10672
|
+
// the file on disk, and if they differ (Unity re-build, git
|
|
10673
|
+
// checkout, etc.) suppresses `enableWasmCollect=true` in the
|
|
10674
|
+
// response so the IDE goes back through prepare instead of dropping
|
|
10675
|
+
// the user straight into Collect with an un-instrumented wasm on
|
|
10676
|
+
// the device. See `preparedMeta.ts` for full rationale.
|
|
10677
|
+
writePreparedMeta({
|
|
10678
|
+
preparedWasmMd5: replacedMd5,
|
|
10679
|
+
codePath: params.wasm_file_path,
|
|
10680
|
+
});
|
|
10681
|
+
console.log(`[wasmtool] prepared-meta written: md5=${replacedMd5} codePath=${params.wasm_file_path}`);
|
|
10682
|
+
return {
|
|
10683
|
+
data: {
|
|
10684
|
+
code: 0,
|
|
10685
|
+
message: 'success',
|
|
10686
|
+
result: { md5: params.wasm_md5 },
|
|
10687
|
+
},
|
|
10688
|
+
error: null,
|
|
10689
|
+
ctx: { logid: 'local', httpStatusCode: 200 },
|
|
10690
|
+
};
|
|
10775
10691
|
}
|
|
10776
|
-
catch (
|
|
10692
|
+
catch (err) {
|
|
10777
10693
|
return {
|
|
10778
|
-
|
|
10694
|
+
data: null,
|
|
10779
10695
|
error: {
|
|
10780
|
-
code:
|
|
10781
|
-
message:
|
|
10696
|
+
code: 500,
|
|
10697
|
+
message: err instanceof Error ? err.message : String(err),
|
|
10782
10698
|
},
|
|
10783
|
-
ctx:
|
|
10699
|
+
ctx: { logid: 'local', httpStatusCode: 500 },
|
|
10784
10700
|
};
|
|
10785
10701
|
}
|
|
10786
10702
|
}
|
|
10787
10703
|
|
|
10788
|
-
|
|
10789
|
-
|
|
10790
|
-
|
|
10704
|
+
/**
|
|
10705
|
+
* Local pipeline: startPrepareLocal already compressed/replaced the wasm and
|
|
10706
|
+
* updated webgl-wasm-split.js, so this step is a no-op that just emits UI
|
|
10707
|
+
* status events for parity with the remote flow.
|
|
10708
|
+
*/
|
|
10709
|
+
async function downloadPrepared$1(_data) {
|
|
10710
|
+
const { preparedWasmPath } = getLocalState();
|
|
10711
|
+
if (!preparedWasmPath) {
|
|
10712
|
+
return {
|
|
10713
|
+
isSuccess: false,
|
|
10714
|
+
error: { code: 404, message: 'Prepared wasm not found. Run prepare first.' },
|
|
10715
|
+
};
|
|
10716
|
+
}
|
|
10717
|
+
wsServer.sendUnitySplitStatus({ status: 'update_wasm_split_config_done' });
|
|
10718
|
+
return { isSuccess: true, ctx: { logid: 'local' } };
|
|
10719
|
+
}
|
|
10720
|
+
|
|
10721
|
+
async function getCollectedFuncIds$1({ client_key, wasm_md5, }) {
|
|
10722
|
+
const res = await request({
|
|
10723
|
+
url: `${WASM_COLLECT_BASE_URL}/progress`,
|
|
10791
10724
|
method: 'GET',
|
|
10792
|
-
headers: DEV_HEADERS,
|
|
10793
10725
|
params: {
|
|
10794
|
-
client_key,
|
|
10726
|
+
app_id: client_key,
|
|
10795
10727
|
wasm_md5,
|
|
10796
10728
|
},
|
|
10797
10729
|
});
|
|
10730
|
+
const funcCount = res?.data?.func_count ?? 0;
|
|
10731
|
+
return {
|
|
10732
|
+
data: {
|
|
10733
|
+
code: res?.data?.code ?? 0,
|
|
10734
|
+
message: 'success',
|
|
10735
|
+
result: {
|
|
10736
|
+
collected_func_count: funcCount,
|
|
10737
|
+
data_size: funcCount,
|
|
10738
|
+
real_data_size: funcCount,
|
|
10739
|
+
collect_state: res?.data?.collect_state,
|
|
10740
|
+
},
|
|
10741
|
+
},
|
|
10742
|
+
error: res.error,
|
|
10743
|
+
ctx: res.ctx,
|
|
10744
|
+
};
|
|
10798
10745
|
}
|
|
10799
10746
|
|
|
10800
|
-
|
|
10801
|
-
|
|
10802
|
-
|
|
10747
|
+
/**
|
|
10748
|
+
* POST /start — opens a collect session (Portal-authenticated).
|
|
10749
|
+
*
|
|
10750
|
+
* Idempotent on the server: re-opening an already-open session just refreshes
|
|
10751
|
+
* `started_at`; only `reset: true` wipes history.
|
|
10752
|
+
*
|
|
10753
|
+
* Default `reset` is `false` to mirror the server-side default documented in
|
|
10754
|
+
* `wasm_api.md` §5.1 — "页面刷新 / 恢复" must NOT silently destroy data. The
|
|
10755
|
+
* "fresh run" semantic (e.g. user clicks "重新开始分包") is the responsibility
|
|
10756
|
+
* of the caller, which must explicitly pass `reset: true`. See `setCollect`
|
|
10757
|
+
* for the CLI-level wiring of those two paths.
|
|
10758
|
+
*
|
|
10759
|
+
* NOTE on naming: the server route is flat (`/start`, not `/session/start`).
|
|
10760
|
+
* Our local symbol stays `startWasmSession` because it's the "start collect
|
|
10761
|
+
* session" lifecycle primitive from the IDE's perspective.
|
|
10762
|
+
*/
|
|
10763
|
+
async function startWasmSession({ client_key, wasm_md5, reset, }) {
|
|
10764
|
+
const res = await request({
|
|
10765
|
+
url: `${WASM_COLLECT_BASE_URL}/start`,
|
|
10803
10766
|
method: 'POST',
|
|
10804
10767
|
data: {
|
|
10805
|
-
client_key,
|
|
10768
|
+
app_id: client_key,
|
|
10806
10769
|
wasm_md5,
|
|
10770
|
+
reset: reset ?? false,
|
|
10807
10771
|
},
|
|
10808
|
-
headers: DEV_HEADERS,
|
|
10809
10772
|
});
|
|
10773
|
+
return {
|
|
10774
|
+
data: res.data
|
|
10775
|
+
? {
|
|
10776
|
+
code: res.data.code ?? 0,
|
|
10777
|
+
message: res.data.message || 'success',
|
|
10778
|
+
result: {
|
|
10779
|
+
collect_state: res.data.collect_state,
|
|
10780
|
+
started_at: res.data.started_at,
|
|
10781
|
+
},
|
|
10782
|
+
}
|
|
10783
|
+
: null,
|
|
10784
|
+
error: res.error,
|
|
10785
|
+
ctx: res.ctx,
|
|
10786
|
+
};
|
|
10810
10787
|
}
|
|
10811
10788
|
|
|
10812
|
-
|
|
10813
|
-
|
|
10814
|
-
|
|
10789
|
+
/**
|
|
10790
|
+
* "开始收集" 的本地 pipeline 实现。语义上等价于老远程流程的
|
|
10791
|
+
* `stark_wasm/v4/post/set_collecting`:**打开 server 端的 collect 窗口**,
|
|
10792
|
+
* 让 plugin 之后的 `/report` 请求能落库。
|
|
10793
|
+
*
|
|
10794
|
+
* 做三件事(顺序敏感):
|
|
10795
|
+
* 1. `POST /start` 打开 session —— 失败必须立即返回给 IDE,
|
|
10796
|
+
* 否则 UI 会让用户进"正在收集"但实际 plugin 所有上报都会被 fail-close
|
|
10797
|
+
* 丢弃,场面非常悲伤。
|
|
10798
|
+
* 2. 成功后上传符号表(`/symbols`)。这一步故意不 await、错误仅 warn —
|
|
10799
|
+
* 符号表只是给 server 端后续调试用的 debug 信息,丢了也不影响分包主链路。
|
|
10800
|
+
* 3. 返回 `{code: 0}`。
|
|
10801
|
+
*
|
|
10802
|
+
* 两种调用语义(与 `wasm_api.md` §5.1 对齐):
|
|
10803
|
+
* - 默认(`resume` 缺省 / false)—— 用户点"开始收集 / 重新开始分包",
|
|
10804
|
+
* 发 `reset: true`,服务端清空历史。这是历史行为,对应 IDE 上的
|
|
10805
|
+
* "willCollect → startCollect" 主入口。
|
|
10806
|
+
* - `resume: true` —— 页面刷新 / 恢复继续,发 `reset: false`,幂等
|
|
10807
|
+
* 打开 session、保留已有 func_ids。需要这条路径的 caller(如 IDE
|
|
10808
|
+
* 重新挂载组件检测到 server `collect_state: "open"` 想接续)必须
|
|
10809
|
+
* 显式传,避免误清。
|
|
10810
|
+
*
|
|
10811
|
+
* Session 生命周期对前端透明——IDE 只知道"开始收集 / 完成收集"两个动作,
|
|
10812
|
+
* `/start` 和 `/finish` 都被封在本地 dispatcher 内。这样远程 pipeline(没有
|
|
10813
|
+
* session 概念)和本地 pipeline(有 session)在 IDE 层看起来是对称的。
|
|
10814
|
+
*/
|
|
10815
|
+
async function setCollect$1({ client_key, wasm_md5, resume, }) {
|
|
10816
|
+
const startRes = await startWasmSession({
|
|
10817
|
+
client_key,
|
|
10818
|
+
wasm_md5,
|
|
10819
|
+
reset: !resume,
|
|
10820
|
+
});
|
|
10821
|
+
if (startRes.error || !startRes.data || startRes.data.code !== 0) {
|
|
10822
|
+
// /start is invoked internally by setCollect now; IDE only sees this
|
|
10823
|
+
// bubbled up as a generic "开始收集失败" toast, so dump a structured
|
|
10824
|
+
// one-liner here with logid — the single most useful field when
|
|
10825
|
+
// asking backend to look up what happened on their side.
|
|
10826
|
+
const code = startRes.error?.code ?? startRes.data?.code ?? -1;
|
|
10827
|
+
const message = startRes.error?.message ||
|
|
10828
|
+
startRes.data?.message ||
|
|
10829
|
+
'Open collect session failed';
|
|
10830
|
+
const logid = startRes.ctx?.logid || 'n/a';
|
|
10831
|
+
console.error(`[wasm-collect] /start failed: code=${code} message=${message} logid=${logid}`);
|
|
10832
|
+
return {
|
|
10833
|
+
data: startRes.data ?? null,
|
|
10834
|
+
error: startRes.error ?? { code, message },
|
|
10835
|
+
ctx: startRes.ctx,
|
|
10836
|
+
};
|
|
10837
|
+
}
|
|
10838
|
+
let symbolPath = path$1.join(process.cwd(), WASM_SYMBOL_FILE_NAME);
|
|
10839
|
+
if (!fs$1.existsSync(symbolPath)) {
|
|
10840
|
+
symbolPath = path$1.join(process.cwd(), TTMG_TEMP_DIR, WASM_SYMBOL_FILE_NAME);
|
|
10841
|
+
}
|
|
10842
|
+
if (fs$1.existsSync(symbolPath)) {
|
|
10843
|
+
const symbols = fs$1.readFileSync(symbolPath, 'utf-8');
|
|
10844
|
+
request({
|
|
10845
|
+
url: `${WASM_COLLECT_BASE_URL}/symbols`,
|
|
10846
|
+
method: 'POST',
|
|
10847
|
+
data: {
|
|
10848
|
+
app_id: client_key,
|
|
10849
|
+
wasm_md5,
|
|
10850
|
+
symbols,
|
|
10851
|
+
},
|
|
10852
|
+
}).catch(err => {
|
|
10853
|
+
console.warn('[wasmtool] Failed to upload symbols:', err);
|
|
10854
|
+
});
|
|
10855
|
+
}
|
|
10856
|
+
return {
|
|
10857
|
+
data: { code: 0, message: 'success', result: {} },
|
|
10858
|
+
error: null,
|
|
10859
|
+
ctx: { logid: 'local', httpStatusCode: 200 },
|
|
10860
|
+
};
|
|
10861
|
+
}
|
|
10862
|
+
|
|
10863
|
+
async function getCollecttingInfo$1({ client_key, wasm_md5, }) {
|
|
10864
|
+
const res = await request({
|
|
10865
|
+
url: `${WASM_COLLECT_BASE_URL}/progress`,
|
|
10815
10866
|
method: 'GET',
|
|
10816
|
-
headers: DEV_HEADERS,
|
|
10817
10867
|
params: {
|
|
10818
|
-
client_key,
|
|
10868
|
+
app_id: client_key,
|
|
10819
10869
|
wasm_md5,
|
|
10820
10870
|
},
|
|
10821
10871
|
});
|
|
10872
|
+
const { totalWasmFuncCount } = getLocalState();
|
|
10873
|
+
// Fall back to game.json.wasmFuncCount so the total survives CLI restarts.
|
|
10874
|
+
const gameJsonFuncCount = Number(getGameJson()?.wasmFuncCount) || 0;
|
|
10875
|
+
return {
|
|
10876
|
+
data: {
|
|
10877
|
+
code: res?.data?.code ?? 0,
|
|
10878
|
+
message: 'success',
|
|
10879
|
+
result: {
|
|
10880
|
+
app_id: client_key,
|
|
10881
|
+
wasm_md5,
|
|
10882
|
+
collected_func_count: res?.data?.func_count ?? 0,
|
|
10883
|
+
total_wasm_func_count: gameJsonFuncCount || totalWasmFuncCount || 0,
|
|
10884
|
+
collect_state: res?.data?.collect_state,
|
|
10885
|
+
},
|
|
10886
|
+
},
|
|
10887
|
+
error: res.error,
|
|
10888
|
+
ctx: res.ctx,
|
|
10889
|
+
};
|
|
10822
10890
|
}
|
|
10823
10891
|
|
|
10824
|
-
|
|
10825
|
-
|
|
10826
|
-
|
|
10827
|
-
|
|
10892
|
+
/**
|
|
10893
|
+
* POST /finish — closes a collect session and returns the final `func_count`
|
|
10894
|
+
* so the IDE can surface "本次共收集 N 个函数" in the success dialog.
|
|
10895
|
+
* Idempotent on the server.
|
|
10896
|
+
*
|
|
10897
|
+
* NOTE on naming: the server route is flat (`/finish`, not `/session/finish`).
|
|
10898
|
+
* The local symbol keeps `finishWasmSession` for symmetry with `startWasmSession`.
|
|
10899
|
+
*/
|
|
10900
|
+
async function finishWasmSession({ client_key, wasm_md5, }) {
|
|
10901
|
+
const res = await request({
|
|
10902
|
+
url: `${WASM_COLLECT_BASE_URL}/finish`,
|
|
10828
10903
|
method: 'POST',
|
|
10829
|
-
headers: {
|
|
10830
|
-
...DEV_HEADERS,
|
|
10831
|
-
},
|
|
10832
10904
|
data: {
|
|
10833
|
-
client_key,
|
|
10905
|
+
app_id: client_key,
|
|
10906
|
+
wasm_md5,
|
|
10907
|
+
},
|
|
10908
|
+
});
|
|
10909
|
+
return {
|
|
10910
|
+
data: res.data
|
|
10911
|
+
? {
|
|
10912
|
+
code: res.data.code ?? 0,
|
|
10913
|
+
message: res.data.message || 'success',
|
|
10914
|
+
result: {
|
|
10915
|
+
collect_state: res.data.collect_state,
|
|
10916
|
+
func_count: res.data.func_count ?? 0,
|
|
10917
|
+
finished_at: res.data.finished_at,
|
|
10918
|
+
},
|
|
10919
|
+
}
|
|
10920
|
+
: null,
|
|
10921
|
+
error: res.error,
|
|
10922
|
+
ctx: res.ctx,
|
|
10923
|
+
};
|
|
10924
|
+
}
|
|
10925
|
+
|
|
10926
|
+
async function startSplit$1({ client_key, wasm_md5, }) {
|
|
10927
|
+
const tempDir = path$1.join(process.cwd(), TTMG_TEMP_DIR);
|
|
10928
|
+
const splitOutputDir = path$1.join(tempDir, 'split-output');
|
|
10929
|
+
if (fs$1.existsSync(splitOutputDir)) {
|
|
10930
|
+
fs$1.rmSync(splitOutputDir, { recursive: true, force: true });
|
|
10931
|
+
}
|
|
10932
|
+
ensureDirSync(splitOutputDir);
|
|
10933
|
+
const { originalWasmPath, isArchiveMode: archive } = getLocalState();
|
|
10934
|
+
const rawWasmPath = originalWasmPath || path$1.join(tempDir, 'original.wasm');
|
|
10935
|
+
if (!fs$1.existsSync(rawWasmPath)) {
|
|
10936
|
+
return {
|
|
10937
|
+
data: null,
|
|
10938
|
+
error: {
|
|
10939
|
+
code: 404,
|
|
10940
|
+
message: 'Original wasm not found. Run prepare first.',
|
|
10941
|
+
},
|
|
10942
|
+
ctx: { logid: 'local', httpStatusCode: 404 },
|
|
10943
|
+
};
|
|
10944
|
+
}
|
|
10945
|
+
const exportRes = await request({
|
|
10946
|
+
url: `${WASM_COLLECT_BASE_URL}/export`,
|
|
10947
|
+
method: 'GET',
|
|
10948
|
+
params: {
|
|
10949
|
+
app_id: client_key,
|
|
10834
10950
|
wasm_md5,
|
|
10951
|
+
strategy: 'union',
|
|
10835
10952
|
},
|
|
10836
10953
|
});
|
|
10954
|
+
const funcIds = exportRes?.data?.func_ids;
|
|
10955
|
+
const bootFuncIds = exportRes?.data?.boot_func_ids ?? [];
|
|
10956
|
+
if (!funcIds?.length) {
|
|
10957
|
+
return {
|
|
10958
|
+
data: null,
|
|
10959
|
+
error: {
|
|
10960
|
+
code: 400,
|
|
10961
|
+
message: 'No collected func IDs found.',
|
|
10962
|
+
},
|
|
10963
|
+
ctx: { logid: 'local', httpStatusCode: 400 },
|
|
10964
|
+
};
|
|
10965
|
+
}
|
|
10966
|
+
console.log(`[wasmtool] splitting with ${funcIds.length} func IDs` +
|
|
10967
|
+
(bootFuncIds.length > 0
|
|
10968
|
+
? `, ${bootFuncIds.length} boot-phase func IDs (→ alwaysInclude)`
|
|
10969
|
+
: ', no boot-phase info (legacy server, falling back to callClosure only)') +
|
|
10970
|
+
`, archive=${archive}`);
|
|
10971
|
+
try {
|
|
10972
|
+
const result = ttmgWasmtool.split({
|
|
10973
|
+
input: rawWasmPath,
|
|
10974
|
+
funcIds,
|
|
10975
|
+
// Boot-phase func ids → `alwaysInclude`. They are a subset of
|
|
10976
|
+
// `funcIds` so this doesn't grow `collect_count`, but it DOES seed
|
|
10977
|
+
// the direct-call closure BFS with the exact set needed for first
|
|
10978
|
+
// frame, and the split tool's `alwaysIncludeAdded` counter is the
|
|
10979
|
+
// observability signal when zero (= server didn't return boot info).
|
|
10980
|
+
alwaysInclude: bootFuncIds.length > 0 ? bootFuncIds : undefined,
|
|
10981
|
+
// Always-on direct-call closure over (collect ∪ alwaysInclude ∪
|
|
10982
|
+
// start_func). Folds in func ids that collect missed (untaken
|
|
10983
|
+
// branches, race conditions during collect) so first-screen code
|
|
10984
|
+
// paths don't trap on archive trampolines. See the split tool's
|
|
10985
|
+
// `closure_added` counter for the per-build size impact.
|
|
10986
|
+
callClosure: true,
|
|
10987
|
+
// Always-on indirect-call type-closure scoped to the boot subset.
|
|
10988
|
+
// Catches IL2CPP virtual / interface / delegate dispatch which is
|
|
10989
|
+
// the dominant source of remaining `firstFrame=BEFORE` archive
|
|
10990
|
+
// trampoline hits after the runtime collect + direct closure
|
|
10991
|
+
// passes (see `indirectClosureAdded` for the per-build size
|
|
10992
|
+
// impact). Defaults to `true` in the wasmtool but we set it
|
|
10993
|
+
// explicitly so a future tool default change can't silently turn
|
|
10994
|
+
// it off in our pipeline.
|
|
10995
|
+
callIndirectClosure: true,
|
|
10996
|
+
outputDir: splitOutputDir,
|
|
10997
|
+
archive,
|
|
10998
|
+
compress: true,
|
|
10999
|
+
quality: 9,
|
|
11000
|
+
});
|
|
11001
|
+
if (result.code !== 0) {
|
|
11002
|
+
return {
|
|
11003
|
+
data: null,
|
|
11004
|
+
error: { code: result.code, message: result.errMsg },
|
|
11005
|
+
ctx: { logid: 'local', httpStatusCode: 500 },
|
|
11006
|
+
};
|
|
11007
|
+
}
|
|
11008
|
+
const mainBrPath = result.mainWasmPath + '.br';
|
|
11009
|
+
const actualMainPath = fs$1.existsSync(mainBrPath)
|
|
11010
|
+
? mainBrPath
|
|
11011
|
+
: result.mainWasmPath;
|
|
11012
|
+
const mainWasmMd5 = computeFileMd5Sync(actualMainPath);
|
|
11013
|
+
const subBrPath = result.subWasmPath ? result.subWasmPath + '.br' : '';
|
|
11014
|
+
const actualSubPath = subBrPath && fs$1.existsSync(subBrPath)
|
|
11015
|
+
? subBrPath
|
|
11016
|
+
: result.subWasmPath;
|
|
11017
|
+
const subWasmMd5 = actualSubPath
|
|
11018
|
+
? computeFileMd5Sync(actualSubPath)
|
|
11019
|
+
: '';
|
|
11020
|
+
let archiveMd5 = '';
|
|
11021
|
+
if (archive && result.archivePath) {
|
|
11022
|
+
const archiveBrPath = result.archivePath + '.br';
|
|
11023
|
+
const actualArchivePath = fs$1.existsSync(archiveBrPath)
|
|
11024
|
+
? archiveBrPath
|
|
11025
|
+
: result.archivePath;
|
|
11026
|
+
console.log(`[wasmtool] archivePath=${result.archivePath}, brExists=${fs$1.existsSync(archiveBrPath)}, actualExists=${fs$1.existsSync(actualArchivePath)}`);
|
|
11027
|
+
if (fs$1.existsSync(actualArchivePath)) {
|
|
11028
|
+
archiveMd5 = computeFileMd5Sync(actualArchivePath);
|
|
11029
|
+
console.log(`[wasmtool] archive_md5=${archiveMd5}`);
|
|
11030
|
+
}
|
|
11031
|
+
}
|
|
11032
|
+
else {
|
|
11033
|
+
console.log(`[wasmtool] skip archive md5: archive=${archive}, archivePath=${result.archivePath}`);
|
|
11034
|
+
}
|
|
11035
|
+
const globalVarList = result.globalVarList
|
|
11036
|
+
.split(';')
|
|
11037
|
+
.filter(Boolean)
|
|
11038
|
+
.map((entry) => {
|
|
11039
|
+
const [name, type, mutable] = entry.trim().split(',');
|
|
11040
|
+
return { name, type, mutable: mutable === '1' };
|
|
11041
|
+
});
|
|
11042
|
+
const splitMeta = {
|
|
11043
|
+
original_wasm_md5: wasm_md5,
|
|
11044
|
+
main_wasm_md5: mainWasmMd5,
|
|
11045
|
+
main_wasm_h5_md5: mainWasmMd5,
|
|
11046
|
+
sub_wasm_md5: subWasmMd5,
|
|
11047
|
+
archive_md5: archiveMd5,
|
|
11048
|
+
table_size: result.tableSize,
|
|
11049
|
+
global_var_list: globalVarList,
|
|
11050
|
+
version: Date.now(),
|
|
11051
|
+
total_wasm_count: result.totalWasmCount,
|
|
11052
|
+
main_wasm_count: result.mainWasmCount,
|
|
11053
|
+
time_cost: result.timeCost,
|
|
11054
|
+
archive,
|
|
11055
|
+
local_main_wasm_path: result.mainWasmPath,
|
|
11056
|
+
local_sub_wasm_path: result.subWasmPath,
|
|
11057
|
+
local_func_meta_path: result.funcMetaPath,
|
|
11058
|
+
local_archive_path: result.archivePath,
|
|
11059
|
+
// Composition breakdown of main_funcs — the single most useful
|
|
11060
|
+
// piece of information when triaging "why is my main package X MB"
|
|
11061
|
+
// (or, conversely, "why are first-screen sub-package batches still
|
|
11062
|
+
// loading"). collect = runtime-observed, always_include =
|
|
11063
|
+
// boot_func_ids, closure = BFS direct callees, indirect_closure =
|
|
11064
|
+
// type-matching pass scoped to boot funcs (covers IL2CPP virtual
|
|
11065
|
+
// dispatch), export = wasm exports. These sum with imports to
|
|
11066
|
+
// main_wasm_count.
|
|
11067
|
+
collect_func_count: result.collectFuncCount,
|
|
11068
|
+
always_include_added: result.alwaysIncludeAdded,
|
|
11069
|
+
closure_added: result.closureAdded,
|
|
11070
|
+
indirect_closure_added: result.indirectClosureAdded,
|
|
11071
|
+
indirect_closure_types: result.indirectClosureTypes,
|
|
11072
|
+
export_added: result.exportAdded,
|
|
11073
|
+
};
|
|
11074
|
+
setLocalState({ splitOutputDir, splitMeta });
|
|
11075
|
+
console.log(`[wasmtool] split done: total=${result.totalWasmCount}, main=${result.mainWasmCount} ` +
|
|
11076
|
+
`(collect=${result.collectFuncCount}, +alwaysInclude=${result.alwaysIncludeAdded}, ` +
|
|
11077
|
+
`+closure=${result.closureAdded}, +indirectClosure=${result.indirectClosureAdded}` +
|
|
11078
|
+
`[types=${result.indirectClosureTypes}], +exports=${result.exportAdded}), ` +
|
|
11079
|
+
`time=${result.timeCost}s`);
|
|
11080
|
+
// Split landed — close the collect session so the plugin stops reporting.
|
|
11081
|
+
// Awaited (not fire-and-forget) so IDE can rely on "wasm-split returned
|
|
11082
|
+
// success" meaning "session definitively closed". If /finish itself
|
|
11083
|
+
// fails (e.g. portal cookie expired mid-run) we still return split
|
|
11084
|
+
// success to the IDE — the plugin already has the MD5-bound session
|
|
11085
|
+
// state from the earlier /report responses and will time out on TTL
|
|
11086
|
+
// anyway; failing split for a finalizer hiccup would be worse UX.
|
|
11087
|
+
let funcCount;
|
|
11088
|
+
try {
|
|
11089
|
+
const finishRes = await finishWasmSession({ client_key, wasm_md5 });
|
|
11090
|
+
if (finishRes.error || !finishRes.data || finishRes.data.code !== 0) {
|
|
11091
|
+
// Soft failure: split already succeeded from the user's POV, but
|
|
11092
|
+
// this is the main diagnostic breadcrumb if someone later reports
|
|
11093
|
+
// "plugin kept uploading after 分包完成". Always include logid so
|
|
11094
|
+
// backend can cross-reference without having to know our build.
|
|
11095
|
+
const code = finishRes.error?.code ?? finishRes.data?.code ?? -1;
|
|
11096
|
+
const message = finishRes.error?.message ||
|
|
11097
|
+
finishRes.data?.message ||
|
|
11098
|
+
'finish session non-success';
|
|
11099
|
+
const logid = finishRes.ctx?.logid || 'n/a';
|
|
11100
|
+
console.error(`[wasm-split] /finish failed (split still succeeded): code=${code} message=${message} logid=${logid}`);
|
|
11101
|
+
}
|
|
11102
|
+
else {
|
|
11103
|
+
funcCount = finishRes.data.result?.func_count;
|
|
11104
|
+
}
|
|
11105
|
+
}
|
|
11106
|
+
catch (e) {
|
|
11107
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
11108
|
+
console.error(`[wasm-split] /finish threw (split still succeeded): ${msg}`);
|
|
11109
|
+
}
|
|
11110
|
+
return {
|
|
11111
|
+
data: { code: 0, message: 'success', func_count: funcCount },
|
|
11112
|
+
error: null,
|
|
11113
|
+
ctx: { logid: 'local', httpStatusCode: 200 },
|
|
11114
|
+
};
|
|
11115
|
+
}
|
|
11116
|
+
catch (err) {
|
|
11117
|
+
return {
|
|
11118
|
+
data: null,
|
|
11119
|
+
error: {
|
|
11120
|
+
code: 500,
|
|
11121
|
+
message: err instanceof Error ? err.message : String(err),
|
|
11122
|
+
},
|
|
11123
|
+
ctx: { logid: 'local', httpStatusCode: 500 },
|
|
11124
|
+
};
|
|
11125
|
+
}
|
|
11126
|
+
}
|
|
11127
|
+
|
|
11128
|
+
const ARCHIVE_SUBPACKAGE_CONFIG = [
|
|
11129
|
+
WASM_SPLIT_SUBPACKAGE_CONFIG.origin,
|
|
11130
|
+
WASM_SPLIT_SUBPACKAGE_CONFIG.archiveSub,
|
|
11131
|
+
WASM_SPLIT_SUBPACKAGE_CONFIG.archiveCode,
|
|
11132
|
+
];
|
|
11133
|
+
function updateSubpackageConfigSync(archive = false) {
|
|
11134
|
+
const gameJsonPath = path__namespace.join(process.cwd(), SUBPACKAGE_CONFIG_FILE_NAME);
|
|
11135
|
+
const raw = fs__namespace.readFileSync(gameJsonPath, 'utf-8');
|
|
11136
|
+
const gameJson = JSON.parse(raw);
|
|
11137
|
+
delete gameJson.wasmFuncCount;
|
|
11138
|
+
const fieldName = SUBPACKAGE_FIELD_NAMES.find(k => k in gameJson) ??
|
|
11139
|
+
SUBPACKAGE_FIELD_NAMES[0];
|
|
11140
|
+
if (!gameJson[fieldName])
|
|
11141
|
+
gameJson[fieldName] = [];
|
|
11142
|
+
const subpackages = gameJson[fieldName];
|
|
11143
|
+
const filtered = subpackages.filter(s => s.name !== WASM_SPLIT_SUBPACKAGE_CONFIG.origin.name);
|
|
11144
|
+
if (archive) {
|
|
11145
|
+
ARCHIVE_SUBPACKAGE_CONFIG.forEach(pkg => filtered.push(pkg));
|
|
11146
|
+
}
|
|
11147
|
+
else {
|
|
11148
|
+
filtered.push(WASM_SPLIT_SUBPACKAGE_CONFIG.androidMain);
|
|
11149
|
+
filtered.push(WASM_SPLIT_SUBPACKAGE_CONFIG.androidSub);
|
|
11150
|
+
filtered.push(WASM_SPLIT_SUBPACKAGE_CONFIG.iosMain);
|
|
11151
|
+
filtered.push(WASM_SPLIT_SUBPACKAGE_CONFIG.iosSub);
|
|
11152
|
+
}
|
|
11153
|
+
const map = new Map(filtered.map(s => [s.name, s]));
|
|
11154
|
+
gameJson[fieldName] = Array.from(map.values());
|
|
11155
|
+
fs__namespace.writeFileSync(gameJsonPath, JSON.stringify(gameJson, null, JSON_INDENT) + JSON_EOL);
|
|
11156
|
+
}
|
|
11157
|
+
|
|
11158
|
+
async function downloadSplited$1(_context) {
|
|
11159
|
+
const cwd = process.cwd();
|
|
11160
|
+
const { splitMeta } = getLocalState();
|
|
11161
|
+
if (!splitMeta) {
|
|
11162
|
+
return {
|
|
11163
|
+
data: { isSuccess: false },
|
|
11164
|
+
error: { message: 'No local split result found. Run split first.' },
|
|
11165
|
+
ctx: _context,
|
|
11166
|
+
};
|
|
11167
|
+
}
|
|
11168
|
+
const splitTempDir = path.join(cwd, WASM_SPLIT_CACHE_DIR, DIR_SPLIT);
|
|
11169
|
+
ensureDirSync(splitTempDir);
|
|
11170
|
+
const isArchive = splitMeta.archive;
|
|
11171
|
+
const mainAndroidDir = path.join(splitTempDir, isArchive ? 'wasmcode' : WASM_SPLIT_SUBPACKAGE_CONFIG.androidMain.root);
|
|
11172
|
+
const subAndroidDir = path.join(splitTempDir, isArchive ? 'wasmcode1' : WASM_SPLIT_SUBPACKAGE_CONFIG.androidSub.root);
|
|
11173
|
+
const mainIosDir = isArchive
|
|
11174
|
+
? mainAndroidDir
|
|
11175
|
+
: path.join(splitTempDir, WASM_SPLIT_SUBPACKAGE_CONFIG.iosMain.root);
|
|
11176
|
+
const subIosDir = path.join(splitTempDir, isArchive ? 'wasmcode-archive' : WASM_SPLIT_SUBPACKAGE_CONFIG.iosSub.root);
|
|
11177
|
+
const dirs = [...new Set([mainAndroidDir, subAndroidDir, mainIosDir, subIosDir])];
|
|
11178
|
+
dirs.forEach(ensureDirSync);
|
|
11179
|
+
try {
|
|
11180
|
+
console.log('[wasmtool] organizing split output...');
|
|
11181
|
+
const mainWasmMd5 = splitMeta.main_wasm_md5;
|
|
11182
|
+
const subWasmMd5 = splitMeta.sub_wasm_md5;
|
|
11183
|
+
const mainWasmH5Md5 = splitMeta.main_wasm_h5_md5;
|
|
11184
|
+
const localMainPath = splitMeta.local_main_wasm_path;
|
|
11185
|
+
const mainBrPath = localMainPath + BR_SUFFIX;
|
|
11186
|
+
const actualMainPath = fs.existsSync(mainBrPath) ? mainBrPath : localMainPath;
|
|
11187
|
+
if (actualMainPath && fs.existsSync(actualMainPath)) {
|
|
11188
|
+
const isBr = actualMainPath.endsWith(BR_SUFFIX);
|
|
11189
|
+
const ext = isBr
|
|
11190
|
+
? `${WASM_FILENAME_SUFFIX}${BR_SUFFIX}`
|
|
11191
|
+
: WASM_FILENAME_SUFFIX;
|
|
11192
|
+
const mainAndroidDest = path.join(mainAndroidDir, `${mainWasmMd5}${ext}`);
|
|
11193
|
+
fs.copyFileSync(actualMainPath, mainAndroidDest);
|
|
11194
|
+
wsServer.sendUnitySplitStatus({
|
|
11195
|
+
status: 'download_android_main_wasm_done',
|
|
11196
|
+
});
|
|
11197
|
+
if (mainIosDir !== mainAndroidDir) {
|
|
11198
|
+
const mainIosDest = path.join(mainIosDir, `${mainWasmH5Md5}${ext}`);
|
|
11199
|
+
fs.copyFileSync(actualMainPath, mainIosDest);
|
|
11200
|
+
}
|
|
11201
|
+
wsServer.sendUnitySplitStatus({
|
|
11202
|
+
status: 'download_ios_main_wasm_done',
|
|
11203
|
+
});
|
|
11204
|
+
}
|
|
11205
|
+
const localSubPath = splitMeta.local_sub_wasm_path;
|
|
11206
|
+
const subBrPath = localSubPath + BR_SUFFIX;
|
|
11207
|
+
const actualSubPath = fs.existsSync(subBrPath) ? subBrPath : localSubPath;
|
|
11208
|
+
if (actualSubPath && fs.existsSync(actualSubPath)) {
|
|
11209
|
+
const isBr = actualSubPath.endsWith(BR_SUFFIX);
|
|
11210
|
+
const ext = isBr
|
|
11211
|
+
? `${WASM_FILENAME_SUFFIX}${BR_SUFFIX}`
|
|
11212
|
+
: WASM_FILENAME_SUFFIX;
|
|
11213
|
+
const subAndroidDest = path.join(subAndroidDir, `${subWasmMd5}${ext}`);
|
|
11214
|
+
fs.copyFileSync(actualSubPath, subAndroidDest);
|
|
11215
|
+
wsServer.sendUnitySplitStatus({
|
|
11216
|
+
status: 'download_android_sub_wasm_code_done',
|
|
11217
|
+
});
|
|
11218
|
+
}
|
|
11219
|
+
const localArchivePath = splitMeta.local_archive_path;
|
|
11220
|
+
if (isArchive && localArchivePath) {
|
|
11221
|
+
const archiveBrPath = localArchivePath + BR_SUFFIX;
|
|
11222
|
+
const actualArchivePath = fs.existsSync(archiveBrPath) ? archiveBrPath : localArchivePath;
|
|
11223
|
+
console.log(`[wasmtool] archive copy: archive_md5=${splitMeta.archive_md5}, localPath=${localArchivePath}, brExists=${fs.existsSync(archiveBrPath)}, actual=${actualArchivePath}`);
|
|
11224
|
+
if (fs.existsSync(actualArchivePath)) {
|
|
11225
|
+
const archiveMd5 = splitMeta.archive_md5 || '';
|
|
11226
|
+
const archiveBaseName = path.basename(actualArchivePath);
|
|
11227
|
+
const destName = archiveMd5 ? `${archiveMd5}.${archiveBaseName}` : archiveBaseName;
|
|
11228
|
+
const archiveDest = path.join(subIosDir, destName);
|
|
11229
|
+
console.log(`[wasmtool] archive dest: ${archiveDest}`);
|
|
11230
|
+
fs.copyFileSync(actualArchivePath, archiveDest);
|
|
11231
|
+
}
|
|
11232
|
+
}
|
|
11233
|
+
dirs.forEach((dir) => {
|
|
11234
|
+
fs.writeFileSync(path.join(dir, 'game.js'), '', { encoding: 'utf-8' });
|
|
11235
|
+
});
|
|
11236
|
+
console.log('[wasmtool] copy split output to root...');
|
|
11237
|
+
wsServer.sendUnitySplitStatus({ status: 'start_write_splited_wasm_br' });
|
|
11238
|
+
for (const file of fs.readdirSync(splitTempDir)) {
|
|
11239
|
+
const srcPath = path.join(splitTempDir, file);
|
|
11240
|
+
const destPath = path.join(cwd, file);
|
|
11241
|
+
if (fs.existsSync(destPath)) {
|
|
11242
|
+
await promises.rm(destPath, { recursive: true, force: true });
|
|
11243
|
+
}
|
|
11244
|
+
await promises.cp(srcPath, destPath, { recursive: true, force: true });
|
|
11245
|
+
}
|
|
11246
|
+
wsServer.sendUnitySplitStatus({ status: 'write_splited_wasm_done' });
|
|
11247
|
+
console.log('[wasmtool] updating subpackage config...');
|
|
11248
|
+
updateSubpackageConfigSync(isArchive);
|
|
11249
|
+
console.log('[wasmtool] updating wasm split config...');
|
|
11250
|
+
wsServer.sendUnitySplitStatus({ status: 'start_update_wasm_split_config' });
|
|
11251
|
+
updateWasmSplitConfig({
|
|
11252
|
+
ENABLEWASMCOLLECT: true,
|
|
11253
|
+
ORIGINALWASMMD5: `${splitMeta.original_wasm_md5}`,
|
|
11254
|
+
WASMTABLESIZE: splitMeta.table_size,
|
|
11255
|
+
GLOBALVARLIST: JSON.stringify(splitMeta.global_var_list ?? []),
|
|
11256
|
+
SUBJSURL: '',
|
|
11257
|
+
IOS_CODE_FILE_MD5: `${splitMeta.main_wasm_h5_md5}`,
|
|
11258
|
+
ANDROID_CODE_FILE_MD5: `${splitMeta.main_wasm_md5}`,
|
|
11259
|
+
ANDROID_SUB_CODE_FILE_MD5: `${splitMeta.sub_wasm_md5}`,
|
|
11260
|
+
ARCHIVE_CODE_FILE_MD5: `${splitMeta.archive_md5 || ''}`,
|
|
11261
|
+
WASMSPLITVERSION: `${splitMeta.version}`,
|
|
11262
|
+
USINGWASMH5: Boolean(splitMeta.main_wasm_h5_md5),
|
|
11263
|
+
ENABLEWASMSPLIT: true,
|
|
11264
|
+
ENABLEARCHIVEMODE: isArchive,
|
|
11265
|
+
});
|
|
11266
|
+
wsServer.sendUnitySplitStatus({ status: 'update_wasm_split_config_done' });
|
|
11267
|
+
return {
|
|
11268
|
+
data: { isSuccess: true },
|
|
11269
|
+
ctx: splitMeta,
|
|
11270
|
+
};
|
|
11271
|
+
}
|
|
11272
|
+
catch (err) {
|
|
11273
|
+
wsServer.sendUnitySplitStatus({
|
|
11274
|
+
status: 'wasm_split_failed',
|
|
11275
|
+
errorMsg: err instanceof Error ? err.message : String(err),
|
|
11276
|
+
});
|
|
11277
|
+
return {
|
|
11278
|
+
data: { isSuccess: false },
|
|
11279
|
+
error: { message: err instanceof Error ? err.message : String(err) },
|
|
11280
|
+
ctx: splitMeta,
|
|
11281
|
+
};
|
|
11282
|
+
}
|
|
11283
|
+
finally {
|
|
11284
|
+
await promises.rm(splitTempDir, { recursive: true, force: true });
|
|
11285
|
+
if (!isArchive) {
|
|
11286
|
+
await promises.rm(path.join(cwd, WASM_SPLIT_SUBPACKAGE_CONFIG.origin.root), {
|
|
11287
|
+
recursive: true,
|
|
11288
|
+
force: true,
|
|
11289
|
+
});
|
|
11290
|
+
}
|
|
11291
|
+
}
|
|
10837
11292
|
}
|
|
10838
11293
|
|
|
10839
11294
|
/*
|
|
@@ -11002,75 +11457,28 @@ function pLimit(concurrency) {
|
|
|
11002
11457
|
return generator;
|
|
11003
11458
|
}
|
|
11004
11459
|
|
|
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;
|
|
11460
|
+
async function downloadOne(opts) {
|
|
11461
|
+
const { startStatus, doneStatus, url, out } = opts;
|
|
11036
11462
|
if (!url)
|
|
11037
11463
|
return;
|
|
11038
11464
|
const willDownloadedFileIsBr = url.includes(BR_SUFFIX);
|
|
11039
|
-
const
|
|
11040
|
-
|
|
11041
|
-
|
|
11042
|
-
console.log(`download url: ${url}`);
|
|
11465
|
+
const finalOut = willDownloadedFileIsBr && !out.endsWith(BR_SUFFIX) ? out + BR_SUFFIX : out;
|
|
11466
|
+
wsServer.sendUnitySplitStatus({ status: startStatus });
|
|
11467
|
+
console.log(`[remote-split-download] fetching -> ${finalOut}`);
|
|
11043
11468
|
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) {
|
|
11469
|
+
await withRetry(() => download(url, finalOut), DOWNLOAD_RETRY);
|
|
11470
|
+
const st = await promises.stat(finalOut);
|
|
11471
|
+
if (!st.size) {
|
|
11472
|
+
await promises.rm(finalOut, { force: true });
|
|
11473
|
+
throw new Error(`Empty download: ${finalOut}`);
|
|
11474
|
+
}
|
|
11475
|
+
console.log(`[remote-split-download] done: ${path.basename(finalOut)} size=${st.size}B time=${Date.now() - t0}ms`);
|
|
11476
|
+
wsServer.sendUnitySplitStatus({ status: doneStatus, url });
|
|
11477
|
+
// Legacy behaviour: write an empty game.js next to each downloaded artifact
|
|
11478
|
+
// so the subpackage loader doesn't complain about missing js entries.
|
|
11479
|
+
fs.writeFileSync(path.join(path.dirname(out), 'game.js'), '', 'utf-8');
|
|
11480
|
+
}
|
|
11481
|
+
async function downloadSplitedRemote(context) {
|
|
11074
11482
|
const cwd = process.cwd();
|
|
11075
11483
|
const splitTempDir = path.join(cwd, WASM_SPLIT_CACHE_DIR, DIR_SPLIT);
|
|
11076
11484
|
ensureDirSync(splitTempDir);
|
|
@@ -11079,248 +11487,172 @@ async function downloadSplited(context) {
|
|
|
11079
11487
|
const mainIosDir = path.join(splitTempDir, WASM_SPLIT_SUBPACKAGE_CONFIG.iosMain.root);
|
|
11080
11488
|
const subIosDir = path.join(splitTempDir, WASM_SPLIT_SUBPACKAGE_CONFIG.iosSub.root);
|
|
11081
11489
|
[mainAndroidDir, subAndroidDir, mainIosDir, subIosDir].forEach(ensureDirSync);
|
|
11082
|
-
const
|
|
11083
|
-
const
|
|
11084
|
-
const
|
|
11490
|
+
const mainAndroidOut = path.join(mainAndroidDir, `${context.main_wasm_md5}${WASM_FILENAME_SUFFIX}`);
|
|
11491
|
+
const subAndroidOut = path.join(subAndroidDir, `${context.sub_wasm_md5}${WASM_FILENAME_SUFFIX}`);
|
|
11492
|
+
const mainIosOut = path.join(mainIosDir, `${context.main_wasm_h5_md5}${WASM_FILENAME_SUFFIX}`);
|
|
11085
11493
|
const limit = pLimit(CONCURRENCY_LIMIT);
|
|
11086
11494
|
try {
|
|
11087
|
-
console.log('
|
|
11088
|
-
|
|
11089
|
-
|
|
11090
|
-
|
|
11495
|
+
console.log('[remote-split-download] start', {
|
|
11496
|
+
original_wasm_md5: context.original_wasm_md5,
|
|
11497
|
+
main_wasm_md5: context.main_wasm_md5,
|
|
11498
|
+
sub_wasm_md5: context.sub_wasm_md5,
|
|
11499
|
+
main_wasm_h5_md5: context.main_wasm_h5_md5,
|
|
11091
11500
|
});
|
|
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
11501
|
await Promise.all([
|
|
11101
|
-
limit(() =>
|
|
11102
|
-
|
|
11103
|
-
|
|
11104
|
-
startCompressStatus: 'start_compress_android_main_wasm',
|
|
11105
|
-
compressDoneStatus: 'compress_android_main_wasm_done',
|
|
11502
|
+
limit(() => downloadOne({
|
|
11503
|
+
startStatus: 'start_download_android_main_wasm',
|
|
11504
|
+
doneStatus: 'download_android_main_wasm_done',
|
|
11106
11505
|
url: context.main_wasm_download_url,
|
|
11107
|
-
out:
|
|
11506
|
+
out: mainAndroidOut,
|
|
11108
11507
|
})),
|
|
11109
|
-
limit(() =>
|
|
11110
|
-
|
|
11111
|
-
|
|
11112
|
-
startCompressStatus: 'start_compress_android_sub_wasm_code',
|
|
11113
|
-
compressDoneStatus: 'compress_android_sub_wasm_code_done',
|
|
11508
|
+
limit(() => downloadOne({
|
|
11509
|
+
startStatus: 'start_download_android_sub_wasm_code',
|
|
11510
|
+
doneStatus: 'download_android_sub_wasm_code_done',
|
|
11114
11511
|
url: context.sub_wasm_download_url,
|
|
11115
|
-
out:
|
|
11512
|
+
out: subAndroidOut,
|
|
11116
11513
|
})),
|
|
11117
|
-
limit(() =>
|
|
11118
|
-
|
|
11119
|
-
|
|
11120
|
-
startCompressStatus: 'start_compress_ios_main_wasm',
|
|
11121
|
-
compressDoneStatus: 'compress_ios_main_wasm_done',
|
|
11514
|
+
limit(() => downloadOne({
|
|
11515
|
+
startStatus: 'start_download_ios_main_wasm',
|
|
11516
|
+
doneStatus: 'download_ios_main_wasm_done',
|
|
11122
11517
|
url: context.main_wasm_h5_download_url,
|
|
11123
|
-
out:
|
|
11518
|
+
out: mainIosOut,
|
|
11124
11519
|
})),
|
|
11125
|
-
|
|
11126
|
-
|
|
11127
|
-
|
|
11128
|
-
downloadDoneStatus: 'download_ios_range_json_done',
|
|
11520
|
+
limit(() => downloadOne({
|
|
11521
|
+
startStatus: 'start_download_ios_range_json',
|
|
11522
|
+
doneStatus: 'download_ios_range_json_done',
|
|
11129
11523
|
url: context.sub_js_range_download_url,
|
|
11130
11524
|
out: path.join(subIosDir, 'func_bytes_range.json'),
|
|
11131
11525
|
})),
|
|
11132
|
-
|
|
11133
|
-
|
|
11134
|
-
|
|
11135
|
-
downloadDoneStatus: 'download_ios_js_data_br_done',
|
|
11526
|
+
limit(() => downloadOne({
|
|
11527
|
+
startStatus: 'start_download_ios_js_data_br',
|
|
11528
|
+
doneStatus: 'download_ios_js_data_br_done',
|
|
11136
11529
|
url: context.sub_js_data_download_url,
|
|
11137
11530
|
out: path.join(subIosDir, 'subjs.data'),
|
|
11138
11531
|
})),
|
|
11139
11532
|
]);
|
|
11140
|
-
|
|
11141
|
-
console.log('copy splitTempDir to root start');
|
|
11533
|
+
console.log('[remote-split-download] copying split output to project root...');
|
|
11142
11534
|
wsServer.sendUnitySplitStatus({ status: 'start_write_splited_wasm_br' });
|
|
11143
11535
|
for (const file of fs.readdirSync(splitTempDir)) {
|
|
11144
11536
|
const srcPath = path.join(splitTempDir, file);
|
|
11145
11537
|
const destPath = path.join(cwd, file);
|
|
11146
|
-
// 如果目标路径有文件或目录,先删除
|
|
11147
11538
|
if (fs.existsSync(destPath)) {
|
|
11148
11539
|
await promises.rm(destPath, { recursive: true, force: true });
|
|
11149
11540
|
}
|
|
11150
11541
|
await promises.cp(srcPath, destPath, { recursive: true, force: true });
|
|
11151
11542
|
}
|
|
11152
11543
|
wsServer.sendUnitySplitStatus({ status: 'write_splited_wasm_done' });
|
|
11153
|
-
console.log('
|
|
11154
|
-
|
|
11155
|
-
console.log('
|
|
11156
|
-
updateSubpackageConfigSync();
|
|
11157
|
-
console.log('updateSubpackageConfigSync end');
|
|
11158
|
-
// 更新 wasm split 配置(保持原始状态文案)
|
|
11159
|
-
console.log('updateWasmSplitConfig start');
|
|
11544
|
+
console.log('[remote-split-download] updating subpackage config...');
|
|
11545
|
+
updateSubpackageConfigSync(false);
|
|
11546
|
+
console.log('[remote-split-download] updating webgl-wasm-split.js...');
|
|
11160
11547
|
wsServer.sendUnitySplitStatus({ status: 'start_update_wasm_split_config' });
|
|
11161
11548
|
updateWasmSplitConfig({
|
|
11162
11549
|
ENABLEWASMCOLLECT: true,
|
|
11163
|
-
|
|
11550
|
+
ENABLEWASMSPLIT: true,
|
|
11551
|
+
ENABLEARCHIVEMODE: false,
|
|
11552
|
+
ORIGINALWASMMD5: `${context.original_wasm_md5 ?? ''}`,
|
|
11164
11553
|
WASMTABLESIZE: context.table_size,
|
|
11165
11554
|
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}`,
|
|
11555
|
+
SUBJSURL: `${context.sub_js_download_url ?? ''}`,
|
|
11556
|
+
IOS_CODE_FILE_MD5: `${context.main_wasm_h5_md5 ?? ''}`,
|
|
11557
|
+
ANDROID_CODE_FILE_MD5: `${context.main_wasm_md5 ?? ''}`,
|
|
11558
|
+
ANDROID_SUB_CODE_FILE_MD5: `${context.sub_wasm_md5 ?? ''}`,
|
|
11559
|
+
WASMSPLITVERSION: `${context.version ?? ''}`,
|
|
11171
11560
|
USINGWASMH5: Boolean(context.main_wasm_h5_md5),
|
|
11172
|
-
ENABLEWASMSPLIT: true,
|
|
11173
|
-
// IOS_SUB_JS_FILE_CONFIG: JSON.stringify(context.merged_js ?? {}),
|
|
11174
11561
|
});
|
|
11175
11562
|
wsServer.sendUnitySplitStatus({ status: 'update_wasm_split_config_done' });
|
|
11176
|
-
console.log('
|
|
11177
|
-
return {
|
|
11178
|
-
data: {
|
|
11179
|
-
isSuccess: true,
|
|
11180
|
-
},
|
|
11181
|
-
ctx: context,
|
|
11182
|
-
};
|
|
11563
|
+
console.log('[remote-split-download] all done');
|
|
11564
|
+
return { data: { isSuccess: true }, ctx: context };
|
|
11183
11565
|
}
|
|
11184
11566
|
catch (err) {
|
|
11185
|
-
|
|
11186
|
-
|
|
11187
|
-
|
|
11188
|
-
});
|
|
11567
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
11568
|
+
console.log('[remote-split-download] failed:', message);
|
|
11569
|
+
wsServer.sendUnitySplitStatus({ status: 'wasm_split_failed', errorMsg: message });
|
|
11189
11570
|
return {
|
|
11190
|
-
data: {
|
|
11191
|
-
|
|
11192
|
-
},
|
|
11193
|
-
error: {
|
|
11194
|
-
message: err instanceof Error ? err.message : String(err),
|
|
11195
|
-
},
|
|
11571
|
+
data: { isSuccess: false },
|
|
11572
|
+
error: { message },
|
|
11196
11573
|
ctx: context,
|
|
11197
11574
|
};
|
|
11198
11575
|
}
|
|
11199
11576
|
finally {
|
|
11200
|
-
// 清理临时目录与旧 wasmcode 目录
|
|
11201
|
-
console.log('delete splitTempDir start');
|
|
11202
11577
|
await promises.rm(splitTempDir, { recursive: true, force: true });
|
|
11203
|
-
|
|
11204
|
-
|
|
11578
|
+
// Legacy flow: the server-produced `wasmcode/` placeholder at the project
|
|
11579
|
+
// root is no longer needed once we've laid down the 4 platform dirs.
|
|
11205
11580
|
await promises.rm(path.join(cwd, WASM_SPLIT_SUBPACKAGE_CONFIG.origin.root), {
|
|
11206
11581
|
recursive: true,
|
|
11207
11582
|
force: true,
|
|
11208
11583
|
});
|
|
11209
|
-
console.log('delete wasmcode end');
|
|
11210
11584
|
}
|
|
11211
11585
|
}
|
|
11212
11586
|
|
|
11213
|
-
async function getSplitResult(
|
|
11214
|
-
|
|
11215
|
-
|
|
11216
|
-
|
|
11217
|
-
|
|
11218
|
-
|
|
11219
|
-
|
|
11220
|
-
|
|
11221
|
-
|
|
11222
|
-
|
|
11223
|
-
|
|
11224
|
-
* 把— __TTMG_TEMP__/wasmcode/ 目录下的所有文件恢复到原本的位置,进行重置
|
|
11225
|
-
*/
|
|
11226
|
-
const { wasmCodePath } = params;
|
|
11227
|
-
const cacheDir = path__namespace.join(process.cwd(), WASM_SPLIT_CACHE_DIR);
|
|
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'));
|
|
11587
|
+
async function getSplitResult$1(_params) {
|
|
11588
|
+
const { splitMeta } = getLocalState();
|
|
11589
|
+
if (!splitMeta) {
|
|
11590
|
+
return {
|
|
11591
|
+
data: null,
|
|
11592
|
+
error: {
|
|
11593
|
+
code: 404,
|
|
11594
|
+
message: 'No local split result found. Run split first.',
|
|
11595
|
+
},
|
|
11596
|
+
ctx: { logid: 'local', httpStatusCode: 404 },
|
|
11597
|
+
};
|
|
11256
11598
|
}
|
|
11257
|
-
|
|
11258
|
-
|
|
11259
|
-
async function resetWasmSplit(data) {
|
|
11260
|
-
const res = await request({
|
|
11261
|
-
url: `${BASE_URL}/api/stark_wasm/v4/post/reset`,
|
|
11262
|
-
method: 'POST',
|
|
11263
|
-
headers: {
|
|
11264
|
-
...DEV_HEADERS,
|
|
11265
|
-
},
|
|
11599
|
+
return {
|
|
11266
11600
|
data: {
|
|
11267
|
-
|
|
11268
|
-
|
|
11601
|
+
code: 0,
|
|
11602
|
+
message: 'success',
|
|
11603
|
+
result: splitMeta,
|
|
11269
11604
|
},
|
|
11270
|
-
|
|
11271
|
-
|
|
11272
|
-
|
|
11273
|
-
|
|
11274
|
-
|
|
11275
|
-
|
|
11276
|
-
|
|
11277
|
-
|
|
11278
|
-
|
|
11279
|
-
|
|
11280
|
-
|
|
11281
|
-
|
|
11282
|
-
|
|
11283
|
-
|
|
11284
|
-
|
|
11285
|
-
|
|
11286
|
-
|
|
11287
|
-
|
|
11288
|
-
|
|
11289
|
-
|
|
11290
|
-
|
|
11291
|
-
|
|
11292
|
-
|
|
11293
|
-
|
|
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 });
|
|
11605
|
+
error: null,
|
|
11606
|
+
ctx: { logid: 'local', httpStatusCode: 200 },
|
|
11607
|
+
};
|
|
11608
|
+
}
|
|
11609
|
+
|
|
11610
|
+
/**
|
|
11611
|
+
* Local-only undo of a prepare run. Routes through the shared
|
|
11612
|
+
* `restoreFromCache` helper so this stays in sync with `resetWasmSplit`
|
|
11613
|
+
* and never falls behind when new split-output dirs are added.
|
|
11614
|
+
*
|
|
11615
|
+
* `wasmCodePath` is kept on the signature for backward-compat with the
|
|
11616
|
+
* existing /game/wasm-cancel route shape, but it's no longer needed —
|
|
11617
|
+
* `restoreFromCache` already resolves the wasm path from the cache
|
|
11618
|
+
* directory contents (whatever `keepCacheSync` recorded as the original).
|
|
11619
|
+
*/
|
|
11620
|
+
function cancelSplit(_params) {
|
|
11621
|
+
restoreFromCache();
|
|
11622
|
+
// Drop the prepared-meta anchor for the same reason resetWasmSplit
|
|
11623
|
+
// does: cancel rolled the wasm back to its un-instrumented state, so
|
|
11624
|
+
// the recorded "preparedWasmMd5" is no longer accurate. Leaving it
|
|
11625
|
+
// would trip the split-config drift guard on the very next Modal open.
|
|
11626
|
+
const preparedMetaPath = path__namespace.join(process.cwd(), TTMG_TEMP_DIR, 'prepared-meta.json');
|
|
11627
|
+
if (fs__namespace.existsSync(preparedMetaPath)) {
|
|
11628
|
+
fs__namespace.rmSync(preparedMetaPath, { force: true });
|
|
11320
11629
|
}
|
|
11321
|
-
|
|
11322
|
-
|
|
11323
|
-
|
|
11630
|
+
}
|
|
11631
|
+
|
|
11632
|
+
async function resetWasmSplit$1(data) {
|
|
11633
|
+
const res = await request({
|
|
11634
|
+
url: `${WASM_COLLECT_BASE_URL}/reset`,
|
|
11635
|
+
method: 'POST',
|
|
11636
|
+
data: {
|
|
11637
|
+
app_id: data.clientkey,
|
|
11638
|
+
wasm_md5: data.wasmMd5,
|
|
11639
|
+
},
|
|
11640
|
+
});
|
|
11641
|
+
// Reuse the shared restore helper so local + remote rollback cleans
|
|
11642
|
+
// exactly the same set of files. Previously this function had its own
|
|
11643
|
+
// inline copy that only removed wasmcode-android / wasmcode1-android /
|
|
11644
|
+
// wasmcode-ios — missing wasmcode1 / wasmcode-archive (archive mode)
|
|
11645
|
+
// and wasmcode1-ios (legacy iOS sub), which left stale split outputs
|
|
11646
|
+
// on disk and caused them to be re-uploaded on the next build.
|
|
11647
|
+
restoreFromCache();
|
|
11648
|
+
// Drop the prepared-meta anchor as well — rollback restores the
|
|
11649
|
+
// original wasm into the project, so any md5 we previously recorded
|
|
11650
|
+
// for the prepared build is no longer valid. Leaving it behind would
|
|
11651
|
+
// make the split-config drift guard fire on the very next Modal open
|
|
11652
|
+
// and force the user through a redundant prepare cycle.
|
|
11653
|
+
const preparedMetaPath = path.join(process.cwd(), TTMG_TEMP_DIR, 'prepared-meta.json');
|
|
11654
|
+
if (fs.existsSync(preparedMetaPath)) {
|
|
11655
|
+
fs.rmSync(preparedMetaPath, { force: true });
|
|
11324
11656
|
}
|
|
11325
11657
|
return res;
|
|
11326
11658
|
}
|
|
@@ -11357,23 +11689,291 @@ function getSplitConfig() {
|
|
|
11357
11689
|
}
|
|
11358
11690
|
}
|
|
11359
11691
|
|
|
11360
|
-
|
|
11361
|
-
|
|
11362
|
-
|
|
11692
|
+
var WasmStatus;
|
|
11693
|
+
(function (WasmStatus) {
|
|
11694
|
+
WasmStatus[WasmStatus["IdleStatus"] = 0] = "IdleStatus";
|
|
11695
|
+
WasmStatus[WasmStatus["WasmPreparingStatus"] = 1] = "WasmPreparingStatus";
|
|
11696
|
+
WasmStatus[WasmStatus["WasmPreparedStatus"] = 2] = "WasmPreparedStatus";
|
|
11697
|
+
WasmStatus[WasmStatus["WasmSplitStatus"] = 3] = "WasmSplitStatus";
|
|
11698
|
+
WasmStatus[WasmStatus["WasmSplittingStatus"] = 4] = "WasmSplittingStatus";
|
|
11699
|
+
WasmStatus[WasmStatus["WasmSplitDoneStatus"] = 5] = "WasmSplitDoneStatus";
|
|
11700
|
+
WasmStatus[WasmStatus["WasmSplitReadyToPrepareStatus"] = 6] = "WasmSplitReadyToPrepareStatus";
|
|
11701
|
+
WasmStatus[WasmStatus["WasmSplitPreparingStatus"] = 7] = "WasmSplitPreparingStatus";
|
|
11702
|
+
WasmStatus[WasmStatus["WasmSplitPreparedStatus"] = 8] = "WasmSplitPreparedStatus";
|
|
11703
|
+
WasmStatus[WasmStatus["WasmCollectingStatus"] = 9] = "WasmCollectingStatus";
|
|
11704
|
+
WasmStatus[WasmStatus["WasmUploadFailStatus"] = -1] = "WasmUploadFailStatus";
|
|
11705
|
+
WasmStatus[WasmStatus["WasmDownloadFailStatus"] = -2] = "WasmDownloadFailStatus";
|
|
11706
|
+
WasmStatus[WasmStatus["WasmFileNotExistStatus"] = -3] = "WasmFileNotExistStatus";
|
|
11707
|
+
WasmStatus[WasmStatus["WasmSplitFailStatus"] = -4] = "WasmSplitFailStatus";
|
|
11708
|
+
WasmStatus[WasmStatus["WasmSplitUpdateDBFailedStatus"] = -5] = "WasmSplitUpdateDBFailedStatus";
|
|
11709
|
+
WasmStatus[WasmStatus["WasmSplitPrepareFailedStatus"] = -6] = "WasmSplitPrepareFailedStatus";
|
|
11710
|
+
})(WasmStatus || (WasmStatus = {}));
|
|
11711
|
+
|
|
11712
|
+
const getTaskStatus$1 = async (params) => {
|
|
11713
|
+
const { preparedWasmPath, splitMeta } = getLocalState();
|
|
11714
|
+
let status = WasmStatus.IdleStatus;
|
|
11715
|
+
if (splitMeta) {
|
|
11716
|
+
status = WasmStatus.WasmSplitDoneStatus;
|
|
11717
|
+
}
|
|
11718
|
+
else if (preparedWasmPath) {
|
|
11719
|
+
status = WasmStatus.WasmSplitPreparedStatus;
|
|
11720
|
+
}
|
|
11721
|
+
return {
|
|
11722
|
+
data: {
|
|
11723
|
+
code: 0,
|
|
11724
|
+
message: 'success',
|
|
11725
|
+
result: {
|
|
11726
|
+
status,
|
|
11727
|
+
wasm_md5: params.wasm_md5,
|
|
11728
|
+
},
|
|
11729
|
+
},
|
|
11730
|
+
error: null,
|
|
11731
|
+
ctx: { logid: 'local', httpStatusCode: 200 },
|
|
11732
|
+
};
|
|
11733
|
+
};
|
|
11734
|
+
|
|
11735
|
+
const getTaskInfo$1 = async (params) => {
|
|
11736
|
+
const res = await request({
|
|
11737
|
+
url: `${WASM_COLLECT_BASE_URL}/progress`,
|
|
11363
11738
|
method: 'GET',
|
|
11364
|
-
|
|
11365
|
-
|
|
11739
|
+
params: {
|
|
11740
|
+
app_id: params.client_key,
|
|
11741
|
+
wasm_md5: params.wasm_md5,
|
|
11742
|
+
},
|
|
11366
11743
|
});
|
|
11744
|
+
const { totalWasmFuncCount, preparedWasmPath, wasmSize } = getLocalState();
|
|
11745
|
+
// Prefer game.json as the source of truth so wasm_size / total_wasm_func_count
|
|
11746
|
+
// survive CLI restarts. localState values are only populated during the
|
|
11747
|
+
// prepare step of the current session; after a restart they default to 0.
|
|
11748
|
+
// game.json carries wasmCodeSize/wasmFuncCount emitted at build time, so
|
|
11749
|
+
// re-entering the collect step still shows the correct totals.
|
|
11750
|
+
const gameJson = getGameJson();
|
|
11751
|
+
const gameJsonWasmSize = Number(gameJson?.wasmCodeSize) || 0;
|
|
11752
|
+
const gameJsonFuncCount = Number(gameJson?.wasmFuncCount) || 0;
|
|
11753
|
+
return {
|
|
11754
|
+
data: {
|
|
11755
|
+
code: res?.data?.code ?? 0,
|
|
11756
|
+
message: 'success',
|
|
11757
|
+
result: {
|
|
11758
|
+
app_id: params.client_key,
|
|
11759
|
+
wasm_md5: params.wasm_md5,
|
|
11760
|
+
is_prepared: Boolean(preparedWasmPath),
|
|
11761
|
+
collected_func_count: res?.data?.func_count ?? 0,
|
|
11762
|
+
total_wasm_func_count: gameJsonFuncCount || totalWasmFuncCount || 0,
|
|
11763
|
+
wasm_size: gameJsonWasmSize || wasmSize || 0,
|
|
11764
|
+
},
|
|
11765
|
+
},
|
|
11766
|
+
error: res.error,
|
|
11767
|
+
ctx: res.ctx,
|
|
11768
|
+
};
|
|
11367
11769
|
};
|
|
11368
11770
|
|
|
11369
|
-
|
|
11771
|
+
async function startPrepareRemote(params) {
|
|
11772
|
+
// Back up the original wasm + split config on the first run so cancel/rollback
|
|
11773
|
+
// works even if the user aborts before the server-side prepare finishes.
|
|
11774
|
+
keepCacheSync({
|
|
11775
|
+
entryDir: process.cwd(),
|
|
11776
|
+
originalWasmPath: params.wasm_file_path,
|
|
11777
|
+
originalSplitConfigPath: WASM_SPLIT_CONFIG_FILE_NAME,
|
|
11778
|
+
});
|
|
11779
|
+
const form = new FormData$1();
|
|
11780
|
+
form.append('desc', params.desc);
|
|
11781
|
+
form.append('wasm_md5', params.wasm_md5);
|
|
11782
|
+
form.append('with_ios', 'true');
|
|
11783
|
+
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' });
|
|
11784
|
+
let symbolFilePath = path$1.join(process.cwd(), TTMG_TEMP_DIR, WASM_SYMBOL_FILE_NAME);
|
|
11785
|
+
if (!fs$1.existsSync(symbolFilePath)) {
|
|
11786
|
+
symbolFilePath = path$1.join(process.cwd(), WASM_SYMBOL_FILE_NAME);
|
|
11787
|
+
}
|
|
11788
|
+
if (!fs$1.existsSync(symbolFilePath)) {
|
|
11789
|
+
return {
|
|
11790
|
+
error: { code: 400, message: `${WASM_SYMBOL_FILE_NAME} not found`, client_key: params.client_key },
|
|
11791
|
+
data: null,
|
|
11792
|
+
ctx: { logid: '', httpStatusCode: 400 },
|
|
11793
|
+
};
|
|
11794
|
+
}
|
|
11795
|
+
form.append('wasm_symbol_file', fs$1.createReadStream(symbolFilePath), {
|
|
11796
|
+
filename: WASM_SYMBOL_FILE_NAME,
|
|
11797
|
+
contentType: 'application/octet-stream',
|
|
11798
|
+
});
|
|
11799
|
+
const formHeaders = form.getHeaders();
|
|
11800
|
+
return request({
|
|
11801
|
+
url: `${BASE_URL}/api/stark_wasm/v4/post/prepare`,
|
|
11802
|
+
method: 'POST',
|
|
11803
|
+
headers: { ...DEV_HEADERS, ...formHeaders },
|
|
11804
|
+
params: { client_key: params.client_key, with_ios: true },
|
|
11805
|
+
data: form,
|
|
11806
|
+
});
|
|
11807
|
+
}
|
|
11808
|
+
async function setCollectRemote({ client_key, wasm_md5 }) {
|
|
11809
|
+
return request({
|
|
11810
|
+
url: `${BASE_URL}/api/stark_wasm/v4/post/set_collecting`,
|
|
11811
|
+
method: 'POST',
|
|
11812
|
+
data: { client_key, wasm_md5 },
|
|
11813
|
+
headers: DEV_HEADERS,
|
|
11814
|
+
});
|
|
11815
|
+
}
|
|
11816
|
+
async function getCollectedFuncIdsRemote({ client_key, wasm_md5 }) {
|
|
11817
|
+
return request({
|
|
11818
|
+
url: `${BASE_URL}/api/stark_wasm/v4/get/collectedfuncids`,
|
|
11819
|
+
method: 'GET',
|
|
11820
|
+
headers: DEV_HEADERS,
|
|
11821
|
+
params: { client_key, wasm_md5 },
|
|
11822
|
+
});
|
|
11823
|
+
}
|
|
11824
|
+
async function getCollecttingInfoRemote({ client_key, wasm_md5 }) {
|
|
11825
|
+
return request({
|
|
11826
|
+
url: `${BASE_URL}/api/stark_wasm/v4/get/funccollect`,
|
|
11827
|
+
method: 'GET',
|
|
11828
|
+
headers: DEV_HEADERS,
|
|
11829
|
+
params: { client_key, wasm_md5 },
|
|
11830
|
+
});
|
|
11831
|
+
}
|
|
11832
|
+
async function startSplitRemote({ client_key, wasm_md5 }) {
|
|
11833
|
+
return request({
|
|
11834
|
+
url: `${BASE_URL}/api/stark_wasm/v4/post/split`,
|
|
11835
|
+
method: 'POST',
|
|
11836
|
+
headers: { ...DEV_HEADERS },
|
|
11837
|
+
data: { client_key, wasm_md5 },
|
|
11838
|
+
});
|
|
11839
|
+
}
|
|
11840
|
+
async function getTaskInfoRemote(params) {
|
|
11370
11841
|
return request({
|
|
11371
11842
|
url: `${BASE_URL}/api/stark_wasm/v4/get/taskinfo`,
|
|
11372
11843
|
method: 'GET',
|
|
11373
11844
|
headers: DEV_HEADERS,
|
|
11374
11845
|
params,
|
|
11375
11846
|
});
|
|
11376
|
-
}
|
|
11847
|
+
}
|
|
11848
|
+
async function getTaskStatusRemote(params) {
|
|
11849
|
+
return request({
|
|
11850
|
+
url: `${BASE_URL}/api/stark_wasm/v4/get/status`,
|
|
11851
|
+
method: 'GET',
|
|
11852
|
+
headers: DEV_HEADERS,
|
|
11853
|
+
params,
|
|
11854
|
+
});
|
|
11855
|
+
}
|
|
11856
|
+
async function resetWasmSplitRemote(data) {
|
|
11857
|
+
const res = await request({
|
|
11858
|
+
url: `${BASE_URL}/api/stark_wasm/v4/post/reset`,
|
|
11859
|
+
method: 'POST',
|
|
11860
|
+
headers: { ...DEV_HEADERS },
|
|
11861
|
+
data: { client_key: data.clientkey, wasm_md5: data.wasmMd5 },
|
|
11862
|
+
});
|
|
11863
|
+
// Restore project files (wasm / webgl-wasm-split.js / game.json) so the
|
|
11864
|
+
// next prepare starts from the original placeholders.
|
|
11865
|
+
restoreFromCache();
|
|
11866
|
+
return res;
|
|
11867
|
+
}
|
|
11868
|
+
async function getSplitResultRemote({ client_key, wasm_md5, wasm_path }) {
|
|
11869
|
+
return request({
|
|
11870
|
+
url: `${BASE_URL}/api/stark_wasm/v4/post/download`,
|
|
11871
|
+
method: 'POST',
|
|
11872
|
+
headers: { ...DEV_HEADERS },
|
|
11873
|
+
data: { client_key, wasm_md5, wasm_path },
|
|
11874
|
+
});
|
|
11875
|
+
}
|
|
11876
|
+
/**
|
|
11877
|
+
* Remote pipeline: after the server finishes preparing (instrumenting) the wasm,
|
|
11878
|
+
* fetch the download URL, download the prepared wasm, replace the project file,
|
|
11879
|
+
* and update webgl-wasm-split.js for the LEGACY reporting flow.
|
|
11880
|
+
*/
|
|
11881
|
+
async function downloadPreparedRemote(data) {
|
|
11882
|
+
wsServer.sendUnitySplitStatus({ status: 'star_fetch_prepared_wasm_url' });
|
|
11883
|
+
const res = await request({
|
|
11884
|
+
url: `${BASE_URL}/api/stark_wasm/v4/post/download_prepared`,
|
|
11885
|
+
method: 'POST',
|
|
11886
|
+
headers: DEV_HEADERS,
|
|
11887
|
+
data,
|
|
11888
|
+
});
|
|
11889
|
+
wsServer.sendUnitySplitStatus({ status: 'fetch_prepared_wasm_url_done' });
|
|
11890
|
+
try {
|
|
11891
|
+
const downloadUrl = res?.data?.result?.download_url;
|
|
11892
|
+
if (!downloadUrl) {
|
|
11893
|
+
console.log('[remote-download-prepared] no download_url in response');
|
|
11894
|
+
return {
|
|
11895
|
+
isSuccess: false,
|
|
11896
|
+
error: { code: res.data?.code, message: res.data?.message || 'No download_url returned' },
|
|
11897
|
+
ctx: res?.ctx,
|
|
11898
|
+
};
|
|
11899
|
+
}
|
|
11900
|
+
const willReplaceWasmPath = path$1.join(process.cwd(), data.wasm_path);
|
|
11901
|
+
const { cacheDir } = keepCacheSync({
|
|
11902
|
+
entryDir: process.cwd(),
|
|
11903
|
+
originalWasmPath: data.wasm_path,
|
|
11904
|
+
originalSplitConfigPath: WASM_SPLIT_CONFIG_FILE_NAME,
|
|
11905
|
+
});
|
|
11906
|
+
console.log(`[remote-download-prepared] target=${willReplaceWasmPath}`);
|
|
11907
|
+
if (downloadUrl.includes('.br')) {
|
|
11908
|
+
const tempWasmPath = path$1.join(cacheDir, '__temp__.wasm.br');
|
|
11909
|
+
console.log('[remote-download-prepared] downloading (br) ->', tempWasmPath);
|
|
11910
|
+
wsServer.sendUnitySplitStatus({ status: 'start_download_prepared_wasm', url: downloadUrl });
|
|
11911
|
+
const startedAt = Date.now();
|
|
11912
|
+
await download(downloadUrl, tempWasmPath);
|
|
11913
|
+
console.log(`[remote-download-prepared] download done in ${Date.now() - startedAt}ms, size=${fs$1.statSync(tempWasmPath).size}`);
|
|
11914
|
+
fs$1.copyFileSync(tempWasmPath, willReplaceWasmPath);
|
|
11915
|
+
wsServer.sendUnitySplitStatus({ status: 'download_prepared_wasm_done', url: downloadUrl });
|
|
11916
|
+
}
|
|
11917
|
+
else {
|
|
11918
|
+
const tempWasmPath = path$1.join(cacheDir, '__temp__.wasm');
|
|
11919
|
+
console.log('[remote-download-prepared] downloading (raw) ->', tempWasmPath);
|
|
11920
|
+
wsServer.sendUnitySplitStatus({ status: 'start_download_prepared_wasm', url: downloadUrl });
|
|
11921
|
+
const startedAt = Date.now();
|
|
11922
|
+
await download(downloadUrl, tempWasmPath);
|
|
11923
|
+
console.log(`[remote-download-prepared] download done in ${Date.now() - startedAt}ms, size=${fs$1.statSync(tempWasmPath).size}`);
|
|
11924
|
+
wsServer.sendUnitySplitStatus({ status: 'download_prepared_wasm_done', url: downloadUrl });
|
|
11925
|
+
wsServer.sendUnitySplitStatus({ status: 'start_compress_prepared_wasm' });
|
|
11926
|
+
await compressWasmFile(tempWasmPath, willReplaceWasmPath);
|
|
11927
|
+
console.log('[remote-download-prepared] compressed and written to project');
|
|
11928
|
+
wsServer.sendUnitySplitStatus({ status: 'compress_prepared_wasm_done', url: downloadUrl });
|
|
11929
|
+
wsServer.sendUnitySplitStatus({ status: 'write_compress_prepared_wasm_done' });
|
|
11930
|
+
}
|
|
11931
|
+
wsServer.sendUnitySplitStatus({ status: 'start_update_wasm_split_config' });
|
|
11932
|
+
// Remote (legacy) pipeline: enable collect but disable archive mode so the
|
|
11933
|
+
// plugin reports to the legacy stark_wasm/v4 collect API.
|
|
11934
|
+
// ORIGINALWASMMD5 must be set now (not only at split time) so the plugin
|
|
11935
|
+
// sends the correct wasm_md5 in every collect report.
|
|
11936
|
+
restoreSplitConfigFromCache();
|
|
11937
|
+
updateWasmSplitConfig({
|
|
11938
|
+
ENABLEWASMCOLLECT: true,
|
|
11939
|
+
ENABLEARCHIVEMODE: false,
|
|
11940
|
+
ORIGINALWASMMD5: res?.data?.result?.original_wasm_md5 ||
|
|
11941
|
+
res?.data?.result?.md5 ||
|
|
11942
|
+
data.wasm_md5,
|
|
11943
|
+
});
|
|
11944
|
+
wsServer.sendUnitySplitStatus({ status: 'update_wasm_split_config_done' });
|
|
11945
|
+
console.log('[remote-download-prepared] split config updated, returning success');
|
|
11946
|
+
return { isSuccess: true, ctx: res?.ctx };
|
|
11947
|
+
}
|
|
11948
|
+
catch (error) {
|
|
11949
|
+
console.log('[remote-download-prepared] error:', error);
|
|
11950
|
+
return {
|
|
11951
|
+
isSuccess: false,
|
|
11952
|
+
error: { code: res.data?.code, message: error instanceof Error ? error.message : String(error) },
|
|
11953
|
+
ctx: res?.ctx,
|
|
11954
|
+
};
|
|
11955
|
+
}
|
|
11956
|
+
}
|
|
11957
|
+
|
|
11958
|
+
function isLocal() {
|
|
11959
|
+
return getLocalState().pipelineMode === 'local';
|
|
11960
|
+
}
|
|
11961
|
+
const startPrepare = (params) => isLocal() ? startPrepare$1(params) : startPrepareRemote(params);
|
|
11962
|
+
const downloadPrepared = (params) => isLocal() ? downloadPrepared$1() : downloadPreparedRemote(params);
|
|
11963
|
+
const setCollect = (params) => isLocal() ? setCollect$1(params) : setCollectRemote(params);
|
|
11964
|
+
const getCollectedFuncIds = (params) => isLocal() ? getCollectedFuncIds$1(params) : getCollectedFuncIdsRemote(params);
|
|
11965
|
+
const getCollecttingInfo = (params) => isLocal() ? getCollecttingInfo$1(params) : getCollecttingInfoRemote(params);
|
|
11966
|
+
const startSplit = (params) => isLocal() ? startSplit$1(params) : startSplitRemote(params);
|
|
11967
|
+
const downloadSplited = (context) => isLocal() ? downloadSplited$1(context) : downloadSplitedRemote(context);
|
|
11968
|
+
const getSplitResult = (params) => isLocal() ? getSplitResult$1() : getSplitResultRemote(params);
|
|
11969
|
+
const getTaskInfo = (params) => isLocal() ? getTaskInfo$1(params) : getTaskInfoRemote(params);
|
|
11970
|
+
const getTaskStatus = (params) => isLocal() ? getTaskStatus$1(params) : getTaskStatusRemote(params);
|
|
11971
|
+
const resetWasmSplit = (data) => isLocal() ? resetWasmSplit$1(data) : resetWasmSplitRemote(data);
|
|
11972
|
+
// Collect session (`/start` / `/finish`) is an implementation detail of the
|
|
11973
|
+
// local `wasm-collect/v1` pipeline — it's invoked inside `setCollectLocal`
|
|
11974
|
+
// and `startSplitLocal` respectively. The remote `stark_wasm/v4` pipeline
|
|
11975
|
+
// has no session concept. IDE never calls these directly, so there is no
|
|
11976
|
+
// dispatcher exposed here.
|
|
11377
11977
|
|
|
11378
11978
|
const gameWasmCancelRoute = {
|
|
11379
11979
|
method: 'post',
|
|
@@ -11381,9 +11981,7 @@ const gameWasmCancelRoute = {
|
|
|
11381
11981
|
handler: async (req, res) => {
|
|
11382
11982
|
const { codePath } = req.body;
|
|
11383
11983
|
console.log('wasm-cancel', req.body);
|
|
11384
|
-
await cancelSplit(
|
|
11385
|
-
wasmCodePath: codePath,
|
|
11386
|
-
});
|
|
11984
|
+
await cancelSplit();
|
|
11387
11985
|
res.send({
|
|
11388
11986
|
code: successCode,
|
|
11389
11987
|
msg: 'cancel success',
|
|
@@ -11477,26 +12075,35 @@ const gameWasmPrepareResultRoute = {
|
|
|
11477
12075
|
method: 'post',
|
|
11478
12076
|
path: '/game/wasm-prepare-result',
|
|
11479
12077
|
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 {
|
|
12078
|
+
const { pipelineMode } = getLocalState();
|
|
12079
|
+
if (pipelineMode === 'local') {
|
|
11494
12080
|
res.send({
|
|
11495
12081
|
code: successCode,
|
|
11496
|
-
data:
|
|
11497
|
-
ctx:
|
|
12082
|
+
data: { status: WasmStatus.WasmSplitPreparedStatus },
|
|
12083
|
+
ctx: { logid: 'local' },
|
|
11498
12084
|
});
|
|
12085
|
+
return;
|
|
12086
|
+
}
|
|
12087
|
+
const { codeMd5, clientKey } = req.body;
|
|
12088
|
+
const result = await getTaskStatus({
|
|
12089
|
+
client_key: clientKey,
|
|
12090
|
+
wasm_md5: codeMd5,
|
|
12091
|
+
});
|
|
12092
|
+
// For the remote pipeline, forward the full `result` payload so the IDE
|
|
12093
|
+
// gets both `status` and the accompanying `package` info, matching the
|
|
12094
|
+
// legacy behaviour that the UI was written against.
|
|
12095
|
+
if (result?.error) {
|
|
12096
|
+
console.log('[wasm-prepare-result] remote error', result.error);
|
|
12097
|
+
res.send({ code: errorCode, error: result.error, ctx: result?.ctx });
|
|
12098
|
+
return;
|
|
11499
12099
|
}
|
|
12100
|
+
const data = result?.data?.result ?? { status: WasmStatus.IdleStatus };
|
|
12101
|
+
console.log(`[wasm-prepare-result] remote status=${data?.status}`);
|
|
12102
|
+
res.send({
|
|
12103
|
+
code: successCode,
|
|
12104
|
+
data,
|
|
12105
|
+
ctx: result?.ctx ?? { logid: 'remote' },
|
|
12106
|
+
});
|
|
11500
12107
|
},
|
|
11501
12108
|
};
|
|
11502
12109
|
|
|
@@ -11505,7 +12112,8 @@ const gameWasmPrepareRoute = {
|
|
|
11505
12112
|
path: '/game/wasm-prepare',
|
|
11506
12113
|
handler: async (req, res) => {
|
|
11507
12114
|
const { codePath, desc, codeMd5, clientKey } = req.body;
|
|
11508
|
-
|
|
12115
|
+
const { pipelineMode } = getLocalState();
|
|
12116
|
+
console.log(`wasm-prepare-start [mode=${pipelineMode}]`, req.body);
|
|
11509
12117
|
const result = await startPrepare({
|
|
11510
12118
|
client_key: clientKey,
|
|
11511
12119
|
desc,
|
|
@@ -11544,11 +12152,19 @@ const gameWasmSetCollectRoute = {
|
|
|
11544
12152
|
method: 'post',
|
|
11545
12153
|
path: '/game/wasm-set-collect',
|
|
11546
12154
|
handler: async (req, res) => {
|
|
11547
|
-
|
|
12155
|
+
// `resume` is optional and only meaningful for the local pipeline:
|
|
12156
|
+
// when the IDE detects an existing open session (e.g. user refreshed
|
|
12157
|
+
// the page mid-collect) and wants to "继续收集" without nuking the
|
|
12158
|
+
// already-uploaded func_ids, it POSTs `{ resume: true }`. Default
|
|
12159
|
+
// (omitted / false) keeps the historical "fresh run" behaviour on
|
|
12160
|
+
// /start (server gets `reset: true`). See `setCollect` jsdoc for the
|
|
12161
|
+
// two-path contract; remote pipeline ignores the field outright.
|
|
12162
|
+
const { clientKey, codeMd5, resume } = req.body;
|
|
11548
12163
|
console.log('wasm-set-collect', req.body);
|
|
11549
12164
|
const response = await setCollect({
|
|
11550
12165
|
client_key: clientKey,
|
|
11551
12166
|
wasm_md5: codeMd5,
|
|
12167
|
+
resume,
|
|
11552
12168
|
});
|
|
11553
12169
|
if (response.error) {
|
|
11554
12170
|
res.send({
|
|
@@ -11571,50 +12187,42 @@ const gameWasmSplitDownloadResultRoute = {
|
|
|
11571
12187
|
method: 'post',
|
|
11572
12188
|
path: '/game/wasm-split-download-result',
|
|
11573
12189
|
handler: async (req, res) => {
|
|
12190
|
+
const { pipelineMode, splitMeta } = getLocalState();
|
|
12191
|
+
if (pipelineMode === 'local') {
|
|
12192
|
+
if (!splitMeta) {
|
|
12193
|
+
res.send({
|
|
12194
|
+
code: errorCode,
|
|
12195
|
+
error: { message: 'No local split result found. Run split first.' },
|
|
12196
|
+
});
|
|
12197
|
+
return;
|
|
12198
|
+
}
|
|
12199
|
+
res.send({
|
|
12200
|
+
code: successCode,
|
|
12201
|
+
data: { result: splitMeta },
|
|
12202
|
+
msg: 'download success',
|
|
12203
|
+
ctx: { logid: 'local' },
|
|
12204
|
+
});
|
|
12205
|
+
return;
|
|
12206
|
+
}
|
|
11574
12207
|
const { clientKey, codeMd5, codePath } = req.body;
|
|
11575
|
-
|
|
11576
|
-
const response = await getSplitResult({
|
|
12208
|
+
const result = await getSplitResult({
|
|
11577
12209
|
client_key: clientKey,
|
|
11578
12210
|
wasm_md5: codeMd5,
|
|
11579
12211
|
wasm_path: codePath,
|
|
11580
12212
|
});
|
|
11581
|
-
if (
|
|
12213
|
+
if (result.error) {
|
|
11582
12214
|
res.send({
|
|
11583
12215
|
code: errorCode,
|
|
11584
|
-
error:
|
|
11585
|
-
ctx:
|
|
12216
|
+
error: result.error,
|
|
12217
|
+
ctx: result.ctx,
|
|
11586
12218
|
});
|
|
11587
12219
|
}
|
|
11588
12220
|
else {
|
|
11589
|
-
const splitResult = (response.data?.result || {});
|
|
11590
|
-
const requiredDownloadFields = [
|
|
11591
|
-
'main_wasm_download_url',
|
|
11592
|
-
'main_wasm_h5_download_url',
|
|
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() === '';
|
|
11601
|
-
});
|
|
11602
|
-
if (missingFields.length > 0) {
|
|
11603
|
-
res.send({
|
|
11604
|
-
code: errorCode,
|
|
11605
|
-
error: {
|
|
11606
|
-
message: `Missing required wasm split fields: ${missingFields.join(', ')}`,
|
|
11607
|
-
},
|
|
11608
|
-
data: response.data || {},
|
|
11609
|
-
ctx: response.ctx,
|
|
11610
|
-
});
|
|
11611
|
-
return;
|
|
11612
|
-
}
|
|
11613
12221
|
res.send({
|
|
11614
12222
|
code: successCode,
|
|
11615
|
-
data:
|
|
12223
|
+
data: result.data || {},
|
|
11616
12224
|
msg: 'download success',
|
|
11617
|
-
ctx:
|
|
12225
|
+
ctx: result.ctx,
|
|
11618
12226
|
});
|
|
11619
12227
|
}
|
|
11620
12228
|
},
|
|
@@ -11703,26 +12311,32 @@ const gameWasmSplitResultRoute = {
|
|
|
11703
12311
|
method: 'post',
|
|
11704
12312
|
path: '/game/wasm-split-result',
|
|
11705
12313
|
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 {
|
|
12314
|
+
const { pipelineMode } = getLocalState();
|
|
12315
|
+
if (pipelineMode === 'local') {
|
|
11720
12316
|
res.send({
|
|
11721
12317
|
code: successCode,
|
|
11722
|
-
data:
|
|
11723
|
-
ctx:
|
|
12318
|
+
data: { status: WasmStatus.WasmSplitDoneStatus },
|
|
12319
|
+
ctx: { logid: 'local' },
|
|
11724
12320
|
});
|
|
12321
|
+
return;
|
|
11725
12322
|
}
|
|
12323
|
+
const { clientKey, codeMd5 } = req.body;
|
|
12324
|
+
const result = await getTaskStatus({
|
|
12325
|
+
client_key: clientKey,
|
|
12326
|
+
wasm_md5: codeMd5,
|
|
12327
|
+
});
|
|
12328
|
+
if (result?.error) {
|
|
12329
|
+
console.log('[wasm-split-result] remote error', result.error);
|
|
12330
|
+
res.send({ code: errorCode, error: result.error, ctx: result?.ctx });
|
|
12331
|
+
return;
|
|
12332
|
+
}
|
|
12333
|
+
const data = result?.data?.result ?? { status: WasmStatus.IdleStatus };
|
|
12334
|
+
console.log(`[wasm-split-result] remote status=${data?.status}`);
|
|
12335
|
+
res.send({
|
|
12336
|
+
code: successCode,
|
|
12337
|
+
data,
|
|
12338
|
+
ctx: result?.ctx ?? { logid: 'remote' },
|
|
12339
|
+
});
|
|
11726
12340
|
},
|
|
11727
12341
|
};
|
|
11728
12342
|
|
|
@@ -11733,10 +12347,52 @@ const gameWasmSplitConfigRoute = {
|
|
|
11733
12347
|
const config = getSplitConfig();
|
|
11734
12348
|
if (!config) {
|
|
11735
12349
|
res.send({ code: errorCode, data: 'Failed to parse split config' });
|
|
12350
|
+
return;
|
|
11736
12351
|
}
|
|
11737
|
-
|
|
11738
|
-
|
|
12352
|
+
// When the CLI is restarted mid-session, localState.pipelineMode resets to
|
|
12353
|
+
// 'local' even though the project on disk may have been prepared in remote
|
|
12354
|
+
// mode. Re-infer it from the persisted split config so subsequent
|
|
12355
|
+
// dispatches (taskinfo / collect / split download) pick the right backend.
|
|
12356
|
+
// Heuristic: the local pipeline always writes enableArchiveMode=true, the
|
|
12357
|
+
// legacy remote pipeline always writes enableArchiveMode=false.
|
|
12358
|
+
if (config.enableWasmCollect) {
|
|
12359
|
+
const inferredMode = config.enableArchiveMode === true ? 'local' : 'remote';
|
|
12360
|
+
const current = getLocalState().pipelineMode;
|
|
12361
|
+
if (current !== inferredMode) {
|
|
12362
|
+
setLocalState({ pipelineMode: inferredMode });
|
|
12363
|
+
console.log(`[pipeline] inferred mode=${inferredMode} from webgl-wasm-split.js (was ${current})`);
|
|
12364
|
+
}
|
|
12365
|
+
}
|
|
12366
|
+
// ── wasm drift guard ─────────────────────────────────────────────
|
|
12367
|
+
//
|
|
12368
|
+
// `webgl-wasm-split.js` is persisted state about "which stage was
|
|
12369
|
+
// last completed", but it can desync from reality: the user's Unity
|
|
12370
|
+
// build re-emits `wasmcode/<file>.br` with a fresh, un-instrumented
|
|
12371
|
+
// binary while the config still claims `enableWasmCollect=true`. The
|
|
12372
|
+
// IDE's `canCollect()` then returns true, the prepare step gets
|
|
12373
|
+
// skipped, and the device loads a wasm that has no `scwebgl.logCall`
|
|
12374
|
+
// import — the `[wasmcollect] FATAL: no scwebgl.logCall import`
|
|
12375
|
+
// failure.
|
|
12376
|
+
//
|
|
12377
|
+
// To guard: compare the wasm file currently on disk to the md5 that
|
|
12378
|
+
// startPrepare wrote into `.ttmg-temp/prepared-meta.json`. If they
|
|
12379
|
+
// differ, demote `enableWasmCollect` back to its placeholder string
|
|
12380
|
+
// in the response so `canCollect()` → false and the IDE walks the
|
|
12381
|
+
// user through prepare again. We never touch the real config file
|
|
12382
|
+
// on disk — this is a transient correction at the read boundary, so
|
|
12383
|
+
// the next successful prepare seamlessly re-aligns everything.
|
|
12384
|
+
if (config.enableWasmCollect === true) {
|
|
12385
|
+
const wasmMeta = computeCurrentProjectWasmMd5();
|
|
12386
|
+
if (wasmMeta && wasmMeta.currentMd5 !== wasmMeta.meta.preparedWasmMd5) {
|
|
12387
|
+
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.`);
|
|
12388
|
+
// Mirror the string-placeholder shape the template uses before
|
|
12389
|
+
// prepare writes a real boolean — matches what `canCollect`
|
|
12390
|
+
// expects and is indistinguishable from "never prepared" from
|
|
12391
|
+
// the IDE's perspective.
|
|
12392
|
+
config.enableWasmCollect = '$ENABLEWASMCOLLECT';
|
|
12393
|
+
}
|
|
11739
12394
|
}
|
|
12395
|
+
res.send({ code: successCode, data: config });
|
|
11740
12396
|
},
|
|
11741
12397
|
};
|
|
11742
12398
|
|
|
@@ -11804,6 +12460,59 @@ function getGameFallbackRoute(publicPath) {
|
|
|
11804
12460
|
};
|
|
11805
12461
|
}
|
|
11806
12462
|
|
|
12463
|
+
/**
|
|
12464
|
+
* Explicit "user clicked a lang toggle in the IDE" endpoint. Unlike
|
|
12465
|
+
* `/game/config-fillback` — which intentionally no-ops when the CLI
|
|
12466
|
+
* already has a lang configured — this route always writes the incoming
|
|
12467
|
+
* lang to TTMGRC so the next IDE bootstrap's `setCurrentLang(cliLang)`
|
|
12468
|
+
* call won't stomp the user's fresh choice.
|
|
12469
|
+
*/
|
|
12470
|
+
const gameLanguageRoute = {
|
|
12471
|
+
method: 'post',
|
|
12472
|
+
path: '/game/language',
|
|
12473
|
+
handler: async (req, res) => {
|
|
12474
|
+
const incomingLang = req.body?.lang;
|
|
12475
|
+
const nextLang = resolveSupportedLanguage(incomingLang);
|
|
12476
|
+
if (!nextLang) {
|
|
12477
|
+
res.send({
|
|
12478
|
+
code: successCode,
|
|
12479
|
+
data: {
|
|
12480
|
+
lang: null,
|
|
12481
|
+
updated: false,
|
|
12482
|
+
},
|
|
12483
|
+
});
|
|
12484
|
+
return;
|
|
12485
|
+
}
|
|
12486
|
+
setTTMGRC({ lang: nextLang });
|
|
12487
|
+
res.send({
|
|
12488
|
+
code: successCode,
|
|
12489
|
+
data: {
|
|
12490
|
+
lang: nextLang,
|
|
12491
|
+
updated: true,
|
|
12492
|
+
},
|
|
12493
|
+
});
|
|
12494
|
+
},
|
|
12495
|
+
};
|
|
12496
|
+
|
|
12497
|
+
const gamePipelineModeRoute = {
|
|
12498
|
+
method: 'post',
|
|
12499
|
+
path: '/game/pipeline-mode',
|
|
12500
|
+
handler: (req, res) => {
|
|
12501
|
+
const { mode } = req.body;
|
|
12502
|
+
setLocalState({ pipelineMode: mode });
|
|
12503
|
+
console.log(`[pipeline] mode set to: ${mode}`);
|
|
12504
|
+
res.send({ code: successCode, data: { mode } });
|
|
12505
|
+
},
|
|
12506
|
+
};
|
|
12507
|
+
const gamePipelineModeGetRoute = {
|
|
12508
|
+
method: 'get',
|
|
12509
|
+
path: '/game/pipeline-mode',
|
|
12510
|
+
handler: (_req, res) => {
|
|
12511
|
+
const { pipelineMode } = getLocalState();
|
|
12512
|
+
res.send({ code: successCode, data: { mode: pipelineMode } });
|
|
12513
|
+
},
|
|
12514
|
+
};
|
|
12515
|
+
|
|
11807
12516
|
const routes = [
|
|
11808
12517
|
gameAssetPreviewUrlRoute,
|
|
11809
12518
|
gameAssetsRoute,
|
|
@@ -11832,6 +12541,9 @@ const routes = [
|
|
|
11832
12541
|
gameWasmSplitDownloadRoute,
|
|
11833
12542
|
gameWasmCancelRoute,
|
|
11834
12543
|
gameWasmSplitResetRoute,
|
|
12544
|
+
gamePipelineModeRoute,
|
|
12545
|
+
gamePipelineModeGetRoute,
|
|
12546
|
+
gameLanguageRoute,
|
|
11835
12547
|
];
|
|
11836
12548
|
function registerRoutes(app, options) {
|
|
11837
12549
|
const allRoutes = [...routes, getGameFallbackRoute(options.publicPath)];
|
|
@@ -11855,6 +12567,14 @@ async function start() {
|
|
|
11855
12567
|
const { url, version } = await listen(app, { maxRetries: 20 });
|
|
11856
12568
|
console.log(chalk.green.bold(`TTMG`), chalk.green(`v${version}`), chalk.gray(t('native.server.readyIn')), chalk.bold(`${Date.now() - startTime}ms`));
|
|
11857
12569
|
showTips({ server: url });
|
|
12570
|
+
// 联调场景下(root `dev:debug` 脚本)我们用 vite dev server 提供 IDE,
|
|
12571
|
+
// 不需要 CLI 自己再开浏览器到 dist/public 里那份打包后的旧 IDE。设置
|
|
12572
|
+
// `TTMG_DEV_NO_OPEN=1` 抑制 openUrl,由调度脚本统一负责开 5173。
|
|
12573
|
+
// 普通用户场景(npm 安装 ttmg)不会设置这个 env var,行为不变。
|
|
12574
|
+
if (process.env.TTMG_DEV_NO_OPEN === '1') {
|
|
12575
|
+
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.`));
|
|
12576
|
+
return;
|
|
12577
|
+
}
|
|
11858
12578
|
openUrl(url);
|
|
11859
12579
|
}
|
|
11860
12580
|
|
|
@@ -12277,7 +12997,7 @@ async function upload({ clientKey, note = '--', dir, }) {
|
|
|
12277
12997
|
}
|
|
12278
12998
|
}
|
|
12279
12999
|
|
|
12280
|
-
var version = "0.3.
|
|
13000
|
+
var version = "0.3.9-beta.wasm.1";
|
|
12281
13001
|
var pkg = {
|
|
12282
13002
|
version: version};
|
|
12283
13003
|
|