goldsync 0.1.12 → 0.1.14

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.
Files changed (2) hide show
  1. package/assets/GoldSync.rbxmx +198 -37
  2. package/package.json +2 -1
@@ -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.11"
25
+ local VERSION = "0.1.14"
26
26
  local REQUEST_INTERVAL_SECONDS = 0.15
27
27
  local REQUEST_RETRY_DELAYS = { 1, 2, 4 }
28
28
 
@@ -340,6 +340,8 @@ local lastRequestAt = 0
340
340
  local serverConfig
341
341
  local states = {}
342
342
  local groups = {}
343
+ local batchingStatusUpdates = false
344
+ local splitRootConnections = {}
343
345
  local refreshConfig
344
346
  local updateGroupSummaries
345
347
  local refreshStatusSummary
@@ -369,6 +371,31 @@ local function collectSplitPaths(root, depth)
369
371
  return paths
370
372
  end
371
373
 
374
+ local function collectSplitInstances(root, depth)
375
+ local current = { root }
376
+ for _ = 1, depth do
377
+ local nextLevel = {}
378
+ for _, parent in current do
379
+ for _, child in parent:GetChildren() do
380
+ table.insert(nextLevel, child)
381
+ end
382
+ end
383
+ current = nextLevel
384
+ end
385
+ return current
386
+ end
387
+
388
+ local function isAtSplitDepth(root, instance, depth)
389
+ local current = instance
390
+ for _ = 1, depth do
391
+ if not current or current == root then
392
+ return false
393
+ end
394
+ current = current.Parent
395
+ end
396
+ return current == root
397
+ end
398
+
372
399
  local function acquireRequestLock()
373
400
  if requestLocked then
374
401
  local waiter = Instance.new("BindableEvent")
@@ -464,6 +491,113 @@ local function resolveParent(studioPath)
464
491
  return current
465
492
  end
466
493
 
494
+ local function disconnectSplitRoot(id)
495
+ local watched = splitRootConnections[id]
496
+ if not watched then
497
+ return
498
+ end
499
+ for _, connection in watched.connections do
500
+ connection:Disconnect()
501
+ end
502
+ for _, connection in watched.nameConnections do
503
+ connection:Disconnect()
504
+ end
505
+ splitRootConnections[id] = nil
506
+ end
507
+
508
+ local function disconnectSplitRoots()
509
+ local ids = {}
510
+ for id in splitRootConnections do
511
+ table.insert(ids, id)
512
+ end
513
+ for _, id in ids do
514
+ disconnectSplitRoot(id)
515
+ end
516
+ end
517
+
518
+ local function discoverSplitRoot(splitRoot, root)
519
+ if not serverConfig or splitRootConnections[splitRoot.id] == nil then
520
+ return
521
+ end
522
+ local existingPaths = {}
523
+ for _, manifest in serverConfig.roots do
524
+ if manifest.splitRootId == splitRoot.id then
525
+ existingPaths[table.concat(manifest.studioPath, "\0")] = true
526
+ end
527
+ end
528
+ local missingPaths = {}
529
+ for _, relativePath in collectSplitPaths(root, splitRoot.splitDepth) do
530
+ local fullPath = table.clone(splitRoot.studioPath)
531
+ for _, segment in relativePath do
532
+ table.insert(fullPath, segment)
533
+ end
534
+ if not existingPaths[table.concat(fullPath, "\0")] then
535
+ table.insert(missingPaths, relativePath)
536
+ end
537
+ end
538
+ if #missingPaths > 0 then
539
+ request("POST", "/v1/splits/" .. splitRoot.id .. "/discover", HttpService:JSONEncode({ paths = missingPaths }), {
540
+ ["Content-Type"] = "application/json",
541
+ })
542
+ end
543
+ end
544
+
545
+ local function watchSplitRoot(splitRoot)
546
+ local root = resolveRoot(splitRoot.studioPath)
547
+ local watched = splitRootConnections[splitRoot.id]
548
+ if watched and watched.root == root then
549
+ return
550
+ end
551
+ disconnectSplitRoot(splitRoot.id)
552
+ if not root then
553
+ return
554
+ end
555
+
556
+ local entry = {
557
+ root = root,
558
+ generation = 0,
559
+ connections = {},
560
+ nameConnections = {},
561
+ }
562
+ splitRootConnections[splitRoot.id] = entry
563
+ local function scheduleDiscovery()
564
+ entry.generation += 1
565
+ local generation = entry.generation
566
+ task.delay(0.1, function()
567
+ if running and splitRootConnections[splitRoot.id] == entry and entry.generation == generation then
568
+ discoverSplitRoot(splitRoot, root)
569
+ end
570
+ end)
571
+ end
572
+ local function watchName(instance)
573
+ if entry.nameConnections[instance] or not isAtSplitDepth(root, instance, splitRoot.splitDepth) then
574
+ return
575
+ end
576
+ entry.nameConnections[instance] = instance:GetPropertyChangedSignal("Name"):Connect(scheduleDiscovery)
577
+ end
578
+ for _, instance in collectSplitInstances(root, splitRoot.splitDepth) do
579
+ watchName(instance)
580
+ end
581
+ table.insert(entry.connections, root.DescendantAdded:Connect(function(descendant)
582
+ watchName(descendant)
583
+ scheduleDiscovery()
584
+ end))
585
+ table.insert(entry.connections, root.DescendantRemoving:Connect(function(descendant)
586
+ local connection = entry.nameConnections[descendant]
587
+ if connection then
588
+ connection:Disconnect()
589
+ entry.nameConnections[descendant] = nil
590
+ end
591
+ scheduleDiscovery()
592
+ end))
593
+ table.insert(entry.connections, root.AncestryChanged:Connect(function()
594
+ if resolveRoot(splitRoot.studioPath) ~= root then
595
+ disconnectSplitRoot(splitRoot.id)
596
+ end
597
+ end))
598
+ discoverSplitRoot(splitRoot, root)
599
+ end
600
+
467
601
  local function applyClassIcon(image, className)
468
602
  local success, icon = pcall(function()
469
603
  return StudioService:GetClassIcon(className)
@@ -499,12 +633,8 @@ local function findCode(root)
499
633
  end
500
634
 
501
635
  local function setStatus(state, text, color)
502
- local previousStatusKind = state.statusKind
503
- state.status.Text = text
504
- state.status.TextColor3 = color or Color3.fromRGB(180, 180, 190)
505
- state.dot.TextColor3 = color or Color3.fromRGB(180, 180, 190)
506
- state.searchQuery = nil
507
- state.statusKind = if string.sub(text, 1, 6) == "Synced"
636
+ local statusColor = color or Color3.fromRGB(180, 180, 190)
637
+ local statusKind = if string.sub(text, 1, 6) == "Synced"
508
638
  then "synced"
509
639
  elseif string.find(text, "syncing")
510
640
  or string.find(text, "Serializing")
@@ -514,7 +644,16 @@ local function setStatus(state, text, color)
514
644
  then "working"
515
645
  elseif string.find(text, "Rojo") and string.find(text, "offline") then "paused"
516
646
  else "issue"
517
- if updateGroupSummaries then
647
+ if state.status.Text == text and state.status.TextColor3 == statusColor and state.statusKind == statusKind then
648
+ return
649
+ end
650
+ local previousStatusKind = state.statusKind
651
+ state.status.Text = text
652
+ state.status.TextColor3 = statusColor
653
+ state.dot.TextColor3 = statusColor
654
+ state.searchQuery = nil
655
+ state.statusKind = statusKind
656
+ if updateGroupSummaries and not batchingStatusUpdates then
518
657
  if previousStatusKind ~= state.statusKind then
519
658
  updateGroupSummaries()
520
659
  elseif refreshStatusSummary then
@@ -2393,6 +2532,19 @@ searchBox:GetPropertyChangedSignal("Text"):Connect(function()
2393
2532
  end)
2394
2533
  end)
2395
2534
 
2535
+ local function manifestChanged(previous, current)
2536
+ return not previous
2537
+ or previous.hash ~= current.hash
2538
+ or previous.exists ~= current.exists
2539
+ or previous.deleted ~= current.deleted
2540
+ or previous.revision ~= current.revision
2541
+ or previous.gitConflict ~= current.gitConflict
2542
+ or previous.conflictToken ~= current.conflictToken
2543
+ or previous.file ~= current.file
2544
+ or previous.splitRootId ~= current.splitRootId
2545
+ or table.concat(previous.studioPath, "\0") ~= table.concat(current.studioPath, "\0")
2546
+ end
2547
+
2396
2548
  refreshConfig = function()
2397
2549
  local response, requestError = request("GET", "/v1/config")
2398
2550
  if not response or not response.Success then
@@ -2407,13 +2559,15 @@ refreshConfig = function()
2407
2559
  end
2408
2560
 
2409
2561
  local decoded = HttpService:JSONDecode(response.Body)
2410
- if serverConfig
2562
+ local projectChanged = serverConfig
2411
2563
  and (serverConfig.projectId ~= decoded.projectId or serverConfig.workspaceId ~= decoded.workspaceId)
2412
- then
2564
+ local connectionChanged = serverConfig and serverConfig.rojoConnected ~= decoded.rojoConnected
2565
+ if projectChanged then
2413
2566
  for _, state in states do
2414
2567
  disconnectRoot(state)
2415
2568
  destroyConflictWorkspace(state)
2416
2569
  end
2570
+ disconnectSplitRoots()
2417
2571
  table.clear(states)
2418
2572
  for _, child in rootsContainer:GetChildren() do
2419
2573
  if child ~= rootsLayout then
@@ -2432,36 +2586,38 @@ refreshConfig = function()
2432
2586
  end
2433
2587
  connectionDot.TextColor3 = connectionStatus.TextColor3
2434
2588
 
2435
- local existingSplitPaths = {}
2436
- for _, manifest in decoded.roots do
2437
- if manifest.splitRootId then
2438
- existingSplitPaths[manifest.splitRootId .. "\0" .. table.concat(manifest.studioPath, "\0")] = true
2439
- end
2440
- end
2589
+ local currentSplitRootIds = {}
2441
2590
  for _, splitRoot in decoded.splitRoots do
2442
- local root = resolveRoot(splitRoot.studioPath)
2443
- if root then
2444
- local missingPaths = {}
2445
- for _, relativePath in collectSplitPaths(root, splitRoot.splitDepth) do
2446
- local fullPath = table.clone(splitRoot.studioPath)
2447
- for _, segment in relativePath do
2448
- table.insert(fullPath, segment)
2449
- end
2450
- if not existingSplitPaths[splitRoot.id .. "\0" .. table.concat(fullPath, "\0")] then
2451
- table.insert(missingPaths, relativePath)
2452
- end
2453
- end
2454
- if #missingPaths > 0 then
2455
- request("POST", "/v1/splits/" .. splitRoot.id .. "/discover", HttpService:JSONEncode({ paths = missingPaths }), {
2456
- ["Content-Type"] = "application/json",
2457
- })
2458
- end
2591
+ currentSplitRootIds[splitRoot.id] = true
2592
+ watchSplitRoot(splitRoot)
2593
+ end
2594
+ local staleSplitRootIds = {}
2595
+ for id in splitRootConnections do
2596
+ if not currentSplitRootIds[id] then
2597
+ table.insert(staleSplitRootIds, id)
2459
2598
  end
2460
2599
  end
2600
+ for _, id in staleSplitRootIds do
2601
+ disconnectSplitRoot(id)
2602
+ end
2461
2603
 
2604
+ local treeChanged = projectChanged == true or connectionChanged == true
2605
+ batchingStatusUpdates = true
2462
2606
  for order, manifest in decoded.roots do
2463
- local state = states[manifest.id] or createRootRow(manifest, order)
2464
- reconcile(state, manifest)
2607
+ local state = states[manifest.id]
2608
+ local created = state == nil
2609
+ if created then
2610
+ state = createRootRow(manifest, order)
2611
+ end
2612
+ local resolvedRoot = resolveRoot(manifest.studioPath)
2613
+ local rootChanged = state.observedRoot ~= resolvedRoot
2614
+ state.observedRoot = resolvedRoot
2615
+ if created or connectionChanged or rootChanged or manifestChanged(state.manifest, manifest) then
2616
+ reconcile(state, manifest)
2617
+ treeChanged = true
2618
+ else
2619
+ state.manifest = manifest
2620
+ end
2465
2621
  end
2466
2622
  local currentIds = {}
2467
2623
  for _, manifest in decoded.roots do
@@ -2479,9 +2635,13 @@ refreshConfig = function()
2479
2635
  destroyConflictWorkspace(state)
2480
2636
  state.row:Destroy()
2481
2637
  states[id] = nil
2638
+ treeChanged = true
2639
+ end
2640
+ batchingStatusUpdates = false
2641
+ if treeChanged then
2642
+ updateGroupSummaries()
2643
+ applyTreeFilter()
2482
2644
  end
2483
- updateGroupSummaries()
2484
- applyTreeFilter()
2485
2645
  return true
2486
2646
  end
2487
2647
 
@@ -2497,6 +2657,7 @@ end)
2497
2657
 
2498
2658
  plugin.Unloading:Connect(function()
2499
2659
  running = false
2660
+ disconnectSplitRoots()
2500
2661
  for _, state in states do
2501
2662
  disconnectRoot(state)
2502
2663
  destroyConflictWorkspace(state)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "goldsync",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "Automatic native Roblox asset synchronization for Rojo projects.",
5
5
  "author": "tay",
6
6
  "license": "SEE LICENSE IN LICENSE.txt",
@@ -20,6 +20,7 @@
20
20
  "scripts": {
21
21
  "test": "node --test",
22
22
  "package:companion": "node scripts/package-companion.mjs",
23
+ "prepublishOnly": "npm test",
23
24
  "prepack": "node scripts/prepare-npm-package.mjs",
24
25
  "postpack": "node scripts/cleanup-npm-package.mjs"
25
26
  },