roport 1.2.0 → 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/bin/roport.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  const { program } = require('commander');
4
4
  const { startServer } = require('../src/server');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roport",
3
- "version": "1.2.0",
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.0 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
@@ -331,7 +331,7 @@ statusText.Parent = statusFrame
331
331
 
332
332
  -- Helper Functions (Global)
333
333
  local function sanitize(name)
334
- return name:gsub("[\\/:*?\"<>|]", "_")
334
+ return (name:gsub("[\\/:*?\"<>|]", "_"))
335
335
  end
336
336
 
337
337
  local function resolveRobloxService(robloxPathArray)
@@ -361,7 +361,7 @@ local function getPath(inst)
361
361
  local relative = {}
362
362
  local current = inst
363
363
  while current ~= root do
364
- table.insert(relative, 1, sanitize(current.Name))
364
+ table.insert(relative, 1, (sanitize(current.Name)))
365
365
  current = current.Parent
366
366
  end
367
367
 
@@ -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
@@ -788,6 +788,8 @@ local function createSettingInput(label, default, callback)
788
788
  end)
789
789
  end
790
790
 
791
+ if not state.settings then state.settings = { port = 3456, syncInterval = 2 } end
792
+
791
793
  createSettingInput("Server Port", state.settings.port, function(val)
792
794
  local n = tonumber(val)
793
795
  if n then
@@ -958,97 +960,8 @@ local function findInstanceByPath(path)
958
960
  return nil
959
961
  end
960
962
 
961
- -- XML Parser for .rbxmx support
962
- local function parseRbxmx(content)
963
- local roots = {}
964
- local stack = {}
965
- local current = nil
966
-
967
- -- Helper to decode entities
968
- local function decode(s)
969
- return s:gsub("&lt;", "<"):gsub("&gt;", ">"):gsub("&quot;", '"'):gsub("&apos;", "'"):gsub("&amp;", "&")
970
- end
971
-
972
- -- 1. Extract Items
973
- -- We iterate through the string finding <Item ...> and </Item>
974
- -- This is a simplified parser that assumes well-formed Roblox XML
975
-
976
- local pos = 1
977
- while true do
978
- local s, e, tag, attrs = content:find("<Item%s+([^>]+)>", pos)
979
- local sEnd, eEnd = content:find("</Item>", pos)
980
-
981
- if not s and not sEnd then break end
982
-
983
- if s and (not sEnd or s < sEnd) then
984
- -- Found start tag
985
- local class = attrs:match('class="([^"]+)"')
986
- local inst = Instance.new(class or "Folder")
987
-
988
- -- Parse Properties immediately following
989
- local propStart, propEnd = content:find("<Properties>", e)
990
- if propStart then
991
- local propContentEnd = content:find("</Properties>", propEnd)
992
- if propContentEnd then
993
- local propBlock = content:sub(propEnd + 1, propContentEnd - 1)
994
-
995
- -- Parse properties
996
- for pType, pName, pVal in propBlock:gmatch("<(%w+)%s+name=\"([^\"]+)\">([^<]*)</%1>") do
997
- pcall(function()
998
- if pType == "string" then inst[pName] = decode(pVal)
999
- elseif pType == "bool" then inst[pName] = (pVal == "true")
1000
- elseif pType == "float" or pType == "double" or pType == "int" or pType == "int64" then inst[pName] = tonumber(pVal)
1001
- elseif pType == "Color3" or pType == "Color3uint8" then
1002
- -- Complex types usually have nested tags in newer format, or text in older
1003
- -- This simple parser handles basic types. Complex types need more logic.
1004
- end
1005
- end)
1006
- end
1007
-
1008
- -- Handle nested tags for complex properties (Vector3, Color3, etc)
1009
- -- <Vector3 name="Position"><X>0</X><Y>10</Y><Z>0</Z></Vector3>
1010
- for pType, pName, pInner in propBlock:gmatch("<(%w+)%s+name=\"([^\"]+)\">([%s%S]-)</%1>") do
1011
- pcall(function()
1012
- if pType == "Vector3" then
1013
- local x = tonumber(pInner:match("<X>(.-)</X>"))
1014
- local y = tonumber(pInner:match("<Y>(.-)</Y>"))
1015
- local z = tonumber(pInner:match("<Z>(.-)</Z>"))
1016
- inst[pName] = Vector3.new(x, y, z)
1017
- elseif pType == "Color3" then
1018
- local r = tonumber(pInner:match("<R>(.-)</R>"))
1019
- local g = tonumber(pInner:match("<G>(.-)</G>"))
1020
- local b = tonumber(pInner:match("<B>(.-)</B>"))
1021
- inst[pName] = Color3.new(r, g, b)
1022
- elseif pType == "UDim2" then
1023
- local xs = tonumber(pInner:match("<XS>(.-)</XS>"))
1024
- local xo = tonumber(pInner:match("<XO>(.-)</XO>"))
1025
- local ys = tonumber(pInner:match("<YS>(.-)</YS>"))
1026
- local yo = tonumber(pInner:match("<YO>(.-)</YO>"))
1027
- inst[pName] = UDim2.new(xs, xo, ys, yo)
1028
- end
1029
- end)
1030
- end
1031
- end
1032
- end
1033
-
1034
- if current then
1035
- inst.Parent = current
1036
- else
1037
- table.insert(roots, inst)
1038
- end
1039
-
1040
- table.insert(stack, current)
1041
- current = inst
1042
- pos = e + 1
1043
- else
1044
- -- Found end tag
1045
- current = table.remove(stack)
1046
- pos = eEnd + 1
1047
- end
1048
- end
1049
-
1050
- return roots
1051
- end
963
+ -- XML Parser for .rbxmx support (Deprecated/Unused - using InsertService instead)
964
+ -- local function parseRbxmx(content) ... end
1052
965
 
1053
966
  local function createInstanceByPath(path, content)
1054
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$")
@@ -1133,7 +1046,7 @@ local function createInstanceByPath(path, content)
1133
1046
  return nil
1134
1047
  end
1135
1048
 
1136
- local function applyUpdate(filePath, content)
1049
+ local function applyUpdate(filePath, content, absolutePath)
1137
1050
  local inst = findInstanceByPath(filePath)
1138
1051
  if not inst then
1139
1052
  inst = createInstanceByPath(filePath, content)
@@ -1144,21 +1057,24 @@ local function applyUpdate(filePath, content)
1144
1057
  inst.Value = content
1145
1058
  elseif filePath:match("%.json$") and not filePath:match("%.meta%.json$") and not filePath:match("%.model%.json$") and inst:IsA("ModuleScript") then
1146
1059
  inst.Source = "return " .. content
1147
- elseif filePath:match("%.rbxmx$") then
1148
- -- Handle Model Update
1149
- local roots = parseRbxmx(content)
1150
- if #roots > 0 then
1151
- local newInst = roots[1]
1152
- newInst.Name = inst.Name -- Keep name from path
1153
- newInst.Parent = inst.Parent
1154
-
1155
- -- Move children that are NOT part of the model content but exist in the tree?
1156
- -- Actually, if we replace the model, we replace its children too usually.
1157
- -- But if the user has scripts inside the model in the file system, they should be preserved?
1158
- -- Rojo usually treats the model file as the source of truth for that instance and its children.
1159
-
1160
- inst:Destroy()
1161
- 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))
1162
1078
  end
1163
1079
  elseif filePath:match("%.model%.json$") or filePath:match("%.meta%.json$") then
1164
1080
  local success, data = pcall(function() return HttpService:JSONDecode(content) end)
@@ -1288,7 +1204,7 @@ task.spawn(function()
1288
1204
  if data.changes and #data.changes > 0 then
1289
1205
  print("Roport: Received " .. #data.changes .. " updates from VS Code")
1290
1206
  for _, change in ipairs(data.changes) do
1291
- applyUpdate(change.filePath, change.content)
1207
+ applyUpdate(change.filePath, change.content, change.absolutePath)
1292
1208
  end
1293
1209
  end
1294
1210
  else