@mflrevan/ucp 0.4.4 → 0.4.6

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 (84) hide show
  1. package/README.md +1 -1
  2. package/bridge/com.ucp.bridge/CHANGELOG.md +145 -0
  3. package/bridge/com.ucp.bridge/CHANGELOG.md.meta +7 -0
  4. package/bridge/com.ucp.bridge/Editor/Bridge/BridgeServer.cs +583 -0
  5. package/bridge/com.ucp.bridge/Editor/Bridge/BridgeServer.cs.meta +2 -0
  6. package/bridge/com.ucp.bridge/Editor/Bridge.meta +8 -0
  7. package/bridge/com.ucp.bridge/Editor/Compatibility/UnityObjectCompat.cs +18 -0
  8. package/bridge/com.ucp.bridge/Editor/Compatibility/UnityObjectCompat.cs.meta +2 -0
  9. package/bridge/com.ucp.bridge/Editor/Compatibility.meta +8 -0
  10. package/bridge/com.ucp.bridge/Editor/Controllers/AssetController.cs +425 -0
  11. package/bridge/com.ucp.bridge/Editor/Controllers/AssetController.cs.meta +2 -0
  12. package/bridge/com.ucp.bridge/Editor/Controllers/AssetImportSupport.cs +355 -0
  13. package/bridge/com.ucp.bridge/Editor/Controllers/AssetImportSupport.cs.meta +2 -0
  14. package/bridge/com.ucp.bridge/Editor/Controllers/BuildController.cs +233 -0
  15. package/bridge/com.ucp.bridge/Editor/Controllers/BuildController.cs.meta +2 -0
  16. package/bridge/com.ucp.bridge/Editor/Controllers/CompilationController.cs +26 -0
  17. package/bridge/com.ucp.bridge/Editor/Controllers/CompilationController.cs.meta +2 -0
  18. package/bridge/com.ucp.bridge/Editor/Controllers/EditorController.cs +31 -0
  19. package/bridge/com.ucp.bridge/Editor/Controllers/EditorController.cs.meta +2 -0
  20. package/bridge/com.ucp.bridge/Editor/Controllers/EditorSettingsController.cs +527 -0
  21. package/bridge/com.ucp.bridge/Editor/Controllers/EditorSettingsController.cs.meta +2 -0
  22. package/bridge/com.ucp.bridge/Editor/Controllers/FileController.cs +141 -0
  23. package/bridge/com.ucp.bridge/Editor/Controllers/FileController.cs.meta +2 -0
  24. package/bridge/com.ucp.bridge/Editor/Controllers/HierarchyController.cs +326 -0
  25. package/bridge/com.ucp.bridge/Editor/Controllers/HierarchyController.cs.meta +2 -0
  26. package/bridge/com.ucp.bridge/Editor/Controllers/ImporterController.cs +209 -0
  27. package/bridge/com.ucp.bridge/Editor/Controllers/ImporterController.cs.meta +2 -0
  28. package/bridge/com.ucp.bridge/Editor/Controllers/LogsController.cs +409 -0
  29. package/bridge/com.ucp.bridge/Editor/Controllers/LogsController.cs.meta +2 -0
  30. package/bridge/com.ucp.bridge/Editor/Controllers/MaterialController.cs +354 -0
  31. package/bridge/com.ucp.bridge/Editor/Controllers/MaterialController.cs.meta +2 -0
  32. package/bridge/com.ucp.bridge/Editor/Controllers/ObjectReferenceResolver.cs +93 -0
  33. package/bridge/com.ucp.bridge/Editor/Controllers/ObjectReferenceResolver.cs.meta +2 -0
  34. package/bridge/com.ucp.bridge/Editor/Controllers/PackagesController.cs +503 -0
  35. package/bridge/com.ucp.bridge/Editor/Controllers/PackagesController.cs.meta +2 -0
  36. package/bridge/com.ucp.bridge/Editor/Controllers/PlayModeController.cs +188 -0
  37. package/bridge/com.ucp.bridge/Editor/Controllers/PlayModeController.cs.meta +2 -0
  38. package/bridge/com.ucp.bridge/Editor/Controllers/PrefabController.cs +260 -0
  39. package/bridge/com.ucp.bridge/Editor/Controllers/PrefabController.cs.meta +2 -0
  40. package/bridge/com.ucp.bridge/Editor/Controllers/ProfilerController.cs +1679 -0
  41. package/bridge/com.ucp.bridge/Editor/Controllers/ProfilerController.cs.meta +2 -0
  42. package/bridge/com.ucp.bridge/Editor/Controllers/PropertyController.cs +579 -0
  43. package/bridge/com.ucp.bridge/Editor/Controllers/PropertyController.cs.meta +2 -0
  44. package/bridge/com.ucp.bridge/Editor/Controllers/SceneChangeTracker.cs +166 -0
  45. package/bridge/com.ucp.bridge/Editor/Controllers/SceneChangeTracker.cs.meta +2 -0
  46. package/bridge/com.ucp.bridge/Editor/Controllers/SceneController.cs +318 -0
  47. package/bridge/com.ucp.bridge/Editor/Controllers/SceneController.cs.meta +2 -0
  48. package/bridge/com.ucp.bridge/Editor/Controllers/ScreenshotController.cs +125 -0
  49. package/bridge/com.ucp.bridge/Editor/Controllers/ScreenshotController.cs.meta +2 -0
  50. package/bridge/com.ucp.bridge/Editor/Controllers/ScriptController.cs +104 -0
  51. package/bridge/com.ucp.bridge/Editor/Controllers/ScriptController.cs.meta +2 -0
  52. package/bridge/com.ucp.bridge/Editor/Controllers/SnapshotController.cs +227 -0
  53. package/bridge/com.ucp.bridge/Editor/Controllers/SnapshotController.cs.meta +2 -0
  54. package/bridge/com.ucp.bridge/Editor/Controllers/TestRunnerController.cs +240 -0
  55. package/bridge/com.ucp.bridge/Editor/Controllers/TestRunnerController.cs.meta +2 -0
  56. package/bridge/com.ucp.bridge/Editor/Controllers/VcsController.cs +611 -0
  57. package/bridge/com.ucp.bridge/Editor/Controllers/VcsController.cs.meta +2 -0
  58. package/bridge/com.ucp.bridge/Editor/Controllers.meta +8 -0
  59. package/bridge/com.ucp.bridge/Editor/Protocol/CommandRouter.cs +53 -0
  60. package/bridge/com.ucp.bridge/Editor/Protocol/CommandRouter.cs.meta +2 -0
  61. package/bridge/com.ucp.bridge/Editor/Protocol/MessageTypes.cs +80 -0
  62. package/bridge/com.ucp.bridge/Editor/Protocol/MessageTypes.cs.meta +2 -0
  63. package/bridge/com.ucp.bridge/Editor/Protocol/MiniJson.cs +358 -0
  64. package/bridge/com.ucp.bridge/Editor/Protocol/MiniJson.cs.meta +2 -0
  65. package/bridge/com.ucp.bridge/Editor/Protocol.meta +8 -0
  66. package/bridge/com.ucp.bridge/Editor/Scripts/IUCPScript.cs +37 -0
  67. package/bridge/com.ucp.bridge/Editor/Scripts/IUCPScript.cs.meta +2 -0
  68. package/bridge/com.ucp.bridge/Editor/Scripts.meta +8 -0
  69. package/bridge/com.ucp.bridge/Editor/UCP.Bridge.Editor.asmdef +18 -0
  70. package/bridge/com.ucp.bridge/Editor/UCP.Bridge.Editor.asmdef.meta +7 -0
  71. package/bridge/com.ucp.bridge/Editor.meta +8 -0
  72. package/bridge/com.ucp.bridge/Runtime/UCP.Bridge.Runtime.asmdef +14 -0
  73. package/bridge/com.ucp.bridge/Runtime/UCP.Bridge.Runtime.asmdef.meta +7 -0
  74. package/bridge/com.ucp.bridge/Runtime.meta +8 -0
  75. package/bridge/com.ucp.bridge/Tests/Editor/ControllerSmokeTests.cs +1085 -0
  76. package/bridge/com.ucp.bridge/Tests/Editor/ControllerSmokeTests.cs.meta +2 -0
  77. package/bridge/com.ucp.bridge/Tests/Editor/UCP.Bridge.Editor.Tests.asmdef +12 -0
  78. package/bridge/com.ucp.bridge/Tests/Editor/UCP.Bridge.Editor.Tests.asmdef.meta +7 -0
  79. package/bridge/com.ucp.bridge/Tests/Editor.meta +8 -0
  80. package/bridge/com.ucp.bridge/Tests.meta +8 -0
  81. package/bridge/com.ucp.bridge/package.json +29 -0
  82. package/bridge/com.ucp.bridge/package.json.meta +7 -0
  83. package/package.json +2 -2
  84. package/scripts/install.js +4 -6
@@ -0,0 +1,611 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using System.Linq;
4
+ using UnityEditor;
5
+ using UnityEditor.VersionControl;
6
+ using UnityEngine;
7
+
8
+ namespace UCP.Bridge
9
+ {
10
+ /// <summary>
11
+ /// Version control operations via UnityEditor.VersionControl (Unity VCS / Plastic SCM).
12
+ /// All commands go through the Editor's VC provider - the active provider must be configured
13
+ /// in Unity's Project Settings > Version Control.
14
+ /// </summary>
15
+ public static class VcsController
16
+ {
17
+ public static void Register(CommandRouter router)
18
+ {
19
+ router.Register("vcs/info", HandleInfo);
20
+ router.Register("vcs/status", HandleStatus);
21
+ router.Register("vcs/checkout", HandleCheckout);
22
+ router.Register("vcs/revert", HandleRevert);
23
+ router.Register("vcs/commit", HandleCommit);
24
+ router.Register("vcs/diff", HandleDiff);
25
+ router.Register("vcs/incoming", HandleIncoming);
26
+ router.Register("vcs/update", HandleUpdate);
27
+ router.Register("vcs/branches", HandleBranches);
28
+ router.Register("vcs/lock", HandleLock);
29
+ router.Register("vcs/unlock", HandleUnlock);
30
+ router.Register("vcs/history", HandleHistory);
31
+ router.Register("vcs/resolve", HandleResolve);
32
+ }
33
+
34
+ // ── Helpers ──────────────────────────────────────────────
35
+
36
+ private static void EnsureVcsActive()
37
+ {
38
+ if (!Provider.enabled || !Provider.isActive)
39
+ throw new InvalidOperationException(
40
+ "Version control is not active. Enable it in Project Settings > Version Control.");
41
+ }
42
+
43
+ private static bool WaitForTask(UnityEditor.VersionControl.Task task, int timeoutMs = 30000)
44
+ {
45
+ if (task == null) return false;
46
+ task.Wait();
47
+ return task.success;
48
+ }
49
+
50
+ private static List<string> ParsePaths(string paramsJson)
51
+ {
52
+ var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
53
+ if (p == null) return new List<string>();
54
+
55
+ if (p.TryGetValue("paths", out var pathsObj) && pathsObj is List<object> list)
56
+ return list.Select(x => x?.ToString()).Where(x => !string.IsNullOrEmpty(x)).ToList();
57
+
58
+ if (p.TryGetValue("path", out var pathObj) && pathObj != null)
59
+ {
60
+ var s = pathObj.ToString();
61
+ if (!string.IsNullOrEmpty(s)) return new List<string> { s };
62
+ }
63
+
64
+ return new List<string>();
65
+ }
66
+
67
+ private static AssetList ToAssetList(IEnumerable<string> paths)
68
+ {
69
+ var list = new AssetList();
70
+ foreach (var path in paths)
71
+ {
72
+ var asset = Provider.GetAssetByPath(path);
73
+ if (asset != null) list.Add(asset);
74
+ }
75
+ return list;
76
+ }
77
+
78
+ private static Dictionary<string, object> AssetToDict(Asset asset)
79
+ {
80
+ return new Dictionary<string, object>
81
+ {
82
+ ["path"] = asset.path ?? "",
83
+ ["state"] = asset.state.ToString(),
84
+ ["isFolder"] = asset.isFolder,
85
+ ["name"] = asset.name ?? "",
86
+ ["locked"] = asset.IsState(Asset.States.LockedLocal) || asset.IsState(Asset.States.LockedRemote),
87
+ ["lockedLocal"] = asset.IsState(Asset.States.LockedLocal),
88
+ ["lockedRemote"] = asset.IsState(Asset.States.LockedRemote),
89
+ };
90
+ }
91
+
92
+ // ── Handlers ─────────────────────────────────────────────
93
+
94
+ private static object HandleInfo(string paramsJson)
95
+ {
96
+ EnsureVcsActive();
97
+
98
+ // Return plugin and workspace info
99
+ var plugin = Provider.GetActivePlugin();
100
+
101
+ return new Dictionary<string, object>
102
+ {
103
+ ["enabled"] = Provider.enabled,
104
+ ["active"] = Provider.isActive,
105
+ ["onlineState"] = Provider.onlineState.ToString(),
106
+ ["offlineReason"] = Provider.offlineReason,
107
+ ["plugin"] = plugin?.name ?? "unknown",
108
+ };
109
+ }
110
+
111
+ private static object HandleStatus(string paramsJson)
112
+ {
113
+ EnsureVcsActive();
114
+
115
+ var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
116
+ var paths = ParsePaths(paramsJson);
117
+ bool all = paths.Count == 0;
118
+
119
+ // If specific paths, query those; otherwise query all changes under Assets
120
+ UnityEditor.VersionControl.Task statusTask;
121
+ if (all)
122
+ statusTask = Provider.Status("Assets", true);
123
+ else
124
+ statusTask = Provider.Status(ToAssetList(paths), true);
125
+
126
+ WaitForTask(statusTask);
127
+
128
+ var items = new List<object>();
129
+ if (statusTask.assetList != null)
130
+ {
131
+ foreach (var asset in statusTask.assetList)
132
+ {
133
+ // Skip clean assets (no pending state) when showing full status
134
+ if (all && !HasPendingState(asset)) continue;
135
+ items.Add(AssetToDict(asset));
136
+ }
137
+ }
138
+
139
+ return new Dictionary<string, object>
140
+ {
141
+ ["count"] = items.Count,
142
+ ["assets"] = items,
143
+ };
144
+ }
145
+
146
+ private static bool HasPendingState(Asset a)
147
+ {
148
+ var states = a.state;
149
+ return (states & (Asset.States.CheckedOutLocal | Asset.States.CheckedOutRemote
150
+ | Asset.States.AddedLocal | Asset.States.AddedRemote
151
+ | Asset.States.DeletedLocal | Asset.States.DeletedRemote
152
+ | Asset.States.LockedLocal | Asset.States.LockedRemote
153
+ | Asset.States.Conflicted
154
+ | Asset.States.MovedLocal
155
+ | Asset.States.MovedRemote | Asset.States.Updating
156
+ | Asset.States.OutOfSync
157
+ )) != 0;
158
+ }
159
+
160
+ private static object HandleCheckout(string paramsJson)
161
+ {
162
+ EnsureVcsActive();
163
+
164
+ var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
165
+ var paths = ParsePaths(paramsJson);
166
+ bool all = p?.ContainsKey("all") == true && Convert.ToBoolean(p["all"]);
167
+
168
+ AssetList assets;
169
+ if (all)
170
+ {
171
+ // Checkout everything that's modified / added under Assets
172
+ var statusTask = Provider.Status("Assets", true);
173
+ WaitForTask(statusTask);
174
+ assets = new AssetList();
175
+ if (statusTask.assetList != null)
176
+ {
177
+ foreach (var a in statusTask.assetList)
178
+ {
179
+ if (HasPendingState(a) || a.IsState(Asset.States.ReadOnly))
180
+ assets.Add(a);
181
+ }
182
+ }
183
+ }
184
+ else
185
+ {
186
+ if (paths.Count == 0)
187
+ throw new ArgumentException("Provide 'path', 'paths', or 'all: true'");
188
+ assets = ToAssetList(paths);
189
+ }
190
+
191
+ if (assets.Count == 0)
192
+ return new Dictionary<string, object> { ["count"] = 0, ["message"] = "Nothing to checkout" };
193
+
194
+ var task = Provider.Checkout(assets, CheckoutMode.Both);
195
+ bool ok = WaitForTask(task);
196
+
197
+ return new Dictionary<string, object>
198
+ {
199
+ ["success"] = ok,
200
+ ["count"] = assets.Count,
201
+ ["paths"] = assets.Select(a => a.path).ToList(),
202
+ };
203
+ }
204
+
205
+ private static object HandleRevert(string paramsJson)
206
+ {
207
+ EnsureVcsActive();
208
+
209
+ var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
210
+ var paths = ParsePaths(paramsJson);
211
+ bool all = p?.ContainsKey("all") == true && Convert.ToBoolean(p["all"]);
212
+
213
+ AssetList assets;
214
+ if (all)
215
+ {
216
+ var statusTask = Provider.Status("Assets", true);
217
+ WaitForTask(statusTask);
218
+ assets = new AssetList();
219
+ if (statusTask.assetList != null)
220
+ {
221
+ foreach (var a in statusTask.assetList)
222
+ {
223
+ if (HasPendingState(a))
224
+ assets.Add(a);
225
+ }
226
+ }
227
+ }
228
+ else
229
+ {
230
+ if (paths.Count == 0)
231
+ throw new ArgumentException("Provide 'path', 'paths', or 'all: true'");
232
+ assets = ToAssetList(paths);
233
+ }
234
+
235
+ if (assets.Count == 0)
236
+ return new Dictionary<string, object> { ["count"] = 0, ["message"] = "Nothing to revert" };
237
+
238
+ var revertMode = RevertMode.Normal;
239
+ if (p?.ContainsKey("keepLocal") == true && Convert.ToBoolean(p["keepLocal"]))
240
+ revertMode = RevertMode.KeepModifications;
241
+
242
+ var task = Provider.Revert(assets, revertMode);
243
+ bool ok = WaitForTask(task);
244
+
245
+ return new Dictionary<string, object>
246
+ {
247
+ ["success"] = ok,
248
+ ["count"] = assets.Count,
249
+ ["paths"] = assets.Select(a => a.path).ToList(),
250
+ };
251
+ }
252
+
253
+ private static object HandleCommit(string paramsJson)
254
+ {
255
+ EnsureVcsActive();
256
+
257
+ var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
258
+ if (p == null || !p.TryGetValue("message", out var msgObj) || string.IsNullOrEmpty(msgObj?.ToString()))
259
+ throw new ArgumentException("Missing 'message' parameter");
260
+
261
+ var message = msgObj.ToString();
262
+ var paths = ParsePaths(paramsJson);
263
+ bool all = paths.Count == 0;
264
+
265
+ // Build changeset
266
+ AssetList assets;
267
+ if (all)
268
+ {
269
+ var statusTask = Provider.Status("Assets", true);
270
+ WaitForTask(statusTask);
271
+ assets = new AssetList();
272
+ if (statusTask.assetList != null)
273
+ {
274
+ foreach (var a in statusTask.assetList)
275
+ {
276
+ if (HasPendingState(a))
277
+ assets.Add(a);
278
+ }
279
+ }
280
+ }
281
+ else
282
+ {
283
+ assets = ToAssetList(paths);
284
+ }
285
+
286
+ if (assets.Count == 0)
287
+ return new Dictionary<string, object> { ["success"] = false, ["message"] = "No pending changes to commit" };
288
+
289
+ var changeset = new ChangeSet("", "");
290
+ var task = Provider.Submit(changeset, assets, message, true);
291
+ bool ok = WaitForTask(task);
292
+
293
+ return new Dictionary<string, object>
294
+ {
295
+ ["success"] = ok,
296
+ ["count"] = assets.Count,
297
+ ["commitMessage"] = message,
298
+ ["paths"] = assets.Select(a => a.path).ToList(),
299
+ };
300
+ }
301
+
302
+ private static object HandleDiff(string paramsJson)
303
+ {
304
+ EnsureVcsActive();
305
+
306
+ var paths = ParsePaths(paramsJson);
307
+
308
+ if (paths.Count == 0)
309
+ {
310
+ // Return summary of changes (no specific diff)
311
+ var statusTask = Provider.Status("Assets", true);
312
+ WaitForTask(statusTask);
313
+
314
+ var summary = new Dictionary<string, int>
315
+ {
316
+ ["modified"] = 0,
317
+ ["added"] = 0,
318
+ ["deleted"] = 0,
319
+ ["moved"] = 0,
320
+ ["conflicted"] = 0,
321
+ ["outOfSync"] = 0
322
+ };
323
+ var items = new List<object>();
324
+ if (statusTask.assetList != null)
325
+ {
326
+ foreach (var a in statusTask.assetList)
327
+ {
328
+ if (!HasPendingState(a)) continue;
329
+ items.Add(AssetToDict(a));
330
+
331
+ if (a.IsState(Asset.States.CheckedOutLocal))
332
+ summary["modified"]++;
333
+ if (a.IsState(Asset.States.AddedLocal)) summary["added"]++;
334
+ if (a.IsState(Asset.States.DeletedLocal)) summary["deleted"]++;
335
+ if (a.IsState(Asset.States.MovedLocal)) summary["moved"]++;
336
+ if (a.IsState(Asset.States.Conflicted))
337
+ summary["conflicted"]++;
338
+ if (a.IsState(Asset.States.OutOfSync)) summary["outOfSync"]++;
339
+ }
340
+ }
341
+
342
+ return new Dictionary<string, object>
343
+ {
344
+ ["summary"] = summary,
345
+ ["count"] = items.Count,
346
+ ["assets"] = items,
347
+ };
348
+ }
349
+ else
350
+ {
351
+ // Diff specific files - launch diff tool if running interactively
352
+ // For CLI, return status info for the requested paths
353
+ var statusTask = Provider.Status(ToAssetList(paths), true);
354
+ WaitForTask(statusTask);
355
+
356
+ var items = new List<object>();
357
+ if (statusTask.assetList != null)
358
+ {
359
+ foreach (var a in statusTask.assetList)
360
+ items.Add(AssetToDict(a));
361
+ }
362
+
363
+ return new Dictionary<string, object>
364
+ {
365
+ ["count"] = items.Count,
366
+ ["assets"] = items,
367
+ };
368
+ }
369
+ }
370
+
371
+ private static object HandleIncoming(string paramsJson)
372
+ {
373
+ EnsureVcsActive();
374
+
375
+ var task = Provider.Incoming();
376
+ WaitForTask(task);
377
+
378
+ var items = new List<object>();
379
+ if (task.assetList != null)
380
+ {
381
+ foreach (var a in task.assetList)
382
+ items.Add(AssetToDict(a));
383
+ }
384
+
385
+ return new Dictionary<string, object>
386
+ {
387
+ ["count"] = items.Count,
388
+ ["assets"] = items,
389
+ };
390
+ }
391
+
392
+ private static object HandleUpdate(string paramsJson)
393
+ {
394
+ EnsureVcsActive();
395
+
396
+ // Get incoming changes and apply them
397
+ var incomingTask = Provider.Incoming();
398
+ WaitForTask(incomingTask);
399
+
400
+ if (incomingTask.assetList == null || incomingTask.assetList.Count == 0)
401
+ return new Dictionary<string, object> { ["success"] = true, ["count"] = 0, ["message"] = "Already up to date" };
402
+
403
+ var task = Provider.GetLatest(incomingTask.assetList);
404
+ bool ok = WaitForTask(task);
405
+
406
+ return new Dictionary<string, object>
407
+ {
408
+ ["success"] = ok,
409
+ ["count"] = incomingTask.assetList.Count,
410
+ ["assets"] = incomingTask.assetList.Select(a => a.path).ToList(),
411
+ };
412
+ }
413
+
414
+ private static object HandleBranches(string paramsJson)
415
+ {
416
+ EnsureVcsActive();
417
+
418
+ // Use the cm CLI tool to list branches - Provider API doesn't expose branches directly
419
+ // Fall back to reporting what we can from the provider
420
+ var result = new Dictionary<string, object>
421
+ {
422
+ ["onlineState"] = Provider.onlineState.ToString(),
423
+ ["plugin"] = Provider.GetActivePlugin()?.name ?? "unknown",
424
+ };
425
+
426
+ // Try running "cm find branches" via process if Plastic SCM is available
427
+ try
428
+ {
429
+ var psi = new System.Diagnostics.ProcessStartInfo
430
+ {
431
+ FileName = "cm",
432
+ Arguments = "find branches --format={name} --nototal",
433
+ RedirectStandardOutput = true,
434
+ RedirectStandardError = true,
435
+ UseShellExecute = false,
436
+ CreateNoWindow = true,
437
+ };
438
+ var proc = System.Diagnostics.Process.Start(psi);
439
+ var output = proc.StandardOutput.ReadToEnd();
440
+ proc.WaitForExit(10000);
441
+
442
+ var branches = output
443
+ .Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
444
+ .Select(b => b.Trim())
445
+ .Where(b => !string.IsNullOrEmpty(b))
446
+ .ToList();
447
+
448
+ result["branches"] = branches;
449
+ result["count"] = branches.Count;
450
+ }
451
+ catch
452
+ {
453
+ result["branches"] = new List<string>();
454
+ result["count"] = 0;
455
+ result["note"] = "Could not list branches. Ensure 'cm' CLI is available in PATH.";
456
+ }
457
+
458
+ // Try getting current branch
459
+ try
460
+ {
461
+ var psi = new System.Diagnostics.ProcessStartInfo
462
+ {
463
+ FileName = "cm",
464
+ Arguments = "status --header --machinereadable",
465
+ RedirectStandardOutput = true,
466
+ RedirectStandardError = true,
467
+ UseShellExecute = false,
468
+ CreateNoWindow = true,
469
+ };
470
+ var proc = System.Diagnostics.Process.Start(psi);
471
+ var output = proc.StandardOutput.ReadToEnd();
472
+ proc.WaitForExit(10000);
473
+
474
+ // First line usually contains the current changeset/branch info
475
+ result["currentInfo"] = output.Trim().Split('\n').FirstOrDefault() ?? "";
476
+ }
477
+ catch { /* cm not available */ }
478
+
479
+ return result;
480
+ }
481
+
482
+ private static object HandleLock(string paramsJson)
483
+ {
484
+ EnsureVcsActive();
485
+
486
+ var paths = ParsePaths(paramsJson);
487
+ if (paths.Count == 0)
488
+ throw new ArgumentException("Provide 'path' or 'paths' to lock");
489
+
490
+ var assets = ToAssetList(paths);
491
+ var task = Provider.Checkout(assets, CheckoutMode.Exact);
492
+ bool ok = WaitForTask(task);
493
+
494
+ return new Dictionary<string, object>
495
+ {
496
+ ["success"] = ok,
497
+ ["count"] = assets.Count,
498
+ ["paths"] = assets.Select(a => a.path).ToList(),
499
+ };
500
+ }
501
+
502
+ private static object HandleUnlock(string paramsJson)
503
+ {
504
+ EnsureVcsActive();
505
+
506
+ var paths = ParsePaths(paramsJson);
507
+ if (paths.Count == 0)
508
+ throw new ArgumentException("Provide 'path' or 'paths' to unlock");
509
+
510
+ var assets = ToAssetList(paths);
511
+ var task = Provider.Revert(assets, RevertMode.Normal);
512
+ bool ok = WaitForTask(task);
513
+
514
+ return new Dictionary<string, object>
515
+ {
516
+ ["success"] = ok,
517
+ ["count"] = assets.Count,
518
+ ["paths"] = assets.Select(a => a.path).ToList(),
519
+ };
520
+ }
521
+
522
+ private static object HandleHistory(string paramsJson)
523
+ {
524
+ EnsureVcsActive();
525
+
526
+ var paths = ParsePaths(paramsJson);
527
+ if (paths.Count == 0)
528
+ paths = new List<string> { "Assets" };
529
+
530
+ // Try cm log for richer history
531
+ try
532
+ {
533
+ var cmPaths = string.Join(" ", paths.Select(p => $"\"{p}\""));
534
+ var psi = new System.Diagnostics.ProcessStartInfo
535
+ {
536
+ FileName = "cm",
537
+ Arguments = $"log --csformat=\"{{changesetid}}|{{date}}|{{owner}}|{{comment}}\" -n 20",
538
+ RedirectStandardOutput = true,
539
+ RedirectStandardError = true,
540
+ UseShellExecute = false,
541
+ CreateNoWindow = true,
542
+ };
543
+ var proc = System.Diagnostics.Process.Start(psi);
544
+ var output = proc.StandardOutput.ReadToEnd();
545
+ proc.WaitForExit(10000);
546
+
547
+ var entries = new List<object>();
548
+ foreach (var line in output.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries))
549
+ {
550
+ var parts = line.Split('|');
551
+ if (parts.Length >= 4)
552
+ {
553
+ entries.Add(new Dictionary<string, object>
554
+ {
555
+ ["changeset"] = parts[0].Trim(),
556
+ ["date"] = parts[1].Trim(),
557
+ ["author"] = parts[2].Trim(),
558
+ ["comment"] = parts[3].Trim(),
559
+ });
560
+ }
561
+ }
562
+
563
+ return new Dictionary<string, object>
564
+ {
565
+ ["count"] = entries.Count,
566
+ ["entries"] = entries,
567
+ };
568
+ }
569
+ catch
570
+ {
571
+ // Fallback: no cm CLI available, try Provider API
572
+ throw new InvalidOperationException(
573
+ "History requires the 'cm' CLI. Ensure Plastic SCM / Unity VCS client is installed.");
574
+ }
575
+ }
576
+
577
+ private static object HandleResolve(string paramsJson)
578
+ {
579
+ EnsureVcsActive();
580
+
581
+ var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
582
+ var paths = ParsePaths(paramsJson);
583
+
584
+ if (paths.Count == 0)
585
+ throw new ArgumentException("Provide 'path' or 'paths' to resolve");
586
+
587
+ var resolveMethod = ResolveMethod.UseMerged;
588
+ if (p?.TryGetValue("method", out var methodObj) == true)
589
+ {
590
+ var m = methodObj?.ToString()?.ToLower();
591
+ resolveMethod = m switch
592
+ {
593
+ "mine" => ResolveMethod.UseMine,
594
+ "theirs" => ResolveMethod.UseTheirs,
595
+ _ => ResolveMethod.UseMerged,
596
+ };
597
+ }
598
+
599
+ var assets = ToAssetList(paths);
600
+ var task = Provider.Resolve(assets, resolveMethod);
601
+ bool ok = WaitForTask(task);
602
+
603
+ return new Dictionary<string, object>
604
+ {
605
+ ["success"] = ok,
606
+ ["count"] = assets.Count,
607
+ ["paths"] = assets.Select(a => a.path).ToList(),
608
+ };
609
+ }
610
+ }
611
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: 99fdf5571d24cc04ca57810b01349b8d
@@ -0,0 +1,8 @@
1
+ fileFormatVersion: 2
2
+ guid: 5bdca13b538ae1f46aa9621569a50a57
3
+ folderAsset: yes
4
+ DefaultImporter:
5
+ externalObjects: {}
6
+ userData:
7
+ assetBundleName:
8
+ assetBundleVariant:
@@ -0,0 +1,53 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using UnityEngine;
4
+
5
+ namespace UCP.Bridge
6
+ {
7
+ /// <summary>
8
+ /// Routes JSON-RPC method names to handler functions.
9
+ /// </summary>
10
+ public class CommandRouter
11
+ {
12
+ public delegate object CommandHandler(string paramsJson);
13
+
14
+ private readonly Dictionary<string, CommandHandler> _handlers = new();
15
+
16
+ public void Register(string method, CommandHandler handler)
17
+ {
18
+ _handlers[method] = handler;
19
+ }
20
+
21
+ public JsonRpcResponse Dispatch(string method, long id, string paramsJson)
22
+ {
23
+ if (!_handlers.TryGetValue(method, out var handler))
24
+ {
25
+ return JsonRpcResponse.Error(id, ErrorCodes.MethodNotFound,
26
+ $"Method not found: {method}");
27
+ }
28
+
29
+ try
30
+ {
31
+ var result = handler(paramsJson);
32
+ return JsonRpcResponse.Success(id, result);
33
+ }
34
+ catch (ArgumentException ex)
35
+ {
36
+ return JsonRpcResponse.Error(id, ErrorCodes.InvalidParams, ex.Message);
37
+ }
38
+ catch (UnauthorizedAccessException ex)
39
+ {
40
+ return JsonRpcResponse.Error(id, ErrorCodes.FileAccessDenied, ex.Message);
41
+ }
42
+ catch (Exception ex)
43
+ {
44
+ Debug.LogError($"[UCP] Error handling '{method}': {ex}");
45
+ return JsonRpcResponse.Error(id, ErrorCodes.InternalError, ex.Message);
46
+ }
47
+ }
48
+
49
+ public bool HasMethod(string method) => _handlers.ContainsKey(method);
50
+
51
+ public IEnumerable<string> RegisteredMethods => _handlers.Keys;
52
+ }
53
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: e9c7045940bdf0f448f083716fcf469a