gdharness 1.0.3 → 1.0.4
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/build/cli.js +836 -772
- package/build/index.js +3715 -3655
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -16015,7 +16015,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16015
16015
|
runtimeWaitMs: {
|
|
16016
16016
|
type: "number",
|
|
16017
16017
|
ops: ["start"],
|
|
16018
|
-
description: "start: how long to wait for the game to announce its runtime before answering. Default 5000. What it waits for is the first frame, so everything the game does before drawing one is inside it, including work the args just asked for: a flag that simulates six years of game time before anything is drawn makes the announcement that late, on a project that announces promptly without it. Raise it for such a run rather than reading runtime listening false as a fault. 0 does not wait at all, which is the one to pass for a scene that announces nothing by construction, such as a bench that prints and quits."
|
|
16018
|
+
description: "start: how long to wait for the game to announce its runtime before answering. Default 5000, or half as long again as the last boot of this project took when that is more, up to 60000: the server notes how long each game took to announce, so a project that boots slowly is waited for from its second start on without being asked to. What it waits for is the first frame, so everything the game does before drawing one is inside it, including work the args just asked for: a flag that simulates six years of game time before anything is drawn makes the announcement that late, on a project that announces promptly without it. Raise it for such a run rather than reading runtime listening false as a fault. 0 does not wait at all, which is the one to pass for a scene that announces nothing by construction, such as a bench that prints and quits."
|
|
16019
16019
|
},
|
|
16020
16020
|
frames: {
|
|
16021
16021
|
type: "number",
|
|
@@ -16040,7 +16040,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16040
16040
|
requires: ["projectPath"]
|
|
16041
16041
|
},
|
|
16042
16042
|
stop: {
|
|
16043
|
-
summary: "end the run and answer with what it printed last, naming the process ended under endedPid: a game that started processes of its own keeps them unless andChildren says to end those too",
|
|
16043
|
+
summary: "end the run and answer with what it printed last, naming the process ended under endedPid: for a run the editor plays, the number its game announced under, read at the stop rather than remembered, and the operating system hands a freed number to the next process, so it can equal the game before it. A game that started processes of its own keeps them unless andChildren says to end those too",
|
|
16044
16044
|
requires: []
|
|
16045
16045
|
},
|
|
16046
16046
|
check: {
|
|
@@ -31240,11 +31240,638 @@ var init_stdio2 = __esm(() => {
|
|
|
31240
31240
|
init_stdio();
|
|
31241
31241
|
});
|
|
31242
31242
|
|
|
31243
|
+
// src/process-children.ts
|
|
31244
|
+
import { execFile as execFile4, execFileSync, spawnSync as spawnSync2 } from "node:child_process";
|
|
31245
|
+
import { basename as basename2 } from "node:path";
|
|
31246
|
+
import { promisify as promisify4 } from "node:util";
|
|
31247
|
+
function startTimesOf(pids) {
|
|
31248
|
+
const began = new Map;
|
|
31249
|
+
if (pids.length === 0) {
|
|
31250
|
+
return began;
|
|
31251
|
+
}
|
|
31252
|
+
try {
|
|
31253
|
+
if (process.platform === "win32") {
|
|
31254
|
+
const filter = pids.map((pid) => `ProcessId=${pid}`).join(" OR ");
|
|
31255
|
+
const said = execFileSync("powershell", [
|
|
31256
|
+
"-NoProfile",
|
|
31257
|
+
"-NonInteractive",
|
|
31258
|
+
"-Command",
|
|
31259
|
+
`Get-CimInstance Win32_Process -Filter "${filter}" | ForEach-Object { "$($_.ProcessId) $(([DateTimeOffset]$_.CreationDate).ToUnixTimeMilliseconds())" }`
|
|
31260
|
+
], { encoding: "utf8", timeout: ASK_TIMEOUT_MS, windowsHide: true });
|
|
31261
|
+
for (const line of said.split(`
|
|
31262
|
+
`)) {
|
|
31263
|
+
const found = /^\s*(\d+)\s+(\d+)\s*$/.exec(line);
|
|
31264
|
+
if (found?.[1] !== undefined && found[2] !== undefined) {
|
|
31265
|
+
began.set(Number(found[1]), Number(found[2]));
|
|
31266
|
+
}
|
|
31267
|
+
}
|
|
31268
|
+
return began;
|
|
31269
|
+
}
|
|
31270
|
+
const said = spawnSync2("ps", ["-p", pids.join(","), "-o", "pid=,lstart="], {
|
|
31271
|
+
encoding: "utf8",
|
|
31272
|
+
timeout: ASK_TIMEOUT_MS,
|
|
31273
|
+
env: { ...process.env, LC_ALL: "C" }
|
|
31274
|
+
}).stdout;
|
|
31275
|
+
for (const line of said.split(`
|
|
31276
|
+
`)) {
|
|
31277
|
+
const found = /^\s*(\d+)\s+(.+)$/.exec(line);
|
|
31278
|
+
const at = Date.parse(found?.[2]?.trim() ?? "");
|
|
31279
|
+
if (found?.[1] !== undefined && !Number.isNaN(at)) {
|
|
31280
|
+
began.set(Number(found[1]), at);
|
|
31281
|
+
}
|
|
31282
|
+
}
|
|
31283
|
+
} catch {}
|
|
31284
|
+
return began;
|
|
31285
|
+
}
|
|
31286
|
+
async function processTree() {
|
|
31287
|
+
try {
|
|
31288
|
+
if (process.platform === "win32") {
|
|
31289
|
+
return parseProcessTable(await askWindows(), true);
|
|
31290
|
+
}
|
|
31291
|
+
return parseProcessTable(await askPosix());
|
|
31292
|
+
} catch {
|
|
31293
|
+
return;
|
|
31294
|
+
}
|
|
31295
|
+
}
|
|
31296
|
+
function parseProcessTable(printed, started = false) {
|
|
31297
|
+
const tree = new Map;
|
|
31298
|
+
for (const line of printed.split(/\r?\n/)) {
|
|
31299
|
+
const fields = /^\s*(\d+)\s+(\d+)(?:\s+(.*))?$/.exec(line);
|
|
31300
|
+
if (fields === null) {
|
|
31301
|
+
continue;
|
|
31302
|
+
}
|
|
31303
|
+
const pid = Number(fields[1]);
|
|
31304
|
+
const parent = Number(fields[2]);
|
|
31305
|
+
const rest = (fields[3] ?? "").trim();
|
|
31306
|
+
if (pid <= 0) {
|
|
31307
|
+
continue;
|
|
31308
|
+
}
|
|
31309
|
+
if (!started) {
|
|
31310
|
+
tree.set(pid, { parent, command: rest });
|
|
31311
|
+
continue;
|
|
31312
|
+
}
|
|
31313
|
+
const timed = /^(\d+)(?:\s+(.*))?$/.exec(rest);
|
|
31314
|
+
if (timed === null) {
|
|
31315
|
+
continue;
|
|
31316
|
+
}
|
|
31317
|
+
const began = Number(timed[1]);
|
|
31318
|
+
tree.set(pid, { parent, command: (timed[2] ?? "").trim(), ...began > 0 ? { startedAt: began } : {} });
|
|
31319
|
+
}
|
|
31320
|
+
return tree;
|
|
31321
|
+
}
|
|
31322
|
+
function linked(tree, child, parent) {
|
|
31323
|
+
const from = tree.get(child);
|
|
31324
|
+
const to = tree.get(parent);
|
|
31325
|
+
if (from === undefined || from.parent !== parent) {
|
|
31326
|
+
return false;
|
|
31327
|
+
}
|
|
31328
|
+
if (from.startedAt === undefined || to?.startedAt === undefined) {
|
|
31329
|
+
return true;
|
|
31330
|
+
}
|
|
31331
|
+
return from.startedAt >= to.startedAt;
|
|
31332
|
+
}
|
|
31333
|
+
function childrenIn(tree, pid) {
|
|
31334
|
+
const children = [];
|
|
31335
|
+
for (const child of tree.keys()) {
|
|
31336
|
+
if (linked(tree, child, pid)) {
|
|
31337
|
+
children.push(child);
|
|
31338
|
+
}
|
|
31339
|
+
}
|
|
31340
|
+
return children;
|
|
31341
|
+
}
|
|
31342
|
+
function descendantsIn(tree, pid) {
|
|
31343
|
+
const found = [];
|
|
31344
|
+
const queue = [pid];
|
|
31345
|
+
const seen = new Set([pid]);
|
|
31346
|
+
while (queue.length > 0) {
|
|
31347
|
+
const next = queue.shift();
|
|
31348
|
+
if (next === undefined) {
|
|
31349
|
+
break;
|
|
31350
|
+
}
|
|
31351
|
+
for (const child of childrenIn(tree, next)) {
|
|
31352
|
+
if (!seen.has(child)) {
|
|
31353
|
+
seen.add(child);
|
|
31354
|
+
found.push(child);
|
|
31355
|
+
queue.push(child);
|
|
31356
|
+
}
|
|
31357
|
+
}
|
|
31358
|
+
}
|
|
31359
|
+
return found;
|
|
31360
|
+
}
|
|
31361
|
+
function ancestorsIn(tree, pid, root) {
|
|
31362
|
+
const chain = [];
|
|
31363
|
+
const seen = new Set([pid]);
|
|
31364
|
+
let child = pid;
|
|
31365
|
+
let parent = tree.get(pid)?.parent;
|
|
31366
|
+
while (parent !== undefined && !seen.has(parent) && linked(tree, child, parent)) {
|
|
31367
|
+
chain.push(parent);
|
|
31368
|
+
if (parent === root) {
|
|
31369
|
+
break;
|
|
31370
|
+
}
|
|
31371
|
+
seen.add(parent);
|
|
31372
|
+
child = parent;
|
|
31373
|
+
parent = tree.get(parent)?.parent;
|
|
31374
|
+
}
|
|
31375
|
+
return chain;
|
|
31376
|
+
}
|
|
31377
|
+
function readCommandLine(command) {
|
|
31378
|
+
const words = commandWords(command);
|
|
31379
|
+
const executable = words[0] === undefined ? "" : basename2(words[0].replaceAll("\\", "/"));
|
|
31380
|
+
const at = words.indexOf("--path");
|
|
31381
|
+
let projectPath = null;
|
|
31382
|
+
if (at !== -1 && at + 1 < words.length) {
|
|
31383
|
+
const taken = [];
|
|
31384
|
+
for (const word of words.slice(at + 1)) {
|
|
31385
|
+
if (taken.length > 0 && (word.startsWith("-") || /^[a-z]+:\/\//.test(word))) {
|
|
31386
|
+
break;
|
|
31387
|
+
}
|
|
31388
|
+
taken.push(word);
|
|
31389
|
+
}
|
|
31390
|
+
projectPath = taken.join(" ");
|
|
31391
|
+
}
|
|
31392
|
+
return {
|
|
31393
|
+
executable,
|
|
31394
|
+
projectPath,
|
|
31395
|
+
editor: words.some((word) => word === "-e" || word === "--editor")
|
|
31396
|
+
};
|
|
31397
|
+
}
|
|
31398
|
+
function commandWords(command) {
|
|
31399
|
+
const words = [];
|
|
31400
|
+
let word = "";
|
|
31401
|
+
let quoted = false;
|
|
31402
|
+
let started = false;
|
|
31403
|
+
for (const char of command) {
|
|
31404
|
+
if (char === '"') {
|
|
31405
|
+
quoted = !quoted;
|
|
31406
|
+
started = true;
|
|
31407
|
+
} else if (!quoted && /\s/.test(char)) {
|
|
31408
|
+
if (started) {
|
|
31409
|
+
words.push(word);
|
|
31410
|
+
word = "";
|
|
31411
|
+
started = false;
|
|
31412
|
+
}
|
|
31413
|
+
} else {
|
|
31414
|
+
word += char;
|
|
31415
|
+
started = true;
|
|
31416
|
+
}
|
|
31417
|
+
}
|
|
31418
|
+
if (started) {
|
|
31419
|
+
words.push(word);
|
|
31420
|
+
}
|
|
31421
|
+
return words;
|
|
31422
|
+
}
|
|
31423
|
+
async function askWindows() {
|
|
31424
|
+
const { stdout } = await run4("powershell.exe", [
|
|
31425
|
+
"-NoProfile",
|
|
31426
|
+
"-NonInteractive",
|
|
31427
|
+
"-Command",
|
|
31428
|
+
'Get-CimInstance Win32_Process | ForEach-Object { $began = 0; if ($_.CreationDate) { $began = ([DateTimeOffset]$_.CreationDate).ToUnixTimeMilliseconds() }; "$($_.ProcessId) $($_.ParentProcessId) $began $($_.CommandLine)" }'
|
|
31429
|
+
], { timeout: ASK_TIMEOUT_MS, windowsHide: true, maxBuffer: 16 * 1024 * 1024 });
|
|
31430
|
+
return stdout;
|
|
31431
|
+
}
|
|
31432
|
+
async function askPosix() {
|
|
31433
|
+
const { stdout } = await run4("ps", ["-e", "-o", "pid=,ppid=,args="], {
|
|
31434
|
+
timeout: ASK_TIMEOUT_MS,
|
|
31435
|
+
maxBuffer: 16 * 1024 * 1024
|
|
31436
|
+
});
|
|
31437
|
+
return stdout;
|
|
31438
|
+
}
|
|
31439
|
+
var run4, ASK_TIMEOUT_MS = 15000;
|
|
31440
|
+
var init_process_children = __esm(() => {
|
|
31441
|
+
run4 = promisify4(execFile4);
|
|
31442
|
+
});
|
|
31443
|
+
|
|
31444
|
+
// src/runtime-client.ts
|
|
31445
|
+
import { existsSync as existsSync10, readdirSync as readdirSync4, readFileSync as readFileSync10, statSync as statSync3, unlinkSync } from "node:fs";
|
|
31446
|
+
import { createConnection } from "node:net";
|
|
31447
|
+
import { tmpdir as tmpdir4 } from "node:os";
|
|
31448
|
+
import { dirname as dirname7, join as join12, resolve as resolve3 } from "node:path";
|
|
31449
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
31450
|
+
function runtimeDirectory(variables = process.env) {
|
|
31451
|
+
const explicit = envValue("GDHARNESS_RUNTIME_DIR", variables);
|
|
31452
|
+
if (explicit) {
|
|
31453
|
+
return explicit;
|
|
31454
|
+
}
|
|
31455
|
+
const perUser = envValue("XDG_RUNTIME_DIR", variables);
|
|
31456
|
+
return join12(perUser ?? tmpdir4(), "gdharness");
|
|
31457
|
+
}
|
|
31458
|
+
function runtimeDirectories(variables = process.env) {
|
|
31459
|
+
const candidates = [runtimeDirectory(variables)];
|
|
31460
|
+
const fallbacks = process.platform === "win32" ? [envValue("SystemRoot", variables) ?? envValue("windir", variables) ?? "C:\\Windows"] : ["/tmp", "/var/tmp"];
|
|
31461
|
+
for (const base of fallbacks) {
|
|
31462
|
+
candidates.push(join12(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
|
|
31463
|
+
}
|
|
31464
|
+
return [...new Set(candidates.map((path) => resolve3(path)))];
|
|
31465
|
+
}
|
|
31466
|
+
function errorReportOf(pid, directories = runtimeDirectories()) {
|
|
31467
|
+
for (const directory of directories) {
|
|
31468
|
+
const path = join12(directory, `runtime-${pid}.log`);
|
|
31469
|
+
if (existsSync10(path)) {
|
|
31470
|
+
return path;
|
|
31471
|
+
}
|
|
31472
|
+
}
|
|
31473
|
+
return null;
|
|
31474
|
+
}
|
|
31475
|
+
function processAlive(pid) {
|
|
31476
|
+
try {
|
|
31477
|
+
process.kill(pid, 0);
|
|
31478
|
+
return true;
|
|
31479
|
+
} catch (error) {
|
|
31480
|
+
return error.code === "EPERM";
|
|
31481
|
+
}
|
|
31482
|
+
}
|
|
31483
|
+
function parseAnnouncement(file, pid) {
|
|
31484
|
+
let fields;
|
|
31485
|
+
try {
|
|
31486
|
+
fields = asParams(JSON.parse(readFileSync10(file, "utf8")));
|
|
31487
|
+
} catch {
|
|
31488
|
+
return { kind: "rubbish" };
|
|
31489
|
+
}
|
|
31490
|
+
const project = readParams(fields, "project");
|
|
31491
|
+
const port = readNumber(fields, "port");
|
|
31492
|
+
const address = readString(fields, "address");
|
|
31493
|
+
const protocol = readNumber(fields, "protocol");
|
|
31494
|
+
if (readNumber(fields, "pid") !== pid || port === undefined || !Number.isInteger(port) || port < 1 || port > 65535 || address === undefined || project === undefined) {
|
|
31495
|
+
return { kind: "rubbish" };
|
|
31496
|
+
}
|
|
31497
|
+
const named = {
|
|
31498
|
+
name: readString(project, "name") ?? "",
|
|
31499
|
+
path: readString(project, "path") ?? ""
|
|
31500
|
+
};
|
|
31501
|
+
if (protocol !== RUNTIME_PROTOCOL) {
|
|
31502
|
+
return { kind: "unspoken", unspoken: { pid, protocol: protocol ?? 0, project: named } };
|
|
31503
|
+
}
|
|
31504
|
+
const editorPid = readNumber(fields, "editor_pid");
|
|
31505
|
+
return {
|
|
31506
|
+
kind: "runtime",
|
|
31507
|
+
endpoint: {
|
|
31508
|
+
pid,
|
|
31509
|
+
port,
|
|
31510
|
+
address,
|
|
31511
|
+
project: named,
|
|
31512
|
+
file,
|
|
31513
|
+
...editorPid !== undefined && Number.isInteger(editorPid) && editorPid > 0 ? { editorPid } : {}
|
|
31514
|
+
}
|
|
31515
|
+
};
|
|
31516
|
+
}
|
|
31517
|
+
function runtimesAnnounced(directories = runtimeDirectories()) {
|
|
31518
|
+
const running = new Map;
|
|
31519
|
+
const unspoken = new Map;
|
|
31520
|
+
for (const directory of directories) {
|
|
31521
|
+
for (const found of announcedIn(directory)) {
|
|
31522
|
+
if (found.kind === "runtime" && !running.has(found.endpoint.pid)) {
|
|
31523
|
+
running.set(found.endpoint.pid, found.endpoint);
|
|
31524
|
+
} else if (found.kind === "unspoken" && !unspoken.has(found.unspoken.pid)) {
|
|
31525
|
+
unspoken.set(found.unspoken.pid, found.unspoken);
|
|
31526
|
+
}
|
|
31527
|
+
}
|
|
31528
|
+
}
|
|
31529
|
+
const newest = (a, b) => b.pid - a.pid;
|
|
31530
|
+
return { running: [...running.values()].sort(newest), unspoken: [...unspoken.values()].sort(newest) };
|
|
31531
|
+
}
|
|
31532
|
+
function discoverRuntimes(directories = runtimeDirectories()) {
|
|
31533
|
+
return runtimesAnnounced(directories).running;
|
|
31534
|
+
}
|
|
31535
|
+
function wentAway(file, pid) {
|
|
31536
|
+
let project = "";
|
|
31537
|
+
try {
|
|
31538
|
+
const fields = asParams(JSON.parse(readFileSync10(file, "utf8")));
|
|
31539
|
+
project = readString(readParams(fields, "project") ?? {}, "path") ?? "";
|
|
31540
|
+
} catch {
|
|
31541
|
+
return;
|
|
31542
|
+
}
|
|
31543
|
+
if (project === "") {
|
|
31544
|
+
return;
|
|
31545
|
+
}
|
|
31546
|
+
gone.unshift({ pid, project, noticedAt: Date.now() });
|
|
31547
|
+
gone.length = Math.min(gone.length, GONE_KEPT);
|
|
31548
|
+
}
|
|
31549
|
+
function announcedAndGone(projectPath) {
|
|
31550
|
+
if (projectPath === undefined) {
|
|
31551
|
+
return [];
|
|
31552
|
+
}
|
|
31553
|
+
const since = Date.now() - GONE_WINDOW_MS;
|
|
31554
|
+
const wanted = resolve3(projectPath);
|
|
31555
|
+
return gone.filter((one) => one.noticedAt >= since && resolve3(one.project) === wanted);
|
|
31556
|
+
}
|
|
31557
|
+
function strangersAmong(candidates, now, startTimes = startTimesOf) {
|
|
31558
|
+
const toJudge = candidates.filter((one) => {
|
|
31559
|
+
if (now - one.writtenAt < JUDGED_AFTER_MS) {
|
|
31560
|
+
return false;
|
|
31561
|
+
}
|
|
31562
|
+
const known = verdicts.get(one.file);
|
|
31563
|
+
return known === undefined || known.writtenAt !== one.writtenAt || !known.stranger && now - known.judgedAt > CONFIRMED_FOR_MS;
|
|
31564
|
+
});
|
|
31565
|
+
if (toJudge.length > 0) {
|
|
31566
|
+
const began = startTimes(toJudge.map((one) => one.pid));
|
|
31567
|
+
for (const one of toJudge) {
|
|
31568
|
+
const at = began.get(one.pid);
|
|
31569
|
+
const stranger = at !== undefined && at - one.writtenAt > STARTED_AFTER_ANNOUNCING_MS;
|
|
31570
|
+
verdicts.set(one.file, { writtenAt: one.writtenAt, judgedAt: now, stranger });
|
|
31571
|
+
}
|
|
31572
|
+
}
|
|
31573
|
+
return new Set(candidates.filter((one) => verdicts.get(one.file)?.stranger === true).map((one) => one.file));
|
|
31574
|
+
}
|
|
31575
|
+
function announcedIn(directory) {
|
|
31576
|
+
if (!existsSync10(directory)) {
|
|
31577
|
+
return [];
|
|
31578
|
+
}
|
|
31579
|
+
const found = [];
|
|
31580
|
+
const candidates = [];
|
|
31581
|
+
const entries = [];
|
|
31582
|
+
for (const entry of readdirSync4(directory)) {
|
|
31583
|
+
const report = ERROR_REPORT_PATTERN.exec(entry);
|
|
31584
|
+
if (report) {
|
|
31585
|
+
sweepErrorReport(join12(directory, entry), Number.parseInt(report[1] ?? "", 10));
|
|
31586
|
+
continue;
|
|
31587
|
+
}
|
|
31588
|
+
const match = ANNOUNCEMENT_PATTERN.exec(entry);
|
|
31589
|
+
if (!match) {
|
|
31590
|
+
continue;
|
|
31591
|
+
}
|
|
31592
|
+
const file = join12(directory, entry);
|
|
31593
|
+
const pid = Number.parseInt(match[1] ?? "", 10);
|
|
31594
|
+
const alive = processAlive(pid);
|
|
31595
|
+
entries.push({ file, pid, alive });
|
|
31596
|
+
if (alive) {
|
|
31597
|
+
try {
|
|
31598
|
+
candidates.push({ file, pid, writtenAt: statSync3(file).mtimeMs });
|
|
31599
|
+
} catch {}
|
|
31600
|
+
}
|
|
31601
|
+
}
|
|
31602
|
+
const strangers = strangersAmong(candidates, Date.now());
|
|
31603
|
+
for (const { file, pid, alive } of entries) {
|
|
31604
|
+
const announced = alive && !strangers.has(file) ? parseAnnouncement(file, pid) : { kind: "rubbish" };
|
|
31605
|
+
if (announced.kind === "rubbish") {
|
|
31606
|
+
if (!alive || strangers.has(file)) {
|
|
31607
|
+
wentAway(file, pid);
|
|
31608
|
+
}
|
|
31609
|
+
try {
|
|
31610
|
+
unlinkSync(file);
|
|
31611
|
+
} catch {}
|
|
31612
|
+
verdicts.delete(file);
|
|
31613
|
+
continue;
|
|
31614
|
+
}
|
|
31615
|
+
found.push(announced);
|
|
31616
|
+
}
|
|
31617
|
+
const held = new Set(entries.map((one) => one.file));
|
|
31618
|
+
const here = join12(directory, ".");
|
|
31619
|
+
for (const file of verdicts.keys()) {
|
|
31620
|
+
if (dirname7(file) === here && !held.has(file)) {
|
|
31621
|
+
verdicts.delete(file);
|
|
31622
|
+
}
|
|
31623
|
+
}
|
|
31624
|
+
return found;
|
|
31625
|
+
}
|
|
31626
|
+
async function announcementEnded(pid, directories = runtimeDirectories()) {
|
|
31627
|
+
const files = directories.map((directory) => join12(directory, `runtime-${pid}.json`)).filter((file) => existsSync10(file));
|
|
31628
|
+
const first = files[0];
|
|
31629
|
+
if (first === undefined) {
|
|
31630
|
+
return;
|
|
31631
|
+
}
|
|
31632
|
+
const until = Date.now() + ENDED_WITHIN_MS;
|
|
31633
|
+
while (processAlive(pid)) {
|
|
31634
|
+
const left = until - Date.now();
|
|
31635
|
+
if (left <= 0) {
|
|
31636
|
+
return;
|
|
31637
|
+
}
|
|
31638
|
+
await delay(Math.min(LOOK_EVERY_MS, left));
|
|
31639
|
+
}
|
|
31640
|
+
wentAway(first, pid);
|
|
31641
|
+
for (const file of files) {
|
|
31642
|
+
try {
|
|
31643
|
+
unlinkSync(file);
|
|
31644
|
+
} catch {}
|
|
31645
|
+
verdicts.delete(file);
|
|
31646
|
+
}
|
|
31647
|
+
}
|
|
31648
|
+
function sweepErrorReport(path, pid, now = Date.now()) {
|
|
31649
|
+
if (processAlive(pid)) {
|
|
31650
|
+
return;
|
|
31651
|
+
}
|
|
31652
|
+
try {
|
|
31653
|
+
if (now - statSync3(path).mtimeMs >= ERROR_REPORT_KEEP_MS) {
|
|
31654
|
+
unlinkSync(path);
|
|
31655
|
+
}
|
|
31656
|
+
} catch {}
|
|
31657
|
+
}
|
|
31658
|
+
async function announcedSince(projectPath, before, waiting = {}) {
|
|
31659
|
+
const wanted = resolve3(projectPath);
|
|
31660
|
+
const directories = waiting.directories ?? runtimeDirectories();
|
|
31661
|
+
const until = Date.now() + Math.max(waiting.budgetMs ?? ANNOUNCE_BUDGET_MS, 0);
|
|
31662
|
+
for (;; ) {
|
|
31663
|
+
const fresh = discoverRuntimes(directories).find((endpoint) => !before.has(endpoint.pid) && resolve3(endpoint.project.path) === wanted && (waiting.accept?.(endpoint) ?? true));
|
|
31664
|
+
if (fresh !== undefined) {
|
|
31665
|
+
return fresh;
|
|
31666
|
+
}
|
|
31667
|
+
const left = until - Date.now();
|
|
31668
|
+
if (left <= 0 || waiting.giveUp?.() === true) {
|
|
31669
|
+
return null;
|
|
31670
|
+
}
|
|
31671
|
+
await delay(Math.min(LOOK_EVERY_MS, left));
|
|
31672
|
+
}
|
|
31673
|
+
}
|
|
31674
|
+
function describe2(endpoint) {
|
|
31675
|
+
return `pid ${endpoint.pid} on ${endpoint.address}:${endpoint.port} (${endpoint.project.name || "unnamed"} at ${endpoint.project.path})`;
|
|
31676
|
+
}
|
|
31677
|
+
function tooNew(unspoken) {
|
|
31678
|
+
const named = unspoken.map((game) => `pid ${game.pid} speaks ${game.protocol} (${game.project.name || "unnamed"} at ${game.project.path})`).join("; ");
|
|
31679
|
+
const half = unspoken.some((game) => game.protocol > RUNTIME_PROTOCOL) ? "this server is the older half: reconnect it so it spawns the installed version" : "the addon is the older half: reinstall it and restart the game";
|
|
31680
|
+
return `A game is running, in a protocol this server does not speak ${named}. This server speaks ${RUNTIME_PROTOCOL}, so ${half}.`;
|
|
31681
|
+
}
|
|
31682
|
+
function chooseRuntime(endpoints, projectPath, unspoken = [], pid) {
|
|
31683
|
+
if (pid !== undefined) {
|
|
31684
|
+
const named = endpoints.find((endpoint) => endpoint.pid === pid);
|
|
31685
|
+
if (named !== undefined) {
|
|
31686
|
+
if (projectPath !== undefined && resolve3(named.project.path) !== resolve3(projectPath)) {
|
|
31687
|
+
return {
|
|
31688
|
+
problem: `The game with pid ${pid} is running ${named.project.path}, not ${resolve3(projectPath)}. Running: ${endpoints.map(describe2).join("; ")}.`
|
|
31689
|
+
};
|
|
31690
|
+
}
|
|
31691
|
+
return { endpoint: named };
|
|
31692
|
+
}
|
|
31693
|
+
const tooNewToo = unspoken.find((game) => game.pid === pid);
|
|
31694
|
+
if (tooNewToo !== undefined) {
|
|
31695
|
+
return { problem: tooNew([tooNewToo]) };
|
|
31696
|
+
}
|
|
31697
|
+
return {
|
|
31698
|
+
problem: endpoints.length === 0 ? `No game with the runtime addon is running, so there is none with pid ${pid}. Start one with editor_run, or play the project from the editor with the addon enabled.` : `No running game has pid ${pid}. Running: ${endpoints.map(describe2).join("; ")}. editor_status lists them under runtimes as they come and go.`
|
|
31699
|
+
};
|
|
31700
|
+
}
|
|
31701
|
+
if (endpoints.length === 0) {
|
|
31702
|
+
if (unspoken.length > 0) {
|
|
31703
|
+
return { problem: tooNew(unspoken) };
|
|
31704
|
+
}
|
|
31705
|
+
const went = announcedAndGone(projectPath);
|
|
31706
|
+
const first = went[0];
|
|
31707
|
+
if (first !== undefined) {
|
|
31708
|
+
const ago = Math.max(1, Math.round((Date.now() - first.noticedAt) / 1000));
|
|
31709
|
+
const others = went.length - 1;
|
|
31710
|
+
const earlier = others === 0 ? "" : ` ${others} earlier ${others === 1 ? "one" : "ones"} went the same way.`;
|
|
31711
|
+
return {
|
|
31712
|
+
problem: `A game with the runtime addon announced itself and its process is gone: pid ${first.pid}` + `${first.project === "" ? "" : ` for ${first.project}`}, noticed ${ago}s ago.${earlier}` + " It quit or was ended rather than never starting, so editor_output has what it printed" + " on the way, including whatever it broke on. Start another with editor_run once you have read it."
|
|
31713
|
+
};
|
|
31714
|
+
}
|
|
31715
|
+
return {
|
|
31716
|
+
problem: "No game with the runtime addon is running. Start one with editor_run, or play the project from the editor with the addon enabled."
|
|
31717
|
+
};
|
|
31718
|
+
}
|
|
31719
|
+
if (projectPath !== undefined) {
|
|
31720
|
+
const wanted = resolve3(projectPath);
|
|
31721
|
+
const matching = endpoints.filter((endpoint) => resolve3(endpoint.project.path) === wanted);
|
|
31722
|
+
if (matching.length === 0) {
|
|
31723
|
+
return {
|
|
31724
|
+
problem: `No running game is from ${wanted}. Running: ${endpoints.map(describe2).join("; ")}.`
|
|
31725
|
+
};
|
|
31726
|
+
}
|
|
31727
|
+
if (matching.length === 1 && matching[0]) {
|
|
31728
|
+
return { endpoint: matching[0] };
|
|
31729
|
+
}
|
|
31730
|
+
return {
|
|
31731
|
+
problem: `Several games are running from ${wanted}: ${matching.map(describe2).join("; ")}. Pass pid to choose one.`
|
|
31732
|
+
};
|
|
31733
|
+
}
|
|
31734
|
+
if (endpoints.length === 1 && endpoints[0]) {
|
|
31735
|
+
return { endpoint: endpoints[0] };
|
|
31736
|
+
}
|
|
31737
|
+
return {
|
|
31738
|
+
problem: `Several games are running: ${endpoints.map(describe2).join("; ")}. Pass projectPath to choose one, or pid when they are from one project.`
|
|
31739
|
+
};
|
|
31740
|
+
}
|
|
31741
|
+
function runtimeRequest(endpoint, command, params, timeoutMs) {
|
|
31742
|
+
const id = nextRequestId++;
|
|
31743
|
+
return new Promise((settle) => {
|
|
31744
|
+
let done = false;
|
|
31745
|
+
let buffered = "";
|
|
31746
|
+
let welcomed = false;
|
|
31747
|
+
const socket = createConnection({ port: endpoint.port, host: endpoint.address });
|
|
31748
|
+
const finish = (reply) => {
|
|
31749
|
+
if (done) {
|
|
31750
|
+
return;
|
|
31751
|
+
}
|
|
31752
|
+
done = true;
|
|
31753
|
+
clearTimeout(timer);
|
|
31754
|
+
socket.destroy();
|
|
31755
|
+
settle(reply);
|
|
31756
|
+
};
|
|
31757
|
+
const timer = setTimeout(() => {
|
|
31758
|
+
finish({
|
|
31759
|
+
ok: false,
|
|
31760
|
+
reason: "busy",
|
|
31761
|
+
message: `The game (${describe2(endpoint)}) accepted the connection but did not answer '${command}' within ${timeoutMs}ms. It may be paused at a breakpoint or stuck in a long frame.`
|
|
31762
|
+
});
|
|
31763
|
+
}, timeoutMs);
|
|
31764
|
+
socket.setEncoding("utf8");
|
|
31765
|
+
socket.on("connect", () => {
|
|
31766
|
+
socket.write(`${JSON.stringify({ id, command, params })}
|
|
31767
|
+
`);
|
|
31768
|
+
});
|
|
31769
|
+
socket.on("data", (chunk) => {
|
|
31770
|
+
buffered += chunk;
|
|
31771
|
+
let newline = buffered.indexOf(`
|
|
31772
|
+
`);
|
|
31773
|
+
while (newline !== -1 && !done) {
|
|
31774
|
+
const line = buffered.slice(0, newline).trim();
|
|
31775
|
+
buffered = buffered.slice(newline + 1);
|
|
31776
|
+
newline = buffered.indexOf(`
|
|
31777
|
+
`);
|
|
31778
|
+
if (line === "") {
|
|
31779
|
+
continue;
|
|
31780
|
+
}
|
|
31781
|
+
let message;
|
|
31782
|
+
try {
|
|
31783
|
+
message = asParams(JSON.parse(line));
|
|
31784
|
+
} catch {
|
|
31785
|
+
continue;
|
|
31786
|
+
}
|
|
31787
|
+
if (!welcomed) {
|
|
31788
|
+
welcomed = true;
|
|
31789
|
+
if (readString(message, "type") === "welcome") {
|
|
31790
|
+
const spoken = readNumber(message, "protocol");
|
|
31791
|
+
if (spoken !== RUNTIME_PROTOCOL) {
|
|
31792
|
+
finish({
|
|
31793
|
+
ok: false,
|
|
31794
|
+
reason: "protocol",
|
|
31795
|
+
message: `The game (${describe2(endpoint)}) speaks runtime protocol ${spoken ?? "unknown"} and this server speaks ${RUNTIME_PROTOCOL}. Reinstall the runtime addon into the project.`
|
|
31796
|
+
});
|
|
31797
|
+
return;
|
|
31798
|
+
}
|
|
31799
|
+
continue;
|
|
31800
|
+
}
|
|
31801
|
+
}
|
|
31802
|
+
if (message["id"] !== id) {
|
|
31803
|
+
continue;
|
|
31804
|
+
}
|
|
31805
|
+
if (readString(message, "type") === "error") {
|
|
31806
|
+
finish({ ok: false, reason: "error", message: readString(message, "message") ?? "unknown error" });
|
|
31807
|
+
return;
|
|
31808
|
+
}
|
|
31809
|
+
finish({ ok: true, payload: message, endpoint });
|
|
31810
|
+
}
|
|
31811
|
+
});
|
|
31812
|
+
socket.on("error", (error) => {
|
|
31813
|
+
finish({
|
|
31814
|
+
ok: false,
|
|
31815
|
+
reason: "refused",
|
|
31816
|
+
message: `The game (${describe2(endpoint)}) is running but nothing answered on its port: ${error.code ?? error.message}. It may still be starting, or be shutting down.`
|
|
31817
|
+
});
|
|
31818
|
+
});
|
|
31819
|
+
socket.on("close", () => {
|
|
31820
|
+
finish({
|
|
31821
|
+
ok: false,
|
|
31822
|
+
reason: "refused",
|
|
31823
|
+
message: `The game (${describe2(endpoint)}) closed the connection before answering '${command}'.`
|
|
31824
|
+
});
|
|
31825
|
+
});
|
|
31826
|
+
});
|
|
31827
|
+
}
|
|
31828
|
+
var RUNTIME_PROTOCOL = 2, ANNOUNCEMENT_PATTERN, ERROR_REPORT_PATTERN, ERROR_REPORT_KEEP_MS, GONE_WINDOW_MS = 120000, GONE_KEPT = 32, gone, STARTED_AFTER_ANNOUNCING_MS = 2000, CONFIRMED_FOR_MS = 60000, JUDGED_AFTER_MS = 60000, verdicts, ENDED_WITHIN_MS = 3000, LOOK_EVERY_MS = 50, ANNOUNCE_BUDGET_MS = 5000, nextRequestId = 1;
|
|
31829
|
+
var init_runtime_client = __esm(() => {
|
|
31830
|
+
init_process_children();
|
|
31831
|
+
ANNOUNCEMENT_PATTERN = /^runtime-(\d+)\.json$/;
|
|
31832
|
+
ERROR_REPORT_PATTERN = /^runtime-(\d+)\.log$/;
|
|
31833
|
+
ERROR_REPORT_KEEP_MS = 60 * 60 * 1000;
|
|
31834
|
+
gone = [];
|
|
31835
|
+
verdicts = new Map;
|
|
31836
|
+
});
|
|
31837
|
+
|
|
31838
|
+
// src/boot-note.ts
|
|
31839
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync11, writeFileSync as writeFileSync8 } from "node:fs";
|
|
31840
|
+
import { dirname as dirname8, join as join13 } from "node:path";
|
|
31841
|
+
function bootNotePath(projectPath) {
|
|
31842
|
+
return join13(projectPath, ".godot", "gdharness-boot.json");
|
|
31843
|
+
}
|
|
31844
|
+
function readBootNote(projectPath) {
|
|
31845
|
+
try {
|
|
31846
|
+
const noted = readNumber(asParams(JSON.parse(readFileSync11(bootNotePath(projectPath), "utf8"))), "announcedAfterMs");
|
|
31847
|
+
return noted !== undefined && Number.isFinite(noted) && noted > 0 ? noted : null;
|
|
31848
|
+
} catch {
|
|
31849
|
+
return null;
|
|
31850
|
+
}
|
|
31851
|
+
}
|
|
31852
|
+
function writeBootNote(projectPath, announcedAfterMs) {
|
|
31853
|
+
try {
|
|
31854
|
+
const path = bootNotePath(projectPath);
|
|
31855
|
+
mkdirSync6(dirname8(path), { recursive: true });
|
|
31856
|
+
writeFileSync8(path, JSON.stringify({ announcedAfterMs: Math.round(announcedAfterMs), at: new Date().toISOString() }, null, 2), "utf8");
|
|
31857
|
+
} catch {}
|
|
31858
|
+
}
|
|
31859
|
+
function waitSizedTo(lastBootMs, usual = ANNOUNCE_BUDGET_MS) {
|
|
31860
|
+
if (lastBootMs === null) {
|
|
31861
|
+
return usual;
|
|
31862
|
+
}
|
|
31863
|
+
return Math.min(LONGEST_SIZED_WAIT_MS, Math.max(usual, Math.round(lastBootMs * 1.5)));
|
|
31864
|
+
}
|
|
31865
|
+
var LONGEST_SIZED_WAIT_MS = 60000;
|
|
31866
|
+
var init_boot_note = __esm(() => {
|
|
31867
|
+
init_runtime_client();
|
|
31868
|
+
});
|
|
31869
|
+
|
|
31243
31870
|
// src/breakpoint-note.ts
|
|
31244
|
-
import { mkdirSync as
|
|
31245
|
-
import { dirname as
|
|
31871
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync12, rmSync as rmSync6, writeFileSync as writeFileSync9 } from "node:fs";
|
|
31872
|
+
import { dirname as dirname9, isAbsolute as isAbsolute2, join as join14, relative as relative2 } from "node:path";
|
|
31246
31873
|
function breakpointNotePath(projectPath) {
|
|
31247
|
-
return
|
|
31874
|
+
return join14(projectPath, ".godot", "gdharness-breakpoints.json");
|
|
31248
31875
|
}
|
|
31249
31876
|
function writeBreakpointNote(projectPath, held) {
|
|
31250
31877
|
const path = breakpointNotePath(projectPath);
|
|
@@ -31261,8 +31888,8 @@ function writeBreakpointNote(projectPath, held) {
|
|
|
31261
31888
|
lines: [...one.lines]
|
|
31262
31889
|
}))
|
|
31263
31890
|
};
|
|
31264
|
-
|
|
31265
|
-
|
|
31891
|
+
mkdirSync7(dirname9(path), { recursive: true });
|
|
31892
|
+
writeFileSync9(path, `${JSON.stringify(note, null, 2)}
|
|
31266
31893
|
`, "utf8");
|
|
31267
31894
|
} catch (error) {
|
|
31268
31895
|
console.error(`[SERVER] Could not record the breakpoints at ${path}: ${errorMessage(error)}`);
|
|
@@ -31270,7 +31897,7 @@ function writeBreakpointNote(projectPath, held) {
|
|
|
31270
31897
|
}
|
|
31271
31898
|
function readBreakpointNote(projectPath) {
|
|
31272
31899
|
try {
|
|
31273
|
-
const parsed = JSON.parse(
|
|
31900
|
+
const parsed = JSON.parse(readFileSync12(breakpointNotePath(projectPath), "utf8"));
|
|
31274
31901
|
if (typeof parsed !== "object" || parsed === null || !Array.isArray(parsed.breakpoints)) {
|
|
31275
31902
|
return [];
|
|
31276
31903
|
}
|
|
@@ -31285,7 +31912,7 @@ function readBreakpointNote(projectPath) {
|
|
|
31285
31912
|
}
|
|
31286
31913
|
const lines = Array.isArray(listed) ? listed.filter((line) => Number.isInteger(line) && line > 0) : [];
|
|
31287
31914
|
if (lines.length > 0) {
|
|
31288
|
-
held.push({ scriptPath: realPathOr(
|
|
31915
|
+
held.push({ scriptPath: realPathOr(join14(projectPath, script)), lines });
|
|
31289
31916
|
}
|
|
31290
31917
|
}
|
|
31291
31918
|
return held;
|
|
@@ -31303,14 +31930,14 @@ var init_breakpoint_note = __esm(() => {
|
|
|
31303
31930
|
});
|
|
31304
31931
|
|
|
31305
31932
|
// src/bridge-announce.ts
|
|
31306
|
-
import { mkdirSync as
|
|
31307
|
-
import { dirname as
|
|
31933
|
+
import { mkdirSync as mkdirSync8, readFileSync as readFileSync13, rmSync as rmSync7, writeFileSync as writeFileSync10 } from "node:fs";
|
|
31934
|
+
import { dirname as dirname10, join as join15 } from "node:path";
|
|
31308
31935
|
function announcementPath(projectPath) {
|
|
31309
|
-
return
|
|
31936
|
+
return join15(projectPath, ".godot", "gdharness-bridge.json");
|
|
31310
31937
|
}
|
|
31311
31938
|
function readAnnouncement(path) {
|
|
31312
31939
|
try {
|
|
31313
|
-
const parsed = JSON.parse(
|
|
31940
|
+
const parsed = JSON.parse(readFileSync13(path, "utf8"));
|
|
31314
31941
|
if (typeof parsed !== "object" || parsed === null) {
|
|
31315
31942
|
return null;
|
|
31316
31943
|
}
|
|
@@ -31329,7 +31956,7 @@ function announcementIsOurs(path) {
|
|
|
31329
31956
|
function announceBridge(projectPath, bridge) {
|
|
31330
31957
|
const path = announcementPath(projectPath);
|
|
31331
31958
|
try {
|
|
31332
|
-
|
|
31959
|
+
mkdirSync8(dirname10(path), { recursive: true });
|
|
31333
31960
|
const announcement = {
|
|
31334
31961
|
protocol: BRIDGE_ANNOUNCE_PROTOCOL,
|
|
31335
31962
|
host: bridge.host,
|
|
@@ -31338,7 +31965,7 @@ function announceBridge(projectPath, bridge) {
|
|
|
31338
31965
|
version: bridge.version,
|
|
31339
31966
|
startedAt: new Date().toISOString()
|
|
31340
31967
|
};
|
|
31341
|
-
|
|
31968
|
+
writeFileSync10(path, `${JSON.stringify(announcement, null, 2)}
|
|
31342
31969
|
`, "utf8");
|
|
31343
31970
|
return path;
|
|
31344
31971
|
} catch (error) {
|
|
@@ -31362,16 +31989,16 @@ var init_bridge_announce = __esm(() => {
|
|
|
31362
31989
|
});
|
|
31363
31990
|
|
|
31364
31991
|
// src/config-pin.ts
|
|
31365
|
-
import { existsSync as
|
|
31366
|
-
import { join as
|
|
31992
|
+
import { existsSync as existsSync11, readFileSync as readFileSync14 } from "node:fs";
|
|
31993
|
+
import { join as join16 } from "node:path";
|
|
31367
31994
|
function versionsNamedInConfig(projectPath) {
|
|
31368
|
-
const config =
|
|
31369
|
-
if (!
|
|
31995
|
+
const config = join16(projectPath, ".mcp.json");
|
|
31996
|
+
if (!existsSync11(config)) {
|
|
31370
31997
|
return [];
|
|
31371
31998
|
}
|
|
31372
31999
|
let text;
|
|
31373
32000
|
try {
|
|
31374
|
-
text =
|
|
32001
|
+
text = readFileSync14(config, "utf8");
|
|
31375
32002
|
JSON.parse(text);
|
|
31376
32003
|
} catch {
|
|
31377
32004
|
return [];
|
|
@@ -31518,8 +32145,8 @@ var init_ports = __esm(() => {
|
|
|
31518
32145
|
});
|
|
31519
32146
|
|
|
31520
32147
|
// src/dap_client.ts
|
|
31521
|
-
import { createConnection } from "node:net";
|
|
31522
|
-
import { setTimeout as
|
|
32148
|
+
import { createConnection as createConnection2 } from "node:net";
|
|
32149
|
+
import { setTimeout as delay2 } from "node:timers/promises";
|
|
31523
32150
|
function said(body, field) {
|
|
31524
32151
|
const value = body?.[field];
|
|
31525
32152
|
return typeof value === "string" ? value : "";
|
|
@@ -31554,7 +32181,7 @@ class GodotDAPClient {
|
|
|
31554
32181
|
return;
|
|
31555
32182
|
}
|
|
31556
32183
|
await new Promise((resolve, reject) => {
|
|
31557
|
-
const socket =
|
|
32184
|
+
const socket = createConnection2({ host: this.host, port: this.port });
|
|
31558
32185
|
this.socket = socket;
|
|
31559
32186
|
const connectTimeout = setTimeout(() => {
|
|
31560
32187
|
socket.destroy();
|
|
@@ -31963,7 +32590,7 @@ class GodotDAPClient {
|
|
|
31963
32590
|
return answered;
|
|
31964
32591
|
}
|
|
31965
32592
|
} catch {}
|
|
31966
|
-
await
|
|
32593
|
+
await delay2(200);
|
|
31967
32594
|
}
|
|
31968
32595
|
return [];
|
|
31969
32596
|
}
|
|
@@ -31984,7 +32611,7 @@ class GodotDAPClient {
|
|
|
31984
32611
|
break;
|
|
31985
32612
|
}
|
|
31986
32613
|
} catch {}
|
|
31987
|
-
await
|
|
32614
|
+
await delay2(200);
|
|
31988
32615
|
}
|
|
31989
32616
|
const named = [];
|
|
31990
32617
|
for (const scope of scopes) {
|
|
@@ -35500,10 +36127,10 @@ var init_godot_bridge = __esm(() => {
|
|
|
35500
36127
|
});
|
|
35501
36128
|
|
|
35502
36129
|
// src/godot-path.ts
|
|
35503
|
-
import { execFile as
|
|
35504
|
-
import { existsSync as
|
|
36130
|
+
import { execFile as execFile5 } from "node:child_process";
|
|
36131
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
35505
36132
|
import { normalize as normalize2 } from "node:path";
|
|
35506
|
-
import { promisify as
|
|
36133
|
+
import { promisify as promisify5 } from "node:util";
|
|
35507
36134
|
|
|
35508
36135
|
class GodotLocator2 {
|
|
35509
36136
|
answered = new Map;
|
|
@@ -35556,11 +36183,11 @@ class GodotLocator2 {
|
|
|
35556
36183
|
return known;
|
|
35557
36184
|
}
|
|
35558
36185
|
let reason;
|
|
35559
|
-
if (path !== "godot" && !
|
|
36186
|
+
if (path !== "godot" && !existsSync12(path)) {
|
|
35560
36187
|
reason = "does not exist";
|
|
35561
36188
|
} else {
|
|
35562
36189
|
try {
|
|
35563
|
-
await
|
|
36190
|
+
await run5(path, ["--version"]);
|
|
35564
36191
|
reason = null;
|
|
35565
36192
|
} catch (error) {
|
|
35566
36193
|
reason = `does not answer --version: ${errorMessage(error)}`;
|
|
@@ -35570,11 +36197,11 @@ class GodotLocator2 {
|
|
|
35570
36197
|
return reason;
|
|
35571
36198
|
}
|
|
35572
36199
|
}
|
|
35573
|
-
var
|
|
36200
|
+
var run5;
|
|
35574
36201
|
var init_godot_path = __esm(() => {
|
|
35575
36202
|
init_detection();
|
|
35576
36203
|
init_errors();
|
|
35577
|
-
|
|
36204
|
+
run5 = promisify5(execFile5);
|
|
35578
36205
|
});
|
|
35579
36206
|
|
|
35580
36207
|
// src/issues.ts
|
|
@@ -35931,8 +36558,8 @@ var init_junit = __esm(() => {
|
|
|
35931
36558
|
// src/lsp_client.ts
|
|
35932
36559
|
import { realpathSync as realpathSync2 } from "node:fs";
|
|
35933
36560
|
import { readFile, realpath } from "node:fs/promises";
|
|
35934
|
-
import { createConnection as
|
|
35935
|
-
import { dirname as
|
|
36561
|
+
import { createConnection as createConnection3 } from "node:net";
|
|
36562
|
+
import { dirname as dirname11, resolve as resolve4 } from "node:path";
|
|
35936
36563
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
35937
36564
|
function diagnosticsKey(uri) {
|
|
35938
36565
|
let decoded;
|
|
@@ -35978,7 +36605,7 @@ class GodotLSPClient {
|
|
|
35978
36605
|
}
|
|
35979
36606
|
this.connectPromise = new Promise((resolveConnect, rejectConnect) => {
|
|
35980
36607
|
let settled = false;
|
|
35981
|
-
const socket =
|
|
36608
|
+
const socket = createConnection3({ port: this.port, host: this.host }, () => {
|
|
35982
36609
|
this.socket = socket;
|
|
35983
36610
|
this.connected = true;
|
|
35984
36611
|
this.reader = new FrameReader;
|
|
@@ -36060,7 +36687,7 @@ class GodotLSPClient {
|
|
|
36060
36687
|
if (this.initialized) {
|
|
36061
36688
|
return;
|
|
36062
36689
|
}
|
|
36063
|
-
const rootPath = this.rootPath ??
|
|
36690
|
+
const rootPath = this.rootPath ?? dirname11(resolve4(filePath));
|
|
36064
36691
|
await this.initialize(rootPath);
|
|
36065
36692
|
}
|
|
36066
36693
|
async sendRequest(method, params) {
|
|
@@ -36198,7 +36825,7 @@ class GodotLSPClient {
|
|
|
36198
36825
|
});
|
|
36199
36826
|
}
|
|
36200
36827
|
toFileUri(filePath) {
|
|
36201
|
-
return pathToFileURL(
|
|
36828
|
+
return pathToFileURL(resolve4(filePath)).href;
|
|
36202
36829
|
}
|
|
36203
36830
|
syncDocument(filePath, content) {
|
|
36204
36831
|
const uri = this.toFileUri(filePath);
|
|
@@ -36239,7 +36866,7 @@ class GodotLSPClient {
|
|
|
36239
36866
|
}
|
|
36240
36867
|
async initialize(rootPath) {
|
|
36241
36868
|
await this.ensureConnected();
|
|
36242
|
-
const resolvedRootPath =
|
|
36869
|
+
const resolvedRootPath = resolve4(rootPath);
|
|
36243
36870
|
const rootUri = pathToFileURL(resolvedRootPath).href;
|
|
36244
36871
|
const result = await this.sendRequest("initialize", {
|
|
36245
36872
|
processId: process.pid,
|
|
@@ -36382,7 +37009,7 @@ function normalizeLSPError(error, port) {
|
|
|
36382
37009
|
return String(error);
|
|
36383
37010
|
}
|
|
36384
37011
|
async function resolveLSPPaths(projectPathValue, scriptPathValue) {
|
|
36385
|
-
const requestedProjectPath =
|
|
37012
|
+
const requestedProjectPath = resolve4(projectPathValue);
|
|
36386
37013
|
let projectPath;
|
|
36387
37014
|
try {
|
|
36388
37015
|
projectPath = await realpath(requestedProjectPath);
|
|
@@ -36435,235 +37062,34 @@ async function handleLSPTool(client, toolName, args) {
|
|
|
36435
37062
|
const completions = await client.getCompletions(scriptPath, content, line, character);
|
|
36436
37063
|
return asToolResponse({ completions });
|
|
36437
37064
|
}
|
|
36438
|
-
case "lsp_get_hover": {
|
|
36439
|
-
const line = Number(parsedArgs["line"]);
|
|
36440
|
-
const character = Number(parsedArgs["character"]);
|
|
36441
|
-
if (!Number.isFinite(line) || !Number.isFinite(character)) {
|
|
36442
|
-
throw new Refusal2("Arguments line and character must be numbers.");
|
|
36443
|
-
}
|
|
36444
|
-
const hover = await client.getHover(scriptPath, content, line, character);
|
|
36445
|
-
return asToolResponse({ hover });
|
|
36446
|
-
}
|
|
36447
|
-
case "lsp_get_symbols": {
|
|
36448
|
-
const symbols = await client.getDocumentSymbols(scriptPath, content);
|
|
36449
|
-
return asToolResponse({ symbols });
|
|
36450
|
-
}
|
|
36451
|
-
default:
|
|
36452
|
-
return asToolResponse({ error: `Unknown LSP tool: ${toolName}` });
|
|
36453
|
-
}
|
|
36454
|
-
} catch (error) {
|
|
36455
|
-
return asToolResponse({
|
|
36456
|
-
error: normalizeLSPError(error, client.port)
|
|
36457
|
-
});
|
|
36458
|
-
}
|
|
36459
|
-
}
|
|
36460
|
-
var DEFAULT_LSP_PORT = 6005, DIAGNOSTICS_TIMEOUT_MS = 5000;
|
|
36461
|
-
var init_lsp_client = __esm(() => {
|
|
36462
|
-
init_errors();
|
|
36463
|
-
init_framing();
|
|
36464
|
-
init_paths();
|
|
36465
|
-
init_ports();
|
|
36466
|
-
});
|
|
36467
|
-
|
|
36468
|
-
// src/process-children.ts
|
|
36469
|
-
import { execFile as execFile5, execFileSync, spawnSync as spawnSync2 } from "node:child_process";
|
|
36470
|
-
import { basename as basename2 } from "node:path";
|
|
36471
|
-
import { promisify as promisify5 } from "node:util";
|
|
36472
|
-
function startTimesOf(pids) {
|
|
36473
|
-
const began = new Map;
|
|
36474
|
-
if (pids.length === 0) {
|
|
36475
|
-
return began;
|
|
36476
|
-
}
|
|
36477
|
-
try {
|
|
36478
|
-
if (process.platform === "win32") {
|
|
36479
|
-
const filter = pids.map((pid) => `ProcessId=${pid}`).join(" OR ");
|
|
36480
|
-
const said = execFileSync("powershell", [
|
|
36481
|
-
"-NoProfile",
|
|
36482
|
-
"-NonInteractive",
|
|
36483
|
-
"-Command",
|
|
36484
|
-
`Get-CimInstance Win32_Process -Filter "${filter}" | ForEach-Object { "$($_.ProcessId) $(([DateTimeOffset]$_.CreationDate).ToUnixTimeMilliseconds())" }`
|
|
36485
|
-
], { encoding: "utf8", timeout: ASK_TIMEOUT_MS, windowsHide: true });
|
|
36486
|
-
for (const line of said.split(`
|
|
36487
|
-
`)) {
|
|
36488
|
-
const found = /^\s*(\d+)\s+(\d+)\s*$/.exec(line);
|
|
36489
|
-
if (found?.[1] !== undefined && found[2] !== undefined) {
|
|
36490
|
-
began.set(Number(found[1]), Number(found[2]));
|
|
36491
|
-
}
|
|
36492
|
-
}
|
|
36493
|
-
return began;
|
|
36494
|
-
}
|
|
36495
|
-
const said = spawnSync2("ps", ["-p", pids.join(","), "-o", "pid=,lstart="], {
|
|
36496
|
-
encoding: "utf8",
|
|
36497
|
-
timeout: ASK_TIMEOUT_MS,
|
|
36498
|
-
env: { ...process.env, LC_ALL: "C" }
|
|
36499
|
-
}).stdout;
|
|
36500
|
-
for (const line of said.split(`
|
|
36501
|
-
`)) {
|
|
36502
|
-
const found = /^\s*(\d+)\s+(.+)$/.exec(line);
|
|
36503
|
-
const at = Date.parse(found?.[2]?.trim() ?? "");
|
|
36504
|
-
if (found?.[1] !== undefined && !Number.isNaN(at)) {
|
|
36505
|
-
began.set(Number(found[1]), at);
|
|
36506
|
-
}
|
|
36507
|
-
}
|
|
36508
|
-
} catch {}
|
|
36509
|
-
return began;
|
|
36510
|
-
}
|
|
36511
|
-
async function processTree() {
|
|
36512
|
-
try {
|
|
36513
|
-
if (process.platform === "win32") {
|
|
36514
|
-
return parseProcessTable(await askWindows(), true);
|
|
36515
|
-
}
|
|
36516
|
-
return parseProcessTable(await askPosix());
|
|
36517
|
-
} catch {
|
|
36518
|
-
return;
|
|
36519
|
-
}
|
|
36520
|
-
}
|
|
36521
|
-
function parseProcessTable(printed, started = false) {
|
|
36522
|
-
const tree = new Map;
|
|
36523
|
-
for (const line of printed.split(/\r?\n/)) {
|
|
36524
|
-
const fields = /^\s*(\d+)\s+(\d+)(?:\s+(.*))?$/.exec(line);
|
|
36525
|
-
if (fields === null) {
|
|
36526
|
-
continue;
|
|
36527
|
-
}
|
|
36528
|
-
const pid = Number(fields[1]);
|
|
36529
|
-
const parent = Number(fields[2]);
|
|
36530
|
-
const rest = (fields[3] ?? "").trim();
|
|
36531
|
-
if (pid <= 0) {
|
|
36532
|
-
continue;
|
|
36533
|
-
}
|
|
36534
|
-
if (!started) {
|
|
36535
|
-
tree.set(pid, { parent, command: rest });
|
|
36536
|
-
continue;
|
|
36537
|
-
}
|
|
36538
|
-
const timed = /^(\d+)(?:\s+(.*))?$/.exec(rest);
|
|
36539
|
-
if (timed === null) {
|
|
36540
|
-
continue;
|
|
36541
|
-
}
|
|
36542
|
-
const began = Number(timed[1]);
|
|
36543
|
-
tree.set(pid, { parent, command: (timed[2] ?? "").trim(), ...began > 0 ? { startedAt: began } : {} });
|
|
36544
|
-
}
|
|
36545
|
-
return tree;
|
|
36546
|
-
}
|
|
36547
|
-
function linked(tree, child, parent) {
|
|
36548
|
-
const from = tree.get(child);
|
|
36549
|
-
const to = tree.get(parent);
|
|
36550
|
-
if (from === undefined || from.parent !== parent) {
|
|
36551
|
-
return false;
|
|
36552
|
-
}
|
|
36553
|
-
if (from.startedAt === undefined || to?.startedAt === undefined) {
|
|
36554
|
-
return true;
|
|
36555
|
-
}
|
|
36556
|
-
return from.startedAt >= to.startedAt;
|
|
36557
|
-
}
|
|
36558
|
-
function childrenIn(tree, pid) {
|
|
36559
|
-
const children = [];
|
|
36560
|
-
for (const child of tree.keys()) {
|
|
36561
|
-
if (linked(tree, child, pid)) {
|
|
36562
|
-
children.push(child);
|
|
36563
|
-
}
|
|
36564
|
-
}
|
|
36565
|
-
return children;
|
|
36566
|
-
}
|
|
36567
|
-
function descendantsIn(tree, pid) {
|
|
36568
|
-
const found = [];
|
|
36569
|
-
const queue = [pid];
|
|
36570
|
-
const seen = new Set([pid]);
|
|
36571
|
-
while (queue.length > 0) {
|
|
36572
|
-
const next = queue.shift();
|
|
36573
|
-
if (next === undefined) {
|
|
36574
|
-
break;
|
|
36575
|
-
}
|
|
36576
|
-
for (const child of childrenIn(tree, next)) {
|
|
36577
|
-
if (!seen.has(child)) {
|
|
36578
|
-
seen.add(child);
|
|
36579
|
-
found.push(child);
|
|
36580
|
-
queue.push(child);
|
|
36581
|
-
}
|
|
36582
|
-
}
|
|
36583
|
-
}
|
|
36584
|
-
return found;
|
|
36585
|
-
}
|
|
36586
|
-
function ancestorsIn(tree, pid, root) {
|
|
36587
|
-
const chain = [];
|
|
36588
|
-
const seen = new Set([pid]);
|
|
36589
|
-
let child = pid;
|
|
36590
|
-
let parent = tree.get(pid)?.parent;
|
|
36591
|
-
while (parent !== undefined && !seen.has(parent) && linked(tree, child, parent)) {
|
|
36592
|
-
chain.push(parent);
|
|
36593
|
-
if (parent === root) {
|
|
36594
|
-
break;
|
|
36595
|
-
}
|
|
36596
|
-
seen.add(parent);
|
|
36597
|
-
child = parent;
|
|
36598
|
-
parent = tree.get(parent)?.parent;
|
|
36599
|
-
}
|
|
36600
|
-
return chain;
|
|
36601
|
-
}
|
|
36602
|
-
function readCommandLine(command) {
|
|
36603
|
-
const words = commandWords(command);
|
|
36604
|
-
const executable = words[0] === undefined ? "" : basename2(words[0].replaceAll("\\", "/"));
|
|
36605
|
-
const at = words.indexOf("--path");
|
|
36606
|
-
let projectPath = null;
|
|
36607
|
-
if (at !== -1 && at + 1 < words.length) {
|
|
36608
|
-
const taken = [];
|
|
36609
|
-
for (const word of words.slice(at + 1)) {
|
|
36610
|
-
if (taken.length > 0 && (word.startsWith("-") || /^[a-z]+:\/\//.test(word))) {
|
|
36611
|
-
break;
|
|
36612
|
-
}
|
|
36613
|
-
taken.push(word);
|
|
36614
|
-
}
|
|
36615
|
-
projectPath = taken.join(" ");
|
|
36616
|
-
}
|
|
36617
|
-
return {
|
|
36618
|
-
executable,
|
|
36619
|
-
projectPath,
|
|
36620
|
-
editor: words.some((word) => word === "-e" || word === "--editor")
|
|
36621
|
-
};
|
|
36622
|
-
}
|
|
36623
|
-
function commandWords(command) {
|
|
36624
|
-
const words = [];
|
|
36625
|
-
let word = "";
|
|
36626
|
-
let quoted = false;
|
|
36627
|
-
let started = false;
|
|
36628
|
-
for (const char of command) {
|
|
36629
|
-
if (char === '"') {
|
|
36630
|
-
quoted = !quoted;
|
|
36631
|
-
started = true;
|
|
36632
|
-
} else if (!quoted && /\s/.test(char)) {
|
|
36633
|
-
if (started) {
|
|
36634
|
-
words.push(word);
|
|
36635
|
-
word = "";
|
|
36636
|
-
started = false;
|
|
36637
|
-
}
|
|
36638
|
-
} else {
|
|
36639
|
-
word += char;
|
|
36640
|
-
started = true;
|
|
37065
|
+
case "lsp_get_hover": {
|
|
37066
|
+
const line = Number(parsedArgs["line"]);
|
|
37067
|
+
const character = Number(parsedArgs["character"]);
|
|
37068
|
+
if (!Number.isFinite(line) || !Number.isFinite(character)) {
|
|
37069
|
+
throw new Refusal2("Arguments line and character must be numbers.");
|
|
37070
|
+
}
|
|
37071
|
+
const hover = await client.getHover(scriptPath, content, line, character);
|
|
37072
|
+
return asToolResponse({ hover });
|
|
37073
|
+
}
|
|
37074
|
+
case "lsp_get_symbols": {
|
|
37075
|
+
const symbols = await client.getDocumentSymbols(scriptPath, content);
|
|
37076
|
+
return asToolResponse({ symbols });
|
|
37077
|
+
}
|
|
37078
|
+
default:
|
|
37079
|
+
return asToolResponse({ error: `Unknown LSP tool: ${toolName}` });
|
|
36641
37080
|
}
|
|
37081
|
+
} catch (error) {
|
|
37082
|
+
return asToolResponse({
|
|
37083
|
+
error: normalizeLSPError(error, client.port)
|
|
37084
|
+
});
|
|
36642
37085
|
}
|
|
36643
|
-
if (started) {
|
|
36644
|
-
words.push(word);
|
|
36645
|
-
}
|
|
36646
|
-
return words;
|
|
36647
|
-
}
|
|
36648
|
-
async function askWindows() {
|
|
36649
|
-
const { stdout } = await run5("powershell.exe", [
|
|
36650
|
-
"-NoProfile",
|
|
36651
|
-
"-NonInteractive",
|
|
36652
|
-
"-Command",
|
|
36653
|
-
'Get-CimInstance Win32_Process | ForEach-Object { $began = 0; if ($_.CreationDate) { $began = ([DateTimeOffset]$_.CreationDate).ToUnixTimeMilliseconds() }; "$($_.ProcessId) $($_.ParentProcessId) $began $($_.CommandLine)" }'
|
|
36654
|
-
], { timeout: ASK_TIMEOUT_MS, windowsHide: true, maxBuffer: 16 * 1024 * 1024 });
|
|
36655
|
-
return stdout;
|
|
36656
|
-
}
|
|
36657
|
-
async function askPosix() {
|
|
36658
|
-
const { stdout } = await run5("ps", ["-e", "-o", "pid=,ppid=,args="], {
|
|
36659
|
-
timeout: ASK_TIMEOUT_MS,
|
|
36660
|
-
maxBuffer: 16 * 1024 * 1024
|
|
36661
|
-
});
|
|
36662
|
-
return stdout;
|
|
36663
37086
|
}
|
|
36664
|
-
var
|
|
36665
|
-
var
|
|
36666
|
-
|
|
37087
|
+
var DEFAULT_LSP_PORT = 6005, DIAGNOSTICS_TIMEOUT_MS = 5000;
|
|
37088
|
+
var init_lsp_client = __esm(() => {
|
|
37089
|
+
init_errors();
|
|
37090
|
+
init_framing();
|
|
37091
|
+
init_paths();
|
|
37092
|
+
init_ports();
|
|
36667
37093
|
});
|
|
36668
37094
|
|
|
36669
37095
|
// src/process-time.ts
|
|
@@ -36706,13 +37132,13 @@ var init_process_time = __esm(() => {
|
|
|
36706
37132
|
});
|
|
36707
37133
|
|
|
36708
37134
|
// src/project-scan.ts
|
|
36709
|
-
import { existsSync as
|
|
36710
|
-
import { join as
|
|
37135
|
+
import { existsSync as existsSync13, readdirSync as readdirSync5, readFileSync as readFileSync15 } from "node:fs";
|
|
37136
|
+
import { join as join17 } from "node:path";
|
|
36711
37137
|
function skipped(name) {
|
|
36712
37138
|
return name.startsWith(".") || SKIPPED.has(name);
|
|
36713
37139
|
}
|
|
36714
37140
|
function steppedOver(directory) {
|
|
36715
|
-
return
|
|
37141
|
+
return existsSync13(join17(directory, ".gdignore"));
|
|
36716
37142
|
}
|
|
36717
37143
|
function projectStructure(projectPath) {
|
|
36718
37144
|
const structure = { scenes: 0, scripts: 0, assets: 0, other: 0 };
|
|
@@ -36720,12 +37146,12 @@ function projectStructure(projectPath) {
|
|
|
36720
37146
|
if (steppedOver(directory)) {
|
|
36721
37147
|
return;
|
|
36722
37148
|
}
|
|
36723
|
-
for (const entry of
|
|
37149
|
+
for (const entry of readdirSync5(directory, { withFileTypes: true })) {
|
|
36724
37150
|
if (skipped(entry.name)) {
|
|
36725
37151
|
continue;
|
|
36726
37152
|
}
|
|
36727
37153
|
if (entry.isDirectory()) {
|
|
36728
|
-
visit(
|
|
37154
|
+
visit(join17(directory, entry.name));
|
|
36729
37155
|
} else if (entry.isFile()) {
|
|
36730
37156
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
36731
37157
|
if (extension === "tscn") {
|
|
@@ -36749,20 +37175,20 @@ function scriptsWithoutUid(projectPath) {
|
|
|
36749
37175
|
if (steppedOver(directory)) {
|
|
36750
37176
|
return;
|
|
36751
37177
|
}
|
|
36752
|
-
for (const entry of
|
|
37178
|
+
for (const entry of readdirSync5(directory, { withFileTypes: true })) {
|
|
36753
37179
|
if (skipped(entry.name)) {
|
|
36754
37180
|
continue;
|
|
36755
37181
|
}
|
|
36756
37182
|
const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
36757
37183
|
if (entry.isDirectory()) {
|
|
36758
|
-
visit(
|
|
37184
|
+
visit(join17(directory, entry.name), spelled);
|
|
36759
37185
|
continue;
|
|
36760
37186
|
}
|
|
36761
37187
|
if (!entry.isFile()) {
|
|
36762
37188
|
continue;
|
|
36763
37189
|
}
|
|
36764
37190
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
36765
|
-
if (CARRIES_A_UID.has(extension) && !
|
|
37191
|
+
if (CARRIES_A_UID.has(extension) && !existsSync13(join17(directory, `${entry.name}.uid`))) {
|
|
36766
37192
|
found.push(spelled);
|
|
36767
37193
|
}
|
|
36768
37194
|
}
|
|
@@ -36790,14 +37216,14 @@ function searchProject(projectPath, options) {
|
|
|
36790
37216
|
if (steppedOver(directory)) {
|
|
36791
37217
|
return;
|
|
36792
37218
|
}
|
|
36793
|
-
for (const entry of
|
|
37219
|
+
for (const entry of readdirSync5(directory, { withFileTypes: true })) {
|
|
36794
37220
|
if (full()) {
|
|
36795
37221
|
return;
|
|
36796
37222
|
}
|
|
36797
37223
|
if (skipped(entry.name)) {
|
|
36798
37224
|
continue;
|
|
36799
37225
|
}
|
|
36800
|
-
const entryPath =
|
|
37226
|
+
const entryPath = join17(directory, entry.name);
|
|
36801
37227
|
if (entry.isDirectory()) {
|
|
36802
37228
|
visit(entryPath);
|
|
36803
37229
|
continue;
|
|
@@ -36808,502 +37234,108 @@ function searchProject(projectPath, options) {
|
|
|
36808
37234
|
}
|
|
36809
37235
|
result.summary.files_searched += 1;
|
|
36810
37236
|
const matches = [];
|
|
36811
|
-
for (const [index, line] of
|
|
37237
|
+
for (const [index, line] of readFileSync15(entryPath, "utf8").split(`
|
|
36812
37238
|
`).entries()) {
|
|
36813
37239
|
if (full()) {
|
|
36814
37240
|
break;
|
|
36815
37241
|
}
|
|
36816
37242
|
const match = regex ? regex.exec(line)?.[0] : (options.caseSensitive ? line : line.toLowerCase()).includes(needle) ? options.query : "";
|
|
36817
|
-
if (match) {
|
|
36818
|
-
matches.push({ line: index + 1, content: line.trim(), match });
|
|
36819
|
-
result.summary.total_matches += 1;
|
|
36820
|
-
}
|
|
36821
|
-
}
|
|
36822
|
-
if (matches.length > 0) {
|
|
36823
|
-
const relativePath = entryPath.slice(projectPath.length + 1).replace(/\\/g, "/");
|
|
36824
|
-
result.results.push({ file: `res://${relativePath}`, matches });
|
|
36825
|
-
result.summary.files_with_matches += 1;
|
|
36826
|
-
}
|
|
36827
|
-
}
|
|
36828
|
-
};
|
|
36829
|
-
visit(projectPath);
|
|
36830
|
-
return result;
|
|
36831
|
-
}
|
|
36832
|
-
var SKIPPED, ASSET_EXTENSIONS, CARRIES_A_UID;
|
|
36833
|
-
var init_project_scan = __esm(() => {
|
|
36834
|
-
SKIPPED = new Set(["node_modules"]);
|
|
36835
|
-
ASSET_EXTENSIONS = new Set(["png", "jpg", "jpeg", "webp", "svg", "ttf", "otf", "wav", "mp3", "ogg"]);
|
|
36836
|
-
CARRIES_A_UID = new Set(["gd", "shader", "gdshader"]);
|
|
36837
|
-
});
|
|
36838
|
-
|
|
36839
|
-
// src/restart-note.ts
|
|
36840
|
-
import { mkdirSync as mkdirSync8, readFileSync as readFileSync14, rmSync as rmSync8, writeFileSync as writeFileSync10 } from "node:fs";
|
|
36841
|
-
import { dirname as dirname10, join as join16 } from "node:path";
|
|
36842
|
-
function restartNotePath(projectPath) {
|
|
36843
|
-
return join16(projectPath, ".godot", "gdharness-restart.json");
|
|
36844
|
-
}
|
|
36845
|
-
function noteRestartBegun(note) {
|
|
36846
|
-
const path = restartNotePath(note.projectPath);
|
|
36847
|
-
try {
|
|
36848
|
-
mkdirSync8(dirname10(path), { recursive: true });
|
|
36849
|
-
writeFileSync10(path, `${JSON.stringify(note, null, 2)}
|
|
36850
|
-
`, "utf8");
|
|
36851
|
-
} catch (error) {
|
|
36852
|
-
console.error(`[SERVER] Could not record the restart at ${path}: ${errorMessage(error)}`);
|
|
36853
|
-
}
|
|
36854
|
-
}
|
|
36855
|
-
function restartOwed(projectPath) {
|
|
36856
|
-
try {
|
|
36857
|
-
const parsed = JSON.parse(readFileSync14(restartNotePath(projectPath), "utf8"));
|
|
36858
|
-
if (typeof parsed !== "object" || parsed === null) {
|
|
36859
|
-
return null;
|
|
36860
|
-
}
|
|
36861
|
-
const said = parsed;
|
|
36862
|
-
if (typeof said.quitAt !== "string" || typeof said.byPid !== "number" || typeof said.ports !== "object") {
|
|
36863
|
-
return null;
|
|
36864
|
-
}
|
|
36865
|
-
return said;
|
|
36866
|
-
} catch {
|
|
36867
|
-
return null;
|
|
36868
|
-
}
|
|
36869
|
-
}
|
|
36870
|
-
function restartSettled(projectPath) {
|
|
36871
|
-
try {
|
|
36872
|
-
rmSync8(restartNotePath(projectPath), { force: true });
|
|
36873
|
-
} catch (error) {
|
|
36874
|
-
console.error(`[SERVER] Could not clear the restart note: ${errorMessage(error)}`);
|
|
36875
|
-
}
|
|
36876
|
-
}
|
|
36877
|
-
var init_restart_note = __esm(() => {
|
|
36878
|
-
init_errors();
|
|
36879
|
-
});
|
|
36880
|
-
|
|
36881
|
-
// src/runtime-client.ts
|
|
36882
|
-
import { existsSync as existsSync13, readdirSync as readdirSync5, readFileSync as readFileSync15, statSync as statSync3, unlinkSync } from "node:fs";
|
|
36883
|
-
import { createConnection as createConnection3 } from "node:net";
|
|
36884
|
-
import { tmpdir as tmpdir4 } from "node:os";
|
|
36885
|
-
import { dirname as dirname11, join as join17, resolve as resolve4 } from "node:path";
|
|
36886
|
-
import { setTimeout as delay2 } from "node:timers/promises";
|
|
36887
|
-
function runtimeDirectory(variables = process.env) {
|
|
36888
|
-
const explicit = envValue("GDHARNESS_RUNTIME_DIR", variables);
|
|
36889
|
-
if (explicit) {
|
|
36890
|
-
return explicit;
|
|
36891
|
-
}
|
|
36892
|
-
const perUser = envValue("XDG_RUNTIME_DIR", variables);
|
|
36893
|
-
return join17(perUser ?? tmpdir4(), "gdharness");
|
|
36894
|
-
}
|
|
36895
|
-
function runtimeDirectories(variables = process.env) {
|
|
36896
|
-
const candidates = [runtimeDirectory(variables)];
|
|
36897
|
-
const fallbacks = process.platform === "win32" ? [envValue("SystemRoot", variables) ?? envValue("windir", variables) ?? "C:\\Windows"] : ["/tmp", "/var/tmp"];
|
|
36898
|
-
for (const base of fallbacks) {
|
|
36899
|
-
candidates.push(join17(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
|
|
36900
|
-
}
|
|
36901
|
-
return [...new Set(candidates.map((path) => resolve4(path)))];
|
|
36902
|
-
}
|
|
36903
|
-
function errorReportOf(pid, directories = runtimeDirectories()) {
|
|
36904
|
-
for (const directory of directories) {
|
|
36905
|
-
const path = join17(directory, `runtime-${pid}.log`);
|
|
36906
|
-
if (existsSync13(path)) {
|
|
36907
|
-
return path;
|
|
36908
|
-
}
|
|
36909
|
-
}
|
|
36910
|
-
return null;
|
|
36911
|
-
}
|
|
36912
|
-
function processAlive(pid) {
|
|
36913
|
-
try {
|
|
36914
|
-
process.kill(pid, 0);
|
|
36915
|
-
return true;
|
|
36916
|
-
} catch (error) {
|
|
36917
|
-
return error.code === "EPERM";
|
|
36918
|
-
}
|
|
36919
|
-
}
|
|
36920
|
-
function parseAnnouncement(file, pid) {
|
|
36921
|
-
let fields;
|
|
36922
|
-
try {
|
|
36923
|
-
fields = asParams(JSON.parse(readFileSync15(file, "utf8")));
|
|
36924
|
-
} catch {
|
|
36925
|
-
return { kind: "rubbish" };
|
|
36926
|
-
}
|
|
36927
|
-
const project = readParams(fields, "project");
|
|
36928
|
-
const port = readNumber(fields, "port");
|
|
36929
|
-
const address = readString(fields, "address");
|
|
36930
|
-
const protocol = readNumber(fields, "protocol");
|
|
36931
|
-
if (readNumber(fields, "pid") !== pid || port === undefined || !Number.isInteger(port) || port < 1 || port > 65535 || address === undefined || project === undefined) {
|
|
36932
|
-
return { kind: "rubbish" };
|
|
36933
|
-
}
|
|
36934
|
-
const named = {
|
|
36935
|
-
name: readString(project, "name") ?? "",
|
|
36936
|
-
path: readString(project, "path") ?? ""
|
|
36937
|
-
};
|
|
36938
|
-
if (protocol !== RUNTIME_PROTOCOL) {
|
|
36939
|
-
return { kind: "unspoken", unspoken: { pid, protocol: protocol ?? 0, project: named } };
|
|
36940
|
-
}
|
|
36941
|
-
const editorPid = readNumber(fields, "editor_pid");
|
|
36942
|
-
return {
|
|
36943
|
-
kind: "runtime",
|
|
36944
|
-
endpoint: {
|
|
36945
|
-
pid,
|
|
36946
|
-
port,
|
|
36947
|
-
address,
|
|
36948
|
-
project: named,
|
|
36949
|
-
file,
|
|
36950
|
-
...editorPid !== undefined && Number.isInteger(editorPid) && editorPid > 0 ? { editorPid } : {}
|
|
36951
|
-
}
|
|
36952
|
-
};
|
|
36953
|
-
}
|
|
36954
|
-
function runtimesAnnounced(directories = runtimeDirectories()) {
|
|
36955
|
-
const running = new Map;
|
|
36956
|
-
const unspoken = new Map;
|
|
36957
|
-
for (const directory of directories) {
|
|
36958
|
-
for (const found of announcedIn(directory)) {
|
|
36959
|
-
if (found.kind === "runtime" && !running.has(found.endpoint.pid)) {
|
|
36960
|
-
running.set(found.endpoint.pid, found.endpoint);
|
|
36961
|
-
} else if (found.kind === "unspoken" && !unspoken.has(found.unspoken.pid)) {
|
|
36962
|
-
unspoken.set(found.unspoken.pid, found.unspoken);
|
|
36963
|
-
}
|
|
36964
|
-
}
|
|
36965
|
-
}
|
|
36966
|
-
const newest = (a, b) => b.pid - a.pid;
|
|
36967
|
-
return { running: [...running.values()].sort(newest), unspoken: [...unspoken.values()].sort(newest) };
|
|
36968
|
-
}
|
|
36969
|
-
function discoverRuntimes(directories = runtimeDirectories()) {
|
|
36970
|
-
return runtimesAnnounced(directories).running;
|
|
36971
|
-
}
|
|
36972
|
-
function wentAway(file, pid) {
|
|
36973
|
-
let project = "";
|
|
36974
|
-
try {
|
|
36975
|
-
const fields = asParams(JSON.parse(readFileSync15(file, "utf8")));
|
|
36976
|
-
project = readString(readParams(fields, "project") ?? {}, "path") ?? "";
|
|
36977
|
-
} catch {
|
|
36978
|
-
return;
|
|
36979
|
-
}
|
|
36980
|
-
if (project === "") {
|
|
36981
|
-
return;
|
|
36982
|
-
}
|
|
36983
|
-
gone.unshift({ pid, project, noticedAt: Date.now() });
|
|
36984
|
-
gone.length = Math.min(gone.length, GONE_KEPT);
|
|
36985
|
-
}
|
|
36986
|
-
function announcedAndGone(projectPath) {
|
|
36987
|
-
if (projectPath === undefined) {
|
|
36988
|
-
return [];
|
|
36989
|
-
}
|
|
36990
|
-
const since = Date.now() - GONE_WINDOW_MS;
|
|
36991
|
-
const wanted = resolve4(projectPath);
|
|
36992
|
-
return gone.filter((one) => one.noticedAt >= since && resolve4(one.project) === wanted);
|
|
36993
|
-
}
|
|
36994
|
-
function strangersAmong(candidates, now, startTimes = startTimesOf) {
|
|
36995
|
-
const toJudge = candidates.filter((one) => {
|
|
36996
|
-
if (now - one.writtenAt < JUDGED_AFTER_MS) {
|
|
36997
|
-
return false;
|
|
36998
|
-
}
|
|
36999
|
-
const known = verdicts.get(one.file);
|
|
37000
|
-
return known === undefined || known.writtenAt !== one.writtenAt || !known.stranger && now - known.judgedAt > CONFIRMED_FOR_MS;
|
|
37001
|
-
});
|
|
37002
|
-
if (toJudge.length > 0) {
|
|
37003
|
-
const began = startTimes(toJudge.map((one) => one.pid));
|
|
37004
|
-
for (const one of toJudge) {
|
|
37005
|
-
const at = began.get(one.pid);
|
|
37006
|
-
const stranger = at !== undefined && at - one.writtenAt > STARTED_AFTER_ANNOUNCING_MS;
|
|
37007
|
-
verdicts.set(one.file, { writtenAt: one.writtenAt, judgedAt: now, stranger });
|
|
37008
|
-
}
|
|
37009
|
-
}
|
|
37010
|
-
return new Set(candidates.filter((one) => verdicts.get(one.file)?.stranger === true).map((one) => one.file));
|
|
37011
|
-
}
|
|
37012
|
-
function announcedIn(directory) {
|
|
37013
|
-
if (!existsSync13(directory)) {
|
|
37014
|
-
return [];
|
|
37015
|
-
}
|
|
37016
|
-
const found = [];
|
|
37017
|
-
const candidates = [];
|
|
37018
|
-
const entries = [];
|
|
37019
|
-
for (const entry of readdirSync5(directory)) {
|
|
37020
|
-
const report = ERROR_REPORT_PATTERN.exec(entry);
|
|
37021
|
-
if (report) {
|
|
37022
|
-
sweepErrorReport(join17(directory, entry), Number.parseInt(report[1] ?? "", 10));
|
|
37023
|
-
continue;
|
|
37024
|
-
}
|
|
37025
|
-
const match = ANNOUNCEMENT_PATTERN.exec(entry);
|
|
37026
|
-
if (!match) {
|
|
37027
|
-
continue;
|
|
37028
|
-
}
|
|
37029
|
-
const file = join17(directory, entry);
|
|
37030
|
-
const pid = Number.parseInt(match[1] ?? "", 10);
|
|
37031
|
-
const alive = processAlive(pid);
|
|
37032
|
-
entries.push({ file, pid, alive });
|
|
37033
|
-
if (alive) {
|
|
37034
|
-
try {
|
|
37035
|
-
candidates.push({ file, pid, writtenAt: statSync3(file).mtimeMs });
|
|
37036
|
-
} catch {}
|
|
37037
|
-
}
|
|
37038
|
-
}
|
|
37039
|
-
const strangers = strangersAmong(candidates, Date.now());
|
|
37040
|
-
for (const { file, pid, alive } of entries) {
|
|
37041
|
-
const announced = alive && !strangers.has(file) ? parseAnnouncement(file, pid) : { kind: "rubbish" };
|
|
37042
|
-
if (announced.kind === "rubbish") {
|
|
37043
|
-
if (!alive || strangers.has(file)) {
|
|
37044
|
-
wentAway(file, pid);
|
|
37243
|
+
if (match) {
|
|
37244
|
+
matches.push({ line: index + 1, content: line.trim(), match });
|
|
37245
|
+
result.summary.total_matches += 1;
|
|
37246
|
+
}
|
|
37247
|
+
}
|
|
37248
|
+
if (matches.length > 0) {
|
|
37249
|
+
const relativePath = entryPath.slice(projectPath.length + 1).replace(/\\/g, "/");
|
|
37250
|
+
result.results.push({ file: `res://${relativePath}`, matches });
|
|
37251
|
+
result.summary.files_with_matches += 1;
|
|
37045
37252
|
}
|
|
37046
|
-
try {
|
|
37047
|
-
unlinkSync(file);
|
|
37048
|
-
} catch {}
|
|
37049
|
-
verdicts.delete(file);
|
|
37050
|
-
continue;
|
|
37051
|
-
}
|
|
37052
|
-
found.push(announced);
|
|
37053
|
-
}
|
|
37054
|
-
const held = new Set(entries.map((one) => one.file));
|
|
37055
|
-
const here = join17(directory, ".");
|
|
37056
|
-
for (const file of verdicts.keys()) {
|
|
37057
|
-
if (dirname11(file) === here && !held.has(file)) {
|
|
37058
|
-
verdicts.delete(file);
|
|
37059
37253
|
}
|
|
37060
|
-
}
|
|
37061
|
-
|
|
37254
|
+
};
|
|
37255
|
+
visit(projectPath);
|
|
37256
|
+
return result;
|
|
37062
37257
|
}
|
|
37063
|
-
|
|
37064
|
-
|
|
37065
|
-
|
|
37066
|
-
|
|
37067
|
-
|
|
37068
|
-
|
|
37069
|
-
|
|
37070
|
-
|
|
37071
|
-
|
|
37072
|
-
|
|
37073
|
-
|
|
37074
|
-
|
|
37075
|
-
await delay2(Math.min(LOOK_EVERY_MS, left));
|
|
37076
|
-
}
|
|
37077
|
-
wentAway(first, pid);
|
|
37078
|
-
for (const file of files) {
|
|
37079
|
-
try {
|
|
37080
|
-
unlinkSync(file);
|
|
37081
|
-
} catch {}
|
|
37082
|
-
verdicts.delete(file);
|
|
37083
|
-
}
|
|
37258
|
+
var SKIPPED, ASSET_EXTENSIONS, CARRIES_A_UID;
|
|
37259
|
+
var init_project_scan = __esm(() => {
|
|
37260
|
+
SKIPPED = new Set(["node_modules"]);
|
|
37261
|
+
ASSET_EXTENSIONS = new Set(["png", "jpg", "jpeg", "webp", "svg", "ttf", "otf", "wav", "mp3", "ogg"]);
|
|
37262
|
+
CARRIES_A_UID = new Set(["gd", "shader", "gdshader"]);
|
|
37263
|
+
});
|
|
37264
|
+
|
|
37265
|
+
// src/restart-note.ts
|
|
37266
|
+
import { mkdirSync as mkdirSync9, readFileSync as readFileSync16, rmSync as rmSync8, writeFileSync as writeFileSync11 } from "node:fs";
|
|
37267
|
+
import { dirname as dirname12, join as join18 } from "node:path";
|
|
37268
|
+
function restartNotePath(projectPath) {
|
|
37269
|
+
return join18(projectPath, ".godot", "gdharness-restart.json");
|
|
37084
37270
|
}
|
|
37085
|
-
function
|
|
37086
|
-
|
|
37087
|
-
return;
|
|
37088
|
-
}
|
|
37271
|
+
function noteRestartBegun(note) {
|
|
37272
|
+
const path = restartNotePath(note.projectPath);
|
|
37089
37273
|
try {
|
|
37090
|
-
|
|
37091
|
-
|
|
37092
|
-
|
|
37093
|
-
} catch {
|
|
37274
|
+
mkdirSync9(dirname12(path), { recursive: true });
|
|
37275
|
+
writeFileSync11(path, `${JSON.stringify(note, null, 2)}
|
|
37276
|
+
`, "utf8");
|
|
37277
|
+
} catch (error) {
|
|
37278
|
+
console.error(`[SERVER] Could not record the restart at ${path}: ${errorMessage(error)}`);
|
|
37279
|
+
}
|
|
37094
37280
|
}
|
|
37095
|
-
|
|
37096
|
-
|
|
37097
|
-
|
|
37098
|
-
|
|
37099
|
-
|
|
37100
|
-
const fresh = discoverRuntimes(directories).find((endpoint) => !before.has(endpoint.pid) && resolve4(endpoint.project.path) === wanted && (waiting.accept?.(endpoint) ?? true));
|
|
37101
|
-
if (fresh !== undefined) {
|
|
37102
|
-
return fresh;
|
|
37281
|
+
function restartOwed(projectPath) {
|
|
37282
|
+
try {
|
|
37283
|
+
const parsed = JSON.parse(readFileSync16(restartNotePath(projectPath), "utf8"));
|
|
37284
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
37285
|
+
return null;
|
|
37103
37286
|
}
|
|
37104
|
-
const
|
|
37105
|
-
if (
|
|
37287
|
+
const said = parsed;
|
|
37288
|
+
if (typeof said.quitAt !== "string" || typeof said.byPid !== "number" || typeof said.ports !== "object") {
|
|
37106
37289
|
return null;
|
|
37107
37290
|
}
|
|
37108
|
-
|
|
37291
|
+
return said;
|
|
37292
|
+
} catch {
|
|
37293
|
+
return null;
|
|
37109
37294
|
}
|
|
37110
37295
|
}
|
|
37111
|
-
function
|
|
37112
|
-
|
|
37113
|
-
}
|
|
37114
|
-
|
|
37115
|
-
|
|
37116
|
-
const half = unspoken.some((game) => game.protocol > RUNTIME_PROTOCOL) ? "this server is the older half: reconnect it so it spawns the installed version" : "the addon is the older half: reinstall it and restart the game";
|
|
37117
|
-
return `A game is running, in a protocol this server does not speak ${named}. This server speaks ${RUNTIME_PROTOCOL}, so ${half}.`;
|
|
37118
|
-
}
|
|
37119
|
-
function chooseRuntime(endpoints, projectPath, unspoken = [], pid) {
|
|
37120
|
-
if (pid !== undefined) {
|
|
37121
|
-
const named = endpoints.find((endpoint) => endpoint.pid === pid);
|
|
37122
|
-
if (named !== undefined) {
|
|
37123
|
-
if (projectPath !== undefined && resolve4(named.project.path) !== resolve4(projectPath)) {
|
|
37124
|
-
return {
|
|
37125
|
-
problem: `The game with pid ${pid} is running ${named.project.path}, not ${resolve4(projectPath)}. Running: ${endpoints.map(describe2).join("; ")}.`
|
|
37126
|
-
};
|
|
37127
|
-
}
|
|
37128
|
-
return { endpoint: named };
|
|
37129
|
-
}
|
|
37130
|
-
const tooNewToo = unspoken.find((game) => game.pid === pid);
|
|
37131
|
-
if (tooNewToo !== undefined) {
|
|
37132
|
-
return { problem: tooNew([tooNewToo]) };
|
|
37133
|
-
}
|
|
37134
|
-
return {
|
|
37135
|
-
problem: endpoints.length === 0 ? `No game with the runtime addon is running, so there is none with pid ${pid}. Start one with editor_run, or play the project from the editor with the addon enabled.` : `No running game has pid ${pid}. Running: ${endpoints.map(describe2).join("; ")}. editor_status lists them under runtimes as they come and go.`
|
|
37136
|
-
};
|
|
37137
|
-
}
|
|
37138
|
-
if (endpoints.length === 0) {
|
|
37139
|
-
if (unspoken.length > 0) {
|
|
37140
|
-
return { problem: tooNew(unspoken) };
|
|
37141
|
-
}
|
|
37142
|
-
const went = announcedAndGone(projectPath);
|
|
37143
|
-
const first = went[0];
|
|
37144
|
-
if (first !== undefined) {
|
|
37145
|
-
const ago = Math.max(1, Math.round((Date.now() - first.noticedAt) / 1000));
|
|
37146
|
-
const others = went.length - 1;
|
|
37147
|
-
const earlier = others === 0 ? "" : ` ${others} earlier ${others === 1 ? "one" : "ones"} went the same way.`;
|
|
37148
|
-
return {
|
|
37149
|
-
problem: `A game with the runtime addon announced itself and its process is gone: pid ${first.pid}` + `${first.project === "" ? "" : ` for ${first.project}`}, noticed ${ago}s ago.${earlier}` + " It quit or was ended rather than never starting, so editor_output has what it printed" + " on the way, including whatever it broke on. Start another with editor_run once you have read it."
|
|
37150
|
-
};
|
|
37151
|
-
}
|
|
37152
|
-
return {
|
|
37153
|
-
problem: "No game with the runtime addon is running. Start one with editor_run, or play the project from the editor with the addon enabled."
|
|
37154
|
-
};
|
|
37155
|
-
}
|
|
37156
|
-
if (projectPath !== undefined) {
|
|
37157
|
-
const wanted = resolve4(projectPath);
|
|
37158
|
-
const matching = endpoints.filter((endpoint) => resolve4(endpoint.project.path) === wanted);
|
|
37159
|
-
if (matching.length === 0) {
|
|
37160
|
-
return {
|
|
37161
|
-
problem: `No running game is from ${wanted}. Running: ${endpoints.map(describe2).join("; ")}.`
|
|
37162
|
-
};
|
|
37163
|
-
}
|
|
37164
|
-
if (matching.length === 1 && matching[0]) {
|
|
37165
|
-
return { endpoint: matching[0] };
|
|
37166
|
-
}
|
|
37167
|
-
return {
|
|
37168
|
-
problem: `Several games are running from ${wanted}: ${matching.map(describe2).join("; ")}. Pass pid to choose one.`
|
|
37169
|
-
};
|
|
37170
|
-
}
|
|
37171
|
-
if (endpoints.length === 1 && endpoints[0]) {
|
|
37172
|
-
return { endpoint: endpoints[0] };
|
|
37296
|
+
function restartSettled(projectPath) {
|
|
37297
|
+
try {
|
|
37298
|
+
rmSync8(restartNotePath(projectPath), { force: true });
|
|
37299
|
+
} catch (error) {
|
|
37300
|
+
console.error(`[SERVER] Could not clear the restart note: ${errorMessage(error)}`);
|
|
37173
37301
|
}
|
|
37174
|
-
return {
|
|
37175
|
-
problem: `Several games are running: ${endpoints.map(describe2).join("; ")}. Pass projectPath to choose one, or pid when they are from one project.`
|
|
37176
|
-
};
|
|
37177
|
-
}
|
|
37178
|
-
function runtimeRequest(endpoint, command, params, timeoutMs) {
|
|
37179
|
-
const id = nextRequestId++;
|
|
37180
|
-
return new Promise((settle) => {
|
|
37181
|
-
let done = false;
|
|
37182
|
-
let buffered = "";
|
|
37183
|
-
let welcomed = false;
|
|
37184
|
-
const socket = createConnection3({ port: endpoint.port, host: endpoint.address });
|
|
37185
|
-
const finish = (reply) => {
|
|
37186
|
-
if (done) {
|
|
37187
|
-
return;
|
|
37188
|
-
}
|
|
37189
|
-
done = true;
|
|
37190
|
-
clearTimeout(timer);
|
|
37191
|
-
socket.destroy();
|
|
37192
|
-
settle(reply);
|
|
37193
|
-
};
|
|
37194
|
-
const timer = setTimeout(() => {
|
|
37195
|
-
finish({
|
|
37196
|
-
ok: false,
|
|
37197
|
-
reason: "busy",
|
|
37198
|
-
message: `The game (${describe2(endpoint)}) accepted the connection but did not answer '${command}' within ${timeoutMs}ms. It may be paused at a breakpoint or stuck in a long frame.`
|
|
37199
|
-
});
|
|
37200
|
-
}, timeoutMs);
|
|
37201
|
-
socket.setEncoding("utf8");
|
|
37202
|
-
socket.on("connect", () => {
|
|
37203
|
-
socket.write(`${JSON.stringify({ id, command, params })}
|
|
37204
|
-
`);
|
|
37205
|
-
});
|
|
37206
|
-
socket.on("data", (chunk) => {
|
|
37207
|
-
buffered += chunk;
|
|
37208
|
-
let newline = buffered.indexOf(`
|
|
37209
|
-
`);
|
|
37210
|
-
while (newline !== -1 && !done) {
|
|
37211
|
-
const line = buffered.slice(0, newline).trim();
|
|
37212
|
-
buffered = buffered.slice(newline + 1);
|
|
37213
|
-
newline = buffered.indexOf(`
|
|
37214
|
-
`);
|
|
37215
|
-
if (line === "") {
|
|
37216
|
-
continue;
|
|
37217
|
-
}
|
|
37218
|
-
let message;
|
|
37219
|
-
try {
|
|
37220
|
-
message = asParams(JSON.parse(line));
|
|
37221
|
-
} catch {
|
|
37222
|
-
continue;
|
|
37223
|
-
}
|
|
37224
|
-
if (!welcomed) {
|
|
37225
|
-
welcomed = true;
|
|
37226
|
-
if (readString(message, "type") === "welcome") {
|
|
37227
|
-
const spoken = readNumber(message, "protocol");
|
|
37228
|
-
if (spoken !== RUNTIME_PROTOCOL) {
|
|
37229
|
-
finish({
|
|
37230
|
-
ok: false,
|
|
37231
|
-
reason: "protocol",
|
|
37232
|
-
message: `The game (${describe2(endpoint)}) speaks runtime protocol ${spoken ?? "unknown"} and this server speaks ${RUNTIME_PROTOCOL}. Reinstall the runtime addon into the project.`
|
|
37233
|
-
});
|
|
37234
|
-
return;
|
|
37235
|
-
}
|
|
37236
|
-
continue;
|
|
37237
|
-
}
|
|
37238
|
-
}
|
|
37239
|
-
if (message["id"] !== id) {
|
|
37240
|
-
continue;
|
|
37241
|
-
}
|
|
37242
|
-
if (readString(message, "type") === "error") {
|
|
37243
|
-
finish({ ok: false, reason: "error", message: readString(message, "message") ?? "unknown error" });
|
|
37244
|
-
return;
|
|
37245
|
-
}
|
|
37246
|
-
finish({ ok: true, payload: message, endpoint });
|
|
37247
|
-
}
|
|
37248
|
-
});
|
|
37249
|
-
socket.on("error", (error) => {
|
|
37250
|
-
finish({
|
|
37251
|
-
ok: false,
|
|
37252
|
-
reason: "refused",
|
|
37253
|
-
message: `The game (${describe2(endpoint)}) is running but nothing answered on its port: ${error.code ?? error.message}. It may still be starting, or be shutting down.`
|
|
37254
|
-
});
|
|
37255
|
-
});
|
|
37256
|
-
socket.on("close", () => {
|
|
37257
|
-
finish({
|
|
37258
|
-
ok: false,
|
|
37259
|
-
reason: "refused",
|
|
37260
|
-
message: `The game (${describe2(endpoint)}) closed the connection before answering '${command}'.`
|
|
37261
|
-
});
|
|
37262
|
-
});
|
|
37263
|
-
});
|
|
37264
37302
|
}
|
|
37265
|
-
var
|
|
37266
|
-
|
|
37267
|
-
init_process_children();
|
|
37268
|
-
ANNOUNCEMENT_PATTERN = /^runtime-(\d+)\.json$/;
|
|
37269
|
-
ERROR_REPORT_PATTERN = /^runtime-(\d+)\.log$/;
|
|
37270
|
-
ERROR_REPORT_KEEP_MS = 60 * 60 * 1000;
|
|
37271
|
-
gone = [];
|
|
37272
|
-
verdicts = new Map;
|
|
37303
|
+
var init_restart_note = __esm(() => {
|
|
37304
|
+
init_errors();
|
|
37273
37305
|
});
|
|
37274
37306
|
|
|
37275
37307
|
// src/run-record.ts
|
|
37276
37308
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
37277
|
-
import { mkdirSync as
|
|
37278
|
-
import { basename as basename3, join as
|
|
37309
|
+
import { mkdirSync as mkdirSync10, openSync, readdirSync as readdirSync6, readFileSync as readFileSync17, rmSync as rmSync9, statSync as statSync4, writeFileSync as writeFileSync12 } from "node:fs";
|
|
37310
|
+
import { basename as basename3, join as join19 } from "node:path";
|
|
37279
37311
|
function runsDirectory() {
|
|
37280
|
-
return
|
|
37312
|
+
return join19(runtimeDirectory(), "runs");
|
|
37281
37313
|
}
|
|
37282
37314
|
function recordPath() {
|
|
37283
|
-
return
|
|
37315
|
+
return join19(runsDirectory(), "run.json");
|
|
37284
37316
|
}
|
|
37285
37317
|
function recordPaths() {
|
|
37286
|
-
return runtimeDirectories().map((directory) =>
|
|
37318
|
+
return runtimeDirectories().map((directory) => join19(directory, "runs", "run.json"));
|
|
37287
37319
|
}
|
|
37288
37320
|
function openTranscript(startedAt) {
|
|
37289
|
-
|
|
37290
|
-
const path =
|
|
37321
|
+
mkdirSync10(runsDirectory(), { recursive: true });
|
|
37322
|
+
const path = join19(runsDirectory(), `run-${startedAt}.log`);
|
|
37291
37323
|
return { path, fd: openSync(path, "a") };
|
|
37292
37324
|
}
|
|
37293
37325
|
function writeRunRecord(record) {
|
|
37294
|
-
|
|
37295
|
-
|
|
37326
|
+
mkdirSync10(runsDirectory(), { recursive: true });
|
|
37327
|
+
writeFileSync12(recordPath(), JSON.stringify(record, null, 2), "utf8");
|
|
37296
37328
|
}
|
|
37297
37329
|
function editorNotePath() {
|
|
37298
|
-
return
|
|
37330
|
+
return join19(runsDirectory(), "editor-run.json");
|
|
37299
37331
|
}
|
|
37300
37332
|
function writeEditorRunNote(note) {
|
|
37301
|
-
|
|
37302
|
-
|
|
37333
|
+
mkdirSync10(runsDirectory(), { recursive: true });
|
|
37334
|
+
writeFileSync12(editorNotePath(), JSON.stringify(note, null, 2), "utf8");
|
|
37303
37335
|
}
|
|
37304
37336
|
function readEditorRunNote() {
|
|
37305
37337
|
for (const directory of runtimeDirectories()) {
|
|
37306
|
-
const note = editorNoteAt(
|
|
37338
|
+
const note = editorNoteAt(join19(directory, "runs", "editor-run.json"));
|
|
37307
37339
|
if (note !== null) {
|
|
37308
37340
|
return note;
|
|
37309
37341
|
}
|
|
@@ -37313,7 +37345,7 @@ function readEditorRunNote() {
|
|
|
37313
37345
|
function editorNoteAt(path) {
|
|
37314
37346
|
let parsed;
|
|
37315
37347
|
try {
|
|
37316
|
-
parsed = JSON.parse(
|
|
37348
|
+
parsed = JSON.parse(readFileSync17(path, "utf8"));
|
|
37317
37349
|
} catch {
|
|
37318
37350
|
return null;
|
|
37319
37351
|
}
|
|
@@ -37336,7 +37368,7 @@ function recordRunEnded(pid, exitCode) {
|
|
|
37336
37368
|
return;
|
|
37337
37369
|
}
|
|
37338
37370
|
try {
|
|
37339
|
-
|
|
37371
|
+
writeFileSync12(path, JSON.stringify({ ...record, exitCode }, null, 2), "utf8");
|
|
37340
37372
|
} catch {}
|
|
37341
37373
|
}
|
|
37342
37374
|
function readRunRecord() {
|
|
@@ -37351,7 +37383,7 @@ function readRunRecord() {
|
|
|
37351
37383
|
function recordAt(path) {
|
|
37352
37384
|
let parsed;
|
|
37353
37385
|
try {
|
|
37354
|
-
parsed = JSON.parse(
|
|
37386
|
+
parsed = JSON.parse(readFileSync17(path, "utf8"));
|
|
37355
37387
|
} catch {
|
|
37356
37388
|
return null;
|
|
37357
37389
|
}
|
|
@@ -37396,7 +37428,7 @@ function runningAs(pid) {
|
|
|
37396
37428
|
const began = startedAt(pid, null);
|
|
37397
37429
|
const when = began === null ? {} : { startedAt: began };
|
|
37398
37430
|
if (process.platform === "linux") {
|
|
37399
|
-
const raw =
|
|
37431
|
+
const raw = readFileSync17(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
|
|
37400
37432
|
return raw === "" ? null : { kind: "commandLine", text: raw, ...when };
|
|
37401
37433
|
}
|
|
37402
37434
|
const args = execFileSync2("ps", ["-p", String(pid), "-o", "args="], {
|
|
@@ -37547,7 +37579,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
37547
37579
|
if (!entry.startsWith("run-") || !entry.endsWith(".log")) {
|
|
37548
37580
|
continue;
|
|
37549
37581
|
}
|
|
37550
|
-
const path =
|
|
37582
|
+
const path = join19(directory, entry);
|
|
37551
37583
|
if (path === keep || ageMs(path, now) < keepMs) {
|
|
37552
37584
|
continue;
|
|
37553
37585
|
}
|
|
@@ -37570,15 +37602,16 @@ import {
|
|
|
37570
37602
|
closeSync,
|
|
37571
37603
|
existsSync as existsSync14,
|
|
37572
37604
|
fstatSync,
|
|
37573
|
-
mkdirSync as
|
|
37605
|
+
mkdirSync as mkdirSync11,
|
|
37574
37606
|
mkdtempSync as mkdtempSync3,
|
|
37575
37607
|
openSync as openSync2,
|
|
37576
37608
|
readdirSync as readdirSync7,
|
|
37577
|
-
readFileSync as
|
|
37578
|
-
readSync
|
|
37609
|
+
readFileSync as readFileSync18,
|
|
37610
|
+
readSync,
|
|
37611
|
+
statSync as statSync5
|
|
37579
37612
|
} from "node:fs";
|
|
37580
37613
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
37581
|
-
import { basename as basename4, dirname as
|
|
37614
|
+
import { basename as basename4, dirname as dirname13, join as join20, normalize as normalize3, relative as relative3 } from "node:path";
|
|
37582
37615
|
import { setTimeout as delay3 } from "node:timers/promises";
|
|
37583
37616
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
37584
37617
|
import { promisify as promisify7 } from "node:util";
|
|
@@ -37701,10 +37734,11 @@ function runtimeVerdict(endpoint, after) {
|
|
|
37701
37734
|
};
|
|
37702
37735
|
}
|
|
37703
37736
|
const state = after.playingSeen === false ? "the editor has not yet reported the game as playing, which a restarted editor does not until its scan is over" : "the game is still running";
|
|
37737
|
+
const sized = after.sizedToMs === undefined ? "" : `, half as long again as the ${after.sizedToMs}ms its last game took,`;
|
|
37704
37738
|
return {
|
|
37705
37739
|
listening: false,
|
|
37706
37740
|
mayYetAnnounce: after.running,
|
|
37707
|
-
note: after.running ? `nothing announced itself within ${after.budgetMs}ms and ${state}, so it may announce a moment from now: editor_status says whether it has, and editor_run start takes runtimeWaitMs to wait longer than this${after.withArgs ? ", which a run carrying its own arguments may well need, since whatever they ask the game to do before its first frame is inside this wait" : ""}` : `nothing announced itself within ${after.budgetMs}ms and the game is no longer running, so nothing is going to: editor_output has what it printed on the way down`,
|
|
37741
|
+
note: after.running ? `nothing announced itself within ${after.budgetMs}ms${sized} and ${state}, so it may announce a moment from now: editor_status says whether it has, and editor_run start takes runtimeWaitMs to wait longer than this${after.withArgs ? ", which a run carrying its own arguments may well need, since whatever they ask the game to do before its first frame is inside this wait" : ""}` : `nothing announced itself within ${after.budgetMs}ms${sized} and the game is no longer running, so nothing is going to: editor_output has what it printed on the way down`,
|
|
37708
37742
|
heldAt: null
|
|
37709
37743
|
};
|
|
37710
37744
|
}
|
|
@@ -37809,7 +37843,7 @@ function aboveTheRunner(entry) {
|
|
|
37809
37843
|
}
|
|
37810
37844
|
function elsewhereIn(projectPath, asked) {
|
|
37811
37845
|
const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
|
|
37812
|
-
if (wanted === "" || existsSync14(
|
|
37846
|
+
if (wanted === "" || existsSync14(join20(projectPath, wanted))) {
|
|
37813
37847
|
return "";
|
|
37814
37848
|
}
|
|
37815
37849
|
const root = (wanted.split("/")[0] ?? "").toLowerCase();
|
|
@@ -37823,7 +37857,7 @@ function elsewhereIn(projectPath, asked) {
|
|
|
37823
37857
|
return near.length === 0 ? "" : ` The project does have ${there}.`;
|
|
37824
37858
|
}
|
|
37825
37859
|
function hasMainScene(projectFile) {
|
|
37826
|
-
const scene = parseProjectGodot(
|
|
37860
|
+
const scene = parseProjectGodot(readFileSync18(projectFile, "utf8"))["application"]?.["run/main_scene"];
|
|
37827
37861
|
return typeof scene === "string" && scene !== "";
|
|
37828
37862
|
}
|
|
37829
37863
|
function runtimeAddonAnnouncesEditors(projectPath) {
|
|
@@ -37831,13 +37865,13 @@ function runtimeAddonAnnouncesEditors(projectPath) {
|
|
|
37831
37865
|
return false;
|
|
37832
37866
|
}
|
|
37833
37867
|
try {
|
|
37834
|
-
return
|
|
37868
|
+
return readFileSync18(join20(projectPath, RUNTIME_AUTOLOAD2.path), "utf8").includes('"editor_pid"');
|
|
37835
37869
|
} catch {
|
|
37836
37870
|
return false;
|
|
37837
37871
|
}
|
|
37838
37872
|
}
|
|
37839
37873
|
function editorPlaysHeadless(projectFile) {
|
|
37840
|
-
const runArgs = parseProjectGodot(
|
|
37874
|
+
const runArgs = parseProjectGodot(readFileSync18(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
|
|
37841
37875
|
return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
|
|
37842
37876
|
}
|
|
37843
37877
|
function camelCased(params) {
|
|
@@ -37853,7 +37887,7 @@ function camelCased(params) {
|
|
|
37853
37887
|
class GodotServer {
|
|
37854
37888
|
mcp;
|
|
37855
37889
|
locator = new GodotLocator2;
|
|
37856
|
-
operationsScript =
|
|
37890
|
+
operationsScript = join20(__dirname2, "godot", "operations", "godot_operations.gd");
|
|
37857
37891
|
godotBridge;
|
|
37858
37892
|
tools = buildToolDefinitions();
|
|
37859
37893
|
activeProcess = null;
|
|
@@ -38511,7 +38545,7 @@ class GodotServer {
|
|
|
38511
38545
|
if (path === undefined) {
|
|
38512
38546
|
return { ok: false, response: this.createErrorResponse("projectPath is required.") };
|
|
38513
38547
|
}
|
|
38514
|
-
const file =
|
|
38548
|
+
const file = join20(path, "project.godot");
|
|
38515
38549
|
if (!existsSync14(file)) {
|
|
38516
38550
|
return {
|
|
38517
38551
|
ok: false,
|
|
@@ -38735,7 +38769,7 @@ class GodotServer {
|
|
|
38735
38769
|
if (!engine.ok) {
|
|
38736
38770
|
return engine.response;
|
|
38737
38771
|
}
|
|
38738
|
-
const application = parseProjectGodot(
|
|
38772
|
+
const application = parseProjectGodot(readFileSync18(project.value.file, "utf8"))["application"] ?? {};
|
|
38739
38773
|
const name = application["config/name"];
|
|
38740
38774
|
const mainScene = application["run/main_scene"];
|
|
38741
38775
|
const info = {
|
|
@@ -38796,18 +38830,18 @@ class GodotServer {
|
|
|
38796
38830
|
if (!engine.ok) {
|
|
38797
38831
|
return engine.response;
|
|
38798
38832
|
}
|
|
38799
|
-
const folder =
|
|
38833
|
+
const folder = dirname13(output.absolutePath);
|
|
38800
38834
|
try {
|
|
38801
|
-
|
|
38835
|
+
mkdirSync11(folder, { recursive: true });
|
|
38802
38836
|
} catch (error) {
|
|
38803
|
-
return this.createErrorResponse(`Cannot create ${
|
|
38837
|
+
return this.createErrorResponse(`Cannot create ${dirname13(output.relativePath)} to export into: ${errorMessage(error)}`);
|
|
38804
38838
|
}
|
|
38805
38839
|
const debug = readBoolean(args, "debug") ?? false;
|
|
38806
|
-
const exportLogs = mkdtempSync3(
|
|
38840
|
+
const exportLogs = mkdtempSync3(join20(tmpdir5(), "gdharness-export-"));
|
|
38807
38841
|
const exportArgs = [
|
|
38808
38842
|
"--headless",
|
|
38809
38843
|
"--log-file",
|
|
38810
|
-
|
|
38844
|
+
join20(exportLogs, "engine.log"),
|
|
38811
38845
|
"--path",
|
|
38812
38846
|
project.value.path,
|
|
38813
38847
|
debug ? "--export-debug" : "--export-release",
|
|
@@ -38865,7 +38899,7 @@ class GodotServer {
|
|
|
38865
38899
|
return contained.response;
|
|
38866
38900
|
}
|
|
38867
38901
|
const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
|
|
38868
|
-
if (!existsSync14(
|
|
38902
|
+
if (!existsSync14(join20(project.value.path, runner))) {
|
|
38869
38903
|
return this.createErrorResponse(`gdUnit4 is not installed in this project: no ${runner}.`, [
|
|
38870
38904
|
"Install gdUnit4 under addons/gdUnit4, from https://github.com/godot-gdunit-labs/gdUnit4"
|
|
38871
38905
|
]);
|
|
@@ -38900,7 +38934,7 @@ class GodotServer {
|
|
|
38900
38934
|
];
|
|
38901
38935
|
const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 600000;
|
|
38902
38936
|
this.logDebug(`Running tests: ${engine.value} ${cmdArgs.join(" ")}`);
|
|
38903
|
-
const userData = mkdtempSync3(
|
|
38937
|
+
const userData = mkdtempSync3(join20(tmpdir5(), "gdharness-tests-"));
|
|
38904
38938
|
const run = this.spawnGame(engine.value, cmdArgs, userDataIn(userData));
|
|
38905
38939
|
const hung = await new Promise((resolve) => {
|
|
38906
38940
|
const timer = setTimeout(() => {
|
|
@@ -38916,14 +38950,14 @@ class GodotServer {
|
|
|
38916
38950
|
resolve(false);
|
|
38917
38951
|
});
|
|
38918
38952
|
});
|
|
38919
|
-
const reportsDir =
|
|
38953
|
+
const reportsDir = join20(project.value.path, ".godot", ...ours.split("/"));
|
|
38920
38954
|
let report = null;
|
|
38921
38955
|
let reportProblem = null;
|
|
38922
38956
|
try {
|
|
38923
38957
|
const written = existsSync14(reportsDir) ? readdirSync7(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
|
|
38924
38958
|
const newest = written.at(-1);
|
|
38925
38959
|
if (newest !== undefined) {
|
|
38926
|
-
report = parseJUnit(
|
|
38960
|
+
report = parseJUnit(readFileSync18(join20(reportsDir, newest, "results.xml"), "utf8"));
|
|
38927
38961
|
}
|
|
38928
38962
|
} catch (error) {
|
|
38929
38963
|
reportProblem = errorMessage(error);
|
|
@@ -39063,7 +39097,7 @@ class GodotServer {
|
|
|
39063
39097
|
return null;
|
|
39064
39098
|
}
|
|
39065
39099
|
try {
|
|
39066
|
-
return
|
|
39100
|
+
return readFileSync18(contained.absolutePath, "utf8");
|
|
39067
39101
|
} catch {
|
|
39068
39102
|
return null;
|
|
39069
39103
|
}
|
|
@@ -39105,7 +39139,7 @@ class GodotServer {
|
|
|
39105
39139
|
return null;
|
|
39106
39140
|
}
|
|
39107
39141
|
try {
|
|
39108
|
-
return
|
|
39142
|
+
return readFileSync18(contained.absolutePath, "utf8");
|
|
39109
39143
|
} catch {
|
|
39110
39144
|
return null;
|
|
39111
39145
|
}
|
|
@@ -39334,7 +39368,7 @@ class GodotServer {
|
|
|
39334
39368
|
return null;
|
|
39335
39369
|
}
|
|
39336
39370
|
try {
|
|
39337
|
-
return settingKeys(
|
|
39371
|
+
return settingKeys(readFileSync18(join20(projectPath, "project.godot"), "utf8"));
|
|
39338
39372
|
} catch {
|
|
39339
39373
|
return null;
|
|
39340
39374
|
}
|
|
@@ -39541,7 +39575,7 @@ class GodotServer {
|
|
|
39541
39575
|
platform: process.platform,
|
|
39542
39576
|
variables: process.env
|
|
39543
39577
|
});
|
|
39544
|
-
const runtimeWaitMs =
|
|
39578
|
+
const runtimeWaitMs = this.announceBudget(args, project.value.path);
|
|
39545
39579
|
const editorWouldPlay = !headless || editorPlaysHeadless(project.value.file);
|
|
39546
39580
|
if (this.godotBridge.isConnected() && editorWouldPlay && given.value.length === 0) {
|
|
39547
39581
|
return await this.playThroughEditor(sceneArgument, refreshed.value, project.value.path, alreadyPlaying, runtimeWaitMs, ended);
|
|
@@ -39574,8 +39608,9 @@ class GodotServer {
|
|
|
39574
39608
|
message: `Use editor_output for what it prints and editor_run stop to end it.${spawnedInstead}${endedForThis}${alsoRunning}`
|
|
39575
39609
|
});
|
|
39576
39610
|
}
|
|
39577
|
-
async runtimeUp(projectPath, before,
|
|
39578
|
-
|
|
39611
|
+
async runtimeUp(projectPath, before, budget, withArgs) {
|
|
39612
|
+
const budgetMs = budget.ms;
|
|
39613
|
+
if (!existsSync14(join20(projectPath, RUNTIME_AUTOLOAD2.path))) {
|
|
39579
39614
|
return runtimeVerdict(null, { addon: false, budgetMs, heldAt: null, running: false, withArgs });
|
|
39580
39615
|
}
|
|
39581
39616
|
let asking = null;
|
|
@@ -39611,16 +39646,42 @@ class GodotServer {
|
|
|
39611
39646
|
const going = this.currentRun();
|
|
39612
39647
|
if (endpoint !== null && going !== null && going.announcedPid === undefined) {
|
|
39613
39648
|
going.announcedPid = endpoint.pid;
|
|
39649
|
+
this.noteBootOf(going, endpoint);
|
|
39614
39650
|
}
|
|
39615
39651
|
return runtimeVerdict(endpoint, {
|
|
39616
39652
|
addon: true,
|
|
39617
39653
|
budgetMs,
|
|
39654
|
+
...budget.sizedToMs === undefined ? {} : { sizedToMs: budget.sizedToMs },
|
|
39618
39655
|
heldAt: this.dapClient?.whereItStopped() ?? null,
|
|
39619
39656
|
running: endpoint !== null || going !== null && await this.runStillGoing(going),
|
|
39620
39657
|
withArgs,
|
|
39621
39658
|
...going?.throughEditor === true ? { playingSeen: going.seenPlaying === true } : {}
|
|
39622
39659
|
});
|
|
39623
39660
|
}
|
|
39661
|
+
announceBudget(args, projectPath) {
|
|
39662
|
+
const asked = readNonNegativeNumber(args, "runtimeWaitMs");
|
|
39663
|
+
if (asked !== undefined) {
|
|
39664
|
+
return { ms: asked };
|
|
39665
|
+
}
|
|
39666
|
+
const lastBoot = readBootNote(projectPath);
|
|
39667
|
+
const ms = waitSizedTo(lastBoot);
|
|
39668
|
+
return lastBoot !== null && ms > ANNOUNCE_BUDGET_MS ? { ms, sizedToMs: lastBoot } : { ms };
|
|
39669
|
+
}
|
|
39670
|
+
noteBootOf(run, endpoint) {
|
|
39671
|
+
if (run.projectPath === null) {
|
|
39672
|
+
return;
|
|
39673
|
+
}
|
|
39674
|
+
let announcedAt;
|
|
39675
|
+
try {
|
|
39676
|
+
announcedAt = statSync5(endpoint.file).mtimeMs;
|
|
39677
|
+
} catch {
|
|
39678
|
+
return;
|
|
39679
|
+
}
|
|
39680
|
+
const took = announcedAt - run.startedAt;
|
|
39681
|
+
if (took > 0 && took <= LONGEST_BOOT_NOTED_MS) {
|
|
39682
|
+
writeBootNote(run.projectPath, took);
|
|
39683
|
+
}
|
|
39684
|
+
}
|
|
39624
39685
|
async playThroughEditor(scene, refreshedClasses, projectPath, alreadyPlaying, runtimeWaitMs, ended) {
|
|
39625
39686
|
const log = new GameLog;
|
|
39626
39687
|
try {
|
|
@@ -39950,6 +40011,7 @@ class GodotServer {
|
|
|
39950
40011
|
const theOne = candidates.length === 1 ? candidates[0] : undefined;
|
|
39951
40012
|
if (theOne !== undefined) {
|
|
39952
40013
|
run.announcedPid = theOne.pid;
|
|
40014
|
+
this.noteBootOf(run, theOne);
|
|
39953
40015
|
}
|
|
39954
40016
|
return run.announcedPid;
|
|
39955
40017
|
}
|
|
@@ -40618,8 +40680,8 @@ class GodotServer {
|
|
|
40618
40680
|
]);
|
|
40619
40681
|
}
|
|
40620
40682
|
const expectsScreenshot = command === "capture_screenshot" || command === "capture_viewport";
|
|
40621
|
-
const screenshotDir = expectsScreenshot ? mkdtempSync3(
|
|
40622
|
-
const screenshotPath = screenshotDir ?
|
|
40683
|
+
const screenshotDir = expectsScreenshot ? mkdtempSync3(join20(tmpdir5(), "gdharness-runtime-screenshot-")) : null;
|
|
40684
|
+
const screenshotPath = screenshotDir ? join20(screenshotDir, "capture.png") : null;
|
|
40623
40685
|
try {
|
|
40624
40686
|
const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs);
|
|
40625
40687
|
if (!reply.ok) {
|
|
@@ -40643,7 +40705,7 @@ class GodotServer {
|
|
|
40643
40705
|
type: "text",
|
|
40644
40706
|
text: `Screenshot captured: ${dimensions}${chosenAmongSeveral ? ` from pid ${own}, the game this server holds` : ""}`
|
|
40645
40707
|
},
|
|
40646
|
-
{ type: "image", data:
|
|
40708
|
+
{ type: "image", data: readFileSync18(screenshotPath).toString("base64"), mimeType: "image/png" }
|
|
40647
40709
|
]
|
|
40648
40710
|
};
|
|
40649
40711
|
} finally {
|
|
@@ -40710,11 +40772,12 @@ async function runGodotServer() {
|
|
|
40710
40772
|
const server = new GodotServer;
|
|
40711
40773
|
await server.run();
|
|
40712
40774
|
}
|
|
40713
|
-
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run7, __dirname2, EDITOR_RESTART_TIMEOUT_MS = 90000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, PLAY_STARTS_WITHIN_MS = 30000, CONSOLE_WRAPPER, VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CACHE_WRITE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/";
|
|
40775
|
+
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run7, __dirname2, EDITOR_RESTART_TIMEOUT_MS = 90000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, PLAY_STARTS_WITHIN_MS = 30000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CACHE_WRITE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/";
|
|
40714
40776
|
var init_server3 = __esm(() => {
|
|
40715
40777
|
init_mcp();
|
|
40716
40778
|
init_stdio2();
|
|
40717
40779
|
init_types();
|
|
40780
|
+
init_boot_note();
|
|
40718
40781
|
init_breakpoint_note();
|
|
40719
40782
|
init_bridge_announce();
|
|
40720
40783
|
init_class_cache();
|
|
@@ -40744,8 +40807,9 @@ var init_server3 = __esm(() => {
|
|
|
40744
40807
|
init_tool_definitions();
|
|
40745
40808
|
init_update_check();
|
|
40746
40809
|
run7 = promisify7(execFile7);
|
|
40747
|
-
__dirname2 =
|
|
40810
|
+
__dirname2 = dirname13(fileURLToPath4(import.meta.url));
|
|
40748
40811
|
CONSOLE_WRAPPER = /_console(\.exe)?$/i;
|
|
40812
|
+
LONGEST_BOOT_NOTED_MS = 5 * 60000;
|
|
40749
40813
|
VALUE_NAMES = {
|
|
40750
40814
|
string: "a string",
|
|
40751
40815
|
number: "a number",
|
|
@@ -40784,7 +40848,7 @@ var init_server3 = __esm(() => {
|
|
|
40784
40848
|
|
|
40785
40849
|
// src/cli.ts
|
|
40786
40850
|
import { existsSync as existsSync15 } from "fs";
|
|
40787
|
-
import { join as
|
|
40851
|
+
import { join as join21, resolve as resolve5 } from "path";
|
|
40788
40852
|
|
|
40789
40853
|
// src/errors.ts
|
|
40790
40854
|
class Refusal extends Error {
|
|
@@ -42489,7 +42553,7 @@ class UsageError extends Refusal {
|
|
|
42489
42553
|
function projectArgument(at) {
|
|
42490
42554
|
const named = args.slice(at).find((value) => !value.startsWith("--"));
|
|
42491
42555
|
const projectPath = resolve5(named ?? ".");
|
|
42492
|
-
if (!existsSync15(
|
|
42556
|
+
if (!existsSync15(join21(projectPath, "project.godot"))) {
|
|
42493
42557
|
throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
|
|
42494
42558
|
}
|
|
42495
42559
|
return projectPath;
|
|
@@ -42528,7 +42592,7 @@ async function registerRuntime(godot, projectPath) {
|
|
|
42528
42592
|
return;
|
|
42529
42593
|
}
|
|
42530
42594
|
console.log(`${RUNTIME_AUTOLOAD.name} autoload left at ${named}, which this project registers itself`);
|
|
42531
|
-
if (!existsSync15(
|
|
42595
|
+
if (!existsSync15(join21(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
|
|
42532
42596
|
console.log(` that file is not in this project, so it boots with a missing script: restore it, or point the entry at res://${RUNTIME_AUTOLOAD.path}`);
|
|
42533
42597
|
}
|
|
42534
42598
|
}
|