@adhisang/minecraft-modding-mcp 1.2.1 → 2.1.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/CHANGELOG.md +73 -0
- package/README.md +184 -64
- package/dist/cli.js +31 -4
- package/dist/compat-stdio-transport.d.ts +2 -7
- package/dist/compat-stdio-transport.js +12 -154
- package/dist/index.js +537 -202
- package/dist/json-rpc-framing.d.ts +22 -0
- package/dist/json-rpc-framing.js +168 -0
- package/dist/mapping-pipeline-service.d.ts +1 -1
- package/dist/mapping-pipeline-service.js +13 -5
- package/dist/mapping-service.d.ts +12 -4
- package/dist/mapping-service.js +222 -105
- package/dist/mcp-helpers.d.ts +10 -2
- package/dist/mcp-helpers.js +59 -5
- package/dist/minecraft-explorer-service.d.ts +1 -2
- package/dist/minecraft-explorer-service.js +120 -24
- package/dist/mixin-validator.d.ts +24 -2
- package/dist/mixin-validator.js +228 -103
- package/dist/mod-decompile-service.d.ts +5 -0
- package/dist/mod-decompile-service.js +40 -5
- package/dist/mod-remap-service.js +142 -30
- package/dist/mojang-tiny-mapping-service.js +26 -26
- package/dist/path-resolver.js +41 -4
- package/dist/registry-service.d.ts +10 -1
- package/dist/registry-service.js +154 -22
- package/dist/resources.js +7 -7
- package/dist/search-hit-accumulator.d.ts +0 -3
- package/dist/search-hit-accumulator.js +27 -6
- package/dist/source-jar-reader.js +16 -2
- package/dist/source-resolver.d.ts +1 -0
- package/dist/source-resolver.js +93 -2
- package/dist/source-service.d.ts +76 -47
- package/dist/source-service.js +1344 -763
- package/dist/stdio-supervisor.d.ts +46 -0
- package/dist/stdio-supervisor.js +349 -0
- package/dist/storage/files-repo.d.ts +3 -0
- package/dist/storage/files-repo.js +66 -1
- package/dist/storage/migrations.d.ts +1 -1
- package/dist/storage/migrations.js +6 -2
- package/dist/storage/schema.d.ts +1 -0
- package/dist/storage/schema.js +7 -0
- package/dist/symbols/symbol-extractor.js +6 -4
- package/dist/tool-execution-gate.d.ts +15 -0
- package/dist/tool-execution-gate.js +58 -0
- package/dist/tool-input.d.ts +6 -0
- package/dist/tool-input.js +64 -0
- package/dist/types.d.ts +1 -1
- package/dist/version-diff-service.js +10 -5
- package/dist/version-service.js +7 -2
- package/dist/workspace-mapping-service.js +12 -0
- package/package.json +4 -1
package/dist/registry-service.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync, rmSync } from "node:fs";
|
|
2
2
|
import { mkdir } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
@@ -6,6 +6,40 @@ import { createError, ERROR_CODES } from "./errors.js";
|
|
|
6
6
|
import { log } from "./logger.js";
|
|
7
7
|
const DATAGEN_TIMEOUT_MS = 5 * 60 * 1000;
|
|
8
8
|
const MAX_STDIO_SNAPSHOT = 6_240;
|
|
9
|
+
function clampPositiveInt(value) {
|
|
10
|
+
if (!Number.isFinite(value) || value == null) {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
return Math.max(1, Math.trunc(value));
|
|
14
|
+
}
|
|
15
|
+
function sortedRegistryEntryNames(entries) {
|
|
16
|
+
return Object.keys(entries).sort((left, right) => left.localeCompare(right));
|
|
17
|
+
}
|
|
18
|
+
function limitRegistryEntries(data, maxEntries) {
|
|
19
|
+
const entryNames = sortedRegistryEntryNames(data.entries);
|
|
20
|
+
const total = entryNames.length;
|
|
21
|
+
if (maxEntries == null || total <= maxEntries) {
|
|
22
|
+
return {
|
|
23
|
+
data,
|
|
24
|
+
total,
|
|
25
|
+
returned: total,
|
|
26
|
+
truncated: false
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const limitedEntries = {};
|
|
30
|
+
for (const entryName of entryNames.slice(0, maxEntries)) {
|
|
31
|
+
limitedEntries[entryName] = data.entries[entryName];
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
data: {
|
|
35
|
+
default: data.default,
|
|
36
|
+
entries: limitedEntries
|
|
37
|
+
},
|
|
38
|
+
total,
|
|
39
|
+
returned: maxEntries,
|
|
40
|
+
truncated: true
|
|
41
|
+
};
|
|
42
|
+
}
|
|
9
43
|
function limitOutput(text) {
|
|
10
44
|
if (text.length <= MAX_STDIO_SNAPSHOT)
|
|
11
45
|
return text;
|
|
@@ -25,6 +59,31 @@ function findRegistryFile(registryDir) {
|
|
|
25
59
|
}
|
|
26
60
|
return undefined;
|
|
27
61
|
}
|
|
62
|
+
function parseRegistrySnapshot(raw, version, registryFile) {
|
|
63
|
+
let parsed;
|
|
64
|
+
try {
|
|
65
|
+
parsed = JSON.parse(raw);
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
throw createError({
|
|
69
|
+
code: ERROR_CODES.REGISTRY_GENERATION_FAILED,
|
|
70
|
+
message: `Failed to parse registries.json for version "${version}".`,
|
|
71
|
+
details: {
|
|
72
|
+
version,
|
|
73
|
+
registryFile,
|
|
74
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
79
|
+
throw createError({
|
|
80
|
+
code: ERROR_CODES.REGISTRY_GENERATION_FAILED,
|
|
81
|
+
message: `registries.json for version "${version}" has invalid structure.`,
|
|
82
|
+
details: { version, registryFile }
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return parsed;
|
|
86
|
+
}
|
|
28
87
|
function runDataGen(serverJarPath, outputDir, version) {
|
|
29
88
|
return new Promise((resolve, reject) => {
|
|
30
89
|
// MC 1.18+ uses bundler format, older versions use -cp with main class directly.
|
|
@@ -131,6 +190,12 @@ export class RegistryService {
|
|
|
131
190
|
const warnings = [];
|
|
132
191
|
const allRegistries = await this.loadRegistries(version, warnings);
|
|
133
192
|
const registryNames = Object.keys(allRegistries).sort();
|
|
193
|
+
const includeData = input.includeData ?? true;
|
|
194
|
+
const maxEntriesPerRegistry = clampPositiveInt(input.maxEntriesPerRegistry);
|
|
195
|
+
const registryEntryCounts = Object.fromEntries(registryNames.map((registryName) => [
|
|
196
|
+
registryName,
|
|
197
|
+
Object.keys(allRegistries[registryName].entries).length
|
|
198
|
+
]));
|
|
134
199
|
if (input.registry) {
|
|
135
200
|
const registryName = normalizeRegistryName(input.registry);
|
|
136
201
|
const data = allRegistries[registryName];
|
|
@@ -145,11 +210,17 @@ export class RegistryService {
|
|
|
145
210
|
}
|
|
146
211
|
});
|
|
147
212
|
}
|
|
213
|
+
const limited = limitRegistryEntries(data, maxEntriesPerRegistry);
|
|
148
214
|
return {
|
|
149
215
|
version,
|
|
150
216
|
registry: registryName,
|
|
151
|
-
data,
|
|
152
|
-
entryCount:
|
|
217
|
+
...(includeData ? { data: limited.data } : {}),
|
|
218
|
+
entryCount: limited.total,
|
|
219
|
+
returnedEntryCount: includeData ? limited.returned : 0,
|
|
220
|
+
registryEntryCounts: {
|
|
221
|
+
[registryName]: limited.total
|
|
222
|
+
},
|
|
223
|
+
...(limited.truncated ? { dataTruncated: true } : {}),
|
|
153
224
|
warnings
|
|
154
225
|
};
|
|
155
226
|
}
|
|
@@ -157,11 +228,25 @@ export class RegistryService {
|
|
|
157
228
|
for (const registry of Object.values(allRegistries)) {
|
|
158
229
|
totalEntries += Object.keys(registry.entries).length;
|
|
159
230
|
}
|
|
231
|
+
let returnedEntryCount = 0;
|
|
232
|
+
let dataTruncated = false;
|
|
233
|
+
const limitedData = {};
|
|
234
|
+
if (includeData) {
|
|
235
|
+
for (const registryName of registryNames) {
|
|
236
|
+
const limited = limitRegistryEntries(allRegistries[registryName], maxEntriesPerRegistry);
|
|
237
|
+
limitedData[registryName] = limited.data;
|
|
238
|
+
returnedEntryCount += limited.returned;
|
|
239
|
+
dataTruncated ||= limited.truncated;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
160
242
|
return {
|
|
161
243
|
version,
|
|
162
244
|
registries: registryNames,
|
|
163
|
-
data:
|
|
245
|
+
...(includeData ? { data: limitedData } : {}),
|
|
164
246
|
entryCount: totalEntries,
|
|
247
|
+
returnedEntryCount: includeData ? returnedEntryCount : 0,
|
|
248
|
+
registryEntryCounts,
|
|
249
|
+
...(dataTruncated ? { dataTruncated: true } : {}),
|
|
165
250
|
warnings
|
|
166
251
|
};
|
|
167
252
|
}
|
|
@@ -170,31 +255,79 @@ export class RegistryService {
|
|
|
170
255
|
if (cached)
|
|
171
256
|
return cached;
|
|
172
257
|
const registryDir = join(this.config.cacheDir, "registries", version);
|
|
173
|
-
|
|
174
|
-
|
|
258
|
+
const cachedRegistries = this.loadExistingRegistries(registryDir, version, warnings);
|
|
259
|
+
if (cachedRegistries) {
|
|
260
|
+
this.cacheRegistries(version, cachedRegistries);
|
|
261
|
+
return cachedRegistries;
|
|
262
|
+
}
|
|
263
|
+
await mkdir(registryDir, { recursive: true });
|
|
264
|
+
const serverJar = await this.versionService.resolveServerJar(version);
|
|
265
|
+
await runDataGen(serverJar.jarPath, registryDir, version);
|
|
266
|
+
const registryFile = findRegistryFile(registryDir);
|
|
175
267
|
if (!registryFile) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
268
|
+
throw createError({
|
|
269
|
+
code: ERROR_CODES.REGISTRY_GENERATION_FAILED,
|
|
270
|
+
message: `Registry data generation did not produce registries.json for version "${version}".`,
|
|
271
|
+
details: { version, registryDir }
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
const parsed = this.readRegistryFileOrThrow(registryFile, version);
|
|
275
|
+
this.cacheRegistries(version, parsed);
|
|
276
|
+
return parsed;
|
|
277
|
+
}
|
|
278
|
+
loadExistingRegistries(registryDir, version, warnings) {
|
|
279
|
+
for (const candidate of resolveRegistryPaths(registryDir)) {
|
|
280
|
+
if (!existsSync(candidate)) {
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
try {
|
|
284
|
+
return this.readRegistryFileOrThrow(candidate, version);
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
if (!this.isInvalidRegistrySnapshot(error)) {
|
|
288
|
+
throw error;
|
|
289
|
+
}
|
|
290
|
+
warnings.push(`Ignored corrupt cached registry snapshot and regenerated it: ${candidate}`);
|
|
291
|
+
log("warn", "registry.cache.invalidated", {
|
|
292
|
+
version,
|
|
293
|
+
registryFile: candidate,
|
|
294
|
+
reason: error.message
|
|
185
295
|
});
|
|
296
|
+
try {
|
|
297
|
+
rmSync(candidate, { force: true });
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
// best-effort cleanup
|
|
301
|
+
}
|
|
186
302
|
}
|
|
187
303
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
304
|
+
return undefined;
|
|
305
|
+
}
|
|
306
|
+
readRegistryFileOrThrow(registryFile, version) {
|
|
307
|
+
let raw;
|
|
308
|
+
try {
|
|
309
|
+
raw = readFileSync(registryFile, "utf8");
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
192
312
|
throw createError({
|
|
193
313
|
code: ERROR_CODES.REGISTRY_GENERATION_FAILED,
|
|
194
|
-
message: `registries.json for version "${version}"
|
|
195
|
-
details: {
|
|
314
|
+
message: `Failed to read registries.json for version "${version}".`,
|
|
315
|
+
details: {
|
|
316
|
+
version,
|
|
317
|
+
registryFile,
|
|
318
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
319
|
+
}
|
|
196
320
|
});
|
|
197
321
|
}
|
|
322
|
+
return parseRegistrySnapshot(raw, version, registryFile);
|
|
323
|
+
}
|
|
324
|
+
isInvalidRegistrySnapshot(error) {
|
|
325
|
+
return (typeof error === "object" &&
|
|
326
|
+
error !== null &&
|
|
327
|
+
"code" in error &&
|
|
328
|
+
error.code === ERROR_CODES.REGISTRY_GENERATION_FAILED);
|
|
329
|
+
}
|
|
330
|
+
cacheRegistries(version, parsed) {
|
|
198
331
|
this.registryCache.set(version, parsed);
|
|
199
332
|
// Trim cache to avoid unbounded growth
|
|
200
333
|
if (this.registryCache.size > 8) {
|
|
@@ -202,7 +335,6 @@ export class RegistryService {
|
|
|
202
335
|
if (oldest !== undefined)
|
|
203
336
|
this.registryCache.delete(oldest);
|
|
204
337
|
}
|
|
205
|
-
return parsed;
|
|
206
338
|
}
|
|
207
339
|
}
|
|
208
340
|
function normalizeRegistryName(name) {
|
package/dist/resources.js
CHANGED
|
@@ -30,7 +30,7 @@ export function registerResources(server, sourceService) {
|
|
|
30
30
|
}
|
|
31
31
|
catch (e) {
|
|
32
32
|
if (isAppError(e))
|
|
33
|
-
return errorResource(uri.href, e.message);
|
|
33
|
+
return errorResource(uri.href, { message: e.message, code: e.code });
|
|
34
34
|
throw e;
|
|
35
35
|
}
|
|
36
36
|
});
|
|
@@ -41,7 +41,7 @@ export function registerResources(server, sourceService) {
|
|
|
41
41
|
}
|
|
42
42
|
catch (e) {
|
|
43
43
|
if (isAppError(e))
|
|
44
|
-
return errorResource(uri.href, e.message);
|
|
44
|
+
return errorResource(uri.href, { message: e.message, code: e.code });
|
|
45
45
|
throw e;
|
|
46
46
|
}
|
|
47
47
|
});
|
|
@@ -56,7 +56,7 @@ export function registerResources(server, sourceService) {
|
|
|
56
56
|
}
|
|
57
57
|
catch (e) {
|
|
58
58
|
if (isAppError(e))
|
|
59
|
-
return errorResource(uri.href, e.message);
|
|
59
|
+
return errorResource(uri.href, { message: e.message, code: e.code });
|
|
60
60
|
throw e;
|
|
61
61
|
}
|
|
62
62
|
});
|
|
@@ -70,7 +70,7 @@ export function registerResources(server, sourceService) {
|
|
|
70
70
|
}
|
|
71
71
|
catch (e) {
|
|
72
72
|
if (isAppError(e))
|
|
73
|
-
return errorResource(uri.href, e.message);
|
|
73
|
+
return errorResource(uri.href, { message: e.message, code: e.code });
|
|
74
74
|
throw e;
|
|
75
75
|
}
|
|
76
76
|
});
|
|
@@ -87,7 +87,7 @@ export function registerResources(server, sourceService) {
|
|
|
87
87
|
}
|
|
88
88
|
catch (e) {
|
|
89
89
|
if (isAppError(e))
|
|
90
|
-
return errorResource(uri.href, e.message);
|
|
90
|
+
return errorResource(uri.href, { message: e.message, code: e.code });
|
|
91
91
|
throw e;
|
|
92
92
|
}
|
|
93
93
|
});
|
|
@@ -101,7 +101,7 @@ export function registerResources(server, sourceService) {
|
|
|
101
101
|
}
|
|
102
102
|
catch (e) {
|
|
103
103
|
if (isAppError(e))
|
|
104
|
-
return errorResource(uri.href, e.message);
|
|
104
|
+
return errorResource(uri.href, { message: e.message, code: e.code });
|
|
105
105
|
throw e;
|
|
106
106
|
}
|
|
107
107
|
});
|
|
@@ -112,7 +112,7 @@ export function registerResources(server, sourceService) {
|
|
|
112
112
|
}
|
|
113
113
|
catch (e) {
|
|
114
114
|
if (isAppError(e))
|
|
115
|
-
return errorResource(uri.href, e.message);
|
|
115
|
+
return errorResource(uri.href, { message: e.message, code: e.code });
|
|
116
116
|
throw e;
|
|
117
117
|
}
|
|
118
118
|
});
|
|
@@ -12,8 +12,8 @@ export function scoreHitOrder(left, right) {
|
|
|
12
12
|
if (symbolCompare !== 0) {
|
|
13
13
|
return symbolCompare;
|
|
14
14
|
}
|
|
15
|
-
const leftLine = left.symbol?.line ??
|
|
16
|
-
const rightLine = right.symbol?.line ??
|
|
15
|
+
const leftLine = left.symbol?.line ?? 0;
|
|
16
|
+
const rightLine = right.symbol?.line ?? 0;
|
|
17
17
|
return leftLine - rightLine;
|
|
18
18
|
}
|
|
19
19
|
export function encodeSearchCursor(hit, contextKey) {
|
|
@@ -21,7 +21,7 @@ export function encodeSearchCursor(hit, contextKey) {
|
|
|
21
21
|
score: hit.score,
|
|
22
22
|
filePath: hit.filePath,
|
|
23
23
|
symbolName: hit.symbol?.symbolName ?? "",
|
|
24
|
-
line: hit.symbol?.line ??
|
|
24
|
+
line: hit.symbol?.line ?? 0,
|
|
25
25
|
contextKey
|
|
26
26
|
}), "utf8").toString("base64");
|
|
27
27
|
}
|
|
@@ -66,7 +66,7 @@ export function isAfterSearchCursor(hit, cursor) {
|
|
|
66
66
|
if (symbolCompare < 0) {
|
|
67
67
|
return false;
|
|
68
68
|
}
|
|
69
|
-
const hitLine = hit.symbol?.line ??
|
|
69
|
+
const hitLine = hit.symbol?.line ?? 0;
|
|
70
70
|
return hitLine > cursor.line;
|
|
71
71
|
}
|
|
72
72
|
/**
|
|
@@ -107,6 +107,21 @@ function heapSiftUp(heap, index) {
|
|
|
107
107
|
index = parent;
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
|
+
function heapPopWorst(heap) {
|
|
111
|
+
if (heap.length === 0) {
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
const root = heap[0];
|
|
115
|
+
const tail = heap.pop();
|
|
116
|
+
if (heap.length === 0) {
|
|
117
|
+
return root;
|
|
118
|
+
}
|
|
119
|
+
if (tail) {
|
|
120
|
+
heap[0] = tail;
|
|
121
|
+
heapSiftDown(heap, 0, heap.length);
|
|
122
|
+
}
|
|
123
|
+
return root;
|
|
124
|
+
}
|
|
110
125
|
export function createSearchHitAccumulator(limit, cursor) {
|
|
111
126
|
const pageLimit = Math.max(1, limit);
|
|
112
127
|
const keepLimit = pageLimit + 1;
|
|
@@ -141,8 +156,14 @@ export function createSearchHitAccumulator(limit, cursor) {
|
|
|
141
156
|
return heap.length;
|
|
142
157
|
},
|
|
143
158
|
finalize() {
|
|
144
|
-
|
|
145
|
-
const
|
|
159
|
+
const sorted = new Array(heap.length);
|
|
160
|
+
const heapCopy = heap.slice();
|
|
161
|
+
for (let index = heapCopy.length - 1; index >= 0; index -= 1) {
|
|
162
|
+
const next = heapPopWorst(heapCopy);
|
|
163
|
+
if (next) {
|
|
164
|
+
sorted[index] = next;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
146
167
|
const page = sorted.slice(0, pageLimit);
|
|
147
168
|
const hasMore = totalAfterCursor > page.length;
|
|
148
169
|
return {
|
|
@@ -9,6 +9,20 @@ function toErrorMessage(value) {
|
|
|
9
9
|
}
|
|
10
10
|
return String(value);
|
|
11
11
|
}
|
|
12
|
+
function hasJavaSourceExtension(entryPath) {
|
|
13
|
+
const suffix = ".java";
|
|
14
|
+
if (entryPath.length < suffix.length) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
for (let index = 0; index < suffix.length; index += 1) {
|
|
18
|
+
const charCode = entryPath.charCodeAt(entryPath.length - suffix.length + index);
|
|
19
|
+
const normalizedCharCode = charCode >= 65 && charCode <= 90 ? charCode + 32 : charCode;
|
|
20
|
+
if (normalizedCharCode !== suffix.charCodeAt(index)) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
12
26
|
function openZipFile(jarPath) {
|
|
13
27
|
return new Promise((resolve, reject) => {
|
|
14
28
|
yauzl.open(jarPath, {
|
|
@@ -128,7 +142,7 @@ export async function listJarEntries(jarPath) {
|
|
|
128
142
|
}
|
|
129
143
|
export async function listJavaEntries(jarPath) {
|
|
130
144
|
const entries = await listJarEntries(jarPath);
|
|
131
|
-
return entries.filter((entry) => entry
|
|
145
|
+
return entries.filter((entry) => hasJavaSourceExtension(entry) && isSecureJarEntryPath(entry));
|
|
132
146
|
}
|
|
133
147
|
export async function readJarEntryAsUtf8(jarPath, entryPath) {
|
|
134
148
|
const contentBuffer = await readJarEntryAsBuffer(jarPath, entryPath);
|
|
@@ -171,7 +185,7 @@ export async function* iterateJavaEntriesAsUtf8(jarPath, maxBytes) {
|
|
|
171
185
|
if (!entry) {
|
|
172
186
|
break;
|
|
173
187
|
}
|
|
174
|
-
if (!entry.fileName
|
|
188
|
+
if (!hasJavaSourceExtension(entry.fileName)) {
|
|
175
189
|
continue;
|
|
176
190
|
}
|
|
177
191
|
if (!isSecureJarEntryPath(entry.fileName)) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Config, ResolvedSourceArtifact, SourceTargetInput } from "./types.js";
|
|
2
2
|
export interface ResolveSourceTargetOptions {
|
|
3
3
|
allowDecompile: boolean;
|
|
4
|
+
preferBinaryOnly?: boolean;
|
|
4
5
|
preferredRepos?: string[];
|
|
5
6
|
onRepoFailover?: (event: {
|
|
6
7
|
stage: "source" | "binary";
|
package/dist/source-resolver.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { readdirSync } from "node:fs";
|
|
2
2
|
import { basename, dirname, join, resolve as resolvePath } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import fastGlob from "fast-glob";
|
|
3
5
|
import { createError, ERROR_CODES } from "./errors.js";
|
|
4
6
|
import { buildRemoteBinaryUrls, buildRemoteSourceUrls, hasExistingJar, parseCoordinate, normalizedCoordinateValue } from "./maven-resolver.js";
|
|
5
7
|
import { defaultDownloadPath, downloadToCache } from "./repo-downloader.js";
|
|
@@ -23,6 +25,16 @@ function resolveExactJarSourceCandidate(inputJarPath) {
|
|
|
23
25
|
const base = jarName.endsWith(".jar") ? jarName.slice(0, -4) : jarName;
|
|
24
26
|
return join(directory, `${base}-sources.jar`);
|
|
25
27
|
}
|
|
28
|
+
function resolveSiblingBinaryJarCandidate(inputJarPath) {
|
|
29
|
+
const directory = dirname(inputJarPath);
|
|
30
|
+
const jarName = basename(inputJarPath);
|
|
31
|
+
if (!jarName.endsWith("-sources.jar")) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
const binaryName = `${jarName.slice(0, -"-sources.jar".length)}.jar`;
|
|
35
|
+
const candidate = join(directory, binaryName);
|
|
36
|
+
return hasExistingJar(candidate) ? candidate : undefined;
|
|
37
|
+
}
|
|
26
38
|
function listAdjacentJarSourceCandidates(inputJarPath) {
|
|
27
39
|
const directory = dirname(inputJarPath);
|
|
28
40
|
const exact = resolveExactJarSourceCandidate(inputJarPath);
|
|
@@ -59,6 +71,66 @@ function resolveLocalCoordinateCandidates(localM2Path, coordinate) {
|
|
|
59
71
|
}
|
|
60
72
|
return [...existing];
|
|
61
73
|
}
|
|
74
|
+
function resolveLocalCoordinateBinaryCandidate(localM2Path, coordinate) {
|
|
75
|
+
const parsed = parseCoordinate(coordinate);
|
|
76
|
+
const groupPath = parsed.groupId.replace(/\./g, "/");
|
|
77
|
+
const baseDir = resolvePath(localM2Path, groupPath, parsed.artifactId, parsed.version);
|
|
78
|
+
const base = `${parsed.artifactId}-${parsed.version}`;
|
|
79
|
+
const classifierSuffix = parsed.classifier ? `-${parsed.classifier}` : "";
|
|
80
|
+
const candidates = [
|
|
81
|
+
resolvePath(baseDir, `${base}${classifierSuffix}.jar`),
|
|
82
|
+
...(classifierSuffix ? [resolvePath(baseDir, `${base}.jar`)] : [])
|
|
83
|
+
];
|
|
84
|
+
return candidates.find((candidate) => hasExistingJar(candidate));
|
|
85
|
+
}
|
|
86
|
+
function resolveGradleUserHome() {
|
|
87
|
+
const configured = process.env.GRADLE_USER_HOME?.trim();
|
|
88
|
+
if (configured) {
|
|
89
|
+
return configured;
|
|
90
|
+
}
|
|
91
|
+
return resolvePath(homedir(), ".gradle");
|
|
92
|
+
}
|
|
93
|
+
function resolveGradleCacheCoordinateCandidate(coordinate) {
|
|
94
|
+
const parsed = parseCoordinate(coordinate);
|
|
95
|
+
const baseDir = resolvePath(resolveGradleUserHome(), "caches", "modules-2", "files-2.1", parsed.groupId, parsed.artifactId, parsed.version);
|
|
96
|
+
const base = `${parsed.artifactId}-${parsed.version}`;
|
|
97
|
+
const classifierSuffix = parsed.classifier ? `-${parsed.classifier}` : "";
|
|
98
|
+
const preferredSourceNames = [
|
|
99
|
+
`${base}${classifierSuffix}-sources.jar`,
|
|
100
|
+
...(classifierSuffix ? [`${base}-sources.jar`] : [])
|
|
101
|
+
];
|
|
102
|
+
const preferredBinaryNames = [
|
|
103
|
+
`${base}${classifierSuffix}.jar`,
|
|
104
|
+
...(classifierSuffix ? [`${base}.jar`] : [])
|
|
105
|
+
];
|
|
106
|
+
let discoveredFiles = [];
|
|
107
|
+
try {
|
|
108
|
+
discoveredFiles = fastGlob.sync("*/*", {
|
|
109
|
+
cwd: baseDir,
|
|
110
|
+
absolute: true,
|
|
111
|
+
onlyFiles: true
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
discoveredFiles = discoveredFiles.filter((entry) => hasExistingJar(entry)).sort((left, right) => left.localeCompare(right));
|
|
118
|
+
const pickFirst = (candidates) => {
|
|
119
|
+
for (const fileName of candidates) {
|
|
120
|
+
const match = discoveredFiles.find((entry) => basename(entry) === fileName);
|
|
121
|
+
if (match) {
|
|
122
|
+
return match;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return undefined;
|
|
126
|
+
};
|
|
127
|
+
const sourceJarPath = pickFirst(preferredSourceNames);
|
|
128
|
+
const binaryJarPath = pickFirst(preferredBinaryNames);
|
|
129
|
+
if (!sourceJarPath && !binaryJarPath) {
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
return { sourceJarPath, binaryJarPath };
|
|
133
|
+
}
|
|
62
134
|
function resolveRemoteBinaryCandidate(coordinate, repos) {
|
|
63
135
|
return buildRemoteBinaryUrls(repos, coordinate);
|
|
64
136
|
}
|
|
@@ -80,19 +152,23 @@ export async function resolveSourceTarget(input, options, explicitConfig) {
|
|
|
80
152
|
const exactSourceJarPath = resolveExactJarSourceCandidate(resolvedJarPath);
|
|
81
153
|
const adjacentSourceCandidates = listAdjacentJarSourceCandidates(resolvedJarPath);
|
|
82
154
|
const maybeAdjacentSourceCandidates = adjacentSourceCandidates.length > 0 ? adjacentSourceCandidates : undefined;
|
|
155
|
+
const preferBinaryOnly = options.preferBinaryOnly ?? false;
|
|
83
156
|
if (await hasJavaSources(resolvedJarPath)) {
|
|
157
|
+
const siblingBinaryJarPath = resolveSiblingBinaryJarCandidate(resolvedJarPath);
|
|
158
|
+
const binaryJarPath = siblingBinaryJarPath ??
|
|
159
|
+
(basename(resolvedJarPath).endsWith("-sources.jar") ? undefined : resolvedJarPath);
|
|
84
160
|
return {
|
|
85
161
|
artifactId: artifactIdForJar("jar", resolvedJarPath, binarySignature),
|
|
86
162
|
artifactSignature: binarySignature,
|
|
87
163
|
origin: "local-jar",
|
|
88
|
-
binaryJarPath
|
|
164
|
+
binaryJarPath,
|
|
89
165
|
sourceJarPath: resolvedJarPath,
|
|
90
166
|
adjacentSourceCandidates: maybeAdjacentSourceCandidates,
|
|
91
167
|
isDecompiled: false,
|
|
92
168
|
resolvedAt: resolvedAtNow()
|
|
93
169
|
};
|
|
94
170
|
}
|
|
95
|
-
if (await hasJavaSources(exactSourceJarPath)) {
|
|
171
|
+
if (!preferBinaryOnly && await hasJavaSources(exactSourceJarPath)) {
|
|
96
172
|
const sourceSignature = readStatsSignature(exactSourceJarPath);
|
|
97
173
|
return {
|
|
98
174
|
artifactId: artifactIdForJar("jar", exactSourceJarPath, sourceSignature),
|
|
@@ -142,12 +218,27 @@ export async function resolveSourceTarget(input, options, explicitConfig) {
|
|
|
142
218
|
artifactSignature: signature,
|
|
143
219
|
origin: "local-m2",
|
|
144
220
|
sourceJarPath: candidate,
|
|
221
|
+
binaryJarPath: resolveLocalCoordinateBinaryCandidate(explicitConfig.localM2Path, coordinate),
|
|
145
222
|
coordinate,
|
|
146
223
|
isDecompiled: false,
|
|
147
224
|
resolvedAt: resolvedAtNow()
|
|
148
225
|
};
|
|
149
226
|
}
|
|
150
227
|
}
|
|
228
|
+
const gradleCacheCandidate = resolveGradleCacheCoordinateCandidate(coordinate);
|
|
229
|
+
if (gradleCacheCandidate?.sourceJarPath && (await hasJavaSources(gradleCacheCandidate.sourceJarPath))) {
|
|
230
|
+
const signature = readStatsSignature(gradleCacheCandidate.sourceJarPath);
|
|
231
|
+
return {
|
|
232
|
+
artifactId: artifactIdForCoordinate(coordinate, "local-m2", signature),
|
|
233
|
+
artifactSignature: signature,
|
|
234
|
+
origin: "local-m2",
|
|
235
|
+
sourceJarPath: gradleCacheCandidate.sourceJarPath,
|
|
236
|
+
binaryJarPath: gradleCacheCandidate.binaryJarPath,
|
|
237
|
+
coordinate,
|
|
238
|
+
isDecompiled: false,
|
|
239
|
+
resolvedAt: resolvedAtNow()
|
|
240
|
+
};
|
|
241
|
+
}
|
|
151
242
|
const remoteSourceUrls = buildRemoteSourceUrls(repos, coordinate);
|
|
152
243
|
for (let index = 0; index < remoteSourceUrls.length; index++) {
|
|
153
244
|
const sourceUrl = remoteSourceUrls[index];
|