@creeperhost/modlens-mcp 1.6.19 → 1.6.21
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/ATTRIBUTION.md +8 -1
- package/README.md +82 -5
- package/RUNTIME.md +262 -0
- package/TESTING.md +8 -0
- package/dist/cli.js +28 -8
- package/dist/cli.js.map +1 -1
- package/dist/hosted-mod-license.d.ts +36 -0
- package/dist/hosted-mod-license.d.ts.map +1 -0
- package/dist/hosted-mod-license.js +65 -0
- package/dist/hosted-mod-license.js.map +1 -0
- package/dist/hosted-policy.d.ts +32 -0
- package/dist/hosted-policy.d.ts.map +1 -0
- package/dist/hosted-policy.js +257 -0
- package/dist/hosted-policy.js.map +1 -0
- package/dist/launcher.js +104 -89
- package/dist/launcher.js.map +1 -1
- package/dist/license-notices.d.ts +8 -0
- package/dist/license-notices.d.ts.map +1 -0
- package/dist/license-notices.js +10 -0
- package/dist/license-notices.js.map +1 -0
- package/dist/license-source-references.d.ts +12 -0
- package/dist/license-source-references.d.ts.map +1 -0
- package/dist/license-source-references.js +18 -0
- package/dist/license-source-references.js.map +1 -0
- package/dist/license-templates.d.ts +6 -0
- package/dist/license-templates.d.ts.map +1 -0
- package/dist/license-templates.js +55 -0
- package/dist/license-templates.js.map +1 -0
- package/dist/local-mod.d.ts +31 -0
- package/dist/local-mod.d.ts.map +1 -0
- package/dist/local-mod.js +71 -0
- package/dist/local-mod.js.map +1 -0
- package/dist/mod-license.d.ts +53 -0
- package/dist/mod-license.d.ts.map +1 -0
- package/dist/mod-license.js +617 -0
- package/dist/mod-license.js.map +1 -0
- package/dist/primer-catalog.d.ts +15 -0
- package/dist/primer-catalog.d.ts.map +1 -0
- package/dist/primer-catalog.js +43 -0
- package/dist/primer-catalog.js.map +1 -0
- package/dist/primer-content.d.ts +7 -0
- package/dist/primer-content.d.ts.map +1 -0
- package/dist/primer-content.js +185 -0
- package/dist/primer-content.js.map +1 -0
- package/dist/runtime/cli.d.ts +2 -0
- package/dist/runtime/cli.d.ts.map +1 -0
- package/dist/runtime/cli.js +163 -0
- package/dist/runtime/cli.js.map +1 -0
- package/dist/runtime/companion.d.ts +32 -0
- package/dist/runtime/companion.d.ts.map +1 -0
- package/dist/runtime/companion.js +135 -0
- package/dist/runtime/companion.js.map +1 -0
- package/dist/runtime/guidance.d.ts +73 -0
- package/dist/runtime/guidance.d.ts.map +1 -0
- package/dist/runtime/guidance.js +40 -0
- package/dist/runtime/guidance.js.map +1 -0
- package/dist/runtime/hub.d.ts +150 -0
- package/dist/runtime/hub.d.ts.map +1 -0
- package/dist/runtime/hub.js +561 -0
- package/dist/runtime/hub.js.map +1 -0
- package/dist/runtime/modlens-agent.jar +0 -0
- package/dist/runtime/protocol.d.ts +202 -0
- package/dist/runtime/protocol.d.ts.map +1 -0
- package/dist/runtime/protocol.js +91 -0
- package/dist/runtime/protocol.js.map +1 -0
- package/dist/runtime/requests.d.ts +367 -0
- package/dist/runtime/requests.d.ts.map +1 -0
- package/dist/runtime/requests.js +57 -0
- package/dist/runtime/requests.js.map +1 -0
- package/dist/server.js +141 -45
- package/dist/server.js.map +1 -1
- package/dist/setup.js +9 -4
- package/dist/setup.js.map +1 -1
- package/dist/tools/primers.d.ts +133 -22
- package/dist/tools/primers.d.ts.map +1 -1
- package/dist/tools/primers.js +391 -386
- package/dist/tools/primers.js.map +1 -1
- package/dist/tools/project.d.ts +11 -7
- package/dist/tools/project.d.ts.map +1 -1
- package/dist/tools/project.js +45 -3
- package/dist/tools/project.js.map +1 -1
- package/dist/tools/report-issue.d.ts +77 -0
- package/dist/tools/report-issue.d.ts.map +1 -0
- package/dist/tools/report-issue.js +92 -0
- package/dist/tools/report-issue.js.map +1 -0
- package/dist/tools/runtime.d.ts +6 -0
- package/dist/tools/runtime.d.ts.map +1 -0
- package/dist/tools/runtime.js +18 -0
- package/dist/tools/runtime.js.map +1 -0
- package/dist/tools/source.d.ts +1 -1
- package/dist/tools/source.d.ts.map +1 -1
- package/dist/tools/source.js +5 -1
- package/dist/tools/source.js.map +1 -1
- package/package.json +8 -1
- package/scripts/build-runtime-agent.mjs +64 -0
- package/scripts/fixtures/mod-license-corpus.json +154 -0
- package/scripts/gradle/modlens-runtime.init.gradle +17 -0
- package/scripts/runtime-test-client.mjs +36 -0
- package/scripts/test-hosted-policy.mjs +123 -0
- package/scripts/test-mod-licenses-live.mjs +86 -0
- package/scripts/test-package.mjs +141 -5
- package/scripts/test-primers-live.mjs +93 -0
- package/scripts/test-project-http.mjs +46 -3
- package/scripts/test-runtime-gradle.mjs +107 -0
- package/scripts/test-runtime-minecraft.mjs +190 -0
- package/scripts/test-runtime-native.mjs +120 -0
- package/scripts/test-runtime-remote.mjs +209 -0
- package/scripts/update-license-templates.mjs +14 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { CACHE_ROOT } from "../cache.js";
|
|
3
|
+
import { RuntimeHub, RUNTIME_HELP } from "../runtime/hub.js";
|
|
4
|
+
import { executeRuntime, runtimeRequest } from "../runtime/requests.js";
|
|
5
|
+
import { localExecutionPlan } from "../runtime/guidance.js";
|
|
6
|
+
export { runtimeToolSchema } from "../runtime/requests.js";
|
|
7
|
+
export const runtimeHub = new RuntimeHub(join(CACHE_ROOT, "runtime"));
|
|
8
|
+
export async function runtimeAction(raw) {
|
|
9
|
+
const request = runtimeRequest.parse(raw);
|
|
10
|
+
if (process.env.MCP_PORT) {
|
|
11
|
+
if (request.action === "help")
|
|
12
|
+
return { ...RUNTIME_HELP, localExecution: localExecutionPlan(request) };
|
|
13
|
+
// A remote tool supplies instructions, never executes paths on the user's PC.
|
|
14
|
+
return localExecutionPlan(request);
|
|
15
|
+
}
|
|
16
|
+
return executeRuntime(runtimeHub, request);
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/tools/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,cAAc,EAAuB,MAAM,wBAAwB,CAAC;AAC7F,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAE5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,MAAM,CAAC,MAAM,UAAU,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC;AAEtE,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAmB;IACnD,MAAM,OAAO,GAAG,cAAc,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC1C,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QACvB,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM;YACzB,OAAO,EAAE,GAAG,YAAY,EAAE,cAAc,EAAE,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5E,8EAA8E;QAC9E,OAAO,kBAAkB,CAAC,OAAO,CAAC,CAAC;IACvC,CAAC;IACD,OAAO,cAAc,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;AAC/C,CAAC"}
|
package/dist/tools/source.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare function getModSource(dbId: number, path?: string): Promise<string>;
|
|
1
|
+
export declare function getModSource(dbId: number, path?: string, startLine?: number, maxLines?: number): Promise<string>;
|
|
2
2
|
export declare function searchSource(query: string, dbId?: number, isRegex?: boolean, limit?: number): Promise<Array<{
|
|
3
3
|
modId: string;
|
|
4
4
|
modVersion: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../../src/tools/source.ts"],"names":[],"mappings":"AA4BA,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../../src/tools/source.ts"],"names":[],"mappings":"AA4BA,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAqBtH;AAED,wBAAsB,YAAY,CAC9B,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE,MAAM,EACb,OAAO,UAAQ,EACf,KAAK,SAAK,GACX,OAAO,CAAC,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAejG;AAkCD,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAaxF"}
|
package/dist/tools/source.js
CHANGED
|
@@ -29,7 +29,7 @@ async function getDecompPath(dbId) {
|
|
|
29
29
|
}
|
|
30
30
|
throw new Error(`Decompile timed out for mod #${dbId}`);
|
|
31
31
|
}
|
|
32
|
-
export async function getModSource(dbId, path) {
|
|
32
|
+
export async function getModSource(dbId, path, startLine, maxLines) {
|
|
33
33
|
validateDbId(dbId);
|
|
34
34
|
const decompPath = await getDecompPath(dbId);
|
|
35
35
|
if (!path) {
|
|
@@ -46,6 +46,10 @@ export async function getModSource(dbId, path) {
|
|
|
46
46
|
return entries.join("\n");
|
|
47
47
|
}
|
|
48
48
|
const content = await readFile(filePath, "utf8");
|
|
49
|
+
if (startLine !== undefined || maxLines !== undefined) {
|
|
50
|
+
const start = Math.max(0, (startLine ?? 1) - 1);
|
|
51
|
+
return content.split(/\r\n|\n|\r/).slice(start, start + (maxLines ?? 200)).join("\n");
|
|
52
|
+
}
|
|
49
53
|
return content.slice(0, 50_000); // cap at 50KB
|
|
50
54
|
}
|
|
51
55
|
export async function searchSource(query, dbId, isRegex = false, limit = 50) {
|
package/dist/tools/source.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"source.js","sourceRoot":"","sources":["../../src/tools/source.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,cAAc,IAAI,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACxE,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAEjC,KAAK,UAAU,aAAa,CAAC,IAAY;IACrC,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,QAAQ,IAAI,YAAY,CAAC,CAAC;IACpD,IAAI,GAAG,CAAC,UAAU,IAAI,MAAM,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC;QAAE,OAAO,GAAG,CAAC,UAAU,CAAC;IAC1E,+DAA+D;IAC/D,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,CAAC;IACxC,IAAI,MAAM,CAAC,MAAM,KAAK,MAAM;QAAE,OAAO,MAAM,CAAC,MAAM,CAAC;IACnD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;IAC5C,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;QAC3B,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;QAC9C,MAAM,CAAC,GAAG,MAAM,kBAAkB,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM;YAAE,OAAO,CAAC,CAAC,MAAM,CAAC;QACzC,IAAI,CAAC,CAAC,MAAM,KAAK,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,IAAI,EAAE,CAAC,CAAC;IACpF,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,gCAAgC,IAAI,EAAE,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAY,EAAE,IAAa;
|
|
1
|
+
{"version":3,"file":"source.js","sourceRoot":"","sources":["../../src/tools/source.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@creeperhost/modlens-mcp",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.21",
|
|
4
4
|
"description": "MCP server for browsing, decompiling, and analyzing Minecraft mod JARs",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"scripts/",
|
|
23
23
|
"package.json",
|
|
24
24
|
"README.md",
|
|
25
|
+
"RUNTIME.md",
|
|
25
26
|
"TESTING.md",
|
|
26
27
|
"LICENSE",
|
|
27
28
|
"ATTRIBUTION.md"
|
|
@@ -29,12 +30,15 @@
|
|
|
29
30
|
"scripts": {
|
|
30
31
|
"postinstall": "prisma generate",
|
|
31
32
|
"build": "npm run db:generate:sqlite && tsc && node scripts/build-template-db.mjs",
|
|
33
|
+
"build:agent": "node scripts/build-runtime-agent.mjs",
|
|
34
|
+
"test:runtime:native": "node scripts/test-runtime-native.mjs",
|
|
32
35
|
"dev": "tsc --watch",
|
|
33
36
|
"start": "node dist/server.js",
|
|
34
37
|
"setup": "npm run build && node dist/setup.js",
|
|
35
38
|
"test": "vitest run",
|
|
36
39
|
"test:package": "node scripts/test-package.mjs",
|
|
37
40
|
"test:minecraft:eras": "node scripts/test-minecraft-eras.mjs",
|
|
41
|
+
"test:primers:live": "node scripts/test-primers-live.mjs",
|
|
38
42
|
"test:project:gradle": "node scripts/test-project-gradle.mjs",
|
|
39
43
|
"test:project:http": "node scripts/test-project-http.mjs",
|
|
40
44
|
"test:watch": "vitest",
|
|
@@ -61,12 +65,15 @@
|
|
|
61
65
|
"better-sqlite3": "^12.11.1",
|
|
62
66
|
"prisma": "^6.0.0",
|
|
63
67
|
"smol-toml": "^1.6.1",
|
|
68
|
+
"turndown": "^7.2.4",
|
|
69
|
+
"turndown-plugin-gfm": "^1.0.2",
|
|
64
70
|
"zod": "^3.23.8"
|
|
65
71
|
},
|
|
66
72
|
"devDependencies": {
|
|
67
73
|
"@types/adm-zip": "^0.5.7",
|
|
68
74
|
"@types/better-sqlite3": "^7.0.0",
|
|
69
75
|
"@types/node": "^22.0.0",
|
|
76
|
+
"@types/turndown": "^5.0.6",
|
|
70
77
|
"typescript": "^5.6.0",
|
|
71
78
|
"vitest": "^4.1.6"
|
|
72
79
|
},
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, readdir, writeFile, rm } from "node:fs/promises";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
5
|
+
|
|
6
|
+
const root = fileURLToPath(new URL("..", import.meta.url));
|
|
7
|
+
const scratch = join(root, ".scratch");
|
|
8
|
+
await mkdir(scratch, { recursive: true });
|
|
9
|
+
// A clean output directory prevents removed/renamed classes leaking into the JAR.
|
|
10
|
+
const build = await mkdtemp(join(scratch, "runtime-build-"));
|
|
11
|
+
const output = join(root, "dist/runtime");
|
|
12
|
+
const javaTool = (name) =>
|
|
13
|
+
process.env.JAVA_HOME
|
|
14
|
+
? join(process.env.JAVA_HOME, "bin", name + (process.platform === "win32" ? ".exe" : ""))
|
|
15
|
+
: name;
|
|
16
|
+
async function sources(dir) {
|
|
17
|
+
return (
|
|
18
|
+
await Promise.all(
|
|
19
|
+
(await readdir(dir, { withFileTypes: true })).map((e) =>
|
|
20
|
+
e.isDirectory()
|
|
21
|
+
? sources(join(dir, e.name))
|
|
22
|
+
: e.name.endsWith(".java")
|
|
23
|
+
? [join(dir, e.name)]
|
|
24
|
+
: [],
|
|
25
|
+
),
|
|
26
|
+
)
|
|
27
|
+
).flat();
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
await mkdir(output, { recursive: true });
|
|
31
|
+
const version = execFileSync(javaTool("javac"), ["-version"], { encoding: "utf8" });
|
|
32
|
+
if (Number(/javac (\d+)/.exec(version)?.[1]) < 25)
|
|
33
|
+
throw new Error("Building the runtime agent requires JDK 25+. Set JAVA_HOME.");
|
|
34
|
+
const files = await sources(join(root, "runtime-agent/src"));
|
|
35
|
+
// javac argument files avoid Windows command length limits and handle spaces.
|
|
36
|
+
const args = ["--release", "25", "-d", build, ...files];
|
|
37
|
+
await writeFile(
|
|
38
|
+
join(build, "javac.args"),
|
|
39
|
+
args.map((a) => `"${a.replaceAll("\\", "/").replaceAll('"', '\\"')}"`).join("\n"),
|
|
40
|
+
);
|
|
41
|
+
execFileSync(javaTool("javac"), ["@" + join(build, "javac.args")], { stdio: "inherit" });
|
|
42
|
+
await writeFile(
|
|
43
|
+
join(build, "MANIFEST.MF"),
|
|
44
|
+
"Manifest-Version: 1.0\nPremain-Class: io.modlens.runtime.Agent\nCan-Redefine-Classes: false\nCan-Retransform-Classes: false\n\n",
|
|
45
|
+
);
|
|
46
|
+
execFileSync(
|
|
47
|
+
javaTool("jar"),
|
|
48
|
+
[
|
|
49
|
+
"--create",
|
|
50
|
+
"--file",
|
|
51
|
+
join(output, "modlens-agent.jar"),
|
|
52
|
+
"--manifest",
|
|
53
|
+
join(build, "MANIFEST.MF"),
|
|
54
|
+
"-C",
|
|
55
|
+
build,
|
|
56
|
+
"io",
|
|
57
|
+
],
|
|
58
|
+
{ stdio: "inherit" },
|
|
59
|
+
);
|
|
60
|
+
console.log("Built " + resolve(output, "modlens-agent.jar"));
|
|
61
|
+
} finally {
|
|
62
|
+
if (dirname(resolve(build)) !== resolve(scratch)) throw new Error("Unexpected build cleanup path");
|
|
63
|
+
await rm(build, { recursive: true, force: true });
|
|
64
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"name": "ModMenu",
|
|
4
|
+
"version": "v6Xx3fbU",
|
|
5
|
+
"release": "11.0.4",
|
|
6
|
+
"hash": "a88e0d67c82b0434cbf56776c482587c275ed660f635d33f53ba485dfa30dcc3",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"expected": "hosted_allowed",
|
|
9
|
+
"target": "com.terraformersmc.modmenu.ModMenu"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"name": "FerriteCore",
|
|
13
|
+
"version": "sOzRw3CG",
|
|
14
|
+
"release": "7.0.3-fabric",
|
|
15
|
+
"hash": "98c3ab1d5aab8f14b5d082d3f1a467727fd335f02d124555c94339761c4c18f2",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"expected": "hosted_allowed",
|
|
18
|
+
"target": "malte0811.ferritecore.IPlatformHooks"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "MouseTweaks",
|
|
22
|
+
"version": "9I21YYxf",
|
|
23
|
+
"release": "1.21-2.26.1-neoforge",
|
|
24
|
+
"hash": "68e6f4201c5de97b77929a7215c9552495696ca6a3bf3ae4eacc34e135f6cc8b",
|
|
25
|
+
"license": "BSD-3-Clause",
|
|
26
|
+
"expected": "hosted_allowed",
|
|
27
|
+
"target": "yalter.mousetweaks.Constants"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "DynamicFPS",
|
|
31
|
+
"version": "LqevwKp5",
|
|
32
|
+
"release": "3.11.4",
|
|
33
|
+
"hash": "0929d2b9e164a08d25a7ba8a5f0750081b66dea9171cec52d2f0fdc792fc7e5e",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"expected": "hosted_allowed",
|
|
36
|
+
"target": "architectury_inject_dynamicfps_common_96ad272d90fa4fcc8d67753a3449cf6d_48138490a95625983251b45ac7336d8a7e463234a1fad5dfdc4c144b1c5f439adynamicfps3114devjar.PlatformMethods"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"name": "Controlling",
|
|
40
|
+
"version": "FaNppCJJ",
|
|
41
|
+
"release": "19.0.5",
|
|
42
|
+
"hash": "cc525fb6b030d9e4d33176983f278919e17b33935e2ca568929bdfa98f2b44a0",
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"expected": "hosted_allowed",
|
|
45
|
+
"target": "com.blamejared.controlling.Controlling"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"name": "Clumps",
|
|
49
|
+
"version": "jo7lDoK4",
|
|
50
|
+
"release": "19.0.0.1",
|
|
51
|
+
"hash": "b524ccdace2ef8fd19f5b2074f7de1103ac5065c52553f064c00e098346c293e",
|
|
52
|
+
"license": "MIT",
|
|
53
|
+
"expected": "hosted_allowed",
|
|
54
|
+
"target": "com.blamejared.clumps.helper.IClumpedOrb"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"name": "AppleSkin",
|
|
58
|
+
"version": "uAKA6Laj",
|
|
59
|
+
"release": "3.0.9+mc1.21",
|
|
60
|
+
"hash": "38b48dd6231341c9f964ce6e42c57ec866c6cc8f72bec938390a59bafb3922df",
|
|
61
|
+
"license": "Unlicense",
|
|
62
|
+
"expected": "hosted_allowed",
|
|
63
|
+
"target": "squeek.appleskin.ModInfo"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"name": "Architectury",
|
|
67
|
+
"version": "1IiqEQGl",
|
|
68
|
+
"release": "13.0.11+neoforge",
|
|
69
|
+
"hash": "9cc92f2c09533fc5482c60f993bd891c655a0e32b637037cdcb7c2b23adedeeb",
|
|
70
|
+
"license": "LGPL-3.0-only",
|
|
71
|
+
"expected": "local_only",
|
|
72
|
+
"target": "architectury_inject_architectury_common_d9fb27da44a148dfa43566d20897d6de_f4ed98e062c0e85c5cc998c6fbbab16b146fa1f727cd0f469b44c896e896070earchitectury13011devjar.PlatformMethods"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"name": "ClothConfig",
|
|
76
|
+
"version": "izKINKFg",
|
|
77
|
+
"release": "15.0.140+neoforge",
|
|
78
|
+
"hash": "65e722e0d98431a07c45f8bdd8d529a217cc8c175fde1740248bd5c1b4f3c0d4",
|
|
79
|
+
"license": "LGPL-3.0-only",
|
|
80
|
+
"expected": "local_only",
|
|
81
|
+
"target": "architectury_inject_clothconfig_common_d551de04735c42148ec07a78c43007d1_c487a9cf12e7d5bea169da255b814f03602bdeb8b51cff67162252c24f285789clothconfig150140devjar.PlatformMethods"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"name": "ModernFix",
|
|
85
|
+
"version": "5HLHxQ2F",
|
|
86
|
+
"release": "5.27.24+mc1.21.1",
|
|
87
|
+
"hash": "e6e9446890f0feb3aab3f6e73ae18cb17575c370f232179fef7baa30e61538fe",
|
|
88
|
+
"license": "LGPL-3.0-only",
|
|
89
|
+
"expected": "local_only",
|
|
90
|
+
"target": "org.embeddedt.modernfix.ModernFix"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"name": "Spark",
|
|
94
|
+
"version": "v5qtqRQi",
|
|
95
|
+
"release": "1.10.124-neoforge-1.21.1",
|
|
96
|
+
"hash": "647e8a81afbe414dba1df4ba15fd06c5d32d4cb544e68828405e8e074c2e16db",
|
|
97
|
+
"license": "GPL-3.0-only",
|
|
98
|
+
"expected": "local_only",
|
|
99
|
+
"target": "me.lucko.spark.neoforge.NeoForgePlatformInfo"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"name": "Sodium",
|
|
103
|
+
"version": "uMOpc5uV",
|
|
104
|
+
"release": "mc1.21.1-0.8.13-neoforge",
|
|
105
|
+
"hash": "575d187b15328d7bddd8a9b1bee64333c22c14735f5780ec49eac0cd5503c08d",
|
|
106
|
+
"license": "LicenseRef-Polyform-Shield-1.0.0",
|
|
107
|
+
"expected": "local_only",
|
|
108
|
+
"target": "net.caffeinemc.mods.sodium.service.SodiumServiceModLocator"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"name": "Iris",
|
|
112
|
+
"version": "t3ruzodq",
|
|
113
|
+
"release": "1.8.12+1.21.1-neoforge",
|
|
114
|
+
"hash": "3e3f94d2d5cdcb8145c4385d521974aa2d8669b2cc212351f70cf06488182e83",
|
|
115
|
+
"license": "LGPL-3.0-only",
|
|
116
|
+
"expected": "local_only",
|
|
117
|
+
"target": "de.odysseus.ithaka.digraph.Digraph"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"name": "Lithium",
|
|
121
|
+
"version": "N08Z8wog",
|
|
122
|
+
"release": "mc1.21.1-0.15.4-fabric",
|
|
123
|
+
"hash": "92329d98c57f5a22091a0adb509fdb1d60113a5602c708b799b13134bad13baa",
|
|
124
|
+
"license": "LGPL-3.0-only",
|
|
125
|
+
"expected": "local_only",
|
|
126
|
+
"target": "net.caffeinemc.mods.lithium.api.inventory.LithiumCooldownReceivingInventory"
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"name": "EntityCulling",
|
|
130
|
+
"version": "8w8FfUPs",
|
|
131
|
+
"release": "1.11.2",
|
|
132
|
+
"hash": "3983b981da4a0583c7e58184a8631a23e56cc3b81f4cb53491ca600382879b82",
|
|
133
|
+
"license": "LicenseRef-tr7zw-Protective-License",
|
|
134
|
+
"expected": "local_only",
|
|
135
|
+
"target": "com.logisticscraft.occlusionculling.DataProvider"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"name": "JourneyMap",
|
|
139
|
+
"version": "CtFE7hwT",
|
|
140
|
+
"release": "1.21.1-6.0.9+neoforge",
|
|
141
|
+
"hash": "9bd7734542233d5281750572f17a9258449e826650c081e9a67dc426262c2fde",
|
|
142
|
+
"license": "LicenseRef-All-Rights-Reserved",
|
|
143
|
+
"expected": "local_only",
|
|
144
|
+
"target": "earcut4j.Earcut"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"name": "FabricAPI",
|
|
148
|
+
"version": "Mys3P7lK",
|
|
149
|
+
"release": "0.116.17+1.21.1",
|
|
150
|
+
"hash": "79ac44b40780acbd884b34c50be1e39af682847e5f5cb3b1fddeeaa768dce800",
|
|
151
|
+
"license": "Apache-2.0",
|
|
152
|
+
"expected": "hosted_allowed"
|
|
153
|
+
}
|
|
154
|
+
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Opt-in launch only. Never instrument Gradle itself or every JavaExec task.
|
|
2
|
+
gradle.projectsEvaluated {
|
|
3
|
+
def configFile = new File(gradle.rootProject.projectDir, '.modlens/runtime/launch.properties')
|
|
4
|
+
if (!configFile.isFile()) throw new GradleException('Call ModLens runtime.setup for this project first')
|
|
5
|
+
def config = new Properties()
|
|
6
|
+
configFile.withInputStream { config.load(it) }
|
|
7
|
+
def taskPath = config.getProperty('task')
|
|
8
|
+
if (!taskPath.startsWith(':')) taskPath = ':' + taskPath
|
|
9
|
+
def target = gradle.rootProject.tasks.findByPath(taskPath)
|
|
10
|
+
if (!(target instanceof JavaExec)) {
|
|
11
|
+
throw new GradleException("ModLens requires a JavaExec dev run task; ${taskPath} is ${target?.class?.name ?: 'missing'}. Use every returned vmOptions entry on the actual game launch instead.")
|
|
12
|
+
}
|
|
13
|
+
target.jvmArgs("-javaagent:${config.getProperty('agent')}=${config.getProperty('connection')}")
|
|
14
|
+
// Minecraft 26.3 client startup needs this even without the agent.
|
|
15
|
+
target.jvmArgs('-XX:StackShadowPages=32')
|
|
16
|
+
target.jvmArgs("-XX:ErrorFile=${new File(configFile.parentFile, 'hs_err_pid%p.log').absolutePath}")
|
|
17
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Test adapter: run the same fixture through separate CLI processes instead of
|
|
2
|
+
// importing RuntimeHub. Each call reconnects to the persistent companion.
|
|
3
|
+
import { writeFile } from "node:fs/promises";
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { randomUUID } from "node:crypto";
|
|
8
|
+
|
|
9
|
+
export function runtimeTestClient(root) {
|
|
10
|
+
const launcher = fileURLToPath(new URL("../dist/launcher.js", import.meta.url));
|
|
11
|
+
const env = { ...process.env, MODLENS_CACHE_ROOT: join(root, "helper-cache") };
|
|
12
|
+
const run = (args) =>
|
|
13
|
+
JSON.parse(
|
|
14
|
+
execFileSync(process.execPath, [launcher, "--runtime", ...args], {
|
|
15
|
+
env,
|
|
16
|
+
encoding: "utf8",
|
|
17
|
+
timeout: 45000,
|
|
18
|
+
}),
|
|
19
|
+
);
|
|
20
|
+
async function request(body) {
|
|
21
|
+
const file = join(root, `helper-${randomUUID()}.json`);
|
|
22
|
+
await writeFile(file, JSON.stringify(body));
|
|
23
|
+
return run(["--request-file", file]);
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
setup: (projectDir, mode, gradleTask, minecraftHooks) =>
|
|
27
|
+
request({ action: "setup", projectDir, mode, gradleTask, minecraftHooks }),
|
|
28
|
+
list: () => request({ action: "sessions" }),
|
|
29
|
+
events: (afterCursor, waitMs, sessionId) =>
|
|
30
|
+
request({ action: "events", afterCursor, waitMs, sessionId }),
|
|
31
|
+
command: (sessionId, command) => request({ action: "command", sessionId, command }),
|
|
32
|
+
artifact: (sessionId, artifactName) => request({ action: "artifact", sessionId, artifactName }),
|
|
33
|
+
launch: (projectId, javaHome) => request({ action: "launch", projectId, javaHome }),
|
|
34
|
+
close: async () => run(["stop"]),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// Real transports, synthetic source only: no Minecraft download or decompilation.
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { randomBytes } from "node:crypto";
|
|
5
|
+
import { mkdtemp, mkdir, writeFile, rm } from "node:fs/promises";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { createServer } from "node:net";
|
|
10
|
+
import { once } from "node:events";
|
|
11
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
12
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
13
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
14
|
+
|
|
15
|
+
const root = await mkdtemp(join(tmpdir(), "modlens-hosted-test-"));
|
|
16
|
+
const entry = fileURLToPath(new URL("../dist/server.js", import.meta.url));
|
|
17
|
+
const listener = createServer();
|
|
18
|
+
listener.listen(0, "127.0.0.1");
|
|
19
|
+
await once(listener, "listening");
|
|
20
|
+
const port = listener.address().port;
|
|
21
|
+
await new Promise(resolve => listener.close(resolve));
|
|
22
|
+
const endpoint = new URL(`http://127.0.0.1:${port}/mcp`);
|
|
23
|
+
const secret = randomBytes(32).toString("hex");
|
|
24
|
+
const env = { ...process.env, MCP_PORT: String(port), MCP_HOST: "127.0.0.1", MODLENS_HOME: root,
|
|
25
|
+
MODLENS_CACHE_ROOT: join(root, "cache"), DATABASE_URL: `file:${join(root, "usage.db")}`,
|
|
26
|
+
MODLENS_HOSTED_LIMITS: "1", MODLENS_HOSTED_PROXY_SECRET: secret, MODLENS_HOSTED_SOURCE_LINES: "200",
|
|
27
|
+
MODLENS_HOSTED_DAILY_BYTES: "15000", MODLENS_HOSTED_PERIOD_BYTES: "100000",
|
|
28
|
+
MODLENS_HOSTED_DAILY_REQUESTS: "1000", MODLENS_HOSTED_MINUTE_REQUESTS: "120",
|
|
29
|
+
MODLENS_HOSTED_RESPONSE_BYTES: "131072", MODLENS_AUTO_EMBED: "0", MODLENS_AUTO_GRAPH: "0" };
|
|
30
|
+
const source = Array.from({ length: 600 }, (_, i) => `// Synthetic fixture line ${i + 1}`).join("\n");
|
|
31
|
+
const fixtureDir = join(env.MODLENS_CACHE_ROOT, "mc-decompiled-named", "1.21.1", "example");
|
|
32
|
+
await mkdir(fixtureDir, { recursive: true });
|
|
33
|
+
await writeFile(join(fixtureDir, "Fixture.java"), source);
|
|
34
|
+
const headers = user => ({ "x-modlens-proxy-secret": secret, "x-modlens-user-id": user });
|
|
35
|
+
let server, exited;
|
|
36
|
+
const clients = [];
|
|
37
|
+
async function start() {
|
|
38
|
+
let logs = "";
|
|
39
|
+
server = spawn(process.execPath, [entry], { cwd: root, env, stdio: ["ignore", "ignore", "pipe"], windowsHide: true });
|
|
40
|
+
exited = once(server, "exit");
|
|
41
|
+
server.stderr.on("data", data => { logs += data; });
|
|
42
|
+
await new Promise((resolve, reject) => {
|
|
43
|
+
const timer = setTimeout(() => reject(new Error(`Startup timed out: ${logs}`)), 15000);
|
|
44
|
+
const ready = () => {
|
|
45
|
+
if (logs.includes("listening on")) { clearTimeout(timer); server.stderr.off("data", ready); resolve(); }
|
|
46
|
+
};
|
|
47
|
+
server.stderr.on("data", ready);
|
|
48
|
+
server.once("error", reject);
|
|
49
|
+
server.once("exit", () => { clearTimeout(timer); reject(new Error(`Server exited: ${logs}`)); });
|
|
50
|
+
ready();
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
async function connect(user) {
|
|
54
|
+
const client = new Client({ name: "hosted-policy-test", version: "1" });
|
|
55
|
+
clients.push(client);
|
|
56
|
+
const transport = new StreamableHTTPClientTransport(endpoint, { requestInit: { headers: headers(user) } });
|
|
57
|
+
await client.connect(transport);
|
|
58
|
+
return { client, transport };
|
|
59
|
+
}
|
|
60
|
+
const read = (client, startLine = 1) => client.callTool({ name: "mc_source", arguments: {
|
|
61
|
+
action: "get_source", version: "1.21.1", className: "example.Fixture", startLine, maxLines: 10000,
|
|
62
|
+
} });
|
|
63
|
+
let passed = false;
|
|
64
|
+
try {
|
|
65
|
+
await start();
|
|
66
|
+
const unauthorized = await fetch(endpoint, { method: "POST", headers: { "x-modlens-user-id": "spoof" }, body: "{}" });
|
|
67
|
+
assert.equal(unauthorized.status, 401);
|
|
68
|
+
const { client, transport } = await connect("developer-a");
|
|
69
|
+
const listed = await client.listTools();
|
|
70
|
+
assert.ok(listed.tools.some(t => t.name === "report_issue"), "Hosted users must discover issue reporting");
|
|
71
|
+
const reporter = await connect("issue-reporter");
|
|
72
|
+
const preparedIssue = await reporter.client.callTool({ name: "report_issue", arguments: {
|
|
73
|
+
action: "prepare", title: "Hosted test fixture", summary: "Synthetic report; never submit.",
|
|
74
|
+
} });
|
|
75
|
+
assert.ok(!preparedIssue.isError, JSON.stringify(preparedIssue));
|
|
76
|
+
const issuePlan = JSON.parse(preparedIssue.content[0].text);
|
|
77
|
+
assert.equal(issuePlan.state, "draft_prepared");
|
|
78
|
+
assert.equal(issuePlan.executed, false);
|
|
79
|
+
assert.equal(issuePlan.repository.url, "https://github.com/CreeperHost/modlens-mcp");
|
|
80
|
+
const mc = listed.tools.find(t => t.name === "mc_source");
|
|
81
|
+
assert.ok(mc.inputSchema.properties.action.enum.includes("bytecode"));
|
|
82
|
+
for (const action of ["decompile", "get_paths", "index", "decompile_status"]) {
|
|
83
|
+
assert.ok(!mc.inputSchema.properties.action.enum.includes(action), action);
|
|
84
|
+
}
|
|
85
|
+
assert.ok(listed.tools.some(t => t.name === "mc_data"));
|
|
86
|
+
assert.ok(listed.tools.find(t => t.name === "mod_bytecode").inputSchema.properties.startLine);
|
|
87
|
+
const denied = await client.callTool({ name: "mc_source", arguments: { action: "get_paths", version: "1.21.1" } });
|
|
88
|
+
assert.ok(denied.isError);
|
|
89
|
+
const wrongOwner = await fetch(endpoint, { headers: { ...headers("developer-b"), "mcp-session-id": transport.sessionId, accept: "text/event-stream" } });
|
|
90
|
+
assert.equal(wrongOwner.status, 404);
|
|
91
|
+
const first = await read(client);
|
|
92
|
+
assert.ok(!first.isError, JSON.stringify(first));
|
|
93
|
+
assert.equal(first.content[0].text, source.split("\n").slice(0, 200).join("\n"));
|
|
94
|
+
const second = await read(client, 201);
|
|
95
|
+
assert.ok(!second.isError, JSON.stringify(second));
|
|
96
|
+
assert.equal(second.content[0].text, source.split("\n").slice(200, 400).join("\n"));
|
|
97
|
+
const exhausted = await read(client, 401);
|
|
98
|
+
assert.ok(exhausted.isError);
|
|
99
|
+
assert.ok(!JSON.stringify(exhausted).includes("Synthetic fixture"));
|
|
100
|
+
const reconnected = await connect("developer-a");
|
|
101
|
+
assert.ok((await read(reconnected.client)).isError, "reconnect must retain quota");
|
|
102
|
+
for (const c of clients.splice(0)) await c.close();
|
|
103
|
+
server.kill(); await exited;
|
|
104
|
+
await start();
|
|
105
|
+
const restarted = await connect("developer-a");
|
|
106
|
+
assert.ok((await read(restarted.client)).isError, "restart must retain quota");
|
|
107
|
+
const different = await connect("developer-b");
|
|
108
|
+
assert.ok(!(await read(different.client)).isError, "separate verified accounts have separate allowances");
|
|
109
|
+
const local = new Client({ name: "hosted-local-test", version: "1" });
|
|
110
|
+
clients.push(local);
|
|
111
|
+
await local.connect(new StdioClientTransport({ command: process.execPath, args: [entry], cwd: root,
|
|
112
|
+
env: { ...env, MCP_PORT: "" }, stderr: "pipe" }));
|
|
113
|
+
const localResult = await read(local);
|
|
114
|
+
assert.ok(!localResult.isError, JSON.stringify(localResult));
|
|
115
|
+
assert.equal(localResult.content[0].text, source, "local source access remains unrestricted");
|
|
116
|
+
passed = true;
|
|
117
|
+
console.log("PASS: hosted discovery, authentication, account/session binding, source paging, cumulative release limits, reconnect/restart persistence and unrestricted local access");
|
|
118
|
+
} finally {
|
|
119
|
+
for (const client of clients) await client.close().catch(() => {});
|
|
120
|
+
if (server && server.exitCode === null) { server.kill(); await exited; }
|
|
121
|
+
if (passed) await rm(root, { recursive: true, force: true });
|
|
122
|
+
else console.error(`Fixture retained for debugging: ${root}`);
|
|
123
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Opt-in regression against real, immutable release JARs and live evidence APIs.
|
|
2
|
+
// Downloads test artifacts only; no credentials, ingestion or bulk source export.
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
6
|
+
import { join, resolve } from 'node:path';
|
|
7
|
+
import { spawn } from 'node:child_process';
|
|
8
|
+
import { once } from 'node:events';
|
|
9
|
+
import AdmZip from 'adm-zip';
|
|
10
|
+
import { inspectModLicense } from '../dist/mod-license.js';
|
|
11
|
+
|
|
12
|
+
const root = resolve('.scratch/license-live');
|
|
13
|
+
await mkdir(root, { recursive: true });
|
|
14
|
+
const cases = [
|
|
15
|
+
{ name: 'JEI', version: 'JhEb3Vl1', hash: 'a9418a15032d1653b4776763f2df7c54544a1c2314fe1c4269ba60d9bb51a204', license: 'MIT', target: 'mezz.jei.common.Constants' },
|
|
16
|
+
{ name: 'Jade', version: 'yd8FKCmx', hash: '067bb4b007e1d6f6b79f0afe99c91252aa825472b99a76d33a60d24442f9e92d', license: 'CC-BY-NC-SA-4.0', target: 'snownee.jade.Jade' },
|
|
17
|
+
{ name: 'Embeddium', version: 'UTbfe5d1', hash: 'eed3d1325f2acc2fd4e69bb495e5ccb91d962126ac5330f0582ebc2a3daf47fb', license: 'LGPL-3.0-only', target: 'org.embeddedt.embeddium.api.EmbeddiumConstants' },
|
|
18
|
+
];
|
|
19
|
+
if (process.argv.includes('--corpus')) {
|
|
20
|
+
cases.push(...JSON.parse(await readFile(new URL('./fixtures/mod-license-corpus.json', import.meta.url), 'utf8')));
|
|
21
|
+
}
|
|
22
|
+
const outcomes = [];
|
|
23
|
+
const digest = data => createHash('sha256').update(data).digest('hex');
|
|
24
|
+
for (const item of cases) {
|
|
25
|
+
const outcome = { name: item.name, version: item.version, expected: item.expected ?? 'hosted_allowed' };
|
|
26
|
+
outcomes.push(outcome);
|
|
27
|
+
try {
|
|
28
|
+
const jar = join(root, `${item.name}.jar`);
|
|
29
|
+
let data = await readFile(jar).catch(() => null);
|
|
30
|
+
if (!data || digest(data) !== item.hash) {
|
|
31
|
+
const response = await fetch(`https://api.modrinth.com/v2/version/${item.version}`, { headers: { 'User-Agent': 'modlens-mcp/license-regression' } });
|
|
32
|
+
assert.equal(response.status, 200);
|
|
33
|
+
const version = await response.json();
|
|
34
|
+
const url = new URL(version.files.find(f => f.primary)?.url ?? version.files[0].url);
|
|
35
|
+
assert.equal(url.origin, 'https://cdn.modrinth.com');
|
|
36
|
+
const download = await fetch(url, { redirect: 'error' });
|
|
37
|
+
assert.equal(download.status, 200);
|
|
38
|
+
data = Buffer.from(await download.arrayBuffer());
|
|
39
|
+
assert.equal(digest(data), item.hash, `${item.name} exact artifact`);
|
|
40
|
+
await writeFile(jar, data);
|
|
41
|
+
}
|
|
42
|
+
const decision = await inspectModLicense(jar);
|
|
43
|
+
await writeFile(join(root, `${item.name}.decision.json`), JSON.stringify(decision, null, 2));
|
|
44
|
+
Object.assign(outcome, { actual: decision.disposition, license: decision.selectedLicense, origin: decision.selectedOrigin, reason: decision.reason, notices: decision.notices.length });
|
|
45
|
+
assert.equal(decision.disposition, outcome.expected, `${item.name}: ${decision.reason}`);
|
|
46
|
+
if (decision.disposition === 'hosted_allowed') {
|
|
47
|
+
assert.equal(decision.selectedLicense, item.license);
|
|
48
|
+
assert.ok(decision.notices.length);
|
|
49
|
+
}
|
|
50
|
+
if (item.name === 'Jade') assert.equal(decision.conditions.noncommercialOnly, true);
|
|
51
|
+
if (item.name === 'Embeddium') {
|
|
52
|
+
assert.ok(decision.conditions.sourceArchive?.includes('87d1a75'));
|
|
53
|
+
assert.ok(decision.notices.some(n => n.name.includes('COPYING.LESSER')));
|
|
54
|
+
assert.ok(decision.notices.some(n => n.name.startsWith('dependency/')));
|
|
55
|
+
const response = await fetch(decision.conditions.sourceArchive, { method: 'HEAD' });
|
|
56
|
+
assert.equal(response.status, 200, 'complete corresponding-source archive is available');
|
|
57
|
+
}
|
|
58
|
+
if (process.argv.includes('--http') && decision.disposition === 'hosted_allowed' && item.target) {
|
|
59
|
+
const bundle = new AdmZip();
|
|
60
|
+
bundle.addFile('artifacts/0.jar', data);
|
|
61
|
+
bundle.addFile('manifest.json', Buffer.from(JSON.stringify({ format: 'modlens-project-v1', project: `license-${item.name}:`, sourceSet: 'main',
|
|
62
|
+
toolchain: 'gradle-compile-classpath', minecraftVersion: '', loaderVersion: '', mappings: 'compile-classpath', javaVersion: 21, transformations: [],
|
|
63
|
+
artifacts: [{ path: 'artifacts/0.jar', name: `${item.name}.jar`, size: data.length, sha256: item.hash, kind: 'classpath' }] })));
|
|
64
|
+
const bundlePath = join(root, `${item.name}.zip`), keyPath = join(root, `${item.name}.key`);
|
|
65
|
+
await writeFile(bundlePath, bundle.toBuffer());
|
|
66
|
+
await writeFile(keyPath, createHash('sha256').update(`disposable-test-${item.hash}`).digest('hex'));
|
|
67
|
+
const child = spawn(process.execPath, ['scripts/test-project-http.mjs', bundlePath, keyPath, item.target], { stdio: 'inherit',
|
|
68
|
+
env: { ...process.env, MODLENS_TEST_EXPECTED_LICENSE: item.license } });
|
|
69
|
+
const [code] = await once(child, 'exit');
|
|
70
|
+
assert.equal(code, 0, `${item.name} real HTTP hosted source path`);
|
|
71
|
+
outcome.http = 'passed';
|
|
72
|
+
} else if (process.argv.includes('--http')) {
|
|
73
|
+
outcome.http = decision.disposition === 'hosted_allowed' ? 'not run: no root class target' : 'not run: local-only decision';
|
|
74
|
+
}
|
|
75
|
+
outcome.passed = true;
|
|
76
|
+
console.log(`PASS: ${item.name}: ${decision.disposition}, ${decision.selectedLicense ?? 'no selected licence'} from ${decision.selectedOrigin ?? 'no selected origin'}, ${decision.notices.length} complete notices`);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
outcome.passed = false;
|
|
79
|
+
outcome.error = error instanceof Error ? error.message : String(error);
|
|
80
|
+
console.error(`FAIL: ${item.name}: ${outcome.error}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const summary = { checkedAt: new Date().toISOString(), total: outcomes.length, passed: outcomes.filter(o => o.passed).length, failed: outcomes.filter(o => !o.passed).length, outcomes };
|
|
84
|
+
await writeFile(join(root, process.argv.includes('--corpus') ? 'corpus-results.json' : 'baseline-results.json'), JSON.stringify(summary, null, 2));
|
|
85
|
+
console.log(`Licence corpus: ${summary.passed}/${summary.total} passed; ${summary.failed} failed.`);
|
|
86
|
+
if (summary.failed) process.exitCode = 1;
|