@duytransipher/gitnexus 1.4.6-sipher.0 → 1.4.6-sipher.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.
- package/package.json +27 -27
- package/scripts/setup-unreal-gitnexus.ps1 +1 -1
- package/vendor/GitNexusUnreal/GitNexusUnreal.uplugin +18 -0
- package/vendor/GitNexusUnreal/Source/GitNexusUnreal/GitNexusUnreal.Build.cs +29 -0
- package/vendor/GitNexusUnreal/Source/GitNexusUnreal/Private/GitNexusBlueprintAnalyzerCommandlet.cpp +465 -0
- package/vendor/GitNexusUnreal/Source/GitNexusUnreal/Private/GitNexusUnrealModule.cpp +15 -0
- package/vendor/GitNexusUnreal/Source/GitNexusUnreal/Public/GitNexusBlueprintAnalyzerCommandlet.h +46 -0
- package/vendor/GitNexusUnreal/Source/GitNexusUnreal/Public/GitNexusUnrealModule.h +10 -0
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "@duytransipher/gitnexus",
|
|
3
|
-
"version": "1.4.6-sipher.
|
|
4
|
-
"description": "Sipher-maintained fork of GitNexus for graph-powered code intelligence via MCP and CLI.",
|
|
5
|
-
"author": "DuyTranSipher",
|
|
6
|
-
"license": "PolyForm-Noncommercial-1.0.0",
|
|
7
|
-
"homepage": "https://github.com/DuyTranSipher/sipher-git-nexus#readme",
|
|
8
|
-
"repository": {
|
|
9
|
-
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/DuyTranSipher/sipher-git-nexus.git",
|
|
11
|
-
"directory": "gitnexus"
|
|
12
|
-
},
|
|
13
|
-
"bugs": {
|
|
14
|
-
"url": "https://github.com/DuyTranSipher/sipher-git-nexus/issues"
|
|
15
|
-
},
|
|
2
|
+
"name": "@duytransipher/gitnexus",
|
|
3
|
+
"version": "1.4.6-sipher.2",
|
|
4
|
+
"description": "Sipher-maintained fork of GitNexus for graph-powered code intelligence via MCP and CLI.",
|
|
5
|
+
"author": "DuyTranSipher",
|
|
6
|
+
"license": "PolyForm-Noncommercial-1.0.0",
|
|
7
|
+
"homepage": "https://github.com/DuyTranSipher/sipher-git-nexus#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/DuyTranSipher/sipher-git-nexus.git",
|
|
11
|
+
"directory": "gitnexus"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/DuyTranSipher/sipher-git-nexus/issues"
|
|
15
|
+
},
|
|
16
16
|
"keywords": [
|
|
17
17
|
"mcp",
|
|
18
18
|
"model-context-protocol",
|
|
@@ -36,19 +36,19 @@
|
|
|
36
36
|
"skills",
|
|
37
37
|
"vendor"
|
|
38
38
|
],
|
|
39
|
-
"scripts": {
|
|
40
|
-
"build": "tsc",
|
|
41
|
-
"dev": "tsx watch src/cli/index.ts",
|
|
42
|
-
"pack:dry-run": "npm pack --dry-run",
|
|
43
|
-
"test": "vitest run",
|
|
44
|
-
"test:unit": "vitest run test/unit",
|
|
45
|
-
"test:integration": "vitest run test/integration",
|
|
46
|
-
"test:watch": "vitest",
|
|
47
|
-
"test:coverage": "vitest run --coverage",
|
|
48
|
-
"prepare": "npm run build",
|
|
49
|
-
"postinstall": "node scripts/patch-tree-sitter-swift.cjs",
|
|
50
|
-
"prepack": "npm run build && node scripts/ensure-cli-executable.cjs"
|
|
51
|
-
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsc",
|
|
41
|
+
"dev": "tsx watch src/cli/index.ts",
|
|
42
|
+
"pack:dry-run": "npm pack --dry-run",
|
|
43
|
+
"test": "vitest run",
|
|
44
|
+
"test:unit": "vitest run test/unit",
|
|
45
|
+
"test:integration": "vitest run test/integration",
|
|
46
|
+
"test:watch": "vitest",
|
|
47
|
+
"test:coverage": "vitest run --coverage",
|
|
48
|
+
"prepare": "npm run build",
|
|
49
|
+
"postinstall": "node scripts/patch-tree-sitter-swift.cjs",
|
|
50
|
+
"prepack": "npm run build && node scripts/ensure-cli-executable.cjs"
|
|
51
|
+
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@huggingface/transformers": "^3.0.0",
|
|
54
54
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"FileVersion": 3,
|
|
3
|
+
"Version": 1,
|
|
4
|
+
"VersionName": "0.1.0",
|
|
5
|
+
"FriendlyName": "GitNexus Unreal",
|
|
6
|
+
"Description": "Blueprint reference analysis commandlet used by GitNexus.",
|
|
7
|
+
"Category": "Developer",
|
|
8
|
+
"CanContainContent": false,
|
|
9
|
+
"IsBetaVersion": true,
|
|
10
|
+
"Installed": false,
|
|
11
|
+
"Modules": [
|
|
12
|
+
{
|
|
13
|
+
"Name": "GitNexusUnreal",
|
|
14
|
+
"Type": "Editor",
|
|
15
|
+
"LoadingPhase": "Default"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
using UnrealBuildTool;
|
|
2
|
+
|
|
3
|
+
public class GitNexusUnreal : ModuleRules
|
|
4
|
+
{
|
|
5
|
+
public GitNexusUnreal(ReadOnlyTargetRules Target) : base(Target)
|
|
6
|
+
{
|
|
7
|
+
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
|
|
8
|
+
|
|
9
|
+
PublicDependencyModuleNames.AddRange(new[]
|
|
10
|
+
{
|
|
11
|
+
"Core",
|
|
12
|
+
"CoreUObject",
|
|
13
|
+
"Engine"
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
PrivateDependencyModuleNames.AddRange(new[]
|
|
17
|
+
{
|
|
18
|
+
"AssetRegistry",
|
|
19
|
+
"BlueprintGraph",
|
|
20
|
+
"Json",
|
|
21
|
+
"JsonUtilities",
|
|
22
|
+
"Kismet",
|
|
23
|
+
"KismetCompiler",
|
|
24
|
+
"Projects",
|
|
25
|
+
"SlateCore",
|
|
26
|
+
"UnrealEd"
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
package/vendor/GitNexusUnreal/Source/GitNexusUnreal/Private/GitNexusBlueprintAnalyzerCommandlet.cpp
ADDED
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
#include "GitNexusBlueprintAnalyzerCommandlet.h"
|
|
2
|
+
|
|
3
|
+
#include "AssetRegistry/AssetRegistryModule.h"
|
|
4
|
+
#include "AssetRegistry/IAssetRegistry.h"
|
|
5
|
+
#include "Blueprint/BlueprintSupport.h"
|
|
6
|
+
#include "Blueprint/UserWidget.h"
|
|
7
|
+
#include "Dom/JsonObject.h"
|
|
8
|
+
#include "EdGraph/EdGraph.h"
|
|
9
|
+
#include "EdGraph/EdGraphNode.h"
|
|
10
|
+
#include "EdGraph/EdGraphPin.h"
|
|
11
|
+
#include "Engine/Blueprint.h"
|
|
12
|
+
#include "K2Node_CallFunction.h"
|
|
13
|
+
#include "K2Node_Event.h"
|
|
14
|
+
#include "Kismet2/BlueprintEditorUtils.h"
|
|
15
|
+
#include "Misc/FileHelper.h"
|
|
16
|
+
#include "Misc/Guid.h"
|
|
17
|
+
#include "Misc/PackageName.h"
|
|
18
|
+
#include "Misc/Paths.h"
|
|
19
|
+
#include "Serialization/JsonReader.h"
|
|
20
|
+
#include "Serialization/JsonSerializer.h"
|
|
21
|
+
#include "UObject/SoftObjectPath.h"
|
|
22
|
+
|
|
23
|
+
UGitNexusBlueprintAnalyzerCommandlet::UGitNexusBlueprintAnalyzerCommandlet()
|
|
24
|
+
{
|
|
25
|
+
IsClient = false;
|
|
26
|
+
IsEditor = true;
|
|
27
|
+
LogToConsole = true;
|
|
28
|
+
ShowErrorCount = true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
int32 UGitNexusBlueprintAnalyzerCommandlet::Main(const FString& Params)
|
|
32
|
+
{
|
|
33
|
+
FString Operation;
|
|
34
|
+
FString OutputJsonPath;
|
|
35
|
+
FParse::Value(*Params, TEXT("Operation="), Operation);
|
|
36
|
+
FParse::Value(*Params, TEXT("OutputJson="), OutputJsonPath);
|
|
37
|
+
|
|
38
|
+
if (Operation.IsEmpty() || OutputJsonPath.IsEmpty())
|
|
39
|
+
{
|
|
40
|
+
UE_LOG(LogTemp, Error, TEXT("GitNexusBlueprintAnalyzer requires Operation= and OutputJson= parameters."));
|
|
41
|
+
return 1;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (Operation.Equals(TEXT("SyncAssets"), ESearchCase::IgnoreCase))
|
|
45
|
+
{
|
|
46
|
+
return RunSyncAssets(OutputJsonPath);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (Operation.Equals(TEXT("FindNativeBlueprintReferences"), ESearchCase::IgnoreCase))
|
|
50
|
+
{
|
|
51
|
+
FString CandidatesJsonPath;
|
|
52
|
+
FString TargetSymbolKey;
|
|
53
|
+
FString TargetClassName;
|
|
54
|
+
FString TargetFunctionName;
|
|
55
|
+
FParse::Value(*Params, TEXT("CandidatesJson="), CandidatesJsonPath);
|
|
56
|
+
FParse::Value(*Params, TEXT("TargetSymbolKey="), TargetSymbolKey);
|
|
57
|
+
FParse::Value(*Params, TEXT("TargetClass="), TargetClassName);
|
|
58
|
+
FParse::Value(*Params, TEXT("TargetFunction="), TargetFunctionName);
|
|
59
|
+
return RunFindNativeBlueprintReferences(OutputJsonPath, CandidatesJsonPath, TargetSymbolKey, TargetClassName, TargetFunctionName);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (Operation.Equals(TEXT("ExpandBlueprintChain"), ESearchCase::IgnoreCase))
|
|
63
|
+
{
|
|
64
|
+
FString AssetPath;
|
|
65
|
+
FString ChainAnchorId;
|
|
66
|
+
FString Direction = TEXT("downstream");
|
|
67
|
+
int32 MaxDepth = 5;
|
|
68
|
+
FParse::Value(*Params, TEXT("AssetPath="), AssetPath);
|
|
69
|
+
FParse::Value(*Params, TEXT("ChainAnchorId="), ChainAnchorId);
|
|
70
|
+
FParse::Value(*Params, TEXT("Direction="), Direction);
|
|
71
|
+
FParse::Value(*Params, TEXT("MaxDepth="), MaxDepth);
|
|
72
|
+
return RunExpandBlueprintChain(OutputJsonPath, AssetPath, ChainAnchorId, Direction, MaxDepth);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
UE_LOG(LogTemp, Error, TEXT("Unsupported GitNexusBlueprintAnalyzer operation: %s"), *Operation);
|
|
76
|
+
return 1;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
int32 UGitNexusBlueprintAnalyzerCommandlet::RunSyncAssets(const FString& OutputJsonPath)
|
|
80
|
+
{
|
|
81
|
+
TArray<TSharedPtr<FJsonValue>> AssetValues;
|
|
82
|
+
|
|
83
|
+
for (const FAssetData& AssetData : GetAllBlueprintAssets())
|
|
84
|
+
{
|
|
85
|
+
UBlueprint* Blueprint = Cast<UBlueprint>(AssetData.GetAsset());
|
|
86
|
+
if (!Blueprint)
|
|
87
|
+
{
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
TSharedPtr<FJsonObject> AssetObject = MakeShared<FJsonObject>();
|
|
92
|
+
AssetObject->SetStringField(TEXT("asset_path"), AssetData.GetSoftObjectPath().ToString());
|
|
93
|
+
|
|
94
|
+
if (Blueprint->GeneratedClass)
|
|
95
|
+
{
|
|
96
|
+
AssetObject->SetStringField(TEXT("generated_class"), Blueprint->GeneratedClass->GetPathName());
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (Blueprint->ParentClass)
|
|
100
|
+
{
|
|
101
|
+
AssetObject->SetStringField(TEXT("parent_class"), Blueprint->ParentClass->GetPathName());
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
TArray<TSharedPtr<FJsonValue>> NativeParents;
|
|
105
|
+
for (UClass* Class = Blueprint->ParentClass; Class; Class = Class->GetSuperClass())
|
|
106
|
+
{
|
|
107
|
+
if (Class->ClassGeneratedBy == nullptr)
|
|
108
|
+
{
|
|
109
|
+
NativeParents.Add(MakeShared<FJsonValueString>(Class->GetName()));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
AssetObject->SetArrayField(TEXT("native_parents"), NativeParents);
|
|
113
|
+
|
|
114
|
+
TArray<FName> Dependencies;
|
|
115
|
+
IAssetRegistry::GetChecked().GetDependencies(AssetData.PackageName, Dependencies, UE::AssetRegistry::EDependencyCategory::Package);
|
|
116
|
+
TArray<TSharedPtr<FJsonValue>> DependencyValues;
|
|
117
|
+
for (const FName& Dependency : Dependencies)
|
|
118
|
+
{
|
|
119
|
+
DependencyValues.Add(MakeShared<FJsonValueString>(Dependency.ToString()));
|
|
120
|
+
}
|
|
121
|
+
AssetObject->SetArrayField(TEXT("dependencies"), DependencyValues);
|
|
122
|
+
|
|
123
|
+
TArray<UEdGraph*> Graphs;
|
|
124
|
+
CollectBlueprintGraphs(Blueprint, Graphs);
|
|
125
|
+
TSet<FString> NativeFunctionRefs;
|
|
126
|
+
for (const UEdGraph* Graph : Graphs)
|
|
127
|
+
{
|
|
128
|
+
if (!Graph)
|
|
129
|
+
{
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
for (const UEdGraphNode* Node : Graph->Nodes)
|
|
134
|
+
{
|
|
135
|
+
if (const UK2Node_CallFunction* CallNode = Cast<UK2Node_CallFunction>(Node))
|
|
136
|
+
{
|
|
137
|
+
if (const UFunction* TargetFunction = CallNode->GetTargetFunction())
|
|
138
|
+
{
|
|
139
|
+
const UClass* OwnerClass = TargetFunction->GetOwnerClass();
|
|
140
|
+
const FString SymbolKey = OwnerClass
|
|
141
|
+
? FString::Printf(TEXT("%s::%s"), *OwnerClass->GetName(), *TargetFunction->GetName())
|
|
142
|
+
: TargetFunction->GetName();
|
|
143
|
+
NativeFunctionRefs.Add(SymbolKey);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
TArray<TSharedPtr<FJsonValue>> NativeFunctionRefValues;
|
|
150
|
+
for (const FString& Ref : NativeFunctionRefs)
|
|
151
|
+
{
|
|
152
|
+
NativeFunctionRefValues.Add(MakeShared<FJsonValueString>(Ref));
|
|
153
|
+
}
|
|
154
|
+
AssetObject->SetArrayField(TEXT("native_function_refs"), NativeFunctionRefValues);
|
|
155
|
+
|
|
156
|
+
AssetValues.Add(MakeShared<FJsonValueObject>(AssetObject));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
TSharedPtr<FJsonObject> RootObject = MakeShared<FJsonObject>();
|
|
160
|
+
RootObject->SetNumberField(TEXT("version"), 1);
|
|
161
|
+
RootObject->SetStringField(TEXT("generated_at"), FDateTime::UtcNow().ToIso8601());
|
|
162
|
+
RootObject->SetStringField(TEXT("project_path"), FPaths::ConvertRelativePathToFull(FPaths::GetProjectFilePath()));
|
|
163
|
+
RootObject->SetArrayField(TEXT("assets"), AssetValues);
|
|
164
|
+
|
|
165
|
+
return WriteJsonToFile(OutputJsonPath, RootObject) ? 0 : 1;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
int32 UGitNexusBlueprintAnalyzerCommandlet::RunFindNativeBlueprintReferences(
|
|
169
|
+
const FString& OutputJsonPath,
|
|
170
|
+
const FString& CandidatesJsonPath,
|
|
171
|
+
const FString& TargetSymbolKey,
|
|
172
|
+
const FString& TargetClassName,
|
|
173
|
+
const FString& TargetFunctionName
|
|
174
|
+
)
|
|
175
|
+
{
|
|
176
|
+
TArray<TSharedPtr<FJsonValue>> ConfirmedReferences;
|
|
177
|
+
TArray<FString> CandidateAssets = LoadCandidateAssets(CandidatesJsonPath);
|
|
178
|
+
|
|
179
|
+
for (const FString& AssetPath : CandidateAssets)
|
|
180
|
+
{
|
|
181
|
+
UBlueprint* Blueprint = LoadBlueprintFromAssetPath(AssetPath);
|
|
182
|
+
if (!Blueprint)
|
|
183
|
+
{
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
TArray<UEdGraph*> Graphs;
|
|
188
|
+
CollectBlueprintGraphs(Blueprint, Graphs);
|
|
189
|
+
for (const UEdGraph* Graph : Graphs)
|
|
190
|
+
{
|
|
191
|
+
if (!Graph)
|
|
192
|
+
{
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
for (const UEdGraphNode* Node : Graph->Nodes)
|
|
197
|
+
{
|
|
198
|
+
if (Node && IsTargetFunctionNode(Node, TargetSymbolKey, TargetClassName, TargetFunctionName))
|
|
199
|
+
{
|
|
200
|
+
ConfirmedReferences.Add(MakeShared<FJsonValueObject>(BuildReferenceJson(Blueprint, Graph, Node)));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
TSharedPtr<FJsonObject> RootObject = MakeShared<FJsonObject>();
|
|
207
|
+
TSharedPtr<FJsonObject> TargetObject = MakeShared<FJsonObject>();
|
|
208
|
+
TargetObject->SetStringField(TEXT("symbol_key"), TargetSymbolKey);
|
|
209
|
+
TargetObject->SetStringField(TEXT("class_name"), TargetClassName);
|
|
210
|
+
TargetObject->SetStringField(TEXT("symbol_name"), TargetFunctionName);
|
|
211
|
+
RootObject->SetObjectField(TEXT("target_function"), TargetObject);
|
|
212
|
+
RootObject->SetNumberField(TEXT("candidates_scanned"), CandidateAssets.Num());
|
|
213
|
+
RootObject->SetArrayField(TEXT("confirmed_references"), ConfirmedReferences);
|
|
214
|
+
|
|
215
|
+
return WriteJsonToFile(OutputJsonPath, RootObject) ? 0 : 1;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
int32 UGitNexusBlueprintAnalyzerCommandlet::RunExpandBlueprintChain(
|
|
219
|
+
const FString& OutputJsonPath,
|
|
220
|
+
const FString& AssetPath,
|
|
221
|
+
const FString& ChainAnchorId,
|
|
222
|
+
const FString& Direction,
|
|
223
|
+
int32 MaxDepth
|
|
224
|
+
)
|
|
225
|
+
{
|
|
226
|
+
UBlueprint* Blueprint = LoadBlueprintFromAssetPath(AssetPath);
|
|
227
|
+
if (!Blueprint)
|
|
228
|
+
{
|
|
229
|
+
return 1;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
UEdGraphNode* StartNode = FindNodeByGuid(Blueprint, ChainAnchorId);
|
|
233
|
+
if (!StartNode)
|
|
234
|
+
{
|
|
235
|
+
return 1;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
TArray<TSharedPtr<FJsonValue>> NodeValues;
|
|
239
|
+
TSet<FGuid> Visited;
|
|
240
|
+
TArray<TPair<UEdGraphNode*, int32>> Frontier;
|
|
241
|
+
Frontier.Add(TPair<UEdGraphNode*, int32>(StartNode, 0));
|
|
242
|
+
Visited.Add(StartNode->NodeGuid);
|
|
243
|
+
|
|
244
|
+
const bool bUpstream = Direction.Equals(TEXT("upstream"), ESearchCase::IgnoreCase);
|
|
245
|
+
|
|
246
|
+
while (Frontier.Num() > 0)
|
|
247
|
+
{
|
|
248
|
+
const TPair<UEdGraphNode*, int32> Current = Frontier[0];
|
|
249
|
+
Frontier.RemoveAt(0);
|
|
250
|
+
|
|
251
|
+
NodeValues.Add(MakeShared<FJsonValueObject>(BuildChainNodeJson(Current.Key->GetGraph(), Current.Key, Current.Value)));
|
|
252
|
+
if (Current.Value >= MaxDepth)
|
|
253
|
+
{
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
for (UEdGraphPin* Pin : Current.Key->Pins)
|
|
258
|
+
{
|
|
259
|
+
if (!Pin)
|
|
260
|
+
{
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const bool bMatchesDirection = bUpstream ? Pin->Direction == EGPD_Input : Pin->Direction == EGPD_Output;
|
|
265
|
+
if (!bMatchesDirection)
|
|
266
|
+
{
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
for (UEdGraphPin* LinkedPin : Pin->LinkedTo)
|
|
271
|
+
{
|
|
272
|
+
if (!LinkedPin || !LinkedPin->GetOwningNode())
|
|
273
|
+
{
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
UEdGraphNode* NextNode = LinkedPin->GetOwningNode();
|
|
278
|
+
if (Visited.Contains(NextNode->NodeGuid))
|
|
279
|
+
{
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
Visited.Add(NextNode->NodeGuid);
|
|
284
|
+
Frontier.Add(TPair<UEdGraphNode*, int32>(NextNode, Current.Value + 1));
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
TSharedPtr<FJsonObject> RootObject = MakeShared<FJsonObject>();
|
|
290
|
+
RootObject->SetArrayField(TEXT("nodes"), NodeValues);
|
|
291
|
+
return WriteJsonToFile(OutputJsonPath, RootObject) ? 0 : 1;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
bool UGitNexusBlueprintAnalyzerCommandlet::WriteJsonToFile(const FString& OutputJsonPath, const TSharedPtr<FJsonObject>& RootObject) const
|
|
295
|
+
{
|
|
296
|
+
FString JsonText;
|
|
297
|
+
const TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&JsonText);
|
|
298
|
+
if (!FJsonSerializer::Serialize(RootObject.ToSharedRef(), Writer))
|
|
299
|
+
{
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return FFileHelper::SaveStringToFile(JsonText, *OutputJsonPath);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
TArray<FString> UGitNexusBlueprintAnalyzerCommandlet::LoadCandidateAssets(const FString& CandidatesJsonPath) const
|
|
307
|
+
{
|
|
308
|
+
TArray<FString> Result;
|
|
309
|
+
if (CandidatesJsonPath.IsEmpty())
|
|
310
|
+
{
|
|
311
|
+
return Result;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
FString RawJson;
|
|
315
|
+
if (!FFileHelper::LoadFileToString(RawJson, *CandidatesJsonPath))
|
|
316
|
+
{
|
|
317
|
+
return Result;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
TSharedPtr<FJsonObject> RootObject;
|
|
321
|
+
const TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(RawJson);
|
|
322
|
+
if (!FJsonSerializer::Deserialize(Reader, RootObject) || !RootObject.IsValid())
|
|
323
|
+
{
|
|
324
|
+
return Result;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const TArray<TSharedPtr<FJsonValue>>* CandidateValues = nullptr;
|
|
328
|
+
if (!RootObject->TryGetArrayField(TEXT("candidate_assets"), CandidateValues) || !CandidateValues)
|
|
329
|
+
{
|
|
330
|
+
return Result;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
for (const TSharedPtr<FJsonValue>& Value : *CandidateValues)
|
|
334
|
+
{
|
|
335
|
+
const TSharedPtr<FJsonObject>* CandidateObject = nullptr;
|
|
336
|
+
if (Value.IsValid() && Value->TryGetObject(CandidateObject) && CandidateObject && CandidateObject->IsValid())
|
|
337
|
+
{
|
|
338
|
+
FString AssetPath;
|
|
339
|
+
if ((*CandidateObject)->TryGetStringField(TEXT("asset_path"), AssetPath))
|
|
340
|
+
{
|
|
341
|
+
Result.Add(AssetPath);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return Result;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
TArray<FAssetData> UGitNexusBlueprintAnalyzerCommandlet::GetAllBlueprintAssets() const
|
|
350
|
+
{
|
|
351
|
+
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
|
|
352
|
+
IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();
|
|
353
|
+
AssetRegistry.SearchAllAssets(true);
|
|
354
|
+
|
|
355
|
+
FARFilter Filter;
|
|
356
|
+
Filter.ClassPaths.Add(UBlueprint::StaticClass()->GetClassPathName());
|
|
357
|
+
Filter.bRecursiveClasses = true;
|
|
358
|
+
|
|
359
|
+
TArray<FAssetData> Assets;
|
|
360
|
+
AssetRegistry.GetAssets(Filter, Assets);
|
|
361
|
+
return Assets;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
UBlueprint* UGitNexusBlueprintAnalyzerCommandlet::LoadBlueprintFromAssetPath(const FString& AssetPath) const
|
|
365
|
+
{
|
|
366
|
+
const FSoftObjectPath SoftObjectPath(AssetPath);
|
|
367
|
+
return Cast<UBlueprint>(SoftObjectPath.TryLoad());
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
void UGitNexusBlueprintAnalyzerCommandlet::CollectBlueprintGraphs(UBlueprint* Blueprint, TArray<UEdGraph*>& OutGraphs) const
|
|
371
|
+
{
|
|
372
|
+
if (!Blueprint)
|
|
373
|
+
{
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
Blueprint->GetAllGraphs(OutGraphs);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
bool UGitNexusBlueprintAnalyzerCommandlet::IsTargetFunctionNode(
|
|
381
|
+
const UEdGraphNode* Node,
|
|
382
|
+
const FString& TargetSymbolKey,
|
|
383
|
+
const FString& TargetClassName,
|
|
384
|
+
const FString& TargetFunctionName
|
|
385
|
+
) const
|
|
386
|
+
{
|
|
387
|
+
if (const UK2Node_CallFunction* CallNode = Cast<UK2Node_CallFunction>(Node))
|
|
388
|
+
{
|
|
389
|
+
if (const UFunction* TargetFunction = CallNode->GetTargetFunction())
|
|
390
|
+
{
|
|
391
|
+
const UClass* OwnerClass = TargetFunction->GetOwnerClass();
|
|
392
|
+
const FString SymbolKey = OwnerClass
|
|
393
|
+
? FString::Printf(TEXT("%s::%s"), *OwnerClass->GetName(), *TargetFunction->GetName())
|
|
394
|
+
: TargetFunction->GetName();
|
|
395
|
+
return SymbolKey == TargetSymbolKey
|
|
396
|
+
|| TargetFunction->GetName() == TargetFunctionName
|
|
397
|
+
|| (OwnerClass && OwnerClass->GetName() == TargetClassName && TargetFunction->GetName() == TargetFunctionName);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (const UK2Node_Event* EventNode = Cast<UK2Node_Event>(Node))
|
|
402
|
+
{
|
|
403
|
+
return EventNode->GetFunctionName().ToString() == TargetFunctionName;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return false;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
TSharedPtr<FJsonObject> UGitNexusBlueprintAnalyzerCommandlet::BuildReferenceJson(UBlueprint* Blueprint, const UEdGraph* Graph, const UEdGraphNode* Node) const
|
|
410
|
+
{
|
|
411
|
+
TSharedPtr<FJsonObject> Object = MakeShared<FJsonObject>();
|
|
412
|
+
Object->SetStringField(TEXT("asset_path"), Blueprint ? Blueprint->GetPathName() : FString());
|
|
413
|
+
Object->SetStringField(TEXT("graph_name"), Graph ? Graph->GetName() : FString());
|
|
414
|
+
Object->SetStringField(TEXT("node_kind"), Node ? Node->GetClass()->GetName() : FString());
|
|
415
|
+
Object->SetStringField(TEXT("node_title"), Node ? Node->GetNodeTitle(ENodeTitleType::ListView).ToString() : FString());
|
|
416
|
+
Object->SetStringField(TEXT("blueprint_owner_function"), Graph ? Graph->GetName() : FString());
|
|
417
|
+
Object->SetStringField(TEXT("chain_anchor_id"), Node ? Node->NodeGuid.ToString(EGuidFormats::DigitsWithHyphens) : FString());
|
|
418
|
+
Object->SetStringField(TEXT("source"), TEXT("editor_confirmed"));
|
|
419
|
+
return Object;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
TSharedPtr<FJsonObject> UGitNexusBlueprintAnalyzerCommandlet::BuildChainNodeJson(const UEdGraph* Graph, const UEdGraphNode* Node, int32 Depth) const
|
|
423
|
+
{
|
|
424
|
+
TSharedPtr<FJsonObject> Object = MakeShared<FJsonObject>();
|
|
425
|
+
Object->SetStringField(TEXT("node_id"), Node ? Node->NodeGuid.ToString(EGuidFormats::DigitsWithHyphens) : FString());
|
|
426
|
+
Object->SetStringField(TEXT("graph_name"), Graph ? Graph->GetName() : FString());
|
|
427
|
+
Object->SetStringField(TEXT("node_kind"), Node ? Node->GetClass()->GetName() : FString());
|
|
428
|
+
Object->SetStringField(TEXT("node_title"), Node ? Node->GetNodeTitle(ENodeTitleType::ListView).ToString() : FString());
|
|
429
|
+
Object->SetNumberField(TEXT("depth"), Depth);
|
|
430
|
+
return Object;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
UEdGraphNode* UGitNexusBlueprintAnalyzerCommandlet::FindNodeByGuid(UBlueprint* Blueprint, const FString& NodeGuid) const
|
|
434
|
+
{
|
|
435
|
+
if (!Blueprint)
|
|
436
|
+
{
|
|
437
|
+
return nullptr;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
FGuid Guid;
|
|
441
|
+
if (!FGuid::Parse(NodeGuid, Guid))
|
|
442
|
+
{
|
|
443
|
+
return nullptr;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
TArray<UEdGraph*> Graphs;
|
|
447
|
+
CollectBlueprintGraphs(Blueprint, Graphs);
|
|
448
|
+
for (UEdGraph* Graph : Graphs)
|
|
449
|
+
{
|
|
450
|
+
if (!Graph)
|
|
451
|
+
{
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
for (UEdGraphNode* Node : Graph->Nodes)
|
|
456
|
+
{
|
|
457
|
+
if (Node && Node->NodeGuid == Guid)
|
|
458
|
+
{
|
|
459
|
+
return Node;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
return nullptr;
|
|
465
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#include "GitNexusUnrealModule.h"
|
|
2
|
+
|
|
3
|
+
#define LOCTEXT_NAMESPACE "FGitNexusUnrealModule"
|
|
4
|
+
|
|
5
|
+
void FGitNexusUnrealModule::StartupModule()
|
|
6
|
+
{
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
void FGitNexusUnrealModule::ShutdownModule()
|
|
10
|
+
{
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
#undef LOCTEXT_NAMESPACE
|
|
14
|
+
|
|
15
|
+
IMPLEMENT_MODULE(FGitNexusUnrealModule, GitNexusUnreal)
|
package/vendor/GitNexusUnreal/Source/GitNexusUnreal/Public/GitNexusBlueprintAnalyzerCommandlet.h
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "Commandlets/Commandlet.h"
|
|
4
|
+
#include "GitNexusBlueprintAnalyzerCommandlet.generated.h"
|
|
5
|
+
|
|
6
|
+
class UBlueprint;
|
|
7
|
+
class UEdGraph;
|
|
8
|
+
class UEdGraphNode;
|
|
9
|
+
|
|
10
|
+
UCLASS()
|
|
11
|
+
class GITNEXUSUNREAL_API UGitNexusBlueprintAnalyzerCommandlet : public UCommandlet
|
|
12
|
+
{
|
|
13
|
+
GENERATED_BODY()
|
|
14
|
+
|
|
15
|
+
public:
|
|
16
|
+
UGitNexusBlueprintAnalyzerCommandlet();
|
|
17
|
+
|
|
18
|
+
virtual int32 Main(const FString& Params) override;
|
|
19
|
+
|
|
20
|
+
private:
|
|
21
|
+
int32 RunSyncAssets(const FString& OutputJsonPath);
|
|
22
|
+
int32 RunFindNativeBlueprintReferences(
|
|
23
|
+
const FString& OutputJsonPath,
|
|
24
|
+
const FString& CandidatesJsonPath,
|
|
25
|
+
const FString& TargetSymbolKey,
|
|
26
|
+
const FString& TargetClassName,
|
|
27
|
+
const FString& TargetFunctionName
|
|
28
|
+
);
|
|
29
|
+
int32 RunExpandBlueprintChain(
|
|
30
|
+
const FString& OutputJsonPath,
|
|
31
|
+
const FString& AssetPath,
|
|
32
|
+
const FString& ChainAnchorId,
|
|
33
|
+
const FString& Direction,
|
|
34
|
+
int32 MaxDepth
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
bool WriteJsonToFile(const FString& OutputJsonPath, const TSharedPtr<FJsonObject>& RootObject) const;
|
|
38
|
+
TArray<FString> LoadCandidateAssets(const FString& CandidatesJsonPath) const;
|
|
39
|
+
TArray<FAssetData> GetAllBlueprintAssets() const;
|
|
40
|
+
UBlueprint* LoadBlueprintFromAssetPath(const FString& AssetPath) const;
|
|
41
|
+
void CollectBlueprintGraphs(UBlueprint* Blueprint, TArray<UEdGraph*>& OutGraphs) const;
|
|
42
|
+
bool IsTargetFunctionNode(const UEdGraphNode* Node, const FString& TargetSymbolKey, const FString& TargetClassName, const FString& TargetFunctionName) const;
|
|
43
|
+
TSharedPtr<FJsonObject> BuildReferenceJson(UBlueprint* Blueprint, const UEdGraph* Graph, const UEdGraphNode* Node) const;
|
|
44
|
+
TSharedPtr<FJsonObject> BuildChainNodeJson(const UEdGraph* Graph, const UEdGraphNode* Node, int32 Depth) const;
|
|
45
|
+
UEdGraphNode* FindNodeByGuid(UBlueprint* Blueprint, const FString& NodeGuid) const;
|
|
46
|
+
};
|