@adhisang/minecraft-modding-mcp 7.0.0 → 7.1.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 +72 -0
- package/README.md +4 -3
- package/dist/access-transformer-parser.d.ts +8 -0
- package/dist/access-transformer-parser.js +8 -1
- package/dist/access-widener-parser.d.ts +17 -0
- package/dist/access-widener-parser.js +12 -1
- package/dist/cache-registry.js +19 -6
- package/dist/entry-tools/analyze-mod-service.d.ts +2 -2
- package/dist/entry-tools/analyze-symbol-service.d.ts +2 -2
- package/dist/entry-tools/batch-class-members-service.d.ts +3 -2
- package/dist/entry-tools/batch-class-members-service.js +20 -6
- package/dist/entry-tools/batch-class-source-service.d.ts +3 -2
- package/dist/entry-tools/batch-class-source-service.js +10 -0
- package/dist/entry-tools/compare-minecraft-service.d.ts +27 -4
- package/dist/entry-tools/compare-minecraft-service.js +65 -4
- package/dist/entry-tools/entry-tool-schema.d.ts +2 -2
- package/dist/entry-tools/inspect-minecraft/handlers/versions.js +15 -9
- package/dist/entry-tools/inspect-minecraft-service.d.ts +2 -2
- package/dist/entry-tools/manage-cache-service.d.ts +2 -2
- package/dist/entry-tools/manage-cache-service.js +10 -14
- package/dist/entry-tools/validate-project/cases/access-transformer.js +22 -3
- package/dist/entry-tools/validate-project/cases/access-widener.js +31 -4
- package/dist/entry-tools/validate-project/cases/mixin.js +11 -3
- package/dist/entry-tools/validate-project/cases/project-summary.js +94 -16
- package/dist/entry-tools/validate-project-service.d.ts +2 -2
- package/dist/entry-tools/verify-mixin-target-service.js +19 -8
- package/dist/index.js +38 -15
- package/dist/java-process.d.ts +1 -0
- package/dist/java-process.js +14 -0
- package/dist/mapping/lookup.js +16 -1
- package/dist/mapping-service.d.ts +14 -0
- package/dist/mapping-service.js +35 -15
- package/dist/minecraft-explorer-service.js +70 -8
- package/dist/mixin/access-validators.js +38 -2
- package/dist/mixin/annotation-validators.js +137 -43
- package/dist/mixin/parsed-validator.js +21 -7
- package/dist/mixin-parser.d.ts +52 -0
- package/dist/mixin-parser.js +709 -130
- package/dist/mod-decompile-service.js +11 -1
- package/dist/mod-remap-service.js +6 -6
- package/dist/nbt/java-nbt-codec.js +7 -1
- package/dist/source/access-validate.js +10 -0
- package/dist/source/artifact-resolver.d.ts +27 -3
- package/dist/source/artifact-resolver.js +235 -30
- package/dist/source/class-source/members-builder.d.ts +7 -0
- package/dist/source/class-source/members-builder.js +4 -1
- package/dist/source/class-source.d.ts +9 -2
- package/dist/source/class-source.js +186 -27
- package/dist/source/indexer.js +69 -1
- package/dist/source/lifecycle/mapping-helpers.d.ts +20 -1
- package/dist/source/lifecycle/mapping-helpers.js +29 -3
- package/dist/source/lifecycle/runtime-check.d.ts +25 -0
- package/dist/source/lifecycle/runtime-check.js +68 -39
- package/dist/source/nested-jars.d.ts +15 -1
- package/dist/source/nested-jars.js +14 -5
- package/dist/source/search.d.ts +10 -2
- package/dist/source/search.js +60 -13
- package/dist/source/symbol-resolver.js +88 -0
- package/dist/source/validate-mixin/pipeline/mapping-health.js +20 -1
- package/dist/source/validate-mixin/pipeline/target-lookup.js +16 -7
- package/dist/source/validate-mixin.d.ts +5 -0
- package/dist/source/validate-mixin.js +136 -21
- package/dist/source/workspace-target.js +75 -7
- package/dist/source-jar-reader.d.ts +48 -1
- package/dist/source-jar-reader.js +93 -3
- package/dist/source-resolver.d.ts +7 -0
- package/dist/source-resolver.js +22 -14
- package/dist/source-service.d.ts +5 -0
- package/dist/source-service.js +7 -0
- package/dist/stdio-supervisor.d.ts +35 -1
- package/dist/stdio-supervisor.js +77 -2
- package/dist/storage/db.d.ts +62 -2
- package/dist/storage/db.js +181 -20
- package/dist/storage/files-repo.d.ts +7 -0
- package/dist/storage/files-repo.js +17 -4
- package/dist/storage/sqlite.d.ts +31 -1
- package/dist/storage/sqlite.js +125 -16
- package/dist/tool-contract-manifest.js +2 -2
- package/dist/tool-execution-gate.js +2 -1
- package/dist/tool-guidance.js +4 -1
- package/dist/tool-schemas.d.ts +64 -52
- package/dist/tool-schemas.js +9 -7
- package/dist/types.d.ts +9 -0
- package/dist/v1-parity-schemas.js +36 -2
- package/dist/version-diff-service.d.ts +23 -0
- package/dist/version-diff-service.js +101 -0
- package/dist/version-service.d.ts +14 -0
- package/dist/version-service.js +52 -3
- package/dist/workspace-context-cache.d.ts +25 -0
- package/dist/workspace-context-cache.js +52 -2
- package/dist/workspace-mapping-service.d.ts +8 -0
- package/dist/workspace-mapping-service.js +151 -21
- package/docs/README-ja.md +3 -1
- package/docs/tool-reference.md +69 -22
- package/package.json +1 -1
package/dist/tool-schemas.js
CHANGED
|
@@ -99,11 +99,13 @@ export const resolveArtifactTargetSchema = z.discriminatedUnion("kind", [
|
|
|
99
99
|
workspaceTargetSchema,
|
|
100
100
|
dependencyTargetSchema
|
|
101
101
|
]);
|
|
102
|
-
// Extended target schema for the source-lookup tools (get-class-source / get-class-members)
|
|
103
|
-
// the
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
102
|
+
// Extended target schema for the source-lookup tools (get-class-source / get-class-members)
|
|
103
|
+
// and the batch tools that fan out over one shared artifact (batch-class-source /
|
|
104
|
+
// batch-class-members): the same kind-based shape as resolveArtifactTargetSchema, PLUS a
|
|
105
|
+
// `kind:"artifact"` variant that short-circuits resolution by reusing an already-resolved
|
|
106
|
+
// artifactId. The shared resolveArtifactTargetSchema is intentionally NOT widened —
|
|
107
|
+
// the artifact kind has no resolution meaning for resolve-artifact / verify-mixin-target,
|
|
108
|
+
// which resolve a target rather than reuse one.
|
|
107
109
|
export const sourceLookupTargetSchema = z.discriminatedUnion("kind", [
|
|
108
110
|
z.object({ kind: z.literal("version"), value: nonEmptyString }),
|
|
109
111
|
z.object({ kind: z.literal("jar"), value: nonEmptyString }),
|
|
@@ -238,7 +240,7 @@ export const batchClassSourceEntrySchema = z.object({
|
|
|
238
240
|
outputFile: optionalNonEmptyString
|
|
239
241
|
});
|
|
240
242
|
export const batchClassSourceShape = {
|
|
241
|
-
target:
|
|
243
|
+
target: sourceLookupTargetSchema.describe(SOURCE_LOOKUP_TARGET_DESCRIPTION),
|
|
242
244
|
mapping: sourceMappingSchema.optional(),
|
|
243
245
|
sourcePriority: mappingSourcePrioritySchema.optional(),
|
|
244
246
|
allowDecompile: z.boolean().optional(),
|
|
@@ -294,7 +296,7 @@ export const batchClassMembersEntrySchema = z.object({
|
|
|
294
296
|
maxMembers: optionalPositiveInt
|
|
295
297
|
});
|
|
296
298
|
export const batchClassMembersShape = {
|
|
297
|
-
target:
|
|
299
|
+
target: sourceLookupTargetSchema.describe(SOURCE_LOOKUP_TARGET_DESCRIPTION),
|
|
298
300
|
mapping: sourceMappingSchema.optional(),
|
|
299
301
|
sourcePriority: mappingSourcePrioritySchema.optional(),
|
|
300
302
|
allowDecompile: z.boolean().optional(),
|
package/dist/types.d.ts
CHANGED
|
@@ -75,6 +75,15 @@ export interface ArtifactProvenance {
|
|
|
75
75
|
repoUrl?: string;
|
|
76
76
|
};
|
|
77
77
|
transformChain: string[];
|
|
78
|
+
/**
|
|
79
|
+
* The artifact's runtime names ship unobfuscated (Minecraft 26.1+): set for a
|
|
80
|
+
* 26.1+ version target, a 26.1+ Minecraft runtime coordinate, and a jar target
|
|
81
|
+
* proven to be a 26.1+ runtime jar; absent otherwise. `mappingApplied` keeps the
|
|
82
|
+
* label the caller asked for, so on such an artifact "obfuscated" names the
|
|
83
|
+
* as-shipped names, which are already Mojang names. Same flag name as
|
|
84
|
+
* `mappingContext.unobfuscatedRuntime`.
|
|
85
|
+
*/
|
|
86
|
+
unobfuscatedRuntime?: boolean;
|
|
78
87
|
workspaceResolution?: WorkspaceResolutionProvenance;
|
|
79
88
|
dependencyResolution?: DependencyResolutionProvenance;
|
|
80
89
|
warnings?: string[];
|
|
@@ -446,9 +446,26 @@ export const V1_PARITY_SCHEMAS = {
|
|
|
446
446
|
"name"
|
|
447
447
|
],
|
|
448
448
|
"additionalProperties": false
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
"type": "object",
|
|
452
|
+
"properties": {
|
|
453
|
+
"kind": {
|
|
454
|
+
"type": "string",
|
|
455
|
+
"const": "artifact"
|
|
456
|
+
},
|
|
457
|
+
"artifactId": {
|
|
458
|
+
"$ref": "#/properties/target/anyOf/0/properties/value"
|
|
459
|
+
}
|
|
460
|
+
},
|
|
461
|
+
"required": [
|
|
462
|
+
"kind",
|
|
463
|
+
"artifactId"
|
|
464
|
+
],
|
|
465
|
+
"additionalProperties": false
|
|
449
466
|
}
|
|
450
467
|
],
|
|
451
|
-
"description": "
|
|
468
|
+
"description": "Same shape as resolve-artifact target (incl. {\"kind\":\"dependency\",...} to read a Fabric/loader dependency class like vanilla), plus {\"kind\":\"artifact\",\"artifactId\":\"...\"} to reuse a resolved artifact. Object, not string."
|
|
452
469
|
},
|
|
453
470
|
"mapping": {
|
|
454
471
|
"type": "string",
|
|
@@ -706,9 +723,26 @@ export const V1_PARITY_SCHEMAS = {
|
|
|
706
723
|
"name"
|
|
707
724
|
],
|
|
708
725
|
"additionalProperties": false
|
|
726
|
+
},
|
|
727
|
+
{
|
|
728
|
+
"type": "object",
|
|
729
|
+
"properties": {
|
|
730
|
+
"kind": {
|
|
731
|
+
"type": "string",
|
|
732
|
+
"const": "artifact"
|
|
733
|
+
},
|
|
734
|
+
"artifactId": {
|
|
735
|
+
"$ref": "#/properties/target/anyOf/0/properties/value"
|
|
736
|
+
}
|
|
737
|
+
},
|
|
738
|
+
"required": [
|
|
739
|
+
"kind",
|
|
740
|
+
"artifactId"
|
|
741
|
+
],
|
|
742
|
+
"additionalProperties": false
|
|
709
743
|
}
|
|
710
744
|
],
|
|
711
|
-
"description": "
|
|
745
|
+
"description": "Same shape as resolve-artifact target (incl. {\"kind\":\"dependency\",...} to read a Fabric/loader dependency class like vanilla), plus {\"kind\":\"artifact\",\"artifactId\":\"...\"} to reuse a resolved artifact. Object, not string."
|
|
712
746
|
},
|
|
713
747
|
"mapping": {
|
|
714
748
|
"type": "string",
|
|
@@ -52,6 +52,29 @@ export type CompareVersionsOutput = {
|
|
|
52
52
|
};
|
|
53
53
|
warnings: string[];
|
|
54
54
|
};
|
|
55
|
+
/**
|
|
56
|
+
* `added`/`removed` entries are `group:artifact:<versions>`, where
|
|
57
|
+
* `<versions>` is the sorted, comma-joined set of distinct versions seen for
|
|
58
|
+
* that key on the side it was diffed from or to (a single version renders
|
|
59
|
+
* as before, e.g. "g:a:1"; multiple platform-specific versions render as
|
|
60
|
+
* "g:a:1,2").
|
|
61
|
+
*/
|
|
62
|
+
export type LibraryDiffResult = {
|
|
63
|
+
added: string[];
|
|
64
|
+
removed: string[];
|
|
65
|
+
addedCount: number;
|
|
66
|
+
removedCount: number;
|
|
67
|
+
/** Count only (per design contract) — no list of which libraries bumped. */
|
|
68
|
+
versionChangedCount: number;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Pure diff of two versions' `libraries` name lists, keyed by
|
|
72
|
+
* `group:artifact` so a per-release version bump or a per-platform natives
|
|
73
|
+
* classifier never reads as churn. Used by migration-overview to surface
|
|
74
|
+
* library swaps (e.g. LWJGL GLFW replaced by SDL) that a class/registry diff
|
|
75
|
+
* cannot see.
|
|
76
|
+
*/
|
|
77
|
+
export declare function diffLibraries(fromNames: string[], toNames: string[]): LibraryDiffResult;
|
|
55
78
|
export declare class VersionDiffService {
|
|
56
79
|
private readonly config;
|
|
57
80
|
private readonly versionService;
|
|
@@ -88,6 +88,107 @@ function diffSets(from, to) {
|
|
|
88
88
|
removed.sort();
|
|
89
89
|
return { added, removed, unchanged };
|
|
90
90
|
}
|
|
91
|
+
// Matches a Maven-style library coordinate "group:artifact:version" with an
|
|
92
|
+
// optional trailing classifier ("...:natives-linux"). Group/artifact/version
|
|
93
|
+
// segments must be non-empty and colon-free; anything else is not a library
|
|
94
|
+
// name this diff can key on, so it is silently ignored rather than crashing
|
|
95
|
+
// or reading as a spurious added/removed entry. A trailing "@extension"
|
|
96
|
+
// (e.g. "...@jar") is stripped by the caller before this regex runs, so it
|
|
97
|
+
// never leaks into the captured version.
|
|
98
|
+
const LIBRARY_NAME_RE = /^([^:\s]+):([^:\s]+):([^:\s]+)(?::[^:\s]*)?$/;
|
|
99
|
+
function parseLibraryName(name) {
|
|
100
|
+
// Strip a trailing "@extension" (e.g. "g:a:1@jar", "g:a:1:natives-linux@jar")
|
|
101
|
+
// before parsing, so it never gets captured as part of the version.
|
|
102
|
+
const withoutExtension = name.trim().replace(/@[^@]*$/, "");
|
|
103
|
+
const match = LIBRARY_NAME_RE.exec(withoutExtension);
|
|
104
|
+
if (!match) {
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
const [, group, artifact, version] = match;
|
|
108
|
+
return { key: `${group}:${artifact}`, version };
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Collapses a version's raw library name list to the set of distinct
|
|
112
|
+
* versions seen per `group:artifact` key, dropping the per-platform natives
|
|
113
|
+
* classifier and any entry whose name does not parse as a library
|
|
114
|
+
* coordinate. Real Mojang manifests can list the same `group:artifact` with
|
|
115
|
+
* different versions under different platform rules, so every version seen
|
|
116
|
+
* is kept (not just the first) — collapsing to one would make the diff
|
|
117
|
+
* depend on input order.
|
|
118
|
+
*/
|
|
119
|
+
function collapseLibraryNames(names) {
|
|
120
|
+
const map = new Map();
|
|
121
|
+
for (const name of names) {
|
|
122
|
+
if (typeof name !== "string") {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const parsed = parseLibraryName(name);
|
|
126
|
+
if (!parsed) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
let versions = map.get(parsed.key);
|
|
130
|
+
if (!versions) {
|
|
131
|
+
versions = new Set();
|
|
132
|
+
map.set(parsed.key, versions);
|
|
133
|
+
}
|
|
134
|
+
versions.add(parsed.version);
|
|
135
|
+
}
|
|
136
|
+
return map;
|
|
137
|
+
}
|
|
138
|
+
/** Renders a key's version set as the sorted, comma-joined suffix used in `added`/`removed`. */
|
|
139
|
+
function renderVersions(versions) {
|
|
140
|
+
return Array.from(versions).sort().join(",");
|
|
141
|
+
}
|
|
142
|
+
function versionSetsEqual(a, b) {
|
|
143
|
+
if (a.size !== b.size) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
for (const version of a) {
|
|
147
|
+
if (!b.has(version)) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Pure diff of two versions' `libraries` name lists, keyed by
|
|
155
|
+
* `group:artifact` so a per-release version bump or a per-platform natives
|
|
156
|
+
* classifier never reads as churn. Used by migration-overview to surface
|
|
157
|
+
* library swaps (e.g. LWJGL GLFW replaced by SDL) that a class/registry diff
|
|
158
|
+
* cannot see.
|
|
159
|
+
*/
|
|
160
|
+
export function diffLibraries(fromNames, toNames) {
|
|
161
|
+
const fromMap = collapseLibraryNames(fromNames ?? []);
|
|
162
|
+
const toMap = collapseLibraryNames(toNames ?? []);
|
|
163
|
+
const added = [];
|
|
164
|
+
const removed = [];
|
|
165
|
+
let versionChangedCount = 0;
|
|
166
|
+
for (const [key, versions] of toMap) {
|
|
167
|
+
if (!fromMap.has(key)) {
|
|
168
|
+
added.push(`${key}:${renderVersions(versions)}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
for (const [key, versions] of fromMap) {
|
|
172
|
+
if (!toMap.has(key)) {
|
|
173
|
+
removed.push(`${key}:${renderVersions(versions)}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
for (const [key, fromVersions] of fromMap) {
|
|
177
|
+
const toVersions = toMap.get(key);
|
|
178
|
+
if (toVersions !== undefined && !versionSetsEqual(fromVersions, toVersions)) {
|
|
179
|
+
versionChangedCount += 1;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
added.sort();
|
|
183
|
+
removed.sort();
|
|
184
|
+
return {
|
|
185
|
+
added,
|
|
186
|
+
removed,
|
|
187
|
+
addedCount: added.length,
|
|
188
|
+
removedCount: removed.length,
|
|
189
|
+
versionChangedCount
|
|
190
|
+
};
|
|
191
|
+
}
|
|
91
192
|
function diffRegistries(fromRegistries, toRegistries) {
|
|
92
193
|
const fromKeys = new Set(Object.keys(fromRegistries));
|
|
93
194
|
const toKeys = new Set(Object.keys(toRegistries));
|
|
@@ -54,6 +54,14 @@ export declare class VersionService {
|
|
|
54
54
|
listVersionIds(input?: ListVersionIdsInput): Promise<string[]>;
|
|
55
55
|
resolveVersionJar(version: string): Promise<ResolvedVersionJar>;
|
|
56
56
|
resolveVersionMappings(version: string): Promise<ResolvedVersionMappings>;
|
|
57
|
+
/**
|
|
58
|
+
* Raw `libraries[].name` coordinates (e.g. "org.lwjgl:lwjgl-glfw:3.4.1")
|
|
59
|
+
* for one version's per-version JSON, in manifest order. Entries missing a
|
|
60
|
+
* non-empty string `name` are dropped here at the JSON boundary; further
|
|
61
|
+
* validation (does the name actually parse as a library coordinate) is the
|
|
62
|
+
* caller's job — see diffLibraries in version-diff-service.ts.
|
|
63
|
+
*/
|
|
64
|
+
getVersionLibraries(version: string): Promise<string[]>;
|
|
57
65
|
resolveServerJar(version: string): Promise<ResolvedServerJar>;
|
|
58
66
|
private fetchManifest;
|
|
59
67
|
private resolveVersionJarInternal;
|
|
@@ -69,5 +77,11 @@ export declare class VersionService {
|
|
|
69
77
|
* MC 26.1+ uses new YY.N version format and ships unobfuscated source.
|
|
70
78
|
* Legacy 1.x.y versions remain obfuscated.
|
|
71
79
|
* Snapshots: "26w01a" (year >= 26) → unobfuscated, "24w01a" → obfuscated.
|
|
80
|
+
*
|
|
81
|
+
* Pre-release / rc / snapshot suffixes: Mojang's real version-manifest ids
|
|
82
|
+
* use a hyphenated "-pre-N" / "-rc-N" / "-snapshot-N" form (e.g.
|
|
83
|
+
* "26.2-pre-6", "26.2-rc-2", "26.3-snapshot-6"). The older no-hyphen
|
|
84
|
+
* "-preN"/"-rcN" form is also accepted for backwards compatibility, but an
|
|
85
|
+
* arbitrary or numberless suffix (e.g. "26.1-foo", "26.1-snapshot") is not.
|
|
72
86
|
*/
|
|
73
87
|
export declare function isUnobfuscatedVersion(version: string): boolean;
|
package/dist/version-service.js
CHANGED
|
@@ -136,6 +136,39 @@ export class VersionService {
|
|
|
136
136
|
mappingsUrl: clientMappingsUrl
|
|
137
137
|
};
|
|
138
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Raw `libraries[].name` coordinates (e.g. "org.lwjgl:lwjgl-glfw:3.4.1")
|
|
141
|
+
* for one version's per-version JSON, in manifest order. Entries missing a
|
|
142
|
+
* non-empty string `name` are dropped here at the JSON boundary; further
|
|
143
|
+
* validation (does the name actually parse as a library coordinate) is the
|
|
144
|
+
* caller's job — see diffLibraries in version-diff-service.ts.
|
|
145
|
+
*/
|
|
146
|
+
async getVersionLibraries(version) {
|
|
147
|
+
const normalizedVersion = version.trim();
|
|
148
|
+
if (!normalizedVersion) {
|
|
149
|
+
throw createError({
|
|
150
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
151
|
+
message: "version must be non-empty."
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
const manifest = await this.fetchManifest();
|
|
155
|
+
const versionEntry = (manifest.versions ?? []).find((entry) => entry.id === normalizedVersion);
|
|
156
|
+
if (!versionEntry) {
|
|
157
|
+
throw createError({
|
|
158
|
+
code: ERROR_CODES.VERSION_NOT_FOUND,
|
|
159
|
+
message: `Minecraft version "${normalizedVersion}" was not found in version manifest.`,
|
|
160
|
+
details: {
|
|
161
|
+
version: normalizedVersion,
|
|
162
|
+
nextAction: "Use list-versions to see available Minecraft versions.",
|
|
163
|
+
...buildSuggestedCall({ tool: "list-versions", params: {} })
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
const details = await this.fetchVersionDetails(versionEntry.url, normalizedVersion);
|
|
168
|
+
return (details.libraries ?? [])
|
|
169
|
+
.map((lib) => lib?.name)
|
|
170
|
+
.filter((name) => typeof name === "string" && name.length > 0);
|
|
171
|
+
}
|
|
139
172
|
async resolveServerJar(version) {
|
|
140
173
|
const normalizedVersion = version.trim();
|
|
141
174
|
if (!normalizedVersion) {
|
|
@@ -357,6 +390,13 @@ export class VersionService {
|
|
|
357
390
|
try {
|
|
358
391
|
const response = await this.fetchFn(url, { signal: timeout.signal });
|
|
359
392
|
if (!response.ok) {
|
|
393
|
+
// Nothing reads an error body here, and an unread one pins its socket
|
|
394
|
+
// until GC reaches it. Released best-effort, exactly as `releaseBody`
|
|
395
|
+
// in repo-downloader.ts: a body that refuses to be cancelled is not a
|
|
396
|
+
// reason to lose the status the caller actually needs.
|
|
397
|
+
void response.body?.cancel().catch(() => {
|
|
398
|
+
// best-effort release
|
|
399
|
+
});
|
|
360
400
|
throw createError({
|
|
361
401
|
code: ERROR_CODES.REPO_FETCH_FAILED,
|
|
362
402
|
message: `Request failed for "${url}" with status ${response.status}.`,
|
|
@@ -473,6 +513,12 @@ export class VersionService {
|
|
|
473
513
|
* MC 26.1+ uses new YY.N version format and ships unobfuscated source.
|
|
474
514
|
* Legacy 1.x.y versions remain obfuscated.
|
|
475
515
|
* Snapshots: "26w01a" (year >= 26) → unobfuscated, "24w01a" → obfuscated.
|
|
516
|
+
*
|
|
517
|
+
* Pre-release / rc / snapshot suffixes: Mojang's real version-manifest ids
|
|
518
|
+
* use a hyphenated "-pre-N" / "-rc-N" / "-snapshot-N" form (e.g.
|
|
519
|
+
* "26.2-pre-6", "26.2-rc-2", "26.3-snapshot-6"). The older no-hyphen
|
|
520
|
+
* "-preN"/"-rcN" form is also accepted for backwards compatibility, but an
|
|
521
|
+
* arbitrary or numberless suffix (e.g. "26.1-foo", "26.1-snapshot") is not.
|
|
476
522
|
*/
|
|
477
523
|
export function isUnobfuscatedVersion(version) {
|
|
478
524
|
if (!version)
|
|
@@ -482,9 +528,12 @@ export function isUnobfuscatedVersion(version) {
|
|
|
482
528
|
if (snapshotMatch) {
|
|
483
529
|
return Number(snapshotMatch[1]) >= 26;
|
|
484
530
|
}
|
|
485
|
-
// New format: YY.N or YY.N.P, optionally with -
|
|
486
|
-
//
|
|
487
|
-
|
|
531
|
+
// New format: YY.N or YY.N.P, optionally with a pre-release/rc/snapshot
|
|
532
|
+
// suffix in either the legacy no-hyphen form (-preN, -rcN) or Mojang's
|
|
533
|
+
// real hyphenated form (-pre-N, -rc-N, -snapshot-N).
|
|
534
|
+
// Examples: "26.1", "27.3.1", "26.1-pre1", "26.1-rc1", "26.2-pre-6",
|
|
535
|
+
// "26.2-rc-2", "26.3-snapshot-6"
|
|
536
|
+
const newFormatMatch = version.match(/^(\d{2,})\.\d+(?:\.\d+)?(?:-(?:pre|rc)\d+|-(?:pre|rc|snapshot)-\d+)?$/);
|
|
488
537
|
if (newFormatMatch) {
|
|
489
538
|
return Number(newFormatMatch[1]) >= 26;
|
|
490
539
|
}
|
|
@@ -5,6 +5,22 @@ export type WorkspaceContextEvidence = {
|
|
|
5
5
|
field: string;
|
|
6
6
|
value?: string;
|
|
7
7
|
};
|
|
8
|
+
/**
|
|
9
|
+
* A stat-based snapshot of one file the detection that produced a
|
|
10
|
+
* `WorkspaceContext` actually read, taken at write time. `read()` re-stats
|
|
11
|
+
* every entry and treats any difference - a changed mtime/size, a file that
|
|
12
|
+
* appeared, or one that disappeared - as a cache miss, so an edit to a
|
|
13
|
+
* project's build files invalidates the cached context immediately instead
|
|
14
|
+
* of waiting out the TTL.
|
|
15
|
+
*/
|
|
16
|
+
export type WorkspaceContextFingerprintEntry = {
|
|
17
|
+
path: string;
|
|
18
|
+
mtimeMs: number;
|
|
19
|
+
size: number;
|
|
20
|
+
} | {
|
|
21
|
+
path: string;
|
|
22
|
+
absent: true;
|
|
23
|
+
};
|
|
8
24
|
export type WorkspaceContext = {
|
|
9
25
|
projectPath: string;
|
|
10
26
|
minecraftVersion?: string;
|
|
@@ -14,6 +30,7 @@ export type WorkspaceContext = {
|
|
|
14
30
|
evidence: WorkspaceContextEvidence[];
|
|
15
31
|
dependencyVersions: Map<string, string>;
|
|
16
32
|
partial?: boolean;
|
|
33
|
+
fingerprint?: WorkspaceContextFingerprintEntry[];
|
|
17
34
|
};
|
|
18
35
|
export interface WorkspaceContextCache {
|
|
19
36
|
read(projectPath: string): WorkspaceContext | undefined;
|
|
@@ -27,6 +44,14 @@ export type WorkspaceContextCacheOptions = {
|
|
|
27
44
|
ttlMs?: number;
|
|
28
45
|
clock?: () => number;
|
|
29
46
|
};
|
|
47
|
+
/**
|
|
48
|
+
* Stat-snapshot the given file paths (deduplicated, order-independent) for a
|
|
49
|
+
* `WorkspaceContext.fingerprint`. Exported so the workspace-context populator
|
|
50
|
+
* (the only place that knows which files a detection actually read) can build
|
|
51
|
+
* the same shape `read()` compares against, without duplicating the stat
|
|
52
|
+
* logic or its absent-file handling.
|
|
53
|
+
*/
|
|
54
|
+
export declare function computeWorkspaceContextFingerprint(paths: readonly string[]): WorkspaceContextFingerprintEntry[];
|
|
30
55
|
export declare function createWorkspaceContextCache(opts?: WorkspaceContextCacheOptions): WorkspaceContextCache;
|
|
31
56
|
export declare function getProcessWorkspaceContextCache(): WorkspaceContextCache;
|
|
32
57
|
export declare function resetProcessWorkspaceContextCacheForTesting(): void;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { statSync } from "node:fs";
|
|
1
2
|
import { resolve as resolvePath } from "node:path";
|
|
2
3
|
import { LruList } from "./lru-list.js";
|
|
3
4
|
const DEFAULT_MAX_ENTRIES = 16;
|
|
@@ -5,6 +6,55 @@ const DEFAULT_TTL_MS = 5 * 60_000;
|
|
|
5
6
|
function normalizeKey(projectPath) {
|
|
6
7
|
return resolvePath(projectPath);
|
|
7
8
|
}
|
|
9
|
+
function currentFingerprintEntry(path) {
|
|
10
|
+
try {
|
|
11
|
+
const stats = statSync(path);
|
|
12
|
+
return { path, mtimeMs: stats.mtimeMs, size: stats.size };
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
// Missing, unreadable, or a broken symlink - all the same "not there"
|
|
16
|
+
// state as far as invalidation cares about.
|
|
17
|
+
return { path, absent: true };
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Stat-snapshot the given file paths (deduplicated, order-independent) for a
|
|
22
|
+
* `WorkspaceContext.fingerprint`. Exported so the workspace-context populator
|
|
23
|
+
* (the only place that knows which files a detection actually read) can build
|
|
24
|
+
* the same shape `read()` compares against, without duplicating the stat
|
|
25
|
+
* logic or its absent-file handling.
|
|
26
|
+
*/
|
|
27
|
+
export function computeWorkspaceContextFingerprint(paths) {
|
|
28
|
+
const unique = Array.from(new Set(paths.map((path) => resolvePath(path))));
|
|
29
|
+
return unique.map((path) => currentFingerprintEntry(path));
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Whether any file recorded in `ctx.fingerprint` no longer matches its
|
|
33
|
+
* recorded size/mtime (or its recorded/current absence disagrees). A context
|
|
34
|
+
* with no fingerprint (nothing was recorded, e.g. a dependency-only partial
|
|
35
|
+
* entry) never invalidates on this check - only the TTL applies to it.
|
|
36
|
+
*/
|
|
37
|
+
function fingerprintStale(ctx) {
|
|
38
|
+
if (!ctx.fingerprint || ctx.fingerprint.length === 0) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
for (const recorded of ctx.fingerprint) {
|
|
42
|
+
const current = currentFingerprintEntry(recorded.path);
|
|
43
|
+
const recordedAbsent = "absent" in recorded && recorded.absent === true;
|
|
44
|
+
const currentAbsent = "absent" in current && current.absent === true;
|
|
45
|
+
if (recordedAbsent !== currentAbsent) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
if (!recordedAbsent && !currentAbsent) {
|
|
49
|
+
const recordedStat = recorded;
|
|
50
|
+
const currentStat = current;
|
|
51
|
+
if (recordedStat.mtimeMs !== currentStat.mtimeMs || recordedStat.size !== currentStat.size) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
8
58
|
export function createWorkspaceContextCache(opts = {}) {
|
|
9
59
|
const maxEntries = opts.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
10
60
|
const ttlMs = opts.ttlMs ?? DEFAULT_TTL_MS;
|
|
@@ -29,7 +79,7 @@ export function createWorkspaceContextCache(opts = {}) {
|
|
|
29
79
|
if (!value) {
|
|
30
80
|
return undefined;
|
|
31
81
|
}
|
|
32
|
-
if (isExpired(value)) {
|
|
82
|
+
if (isExpired(value) || fingerprintStale(value)) {
|
|
33
83
|
lru.remove(key);
|
|
34
84
|
return undefined;
|
|
35
85
|
}
|
|
@@ -46,7 +96,7 @@ export function createWorkspaceContextCache(opts = {}) {
|
|
|
46
96
|
},
|
|
47
97
|
list() {
|
|
48
98
|
const all = lru.toArray().map((entry) => entry.value);
|
|
49
|
-
return all.filter((value) => !isExpired(value));
|
|
99
|
+
return all.filter((value) => !isExpired(value) && !fingerprintStale(value));
|
|
50
100
|
},
|
|
51
101
|
clear() {
|
|
52
102
|
lru.clear();
|
|
@@ -50,6 +50,14 @@ export type DependencyVersionOptions = {
|
|
|
50
50
|
export { isSafeMavenVersionToken };
|
|
51
51
|
export declare class WorkspaceMappingService {
|
|
52
52
|
detectCompileMapping(input: WorkspaceCompileMappingInput): Promise<WorkspaceCompileMappingOutput>;
|
|
53
|
+
/**
|
|
54
|
+
* Whether `projectPath` holds at least one build.gradle(.kts) that
|
|
55
|
+
* `detectCompileMapping` reads: the same files, found the same way, and readable.
|
|
56
|
+
* `detectCompileMapping` answers "no evidence" both when a build declares no
|
|
57
|
+
* mappings and when there was no build to read (a missing, mistyped or empty
|
|
58
|
+
* directory); this tells the two apart without changing that output.
|
|
59
|
+
*/
|
|
60
|
+
hasReadableBuildScript(projectPath: string): Promise<boolean>;
|
|
53
61
|
detectProjectMinecraftVersion(projectPath: string): Promise<string | undefined>;
|
|
54
62
|
detectDependencyVersion(projectPath: string, group: string, name: string, opts?: DependencyVersionOptions): Promise<DependencyVersionResolution>;
|
|
55
63
|
detectProjectLoader(projectPath: string): Promise<WorkspaceProjectLoaderOutput>;
|