@kzheart_/mc-pilot 0.1.6 → 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/client/FabricRuntimeDownloader.js +17 -5
- 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
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { GlobalStateStore } from "../util/global-state.js";
|
|
2
|
+
import type { ServerInstanceMeta, ServerRuntimeEntry, ServerType } from "../util/instance-types.js";
|
|
3
|
+
export interface CreateServerOptions {
|
|
4
|
+
name: string;
|
|
5
|
+
project: string;
|
|
6
|
+
type: ServerType;
|
|
7
|
+
version: string;
|
|
8
|
+
port?: number;
|
|
9
|
+
jvmArgs?: string[];
|
|
10
|
+
eula?: boolean;
|
|
11
|
+
cachedJarPath?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface StartServerOptions {
|
|
14
|
+
eula?: boolean;
|
|
15
|
+
jvmArgs?: string[];
|
|
16
|
+
}
|
|
17
|
+
export declare class ServerInstanceManager {
|
|
18
|
+
private readonly globalState;
|
|
19
|
+
private readonly project;
|
|
20
|
+
constructor(globalState: GlobalStateStore, project: string);
|
|
21
|
+
create(options: CreateServerOptions): Promise<ServerInstanceMeta>;
|
|
22
|
+
start(serverName: string, options?: StartServerOptions): Promise<ServerRuntimeEntry & {
|
|
23
|
+
running: true;
|
|
24
|
+
}>;
|
|
25
|
+
stop(serverName: string): Promise<{
|
|
26
|
+
running: boolean;
|
|
27
|
+
stopped: boolean;
|
|
28
|
+
pid?: undefined;
|
|
29
|
+
} | {
|
|
30
|
+
running: boolean;
|
|
31
|
+
stopped: boolean;
|
|
32
|
+
pid: number;
|
|
33
|
+
}>;
|
|
34
|
+
status(serverName?: string): Promise<{
|
|
35
|
+
[key: string]: unknown;
|
|
36
|
+
running: boolean;
|
|
37
|
+
}[] | {
|
|
38
|
+
running: boolean;
|
|
39
|
+
} | {
|
|
40
|
+
pid: number;
|
|
41
|
+
project: string;
|
|
42
|
+
name: string;
|
|
43
|
+
port: number;
|
|
44
|
+
startedAt: string;
|
|
45
|
+
logPath: string;
|
|
46
|
+
instanceDir: string;
|
|
47
|
+
running: boolean;
|
|
48
|
+
stale: boolean;
|
|
49
|
+
} | {
|
|
50
|
+
pid: number;
|
|
51
|
+
project: string;
|
|
52
|
+
name: string;
|
|
53
|
+
port: number;
|
|
54
|
+
startedAt: string;
|
|
55
|
+
logPath: string;
|
|
56
|
+
instanceDir: string;
|
|
57
|
+
running: boolean;
|
|
58
|
+
}>;
|
|
59
|
+
waitReady(serverName: string, timeoutSeconds: number): Promise<{
|
|
60
|
+
reachable: boolean;
|
|
61
|
+
host: string;
|
|
62
|
+
port: number;
|
|
63
|
+
}>;
|
|
64
|
+
list(): Promise<ServerInstanceMeta[]>;
|
|
65
|
+
static listAll(globalState: GlobalStateStore): Promise<ServerInstanceMeta[]>;
|
|
66
|
+
deploy(serverName: string, jarPaths: string[], cwd: string): Promise<string[]>;
|
|
67
|
+
loadMeta(serverName: string): Promise<ServerInstanceMeta>;
|
|
68
|
+
private findJarFile;
|
|
69
|
+
private findAvailablePort;
|
|
70
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { copyFile, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { mkdirSync, openSync } from "node:fs";
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { resolveProjectDir, resolveServerInstanceDir } from "../util/paths.js";
|
|
6
|
+
import { MctError } from "../util/errors.js";
|
|
7
|
+
import { waitForTcpPort } from "../util/net.js";
|
|
8
|
+
import { isProcessRunning, killProcessTree } from "../util/process.js";
|
|
9
|
+
import { copyFileIfMissing } from "../download/DownloadUtils.js";
|
|
10
|
+
const INSTANCE_FILE = "instance.json";
|
|
11
|
+
export class ServerInstanceManager {
|
|
12
|
+
globalState;
|
|
13
|
+
project;
|
|
14
|
+
constructor(globalState, project) {
|
|
15
|
+
this.globalState = globalState;
|
|
16
|
+
this.project = project;
|
|
17
|
+
}
|
|
18
|
+
async create(options) {
|
|
19
|
+
const instanceDir = resolveServerInstanceDir(options.project, options.name);
|
|
20
|
+
await mkdir(instanceDir, { recursive: true });
|
|
21
|
+
const port = options.port ?? (await this.findAvailablePort());
|
|
22
|
+
const jarPath = options.cachedJarPath;
|
|
23
|
+
if (jarPath) {
|
|
24
|
+
const targetJar = path.join(instanceDir, path.basename(jarPath));
|
|
25
|
+
await copyFileIfMissing(jarPath, targetJar);
|
|
26
|
+
}
|
|
27
|
+
if (options.eula) {
|
|
28
|
+
await writeFile(path.join(instanceDir, "eula.txt"), "eula=true\n", "utf8");
|
|
29
|
+
}
|
|
30
|
+
await mkdir(path.join(instanceDir, "plugins"), { recursive: true });
|
|
31
|
+
const meta = {
|
|
32
|
+
name: options.name,
|
|
33
|
+
project: options.project,
|
|
34
|
+
type: options.type,
|
|
35
|
+
mcVersion: options.version,
|
|
36
|
+
port,
|
|
37
|
+
jvmArgs: options.jvmArgs ?? [],
|
|
38
|
+
createdAt: new Date().toISOString()
|
|
39
|
+
};
|
|
40
|
+
await writeFile(path.join(instanceDir, INSTANCE_FILE), `${JSON.stringify(meta, null, 2)}\n`, "utf8");
|
|
41
|
+
return meta;
|
|
42
|
+
}
|
|
43
|
+
async start(serverName, options = {}) {
|
|
44
|
+
const stateKey = `${this.project}/${serverName}`;
|
|
45
|
+
const state = await this.globalState.readServerState();
|
|
46
|
+
const existing = state.servers[stateKey];
|
|
47
|
+
if (existing && isProcessRunning(existing.pid)) {
|
|
48
|
+
throw new MctError({ code: "SERVER_ALREADY_RUNNING", message: `Server ${stateKey} is already running`, details: existing }, 5);
|
|
49
|
+
}
|
|
50
|
+
const meta = await this.loadMeta(serverName);
|
|
51
|
+
const instanceDir = resolveServerInstanceDir(this.project, serverName);
|
|
52
|
+
const jarFile = await this.findJarFile(instanceDir);
|
|
53
|
+
if (!jarFile) {
|
|
54
|
+
throw new MctError({ code: "INVALID_PARAMS", message: `No server jar found in ${instanceDir}` }, 4);
|
|
55
|
+
}
|
|
56
|
+
if (options.eula) {
|
|
57
|
+
await writeFile(path.join(instanceDir, "eula.txt"), "eula=true\n", "utf8");
|
|
58
|
+
}
|
|
59
|
+
const logsDir = path.join(this.globalState.getRootDir(), "logs");
|
|
60
|
+
mkdirSync(logsDir, { recursive: true });
|
|
61
|
+
const logPath = path.join(logsDir, `server-${this.project}-${serverName}.log`);
|
|
62
|
+
const stdout = openSync(logPath, "a");
|
|
63
|
+
const jvmArgs = options.jvmArgs ?? meta.jvmArgs;
|
|
64
|
+
const child = spawn("java", [...jvmArgs, "-jar", jarFile, "nogui"], {
|
|
65
|
+
cwd: instanceDir,
|
|
66
|
+
detached: true,
|
|
67
|
+
stdio: ["ignore", stdout, stdout],
|
|
68
|
+
env: {
|
|
69
|
+
...process.env,
|
|
70
|
+
MCT_SERVER_PORT: String(meta.port)
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
child.unref();
|
|
74
|
+
const entry = {
|
|
75
|
+
pid: child.pid ?? 0,
|
|
76
|
+
project: this.project,
|
|
77
|
+
name: serverName,
|
|
78
|
+
port: meta.port,
|
|
79
|
+
startedAt: new Date().toISOString(),
|
|
80
|
+
logPath,
|
|
81
|
+
instanceDir
|
|
82
|
+
};
|
|
83
|
+
state.servers[stateKey] = entry;
|
|
84
|
+
await this.globalState.writeServerState(state);
|
|
85
|
+
return { running: true, ...entry };
|
|
86
|
+
}
|
|
87
|
+
async stop(serverName) {
|
|
88
|
+
const stateKey = `${this.project}/${serverName}`;
|
|
89
|
+
const state = await this.globalState.readServerState();
|
|
90
|
+
const entry = state.servers[stateKey];
|
|
91
|
+
if (!entry) {
|
|
92
|
+
return { running: false, stopped: false };
|
|
93
|
+
}
|
|
94
|
+
if (isProcessRunning(entry.pid)) {
|
|
95
|
+
killProcessTree(entry.pid);
|
|
96
|
+
}
|
|
97
|
+
delete state.servers[stateKey];
|
|
98
|
+
await this.globalState.writeServerState(state);
|
|
99
|
+
return { running: false, stopped: true, pid: entry.pid };
|
|
100
|
+
}
|
|
101
|
+
async status(serverName) {
|
|
102
|
+
const state = await this.globalState.readServerState();
|
|
103
|
+
if (serverName) {
|
|
104
|
+
const stateKey = `${this.project}/${serverName}`;
|
|
105
|
+
const entry = state.servers[stateKey];
|
|
106
|
+
if (!entry) {
|
|
107
|
+
return { running: false };
|
|
108
|
+
}
|
|
109
|
+
const running = isProcessRunning(entry.pid);
|
|
110
|
+
if (!running) {
|
|
111
|
+
delete state.servers[stateKey];
|
|
112
|
+
await this.globalState.writeServerState(state);
|
|
113
|
+
return { running: false, stale: true, ...entry };
|
|
114
|
+
}
|
|
115
|
+
return { running: true, ...entry };
|
|
116
|
+
}
|
|
117
|
+
const results = [];
|
|
118
|
+
for (const [key, entry] of Object.entries(state.servers)) {
|
|
119
|
+
if (entry.project === this.project) {
|
|
120
|
+
const running = isProcessRunning(entry.pid);
|
|
121
|
+
if (!running) {
|
|
122
|
+
delete state.servers[key];
|
|
123
|
+
}
|
|
124
|
+
results.push({ running, ...entry });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
await this.globalState.writeServerState(state);
|
|
128
|
+
return results;
|
|
129
|
+
}
|
|
130
|
+
async waitReady(serverName, timeoutSeconds) {
|
|
131
|
+
const stateKey = `${this.project}/${serverName}`;
|
|
132
|
+
const state = await this.globalState.readServerState();
|
|
133
|
+
const entry = state.servers[stateKey];
|
|
134
|
+
if (!entry) {
|
|
135
|
+
throw new MctError({ code: "SERVER_NOT_RUNNING", message: `Server ${stateKey} is not running` }, 5);
|
|
136
|
+
}
|
|
137
|
+
return waitForTcpPort("127.0.0.1", entry.port, timeoutSeconds);
|
|
138
|
+
}
|
|
139
|
+
async list() {
|
|
140
|
+
const projectDir = resolveProjectDir(this.project);
|
|
141
|
+
try {
|
|
142
|
+
const entries = await readdir(projectDir, { withFileTypes: true });
|
|
143
|
+
const results = [];
|
|
144
|
+
for (const entry of entries) {
|
|
145
|
+
if (entry.isDirectory()) {
|
|
146
|
+
try {
|
|
147
|
+
const meta = await this.loadMeta(entry.name);
|
|
148
|
+
results.push(meta);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// not a valid instance
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return results;
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return [];
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
static async listAll(globalState) {
|
|
162
|
+
const { resolveProjectsDir } = await import("../util/paths.js");
|
|
163
|
+
const projectsDir = resolveProjectsDir();
|
|
164
|
+
const results = [];
|
|
165
|
+
try {
|
|
166
|
+
const projects = await readdir(projectsDir, { withFileTypes: true });
|
|
167
|
+
for (const project of projects) {
|
|
168
|
+
if (project.isDirectory()) {
|
|
169
|
+
const manager = new ServerInstanceManager(globalState, project.name);
|
|
170
|
+
const instances = await manager.list();
|
|
171
|
+
results.push(...instances);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
// projects dir doesn't exist yet
|
|
177
|
+
}
|
|
178
|
+
return results;
|
|
179
|
+
}
|
|
180
|
+
async deploy(serverName, jarPaths, cwd) {
|
|
181
|
+
const instanceDir = resolveServerInstanceDir(this.project, serverName);
|
|
182
|
+
const pluginsDir = path.join(instanceDir, "plugins");
|
|
183
|
+
await mkdir(pluginsDir, { recursive: true });
|
|
184
|
+
const deployed = [];
|
|
185
|
+
for (const jarPath of jarPaths) {
|
|
186
|
+
const resolved = path.resolve(cwd, jarPath);
|
|
187
|
+
const target = path.join(pluginsDir, path.basename(resolved));
|
|
188
|
+
await copyFile(resolved, target);
|
|
189
|
+
deployed.push(target);
|
|
190
|
+
}
|
|
191
|
+
return deployed;
|
|
192
|
+
}
|
|
193
|
+
async loadMeta(serverName) {
|
|
194
|
+
const instanceDir = resolveServerInstanceDir(this.project, serverName);
|
|
195
|
+
const metaPath = path.join(instanceDir, INSTANCE_FILE);
|
|
196
|
+
try {
|
|
197
|
+
const raw = await readFile(metaPath, "utf8");
|
|
198
|
+
return JSON.parse(raw);
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
throw new MctError({ code: "INSTANCE_NOT_FOUND", message: `Server instance ${this.project}/${serverName} not found` }, 3);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
async findJarFile(instanceDir) {
|
|
205
|
+
try {
|
|
206
|
+
const entries = await readdir(instanceDir);
|
|
207
|
+
const jar = entries.find((e) => e.endsWith(".jar"));
|
|
208
|
+
return jar ? path.join(instanceDir, jar) : null;
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
async findAvailablePort() {
|
|
215
|
+
const state = await this.globalState.readServerState();
|
|
216
|
+
const usedPorts = new Set(Object.values(state.servers).map((s) => s.port));
|
|
217
|
+
let port = 25565;
|
|
218
|
+
while (usedPorts.has(port)) {
|
|
219
|
+
port += 1;
|
|
220
|
+
}
|
|
221
|
+
return port;
|
|
222
|
+
}
|
|
223
|
+
}
|
package/dist/util/command.js
CHANGED
|
@@ -5,8 +5,8 @@ import { printError, printSuccess } from "./output.js";
|
|
|
5
5
|
export function attachGlobalOptions(command) {
|
|
6
6
|
return command
|
|
7
7
|
.option("--human", "Human-readable output (default: JSON)")
|
|
8
|
-
.option("--
|
|
9
|
-
.option("--
|
|
8
|
+
.option("--project <name>", "Project name (default: from mct.project.json)")
|
|
9
|
+
.option("--profile <name>", "Profile name (default: from mct.project.json)")
|
|
10
10
|
.option("--client <name>", "Target client name (required when multiple clients are running)");
|
|
11
11
|
}
|
|
12
12
|
export function wrapCommand(action) {
|
package/dist/util/context.d.ts
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { GlobalStateStore } from "./global-state.js";
|
|
2
|
+
import type { OutputMode } from "./output.js";
|
|
3
|
+
import { type MctProfile, type MctProjectFile } from "./project.js";
|
|
4
4
|
export interface GlobalOptions {
|
|
5
5
|
human?: boolean;
|
|
6
|
-
config?: string;
|
|
7
|
-
stateDir?: string;
|
|
8
6
|
client?: string;
|
|
7
|
+
project?: string;
|
|
8
|
+
profile?: string;
|
|
9
9
|
}
|
|
10
10
|
export interface CommandContext {
|
|
11
11
|
cwd: string;
|
|
12
|
-
configPath: string;
|
|
13
|
-
config: MctConfig;
|
|
14
|
-
state: StateStore;
|
|
15
12
|
outputMode: OutputMode;
|
|
13
|
+
globalState: GlobalStateStore;
|
|
14
|
+
projectFile: MctProjectFile | null;
|
|
15
|
+
activeProfile: MctProfile | null;
|
|
16
|
+
projectName: string | null;
|
|
17
|
+
timeout(key: "serverReady" | "clientReady" | "default"): number;
|
|
16
18
|
}
|
|
17
19
|
export declare function createCommandContext(options: GlobalOptions): Promise<CommandContext>;
|
package/dist/util/context.js
CHANGED
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
import process from "node:process";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { GlobalStateStore } from "./global-state.js";
|
|
3
|
+
import { loadProjectFile, resolveProfile } from "./project.js";
|
|
4
|
+
const TIMEOUT_DEFAULTS = {
|
|
5
|
+
serverReady: 120,
|
|
6
|
+
clientReady: 60,
|
|
7
|
+
default: 10
|
|
8
|
+
};
|
|
4
9
|
export async function createCommandContext(options) {
|
|
5
10
|
const cwd = process.cwd();
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
11
|
+
const globalState = new GlobalStateStore();
|
|
12
|
+
const projectFile = await loadProjectFile(cwd);
|
|
13
|
+
const projectName = options.project ?? projectFile?.project ?? null;
|
|
14
|
+
const activeProfile = projectFile
|
|
15
|
+
? resolveProfile(projectFile, options.profile)
|
|
16
|
+
: null;
|
|
9
17
|
return {
|
|
10
18
|
cwd,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
19
|
+
outputMode: options.human ? "human" : "json",
|
|
20
|
+
globalState,
|
|
21
|
+
projectFile,
|
|
22
|
+
activeProfile,
|
|
23
|
+
projectName,
|
|
24
|
+
timeout(key) {
|
|
25
|
+
return projectFile?.timeout?.[key] ?? TIMEOUT_DEFAULTS[key];
|
|
26
|
+
}
|
|
15
27
|
};
|
|
16
28
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { StateStore } from "./state.js";
|
|
2
|
+
import type { GlobalClientState, GlobalServerState } from "./instance-types.js";
|
|
3
|
+
export declare class GlobalStateStore extends StateStore {
|
|
4
|
+
constructor();
|
|
5
|
+
readServerState(): Promise<GlobalServerState>;
|
|
6
|
+
writeServerState(state: GlobalServerState): Promise<void>;
|
|
7
|
+
readClientState(): Promise<GlobalClientState>;
|
|
8
|
+
writeClientState(state: GlobalClientState): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { resolveGlobalStateDir } from "./paths.js";
|
|
2
|
+
import { StateStore } from "./state.js";
|
|
3
|
+
const SERVERS_STATE_FILE = "servers.json";
|
|
4
|
+
const CLIENTS_STATE_FILE = "clients.json";
|
|
5
|
+
export class GlobalStateStore extends StateStore {
|
|
6
|
+
constructor() {
|
|
7
|
+
super(resolveGlobalStateDir());
|
|
8
|
+
}
|
|
9
|
+
async readServerState() {
|
|
10
|
+
return this.readJson(SERVERS_STATE_FILE, { servers: {} });
|
|
11
|
+
}
|
|
12
|
+
async writeServerState(state) {
|
|
13
|
+
await this.writeJson(SERVERS_STATE_FILE, state);
|
|
14
|
+
}
|
|
15
|
+
async readClientState() {
|
|
16
|
+
return this.readJson(CLIENTS_STATE_FILE, { clients: {} });
|
|
17
|
+
}
|
|
18
|
+
async writeClientState(state) {
|
|
19
|
+
await this.writeJson(CLIENTS_STATE_FILE, state);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export type ServerType = "paper" | "purpur" | "vanilla" | "spigot";
|
|
2
|
+
export type LoaderType = "fabric" | "forge" | "neoforge";
|
|
3
|
+
export interface ServerInstanceMeta {
|
|
4
|
+
name: string;
|
|
5
|
+
project: string;
|
|
6
|
+
type: ServerType;
|
|
7
|
+
mcVersion: string;
|
|
8
|
+
port: number;
|
|
9
|
+
jvmArgs: string[];
|
|
10
|
+
createdAt: string;
|
|
11
|
+
}
|
|
12
|
+
export interface ClientInstanceMeta {
|
|
13
|
+
name: string;
|
|
14
|
+
loader: LoaderType;
|
|
15
|
+
mcVersion: string;
|
|
16
|
+
wsPort: number;
|
|
17
|
+
account?: string;
|
|
18
|
+
headless?: boolean;
|
|
19
|
+
launchArgs?: string[];
|
|
20
|
+
env?: Record<string, string>;
|
|
21
|
+
createdAt: string;
|
|
22
|
+
}
|
|
23
|
+
export interface ServerRuntimeEntry {
|
|
24
|
+
pid: number;
|
|
25
|
+
project: string;
|
|
26
|
+
name: string;
|
|
27
|
+
port: number;
|
|
28
|
+
startedAt: string;
|
|
29
|
+
logPath: string;
|
|
30
|
+
instanceDir: string;
|
|
31
|
+
}
|
|
32
|
+
export interface ClientRuntimeEntry {
|
|
33
|
+
pid: number;
|
|
34
|
+
name: string;
|
|
35
|
+
wsPort: number;
|
|
36
|
+
startedAt: string;
|
|
37
|
+
logPath: string;
|
|
38
|
+
instanceDir: string;
|
|
39
|
+
}
|
|
40
|
+
export interface GlobalServerState {
|
|
41
|
+
servers: Record<string, ServerRuntimeEntry>;
|
|
42
|
+
}
|
|
43
|
+
export interface GlobalClientState {
|
|
44
|
+
defaultClient?: string;
|
|
45
|
+
clients: Record<string, ClientRuntimeEntry>;
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function resolveMctHome(): string;
|
|
2
|
+
export declare function resolveClientsDir(): string;
|
|
3
|
+
export declare function resolveClientInstanceDir(name: string): string;
|
|
4
|
+
export declare function resolveProjectsDir(): string;
|
|
5
|
+
export declare function resolveProjectDir(project: string): string;
|
|
6
|
+
export declare function resolveServerInstanceDir(project: string, server: string): string;
|
|
7
|
+
export declare function resolveGlobalStateDir(): string;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export function resolveMctHome() {
|
|
4
|
+
return process.env.MCT_HOME || path.join(os.homedir(), ".mct");
|
|
5
|
+
}
|
|
6
|
+
export function resolveClientsDir() {
|
|
7
|
+
return path.join(resolveMctHome(), "clients");
|
|
8
|
+
}
|
|
9
|
+
export function resolveClientInstanceDir(name) {
|
|
10
|
+
return path.join(resolveClientsDir(), name);
|
|
11
|
+
}
|
|
12
|
+
export function resolveProjectsDir() {
|
|
13
|
+
return path.join(resolveMctHome(), "projects");
|
|
14
|
+
}
|
|
15
|
+
export function resolveProjectDir(project) {
|
|
16
|
+
return path.join(resolveProjectsDir(), project);
|
|
17
|
+
}
|
|
18
|
+
export function resolveServerInstanceDir(project, server) {
|
|
19
|
+
return path.join(resolveProjectDir(project), server);
|
|
20
|
+
}
|
|
21
|
+
export function resolveGlobalStateDir() {
|
|
22
|
+
return path.join(resolveMctHome(), "state");
|
|
23
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface MctProfile {
|
|
2
|
+
server: string;
|
|
3
|
+
clients: string[];
|
|
4
|
+
deployPlugins?: string[];
|
|
5
|
+
}
|
|
6
|
+
export interface MctProjectFile {
|
|
7
|
+
project: string;
|
|
8
|
+
profiles: Record<string, MctProfile>;
|
|
9
|
+
defaultProfile?: string;
|
|
10
|
+
screenshot?: {
|
|
11
|
+
outputDir: string;
|
|
12
|
+
};
|
|
13
|
+
timeout?: {
|
|
14
|
+
serverReady?: number;
|
|
15
|
+
clientReady?: number;
|
|
16
|
+
default?: number;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export declare const PROJECT_FILE_NAME = "mct.project.json";
|
|
20
|
+
export declare function resolveProjectFilePath(cwd: string): string;
|
|
21
|
+
export declare function loadProjectFile(cwd: string): Promise<MctProjectFile | null>;
|
|
22
|
+
export declare function writeProjectFile(cwd: string, project: MctProjectFile): Promise<void>;
|
|
23
|
+
export declare function resolveProfile(projectFile: MctProjectFile, profileName?: string): MctProfile | null;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { access, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export const PROJECT_FILE_NAME = "mct.project.json";
|
|
4
|
+
export function resolveProjectFilePath(cwd) {
|
|
5
|
+
return path.join(cwd, PROJECT_FILE_NAME);
|
|
6
|
+
}
|
|
7
|
+
export async function loadProjectFile(cwd) {
|
|
8
|
+
const filePath = resolveProjectFilePath(cwd);
|
|
9
|
+
try {
|
|
10
|
+
await access(filePath);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
const raw = await readFile(filePath, "utf8");
|
|
16
|
+
return JSON.parse(raw);
|
|
17
|
+
}
|
|
18
|
+
export async function writeProjectFile(cwd, project) {
|
|
19
|
+
const filePath = resolveProjectFilePath(cwd);
|
|
20
|
+
await writeFile(filePath, `${JSON.stringify(project, null, 2)}\n`, "utf8");
|
|
21
|
+
}
|
|
22
|
+
export function resolveProfile(projectFile, profileName) {
|
|
23
|
+
const name = profileName ?? projectFile.defaultProfile;
|
|
24
|
+
if (!name) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return projectFile.profiles[name] ?? null;
|
|
28
|
+
}
|