gitnexus 1.6.11-rc.17 → 1.6.11-rc.18
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.
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { XMLParser } from 'fast-xml-parser';
|
|
3
4
|
import { shouldIgnorePath, loadIgnoreRules } from '../../../config/ignore-service.js';
|
|
4
5
|
import { logger } from '../../logger.js';
|
|
6
|
+
// POMs are static metadata. Parse hierarchy with a real XML parser, but do not
|
|
7
|
+
// invoke Maven or resolve the effective model. Properties, profiles, and remote
|
|
8
|
+
// parent resolution remain outside this extractor's deterministic boundary.
|
|
9
|
+
const pomParser = new XMLParser({
|
|
10
|
+
ignoreAttributes: true,
|
|
11
|
+
removeNSPrefix: true,
|
|
12
|
+
trimValues: true,
|
|
13
|
+
parseTagValue: false,
|
|
14
|
+
processEntities: false,
|
|
15
|
+
ignoreDeclaration: true,
|
|
16
|
+
ignorePiTags: true,
|
|
17
|
+
});
|
|
5
18
|
async function parseJavaManifest(repoPath) {
|
|
6
19
|
const pomPath = path.join(repoPath, 'pom.xml');
|
|
7
20
|
try {
|
|
@@ -9,13 +22,14 @@ async function parseJavaManifest(repoPath) {
|
|
|
9
22
|
return parsePom(content);
|
|
10
23
|
}
|
|
11
24
|
catch {
|
|
12
|
-
// fall through to Gradle
|
|
25
|
+
// Missing pom.xml — fall through to Gradle.
|
|
13
26
|
}
|
|
27
|
+
const gradleSidecars = await readGradleSidecars(repoPath);
|
|
14
28
|
for (const name of ['build.gradle.kts', 'build.gradle']) {
|
|
15
29
|
const gradlePath = path.join(repoPath, name);
|
|
16
30
|
try {
|
|
17
31
|
const content = await fs.readFile(gradlePath, 'utf-8');
|
|
18
|
-
return parseGradle(content, repoPath);
|
|
32
|
+
return parseGradle(content, repoPath, gradleSidecars);
|
|
19
33
|
}
|
|
20
34
|
catch {
|
|
21
35
|
continue;
|
|
@@ -23,45 +37,244 @@ async function parseJavaManifest(repoPath) {
|
|
|
23
37
|
}
|
|
24
38
|
return null;
|
|
25
39
|
}
|
|
40
|
+
async function readIfPresent(filePath) {
|
|
41
|
+
try {
|
|
42
|
+
return await fs.readFile(filePath, 'utf-8');
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async function readGradleSidecars(repoPath) {
|
|
49
|
+
const [properties, settingsKts, settingsGroovy, catalog] = await Promise.all([
|
|
50
|
+
readIfPresent(path.join(repoPath, 'gradle.properties')),
|
|
51
|
+
readIfPresent(path.join(repoPath, 'settings.gradle.kts')),
|
|
52
|
+
readIfPresent(path.join(repoPath, 'settings.gradle')),
|
|
53
|
+
readIfPresent(path.join(repoPath, 'gradle', 'libs.versions.toml')),
|
|
54
|
+
]);
|
|
55
|
+
const sidecars = {
|
|
56
|
+
catalogLibraries: new Map(),
|
|
57
|
+
catalogBundles: new Map(),
|
|
58
|
+
};
|
|
59
|
+
const groupMatch = properties?.match(/(?:^|\n)\s*group\s*=\s*([^\s#]+)/);
|
|
60
|
+
if (groupMatch)
|
|
61
|
+
sidecars.propertiesGroup = groupMatch[1];
|
|
62
|
+
const settings = settingsKts ?? settingsGroovy;
|
|
63
|
+
const nameMatch = settings?.match(/rootProject\.name\s*=\s*['"]([^'"]+)['"]/);
|
|
64
|
+
if (nameMatch)
|
|
65
|
+
sidecars.rootProjectName = nameMatch[1];
|
|
66
|
+
if (catalog) {
|
|
67
|
+
const parsed = parseGradleVersionCatalog(catalog);
|
|
68
|
+
sidecars.catalogLibraries = parsed.libraries;
|
|
69
|
+
sidecars.catalogBundles = parsed.bundles;
|
|
70
|
+
}
|
|
71
|
+
return sidecars;
|
|
72
|
+
}
|
|
73
|
+
function catalogAccessors(alias) {
|
|
74
|
+
const dotted = alias.replace(/[-_]/g, '.');
|
|
75
|
+
const camel = alias.replace(/[-_]+([A-Za-z0-9])/g, (_, char) => char.toUpperCase());
|
|
76
|
+
return [...new Set([alias, dotted, camel])];
|
|
77
|
+
}
|
|
78
|
+
function projectAccessorToArtifactId(accessor) {
|
|
79
|
+
const last = accessor.split('.').pop();
|
|
80
|
+
return last.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`).replace(/^-/, '');
|
|
81
|
+
}
|
|
82
|
+
function moduleToGa(module) {
|
|
83
|
+
const parts = module.split(':');
|
|
84
|
+
return parts.length >= 2 ? `${parts[0]}:${parts[1]}` : undefined;
|
|
85
|
+
}
|
|
86
|
+
function parseInlineTomlTable(rhs) {
|
|
87
|
+
const fields = {};
|
|
88
|
+
for (const match of rhs.matchAll(/([A-Za-z0-9_-]+)\s*=\s*['"]([^'"]+)['"]/g)) {
|
|
89
|
+
fields[match[1]] = match[2];
|
|
90
|
+
}
|
|
91
|
+
return fields;
|
|
92
|
+
}
|
|
93
|
+
/** Default Gradle catalog (`gradle/libs.versions.toml`) — aliases only, no version resolution. */
|
|
94
|
+
function parseGradleVersionCatalog(toml) {
|
|
95
|
+
const libraries = new Map();
|
|
96
|
+
const bundles = new Map();
|
|
97
|
+
let section = 'other';
|
|
98
|
+
const addLibrary = (alias, ga) => {
|
|
99
|
+
for (const accessor of catalogAccessors(alias))
|
|
100
|
+
libraries.set(accessor, ga);
|
|
101
|
+
};
|
|
102
|
+
for (const raw of toml.split(/\r?\n/)) {
|
|
103
|
+
const line = raw.replace(/#.*$/, '').trim();
|
|
104
|
+
if (!line)
|
|
105
|
+
continue;
|
|
106
|
+
const header = line.match(/^\[([^\]]+)\]$/);
|
|
107
|
+
if (header) {
|
|
108
|
+
const name = header[1];
|
|
109
|
+
section =
|
|
110
|
+
name === 'libraries' || name.endsWith('.libraries')
|
|
111
|
+
? 'libraries'
|
|
112
|
+
: name === 'bundles' || name.endsWith('.bundles')
|
|
113
|
+
? 'bundles'
|
|
114
|
+
: 'other';
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (section === 'libraries') {
|
|
118
|
+
const dottedModule = line.match(/^([A-Za-z0-9._-]+)\.module\s*=\s*['"]([^'"]+)['"]$/);
|
|
119
|
+
if (dottedModule) {
|
|
120
|
+
const ga = moduleToGa(dottedModule[2]);
|
|
121
|
+
if (ga)
|
|
122
|
+
addLibrary(dottedModule[1], ga);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const assignment = line.match(/^([A-Za-z0-9._-]+)\s*=\s*(.+)$/);
|
|
126
|
+
if (!assignment)
|
|
127
|
+
continue;
|
|
128
|
+
const alias = assignment[1];
|
|
129
|
+
const rhs = assignment[2].trim();
|
|
130
|
+
const quoted = rhs.match(/^['"]([^'"]+)['"]$/);
|
|
131
|
+
if (quoted) {
|
|
132
|
+
const ga = moduleToGa(quoted[1]);
|
|
133
|
+
if (ga)
|
|
134
|
+
addLibrary(alias, ga);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const table = parseInlineTomlTable(rhs);
|
|
138
|
+
const ga = table.module
|
|
139
|
+
? moduleToGa(table.module)
|
|
140
|
+
: table.group && table.name
|
|
141
|
+
? `${table.group}:${table.name}`
|
|
142
|
+
: undefined;
|
|
143
|
+
if (ga)
|
|
144
|
+
addLibrary(alias, ga);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (section === 'bundles') {
|
|
148
|
+
const assignment = line.match(/^([A-Za-z0-9._-]+)\s*=\s*\[([^\]]*)\]$/);
|
|
149
|
+
if (!assignment)
|
|
150
|
+
continue;
|
|
151
|
+
const members = [...assignment[2].matchAll(/['"]([^'"]+)['"]/g)].map((match) => match[1]);
|
|
152
|
+
for (const accessor of catalogAccessors(assignment[1]))
|
|
153
|
+
bundles.set(accessor, members);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return { libraries, bundles };
|
|
157
|
+
}
|
|
158
|
+
const GRADLE_GROUP_PATTERNS = [
|
|
159
|
+
/(?:^|[\n{;])\s*(?:rootProject\.)?group\s*=\s*['"]([^'"]+)['"]/,
|
|
160
|
+
/(?:^|[\n{;])\s*group\s+['"]([^'"]+)['"]/,
|
|
161
|
+
];
|
|
162
|
+
const GRADLE_COORD_CONFIGS = 'implementation|api|compileOnly|runtimeOnly|testImplementation|testApi|testCompileOnly|compile|kapt|ksp|commonMainImplementation|commonMainApi';
|
|
163
|
+
const CATALOG_ALIAS = '([A-Za-z0-9_]+(?:\\.[A-Za-z0-9_]+)*)(?:\\.get\\(\\)|\\.asProvider\\(\\))?';
|
|
164
|
+
function gradleDepRe(suffix) {
|
|
165
|
+
return new RegExp(`(?:${GRADLE_COORD_CONFIGS})\\s*${suffix}`, 'g');
|
|
166
|
+
}
|
|
167
|
+
function parseGradleGroup(content) {
|
|
168
|
+
for (const pattern of GRADLE_GROUP_PATTERNS) {
|
|
169
|
+
const match = content.match(pattern);
|
|
170
|
+
if (match?.[1])
|
|
171
|
+
return match[1];
|
|
172
|
+
}
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
function asXmlNode(value) {
|
|
176
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
177
|
+
? value
|
|
178
|
+
: undefined;
|
|
179
|
+
}
|
|
180
|
+
function xmlText(value) {
|
|
181
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
182
|
+
const text = String(value).trim();
|
|
183
|
+
return text || undefined;
|
|
184
|
+
}
|
|
185
|
+
const nested = asXmlNode(value)?.['#text'];
|
|
186
|
+
if (nested === undefined)
|
|
187
|
+
return undefined;
|
|
188
|
+
return xmlText(nested);
|
|
189
|
+
}
|
|
190
|
+
function xmlChildText(node, name) {
|
|
191
|
+
return node ? xmlText(node[name]) : undefined;
|
|
192
|
+
}
|
|
193
|
+
function asList(value) {
|
|
194
|
+
if (value === undefined || value === null)
|
|
195
|
+
return [];
|
|
196
|
+
return Array.isArray(value) ? value : [value];
|
|
197
|
+
}
|
|
198
|
+
/** Direct project dependencies only — not BOM, profiles, or plugin classpath. */
|
|
199
|
+
function collectProjectDependencies(project, deps) {
|
|
200
|
+
const dependencies = asXmlNode(project.dependencies);
|
|
201
|
+
if (!dependencies)
|
|
202
|
+
return;
|
|
203
|
+
for (const dep of asList(dependencies.dependency)) {
|
|
204
|
+
const depNode = asXmlNode(dep);
|
|
205
|
+
const groupId = xmlChildText(depNode, 'groupId');
|
|
206
|
+
const artifactId = xmlChildText(depNode, 'artifactId');
|
|
207
|
+
if (groupId && artifactId)
|
|
208
|
+
deps.push(`${groupId}:${artifactId}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
26
211
|
function parsePom(content) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
212
|
+
let parsed;
|
|
213
|
+
try {
|
|
214
|
+
// parseSourceSafe guards tree-sitter's Windows SIGSEGV by switching to a
|
|
215
|
+
// chunked input callback above 16 KB; XMLParser only accepts XML text, so
|
|
216
|
+
// routing POMs through it silently yields an empty document.
|
|
217
|
+
// eslint-disable-next-line gitnexus/require-safe-parse
|
|
218
|
+
parsed = pomParser.parse(content);
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
30
221
|
return null;
|
|
31
|
-
const groupId = projectGroupMatch[1].trim();
|
|
32
|
-
const artifactId = projectArtifactMatch[1].trim();
|
|
33
|
-
const deps = [];
|
|
34
|
-
const depBlocks = content.matchAll(/<dependency>\s*([\s\S]*?)<\/dependency>/g);
|
|
35
|
-
for (const block of depBlocks) {
|
|
36
|
-
const gMatch = block[1].match(/<groupId>([^<]+)<\/groupId>/);
|
|
37
|
-
const aMatch = block[1].match(/<artifactId>([^<]+)<\/artifactId>/);
|
|
38
|
-
if (gMatch && aMatch) {
|
|
39
|
-
deps.push(`${gMatch[1].trim()}:${aMatch[1].trim()}`);
|
|
40
|
-
}
|
|
41
222
|
}
|
|
223
|
+
const project = asXmlNode(asXmlNode(parsed)?.project);
|
|
224
|
+
if (!project)
|
|
225
|
+
return null;
|
|
226
|
+
// Maven inherits groupId from <parent>, but artifactId is always the
|
|
227
|
+
// project's own direct child and must never fall back to parent.artifactId.
|
|
228
|
+
const groupId = xmlChildText(project, 'groupId') ?? xmlChildText(asXmlNode(project.parent), 'groupId');
|
|
229
|
+
const artifactId = xmlChildText(project, 'artifactId');
|
|
230
|
+
if (!groupId || !artifactId)
|
|
231
|
+
return null;
|
|
232
|
+
const deps = [];
|
|
233
|
+
collectProjectDependencies(project, deps);
|
|
42
234
|
return { groupId, artifactId, deps: [...new Set(deps)] };
|
|
43
235
|
}
|
|
44
|
-
function parseGradle(content, repoPath) {
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
const groupId = groupMatch ? groupMatch[1] : '';
|
|
236
|
+
function parseGradle(content, repoPath, sidecars = { catalogLibraries: new Map(), catalogBundles: new Map() }) {
|
|
237
|
+
// Static text + default catalog file. Do not execute Gradle.
|
|
238
|
+
const groupId = parseGradleGroup(content) ?? sidecars.propertiesGroup ?? '';
|
|
48
239
|
if (!groupId)
|
|
49
240
|
return null;
|
|
50
|
-
const artifactId =
|
|
241
|
+
const artifactId = sidecars.rootProjectName ?? path.basename(repoPath);
|
|
242
|
+
const { catalogLibraries, catalogBundles } = sidecars;
|
|
51
243
|
const deps = [];
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
244
|
+
const pushCatalogAlias = (alias) => {
|
|
245
|
+
const ga = catalogLibraries.get(alias);
|
|
246
|
+
if (ga)
|
|
247
|
+
deps.push(ga);
|
|
248
|
+
};
|
|
249
|
+
const namedPattern = gradleDepRe(`(?:\\(\\s*)?(?:group\\s*=\\s*['"](?<group1>[^'"]+)['"]\\s*,\\s*name\\s*=\\s*['"](?<name1>[^'"]+)['"]|name\\s*=\\s*['"](?<name2>[^'"]+)['"]\\s*,\\s*group\\s*=\\s*['"](?<group2>[^'"]+)['"]|group:\\s*['"](?<group3>[^'"]+)['"]\\s*,\\s*name:\\s*['"](?<name3>[^'"]+)['"]|name:\\s*['"](?<name4>[^'"]+)['"]\\s*,\\s*group:\\s*['"](?<group4>[^'"]+)['"])`);
|
|
250
|
+
for (const match of content.matchAll(namedPattern)) {
|
|
251
|
+
const group = match.groups?.group1 ?? match.groups?.group2 ?? match.groups?.group3 ?? match.groups?.group4;
|
|
252
|
+
const name = match.groups?.name1 ?? match.groups?.name2 ?? match.groups?.name3 ?? match.groups?.name4;
|
|
253
|
+
if (group && name)
|
|
254
|
+
deps.push(`${group}:${name}`);
|
|
255
|
+
}
|
|
256
|
+
for (const match of content.matchAll(gradleDepRe(`(?:\\(\\s*)?libs(?:\\.libraries)?\\.(?!bundles\\.|plugins\\.)${CATALOG_ALIAS}`))) {
|
|
257
|
+
pushCatalogAlias(match[1]);
|
|
258
|
+
}
|
|
259
|
+
for (const match of content.matchAll(gradleDepRe(`(?:\\(\\s*)?libs\\.bundles\\.${CATALOG_ALIAS}`))) {
|
|
260
|
+
for (const member of catalogBundles.get(match[1]) ?? []) {
|
|
261
|
+
for (const accessor of catalogAccessors(member))
|
|
262
|
+
pushCatalogAlias(accessor);
|
|
58
263
|
}
|
|
59
264
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
265
|
+
for (const match of content.matchAll(gradleDepRe(`\\(\\s*projects\\.([A-Za-z][A-Za-z0-9.]*)`))) {
|
|
266
|
+
deps.push(`${groupId}:${projectAccessorToArtifactId(match[1])}`);
|
|
267
|
+
}
|
|
268
|
+
for (const match of content.matchAll(gradleDepRe(`(?:\\(\\s*['"]([^'"]+)['"]\\s*\\)|['"]([^'"]+)['"])`))) {
|
|
269
|
+
const coord = match[1] ?? match[2];
|
|
270
|
+
if (!coord)
|
|
271
|
+
continue;
|
|
272
|
+
const parts = coord.split(':');
|
|
273
|
+
if (parts.length >= 2)
|
|
274
|
+
deps.push(`${parts[0]}:${parts[1]}`);
|
|
275
|
+
}
|
|
276
|
+
for (const match of content.matchAll(gradleDepRe(`(?:\\(\\s*)?project\\s*\\(\\s*['"]([^'"]+)['"]\\s*\\)`))) {
|
|
277
|
+
deps.push(`${groupId}:${match[1].replace(/^:/, '')}`);
|
|
65
278
|
}
|
|
66
279
|
return { groupId, artifactId, deps: [...new Set(deps)] };
|
|
67
280
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gitnexus",
|
|
3
|
-
"version": "1.6.11-rc.
|
|
3
|
+
"version": "1.6.11-rc.18",
|
|
4
4
|
"description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
|
|
5
5
|
"author": "Abhigyan Patwari",
|
|
6
6
|
"license": "PolyForm-Noncommercial-1.0.0",
|
|
@@ -66,6 +66,7 @@
|
|
|
66
66
|
"cors": "^2.8.5",
|
|
67
67
|
"express": "^5.2.1",
|
|
68
68
|
"express-rate-limit": "^8.4.1",
|
|
69
|
+
"fast-xml-parser": "^5.11.1",
|
|
69
70
|
"glob": "^13.0.6",
|
|
70
71
|
"graphology": "^0.26.0",
|
|
71
72
|
"graphology-indices": "^0.17.0",
|