@kzheart_/mc-pilot 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/bin/mct +2 -0
  2. package/data/variants.json +139 -0
  3. package/dist/client/ClientManager.d.ts +82 -0
  4. package/dist/client/ClientManager.js +213 -0
  5. package/dist/client/WebSocketClient.d.ts +15 -0
  6. package/dist/client/WebSocketClient.js +76 -0
  7. package/dist/commands/block.d.ts +2 -0
  8. package/dist/commands/block.js +52 -0
  9. package/dist/commands/book.d.ts +2 -0
  10. package/dist/commands/book.js +21 -0
  11. package/dist/commands/channel.d.ts +2 -0
  12. package/dist/commands/channel.js +24 -0
  13. package/dist/commands/chat.d.ts +2 -0
  14. package/dist/commands/chat.js +31 -0
  15. package/dist/commands/client.d.ts +2 -0
  16. package/dist/commands/client.js +87 -0
  17. package/dist/commands/combat.d.ts +2 -0
  18. package/dist/commands/combat.js +46 -0
  19. package/dist/commands/craft.d.ts +5 -0
  20. package/dist/commands/craft.js +45 -0
  21. package/dist/commands/effects.d.ts +2 -0
  22. package/dist/commands/effects.js +16 -0
  23. package/dist/commands/entity.d.ts +2 -0
  24. package/dist/commands/entity.js +53 -0
  25. package/dist/commands/gui.d.ts +2 -0
  26. package/dist/commands/gui.js +49 -0
  27. package/dist/commands/hud.d.ts +2 -0
  28. package/dist/commands/hud.js +16 -0
  29. package/dist/commands/input.d.ts +2 -0
  30. package/dist/commands/input.js +124 -0
  31. package/dist/commands/inventory.d.ts +2 -0
  32. package/dist/commands/inventory.js +28 -0
  33. package/dist/commands/look.d.ts +2 -0
  34. package/dist/commands/look.js +37 -0
  35. package/dist/commands/move.d.ts +2 -0
  36. package/dist/commands/move.js +50 -0
  37. package/dist/commands/position.d.ts +2 -0
  38. package/dist/commands/position.js +7 -0
  39. package/dist/commands/request-helpers.d.ts +26 -0
  40. package/dist/commands/request-helpers.js +58 -0
  41. package/dist/commands/resourcepack.d.ts +2 -0
  42. package/dist/commands/resourcepack.js +9 -0
  43. package/dist/commands/rotation.d.ts +2 -0
  44. package/dist/commands/rotation.js +7 -0
  45. package/dist/commands/screen.d.ts +2 -0
  46. package/dist/commands/screen.js +7 -0
  47. package/dist/commands/screenshot.d.ts +2 -0
  48. package/dist/commands/screenshot.js +14 -0
  49. package/dist/commands/server.d.ts +2 -0
  50. package/dist/commands/server.js +66 -0
  51. package/dist/commands/sign.d.ts +2 -0
  52. package/dist/commands/sign.js +30 -0
  53. package/dist/commands/status.d.ts +2 -0
  54. package/dist/commands/status.js +17 -0
  55. package/dist/commands/wait.d.ts +2 -0
  56. package/dist/commands/wait.js +23 -0
  57. package/dist/download/CacheManager.d.ts +20 -0
  58. package/dist/download/CacheManager.js +50 -0
  59. package/dist/download/DownloadUtils.d.ts +2 -0
  60. package/dist/download/DownloadUtils.js +23 -0
  61. package/dist/download/JavaDetector.d.ts +7 -0
  62. package/dist/download/JavaDetector.js +31 -0
  63. package/dist/download/ModVariantCatalog.d.ts +10 -0
  64. package/dist/download/ModVariantCatalog.js +52 -0
  65. package/dist/download/SearchCommand.d.ts +29 -0
  66. package/dist/download/SearchCommand.js +37 -0
  67. package/dist/download/VersionMatrix.d.ts +72 -0
  68. package/dist/download/VersionMatrix.js +227 -0
  69. package/dist/download/client/Arm64LwjglPatcher.d.ts +5 -0
  70. package/dist/download/client/Arm64LwjglPatcher.js +153 -0
  71. package/dist/download/client/ClientDownloader.d.ts +42 -0
  72. package/dist/download/client/ClientDownloader.js +233 -0
  73. package/dist/download/client/FabricRuntimeDownloader.d.ts +10 -0
  74. package/dist/download/client/FabricRuntimeDownloader.js +91 -0
  75. package/dist/download/server/ServerDownloader.d.ts +51 -0
  76. package/dist/download/server/ServerDownloader.js +196 -0
  77. package/dist/download/types.d.ts +37 -0
  78. package/dist/download/types.js +1 -0
  79. package/dist/index.d.ts +2 -0
  80. package/dist/index.js +89 -0
  81. package/dist/server/ServerManager.d.ts +63 -0
  82. package/dist/server/ServerManager.js +114 -0
  83. package/dist/util/command.d.ts +10 -0
  84. package/dist/util/command.js +35 -0
  85. package/dist/util/config.d.ts +30 -0
  86. package/dist/util/config.js +59 -0
  87. package/dist/util/context.d.ts +17 -0
  88. package/dist/util/context.js +16 -0
  89. package/dist/util/errors.d.ts +20 -0
  90. package/dist/util/errors.js +37 -0
  91. package/dist/util/net.d.ts +5 -0
  92. package/dist/util/net.js +35 -0
  93. package/dist/util/output.d.ts +4 -0
  94. package/dist/util/output.js +23 -0
  95. package/dist/util/process.d.ts +3 -0
  96. package/dist/util/process.js +32 -0
  97. package/dist/util/state.d.ts +10 -0
  98. package/dist/util/state.js +40 -0
  99. package/package.json +54 -0
@@ -0,0 +1,26 @@
1
+ import { Command } from "commander";
2
+ import type { CommandContext, GlobalOptions } from "../util/context.js";
3
+ export interface RequestPayload<TOptions> {
4
+ args: string[];
5
+ options: TOptions;
6
+ globalOptions: GlobalOptions;
7
+ }
8
+ export declare function sendClientRequest(context: CommandContext, clientName: string | undefined, action: string, params: Record<string, unknown>, timeoutSeconds?: number): Promise<unknown>;
9
+ export declare function createRequestAction<TOptions = Record<string, any>>(action: string, buildParams: (payload: RequestPayload<TOptions>) => Record<string, unknown>, timeoutSelector?: (payload: RequestPayload<TOptions>, context: CommandContext) => number | undefined): (this: Command, ...input: unknown[]) => Promise<void>;
10
+ export declare function parseJson(text: string, fieldName: string): Record<string, unknown>;
11
+ export declare function parseNumberList(text: string): number[];
12
+ export declare function withTransportTimeoutBuffer(requestedTimeout: number | undefined, fallbackTimeout: number): number;
13
+ export declare function buildEntityFilter(options: {
14
+ type?: string;
15
+ name?: string;
16
+ nearest?: boolean;
17
+ id?: number;
18
+ maxDistance?: number;
19
+ }): {
20
+ type: string | undefined;
21
+ name: string | undefined;
22
+ nearest: boolean | undefined;
23
+ id: number | undefined;
24
+ maxDistance: number | undefined;
25
+ };
26
+ export declare function command(name: string, description: string): Command;
@@ -0,0 +1,58 @@
1
+ import { Command } from "commander";
2
+ import { ClientManager } from "../client/ClientManager.js";
3
+ import { WebSocketClient } from "../client/WebSocketClient.js";
4
+ import { MctError } from "../util/errors.js";
5
+ import { wrapCommand } from "../util/command.js";
6
+ export async function sendClientRequest(context, clientName, action, params, timeoutSeconds) {
7
+ const manager = new ClientManager(context);
8
+ const client = await manager.getClient(clientName);
9
+ const ws = new WebSocketClient(manager.getWsUrl(client.wsPort));
10
+ return ws.send(action, params, timeoutSeconds ?? context.config.timeout.default);
11
+ }
12
+ export function createRequestAction(action, buildParams, timeoutSelector) {
13
+ return wrapCommand(async (context, payload) => {
14
+ const timeout = timeoutSelector?.(payload, context);
15
+ return sendClientRequest(context, payload.globalOptions.client, action, buildParams(payload), timeout);
16
+ });
17
+ }
18
+ export function parseJson(text, fieldName) {
19
+ try {
20
+ return JSON.parse(text);
21
+ }
22
+ catch {
23
+ throw new MctError({
24
+ code: "INVALID_PARAMS",
25
+ message: `${fieldName} must be valid JSON`
26
+ }, 4);
27
+ }
28
+ }
29
+ export function parseNumberList(text) {
30
+ return text
31
+ .split(",")
32
+ .map((value) => value.trim())
33
+ .filter(Boolean)
34
+ .map((value) => Number(value));
35
+ }
36
+ export function withTransportTimeoutBuffer(requestedTimeout, fallbackTimeout) {
37
+ const effectiveTimeout = requestedTimeout ?? fallbackTimeout;
38
+ return Math.max(effectiveTimeout + 10, fallbackTimeout);
39
+ }
40
+ export function buildEntityFilter(options) {
41
+ const filter = {
42
+ type: options.type,
43
+ name: options.name,
44
+ nearest: options.nearest,
45
+ id: options.id,
46
+ maxDistance: options.maxDistance
47
+ };
48
+ if (!filter.type && !filter.name && !filter.nearest && !filter.id && !filter.maxDistance) {
49
+ throw new MctError({
50
+ code: "INVALID_PARAMS",
51
+ message: "At least one entity filter option is required"
52
+ }, 4);
53
+ }
54
+ return filter;
55
+ }
56
+ export function command(name, description) {
57
+ return new Command(name).description(description);
58
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function createResourcepackCommand(): Command;
@@ -0,0 +1,9 @@
1
+ import { Command } from "commander";
2
+ import { createRequestAction } from "./request-helpers.js";
3
+ export function createResourcepackCommand() {
4
+ const command = new Command("resourcepack").description("Resource pack operations");
5
+ command.command("status").description("Get resource pack status").action(createRequestAction("resourcepack.status", () => ({})));
6
+ command.command("accept").description("Accept the pending resource pack").action(createRequestAction("resourcepack.accept", () => ({})));
7
+ command.command("reject").description("Reject the pending resource pack").action(createRequestAction("resourcepack.reject", () => ({})));
8
+ return command;
9
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function createRotationCommand(): Command;
@@ -0,0 +1,7 @@
1
+ import { Command } from "commander";
2
+ import { createRequestAction } from "./request-helpers.js";
3
+ export function createRotationCommand() {
4
+ const command = new Command("rotation").description("View direction query");
5
+ command.command("get").description("Get current view direction (yaw, pitch)").action(createRequestAction("rotation.get", () => ({})));
6
+ return command;
7
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function createScreenCommand(): Command;
@@ -0,0 +1,7 @@
1
+ import { Command } from "commander";
2
+ import { createRequestAction } from "./request-helpers.js";
3
+ export function createScreenCommand() {
4
+ const command = new Command("screen").description("Screen info");
5
+ command.command("size").description("Get screen dimensions (width, height)").action(createRequestAction("screen.size", () => ({})));
6
+ return command;
7
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function createScreenshotCommand(): Command;
@@ -0,0 +1,14 @@
1
+ import { Command } from "commander";
2
+ import { createRequestAction } from "./request-helpers.js";
3
+ export function createScreenshotCommand() {
4
+ return new Command("screenshot")
5
+ .description("Take a screenshot")
6
+ .requiredOption("--output <path>", "Output file path (e.g. ./screenshots/test.png)")
7
+ .option("--region <region>", "Capture a sub-region, format: x,y,w,h")
8
+ .option("--gui", "Capture the current GUI screen")
9
+ .action(createRequestAction("capture.screenshot", ({ options }) => ({
10
+ output: options.output,
11
+ region: options.region,
12
+ gui: Boolean(options.gui)
13
+ })));
14
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function createServerCommand(): Command;
@@ -0,0 +1,66 @@
1
+ import { Command } from "commander";
2
+ import { buildServerSearchResults } from "../download/SearchCommand.js";
3
+ import { downloadServerJar } from "../download/server/ServerDownloader.js";
4
+ import { wrapCommand } from "../util/command.js";
5
+ import { ServerManager } from "../server/ServerManager.js";
6
+ export function createServerCommand() {
7
+ const command = new Command("server").description("Manage Minecraft server");
8
+ command
9
+ .command("search")
10
+ .description("Search available server versions")
11
+ .option("--type <type>", "Server type: vanilla|paper|purpur|spigot")
12
+ .option("--version <version>", "Minecraft version")
13
+ .action(wrapCommand(async (_context, { options }) => {
14
+ return {
15
+ results: buildServerSearchResults({
16
+ type: options.type,
17
+ version: options.version
18
+ })
19
+ };
20
+ }));
21
+ command
22
+ .command("download")
23
+ .description("Download server jar and update config")
24
+ .option("--type <type>", "Server type: vanilla|paper|purpur|spigot")
25
+ .option("--version <version>", "Minecraft version")
26
+ .option("--build <build>", "Specific build number")
27
+ .option("--dir <path>", "Download target directory")
28
+ .option("--fixtures <path>", "Fixture plugin jar, auto-copied to plugins/")
29
+ .action(wrapCommand(async (context, { options }) => {
30
+ return downloadServerJar(context, options);
31
+ }));
32
+ command
33
+ .command("start")
34
+ .description("Start the server")
35
+ .option("--jar <path>", "Server jar path (default: from config server.jar)")
36
+ .option("--dir <path>", "Server directory (default: from config server.dir)")
37
+ .option("--port <number>", "Server port (default: 25565)", Number)
38
+ .option("--eula", "Auto-accept EULA")
39
+ .action(wrapCommand(async (context, { options }) => {
40
+ const manager = new ServerManager(context);
41
+ return manager.start(options);
42
+ }));
43
+ command
44
+ .command("stop")
45
+ .description("Stop the server")
46
+ .action(wrapCommand(async (context) => {
47
+ const manager = new ServerManager(context);
48
+ return manager.stop();
49
+ }));
50
+ command
51
+ .command("status")
52
+ .description("Show server status")
53
+ .action(wrapCommand(async (context) => {
54
+ const manager = new ServerManager(context);
55
+ return manager.status();
56
+ }));
57
+ command
58
+ .command("wait-ready")
59
+ .description("Wait until server port is connectable")
60
+ .option("--timeout <seconds>", "Timeout in seconds", Number)
61
+ .action(wrapCommand(async (context, { options }) => {
62
+ const manager = new ServerManager(context);
63
+ return manager.waitReady(options.timeout ?? context.config.timeout.serverReady);
64
+ }));
65
+ return command;
66
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function createSignCommand(): Command;
@@ -0,0 +1,30 @@
1
+ import { Command } from "commander";
2
+ import { createRequestAction } from "./request-helpers.js";
3
+ export function createSignCommand() {
4
+ const command = new Command("sign").description("Sign block operations (reads/writes directly, no GUI interaction needed)");
5
+ command
6
+ .command("read")
7
+ .description("Read sign text")
8
+ .argument("<x>", "X coordinate")
9
+ .argument("<y>", "Y coordinate")
10
+ .argument("<z>", "Z coordinate")
11
+ .action(createRequestAction("sign.read", ({ args }) => ({
12
+ x: Number(args[0]),
13
+ y: Number(args[1]),
14
+ z: Number(args[2])
15
+ })));
16
+ command
17
+ .command("edit")
18
+ .description("Edit sign text")
19
+ .argument("<x>", "X coordinate")
20
+ .argument("<y>", "Y coordinate")
21
+ .argument("<z>", "Z coordinate")
22
+ .requiredOption("--lines <lines...>", "Four lines of text, e.g. --lines \"Line 1\" \"Line 2\" \"Line 3\" \"Line 4\"")
23
+ .action(createRequestAction("sign.edit", ({ args, options }) => ({
24
+ x: Number(args[0]),
25
+ y: Number(args[1]),
26
+ z: Number(args[2]),
27
+ lines: options.lines
28
+ })));
29
+ return command;
30
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function createStatusCommand(): Command;
@@ -0,0 +1,17 @@
1
+ import { Command } from "commander";
2
+ import { createRequestAction } from "./request-helpers.js";
3
+ export function createStatusCommand() {
4
+ const command = new Command("status").description("Player status queries");
5
+ const statusLabels = {
6
+ health: "Get health and hunger",
7
+ effects: "Get active potion effects",
8
+ experience: "Get XP level and progress",
9
+ gamemode: "Get current game mode",
10
+ world: "Get current world info",
11
+ all: "Get all status at once"
12
+ };
13
+ for (const sub of ["health", "effects", "experience", "gamemode", "world", "all"]) {
14
+ command.command(sub).description(statusLabels[sub]).action(createRequestAction(`status.${sub}`, () => ({})));
15
+ }
16
+ return command;
17
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function createWaitCommand(): Command;
@@ -0,0 +1,23 @@
1
+ import { Command } from "commander";
2
+ import { createRequestAction, withTransportTimeoutBuffer } from "./request-helpers.js";
3
+ export function createWaitCommand() {
4
+ return new Command("wait")
5
+ .description("Wait and synchronization")
6
+ .argument("[seconds]", "Wait for a number of seconds")
7
+ .option("--ticks <ticks>", "Wait for a number of game ticks", Number)
8
+ .option("--until-health-above <value>", "Wait until health is above this value", Number)
9
+ .option("--until-gui-open", "Wait until a GUI opens")
10
+ .option("--until-on-ground", "Wait until the player is on the ground")
11
+ .option("--timeout <seconds>", "Timeout in seconds", Number)
12
+ .action(createRequestAction("wait.perform", ({ args, options }) => ({
13
+ seconds: args[0] ? Number(args[0]) : undefined,
14
+ ticks: options.ticks,
15
+ untilHealthAbove: options.untilHealthAbove,
16
+ untilGuiOpen: Boolean(options.untilGuiOpen),
17
+ untilOnGround: Boolean(options.untilOnGround),
18
+ timeout: options.timeout
19
+ }), ({ options, args }, context) => {
20
+ const requested = options.timeout ? Number(options.timeout) : args[0] ? Number(args[0]) : context.config.timeout.default;
21
+ return withTransportTimeoutBuffer(requested, context.config.timeout.default);
22
+ }));
23
+ }
@@ -0,0 +1,20 @@
1
+ import type { ServerType } from "./VersionMatrix.js";
2
+ import type { LoaderType } from "./types.js";
3
+ export declare function resolveCacheRoot(): string;
4
+ export declare class CacheManager {
5
+ private readonly rootDir;
6
+ constructor(rootDir?: string);
7
+ getRootDir(): string;
8
+ getServerJarPath(type: ServerType, version: string, build: string): string;
9
+ getServerFile(type: ServerType, version: string, build: string): string;
10
+ getMinecraftDir(version: string): string;
11
+ getClientMetadataFile(scope: "version-manifest" | "minecraft-version" | "fabric-loader-profile", id: string): string;
12
+ getClientLibraryFile(relativePath: string): string;
13
+ getClientJarPath(version: string): string;
14
+ getClientAssetIndexPath(assetIndexId: string): string;
15
+ getClientAssetObjectPath(hash: string): string;
16
+ getClientAssetsRoot(): string;
17
+ getClientLoggingConfigPath(fileName: string): string;
18
+ getLoaderDir(loader: LoaderType): string;
19
+ getModFile(fileName: string): string;
20
+ }
@@ -0,0 +1,50 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ export function resolveCacheRoot() {
4
+ return process.env.MCT_CACHE_DIR || path.join(os.homedir(), ".mct", "cache");
5
+ }
6
+ export class CacheManager {
7
+ rootDir;
8
+ constructor(rootDir = resolveCacheRoot()) {
9
+ this.rootDir = rootDir;
10
+ }
11
+ getRootDir() {
12
+ return this.rootDir;
13
+ }
14
+ getServerJarPath(type, version, build) {
15
+ return path.join(this.rootDir, "server", type, `${version}-${build}.jar`);
16
+ }
17
+ getServerFile(type, version, build) {
18
+ return this.getServerJarPath(type, version, build);
19
+ }
20
+ getMinecraftDir(version) {
21
+ return path.join(this.rootDir, "client", "minecraft", version);
22
+ }
23
+ getClientMetadataFile(scope, id) {
24
+ return path.join(this.rootDir, "client", "metadata", scope, `${id}.json`);
25
+ }
26
+ getClientLibraryFile(relativePath) {
27
+ return path.join(this.rootDir, "client", "libraries", relativePath);
28
+ }
29
+ getClientJarPath(version) {
30
+ return path.join(this.rootDir, "client", "jars", `minecraft-client-${version}.jar`);
31
+ }
32
+ getClientAssetIndexPath(assetIndexId) {
33
+ return path.join(this.rootDir, "client", "assets", "indexes", `${assetIndexId}.json`);
34
+ }
35
+ getClientAssetObjectPath(hash) {
36
+ return path.join(this.rootDir, "client", "assets", "objects", hash.slice(0, 2), hash);
37
+ }
38
+ getClientAssetsRoot() {
39
+ return path.join(this.rootDir, "client", "assets");
40
+ }
41
+ getClientLoggingConfigPath(fileName) {
42
+ return path.join(this.rootDir, "client", "logging", fileName);
43
+ }
44
+ getLoaderDir(loader) {
45
+ return path.join(this.rootDir, "client", loader);
46
+ }
47
+ getModFile(fileName) {
48
+ return path.join(this.rootDir, "mod", fileName);
49
+ }
50
+ }
@@ -0,0 +1,2 @@
1
+ export declare function copyFileIfMissing(sourcePath: string, targetPath: string): Promise<void>;
2
+ export declare function downloadFile(url: string, targetPath: string, fetchImpl?: typeof fetch): Promise<void>;
@@ -0,0 +1,23 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { MctError } from "../util/errors.js";
4
+ export async function copyFileIfMissing(sourcePath, targetPath) {
5
+ const content = await readFile(sourcePath);
6
+ await mkdir(path.dirname(targetPath), { recursive: true });
7
+ await writeFile(targetPath, content);
8
+ }
9
+ export async function downloadFile(url, targetPath, fetchImpl = fetch) {
10
+ const response = await fetchImpl(url);
11
+ if (!response.ok) {
12
+ throw new MctError({
13
+ code: "DOWNLOAD_FAILED",
14
+ message: `Failed to download ${url}`,
15
+ details: {
16
+ status: response.status
17
+ }
18
+ }, 2);
19
+ }
20
+ const arrayBuffer = await response.arrayBuffer();
21
+ await mkdir(path.dirname(targetPath), { recursive: true });
22
+ await writeFile(targetPath, Buffer.from(arrayBuffer));
23
+ }
@@ -0,0 +1,7 @@
1
+ export interface JavaDetectionResult {
2
+ available: boolean;
3
+ command: string;
4
+ majorVersion?: number;
5
+ }
6
+ export declare function parseJavaMajorVersion(output: string): number | undefined;
7
+ export declare function detectJava(command?: string): Promise<JavaDetectionResult>;
@@ -0,0 +1,31 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ const execFileAsync = promisify(execFile);
4
+ export function parseJavaMajorVersion(output) {
5
+ const match = output.match(/version "(?<version>[^"]+)"/);
6
+ if (!match?.groups?.version) {
7
+ return undefined;
8
+ }
9
+ const [first, second] = match.groups.version.split(".");
10
+ if (first === "1" && second) {
11
+ return Number.parseInt(second, 10);
12
+ }
13
+ return Number.parseInt(first, 10);
14
+ }
15
+ export async function detectJava(command = "java") {
16
+ try {
17
+ const result = await execFileAsync(command, ["-version"]);
18
+ const output = `${result.stdout}\n${result.stderr}`;
19
+ return {
20
+ available: true,
21
+ command,
22
+ majorVersion: parseJavaMajorVersion(output)
23
+ };
24
+ }
25
+ catch {
26
+ return {
27
+ available: false,
28
+ command
29
+ };
30
+ }
31
+ }
@@ -0,0 +1,10 @@
1
+ import type { ModVariantCatalog, ModVariant } from "./types.js";
2
+ export declare function loadModVariantCatalog(filePath?: string): Promise<ModVariantCatalog>;
3
+ export declare function loadModVariantCatalogSync(filePath?: string): ModVariantCatalog;
4
+ export declare function sortVariants(variants: ModVariant[]): ModVariant[];
5
+ export declare function findVariant(catalog: ModVariantCatalog, variantId: string): ModVariant | undefined;
6
+ export declare function getDefaultVariant(catalog: ModVariantCatalog): ModVariant | undefined;
7
+ export declare function findVariantByVersionAndLoader(catalog: ModVariantCatalog, minecraftVersion: string, loader: ModVariant["loader"]): ModVariant | undefined;
8
+ export declare function getBuildableFabricVariants(catalog: ModVariantCatalog): ModVariant[];
9
+ export declare function getSupportedMinecraftVersions(catalog: ModVariantCatalog): string[];
10
+ export declare function getModArtifactFileName(variant: ModVariant): string;
@@ -0,0 +1,52 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { readFile } from "node:fs/promises";
3
+ import { fileURLToPath } from "node:url";
4
+ const DEFAULT_VARIANTS_PATH = fileURLToPath(new URL("../../data/variants.json", import.meta.url));
5
+ function compareVersions(left, right) {
6
+ const leftParts = left.split(".").map((value) => Number.parseInt(value, 10));
7
+ const rightParts = right.split(".").map((value) => Number.parseInt(value, 10));
8
+ const length = Math.max(leftParts.length, rightParts.length);
9
+ for (let index = 0; index < length; index += 1) {
10
+ const delta = (leftParts[index] ?? 0) - (rightParts[index] ?? 0);
11
+ if (delta !== 0) {
12
+ return delta;
13
+ }
14
+ }
15
+ return 0;
16
+ }
17
+ export async function loadModVariantCatalog(filePath = DEFAULT_VARIANTS_PATH) {
18
+ const raw = await readFile(filePath, "utf8");
19
+ return JSON.parse(raw);
20
+ }
21
+ export function loadModVariantCatalogSync(filePath = DEFAULT_VARIANTS_PATH) {
22
+ const raw = readFileSync(filePath, "utf8");
23
+ return JSON.parse(raw);
24
+ }
25
+ export function sortVariants(variants) {
26
+ return [...variants].sort((left, right) => {
27
+ const versionDelta = compareVersions(right.minecraftVersion, left.minecraftVersion);
28
+ if (versionDelta !== 0) {
29
+ return versionDelta;
30
+ }
31
+ return left.loader.localeCompare(right.loader);
32
+ });
33
+ }
34
+ export function findVariant(catalog, variantId) {
35
+ return catalog.variants.find((variant) => variant.id === variantId);
36
+ }
37
+ export function getDefaultVariant(catalog) {
38
+ return findVariant(catalog, catalog.defaultVariant);
39
+ }
40
+ export function findVariantByVersionAndLoader(catalog, minecraftVersion, loader) {
41
+ return catalog.variants.find((variant) => variant.minecraftVersion === minecraftVersion && variant.loader === loader);
42
+ }
43
+ export function getBuildableFabricVariants(catalog) {
44
+ return sortVariants(catalog.variants.filter((variant) => variant.loader === "fabric" && variant.yarnMappings && variant.fabricLoaderVersion
45
+ && (variant.support === "ready" || variant.support === "configured")));
46
+ }
47
+ export function getSupportedMinecraftVersions(catalog) {
48
+ return [...new Set(catalog.variants.map((variant) => variant.minecraftVersion))].sort(compareVersions).reverse();
49
+ }
50
+ export function getModArtifactFileName(variant) {
51
+ return `mct-client-mod-${variant.loader}-${variant.minecraftVersion}.jar`;
52
+ }
@@ -0,0 +1,29 @@
1
+ import { type ClientLoader, type ServerType } from "./VersionMatrix.js";
2
+ export interface ServerSearchCommandResult {
3
+ type: ServerType;
4
+ versions: Array<{
5
+ version: string;
6
+ build?: string;
7
+ requiresBuildTools?: boolean;
8
+ }>;
9
+ }
10
+ export interface ClientSearchCommandResult {
11
+ version: string;
12
+ javaVersion: string;
13
+ loaders: Array<{
14
+ loader: ClientLoader;
15
+ supported: boolean;
16
+ loaderVersion?: string;
17
+ modVersion?: string;
18
+ validation?: "verified" | "limited" | "planned";
19
+ notes?: string;
20
+ }>;
21
+ }
22
+ export declare function buildServerSearchResults(filter?: {
23
+ type?: ServerType;
24
+ version?: string;
25
+ }): ServerSearchCommandResult[];
26
+ export declare function buildClientSearchResults(filter?: {
27
+ loader?: ClientLoader;
28
+ version?: string;
29
+ }): ClientSearchCommandResult[];
@@ -0,0 +1,37 @@
1
+ import { searchClientVersions, searchServerVersions } from "./VersionMatrix.js";
2
+ export function buildServerSearchResults(filter) {
3
+ const grouped = new Map();
4
+ for (const entry of searchServerVersions(filter).filter((item) => item.supported)) {
5
+ const current = grouped.get(entry.type) ?? {
6
+ type: entry.type,
7
+ versions: []
8
+ };
9
+ current.versions.push({
10
+ version: entry.minecraftVersion,
11
+ ...(entry.latestBuild != null ? { build: String(entry.latestBuild) } : {}),
12
+ ...(entry.requiresBuildTools ? { requiresBuildTools: true } : {})
13
+ });
14
+ grouped.set(entry.type, current);
15
+ }
16
+ return [...grouped.values()];
17
+ }
18
+ export function buildClientSearchResults(filter) {
19
+ const grouped = new Map();
20
+ for (const entry of searchClientVersions(filter)) {
21
+ const current = grouped.get(entry.minecraftVersion) ?? {
22
+ version: entry.minecraftVersion,
23
+ javaVersion: entry.javaVersion,
24
+ loaders: []
25
+ };
26
+ current.loaders.push({
27
+ loader: entry.loader,
28
+ supported: entry.supported,
29
+ ...(entry.loaderVersion ? { loaderVersion: entry.loaderVersion } : {}),
30
+ ...(entry.modVersion ? { modVersion: entry.modVersion } : {}),
31
+ ...(entry.validation ? { validation: entry.validation } : {}),
32
+ ...(entry.notes ? { notes: entry.notes } : {})
33
+ });
34
+ grouped.set(entry.minecraftVersion, current);
35
+ }
36
+ return [...grouped.values()];
37
+ }
@@ -0,0 +1,72 @@
1
+ export type ServerType = "paper" | "purpur" | "spigot" | "vanilla";
2
+ export type ClientLoader = "fabric" | "forge" | "neoforge";
3
+ export interface ServerSupportInfo {
4
+ supported: boolean;
5
+ latestBuild?: number;
6
+ requiresBuildTools?: boolean;
7
+ }
8
+ export interface ClientLoaderSupportInfo {
9
+ supported: boolean;
10
+ loaderVersion?: string;
11
+ modVersion?: string;
12
+ validation?: "verified" | "limited" | "planned";
13
+ notes?: string;
14
+ }
15
+ export interface MinecraftSupportEntry {
16
+ minecraftVersion: string;
17
+ javaVersion: string;
18
+ servers: Record<ServerType, ServerSupportInfo>;
19
+ clients: Record<ClientLoader, ClientLoaderSupportInfo>;
20
+ }
21
+ export interface ServerSearchResult {
22
+ type: ServerType;
23
+ minecraftVersion: string;
24
+ supported: boolean;
25
+ latestBuild?: number;
26
+ requiresBuildTools?: boolean;
27
+ }
28
+ export interface ServerCatalogEntry {
29
+ version: string;
30
+ build?: string;
31
+ requiresBuildTools?: boolean;
32
+ }
33
+ export interface ClientSearchResult {
34
+ loader: ClientLoader;
35
+ minecraftVersion: string;
36
+ supported: boolean;
37
+ loaderVersion?: string;
38
+ modVersion?: string;
39
+ validation?: "verified" | "limited" | "planned";
40
+ notes?: string;
41
+ javaVersion: string;
42
+ }
43
+ export declare function getVersionMatrix(): {
44
+ minecraftVersion: string;
45
+ javaVersion: string;
46
+ servers: {
47
+ paper: ServerSupportInfo;
48
+ purpur: ServerSupportInfo;
49
+ spigot: ServerSupportInfo;
50
+ vanilla: ServerSupportInfo;
51
+ };
52
+ clients: {
53
+ fabric: ClientLoaderSupportInfo;
54
+ forge: ClientLoaderSupportInfo;
55
+ neoforge: ClientLoaderSupportInfo;
56
+ };
57
+ }[];
58
+ export declare function getSupportedMinecraftVersions(): string[];
59
+ export declare function getMinecraftSupport(version: string): MinecraftSupportEntry | undefined;
60
+ export declare function searchServerVersions(filter?: {
61
+ type?: ServerType;
62
+ version?: string;
63
+ }): ServerSearchResult[];
64
+ export declare function searchClientVersions(filter?: {
65
+ loader?: ClientLoader;
66
+ version?: string;
67
+ }): ClientSearchResult[];
68
+ export declare function getServerVersionMatrix(): ServerSearchResult[];
69
+ export declare function getClientVersionMatrix(): ClientSearchResult[];
70
+ export declare function getServerVersionCatalog(): Record<ServerType, ServerCatalogEntry[]>;
71
+ export declare function getServerTypes(): ServerType[];
72
+ export declare function getClientLoaders(): ClientLoader[];