cyclops-clientdevbridge-cli 0.1.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/CHANGELOG.md +41 -0
- package/LICENSE +21 -0
- package/README.md +218 -0
- package/dist/artifacts.d.ts +56 -0
- package/dist/artifacts.d.ts.map +1 -0
- package/dist/artifacts.js +58 -0
- package/dist/artifacts.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +348 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/compare.d.ts +27 -0
- package/dist/commands/compare.d.ts.map +1 -0
- package/dist/commands/compare.js +209 -0
- package/dist/commands/compare.js.map +1 -0
- package/dist/commands/context.d.ts +25 -0
- package/dist/commands/context.d.ts.map +1 -0
- package/dist/commands/context.js +36 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/doctor.d.ts +31 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +234 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/hotswap.d.ts +7 -0
- package/dist/commands/hotswap.d.ts.map +1 -0
- package/dist/commands/hotswap.js +76 -0
- package/dist/commands/hotswap.js.map +1 -0
- package/dist/commands/input.d.ts +53 -0
- package/dist/commands/input.d.ts.map +1 -0
- package/dist/commands/input.js +204 -0
- package/dist/commands/input.js.map +1 -0
- package/dist/commands/inspect.d.ts +19 -0
- package/dist/commands/inspect.d.ts.map +1 -0
- package/dist/commands/inspect.js +90 -0
- package/dist/commands/inspect.js.map +1 -0
- package/dist/commands/lifecycle.d.ts +29 -0
- package/dist/commands/lifecycle.d.ts.map +1 -0
- package/dist/commands/lifecycle.js +201 -0
- package/dist/commands/lifecycle.js.map +1 -0
- package/dist/commands/snapshot.d.ts +37 -0
- package/dist/commands/snapshot.d.ts.map +1 -0
- package/dist/commands/snapshot.js +133 -0
- package/dist/commands/snapshot.js.map +1 -0
- package/dist/commands/world.d.ts +27 -0
- package/dist/commands/world.d.ts.map +1 -0
- package/dist/commands/world.js +203 -0
- package/dist/commands/world.js.map +1 -0
- package/dist/detect.d.ts +58 -0
- package/dist/detect.d.ts.map +1 -0
- package/dist/detect.js +185 -0
- package/dist/detect.js.map +1 -0
- package/dist/errors.d.ts +24 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +37 -0
- package/dist/errors.js.map +1 -0
- package/dist/hotswap.d.ts +58 -0
- package/dist/hotswap.d.ts.map +1 -0
- package/dist/hotswap.js +284 -0
- package/dist/hotswap.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/initscript.d.ts +49 -0
- package/dist/initscript.d.ts.map +1 -0
- package/dist/initscript.js +202 -0
- package/dist/initscript.js.map +1 -0
- package/dist/java.d.ts +47 -0
- package/dist/java.d.ts.map +1 -0
- package/dist/java.js +114 -0
- package/dist/java.js.map +1 -0
- package/dist/launcher.d.ts +104 -0
- package/dist/launcher.d.ts.map +1 -0
- package/dist/launcher.js +609 -0
- package/dist/launcher.js.map +1 -0
- package/dist/loopback.d.ts +17 -0
- package/dist/loopback.d.ts.map +1 -0
- package/dist/loopback.js +19 -0
- package/dist/loopback.js.map +1 -0
- package/dist/output.d.ts +21 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +41 -0
- package/dist/output.js.map +1 -0
- package/dist/paths.d.ts +23 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +47 -0
- package/dist/paths.js.map +1 -0
- package/dist/protocol/client.d.ts +35 -0
- package/dist/protocol/client.d.ts.map +1 -0
- package/dist/protocol/client.js +170 -0
- package/dist/protocol/client.js.map +1 -0
- package/dist/protocol/types.d.ts +114 -0
- package/dist/protocol/types.d.ts.map +1 -0
- package/dist/protocol/types.js +38 -0
- package/dist/protocol/types.js.map +1 -0
- package/dist/session.d.ts +77 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +79 -0
- package/dist/session.js.map +1 -0
- package/dist/snapshot/model.d.ts +327 -0
- package/dist/snapshot/model.d.ts.map +1 -0
- package/dist/snapshot/model.js +91 -0
- package/dist/snapshot/model.js.map +1 -0
- package/dist/snapshot/outline.d.ts +35 -0
- package/dist/snapshot/outline.d.ts.map +1 -0
- package/dist/snapshot/outline.js +135 -0
- package/dist/snapshot/outline.js.map +1 -0
- package/dist/xvfb.d.ts +30 -0
- package/dist/xvfb.d.ts.map +1 -0
- package/dist/xvfb.js +66 -0
- package/dist/xvfb.js.map +1 -0
- package/package.json +66 -0
package/dist/launcher.js
ADDED
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
import { execFileSync, spawn } from 'node:child_process';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as net from 'node:net';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { CliError, SessionError } from './errors.js';
|
|
6
|
+
import { LOOPBACK_HOSTS } from './loopback.js';
|
|
7
|
+
import { resolveJavaHome } from './java.js';
|
|
8
|
+
import { detectProject } from './detect.js';
|
|
9
|
+
import { pinOptions, renderInitScript } from './initscript.js';
|
|
10
|
+
import { ensureDirectories, ensureGitignore, resolvePaths } from './paths.js';
|
|
11
|
+
import { clearSession, isProcessAlive, readSession, writeSession } from './session.js';
|
|
12
|
+
import { BridgeClient } from './protocol/client.js';
|
|
13
|
+
import { planDisplay, xvfbPids } from './xvfb.js';
|
|
14
|
+
import { artifactId, findLine, GROUP, unsupportedMessage } from './artifacts.js';
|
|
15
|
+
export const DEFAULT_PORT = 25599;
|
|
16
|
+
export const DEFAULT_WIDTH = 854;
|
|
17
|
+
export const DEFAULT_HEIGHT = 480;
|
|
18
|
+
export const DEFAULT_USERNAME = 'ClientDevBridge';
|
|
19
|
+
/** gradle.log is truncated to this size, so a long session cannot fill the disk. */
|
|
20
|
+
const GRADLE_LOG_MAX_BYTES = 10 * 1024 * 1024;
|
|
21
|
+
/**
|
|
22
|
+
* Best-effort identification of whatever holds a port, for the orphaned-client message.
|
|
23
|
+
* Returns null when the platform has no tool for it; this is a nicety, never a requirement.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Removes Loom's cached *remapped* copy of the bridge mod.
|
|
27
|
+
*
|
|
28
|
+
* Loom remaps every mod on the dev runtime classpath and caches the result keyed by group,
|
|
29
|
+
* artifact and version. A local build republished under the same version (`1.0.0-DEV`, say) is
|
|
30
|
+
* therefore never re-read: the client silently keeps running the previous build, which looks
|
|
31
|
+
* exactly like the new code having no effect. NeoGradle uses the jar directly and does not have
|
|
32
|
+
* this problem, so it only ever bites on Fabric — and only while developing ClientDevBridge
|
|
33
|
+
* itself, which is precisely when it is most confusing.
|
|
34
|
+
*
|
|
35
|
+
* Only local builds are affected: a released version is immutable, so its cache entry is correct.
|
|
36
|
+
*/
|
|
37
|
+
export function clearLoomRemapCache(projectDir, minecraftVersion, loader) {
|
|
38
|
+
if (loader !== 'fabric') {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
const cleared = [];
|
|
42
|
+
const artifact = artifactId(minecraftVersion, loader);
|
|
43
|
+
for (const cacheRoot of findLoomCaches(projectDir)) {
|
|
44
|
+
const stack = [cacheRoot];
|
|
45
|
+
while (stack.length > 0) {
|
|
46
|
+
const directory = stack.pop();
|
|
47
|
+
let entries;
|
|
48
|
+
try {
|
|
49
|
+
entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
for (const entry of entries) {
|
|
55
|
+
if (!entry.isDirectory()) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const full = path.join(directory, entry.name);
|
|
59
|
+
if (entry.name === artifact) {
|
|
60
|
+
fs.rmSync(full, { recursive: true, force: true });
|
|
61
|
+
cleared.push(full);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
stack.push(full);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return cleared;
|
|
70
|
+
}
|
|
71
|
+
function findLoomCaches(projectDir) {
|
|
72
|
+
const roots = [];
|
|
73
|
+
const candidates = [projectDir, ...fs
|
|
74
|
+
.readdirSync(projectDir, { withFileTypes: true })
|
|
75
|
+
.filter((entry) => entry.isDirectory() && entry.name.startsWith('loader-'))
|
|
76
|
+
.map((entry) => path.join(projectDir, entry.name))];
|
|
77
|
+
for (const candidate of candidates) {
|
|
78
|
+
const cache = path.join(candidate, '.gradle', 'loom-cache', 'remapped_mods');
|
|
79
|
+
if (fs.existsSync(cache)) {
|
|
80
|
+
roots.push(cache);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return roots;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Asks whatever is listening on the bridge port who it is.
|
|
87
|
+
*
|
|
88
|
+
* A client on the port is not automatically an orphan: a mod developer with two checkouts open
|
|
89
|
+
* hits this constantly, and telling them to `kill` a perfectly healthy client of their own is bad
|
|
90
|
+
* advice given confidently. A ClientDevBridge client answers with the project it belongs to.
|
|
91
|
+
*/
|
|
92
|
+
export async function describeBridgeOnPort(port) {
|
|
93
|
+
try {
|
|
94
|
+
const client = await BridgeClient.connect({ port, timeoutMs: 3_000 });
|
|
95
|
+
const hello = client.hello;
|
|
96
|
+
client.close();
|
|
97
|
+
return {
|
|
98
|
+
projectDir: hello.projectDir ?? null,
|
|
99
|
+
mcVersion: hello.mcVersion,
|
|
100
|
+
loader: hello.loader,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// Not a bridge, or not answering: the caller falls back to the pid it can see.
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/** What to tell someone whose bridge port is already taken. */
|
|
109
|
+
export function hintForOccupiedPort(port, owner, bridge, thisProject) {
|
|
110
|
+
if (bridge === null) {
|
|
111
|
+
return owner === null
|
|
112
|
+
? `Pass --port <other> to use a different one, or stop whatever is listening on ${port}.`
|
|
113
|
+
: `Something that is not a ClientDevBridge client holds it: pid ${owner}. ` +
|
|
114
|
+
`Stop it with 'kill ${owner.split(' ')[0]}', or start this one elsewhere with --port <other>.`;
|
|
115
|
+
}
|
|
116
|
+
const where = bridge.projectDir;
|
|
117
|
+
if (where !== null && path.resolve(where) !== path.resolve(thisProject)) {
|
|
118
|
+
return (`A ClientDevBridge client for another project holds it (Minecraft ${bridge.mcVersion}, ${bridge.loader}):\n` +
|
|
119
|
+
` ${where}\n` +
|
|
120
|
+
` Stop that one with 'clientdevbridge --project ${where} stop', or run this one alongside ` +
|
|
121
|
+
'it with --port <other>.');
|
|
122
|
+
}
|
|
123
|
+
return (`A ClientDevBridge client for this project already holds it (Minecraft ${bridge.mcVersion}, ` +
|
|
124
|
+
`${bridge.loader}), but its session file is gone, so 'stop' cannot find it. ` +
|
|
125
|
+
(owner === null
|
|
126
|
+
? 'Stop it by hand, or start elsewhere with --port <other>.'
|
|
127
|
+
: `Stop it with 'pkill -g $(ps -o pgid= -p ${owner.split(' ')[0]} | tr -d " ")', or start ` +
|
|
128
|
+
'elsewhere with --port <other>.'));
|
|
129
|
+
}
|
|
130
|
+
export function describePortOwner(port) {
|
|
131
|
+
if (process.platform === 'win32') {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
const output = execFileSync('lsof', ['-nP', `-iTCP:${port}`, '-sTCP:LISTEN', '-Fpc'], {
|
|
136
|
+
encoding: 'utf8',
|
|
137
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
138
|
+
});
|
|
139
|
+
const pid = /^p(\d+)/m.exec(output);
|
|
140
|
+
if (pid !== null) {
|
|
141
|
+
const name = /^c(.+)$/m.exec(output);
|
|
142
|
+
return `${pid[1]} (${name === null ? 'unknown' : name[1]})`;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
// lsof is missing or found nothing; the caller falls back to a generic hint.
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Whether nothing is listening on this port, on any loopback address.
|
|
152
|
+
*
|
|
153
|
+
* Both families are probed because a client can be on either -- see LOOPBACK_HOSTS. Treating a
|
|
154
|
+
* client on ::1 as "the port is free" is the worst of the two errors: `start` would launch a
|
|
155
|
+
* second one and then wait out its whole timeout for the first.
|
|
156
|
+
*/
|
|
157
|
+
export async function isPortFree(port) {
|
|
158
|
+
const results = await Promise.all(LOOPBACK_HOSTS.map((host) => isPortFreeOn(host, port)));
|
|
159
|
+
return results.every((free) => free);
|
|
160
|
+
}
|
|
161
|
+
function isPortFreeOn(host, port) {
|
|
162
|
+
return new Promise((resolve) => {
|
|
163
|
+
const socket = net.connect({ port, host });
|
|
164
|
+
socket.once('connect', () => {
|
|
165
|
+
socket.destroy();
|
|
166
|
+
resolve(false);
|
|
167
|
+
});
|
|
168
|
+
socket.once('error', () => {
|
|
169
|
+
socket.destroy();
|
|
170
|
+
resolve(true);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Launches a client and waits until it answers the handshake.
|
|
176
|
+
*
|
|
177
|
+
* The process is detached into its own group so it survives this short-lived CLI invocation;
|
|
178
|
+
* `stop` later kills the whole group, which is what actually reaps Gradle's forked JVM.
|
|
179
|
+
*/
|
|
180
|
+
export async function start(options) {
|
|
181
|
+
const project = detectProject(options.projectDir, {
|
|
182
|
+
loader: options.loader,
|
|
183
|
+
minecraftVersion: options.minecraftVersion,
|
|
184
|
+
});
|
|
185
|
+
const paths = resolvePaths(project.projectDir);
|
|
186
|
+
const existing = readSession(project.projectDir);
|
|
187
|
+
if (existing.session !== null && !existing.stale) {
|
|
188
|
+
throw new CliError(`A ClientDevBridge client is already running for ${project.projectDir} (pid ${existing.session.pid}, port ${existing.session.port}).`, 2, "Run 'clientdevbridge stop' first, or 'clientdevbridge restart' to replace it.");
|
|
189
|
+
}
|
|
190
|
+
if (existing.stale) {
|
|
191
|
+
clearSession(paths);
|
|
192
|
+
}
|
|
193
|
+
const bridgeVersion = options.bridgeVersion ?? resolveBridgeVersion(project.minecraftVersion, project.loader);
|
|
194
|
+
if (!(await isPortFree(options.port))) {
|
|
195
|
+
// Whose client it is decides the advice. A ClientDevBridge client answers with the project it
|
|
196
|
+
// was launched for, and the common case by far is another checkout of the developer's own --
|
|
197
|
+
// telling them to kill it is confident, wrong, and worse than saying nothing. Even for a
|
|
198
|
+
// client of this project, `kill <pid>` names the java process, whose process group is not the
|
|
199
|
+
// xvfb-run one the session tracks, so it leaves the virtual display behind; `stop` does not.
|
|
200
|
+
const owner = describePortOwner(options.port);
|
|
201
|
+
const bridge = await describeBridgeOnPort(options.port);
|
|
202
|
+
throw new CliError(`Port ${options.port} on localhost is already in use, so the client could not claim it.`, 2, hintForOccupiedPort(options.port, owner, bridge, project.projectDir));
|
|
203
|
+
}
|
|
204
|
+
// A mavenLocal build means someone is iterating on ClientDevBridge itself, and Loom would
|
|
205
|
+
// otherwise keep serving the previously remapped copy of the same version.
|
|
206
|
+
if (options.bridgeVersion === undefined && newestInMavenLocal(project.minecraftVersion, project.loader) !== null) {
|
|
207
|
+
const cleared = clearLoomRemapCache(project.projectDir, project.minecraftVersion, project.loader);
|
|
208
|
+
if (cleared.length > 0) {
|
|
209
|
+
options.onProgress?.(`Cleared Loom's cached remap of the local build (${cleared.length} entry/entries)`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
ensureDirectories(paths);
|
|
213
|
+
if (ensureGitignore(paths)) {
|
|
214
|
+
options.onProgress?.(`Added .clientdevbridge/ to ${path.join(project.projectDir, '.gitignore')}`);
|
|
215
|
+
}
|
|
216
|
+
fs.writeFileSync(paths.initScript, renderInitScript({
|
|
217
|
+
minecraftVersion: project.minecraftVersion,
|
|
218
|
+
loader: project.loader,
|
|
219
|
+
bridgeVersion,
|
|
220
|
+
port: options.port,
|
|
221
|
+
evalEnabled: options.evalEnabled,
|
|
222
|
+
world: options.world ?? null,
|
|
223
|
+
username: DEFAULT_USERNAME,
|
|
224
|
+
width: options.width,
|
|
225
|
+
height: options.height,
|
|
226
|
+
jdwpPort: options.jdwpPort,
|
|
227
|
+
projectDir: project.projectDir,
|
|
228
|
+
// The Gradle path of the module being launched: ':loader-neoforge:runClient' -> ':loader-neoforge'.
|
|
229
|
+
targetProjectPath: project.gradleTask.replace(/:runClient$/, '') || ':',
|
|
230
|
+
}), 'utf8');
|
|
231
|
+
if (options.pinOptions) {
|
|
232
|
+
const result = pinOptions(project.runDir);
|
|
233
|
+
if (result.changed.length > 0) {
|
|
234
|
+
options.onProgress?.(`Pinned determinism options in ${path.join(project.runDir, 'options.txt')} (changed: ${result.changed.join(', ')})`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const gradleArgs = [
|
|
238
|
+
project.gradleTask,
|
|
239
|
+
'--init-script',
|
|
240
|
+
paths.initScript,
|
|
241
|
+
'--console=plain',
|
|
242
|
+
'--no-daemon',
|
|
243
|
+
...options.gradleArgs,
|
|
244
|
+
];
|
|
245
|
+
const display = planDisplay(project.gradleWrapper, {
|
|
246
|
+
headed: options.headed,
|
|
247
|
+
width: options.width,
|
|
248
|
+
height: options.height,
|
|
249
|
+
});
|
|
250
|
+
options.onProgress?.(`Display: ${display.description}`);
|
|
251
|
+
const args = display.command === project.gradleWrapper
|
|
252
|
+
? gradleArgs
|
|
253
|
+
: [...display.prefixArgs, project.gradleWrapper, ...gradleArgs];
|
|
254
|
+
fs.writeFileSync(paths.gradleLog, '', 'utf8');
|
|
255
|
+
const logStream = fs.openSync(paths.gradleLog, 'a');
|
|
256
|
+
// xvfb-run picks a free display itself and never says which, so the only way to know what it
|
|
257
|
+
// started is to compare before and after.
|
|
258
|
+
const xvfbBefore = new Set(xvfbPids());
|
|
259
|
+
// Gradle runs on JAVA_HOME, not on whatever `java` the PATH resolves to, and the loader plugins
|
|
260
|
+
// check that JDK rather than the toolchain: Loom refuses to configure a Minecraft 26 project on
|
|
261
|
+
// Java 21, during configuration, with an error that never mentions JAVA_HOME. Point Gradle at a
|
|
262
|
+
// JDK that satisfies the project when the environment's own does not.
|
|
263
|
+
const java = resolveJavaHome(project.javaVersion);
|
|
264
|
+
if (java.substituted) {
|
|
265
|
+
options.onProgress?.(`This project needs Java ${project.javaVersion}; running Gradle on ${java.javaHome} ` +
|
|
266
|
+
`(Java ${java.probe.major}) instead of the environment's own.`);
|
|
267
|
+
}
|
|
268
|
+
const child = spawn(display.command, args, {
|
|
269
|
+
cwd: project.projectDir,
|
|
270
|
+
detached: true,
|
|
271
|
+
stdio: ['ignore', logStream, logStream],
|
|
272
|
+
env: {
|
|
273
|
+
...process.env,
|
|
274
|
+
...(java.javaHome === null ? {} : { JAVA_HOME: java.javaHome }),
|
|
275
|
+
...display.env,
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
child.unref();
|
|
279
|
+
if (child.pid === undefined) {
|
|
280
|
+
throw new CliError('Failed to spawn Gradle.', 2);
|
|
281
|
+
}
|
|
282
|
+
const session = {
|
|
283
|
+
version: 1,
|
|
284
|
+
port: options.port,
|
|
285
|
+
pid: child.pid,
|
|
286
|
+
pgid: child.pid,
|
|
287
|
+
loader: project.loader,
|
|
288
|
+
mcVersion: project.minecraftVersion,
|
|
289
|
+
bridgeVersion,
|
|
290
|
+
projectDir: project.projectDir,
|
|
291
|
+
gradleTask: project.gradleTask,
|
|
292
|
+
startedAt: new Date().toISOString(),
|
|
293
|
+
world: options.world ?? null,
|
|
294
|
+
headed: options.headed,
|
|
295
|
+
display: display.description,
|
|
296
|
+
jdwpPort: options.jdwpPort,
|
|
297
|
+
xvfbPids: [],
|
|
298
|
+
};
|
|
299
|
+
writeSession(paths, session);
|
|
300
|
+
const { hello, gameDir } = await waitForHandshake(session, paths.gradleLog, options.timeoutMs, options.onProgress);
|
|
301
|
+
// Now, not right after the spawn: Gradle takes most of a minute to get as far as launching the
|
|
302
|
+
// client, and xvfb-run only starts a server when it does. A client that has answered has one.
|
|
303
|
+
const started = { ...session, xvfbPids: xvfbPids().filter((pid) => !xvfbBefore.has(pid)) };
|
|
304
|
+
writeSession(paths, started);
|
|
305
|
+
if (options.pinOptions && gameDir !== null && path.resolve(gameDir) !== path.resolve(project.runDir)) {
|
|
306
|
+
// The guess was wrong, so this run is not pinned. Pin the directory the client actually named,
|
|
307
|
+
// which is also what `detectRunDir` will find next time, and say so rather than let the next
|
|
308
|
+
// screenshot comparison fail for a reason nothing points at.
|
|
309
|
+
pinOptions(gameDir);
|
|
310
|
+
options.onProgress?.(`The client runs in ${gameDir}, not ${project.runDir}. Pinned the determinism options there; ` +
|
|
311
|
+
'restart to apply them to a running client.');
|
|
312
|
+
}
|
|
313
|
+
return { session: started, hello };
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Picks which ClientDevBridge build to inject.
|
|
317
|
+
*
|
|
318
|
+
* A build in the local Maven repository always wins, because the only reason one is there is that
|
|
319
|
+
* someone is working on ClientDevBridge itself and wants to test that build. Otherwise Gradle is
|
|
320
|
+
* asked for the newest release in the line; `--clientdevbridge-version` overrides both.
|
|
321
|
+
*/
|
|
322
|
+
function resolveBridgeVersion(minecraftVersion, loader) {
|
|
323
|
+
if (findLine(minecraftVersion) === undefined) {
|
|
324
|
+
throw new CliError(unsupportedMessage(minecraftVersion), 2);
|
|
325
|
+
}
|
|
326
|
+
const local = newestInMavenLocal(minecraftVersion, loader);
|
|
327
|
+
return local ?? '+';
|
|
328
|
+
}
|
|
329
|
+
export function newestInMavenLocal(minecraftVersion, loader) {
|
|
330
|
+
const home = process.env['HOME'] ?? process.env['USERPROFILE'];
|
|
331
|
+
if (home === undefined) {
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
const directory = path.join(home, '.m2', 'repository', GROUP.replace(/\./g, path.sep), artifactId(minecraftVersion, loader));
|
|
335
|
+
if (!fs.existsSync(directory)) {
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
const versions = fs
|
|
339
|
+
.readdirSync(directory, { withFileTypes: true })
|
|
340
|
+
.filter((entry) => entry.isDirectory())
|
|
341
|
+
.map((entry) => entry.name)
|
|
342
|
+
.sort();
|
|
343
|
+
return versions.length === 0 ? null : versions[versions.length - 1];
|
|
344
|
+
}
|
|
345
|
+
async function waitForHandshake(session, gradleLog, timeoutMs, onProgress) {
|
|
346
|
+
const deadline = Date.now() + timeoutMs;
|
|
347
|
+
let lastReport = 0;
|
|
348
|
+
// What the loop last observed, so that giving up can say which of the several quite different
|
|
349
|
+
// reasons it was still waiting for: nothing listening, a socket that never handshakes, or a
|
|
350
|
+
// client that answers and simply is not ready.
|
|
351
|
+
let attempts = 0;
|
|
352
|
+
let lastObservation = 'the port was never reachable';
|
|
353
|
+
while (Date.now() < deadline) {
|
|
354
|
+
if (!isProcessAlive(session.pid)) {
|
|
355
|
+
throw new SessionError(`Gradle exited before the client came up.\n${tailFile(gradleLog, 25)}`, `The full log is at ${gradleLog}.`);
|
|
356
|
+
}
|
|
357
|
+
if (!(await isPortFree(session.port))) {
|
|
358
|
+
attempts += 1;
|
|
359
|
+
try {
|
|
360
|
+
const client = await BridgeClient.connect({ port: session.port, timeoutMs: 10_000 });
|
|
361
|
+
// The socket opens during mod initialisation, long before the game is usable: the
|
|
362
|
+
// resource reload is still running, no client ticks are happening, and the next command
|
|
363
|
+
// would race the startup. Wait for the game to actually be up.
|
|
364
|
+
const status = await client.call('status', {}, 10_000);
|
|
365
|
+
const ready = status['loaded'] === true;
|
|
366
|
+
const hello = client.hello;
|
|
367
|
+
const reported = status['gameDir'];
|
|
368
|
+
client.close();
|
|
369
|
+
if (ready) {
|
|
370
|
+
return { hello, gameDir: typeof reported === 'string' ? reported : null };
|
|
371
|
+
}
|
|
372
|
+
lastObservation =
|
|
373
|
+
`the client answered but was not ready yet (screen ${String(status['screenClass'] ?? 'none')}, ` +
|
|
374
|
+
`inWorld ${String(status['inWorld'])}, tick ${String(status['tick'])}, fps ${String(status['fps'])})`;
|
|
375
|
+
}
|
|
376
|
+
catch (error) {
|
|
377
|
+
lastObservation = `connecting to the open port failed: ${error.message}`;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (Date.now() - lastReport > 15_000) {
|
|
381
|
+
lastReport = Date.now();
|
|
382
|
+
onProgress?.(`Still starting... (${Math.round((Date.now() - (deadline - timeoutMs)) / 1000)}s) ${lastGradleLine(gradleLog)}`);
|
|
383
|
+
}
|
|
384
|
+
await delay(500);
|
|
385
|
+
}
|
|
386
|
+
// A client that never answers is blocked on something, and the log cannot say what: it simply
|
|
387
|
+
// stops. Ask the JVM for a thread dump before giving up, so the render thread's stack lands in
|
|
388
|
+
// the game log where whoever debugs this will look.
|
|
389
|
+
const dumped = await requestThreadDump();
|
|
390
|
+
// A port that was never reachable at all is the case that reads as "the client never started"
|
|
391
|
+
// while the client is in fact running and listening -- it just bound an address this CLI did not
|
|
392
|
+
// try. Naming the sockets that do exist turns that into a one-line diagnosis.
|
|
393
|
+
const listeners = attempts === 0 ? describeListeners(session.port) : null;
|
|
394
|
+
throw new SessionError(`The client did not answer on port ${session.port} within ${Math.round(timeoutMs / 1000)}s.\n` +
|
|
395
|
+
`Reached the port ${attempts} time(s); last time, ${lastObservation}.\n` +
|
|
396
|
+
(listeners === null ? '' : `Sockets listening on that port: ${listeners}\n`) +
|
|
397
|
+
tailFile(gradleLog, 25), dumped
|
|
398
|
+
? `A thread dump was appended to ${gradleLog}. The stack of "Render thread" is what stalled; ` +
|
|
399
|
+
'increase --timeout only if it shows real work in progress.'
|
|
400
|
+
: `Increase --timeout, or read the full log at ${gradleLog}.`);
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* What is listening on this port, as the operating system sees it.
|
|
404
|
+
*
|
|
405
|
+
* Only used to explain a failure, so every way of finding out is best-effort: a machine with
|
|
406
|
+
* neither `ss` nor `lsof` simply gets a message saying so.
|
|
407
|
+
*/
|
|
408
|
+
function describeListeners(port) {
|
|
409
|
+
for (const [command, args] of [
|
|
410
|
+
['ss', ['-ltnH', `sport = :${port}`]],
|
|
411
|
+
['lsof', ['-nP', `-iTCP:${port}`, '-sTCP:LISTEN']],
|
|
412
|
+
]) {
|
|
413
|
+
try {
|
|
414
|
+
const output = execFileSync(command, [...args], {
|
|
415
|
+
encoding: 'utf8',
|
|
416
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
417
|
+
}).trim();
|
|
418
|
+
return output === '' ? `none (${command} found no listening socket)` : `\n${output}`;
|
|
419
|
+
}
|
|
420
|
+
catch {
|
|
421
|
+
// Not installed, or it failed; try the next one.
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return 'unknown (neither ss nor lsof is available)';
|
|
425
|
+
}
|
|
426
|
+
/** Whether a pid belongs to a JVM, which is the only kind of process SIGQUIT is safe to send to. */
|
|
427
|
+
function isJavaProcess(pid) {
|
|
428
|
+
try {
|
|
429
|
+
const command = execFileSync('ps', ['-o', 'comm=', '-p', String(pid)], {
|
|
430
|
+
encoding: 'utf8',
|
|
431
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
432
|
+
}).trim();
|
|
433
|
+
return path.basename(command) === 'java';
|
|
434
|
+
}
|
|
435
|
+
catch {
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Asks the client JVM to print every thread's stack into the game log.
|
|
441
|
+
*
|
|
442
|
+
* SIGQUIT makes a JVM dump its threads to its own stdout, which the launcher is already capturing
|
|
443
|
+
* into gradle.log -- so the evidence ends up in the file the error message points at, with no
|
|
444
|
+
* debugger and no second run.
|
|
445
|
+
*
|
|
446
|
+
* Only the client JVM is signalled, located by the system property that launched it. Signalling
|
|
447
|
+
* the process group instead would reach xvfb-run and Xvfb, for which SIGQUIT is fatal: that would
|
|
448
|
+
* take the display down and destroy the very evidence being collected.
|
|
449
|
+
*
|
|
450
|
+
* @return whether a dump was requested from at least one process
|
|
451
|
+
*/
|
|
452
|
+
export async function requestThreadDump() {
|
|
453
|
+
let pids;
|
|
454
|
+
try {
|
|
455
|
+
// The `--` matters: the pattern starts with a dash, and without it pgrep reads it as options
|
|
456
|
+
// and exits with "invalid option -- 'D'" -- which the catch below would quietly turn into
|
|
457
|
+
// "no dump available", exactly when a dump is what is needed.
|
|
458
|
+
pids = execFileSync('pgrep', ['-f', '--', '-Dclientdevbridge.enabled=true'], { encoding: 'utf8' })
|
|
459
|
+
.split('\n')
|
|
460
|
+
.map((line) => Number(line.trim()))
|
|
461
|
+
.filter((pid) => Number.isInteger(pid) && pid > 0);
|
|
462
|
+
}
|
|
463
|
+
catch (error) {
|
|
464
|
+
// pgrep exits 1 when it simply matched nothing, and >1 when the call itself was wrong. Those
|
|
465
|
+
// are very different, and collapsing them is how a bug hid here once already: the pattern
|
|
466
|
+
// starts with a dash, pgrep rejected it as an option, and the failure looked exactly like
|
|
467
|
+
// "there is no client to dump".
|
|
468
|
+
const status = error.status;
|
|
469
|
+
if (status !== 1) {
|
|
470
|
+
process.stderr.write(`Could not ask the client for a thread dump: pgrep exited ${status ?? 'abnormally'}.\n`);
|
|
471
|
+
}
|
|
472
|
+
return false;
|
|
473
|
+
}
|
|
474
|
+
let signalled = false;
|
|
475
|
+
for (const pid of pids) {
|
|
476
|
+
// Only ever signal a JVM. `pgrep -f` matches on the whole command line, so it also finds
|
|
477
|
+
// anything that merely mentions the property -- a shell grepping for it, an editor with the
|
|
478
|
+
// init script open. SIGQUIT is a thread dump to a JVM and a *kill* to everything else, so
|
|
479
|
+
// signalling an unchecked match would terminate an innocent process while trying to debug.
|
|
480
|
+
if (!isJavaProcess(pid)) {
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
try {
|
|
484
|
+
process.kill(pid, 'SIGQUIT');
|
|
485
|
+
signalled = true;
|
|
486
|
+
}
|
|
487
|
+
catch {
|
|
488
|
+
// Already gone.
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
if (signalled) {
|
|
492
|
+
// The dump is written asynchronously by the JVM; give it a moment to reach the log file.
|
|
493
|
+
await delay(3_000);
|
|
494
|
+
}
|
|
495
|
+
return signalled;
|
|
496
|
+
}
|
|
497
|
+
function delay(ms) {
|
|
498
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
499
|
+
}
|
|
500
|
+
export function tailFile(file, lines) {
|
|
501
|
+
if (!fs.existsSync(file)) {
|
|
502
|
+
return '(no log yet)';
|
|
503
|
+
}
|
|
504
|
+
const content = readTruncated(file);
|
|
505
|
+
const all = content.split('\n').filter((line) => line.length > 0);
|
|
506
|
+
return all.slice(-lines).join('\n');
|
|
507
|
+
}
|
|
508
|
+
function lastGradleLine(file) {
|
|
509
|
+
const tail = tailFile(file, 1);
|
|
510
|
+
return tail.length > 160 ? `${tail.slice(0, 157)}...` : tail;
|
|
511
|
+
}
|
|
512
|
+
/** Reads at most the last {@link GRADLE_LOG_MAX_BYTES} of a file, so a huge log cannot blow up memory. */
|
|
513
|
+
export function readTruncated(file) {
|
|
514
|
+
const stat = fs.statSync(file);
|
|
515
|
+
if (stat.size <= GRADLE_LOG_MAX_BYTES) {
|
|
516
|
+
return fs.readFileSync(file, 'utf8');
|
|
517
|
+
}
|
|
518
|
+
const handle = fs.openSync(file, 'r');
|
|
519
|
+
try {
|
|
520
|
+
const buffer = Buffer.alloc(GRADLE_LOG_MAX_BYTES);
|
|
521
|
+
fs.readSync(handle, buffer, 0, GRADLE_LOG_MAX_BYTES, stat.size - GRADLE_LOG_MAX_BYTES);
|
|
522
|
+
return buffer.toString('utf8');
|
|
523
|
+
}
|
|
524
|
+
finally {
|
|
525
|
+
fs.closeSync(handle);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Kills the whole process group, so Gradle, xvfb-run, and the forked client JVM all go together.
|
|
530
|
+
*/
|
|
531
|
+
export async function stop(projectDir, port = DEFAULT_PORT) {
|
|
532
|
+
const status = readSession(projectDir);
|
|
533
|
+
if (status.session === null) {
|
|
534
|
+
// No session file, but something may still be holding the port: a client whose session.json
|
|
535
|
+
// was deleted is exactly the case where a caller most needs to be told, rather than quietly
|
|
536
|
+
// told "nothing was running" while a Minecraft client keeps going.
|
|
537
|
+
const orphan = (await isPortFree(port)) ? null : describePortOwner(port);
|
|
538
|
+
return { stopped: false, wasStale: status.stale, orphan };
|
|
539
|
+
}
|
|
540
|
+
if (status.stale) {
|
|
541
|
+
clearSession(status.paths);
|
|
542
|
+
const orphan = (await isPortFree(status.session.port)) ? null : describePortOwner(status.session.port);
|
|
543
|
+
return { stopped: false, wasStale: true, orphan };
|
|
544
|
+
}
|
|
545
|
+
const pgid = status.session.pgid;
|
|
546
|
+
signalGroup(pgid, 'SIGTERM');
|
|
547
|
+
const deadline = Date.now() + 15_000;
|
|
548
|
+
while (Date.now() < deadline && isProcessAlive(status.session.pid)) {
|
|
549
|
+
await delay(250);
|
|
550
|
+
}
|
|
551
|
+
if (isProcessAlive(status.session.pid)) {
|
|
552
|
+
signalGroup(pgid, 'SIGKILL');
|
|
553
|
+
await delay(500);
|
|
554
|
+
}
|
|
555
|
+
await reapXvfb(status.session.xvfbPids ?? []);
|
|
556
|
+
clearSession(status.paths);
|
|
557
|
+
return { stopped: true, wasStale: false, orphan: null };
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Kills the Xvfb servers a launch started.
|
|
561
|
+
*
|
|
562
|
+
* Killing the process group does not reliably take them: xvfb-run backgrounds Xvfb and relies on
|
|
563
|
+
* its own exit trap to clean up, and the trap does not run when the group is killed. The leak is
|
|
564
|
+
* not harmless -- Xvfb keeps its display's lock, and a later run that lands on that display dies
|
|
565
|
+
* with an X GLX BadAccess before it renders anything.
|
|
566
|
+
*
|
|
567
|
+
* Only pids recorded by this session are touched, so another project's client is never disturbed.
|
|
568
|
+
*/
|
|
569
|
+
async function reapXvfb(pids) {
|
|
570
|
+
for (const pid of pids) {
|
|
571
|
+
try {
|
|
572
|
+
process.kill(pid, 'SIGTERM');
|
|
573
|
+
}
|
|
574
|
+
catch {
|
|
575
|
+
// Already gone, which is the common case: xvfb-run's trap does sometimes get to run.
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
// Wait for them to actually go. A dying X server still holds its display lock, and `xvfb-run -a`
|
|
579
|
+
// picks a display by looking at exactly those locks -- so returning early lets the next `start`
|
|
580
|
+
// land on a server that is halfway through shutting down, which fails with an X GLX BadAccess
|
|
581
|
+
// long after anything useful could point at the cause.
|
|
582
|
+
const deadline = Date.now() + 5_000;
|
|
583
|
+
while (Date.now() < deadline && pids.some(isProcessAlive)) {
|
|
584
|
+
await delay(100);
|
|
585
|
+
}
|
|
586
|
+
for (const pid of pids.filter(isProcessAlive)) {
|
|
587
|
+
try {
|
|
588
|
+
process.kill(pid, 'SIGKILL');
|
|
589
|
+
}
|
|
590
|
+
catch {
|
|
591
|
+
// Raced with its own exit.
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
function signalGroup(pgid, signal) {
|
|
596
|
+
try {
|
|
597
|
+
// A negative pid addresses the whole process group.
|
|
598
|
+
process.kill(-pgid, signal);
|
|
599
|
+
}
|
|
600
|
+
catch {
|
|
601
|
+
try {
|
|
602
|
+
process.kill(pgid, signal);
|
|
603
|
+
}
|
|
604
|
+
catch {
|
|
605
|
+
// Already gone.
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
//# sourceMappingURL=launcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"launcher.js","sourceRoot":"","sources":["../src/launcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The loopback addresses a ClientDevBridge client may be listening on.
|
|
3
|
+
*
|
|
4
|
+
* 127.0.0.1 is what the mod binds, and the only address it has ever meant to bind. But a client
|
|
5
|
+
* built before that was made explicit calls `InetAddress.getLoopbackAddress()`, and NeoForge's dev
|
|
6
|
+
* run configuration passes `-Djava.net.preferIPv6Addresses=system`, under which the JDK answers
|
|
7
|
+
* `::1` on any machine that has `::1` bound. Such a client is perfectly healthy and completely
|
|
8
|
+
* unreachable on 127.0.0.1, so the CLI tries both rather than making the two versions move
|
|
9
|
+
* together.
|
|
10
|
+
*
|
|
11
|
+
* IPv4 comes first: it is the normal case, and trying it first keeps the usual error message the
|
|
12
|
+
* IPv4 one.
|
|
13
|
+
*/
|
|
14
|
+
export declare const LOOPBACK_HOSTS: readonly ["127.0.0.1", "::1"];
|
|
15
|
+
/** Wraps an IPv6 literal in the brackets a URL authority needs; IPv4 passes through. */
|
|
16
|
+
export declare function urlHost(host: string): string;
|
|
17
|
+
//# sourceMappingURL=loopback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loopback.d.ts","sourceRoot":"","sources":["../src/loopback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,cAAc,+BAAgC,CAAC;AAE5D,wFAAwF;AACxF,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5C"}
|