@adhisang/minecraft-modding-mcp 3.1.1 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/README.md +20 -8
- package/dist/access-transformer-parser.d.ts +17 -0
- package/dist/access-transformer-parser.js +97 -0
- package/dist/concurrency.d.ts +1 -0
- package/dist/concurrency.js +24 -0
- package/dist/decompiler/vineflower.js +22 -21
- package/dist/entry-tools/analyze-mod-service.d.ts +4 -4
- package/dist/entry-tools/analyze-symbol-service.d.ts +20 -20
- package/dist/entry-tools/inspect-minecraft-service.d.ts +148 -148
- package/dist/entry-tools/validate-project-service.d.ts +153 -16
- package/dist/entry-tools/validate-project-service.js +360 -23
- package/dist/gradle-paths.d.ts +4 -0
- package/dist/gradle-paths.js +57 -0
- package/dist/index.js +65 -13
- package/dist/mapping-pipeline-service.d.ts +3 -1
- package/dist/mapping-pipeline-service.js +16 -1
- package/dist/mapping-service.d.ts +4 -0
- package/dist/mapping-service.js +155 -60
- package/dist/minecraft-explorer-service.d.ts +13 -0
- package/dist/minecraft-explorer-service.js +8 -4
- package/dist/mixin-validator.d.ts +33 -2
- package/dist/mixin-validator.js +197 -11
- package/dist/mod-analyzer.d.ts +1 -0
- package/dist/mod-analyzer.js +17 -1
- package/dist/mod-decompile-service.js +4 -4
- package/dist/mod-remap-service.js +1 -54
- package/dist/mod-search-service.d.ts +1 -0
- package/dist/mod-search-service.js +84 -51
- package/dist/response-utils.d.ts +35 -0
- package/dist/response-utils.js +113 -0
- package/dist/source-jar-reader.d.ts +16 -0
- package/dist/source-jar-reader.js +103 -1
- package/dist/source-resolver.js +9 -10
- package/dist/source-service.d.ts +22 -2
- package/dist/source-service.js +914 -105
- package/dist/tool-contract-manifest.js +8 -6
- package/dist/types.d.ts +17 -0
- package/dist/workspace-mapping-service.d.ts +13 -0
- package/dist/workspace-mapping-service.js +146 -14
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { mapWithConcurrencyLimit } from "./concurrency.js";
|
|
3
4
|
import { createError, ERROR_CODES } from "./errors.js";
|
|
4
5
|
import { log } from "./logger.js";
|
|
5
6
|
import { validateAndNormalizeJarPath } from "./path-resolver.js";
|
|
@@ -9,6 +10,7 @@ const DEFAULT_LIMIT = 50;
|
|
|
9
10
|
const MAX_LIMIT = 200;
|
|
10
11
|
const MAX_QUERY_LENGTH = 200;
|
|
11
12
|
const CONTEXT_LINES = 1;
|
|
13
|
+
const DECOMPILED_JAVA_READ_CONCURRENCY = 8;
|
|
12
14
|
const METHOD_PATTERN = /^\s*(public|private|protected)\s+.*\(/;
|
|
13
15
|
const FIELD_PATTERN = /^\s*(public|private|protected)\s+(?:static\s+)?(?:final\s+)?[\w<>,\[\]?]+\s+\w+\s*[;=]/;
|
|
14
16
|
function buildRegex(query) {
|
|
@@ -41,6 +43,9 @@ function extractContext(lines, lineIndex) {
|
|
|
41
43
|
function filePathToClassName(filePath) {
|
|
42
44
|
return filePath.replace(/\.java$/, "").replaceAll("/", ".");
|
|
43
45
|
}
|
|
46
|
+
function cloneRegex(regex) {
|
|
47
|
+
return new RegExp(regex.source, regex.flags);
|
|
48
|
+
}
|
|
44
49
|
export class ModSearchService {
|
|
45
50
|
modDecompileService;
|
|
46
51
|
constructor(modDecompileService) {
|
|
@@ -112,65 +117,40 @@ export class ModSearchService {
|
|
|
112
117
|
const hits = [];
|
|
113
118
|
let totalHits = 0;
|
|
114
119
|
let reachedLimit = false;
|
|
115
|
-
for (
|
|
120
|
+
for (let batchStartIndex = 0; batchStartIndex < classNames.length; batchStartIndex += DECOMPILED_JAVA_READ_CONCURRENCY) {
|
|
116
121
|
if (hits.length >= limit) {
|
|
117
122
|
reachedLimit = true;
|
|
118
123
|
break;
|
|
119
124
|
}
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
// If searching only classes, skip content search for this file
|
|
135
|
-
if (searchType === "class")
|
|
136
|
-
continue;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
// Content/method/field search: read and scan the file
|
|
140
|
-
if (searchType === "method" || searchType === "field" || searchType === "content" || searchType === "all") {
|
|
141
|
-
let content;
|
|
142
|
-
try {
|
|
143
|
-
content = readFileSync(join(outputDir, filePath), "utf8");
|
|
125
|
+
const batchClassNames = classNames.slice(batchStartIndex, batchStartIndex + DECOMPILED_JAVA_READ_CONCURRENCY);
|
|
126
|
+
const batchHitLimit = Math.max(1, limit - hits.length);
|
|
127
|
+
const fileResults = await mapWithConcurrencyLimit(batchClassNames, DECOMPILED_JAVA_READ_CONCURRENCY, async (className) => this.searchDecompiledClassFile({
|
|
128
|
+
className,
|
|
129
|
+
outputDir,
|
|
130
|
+
searchType,
|
|
131
|
+
regex: cloneRegex(regex),
|
|
132
|
+
maxHits: batchHitLimit
|
|
133
|
+
}));
|
|
134
|
+
for (const fileResult of fileResults) {
|
|
135
|
+
if (hits.length >= limit) {
|
|
136
|
+
reachedLimit = true;
|
|
137
|
+
break;
|
|
144
138
|
}
|
|
145
|
-
|
|
146
|
-
// File might not exist at the expected path, skip
|
|
139
|
+
if (fileResult.hits.length === 0) {
|
|
147
140
|
continue;
|
|
148
141
|
}
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (!regex.test(lines[i]))
|
|
157
|
-
continue;
|
|
158
|
-
const lineType = classifyLine(lines[i]);
|
|
159
|
-
// Filter by search type
|
|
160
|
-
if (searchType !== "all" && searchType !== lineType)
|
|
161
|
-
continue;
|
|
162
|
-
totalHits++;
|
|
163
|
-
if (hits.length < limit) {
|
|
164
|
-
hits.push({
|
|
165
|
-
type: lineType,
|
|
166
|
-
name: lineType === "content" ? className : extractSymbolName(lines[i], lineType),
|
|
167
|
-
file: filePath,
|
|
168
|
-
line: i + 1,
|
|
169
|
-
context: extractContext(lines, i)
|
|
170
|
-
});
|
|
171
|
-
}
|
|
142
|
+
const remaining = limit - hits.length;
|
|
143
|
+
const acceptedHits = fileResult.hits.slice(0, remaining);
|
|
144
|
+
hits.push(...acceptedHits);
|
|
145
|
+
totalHits += acceptedHits.length;
|
|
146
|
+
if (hits.length >= limit || fileResult.hits.length > remaining) {
|
|
147
|
+
reachedLimit = true;
|
|
148
|
+
break;
|
|
172
149
|
}
|
|
173
150
|
}
|
|
151
|
+
if (reachedLimit) {
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
174
154
|
}
|
|
175
155
|
log("info", "mod-search.done", {
|
|
176
156
|
jarPath,
|
|
@@ -255,6 +235,59 @@ export class ModSearchService {
|
|
|
255
235
|
warnings
|
|
256
236
|
};
|
|
257
237
|
}
|
|
238
|
+
async searchDecompiledClassFile(input) {
|
|
239
|
+
const hits = [];
|
|
240
|
+
const filePath = input.className.replaceAll(".", "/") + ".java";
|
|
241
|
+
if (input.searchType === "class" || input.searchType === "all") {
|
|
242
|
+
const simpleClassName = input.className.split(".").pop() ?? input.className;
|
|
243
|
+
input.regex.lastIndex = 0;
|
|
244
|
+
if (input.regex.test(simpleClassName)) {
|
|
245
|
+
hits.push({
|
|
246
|
+
type: "class",
|
|
247
|
+
name: input.className,
|
|
248
|
+
file: filePath
|
|
249
|
+
});
|
|
250
|
+
if (input.searchType === "class" || hits.length >= input.maxHits) {
|
|
251
|
+
return { hits };
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (input.searchType !== "method" &&
|
|
256
|
+
input.searchType !== "field" &&
|
|
257
|
+
input.searchType !== "content" &&
|
|
258
|
+
input.searchType !== "all") {
|
|
259
|
+
return { hits };
|
|
260
|
+
}
|
|
261
|
+
let content;
|
|
262
|
+
try {
|
|
263
|
+
content = await readFile(join(input.outputDir, filePath), "utf8");
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
return { hits };
|
|
267
|
+
}
|
|
268
|
+
const lines = content.split("\n");
|
|
269
|
+
for (let i = 0; i < lines.length; i++) {
|
|
270
|
+
if (hits.length >= input.maxHits) {
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
input.regex.lastIndex = 0;
|
|
274
|
+
if (!input.regex.test(lines[i])) {
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
const lineType = classifyLine(lines[i]);
|
|
278
|
+
if (input.searchType !== "all" && input.searchType !== lineType) {
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
hits.push({
|
|
282
|
+
type: lineType,
|
|
283
|
+
name: lineType === "content" ? input.className : extractSymbolName(lines[i], lineType),
|
|
284
|
+
file: filePath,
|
|
285
|
+
line: i + 1,
|
|
286
|
+
context: extractContext(lines, i)
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
return { hits };
|
|
290
|
+
}
|
|
258
291
|
}
|
|
259
292
|
function extractSymbolName(line, type) {
|
|
260
293
|
const trimmed = line.trim();
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compact-mode response utilities.
|
|
3
|
+
*
|
|
4
|
+
* Applied at the public boundary (runTool) only — internal service types are never modified.
|
|
5
|
+
*/
|
|
6
|
+
/** Tools that accept the `compact` parameter. */
|
|
7
|
+
export declare const COMPACT_ENABLED_TOOL_NAMES: Set<string>;
|
|
8
|
+
/** Mapping-oriented tools that get additional field projection via compactMappingResponse. */
|
|
9
|
+
export declare const COMPACT_MAPPING_TOOL_NAMES: Set<string>;
|
|
10
|
+
/**
|
|
11
|
+
* Double-gated compact check: tool must be in the allowlist AND parsedInput.compact must be true.
|
|
12
|
+
* Prevents activation on passthrough schemas where Zod doesn't strip unknown keys.
|
|
13
|
+
*/
|
|
14
|
+
export declare function isCompactEnabled(tool: string, parsedInput: unknown): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Shallow-strip empty values from a response object.
|
|
17
|
+
* Only operates on the top level — nested structures are preserved as-is.
|
|
18
|
+
* Non-plain objects (Date, Map, class instances) are never treated as empty.
|
|
19
|
+
*/
|
|
20
|
+
export declare function compactResponse(obj: Record<string, unknown>): Record<string, unknown>;
|
|
21
|
+
/** resolve-artifact compact: omit debug/diagnostic fields. */
|
|
22
|
+
export declare function compactArtifactResponse(obj: Record<string, unknown>): Record<string, unknown>;
|
|
23
|
+
/**
|
|
24
|
+
* Mapping tool compact: omit candidates only when provably redundant.
|
|
25
|
+
*
|
|
26
|
+
* Candidates are omitted when ALL of:
|
|
27
|
+
* 1. resolved === true
|
|
28
|
+
* 2. resolvedSymbol exists
|
|
29
|
+
* 3. candidates is an array of length 1
|
|
30
|
+
* 4. candidateCount === 1
|
|
31
|
+
* 5. candidatesTruncated is falsy
|
|
32
|
+
* 6. candidates[0].matchKind === "exact"
|
|
33
|
+
* 7. candidates[0].confidence is undefined or 1
|
|
34
|
+
*/
|
|
35
|
+
export declare function compactMappingResponse(obj: Record<string, unknown>): Record<string, unknown>;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compact-mode response utilities.
|
|
3
|
+
*
|
|
4
|
+
* Applied at the public boundary (runTool) only — internal service types are never modified.
|
|
5
|
+
*/
|
|
6
|
+
/** Tools that accept the `compact` parameter. */
|
|
7
|
+
export const COMPACT_ENABLED_TOOL_NAMES = new Set([
|
|
8
|
+
"resolve-artifact",
|
|
9
|
+
"find-mapping",
|
|
10
|
+
"resolve-method-mapping-exact",
|
|
11
|
+
"resolve-workspace-symbol",
|
|
12
|
+
"check-symbol-exists"
|
|
13
|
+
]);
|
|
14
|
+
/** Mapping-oriented tools that get additional field projection via compactMappingResponse. */
|
|
15
|
+
export const COMPACT_MAPPING_TOOL_NAMES = new Set([
|
|
16
|
+
"find-mapping",
|
|
17
|
+
"resolve-method-mapping-exact",
|
|
18
|
+
"resolve-workspace-symbol",
|
|
19
|
+
"check-symbol-exists"
|
|
20
|
+
]);
|
|
21
|
+
/**
|
|
22
|
+
* Double-gated compact check: tool must be in the allowlist AND parsedInput.compact must be true.
|
|
23
|
+
* Prevents activation on passthrough schemas where Zod doesn't strip unknown keys.
|
|
24
|
+
*/
|
|
25
|
+
export function isCompactEnabled(tool, parsedInput) {
|
|
26
|
+
if (!COMPACT_ENABLED_TOOL_NAMES.has(tool))
|
|
27
|
+
return false;
|
|
28
|
+
if (parsedInput &&
|
|
29
|
+
typeof parsedInput === "object" &&
|
|
30
|
+
!Array.isArray(parsedInput) &&
|
|
31
|
+
parsedInput.compact === true) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
function isPlainObject(v) {
|
|
37
|
+
if (typeof v !== "object" || v === null || Array.isArray(v))
|
|
38
|
+
return false;
|
|
39
|
+
const proto = Object.getPrototypeOf(v);
|
|
40
|
+
return proto === Object.prototype || proto === null;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Shallow-strip empty values from a response object.
|
|
44
|
+
* Only operates on the top level — nested structures are preserved as-is.
|
|
45
|
+
* Non-plain objects (Date, Map, class instances) are never treated as empty.
|
|
46
|
+
*/
|
|
47
|
+
export function compactResponse(obj) {
|
|
48
|
+
if (!isPlainObject(obj))
|
|
49
|
+
return {};
|
|
50
|
+
const result = {};
|
|
51
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
52
|
+
if (value === null || value === undefined)
|
|
53
|
+
continue;
|
|
54
|
+
if (Array.isArray(value) && value.length === 0)
|
|
55
|
+
continue;
|
|
56
|
+
if (isPlainObject(value) && Object.keys(value).length === 0)
|
|
57
|
+
continue;
|
|
58
|
+
result[key] = value;
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
/** Fields to omit from resolve-artifact in compact mode. */
|
|
63
|
+
const ARTIFACT_COMPACT_OMIT_KEYS = new Set([
|
|
64
|
+
"provenance",
|
|
65
|
+
"artifactContents",
|
|
66
|
+
"sampleEntries",
|
|
67
|
+
"adjacentSourceCandidates",
|
|
68
|
+
"binaryJarPath",
|
|
69
|
+
"coordinate",
|
|
70
|
+
"repoUrl",
|
|
71
|
+
"resolvedSourceJarPath"
|
|
72
|
+
]);
|
|
73
|
+
/** resolve-artifact compact: omit debug/diagnostic fields. */
|
|
74
|
+
export function compactArtifactResponse(obj) {
|
|
75
|
+
const projected = {};
|
|
76
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
77
|
+
if (ARTIFACT_COMPACT_OMIT_KEYS.has(key))
|
|
78
|
+
continue;
|
|
79
|
+
projected[key] = value;
|
|
80
|
+
}
|
|
81
|
+
return projected;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Mapping tool compact: omit candidates only when provably redundant.
|
|
85
|
+
*
|
|
86
|
+
* Candidates are omitted when ALL of:
|
|
87
|
+
* 1. resolved === true
|
|
88
|
+
* 2. resolvedSymbol exists
|
|
89
|
+
* 3. candidates is an array of length 1
|
|
90
|
+
* 4. candidateCount === 1
|
|
91
|
+
* 5. candidatesTruncated is falsy
|
|
92
|
+
* 6. candidates[0].matchKind === "exact"
|
|
93
|
+
* 7. candidates[0].confidence is undefined or 1
|
|
94
|
+
*/
|
|
95
|
+
export function compactMappingResponse(obj) {
|
|
96
|
+
const projected = { ...obj };
|
|
97
|
+
const candidates = projected.candidates;
|
|
98
|
+
if (projected.resolved === true &&
|
|
99
|
+
projected.resolvedSymbol !== undefined &&
|
|
100
|
+
Array.isArray(candidates) &&
|
|
101
|
+
candidates.length === 1 &&
|
|
102
|
+
projected.candidateCount === 1 &&
|
|
103
|
+
!projected.candidatesTruncated) {
|
|
104
|
+
const candidate = candidates[0];
|
|
105
|
+
if (candidate &&
|
|
106
|
+
candidate.matchKind === "exact" &&
|
|
107
|
+
(candidate.confidence === undefined || candidate.confidence === 1)) {
|
|
108
|
+
delete projected.candidates;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return projected;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=response-utils.js.map
|
|
@@ -2,12 +2,28 @@ export interface JavaEntryText {
|
|
|
2
2
|
filePath: string;
|
|
3
3
|
content: string;
|
|
4
4
|
}
|
|
5
|
+
export interface JarEntryText {
|
|
6
|
+
filePath: string;
|
|
7
|
+
content: string;
|
|
8
|
+
}
|
|
9
|
+
export interface CollectMatchedJarEntriesOptions {
|
|
10
|
+
maxBytes?: number;
|
|
11
|
+
maxEntries?: number;
|
|
12
|
+
continueOnError?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function hasJavaSourceExtension(entryPath: string): boolean;
|
|
5
15
|
export declare class EntryTooLargeError extends Error {
|
|
6
16
|
constructor(entryPath: string, jarPath: string, maxBytes: number);
|
|
7
17
|
}
|
|
8
18
|
export declare function listJarEntries(jarPath: string): Promise<string[]>;
|
|
9
19
|
export declare function listJavaEntries(jarPath: string): Promise<string[]>;
|
|
20
|
+
export declare function hasAnyJarEntry(jarPath: string, predicate: (entryPath: string) => boolean): Promise<boolean>;
|
|
10
21
|
export declare function readJarEntryAsUtf8(jarPath: string, entryPath: string): Promise<string>;
|
|
11
22
|
export declare function readJarEntryAsBuffer(jarPath: string, entryPath: string): Promise<Buffer>;
|
|
23
|
+
export declare function collectMatchedJarEntriesAsUtf8(jarPath: string, predicate: (entryPath: string) => boolean, options?: CollectMatchedJarEntriesOptions): Promise<JarEntryText[]>;
|
|
12
24
|
export declare function iterateJavaEntriesAsUtf8(jarPath: string, maxBytes?: number): AsyncGenerator<JavaEntryText>;
|
|
13
25
|
export declare function readAllJavaEntriesAsUtf8(jarPath: string, maxBytes?: number): Promise<JavaEntryText[]>;
|
|
26
|
+
export declare function detectFabricLikeInputNamespace(inputJar: string): Promise<{
|
|
27
|
+
fromNamespace: "intermediary" | "mojang";
|
|
28
|
+
warnings: string[];
|
|
29
|
+
}>;
|
|
@@ -9,7 +9,7 @@ function toErrorMessage(value) {
|
|
|
9
9
|
}
|
|
10
10
|
return String(value);
|
|
11
11
|
}
|
|
12
|
-
function hasJavaSourceExtension(entryPath) {
|
|
12
|
+
export function hasJavaSourceExtension(entryPath) {
|
|
13
13
|
const suffix = ".java";
|
|
14
14
|
if (entryPath.length < suffix.length) {
|
|
15
15
|
return false;
|
|
@@ -144,6 +144,22 @@ export async function listJavaEntries(jarPath) {
|
|
|
144
144
|
const entries = await listJarEntries(jarPath);
|
|
145
145
|
return entries.filter((entry) => hasJavaSourceExtension(entry) && isSecureJarEntryPath(entry));
|
|
146
146
|
}
|
|
147
|
+
export async function hasAnyJarEntry(jarPath, predicate) {
|
|
148
|
+
return withZipFile(jarPath, async (zipFile) => {
|
|
149
|
+
while (true) {
|
|
150
|
+
const entry = await readNextEntry(zipFile);
|
|
151
|
+
if (!entry) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
if (!isSecureJarEntryPath(entry.fileName)) {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (predicate(entry.fileName)) {
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
147
163
|
export async function readJarEntryAsUtf8(jarPath, entryPath) {
|
|
148
164
|
const contentBuffer = await readJarEntryAsBuffer(jarPath, entryPath);
|
|
149
165
|
return decodeUtf8OrThrow(contentBuffer, jarPath, entryPath);
|
|
@@ -177,6 +193,39 @@ export async function readJarEntryAsBuffer(jarPath, entryPath) {
|
|
|
177
193
|
}
|
|
178
194
|
});
|
|
179
195
|
}
|
|
196
|
+
export async function collectMatchedJarEntriesAsUtf8(jarPath, predicate, options = {}) {
|
|
197
|
+
return withZipFile(jarPath, async (zipFile) => {
|
|
198
|
+
const entries = [];
|
|
199
|
+
const maxEntries = options.maxEntries == null ? undefined : Math.max(1, Math.trunc(options.maxEntries));
|
|
200
|
+
while (true) {
|
|
201
|
+
const entry = await readNextEntry(zipFile);
|
|
202
|
+
if (!entry) {
|
|
203
|
+
return entries;
|
|
204
|
+
}
|
|
205
|
+
if (!isSecureJarEntryPath(entry.fileName)) {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (!predicate(entry.fileName)) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
const contentBuffer = await readEntryStream(zipFile, entry, jarPath, options.maxBytes);
|
|
213
|
+
entries.push({
|
|
214
|
+
filePath: entry.fileName,
|
|
215
|
+
content: decodeUtf8OrThrow(contentBuffer, jarPath, entry.fileName)
|
|
216
|
+
});
|
|
217
|
+
if (maxEntries != null && entries.length >= maxEntries) {
|
|
218
|
+
return entries;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
if (!options.continueOnError) {
|
|
223
|
+
throw error;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
}
|
|
180
229
|
export async function* iterateJavaEntriesAsUtf8(jarPath, maxBytes) {
|
|
181
230
|
const zipFile = await openZipFile(jarPath);
|
|
182
231
|
try {
|
|
@@ -227,4 +276,57 @@ export async function readAllJavaEntriesAsUtf8(jarPath, maxBytes) {
|
|
|
227
276
|
}
|
|
228
277
|
return entries;
|
|
229
278
|
}
|
|
279
|
+
function countMatches(input, pattern) {
|
|
280
|
+
const flags = pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`;
|
|
281
|
+
const globalPattern = new RegExp(pattern.source, flags);
|
|
282
|
+
let count = 0;
|
|
283
|
+
while (globalPattern.exec(input)) {
|
|
284
|
+
count += 1;
|
|
285
|
+
}
|
|
286
|
+
return count;
|
|
287
|
+
}
|
|
288
|
+
export async function detectFabricLikeInputNamespace(inputJar) {
|
|
289
|
+
const warnings = [];
|
|
290
|
+
const classEntries = (await listJarEntries(inputJar))
|
|
291
|
+
.filter((entry) => entry.endsWith(".class"))
|
|
292
|
+
.slice(0, 24);
|
|
293
|
+
if (classEntries.length === 0) {
|
|
294
|
+
warnings.push("Could not inspect class entries to detect input mapping; assuming intermediary.");
|
|
295
|
+
return {
|
|
296
|
+
fromNamespace: "intermediary",
|
|
297
|
+
warnings
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
let mojangScore = 0;
|
|
301
|
+
let intermediaryScore = 0;
|
|
302
|
+
for (const entry of classEntries) {
|
|
303
|
+
let text = "";
|
|
304
|
+
try {
|
|
305
|
+
text = (await readJarEntryAsBuffer(inputJar, entry)).toString("latin1");
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
mojangScore += countMatches(text, /net\/minecraft\/(?:advancements|client|commands|core|data|gametest|nbt|network|recipe|resources|server|sounds|stats|tags|util|world)\//g) * 3;
|
|
311
|
+
intermediaryScore += countMatches(text, /net\/minecraft\/class_\d+/g) * 3;
|
|
312
|
+
intermediaryScore += countMatches(text, /\b(?:method|field)_\d+\b/g);
|
|
313
|
+
}
|
|
314
|
+
if (mojangScore > intermediaryScore && mojangScore > 0) {
|
|
315
|
+
return {
|
|
316
|
+
fromNamespace: "mojang",
|
|
317
|
+
warnings
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
if (intermediaryScore > mojangScore && intermediaryScore > 0) {
|
|
321
|
+
return {
|
|
322
|
+
fromNamespace: "intermediary",
|
|
323
|
+
warnings
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
warnings.push("Could not confidently detect whether the input jar uses intermediary or mojang names; assuming intermediary.");
|
|
327
|
+
return {
|
|
328
|
+
fromNamespace: "intermediary",
|
|
329
|
+
warnings
|
|
330
|
+
};
|
|
331
|
+
}
|
|
230
332
|
//# sourceMappingURL=source-jar-reader.js.map
|
package/dist/source-resolver.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readdir } from "node:fs/promises";
|
|
2
2
|
import { basename, dirname, join, resolve as resolvePath } from "node:path";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import fastGlob from "fast-glob";
|
|
@@ -7,7 +7,7 @@ import { buildRemoteBinaryUrls, buildRemoteSourceUrls, hasExistingJar, parseCoor
|
|
|
7
7
|
import { defaultDownloadPath, downloadToCache } from "./repo-downloader.js";
|
|
8
8
|
import { artifactSignatureFromFile, normalizeJarPath } from "./path-resolver.js";
|
|
9
9
|
import { stableArtifactId } from "./config.js";
|
|
10
|
-
import {
|
|
10
|
+
import { hasAnyJarEntry, hasJavaSourceExtension } from "./source-jar-reader.js";
|
|
11
11
|
function readStatsSignature(filePath) {
|
|
12
12
|
const stats = artifactSignatureFromFile(filePath);
|
|
13
13
|
return stats.signature;
|
|
@@ -16,8 +16,7 @@ async function hasJavaSources(jarPath) {
|
|
|
16
16
|
if (!hasExistingJar(jarPath)) {
|
|
17
17
|
return false;
|
|
18
18
|
}
|
|
19
|
-
|
|
20
|
-
return entries.length > 0;
|
|
19
|
+
return hasAnyJarEntry(jarPath, hasJavaSourceExtension);
|
|
21
20
|
}
|
|
22
21
|
function resolveExactJarSourceCandidate(inputJarPath) {
|
|
23
22
|
const directory = dirname(inputJarPath);
|
|
@@ -35,12 +34,12 @@ function resolveSiblingBinaryJarCandidate(inputJarPath) {
|
|
|
35
34
|
const candidate = join(directory, binaryName);
|
|
36
35
|
return hasExistingJar(candidate) ? candidate : undefined;
|
|
37
36
|
}
|
|
38
|
-
function listAdjacentJarSourceCandidates(inputJarPath) {
|
|
37
|
+
async function listAdjacentJarSourceCandidates(inputJarPath) {
|
|
39
38
|
const directory = dirname(inputJarPath);
|
|
40
39
|
const exact = resolveExactJarSourceCandidate(inputJarPath);
|
|
41
40
|
const candidates = new Set();
|
|
42
41
|
try {
|
|
43
|
-
for (const file of
|
|
42
|
+
for (const file of await readdir(directory)) {
|
|
44
43
|
if (file.toLowerCase().endsWith("-sources.jar")) {
|
|
45
44
|
const candidate = join(directory, file);
|
|
46
45
|
if (candidate !== inputJarPath && candidate !== exact) {
|
|
@@ -90,7 +89,7 @@ function resolveGradleUserHome() {
|
|
|
90
89
|
}
|
|
91
90
|
return resolvePath(homedir(), ".gradle");
|
|
92
91
|
}
|
|
93
|
-
function resolveGradleCacheCoordinateCandidate(coordinate) {
|
|
92
|
+
async function resolveGradleCacheCoordinateCandidate(coordinate) {
|
|
94
93
|
const parsed = parseCoordinate(coordinate);
|
|
95
94
|
const baseDir = resolvePath(resolveGradleUserHome(), "caches", "modules-2", "files-2.1", parsed.groupId, parsed.artifactId, parsed.version);
|
|
96
95
|
const base = `${parsed.artifactId}-${parsed.version}`;
|
|
@@ -105,7 +104,7 @@ function resolveGradleCacheCoordinateCandidate(coordinate) {
|
|
|
105
104
|
];
|
|
106
105
|
let discoveredFiles = [];
|
|
107
106
|
try {
|
|
108
|
-
discoveredFiles = fastGlob.
|
|
107
|
+
discoveredFiles = await fastGlob.glob("*/*", {
|
|
109
108
|
cwd: baseDir,
|
|
110
109
|
absolute: true,
|
|
111
110
|
onlyFiles: true
|
|
@@ -150,7 +149,7 @@ export async function resolveSourceTarget(input, options, explicitConfig) {
|
|
|
150
149
|
const resolvedJarPath = normalizeJarPath(input.value);
|
|
151
150
|
const binarySignature = readStatsSignature(resolvedJarPath);
|
|
152
151
|
const exactSourceJarPath = resolveExactJarSourceCandidate(resolvedJarPath);
|
|
153
|
-
const adjacentSourceCandidates = listAdjacentJarSourceCandidates(resolvedJarPath);
|
|
152
|
+
const adjacentSourceCandidates = await listAdjacentJarSourceCandidates(resolvedJarPath);
|
|
154
153
|
const maybeAdjacentSourceCandidates = adjacentSourceCandidates.length > 0 ? adjacentSourceCandidates : undefined;
|
|
155
154
|
const preferBinaryOnly = options.preferBinaryOnly ?? false;
|
|
156
155
|
if (await hasJavaSources(resolvedJarPath)) {
|
|
@@ -225,7 +224,7 @@ export async function resolveSourceTarget(input, options, explicitConfig) {
|
|
|
225
224
|
};
|
|
226
225
|
}
|
|
227
226
|
}
|
|
228
|
-
const gradleCacheCandidate = resolveGradleCacheCoordinateCandidate(coordinate);
|
|
227
|
+
const gradleCacheCandidate = await resolveGradleCacheCoordinateCandidate(coordinate);
|
|
229
228
|
if (gradleCacheCandidate?.sourceJarPath && (await hasJavaSources(gradleCacheCandidate.sourceJarPath))) {
|
|
230
229
|
const signature = readStatsSignature(gradleCacheCandidate.sourceJarPath);
|
|
231
230
|
return {
|
package/dist/source-service.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { type ResponseContext as ExplorerResponseContext, type SignatureMember } from "./minecraft-explorer-service.js";
|
|
2
|
-
import { type MixinValidationResult, type MixinValidationProvenance, type MappingHealthReport, type AccessWidenerValidationResult } from "./mixin-validator.js";
|
|
2
|
+
import { type MixinValidationResult, type MixinValidationProvenance, type MappingHealthReport, type AccessWidenerValidationResult, type AccessTransformerValidationResult } from "./mixin-validator.js";
|
|
3
3
|
import { type ClassApiMatrixInput as MappingClassApiMatrixInput, type ClassApiMatrixOutput as MappingClassApiMatrixOutput, type FindMappingInput as MappingFindMappingInput, type FindMappingOutput as MappingFindMappingOutput, type ResolveMethodMappingExactInput as MappingResolveMethodMappingExactInput, type ResolveMethodMappingExactOutput as MappingResolveMethodMappingExactOutput, type SymbolResolutionOutput as MappingSymbolResolutionOutput, type SymbolExistenceInput as MappingSymbolExistenceInput, type SymbolExistenceOutput as MappingSymbolExistenceOutput } from "./mapping-service.js";
|
|
4
4
|
import { RuntimeMetrics, type RuntimeMetricSnapshot } from "./observability.js";
|
|
5
5
|
import { type WorkspaceCompileMappingOutput } from "./workspace-mapping-service.js";
|
|
6
|
-
import type { ArtifactProvenance, ArtifactRow, ArtifactScope, Config, MappingSourcePriority, ResolvedSourceArtifact, SourceMapping, SourceTargetInput } from "./types.js";
|
|
6
|
+
import type { AccessTransformerNamespace, ArtifactProvenance, ArtifactRow, ArtifactScope, Config, MappingSourcePriority, ResolvedSourceArtifact, SourceMapping, SourceTargetInput } from "./types.js";
|
|
7
7
|
import { type ListVersionsInput, type ListVersionsOutput } from "./version-service.js";
|
|
8
8
|
import { type GetRegistryDataInput, type GetRegistryDataOutput } from "./registry-service.js";
|
|
9
9
|
import { type CompareVersionsInput, type CompareVersionsOutput } from "./version-diff-service.js";
|
|
@@ -424,8 +424,21 @@ export type ValidateAccessWidenerInput = {
|
|
|
424
424
|
version: string;
|
|
425
425
|
mapping?: SourceMapping;
|
|
426
426
|
sourcePriority?: MappingSourcePriority;
|
|
427
|
+
projectPath?: string;
|
|
428
|
+
scope?: ArtifactScope;
|
|
429
|
+
preferProjectVersion?: boolean;
|
|
427
430
|
};
|
|
428
431
|
export type ValidateAccessWidenerOutput = AccessWidenerValidationResult;
|
|
432
|
+
export type ValidateAccessTransformerInput = {
|
|
433
|
+
content: string;
|
|
434
|
+
version: string;
|
|
435
|
+
atNamespace?: AccessTransformerNamespace;
|
|
436
|
+
sourcePriority?: MappingSourcePriority;
|
|
437
|
+
projectPath?: string;
|
|
438
|
+
scope?: ArtifactScope;
|
|
439
|
+
preferProjectVersion?: boolean;
|
|
440
|
+
};
|
|
441
|
+
export type ValidateAccessTransformerOutput = AccessTransformerValidationResult;
|
|
429
442
|
export declare class SourceService {
|
|
430
443
|
private readonly config;
|
|
431
444
|
private readonly db;
|
|
@@ -445,6 +458,11 @@ export declare class SourceService {
|
|
|
445
458
|
private cacheMetricsState;
|
|
446
459
|
constructor(explicitConfig?: Config, metrics?: RuntimeMetrics);
|
|
447
460
|
private discoverVersionSourceJar;
|
|
461
|
+
private discoverAccessWidenerRuntimeCandidates;
|
|
462
|
+
private discoverAccessTransformerRuntimeCandidates;
|
|
463
|
+
private resolveAccessWidenerRuntimeArtifact;
|
|
464
|
+
private resolveAccessTransformerNamespace;
|
|
465
|
+
private resolveAccessTransformerRuntimeArtifact;
|
|
448
466
|
private buildVersionSourceRecoveryCommand;
|
|
449
467
|
private buildArtifactContentsSummary;
|
|
450
468
|
private inferVersionFromContext;
|
|
@@ -464,6 +482,7 @@ export declare class SourceService {
|
|
|
464
482
|
getClassApiMatrix(input: GetClassApiMatrixInput): Promise<GetClassApiMatrixOutput>;
|
|
465
483
|
checkSymbolExists(input: CheckSymbolExistsInput): Promise<CheckSymbolExistsOutput>;
|
|
466
484
|
resolveWorkspaceSymbol(input: ResolveWorkspaceSymbolInput): Promise<ResolveWorkspaceSymbolOutput>;
|
|
485
|
+
private checkSymbolExistsInUnobfuscatedRuntime;
|
|
467
486
|
traceSymbolLifecycle(input: TraceSymbolLifecycleInput): Promise<TraceSymbolLifecycleOutput>;
|
|
468
487
|
diffClassSignatures(input: DiffClassSignaturesInput): Promise<DiffClassSignaturesOutput>;
|
|
469
488
|
findClass(input: FindClassInput): FindClassOutput;
|
|
@@ -480,6 +499,7 @@ export declare class SourceService {
|
|
|
480
499
|
private applyValidateMixinOutputCompaction;
|
|
481
500
|
private buildValidateMixinOutput;
|
|
482
501
|
validateAccessWidener(input: ValidateAccessWidenerInput): Promise<ValidateAccessWidenerOutput>;
|
|
502
|
+
validateAccessTransformer(input: ValidateAccessTransformerInput): Promise<ValidateAccessTransformerOutput>;
|
|
483
503
|
getRuntimeMetrics(): RuntimeMetricSnapshot;
|
|
484
504
|
indexArtifact(input: IndexArtifactInput): Promise<IndexArtifactOutput>;
|
|
485
505
|
private searchSymbolIntent;
|