@mflrevan/ucp 0.5.0 → 0.5.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.
@@ -22,11 +22,14 @@ namespace UCP.Bridge.Tests
22
22
  private const string TempScriptPath = "Assets/UcpControllerSmokeComponent.cs";
23
23
  private const string TempTexturePath = "Assets/UcpImporterSmoke.png";
24
24
  private const string TempScenePath = "Assets/UcpControllerMoveScene.unity";
25
+ private const string TempSceneBPath = "Assets/UcpControllerSceneB.unity";
25
26
  private const string TempMovedFolderPath = "Assets/UcpControllerMoved";
26
27
  private const string TempMovedAssetPath = "Assets/UcpControllerMoved/UcpControllerSmoke.asset";
27
28
  private const string TempMovedReferenceAssetPath = "Assets/UcpControllerMoved/UcpControllerReference.asset";
28
29
  private const string TempMovedMaterialPath = "Assets/UcpControllerMoved/UcpControllerSmoke.mat";
29
30
  private const string TempMovedScenePath = "Assets/UcpControllerMoved/UcpControllerMoveScene.unity";
31
+ private const string TempReimportFolderPath = "Assets/UcpRecursiveReimport";
32
+ private const string TempReimportTexturePath = "Assets/UcpRecursiveReimport/UcpRecursiveA.png";
30
33
  private const string TempProfilerExportPath = "ProfilerCaptures\\smoke-export.json";
31
34
  private const string TempLocalPackageFolder = "TempUcpLocalPackage";
32
35
  private const string TempLocalPackageName = "com.ucp.temp.local";
@@ -40,6 +43,8 @@ namespace UCP.Bridge.Tests
40
43
  SnapshotController.Register(_router);
41
44
  AssetController.Register(_router);
42
45
  ImporterController.Register(_router);
46
+ PlayModeController.Register(_router);
47
+ ReferenceController.Register(_router);
43
48
  LogsController.Register(_router);
44
49
  HierarchyController.Register(_router);
45
50
  ProfilerController.Register(_router);
@@ -60,7 +65,9 @@ namespace UCP.Bridge.Tests
60
65
  DeleteTempScriptFile();
61
66
  DeleteTempTextureAsset();
62
67
  DeleteTempScene();
68
+ DeleteTempSceneB();
63
69
  DeleteTempMovedFolder();
70
+ DeleteTempReimportFolder();
64
71
  DeleteTempProfilerExport();
65
72
  DeleteTempLocalPackage();
66
73
  RemoveTempLocalPackageDependencyIfPresent();
@@ -83,7 +90,9 @@ namespace UCP.Bridge.Tests
83
90
  DeleteTempScriptFile();
84
91
  DeleteTempTextureAsset();
85
92
  DeleteTempScene();
93
+ DeleteTempSceneB();
86
94
  DeleteTempMovedFolder();
95
+ DeleteTempReimportFolder();
87
96
  DeleteTempProfilerExport();
88
97
  DeleteTempLocalPackage();
89
98
  RemoveTempLocalPackageDependencyIfPresent();
@@ -246,6 +255,64 @@ namespace UCP.Bridge.Tests
246
255
  Assert.That(Convert.ToBoolean(match["isSubAsset"]), Is.True);
247
256
  }
248
257
 
258
+ [Test]
259
+ public void AssetSearch_SupportsRegexNameFiltering()
260
+ {
261
+ const string RegexAssetPath = "Assets/SCN_101.asset";
262
+
263
+ try
264
+ {
265
+ var sceneAsset = ScriptableObject.CreateInstance<SearchRootAsset>();
266
+ sceneAsset.name = "SCN_101";
267
+ AssetDatabase.CreateAsset(sceneAsset, RegexAssetPath);
268
+ AssetDatabase.SaveAssets();
269
+
270
+ var response = _router.Dispatch(
271
+ "asset/search",
272
+ 1,
273
+ "{\"name\":\"^SCN_[0-9]+$\",\"regex\":true,\"path\":\"Assets\",\"maxResults\":10}");
274
+
275
+ Assert.That(response.error, Is.Null);
276
+
277
+ var result = (Dictionary<string, object>)response.result;
278
+ var matches = (List<object>)result["results"];
279
+ var match = FindAssetMatchByPath(matches, RegexAssetPath);
280
+ Assert.That(match, Is.Not.Null);
281
+ }
282
+ finally
283
+ {
284
+ if (AssetDatabase.AssetPathExists(RegexAssetPath))
285
+ {
286
+ AssetDatabase.DeleteAsset(RegexAssetPath);
287
+ AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport);
288
+ }
289
+ }
290
+ }
291
+
292
+ [Test]
293
+ public void AssetSearch_DoesNotEmitSceneReadObjectThreadedErrors()
294
+ {
295
+ EditorSceneManager.NewScene(NewSceneSetup.DefaultGameObjects, NewSceneMode.Single);
296
+ EditorSceneManager.SaveScene(UnityEngine.SceneManagement.SceneManager.GetActiveScene(), TempScenePath);
297
+ AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport);
298
+ LogsController.ClearHistoryForTests();
299
+
300
+ var response = _router.Dispatch(
301
+ "asset/search",
302
+ 1,
303
+ "{\"name\":\"UcpControllerMoveScene\",\"path\":\"Assets\",\"maxResults\":10}");
304
+
305
+ Assert.That(response.error, Is.Null);
306
+
307
+ var status = _router.Dispatch("logs/status", 1, "{}");
308
+ Assert.That(status.error, Is.Null);
309
+
310
+ var result = (Dictionary<string, object>)status.result;
311
+ var byLevel = (Dictionary<string, object>)result["byLevel"];
312
+ Assert.That(Convert.ToInt32(byLevel["error"]), Is.EqualTo(0));
313
+ Assert.That(Convert.ToInt32(byLevel["exception"]), Is.EqualTo(0));
314
+ }
315
+
249
316
  [Test]
250
317
  public void LogsTail_ReturnsRequestedBufferedCount()
251
318
  {
@@ -344,6 +411,102 @@ namespace UCP.Bridge.Tests
344
411
  Assert.That(result["stackTrace"], Is.EqualTo("stack line 1\nstack line 2"));
345
412
  }
346
413
 
414
+ [Test]
415
+ public void LogsStatus_CanScopeToNewEntriesAfterCursor()
416
+ {
417
+ var first = LogsController.RecordTestLog("info", "Before");
418
+ LogsController.RecordTestLog("warning", "After warning");
419
+ LogsController.RecordTestLog("error", "After error");
420
+
421
+ var response = _router.Dispatch(
422
+ "logs/status",
423
+ 1,
424
+ "{\"afterId\":" + Convert.ToInt64(first["id"]) + "}");
425
+
426
+ Assert.That(response.error, Is.Null);
427
+
428
+ var result = (Dictionary<string, object>)response.result;
429
+ Assert.That(Convert.ToInt64(result["afterId"]), Is.EqualTo(Convert.ToInt64(first["id"])));
430
+ Assert.That(Convert.ToInt32(result["total"]), Is.EqualTo(2));
431
+
432
+ var byLevel = (Dictionary<string, object>)result["byLevel"];
433
+ Assert.That(Convert.ToInt32(byLevel["info"]), Is.EqualTo(0));
434
+ Assert.That(Convert.ToInt32(byLevel["warning"]), Is.EqualTo(1));
435
+ Assert.That(Convert.ToInt32(byLevel["error"]), Is.EqualTo(1));
436
+ }
437
+
438
+ [Test]
439
+ public void LogsStatus_BackfillsConsoleEntriesWhenHistoryStartsEmpty()
440
+ {
441
+ LogsController.SetConsoleBackfillProviderForTests(() => new List<LogsController.ConsoleBackfillEntry>
442
+ {
443
+ new LogsController.ConsoleBackfillEntry { Level = "warning", Message = "Compiler warning" },
444
+ new LogsController.ConsoleBackfillEntry { Level = "error", Message = "Compiler error" }
445
+ });
446
+
447
+ var response = _router.Dispatch("logs/status", 1, "{}");
448
+
449
+ Assert.That(response.error, Is.Null);
450
+
451
+ var result = (Dictionary<string, object>)response.result;
452
+ Assert.That(Convert.ToInt32(result["total"]), Is.EqualTo(2));
453
+
454
+ var byLevel = (Dictionary<string, object>)result["byLevel"];
455
+ Assert.That(Convert.ToInt32(byLevel["warning"]), Is.EqualTo(1));
456
+ Assert.That(Convert.ToInt32(byLevel["error"]), Is.EqualTo(1));
457
+ }
458
+
459
+ [Test]
460
+ public void LogsTail_BackfillIgnoresUcpBridgeNoise()
461
+ {
462
+ LogsController.SetConsoleBackfillProviderForTests(() => new List<LogsController.ConsoleBackfillEntry>
463
+ {
464
+ new LogsController.ConsoleBackfillEntry { Level = "info", Message = "[UCP] Bridge server started on port 21342" },
465
+ new LogsController.ConsoleBackfillEntry { Level = "warning", Message = "User-facing warning" }
466
+ });
467
+
468
+ var response = _router.Dispatch("logs/tail", 1, "{\"count\":20}");
469
+
470
+ Assert.That(response.error, Is.Null);
471
+
472
+ var result = (Dictionary<string, object>)response.result;
473
+ Assert.That(Convert.ToInt32(result["total"]), Is.EqualTo(1));
474
+
475
+ var logs = (List<object>)result["logs"];
476
+ var only = (Dictionary<string, object>)logs[0];
477
+ Assert.That(only["messagePreview"], Is.EqualTo("User-facing warning"));
478
+ }
479
+
480
+ [Test]
481
+ public void LogsGet_CanReadBackfilledConsoleEntries()
482
+ {
483
+ LogsController.SetConsoleBackfillProviderForTests(() => new List<LogsController.ConsoleBackfillEntry>
484
+ {
485
+ new LogsController.ConsoleBackfillEntry
486
+ {
487
+ Level = "exception",
488
+ Message = "Backfilled exception",
489
+ StackTrace = "stack line 1\nstack line 2"
490
+ }
491
+ });
492
+
493
+ var tail = _router.Dispatch("logs/tail", 1, "{\"count\":20}");
494
+ Assert.That(tail.error, Is.Null);
495
+
496
+ var tailResult = (Dictionary<string, object>)tail.result;
497
+ var logs = (List<object>)tailResult["logs"];
498
+ var first = (Dictionary<string, object>)logs[0];
499
+ var id = Convert.ToInt64(first["id"]);
500
+
501
+ var get = _router.Dispatch("logs/get", 1, "{\"id\":" + id + "}");
502
+ Assert.That(get.error, Is.Null);
503
+
504
+ var getResult = (Dictionary<string, object>)get.result;
505
+ Assert.That(getResult["level"], Is.EqualTo("exception"));
506
+ Assert.That(getResult["message"], Is.EqualTo("Backfilled exception"));
507
+ Assert.That(getResult["stackTrace"], Is.EqualTo("stack line 1\nstack line 2"));
508
+ }
509
+
347
510
  [Test]
348
511
  public void ObjectLifecycle_CreateMutateAndDelete_WorksEndToEnd()
349
512
  {
@@ -556,6 +719,28 @@ namespace UCP.Bridge.Tests
556
719
  Assert.That(AssetDatabase.GetAssetPath(movedAsset.referenceAsset), Is.EqualTo(TempMovedReferenceAssetPath));
557
720
  }
558
721
 
722
+ [Test]
723
+ public void AssetBulkMove_DryRunDoesNotMutateProject()
724
+ {
725
+ var asset = ScriptableObject.CreateInstance<SearchRootAsset>();
726
+ asset.name = "DryRunAsset";
727
+ AssetDatabase.CreateAsset(asset, TempAssetPath);
728
+ AssetDatabase.SaveAssets();
729
+
730
+ var response = _router.Dispatch(
731
+ "asset/bulk-move",
732
+ 1,
733
+ "{\"dryRun\":true,\"moves\":[{\"from\":\"" + TempAssetPath + "\",\"to\":\"" + TempMovedAssetPath + "\"}]}");
734
+
735
+ Assert.That(response.error, Is.Null);
736
+
737
+ var result = (Dictionary<string, object>)response.result;
738
+ Assert.That(Convert.ToBoolean(result["dryRun"]), Is.True);
739
+ Assert.That(Convert.ToInt32(result["moved"]), Is.EqualTo(1));
740
+ Assert.That(AssetDatabase.LoadAssetAtPath<SearchRootAsset>(TempAssetPath), Is.Not.Null);
741
+ Assert.That(AssetDatabase.LoadAssetAtPath<SearchRootAsset>(TempMovedAssetPath), Is.Null);
742
+ }
743
+
559
744
  [Test]
560
745
  public void AssetMove_PreservesSceneMaterialReference()
561
746
  {
@@ -758,6 +943,53 @@ namespace UCP.Bridge.Tests
758
943
  Assert.That(importer.isReadable, Is.True);
759
944
  }
760
945
 
946
+ [Test]
947
+ public void ImporterController_ReimportRecursive_ReimportsFolderContents()
948
+ {
949
+ CreateTempRecursiveTextureAsset(Color.magenta);
950
+ AssetImportSupport.ClearTestState();
951
+
952
+ var response = _router.Dispatch(
953
+ "asset/reimport",
954
+ 1,
955
+ "{\"path\":\"" + TempReimportFolderPath + "\",\"recursive\":true}");
956
+
957
+ Assert.That(response.error, Is.Null);
958
+
959
+ var result = (Dictionary<string, object>)response.result;
960
+ Assert.That(Convert.ToBoolean(result["recursive"]), Is.True);
961
+ Assert.That(Convert.ToInt32(result["requested"]), Is.GreaterThanOrEqualTo(1));
962
+ Assert.That(Convert.ToInt32(result["reimported"]), Is.GreaterThanOrEqualTo(1));
963
+ Assert.That(AssetImportSupport.LastReimportedPathForTests, Is.EqualTo(TempReimportTexturePath));
964
+ }
965
+
966
+ [Test]
967
+ public void SceneController_Load_AdditiveKeepsExistingSceneLoaded()
968
+ {
969
+ var firstScene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
970
+ new GameObject("SceneAObject");
971
+ Assert.That(EditorSceneManager.SaveScene(firstScene, TempScenePath), Is.True);
972
+
973
+ var secondScene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
974
+ new GameObject("SceneBObject");
975
+ Assert.That(EditorSceneManager.SaveScene(secondScene, TempSceneBPath), Is.True);
976
+
977
+ EditorSceneManager.OpenScene(TempScenePath, OpenSceneMode.Single);
978
+
979
+ var response = _router.Dispatch(
980
+ "scene/load",
981
+ 1,
982
+ "{\"path\":\"" + TempSceneBPath + "\",\"additive\":true,\"saveDirtyScenes\":true,\"discardUntitled\":true}");
983
+
984
+ Assert.That(response.error, Is.Null);
985
+
986
+ var result = (Dictionary<string, object>)response.result;
987
+ Assert.That(Convert.ToBoolean(result["additive"]), Is.True);
988
+ Assert.That(UnityEngine.SceneManagement.SceneManager.sceneCount, Is.EqualTo(2));
989
+ Assert.That(UnityEngine.SceneManagement.SceneManager.GetSceneByPath(TempScenePath).isLoaded, Is.True);
990
+ Assert.That(UnityEngine.SceneManagement.SceneManager.GetSceneByPath(TempSceneBPath).isLoaded, Is.True);
991
+ }
992
+
761
993
  [Test]
762
994
  public void PackagesController_DependencySetInfoAndRemove_LocalFilePackage()
763
995
  {
@@ -1092,6 +1324,15 @@ namespace UCP.Bridge.Tests
1092
1324
  }
1093
1325
  }
1094
1326
 
1327
+ private static void DeleteTempSceneB()
1328
+ {
1329
+ if (AssetDatabase.LoadMainAssetAtPath(TempSceneBPath) != null)
1330
+ {
1331
+ AssetDatabase.DeleteAsset(TempSceneBPath);
1332
+ AssetDatabase.SaveAssets();
1333
+ }
1334
+ }
1335
+
1095
1336
  private static void DeleteTempMovedFolder()
1096
1337
  {
1097
1338
  if (AssetDatabase.IsValidFolder(TempMovedFolderPath))
@@ -1101,6 +1342,15 @@ namespace UCP.Bridge.Tests
1101
1342
  }
1102
1343
  }
1103
1344
 
1345
+ private static void DeleteTempReimportFolder()
1346
+ {
1347
+ if (AssetDatabase.IsValidFolder(TempReimportFolderPath))
1348
+ {
1349
+ AssetDatabase.DeleteAsset(TempReimportFolderPath);
1350
+ AssetDatabase.SaveAssets();
1351
+ }
1352
+ }
1353
+
1104
1354
  private static void DeleteTempProfilerExport()
1105
1355
  {
1106
1356
  var fullPath = ResolveProjectRelativePath(TempProfilerExportPath);
@@ -1180,6 +1430,24 @@ namespace UCP.Bridge.Tests
1180
1430
  ImportAssetOptions.ForceUpdate | ImportAssetOptions.ForceSynchronousImport);
1181
1431
  }
1182
1432
 
1433
+ private static void CreateTempRecursiveTextureAsset(Color color)
1434
+ {
1435
+ DeleteTempReimportFolder();
1436
+ Directory.CreateDirectory(ResolveProjectRelativePath(TempReimportFolderPath));
1437
+
1438
+ var texture = new Texture2D(2, 2, TextureFormat.RGBA32, false);
1439
+ texture.SetPixels(new[] { color, color, color, color });
1440
+ texture.Apply();
1441
+
1442
+ var bytes = texture.EncodeToPNG();
1443
+ UnityEngine.Object.DestroyImmediate(texture);
1444
+
1445
+ File.WriteAllBytes(ResolveProjectRelativePath(TempReimportTexturePath), bytes);
1446
+ AssetDatabase.ImportAsset(
1447
+ TempReimportTexturePath,
1448
+ ImportAssetOptions.ForceUpdate | ImportAssetOptions.ForceSynchronousImport);
1449
+ }
1450
+
1183
1451
  private static string ResolveProjectRelativePath(string assetPath)
1184
1452
  {
1185
1453
  var projectRoot = Path.GetDirectoryName(Application.dataPath);
@@ -1228,6 +1496,19 @@ namespace UCP.Bridge.Tests
1228
1496
  return null;
1229
1497
  }
1230
1498
 
1499
+ private static Dictionary<string, object> FindAssetMatchByPath(List<object> matches, string expectedPath)
1500
+ {
1501
+ foreach (var entry in matches)
1502
+ {
1503
+ var match = (Dictionary<string, object>)entry;
1504
+ var path = match.ContainsKey("path") ? match["path"].ToString() : string.Empty;
1505
+ if (path == expectedPath)
1506
+ return match;
1507
+ }
1508
+
1509
+ return null;
1510
+ }
1511
+
1231
1512
  private sealed class SearchRootAsset : ScriptableObject
1232
1513
  {
1233
1514
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "com.ucp.bridge",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "displayName": "Unity Control Protocol Bridge",
5
5
  "description": "WebSocket bridge for programmatic Unity Editor control via CLI and AI agents.",
6
6
  "unity": "2021.3",
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@mflrevan/ucp",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Unity Control Protocol - CLI for programmatic Unity Editor control",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
8
- "url": "https://github.com/mflRevan/unity-control-protocol.git"
8
+ "url": "git+https://github.com/mflRevan/unity-control-protocol.git"
9
9
  },
10
10
  "bin": {
11
11
  "ucp": "bin/ucp.js"