@kzheart_/mc-pilot 0.1.7 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/chat.js +1 -1
- package/dist/commands/client.js +123 -34
- package/dist/commands/combat.js +3 -3
- package/dist/commands/gui.js +2 -2
- package/dist/commands/input.js +1 -1
- package/dist/commands/move.js +2 -2
- package/dist/commands/project.d.ts +6 -0
- package/dist/commands/project.js +155 -0
- package/dist/commands/request-helpers.js +4 -4
- package/dist/commands/server.js +82 -28
- package/dist/commands/wait.js +2 -2
- package/dist/download/VersionMatrix.d.ts +1 -1
- package/dist/download/client/ClientDownloader.d.ts +20 -6
- package/dist/download/client/ClientDownloader.js +14 -35
- package/dist/download/server/ServerDownloader.d.ts +3 -5
- package/dist/download/server/ServerDownloader.js +2 -17
- package/dist/index.js +20 -15
- package/dist/instance/ClientInstanceManager.d.ts +49 -0
- package/dist/instance/ClientInstanceManager.js +237 -0
- package/dist/instance/ServerInstanceManager.d.ts +70 -0
- package/dist/instance/ServerInstanceManager.js +223 -0
- package/dist/util/command.js +2 -2
- package/dist/util/context.d.ts +10 -8
- package/dist/util/context.js +21 -9
- package/dist/util/global-state.d.ts +9 -0
- package/dist/util/global-state.js +21 -0
- package/dist/util/instance-types.d.ts +46 -0
- package/dist/util/instance-types.js +1 -0
- package/dist/util/paths.d.ts +7 -0
- package/dist/util/paths.js +23 -0
- package/dist/util/project.d.ts +23 -0
- package/dist/util/project.js +28 -0
- package/package.json +1 -1
package/dist/commands/chat.js
CHANGED
|
@@ -22,7 +22,7 @@ export function createChatCommand() {
|
|
|
22
22
|
.description("Wait for a chat message matching a pattern")
|
|
23
23
|
.requiredOption("--match <pattern>", "Substring match by default; prefix with / for regex (e.g. /player\\d+/)")
|
|
24
24
|
.option("--timeout <seconds>", "Timeout in seconds", Number)
|
|
25
|
-
.action(createRequestAction("chat.wait", ({ options }) => ({ match: options.match, timeout: options.timeout }), ({ options }, context) => withTransportTimeoutBuffer(options.timeout ? Number(options.timeout) : undefined, context.
|
|
25
|
+
.action(createRequestAction("chat.wait", ({ options }) => ({ match: options.match, timeout: options.timeout }), ({ options }, context) => withTransportTimeoutBuffer(options.timeout ? Number(options.timeout) : undefined, context.timeout("default"))));
|
|
26
26
|
command
|
|
27
27
|
.command("last")
|
|
28
28
|
.description("Get the last chat message")
|
package/dist/commands/client.js
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import { ClientManager } from "../client/ClientManager.js";
|
|
3
|
-
import { downloadClientMod } from "../download/client/ClientDownloader.js";
|
|
4
2
|
import { buildClientSearchResults } from "../download/SearchCommand.js";
|
|
3
|
+
import { ClientInstanceManager } from "../instance/ClientInstanceManager.js";
|
|
4
|
+
import { MctError } from "../util/errors.js";
|
|
5
5
|
import { createRequestAction } from "./request-helpers.js";
|
|
6
6
|
import { wrapCommand } from "../util/command.js";
|
|
7
|
+
import { CacheManager } from "../download/CacheManager.js";
|
|
8
|
+
import { findVariantByVersionAndLoader, getModArtifactFileName, loadModVariantCatalog } from "../download/ModVariantCatalog.js";
|
|
9
|
+
import { detectJava } from "../download/JavaDetector.js";
|
|
10
|
+
import { prepareManagedFabricRuntime } from "../download/client/FabricRuntimeDownloader.js";
|
|
11
|
+
import { copyFileIfMissing, downloadFile } from "../download/DownloadUtils.js";
|
|
12
|
+
import { resolveClientInstanceDir } from "../util/paths.js";
|
|
13
|
+
import { access, mkdir } from "node:fs/promises";
|
|
14
|
+
import path from "node:path";
|
|
7
15
|
export function createClientCommand() {
|
|
8
|
-
const command = new Command("client").description("Manage Minecraft client");
|
|
16
|
+
const command = new Command("client").description("Manage Minecraft client instances");
|
|
9
17
|
command
|
|
10
18
|
.command("search")
|
|
11
19
|
.description("Search available client version and loader combinations")
|
|
@@ -20,66 +28,147 @@ export function createClientCommand() {
|
|
|
20
28
|
};
|
|
21
29
|
}));
|
|
22
30
|
command
|
|
23
|
-
.command("
|
|
24
|
-
.description("
|
|
25
|
-
.
|
|
26
|
-
.option("--version <version>", "Minecraft version (default: 1.
|
|
27
|
-
.option("--
|
|
28
|
-
.option("--
|
|
29
|
-
.option("--
|
|
30
|
-
.option("--
|
|
31
|
-
.option("--
|
|
32
|
-
.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
.command("create")
|
|
32
|
+
.description("Create a new client instance")
|
|
33
|
+
.argument("<name>", "Client instance name (e.g. fabric-1.20.4)")
|
|
34
|
+
.option("--version <version>", "Minecraft version (default: 1.21.4)")
|
|
35
|
+
.option("--loader <loader>", "Client loader: fabric (default: fabric)")
|
|
36
|
+
.option("--ws-port <port>", "WebSocket port (auto-assigned if omitted)", Number)
|
|
37
|
+
.option("--account <account>", "Offline username or account identifier")
|
|
38
|
+
.option("--headless", "Launch in headless mode")
|
|
39
|
+
.option("--java <command>", "Java command to use")
|
|
40
|
+
.action(wrapCommand(async (_context, { args, options }) => {
|
|
41
|
+
const clientName = args[0];
|
|
42
|
+
const loader = options.loader ?? "fabric";
|
|
43
|
+
const version = options.version ?? "1.21.4";
|
|
44
|
+
const cacheManager = new CacheManager();
|
|
45
|
+
const catalog = await loadModVariantCatalog();
|
|
46
|
+
const variant = findVariantByVersionAndLoader(catalog, version, loader);
|
|
47
|
+
if (!variant) {
|
|
48
|
+
throw new MctError({ code: "VARIANT_NOT_FOUND", message: `No mod variant found for ${version} / ${loader}` }, 4);
|
|
49
|
+
}
|
|
50
|
+
if (variant.loader !== "fabric") {
|
|
51
|
+
throw new MctError({ code: "UNSUPPORTED_LOADER", message: `Loader ${variant.loader} is not implemented yet` }, 4);
|
|
52
|
+
}
|
|
53
|
+
// Check Java
|
|
54
|
+
const java = await detectJava(options.java ?? "java");
|
|
55
|
+
const requiredJava = variant.javaVersion ?? 17;
|
|
56
|
+
if (!java.available || (java.majorVersion ?? 0) < requiredJava) {
|
|
57
|
+
throw new MctError({ code: "JAVA_NOT_FOUND", message: `Java ${requiredJava}+ is required for ${variant.id}` }, 4);
|
|
58
|
+
}
|
|
59
|
+
// Resolve mod artifact
|
|
60
|
+
const artifactFileName = getModArtifactFileName(variant);
|
|
61
|
+
const cacheArtifactPath = cacheManager.getModFile(artifactFileName);
|
|
62
|
+
const gradleModule = variant.gradleModule ?? `version-${variant.minecraftVersion}`;
|
|
63
|
+
const localBuildPath = path.join(process.cwd(), "client-mod", gradleModule, "build", "libs", artifactFileName);
|
|
64
|
+
let sourcePath;
|
|
65
|
+
try {
|
|
66
|
+
await access(localBuildPath);
|
|
67
|
+
sourcePath = localBuildPath;
|
|
68
|
+
await copyFileIfMissing(localBuildPath, cacheArtifactPath);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
try {
|
|
72
|
+
await access(cacheArtifactPath);
|
|
73
|
+
sourcePath = cacheArtifactPath;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
const modVersion = variant.modVersion ?? "0.1.0";
|
|
77
|
+
const baseUrl = process.env.MCT_MOD_DOWNLOAD_BASE_URL || "https://github.com/kzheart/mc-pilot/releases/download";
|
|
78
|
+
const downloadUrl = `${baseUrl}/v${modVersion}/${artifactFileName}`;
|
|
79
|
+
await downloadFile(downloadUrl, cacheArtifactPath, fetch);
|
|
80
|
+
sourcePath = cacheArtifactPath;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Set up client instance directory
|
|
84
|
+
const instanceDir = resolveClientInstanceDir(clientName);
|
|
85
|
+
const minecraftDir = path.join(instanceDir, "minecraft");
|
|
86
|
+
const modsDir = path.join(minecraftDir, "mods");
|
|
87
|
+
await mkdir(modsDir, { recursive: true });
|
|
88
|
+
await copyFileIfMissing(sourcePath, path.join(modsDir, artifactFileName));
|
|
89
|
+
// Prepare Fabric runtime
|
|
90
|
+
const runtimeRootDir = path.join(cacheManager.getRootDir(), "client", "runtime", variant.minecraftVersion);
|
|
91
|
+
const managedRuntime = await prepareManagedFabricRuntime(variant, {
|
|
92
|
+
runtimeRootDir,
|
|
93
|
+
gameDir: minecraftDir
|
|
94
|
+
}, { fetchImpl: fetch });
|
|
95
|
+
const launchArgs = [
|
|
96
|
+
"--runtime-root", managedRuntime.runtimeRootDir,
|
|
97
|
+
"--version-id", managedRuntime.versionId,
|
|
98
|
+
"--game-dir", managedRuntime.gameDir
|
|
99
|
+
];
|
|
100
|
+
const manager = new ClientInstanceManager(_context.globalState);
|
|
101
|
+
const meta = await manager.create({
|
|
102
|
+
name: clientName,
|
|
103
|
+
loader,
|
|
104
|
+
version: variant.minecraftVersion,
|
|
105
|
+
wsPort: options.wsPort,
|
|
106
|
+
account: options.account,
|
|
107
|
+
headless: options.headless,
|
|
108
|
+
launchArgs,
|
|
109
|
+
env: {
|
|
110
|
+
MCT_CLIENT_MOD_VARIANT: variant.id,
|
|
111
|
+
MCT_CLIENT_MOD_JAR: path.join(modsDir, artifactFileName)
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
return {
|
|
115
|
+
created: true,
|
|
116
|
+
...meta,
|
|
117
|
+
javaCommand: java.command,
|
|
118
|
+
javaVersion: java.majorVersion,
|
|
119
|
+
modsDir,
|
|
120
|
+
runtimeRootDir: managedRuntime.runtimeRootDir,
|
|
121
|
+
runtimeVersionId: managedRuntime.versionId
|
|
122
|
+
};
|
|
38
123
|
}));
|
|
39
124
|
command
|
|
40
125
|
.command("launch")
|
|
41
126
|
.description("Launch a client instance")
|
|
42
|
-
.argument("
|
|
43
|
-
.option("--version <version>", "Minecraft version")
|
|
127
|
+
.argument("[name]", "Client instance name (default: from active profile)")
|
|
44
128
|
.option("--server <address>", "Target server address (e.g. localhost:25565)")
|
|
45
129
|
.option("--account <account>", "Offline username or account identifier")
|
|
46
|
-
.option("--ws-port <port>", "WebSocket port
|
|
47
|
-
.option("--headless", "Launch in headless mode
|
|
130
|
+
.option("--ws-port <port>", "WebSocket port override", Number)
|
|
131
|
+
.option("--headless", "Launch in headless mode")
|
|
48
132
|
.action(wrapCommand(async (context, { args, options }) => {
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
133
|
+
const clientName = args[0] ?? context.activeProfile?.clients[0];
|
|
134
|
+
if (!clientName) {
|
|
135
|
+
throw new MctError({ code: "INVALID_PARAMS", message: "Client name is required. Specify it as argument or set a profile." }, 4);
|
|
136
|
+
}
|
|
137
|
+
const manager = new ClientInstanceManager(context.globalState);
|
|
138
|
+
return manager.launch(clientName, options);
|
|
54
139
|
}));
|
|
55
140
|
command
|
|
56
141
|
.command("stop")
|
|
57
142
|
.description("Stop a client instance")
|
|
58
|
-
.argument("<name>", "Client name")
|
|
143
|
+
.argument("<name>", "Client instance name")
|
|
59
144
|
.action(wrapCommand(async (context, { args }) => {
|
|
60
|
-
const manager = new
|
|
145
|
+
const manager = new ClientInstanceManager(context.globalState);
|
|
61
146
|
return manager.stop(args[0]);
|
|
62
147
|
}));
|
|
63
148
|
command
|
|
64
149
|
.command("list")
|
|
65
150
|
.description("List all client instances and their status")
|
|
66
151
|
.action(wrapCommand(async (context) => {
|
|
67
|
-
const manager = new
|
|
152
|
+
const manager = new ClientInstanceManager(context.globalState);
|
|
68
153
|
return manager.list();
|
|
69
154
|
}));
|
|
70
155
|
command
|
|
71
156
|
.command("wait-ready")
|
|
72
157
|
.description("Wait until client WebSocket is connected")
|
|
73
|
-
.argument("
|
|
158
|
+
.argument("[name]", "Client instance name (default: from active profile)")
|
|
74
159
|
.option("--timeout <seconds>", "Timeout in seconds", Number)
|
|
75
160
|
.action(wrapCommand(async (context, { args, options }) => {
|
|
76
|
-
const
|
|
77
|
-
|
|
161
|
+
const clientName = args[0] ?? context.activeProfile?.clients[0];
|
|
162
|
+
if (!clientName) {
|
|
163
|
+
throw new MctError({ code: "INVALID_PARAMS", message: "Client name is required" }, 4);
|
|
164
|
+
}
|
|
165
|
+
const manager = new ClientInstanceManager(context.globalState);
|
|
166
|
+
return manager.waitReady(clientName, options.timeout ?? context.timeout("clientReady"));
|
|
78
167
|
}));
|
|
79
168
|
command
|
|
80
169
|
.command("reconnect")
|
|
81
170
|
.description("Reconnect the client to the server")
|
|
82
|
-
.option("--address <address>", "Target server address
|
|
171
|
+
.option("--address <address>", "Target server address")
|
|
83
172
|
.action(createRequestAction("client.reconnect", ({ options }) => ({
|
|
84
173
|
address: options.address
|
|
85
174
|
})));
|
package/dist/commands/combat.js
CHANGED
|
@@ -20,7 +20,7 @@ export function createCombatCommand() {
|
|
|
20
20
|
.action(createRequestAction(`combat.${actionName}`, ({ options }) => ({
|
|
21
21
|
filter: buildEntityFilter(options),
|
|
22
22
|
timeout: options.timeout
|
|
23
|
-
}), ({ options }, context) => withTransportTimeoutBuffer(options.timeout ? Number(options.timeout) : 30, context.
|
|
23
|
+
}), ({ options }, context) => withTransportTimeoutBuffer(options.timeout ? Number(options.timeout) : 30, context.timeout("default"))));
|
|
24
24
|
}
|
|
25
25
|
command
|
|
26
26
|
.command("clear")
|
|
@@ -32,7 +32,7 @@ export function createCombatCommand() {
|
|
|
32
32
|
type: options.type,
|
|
33
33
|
radius: options.radius ?? 16,
|
|
34
34
|
timeout: options.timeout
|
|
35
|
-
}), ({ options }, context) => withTransportTimeoutBuffer(options.timeout ? Number(options.timeout) : 60, context.
|
|
35
|
+
}), ({ options }, context) => withTransportTimeoutBuffer(options.timeout ? Number(options.timeout) : 60, context.timeout("default"))));
|
|
36
36
|
command
|
|
37
37
|
.command("pickup")
|
|
38
38
|
.description("Pick up nearby dropped items")
|
|
@@ -41,6 +41,6 @@ export function createCombatCommand() {
|
|
|
41
41
|
.action(createRequestAction("combat.pickup", ({ options }) => ({
|
|
42
42
|
radius: options.radius ?? 5,
|
|
43
43
|
timeout: options.timeout
|
|
44
|
-
}), ({ options }, context) => withTransportTimeoutBuffer(options.timeout ? Number(options.timeout) : 10, context.
|
|
44
|
+
}), ({ options }, context) => withTransportTimeoutBuffer(options.timeout ? Number(options.timeout) : 10, context.timeout("default"))));
|
|
45
45
|
return command;
|
|
46
46
|
}
|
package/dist/commands/gui.js
CHANGED
|
@@ -34,12 +34,12 @@ export function createGuiCommand() {
|
|
|
34
34
|
.command("wait-open")
|
|
35
35
|
.description("Wait for a GUI to open")
|
|
36
36
|
.option("--timeout <seconds>", "Timeout in seconds", Number)
|
|
37
|
-
.action(createRequestAction("gui.wait-open", ({ options }) => ({ timeout: options.timeout }), ({ options }, context) => withTransportTimeoutBuffer(options.timeout ? Number(options.timeout) : undefined, context.
|
|
37
|
+
.action(createRequestAction("gui.wait-open", ({ options }) => ({ timeout: options.timeout }), ({ options }, context) => withTransportTimeoutBuffer(options.timeout ? Number(options.timeout) : undefined, context.timeout("default"))));
|
|
38
38
|
command
|
|
39
39
|
.command("wait-update")
|
|
40
40
|
.description("Wait for the GUI to update")
|
|
41
41
|
.option("--timeout <seconds>", "Timeout in seconds", Number)
|
|
42
|
-
.action(createRequestAction("gui.wait-update", ({ options }) => ({ timeout: options.timeout }), ({ options }, context) => withTransportTimeoutBuffer(options.timeout ? Number(options.timeout) : undefined, context.
|
|
42
|
+
.action(createRequestAction("gui.wait-update", ({ options }) => ({ timeout: options.timeout }), ({ options }, context) => withTransportTimeoutBuffer(options.timeout ? Number(options.timeout) : undefined, context.timeout("default"))));
|
|
43
43
|
command
|
|
44
44
|
.command("screenshot")
|
|
45
45
|
.description("Take a screenshot of the current GUI")
|
package/dist/commands/input.js
CHANGED
|
@@ -86,7 +86,7 @@ export function createInputCommand() {
|
|
|
86
86
|
.action(createRequestAction("input.key-hold", ({ args, options }) => ({
|
|
87
87
|
key: String(args[0]),
|
|
88
88
|
duration: Number(options.duration)
|
|
89
|
-
}), ({ options }, context) => withTransportTimeoutBuffer(Math.max(Number(options.duration ?? 0) / 1000 + 2, 3), context.
|
|
89
|
+
}), ({ options }, context) => withTransportTimeoutBuffer(Math.max(Number(options.duration ?? 0) / 1000 + 2, 3), context.timeout("default"))));
|
|
90
90
|
keyCommand
|
|
91
91
|
.command("down")
|
|
92
92
|
.description("Press a key down (without releasing)")
|
package/dist/commands/move.js
CHANGED
|
@@ -12,7 +12,7 @@ export function createMoveCommand() {
|
|
|
12
12
|
x: Number(args[0]),
|
|
13
13
|
y: Number(args[1]),
|
|
14
14
|
z: Number(args[2])
|
|
15
|
-
}), (_payload, context) => withTransportTimeoutBuffer(30, context.
|
|
15
|
+
}), (_payload, context) => withTransportTimeoutBuffer(30, context.timeout("default"))));
|
|
16
16
|
const directionLabels = { forward: "Move forward", back: "Move backward", left: "Move left", right: "Move right" };
|
|
17
17
|
for (const direction of ["forward", "back", "left", "right"]) {
|
|
18
18
|
command
|
|
@@ -25,7 +25,7 @@ export function createMoveCommand() {
|
|
|
25
25
|
}), ({ args }, context) => {
|
|
26
26
|
const blocks = Math.abs(Number(args[0]));
|
|
27
27
|
const timeout = Math.max(1.5, blocks * 2.0);
|
|
28
|
-
return withTransportTimeoutBuffer(timeout, context.
|
|
28
|
+
return withTransportTimeoutBuffer(timeout, context.timeout("default"));
|
|
29
29
|
}));
|
|
30
30
|
}
|
|
31
31
|
command
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
export declare function createInitCommand(): Command;
|
|
3
|
+
export declare function createDeployCommand(): Command;
|
|
4
|
+
export declare function createUpCommand(): Command;
|
|
5
|
+
export declare function createDownCommand(): Command;
|
|
6
|
+
export declare function createUseCommand(): Command;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { ServerInstanceManager } from "../instance/ServerInstanceManager.js";
|
|
4
|
+
import { ClientInstanceManager } from "../instance/ClientInstanceManager.js";
|
|
5
|
+
import { MctError } from "../util/errors.js";
|
|
6
|
+
import { wrapCommand } from "../util/command.js";
|
|
7
|
+
import { loadProjectFile, resolveProfile, writeProjectFile } from "../util/project.js";
|
|
8
|
+
export function createInitCommand() {
|
|
9
|
+
return new Command("init")
|
|
10
|
+
.description("Initialize a new MC Pilot project in the current directory")
|
|
11
|
+
.option("--name <name>", "Project name (default: directory name)")
|
|
12
|
+
.action(wrapCommand(async (context, { options }) => {
|
|
13
|
+
const existing = await loadProjectFile(context.cwd);
|
|
14
|
+
if (existing) {
|
|
15
|
+
throw new MctError({ code: "PROJECT_EXISTS", message: "mct.project.json already exists in this directory" }, 4);
|
|
16
|
+
}
|
|
17
|
+
const projectName = options.name ?? path.basename(context.cwd);
|
|
18
|
+
const project = {
|
|
19
|
+
project: projectName,
|
|
20
|
+
profiles: {},
|
|
21
|
+
screenshot: {
|
|
22
|
+
outputDir: "./screenshots"
|
|
23
|
+
},
|
|
24
|
+
timeout: {
|
|
25
|
+
serverReady: 120,
|
|
26
|
+
clientReady: 60,
|
|
27
|
+
default: 10
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
await writeProjectFile(context.cwd, project);
|
|
31
|
+
return {
|
|
32
|
+
created: true,
|
|
33
|
+
project: projectName,
|
|
34
|
+
file: "mct.project.json"
|
|
35
|
+
};
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
38
|
+
export function createDeployCommand() {
|
|
39
|
+
return new Command("deploy")
|
|
40
|
+
.description("Deploy plugin JARs to the server instance")
|
|
41
|
+
.option("--profile <name>", "Profile name")
|
|
42
|
+
.action(wrapCommand(async (context, { options }) => {
|
|
43
|
+
const { projectFile, projectName } = context;
|
|
44
|
+
if (!projectFile || !projectName) {
|
|
45
|
+
throw new MctError({ code: "NO_PROJECT", message: "No project context. Run 'mct init' first." }, 4);
|
|
46
|
+
}
|
|
47
|
+
const profile = resolveProfile(projectFile, options.profile ?? projectFile.defaultProfile);
|
|
48
|
+
if (!profile) {
|
|
49
|
+
throw new MctError({ code: "NO_PROFILE", message: "No profile specified and no defaultProfile set" }, 4);
|
|
50
|
+
}
|
|
51
|
+
if (!profile.deployPlugins || profile.deployPlugins.length === 0) {
|
|
52
|
+
return { deployed: [], message: "No deployPlugins configured in profile" };
|
|
53
|
+
}
|
|
54
|
+
const manager = new ServerInstanceManager(context.globalState, projectName);
|
|
55
|
+
const deployed = await manager.deploy(profile.server, profile.deployPlugins, context.cwd);
|
|
56
|
+
return { deployed, server: profile.server };
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
export function createUpCommand() {
|
|
60
|
+
return new Command("up")
|
|
61
|
+
.description("Deploy plugins, start server and clients, wait for ready")
|
|
62
|
+
.option("--profile <name>", "Profile name")
|
|
63
|
+
.option("--eula", "Auto-accept EULA")
|
|
64
|
+
.action(wrapCommand(async (context, { options }) => {
|
|
65
|
+
const { projectFile, projectName } = context;
|
|
66
|
+
if (!projectFile || !projectName) {
|
|
67
|
+
throw new MctError({ code: "NO_PROJECT", message: "No project context. Run 'mct init' first." }, 4);
|
|
68
|
+
}
|
|
69
|
+
const profile = resolveProfile(projectFile, options.profile ?? projectFile.defaultProfile);
|
|
70
|
+
if (!profile) {
|
|
71
|
+
throw new MctError({ code: "NO_PROFILE", message: "No profile specified and no defaultProfile set" }, 4);
|
|
72
|
+
}
|
|
73
|
+
const serverManager = new ServerInstanceManager(context.globalState, projectName);
|
|
74
|
+
const clientManager = new ClientInstanceManager(context.globalState);
|
|
75
|
+
const results = {};
|
|
76
|
+
// 1. Deploy plugins
|
|
77
|
+
if (profile.deployPlugins && profile.deployPlugins.length > 0) {
|
|
78
|
+
results.deployed = await serverManager.deploy(profile.server, profile.deployPlugins, context.cwd);
|
|
79
|
+
}
|
|
80
|
+
// 2. Start server
|
|
81
|
+
results.server = await serverManager.start(profile.server, { eula: options.eula });
|
|
82
|
+
// 3. Wait for server
|
|
83
|
+
const serverMeta = await serverManager.loadMeta(profile.server);
|
|
84
|
+
await serverManager.waitReady(profile.server, context.timeout("serverReady"));
|
|
85
|
+
// 4. Launch clients
|
|
86
|
+
const clientResults = [];
|
|
87
|
+
for (const clientName of profile.clients) {
|
|
88
|
+
const result = await clientManager.launch(clientName, {
|
|
89
|
+
server: `localhost:${serverMeta.port}`
|
|
90
|
+
});
|
|
91
|
+
clientResults.push(result);
|
|
92
|
+
}
|
|
93
|
+
results.clients = clientResults;
|
|
94
|
+
// 5. Wait for clients
|
|
95
|
+
for (const clientName of profile.clients) {
|
|
96
|
+
await clientManager.waitReady(clientName, context.timeout("clientReady"));
|
|
97
|
+
}
|
|
98
|
+
results.ready = true;
|
|
99
|
+
return results;
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
102
|
+
export function createDownCommand() {
|
|
103
|
+
return new Command("down")
|
|
104
|
+
.description("Stop server and clients for the active profile")
|
|
105
|
+
.option("--profile <name>", "Profile name")
|
|
106
|
+
.action(wrapCommand(async (context, { options }) => {
|
|
107
|
+
const { projectFile, projectName } = context;
|
|
108
|
+
if (!projectFile || !projectName) {
|
|
109
|
+
throw new MctError({ code: "NO_PROJECT", message: "No project context. Run 'mct init' first." }, 4);
|
|
110
|
+
}
|
|
111
|
+
const profile = resolveProfile(projectFile, options.profile ?? projectFile.defaultProfile);
|
|
112
|
+
if (!profile) {
|
|
113
|
+
throw new MctError({ code: "NO_PROFILE", message: "No profile specified and no defaultProfile set" }, 4);
|
|
114
|
+
}
|
|
115
|
+
const serverManager = new ServerInstanceManager(context.globalState, projectName);
|
|
116
|
+
const clientManager = new ClientInstanceManager(context.globalState);
|
|
117
|
+
const results = {};
|
|
118
|
+
// Stop clients first
|
|
119
|
+
const clientResults = [];
|
|
120
|
+
for (const clientName of profile.clients) {
|
|
121
|
+
const result = await clientManager.stop(clientName);
|
|
122
|
+
clientResults.push(result);
|
|
123
|
+
}
|
|
124
|
+
results.clients = clientResults;
|
|
125
|
+
// Stop server
|
|
126
|
+
results.server = await serverManager.stop(profile.server);
|
|
127
|
+
return results;
|
|
128
|
+
}));
|
|
129
|
+
}
|
|
130
|
+
export function createUseCommand() {
|
|
131
|
+
return new Command("use")
|
|
132
|
+
.description("Set the default profile")
|
|
133
|
+
.argument("<profile>", "Profile name to set as default")
|
|
134
|
+
.action(wrapCommand(async (context, { args }) => {
|
|
135
|
+
const { projectFile } = context;
|
|
136
|
+
if (!projectFile) {
|
|
137
|
+
throw new MctError({ code: "NO_PROJECT", message: "No project context. Run 'mct init' first." }, 4);
|
|
138
|
+
}
|
|
139
|
+
const profileName = args[0];
|
|
140
|
+
if (!projectFile.profiles[profileName]) {
|
|
141
|
+
const available = Object.keys(projectFile.profiles);
|
|
142
|
+
throw new MctError({
|
|
143
|
+
code: "PROFILE_NOT_FOUND",
|
|
144
|
+
message: `Profile '${profileName}' not found`,
|
|
145
|
+
details: { available }
|
|
146
|
+
}, 4);
|
|
147
|
+
}
|
|
148
|
+
projectFile.defaultProfile = profileName;
|
|
149
|
+
await writeProjectFile(context.cwd, projectFile);
|
|
150
|
+
return {
|
|
151
|
+
defaultProfile: profileName,
|
|
152
|
+
profile: projectFile.profiles[profileName]
|
|
153
|
+
};
|
|
154
|
+
}));
|
|
155
|
+
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import {
|
|
2
|
+
import { ClientInstanceManager } from "../instance/ClientInstanceManager.js";
|
|
3
3
|
import { WebSocketClient } from "../client/WebSocketClient.js";
|
|
4
4
|
import { MctError } from "../util/errors.js";
|
|
5
5
|
import { wrapCommand } from "../util/command.js";
|
|
6
6
|
export async function sendClientRequest(context, clientName, action, params, timeoutSeconds) {
|
|
7
|
-
const manager = new
|
|
7
|
+
const manager = new ClientInstanceManager(context.globalState);
|
|
8
8
|
const client = await manager.getClient(clientName);
|
|
9
|
-
const ws = new WebSocketClient(
|
|
10
|
-
return ws.send(action, params, timeoutSeconds ?? context.
|
|
9
|
+
const ws = new WebSocketClient(`ws://127.0.0.1:${client.wsPort}`);
|
|
10
|
+
return ws.send(action, params, timeoutSeconds ?? context.timeout("default"));
|
|
11
11
|
}
|
|
12
12
|
export function createRequestAction(action, buildParams, timeoutSelector) {
|
|
13
13
|
return wrapCommand(async (context, payload) => {
|
package/dist/commands/server.js
CHANGED
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { buildServerSearchResults } from "../download/SearchCommand.js";
|
|
3
|
-
import {
|
|
3
|
+
import { downloadServerJarToCache } from "../download/server/ServerDownloader.js";
|
|
4
|
+
import { ServerInstanceManager } from "../instance/ServerInstanceManager.js";
|
|
5
|
+
import { MctError } from "../util/errors.js";
|
|
4
6
|
import { wrapCommand } from "../util/command.js";
|
|
5
|
-
|
|
7
|
+
function requireProject(context) {
|
|
8
|
+
if (!context.projectName) {
|
|
9
|
+
throw new MctError({ code: "NO_PROJECT", message: "No project context. Run 'mct init' first or use --project <name>" }, 4);
|
|
10
|
+
}
|
|
11
|
+
return context.projectName;
|
|
12
|
+
}
|
|
13
|
+
function resolveServerName(context, explicit) {
|
|
14
|
+
if (explicit)
|
|
15
|
+
return explicit;
|
|
16
|
+
if (context.activeProfile?.server)
|
|
17
|
+
return context.activeProfile.server;
|
|
18
|
+
throw new MctError({ code: "INVALID_PARAMS", message: "Server name is required. Specify it as argument or set a profile." }, 4);
|
|
19
|
+
}
|
|
6
20
|
export function createServerCommand() {
|
|
7
|
-
const command = new Command("server").description("Manage Minecraft server");
|
|
21
|
+
const command = new Command("server").description("Manage Minecraft server instances");
|
|
8
22
|
command
|
|
9
23
|
.command("search")
|
|
10
24
|
.description("Search available server versions")
|
|
@@ -19,48 +33,88 @@ export function createServerCommand() {
|
|
|
19
33
|
};
|
|
20
34
|
}));
|
|
21
35
|
command
|
|
22
|
-
.command("
|
|
23
|
-
.description("
|
|
24
|
-
.
|
|
25
|
-
.option("--
|
|
36
|
+
.command("create")
|
|
37
|
+
.description("Create a new server instance")
|
|
38
|
+
.argument("<name>", "Server instance name (e.g. paper-1.20.4)")
|
|
39
|
+
.option("--type <type>", "Server type: vanilla|paper|purpur|spigot (default: paper)")
|
|
40
|
+
.option("--version <version>", "Minecraft version (default: 1.21.4)")
|
|
26
41
|
.option("--build <build>", "Specific build number")
|
|
27
|
-
.option("--
|
|
28
|
-
.option("--
|
|
29
|
-
.
|
|
30
|
-
|
|
42
|
+
.option("--port <number>", "Server port (auto-assigned if omitted)", Number)
|
|
43
|
+
.option("--jvm-args <args>", "JVM arguments (comma-separated)")
|
|
44
|
+
.option("--eula", "Auto-accept EULA")
|
|
45
|
+
.action(wrapCommand(async (context, { args, options }) => {
|
|
46
|
+
const project = requireProject(context);
|
|
47
|
+
const serverType = (options.type ?? "paper");
|
|
48
|
+
const version = options.version ?? "1.21.4";
|
|
49
|
+
const downloadResult = await downloadServerJarToCache({ type: serverType, version, build: options.build });
|
|
50
|
+
const manager = new ServerInstanceManager(context.globalState, project);
|
|
51
|
+
return manager.create({
|
|
52
|
+
name: args[0],
|
|
53
|
+
project,
|
|
54
|
+
type: serverType,
|
|
55
|
+
version,
|
|
56
|
+
port: options.port,
|
|
57
|
+
jvmArgs: options.jvmArgs?.split(",").map(a => a.trim()) ?? [],
|
|
58
|
+
eula: options.eula,
|
|
59
|
+
cachedJarPath: downloadResult.cachePath
|
|
60
|
+
});
|
|
31
61
|
}));
|
|
32
62
|
command
|
|
33
63
|
.command("start")
|
|
34
|
-
.description("Start
|
|
35
|
-
.
|
|
36
|
-
.option("--dir <path>", "Server directory (default: from config server.dir)")
|
|
37
|
-
.option("--port <number>", "Server port (default: 25565)", Number)
|
|
64
|
+
.description("Start a server instance")
|
|
65
|
+
.argument("[name]", "Server instance name (default: from active profile)")
|
|
38
66
|
.option("--eula", "Auto-accept EULA")
|
|
39
|
-
.
|
|
40
|
-
|
|
41
|
-
|
|
67
|
+
.option("--jvm-args <args>", "Override JVM arguments (comma-separated)")
|
|
68
|
+
.action(wrapCommand(async (context, { args, options }) => {
|
|
69
|
+
const project = requireProject(context);
|
|
70
|
+
const serverName = resolveServerName(context, args[0]);
|
|
71
|
+
const manager = new ServerInstanceManager(context.globalState, project);
|
|
72
|
+
return manager.start(serverName, {
|
|
73
|
+
eula: options.eula,
|
|
74
|
+
jvmArgs: options.jvmArgs?.split(",").map(a => a.trim())
|
|
75
|
+
});
|
|
42
76
|
}));
|
|
43
77
|
command
|
|
44
78
|
.command("stop")
|
|
45
|
-
.description("Stop
|
|
46
|
-
.
|
|
47
|
-
|
|
48
|
-
|
|
79
|
+
.description("Stop a server instance")
|
|
80
|
+
.argument("[name]", "Server instance name (default: from active profile)")
|
|
81
|
+
.action(wrapCommand(async (context, { args }) => {
|
|
82
|
+
const project = requireProject(context);
|
|
83
|
+
const serverName = resolveServerName(context, args[0]);
|
|
84
|
+
const manager = new ServerInstanceManager(context.globalState, project);
|
|
85
|
+
return manager.stop(serverName);
|
|
49
86
|
}));
|
|
50
87
|
command
|
|
51
88
|
.command("status")
|
|
52
89
|
.description("Show server status")
|
|
53
|
-
.
|
|
54
|
-
|
|
55
|
-
|
|
90
|
+
.argument("[name]", "Server instance name (omit to show all in project)")
|
|
91
|
+
.action(wrapCommand(async (context, { args }) => {
|
|
92
|
+
const project = requireProject(context);
|
|
93
|
+
const manager = new ServerInstanceManager(context.globalState, project);
|
|
94
|
+
return manager.status(args[0]);
|
|
95
|
+
}));
|
|
96
|
+
command
|
|
97
|
+
.command("list")
|
|
98
|
+
.description("List server instances")
|
|
99
|
+
.option("--all", "List instances across all projects")
|
|
100
|
+
.action(wrapCommand(async (context, { options }) => {
|
|
101
|
+
if (options.all) {
|
|
102
|
+
return { instances: await ServerInstanceManager.listAll(context.globalState) };
|
|
103
|
+
}
|
|
104
|
+
const project = requireProject(context);
|
|
105
|
+
const manager = new ServerInstanceManager(context.globalState, project);
|
|
106
|
+
return { instances: await manager.list() };
|
|
56
107
|
}));
|
|
57
108
|
command
|
|
58
109
|
.command("wait-ready")
|
|
59
110
|
.description("Wait until server port is connectable")
|
|
111
|
+
.argument("[name]", "Server instance name (default: from active profile)")
|
|
60
112
|
.option("--timeout <seconds>", "Timeout in seconds", Number)
|
|
61
|
-
.action(wrapCommand(async (context, { options }) => {
|
|
62
|
-
const
|
|
63
|
-
|
|
113
|
+
.action(wrapCommand(async (context, { args, options }) => {
|
|
114
|
+
const project = requireProject(context);
|
|
115
|
+
const serverName = resolveServerName(context, args[0]);
|
|
116
|
+
const manager = new ServerInstanceManager(context.globalState, project);
|
|
117
|
+
return manager.waitReady(serverName, options.timeout ?? context.timeout("serverReady"));
|
|
64
118
|
}));
|
|
65
119
|
return command;
|
|
66
120
|
}
|
package/dist/commands/wait.js
CHANGED
|
@@ -17,7 +17,7 @@ export function createWaitCommand() {
|
|
|
17
17
|
untilOnGround: Boolean(options.untilOnGround),
|
|
18
18
|
timeout: options.timeout
|
|
19
19
|
}), ({ options, args }, context) => {
|
|
20
|
-
const requested = options.timeout ? Number(options.timeout) : args[0] ? Number(args[0]) : context.
|
|
21
|
-
return withTransportTimeoutBuffer(requested, context.
|
|
20
|
+
const requested = options.timeout ? Number(options.timeout) : args[0] ? Number(args[0]) : context.timeout("default");
|
|
21
|
+
return withTransportTimeoutBuffer(requested, context.timeout("default"));
|
|
22
22
|
}));
|
|
23
23
|
}
|
|
@@ -46,8 +46,8 @@ export declare function getVersionMatrix(): {
|
|
|
46
46
|
servers: {
|
|
47
47
|
paper: ServerSupportInfo;
|
|
48
48
|
purpur: ServerSupportInfo;
|
|
49
|
-
spigot: ServerSupportInfo;
|
|
50
49
|
vanilla: ServerSupportInfo;
|
|
50
|
+
spigot: ServerSupportInfo;
|
|
51
51
|
};
|
|
52
52
|
clients: {
|
|
53
53
|
fabric: ClientLoaderSupportInfo;
|