genexus-mcp 3.1.0 → 3.2.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.
- package/README.md +57 -6
- package/cli/commands/axi.js +234 -22
- package/cli/lib/config.js +299 -40
- package/cli/run.test.js +233 -3
- package/config/gx-versions.json +20 -0
- package/package.json +5 -2
- package/publish/GxMcp.Gateway.deps.json +2 -2
- package/publish/GxMcp.Gateway.dll +0 -0
- package/publish/GxMcp.Gateway.exe +0 -0
- package/publish/config/gx-versions.json +20 -0
- package/publish/config.json +8 -8
- package/publish/gxmcp-manifest.json +9 -9
- package/publish/gxmcp-sbom.json +4 -4
- package/publish/nexus-ide.vsix +0 -0
- package/publish/worker/GxMcp.Worker.exe +0 -0
- package/publish/worker/config/gx-versions.json +20 -0
- package/publish/worker/gx-versions.json +20 -0
package/cli/lib/config.js
CHANGED
|
@@ -56,34 +56,95 @@ function getToolDefinitionsPath() {
|
|
|
56
56
|
return candidates[0];
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
function
|
|
59
|
+
function getGeneXusVersionCatalog() {
|
|
60
|
+
const candidates = [
|
|
61
|
+
path.join(__dirname, '..', '..', 'config', 'gx-versions.json'),
|
|
62
|
+
path.join(__dirname, '..', '..', 'publish', 'config', 'gx-versions.json')
|
|
63
|
+
];
|
|
64
|
+
for (const candidate of candidates) {
|
|
65
|
+
try {
|
|
66
|
+
const catalog = JSON.parse(fs.readFileSync(candidate, 'utf8'));
|
|
67
|
+
if (!catalog || !catalog.primaryMajor || !Array.isArray(catalog.supportedMajors)) continue;
|
|
68
|
+
const entries = catalog.supportedMajors.filter((entry) => entry && /^\d+$/.test(String(entry.major)));
|
|
69
|
+
const primary = entries.find((entry) => String(entry.major) === String(catalog.primaryMajor));
|
|
70
|
+
if (entries.length > 0 && primary?.defaultInstallPath) {
|
|
71
|
+
return { ...catalog, supportedMajors: entries };
|
|
72
|
+
}
|
|
73
|
+
} catch {
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
primaryMajor: '18',
|
|
78
|
+
supportedMajors: [
|
|
79
|
+
{ major: '17', displayName: 'GeneXus 17', defaultInstallPath: 'C:\\Program Files (x86)\\GeneXus\\GeneXus17Trial' },
|
|
80
|
+
{ major: '18', displayName: 'GeneXus 18', defaultInstallPath: 'C:\\Program Files (x86)\\GeneXus\\GeneXus18' }
|
|
81
|
+
],
|
|
82
|
+
source: 'built-in-fallback'
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function getGeneXusCatalogEntries(preferredMajor = null) {
|
|
87
|
+
const catalog = getGeneXusVersionCatalog();
|
|
88
|
+
return [...catalog.supportedMajors].sort((left, right) => {
|
|
89
|
+
const leftPreferred = preferredMajor !== null && String(left.major) === String(preferredMajor);
|
|
90
|
+
const rightPreferred = preferredMajor !== null && String(right.major) === String(preferredMajor);
|
|
91
|
+
if (leftPreferred !== rightPreferred) return Number(rightPreferred) - Number(leftPreferred);
|
|
92
|
+
const leftPrimary = String(left.major) === String(catalog.primaryMajor);
|
|
93
|
+
const rightPrimary = String(right.major) === String(catalog.primaryMajor);
|
|
94
|
+
return Number(rightPrimary) - Number(leftPrimary);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function discoverGeneXusFromRegistry(preferredMajor = null) {
|
|
60
99
|
if (process.platform !== 'win32') return null;
|
|
61
100
|
try {
|
|
62
101
|
const { execFileSync } = require('child_process');
|
|
63
|
-
const
|
|
102
|
+
const entries = getGeneXusCatalogEntries(preferredMajor);
|
|
64
103
|
const hives = [
|
|
65
104
|
'HKLM\\SOFTWARE\\WOW6432Node\\Artech',
|
|
66
105
|
'HKLM\\SOFTWARE\\Artech',
|
|
67
106
|
'HKCU\\SOFTWARE\\Artech'
|
|
68
107
|
];
|
|
69
108
|
for (const hive of hives) {
|
|
70
|
-
for (const
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
109
|
+
for (const entry of entries) {
|
|
110
|
+
const names = Array.isArray(entry.registryNames) && entry.registryNames.length > 0
|
|
111
|
+
? entry.registryNames
|
|
112
|
+
: [`GeneXus ${entry.major}`];
|
|
113
|
+
for (const ver of names) {
|
|
114
|
+
const key = `${hive}\\${ver}`;
|
|
115
|
+
try {
|
|
116
|
+
const out = execFileSync('reg.exe', ['query', key, '/v', 'InstallationDirectory'], {
|
|
117
|
+
encoding: 'utf8',
|
|
118
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
119
|
+
windowsHide: true,
|
|
120
|
+
timeout: 3000
|
|
121
|
+
});
|
|
122
|
+
const match = out.match(/InstallationDirectory\s+REG_SZ\s+(.+?)\r?\n/i);
|
|
123
|
+
if (match) {
|
|
124
|
+
const candidate = match[1].trim().replace(/[\\/]+$/, '');
|
|
125
|
+
if (candidate && fs.existsSync(path.join(candidate, 'genexus.exe')) && matchesPreferredGeneXusMajor(candidate, preferredMajor)) {
|
|
126
|
+
return candidate;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
} catch {
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
for (const legacyVersion of (entry.legacyRegistryVersions || [])) {
|
|
133
|
+
const key = `${hive}\\GeneXus\\${legacyVersion}`;
|
|
134
|
+
try {
|
|
135
|
+
const out = execFileSync('reg.exe', ['query', key, '/v', 'InstallPath'], {
|
|
136
|
+
encoding: 'utf8',
|
|
137
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
138
|
+
windowsHide: true,
|
|
139
|
+
timeout: 3000
|
|
140
|
+
});
|
|
141
|
+
const match = out.match(/InstallPath\s+REG_SZ\s+(.+?)\r?\n/i);
|
|
142
|
+
if (match) {
|
|
143
|
+
const candidate = match[1].trim().replace(/[\\/]+$/, '');
|
|
144
|
+
if (candidate && fs.existsSync(path.join(candidate, 'genexus.exe')) && matchesPreferredGeneXusMajor(candidate, preferredMajor)) return candidate;
|
|
84
145
|
}
|
|
146
|
+
} catch {
|
|
85
147
|
}
|
|
86
|
-
} catch {
|
|
87
148
|
}
|
|
88
149
|
}
|
|
89
150
|
}
|
|
@@ -92,13 +153,13 @@ function discoverGeneXusFromRegistry() {
|
|
|
92
153
|
return null;
|
|
93
154
|
}
|
|
94
155
|
|
|
95
|
-
function discoverGeneXusInstallation() {
|
|
156
|
+
function discoverGeneXusInstallation(preferredMajor = null) {
|
|
96
157
|
if (process.env.GENEXUS_HOME) {
|
|
97
158
|
const candidate = process.env.GENEXUS_HOME.replace(/[\\/]+$/, '');
|
|
98
|
-
if (fs.existsSync(path.join(candidate, 'genexus.exe'))) return candidate;
|
|
159
|
+
if (fs.existsSync(path.join(candidate, 'genexus.exe')) && matchesPreferredGeneXusMajor(candidate, preferredMajor)) return candidate;
|
|
99
160
|
}
|
|
100
161
|
|
|
101
|
-
const fromRegistry = discoverGeneXusFromRegistry();
|
|
162
|
+
const fromRegistry = discoverGeneXusFromRegistry(preferredMajor);
|
|
102
163
|
if (fromRegistry) return fromRegistry;
|
|
103
164
|
|
|
104
165
|
const programDirs = [];
|
|
@@ -110,17 +171,24 @@ function discoverGeneXusInstallation() {
|
|
|
110
171
|
programDirs.push(`${drive}:\\Program Files`);
|
|
111
172
|
}
|
|
112
173
|
|
|
113
|
-
const
|
|
174
|
+
const entries = getGeneXusCatalogEntries(preferredMajor);
|
|
114
175
|
const seen = new Set();
|
|
115
176
|
for (const base of programDirs) {
|
|
116
177
|
const root = path.join(base, 'GeneXus');
|
|
117
178
|
const key = root.toLowerCase();
|
|
118
179
|
if (seen.has(key)) continue;
|
|
119
180
|
seen.add(key);
|
|
120
|
-
for (const
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
181
|
+
for (const entry of entries) {
|
|
182
|
+
const candidateNames = new Set([
|
|
183
|
+
`GeneXus${entry.major}`,
|
|
184
|
+
path.basename(String(entry.defaultInstallPath || ''))
|
|
185
|
+
]);
|
|
186
|
+
for (const ver of candidateNames) {
|
|
187
|
+
if (!ver) continue;
|
|
188
|
+
const candidate = path.join(root, ver);
|
|
189
|
+
if (fs.existsSync(path.join(candidate, 'genexus.exe')) && matchesPreferredGeneXusMajor(candidate, preferredMajor)) {
|
|
190
|
+
return candidate;
|
|
191
|
+
}
|
|
124
192
|
}
|
|
125
193
|
}
|
|
126
194
|
// Also scan any GeneXus* sibling (e.g. custom-named "GeneXus18 U10").
|
|
@@ -128,8 +196,10 @@ function discoverGeneXusInstallation() {
|
|
|
128
196
|
if (fs.existsSync(root)) {
|
|
129
197
|
for (const entry of fs.readdirSync(root)) {
|
|
130
198
|
if (!/^GeneXus/i.test(entry)) continue;
|
|
199
|
+
const majorMatch = entry.match(/^GeneXus\s*(\d+)/i);
|
|
200
|
+
if (majorMatch && !entries.some((item) => String(item.major) === majorMatch[1])) continue;
|
|
131
201
|
const candidate = path.join(root, entry);
|
|
132
|
-
if (fs.existsSync(path.join(candidate, 'genexus.exe'))) {
|
|
202
|
+
if (fs.existsSync(path.join(candidate, 'genexus.exe')) && matchesPreferredGeneXusMajor(candidate, preferredMajor)) {
|
|
133
203
|
return candidate;
|
|
134
204
|
}
|
|
135
205
|
}
|
|
@@ -138,13 +208,13 @@ function discoverGeneXusInstallation() {
|
|
|
138
208
|
}
|
|
139
209
|
}
|
|
140
210
|
|
|
141
|
-
const fromPath = discoverGeneXusFromPath();
|
|
211
|
+
const fromPath = discoverGeneXusFromPath(preferredMajor);
|
|
142
212
|
if (fromPath) return fromPath;
|
|
143
213
|
|
|
144
214
|
return null;
|
|
145
215
|
}
|
|
146
216
|
|
|
147
|
-
function discoverGeneXusFromPath() {
|
|
217
|
+
function discoverGeneXusFromPath(preferredMajor = null) {
|
|
148
218
|
if (process.platform !== 'win32') return null;
|
|
149
219
|
try {
|
|
150
220
|
const { execFileSync } = require('child_process');
|
|
@@ -155,7 +225,7 @@ function discoverGeneXusFromPath() {
|
|
|
155
225
|
timeout: 3000
|
|
156
226
|
});
|
|
157
227
|
const first = out.split(/\r?\n/).map((s) => s.trim()).find(Boolean);
|
|
158
|
-
if (first && fs.existsSync(first)) {
|
|
228
|
+
if (first && fs.existsSync(first) && matchesPreferredGeneXusMajor(path.dirname(first), preferredMajor)) {
|
|
159
229
|
return path.dirname(first);
|
|
160
230
|
}
|
|
161
231
|
} catch {
|
|
@@ -1352,8 +1422,43 @@ function getLocalAppDataCacheDir() {
|
|
|
1352
1422
|
return path.join(base, 'GenexusMCP');
|
|
1353
1423
|
}
|
|
1354
1424
|
|
|
1355
|
-
function
|
|
1356
|
-
|
|
1425
|
+
function getGeneXusMajor(version) {
|
|
1426
|
+
const match = String(version || '').match(/^\s*(\d+)/);
|
|
1427
|
+
return match ? match[1] : null;
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
function matchesPreferredGeneXusMajor(gxPath, preferredMajor) {
|
|
1431
|
+
if (preferredMajor === null || preferredMajor === undefined || String(preferredMajor).trim() === '') return true;
|
|
1432
|
+
const identity = readGeneXusInstallationIdentity(gxPath);
|
|
1433
|
+
return String(identity.major || '') === String(preferredMajor);
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
function readExecutableProductVersion(exePath) {
|
|
1437
|
+
if (process.platform !== 'win32' || !exePath || !fs.existsSync(exePath)) return null;
|
|
1438
|
+
try {
|
|
1439
|
+
const { execFileSync } = require('child_process');
|
|
1440
|
+
const output = execFileSync('powershell.exe', [
|
|
1441
|
+
'-NoProfile',
|
|
1442
|
+
'-NonInteractive',
|
|
1443
|
+
'-Command',
|
|
1444
|
+
'$versionInfo = (Get-Item -LiteralPath $env:GXMCP_VERSION_EXE -ErrorAction Stop).VersionInfo; if ($versionInfo.ProductVersion) { $versionInfo.ProductVersion } else { $versionInfo.FileVersion }'
|
|
1445
|
+
], {
|
|
1446
|
+
encoding: 'utf8',
|
|
1447
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
1448
|
+
windowsHide: true,
|
|
1449
|
+
timeout: 3000,
|
|
1450
|
+
env: { ...process.env, GXMCP_VERSION_EXE: exePath }
|
|
1451
|
+
});
|
|
1452
|
+
return output.split(/\r?\n/).map((line) => line.trim()).find(Boolean) || null;
|
|
1453
|
+
} catch {
|
|
1454
|
+
return null;
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
function readGeneXusInstallationIdentity(gxPath, options = {}) {
|
|
1459
|
+
if (!gxPath) return { version: null, major: null, source: 'unavailable' };
|
|
1460
|
+
|
|
1461
|
+
let unresolvedVersionFile = null;
|
|
1357
1462
|
const candidates = [
|
|
1358
1463
|
path.join(gxPath, 'version.txt'),
|
|
1359
1464
|
path.join(gxPath, 'Version.txt'),
|
|
@@ -1362,11 +1467,138 @@ function readGeneXusVersionFromInstall(gxPath) {
|
|
|
1362
1467
|
for (const candidate of candidates) {
|
|
1363
1468
|
try {
|
|
1364
1469
|
const raw = fs.readFileSync(candidate, 'utf8').trim();
|
|
1365
|
-
|
|
1470
|
+
const version = raw.split(/\r?\n/)[0].trim();
|
|
1471
|
+
if (version) {
|
|
1472
|
+
const major = getGeneXusMajor(version);
|
|
1473
|
+
if (major) return { version, major, source: 'version-file' };
|
|
1474
|
+
unresolvedVersionFile = unresolvedVersionFile || version;
|
|
1475
|
+
}
|
|
1366
1476
|
} catch {
|
|
1367
1477
|
}
|
|
1368
1478
|
}
|
|
1369
|
-
|
|
1479
|
+
|
|
1480
|
+
const readExecutableVersion = typeof options.readExecutableVersion === 'function'
|
|
1481
|
+
? options.readExecutableVersion
|
|
1482
|
+
: readExecutableProductVersion;
|
|
1483
|
+
const executableVersion = readExecutableVersion(path.join(gxPath, 'GeneXus.exe'));
|
|
1484
|
+
if (executableVersion) {
|
|
1485
|
+
return {
|
|
1486
|
+
version: executableVersion,
|
|
1487
|
+
major: getGeneXusMajor(executableVersion),
|
|
1488
|
+
source: 'executable-metadata'
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
const pathMatch = path.basename(String(gxPath)).match(/^GeneXus\s*(\d+)/i);
|
|
1493
|
+
if (pathMatch && fs.existsSync(gxPath)) return { version: null, major: pathMatch[1], source: 'path-name' };
|
|
1494
|
+
if (unresolvedVersionFile) return { version: unresolvedVersionFile, major: null, source: 'version-file' };
|
|
1495
|
+
return { version: null, major: null, source: 'unavailable' };
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
function readGeneXusVersionFromInstall(gxPath, options) {
|
|
1499
|
+
return readGeneXusInstallationIdentity(gxPath, options).version;
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
function isWellFormedXml(source) {
|
|
1503
|
+
const xml = String(source || '').replace(/^\uFEFF/, '');
|
|
1504
|
+
const stack = [];
|
|
1505
|
+
let rootCount = 0;
|
|
1506
|
+
let index = 0;
|
|
1507
|
+
while (true) {
|
|
1508
|
+
const open = xml.indexOf('<', index);
|
|
1509
|
+
if (open < 0) break;
|
|
1510
|
+
if (xml.startsWith('<!--', open)) {
|
|
1511
|
+
const endComment = xml.indexOf('-->', open + 4);
|
|
1512
|
+
if (endComment < 0) return false;
|
|
1513
|
+
index = endComment + 3;
|
|
1514
|
+
continue;
|
|
1515
|
+
}
|
|
1516
|
+
if (xml.startsWith('<![CDATA[', open)) {
|
|
1517
|
+
const endCdata = xml.indexOf(']]>', open + 9);
|
|
1518
|
+
if (endCdata < 0) return false;
|
|
1519
|
+
index = endCdata + 3;
|
|
1520
|
+
continue;
|
|
1521
|
+
}
|
|
1522
|
+
if (xml.startsWith('<?', open)) {
|
|
1523
|
+
const endInstruction = xml.indexOf('?>', open + 2);
|
|
1524
|
+
if (endInstruction < 0) return false;
|
|
1525
|
+
index = endInstruction + 2;
|
|
1526
|
+
continue;
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
let end = open + 1;
|
|
1530
|
+
let quote = null;
|
|
1531
|
+
for (; end < xml.length; end++) {
|
|
1532
|
+
const character = xml[end];
|
|
1533
|
+
if (quote) {
|
|
1534
|
+
if (character === quote) quote = null;
|
|
1535
|
+
} else if (character === '"' || character === "'") {
|
|
1536
|
+
quote = character;
|
|
1537
|
+
} else if (character === '>') {
|
|
1538
|
+
break;
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
if (end >= xml.length || quote) return false;
|
|
1542
|
+
|
|
1543
|
+
const body = xml.slice(open + 1, end).trim();
|
|
1544
|
+
if (!body || body.startsWith('!')) {
|
|
1545
|
+
index = end + 1;
|
|
1546
|
+
continue;
|
|
1547
|
+
}
|
|
1548
|
+
if (body.startsWith('/')) {
|
|
1549
|
+
const closing = body.slice(1).trim();
|
|
1550
|
+
const closingMatch = closing.match(/^([A-Za-z_][A-Za-z0-9_.:-]*)\s*$/);
|
|
1551
|
+
if (!closingMatch || stack.length === 0 || stack[stack.length - 1] !== closingMatch[1]) return false;
|
|
1552
|
+
stack.pop();
|
|
1553
|
+
} else {
|
|
1554
|
+
const opening = body.replace(/\/\s*$/, '').trim();
|
|
1555
|
+
const openingMatch = opening.match(/^([A-Za-z_][A-Za-z0-9_.:-]*)\b/);
|
|
1556
|
+
if (!openingMatch) return false;
|
|
1557
|
+
if (stack.length === 0) rootCount++;
|
|
1558
|
+
if (!body.endsWith('/')) stack.push(openingMatch[1]);
|
|
1559
|
+
}
|
|
1560
|
+
index = end + 1;
|
|
1561
|
+
}
|
|
1562
|
+
return rootCount === 1 && stack.length === 0;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
function readGeneXusKbIdentity(kbPath) {
|
|
1566
|
+
if (!kbPath) return { version: null, major: null, source: 'unavailable', reason: 'missing-kb-path' };
|
|
1567
|
+
|
|
1568
|
+
let gxwFiles;
|
|
1569
|
+
try {
|
|
1570
|
+
gxwFiles = fs.readdirSync(kbPath)
|
|
1571
|
+
.filter((fileName) => fileName.toLowerCase().endsWith('.gxw'))
|
|
1572
|
+
.map((fileName) => path.join(kbPath, fileName));
|
|
1573
|
+
} catch {
|
|
1574
|
+
return { version: null, major: null, source: 'unavailable', reason: 'unreadable-kb-path' };
|
|
1575
|
+
}
|
|
1576
|
+
if (gxwFiles.length === 0) return { version: null, major: null, source: 'unavailable', reason: 'no-gxw' };
|
|
1577
|
+
if (gxwFiles.length > 1) return { version: null, major: null, source: 'unavailable', reason: 'multiple-gxw' };
|
|
1578
|
+
|
|
1579
|
+
try {
|
|
1580
|
+
const source = fs.readFileSync(gxwFiles[0], 'utf8');
|
|
1581
|
+
if (!source.trim()) return { version: null, major: null, source: 'unavailable', reason: 'empty-gxw' };
|
|
1582
|
+
if (!isWellFormedXml(source)) return { version: null, major: null, source: 'unavailable', reason: 'malformed-gxw' };
|
|
1583
|
+
const raw = source.replace(/<!--[\s\S]*?-->/g, '');
|
|
1584
|
+
for (const field of ['VersionNumber', 'FriendlyVersion']) {
|
|
1585
|
+
const pattern = new RegExp(`<(?:(?:[\\w.-]+):)?${field}\\b[^>]*>([\\s\\S]*?)<\\/(?:(?:[\\w.-]+):)?${field}\\s*>`, 'i');
|
|
1586
|
+
const match = raw.match(pattern);
|
|
1587
|
+
const version = match && match[1] ? match[1].replace(/<[^>]+>/g, '').trim() : '';
|
|
1588
|
+
if (version) return { version, major: getGeneXusMajor(version), source: 'gxw-version' };
|
|
1589
|
+
}
|
|
1590
|
+
return { version: null, major: null, source: 'unavailable', reason: 'missing-gxw-version' };
|
|
1591
|
+
} catch {
|
|
1592
|
+
return { version: null, major: null, source: 'unavailable', reason: 'unreadable-gxw' };
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
function compareGeneXusKbAndInstallation(kbPath, gxPath) {
|
|
1597
|
+
const kb = readGeneXusKbIdentity(kbPath);
|
|
1598
|
+
const gx = readGeneXusInstallationIdentity(gxPath);
|
|
1599
|
+
let status = 'unresolved';
|
|
1600
|
+
if (kb.major && gx.major) status = kb.major === gx.major ? 'match' : 'mismatch';
|
|
1601
|
+
return { status, kb, gx };
|
|
1370
1602
|
}
|
|
1371
1603
|
|
|
1372
1604
|
function normalizeKbCatalog(raw) {
|
|
@@ -1497,21 +1729,42 @@ function applyLauncherConfigOrExit({ cwd, stderr, quiet }) {
|
|
|
1497
1729
|
return { ok: true };
|
|
1498
1730
|
}
|
|
1499
1731
|
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
log(
|
|
1732
|
+
if (!directoryLooksLikeKnowledgeBase(cwd)) {
|
|
1733
|
+
log('[genexus-mcp] ERROR: Zero-config failed because current directory is not a GeneXus KB.');
|
|
1734
|
+
log(`[genexus-mcp] CWD: ${cwd}`);
|
|
1503
1735
|
log('[genexus-mcp] Fix with: npx genexus-mcp init --interactive');
|
|
1504
1736
|
return { ok: false };
|
|
1505
1737
|
}
|
|
1506
1738
|
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
log(
|
|
1739
|
+
const kbIdentity = readGeneXusKbIdentity(cwd);
|
|
1740
|
+
if (!kbIdentity.major) {
|
|
1741
|
+
log('[genexus-mcp] ERROR: Zero-config could not determine the KB GeneXus major safely.');
|
|
1742
|
+
log('[genexus-mcp] Fix with: npx genexus-mcp init --kb "<kbPath>" --gx "<geneXusPath>"');
|
|
1743
|
+
return { ok: false };
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
const foundGxPath = discoverGeneXusInstallation(kbIdentity.major);
|
|
1747
|
+
if (!foundGxPath) {
|
|
1748
|
+
log('[genexus-mcp] ERROR: No GeneXus installation matching the KB major was auto-discovered.');
|
|
1749
|
+
log(`[genexus-mcp] KB major: ${kbIdentity.major}`);
|
|
1510
1750
|
log('[genexus-mcp] Fix with: npx genexus-mcp init --interactive');
|
|
1511
1751
|
return { ok: false };
|
|
1512
1752
|
}
|
|
1513
1753
|
|
|
1514
|
-
|
|
1754
|
+
const compatibility = compareGeneXusKbAndInstallation(cwd, foundGxPath);
|
|
1755
|
+
if (compatibility.status === 'mismatch') {
|
|
1756
|
+
log(`[genexus-mcp] ERROR: Auto-discovered SDK major ${compatibility.gx.major} does not match KB major ${compatibility.kb.major}.`);
|
|
1757
|
+
log('[genexus-mcp] Fix with: npx genexus-mcp init --kb "<kbPath>" --gx "<matching GeneXus path>"');
|
|
1758
|
+
return { ok: false };
|
|
1759
|
+
}
|
|
1760
|
+
if (compatibility.status !== 'match') {
|
|
1761
|
+
log('[genexus-mcp] ERROR: Zero-config could not verify that the discovered GeneXus SDK matches the KB major.');
|
|
1762
|
+
log(`[genexus-mcp] KB major: ${compatibility.kb.major}; SDK detection: ${compatibility.gx.source}`);
|
|
1763
|
+
log('[genexus-mcp] Fix with: npx genexus-mcp init --kb "<kbPath>" --gx "<matching GeneXus path>"');
|
|
1764
|
+
return { ok: false };
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
log(`[genexus-mcp] Auto-discovered GeneXus ${kbIdentity.major} at: ${foundGxPath}`);
|
|
1515
1768
|
log(`[genexus-mcp] Generating default config.json for KB at: ${cwd}`);
|
|
1516
1769
|
|
|
1517
1770
|
const defaultConfig = generateConfig(foundGxPath, cwd);
|
|
@@ -1525,6 +1778,8 @@ module.exports = {
|
|
|
1525
1778
|
generateConfig,
|
|
1526
1779
|
getGatewayExePath,
|
|
1527
1780
|
getToolDefinitionsPath,
|
|
1781
|
+
getGeneXusVersionCatalog,
|
|
1782
|
+
getGeneXusCatalogEntries,
|
|
1528
1783
|
discoverGeneXusInstallation,
|
|
1529
1784
|
discoverGeneXusFromRegistry,
|
|
1530
1785
|
discoverKnowledgeBase,
|
|
@@ -1541,7 +1796,11 @@ module.exports = {
|
|
|
1541
1796
|
listSupportedClientIds,
|
|
1542
1797
|
filterClientTargets,
|
|
1543
1798
|
getLocalAppDataCacheDir,
|
|
1799
|
+
getGeneXusMajor,
|
|
1800
|
+
readGeneXusInstallationIdentity,
|
|
1544
1801
|
readGeneXusVersionFromInstall,
|
|
1802
|
+
readGeneXusKbIdentity,
|
|
1803
|
+
compareGeneXusKbAndInstallation,
|
|
1545
1804
|
readKbCatalog,
|
|
1546
1805
|
addKbToConfig,
|
|
1547
1806
|
removeKbFromConfig,
|