@chrrxs/robloxstudio-mcp-inspector 2.9.0 → 2.9.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
CHANGED
|
@@ -1206,6 +1206,17 @@ function luaLongQuote(s) {
|
|
|
1206
1206
|
${s}
|
|
1207
1207
|
]${eq}]`;
|
|
1208
1208
|
}
|
|
1209
|
+
function parseBridgeResponse(response) {
|
|
1210
|
+
const r = response;
|
|
1211
|
+
if (r && typeof r.returnValue === "string") {
|
|
1212
|
+
try {
|
|
1213
|
+
const parsed = JSON.parse(r.returnValue);
|
|
1214
|
+
return JSON.stringify(parsed);
|
|
1215
|
+
} catch {
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
return JSON.stringify(response);
|
|
1219
|
+
}
|
|
1209
1220
|
var SERVER_LOCAL_NAME, CLIENT_LOCAL_NAME, RobloxStudioTools;
|
|
1210
1221
|
var init_tools = __esm({
|
|
1211
1222
|
"../core/dist/tools/index.js"() {
|
|
@@ -1755,27 +1766,28 @@ ${code}`
|
|
|
1755
1766
|
throw new Error("Code is required for eval_server_runtime");
|
|
1756
1767
|
}
|
|
1757
1768
|
const wrapper = `
|
|
1769
|
+
local HttpService = game:GetService("HttpService")
|
|
1758
1770
|
local bf = game:GetService("ServerScriptService"):FindFirstChild("${SERVER_LOCAL_NAME}")
|
|
1759
1771
|
if not bf then
|
|
1760
|
-
return {
|
|
1772
|
+
return HttpService:JSONEncode({
|
|
1761
1773
|
bridge = "missing",
|
|
1762
1774
|
error = "ServerEvalBridge not installed. Bridges are auto-installed at start_playtest and removed at stop_playtest. Start a playtest before calling eval_server_runtime.",
|
|
1763
|
-
}
|
|
1775
|
+
})
|
|
1764
1776
|
end
|
|
1765
1777
|
local USER_CODE = ${luaLongQuote(code)}
|
|
1766
1778
|
local ok, result = bf:Invoke(USER_CODE)
|
|
1767
|
-
return {
|
|
1779
|
+
return HttpService:JSONEncode({
|
|
1768
1780
|
bridge = "ok",
|
|
1769
1781
|
ok = ok,
|
|
1770
1782
|
result = if result == nil then nil else tostring(result),
|
|
1771
|
-
}
|
|
1783
|
+
})
|
|
1772
1784
|
`;
|
|
1773
1785
|
const response = await this.client.request("/api/execute-luau", { code: wrapper }, "server");
|
|
1774
1786
|
return {
|
|
1775
1787
|
content: [
|
|
1776
1788
|
{
|
|
1777
1789
|
type: "text",
|
|
1778
|
-
text:
|
|
1790
|
+
text: parseBridgeResponse(response)
|
|
1779
1791
|
}
|
|
1780
1792
|
]
|
|
1781
1793
|
};
|
|
@@ -1789,12 +1801,13 @@ return {
|
|
|
1789
1801
|
throw new Error(`eval_client_runtime requires target=client-N (got: ${clientTarget})`);
|
|
1790
1802
|
}
|
|
1791
1803
|
const wrapper = `
|
|
1804
|
+
local HttpService = game:GetService("HttpService")
|
|
1792
1805
|
local bf = game:GetService("ReplicatedStorage"):FindFirstChild("${CLIENT_LOCAL_NAME}")
|
|
1793
1806
|
if not bf then
|
|
1794
|
-
return {
|
|
1807
|
+
return HttpService:JSONEncode({
|
|
1795
1808
|
bridge = "missing",
|
|
1796
1809
|
error = "ClientEvalBridge not installed. Bridges are auto-installed at start_playtest and removed at stop_playtest. Start a playtest before calling eval_client_runtime.",
|
|
1797
|
-
}
|
|
1810
|
+
})
|
|
1798
1811
|
end
|
|
1799
1812
|
local USER_CODE = ${luaLongQuote(code)}
|
|
1800
1813
|
local m = Instance.new("ModuleScript")
|
|
@@ -1802,23 +1815,23 @@ m.Name = "__MCPEvalPayload"
|
|
|
1802
1815
|
local okSet, setErr = pcall(function() m.Source = USER_CODE end)
|
|
1803
1816
|
if not okSet then
|
|
1804
1817
|
m:Destroy()
|
|
1805
|
-
return { bridge = "ok", ok = false, result = "ModuleScript Source set failed: " .. tostring(setErr) }
|
|
1818
|
+
return HttpService:JSONEncode({ bridge = "ok", ok = false, result = "ModuleScript Source set failed: " .. tostring(setErr) })
|
|
1806
1819
|
end
|
|
1807
1820
|
m.Parent = workspace
|
|
1808
1821
|
local ok, result = bf:Invoke(m)
|
|
1809
1822
|
m:Destroy()
|
|
1810
|
-
return {
|
|
1823
|
+
return HttpService:JSONEncode({
|
|
1811
1824
|
bridge = "ok",
|
|
1812
1825
|
ok = ok,
|
|
1813
1826
|
result = if result == nil then nil else tostring(result),
|
|
1814
|
-
}
|
|
1827
|
+
})
|
|
1815
1828
|
`;
|
|
1816
1829
|
const response = await this.client.request("/api/execute-luau", { code: wrapper }, clientTarget);
|
|
1817
1830
|
return {
|
|
1818
1831
|
content: [
|
|
1819
1832
|
{
|
|
1820
1833
|
type: "text",
|
|
1821
|
-
text:
|
|
1834
|
+
text: parseBridgeResponse(response)
|
|
1822
1835
|
}
|
|
1823
1836
|
]
|
|
1824
1837
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chrrxs/robloxstudio-mcp-inspector",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.1",
|
|
4
4
|
"description": "Read-only MCP Server for Roblox Studio (fork of boshyxd/robloxstudio-mcp-inspector with per-peer execute_luau fixes baked in)",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -796,7 +796,18 @@ local TS = require(script.Parent.Parent.include.RuntimeLib)
|
|
|
796
796
|
-- DataModel into the play DMs, so the scripts come along and run there.
|
|
797
797
|
-- TestHandlers cleans them up from the edit DM when ExecutePlayModeAsync
|
|
798
798
|
-- returns (test ended for any reason: stop_playtest, manual close, EndTest).
|
|
799
|
-
--
|
|
799
|
+
--
|
|
800
|
+
-- Archivable handling: ExecutePlayModeAsync's deep-clone SKIPS instances
|
|
801
|
+
-- with Archivable=false (verified empirically in v2.9.0 testing - bridges
|
|
802
|
+
-- never reached the play DMs because we'd set them to false). We now keep
|
|
803
|
+
-- Archivable=true so the clone works, and rely on cleanupBridges() to
|
|
804
|
+
-- remove the scripts from the edit DM when the test ends. The only failure
|
|
805
|
+
-- mode is the user saving DURING an active playtest, which would persist
|
|
806
|
+
-- the bridges to the .rbxl - that's a no-op next session because
|
|
807
|
+
-- installBridges() always calls cleanupBridges() first to clear stale
|
|
808
|
+
-- instances. The RemoteFunction/BindableFunction that the bridge scripts
|
|
809
|
+
-- CREATE at runtime stay Archivable=false (they're runtime-only and should
|
|
810
|
+
-- never appear in a save).
|
|
800
811
|
local _services = TS.import(script, script.Parent.Parent, "node_modules", "@rbxts", "services")
|
|
801
812
|
local ServerScriptService = _services.ServerScriptService
|
|
802
813
|
local StarterPlayer = _services.StarterPlayer
|
|
@@ -944,7 +955,9 @@ local function installBridges()
|
|
|
944
955
|
local ok, err = pcall(function()
|
|
945
956
|
local serverScript = Instance.new("Script")
|
|
946
957
|
serverScript.Name = SERVER_SCRIPT_NAME
|
|
947
|
-
|
|
958
|
+
-- Archivable=true so ExecutePlayModeAsync's deep-clone includes the
|
|
959
|
+
-- script. cleanupBridges() removes it from the edit DM when the
|
|
960
|
+
-- playtest ends.
|
|
948
961
|
setSource(serverScript, SERVER_BRIDGE_SOURCE)
|
|
949
962
|
serverScript.Parent = ServerScriptService
|
|
950
963
|
local sps = getStarterPlayerScripts()
|
|
@@ -953,7 +966,6 @@ local function installBridges()
|
|
|
953
966
|
end
|
|
954
967
|
local clientScript = Instance.new("LocalScript")
|
|
955
968
|
clientScript.Name = CLIENT_SCRIPT_NAME
|
|
956
|
-
clientScript.Archivable = false
|
|
957
969
|
setSource(clientScript, CLIENT_BRIDGE_SOURCE)
|
|
958
970
|
clientScript.Parent = sps
|
|
959
971
|
end)
|
|
@@ -3127,13 +3139,56 @@ local function executeLuau(requestData)
|
|
|
3127
3139
|
table.insert(output, _arg0)
|
|
3128
3140
|
oldWarn(unpack(args))
|
|
3129
3141
|
end
|
|
3142
|
+
-- Try loadstring first (preserves print/warn interception). When
|
|
3143
|
+
-- ServerScriptService.LoadStringEnabled=false AND the plugin runs in a
|
|
3144
|
+
-- peer where the engine respects that gate (notably the play-server DM
|
|
3145
|
+
-- in some Studio configurations), loadstring either returns nil with a
|
|
3146
|
+
-- "loadstring() is not available" message OR throws that same message
|
|
3147
|
+
-- directly. Both paths must trigger the ModuleScript + require
|
|
3148
|
+
-- fallback. The fallback can't intercept print/warn since the
|
|
3149
|
+
-- ModuleScript runs in its own environment, so the output array stays
|
|
3150
|
+
-- empty in that branch - the playtest log buffer already captures
|
|
3151
|
+
-- prints separately via LogService.MessageOut.
|
|
3152
|
+
local runViaModuleScript = function()
|
|
3153
|
+
local m = Instance.new("ModuleScript")
|
|
3154
|
+
m.Name = "__MCPExecLuauPayload"
|
|
3155
|
+
local okSet, setErr = pcall(function()
|
|
3156
|
+
m.Source = code
|
|
3157
|
+
end)
|
|
3158
|
+
if not okSet then
|
|
3159
|
+
m:Destroy()
|
|
3160
|
+
error(`ModuleScript Source set failed: {tostring(setErr)}`)
|
|
3161
|
+
end
|
|
3162
|
+
m.Parent = game:GetService("Workspace")
|
|
3163
|
+
local okReq, reqResult = pcall(function()
|
|
3164
|
+
return require(m)
|
|
3165
|
+
end)
|
|
3166
|
+
m:Destroy()
|
|
3167
|
+
if not okReq then
|
|
3168
|
+
error(tostring(reqResult))
|
|
3169
|
+
end
|
|
3170
|
+
return reqResult
|
|
3171
|
+
end
|
|
3172
|
+
local isLoadstringUnavailable = function(err)
|
|
3173
|
+
local errStr = tostring(err)
|
|
3174
|
+
local matchStart = string.find(errStr, "not available", 1, true)
|
|
3175
|
+
return matchStart ~= nil
|
|
3176
|
+
end
|
|
3130
3177
|
local success, result = pcall(function()
|
|
3131
3178
|
local fn, compileError = loadstring(code)
|
|
3132
3179
|
if not fn then
|
|
3180
|
+
if isLoadstringUnavailable(compileError) then
|
|
3181
|
+
return runViaModuleScript()
|
|
3182
|
+
end
|
|
3133
3183
|
error(`Compile error: {compileError}`)
|
|
3134
3184
|
end
|
|
3135
3185
|
return fn()
|
|
3136
3186
|
end)
|
|
3187
|
+
-- loadstring throws (not returns nil) in some plugin contexts when
|
|
3188
|
+
-- LoadStringEnabled=false. Catch that here as a second-chance fallback.
|
|
3189
|
+
if not success and isLoadstringUnavailable(result) then
|
|
3190
|
+
success, result = pcall(runViaModuleScript)
|
|
3191
|
+
end
|
|
3137
3192
|
env.print = oldPrint
|
|
3138
3193
|
env.warn = oldWarn
|
|
3139
3194
|
if success then
|
|
@@ -5579,7 +5634,7 @@ return {
|
|
|
5579
5634
|
<Properties>
|
|
5580
5635
|
<string name="Name">State</string>
|
|
5581
5636
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
5582
|
-
local CURRENT_VERSION = "2.9.
|
|
5637
|
+
local CURRENT_VERSION = "2.9.1"
|
|
5583
5638
|
local MAX_CONNECTIONS = 5
|
|
5584
5639
|
local BASE_PORT = 58741
|
|
5585
5640
|
local activeTabIndex = 0
|
|
@@ -796,7 +796,18 @@ local TS = require(script.Parent.Parent.include.RuntimeLib)
|
|
|
796
796
|
-- DataModel into the play DMs, so the scripts come along and run there.
|
|
797
797
|
-- TestHandlers cleans them up from the edit DM when ExecutePlayModeAsync
|
|
798
798
|
-- returns (test ended for any reason: stop_playtest, manual close, EndTest).
|
|
799
|
-
--
|
|
799
|
+
--
|
|
800
|
+
-- Archivable handling: ExecutePlayModeAsync's deep-clone SKIPS instances
|
|
801
|
+
-- with Archivable=false (verified empirically in v2.9.0 testing - bridges
|
|
802
|
+
-- never reached the play DMs because we'd set them to false). We now keep
|
|
803
|
+
-- Archivable=true so the clone works, and rely on cleanupBridges() to
|
|
804
|
+
-- remove the scripts from the edit DM when the test ends. The only failure
|
|
805
|
+
-- mode is the user saving DURING an active playtest, which would persist
|
|
806
|
+
-- the bridges to the .rbxl - that's a no-op next session because
|
|
807
|
+
-- installBridges() always calls cleanupBridges() first to clear stale
|
|
808
|
+
-- instances. The RemoteFunction/BindableFunction that the bridge scripts
|
|
809
|
+
-- CREATE at runtime stay Archivable=false (they're runtime-only and should
|
|
810
|
+
-- never appear in a save).
|
|
800
811
|
local _services = TS.import(script, script.Parent.Parent, "node_modules", "@rbxts", "services")
|
|
801
812
|
local ServerScriptService = _services.ServerScriptService
|
|
802
813
|
local StarterPlayer = _services.StarterPlayer
|
|
@@ -944,7 +955,9 @@ local function installBridges()
|
|
|
944
955
|
local ok, err = pcall(function()
|
|
945
956
|
local serverScript = Instance.new("Script")
|
|
946
957
|
serverScript.Name = SERVER_SCRIPT_NAME
|
|
947
|
-
|
|
958
|
+
-- Archivable=true so ExecutePlayModeAsync's deep-clone includes the
|
|
959
|
+
-- script. cleanupBridges() removes it from the edit DM when the
|
|
960
|
+
-- playtest ends.
|
|
948
961
|
setSource(serverScript, SERVER_BRIDGE_SOURCE)
|
|
949
962
|
serverScript.Parent = ServerScriptService
|
|
950
963
|
local sps = getStarterPlayerScripts()
|
|
@@ -953,7 +966,6 @@ local function installBridges()
|
|
|
953
966
|
end
|
|
954
967
|
local clientScript = Instance.new("LocalScript")
|
|
955
968
|
clientScript.Name = CLIENT_SCRIPT_NAME
|
|
956
|
-
clientScript.Archivable = false
|
|
957
969
|
setSource(clientScript, CLIENT_BRIDGE_SOURCE)
|
|
958
970
|
clientScript.Parent = sps
|
|
959
971
|
end)
|
|
@@ -3127,13 +3139,56 @@ local function executeLuau(requestData)
|
|
|
3127
3139
|
table.insert(output, _arg0)
|
|
3128
3140
|
oldWarn(unpack(args))
|
|
3129
3141
|
end
|
|
3142
|
+
-- Try loadstring first (preserves print/warn interception). When
|
|
3143
|
+
-- ServerScriptService.LoadStringEnabled=false AND the plugin runs in a
|
|
3144
|
+
-- peer where the engine respects that gate (notably the play-server DM
|
|
3145
|
+
-- in some Studio configurations), loadstring either returns nil with a
|
|
3146
|
+
-- "loadstring() is not available" message OR throws that same message
|
|
3147
|
+
-- directly. Both paths must trigger the ModuleScript + require
|
|
3148
|
+
-- fallback. The fallback can't intercept print/warn since the
|
|
3149
|
+
-- ModuleScript runs in its own environment, so the output array stays
|
|
3150
|
+
-- empty in that branch - the playtest log buffer already captures
|
|
3151
|
+
-- prints separately via LogService.MessageOut.
|
|
3152
|
+
local runViaModuleScript = function()
|
|
3153
|
+
local m = Instance.new("ModuleScript")
|
|
3154
|
+
m.Name = "__MCPExecLuauPayload"
|
|
3155
|
+
local okSet, setErr = pcall(function()
|
|
3156
|
+
m.Source = code
|
|
3157
|
+
end)
|
|
3158
|
+
if not okSet then
|
|
3159
|
+
m:Destroy()
|
|
3160
|
+
error(`ModuleScript Source set failed: {tostring(setErr)}`)
|
|
3161
|
+
end
|
|
3162
|
+
m.Parent = game:GetService("Workspace")
|
|
3163
|
+
local okReq, reqResult = pcall(function()
|
|
3164
|
+
return require(m)
|
|
3165
|
+
end)
|
|
3166
|
+
m:Destroy()
|
|
3167
|
+
if not okReq then
|
|
3168
|
+
error(tostring(reqResult))
|
|
3169
|
+
end
|
|
3170
|
+
return reqResult
|
|
3171
|
+
end
|
|
3172
|
+
local isLoadstringUnavailable = function(err)
|
|
3173
|
+
local errStr = tostring(err)
|
|
3174
|
+
local matchStart = string.find(errStr, "not available", 1, true)
|
|
3175
|
+
return matchStart ~= nil
|
|
3176
|
+
end
|
|
3130
3177
|
local success, result = pcall(function()
|
|
3131
3178
|
local fn, compileError = loadstring(code)
|
|
3132
3179
|
if not fn then
|
|
3180
|
+
if isLoadstringUnavailable(compileError) then
|
|
3181
|
+
return runViaModuleScript()
|
|
3182
|
+
end
|
|
3133
3183
|
error(`Compile error: {compileError}`)
|
|
3134
3184
|
end
|
|
3135
3185
|
return fn()
|
|
3136
3186
|
end)
|
|
3187
|
+
-- loadstring throws (not returns nil) in some plugin contexts when
|
|
3188
|
+
-- LoadStringEnabled=false. Catch that here as a second-chance fallback.
|
|
3189
|
+
if not success and isLoadstringUnavailable(result) then
|
|
3190
|
+
success, result = pcall(runViaModuleScript)
|
|
3191
|
+
end
|
|
3137
3192
|
env.print = oldPrint
|
|
3138
3193
|
env.warn = oldWarn
|
|
3139
3194
|
if success then
|
|
@@ -5579,7 +5634,7 @@ return {
|
|
|
5579
5634
|
<Properties>
|
|
5580
5635
|
<string name="Name">State</string>
|
|
5581
5636
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
5582
|
-
local CURRENT_VERSION = "2.9.
|
|
5637
|
+
local CURRENT_VERSION = "2.9.1"
|
|
5583
5638
|
local MAX_CONNECTIONS = 5
|
|
5584
5639
|
local BASE_PORT = 58741
|
|
5585
5640
|
local activeTabIndex = 0
|
|
@@ -19,7 +19,18 @@
|
|
|
19
19
|
// DataModel into the play DMs, so the scripts come along and run there.
|
|
20
20
|
// TestHandlers cleans them up from the edit DM when ExecutePlayModeAsync
|
|
21
21
|
// returns (test ended for any reason: stop_playtest, manual close, EndTest).
|
|
22
|
-
//
|
|
22
|
+
//
|
|
23
|
+
// Archivable handling: ExecutePlayModeAsync's deep-clone SKIPS instances
|
|
24
|
+
// with Archivable=false (verified empirically in v2.9.0 testing - bridges
|
|
25
|
+
// never reached the play DMs because we'd set them to false). We now keep
|
|
26
|
+
// Archivable=true so the clone works, and rely on cleanupBridges() to
|
|
27
|
+
// remove the scripts from the edit DM when the test ends. The only failure
|
|
28
|
+
// mode is the user saving DURING an active playtest, which would persist
|
|
29
|
+
// the bridges to the .rbxl - that's a no-op next session because
|
|
30
|
+
// installBridges() always calls cleanupBridges() first to clear stale
|
|
31
|
+
// instances. The RemoteFunction/BindableFunction that the bridge scripts
|
|
32
|
+
// CREATE at runtime stay Archivable=false (they're runtime-only and should
|
|
33
|
+
// never appear in a save).
|
|
23
34
|
|
|
24
35
|
import { ServerScriptService, StarterPlayer } from "@rbxts/services";
|
|
25
36
|
|
|
@@ -169,7 +180,9 @@ export function installBridges(): { installed: boolean; error?: string } {
|
|
|
169
180
|
const [ok, err] = pcall(() => {
|
|
170
181
|
const serverScript = new Instance("Script");
|
|
171
182
|
serverScript.Name = SERVER_SCRIPT_NAME;
|
|
172
|
-
|
|
183
|
+
// Archivable=true so ExecutePlayModeAsync's deep-clone includes the
|
|
184
|
+
// script. cleanupBridges() removes it from the edit DM when the
|
|
185
|
+
// playtest ends.
|
|
173
186
|
setSource(serverScript, SERVER_BRIDGE_SOURCE);
|
|
174
187
|
serverScript.Parent = ServerScriptService;
|
|
175
188
|
|
|
@@ -179,7 +192,6 @@ export function installBridges(): { installed: boolean; error?: string } {
|
|
|
179
192
|
}
|
|
180
193
|
const clientScript = new Instance("LocalScript");
|
|
181
194
|
clientScript.Name = CLIENT_SCRIPT_NAME;
|
|
182
|
-
clientScript.Archivable = false;
|
|
183
195
|
setSource(clientScript, CLIENT_BRIDGE_SOURCE);
|
|
184
196
|
clientScript.Parent = sps;
|
|
185
197
|
});
|
|
@@ -281,12 +281,56 @@ function executeLuau(requestData: Record<string, unknown>) {
|
|
|
281
281
|
oldWarn(...(args as [defined, ...defined[]]));
|
|
282
282
|
};
|
|
283
283
|
|
|
284
|
-
|
|
284
|
+
// Try loadstring first (preserves print/warn interception). When
|
|
285
|
+
// ServerScriptService.LoadStringEnabled=false AND the plugin runs in a
|
|
286
|
+
// peer where the engine respects that gate (notably the play-server DM
|
|
287
|
+
// in some Studio configurations), loadstring either returns nil with a
|
|
288
|
+
// "loadstring() is not available" message OR throws that same message
|
|
289
|
+
// directly. Both paths must trigger the ModuleScript + require
|
|
290
|
+
// fallback. The fallback can't intercept print/warn since the
|
|
291
|
+
// ModuleScript runs in its own environment, so the output array stays
|
|
292
|
+
// empty in that branch - the playtest log buffer already captures
|
|
293
|
+
// prints separately via LogService.MessageOut.
|
|
294
|
+
const runViaModuleScript = () => {
|
|
295
|
+
const m = new Instance("ModuleScript");
|
|
296
|
+
m.Name = "__MCPExecLuauPayload";
|
|
297
|
+
const [okSet, setErr] = pcall(() => {
|
|
298
|
+
(m as unknown as { Source: string }).Source = code;
|
|
299
|
+
});
|
|
300
|
+
if (!okSet) {
|
|
301
|
+
m.Destroy();
|
|
302
|
+
error(`ModuleScript Source set failed: ${tostring(setErr)}`);
|
|
303
|
+
}
|
|
304
|
+
m.Parent = game.GetService("Workspace");
|
|
305
|
+
const [okReq, reqResult] = pcall(() => require(m));
|
|
306
|
+
m.Destroy();
|
|
307
|
+
if (!okReq) error(tostring(reqResult));
|
|
308
|
+
return reqResult;
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
const isLoadstringUnavailable = (err: unknown): boolean => {
|
|
312
|
+
const errStr = tostring(err);
|
|
313
|
+
const [matchStart] = string.find(errStr, "not available", 1, true);
|
|
314
|
+
return matchStart !== undefined;
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
let [success, result] = pcall(() => {
|
|
285
318
|
const [fn, compileError] = loadstring(code);
|
|
286
|
-
if (!fn)
|
|
319
|
+
if (!fn) {
|
|
320
|
+
if (isLoadstringUnavailable(compileError)) {
|
|
321
|
+
return runViaModuleScript();
|
|
322
|
+
}
|
|
323
|
+
error(`Compile error: ${compileError}`);
|
|
324
|
+
}
|
|
287
325
|
return fn();
|
|
288
326
|
});
|
|
289
327
|
|
|
328
|
+
// loadstring throws (not returns nil) in some plugin contexts when
|
|
329
|
+
// LoadStringEnabled=false. Catch that here as a second-chance fallback.
|
|
330
|
+
if (!success && isLoadstringUnavailable(result)) {
|
|
331
|
+
[success, result] = pcall(runViaModuleScript);
|
|
332
|
+
}
|
|
333
|
+
|
|
290
334
|
env["print"] = oldPrint;
|
|
291
335
|
env["warn"] = oldWarn;
|
|
292
336
|
|