@chrrxs/robloxstudio-mcp-inspector 2.20.0 → 2.21.0
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 +1051 -64
- package/package.json +2 -2
- package/studio-plugin/MCPInspectorPlugin.rbxmx +3 -3
- package/studio-plugin/MCPPlugin.rbxmx +3 -3
package/dist/index.js
CHANGED
|
@@ -653,9 +653,57 @@ var init_mcp_compat = __esm({
|
|
|
653
653
|
}
|
|
654
654
|
});
|
|
655
655
|
|
|
656
|
+
// ../core/dist/auth.js
|
|
657
|
+
import { randomBytes, createHash, timingSafeEqual } from "crypto";
|
|
658
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync } from "fs";
|
|
659
|
+
import { join, dirname } from "path";
|
|
660
|
+
import { homedir } from "os";
|
|
661
|
+
function authTokenFilePath() {
|
|
662
|
+
return join(homedir(), ".robloxstudio-mcp", "auth-token");
|
|
663
|
+
}
|
|
664
|
+
function resolveAuthToken() {
|
|
665
|
+
const noAuth = (process.env.ROBLOX_STUDIO_NO_AUTH || "").toLowerCase();
|
|
666
|
+
if (noAuth === "1" || noAuth === "true") {
|
|
667
|
+
return { source: "disabled" };
|
|
668
|
+
}
|
|
669
|
+
const envToken = process.env.ROBLOX_STUDIO_AUTH_TOKEN?.trim();
|
|
670
|
+
if (envToken) {
|
|
671
|
+
return { token: envToken, source: "env" };
|
|
672
|
+
}
|
|
673
|
+
const filePath = authTokenFilePath();
|
|
674
|
+
try {
|
|
675
|
+
if (existsSync(filePath)) {
|
|
676
|
+
const existing = readFileSync(filePath, "utf8").trim();
|
|
677
|
+
if (existing) {
|
|
678
|
+
return { token: existing, source: "file", filePath };
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
const fresh = randomBytes(32).toString("hex");
|
|
682
|
+
mkdirSync(dirname(filePath), { recursive: true, mode: 448 });
|
|
683
|
+
writeFileSync(filePath, fresh + "\n", { encoding: "utf8", mode: 384 });
|
|
684
|
+
try {
|
|
685
|
+
chmodSync(filePath, 384);
|
|
686
|
+
} catch {
|
|
687
|
+
}
|
|
688
|
+
return { token: fresh, source: "file", filePath };
|
|
689
|
+
} catch (err) {
|
|
690
|
+
console.error(`[auth] Could not read/create ${filePath} (${err instanceof Error ? err.message : err}). Using an in-memory token for this process; set ROBLOX_STUDIO_AUTH_TOKEN to share one across sessions.`);
|
|
691
|
+
return { token: randomBytes(32).toString("hex"), source: "file" };
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
function tokensMatch(provided, expected) {
|
|
695
|
+
const a = createHash("sha256").update(provided).digest();
|
|
696
|
+
const b = createHash("sha256").update(expected).digest();
|
|
697
|
+
return timingSafeEqual(a, b);
|
|
698
|
+
}
|
|
699
|
+
var init_auth = __esm({
|
|
700
|
+
"../core/dist/auth.js"() {
|
|
701
|
+
"use strict";
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
|
|
656
705
|
// ../core/dist/http-server.js
|
|
657
706
|
import express from "express";
|
|
658
|
-
import cors from "cors";
|
|
659
707
|
import http from "http";
|
|
660
708
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
661
709
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
@@ -708,7 +756,7 @@ function requiredClosedLineRange(body, toolName) {
|
|
|
708
756
|
}
|
|
709
757
|
return { startLine: parsed.startLine, endLine: parsed.endLine };
|
|
710
758
|
}
|
|
711
|
-
function createHttpServer(tools, bridge, allowedTools, serverConfig) {
|
|
759
|
+
function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
|
|
712
760
|
const app = express();
|
|
713
761
|
let mcpServerActive = false;
|
|
714
762
|
let lastMCPActivity = 0;
|
|
@@ -738,7 +786,49 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig) {
|
|
|
738
786
|
const isPluginConnected = () => {
|
|
739
787
|
return bridge.getInstances().length > 0;
|
|
740
788
|
};
|
|
741
|
-
|
|
789
|
+
const allowedOrigins = new Set(security?.allowedOrigins ?? []);
|
|
790
|
+
app.use((req, res, next) => {
|
|
791
|
+
const origin = req.headers.origin;
|
|
792
|
+
if (typeof origin !== "string" || origin === "") {
|
|
793
|
+
next();
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
if (!allowedOrigins.has(origin)) {
|
|
797
|
+
res.status(403).json({
|
|
798
|
+
error: "forbidden_origin",
|
|
799
|
+
message: `Cross-origin requests are not allowed from ${origin}. Set ROBLOX_STUDIO_ALLOWED_ORIGINS to allowlist specific origins.`
|
|
800
|
+
});
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
804
|
+
res.setHeader("Vary", "Origin");
|
|
805
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
|
|
806
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-MCP-Auth, Mcp-Protocol-Version");
|
|
807
|
+
if (req.method === "OPTIONS") {
|
|
808
|
+
res.status(204).end();
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
next();
|
|
812
|
+
});
|
|
813
|
+
const authToken = security?.authToken;
|
|
814
|
+
const authRequired = (path5) => path5 === "/mcp" || path5.startsWith("/mcp/") || path5 === "/proxy" || path5 === "/instances" || path5 === "/unregister-instance-id";
|
|
815
|
+
app.use((req, res, next) => {
|
|
816
|
+
if (!authToken || !authRequired(req.path)) {
|
|
817
|
+
next();
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
const headerToken = req.headers["x-mcp-auth"];
|
|
821
|
+
const bearer = typeof req.headers.authorization === "string" && req.headers.authorization.startsWith("Bearer ") ? req.headers.authorization.slice("Bearer ".length) : void 0;
|
|
822
|
+
const provided = typeof headerToken === "string" && headerToken !== "" ? headerToken : bearer;
|
|
823
|
+
if (provided !== void 0 && tokensMatch(provided, authToken)) {
|
|
824
|
+
next();
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
res.status(401).json({
|
|
828
|
+
error: "unauthorized",
|
|
829
|
+
message: 'Missing or invalid auth token. Send it as "X-MCP-Auth: <token>" or "Authorization: Bearer <token>". ' + (security?.authTokenHint ?? "The token is in ~/.robloxstudio-mcp/auth-token (or ROBLOX_STUDIO_AUTH_TOKEN).")
|
|
830
|
+
});
|
|
831
|
+
});
|
|
742
832
|
app.use(express.json({ limit: "50mb" }));
|
|
743
833
|
app.use(express.urlencoded({ limit: "50mb", extended: true }));
|
|
744
834
|
app.get("/health", (req, res) => {
|
|
@@ -1108,6 +1198,7 @@ var init_http_server = __esm({
|
|
|
1108
1198
|
"use strict";
|
|
1109
1199
|
init_bridge_service();
|
|
1110
1200
|
init_mcp_compat();
|
|
1201
|
+
init_auth();
|
|
1111
1202
|
TOOL_HANDLERS = {
|
|
1112
1203
|
get_roblox_docs: (tools, body) => tools.getRobloxDocs(body.name, body.doc_type, body.section),
|
|
1113
1204
|
get_file_tree: (tools, body) => tools.getFileTree(body.path, body.instance_id),
|
|
@@ -1275,8 +1366,844 @@ var init_studio_client = __esm({
|
|
|
1275
1366
|
}
|
|
1276
1367
|
});
|
|
1277
1368
|
|
|
1369
|
+
// ../core/dist/tools/build-interpreter.js
|
|
1370
|
+
import * as acorn from "acorn";
|
|
1371
|
+
function isPlainObject(value) {
|
|
1372
|
+
if (value === null || typeof value !== "object")
|
|
1373
|
+
return false;
|
|
1374
|
+
const proto = Object.getPrototypeOf(value);
|
|
1375
|
+
return proto === Object.prototype || proto === null;
|
|
1376
|
+
}
|
|
1377
|
+
function runRestrictedScript(code, globals, options) {
|
|
1378
|
+
const timeoutMs = options?.timeoutMs ?? 1e4;
|
|
1379
|
+
const maxOps = options?.maxOps ?? 2e7;
|
|
1380
|
+
const deadline = Date.now() + timeoutMs;
|
|
1381
|
+
let ops = 0;
|
|
1382
|
+
let ast;
|
|
1383
|
+
try {
|
|
1384
|
+
ast = acorn.parse(code, { ecmaVersion: 2022, sourceType: "script" });
|
|
1385
|
+
} catch (err) {
|
|
1386
|
+
throw new Error(`Syntax error: ${err.message}`);
|
|
1387
|
+
}
|
|
1388
|
+
const globalScope = new Scope(void 0, true);
|
|
1389
|
+
for (const [name, value] of Object.entries(globals)) {
|
|
1390
|
+
globalScope.declare(name, value, "const");
|
|
1391
|
+
}
|
|
1392
|
+
function tick() {
|
|
1393
|
+
ops++;
|
|
1394
|
+
if (ops > maxOps) {
|
|
1395
|
+
throw new ScriptTimeoutError(`Operation budget exceeded (${maxOps} ops)`);
|
|
1396
|
+
}
|
|
1397
|
+
if ((ops & 8191) === 0 && Date.now() > deadline) {
|
|
1398
|
+
throw new ScriptTimeoutError(`Execution timed out after ${timeoutMs}ms`);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
function assertPropAllowed(prop) {
|
|
1402
|
+
const name = typeof prop === "symbol" ? String(prop) : String(prop);
|
|
1403
|
+
if (FORBIDDEN_PROPS.has(name)) {
|
|
1404
|
+
throw new Error(`Access to property "${name}" is not allowed`);
|
|
1405
|
+
}
|
|
1406
|
+
return name;
|
|
1407
|
+
}
|
|
1408
|
+
function getMember(obj, prop) {
|
|
1409
|
+
if (obj === null || obj === void 0) {
|
|
1410
|
+
throw new Error(`Cannot read property "${String(prop)}" of ${obj}`);
|
|
1411
|
+
}
|
|
1412
|
+
const name = assertPropAllowed(prop);
|
|
1413
|
+
if (Array.isArray(obj)) {
|
|
1414
|
+
if (name === "length")
|
|
1415
|
+
return obj.length;
|
|
1416
|
+
const idx = Number(name);
|
|
1417
|
+
if (Number.isInteger(idx) && idx >= 0)
|
|
1418
|
+
return obj[idx];
|
|
1419
|
+
if (ARRAY_MEMBERS.has(name))
|
|
1420
|
+
return obj[name];
|
|
1421
|
+
throw new Error(`Array method "${name}" is not allowed`);
|
|
1422
|
+
}
|
|
1423
|
+
if (typeof obj === "string") {
|
|
1424
|
+
if (name === "length")
|
|
1425
|
+
return obj.length;
|
|
1426
|
+
const idx = Number(name);
|
|
1427
|
+
if (Number.isInteger(idx) && idx >= 0)
|
|
1428
|
+
return obj[idx];
|
|
1429
|
+
if (STRING_MEMBERS.has(name))
|
|
1430
|
+
return obj[name];
|
|
1431
|
+
throw new Error(`String method "${name}" is not allowed`);
|
|
1432
|
+
}
|
|
1433
|
+
if (typeof obj === "number") {
|
|
1434
|
+
if (NUMBER_MEMBERS.has(name))
|
|
1435
|
+
return obj[name];
|
|
1436
|
+
throw new Error(`Number method "${name}" is not allowed`);
|
|
1437
|
+
}
|
|
1438
|
+
if (isPlainObject(obj)) {
|
|
1439
|
+
if (Object.prototype.hasOwnProperty.call(obj, name))
|
|
1440
|
+
return obj[name];
|
|
1441
|
+
return void 0;
|
|
1442
|
+
}
|
|
1443
|
+
throw new Error(`Property access on this value type is not allowed`);
|
|
1444
|
+
}
|
|
1445
|
+
function setMember(obj, prop, value) {
|
|
1446
|
+
if (obj === null || obj === void 0) {
|
|
1447
|
+
throw new Error(`Cannot set property "${String(prop)}" of ${obj}`);
|
|
1448
|
+
}
|
|
1449
|
+
const name = assertPropAllowed(prop);
|
|
1450
|
+
if (Object.isFrozen(obj)) {
|
|
1451
|
+
throw new Error(`Cannot assign to property "${name}" of a frozen object`);
|
|
1452
|
+
}
|
|
1453
|
+
if (Array.isArray(obj)) {
|
|
1454
|
+
const idx = Number(name);
|
|
1455
|
+
if (Number.isInteger(idx) && idx >= 0 || name === "length") {
|
|
1456
|
+
obj[name] = value;
|
|
1457
|
+
return;
|
|
1458
|
+
}
|
|
1459
|
+
throw new Error(`Cannot set property "${name}" on an array`);
|
|
1460
|
+
}
|
|
1461
|
+
if (isPlainObject(obj)) {
|
|
1462
|
+
obj[name] = value;
|
|
1463
|
+
return;
|
|
1464
|
+
}
|
|
1465
|
+
throw new Error(`Property assignment on this value type is not allowed`);
|
|
1466
|
+
}
|
|
1467
|
+
function callFunction(fn, thisArg, args, desc) {
|
|
1468
|
+
if (typeof fn !== "function") {
|
|
1469
|
+
throw new Error(`${desc} is not a function`);
|
|
1470
|
+
}
|
|
1471
|
+
return fn.apply(thisArg, args);
|
|
1472
|
+
}
|
|
1473
|
+
function bindPattern(pattern, value, scope, kind) {
|
|
1474
|
+
switch (pattern.type) {
|
|
1475
|
+
case "Identifier":
|
|
1476
|
+
scope.declare(pattern.name, value, kind);
|
|
1477
|
+
return;
|
|
1478
|
+
case "AssignmentPattern":
|
|
1479
|
+
bindPattern(pattern.left, value === void 0 ? evaluate(pattern.right, scope) : value, scope, kind);
|
|
1480
|
+
return;
|
|
1481
|
+
case "ArrayPattern": {
|
|
1482
|
+
if (value === null || value === void 0)
|
|
1483
|
+
throw new Error("Cannot destructure null/undefined");
|
|
1484
|
+
let i = 0;
|
|
1485
|
+
for (const el of pattern.elements) {
|
|
1486
|
+
if (el === null) {
|
|
1487
|
+
i++;
|
|
1488
|
+
continue;
|
|
1489
|
+
}
|
|
1490
|
+
if (el.type === "RestElement") {
|
|
1491
|
+
bindPattern(el.argument, Array.isArray(value) ? value.slice(i) : [], scope, kind);
|
|
1492
|
+
break;
|
|
1493
|
+
}
|
|
1494
|
+
bindPattern(el, getMember(value, i), scope, kind);
|
|
1495
|
+
i++;
|
|
1496
|
+
}
|
|
1497
|
+
return;
|
|
1498
|
+
}
|
|
1499
|
+
case "ObjectPattern": {
|
|
1500
|
+
if (value === null || value === void 0)
|
|
1501
|
+
throw new Error("Cannot destructure null/undefined");
|
|
1502
|
+
const used = /* @__PURE__ */ new Set();
|
|
1503
|
+
for (const propNode of pattern.properties) {
|
|
1504
|
+
if (propNode.type === "RestElement") {
|
|
1505
|
+
const rest = {};
|
|
1506
|
+
if (isPlainObject(value)) {
|
|
1507
|
+
for (const k of Object.keys(value)) {
|
|
1508
|
+
if (!used.has(k) && !FORBIDDEN_PROPS.has(k))
|
|
1509
|
+
rest[k] = value[k];
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
bindPattern(propNode.argument, rest, scope, kind);
|
|
1513
|
+
continue;
|
|
1514
|
+
}
|
|
1515
|
+
const key = propNode.computed ? String(evaluate(propNode.key, scope)) : propNode.key.type === "Identifier" ? propNode.key.name : String(propNode.key.value);
|
|
1516
|
+
used.add(key);
|
|
1517
|
+
bindPattern(propNode.value, getMember(value, key), scope, kind);
|
|
1518
|
+
}
|
|
1519
|
+
return;
|
|
1520
|
+
}
|
|
1521
|
+
default:
|
|
1522
|
+
throw new Error(`Unsupported binding pattern: ${pattern.type}`);
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
function makeFunction(node, closureScope) {
|
|
1526
|
+
return (...args) => {
|
|
1527
|
+
tick();
|
|
1528
|
+
const fnScope = new Scope(closureScope, true);
|
|
1529
|
+
node.params.forEach((param, i) => {
|
|
1530
|
+
if (param.type === "RestElement") {
|
|
1531
|
+
bindPattern(param.argument, args.slice(i), fnScope, "let");
|
|
1532
|
+
} else {
|
|
1533
|
+
bindPattern(param, args[i], fnScope, "let");
|
|
1534
|
+
}
|
|
1535
|
+
});
|
|
1536
|
+
try {
|
|
1537
|
+
if (node.body.type === "BlockStatement") {
|
|
1538
|
+
executeBlock(node.body.body, new Scope(fnScope));
|
|
1539
|
+
return void 0;
|
|
1540
|
+
}
|
|
1541
|
+
return evaluate(node.body, fnScope);
|
|
1542
|
+
} catch (signal) {
|
|
1543
|
+
if (signal instanceof ReturnSignal)
|
|
1544
|
+
return signal.value;
|
|
1545
|
+
throw signal;
|
|
1546
|
+
}
|
|
1547
|
+
};
|
|
1548
|
+
}
|
|
1549
|
+
function hoistFunctions(statements, scope) {
|
|
1550
|
+
for (const stmt of statements) {
|
|
1551
|
+
if (stmt.type === "FunctionDeclaration" && stmt.id) {
|
|
1552
|
+
if (stmt.async || stmt.generator)
|
|
1553
|
+
throw new Error("async/generator functions are not supported");
|
|
1554
|
+
scope.declare(stmt.id.name, makeFunction(stmt, scope), "let");
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
function executeBlock(statements, scope) {
|
|
1559
|
+
hoistFunctions(statements, scope);
|
|
1560
|
+
for (const stmt of statements) {
|
|
1561
|
+
execute(stmt, scope);
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
function execute(node, scope) {
|
|
1565
|
+
tick();
|
|
1566
|
+
switch (node.type) {
|
|
1567
|
+
case "ExpressionStatement":
|
|
1568
|
+
evaluate(node.expression, scope);
|
|
1569
|
+
return;
|
|
1570
|
+
case "VariableDeclaration":
|
|
1571
|
+
for (const decl of node.declarations) {
|
|
1572
|
+
const value = decl.init ? evaluate(decl.init, scope) : void 0;
|
|
1573
|
+
bindPattern(decl.id, value, scope, node.kind);
|
|
1574
|
+
}
|
|
1575
|
+
return;
|
|
1576
|
+
case "FunctionDeclaration":
|
|
1577
|
+
return;
|
|
1578
|
+
// hoisted by executeBlock
|
|
1579
|
+
case "BlockStatement":
|
|
1580
|
+
executeBlock(node.body, new Scope(scope));
|
|
1581
|
+
return;
|
|
1582
|
+
case "EmptyStatement":
|
|
1583
|
+
return;
|
|
1584
|
+
case "IfStatement":
|
|
1585
|
+
if (evaluate(node.test, scope)) {
|
|
1586
|
+
execute(node.consequent, scope);
|
|
1587
|
+
} else if (node.alternate) {
|
|
1588
|
+
execute(node.alternate, scope);
|
|
1589
|
+
}
|
|
1590
|
+
return;
|
|
1591
|
+
case "ForStatement": {
|
|
1592
|
+
const forScope = new Scope(scope);
|
|
1593
|
+
if (node.init) {
|
|
1594
|
+
if (node.init.type === "VariableDeclaration")
|
|
1595
|
+
execute(node.init, forScope);
|
|
1596
|
+
else
|
|
1597
|
+
evaluate(node.init, forScope);
|
|
1598
|
+
}
|
|
1599
|
+
while (node.test === null || evaluate(node.test, forScope)) {
|
|
1600
|
+
tick();
|
|
1601
|
+
try {
|
|
1602
|
+
execute(node.body, new Scope(forScope));
|
|
1603
|
+
} catch (signal) {
|
|
1604
|
+
if (signal instanceof BreakSignal)
|
|
1605
|
+
break;
|
|
1606
|
+
if (!(signal instanceof ContinueSignal))
|
|
1607
|
+
throw signal;
|
|
1608
|
+
}
|
|
1609
|
+
if (node.update)
|
|
1610
|
+
evaluate(node.update, forScope);
|
|
1611
|
+
}
|
|
1612
|
+
return;
|
|
1613
|
+
}
|
|
1614
|
+
case "ForOfStatement": {
|
|
1615
|
+
const iterable = evaluate(node.right, scope);
|
|
1616
|
+
if (!Array.isArray(iterable) && typeof iterable !== "string") {
|
|
1617
|
+
throw new Error("for...of only supports arrays and strings");
|
|
1618
|
+
}
|
|
1619
|
+
for (const item of iterable) {
|
|
1620
|
+
tick();
|
|
1621
|
+
const iterScope = new Scope(scope);
|
|
1622
|
+
if (node.left.type === "VariableDeclaration") {
|
|
1623
|
+
bindPattern(node.left.declarations[0].id, item, iterScope, node.left.kind);
|
|
1624
|
+
} else {
|
|
1625
|
+
assignTo(node.left, item, iterScope);
|
|
1626
|
+
}
|
|
1627
|
+
try {
|
|
1628
|
+
execute(node.body, iterScope);
|
|
1629
|
+
} catch (signal) {
|
|
1630
|
+
if (signal instanceof BreakSignal)
|
|
1631
|
+
break;
|
|
1632
|
+
if (!(signal instanceof ContinueSignal))
|
|
1633
|
+
throw signal;
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
return;
|
|
1637
|
+
}
|
|
1638
|
+
case "ForInStatement": {
|
|
1639
|
+
const obj = evaluate(node.right, scope);
|
|
1640
|
+
const keys = isPlainObject(obj) ? Object.keys(obj) : Array.isArray(obj) ? obj.map((_, i) => String(i)) : [];
|
|
1641
|
+
for (const key of keys) {
|
|
1642
|
+
tick();
|
|
1643
|
+
const iterScope = new Scope(scope);
|
|
1644
|
+
if (node.left.type === "VariableDeclaration") {
|
|
1645
|
+
bindPattern(node.left.declarations[0].id, key, iterScope, node.left.kind);
|
|
1646
|
+
} else {
|
|
1647
|
+
assignTo(node.left, key, iterScope);
|
|
1648
|
+
}
|
|
1649
|
+
try {
|
|
1650
|
+
execute(node.body, iterScope);
|
|
1651
|
+
} catch (signal) {
|
|
1652
|
+
if (signal instanceof BreakSignal)
|
|
1653
|
+
break;
|
|
1654
|
+
if (!(signal instanceof ContinueSignal))
|
|
1655
|
+
throw signal;
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
return;
|
|
1659
|
+
}
|
|
1660
|
+
case "WhileStatement":
|
|
1661
|
+
while (evaluate(node.test, scope)) {
|
|
1662
|
+
tick();
|
|
1663
|
+
try {
|
|
1664
|
+
execute(node.body, new Scope(scope));
|
|
1665
|
+
} catch (signal) {
|
|
1666
|
+
if (signal instanceof BreakSignal)
|
|
1667
|
+
break;
|
|
1668
|
+
if (!(signal instanceof ContinueSignal))
|
|
1669
|
+
throw signal;
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
return;
|
|
1673
|
+
case "DoWhileStatement":
|
|
1674
|
+
do {
|
|
1675
|
+
tick();
|
|
1676
|
+
try {
|
|
1677
|
+
execute(node.body, new Scope(scope));
|
|
1678
|
+
} catch (signal) {
|
|
1679
|
+
if (signal instanceof BreakSignal)
|
|
1680
|
+
break;
|
|
1681
|
+
if (!(signal instanceof ContinueSignal))
|
|
1682
|
+
throw signal;
|
|
1683
|
+
}
|
|
1684
|
+
} while (evaluate(node.test, scope));
|
|
1685
|
+
return;
|
|
1686
|
+
case "BreakStatement":
|
|
1687
|
+
throw new BreakSignal(node.label?.name);
|
|
1688
|
+
case "ContinueStatement":
|
|
1689
|
+
throw new ContinueSignal(node.label?.name);
|
|
1690
|
+
case "ReturnStatement":
|
|
1691
|
+
throw new ReturnSignal(node.argument ? evaluate(node.argument, scope) : void 0);
|
|
1692
|
+
case "ThrowStatement":
|
|
1693
|
+
throw evaluate(node.argument, scope);
|
|
1694
|
+
case "TryStatement": {
|
|
1695
|
+
try {
|
|
1696
|
+
execute(node.block, scope);
|
|
1697
|
+
} catch (err) {
|
|
1698
|
+
if (err instanceof BreakSignal || err instanceof ContinueSignal || err instanceof ReturnSignal)
|
|
1699
|
+
throw err;
|
|
1700
|
+
if (err instanceof ScriptTimeoutError)
|
|
1701
|
+
throw err;
|
|
1702
|
+
if (node.handler) {
|
|
1703
|
+
const catchScope = new Scope(scope);
|
|
1704
|
+
if (node.handler.param) {
|
|
1705
|
+
const message = err instanceof Error ? { message: err.message } : err;
|
|
1706
|
+
bindPattern(node.handler.param, message, catchScope, "let");
|
|
1707
|
+
}
|
|
1708
|
+
execute(node.handler.body, catchScope);
|
|
1709
|
+
}
|
|
1710
|
+
} finally {
|
|
1711
|
+
if (node.finalizer)
|
|
1712
|
+
execute(node.finalizer, scope);
|
|
1713
|
+
}
|
|
1714
|
+
return;
|
|
1715
|
+
}
|
|
1716
|
+
case "SwitchStatement": {
|
|
1717
|
+
const disc = evaluate(node.discriminant, scope);
|
|
1718
|
+
const switchScope = new Scope(scope);
|
|
1719
|
+
let matched = false;
|
|
1720
|
+
try {
|
|
1721
|
+
for (const caseNode of node.cases) {
|
|
1722
|
+
if (!matched) {
|
|
1723
|
+
if (caseNode.test === null)
|
|
1724
|
+
matched = true;
|
|
1725
|
+
else if (evaluate(caseNode.test, switchScope) === disc)
|
|
1726
|
+
matched = true;
|
|
1727
|
+
}
|
|
1728
|
+
if (matched) {
|
|
1729
|
+
for (const stmt of caseNode.consequent)
|
|
1730
|
+
execute(stmt, switchScope);
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
} catch (signal) {
|
|
1734
|
+
if (!(signal instanceof BreakSignal))
|
|
1735
|
+
throw signal;
|
|
1736
|
+
}
|
|
1737
|
+
return;
|
|
1738
|
+
}
|
|
1739
|
+
case "LabeledStatement":
|
|
1740
|
+
try {
|
|
1741
|
+
execute(node.body, scope);
|
|
1742
|
+
} catch (signal) {
|
|
1743
|
+
if (signal instanceof BreakSignal && signal.label === node.label.name)
|
|
1744
|
+
return;
|
|
1745
|
+
throw signal;
|
|
1746
|
+
}
|
|
1747
|
+
return;
|
|
1748
|
+
default:
|
|
1749
|
+
throw new Error(`Unsupported statement: ${node.type}`);
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
function assignTo(target, value, scope) {
|
|
1753
|
+
if (target.type === "Identifier") {
|
|
1754
|
+
scope.set(target.name, value);
|
|
1755
|
+
} else if (target.type === "MemberExpression") {
|
|
1756
|
+
const obj = evaluate(target.object, scope);
|
|
1757
|
+
const prop = target.computed ? evaluate(target.property, scope) : target.property.name;
|
|
1758
|
+
setMember(obj, prop, value);
|
|
1759
|
+
} else {
|
|
1760
|
+
throw new Error(`Unsupported assignment target: ${target.type}`);
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
function evaluate(node, scope) {
|
|
1764
|
+
tick();
|
|
1765
|
+
switch (node.type) {
|
|
1766
|
+
case "Literal":
|
|
1767
|
+
if (node.regex)
|
|
1768
|
+
throw new Error("Regular expression literals are not supported");
|
|
1769
|
+
return node.value;
|
|
1770
|
+
case "TemplateLiteral": {
|
|
1771
|
+
let out = "";
|
|
1772
|
+
node.quasis.forEach((quasi, i) => {
|
|
1773
|
+
out += quasi.value.cooked ?? "";
|
|
1774
|
+
if (i < node.expressions.length)
|
|
1775
|
+
out += String(evaluate(node.expressions[i], scope));
|
|
1776
|
+
});
|
|
1777
|
+
return out;
|
|
1778
|
+
}
|
|
1779
|
+
case "Identifier":
|
|
1780
|
+
return scope.get(node.name);
|
|
1781
|
+
case "ArrayExpression": {
|
|
1782
|
+
const arr = [];
|
|
1783
|
+
for (const el of node.elements) {
|
|
1784
|
+
if (el === null) {
|
|
1785
|
+
arr.length++;
|
|
1786
|
+
continue;
|
|
1787
|
+
}
|
|
1788
|
+
if (el.type === "SpreadElement") {
|
|
1789
|
+
const spread = evaluate(el.argument, scope);
|
|
1790
|
+
if (!Array.isArray(spread) && typeof spread !== "string") {
|
|
1791
|
+
throw new Error("Spread only supports arrays and strings");
|
|
1792
|
+
}
|
|
1793
|
+
for (const item of spread)
|
|
1794
|
+
arr.push(item);
|
|
1795
|
+
} else {
|
|
1796
|
+
arr.push(evaluate(el, scope));
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
return arr;
|
|
1800
|
+
}
|
|
1801
|
+
case "ObjectExpression": {
|
|
1802
|
+
const obj = {};
|
|
1803
|
+
for (const prop of node.properties) {
|
|
1804
|
+
if (prop.type === "SpreadElement") {
|
|
1805
|
+
const spread = evaluate(prop.argument, scope);
|
|
1806
|
+
if (isPlainObject(spread)) {
|
|
1807
|
+
for (const k of Object.keys(spread)) {
|
|
1808
|
+
if (!FORBIDDEN_PROPS.has(k))
|
|
1809
|
+
obj[k] = spread[k];
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
continue;
|
|
1813
|
+
}
|
|
1814
|
+
if (prop.kind !== "init")
|
|
1815
|
+
throw new Error("Getters/setters are not supported");
|
|
1816
|
+
const key = prop.computed ? String(evaluate(prop.key, scope)) : prop.key.type === "Identifier" ? prop.key.name : String(prop.key.value);
|
|
1817
|
+
assertPropAllowed(key);
|
|
1818
|
+
obj[key] = evaluate(prop.value, scope);
|
|
1819
|
+
}
|
|
1820
|
+
return obj;
|
|
1821
|
+
}
|
|
1822
|
+
case "ArrowFunctionExpression":
|
|
1823
|
+
case "FunctionExpression":
|
|
1824
|
+
if (node.async || node.generator)
|
|
1825
|
+
throw new Error("async/generator functions are not supported");
|
|
1826
|
+
return makeFunction(node, scope);
|
|
1827
|
+
case "UnaryExpression": {
|
|
1828
|
+
if (node.operator === "typeof" && node.argument.type === "Identifier" && !scope.has(node.argument.name)) {
|
|
1829
|
+
return "undefined";
|
|
1830
|
+
}
|
|
1831
|
+
if (node.operator === "delete") {
|
|
1832
|
+
if (node.argument.type !== "MemberExpression")
|
|
1833
|
+
return true;
|
|
1834
|
+
const obj = evaluate(node.argument.object, scope);
|
|
1835
|
+
const prop = node.argument.computed ? evaluate(node.argument.property, scope) : node.argument.property.name;
|
|
1836
|
+
const name = assertPropAllowed(prop);
|
|
1837
|
+
if (isPlainObject(obj) && !Object.isFrozen(obj)) {
|
|
1838
|
+
delete obj[name];
|
|
1839
|
+
return true;
|
|
1840
|
+
}
|
|
1841
|
+
throw new Error("delete is only allowed on plain objects");
|
|
1842
|
+
}
|
|
1843
|
+
const arg = evaluate(node.argument, scope);
|
|
1844
|
+
switch (node.operator) {
|
|
1845
|
+
case "-":
|
|
1846
|
+
return -arg;
|
|
1847
|
+
case "+":
|
|
1848
|
+
return +arg;
|
|
1849
|
+
case "!":
|
|
1850
|
+
return !arg;
|
|
1851
|
+
case "~":
|
|
1852
|
+
return ~arg;
|
|
1853
|
+
case "typeof":
|
|
1854
|
+
return typeof arg;
|
|
1855
|
+
case "void":
|
|
1856
|
+
return void 0;
|
|
1857
|
+
default:
|
|
1858
|
+
throw new Error(`Unsupported unary operator: ${node.operator}`);
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
case "UpdateExpression": {
|
|
1862
|
+
const old = node.argument.type === "Identifier" ? scope.get(node.argument.name) : getMember(evaluate(node.argument.object, scope), node.argument.computed ? evaluate(node.argument.property, scope) : node.argument.property.name);
|
|
1863
|
+
const next = node.operator === "++" ? old + 1 : old - 1;
|
|
1864
|
+
assignTo(node.argument, next, scope);
|
|
1865
|
+
return node.prefix ? next : old;
|
|
1866
|
+
}
|
|
1867
|
+
case "BinaryExpression": {
|
|
1868
|
+
if (node.operator === "in") {
|
|
1869
|
+
const key = assertPropAllowed(evaluate(node.left, scope));
|
|
1870
|
+
const obj = evaluate(node.right, scope);
|
|
1871
|
+
if (isPlainObject(obj))
|
|
1872
|
+
return Object.prototype.hasOwnProperty.call(obj, key);
|
|
1873
|
+
if (Array.isArray(obj))
|
|
1874
|
+
return Number(key) >= 0 && Number(key) < obj.length;
|
|
1875
|
+
throw new Error('"in" is only supported on plain objects and arrays');
|
|
1876
|
+
}
|
|
1877
|
+
if (node.operator === "instanceof")
|
|
1878
|
+
throw new Error("instanceof is not supported");
|
|
1879
|
+
const l = evaluate(node.left, scope);
|
|
1880
|
+
const r = evaluate(node.right, scope);
|
|
1881
|
+
switch (node.operator) {
|
|
1882
|
+
case "+":
|
|
1883
|
+
return l + r;
|
|
1884
|
+
case "-":
|
|
1885
|
+
return l - r;
|
|
1886
|
+
case "*":
|
|
1887
|
+
return l * r;
|
|
1888
|
+
case "/":
|
|
1889
|
+
return l / r;
|
|
1890
|
+
case "%":
|
|
1891
|
+
return l % r;
|
|
1892
|
+
case "**":
|
|
1893
|
+
return l ** r;
|
|
1894
|
+
case "==":
|
|
1895
|
+
return l == r;
|
|
1896
|
+
// eslint-disable-line eqeqeq
|
|
1897
|
+
case "!=":
|
|
1898
|
+
return l != r;
|
|
1899
|
+
// eslint-disable-line eqeqeq
|
|
1900
|
+
case "===":
|
|
1901
|
+
return l === r;
|
|
1902
|
+
case "!==":
|
|
1903
|
+
return l !== r;
|
|
1904
|
+
case "<":
|
|
1905
|
+
return l < r;
|
|
1906
|
+
case "<=":
|
|
1907
|
+
return l <= r;
|
|
1908
|
+
case ">":
|
|
1909
|
+
return l > r;
|
|
1910
|
+
case ">=":
|
|
1911
|
+
return l >= r;
|
|
1912
|
+
case "&":
|
|
1913
|
+
return l & r;
|
|
1914
|
+
case "|":
|
|
1915
|
+
return l | r;
|
|
1916
|
+
case "^":
|
|
1917
|
+
return l ^ r;
|
|
1918
|
+
case "<<":
|
|
1919
|
+
return l << r;
|
|
1920
|
+
case ">>":
|
|
1921
|
+
return l >> r;
|
|
1922
|
+
case ">>>":
|
|
1923
|
+
return l >>> r;
|
|
1924
|
+
default:
|
|
1925
|
+
throw new Error(`Unsupported binary operator: ${node.operator}`);
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
case "LogicalExpression": {
|
|
1929
|
+
const l = evaluate(node.left, scope);
|
|
1930
|
+
if (node.operator === "&&")
|
|
1931
|
+
return l ? evaluate(node.right, scope) : l;
|
|
1932
|
+
if (node.operator === "||")
|
|
1933
|
+
return l ? l : evaluate(node.right, scope);
|
|
1934
|
+
if (node.operator === "??")
|
|
1935
|
+
return l ?? evaluate(node.right, scope);
|
|
1936
|
+
throw new Error(`Unsupported logical operator: ${node.operator}`);
|
|
1937
|
+
}
|
|
1938
|
+
case "AssignmentExpression": {
|
|
1939
|
+
let value;
|
|
1940
|
+
if (node.operator === "=") {
|
|
1941
|
+
value = evaluate(node.right, scope);
|
|
1942
|
+
} else {
|
|
1943
|
+
const current = node.left.type === "Identifier" ? scope.get(node.left.name) : getMember(evaluate(node.left.object, scope), node.left.computed ? evaluate(node.left.property, scope) : node.left.property.name);
|
|
1944
|
+
const rhs = () => evaluate(node.right, scope);
|
|
1945
|
+
switch (node.operator) {
|
|
1946
|
+
case "+=":
|
|
1947
|
+
value = current + rhs();
|
|
1948
|
+
break;
|
|
1949
|
+
case "-=":
|
|
1950
|
+
value = current - rhs();
|
|
1951
|
+
break;
|
|
1952
|
+
case "*=":
|
|
1953
|
+
value = current * rhs();
|
|
1954
|
+
break;
|
|
1955
|
+
case "/=":
|
|
1956
|
+
value = current / rhs();
|
|
1957
|
+
break;
|
|
1958
|
+
case "%=":
|
|
1959
|
+
value = current % rhs();
|
|
1960
|
+
break;
|
|
1961
|
+
case "**=":
|
|
1962
|
+
value = current ** rhs();
|
|
1963
|
+
break;
|
|
1964
|
+
case "&=":
|
|
1965
|
+
value = current & rhs();
|
|
1966
|
+
break;
|
|
1967
|
+
case "|=":
|
|
1968
|
+
value = current | rhs();
|
|
1969
|
+
break;
|
|
1970
|
+
case "^=":
|
|
1971
|
+
value = current ^ rhs();
|
|
1972
|
+
break;
|
|
1973
|
+
case "<<=":
|
|
1974
|
+
value = current << rhs();
|
|
1975
|
+
break;
|
|
1976
|
+
case ">>=":
|
|
1977
|
+
value = current >> rhs();
|
|
1978
|
+
break;
|
|
1979
|
+
case ">>>=":
|
|
1980
|
+
value = current >>> rhs();
|
|
1981
|
+
break;
|
|
1982
|
+
case "&&=":
|
|
1983
|
+
value = current ? rhs() : current;
|
|
1984
|
+
break;
|
|
1985
|
+
case "||=":
|
|
1986
|
+
value = current ? current : rhs();
|
|
1987
|
+
break;
|
|
1988
|
+
case "??=":
|
|
1989
|
+
value = current ?? rhs();
|
|
1990
|
+
break;
|
|
1991
|
+
default:
|
|
1992
|
+
throw new Error(`Unsupported assignment operator: ${node.operator}`);
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
assignTo(node.left, value, scope);
|
|
1996
|
+
return value;
|
|
1997
|
+
}
|
|
1998
|
+
case "ConditionalExpression":
|
|
1999
|
+
return evaluate(node.test, scope) ? evaluate(node.consequent, scope) : evaluate(node.alternate, scope);
|
|
2000
|
+
case "CallExpression": {
|
|
2001
|
+
const args = [];
|
|
2002
|
+
for (const argNode of node.arguments) {
|
|
2003
|
+
if (argNode.type === "SpreadElement") {
|
|
2004
|
+
const spread = evaluate(argNode.argument, scope);
|
|
2005
|
+
if (!Array.isArray(spread) && typeof spread !== "string") {
|
|
2006
|
+
throw new Error("Spread only supports arrays and strings");
|
|
2007
|
+
}
|
|
2008
|
+
for (const item of spread)
|
|
2009
|
+
args.push(item);
|
|
2010
|
+
} else {
|
|
2011
|
+
args.push(evaluate(argNode, scope));
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
if (node.callee.type === "MemberExpression") {
|
|
2015
|
+
const obj = evaluate(node.callee.object, scope);
|
|
2016
|
+
if (node.optional && (obj === null || obj === void 0))
|
|
2017
|
+
return void 0;
|
|
2018
|
+
const prop = node.callee.computed ? evaluate(node.callee.property, scope) : node.callee.property.name;
|
|
2019
|
+
const method = getMember(obj, prop);
|
|
2020
|
+
if (node.callee.optional && (method === null || method === void 0))
|
|
2021
|
+
return void 0;
|
|
2022
|
+
return callFunction(method, obj, args, `${String(prop)}`);
|
|
2023
|
+
}
|
|
2024
|
+
const fn = evaluate(node.callee, scope);
|
|
2025
|
+
if (node.optional && (fn === null || fn === void 0))
|
|
2026
|
+
return void 0;
|
|
2027
|
+
const desc = node.callee.type === "Identifier" ? node.callee.name : "expression";
|
|
2028
|
+
return callFunction(fn, void 0, args, desc);
|
|
2029
|
+
}
|
|
2030
|
+
case "MemberExpression": {
|
|
2031
|
+
const obj = evaluate(node.object, scope);
|
|
2032
|
+
if (node.optional && (obj === null || obj === void 0))
|
|
2033
|
+
return void 0;
|
|
2034
|
+
const prop = node.computed ? evaluate(node.property, scope) : node.property.name;
|
|
2035
|
+
return getMember(obj, prop);
|
|
2036
|
+
}
|
|
2037
|
+
case "ChainExpression":
|
|
2038
|
+
return evaluate(node.expression, scope);
|
|
2039
|
+
case "SequenceExpression": {
|
|
2040
|
+
let result;
|
|
2041
|
+
for (const expr of node.expressions)
|
|
2042
|
+
result = evaluate(expr, scope);
|
|
2043
|
+
return result;
|
|
2044
|
+
}
|
|
2045
|
+
case "NewExpression":
|
|
2046
|
+
throw new Error('"new" is not supported in build code');
|
|
2047
|
+
case "ThisExpression":
|
|
2048
|
+
throw new Error('"this" is not supported in build code');
|
|
2049
|
+
case "AwaitExpression":
|
|
2050
|
+
throw new Error("await is not supported in build code");
|
|
2051
|
+
case "TaggedTemplateExpression":
|
|
2052
|
+
throw new Error("Tagged templates are not supported in build code");
|
|
2053
|
+
default:
|
|
2054
|
+
throw new Error(`Unsupported expression: ${node.type}`);
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
executeBlock(ast.body, globalScope);
|
|
2058
|
+
}
|
|
2059
|
+
var ScriptTimeoutError, BreakSignal, ContinueSignal, ReturnSignal, FORBIDDEN_PROPS, ARRAY_MEMBERS, STRING_MEMBERS, NUMBER_MEMBERS, Scope;
|
|
2060
|
+
var init_build_interpreter = __esm({
|
|
2061
|
+
"../core/dist/tools/build-interpreter.js"() {
|
|
2062
|
+
"use strict";
|
|
2063
|
+
ScriptTimeoutError = class extends Error {
|
|
2064
|
+
constructor(message) {
|
|
2065
|
+
super(message);
|
|
2066
|
+
this.name = "ScriptTimeoutError";
|
|
2067
|
+
}
|
|
2068
|
+
};
|
|
2069
|
+
BreakSignal = class {
|
|
2070
|
+
label;
|
|
2071
|
+
constructor(label) {
|
|
2072
|
+
this.label = label;
|
|
2073
|
+
}
|
|
2074
|
+
};
|
|
2075
|
+
ContinueSignal = class {
|
|
2076
|
+
label;
|
|
2077
|
+
constructor(label) {
|
|
2078
|
+
this.label = label;
|
|
2079
|
+
}
|
|
2080
|
+
};
|
|
2081
|
+
ReturnSignal = class {
|
|
2082
|
+
value;
|
|
2083
|
+
constructor(value) {
|
|
2084
|
+
this.value = value;
|
|
2085
|
+
}
|
|
2086
|
+
};
|
|
2087
|
+
FORBIDDEN_PROPS = /* @__PURE__ */ new Set([
|
|
2088
|
+
"__proto__",
|
|
2089
|
+
"constructor",
|
|
2090
|
+
"prototype",
|
|
2091
|
+
"__defineGetter__",
|
|
2092
|
+
"__defineSetter__",
|
|
2093
|
+
"__lookupGetter__",
|
|
2094
|
+
"__lookupSetter__",
|
|
2095
|
+
"caller",
|
|
2096
|
+
"callee",
|
|
2097
|
+
"arguments",
|
|
2098
|
+
"bind",
|
|
2099
|
+
"call",
|
|
2100
|
+
"apply"
|
|
2101
|
+
]);
|
|
2102
|
+
ARRAY_MEMBERS = /* @__PURE__ */ new Set([
|
|
2103
|
+
"length",
|
|
2104
|
+
"push",
|
|
2105
|
+
"pop",
|
|
2106
|
+
"shift",
|
|
2107
|
+
"unshift",
|
|
2108
|
+
"slice",
|
|
2109
|
+
"splice",
|
|
2110
|
+
"indexOf",
|
|
2111
|
+
"lastIndexOf",
|
|
2112
|
+
"includes",
|
|
2113
|
+
"join",
|
|
2114
|
+
"map",
|
|
2115
|
+
"filter",
|
|
2116
|
+
"forEach",
|
|
2117
|
+
"reduce",
|
|
2118
|
+
"reduceRight",
|
|
2119
|
+
"concat",
|
|
2120
|
+
"reverse",
|
|
2121
|
+
"sort",
|
|
2122
|
+
"find",
|
|
2123
|
+
"findIndex",
|
|
2124
|
+
"some",
|
|
2125
|
+
"every",
|
|
2126
|
+
"flat",
|
|
2127
|
+
"flatMap",
|
|
2128
|
+
"fill"
|
|
2129
|
+
]);
|
|
2130
|
+
STRING_MEMBERS = /* @__PURE__ */ new Set([
|
|
2131
|
+
"length",
|
|
2132
|
+
"charAt",
|
|
2133
|
+
"charCodeAt",
|
|
2134
|
+
"codePointAt",
|
|
2135
|
+
"indexOf",
|
|
2136
|
+
"lastIndexOf",
|
|
2137
|
+
"includes",
|
|
2138
|
+
"startsWith",
|
|
2139
|
+
"endsWith",
|
|
2140
|
+
"slice",
|
|
2141
|
+
"substring",
|
|
2142
|
+
"toUpperCase",
|
|
2143
|
+
"toLowerCase",
|
|
2144
|
+
"trim",
|
|
2145
|
+
"trimStart",
|
|
2146
|
+
"trimEnd",
|
|
2147
|
+
"split",
|
|
2148
|
+
"repeat",
|
|
2149
|
+
"padStart",
|
|
2150
|
+
"padEnd",
|
|
2151
|
+
"concat",
|
|
2152
|
+
"at",
|
|
2153
|
+
"replace",
|
|
2154
|
+
"replaceAll"
|
|
2155
|
+
]);
|
|
2156
|
+
NUMBER_MEMBERS = /* @__PURE__ */ new Set(["toFixed", "toPrecision", "toString"]);
|
|
2157
|
+
Scope = class {
|
|
2158
|
+
parent;
|
|
2159
|
+
isFunctionScope;
|
|
2160
|
+
vars = /* @__PURE__ */ new Map();
|
|
2161
|
+
consts = /* @__PURE__ */ new Set();
|
|
2162
|
+
constructor(parent, isFunctionScope = false) {
|
|
2163
|
+
this.parent = parent;
|
|
2164
|
+
this.isFunctionScope = isFunctionScope;
|
|
2165
|
+
}
|
|
2166
|
+
declare(name, value, kind) {
|
|
2167
|
+
if (kind === "var" && !this.isFunctionScope && this.parent) {
|
|
2168
|
+
this.parent.declare(name, value, kind);
|
|
2169
|
+
return;
|
|
2170
|
+
}
|
|
2171
|
+
this.vars.set(name, value);
|
|
2172
|
+
if (kind === "const")
|
|
2173
|
+
this.consts.add(name);
|
|
2174
|
+
}
|
|
2175
|
+
resolve(name) {
|
|
2176
|
+
if (this.vars.has(name))
|
|
2177
|
+
return this;
|
|
2178
|
+
return this.parent?.resolve(name);
|
|
2179
|
+
}
|
|
2180
|
+
globalScope() {
|
|
2181
|
+
return this.parent ? this.parent.globalScope() : this;
|
|
2182
|
+
}
|
|
2183
|
+
has(name) {
|
|
2184
|
+
return this.resolve(name) !== void 0;
|
|
2185
|
+
}
|
|
2186
|
+
get(name) {
|
|
2187
|
+
const s = this.resolve(name);
|
|
2188
|
+
if (!s)
|
|
2189
|
+
throw new Error(`${name} is not defined`);
|
|
2190
|
+
return s.vars.get(name);
|
|
2191
|
+
}
|
|
2192
|
+
set(name, value) {
|
|
2193
|
+
const s = this.resolve(name);
|
|
2194
|
+
if (!s) {
|
|
2195
|
+
this.globalScope().vars.set(name, value);
|
|
2196
|
+
return;
|
|
2197
|
+
}
|
|
2198
|
+
if (s.consts.has(name))
|
|
2199
|
+
throw new Error(`Assignment to constant variable "${name}"`);
|
|
2200
|
+
s.vars.set(name, value);
|
|
2201
|
+
}
|
|
2202
|
+
};
|
|
2203
|
+
}
|
|
2204
|
+
});
|
|
2205
|
+
|
|
1278
2206
|
// ../core/dist/tools/build-executor.js
|
|
1279
|
-
import * as vm from "vm";
|
|
1280
2207
|
function createSeededRng(seed) {
|
|
1281
2208
|
let s = seed;
|
|
1282
2209
|
return () => {
|
|
@@ -1626,6 +2553,7 @@ function runBuildExecutor(code, palette, seed, options) {
|
|
|
1626
2553
|
]);
|
|
1627
2554
|
}
|
|
1628
2555
|
const rng = createSeededRng(seed ?? 42);
|
|
2556
|
+
const safeMath = Object.freeze(Object.create(null, Object.getOwnPropertyDescriptors(Math)));
|
|
1629
2557
|
const sandbox = {
|
|
1630
2558
|
part: partFn,
|
|
1631
2559
|
rpart: rpartFn,
|
|
@@ -1642,25 +2570,21 @@ function runBuildExecutor(code, palette, seed, options) {
|
|
|
1642
2570
|
column: columnFn,
|
|
1643
2571
|
pew: pewFn,
|
|
1644
2572
|
fence: fenceFn,
|
|
1645
|
-
Math,
|
|
2573
|
+
Math: safeMath,
|
|
1646
2574
|
GRID_SIZE: 1,
|
|
1647
2575
|
rng,
|
|
1648
|
-
console: { log: () => {
|
|
2576
|
+
console: Object.freeze({ log: () => {
|
|
1649
2577
|
}, warn: () => {
|
|
1650
2578
|
}, error: () => {
|
|
1651
|
-
} }
|
|
2579
|
+
} })
|
|
1652
2580
|
};
|
|
1653
|
-
const context = vm.createContext(sandbox, {
|
|
1654
|
-
codeGeneration: { strings: false, wasm: false }
|
|
1655
|
-
});
|
|
1656
|
-
const script = new vm.Script(code, { filename: "build-generator.js" });
|
|
1657
2581
|
try {
|
|
1658
|
-
|
|
2582
|
+
runRestrictedScript(code, sandbox, { timeoutMs: timeout });
|
|
1659
2583
|
} catch (err) {
|
|
1660
|
-
if (err
|
|
2584
|
+
if (err instanceof ScriptTimeoutError) {
|
|
1661
2585
|
throw new Error(`Build code execution timed out after ${timeout}ms`);
|
|
1662
2586
|
}
|
|
1663
|
-
throw new Error(`Build code execution error: ${err
|
|
2587
|
+
throw new Error(`Build code execution error: ${err?.message ?? String(err)}`);
|
|
1664
2588
|
}
|
|
1665
2589
|
if (parts.length === 0) {
|
|
1666
2590
|
throw new Error("Build code produced no parts. Make sure to call part(), wall(), floor(), etc.");
|
|
@@ -1672,6 +2596,7 @@ var DEFAULT_TIMEOUT, DEFAULT_MAX_PARTS, VALID_SHAPES;
|
|
|
1672
2596
|
var init_build_executor = __esm({
|
|
1673
2597
|
"../core/dist/tools/build-executor.js"() {
|
|
1674
2598
|
"use strict";
|
|
2599
|
+
init_build_interpreter();
|
|
1675
2600
|
DEFAULT_TIMEOUT = 1e4;
|
|
1676
2601
|
DEFAULT_MAX_PARTS = 1e4;
|
|
1677
2602
|
VALID_SHAPES = /* @__PURE__ */ new Set(["Block", "Wedge", "Cylinder", "Ball", "CornerWedge"]);
|
|
@@ -2297,7 +3222,7 @@ var init_managed_instance_registry = __esm({
|
|
|
2297
3222
|
|
|
2298
3223
|
// ../core/dist/studio-instance-manager.js
|
|
2299
3224
|
import { execFileSync, spawn } from "child_process";
|
|
2300
|
-
import { copyFileSync, existsSync, mkdirSync as
|
|
3225
|
+
import { copyFileSync, existsSync as existsSync2, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync3, realpathSync, rmSync as rmSync2, statSync as statSync2 } from "fs";
|
|
2301
3226
|
import { randomUUID } from "crypto";
|
|
2302
3227
|
import * as os2 from "os";
|
|
2303
3228
|
import * as path2 from "path";
|
|
@@ -2312,14 +3237,14 @@ function isWsl() {
|
|
|
2312
3237
|
if (process.platform !== "linux")
|
|
2313
3238
|
return false;
|
|
2314
3239
|
try {
|
|
2315
|
-
return /microsoft|wsl/i.test(
|
|
3240
|
+
return /microsoft|wsl/i.test(readFileSync3("/proc/version", "utf8"));
|
|
2316
3241
|
} catch {
|
|
2317
3242
|
return false;
|
|
2318
3243
|
}
|
|
2319
3244
|
}
|
|
2320
3245
|
function powershell(script) {
|
|
2321
3246
|
return run("powershell.exe", ["-NoProfile", "-Command", script], {
|
|
2322
|
-
cwd: isWsl() &&
|
|
3247
|
+
cwd: isWsl() && existsSync2("/mnt/c/Windows") ? "/mnt/c/Windows" : process.cwd()
|
|
2323
3248
|
});
|
|
2324
3249
|
}
|
|
2325
3250
|
function windowsLocalAppData() {
|
|
@@ -2329,7 +3254,7 @@ function windowsLocalAppData() {
|
|
|
2329
3254
|
return void 0;
|
|
2330
3255
|
try {
|
|
2331
3256
|
return run("cmd.exe", ["/c", "echo %LOCALAPPDATA%"], {
|
|
2332
|
-
cwd:
|
|
3257
|
+
cwd: existsSync2("/mnt/c/Windows") ? "/mnt/c/Windows" : process.cwd()
|
|
2333
3258
|
});
|
|
2334
3259
|
} catch {
|
|
2335
3260
|
return void 0;
|
|
@@ -2341,7 +3266,7 @@ function toWslPath(windowsPath) {
|
|
|
2341
3266
|
return run("wslpath", ["-u", windowsPath]);
|
|
2342
3267
|
}
|
|
2343
3268
|
function toStudioLaunchArg(arg) {
|
|
2344
|
-
if (!isWsl() || !path2.isAbsolute(arg) || !
|
|
3269
|
+
if (!isWsl() || !path2.isAbsolute(arg) || !existsSync2(arg))
|
|
2345
3270
|
return arg;
|
|
2346
3271
|
return run("wslpath", ["-w", arg]);
|
|
2347
3272
|
}
|
|
@@ -2367,13 +3292,44 @@ function resolveBaseplateTemplatePath() {
|
|
|
2367
3292
|
path2.join(process.cwd(), "assets", BASEPLATE_TEMPLATE_NAME)
|
|
2368
3293
|
];
|
|
2369
3294
|
for (const candidate of candidates) {
|
|
2370
|
-
if (
|
|
3295
|
+
if (existsSync2(candidate))
|
|
2371
3296
|
return candidate;
|
|
2372
3297
|
}
|
|
2373
3298
|
throw new Error(`Baseplate template not found. Expected ${BASEPLATE_TEMPLATE_NAME} in one of: ${candidates.join(", ")}`);
|
|
2374
3299
|
}
|
|
3300
|
+
function isProcessAlive(pid) {
|
|
3301
|
+
try {
|
|
3302
|
+
process.kill(pid, 0);
|
|
3303
|
+
return true;
|
|
3304
|
+
} catch (err) {
|
|
3305
|
+
return err.code === "EPERM";
|
|
3306
|
+
}
|
|
3307
|
+
}
|
|
3308
|
+
function sweepStaleBaseplateFiles() {
|
|
3309
|
+
let entries;
|
|
3310
|
+
try {
|
|
3311
|
+
entries = readdirSync2(BASEPLATE_TEMP_DIR);
|
|
3312
|
+
} catch {
|
|
3313
|
+
return;
|
|
3314
|
+
}
|
|
3315
|
+
const cutoff = Date.now() - STALE_BASEPLATE_MAX_AGE_MS;
|
|
3316
|
+
for (const entry of entries) {
|
|
3317
|
+
const match = BASEPLATE_TEMP_SWEEP_NAME.exec(entry);
|
|
3318
|
+
if (!match)
|
|
3319
|
+
continue;
|
|
3320
|
+
if (Number(match[1]) !== process.pid && isProcessAlive(Number(match[1])))
|
|
3321
|
+
continue;
|
|
3322
|
+
const file = path2.join(BASEPLATE_TEMP_DIR, entry);
|
|
3323
|
+
try {
|
|
3324
|
+
if (statSync2(file).mtimeMs < cutoff)
|
|
3325
|
+
rmSync2(file, { force: true });
|
|
3326
|
+
} catch {
|
|
3327
|
+
}
|
|
3328
|
+
}
|
|
3329
|
+
}
|
|
2375
3330
|
function createBaseplatePlaceFile() {
|
|
2376
|
-
|
|
3331
|
+
mkdirSync3(BASEPLATE_TEMP_DIR, { recursive: true });
|
|
3332
|
+
sweepStaleBaseplateFiles();
|
|
2377
3333
|
const file = path2.join(BASEPLATE_TEMP_DIR, `Baseplate-${process.pid}-${Date.now()}.rbxl`);
|
|
2378
3334
|
copyFileSync(resolveBaseplateTemplatePath(), file);
|
|
2379
3335
|
return file;
|
|
@@ -2387,8 +3343,12 @@ function cleanupManagedBaseplateFiles(record) {
|
|
|
2387
3343
|
return;
|
|
2388
3344
|
if (!isGeneratedBaseplatePlaceFile(record.localPlaceFile))
|
|
2389
3345
|
return;
|
|
2390
|
-
|
|
2391
|
-
|
|
3346
|
+
for (const file of [record.localPlaceFile, `${record.localPlaceFile}.lock`]) {
|
|
3347
|
+
try {
|
|
3348
|
+
rmSync2(file, { force: true });
|
|
3349
|
+
} catch {
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
2392
3352
|
}
|
|
2393
3353
|
function prepareStudioLaunchOptions(options) {
|
|
2394
3354
|
if (options.source !== "baseplate" || options.localPlaceFile)
|
|
@@ -2409,10 +3369,10 @@ function resolveStudioExe() {
|
|
|
2409
3369
|
}
|
|
2410
3370
|
const localAppData = windowsLocalAppData();
|
|
2411
3371
|
const root = localAppData ? path2.join(toWslPath(localAppData), "Roblox", "Versions") : path2.join(os2.homedir(), "AppData", "Local", "Roblox", "Versions");
|
|
2412
|
-
if (!
|
|
3372
|
+
if (!existsSync2(root)) {
|
|
2413
3373
|
throw new Error(`Roblox Studio Versions folder not found: ${root}. Set ROBLOX_STUDIO_EXE.`);
|
|
2414
3374
|
}
|
|
2415
|
-
const candidates = readdirSync2(root).filter((name) => name.startsWith("version-")).map((name) => path2.join(root, name, "RobloxStudioBeta.exe")).filter((candidate) =>
|
|
3375
|
+
const candidates = readdirSync2(root).filter((name) => name.startsWith("version-")).map((name) => path2.join(root, name, "RobloxStudioBeta.exe")).filter((candidate) => existsSync2(candidate)).sort((a, b) => statSync2(b).mtimeMs - statSync2(a).mtimeMs);
|
|
2416
3376
|
if (candidates.length === 0) {
|
|
2417
3377
|
throw new Error(`RobloxStudioBeta.exe not found under ${root}. Set ROBLOX_STUDIO_EXE.`);
|
|
2418
3378
|
}
|
|
@@ -2447,7 +3407,7 @@ function listStudioProcesses() {
|
|
|
2447
3407
|
function currentBootId() {
|
|
2448
3408
|
if (process.platform === "linux") {
|
|
2449
3409
|
try {
|
|
2450
|
-
return
|
|
3410
|
+
return readFileSync3("/proc/sys/kernel/random/boot_id", "utf8").trim();
|
|
2451
3411
|
} catch {
|
|
2452
3412
|
}
|
|
2453
3413
|
}
|
|
@@ -2504,7 +3464,7 @@ function delay(ms) {
|
|
|
2504
3464
|
function basenameAny(filePath) {
|
|
2505
3465
|
return path2.basename(filePath.replace(/\\/g, "/"));
|
|
2506
3466
|
}
|
|
2507
|
-
var BASEPLATE_TEMP_DIR, BASEPLATE_TEMP_NAME, BASEPLATE_TEMPLATE_NAME, StudioInstanceManager;
|
|
3467
|
+
var BASEPLATE_TEMP_DIR, BASEPLATE_TEMP_NAME, BASEPLATE_TEMPLATE_NAME, STALE_BASEPLATE_MAX_AGE_MS, BASEPLATE_TEMP_SWEEP_NAME, StudioInstanceManager;
|
|
2508
3468
|
var init_studio_instance_manager = __esm({
|
|
2509
3469
|
"../core/dist/studio-instance-manager.js"() {
|
|
2510
3470
|
"use strict";
|
|
@@ -2512,6 +3472,8 @@ var init_studio_instance_manager = __esm({
|
|
|
2512
3472
|
BASEPLATE_TEMP_DIR = path2.join(os2.tmpdir(), "robloxstudio-mcp-baseplates");
|
|
2513
3473
|
BASEPLATE_TEMP_NAME = /^Baseplate-\d+-\d+\.rbxl$/;
|
|
2514
3474
|
BASEPLATE_TEMPLATE_NAME = "Baseplate.rbxl";
|
|
3475
|
+
STALE_BASEPLATE_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
3476
|
+
BASEPLATE_TEMP_SWEEP_NAME = /^Baseplate-(\d+)-\d+\.rbxl(\.lock)?$/;
|
|
2515
3477
|
StudioInstanceManager = class {
|
|
2516
3478
|
managedByInstanceId = /* @__PURE__ */ new Map();
|
|
2517
3479
|
pending = /* @__PURE__ */ new Set();
|
|
@@ -2556,7 +3518,7 @@ var init_studio_instance_manager = __esm({
|
|
|
2556
3518
|
const exe = this.processAdapter.resolveStudioExe?.() ?? resolveStudioExe();
|
|
2557
3519
|
const args = buildStudioLaunchArgs(preparedOptions).map(toStudioLaunchArg);
|
|
2558
3520
|
const spawnOptions = {
|
|
2559
|
-
cwd: isWsl() &&
|
|
3521
|
+
cwd: isWsl() && existsSync2("/mnt/c/Windows") ? "/mnt/c/Windows" : process.cwd(),
|
|
2560
3522
|
detached: true,
|
|
2561
3523
|
stdio: "ignore"
|
|
2562
3524
|
};
|
|
@@ -8107,21 +9069,31 @@ var init_proxy_bridge_service = __esm({
|
|
|
8107
9069
|
init_bridge_service();
|
|
8108
9070
|
ProxyBridgeService = class _ProxyBridgeService extends BridgeService {
|
|
8109
9071
|
primaryBaseUrl;
|
|
9072
|
+
authToken;
|
|
8110
9073
|
proxyInstanceId;
|
|
8111
9074
|
proxyRequestTimeout = 3e4;
|
|
8112
9075
|
cachedInstances = [];
|
|
8113
9076
|
refreshTimer;
|
|
8114
9077
|
static REFRESH_INTERVAL_MS = 1e3;
|
|
8115
|
-
constructor(primaryBaseUrl) {
|
|
9078
|
+
constructor(primaryBaseUrl, authToken) {
|
|
8116
9079
|
super();
|
|
8117
9080
|
this.primaryBaseUrl = primaryBaseUrl;
|
|
9081
|
+
this.authToken = authToken;
|
|
8118
9082
|
this.proxyInstanceId = uuidv42();
|
|
8119
9083
|
this.refreshInstances();
|
|
8120
9084
|
this.refreshTimer = setInterval(() => this.refreshInstances(), _ProxyBridgeService.REFRESH_INTERVAL_MS);
|
|
8121
9085
|
}
|
|
9086
|
+
authHeaders(extra) {
|
|
9087
|
+
const headers = { ...extra };
|
|
9088
|
+
if (this.authToken)
|
|
9089
|
+
headers["X-MCP-Auth"] = this.authToken;
|
|
9090
|
+
return headers;
|
|
9091
|
+
}
|
|
8122
9092
|
async refreshInstances() {
|
|
8123
9093
|
try {
|
|
8124
|
-
const res = await fetch(`${this.primaryBaseUrl}/instances
|
|
9094
|
+
const res = await fetch(`${this.primaryBaseUrl}/instances`, {
|
|
9095
|
+
headers: this.authHeaders()
|
|
9096
|
+
});
|
|
8125
9097
|
if (!res.ok)
|
|
8126
9098
|
return;
|
|
8127
9099
|
const body = await res.json();
|
|
@@ -8137,7 +9109,7 @@ var init_proxy_bridge_service = __esm({
|
|
|
8137
9109
|
async unregisterInstanceIdEverywhere(instanceId) {
|
|
8138
9110
|
const response = await fetch(`${this.primaryBaseUrl}/unregister-instance-id`, {
|
|
8139
9111
|
method: "POST",
|
|
8140
|
-
headers: { "Content-Type": "application/json" },
|
|
9112
|
+
headers: this.authHeaders({ "Content-Type": "application/json" }),
|
|
8141
9113
|
body: JSON.stringify({ instanceId })
|
|
8142
9114
|
});
|
|
8143
9115
|
if (!response.ok) {
|
|
@@ -8166,7 +9138,7 @@ var init_proxy_bridge_service = __esm({
|
|
|
8166
9138
|
try {
|
|
8167
9139
|
const response = await fetch(`${this.primaryBaseUrl}/proxy`, {
|
|
8168
9140
|
method: "POST",
|
|
8169
|
-
headers: { "Content-Type": "application/json" },
|
|
9141
|
+
headers: this.authHeaders({ "Content-Type": "application/json" }),
|
|
8170
9142
|
body: JSON.stringify({
|
|
8171
9143
|
endpoint,
|
|
8172
9144
|
data,
|
|
@@ -8211,6 +9183,7 @@ var init_server = __esm({
|
|
|
8211
9183
|
"../core/dist/server.js"() {
|
|
8212
9184
|
"use strict";
|
|
8213
9185
|
init_http_server();
|
|
9186
|
+
init_auth();
|
|
8214
9187
|
init_tools();
|
|
8215
9188
|
init_bridge_service();
|
|
8216
9189
|
init_proxy_bridge_service();
|
|
@@ -8280,14 +9253,28 @@ var init_server = __esm({
|
|
|
8280
9253
|
}
|
|
8281
9254
|
async run() {
|
|
8282
9255
|
const basePort = process.env.ROBLOX_STUDIO_PORT ? parseInt(process.env.ROBLOX_STUDIO_PORT) : 58741;
|
|
8283
|
-
const host = process.env.ROBLOX_STUDIO_HOST || "
|
|
9256
|
+
const host = process.env.ROBLOX_STUDIO_HOST?.trim() || "127.0.0.1";
|
|
9257
|
+
if (host !== "127.0.0.1" && host !== "localhost" && host !== "::1") {
|
|
9258
|
+
console.error(`WARNING: ROBLOX_STUDIO_HOST=${host} exposes the MCP bridge beyond this machine. Anyone who can reach the port and knows the auth token can control Roblox Studio.`);
|
|
9259
|
+
}
|
|
9260
|
+
const auth = resolveAuthToken();
|
|
9261
|
+
const security = {
|
|
9262
|
+
authToken: auth.token,
|
|
9263
|
+
authTokenHint: auth.source === "env" ? "The token comes from ROBLOX_STUDIO_AUTH_TOKEN." : auth.filePath ? `The token is in ${auth.filePath}.` : void 0,
|
|
9264
|
+
allowedOrigins: (process.env.ROBLOX_STUDIO_ALLOWED_ORIGINS || "").split(",").map((o) => o.trim()).filter((o) => o !== "")
|
|
9265
|
+
};
|
|
9266
|
+
if (auth.source === "disabled") {
|
|
9267
|
+
console.error("WARNING: ROBLOX_STUDIO_NO_AUTH is set - tool endpoints accept unauthenticated requests.");
|
|
9268
|
+
} else if (auth.filePath) {
|
|
9269
|
+
console.error(`Auth token loaded from ${auth.filePath} (HTTP clients must send X-MCP-Auth or Authorization: Bearer)`);
|
|
9270
|
+
}
|
|
8284
9271
|
let bridgeMode = "primary";
|
|
8285
9272
|
let httpHandle;
|
|
8286
9273
|
let primaryApp;
|
|
8287
9274
|
let boundPort = 0;
|
|
8288
9275
|
let promotionInterval;
|
|
8289
9276
|
try {
|
|
8290
|
-
primaryApp = createHttpServer(this.tools, this.bridge, this.allowedToolNames, this.config);
|
|
9277
|
+
primaryApp = createHttpServer(this.tools, this.bridge, this.allowedToolNames, this.config, security);
|
|
8291
9278
|
const result = await listenWithRetry(primaryApp, host, basePort, 1);
|
|
8292
9279
|
httpHandle = result.server;
|
|
8293
9280
|
boundPort = result.port;
|
|
@@ -8296,7 +9283,7 @@ var init_server = __esm({
|
|
|
8296
9283
|
} catch {
|
|
8297
9284
|
bridgeMode = "proxy";
|
|
8298
9285
|
primaryApp = void 0;
|
|
8299
|
-
const proxyBridge = new ProxyBridgeService(`http://localhost:${basePort}
|
|
9286
|
+
const proxyBridge = new ProxyBridgeService(`http://localhost:${basePort}`, auth.token);
|
|
8300
9287
|
this.bridge = proxyBridge;
|
|
8301
9288
|
this.tools = new RobloxStudioTools(this.bridge);
|
|
8302
9289
|
console.error(`Port ${basePort} in use - entering proxy mode (forwarding to localhost:${basePort})`);
|
|
@@ -8304,7 +9291,7 @@ var init_server = __esm({
|
|
|
8304
9291
|
promotionInterval = setInterval(async () => {
|
|
8305
9292
|
const candidateBridge = new BridgeService();
|
|
8306
9293
|
const candidateTools = new RobloxStudioTools(candidateBridge);
|
|
8307
|
-
const candidateApp = createHttpServer(candidateTools, candidateBridge, this.allowedToolNames, this.config);
|
|
9294
|
+
const candidateApp = createHttpServer(candidateTools, candidateBridge, this.allowedToolNames, this.config, security);
|
|
8308
9295
|
try {
|
|
8309
9296
|
const result = await listenWithRetry(candidateApp, host, basePort, 1);
|
|
8310
9297
|
const oldBridge = this.bridge;
|
|
@@ -8329,7 +9316,7 @@ var init_server = __esm({
|
|
|
8329
9316
|
let legacyHandle;
|
|
8330
9317
|
let legacyApp;
|
|
8331
9318
|
if (boundPort !== LEGACY_PORT && bridgeMode === "primary") {
|
|
8332
|
-
legacyApp = createHttpServer(this.tools, this.bridge, this.allowedToolNames, this.config);
|
|
9319
|
+
legacyApp = createHttpServer(this.tools, this.bridge, this.allowedToolNames, this.config, security);
|
|
8333
9320
|
try {
|
|
8334
9321
|
const result = await listenWithRetry(legacyApp, host, LEGACY_PORT, 1);
|
|
8335
9322
|
legacyHandle = result.server;
|
|
@@ -10186,7 +11173,7 @@ CYLINDER AXIS: Roblox cylinders extend along the X axis. For upright cylinders,
|
|
|
10186
11173
|
EXAMPLE - compact cabin (17 lines):
|
|
10187
11174
|
room(0,0,0,8,4,6,"a","b","a")
|
|
10188
11175
|
roof(0,4,0,8,6,"gable","c")
|
|
10189
|
-
wall(-4
|
|
11176
|
+
wall(-4,-2,4,-2,4,1,"a")
|
|
10190
11177
|
part(0,2,3,3,3,0.3,"a","Block",0.4)
|
|
10191
11178
|
row(-2,0,-1,3,0,2,(i,cx,cy,cz)=>{pew(cx,0,cz,3,2,"d")})
|
|
10192
11179
|
column(-3,0,-2,4,0.5,"a","b")
|
|
@@ -11164,15 +12151,15 @@ part(0,2,0,2,1,1,"b")`,
|
|
|
11164
12151
|
});
|
|
11165
12152
|
|
|
11166
12153
|
// ../core/dist/install-plugin-helpers.js
|
|
11167
|
-
import { existsSync as
|
|
12154
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6, unlinkSync } from "fs";
|
|
11168
12155
|
import { execSync } from "child_process";
|
|
11169
|
-
import { join as
|
|
11170
|
-
import { homedir as
|
|
12156
|
+
import { join as join5 } from "path";
|
|
12157
|
+
import { homedir as homedir5 } from "os";
|
|
11171
12158
|
function isWSL() {
|
|
11172
12159
|
if (process.platform !== "linux")
|
|
11173
12160
|
return false;
|
|
11174
12161
|
try {
|
|
11175
|
-
const v =
|
|
12162
|
+
const v = readFileSync6("/proc/version", "utf8");
|
|
11176
12163
|
return /microsoft|wsl/i.test(v);
|
|
11177
12164
|
} catch {
|
|
11178
12165
|
return false;
|
|
@@ -11190,7 +12177,7 @@ function getWindowsUserPluginsDir() {
|
|
|
11190
12177
|
}).toString().trim();
|
|
11191
12178
|
if (!linuxPath)
|
|
11192
12179
|
return null;
|
|
11193
|
-
return
|
|
12180
|
+
return join5(linuxPath, "Roblox", "Plugins");
|
|
11194
12181
|
} catch {
|
|
11195
12182
|
return null;
|
|
11196
12183
|
}
|
|
@@ -11199,7 +12186,7 @@ function getPluginsFolder() {
|
|
|
11199
12186
|
if (process.env.MCP_PLUGINS_DIR)
|
|
11200
12187
|
return process.env.MCP_PLUGINS_DIR;
|
|
11201
12188
|
if (process.platform === "win32") {
|
|
11202
|
-
return
|
|
12189
|
+
return join5(process.env.LOCALAPPDATA || join5(homedir5(), "AppData", "Local"), "Roblox", "Plugins");
|
|
11203
12190
|
}
|
|
11204
12191
|
if (isWSL()) {
|
|
11205
12192
|
const win = getWindowsUserPluginsDir();
|
|
@@ -11207,11 +12194,11 @@ function getPluginsFolder() {
|
|
|
11207
12194
|
return win;
|
|
11208
12195
|
console.warn("[install-plugin] WSL detected but could not resolve Windows %LOCALAPPDATA%. Falling back to ~/Documents/Roblox/Plugins/ - you will likely need to copy the rbxmx to /mnt/c/Users/<you>/AppData/Local/Roblox/Plugins/ manually. Set MCP_PLUGINS_DIR to skip detection.");
|
|
11209
12196
|
}
|
|
11210
|
-
return
|
|
12197
|
+
return join5(homedir5(), "Documents", "Roblox", "Plugins");
|
|
11211
12198
|
}
|
|
11212
12199
|
function handleVariantConflict({ pluginsFolder, otherAssetName, replace, log = console.log, warn = console.warn }) {
|
|
11213
|
-
const otherDest =
|
|
11214
|
-
if (!
|
|
12200
|
+
const otherDest = join5(pluginsFolder, otherAssetName);
|
|
12201
|
+
if (!existsSync5(otherDest))
|
|
11215
12202
|
return;
|
|
11216
12203
|
if (replace) {
|
|
11217
12204
|
try {
|
|
@@ -11256,8 +12243,8 @@ __export(install_plugin_exports, {
|
|
|
11256
12243
|
installBundledPlugin: () => installBundledPlugin,
|
|
11257
12244
|
installPlugin: () => installPlugin
|
|
11258
12245
|
});
|
|
11259
|
-
import { copyFileSync as copyFileSync2, createWriteStream, existsSync as
|
|
11260
|
-
import { dirname as
|
|
12246
|
+
import { copyFileSync as copyFileSync2, createWriteStream, existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync7, unlinkSync as unlinkSync2 } from "fs";
|
|
12247
|
+
import { dirname as dirname4, join as join6 } from "path";
|
|
11261
12248
|
import { fileURLToPath } from "url";
|
|
11262
12249
|
import { get } from "https";
|
|
11263
12250
|
function httpsGet(url) {
|
|
@@ -11317,8 +12304,8 @@ function prepareInstall({
|
|
|
11317
12304
|
warn
|
|
11318
12305
|
}) {
|
|
11319
12306
|
const pluginsFolder = getPluginsFolder();
|
|
11320
|
-
if (!
|
|
11321
|
-
|
|
12307
|
+
if (!existsSync6(pluginsFolder)) {
|
|
12308
|
+
mkdirSync5(pluginsFolder, { recursive: true });
|
|
11322
12309
|
}
|
|
11323
12310
|
handleVariantConflict({
|
|
11324
12311
|
pluginsFolder,
|
|
@@ -11330,23 +12317,23 @@ function prepareInstall({
|
|
|
11330
12317
|
return pluginsFolder;
|
|
11331
12318
|
}
|
|
11332
12319
|
function bundledAssetPath() {
|
|
11333
|
-
const currentDir =
|
|
12320
|
+
const currentDir = dirname4(fileURLToPath(import.meta.url));
|
|
11334
12321
|
const candidates = [
|
|
11335
|
-
|
|
11336
|
-
|
|
12322
|
+
join6(currentDir, "..", "studio-plugin", ASSET_NAME),
|
|
12323
|
+
join6(currentDir, "..", "..", "..", "studio-plugin", ASSET_NAME)
|
|
11337
12324
|
];
|
|
11338
|
-
return candidates.find((candidate) =>
|
|
12325
|
+
return candidates.find((candidate) => existsSync6(candidate)) ?? null;
|
|
11339
12326
|
}
|
|
11340
12327
|
function packageVersion() {
|
|
11341
|
-
const currentDir =
|
|
11342
|
-
const pkg = JSON.parse(
|
|
12328
|
+
const currentDir = dirname4(fileURLToPath(import.meta.url));
|
|
12329
|
+
const pkg = JSON.parse(readFileSync7(join6(currentDir, "..", "package.json"), "utf8"));
|
|
11343
12330
|
if (!pkg.version) {
|
|
11344
12331
|
throw new Error("Package version not found");
|
|
11345
12332
|
}
|
|
11346
12333
|
return pkg.version;
|
|
11347
12334
|
}
|
|
11348
12335
|
function bundledPluginVersion(source) {
|
|
11349
|
-
const match =
|
|
12336
|
+
const match = readFileSync7(source, "utf8").match(/local CURRENT_VERSION = "([^"]+)"/);
|
|
11350
12337
|
return match ? match[1] : null;
|
|
11351
12338
|
}
|
|
11352
12339
|
function assertBundledPluginVersion(source) {
|
|
@@ -11359,9 +12346,9 @@ function assertBundledPluginVersion(source) {
|
|
|
11359
12346
|
}
|
|
11360
12347
|
}
|
|
11361
12348
|
function filesMatch(a, b) {
|
|
11362
|
-
if (!
|
|
11363
|
-
const aBytes =
|
|
11364
|
-
const bBytes =
|
|
12349
|
+
if (!existsSync6(b)) return false;
|
|
12350
|
+
const aBytes = readFileSync7(a);
|
|
12351
|
+
const bBytes = readFileSync7(b);
|
|
11365
12352
|
return aBytes.length === bBytes.length && aBytes.equals(bBytes);
|
|
11366
12353
|
}
|
|
11367
12354
|
async function installBundledPlugin(options = {}) {
|
|
@@ -11374,7 +12361,7 @@ async function installBundledPlugin(options = {}) {
|
|
|
11374
12361
|
}
|
|
11375
12362
|
assertBundledPluginVersion(source);
|
|
11376
12363
|
const pluginsFolder = prepareInstall({ replaceVariant, log, warn });
|
|
11377
|
-
const dest =
|
|
12364
|
+
const dest = join6(pluginsFolder, ASSET_NAME);
|
|
11378
12365
|
if (filesMatch(source, dest)) return;
|
|
11379
12366
|
copyFileSync2(source, dest);
|
|
11380
12367
|
log(`Installed ${ASSET_NAME} to ${dest}`);
|
|
@@ -11387,7 +12374,7 @@ async function installPlugin(options = {}) {
|
|
|
11387
12374
|
const bundled = bundledAssetPath();
|
|
11388
12375
|
if (bundled) {
|
|
11389
12376
|
assertBundledPluginVersion(bundled);
|
|
11390
|
-
const dest2 =
|
|
12377
|
+
const dest2 = join6(pluginsFolder, ASSET_NAME);
|
|
11391
12378
|
if (filesMatch(bundled, dest2)) {
|
|
11392
12379
|
log(`${ASSET_NAME} already installed.`);
|
|
11393
12380
|
return;
|
|
@@ -11402,7 +12389,7 @@ async function installPlugin(options = {}) {
|
|
|
11402
12389
|
if (!asset) {
|
|
11403
12390
|
throw new Error(`${ASSET_NAME} not found in release ${release.tag_name}`);
|
|
11404
12391
|
}
|
|
11405
|
-
const dest =
|
|
12392
|
+
const dest = join6(pluginsFolder, ASSET_NAME);
|
|
11406
12393
|
log(`Downloading ${ASSET_NAME} from ${release.tag_name}...`);
|
|
11407
12394
|
await download(asset.browser_download_url, dest);
|
|
11408
12395
|
log(`Installed to ${dest}`);
|