@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,186 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, mkdirSync, statSync, rmSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { createError, ERROR_CODES } from "./errors.js";
|
|
6
|
+
import { log } from "./logger.js";
|
|
7
|
+
import { resolveTinyMappingFile } from "./mapping-service.js";
|
|
8
|
+
import { resolveMojangTinyFile } from "./mojang-tiny-mapping-service.js";
|
|
9
|
+
import { analyzeModJar } from "./mod-analyzer.js";
|
|
10
|
+
import { normalizePathForHost } from "./path-converter.js";
|
|
11
|
+
import { remapJar } from "./tiny-remapper-service.js";
|
|
12
|
+
import { resolveTinyRemapperJar } from "./tiny-remapper-resolver.js";
|
|
13
|
+
function normalizeTargetNamespace(target) {
|
|
14
|
+
return target === "yarn" ? "yarn" : "mojang";
|
|
15
|
+
}
|
|
16
|
+
function sourceNamespaceForLoader(loader) {
|
|
17
|
+
if (loader === "fabric" || loader === "quilt") {
|
|
18
|
+
return "intermediary";
|
|
19
|
+
}
|
|
20
|
+
throw createError({
|
|
21
|
+
code: ERROR_CODES.REMAP_FAILED,
|
|
22
|
+
message: `Unsupported mod loader for remapping: "${loader}". Only Fabric and Quilt mods are supported.`,
|
|
23
|
+
details: { loader }
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function extractMinecraftVersion(dependencies) {
|
|
27
|
+
if (!dependencies) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
const mcDep = dependencies.find((dep) => dep.modId === "minecraft");
|
|
31
|
+
if (!mcDep?.versionRange) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
// Try to extract exact version from ranges like ">=1.20.4", "~1.20.4", "1.20.4", "^1.20.4"
|
|
35
|
+
const match = mcDep.versionRange.match(/(\d+\.\d+(?:\.\d+)?)/);
|
|
36
|
+
return match?.[1];
|
|
37
|
+
}
|
|
38
|
+
function buildCacheKey(inputJar, fromNamespace, targetNamespace, mcVersion) {
|
|
39
|
+
const stat = statSync(inputJar, { throwIfNoEntry: false });
|
|
40
|
+
const signature = stat ? `${stat.mtimeMs}:${stat.size}` : "unknown";
|
|
41
|
+
return createHash("sha256")
|
|
42
|
+
.update(`${inputJar}|${signature}|${fromNamespace}|${targetNamespace}|${mcVersion}`)
|
|
43
|
+
.digest("hex");
|
|
44
|
+
}
|
|
45
|
+
export async function remapModJar(input, config) {
|
|
46
|
+
const startedAt = Date.now();
|
|
47
|
+
const warnings = [];
|
|
48
|
+
// 1. Normalize input JAR path
|
|
49
|
+
const normalizedInput = normalizePathForHost(input.inputJar, undefined, "inputJar");
|
|
50
|
+
if (!normalizedInput.toLowerCase().endsWith(".jar")) {
|
|
51
|
+
throw createError({
|
|
52
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
53
|
+
message: "inputJar must point to a .jar file.",
|
|
54
|
+
details: { inputJar: normalizedInput }
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
if (!existsSync(normalizedInput)) {
|
|
58
|
+
throw createError({
|
|
59
|
+
code: ERROR_CODES.JAR_NOT_FOUND,
|
|
60
|
+
message: `Input JAR not found: ${normalizedInput}`,
|
|
61
|
+
details: { inputJar: normalizedInput }
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
const resolvedTargetNamespace = normalizeTargetNamespace(input.targetMapping);
|
|
65
|
+
// 2. Analyze mod metadata
|
|
66
|
+
const analysis = await analyzeModJar(normalizedInput);
|
|
67
|
+
if (analysis.loader === "unknown") {
|
|
68
|
+
throw createError({
|
|
69
|
+
code: ERROR_CODES.REMAP_FAILED,
|
|
70
|
+
message: "Could not detect mod loader. Only Fabric and Quilt mods are supported.",
|
|
71
|
+
details: { inputJar: normalizedInput }
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const fromNamespace = sourceNamespaceForLoader(analysis.loader);
|
|
75
|
+
// 3. Determine MC version
|
|
76
|
+
const mcVersion = input.mcVersion ?? extractMinecraftVersion(analysis.dependencies);
|
|
77
|
+
if (!mcVersion) {
|
|
78
|
+
throw createError({
|
|
79
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
80
|
+
message: "Could not determine Minecraft version from mod metadata. Please provide mcVersion explicitly.",
|
|
81
|
+
details: {
|
|
82
|
+
inputJar: normalizedInput,
|
|
83
|
+
loader: analysis.loader,
|
|
84
|
+
modId: analysis.modId
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
// 4. Check cache after mapping context is known
|
|
89
|
+
const cacheKey = buildCacheKey(normalizedInput, fromNamespace, resolvedTargetNamespace, mcVersion);
|
|
90
|
+
const cacheDir = join(config.cacheDir, "remapped-mods");
|
|
91
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
92
|
+
const cachedOutput = join(cacheDir, `${cacheKey}.jar`);
|
|
93
|
+
if (existsSync(cachedOutput)) {
|
|
94
|
+
const outputJar = input.outputJar
|
|
95
|
+
? normalizePathForHost(input.outputJar, undefined, "outputJar")
|
|
96
|
+
: cachedOutput;
|
|
97
|
+
if (outputJar !== cachedOutput) {
|
|
98
|
+
const { copyFileSync } = await import("node:fs");
|
|
99
|
+
mkdirSync(dirname(outputJar), { recursive: true });
|
|
100
|
+
copyFileSync(cachedOutput, outputJar);
|
|
101
|
+
}
|
|
102
|
+
log("info", "remap.cache-hit", { inputJar: normalizedInput, outputJar });
|
|
103
|
+
return {
|
|
104
|
+
outputJar,
|
|
105
|
+
mcVersion,
|
|
106
|
+
fromMapping: fromNamespace,
|
|
107
|
+
targetMapping: input.targetMapping,
|
|
108
|
+
resolvedTargetNamespace,
|
|
109
|
+
durationMs: Date.now() - startedAt,
|
|
110
|
+
warnings: ["Result served from cache."]
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
// 5. Resolve tiny-remapper
|
|
114
|
+
const tinyRemapperJar = await resolveTinyRemapperJar(config.cacheDir, config.tinyRemapperJarPath);
|
|
115
|
+
// 6. Resolve mapping file and remap
|
|
116
|
+
let mappingsFile;
|
|
117
|
+
let toNamespace;
|
|
118
|
+
if (resolvedTargetNamespace === "yarn") {
|
|
119
|
+
mappingsFile = await resolveTinyMappingFile(mcVersion, "yarn", config.cacheDir);
|
|
120
|
+
toNamespace = "named";
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
const mojangTiny = await resolveMojangTinyFile(mcVersion, config);
|
|
124
|
+
mappingsFile = mojangTiny.path;
|
|
125
|
+
toNamespace = "mojang";
|
|
126
|
+
warnings.push(...mojangTiny.warnings);
|
|
127
|
+
}
|
|
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
|
+
mkdirSync(dirname(outputJar), { recursive: true });
|
|
136
|
+
// 8. Use temporary directory for intermediate work
|
|
137
|
+
const tempDir = join(tmpdir(), `mcp-remap-${cacheKey.slice(0, 12)}`);
|
|
138
|
+
mkdirSync(tempDir, { recursive: true });
|
|
139
|
+
try {
|
|
140
|
+
const tempOutput = join(tempDir, "remapped.jar");
|
|
141
|
+
await remapJar(tinyRemapperJar, {
|
|
142
|
+
inputJar: normalizedInput,
|
|
143
|
+
outputJar: tempOutput,
|
|
144
|
+
mappingsFile,
|
|
145
|
+
fromNamespace,
|
|
146
|
+
toNamespace,
|
|
147
|
+
timeoutMs: config.remapTimeoutMs,
|
|
148
|
+
maxMemoryMb: config.remapMaxMemoryMb
|
|
149
|
+
});
|
|
150
|
+
// Copy to final destination and cache
|
|
151
|
+
const { copyFileSync } = await import("node:fs");
|
|
152
|
+
copyFileSync(tempOutput, outputJar);
|
|
153
|
+
if (outputJar !== cachedOutput) {
|
|
154
|
+
mkdirSync(dirname(cachedOutput), { recursive: true });
|
|
155
|
+
copyFileSync(tempOutput, cachedOutput);
|
|
156
|
+
}
|
|
157
|
+
const durationMs = Date.now() - startedAt;
|
|
158
|
+
log("info", "remap.pipeline.done", {
|
|
159
|
+
inputJar: normalizedInput,
|
|
160
|
+
outputJar,
|
|
161
|
+
mcVersion,
|
|
162
|
+
fromMapping: fromNamespace,
|
|
163
|
+
targetMapping: input.targetMapping,
|
|
164
|
+
durationMs
|
|
165
|
+
});
|
|
166
|
+
return {
|
|
167
|
+
outputJar,
|
|
168
|
+
mcVersion,
|
|
169
|
+
fromMapping: fromNamespace,
|
|
170
|
+
targetMapping: input.targetMapping,
|
|
171
|
+
resolvedTargetNamespace,
|
|
172
|
+
durationMs,
|
|
173
|
+
warnings
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
finally {
|
|
177
|
+
// Cleanup temporary directory
|
|
178
|
+
try {
|
|
179
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
// best-effort cleanup
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=mod-remap-service.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ModDecompileService } from "./mod-decompile-service.js";
|
|
2
|
+
export type SearchModSourceSearchType = "class" | "method" | "field" | "content" | "all";
|
|
3
|
+
export type SearchModSourceInput = {
|
|
4
|
+
jarPath: string;
|
|
5
|
+
query: string;
|
|
6
|
+
searchType?: SearchModSourceSearchType;
|
|
7
|
+
limit?: number;
|
|
8
|
+
};
|
|
9
|
+
export type SearchModSourceHit = {
|
|
10
|
+
type: "class" | "method" | "field" | "content";
|
|
11
|
+
name: string;
|
|
12
|
+
file: string;
|
|
13
|
+
line?: number;
|
|
14
|
+
context?: string;
|
|
15
|
+
};
|
|
16
|
+
export type SearchModSourceOutput = {
|
|
17
|
+
query: string;
|
|
18
|
+
searchType: SearchModSourceSearchType;
|
|
19
|
+
hits: SearchModSourceHit[];
|
|
20
|
+
totalHits: number;
|
|
21
|
+
truncated: boolean;
|
|
22
|
+
warnings: string[];
|
|
23
|
+
};
|
|
24
|
+
export declare class ModSearchService {
|
|
25
|
+
private readonly modDecompileService;
|
|
26
|
+
constructor(modDecompileService: ModDecompileService);
|
|
27
|
+
searchModSource(input: SearchModSourceInput): Promise<SearchModSourceOutput>;
|
|
28
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { createError, ERROR_CODES } from "./errors.js";
|
|
4
|
+
import { log } from "./logger.js";
|
|
5
|
+
import { validateAndNormalizeJarPath } from "./path-resolver.js";
|
|
6
|
+
const DEFAULT_LIMIT = 50;
|
|
7
|
+
const MAX_LIMIT = 200;
|
|
8
|
+
const MAX_QUERY_LENGTH = 200;
|
|
9
|
+
const CONTEXT_LINES = 1;
|
|
10
|
+
const METHOD_PATTERN = /^\s*(public|private|protected)\s+.*\(/;
|
|
11
|
+
const FIELD_PATTERN = /^\s*(public|private|protected)\s+(?:static\s+)?(?:final\s+)?[\w<>,\[\]?]+\s+\w+\s*[;=]/;
|
|
12
|
+
function buildRegex(query) {
|
|
13
|
+
try {
|
|
14
|
+
return new RegExp(query, "gi");
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// If the query is not valid regex, escape it and use as literal
|
|
18
|
+
const escaped = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
19
|
+
try {
|
|
20
|
+
return new RegExp(escaped, "gi");
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function classifyLine(line) {
|
|
28
|
+
if (METHOD_PATTERN.test(line))
|
|
29
|
+
return "method";
|
|
30
|
+
if (FIELD_PATTERN.test(line))
|
|
31
|
+
return "field";
|
|
32
|
+
return "content";
|
|
33
|
+
}
|
|
34
|
+
function extractContext(lines, lineIndex) {
|
|
35
|
+
const start = Math.max(0, lineIndex - CONTEXT_LINES);
|
|
36
|
+
const end = Math.min(lines.length - 1, lineIndex + CONTEXT_LINES);
|
|
37
|
+
return lines.slice(start, end + 1).join("\n");
|
|
38
|
+
}
|
|
39
|
+
function filePathToClassName(filePath) {
|
|
40
|
+
return filePath.replace(/\.java$/, "").replaceAll("/", ".");
|
|
41
|
+
}
|
|
42
|
+
export class ModSearchService {
|
|
43
|
+
modDecompileService;
|
|
44
|
+
constructor(modDecompileService) {
|
|
45
|
+
this.modDecompileService = modDecompileService;
|
|
46
|
+
}
|
|
47
|
+
async searchModSource(input) {
|
|
48
|
+
const jarPath = validateAndNormalizeJarPath(input.jarPath);
|
|
49
|
+
const query = input.query.trim();
|
|
50
|
+
const searchType = input.searchType ?? "all";
|
|
51
|
+
const requestedLimit = Math.max(1, Math.trunc(input.limit ?? DEFAULT_LIMIT));
|
|
52
|
+
const limit = Math.min(requestedLimit, MAX_LIMIT);
|
|
53
|
+
if (!query) {
|
|
54
|
+
throw createError({
|
|
55
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
56
|
+
message: "query must be non-empty."
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
if (query.length > MAX_QUERY_LENGTH) {
|
|
60
|
+
throw createError({
|
|
61
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
62
|
+
message: `query exceeds max length of ${MAX_QUERY_LENGTH} characters.`,
|
|
63
|
+
details: { queryLength: query.length, maxLength: MAX_QUERY_LENGTH }
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
const regex = buildRegex(query);
|
|
67
|
+
if (!regex) {
|
|
68
|
+
throw createError({
|
|
69
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
70
|
+
message: `Invalid search query: "${query}".`,
|
|
71
|
+
details: { query }
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const warnings = [];
|
|
75
|
+
if (requestedLimit > MAX_LIMIT) {
|
|
76
|
+
warnings.push(`limit was clamped to ${MAX_LIMIT} from ${requestedLimit}.`);
|
|
77
|
+
}
|
|
78
|
+
const startedAt = Date.now();
|
|
79
|
+
const decompileResult = await this.modDecompileService.decompileModJar({ jarPath });
|
|
80
|
+
const outputDir = decompileResult.outputDir;
|
|
81
|
+
warnings.push(...decompileResult.warnings);
|
|
82
|
+
const classNames = decompileResult.files ?? [];
|
|
83
|
+
const hits = [];
|
|
84
|
+
let totalHits = 0;
|
|
85
|
+
let reachedLimit = false;
|
|
86
|
+
for (const className of classNames) {
|
|
87
|
+
if (hits.length >= limit) {
|
|
88
|
+
reachedLimit = true;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
const filePath = className.replaceAll(".", "/") + ".java";
|
|
92
|
+
// Class name search: check if the simple class name matches
|
|
93
|
+
if (searchType === "class" || searchType === "all") {
|
|
94
|
+
const simpleClassName = className.split(".").pop() ?? className;
|
|
95
|
+
regex.lastIndex = 0;
|
|
96
|
+
if (regex.test(simpleClassName)) {
|
|
97
|
+
totalHits++;
|
|
98
|
+
if (hits.length < limit) {
|
|
99
|
+
hits.push({
|
|
100
|
+
type: "class",
|
|
101
|
+
name: className,
|
|
102
|
+
file: filePath
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
// If searching only classes, skip content search for this file
|
|
106
|
+
if (searchType === "class")
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Content/method/field search: read and scan the file
|
|
111
|
+
if (searchType === "method" || searchType === "field" || searchType === "content" || searchType === "all") {
|
|
112
|
+
let content;
|
|
113
|
+
try {
|
|
114
|
+
content = readFileSync(join(outputDir, filePath), "utf8");
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// File might not exist at the expected path, skip
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const lines = content.split("\n");
|
|
121
|
+
for (let i = 0; i < lines.length; i++) {
|
|
122
|
+
if (hits.length >= limit) {
|
|
123
|
+
reachedLimit = true;
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
regex.lastIndex = 0;
|
|
127
|
+
if (!regex.test(lines[i]))
|
|
128
|
+
continue;
|
|
129
|
+
const lineType = classifyLine(lines[i]);
|
|
130
|
+
// Filter by search type
|
|
131
|
+
if (searchType !== "all" && searchType !== lineType)
|
|
132
|
+
continue;
|
|
133
|
+
totalHits++;
|
|
134
|
+
if (hits.length < limit) {
|
|
135
|
+
hits.push({
|
|
136
|
+
type: lineType,
|
|
137
|
+
name: lineType === "content" ? className : extractSymbolName(lines[i], lineType),
|
|
138
|
+
file: filePath,
|
|
139
|
+
line: i + 1,
|
|
140
|
+
context: extractContext(lines, i)
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
log("info", "mod-search.done", {
|
|
147
|
+
jarPath,
|
|
148
|
+
query,
|
|
149
|
+
searchType,
|
|
150
|
+
hitCount: hits.length,
|
|
151
|
+
durationMs: Date.now() - startedAt
|
|
152
|
+
});
|
|
153
|
+
return {
|
|
154
|
+
query,
|
|
155
|
+
searchType,
|
|
156
|
+
hits,
|
|
157
|
+
totalHits,
|
|
158
|
+
truncated: reachedLimit,
|
|
159
|
+
warnings
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function extractSymbolName(line, type) {
|
|
164
|
+
const trimmed = line.trim();
|
|
165
|
+
if (type === "method") {
|
|
166
|
+
// Extract method name: last identifier before '('
|
|
167
|
+
const match = trimmed.match(/(\w+)\s*\(/);
|
|
168
|
+
return match?.[1] ?? trimmed.slice(0, 60);
|
|
169
|
+
}
|
|
170
|
+
// Extract field name: last identifier before '=' or ';'
|
|
171
|
+
const match = trimmed.match(/(\w+)\s*[;=]/);
|
|
172
|
+
return match?.[1] ?? trimmed.slice(0, 60);
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=mod-search-service.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Config } from "./types.js";
|
|
2
|
+
import { VersionService } from "./version-service.js";
|
|
3
|
+
type VersionMappingsResolver = Pick<VersionService, "resolveVersionMappings">;
|
|
4
|
+
export interface ResolveMojangTinyDeps {
|
|
5
|
+
fetchFn?: typeof fetch;
|
|
6
|
+
versionService?: VersionMappingsResolver;
|
|
7
|
+
}
|
|
8
|
+
export interface ResolveMojangTinyResult {
|
|
9
|
+
path: string;
|
|
10
|
+
warnings: string[];
|
|
11
|
+
}
|
|
12
|
+
export declare function resolveMojangTinyFile(version: string, config: Config, deps?: ResolveMojangTinyDeps): Promise<ResolveMojangTinyResult>;
|
|
13
|
+
export {};
|