@kzheart_/mc-pilot 0.8.1 → 0.9.1
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/data/variants.json +12 -12
- package/dist/commands/chat.js +3 -3
- package/dist/commands/client.d.ts +1 -1
- package/dist/commands/client.js +4 -4
- package/dist/commands/gui.js +8 -3
- package/dist/commands/plugin.js +3 -3
- package/dist/commands/project.js +22 -30
- package/dist/commands/request-helpers.d.ts +2 -0
- package/dist/commands/request-helpers.js +18 -0
- package/dist/commands/screenshot.js +10 -7
- package/dist/commands/server.js +7 -7
- package/dist/download/VersionMatrix.js +14 -14
- package/dist/download/client/ClientDownloader.js +1 -1
- package/dist/index.js +3 -0
- package/dist/instance/ClientInstanceManager.d.ts +1 -0
- package/dist/instance/ClientInstanceManager.js +105 -98
- package/dist/instance/ServerInstanceManager.d.ts +5 -0
- package/dist/instance/ServerInstanceManager.js +85 -3
- package/dist/util/command-log.d.ts +12 -0
- package/dist/util/command-log.js +13 -0
- package/dist/util/command.js +2 -2
- package/dist/util/context.d.ts +3 -0
- package/dist/util/context.js +10 -3
- package/dist/util/global-state.d.ts +4 -0
- package/dist/util/global-state.js +22 -0
- package/dist/util/net.d.ts +1 -0
- package/dist/util/net.js +14 -12
- package/dist/util/paths.d.ts +2 -0
- package/dist/util/paths.js +6 -0
- package/dist/util/project.d.ts +16 -4
- package/dist/util/project.js +63 -8
- package/dist/util/state.d.ts +5 -0
- package/dist/util/state.js +81 -2
- package/package.json +1 -1
- package/scripts/launch-fabric-client.mjs +13 -5
package/dist/util/project.js
CHANGED
|
@@ -1,11 +1,24 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
2
|
+
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
|
-
|
|
4
|
-
export
|
|
5
|
-
|
|
4
|
+
import { resolveProjectConfigPath, resolveProjectScreenshotsDir } from "./paths.js";
|
|
5
|
+
export const PROJECT_FILE_NAME = "project.json";
|
|
6
|
+
export function normalizeProjectRoot(cwd) {
|
|
7
|
+
try {
|
|
8
|
+
return realpathSync(cwd);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return path.resolve(cwd);
|
|
12
|
+
}
|
|
6
13
|
}
|
|
7
|
-
export
|
|
8
|
-
|
|
14
|
+
export function slugifyProjectId(cwd) {
|
|
15
|
+
return cwd.replace(/[^A-Za-z0-9._-]/g, "-").replace(/-+/g, "-");
|
|
16
|
+
}
|
|
17
|
+
export function resolveProjectFilePath(projectId) {
|
|
18
|
+
return resolveProjectConfigPath(projectId);
|
|
19
|
+
}
|
|
20
|
+
export async function loadProjectFileById(projectId) {
|
|
21
|
+
const filePath = resolveProjectFilePath(projectId);
|
|
9
22
|
try {
|
|
10
23
|
await access(filePath);
|
|
11
24
|
}
|
|
@@ -15,10 +28,52 @@ export async function loadProjectFile(cwd) {
|
|
|
15
28
|
const raw = await readFile(filePath, "utf8");
|
|
16
29
|
return JSON.parse(raw);
|
|
17
30
|
}
|
|
18
|
-
export async function
|
|
19
|
-
const
|
|
31
|
+
export async function loadProjectFileForCwd(cwd) {
|
|
32
|
+
const projectId = slugifyProjectId(normalizeProjectRoot(cwd));
|
|
33
|
+
const projectFile = await loadProjectFileById(projectId);
|
|
34
|
+
if (!projectFile) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
projectId,
|
|
39
|
+
filePath: resolveProjectFilePath(projectId),
|
|
40
|
+
projectFile
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export async function loadProjectFileForId(projectId) {
|
|
44
|
+
const projectFile = await loadProjectFileById(projectId);
|
|
45
|
+
if (!projectFile) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
projectId,
|
|
50
|
+
filePath: resolveProjectFilePath(projectId),
|
|
51
|
+
projectFile
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export async function writeProjectFile(projectId, project) {
|
|
55
|
+
const filePath = resolveProjectFilePath(projectId);
|
|
56
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
20
57
|
await writeFile(filePath, `${JSON.stringify(project, null, 2)}\n`, "utf8");
|
|
21
58
|
}
|
|
59
|
+
export function createDefaultProjectFile(cwd, projectName) {
|
|
60
|
+
const rootDir = normalizeProjectRoot(cwd);
|
|
61
|
+
const projectId = slugifyProjectId(rootDir);
|
|
62
|
+
return {
|
|
63
|
+
projectId,
|
|
64
|
+
project: projectName,
|
|
65
|
+
rootDir,
|
|
66
|
+
profiles: {},
|
|
67
|
+
screenshot: {
|
|
68
|
+
outputDir: resolveProjectScreenshotsDir(projectId)
|
|
69
|
+
},
|
|
70
|
+
timeout: {
|
|
71
|
+
serverReady: 120,
|
|
72
|
+
clientReady: 60,
|
|
73
|
+
default: 10
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
22
77
|
export function resolveProfile(projectFile, profileName) {
|
|
23
78
|
const name = profileName ?? projectFile.defaultProfile;
|
|
24
79
|
if (!name) {
|
package/dist/util/state.d.ts
CHANGED
|
@@ -6,5 +6,10 @@ export declare class StateStore {
|
|
|
6
6
|
readJson<T>(name: string, fallback: T): Promise<T>;
|
|
7
7
|
writeJson(name: string, value: unknown): Promise<void>;
|
|
8
8
|
remove(name: string): Promise<void>;
|
|
9
|
+
withLock<T>(name: string, task: () => Promise<T>, options?: {
|
|
10
|
+
timeoutMs?: number;
|
|
11
|
+
staleMs?: number;
|
|
12
|
+
}): Promise<T>;
|
|
13
|
+
private cleanupStaleLock;
|
|
9
14
|
}
|
|
10
15
|
export declare function resolveStateDir(stateDir: string | undefined, cwd: string): string;
|
package/dist/util/state.js
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
const DEFAULT_LOCK_TIMEOUT_MS = 15_000;
|
|
6
|
+
const DEFAULT_LOCK_STALE_MS = 60_000;
|
|
7
|
+
const LOCK_POLL_INTERVAL_MS = 50;
|
|
8
|
+
function sleep(ms) {
|
|
9
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
10
|
+
}
|
|
11
|
+
function isPidRunning(pid) {
|
|
12
|
+
try {
|
|
13
|
+
process.kill(pid, 0);
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
3
20
|
export class StateStore {
|
|
4
21
|
rootDir;
|
|
5
22
|
constructor(rootDir) {
|
|
@@ -25,12 +42,74 @@ export class StateStore {
|
|
|
25
42
|
async writeJson(name, value) {
|
|
26
43
|
await this.ensure();
|
|
27
44
|
const target = path.join(this.rootDir, name);
|
|
45
|
+
const tempTarget = `${target}.${process.pid}.${randomUUID()}.tmp`;
|
|
28
46
|
const content = JSON.stringify(value, null, 2);
|
|
29
|
-
await writeFile(
|
|
47
|
+
await writeFile(tempTarget, `${content}\n`, "utf8");
|
|
48
|
+
await rename(tempTarget, target);
|
|
30
49
|
}
|
|
31
50
|
async remove(name) {
|
|
32
51
|
await rm(path.join(this.rootDir, name), { force: true });
|
|
33
52
|
}
|
|
53
|
+
async withLock(name, task, options = {}) {
|
|
54
|
+
await this.ensure();
|
|
55
|
+
const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_LOCK_TIMEOUT_MS);
|
|
56
|
+
const staleMs = options.staleMs ?? DEFAULT_LOCK_STALE_MS;
|
|
57
|
+
const safeName = name.replace(/[\\/]/g, "-");
|
|
58
|
+
const lockDir = path.join(this.rootDir, `${safeName}.lock`);
|
|
59
|
+
const ownerPath = path.join(lockDir, "owner.json");
|
|
60
|
+
while (true) {
|
|
61
|
+
try {
|
|
62
|
+
await mkdir(lockDir);
|
|
63
|
+
const owner = {
|
|
64
|
+
pid: process.pid,
|
|
65
|
+
acquiredAt: new Date().toISOString(),
|
|
66
|
+
acquiredAtMs: Date.now()
|
|
67
|
+
};
|
|
68
|
+
await writeFile(ownerPath, `${JSON.stringify(owner, null, 2)}\n`, "utf8");
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
const lockExists = typeof error === "object"
|
|
73
|
+
&& error !== null
|
|
74
|
+
&& "code" in error
|
|
75
|
+
&& error.code === "EEXIST";
|
|
76
|
+
if (!lockExists) {
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
if (await this.cleanupStaleLock(lockDir, ownerPath, staleMs)) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (Date.now() >= deadline) {
|
|
83
|
+
throw new Error(`Timed out waiting for lock ${safeName}`);
|
|
84
|
+
}
|
|
85
|
+
await sleep(LOCK_POLL_INTERVAL_MS);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
return await task();
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
await rm(lockDir, { recursive: true, force: true });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async cleanupStaleLock(lockDir, ownerPath, staleMs) {
|
|
96
|
+
try {
|
|
97
|
+
const raw = await readFile(ownerPath, "utf8");
|
|
98
|
+
const owner = JSON.parse(raw);
|
|
99
|
+
const pid = Number(owner.pid);
|
|
100
|
+
const acquiredAtMs = typeof owner.acquiredAtMs === "number" ? owner.acquiredAtMs : NaN;
|
|
101
|
+
const ownerAlive = Number.isInteger(pid) && pid > 0 ? isPidRunning(pid) : false;
|
|
102
|
+
const isStale = Number.isFinite(acquiredAtMs) && Date.now() - acquiredAtMs > staleMs;
|
|
103
|
+
if (!ownerAlive || isStale) {
|
|
104
|
+
await rm(lockDir, { recursive: true, force: true });
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// The owner file may not exist yet while another process is finalizing lock acquisition.
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
34
113
|
}
|
|
35
114
|
export function resolveStateDir(stateDir, cwd) {
|
|
36
115
|
if (!stateDir) {
|
package/package.json
CHANGED
|
@@ -126,9 +126,17 @@ async function readJson(filePath) {
|
|
|
126
126
|
return JSON.parse(await readFile(filePath, "utf8"));
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
|
|
130
|
-
const artifactName = `mct-client-mod-${
|
|
131
|
-
const
|
|
129
|
+
function getLocalBuildArtifactPath(repoRoot, variant) {
|
|
130
|
+
const artifactName = `mct-client-mod-${variant.loader ?? "fabric"}-${variant.minecraftVersion}.jar`;
|
|
131
|
+
const gradleModule = variant.gradleModule || `version-${variant.minecraftVersion}`;
|
|
132
|
+
return {
|
|
133
|
+
artifactName,
|
|
134
|
+
sourceJar: path.join(repoRoot, "client-mod", gradleModule, "build", "libs", artifactName)
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function syncBuiltMod(instanceRoot, repoRoot, variant) {
|
|
139
|
+
const { artifactName, sourceJar } = getLocalBuildArtifactPath(repoRoot, variant);
|
|
132
140
|
const targetDir = path.join(instanceRoot, "minecraft", "mods");
|
|
133
141
|
const targetJar = path.join(targetDir, artifactName);
|
|
134
142
|
|
|
@@ -193,7 +201,7 @@ async function ensureAutomationOptions(gameDir, server) {
|
|
|
193
201
|
}
|
|
194
202
|
|
|
195
203
|
async function buildLaunchSpec(options) {
|
|
196
|
-
const repoRoot =
|
|
204
|
+
const repoRoot = path.resolve(__dirname, "..", "..");
|
|
197
205
|
const defaultVariant = getDefaultVariant();
|
|
198
206
|
const minecraftVersion = process.env.MCT_CLIENT_VERSION || options["minecraft-version"] || defaultVariant.minecraftVersion;
|
|
199
207
|
const modVariantId = process.env.MCT_CLIENT_MOD_VARIANT || options["mod-variant"] || `${minecraftVersion}-fabric`;
|
|
@@ -206,7 +214,7 @@ async function buildLaunchSpec(options) {
|
|
|
206
214
|
const nativesDir = options["natives-dir"] || path.join(instanceRoot, "natives");
|
|
207
215
|
const gameDir = path.join(instanceRoot, "minecraft");
|
|
208
216
|
const packMeta = await readJson(path.join(instanceRoot, "mmc-pack.json"));
|
|
209
|
-
await syncBuiltMod(instanceRoot, repoRoot,
|
|
217
|
+
await syncBuiltMod(instanceRoot, repoRoot, selectedVariant);
|
|
210
218
|
const componentMetas = new Map();
|
|
211
219
|
for (const component of packMeta.components) {
|
|
212
220
|
const componentMetaPath = path.join(metaRoot, component.uid, `${component.version}.json`);
|