gdharness 1.0.10 → 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 +475 -112
- package/build/index.js +480 -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];
|
|
@@ -15348,6 +15361,9 @@ function projectPathSentence(without = callsWithoutProjectPath()) {
|
|
|
15348
15361
|
const named = without.map((name) => `\`${name}\``).join(", ");
|
|
15349
15362
|
return `Every call takes \`projectPath\` except ${named}, which ${without.length === 1 ? "takes" : "take"} none`;
|
|
15350
15363
|
}
|
|
15364
|
+
function theGamePicked() {
|
|
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";
|
|
15366
|
+
}
|
|
15351
15367
|
function toolSpec(name) {
|
|
15352
15368
|
return SPECS_BY_NAME[name];
|
|
15353
15369
|
}
|
|
@@ -15408,7 +15424,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
15408
15424
|
};
|
|
15409
15425
|
RUNNING_PROJECT_PATH = {
|
|
15410
15426
|
type: "string",
|
|
15411
|
-
description:
|
|
15427
|
+
description: `Which game, when more than one is running: the project directory it was started from. The pick without it, in order: ${theGamePicked().replaceAll("`", "")}. Not needed with one game, nor for a call to this server's own.`
|
|
15412
15428
|
};
|
|
15413
15429
|
RUNNING_PID = {
|
|
15414
15430
|
type: "number",
|
|
@@ -16067,7 +16083,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16067
16083
|
},
|
|
16068
16084
|
{
|
|
16069
16085
|
name: "editor_output",
|
|
16070
|
-
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.",
|
|
16071
16087
|
parameters: {
|
|
16072
16088
|
severity: {
|
|
16073
16089
|
type: "string",
|
|
@@ -16075,10 +16091,12 @@ var init_tool_definitions = __esm(() => {
|
|
|
16075
16091
|
description: "The least severe entry to include. Default info, which is everything."
|
|
16076
16092
|
},
|
|
16077
16093
|
sinceLastCall: {
|
|
16094
|
+
ops: ["run"],
|
|
16078
16095
|
type: "boolean",
|
|
16079
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."
|
|
16080
16097
|
},
|
|
16081
16098
|
cpu: {
|
|
16099
|
+
ops: ["run"],
|
|
16082
16100
|
type: "boolean",
|
|
16083
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."
|
|
16084
16102
|
},
|
|
@@ -16089,8 +16107,24 @@ var init_tool_definitions = __esm(() => {
|
|
|
16089
16107
|
limit: {
|
|
16090
16108
|
type: "number",
|
|
16091
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: []
|
|
16092
16125
|
}
|
|
16093
16126
|
},
|
|
16127
|
+
defaultOperation: "run",
|
|
16094
16128
|
requires: []
|
|
16095
16129
|
},
|
|
16096
16130
|
{
|
|
@@ -31690,10 +31724,37 @@ async function announcedSince(projectPath, before, waiting = {}) {
|
|
|
31690
31724
|
function describe2(endpoint) {
|
|
31691
31725
|
return `pid ${endpoint.pid} on ${endpoint.address}:${endpoint.port} (${endpoint.project.name || "unnamed"} at ${endpoint.project.path})`;
|
|
31692
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
|
+
}
|
|
31693
31753
|
function tooNew(unspoken) {
|
|
31694
31754
|
const named = unspoken.map((game) => `pid ${game.pid} speaks ${game.protocol} (${game.project.name || "unnamed"} at ${game.project.path})`).join("; ");
|
|
31695
|
-
const
|
|
31696
|
-
|
|
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)}.`;
|
|
31697
31758
|
}
|
|
31698
31759
|
function chooseRuntime(endpoints, projectPath, unspoken = [], pid) {
|
|
31699
31760
|
if (pid !== undefined) {
|
|
@@ -31710,8 +31771,16 @@ function chooseRuntime(endpoints, projectPath, unspoken = [], pid) {
|
|
|
31710
31771
|
if (tooNewToo !== undefined) {
|
|
31711
31772
|
return { problem: tooNew([tooNewToo]) };
|
|
31712
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
|
+
}
|
|
31713
31782
|
return {
|
|
31714
|
-
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.`
|
|
31715
31784
|
};
|
|
31716
31785
|
}
|
|
31717
31786
|
if (endpoints.length === 0) {
|
|
@@ -31743,15 +31812,17 @@ function chooseRuntime(endpoints, projectPath, unspoken = [], pid) {
|
|
|
31743
31812
|
if (matching.length === 1 && matching[0]) {
|
|
31744
31813
|
return { endpoint: matching[0] };
|
|
31745
31814
|
}
|
|
31746
|
-
return {
|
|
31747
|
-
problem: `Several games are running from ${wanted}: ${matching.map(describe2).join("; ")}. Pass pid to choose one.`
|
|
31748
|
-
};
|
|
31815
|
+
return { problem: severalFrom(wanted, matching) };
|
|
31749
31816
|
}
|
|
31750
31817
|
if (endpoints.length === 1 && endpoints[0]) {
|
|
31751
31818
|
return { endpoint: endpoints[0] };
|
|
31752
31819
|
}
|
|
31820
|
+
const shared = theOneProject(endpoints);
|
|
31821
|
+
if (shared !== undefined) {
|
|
31822
|
+
return { problem: severalFrom(shared, endpoints) };
|
|
31823
|
+
}
|
|
31753
31824
|
return {
|
|
31754
|
-
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.`
|
|
31755
31826
|
};
|
|
31756
31827
|
}
|
|
31757
31828
|
function runtimeRequest(endpoint, command, params, timeoutMs) {
|
|
@@ -32799,6 +32870,235 @@ var init_dap_client = __esm(() => {
|
|
|
32799
32870
|
init_ports();
|
|
32800
32871
|
});
|
|
32801
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
|
+
|
|
32802
33102
|
// node_modules/ws/lib/constants.js
|
|
32803
33103
|
var require_constants = __commonJS(function(exports, module) {
|
|
32804
33104
|
var BINARY_TYPES = ["nodebuffer", "arraybuffer", "fragments"];
|
|
@@ -36179,7 +36479,7 @@ var init_godot_bridge = __esm(() => {
|
|
|
36179
36479
|
|
|
36180
36480
|
// src/godot-path.ts
|
|
36181
36481
|
import { execFile as execFile5 } from "node:child_process";
|
|
36182
|
-
import { existsSync as
|
|
36482
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
36183
36483
|
import { normalize as normalize2 } from "node:path";
|
|
36184
36484
|
import { promisify as promisify5 } from "node:util";
|
|
36185
36485
|
|
|
@@ -36234,7 +36534,7 @@ class GodotLocator2 {
|
|
|
36234
36534
|
return known;
|
|
36235
36535
|
}
|
|
36236
36536
|
let reason;
|
|
36237
|
-
if (path !== "godot" && !
|
|
36537
|
+
if (path !== "godot" && !existsSync13(path)) {
|
|
36238
36538
|
reason = "does not exist";
|
|
36239
36539
|
} else {
|
|
36240
36540
|
try {
|
|
@@ -36610,7 +36910,7 @@ var init_junit = __esm(() => {
|
|
|
36610
36910
|
import { realpathSync as realpathSync2 } from "node:fs";
|
|
36611
36911
|
import { readFile, realpath } from "node:fs/promises";
|
|
36612
36912
|
import { createConnection as createConnection3 } from "node:net";
|
|
36613
|
-
import { dirname as
|
|
36913
|
+
import { dirname as dirname13, resolve as resolve4 } from "node:path";
|
|
36614
36914
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
36615
36915
|
function diagnosticsKey(uri) {
|
|
36616
36916
|
let decoded;
|
|
@@ -36738,7 +37038,7 @@ class GodotLSPClient {
|
|
|
36738
37038
|
if (this.initialized) {
|
|
36739
37039
|
return;
|
|
36740
37040
|
}
|
|
36741
|
-
const rootPath = this.rootPath ??
|
|
37041
|
+
const rootPath = this.rootPath ?? dirname13(resolve4(filePath));
|
|
36742
37042
|
await this.initialize(rootPath);
|
|
36743
37043
|
}
|
|
36744
37044
|
async sendRequest(method, params) {
|
|
@@ -37183,13 +37483,13 @@ var init_process_time = __esm(() => {
|
|
|
37183
37483
|
});
|
|
37184
37484
|
|
|
37185
37485
|
// src/project-scan.ts
|
|
37186
|
-
import { existsSync as
|
|
37187
|
-
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";
|
|
37188
37488
|
function skipped(name) {
|
|
37189
37489
|
return name.startsWith(".") || SKIPPED.has(name);
|
|
37190
37490
|
}
|
|
37191
37491
|
function steppedOver(directory) {
|
|
37192
|
-
return
|
|
37492
|
+
return existsSync14(join19(directory, ".gdignore"));
|
|
37193
37493
|
}
|
|
37194
37494
|
function projectStructure(projectPath) {
|
|
37195
37495
|
const structure = { scenes: 0, scripts: 0, assets: 0, other: 0 };
|
|
@@ -37202,7 +37502,7 @@ function projectStructure(projectPath) {
|
|
|
37202
37502
|
continue;
|
|
37203
37503
|
}
|
|
37204
37504
|
if (entry.isDirectory()) {
|
|
37205
|
-
visit(
|
|
37505
|
+
visit(join19(directory, entry.name));
|
|
37206
37506
|
} else if (entry.isFile()) {
|
|
37207
37507
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
37208
37508
|
if (extension === "tscn") {
|
|
@@ -37232,14 +37532,14 @@ function scriptsWithoutUid(projectPath) {
|
|
|
37232
37532
|
}
|
|
37233
37533
|
const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
37234
37534
|
if (entry.isDirectory()) {
|
|
37235
|
-
visit(
|
|
37535
|
+
visit(join19(directory, entry.name), spelled);
|
|
37236
37536
|
continue;
|
|
37237
37537
|
}
|
|
37238
37538
|
if (!entry.isFile()) {
|
|
37239
37539
|
continue;
|
|
37240
37540
|
}
|
|
37241
37541
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
37242
|
-
if (CARRIES_A_UID.has(extension) && !
|
|
37542
|
+
if (CARRIES_A_UID.has(extension) && !existsSync14(join19(directory, `${entry.name}.uid`))) {
|
|
37243
37543
|
found.push(spelled);
|
|
37244
37544
|
}
|
|
37245
37545
|
}
|
|
@@ -37274,7 +37574,7 @@ function searchProject(projectPath, options) {
|
|
|
37274
37574
|
if (skipped(entry.name)) {
|
|
37275
37575
|
continue;
|
|
37276
37576
|
}
|
|
37277
|
-
const entryPath =
|
|
37577
|
+
const entryPath = join19(directory, entry.name);
|
|
37278
37578
|
if (entry.isDirectory()) {
|
|
37279
37579
|
visit(entryPath);
|
|
37280
37580
|
continue;
|
|
@@ -37285,7 +37585,7 @@ function searchProject(projectPath, options) {
|
|
|
37285
37585
|
}
|
|
37286
37586
|
result.summary.files_searched += 1;
|
|
37287
37587
|
const matches = [];
|
|
37288
|
-
for (const [index, line] of
|
|
37588
|
+
for (const [index, line] of readFileSync17(entryPath, "utf8").split(`
|
|
37289
37589
|
`).entries()) {
|
|
37290
37590
|
if (full()) {
|
|
37291
37591
|
break;
|
|
@@ -37314,16 +37614,16 @@ var init_project_scan = __esm(() => {
|
|
|
37314
37614
|
});
|
|
37315
37615
|
|
|
37316
37616
|
// src/restart-note.ts
|
|
37317
|
-
import { mkdirSync as
|
|
37318
|
-
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";
|
|
37319
37619
|
function restartNotePath(projectPath) {
|
|
37320
|
-
return
|
|
37620
|
+
return join20(projectPath, ".godot", "gdharness-restart.json");
|
|
37321
37621
|
}
|
|
37322
37622
|
function noteRestartBegun(note) {
|
|
37323
37623
|
const path = restartNotePath(note.projectPath);
|
|
37324
37624
|
try {
|
|
37325
|
-
|
|
37326
|
-
|
|
37625
|
+
mkdirSync11(dirname14(path), { recursive: true });
|
|
37626
|
+
writeFileSync13(path, `${JSON.stringify(note, null, 2)}
|
|
37327
37627
|
`, "utf8");
|
|
37328
37628
|
} catch (error) {
|
|
37329
37629
|
console.error(`[SERVER] Could not record the restart at ${path}: ${errorMessage(error)}`);
|
|
@@ -37331,7 +37631,7 @@ function noteRestartBegun(note) {
|
|
|
37331
37631
|
}
|
|
37332
37632
|
function restartOwed(projectPath) {
|
|
37333
37633
|
try {
|
|
37334
|
-
const parsed = JSON.parse(
|
|
37634
|
+
const parsed = JSON.parse(readFileSync18(restartNotePath(projectPath), "utf8"));
|
|
37335
37635
|
if (typeof parsed !== "object" || parsed === null) {
|
|
37336
37636
|
return null;
|
|
37337
37637
|
}
|
|
@@ -37346,7 +37646,7 @@ function restartOwed(projectPath) {
|
|
|
37346
37646
|
}
|
|
37347
37647
|
function restartSettled(projectPath) {
|
|
37348
37648
|
try {
|
|
37349
|
-
|
|
37649
|
+
rmSync9(restartNotePath(projectPath), { force: true });
|
|
37350
37650
|
} catch (error) {
|
|
37351
37651
|
console.error(`[SERVER] Could not clear the restart note: ${errorMessage(error)}`);
|
|
37352
37652
|
}
|
|
@@ -37357,36 +37657,36 @@ var init_restart_note = __esm(() => {
|
|
|
37357
37657
|
|
|
37358
37658
|
// src/run-record.ts
|
|
37359
37659
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
37360
|
-
import { mkdirSync as
|
|
37361
|
-
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";
|
|
37362
37662
|
function runsDirectory() {
|
|
37363
|
-
return
|
|
37663
|
+
return join21(runtimeDirectory(), "runs");
|
|
37364
37664
|
}
|
|
37365
37665
|
function recordPath() {
|
|
37366
|
-
return
|
|
37666
|
+
return join21(runsDirectory(), "run.json");
|
|
37367
37667
|
}
|
|
37368
37668
|
function recordPaths() {
|
|
37369
|
-
return runtimeDirectories().map((directory) =>
|
|
37669
|
+
return runtimeDirectories().map((directory) => join21(directory, "runs", "run.json"));
|
|
37370
37670
|
}
|
|
37371
37671
|
function openTranscript(startedAt) {
|
|
37372
|
-
|
|
37373
|
-
const path =
|
|
37374
|
-
return { path, fd:
|
|
37672
|
+
mkdirSync12(runsDirectory(), { recursive: true });
|
|
37673
|
+
const path = join21(runsDirectory(), `run-${startedAt}.log`);
|
|
37674
|
+
return { path, fd: openSync2(path, "a") };
|
|
37375
37675
|
}
|
|
37376
37676
|
function writeRunRecord(record) {
|
|
37377
|
-
|
|
37378
|
-
|
|
37677
|
+
mkdirSync12(runsDirectory(), { recursive: true });
|
|
37678
|
+
writeFileSync14(recordPath(), JSON.stringify(record, null, 2), "utf8");
|
|
37379
37679
|
}
|
|
37380
37680
|
function editorNotePath() {
|
|
37381
|
-
return
|
|
37681
|
+
return join21(runsDirectory(), "editor-run.json");
|
|
37382
37682
|
}
|
|
37383
37683
|
function writeEditorRunNote(note) {
|
|
37384
|
-
|
|
37385
|
-
|
|
37684
|
+
mkdirSync12(runsDirectory(), { recursive: true });
|
|
37685
|
+
writeFileSync14(editorNotePath(), JSON.stringify(note, null, 2), "utf8");
|
|
37386
37686
|
}
|
|
37387
37687
|
function readEditorRunNote() {
|
|
37388
37688
|
for (const directory of runtimeDirectories()) {
|
|
37389
|
-
const note = editorNoteAt(
|
|
37689
|
+
const note = editorNoteAt(join21(directory, "runs", "editor-run.json"));
|
|
37390
37690
|
if (note !== null) {
|
|
37391
37691
|
return note;
|
|
37392
37692
|
}
|
|
@@ -37396,7 +37696,7 @@ function readEditorRunNote() {
|
|
|
37396
37696
|
function editorNoteAt(path) {
|
|
37397
37697
|
let parsed;
|
|
37398
37698
|
try {
|
|
37399
|
-
parsed = JSON.parse(
|
|
37699
|
+
parsed = JSON.parse(readFileSync19(path, "utf8"));
|
|
37400
37700
|
} catch {
|
|
37401
37701
|
return null;
|
|
37402
37702
|
}
|
|
@@ -37419,7 +37719,7 @@ function recordRunEnded(pid, exitCode) {
|
|
|
37419
37719
|
return;
|
|
37420
37720
|
}
|
|
37421
37721
|
try {
|
|
37422
|
-
|
|
37722
|
+
writeFileSync14(path, JSON.stringify({ ...record, exitCode }, null, 2), "utf8");
|
|
37423
37723
|
} catch {}
|
|
37424
37724
|
}
|
|
37425
37725
|
function readRunRecord() {
|
|
@@ -37434,7 +37734,7 @@ function readRunRecord() {
|
|
|
37434
37734
|
function recordAt(path) {
|
|
37435
37735
|
let parsed;
|
|
37436
37736
|
try {
|
|
37437
|
-
parsed = JSON.parse(
|
|
37737
|
+
parsed = JSON.parse(readFileSync19(path, "utf8"));
|
|
37438
37738
|
} catch {
|
|
37439
37739
|
return null;
|
|
37440
37740
|
}
|
|
@@ -37479,7 +37779,7 @@ function runningAs(pid) {
|
|
|
37479
37779
|
const began = startedAt(pid, null);
|
|
37480
37780
|
const when = began === null ? {} : { startedAt: began };
|
|
37481
37781
|
if (process.platform === "linux") {
|
|
37482
|
-
const raw =
|
|
37782
|
+
const raw = readFileSync19(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
|
|
37483
37783
|
return raw === "" ? null : { kind: "commandLine", text: raw, ...when };
|
|
37484
37784
|
}
|
|
37485
37785
|
const args = execFileSync2("ps", ["-p", String(pid), "-o", "args="], {
|
|
@@ -37606,13 +37906,13 @@ function clearRunRecord(ours = () => true) {
|
|
|
37606
37906
|
continue;
|
|
37607
37907
|
}
|
|
37608
37908
|
try {
|
|
37609
|
-
|
|
37909
|
+
rmSync10(path);
|
|
37610
37910
|
} catch {}
|
|
37611
37911
|
}
|
|
37612
37912
|
}
|
|
37613
37913
|
function ageMs(path, now) {
|
|
37614
37914
|
try {
|
|
37615
|
-
return now -
|
|
37915
|
+
return now - statSync5(path).mtimeMs;
|
|
37616
37916
|
} catch {
|
|
37617
37917
|
return 0;
|
|
37618
37918
|
}
|
|
@@ -37630,12 +37930,12 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
37630
37930
|
if (!entry.startsWith("run-") || !entry.endsWith(".log")) {
|
|
37631
37931
|
continue;
|
|
37632
37932
|
}
|
|
37633
|
-
const path =
|
|
37933
|
+
const path = join21(directory, entry);
|
|
37634
37934
|
if (path === keep || ageMs(path, now) < keepMs) {
|
|
37635
37935
|
continue;
|
|
37636
37936
|
}
|
|
37637
37937
|
try {
|
|
37638
|
-
|
|
37938
|
+
rmSync10(path);
|
|
37639
37939
|
} catch {}
|
|
37640
37940
|
}
|
|
37641
37941
|
}
|
|
@@ -37651,20 +37951,20 @@ import { execFile as execFile7, spawn } from "node:child_process";
|
|
|
37651
37951
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
37652
37952
|
import {
|
|
37653
37953
|
appendFileSync,
|
|
37654
|
-
closeSync,
|
|
37655
|
-
existsSync as
|
|
37954
|
+
closeSync as closeSync2,
|
|
37955
|
+
existsSync as existsSync15,
|
|
37656
37956
|
fstatSync,
|
|
37657
|
-
mkdirSync as
|
|
37957
|
+
mkdirSync as mkdirSync13,
|
|
37658
37958
|
mkdtempSync as mkdtempSync3,
|
|
37659
|
-
openSync as
|
|
37959
|
+
openSync as openSync3,
|
|
37660
37960
|
readdirSync as readdirSync7,
|
|
37661
|
-
readFileSync as
|
|
37662
|
-
readSync,
|
|
37961
|
+
readFileSync as readFileSync20,
|
|
37962
|
+
readSync as readSync2,
|
|
37663
37963
|
rmdirSync,
|
|
37664
|
-
statSync as
|
|
37964
|
+
statSync as statSync6
|
|
37665
37965
|
} from "node:fs";
|
|
37666
37966
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
37667
|
-
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";
|
|
37668
37968
|
import { setTimeout as delay3 } from "node:timers/promises";
|
|
37669
37969
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
37670
37970
|
import { promisify as promisify7 } from "node:util";
|
|
@@ -37906,7 +38206,7 @@ function aboveTheRunner(entry) {
|
|
|
37906
38206
|
}
|
|
37907
38207
|
function elsewhereIn(projectPath, asked) {
|
|
37908
38208
|
const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
|
|
37909
|
-
if (wanted === "" ||
|
|
38209
|
+
if (wanted === "" || existsSync15(join22(projectPath, wanted))) {
|
|
37910
38210
|
return "";
|
|
37911
38211
|
}
|
|
37912
38212
|
const root = (wanted.split("/")[0] ?? "").toLowerCase();
|
|
@@ -37920,7 +38220,7 @@ function elsewhereIn(projectPath, asked) {
|
|
|
37920
38220
|
return near.length === 0 ? "" : ` The project does have ${there}.`;
|
|
37921
38221
|
}
|
|
37922
38222
|
function hasMainScene(projectFile) {
|
|
37923
|
-
const scene = parseProjectGodot(
|
|
38223
|
+
const scene = parseProjectGodot(readFileSync20(projectFile, "utf8"))["application"]?.["run/main_scene"];
|
|
37924
38224
|
return typeof scene === "string" && scene !== "";
|
|
37925
38225
|
}
|
|
37926
38226
|
function runtimeAddonAnnouncesEditors(projectPath) {
|
|
@@ -37928,13 +38228,13 @@ function runtimeAddonAnnouncesEditors(projectPath) {
|
|
|
37928
38228
|
return false;
|
|
37929
38229
|
}
|
|
37930
38230
|
try {
|
|
37931
|
-
return
|
|
38231
|
+
return readFileSync20(join22(projectPath, RUNTIME_AUTOLOAD2.path), "utf8").includes('"editor_pid"');
|
|
37932
38232
|
} catch {
|
|
37933
38233
|
return false;
|
|
37934
38234
|
}
|
|
37935
38235
|
}
|
|
37936
38236
|
function editorPlaysHeadless(projectFile) {
|
|
37937
|
-
const runArgs = parseProjectGodot(
|
|
38237
|
+
const runArgs = parseProjectGodot(readFileSync20(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
|
|
37938
38238
|
return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
|
|
37939
38239
|
}
|
|
37940
38240
|
function camelCased(params) {
|
|
@@ -37953,7 +38253,7 @@ function needsItsOwnProcess(asked) {
|
|
|
37953
38253
|
class GodotServer {
|
|
37954
38254
|
mcp;
|
|
37955
38255
|
locator = new GodotLocator2;
|
|
37956
|
-
operationsScript =
|
|
38256
|
+
operationsScript = join22(__dirname2, "godot", "operations", "godot_operations.gd");
|
|
37957
38257
|
godotBridge;
|
|
37958
38258
|
tools = buildToolDefinitions();
|
|
37959
38259
|
activeProcess = null;
|
|
@@ -38618,8 +38918,8 @@ class GodotServer {
|
|
|
38618
38918
|
if (path === undefined) {
|
|
38619
38919
|
return { ok: false, response: this.createErrorResponse("projectPath is required.") };
|
|
38620
38920
|
}
|
|
38621
|
-
const file =
|
|
38622
|
-
if (!
|
|
38921
|
+
const file = join22(path, "project.godot");
|
|
38922
|
+
if (!existsSync15(file)) {
|
|
38623
38923
|
return {
|
|
38624
38924
|
ok: false,
|
|
38625
38925
|
response: this.createErrorResponse(`Not a Godot project: ${path}`, [
|
|
@@ -38887,7 +39187,7 @@ class GodotServer {
|
|
|
38887
39187
|
if (!engine.ok) {
|
|
38888
39188
|
return engine.response;
|
|
38889
39189
|
}
|
|
38890
|
-
const application = parseProjectGodot(
|
|
39190
|
+
const application = parseProjectGodot(readFileSync20(project.value.file, "utf8"))["application"] ?? {};
|
|
38891
39191
|
const name = application["config/name"];
|
|
38892
39192
|
const mainScene = application["run/main_scene"];
|
|
38893
39193
|
const info = {
|
|
@@ -38948,18 +39248,18 @@ class GodotServer {
|
|
|
38948
39248
|
if (!engine.ok) {
|
|
38949
39249
|
return engine.response;
|
|
38950
39250
|
}
|
|
38951
|
-
const folder =
|
|
39251
|
+
const folder = dirname15(output.absolutePath);
|
|
38952
39252
|
try {
|
|
38953
|
-
|
|
39253
|
+
mkdirSync13(folder, { recursive: true });
|
|
38954
39254
|
} catch (error) {
|
|
38955
|
-
return this.createErrorResponse(`Cannot create ${
|
|
39255
|
+
return this.createErrorResponse(`Cannot create ${dirname15(output.relativePath)} to export into: ${errorMessage(error)}`);
|
|
38956
39256
|
}
|
|
38957
39257
|
const debug = readBoolean(args, "debug") ?? false;
|
|
38958
|
-
const exportLogs = mkdtempSync3(
|
|
39258
|
+
const exportLogs = mkdtempSync3(join22(tmpdir5(), "gdharness-export-"));
|
|
38959
39259
|
const exportArgs = [
|
|
38960
39260
|
"--headless",
|
|
38961
39261
|
"--log-file",
|
|
38962
|
-
|
|
39262
|
+
join22(exportLogs, "engine.log"),
|
|
38963
39263
|
"--path",
|
|
38964
39264
|
project.value.path,
|
|
38965
39265
|
debug ? "--export-debug" : "--export-release",
|
|
@@ -38987,7 +39287,7 @@ class GodotServer {
|
|
|
38987
39287
|
log.finish();
|
|
38988
39288
|
const problems = log.select({ severity: "warning", sinceLastCall: false, limit: 200 });
|
|
38989
39289
|
const verdict = {
|
|
38990
|
-
exported: exitCode === 0 && log.count("error") === 0 &&
|
|
39290
|
+
exported: exitCode === 0 && log.count("error") === 0 && existsSync15(output.absolutePath),
|
|
38991
39291
|
preset,
|
|
38992
39292
|
outputPath: output.relativePath,
|
|
38993
39293
|
debug,
|
|
@@ -39017,7 +39317,7 @@ class GodotServer {
|
|
|
39017
39317
|
return contained.response;
|
|
39018
39318
|
}
|
|
39019
39319
|
const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
|
|
39020
|
-
if (!
|
|
39320
|
+
if (!existsSync15(join22(project.value.path, runner))) {
|
|
39021
39321
|
return this.createErrorResponse(`gdUnit4 is not installed in this project: no ${runner}.`, [
|
|
39022
39322
|
"Install gdUnit4 under addons/gdUnit4, from https://github.com/godot-gdunit-labs/gdUnit4"
|
|
39023
39323
|
]);
|
|
@@ -39052,7 +39352,7 @@ class GodotServer {
|
|
|
39052
39352
|
];
|
|
39053
39353
|
const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 600000;
|
|
39054
39354
|
this.logDebug(`Running tests: ${engine.value} ${cmdArgs.join(" ")}`);
|
|
39055
|
-
const userData = mkdtempSync3(
|
|
39355
|
+
const userData = mkdtempSync3(join22(tmpdir5(), "gdharness-tests-"));
|
|
39056
39356
|
const run = this.spawnGame(engine.value, cmdArgs, userDataIn(userData));
|
|
39057
39357
|
const hung = await new Promise((resolve) => {
|
|
39058
39358
|
const timer = setTimeout(() => {
|
|
@@ -39068,21 +39368,21 @@ class GodotServer {
|
|
|
39068
39368
|
resolve(false);
|
|
39069
39369
|
});
|
|
39070
39370
|
});
|
|
39071
|
-
const reportsDir =
|
|
39371
|
+
const reportsDir = join22(project.value.path, ".godot", ...ours.split("/"));
|
|
39072
39372
|
let report = null;
|
|
39073
39373
|
let reportProblem = null;
|
|
39074
39374
|
try {
|
|
39075
|
-
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))) : [];
|
|
39076
39376
|
const newest = written.at(-1);
|
|
39077
39377
|
if (newest !== undefined) {
|
|
39078
|
-
report = parseJUnit(
|
|
39378
|
+
report = parseJUnit(readFileSync20(join22(reportsDir, newest, "results.xml"), "utf8"));
|
|
39079
39379
|
}
|
|
39080
39380
|
} catch (error) {
|
|
39081
39381
|
reportProblem = errorMessage(error);
|
|
39082
39382
|
} finally {
|
|
39083
39383
|
discard(reportsDir, userData);
|
|
39084
39384
|
try {
|
|
39085
|
-
rmdirSync(
|
|
39385
|
+
rmdirSync(dirname15(reportsDir));
|
|
39086
39386
|
} catch {}
|
|
39087
39387
|
}
|
|
39088
39388
|
const engineEntries = run.log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries.map(aboveTheRunner);
|
|
@@ -39214,11 +39514,11 @@ class GodotServer {
|
|
|
39214
39514
|
const cached = cachedClasses(projectPath);
|
|
39215
39515
|
const contradicted = cached === null ? [] : contradictedDiagnostics(messages, cached, (resourcePath) => {
|
|
39216
39516
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
39217
|
-
if (!contained.ok || !
|
|
39517
|
+
if (!contained.ok || !existsSync15(contained.absolutePath)) {
|
|
39218
39518
|
return null;
|
|
39219
39519
|
}
|
|
39220
39520
|
try {
|
|
39221
|
-
return
|
|
39521
|
+
return readFileSync20(contained.absolutePath, "utf8");
|
|
39222
39522
|
} catch {
|
|
39223
39523
|
return null;
|
|
39224
39524
|
}
|
|
@@ -39268,11 +39568,11 @@ class GodotServer {
|
|
|
39268
39568
|
}
|
|
39269
39569
|
sourceOfClass(projectPath, resourcePath) {
|
|
39270
39570
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
39271
|
-
if (!contained.ok || !
|
|
39571
|
+
if (!contained.ok || !existsSync15(contained.absolutePath)) {
|
|
39272
39572
|
return null;
|
|
39273
39573
|
}
|
|
39274
39574
|
try {
|
|
39275
|
-
return
|
|
39575
|
+
return readFileSync20(contained.absolutePath, "utf8");
|
|
39276
39576
|
} catch {
|
|
39277
39577
|
return null;
|
|
39278
39578
|
}
|
|
@@ -39501,7 +39801,7 @@ class GodotServer {
|
|
|
39501
39801
|
return null;
|
|
39502
39802
|
}
|
|
39503
39803
|
try {
|
|
39504
|
-
return settingKeys(
|
|
39804
|
+
return settingKeys(readFileSync20(join22(projectPath, "project.godot"), "utf8"));
|
|
39505
39805
|
} catch {
|
|
39506
39806
|
return null;
|
|
39507
39807
|
}
|
|
@@ -39631,7 +39931,8 @@ class GodotServer {
|
|
|
39631
39931
|
});
|
|
39632
39932
|
}
|
|
39633
39933
|
async openAnEditor(engine, projectPath, ports) {
|
|
39634
|
-
|
|
39934
|
+
clearEditorLog(projectPath);
|
|
39935
|
+
const editor = spawn(engine, editorArguments(projectPath, ports, editorLogPath(projectPath)), {
|
|
39635
39936
|
stdio: "ignore",
|
|
39636
39937
|
detached: true,
|
|
39637
39938
|
env: {
|
|
@@ -39642,6 +39943,9 @@ class GodotServer {
|
|
|
39642
39943
|
[OPENED_BY_A_SERVER]: "1"
|
|
39643
39944
|
}
|
|
39644
39945
|
});
|
|
39946
|
+
if (editor.pid !== undefined) {
|
|
39947
|
+
writeEditorLogNote(projectPath, editor.pid);
|
|
39948
|
+
}
|
|
39645
39949
|
const started = await new Promise((resolve) => {
|
|
39646
39950
|
editor.once("spawn", () => {
|
|
39647
39951
|
resolve(null);
|
|
@@ -39748,7 +40052,7 @@ class GodotServer {
|
|
|
39748
40052
|
}
|
|
39749
40053
|
async runtimeUp(projectPath, before, budget, withArgs) {
|
|
39750
40054
|
const budgetMs = budget.ms;
|
|
39751
|
-
if (!
|
|
40055
|
+
if (!existsSync15(join22(projectPath, RUNTIME_AUTOLOAD2.path))) {
|
|
39752
40056
|
return runtimeVerdict(null, { addon: false, budgetMs, heldAt: null, running: false, withArgs });
|
|
39753
40057
|
}
|
|
39754
40058
|
let asking = null;
|
|
@@ -39811,7 +40115,7 @@ class GodotServer {
|
|
|
39811
40115
|
}
|
|
39812
40116
|
let announcedAt;
|
|
39813
40117
|
try {
|
|
39814
|
-
announcedAt =
|
|
40118
|
+
announcedAt = statSync6(endpoint.file).mtimeMs;
|
|
39815
40119
|
} catch {
|
|
39816
40120
|
return;
|
|
39817
40121
|
}
|
|
@@ -39847,7 +40151,7 @@ class GodotServer {
|
|
|
39847
40151
|
}
|
|
39848
40152
|
const startedAt = Date.now();
|
|
39849
40153
|
const transcript = openTranscript(startedAt);
|
|
39850
|
-
|
|
40154
|
+
closeSync2(transcript.fd);
|
|
39851
40155
|
const playAnswer = asParams(JSON.parse(answer.content[0]?.text ?? "{}"));
|
|
39852
40156
|
const played = {
|
|
39853
40157
|
process: null,
|
|
@@ -39914,7 +40218,7 @@ class GodotServer {
|
|
|
39914
40218
|
}
|
|
39915
40219
|
let handle;
|
|
39916
40220
|
try {
|
|
39917
|
-
handle =
|
|
40221
|
+
handle = openSync3(game.errorReport, "r");
|
|
39918
40222
|
} catch {
|
|
39919
40223
|
return;
|
|
39920
40224
|
}
|
|
@@ -39925,7 +40229,7 @@ class GodotServer {
|
|
|
39925
40229
|
return;
|
|
39926
40230
|
}
|
|
39927
40231
|
const buffer = Buffer.alloc(size - from);
|
|
39928
|
-
const read =
|
|
40232
|
+
const read = readSync2(handle, buffer, 0, buffer.length, from);
|
|
39929
40233
|
game.errorReportOffset = from + read;
|
|
39930
40234
|
const reported = buffer.subarray(0, read);
|
|
39931
40235
|
if (game.transcript !== null) {
|
|
@@ -39934,7 +40238,7 @@ class GodotServer {
|
|
|
39934
40238
|
game.log.append("stderr", reported);
|
|
39935
40239
|
}
|
|
39936
40240
|
} finally {
|
|
39937
|
-
|
|
40241
|
+
closeSync2(handle);
|
|
39938
40242
|
}
|
|
39939
40243
|
}
|
|
39940
40244
|
recordWhatItBrokeOn(game) {
|
|
@@ -40028,7 +40332,7 @@ class GodotServer {
|
|
|
40028
40332
|
...env ? { env } : {}
|
|
40029
40333
|
});
|
|
40030
40334
|
} finally {
|
|
40031
|
-
|
|
40335
|
+
closeSync2(transcript.fd);
|
|
40032
40336
|
}
|
|
40033
40337
|
child.unref();
|
|
40034
40338
|
const log = new GameLog;
|
|
@@ -40075,7 +40379,7 @@ class GodotServer {
|
|
|
40075
40379
|
}
|
|
40076
40380
|
let handle;
|
|
40077
40381
|
try {
|
|
40078
|
-
handle =
|
|
40382
|
+
handle = openSync3(game.transcript, "r");
|
|
40079
40383
|
} catch {
|
|
40080
40384
|
return;
|
|
40081
40385
|
}
|
|
@@ -40085,11 +40389,11 @@ class GodotServer {
|
|
|
40085
40389
|
return;
|
|
40086
40390
|
}
|
|
40087
40391
|
const buffer = Buffer.alloc(size - game.readOffset);
|
|
40088
|
-
const read =
|
|
40392
|
+
const read = readSync2(handle, buffer, 0, buffer.length, game.readOffset);
|
|
40089
40393
|
game.readOffset += read;
|
|
40090
40394
|
game.log.append("transcript", buffer.subarray(0, read));
|
|
40091
40395
|
} finally {
|
|
40092
|
-
|
|
40396
|
+
closeSync2(handle);
|
|
40093
40397
|
}
|
|
40094
40398
|
}
|
|
40095
40399
|
async pickUpWhatTheEditorIsPlaying() {
|
|
@@ -40287,7 +40591,7 @@ class GodotServer {
|
|
|
40287
40591
|
}
|
|
40288
40592
|
const unreadable = this.aRuntimeOfOursIsTooFarOff();
|
|
40289
40593
|
if (unreadable !== null) {
|
|
40290
|
-
const behind = unreadable
|
|
40594
|
+
const behind = whichHalfIsBehind([unreadable]);
|
|
40291
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.";
|
|
40292
40596
|
}
|
|
40293
40597
|
const record = readRunRecord();
|
|
@@ -40412,7 +40716,57 @@ class GodotServer {
|
|
|
40412
40716
|
}
|
|
40413
40717
|
return runIsUp(run, said);
|
|
40414
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
|
+
}
|
|
40415
40766
|
async handleGetDebugOutput(args, waitedMs) {
|
|
40767
|
+
if (readString(args, "op") === "editor") {
|
|
40768
|
+
return this.handleEditorConsole(args);
|
|
40769
|
+
}
|
|
40416
40770
|
await this.pickUpWhatTheEditorIsPlaying();
|
|
40417
40771
|
const run = this.currentRun();
|
|
40418
40772
|
if (!run) {
|
|
@@ -40863,8 +41217,8 @@ class GodotServer {
|
|
|
40863
41217
|
]);
|
|
40864
41218
|
}
|
|
40865
41219
|
const expectsScreenshot = command === "capture_screenshot" || command === "capture_viewport";
|
|
40866
|
-
const screenshotDir = expectsScreenshot ? mkdtempSync3(
|
|
40867
|
-
const screenshotPath = screenshotDir ?
|
|
41220
|
+
const screenshotDir = expectsScreenshot ? mkdtempSync3(join22(tmpdir5(), "gdharness-runtime-screenshot-")) : null;
|
|
41221
|
+
const screenshotPath = screenshotDir ? join22(screenshotDir, "capture.png") : null;
|
|
40868
41222
|
try {
|
|
40869
41223
|
const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs);
|
|
40870
41224
|
if (!reply.ok) {
|
|
@@ -40888,7 +41242,7 @@ class GodotServer {
|
|
|
40888
41242
|
type: "text",
|
|
40889
41243
|
text: `Screenshot captured: ${dimensions}${chosenAmongSeveral ? ` from pid ${own}, the game this server holds` : ""}`
|
|
40890
41244
|
},
|
|
40891
|
-
{ type: "image", data:
|
|
41245
|
+
{ type: "image", data: readFileSync20(screenshotPath).toString("base64"), mimeType: "image/png" }
|
|
40892
41246
|
]
|
|
40893
41247
|
};
|
|
40894
41248
|
} finally {
|
|
@@ -40955,7 +41309,7 @@ async function runGodotServer() {
|
|
|
40955
41309
|
const server = new GodotServer;
|
|
40956
41310
|
await server.run();
|
|
40957
41311
|
}
|
|
40958
|
-
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/";
|
|
40959
41313
|
var init_server3 = __esm(() => {
|
|
40960
41314
|
init_mcp();
|
|
40961
41315
|
init_stdio2();
|
|
@@ -40967,6 +41321,7 @@ var init_server3 = __esm(() => {
|
|
|
40967
41321
|
init_class_note();
|
|
40968
41322
|
init_config_pin();
|
|
40969
41323
|
init_dap_client();
|
|
41324
|
+
init_editor_log();
|
|
40970
41325
|
init_errors();
|
|
40971
41326
|
init_game_log();
|
|
40972
41327
|
init_godot_bridge();
|
|
@@ -40991,7 +41346,7 @@ var init_server3 = __esm(() => {
|
|
|
40991
41346
|
init_tool_definitions();
|
|
40992
41347
|
init_update_check();
|
|
40993
41348
|
run7 = promisify7(execFile7);
|
|
40994
|
-
__dirname2 =
|
|
41349
|
+
__dirname2 = dirname15(fileURLToPath4(import.meta.url));
|
|
40995
41350
|
CONSOLE_WRAPPER = /_console(\.exe)?$/i;
|
|
40996
41351
|
LONGEST_BOOT_NOTED_MS = 5 * 60000;
|
|
40997
41352
|
VALUE_NAMES = {
|
|
@@ -41031,8 +41386,8 @@ var init_server3 = __esm(() => {
|
|
|
41031
41386
|
});
|
|
41032
41387
|
|
|
41033
41388
|
// src/cli.ts
|
|
41034
|
-
import { existsSync as
|
|
41035
|
-
import { join as
|
|
41389
|
+
import { existsSync as existsSync16 } from "fs";
|
|
41390
|
+
import { join as join23, resolve as resolve5 } from "path";
|
|
41036
41391
|
|
|
41037
41392
|
// src/errors.ts
|
|
41038
41393
|
class Refusal extends Error {
|
|
@@ -42443,7 +42798,8 @@ function renderToolsMarkdown() {
|
|
|
42443
42798
|
"`op`. An unknown op or argument is refused with the valid set listed. Generated from the server.",
|
|
42444
42799
|
"",
|
|
42445
42800
|
`${projectPathSentence()}: the \`runtime_*\` and \`debug_*\` ones pick between running games`,
|
|
42446
|
-
"instead, and the others answer about the editor on this server.
|
|
42801
|
+
"instead, and the others answer about the editor on this server. The pick, when a call names",
|
|
42802
|
+
`neither \`projectPath\` nor \`pid\`: ${theGamePicked()}. An argument a tool does not`,
|
|
42447
42803
|
"declare is refused rather than ignored. Answers are read from the engine after the change, not",
|
|
42448
42804
|
"echoed from the request. Engine stderr comes back under `engine_messages`.",
|
|
42449
42805
|
"",
|
|
@@ -42503,9 +42859,8 @@ that is running, and the project on disk. ${TOOL_SPECS.length} tools, named \`do
|
|
|
42503
42859
|
|
|
42504
42860
|
- ${projectPathSentence()}. The \`runtime_*\` and \`debug_*\` ones pick between running
|
|
42505
42861
|
games instead, by \`projectPath\`, or by \`pid\` when several run from one project, as a bench
|
|
42506
|
-
and its workers do
|
|
42507
|
-
|
|
42508
|
-
connected to. There is no ambient project, and an argument a tool does not declare is refused
|
|
42862
|
+
and its workers do. Naming neither picks ${theGamePicked()}; the others answer about the editor
|
|
42863
|
+
this server is connected to. There is no ambient project, and an argument a tool does not declare is refused
|
|
42509
42864
|
rather than ignored.
|
|
42510
42865
|
- \`editor_status\` says whether an editor is connected and whether its addon matches the server.
|
|
42511
42866
|
\`addonIsStale\` means they differ, and \`staleNote\` says which half is behind: an editor that
|
|
@@ -42529,6 +42884,14 @@ that is running, and the project on disk. ${TOOL_SPECS.length} tools, named \`do
|
|
|
42529
42884
|
runtime addon announces nothing, so there is no process id and \`running\` is the editor's answer,
|
|
42530
42885
|
which can lag by seconds after the game has gone. \`editor_status\` names the game under
|
|
42531
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.
|
|
42532
42895
|
- One server, one editor. A second project is a second harness session with its own server, and
|
|
42533
42896
|
that works: each editor is opened on its own language server and debug adapter ports, and says
|
|
42534
42897
|
where it serves. Two editors on one project is the thing to refuse.
|
|
@@ -42738,7 +43101,7 @@ class UsageError extends Refusal {
|
|
|
42738
43101
|
function projectArgument(at) {
|
|
42739
43102
|
const named = args.slice(at).find((value) => !value.startsWith("--"));
|
|
42740
43103
|
const projectPath = resolve5(named ?? ".");
|
|
42741
|
-
if (!
|
|
43104
|
+
if (!existsSync16(join23(projectPath, "project.godot"))) {
|
|
42742
43105
|
throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
|
|
42743
43106
|
}
|
|
42744
43107
|
return projectPath;
|
|
@@ -42777,7 +43140,7 @@ async function registerRuntime(godot, projectPath) {
|
|
|
42777
43140
|
return;
|
|
42778
43141
|
}
|
|
42779
43142
|
console.log(`${RUNTIME_AUTOLOAD.name} autoload left at ${named}, which this project registers itself`);
|
|
42780
|
-
if (!
|
|
43143
|
+
if (!existsSync16(join23(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
|
|
42781
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}`);
|
|
42782
43145
|
}
|
|
42783
43146
|
}
|
|
@@ -42909,7 +43272,7 @@ async function setup() {
|
|
|
42909
43272
|
console.log("no harness configured; name one yourself, and gdharness harnesses lists them all");
|
|
42910
43273
|
}
|
|
42911
43274
|
if (!args.includes("--no-skill")) {
|
|
42912
|
-
const directories = skillDirectories(chosen.harnesses, projectPath,
|
|
43275
|
+
const directories = skillDirectories(chosen.harnesses, projectPath, existsSync16);
|
|
42913
43276
|
for (const written of writeSkill(directories, getLocalVersion())) {
|
|
42914
43277
|
console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
|
|
42915
43278
|
}
|
|
@@ -43002,7 +43365,7 @@ async function upgrade() {
|
|
|
43002
43365
|
moved.push(took.from);
|
|
43003
43366
|
}
|
|
43004
43367
|
}
|
|
43005
|
-
for (const written of writeSkill(skillDirectories(already, projectPath,
|
|
43368
|
+
for (const written of writeSkill(skillDirectories(already, projectPath, existsSync16), version)) {
|
|
43006
43369
|
console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
|
|
43007
43370
|
}
|
|
43008
43371
|
console.log(`
|