@duytransipher/gitnexus 1.2.1 → 1.3.0

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.
@@ -4,6 +4,7 @@ import { execFile } from 'node:child_process';
4
4
  import { promisify } from 'node:util';
5
5
  import { randomUUID } from 'node:crypto';
6
6
  import { ensureUnrealStorage, saveUnrealAssetManifest } from './config.js';
7
+ import { loadIgnoreRules } from '../config/ignore-service.js';
7
8
  const execFileAsync = promisify(execFile);
8
9
  function buildBaseArgs(config, operation, outputPath) {
9
10
  return [
@@ -40,7 +41,17 @@ async function readUELogErrors(config) {
40
41
  const logPath = path.join(projectDir, 'Saved', 'Logs', `${projectName}.log`);
41
42
  const content = await fs.readFile(logPath, 'utf-8');
42
43
  const lines = content.split(/\r?\n/);
43
- const errorLines = lines.filter(l => /\bError\b/i.test(l) && !/^LogWindows.*Failed to get driver/i.test(l.replace(/^\[.*?\]\[\s*\d+\]/, '')));
44
+ const stripped = (l) => l.replace(/^\[.*?\]\[\s*\d+\]/, '');
45
+ // Skip callstack lines, driver errors, and empty error lines
46
+ const isNoise = (l) => {
47
+ const s = stripped(l);
48
+ return /^LogWindows.*Failed to get driver/i.test(s)
49
+ || /\[Callstack\]/i.test(s)
50
+ || /^LogWindows: Error:\s*$/i.test(s)
51
+ || /^LogWindows: Error: ===/.test(s)
52
+ || /^LogWindows: Error: Fatal error!/i.test(s);
53
+ };
54
+ const errorLines = lines.filter(l => /\bError\b/i.test(l) && !isNoise(l));
44
55
  if (errorLines.length === 0)
45
56
  return '';
46
57
  return 'UE Log errors:\n' + errorLines.slice(-10).join('\n');
@@ -58,10 +69,74 @@ async function readOutputJson(outputPath, stdout) {
58
69
  return JSON.parse(stdout);
59
70
  }
60
71
  }
61
- export async function syncUnrealAssetManifest(storagePath, config) {
72
+ /**
73
+ * Build include/exclude prefix filters for the Unreal commandlet.
74
+ * Merges config `include_paths`/`exclude_paths` with .gitnexusignore patterns,
75
+ * mapping filesystem patterns to Unreal asset path prefixes.
76
+ */
77
+ async function buildFilterPrefixes(repoPath, config) {
78
+ const include_prefixes = [];
79
+ const exclude_prefixes = [];
80
+ // Add explicit include_paths (whitelist) from unreal config
81
+ if (config.include_paths && Array.isArray(config.include_paths)) {
82
+ for (const p of config.include_paths) {
83
+ include_prefixes.push(p);
84
+ }
85
+ }
86
+ // Add explicit exclude_paths from unreal config
87
+ if (config.exclude_paths && Array.isArray(config.exclude_paths)) {
88
+ for (const p of config.exclude_paths) {
89
+ exclude_prefixes.push(p);
90
+ }
91
+ }
92
+ // Derive exclude prefixes from .gitnexusignore patterns
93
+ if (repoPath) {
94
+ const ig = await loadIgnoreRules(repoPath);
95
+ if (ig) {
96
+ const projectDir = path.dirname(config.project_path);
97
+ try {
98
+ const contentDir = path.join(projectDir, 'Content');
99
+ const entries = await fs.readdir(contentDir, { withFileTypes: true });
100
+ for (const entry of entries) {
101
+ if (entry.isDirectory()) {
102
+ const relPath = `Content/${entry.name}`;
103
+ if (ig.ignores(relPath) || ig.ignores(relPath + '/')) {
104
+ exclude_prefixes.push(`/Game/${entry.name}`);
105
+ }
106
+ }
107
+ }
108
+ }
109
+ catch { /* Content dir might not exist */ }
110
+ try {
111
+ const pluginsDir = path.join(projectDir, 'Plugins');
112
+ const entries = await fs.readdir(pluginsDir, { withFileTypes: true });
113
+ for (const entry of entries) {
114
+ if (entry.isDirectory()) {
115
+ const relPath = `Plugins/${entry.name}`;
116
+ if (ig.ignores(relPath) || ig.ignores(relPath + '/')) {
117
+ exclude_prefixes.push(`/${entry.name}`);
118
+ }
119
+ }
120
+ }
121
+ }
122
+ catch { /* Plugins dir might not exist */ }
123
+ }
124
+ }
125
+ return { include_prefixes, exclude_prefixes };
126
+ }
127
+ export async function syncUnrealAssetManifest(storagePath, config, repoPath, deep) {
62
128
  const unrealPaths = await ensureUnrealStorage(storagePath);
63
129
  const { outputPath } = requestPaths(unrealPaths);
64
130
  const args = buildBaseArgs(config, 'SyncAssets', outputPath);
131
+ // Pass scan mode: metadata (default, zero loading) or deep (full Blueprint loading)
132
+ args.push(`-Mode=${deep ? 'deep' : 'metadata'}`);
133
+ // Build and pass filter prefixes (include + exclude)
134
+ const filters = await buildFilterPrefixes(repoPath, config);
135
+ if (filters.include_prefixes.length > 0 || filters.exclude_prefixes.length > 0) {
136
+ const filterJsonPath = path.join(unrealPaths.requests_dir, `filter-${randomUUID()}.json`);
137
+ await fs.writeFile(filterJsonPath, JSON.stringify(filters), 'utf-8');
138
+ args.push(`-FilterJson=${filterJsonPath}`);
139
+ }
65
140
  try {
66
141
  const { stdout } = await runCommand(config, 'SyncAssets', args);
67
142
  const manifest = await readOutputJson(outputPath, stdout);
@@ -75,6 +150,23 @@ export async function syncUnrealAssetManifest(storagePath, config) {
75
150
  };
76
151
  }
77
152
  catch (error) {
153
+ // UE may exit non-zero due to Blueprint compilation warnings even though
154
+ // the commandlet completed and wrote valid output. Try reading the file first.
155
+ try {
156
+ const stdout = error?.stdout ? String(error.stdout).trim() : '';
157
+ const manifest = await readOutputJson(outputPath, stdout);
158
+ if (manifest && Array.isArray(manifest.assets) && manifest.assets.length > 0) {
159
+ const manifestPath = await saveUnrealAssetManifest(storagePath, manifest);
160
+ return {
161
+ status: 'success',
162
+ manifest_path: manifestPath,
163
+ asset_count: manifest.assets.length,
164
+ generated_at: manifest.generated_at,
165
+ warnings: ['UE exited with non-zero code (likely Blueprint compilation warnings)'],
166
+ };
167
+ }
168
+ }
169
+ catch { /* output file not readable, fall through to error */ }
78
170
  const stderr = error?.stderr ? String(error.stderr).trim() : '';
79
171
  const stdout = error?.stdout ? String(error.stdout).trim() : '';
80
172
  const msg = error instanceof Error ? error.message : String(error);
@@ -19,6 +19,10 @@ export interface UnrealConfig {
19
19
  timeout_ms?: number;
20
20
  working_directory?: string;
21
21
  extra_args?: string[];
22
+ /** Unreal package path prefixes to exclude from sync (e.g., "/Game/ThirdParty", "/SomePlugin") */
23
+ exclude_paths?: string[];
24
+ /** Unreal package path prefixes to include (whitelist). If set, ONLY assets under these prefixes are scanned. */
25
+ include_paths?: string[];
22
26
  }
23
27
  export interface UnrealStoragePaths {
24
28
  root_dir: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duytransipher/gitnexus",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "description": "Sipher-maintained fork of GitNexus for graph-powered code intelligence via MCP and CLI.",
5
5
  "author": "DuyTranSipher",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",