gdharness 0.13.37 → 0.13.39
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 +401 -135
- package/build/index.js +478 -216
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -15917,7 +15917,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
15917
15917
|
},
|
|
15918
15918
|
{
|
|
15919
15919
|
name: "script_diagnostics",
|
|
15920
|
-
description: "Errors and warnings for a script from the editor's language server, and whether the script is clean. Needs the editor running. Diagnostics are checked against the project's own files, and the two that the files disprove are named rather than passed on: a member the class cache's file declares comes back under contradictedByTheFile, and a class this project declares that the diagnostic could not resolve comes back under typesTheEditorHasNotLoaded, with inTheClassCache saying whether a launched game would resolve it. staleAnalysis then says
|
|
15920
|
+
description: "Errors and warnings for a script from the editor's language server, and whether the script is clean. Needs the editor running. Diagnostics are checked against the project's own files, and the two that the files disprove are named rather than passed on: a member the class cache's file declares comes back under contradictedByTheFile, and a class this project declares that the diagnostic could not resolve comes back under typesTheEditorHasNotLoaded, with inTheClassCache saying whether a launched game would resolve it. staleAnalysis then says what to do, and for both states it is editor_rescan first: it asks the editor already running, starts nothing, and has cleared each in every reproduction measured. What follows differs: for a stale analysed type, editor_rescan with reloadScript recompiles the held copy and editor_launch restart is the last resort that costs a window; for a class missing from the cache, project_import refresh_classes rewrites the cache from disk at the cost of a short headless engine, which matters beside a running bench.",
|
|
15921
15921
|
parameters: {
|
|
15922
15922
|
projectPath: PROJECT_PATH,
|
|
15923
15923
|
scriptPath: SCRIPT_PATH
|
|
@@ -16029,7 +16029,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16029
16029
|
},
|
|
16030
16030
|
{
|
|
16031
16031
|
name: "editor_output",
|
|
16032
|
-
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. Answers with the counts and the verdict as well as the entries. 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.",
|
|
16032
|
+
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. 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.",
|
|
16033
16033
|
parameters: {
|
|
16034
16034
|
severity: {
|
|
16035
16035
|
type: "string",
|
|
@@ -16057,7 +16057,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16057
16057
|
},
|
|
16058
16058
|
{
|
|
16059
16059
|
name: "editor_status",
|
|
16060
|
-
description:
|
|
16060
|
+
description: "Whether the editor addon is connected, which Godot answers, whether the editor is playing something, and whether a game with the runtime addon is reachable. playingInEditor is null when the editor was not asked or did not answer, which is not the same as a no: with no editor connected there is nobody to ask, and an editor that does not serve the question is given a moment and then left. A game can be playing while this is null, so read it as unanswered rather than as nothing playing, and use processActive and runtimes, which come from elsewhere and do not depend on the editor answering. Each game under runtimes is pinged with a short wait, so one held at a breakpoint is listed as unreachable rather than making this call wait out the runtime timeout; for the run this server is reporting on, heldAt says whether that is what it is, with the same three answers editor_output gives. When connected is false, mayYetConnect says whether that is final: the editor dials this server rather than the other way round and backs off between tries, so for the first half-minute after a server starts, false means \"not reached yet\" as often as it means \"no editor\". True is worth waiting out; false is an editor that is not there. False beside restartInterrupted is one that an editor_launch restart on a previous server asked to quit, where the launch half never ran because that server was ended in between, usually by the reconnect the restart was taken for; editor_launch open finishes it. listeningSince is when this server took the bridge port, which is what that window is measured from and what a caller wanting its own window should read. An editor this server opened counts for as long as its process lives, however far past that window the import runs, and awaitingLaunchedEditor names its pid so the two reasons for true are told apart. settingsDropped appears here once when the editor this server opened saved project.godot on its way in, which is the same reading editor_launch restart makes in its own answer. A game that has announced itself in a protocol this server does not speak is listed under unreadable rather than left out, with the protocol it speaks and whether the server or the addon is the older half. This happens during an upgrade that moves the runtime protocol, which most do not: installing replaces the addon on disk as soon as the pin moves, while a server that is already running stays on the version it started with, so the two speak different protocols until the server is reconnected. Those games are real and running, but no tool here can reach them until the halves match, so runtimes stays empty and runtimeConnected stays false. An upgrade that leaves the protocol alone opens no such window and this list stays empty through it.",
|
|
16061
16061
|
parameters: {},
|
|
16062
16062
|
requires: []
|
|
16063
16063
|
},
|
|
@@ -16382,7 +16382,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16382
16382
|
},
|
|
16383
16383
|
{
|
|
16384
16384
|
name: "debug_breakpoint",
|
|
16385
|
-
description: "Sets or removes a breakpoint through the editor's debug adapter. Needs the editor, not a running game: set them first, then editor_run, and the game stops where you asked.",
|
|
16385
|
+
description: "Sets or removes a breakpoint through the editor's debug adapter. Needs the editor, not a running game: set them first, then editor_run, and the game stops where you asked. The editor keeps a breakpoint set this way for one play, so every breakpoint this server holds is sent again before each play editor_run starts, and the start answer lists them under breakpoints. The set is also kept in the project, for the server that comes after a reconnect: a breakpoint stays until it is removed here, whichever server set it. Every answer lists the whole set under held.",
|
|
16386
16386
|
parameters: {
|
|
16387
16387
|
projectPath: PROJECT_PATH,
|
|
16388
16388
|
scriptPath: SCRIPT_PATH,
|
|
@@ -16407,7 +16407,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16407
16407
|
},
|
|
16408
16408
|
{
|
|
16409
16409
|
name: "debug_state",
|
|
16410
|
-
description: "Where the debugged game is stopped: the stack trace, what is in scope at a frame with the values, or the debug adapter's console output so far.",
|
|
16410
|
+
description: "Where the debugged game is stopped: the stack trace, what is in scope at a frame with the values, or the debug adapter's console output so far. A stop is reported by the editor's adapter only to the sessions connected when it happened, so a server that connected afterwards, which is every server after a reconnect, is shown a thread and no frames about a game still sitting at its breakpoint. That server asks the runtime instead: a held game accepts a connection and never answers, and the refusal then says held rather than running, and that the stack cannot be read from here. debug_control continue lets it go, and the next stop is one this session is told about, with its stack.",
|
|
16411
16411
|
parameters: {
|
|
16412
16412
|
frameId: {
|
|
16413
16413
|
type: "number",
|
|
@@ -31179,15 +31179,77 @@ var init_stdio2 = __esm(() => {
|
|
|
31179
31179
|
init_stdio();
|
|
31180
31180
|
});
|
|
31181
31181
|
|
|
31182
|
-
// src/
|
|
31182
|
+
// src/breakpoint-note.ts
|
|
31183
31183
|
import { mkdirSync as mkdirSync6, readFileSync as readFileSync10, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "node:fs";
|
|
31184
|
-
import { dirname as dirname7, join as join12 } from "node:path";
|
|
31184
|
+
import { dirname as dirname7, isAbsolute as isAbsolute2, join as join12, relative as relative2 } from "node:path";
|
|
31185
|
+
function breakpointNotePath(projectPath) {
|
|
31186
|
+
return join12(projectPath, ".godot", "gdharness-breakpoints.json");
|
|
31187
|
+
}
|
|
31188
|
+
function writeBreakpointNote(projectPath, held) {
|
|
31189
|
+
const path = breakpointNotePath(projectPath);
|
|
31190
|
+
const root = realPathOr(projectPath);
|
|
31191
|
+
const inside = held.map((one) => ({ scriptPath: realPathOr(one.scriptPath), lines: one.lines })).filter((one) => one.lines.length > 0 && withinProject(root, one.scriptPath));
|
|
31192
|
+
try {
|
|
31193
|
+
if (inside.length === 0) {
|
|
31194
|
+
rmSync6(path, { force: true });
|
|
31195
|
+
return;
|
|
31196
|
+
}
|
|
31197
|
+
const note = {
|
|
31198
|
+
breakpoints: inside.map((one) => ({
|
|
31199
|
+
script: relative2(root, one.scriptPath).replaceAll("\\", "/"),
|
|
31200
|
+
lines: [...one.lines]
|
|
31201
|
+
}))
|
|
31202
|
+
};
|
|
31203
|
+
mkdirSync6(dirname7(path), { recursive: true });
|
|
31204
|
+
writeFileSync8(path, `${JSON.stringify(note, null, 2)}
|
|
31205
|
+
`, "utf8");
|
|
31206
|
+
} catch (error) {
|
|
31207
|
+
console.error(`[SERVER] Could not record the breakpoints at ${path}: ${errorMessage(error)}`);
|
|
31208
|
+
}
|
|
31209
|
+
}
|
|
31210
|
+
function readBreakpointNote(projectPath) {
|
|
31211
|
+
try {
|
|
31212
|
+
const parsed = JSON.parse(readFileSync10(breakpointNotePath(projectPath), "utf8"));
|
|
31213
|
+
if (typeof parsed !== "object" || parsed === null || !Array.isArray(parsed.breakpoints)) {
|
|
31214
|
+
return [];
|
|
31215
|
+
}
|
|
31216
|
+
const held = [];
|
|
31217
|
+
for (const entry of parsed.breakpoints) {
|
|
31218
|
+
if (typeof entry !== "object" || entry === null) {
|
|
31219
|
+
continue;
|
|
31220
|
+
}
|
|
31221
|
+
const { script, lines: listed } = entry;
|
|
31222
|
+
if (typeof script !== "string" || isAbsolute2(script) || script.split("/").includes("..")) {
|
|
31223
|
+
continue;
|
|
31224
|
+
}
|
|
31225
|
+
const lines = Array.isArray(listed) ? listed.filter((line) => Number.isInteger(line) && line > 0) : [];
|
|
31226
|
+
if (lines.length > 0) {
|
|
31227
|
+
held.push({ scriptPath: realPathOr(join12(projectPath, script)), lines });
|
|
31228
|
+
}
|
|
31229
|
+
}
|
|
31230
|
+
return held;
|
|
31231
|
+
} catch {
|
|
31232
|
+
return [];
|
|
31233
|
+
}
|
|
31234
|
+
}
|
|
31235
|
+
function withinProject(projectPath, scriptPath) {
|
|
31236
|
+
const rel = relative2(projectPath, scriptPath);
|
|
31237
|
+
return rel !== "" && !rel.startsWith("..") && !isAbsolute2(rel);
|
|
31238
|
+
}
|
|
31239
|
+
var init_breakpoint_note = __esm(() => {
|
|
31240
|
+
init_errors();
|
|
31241
|
+
init_paths();
|
|
31242
|
+
});
|
|
31243
|
+
|
|
31244
|
+
// src/bridge-announce.ts
|
|
31245
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync11, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "node:fs";
|
|
31246
|
+
import { dirname as dirname8, join as join13 } from "node:path";
|
|
31185
31247
|
function announcementPath(projectPath) {
|
|
31186
|
-
return
|
|
31248
|
+
return join13(projectPath, ".godot", "gdharness-bridge.json");
|
|
31187
31249
|
}
|
|
31188
31250
|
function readAnnouncement(path) {
|
|
31189
31251
|
try {
|
|
31190
|
-
const parsed = JSON.parse(
|
|
31252
|
+
const parsed = JSON.parse(readFileSync11(path, "utf8"));
|
|
31191
31253
|
if (typeof parsed !== "object" || parsed === null) {
|
|
31192
31254
|
return null;
|
|
31193
31255
|
}
|
|
@@ -31206,7 +31268,7 @@ function announcementIsOurs(path) {
|
|
|
31206
31268
|
function announceBridge(projectPath, bridge) {
|
|
31207
31269
|
const path = announcementPath(projectPath);
|
|
31208
31270
|
try {
|
|
31209
|
-
|
|
31271
|
+
mkdirSync7(dirname8(path), { recursive: true });
|
|
31210
31272
|
const announcement = {
|
|
31211
31273
|
protocol: BRIDGE_ANNOUNCE_PROTOCOL,
|
|
31212
31274
|
host: bridge.host,
|
|
@@ -31215,7 +31277,7 @@ function announceBridge(projectPath, bridge) {
|
|
|
31215
31277
|
version: bridge.version,
|
|
31216
31278
|
startedAt: new Date().toISOString()
|
|
31217
31279
|
};
|
|
31218
|
-
|
|
31280
|
+
writeFileSync9(path, `${JSON.stringify(announcement, null, 2)}
|
|
31219
31281
|
`, "utf8");
|
|
31220
31282
|
return path;
|
|
31221
31283
|
} catch (error) {
|
|
@@ -31228,7 +31290,7 @@ function withdrawBridge(path) {
|
|
|
31228
31290
|
return;
|
|
31229
31291
|
}
|
|
31230
31292
|
try {
|
|
31231
|
-
|
|
31293
|
+
rmSync7(path, { force: true });
|
|
31232
31294
|
} catch (error) {
|
|
31233
31295
|
console.error(`[SERVER] Could not withdraw the editor bridge announcement: ${errorMessage(error)}`);
|
|
31234
31296
|
}
|
|
@@ -31239,16 +31301,16 @@ var init_bridge_announce = __esm(() => {
|
|
|
31239
31301
|
});
|
|
31240
31302
|
|
|
31241
31303
|
// src/config-pin.ts
|
|
31242
|
-
import { existsSync as existsSync10, readFileSync as
|
|
31243
|
-
import { join as
|
|
31304
|
+
import { existsSync as existsSync10, readFileSync as readFileSync12 } from "node:fs";
|
|
31305
|
+
import { join as join14 } from "node:path";
|
|
31244
31306
|
function versionsNamedInConfig(projectPath) {
|
|
31245
|
-
const config =
|
|
31307
|
+
const config = join14(projectPath, ".mcp.json");
|
|
31246
31308
|
if (!existsSync10(config)) {
|
|
31247
31309
|
return [];
|
|
31248
31310
|
}
|
|
31249
31311
|
let text;
|
|
31250
31312
|
try {
|
|
31251
|
-
text =
|
|
31313
|
+
text = readFileSync12(config, "utf8");
|
|
31252
31314
|
JSON.parse(text);
|
|
31253
31315
|
} catch {
|
|
31254
31316
|
return [];
|
|
@@ -31417,6 +31479,7 @@ class GodotDAPClient {
|
|
|
31417
31479
|
attached = false;
|
|
31418
31480
|
lastThreadId = 1;
|
|
31419
31481
|
halt = null;
|
|
31482
|
+
holdKnown = false;
|
|
31420
31483
|
breakpoints = new Map;
|
|
31421
31484
|
constructor(port = portFromEnv("GDHARNESS_DAP_PORT", DEFAULT_DAP_PORT), host = "127.0.0.1") {
|
|
31422
31485
|
this.port = port;
|
|
@@ -31462,11 +31525,7 @@ class GodotDAPClient {
|
|
|
31462
31525
|
this.failPendingRequests(error);
|
|
31463
31526
|
});
|
|
31464
31527
|
socket.on("close", () => {
|
|
31465
|
-
this.
|
|
31466
|
-
this.initialized = false;
|
|
31467
|
-
this.attached = false;
|
|
31468
|
-
this.halt = null;
|
|
31469
|
-
this.socket = null;
|
|
31528
|
+
this.forgetConnection();
|
|
31470
31529
|
this.failPendingRequests(new Error("DAP connection closed"));
|
|
31471
31530
|
});
|
|
31472
31531
|
resolve();
|
|
@@ -31482,10 +31541,7 @@ class GodotDAPClient {
|
|
|
31482
31541
|
}
|
|
31483
31542
|
async abandon() {
|
|
31484
31543
|
if (!this.socket) {
|
|
31485
|
-
this.
|
|
31486
|
-
this.initialized = false;
|
|
31487
|
-
this.attached = false;
|
|
31488
|
-
this.halt = null;
|
|
31544
|
+
this.forgetConnection();
|
|
31489
31545
|
return;
|
|
31490
31546
|
}
|
|
31491
31547
|
await new Promise((resolve) => {
|
|
@@ -31505,11 +31561,15 @@ class GodotDAPClient {
|
|
|
31505
31561
|
resolve();
|
|
31506
31562
|
}, 500);
|
|
31507
31563
|
});
|
|
31564
|
+
this.forgetConnection();
|
|
31565
|
+
}
|
|
31566
|
+
forgetConnection() {
|
|
31508
31567
|
this.socket = null;
|
|
31509
31568
|
this.connected = false;
|
|
31510
31569
|
this.initialized = false;
|
|
31511
31570
|
this.attached = false;
|
|
31512
31571
|
this.halt = null;
|
|
31572
|
+
this.holdKnown = false;
|
|
31513
31573
|
}
|
|
31514
31574
|
async ensureConnected() {
|
|
31515
31575
|
if (!this.connected) {
|
|
@@ -31598,6 +31658,12 @@ class GodotDAPClient {
|
|
|
31598
31658
|
description: said(body, "description"),
|
|
31599
31659
|
text: said(body, "text")
|
|
31600
31660
|
};
|
|
31661
|
+
this.holdKnown = true;
|
|
31662
|
+
return;
|
|
31663
|
+
}
|
|
31664
|
+
if (eventName === "continued") {
|
|
31665
|
+
this.halt = null;
|
|
31666
|
+
this.holdKnown = true;
|
|
31601
31667
|
return;
|
|
31602
31668
|
}
|
|
31603
31669
|
if (eventName === "terminated" || eventName === "exited") {
|
|
@@ -31636,6 +31702,43 @@ class GodotDAPClient {
|
|
|
31636
31702
|
await this.sendRequest("attach", {});
|
|
31637
31703
|
await this.sendRequest("configurationDone", {});
|
|
31638
31704
|
this.attached = true;
|
|
31705
|
+
if (!this.holdKnown) {
|
|
31706
|
+
await this.learnWhetherHeld();
|
|
31707
|
+
}
|
|
31708
|
+
}
|
|
31709
|
+
async learnWhetherHeld() {
|
|
31710
|
+
try {
|
|
31711
|
+
const threads = await this.sendRequest("threads");
|
|
31712
|
+
const listed = threads["threads"];
|
|
31713
|
+
const first = Array.isArray(listed) ? listed[0]?.["id"] : undefined;
|
|
31714
|
+
if (typeof first !== "number") {
|
|
31715
|
+
return;
|
|
31716
|
+
}
|
|
31717
|
+
this.lastThreadId = first;
|
|
31718
|
+
const response = await this.sendRequest("stackTrace", { threadId: first, startFrame: 0, levels: 1 });
|
|
31719
|
+
if (this.holdKnown) {
|
|
31720
|
+
return;
|
|
31721
|
+
}
|
|
31722
|
+
const frames = response["stackFrames"];
|
|
31723
|
+
if (Array.isArray(frames) && frames.length > 0) {
|
|
31724
|
+
this.halt = {
|
|
31725
|
+
reason: "attached",
|
|
31726
|
+
description: "held when this session attached; the stop that held it was reported to an earlier one",
|
|
31727
|
+
text: ""
|
|
31728
|
+
};
|
|
31729
|
+
this.holdKnown = true;
|
|
31730
|
+
}
|
|
31731
|
+
} catch {}
|
|
31732
|
+
}
|
|
31733
|
+
holdIsKnown() {
|
|
31734
|
+
return this.holdKnown;
|
|
31735
|
+
}
|
|
31736
|
+
learnedRunning() {
|
|
31737
|
+
if (this.holdKnown) {
|
|
31738
|
+
return;
|
|
31739
|
+
}
|
|
31740
|
+
this.halt = null;
|
|
31741
|
+
this.holdKnown = true;
|
|
31639
31742
|
}
|
|
31640
31743
|
setOutputSink(sink) {
|
|
31641
31744
|
this.onOutput = sink;
|
|
@@ -31648,38 +31751,70 @@ class GodotDAPClient {
|
|
|
31648
31751
|
return lines;
|
|
31649
31752
|
}
|
|
31650
31753
|
async setBreakpoint(filePath, line) {
|
|
31651
|
-
|
|
31652
|
-
|
|
31653
|
-
|
|
31654
|
-
fileBreakpoints.add(line);
|
|
31655
|
-
this.breakpoints.set(filePath, fileBreakpoints);
|
|
31656
|
-
const lines = Array.from(fileBreakpoints).sort((a, b) => a - b);
|
|
31657
|
-
return await this.sendRequest("setBreakpoints", {
|
|
31658
|
-
source: { path: filePath },
|
|
31659
|
-
breakpoints: lines.map((breakpointLine) => ({ line: breakpointLine }))
|
|
31660
|
-
});
|
|
31754
|
+
const wanted = new Set(this.breakpoints.get(filePath) ?? []);
|
|
31755
|
+
wanted.add(line);
|
|
31756
|
+
return await this.sendBreakpoints(filePath, wanted);
|
|
31661
31757
|
}
|
|
31662
31758
|
async removeBreakpoint(filePath, line) {
|
|
31759
|
+
const wanted = new Set(this.breakpoints.get(filePath) ?? []);
|
|
31760
|
+
wanted.delete(line);
|
|
31761
|
+
return await this.sendBreakpoints(filePath, wanted);
|
|
31762
|
+
}
|
|
31763
|
+
async sendBreakpoints(filePath, lines) {
|
|
31663
31764
|
await this.ensureConnected();
|
|
31664
31765
|
await this.initialize();
|
|
31665
|
-
const
|
|
31666
|
-
|
|
31667
|
-
|
|
31766
|
+
const sorted = Array.from(lines).sort((a, b) => a - b);
|
|
31767
|
+
const answer = await this.sendRequest("setBreakpoints", {
|
|
31768
|
+
source: { path: filePath },
|
|
31769
|
+
breakpoints: sorted.map((breakpointLine) => ({ line: breakpointLine }))
|
|
31770
|
+
});
|
|
31771
|
+
if (lines.size === 0) {
|
|
31668
31772
|
this.breakpoints.delete(filePath);
|
|
31669
31773
|
} else {
|
|
31670
|
-
this.breakpoints.set(filePath,
|
|
31774
|
+
this.breakpoints.set(filePath, new Set(lines));
|
|
31775
|
+
}
|
|
31776
|
+
return answer;
|
|
31777
|
+
}
|
|
31778
|
+
async reapplyBreakpoints() {
|
|
31779
|
+
const applied = [];
|
|
31780
|
+
const refused = [];
|
|
31781
|
+
for (const [filePath, lines] of Array.from(this.breakpoints)) {
|
|
31782
|
+
try {
|
|
31783
|
+
await this.sendBreakpoints(filePath, lines);
|
|
31784
|
+
applied.push({ scriptPath: filePath, lines: Array.from(lines).sort((a, b) => a - b) });
|
|
31785
|
+
} catch (error) {
|
|
31786
|
+
this.breakpoints.delete(filePath);
|
|
31787
|
+
refused.push({
|
|
31788
|
+
scriptPath: filePath,
|
|
31789
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
31790
|
+
});
|
|
31791
|
+
}
|
|
31792
|
+
}
|
|
31793
|
+
return { applied, refused };
|
|
31794
|
+
}
|
|
31795
|
+
breakpointsHeld() {
|
|
31796
|
+
return Array.from(this.breakpoints, ([scriptPath, lines]) => ({
|
|
31797
|
+
scriptPath,
|
|
31798
|
+
lines: Array.from(lines).sort((a, b) => a - b)
|
|
31799
|
+
}));
|
|
31800
|
+
}
|
|
31801
|
+
holdBreakpoints(held) {
|
|
31802
|
+
for (const { scriptPath, lines } of held) {
|
|
31803
|
+
const fileBreakpoints = this.breakpoints.get(scriptPath) ?? new Set;
|
|
31804
|
+
for (const line of lines) {
|
|
31805
|
+
fileBreakpoints.add(line);
|
|
31806
|
+
}
|
|
31807
|
+
if (fileBreakpoints.size > 0) {
|
|
31808
|
+
this.breakpoints.set(scriptPath, fileBreakpoints);
|
|
31809
|
+
}
|
|
31671
31810
|
}
|
|
31672
|
-
const remaining = Array.from(fileBreakpoints).sort((a, b) => a - b);
|
|
31673
|
-
return await this.sendRequest("setBreakpoints", {
|
|
31674
|
-
source: { path: filePath },
|
|
31675
|
-
breakpoints: remaining.map((breakpointLine) => ({ line: breakpointLine }))
|
|
31676
|
-
});
|
|
31677
31811
|
}
|
|
31678
31812
|
async continue(threadId) {
|
|
31679
31813
|
await this.attach();
|
|
31680
31814
|
const resolvedThreadId = await this.resolveThreadId(threadId);
|
|
31681
31815
|
await this.sendRequest("continue", { threadId: resolvedThreadId });
|
|
31682
31816
|
this.halt = null;
|
|
31817
|
+
this.holdKnown = true;
|
|
31683
31818
|
}
|
|
31684
31819
|
async stepOver(threadId) {
|
|
31685
31820
|
await this.attach();
|
|
@@ -31778,11 +31913,7 @@ class GodotDAPClient {
|
|
|
31778
31913
|
}
|
|
31779
31914
|
failOversizedStream(detail) {
|
|
31780
31915
|
const socket = this.socket;
|
|
31781
|
-
this.
|
|
31782
|
-
this.connected = false;
|
|
31783
|
-
this.initialized = false;
|
|
31784
|
-
this.attached = false;
|
|
31785
|
-
this.halt = null;
|
|
31916
|
+
this.forgetConnection();
|
|
31786
31917
|
this.reader = new FrameReader;
|
|
31787
31918
|
socket?.destroy();
|
|
31788
31919
|
this.failPendingRequests(new Error(`Godot DAP ${detail}. The connection was dropped.`));
|
|
@@ -35688,7 +35819,7 @@ var init_junit = __esm(() => {
|
|
|
35688
35819
|
import { realpathSync as realpathSync2 } from "node:fs";
|
|
35689
35820
|
import { readFile, realpath } from "node:fs/promises";
|
|
35690
35821
|
import { createConnection as createConnection2 } from "node:net";
|
|
35691
|
-
import { dirname as
|
|
35822
|
+
import { dirname as dirname9, resolve as resolve3 } from "node:path";
|
|
35692
35823
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
35693
35824
|
function diagnosticsKey(uri) {
|
|
35694
35825
|
let decoded;
|
|
@@ -35816,7 +35947,7 @@ class GodotLSPClient {
|
|
|
35816
35947
|
if (this.initialized) {
|
|
35817
35948
|
return;
|
|
35818
35949
|
}
|
|
35819
|
-
const rootPath = this.rootPath ??
|
|
35950
|
+
const rootPath = this.rootPath ?? dirname9(resolve3(filePath));
|
|
35820
35951
|
await this.initialize(rootPath);
|
|
35821
35952
|
}
|
|
35822
35953
|
async sendRequest(method, params) {
|
|
@@ -36261,13 +36392,13 @@ var init_process_time = __esm(() => {
|
|
|
36261
36392
|
});
|
|
36262
36393
|
|
|
36263
36394
|
// src/project-scan.ts
|
|
36264
|
-
import { existsSync as existsSync12, readdirSync as readdirSync4, readFileSync as
|
|
36265
|
-
import { join as
|
|
36395
|
+
import { existsSync as existsSync12, readdirSync as readdirSync4, readFileSync as readFileSync13 } from "node:fs";
|
|
36396
|
+
import { join as join15 } from "node:path";
|
|
36266
36397
|
function skipped(name) {
|
|
36267
36398
|
return name.startsWith(".") || SKIPPED.has(name);
|
|
36268
36399
|
}
|
|
36269
36400
|
function steppedOver(directory) {
|
|
36270
|
-
return existsSync12(
|
|
36401
|
+
return existsSync12(join15(directory, ".gdignore"));
|
|
36271
36402
|
}
|
|
36272
36403
|
function projectStructure(projectPath) {
|
|
36273
36404
|
const structure = { scenes: 0, scripts: 0, assets: 0, other: 0 };
|
|
@@ -36280,7 +36411,7 @@ function projectStructure(projectPath) {
|
|
|
36280
36411
|
continue;
|
|
36281
36412
|
}
|
|
36282
36413
|
if (entry.isDirectory()) {
|
|
36283
|
-
visit(
|
|
36414
|
+
visit(join15(directory, entry.name));
|
|
36284
36415
|
} else if (entry.isFile()) {
|
|
36285
36416
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
36286
36417
|
if (extension === "tscn") {
|
|
@@ -36310,14 +36441,14 @@ function scriptsWithoutUid(projectPath) {
|
|
|
36310
36441
|
}
|
|
36311
36442
|
const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
36312
36443
|
if (entry.isDirectory()) {
|
|
36313
|
-
visit(
|
|
36444
|
+
visit(join15(directory, entry.name), spelled);
|
|
36314
36445
|
continue;
|
|
36315
36446
|
}
|
|
36316
36447
|
if (!entry.isFile()) {
|
|
36317
36448
|
continue;
|
|
36318
36449
|
}
|
|
36319
36450
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
36320
|
-
if (CARRIES_A_UID.has(extension) && !existsSync12(
|
|
36451
|
+
if (CARRIES_A_UID.has(extension) && !existsSync12(join15(directory, `${entry.name}.uid`))) {
|
|
36321
36452
|
found.push(spelled);
|
|
36322
36453
|
}
|
|
36323
36454
|
}
|
|
@@ -36352,7 +36483,7 @@ function searchProject(projectPath, options) {
|
|
|
36352
36483
|
if (skipped(entry.name)) {
|
|
36353
36484
|
continue;
|
|
36354
36485
|
}
|
|
36355
|
-
const entryPath =
|
|
36486
|
+
const entryPath = join15(directory, entry.name);
|
|
36356
36487
|
if (entry.isDirectory()) {
|
|
36357
36488
|
visit(entryPath);
|
|
36358
36489
|
continue;
|
|
@@ -36363,7 +36494,7 @@ function searchProject(projectPath, options) {
|
|
|
36363
36494
|
}
|
|
36364
36495
|
result.summary.files_searched += 1;
|
|
36365
36496
|
const matches = [];
|
|
36366
|
-
for (const [index, line] of
|
|
36497
|
+
for (const [index, line] of readFileSync13(entryPath, "utf8").split(`
|
|
36367
36498
|
`).entries()) {
|
|
36368
36499
|
if (full()) {
|
|
36369
36500
|
break;
|
|
@@ -36392,16 +36523,16 @@ var init_project_scan = __esm(() => {
|
|
|
36392
36523
|
});
|
|
36393
36524
|
|
|
36394
36525
|
// src/restart-note.ts
|
|
36395
|
-
import { mkdirSync as
|
|
36396
|
-
import { dirname as
|
|
36526
|
+
import { mkdirSync as mkdirSync8, readFileSync as readFileSync14, rmSync as rmSync8, writeFileSync as writeFileSync10 } from "node:fs";
|
|
36527
|
+
import { dirname as dirname10, join as join16 } from "node:path";
|
|
36397
36528
|
function restartNotePath(projectPath) {
|
|
36398
|
-
return
|
|
36529
|
+
return join16(projectPath, ".godot", "gdharness-restart.json");
|
|
36399
36530
|
}
|
|
36400
36531
|
function noteRestartBegun(note) {
|
|
36401
36532
|
const path = restartNotePath(note.projectPath);
|
|
36402
36533
|
try {
|
|
36403
|
-
|
|
36404
|
-
|
|
36534
|
+
mkdirSync8(dirname10(path), { recursive: true });
|
|
36535
|
+
writeFileSync10(path, `${JSON.stringify(note, null, 2)}
|
|
36405
36536
|
`, "utf8");
|
|
36406
36537
|
} catch (error) {
|
|
36407
36538
|
console.error(`[SERVER] Could not record the restart at ${path}: ${errorMessage(error)}`);
|
|
@@ -36409,7 +36540,7 @@ function noteRestartBegun(note) {
|
|
|
36409
36540
|
}
|
|
36410
36541
|
function restartOwed(projectPath) {
|
|
36411
36542
|
try {
|
|
36412
|
-
const parsed = JSON.parse(
|
|
36543
|
+
const parsed = JSON.parse(readFileSync14(restartNotePath(projectPath), "utf8"));
|
|
36413
36544
|
if (typeof parsed !== "object" || parsed === null) {
|
|
36414
36545
|
return null;
|
|
36415
36546
|
}
|
|
@@ -36424,7 +36555,7 @@ function restartOwed(projectPath) {
|
|
|
36424
36555
|
}
|
|
36425
36556
|
function restartSettled(projectPath) {
|
|
36426
36557
|
try {
|
|
36427
|
-
|
|
36558
|
+
rmSync8(restartNotePath(projectPath), { force: true });
|
|
36428
36559
|
} catch (error) {
|
|
36429
36560
|
console.error(`[SERVER] Could not clear the restart note: ${errorMessage(error)}`);
|
|
36430
36561
|
}
|
|
@@ -36434,10 +36565,10 @@ var init_restart_note = __esm(() => {
|
|
|
36434
36565
|
});
|
|
36435
36566
|
|
|
36436
36567
|
// src/runtime-client.ts
|
|
36437
|
-
import { existsSync as existsSync13, readdirSync as readdirSync5, readFileSync as
|
|
36568
|
+
import { existsSync as existsSync13, readdirSync as readdirSync5, readFileSync as readFileSync15, unlinkSync } from "node:fs";
|
|
36438
36569
|
import { createConnection as createConnection3 } from "node:net";
|
|
36439
36570
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
36440
|
-
import { join as
|
|
36571
|
+
import { join as join17, resolve as resolve4 } from "node:path";
|
|
36441
36572
|
import { setTimeout as delay2 } from "node:timers/promises";
|
|
36442
36573
|
function runtimeDirectory(variables = process.env) {
|
|
36443
36574
|
const explicit = envValue("GDHARNESS_RUNTIME_DIR", variables);
|
|
@@ -36445,13 +36576,13 @@ function runtimeDirectory(variables = process.env) {
|
|
|
36445
36576
|
return explicit;
|
|
36446
36577
|
}
|
|
36447
36578
|
const perUser = envValue("XDG_RUNTIME_DIR", variables);
|
|
36448
|
-
return
|
|
36579
|
+
return join17(perUser ?? tmpdir4(), "gdharness");
|
|
36449
36580
|
}
|
|
36450
36581
|
function runtimeDirectories(variables = process.env) {
|
|
36451
36582
|
const candidates = [runtimeDirectory(variables)];
|
|
36452
36583
|
const fallbacks = process.platform === "win32" ? [envValue("SystemRoot", variables) ?? envValue("windir", variables) ?? "C:\\Windows"] : ["/tmp", "/var/tmp"];
|
|
36453
36584
|
for (const base of fallbacks) {
|
|
36454
|
-
candidates.push(
|
|
36585
|
+
candidates.push(join17(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
|
|
36455
36586
|
}
|
|
36456
36587
|
return [...new Set(candidates.map((path) => resolve4(path)))];
|
|
36457
36588
|
}
|
|
@@ -36466,7 +36597,7 @@ function processAlive(pid) {
|
|
|
36466
36597
|
function parseAnnouncement(file, pid) {
|
|
36467
36598
|
let fields;
|
|
36468
36599
|
try {
|
|
36469
|
-
fields = asParams(JSON.parse(
|
|
36600
|
+
fields = asParams(JSON.parse(readFileSync15(file, "utf8")));
|
|
36470
36601
|
} catch {
|
|
36471
36602
|
return { kind: "rubbish" };
|
|
36472
36603
|
}
|
|
@@ -36507,7 +36638,7 @@ function discoverRuntimes(directories = runtimeDirectories()) {
|
|
|
36507
36638
|
function wentAway(file, pid) {
|
|
36508
36639
|
let project = "";
|
|
36509
36640
|
try {
|
|
36510
|
-
const fields = asParams(JSON.parse(
|
|
36641
|
+
const fields = asParams(JSON.parse(readFileSync15(file, "utf8")));
|
|
36511
36642
|
project = readString(readParams(fields, "project") ?? {}, "path") ?? "";
|
|
36512
36643
|
} catch {
|
|
36513
36644
|
return;
|
|
@@ -36536,7 +36667,7 @@ function announcedIn(directory) {
|
|
|
36536
36667
|
if (!match) {
|
|
36537
36668
|
continue;
|
|
36538
36669
|
}
|
|
36539
|
-
const file =
|
|
36670
|
+
const file = join17(directory, entry);
|
|
36540
36671
|
const pid = Number.parseInt(match[1] ?? "", 10);
|
|
36541
36672
|
const alive = processAlive(pid);
|
|
36542
36673
|
const announced = alive ? parseAnnouncement(file, pid) : { kind: "rubbish" };
|
|
@@ -36713,36 +36844,36 @@ var init_runtime_client = __esm(() => {
|
|
|
36713
36844
|
|
|
36714
36845
|
// src/run-record.ts
|
|
36715
36846
|
import { execFileSync } from "node:child_process";
|
|
36716
|
-
import { mkdirSync as
|
|
36717
|
-
import { basename as basename2, join as
|
|
36847
|
+
import { mkdirSync as mkdirSync9, openSync, readdirSync as readdirSync6, readFileSync as readFileSync16, rmSync as rmSync9, statSync as statSync3, writeFileSync as writeFileSync11 } from "node:fs";
|
|
36848
|
+
import { basename as basename2, join as join18 } from "node:path";
|
|
36718
36849
|
function runsDirectory() {
|
|
36719
|
-
return
|
|
36850
|
+
return join18(runtimeDirectory(), "runs");
|
|
36720
36851
|
}
|
|
36721
36852
|
function recordPath() {
|
|
36722
|
-
return
|
|
36853
|
+
return join18(runsDirectory(), "run.json");
|
|
36723
36854
|
}
|
|
36724
36855
|
function recordPaths() {
|
|
36725
|
-
return runtimeDirectories().map((directory) =>
|
|
36856
|
+
return runtimeDirectories().map((directory) => join18(directory, "runs", "run.json"));
|
|
36726
36857
|
}
|
|
36727
36858
|
function openTranscript(startedAt) {
|
|
36728
|
-
|
|
36729
|
-
const path =
|
|
36859
|
+
mkdirSync9(runsDirectory(), { recursive: true });
|
|
36860
|
+
const path = join18(runsDirectory(), `run-${startedAt}.log`);
|
|
36730
36861
|
return { path, fd: openSync(path, "a") };
|
|
36731
36862
|
}
|
|
36732
36863
|
function writeRunRecord(record) {
|
|
36733
|
-
|
|
36734
|
-
|
|
36864
|
+
mkdirSync9(runsDirectory(), { recursive: true });
|
|
36865
|
+
writeFileSync11(recordPath(), JSON.stringify(record, null, 2), "utf8");
|
|
36735
36866
|
}
|
|
36736
36867
|
function editorNotePath() {
|
|
36737
|
-
return
|
|
36868
|
+
return join18(runsDirectory(), "editor-run.json");
|
|
36738
36869
|
}
|
|
36739
36870
|
function writeEditorRunNote(note) {
|
|
36740
|
-
|
|
36741
|
-
|
|
36871
|
+
mkdirSync9(runsDirectory(), { recursive: true });
|
|
36872
|
+
writeFileSync11(editorNotePath(), JSON.stringify(note, null, 2), "utf8");
|
|
36742
36873
|
}
|
|
36743
36874
|
function readEditorRunNote() {
|
|
36744
36875
|
for (const directory of runtimeDirectories()) {
|
|
36745
|
-
const note = editorNoteAt(
|
|
36876
|
+
const note = editorNoteAt(join18(directory, "runs", "editor-run.json"));
|
|
36746
36877
|
if (note !== null) {
|
|
36747
36878
|
return note;
|
|
36748
36879
|
}
|
|
@@ -36752,7 +36883,7 @@ function readEditorRunNote() {
|
|
|
36752
36883
|
function editorNoteAt(path) {
|
|
36753
36884
|
let parsed;
|
|
36754
36885
|
try {
|
|
36755
|
-
parsed = JSON.parse(
|
|
36886
|
+
parsed = JSON.parse(readFileSync16(path, "utf8"));
|
|
36756
36887
|
} catch {
|
|
36757
36888
|
return null;
|
|
36758
36889
|
}
|
|
@@ -36775,7 +36906,7 @@ function recordRunEnded(pid, exitCode) {
|
|
|
36775
36906
|
return;
|
|
36776
36907
|
}
|
|
36777
36908
|
try {
|
|
36778
|
-
|
|
36909
|
+
writeFileSync11(path, JSON.stringify({ ...record, exitCode }, null, 2), "utf8");
|
|
36779
36910
|
} catch {}
|
|
36780
36911
|
}
|
|
36781
36912
|
function readRunRecord() {
|
|
@@ -36790,7 +36921,7 @@ function readRunRecord() {
|
|
|
36790
36921
|
function recordAt(path) {
|
|
36791
36922
|
let parsed;
|
|
36792
36923
|
try {
|
|
36793
|
-
parsed = JSON.parse(
|
|
36924
|
+
parsed = JSON.parse(readFileSync16(path, "utf8"));
|
|
36794
36925
|
} catch {
|
|
36795
36926
|
return null;
|
|
36796
36927
|
}
|
|
@@ -36835,7 +36966,7 @@ function runningAs(pid) {
|
|
|
36835
36966
|
const began = startedAt(pid, null);
|
|
36836
36967
|
const when = began === null ? {} : { startedAt: began };
|
|
36837
36968
|
if (process.platform === "linux") {
|
|
36838
|
-
const raw =
|
|
36969
|
+
const raw = readFileSync16(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
|
|
36839
36970
|
return raw === "" ? null : { kind: "commandLine", text: raw, ...when };
|
|
36840
36971
|
}
|
|
36841
36972
|
const args = execFileSync("ps", ["-p", String(pid), "-o", "args="], {
|
|
@@ -36955,7 +37086,7 @@ function clearRunRecord(ours = () => true) {
|
|
|
36955
37086
|
continue;
|
|
36956
37087
|
}
|
|
36957
37088
|
try {
|
|
36958
|
-
|
|
37089
|
+
rmSync9(path);
|
|
36959
37090
|
} catch {}
|
|
36960
37091
|
}
|
|
36961
37092
|
}
|
|
@@ -36979,12 +37110,12 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
36979
37110
|
if (!entry.startsWith("run-") || !entry.endsWith(".log")) {
|
|
36980
37111
|
continue;
|
|
36981
37112
|
}
|
|
36982
|
-
const path =
|
|
37113
|
+
const path = join18(directory, entry);
|
|
36983
37114
|
if (path === keep || ageMs(path, now) < keepMs) {
|
|
36984
37115
|
continue;
|
|
36985
37116
|
}
|
|
36986
37117
|
try {
|
|
36987
|
-
|
|
37118
|
+
rmSync9(path);
|
|
36988
37119
|
} catch {}
|
|
36989
37120
|
}
|
|
36990
37121
|
}
|
|
@@ -37002,21 +37133,38 @@ import {
|
|
|
37002
37133
|
closeSync,
|
|
37003
37134
|
existsSync as existsSync14,
|
|
37004
37135
|
fstatSync,
|
|
37005
|
-
mkdirSync as
|
|
37136
|
+
mkdirSync as mkdirSync10,
|
|
37006
37137
|
mkdtempSync as mkdtempSync3,
|
|
37007
37138
|
openSync as openSync2,
|
|
37008
37139
|
readdirSync as readdirSync7,
|
|
37009
|
-
readFileSync as
|
|
37140
|
+
readFileSync as readFileSync17,
|
|
37010
37141
|
readSync
|
|
37011
37142
|
} from "node:fs";
|
|
37012
37143
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
37013
|
-
import { basename as basename3, dirname as
|
|
37144
|
+
import { basename as basename3, dirname as dirname11, join as join19, normalize as normalize3, relative as relative3 } from "node:path";
|
|
37014
37145
|
import { setTimeout as delay3 } from "node:timers/promises";
|
|
37015
37146
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
37016
37147
|
import { promisify as promisify6 } from "node:util";
|
|
37017
37148
|
function patienceForFrames(frames, atLeast) {
|
|
37018
37149
|
return Math.max(atLeast, frames / SLOWEST_FRAME_RATE * 1000 + atLeast);
|
|
37019
37150
|
}
|
|
37151
|
+
function projectSpelling(projectPath, absolutePath) {
|
|
37152
|
+
return `res://${relative3(realPathOr(projectPath), realPathOr(absolutePath)).replaceAll("\\", "/")}`;
|
|
37153
|
+
}
|
|
37154
|
+
function describeHalt(halt) {
|
|
37155
|
+
switch (halt.reason) {
|
|
37156
|
+
case "breakpoint":
|
|
37157
|
+
return "at a breakpoint";
|
|
37158
|
+
case "step":
|
|
37159
|
+
return "after a step";
|
|
37160
|
+
case "exception":
|
|
37161
|
+
return halt.text === "" ? "on an error" : `on an error: ${halt.text}`;
|
|
37162
|
+
case "attached":
|
|
37163
|
+
return "held when this session attached";
|
|
37164
|
+
default:
|
|
37165
|
+
return `stopped with reason "${halt.reason}"`;
|
|
37166
|
+
}
|
|
37167
|
+
}
|
|
37020
37168
|
function stillRunning(run) {
|
|
37021
37169
|
if (run?.exitCode !== null || run.endedUnwatched === true) {
|
|
37022
37170
|
return false;
|
|
@@ -37165,7 +37313,7 @@ function aboveTheRunner(entry) {
|
|
|
37165
37313
|
}
|
|
37166
37314
|
function elsewhereIn(projectPath, asked) {
|
|
37167
37315
|
const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
|
|
37168
|
-
if (wanted === "" || existsSync14(
|
|
37316
|
+
if (wanted === "" || existsSync14(join19(projectPath, wanted))) {
|
|
37169
37317
|
return "";
|
|
37170
37318
|
}
|
|
37171
37319
|
const root = (wanted.split("/")[0] ?? "").toLowerCase();
|
|
@@ -37179,11 +37327,11 @@ function elsewhereIn(projectPath, asked) {
|
|
|
37179
37327
|
return near.length === 0 ? "" : ` The project does have ${there}.`;
|
|
37180
37328
|
}
|
|
37181
37329
|
function hasMainScene(projectFile) {
|
|
37182
|
-
const scene = parseProjectGodot(
|
|
37330
|
+
const scene = parseProjectGodot(readFileSync17(projectFile, "utf8"))["application"]?.["run/main_scene"];
|
|
37183
37331
|
return typeof scene === "string" && scene !== "";
|
|
37184
37332
|
}
|
|
37185
37333
|
function editorPlaysHeadless(projectFile) {
|
|
37186
|
-
const runArgs = parseProjectGodot(
|
|
37334
|
+
const runArgs = parseProjectGodot(readFileSync17(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
|
|
37187
37335
|
return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
|
|
37188
37336
|
}
|
|
37189
37337
|
function camelCased(params) {
|
|
@@ -37199,7 +37347,7 @@ function camelCased(params) {
|
|
|
37199
37347
|
class GodotServer {
|
|
37200
37348
|
mcp;
|
|
37201
37349
|
locator = new GodotLocator2;
|
|
37202
|
-
operationsScript =
|
|
37350
|
+
operationsScript = join19(__dirname2, "godot", "operations", "godot_operations.gd");
|
|
37203
37351
|
godotBridge;
|
|
37204
37352
|
tools = buildToolDefinitions();
|
|
37205
37353
|
activeProcess = null;
|
|
@@ -37211,6 +37359,7 @@ class GodotServer {
|
|
|
37211
37359
|
lastGodotVersion = null;
|
|
37212
37360
|
lspClient = null;
|
|
37213
37361
|
dapClient = null;
|
|
37362
|
+
breakpointsSeededOn = null;
|
|
37214
37363
|
bridgeStartupError = null;
|
|
37215
37364
|
bridgeRetry = null;
|
|
37216
37365
|
successorWatch = null;
|
|
@@ -37809,17 +37958,28 @@ class GodotServer {
|
|
|
37809
37958
|
if (!located.ok) {
|
|
37810
37959
|
return this.createErrorResponse(located.reason, PATH_SOLUTIONS);
|
|
37811
37960
|
}
|
|
37812
|
-
|
|
37961
|
+
const session = this.dapHoldingBreakpointsOf(project.value.path);
|
|
37962
|
+
const answered = await this.handleDAP(op === "set" ? "dap_set_breakpoint" : "dap_remove_breakpoint", {
|
|
37813
37963
|
...args,
|
|
37814
37964
|
scriptPath: realPathOr(located.absolutePath)
|
|
37815
37965
|
});
|
|
37966
|
+
if (answered.isError === true) {
|
|
37967
|
+
return answered;
|
|
37968
|
+
}
|
|
37969
|
+
const held = session.breakpointsHeld();
|
|
37970
|
+
writeBreakpointNote(project.value.path, held);
|
|
37971
|
+
return this.jsonTextResponse({
|
|
37972
|
+
...asParams(JSON.parse(answered.content[0]?.text ?? "{}")),
|
|
37973
|
+
held: this.breakpointsAsProjectSpells(project.value.path, held),
|
|
37974
|
+
note: "The editor keeps a breakpoint set this way for one play. Every breakpoint held here is sent again before each play editor_run starts, and kept in the project for the server after a reconnect."
|
|
37975
|
+
});
|
|
37816
37976
|
}
|
|
37817
37977
|
case "debug_control":
|
|
37818
37978
|
case "debug_state": {
|
|
37819
37979
|
if (tool === "debug_state" && op === "output") {
|
|
37820
37980
|
return await this.handleDAP("dap_get_output", args);
|
|
37821
37981
|
}
|
|
37822
|
-
const held = await this.debuggedGame();
|
|
37982
|
+
const held = await this.debuggedGame(tool === "debug_control" ? "move" : "read");
|
|
37823
37983
|
if (!held.ok) {
|
|
37824
37984
|
return held.response;
|
|
37825
37985
|
}
|
|
@@ -37842,7 +38002,7 @@ class GodotServer {
|
|
|
37842
38002
|
if (path === undefined) {
|
|
37843
38003
|
return { ok: false, response: this.createErrorResponse("projectPath is required.") };
|
|
37844
38004
|
}
|
|
37845
|
-
const file =
|
|
38005
|
+
const file = join19(path, "project.godot");
|
|
37846
38006
|
if (!existsSync14(file)) {
|
|
37847
38007
|
return {
|
|
37848
38008
|
ok: false,
|
|
@@ -37868,7 +38028,7 @@ class GodotServer {
|
|
|
37868
38028
|
}
|
|
37869
38029
|
return { ok: true, value: given };
|
|
37870
38030
|
}
|
|
37871
|
-
async debuggedGame() {
|
|
38031
|
+
async debuggedGame(intent) {
|
|
37872
38032
|
const game = this.activeProcess;
|
|
37873
38033
|
if (game !== null && game.exitCode !== null) {
|
|
37874
38034
|
return {
|
|
@@ -37906,6 +38066,19 @@ class GodotServer {
|
|
|
37906
38066
|
};
|
|
37907
38067
|
}
|
|
37908
38068
|
if (!this.dap().isStopped()) {
|
|
38069
|
+
const hold = await this.holdOf(game);
|
|
38070
|
+
if (hold.heldAt !== null) {
|
|
38071
|
+
if (intent === "move") {
|
|
38072
|
+
return { ok: true, value: game };
|
|
38073
|
+
}
|
|
38074
|
+
return {
|
|
38075
|
+
ok: false,
|
|
38076
|
+
response: this.createErrorResponse(hold.heldAt === undefined ? `Whether the game is held cannot be told from here, so there is no stack to read. ${hold.heldNote ?? ""}` : `The game is held, and this server cannot read its stack: ${hold.heldAt.description}.`, [
|
|
38077
|
+
"debug_control continue lets a held game go, and the next stop is one this session is told about",
|
|
38078
|
+
"editor_output says what it printed on the way to where it is"
|
|
38079
|
+
])
|
|
38080
|
+
};
|
|
38081
|
+
}
|
|
37909
38082
|
return {
|
|
37910
38083
|
ok: false,
|
|
37911
38084
|
response: this.createErrorResponse("The game is running, not stopped, so it has no stack and no scope to read.", [
|
|
@@ -38043,7 +38216,7 @@ class GodotServer {
|
|
|
38043
38216
|
if (!engine.ok) {
|
|
38044
38217
|
return engine.response;
|
|
38045
38218
|
}
|
|
38046
|
-
const application = parseProjectGodot(
|
|
38219
|
+
const application = parseProjectGodot(readFileSync17(project.value.file, "utf8"))["application"] ?? {};
|
|
38047
38220
|
const name = application["config/name"];
|
|
38048
38221
|
const mainScene = application["run/main_scene"];
|
|
38049
38222
|
const info = {
|
|
@@ -38104,18 +38277,18 @@ class GodotServer {
|
|
|
38104
38277
|
if (!engine.ok) {
|
|
38105
38278
|
return engine.response;
|
|
38106
38279
|
}
|
|
38107
|
-
const folder =
|
|
38280
|
+
const folder = dirname11(output.absolutePath);
|
|
38108
38281
|
try {
|
|
38109
|
-
|
|
38282
|
+
mkdirSync10(folder, { recursive: true });
|
|
38110
38283
|
} catch (error) {
|
|
38111
|
-
return this.createErrorResponse(`Cannot create ${
|
|
38284
|
+
return this.createErrorResponse(`Cannot create ${dirname11(output.relativePath)} to export into: ${errorMessage(error)}`);
|
|
38112
38285
|
}
|
|
38113
38286
|
const debug = readBoolean(args, "debug") ?? false;
|
|
38114
|
-
const exportLogs = mkdtempSync3(
|
|
38287
|
+
const exportLogs = mkdtempSync3(join19(tmpdir5(), "gdharness-export-"));
|
|
38115
38288
|
const exportArgs = [
|
|
38116
38289
|
"--headless",
|
|
38117
38290
|
"--log-file",
|
|
38118
|
-
|
|
38291
|
+
join19(exportLogs, "engine.log"),
|
|
38119
38292
|
"--path",
|
|
38120
38293
|
project.value.path,
|
|
38121
38294
|
debug ? "--export-debug" : "--export-release",
|
|
@@ -38173,7 +38346,7 @@ class GodotServer {
|
|
|
38173
38346
|
return contained.response;
|
|
38174
38347
|
}
|
|
38175
38348
|
const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
|
|
38176
|
-
if (!existsSync14(
|
|
38349
|
+
if (!existsSync14(join19(project.value.path, runner))) {
|
|
38177
38350
|
return this.createErrorResponse(`gdUnit4 is not installed in this project: no ${runner}.`, [
|
|
38178
38351
|
"Install gdUnit4 under addons/gdUnit4, from https://github.com/godot-gdunit-labs/gdUnit4"
|
|
38179
38352
|
]);
|
|
@@ -38208,7 +38381,7 @@ class GodotServer {
|
|
|
38208
38381
|
];
|
|
38209
38382
|
const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 600000;
|
|
38210
38383
|
this.logDebug(`Running tests: ${engine.value} ${cmdArgs.join(" ")}`);
|
|
38211
|
-
const userData = mkdtempSync3(
|
|
38384
|
+
const userData = mkdtempSync3(join19(tmpdir5(), "gdharness-tests-"));
|
|
38212
38385
|
const run = this.spawnGame(engine.value, cmdArgs, userDataIn(userData));
|
|
38213
38386
|
const hung = await new Promise((resolve) => {
|
|
38214
38387
|
const timer = setTimeout(() => {
|
|
@@ -38224,14 +38397,14 @@ class GodotServer {
|
|
|
38224
38397
|
resolve(false);
|
|
38225
38398
|
});
|
|
38226
38399
|
});
|
|
38227
|
-
const reportsDir =
|
|
38400
|
+
const reportsDir = join19(project.value.path, ".godot", ...ours.split("/"));
|
|
38228
38401
|
let report = null;
|
|
38229
38402
|
let reportProblem = null;
|
|
38230
38403
|
try {
|
|
38231
38404
|
const written = existsSync14(reportsDir) ? readdirSync7(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
|
|
38232
38405
|
const newest = written.at(-1);
|
|
38233
38406
|
if (newest !== undefined) {
|
|
38234
|
-
report = parseJUnit(
|
|
38407
|
+
report = parseJUnit(readFileSync17(join19(reportsDir, newest, "results.xml"), "utf8"));
|
|
38235
38408
|
}
|
|
38236
38409
|
} catch (error) {
|
|
38237
38410
|
reportProblem = errorMessage(error);
|
|
@@ -38371,7 +38544,7 @@ class GodotServer {
|
|
|
38371
38544
|
return null;
|
|
38372
38545
|
}
|
|
38373
38546
|
try {
|
|
38374
|
-
return
|
|
38547
|
+
return readFileSync17(contained.absolutePath, "utf8");
|
|
38375
38548
|
} catch {
|
|
38376
38549
|
return null;
|
|
38377
38550
|
}
|
|
@@ -38413,7 +38586,7 @@ class GodotServer {
|
|
|
38413
38586
|
return null;
|
|
38414
38587
|
}
|
|
38415
38588
|
try {
|
|
38416
|
-
return
|
|
38589
|
+
return readFileSync17(contained.absolutePath, "utf8");
|
|
38417
38590
|
} catch {
|
|
38418
38591
|
return null;
|
|
38419
38592
|
}
|
|
@@ -38462,6 +38635,20 @@ class GodotServer {
|
|
|
38462
38635
|
}
|
|
38463
38636
|
return this.dapClient;
|
|
38464
38637
|
}
|
|
38638
|
+
dapHoldingBreakpointsOf(projectPath) {
|
|
38639
|
+
const client = this.dap();
|
|
38640
|
+
if (this.breakpointsSeededOn !== client) {
|
|
38641
|
+
client.holdBreakpoints(readBreakpointNote(projectPath));
|
|
38642
|
+
this.breakpointsSeededOn = client;
|
|
38643
|
+
}
|
|
38644
|
+
return client;
|
|
38645
|
+
}
|
|
38646
|
+
breakpointsAsProjectSpells(projectPath, held) {
|
|
38647
|
+
return held.map((one) => ({
|
|
38648
|
+
scriptPath: projectSpelling(projectPath, one.scriptPath),
|
|
38649
|
+
lines: one.lines
|
|
38650
|
+
}));
|
|
38651
|
+
}
|
|
38465
38652
|
writeToTranscript(line) {
|
|
38466
38653
|
const run = this.activeProcess;
|
|
38467
38654
|
if (run?.throughEditor !== true || run.transcript === null) {
|
|
@@ -38551,8 +38738,12 @@ class GodotServer {
|
|
|
38551
38738
|
async handleEditorStatus() {
|
|
38552
38739
|
const located = await this.locator.find();
|
|
38553
38740
|
const announced = runtimesAnnounced();
|
|
38554
|
-
const
|
|
38555
|
-
const
|
|
38741
|
+
const pinged = Promise.all(announced.running.map(async (endpoint) => {
|
|
38742
|
+
const held = this.knownHoldOn(endpoint);
|
|
38743
|
+
const reply = held === null ? await runtimeRequest(endpoint, "ping", {}, HOLD_PING_MS) : {
|
|
38744
|
+
ok: false,
|
|
38745
|
+
message: `The game is held by the editor's debugger, ${describeHalt(held)}, and answers no runtime call while it is. debug_control continue lets it go.`
|
|
38746
|
+
};
|
|
38556
38747
|
return {
|
|
38557
38748
|
pid: endpoint.pid,
|
|
38558
38749
|
port: endpoint.port,
|
|
@@ -38561,8 +38752,13 @@ class GodotServer {
|
|
|
38561
38752
|
problem: reply.ok ? null : reply.message
|
|
38562
38753
|
};
|
|
38563
38754
|
}));
|
|
38564
|
-
const
|
|
38565
|
-
|
|
38755
|
+
const asked = (async () => {
|
|
38756
|
+
const playing = await this.editorPlayingState();
|
|
38757
|
+
await this.pickUpWhatTheEditorIsPlaying();
|
|
38758
|
+
const current = this.currentRun();
|
|
38759
|
+
return { playing, hold: current === null ? {} : await this.holdOf(current) };
|
|
38760
|
+
})();
|
|
38761
|
+
const [games, { playing, hold }] = await Promise.all([pinged, asked]);
|
|
38566
38762
|
return this.jsonTextResponse({
|
|
38567
38763
|
editor: {
|
|
38568
38764
|
...this.getEditorStatusPayload(),
|
|
@@ -38576,6 +38772,7 @@ class GodotServer {
|
|
|
38576
38772
|
game: {
|
|
38577
38773
|
processActive: runIsUp(this.currentRun(), playing === null ? null : playing.playing),
|
|
38578
38774
|
playingInEditor: playing,
|
|
38775
|
+
...hold,
|
|
38579
38776
|
runtimeConnected: games.some((game) => game.reachable),
|
|
38580
38777
|
runtimes: games,
|
|
38581
38778
|
unreadable: announced.unspoken.map((game) => ({
|
|
@@ -38593,7 +38790,7 @@ class GodotServer {
|
|
|
38593
38790
|
return null;
|
|
38594
38791
|
}
|
|
38595
38792
|
try {
|
|
38596
|
-
return settingKeys(
|
|
38793
|
+
return settingKeys(readFileSync17(join19(projectPath, "project.godot"), "utf8"));
|
|
38597
38794
|
} catch {
|
|
38598
38795
|
return null;
|
|
38599
38796
|
}
|
|
@@ -38836,7 +39033,7 @@ class GodotServer {
|
|
|
38836
39033
|
return runIsUp(run, editorSays);
|
|
38837
39034
|
}
|
|
38838
39035
|
async runtimeUp(projectPath, before, budgetMs, withArgs) {
|
|
38839
|
-
if (!existsSync14(
|
|
39036
|
+
if (!existsSync14(join19(projectPath, RUNTIME_AUTOLOAD2.path))) {
|
|
38840
39037
|
return runtimeVerdict(null, { addon: false, budgetMs, heldAt: null, running: false, withArgs });
|
|
38841
39038
|
}
|
|
38842
39039
|
const endpoint = await announcedSince(projectPath, before, {
|
|
@@ -38874,6 +39071,7 @@ class GodotServer {
|
|
|
38874
39071
|
"Closing the other editor, or moving its debug adapter port in its editor settings, frees this one"
|
|
38875
39072
|
]);
|
|
38876
39073
|
}
|
|
39074
|
+
const breakpoints = await this.dapHoldingBreakpointsOf(projectPath).reapplyBreakpoints();
|
|
38877
39075
|
const answer = await this.handleViaBridge("play_scene", scene === null ? {} : { scenePath: `res://${scene}` });
|
|
38878
39076
|
if (answer.isError === true) {
|
|
38879
39077
|
return answer;
|
|
@@ -38903,6 +39101,11 @@ class GodotServer {
|
|
|
38903
39101
|
refreshedClasses,
|
|
38904
39102
|
debugPort: readNumber(asParams(JSON.parse(answer.content[0]?.text ?? "{}")), "debugPort"),
|
|
38905
39103
|
endedPreviousRun: ended ?? undefined,
|
|
39104
|
+
breakpoints: breakpoints.applied.length === 0 ? undefined : this.breakpointsAsProjectSpells(projectPath, breakpoints.applied),
|
|
39105
|
+
breakpointsRefused: breakpoints.refused.length === 0 ? undefined : breakpoints.refused.map((one) => ({
|
|
39106
|
+
scriptPath: projectSpelling(projectPath, one.scriptPath),
|
|
39107
|
+
reason: one.reason
|
|
39108
|
+
})),
|
|
38906
39109
|
runtime: await this.runtimeUp(projectPath, alreadyPlaying, runtimeWaitMs, false),
|
|
38907
39110
|
message: "The editor is playing it, so its debugger holds it: the debug_* tools can reach it, " + "editor_output reads its console through the debug adapter, and editor_run stop ends it." + (ended === null ? "" : ` The run that was going, pid ${ended}, was ended to start this one; its output is no longer what editor_output answers about.`)
|
|
38908
39111
|
});
|
|
@@ -39100,7 +39303,7 @@ class GodotServer {
|
|
|
39100
39303
|
return;
|
|
39101
39304
|
}
|
|
39102
39305
|
try {
|
|
39103
|
-
await this.dap().
|
|
39306
|
+
await this.dap().attach();
|
|
39104
39307
|
} catch (error) {
|
|
39105
39308
|
this.logDebug(`Picked up an editor-played run without its adapter: ${errorMessage(error)}`);
|
|
39106
39309
|
}
|
|
@@ -39120,8 +39323,16 @@ class GodotServer {
|
|
|
39120
39323
|
brokeOn: null,
|
|
39121
39324
|
pickedUpPlaying: true
|
|
39122
39325
|
};
|
|
39326
|
+
const announced = this.theOneAnnouncedForOurProject();
|
|
39327
|
+
if (announced !== undefined) {
|
|
39328
|
+
picked.announcedPid = announced.pid;
|
|
39329
|
+
}
|
|
39123
39330
|
this.activeProcess = picked;
|
|
39124
39331
|
}
|
|
39332
|
+
theOneAnnouncedForOurProject() {
|
|
39333
|
+
const ofProject = this.allAnnouncedForOurProject(runtimesAnnounced().running);
|
|
39334
|
+
return ofProject.length === 1 ? ofProject[0] : undefined;
|
|
39335
|
+
}
|
|
39125
39336
|
adoptRecordedRun() {
|
|
39126
39337
|
const record = readRunRecord();
|
|
39127
39338
|
if (record === null || !this.couldBeOurs(record.projectPath)) {
|
|
@@ -39276,7 +39487,7 @@ class GodotServer {
|
|
|
39276
39487
|
contains: readNonEmptyString(args, "contains"),
|
|
39277
39488
|
limit: readPositiveNumber(args, "limit") ?? 200
|
|
39278
39489
|
});
|
|
39279
|
-
const
|
|
39490
|
+
const hold = await this.holdOf(run);
|
|
39280
39491
|
const wanted = readBoolean(args, "cpu") ?? false;
|
|
39281
39492
|
const cpuSeconds = wanted && run.pid !== null && stillRunning(run) ? await cpuSecondsOf(run.pid) : undefined;
|
|
39282
39493
|
const notes = [];
|
|
@@ -39313,7 +39524,7 @@ class GodotServer {
|
|
|
39313
39524
|
warnings: run.log.count("warning"),
|
|
39314
39525
|
clean: run.consoleLost === true ? undefined : run.log.count("error") === 0,
|
|
39315
39526
|
consoleLost: run.consoleLost === true ? true : undefined,
|
|
39316
|
-
|
|
39527
|
+
...hold,
|
|
39317
39528
|
endedUnwatched: run.endedUnwatched === true ? true : undefined,
|
|
39318
39529
|
endedBy: stillRunning(run) ? undefined : run.endedHere ?? null,
|
|
39319
39530
|
project: !run.throughEditor && run.process === null ? run.projectPath ?? undefined : undefined,
|
|
@@ -39496,6 +39707,50 @@ class GodotServer {
|
|
|
39496
39707
|
const override = Number.parseInt(envValue("GDHARNESS_RUNTIME_TIMEOUT_MS") ?? "", 10);
|
|
39497
39708
|
return Number.isInteger(override) && override > 0 ? override : 1e4;
|
|
39498
39709
|
}
|
|
39710
|
+
knownHoldOn(endpoint) {
|
|
39711
|
+
const run = this.activeProcess;
|
|
39712
|
+
const session = this.dapClient;
|
|
39713
|
+
if (run === null || !run.throughEditor || !stillRunning(run) || session === null) {
|
|
39714
|
+
return null;
|
|
39715
|
+
}
|
|
39716
|
+
const its = run.announcedPid ?? this.theOneAnnouncedForOurProject()?.pid;
|
|
39717
|
+
if (its !== endpoint.pid || !session.holdIsKnown()) {
|
|
39718
|
+
return null;
|
|
39719
|
+
}
|
|
39720
|
+
return session.whereItStopped();
|
|
39721
|
+
}
|
|
39722
|
+
async holdOf(run) {
|
|
39723
|
+
if (!run.throughEditor || !stillRunning(run)) {
|
|
39724
|
+
return { heldAt: null };
|
|
39725
|
+
}
|
|
39726
|
+
const session = this.dapClient;
|
|
39727
|
+
if (session?.holdIsKnown()) {
|
|
39728
|
+
return { heldAt: session.whereItStopped() };
|
|
39729
|
+
}
|
|
39730
|
+
const endpoint = run.announcedPid !== undefined ? runtimesAnnounced().running.find((one) => one.pid === run.announcedPid) : this.theOneAnnouncedForOurProject();
|
|
39731
|
+
if (endpoint === undefined) {
|
|
39732
|
+
return {
|
|
39733
|
+
heldAt: undefined,
|
|
39734
|
+
heldUnknown: true,
|
|
39735
|
+
heldNote: "Whether this run is held at a breakpoint cannot be told from here: this server connected to the debugger after any stop, the adapter reports a stop only to the sessions connected when it happened, and the run has announced no runtime to ask instead. A game that draws nothing and answers nothing is held; debug_control continue lets a held game go."
|
|
39736
|
+
};
|
|
39737
|
+
}
|
|
39738
|
+
const reply = await runtimeRequest(endpoint, "ping", {}, HOLD_PING_MS);
|
|
39739
|
+
if (reply.ok) {
|
|
39740
|
+
session?.learnedRunning();
|
|
39741
|
+
return { heldAt: session?.whereItStopped() ?? null };
|
|
39742
|
+
}
|
|
39743
|
+
if (reply.reason === "busy") {
|
|
39744
|
+
return {
|
|
39745
|
+
heldAt: {
|
|
39746
|
+
reason: "unanswered",
|
|
39747
|
+
description: `the game accepted a runtime connection and did not answer a ping within ${HOLD_PING_MS}ms, which is what a game held at a breakpoint does, and what one stuck in a long frame does. This server connected to the debugger after the stop, so the adapter will not show it the stack; debug_control continue lets a held game go`,
|
|
39748
|
+
text: ""
|
|
39749
|
+
}
|
|
39750
|
+
};
|
|
39751
|
+
}
|
|
39752
|
+
return { heldAt: undefined, heldUnknown: true, heldNote: reply.message };
|
|
39753
|
+
}
|
|
39499
39754
|
async handleRuntimeCommand(command, args, timeoutMs = this.runtimeTimeoutMs()) {
|
|
39500
39755
|
const { op: _op, projectPath, ...params } = asParams(args);
|
|
39501
39756
|
const announced = runtimesAnnounced();
|
|
@@ -39503,9 +39758,17 @@ class GodotServer {
|
|
|
39503
39758
|
if ("problem" in choice) {
|
|
39504
39759
|
return this.createErrorResponse(choice.problem);
|
|
39505
39760
|
}
|
|
39761
|
+
const held = this.knownHoldOn(choice.endpoint);
|
|
39762
|
+
if (held !== null) {
|
|
39763
|
+
return this.createErrorResponse(`The game (pid ${choice.endpoint.pid}) is held by the editor's debugger, ${describeHalt(held)}, and answers no runtime call while it is.`, [
|
|
39764
|
+
"debug_control continue lets it go, after which this call answers",
|
|
39765
|
+
"debug_state stack reads where it is held, and debug_state scopes what it is holding there",
|
|
39766
|
+
"editor_output says heldAt for the run, and editor_run stop ends it"
|
|
39767
|
+
]);
|
|
39768
|
+
}
|
|
39506
39769
|
const expectsScreenshot = command === "capture_screenshot" || command === "capture_viewport";
|
|
39507
|
-
const screenshotDir = expectsScreenshot ? mkdtempSync3(
|
|
39508
|
-
const screenshotPath = screenshotDir ?
|
|
39770
|
+
const screenshotDir = expectsScreenshot ? mkdtempSync3(join19(tmpdir5(), "gdharness-runtime-screenshot-")) : null;
|
|
39771
|
+
const screenshotPath = screenshotDir ? join19(screenshotDir, "capture.png") : null;
|
|
39509
39772
|
try {
|
|
39510
39773
|
const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs);
|
|
39511
39774
|
if (!reply.ok) {
|
|
@@ -39526,7 +39789,7 @@ class GodotServer {
|
|
|
39526
39789
|
return {
|
|
39527
39790
|
content: [
|
|
39528
39791
|
{ type: "text", text: `Screenshot captured: ${dimensions}` },
|
|
39529
|
-
{ type: "image", data:
|
|
39792
|
+
{ type: "image", data: readFileSync17(screenshotPath).toString("base64"), mimeType: "image/png" }
|
|
39530
39793
|
]
|
|
39531
39794
|
};
|
|
39532
39795
|
} finally {
|
|
@@ -39592,11 +39855,12 @@ async function runGodotServer() {
|
|
|
39592
39855
|
const server = new GodotServer;
|
|
39593
39856
|
await server.run();
|
|
39594
39857
|
}
|
|
39595
|
-
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run6, __dirname2, EDITOR_RESTART_TIMEOUT_MS = 90000, SLOWEST_FRAME_RATE = 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/";
|
|
39858
|
+
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run6, __dirname2, EDITOR_RESTART_TIMEOUT_MS = 90000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 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/";
|
|
39596
39859
|
var init_server3 = __esm(() => {
|
|
39597
39860
|
init_mcp();
|
|
39598
39861
|
init_stdio2();
|
|
39599
39862
|
init_types();
|
|
39863
|
+
init_breakpoint_note();
|
|
39600
39864
|
init_bridge_announce();
|
|
39601
39865
|
init_class_cache();
|
|
39602
39866
|
init_config_pin();
|
|
@@ -39624,7 +39888,7 @@ var init_server3 = __esm(() => {
|
|
|
39624
39888
|
init_tool_definitions();
|
|
39625
39889
|
init_update_check();
|
|
39626
39890
|
run6 = promisify6(execFile6);
|
|
39627
|
-
__dirname2 =
|
|
39891
|
+
__dirname2 = dirname11(fileURLToPath4(import.meta.url));
|
|
39628
39892
|
VALUE_NAMES = {
|
|
39629
39893
|
string: "a string",
|
|
39630
39894
|
number: "a number",
|
|
@@ -39663,7 +39927,7 @@ var init_server3 = __esm(() => {
|
|
|
39663
39927
|
|
|
39664
39928
|
// src/cli.ts
|
|
39665
39929
|
import { existsSync as existsSync15 } from "fs";
|
|
39666
|
-
import { join as
|
|
39930
|
+
import { join as join20, resolve as resolve5 } from "path";
|
|
39667
39931
|
|
|
39668
39932
|
// src/errors.ts
|
|
39669
39933
|
class Refusal extends Error {
|
|
@@ -41231,8 +41495,10 @@ its own, which is the measured cure and needs no change to the declaring script;
|
|
|
41231
41495
|
restart\` also does it and costs a window.
|
|
41232
41496
|
|
|
41233
41497
|
The scan writes \`.godot/global_script_class_cache.cfg\` from the list the editor is holding, so
|
|
41234
|
-
a class it cannot resolve
|
|
41235
|
-
|
|
41498
|
+
a class it cannot resolve would go out of that file with it. The answer names that loss under
|
|
41499
|
+
\`cacheLost\` and rebuilds the cache from the declarations on disk, naming what came back under
|
|
41500
|
+
\`cacheRestored\`, so no fresh engine, CI run or clone inherits the short file. That is the same
|
|
41501
|
+
class \`unseenByEditor\` names, and the loss is not a reason to refuse the scan.
|
|
41236
41502
|
|
|
41237
41503
|
A diagnostic about a member rather than a type, \`Static function "x()" not found in base "Y"\`
|
|
41238
41504
|
about something the engine compiles, is a different thing and is no longer stale here: each ask
|
|
@@ -41351,7 +41617,7 @@ class UsageError extends Refusal {
|
|
|
41351
41617
|
function projectArgument(at) {
|
|
41352
41618
|
const named = args.slice(at).find((value) => !value.startsWith("--"));
|
|
41353
41619
|
const projectPath = resolve5(named ?? ".");
|
|
41354
|
-
if (!existsSync15(
|
|
41620
|
+
if (!existsSync15(join20(projectPath, "project.godot"))) {
|
|
41355
41621
|
throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
|
|
41356
41622
|
}
|
|
41357
41623
|
return projectPath;
|
|
@@ -41390,7 +41656,7 @@ async function registerRuntime(godot, projectPath) {
|
|
|
41390
41656
|
return;
|
|
41391
41657
|
}
|
|
41392
41658
|
console.log(`${RUNTIME_AUTOLOAD.name} autoload left at ${named}, which this project registers itself`);
|
|
41393
|
-
if (!existsSync15(
|
|
41659
|
+
if (!existsSync15(join20(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
|
|
41394
41660
|
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}`);
|
|
41395
41661
|
}
|
|
41396
41662
|
}
|