gdharness 0.5.7 → 0.5.9
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 +134 -39
- package/build/godot/addons/auto_reload/auto_reload.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/bridge_client.gd +143 -2
- package/build/godot/addons/gdharness_editor/bridge_client.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/plugin.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tool_executor.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/play_tools.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +1 -0
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/runtime_capture.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +76 -0
- package/build/godot/addons/gdharness_runtime/runtime_input.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +9 -0
- package/build/godot/addons/gdharness_runtime/runtime_values.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd.uid +1 -0
- package/build/godot/operations/serialisation.gd +8 -5
- package/build/index.js +131 -45
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -15821,7 +15821,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
15821
15821
|
},
|
|
15822
15822
|
{
|
|
15823
15823
|
name: "runtime_input",
|
|
15824
|
-
description: "Input to the running game: a whole click on a Control named by path, or a raw action, key, mouse button or mouse motion. All of it works headless, where the window is 64 by 64 and the GUI only takes what is inside it.",
|
|
15824
|
+
description: "Input to the running game: a whole click on a Control named by path, typing into whatever has the focus, or a raw action, key, mouse button or mouse motion. All of it works headless, where the window is 64 by 64 and the GUI only takes what is inside it.",
|
|
15825
15825
|
parameters: {
|
|
15826
15826
|
projectPath: RUNNING_PROJECT_PATH,
|
|
15827
15827
|
nodePath: { type: "string", description: "click: the Control to click, at its centre." },
|
|
@@ -15829,6 +15829,10 @@ var init_tool_definitions = __esm(() => {
|
|
|
15829
15829
|
pressed: { type: "boolean", description: "Press or release. Default true." },
|
|
15830
15830
|
strength: { type: "number", description: "action: 0 to 1. Default 1." },
|
|
15831
15831
|
keycode: { type: "string", description: 'key: the key name, such as "Space" or "A".' },
|
|
15832
|
+
text: {
|
|
15833
|
+
type: "string",
|
|
15834
|
+
description: "text: what to type. A newline is Enter and a tab is Tab."
|
|
15835
|
+
},
|
|
15832
15836
|
shift: { type: "boolean" },
|
|
15833
15837
|
ctrl: { type: "boolean" },
|
|
15834
15838
|
alt: { type: "boolean" },
|
|
@@ -15851,6 +15855,10 @@ var init_tool_definitions = __esm(() => {
|
|
|
15851
15855
|
},
|
|
15852
15856
|
action: { summary: "press or release an action", requires: ["action"] },
|
|
15853
15857
|
key: { summary: "press or release a key", requires: ["keycode"] },
|
|
15858
|
+
text: {
|
|
15859
|
+
summary: "type a string wherever the focus is, a character at a time",
|
|
15860
|
+
requires: ["text"]
|
|
15861
|
+
},
|
|
15854
15862
|
mouse_click: { summary: "one mouse button event at a position", requires: ["x", "y"] },
|
|
15855
15863
|
mouse_motion: { summary: "move the mouse to a position", requires: ["x", "y"] }
|
|
15856
15864
|
}
|
|
@@ -30638,6 +30646,47 @@ var init_stdio2 = __esm(() => {
|
|
|
30638
30646
|
init_stdio();
|
|
30639
30647
|
});
|
|
30640
30648
|
|
|
30649
|
+
// src/bridge-announce.ts
|
|
30650
|
+
import { mkdirSync as mkdirSync5, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "node:fs";
|
|
30651
|
+
import { dirname as dirname6, join as join11 } from "node:path";
|
|
30652
|
+
function announcementPath(projectPath) {
|
|
30653
|
+
return join11(projectPath, ".godot", "gdharness-bridge.json");
|
|
30654
|
+
}
|
|
30655
|
+
function announceBridge(projectPath, bridge) {
|
|
30656
|
+
const path = announcementPath(projectPath);
|
|
30657
|
+
try {
|
|
30658
|
+
mkdirSync5(dirname6(path), { recursive: true });
|
|
30659
|
+
const announcement = {
|
|
30660
|
+
protocol: BRIDGE_ANNOUNCE_PROTOCOL,
|
|
30661
|
+
host: bridge.host,
|
|
30662
|
+
port: bridge.port,
|
|
30663
|
+
pid: process.pid,
|
|
30664
|
+
version: bridge.version,
|
|
30665
|
+
startedAt: new Date().toISOString()
|
|
30666
|
+
};
|
|
30667
|
+
writeFileSync7(path, `${JSON.stringify(announcement, null, 2)}
|
|
30668
|
+
`, "utf8");
|
|
30669
|
+
return path;
|
|
30670
|
+
} catch (error) {
|
|
30671
|
+
console.error(`[SERVER] Could not announce the editor bridge at ${path}: ${errorMessage(error)}`);
|
|
30672
|
+
return null;
|
|
30673
|
+
}
|
|
30674
|
+
}
|
|
30675
|
+
function withdrawBridge(path) {
|
|
30676
|
+
if (path === null) {
|
|
30677
|
+
return;
|
|
30678
|
+
}
|
|
30679
|
+
try {
|
|
30680
|
+
rmSync6(path, { force: true });
|
|
30681
|
+
} catch (error) {
|
|
30682
|
+
console.error(`[SERVER] Could not withdraw the editor bridge announcement: ${errorMessage(error)}`);
|
|
30683
|
+
}
|
|
30684
|
+
}
|
|
30685
|
+
var BRIDGE_ANNOUNCE_PROTOCOL = 1;
|
|
30686
|
+
var init_bridge_announce = __esm(() => {
|
|
30687
|
+
init_errors();
|
|
30688
|
+
});
|
|
30689
|
+
|
|
30641
30690
|
// src/framing.ts
|
|
30642
30691
|
function frame(message) {
|
|
30643
30692
|
const body = Buffer.from(JSON.stringify(message), "utf8");
|
|
@@ -34105,8 +34154,8 @@ function resolveDefaultBridgeHost() {
|
|
|
34105
34154
|
const host = process.env["GDHARNESS_BRIDGE_HOST"]?.trim();
|
|
34106
34155
|
return host === undefined || host === "" ? DEFAULT_HOST : host;
|
|
34107
34156
|
}
|
|
34108
|
-
function getDefaultBridge() {
|
|
34109
|
-
defaultBridge ??= new GodotBridge(resolveDefaultBridgePort(), resolveDefaultBridgeHost());
|
|
34157
|
+
function getDefaultBridge(mayMove = false) {
|
|
34158
|
+
defaultBridge ??= new GodotBridge(resolveDefaultBridgePort(), resolveDefaultBridgeHost(), DEFAULT_TIMEOUT_MS, mayMove);
|
|
34110
34159
|
return defaultBridge;
|
|
34111
34160
|
}
|
|
34112
34161
|
var DEFAULT_PORT = 6505, DEFAULT_HOST = "127.0.0.1", DEFAULT_TIMEOUT_MS = 30000, KEEPALIVE_INTERVAL_MS = 1e4, SECOND_CONNECTION_CLOSE_CODE = 4000, GodotBridge, defaultBridge = null;
|
|
@@ -34122,19 +34171,37 @@ var init_godot_bridge = __esm(() => {
|
|
|
34122
34171
|
connectionInfo = null;
|
|
34123
34172
|
pendingRequests = new Map;
|
|
34124
34173
|
resourceQueues = new Map;
|
|
34125
|
-
|
|
34174
|
+
wantedPort;
|
|
34175
|
+
boundPort = null;
|
|
34126
34176
|
host;
|
|
34127
34177
|
timeoutMs;
|
|
34128
|
-
|
|
34178
|
+
mayMove;
|
|
34179
|
+
constructor(port = DEFAULT_PORT, host = DEFAULT_HOST, timeoutMs = DEFAULT_TIMEOUT_MS, mayMove = false) {
|
|
34129
34180
|
super();
|
|
34130
|
-
this.
|
|
34181
|
+
this.wantedPort = port;
|
|
34131
34182
|
this.host = host;
|
|
34132
34183
|
this.timeoutMs = timeoutMs;
|
|
34184
|
+
this.mayMove = mayMove;
|
|
34133
34185
|
}
|
|
34134
|
-
|
|
34186
|
+
get port() {
|
|
34187
|
+
return this.boundPort ?? this.wantedPort;
|
|
34188
|
+
}
|
|
34189
|
+
async start() {
|
|
34135
34190
|
if (this.httpServer) {
|
|
34136
|
-
return
|
|
34191
|
+
return;
|
|
34192
|
+
}
|
|
34193
|
+
try {
|
|
34194
|
+
await this.listenOn(this.wantedPort);
|
|
34195
|
+
} catch (error) {
|
|
34196
|
+
const held = error instanceof Error && "code" in error && error.code === "EADDRINUSE";
|
|
34197
|
+
if (!held || !this.mayMove) {
|
|
34198
|
+
throw error;
|
|
34199
|
+
}
|
|
34200
|
+
this.log("warn", `Editor bridge port ${this.wantedPort} is held; taking another one.`);
|
|
34201
|
+
await this.listenOn(0);
|
|
34137
34202
|
}
|
|
34203
|
+
}
|
|
34204
|
+
listenOn(port) {
|
|
34138
34205
|
return new Promise((resolve, reject) => {
|
|
34139
34206
|
const server = http.createServer((_req, res) => {
|
|
34140
34207
|
res.writeHead(404);
|
|
@@ -34158,6 +34225,8 @@ var init_godot_bridge = __esm(() => {
|
|
|
34158
34225
|
settled = true;
|
|
34159
34226
|
this.httpServer = server;
|
|
34160
34227
|
this.godotWss = godotWss;
|
|
34228
|
+
const bound = server.address();
|
|
34229
|
+
this.boundPort = typeof bound === "object" && bound !== null ? bound.port : port;
|
|
34161
34230
|
this.log("info", `Editor bridge listening on ${this.host}:${this.port}`);
|
|
34162
34231
|
resolve();
|
|
34163
34232
|
});
|
|
@@ -34174,7 +34243,7 @@ var init_godot_bridge = __esm(() => {
|
|
|
34174
34243
|
godotWss.on("error", (error) => {
|
|
34175
34244
|
this.log("error", `Godot WebSocket server error: ${error.message}`);
|
|
34176
34245
|
});
|
|
34177
|
-
server.listen(
|
|
34246
|
+
server.listen(port, this.host);
|
|
34178
34247
|
});
|
|
34179
34248
|
}
|
|
34180
34249
|
async stop() {
|
|
@@ -34208,6 +34277,7 @@ var init_godot_bridge = __esm(() => {
|
|
|
34208
34277
|
this.httpServer = null;
|
|
34209
34278
|
}
|
|
34210
34279
|
await Promise.all(closeTasks);
|
|
34280
|
+
this.boundPort = null;
|
|
34211
34281
|
this.connectionInfo = null;
|
|
34212
34282
|
this.log("info", "WebSocket bridge stopped");
|
|
34213
34283
|
}
|
|
@@ -34857,7 +34927,7 @@ var init_junit = __esm(() => {
|
|
|
34857
34927
|
import { realpathSync } from "node:fs";
|
|
34858
34928
|
import { readFile, realpath } from "node:fs/promises";
|
|
34859
34929
|
import { createConnection as createConnection2 } from "node:net";
|
|
34860
|
-
import { dirname as
|
|
34930
|
+
import { dirname as dirname7, resolve as resolve3 } from "node:path";
|
|
34861
34931
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
34862
34932
|
function diagnosticsKey(uri) {
|
|
34863
34933
|
let decoded;
|
|
@@ -34983,7 +35053,7 @@ class GodotLSPClient {
|
|
|
34983
35053
|
if (this.initialized) {
|
|
34984
35054
|
return;
|
|
34985
35055
|
}
|
|
34986
|
-
const rootPath = this.rootPath ??
|
|
35056
|
+
const rootPath = this.rootPath ?? dirname7(resolve3(filePath));
|
|
34987
35057
|
await this.initialize(rootPath);
|
|
34988
35058
|
}
|
|
34989
35059
|
async sendRequest(method, params) {
|
|
@@ -35358,7 +35428,7 @@ var init_lsp_client = __esm(() => {
|
|
|
35358
35428
|
|
|
35359
35429
|
// src/project-scan.ts
|
|
35360
35430
|
import { readdirSync as readdirSync4, readFileSync as readFileSync9 } from "node:fs";
|
|
35361
|
-
import { join as
|
|
35431
|
+
import { join as join12 } from "node:path";
|
|
35362
35432
|
function projectStructure(projectPath) {
|
|
35363
35433
|
const structure = { scenes: 0, scripts: 0, assets: 0, other: 0 };
|
|
35364
35434
|
const visit = (directory) => {
|
|
@@ -35367,7 +35437,7 @@ function projectStructure(projectPath) {
|
|
|
35367
35437
|
continue;
|
|
35368
35438
|
}
|
|
35369
35439
|
if (entry.isDirectory()) {
|
|
35370
|
-
visit(
|
|
35440
|
+
visit(join12(directory, entry.name));
|
|
35371
35441
|
} else if (entry.isFile()) {
|
|
35372
35442
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
35373
35443
|
if (extension === "tscn") {
|
|
@@ -35409,7 +35479,7 @@ function searchProject(projectPath, options) {
|
|
|
35409
35479
|
if (SKIPPED.has(entry.name)) {
|
|
35410
35480
|
continue;
|
|
35411
35481
|
}
|
|
35412
|
-
const entryPath =
|
|
35482
|
+
const entryPath = join12(directory, entry.name);
|
|
35413
35483
|
if (entry.isDirectory()) {
|
|
35414
35484
|
visit(entryPath);
|
|
35415
35485
|
continue;
|
|
@@ -35451,20 +35521,20 @@ var init_project_scan = __esm(() => {
|
|
|
35451
35521
|
import { existsSync as existsSync10, readdirSync as readdirSync5, readFileSync as readFileSync10, unlinkSync } from "node:fs";
|
|
35452
35522
|
import { createConnection as createConnection3 } from "node:net";
|
|
35453
35523
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
35454
|
-
import { join as
|
|
35524
|
+
import { join as join13, resolve as resolve4 } from "node:path";
|
|
35455
35525
|
function runtimeDirectory(variables = process.env) {
|
|
35456
35526
|
const explicit = envValue("GDHARNESS_RUNTIME_DIR", variables);
|
|
35457
35527
|
if (explicit) {
|
|
35458
35528
|
return explicit;
|
|
35459
35529
|
}
|
|
35460
35530
|
const perUser = envValue("XDG_RUNTIME_DIR", variables);
|
|
35461
|
-
return
|
|
35531
|
+
return join13(perUser ?? tmpdir4(), "gdharness");
|
|
35462
35532
|
}
|
|
35463
35533
|
function runtimeDirectories(variables = process.env) {
|
|
35464
35534
|
const candidates = [runtimeDirectory(variables)];
|
|
35465
35535
|
const fallbacks = process.platform === "win32" ? [envValue("SystemRoot", variables) ?? envValue("windir", variables) ?? "C:\\Windows"] : ["/tmp", "/var/tmp"];
|
|
35466
35536
|
for (const base of fallbacks) {
|
|
35467
|
-
candidates.push(
|
|
35537
|
+
candidates.push(join13(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
|
|
35468
35538
|
}
|
|
35469
35539
|
return [...new Set(candidates.map((path) => resolve4(path)))];
|
|
35470
35540
|
}
|
|
@@ -35527,7 +35597,7 @@ function announcedIn(directory) {
|
|
|
35527
35597
|
if (!match) {
|
|
35528
35598
|
continue;
|
|
35529
35599
|
}
|
|
35530
|
-
const file =
|
|
35600
|
+
const file = join13(directory, entry);
|
|
35531
35601
|
const pid = Number.parseInt(match[1] ?? "", 10);
|
|
35532
35602
|
const announced = processAlive(pid) ? parseAnnouncement(file, pid) : { kind: "rubbish" };
|
|
35533
35603
|
if (announced.kind === "rubbish") {
|
|
@@ -35673,9 +35743,9 @@ var init_runtime_client = __esm(() => {
|
|
|
35673
35743
|
|
|
35674
35744
|
// src/server.ts
|
|
35675
35745
|
import { execFile as execFile5, spawn } from "node:child_process";
|
|
35676
|
-
import { existsSync as existsSync11, mkdtempSync as mkdtempSync3, readdirSync as readdirSync6, readFileSync as readFileSync11, realpathSync as realpathSync2, rmSync as
|
|
35746
|
+
import { existsSync as existsSync11, mkdtempSync as mkdtempSync3, readdirSync as readdirSync6, readFileSync as readFileSync11, realpathSync as realpathSync2, rmSync as rmSync7 } from "node:fs";
|
|
35677
35747
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
35678
|
-
import { basename as basename2, dirname as
|
|
35748
|
+
import { basename as basename2, dirname as dirname8, join as join14, normalize as normalize3 } from "node:path";
|
|
35679
35749
|
import { setTimeout as delay2 } from "node:timers/promises";
|
|
35680
35750
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
35681
35751
|
import { promisify as promisify5 } from "node:util";
|
|
@@ -35721,7 +35791,7 @@ function camelCased(params) {
|
|
|
35721
35791
|
class GodotServer {
|
|
35722
35792
|
mcp;
|
|
35723
35793
|
locator = new GodotLocator2;
|
|
35724
|
-
operationsScript =
|
|
35794
|
+
operationsScript = join14(__dirname2, "godot", "operations", "godot_operations.gd");
|
|
35725
35795
|
godotBridge;
|
|
35726
35796
|
tools = buildToolDefinitions();
|
|
35727
35797
|
activeProcess = null;
|
|
@@ -35735,8 +35805,11 @@ class GodotServer {
|
|
|
35735
35805
|
bridgeRetry = null;
|
|
35736
35806
|
lastProjectPath = null;
|
|
35737
35807
|
shutdownInitiated = false;
|
|
35808
|
+
ownProject;
|
|
35809
|
+
announcedAt = null;
|
|
35738
35810
|
constructor() {
|
|
35739
|
-
this.
|
|
35811
|
+
this.ownProject = envValue("GDHARNESS_PROJECT") ?? null;
|
|
35812
|
+
this.godotBridge = getDefaultBridge(this.ownProject !== null);
|
|
35740
35813
|
this.mcp = new McpServer({ name: "gdharness", version: SERVER_VERSION }, { capabilities: { tools: {}, resources: {} } });
|
|
35741
35814
|
this.setupToolHandlers();
|
|
35742
35815
|
setupResourceHandlers(this.mcp, () => this.lastProjectPath);
|
|
@@ -35784,6 +35857,7 @@ class GodotServer {
|
|
|
35784
35857
|
this.bridgeStartupError = null;
|
|
35785
35858
|
const bridgeStatus = this.godotBridge.getStatus();
|
|
35786
35859
|
console.error(`[SERVER] Godot Editor Bridge started on ${bridgeStatus.host}:${bridgeStatus.port}`);
|
|
35860
|
+
this.announceTheBridge();
|
|
35787
35861
|
} catch (bridgeError) {
|
|
35788
35862
|
const code = bridgeError instanceof Error && "code" in bridgeError && typeof bridgeError.code === "string" ? bridgeError.code : null;
|
|
35789
35863
|
const reason = errorMessage(bridgeError);
|
|
@@ -35816,6 +35890,18 @@ class GodotServer {
|
|
|
35816
35890
|
this.stopTryingTheBridge();
|
|
35817
35891
|
const bridgeStatus = this.godotBridge.getStatus();
|
|
35818
35892
|
console.error(`[SERVER] Godot Editor Bridge came up on ${bridgeStatus.host}:${bridgeStatus.port}; editor tools are live.`);
|
|
35893
|
+
this.announceTheBridge();
|
|
35894
|
+
}
|
|
35895
|
+
announceTheBridge() {
|
|
35896
|
+
if (this.ownProject === null) {
|
|
35897
|
+
return;
|
|
35898
|
+
}
|
|
35899
|
+
const status = this.godotBridge.getStatus();
|
|
35900
|
+
this.announcedAt = announceBridge(this.ownProject, {
|
|
35901
|
+
host: status.host,
|
|
35902
|
+
port: status.port,
|
|
35903
|
+
version: SERVER_VERSION
|
|
35904
|
+
});
|
|
35819
35905
|
}
|
|
35820
35906
|
stopTryingTheBridge() {
|
|
35821
35907
|
if (this.bridgeRetry !== null) {
|
|
@@ -35826,6 +35912,8 @@ class GodotServer {
|
|
|
35826
35912
|
async cleanup() {
|
|
35827
35913
|
this.logDebug("Cleaning up resources");
|
|
35828
35914
|
this.stopTryingTheBridge();
|
|
35915
|
+
withdrawBridge(this.announcedAt);
|
|
35916
|
+
this.announcedAt = null;
|
|
35829
35917
|
if (this.activeProcess) {
|
|
35830
35918
|
this.activeProcess.process?.kill();
|
|
35831
35919
|
this.activeProcess = null;
|
|
@@ -36208,7 +36296,7 @@ class GodotServer {
|
|
|
36208
36296
|
if (path === undefined) {
|
|
36209
36297
|
return { ok: false, response: this.createErrorResponse("projectPath is required.") };
|
|
36210
36298
|
}
|
|
36211
|
-
const file =
|
|
36299
|
+
const file = join14(path, "project.godot");
|
|
36212
36300
|
if (!existsSync11(file)) {
|
|
36213
36301
|
return {
|
|
36214
36302
|
ok: false,
|
|
@@ -36473,7 +36561,7 @@ class GodotServer {
|
|
|
36473
36561
|
return contained.response;
|
|
36474
36562
|
}
|
|
36475
36563
|
const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
|
|
36476
|
-
if (!existsSync11(
|
|
36564
|
+
if (!existsSync11(join14(project.value.path, runner))) {
|
|
36477
36565
|
return this.createErrorResponse(`gdUnit4 is not installed in this project: no ${runner}.`, [
|
|
36478
36566
|
"Install gdUnit4 under addons/gdUnit4, from https://github.com/godot-gdunit-labs/gdUnit4"
|
|
36479
36567
|
]);
|
|
@@ -36506,7 +36594,7 @@ class GodotServer {
|
|
|
36506
36594
|
];
|
|
36507
36595
|
const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 600000;
|
|
36508
36596
|
this.logDebug(`Running tests: ${engine.value} ${cmdArgs.join(" ")}`);
|
|
36509
|
-
const userData = mkdtempSync3(
|
|
36597
|
+
const userData = mkdtempSync3(join14(tmpdir5(), "gdharness-tests-"));
|
|
36510
36598
|
const run = this.spawnGame(engine.value, cmdArgs, userDataIn(userData));
|
|
36511
36599
|
const hung = await new Promise((resolve) => {
|
|
36512
36600
|
const timer = setTimeout(() => {
|
|
@@ -36522,20 +36610,20 @@ class GodotServer {
|
|
|
36522
36610
|
resolve(false);
|
|
36523
36611
|
});
|
|
36524
36612
|
});
|
|
36525
|
-
const reportsDir =
|
|
36613
|
+
const reportsDir = join14(project.value.path, ".godot", "gdharness-reports");
|
|
36526
36614
|
let report = null;
|
|
36527
36615
|
let reportProblem = null;
|
|
36528
36616
|
try {
|
|
36529
36617
|
const written = existsSync11(reportsDir) ? readdirSync6(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
|
|
36530
36618
|
const newest = written.at(-1);
|
|
36531
36619
|
if (newest !== undefined) {
|
|
36532
|
-
report = parseJUnit(readFileSync11(
|
|
36620
|
+
report = parseJUnit(readFileSync11(join14(reportsDir, newest, "results.xml"), "utf8"));
|
|
36533
36621
|
}
|
|
36534
36622
|
} catch (error) {
|
|
36535
36623
|
reportProblem = errorMessage(error);
|
|
36536
36624
|
} finally {
|
|
36537
|
-
|
|
36538
|
-
|
|
36625
|
+
rmSync7(reportsDir, { recursive: true, force: true });
|
|
36626
|
+
rmSync7(userData, { recursive: true, force: true });
|
|
36539
36627
|
}
|
|
36540
36628
|
const engineEntries = run.log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries;
|
|
36541
36629
|
const verdicts = {
|
|
@@ -36648,6 +36736,7 @@ class GodotServer {
|
|
|
36648
36736
|
startupError: this.bridgeStartupError,
|
|
36649
36737
|
staleNote: stale ? addonMismatch(status.addonVersion, SERVER_VERSION) : undefined,
|
|
36650
36738
|
retryingBridge: this.bridgeRetry === null ? undefined : true,
|
|
36739
|
+
announcedAt: this.announcedAt ?? undefined,
|
|
36651
36740
|
note: isPortConflict ? "Bridge port is already in use. Another gdharness instance owns the editor bridge, so this server cannot reach the editor. Usually the server this one replaced, still on its way out." : undefined,
|
|
36652
36741
|
suggestion: isPortConflict ? `This server is asking for the port again every ${BRIDGE_RETRY_MS / 1000}s and takes it the moment the other one lets go, so editor tools come back on their own. Ask again, or end the older gdharness process to have it now.` : undefined
|
|
36653
36742
|
};
|
|
@@ -37043,8 +37132,8 @@ class GodotServer {
|
|
|
37043
37132
|
return this.createErrorResponse(choice.problem);
|
|
37044
37133
|
}
|
|
37045
37134
|
const expectsScreenshot = command === "capture_screenshot" || command === "capture_viewport";
|
|
37046
|
-
const screenshotDir = expectsScreenshot ? mkdtempSync3(
|
|
37047
|
-
const screenshotPath = screenshotDir ?
|
|
37135
|
+
const screenshotDir = expectsScreenshot ? mkdtempSync3(join14(tmpdir5(), "gdharness-runtime-screenshot-")) : null;
|
|
37136
|
+
const screenshotPath = screenshotDir ? join14(screenshotDir, "capture.png") : null;
|
|
37048
37137
|
try {
|
|
37049
37138
|
const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs);
|
|
37050
37139
|
if (!reply.ok) {
|
|
@@ -37070,7 +37159,7 @@ class GodotServer {
|
|
|
37070
37159
|
};
|
|
37071
37160
|
} finally {
|
|
37072
37161
|
if (screenshotDir) {
|
|
37073
|
-
|
|
37162
|
+
rmSync7(screenshotDir, { recursive: true, force: true });
|
|
37074
37163
|
}
|
|
37075
37164
|
}
|
|
37076
37165
|
}
|
|
@@ -37132,6 +37221,7 @@ var init_server3 = __esm(() => {
|
|
|
37132
37221
|
init_mcp();
|
|
37133
37222
|
init_stdio2();
|
|
37134
37223
|
init_types();
|
|
37224
|
+
init_bridge_announce();
|
|
37135
37225
|
init_class_cache();
|
|
37136
37226
|
init_dap_client();
|
|
37137
37227
|
init_errors();
|
|
@@ -37150,7 +37240,7 @@ var init_server3 = __esm(() => {
|
|
|
37150
37240
|
init_tool_definitions();
|
|
37151
37241
|
init_update_check();
|
|
37152
37242
|
run5 = promisify5(execFile5);
|
|
37153
|
-
__dirname2 =
|
|
37243
|
+
__dirname2 = dirname8(fileURLToPath4(import.meta.url));
|
|
37154
37244
|
PATH_SOLUTIONS = [
|
|
37155
37245
|
'Give the path relative to the project, such as "scenes/main.tscn" or "res://scenes/main.tscn"',
|
|
37156
37246
|
"Point projectPath at the project the file belongs to"
|
|
@@ -37208,7 +37298,7 @@ var init_server3 = __esm(() => {
|
|
|
37208
37298
|
|
|
37209
37299
|
// src/cli.ts
|
|
37210
37300
|
import { existsSync as existsSync12 } from "fs";
|
|
37211
|
-
import { join as
|
|
37301
|
+
import { join as join15, resolve as resolve5 } from "path";
|
|
37212
37302
|
|
|
37213
37303
|
// src/errors.ts
|
|
37214
37304
|
class Refusal extends Error {
|
|
@@ -37418,9 +37508,13 @@ function isEmptyCollection(node) {
|
|
|
37418
37508
|
init_errors();
|
|
37419
37509
|
init_runner();
|
|
37420
37510
|
var SERVER_KEY = "gdharness";
|
|
37421
|
-
function launchFor(version, godotPath, runner = currentRunner()) {
|
|
37511
|
+
function launchFor(version, godotPath, projectPath, runner = currentRunner()) {
|
|
37422
37512
|
const spawn = spawnFor(runner, version);
|
|
37423
|
-
return {
|
|
37513
|
+
return {
|
|
37514
|
+
command: spawn.command,
|
|
37515
|
+
args: spawn.args,
|
|
37516
|
+
env: { GODOT_PATH: godotPath, GDHARNESS_PROJECT: projectPath }
|
|
37517
|
+
};
|
|
37424
37518
|
}
|
|
37425
37519
|
function tomlArray(values) {
|
|
37426
37520
|
return `[${values.map((value) => `"${value}"`).join(", ")}]`;
|
|
@@ -38469,6 +38563,7 @@ that is running, and the project on disk. ${TOOL_SPECS.length} tools, named \`do
|
|
|
38469
38563
|
| Where a control is, and whether it is visible | \`runtime_inspect\` \`find\`, \`rect\` |
|
|
38470
38564
|
| What a property reads right now | \`runtime_inspect\` \`property\`, \`runtime_invoke\` |
|
|
38471
38565
|
| Press a button | \`runtime_input click\`, which says what was under the pointer |
|
|
38566
|
+
| Fill in a field | \`runtime_input click\` on it, then \`runtime_input text\` |
|
|
38472
38567
|
| Wait for something | \`runtime_wait\`, never a sleep |
|
|
38473
38568
|
| A picture, for a person who asked to see one | \`runtime_capture\` |
|
|
38474
38569
|
|
|
@@ -38605,7 +38700,7 @@ class UsageError extends Refusal {
|
|
|
38605
38700
|
function projectArgument(at) {
|
|
38606
38701
|
const named = args.slice(at).find((value) => !value.startsWith("--"));
|
|
38607
38702
|
const projectPath = resolve5(named ?? ".");
|
|
38608
|
-
if (!existsSync12(
|
|
38703
|
+
if (!existsSync12(join15(projectPath, "project.godot"))) {
|
|
38609
38704
|
throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
|
|
38610
38705
|
}
|
|
38611
38706
|
return projectPath;
|
|
@@ -38731,7 +38826,7 @@ async function setup() {
|
|
|
38731
38826
|
console.log(`class list rebuilt: ${String(classes.payload["classes"])} classes`);
|
|
38732
38827
|
}
|
|
38733
38828
|
if (!args.includes("--no-connect")) {
|
|
38734
|
-
const launch = launchFor(getLocalVersion(), godot.godotPath);
|
|
38829
|
+
const launch = launchFor(getLocalVersion(), godot.godotPath, projectPath);
|
|
38735
38830
|
const ask = new Ask;
|
|
38736
38831
|
let chosen;
|
|
38737
38832
|
try {
|
|
@@ -38826,7 +38921,7 @@ async function upgrade() {
|
|
|
38826
38921
|
said(await setRuntime(godot, projectPath, true), "registering the runtime autoload");
|
|
38827
38922
|
}
|
|
38828
38923
|
said(await runOperation(godot, "refresh_class_cache", {}, projectPath), "rebuilding the class list");
|
|
38829
|
-
const launch = launchFor(version, godot.godotPath);
|
|
38924
|
+
const launch = launchFor(version, godot.godotPath, projectPath);
|
|
38830
38925
|
const already = HARNESSES.filter((harness) => registered(harness, projectPath));
|
|
38831
38926
|
for (const group of groupByFile(already, projectPath)) {
|
|
38832
38927
|
reportConnection(group, launch, projectPath);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
uid://dfde0vm55ws7m
|