@adhisang/minecraft-modding-mcp 6.3.0 → 7.0.0-rc.1
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 +90 -0
- package/README.md +13 -3
- package/dist/cache-policy.d.ts +71 -0
- package/dist/cache-policy.js +83 -0
- package/dist/cache-registry.js +45 -8
- package/dist/cli.js +74 -3
- package/dist/compat-stdio-transport.d.ts +1 -1
- package/dist/compat-stdio-transport.js +13 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.js +8 -2
- package/dist/decompiler/vineflower.d.ts +1 -0
- package/dist/decompiler/vineflower.js +8 -5
- package/dist/entry-tools/analyze-mod-service.d.ts +70 -136
- package/dist/entry-tools/analyze-symbol-service.d.ts +112 -150
- package/dist/entry-tools/compare-minecraft-service.d.ts +59 -145
- package/dist/entry-tools/entry-tool-schema.d.ts +38 -4
- package/dist/entry-tools/entry-tool-schema.js +4 -1
- package/dist/entry-tools/inspect-minecraft/internal.d.ts +235 -799
- package/dist/entry-tools/inspect-minecraft/internal.js +50 -13
- package/dist/entry-tools/inspect-minecraft-service.d.ts +372 -1736
- package/dist/entry-tools/manage-cache-service.d.ts +81 -91
- package/dist/entry-tools/validate-project/cases/mixin.js +26 -6
- package/dist/entry-tools/validate-project/cases/project-summary.d.ts +7 -7
- package/dist/entry-tools/validate-project-service.d.ts +164 -592
- package/dist/entry-tools/verify-mixin-target-service.d.ts +3 -19
- package/dist/entry-tools/verify-mixin-target-service.js +19 -1
- package/dist/era-classifier.d.ts +161 -0
- package/dist/era-classifier.js +292 -0
- package/dist/error-mapping.d.ts +76 -0
- package/dist/error-mapping.js +116 -8
- package/dist/index.d.ts +42 -4
- package/dist/index.js +636 -473
- package/dist/java-process.d.ts +2 -0
- package/dist/java-process.js +22 -2
- package/dist/json-rpc-framing.d.ts +77 -1
- package/dist/json-rpc-framing.js +249 -13
- package/dist/mapping/loaders/tiny-loom-selection.d.ts +88 -0
- package/dist/mapping/loaders/tiny-loom-selection.js +223 -0
- package/dist/mapping/loaders/tiny-loom.js +45 -33
- package/dist/mapping/loaders/tiny-maven.js +6 -11
- package/dist/mapping/parsers/tiny.d.ts +57 -0
- package/dist/mapping/parsers/tiny.js +99 -22
- package/dist/mapping-service.d.ts +19 -0
- package/dist/mapping-service.js +93 -9
- package/dist/maven-resolver.d.ts +18 -0
- package/dist/maven-resolver.js +20 -0
- package/dist/mcp-helpers.d.ts +19 -2
- package/dist/mcp-helpers.js +58 -9
- package/dist/minecraft-explorer-service.d.ts +1 -1
- package/dist/mixin/types.d.ts +8 -0
- package/dist/mod-analyzer.js +7 -7
- package/dist/mod-decompile-service.js +1 -0
- package/dist/nbt/java-nbt-codec.js +12 -2
- package/dist/nbt/json-patch.js +14 -3
- package/dist/nbt/pipeline.js +40 -3
- package/dist/nbt/typed-json.js +26 -1
- package/dist/registration-adapter.d.ts +32 -0
- package/dist/registration-adapter.js +52 -0
- package/dist/repo-downloader.d.ts +165 -0
- package/dist/repo-downloader.js +568 -10
- package/dist/request-context.d.ts +7 -0
- package/dist/request-context.js +9 -0
- package/dist/resources.d.ts +1 -1
- package/dist/resources.js +25 -19
- package/dist/server-identity.d.ts +27 -0
- package/dist/server-identity.js +26 -0
- package/dist/source/access-validate.js +53 -0
- package/dist/source/artifact-resolver.d.ts +81 -2
- package/dist/source/artifact-resolver.js +227 -15
- package/dist/source/class-source.d.ts +36 -0
- package/dist/source/class-source.js +222 -38
- package/dist/source/did-you-mean.d.ts +12 -1
- package/dist/source/did-you-mean.js +6 -2
- package/dist/source/file-access.js +150 -46
- package/dist/source/indexer.js +1 -0
- package/dist/source/shared-utils.d.ts +21 -0
- package/dist/source/shared-utils.js +23 -0
- package/dist/source-resolver.js +224 -57
- package/dist/source-service.d.ts +12 -1
- package/dist/stdio-supervisor.d.ts +357 -2
- package/dist/stdio-supervisor.js +1031 -80
- package/dist/storage/db.d.ts +2 -1
- package/dist/storage/db.js +15 -8
- package/dist/synthetic-decorator.d.ts +24 -0
- package/dist/synthetic-decorator.js +48 -0
- package/dist/tool-guidance.d.ts +17 -1
- package/dist/tool-guidance.js +323 -18
- package/dist/tool-schema-registry.d.ts +2 -0
- package/dist/tool-schema-registry.js +4 -0
- package/dist/tool-schemas.d.ts +2212 -3919
- package/dist/tool-schemas.js +33 -7
- package/dist/types.d.ts +35 -0
- package/dist/v1-parity-schemas.d.ts +7 -0
- package/dist/v1-parity-schemas.js +5584 -0
- package/dist/version-diff-service.d.ts +33 -0
- package/dist/version-diff-service.js +148 -3
- package/dist/version-service.js +36 -14
- package/dist/warning-details.js +18 -1
- package/docs/README-ja.md +5 -3
- package/docs/tool-reference.md +196 -19
- package/package.json +13 -8
package/dist/source-resolver.js
CHANGED
|
@@ -3,8 +3,8 @@ import { basename, dirname, join, resolve as resolvePath } from "node:path";
|
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import fastGlob from "fast-glob";
|
|
5
5
|
import { createError, ERROR_CODES } from "./errors.js";
|
|
6
|
-
import { buildRemoteBinaryUrls, buildRemoteSourceUrls, hasExistingJar, parseCoordinate, normalizedCoordinateValue } from "./maven-resolver.js";
|
|
7
|
-
import { defaultDownloadPath,
|
|
6
|
+
import { buildRemoteBinaryUrls, buildRemoteSourceUrls, hasExistingJar, isMutableMavenCoordinate, parseCoordinate, normalizedCoordinateValue } from "./maven-resolver.js";
|
|
7
|
+
import { defaultDownloadPath, discardCachedDownload, resolveCachedDownload } from "./repo-downloader.js";
|
|
8
8
|
import { artifactSignatureFromFile, normalizeJarPath } from "./path-resolver.js";
|
|
9
9
|
import { stableArtifactId } from "./config.js";
|
|
10
10
|
import { hasAnyJarEntry, hasJavaSourceExtension } from "./source-jar-reader.js";
|
|
@@ -16,7 +16,13 @@ async function hasJavaSources(jarPath) {
|
|
|
16
16
|
if (!hasExistingJar(jarPath)) {
|
|
17
17
|
return false;
|
|
18
18
|
}
|
|
19
|
-
|
|
19
|
+
// Archive errors deliberately propagate. This runs on the jar-target path
|
|
20
|
+
// (:336) where the jar IS the subject, and the reader's rejection of an
|
|
21
|
+
// unsafe archive - a traversal-named entry, for one - is the fail-closed
|
|
22
|
+
// refusal itself. Swallowing it here would downgrade "this archive is not
|
|
23
|
+
// safe to open" into "this archive has no sources" and let resolution
|
|
24
|
+
// continue past a jar it had already refused.
|
|
25
|
+
return await hasAnyJarEntry(jarPath, hasJavaSourceExtension);
|
|
20
26
|
}
|
|
21
27
|
function resolveExactJarSourceCandidate(inputJarPath) {
|
|
22
28
|
const directory = dirname(inputJarPath);
|
|
@@ -70,17 +76,51 @@ function resolveLocalCoordinateCandidates(localM2Path, coordinate) {
|
|
|
70
76
|
}
|
|
71
77
|
return [...existing];
|
|
72
78
|
}
|
|
73
|
-
function
|
|
79
|
+
function resolveLocalCoordinateBinaryCandidates(localM2Path, coordinate) {
|
|
74
80
|
const parsed = parseCoordinate(coordinate);
|
|
75
81
|
const groupPath = parsed.groupId.replace(/\./g, "/");
|
|
76
82
|
const baseDir = resolvePath(localM2Path, groupPath, parsed.artifactId, parsed.version);
|
|
77
83
|
const base = `${parsed.artifactId}-${parsed.version}`;
|
|
78
84
|
const classifierSuffix = parsed.classifier ? `-${parsed.classifier}` : "";
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return
|
|
85
|
+
const exactCandidate = resolvePath(baseDir, `${base}${classifierSuffix}.jar`);
|
|
86
|
+
const fallbackCandidates = classifierSuffix ? [resolvePath(baseDir, `${base}.jar`)] : [];
|
|
87
|
+
const exact = hasExistingJar(exactCandidate) ? exactCandidate : undefined;
|
|
88
|
+
const companion = exact ?? fallbackCandidates.find((candidate) => hasExistingJar(candidate));
|
|
89
|
+
return { exact, companion };
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Whether a jar on disk can actually be opened as an archive.
|
|
93
|
+
*
|
|
94
|
+
* `hasExistingJar` only proves that a file is present: an interrupted Gradle or
|
|
95
|
+
* Maven copy leaves a 0-byte or truncated jar behind that passes that check and
|
|
96
|
+
* then throws inside the decompiler on every call. Every sources branch of the
|
|
97
|
+
* cascade already proves its candidate by opening the zip; a binary branch that
|
|
98
|
+
* suppresses a remote download owes the caller the same proof.
|
|
99
|
+
*/
|
|
100
|
+
async function isReadableJarArchive(jarPath) {
|
|
101
|
+
try {
|
|
102
|
+
return await hasAnyJarEntry(jarPath, () => true);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The first candidate that can actually be opened as an archive, in preference
|
|
110
|
+
* order.
|
|
111
|
+
*
|
|
112
|
+
* Preference order is not the same as a pick: choosing the most-preferred
|
|
113
|
+
* candidate and only then proving it lets one corrupt file veto every good one
|
|
114
|
+
* behind it. Each candidate is proved before the next is considered, and the
|
|
115
|
+
* archives are opened lazily, so the usual case still opens exactly one zip.
|
|
116
|
+
*/
|
|
117
|
+
async function firstReadableJarArchive(candidates) {
|
|
118
|
+
for (const candidate of candidates) {
|
|
119
|
+
if (await isReadableJarArchive(candidate)) {
|
|
120
|
+
return candidate;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return undefined;
|
|
84
124
|
}
|
|
85
125
|
function resolveGradleUserHome() {
|
|
86
126
|
const configured = process.env.GRADLE_USER_HOME?.trim();
|
|
@@ -98,10 +138,8 @@ async function resolveGradleCacheCoordinateCandidate(coordinate) {
|
|
|
98
138
|
`${base}${classifierSuffix}-sources.jar`,
|
|
99
139
|
...(classifierSuffix ? [`${base}-sources.jar`] : [])
|
|
100
140
|
];
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
...(classifierSuffix ? [`${base}.jar`] : [])
|
|
104
|
-
];
|
|
141
|
+
const exactBinaryName = `${base}${classifierSuffix}.jar`;
|
|
142
|
+
const preferredBinaryNames = [exactBinaryName, ...(classifierSuffix ? [`${base}.jar`] : [])];
|
|
105
143
|
let discoveredFiles = [];
|
|
106
144
|
try {
|
|
107
145
|
discoveredFiles = await fastGlob.glob("*/*", {
|
|
@@ -128,7 +166,7 @@ async function resolveGradleCacheCoordinateCandidate(coordinate) {
|
|
|
128
166
|
if (!sourceJarPath && !binaryJarPath) {
|
|
129
167
|
return undefined;
|
|
130
168
|
}
|
|
131
|
-
return { sourceJarPath, binaryJarPath };
|
|
169
|
+
return { sourceJarPath, binaryJarPath, exactBinaryJarPath: pickFirst([exactBinaryName]) };
|
|
132
170
|
}
|
|
133
171
|
function resolveRemoteBinaryCandidate(coordinate, repos) {
|
|
134
172
|
return buildRemoteBinaryUrls(repos, coordinate);
|
|
@@ -150,6 +188,20 @@ function artifactIdForCoordinate(coordinate, source, signature, mappingVariant =
|
|
|
150
188
|
function resolvedAtNow() {
|
|
151
189
|
return new Date().toISOString();
|
|
152
190
|
}
|
|
191
|
+
/** Shared shape for every artifact the coordinate cascade can return. */
|
|
192
|
+
function coordinateArtifact(spec) {
|
|
193
|
+
return {
|
|
194
|
+
artifactId: artifactIdForCoordinate(spec.coordinate, spec.idSource, spec.signature, spec.mappingVariant ?? "pass"),
|
|
195
|
+
artifactSignature: spec.signature,
|
|
196
|
+
origin: spec.origin,
|
|
197
|
+
sourceJarPath: spec.sourceJarPath,
|
|
198
|
+
binaryJarPath: spec.binaryJarPath,
|
|
199
|
+
coordinate: spec.coordinate,
|
|
200
|
+
repoUrl: spec.repoUrl,
|
|
201
|
+
isDecompiled: spec.isDecompiled,
|
|
202
|
+
resolvedAt: resolvedAtNow()
|
|
203
|
+
};
|
|
204
|
+
}
|
|
153
205
|
export async function resolveSourceTarget(input, options, explicitConfig) {
|
|
154
206
|
const repos = options.preferredRepos?.length ? options.preferredRepos : explicitConfig.sourceRepos;
|
|
155
207
|
let sawRemoteRepoFailure = false;
|
|
@@ -216,51 +268,109 @@ export async function resolveSourceTarget(input, options, explicitConfig) {
|
|
|
216
268
|
});
|
|
217
269
|
}
|
|
218
270
|
const coordinate = normalizedCoordinateValue(input.value);
|
|
271
|
+
// Maven republishes -SNAPSHOT coordinates under the same name, so their cached
|
|
272
|
+
// downloads have to be confirmed with the repository; every other version is
|
|
273
|
+
// immutable and is served straight from the download cache. Decided once so
|
|
274
|
+
// the sources leg and the binary leg cannot disagree.
|
|
275
|
+
const downloadFreshness = isMutableMavenCoordinate(coordinate)
|
|
276
|
+
? "revalidate"
|
|
277
|
+
: "immutable";
|
|
219
278
|
const isTransientFailure = (statusCode) => statusCode === undefined || statusCode >= 500 || statusCode === 429;
|
|
279
|
+
const localM2Binary = resolveLocalCoordinateBinaryCandidates(explicitConfig.localM2Path, coordinate);
|
|
280
|
+
const localM2BinaryJarPath = localM2Binary.companion;
|
|
220
281
|
for (const candidate of resolveLocalCoordinateCandidates(explicitConfig.localM2Path, coordinate)) {
|
|
221
282
|
if (await hasJavaSources(candidate)) {
|
|
222
283
|
const signature = readStatsSignature(candidate);
|
|
223
|
-
return {
|
|
224
|
-
|
|
225
|
-
|
|
284
|
+
return coordinateArtifact({
|
|
285
|
+
coordinate,
|
|
286
|
+
idSource: "local-m2",
|
|
287
|
+
signature,
|
|
226
288
|
origin: "local-m2",
|
|
227
289
|
sourceJarPath: candidate,
|
|
228
|
-
binaryJarPath:
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
resolvedAt: resolvedAtNow()
|
|
232
|
-
};
|
|
290
|
+
binaryJarPath: localM2BinaryJarPath,
|
|
291
|
+
isDecompiled: false
|
|
292
|
+
});
|
|
233
293
|
}
|
|
234
294
|
}
|
|
235
295
|
const gradleCacheCandidate = await resolveGradleCacheCoordinateCandidate(coordinate);
|
|
236
296
|
if (gradleCacheCandidate?.sourceJarPath && (await hasJavaSources(gradleCacheCandidate.sourceJarPath))) {
|
|
237
297
|
const signature = readStatsSignature(gradleCacheCandidate.sourceJarPath);
|
|
238
|
-
return {
|
|
239
|
-
|
|
240
|
-
|
|
298
|
+
return coordinateArtifact({
|
|
299
|
+
coordinate,
|
|
300
|
+
idSource: "local-m2",
|
|
301
|
+
signature,
|
|
241
302
|
origin: "local-m2",
|
|
242
303
|
sourceJarPath: gradleCacheCandidate.sourceJarPath,
|
|
243
304
|
binaryJarPath: gradleCacheCandidate.binaryJarPath,
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
resolvedAt: resolvedAtNow()
|
|
247
|
-
};
|
|
305
|
+
isDecompiled: false
|
|
306
|
+
});
|
|
248
307
|
}
|
|
308
|
+
// Both local branches above are gated on SOURCES: a module that ships a binary jar
|
|
309
|
+
// locally but publishes no sources jar falls through them, and the binary jar they
|
|
310
|
+
// already discovered would be dropped even though it is still perfectly usable. Keep
|
|
311
|
+
// it here so the artifact that is ultimately returned still carries a binaryJarPath
|
|
312
|
+
// for binary-only consumers (get-class-members and friends).
|
|
313
|
+
//
|
|
314
|
+
// Existence alone is not proof: an interrupted local copy would otherwise get
|
|
315
|
+
// to veto a perfectly good companion in the other cache, exactly the failure
|
|
316
|
+
// `firstReadableJarArchive` exists to prevent elsewhere in this cascade. Kept
|
|
317
|
+
// as a candidate list rather than resolved here, so the readable one is only
|
|
318
|
+
// picked - lazily, at the one site that attaches it - once a remote sources
|
|
319
|
+
// jar has actually been found and this is known to be needed.
|
|
320
|
+
const localBinaryJarCandidates = [
|
|
321
|
+
...new Set([localM2BinaryJarPath, gradleCacheCandidate?.binaryJarPath].filter((candidate) => candidate !== undefined))
|
|
322
|
+
];
|
|
323
|
+
// The companion above may be the classifier-less jar, which is a different
|
|
324
|
+
// artifact than a classified coordinate asks for. That substitution is fine
|
|
325
|
+
// for a jar that merely rides along beside a sources jar, and wrong for one
|
|
326
|
+
// returned as the resolution itself, so the decompile branch below considers
|
|
327
|
+
// only jars whose name spells the coordinate out in full.
|
|
328
|
+
//
|
|
329
|
+
// A list rather than a single pick: ~/.m2 is preferred over the Gradle cache,
|
|
330
|
+
// but preferring it must not mean an interrupted copy sitting there gets to
|
|
331
|
+
// veto a perfectly good jar behind it - forcing a needless download, or an
|
|
332
|
+
// outright failure when offline.
|
|
333
|
+
const localDecompilableBinaryCandidates = [
|
|
334
|
+
...new Set([localM2Binary.exact, gradleCacheCandidate?.exactBinaryJarPath].filter((candidate) => candidate !== undefined))
|
|
335
|
+
];
|
|
249
336
|
const remoteSourceUrls = buildRemoteSourceUrls(repos, coordinate);
|
|
250
337
|
for (let index = 0; index < remoteSourceUrls.length; index++) {
|
|
251
338
|
const sourceUrl = remoteSourceUrls[index];
|
|
252
339
|
const hasNextAttempt = index < remoteSourceUrls.length - 1;
|
|
253
340
|
try {
|
|
254
|
-
const
|
|
341
|
+
const sourceDestinationPath = defaultDownloadPath(explicitConfig.cacheDir, sourceUrl);
|
|
342
|
+
const download = await resolveCachedDownload(sourceUrl, sourceDestinationPath, {
|
|
343
|
+
freshness: downloadFreshness,
|
|
255
344
|
retries: explicitConfig.fetchRetries,
|
|
256
345
|
timeoutMs: explicitConfig.fetchTimeoutMs
|
|
257
346
|
});
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
347
|
+
let sourceJarHasJavaSources;
|
|
348
|
+
try {
|
|
349
|
+
sourceJarHasJavaSources = download.ok && (await hasJavaSources(download.path));
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
// The download reports success, but the bytes are not a readable
|
|
353
|
+
// archive - the same "200 but not really a jar" shape the binary leg
|
|
354
|
+
// guards against below. Evict it now: this url is immutable for every
|
|
355
|
+
// non-SNAPSHOT coordinate, so a future call would otherwise be served
|
|
356
|
+
// the same poison with no request made at all.
|
|
357
|
+
if (download.ok) {
|
|
358
|
+
discardCachedDownload(download.path);
|
|
359
|
+
}
|
|
360
|
+
sourceJarHasJavaSources = false;
|
|
361
|
+
}
|
|
362
|
+
if (!download.ok || !sourceJarHasJavaSources) {
|
|
363
|
+
// Transience only decides the error CODE at the end of the cascade: an
|
|
364
|
+
// unstable repository earns ERR_REPO_FETCH_FAILED, while "this
|
|
365
|
+
// repository does not publish it" stays a plain not-found.
|
|
366
|
+
const transient = !download.ok && isTransientFailure(download.statusCode);
|
|
262
367
|
sawRemoteRepoFailure = sawRemoteRepoFailure || transient;
|
|
263
|
-
|
|
368
|
+
// Moving off this repository is reportable however it happened. A
|
|
369
|
+
// withdrawn artifact reaches here as an ordinary failure now that the
|
|
370
|
+
// downloader refuses to launder a 404 into a stale success, and leaving
|
|
371
|
+
// that silent would hide the one event that explains why the resolved
|
|
372
|
+
// artifact came from somewhere else.
|
|
373
|
+
if (hasNextAttempt) {
|
|
264
374
|
options.onRepoFailover?.({
|
|
265
375
|
stage: "source",
|
|
266
376
|
repoUrl: sourceUrl,
|
|
@@ -272,17 +382,20 @@ export async function resolveSourceTarget(input, options, explicitConfig) {
|
|
|
272
382
|
}
|
|
273
383
|
continue;
|
|
274
384
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
385
|
+
// Identity follows the bytes. The HTTP validators that used to form this
|
|
386
|
+
// signature rotate whenever a CDN or repository migration happens, even
|
|
387
|
+
// when the jar is byte-identical.
|
|
388
|
+
const signature = download.contentSha256;
|
|
389
|
+
return coordinateArtifact({
|
|
390
|
+
coordinate,
|
|
391
|
+
idSource: "remote-repo",
|
|
392
|
+
signature,
|
|
279
393
|
origin: "remote-repo",
|
|
280
394
|
sourceJarPath: download.path,
|
|
281
|
-
|
|
395
|
+
binaryJarPath: await firstReadableJarArchive(localBinaryJarCandidates),
|
|
282
396
|
repoUrl: sourceUrl,
|
|
283
|
-
isDecompiled: false
|
|
284
|
-
|
|
285
|
-
};
|
|
397
|
+
isDecompiled: false
|
|
398
|
+
});
|
|
286
399
|
}
|
|
287
400
|
catch (caughtError) {
|
|
288
401
|
sawRemoteRepoFailure = true;
|
|
@@ -306,43 +419,97 @@ export async function resolveSourceTarget(input, options, explicitConfig) {
|
|
|
306
419
|
details: { coordinate }
|
|
307
420
|
});
|
|
308
421
|
}
|
|
422
|
+
// A module that publishes no sources jar but whose binary jar is already on
|
|
423
|
+
// local disk has nothing left to fetch: ingest decompiles the binary, and the
|
|
424
|
+
// local copy is the same artifact the repository would hand back. Deliberately
|
|
425
|
+
// placed after the allowDecompile guard - returning it earlier would perform
|
|
426
|
+
// the decompile the caller just declined. The dedicated "local-binary" id space
|
|
427
|
+
// keeps it apart from the remote "decompiled" one; no coordinate resolve has
|
|
428
|
+
// ever returned a local binary jar, so nothing needs migrating.
|
|
429
|
+
//
|
|
430
|
+
// The jar has to be a readable archive before it may stand in for the download:
|
|
431
|
+
// a truncated local copy would otherwise be handed to the decompiler on every
|
|
432
|
+
// call with no way out. When none of the candidates is, the remote binary loop
|
|
433
|
+
// below is exactly the repair path, so this falls through to it instead of
|
|
434
|
+
// failing. The zips are opened here and only here - nothing upstream has looked
|
|
435
|
+
// inside these jars, and control only reaches this point when the branch is
|
|
436
|
+
// about to be taken.
|
|
437
|
+
const localDecompilableBinaryJarPath = await firstReadableJarArchive(localDecompilableBinaryCandidates);
|
|
438
|
+
if (localDecompilableBinaryJarPath) {
|
|
439
|
+
const signature = readStatsSignature(localDecompilableBinaryJarPath);
|
|
440
|
+
return coordinateArtifact({
|
|
441
|
+
coordinate,
|
|
442
|
+
idSource: "local-binary",
|
|
443
|
+
signature,
|
|
444
|
+
origin: "local-m2",
|
|
445
|
+
binaryJarPath: localDecompilableBinaryJarPath,
|
|
446
|
+
isDecompiled: true,
|
|
447
|
+
mappingVariant: options.mappingVariant ?? "pass"
|
|
448
|
+
});
|
|
449
|
+
}
|
|
309
450
|
const binaryCandidates = resolveRemoteBinaryCandidate(coordinate, repos);
|
|
310
451
|
for (let index = 0; index < binaryCandidates.length; index++) {
|
|
311
452
|
const binaryUrl = binaryCandidates[index];
|
|
312
453
|
const hasNextAttempt = index < binaryCandidates.length - 1;
|
|
313
454
|
try {
|
|
314
|
-
const
|
|
455
|
+
const binaryDestinationPath = defaultDownloadPath(explicitConfig.cacheDir, binaryUrl);
|
|
456
|
+
const downloaded = await resolveCachedDownload(binaryUrl, binaryDestinationPath, {
|
|
457
|
+
freshness: downloadFreshness,
|
|
315
458
|
retries: explicitConfig.fetchRetries,
|
|
316
459
|
timeoutMs: explicitConfig.fetchTimeoutMs
|
|
317
460
|
});
|
|
318
|
-
if (!downloaded.ok
|
|
319
|
-
const transient = downloaded.
|
|
320
|
-
? false
|
|
321
|
-
: downloaded.statusCode !== 404 && isTransientFailure(downloaded.statusCode);
|
|
461
|
+
if (!downloaded.ok) {
|
|
462
|
+
const transient = isTransientFailure(downloaded.statusCode);
|
|
322
463
|
sawRemoteRepoFailure = sawRemoteRepoFailure || transient;
|
|
323
|
-
if (hasNextAttempt
|
|
464
|
+
if (hasNextAttempt) {
|
|
324
465
|
options.onRepoFailover?.({
|
|
325
466
|
stage: "binary",
|
|
326
467
|
repoUrl: binaryUrl,
|
|
327
468
|
statusCode: downloaded.statusCode,
|
|
328
|
-
reason:
|
|
469
|
+
reason: "download-failed",
|
|
329
470
|
attempt: index + 1,
|
|
330
471
|
totalAttempts: binaryCandidates.length
|
|
331
472
|
});
|
|
332
473
|
}
|
|
333
474
|
continue;
|
|
334
475
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
476
|
+
// A 200 is not proof of a jar. A repository, a mirror, or a proxy in front
|
|
477
|
+
// of one answers a jar request with an HTML error page often enough that
|
|
478
|
+
// accepting any non-empty body writes it into the (immutable) download
|
|
479
|
+
// cache, returns it as the artifact, and blocks failover to a repository
|
|
480
|
+
// that has the real thing - with the failure surfacing much later, inside
|
|
481
|
+
// the decompiler. The sources leg has always proved its download by opening
|
|
482
|
+
// it; this one owes the caller the same proof.
|
|
483
|
+
if (!(await isReadableJarArchive(downloaded.path))) {
|
|
484
|
+
// And the body must not survive as a cache entry: this url is immutable
|
|
485
|
+
// for every non-SNAPSHOT coordinate, so the next run would be served the
|
|
486
|
+
// same poison with no request made at all.
|
|
487
|
+
discardCachedDownload(downloaded.path);
|
|
488
|
+
if (hasNextAttempt) {
|
|
489
|
+
options.onRepoFailover?.({
|
|
490
|
+
stage: "binary",
|
|
491
|
+
repoUrl: binaryUrl,
|
|
492
|
+
statusCode: downloaded.statusCode,
|
|
493
|
+
reason: "downloaded-not-an-archive",
|
|
494
|
+
attempt: index + 1,
|
|
495
|
+
totalAttempts: binaryCandidates.length
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
// A file in the URL-keyed download cache is identified by its bytes, not
|
|
501
|
+
// by a stat signature that a re-download would change for free.
|
|
502
|
+
const signature = downloaded.contentSha256;
|
|
503
|
+
return coordinateArtifact({
|
|
504
|
+
coordinate,
|
|
505
|
+
idSource: "decompiled",
|
|
506
|
+
signature,
|
|
339
507
|
origin: "decompiled",
|
|
340
508
|
binaryJarPath: downloaded.path,
|
|
341
|
-
coordinate,
|
|
342
509
|
repoUrl: binaryUrl,
|
|
343
510
|
isDecompiled: true,
|
|
344
|
-
|
|
345
|
-
};
|
|
511
|
+
mappingVariant: options.mappingVariant ?? "pass"
|
|
512
|
+
});
|
|
346
513
|
}
|
|
347
514
|
catch (caughtError) {
|
|
348
515
|
sawRemoteRepoFailure = true;
|
package/dist/source-service.d.ts
CHANGED
|
@@ -268,11 +268,20 @@ export type FindClassMatch = {
|
|
|
268
268
|
filePath: string;
|
|
269
269
|
line: number;
|
|
270
270
|
symbolKind: string;
|
|
271
|
+
/** Set when the match is a type declared INSIDE another type. */
|
|
272
|
+
nested?: boolean;
|
|
273
|
+
/** The enclosing top-level type, present only on a nested match. */
|
|
274
|
+
enclosingClass?: string;
|
|
271
275
|
};
|
|
272
276
|
export type FindClassOutput = {
|
|
273
277
|
matches: FindClassMatch[];
|
|
274
278
|
total: number;
|
|
275
279
|
warnings: string[];
|
|
280
|
+
/** Recovery route when the index legitimately cannot answer (partial coverage). */
|
|
281
|
+
suggestedCall?: {
|
|
282
|
+
tool: string;
|
|
283
|
+
params: Record<string, unknown>;
|
|
284
|
+
};
|
|
276
285
|
};
|
|
277
286
|
type MemberAccess = "public" | "all";
|
|
278
287
|
export type GetClassMembersInput = {
|
|
@@ -617,7 +626,7 @@ export declare class SourceService {
|
|
|
617
626
|
buildArtifactContentsSummary(input: {
|
|
618
627
|
origin: ResolvedSourceArtifact["origin"];
|
|
619
628
|
sourceJarPath?: string;
|
|
620
|
-
isDecompiled
|
|
629
|
+
isDecompiled: boolean;
|
|
621
630
|
qualityFlags: string[];
|
|
622
631
|
}): ArtifactContentsSummary;
|
|
623
632
|
discoverVersionSourceJar(input: {
|
|
@@ -638,6 +647,7 @@ export declare class SourceService {
|
|
|
638
647
|
requestedScope: ArtifactScope;
|
|
639
648
|
atNamespace: AccessTransformerNamespace;
|
|
640
649
|
loader: import("./workspace-mapping-service.js").WorkspaceProjectLoader | "unknown";
|
|
650
|
+
projectMinecraftVersion?: string;
|
|
641
651
|
}): ReturnType<typeof artifactResolver.discoverAccessTransformerRuntimeCandidates>;
|
|
642
652
|
buildMappingFallbackSuggestedCall(args: {
|
|
643
653
|
input: ResolveArtifactInput;
|
|
@@ -702,6 +712,7 @@ export declare class SourceService {
|
|
|
702
712
|
mappingApplied: SourceMapping;
|
|
703
713
|
provenance?: ArtifactProvenance;
|
|
704
714
|
qualityFlags: string[];
|
|
715
|
+
allowDecompile?: boolean;
|
|
705
716
|
}): Promise<ResolvedSourceArtifact | undefined>;
|
|
706
717
|
getArtifact(artifactId: string): ArtifactRow;
|
|
707
718
|
ingestIfNeeded(resolved: ResolvedSourceArtifact): Promise<void>;
|