goldsync 0.1.14 → 0.1.16
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/README.md +4 -0
- package/assets/GoldSync.rbxmx +165 -39
- package/package.json +1 -1
- package/src/config.mjs +22 -2
- package/src/server.mjs +1 -0
- package/src/store.mjs +6 -1
package/README.md
CHANGED
|
@@ -73,6 +73,10 @@ Place `goldsync.project.json` at the game repository root:
|
|
|
73
73
|
|
|
74
74
|
A `file` root becomes one RBXM. A `directory` root creates separate files for descendants at `splitDepth`. Split at complete models, effects, or UI components—not individual parts or attachments. Never map the same hierarchy in Rojo, and do not include scripts inside a GoldSync root.
|
|
75
75
|
|
|
76
|
+
Directory roots can contain more specific roots. Discovery skips branches owned by those roots, including existing files on disk. For example, split `ReplicatedStorage.Assets` at depth 1 and add another depth-1 directory root for `ReplicatedStorage.Assets.Effects`. New asset categories and individual effects are discovered automatically, without creating an overlapping `Effects.rbxm`. Existing explicit file roots can stay in place to preserve their sync history. File roots cannot contain other roots because their snapshots include the entire subtree.
|
|
77
|
+
|
|
78
|
+
When upgrading an existing project, put the new parent directory roots in the optional top-level `discoveryRoots` array, using the same fields as directory roots. Older GoldSync versions ignore that array, so they cannot create overlapping snapshots before the service is restarted with nested-root support. Existing roots and files remain unchanged.
|
|
79
|
+
|
|
76
80
|
GoldSync reads the Rojo project name and fallback `servePort` from `default.project.json`. It automatically detects `rojo serve --port` overrides and verifies the listener's project name before connecting. That active Rojo session selects the GoldSync workspace, so Studio place IDs and names do not need configuration. Set `rojo.projectFile` only when the repository uses another project file.
|
|
77
81
|
|
|
78
82
|
Map GoldSync's generated live-session marker in the Rojo project tree:
|
package/assets/GoldSync.rbxmx
CHANGED
|
@@ -22,7 +22,7 @@ local CLIENT_HEADERS = {
|
|
|
22
22
|
}
|
|
23
23
|
local SETTINGS_PREFIX = "GoldSync:v2:"
|
|
24
24
|
local UI_SETTINGS_PREFIX = "GoldSync:ui:v1:"
|
|
25
|
-
local VERSION = "0.1.
|
|
25
|
+
local VERSION = "0.1.16"
|
|
26
26
|
local REQUEST_INTERVAL_SECONDS = 0.15
|
|
27
27
|
local REQUEST_RETRY_DELAYS = { 1, 2, 4 }
|
|
28
28
|
|
|
@@ -185,13 +185,13 @@ local searchBox = Instance.new("TextBox")
|
|
|
185
185
|
searchBox.BackgroundColor3 = Color3.fromRGB(40, 40, 46)
|
|
186
186
|
searchBox.BorderSizePixel = 0
|
|
187
187
|
searchBox.ClearTextOnFocus = false
|
|
188
|
-
searchBox.
|
|
189
|
-
searchBox.PlaceholderColor3 = Color3.fromRGB(
|
|
188
|
+
searchBox.FontFace = Font.new("rbxasset://fonts/families/BuilderSans.json", Enum.FontWeight.Medium)
|
|
189
|
+
searchBox.PlaceholderColor3 = Color3.fromRGB(195, 195, 205)
|
|
190
190
|
searchBox.PlaceholderText = "Search files and instances..."
|
|
191
191
|
searchBox.Size = UDim2.new(1, -132, 1, 0)
|
|
192
192
|
searchBox.Text = ""
|
|
193
193
|
searchBox.TextColor3 = Color3.fromRGB(235, 235, 240)
|
|
194
|
-
searchBox.TextSize =
|
|
194
|
+
searchBox.TextSize = 13
|
|
195
195
|
searchBox.TextXAlignment = Enum.TextXAlignment.Left
|
|
196
196
|
searchBox.Parent = controls
|
|
197
197
|
Instance.new("UICorner", searchBox).CornerRadius = UDim.new(0, 4)
|
|
@@ -202,12 +202,12 @@ searchPadding.PaddingRight = UDim.new(0, 8)
|
|
|
202
202
|
local unsyncedButton = Instance.new("TextButton")
|
|
203
203
|
unsyncedButton.BackgroundColor3 = Color3.fromRGB(40, 40, 46)
|
|
204
204
|
unsyncedButton.BorderSizePixel = 0
|
|
205
|
-
unsyncedButton.
|
|
205
|
+
unsyncedButton.FontFace = Font.new("rbxasset://fonts/families/BuilderSans.json", Enum.FontWeight.Medium)
|
|
206
206
|
unsyncedButton.Position = UDim2.new(1, -126, 0, 0)
|
|
207
207
|
unsyncedButton.Size = UDim2.fromOffset(92, 28)
|
|
208
208
|
unsyncedButton.Text = "Unsynced · 0"
|
|
209
209
|
unsyncedButton.TextColor3 = Color3.fromRGB(190, 190, 198)
|
|
210
|
-
unsyncedButton.TextSize =
|
|
210
|
+
unsyncedButton.TextSize = 13
|
|
211
211
|
unsyncedButton.Parent = controls
|
|
212
212
|
Instance.new("UICorner", unsyncedButton).CornerRadius = UDim.new(0, 4)
|
|
213
213
|
|
|
@@ -292,12 +292,12 @@ local statusBanner = Instance.new("TextLabel")
|
|
|
292
292
|
statusBanner.AutomaticSize = Enum.AutomaticSize.Y
|
|
293
293
|
statusBanner.BackgroundColor3 = Color3.fromRGB(66, 38, 40)
|
|
294
294
|
statusBanner.BorderSizePixel = 0
|
|
295
|
-
statusBanner.
|
|
295
|
+
statusBanner.FontFace = Font.new("rbxasset://fonts/families/BuilderSans.json", Enum.FontWeight.Medium)
|
|
296
296
|
statusBanner.LayoutOrder = 4
|
|
297
297
|
statusBanner.Size = UDim2.new(1, 0, 0, 0)
|
|
298
298
|
statusBanner.Text = ""
|
|
299
299
|
statusBanner.TextColor3 = Color3.fromRGB(255, 205, 205)
|
|
300
|
-
statusBanner.TextSize =
|
|
300
|
+
statusBanner.TextSize = 14
|
|
301
301
|
statusBanner.TextWrapped = true
|
|
302
302
|
statusBanner.TextXAlignment = Enum.TextXAlignment.Left
|
|
303
303
|
statusBanner.Visible = false
|
|
@@ -309,13 +309,66 @@ statusBannerPadding.PaddingLeft = UDim.new(0, 8)
|
|
|
309
309
|
statusBannerPadding.PaddingRight = UDim.new(0, 8)
|
|
310
310
|
statusBannerPadding.PaddingTop = UDim.new(0, 7)
|
|
311
311
|
|
|
312
|
+
local attentionBox = Instance.new("Frame")
|
|
313
|
+
attentionBox.Name = "AttentionBox"
|
|
314
|
+
attentionBox.AutomaticSize = Enum.AutomaticSize.Y
|
|
315
|
+
attentionBox.BackgroundColor3 = Color3.fromRGB(43, 42, 38)
|
|
316
|
+
attentionBox.BorderSizePixel = 0
|
|
317
|
+
attentionBox.LayoutOrder = 5
|
|
318
|
+
attentionBox.Size = UDim2.fromScale(1, 0)
|
|
319
|
+
attentionBox.Visible = false
|
|
320
|
+
attentionBox.Parent = content
|
|
321
|
+
Instance.new("UICorner", attentionBox).CornerRadius = UDim.new(0.04, 0)
|
|
322
|
+
local attentionBoxLayout = Instance.new("UIListLayout")
|
|
323
|
+
attentionBoxLayout.SortOrder = Enum.SortOrder.LayoutOrder
|
|
324
|
+
attentionBoxLayout.Parent = attentionBox
|
|
325
|
+
|
|
326
|
+
local attentionActions = Instance.new("Frame")
|
|
327
|
+
attentionActions.BackgroundTransparency = 1
|
|
328
|
+
attentionActions.LayoutOrder = 1
|
|
329
|
+
attentionActions.Size = UDim2.new(1, 0, 0, 30)
|
|
330
|
+
attentionActions.Parent = attentionBox
|
|
331
|
+
local attentionBulkButtons = {}
|
|
332
|
+
for index, text in { "Push All →", "← Pull All" } do
|
|
333
|
+
local button = Instance.new("ImageButton")
|
|
334
|
+
button.AnchorPoint = Vector2.new(0.5, 0.5)
|
|
335
|
+
button.BackgroundColor3 = Color3.fromRGB(57, 57, 67)
|
|
336
|
+
button.BorderSizePixel = 0
|
|
337
|
+
button.Position = UDim2.fromScale(0.25 + (index - 1) * 0.5, 0.5)
|
|
338
|
+
button.Size = UDim2.fromScale(0.47, 0.8)
|
|
339
|
+
button.Parent = attentionActions
|
|
340
|
+
Instance.new("UICorner", button).CornerRadius = UDim.new(0.22, 0)
|
|
341
|
+
local label = Instance.new("TextLabel")
|
|
342
|
+
label.BackgroundTransparency = 1
|
|
343
|
+
label.Position = UDim2.fromScale(0.12, 0.18)
|
|
344
|
+
label.Size = UDim2.fromScale(0.76, 0.64)
|
|
345
|
+
label.Font = Enum.Font.BuilderSansBold
|
|
346
|
+
label.Text = text
|
|
347
|
+
label.TextColor3 = Color3.fromRGB(215, 225, 240)
|
|
348
|
+
label.TextScaled = true
|
|
349
|
+
label.Parent = button
|
|
350
|
+
attentionBulkButtons[index] = button
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
local attentionList = Instance.new("Frame")
|
|
354
|
+
attentionList.Name = "AttentionList"
|
|
355
|
+
attentionList.AutomaticSize = Enum.AutomaticSize.Y
|
|
356
|
+
attentionList.BackgroundTransparency = 1
|
|
357
|
+
attentionList.LayoutOrder = 5
|
|
358
|
+
attentionList.Size = UDim2.fromScale(1, 0)
|
|
359
|
+
attentionList.Visible = false
|
|
360
|
+
attentionList.Parent = attentionBox
|
|
361
|
+
local attentionLayout = Instance.new("UIListLayout")
|
|
362
|
+
attentionLayout.SortOrder = Enum.SortOrder.LayoutOrder
|
|
363
|
+
attentionLayout.Parent = attentionList
|
|
364
|
+
|
|
312
365
|
local directionLegend = Instance.new("TextLabel")
|
|
313
366
|
directionLegend.BackgroundTransparency = 1
|
|
314
|
-
directionLegend.
|
|
315
|
-
directionLegend.LayoutOrder =
|
|
367
|
+
directionLegend.FontFace = Font.new("rbxasset://fonts/families/BuilderSans.json", Enum.FontWeight.Medium)
|
|
368
|
+
directionLegend.LayoutOrder = 6
|
|
316
369
|
directionLegend.Size = UDim2.new(1, 0, 0, 18)
|
|
317
370
|
directionLegend.Text = "→ Studio to file ← File to Studio"
|
|
318
|
-
directionLegend.TextColor3 = Color3.fromRGB(
|
|
371
|
+
directionLegend.TextColor3 = Color3.fromRGB(195, 195, 205)
|
|
319
372
|
directionLegend.TextSize = 11
|
|
320
373
|
directionLegend.TextXAlignment = Enum.TextXAlignment.Right
|
|
321
374
|
directionLegend.Parent = content
|
|
@@ -323,7 +376,7 @@ directionLegend.Parent = content
|
|
|
323
376
|
local rootsContainer = Instance.new("Frame")
|
|
324
377
|
rootsContainer.AutomaticSize = Enum.AutomaticSize.Y
|
|
325
378
|
rootsContainer.BackgroundTransparency = 1
|
|
326
|
-
rootsContainer.LayoutOrder =
|
|
379
|
+
rootsContainer.LayoutOrder = 7
|
|
327
380
|
rootsContainer.Size = UDim2.new(1, 0, 0, 0)
|
|
328
381
|
rootsContainer.Parent = content
|
|
329
382
|
|
|
@@ -354,6 +407,34 @@ local resolveGitConflict
|
|
|
354
407
|
local applyTreeFilter
|
|
355
408
|
local resetInstanceTrees
|
|
356
409
|
|
|
410
|
+
for index, button in attentionBulkButtons do
|
|
411
|
+
button.Activated:Connect(function()
|
|
412
|
+
if bulkRunning or not serverConfig or not serverConfig.rojoConnected then
|
|
413
|
+
return
|
|
414
|
+
end
|
|
415
|
+
local affected = {}
|
|
416
|
+
for _, state in states do
|
|
417
|
+
if state.statusKind == "issue" and not state.manifest.gitConflict then
|
|
418
|
+
table.insert(affected, state)
|
|
419
|
+
end
|
|
420
|
+
end
|
|
421
|
+
bulkRunning = true
|
|
422
|
+
refreshStatusSummary()
|
|
423
|
+
for _, state in affected do
|
|
424
|
+
if states[state.manifest.id] ~= state or state.manifest.gitConflict then
|
|
425
|
+
continue
|
|
426
|
+
end
|
|
427
|
+
if index == 1 then
|
|
428
|
+
upload(state, true)
|
|
429
|
+
else
|
|
430
|
+
pull(state)
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
bulkRunning = false
|
|
434
|
+
refreshStatusSummary()
|
|
435
|
+
end)
|
|
436
|
+
end
|
|
437
|
+
|
|
357
438
|
local function collectSplitPaths(root, depth)
|
|
358
439
|
local paths = {}
|
|
359
440
|
local function visit(instance, remainingDepth, relativePath)
|
|
@@ -527,6 +608,24 @@ local function discoverSplitRoot(splitRoot, root)
|
|
|
527
608
|
end
|
|
528
609
|
local missingPaths = {}
|
|
529
610
|
for _, relativePath in collectSplitPaths(root, splitRoot.splitDepth) do
|
|
611
|
+
local excluded = false
|
|
612
|
+
for _, excludedPath in splitRoot.excludedPaths or {} do
|
|
613
|
+
local matches = true
|
|
614
|
+
for index, segment in excludedPath do
|
|
615
|
+
if relativePath[index] ~= segment then
|
|
616
|
+
matches = false
|
|
617
|
+
break
|
|
618
|
+
end
|
|
619
|
+
end
|
|
620
|
+
if matches then
|
|
621
|
+
excluded = true
|
|
622
|
+
break
|
|
623
|
+
end
|
|
624
|
+
end
|
|
625
|
+
if excluded then
|
|
626
|
+
continue
|
|
627
|
+
end
|
|
628
|
+
|
|
530
629
|
local fullPath = table.clone(splitRoot.studioPath)
|
|
531
630
|
for _, segment in relativePath do
|
|
532
631
|
table.insert(fullPath, segment)
|
|
@@ -1553,14 +1652,6 @@ local function createGroup(path, order)
|
|
|
1553
1652
|
section.LayoutOrder = order
|
|
1554
1653
|
section.Size = UDim2.new(1, 0, 0, 0)
|
|
1555
1654
|
section.Parent = if parentGroup then parentGroup.list else rootsContainer
|
|
1556
|
-
if parentGroup then
|
|
1557
|
-
local connector = Instance.new("Frame")
|
|
1558
|
-
connector.BackgroundColor3 = Color3.fromRGB(72, 72, 82)
|
|
1559
|
-
connector.BorderSizePixel = 0
|
|
1560
|
-
connector.Position = UDim2.new(0, -8, 0, 11)
|
|
1561
|
-
connector.Size = UDim2.fromOffset(35, 1)
|
|
1562
|
-
connector.Parent = section
|
|
1563
|
-
end
|
|
1564
1655
|
|
|
1565
1656
|
local sectionLayout = Instance.new("UIListLayout")
|
|
1566
1657
|
sectionLayout.Padding = UDim.new(0, 2)
|
|
@@ -1573,6 +1664,14 @@ local function createGroup(path, order)
|
|
|
1573
1664
|
header.LayoutOrder = 1
|
|
1574
1665
|
header.Size = UDim2.new(1, 0, 0, 24)
|
|
1575
1666
|
header.Parent = section
|
|
1667
|
+
if parentGroup then
|
|
1668
|
+
local connector = Instance.new("Frame")
|
|
1669
|
+
connector.BackgroundColor3 = Color3.fromRGB(72, 72, 82)
|
|
1670
|
+
connector.BorderSizePixel = 0
|
|
1671
|
+
connector.Position = UDim2.new(0, -8, 0, 11)
|
|
1672
|
+
connector.Size = UDim2.fromOffset(35, 1)
|
|
1673
|
+
connector.Parent = header
|
|
1674
|
+
end
|
|
1576
1675
|
|
|
1577
1676
|
local disclosure = Instance.new("TextButton")
|
|
1578
1677
|
disclosure.BackgroundTransparency = 1
|
|
@@ -1735,6 +1834,7 @@ updateGroupSummaries = function()
|
|
|
1735
1834
|
local total = 0
|
|
1736
1835
|
local synced = 0
|
|
1737
1836
|
local issues = 0
|
|
1837
|
+
local issueColor = Color3.fromRGB(244, 194, 92)
|
|
1738
1838
|
local pending = 0
|
|
1739
1839
|
local paused = 0
|
|
1740
1840
|
for _, state in states do
|
|
@@ -1744,6 +1844,9 @@ updateGroupSummaries = function()
|
|
|
1744
1844
|
synced += 1
|
|
1745
1845
|
elseif state.statusKind == "issue" then
|
|
1746
1846
|
issues += 1
|
|
1847
|
+
if state.status.TextColor3 == Color3.fromRGB(245, 106, 106) then
|
|
1848
|
+
issueColor = state.status.TextColor3
|
|
1849
|
+
end
|
|
1747
1850
|
elseif state.statusKind == "paused" then
|
|
1748
1851
|
paused += 1
|
|
1749
1852
|
else
|
|
@@ -1766,7 +1869,7 @@ updateGroupSummaries = function()
|
|
|
1766
1869
|
group.bulkPush.Visible = true
|
|
1767
1870
|
group.bulkPull.Visible = true
|
|
1768
1871
|
group.statusDot.TextColor3 = if issues > 0
|
|
1769
|
-
then
|
|
1872
|
+
then issueColor
|
|
1770
1873
|
elseif synced == total then Color3.fromRGB(102, 214, 139)
|
|
1771
1874
|
elseif pending > 0 or paused > 0 then Color3.fromRGB(244, 194, 92)
|
|
1772
1875
|
else Color3.fromRGB(180, 180, 190)
|
|
@@ -1802,7 +1905,7 @@ refreshUnsyncedButton = function()
|
|
|
1802
1905
|
then Color3.fromRGB(40, 40, 46)
|
|
1803
1906
|
else Color3.fromRGB(78, 66, 38)
|
|
1804
1907
|
unsyncedButton.TextColor3 = if showSynced
|
|
1805
|
-
then Color3.fromRGB(
|
|
1908
|
+
then Color3.fromRGB(220, 220, 230)
|
|
1806
1909
|
else Color3.fromRGB(255, 218, 126)
|
|
1807
1910
|
end
|
|
1808
1911
|
|
|
@@ -1813,13 +1916,29 @@ refreshStatusSummary = function()
|
|
|
1813
1916
|
local entry = {
|
|
1814
1917
|
path = table.concat(state.manifest.studioPath, "."),
|
|
1815
1918
|
status = state.status.Text,
|
|
1919
|
+
state = state,
|
|
1816
1920
|
}
|
|
1817
1921
|
if state.statusKind == "issue" then
|
|
1818
1922
|
table.insert(issues, entry)
|
|
1819
|
-
|
|
1820
|
-
|
|
1923
|
+
else
|
|
1924
|
+
if state.row.Parent == attentionList then
|
|
1925
|
+
state.row.Parent = state.group.list
|
|
1926
|
+
state.connector.Visible = true
|
|
1927
|
+
state.row.LayoutOrder = state.treeOrder
|
|
1928
|
+
state.title.Text = state.manifest.studioPath[#state.manifest.studioPath]
|
|
1929
|
+
setRootExpanded(state, state.expandedBeforeAttention)
|
|
1930
|
+
end
|
|
1931
|
+
if state.statusKind == "working" then
|
|
1932
|
+
table.insert(working, entry)
|
|
1933
|
+
end
|
|
1821
1934
|
end
|
|
1822
1935
|
end
|
|
1936
|
+
attentionList.Visible = #issues > 0
|
|
1937
|
+
attentionBox.Visible = #issues > 0
|
|
1938
|
+
for _, button in attentionBulkButtons do
|
|
1939
|
+
button.Active = serverConfig.rojoConnected and not bulkRunning
|
|
1940
|
+
button.AutoButtonColor = button.Active
|
|
1941
|
+
end
|
|
1823
1942
|
local entries = if #issues > 0 then issues else working
|
|
1824
1943
|
if #entries == 0 then
|
|
1825
1944
|
statusBanner.Visible = false
|
|
@@ -1833,19 +1952,25 @@ refreshStatusSummary = function()
|
|
|
1833
1952
|
then string.format("%d need attention", #issues)
|
|
1834
1953
|
else string.format("%d still syncing", #working),
|
|
1835
1954
|
}
|
|
1836
|
-
for index
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1955
|
+
for index, entry in entries do
|
|
1956
|
+
if #issues > 0 then
|
|
1957
|
+
local state = entry.state
|
|
1958
|
+
if state.row.Parent ~= attentionList then
|
|
1959
|
+
state.expandedBeforeAttention = state.expanded
|
|
1960
|
+
state.row.Parent = attentionList
|
|
1961
|
+
state.connector.Visible = false
|
|
1962
|
+
setRootExpanded(state, false)
|
|
1963
|
+
end
|
|
1964
|
+
state.row.LayoutOrder = index
|
|
1965
|
+
state.title.Text = entry.path
|
|
1966
|
+
else
|
|
1967
|
+
table.insert(lines, entry.path .. " — " .. entry.status)
|
|
1968
|
+
end
|
|
1842
1969
|
end
|
|
1843
1970
|
statusBanner.BackgroundColor3 = if #issues > 0
|
|
1844
|
-
then Color3.fromRGB(
|
|
1845
|
-
else Color3.fromRGB(
|
|
1846
|
-
statusBanner.TextColor3 =
|
|
1847
|
-
then Color3.fromRGB(255, 205, 205)
|
|
1848
|
-
else Color3.fromRGB(255, 224, 145)
|
|
1971
|
+
then Color3.fromRGB(49, 46, 38)
|
|
1972
|
+
else Color3.fromRGB(43, 42, 38)
|
|
1973
|
+
statusBanner.TextColor3 = Color3.fromRGB(230, 222, 200)
|
|
1849
1974
|
statusBanner.Text = table.concat(lines, "\n")
|
|
1850
1975
|
statusBanner.Visible = true
|
|
1851
1976
|
end
|
|
@@ -1865,7 +1990,6 @@ local function createInstanceNode(instance, parent, depth, order)
|
|
|
1865
1990
|
connector.BorderSizePixel = 0
|
|
1866
1991
|
connector.Position = UDim2.new(0, -8, 0, 11)
|
|
1867
1992
|
connector.Size = UDim2.fromOffset(35, 1)
|
|
1868
|
-
connector.Parent = section
|
|
1869
1993
|
|
|
1870
1994
|
local header = Instance.new("Frame")
|
|
1871
1995
|
header.BackgroundColor3 = Color3.fromRGB(40, 40, 46)
|
|
@@ -1873,6 +1997,7 @@ local function createInstanceNode(instance, parent, depth, order)
|
|
|
1873
1997
|
header.LayoutOrder = 1
|
|
1874
1998
|
header.Size = UDim2.new(1, 0, 0, 22)
|
|
1875
1999
|
header.Parent = section
|
|
2000
|
+
connector.Parent = header
|
|
1876
2001
|
header.MouseEnter:Connect(function()
|
|
1877
2002
|
header.BackgroundTransparency = 0
|
|
1878
2003
|
end)
|
|
@@ -2033,7 +2158,6 @@ local function createRootRow(manifest, order)
|
|
|
2033
2158
|
connector.BorderSizePixel = 0
|
|
2034
2159
|
connector.Position = UDim2.new(0, -8, 0, 11)
|
|
2035
2160
|
connector.Size = UDim2.fromOffset(35, 1)
|
|
2036
|
-
connector.Parent = row
|
|
2037
2161
|
|
|
2038
2162
|
local corner = Instance.new("UICorner")
|
|
2039
2163
|
corner.CornerRadius = UDim.new(0, 6)
|
|
@@ -2041,8 +2165,6 @@ local function createRootRow(manifest, order)
|
|
|
2041
2165
|
|
|
2042
2166
|
local rowPadding = Instance.new("UIPadding")
|
|
2043
2167
|
rowPadding.PaddingBottom = UDim.new(0, 5)
|
|
2044
|
-
rowPadding.PaddingLeft = UDim.new(0, 6)
|
|
2045
|
-
rowPadding.PaddingRight = UDim.new(0, 6)
|
|
2046
2168
|
rowPadding.PaddingTop = UDim.new(0, 5)
|
|
2047
2169
|
rowPadding.Parent = row
|
|
2048
2170
|
|
|
@@ -2057,6 +2179,7 @@ local function createRootRow(manifest, order)
|
|
|
2057
2179
|
titleLine.LayoutOrder = 1
|
|
2058
2180
|
titleLine.Size = UDim2.new(1, 0, 0, 22)
|
|
2059
2181
|
titleLine.Parent = row
|
|
2182
|
+
connector.Parent = titleLine
|
|
2060
2183
|
|
|
2061
2184
|
local contentDisclosure = Instance.new("TextButton")
|
|
2062
2185
|
contentDisclosure.BackgroundTransparency = 1
|
|
@@ -2256,6 +2379,8 @@ local function createRootRow(manifest, order)
|
|
|
2256
2379
|
manifest = manifest,
|
|
2257
2380
|
group = group,
|
|
2258
2381
|
row = row,
|
|
2382
|
+
treeOrder = order,
|
|
2383
|
+
connector = connector,
|
|
2259
2384
|
title = title,
|
|
2260
2385
|
icon = icon,
|
|
2261
2386
|
iconClass = if rootInstance then rootInstance.ClassName else "Model",
|
|
@@ -2566,6 +2691,7 @@ refreshConfig = function()
|
|
|
2566
2691
|
for _, state in states do
|
|
2567
2692
|
disconnectRoot(state)
|
|
2568
2693
|
destroyConflictWorkspace(state)
|
|
2694
|
+
state.row:Destroy()
|
|
2569
2695
|
end
|
|
2570
2696
|
disconnectSplitRoots()
|
|
2571
2697
|
table.clear(states)
|
package/package.json
CHANGED
package/src/config.mjs
CHANGED
|
@@ -86,6 +86,10 @@ export async function loadConfig(configPath) {
|
|
|
86
86
|
if (!Array.isArray(raw.roots) || raw.roots.length === 0) {
|
|
87
87
|
throw new Error("roots must contain at least one sync root");
|
|
88
88
|
}
|
|
89
|
+
if (raw.discoveryRoots !== undefined && (!Array.isArray(raw.discoveryRoots)
|
|
90
|
+
|| raw.discoveryRoots.some((root) => !root || root.splitDepth === undefined))) {
|
|
91
|
+
throw new Error("discoveryRoots must contain directory roots with splitDepth");
|
|
92
|
+
}
|
|
89
93
|
const maxPathDepth = raw.maxPathDepth ?? 3;
|
|
90
94
|
if (!Number.isInteger(maxPathDepth) || maxPathDepth < 2 || maxPathDepth > 10) {
|
|
91
95
|
throw new Error("maxPathDepth must be an integer between 2 and 10");
|
|
@@ -95,8 +99,8 @@ export async function loadConfig(configPath) {
|
|
|
95
99
|
const studioPaths = new Set();
|
|
96
100
|
const files = new Set();
|
|
97
101
|
const splitRoots = [];
|
|
98
|
-
const roots = raw.roots.flatMap((root, index) => {
|
|
99
|
-
const label = `roots[${index}]`;
|
|
102
|
+
const roots = [...raw.roots, ...(raw.discoveryRoots ?? [])].flatMap((root, index) => {
|
|
103
|
+
const label = index < raw.roots.length ? `roots[${index}]` : `discoveryRoots[${index - raw.roots.length}]`;
|
|
100
104
|
const id = requireString(root.id, `${label}.id`);
|
|
101
105
|
if (!ID_PATTERN.test(id)) {
|
|
102
106
|
throw new Error(`${label}.id must contain lowercase letters, numbers, or hyphens`);
|
|
@@ -145,6 +149,22 @@ export async function loadConfig(configPath) {
|
|
|
145
149
|
}];
|
|
146
150
|
});
|
|
147
151
|
|
|
152
|
+
const configuredRoots = [...roots, ...splitRoots];
|
|
153
|
+
for (const root of roots) {
|
|
154
|
+
if (configuredRoots.some((other) => other !== root
|
|
155
|
+
&& other.studioPath.length > root.studioPath.length
|
|
156
|
+
&& root.studioPath.every((segment, index) => other.studioPath[index] === segment))) {
|
|
157
|
+
throw new Error(`file root ${root.id} contains another sync root; use a directory root instead`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
for (const root of splitRoots) {
|
|
161
|
+
root.excludedPaths = configuredRoots
|
|
162
|
+
.filter((other) => other !== root
|
|
163
|
+
&& other.studioPath.length > root.studioPath.length
|
|
164
|
+
&& root.studioPath.every((segment, index) => other.studioPath[index] === segment))
|
|
165
|
+
.map((other) => other.studioPath.slice(root.studioPath.length, root.studioPath.length + root.splitDepth));
|
|
166
|
+
}
|
|
167
|
+
|
|
148
168
|
return {
|
|
149
169
|
version: 1,
|
|
150
170
|
name: requireString(raw.name, "name"),
|
package/src/server.mjs
CHANGED
|
@@ -161,6 +161,7 @@ export class GoldSyncServer {
|
|
|
161
161
|
id: root.id,
|
|
162
162
|
studioPath: root.studioPath,
|
|
163
163
|
splitDepth: root.splitDepth,
|
|
164
|
+
excludedPaths: root.excludedPaths ?? [],
|
|
164
165
|
})),
|
|
165
166
|
roots: this.store.getManifests().map((manifest) => {
|
|
166
167
|
const conflict = conflicts.get(manifest.id);
|
package/src/store.mjs
CHANGED
|
@@ -140,7 +140,12 @@ export class SnapshotStore {
|
|
|
140
140
|
}
|
|
141
141
|
let changed = false;
|
|
142
142
|
for (const relativePath of relativePaths) {
|
|
143
|
-
|
|
143
|
+
const root = this.makeSplitRoot(splitRoot, relativePath);
|
|
144
|
+
if ((splitRoot.excludedPaths ?? []).some((excluded) =>
|
|
145
|
+
excluded.every((segment, index) => relativePath[index] === segment))) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
changed = (await this.addRoot(root)) || changed;
|
|
144
149
|
}
|
|
145
150
|
if (changed) {
|
|
146
151
|
await this.persistState();
|