roport 1.3.1 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roport",
3
- "version": "1.3.1",
3
+ "version": "1.3.2",
4
4
  "description": "A sync server for Roblox development. Works with the Roport Roblox Plugin. Features AI integration and full project sync.",
5
5
  "main": "src/server.js",
6
6
  "bin": {
@@ -602,7 +602,7 @@ local function pullChanges()
602
602
  if data.changes and #data.changes > 0 then
603
603
  print("Roport: Pulling " .. #data.changes .. " files...")
604
604
  for _, change in ipairs(data.changes) do
605
- applyUpdate(change.filePath, change.content)
605
+ applyUpdate(change.filePath, change.content, change.absolutePath)
606
606
  end
607
607
  showToast("Updated " .. #data.changes .. " files", "success")
608
608
  else
@@ -746,7 +746,7 @@ end
746
746
  -- Two-Way Sync Logic
747
747
  local function findInstanceByPath(path)
748
748
  -- Remove extension
749
- local cleanPath = path:gsub("%.server%.luau$", ""):gsub("%.client%.luau$", ""):gsub("%.luau$", ""):gsub("/init%.meta%.json$", "")
749
+ local cleanPath = path:gsub("%.server%.luau$", ""):gsub("%.client%.luau$", ""):gsub("%.luau$", ""):gsub("%.server%.lua$", ""):gsub("%.client%.lua$", ""):gsub("%.lua$", ""):gsub("/init%.meta%.json$", "")
750
750
 
751
751
  local parts = cleanPath:split("/")
752
752
  if parts[1] ~= "src" then return nil end
@@ -779,7 +779,7 @@ local function findInstanceByPath(path)
779
779
  end
780
780
 
781
781
  local function createInstanceByPath(path, content)
782
- local cleanPath = path:gsub("%.server%.luau$", ""):gsub("%.client%.luau$", ""):gsub("%.luau$", ""):gsub("/init%.meta%.json$", "")
782
+ local cleanPath = path:gsub("%.server%.luau$", ""):gsub("%.client%.luau$", ""):gsub("%.luau$", ""):gsub("%.server%.lua$", ""):gsub("%.client%.lua$", ""):gsub("%.lua$", ""):gsub("/init%.meta%.json$", "")
783
783
  local parts = cleanPath:split("/")
784
784
  if parts[1] ~= "src" then return nil end
785
785
 
@@ -807,9 +807,9 @@ local function createInstanceByPath(path, content)
807
807
  local className = "Folder"
808
808
 
809
809
  if isLast then
810
- if path:match("%.server%.luau$") then className = "Script"
811
- elseif path:match("%.client%.luau$") then className = "LocalScript"
812
- elseif path:match("%.luau$") then className = "ModuleScript"
810
+ if path:match("%.server%.luau$") or path:match("%.server%.lua$") then className = "Script"
811
+ elseif path:match("%.client%.luau$") or path:match("%.client%.lua$") then className = "LocalScript"
812
+ elseif path:match("%.luau$") or path:match("%.lua$") then className = "ModuleScript"
813
813
  elseif path:match("%.json$") then
814
814
  -- Try to peek className from content
815
815
  local success, data = pcall(function() return HttpService:JSONDecode(content) end)
@@ -834,7 +834,47 @@ local function createInstanceByPath(path, content)
834
834
  return current
835
835
  end
836
836
 
837
- local function applyUpdate(filePath, content)
837
+ local function applyUpdate(filePath, content, absolutePath)
838
+ -- Handle RBXMX
839
+ if filePath:match("%.rbxmx$") and absolutePath then
840
+ local success, model = pcall(function()
841
+ return game:GetService("InsertService"):LoadLocalAsset(absolutePath)
842
+ end)
843
+
844
+ if success and model then
845
+ local parentPath = filePath:match("(.+)/[^/]+$")
846
+ local parentInst = findInstanceByPath(parentPath or "")
847
+
848
+ if not parentInst and parentPath then
849
+ parentInst = createInstanceByPath(parentPath, "")
850
+ end
851
+
852
+ if parentInst then
853
+ local children = model:GetChildren()
854
+ if #children > 0 then
855
+ local newObj = children[1]
856
+ local name = filePath:match("([^/]+)%.rbxmx$")
857
+ local existing = parentInst:FindFirstChild(name)
858
+
859
+ if existing then
860
+ newObj.Name = existing.Name
861
+ newObj.Parent = existing.Parent
862
+ existing:Destroy()
863
+ else
864
+ newObj.Name = name
865
+ newObj.Parent = parentInst
866
+ end
867
+ print("Roport: Imported " .. newObj.Name .. " from " .. filePath)
868
+ end
869
+ else
870
+ warn("Roport: Could not find parent for " .. filePath)
871
+ end
872
+ else
873
+ warn("Roport: Failed to load RBXMX: " .. tostring(model))
874
+ end
875
+ return
876
+ end
877
+
838
878
  local inst = findInstanceByPath(filePath)
839
879
  if not inst then
840
880
  inst = createInstanceByPath(filePath, content)
@@ -894,7 +934,7 @@ task.spawn(function()
894
934
  if data.changes and #data.changes > 0 then
895
935
  print("Roport: Received " .. #data.changes .. " updates from VS Code")
896
936
  for _, change in ipairs(data.changes) do
897
- applyUpdate(change.filePath, change.content)
937
+ applyUpdate(change.filePath, change.content, change.absolutePath)
898
938
  end
899
939
  end
900
940
  else
package/src/server.js CHANGED
@@ -195,9 +195,9 @@ function startServer(port) {
195
195
  const stat = await fs.stat(fullPath);
196
196
  if (stat.isFile()) {
197
197
  const content = await fs.readFile(fullPath, 'utf8');
198
- response.changes.push({ filePath, content });
198
+ response.changes.push({ filePath, absolutePath: fullPath, content });
199
199
  } else if (stat.isDirectory()) {
200
- response.changes.push({ filePath, isDirectory: true });
200
+ response.changes.push({ filePath, absolutePath: fullPath, isDirectory: true });
201
201
  }
202
202
  } else {
203
203
  // File Does Not Exist -> Deletion
@@ -12,7 +12,7 @@ local RunService = game:GetService("RunService")
12
12
  local TweenService = game:GetService("TweenService")
13
13
  local ScriptEditorService = game:GetService("ScriptEditorService")
14
14
 
15
- print("Roport Plugin v1.3.1 Loaded (Project Config Support)")
15
+ print("Roport Plugin v1.3.2 Loaded (Project Config Support)")
16
16
 
17
17
  -- Prevent running as a normal script
18
18
  if not plugin then return end
@@ -685,7 +685,7 @@ local function pullChanges()
685
685
  if data.changes and #data.changes > 0 then
686
686
  print("Roport: Pulling " .. #data.changes .. " files...")
687
687
  for _, change in ipairs(data.changes) do
688
- applyUpdate(change.filePath, change.content)
688
+ applyUpdate(change.filePath, change.content, change.absolutePath)
689
689
  end
690
690
  showToast("Updated " .. #data.changes .. " files", "success")
691
691
  else
@@ -960,97 +960,8 @@ local function findInstanceByPath(path)
960
960
  return nil
961
961
  end
962
962
 
963
- -- XML Parser for .rbxmx support
964
- local function parseRbxmx(content)
965
- local roots = {}
966
- local stack = {}
967
- local current = nil
968
-
969
- -- Helper to decode entities
970
- local function decode(s)
971
- return s:gsub("&lt;", "<"):gsub("&gt;", ">"):gsub("&quot;", '"'):gsub("&apos;", "'"):gsub("&amp;", "&")
972
- end
973
-
974
- -- 1. Extract Items
975
- -- We iterate through the string finding <Item ...> and </Item>
976
- -- This is a simplified parser that assumes well-formed Roblox XML
977
-
978
- local pos = 1
979
- while true do
980
- local s, e, tag, attrs = content:find("<Item%s+([^>]+)>", pos)
981
- local sEnd, eEnd = content:find("</Item>", pos)
982
-
983
- if not s and not sEnd then break end
984
-
985
- if s and (not sEnd or s < sEnd) then
986
- -- Found start tag
987
- local class = attrs:match('class="([^"]+)"')
988
- local inst = Instance.new(class or "Folder")
989
-
990
- -- Parse Properties immediately following
991
- local propStart, propEnd = content:find("<Properties>", e)
992
- if propStart then
993
- local propContentEnd = content:find("</Properties>", propEnd)
994
- if propContentEnd then
995
- local propBlock = content:sub(propEnd + 1, propContentEnd - 1)
996
-
997
- -- Parse properties
998
- for pType, pName, pVal in propBlock:gmatch("<(%w+)%s+name=\"([^\"]+)\">([^<]*)</%1>") do
999
- pcall(function()
1000
- if pType == "string" then inst[pName] = decode(pVal)
1001
- elseif pType == "bool" then inst[pName] = (pVal == "true")
1002
- elseif pType == "float" or pType == "double" or pType == "int" or pType == "int64" then inst[pName] = tonumber(pVal)
1003
- elseif pType == "Color3" or pType == "Color3uint8" then
1004
- -- Complex types usually have nested tags in newer format, or text in older
1005
- -- This simple parser handles basic types. Complex types need more logic.
1006
- end
1007
- end)
1008
- end
1009
-
1010
- -- Handle nested tags for complex properties (Vector3, Color3, etc)
1011
- -- <Vector3 name="Position"><X>0</X><Y>10</Y><Z>0</Z></Vector3>
1012
- for pType, pName, pInner in propBlock:gmatch("<(%w+)%s+name=\"([^\"]+)\">([%s%S]-)</%1>") do
1013
- pcall(function()
1014
- if pType == "Vector3" then
1015
- local x = tonumber(pInner:match("<X>(.-)</X>"))
1016
- local y = tonumber(pInner:match("<Y>(.-)</Y>"))
1017
- local z = tonumber(pInner:match("<Z>(.-)</Z>"))
1018
- inst[pName] = Vector3.new(x, y, z)
1019
- elseif pType == "Color3" then
1020
- local r = tonumber(pInner:match("<R>(.-)</R>"))
1021
- local g = tonumber(pInner:match("<G>(.-)</G>"))
1022
- local b = tonumber(pInner:match("<B>(.-)</B>"))
1023
- inst[pName] = Color3.new(r, g, b)
1024
- elseif pType == "UDim2" then
1025
- local xs = tonumber(pInner:match("<XS>(.-)</XS>"))
1026
- local xo = tonumber(pInner:match("<XO>(.-)</XO>"))
1027
- local ys = tonumber(pInner:match("<YS>(.-)</YS>"))
1028
- local yo = tonumber(pInner:match("<YO>(.-)</YO>"))
1029
- inst[pName] = UDim2.new(xs, xo, ys, yo)
1030
- end
1031
- end)
1032
- end
1033
- end
1034
- end
1035
-
1036
- if current then
1037
- inst.Parent = current
1038
- else
1039
- table.insert(roots, inst)
1040
- end
1041
-
1042
- table.insert(stack, current)
1043
- current = inst
1044
- pos = e + 1
1045
- else
1046
- -- Found end tag
1047
- current = table.remove(stack)
1048
- pos = eEnd + 1
1049
- end
1050
- end
1051
-
1052
- return roots
1053
- end
963
+ -- XML Parser for .rbxmx support (Deprecated/Unused - using InsertService instead)
964
+ -- local function parseRbxmx(content) ... end
1054
965
 
1055
966
  local function createInstanceByPath(path, content)
1056
967
  local isInit = path:match("/init%.luau$") or path:match("/init%.server%.luau$") or path:match("/init%.client%.luau$") or path:match("/init%.lua$") or path:match("/init%.server%.lua$") or path:match("/init%.client%.lua$")
@@ -1135,7 +1046,7 @@ local function createInstanceByPath(path, content)
1135
1046
  return nil
1136
1047
  end
1137
1048
 
1138
- local function applyUpdate(filePath, content)
1049
+ local function applyUpdate(filePath, content, absolutePath)
1139
1050
  local inst = findInstanceByPath(filePath)
1140
1051
  if not inst then
1141
1052
  inst = createInstanceByPath(filePath, content)
@@ -1146,21 +1057,24 @@ local function applyUpdate(filePath, content)
1146
1057
  inst.Value = content
1147
1058
  elseif filePath:match("%.json$") and not filePath:match("%.meta%.json$") and not filePath:match("%.model%.json$") and inst:IsA("ModuleScript") then
1148
1059
  inst.Source = "return " .. content
1149
- elseif filePath:match("%.rbxmx$") then
1150
- -- Handle Model Update
1151
- local roots = parseRbxmx(content)
1152
- if #roots > 0 then
1153
- local newInst = roots[1]
1154
- newInst.Name = inst.Name -- Keep name from path
1155
- newInst.Parent = inst.Parent
1156
-
1157
- -- Move children that are NOT part of the model content but exist in the tree?
1158
- -- Actually, if we replace the model, we replace its children too usually.
1159
- -- But if the user has scripts inside the model in the file system, they should be preserved?
1160
- -- Rojo usually treats the model file as the source of truth for that instance and its children.
1161
-
1162
- inst:Destroy()
1163
- inst = newInst
1060
+ elseif filePath:match("%.rbxmx$") and absolutePath then
1061
+ -- Handle Model Update via InsertService
1062
+ local success, model = pcall(function()
1063
+ return game:GetService("InsertService"):LoadLocalAsset(absolutePath)
1064
+ end)
1065
+
1066
+ if success and model then
1067
+ local children = model:GetChildren()
1068
+ if #children > 0 then
1069
+ local newObj = children[1]
1070
+ newObj.Name = inst.Name
1071
+ newObj.Parent = inst.Parent
1072
+ inst:Destroy()
1073
+ inst = newObj
1074
+ print("Roport: Imported " .. newObj.Name .. " from " .. filePath)
1075
+ end
1076
+ else
1077
+ warn("Roport: Failed to load RBXMX: " .. tostring(model))
1164
1078
  end
1165
1079
  elseif filePath:match("%.model%.json$") or filePath:match("%.meta%.json$") then
1166
1080
  local success, data = pcall(function() return HttpService:JSONDecode(content) end)
@@ -1290,7 +1204,7 @@ task.spawn(function()
1290
1204
  if data.changes and #data.changes > 0 then
1291
1205
  print("Roport: Received " .. #data.changes .. " updates from VS Code")
1292
1206
  for _, change in ipairs(data.changes) do
1293
- applyUpdate(change.filePath, change.content)
1207
+ applyUpdate(change.filePath, change.content, change.absolutePath)
1294
1208
  end
1295
1209
  end
1296
1210
  else