Add CI pipeline documentation and enhance syncback architecture

- Introduced a new CI pipeline documentation file outlining steps for auto-fixing formatting, running linters, building, and testing.
- Documented the Rojo syncback architecture, detailing improvements to prevent duplicate file creation and format mismatches.
- Updated syncback logic to ensure existing instances are updated instead of creating duplicates, enhancing project file management.
- Added tests for encoding instances and handling nested children to improve reliability in syncback operations.
This commit is contained in:
Joe 2026-02-01 18:18:31 -06:00
parent edc09741bf
commit b368eb2401
46 changed files with 3688 additions and 112 deletions

View file

@ -175,13 +175,21 @@ local function shouldDeleteChild(virtualInstance, childInstance)
return false
end
local function diff(instanceMap, virtualInstances, rootId)
local function diff(instanceMap, virtualInstances, rootId, serverInfo)
local patch = {
removed = {},
added = {},
updated = {},
}
-- Build a lookup table for visible services (for ignoreHiddenServices check)
local visibleServicesSet: { [string]: boolean } = {}
if serverInfo and serverInfo.ignoreHiddenServices and serverInfo.visibleServices then
for _, serviceName in ipairs(serverInfo.visibleServices) do
visibleServicesSet[serviceName] = true
end
end
-- Count of locations with duplicate-named siblings
local skippedDuplicateCount = 0
@ -403,6 +411,20 @@ local function diff(instanceMap, virtualInstances, rootId)
continue
end
-- Skip hidden services when ignoreHiddenServices is enabled.
-- Services are direct children of game (DataModel), and if they're
-- not in the visible services list, they weren't synced back to disk,
-- so we shouldn't mark them for deletion during forward sync.
if serverInfo and serverInfo.ignoreHiddenServices and instance == game then
if not visibleServicesSet[childInstance.Name] then
Log.trace(
"Skipping hidden service {} (ignoreHiddenServices is enabled)",
childInstance.Name
)
continue
end
end
-- This is an existing instance not present in the virtual DOM.
-- We can mark it for deletion unless the user has asked us not
-- to delete unknown stuff. Scripts are always eligible for deletion

View file

@ -1,7 +1,4 @@
return function()
local Packages = script.Parent.Parent.Parent.Packages
local Log = require(Packages.Log)
local InstanceMap = require(script.Parent.Parent.InstanceMap)
local PatchSet = require(script.Parent.Parent.PatchSet)
@ -164,15 +161,7 @@ return function()
assert(isEmpty(patch.updated))
end)
--[[
Because rbx_dom_lua resolves non-canonical properties to their canonical
variants, this test does not work as intended.
Instead, heat_xml is diffed with Heat, the canonical property variant,
and a patch trying to assign to heat_xml is generated. This is
incorrect, but will require more invasive changes to fix later.
]]
itFIXME("should ignore unreadable properties", function()
it("should ignore unreadable properties", function()
local knownInstances = InstanceMap.new()
local virtualInstances = {
ROOT = {
@ -194,8 +183,6 @@ return function()
local ok, patch = diff(knownInstances, virtualInstances, "ROOT")
Log.warn("{:#?}", patch)
assert(ok, tostring(patch))
assert(isEmpty(patch.removed))

View file

@ -21,6 +21,18 @@ local function getProperty(instance, propertyName)
})
end
-- If the property name we were given is not the canonical name, treat it
-- as unknown. This handles serialization-only aliases like `heat_xml`
-- which resolve to `Heat` - we should skip these and let the canonical
-- property name be used instead.
if descriptor.name ~= propertyName then
return false,
Error.new(Error.UnknownProperty, {
className = instance.ClassName,
propertyName = propertyName,
})
end
if descriptor.scriptability == "None" or descriptor.scriptability == "Write" then
return false,
Error.new(Error.UnreadableProperty, {

View file

@ -41,9 +41,9 @@ function Reconciler:hydrate(virtualInstances, rootId, rootInstance)
return result
end
function Reconciler:diff(virtualInstances, rootId)
function Reconciler:diff(virtualInstances, rootId, serverInfo)
Timer.start("Reconciler:diff")
local success, result = diff(self.__instanceMap, virtualInstances, rootId)
local success, result = diff(self.__instanceMap, virtualInstances, rootId, serverInfo)
Timer.stop()
return success, result