gdharness 1.0.11 → 1.0.12
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 +468 -108
- package/build/index.js +477 -125
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -10172,20 +10172,20 @@ import { execFile as execFile5, spawn } from "node:child_process";
|
|
|
10172
10172
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
10173
10173
|
import {
|
|
10174
10174
|
appendFileSync,
|
|
10175
|
-
closeSync,
|
|
10176
|
-
existsSync as
|
|
10175
|
+
closeSync as closeSync2,
|
|
10176
|
+
existsSync as existsSync10,
|
|
10177
10177
|
fstatSync,
|
|
10178
|
-
mkdirSync as
|
|
10178
|
+
mkdirSync as mkdirSync10,
|
|
10179
10179
|
mkdtempSync as mkdtempSync2,
|
|
10180
|
-
openSync as
|
|
10180
|
+
openSync as openSync3,
|
|
10181
10181
|
readdirSync as readdirSync6,
|
|
10182
|
-
readFileSync as
|
|
10183
|
-
readSync,
|
|
10182
|
+
readFileSync as readFileSync16,
|
|
10183
|
+
readSync as readSync2,
|
|
10184
10184
|
rmdirSync,
|
|
10185
|
-
statSync as
|
|
10185
|
+
statSync as statSync6
|
|
10186
10186
|
} from "node:fs";
|
|
10187
10187
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
10188
|
-
import { basename as basename3, dirname as
|
|
10188
|
+
import { basename as basename3, dirname as dirname10, isAbsolute as isAbsolute3, join as join16, normalize as normalize2, relative as relative3 } from "node:path";
|
|
10189
10189
|
import { setTimeout as delay3 } from "node:timers/promises";
|
|
10190
10190
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
10191
10191
|
import { promisify as promisify5 } from "node:util";
|
|
@@ -24791,8 +24791,18 @@ function runArguments(options) {
|
|
|
24791
24791
|
}
|
|
24792
24792
|
return args;
|
|
24793
24793
|
}
|
|
24794
|
-
function editorArguments(projectPath, ports) {
|
|
24795
|
-
return [
|
|
24794
|
+
function editorArguments(projectPath, ports, logFile) {
|
|
24795
|
+
return [
|
|
24796
|
+
"-e",
|
|
24797
|
+
"--path",
|
|
24798
|
+
projectPath,
|
|
24799
|
+
"--lsp-port",
|
|
24800
|
+
String(ports.lsp),
|
|
24801
|
+
"--dap-port",
|
|
24802
|
+
String(ports.dap),
|
|
24803
|
+
"--log-file",
|
|
24804
|
+
logFile
|
|
24805
|
+
];
|
|
24796
24806
|
}
|
|
24797
24807
|
var OPENED_BY_A_SERVER = "GDHARNESS_OPENED_BY_A_SERVER";
|
|
24798
24808
|
function userDataIn(home, variables = process.env) {
|
|
@@ -25318,10 +25328,37 @@ async function announcedSince(projectPath, before, waiting = {}) {
|
|
|
25318
25328
|
function describe2(endpoint) {
|
|
25319
25329
|
return `pid ${endpoint.pid} on ${endpoint.address}:${endpoint.port} (${endpoint.project.name || "unnamed"} at ${endpoint.project.path})`;
|
|
25320
25330
|
}
|
|
25331
|
+
function describeWithin(endpoint) {
|
|
25332
|
+
return `pid ${endpoint.pid} on ${endpoint.address}:${endpoint.port}`;
|
|
25333
|
+
}
|
|
25334
|
+
function theOneProject(endpoints) {
|
|
25335
|
+
if (endpoints.some((endpoint) => endpoint.project.path === "")) {
|
|
25336
|
+
return;
|
|
25337
|
+
}
|
|
25338
|
+
const paths = new Set(endpoints.map((endpoint) => resolve(endpoint.project.path)));
|
|
25339
|
+
const only = [...paths][0];
|
|
25340
|
+
return paths.size === 1 ? only : undefined;
|
|
25341
|
+
}
|
|
25342
|
+
function severalFrom(project, games) {
|
|
25343
|
+
return `${games.length} games are running from ${project}: ${games.map(describeWithin).join("; ")}. ` + "Pass pid to choose one; projectPath cannot, because every one of them has this one. " + "editor_status lists them under runtimes.";
|
|
25344
|
+
}
|
|
25345
|
+
function whichHalfIsBehind(unspoken) {
|
|
25346
|
+
const pids = (games) => games.map((game) => `pid ${game.pid}`).join(", ");
|
|
25347
|
+
const ahead = unspoken.filter((game) => game.protocol > RUNTIME_PROTOCOL);
|
|
25348
|
+
const behind = unspoken.filter((game) => game.protocol < RUNTIME_PROTOCOL);
|
|
25349
|
+
if (ahead.length > 0 && behind.length > 0) {
|
|
25350
|
+
return `they are on both sides of it: reconnect this server for ${pids(ahead)}, and reinstall the ` + `addon and restart ${pids(behind)}`;
|
|
25351
|
+
}
|
|
25352
|
+
if (ahead.length > 0) {
|
|
25353
|
+
return "this server is the older half: reconnect it so it spawns the installed version";
|
|
25354
|
+
}
|
|
25355
|
+
return `the addon is the older half: reinstall it and restart ${behind.length === 1 ? "the game" : "those games"}`;
|
|
25356
|
+
}
|
|
25321
25357
|
function tooNew(unspoken) {
|
|
25322
25358
|
const named = unspoken.map((game) => `pid ${game.pid} speaks ${game.protocol} (${game.project.name || "unnamed"} at ${game.project.path})`).join("; ");
|
|
25323
|
-
const
|
|
25324
|
-
|
|
25359
|
+
const count = unspoken.length === 1 ? "A game is running" : `${unspoken.length} games are running`;
|
|
25360
|
+
const spoken = new Set(unspoken.map((game) => game.protocol)).size === 1 ? "a protocol" : "protocols";
|
|
25361
|
+
return `${count}, in ${spoken} this server does not speak: ${named}. This server speaks ${RUNTIME_PROTOCOL}, so ${whichHalfIsBehind(unspoken)}.`;
|
|
25325
25362
|
}
|
|
25326
25363
|
function chooseRuntime(endpoints, projectPath, unspoken = [], pid) {
|
|
25327
25364
|
if (pid !== undefined) {
|
|
@@ -25338,8 +25375,16 @@ function chooseRuntime(endpoints, projectPath, unspoken = [], pid) {
|
|
|
25338
25375
|
if (tooNewToo !== undefined) {
|
|
25339
25376
|
return { problem: tooNew([tooNewToo]) };
|
|
25340
25377
|
}
|
|
25378
|
+
if (endpoints.length > 0) {
|
|
25379
|
+
return {
|
|
25380
|
+
problem: `No running game has pid ${pid}. Running: ${endpoints.map(describe2).join("; ")}. editor_status lists them under runtimes as they come and go.`
|
|
25381
|
+
};
|
|
25382
|
+
}
|
|
25383
|
+
if (unspoken.length > 0) {
|
|
25384
|
+
return { problem: `No game reachable from here has pid ${pid}. ${tooNew(unspoken)}` };
|
|
25385
|
+
}
|
|
25341
25386
|
return {
|
|
25342
|
-
problem:
|
|
25387
|
+
problem: `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.`
|
|
25343
25388
|
};
|
|
25344
25389
|
}
|
|
25345
25390
|
if (endpoints.length === 0) {
|
|
@@ -25371,15 +25416,17 @@ function chooseRuntime(endpoints, projectPath, unspoken = [], pid) {
|
|
|
25371
25416
|
if (matching.length === 1 && matching[0]) {
|
|
25372
25417
|
return { endpoint: matching[0] };
|
|
25373
25418
|
}
|
|
25374
|
-
return {
|
|
25375
|
-
problem: `Several games are running from ${wanted}: ${matching.map(describe2).join("; ")}. Pass pid to choose one.`
|
|
25376
|
-
};
|
|
25419
|
+
return { problem: severalFrom(wanted, matching) };
|
|
25377
25420
|
}
|
|
25378
25421
|
if (endpoints.length === 1 && endpoints[0]) {
|
|
25379
25422
|
return { endpoint: endpoints[0] };
|
|
25380
25423
|
}
|
|
25424
|
+
const shared = theOneProject(endpoints);
|
|
25425
|
+
if (shared !== undefined) {
|
|
25426
|
+
return { problem: severalFrom(shared, endpoints) };
|
|
25427
|
+
}
|
|
25381
25428
|
return {
|
|
25382
|
-
problem: `Several games are running: ${endpoints.map(describe2).join("; ")}. Pass
|
|
25429
|
+
problem: `Several games are running: ${endpoints.map(describe2).join("; ")}. Pass pid to choose one, or projectPath when the project you mean is running only one.`
|
|
25383
25430
|
};
|
|
25384
25431
|
}
|
|
25385
25432
|
var nextRequestId = 1;
|
|
@@ -26613,6 +26660,20 @@ function dictionary(entries) {
|
|
|
26613
26660
|
return Object.assign(emptyRecord(), entries);
|
|
26614
26661
|
}
|
|
26615
26662
|
|
|
26663
|
+
// src/editor-log.ts
|
|
26664
|
+
import {
|
|
26665
|
+
closeSync,
|
|
26666
|
+
existsSync as existsSync5,
|
|
26667
|
+
mkdirSync as mkdirSync6,
|
|
26668
|
+
openSync,
|
|
26669
|
+
readFileSync as readFileSync10,
|
|
26670
|
+
readSync,
|
|
26671
|
+
rmSync as rmSync3,
|
|
26672
|
+
statSync as statSync3,
|
|
26673
|
+
writeFileSync as writeFileSync6
|
|
26674
|
+
} from "node:fs";
|
|
26675
|
+
import { dirname as dirname6, join as join9 } from "node:path";
|
|
26676
|
+
|
|
26616
26677
|
// src/game-log.ts
|
|
26617
26678
|
import { StringDecoder } from "node:string_decoder";
|
|
26618
26679
|
var HEADLINE = /^(USER )?(SCRIPT ERROR|SHADER ERROR|ERROR|WARNING):\s?(.*)$/;
|
|
@@ -26699,6 +26760,9 @@ class GameLog {
|
|
|
26699
26760
|
count(severity) {
|
|
26700
26761
|
return this.entries.filter((entry) => entry.severity === severity).length;
|
|
26701
26762
|
}
|
|
26763
|
+
everything() {
|
|
26764
|
+
return this.entries;
|
|
26765
|
+
}
|
|
26702
26766
|
select(options) {
|
|
26703
26767
|
const floor = options.sinceLastCall ? this.seen[options.severity] : 0;
|
|
26704
26768
|
const wanted = ADMITTED[options.severity];
|
|
@@ -26722,6 +26786,221 @@ class GameLog {
|
|
|
26722
26786
|
}
|
|
26723
26787
|
}
|
|
26724
26788
|
|
|
26789
|
+
// src/editor-log.ts
|
|
26790
|
+
function editorLogPath(projectPath) {
|
|
26791
|
+
return join9(projectPath, ".godot", "gdharness-editor.log");
|
|
26792
|
+
}
|
|
26793
|
+
function editorLogNotePath(projectPath) {
|
|
26794
|
+
return join9(projectPath, ".godot", "gdharness-editor.json");
|
|
26795
|
+
}
|
|
26796
|
+
function readEditorLogNote(projectPath) {
|
|
26797
|
+
try {
|
|
26798
|
+
const fields = asParams(JSON.parse(readFileSync10(editorLogNotePath(projectPath), "utf8")));
|
|
26799
|
+
const pid = readNumber(fields, "pid");
|
|
26800
|
+
const startedAt = readString(fields, "startedAt");
|
|
26801
|
+
return pid !== undefined && Number.isInteger(pid) && pid > 0 && startedAt !== undefined ? { pid, startedAt } : null;
|
|
26802
|
+
} catch {
|
|
26803
|
+
return null;
|
|
26804
|
+
}
|
|
26805
|
+
}
|
|
26806
|
+
function clearEditorLog(projectPath) {
|
|
26807
|
+
try {
|
|
26808
|
+
const path = editorLogPath(projectPath);
|
|
26809
|
+
mkdirSync6(dirname6(path), { recursive: true });
|
|
26810
|
+
rmSync3(path, { force: true });
|
|
26811
|
+
rmSync3(editorLogNotePath(projectPath), { force: true });
|
|
26812
|
+
} catch {}
|
|
26813
|
+
}
|
|
26814
|
+
function writeEditorLogNote(projectPath, pid) {
|
|
26815
|
+
try {
|
|
26816
|
+
const path = editorLogNotePath(projectPath);
|
|
26817
|
+
mkdirSync6(dirname6(path), { recursive: true });
|
|
26818
|
+
writeFileSync6(path, JSON.stringify({ pid, startedAt: new Date().toISOString() }, null, 2), "utf8");
|
|
26819
|
+
} catch {}
|
|
26820
|
+
}
|
|
26821
|
+
function theConsoleWasNotCaptured(why, projectPath) {
|
|
26822
|
+
if (why.kind === "not ours") {
|
|
26823
|
+
return "This editor was not opened by gdharness, so its console was never captured and nothing " + "here has it: an editor prints to a console no plugin can read, and the addon cannot get " + "at it from inside. This is not an editor that printed nothing.";
|
|
26824
|
+
}
|
|
26825
|
+
if (why.kind === "older server") {
|
|
26826
|
+
return `This editor was opened by a gdharness server, but the console beside ${projectPath} belongs ` + `to editor ${why.notedPid ?? "none recorded"} rather than to this one, so it holds an ` + "earlier session and is not offered as this one. A server from before consoles were " + "captured leaves exactly this.";
|
|
26827
|
+
}
|
|
26828
|
+
return "This editor was opened by gdharness and has written nothing to its console file yet, not " + "even the engine version it prints first, so the file is missing rather than empty. An " + "editor that could not open the file for writing leaves this.";
|
|
26829
|
+
}
|
|
26830
|
+
function editorConsole(projectPath, editorPid, openedByAServer) {
|
|
26831
|
+
if (!openedByAServer) {
|
|
26832
|
+
return { kind: "not ours" };
|
|
26833
|
+
}
|
|
26834
|
+
const noted = readEditorLogNote(projectPath);
|
|
26835
|
+
if (noted === null || editorPid === null || noted.pid !== editorPid) {
|
|
26836
|
+
return { kind: "older server", notedPid: noted?.pid ?? null };
|
|
26837
|
+
}
|
|
26838
|
+
const path = editorLogPath(projectPath);
|
|
26839
|
+
if (!existsSync5(path)) {
|
|
26840
|
+
return { kind: "not written" };
|
|
26841
|
+
}
|
|
26842
|
+
const log = new GameLog;
|
|
26843
|
+
const { text, of } = theFirstOf(path);
|
|
26844
|
+
log.append("transcript", text);
|
|
26845
|
+
log.finish();
|
|
26846
|
+
return { log, path, ...of === undefined ? {} : { readBytes: text.length, of } };
|
|
26847
|
+
}
|
|
26848
|
+
var MOST_CONSOLE_BYTES = 8 * 1024 * 1024;
|
|
26849
|
+
function theFirstOf(path) {
|
|
26850
|
+
const whole = statSync3(path).size;
|
|
26851
|
+
if (whole <= MOST_CONSOLE_BYTES) {
|
|
26852
|
+
return { text: readFileSync10(path, "utf8") };
|
|
26853
|
+
}
|
|
26854
|
+
const handle = openSync(path, "r");
|
|
26855
|
+
try {
|
|
26856
|
+
const buffer = Buffer.alloc(MOST_CONSOLE_BYTES);
|
|
26857
|
+
const read = readSync(handle, buffer, 0, MOST_CONSOLE_BYTES, 0);
|
|
26858
|
+
const decoded = buffer.subarray(0, read).toString("utf8");
|
|
26859
|
+
const lastLine = decoded.lastIndexOf(`
|
|
26860
|
+
`);
|
|
26861
|
+
return { text: lastLine === -1 ? decoded : decoded.slice(0, lastLine + 1), of: whole };
|
|
26862
|
+
} finally {
|
|
26863
|
+
closeSync(handle);
|
|
26864
|
+
}
|
|
26865
|
+
}
|
|
26866
|
+
var NAMED_VALUES = 6;
|
|
26867
|
+
var WALK_BACK_LIMIT = 500;
|
|
26868
|
+
var SLOTS = [/"[^"]*"/g, /'[^']*'/g, /\b(?:res|user):\/\/\S+/g, /(?<=\.gd|\.tscn|\.cs):\d+(?::\d+)?/g];
|
|
26869
|
+
function shapeOf(text) {
|
|
26870
|
+
const values = [];
|
|
26871
|
+
let shape = "";
|
|
26872
|
+
let at = 0;
|
|
26873
|
+
while (at < text.length) {
|
|
26874
|
+
let earliest = null;
|
|
26875
|
+
for (const pattern of SLOTS) {
|
|
26876
|
+
pattern.lastIndex = at;
|
|
26877
|
+
const found = pattern.exec(text);
|
|
26878
|
+
if (found !== null && (earliest === null || found.index < earliest.index || found.index === earliest.index && found[0].length > earliest.value.length)) {
|
|
26879
|
+
earliest = { index: found.index, value: found[0] };
|
|
26880
|
+
}
|
|
26881
|
+
}
|
|
26882
|
+
if (earliest === null) {
|
|
26883
|
+
shape += text.slice(at);
|
|
26884
|
+
break;
|
|
26885
|
+
}
|
|
26886
|
+
if (earliest.value.length === 0) {
|
|
26887
|
+
shape += text.slice(at, at + 1);
|
|
26888
|
+
at += 1;
|
|
26889
|
+
continue;
|
|
26890
|
+
}
|
|
26891
|
+
shape += `${text.slice(at, earliest.index)}…`;
|
|
26892
|
+
values.push(earliest.value);
|
|
26893
|
+
at = earliest.index + earliest.value.length;
|
|
26894
|
+
}
|
|
26895
|
+
return { shape, values };
|
|
26896
|
+
}
|
|
26897
|
+
function bursts(entries, before) {
|
|
26898
|
+
const shapes = new Map;
|
|
26899
|
+
const shaped = entries.map((entry) => shapeOf(entry.text));
|
|
26900
|
+
for (const [at, entry] of entries.entries()) {
|
|
26901
|
+
const key = `${entry.severity}\x00${shaped[at]?.shape ?? entry.text}`;
|
|
26902
|
+
const group = shapes.get(key) ?? { indices: [], values: [] };
|
|
26903
|
+
group.indices.push(at);
|
|
26904
|
+
group.values.push(shaped[at]?.values ?? []);
|
|
26905
|
+
shapes.set(key, group);
|
|
26906
|
+
}
|
|
26907
|
+
const collapsed = new Set;
|
|
26908
|
+
for (const group of shapes.values()) {
|
|
26909
|
+
if (group.indices.length > 1) {
|
|
26910
|
+
for (const at of group.indices) {
|
|
26911
|
+
collapsed.add(at);
|
|
26912
|
+
}
|
|
26913
|
+
}
|
|
26914
|
+
}
|
|
26915
|
+
const repeated = [];
|
|
26916
|
+
for (const [key, group] of shapes) {
|
|
26917
|
+
const first = group.indices[0];
|
|
26918
|
+
if (group.indices.length < 2 || first === undefined) {
|
|
26919
|
+
continue;
|
|
26920
|
+
}
|
|
26921
|
+
const entry = entries[first];
|
|
26922
|
+
if (entry === undefined) {
|
|
26923
|
+
continue;
|
|
26924
|
+
}
|
|
26925
|
+
repeated.push({
|
|
26926
|
+
shape: key.slice(key.indexOf("\x00") + 1),
|
|
26927
|
+
count: group.indices.length,
|
|
26928
|
+
severity: entry.severity,
|
|
26929
|
+
first: { index: entry.index, text: entry.text },
|
|
26930
|
+
slots: slotsOf(group.values, group.indices.length),
|
|
26931
|
+
before: linesBefore(entries, first, collapsed, before)
|
|
26932
|
+
});
|
|
26933
|
+
}
|
|
26934
|
+
return repeated.sort((one, other) => other.count - one.count);
|
|
26935
|
+
}
|
|
26936
|
+
function slotsOf(values, count) {
|
|
26937
|
+
const width = Math.max(0, ...values.map((row) => row.length));
|
|
26938
|
+
const distinct = [];
|
|
26939
|
+
for (let at = 0;at < width; at += 1) {
|
|
26940
|
+
distinct.push([...new Set(values.map((row) => row[at]).filter((one) => one !== undefined))]);
|
|
26941
|
+
}
|
|
26942
|
+
const slots = [];
|
|
26943
|
+
for (let at = 0;at < width; at += 1) {
|
|
26944
|
+
const seen = distinct[at] ?? [];
|
|
26945
|
+
const partner = theSlotItMovesWith(values, distinct, at);
|
|
26946
|
+
const paired = partner === undefined ? {} : { movesWith: partner };
|
|
26947
|
+
if (seen.length === count && count > 1) {
|
|
26948
|
+
slots.push({ distinct: seen.length, allDifferent: true, ...paired });
|
|
26949
|
+
continue;
|
|
26950
|
+
}
|
|
26951
|
+
const some = seen.slice(0, NAMED_VALUES);
|
|
26952
|
+
slots.push({
|
|
26953
|
+
distinct: seen.length,
|
|
26954
|
+
some,
|
|
26955
|
+
...seen.length > some.length ? { more: seen.length - some.length } : {},
|
|
26956
|
+
...paired
|
|
26957
|
+
});
|
|
26958
|
+
}
|
|
26959
|
+
return slots;
|
|
26960
|
+
}
|
|
26961
|
+
function theSlotItMovesWith(values, distinct, at) {
|
|
26962
|
+
const mine = distinct[at] ?? [];
|
|
26963
|
+
if (mine.length < 2) {
|
|
26964
|
+
return;
|
|
26965
|
+
}
|
|
26966
|
+
for (let other = 0;other < at; other += 1) {
|
|
26967
|
+
if ((distinct[other] ?? []).length !== mine.length) {
|
|
26968
|
+
continue;
|
|
26969
|
+
}
|
|
26970
|
+
const goesWith = new Map;
|
|
26971
|
+
let oneForOne = true;
|
|
26972
|
+
for (const row of values) {
|
|
26973
|
+
const theirs = row[other];
|
|
26974
|
+
const ours = row[at];
|
|
26975
|
+
if (theirs === undefined || ours === undefined) {
|
|
26976
|
+
oneForOne = false;
|
|
26977
|
+
break;
|
|
26978
|
+
}
|
|
26979
|
+
const already = goesWith.get(theirs);
|
|
26980
|
+
if (already === undefined) {
|
|
26981
|
+
goesWith.set(theirs, ours);
|
|
26982
|
+
} else if (already !== ours) {
|
|
26983
|
+
oneForOne = false;
|
|
26984
|
+
break;
|
|
26985
|
+
}
|
|
26986
|
+
}
|
|
26987
|
+
if (oneForOne) {
|
|
26988
|
+
return other;
|
|
26989
|
+
}
|
|
26990
|
+
}
|
|
26991
|
+
return;
|
|
26992
|
+
}
|
|
26993
|
+
function linesBefore(entries, at, collapsed, wanted) {
|
|
26994
|
+
const found = [];
|
|
26995
|
+
for (let back = at - 1;back >= 0 && at - back <= WALK_BACK_LIMIT && found.length < wanted; back -= 1) {
|
|
26996
|
+
const entry = entries[back];
|
|
26997
|
+
if (entry !== undefined && !collapsed.has(back)) {
|
|
26998
|
+
found.push(entry.text);
|
|
26999
|
+
}
|
|
27000
|
+
}
|
|
27001
|
+
return found.reverse();
|
|
27002
|
+
}
|
|
27003
|
+
|
|
26725
27004
|
// src/godot-bridge.ts
|
|
26726
27005
|
import { randomUUID } from "node:crypto";
|
|
26727
27006
|
import { EventEmitter } from "node:events";
|
|
@@ -27223,14 +27502,14 @@ function getDefaultBridge(ownProject = null) {
|
|
|
27223
27502
|
|
|
27224
27503
|
// src/godot-path.ts
|
|
27225
27504
|
import { execFile as execFile2 } from "node:child_process";
|
|
27226
|
-
import { existsSync as
|
|
27505
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
27227
27506
|
import { normalize } from "node:path";
|
|
27228
27507
|
import { promisify as promisify2 } from "node:util";
|
|
27229
27508
|
|
|
27230
27509
|
// src/detection.ts
|
|
27231
|
-
import { existsSync as
|
|
27510
|
+
import { existsSync as existsSync6, readdirSync as readdirSync3, statSync as statSync4 } from "node:fs";
|
|
27232
27511
|
import { homedir as homedir2 } from "node:os";
|
|
27233
|
-
import { join as
|
|
27512
|
+
import { join as join10 } from "node:path";
|
|
27234
27513
|
function resolveHomeDirectory() {
|
|
27235
27514
|
try {
|
|
27236
27515
|
return homedir2();
|
|
@@ -27239,7 +27518,7 @@ function resolveHomeDirectory() {
|
|
|
27239
27518
|
}
|
|
27240
27519
|
}
|
|
27241
27520
|
function scanDirectoryForGodotBinaries(directory, platform) {
|
|
27242
|
-
if (!directory || !
|
|
27521
|
+
if (!directory || !existsSync6(directory)) {
|
|
27243
27522
|
return [];
|
|
27244
27523
|
}
|
|
27245
27524
|
let entries;
|
|
@@ -27254,16 +27533,16 @@ function scanDirectoryForGodotBinaries(directory, platform) {
|
|
|
27254
27533
|
if (!pattern.test(name)) {
|
|
27255
27534
|
continue;
|
|
27256
27535
|
}
|
|
27257
|
-
const fullPath =
|
|
27536
|
+
const fullPath = join10(directory, name);
|
|
27258
27537
|
try {
|
|
27259
|
-
const stat =
|
|
27538
|
+
const stat = statSync4(fullPath);
|
|
27260
27539
|
if (stat.isFile()) {
|
|
27261
27540
|
matches.push({ name, mtime: stat.mtimeMs });
|
|
27262
27541
|
}
|
|
27263
27542
|
} catch {}
|
|
27264
27543
|
}
|
|
27265
27544
|
matches.sort((a, b) => b.mtime - a.mtime);
|
|
27266
|
-
return matches.map((m) =>
|
|
27545
|
+
return matches.map((m) => join10(directory, m.name));
|
|
27267
27546
|
}
|
|
27268
27547
|
function conventionalPaths(platform, home) {
|
|
27269
27548
|
const paths = ["godot"];
|
|
@@ -27366,7 +27645,7 @@ class GodotLocator {
|
|
|
27366
27645
|
return known;
|
|
27367
27646
|
}
|
|
27368
27647
|
let reason;
|
|
27369
|
-
if (path !== "godot" && !
|
|
27648
|
+
if (path !== "godot" && !existsSync7(path)) {
|
|
27370
27649
|
reason = "does not exist";
|
|
27371
27650
|
} else {
|
|
27372
27651
|
try {
|
|
@@ -27383,13 +27662,13 @@ class GodotLocator {
|
|
|
27383
27662
|
|
|
27384
27663
|
// src/headless.ts
|
|
27385
27664
|
import { execFile as execFile3 } from "node:child_process";
|
|
27386
|
-
import { mkdtempSync, writeFileSync as
|
|
27665
|
+
import { mkdtempSync, writeFileSync as writeFileSync7 } from "node:fs";
|
|
27387
27666
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
27388
|
-
import { join as
|
|
27667
|
+
import { join as join11 } from "node:path";
|
|
27389
27668
|
import { promisify as promisify3 } from "node:util";
|
|
27390
27669
|
|
|
27391
27670
|
// src/scratch.ts
|
|
27392
|
-
import { rmSync as
|
|
27671
|
+
import { rmSync as rmSync4 } from "node:fs";
|
|
27393
27672
|
var SWEPT = { recursive: true, force: true, maxRetries: 20, retryDelay: 250 };
|
|
27394
27673
|
function discardWith(remove, paths) {
|
|
27395
27674
|
const left = [];
|
|
@@ -27407,7 +27686,7 @@ function discardWith(remove, paths) {
|
|
|
27407
27686
|
}
|
|
27408
27687
|
function discard(...paths) {
|
|
27409
27688
|
return discardWith((path) => {
|
|
27410
|
-
|
|
27689
|
+
rmSync4(path, SWEPT);
|
|
27411
27690
|
}, paths);
|
|
27412
27691
|
}
|
|
27413
27692
|
|
|
@@ -27451,12 +27730,12 @@ function reason(stdout, stderr) {
|
|
|
27451
27730
|
return stdout.trim().split(/\r?\n/).at(-1) ?? "no output at all";
|
|
27452
27731
|
}
|
|
27453
27732
|
async function runImport(godotPath, projectPath) {
|
|
27454
|
-
const logDir = mkdtempSync(
|
|
27733
|
+
const logDir = mkdtempSync(join11(tmpdir3(), "gdharness-import-"));
|
|
27455
27734
|
try {
|
|
27456
27735
|
const { stderr } = await run3(godotPath, [
|
|
27457
27736
|
"--headless",
|
|
27458
27737
|
"--log-file",
|
|
27459
|
-
|
|
27738
|
+
join11(logDir, "engine.log"),
|
|
27460
27739
|
"--path",
|
|
27461
27740
|
projectPath,
|
|
27462
27741
|
"--import"
|
|
@@ -27481,13 +27760,13 @@ async function runImport(godotPath, projectPath) {
|
|
|
27481
27760
|
}
|
|
27482
27761
|
}
|
|
27483
27762
|
async function runOperation(engine, operation, params, projectPath) {
|
|
27484
|
-
const paramsDir = mkdtempSync(
|
|
27485
|
-
const paramsFile =
|
|
27486
|
-
|
|
27763
|
+
const paramsDir = mkdtempSync(join11(tmpdir3(), "gdharness-params-"));
|
|
27764
|
+
const paramsFile = join11(paramsDir, `${operation}.json`);
|
|
27765
|
+
writeFileSync7(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
|
|
27487
27766
|
const args = [
|
|
27488
27767
|
"--headless",
|
|
27489
27768
|
"--log-file",
|
|
27490
|
-
|
|
27769
|
+
join11(paramsDir, "engine.log"),
|
|
27491
27770
|
"--path",
|
|
27492
27771
|
projectPath,
|
|
27493
27772
|
"--script",
|
|
@@ -27916,7 +28195,7 @@ function orphansPrinted(printed) {
|
|
|
27916
28195
|
import { realpathSync as realpathSync2 } from "node:fs";
|
|
27917
28196
|
import { readFile, realpath } from "node:fs/promises";
|
|
27918
28197
|
import { createConnection as createConnection3 } from "node:net";
|
|
27919
|
-
import { dirname as
|
|
28198
|
+
import { dirname as dirname7, resolve as resolve3 } from "node:path";
|
|
27920
28199
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
27921
28200
|
var DEFAULT_LSP_PORT = 6005;
|
|
27922
28201
|
var DIAGNOSTICS_TIMEOUT_MS = 5000;
|
|
@@ -28046,7 +28325,7 @@ class GodotLSPClient {
|
|
|
28046
28325
|
if (this.initialized) {
|
|
28047
28326
|
return;
|
|
28048
28327
|
}
|
|
28049
|
-
const rootPath = this.rootPath ??
|
|
28328
|
+
const rootPath = this.rootPath ?? dirname7(resolve3(filePath));
|
|
28050
28329
|
await this.initialize(rootPath);
|
|
28051
28330
|
}
|
|
28052
28331
|
async sendRequest(method, params) {
|
|
@@ -28482,14 +28761,14 @@ function secondsFromClock(printed) {
|
|
|
28482
28761
|
}
|
|
28483
28762
|
|
|
28484
28763
|
// src/project-scan.ts
|
|
28485
|
-
import { existsSync as
|
|
28486
|
-
import { join as
|
|
28764
|
+
import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync11 } from "node:fs";
|
|
28765
|
+
import { join as join12 } from "node:path";
|
|
28487
28766
|
var SKIPPED = new Set(["node_modules"]);
|
|
28488
28767
|
function skipped(name) {
|
|
28489
28768
|
return name.startsWith(".") || SKIPPED.has(name);
|
|
28490
28769
|
}
|
|
28491
28770
|
function steppedOver(directory) {
|
|
28492
|
-
return
|
|
28771
|
+
return existsSync8(join12(directory, ".gdignore"));
|
|
28493
28772
|
}
|
|
28494
28773
|
var ASSET_EXTENSIONS = new Set(["png", "jpg", "jpeg", "webp", "svg", "ttf", "otf", "wav", "mp3", "ogg"]);
|
|
28495
28774
|
function projectStructure(projectPath) {
|
|
@@ -28503,7 +28782,7 @@ function projectStructure(projectPath) {
|
|
|
28503
28782
|
continue;
|
|
28504
28783
|
}
|
|
28505
28784
|
if (entry.isDirectory()) {
|
|
28506
|
-
visit(
|
|
28785
|
+
visit(join12(directory, entry.name));
|
|
28507
28786
|
} else if (entry.isFile()) {
|
|
28508
28787
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
28509
28788
|
if (extension === "tscn") {
|
|
@@ -28534,14 +28813,14 @@ function scriptsWithoutUid(projectPath) {
|
|
|
28534
28813
|
}
|
|
28535
28814
|
const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
28536
28815
|
if (entry.isDirectory()) {
|
|
28537
|
-
visit(
|
|
28816
|
+
visit(join12(directory, entry.name), spelled);
|
|
28538
28817
|
continue;
|
|
28539
28818
|
}
|
|
28540
28819
|
if (!entry.isFile()) {
|
|
28541
28820
|
continue;
|
|
28542
28821
|
}
|
|
28543
28822
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
28544
|
-
if (CARRIES_A_UID.has(extension) && !
|
|
28823
|
+
if (CARRIES_A_UID.has(extension) && !existsSync8(join12(directory, `${entry.name}.uid`))) {
|
|
28545
28824
|
found.push(spelled);
|
|
28546
28825
|
}
|
|
28547
28826
|
}
|
|
@@ -28576,7 +28855,7 @@ function searchProject(projectPath, options) {
|
|
|
28576
28855
|
if (skipped(entry.name)) {
|
|
28577
28856
|
continue;
|
|
28578
28857
|
}
|
|
28579
|
-
const entryPath =
|
|
28858
|
+
const entryPath = join12(directory, entry.name);
|
|
28580
28859
|
if (entry.isDirectory()) {
|
|
28581
28860
|
visit(entryPath);
|
|
28582
28861
|
continue;
|
|
@@ -28587,7 +28866,7 @@ function searchProject(projectPath, options) {
|
|
|
28587
28866
|
}
|
|
28588
28867
|
result.summary.files_searched += 1;
|
|
28589
28868
|
const matches = [];
|
|
28590
|
-
for (const [index, line] of
|
|
28869
|
+
for (const [index, line] of readFileSync11(entryPath, "utf8").split(`
|
|
28591
28870
|
`).entries()) {
|
|
28592
28871
|
if (full()) {
|
|
28593
28872
|
break;
|
|
@@ -28610,7 +28889,7 @@ function searchProject(projectPath, options) {
|
|
|
28610
28889
|
}
|
|
28611
28890
|
|
|
28612
28891
|
// src/resources.ts
|
|
28613
|
-
import { readFileSync as
|
|
28892
|
+
import { readFileSync as readFileSync12 } from "node:fs";
|
|
28614
28893
|
import { extname, resolve as resolve4 } from "node:path";
|
|
28615
28894
|
var STATIC_RESOURCES = [
|
|
28616
28895
|
{
|
|
@@ -28805,7 +29084,7 @@ function readResourceText(uri, getProjectPath) {
|
|
|
28805
29084
|
const parsedUri = parseGodotUri(uri);
|
|
28806
29085
|
if (parsedUri.kind === "project-info") {
|
|
28807
29086
|
const projectFilePath = resolveProjectFile(projectPath, "project.godot");
|
|
28808
|
-
const rawProject =
|
|
29087
|
+
const rawProject = readFileSync12(projectFilePath, "utf-8");
|
|
28809
29088
|
const parsedProject = parseProjectGodot(rawProject);
|
|
28810
29089
|
return {
|
|
28811
29090
|
mimeType: "application/json",
|
|
@@ -28814,7 +29093,7 @@ function readResourceText(uri, getProjectPath) {
|
|
|
28814
29093
|
}
|
|
28815
29094
|
const filePath = resolveProjectFile(projectPath, parsedUri.resourcePath);
|
|
28816
29095
|
ensureAllowedExtension(parsedUri.kind, filePath);
|
|
28817
|
-
const text =
|
|
29096
|
+
const text = readFileSync12(filePath, "utf-8");
|
|
28818
29097
|
return {
|
|
28819
29098
|
mimeType: parsedUri.kind === "script" ? "text/x-gdscript" : "text/plain",
|
|
28820
29099
|
text
|
|
@@ -28851,16 +29130,16 @@ function setupResourceHandlers(mcp, getProjectPath) {
|
|
|
28851
29130
|
}
|
|
28852
29131
|
|
|
28853
29132
|
// src/restart-note.ts
|
|
28854
|
-
import { mkdirSync as
|
|
28855
|
-
import { dirname as
|
|
29133
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync13, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "node:fs";
|
|
29134
|
+
import { dirname as dirname8, join as join13 } from "node:path";
|
|
28856
29135
|
function restartNotePath(projectPath) {
|
|
28857
|
-
return
|
|
29136
|
+
return join13(projectPath, ".godot", "gdharness-restart.json");
|
|
28858
29137
|
}
|
|
28859
29138
|
function noteRestartBegun(note) {
|
|
28860
29139
|
const path = restartNotePath(note.projectPath);
|
|
28861
29140
|
try {
|
|
28862
|
-
|
|
28863
|
-
|
|
29141
|
+
mkdirSync7(dirname8(path), { recursive: true });
|
|
29142
|
+
writeFileSync8(path, `${JSON.stringify(note, null, 2)}
|
|
28864
29143
|
`, "utf8");
|
|
28865
29144
|
} catch (error) {
|
|
28866
29145
|
console.error(`[SERVER] Could not record the restart at ${path}: ${errorMessage(error)}`);
|
|
@@ -28868,7 +29147,7 @@ function noteRestartBegun(note) {
|
|
|
28868
29147
|
}
|
|
28869
29148
|
function restartOwed(projectPath) {
|
|
28870
29149
|
try {
|
|
28871
|
-
const parsed = JSON.parse(
|
|
29150
|
+
const parsed = JSON.parse(readFileSync13(restartNotePath(projectPath), "utf8"));
|
|
28872
29151
|
if (typeof parsed !== "object" || parsed === null) {
|
|
28873
29152
|
return null;
|
|
28874
29153
|
}
|
|
@@ -28883,7 +29162,7 @@ function restartOwed(projectPath) {
|
|
|
28883
29162
|
}
|
|
28884
29163
|
function restartSettled(projectPath) {
|
|
28885
29164
|
try {
|
|
28886
|
-
|
|
29165
|
+
rmSync5(restartNotePath(projectPath), { force: true });
|
|
28887
29166
|
} catch (error) {
|
|
28888
29167
|
console.error(`[SERVER] Could not clear the restart note: ${errorMessage(error)}`);
|
|
28889
29168
|
}
|
|
@@ -28891,36 +29170,36 @@ function restartSettled(projectPath) {
|
|
|
28891
29170
|
|
|
28892
29171
|
// src/run-record.ts
|
|
28893
29172
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
28894
|
-
import { mkdirSync as
|
|
28895
|
-
import { basename as basename2, join as
|
|
29173
|
+
import { mkdirSync as mkdirSync8, openSync as openSync2, readdirSync as readdirSync5, readFileSync as readFileSync14, rmSync as rmSync6, statSync as statSync5, writeFileSync as writeFileSync9 } from "node:fs";
|
|
29174
|
+
import { basename as basename2, join as join14 } from "node:path";
|
|
28896
29175
|
function runsDirectory() {
|
|
28897
|
-
return
|
|
29176
|
+
return join14(runtimeDirectory(), "runs");
|
|
28898
29177
|
}
|
|
28899
29178
|
function recordPath() {
|
|
28900
|
-
return
|
|
29179
|
+
return join14(runsDirectory(), "run.json");
|
|
28901
29180
|
}
|
|
28902
29181
|
function recordPaths() {
|
|
28903
|
-
return runtimeDirectories().map((directory) =>
|
|
29182
|
+
return runtimeDirectories().map((directory) => join14(directory, "runs", "run.json"));
|
|
28904
29183
|
}
|
|
28905
29184
|
function openTranscript(startedAt) {
|
|
28906
|
-
|
|
28907
|
-
const path =
|
|
28908
|
-
return { path, fd:
|
|
29185
|
+
mkdirSync8(runsDirectory(), { recursive: true });
|
|
29186
|
+
const path = join14(runsDirectory(), `run-${startedAt}.log`);
|
|
29187
|
+
return { path, fd: openSync2(path, "a") };
|
|
28909
29188
|
}
|
|
28910
29189
|
function writeRunRecord(record) {
|
|
28911
|
-
|
|
28912
|
-
|
|
29190
|
+
mkdirSync8(runsDirectory(), { recursive: true });
|
|
29191
|
+
writeFileSync9(recordPath(), JSON.stringify(record, null, 2), "utf8");
|
|
28913
29192
|
}
|
|
28914
29193
|
function editorNotePath() {
|
|
28915
|
-
return
|
|
29194
|
+
return join14(runsDirectory(), "editor-run.json");
|
|
28916
29195
|
}
|
|
28917
29196
|
function writeEditorRunNote(note) {
|
|
28918
|
-
|
|
28919
|
-
|
|
29197
|
+
mkdirSync8(runsDirectory(), { recursive: true });
|
|
29198
|
+
writeFileSync9(editorNotePath(), JSON.stringify(note, null, 2), "utf8");
|
|
28920
29199
|
}
|
|
28921
29200
|
function readEditorRunNote() {
|
|
28922
29201
|
for (const directory of runtimeDirectories()) {
|
|
28923
|
-
const note = editorNoteAt(
|
|
29202
|
+
const note = editorNoteAt(join14(directory, "runs", "editor-run.json"));
|
|
28924
29203
|
if (note !== null) {
|
|
28925
29204
|
return note;
|
|
28926
29205
|
}
|
|
@@ -28930,7 +29209,7 @@ function readEditorRunNote() {
|
|
|
28930
29209
|
function editorNoteAt(path) {
|
|
28931
29210
|
let parsed;
|
|
28932
29211
|
try {
|
|
28933
|
-
parsed = JSON.parse(
|
|
29212
|
+
parsed = JSON.parse(readFileSync14(path, "utf8"));
|
|
28934
29213
|
} catch {
|
|
28935
29214
|
return null;
|
|
28936
29215
|
}
|
|
@@ -28953,7 +29232,7 @@ function recordRunEnded(pid, exitCode) {
|
|
|
28953
29232
|
return;
|
|
28954
29233
|
}
|
|
28955
29234
|
try {
|
|
28956
|
-
|
|
29235
|
+
writeFileSync9(path, JSON.stringify({ ...record, exitCode }, null, 2), "utf8");
|
|
28957
29236
|
} catch {}
|
|
28958
29237
|
}
|
|
28959
29238
|
function readRunRecord() {
|
|
@@ -28968,7 +29247,7 @@ function readRunRecord() {
|
|
|
28968
29247
|
function recordAt(path) {
|
|
28969
29248
|
let parsed;
|
|
28970
29249
|
try {
|
|
28971
|
-
parsed = JSON.parse(
|
|
29250
|
+
parsed = JSON.parse(readFileSync14(path, "utf8"));
|
|
28972
29251
|
} catch {
|
|
28973
29252
|
return null;
|
|
28974
29253
|
}
|
|
@@ -29013,7 +29292,7 @@ function runningAs(pid) {
|
|
|
29013
29292
|
const began = startedAt(pid, null);
|
|
29014
29293
|
const when = began === null ? {} : { startedAt: began };
|
|
29015
29294
|
if (process.platform === "linux") {
|
|
29016
|
-
const raw =
|
|
29295
|
+
const raw = readFileSync14(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
|
|
29017
29296
|
return raw === "" ? null : { kind: "commandLine", text: raw, ...when };
|
|
29018
29297
|
}
|
|
29019
29298
|
const args = execFileSync2("ps", ["-p", String(pid), "-o", "args="], {
|
|
@@ -29142,13 +29421,13 @@ function clearRunRecord(ours = () => true) {
|
|
|
29142
29421
|
continue;
|
|
29143
29422
|
}
|
|
29144
29423
|
try {
|
|
29145
|
-
|
|
29424
|
+
rmSync6(path);
|
|
29146
29425
|
} catch {}
|
|
29147
29426
|
}
|
|
29148
29427
|
}
|
|
29149
29428
|
function ageMs(path, now) {
|
|
29150
29429
|
try {
|
|
29151
|
-
return now -
|
|
29430
|
+
return now - statSync5(path).mtimeMs;
|
|
29152
29431
|
} catch {
|
|
29153
29432
|
return 0;
|
|
29154
29433
|
}
|
|
@@ -29166,19 +29445,19 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
29166
29445
|
if (!entry.startsWith("run-") || !entry.endsWith(".log")) {
|
|
29167
29446
|
continue;
|
|
29168
29447
|
}
|
|
29169
|
-
const path =
|
|
29448
|
+
const path = join14(directory, entry);
|
|
29170
29449
|
if (path === keep || ageMs(path, now) < keepMs) {
|
|
29171
29450
|
continue;
|
|
29172
29451
|
}
|
|
29173
29452
|
try {
|
|
29174
|
-
|
|
29453
|
+
rmSync6(path);
|
|
29175
29454
|
} catch {}
|
|
29176
29455
|
}
|
|
29177
29456
|
}
|
|
29178
29457
|
|
|
29179
29458
|
// src/setup.ts
|
|
29180
|
-
import { cpSync, existsSync as
|
|
29181
|
-
import { dirname as
|
|
29459
|
+
import { cpSync, existsSync as existsSync9, mkdirSync as mkdirSync9, readFileSync as readFileSync15, rmSync as rmSync7, writeFileSync as writeFileSync10 } from "node:fs";
|
|
29460
|
+
import { dirname as dirname9, join as join15 } from "node:path";
|
|
29182
29461
|
var ADDONS = ["gdharness_editor", "gdharness_runtime", "auto_reload"];
|
|
29183
29462
|
var RUNTIME_AUTOLOAD = {
|
|
29184
29463
|
name: "GdharnessRuntime",
|
|
@@ -29186,11 +29465,11 @@ var RUNTIME_AUTOLOAD = {
|
|
|
29186
29465
|
};
|
|
29187
29466
|
var VERSION_MARKER = ".gdharness-version";
|
|
29188
29467
|
function installedAddonVersion(projectPath) {
|
|
29189
|
-
const marker =
|
|
29190
|
-
if (!
|
|
29468
|
+
const marker = join15(projectPath, "addons", ADDONS[0], VERSION_MARKER);
|
|
29469
|
+
if (!existsSync9(marker)) {
|
|
29191
29470
|
return null;
|
|
29192
29471
|
}
|
|
29193
|
-
const said =
|
|
29472
|
+
const said = readFileSync15(marker, "utf8").trim();
|
|
29194
29473
|
return said === "" ? null : said;
|
|
29195
29474
|
}
|
|
29196
29475
|
|
|
@@ -29211,7 +29490,7 @@ function argumentsOf(spec, op) {
|
|
|
29211
29490
|
return Object.keys(spec.parameters).filter((name) => opTakes(spec, op, name));
|
|
29212
29491
|
}
|
|
29213
29492
|
function theGamePicked() {
|
|
29214
|
-
return "the game this server started or is playing when it is among those running, named under " + "`answeredBy` where there was a choice; else the only game there is; else a refusal naming " + "every game
|
|
29493
|
+
return "the game this server started or is playing when it is among those running, named under " + "`answeredBy` where there was a choice; else the only game there is; else a refusal naming " + "every game and which argument tells them apart, `pid` when they are all one project and " + "`projectPath` when they are not";
|
|
29215
29494
|
}
|
|
29216
29495
|
var PROJECT_PATH = {
|
|
29217
29496
|
type: "string",
|
|
@@ -29878,7 +30157,7 @@ var TOOL_SPECS = [
|
|
|
29878
30157
|
},
|
|
29879
30158
|
{
|
|
29880
30159
|
name: "editor_output",
|
|
29881
|
-
description: "What the project started by editor_run has printed, as entries with a severity: the errors and warnings the engine reported, each with where it happened, and everything else as info. For a run the editor plays the reports come from the game itself, through the runtime addon, since the editor's debugger relays what a game prints and not what it reports; a played game without the runtime addon answers here with its prints alone. Answers with the counts and the verdict as well as the entries. heldAt is three answers: null for a run this server knows is not held, running or over, an object for one it knows is held at a breakpoint, whether the adapter said so or the runtime did by accepting a connection and never answering, and absent with heldUnknown beside it for a server that connected to the debugger after the stop and has no runtime to ask, since the adapter reports a stop only to the sessions connected when it happened. A run that has quit still answers here, with running false and its exit code, until the next one starts. A run this server started also answers with transcript, the file both its streams are written to: uncapped, written while the run is going, and the thing to read or tail for a long run rather than the engine's own log, which every engine start rotates away. Read the file for output and this answer for state: endedBy says whether gdharness ended the run, and a transcript that has stopped growing is a run between prints rather than a run that is over. running is asked of the operating system whenever there is a process to ask about, which is every run this server started and every editor-played run whose game announced its runtime. For an editor-played run that never announced, there is no process id here and running is the editor's answer, which can lag: one measured elsewhere reported a game as playing for fifteen seconds after its process had been ended from outside. editor_status names that game under runtimes, which does not depend on the editor answering.",
|
|
30160
|
+
description: "What the project started by editor_run has printed, as entries with a severity: the errors and warnings the engine reported, each with where it happened, and everything else as info. For a run the editor plays the reports come from the game itself, through the runtime addon, since the editor's debugger relays what a game prints and not what it reports; a played game without the runtime addon answers here with its prints alone. Answers with the counts and the verdict as well as the entries. heldAt is three answers: null for a run this server knows is not held, running or over, an object for one it knows is held at a breakpoint, whether the adapter said so or the runtime did by accepting a connection and never answering, and absent with heldUnknown beside it for a server that connected to the debugger after the stop and has no runtime to ask, since the adapter reports a stop only to the sessions connected when it happened. A run that has quit still answers here, with running false and its exit code, until the next one starts. A run this server started also answers with transcript, the file both its streams are written to: uncapped, written while the run is going, and the thing to read or tail for a long run rather than the engine's own log, which every engine start rotates away. Read the file for output and this answer for state: endedBy says whether gdharness ended the run, and a transcript that has stopped growing is a run between prints rather than a run that is over. running is asked of the operating system whenever there is a process to ask about, which is every run this server started and every editor-played run whose game announced its runtime. For an editor-played run that never announced, there is no process id here and running is the editor's answer, which can lag: one measured elsewhere reported a game as playing for fifteen seconds after its process had been ended from outside. editor_status names that game under runtimes, which does not depend on the editor answering. op: \"editor\" answers about the editor itself instead, which is a different log and the only way anything here can read what an editor printed: its console reaches no plugin, so the addon cannot scrape it, and an editor gdharness opened is asked for a log file as it starts. That covers the whole session from the editor's first line, which is where the output worth reading usually is, since the class cache, the plugins and the language server all say what they have to say before any harness has connected. The answer carries the entries in the order they were printed and, beside them under repeated, each message shape several lines share: how many, what varied in each slot of it and how many different values stood there, the first occurrence, and the ordinary lines above the group, found by walking back past the group rather than by counting lines. An editor somebody opened by hand has no console here and is refused saying so rather than answered empty, because an editor that printed nothing and an editor nobody captured are not the same thing.",
|
|
29882
30161
|
parameters: {
|
|
29883
30162
|
severity: {
|
|
29884
30163
|
type: "string",
|
|
@@ -29886,10 +30165,12 @@ var TOOL_SPECS = [
|
|
|
29886
30165
|
description: "The least severe entry to include. Default info, which is everything."
|
|
29887
30166
|
},
|
|
29888
30167
|
sinceLastCall: {
|
|
30168
|
+
ops: ["run"],
|
|
29889
30169
|
type: "boolean",
|
|
29890
30170
|
description: "Only entries this has not already answered with at this severity. Default false. Measured per severity floor and not across them, so polling for errors leaves the rest of the output for a later read rather than consuming it, and a read with contains is a search over the whole run and consumes nothing."
|
|
29891
30171
|
},
|
|
29892
30172
|
cpu: {
|
|
30173
|
+
ops: ["run"],
|
|
29893
30174
|
type: "boolean",
|
|
29894
30175
|
description: "Also answer with cpuSeconds, the processor time the run has used. Default false, because asking costs a subprocess. Worth it when elapsedMs is climbing and nothing is being printed: processor time standing still is a run that has stopped doing anything, rather than one that is slow."
|
|
29895
30176
|
},
|
|
@@ -29900,8 +30181,24 @@ var TOOL_SPECS = [
|
|
|
29900
30181
|
limit: {
|
|
29901
30182
|
type: "number",
|
|
29902
30183
|
description: "The most entries to answer with, newest kept. Default 200, and omitted says how many matching entries that left out. A run long enough to pass it needs this raised, not just filtered: filtering narrows what counts as matching, and the newest of those is still all one answer carries."
|
|
30184
|
+
},
|
|
30185
|
+
before: {
|
|
30186
|
+
ops: ["editor"],
|
|
30187
|
+
type: "number",
|
|
30188
|
+
description: "How many lines above a repeated group to carry with it, counting only lines that are not themselves part of a group. Default 3. The line that explains a wall of identical errors is the last ordinary line above it, so this walks past the wall rather than a fixed distance back."
|
|
29903
30189
|
}
|
|
29904
30190
|
},
|
|
30191
|
+
operations: {
|
|
30192
|
+
run: {
|
|
30193
|
+
summary: "What the game a run is playing has printed, which is everything described above",
|
|
30194
|
+
requires: []
|
|
30195
|
+
},
|
|
30196
|
+
editor: {
|
|
30197
|
+
summary: "What the connected editor itself has printed, from its first line, with repeated messages grouped and the ordinary lines above each group carried with it",
|
|
30198
|
+
requires: []
|
|
30199
|
+
}
|
|
30200
|
+
},
|
|
30201
|
+
defaultOperation: "run",
|
|
29905
30202
|
requires: []
|
|
29906
30203
|
},
|
|
29907
30204
|
{
|
|
@@ -30360,7 +30657,7 @@ function buildToolDefinitions() {
|
|
|
30360
30657
|
var UPDATE_NOTICE_EVERY = 500;
|
|
30361
30658
|
var FEEDBACK_NOTICE_EVERY = 250;
|
|
30362
30659
|
var run5 = promisify5(execFile5);
|
|
30363
|
-
var __dirname2 =
|
|
30660
|
+
var __dirname2 = dirname10(fileURLToPath2(import.meta.url));
|
|
30364
30661
|
var EDITOR_RESTART_TIMEOUT_MS = 90000;
|
|
30365
30662
|
var HOLD_PING_MS = 2000;
|
|
30366
30663
|
var SLOWEST_FRAME_RATE = 20;
|
|
@@ -30473,6 +30770,7 @@ function endedToStartThis(ended) {
|
|
|
30473
30770
|
return ` ${which} was ended to start this one; its output is no longer what editor_output answers about.`;
|
|
30474
30771
|
}
|
|
30475
30772
|
var LONGEST_BOOT_NOTED_MS = 5 * 60000;
|
|
30773
|
+
var MOST_GROUPS = 20;
|
|
30476
30774
|
function runtimeVerdict(endpoint, after) {
|
|
30477
30775
|
if (endpoint !== null) {
|
|
30478
30776
|
return { listening: true, pid: endpoint.pid, port: endpoint.port };
|
|
@@ -30647,7 +30945,7 @@ function aboveTheRunner(entry) {
|
|
|
30647
30945
|
}
|
|
30648
30946
|
function elsewhereIn(projectPath, asked) {
|
|
30649
30947
|
const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
|
|
30650
|
-
if (wanted === "" ||
|
|
30948
|
+
if (wanted === "" || existsSync10(join16(projectPath, wanted))) {
|
|
30651
30949
|
return "";
|
|
30652
30950
|
}
|
|
30653
30951
|
const root = (wanted.split("/")[0] ?? "").toLowerCase();
|
|
@@ -30661,7 +30959,7 @@ function elsewhereIn(projectPath, asked) {
|
|
|
30661
30959
|
return near.length === 0 ? "" : ` The project does have ${there}.`;
|
|
30662
30960
|
}
|
|
30663
30961
|
function hasMainScene(projectFile) {
|
|
30664
|
-
const scene = parseProjectGodot(
|
|
30962
|
+
const scene = parseProjectGodot(readFileSync16(projectFile, "utf8"))["application"]?.["run/main_scene"];
|
|
30665
30963
|
return typeof scene === "string" && scene !== "";
|
|
30666
30964
|
}
|
|
30667
30965
|
function runtimeAddonAnnouncesEditors(projectPath) {
|
|
@@ -30669,13 +30967,13 @@ function runtimeAddonAnnouncesEditors(projectPath) {
|
|
|
30669
30967
|
return false;
|
|
30670
30968
|
}
|
|
30671
30969
|
try {
|
|
30672
|
-
return
|
|
30970
|
+
return readFileSync16(join16(projectPath, RUNTIME_AUTOLOAD.path), "utf8").includes('"editor_pid"');
|
|
30673
30971
|
} catch {
|
|
30674
30972
|
return false;
|
|
30675
30973
|
}
|
|
30676
30974
|
}
|
|
30677
30975
|
function editorPlaysHeadless(projectFile) {
|
|
30678
|
-
const runArgs = parseProjectGodot(
|
|
30976
|
+
const runArgs = parseProjectGodot(readFileSync16(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
|
|
30679
30977
|
return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
|
|
30680
30978
|
}
|
|
30681
30979
|
function camelCased(params) {
|
|
@@ -30694,7 +30992,7 @@ function needsItsOwnProcess(asked) {
|
|
|
30694
30992
|
class GodotServer {
|
|
30695
30993
|
mcp;
|
|
30696
30994
|
locator = new GodotLocator;
|
|
30697
|
-
operationsScript =
|
|
30995
|
+
operationsScript = join16(__dirname2, "godot", "operations", "godot_operations.gd");
|
|
30698
30996
|
godotBridge;
|
|
30699
30997
|
tools = buildToolDefinitions();
|
|
30700
30998
|
activeProcess = null;
|
|
@@ -31359,8 +31657,8 @@ class GodotServer {
|
|
|
31359
31657
|
if (path === undefined) {
|
|
31360
31658
|
return { ok: false, response: this.createErrorResponse("projectPath is required.") };
|
|
31361
31659
|
}
|
|
31362
|
-
const file =
|
|
31363
|
-
if (!
|
|
31660
|
+
const file = join16(path, "project.godot");
|
|
31661
|
+
if (!existsSync10(file)) {
|
|
31364
31662
|
return {
|
|
31365
31663
|
ok: false,
|
|
31366
31664
|
response: this.createErrorResponse(`Not a Godot project: ${path}`, [
|
|
@@ -31628,7 +31926,7 @@ class GodotServer {
|
|
|
31628
31926
|
if (!engine.ok) {
|
|
31629
31927
|
return engine.response;
|
|
31630
31928
|
}
|
|
31631
|
-
const application = parseProjectGodot(
|
|
31929
|
+
const application = parseProjectGodot(readFileSync16(project.value.file, "utf8"))["application"] ?? {};
|
|
31632
31930
|
const name = application["config/name"];
|
|
31633
31931
|
const mainScene = application["run/main_scene"];
|
|
31634
31932
|
const info = {
|
|
@@ -31689,18 +31987,18 @@ class GodotServer {
|
|
|
31689
31987
|
if (!engine.ok) {
|
|
31690
31988
|
return engine.response;
|
|
31691
31989
|
}
|
|
31692
|
-
const folder =
|
|
31990
|
+
const folder = dirname10(output.absolutePath);
|
|
31693
31991
|
try {
|
|
31694
|
-
|
|
31992
|
+
mkdirSync10(folder, { recursive: true });
|
|
31695
31993
|
} catch (error) {
|
|
31696
|
-
return this.createErrorResponse(`Cannot create ${
|
|
31994
|
+
return this.createErrorResponse(`Cannot create ${dirname10(output.relativePath)} to export into: ${errorMessage(error)}`);
|
|
31697
31995
|
}
|
|
31698
31996
|
const debug = readBoolean(args, "debug") ?? false;
|
|
31699
|
-
const exportLogs = mkdtempSync2(
|
|
31997
|
+
const exportLogs = mkdtempSync2(join16(tmpdir4(), "gdharness-export-"));
|
|
31700
31998
|
const exportArgs = [
|
|
31701
31999
|
"--headless",
|
|
31702
32000
|
"--log-file",
|
|
31703
|
-
|
|
32001
|
+
join16(exportLogs, "engine.log"),
|
|
31704
32002
|
"--path",
|
|
31705
32003
|
project.value.path,
|
|
31706
32004
|
debug ? "--export-debug" : "--export-release",
|
|
@@ -31728,7 +32026,7 @@ class GodotServer {
|
|
|
31728
32026
|
log.finish();
|
|
31729
32027
|
const problems = log.select({ severity: "warning", sinceLastCall: false, limit: 200 });
|
|
31730
32028
|
const verdict = {
|
|
31731
|
-
exported: exitCode === 0 && log.count("error") === 0 &&
|
|
32029
|
+
exported: exitCode === 0 && log.count("error") === 0 && existsSync10(output.absolutePath),
|
|
31732
32030
|
preset,
|
|
31733
32031
|
outputPath: output.relativePath,
|
|
31734
32032
|
debug,
|
|
@@ -31758,7 +32056,7 @@ class GodotServer {
|
|
|
31758
32056
|
return contained.response;
|
|
31759
32057
|
}
|
|
31760
32058
|
const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
|
|
31761
|
-
if (!
|
|
32059
|
+
if (!existsSync10(join16(project.value.path, runner))) {
|
|
31762
32060
|
return this.createErrorResponse(`gdUnit4 is not installed in this project: no ${runner}.`, [
|
|
31763
32061
|
"Install gdUnit4 under addons/gdUnit4, from https://github.com/godot-gdunit-labs/gdUnit4"
|
|
31764
32062
|
]);
|
|
@@ -31793,7 +32091,7 @@ class GodotServer {
|
|
|
31793
32091
|
];
|
|
31794
32092
|
const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 600000;
|
|
31795
32093
|
this.logDebug(`Running tests: ${engine.value} ${cmdArgs.join(" ")}`);
|
|
31796
|
-
const userData = mkdtempSync2(
|
|
32094
|
+
const userData = mkdtempSync2(join16(tmpdir4(), "gdharness-tests-"));
|
|
31797
32095
|
const run = this.spawnGame(engine.value, cmdArgs, userDataIn(userData));
|
|
31798
32096
|
const hung = await new Promise((resolve) => {
|
|
31799
32097
|
const timer = setTimeout(() => {
|
|
@@ -31809,21 +32107,21 @@ class GodotServer {
|
|
|
31809
32107
|
resolve(false);
|
|
31810
32108
|
});
|
|
31811
32109
|
});
|
|
31812
|
-
const reportsDir =
|
|
32110
|
+
const reportsDir = join16(project.value.path, ".godot", ...ours.split("/"));
|
|
31813
32111
|
let report = null;
|
|
31814
32112
|
let reportProblem = null;
|
|
31815
32113
|
try {
|
|
31816
|
-
const written =
|
|
32114
|
+
const written = existsSync10(reportsDir) ? readdirSync6(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
|
|
31817
32115
|
const newest = written.at(-1);
|
|
31818
32116
|
if (newest !== undefined) {
|
|
31819
|
-
report = parseJUnit(
|
|
32117
|
+
report = parseJUnit(readFileSync16(join16(reportsDir, newest, "results.xml"), "utf8"));
|
|
31820
32118
|
}
|
|
31821
32119
|
} catch (error) {
|
|
31822
32120
|
reportProblem = errorMessage(error);
|
|
31823
32121
|
} finally {
|
|
31824
32122
|
discard(reportsDir, userData);
|
|
31825
32123
|
try {
|
|
31826
|
-
rmdirSync(
|
|
32124
|
+
rmdirSync(dirname10(reportsDir));
|
|
31827
32125
|
} catch {}
|
|
31828
32126
|
}
|
|
31829
32127
|
const engineEntries = run.log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries.map(aboveTheRunner);
|
|
@@ -31955,11 +32253,11 @@ class GodotServer {
|
|
|
31955
32253
|
const cached = cachedClasses(projectPath);
|
|
31956
32254
|
const contradicted = cached === null ? [] : contradictedDiagnostics(messages, cached, (resourcePath) => {
|
|
31957
32255
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
31958
|
-
if (!contained.ok || !
|
|
32256
|
+
if (!contained.ok || !existsSync10(contained.absolutePath)) {
|
|
31959
32257
|
return null;
|
|
31960
32258
|
}
|
|
31961
32259
|
try {
|
|
31962
|
-
return
|
|
32260
|
+
return readFileSync16(contained.absolutePath, "utf8");
|
|
31963
32261
|
} catch {
|
|
31964
32262
|
return null;
|
|
31965
32263
|
}
|
|
@@ -32009,11 +32307,11 @@ class GodotServer {
|
|
|
32009
32307
|
}
|
|
32010
32308
|
sourceOfClass(projectPath, resourcePath) {
|
|
32011
32309
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
32012
|
-
if (!contained.ok || !
|
|
32310
|
+
if (!contained.ok || !existsSync10(contained.absolutePath)) {
|
|
32013
32311
|
return null;
|
|
32014
32312
|
}
|
|
32015
32313
|
try {
|
|
32016
|
-
return
|
|
32314
|
+
return readFileSync16(contained.absolutePath, "utf8");
|
|
32017
32315
|
} catch {
|
|
32018
32316
|
return null;
|
|
32019
32317
|
}
|
|
@@ -32242,7 +32540,7 @@ class GodotServer {
|
|
|
32242
32540
|
return null;
|
|
32243
32541
|
}
|
|
32244
32542
|
try {
|
|
32245
|
-
return settingKeys(
|
|
32543
|
+
return settingKeys(readFileSync16(join16(projectPath, "project.godot"), "utf8"));
|
|
32246
32544
|
} catch {
|
|
32247
32545
|
return null;
|
|
32248
32546
|
}
|
|
@@ -32372,7 +32670,8 @@ class GodotServer {
|
|
|
32372
32670
|
});
|
|
32373
32671
|
}
|
|
32374
32672
|
async openAnEditor(engine, projectPath, ports) {
|
|
32375
|
-
|
|
32673
|
+
clearEditorLog(projectPath);
|
|
32674
|
+
const editor = spawn(engine, editorArguments(projectPath, ports, editorLogPath(projectPath)), {
|
|
32376
32675
|
stdio: "ignore",
|
|
32377
32676
|
detached: true,
|
|
32378
32677
|
env: {
|
|
@@ -32383,6 +32682,9 @@ class GodotServer {
|
|
|
32383
32682
|
[OPENED_BY_A_SERVER]: "1"
|
|
32384
32683
|
}
|
|
32385
32684
|
});
|
|
32685
|
+
if (editor.pid !== undefined) {
|
|
32686
|
+
writeEditorLogNote(projectPath, editor.pid);
|
|
32687
|
+
}
|
|
32386
32688
|
const started = await new Promise((resolve) => {
|
|
32387
32689
|
editor.once("spawn", () => {
|
|
32388
32690
|
resolve(null);
|
|
@@ -32489,7 +32791,7 @@ class GodotServer {
|
|
|
32489
32791
|
}
|
|
32490
32792
|
async runtimeUp(projectPath, before, budget, withArgs) {
|
|
32491
32793
|
const budgetMs = budget.ms;
|
|
32492
|
-
if (!
|
|
32794
|
+
if (!existsSync10(join16(projectPath, RUNTIME_AUTOLOAD.path))) {
|
|
32493
32795
|
return runtimeVerdict(null, { addon: false, budgetMs, heldAt: null, running: false, withArgs });
|
|
32494
32796
|
}
|
|
32495
32797
|
let asking = null;
|
|
@@ -32552,7 +32854,7 @@ class GodotServer {
|
|
|
32552
32854
|
}
|
|
32553
32855
|
let announcedAt;
|
|
32554
32856
|
try {
|
|
32555
|
-
announcedAt =
|
|
32857
|
+
announcedAt = statSync6(endpoint.file).mtimeMs;
|
|
32556
32858
|
} catch {
|
|
32557
32859
|
return;
|
|
32558
32860
|
}
|
|
@@ -32588,7 +32890,7 @@ class GodotServer {
|
|
|
32588
32890
|
}
|
|
32589
32891
|
const startedAt = Date.now();
|
|
32590
32892
|
const transcript = openTranscript(startedAt);
|
|
32591
|
-
|
|
32893
|
+
closeSync2(transcript.fd);
|
|
32592
32894
|
const playAnswer = asParams(JSON.parse(answer.content[0]?.text ?? "{}"));
|
|
32593
32895
|
const played = {
|
|
32594
32896
|
process: null,
|
|
@@ -32655,7 +32957,7 @@ class GodotServer {
|
|
|
32655
32957
|
}
|
|
32656
32958
|
let handle;
|
|
32657
32959
|
try {
|
|
32658
|
-
handle =
|
|
32960
|
+
handle = openSync3(game.errorReport, "r");
|
|
32659
32961
|
} catch {
|
|
32660
32962
|
return;
|
|
32661
32963
|
}
|
|
@@ -32666,7 +32968,7 @@ class GodotServer {
|
|
|
32666
32968
|
return;
|
|
32667
32969
|
}
|
|
32668
32970
|
const buffer = Buffer.alloc(size - from);
|
|
32669
|
-
const read =
|
|
32971
|
+
const read = readSync2(handle, buffer, 0, buffer.length, from);
|
|
32670
32972
|
game.errorReportOffset = from + read;
|
|
32671
32973
|
const reported = buffer.subarray(0, read);
|
|
32672
32974
|
if (game.transcript !== null) {
|
|
@@ -32675,7 +32977,7 @@ class GodotServer {
|
|
|
32675
32977
|
game.log.append("stderr", reported);
|
|
32676
32978
|
}
|
|
32677
32979
|
} finally {
|
|
32678
|
-
|
|
32980
|
+
closeSync2(handle);
|
|
32679
32981
|
}
|
|
32680
32982
|
}
|
|
32681
32983
|
recordWhatItBrokeOn(game) {
|
|
@@ -32769,7 +33071,7 @@ class GodotServer {
|
|
|
32769
33071
|
...env ? { env } : {}
|
|
32770
33072
|
});
|
|
32771
33073
|
} finally {
|
|
32772
|
-
|
|
33074
|
+
closeSync2(transcript.fd);
|
|
32773
33075
|
}
|
|
32774
33076
|
child.unref();
|
|
32775
33077
|
const log = new GameLog;
|
|
@@ -32816,7 +33118,7 @@ class GodotServer {
|
|
|
32816
33118
|
}
|
|
32817
33119
|
let handle;
|
|
32818
33120
|
try {
|
|
32819
|
-
handle =
|
|
33121
|
+
handle = openSync3(game.transcript, "r");
|
|
32820
33122
|
} catch {
|
|
32821
33123
|
return;
|
|
32822
33124
|
}
|
|
@@ -32826,11 +33128,11 @@ class GodotServer {
|
|
|
32826
33128
|
return;
|
|
32827
33129
|
}
|
|
32828
33130
|
const buffer = Buffer.alloc(size - game.readOffset);
|
|
32829
|
-
const read =
|
|
33131
|
+
const read = readSync2(handle, buffer, 0, buffer.length, game.readOffset);
|
|
32830
33132
|
game.readOffset += read;
|
|
32831
33133
|
game.log.append("transcript", buffer.subarray(0, read));
|
|
32832
33134
|
} finally {
|
|
32833
|
-
|
|
33135
|
+
closeSync2(handle);
|
|
32834
33136
|
}
|
|
32835
33137
|
}
|
|
32836
33138
|
async pickUpWhatTheEditorIsPlaying() {
|
|
@@ -33028,7 +33330,7 @@ class GodotServer {
|
|
|
33028
33330
|
}
|
|
33029
33331
|
const unreadable = this.aRuntimeOfOursIsTooFarOff();
|
|
33030
33332
|
if (unreadable !== null) {
|
|
33031
|
-
const behind = unreadable
|
|
33333
|
+
const behind = whichHalfIsBehind([unreadable]);
|
|
33032
33334
|
return `A game is running for ${unreadable.project.path}, pid ${unreadable.pid}, announced in protocol` + ` ${unreadable.protocol}, which this server does not speak: it speaks ${RUNTIME_PROTOCOL}, so ${behind}.` + " Until then neither editor_run nor the runtime_* tools can reach it." + " A run started here is a separate process rather than a replacement, so starting one leaves" + " this game running as well.";
|
|
33033
33335
|
}
|
|
33034
33336
|
const record = readRunRecord();
|
|
@@ -33153,7 +33455,57 @@ class GodotServer {
|
|
|
33153
33455
|
}
|
|
33154
33456
|
return runIsUp(run, said);
|
|
33155
33457
|
}
|
|
33458
|
+
handleEditorConsole(args) {
|
|
33459
|
+
const status = this.godotBridge.getStatus();
|
|
33460
|
+
const projectPath = status.projectPath ?? this.ownProject ?? undefined;
|
|
33461
|
+
if (projectPath === undefined) {
|
|
33462
|
+
return this.createErrorResponse("No editor is connected, so there is no console to read.", [
|
|
33463
|
+
"editor_status says whether one is connected and whether it may yet be",
|
|
33464
|
+
"editor_launch opens one, and an editor opened here has its console captured"
|
|
33465
|
+
]);
|
|
33466
|
+
}
|
|
33467
|
+
const console2 = editorConsole(projectPath, status.editorPid ?? null, status.openedByAServer === true);
|
|
33468
|
+
if ("kind" in console2) {
|
|
33469
|
+
return this.createErrorResponse(theConsoleWasNotCaptured(console2, projectPath), [
|
|
33470
|
+
"editor_launch restart replaces this editor with one whose console is captured",
|
|
33471
|
+
"editor_output with no op answers about the game a run is playing, which is a different log"
|
|
33472
|
+
]);
|
|
33473
|
+
}
|
|
33474
|
+
const severity = readString(args, "severity");
|
|
33475
|
+
const selected = console2.log.select({
|
|
33476
|
+
severity: severity === "error" || severity === "warning" ? severity : "info",
|
|
33477
|
+
sinceLastCall: false,
|
|
33478
|
+
contains: readNonEmptyString(args, "contains"),
|
|
33479
|
+
limit: readPositiveNumber(args, "limit") ?? 200
|
|
33480
|
+
});
|
|
33481
|
+
const everything = console2.log.everything();
|
|
33482
|
+
const grouped = bursts(everything, readPositiveNumber(args, "before") ?? 3);
|
|
33483
|
+
const shown = grouped.slice(0, MOST_GROUPS);
|
|
33484
|
+
return this.jsonTextResponse({
|
|
33485
|
+
editorPid: status.editorPid,
|
|
33486
|
+
projectPath,
|
|
33487
|
+
capturedIn: console2.path,
|
|
33488
|
+
...console2.of === undefined ? {} : {
|
|
33489
|
+
readFromTheStart: console2.readBytes,
|
|
33490
|
+
consoleBytes: console2.of,
|
|
33491
|
+
truncatedNote: `This editor's console is ${console2.of} bytes and the first ${console2.readBytes} of them were read, which is the start of the session rather than the end of it. Restart the editor to begin a fresh console.`
|
|
33492
|
+
},
|
|
33493
|
+
lines: everything.length,
|
|
33494
|
+
counts: {
|
|
33495
|
+
error: console2.log.count("error"),
|
|
33496
|
+
warning: console2.log.count("warning"),
|
|
33497
|
+
info: console2.log.count("info")
|
|
33498
|
+
},
|
|
33499
|
+
repeated: shown,
|
|
33500
|
+
moreShapes: grouped.length > shown.length ? grouped.length - shown.length : undefined,
|
|
33501
|
+
entries: forAnswer(selected.entries),
|
|
33502
|
+
omitted: selected.omitted === 0 ? undefined : selected.omitted
|
|
33503
|
+
});
|
|
33504
|
+
}
|
|
33156
33505
|
async handleGetDebugOutput(args, waitedMs) {
|
|
33506
|
+
if (readString(args, "op") === "editor") {
|
|
33507
|
+
return this.handleEditorConsole(args);
|
|
33508
|
+
}
|
|
33157
33509
|
await this.pickUpWhatTheEditorIsPlaying();
|
|
33158
33510
|
const run = this.currentRun();
|
|
33159
33511
|
if (!run) {
|
|
@@ -33604,8 +33956,8 @@ class GodotServer {
|
|
|
33604
33956
|
]);
|
|
33605
33957
|
}
|
|
33606
33958
|
const expectsScreenshot = command === "capture_screenshot" || command === "capture_viewport";
|
|
33607
|
-
const screenshotDir = expectsScreenshot ? mkdtempSync2(
|
|
33608
|
-
const screenshotPath = screenshotDir ?
|
|
33959
|
+
const screenshotDir = expectsScreenshot ? mkdtempSync2(join16(tmpdir4(), "gdharness-runtime-screenshot-")) : null;
|
|
33960
|
+
const screenshotPath = screenshotDir ? join16(screenshotDir, "capture.png") : null;
|
|
33609
33961
|
try {
|
|
33610
33962
|
const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs);
|
|
33611
33963
|
if (!reply.ok) {
|
|
@@ -33629,7 +33981,7 @@ class GodotServer {
|
|
|
33629
33981
|
type: "text",
|
|
33630
33982
|
text: `Screenshot captured: ${dimensions}${chosenAmongSeveral ? ` from pid ${own}, the game this server holds` : ""}`
|
|
33631
33983
|
},
|
|
33632
|
-
{ type: "image", data:
|
|
33984
|
+
{ type: "image", data: readFileSync16(screenshotPath).toString("base64"), mimeType: "image/png" }
|
|
33633
33985
|
]
|
|
33634
33986
|
};
|
|
33635
33987
|
} finally {
|