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/cli.js
CHANGED
|
@@ -182,8 +182,18 @@ function runArguments(options) {
|
|
|
182
182
|
}
|
|
183
183
|
return args;
|
|
184
184
|
}
|
|
185
|
-
function editorArguments(projectPath, ports) {
|
|
186
|
-
return [
|
|
185
|
+
function editorArguments(projectPath, ports, logFile) {
|
|
186
|
+
return [
|
|
187
|
+
"-e",
|
|
188
|
+
"--path",
|
|
189
|
+
projectPath,
|
|
190
|
+
"--lsp-port",
|
|
191
|
+
String(ports.lsp),
|
|
192
|
+
"--dap-port",
|
|
193
|
+
String(ports.dap),
|
|
194
|
+
"--log-file",
|
|
195
|
+
logFile
|
|
196
|
+
];
|
|
187
197
|
}
|
|
188
198
|
function userDataIn(home, variables = process.env) {
|
|
189
199
|
const moved = ["APPDATA", "XDG_DATA_HOME"];
|
|
@@ -7263,6 +7273,9 @@ class GameLog {
|
|
|
7263
7273
|
count(severity) {
|
|
7264
7274
|
return this.entries.filter((entry) => entry.severity === severity).length;
|
|
7265
7275
|
}
|
|
7276
|
+
everything() {
|
|
7277
|
+
return this.entries;
|
|
7278
|
+
}
|
|
7266
7279
|
select(options) {
|
|
7267
7280
|
const floor = options.sinceLastCall ? this.seen[options.severity] : 0;
|
|
7268
7281
|
const wanted = ADMITTED[options.severity];
|
|
@@ -15349,7 +15362,7 @@ function projectPathSentence(without = callsWithoutProjectPath()) {
|
|
|
15349
15362
|
return `Every call takes \`projectPath\` except ${named}, which ${without.length === 1 ? "takes" : "take"} none`;
|
|
15350
15363
|
}
|
|
15351
15364
|
function theGamePicked() {
|
|
15352
|
-
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
|
|
15365
|
+
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";
|
|
15353
15366
|
}
|
|
15354
15367
|
function toolSpec(name) {
|
|
15355
15368
|
return SPECS_BY_NAME[name];
|
|
@@ -16070,7 +16083,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16070
16083
|
},
|
|
16071
16084
|
{
|
|
16072
16085
|
name: "editor_output",
|
|
16073
|
-
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.",
|
|
16086
|
+
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.",
|
|
16074
16087
|
parameters: {
|
|
16075
16088
|
severity: {
|
|
16076
16089
|
type: "string",
|
|
@@ -16078,10 +16091,12 @@ var init_tool_definitions = __esm(() => {
|
|
|
16078
16091
|
description: "The least severe entry to include. Default info, which is everything."
|
|
16079
16092
|
},
|
|
16080
16093
|
sinceLastCall: {
|
|
16094
|
+
ops: ["run"],
|
|
16081
16095
|
type: "boolean",
|
|
16082
16096
|
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."
|
|
16083
16097
|
},
|
|
16084
16098
|
cpu: {
|
|
16099
|
+
ops: ["run"],
|
|
16085
16100
|
type: "boolean",
|
|
16086
16101
|
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."
|
|
16087
16102
|
},
|
|
@@ -16092,8 +16107,24 @@ var init_tool_definitions = __esm(() => {
|
|
|
16092
16107
|
limit: {
|
|
16093
16108
|
type: "number",
|
|
16094
16109
|
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."
|
|
16110
|
+
},
|
|
16111
|
+
before: {
|
|
16112
|
+
ops: ["editor"],
|
|
16113
|
+
type: "number",
|
|
16114
|
+
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."
|
|
16115
|
+
}
|
|
16116
|
+
},
|
|
16117
|
+
operations: {
|
|
16118
|
+
run: {
|
|
16119
|
+
summary: "What the game a run is playing has printed, which is everything described above",
|
|
16120
|
+
requires: []
|
|
16121
|
+
},
|
|
16122
|
+
editor: {
|
|
16123
|
+
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",
|
|
16124
|
+
requires: []
|
|
16095
16125
|
}
|
|
16096
16126
|
},
|
|
16127
|
+
defaultOperation: "run",
|
|
16097
16128
|
requires: []
|
|
16098
16129
|
},
|
|
16099
16130
|
{
|
|
@@ -31693,10 +31724,37 @@ async function announcedSince(projectPath, before, waiting = {}) {
|
|
|
31693
31724
|
function describe2(endpoint) {
|
|
31694
31725
|
return `pid ${endpoint.pid} on ${endpoint.address}:${endpoint.port} (${endpoint.project.name || "unnamed"} at ${endpoint.project.path})`;
|
|
31695
31726
|
}
|
|
31727
|
+
function describeWithin(endpoint) {
|
|
31728
|
+
return `pid ${endpoint.pid} on ${endpoint.address}:${endpoint.port}`;
|
|
31729
|
+
}
|
|
31730
|
+
function theOneProject(endpoints) {
|
|
31731
|
+
if (endpoints.some((endpoint) => endpoint.project.path === "")) {
|
|
31732
|
+
return;
|
|
31733
|
+
}
|
|
31734
|
+
const paths = new Set(endpoints.map((endpoint) => resolve3(endpoint.project.path)));
|
|
31735
|
+
const only = [...paths][0];
|
|
31736
|
+
return paths.size === 1 ? only : undefined;
|
|
31737
|
+
}
|
|
31738
|
+
function severalFrom(project, games) {
|
|
31739
|
+
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.";
|
|
31740
|
+
}
|
|
31741
|
+
function whichHalfIsBehind(unspoken) {
|
|
31742
|
+
const pids = (games) => games.map((game) => `pid ${game.pid}`).join(", ");
|
|
31743
|
+
const ahead = unspoken.filter((game) => game.protocol > RUNTIME_PROTOCOL);
|
|
31744
|
+
const behind = unspoken.filter((game) => game.protocol < RUNTIME_PROTOCOL);
|
|
31745
|
+
if (ahead.length > 0 && behind.length > 0) {
|
|
31746
|
+
return `they are on both sides of it: reconnect this server for ${pids(ahead)}, and reinstall the ` + `addon and restart ${pids(behind)}`;
|
|
31747
|
+
}
|
|
31748
|
+
if (ahead.length > 0) {
|
|
31749
|
+
return "this server is the older half: reconnect it so it spawns the installed version";
|
|
31750
|
+
}
|
|
31751
|
+
return `the addon is the older half: reinstall it and restart ${behind.length === 1 ? "the game" : "those games"}`;
|
|
31752
|
+
}
|
|
31696
31753
|
function tooNew(unspoken) {
|
|
31697
31754
|
const named = unspoken.map((game) => `pid ${game.pid} speaks ${game.protocol} (${game.project.name || "unnamed"} at ${game.project.path})`).join("; ");
|
|
31698
|
-
const
|
|
31699
|
-
|
|
31755
|
+
const count = unspoken.length === 1 ? "A game is running" : `${unspoken.length} games are running`;
|
|
31756
|
+
const spoken = new Set(unspoken.map((game) => game.protocol)).size === 1 ? "a protocol" : "protocols";
|
|
31757
|
+
return `${count}, in ${spoken} this server does not speak: ${named}. This server speaks ${RUNTIME_PROTOCOL}, so ${whichHalfIsBehind(unspoken)}.`;
|
|
31700
31758
|
}
|
|
31701
31759
|
function chooseRuntime(endpoints, projectPath, unspoken = [], pid) {
|
|
31702
31760
|
if (pid !== undefined) {
|
|
@@ -31713,8 +31771,16 @@ function chooseRuntime(endpoints, projectPath, unspoken = [], pid) {
|
|
|
31713
31771
|
if (tooNewToo !== undefined) {
|
|
31714
31772
|
return { problem: tooNew([tooNewToo]) };
|
|
31715
31773
|
}
|
|
31774
|
+
if (endpoints.length > 0) {
|
|
31775
|
+
return {
|
|
31776
|
+
problem: `No running game has pid ${pid}. Running: ${endpoints.map(describe2).join("; ")}. editor_status lists them under runtimes as they come and go.`
|
|
31777
|
+
};
|
|
31778
|
+
}
|
|
31779
|
+
if (unspoken.length > 0) {
|
|
31780
|
+
return { problem: `No game reachable from here has pid ${pid}. ${tooNew(unspoken)}` };
|
|
31781
|
+
}
|
|
31716
31782
|
return {
|
|
31717
|
-
problem:
|
|
31783
|
+
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.`
|
|
31718
31784
|
};
|
|
31719
31785
|
}
|
|
31720
31786
|
if (endpoints.length === 0) {
|
|
@@ -31746,15 +31812,17 @@ function chooseRuntime(endpoints, projectPath, unspoken = [], pid) {
|
|
|
31746
31812
|
if (matching.length === 1 && matching[0]) {
|
|
31747
31813
|
return { endpoint: matching[0] };
|
|
31748
31814
|
}
|
|
31749
|
-
return {
|
|
31750
|
-
problem: `Several games are running from ${wanted}: ${matching.map(describe2).join("; ")}. Pass pid to choose one.`
|
|
31751
|
-
};
|
|
31815
|
+
return { problem: severalFrom(wanted, matching) };
|
|
31752
31816
|
}
|
|
31753
31817
|
if (endpoints.length === 1 && endpoints[0]) {
|
|
31754
31818
|
return { endpoint: endpoints[0] };
|
|
31755
31819
|
}
|
|
31820
|
+
const shared = theOneProject(endpoints);
|
|
31821
|
+
if (shared !== undefined) {
|
|
31822
|
+
return { problem: severalFrom(shared, endpoints) };
|
|
31823
|
+
}
|
|
31756
31824
|
return {
|
|
31757
|
-
problem: `Several games are running: ${endpoints.map(describe2).join("; ")}. Pass
|
|
31825
|
+
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.`
|
|
31758
31826
|
};
|
|
31759
31827
|
}
|
|
31760
31828
|
function runtimeRequest(endpoint, command, params, timeoutMs) {
|
|
@@ -32802,6 +32870,235 @@ var init_dap_client = __esm(() => {
|
|
|
32802
32870
|
init_ports();
|
|
32803
32871
|
});
|
|
32804
32872
|
|
|
32873
|
+
// src/editor-log.ts
|
|
32874
|
+
import {
|
|
32875
|
+
closeSync,
|
|
32876
|
+
existsSync as existsSync12,
|
|
32877
|
+
mkdirSync as mkdirSync10,
|
|
32878
|
+
openSync,
|
|
32879
|
+
readFileSync as readFileSync16,
|
|
32880
|
+
readSync,
|
|
32881
|
+
rmSync as rmSync8,
|
|
32882
|
+
statSync as statSync4,
|
|
32883
|
+
writeFileSync as writeFileSync12
|
|
32884
|
+
} from "node:fs";
|
|
32885
|
+
import { dirname as dirname12, join as join18 } from "node:path";
|
|
32886
|
+
function editorLogPath(projectPath) {
|
|
32887
|
+
return join18(projectPath, ".godot", "gdharness-editor.log");
|
|
32888
|
+
}
|
|
32889
|
+
function editorLogNotePath(projectPath) {
|
|
32890
|
+
return join18(projectPath, ".godot", "gdharness-editor.json");
|
|
32891
|
+
}
|
|
32892
|
+
function readEditorLogNote(projectPath) {
|
|
32893
|
+
try {
|
|
32894
|
+
const fields = asParams(JSON.parse(readFileSync16(editorLogNotePath(projectPath), "utf8")));
|
|
32895
|
+
const pid = readNumber(fields, "pid");
|
|
32896
|
+
const startedAt = readString(fields, "startedAt");
|
|
32897
|
+
return pid !== undefined && Number.isInteger(pid) && pid > 0 && startedAt !== undefined ? { pid, startedAt } : null;
|
|
32898
|
+
} catch {
|
|
32899
|
+
return null;
|
|
32900
|
+
}
|
|
32901
|
+
}
|
|
32902
|
+
function clearEditorLog(projectPath) {
|
|
32903
|
+
try {
|
|
32904
|
+
const path = editorLogPath(projectPath);
|
|
32905
|
+
mkdirSync10(dirname12(path), { recursive: true });
|
|
32906
|
+
rmSync8(path, { force: true });
|
|
32907
|
+
rmSync8(editorLogNotePath(projectPath), { force: true });
|
|
32908
|
+
} catch {}
|
|
32909
|
+
}
|
|
32910
|
+
function writeEditorLogNote(projectPath, pid) {
|
|
32911
|
+
try {
|
|
32912
|
+
const path = editorLogNotePath(projectPath);
|
|
32913
|
+
mkdirSync10(dirname12(path), { recursive: true });
|
|
32914
|
+
writeFileSync12(path, JSON.stringify({ pid, startedAt: new Date().toISOString() }, null, 2), "utf8");
|
|
32915
|
+
} catch {}
|
|
32916
|
+
}
|
|
32917
|
+
function theConsoleWasNotCaptured(why, projectPath) {
|
|
32918
|
+
if (why.kind === "not ours") {
|
|
32919
|
+
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.";
|
|
32920
|
+
}
|
|
32921
|
+
if (why.kind === "older server") {
|
|
32922
|
+
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.";
|
|
32923
|
+
}
|
|
32924
|
+
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.";
|
|
32925
|
+
}
|
|
32926
|
+
function editorConsole(projectPath, editorPid, openedByAServer) {
|
|
32927
|
+
if (!openedByAServer) {
|
|
32928
|
+
return { kind: "not ours" };
|
|
32929
|
+
}
|
|
32930
|
+
const noted = readEditorLogNote(projectPath);
|
|
32931
|
+
if (noted === null || editorPid === null || noted.pid !== editorPid) {
|
|
32932
|
+
return { kind: "older server", notedPid: noted?.pid ?? null };
|
|
32933
|
+
}
|
|
32934
|
+
const path = editorLogPath(projectPath);
|
|
32935
|
+
if (!existsSync12(path)) {
|
|
32936
|
+
return { kind: "not written" };
|
|
32937
|
+
}
|
|
32938
|
+
const log = new GameLog;
|
|
32939
|
+
const { text, of } = theFirstOf(path);
|
|
32940
|
+
log.append("transcript", text);
|
|
32941
|
+
log.finish();
|
|
32942
|
+
return { log, path, ...of === undefined ? {} : { readBytes: text.length, of } };
|
|
32943
|
+
}
|
|
32944
|
+
function theFirstOf(path) {
|
|
32945
|
+
const whole = statSync4(path).size;
|
|
32946
|
+
if (whole <= MOST_CONSOLE_BYTES) {
|
|
32947
|
+
return { text: readFileSync16(path, "utf8") };
|
|
32948
|
+
}
|
|
32949
|
+
const handle = openSync(path, "r");
|
|
32950
|
+
try {
|
|
32951
|
+
const buffer = Buffer.alloc(MOST_CONSOLE_BYTES);
|
|
32952
|
+
const read = readSync(handle, buffer, 0, MOST_CONSOLE_BYTES, 0);
|
|
32953
|
+
const decoded = buffer.subarray(0, read).toString("utf8");
|
|
32954
|
+
const lastLine = decoded.lastIndexOf(`
|
|
32955
|
+
`);
|
|
32956
|
+
return { text: lastLine === -1 ? decoded : decoded.slice(0, lastLine + 1), of: whole };
|
|
32957
|
+
} finally {
|
|
32958
|
+
closeSync(handle);
|
|
32959
|
+
}
|
|
32960
|
+
}
|
|
32961
|
+
function shapeOf(text) {
|
|
32962
|
+
const values = [];
|
|
32963
|
+
let shape = "";
|
|
32964
|
+
let at = 0;
|
|
32965
|
+
while (at < text.length) {
|
|
32966
|
+
let earliest = null;
|
|
32967
|
+
for (const pattern of SLOTS) {
|
|
32968
|
+
pattern.lastIndex = at;
|
|
32969
|
+
const found = pattern.exec(text);
|
|
32970
|
+
if (found !== null && (earliest === null || found.index < earliest.index || found.index === earliest.index && found[0].length > earliest.value.length)) {
|
|
32971
|
+
earliest = { index: found.index, value: found[0] };
|
|
32972
|
+
}
|
|
32973
|
+
}
|
|
32974
|
+
if (earliest === null) {
|
|
32975
|
+
shape += text.slice(at);
|
|
32976
|
+
break;
|
|
32977
|
+
}
|
|
32978
|
+
if (earliest.value.length === 0) {
|
|
32979
|
+
shape += text.slice(at, at + 1);
|
|
32980
|
+
at += 1;
|
|
32981
|
+
continue;
|
|
32982
|
+
}
|
|
32983
|
+
shape += `${text.slice(at, earliest.index)}…`;
|
|
32984
|
+
values.push(earliest.value);
|
|
32985
|
+
at = earliest.index + earliest.value.length;
|
|
32986
|
+
}
|
|
32987
|
+
return { shape, values };
|
|
32988
|
+
}
|
|
32989
|
+
function bursts(entries, before) {
|
|
32990
|
+
const shapes = new Map;
|
|
32991
|
+
const shaped = entries.map((entry) => shapeOf(entry.text));
|
|
32992
|
+
for (const [at, entry] of entries.entries()) {
|
|
32993
|
+
const key = `${entry.severity}\x00${shaped[at]?.shape ?? entry.text}`;
|
|
32994
|
+
const group = shapes.get(key) ?? { indices: [], values: [] };
|
|
32995
|
+
group.indices.push(at);
|
|
32996
|
+
group.values.push(shaped[at]?.values ?? []);
|
|
32997
|
+
shapes.set(key, group);
|
|
32998
|
+
}
|
|
32999
|
+
const collapsed = new Set;
|
|
33000
|
+
for (const group of shapes.values()) {
|
|
33001
|
+
if (group.indices.length > 1) {
|
|
33002
|
+
for (const at of group.indices) {
|
|
33003
|
+
collapsed.add(at);
|
|
33004
|
+
}
|
|
33005
|
+
}
|
|
33006
|
+
}
|
|
33007
|
+
const repeated = [];
|
|
33008
|
+
for (const [key, group] of shapes) {
|
|
33009
|
+
const first = group.indices[0];
|
|
33010
|
+
if (group.indices.length < 2 || first === undefined) {
|
|
33011
|
+
continue;
|
|
33012
|
+
}
|
|
33013
|
+
const entry = entries[first];
|
|
33014
|
+
if (entry === undefined) {
|
|
33015
|
+
continue;
|
|
33016
|
+
}
|
|
33017
|
+
repeated.push({
|
|
33018
|
+
shape: key.slice(key.indexOf("\x00") + 1),
|
|
33019
|
+
count: group.indices.length,
|
|
33020
|
+
severity: entry.severity,
|
|
33021
|
+
first: { index: entry.index, text: entry.text },
|
|
33022
|
+
slots: slotsOf(group.values, group.indices.length),
|
|
33023
|
+
before: linesBefore(entries, first, collapsed, before)
|
|
33024
|
+
});
|
|
33025
|
+
}
|
|
33026
|
+
return repeated.sort((one, other) => other.count - one.count);
|
|
33027
|
+
}
|
|
33028
|
+
function slotsOf(values, count) {
|
|
33029
|
+
const width = Math.max(0, ...values.map((row) => row.length));
|
|
33030
|
+
const distinct = [];
|
|
33031
|
+
for (let at = 0;at < width; at += 1) {
|
|
33032
|
+
distinct.push([...new Set(values.map((row) => row[at]).filter((one) => one !== undefined))]);
|
|
33033
|
+
}
|
|
33034
|
+
const slots = [];
|
|
33035
|
+
for (let at = 0;at < width; at += 1) {
|
|
33036
|
+
const seen = distinct[at] ?? [];
|
|
33037
|
+
const partner = theSlotItMovesWith(values, distinct, at);
|
|
33038
|
+
const paired = partner === undefined ? {} : { movesWith: partner };
|
|
33039
|
+
if (seen.length === count && count > 1) {
|
|
33040
|
+
slots.push({ distinct: seen.length, allDifferent: true, ...paired });
|
|
33041
|
+
continue;
|
|
33042
|
+
}
|
|
33043
|
+
const some = seen.slice(0, NAMED_VALUES);
|
|
33044
|
+
slots.push({
|
|
33045
|
+
distinct: seen.length,
|
|
33046
|
+
some,
|
|
33047
|
+
...seen.length > some.length ? { more: seen.length - some.length } : {},
|
|
33048
|
+
...paired
|
|
33049
|
+
});
|
|
33050
|
+
}
|
|
33051
|
+
return slots;
|
|
33052
|
+
}
|
|
33053
|
+
function theSlotItMovesWith(values, distinct, at) {
|
|
33054
|
+
const mine = distinct[at] ?? [];
|
|
33055
|
+
if (mine.length < 2) {
|
|
33056
|
+
return;
|
|
33057
|
+
}
|
|
33058
|
+
for (let other = 0;other < at; other += 1) {
|
|
33059
|
+
if ((distinct[other] ?? []).length !== mine.length) {
|
|
33060
|
+
continue;
|
|
33061
|
+
}
|
|
33062
|
+
const goesWith = new Map;
|
|
33063
|
+
let oneForOne = true;
|
|
33064
|
+
for (const row of values) {
|
|
33065
|
+
const theirs = row[other];
|
|
33066
|
+
const ours = row[at];
|
|
33067
|
+
if (theirs === undefined || ours === undefined) {
|
|
33068
|
+
oneForOne = false;
|
|
33069
|
+
break;
|
|
33070
|
+
}
|
|
33071
|
+
const already = goesWith.get(theirs);
|
|
33072
|
+
if (already === undefined) {
|
|
33073
|
+
goesWith.set(theirs, ours);
|
|
33074
|
+
} else if (already !== ours) {
|
|
33075
|
+
oneForOne = false;
|
|
33076
|
+
break;
|
|
33077
|
+
}
|
|
33078
|
+
}
|
|
33079
|
+
if (oneForOne) {
|
|
33080
|
+
return other;
|
|
33081
|
+
}
|
|
33082
|
+
}
|
|
33083
|
+
return;
|
|
33084
|
+
}
|
|
33085
|
+
function linesBefore(entries, at, collapsed, wanted) {
|
|
33086
|
+
const found = [];
|
|
33087
|
+
for (let back = at - 1;back >= 0 && at - back <= WALK_BACK_LIMIT && found.length < wanted; back -= 1) {
|
|
33088
|
+
const entry = entries[back];
|
|
33089
|
+
if (entry !== undefined && !collapsed.has(back)) {
|
|
33090
|
+
found.push(entry.text);
|
|
33091
|
+
}
|
|
33092
|
+
}
|
|
33093
|
+
return found.reverse();
|
|
33094
|
+
}
|
|
33095
|
+
var MOST_CONSOLE_BYTES, NAMED_VALUES = 6, WALK_BACK_LIMIT = 500, SLOTS;
|
|
33096
|
+
var init_editor_log = __esm(() => {
|
|
33097
|
+
init_game_log();
|
|
33098
|
+
MOST_CONSOLE_BYTES = 8 * 1024 * 1024;
|
|
33099
|
+
SLOTS = [/"[^"]*"/g, /'[^']*'/g, /\b(?:res|user):\/\/\S+/g, /(?<=\.gd|\.tscn|\.cs):\d+(?::\d+)?/g];
|
|
33100
|
+
});
|
|
33101
|
+
|
|
32805
33102
|
// node_modules/ws/lib/constants.js
|
|
32806
33103
|
var require_constants = __commonJS(function(exports, module) {
|
|
32807
33104
|
var BINARY_TYPES = ["nodebuffer", "arraybuffer", "fragments"];
|
|
@@ -36182,7 +36479,7 @@ var init_godot_bridge = __esm(() => {
|
|
|
36182
36479
|
|
|
36183
36480
|
// src/godot-path.ts
|
|
36184
36481
|
import { execFile as execFile5 } from "node:child_process";
|
|
36185
|
-
import { existsSync as
|
|
36482
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
36186
36483
|
import { normalize as normalize2 } from "node:path";
|
|
36187
36484
|
import { promisify as promisify5 } from "node:util";
|
|
36188
36485
|
|
|
@@ -36237,7 +36534,7 @@ class GodotLocator2 {
|
|
|
36237
36534
|
return known;
|
|
36238
36535
|
}
|
|
36239
36536
|
let reason;
|
|
36240
|
-
if (path !== "godot" && !
|
|
36537
|
+
if (path !== "godot" && !existsSync13(path)) {
|
|
36241
36538
|
reason = "does not exist";
|
|
36242
36539
|
} else {
|
|
36243
36540
|
try {
|
|
@@ -36613,7 +36910,7 @@ var init_junit = __esm(() => {
|
|
|
36613
36910
|
import { realpathSync as realpathSync2 } from "node:fs";
|
|
36614
36911
|
import { readFile, realpath } from "node:fs/promises";
|
|
36615
36912
|
import { createConnection as createConnection3 } from "node:net";
|
|
36616
|
-
import { dirname as
|
|
36913
|
+
import { dirname as dirname13, resolve as resolve4 } from "node:path";
|
|
36617
36914
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
36618
36915
|
function diagnosticsKey(uri) {
|
|
36619
36916
|
let decoded;
|
|
@@ -36741,7 +37038,7 @@ class GodotLSPClient {
|
|
|
36741
37038
|
if (this.initialized) {
|
|
36742
37039
|
return;
|
|
36743
37040
|
}
|
|
36744
|
-
const rootPath = this.rootPath ??
|
|
37041
|
+
const rootPath = this.rootPath ?? dirname13(resolve4(filePath));
|
|
36745
37042
|
await this.initialize(rootPath);
|
|
36746
37043
|
}
|
|
36747
37044
|
async sendRequest(method, params) {
|
|
@@ -37186,13 +37483,13 @@ var init_process_time = __esm(() => {
|
|
|
37186
37483
|
});
|
|
37187
37484
|
|
|
37188
37485
|
// src/project-scan.ts
|
|
37189
|
-
import { existsSync as
|
|
37190
|
-
import { join as
|
|
37486
|
+
import { existsSync as existsSync14, readdirSync as readdirSync5, readFileSync as readFileSync17 } from "node:fs";
|
|
37487
|
+
import { join as join19 } from "node:path";
|
|
37191
37488
|
function skipped(name) {
|
|
37192
37489
|
return name.startsWith(".") || SKIPPED.has(name);
|
|
37193
37490
|
}
|
|
37194
37491
|
function steppedOver(directory) {
|
|
37195
|
-
return
|
|
37492
|
+
return existsSync14(join19(directory, ".gdignore"));
|
|
37196
37493
|
}
|
|
37197
37494
|
function projectStructure(projectPath) {
|
|
37198
37495
|
const structure = { scenes: 0, scripts: 0, assets: 0, other: 0 };
|
|
@@ -37205,7 +37502,7 @@ function projectStructure(projectPath) {
|
|
|
37205
37502
|
continue;
|
|
37206
37503
|
}
|
|
37207
37504
|
if (entry.isDirectory()) {
|
|
37208
|
-
visit(
|
|
37505
|
+
visit(join19(directory, entry.name));
|
|
37209
37506
|
} else if (entry.isFile()) {
|
|
37210
37507
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
37211
37508
|
if (extension === "tscn") {
|
|
@@ -37235,14 +37532,14 @@ function scriptsWithoutUid(projectPath) {
|
|
|
37235
37532
|
}
|
|
37236
37533
|
const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
37237
37534
|
if (entry.isDirectory()) {
|
|
37238
|
-
visit(
|
|
37535
|
+
visit(join19(directory, entry.name), spelled);
|
|
37239
37536
|
continue;
|
|
37240
37537
|
}
|
|
37241
37538
|
if (!entry.isFile()) {
|
|
37242
37539
|
continue;
|
|
37243
37540
|
}
|
|
37244
37541
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
37245
|
-
if (CARRIES_A_UID.has(extension) && !
|
|
37542
|
+
if (CARRIES_A_UID.has(extension) && !existsSync14(join19(directory, `${entry.name}.uid`))) {
|
|
37246
37543
|
found.push(spelled);
|
|
37247
37544
|
}
|
|
37248
37545
|
}
|
|
@@ -37277,7 +37574,7 @@ function searchProject(projectPath, options) {
|
|
|
37277
37574
|
if (skipped(entry.name)) {
|
|
37278
37575
|
continue;
|
|
37279
37576
|
}
|
|
37280
|
-
const entryPath =
|
|
37577
|
+
const entryPath = join19(directory, entry.name);
|
|
37281
37578
|
if (entry.isDirectory()) {
|
|
37282
37579
|
visit(entryPath);
|
|
37283
37580
|
continue;
|
|
@@ -37288,7 +37585,7 @@ function searchProject(projectPath, options) {
|
|
|
37288
37585
|
}
|
|
37289
37586
|
result.summary.files_searched += 1;
|
|
37290
37587
|
const matches = [];
|
|
37291
|
-
for (const [index, line] of
|
|
37588
|
+
for (const [index, line] of readFileSync17(entryPath, "utf8").split(`
|
|
37292
37589
|
`).entries()) {
|
|
37293
37590
|
if (full()) {
|
|
37294
37591
|
break;
|
|
@@ -37317,16 +37614,16 @@ var init_project_scan = __esm(() => {
|
|
|
37317
37614
|
});
|
|
37318
37615
|
|
|
37319
37616
|
// src/restart-note.ts
|
|
37320
|
-
import { mkdirSync as
|
|
37321
|
-
import { dirname as
|
|
37617
|
+
import { mkdirSync as mkdirSync11, readFileSync as readFileSync18, rmSync as rmSync9, writeFileSync as writeFileSync13 } from "node:fs";
|
|
37618
|
+
import { dirname as dirname14, join as join20 } from "node:path";
|
|
37322
37619
|
function restartNotePath(projectPath) {
|
|
37323
|
-
return
|
|
37620
|
+
return join20(projectPath, ".godot", "gdharness-restart.json");
|
|
37324
37621
|
}
|
|
37325
37622
|
function noteRestartBegun(note) {
|
|
37326
37623
|
const path = restartNotePath(note.projectPath);
|
|
37327
37624
|
try {
|
|
37328
|
-
|
|
37329
|
-
|
|
37625
|
+
mkdirSync11(dirname14(path), { recursive: true });
|
|
37626
|
+
writeFileSync13(path, `${JSON.stringify(note, null, 2)}
|
|
37330
37627
|
`, "utf8");
|
|
37331
37628
|
} catch (error) {
|
|
37332
37629
|
console.error(`[SERVER] Could not record the restart at ${path}: ${errorMessage(error)}`);
|
|
@@ -37334,7 +37631,7 @@ function noteRestartBegun(note) {
|
|
|
37334
37631
|
}
|
|
37335
37632
|
function restartOwed(projectPath) {
|
|
37336
37633
|
try {
|
|
37337
|
-
const parsed = JSON.parse(
|
|
37634
|
+
const parsed = JSON.parse(readFileSync18(restartNotePath(projectPath), "utf8"));
|
|
37338
37635
|
if (typeof parsed !== "object" || parsed === null) {
|
|
37339
37636
|
return null;
|
|
37340
37637
|
}
|
|
@@ -37349,7 +37646,7 @@ function restartOwed(projectPath) {
|
|
|
37349
37646
|
}
|
|
37350
37647
|
function restartSettled(projectPath) {
|
|
37351
37648
|
try {
|
|
37352
|
-
|
|
37649
|
+
rmSync9(restartNotePath(projectPath), { force: true });
|
|
37353
37650
|
} catch (error) {
|
|
37354
37651
|
console.error(`[SERVER] Could not clear the restart note: ${errorMessage(error)}`);
|
|
37355
37652
|
}
|
|
@@ -37360,36 +37657,36 @@ var init_restart_note = __esm(() => {
|
|
|
37360
37657
|
|
|
37361
37658
|
// src/run-record.ts
|
|
37362
37659
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
37363
|
-
import { mkdirSync as
|
|
37364
|
-
import { basename as basename3, join as
|
|
37660
|
+
import { mkdirSync as mkdirSync12, openSync as openSync2, readdirSync as readdirSync6, readFileSync as readFileSync19, rmSync as rmSync10, statSync as statSync5, writeFileSync as writeFileSync14 } from "node:fs";
|
|
37661
|
+
import { basename as basename3, join as join21 } from "node:path";
|
|
37365
37662
|
function runsDirectory() {
|
|
37366
|
-
return
|
|
37663
|
+
return join21(runtimeDirectory(), "runs");
|
|
37367
37664
|
}
|
|
37368
37665
|
function recordPath() {
|
|
37369
|
-
return
|
|
37666
|
+
return join21(runsDirectory(), "run.json");
|
|
37370
37667
|
}
|
|
37371
37668
|
function recordPaths() {
|
|
37372
|
-
return runtimeDirectories().map((directory) =>
|
|
37669
|
+
return runtimeDirectories().map((directory) => join21(directory, "runs", "run.json"));
|
|
37373
37670
|
}
|
|
37374
37671
|
function openTranscript(startedAt) {
|
|
37375
|
-
|
|
37376
|
-
const path =
|
|
37377
|
-
return { path, fd:
|
|
37672
|
+
mkdirSync12(runsDirectory(), { recursive: true });
|
|
37673
|
+
const path = join21(runsDirectory(), `run-${startedAt}.log`);
|
|
37674
|
+
return { path, fd: openSync2(path, "a") };
|
|
37378
37675
|
}
|
|
37379
37676
|
function writeRunRecord(record) {
|
|
37380
|
-
|
|
37381
|
-
|
|
37677
|
+
mkdirSync12(runsDirectory(), { recursive: true });
|
|
37678
|
+
writeFileSync14(recordPath(), JSON.stringify(record, null, 2), "utf8");
|
|
37382
37679
|
}
|
|
37383
37680
|
function editorNotePath() {
|
|
37384
|
-
return
|
|
37681
|
+
return join21(runsDirectory(), "editor-run.json");
|
|
37385
37682
|
}
|
|
37386
37683
|
function writeEditorRunNote(note) {
|
|
37387
|
-
|
|
37388
|
-
|
|
37684
|
+
mkdirSync12(runsDirectory(), { recursive: true });
|
|
37685
|
+
writeFileSync14(editorNotePath(), JSON.stringify(note, null, 2), "utf8");
|
|
37389
37686
|
}
|
|
37390
37687
|
function readEditorRunNote() {
|
|
37391
37688
|
for (const directory of runtimeDirectories()) {
|
|
37392
|
-
const note = editorNoteAt(
|
|
37689
|
+
const note = editorNoteAt(join21(directory, "runs", "editor-run.json"));
|
|
37393
37690
|
if (note !== null) {
|
|
37394
37691
|
return note;
|
|
37395
37692
|
}
|
|
@@ -37399,7 +37696,7 @@ function readEditorRunNote() {
|
|
|
37399
37696
|
function editorNoteAt(path) {
|
|
37400
37697
|
let parsed;
|
|
37401
37698
|
try {
|
|
37402
|
-
parsed = JSON.parse(
|
|
37699
|
+
parsed = JSON.parse(readFileSync19(path, "utf8"));
|
|
37403
37700
|
} catch {
|
|
37404
37701
|
return null;
|
|
37405
37702
|
}
|
|
@@ -37422,7 +37719,7 @@ function recordRunEnded(pid, exitCode) {
|
|
|
37422
37719
|
return;
|
|
37423
37720
|
}
|
|
37424
37721
|
try {
|
|
37425
|
-
|
|
37722
|
+
writeFileSync14(path, JSON.stringify({ ...record, exitCode }, null, 2), "utf8");
|
|
37426
37723
|
} catch {}
|
|
37427
37724
|
}
|
|
37428
37725
|
function readRunRecord() {
|
|
@@ -37437,7 +37734,7 @@ function readRunRecord() {
|
|
|
37437
37734
|
function recordAt(path) {
|
|
37438
37735
|
let parsed;
|
|
37439
37736
|
try {
|
|
37440
|
-
parsed = JSON.parse(
|
|
37737
|
+
parsed = JSON.parse(readFileSync19(path, "utf8"));
|
|
37441
37738
|
} catch {
|
|
37442
37739
|
return null;
|
|
37443
37740
|
}
|
|
@@ -37482,7 +37779,7 @@ function runningAs(pid) {
|
|
|
37482
37779
|
const began = startedAt(pid, null);
|
|
37483
37780
|
const when = began === null ? {} : { startedAt: began };
|
|
37484
37781
|
if (process.platform === "linux") {
|
|
37485
|
-
const raw =
|
|
37782
|
+
const raw = readFileSync19(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
|
|
37486
37783
|
return raw === "" ? null : { kind: "commandLine", text: raw, ...when };
|
|
37487
37784
|
}
|
|
37488
37785
|
const args = execFileSync2("ps", ["-p", String(pid), "-o", "args="], {
|
|
@@ -37609,13 +37906,13 @@ function clearRunRecord(ours = () => true) {
|
|
|
37609
37906
|
continue;
|
|
37610
37907
|
}
|
|
37611
37908
|
try {
|
|
37612
|
-
|
|
37909
|
+
rmSync10(path);
|
|
37613
37910
|
} catch {}
|
|
37614
37911
|
}
|
|
37615
37912
|
}
|
|
37616
37913
|
function ageMs(path, now) {
|
|
37617
37914
|
try {
|
|
37618
|
-
return now -
|
|
37915
|
+
return now - statSync5(path).mtimeMs;
|
|
37619
37916
|
} catch {
|
|
37620
37917
|
return 0;
|
|
37621
37918
|
}
|
|
@@ -37633,12 +37930,12 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
37633
37930
|
if (!entry.startsWith("run-") || !entry.endsWith(".log")) {
|
|
37634
37931
|
continue;
|
|
37635
37932
|
}
|
|
37636
|
-
const path =
|
|
37933
|
+
const path = join21(directory, entry);
|
|
37637
37934
|
if (path === keep || ageMs(path, now) < keepMs) {
|
|
37638
37935
|
continue;
|
|
37639
37936
|
}
|
|
37640
37937
|
try {
|
|
37641
|
-
|
|
37938
|
+
rmSync10(path);
|
|
37642
37939
|
} catch {}
|
|
37643
37940
|
}
|
|
37644
37941
|
}
|
|
@@ -37654,20 +37951,20 @@ import { execFile as execFile7, spawn } from "node:child_process";
|
|
|
37654
37951
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
37655
37952
|
import {
|
|
37656
37953
|
appendFileSync,
|
|
37657
|
-
closeSync,
|
|
37658
|
-
existsSync as
|
|
37954
|
+
closeSync as closeSync2,
|
|
37955
|
+
existsSync as existsSync15,
|
|
37659
37956
|
fstatSync,
|
|
37660
|
-
mkdirSync as
|
|
37957
|
+
mkdirSync as mkdirSync13,
|
|
37661
37958
|
mkdtempSync as mkdtempSync3,
|
|
37662
|
-
openSync as
|
|
37959
|
+
openSync as openSync3,
|
|
37663
37960
|
readdirSync as readdirSync7,
|
|
37664
|
-
readFileSync as
|
|
37665
|
-
readSync,
|
|
37961
|
+
readFileSync as readFileSync20,
|
|
37962
|
+
readSync as readSync2,
|
|
37666
37963
|
rmdirSync,
|
|
37667
|
-
statSync as
|
|
37964
|
+
statSync as statSync6
|
|
37668
37965
|
} from "node:fs";
|
|
37669
37966
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
37670
|
-
import { basename as basename4, dirname as
|
|
37967
|
+
import { basename as basename4, dirname as dirname15, isAbsolute as isAbsolute3, join as join22, normalize as normalize3, relative as relative3 } from "node:path";
|
|
37671
37968
|
import { setTimeout as delay3 } from "node:timers/promises";
|
|
37672
37969
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
37673
37970
|
import { promisify as promisify7 } from "node:util";
|
|
@@ -37909,7 +38206,7 @@ function aboveTheRunner(entry) {
|
|
|
37909
38206
|
}
|
|
37910
38207
|
function elsewhereIn(projectPath, asked) {
|
|
37911
38208
|
const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
|
|
37912
|
-
if (wanted === "" ||
|
|
38209
|
+
if (wanted === "" || existsSync15(join22(projectPath, wanted))) {
|
|
37913
38210
|
return "";
|
|
37914
38211
|
}
|
|
37915
38212
|
const root = (wanted.split("/")[0] ?? "").toLowerCase();
|
|
@@ -37923,7 +38220,7 @@ function elsewhereIn(projectPath, asked) {
|
|
|
37923
38220
|
return near.length === 0 ? "" : ` The project does have ${there}.`;
|
|
37924
38221
|
}
|
|
37925
38222
|
function hasMainScene(projectFile) {
|
|
37926
|
-
const scene = parseProjectGodot(
|
|
38223
|
+
const scene = parseProjectGodot(readFileSync20(projectFile, "utf8"))["application"]?.["run/main_scene"];
|
|
37927
38224
|
return typeof scene === "string" && scene !== "";
|
|
37928
38225
|
}
|
|
37929
38226
|
function runtimeAddonAnnouncesEditors(projectPath) {
|
|
@@ -37931,13 +38228,13 @@ function runtimeAddonAnnouncesEditors(projectPath) {
|
|
|
37931
38228
|
return false;
|
|
37932
38229
|
}
|
|
37933
38230
|
try {
|
|
37934
|
-
return
|
|
38231
|
+
return readFileSync20(join22(projectPath, RUNTIME_AUTOLOAD2.path), "utf8").includes('"editor_pid"');
|
|
37935
38232
|
} catch {
|
|
37936
38233
|
return false;
|
|
37937
38234
|
}
|
|
37938
38235
|
}
|
|
37939
38236
|
function editorPlaysHeadless(projectFile) {
|
|
37940
|
-
const runArgs = parseProjectGodot(
|
|
38237
|
+
const runArgs = parseProjectGodot(readFileSync20(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
|
|
37941
38238
|
return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
|
|
37942
38239
|
}
|
|
37943
38240
|
function camelCased(params) {
|
|
@@ -37956,7 +38253,7 @@ function needsItsOwnProcess(asked) {
|
|
|
37956
38253
|
class GodotServer {
|
|
37957
38254
|
mcp;
|
|
37958
38255
|
locator = new GodotLocator2;
|
|
37959
|
-
operationsScript =
|
|
38256
|
+
operationsScript = join22(__dirname2, "godot", "operations", "godot_operations.gd");
|
|
37960
38257
|
godotBridge;
|
|
37961
38258
|
tools = buildToolDefinitions();
|
|
37962
38259
|
activeProcess = null;
|
|
@@ -38621,8 +38918,8 @@ class GodotServer {
|
|
|
38621
38918
|
if (path === undefined) {
|
|
38622
38919
|
return { ok: false, response: this.createErrorResponse("projectPath is required.") };
|
|
38623
38920
|
}
|
|
38624
|
-
const file =
|
|
38625
|
-
if (!
|
|
38921
|
+
const file = join22(path, "project.godot");
|
|
38922
|
+
if (!existsSync15(file)) {
|
|
38626
38923
|
return {
|
|
38627
38924
|
ok: false,
|
|
38628
38925
|
response: this.createErrorResponse(`Not a Godot project: ${path}`, [
|
|
@@ -38890,7 +39187,7 @@ class GodotServer {
|
|
|
38890
39187
|
if (!engine.ok) {
|
|
38891
39188
|
return engine.response;
|
|
38892
39189
|
}
|
|
38893
|
-
const application = parseProjectGodot(
|
|
39190
|
+
const application = parseProjectGodot(readFileSync20(project.value.file, "utf8"))["application"] ?? {};
|
|
38894
39191
|
const name = application["config/name"];
|
|
38895
39192
|
const mainScene = application["run/main_scene"];
|
|
38896
39193
|
const info = {
|
|
@@ -38951,18 +39248,18 @@ class GodotServer {
|
|
|
38951
39248
|
if (!engine.ok) {
|
|
38952
39249
|
return engine.response;
|
|
38953
39250
|
}
|
|
38954
|
-
const folder =
|
|
39251
|
+
const folder = dirname15(output.absolutePath);
|
|
38955
39252
|
try {
|
|
38956
|
-
|
|
39253
|
+
mkdirSync13(folder, { recursive: true });
|
|
38957
39254
|
} catch (error) {
|
|
38958
|
-
return this.createErrorResponse(`Cannot create ${
|
|
39255
|
+
return this.createErrorResponse(`Cannot create ${dirname15(output.relativePath)} to export into: ${errorMessage(error)}`);
|
|
38959
39256
|
}
|
|
38960
39257
|
const debug = readBoolean(args, "debug") ?? false;
|
|
38961
|
-
const exportLogs = mkdtempSync3(
|
|
39258
|
+
const exportLogs = mkdtempSync3(join22(tmpdir5(), "gdharness-export-"));
|
|
38962
39259
|
const exportArgs = [
|
|
38963
39260
|
"--headless",
|
|
38964
39261
|
"--log-file",
|
|
38965
|
-
|
|
39262
|
+
join22(exportLogs, "engine.log"),
|
|
38966
39263
|
"--path",
|
|
38967
39264
|
project.value.path,
|
|
38968
39265
|
debug ? "--export-debug" : "--export-release",
|
|
@@ -38990,7 +39287,7 @@ class GodotServer {
|
|
|
38990
39287
|
log.finish();
|
|
38991
39288
|
const problems = log.select({ severity: "warning", sinceLastCall: false, limit: 200 });
|
|
38992
39289
|
const verdict = {
|
|
38993
|
-
exported: exitCode === 0 && log.count("error") === 0 &&
|
|
39290
|
+
exported: exitCode === 0 && log.count("error") === 0 && existsSync15(output.absolutePath),
|
|
38994
39291
|
preset,
|
|
38995
39292
|
outputPath: output.relativePath,
|
|
38996
39293
|
debug,
|
|
@@ -39020,7 +39317,7 @@ class GodotServer {
|
|
|
39020
39317
|
return contained.response;
|
|
39021
39318
|
}
|
|
39022
39319
|
const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
|
|
39023
|
-
if (!
|
|
39320
|
+
if (!existsSync15(join22(project.value.path, runner))) {
|
|
39024
39321
|
return this.createErrorResponse(`gdUnit4 is not installed in this project: no ${runner}.`, [
|
|
39025
39322
|
"Install gdUnit4 under addons/gdUnit4, from https://github.com/godot-gdunit-labs/gdUnit4"
|
|
39026
39323
|
]);
|
|
@@ -39055,7 +39352,7 @@ class GodotServer {
|
|
|
39055
39352
|
];
|
|
39056
39353
|
const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 600000;
|
|
39057
39354
|
this.logDebug(`Running tests: ${engine.value} ${cmdArgs.join(" ")}`);
|
|
39058
|
-
const userData = mkdtempSync3(
|
|
39355
|
+
const userData = mkdtempSync3(join22(tmpdir5(), "gdharness-tests-"));
|
|
39059
39356
|
const run = this.spawnGame(engine.value, cmdArgs, userDataIn(userData));
|
|
39060
39357
|
const hung = await new Promise((resolve) => {
|
|
39061
39358
|
const timer = setTimeout(() => {
|
|
@@ -39071,21 +39368,21 @@ class GodotServer {
|
|
|
39071
39368
|
resolve(false);
|
|
39072
39369
|
});
|
|
39073
39370
|
});
|
|
39074
|
-
const reportsDir =
|
|
39371
|
+
const reportsDir = join22(project.value.path, ".godot", ...ours.split("/"));
|
|
39075
39372
|
let report = null;
|
|
39076
39373
|
let reportProblem = null;
|
|
39077
39374
|
try {
|
|
39078
|
-
const written =
|
|
39375
|
+
const written = existsSync15(reportsDir) ? readdirSync7(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
|
|
39079
39376
|
const newest = written.at(-1);
|
|
39080
39377
|
if (newest !== undefined) {
|
|
39081
|
-
report = parseJUnit(
|
|
39378
|
+
report = parseJUnit(readFileSync20(join22(reportsDir, newest, "results.xml"), "utf8"));
|
|
39082
39379
|
}
|
|
39083
39380
|
} catch (error) {
|
|
39084
39381
|
reportProblem = errorMessage(error);
|
|
39085
39382
|
} finally {
|
|
39086
39383
|
discard(reportsDir, userData);
|
|
39087
39384
|
try {
|
|
39088
|
-
rmdirSync(
|
|
39385
|
+
rmdirSync(dirname15(reportsDir));
|
|
39089
39386
|
} catch {}
|
|
39090
39387
|
}
|
|
39091
39388
|
const engineEntries = run.log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries.map(aboveTheRunner);
|
|
@@ -39217,11 +39514,11 @@ class GodotServer {
|
|
|
39217
39514
|
const cached = cachedClasses(projectPath);
|
|
39218
39515
|
const contradicted = cached === null ? [] : contradictedDiagnostics(messages, cached, (resourcePath) => {
|
|
39219
39516
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
39220
|
-
if (!contained.ok || !
|
|
39517
|
+
if (!contained.ok || !existsSync15(contained.absolutePath)) {
|
|
39221
39518
|
return null;
|
|
39222
39519
|
}
|
|
39223
39520
|
try {
|
|
39224
|
-
return
|
|
39521
|
+
return readFileSync20(contained.absolutePath, "utf8");
|
|
39225
39522
|
} catch {
|
|
39226
39523
|
return null;
|
|
39227
39524
|
}
|
|
@@ -39271,11 +39568,11 @@ class GodotServer {
|
|
|
39271
39568
|
}
|
|
39272
39569
|
sourceOfClass(projectPath, resourcePath) {
|
|
39273
39570
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
39274
|
-
if (!contained.ok || !
|
|
39571
|
+
if (!contained.ok || !existsSync15(contained.absolutePath)) {
|
|
39275
39572
|
return null;
|
|
39276
39573
|
}
|
|
39277
39574
|
try {
|
|
39278
|
-
return
|
|
39575
|
+
return readFileSync20(contained.absolutePath, "utf8");
|
|
39279
39576
|
} catch {
|
|
39280
39577
|
return null;
|
|
39281
39578
|
}
|
|
@@ -39504,7 +39801,7 @@ class GodotServer {
|
|
|
39504
39801
|
return null;
|
|
39505
39802
|
}
|
|
39506
39803
|
try {
|
|
39507
|
-
return settingKeys(
|
|
39804
|
+
return settingKeys(readFileSync20(join22(projectPath, "project.godot"), "utf8"));
|
|
39508
39805
|
} catch {
|
|
39509
39806
|
return null;
|
|
39510
39807
|
}
|
|
@@ -39634,7 +39931,8 @@ class GodotServer {
|
|
|
39634
39931
|
});
|
|
39635
39932
|
}
|
|
39636
39933
|
async openAnEditor(engine, projectPath, ports) {
|
|
39637
|
-
|
|
39934
|
+
clearEditorLog(projectPath);
|
|
39935
|
+
const editor = spawn(engine, editorArguments(projectPath, ports, editorLogPath(projectPath)), {
|
|
39638
39936
|
stdio: "ignore",
|
|
39639
39937
|
detached: true,
|
|
39640
39938
|
env: {
|
|
@@ -39645,6 +39943,9 @@ class GodotServer {
|
|
|
39645
39943
|
[OPENED_BY_A_SERVER]: "1"
|
|
39646
39944
|
}
|
|
39647
39945
|
});
|
|
39946
|
+
if (editor.pid !== undefined) {
|
|
39947
|
+
writeEditorLogNote(projectPath, editor.pid);
|
|
39948
|
+
}
|
|
39648
39949
|
const started = await new Promise((resolve) => {
|
|
39649
39950
|
editor.once("spawn", () => {
|
|
39650
39951
|
resolve(null);
|
|
@@ -39751,7 +40052,7 @@ class GodotServer {
|
|
|
39751
40052
|
}
|
|
39752
40053
|
async runtimeUp(projectPath, before, budget, withArgs) {
|
|
39753
40054
|
const budgetMs = budget.ms;
|
|
39754
|
-
if (!
|
|
40055
|
+
if (!existsSync15(join22(projectPath, RUNTIME_AUTOLOAD2.path))) {
|
|
39755
40056
|
return runtimeVerdict(null, { addon: false, budgetMs, heldAt: null, running: false, withArgs });
|
|
39756
40057
|
}
|
|
39757
40058
|
let asking = null;
|
|
@@ -39814,7 +40115,7 @@ class GodotServer {
|
|
|
39814
40115
|
}
|
|
39815
40116
|
let announcedAt;
|
|
39816
40117
|
try {
|
|
39817
|
-
announcedAt =
|
|
40118
|
+
announcedAt = statSync6(endpoint.file).mtimeMs;
|
|
39818
40119
|
} catch {
|
|
39819
40120
|
return;
|
|
39820
40121
|
}
|
|
@@ -39850,7 +40151,7 @@ class GodotServer {
|
|
|
39850
40151
|
}
|
|
39851
40152
|
const startedAt = Date.now();
|
|
39852
40153
|
const transcript = openTranscript(startedAt);
|
|
39853
|
-
|
|
40154
|
+
closeSync2(transcript.fd);
|
|
39854
40155
|
const playAnswer = asParams(JSON.parse(answer.content[0]?.text ?? "{}"));
|
|
39855
40156
|
const played = {
|
|
39856
40157
|
process: null,
|
|
@@ -39917,7 +40218,7 @@ class GodotServer {
|
|
|
39917
40218
|
}
|
|
39918
40219
|
let handle;
|
|
39919
40220
|
try {
|
|
39920
|
-
handle =
|
|
40221
|
+
handle = openSync3(game.errorReport, "r");
|
|
39921
40222
|
} catch {
|
|
39922
40223
|
return;
|
|
39923
40224
|
}
|
|
@@ -39928,7 +40229,7 @@ class GodotServer {
|
|
|
39928
40229
|
return;
|
|
39929
40230
|
}
|
|
39930
40231
|
const buffer = Buffer.alloc(size - from);
|
|
39931
|
-
const read =
|
|
40232
|
+
const read = readSync2(handle, buffer, 0, buffer.length, from);
|
|
39932
40233
|
game.errorReportOffset = from + read;
|
|
39933
40234
|
const reported = buffer.subarray(0, read);
|
|
39934
40235
|
if (game.transcript !== null) {
|
|
@@ -39937,7 +40238,7 @@ class GodotServer {
|
|
|
39937
40238
|
game.log.append("stderr", reported);
|
|
39938
40239
|
}
|
|
39939
40240
|
} finally {
|
|
39940
|
-
|
|
40241
|
+
closeSync2(handle);
|
|
39941
40242
|
}
|
|
39942
40243
|
}
|
|
39943
40244
|
recordWhatItBrokeOn(game) {
|
|
@@ -40031,7 +40332,7 @@ class GodotServer {
|
|
|
40031
40332
|
...env ? { env } : {}
|
|
40032
40333
|
});
|
|
40033
40334
|
} finally {
|
|
40034
|
-
|
|
40335
|
+
closeSync2(transcript.fd);
|
|
40035
40336
|
}
|
|
40036
40337
|
child.unref();
|
|
40037
40338
|
const log = new GameLog;
|
|
@@ -40078,7 +40379,7 @@ class GodotServer {
|
|
|
40078
40379
|
}
|
|
40079
40380
|
let handle;
|
|
40080
40381
|
try {
|
|
40081
|
-
handle =
|
|
40382
|
+
handle = openSync3(game.transcript, "r");
|
|
40082
40383
|
} catch {
|
|
40083
40384
|
return;
|
|
40084
40385
|
}
|
|
@@ -40088,11 +40389,11 @@ class GodotServer {
|
|
|
40088
40389
|
return;
|
|
40089
40390
|
}
|
|
40090
40391
|
const buffer = Buffer.alloc(size - game.readOffset);
|
|
40091
|
-
const read =
|
|
40392
|
+
const read = readSync2(handle, buffer, 0, buffer.length, game.readOffset);
|
|
40092
40393
|
game.readOffset += read;
|
|
40093
40394
|
game.log.append("transcript", buffer.subarray(0, read));
|
|
40094
40395
|
} finally {
|
|
40095
|
-
|
|
40396
|
+
closeSync2(handle);
|
|
40096
40397
|
}
|
|
40097
40398
|
}
|
|
40098
40399
|
async pickUpWhatTheEditorIsPlaying() {
|
|
@@ -40290,7 +40591,7 @@ class GodotServer {
|
|
|
40290
40591
|
}
|
|
40291
40592
|
const unreadable = this.aRuntimeOfOursIsTooFarOff();
|
|
40292
40593
|
if (unreadable !== null) {
|
|
40293
|
-
const behind = unreadable
|
|
40594
|
+
const behind = whichHalfIsBehind([unreadable]);
|
|
40294
40595
|
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.";
|
|
40295
40596
|
}
|
|
40296
40597
|
const record = readRunRecord();
|
|
@@ -40415,7 +40716,57 @@ class GodotServer {
|
|
|
40415
40716
|
}
|
|
40416
40717
|
return runIsUp(run, said);
|
|
40417
40718
|
}
|
|
40719
|
+
handleEditorConsole(args) {
|
|
40720
|
+
const status = this.godotBridge.getStatus();
|
|
40721
|
+
const projectPath = status.projectPath ?? this.ownProject ?? undefined;
|
|
40722
|
+
if (projectPath === undefined) {
|
|
40723
|
+
return this.createErrorResponse("No editor is connected, so there is no console to read.", [
|
|
40724
|
+
"editor_status says whether one is connected and whether it may yet be",
|
|
40725
|
+
"editor_launch opens one, and an editor opened here has its console captured"
|
|
40726
|
+
]);
|
|
40727
|
+
}
|
|
40728
|
+
const console2 = editorConsole(projectPath, status.editorPid ?? null, status.openedByAServer === true);
|
|
40729
|
+
if ("kind" in console2) {
|
|
40730
|
+
return this.createErrorResponse(theConsoleWasNotCaptured(console2, projectPath), [
|
|
40731
|
+
"editor_launch restart replaces this editor with one whose console is captured",
|
|
40732
|
+
"editor_output with no op answers about the game a run is playing, which is a different log"
|
|
40733
|
+
]);
|
|
40734
|
+
}
|
|
40735
|
+
const severity = readString(args, "severity");
|
|
40736
|
+
const selected = console2.log.select({
|
|
40737
|
+
severity: severity === "error" || severity === "warning" ? severity : "info",
|
|
40738
|
+
sinceLastCall: false,
|
|
40739
|
+
contains: readNonEmptyString(args, "contains"),
|
|
40740
|
+
limit: readPositiveNumber(args, "limit") ?? 200
|
|
40741
|
+
});
|
|
40742
|
+
const everything = console2.log.everything();
|
|
40743
|
+
const grouped = bursts(everything, readPositiveNumber(args, "before") ?? 3);
|
|
40744
|
+
const shown = grouped.slice(0, MOST_GROUPS);
|
|
40745
|
+
return this.jsonTextResponse({
|
|
40746
|
+
editorPid: status.editorPid,
|
|
40747
|
+
projectPath,
|
|
40748
|
+
capturedIn: console2.path,
|
|
40749
|
+
...console2.of === undefined ? {} : {
|
|
40750
|
+
readFromTheStart: console2.readBytes,
|
|
40751
|
+
consoleBytes: console2.of,
|
|
40752
|
+
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.`
|
|
40753
|
+
},
|
|
40754
|
+
lines: everything.length,
|
|
40755
|
+
counts: {
|
|
40756
|
+
error: console2.log.count("error"),
|
|
40757
|
+
warning: console2.log.count("warning"),
|
|
40758
|
+
info: console2.log.count("info")
|
|
40759
|
+
},
|
|
40760
|
+
repeated: shown,
|
|
40761
|
+
moreShapes: grouped.length > shown.length ? grouped.length - shown.length : undefined,
|
|
40762
|
+
entries: forAnswer(selected.entries),
|
|
40763
|
+
omitted: selected.omitted === 0 ? undefined : selected.omitted
|
|
40764
|
+
});
|
|
40765
|
+
}
|
|
40418
40766
|
async handleGetDebugOutput(args, waitedMs) {
|
|
40767
|
+
if (readString(args, "op") === "editor") {
|
|
40768
|
+
return this.handleEditorConsole(args);
|
|
40769
|
+
}
|
|
40419
40770
|
await this.pickUpWhatTheEditorIsPlaying();
|
|
40420
40771
|
const run = this.currentRun();
|
|
40421
40772
|
if (!run) {
|
|
@@ -40866,8 +41217,8 @@ class GodotServer {
|
|
|
40866
41217
|
]);
|
|
40867
41218
|
}
|
|
40868
41219
|
const expectsScreenshot = command === "capture_screenshot" || command === "capture_viewport";
|
|
40869
|
-
const screenshotDir = expectsScreenshot ? mkdtempSync3(
|
|
40870
|
-
const screenshotPath = screenshotDir ?
|
|
41220
|
+
const screenshotDir = expectsScreenshot ? mkdtempSync3(join22(tmpdir5(), "gdharness-runtime-screenshot-")) : null;
|
|
41221
|
+
const screenshotPath = screenshotDir ? join22(screenshotDir, "capture.png") : null;
|
|
40871
41222
|
try {
|
|
40872
41223
|
const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs);
|
|
40873
41224
|
if (!reply.ok) {
|
|
@@ -40891,7 +41242,7 @@ class GodotServer {
|
|
|
40891
41242
|
type: "text",
|
|
40892
41243
|
text: `Screenshot captured: ${dimensions}${chosenAmongSeveral ? ` from pid ${own}, the game this server holds` : ""}`
|
|
40893
41244
|
},
|
|
40894
|
-
{ type: "image", data:
|
|
41245
|
+
{ type: "image", data: readFileSync20(screenshotPath).toString("base64"), mimeType: "image/png" }
|
|
40895
41246
|
]
|
|
40896
41247
|
};
|
|
40897
41248
|
} finally {
|
|
@@ -40958,7 +41309,7 @@ async function runGodotServer() {
|
|
|
40958
41309
|
const server = new GodotServer;
|
|
40959
41310
|
await server.run();
|
|
40960
41311
|
}
|
|
40961
|
-
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/";
|
|
41312
|
+
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, MOST_GROUPS = 20, 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/";
|
|
40962
41313
|
var init_server3 = __esm(() => {
|
|
40963
41314
|
init_mcp();
|
|
40964
41315
|
init_stdio2();
|
|
@@ -40970,6 +41321,7 @@ var init_server3 = __esm(() => {
|
|
|
40970
41321
|
init_class_note();
|
|
40971
41322
|
init_config_pin();
|
|
40972
41323
|
init_dap_client();
|
|
41324
|
+
init_editor_log();
|
|
40973
41325
|
init_errors();
|
|
40974
41326
|
init_game_log();
|
|
40975
41327
|
init_godot_bridge();
|
|
@@ -40994,7 +41346,7 @@ var init_server3 = __esm(() => {
|
|
|
40994
41346
|
init_tool_definitions();
|
|
40995
41347
|
init_update_check();
|
|
40996
41348
|
run7 = promisify7(execFile7);
|
|
40997
|
-
__dirname2 =
|
|
41349
|
+
__dirname2 = dirname15(fileURLToPath4(import.meta.url));
|
|
40998
41350
|
CONSOLE_WRAPPER = /_console(\.exe)?$/i;
|
|
40999
41351
|
LONGEST_BOOT_NOTED_MS = 5 * 60000;
|
|
41000
41352
|
VALUE_NAMES = {
|
|
@@ -41034,8 +41386,8 @@ var init_server3 = __esm(() => {
|
|
|
41034
41386
|
});
|
|
41035
41387
|
|
|
41036
41388
|
// src/cli.ts
|
|
41037
|
-
import { existsSync as
|
|
41038
|
-
import { join as
|
|
41389
|
+
import { existsSync as existsSync16 } from "fs";
|
|
41390
|
+
import { join as join23, resolve as resolve5 } from "path";
|
|
41039
41391
|
|
|
41040
41392
|
// src/errors.ts
|
|
41041
41393
|
class Refusal extends Error {
|
|
@@ -42532,6 +42884,14 @@ that is running, and the project on disk. ${TOOL_SPECS.length} tools, named \`do
|
|
|
42532
42884
|
runtime addon announces nothing, so there is no process id and \`running\` is the editor's answer,
|
|
42533
42885
|
which can lag by seconds after the game has gone. \`editor_status\` names the game under
|
|
42534
42886
|
\`runtimes\`, which does not depend on the editor answering.
|
|
42887
|
+
- When the tools all say the project is fine and something is still wrong, read the editor's own
|
|
42888
|
+
console with \`editor_output op: "editor"\`. It is a different log from the one above and the only
|
|
42889
|
+
way anything here sees what the editor printed: its console reaches no plugin, so an editor
|
|
42890
|
+
gdharness opened is asked for a log file as it starts, and one somebody opened by hand is refused
|
|
42891
|
+
rather than answered empty. It carries the whole session from the editor's first line, which is
|
|
42892
|
+
where a wall of startup errors will be, with repeated messages grouped under \`repeated\` and the
|
|
42893
|
+
ordinary lines above each group beside them: a plugin announcing itself on the line before a
|
|
42894
|
+
burst of parse errors is usually the whole diagnosis.
|
|
42535
42895
|
- One server, one editor. A second project is a second harness session with its own server, and
|
|
42536
42896
|
that works: each editor is opened on its own language server and debug adapter ports, and says
|
|
42537
42897
|
where it serves. Two editors on one project is the thing to refuse.
|
|
@@ -42741,7 +43101,7 @@ class UsageError extends Refusal {
|
|
|
42741
43101
|
function projectArgument(at) {
|
|
42742
43102
|
const named = args.slice(at).find((value) => !value.startsWith("--"));
|
|
42743
43103
|
const projectPath = resolve5(named ?? ".");
|
|
42744
|
-
if (!
|
|
43104
|
+
if (!existsSync16(join23(projectPath, "project.godot"))) {
|
|
42745
43105
|
throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
|
|
42746
43106
|
}
|
|
42747
43107
|
return projectPath;
|
|
@@ -42780,7 +43140,7 @@ async function registerRuntime(godot, projectPath) {
|
|
|
42780
43140
|
return;
|
|
42781
43141
|
}
|
|
42782
43142
|
console.log(`${RUNTIME_AUTOLOAD.name} autoload left at ${named}, which this project registers itself`);
|
|
42783
|
-
if (!
|
|
43143
|
+
if (!existsSync16(join23(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
|
|
42784
43144
|
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}`);
|
|
42785
43145
|
}
|
|
42786
43146
|
}
|
|
@@ -42912,7 +43272,7 @@ async function setup() {
|
|
|
42912
43272
|
console.log("no harness configured; name one yourself, and gdharness harnesses lists them all");
|
|
42913
43273
|
}
|
|
42914
43274
|
if (!args.includes("--no-skill")) {
|
|
42915
|
-
const directories = skillDirectories(chosen.harnesses, projectPath,
|
|
43275
|
+
const directories = skillDirectories(chosen.harnesses, projectPath, existsSync16);
|
|
42916
43276
|
for (const written of writeSkill(directories, getLocalVersion())) {
|
|
42917
43277
|
console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
|
|
42918
43278
|
}
|
|
@@ -43005,7 +43365,7 @@ async function upgrade() {
|
|
|
43005
43365
|
moved.push(took.from);
|
|
43006
43366
|
}
|
|
43007
43367
|
}
|
|
43008
|
-
for (const written of writeSkill(skillDirectories(already, projectPath,
|
|
43368
|
+
for (const written of writeSkill(skillDirectories(already, projectPath, existsSync16), version)) {
|
|
43009
43369
|
console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
|
|
43010
43370
|
}
|
|
43011
43371
|
console.log(`
|