@adhisang/minecraft-modding-mcp 3.1.1 → 4.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 +49 -0
- package/README.md +37 -18
- package/dist/access-transformer-parser.d.ts +17 -0
- package/dist/access-transformer-parser.js +97 -0
- package/dist/cache-registry.d.ts +1 -1
- package/dist/cache-registry.js +10 -2
- package/dist/concurrency.d.ts +1 -0
- package/dist/concurrency.js +24 -0
- package/dist/config.d.ts +10 -1
- package/dist/config.js +52 -1
- 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 +22 -22
- package/dist/entry-tools/analyze-symbol-service.js +13 -2
- package/dist/entry-tools/inspect-minecraft-service.d.ts +168 -168
- package/dist/entry-tools/inspect-minecraft-service.js +8 -2
- package/dist/entry-tools/manage-cache-service.d.ts +4 -4
- package/dist/entry-tools/validate-project-service.d.ts +153 -16
- package/dist/entry-tools/validate-project-service.js +442 -25
- package/dist/gradle-paths.d.ts +4 -0
- package/dist/gradle-paths.js +57 -0
- package/dist/index.js +148 -30
- package/dist/lru-list.d.ts +31 -0
- package/dist/lru-list.js +102 -0
- package/dist/mapping-pipeline-service.d.ts +12 -1
- package/dist/mapping-pipeline-service.js +28 -1
- package/dist/mapping-service.d.ts +16 -0
- package/dist/mapping-service.js +405 -68
- 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 +218 -17
- 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/observability.d.ts +18 -1
- package/dist/observability.js +44 -1
- package/dist/response-utils.d.ts +69 -0
- package/dist/response-utils.js +227 -0
- package/dist/source-jar-reader.d.ts +16 -0
- package/dist/source-jar-reader.js +103 -1
- package/dist/source-resolver.d.ts +9 -1
- package/dist/source-resolver.js +23 -16
- package/dist/source-service.d.ts +119 -3
- package/dist/source-service.js +1836 -218
- package/dist/storage/artifacts-repo.d.ts +4 -1
- package/dist/storage/artifacts-repo.js +33 -5
- package/dist/storage/files-repo.d.ts +0 -2
- package/dist/storage/files-repo.js +0 -11
- package/dist/storage/migrations.d.ts +1 -1
- package/dist/storage/migrations.js +10 -2
- package/dist/storage/schema.d.ts +2 -0
- package/dist/storage/schema.js +25 -0
- package/dist/tool-contract-manifest.js +8 -6
- package/dist/types.d.ts +20 -0
- package/dist/workspace-mapping-service.d.ts +13 -0
- package/dist/workspace-mapping-service.js +146 -14
- package/package.json +3 -1
|
@@ -2,12 +2,14 @@ import { readFile } from "node:fs/promises";
|
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import fastGlob from "fast-glob";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
+
import { mapWithConcurrencyLimit } from "../concurrency.js";
|
|
5
6
|
import { createError, ERROR_CODES } from "../errors.js";
|
|
6
7
|
import { buildIncludeSchema, detailSchema } from "./entry-tool-schema.js";
|
|
7
8
|
import { buildEntryToolResult, createSummarySubject } from "./response-contract.js";
|
|
8
9
|
import { resolveDetail, resolveInclude } from "./request-normalizers.js";
|
|
9
10
|
const nonEmptyString = z.string().trim().min(1);
|
|
10
11
|
const INCLUDE_GROUPS = ["warnings", "issues", "workspace", "recovery"];
|
|
12
|
+
const WORKSPACE_TEXT_FILE_READ_CONCURRENCY = 4;
|
|
11
13
|
const mixinInputSchema = z.discriminatedUnion("mode", [
|
|
12
14
|
z.object({ mode: z.literal("inline"), source: nonEmptyString }),
|
|
13
15
|
z.object({ mode: z.literal("path"), path: nonEmptyString }),
|
|
@@ -19,11 +21,15 @@ const accessWidenerInputSchema = z.discriminatedUnion("mode", [
|
|
|
19
21
|
z.object({ mode: z.literal("inline"), content: nonEmptyString }),
|
|
20
22
|
z.object({ mode: z.literal("path"), path: nonEmptyString })
|
|
21
23
|
]);
|
|
24
|
+
const accessTransformerInputSchema = z.discriminatedUnion("mode", [
|
|
25
|
+
z.object({ mode: z.literal("inline"), content: nonEmptyString }),
|
|
26
|
+
z.object({ mode: z.literal("path"), path: nonEmptyString })
|
|
27
|
+
]);
|
|
22
28
|
const subjectSchema = z.discriminatedUnion("kind", [
|
|
23
29
|
z.object({
|
|
24
30
|
kind: z.literal("workspace"),
|
|
25
31
|
projectPath: nonEmptyString,
|
|
26
|
-
discover: z.array(z.enum(["mixins", "access-wideners"])).optional()
|
|
32
|
+
discover: z.array(z.enum(["mixins", "access-wideners", "access-transformers"])).optional()
|
|
27
33
|
}),
|
|
28
34
|
z.object({
|
|
29
35
|
kind: z.literal("mixin"),
|
|
@@ -32,13 +38,18 @@ const subjectSchema = z.discriminatedUnion("kind", [
|
|
|
32
38
|
z.object({
|
|
33
39
|
kind: z.literal("access-widener"),
|
|
34
40
|
input: accessWidenerInputSchema
|
|
41
|
+
}),
|
|
42
|
+
z.object({
|
|
43
|
+
kind: z.literal("access-transformer"),
|
|
44
|
+
input: accessTransformerInputSchema
|
|
35
45
|
})
|
|
36
46
|
]);
|
|
37
47
|
export const validateProjectShape = {
|
|
38
|
-
task: z.enum(["project-summary", "mixin", "access-widener"]),
|
|
48
|
+
task: z.enum(["project-summary", "mixin", "access-widener", "access-transformer"]),
|
|
39
49
|
subject: subjectSchema,
|
|
40
50
|
version: nonEmptyString.optional(),
|
|
41
51
|
mapping: z.enum(["obfuscated", "mojang", "intermediary", "yarn"]).optional(),
|
|
52
|
+
atNamespace: z.enum(["srg", "mojang", "obfuscated"]).optional(),
|
|
42
53
|
sourcePriority: z.enum(["loom-first", "maven-first"]).optional(),
|
|
43
54
|
scope: z.enum(["vanilla", "merged", "loader"]).optional(),
|
|
44
55
|
preferProjectVersion: z.boolean().optional(),
|
|
@@ -77,6 +88,13 @@ export const validateProjectSchema = z.object(validateProjectShape).superRefine(
|
|
|
77
88
|
message: "task=access-widener requires subject.kind=access-widener."
|
|
78
89
|
});
|
|
79
90
|
}
|
|
91
|
+
if (value.task === "access-transformer" && value.subject.kind !== "access-transformer") {
|
|
92
|
+
ctx.addIssue({
|
|
93
|
+
code: z.ZodIssueCode.custom,
|
|
94
|
+
path: ["subject", "kind"],
|
|
95
|
+
message: "task=access-transformer requires subject.kind=access-transformer."
|
|
96
|
+
});
|
|
97
|
+
}
|
|
80
98
|
if (value.configPaths?.length && value.task !== "project-summary") {
|
|
81
99
|
ctx.addIssue({
|
|
82
100
|
code: z.ZodIssueCode.custom,
|
|
@@ -89,33 +107,153 @@ export async function discoverWorkspaceMixins(projectPath, configPaths) {
|
|
|
89
107
|
if (configPaths?.length) {
|
|
90
108
|
return [...configPaths];
|
|
91
109
|
}
|
|
92
|
-
return fastGlob.
|
|
110
|
+
return (await fastGlob.glob(["**/*.mixins.json"], {
|
|
93
111
|
cwd: projectPath,
|
|
94
112
|
absolute: true,
|
|
95
113
|
onlyFiles: true,
|
|
96
114
|
ignore: ["**/.git/**", "**/build/**", "**/out/**", "**/node_modules/**"]
|
|
97
|
-
});
|
|
115
|
+
})).sort((left, right) => left.localeCompare(right));
|
|
98
116
|
}
|
|
99
117
|
export async function discoverWorkspaceAccessWideners(projectPath) {
|
|
100
|
-
const descriptorFiles = fastGlob.
|
|
118
|
+
const descriptorFiles = (await fastGlob.glob(["fabric.mod.json", "quilt.mod.json", "**/fabric.mod.json", "**/quilt.mod.json"], {
|
|
101
119
|
cwd: projectPath,
|
|
102
120
|
absolute: true,
|
|
103
121
|
onlyFiles: true,
|
|
104
122
|
ignore: ["**/.git/**", "**/build/**", "**/out/**", "**/node_modules/**"]
|
|
105
|
-
});
|
|
123
|
+
})).sort((left, right) => left.localeCompare(right));
|
|
106
124
|
const discovered = new Set();
|
|
107
|
-
|
|
125
|
+
const matches = await mapWithConcurrencyLimit(descriptorFiles, WORKSPACE_TEXT_FILE_READ_CONCURRENCY, async (descriptorPath) => {
|
|
108
126
|
try {
|
|
109
127
|
const parsed = JSON.parse(await readFile(descriptorPath, "utf8"));
|
|
110
128
|
const relative = parsed.accessWidener ?? parsed.access_widener;
|
|
111
|
-
|
|
112
|
-
|
|
129
|
+
return relative ? [resolve(descriptorPath, "..", relative)] : [];
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
for (const matchList of matches) {
|
|
136
|
+
for (const match of matchList) {
|
|
137
|
+
discovered.add(match);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return [...discovered].sort((left, right) => left.localeCompare(right));
|
|
141
|
+
}
|
|
142
|
+
function addDiscoveredPath(discovered, filePath, relativePath) {
|
|
143
|
+
const trimmed = relativePath?.trim();
|
|
144
|
+
if (!trimmed) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
discovered.add(resolve(filePath, "..", trimmed));
|
|
148
|
+
}
|
|
149
|
+
function collectFileArgumentMatches(content, filePath, discovered, pattern) {
|
|
150
|
+
for (const match of content.matchAll(pattern)) {
|
|
151
|
+
addDiscoveredPath(discovered, filePath, match[2]);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function extractNamedDslBlocks(content, blockName) {
|
|
155
|
+
const blocks = [];
|
|
156
|
+
const blockPattern = new RegExp(`${blockName}\\s*\\{`, "g");
|
|
157
|
+
for (const match of content.matchAll(blockPattern)) {
|
|
158
|
+
const blockStart = (match.index ?? -1) + match[0].length;
|
|
159
|
+
if (blockStart < match[0].length) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
let depth = 1;
|
|
163
|
+
for (let index = blockStart; index < content.length; index++) {
|
|
164
|
+
const char = content[index];
|
|
165
|
+
if (char === "{") {
|
|
166
|
+
depth++;
|
|
113
167
|
}
|
|
168
|
+
else if (char === "}") {
|
|
169
|
+
depth--;
|
|
170
|
+
}
|
|
171
|
+
if (depth === 0) {
|
|
172
|
+
blocks.push(content.slice(blockStart, index));
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return blocks;
|
|
178
|
+
}
|
|
179
|
+
function collectTomlAccessTransformerEntries(content, filePath, discovered) {
|
|
180
|
+
const lines = content.split(/\r?\n/);
|
|
181
|
+
let currentBlock = [];
|
|
182
|
+
const flushBlock = () => {
|
|
183
|
+
if (currentBlock.length === 0) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
for (const match of currentBlock.join("\n").matchAll(/^\s*file\s*=\s*(["'])(.+?)\1\s*$/gm)) {
|
|
187
|
+
addDiscoveredPath(discovered, filePath, match[2]);
|
|
188
|
+
}
|
|
189
|
+
currentBlock = [];
|
|
190
|
+
};
|
|
191
|
+
for (const line of lines) {
|
|
192
|
+
if (/^\s*\[\[accessTransformers\]\]\s*$/.test(line)) {
|
|
193
|
+
flushBlock();
|
|
194
|
+
currentBlock.push(line);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (currentBlock.length > 0 && /^\s*(?:\[\[.*\]\]|\[[^\[])/.test(line)) {
|
|
198
|
+
flushBlock();
|
|
199
|
+
}
|
|
200
|
+
if (currentBlock.length > 0) {
|
|
201
|
+
currentBlock.push(line);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
flushBlock();
|
|
205
|
+
}
|
|
206
|
+
export async function discoverWorkspaceAccessTransformers(projectPath) {
|
|
207
|
+
const discovered = new Set();
|
|
208
|
+
const textFiles = (await fastGlob.glob([
|
|
209
|
+
"build.gradle",
|
|
210
|
+
"build.gradle.kts",
|
|
211
|
+
"META-INF/mods.toml",
|
|
212
|
+
"META-INF/neoforge.mods.toml",
|
|
213
|
+
"**/build.gradle",
|
|
214
|
+
"**/build.gradle.kts",
|
|
215
|
+
"**/META-INF/mods.toml",
|
|
216
|
+
"**/META-INF/neoforge.mods.toml"
|
|
217
|
+
], {
|
|
218
|
+
cwd: projectPath,
|
|
219
|
+
absolute: true,
|
|
220
|
+
onlyFiles: true,
|
|
221
|
+
ignore: ["**/.git/**", "**/build/**", "**/out/**", "**/node_modules/**"]
|
|
222
|
+
})).sort((left, right) => left.localeCompare(right));
|
|
223
|
+
const discoveredByFile = await mapWithConcurrencyLimit(textFiles, WORKSPACE_TEXT_FILE_READ_CONCURRENCY, async (filePath) => {
|
|
224
|
+
let content;
|
|
225
|
+
try {
|
|
226
|
+
content = await readFile(filePath, "utf8");
|
|
114
227
|
}
|
|
115
228
|
catch {
|
|
116
|
-
|
|
229
|
+
return [];
|
|
230
|
+
}
|
|
231
|
+
const perFileDiscovered = new Set();
|
|
232
|
+
collectFileArgumentMatches(content, filePath, perFileDiscovered, /accessTransformer\s*=\s*file\(\s*(["'])(.+?)\1\s*\)/g);
|
|
233
|
+
collectFileArgumentMatches(content, filePath, perFileDiscovered, /accessTransformers\.from\s*\(\s*file\(\s*(["'])(.+?)\1\s*\)\s*\)/g);
|
|
234
|
+
for (const block of extractNamedDslBlocks(content, "accessTransformers")) {
|
|
235
|
+
collectFileArgumentMatches(block, filePath, perFileDiscovered, /file\(\s*(["'])(.+?)\1\s*\)/g);
|
|
236
|
+
}
|
|
237
|
+
collectTomlAccessTransformerEntries(content, filePath, perFileDiscovered);
|
|
238
|
+
return [...perFileDiscovered];
|
|
239
|
+
});
|
|
240
|
+
for (const matchList of discoveredByFile) {
|
|
241
|
+
for (const match of matchList) {
|
|
242
|
+
discovered.add(match);
|
|
117
243
|
}
|
|
118
244
|
}
|
|
245
|
+
for (const fallbackPath of (await fastGlob.glob([
|
|
246
|
+
"**/META-INF/accesstransformer.cfg",
|
|
247
|
+
"**/*_at.cfg",
|
|
248
|
+
"**/accesstransformer*.cfg"
|
|
249
|
+
], {
|
|
250
|
+
cwd: projectPath,
|
|
251
|
+
absolute: true,
|
|
252
|
+
onlyFiles: true,
|
|
253
|
+
ignore: ["**/.git/**", "**/build/**", "**/out/**", "**/node_modules/**"]
|
|
254
|
+
})).sort((left, right) => left.localeCompare(right))) {
|
|
255
|
+
discovered.add(fallbackPath);
|
|
256
|
+
}
|
|
119
257
|
return [...discovered].sort((left, right) => left.localeCompare(right));
|
|
120
258
|
}
|
|
121
259
|
export class ValidateProjectService {
|
|
@@ -131,7 +269,32 @@ export class ValidateProjectService {
|
|
|
131
269
|
if (input.subject.kind !== "mixin") {
|
|
132
270
|
throw createError({
|
|
133
271
|
code: ERROR_CODES.INVALID_INPUT,
|
|
134
|
-
message: "task=mixin requires subject.kind=mixin."
|
|
272
|
+
message: "task=mixin requires subject.kind=mixin.",
|
|
273
|
+
details: {
|
|
274
|
+
task: input.task,
|
|
275
|
+
subjectKind: input.subject.kind,
|
|
276
|
+
failedStage: "input-validation",
|
|
277
|
+
nextAction: "Set subject.kind to \"mixin\" for task=\"mixin\"."
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
if (!input.version) {
|
|
282
|
+
throw createError({
|
|
283
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
284
|
+
message: "task=mixin requires version.",
|
|
285
|
+
details: {
|
|
286
|
+
task: "mixin",
|
|
287
|
+
failedStage: "input-validation",
|
|
288
|
+
nextAction: "Pass version explicitly (e.g. \"1.21.10\"). task=\"project-summary\" supports preferProjectVersion for auto-detection from gradle.properties, but direct task=\"mixin\" requires an explicit version.",
|
|
289
|
+
suggestedCall: {
|
|
290
|
+
tool: "validate-project",
|
|
291
|
+
params: {
|
|
292
|
+
task: "mixin",
|
|
293
|
+
subject: input.subject,
|
|
294
|
+
version: "1.21.10"
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
135
298
|
});
|
|
136
299
|
}
|
|
137
300
|
const output = await this.deps.validateMixin({
|
|
@@ -192,7 +355,32 @@ export class ValidateProjectService {
|
|
|
192
355
|
if (input.subject.kind !== "access-widener") {
|
|
193
356
|
throw createError({
|
|
194
357
|
code: ERROR_CODES.INVALID_INPUT,
|
|
195
|
-
message: "task=access-widener requires subject.kind=access-widener."
|
|
358
|
+
message: "task=access-widener requires subject.kind=access-widener.",
|
|
359
|
+
details: {
|
|
360
|
+
task: input.task,
|
|
361
|
+
subjectKind: input.subject.kind,
|
|
362
|
+
failedStage: "input-validation",
|
|
363
|
+
nextAction: "Set subject.kind to \"access-widener\" for task=\"access-widener\"."
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
if (!input.version) {
|
|
368
|
+
throw createError({
|
|
369
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
370
|
+
message: "task=access-widener requires version.",
|
|
371
|
+
details: {
|
|
372
|
+
task: "access-widener",
|
|
373
|
+
failedStage: "input-validation",
|
|
374
|
+
nextAction: "Pass version explicitly (e.g. \"1.21.10\"). Access Widener validation resolves class names against a specific Minecraft version.",
|
|
375
|
+
suggestedCall: {
|
|
376
|
+
tool: "validate-project",
|
|
377
|
+
params: {
|
|
378
|
+
task: "access-widener",
|
|
379
|
+
subject: input.subject,
|
|
380
|
+
version: "1.21.10"
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
196
384
|
});
|
|
197
385
|
}
|
|
198
386
|
const content = input.subject.input.mode === "inline"
|
|
@@ -202,7 +390,9 @@ export class ValidateProjectService {
|
|
|
202
390
|
content,
|
|
203
391
|
version: input.version,
|
|
204
392
|
mapping: input.mapping,
|
|
205
|
-
sourcePriority: input.sourcePriority
|
|
393
|
+
sourcePriority: input.sourcePriority,
|
|
394
|
+
scope: input.scope,
|
|
395
|
+
preferProjectVersion: input.preferProjectVersion
|
|
206
396
|
});
|
|
207
397
|
return {
|
|
208
398
|
...buildEntryToolResult({
|
|
@@ -242,6 +432,107 @@ export class ValidateProjectService {
|
|
|
242
432
|
warnings: Array.isArray(output.warnings) ? output.warnings : []
|
|
243
433
|
};
|
|
244
434
|
}
|
|
435
|
+
case "access-transformer": {
|
|
436
|
+
if (input.subject.kind !== "access-transformer") {
|
|
437
|
+
throw createError({
|
|
438
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
439
|
+
message: "task=access-transformer requires subject.kind=access-transformer.",
|
|
440
|
+
details: {
|
|
441
|
+
task: input.task,
|
|
442
|
+
subjectKind: input.subject.kind,
|
|
443
|
+
failedStage: "input-validation",
|
|
444
|
+
nextAction: "Set subject.kind to \"access-transformer\" for task=\"access-transformer\"."
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
if (!input.version) {
|
|
449
|
+
throw createError({
|
|
450
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
451
|
+
message: "task=access-transformer requires version.",
|
|
452
|
+
details: {
|
|
453
|
+
task: "access-transformer",
|
|
454
|
+
failedStage: "input-validation",
|
|
455
|
+
nextAction: "Pass version explicitly (e.g. \"1.21.10\"). Access Transformer validation resolves class names against a specific Minecraft version.",
|
|
456
|
+
suggestedCall: {
|
|
457
|
+
tool: "validate-project",
|
|
458
|
+
params: {
|
|
459
|
+
task: "access-transformer",
|
|
460
|
+
subject: input.subject,
|
|
461
|
+
version: "1.21.10"
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
const content = input.subject.input.mode === "inline"
|
|
468
|
+
? input.subject.input.content
|
|
469
|
+
: await readFile(input.subject.input.path, "utf8");
|
|
470
|
+
if (!this.deps.validateAccessTransformer) {
|
|
471
|
+
throw createError({
|
|
472
|
+
code: ERROR_CODES.CONTEXT_UNRESOLVED,
|
|
473
|
+
message: "Access Transformer validation is not configured.",
|
|
474
|
+
details: {
|
|
475
|
+
task: "access-transformer",
|
|
476
|
+
failedStage: "dependency-resolution",
|
|
477
|
+
nextAction: "The current runtime was built without an Access Transformer validator. Rebuild the MCP server with validateAccessTransformer configured, or use task=\"access-widener\" if the workspace uses Fabric AccessWideners."
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
const output = await this.deps.validateAccessTransformer({
|
|
482
|
+
content,
|
|
483
|
+
version: input.version,
|
|
484
|
+
atNamespace: input.atNamespace,
|
|
485
|
+
sourcePriority: input.sourcePriority,
|
|
486
|
+
scope: input.scope,
|
|
487
|
+
preferProjectVersion: input.preferProjectVersion
|
|
488
|
+
});
|
|
489
|
+
const issueEntries = Array.isArray(output.entries)
|
|
490
|
+
? output.entries.filter((entry) => {
|
|
491
|
+
if (!entry || typeof entry !== "object" || !("valid" in entry)) {
|
|
492
|
+
return true;
|
|
493
|
+
}
|
|
494
|
+
return entry.valid !== true;
|
|
495
|
+
})
|
|
496
|
+
: undefined;
|
|
497
|
+
return {
|
|
498
|
+
...buildEntryToolResult({
|
|
499
|
+
task: "access-transformer",
|
|
500
|
+
detail,
|
|
501
|
+
include,
|
|
502
|
+
summary: {
|
|
503
|
+
status: output.valid ? "ok" : "invalid",
|
|
504
|
+
headline: output.valid
|
|
505
|
+
? "Access Transformer is valid."
|
|
506
|
+
: "Access Transformer contains validation issues.",
|
|
507
|
+
subject: createSummarySubject({
|
|
508
|
+
task: "access-transformer",
|
|
509
|
+
kind: input.subject.kind,
|
|
510
|
+
input: input.subject.input,
|
|
511
|
+
version: input.version,
|
|
512
|
+
sourcePriority: input.sourcePriority,
|
|
513
|
+
scope: input.scope,
|
|
514
|
+
atNamespace: input.atNamespace
|
|
515
|
+
}),
|
|
516
|
+
counts: {
|
|
517
|
+
valid: output.valid ? 1 : 0,
|
|
518
|
+
invalid: output.valid ? 0 : 1
|
|
519
|
+
}
|
|
520
|
+
},
|
|
521
|
+
blocks: {
|
|
522
|
+
project: {
|
|
523
|
+
summary: {
|
|
524
|
+
total: 1,
|
|
525
|
+
valid: output.valid ? 1 : 0,
|
|
526
|
+
invalid: output.valid ? 0 : 1
|
|
527
|
+
}
|
|
528
|
+
},
|
|
529
|
+
issues: include.includes("issues") || detail !== "summary" ? issueEntries : undefined
|
|
530
|
+
},
|
|
531
|
+
alwaysBlocks: ["project"]
|
|
532
|
+
}),
|
|
533
|
+
warnings: Array.isArray(output.warnings) ? output.warnings : []
|
|
534
|
+
};
|
|
535
|
+
}
|
|
245
536
|
case "project-summary": {
|
|
246
537
|
if (input.subject.kind !== "workspace") {
|
|
247
538
|
throw createError({
|
|
@@ -269,10 +560,12 @@ export class ValidateProjectService {
|
|
|
269
560
|
tool: "validate-project",
|
|
270
561
|
params: {
|
|
271
562
|
task: "project-summary",
|
|
272
|
-
version: "1.21.10",
|
|
273
563
|
subject: input.subject
|
|
274
564
|
}
|
|
275
565
|
}
|
|
566
|
+
],
|
|
567
|
+
notes: [
|
|
568
|
+
"Pass version explicitly, or retry with preferProjectVersion=true when gradle.properties declares the Minecraft version."
|
|
276
569
|
]
|
|
277
570
|
},
|
|
278
571
|
blocks: {
|
|
@@ -285,15 +578,98 @@ export class ValidateProjectService {
|
|
|
285
578
|
};
|
|
286
579
|
}
|
|
287
580
|
const projectPath = input.subject.projectPath;
|
|
581
|
+
const detectedProjectVersion = input.preferProjectVersion
|
|
582
|
+
? await this.deps.detectProjectMinecraftVersion?.(projectPath)
|
|
583
|
+
: undefined;
|
|
584
|
+
const resolvedVersion = detectedProjectVersion ?? input.version;
|
|
288
585
|
const discover = input.subject.discover ?? ["mixins", "access-wideners"];
|
|
289
|
-
const [mixinConfigs, accessWideners] = await Promise.all([
|
|
586
|
+
const [mixinConfigs, accessWideners, accessTransformers] = await Promise.all([
|
|
290
587
|
discover.includes("mixins")
|
|
291
588
|
? this.deps.discoverMixins(projectPath, input.configPaths)
|
|
292
589
|
: Promise.resolve([]),
|
|
293
590
|
discover.includes("access-wideners")
|
|
294
591
|
? this.deps.discoverAccessWideners(projectPath)
|
|
592
|
+
: Promise.resolve([]),
|
|
593
|
+
discover.includes("access-transformers")
|
|
594
|
+
? this.deps.discoverAccessTransformers?.(projectPath) ?? Promise.resolve([])
|
|
295
595
|
: Promise.resolve([])
|
|
296
596
|
]);
|
|
597
|
+
if (!resolvedVersion && (mixinConfigs.length > 0 || accessWideners.length > 0 || accessTransformers.length > 0)) {
|
|
598
|
+
return {
|
|
599
|
+
...buildEntryToolResult({
|
|
600
|
+
task: "project-summary",
|
|
601
|
+
detail,
|
|
602
|
+
include,
|
|
603
|
+
summary: {
|
|
604
|
+
status: "blocked",
|
|
605
|
+
headline: "Could not resolve Minecraft version for discovered workspace validators.",
|
|
606
|
+
subject: createSummarySubject({
|
|
607
|
+
task: "project-summary",
|
|
608
|
+
kind: input.subject.kind,
|
|
609
|
+
projectPath,
|
|
610
|
+
discover: input.subject.discover,
|
|
611
|
+
mapping: input.mapping,
|
|
612
|
+
sourcePriority: input.sourcePriority,
|
|
613
|
+
scope: input.scope
|
|
614
|
+
}),
|
|
615
|
+
nextActions: [
|
|
616
|
+
{
|
|
617
|
+
tool: "validate-project",
|
|
618
|
+
params: {
|
|
619
|
+
task: "project-summary",
|
|
620
|
+
subject: input.subject
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
],
|
|
624
|
+
notes: [
|
|
625
|
+
"Pass version explicitly, or make sure gradle.properties declares the Minecraft version before using preferProjectVersion=true."
|
|
626
|
+
]
|
|
627
|
+
},
|
|
628
|
+
blocks: {
|
|
629
|
+
workspace: {
|
|
630
|
+
projectPath
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}),
|
|
634
|
+
warnings: [
|
|
635
|
+
"Could not resolve Minecraft version from gradle.properties for discovered workspace validators."
|
|
636
|
+
]
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
if (!resolvedVersion) {
|
|
640
|
+
return {
|
|
641
|
+
...buildEntryToolResult({
|
|
642
|
+
task: "project-summary",
|
|
643
|
+
detail,
|
|
644
|
+
include,
|
|
645
|
+
summary: {
|
|
646
|
+
status: "ok",
|
|
647
|
+
headline: `Validated ${mixinConfigs.length} mixin config(s), ${accessWideners.length} access widener(s), and ${accessTransformers.length} access transformer(s).`,
|
|
648
|
+
subject: createSummarySubject({
|
|
649
|
+
task: "project-summary",
|
|
650
|
+
kind: input.subject.kind,
|
|
651
|
+
projectPath,
|
|
652
|
+
discover: input.subject.discover,
|
|
653
|
+
mapping: input.mapping,
|
|
654
|
+
sourcePriority: input.sourcePriority,
|
|
655
|
+
scope: input.scope
|
|
656
|
+
}),
|
|
657
|
+
counts: {
|
|
658
|
+
valid: 0,
|
|
659
|
+
partial: 0,
|
|
660
|
+
invalid: 0
|
|
661
|
+
}
|
|
662
|
+
},
|
|
663
|
+
blocks: {
|
|
664
|
+
workspace: {
|
|
665
|
+
projectPath
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}),
|
|
669
|
+
warnings: []
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
const validationVersion = resolvedVersion;
|
|
297
673
|
const warnings = [];
|
|
298
674
|
let validMixins = 0;
|
|
299
675
|
let partialMixins = 0;
|
|
@@ -305,11 +681,12 @@ export class ValidateProjectService {
|
|
|
305
681
|
mode: "config",
|
|
306
682
|
configPaths: [configPath]
|
|
307
683
|
},
|
|
308
|
-
version:
|
|
684
|
+
version: validationVersion,
|
|
309
685
|
mapping: input.mapping,
|
|
310
686
|
sourcePriority: input.sourcePriority,
|
|
311
687
|
scope: input.scope,
|
|
312
|
-
|
|
688
|
+
projectPath,
|
|
689
|
+
preferProjectVersion: false,
|
|
313
690
|
preferProjectMapping: input.preferProjectMapping,
|
|
314
691
|
sourceRoots: input.sourceRoots,
|
|
315
692
|
minSeverity: input.minSeverity,
|
|
@@ -341,9 +718,12 @@ export class ValidateProjectService {
|
|
|
341
718
|
try {
|
|
342
719
|
const output = await this.deps.validateAccessWidener({
|
|
343
720
|
content: await readFile(awPath, "utf8"),
|
|
344
|
-
version:
|
|
721
|
+
version: validationVersion,
|
|
345
722
|
mapping: input.mapping,
|
|
346
|
-
sourcePriority: input.sourcePriority
|
|
723
|
+
sourcePriority: input.sourcePriority,
|
|
724
|
+
projectPath,
|
|
725
|
+
scope: input.scope,
|
|
726
|
+
preferProjectVersion: input.preferProjectVersion
|
|
347
727
|
});
|
|
348
728
|
if (output.valid) {
|
|
349
729
|
validAw += 1;
|
|
@@ -362,7 +742,43 @@ export class ValidateProjectService {
|
|
|
362
742
|
}
|
|
363
743
|
}
|
|
364
744
|
}
|
|
365
|
-
|
|
745
|
+
let validAt = 0;
|
|
746
|
+
let invalidAt = 0;
|
|
747
|
+
for (const atPath of accessTransformers) {
|
|
748
|
+
try {
|
|
749
|
+
if (!this.deps.validateAccessTransformer) {
|
|
750
|
+
throw createError({
|
|
751
|
+
code: ERROR_CODES.CONTEXT_UNRESOLVED,
|
|
752
|
+
message: "Access Transformer validation is not configured."
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
const output = await this.deps.validateAccessTransformer({
|
|
756
|
+
content: await readFile(atPath, "utf8"),
|
|
757
|
+
version: validationVersion,
|
|
758
|
+
atNamespace: input.atNamespace,
|
|
759
|
+
sourcePriority: input.sourcePriority,
|
|
760
|
+
projectPath,
|
|
761
|
+
scope: input.scope,
|
|
762
|
+
preferProjectVersion: input.preferProjectVersion
|
|
763
|
+
});
|
|
764
|
+
if (output.valid) {
|
|
765
|
+
validAt += 1;
|
|
766
|
+
}
|
|
767
|
+
else {
|
|
768
|
+
invalidAt += 1;
|
|
769
|
+
}
|
|
770
|
+
if (Array.isArray(output.warnings)) {
|
|
771
|
+
warnings.push(...output.warnings);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
catch (error) {
|
|
775
|
+
invalidAt += 1;
|
|
776
|
+
if (error instanceof Error) {
|
|
777
|
+
warnings.push(error.message);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
const invalidCount = invalidMixins + invalidAw + invalidAt;
|
|
366
782
|
const partialCount = partialMixins;
|
|
367
783
|
const status = invalidCount > 0 ? "invalid" : partialCount > 0 ? "partial" : "ok";
|
|
368
784
|
return {
|
|
@@ -372,19 +788,19 @@ export class ValidateProjectService {
|
|
|
372
788
|
include,
|
|
373
789
|
summary: {
|
|
374
790
|
status,
|
|
375
|
-
headline: `Validated ${mixinConfigs.length} mixin config(s)
|
|
791
|
+
headline: `Validated ${mixinConfigs.length} mixin config(s), ${accessWideners.length} access widener(s), and ${accessTransformers.length} access transformer(s).`,
|
|
376
792
|
subject: createSummarySubject({
|
|
377
793
|
task: "project-summary",
|
|
378
794
|
kind: input.subject.kind,
|
|
379
795
|
projectPath,
|
|
380
796
|
discover: input.subject.discover,
|
|
381
|
-
version:
|
|
797
|
+
version: resolvedVersion,
|
|
382
798
|
mapping: input.mapping,
|
|
383
799
|
sourcePriority: input.sourcePriority,
|
|
384
800
|
scope: input.scope
|
|
385
801
|
}),
|
|
386
802
|
counts: {
|
|
387
|
-
valid: validMixins + validAw,
|
|
803
|
+
valid: validMixins + validAw + validAt,
|
|
388
804
|
partial: partialCount,
|
|
389
805
|
invalid: invalidCount
|
|
390
806
|
}
|
|
@@ -392,7 +808,7 @@ export class ValidateProjectService {
|
|
|
392
808
|
blocks: {
|
|
393
809
|
project: {
|
|
394
810
|
summary: {
|
|
395
|
-
valid: validMixins + validAw,
|
|
811
|
+
valid: validMixins + validAw + validAt,
|
|
396
812
|
partial: partialCount,
|
|
397
813
|
invalid: invalidCount
|
|
398
814
|
}
|
|
@@ -400,7 +816,8 @@ export class ValidateProjectService {
|
|
|
400
816
|
workspace: {
|
|
401
817
|
projectPath,
|
|
402
818
|
mixinConfigs,
|
|
403
|
-
accessWideners
|
|
819
|
+
accessWideners,
|
|
820
|
+
accessTransformers
|
|
404
821
|
}
|
|
405
822
|
},
|
|
406
823
|
alwaysBlocks: ["project"]
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function normalizeOptionalProjectPath(projectPath: string | undefined): string | undefined;
|
|
2
|
+
export declare function resolveGradleUserHomePath(): string;
|
|
3
|
+
export declare function buildVersionSourceSearchRoots(projectPath: string | undefined): string[];
|
|
4
|
+
export declare function buildLoaderRuntimeSearchRoots(projectPath: string | undefined): string[];
|