gdharness 0.5.8 → 0.5.10
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 +240 -82
- package/build/godot/addons/gdharness_editor/bridge_client.gd +136 -1
- package/build/index.js +217 -74
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -7229,6 +7229,16 @@ class GameLog {
|
|
|
7229
7229
|
this.entries.push(entry);
|
|
7230
7230
|
this.lastHeadline = headline ? { index: entry.index, detail } : null;
|
|
7231
7231
|
}
|
|
7232
|
+
record(severity, text) {
|
|
7233
|
+
this.entries.push({
|
|
7234
|
+
index: this.entries.length,
|
|
7235
|
+
severity,
|
|
7236
|
+
source: "debugger",
|
|
7237
|
+
text,
|
|
7238
|
+
detail: []
|
|
7239
|
+
});
|
|
7240
|
+
this.lastHeadline = null;
|
|
7241
|
+
}
|
|
7232
7242
|
get all() {
|
|
7233
7243
|
return this.entries;
|
|
7234
7244
|
}
|
|
@@ -30646,6 +30656,47 @@ var init_stdio2 = __esm(() => {
|
|
|
30646
30656
|
init_stdio();
|
|
30647
30657
|
});
|
|
30648
30658
|
|
|
30659
|
+
// src/bridge-announce.ts
|
|
30660
|
+
import { mkdirSync as mkdirSync5, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "node:fs";
|
|
30661
|
+
import { dirname as dirname6, join as join11 } from "node:path";
|
|
30662
|
+
function announcementPath(projectPath) {
|
|
30663
|
+
return join11(projectPath, ".godot", "gdharness-bridge.json");
|
|
30664
|
+
}
|
|
30665
|
+
function announceBridge(projectPath, bridge) {
|
|
30666
|
+
const path = announcementPath(projectPath);
|
|
30667
|
+
try {
|
|
30668
|
+
mkdirSync5(dirname6(path), { recursive: true });
|
|
30669
|
+
const announcement = {
|
|
30670
|
+
protocol: BRIDGE_ANNOUNCE_PROTOCOL,
|
|
30671
|
+
host: bridge.host,
|
|
30672
|
+
port: bridge.port,
|
|
30673
|
+
pid: process.pid,
|
|
30674
|
+
version: bridge.version,
|
|
30675
|
+
startedAt: new Date().toISOString()
|
|
30676
|
+
};
|
|
30677
|
+
writeFileSync7(path, `${JSON.stringify(announcement, null, 2)}
|
|
30678
|
+
`, "utf8");
|
|
30679
|
+
return path;
|
|
30680
|
+
} catch (error) {
|
|
30681
|
+
console.error(`[SERVER] Could not announce the editor bridge at ${path}: ${errorMessage(error)}`);
|
|
30682
|
+
return null;
|
|
30683
|
+
}
|
|
30684
|
+
}
|
|
30685
|
+
function withdrawBridge(path) {
|
|
30686
|
+
if (path === null) {
|
|
30687
|
+
return;
|
|
30688
|
+
}
|
|
30689
|
+
try {
|
|
30690
|
+
rmSync6(path, { force: true });
|
|
30691
|
+
} catch (error) {
|
|
30692
|
+
console.error(`[SERVER] Could not withdraw the editor bridge announcement: ${errorMessage(error)}`);
|
|
30693
|
+
}
|
|
30694
|
+
}
|
|
30695
|
+
var BRIDGE_ANNOUNCE_PROTOCOL = 1;
|
|
30696
|
+
var init_bridge_announce = __esm(() => {
|
|
30697
|
+
init_errors();
|
|
30698
|
+
});
|
|
30699
|
+
|
|
30649
30700
|
// src/framing.ts
|
|
30650
30701
|
function frame(message) {
|
|
30651
30702
|
const body = Buffer.from(JSON.stringify(message), "utf8");
|
|
@@ -30744,6 +30795,10 @@ var init_ports = __esm(() => {
|
|
|
30744
30795
|
// src/dap_client.ts
|
|
30745
30796
|
import { createConnection } from "node:net";
|
|
30746
30797
|
import { setTimeout as delay } from "node:timers/promises";
|
|
30798
|
+
function said(body, field) {
|
|
30799
|
+
const value = body?.[field];
|
|
30800
|
+
return typeof value === "string" ? value : "";
|
|
30801
|
+
}
|
|
30747
30802
|
|
|
30748
30803
|
class GodotDAPClient {
|
|
30749
30804
|
socket = null;
|
|
@@ -30758,7 +30813,7 @@ class GodotDAPClient {
|
|
|
30758
30813
|
initialized = false;
|
|
30759
30814
|
attached = false;
|
|
30760
30815
|
lastThreadId = 1;
|
|
30761
|
-
|
|
30816
|
+
halt = null;
|
|
30762
30817
|
breakpoints = new Map;
|
|
30763
30818
|
constructor(port = portFromEnv("GDHARNESS_DAP_PORT", DEFAULT_DAP_PORT), host = "127.0.0.1") {
|
|
30764
30819
|
this.port = port;
|
|
@@ -30807,7 +30862,7 @@ class GodotDAPClient {
|
|
|
30807
30862
|
this.connected = false;
|
|
30808
30863
|
this.initialized = false;
|
|
30809
30864
|
this.attached = false;
|
|
30810
|
-
this.
|
|
30865
|
+
this.halt = null;
|
|
30811
30866
|
this.socket = null;
|
|
30812
30867
|
this.failPendingRequests(new Error("DAP connection closed"));
|
|
30813
30868
|
});
|
|
@@ -30827,7 +30882,7 @@ class GodotDAPClient {
|
|
|
30827
30882
|
this.connected = false;
|
|
30828
30883
|
this.initialized = false;
|
|
30829
30884
|
this.attached = false;
|
|
30830
|
-
this.
|
|
30885
|
+
this.halt = null;
|
|
30831
30886
|
return;
|
|
30832
30887
|
}
|
|
30833
30888
|
if (this.connected) {
|
|
@@ -30856,7 +30911,7 @@ class GodotDAPClient {
|
|
|
30856
30911
|
this.connected = false;
|
|
30857
30912
|
this.initialized = false;
|
|
30858
30913
|
this.attached = false;
|
|
30859
|
-
this.
|
|
30914
|
+
this.halt = null;
|
|
30860
30915
|
}
|
|
30861
30916
|
async ensureConnected() {
|
|
30862
30917
|
if (!this.connected) {
|
|
@@ -30933,12 +30988,16 @@ class GodotDAPClient {
|
|
|
30933
30988
|
if (typeof threadId === "number") {
|
|
30934
30989
|
this.lastThreadId = threadId;
|
|
30935
30990
|
}
|
|
30936
|
-
this.
|
|
30991
|
+
this.halt = {
|
|
30992
|
+
reason: said(body, "reason"),
|
|
30993
|
+
description: said(body, "description"),
|
|
30994
|
+
text: said(body, "text")
|
|
30995
|
+
};
|
|
30937
30996
|
return;
|
|
30938
30997
|
}
|
|
30939
30998
|
if (eventName === "terminated" || eventName === "exited") {
|
|
30940
30999
|
this.attached = false;
|
|
30941
|
-
this.
|
|
31000
|
+
this.halt = null;
|
|
30942
31001
|
}
|
|
30943
31002
|
}
|
|
30944
31003
|
async initialize() {
|
|
@@ -31012,7 +31071,7 @@ class GodotDAPClient {
|
|
|
31012
31071
|
await this.attach();
|
|
31013
31072
|
const resolvedThreadId = await this.resolveThreadId(threadId);
|
|
31014
31073
|
await this.sendRequest("continue", { threadId: resolvedThreadId });
|
|
31015
|
-
this.
|
|
31074
|
+
this.halt = null;
|
|
31016
31075
|
}
|
|
31017
31076
|
async stepOver(threadId) {
|
|
31018
31077
|
await this.attach();
|
|
@@ -31083,7 +31142,10 @@ class GodotDAPClient {
|
|
|
31083
31142
|
return this.connected;
|
|
31084
31143
|
}
|
|
31085
31144
|
isStopped() {
|
|
31086
|
-
return this.
|
|
31145
|
+
return this.halt !== null;
|
|
31146
|
+
}
|
|
31147
|
+
whereItStopped() {
|
|
31148
|
+
return this.halt;
|
|
31087
31149
|
}
|
|
31088
31150
|
async resolveThreadId(threadId) {
|
|
31089
31151
|
if (typeof threadId === "number" && threadId > 0) {
|
|
@@ -31112,7 +31174,7 @@ class GodotDAPClient {
|
|
|
31112
31174
|
this.connected = false;
|
|
31113
31175
|
this.initialized = false;
|
|
31114
31176
|
this.attached = false;
|
|
31115
|
-
this.
|
|
31177
|
+
this.halt = null;
|
|
31116
31178
|
this.reader = new FrameReader;
|
|
31117
31179
|
socket?.destroy();
|
|
31118
31180
|
this.failPendingRequests(new Error(`Godot DAP ${detail}. The connection was dropped.`));
|
|
@@ -34113,8 +34175,8 @@ function resolveDefaultBridgeHost() {
|
|
|
34113
34175
|
const host = process.env["GDHARNESS_BRIDGE_HOST"]?.trim();
|
|
34114
34176
|
return host === undefined || host === "" ? DEFAULT_HOST : host;
|
|
34115
34177
|
}
|
|
34116
|
-
function getDefaultBridge() {
|
|
34117
|
-
defaultBridge ??= new GodotBridge(resolveDefaultBridgePort(), resolveDefaultBridgeHost());
|
|
34178
|
+
function getDefaultBridge(mayMove = false) {
|
|
34179
|
+
defaultBridge ??= new GodotBridge(resolveDefaultBridgePort(), resolveDefaultBridgeHost(), DEFAULT_TIMEOUT_MS, mayMove);
|
|
34118
34180
|
return defaultBridge;
|
|
34119
34181
|
}
|
|
34120
34182
|
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;
|
|
@@ -34130,19 +34192,37 @@ var init_godot_bridge = __esm(() => {
|
|
|
34130
34192
|
connectionInfo = null;
|
|
34131
34193
|
pendingRequests = new Map;
|
|
34132
34194
|
resourceQueues = new Map;
|
|
34133
|
-
|
|
34195
|
+
wantedPort;
|
|
34196
|
+
boundPort = null;
|
|
34134
34197
|
host;
|
|
34135
34198
|
timeoutMs;
|
|
34136
|
-
|
|
34199
|
+
mayMove;
|
|
34200
|
+
constructor(port = DEFAULT_PORT, host = DEFAULT_HOST, timeoutMs = DEFAULT_TIMEOUT_MS, mayMove = false) {
|
|
34137
34201
|
super();
|
|
34138
|
-
this.
|
|
34202
|
+
this.wantedPort = port;
|
|
34139
34203
|
this.host = host;
|
|
34140
34204
|
this.timeoutMs = timeoutMs;
|
|
34205
|
+
this.mayMove = mayMove;
|
|
34141
34206
|
}
|
|
34142
|
-
|
|
34207
|
+
get port() {
|
|
34208
|
+
return this.boundPort ?? this.wantedPort;
|
|
34209
|
+
}
|
|
34210
|
+
async start() {
|
|
34143
34211
|
if (this.httpServer) {
|
|
34144
|
-
return
|
|
34212
|
+
return;
|
|
34145
34213
|
}
|
|
34214
|
+
try {
|
|
34215
|
+
await this.listenOn(this.wantedPort);
|
|
34216
|
+
} catch (error) {
|
|
34217
|
+
const held = error instanceof Error && "code" in error && error.code === "EADDRINUSE";
|
|
34218
|
+
if (!held || !this.mayMove) {
|
|
34219
|
+
throw error;
|
|
34220
|
+
}
|
|
34221
|
+
this.log("warn", `Editor bridge port ${this.wantedPort} is held; taking another one.`);
|
|
34222
|
+
await this.listenOn(0);
|
|
34223
|
+
}
|
|
34224
|
+
}
|
|
34225
|
+
listenOn(port) {
|
|
34146
34226
|
return new Promise((resolve, reject) => {
|
|
34147
34227
|
const server = http.createServer((_req, res) => {
|
|
34148
34228
|
res.writeHead(404);
|
|
@@ -34166,6 +34246,8 @@ var init_godot_bridge = __esm(() => {
|
|
|
34166
34246
|
settled = true;
|
|
34167
34247
|
this.httpServer = server;
|
|
34168
34248
|
this.godotWss = godotWss;
|
|
34249
|
+
const bound = server.address();
|
|
34250
|
+
this.boundPort = typeof bound === "object" && bound !== null ? bound.port : port;
|
|
34169
34251
|
this.log("info", `Editor bridge listening on ${this.host}:${this.port}`);
|
|
34170
34252
|
resolve();
|
|
34171
34253
|
});
|
|
@@ -34182,7 +34264,7 @@ var init_godot_bridge = __esm(() => {
|
|
|
34182
34264
|
godotWss.on("error", (error) => {
|
|
34183
34265
|
this.log("error", `Godot WebSocket server error: ${error.message}`);
|
|
34184
34266
|
});
|
|
34185
|
-
server.listen(
|
|
34267
|
+
server.listen(port, this.host);
|
|
34186
34268
|
});
|
|
34187
34269
|
}
|
|
34188
34270
|
async stop() {
|
|
@@ -34216,6 +34298,7 @@ var init_godot_bridge = __esm(() => {
|
|
|
34216
34298
|
this.httpServer = null;
|
|
34217
34299
|
}
|
|
34218
34300
|
await Promise.all(closeTasks);
|
|
34301
|
+
this.boundPort = null;
|
|
34219
34302
|
this.connectionInfo = null;
|
|
34220
34303
|
this.log("info", "WebSocket bridge stopped");
|
|
34221
34304
|
}
|
|
@@ -34865,7 +34948,7 @@ var init_junit = __esm(() => {
|
|
|
34865
34948
|
import { realpathSync } from "node:fs";
|
|
34866
34949
|
import { readFile, realpath } from "node:fs/promises";
|
|
34867
34950
|
import { createConnection as createConnection2 } from "node:net";
|
|
34868
|
-
import { dirname as
|
|
34951
|
+
import { dirname as dirname7, resolve as resolve3 } from "node:path";
|
|
34869
34952
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
34870
34953
|
function diagnosticsKey(uri) {
|
|
34871
34954
|
let decoded;
|
|
@@ -34991,7 +35074,7 @@ class GodotLSPClient {
|
|
|
34991
35074
|
if (this.initialized) {
|
|
34992
35075
|
return;
|
|
34993
35076
|
}
|
|
34994
|
-
const rootPath = this.rootPath ??
|
|
35077
|
+
const rootPath = this.rootPath ?? dirname7(resolve3(filePath));
|
|
34995
35078
|
await this.initialize(rootPath);
|
|
34996
35079
|
}
|
|
34997
35080
|
async sendRequest(method, params) {
|
|
@@ -35366,7 +35449,7 @@ var init_lsp_client = __esm(() => {
|
|
|
35366
35449
|
|
|
35367
35450
|
// src/project-scan.ts
|
|
35368
35451
|
import { readdirSync as readdirSync4, readFileSync as readFileSync9 } from "node:fs";
|
|
35369
|
-
import { join as
|
|
35452
|
+
import { join as join12 } from "node:path";
|
|
35370
35453
|
function projectStructure(projectPath) {
|
|
35371
35454
|
const structure = { scenes: 0, scripts: 0, assets: 0, other: 0 };
|
|
35372
35455
|
const visit = (directory) => {
|
|
@@ -35375,7 +35458,7 @@ function projectStructure(projectPath) {
|
|
|
35375
35458
|
continue;
|
|
35376
35459
|
}
|
|
35377
35460
|
if (entry.isDirectory()) {
|
|
35378
|
-
visit(
|
|
35461
|
+
visit(join12(directory, entry.name));
|
|
35379
35462
|
} else if (entry.isFile()) {
|
|
35380
35463
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
35381
35464
|
if (extension === "tscn") {
|
|
@@ -35417,7 +35500,7 @@ function searchProject(projectPath, options) {
|
|
|
35417
35500
|
if (SKIPPED.has(entry.name)) {
|
|
35418
35501
|
continue;
|
|
35419
35502
|
}
|
|
35420
|
-
const entryPath =
|
|
35503
|
+
const entryPath = join12(directory, entry.name);
|
|
35421
35504
|
if (entry.isDirectory()) {
|
|
35422
35505
|
visit(entryPath);
|
|
35423
35506
|
continue;
|
|
@@ -35459,20 +35542,20 @@ var init_project_scan = __esm(() => {
|
|
|
35459
35542
|
import { existsSync as existsSync10, readdirSync as readdirSync5, readFileSync as readFileSync10, unlinkSync } from "node:fs";
|
|
35460
35543
|
import { createConnection as createConnection3 } from "node:net";
|
|
35461
35544
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
35462
|
-
import { join as
|
|
35545
|
+
import { join as join13, resolve as resolve4 } from "node:path";
|
|
35463
35546
|
function runtimeDirectory(variables = process.env) {
|
|
35464
35547
|
const explicit = envValue("GDHARNESS_RUNTIME_DIR", variables);
|
|
35465
35548
|
if (explicit) {
|
|
35466
35549
|
return explicit;
|
|
35467
35550
|
}
|
|
35468
35551
|
const perUser = envValue("XDG_RUNTIME_DIR", variables);
|
|
35469
|
-
return
|
|
35552
|
+
return join13(perUser ?? tmpdir4(), "gdharness");
|
|
35470
35553
|
}
|
|
35471
35554
|
function runtimeDirectories(variables = process.env) {
|
|
35472
35555
|
const candidates = [runtimeDirectory(variables)];
|
|
35473
35556
|
const fallbacks = process.platform === "win32" ? [envValue("SystemRoot", variables) ?? envValue("windir", variables) ?? "C:\\Windows"] : ["/tmp", "/var/tmp"];
|
|
35474
35557
|
for (const base of fallbacks) {
|
|
35475
|
-
candidates.push(
|
|
35558
|
+
candidates.push(join13(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
|
|
35476
35559
|
}
|
|
35477
35560
|
return [...new Set(candidates.map((path) => resolve4(path)))];
|
|
35478
35561
|
}
|
|
@@ -35535,7 +35618,7 @@ function announcedIn(directory) {
|
|
|
35535
35618
|
if (!match) {
|
|
35536
35619
|
continue;
|
|
35537
35620
|
}
|
|
35538
|
-
const file =
|
|
35621
|
+
const file = join13(directory, entry);
|
|
35539
35622
|
const pid = Number.parseInt(match[1] ?? "", 10);
|
|
35540
35623
|
const announced = processAlive(pid) ? parseAnnouncement(file, pid) : { kind: "rubbish" };
|
|
35541
35624
|
if (announced.kind === "rubbish") {
|
|
@@ -35679,11 +35762,31 @@ var init_runtime_client = __esm(() => {
|
|
|
35679
35762
|
ANNOUNCEMENT_PATTERN = /^runtime-(\d+)\.json$/;
|
|
35680
35763
|
});
|
|
35681
35764
|
|
|
35765
|
+
// src/setup.ts
|
|
35766
|
+
import { cpSync as cpSync2, existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync11, rmSync as rmSync7, writeFileSync as writeFileSync8 } from "node:fs";
|
|
35767
|
+
import { dirname as dirname8, join as join14 } from "node:path";
|
|
35768
|
+
function installedAddonVersion(projectPath) {
|
|
35769
|
+
const marker = join14(projectPath, "addons", ADDONS2[0], VERSION_MARKER2);
|
|
35770
|
+
if (!existsSync11(marker)) {
|
|
35771
|
+
return null;
|
|
35772
|
+
}
|
|
35773
|
+
const said = readFileSync11(marker, "utf8").trim();
|
|
35774
|
+
return said === "" ? null : said;
|
|
35775
|
+
}
|
|
35776
|
+
var ADDONS2, VERSION_MARKER2 = ".gdharness-version";
|
|
35777
|
+
var init_setup = __esm(() => {
|
|
35778
|
+
init_class_cache();
|
|
35779
|
+
init_headless();
|
|
35780
|
+
init_resources();
|
|
35781
|
+
init_server_version();
|
|
35782
|
+
ADDONS2 = ["gdharness_editor", "gdharness_runtime", "auto_reload"];
|
|
35783
|
+
});
|
|
35784
|
+
|
|
35682
35785
|
// src/server.ts
|
|
35683
35786
|
import { execFile as execFile5, spawn } from "node:child_process";
|
|
35684
|
-
import { existsSync as
|
|
35787
|
+
import { existsSync as existsSync12, mkdtempSync as mkdtempSync3, readdirSync as readdirSync6, readFileSync as readFileSync12, realpathSync as realpathSync2, rmSync as rmSync8 } from "node:fs";
|
|
35685
35788
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
35686
|
-
import { basename as basename2, dirname as
|
|
35789
|
+
import { basename as basename2, dirname as dirname9, join as join15, normalize as normalize3 } from "node:path";
|
|
35687
35790
|
import { setTimeout as delay2 } from "node:timers/promises";
|
|
35688
35791
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
35689
35792
|
import { promisify as promisify5 } from "node:util";
|
|
@@ -35709,11 +35812,11 @@ function realPathOr(path) {
|
|
|
35709
35812
|
}
|
|
35710
35813
|
}
|
|
35711
35814
|
function hasMainScene(projectFile) {
|
|
35712
|
-
const scene = parseProjectGodot(
|
|
35815
|
+
const scene = parseProjectGodot(readFileSync12(projectFile, "utf8"))["application"]?.["run/main_scene"];
|
|
35713
35816
|
return typeof scene === "string" && scene !== "";
|
|
35714
35817
|
}
|
|
35715
35818
|
function editorPlaysHeadless(projectFile) {
|
|
35716
|
-
const runArgs = parseProjectGodot(
|
|
35819
|
+
const runArgs = parseProjectGodot(readFileSync12(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
|
|
35717
35820
|
return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
|
|
35718
35821
|
}
|
|
35719
35822
|
function camelCased(params) {
|
|
@@ -35729,7 +35832,7 @@ function camelCased(params) {
|
|
|
35729
35832
|
class GodotServer {
|
|
35730
35833
|
mcp;
|
|
35731
35834
|
locator = new GodotLocator2;
|
|
35732
|
-
operationsScript =
|
|
35835
|
+
operationsScript = join15(__dirname2, "godot", "operations", "godot_operations.gd");
|
|
35733
35836
|
godotBridge;
|
|
35734
35837
|
tools = buildToolDefinitions();
|
|
35735
35838
|
activeProcess = null;
|
|
@@ -35743,8 +35846,11 @@ class GodotServer {
|
|
|
35743
35846
|
bridgeRetry = null;
|
|
35744
35847
|
lastProjectPath = null;
|
|
35745
35848
|
shutdownInitiated = false;
|
|
35849
|
+
ownProject;
|
|
35850
|
+
announcedAt = null;
|
|
35746
35851
|
constructor() {
|
|
35747
|
-
this.
|
|
35852
|
+
this.ownProject = envValue("GDHARNESS_PROJECT") ?? null;
|
|
35853
|
+
this.godotBridge = getDefaultBridge(this.ownProject !== null);
|
|
35748
35854
|
this.mcp = new McpServer({ name: "gdharness", version: SERVER_VERSION }, { capabilities: { tools: {}, resources: {} } });
|
|
35749
35855
|
this.setupToolHandlers();
|
|
35750
35856
|
setupResourceHandlers(this.mcp, () => this.lastProjectPath);
|
|
@@ -35792,6 +35898,7 @@ class GodotServer {
|
|
|
35792
35898
|
this.bridgeStartupError = null;
|
|
35793
35899
|
const bridgeStatus = this.godotBridge.getStatus();
|
|
35794
35900
|
console.error(`[SERVER] Godot Editor Bridge started on ${bridgeStatus.host}:${bridgeStatus.port}`);
|
|
35901
|
+
this.announceTheBridge();
|
|
35795
35902
|
} catch (bridgeError) {
|
|
35796
35903
|
const code = bridgeError instanceof Error && "code" in bridgeError && typeof bridgeError.code === "string" ? bridgeError.code : null;
|
|
35797
35904
|
const reason = errorMessage(bridgeError);
|
|
@@ -35824,6 +35931,18 @@ class GodotServer {
|
|
|
35824
35931
|
this.stopTryingTheBridge();
|
|
35825
35932
|
const bridgeStatus = this.godotBridge.getStatus();
|
|
35826
35933
|
console.error(`[SERVER] Godot Editor Bridge came up on ${bridgeStatus.host}:${bridgeStatus.port}; editor tools are live.`);
|
|
35934
|
+
this.announceTheBridge();
|
|
35935
|
+
}
|
|
35936
|
+
announceTheBridge() {
|
|
35937
|
+
if (this.ownProject === null) {
|
|
35938
|
+
return;
|
|
35939
|
+
}
|
|
35940
|
+
const status = this.godotBridge.getStatus();
|
|
35941
|
+
this.announcedAt = announceBridge(this.ownProject, {
|
|
35942
|
+
host: status.host,
|
|
35943
|
+
port: status.port,
|
|
35944
|
+
version: SERVER_VERSION
|
|
35945
|
+
});
|
|
35827
35946
|
}
|
|
35828
35947
|
stopTryingTheBridge() {
|
|
35829
35948
|
if (this.bridgeRetry !== null) {
|
|
@@ -35834,6 +35953,8 @@ class GodotServer {
|
|
|
35834
35953
|
async cleanup() {
|
|
35835
35954
|
this.logDebug("Cleaning up resources");
|
|
35836
35955
|
this.stopTryingTheBridge();
|
|
35956
|
+
withdrawBridge(this.announcedAt);
|
|
35957
|
+
this.announcedAt = null;
|
|
35837
35958
|
if (this.activeProcess) {
|
|
35838
35959
|
this.activeProcess.process?.kill();
|
|
35839
35960
|
this.activeProcess = null;
|
|
@@ -35949,28 +36070,44 @@ class GodotServer {
|
|
|
35949
36070
|
if (this.callsSinceNotice < UPDATE_NOTICE_EVERY && this.noticedUpdate) {
|
|
35950
36071
|
return answer;
|
|
35951
36072
|
}
|
|
36073
|
+
const moved = this.projectHasMovedOn();
|
|
36074
|
+
if (moved !== null) {
|
|
36075
|
+
this.noticedUpdate = true;
|
|
36076
|
+
this.callsSinceNotice = 0;
|
|
36077
|
+
return this.saying(answer, {
|
|
36078
|
+
project_upgraded_under_this_server: {
|
|
36079
|
+
server_is: SERVER_VERSION,
|
|
36080
|
+
project_is: moved,
|
|
36081
|
+
what_to_do: "The project was upgraded while this server has been running, so it is still answering " + "as the version it was started as. Tell the user to reconnect the MCP server, which is " + "the only thing that replaces it: a server cannot restart itself."
|
|
36082
|
+
}
|
|
36083
|
+
});
|
|
36084
|
+
}
|
|
35952
36085
|
const notice = this.updates.notice();
|
|
35953
36086
|
if (notice === null) {
|
|
35954
36087
|
return answer;
|
|
35955
36088
|
}
|
|
35956
36089
|
this.noticedUpdate = true;
|
|
35957
36090
|
this.callsSinceNotice = 0;
|
|
36091
|
+
return this.saying(answer, {
|
|
36092
|
+
update_available: {
|
|
36093
|
+
...notice,
|
|
36094
|
+
what_to_do: "Tell the user a newer gdharness is out, with what changed, and offer to take it. " + "Only run the upgrade command if they say yes: it restarts their editor and the " + "MCP server has to be reconnected afterwards."
|
|
36095
|
+
}
|
|
36096
|
+
});
|
|
36097
|
+
}
|
|
36098
|
+
saying(answer, block) {
|
|
35958
36099
|
return {
|
|
35959
36100
|
...answer,
|
|
35960
|
-
content: [
|
|
35961
|
-
...answer.content,
|
|
35962
|
-
{
|
|
35963
|
-
type: "text",
|
|
35964
|
-
text: JSON.stringify({
|
|
35965
|
-
update_available: {
|
|
35966
|
-
...notice,
|
|
35967
|
-
what_to_do: "Tell the user a newer gdharness is out, with what changed, and offer to take it. " + "Only run the upgrade command if they say yes: it restarts their editor and the " + "MCP server has to be reconnected afterwards."
|
|
35968
|
-
}
|
|
35969
|
-
}, null, 2)
|
|
35970
|
-
}
|
|
35971
|
-
]
|
|
36101
|
+
content: [...answer.content, { type: "text", text: JSON.stringify(block, null, 2) }]
|
|
35972
36102
|
};
|
|
35973
36103
|
}
|
|
36104
|
+
projectHasMovedOn() {
|
|
36105
|
+
if (this.ownProject === null) {
|
|
36106
|
+
return null;
|
|
36107
|
+
}
|
|
36108
|
+
const installed = installedAddonVersion(this.ownProject);
|
|
36109
|
+
return installed === null || installed === SERVER_VERSION ? null : installed;
|
|
36110
|
+
}
|
|
35974
36111
|
withFeedbackNotice(answer) {
|
|
35975
36112
|
this.callsSinceFeedback += 1;
|
|
35976
36113
|
if (this.callsSinceFeedback < FEEDBACK_NOTICE_EVERY) {
|
|
@@ -36216,8 +36353,8 @@ class GodotServer {
|
|
|
36216
36353
|
if (path === undefined) {
|
|
36217
36354
|
return { ok: false, response: this.createErrorResponse("projectPath is required.") };
|
|
36218
36355
|
}
|
|
36219
|
-
const file =
|
|
36220
|
-
if (!
|
|
36356
|
+
const file = join15(path, "project.godot");
|
|
36357
|
+
if (!existsSync12(file)) {
|
|
36221
36358
|
return {
|
|
36222
36359
|
ok: false,
|
|
36223
36360
|
response: this.createErrorResponse(`Not a Godot project: ${path}`, [
|
|
@@ -36362,7 +36499,7 @@ class GodotServer {
|
|
|
36362
36499
|
if (!engine.ok) {
|
|
36363
36500
|
return engine.response;
|
|
36364
36501
|
}
|
|
36365
|
-
const application = parseProjectGodot(
|
|
36502
|
+
const application = parseProjectGodot(readFileSync12(project.value.file, "utf8"))["application"] ?? {};
|
|
36366
36503
|
const name = application["config/name"];
|
|
36367
36504
|
const mainScene = application["run/main_scene"];
|
|
36368
36505
|
const info = {
|
|
@@ -36451,7 +36588,7 @@ class GodotServer {
|
|
|
36451
36588
|
log.finish();
|
|
36452
36589
|
const problems = log.select({ severity: "warning", sinceLastCall: false, limit: 200 });
|
|
36453
36590
|
const verdict = {
|
|
36454
|
-
exported: exitCode === 0 && log.count("error") === 0 &&
|
|
36591
|
+
exported: exitCode === 0 && log.count("error") === 0 && existsSync12(output.absolutePath),
|
|
36455
36592
|
preset,
|
|
36456
36593
|
outputPath: output.relativePath,
|
|
36457
36594
|
debug,
|
|
@@ -36481,7 +36618,7 @@ class GodotServer {
|
|
|
36481
36618
|
return contained.response;
|
|
36482
36619
|
}
|
|
36483
36620
|
const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
|
|
36484
|
-
if (!
|
|
36621
|
+
if (!existsSync12(join15(project.value.path, runner))) {
|
|
36485
36622
|
return this.createErrorResponse(`gdUnit4 is not installed in this project: no ${runner}.`, [
|
|
36486
36623
|
"Install gdUnit4 under addons/gdUnit4, from https://github.com/godot-gdunit-labs/gdUnit4"
|
|
36487
36624
|
]);
|
|
@@ -36514,7 +36651,7 @@ class GodotServer {
|
|
|
36514
36651
|
];
|
|
36515
36652
|
const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 600000;
|
|
36516
36653
|
this.logDebug(`Running tests: ${engine.value} ${cmdArgs.join(" ")}`);
|
|
36517
|
-
const userData = mkdtempSync3(
|
|
36654
|
+
const userData = mkdtempSync3(join15(tmpdir5(), "gdharness-tests-"));
|
|
36518
36655
|
const run = this.spawnGame(engine.value, cmdArgs, userDataIn(userData));
|
|
36519
36656
|
const hung = await new Promise((resolve) => {
|
|
36520
36657
|
const timer = setTimeout(() => {
|
|
@@ -36530,20 +36667,20 @@ class GodotServer {
|
|
|
36530
36667
|
resolve(false);
|
|
36531
36668
|
});
|
|
36532
36669
|
});
|
|
36533
|
-
const reportsDir =
|
|
36670
|
+
const reportsDir = join15(project.value.path, ".godot", "gdharness-reports");
|
|
36534
36671
|
let report = null;
|
|
36535
36672
|
let reportProblem = null;
|
|
36536
36673
|
try {
|
|
36537
|
-
const written =
|
|
36674
|
+
const written = existsSync12(reportsDir) ? readdirSync6(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
|
|
36538
36675
|
const newest = written.at(-1);
|
|
36539
36676
|
if (newest !== undefined) {
|
|
36540
|
-
report = parseJUnit(
|
|
36677
|
+
report = parseJUnit(readFileSync12(join15(reportsDir, newest, "results.xml"), "utf8"));
|
|
36541
36678
|
}
|
|
36542
36679
|
} catch (error) {
|
|
36543
36680
|
reportProblem = errorMessage(error);
|
|
36544
36681
|
} finally {
|
|
36545
|
-
|
|
36546
|
-
|
|
36682
|
+
rmSync8(reportsDir, { recursive: true, force: true });
|
|
36683
|
+
rmSync8(userData, { recursive: true, force: true });
|
|
36547
36684
|
}
|
|
36548
36685
|
const engineEntries = run.log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries;
|
|
36549
36686
|
const verdicts = {
|
|
@@ -36656,6 +36793,8 @@ class GodotServer {
|
|
|
36656
36793
|
startupError: this.bridgeStartupError,
|
|
36657
36794
|
staleNote: stale ? addonMismatch(status.addonVersion, SERVER_VERSION) : undefined,
|
|
36658
36795
|
retryingBridge: this.bridgeRetry === null ? undefined : true,
|
|
36796
|
+
announcedAt: this.announcedAt ?? undefined,
|
|
36797
|
+
projectIs: this.projectHasMovedOn() ?? undefined,
|
|
36659
36798
|
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,
|
|
36660
36799
|
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
|
|
36661
36800
|
};
|
|
@@ -36868,7 +37007,8 @@ class GodotServer {
|
|
|
36868
37007
|
log,
|
|
36869
37008
|
startedAt: Date.now(),
|
|
36870
37009
|
exitCode: null,
|
|
36871
|
-
throughEditor: true
|
|
37010
|
+
throughEditor: true,
|
|
37011
|
+
brokeOn: null
|
|
36872
37012
|
};
|
|
36873
37013
|
this.activeProcess = played;
|
|
36874
37014
|
return this.jsonTextResponse({
|
|
@@ -36888,6 +37028,15 @@ class GodotServer {
|
|
|
36888
37028
|
`) ? line : `${line}
|
|
36889
37029
|
`);
|
|
36890
37030
|
}
|
|
37031
|
+
this.recordWhatItBrokeOn(game);
|
|
37032
|
+
}
|
|
37033
|
+
recordWhatItBrokeOn(game) {
|
|
37034
|
+
const halt = this.dapClient?.whereItStopped() ?? null;
|
|
37035
|
+
if (halt?.reason !== "exception" || halt.text === "" || game.brokeOn === halt.text) {
|
|
37036
|
+
return;
|
|
37037
|
+
}
|
|
37038
|
+
game.brokeOn = halt.text;
|
|
37039
|
+
game.log.record("error", halt.text);
|
|
36891
37040
|
}
|
|
36892
37041
|
async endActiveGame() {
|
|
36893
37042
|
const running = this.activeProcess;
|
|
@@ -36909,7 +37058,8 @@ class GodotServer {
|
|
|
36909
37058
|
log,
|
|
36910
37059
|
startedAt: Date.now(),
|
|
36911
37060
|
exitCode: null,
|
|
36912
|
-
throughEditor: false
|
|
37061
|
+
throughEditor: false,
|
|
37062
|
+
brokeOn: null
|
|
36913
37063
|
};
|
|
36914
37064
|
child.stdout.on("data", (data) => {
|
|
36915
37065
|
log.append("stdout", data);
|
|
@@ -36974,6 +37124,7 @@ class GodotServer {
|
|
|
36974
37124
|
contains: readNonEmptyString(args, "contains"),
|
|
36975
37125
|
limit: readPositiveNumber(args, "limit") ?? 200
|
|
36976
37126
|
});
|
|
37127
|
+
const halt = this.activeProcess.throughEditor ? this.dapClient?.whereItStopped() ?? null : null;
|
|
36977
37128
|
return this.jsonTextResponse({
|
|
36978
37129
|
running: this.activeProcess.exitCode === null,
|
|
36979
37130
|
exitCode: this.activeProcess.exitCode,
|
|
@@ -36982,6 +37133,7 @@ class GodotServer {
|
|
|
36982
37133
|
errors: this.activeProcess.log.count("error"),
|
|
36983
37134
|
warnings: this.activeProcess.log.count("warning"),
|
|
36984
37135
|
clean: this.activeProcess.log.count("error") === 0,
|
|
37136
|
+
heldAt: halt,
|
|
36985
37137
|
omitted: selected.omitted,
|
|
36986
37138
|
entries: selected.entries
|
|
36987
37139
|
});
|
|
@@ -37051,8 +37203,8 @@ class GodotServer {
|
|
|
37051
37203
|
return this.createErrorResponse(choice.problem);
|
|
37052
37204
|
}
|
|
37053
37205
|
const expectsScreenshot = command === "capture_screenshot" || command === "capture_viewport";
|
|
37054
|
-
const screenshotDir = expectsScreenshot ? mkdtempSync3(
|
|
37055
|
-
const screenshotPath = screenshotDir ?
|
|
37206
|
+
const screenshotDir = expectsScreenshot ? mkdtempSync3(join15(tmpdir5(), "gdharness-runtime-screenshot-")) : null;
|
|
37207
|
+
const screenshotPath = screenshotDir ? join15(screenshotDir, "capture.png") : null;
|
|
37056
37208
|
try {
|
|
37057
37209
|
const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs);
|
|
37058
37210
|
if (!reply.ok) {
|
|
@@ -37073,12 +37225,12 @@ class GodotServer {
|
|
|
37073
37225
|
return {
|
|
37074
37226
|
content: [
|
|
37075
37227
|
{ type: "text", text: `Screenshot captured: ${dimensions}` },
|
|
37076
|
-
{ type: "image", data:
|
|
37228
|
+
{ type: "image", data: readFileSync12(screenshotPath).toString("base64"), mimeType: "image/png" }
|
|
37077
37229
|
]
|
|
37078
37230
|
};
|
|
37079
37231
|
} finally {
|
|
37080
37232
|
if (screenshotDir) {
|
|
37081
|
-
|
|
37233
|
+
rmSync8(screenshotDir, { recursive: true, force: true });
|
|
37082
37234
|
}
|
|
37083
37235
|
}
|
|
37084
37236
|
}
|
|
@@ -37140,6 +37292,7 @@ var init_server3 = __esm(() => {
|
|
|
37140
37292
|
init_mcp();
|
|
37141
37293
|
init_stdio2();
|
|
37142
37294
|
init_types();
|
|
37295
|
+
init_bridge_announce();
|
|
37143
37296
|
init_class_cache();
|
|
37144
37297
|
init_dap_client();
|
|
37145
37298
|
init_errors();
|
|
@@ -37155,10 +37308,11 @@ var init_server3 = __esm(() => {
|
|
|
37155
37308
|
init_resources();
|
|
37156
37309
|
init_runtime_client();
|
|
37157
37310
|
init_server_version();
|
|
37311
|
+
init_setup();
|
|
37158
37312
|
init_tool_definitions();
|
|
37159
37313
|
init_update_check();
|
|
37160
37314
|
run5 = promisify5(execFile5);
|
|
37161
|
-
__dirname2 =
|
|
37315
|
+
__dirname2 = dirname9(fileURLToPath4(import.meta.url));
|
|
37162
37316
|
PATH_SOLUTIONS = [
|
|
37163
37317
|
'Give the path relative to the project, such as "scenes/main.tscn" or "res://scenes/main.tscn"',
|
|
37164
37318
|
"Point projectPath at the project the file belongs to"
|
|
@@ -37215,8 +37369,8 @@ var init_server3 = __esm(() => {
|
|
|
37215
37369
|
});
|
|
37216
37370
|
|
|
37217
37371
|
// src/cli.ts
|
|
37218
|
-
import { existsSync as
|
|
37219
|
-
import { join as
|
|
37372
|
+
import { existsSync as existsSync13 } from "fs";
|
|
37373
|
+
import { join as join16, resolve as resolve5 } from "path";
|
|
37220
37374
|
|
|
37221
37375
|
// src/errors.ts
|
|
37222
37376
|
class Refusal extends Error {
|
|
@@ -37426,9 +37580,13 @@ function isEmptyCollection(node) {
|
|
|
37426
37580
|
init_errors();
|
|
37427
37581
|
init_runner();
|
|
37428
37582
|
var SERVER_KEY = "gdharness";
|
|
37429
|
-
function launchFor(version, godotPath, runner = currentRunner()) {
|
|
37583
|
+
function launchFor(version, godotPath, projectPath, runner = currentRunner()) {
|
|
37430
37584
|
const spawn = spawnFor(runner, version);
|
|
37431
|
-
return {
|
|
37585
|
+
return {
|
|
37586
|
+
command: spawn.command,
|
|
37587
|
+
args: spawn.args,
|
|
37588
|
+
env: { GODOT_PATH: godotPath, GDHARNESS_PROJECT: projectPath }
|
|
37589
|
+
};
|
|
37432
37590
|
}
|
|
37433
37591
|
function tomlArray(values) {
|
|
37434
37592
|
return `[${values.map((value) => `"${value}"`).join(", ")}]`;
|
|
@@ -38614,7 +38772,7 @@ class UsageError extends Refusal {
|
|
|
38614
38772
|
function projectArgument(at) {
|
|
38615
38773
|
const named = args.slice(at).find((value) => !value.startsWith("--"));
|
|
38616
38774
|
const projectPath = resolve5(named ?? ".");
|
|
38617
|
-
if (!
|
|
38775
|
+
if (!existsSync13(join16(projectPath, "project.godot"))) {
|
|
38618
38776
|
throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
|
|
38619
38777
|
}
|
|
38620
38778
|
return projectPath;
|
|
@@ -38626,7 +38784,7 @@ async function engine() {
|
|
|
38626
38784
|
}
|
|
38627
38785
|
return { godotPath, script: shippedOperationsScript(), debug: GODOT_DEBUG_MODE_DEFAULT2 };
|
|
38628
38786
|
}
|
|
38629
|
-
function
|
|
38787
|
+
function said2(outcome, what) {
|
|
38630
38788
|
if (!outcome.ok) {
|
|
38631
38789
|
throw new Refusal(`${what}: ${outcome.message}`);
|
|
38632
38790
|
}
|
|
@@ -38727,20 +38885,20 @@ async function setup() {
|
|
|
38727
38885
|
const enabled = await enablePlugins(godot, projectPath, EDITOR_PLUGINS);
|
|
38728
38886
|
for (const [index, outcome] of enabled.entries()) {
|
|
38729
38887
|
const name = EDITOR_PLUGINS[index] ?? "";
|
|
38730
|
-
|
|
38888
|
+
said2(outcome, `enabling ${name}`);
|
|
38731
38889
|
console.log(`${name}: ${outcome.ok ? String(outcome.payload["action"]) : "failed"}`);
|
|
38732
38890
|
}
|
|
38733
38891
|
if (runtime) {
|
|
38734
|
-
|
|
38892
|
+
said2(await setRuntime(godot, projectPath, true), "registering the runtime autoload");
|
|
38735
38893
|
console.log(`${RUNTIME_AUTOLOAD.name} autoload registered`);
|
|
38736
38894
|
}
|
|
38737
38895
|
const classes = await runOperation(godot, "refresh_class_cache", {}, projectPath);
|
|
38738
|
-
|
|
38896
|
+
said2(classes, "rebuilding the class list");
|
|
38739
38897
|
if (classes.ok) {
|
|
38740
38898
|
console.log(`class list rebuilt: ${String(classes.payload["classes"])} classes`);
|
|
38741
38899
|
}
|
|
38742
38900
|
if (!args.includes("--no-connect")) {
|
|
38743
|
-
const launch = launchFor(getLocalVersion(), godot.godotPath);
|
|
38901
|
+
const launch = launchFor(getLocalVersion(), godot.godotPath, projectPath);
|
|
38744
38902
|
const ask = new Ask;
|
|
38745
38903
|
let chosen;
|
|
38746
38904
|
try {
|
|
@@ -38758,7 +38916,7 @@ async function setup() {
|
|
|
38758
38916
|
console.log("no harness configured; name one yourself, and gdharness harnesses lists them all");
|
|
38759
38917
|
}
|
|
38760
38918
|
if (!args.includes("--no-skill")) {
|
|
38761
|
-
const directories = skillDirectories(chosen.harnesses, projectPath,
|
|
38919
|
+
const directories = skillDirectories(chosen.harnesses, projectPath, existsSync13);
|
|
38762
38920
|
for (const written of writeSkill(directories, getLocalVersion())) {
|
|
38763
38921
|
console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
|
|
38764
38922
|
}
|
|
@@ -38797,11 +38955,11 @@ async function uninstall() {
|
|
|
38797
38955
|
const disabled = await disablePlugins(godot, projectPath, enabled);
|
|
38798
38956
|
for (const [index, outcome] of disabled.entries()) {
|
|
38799
38957
|
const name = enabled[index] ?? "";
|
|
38800
|
-
|
|
38958
|
+
said2(outcome, `disabling ${name}`);
|
|
38801
38959
|
console.log(`${name}: ${outcome.ok ? String(outcome.payload["action"]) : "failed"}`);
|
|
38802
38960
|
}
|
|
38803
38961
|
if (before.runtimeAutoload) {
|
|
38804
|
-
|
|
38962
|
+
said2(await setRuntime(godot, projectPath, false), "removing the runtime autoload");
|
|
38805
38963
|
console.log(`${RUNTIME_AUTOLOAD.name} autoload removed`);
|
|
38806
38964
|
}
|
|
38807
38965
|
const addons = removeAddons(projectPath);
|
|
@@ -38829,13 +38987,13 @@ async function upgrade() {
|
|
|
38829
38987
|
}
|
|
38830
38988
|
const enabled = await enablePlugins(godot, projectPath, EDITOR_PLUGINS);
|
|
38831
38989
|
for (const [index, outcome] of enabled.entries()) {
|
|
38832
|
-
|
|
38990
|
+
said2(outcome, `enabling ${EDITOR_PLUGINS[index] ?? ""}`);
|
|
38833
38991
|
}
|
|
38834
38992
|
if (before.runtimeAutoload) {
|
|
38835
|
-
|
|
38993
|
+
said2(await setRuntime(godot, projectPath, true), "registering the runtime autoload");
|
|
38836
38994
|
}
|
|
38837
|
-
|
|
38838
|
-
const launch = launchFor(version, godot.godotPath);
|
|
38995
|
+
said2(await runOperation(godot, "refresh_class_cache", {}, projectPath), "rebuilding the class list");
|
|
38996
|
+
const launch = launchFor(version, godot.godotPath, projectPath);
|
|
38839
38997
|
const already = HARNESSES.filter((harness) => registered(harness, projectPath));
|
|
38840
38998
|
for (const group of groupByFile(already, projectPath)) {
|
|
38841
38999
|
reportConnection(group, launch, projectPath);
|
|
@@ -38843,7 +39001,7 @@ async function upgrade() {
|
|
|
38843
39001
|
if (already.length === 0) {
|
|
38844
39002
|
console.log("no harness config names gdharness, so none was re-pinned");
|
|
38845
39003
|
}
|
|
38846
|
-
for (const written of writeSkill(skillDirectories(already, projectPath,
|
|
39004
|
+
for (const written of writeSkill(skillDirectories(already, projectPath, existsSync13), version)) {
|
|
38847
39005
|
console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
|
|
38848
39006
|
}
|
|
38849
39007
|
console.log(`
|
|
@@ -38879,14 +39037,14 @@ async function runtime3() {
|
|
|
38879
39037
|
}
|
|
38880
39038
|
const projectPath = projectArgument(2);
|
|
38881
39039
|
const godot = await engine();
|
|
38882
|
-
|
|
39040
|
+
said2(await setRuntime(godot, projectPath, state === "on"), `turning the runtime ${state}`);
|
|
38883
39041
|
console.log(`${RUNTIME_AUTOLOAD.name} autoload ${state === "on" ? "registered" : "removed"}`);
|
|
38884
39042
|
}
|
|
38885
39043
|
async function classes() {
|
|
38886
39044
|
const projectPath = projectArgument(1);
|
|
38887
39045
|
const godot = await engine();
|
|
38888
39046
|
const outcome = await runOperation(godot, "refresh_class_cache", {}, projectPath);
|
|
38889
|
-
|
|
39047
|
+
said2(outcome, "rebuilding the class list");
|
|
38890
39048
|
if (outcome.ok) {
|
|
38891
39049
|
console.log(JSON.stringify(outcome.payload, null, 2));
|
|
38892
39050
|
}
|