@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
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { existsSync, mkdirSync,
|
|
2
|
+
import { copyFileSync, existsSync, mkdirSync, rmSync, statSync } from "node:fs";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
|
-
import { createError, ERROR_CODES } from "./errors.js";
|
|
5
|
+
import { createError, ERROR_CODES, isAppError } from "./errors.js";
|
|
6
6
|
import { log } from "./logger.js";
|
|
7
7
|
import { resolveTinyMappingFile } from "./mapping-service.js";
|
|
8
8
|
import { resolveMojangTinyFile } from "./mojang-tiny-mapping-service.js";
|
|
9
9
|
import { analyzeModJar } from "./mod-analyzer.js";
|
|
10
10
|
import { normalizePathForHost } from "./path-converter.js";
|
|
11
|
+
import { listJarEntries, readJarEntryAsBuffer } from "./source-jar-reader.js";
|
|
11
12
|
import { remapJar } from "./tiny-remapper-service.js";
|
|
12
13
|
import { resolveTinyRemapperJar } from "./tiny-remapper-resolver.js";
|
|
13
14
|
function normalizeTargetNamespace(target) {
|
|
14
15
|
return target === "yarn" ? "yarn" : "mojang";
|
|
15
16
|
}
|
|
16
|
-
function
|
|
17
|
+
function defaultSourceNamespaceForLoader(loader) {
|
|
17
18
|
if (loader === "fabric" || loader === "quilt") {
|
|
18
19
|
return "intermediary";
|
|
19
20
|
}
|
|
@@ -35,6 +36,81 @@ function extractMinecraftVersion(dependencies) {
|
|
|
35
36
|
const match = mcDep.versionRange.match(/(\d+\.\d+(?:\.\d+)?)/);
|
|
36
37
|
return match?.[1];
|
|
37
38
|
}
|
|
39
|
+
function countMatches(input, pattern) {
|
|
40
|
+
const flags = pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`;
|
|
41
|
+
const globalPattern = new RegExp(pattern.source, flags);
|
|
42
|
+
let count = 0;
|
|
43
|
+
while (globalPattern.exec(input)) {
|
|
44
|
+
count += 1;
|
|
45
|
+
}
|
|
46
|
+
return count;
|
|
47
|
+
}
|
|
48
|
+
async function detectFabricLikeInputNamespace(inputJar) {
|
|
49
|
+
const warnings = [];
|
|
50
|
+
const classEntries = (await listJarEntries(inputJar))
|
|
51
|
+
.filter((entry) => entry.endsWith(".class"))
|
|
52
|
+
.slice(0, 24);
|
|
53
|
+
if (classEntries.length === 0) {
|
|
54
|
+
warnings.push("Could not inspect class entries to detect input mapping; assuming intermediary.");
|
|
55
|
+
return {
|
|
56
|
+
fromNamespace: "intermediary",
|
|
57
|
+
warnings
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
let mojangScore = 0;
|
|
61
|
+
let intermediaryScore = 0;
|
|
62
|
+
for (const entry of classEntries) {
|
|
63
|
+
let text = "";
|
|
64
|
+
try {
|
|
65
|
+
text = (await readJarEntryAsBuffer(inputJar, entry)).toString("latin1");
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
mojangScore += countMatches(text, /net\/minecraft\/(?:advancements|client|commands|core|data|gametest|nbt|network|recipe|resources|server|sounds|stats|tags|util|world)\//g) * 3;
|
|
71
|
+
intermediaryScore += countMatches(text, /net\/minecraft\/class_\d+/g) * 3;
|
|
72
|
+
intermediaryScore += countMatches(text, /\b(?:method|field)_\d+\b/g);
|
|
73
|
+
}
|
|
74
|
+
if (mojangScore > intermediaryScore && mojangScore > 0) {
|
|
75
|
+
return {
|
|
76
|
+
fromNamespace: "mojang",
|
|
77
|
+
warnings
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
if (intermediaryScore > mojangScore && intermediaryScore > 0) {
|
|
81
|
+
return {
|
|
82
|
+
fromNamespace: "intermediary",
|
|
83
|
+
warnings
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
warnings.push("Could not confidently detect whether the input jar uses intermediary or mojang names; assuming intermediary.");
|
|
87
|
+
return {
|
|
88
|
+
fromNamespace: "intermediary",
|
|
89
|
+
warnings
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
async function detectInputNamespaceForLoader(inputJar, loader) {
|
|
93
|
+
if (loader === "fabric" || loader === "quilt") {
|
|
94
|
+
return detectFabricLikeInputNamespace(inputJar);
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
fromNamespace: defaultSourceNamespaceForLoader(loader),
|
|
98
|
+
warnings: []
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function resolveOutputJarPath(input, normalizedInput, modId, modVersion) {
|
|
102
|
+
const defaultOutputName = `${modId ?? "mod"}-${modVersion ?? "0"}-${input.targetMapping}.jar`;
|
|
103
|
+
return input.outputJar
|
|
104
|
+
? normalizePathForHost(input.outputJar, undefined, "outputJar")
|
|
105
|
+
: join(dirname(normalizedInput), defaultOutputName);
|
|
106
|
+
}
|
|
107
|
+
function copyJarToDestination(sourceJar, destinationJar) {
|
|
108
|
+
if (sourceJar === destinationJar) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
mkdirSync(dirname(destinationJar), { recursive: true });
|
|
112
|
+
copyFileSync(sourceJar, destinationJar);
|
|
113
|
+
}
|
|
38
114
|
function buildCacheKey(inputJar, fromNamespace, targetNamespace, mcVersion) {
|
|
39
115
|
const stat = statSync(inputJar, { throwIfNoEntry: false });
|
|
40
116
|
const signature = stat ? `${stat.mtimeMs}:${stat.size}` : "unknown";
|
|
@@ -71,7 +147,9 @@ export async function remapModJar(input, config) {
|
|
|
71
147
|
details: { inputJar: normalizedInput }
|
|
72
148
|
});
|
|
73
149
|
}
|
|
74
|
-
const
|
|
150
|
+
const namespaceDetection = await detectInputNamespaceForLoader(normalizedInput, analysis.loader);
|
|
151
|
+
warnings.push(...namespaceDetection.warnings);
|
|
152
|
+
const fromNamespace = namespaceDetection.fromNamespace;
|
|
75
153
|
// 3. Determine MC version
|
|
76
154
|
const mcVersion = input.mcVersion ?? extractMinecraftVersion(analysis.dependencies);
|
|
77
155
|
if (!mcVersion) {
|
|
@@ -85,21 +163,32 @@ export async function remapModJar(input, config) {
|
|
|
85
163
|
}
|
|
86
164
|
});
|
|
87
165
|
}
|
|
166
|
+
const outputJar = resolveOutputJarPath(input, normalizedInput, analysis.modId, analysis.modVersion);
|
|
88
167
|
// 4. Check cache after mapping context is known
|
|
89
168
|
const cacheKey = buildCacheKey(normalizedInput, fromNamespace, resolvedTargetNamespace, mcVersion);
|
|
90
169
|
const cacheDir = join(config.cacheDir, "remapped-mods");
|
|
91
170
|
mkdirSync(cacheDir, { recursive: true });
|
|
92
171
|
const cachedOutput = join(cacheDir, `${cacheKey}.jar`);
|
|
93
172
|
if (existsSync(cachedOutput)) {
|
|
94
|
-
const
|
|
95
|
-
?
|
|
173
|
+
const cacheHitOutputJar = input.outputJar
|
|
174
|
+
? outputJar
|
|
96
175
|
: cachedOutput;
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
176
|
+
copyJarToDestination(cachedOutput, cacheHitOutputJar);
|
|
177
|
+
log("info", "remap.cache-hit", { inputJar: normalizedInput, outputJar: cacheHitOutputJar });
|
|
178
|
+
return {
|
|
179
|
+
outputJar: cacheHitOutputJar,
|
|
180
|
+
mcVersion,
|
|
181
|
+
fromMapping: fromNamespace,
|
|
182
|
+
targetMapping: input.targetMapping,
|
|
183
|
+
resolvedTargetNamespace,
|
|
184
|
+
durationMs: Date.now() - startedAt,
|
|
185
|
+
warnings: [...warnings, "Result served from cache."]
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
if (fromNamespace === resolvedTargetNamespace) {
|
|
189
|
+
copyJarToDestination(normalizedInput, outputJar);
|
|
190
|
+
copyJarToDestination(normalizedInput, cachedOutput);
|
|
191
|
+
warnings.push(`Input JAR already uses ${fromNamespace} names; output is a copy of the input JAR.`);
|
|
103
192
|
return {
|
|
104
193
|
outputJar,
|
|
105
194
|
mcVersion,
|
|
@@ -107,9 +196,23 @@ export async function remapModJar(input, config) {
|
|
|
107
196
|
targetMapping: input.targetMapping,
|
|
108
197
|
resolvedTargetNamespace,
|
|
109
198
|
durationMs: Date.now() - startedAt,
|
|
110
|
-
warnings
|
|
199
|
+
warnings
|
|
111
200
|
};
|
|
112
201
|
}
|
|
202
|
+
if (fromNamespace === "mojang" && resolvedTargetNamespace === "yarn") {
|
|
203
|
+
throw createError({
|
|
204
|
+
code: ERROR_CODES.REMAP_FAILED,
|
|
205
|
+
message: "Mojang-mapped Fabric/Quilt input jars cannot be remapped to yarn with the available mapping files.",
|
|
206
|
+
details: {
|
|
207
|
+
inputJar: normalizedInput,
|
|
208
|
+
mcVersion,
|
|
209
|
+
fromMapping: fromNamespace,
|
|
210
|
+
targetMapping: input.targetMapping,
|
|
211
|
+
resolvedTargetNamespace,
|
|
212
|
+
nextAction: 'Use targetMapping="mojang" for Mojang-mapped inputs, or rebuild the mod against intermediary mappings before requesting yarn output.'
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
113
216
|
// 5. Resolve tiny-remapper
|
|
114
217
|
const tinyRemapperJar = await resolveTinyRemapperJar(config.cacheDir, config.tinyRemapperJarPath);
|
|
115
218
|
// 6. Resolve mapping file and remap
|
|
@@ -125,30 +228,39 @@ export async function remapModJar(input, config) {
|
|
|
125
228
|
toNamespace = "mojang";
|
|
126
229
|
warnings.push(...mojangTiny.warnings);
|
|
127
230
|
}
|
|
128
|
-
// 7. Determine output path
|
|
129
|
-
const modId = analysis.modId ?? "mod";
|
|
130
|
-
const modVersion = analysis.modVersion ?? "0";
|
|
131
|
-
const defaultOutputName = `${modId}-${modVersion}-${input.targetMapping}.jar`;
|
|
132
|
-
const outputJar = input.outputJar
|
|
133
|
-
? normalizePathForHost(input.outputJar, undefined, "outputJar")
|
|
134
|
-
: join(dirname(normalizedInput), defaultOutputName);
|
|
135
231
|
mkdirSync(dirname(outputJar), { recursive: true });
|
|
136
232
|
// 8. Use temporary directory for intermediate work
|
|
137
233
|
const tempDir = join(tmpdir(), `mcp-remap-${cacheKey.slice(0, 12)}`);
|
|
138
234
|
mkdirSync(tempDir, { recursive: true });
|
|
139
235
|
try {
|
|
140
236
|
const tempOutput = join(tempDir, "remapped.jar");
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
237
|
+
try {
|
|
238
|
+
await remapJar(tinyRemapperJar, {
|
|
239
|
+
inputJar: normalizedInput,
|
|
240
|
+
outputJar: tempOutput,
|
|
241
|
+
mappingsFile,
|
|
242
|
+
fromNamespace,
|
|
243
|
+
toNamespace,
|
|
244
|
+
timeoutMs: config.remapTimeoutMs,
|
|
245
|
+
maxMemoryMb: config.remapMaxMemoryMb
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
catch (caughtError) {
|
|
249
|
+
if (isAppError(caughtError)) {
|
|
250
|
+
throw createError({
|
|
251
|
+
code: caughtError.code,
|
|
252
|
+
message: caughtError.message,
|
|
253
|
+
details: {
|
|
254
|
+
...(caughtError.details ?? {}),
|
|
255
|
+
fromMapping: fromNamespace,
|
|
256
|
+
targetMapping: input.targetMapping,
|
|
257
|
+
resolvedTargetNamespace
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
throw caughtError;
|
|
262
|
+
}
|
|
150
263
|
// Copy to final destination and cache
|
|
151
|
-
const { copyFileSync } = await import("node:fs");
|
|
152
264
|
copyFileSync(tempOutput, outputJar);
|
|
153
265
|
if (outputJar !== cachedOutput) {
|
|
154
266
|
mkdirSync(dirname(cachedOutput), { recursive: true });
|
|
@@ -30,13 +30,13 @@ function parseProguardMappings(text) {
|
|
|
30
30
|
const classMatch = /^(.+?)\s+->\s+(.+):$/.exec(line);
|
|
31
31
|
if (classMatch) {
|
|
32
32
|
const mojangFqn = normalizeFqn(classMatch[1] ?? "");
|
|
33
|
-
const
|
|
34
|
-
if (!mojangFqn || !
|
|
33
|
+
const obfuscatedInternal = normalizeInternalName(classMatch[2] ?? "");
|
|
34
|
+
if (!mojangFqn || !obfuscatedInternal) {
|
|
35
35
|
currentClass = undefined;
|
|
36
36
|
continue;
|
|
37
37
|
}
|
|
38
38
|
currentClass = mojangFqn;
|
|
39
|
-
classes.push({ mojangFqn,
|
|
39
|
+
classes.push({ mojangFqn, obfuscatedInternal });
|
|
40
40
|
continue;
|
|
41
41
|
}
|
|
42
42
|
if (!currentClass) {
|
|
@@ -47,14 +47,14 @@ function parseProguardMappings(text) {
|
|
|
47
47
|
continue;
|
|
48
48
|
}
|
|
49
49
|
const leftRaw = stripLineInfo(line.slice(0, arrowIndex));
|
|
50
|
-
const
|
|
51
|
-
if (!leftRaw || !
|
|
50
|
+
const obfuscatedName = line.slice(arrowIndex + 4).trim();
|
|
51
|
+
if (!leftRaw || !obfuscatedName) {
|
|
52
52
|
continue;
|
|
53
53
|
}
|
|
54
54
|
members.push({
|
|
55
55
|
ownerMojangFqn: currentClass,
|
|
56
56
|
leftSignature: leftRaw,
|
|
57
|
-
|
|
57
|
+
obfuscatedName
|
|
58
58
|
});
|
|
59
59
|
}
|
|
60
60
|
return { classes, members };
|
|
@@ -164,8 +164,8 @@ function normalizeMemberMappings(rawMappings, classMap, warnings) {
|
|
|
164
164
|
const seen = new Set();
|
|
165
165
|
for (const mapping of rawMappings) {
|
|
166
166
|
for (const member of mapping.members) {
|
|
167
|
-
const
|
|
168
|
-
if (!
|
|
167
|
+
const ownerObfuscated = classMap.get(normalizeFqn(member.ownerMojangFqn));
|
|
168
|
+
if (!ownerObfuscated) {
|
|
169
169
|
warnings.push(`Skipping member mapping for "${member.ownerMojangFqn}" because class mapping is missing.`);
|
|
170
170
|
continue;
|
|
171
171
|
}
|
|
@@ -183,7 +183,7 @@ function normalizeMemberMappings(rawMappings, classMap, warnings) {
|
|
|
183
183
|
record = {
|
|
184
184
|
kind: "m",
|
|
185
185
|
descriptor,
|
|
186
|
-
|
|
186
|
+
obfuscatedName: member.obfuscatedName,
|
|
187
187
|
mojangName
|
|
188
188
|
};
|
|
189
189
|
}
|
|
@@ -203,19 +203,19 @@ function normalizeMemberMappings(rawMappings, classMap, warnings) {
|
|
|
203
203
|
record = {
|
|
204
204
|
kind: "f",
|
|
205
205
|
descriptor,
|
|
206
|
-
|
|
206
|
+
obfuscatedName: member.obfuscatedName,
|
|
207
207
|
mojangName
|
|
208
208
|
};
|
|
209
209
|
}
|
|
210
|
-
const dedupeKey = `${
|
|
210
|
+
const dedupeKey = `${ownerObfuscated}|${record.kind}|${record.obfuscatedName}|` +
|
|
211
211
|
`${record.mojangName}|${record.descriptor}`;
|
|
212
212
|
if (seen.has(dedupeKey)) {
|
|
213
213
|
continue;
|
|
214
214
|
}
|
|
215
215
|
seen.add(dedupeKey);
|
|
216
|
-
const list = membersByOwner.get(
|
|
216
|
+
const list = membersByOwner.get(ownerObfuscated) ?? [];
|
|
217
217
|
list.push(record);
|
|
218
|
-
membersByOwner.set(
|
|
218
|
+
membersByOwner.set(ownerObfuscated, list);
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
221
|
return membersByOwner;
|
|
@@ -225,38 +225,38 @@ function mergeClasses(rawMappings, warnings) {
|
|
|
225
225
|
for (const mapping of rawMappings) {
|
|
226
226
|
for (const clazz of mapping.classes) {
|
|
227
227
|
const mojang = normalizeFqn(clazz.mojangFqn);
|
|
228
|
-
const
|
|
228
|
+
const obfuscated = normalizeInternalName(clazz.obfuscatedInternal);
|
|
229
229
|
const existing = classMap.get(mojang);
|
|
230
|
-
if (existing && existing !==
|
|
231
|
-
warnings.push(`Conflicting class mapping for "${mojang}" (${existing} vs ${
|
|
230
|
+
if (existing && existing !== obfuscated) {
|
|
231
|
+
warnings.push(`Conflicting class mapping for "${mojang}" (${existing} vs ${obfuscated}); keeping first.`);
|
|
232
232
|
continue;
|
|
233
233
|
}
|
|
234
|
-
classMap.set(mojang,
|
|
234
|
+
classMap.set(mojang, obfuscated);
|
|
235
235
|
}
|
|
236
236
|
}
|
|
237
237
|
return classMap;
|
|
238
238
|
}
|
|
239
239
|
function renderTinyV2(classMap, membersByOwner) {
|
|
240
240
|
const classEntries = [...classMap.entries()]
|
|
241
|
-
.map(([mojangFqn,
|
|
242
|
-
|
|
241
|
+
.map(([mojangFqn, obfuscatedInternal]) => ({
|
|
242
|
+
obfuscatedInternal,
|
|
243
243
|
mojangInternal: mojangFqn.replace(/\./g, "/")
|
|
244
244
|
}))
|
|
245
|
-
.sort((left, right) => left.
|
|
246
|
-
const lines = ["tiny\t2\t0\
|
|
245
|
+
.sort((left, right) => left.obfuscatedInternal.localeCompare(right.obfuscatedInternal));
|
|
246
|
+
const lines = ["tiny\t2\t0\tobfuscated\tmojang"];
|
|
247
247
|
for (const entry of classEntries) {
|
|
248
|
-
lines.push(`c\t${entry.
|
|
249
|
-
const members = [...(membersByOwner.get(entry.
|
|
248
|
+
lines.push(`c\t${entry.obfuscatedInternal}\t${entry.mojangInternal}`);
|
|
249
|
+
const members = [...(membersByOwner.get(entry.obfuscatedInternal) ?? [])].sort((left, right) => {
|
|
250
250
|
if (left.kind !== right.kind) {
|
|
251
251
|
return left.kind.localeCompare(right.kind);
|
|
252
252
|
}
|
|
253
|
-
if (left.
|
|
254
|
-
return left.
|
|
253
|
+
if (left.obfuscatedName !== right.obfuscatedName) {
|
|
254
|
+
return left.obfuscatedName.localeCompare(right.obfuscatedName);
|
|
255
255
|
}
|
|
256
256
|
return left.descriptor.localeCompare(right.descriptor);
|
|
257
257
|
});
|
|
258
258
|
for (const member of members) {
|
|
259
|
-
lines.push(`\t${member.kind}\t${member.descriptor}\t${member.
|
|
259
|
+
lines.push(`\t${member.kind}\t${member.descriptor}\t${member.obfuscatedName}\t${member.mojangName}`);
|
|
260
260
|
}
|
|
261
261
|
}
|
|
262
262
|
return `${lines.join("\n")}\n`;
|
package/dist/path-resolver.js
CHANGED
|
@@ -2,12 +2,35 @@ import { realpathSync, statSync } from "node:fs";
|
|
|
2
2
|
import { extname, resolve } from "node:path";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
4
|
import { normalizePathForHost } from "./path-converter.js";
|
|
5
|
-
import { createError, ERROR_CODES } from "./errors.js";
|
|
5
|
+
import { createError, ERROR_CODES, isAppError } from "./errors.js";
|
|
6
6
|
const INVALID_ENTRY = /(^|\/|\\)\.\.(\/|\\|$)/;
|
|
7
7
|
export function normalizeJarPath(jarPath) {
|
|
8
8
|
const normalizedInput = normalizePathForHost(jarPath, undefined, "jarPath");
|
|
9
9
|
const absolute = resolve(normalizedInput);
|
|
10
|
-
|
|
10
|
+
let stats;
|
|
11
|
+
try {
|
|
12
|
+
stats = statSync(absolute, { throwIfNoEntry: false });
|
|
13
|
+
}
|
|
14
|
+
catch (cause) {
|
|
15
|
+
if (isAppError(cause)) {
|
|
16
|
+
throw cause;
|
|
17
|
+
}
|
|
18
|
+
throw createError({
|
|
19
|
+
code: ERROR_CODES.JAR_NOT_FOUND,
|
|
20
|
+
message: `Could not access jar "${normalizedInput}".`,
|
|
21
|
+
details: {
|
|
22
|
+
jarPath: normalizedInput,
|
|
23
|
+
reason: cause instanceof Error ? cause.message : String(cause)
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
if (!stats) {
|
|
28
|
+
throw createError({
|
|
29
|
+
code: ERROR_CODES.JAR_NOT_FOUND,
|
|
30
|
+
message: `Jar not found: "${normalizedInput}".`,
|
|
31
|
+
details: { jarPath: normalizedInput }
|
|
32
|
+
});
|
|
33
|
+
}
|
|
11
34
|
if (!stats.isFile()) {
|
|
12
35
|
throw createError({
|
|
13
36
|
code: ERROR_CODES.JAR_NOT_FOUND,
|
|
@@ -22,8 +45,22 @@ export function normalizeJarPath(jarPath) {
|
|
|
22
45
|
details: { jarPath: normalizedInput }
|
|
23
46
|
});
|
|
24
47
|
}
|
|
25
|
-
|
|
26
|
-
|
|
48
|
+
try {
|
|
49
|
+
return realpathSync(absolute);
|
|
50
|
+
}
|
|
51
|
+
catch (cause) {
|
|
52
|
+
if (isAppError(cause)) {
|
|
53
|
+
throw cause;
|
|
54
|
+
}
|
|
55
|
+
throw createError({
|
|
56
|
+
code: ERROR_CODES.JAR_NOT_FOUND,
|
|
57
|
+
message: `Could not resolve jar "${normalizedInput}".`,
|
|
58
|
+
details: {
|
|
59
|
+
jarPath: normalizedInput,
|
|
60
|
+
reason: cause instanceof Error ? cause.message : String(cause)
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
27
64
|
}
|
|
28
65
|
export function resolveJarPathWithSymlinkCheck(jarPath) {
|
|
29
66
|
const resolvedPath = normalizeJarPath(jarPath);
|
|
@@ -3,6 +3,8 @@ import type { Config } from "./types.js";
|
|
|
3
3
|
export type GetRegistryDataInput = {
|
|
4
4
|
version: string;
|
|
5
5
|
registry?: string;
|
|
6
|
+
includeData?: boolean;
|
|
7
|
+
maxEntriesPerRegistry?: number;
|
|
6
8
|
};
|
|
7
9
|
export type RegistryEntry = {
|
|
8
10
|
protocol_id: number;
|
|
@@ -15,8 +17,11 @@ export type GetRegistryDataOutput = {
|
|
|
15
17
|
version: string;
|
|
16
18
|
registry?: string;
|
|
17
19
|
registries?: string[];
|
|
18
|
-
data
|
|
20
|
+
data?: Record<string, RegistryData> | RegistryData;
|
|
19
21
|
entryCount: number;
|
|
22
|
+
returnedEntryCount?: number;
|
|
23
|
+
registryEntryCounts?: Record<string, number>;
|
|
24
|
+
dataTruncated?: boolean;
|
|
20
25
|
warnings: string[];
|
|
21
26
|
};
|
|
22
27
|
export declare class RegistryService {
|
|
@@ -26,4 +31,8 @@ export declare class RegistryService {
|
|
|
26
31
|
constructor(config: Config, versionService: VersionService);
|
|
27
32
|
getRegistryData(input: GetRegistryDataInput): Promise<GetRegistryDataOutput>;
|
|
28
33
|
private loadRegistries;
|
|
34
|
+
private loadExistingRegistries;
|
|
35
|
+
private readRegistryFileOrThrow;
|
|
36
|
+
private isInvalidRegistrySnapshot;
|
|
37
|
+
private cacheRegistries;
|
|
29
38
|
}
|