@creeperhost/modlens-mcp 1.6.18 → 1.6.19
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/README.md +97 -10
- package/TESTING.md +69 -0
- package/dist/cli.js +21 -5
- package/dist/cli.js.map +1 -1
- package/dist/curseforge.d.ts +9 -7
- package/dist/curseforge.d.ts.map +1 -1
- package/dist/curseforge.js +48 -55
- package/dist/curseforge.js.map +1 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +31 -1
- package/dist/db.js.map +1 -1
- package/dist/embeddings.d.ts.map +1 -1
- package/dist/embeddings.js +1 -0
- package/dist/embeddings.js.map +1 -1
- package/dist/feed-the-beast.d.ts +5 -5
- package/dist/feed-the-beast.d.ts.map +1 -1
- package/dist/feed-the-beast.js +5 -21
- package/dist/feed-the-beast.js.map +1 -1
- package/dist/java-tools.d.ts +1 -0
- package/dist/java-tools.d.ts.map +1 -1
- package/dist/java-tools.js +31 -7
- package/dist/java-tools.js.map +1 -1
- package/dist/launcher.js +15 -0
- package/dist/launcher.js.map +1 -1
- package/dist/mappings.d.ts +4 -1
- package/dist/mappings.d.ts.map +1 -1
- package/dist/mappings.js +120 -52
- package/dist/mappings.js.map +1 -1
- package/dist/minecraft.d.ts +0 -4
- package/dist/minecraft.d.ts.map +1 -1
- package/dist/minecraft.js +7 -23
- package/dist/minecraft.js.map +1 -1
- package/dist/mod-artifacts.d.ts +4 -0
- package/dist/mod-artifacts.d.ts.map +1 -0
- package/dist/mod-artifacts.js +58 -0
- package/dist/mod-artifacts.js.map +1 -0
- package/dist/modpacks-ch.d.ts +72 -46
- package/dist/modpacks-ch.d.ts.map +1 -1
- package/dist/modpacks-ch.js +134 -20
- package/dist/modpacks-ch.js.map +1 -1
- package/dist/modrinth.d.ts +14 -6
- package/dist/modrinth.d.ts.map +1 -1
- package/dist/modrinth.js +70 -55
- package/dist/modrinth.js.map +1 -1
- package/dist/platform-adapter.d.ts +1 -0
- package/dist/platform-adapter.d.ts.map +1 -1
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +20 -44
- package/dist/platform.js.map +1 -1
- package/dist/processor.d.ts +5 -0
- package/dist/processor.d.ts.map +1 -1
- package/dist/processor.js +166 -46
- package/dist/processor.js.map +1 -1
- package/dist/registry-submission.d.ts +34 -0
- package/dist/registry-submission.d.ts.map +1 -0
- package/dist/registry-submission.js +128 -0
- package/dist/registry-submission.js.map +1 -0
- package/dist/repositories/array-fields.d.ts +3 -0
- package/dist/repositories/array-fields.d.ts.map +1 -0
- package/dist/repositories/array-fields.js +8 -0
- package/dist/repositories/array-fields.js.map +1 -0
- package/dist/repositories/embeddings-sqlite.d.ts +1 -0
- package/dist/repositories/embeddings-sqlite.d.ts.map +1 -1
- package/dist/repositories/embeddings-sqlite.js +26 -58
- package/dist/repositories/embeddings-sqlite.js.map +1 -1
- package/dist/repositories/embeddings.d.ts.map +1 -1
- package/dist/repositories/embeddings.js +26 -3
- package/dist/repositories/embeddings.js.map +1 -1
- package/dist/repositories/index.d.ts +1 -1
- package/dist/repositories/index.d.ts.map +1 -1
- package/dist/repositories/mod.d.ts +8 -5
- package/dist/repositories/mod.d.ts.map +1 -1
- package/dist/repositories/mod.js +25 -11
- package/dist/repositories/mod.js.map +1 -1
- package/dist/repositories/packs.d.ts.map +1 -1
- package/dist/repositories/packs.js +10 -2
- package/dist/repositories/packs.js.map +1 -1
- package/dist/search-adapter.d.ts +2 -0
- package/dist/search-adapter.d.ts.map +1 -1
- package/dist/search-adapter.js +21 -9
- package/dist/search-adapter.js.map +1 -1
- package/dist/security.d.ts +2 -0
- package/dist/security.d.ts.map +1 -1
- package/dist/security.js +5 -1
- package/dist/security.js.map +1 -1
- package/dist/server.js +63 -25
- package/dist/server.js.map +1 -1
- package/dist/sqlite-json.d.ts +3 -0
- package/dist/sqlite-json.d.ts.map +1 -0
- package/dist/sqlite-json.js +78 -0
- package/dist/sqlite-json.js.map +1 -0
- package/dist/sqlite-parameters.d.ts +3 -0
- package/dist/sqlite-parameters.d.ts.map +1 -0
- package/dist/sqlite-parameters.js +19 -0
- package/dist/sqlite-parameters.js.map +1 -0
- package/dist/sqlite-schema.d.ts +4 -0
- package/dist/sqlite-schema.d.ts.map +1 -0
- package/dist/sqlite-schema.js +76 -0
- package/dist/sqlite-schema.js.map +1 -0
- package/dist/tools/catalog.d.ts +1 -6
- package/dist/tools/catalog.d.ts.map +1 -1
- package/dist/tools/catalog.js +10 -14
- package/dist/tools/catalog.js.map +1 -1
- package/dist/tools/compat-check.d.ts.map +1 -1
- package/dist/tools/compat-check.js +18 -30
- package/dist/tools/compat-check.js.map +1 -1
- package/dist/tools/diagnostics.js.map +1 -1
- package/dist/tools/docs.d.ts.map +1 -1
- package/dist/tools/docs.js +8 -3
- package/dist/tools/docs.js.map +1 -1
- package/dist/tools/embed-registry.d.ts +20 -4
- package/dist/tools/embed-registry.d.ts.map +1 -1
- package/dist/tools/embed-registry.js +30 -19
- package/dist/tools/embed-registry.js.map +1 -1
- package/dist/tools/graphify.d.ts +19 -4
- package/dist/tools/graphify.d.ts.map +1 -1
- package/dist/tools/graphify.js +61 -13
- package/dist/tools/graphify.js.map +1 -1
- package/dist/tools/ingest.d.ts.map +1 -1
- package/dist/tools/ingest.js +4 -4
- package/dist/tools/ingest.js.map +1 -1
- package/dist/tools/kubejs.js +1 -1
- package/dist/tools/kubejs.js.map +1 -1
- package/dist/tools/mc-fts.d.ts.map +1 -1
- package/dist/tools/mc-fts.js +9 -4
- package/dist/tools/mc-fts.js.map +1 -1
- package/dist/tools/mcmeta.d.ts +10 -1
- package/dist/tools/mcmeta.d.ts.map +1 -1
- package/dist/tools/mcmeta.js +43 -29
- package/dist/tools/mcmeta.js.map +1 -1
- package/dist/tools/mixin-scan.d.ts +12 -6
- package/dist/tools/mixin-scan.d.ts.map +1 -1
- package/dist/tools/mixin-scan.js +12 -7
- package/dist/tools/mixin-scan.js.map +1 -1
- package/dist/tools/mod-data.d.ts.map +1 -1
- package/dist/tools/mod-data.js +40 -27
- package/dist/tools/mod-data.js.map +1 -1
- package/dist/tools/modpacks-ch.d.ts +9 -5
- package/dist/tools/modpacks-ch.d.ts.map +1 -1
- package/dist/tools/modpacks-ch.js +54 -33
- package/dist/tools/modpacks-ch.js.map +1 -1
- package/dist/tools/packtools.js +3 -3
- package/dist/tools/packtools.js.map +1 -1
- package/dist/tools/platform.d.ts +2 -2
- package/dist/tools/platform.d.ts.map +1 -1
- package/dist/tools/platform.js +33 -23
- package/dist/tools/platform.js.map +1 -1
- package/dist/tools/project.d.ts +342 -0
- package/dist/tools/project.d.ts.map +1 -0
- package/dist/tools/project.js +519 -0
- package/dist/tools/project.js.map +1 -0
- package/dist/tools/reports.d.ts.map +1 -1
- package/dist/tools/reports.js +5 -3
- package/dist/tools/reports.js.map +1 -1
- package/dist/tools/vanilla-data.d.ts.map +1 -1
- package/dist/tools/vanilla-data.js +3 -33
- package/dist/tools/vanilla-data.js.map +1 -1
- package/dist/tools/vanilla.d.ts +3 -1
- package/dist/tools/vanilla.d.ts.map +1 -1
- package/dist/tools/vanilla.js +26 -12
- package/dist/tools/vanilla.js.map +1 -1
- package/dist/tools/version-diff.d.ts.map +1 -1
- package/dist/tools/version-diff.js +4 -24
- package/dist/tools/version-diff.js.map +1 -1
- package/dist/version-ranges.d.ts +5 -0
- package/dist/version-ranges.d.ts.map +1 -0
- package/dist/version-ranges.js +93 -0
- package/dist/version-ranges.js.map +1 -0
- package/package.json +5 -1
- package/prisma/backends/template.db +0 -0
- package/scripts/build-template-db.mjs +3 -1
- package/scripts/enable-sqlite-vec.mjs +2 -106
- package/scripts/gradle/modlens.init.gradle +154 -0
- package/scripts/project-upload.mjs +74 -0
- package/scripts/test-minecraft-eras.mjs +202 -0
- package/scripts/test-package.mjs +8 -0
- package/scripts/test-project-gradle.mjs +76 -0
- package/scripts/test-project-http.mjs +98 -0
- package/scripts/test-setup.mjs +18 -0
package/dist/platform.js
CHANGED
|
@@ -2,36 +2,27 @@ import { createWriteStream } from "fs";
|
|
|
2
2
|
import { pipeline } from "stream/promises";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import { CACHE_ROOT, ensureDir, exists } from "./cache.js";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
const NEOFORGE_MAVEN = "https://maven.
|
|
8
|
-
const NEOFORGE_META =
|
|
9
|
-
const FORGE_MAVEN = "https://maven.
|
|
5
|
+
import { fetchMcVersionList } from "./minecraft.js";
|
|
6
|
+
import { loaderVersions, providerVersions } from "./modpacks-ch.js";
|
|
7
|
+
const NEOFORGE_MAVEN = "https://maven.creeperhost.net/net/neoforged/neoforge";
|
|
8
|
+
const NEOFORGE_META = `${NEOFORGE_MAVEN}/maven-metadata.xml`;
|
|
9
|
+
const FORGE_MAVEN = "https://maven.creeperhost.net/net/minecraftforge/forge";
|
|
10
10
|
const FORGE_PROMOS = "https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json";
|
|
11
|
-
let mcCache = null;
|
|
12
11
|
let neoforgeCache = null;
|
|
13
12
|
let forgeCache = null;
|
|
14
13
|
export async function listMcVersions(type) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (!res.ok)
|
|
18
|
-
throw new Error(`Failed to fetch MC versions: ${res.status}`);
|
|
19
|
-
const data = await res.json();
|
|
20
|
-
mcCache = data.versions.filter((v) => v.type !== "old_beta" && v.type !== "old_alpha" &&
|
|
21
|
-
(new Date(v.releaseTime) >= new Date("2019-04-23") || LEGACY_SRG_VERSIONS.has(v.id) || LEGACY_RETROMCP_VERSIONS.has(v.id)));
|
|
22
|
-
}
|
|
23
|
-
if (!type || type === "all")
|
|
24
|
-
return mcCache;
|
|
25
|
-
if (type === "release")
|
|
26
|
-
return mcCache.filter((v) => v.type === "release");
|
|
27
|
-
return mcCache.filter((v) => v.type === "snapshot");
|
|
14
|
+
const versions = (await fetchMcVersionList(true)).map(({ id, type, releaseTime }) => ({ id, type, releaseTime }));
|
|
15
|
+
return !type || type === "all" ? versions : versions.filter(version => version.type === type);
|
|
28
16
|
}
|
|
29
17
|
export async function listNeoForgeVersions(mcVersion, limit = 20) {
|
|
18
|
+
if (mcVersion)
|
|
19
|
+
return (await loaderVersions(mcVersion, "neoforge")).slice(0, limit)
|
|
20
|
+
.map(v => ({ version: v.version, mcVersion: v.gameVersion ?? mcVersion }));
|
|
30
21
|
if (!neoforgeCache) {
|
|
31
22
|
const res = await fetch(NEOFORGE_META);
|
|
32
23
|
if (!res.ok)
|
|
33
24
|
throw new Error(`Failed to fetch NeoForge versions: ${res.status}`);
|
|
34
|
-
const data = await res.
|
|
25
|
+
const data = { versions: [...(await res.text()).matchAll(/<version>([^<]+)<\/version>/g)].map(m => m[1]) };
|
|
35
26
|
// NeoForge versions look like "21.1.0", "21.1.1", etc. — leading number = MC major
|
|
36
27
|
neoforgeCache = data.versions
|
|
37
28
|
.filter((v) => /^\d+\.\d+\.\d+/.test(v))
|
|
@@ -50,21 +41,17 @@ export async function listNeoForgeVersions(mcVersion, limit = 20) {
|
|
|
50
41
|
return filtered.slice(0, limit);
|
|
51
42
|
}
|
|
52
43
|
export async function listFabricApiVersions(mcVersion, limit = 20) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
:
|
|
57
|
-
|
|
58
|
-
if (!res.ok)
|
|
59
|
-
throw new Error(`Failed to fetch Fabric API versions: ${res.status}`);
|
|
60
|
-
const versions = await res.json();
|
|
61
|
-
return versions.slice(0, limit).map((v) => ({
|
|
62
|
-
version: v.version_number,
|
|
63
|
-
mcVersion: v.game_versions[0] ?? "unknown",
|
|
64
|
-
datePublished: v.date_published,
|
|
44
|
+
const versions = await providerVersions("mod", "P7dR8mSH", { mcVersion, loader: "fabric", limit });
|
|
45
|
+
return versions.map((v) => ({
|
|
46
|
+
version: v.version,
|
|
47
|
+
mcVersion: (v.targets ?? []).find(t => t.type === "game")?.version ?? "unknown",
|
|
48
|
+
datePublished: new Date((v.updated ?? 0) * 1000).toISOString(),
|
|
65
49
|
}));
|
|
66
50
|
}
|
|
67
51
|
export async function listForgeVersions(mcVersion, limit = 20) {
|
|
52
|
+
if (mcVersion)
|
|
53
|
+
return (await loaderVersions(mcVersion, "forge")).slice(0, limit)
|
|
54
|
+
.map(v => ({ version: v.version, fullVersion: `${mcVersion}-${v.version}`, mcVersion: v.gameVersion ?? mcVersion }));
|
|
68
55
|
if (!forgeCache) {
|
|
69
56
|
// Fetch promotions to know which versions are recommended
|
|
70
57
|
const promoRes = await fetch(FORGE_PROMOS, { headers: { "User-Agent": "modlens-mcp/1.0" } });
|
|
@@ -178,17 +165,6 @@ export async function downloadFabricApi(version) {
|
|
|
178
165
|
const destPath = join(CACHE_ROOT, "loaders", "fabric-api", `fabric-api-${version}.jar`);
|
|
179
166
|
if (await exists(destPath))
|
|
180
167
|
return destPath;
|
|
181
|
-
|
|
182
|
-
const res = await fetch(`https://api.modrinth.com/v2/project/P7dR8mSH/version?loaders=%5B%22fabric%22%5D`, { headers: { "User-Agent": "modlens-mcp/1.0" } });
|
|
183
|
-
if (!res.ok)
|
|
184
|
-
throw new Error(`Modrinth lookup failed: ${res.status}`);
|
|
185
|
-
const versions = await res.json();
|
|
186
|
-
const match = versions.find((v) => v.version_number === version);
|
|
187
|
-
if (!match)
|
|
188
|
-
throw new Error(`Fabric API version "${version}" not found on Modrinth`);
|
|
189
|
-
const file = match.files.find((f) => f.primary) ?? match.files[0];
|
|
190
|
-
if (!file)
|
|
191
|
-
throw new Error(`No download file found for Fabric API ${version}`);
|
|
192
|
-
return downloadJar(file.url, destPath);
|
|
168
|
+
return downloadJar(`https://maven.creeperhost.net/net/fabricmc/fabric-api/fabric-api/${encodeURIComponent(version)}/fabric-api-${encodeURIComponent(version)}.jar`, destPath);
|
|
193
169
|
}
|
|
194
170
|
//# sourceMappingURL=platform.js.map
|
package/dist/platform.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,IAAI,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"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"}
|
package/dist/processor.d.ts
CHANGED
|
@@ -24,11 +24,15 @@ export interface ParsedManifest {
|
|
|
24
24
|
mixinTargets: string[];
|
|
25
25
|
metadataSource: MetadataSource;
|
|
26
26
|
}
|
|
27
|
+
/** Read every declared loader, including JARs that ship metadata for multiple loaders. */
|
|
28
|
+
export declare function inspectJarLoaders(jarPath: string): Promise<Array<Exclude<ParsedManifest["loader"], "unknown">>>;
|
|
27
29
|
export declare function parseJar(jarPath: string): Promise<ParsedManifest>;
|
|
28
30
|
interface ForgeModAnnotationResult {
|
|
29
31
|
modId: string;
|
|
30
32
|
name: string;
|
|
31
33
|
version: string;
|
|
34
|
+
mcVersion: string;
|
|
35
|
+
dependencies: string;
|
|
32
36
|
}
|
|
33
37
|
/**
|
|
34
38
|
* Scan class entries in a JAR for the Forge @Mod annotation.
|
|
@@ -41,6 +45,7 @@ export declare function parseClassForModAnnotation(buf: Buffer): ForgeModAnnotat
|
|
|
41
45
|
export declare function parseAtEntries(content: string): string[];
|
|
42
46
|
export declare function parseAwEntries(content: string): string[];
|
|
43
47
|
export declare function computeHashes(jarPath: string): Promise<{
|
|
48
|
+
sha1: string;
|
|
44
49
|
sha256: string;
|
|
45
50
|
sha512: string;
|
|
46
51
|
murmur2: string;
|
package/dist/processor.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"processor.d.ts","sourceRoot":"","sources":["../src/processor.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"processor.d.ts","sourceRoot":"","sources":["../src/processor.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,SAAS,CAAC;AAO7B,MAAM,MAAM,cAAc,GAAG,iBAAiB,GAAG,gBAAgB,GAAG,WAAW,GAAG,YAAY,GAAG,iBAAiB,GAAG,UAAU,CAAC;AAEhI,qGAAqG;AACrG,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,cAAc,EAAE,MAAM,CAO3D,CAAC;AAEF,MAAM,WAAW,cAAc;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,QAAQ,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,GAAG,SAAS,CAAC;IAC9D,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,YAAY,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAC;KAAE,CAAC,CAAC;IACzE,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,cAAc,EAAE,cAAc,CAAC;CAClC;AAED,0FAA0F;AAC1F,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,CAgCrH;AAED,wBAAsB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CA4HvE;AA4PD,UAAU,wBAAwB;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,wBAAwB,GAAG,IAAI,CAqBtF;AAED,sEAAsE;AACtE,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,MAAM,GAAG,wBAAwB,GAAG,IAAI,CAwEvF;AA8ID,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAKxD;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAKxD;AAeD,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;CAAE,CAAC,CAMhI;AAED,oFAAoF;AACpF,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAyBnD"}
|
package/dist/processor.js
CHANGED
|
@@ -3,6 +3,7 @@ import { createHash } from "crypto";
|
|
|
3
3
|
import { readFile } from "fs/promises";
|
|
4
4
|
import { parse as parseToml } from "smol-toml";
|
|
5
5
|
import { assertJarPath } from "./security.js";
|
|
6
|
+
import { normalizeVersionRange } from "./version-ranges.js";
|
|
6
7
|
/** Higher = better quality metadata. Used to decide whether re-parse upgrades an existing record. */
|
|
7
8
|
export const METADATA_QUALITY = {
|
|
8
9
|
"filename": 0,
|
|
@@ -12,6 +13,49 @@ export const METADATA_QUALITY = {
|
|
|
12
13
|
"quilt.mod.json": 3,
|
|
13
14
|
"fabric.mod.json": 3,
|
|
14
15
|
};
|
|
16
|
+
/** Read every declared loader, including JARs that ship metadata for multiple loaders. */
|
|
17
|
+
export async function inspectJarLoaders(jarPath) {
|
|
18
|
+
assertJarPath(jarPath);
|
|
19
|
+
const zip = new AdmZip(jarPath);
|
|
20
|
+
const readEntry = (name) => zip.readAsText(name) || null;
|
|
21
|
+
const loaders = new Set();
|
|
22
|
+
const addManifest = (parse) => {
|
|
23
|
+
try {
|
|
24
|
+
const manifest = parse();
|
|
25
|
+
if (manifest.loader !== "unknown" && manifest.modId && manifest.modId !== "unknown")
|
|
26
|
+
loaders.add(manifest.loader);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// Invalid metadata is not loader evidence; another declaration may be valid.
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const fabric = readEntry("fabric.mod.json");
|
|
33
|
+
const quilt = readEntry("quilt.mod.json");
|
|
34
|
+
const forge = readEntry("META-INF/mods.toml");
|
|
35
|
+
const neoforge = readEntry("META-INF/neoforge.mods.toml");
|
|
36
|
+
const legacy = readEntry("mcmod.info");
|
|
37
|
+
if (fabric)
|
|
38
|
+
addManifest(() => parseFabric(fabric, []));
|
|
39
|
+
if (quilt)
|
|
40
|
+
addManifest(() => parseQuilt(quilt, []));
|
|
41
|
+
if (forge)
|
|
42
|
+
addManifest(() => parseForgeToml(forge, "forge", [], readEntry));
|
|
43
|
+
if (neoforge)
|
|
44
|
+
addManifest(() => parseForgeToml(neoforge, "neoforge", [], readEntry));
|
|
45
|
+
if (legacy)
|
|
46
|
+
addManifest(() => parseMcModInfo(legacy, []));
|
|
47
|
+
if (!loaders.size) {
|
|
48
|
+
// Annotation-only FML mods have no metadata file. Never guess from a filename.
|
|
49
|
+
try {
|
|
50
|
+
if (extractForgeModAnnotation(zip)?.modId)
|
|
51
|
+
loaders.add("forge");
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Unrecognised bytecode provides no loader evidence.
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return [...loaders];
|
|
58
|
+
}
|
|
15
59
|
export async function parseJar(jarPath) {
|
|
16
60
|
assertJarPath(jarPath);
|
|
17
61
|
const zip = new AdmZip(jarPath);
|
|
@@ -20,6 +64,7 @@ export async function parseJar(jarPath) {
|
|
|
20
64
|
const e = zip.getEntry(name);
|
|
21
65
|
return e ? zip.readFile(e)?.toString("utf8") ?? null : null;
|
|
22
66
|
};
|
|
67
|
+
const jarAttributes = parseJarAttributes(readEntry("META-INF/MANIFEST.MF") ?? "");
|
|
23
68
|
// Detect loader
|
|
24
69
|
const fabricJson = readEntry("fabric.mod.json");
|
|
25
70
|
const quiltJson = readEntry("quilt.mod.json");
|
|
@@ -33,19 +78,19 @@ export async function parseJar(jarPath) {
|
|
|
33
78
|
manifest = parseQuilt(quiltJson, entries);
|
|
34
79
|
}
|
|
35
80
|
else if (neoforgeToml) {
|
|
36
|
-
manifest =
|
|
81
|
+
manifest = parseForgeToml(neoforgeToml, "neoforge", entries, readEntry);
|
|
37
82
|
}
|
|
38
83
|
else if (forgeToml) {
|
|
39
|
-
manifest =
|
|
84
|
+
manifest = parseForgeToml(forgeToml, "forge", entries, readEntry);
|
|
40
85
|
}
|
|
41
86
|
else {
|
|
42
|
-
//
|
|
87
|
+
// FML-era mods can provide mcmod.info, an @Mod annotation, or both.
|
|
43
88
|
const mcmodInfoRaw = readEntry("mcmod.info");
|
|
44
89
|
if (mcmodInfoRaw) {
|
|
45
90
|
manifest = parseMcModInfo(mcmodInfoRaw, entries);
|
|
46
91
|
}
|
|
47
92
|
else {
|
|
48
|
-
//
|
|
93
|
+
// Annotation metadata is also used by mods that omit mcmod.info.
|
|
49
94
|
let modAnnotation = null;
|
|
50
95
|
try {
|
|
51
96
|
modAnnotation = extractForgeModAnnotation(zip);
|
|
@@ -54,16 +99,16 @@ export async function parseJar(jarPath) {
|
|
|
54
99
|
// Malformed class files (obfuscators, Kotlin metadata, etc.) — degrade gracefully
|
|
55
100
|
}
|
|
56
101
|
if (modAnnotation) {
|
|
57
|
-
const mixinConfigs = entries
|
|
102
|
+
const mixinConfigs = mixinConfigNames(entries);
|
|
58
103
|
manifest = {
|
|
59
104
|
modId: modAnnotation.modId,
|
|
60
105
|
displayName: modAnnotation.name,
|
|
61
106
|
version: modAnnotation.version,
|
|
62
|
-
mcVersion:
|
|
107
|
+
mcVersion: modAnnotation.mcVersion,
|
|
63
108
|
loader: "forge",
|
|
64
109
|
description: "",
|
|
65
110
|
sourceUrl: null,
|
|
66
|
-
dependencies:
|
|
111
|
+
dependencies: parseLegacyDependencies(modAnnotation.dependencies.split(";")),
|
|
67
112
|
mixinConfigs,
|
|
68
113
|
hasMixins: mixinConfigs.length > 0,
|
|
69
114
|
hasAt: entries.includes("META-INF/accesstransformer.cfg"),
|
|
@@ -79,12 +124,27 @@ export async function parseJar(jarPath) {
|
|
|
79
124
|
}
|
|
80
125
|
}
|
|
81
126
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
127
|
+
if (manifest.version === "${file.jarVersion}" && jarAttributes["implementation-version"]) {
|
|
128
|
+
manifest.version = jarAttributes["implementation-version"];
|
|
129
|
+
}
|
|
130
|
+
// Legacy FML names AT files in the manifest; later Forge uses a fixed path.
|
|
131
|
+
const atPaths = new Set(["META-INF/accesstransformer.cfg"]);
|
|
132
|
+
for (const name of (jarAttributes.fmlat ?? "").split(/\s+/).filter(Boolean)) {
|
|
133
|
+
atPaths.add(name.startsWith("META-INF/") ? name : `META-INF/${name}`);
|
|
134
|
+
}
|
|
135
|
+
for (const path of atPaths) {
|
|
136
|
+
const content = readEntry(path);
|
|
137
|
+
if (content !== null) {
|
|
138
|
+
manifest.hasAt = true;
|
|
139
|
+
manifest.atEntries.push(...parseAtEntries(content));
|
|
140
|
+
}
|
|
87
141
|
}
|
|
142
|
+
manifest.atEntries = [...new Set(manifest.atEntries)];
|
|
143
|
+
manifest.mixinConfigs = [...new Set([
|
|
144
|
+
...manifest.mixinConfigs,
|
|
145
|
+
...(jarAttributes.mixinconfigs ?? "").split(",").map(name => name.trim()).filter(name => entries.includes(name)),
|
|
146
|
+
])];
|
|
147
|
+
manifest.hasMixins = manifest.mixinConfigs.length > 0;
|
|
88
148
|
// AW entries — scan all entries for *.accesswidener
|
|
89
149
|
const awEntry = entries.find((e) => e.endsWith(".accesswidener"));
|
|
90
150
|
if (awEntry) {
|
|
@@ -116,6 +176,26 @@ export async function parseJar(jarPath) {
|
|
|
116
176
|
}
|
|
117
177
|
return manifest;
|
|
118
178
|
}
|
|
179
|
+
/** JAR manifest main attributes, with continuation lines unfolded per the JAR format. */
|
|
180
|
+
function parseJarAttributes(content) {
|
|
181
|
+
const main = content.replace(/\r?\n /g, "").split(/\r?\n\r?\n/, 1)[0];
|
|
182
|
+
const attributes = {};
|
|
183
|
+
for (const line of main.split(/\r?\n/)) {
|
|
184
|
+
const separator = line.indexOf(": ");
|
|
185
|
+
if (separator > 0)
|
|
186
|
+
attributes[line.slice(0, separator).toLowerCase()] = line.slice(separator + 2);
|
|
187
|
+
}
|
|
188
|
+
return attributes;
|
|
189
|
+
}
|
|
190
|
+
function mixinConfigNames(entries, declared) {
|
|
191
|
+
const values = Array.isArray(declared) ? declared : declared ? [declared] : [];
|
|
192
|
+
const names = values.map(value => typeof value === "string" ? value
|
|
193
|
+
: value && typeof value === "object" ? value.config : undefined);
|
|
194
|
+
return [...new Set([
|
|
195
|
+
...names.filter((name) => typeof name === "string" && entries.includes(name)),
|
|
196
|
+
...entries.filter(name => name.endsWith(".mixins.json") || /(^|\/)mixins\.[^/]+\.json$/.test(name)),
|
|
197
|
+
])];
|
|
198
|
+
}
|
|
119
199
|
function parseFabric(raw, entries) {
|
|
120
200
|
let json;
|
|
121
201
|
try {
|
|
@@ -124,11 +204,20 @@ function parseFabric(raw, entries) {
|
|
|
124
204
|
catch {
|
|
125
205
|
json = {};
|
|
126
206
|
}
|
|
127
|
-
const mixinConfigs = entries
|
|
207
|
+
const mixinConfigs = mixinConfigNames(entries, json.mixins);
|
|
128
208
|
const deps = [];
|
|
129
209
|
const rawDeps = (json.depends ?? {});
|
|
130
210
|
for (const [id, ver] of Object.entries(rawDeps)) {
|
|
131
|
-
deps.push({ id, version: ver, required: true });
|
|
211
|
+
deps.push({ id, version: normalizeVersionRange(ver), required: true });
|
|
212
|
+
}
|
|
213
|
+
for (const field of ["recommends", "suggests"]) {
|
|
214
|
+
const optional = json[field];
|
|
215
|
+
if (!optional || typeof optional !== "object")
|
|
216
|
+
continue;
|
|
217
|
+
for (const [id, ver] of Object.entries(optional)) {
|
|
218
|
+
if (!deps.some(dep => dep.id === id))
|
|
219
|
+
deps.push({ id, version: normalizeVersionRange(ver), required: false });
|
|
220
|
+
}
|
|
132
221
|
}
|
|
133
222
|
return {
|
|
134
223
|
modId: String(json.id ?? "unknown"),
|
|
@@ -158,16 +247,26 @@ function parseQuilt(raw, entries) {
|
|
|
158
247
|
json = {};
|
|
159
248
|
}
|
|
160
249
|
const ql = (json.quilt_loader ?? {});
|
|
161
|
-
const mixinConfigs = entries
|
|
250
|
+
const mixinConfigs = mixinConfigNames(entries, json.mixin);
|
|
251
|
+
const metadata = (ql.metadata ?? {});
|
|
252
|
+
const dependencies = [];
|
|
253
|
+
for (const value of Array.isArray(ql.depends) ? ql.depends : []) {
|
|
254
|
+
if (typeof value === "string")
|
|
255
|
+
dependencies.push({ id: value, version: "*", required: true });
|
|
256
|
+
else if (value && typeof value.id === "string")
|
|
257
|
+
dependencies.push({
|
|
258
|
+
id: value.id, version: normalizeVersionRange(value.versions) || "*", required: value.optional !== true,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
162
261
|
return {
|
|
163
262
|
modId: String(ql.id ?? "unknown"),
|
|
164
263
|
displayName: String(ql.metadata?.name ?? ql.id ?? "unknown"),
|
|
165
264
|
version: String(ql.version ?? "0.0.0"),
|
|
166
|
-
mcVersion: "",
|
|
265
|
+
mcVersion: dependencies.find(dep => dep.id === "minecraft")?.version ?? "",
|
|
167
266
|
loader: "quilt",
|
|
168
|
-
description: "",
|
|
169
|
-
sourceUrl:
|
|
170
|
-
dependencies
|
|
267
|
+
description: typeof metadata.description === "string" ? metadata.description : "",
|
|
268
|
+
sourceUrl: extractString(metadata, "contact", "sources"),
|
|
269
|
+
dependencies,
|
|
171
270
|
mixinConfigs,
|
|
172
271
|
hasMixins: mixinConfigs.length > 0,
|
|
173
272
|
hasAt: false,
|
|
@@ -178,13 +277,7 @@ function parseQuilt(raw, entries) {
|
|
|
178
277
|
metadataSource: "quilt.mod.json",
|
|
179
278
|
};
|
|
180
279
|
}
|
|
181
|
-
function
|
|
182
|
-
return parseForgeToml(raw, "neoforge", entries);
|
|
183
|
-
}
|
|
184
|
-
function parseForge(raw, entries) {
|
|
185
|
-
return parseForgeToml(raw, "forge", entries);
|
|
186
|
-
}
|
|
187
|
-
function parseForgeToml(raw, loader, entries) {
|
|
280
|
+
function parseForgeToml(raw, loader, entries, readEntry) {
|
|
188
281
|
let doc;
|
|
189
282
|
try {
|
|
190
283
|
doc = parseToml(raw);
|
|
@@ -213,19 +306,31 @@ function parseForgeToml(raw, loader, entries) {
|
|
|
213
306
|
if (Array.isArray(v))
|
|
214
307
|
allDepObjs.push(...v);
|
|
215
308
|
}
|
|
309
|
+
// Early NeoForge versions still used mods.toml.
|
|
310
|
+
if (modId === "neoforge" || allDepObjs.some(dep => dep.modId === "neoforge"))
|
|
311
|
+
loader = "neoforge";
|
|
216
312
|
const mcDepEntry = allDepObjs.find((d) => str(d.modId) === "minecraft");
|
|
217
313
|
const mcVersion = str(mcDepEntry?.versionRange);
|
|
218
314
|
const dependencies = allDepObjs
|
|
219
315
|
.filter((d) => {
|
|
220
316
|
const id = str(d.modId);
|
|
221
|
-
return id && id !== "minecraft" && id !== "neoforge" && id !== "forge"
|
|
317
|
+
return id && id !== "minecraft" && id !== "neoforge" && id !== "forge"
|
|
318
|
+
&& d.type !== "incompatible" && d.type !== "discouraged";
|
|
222
319
|
})
|
|
223
320
|
.map((d) => ({
|
|
224
321
|
id: str(d.modId),
|
|
225
322
|
version: str(d.versionRange, "*"),
|
|
226
|
-
required: d.mandatory !== false,
|
|
323
|
+
required: d.type ? d.type === "required" : d.mandatory !== false,
|
|
227
324
|
}));
|
|
228
|
-
const mixinConfigs = entries
|
|
325
|
+
const mixinConfigs = mixinConfigNames(entries, doc.mixins);
|
|
326
|
+
const atEntries = [];
|
|
327
|
+
for (const at of Array.isArray(doc.accessTransformers) ? doc.accessTransformers : []) {
|
|
328
|
+
if (typeof at?.file !== "string")
|
|
329
|
+
continue;
|
|
330
|
+
const content = readEntry(at.file);
|
|
331
|
+
if (content !== null)
|
|
332
|
+
atEntries.push(...parseAtEntries(content));
|
|
333
|
+
}
|
|
229
334
|
return {
|
|
230
335
|
modId,
|
|
231
336
|
displayName,
|
|
@@ -237,9 +342,9 @@ function parseForgeToml(raw, loader, entries) {
|
|
|
237
342
|
dependencies,
|
|
238
343
|
mixinConfigs,
|
|
239
344
|
hasMixins: mixinConfigs.length > 0,
|
|
240
|
-
hasAt: entries.includes("META-INF/accesstransformer.cfg"),
|
|
345
|
+
hasAt: entries.includes("META-INF/accesstransformer.cfg") || atEntries.length > 0,
|
|
241
346
|
hasAw: false,
|
|
242
|
-
atEntries
|
|
347
|
+
atEntries,
|
|
243
348
|
awEntries: [],
|
|
244
349
|
mixinTargets: [],
|
|
245
350
|
metadataSource: "mods.toml",
|
|
@@ -271,21 +376,11 @@ function parseMcModInfo(raw, entries) {
|
|
|
271
376
|
return unknownMod("mcmod.info");
|
|
272
377
|
const str = (v, fallback = "") => (typeof v === "string" ? v : fallback);
|
|
273
378
|
const modId = str(mod.modid ?? mod.modId, "unknown");
|
|
274
|
-
const mixinConfigs = entries
|
|
379
|
+
const mixinConfigs = mixinConfigNames(entries);
|
|
275
380
|
// Dependencies — handle multiple legacy formats
|
|
276
381
|
const rawDeps = Array.isArray(mod.dependencies) ? mod.dependencies : [];
|
|
277
382
|
const rawRequired = Array.isArray(mod.requiredMods) ? mod.requiredMods : [];
|
|
278
|
-
const
|
|
279
|
-
const dependencies = allDeps
|
|
280
|
-
.filter((d) => typeof d === "string")
|
|
281
|
-
.filter((d) => d !== "Forge" && d !== "forge" && d !== "FML" && d !== "mcp" && d !== "minecraft")
|
|
282
|
-
// Strip version ranges from dependency strings like "required-after:SomeLib@[1.0,)"
|
|
283
|
-
.map((d) => {
|
|
284
|
-
const depMatch = d.match(/^(?:required-after|after|required-before|before|required):?\s*([^@\s;]+)/i);
|
|
285
|
-
return depMatch ? depMatch[1] : d;
|
|
286
|
-
})
|
|
287
|
-
.filter((d) => d !== "Forge" && d !== "forge" && d !== "FML")
|
|
288
|
-
.map((d) => ({ id: d, version: "*", required: true }));
|
|
383
|
+
const dependencies = parseLegacyDependencies(rawDeps, rawRequired);
|
|
289
384
|
return {
|
|
290
385
|
modId,
|
|
291
386
|
displayName: str(mod.name, modId),
|
|
@@ -305,6 +400,27 @@ function parseMcModInfo(raw, entries) {
|
|
|
305
400
|
metadataSource: "mcmod.info",
|
|
306
401
|
};
|
|
307
402
|
}
|
|
403
|
+
function parseLegacyDependencies(declarations, requiredMods = []) {
|
|
404
|
+
const dependencies = new Map();
|
|
405
|
+
for (const [values, required] of [[declarations, false], [requiredMods, true]]) {
|
|
406
|
+
for (const value of values) {
|
|
407
|
+
if (typeof value !== "string")
|
|
408
|
+
continue;
|
|
409
|
+
const match = value.trim().match(/^(?:(required-(?:after|before)|after|before|required):)?([^@\s;]+)(?:@(.+))?$/i);
|
|
410
|
+
if (!match)
|
|
411
|
+
continue;
|
|
412
|
+
const [, order = "", id, version = "*"] = match;
|
|
413
|
+
if (["forge", "fml", "mcp", "minecraft", "*"].includes(id.toLowerCase()))
|
|
414
|
+
continue;
|
|
415
|
+
const previous = dependencies.get(id);
|
|
416
|
+
dependencies.set(id, {
|
|
417
|
+
id, version: version === "*" ? previous?.version ?? "*" : version,
|
|
418
|
+
required: required || order.toLowerCase().startsWith("required") || previous?.required === true,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return [...dependencies.values()];
|
|
423
|
+
}
|
|
308
424
|
// ── @Mod annotation extraction from bytecode ──────────────────────────────────
|
|
309
425
|
const FORGE_MOD_DESCRIPTORS = new Set([
|
|
310
426
|
"Lcpw/mods/fml/common/Mod;", // Forge 1.7.10 and earlier
|
|
@@ -345,6 +461,8 @@ export function extractForgeModAnnotation(zip) {
|
|
|
345
461
|
}
|
|
346
462
|
/** Parse a single .class file's bytes for a Forge @Mod annotation. */
|
|
347
463
|
export function parseClassForModAnnotation(buf) {
|
|
464
|
+
if (buf.length < 10)
|
|
465
|
+
return null;
|
|
348
466
|
if (buf.readUInt32BE(0) !== 0xCAFEBABE)
|
|
349
467
|
return null;
|
|
350
468
|
// ── Parse constant pool ──
|
|
@@ -494,6 +612,8 @@ function parseModAnnotationAttr(buf, start, cpUtf8) {
|
|
|
494
612
|
modId,
|
|
495
613
|
name: values["name"] ?? modId,
|
|
496
614
|
version: values["version"] ?? "0.0.0",
|
|
615
|
+
mcVersion: values["acceptedMinecraftVersions"] ?? "",
|
|
616
|
+
dependencies: values["dependencies"] ?? "",
|
|
497
617
|
};
|
|
498
618
|
}
|
|
499
619
|
}
|
|
@@ -592,7 +712,7 @@ function unknownMod(jarPath) {
|
|
|
592
712
|
export function parseAtEntries(content) {
|
|
593
713
|
return content
|
|
594
714
|
.split("\n")
|
|
595
|
-
.map((l) => l.trim())
|
|
715
|
+
.map((l) => l.split("#", 1)[0].trim())
|
|
596
716
|
.filter((l) => l && !l.startsWith("#"));
|
|
597
717
|
}
|
|
598
718
|
export function parseAwEntries(content) {
|
|
@@ -602,7 +722,7 @@ export function parseAwEntries(content) {
|
|
|
602
722
|
.filter((l) => l && !l.startsWith("#") && !l.startsWith("accessWidener"));
|
|
603
723
|
}
|
|
604
724
|
function extractFabricMcVersion(deps) {
|
|
605
|
-
return deps["minecraft"]
|
|
725
|
+
return normalizeVersionRange(deps["minecraft"]);
|
|
606
726
|
}
|
|
607
727
|
function extractString(obj, ...keys) {
|
|
608
728
|
let cur = obj;
|
|
@@ -618,7 +738,7 @@ export async function computeHashes(jarPath) {
|
|
|
618
738
|
const sha256 = createHash("sha256").update(buf).digest("hex");
|
|
619
739
|
const sha512 = createHash("sha512").update(buf).digest("hex");
|
|
620
740
|
const murmur2 = computeMurmur2(buf).toString();
|
|
621
|
-
return { sha256, sha512, murmur2 };
|
|
741
|
+
return { sha1: createHash("sha1").update(buf).digest("hex"), sha256, sha512, murmur2 };
|
|
622
742
|
}
|
|
623
743
|
/** CurseForge Murmur2 hash — matches CF API fingerprint (whitespace-normalized). */
|
|
624
744
|
export function computeMurmur2(data) {
|