@adhisang/minecraft-modding-mcp 1.0.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 +11 -0
- package/LICENSE +21 -0
- package/README.md +765 -0
- package/dist/access-widener-parser.d.ts +24 -0
- package/dist/access-widener-parser.js +77 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +4 -0
- package/dist/config.d.ts +27 -0
- package/dist/config.js +178 -0
- package/dist/decompiler/vineflower.d.ts +15 -0
- package/dist/decompiler/vineflower.js +185 -0
- package/dist/errors.d.ts +50 -0
- package/dist/errors.js +49 -0
- package/dist/hash.d.ts +1 -0
- package/dist/hash.js +12 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +1447 -0
- package/dist/java-process.d.ts +16 -0
- package/dist/java-process.js +120 -0
- package/dist/logger.d.ts +3 -0
- package/dist/logger.js +21 -0
- package/dist/mapping-pipeline-service.d.ts +18 -0
- package/dist/mapping-pipeline-service.js +60 -0
- package/dist/mapping-service.d.ts +161 -0
- package/dist/mapping-service.js +1706 -0
- package/dist/maven-resolver.d.ts +22 -0
- package/dist/maven-resolver.js +122 -0
- package/dist/minecraft-explorer-service.d.ts +43 -0
- package/dist/minecraft-explorer-service.js +562 -0
- package/dist/mixin-parser.d.ts +34 -0
- package/dist/mixin-parser.js +194 -0
- package/dist/mixin-validator.d.ts +59 -0
- package/dist/mixin-validator.js +274 -0
- package/dist/mod-analyzer.d.ts +23 -0
- package/dist/mod-analyzer.js +346 -0
- package/dist/mod-decompile-service.d.ts +39 -0
- package/dist/mod-decompile-service.js +136 -0
- package/dist/mod-remap-service.d.ts +17 -0
- package/dist/mod-remap-service.js +186 -0
- package/dist/mod-search-service.d.ts +28 -0
- package/dist/mod-search-service.js +174 -0
- package/dist/mojang-tiny-mapping-service.d.ts +13 -0
- package/dist/mojang-tiny-mapping-service.js +351 -0
- package/dist/nbt/java-nbt-codec.d.ts +3 -0
- package/dist/nbt/java-nbt-codec.js +385 -0
- package/dist/nbt/json-patch.d.ts +3 -0
- package/dist/nbt/json-patch.js +352 -0
- package/dist/nbt/pipeline.d.ts +39 -0
- package/dist/nbt/pipeline.js +173 -0
- package/dist/nbt/typed-json.d.ts +10 -0
- package/dist/nbt/typed-json.js +205 -0
- package/dist/nbt/types.d.ts +66 -0
- package/dist/nbt/types.js +2 -0
- package/dist/observability.d.ts +88 -0
- package/dist/observability.js +165 -0
- package/dist/path-converter.d.ts +12 -0
- package/dist/path-converter.js +161 -0
- package/dist/path-resolver.d.ts +19 -0
- package/dist/path-resolver.js +78 -0
- package/dist/registry-service.d.ts +29 -0
- package/dist/registry-service.js +214 -0
- package/dist/repo-downloader.d.ts +15 -0
- package/dist/repo-downloader.js +111 -0
- package/dist/resources.d.ts +3 -0
- package/dist/resources.js +154 -0
- package/dist/search-hit-accumulator.d.ts +38 -0
- package/dist/search-hit-accumulator.js +153 -0
- package/dist/source-jar-reader.d.ts +13 -0
- package/dist/source-jar-reader.js +216 -0
- package/dist/source-resolver.d.ts +14 -0
- package/dist/source-resolver.js +274 -0
- package/dist/source-service.d.ts +404 -0
- package/dist/source-service.js +2881 -0
- package/dist/storage/artifacts-repo.d.ts +45 -0
- package/dist/storage/artifacts-repo.js +209 -0
- package/dist/storage/db.d.ts +14 -0
- package/dist/storage/db.js +132 -0
- package/dist/storage/files-repo.d.ts +78 -0
- package/dist/storage/files-repo.js +437 -0
- package/dist/storage/index-meta-repo.d.ts +35 -0
- package/dist/storage/index-meta-repo.js +97 -0
- package/dist/storage/migrations.d.ts +11 -0
- package/dist/storage/migrations.js +71 -0
- package/dist/storage/schema.d.ts +1 -0
- package/dist/storage/schema.js +160 -0
- package/dist/storage/sqlite.d.ts +20 -0
- package/dist/storage/sqlite.js +111 -0
- package/dist/storage/symbols-repo.d.ts +63 -0
- package/dist/storage/symbols-repo.js +401 -0
- package/dist/symbols/symbol-extractor.d.ts +7 -0
- package/dist/symbols/symbol-extractor.js +64 -0
- package/dist/tiny-remapper-resolver.d.ts +1 -0
- package/dist/tiny-remapper-resolver.js +62 -0
- package/dist/tiny-remapper-service.d.ts +16 -0
- package/dist/tiny-remapper-service.js +73 -0
- package/dist/types.d.ts +120 -0
- package/dist/types.js +2 -0
- package/dist/version-diff-service.d.ts +41 -0
- package/dist/version-diff-service.js +222 -0
- package/dist/version-service.d.ts +70 -0
- package/dist/version-service.js +411 -0
- package/dist/vineflower-resolver.d.ts +1 -0
- package/dist/vineflower-resolver.js +62 -0
- package/dist/workspace-mapping-service.d.ts +18 -0
- package/dist/workspace-mapping-service.js +89 -0
- package/package.json +61 -0
|
@@ -0,0 +1,1706 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import fastGlob from "fast-glob";
|
|
5
|
+
import { createError, ERROR_CODES } from "./errors.js";
|
|
6
|
+
import { defaultDownloadPath, downloadToCache } from "./repo-downloader.js";
|
|
7
|
+
import { listJarEntries, readJarEntryAsUtf8 } from "./source-jar-reader.js";
|
|
8
|
+
import { VersionService, isUnobfuscatedVersion } from "./version-service.js";
|
|
9
|
+
const SUPPORTED_MAPPINGS = new Set([
|
|
10
|
+
"official",
|
|
11
|
+
"mojang",
|
|
12
|
+
"intermediary",
|
|
13
|
+
"yarn"
|
|
14
|
+
]);
|
|
15
|
+
const MATCH_RANK = {
|
|
16
|
+
exact: 3,
|
|
17
|
+
normalized: 2,
|
|
18
|
+
"simple-name": 1
|
|
19
|
+
};
|
|
20
|
+
const DESCRIPTOR_FALLBACK_CONFIDENCE = 0.85;
|
|
21
|
+
const MAX_CANDIDATES = 200;
|
|
22
|
+
function createDirectionIndex() {
|
|
23
|
+
return {
|
|
24
|
+
exact: new Map(),
|
|
25
|
+
normalized: new Map(),
|
|
26
|
+
simple: new Map(),
|
|
27
|
+
records: new Map()
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function addToSetMap(map, key, value) {
|
|
31
|
+
const normalizedKey = key.trim();
|
|
32
|
+
if (!normalizedKey) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const existing = map.get(normalizedKey) ?? new Set();
|
|
36
|
+
existing.add(value);
|
|
37
|
+
map.set(normalizedKey, existing);
|
|
38
|
+
}
|
|
39
|
+
function normalizedVariants(symbol) {
|
|
40
|
+
const variants = new Set();
|
|
41
|
+
variants.add(symbol);
|
|
42
|
+
const dotted = symbol.replace(/\//g, ".");
|
|
43
|
+
variants.add(dotted);
|
|
44
|
+
const slashed = symbol.replace(/\./g, "/");
|
|
45
|
+
variants.add(slashed);
|
|
46
|
+
return [...variants];
|
|
47
|
+
}
|
|
48
|
+
function simpleName(symbol) {
|
|
49
|
+
const trimmed = symbol.trim();
|
|
50
|
+
if (!trimmed) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
const withoutDescriptor = trimmed.includes("(") ? trimmed.slice(0, trimmed.indexOf("(")) : trimmed;
|
|
54
|
+
const base = withoutDescriptor.split(/[./]/).at(-1)?.trim();
|
|
55
|
+
return base || undefined;
|
|
56
|
+
}
|
|
57
|
+
function normalizeMappedSymbolOutput(symbol) {
|
|
58
|
+
return symbol.replace(/\//g, ".");
|
|
59
|
+
}
|
|
60
|
+
function splitOwnerAndName(symbol) {
|
|
61
|
+
const trimmed = symbol.trim();
|
|
62
|
+
const separatorIndex = Math.max(trimmed.lastIndexOf("."), trimmed.lastIndexOf("/"));
|
|
63
|
+
if (separatorIndex <= 0 || separatorIndex >= trimmed.length - 1) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
owner: trimmed.slice(0, separatorIndex),
|
|
68
|
+
name: trimmed.slice(separatorIndex + 1)
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function stripLineInfo(input) {
|
|
72
|
+
let value = input.trim();
|
|
73
|
+
while (/^\d+:\d+:/.test(value)) {
|
|
74
|
+
value = value.replace(/^\d+:\d+:/, "");
|
|
75
|
+
}
|
|
76
|
+
return value.replace(/:\d+:\d+$/, "").trim();
|
|
77
|
+
}
|
|
78
|
+
function parseMethodName(value) {
|
|
79
|
+
const match = /^(.+?)\s+([^\s(]+)\((.*)\)$/.exec(value);
|
|
80
|
+
if (!match) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
return match[2]?.trim() || undefined;
|
|
84
|
+
}
|
|
85
|
+
function parseFieldName(value) {
|
|
86
|
+
const match = /^(.+?)\s+([^\s]+)$/.exec(value);
|
|
87
|
+
if (!match) {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
return match[2]?.trim() || undefined;
|
|
91
|
+
}
|
|
92
|
+
function buildSymbolKey(record) {
|
|
93
|
+
return `${record.kind}|${record.owner ?? ""}|${record.name}|${record.descriptor ?? ""}`;
|
|
94
|
+
}
|
|
95
|
+
function classNameParts(classFqn) {
|
|
96
|
+
const separatorIndex = classFqn.lastIndexOf(".");
|
|
97
|
+
if (separatorIndex <= 0 || separatorIndex >= classFqn.length - 1) {
|
|
98
|
+
return {
|
|
99
|
+
owner: undefined,
|
|
100
|
+
name: classFqn
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
owner: classFqn.slice(0, separatorIndex),
|
|
105
|
+
name: classFqn.slice(separatorIndex + 1)
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function createClassSymbolRecord(className) {
|
|
109
|
+
const symbol = normalizeMappedSymbolOutput(className.trim());
|
|
110
|
+
const parts = classNameParts(symbol);
|
|
111
|
+
return {
|
|
112
|
+
kind: "class",
|
|
113
|
+
symbol,
|
|
114
|
+
owner: parts.owner,
|
|
115
|
+
name: parts.name
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function createFieldSymbolRecord(owner, fieldName) {
|
|
119
|
+
const normalizedOwner = normalizeMappedSymbolOutput(owner.trim());
|
|
120
|
+
const normalizedName = fieldName.trim();
|
|
121
|
+
return {
|
|
122
|
+
kind: "field",
|
|
123
|
+
symbol: `${normalizedOwner}.${normalizedName}`,
|
|
124
|
+
owner: normalizedOwner,
|
|
125
|
+
name: normalizedName
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function createMethodSymbolRecord(owner, methodName, descriptor) {
|
|
129
|
+
const normalizedOwner = normalizeMappedSymbolOutput(owner.trim());
|
|
130
|
+
const normalizedName = methodName.trim();
|
|
131
|
+
const normalizedDescriptor = descriptor?.trim() || undefined;
|
|
132
|
+
return {
|
|
133
|
+
kind: "method",
|
|
134
|
+
symbol: `${normalizedOwner}.${normalizedName}${normalizedDescriptor ?? ""}`,
|
|
135
|
+
owner: normalizedOwner,
|
|
136
|
+
name: normalizedName,
|
|
137
|
+
descriptor: normalizedDescriptor
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function parseInputSymbol(symbol) {
|
|
141
|
+
const trimmed = symbol.trim();
|
|
142
|
+
if (!trimmed || /\s/.test(trimmed)) {
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
const openIndex = trimmed.indexOf("(");
|
|
146
|
+
if (openIndex >= 0) {
|
|
147
|
+
const closeIndex = trimmed.indexOf(")", openIndex);
|
|
148
|
+
if (closeIndex < 0) {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
const ownerAndMethod = splitOwnerAndName(trimmed.slice(0, openIndex));
|
|
152
|
+
if (!ownerAndMethod) {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
const descriptor = trimmed.slice(openIndex);
|
|
156
|
+
return createMethodSymbolRecord(ownerAndMethod.owner, ownerAndMethod.name, descriptor);
|
|
157
|
+
}
|
|
158
|
+
const ownerAndName = splitOwnerAndName(trimmed);
|
|
159
|
+
if (!ownerAndName) {
|
|
160
|
+
return createClassSymbolRecord(trimmed);
|
|
161
|
+
}
|
|
162
|
+
if (/^[A-Z$]/.test(ownerAndName.name)) {
|
|
163
|
+
return createClassSymbolRecord(trimmed);
|
|
164
|
+
}
|
|
165
|
+
return createFieldSymbolRecord(ownerAndName.owner, ownerAndName.name);
|
|
166
|
+
}
|
|
167
|
+
function exactLookupKeys(record) {
|
|
168
|
+
const keys = new Set([record.symbol]);
|
|
169
|
+
if (record.kind === "method" && record.owner && record.descriptor) {
|
|
170
|
+
keys.add(`${record.owner}.${record.name}`);
|
|
171
|
+
}
|
|
172
|
+
return [...keys];
|
|
173
|
+
}
|
|
174
|
+
function simpleLookupKeys(record) {
|
|
175
|
+
if (record.kind === "class") {
|
|
176
|
+
return [record.name];
|
|
177
|
+
}
|
|
178
|
+
if (record.kind === "field") {
|
|
179
|
+
return [record.name];
|
|
180
|
+
}
|
|
181
|
+
if (record.descriptor) {
|
|
182
|
+
return [record.name, `${record.name}${record.descriptor}`];
|
|
183
|
+
}
|
|
184
|
+
return [record.name];
|
|
185
|
+
}
|
|
186
|
+
function registerRecord(index, record) {
|
|
187
|
+
const key = buildSymbolKey(record);
|
|
188
|
+
if (!index.records.has(key)) {
|
|
189
|
+
index.records.set(key, record);
|
|
190
|
+
}
|
|
191
|
+
return key;
|
|
192
|
+
}
|
|
193
|
+
function addLookupEntries(index, fromRecord, toRecord) {
|
|
194
|
+
if (!fromRecord.symbol || !toRecord.symbol) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const targetKey = registerRecord(index, toRecord);
|
|
198
|
+
for (const key of exactLookupKeys(fromRecord)) {
|
|
199
|
+
addToSetMap(index.exact, key, targetKey);
|
|
200
|
+
for (const variant of normalizedVariants(key)) {
|
|
201
|
+
if (variant !== key) {
|
|
202
|
+
addToSetMap(index.normalized, variant, targetKey);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
for (const key of simpleLookupKeys(fromRecord)) {
|
|
207
|
+
addToSetMap(index.simple, key, targetKey);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function mergeDirectionIndexes(target, source) {
|
|
211
|
+
const mergeMap = (targetMap, sourceMap) => {
|
|
212
|
+
for (const [key, values] of sourceMap.entries()) {
|
|
213
|
+
const existing = targetMap.get(key) ?? new Set();
|
|
214
|
+
for (const value of values) {
|
|
215
|
+
existing.add(value);
|
|
216
|
+
}
|
|
217
|
+
targetMap.set(key, existing);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
mergeMap(target.exact, source.exact);
|
|
221
|
+
mergeMap(target.normalized, source.normalized);
|
|
222
|
+
mergeMap(target.simple, source.simple);
|
|
223
|
+
for (const [key, value] of source.records.entries()) {
|
|
224
|
+
if (!target.records.has(key)) {
|
|
225
|
+
target.records.set(key, value);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function pairKey(sourceMapping, targetMapping) {
|
|
230
|
+
return `${sourceMapping}->${targetMapping}`;
|
|
231
|
+
}
|
|
232
|
+
function parsePairKey(key) {
|
|
233
|
+
const [source, target] = key.split("->");
|
|
234
|
+
return {
|
|
235
|
+
sourceMapping: source,
|
|
236
|
+
targetMapping: target
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function ensurePairIndex(indexes, from, to) {
|
|
240
|
+
const key = pairKey(from, to);
|
|
241
|
+
const existing = indexes.get(key);
|
|
242
|
+
if (existing) {
|
|
243
|
+
return existing;
|
|
244
|
+
}
|
|
245
|
+
const created = createDirectionIndex();
|
|
246
|
+
indexes.set(key, created);
|
|
247
|
+
return created;
|
|
248
|
+
}
|
|
249
|
+
function parseClientMappings(text) {
|
|
250
|
+
const officialToMojang = createDirectionIndex();
|
|
251
|
+
const mojangToOfficial = createDirectionIndex();
|
|
252
|
+
let classCount = 0;
|
|
253
|
+
let currentClass;
|
|
254
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
255
|
+
const line = rawLine.trim();
|
|
256
|
+
if (!line || line.startsWith("#")) {
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
const classMatch = /^(.+?)\s+->\s+(.+):$/.exec(line);
|
|
260
|
+
if (classMatch) {
|
|
261
|
+
const mojangClass = classMatch[1]?.trim() ?? "";
|
|
262
|
+
const officialClass = classMatch[2]?.trim() ?? "";
|
|
263
|
+
if (!mojangClass || !officialClass) {
|
|
264
|
+
currentClass = undefined;
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
classCount += 1;
|
|
268
|
+
currentClass = {
|
|
269
|
+
official: officialClass,
|
|
270
|
+
mojang: mojangClass
|
|
271
|
+
};
|
|
272
|
+
addLookupEntries(officialToMojang, createClassSymbolRecord(officialClass), createClassSymbolRecord(mojangClass));
|
|
273
|
+
addLookupEntries(mojangToOfficial, createClassSymbolRecord(mojangClass), createClassSymbolRecord(officialClass));
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
if (!currentClass) {
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
const arrowIndex = line.indexOf(" -> ");
|
|
280
|
+
if (arrowIndex < 0) {
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
const leftRaw = line.slice(0, arrowIndex).trim();
|
|
284
|
+
const rightRaw = line.slice(arrowIndex + 4).trim();
|
|
285
|
+
if (!leftRaw || !rightRaw) {
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
const mojangMemberSignature = stripLineInfo(leftRaw);
|
|
289
|
+
const methodName = parseMethodName(mojangMemberSignature);
|
|
290
|
+
if (methodName) {
|
|
291
|
+
addLookupEntries(officialToMojang, createMethodSymbolRecord(currentClass.official, rightRaw, undefined), createMethodSymbolRecord(currentClass.mojang, methodName, undefined));
|
|
292
|
+
addLookupEntries(mojangToOfficial, createMethodSymbolRecord(currentClass.mojang, methodName, undefined), createMethodSymbolRecord(currentClass.official, rightRaw, undefined));
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
const fieldName = parseFieldName(mojangMemberSignature);
|
|
296
|
+
if (!fieldName) {
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
addLookupEntries(officialToMojang, createFieldSymbolRecord(currentClass.official, rightRaw), createFieldSymbolRecord(currentClass.mojang, fieldName));
|
|
300
|
+
addLookupEntries(mojangToOfficial, createFieldSymbolRecord(currentClass.mojang, fieldName), createFieldSymbolRecord(currentClass.official, rightRaw));
|
|
301
|
+
}
|
|
302
|
+
if (classCount === 0) {
|
|
303
|
+
throw createError({
|
|
304
|
+
code: ERROR_CODES.MAPPING_UNAVAILABLE,
|
|
305
|
+
message: "No class mappings could be parsed from client mappings."
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
const result = new Map();
|
|
309
|
+
result.set(pairKey("official", "mojang"), officialToMojang);
|
|
310
|
+
result.set(pairKey("mojang", "official"), mojangToOfficial);
|
|
311
|
+
return result;
|
|
312
|
+
}
|
|
313
|
+
function normalizeTinyNamespace(namespace) {
|
|
314
|
+
const normalized = namespace.trim().toLowerCase();
|
|
315
|
+
if (normalized === "official") {
|
|
316
|
+
return "official";
|
|
317
|
+
}
|
|
318
|
+
if (normalized === "mojang") {
|
|
319
|
+
return "mojang";
|
|
320
|
+
}
|
|
321
|
+
if (normalized === "intermediary") {
|
|
322
|
+
return "intermediary";
|
|
323
|
+
}
|
|
324
|
+
if (normalized === "named" || normalized === "yarn") {
|
|
325
|
+
return "yarn";
|
|
326
|
+
}
|
|
327
|
+
return undefined;
|
|
328
|
+
}
|
|
329
|
+
function addPairRecords(target, records) {
|
|
330
|
+
for (const [sourceMapping, sourceRecord] of records.entries()) {
|
|
331
|
+
for (const [targetMapping, targetRecord] of records.entries()) {
|
|
332
|
+
if (sourceMapping === targetMapping) {
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
addLookupEntries(ensurePairIndex(target, sourceMapping, targetMapping), sourceRecord, targetRecord);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
function parseTinyMappings(text) {
|
|
340
|
+
const lines = text.split(/\r?\n/).filter((line) => line.trim().length > 0);
|
|
341
|
+
if (lines.length === 0) {
|
|
342
|
+
return new Map();
|
|
343
|
+
}
|
|
344
|
+
const header = lines[0].split("\t");
|
|
345
|
+
if (header.length < 5 || header[0] !== "tiny" || header[1] !== "2") {
|
|
346
|
+
return new Map();
|
|
347
|
+
}
|
|
348
|
+
const namespaceColumns = header.slice(3).map((namespace, index) => ({
|
|
349
|
+
mapping: normalizeTinyNamespace(namespace),
|
|
350
|
+
columnIndex: index + 1
|
|
351
|
+
}));
|
|
352
|
+
const recognized = namespaceColumns.filter((entry) => entry.mapping != null);
|
|
353
|
+
if (recognized.length < 2) {
|
|
354
|
+
return new Map();
|
|
355
|
+
}
|
|
356
|
+
const result = new Map();
|
|
357
|
+
const currentClassNames = new Map();
|
|
358
|
+
for (const line of lines.slice(1)) {
|
|
359
|
+
const columns = line.split("\t");
|
|
360
|
+
if (columns[0] === "c") {
|
|
361
|
+
const classRecords = new Map();
|
|
362
|
+
for (const namespace of recognized) {
|
|
363
|
+
const value = columns[namespace.columnIndex]?.trim() ?? "";
|
|
364
|
+
if (!value) {
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
currentClassNames.set(namespace.mapping, value);
|
|
368
|
+
classRecords.set(namespace.mapping, createClassSymbolRecord(value));
|
|
369
|
+
}
|
|
370
|
+
addPairRecords(result, classRecords);
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
if (columns[0] === "" && columns[1] === "f") {
|
|
374
|
+
const fieldRecords = new Map();
|
|
375
|
+
for (const namespace of recognized) {
|
|
376
|
+
const owner = currentClassNames.get(namespace.mapping);
|
|
377
|
+
const value = columns[namespace.columnIndex + 2]?.trim() ?? "";
|
|
378
|
+
if (!owner || !value) {
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
fieldRecords.set(namespace.mapping, createFieldSymbolRecord(owner, value));
|
|
382
|
+
}
|
|
383
|
+
addPairRecords(result, fieldRecords);
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
if (columns[0] === "" && columns[1] === "m") {
|
|
387
|
+
const descriptor = columns[2]?.trim() || undefined;
|
|
388
|
+
const methodRecords = new Map();
|
|
389
|
+
for (const namespace of recognized) {
|
|
390
|
+
const owner = currentClassNames.get(namespace.mapping);
|
|
391
|
+
const value = columns[namespace.columnIndex + 2]?.trim() ?? "";
|
|
392
|
+
if (!owner || !value) {
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
methodRecords.set(namespace.mapping, createMethodSymbolRecord(owner, value, descriptor));
|
|
396
|
+
}
|
|
397
|
+
addPairRecords(result, methodRecords);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return result;
|
|
401
|
+
}
|
|
402
|
+
function addCandidates(target, index, symbols, kind, confidence) {
|
|
403
|
+
if (!symbols || symbols.size === 0) {
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
const rank = MATCH_RANK[kind];
|
|
407
|
+
for (const key of symbols) {
|
|
408
|
+
const record = index.records.get(key);
|
|
409
|
+
if (!record) {
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
const current = target.get(key);
|
|
413
|
+
if (!current || rank > current.rank || (rank === current.rank && confidence > current.confidence)) {
|
|
414
|
+
target.set(key, {
|
|
415
|
+
key,
|
|
416
|
+
record,
|
|
417
|
+
matchKind: kind,
|
|
418
|
+
confidence,
|
|
419
|
+
rank
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
function lookupCandidates(index, query) {
|
|
425
|
+
const trimmedQuery = query.symbol.trim();
|
|
426
|
+
const collected = new Map();
|
|
427
|
+
addCandidates(collected, index, index.exact.get(trimmedQuery), "exact", 1);
|
|
428
|
+
for (const variant of normalizedVariants(trimmedQuery)) {
|
|
429
|
+
addCandidates(collected, index, index.normalized.get(variant), "normalized", 0.9);
|
|
430
|
+
}
|
|
431
|
+
if (query.kind === "method" && query.owner && query.descriptor) {
|
|
432
|
+
const descriptorlessKey = `${query.owner}.${query.name}`;
|
|
433
|
+
addCandidates(collected, index, index.exact.get(descriptorlessKey), "normalized", DESCRIPTOR_FALLBACK_CONFIDENCE);
|
|
434
|
+
for (const variant of normalizedVariants(descriptorlessKey)) {
|
|
435
|
+
addCandidates(collected, index, index.normalized.get(variant), "normalized", DESCRIPTOR_FALLBACK_CONFIDENCE);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
const simpleKeys = new Set();
|
|
439
|
+
const shortName = simpleName(trimmedQuery);
|
|
440
|
+
if (shortName) {
|
|
441
|
+
simpleKeys.add(shortName);
|
|
442
|
+
}
|
|
443
|
+
if (query.kind !== "class") {
|
|
444
|
+
simpleKeys.add(query.name);
|
|
445
|
+
}
|
|
446
|
+
if (query.kind === "method" && query.descriptor) {
|
|
447
|
+
simpleKeys.add(`${query.name}${query.descriptor}`);
|
|
448
|
+
}
|
|
449
|
+
for (const key of simpleKeys) {
|
|
450
|
+
addCandidates(collected, index, index.simple.get(key), "simple-name", 0.75);
|
|
451
|
+
}
|
|
452
|
+
return [...collected.values()]
|
|
453
|
+
.sort((left, right) => {
|
|
454
|
+
if (right.confidence !== left.confidence) {
|
|
455
|
+
return right.confidence - left.confidence;
|
|
456
|
+
}
|
|
457
|
+
if (right.rank !== left.rank) {
|
|
458
|
+
return right.rank - left.rank;
|
|
459
|
+
}
|
|
460
|
+
return left.record.symbol.localeCompare(right.record.symbol);
|
|
461
|
+
})
|
|
462
|
+
.slice(0, MAX_CANDIDATES)
|
|
463
|
+
.map(({ record, matchKind, confidence }) => ({
|
|
464
|
+
symbol: record.symbol,
|
|
465
|
+
matchKind,
|
|
466
|
+
confidence,
|
|
467
|
+
kind: record.kind,
|
|
468
|
+
owner: record.owner,
|
|
469
|
+
name: record.name,
|
|
470
|
+
descriptor: record.descriptor
|
|
471
|
+
}));
|
|
472
|
+
}
|
|
473
|
+
function mappingPriorityFromInput(configPriority, override) {
|
|
474
|
+
if (override === "loom-first" || override === "maven-first") {
|
|
475
|
+
return override;
|
|
476
|
+
}
|
|
477
|
+
return configPriority;
|
|
478
|
+
}
|
|
479
|
+
function mappingSourceOrder(priority) {
|
|
480
|
+
if (priority === "maven-first") {
|
|
481
|
+
return ["maven", "loom-cache"];
|
|
482
|
+
}
|
|
483
|
+
return ["loom-cache", "maven"];
|
|
484
|
+
}
|
|
485
|
+
function namespacePath(pairs, sourceMapping, targetMapping) {
|
|
486
|
+
if (sourceMapping === targetMapping) {
|
|
487
|
+
return [sourceMapping];
|
|
488
|
+
}
|
|
489
|
+
const queue = [sourceMapping];
|
|
490
|
+
const parent = new Map([[sourceMapping, undefined]]);
|
|
491
|
+
while (queue.length > 0) {
|
|
492
|
+
const current = queue.shift();
|
|
493
|
+
if (current === targetMapping) {
|
|
494
|
+
break;
|
|
495
|
+
}
|
|
496
|
+
for (const key of pairs.keys()) {
|
|
497
|
+
const parsed = parsePairKey(key);
|
|
498
|
+
if (parsed.sourceMapping !== current) {
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
if (parent.has(parsed.targetMapping)) {
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
parent.set(parsed.targetMapping, current);
|
|
505
|
+
queue.push(parsed.targetMapping);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
if (!parent.has(targetMapping)) {
|
|
509
|
+
return undefined;
|
|
510
|
+
}
|
|
511
|
+
const path = [];
|
|
512
|
+
let cursor = targetMapping;
|
|
513
|
+
while (cursor) {
|
|
514
|
+
path.unshift(cursor);
|
|
515
|
+
cursor = parent.get(cursor);
|
|
516
|
+
}
|
|
517
|
+
return path;
|
|
518
|
+
}
|
|
519
|
+
function pathUsesSource(pairs, path, source) {
|
|
520
|
+
for (let index = 0; index < path.length - 1; index += 1) {
|
|
521
|
+
const hop = pairs.get(pairKey(path[index], path[index + 1]));
|
|
522
|
+
if (hop?.source === source) {
|
|
523
|
+
return true;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return false;
|
|
527
|
+
}
|
|
528
|
+
function pathToTransformChain(path) {
|
|
529
|
+
if (path.length <= 1) {
|
|
530
|
+
return [];
|
|
531
|
+
}
|
|
532
|
+
const transform = [];
|
|
533
|
+
for (let index = 0; index < path.length - 1; index += 1) {
|
|
534
|
+
transform.push(`mapping:${path[index]}->${path[index + 1]}`);
|
|
535
|
+
}
|
|
536
|
+
return transform;
|
|
537
|
+
}
|
|
538
|
+
function toLookupCandidate(record) {
|
|
539
|
+
return {
|
|
540
|
+
symbol: record.symbol,
|
|
541
|
+
matchKind: "exact",
|
|
542
|
+
confidence: 1,
|
|
543
|
+
kind: record.kind,
|
|
544
|
+
owner: record.owner,
|
|
545
|
+
name: record.name,
|
|
546
|
+
descriptor: record.descriptor
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
function toSymbolReference(record) {
|
|
550
|
+
return {
|
|
551
|
+
kind: record.kind,
|
|
552
|
+
name: record.kind === "class" ? record.symbol : record.name,
|
|
553
|
+
owner: record.kind === "class" ? undefined : record.owner,
|
|
554
|
+
descriptor: record.kind === "method" ? record.descriptor : undefined,
|
|
555
|
+
symbol: record.symbol
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
function toResolutionCandidate(candidate) {
|
|
559
|
+
return {
|
|
560
|
+
kind: candidate.kind,
|
|
561
|
+
name: candidate.kind === "class" ? candidate.symbol : candidate.name,
|
|
562
|
+
owner: candidate.kind === "class" ? undefined : candidate.owner,
|
|
563
|
+
descriptor: candidate.kind === "method" ? candidate.descriptor : undefined,
|
|
564
|
+
symbol: candidate.symbol,
|
|
565
|
+
matchKind: candidate.matchKind,
|
|
566
|
+
confidence: candidate.confidence
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
function invalidInputError(message, details) {
|
|
570
|
+
return createError({
|
|
571
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
572
|
+
message,
|
|
573
|
+
details
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
function normalizeMemberName(name) {
|
|
577
|
+
const normalized = name.trim();
|
|
578
|
+
if (!normalized || /[\s./()]/.test(normalized)) {
|
|
579
|
+
throw invalidInputError("name must be a simple member name without separators when kind is field or method.", {
|
|
580
|
+
name
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
return normalized;
|
|
584
|
+
}
|
|
585
|
+
function normalizeMethodDescriptor(descriptor) {
|
|
586
|
+
const normalized = descriptor?.trim() ?? "";
|
|
587
|
+
if (!normalized || !normalized.startsWith("(") || !normalized.includes(")")) {
|
|
588
|
+
throw invalidInputError("descriptor must be a valid JVM descriptor when kind=method.", {
|
|
589
|
+
descriptor
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
return normalized;
|
|
593
|
+
}
|
|
594
|
+
function normalizeQuerySymbol(input) {
|
|
595
|
+
if (input.kind !== "class" && input.kind !== "field" && input.kind !== "method") {
|
|
596
|
+
throw invalidInputError('kind must be one of "class", "field", or "method".', {
|
|
597
|
+
kind: input.kind
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
const normalizedName = input.name?.trim() ?? "";
|
|
601
|
+
if (!normalizedName) {
|
|
602
|
+
throw invalidInputError("name must be a non-empty string.", {
|
|
603
|
+
name: input.name
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
if (input.kind === "class") {
|
|
607
|
+
const owner = input.owner?.trim();
|
|
608
|
+
if (owner) {
|
|
609
|
+
throw invalidInputError("owner is not allowed when kind=class. Use name as FQCN.", {
|
|
610
|
+
owner: input.owner,
|
|
611
|
+
nextAction: 'Provide class as name, e.g. "net.minecraft.server.Main".'
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
if (input.descriptor?.trim()) {
|
|
615
|
+
throw invalidInputError("descriptor is not allowed when kind=class.", {
|
|
616
|
+
descriptor: input.descriptor
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
const className = normalizeMappedSymbolOutput(normalizedName);
|
|
620
|
+
if (!className.includes(".")) {
|
|
621
|
+
throw invalidInputError("name must be a fully qualified class name when kind=class.", {
|
|
622
|
+
name: input.name
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
const record = createClassSymbolRecord(className);
|
|
626
|
+
return {
|
|
627
|
+
record,
|
|
628
|
+
querySymbol: toSymbolReference(record)
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
const owner = normalizeMappedSymbolOutput(input.owner?.trim() ?? "");
|
|
632
|
+
if (!owner) {
|
|
633
|
+
throw invalidInputError("owner is required when kind is field or method.", {
|
|
634
|
+
owner: input.owner,
|
|
635
|
+
kind: input.kind
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
if (input.kind === "field") {
|
|
639
|
+
if (input.descriptor?.trim()) {
|
|
640
|
+
throw invalidInputError("descriptor is not allowed when kind=field.", {
|
|
641
|
+
descriptor: input.descriptor
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
const record = createFieldSymbolRecord(owner, normalizeMemberName(normalizedName));
|
|
645
|
+
return {
|
|
646
|
+
record,
|
|
647
|
+
querySymbol: toSymbolReference(record)
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
const record = createMethodSymbolRecord(owner, normalizeMemberName(normalizedName), normalizeMethodDescriptor(input.descriptor));
|
|
651
|
+
return {
|
|
652
|
+
record,
|
|
653
|
+
querySymbol: toSymbolReference(record)
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
function collectTargetRecords(graph, targetMapping) {
|
|
657
|
+
const merged = new Map();
|
|
658
|
+
for (const [key, pair] of graph.pairs.entries()) {
|
|
659
|
+
const parsed = parsePairKey(key);
|
|
660
|
+
if (parsed.targetMapping !== targetMapping) {
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
663
|
+
for (const record of pair.index.records.values()) {
|
|
664
|
+
merged.set(buildSymbolKey(record), record);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return [...merged.values()];
|
|
668
|
+
}
|
|
669
|
+
function normalizeIncludedKinds(inputKinds) {
|
|
670
|
+
const normalized = new Set();
|
|
671
|
+
const kinds = inputKinds ?? ["class", "field", "method"];
|
|
672
|
+
for (const kind of kinds) {
|
|
673
|
+
if (kind === "class" || kind === "field" || kind === "method") {
|
|
674
|
+
normalized.add(kind);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
if (normalized.size === 0) {
|
|
678
|
+
normalized.add("class");
|
|
679
|
+
normalized.add("field");
|
|
680
|
+
normalized.add("method");
|
|
681
|
+
}
|
|
682
|
+
return normalized;
|
|
683
|
+
}
|
|
684
|
+
export class MappingService {
|
|
685
|
+
config;
|
|
686
|
+
versionService;
|
|
687
|
+
fetchFn;
|
|
688
|
+
graphCache = new Map();
|
|
689
|
+
buildLocks = new Map();
|
|
690
|
+
constructor(config, versionService = new VersionService(config), fetchFn = globalThis.fetch) {
|
|
691
|
+
this.config = config;
|
|
692
|
+
this.versionService = versionService;
|
|
693
|
+
this.fetchFn = fetchFn;
|
|
694
|
+
}
|
|
695
|
+
async findMapping(input) {
|
|
696
|
+
const version = input.version.trim();
|
|
697
|
+
if (!version) {
|
|
698
|
+
throw createError({
|
|
699
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
700
|
+
message: "version must be non-empty.",
|
|
701
|
+
details: {
|
|
702
|
+
version: input.version
|
|
703
|
+
}
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
const { record: queryRecord, querySymbol } = normalizeQuerySymbol(input);
|
|
707
|
+
const sourceMapping = input.sourceMapping;
|
|
708
|
+
const targetMapping = input.targetMapping;
|
|
709
|
+
if (!SUPPORTED_MAPPINGS.has(sourceMapping) || !SUPPORTED_MAPPINGS.has(targetMapping)) {
|
|
710
|
+
throw createError({
|
|
711
|
+
code: ERROR_CODES.MAPPING_UNAVAILABLE,
|
|
712
|
+
message: "Unsupported mapping pair for lookup.",
|
|
713
|
+
details: {
|
|
714
|
+
version,
|
|
715
|
+
sourceMapping,
|
|
716
|
+
targetMapping
|
|
717
|
+
}
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
const priority = mappingPriorityFromInput(this.config.mappingSourcePriority, input.sourcePriority);
|
|
721
|
+
const mappingContext = {
|
|
722
|
+
version,
|
|
723
|
+
sourceMapping,
|
|
724
|
+
targetMapping,
|
|
725
|
+
sourcePriorityApplied: priority
|
|
726
|
+
};
|
|
727
|
+
if (sourceMapping === targetMapping) {
|
|
728
|
+
const identity = toResolutionCandidate({
|
|
729
|
+
...toLookupCandidate(queryRecord),
|
|
730
|
+
matchKind: "exact",
|
|
731
|
+
confidence: 1
|
|
732
|
+
});
|
|
733
|
+
return {
|
|
734
|
+
querySymbol,
|
|
735
|
+
mappingContext,
|
|
736
|
+
resolved: true,
|
|
737
|
+
status: "resolved",
|
|
738
|
+
resolvedSymbol: querySymbol,
|
|
739
|
+
candidates: [identity],
|
|
740
|
+
warnings: []
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
const graph = await this.loadGraph(version, priority);
|
|
744
|
+
const path = namespacePath(graph.pairs, sourceMapping, targetMapping);
|
|
745
|
+
if (!path) {
|
|
746
|
+
return {
|
|
747
|
+
querySymbol,
|
|
748
|
+
mappingContext,
|
|
749
|
+
resolved: false,
|
|
750
|
+
status: "mapping_unavailable",
|
|
751
|
+
candidates: [],
|
|
752
|
+
warnings: [
|
|
753
|
+
`No mapping path is available for ${sourceMapping} -> ${targetMapping} on version "${version}".`
|
|
754
|
+
]
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
const rawCandidates = this.mapCandidatesAlongPath(graph, path, queryRecord);
|
|
758
|
+
const candidates = rawCandidates.map(toResolutionCandidate);
|
|
759
|
+
const warnings = [];
|
|
760
|
+
if (queryRecord.kind === "method" &&
|
|
761
|
+
queryRecord.descriptor &&
|
|
762
|
+
pathUsesSource(graph.pairs, path, "mojang-client-mappings")) {
|
|
763
|
+
warnings.push("Method descriptor could not be preserved through mojang-client-mappings and may have used name-based fallback.");
|
|
764
|
+
}
|
|
765
|
+
if (candidates.length === 0) {
|
|
766
|
+
warnings.push("No mapping candidate matched the input symbol.");
|
|
767
|
+
}
|
|
768
|
+
const status = candidates.length === 0 ? "not_found" : candidates.length === 1 ? "resolved" : "ambiguous";
|
|
769
|
+
return {
|
|
770
|
+
querySymbol,
|
|
771
|
+
mappingContext,
|
|
772
|
+
resolved: status === "resolved",
|
|
773
|
+
status,
|
|
774
|
+
resolvedSymbol: status === "resolved" ? candidates[0] : undefined,
|
|
775
|
+
candidates,
|
|
776
|
+
warnings,
|
|
777
|
+
provenance: this.provenanceForPath(graph, path)
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
async ensureMappingAvailable(input) {
|
|
781
|
+
const version = input.version.trim();
|
|
782
|
+
if (!version) {
|
|
783
|
+
throw createError({
|
|
784
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
785
|
+
message: "version must be non-empty.",
|
|
786
|
+
details: {
|
|
787
|
+
version: input.version
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
const sourceMapping = input.sourceMapping;
|
|
792
|
+
const targetMapping = input.targetMapping;
|
|
793
|
+
if (!SUPPORTED_MAPPINGS.has(sourceMapping) || !SUPPORTED_MAPPINGS.has(targetMapping)) {
|
|
794
|
+
throw createError({
|
|
795
|
+
code: ERROR_CODES.MAPPING_UNAVAILABLE,
|
|
796
|
+
message: "Unsupported mapping pair.",
|
|
797
|
+
details: {
|
|
798
|
+
version,
|
|
799
|
+
sourceMapping,
|
|
800
|
+
targetMapping
|
|
801
|
+
}
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
const priority = mappingPriorityFromInput(this.config.mappingSourcePriority, input.sourcePriority);
|
|
805
|
+
if (sourceMapping === targetMapping) {
|
|
806
|
+
return {
|
|
807
|
+
transformChain: [`mapping:${sourceMapping}->${targetMapping}`],
|
|
808
|
+
warnings: []
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
const graph = await this.loadGraph(version, priority);
|
|
812
|
+
const path = namespacePath(graph.pairs, sourceMapping, targetMapping);
|
|
813
|
+
if (!path) {
|
|
814
|
+
throw createError({
|
|
815
|
+
code: ERROR_CODES.MAPPING_UNAVAILABLE,
|
|
816
|
+
message: `No mapping path is available for ${sourceMapping} -> ${targetMapping} on version "${version}".`,
|
|
817
|
+
details: {
|
|
818
|
+
version,
|
|
819
|
+
sourceMapping,
|
|
820
|
+
targetMapping,
|
|
821
|
+
sourcePriority: priority
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
const provenance = this.provenanceForPath(graph, path);
|
|
826
|
+
const transformChain = [
|
|
827
|
+
provenance ? `mapping-source:${provenance.source}` : undefined,
|
|
828
|
+
...pathToTransformChain(path)
|
|
829
|
+
].filter((entry) => Boolean(entry));
|
|
830
|
+
return {
|
|
831
|
+
transformChain,
|
|
832
|
+
warnings: [...graph.warnings],
|
|
833
|
+
provenance
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
async resolveMethodMappingExact(input) {
|
|
837
|
+
const version = input.version.trim();
|
|
838
|
+
if (!version) {
|
|
839
|
+
throw createError({
|
|
840
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
841
|
+
message: "version must be non-empty.",
|
|
842
|
+
details: {
|
|
843
|
+
version: input.version
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
if (input.kind !== "method") {
|
|
848
|
+
throw createError({
|
|
849
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
850
|
+
message: 'resolveMethodMappingExact requires kind="method".',
|
|
851
|
+
details: {
|
|
852
|
+
kind: input.kind
|
|
853
|
+
}
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
const { record: queryRecord, querySymbol } = normalizeQuerySymbol(input);
|
|
857
|
+
const owner = queryRecord.owner;
|
|
858
|
+
const method = queryRecord.name;
|
|
859
|
+
const descriptor = queryRecord.descriptor;
|
|
860
|
+
const sourceMapping = input.sourceMapping;
|
|
861
|
+
const targetMapping = input.targetMapping;
|
|
862
|
+
if (!SUPPORTED_MAPPINGS.has(sourceMapping) || !SUPPORTED_MAPPINGS.has(targetMapping)) {
|
|
863
|
+
throw createError({
|
|
864
|
+
code: ERROR_CODES.MAPPING_UNAVAILABLE,
|
|
865
|
+
message: "Unsupported mapping pair for exact method resolution.",
|
|
866
|
+
details: {
|
|
867
|
+
version,
|
|
868
|
+
sourceMapping,
|
|
869
|
+
targetMapping
|
|
870
|
+
}
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
const priority = mappingPriorityFromInput(this.config.mappingSourcePriority, input.sourcePriority);
|
|
874
|
+
const mappingContext = {
|
|
875
|
+
version,
|
|
876
|
+
sourceMapping,
|
|
877
|
+
targetMapping,
|
|
878
|
+
sourcePriorityApplied: priority
|
|
879
|
+
};
|
|
880
|
+
if (sourceMapping === targetMapping) {
|
|
881
|
+
const resolvedCandidate = toResolutionCandidate({
|
|
882
|
+
...toLookupCandidate(queryRecord),
|
|
883
|
+
matchKind: "exact",
|
|
884
|
+
confidence: 1
|
|
885
|
+
});
|
|
886
|
+
return {
|
|
887
|
+
querySymbol,
|
|
888
|
+
mappingContext,
|
|
889
|
+
resolved: true,
|
|
890
|
+
status: "resolved",
|
|
891
|
+
resolvedSymbol: resolvedCandidate,
|
|
892
|
+
candidates: [resolvedCandidate],
|
|
893
|
+
warnings: []
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
const graph = await this.loadGraph(version, priority);
|
|
897
|
+
const path = namespacePath(graph.pairs, sourceMapping, targetMapping);
|
|
898
|
+
if (!path) {
|
|
899
|
+
return {
|
|
900
|
+
querySymbol,
|
|
901
|
+
mappingContext,
|
|
902
|
+
resolved: false,
|
|
903
|
+
status: "mapping_unavailable",
|
|
904
|
+
candidates: [],
|
|
905
|
+
warnings: [
|
|
906
|
+
`No mapping path is available for ${sourceMapping} -> ${targetMapping} on version "${version}".`
|
|
907
|
+
]
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
const warnings = [];
|
|
911
|
+
const rawCandidates = this
|
|
912
|
+
.mapCandidatesAlongPath(graph, path, queryRecord)
|
|
913
|
+
.filter((candidate) => candidate.kind === "method");
|
|
914
|
+
const candidates = rawCandidates.map(toResolutionCandidate);
|
|
915
|
+
const strictCandidates = rawCandidates.filter((candidate) => candidate.descriptor === descriptor);
|
|
916
|
+
if (strictCandidates.length === 1) {
|
|
917
|
+
const resolved = toResolutionCandidate(strictCandidates[0]);
|
|
918
|
+
return {
|
|
919
|
+
querySymbol,
|
|
920
|
+
mappingContext,
|
|
921
|
+
resolved: true,
|
|
922
|
+
status: "resolved",
|
|
923
|
+
resolvedSymbol: resolved,
|
|
924
|
+
candidates,
|
|
925
|
+
warnings,
|
|
926
|
+
provenance: this.provenanceForPath(graph, path)
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
if (strictCandidates.length > 1) {
|
|
930
|
+
warnings.push("Exact method mapping is ambiguous for owner+method+descriptor.");
|
|
931
|
+
return {
|
|
932
|
+
querySymbol,
|
|
933
|
+
mappingContext,
|
|
934
|
+
resolved: false,
|
|
935
|
+
status: "ambiguous",
|
|
936
|
+
candidates,
|
|
937
|
+
warnings,
|
|
938
|
+
provenance: this.provenanceForPath(graph, path)
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
if (pathUsesSource(graph.pairs, path, "mojang-client-mappings")) {
|
|
942
|
+
warnings.push("Method descriptor could not be preserved through mojang-client-mappings and exact resolution is unavailable.");
|
|
943
|
+
return {
|
|
944
|
+
querySymbol,
|
|
945
|
+
mappingContext,
|
|
946
|
+
resolved: false,
|
|
947
|
+
status: "mapping_unavailable",
|
|
948
|
+
candidates,
|
|
949
|
+
warnings,
|
|
950
|
+
provenance: this.provenanceForPath(graph, path)
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
return {
|
|
954
|
+
querySymbol,
|
|
955
|
+
mappingContext,
|
|
956
|
+
resolved: false,
|
|
957
|
+
status: "not_found",
|
|
958
|
+
candidates,
|
|
959
|
+
warnings,
|
|
960
|
+
provenance: this.provenanceForPath(graph, path)
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
async getClassApiMatrix(input) {
|
|
964
|
+
const version = input.version.trim();
|
|
965
|
+
const className = normalizeMappedSymbolOutput(input.className.trim());
|
|
966
|
+
if (!version || !className) {
|
|
967
|
+
throw createError({
|
|
968
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
969
|
+
message: "version and className must be non-empty strings.",
|
|
970
|
+
details: {
|
|
971
|
+
version: input.version,
|
|
972
|
+
className: input.className
|
|
973
|
+
}
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
const classNameMapping = input.classNameMapping;
|
|
977
|
+
if (!SUPPORTED_MAPPINGS.has(classNameMapping)) {
|
|
978
|
+
throw createError({
|
|
979
|
+
code: ERROR_CODES.MAPPING_UNAVAILABLE,
|
|
980
|
+
message: "Unsupported classNameMapping.",
|
|
981
|
+
details: {
|
|
982
|
+
classNameMapping
|
|
983
|
+
}
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
const priority = mappingPriorityFromInput(this.config.mappingSourcePriority, input.sourcePriority);
|
|
987
|
+
const graph = await this.loadGraph(version, priority);
|
|
988
|
+
const warnings = [...graph.warnings];
|
|
989
|
+
const includeKinds = normalizeIncludedKinds(input.includeKinds);
|
|
990
|
+
const classByMapping = {
|
|
991
|
+
[classNameMapping]: createClassSymbolRecord(className)
|
|
992
|
+
};
|
|
993
|
+
for (const mapping of SUPPORTED_MAPPINGS) {
|
|
994
|
+
if (mapping === classNameMapping) {
|
|
995
|
+
continue;
|
|
996
|
+
}
|
|
997
|
+
const mapped = this.mapRecordBetweenMappings(graph, classNameMapping, mapping, classByMapping[classNameMapping]);
|
|
998
|
+
if (mapped.length > 1) {
|
|
999
|
+
warnings.push(`Class identity mapping to ${mapping} is ambiguous for "${className}".`);
|
|
1000
|
+
}
|
|
1001
|
+
if (mapped.length > 0) {
|
|
1002
|
+
classByMapping[mapping] = mapped[0];
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
const baseMapping = classByMapping.official ? "official" : classNameMapping;
|
|
1006
|
+
const baseClass = classByMapping[baseMapping];
|
|
1007
|
+
if (!baseClass) {
|
|
1008
|
+
return {
|
|
1009
|
+
version,
|
|
1010
|
+
className,
|
|
1011
|
+
classNameMapping,
|
|
1012
|
+
classIdentity: {
|
|
1013
|
+
official: classByMapping.official?.symbol,
|
|
1014
|
+
mojang: classByMapping.mojang?.symbol,
|
|
1015
|
+
intermediary: classByMapping.intermediary?.symbol,
|
|
1016
|
+
yarn: classByMapping.yarn?.symbol
|
|
1017
|
+
},
|
|
1018
|
+
rows: [],
|
|
1019
|
+
warnings
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
const baseRecords = collectTargetRecords(graph, baseMapping).filter((record) => {
|
|
1023
|
+
if (record.kind === "class") {
|
|
1024
|
+
return includeKinds.has("class") && record.symbol === baseClass.symbol;
|
|
1025
|
+
}
|
|
1026
|
+
if (record.owner !== baseClass.symbol) {
|
|
1027
|
+
return false;
|
|
1028
|
+
}
|
|
1029
|
+
if (record.kind === "field") {
|
|
1030
|
+
return includeKinds.has("field");
|
|
1031
|
+
}
|
|
1032
|
+
return includeKinds.has("method");
|
|
1033
|
+
});
|
|
1034
|
+
const rows = [];
|
|
1035
|
+
const rowSeen = new Set();
|
|
1036
|
+
const rowKindOrder = {
|
|
1037
|
+
class: 0,
|
|
1038
|
+
field: 1,
|
|
1039
|
+
method: 2
|
|
1040
|
+
};
|
|
1041
|
+
const sortedBase = [...baseRecords].sort((left, right) => {
|
|
1042
|
+
const leftKind = rowKindOrder[left.kind];
|
|
1043
|
+
const rightKind = rowKindOrder[right.kind];
|
|
1044
|
+
if (leftKind !== rightKind) {
|
|
1045
|
+
return leftKind - rightKind;
|
|
1046
|
+
}
|
|
1047
|
+
if ((left.descriptor ?? "") !== (right.descriptor ?? "")) {
|
|
1048
|
+
return (left.descriptor ?? "").localeCompare(right.descriptor ?? "");
|
|
1049
|
+
}
|
|
1050
|
+
return left.symbol.localeCompare(right.symbol);
|
|
1051
|
+
});
|
|
1052
|
+
for (const baseRecord of sortedBase) {
|
|
1053
|
+
const key = buildSymbolKey(baseRecord);
|
|
1054
|
+
if (rowSeen.has(key)) {
|
|
1055
|
+
continue;
|
|
1056
|
+
}
|
|
1057
|
+
rowSeen.add(key);
|
|
1058
|
+
const row = {
|
|
1059
|
+
kind: baseRecord.kind,
|
|
1060
|
+
descriptor: baseRecord.descriptor,
|
|
1061
|
+
completeness: false
|
|
1062
|
+
};
|
|
1063
|
+
for (const mapping of SUPPORTED_MAPPINGS) {
|
|
1064
|
+
const classIdentity = classByMapping[mapping];
|
|
1065
|
+
let resolved;
|
|
1066
|
+
if (mapping === baseMapping) {
|
|
1067
|
+
resolved = baseRecord;
|
|
1068
|
+
}
|
|
1069
|
+
else {
|
|
1070
|
+
const mapped = this.mapRecordBetweenMappings(graph, baseMapping, mapping, baseRecord);
|
|
1071
|
+
let filtered = mapped;
|
|
1072
|
+
if (baseRecord.kind !== "class" && classIdentity) {
|
|
1073
|
+
filtered = filtered.filter((candidate) => candidate.owner === classIdentity.symbol);
|
|
1074
|
+
}
|
|
1075
|
+
if (baseRecord.kind === "method" && baseRecord.descriptor) {
|
|
1076
|
+
const descriptorMatched = filtered.filter((candidate) => candidate.descriptor === baseRecord.descriptor);
|
|
1077
|
+
if (descriptorMatched.length > 0) {
|
|
1078
|
+
filtered = descriptorMatched;
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
if (filtered.length > 1) {
|
|
1082
|
+
warnings.push(`Row mapping to ${mapping} is ambiguous for "${baseRecord.symbol}". Using highest-ranked candidate.`);
|
|
1083
|
+
}
|
|
1084
|
+
resolved = filtered[0];
|
|
1085
|
+
}
|
|
1086
|
+
if (!resolved) {
|
|
1087
|
+
continue;
|
|
1088
|
+
}
|
|
1089
|
+
const entry = {
|
|
1090
|
+
symbol: resolved.symbol,
|
|
1091
|
+
owner: resolved.owner,
|
|
1092
|
+
name: resolved.name,
|
|
1093
|
+
descriptor: resolved.descriptor
|
|
1094
|
+
};
|
|
1095
|
+
row[mapping] = entry;
|
|
1096
|
+
}
|
|
1097
|
+
row.completeness = Boolean(row.official && row.mojang && row.intermediary && row.yarn);
|
|
1098
|
+
rows.push(row);
|
|
1099
|
+
}
|
|
1100
|
+
return {
|
|
1101
|
+
version,
|
|
1102
|
+
className,
|
|
1103
|
+
classNameMapping,
|
|
1104
|
+
classIdentity: {
|
|
1105
|
+
official: classByMapping.official?.symbol,
|
|
1106
|
+
mojang: classByMapping.mojang?.symbol,
|
|
1107
|
+
intermediary: classByMapping.intermediary?.symbol,
|
|
1108
|
+
yarn: classByMapping.yarn?.symbol
|
|
1109
|
+
},
|
|
1110
|
+
rows,
|
|
1111
|
+
warnings
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
async checkSymbolExists(input) {
|
|
1115
|
+
const version = input.version.trim();
|
|
1116
|
+
if (!version) {
|
|
1117
|
+
throw createError({
|
|
1118
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
1119
|
+
message: "version must be non-empty.",
|
|
1120
|
+
details: {
|
|
1121
|
+
version: input.version
|
|
1122
|
+
}
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
const { record: queryRecord, querySymbol } = normalizeQuerySymbol(input);
|
|
1126
|
+
const sourceMapping = input.sourceMapping;
|
|
1127
|
+
if (!SUPPORTED_MAPPINGS.has(sourceMapping)) {
|
|
1128
|
+
throw createError({
|
|
1129
|
+
code: ERROR_CODES.MAPPING_UNAVAILABLE,
|
|
1130
|
+
message: "Unsupported mapping namespace for existence check.",
|
|
1131
|
+
details: {
|
|
1132
|
+
sourceMapping
|
|
1133
|
+
}
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
const priority = mappingPriorityFromInput(this.config.mappingSourcePriority, input.sourcePriority);
|
|
1137
|
+
const mappingContext = {
|
|
1138
|
+
version,
|
|
1139
|
+
sourceMapping,
|
|
1140
|
+
sourcePriorityApplied: priority
|
|
1141
|
+
};
|
|
1142
|
+
const graph = await this.loadGraph(version, priority);
|
|
1143
|
+
const warnings = [...graph.warnings];
|
|
1144
|
+
const records = collectTargetRecords(graph, sourceMapping);
|
|
1145
|
+
if (records.length === 0) {
|
|
1146
|
+
return {
|
|
1147
|
+
querySymbol,
|
|
1148
|
+
mappingContext,
|
|
1149
|
+
resolved: false,
|
|
1150
|
+
status: "mapping_unavailable",
|
|
1151
|
+
candidates: [],
|
|
1152
|
+
warnings
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
const buildOutput = (matched, status) => {
|
|
1156
|
+
const candidates = matched.map((record) => toResolutionCandidate(toLookupCandidate(record)));
|
|
1157
|
+
return {
|
|
1158
|
+
querySymbol,
|
|
1159
|
+
mappingContext,
|
|
1160
|
+
resolved: status === "resolved",
|
|
1161
|
+
status,
|
|
1162
|
+
resolvedSymbol: status === "resolved" ? candidates[0] : undefined,
|
|
1163
|
+
candidates,
|
|
1164
|
+
warnings
|
|
1165
|
+
};
|
|
1166
|
+
};
|
|
1167
|
+
if (queryRecord.kind === "class") {
|
|
1168
|
+
const matched = records.filter((record) => record.kind === "class" && record.symbol === queryRecord.symbol);
|
|
1169
|
+
const status = matched.length === 1 ? "resolved" : matched.length > 1 ? "ambiguous" : "not_found";
|
|
1170
|
+
return buildOutput(matched, status);
|
|
1171
|
+
}
|
|
1172
|
+
if (queryRecord.kind === "field") {
|
|
1173
|
+
const matched = records.filter((record) => record.kind === "field" && record.owner === queryRecord.owner && record.name === queryRecord.name);
|
|
1174
|
+
const status = matched.length === 1 ? "resolved" : matched.length > 1 ? "ambiguous" : "not_found";
|
|
1175
|
+
return buildOutput(matched, status);
|
|
1176
|
+
}
|
|
1177
|
+
const methodCandidates = records.filter((record) => record.kind === "method" && record.owner === queryRecord.owner && record.name === queryRecord.name);
|
|
1178
|
+
const descriptorMatched = methodCandidates.filter((record) => record.descriptor === queryRecord.descriptor);
|
|
1179
|
+
if (descriptorMatched.length === 1) {
|
|
1180
|
+
return buildOutput(descriptorMatched, "resolved");
|
|
1181
|
+
}
|
|
1182
|
+
if (descriptorMatched.length > 1) {
|
|
1183
|
+
return buildOutput(descriptorMatched, "ambiguous");
|
|
1184
|
+
}
|
|
1185
|
+
if (methodCandidates.some((candidate) => candidate.descriptor == null)) {
|
|
1186
|
+
warnings.push("Descriptor-level existence checks are unavailable for descriptorless mapping entries.");
|
|
1187
|
+
return buildOutput(methodCandidates, "mapping_unavailable");
|
|
1188
|
+
}
|
|
1189
|
+
return buildOutput([], "not_found");
|
|
1190
|
+
}
|
|
1191
|
+
mapRecordBetweenMappings(graph, sourceMapping, targetMapping, record) {
|
|
1192
|
+
if (sourceMapping === targetMapping) {
|
|
1193
|
+
return [record];
|
|
1194
|
+
}
|
|
1195
|
+
const path = namespacePath(graph.pairs, sourceMapping, targetMapping);
|
|
1196
|
+
if (!path) {
|
|
1197
|
+
return [];
|
|
1198
|
+
}
|
|
1199
|
+
let mapped = this
|
|
1200
|
+
.mapCandidatesAlongPath(graph, path, record)
|
|
1201
|
+
.filter((candidate) => candidate.kind === record.kind)
|
|
1202
|
+
.map((candidate) => ({
|
|
1203
|
+
kind: candidate.kind,
|
|
1204
|
+
symbol: candidate.symbol,
|
|
1205
|
+
owner: candidate.owner,
|
|
1206
|
+
name: candidate.name,
|
|
1207
|
+
descriptor: candidate.descriptor
|
|
1208
|
+
}));
|
|
1209
|
+
if (record.kind === "method" && record.descriptor) {
|
|
1210
|
+
const descriptorMatched = mapped.filter((candidate) => candidate.descriptor === record.descriptor);
|
|
1211
|
+
if (descriptorMatched.length > 0) {
|
|
1212
|
+
mapped = descriptorMatched;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
const deduped = new Map();
|
|
1216
|
+
for (const candidate of mapped) {
|
|
1217
|
+
deduped.set(buildSymbolKey(candidate), candidate);
|
|
1218
|
+
}
|
|
1219
|
+
return [...deduped.values()];
|
|
1220
|
+
}
|
|
1221
|
+
mapCandidatesAlongPath(graph, path, query) {
|
|
1222
|
+
const queryKey = buildSymbolKey(query);
|
|
1223
|
+
let current = new Map([
|
|
1224
|
+
[
|
|
1225
|
+
queryKey,
|
|
1226
|
+
{
|
|
1227
|
+
key: queryKey,
|
|
1228
|
+
record: query,
|
|
1229
|
+
matchKind: "exact",
|
|
1230
|
+
confidence: 1,
|
|
1231
|
+
rank: MATCH_RANK.exact
|
|
1232
|
+
}
|
|
1233
|
+
]
|
|
1234
|
+
]);
|
|
1235
|
+
for (let index = 0; index < path.length - 1; index += 1) {
|
|
1236
|
+
const from = path[index];
|
|
1237
|
+
const to = path[index + 1];
|
|
1238
|
+
const record = graph.pairs.get(pairKey(from, to));
|
|
1239
|
+
if (!record) {
|
|
1240
|
+
return [];
|
|
1241
|
+
}
|
|
1242
|
+
const next = new Map();
|
|
1243
|
+
for (const candidate of current.values()) {
|
|
1244
|
+
const mapped = lookupCandidates(record.index, candidate.record);
|
|
1245
|
+
for (const item of mapped) {
|
|
1246
|
+
const mappedRecord = {
|
|
1247
|
+
kind: item.kind,
|
|
1248
|
+
symbol: item.symbol,
|
|
1249
|
+
owner: item.owner,
|
|
1250
|
+
name: item.name,
|
|
1251
|
+
descriptor: item.descriptor
|
|
1252
|
+
};
|
|
1253
|
+
const mappedKey = buildSymbolKey(mappedRecord);
|
|
1254
|
+
const rank = MATCH_RANK[item.matchKind];
|
|
1255
|
+
const composedConfidence = candidate.confidence * item.confidence;
|
|
1256
|
+
const existing = next.get(mappedKey);
|
|
1257
|
+
if (!existing ||
|
|
1258
|
+
composedConfidence > existing.confidence ||
|
|
1259
|
+
(composedConfidence === existing.confidence && rank > existing.rank)) {
|
|
1260
|
+
next.set(mappedKey, {
|
|
1261
|
+
key: mappedKey,
|
|
1262
|
+
record: mappedRecord,
|
|
1263
|
+
matchKind: item.matchKind,
|
|
1264
|
+
confidence: composedConfidence,
|
|
1265
|
+
rank
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
current = next;
|
|
1271
|
+
if (current.size === 0) {
|
|
1272
|
+
return [];
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
return [...current.values()]
|
|
1276
|
+
.sort((left, right) => {
|
|
1277
|
+
if (right.confidence !== left.confidence) {
|
|
1278
|
+
return right.confidence - left.confidence;
|
|
1279
|
+
}
|
|
1280
|
+
if (right.rank !== left.rank) {
|
|
1281
|
+
return right.rank - left.rank;
|
|
1282
|
+
}
|
|
1283
|
+
return left.record.symbol.localeCompare(right.record.symbol);
|
|
1284
|
+
})
|
|
1285
|
+
.slice(0, MAX_CANDIDATES)
|
|
1286
|
+
.map((item) => ({
|
|
1287
|
+
symbol: item.record.symbol,
|
|
1288
|
+
matchKind: item.matchKind,
|
|
1289
|
+
confidence: Number(item.confidence.toFixed(6)),
|
|
1290
|
+
kind: item.record.kind,
|
|
1291
|
+
owner: item.record.owner,
|
|
1292
|
+
name: item.record.name,
|
|
1293
|
+
descriptor: item.record.descriptor
|
|
1294
|
+
}));
|
|
1295
|
+
}
|
|
1296
|
+
provenanceForPath(graph, path) {
|
|
1297
|
+
if (path.length <= 1) {
|
|
1298
|
+
return undefined;
|
|
1299
|
+
}
|
|
1300
|
+
const first = graph.pairs.get(pairKey(path[0], path[1]));
|
|
1301
|
+
if (!first) {
|
|
1302
|
+
return undefined;
|
|
1303
|
+
}
|
|
1304
|
+
return {
|
|
1305
|
+
source: first.source,
|
|
1306
|
+
mappingArtifact: first.mappingArtifact,
|
|
1307
|
+
version: graph.version,
|
|
1308
|
+
priority: graph.priority
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
async loadGraph(version, priority) {
|
|
1312
|
+
const cacheKey = `${version}|${priority}`;
|
|
1313
|
+
const cached = this.graphCache.get(cacheKey);
|
|
1314
|
+
if (cached) {
|
|
1315
|
+
this.graphCache.delete(cacheKey);
|
|
1316
|
+
this.graphCache.set(cacheKey, cached);
|
|
1317
|
+
return cached;
|
|
1318
|
+
}
|
|
1319
|
+
const existingLock = this.buildLocks.get(cacheKey);
|
|
1320
|
+
if (existingLock) {
|
|
1321
|
+
return existingLock;
|
|
1322
|
+
}
|
|
1323
|
+
const buildPromise = this.buildGraph(version, priority);
|
|
1324
|
+
this.buildLocks.set(cacheKey, buildPromise);
|
|
1325
|
+
try {
|
|
1326
|
+
const built = await buildPromise;
|
|
1327
|
+
this.graphCache.set(cacheKey, built);
|
|
1328
|
+
this.trimGraphCache();
|
|
1329
|
+
return built;
|
|
1330
|
+
}
|
|
1331
|
+
finally {
|
|
1332
|
+
this.buildLocks.delete(cacheKey);
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
async buildGraph(version, priority) {
|
|
1336
|
+
if (isUnobfuscatedVersion(version)) {
|
|
1337
|
+
return {
|
|
1338
|
+
version,
|
|
1339
|
+
priority,
|
|
1340
|
+
pairs: new Map(),
|
|
1341
|
+
warnings: [
|
|
1342
|
+
`Version ${version} is unobfuscated; mapping graph is empty (official names are final).`
|
|
1343
|
+
]
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1346
|
+
const graph = {
|
|
1347
|
+
version,
|
|
1348
|
+
priority,
|
|
1349
|
+
pairs: new Map(),
|
|
1350
|
+
warnings: []
|
|
1351
|
+
};
|
|
1352
|
+
const mojangLoad = await this.loadMojangPairs(version);
|
|
1353
|
+
graph.warnings.push(...mojangLoad.warnings);
|
|
1354
|
+
this.mergePairs(graph.pairs, mojangLoad.pairs, "mojang-client-mappings", mojangLoad.mappingArtifact);
|
|
1355
|
+
let tinyLoaded = false;
|
|
1356
|
+
for (const source of mappingSourceOrder(priority)) {
|
|
1357
|
+
const tinyLoad = source === "loom-cache"
|
|
1358
|
+
? await this.loadTinyPairsFromLoom(version)
|
|
1359
|
+
: await this.loadTinyPairsFromMaven(version);
|
|
1360
|
+
graph.warnings.push(...tinyLoad.warnings);
|
|
1361
|
+
if (tinyLoad.pairs.size === 0) {
|
|
1362
|
+
continue;
|
|
1363
|
+
}
|
|
1364
|
+
tinyLoaded = true;
|
|
1365
|
+
this.mergePairs(graph.pairs, tinyLoad.pairs, source, tinyLoad.mappingArtifact);
|
|
1366
|
+
break;
|
|
1367
|
+
}
|
|
1368
|
+
if (!tinyLoaded) {
|
|
1369
|
+
graph.warnings.push("No intermediary/yarn tiny mappings were found for this version.");
|
|
1370
|
+
}
|
|
1371
|
+
return graph;
|
|
1372
|
+
}
|
|
1373
|
+
mergePairs(target, source, pairSource, mappingArtifact) {
|
|
1374
|
+
for (const [key, incoming] of source.entries()) {
|
|
1375
|
+
const existing = target.get(key);
|
|
1376
|
+
if (!existing) {
|
|
1377
|
+
target.set(key, {
|
|
1378
|
+
index: incoming,
|
|
1379
|
+
source: pairSource,
|
|
1380
|
+
mappingArtifact
|
|
1381
|
+
});
|
|
1382
|
+
continue;
|
|
1383
|
+
}
|
|
1384
|
+
if (existing.source !== pairSource) {
|
|
1385
|
+
continue;
|
|
1386
|
+
}
|
|
1387
|
+
mergeDirectionIndexes(existing.index, incoming);
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
async loadMojangPairs(version) {
|
|
1391
|
+
const warnings = [];
|
|
1392
|
+
let metadata;
|
|
1393
|
+
try {
|
|
1394
|
+
metadata = await this.versionService.resolveVersionMappings(version);
|
|
1395
|
+
}
|
|
1396
|
+
catch (caughtError) {
|
|
1397
|
+
return {
|
|
1398
|
+
pairs: new Map(),
|
|
1399
|
+
warnings: [
|
|
1400
|
+
`Failed to resolve version metadata for "${version}": ${caughtError instanceof Error ? caughtError.message : String(caughtError)}`
|
|
1401
|
+
],
|
|
1402
|
+
mappingArtifact: `version:${version}`
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
const clientMappingsUrl = metadata.clientMappingsUrl ?? metadata.mappingsUrl;
|
|
1406
|
+
if (!clientMappingsUrl) {
|
|
1407
|
+
warnings.push(`Minecraft version "${version}" does not expose client mappings URL.`);
|
|
1408
|
+
return {
|
|
1409
|
+
pairs: new Map(),
|
|
1410
|
+
warnings,
|
|
1411
|
+
mappingArtifact: metadata.versionDetailUrl
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
const mappingsPath = join(this.config.cacheDir, "mappings", version, "client_mappings.txt");
|
|
1415
|
+
if (!existsSync(mappingsPath)) {
|
|
1416
|
+
await mkdir(dirname(mappingsPath), { recursive: true });
|
|
1417
|
+
const downloaded = await downloadToCache(clientMappingsUrl, mappingsPath, {
|
|
1418
|
+
fetchFn: this.fetchFn,
|
|
1419
|
+
retries: this.config.fetchRetries,
|
|
1420
|
+
timeoutMs: this.config.fetchTimeoutMs
|
|
1421
|
+
});
|
|
1422
|
+
if (!downloaded.ok || !downloaded.path) {
|
|
1423
|
+
warnings.push(`Failed to download client mappings from "${clientMappingsUrl}" (status: ${downloaded.statusCode ?? "unknown"}).`);
|
|
1424
|
+
return {
|
|
1425
|
+
pairs: new Map(),
|
|
1426
|
+
warnings,
|
|
1427
|
+
mappingArtifact: clientMappingsUrl
|
|
1428
|
+
};
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
try {
|
|
1432
|
+
const content = await readFile(mappingsPath, "utf8");
|
|
1433
|
+
return {
|
|
1434
|
+
pairs: parseClientMappings(content),
|
|
1435
|
+
warnings,
|
|
1436
|
+
mappingArtifact: clientMappingsUrl
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
catch (caughtError) {
|
|
1440
|
+
warnings.push(`Failed to parse client mappings for "${version}": ${caughtError instanceof Error ? caughtError.message : String(caughtError)}`);
|
|
1441
|
+
return {
|
|
1442
|
+
pairs: new Map(),
|
|
1443
|
+
warnings,
|
|
1444
|
+
mappingArtifact: clientMappingsUrl
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
async loadTinyPairsFromLoom(version) {
|
|
1449
|
+
const patterns = [".gradle/loom-cache/**/*.tiny", ".gradle/loom-cache/**/*.tinyv2"];
|
|
1450
|
+
const candidates = fastGlob.sync(patterns, {
|
|
1451
|
+
cwd: process.cwd(),
|
|
1452
|
+
absolute: true,
|
|
1453
|
+
onlyFiles: true
|
|
1454
|
+
});
|
|
1455
|
+
const byVersion = candidates
|
|
1456
|
+
.filter((p) => p.replaceAll("\\", "/").includes(`/${version}/`))
|
|
1457
|
+
.sort((left, right) => left.localeCompare(right));
|
|
1458
|
+
if (byVersion.length === 0) {
|
|
1459
|
+
return {
|
|
1460
|
+
pairs: new Map(),
|
|
1461
|
+
warnings: [`No Loom tiny mapping files matched version "${version}".`],
|
|
1462
|
+
mappingArtifact: "loom-cache:none"
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
1465
|
+
const merged = new Map();
|
|
1466
|
+
for (const path of byVersion) {
|
|
1467
|
+
try {
|
|
1468
|
+
const content = await readFile(path, "utf8");
|
|
1469
|
+
const parsed = parseTinyMappings(content);
|
|
1470
|
+
for (const [key, index] of parsed.entries()) {
|
|
1471
|
+
const existing = merged.get(key);
|
|
1472
|
+
if (!existing) {
|
|
1473
|
+
merged.set(key, index);
|
|
1474
|
+
}
|
|
1475
|
+
else {
|
|
1476
|
+
mergeDirectionIndexes(existing, index);
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
catch {
|
|
1481
|
+
// best effort: skip unreadable or invalid files
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
return {
|
|
1485
|
+
pairs: merged,
|
|
1486
|
+
warnings: [],
|
|
1487
|
+
mappingArtifact: byVersion[0]
|
|
1488
|
+
};
|
|
1489
|
+
}
|
|
1490
|
+
async loadTinyPairsFromMaven(version) {
|
|
1491
|
+
const warnings = [];
|
|
1492
|
+
const merged = new Map();
|
|
1493
|
+
const attemptedArtifacts = [];
|
|
1494
|
+
const repos = this.config.sourceRepos;
|
|
1495
|
+
const intermediaryUrls = [];
|
|
1496
|
+
const yarnUrls = [];
|
|
1497
|
+
for (const repo of repos) {
|
|
1498
|
+
const base = repo.replace(/\/+$/, "");
|
|
1499
|
+
intermediaryUrls.push(`${base}/net/fabricmc/intermediary/${version}/intermediary-${version}-v2.jar`, `${base}/net/fabricmc/intermediary/${version}/intermediary-${version}.jar`);
|
|
1500
|
+
const yarnCoordinates = await this.fetchYarnCoordinates(base, version);
|
|
1501
|
+
for (const coordinate of yarnCoordinates) {
|
|
1502
|
+
yarnUrls.push(`${base}/net/fabricmc/yarn/${coordinate}/yarn-${coordinate}-v2.jar`, `${base}/net/fabricmc/yarn/${coordinate}/yarn-${coordinate}.jar`);
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
const allUrls = [...intermediaryUrls, ...yarnUrls];
|
|
1506
|
+
for (const url of allUrls) {
|
|
1507
|
+
attemptedArtifacts.push(url);
|
|
1508
|
+
const downloaded = await downloadToCache(url, defaultDownloadPath(this.config.cacheDir, url), {
|
|
1509
|
+
fetchFn: this.fetchFn,
|
|
1510
|
+
retries: this.config.fetchRetries,
|
|
1511
|
+
timeoutMs: this.config.fetchTimeoutMs
|
|
1512
|
+
});
|
|
1513
|
+
if (!downloaded.ok || !downloaded.path) {
|
|
1514
|
+
continue;
|
|
1515
|
+
}
|
|
1516
|
+
const parsed = await this.parseTinyFromJar(downloaded.path);
|
|
1517
|
+
for (const [key, index] of parsed.entries()) {
|
|
1518
|
+
const existing = merged.get(key);
|
|
1519
|
+
if (!existing) {
|
|
1520
|
+
merged.set(key, index);
|
|
1521
|
+
}
|
|
1522
|
+
else {
|
|
1523
|
+
mergeDirectionIndexes(existing, index);
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
if (merged.size === 0) {
|
|
1528
|
+
warnings.push(`No Maven tiny mappings could be loaded for "${version}".`);
|
|
1529
|
+
}
|
|
1530
|
+
return {
|
|
1531
|
+
pairs: merged,
|
|
1532
|
+
warnings,
|
|
1533
|
+
mappingArtifact: attemptedArtifacts[0] ?? "maven:none"
|
|
1534
|
+
};
|
|
1535
|
+
}
|
|
1536
|
+
async parseTinyFromJar(jarPath) {
|
|
1537
|
+
const entries = await listJarEntries(jarPath);
|
|
1538
|
+
const tinyEntries = entries
|
|
1539
|
+
.filter((entry) => entry.toLowerCase().endsWith(".tiny") || entry.toLowerCase().endsWith(".tinyv2"))
|
|
1540
|
+
.sort((left, right) => left.localeCompare(right));
|
|
1541
|
+
const merged = new Map();
|
|
1542
|
+
for (const entry of tinyEntries) {
|
|
1543
|
+
try {
|
|
1544
|
+
const text = await readJarEntryAsUtf8(jarPath, entry);
|
|
1545
|
+
const parsed = parseTinyMappings(text);
|
|
1546
|
+
for (const [key, index] of parsed.entries()) {
|
|
1547
|
+
const existing = merged.get(key);
|
|
1548
|
+
if (!existing) {
|
|
1549
|
+
merged.set(key, index);
|
|
1550
|
+
}
|
|
1551
|
+
else {
|
|
1552
|
+
mergeDirectionIndexes(existing, index);
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
catch {
|
|
1557
|
+
// skip malformed tiny entries
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
return merged;
|
|
1561
|
+
}
|
|
1562
|
+
async fetchYarnCoordinates(repoBase, version) {
|
|
1563
|
+
const metadataUrl = `${repoBase}/net/fabricmc/yarn/maven-metadata.xml`;
|
|
1564
|
+
try {
|
|
1565
|
+
const response = await this.fetchFn(metadataUrl);
|
|
1566
|
+
if (!response.ok) {
|
|
1567
|
+
return [];
|
|
1568
|
+
}
|
|
1569
|
+
const xml = await response.text();
|
|
1570
|
+
const versions = [...xml.matchAll(/<version>([^<]+)<\/version>/g)]
|
|
1571
|
+
.map((match) => match[1]?.trim() ?? "")
|
|
1572
|
+
.filter((value) => value.startsWith(`${version}+build.`));
|
|
1573
|
+
const sorted = versions.sort((left, right) => {
|
|
1574
|
+
const leftBuild = Number.parseInt(left.split("+build.")[1] ?? "0", 10);
|
|
1575
|
+
const rightBuild = Number.parseInt(right.split("+build.")[1] ?? "0", 10);
|
|
1576
|
+
return rightBuild - leftBuild;
|
|
1577
|
+
});
|
|
1578
|
+
if (sorted.length > 0) {
|
|
1579
|
+
return sorted.slice(0, 3);
|
|
1580
|
+
}
|
|
1581
|
+
return [version];
|
|
1582
|
+
}
|
|
1583
|
+
catch {
|
|
1584
|
+
return [version];
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
trimGraphCache() {
|
|
1588
|
+
const maxEntries = Math.max(1, this.config.maxMappingGraphCache ?? 16);
|
|
1589
|
+
while (this.graphCache.size > maxEntries) {
|
|
1590
|
+
const oldestKey = this.graphCache.keys().next().value;
|
|
1591
|
+
if (!oldestKey) {
|
|
1592
|
+
return;
|
|
1593
|
+
}
|
|
1594
|
+
this.graphCache.delete(oldestKey);
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
// ---------------------------------------------------------------------------
|
|
1599
|
+
// Standalone: Tiny v2 mapping file resolution for remapping
|
|
1600
|
+
// ---------------------------------------------------------------------------
|
|
1601
|
+
const FABRIC_MAVEN = "https://maven.fabricmc.net";
|
|
1602
|
+
async function fetchYarnCoordinatesStandalone(version, fetchFn = globalThis.fetch) {
|
|
1603
|
+
const metadataUrl = `${FABRIC_MAVEN}/net/fabricmc/yarn/maven-metadata.xml`;
|
|
1604
|
+
try {
|
|
1605
|
+
const response = await fetchFn(metadataUrl);
|
|
1606
|
+
if (!response.ok) {
|
|
1607
|
+
return [];
|
|
1608
|
+
}
|
|
1609
|
+
const xml = await response.text();
|
|
1610
|
+
const versions = [...xml.matchAll(/<version>([^<]+)<\/version>/g)]
|
|
1611
|
+
.map((match) => match[1]?.trim() ?? "")
|
|
1612
|
+
.filter((value) => value.startsWith(`${version}+build.`));
|
|
1613
|
+
const sorted = versions.sort((left, right) => {
|
|
1614
|
+
const leftBuild = Number.parseInt(left.split("+build.")[1] ?? "0", 10);
|
|
1615
|
+
const rightBuild = Number.parseInt(right.split("+build.")[1] ?? "0", 10);
|
|
1616
|
+
return rightBuild - leftBuild;
|
|
1617
|
+
});
|
|
1618
|
+
return sorted.length > 0 ? sorted.slice(0, 3) : [];
|
|
1619
|
+
}
|
|
1620
|
+
catch {
|
|
1621
|
+
return [];
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
async function extractTinyFromJar(jarPath, outputPath) {
|
|
1625
|
+
const entries = await listJarEntries(jarPath);
|
|
1626
|
+
const tinyEntry = entries.find((entry) => entry === "mappings/mappings.tiny" || entry.toLowerCase().endsWith(".tiny"));
|
|
1627
|
+
if (!tinyEntry) {
|
|
1628
|
+
return false;
|
|
1629
|
+
}
|
|
1630
|
+
const content = await readJarEntryAsUtf8(jarPath, tinyEntry);
|
|
1631
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
1632
|
+
await writeFile(outputPath, content, "utf8");
|
|
1633
|
+
return true;
|
|
1634
|
+
}
|
|
1635
|
+
/**
|
|
1636
|
+
* Resolve and cache a Tiny v2 mapping file for the given Minecraft version.
|
|
1637
|
+
*
|
|
1638
|
+
* @param version - Minecraft version (e.g. "1.20.4")
|
|
1639
|
+
* @param mapping - "intermediary" or "yarn"
|
|
1640
|
+
* @param cacheDir - The application cache directory
|
|
1641
|
+
* @param fetchFn - Optional fetch implementation for testing
|
|
1642
|
+
* @returns Path to the extracted Tiny v2 file
|
|
1643
|
+
*/
|
|
1644
|
+
export async function resolveTinyMappingFile(version, mapping, cacheDir, fetchFn) {
|
|
1645
|
+
const cachedTiny = join(cacheDir, "mappings", `${version}-${mapping}.tiny`);
|
|
1646
|
+
if (existsSync(cachedTiny)) {
|
|
1647
|
+
return cachedTiny;
|
|
1648
|
+
}
|
|
1649
|
+
const effectiveFetch = fetchFn ?? globalThis.fetch;
|
|
1650
|
+
if (mapping === "intermediary") {
|
|
1651
|
+
const url = `${FABRIC_MAVEN}/net/fabricmc/intermediary/${version}/intermediary-${version}-v2.jar`;
|
|
1652
|
+
const jarDest = defaultDownloadPath(cacheDir, url);
|
|
1653
|
+
const downloaded = await downloadToCache(url, jarDest, {
|
|
1654
|
+
fetchFn: effectiveFetch,
|
|
1655
|
+
retries: 2,
|
|
1656
|
+
timeoutMs: 30_000
|
|
1657
|
+
});
|
|
1658
|
+
if (!downloaded.ok || !downloaded.path) {
|
|
1659
|
+
throw createError({
|
|
1660
|
+
code: ERROR_CODES.MAPPING_UNAVAILABLE,
|
|
1661
|
+
message: `Failed to download intermediary mappings for ${version}.`,
|
|
1662
|
+
details: { version, url }
|
|
1663
|
+
});
|
|
1664
|
+
}
|
|
1665
|
+
const extracted = await extractTinyFromJar(downloaded.path, cachedTiny);
|
|
1666
|
+
if (!extracted) {
|
|
1667
|
+
throw createError({
|
|
1668
|
+
code: ERROR_CODES.MAPPING_UNAVAILABLE,
|
|
1669
|
+
message: `No tiny mapping found in intermediary JAR for ${version}.`,
|
|
1670
|
+
details: { version, jarPath: downloaded.path }
|
|
1671
|
+
});
|
|
1672
|
+
}
|
|
1673
|
+
return cachedTiny;
|
|
1674
|
+
}
|
|
1675
|
+
// yarn
|
|
1676
|
+
const yarnCoordinates = await fetchYarnCoordinatesStandalone(version, effectiveFetch);
|
|
1677
|
+
if (yarnCoordinates.length === 0) {
|
|
1678
|
+
throw createError({
|
|
1679
|
+
code: ERROR_CODES.MAPPING_UNAVAILABLE,
|
|
1680
|
+
message: `No yarn builds found for Minecraft ${version}.`,
|
|
1681
|
+
details: { version }
|
|
1682
|
+
});
|
|
1683
|
+
}
|
|
1684
|
+
for (const coordinate of yarnCoordinates) {
|
|
1685
|
+
const url = `${FABRIC_MAVEN}/net/fabricmc/yarn/${coordinate}/yarn-${coordinate}-v2.jar`;
|
|
1686
|
+
const jarDest = defaultDownloadPath(cacheDir, url);
|
|
1687
|
+
const downloaded = await downloadToCache(url, jarDest, {
|
|
1688
|
+
fetchFn: effectiveFetch,
|
|
1689
|
+
retries: 2,
|
|
1690
|
+
timeoutMs: 30_000
|
|
1691
|
+
});
|
|
1692
|
+
if (!downloaded.ok || !downloaded.path) {
|
|
1693
|
+
continue;
|
|
1694
|
+
}
|
|
1695
|
+
const extracted = await extractTinyFromJar(downloaded.path, cachedTiny);
|
|
1696
|
+
if (extracted) {
|
|
1697
|
+
return cachedTiny;
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
throw createError({
|
|
1701
|
+
code: ERROR_CODES.MAPPING_UNAVAILABLE,
|
|
1702
|
+
message: `Failed to download yarn mappings for ${version}.`,
|
|
1703
|
+
details: { version, triedCoordinates: yarnCoordinates }
|
|
1704
|
+
});
|
|
1705
|
+
}
|
|
1706
|
+
//# sourceMappingURL=mapping-service.js.map
|