gdharness 1.0.11 → 1.0.13
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 +650 -147
- package/build/godot/addons/gdharness_editor/bridge_client.gd +18 -2
- package/build/index.js +589 -142
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -182,8 +182,18 @@ function runArguments(options) {
|
|
|
182
182
|
}
|
|
183
183
|
return args;
|
|
184
184
|
}
|
|
185
|
-
function editorArguments(projectPath, ports) {
|
|
186
|
-
return [
|
|
185
|
+
function editorArguments(projectPath, ports, logFile) {
|
|
186
|
+
return [
|
|
187
|
+
"-e",
|
|
188
|
+
"--path",
|
|
189
|
+
projectPath,
|
|
190
|
+
"--lsp-port",
|
|
191
|
+
String(ports.lsp),
|
|
192
|
+
"--dap-port",
|
|
193
|
+
String(ports.dap),
|
|
194
|
+
"--log-file",
|
|
195
|
+
logFile
|
|
196
|
+
];
|
|
187
197
|
}
|
|
188
198
|
function userDataIn(home, variables = process.env) {
|
|
189
199
|
const moved = ["APPDATA", "XDG_DATA_HOME"];
|
|
@@ -7263,6 +7273,9 @@ class GameLog {
|
|
|
7263
7273
|
count(severity) {
|
|
7264
7274
|
return this.entries.filter((entry) => entry.severity === severity).length;
|
|
7265
7275
|
}
|
|
7276
|
+
everything() {
|
|
7277
|
+
return this.entries;
|
|
7278
|
+
}
|
|
7266
7279
|
select(options) {
|
|
7267
7280
|
const floor = options.sinceLastCall ? this.seen[options.severity] : 0;
|
|
7268
7281
|
const wanted = ADMITTED[options.severity];
|
|
@@ -7518,11 +7531,20 @@ var init_update_check = __esm(() => {
|
|
|
7518
7531
|
|
|
7519
7532
|
// src/server-version.ts
|
|
7520
7533
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
7521
|
-
function
|
|
7522
|
-
if (
|
|
7534
|
+
function editorIsStale(addonVersion, serverVersion, addonDigest, shippedDigest) {
|
|
7535
|
+
if (addonDigest !== undefined && addonDigest !== "" && shippedDigest !== undefined) {
|
|
7536
|
+
return addonDigest !== shippedDigest;
|
|
7537
|
+
}
|
|
7538
|
+
return addonVersion !== serverVersion;
|
|
7539
|
+
}
|
|
7540
|
+
function addonMismatch(addonVersion, serverVersion, addonDigest, shippedDigest) {
|
|
7541
|
+
if (!editorIsStale(addonVersion, serverVersion, addonDigest, shippedDigest)) {
|
|
7523
7542
|
return;
|
|
7524
7543
|
}
|
|
7525
7544
|
const reported = addonVersion ?? "";
|
|
7545
|
+
if (reported === serverVersion) {
|
|
7546
|
+
return `The editor is running a different build of the ${reported} addon from the one this server ships. Restart it with editor_launch restart to pick this one up.`;
|
|
7547
|
+
}
|
|
7526
7548
|
const editor = reported === "" ? UNVERSIONED : `the ${reported} addon`;
|
|
7527
7549
|
const both = `The editor is running ${editor} while this server ships ${serverVersion}.`;
|
|
7528
7550
|
if (reported !== "" && isNewer(reported, serverVersion)) {
|
|
@@ -7530,8 +7552,8 @@ function addonMismatch(addonVersion, serverVersion) {
|
|
|
7530
7552
|
}
|
|
7531
7553
|
return `${both} Restart it with editor_launch restart to pick the new one up.`;
|
|
7532
7554
|
}
|
|
7533
|
-
function markIfStale(answer, addonVersion, serverVersion) {
|
|
7534
|
-
const staleNote = addonMismatch(addonVersion, serverVersion);
|
|
7555
|
+
function markIfStale(answer, addonVersion, serverVersion, addonDigest, shippedDigest) {
|
|
7556
|
+
const staleNote = addonMismatch(addonVersion, serverVersion, addonDigest, shippedDigest);
|
|
7535
7557
|
if (staleNote === undefined || typeof answer !== "object" || answer === null || Array.isArray(answer)) {
|
|
7536
7558
|
return answer;
|
|
7537
7559
|
}
|
|
@@ -7677,6 +7699,19 @@ function cacheWrittenAt(projectPath) {
|
|
|
7677
7699
|
return null;
|
|
7678
7700
|
}
|
|
7679
7701
|
}
|
|
7702
|
+
function declaredSince(projectPath, paths, names, since) {
|
|
7703
|
+
return names.filter((name) => {
|
|
7704
|
+
const path = paths.get(name);
|
|
7705
|
+
if (path === undefined || !path.startsWith("res://")) {
|
|
7706
|
+
return false;
|
|
7707
|
+
}
|
|
7708
|
+
try {
|
|
7709
|
+
return statSync2(join6(projectPath, path.slice("res://".length))).mtimeMs > since;
|
|
7710
|
+
} catch {
|
|
7711
|
+
return false;
|
|
7712
|
+
}
|
|
7713
|
+
});
|
|
7714
|
+
}
|
|
7680
7715
|
function cachedAtMissingPaths(cached, projectPath) {
|
|
7681
7716
|
return [...cached].filter(([, path]) => path.startsWith("res://") && !existsSync6(join6(projectPath, path.slice("res://".length)))).map(([name]) => name).sort();
|
|
7682
7717
|
}
|
|
@@ -15295,8 +15330,57 @@ function readParams(params, key) {
|
|
|
15295
15330
|
}
|
|
15296
15331
|
|
|
15297
15332
|
// src/setup.ts
|
|
15298
|
-
import {
|
|
15333
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
15334
|
+
import {
|
|
15335
|
+
cpSync as cpSync2,
|
|
15336
|
+
existsSync as existsSync8,
|
|
15337
|
+
mkdirSync as mkdirSync4,
|
|
15338
|
+
readdirSync as readdirSync4,
|
|
15339
|
+
readFileSync as readFileSync8,
|
|
15340
|
+
rmSync as rmSync4,
|
|
15341
|
+
statSync as statSync4,
|
|
15342
|
+
writeFileSync as writeFileSync6
|
|
15343
|
+
} from "node:fs";
|
|
15299
15344
|
import { dirname as dirname4, join as join9 } from "node:path";
|
|
15345
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
15346
|
+
function editorAddonDigest2(from = shippedAddonsDirectory2()) {
|
|
15347
|
+
const hash = createHash3("sha256");
|
|
15348
|
+
const walk = (directory, relative) => {
|
|
15349
|
+
for (const name of readdirSync4(directory).sort()) {
|
|
15350
|
+
if (name === VERSION_MARKER2 || name === DIGEST_MARKER2) {
|
|
15351
|
+
continue;
|
|
15352
|
+
}
|
|
15353
|
+
const path = join9(directory, name);
|
|
15354
|
+
const inside = `${relative}/${name}`;
|
|
15355
|
+
if (statSync4(path).isDirectory()) {
|
|
15356
|
+
walk(path, inside);
|
|
15357
|
+
} else {
|
|
15358
|
+
hash.update(`${inside}
|
|
15359
|
+
`);
|
|
15360
|
+
hash.update(readFileSync8(path));
|
|
15361
|
+
hash.update(`
|
|
15362
|
+
`);
|
|
15363
|
+
}
|
|
15364
|
+
}
|
|
15365
|
+
};
|
|
15366
|
+
for (const name of EDITOR_PLUGINS2) {
|
|
15367
|
+
walk(join9(from, name), name);
|
|
15368
|
+
}
|
|
15369
|
+
return hash.digest("hex");
|
|
15370
|
+
}
|
|
15371
|
+
function shippedEditorDigest() {
|
|
15372
|
+
if (shippedDigest === undefined) {
|
|
15373
|
+
try {
|
|
15374
|
+
shippedDigest = editorAddonDigest2();
|
|
15375
|
+
} catch {
|
|
15376
|
+
shippedDigest = null;
|
|
15377
|
+
}
|
|
15378
|
+
}
|
|
15379
|
+
return shippedDigest ?? undefined;
|
|
15380
|
+
}
|
|
15381
|
+
function shippedAddonsDirectory2() {
|
|
15382
|
+
return join9(dirname4(fileURLToPath2(import.meta.url)), "godot", "addons");
|
|
15383
|
+
}
|
|
15300
15384
|
function installedAddonVersion(projectPath) {
|
|
15301
15385
|
const marker = join9(projectPath, "addons", ADDONS2[0], VERSION_MARKER2);
|
|
15302
15386
|
if (!existsSync8(marker)) {
|
|
@@ -15305,13 +15389,14 @@ function installedAddonVersion(projectPath) {
|
|
|
15305
15389
|
const said = readFileSync8(marker, "utf8").trim();
|
|
15306
15390
|
return said === "" ? null : said;
|
|
15307
15391
|
}
|
|
15308
|
-
var ADDONS2, RUNTIME_AUTOLOAD2, SCRIPT_RUNS_SETTING = "gdharness/runtime/serve_script_runs", BIND_ADDRESS_SETTING = "gdharness/runtime/bind_address", PORT_SETTING = "gdharness/runtime/port", VERSION_MARKER2 = ".gdharness-version";
|
|
15392
|
+
var ADDONS2, EDITOR_PLUGINS2, RUNTIME_AUTOLOAD2, SCRIPT_RUNS_SETTING = "gdharness/runtime/serve_script_runs", BIND_ADDRESS_SETTING = "gdharness/runtime/bind_address", PORT_SETTING = "gdharness/runtime/port", VERSION_MARKER2 = ".gdharness-version", DIGEST_MARKER2 = ".gdharness-digest", shippedDigest;
|
|
15309
15393
|
var init_setup = __esm(() => {
|
|
15310
15394
|
init_class_cache();
|
|
15311
15395
|
init_headless();
|
|
15312
15396
|
init_resources();
|
|
15313
15397
|
init_server_version();
|
|
15314
15398
|
ADDONS2 = ["gdharness_editor", "gdharness_runtime", "auto_reload"];
|
|
15399
|
+
EDITOR_PLUGINS2 = ["gdharness_editor", "auto_reload"];
|
|
15315
15400
|
RUNTIME_AUTOLOAD2 = {
|
|
15316
15401
|
name: "GdharnessRuntime",
|
|
15317
15402
|
path: "addons/gdharness_runtime/runtime_autoload.gd"
|
|
@@ -15349,7 +15434,7 @@ function projectPathSentence(without = callsWithoutProjectPath()) {
|
|
|
15349
15434
|
return `Every call takes \`projectPath\` except ${named}, which ${without.length === 1 ? "takes" : "take"} none`;
|
|
15350
15435
|
}
|
|
15351
15436
|
function theGamePicked() {
|
|
15352
|
-
return "the game this server started or is playing when it is among those running, named under " + "`answeredBy` where there was a choice; else the only game there is; else a refusal naming " + "every game
|
|
15437
|
+
return "the game this server started or is playing when it is among those running, named under " + "`answeredBy` where there was a choice; else the only game there is; else a refusal naming " + "every game and which argument tells them apart, `pid` when they are all one project and " + "`projectPath` when they are not";
|
|
15353
15438
|
}
|
|
15354
15439
|
function toolSpec(name) {
|
|
15355
15440
|
return SPECS_BY_NAME[name];
|
|
@@ -16070,7 +16155,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16070
16155
|
},
|
|
16071
16156
|
{
|
|
16072
16157
|
name: "editor_output",
|
|
16073
|
-
description: "What the project started by editor_run has printed, as entries with a severity: the errors and warnings the engine reported, each with where it happened, and everything else as info. For a run the editor plays the reports come from the game itself, through the runtime addon, since the editor's debugger relays what a game prints and not what it reports; a played game without the runtime addon answers here with its prints alone. Answers with the counts and the verdict as well as the entries. heldAt is three answers: null for a run this server knows is not held, running or over, an object for one it knows is held at a breakpoint, whether the adapter said so or the runtime did by accepting a connection and never answering, and absent with heldUnknown beside it for a server that connected to the debugger after the stop and has no runtime to ask, since the adapter reports a stop only to the sessions connected when it happened. A run that has quit still answers here, with running false and its exit code, until the next one starts. A run this server started also answers with transcript, the file both its streams are written to: uncapped, written while the run is going, and the thing to read or tail for a long run rather than the engine's own log, which every engine start rotates away. Read the file for output and this answer for state: endedBy says whether gdharness ended the run, and a transcript that has stopped growing is a run between prints rather than a run that is over. running is asked of the operating system whenever there is a process to ask about, which is every run this server started and every editor-played run whose game announced its runtime. For an editor-played run that never announced, there is no process id here and running is the editor's answer, which can lag: one measured elsewhere reported a game as playing for fifteen seconds after its process had been ended from outside. editor_status names that game under runtimes, which does not depend on the editor answering.",
|
|
16158
|
+
description: "What the project started by editor_run has printed, as entries with a severity: the errors and warnings the engine reported, each with where it happened, and everything else as info. For a run the editor plays the reports come from the game itself, through the runtime addon, since the editor's debugger relays what a game prints and not what it reports; a played game without the runtime addon answers here with its prints alone. Answers with the counts and the verdict as well as the entries. heldAt is three answers: null for a run this server knows is not held, running or over, an object for one it knows is held at a breakpoint, whether the adapter said so or the runtime did by accepting a connection and never answering, and absent with heldUnknown beside it for a server that connected to the debugger after the stop and has no runtime to ask, since the adapter reports a stop only to the sessions connected when it happened. A run that has quit still answers here, with running false and its exit code, until the next one starts. A run this server started also answers with transcript, the file both its streams are written to: uncapped, written while the run is going, and the thing to read or tail for a long run rather than the engine's own log, which every engine start rotates away. Read the file for output and this answer for state: endedBy says whether gdharness ended the run, and a transcript that has stopped growing is a run between prints rather than a run that is over. running is asked of the operating system whenever there is a process to ask about, which is every run this server started and every editor-played run whose game announced its runtime. For an editor-played run that never announced, there is no process id here and running is the editor's answer, which can lag: one measured elsewhere reported a game as playing for fifteen seconds after its process had been ended from outside. editor_status names that game under runtimes, which does not depend on the editor answering. op: \"editor\" answers about the editor itself instead, which is a different log and the only way anything here can read what an editor printed: its console reaches no plugin, so the addon cannot scrape it, and an editor gdharness opened is asked for a log file as it starts. That covers the whole session from the editor's first line, which is where the output worth reading usually is, since the class cache, the plugins and the language server all say what they have to say before any harness has connected. The answer carries the entries in the order they were printed and, beside them under repeated, each message shape several lines share: how many, what varied in each slot of it and how many different values stood there, the first occurrence, and the ordinary lines above the group, found by walking back past the group rather than by counting lines. An editor somebody opened by hand has no console here and is refused saying so rather than answered empty, because an editor that printed nothing and an editor nobody captured are not the same thing.",
|
|
16074
16159
|
parameters: {
|
|
16075
16160
|
severity: {
|
|
16076
16161
|
type: "string",
|
|
@@ -16078,10 +16163,12 @@ var init_tool_definitions = __esm(() => {
|
|
|
16078
16163
|
description: "The least severe entry to include. Default info, which is everything."
|
|
16079
16164
|
},
|
|
16080
16165
|
sinceLastCall: {
|
|
16166
|
+
ops: ["run"],
|
|
16081
16167
|
type: "boolean",
|
|
16082
16168
|
description: "Only entries this has not already answered with at this severity. Default false. Measured per severity floor and not across them, so polling for errors leaves the rest of the output for a later read rather than consuming it, and a read with contains is a search over the whole run and consumes nothing."
|
|
16083
16169
|
},
|
|
16084
16170
|
cpu: {
|
|
16171
|
+
ops: ["run"],
|
|
16085
16172
|
type: "boolean",
|
|
16086
16173
|
description: "Also answer with cpuSeconds, the processor time the run has used. Default false, because asking costs a subprocess. Worth it when elapsedMs is climbing and nothing is being printed: processor time standing still is a run that has stopped doing anything, rather than one that is slow."
|
|
16087
16174
|
},
|
|
@@ -16092,8 +16179,24 @@ var init_tool_definitions = __esm(() => {
|
|
|
16092
16179
|
limit: {
|
|
16093
16180
|
type: "number",
|
|
16094
16181
|
description: "The most entries to answer with, newest kept. Default 200, and omitted says how many matching entries that left out. A run long enough to pass it needs this raised, not just filtered: filtering narrows what counts as matching, and the newest of those is still all one answer carries."
|
|
16182
|
+
},
|
|
16183
|
+
before: {
|
|
16184
|
+
ops: ["editor"],
|
|
16185
|
+
type: "number",
|
|
16186
|
+
description: "How many lines above a repeated group to carry with it, counting only lines that are not themselves part of a group. Default 3. The line that explains a wall of identical errors is the last ordinary line above it, so this walks past the wall rather than a fixed distance back."
|
|
16095
16187
|
}
|
|
16096
16188
|
},
|
|
16189
|
+
operations: {
|
|
16190
|
+
run: {
|
|
16191
|
+
summary: "What the game a run is playing has printed, which is everything described above",
|
|
16192
|
+
requires: []
|
|
16193
|
+
},
|
|
16194
|
+
editor: {
|
|
16195
|
+
summary: "What the connected editor itself has printed, from its first line, with repeated messages grouped and the ordinary lines above each group carried with it",
|
|
16196
|
+
requires: []
|
|
16197
|
+
}
|
|
16198
|
+
},
|
|
16199
|
+
defaultOperation: "run",
|
|
16097
16200
|
requires: []
|
|
16098
16201
|
},
|
|
16099
16202
|
{
|
|
@@ -31459,7 +31562,7 @@ var init_process_children = __esm(() => {
|
|
|
31459
31562
|
});
|
|
31460
31563
|
|
|
31461
31564
|
// src/runtime-client.ts
|
|
31462
|
-
import { existsSync as existsSync10, readdirSync as
|
|
31565
|
+
import { existsSync as existsSync10, readdirSync as readdirSync6, readFileSync as readFileSync10, statSync as statSync5, unlinkSync } from "node:fs";
|
|
31463
31566
|
import { createConnection } from "node:net";
|
|
31464
31567
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
31465
31568
|
import { dirname as dirname7, join as join12, resolve as resolve3 } from "node:path";
|
|
@@ -31596,7 +31699,7 @@ function announcedIn(directory) {
|
|
|
31596
31699
|
const found = [];
|
|
31597
31700
|
const candidates = [];
|
|
31598
31701
|
const entries = [];
|
|
31599
|
-
for (const entry of
|
|
31702
|
+
for (const entry of readdirSync6(directory)) {
|
|
31600
31703
|
const report = ERROR_REPORT_PATTERN.exec(entry);
|
|
31601
31704
|
if (report) {
|
|
31602
31705
|
sweepErrorReport(join12(directory, entry), Number.parseInt(report[1] ?? "", 10));
|
|
@@ -31612,7 +31715,7 @@ function announcedIn(directory) {
|
|
|
31612
31715
|
entries.push({ file, pid, alive });
|
|
31613
31716
|
if (alive) {
|
|
31614
31717
|
try {
|
|
31615
|
-
candidates.push({ file, pid, writtenAt:
|
|
31718
|
+
candidates.push({ file, pid, writtenAt: statSync5(file).mtimeMs });
|
|
31616
31719
|
} catch {}
|
|
31617
31720
|
}
|
|
31618
31721
|
}
|
|
@@ -31669,7 +31772,7 @@ function sweepErrorReport(path, pid, now = Date.now()) {
|
|
|
31669
31772
|
return;
|
|
31670
31773
|
}
|
|
31671
31774
|
try {
|
|
31672
|
-
if (now -
|
|
31775
|
+
if (now - statSync5(path).mtimeMs >= ERROR_REPORT_KEEP_MS) {
|
|
31673
31776
|
unlinkSync(path);
|
|
31674
31777
|
}
|
|
31675
31778
|
} catch {}
|
|
@@ -31693,10 +31796,37 @@ async function announcedSince(projectPath, before, waiting = {}) {
|
|
|
31693
31796
|
function describe2(endpoint) {
|
|
31694
31797
|
return `pid ${endpoint.pid} on ${endpoint.address}:${endpoint.port} (${endpoint.project.name || "unnamed"} at ${endpoint.project.path})`;
|
|
31695
31798
|
}
|
|
31799
|
+
function describeWithin(endpoint) {
|
|
31800
|
+
return `pid ${endpoint.pid} on ${endpoint.address}:${endpoint.port}`;
|
|
31801
|
+
}
|
|
31802
|
+
function theOneProject(endpoints) {
|
|
31803
|
+
if (endpoints.some((endpoint) => endpoint.project.path === "")) {
|
|
31804
|
+
return;
|
|
31805
|
+
}
|
|
31806
|
+
const paths = new Set(endpoints.map((endpoint) => resolve3(endpoint.project.path)));
|
|
31807
|
+
const only = [...paths][0];
|
|
31808
|
+
return paths.size === 1 ? only : undefined;
|
|
31809
|
+
}
|
|
31810
|
+
function severalFrom(project, games) {
|
|
31811
|
+
return `${games.length} games are running from ${project}: ${games.map(describeWithin).join("; ")}. ` + "Pass pid to choose one; projectPath cannot, because every one of them has this one. " + "editor_status lists them under runtimes.";
|
|
31812
|
+
}
|
|
31813
|
+
function whichHalfIsBehind(unspoken) {
|
|
31814
|
+
const pids = (games) => games.map((game) => `pid ${game.pid}`).join(", ");
|
|
31815
|
+
const ahead = unspoken.filter((game) => game.protocol > RUNTIME_PROTOCOL);
|
|
31816
|
+
const behind = unspoken.filter((game) => game.protocol < RUNTIME_PROTOCOL);
|
|
31817
|
+
if (ahead.length > 0 && behind.length > 0) {
|
|
31818
|
+
return `they are on both sides of it: reconnect this server for ${pids(ahead)}, and reinstall the ` + `addon and restart ${pids(behind)}`;
|
|
31819
|
+
}
|
|
31820
|
+
if (ahead.length > 0) {
|
|
31821
|
+
return "this server is the older half: reconnect it so it spawns the installed version";
|
|
31822
|
+
}
|
|
31823
|
+
return `the addon is the older half: reinstall it and restart ${behind.length === 1 ? "the game" : "those games"}`;
|
|
31824
|
+
}
|
|
31696
31825
|
function tooNew(unspoken) {
|
|
31697
31826
|
const named = unspoken.map((game) => `pid ${game.pid} speaks ${game.protocol} (${game.project.name || "unnamed"} at ${game.project.path})`).join("; ");
|
|
31698
|
-
const
|
|
31699
|
-
|
|
31827
|
+
const count = unspoken.length === 1 ? "A game is running" : `${unspoken.length} games are running`;
|
|
31828
|
+
const spoken = new Set(unspoken.map((game) => game.protocol)).size === 1 ? "a protocol" : "protocols";
|
|
31829
|
+
return `${count}, in ${spoken} this server does not speak: ${named}. This server speaks ${RUNTIME_PROTOCOL}, so ${whichHalfIsBehind(unspoken)}.`;
|
|
31700
31830
|
}
|
|
31701
31831
|
function chooseRuntime(endpoints, projectPath, unspoken = [], pid) {
|
|
31702
31832
|
if (pid !== undefined) {
|
|
@@ -31713,8 +31843,16 @@ function chooseRuntime(endpoints, projectPath, unspoken = [], pid) {
|
|
|
31713
31843
|
if (tooNewToo !== undefined) {
|
|
31714
31844
|
return { problem: tooNew([tooNewToo]) };
|
|
31715
31845
|
}
|
|
31846
|
+
if (endpoints.length > 0) {
|
|
31847
|
+
return {
|
|
31848
|
+
problem: `No running game has pid ${pid}. Running: ${endpoints.map(describe2).join("; ")}. editor_status lists them under runtimes as they come and go.`
|
|
31849
|
+
};
|
|
31850
|
+
}
|
|
31851
|
+
if (unspoken.length > 0) {
|
|
31852
|
+
return { problem: `No game reachable from here has pid ${pid}. ${tooNew(unspoken)}` };
|
|
31853
|
+
}
|
|
31716
31854
|
return {
|
|
31717
|
-
problem:
|
|
31855
|
+
problem: `No game with the runtime addon is running, so there is none with pid ${pid}. Start one with editor_run, or play the project from the editor with the addon enabled.`
|
|
31718
31856
|
};
|
|
31719
31857
|
}
|
|
31720
31858
|
if (endpoints.length === 0) {
|
|
@@ -31746,15 +31884,17 @@ function chooseRuntime(endpoints, projectPath, unspoken = [], pid) {
|
|
|
31746
31884
|
if (matching.length === 1 && matching[0]) {
|
|
31747
31885
|
return { endpoint: matching[0] };
|
|
31748
31886
|
}
|
|
31749
|
-
return {
|
|
31750
|
-
problem: `Several games are running from ${wanted}: ${matching.map(describe2).join("; ")}. Pass pid to choose one.`
|
|
31751
|
-
};
|
|
31887
|
+
return { problem: severalFrom(wanted, matching) };
|
|
31752
31888
|
}
|
|
31753
31889
|
if (endpoints.length === 1 && endpoints[0]) {
|
|
31754
31890
|
return { endpoint: endpoints[0] };
|
|
31755
31891
|
}
|
|
31892
|
+
const shared = theOneProject(endpoints);
|
|
31893
|
+
if (shared !== undefined) {
|
|
31894
|
+
return { problem: severalFrom(shared, endpoints) };
|
|
31895
|
+
}
|
|
31756
31896
|
return {
|
|
31757
|
-
problem: `Several games are running: ${endpoints.map(describe2).join("; ")}. Pass
|
|
31897
|
+
problem: `Several games are running: ${endpoints.map(describe2).join("; ")}. Pass pid to choose one, or projectPath when the project you mean is running only one.`
|
|
31758
31898
|
};
|
|
31759
31899
|
}
|
|
31760
31900
|
function runtimeRequest(endpoint, command, params, timeoutMs) {
|
|
@@ -32802,6 +32942,235 @@ var init_dap_client = __esm(() => {
|
|
|
32802
32942
|
init_ports();
|
|
32803
32943
|
});
|
|
32804
32944
|
|
|
32945
|
+
// src/editor-log.ts
|
|
32946
|
+
import {
|
|
32947
|
+
closeSync,
|
|
32948
|
+
existsSync as existsSync12,
|
|
32949
|
+
mkdirSync as mkdirSync10,
|
|
32950
|
+
openSync,
|
|
32951
|
+
readFileSync as readFileSync16,
|
|
32952
|
+
readSync,
|
|
32953
|
+
rmSync as rmSync8,
|
|
32954
|
+
statSync as statSync6,
|
|
32955
|
+
writeFileSync as writeFileSync12
|
|
32956
|
+
} from "node:fs";
|
|
32957
|
+
import { dirname as dirname12, join as join18 } from "node:path";
|
|
32958
|
+
function editorLogPath(projectPath) {
|
|
32959
|
+
return join18(projectPath, ".godot", "gdharness-editor.log");
|
|
32960
|
+
}
|
|
32961
|
+
function editorLogNotePath(projectPath) {
|
|
32962
|
+
return join18(projectPath, ".godot", "gdharness-editor.json");
|
|
32963
|
+
}
|
|
32964
|
+
function readEditorLogNote(projectPath) {
|
|
32965
|
+
try {
|
|
32966
|
+
const fields = asParams(JSON.parse(readFileSync16(editorLogNotePath(projectPath), "utf8")));
|
|
32967
|
+
const pid = readNumber(fields, "pid");
|
|
32968
|
+
const startedAt = readString(fields, "startedAt");
|
|
32969
|
+
return pid !== undefined && Number.isInteger(pid) && pid > 0 && startedAt !== undefined ? { pid, startedAt } : null;
|
|
32970
|
+
} catch {
|
|
32971
|
+
return null;
|
|
32972
|
+
}
|
|
32973
|
+
}
|
|
32974
|
+
function clearEditorLog(projectPath) {
|
|
32975
|
+
try {
|
|
32976
|
+
const path = editorLogPath(projectPath);
|
|
32977
|
+
mkdirSync10(dirname12(path), { recursive: true });
|
|
32978
|
+
rmSync8(path, { force: true });
|
|
32979
|
+
rmSync8(editorLogNotePath(projectPath), { force: true });
|
|
32980
|
+
} catch {}
|
|
32981
|
+
}
|
|
32982
|
+
function writeEditorLogNote(projectPath, pid) {
|
|
32983
|
+
try {
|
|
32984
|
+
const path = editorLogNotePath(projectPath);
|
|
32985
|
+
mkdirSync10(dirname12(path), { recursive: true });
|
|
32986
|
+
writeFileSync12(path, JSON.stringify({ pid, startedAt: new Date().toISOString() }, null, 2), "utf8");
|
|
32987
|
+
} catch {}
|
|
32988
|
+
}
|
|
32989
|
+
function theConsoleWasNotCaptured(why, projectPath) {
|
|
32990
|
+
if (why.kind === "not ours") {
|
|
32991
|
+
return "This editor was not opened by gdharness, so its console was never captured and nothing " + "here has it: an editor prints to a console no plugin can read, and the addon cannot get " + "at it from inside. This is not an editor that printed nothing.";
|
|
32992
|
+
}
|
|
32993
|
+
if (why.kind === "older server") {
|
|
32994
|
+
return `This editor was opened by a gdharness server, but the console beside ${projectPath} belongs ` + `to editor ${why.notedPid ?? "none recorded"} rather than to this one, so it holds an ` + "earlier session and is not offered as this one. A server from before consoles were " + "captured leaves exactly this.";
|
|
32995
|
+
}
|
|
32996
|
+
return "This editor was opened by gdharness and has written nothing to its console file yet, not " + "even the engine version it prints first, so the file is missing rather than empty. An " + "editor that could not open the file for writing leaves this.";
|
|
32997
|
+
}
|
|
32998
|
+
function editorConsole(projectPath, editorPid, openedByAServer) {
|
|
32999
|
+
if (!openedByAServer) {
|
|
33000
|
+
return { kind: "not ours" };
|
|
33001
|
+
}
|
|
33002
|
+
const noted = readEditorLogNote(projectPath);
|
|
33003
|
+
if (noted === null || editorPid === null || noted.pid !== editorPid) {
|
|
33004
|
+
return { kind: "older server", notedPid: noted?.pid ?? null };
|
|
33005
|
+
}
|
|
33006
|
+
const path = editorLogPath(projectPath);
|
|
33007
|
+
if (!existsSync12(path)) {
|
|
33008
|
+
return { kind: "not written" };
|
|
33009
|
+
}
|
|
33010
|
+
const log = new GameLog;
|
|
33011
|
+
const { text, of } = theFirstOf(path);
|
|
33012
|
+
log.append("transcript", text);
|
|
33013
|
+
log.finish();
|
|
33014
|
+
return { log, path, ...of === undefined ? {} : { readBytes: text.length, of } };
|
|
33015
|
+
}
|
|
33016
|
+
function theFirstOf(path) {
|
|
33017
|
+
const whole = statSync6(path).size;
|
|
33018
|
+
if (whole <= MOST_CONSOLE_BYTES) {
|
|
33019
|
+
return { text: readFileSync16(path, "utf8") };
|
|
33020
|
+
}
|
|
33021
|
+
const handle = openSync(path, "r");
|
|
33022
|
+
try {
|
|
33023
|
+
const buffer = Buffer.alloc(MOST_CONSOLE_BYTES);
|
|
33024
|
+
const read = readSync(handle, buffer, 0, MOST_CONSOLE_BYTES, 0);
|
|
33025
|
+
const decoded = buffer.subarray(0, read).toString("utf8");
|
|
33026
|
+
const lastLine = decoded.lastIndexOf(`
|
|
33027
|
+
`);
|
|
33028
|
+
return { text: lastLine === -1 ? decoded : decoded.slice(0, lastLine + 1), of: whole };
|
|
33029
|
+
} finally {
|
|
33030
|
+
closeSync(handle);
|
|
33031
|
+
}
|
|
33032
|
+
}
|
|
33033
|
+
function shapeOf(text) {
|
|
33034
|
+
const values = [];
|
|
33035
|
+
let shape = "";
|
|
33036
|
+
let at = 0;
|
|
33037
|
+
while (at < text.length) {
|
|
33038
|
+
let earliest = null;
|
|
33039
|
+
for (const pattern of SLOTS) {
|
|
33040
|
+
pattern.lastIndex = at;
|
|
33041
|
+
const found = pattern.exec(text);
|
|
33042
|
+
if (found !== null && (earliest === null || found.index < earliest.index || found.index === earliest.index && found[0].length > earliest.value.length)) {
|
|
33043
|
+
earliest = { index: found.index, value: found[0] };
|
|
33044
|
+
}
|
|
33045
|
+
}
|
|
33046
|
+
if (earliest === null) {
|
|
33047
|
+
shape += text.slice(at);
|
|
33048
|
+
break;
|
|
33049
|
+
}
|
|
33050
|
+
if (earliest.value.length === 0) {
|
|
33051
|
+
shape += text.slice(at, at + 1);
|
|
33052
|
+
at += 1;
|
|
33053
|
+
continue;
|
|
33054
|
+
}
|
|
33055
|
+
shape += `${text.slice(at, earliest.index)}…`;
|
|
33056
|
+
values.push(earliest.value);
|
|
33057
|
+
at = earliest.index + earliest.value.length;
|
|
33058
|
+
}
|
|
33059
|
+
return { shape, values };
|
|
33060
|
+
}
|
|
33061
|
+
function bursts(entries, before) {
|
|
33062
|
+
const shapes = new Map;
|
|
33063
|
+
const shaped = entries.map((entry) => shapeOf(entry.text));
|
|
33064
|
+
for (const [at, entry] of entries.entries()) {
|
|
33065
|
+
const key = `${entry.severity}\x00${shaped[at]?.shape ?? entry.text}`;
|
|
33066
|
+
const group = shapes.get(key) ?? { indices: [], values: [] };
|
|
33067
|
+
group.indices.push(at);
|
|
33068
|
+
group.values.push(shaped[at]?.values ?? []);
|
|
33069
|
+
shapes.set(key, group);
|
|
33070
|
+
}
|
|
33071
|
+
const collapsed = new Set;
|
|
33072
|
+
for (const group of shapes.values()) {
|
|
33073
|
+
if (group.indices.length > 1) {
|
|
33074
|
+
for (const at of group.indices) {
|
|
33075
|
+
collapsed.add(at);
|
|
33076
|
+
}
|
|
33077
|
+
}
|
|
33078
|
+
}
|
|
33079
|
+
const repeated = [];
|
|
33080
|
+
for (const [key, group] of shapes) {
|
|
33081
|
+
const first = group.indices[0];
|
|
33082
|
+
if (group.indices.length < 2 || first === undefined) {
|
|
33083
|
+
continue;
|
|
33084
|
+
}
|
|
33085
|
+
const entry = entries[first];
|
|
33086
|
+
if (entry === undefined) {
|
|
33087
|
+
continue;
|
|
33088
|
+
}
|
|
33089
|
+
repeated.push({
|
|
33090
|
+
shape: key.slice(key.indexOf("\x00") + 1),
|
|
33091
|
+
count: group.indices.length,
|
|
33092
|
+
severity: entry.severity,
|
|
33093
|
+
first: { index: entry.index, text: entry.text },
|
|
33094
|
+
slots: slotsOf(group.values, group.indices.length),
|
|
33095
|
+
before: linesBefore(entries, first, collapsed, before)
|
|
33096
|
+
});
|
|
33097
|
+
}
|
|
33098
|
+
return repeated.sort((one, other) => other.count - one.count);
|
|
33099
|
+
}
|
|
33100
|
+
function slotsOf(values, count) {
|
|
33101
|
+
const width = Math.max(0, ...values.map((row) => row.length));
|
|
33102
|
+
const distinct = [];
|
|
33103
|
+
for (let at = 0;at < width; at += 1) {
|
|
33104
|
+
distinct.push([...new Set(values.map((row) => row[at]).filter((one) => one !== undefined))]);
|
|
33105
|
+
}
|
|
33106
|
+
const slots = [];
|
|
33107
|
+
for (let at = 0;at < width; at += 1) {
|
|
33108
|
+
const seen = distinct[at] ?? [];
|
|
33109
|
+
const partner = theSlotItMovesWith(values, distinct, at);
|
|
33110
|
+
const paired = partner === undefined ? {} : { movesWith: partner };
|
|
33111
|
+
if (seen.length === count && count > 1) {
|
|
33112
|
+
slots.push({ distinct: seen.length, allDifferent: true, ...paired });
|
|
33113
|
+
continue;
|
|
33114
|
+
}
|
|
33115
|
+
const some = seen.slice(0, NAMED_VALUES);
|
|
33116
|
+
slots.push({
|
|
33117
|
+
distinct: seen.length,
|
|
33118
|
+
some,
|
|
33119
|
+
...seen.length > some.length ? { more: seen.length - some.length } : {},
|
|
33120
|
+
...paired
|
|
33121
|
+
});
|
|
33122
|
+
}
|
|
33123
|
+
return slots;
|
|
33124
|
+
}
|
|
33125
|
+
function theSlotItMovesWith(values, distinct, at) {
|
|
33126
|
+
const mine = distinct[at] ?? [];
|
|
33127
|
+
if (mine.length < 2) {
|
|
33128
|
+
return;
|
|
33129
|
+
}
|
|
33130
|
+
for (let other = 0;other < at; other += 1) {
|
|
33131
|
+
if ((distinct[other] ?? []).length !== mine.length) {
|
|
33132
|
+
continue;
|
|
33133
|
+
}
|
|
33134
|
+
const goesWith = new Map;
|
|
33135
|
+
let oneForOne = true;
|
|
33136
|
+
for (const row of values) {
|
|
33137
|
+
const theirs = row[other];
|
|
33138
|
+
const ours = row[at];
|
|
33139
|
+
if (theirs === undefined || ours === undefined) {
|
|
33140
|
+
oneForOne = false;
|
|
33141
|
+
break;
|
|
33142
|
+
}
|
|
33143
|
+
const already = goesWith.get(theirs);
|
|
33144
|
+
if (already === undefined) {
|
|
33145
|
+
goesWith.set(theirs, ours);
|
|
33146
|
+
} else if (already !== ours) {
|
|
33147
|
+
oneForOne = false;
|
|
33148
|
+
break;
|
|
33149
|
+
}
|
|
33150
|
+
}
|
|
33151
|
+
if (oneForOne) {
|
|
33152
|
+
return other;
|
|
33153
|
+
}
|
|
33154
|
+
}
|
|
33155
|
+
return;
|
|
33156
|
+
}
|
|
33157
|
+
function linesBefore(entries, at, collapsed, wanted) {
|
|
33158
|
+
const found = [];
|
|
33159
|
+
for (let back = at - 1;back >= 0 && at - back <= WALK_BACK_LIMIT && found.length < wanted; back -= 1) {
|
|
33160
|
+
const entry = entries[back];
|
|
33161
|
+
if (entry !== undefined && !collapsed.has(back)) {
|
|
33162
|
+
found.push(entry.text);
|
|
33163
|
+
}
|
|
33164
|
+
}
|
|
33165
|
+
return found.reverse();
|
|
33166
|
+
}
|
|
33167
|
+
var MOST_CONSOLE_BYTES, NAMED_VALUES = 6, WALK_BACK_LIMIT = 500, SLOTS;
|
|
33168
|
+
var init_editor_log = __esm(() => {
|
|
33169
|
+
init_game_log();
|
|
33170
|
+
MOST_CONSOLE_BYTES = 8 * 1024 * 1024;
|
|
33171
|
+
SLOTS = [/"[^"]*"/g, /'[^']*'/g, /\b(?:res|user):\/\/\S+/g, /(?<=\.gd|\.tscn|\.cs):\d+(?::\d+)?/g];
|
|
33172
|
+
});
|
|
33173
|
+
|
|
32805
33174
|
// node_modules/ws/lib/constants.js
|
|
32806
33175
|
var require_constants = __commonJS(function(exports, module) {
|
|
32807
33176
|
var BINARY_TYPES = ["nodebuffer", "arraybuffer", "fragments"];
|
|
@@ -35863,6 +36232,7 @@ var init_godot_bridge = __esm(() => {
|
|
|
35863
36232
|
connectedAt: this.connectionInfo?.connectedAt,
|
|
35864
36233
|
lastPongAt: this.connectionInfo?.lastPongAt,
|
|
35865
36234
|
addonVersion: this.connectionInfo?.addonVersion,
|
|
36235
|
+
addonDigest: this.connectionInfo?.addonDigest,
|
|
35866
36236
|
editorPid: this.connectionInfo?.editorPid,
|
|
35867
36237
|
lspPort: this.connectionInfo?.lspPort,
|
|
35868
36238
|
dapPort: this.connectionInfo?.dapPort,
|
|
@@ -36006,6 +36376,7 @@ var init_godot_bridge = __esm(() => {
|
|
|
36006
36376
|
}
|
|
36007
36377
|
this.connectionInfo.projectPath = message.project_path;
|
|
36008
36378
|
this.connectionInfo.addonVersion = message.addon_version ?? "";
|
|
36379
|
+
this.connectionInfo.addonDigest = message.addon_digest;
|
|
36009
36380
|
this.connectionInfo.editorPid = message.editor_pid;
|
|
36010
36381
|
this.connectionInfo.lspPort = servedPort(message.lsp_port);
|
|
36011
36382
|
this.connectionInfo.dapPort = servedPort(message.dap_port);
|
|
@@ -36182,7 +36553,7 @@ var init_godot_bridge = __esm(() => {
|
|
|
36182
36553
|
|
|
36183
36554
|
// src/godot-path.ts
|
|
36184
36555
|
import { execFile as execFile5 } from "node:child_process";
|
|
36185
|
-
import { existsSync as
|
|
36556
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
36186
36557
|
import { normalize as normalize2 } from "node:path";
|
|
36187
36558
|
import { promisify as promisify5 } from "node:util";
|
|
36188
36559
|
|
|
@@ -36237,7 +36608,7 @@ class GodotLocator2 {
|
|
|
36237
36608
|
return known;
|
|
36238
36609
|
}
|
|
36239
36610
|
let reason;
|
|
36240
|
-
if (path !== "godot" && !
|
|
36611
|
+
if (path !== "godot" && !existsSync13(path)) {
|
|
36241
36612
|
reason = "does not exist";
|
|
36242
36613
|
} else {
|
|
36243
36614
|
try {
|
|
@@ -36259,7 +36630,7 @@ var init_godot_path = __esm(() => {
|
|
|
36259
36630
|
});
|
|
36260
36631
|
|
|
36261
36632
|
// src/issues.ts
|
|
36262
|
-
import { createHash as
|
|
36633
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
36263
36634
|
import process7 from "node:process";
|
|
36264
36635
|
function feedbackNotice() {
|
|
36265
36636
|
return JSON.stringify({
|
|
@@ -36277,7 +36648,7 @@ function runtime2() {
|
|
|
36277
36648
|
function defectSignature2(where, message) {
|
|
36278
36649
|
const normalised = `${where}
|
|
36279
36650
|
${message}`.toLowerCase().replaceAll("\\", "/").replaceAll(/(?:[a-z]:)?(?:\/+[\w.-]+){2,}/gu, "<path>").replaceAll(/\d+/gu, "0");
|
|
36280
|
-
return
|
|
36651
|
+
return createHash4("sha256").update(normalised).digest("hex").slice(0, 8);
|
|
36281
36652
|
}
|
|
36282
36653
|
function facts2(where, message, godotVersion) {
|
|
36283
36654
|
return [
|
|
@@ -36613,8 +36984,8 @@ var init_junit = __esm(() => {
|
|
|
36613
36984
|
import { realpathSync as realpathSync2 } from "node:fs";
|
|
36614
36985
|
import { readFile, realpath } from "node:fs/promises";
|
|
36615
36986
|
import { createConnection as createConnection3 } from "node:net";
|
|
36616
|
-
import { dirname as
|
|
36617
|
-
import { fileURLToPath as
|
|
36987
|
+
import { dirname as dirname13, resolve as resolve4 } from "node:path";
|
|
36988
|
+
import { fileURLToPath as fileURLToPath4, pathToFileURL } from "node:url";
|
|
36618
36989
|
function diagnosticsKey(uri) {
|
|
36619
36990
|
let decoded;
|
|
36620
36991
|
try {
|
|
@@ -36626,7 +36997,7 @@ function diagnosticsKey(uri) {
|
|
|
36626
36997
|
return decoded;
|
|
36627
36998
|
}
|
|
36628
36999
|
try {
|
|
36629
|
-
return realpathSync2.native(
|
|
37000
|
+
return realpathSync2.native(fileURLToPath4(decoded)).toLowerCase();
|
|
36630
37001
|
} catch {
|
|
36631
37002
|
return decoded.toLowerCase();
|
|
36632
37003
|
}
|
|
@@ -36741,7 +37112,7 @@ class GodotLSPClient {
|
|
|
36741
37112
|
if (this.initialized) {
|
|
36742
37113
|
return;
|
|
36743
37114
|
}
|
|
36744
|
-
const rootPath = this.rootPath ??
|
|
37115
|
+
const rootPath = this.rootPath ?? dirname13(resolve4(filePath));
|
|
36745
37116
|
await this.initialize(rootPath);
|
|
36746
37117
|
}
|
|
36747
37118
|
async sendRequest(method, params) {
|
|
@@ -37186,13 +37557,13 @@ var init_process_time = __esm(() => {
|
|
|
37186
37557
|
});
|
|
37187
37558
|
|
|
37188
37559
|
// src/project-scan.ts
|
|
37189
|
-
import { existsSync as
|
|
37190
|
-
import { join as
|
|
37560
|
+
import { existsSync as existsSync14, readdirSync as readdirSync7, readFileSync as readFileSync17 } from "node:fs";
|
|
37561
|
+
import { join as join19 } from "node:path";
|
|
37191
37562
|
function skipped(name) {
|
|
37192
37563
|
return name.startsWith(".") || SKIPPED.has(name);
|
|
37193
37564
|
}
|
|
37194
37565
|
function steppedOver(directory) {
|
|
37195
|
-
return
|
|
37566
|
+
return existsSync14(join19(directory, ".gdignore"));
|
|
37196
37567
|
}
|
|
37197
37568
|
function projectStructure(projectPath) {
|
|
37198
37569
|
const structure = { scenes: 0, scripts: 0, assets: 0, other: 0 };
|
|
@@ -37200,12 +37571,12 @@ function projectStructure(projectPath) {
|
|
|
37200
37571
|
if (steppedOver(directory)) {
|
|
37201
37572
|
return;
|
|
37202
37573
|
}
|
|
37203
|
-
for (const entry of
|
|
37574
|
+
for (const entry of readdirSync7(directory, { withFileTypes: true })) {
|
|
37204
37575
|
if (skipped(entry.name)) {
|
|
37205
37576
|
continue;
|
|
37206
37577
|
}
|
|
37207
37578
|
if (entry.isDirectory()) {
|
|
37208
|
-
visit(
|
|
37579
|
+
visit(join19(directory, entry.name));
|
|
37209
37580
|
} else if (entry.isFile()) {
|
|
37210
37581
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
37211
37582
|
if (extension === "tscn") {
|
|
@@ -37229,20 +37600,20 @@ function scriptsWithoutUid(projectPath) {
|
|
|
37229
37600
|
if (steppedOver(directory)) {
|
|
37230
37601
|
return;
|
|
37231
37602
|
}
|
|
37232
|
-
for (const entry of
|
|
37603
|
+
for (const entry of readdirSync7(directory, { withFileTypes: true })) {
|
|
37233
37604
|
if (skipped(entry.name)) {
|
|
37234
37605
|
continue;
|
|
37235
37606
|
}
|
|
37236
37607
|
const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
37237
37608
|
if (entry.isDirectory()) {
|
|
37238
|
-
visit(
|
|
37609
|
+
visit(join19(directory, entry.name), spelled);
|
|
37239
37610
|
continue;
|
|
37240
37611
|
}
|
|
37241
37612
|
if (!entry.isFile()) {
|
|
37242
37613
|
continue;
|
|
37243
37614
|
}
|
|
37244
37615
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
37245
|
-
if (CARRIES_A_UID.has(extension) && !
|
|
37616
|
+
if (CARRIES_A_UID.has(extension) && !existsSync14(join19(directory, `${entry.name}.uid`))) {
|
|
37246
37617
|
found.push(spelled);
|
|
37247
37618
|
}
|
|
37248
37619
|
}
|
|
@@ -37270,14 +37641,14 @@ function searchProject(projectPath, options) {
|
|
|
37270
37641
|
if (steppedOver(directory)) {
|
|
37271
37642
|
return;
|
|
37272
37643
|
}
|
|
37273
|
-
for (const entry of
|
|
37644
|
+
for (const entry of readdirSync7(directory, { withFileTypes: true })) {
|
|
37274
37645
|
if (full()) {
|
|
37275
37646
|
return;
|
|
37276
37647
|
}
|
|
37277
37648
|
if (skipped(entry.name)) {
|
|
37278
37649
|
continue;
|
|
37279
37650
|
}
|
|
37280
|
-
const entryPath =
|
|
37651
|
+
const entryPath = join19(directory, entry.name);
|
|
37281
37652
|
if (entry.isDirectory()) {
|
|
37282
37653
|
visit(entryPath);
|
|
37283
37654
|
continue;
|
|
@@ -37288,7 +37659,7 @@ function searchProject(projectPath, options) {
|
|
|
37288
37659
|
}
|
|
37289
37660
|
result.summary.files_searched += 1;
|
|
37290
37661
|
const matches = [];
|
|
37291
|
-
for (const [index, line] of
|
|
37662
|
+
for (const [index, line] of readFileSync17(entryPath, "utf8").split(`
|
|
37292
37663
|
`).entries()) {
|
|
37293
37664
|
if (full()) {
|
|
37294
37665
|
break;
|
|
@@ -37317,16 +37688,16 @@ var init_project_scan = __esm(() => {
|
|
|
37317
37688
|
});
|
|
37318
37689
|
|
|
37319
37690
|
// src/restart-note.ts
|
|
37320
|
-
import { mkdirSync as
|
|
37321
|
-
import { dirname as
|
|
37691
|
+
import { mkdirSync as mkdirSync11, readFileSync as readFileSync18, rmSync as rmSync9, writeFileSync as writeFileSync13 } from "node:fs";
|
|
37692
|
+
import { dirname as dirname14, join as join20 } from "node:path";
|
|
37322
37693
|
function restartNotePath(projectPath) {
|
|
37323
|
-
return
|
|
37694
|
+
return join20(projectPath, ".godot", "gdharness-restart.json");
|
|
37324
37695
|
}
|
|
37325
37696
|
function noteRestartBegun(note) {
|
|
37326
37697
|
const path = restartNotePath(note.projectPath);
|
|
37327
37698
|
try {
|
|
37328
|
-
|
|
37329
|
-
|
|
37699
|
+
mkdirSync11(dirname14(path), { recursive: true });
|
|
37700
|
+
writeFileSync13(path, `${JSON.stringify(note, null, 2)}
|
|
37330
37701
|
`, "utf8");
|
|
37331
37702
|
} catch (error) {
|
|
37332
37703
|
console.error(`[SERVER] Could not record the restart at ${path}: ${errorMessage(error)}`);
|
|
@@ -37334,7 +37705,7 @@ function noteRestartBegun(note) {
|
|
|
37334
37705
|
}
|
|
37335
37706
|
function restartOwed(projectPath) {
|
|
37336
37707
|
try {
|
|
37337
|
-
const parsed = JSON.parse(
|
|
37708
|
+
const parsed = JSON.parse(readFileSync18(restartNotePath(projectPath), "utf8"));
|
|
37338
37709
|
if (typeof parsed !== "object" || parsed === null) {
|
|
37339
37710
|
return null;
|
|
37340
37711
|
}
|
|
@@ -37349,7 +37720,7 @@ function restartOwed(projectPath) {
|
|
|
37349
37720
|
}
|
|
37350
37721
|
function restartSettled(projectPath) {
|
|
37351
37722
|
try {
|
|
37352
|
-
|
|
37723
|
+
rmSync9(restartNotePath(projectPath), { force: true });
|
|
37353
37724
|
} catch (error) {
|
|
37354
37725
|
console.error(`[SERVER] Could not clear the restart note: ${errorMessage(error)}`);
|
|
37355
37726
|
}
|
|
@@ -37360,36 +37731,36 @@ var init_restart_note = __esm(() => {
|
|
|
37360
37731
|
|
|
37361
37732
|
// src/run-record.ts
|
|
37362
37733
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
37363
|
-
import { mkdirSync as
|
|
37364
|
-
import { basename as basename3, join as
|
|
37734
|
+
import { mkdirSync as mkdirSync12, openSync as openSync2, readdirSync as readdirSync8, readFileSync as readFileSync19, rmSync as rmSync10, statSync as statSync7, writeFileSync as writeFileSync14 } from "node:fs";
|
|
37735
|
+
import { basename as basename3, join as join21 } from "node:path";
|
|
37365
37736
|
function runsDirectory() {
|
|
37366
|
-
return
|
|
37737
|
+
return join21(runtimeDirectory(), "runs");
|
|
37367
37738
|
}
|
|
37368
37739
|
function recordPath() {
|
|
37369
|
-
return
|
|
37740
|
+
return join21(runsDirectory(), "run.json");
|
|
37370
37741
|
}
|
|
37371
37742
|
function recordPaths() {
|
|
37372
|
-
return runtimeDirectories().map((directory) =>
|
|
37743
|
+
return runtimeDirectories().map((directory) => join21(directory, "runs", "run.json"));
|
|
37373
37744
|
}
|
|
37374
37745
|
function openTranscript(startedAt) {
|
|
37375
|
-
|
|
37376
|
-
const path =
|
|
37377
|
-
return { path, fd:
|
|
37746
|
+
mkdirSync12(runsDirectory(), { recursive: true });
|
|
37747
|
+
const path = join21(runsDirectory(), `run-${startedAt}.log`);
|
|
37748
|
+
return { path, fd: openSync2(path, "a") };
|
|
37378
37749
|
}
|
|
37379
37750
|
function writeRunRecord(record) {
|
|
37380
|
-
|
|
37381
|
-
|
|
37751
|
+
mkdirSync12(runsDirectory(), { recursive: true });
|
|
37752
|
+
writeFileSync14(recordPath(), JSON.stringify(record, null, 2), "utf8");
|
|
37382
37753
|
}
|
|
37383
37754
|
function editorNotePath() {
|
|
37384
|
-
return
|
|
37755
|
+
return join21(runsDirectory(), "editor-run.json");
|
|
37385
37756
|
}
|
|
37386
37757
|
function writeEditorRunNote(note) {
|
|
37387
|
-
|
|
37388
|
-
|
|
37758
|
+
mkdirSync12(runsDirectory(), { recursive: true });
|
|
37759
|
+
writeFileSync14(editorNotePath(), JSON.stringify(note, null, 2), "utf8");
|
|
37389
37760
|
}
|
|
37390
37761
|
function readEditorRunNote() {
|
|
37391
37762
|
for (const directory of runtimeDirectories()) {
|
|
37392
|
-
const note = editorNoteAt(
|
|
37763
|
+
const note = editorNoteAt(join21(directory, "runs", "editor-run.json"));
|
|
37393
37764
|
if (note !== null) {
|
|
37394
37765
|
return note;
|
|
37395
37766
|
}
|
|
@@ -37399,7 +37770,7 @@ function readEditorRunNote() {
|
|
|
37399
37770
|
function editorNoteAt(path) {
|
|
37400
37771
|
let parsed;
|
|
37401
37772
|
try {
|
|
37402
|
-
parsed = JSON.parse(
|
|
37773
|
+
parsed = JSON.parse(readFileSync19(path, "utf8"));
|
|
37403
37774
|
} catch {
|
|
37404
37775
|
return null;
|
|
37405
37776
|
}
|
|
@@ -37422,7 +37793,7 @@ function recordRunEnded(pid, exitCode) {
|
|
|
37422
37793
|
return;
|
|
37423
37794
|
}
|
|
37424
37795
|
try {
|
|
37425
|
-
|
|
37796
|
+
writeFileSync14(path, JSON.stringify({ ...record, exitCode }, null, 2), "utf8");
|
|
37426
37797
|
} catch {}
|
|
37427
37798
|
}
|
|
37428
37799
|
function readRunRecord() {
|
|
@@ -37437,7 +37808,7 @@ function readRunRecord() {
|
|
|
37437
37808
|
function recordAt(path) {
|
|
37438
37809
|
let parsed;
|
|
37439
37810
|
try {
|
|
37440
|
-
parsed = JSON.parse(
|
|
37811
|
+
parsed = JSON.parse(readFileSync19(path, "utf8"));
|
|
37441
37812
|
} catch {
|
|
37442
37813
|
return null;
|
|
37443
37814
|
}
|
|
@@ -37482,7 +37853,7 @@ function runningAs(pid) {
|
|
|
37482
37853
|
const began = startedAt(pid, null);
|
|
37483
37854
|
const when = began === null ? {} : { startedAt: began };
|
|
37484
37855
|
if (process.platform === "linux") {
|
|
37485
|
-
const raw =
|
|
37856
|
+
const raw = readFileSync19(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
|
|
37486
37857
|
return raw === "" ? null : { kind: "commandLine", text: raw, ...when };
|
|
37487
37858
|
}
|
|
37488
37859
|
const args = execFileSync2("ps", ["-p", String(pid), "-o", "args="], {
|
|
@@ -37609,13 +37980,13 @@ function clearRunRecord(ours = () => true) {
|
|
|
37609
37980
|
continue;
|
|
37610
37981
|
}
|
|
37611
37982
|
try {
|
|
37612
|
-
|
|
37983
|
+
rmSync10(path);
|
|
37613
37984
|
} catch {}
|
|
37614
37985
|
}
|
|
37615
37986
|
}
|
|
37616
37987
|
function ageMs(path, now) {
|
|
37617
37988
|
try {
|
|
37618
|
-
return now -
|
|
37989
|
+
return now - statSync7(path).mtimeMs;
|
|
37619
37990
|
} catch {
|
|
37620
37991
|
return 0;
|
|
37621
37992
|
}
|
|
@@ -37625,7 +37996,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
37625
37996
|
const keep = readRunRecord()?.transcript;
|
|
37626
37997
|
let entries;
|
|
37627
37998
|
try {
|
|
37628
|
-
entries =
|
|
37999
|
+
entries = readdirSync8(directory);
|
|
37629
38000
|
} catch {
|
|
37630
38001
|
return;
|
|
37631
38002
|
}
|
|
@@ -37633,12 +38004,12 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
37633
38004
|
if (!entry.startsWith("run-") || !entry.endsWith(".log")) {
|
|
37634
38005
|
continue;
|
|
37635
38006
|
}
|
|
37636
|
-
const path =
|
|
38007
|
+
const path = join21(directory, entry);
|
|
37637
38008
|
if (path === keep || ageMs(path, now) < keepMs) {
|
|
37638
38009
|
continue;
|
|
37639
38010
|
}
|
|
37640
38011
|
try {
|
|
37641
|
-
|
|
38012
|
+
rmSync10(path);
|
|
37642
38013
|
} catch {}
|
|
37643
38014
|
}
|
|
37644
38015
|
}
|
|
@@ -37654,22 +38025,22 @@ import { execFile as execFile7, spawn } from "node:child_process";
|
|
|
37654
38025
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
37655
38026
|
import {
|
|
37656
38027
|
appendFileSync,
|
|
37657
|
-
closeSync,
|
|
37658
|
-
existsSync as
|
|
38028
|
+
closeSync as closeSync2,
|
|
38029
|
+
existsSync as existsSync15,
|
|
37659
38030
|
fstatSync,
|
|
37660
|
-
mkdirSync as
|
|
38031
|
+
mkdirSync as mkdirSync13,
|
|
37661
38032
|
mkdtempSync as mkdtempSync3,
|
|
37662
|
-
openSync as
|
|
37663
|
-
readdirSync as
|
|
37664
|
-
readFileSync as
|
|
37665
|
-
readSync,
|
|
38033
|
+
openSync as openSync3,
|
|
38034
|
+
readdirSync as readdirSync9,
|
|
38035
|
+
readFileSync as readFileSync20,
|
|
38036
|
+
readSync as readSync2,
|
|
37666
38037
|
rmdirSync,
|
|
37667
|
-
statSync as
|
|
38038
|
+
statSync as statSync8
|
|
37668
38039
|
} from "node:fs";
|
|
37669
38040
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
37670
|
-
import { basename as basename4, dirname as
|
|
38041
|
+
import { basename as basename4, dirname as dirname15, isAbsolute as isAbsolute3, join as join22, normalize as normalize3, relative as relative3 } from "node:path";
|
|
37671
38042
|
import { setTimeout as delay3 } from "node:timers/promises";
|
|
37672
|
-
import { fileURLToPath as
|
|
38043
|
+
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
37673
38044
|
import { promisify as promisify7 } from "node:util";
|
|
37674
38045
|
function patienceForFrames(frames, atLeast) {
|
|
37675
38046
|
return Math.max(atLeast, frames / SLOWEST_FRAME_RATE * 1000 + atLeast);
|
|
@@ -37909,13 +38280,13 @@ function aboveTheRunner(entry) {
|
|
|
37909
38280
|
}
|
|
37910
38281
|
function elsewhereIn(projectPath, asked) {
|
|
37911
38282
|
const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
|
|
37912
|
-
if (wanted === "" ||
|
|
38283
|
+
if (wanted === "" || existsSync15(join22(projectPath, wanted))) {
|
|
37913
38284
|
return "";
|
|
37914
38285
|
}
|
|
37915
38286
|
const root = (wanted.split("/")[0] ?? "").toLowerCase();
|
|
37916
38287
|
let near = [];
|
|
37917
38288
|
try {
|
|
37918
|
-
near =
|
|
38289
|
+
near = readdirSync9(projectPath, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.toLowerCase() !== root).map((entry) => entry.name).filter((name) => name.toLowerCase().startsWith("test") || root.startsWith(name.toLowerCase()));
|
|
37919
38290
|
} catch {
|
|
37920
38291
|
return "";
|
|
37921
38292
|
}
|
|
@@ -37923,7 +38294,7 @@ function elsewhereIn(projectPath, asked) {
|
|
|
37923
38294
|
return near.length === 0 ? "" : ` The project does have ${there}.`;
|
|
37924
38295
|
}
|
|
37925
38296
|
function hasMainScene(projectFile) {
|
|
37926
|
-
const scene = parseProjectGodot(
|
|
38297
|
+
const scene = parseProjectGodot(readFileSync20(projectFile, "utf8"))["application"]?.["run/main_scene"];
|
|
37927
38298
|
return typeof scene === "string" && scene !== "";
|
|
37928
38299
|
}
|
|
37929
38300
|
function runtimeAddonAnnouncesEditors(projectPath) {
|
|
@@ -37931,13 +38302,13 @@ function runtimeAddonAnnouncesEditors(projectPath) {
|
|
|
37931
38302
|
return false;
|
|
37932
38303
|
}
|
|
37933
38304
|
try {
|
|
37934
|
-
return
|
|
38305
|
+
return readFileSync20(join22(projectPath, RUNTIME_AUTOLOAD2.path), "utf8").includes('"editor_pid"');
|
|
37935
38306
|
} catch {
|
|
37936
38307
|
return false;
|
|
37937
38308
|
}
|
|
37938
38309
|
}
|
|
37939
38310
|
function editorPlaysHeadless(projectFile) {
|
|
37940
|
-
const runArgs = parseProjectGodot(
|
|
38311
|
+
const runArgs = parseProjectGodot(readFileSync20(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
|
|
37941
38312
|
return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
|
|
37942
38313
|
}
|
|
37943
38314
|
function camelCased(params) {
|
|
@@ -37956,7 +38327,7 @@ function needsItsOwnProcess(asked) {
|
|
|
37956
38327
|
class GodotServer {
|
|
37957
38328
|
mcp;
|
|
37958
38329
|
locator = new GodotLocator2;
|
|
37959
|
-
operationsScript =
|
|
38330
|
+
operationsScript = join22(__dirname2, "godot", "operations", "godot_operations.gd");
|
|
37960
38331
|
godotBridge;
|
|
37961
38332
|
tools = buildToolDefinitions();
|
|
37962
38333
|
activeProcess = null;
|
|
@@ -38621,8 +38992,8 @@ class GodotServer {
|
|
|
38621
38992
|
if (path === undefined) {
|
|
38622
38993
|
return { ok: false, response: this.createErrorResponse("projectPath is required.") };
|
|
38623
38994
|
}
|
|
38624
|
-
const file =
|
|
38625
|
-
if (!
|
|
38995
|
+
const file = join22(path, "project.godot");
|
|
38996
|
+
if (!existsSync15(file)) {
|
|
38626
38997
|
return {
|
|
38627
38998
|
ok: false,
|
|
38628
38999
|
response: this.createErrorResponse(`Not a Godot project: ${path}`, [
|
|
@@ -38890,7 +39261,7 @@ class GodotServer {
|
|
|
38890
39261
|
if (!engine.ok) {
|
|
38891
39262
|
return engine.response;
|
|
38892
39263
|
}
|
|
38893
|
-
const application = parseProjectGodot(
|
|
39264
|
+
const application = parseProjectGodot(readFileSync20(project.value.file, "utf8"))["application"] ?? {};
|
|
38894
39265
|
const name = application["config/name"];
|
|
38895
39266
|
const mainScene = application["run/main_scene"];
|
|
38896
39267
|
const info = {
|
|
@@ -38951,18 +39322,18 @@ class GodotServer {
|
|
|
38951
39322
|
if (!engine.ok) {
|
|
38952
39323
|
return engine.response;
|
|
38953
39324
|
}
|
|
38954
|
-
const folder =
|
|
39325
|
+
const folder = dirname15(output.absolutePath);
|
|
38955
39326
|
try {
|
|
38956
|
-
|
|
39327
|
+
mkdirSync13(folder, { recursive: true });
|
|
38957
39328
|
} catch (error) {
|
|
38958
|
-
return this.createErrorResponse(`Cannot create ${
|
|
39329
|
+
return this.createErrorResponse(`Cannot create ${dirname15(output.relativePath)} to export into: ${errorMessage(error)}`);
|
|
38959
39330
|
}
|
|
38960
39331
|
const debug = readBoolean(args, "debug") ?? false;
|
|
38961
|
-
const exportLogs = mkdtempSync3(
|
|
39332
|
+
const exportLogs = mkdtempSync3(join22(tmpdir5(), "gdharness-export-"));
|
|
38962
39333
|
const exportArgs = [
|
|
38963
39334
|
"--headless",
|
|
38964
39335
|
"--log-file",
|
|
38965
|
-
|
|
39336
|
+
join22(exportLogs, "engine.log"),
|
|
38966
39337
|
"--path",
|
|
38967
39338
|
project.value.path,
|
|
38968
39339
|
debug ? "--export-debug" : "--export-release",
|
|
@@ -38990,7 +39361,7 @@ class GodotServer {
|
|
|
38990
39361
|
log.finish();
|
|
38991
39362
|
const problems = log.select({ severity: "warning", sinceLastCall: false, limit: 200 });
|
|
38992
39363
|
const verdict = {
|
|
38993
|
-
exported: exitCode === 0 && log.count("error") === 0 &&
|
|
39364
|
+
exported: exitCode === 0 && log.count("error") === 0 && existsSync15(output.absolutePath),
|
|
38994
39365
|
preset,
|
|
38995
39366
|
outputPath: output.relativePath,
|
|
38996
39367
|
debug,
|
|
@@ -39020,7 +39391,7 @@ class GodotServer {
|
|
|
39020
39391
|
return contained.response;
|
|
39021
39392
|
}
|
|
39022
39393
|
const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
|
|
39023
|
-
if (!
|
|
39394
|
+
if (!existsSync15(join22(project.value.path, runner))) {
|
|
39024
39395
|
return this.createErrorResponse(`gdUnit4 is not installed in this project: no ${runner}.`, [
|
|
39025
39396
|
"Install gdUnit4 under addons/gdUnit4, from https://github.com/godot-gdunit-labs/gdUnit4"
|
|
39026
39397
|
]);
|
|
@@ -39055,7 +39426,7 @@ class GodotServer {
|
|
|
39055
39426
|
];
|
|
39056
39427
|
const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 600000;
|
|
39057
39428
|
this.logDebug(`Running tests: ${engine.value} ${cmdArgs.join(" ")}`);
|
|
39058
|
-
const userData = mkdtempSync3(
|
|
39429
|
+
const userData = mkdtempSync3(join22(tmpdir5(), "gdharness-tests-"));
|
|
39059
39430
|
const run = this.spawnGame(engine.value, cmdArgs, userDataIn(userData));
|
|
39060
39431
|
const hung = await new Promise((resolve) => {
|
|
39061
39432
|
const timer = setTimeout(() => {
|
|
@@ -39071,21 +39442,21 @@ class GodotServer {
|
|
|
39071
39442
|
resolve(false);
|
|
39072
39443
|
});
|
|
39073
39444
|
});
|
|
39074
|
-
const reportsDir =
|
|
39445
|
+
const reportsDir = join22(project.value.path, ".godot", ...ours.split("/"));
|
|
39075
39446
|
let report = null;
|
|
39076
39447
|
let reportProblem = null;
|
|
39077
39448
|
try {
|
|
39078
|
-
const written =
|
|
39449
|
+
const written = existsSync15(reportsDir) ? readdirSync9(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
|
|
39079
39450
|
const newest = written.at(-1);
|
|
39080
39451
|
if (newest !== undefined) {
|
|
39081
|
-
report = parseJUnit(
|
|
39452
|
+
report = parseJUnit(readFileSync20(join22(reportsDir, newest, "results.xml"), "utf8"));
|
|
39082
39453
|
}
|
|
39083
39454
|
} catch (error) {
|
|
39084
39455
|
reportProblem = errorMessage(error);
|
|
39085
39456
|
} finally {
|
|
39086
39457
|
discard(reportsDir, userData);
|
|
39087
39458
|
try {
|
|
39088
|
-
rmdirSync(
|
|
39459
|
+
rmdirSync(dirname15(reportsDir));
|
|
39089
39460
|
} catch {}
|
|
39090
39461
|
}
|
|
39091
39462
|
const engineEntries = run.log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries.map(aboveTheRunner);
|
|
@@ -39204,7 +39575,7 @@ class GodotServer {
|
|
|
39204
39575
|
warnings: diagnostics.length - errors,
|
|
39205
39576
|
diagnostics,
|
|
39206
39577
|
...disagrees
|
|
39207
|
-
}, this.godotBridge.getStatus().addonVersion, SERVER_VERSION));
|
|
39578
|
+
}, this.godotBridge.getStatus().addonVersion, SERVER_VERSION, this.godotBridge.getStatus().addonDigest, shippedEditorDigest()));
|
|
39208
39579
|
}
|
|
39209
39580
|
whereTheFileDisagrees(diagnostics, args) {
|
|
39210
39581
|
const messages = diagnostics.map((entry) => asParams(entry)["message"]).filter((message) => typeof message === "string");
|
|
@@ -39217,11 +39588,11 @@ class GodotServer {
|
|
|
39217
39588
|
const cached = cachedClasses(projectPath);
|
|
39218
39589
|
const contradicted = cached === null ? [] : contradictedDiagnostics(messages, cached, (resourcePath) => {
|
|
39219
39590
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
39220
|
-
if (!contained.ok || !
|
|
39591
|
+
if (!contained.ok || !existsSync15(contained.absolutePath)) {
|
|
39221
39592
|
return null;
|
|
39222
39593
|
}
|
|
39223
39594
|
try {
|
|
39224
|
-
return
|
|
39595
|
+
return readFileSync20(contained.absolutePath, "utf8");
|
|
39225
39596
|
} catch {
|
|
39226
39597
|
return null;
|
|
39227
39598
|
}
|
|
@@ -39271,11 +39642,11 @@ class GodotServer {
|
|
|
39271
39642
|
}
|
|
39272
39643
|
sourceOfClass(projectPath, resourcePath) {
|
|
39273
39644
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
39274
|
-
if (!contained.ok || !
|
|
39645
|
+
if (!contained.ok || !existsSync15(contained.absolutePath)) {
|
|
39275
39646
|
return null;
|
|
39276
39647
|
}
|
|
39277
39648
|
try {
|
|
39278
|
-
return
|
|
39649
|
+
return readFileSync20(contained.absolutePath, "utf8");
|
|
39279
39650
|
} catch {
|
|
39280
39651
|
return null;
|
|
39281
39652
|
}
|
|
@@ -39391,7 +39762,7 @@ class GodotServer {
|
|
|
39391
39762
|
getEditorStatusPayload() {
|
|
39392
39763
|
const status = this.godotBridge.getStatus();
|
|
39393
39764
|
const isPortConflict = this.bridgeStartupError?.includes("EADDRINUSE") ?? false;
|
|
39394
|
-
const stale = status.connected && status.addonVersion
|
|
39765
|
+
const stale = status.connected && editorIsStale(status.addonVersion, SERVER_VERSION, status.addonDigest, shippedEditorDigest());
|
|
39395
39766
|
const unfinished = this.restartLeftUnfinished(status.connected);
|
|
39396
39767
|
return {
|
|
39397
39768
|
...status,
|
|
@@ -39408,7 +39779,7 @@ class GodotServer {
|
|
|
39408
39779
|
awaitingLaunchedEditor: status.connected || !this.launchedEditorIsUp() ? undefined : this.launchedEditor?.pid ?? undefined,
|
|
39409
39780
|
...status.connected ? this.whatTheLaunchedEditorDropped() : {},
|
|
39410
39781
|
startupError: this.bridgeStartupError,
|
|
39411
|
-
staleNote: stale ? addonMismatch(status.addonVersion, SERVER_VERSION) : undefined,
|
|
39782
|
+
staleNote: stale ? addonMismatch(status.addonVersion, SERVER_VERSION, status.addonDigest, shippedEditorDigest()) : undefined,
|
|
39412
39783
|
...this.breakpointsAtRisk(),
|
|
39413
39784
|
retryingBridge: this.bridgeRetry === null ? undefined : true,
|
|
39414
39785
|
announcedAt: this.announcedAt ?? undefined,
|
|
@@ -39504,7 +39875,7 @@ class GodotServer {
|
|
|
39504
39875
|
return null;
|
|
39505
39876
|
}
|
|
39506
39877
|
try {
|
|
39507
|
-
return settingKeys(
|
|
39878
|
+
return settingKeys(readFileSync20(join22(projectPath, "project.godot"), "utf8"));
|
|
39508
39879
|
} catch {
|
|
39509
39880
|
return null;
|
|
39510
39881
|
}
|
|
@@ -39540,8 +39911,8 @@ class GodotServer {
|
|
|
39540
39911
|
editorPid: now.editorPid,
|
|
39541
39912
|
addonVersion: now.addonVersion,
|
|
39542
39913
|
serverVersion: SERVER_VERSION,
|
|
39543
|
-
addonIsStale: now.addonVersion
|
|
39544
|
-
staleNote: addonMismatch(now.addonVersion, SERVER_VERSION),
|
|
39914
|
+
addonIsStale: editorIsStale(now.addonVersion, SERVER_VERSION, now.addonDigest, shippedEditorDigest()),
|
|
39915
|
+
staleNote: addonMismatch(now.addonVersion, SERVER_VERSION, now.addonDigest, shippedEditorDigest()),
|
|
39545
39916
|
...this.whatTheEditorDropped(settingsBefore, after),
|
|
39546
39917
|
tookMs: Date.now() - began
|
|
39547
39918
|
});
|
|
@@ -39634,7 +40005,8 @@ class GodotServer {
|
|
|
39634
40005
|
});
|
|
39635
40006
|
}
|
|
39636
40007
|
async openAnEditor(engine, projectPath, ports) {
|
|
39637
|
-
|
|
40008
|
+
clearEditorLog(projectPath);
|
|
40009
|
+
const editor = spawn(engine, editorArguments(projectPath, ports, editorLogPath(projectPath)), {
|
|
39638
40010
|
stdio: "ignore",
|
|
39639
40011
|
detached: true,
|
|
39640
40012
|
env: {
|
|
@@ -39645,6 +40017,9 @@ class GodotServer {
|
|
|
39645
40017
|
[OPENED_BY_A_SERVER]: "1"
|
|
39646
40018
|
}
|
|
39647
40019
|
});
|
|
40020
|
+
if (editor.pid !== undefined) {
|
|
40021
|
+
writeEditorLogNote(projectPath, editor.pid);
|
|
40022
|
+
}
|
|
39648
40023
|
const started = await new Promise((resolve) => {
|
|
39649
40024
|
editor.once("spawn", () => {
|
|
39650
40025
|
resolve(null);
|
|
@@ -39751,7 +40126,7 @@ class GodotServer {
|
|
|
39751
40126
|
}
|
|
39752
40127
|
async runtimeUp(projectPath, before, budget, withArgs) {
|
|
39753
40128
|
const budgetMs = budget.ms;
|
|
39754
|
-
if (!
|
|
40129
|
+
if (!existsSync15(join22(projectPath, RUNTIME_AUTOLOAD2.path))) {
|
|
39755
40130
|
return runtimeVerdict(null, { addon: false, budgetMs, heldAt: null, running: false, withArgs });
|
|
39756
40131
|
}
|
|
39757
40132
|
let asking = null;
|
|
@@ -39814,7 +40189,7 @@ class GodotServer {
|
|
|
39814
40189
|
}
|
|
39815
40190
|
let announcedAt;
|
|
39816
40191
|
try {
|
|
39817
|
-
announcedAt =
|
|
40192
|
+
announcedAt = statSync8(endpoint.file).mtimeMs;
|
|
39818
40193
|
} catch {
|
|
39819
40194
|
return;
|
|
39820
40195
|
}
|
|
@@ -39850,7 +40225,7 @@ class GodotServer {
|
|
|
39850
40225
|
}
|
|
39851
40226
|
const startedAt = Date.now();
|
|
39852
40227
|
const transcript = openTranscript(startedAt);
|
|
39853
|
-
|
|
40228
|
+
closeSync2(transcript.fd);
|
|
39854
40229
|
const playAnswer = asParams(JSON.parse(answer.content[0]?.text ?? "{}"));
|
|
39855
40230
|
const played = {
|
|
39856
40231
|
process: null,
|
|
@@ -39917,7 +40292,7 @@ class GodotServer {
|
|
|
39917
40292
|
}
|
|
39918
40293
|
let handle;
|
|
39919
40294
|
try {
|
|
39920
|
-
handle =
|
|
40295
|
+
handle = openSync3(game.errorReport, "r");
|
|
39921
40296
|
} catch {
|
|
39922
40297
|
return;
|
|
39923
40298
|
}
|
|
@@ -39928,7 +40303,7 @@ class GodotServer {
|
|
|
39928
40303
|
return;
|
|
39929
40304
|
}
|
|
39930
40305
|
const buffer = Buffer.alloc(size - from);
|
|
39931
|
-
const read =
|
|
40306
|
+
const read = readSync2(handle, buffer, 0, buffer.length, from);
|
|
39932
40307
|
game.errorReportOffset = from + read;
|
|
39933
40308
|
const reported = buffer.subarray(0, read);
|
|
39934
40309
|
if (game.transcript !== null) {
|
|
@@ -39937,7 +40312,7 @@ class GodotServer {
|
|
|
39937
40312
|
game.log.append("stderr", reported);
|
|
39938
40313
|
}
|
|
39939
40314
|
} finally {
|
|
39940
|
-
|
|
40315
|
+
closeSync2(handle);
|
|
39941
40316
|
}
|
|
39942
40317
|
}
|
|
39943
40318
|
recordWhatItBrokeOn(game) {
|
|
@@ -40031,7 +40406,7 @@ class GodotServer {
|
|
|
40031
40406
|
...env ? { env } : {}
|
|
40032
40407
|
});
|
|
40033
40408
|
} finally {
|
|
40034
|
-
|
|
40409
|
+
closeSync2(transcript.fd);
|
|
40035
40410
|
}
|
|
40036
40411
|
child.unref();
|
|
40037
40412
|
const log = new GameLog;
|
|
@@ -40078,7 +40453,7 @@ class GodotServer {
|
|
|
40078
40453
|
}
|
|
40079
40454
|
let handle;
|
|
40080
40455
|
try {
|
|
40081
|
-
handle =
|
|
40456
|
+
handle = openSync3(game.transcript, "r");
|
|
40082
40457
|
} catch {
|
|
40083
40458
|
return;
|
|
40084
40459
|
}
|
|
@@ -40088,11 +40463,11 @@ class GodotServer {
|
|
|
40088
40463
|
return;
|
|
40089
40464
|
}
|
|
40090
40465
|
const buffer = Buffer.alloc(size - game.readOffset);
|
|
40091
|
-
const read =
|
|
40466
|
+
const read = readSync2(handle, buffer, 0, buffer.length, game.readOffset);
|
|
40092
40467
|
game.readOffset += read;
|
|
40093
40468
|
game.log.append("transcript", buffer.subarray(0, read));
|
|
40094
40469
|
} finally {
|
|
40095
|
-
|
|
40470
|
+
closeSync2(handle);
|
|
40096
40471
|
}
|
|
40097
40472
|
}
|
|
40098
40473
|
async pickUpWhatTheEditorIsPlaying() {
|
|
@@ -40290,7 +40665,7 @@ class GodotServer {
|
|
|
40290
40665
|
}
|
|
40291
40666
|
const unreadable = this.aRuntimeOfOursIsTooFarOff();
|
|
40292
40667
|
if (unreadable !== null) {
|
|
40293
|
-
const behind = unreadable
|
|
40668
|
+
const behind = whichHalfIsBehind([unreadable]);
|
|
40294
40669
|
return `A game is running for ${unreadable.project.path}, pid ${unreadable.pid}, announced in protocol` + ` ${unreadable.protocol}, which this server does not speak: it speaks ${RUNTIME_PROTOCOL}, so ${behind}.` + " Until then neither editor_run nor the runtime_* tools can reach it." + " A run started here is a separate process rather than a replacement, so starting one leaves" + " this game running as well.";
|
|
40295
40670
|
}
|
|
40296
40671
|
const record = readRunRecord();
|
|
@@ -40415,7 +40790,66 @@ class GodotServer {
|
|
|
40415
40790
|
}
|
|
40416
40791
|
return runIsUp(run, said);
|
|
40417
40792
|
}
|
|
40793
|
+
handleEditorConsole(args) {
|
|
40794
|
+
const status = this.godotBridge.getStatus();
|
|
40795
|
+
const projectPath = status.projectPath ?? this.ownProject ?? undefined;
|
|
40796
|
+
if (projectPath === undefined) {
|
|
40797
|
+
return this.createErrorResponse("No editor is connected, so there is no console to read.", [
|
|
40798
|
+
"editor_status says whether one is connected and whether it may yet be",
|
|
40799
|
+
"editor_launch opens one, and an editor opened here has its console captured"
|
|
40800
|
+
]);
|
|
40801
|
+
}
|
|
40802
|
+
const launched = status.connected ? null : this.launchedEditor?.pid ?? null;
|
|
40803
|
+
if (!status.connected && launched === null) {
|
|
40804
|
+
return this.createErrorResponse("No editor is connected, so there is no console to read.", [
|
|
40805
|
+
"editor_status says whether one is connected and whether it may yet be",
|
|
40806
|
+
"editor_launch opens one, and an editor opened here has its console captured"
|
|
40807
|
+
]);
|
|
40808
|
+
}
|
|
40809
|
+
const editorPid = status.connected ? status.editorPid ?? null : launched;
|
|
40810
|
+
const console2 = editorConsole(projectPath, editorPid, status.connected ? status.openedByAServer === true : true);
|
|
40811
|
+
if ("kind" in console2) {
|
|
40812
|
+
return this.createErrorResponse(theConsoleWasNotCaptured(console2, projectPath), [
|
|
40813
|
+
"editor_launch restart replaces this editor with one whose console is captured",
|
|
40814
|
+
"editor_output with no op answers about the game a run is playing, which is a different log"
|
|
40815
|
+
]);
|
|
40816
|
+
}
|
|
40817
|
+
const severity = readString(args, "severity");
|
|
40818
|
+
const selected = console2.log.select({
|
|
40819
|
+
severity: severity === "error" || severity === "warning" ? severity : "info",
|
|
40820
|
+
sinceLastCall: false,
|
|
40821
|
+
contains: readNonEmptyString(args, "contains"),
|
|
40822
|
+
limit: readPositiveNumber(args, "limit") ?? 200
|
|
40823
|
+
});
|
|
40824
|
+
const everything = console2.log.everything();
|
|
40825
|
+
const grouped = bursts(everything, readPositiveNumber(args, "before") ?? 3);
|
|
40826
|
+
const shown = grouped.slice(0, MOST_GROUPS);
|
|
40827
|
+
return this.jsonTextResponse({
|
|
40828
|
+
editorPid: editorPid ?? undefined,
|
|
40829
|
+
projectPath,
|
|
40830
|
+
...status.connected ? {} : this.launchedEditorIsUp() ? { stillConnecting: true } : { exitedBeforeConnecting: true },
|
|
40831
|
+
capturedIn: console2.path,
|
|
40832
|
+
...console2.of === undefined ? {} : {
|
|
40833
|
+
readFromTheStart: console2.readBytes,
|
|
40834
|
+
consoleBytes: console2.of,
|
|
40835
|
+
truncatedNote: `This editor's console is ${console2.of} bytes and the first ${console2.readBytes} of them were read, which is the start of the session rather than the end of it. Restart the editor to begin a fresh console.`
|
|
40836
|
+
},
|
|
40837
|
+
lines: everything.length,
|
|
40838
|
+
counts: {
|
|
40839
|
+
error: console2.log.count("error"),
|
|
40840
|
+
warning: console2.log.count("warning"),
|
|
40841
|
+
info: console2.log.count("info")
|
|
40842
|
+
},
|
|
40843
|
+
repeated: shown,
|
|
40844
|
+
moreShapes: grouped.length > shown.length ? grouped.length - shown.length : undefined,
|
|
40845
|
+
entries: forAnswer(selected.entries),
|
|
40846
|
+
omitted: selected.omitted === 0 ? undefined : selected.omitted
|
|
40847
|
+
});
|
|
40848
|
+
}
|
|
40418
40849
|
async handleGetDebugOutput(args, waitedMs) {
|
|
40850
|
+
if (readString(args, "op") === "editor") {
|
|
40851
|
+
return this.handleEditorConsole(args);
|
|
40852
|
+
}
|
|
40419
40853
|
await this.pickUpWhatTheEditorIsPlaying();
|
|
40420
40854
|
const run = this.currentRun();
|
|
40421
40855
|
if (!run) {
|
|
@@ -40692,6 +41126,7 @@ class GodotServer {
|
|
|
40692
41126
|
}
|
|
40693
41127
|
async rebuildClassCache(projectPath) {
|
|
40694
41128
|
const before = cachedClasses(projectPath);
|
|
41129
|
+
const writtenBefore = cacheWrittenAt(projectPath);
|
|
40695
41130
|
const noted = readClassNote(projectPath);
|
|
40696
41131
|
const rebuilt = await this.operation("refresh_class_cache", {}, projectPath);
|
|
40697
41132
|
if (!rebuilt.ok) {
|
|
@@ -40702,21 +41137,30 @@ class GodotServer {
|
|
|
40702
41137
|
return rebuilt;
|
|
40703
41138
|
}
|
|
40704
41139
|
const lost = noted === null || before === null ? [] : noted.rebuiltWith.filter((name) => !before.has(name) && after.has(name)).sort();
|
|
40705
|
-
const
|
|
41140
|
+
const cameBack = writtenBefore === null ? [] : declaredSince(projectPath, after, lost, writtenBefore);
|
|
41141
|
+
const droppedByTheEditor = lost.filter((name) => !cameBack.includes(name));
|
|
41142
|
+
const seenLosing = droppedByTheEditor.length > 0 && this.godotBridge.isConnected();
|
|
40706
41143
|
writeClassNote(projectPath, {
|
|
40707
41144
|
rebuiltWith: [...after.keys()].sort(),
|
|
40708
41145
|
...seenLosing ? { shortListEditorPid: this.godotBridge.getStatus().editorPid ?? null } : noted?.shortListEditorPid === undefined ? {} : { shortListEditorPid: noted.shortListEditorPid }
|
|
40709
41146
|
});
|
|
40710
|
-
|
|
41147
|
+
const notes = [];
|
|
41148
|
+
if (seenLosing) {
|
|
41149
|
+
const them = droppedByTheEditor.length === 1 ? "it" : "them";
|
|
41150
|
+
notes.push(`The cache held ${droppedByTheEditor.join(", ")} after the last rebuild and not when this one began, so the editor holding this project wrote it without ${them} in between: it holds a list shorter than the files and writes that list on every save and scan, and each rebuild puts ${them} back. editor_launch restart is what ends it.`);
|
|
41151
|
+
}
|
|
41152
|
+
if (cameBack.length > 0) {
|
|
41153
|
+
notes.push(`${cameBack.join(", ")} left the cache while ${cameBack.length === 1 ? "its script was" : "their scripts were"} gone: the script${cameBack.length === 1 ? " is" : "s are"} newer than the cache the editor last wrote, so it listed what was on disk then. editor_rescan picks ${cameBack.length === 1 ? "it" : "them"} up.`);
|
|
41154
|
+
}
|
|
41155
|
+
if (notes.length === 0) {
|
|
40711
41156
|
return rebuilt;
|
|
40712
41157
|
}
|
|
40713
|
-
const them = lost.length === 1 ? "it" : "them";
|
|
40714
41158
|
return {
|
|
40715
41159
|
...rebuilt,
|
|
40716
41160
|
payload: {
|
|
40717
41161
|
...rebuilt.payload,
|
|
40718
41162
|
lostSinceLastRebuild: lost,
|
|
40719
|
-
note:
|
|
41163
|
+
note: notes.join(" ")
|
|
40720
41164
|
}
|
|
40721
41165
|
};
|
|
40722
41166
|
}
|
|
@@ -40792,7 +41236,7 @@ class GodotServer {
|
|
|
40792
41236
|
]);
|
|
40793
41237
|
}
|
|
40794
41238
|
try {
|
|
40795
|
-
return this.jsonTextResponse(markIfStale(await this.godotBridge.invokeTool(toolName, args), this.godotBridge.getStatus().addonVersion, SERVER_VERSION));
|
|
41239
|
+
return this.jsonTextResponse(markIfStale(await this.godotBridge.invokeTool(toolName, args), this.godotBridge.getStatus().addonVersion, SERVER_VERSION, this.godotBridge.getStatus().addonDigest, shippedEditorDigest()));
|
|
40796
41240
|
} catch (error) {
|
|
40797
41241
|
return this.createErrorResponse(`The editor answered ${toolName} with an error: ${errorMessage(error)}`);
|
|
40798
41242
|
}
|
|
@@ -40866,8 +41310,8 @@ class GodotServer {
|
|
|
40866
41310
|
]);
|
|
40867
41311
|
}
|
|
40868
41312
|
const expectsScreenshot = command === "capture_screenshot" || command === "capture_viewport";
|
|
40869
|
-
const screenshotDir = expectsScreenshot ? mkdtempSync3(
|
|
40870
|
-
const screenshotPath = screenshotDir ?
|
|
41313
|
+
const screenshotDir = expectsScreenshot ? mkdtempSync3(join22(tmpdir5(), "gdharness-runtime-screenshot-")) : null;
|
|
41314
|
+
const screenshotPath = screenshotDir ? join22(screenshotDir, "capture.png") : null;
|
|
40871
41315
|
try {
|
|
40872
41316
|
const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs);
|
|
40873
41317
|
if (!reply.ok) {
|
|
@@ -40891,7 +41335,7 @@ class GodotServer {
|
|
|
40891
41335
|
type: "text",
|
|
40892
41336
|
text: `Screenshot captured: ${dimensions}${chosenAmongSeveral ? ` from pid ${own}, the game this server holds` : ""}`
|
|
40893
41337
|
},
|
|
40894
|
-
{ type: "image", data:
|
|
41338
|
+
{ type: "image", data: readFileSync20(screenshotPath).toString("base64"), mimeType: "image/png" }
|
|
40895
41339
|
]
|
|
40896
41340
|
};
|
|
40897
41341
|
} finally {
|
|
@@ -40958,7 +41402,7 @@ async function runGodotServer() {
|
|
|
40958
41402
|
const server = new GodotServer;
|
|
40959
41403
|
await server.run();
|
|
40960
41404
|
}
|
|
40961
|
-
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run7, __dirname2, EDITOR_RESTART_TIMEOUT_MS = 90000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, PLAY_STARTS_WITHIN_MS = 30000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CACHE_WRITE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/";
|
|
41405
|
+
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run7, __dirname2, EDITOR_RESTART_TIMEOUT_MS = 90000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, PLAY_STARTS_WITHIN_MS = 30000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, MOST_GROUPS = 20, VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CACHE_WRITE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/";
|
|
40962
41406
|
var init_server3 = __esm(() => {
|
|
40963
41407
|
init_mcp();
|
|
40964
41408
|
init_stdio2();
|
|
@@ -40970,6 +41414,7 @@ var init_server3 = __esm(() => {
|
|
|
40970
41414
|
init_class_note();
|
|
40971
41415
|
init_config_pin();
|
|
40972
41416
|
init_dap_client();
|
|
41417
|
+
init_editor_log();
|
|
40973
41418
|
init_errors();
|
|
40974
41419
|
init_game_log();
|
|
40975
41420
|
init_godot_bridge();
|
|
@@ -40994,7 +41439,7 @@ var init_server3 = __esm(() => {
|
|
|
40994
41439
|
init_tool_definitions();
|
|
40995
41440
|
init_update_check();
|
|
40996
41441
|
run7 = promisify7(execFile7);
|
|
40997
|
-
__dirname2 =
|
|
41442
|
+
__dirname2 = dirname15(fileURLToPath5(import.meta.url));
|
|
40998
41443
|
CONSOLE_WRAPPER = /_console(\.exe)?$/i;
|
|
40999
41444
|
LONGEST_BOOT_NOTED_MS = 5 * 60000;
|
|
41000
41445
|
VALUE_NAMES = {
|
|
@@ -41034,8 +41479,8 @@ var init_server3 = __esm(() => {
|
|
|
41034
41479
|
});
|
|
41035
41480
|
|
|
41036
41481
|
// src/cli.ts
|
|
41037
|
-
import { existsSync as
|
|
41038
|
-
import { join as
|
|
41482
|
+
import { existsSync as existsSync16 } from "fs";
|
|
41483
|
+
import { join as join23, resolve as resolve5 } from "path";
|
|
41039
41484
|
|
|
41040
41485
|
// src/errors.ts
|
|
41041
41486
|
class Refusal extends Error {
|
|
@@ -42213,7 +42658,17 @@ init_headless();
|
|
|
42213
42658
|
init_resources();
|
|
42214
42659
|
init_server_version();
|
|
42215
42660
|
import { spawnSync } from "node:child_process";
|
|
42216
|
-
import {
|
|
42661
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
42662
|
+
import {
|
|
42663
|
+
cpSync,
|
|
42664
|
+
existsSync as existsSync7,
|
|
42665
|
+
mkdirSync as mkdirSync3,
|
|
42666
|
+
readdirSync as readdirSync3,
|
|
42667
|
+
readFileSync as readFileSync7,
|
|
42668
|
+
rmSync as rmSync3,
|
|
42669
|
+
statSync as statSync3,
|
|
42670
|
+
writeFileSync as writeFileSync5
|
|
42671
|
+
} from "node:fs";
|
|
42217
42672
|
import { dirname as dirname3, join as join8 } from "node:path";
|
|
42218
42673
|
import { fileURLToPath } from "node:url";
|
|
42219
42674
|
var ADDONS = ["gdharness_editor", "gdharness_runtime", "auto_reload"];
|
|
@@ -42223,6 +42678,40 @@ var RUNTIME_AUTOLOAD = {
|
|
|
42223
42678
|
path: "addons/gdharness_runtime/runtime_autoload.gd"
|
|
42224
42679
|
};
|
|
42225
42680
|
var VERSION_MARKER = ".gdharness-version";
|
|
42681
|
+
var DIGEST_MARKER = ".gdharness-digest";
|
|
42682
|
+
function editorAddonDigest(from = shippedAddonsDirectory()) {
|
|
42683
|
+
const hash = createHash2("sha256");
|
|
42684
|
+
const walk = (directory, relative) => {
|
|
42685
|
+
for (const name of readdirSync3(directory).sort()) {
|
|
42686
|
+
if (name === VERSION_MARKER || name === DIGEST_MARKER) {
|
|
42687
|
+
continue;
|
|
42688
|
+
}
|
|
42689
|
+
const path = join8(directory, name);
|
|
42690
|
+
const inside = `${relative}/${name}`;
|
|
42691
|
+
if (statSync3(path).isDirectory()) {
|
|
42692
|
+
walk(path, inside);
|
|
42693
|
+
} else {
|
|
42694
|
+
hash.update(`${inside}
|
|
42695
|
+
`);
|
|
42696
|
+
hash.update(readFileSync7(path));
|
|
42697
|
+
hash.update(`
|
|
42698
|
+
`);
|
|
42699
|
+
}
|
|
42700
|
+
}
|
|
42701
|
+
};
|
|
42702
|
+
for (const name of EDITOR_PLUGINS) {
|
|
42703
|
+
walk(join8(from, name), name);
|
|
42704
|
+
}
|
|
42705
|
+
return hash.digest("hex");
|
|
42706
|
+
}
|
|
42707
|
+
function installedEditorDigest(projectPath) {
|
|
42708
|
+
const marker = join8(projectPath, "addons", ADDONS[0], DIGEST_MARKER);
|
|
42709
|
+
if (!existsSync7(marker)) {
|
|
42710
|
+
return null;
|
|
42711
|
+
}
|
|
42712
|
+
const said = readFileSync7(marker, "utf8").trim();
|
|
42713
|
+
return said === "" ? null : said;
|
|
42714
|
+
}
|
|
42226
42715
|
function shippedAddonsDirectory() {
|
|
42227
42716
|
return join8(dirname3(fileURLToPath(import.meta.url)), "godot", "addons");
|
|
42228
42717
|
}
|
|
@@ -42245,6 +42734,8 @@ function installAddons(projectPath, from = shippedAddonsDirectory()) {
|
|
|
42245
42734
|
`);
|
|
42246
42735
|
installed.push({ name, path: target, replaced });
|
|
42247
42736
|
}
|
|
42737
|
+
writeFileSync5(join8(projectPath, "addons", ADDONS[0], DIGEST_MARKER), `${editorAddonDigest(from)}
|
|
42738
|
+
`);
|
|
42248
42739
|
return installed;
|
|
42249
42740
|
}
|
|
42250
42741
|
function removeAddons(projectPath) {
|
|
@@ -42389,7 +42880,7 @@ function inspectProject(projectPath) {
|
|
|
42389
42880
|
// src/skill.ts
|
|
42390
42881
|
init_setup();
|
|
42391
42882
|
init_tool_definitions();
|
|
42392
|
-
import { existsSync as existsSync9, mkdirSync as mkdirSync5, readdirSync as
|
|
42883
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync5, readdirSync as readdirSync5, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "node:fs";
|
|
42393
42884
|
import { basename, dirname as dirname5, join as join10 } from "node:path";
|
|
42394
42885
|
|
|
42395
42886
|
// src/tool-reference.ts
|
|
@@ -42511,7 +43002,8 @@ that is running, and the project on disk. ${TOOL_SPECS.length} tools, named \`do
|
|
|
42511
43002
|
this server is connected to. There is no ambient project, and an argument a tool does not declare is refused
|
|
42512
43003
|
rather than ignored.
|
|
42513
43004
|
- \`editor_status\` says whether an editor is connected and whether its addon matches the server.
|
|
42514
|
-
\`addonIsStale\` means
|
|
43005
|
+
\`addonIsStale\` means the editor is running different addon code from what the server ships
|
|
43006
|
+
(an older \`addonVersion\` on the same code is not stale), and \`staleNote\` says which half is behind: an editor that
|
|
42515
43007
|
needs restarting, or a server that needs reconnecting in your harness. Every answer that came
|
|
42516
43008
|
from the editor carries the same two when they differ, so a stale addon cannot answer quietly:
|
|
42517
43009
|
believe a restart over a diagnostic that arrived with them.
|
|
@@ -42532,6 +43024,14 @@ that is running, and the project on disk. ${TOOL_SPECS.length} tools, named \`do
|
|
|
42532
43024
|
runtime addon announces nothing, so there is no process id and \`running\` is the editor's answer,
|
|
42533
43025
|
which can lag by seconds after the game has gone. \`editor_status\` names the game under
|
|
42534
43026
|
\`runtimes\`, which does not depend on the editor answering.
|
|
43027
|
+
- When the tools all say the project is fine and something is still wrong, read the editor's own
|
|
43028
|
+
console with \`editor_output op: "editor"\`. It is a different log from the one above and the only
|
|
43029
|
+
way anything here sees what the editor printed: its console reaches no plugin, so an editor
|
|
43030
|
+
gdharness opened is asked for a log file as it starts, and one somebody opened by hand is refused
|
|
43031
|
+
rather than answered empty. It carries the whole session from the editor's first line, which is
|
|
43032
|
+
where a wall of startup errors will be, with repeated messages grouped under \`repeated\` and the
|
|
43033
|
+
ordinary lines above each group beside them: a plugin announcing itself on the line before a
|
|
43034
|
+
burst of parse errors is usually the whole diagnosis.
|
|
42535
43035
|
- One server, one editor. A second project is a second harness session with its own server, and
|
|
42536
43036
|
that works: each editor is opened on its own language server and debug adapter ports, and says
|
|
42537
43037
|
where it serves. Two editors on one project is the thing to refuse.
|
|
@@ -42690,7 +43190,7 @@ function pruneEmpty(directory, projectPath) {
|
|
|
42690
43190
|
const shared = join10(projectPath, ".agents");
|
|
42691
43191
|
let path = directory;
|
|
42692
43192
|
while (path !== projectPath && (basename(path) === "skills" || path === shared)) {
|
|
42693
|
-
if (
|
|
43193
|
+
if (readdirSync5(path).length > 0) {
|
|
42694
43194
|
return;
|
|
42695
43195
|
}
|
|
42696
43196
|
rmSync5(path, { recursive: true, force: true });
|
|
@@ -42714,9 +43214,9 @@ function writeSkill(directories, version) {
|
|
|
42714
43214
|
// src/version.ts
|
|
42715
43215
|
import { readFileSync as readFileSync9 } from "node:fs";
|
|
42716
43216
|
import { dirname as dirname6, join as join11 } from "node:path";
|
|
42717
|
-
import { fileURLToPath as
|
|
43217
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
42718
43218
|
function getLocalVersion() {
|
|
42719
|
-
const currentDirectory = dirname6(
|
|
43219
|
+
const currentDirectory = dirname6(fileURLToPath3(import.meta.url));
|
|
42720
43220
|
const packageCandidates = [
|
|
42721
43221
|
join11(currentDirectory, "..", "package.json"),
|
|
42722
43222
|
join11(currentDirectory, "..", "..", "package.json")
|
|
@@ -42741,7 +43241,7 @@ class UsageError extends Refusal {
|
|
|
42741
43241
|
function projectArgument(at) {
|
|
42742
43242
|
const named = args.slice(at).find((value) => !value.startsWith("--"));
|
|
42743
43243
|
const projectPath = resolve5(named ?? ".");
|
|
42744
|
-
if (!
|
|
43244
|
+
if (!existsSync16(join23(projectPath, "project.godot"))) {
|
|
42745
43245
|
throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
|
|
42746
43246
|
}
|
|
42747
43247
|
return projectPath;
|
|
@@ -42780,7 +43280,7 @@ async function registerRuntime(godot, projectPath) {
|
|
|
42780
43280
|
return;
|
|
42781
43281
|
}
|
|
42782
43282
|
console.log(`${RUNTIME_AUTOLOAD.name} autoload left at ${named}, which this project registers itself`);
|
|
42783
|
-
if (!
|
|
43283
|
+
if (!existsSync16(join23(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
|
|
42784
43284
|
console.log(` that file is not in this project, so it boots with a missing script: restore it, or point the entry at res://${RUNTIME_AUTOLOAD.path}`);
|
|
42785
43285
|
}
|
|
42786
43286
|
}
|
|
@@ -42912,7 +43412,7 @@ async function setup() {
|
|
|
42912
43412
|
console.log("no harness configured; name one yourself, and gdharness harnesses lists them all");
|
|
42913
43413
|
}
|
|
42914
43414
|
if (!args.includes("--no-skill")) {
|
|
42915
|
-
const directories = skillDirectories(chosen.harnesses, projectPath,
|
|
43415
|
+
const directories = skillDirectories(chosen.harnesses, projectPath, existsSync16);
|
|
42916
43416
|
for (const written of writeSkill(directories, getLocalVersion())) {
|
|
42917
43417
|
console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
|
|
42918
43418
|
}
|
|
@@ -42978,6 +43478,7 @@ async function upgrade() {
|
|
|
42978
43478
|
}
|
|
42979
43479
|
const godot = await engine(projectPath);
|
|
42980
43480
|
console.log(installed === version ? `already ${version}; reinstalling` : `${installed} -> ${version}`);
|
|
43481
|
+
const digestBefore = installedEditorDigest(projectPath);
|
|
42981
43482
|
for (const addon of installAddons(projectPath)) {
|
|
42982
43483
|
console.log(`${addon.replaced ? "replaced" : "installed"} ${addon.path}`);
|
|
42983
43484
|
}
|
|
@@ -43005,12 +43506,14 @@ async function upgrade() {
|
|
|
43005
43506
|
moved.push(took.from);
|
|
43006
43507
|
}
|
|
43007
43508
|
}
|
|
43008
|
-
for (const written of writeSkill(skillDirectories(already, projectPath,
|
|
43509
|
+
for (const written of writeSkill(skillDirectories(already, projectPath, existsSync16), version)) {
|
|
43009
43510
|
console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
|
|
43010
43511
|
}
|
|
43011
43512
|
console.log(`
|
|
43012
|
-
On ${version}.
|
|
43013
|
-
console.log(` 1.
|
|
43513
|
+
On ${version}. What is left for you:`);
|
|
43514
|
+
console.log(digestBefore !== null && digestBefore === installedEditorDigest(projectPath) ? ` 1. Nothing for the editor: this upgrade left the editor addons the same code as before,
|
|
43515
|
+
` + ` so it gives an open editor nothing to restart for. editor_status says addonIsStale
|
|
43516
|
+
` + " if the editor is older than that." : ` 1. The open editor is still running the addons it loaded at startup. Restart it with the
|
|
43014
43517
|
` + " editor_launch restart tool, which closes and reopens the window.");
|
|
43015
43518
|
console.log(harnessNote({ moved, written, byHand }, [launch.command, ...launch.args].join(" ")));
|
|
43016
43519
|
}
|