gdharness 1.2.26 → 1.2.28
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 +625 -350
- package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +152 -6
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +22 -0
- package/build/index.js +494 -224
- package/build/keeper.js +1207 -753
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -3363,17 +3363,17 @@ import {
|
|
|
3363
3363
|
closeSync as closeSync2,
|
|
3364
3364
|
constants,
|
|
3365
3365
|
copyFileSync,
|
|
3366
|
-
existsSync as
|
|
3366
|
+
existsSync as existsSync18,
|
|
3367
3367
|
fstatSync,
|
|
3368
3368
|
mkdirSync as mkdirSync14,
|
|
3369
3369
|
openSync as openSync3,
|
|
3370
|
-
readdirSync as
|
|
3371
|
-
readFileSync as
|
|
3370
|
+
readdirSync as readdirSync10,
|
|
3371
|
+
readFileSync as readFileSync22,
|
|
3372
3372
|
readSync as readSync2,
|
|
3373
3373
|
rmdirSync as rmdirSync2,
|
|
3374
3374
|
statSync as statSync9
|
|
3375
3375
|
} from "node:fs";
|
|
3376
|
-
import { basename as
|
|
3376
|
+
import { basename as basename6, dirname as dirname17, extname as extname3, isAbsolute as isAbsolute4, join as join24, normalize as normalize2, relative as relative4 } from "node:path";
|
|
3377
3377
|
import { setTimeout as delay4 } from "node:timers/promises";
|
|
3378
3378
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
3379
3379
|
import { promisify as promisify5 } from "node:util";
|
|
@@ -27929,6 +27929,18 @@ class GodotDAPClient {
|
|
|
27929
27929
|
lines: Array.from(lines).sort((a, b) => a - b)
|
|
27930
27930
|
}));
|
|
27931
27931
|
}
|
|
27932
|
+
forgetBreakpoint(scriptPath, line) {
|
|
27933
|
+
const key = fileKey(scriptPath);
|
|
27934
|
+
const lines = this.breakpoints.get(key);
|
|
27935
|
+
if (lines === undefined) {
|
|
27936
|
+
return;
|
|
27937
|
+
}
|
|
27938
|
+
lines.delete(line);
|
|
27939
|
+
if (lines.size === 0) {
|
|
27940
|
+
this.breakpoints.delete(key);
|
|
27941
|
+
this.spelled.delete(key);
|
|
27942
|
+
}
|
|
27943
|
+
}
|
|
27932
27944
|
holdBreakpoints(held) {
|
|
27933
27945
|
for (const { scriptPath, lines } of held) {
|
|
27934
27946
|
const key = fileKey(scriptPath);
|
|
@@ -28707,6 +28719,22 @@ function howItEnded(run) {
|
|
|
28707
28719
|
return run.exitSignal === null ? `exit code ${run.exitCode ?? "unknown"}` : `ended by ${run.exitSignal}`;
|
|
28708
28720
|
}
|
|
28709
28721
|
|
|
28722
|
+
// src/godot-variant.ts
|
|
28723
|
+
var FLAG_64 = 1 << 16;
|
|
28724
|
+
var MOST_MESSAGE_BYTES = (8 << 20) + 4;
|
|
28725
|
+
|
|
28726
|
+
// src/game-debugger.ts
|
|
28727
|
+
var PAUSE_MS = 30000;
|
|
28728
|
+
var STEP_MS = 1e4;
|
|
28729
|
+
var READ_MS = 5000;
|
|
28730
|
+
var REPLIES = new Set([
|
|
28731
|
+
"debug_enter",
|
|
28732
|
+
"debug_exit",
|
|
28733
|
+
"stack_dump",
|
|
28734
|
+
"stack_frame_vars",
|
|
28735
|
+
"stack_frame_var"
|
|
28736
|
+
]);
|
|
28737
|
+
|
|
28710
28738
|
// src/godot-bridge.ts
|
|
28711
28739
|
import { randomUUID } from "node:crypto";
|
|
28712
28740
|
import { EventEmitter } from "node:events";
|
|
@@ -29489,33 +29517,114 @@ class GodotLocator {
|
|
|
29489
29517
|
}
|
|
29490
29518
|
|
|
29491
29519
|
// src/headless.ts
|
|
29492
|
-
import { existsSync as
|
|
29493
|
-
import { dirname as
|
|
29520
|
+
import { existsSync as existsSync12, readFileSync as readFileSync14, realpathSync as realpathSync2, writeFileSync as writeFileSync9 } from "node:fs";
|
|
29521
|
+
import { dirname as dirname9, join as join15 } from "node:path";
|
|
29522
|
+
|
|
29523
|
+
// src/library-copies.ts
|
|
29524
|
+
import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync12, renameSync, unlinkSync as unlinkSync2 } from "node:fs";
|
|
29525
|
+
import { basename as basename2, dirname as dirname8, isAbsolute as isAbsolute3, join as join12 } from "node:path";
|
|
29526
|
+
function freeLibraryCopies(projectPath, platform = process.platform) {
|
|
29527
|
+
const setAside = [];
|
|
29528
|
+
const swept = [];
|
|
29529
|
+
if (platform !== "win32") {
|
|
29530
|
+
return { setAside, swept };
|
|
29531
|
+
}
|
|
29532
|
+
for (const library of windowsLibraries(projectPath)) {
|
|
29533
|
+
const folder = dirname8(library);
|
|
29534
|
+
const copy = `~${basename2(library)}`;
|
|
29535
|
+
if (existsSync9(join12(folder, copy))) {
|
|
29536
|
+
const aside = join12(folder, `${copy}.aside-${process.pid}-${Date.now()}`);
|
|
29537
|
+
try {
|
|
29538
|
+
renameSync(join12(folder, copy), aside);
|
|
29539
|
+
setAside.push(aside);
|
|
29540
|
+
} catch {}
|
|
29541
|
+
}
|
|
29542
|
+
let names;
|
|
29543
|
+
try {
|
|
29544
|
+
names = readdirSync4(folder);
|
|
29545
|
+
} catch {
|
|
29546
|
+
continue;
|
|
29547
|
+
}
|
|
29548
|
+
for (const name of names) {
|
|
29549
|
+
if (name.length > copy.length && name.toLowerCase().startsWith(copy.toLowerCase())) {
|
|
29550
|
+
try {
|
|
29551
|
+
unlinkSync2(join12(folder, name));
|
|
29552
|
+
swept.push(join12(folder, name));
|
|
29553
|
+
} catch {}
|
|
29554
|
+
}
|
|
29555
|
+
}
|
|
29556
|
+
}
|
|
29557
|
+
return { setAside, swept };
|
|
29558
|
+
}
|
|
29559
|
+
function windowsLibraries(projectPath) {
|
|
29560
|
+
const listed = readOrNull(join12(projectPath, ".godot", "extension_list.cfg"));
|
|
29561
|
+
if (listed === null) {
|
|
29562
|
+
return [];
|
|
29563
|
+
}
|
|
29564
|
+
const libraries = new Set;
|
|
29565
|
+
for (const line of listed.split(/\r?\n/)) {
|
|
29566
|
+
const manifest = resolveResource(projectPath, projectPath, line.trim());
|
|
29567
|
+
const text = manifest === null ? null : readOrNull(manifest);
|
|
29568
|
+
if (manifest === null || text === null) {
|
|
29569
|
+
continue;
|
|
29570
|
+
}
|
|
29571
|
+
let inLibraries = false;
|
|
29572
|
+
for (const row of text.split(/\r?\n/)) {
|
|
29573
|
+
const section = /^\s*\[([^\]]+)\]/.exec(row)?.[1];
|
|
29574
|
+
if (section !== undefined) {
|
|
29575
|
+
inLibraries = section.trim() === "libraries";
|
|
29576
|
+
continue;
|
|
29577
|
+
}
|
|
29578
|
+
const named = inLibraries ? /^\s*windows[\w.]*\s*=\s*"([^"]+)"/.exec(row)?.[1] : undefined;
|
|
29579
|
+
const library = named === undefined ? null : resolveResource(projectPath, dirname8(manifest), named);
|
|
29580
|
+
if (library !== null) {
|
|
29581
|
+
libraries.add(library);
|
|
29582
|
+
}
|
|
29583
|
+
}
|
|
29584
|
+
}
|
|
29585
|
+
return [...libraries];
|
|
29586
|
+
}
|
|
29587
|
+
function resolveResource(projectPath, relativeTo, path) {
|
|
29588
|
+
if (path === "") {
|
|
29589
|
+
return null;
|
|
29590
|
+
}
|
|
29591
|
+
if (path.startsWith("res://")) {
|
|
29592
|
+
return join12(projectPath, path.slice("res://".length));
|
|
29593
|
+
}
|
|
29594
|
+
return isAbsolute3(path) ? path : join12(relativeTo, path);
|
|
29595
|
+
}
|
|
29596
|
+
function readOrNull(path) {
|
|
29597
|
+
try {
|
|
29598
|
+
return readFileSync12(path, "utf8");
|
|
29599
|
+
} catch {
|
|
29600
|
+
return null;
|
|
29601
|
+
}
|
|
29602
|
+
}
|
|
29494
29603
|
|
|
29495
29604
|
// src/reimport.ts
|
|
29496
29605
|
import { createHash as createHash2 } from "node:crypto";
|
|
29497
|
-
import { existsSync as
|
|
29498
|
-
import { basename as
|
|
29606
|
+
import { existsSync as existsSync10, readFileSync as readFileSync13, rmSync as rmSync5, statSync as statSync5, utimesSync, writeFileSync as writeFileSync8 } from "node:fs";
|
|
29607
|
+
import { basename as basename3, join as join13 } from "node:path";
|
|
29499
29608
|
function forceNextImport(projectPath, resourcePath) {
|
|
29500
29609
|
const relative = resourcePath.replace(/^res:\/\//, "");
|
|
29501
|
-
const sidecar =
|
|
29502
|
-
if (!
|
|
29610
|
+
const sidecar = join13(projectPath, `${relative}.import`);
|
|
29611
|
+
if (!existsSync10(sidecar)) {
|
|
29503
29612
|
return;
|
|
29504
29613
|
}
|
|
29505
29614
|
const { atime, mtimeMs } = statSync5(sidecar);
|
|
29506
|
-
writeFileSync8(sidecar,
|
|
29615
|
+
writeFileSync8(sidecar, readFileSync13(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
|
|
29507
29616
|
utimesSync(sidecar, atime, new Date(mtimeMs + 2000));
|
|
29508
29617
|
rmSync5(importRecord(projectPath, resourcePath), { force: true });
|
|
29509
29618
|
}
|
|
29510
29619
|
function importRecord(projectPath, resourcePath) {
|
|
29511
29620
|
const digest = createHash2("md5").update(resourcePath).digest("hex");
|
|
29512
|
-
return
|
|
29621
|
+
return join13(projectPath, ".godot", "imported", `${basename3(resourcePath)}-${digest}.md5`);
|
|
29513
29622
|
}
|
|
29514
29623
|
|
|
29515
29624
|
// src/scratch.ts
|
|
29516
|
-
import { existsSync as
|
|
29625
|
+
import { existsSync as existsSync11, mkdtempSync, readdirSync as readdirSync5, rmSync as rmSync6 } from "node:fs";
|
|
29517
29626
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
29518
|
-
import { join as
|
|
29627
|
+
import { join as join14 } from "node:path";
|
|
29519
29628
|
import process7 from "node:process";
|
|
29520
29629
|
var SWEPT = { recursive: true, force: true };
|
|
29521
29630
|
var AGAIN_AFTER_MS = [250, 500, 1000, 2000, 4000, 8000, 16000];
|
|
@@ -29530,12 +29639,12 @@ var KINDS = [
|
|
|
29530
29639
|
"tests"
|
|
29531
29640
|
];
|
|
29532
29641
|
function scratchDirectory(kind) {
|
|
29533
|
-
return mkdtempSync(
|
|
29642
|
+
return mkdtempSync(join14(tmpdir4(), `gdharness-${kind}-${process7.pid}-`));
|
|
29534
29643
|
}
|
|
29535
29644
|
function sweepAbandonedScratch(directory = tmpdir4()) {
|
|
29536
29645
|
let entries;
|
|
29537
29646
|
try {
|
|
29538
|
-
entries =
|
|
29647
|
+
entries = readdirSync5(directory);
|
|
29539
29648
|
} catch {
|
|
29540
29649
|
return;
|
|
29541
29650
|
}
|
|
@@ -29546,7 +29655,7 @@ function sweepAbandonedScratch(directory = tmpdir4()) {
|
|
|
29546
29655
|
continue;
|
|
29547
29656
|
}
|
|
29548
29657
|
try {
|
|
29549
|
-
remove(
|
|
29658
|
+
remove(join14(directory, entry));
|
|
29550
29659
|
} catch {}
|
|
29551
29660
|
}
|
|
29552
29661
|
}
|
|
@@ -29584,7 +29693,7 @@ function tryAgain(path, attempt) {
|
|
|
29584
29693
|
lastTryOnExit();
|
|
29585
29694
|
}
|
|
29586
29695
|
const after = AGAIN_AFTER_MS[attempt];
|
|
29587
|
-
if (after === undefined || !
|
|
29696
|
+
if (after === undefined || !existsSync11(path)) {
|
|
29588
29697
|
waiting.delete(path);
|
|
29589
29698
|
if (after === undefined) {
|
|
29590
29699
|
atExit.add(path);
|
|
@@ -29675,7 +29784,7 @@ function refusalsIn(log) {
|
|
|
29675
29784
|
}
|
|
29676
29785
|
function scriptFault(problems, script) {
|
|
29677
29786
|
const spelled = (path) => `(${path.replaceAll("\\", "/").toLowerCase()}/`;
|
|
29678
|
-
const directories = [...new Set([spelled(
|
|
29787
|
+
const directories = [...new Set([spelled(dirname9(script)), spelled(resolvedOr(dirname9(script)))])];
|
|
29679
29788
|
return problems.find((entry) => {
|
|
29680
29789
|
const at = entry.detail.find((line) => line.startsWith("at: "))?.replaceAll("\\", "/").toLowerCase();
|
|
29681
29790
|
return entry.severity === "error" && at !== undefined && directories.some((directory) => at.includes(directory));
|
|
@@ -29700,7 +29809,7 @@ function librariesNotCopied(messages) {
|
|
|
29700
29809
|
}
|
|
29701
29810
|
function extensionRetryNote(libraries, stillNotLoaded = []) {
|
|
29702
29811
|
const second = stillNotLoaded.length === 0 ? "so the import was run again and loaded the extension, and the messages are from that second run." : `so the import was run again, and it still could not load ${stillNotLoaded.join(" and ")}: this answer is from an import without ${stillNotLoaded.length === 1 ? "it" : "them"}, and its messages say what the engine hit.`;
|
|
29703
|
-
return `The first import could not load ${libraries.join(" and ")}: on Windows an engine running as the editor ` + "loads a GDExtension from a ~ copy beside the library, and the editor open on this project holds its own " + "copy there, so the first engine started beside it cannot make one. That failed attempt moves the " + `editor's copy aside, ${second} The editor's copy is left beside the library as a ~RF….TMP file, which ` + "
|
|
29812
|
+
return `The first import could not load ${libraries.join(" and ")}: on Windows an engine running as the editor ` + "loads a GDExtension from a ~ copy beside the library, and the editor open on this project holds its own " + "copy there, so the first engine started beside it cannot make one. That failed attempt moves the " + `editor's copy aside, ${second} The editor's copy is left beside the library as a ~RF….TMP file, which ` + "the next import, export or editor this server starts on the project deletes once the editor has exited.";
|
|
29704
29813
|
}
|
|
29705
29814
|
async function runImport(godotPath, projectPath, options = {}) {
|
|
29706
29815
|
const once = options.once ?? importOnce;
|
|
@@ -29708,6 +29817,7 @@ async function runImport(godotPath, projectPath, options = {}) {
|
|
|
29708
29817
|
for (const target of options.forcing ?? []) {
|
|
29709
29818
|
forceNextImport(projectPath, target);
|
|
29710
29819
|
}
|
|
29820
|
+
freeLibraryCopies(projectPath);
|
|
29711
29821
|
return once(godotPath, projectPath);
|
|
29712
29822
|
};
|
|
29713
29823
|
const first = await pass();
|
|
@@ -29731,7 +29841,7 @@ async function importOnce(godotPath, projectPath) {
|
|
|
29731
29841
|
ran = await runEngine(godotPath, [
|
|
29732
29842
|
"--headless",
|
|
29733
29843
|
"--log-file",
|
|
29734
|
-
|
|
29844
|
+
join15(logDir, "engine.log"),
|
|
29735
29845
|
"--path",
|
|
29736
29846
|
projectPath,
|
|
29737
29847
|
"--import"
|
|
@@ -29751,13 +29861,13 @@ async function importOnce(godotPath, projectPath) {
|
|
|
29751
29861
|
}
|
|
29752
29862
|
async function runOperation(engine, operation, params, projectPath) {
|
|
29753
29863
|
const paramsDir = scratchDirectory("params");
|
|
29754
|
-
const paramsFile =
|
|
29755
|
-
const answerFile =
|
|
29864
|
+
const paramsFile = join15(paramsDir, `${operation}.json`);
|
|
29865
|
+
const answerFile = join15(paramsDir, "answer.json");
|
|
29756
29866
|
writeFileSync9(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
|
|
29757
29867
|
const args = [
|
|
29758
29868
|
"--headless",
|
|
29759
29869
|
"--log-file",
|
|
29760
|
-
|
|
29870
|
+
join15(paramsDir, "engine.log"),
|
|
29761
29871
|
"--path",
|
|
29762
29872
|
projectPath,
|
|
29763
29873
|
"--script",
|
|
@@ -29827,10 +29937,10 @@ async function runOperation(engine, operation, params, projectPath) {
|
|
|
29827
29937
|
};
|
|
29828
29938
|
}
|
|
29829
29939
|
function answerIn(file) {
|
|
29830
|
-
if (!
|
|
29940
|
+
if (!existsSync12(file)) {
|
|
29831
29941
|
return { kind: "none" };
|
|
29832
29942
|
}
|
|
29833
|
-
const text =
|
|
29943
|
+
const text = readFileSync14(file, "utf8");
|
|
29834
29944
|
try {
|
|
29835
29945
|
const parsed = JSON.parse(text);
|
|
29836
29946
|
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? { kind: "answer", payload: parsed } : { kind: "unreadable", why: `it is JSON but not an object: ${text.slice(0, 200)}` };
|
|
@@ -30340,7 +30450,31 @@ function testProgressMessage(printed, elapsedMs) {
|
|
|
30340
30450
|
|
|
30341
30451
|
// src/keeper-channel.ts
|
|
30342
30452
|
import { connect, createServer as createServer2 } from "node:net";
|
|
30343
|
-
|
|
30453
|
+
var DEBUG_PREFIX = "debug ";
|
|
30454
|
+
async function askTheKeeperToStop(address, withinMs) {
|
|
30455
|
+
const answered = await askTheKeeper(address, "stop", withinMs);
|
|
30456
|
+
if (typeof answered !== "string") {
|
|
30457
|
+
return answered;
|
|
30458
|
+
}
|
|
30459
|
+
return answered === "signalled" || answered === "gone" ? answered : { unreached: `it answered ${JSON.stringify(answered)}` };
|
|
30460
|
+
}
|
|
30461
|
+
async function askTheKeepersDebugger(address, request, withinMs) {
|
|
30462
|
+
const answered = await askTheKeeper(address, `${DEBUG_PREFIX}${JSON.stringify(request)}`, withinMs);
|
|
30463
|
+
if (typeof answered !== "string") {
|
|
30464
|
+
return answered;
|
|
30465
|
+
}
|
|
30466
|
+
if (answered === "unknown") {
|
|
30467
|
+
return { unreached: "its keeper was started by a gdharness from before game debuggers, and has none" };
|
|
30468
|
+
}
|
|
30469
|
+
try {
|
|
30470
|
+
const parsed = JSON.parse(answered);
|
|
30471
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
30472
|
+
return parsed;
|
|
30473
|
+
}
|
|
30474
|
+
} catch {}
|
|
30475
|
+
return { unreached: `it answered ${JSON.stringify(answered.slice(0, 200))}` };
|
|
30476
|
+
}
|
|
30477
|
+
function askTheKeeper(address, line, withinMs) {
|
|
30344
30478
|
return new Promise((resolve) => {
|
|
30345
30479
|
let said = "";
|
|
30346
30480
|
let settled = false;
|
|
@@ -30358,16 +30492,15 @@ function askTheKeeperToStop(address, withinMs) {
|
|
|
30358
30492
|
}, withinMs);
|
|
30359
30493
|
socket.setEncoding("utf8");
|
|
30360
30494
|
socket.on("connect", () => {
|
|
30361
|
-
socket.write(
|
|
30495
|
+
socket.write(`${line}
|
|
30362
30496
|
`);
|
|
30363
30497
|
});
|
|
30364
30498
|
socket.on("data", (chunk) => {
|
|
30365
30499
|
said += chunk;
|
|
30366
|
-
const line = said.split(`
|
|
30367
|
-
`)[0] ?? "";
|
|
30368
30500
|
if (said.includes(`
|
|
30369
30501
|
`)) {
|
|
30370
|
-
settle(
|
|
30502
|
+
settle(said.split(`
|
|
30503
|
+
`)[0] ?? "");
|
|
30371
30504
|
}
|
|
30372
30505
|
});
|
|
30373
30506
|
socket.on("error", (error) => {
|
|
@@ -30380,20 +30513,20 @@ function askTheKeeperToStop(address, withinMs) {
|
|
|
30380
30513
|
}
|
|
30381
30514
|
|
|
30382
30515
|
// src/lifecycle-log.ts
|
|
30383
|
-
import { appendFileSync, mkdirSync as mkdirSync8, renameSync, statSync as statSync6 } from "node:fs";
|
|
30384
|
-
import { dirname as
|
|
30516
|
+
import { appendFileSync, mkdirSync as mkdirSync8, renameSync as renameSync2, statSync as statSync6 } from "node:fs";
|
|
30517
|
+
import { dirname as dirname10, join as join16 } from "node:path";
|
|
30385
30518
|
import process8 from "node:process";
|
|
30386
30519
|
function lifecycleLogPath() {
|
|
30387
|
-
return
|
|
30520
|
+
return join16(runtimeDirectory(), "servers.log");
|
|
30388
30521
|
}
|
|
30389
30522
|
var LIFECYCLE_LOG_LIMIT = 1e6;
|
|
30390
30523
|
function writeLifecycle(event, fields = {}) {
|
|
30391
30524
|
const path = lifecycleLogPath();
|
|
30392
30525
|
try {
|
|
30393
|
-
mkdirSync8(
|
|
30526
|
+
mkdirSync8(dirname10(path), { recursive: true });
|
|
30394
30527
|
if ((statSync6(path, { throwIfNoEntry: false })?.size ?? 0) > LIFECYCLE_LOG_LIMIT) {
|
|
30395
30528
|
try {
|
|
30396
|
-
|
|
30529
|
+
renameSync2(path, `${path}.1`);
|
|
30397
30530
|
} catch {}
|
|
30398
30531
|
}
|
|
30399
30532
|
const line = {
|
|
@@ -30423,10 +30556,10 @@ function callsInFlight(calls, now = Date.now()) {
|
|
|
30423
30556
|
}
|
|
30424
30557
|
|
|
30425
30558
|
// src/lsp_client.ts
|
|
30426
|
-
import { existsSync as
|
|
30559
|
+
import { existsSync as existsSync13, realpathSync as realpathSync3 } from "node:fs";
|
|
30427
30560
|
import { readFile, realpath } from "node:fs/promises";
|
|
30428
30561
|
import { createConnection as createConnection3 } from "node:net";
|
|
30429
|
-
import { dirname as
|
|
30562
|
+
import { dirname as dirname11, join as join17, resolve as resolve3 } from "node:path";
|
|
30430
30563
|
import { setTimeout as delay3 } from "node:timers/promises";
|
|
30431
30564
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
30432
30565
|
var DEFAULT_LSP_PORT = 6005;
|
|
@@ -30624,7 +30757,7 @@ class GodotLSPClient {
|
|
|
30624
30757
|
if (this.initialized) {
|
|
30625
30758
|
return;
|
|
30626
30759
|
}
|
|
30627
|
-
const rootPath = this.rootPath ??
|
|
30760
|
+
const rootPath = this.rootPath ?? dirname11(resolve3(filePath));
|
|
30628
30761
|
await this.initialize(rootPath);
|
|
30629
30762
|
}
|
|
30630
30763
|
async sendRequest(method, params, timeoutMs = this.timeouts.requestMs) {
|
|
@@ -31182,7 +31315,7 @@ async function resolveLSPPaths(projectPathValue, scriptPathValue) {
|
|
|
31182
31315
|
} catch {
|
|
31183
31316
|
throw new ArgumentRefusal(`Project path does not exist: ${requestedProjectPath}`);
|
|
31184
31317
|
}
|
|
31185
|
-
if (!
|
|
31318
|
+
if (!existsSync13(join17(projectPath, "project.godot"))) {
|
|
31186
31319
|
throw new ArgumentRefusal(`Not a Godot project: ${projectPath}. Point projectPath at the directory holding project.godot.`);
|
|
31187
31320
|
}
|
|
31188
31321
|
const contained = resolveWithinProject(projectPath, scriptPathValue);
|
|
@@ -31389,8 +31522,8 @@ function secondsFromClock(printed) {
|
|
|
31389
31522
|
}
|
|
31390
31523
|
|
|
31391
31524
|
// src/project-scan.ts
|
|
31392
|
-
import { existsSync as
|
|
31393
|
-
import { extname, join as
|
|
31525
|
+
import { existsSync as existsSync14, readdirSync as readdirSync6, readFileSync as readFileSync15 } from "node:fs";
|
|
31526
|
+
import { extname, join as join18 } from "node:path";
|
|
31394
31527
|
var SKIPPED = new Set(["node_modules"]);
|
|
31395
31528
|
function skipped(name) {
|
|
31396
31529
|
return name.startsWith(".") || SKIPPED.has(name);
|
|
@@ -31400,10 +31533,10 @@ function steppedOver(directory) {
|
|
|
31400
31533
|
return gdignored(directory) || installedByUs(directory);
|
|
31401
31534
|
}
|
|
31402
31535
|
function gdignored(directory) {
|
|
31403
|
-
return
|
|
31536
|
+
return existsSync14(join18(directory, ".gdignore"));
|
|
31404
31537
|
}
|
|
31405
31538
|
function installedByUs(directory) {
|
|
31406
|
-
return
|
|
31539
|
+
return existsSync14(join18(directory, VERSION_MARKER));
|
|
31407
31540
|
}
|
|
31408
31541
|
var SCENE_EXTENSIONS = new Set(["tscn", "scn"]);
|
|
31409
31542
|
var ASSET_EXTENSIONS = new Set([
|
|
@@ -31442,12 +31575,12 @@ function projectStructure(projectPath) {
|
|
|
31442
31575
|
if (steppedOver(directory)) {
|
|
31443
31576
|
return;
|
|
31444
31577
|
}
|
|
31445
|
-
for (const entry of
|
|
31578
|
+
for (const entry of readdirSync6(directory, { withFileTypes: true })) {
|
|
31446
31579
|
if (skipped(entry.name)) {
|
|
31447
31580
|
continue;
|
|
31448
31581
|
}
|
|
31449
31582
|
if (entry.isDirectory()) {
|
|
31450
|
-
visit(
|
|
31583
|
+
visit(join18(directory, entry.name));
|
|
31451
31584
|
} else if (entry.isFile()) {
|
|
31452
31585
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
31453
31586
|
if (SCENE_EXTENSIONS.has(extension)) {
|
|
@@ -31471,13 +31604,13 @@ function projectFiles(projectPath, extensions) {
|
|
|
31471
31604
|
if (steppedOver(directory)) {
|
|
31472
31605
|
return;
|
|
31473
31606
|
}
|
|
31474
|
-
for (const entry of
|
|
31607
|
+
for (const entry of readdirSync6(directory, { withFileTypes: true })) {
|
|
31475
31608
|
if (skipped(entry.name)) {
|
|
31476
31609
|
continue;
|
|
31477
31610
|
}
|
|
31478
31611
|
const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
31479
31612
|
if (entry.isDirectory()) {
|
|
31480
|
-
visit(
|
|
31613
|
+
visit(join18(directory, entry.name), spelled);
|
|
31481
31614
|
} else if (entry.isFile() && extensions.includes(extname(entry.name).toLowerCase())) {
|
|
31482
31615
|
found.push(spelled);
|
|
31483
31616
|
}
|
|
@@ -31493,20 +31626,20 @@ function scriptsWithoutUid(projectPath) {
|
|
|
31493
31626
|
if (steppedOver(directory)) {
|
|
31494
31627
|
return;
|
|
31495
31628
|
}
|
|
31496
|
-
for (const entry of
|
|
31629
|
+
for (const entry of readdirSync6(directory, { withFileTypes: true })) {
|
|
31497
31630
|
if (skipped(entry.name)) {
|
|
31498
31631
|
continue;
|
|
31499
31632
|
}
|
|
31500
31633
|
const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
31501
31634
|
if (entry.isDirectory()) {
|
|
31502
|
-
visit(
|
|
31635
|
+
visit(join18(directory, entry.name), spelled);
|
|
31503
31636
|
continue;
|
|
31504
31637
|
}
|
|
31505
31638
|
if (!entry.isFile()) {
|
|
31506
31639
|
continue;
|
|
31507
31640
|
}
|
|
31508
31641
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
31509
|
-
if (CARRIES_A_UID.has(extension) && !
|
|
31642
|
+
if (CARRIES_A_UID.has(extension) && !existsSync14(join18(directory, `${entry.name}.uid`))) {
|
|
31510
31643
|
found.push(spelled);
|
|
31511
31644
|
}
|
|
31512
31645
|
}
|
|
@@ -31534,11 +31667,11 @@ function projectTexts(projectPath) {
|
|
|
31534
31667
|
if (steppedOver(directory)) {
|
|
31535
31668
|
return;
|
|
31536
31669
|
}
|
|
31537
|
-
for (const entry of
|
|
31670
|
+
for (const entry of readdirSync6(directory, { withFileTypes: true })) {
|
|
31538
31671
|
if (skipped(entry.name)) {
|
|
31539
31672
|
continue;
|
|
31540
31673
|
}
|
|
31541
|
-
const absolute =
|
|
31674
|
+
const absolute = join18(directory, entry.name);
|
|
31542
31675
|
const spelled = `${prefix}${entry.name}`;
|
|
31543
31676
|
if (entry.isDirectory()) {
|
|
31544
31677
|
visit(absolute, `${spelled}/`);
|
|
@@ -31548,7 +31681,7 @@ function projectTexts(projectPath) {
|
|
|
31548
31681
|
if (!entry.isFile() || BINARY_EXTENSIONS.has(extension)) {
|
|
31549
31682
|
continue;
|
|
31550
31683
|
}
|
|
31551
|
-
const bytes =
|
|
31684
|
+
const bytes = readFileSync15(absolute);
|
|
31552
31685
|
if (!isBinary(bytes)) {
|
|
31553
31686
|
found.push({ path: `res://${spelled}`, absolute, text: bytes.toString("utf8") });
|
|
31554
31687
|
}
|
|
@@ -31564,11 +31697,11 @@ function ignoredTexts(projectPath) {
|
|
|
31564
31697
|
return;
|
|
31565
31698
|
}
|
|
31566
31699
|
const under = ignored || gdignored(directory);
|
|
31567
|
-
for (const entry of
|
|
31700
|
+
for (const entry of readdirSync6(directory, { withFileTypes: true })) {
|
|
31568
31701
|
if (skipped(entry.name)) {
|
|
31569
31702
|
continue;
|
|
31570
31703
|
}
|
|
31571
|
-
const absolute =
|
|
31704
|
+
const absolute = join18(directory, entry.name);
|
|
31572
31705
|
const spelled = `${prefix}${entry.name}`;
|
|
31573
31706
|
if (entry.isDirectory()) {
|
|
31574
31707
|
visit(absolute, `${spelled}/`, under);
|
|
@@ -31578,7 +31711,7 @@ function ignoredTexts(projectPath) {
|
|
|
31578
31711
|
if (!under || !entry.isFile() || BINARY_EXTENSIONS.has(extension) || entry.name === ".gdignore") {
|
|
31579
31712
|
continue;
|
|
31580
31713
|
}
|
|
31581
|
-
const bytes =
|
|
31714
|
+
const bytes = readFileSync15(absolute);
|
|
31582
31715
|
if (!isBinary(bytes)) {
|
|
31583
31716
|
found.push({ path: `res://${spelled}`, absolute, text: bytes.toString("utf8") });
|
|
31584
31717
|
}
|
|
@@ -31601,14 +31734,14 @@ function searchProject(projectPath, options) {
|
|
|
31601
31734
|
if (steppedOver(directory)) {
|
|
31602
31735
|
return;
|
|
31603
31736
|
}
|
|
31604
|
-
for (const entry of
|
|
31737
|
+
for (const entry of readdirSync6(directory, { withFileTypes: true })) {
|
|
31605
31738
|
if (full()) {
|
|
31606
31739
|
return;
|
|
31607
31740
|
}
|
|
31608
31741
|
if (skipped(entry.name)) {
|
|
31609
31742
|
continue;
|
|
31610
31743
|
}
|
|
31611
|
-
const entryPath =
|
|
31744
|
+
const entryPath = join18(directory, entry.name);
|
|
31612
31745
|
if (entry.isDirectory()) {
|
|
31613
31746
|
visit(entryPath);
|
|
31614
31747
|
continue;
|
|
@@ -31617,7 +31750,7 @@ function searchProject(projectPath, options) {
|
|
|
31617
31750
|
if (!entry.isFile() || extensions !== null && !extensions.has(extension)) {
|
|
31618
31751
|
continue;
|
|
31619
31752
|
}
|
|
31620
|
-
const bytes =
|
|
31753
|
+
const bytes = readFileSync15(entryPath);
|
|
31621
31754
|
if (extensions === null && isBinary(bytes)) {
|
|
31622
31755
|
continue;
|
|
31623
31756
|
}
|
|
@@ -31779,8 +31912,8 @@ function setupPromptHandlers(server) {
|
|
|
31779
31912
|
}
|
|
31780
31913
|
|
|
31781
31914
|
// src/rename.ts
|
|
31782
|
-
import { existsSync as
|
|
31783
|
-
import { dirname as
|
|
31915
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync9, readFileSync as readFileSync16, renameSync as renameSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync10 } from "node:fs";
|
|
31916
|
+
import { dirname as dirname12, posix as posix2 } from "node:path";
|
|
31784
31917
|
class Script {
|
|
31785
31918
|
path;
|
|
31786
31919
|
text;
|
|
@@ -32502,26 +32635,26 @@ function rewritesOf(plan, texts, move) {
|
|
|
32502
32635
|
}
|
|
32503
32636
|
function applyRewrites(rewrites) {
|
|
32504
32637
|
for (const rewrite of rewrites) {
|
|
32505
|
-
const now =
|
|
32638
|
+
const now = readFileSync16(rewrite.absolute, "utf8");
|
|
32506
32639
|
if (now !== rewrite.before) {
|
|
32507
32640
|
throw new Error(`${rewrite.path} changed on disk while the rename was being worked out; nothing was written`);
|
|
32508
32641
|
}
|
|
32509
|
-
if (rewrite.writeTo !== rewrite.absolute &&
|
|
32642
|
+
if (rewrite.writeTo !== rewrite.absolute && existsSync15(rewrite.writeTo)) {
|
|
32510
32643
|
throw new Error(`${rewrite.writeTo} already exists; nothing was written`);
|
|
32511
32644
|
}
|
|
32512
32645
|
}
|
|
32513
32646
|
const done = [];
|
|
32514
32647
|
try {
|
|
32515
32648
|
for (const rewrite of rewrites) {
|
|
32516
|
-
mkdirSync9(
|
|
32649
|
+
mkdirSync9(dirname12(rewrite.writeTo), { recursive: true });
|
|
32517
32650
|
const staging = `${rewrite.writeTo}.gdharness-rename`;
|
|
32518
32651
|
writeFileSync10(staging, rewrite.after, "utf8");
|
|
32519
|
-
|
|
32652
|
+
renameSync3(staging, rewrite.writeTo);
|
|
32520
32653
|
let movedUid = false;
|
|
32521
32654
|
if (rewrite.writeTo !== rewrite.absolute) {
|
|
32522
|
-
|
|
32523
|
-
if (
|
|
32524
|
-
|
|
32655
|
+
unlinkSync3(rewrite.absolute);
|
|
32656
|
+
if (existsSync15(`${rewrite.absolute}.uid`)) {
|
|
32657
|
+
renameSync3(`${rewrite.absolute}.uid`, `${rewrite.writeTo}.uid`);
|
|
32525
32658
|
movedUid = true;
|
|
32526
32659
|
}
|
|
32527
32660
|
}
|
|
@@ -32532,9 +32665,9 @@ function applyRewrites(rewrites) {
|
|
|
32532
32665
|
try {
|
|
32533
32666
|
writeFileSync10(rewrite.absolute, rewrite.before, "utf8");
|
|
32534
32667
|
if (rewrite.writeTo !== rewrite.absolute) {
|
|
32535
|
-
|
|
32668
|
+
unlinkSync3(rewrite.writeTo);
|
|
32536
32669
|
if (movedUid) {
|
|
32537
|
-
|
|
32670
|
+
renameSync3(`${rewrite.writeTo}.uid`, `${rewrite.absolute}.uid`);
|
|
32538
32671
|
}
|
|
32539
32672
|
}
|
|
32540
32673
|
} catch {}
|
|
@@ -32544,11 +32677,11 @@ function applyRewrites(rewrites) {
|
|
|
32544
32677
|
}
|
|
32545
32678
|
|
|
32546
32679
|
// src/rename-symbol.ts
|
|
32547
|
-
import { existsSync as
|
|
32548
|
-
import { basename as
|
|
32680
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync10, readdirSync as readdirSync7, realpathSync as realpathSync5, rmSync as rmSync7, writeFileSync as writeFileSync11 } from "node:fs";
|
|
32681
|
+
import { basename as basename4, dirname as dirname13, join as join19, relative as relative3, resolve as resolve5 } from "node:path";
|
|
32549
32682
|
|
|
32550
32683
|
// src/resources.ts
|
|
32551
|
-
import { readFileSync as
|
|
32684
|
+
import { readFileSync as readFileSync17, realpathSync as realpathSync4 } from "node:fs";
|
|
32552
32685
|
import { extname as extname2, resolve as resolve4 } from "node:path";
|
|
32553
32686
|
var STATIC_RESOURCES = [
|
|
32554
32687
|
{
|
|
@@ -32779,7 +32912,7 @@ function readResourceText(uri, getProjectPath) {
|
|
|
32779
32912
|
const parsedUri = parseGodotUri(uri);
|
|
32780
32913
|
if (parsedUri.kind === "project-info") {
|
|
32781
32914
|
const projectFilePath = resolveProjectFile(projectPath, "project.godot");
|
|
32782
|
-
const rawProject =
|
|
32915
|
+
const rawProject = readFileSync17(projectFilePath, "utf-8");
|
|
32783
32916
|
const parsedProject = parseProjectGodot(rawProject);
|
|
32784
32917
|
return {
|
|
32785
32918
|
mimeType: "application/json",
|
|
@@ -32788,7 +32921,7 @@ function readResourceText(uri, getProjectPath) {
|
|
|
32788
32921
|
}
|
|
32789
32922
|
const filePath = resolveProjectFile(projectPath, parsedUri.resourcePath);
|
|
32790
32923
|
ensureAllowedExtension(parsedUri.kind, filePath);
|
|
32791
|
-
const text =
|
|
32924
|
+
const text = readFileSync17(filePath, "utf-8");
|
|
32792
32925
|
return {
|
|
32793
32926
|
mimeType: parsedUri.kind === "script" ? "text/x-gdscript" : "text/plain",
|
|
32794
32927
|
text
|
|
@@ -32853,13 +32986,13 @@ function onDiskSpelling(path) {
|
|
|
32853
32986
|
let at = resolve5(path);
|
|
32854
32987
|
for (;; ) {
|
|
32855
32988
|
try {
|
|
32856
|
-
return
|
|
32989
|
+
return join19(realpathSync5.native(at), ...missing.reverse());
|
|
32857
32990
|
} catch {
|
|
32858
|
-
const parent =
|
|
32991
|
+
const parent = dirname13(at);
|
|
32859
32992
|
if (parent === at) {
|
|
32860
32993
|
return resolve5(path);
|
|
32861
32994
|
}
|
|
32862
|
-
missing.push(
|
|
32995
|
+
missing.push(basename4(at));
|
|
32863
32996
|
at = parent;
|
|
32864
32997
|
}
|
|
32865
32998
|
}
|
|
@@ -32906,7 +33039,7 @@ async function renameSymbol(request, services) {
|
|
|
32906
33039
|
reason: `newScriptPath must be a .gd path inside the project, and ${request.newScriptPath} is not.`
|
|
32907
33040
|
};
|
|
32908
33041
|
}
|
|
32909
|
-
if (
|
|
33042
|
+
if (existsSync16(request.newScriptPath)) {
|
|
32910
33043
|
return { ok: false, reason: `${moveTo} already exists, so the script cannot be moved there.` };
|
|
32911
33044
|
}
|
|
32912
33045
|
}
|
|
@@ -33082,7 +33215,7 @@ async function renameSymbol(request, services) {
|
|
|
33082
33215
|
scripts,
|
|
33083
33216
|
from: declaringPath,
|
|
33084
33217
|
to: moveTo,
|
|
33085
|
-
exists: (path) =>
|
|
33218
|
+
exists: (path) => existsSync16(`${projectPath}/${path.slice("res://".length)}`)
|
|
33086
33219
|
}, plan);
|
|
33087
33220
|
}
|
|
33088
33221
|
plan = mentionIgnored(plan, ignoredTexts(projectPath), symbol, moveTo === null ? null : declaringPath);
|
|
@@ -33283,15 +33416,15 @@ ${mention.kind}`;
|
|
|
33283
33416
|
var LEFT_ALONE_ANSWER_LINES = 300;
|
|
33284
33417
|
var detailWrites = 0;
|
|
33285
33418
|
function writeLeftAloneDetail(projectPath, symbol, detail) {
|
|
33286
|
-
const directory =
|
|
33419
|
+
const directory = join19(projectPath, ".godot", "gdharness-renames");
|
|
33287
33420
|
mkdirSync10(directory, { recursive: true });
|
|
33288
33421
|
detailWrites += 1;
|
|
33289
|
-
const path =
|
|
33422
|
+
const path = join19(directory, `${String(Date.now())}-${String(detailWrites).padStart(6, "0")}-${String(process.pid)}-${symbol}.json`);
|
|
33290
33423
|
writeFileSync11(path, `${JSON.stringify(detail, null, 2)}
|
|
33291
33424
|
`, "utf8");
|
|
33292
|
-
const kept =
|
|
33425
|
+
const kept = readdirSync7(directory).filter((name) => name.endsWith(".json")).sort().reverse();
|
|
33293
33426
|
for (const old of kept.slice(10)) {
|
|
33294
|
-
rmSync7(
|
|
33427
|
+
rmSync7(join19(directory, old), { force: true });
|
|
33295
33428
|
}
|
|
33296
33429
|
return path;
|
|
33297
33430
|
}
|
|
@@ -33314,15 +33447,15 @@ function summaryOf(edits, mentions, rewrites, left) {
|
|
|
33314
33447
|
}
|
|
33315
33448
|
|
|
33316
33449
|
// src/restart-note.ts
|
|
33317
|
-
import { mkdirSync as mkdirSync11, readFileSync as
|
|
33318
|
-
import { dirname as
|
|
33450
|
+
import { mkdirSync as mkdirSync11, readFileSync as readFileSync18, rmSync as rmSync8, writeFileSync as writeFileSync12 } from "node:fs";
|
|
33451
|
+
import { dirname as dirname14, join as join20 } from "node:path";
|
|
33319
33452
|
function restartNotePath(projectPath) {
|
|
33320
|
-
return
|
|
33453
|
+
return join20(projectPath, ".godot", "gdharness-restart.json");
|
|
33321
33454
|
}
|
|
33322
33455
|
function noteRestartBegun(note) {
|
|
33323
33456
|
const path = restartNotePath(note.projectPath);
|
|
33324
33457
|
try {
|
|
33325
|
-
mkdirSync11(
|
|
33458
|
+
mkdirSync11(dirname14(path), { recursive: true });
|
|
33326
33459
|
writeFileSync12(path, `${JSON.stringify(note, null, 2)}
|
|
33327
33460
|
`, "utf8");
|
|
33328
33461
|
} catch (error) {
|
|
@@ -33331,7 +33464,7 @@ function noteRestartBegun(note) {
|
|
|
33331
33464
|
}
|
|
33332
33465
|
function restartOwed(projectPath) {
|
|
33333
33466
|
try {
|
|
33334
|
-
const parsed = JSON.parse(
|
|
33467
|
+
const parsed = JSON.parse(readFileSync18(restartNotePath(projectPath), "utf8"));
|
|
33335
33468
|
if (typeof parsed !== "object" || parsed === null) {
|
|
33336
33469
|
return null;
|
|
33337
33470
|
}
|
|
@@ -33355,12 +33488,12 @@ function restartSettled(projectPath) {
|
|
|
33355
33488
|
// src/run-record.ts
|
|
33356
33489
|
import { execFile as execFile4 } from "node:child_process";
|
|
33357
33490
|
import { createHash as createHash3 } from "node:crypto";
|
|
33358
|
-
import { mkdirSync as mkdirSync12, openSync as openSync2, readdirSync as
|
|
33359
|
-
import { basename as
|
|
33491
|
+
import { mkdirSync as mkdirSync12, openSync as openSync2, readdirSync as readdirSync8, readFileSync as readFileSync19, realpathSync as realpathSync6, rmSync as rmSync10, statSync as statSync7 } from "node:fs";
|
|
33492
|
+
import { basename as basename5, dirname as dirname15, join as join21, resolve as resolve6 } from "node:path";
|
|
33360
33493
|
import { promisify as promisify4 } from "node:util";
|
|
33361
33494
|
|
|
33362
33495
|
// src/whole-file.ts
|
|
33363
|
-
import { renameSync as
|
|
33496
|
+
import { renameSync as renameSync4, rmSync as rmSync9, writeFileSync as writeFileSync13 } from "node:fs";
|
|
33364
33497
|
var HELD_FILE_MS = 1000;
|
|
33365
33498
|
var HELD_FILE_RETRY_MS = 10;
|
|
33366
33499
|
var HELD_BY_A_READER = new Set(["EPERM", "EACCES", "EBUSY"]);
|
|
@@ -33374,7 +33507,7 @@ function writeWhole(path, text) {
|
|
|
33374
33507
|
const until = Date.now() + HELD_FILE_MS;
|
|
33375
33508
|
for (;; ) {
|
|
33376
33509
|
try {
|
|
33377
|
-
|
|
33510
|
+
renameSync4(partial, path);
|
|
33378
33511
|
return true;
|
|
33379
33512
|
} catch (error) {
|
|
33380
33513
|
const code = error.code ?? "";
|
|
@@ -33392,20 +33525,20 @@ function writeWhole(path, text) {
|
|
|
33392
33525
|
// src/run-record.ts
|
|
33393
33526
|
var execFileAsync = promisify4(execFile4);
|
|
33394
33527
|
function runsDirectory() {
|
|
33395
|
-
return
|
|
33528
|
+
return join21(runtimeDirectory(), "runs");
|
|
33396
33529
|
}
|
|
33397
33530
|
function realAsFarAsItExists(path) {
|
|
33398
33531
|
const missing = [];
|
|
33399
33532
|
let at = path;
|
|
33400
33533
|
for (;; ) {
|
|
33401
33534
|
try {
|
|
33402
|
-
return
|
|
33535
|
+
return join21(realpathSync6.native(at), ...missing);
|
|
33403
33536
|
} catch {
|
|
33404
|
-
const parent =
|
|
33537
|
+
const parent = dirname15(at);
|
|
33405
33538
|
if (parent === at) {
|
|
33406
33539
|
return path;
|
|
33407
33540
|
}
|
|
33408
|
-
missing.unshift(
|
|
33541
|
+
missing.unshift(basename5(at));
|
|
33409
33542
|
at = parent;
|
|
33410
33543
|
}
|
|
33411
33544
|
}
|
|
@@ -33416,10 +33549,10 @@ function projectKey(projectPath) {
|
|
|
33416
33549
|
return createHash3("sha256").update(folded.replaceAll("\\", "/")).digest("hex").slice(0, 16);
|
|
33417
33550
|
}
|
|
33418
33551
|
function runRecordPath(projectPath, directory = runtimeDirectory()) {
|
|
33419
|
-
return
|
|
33552
|
+
return join21(directory, "runs", `note-${projectKey(projectPath)}.json`);
|
|
33420
33553
|
}
|
|
33421
33554
|
function latestRunPath(servedBy, directory = runtimeDirectory()) {
|
|
33422
|
-
return
|
|
33555
|
+
return join21(directory, "runs", `latest-${projectKey(servedBy)}.json`);
|
|
33423
33556
|
}
|
|
33424
33557
|
function writeLatestRun(servedBy, latest) {
|
|
33425
33558
|
try {
|
|
@@ -33429,7 +33562,7 @@ function writeLatestRun(servedBy, latest) {
|
|
|
33429
33562
|
}
|
|
33430
33563
|
function readLatestRun(servedBy, directory = runtimeDirectory()) {
|
|
33431
33564
|
try {
|
|
33432
|
-
const fields = JSON.parse(
|
|
33565
|
+
const fields = JSON.parse(readFileSync19(latestRunPath(servedBy, directory), "utf8"));
|
|
33433
33566
|
if (typeof fields !== "object" || fields === null) {
|
|
33434
33567
|
return null;
|
|
33435
33568
|
}
|
|
@@ -33445,17 +33578,17 @@ function readLatestRun(servedBy, directory = runtimeDirectory()) {
|
|
|
33445
33578
|
var SHARED_NOTE = "run.json";
|
|
33446
33579
|
function openTranscript(startedAt) {
|
|
33447
33580
|
mkdirSync12(runsDirectory(), { recursive: true });
|
|
33448
|
-
const path =
|
|
33581
|
+
const path = join21(runsDirectory(), `run-${startedAt}.log`);
|
|
33449
33582
|
return { path, fd: openSync2(path, "a") };
|
|
33450
33583
|
}
|
|
33451
33584
|
function profilePath(startedAt) {
|
|
33452
|
-
return
|
|
33585
|
+
return join21(runsDirectory(), `run-${startedAt}.profile.json`);
|
|
33453
33586
|
}
|
|
33454
33587
|
function noteIsAbout(note, playing) {
|
|
33455
33588
|
return note.play !== undefined && playing !== null && note.play.play === playing.play && note.play.editorPid === playing.editorPid;
|
|
33456
33589
|
}
|
|
33457
33590
|
function editorNotePath(projectPath, directory = runtimeDirectory()) {
|
|
33458
|
-
return
|
|
33591
|
+
return join21(directory, "runs", `editor-${projectKey(projectPath)}.json`);
|
|
33459
33592
|
}
|
|
33460
33593
|
function writeEditorRunNote(note) {
|
|
33461
33594
|
mkdirSync12(runsDirectory(), { recursive: true });
|
|
@@ -33466,7 +33599,7 @@ function writeEditorRunNote(note) {
|
|
|
33466
33599
|
}
|
|
33467
33600
|
function readEditorRunNote(projectPath) {
|
|
33468
33601
|
for (const directory of runtimeDirectories()) {
|
|
33469
|
-
const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(
|
|
33602
|
+
const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(join21(directory, "runs", "editor-run.json"));
|
|
33470
33603
|
if (note !== null && isSameDirectory(note.projectPath, projectPath)) {
|
|
33471
33604
|
return note;
|
|
33472
33605
|
}
|
|
@@ -33476,7 +33609,7 @@ function readEditorRunNote(projectPath) {
|
|
|
33476
33609
|
function editorNoteAt(path) {
|
|
33477
33610
|
let parsed;
|
|
33478
33611
|
try {
|
|
33479
|
-
parsed = JSON.parse(
|
|
33612
|
+
parsed = JSON.parse(readFileSync19(path, "utf8"));
|
|
33480
33613
|
} catch {
|
|
33481
33614
|
return null;
|
|
33482
33615
|
}
|
|
@@ -33500,7 +33633,7 @@ function readRunRecord(projectPath) {
|
|
|
33500
33633
|
if (own !== null) {
|
|
33501
33634
|
return own;
|
|
33502
33635
|
}
|
|
33503
|
-
const shared = recordAt(
|
|
33636
|
+
const shared = recordAt(join21(directory, "runs", SHARED_NOTE));
|
|
33504
33637
|
if (shared !== null && shared.projectPath !== "" && isSameDirectory(shared.projectPath, projectPath)) {
|
|
33505
33638
|
return shared;
|
|
33506
33639
|
}
|
|
@@ -33512,13 +33645,13 @@ function everyRunRecord() {
|
|
|
33512
33645
|
for (const directory of runtimeDirectories()) {
|
|
33513
33646
|
let entries;
|
|
33514
33647
|
try {
|
|
33515
|
-
entries =
|
|
33648
|
+
entries = readdirSync8(join21(directory, "runs"));
|
|
33516
33649
|
} catch {
|
|
33517
33650
|
continue;
|
|
33518
33651
|
}
|
|
33519
33652
|
for (const entry of entries) {
|
|
33520
33653
|
if (entry === SHARED_NOTE || entry.startsWith("note-") && entry.endsWith(".json")) {
|
|
33521
|
-
const record = recordAt(
|
|
33654
|
+
const record = recordAt(join21(directory, "runs", entry));
|
|
33522
33655
|
if (record !== null) {
|
|
33523
33656
|
found.push(record);
|
|
33524
33657
|
}
|
|
@@ -33530,7 +33663,7 @@ function everyRunRecord() {
|
|
|
33530
33663
|
function recordAt(path) {
|
|
33531
33664
|
let parsed;
|
|
33532
33665
|
try {
|
|
33533
|
-
parsed = JSON.parse(
|
|
33666
|
+
parsed = JSON.parse(readFileSync19(path, "utf8"));
|
|
33534
33667
|
} catch {
|
|
33535
33668
|
return null;
|
|
33536
33669
|
}
|
|
@@ -33583,7 +33716,7 @@ async function runningAs(pid) {
|
|
|
33583
33716
|
const began = await startedAt(pid, null);
|
|
33584
33717
|
const when = began === null ? {} : { startedAt: began };
|
|
33585
33718
|
if (process.platform === "linux") {
|
|
33586
|
-
const raw =
|
|
33719
|
+
const raw = readFileSync19(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
|
|
33587
33720
|
return raw === "" ? null : { kind: "commandLine", text: raw, ...when };
|
|
33588
33721
|
}
|
|
33589
33722
|
const args = (await execFileAsync("ps", ["-p", String(pid), "-o", "args="], { encoding: "utf8", timeout: 15000 })).stdout.trim();
|
|
@@ -33690,7 +33823,7 @@ function whyNotTheRun(record, running, needed) {
|
|
|
33690
33823
|
if (after > SAME_RUN_WINDOW_MS) {
|
|
33691
33824
|
return `that process started ${Math.round(after / 1000)} seconds after the run had`;
|
|
33692
33825
|
}
|
|
33693
|
-
const engine = record.command === undefined ? null :
|
|
33826
|
+
const engine = record.command === undefined ? null : basename5(record.command);
|
|
33694
33827
|
const said = process.platform === "win32" ? running.text.toLowerCase() : running.text;
|
|
33695
33828
|
const wanted = engine === null || process.platform !== "win32" ? engine : engine.toLowerCase();
|
|
33696
33829
|
if (wanted !== null && !said.includes(wanted)) {
|
|
@@ -33729,7 +33862,7 @@ function judgeAnnouncedGame(running, projectPath, announcedAt) {
|
|
|
33729
33862
|
}
|
|
33730
33863
|
function clearRunRecord(projectPath) {
|
|
33731
33864
|
for (const directory of runtimeDirectories()) {
|
|
33732
|
-
const shared =
|
|
33865
|
+
const shared = join21(directory, "runs", SHARED_NOTE);
|
|
33733
33866
|
const sharedRecord = recordAt(shared);
|
|
33734
33867
|
const ours = sharedRecord !== null && sharedRecord.projectPath !== "" && isSameDirectory(sharedRecord.projectPath, projectPath);
|
|
33735
33868
|
for (const path of [runRecordPath(projectPath, directory), ...ours ? [shared] : []]) {
|
|
@@ -33754,7 +33887,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
33754
33887
|
]));
|
|
33755
33888
|
let entries;
|
|
33756
33889
|
try {
|
|
33757
|
-
entries =
|
|
33890
|
+
entries = readdirSync8(directory);
|
|
33758
33891
|
} catch {
|
|
33759
33892
|
return;
|
|
33760
33893
|
}
|
|
@@ -33763,7 +33896,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
33763
33896
|
if (!transcript && !entry.endsWith(".partial")) {
|
|
33764
33897
|
continue;
|
|
33765
33898
|
}
|
|
33766
|
-
const path =
|
|
33899
|
+
const path = join21(directory, entry);
|
|
33767
33900
|
if (kept.has(path) || ageMs(path, now) < keepMs) {
|
|
33768
33901
|
continue;
|
|
33769
33902
|
}
|
|
@@ -33774,7 +33907,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
33774
33907
|
}
|
|
33775
33908
|
|
|
33776
33909
|
// src/script-origins.ts
|
|
33777
|
-
import { join as
|
|
33910
|
+
import { join as join22 } from "node:path";
|
|
33778
33911
|
var UNRESOLVED = /Could not resolve class "([A-Za-z_][A-Za-z0-9_]*)", because of a parser error/;
|
|
33779
33912
|
function unresolvedClassIn(message) {
|
|
33780
33913
|
return UNRESOLVED.exec(message)?.[1] ?? null;
|
|
@@ -33830,7 +33963,7 @@ function checkWithEngine(godotPath, projectPath) {
|
|
|
33830
33963
|
const ran = await runEngine(godotPath, [
|
|
33831
33964
|
"--headless",
|
|
33832
33965
|
"--log-file",
|
|
33833
|
-
|
|
33966
|
+
join22(logDir, "engine.log"),
|
|
33834
33967
|
"--path",
|
|
33835
33968
|
projectPath,
|
|
33836
33969
|
"--check-only",
|
|
@@ -33867,16 +34000,10 @@ function withoutListed(entries, errors) {
|
|
|
33867
34000
|
}
|
|
33868
34001
|
|
|
33869
34002
|
// src/script-profile.ts
|
|
33870
|
-
import { readFileSync as
|
|
33871
|
-
|
|
33872
|
-
// src/godot-variant.ts
|
|
33873
|
-
var FLAG_64 = 1 << 16;
|
|
33874
|
-
var MOST_MESSAGE_BYTES = (8 << 20) + 4;
|
|
33875
|
-
|
|
33876
|
-
// src/script-profile.ts
|
|
34003
|
+
import { readFileSync as readFileSync20 } from "node:fs";
|
|
33877
34004
|
function readProfileFile(path) {
|
|
33878
34005
|
try {
|
|
33879
|
-
const parsed = JSON.parse(
|
|
34006
|
+
const parsed = JSON.parse(readFileSync20(path, "utf8"));
|
|
33880
34007
|
return typeof parsed === "object" && typeof parsed.connected === "boolean" ? parsed : null;
|
|
33881
34008
|
} catch {
|
|
33882
34009
|
return null;
|
|
@@ -34992,7 +35119,7 @@ var TOOL_SPECS = [
|
|
|
34992
35119
|
type: "array",
|
|
34993
35120
|
items: { type: "string" },
|
|
34994
35121
|
ops: ["start", "check"],
|
|
34995
|
-
description: `The game's own arguments, what OS.get_cmdline_user_args() answers, such as ["--level=2"]. The separator is added here, so a flag the engine reads goes to the game instead, and the answer says so under argsNote. A run with any is started by this server rather than by the editor, which fixes the game's command line when it opens the project, so the debug_* tools
|
|
35122
|
+
description: `The game's own arguments, what OS.get_cmdline_user_args() answers, such as ["--level=2"]. The separator is added here, so a flag the engine reads goes to the game instead, and the answer says so under argsNote. A run with any is started by this server rather than by the editor, which fixes the game's command line when it opens the project, so its debugger is the keeper holding it rather than the editor; the debug_* tools answer for it all the same.`
|
|
34996
35123
|
},
|
|
34997
35124
|
engineArgs: {
|
|
34998
35125
|
type: "array",
|
|
@@ -35009,7 +35136,7 @@ var TOOL_SPECS = [
|
|
|
35009
35136
|
silent: {
|
|
35010
35137
|
type: "boolean",
|
|
35011
35138
|
ops: ["start"],
|
|
35012
|
-
description: "start: run the game with no sound out of the machine, by starting the engine with --audio-driver Dummy: every stream still plays and every playback position still advances, so what is playing can still be asked. The editor takes no engine flags for a game it plays, so a silent windowed run is started by this server,
|
|
35139
|
+
description: "start: run the game with no sound out of the machine, by starting the engine with --audio-driver Dummy: every stream still plays and every playback position still advances, so what is playing can still be asked. The editor takes no engine flags for a game it plays, so a silent windowed run is started by this server, whose keeper is its debugger rather than the editor. A headless run, check's included, is silent already. Default false, or true when GDHARNESS_SILENT is set in the server's environment to anything but 0 or false; silent: false then plays through the editor as before."
|
|
35013
35140
|
},
|
|
35014
35141
|
savesIn: {
|
|
35015
35142
|
type: "string",
|
|
@@ -35335,12 +35462,12 @@ var TOOL_SPECS = [
|
|
|
35335
35462
|
},
|
|
35336
35463
|
metrics: {
|
|
35337
35464
|
effect: "read",
|
|
35338
|
-
summary: "frame time, memory, draw calls and the rest. frame_time is the mean wall time of a frame over the last frames_timed frames (up to 120, timed by the game itself, physics and the wait for the display included), frame_time_max the slowest of them, both in seconds and null before a frame has passed; vsync says whether a frame waits for the display, and max_fps is the game's frame-rate cap, 0 for none. fps, process_time_max and physics_time are the engine's monitors, which it sets once a second: the frames counted in the last second, and the slowest process and physics step in it, so they trail a change by up to a second and the last two are a worst case rather than the frame. The memory, object and render counts are read as asked. memory_static is what the engine allocated on the CPU side, in bytes; render_video_mem_used, render_texture_mem_used and render_buffer_mem_used are what the renderer has on the GPU, in bytes, which memory_static does not count",
|
|
35465
|
+
summary: "frame time, memory, draw calls and the rest. frame_time is the mean wall time of a frame over the last frames_timed frames (up to 120, timed by the game itself, physics and the wait for the display included), frame_time_max the slowest of them, both in seconds and null before a frame has passed; vsync says whether a frame waits for the display, and max_fps is the game's frame-rate cap, 0 for none. physics_steps_per_second is the physics steps taken a second over those frames, fewer than physics_ticks_per_second when a frame needs more than max_physics_steps_per_frame or the editor's Game view slows it. fps, process_time_max and physics_time are the engine's monitors, which it sets once a second: the frames counted in the last second, and the slowest process and physics step in it, so they trail a change by up to a second and the last two are a worst case rather than the frame. The memory, object and render counts are read as asked. memory_static is what the engine allocated on the CPU side, in bytes; render_video_mem_used, render_texture_mem_used and render_buffer_mem_used are what the renderer has on the GPU, in bytes, which memory_static does not count",
|
|
35339
35466
|
requires: []
|
|
35340
35467
|
},
|
|
35341
35468
|
accessibility: {
|
|
35342
35469
|
effect: "read",
|
|
35343
|
-
summary:
|
|
35470
|
+
summary: "what the game tells a screen reader, read through UI Automation as a Windows screen reader reads it, under windows: one tree per window, each element with its role (text for static text, button, check_box, group for a control holding others), name, value, description, placeholder, bounds in the window's pixels, the actions a reader is offered (click, focus, toggle, set_value, select, expand_collapse, scroll_into_view), and focused on the one with the focus. node is the path of the node that made the element; an element a node made as a part of itself has part_of instead, and part: announcement marks the text a window speaks announcements through. An empty name is read as nothing: static text takes it from accessibility_update_set_value, and _note counts the text elements on screen with none. tooltip is the node's tooltip_text, which a Windows reader is not told. With no control focused, the engine reports the first node under the root, GdharnessRuntime, as focused. Needs a windowed run on Windows started with engineArgs [\"--accessibility\", \"always\"] or accessibility_support at Always Active, since the engine builds the tree only when asked; reading takes a second or two",
|
|
35344
35471
|
requires: []
|
|
35345
35472
|
}
|
|
35346
35473
|
},
|
|
@@ -35690,7 +35817,7 @@ var TOOL_SPECS = [
|
|
|
35690
35817
|
{
|
|
35691
35818
|
name: "debug_breakpoint",
|
|
35692
35819
|
title: "Breakpoints",
|
|
35693
|
-
description: "Sets or removes a breakpoint through the editor's debug adapter.
|
|
35820
|
+
description: "Sets or removes a breakpoint. Set them first, then editor_run, and the game stops where you asked. With the editor connected it goes through the editor's debug adapter; with none, it is held here. A game this server starts itself, which editor_run start does for a run given args, savesIn, env or silent, or with no editor, is handed every breakpoint held here as it starts, and one set while that game runs reaches it at once (runningGame says so). The editor keeps a breakpoint set this way for one play, so every breakpoint this server holds is sent again before each play editor_run starts, and the start answer lists them under breakpoints. The set is also kept in the project, for the server that comes after a reconnect: a breakpoint stays until it is removed here, whichever server set it. Every answer lists the whole set under held, and the breakpoints the user set in the editor's own gutter under setInEditor; setting one here never takes those away, and removing a line here clears it whoever set it. Godot clears every breakpoint in the editor when a debug session opens unless network/debug_adapter/sync_breakpoints is on, which the addon turns on as it loads; an editor running an older addon is said so under breakpointsAtRisk, here and in editor_status.",
|
|
35694
35821
|
parameters: {
|
|
35695
35822
|
projectPath: PROJECT_PATH2,
|
|
35696
35823
|
scriptPath: SCRIPT_PATH,
|
|
@@ -35705,23 +35832,33 @@ var TOOL_SPECS = [
|
|
|
35705
35832
|
{
|
|
35706
35833
|
name: "debug_control",
|
|
35707
35834
|
title: "Step the debugger",
|
|
35708
|
-
description: "
|
|
35835
|
+
description: "Pauses, continues or steps the debugged game. A step waits for the game to stop again and answers with the stack where it ended up; one that has not stopped the game again within ten seconds answers heldAgain false and no stack. A game the editor plays is moved through the editor's debug adapter, which cannot pause or step out: it answers a pause by reporting the game stopped and leaving it running, and implements no stepOut, so hold such a game with a breakpoint and step over or into from there. A game this server started itself has its keeper as its debugger, which does both: pause breaks into the game wherever it is, a script spinning in a loop included, which is how a game hung on its first frame is asked where it is.",
|
|
35709
35836
|
parameters: {},
|
|
35710
35837
|
requires: [],
|
|
35711
35838
|
operations: {
|
|
35839
|
+
pause: {
|
|
35840
|
+
effect: "additive",
|
|
35841
|
+
summary: "stop a game this server started where it is, answering with the stack. The game takes it between frames, and in a script every 2048 lines it runs, so a loop that sleeps between few lines can take seconds; the call waits thirty, and past that answers paused false with the request still standing. Stopped between frames, where no script runs, the game is stepped on to the next line of script it runs",
|
|
35842
|
+
requires: []
|
|
35843
|
+
},
|
|
35712
35844
|
continue: { effect: "additive", summary: "resume after a breakpoint", requires: [] },
|
|
35713
35845
|
step_over: { effect: "additive", summary: "run the current line", requires: [] },
|
|
35714
35846
|
step_into: {
|
|
35715
35847
|
effect: "additive",
|
|
35716
35848
|
summary: "run the current line, stopping inside whatever it calls",
|
|
35717
35849
|
requires: []
|
|
35850
|
+
},
|
|
35851
|
+
step_out: {
|
|
35852
|
+
effect: "additive",
|
|
35853
|
+
summary: "run to the end of the current function and stop in its caller; a game this server started only",
|
|
35854
|
+
requires: []
|
|
35718
35855
|
}
|
|
35719
35856
|
}
|
|
35720
35857
|
},
|
|
35721
35858
|
{
|
|
35722
35859
|
name: "debug_state",
|
|
35723
35860
|
title: "Debugger state",
|
|
35724
|
-
description: "Where the debugged game is stopped: the stack trace, what is in scope at a frame with the values, or the
|
|
35861
|
+
description: "Where the debugged game is stopped: the stack trace, what is in scope at a frame with the values, or the debugger's console output so far, its last thousand lines, with any earlier ones counted under notShown. For a game this server started, its keeper's debugger answers: each frame names its script as the project spells it under script and on this machine under path, and held says which thread stopped and why. For a game the editor plays, the editor's debug adapter answers. A stop is reported by the editor's adapter only to the sessions connected when it happened, so a server that connected afterwards, which is every server after a reconnect, is shown a thread and no frames about a game still sitting at its breakpoint. That server asks the runtime instead: a held game accepts a connection and never answers, and the refusal then says held rather than running, and that the stack cannot be read from here. debug_control continue lets it go, and the next stop is one this session is told about, with its stack.",
|
|
35725
35862
|
parameters: {
|
|
35726
35863
|
frameId: {
|
|
35727
35864
|
type: "number",
|
|
@@ -35811,7 +35948,7 @@ function serverInstructions() {
|
|
|
35811
35948
|
"Call editor_status first: it says whether an editor is connected and whether its addon matches this server.",
|
|
35812
35949
|
"Start the game with editor_run start, never by spawning an engine, and read editor_output after every run:",
|
|
35813
35950
|
"its clean verdict and its error entries are how a run is judged, and editor_run wait is how to wait for one.",
|
|
35814
|
-
"The runtime_* tools work on the running game and the debug_* tools on
|
|
35951
|
+
"The runtime_* tools work on the running game and the debug_* tools on its debugger.",
|
|
35815
35952
|
"A refusal names what would have worked, so read it before calling again."
|
|
35816
35953
|
].join(" ");
|
|
35817
35954
|
}
|
|
@@ -35821,16 +35958,16 @@ import { spawnSync } from "node:child_process";
|
|
|
35821
35958
|
import { createHash as createHash4 } from "node:crypto";
|
|
35822
35959
|
import {
|
|
35823
35960
|
cpSync,
|
|
35824
|
-
existsSync as
|
|
35961
|
+
existsSync as existsSync17,
|
|
35825
35962
|
mkdirSync as mkdirSync13,
|
|
35826
|
-
readdirSync as
|
|
35827
|
-
readFileSync as
|
|
35963
|
+
readdirSync as readdirSync9,
|
|
35964
|
+
readFileSync as readFileSync21,
|
|
35828
35965
|
rmdirSync,
|
|
35829
35966
|
rmSync as rmSync11,
|
|
35830
35967
|
statSync as statSync8,
|
|
35831
35968
|
writeFileSync as writeFileSync14
|
|
35832
35969
|
} from "node:fs";
|
|
35833
|
-
import { dirname as
|
|
35970
|
+
import { dirname as dirname16, join as join23 } from "node:path";
|
|
35834
35971
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
35835
35972
|
var ADDONS = ["gdharness_editor", "gdharness_runtime", "auto_reload"];
|
|
35836
35973
|
var EDITOR_PLUGINS = ["gdharness_editor", "auto_reload"];
|
|
@@ -35849,25 +35986,25 @@ function runtimeAddonDigest(from = shippedAddonsDirectory()) {
|
|
|
35849
35986
|
function addonsDigest(names, from) {
|
|
35850
35987
|
const hash = createHash4("sha256");
|
|
35851
35988
|
const walk = (directory, relative) => {
|
|
35852
|
-
for (const name of
|
|
35989
|
+
for (const name of readdirSync9(directory).sort()) {
|
|
35853
35990
|
if (name === VERSION_MARKER || name === DIGEST_MARKER) {
|
|
35854
35991
|
continue;
|
|
35855
35992
|
}
|
|
35856
|
-
const path =
|
|
35993
|
+
const path = join23(directory, name);
|
|
35857
35994
|
const inside = `${relative}/${name}`;
|
|
35858
35995
|
if (statSync8(path).isDirectory()) {
|
|
35859
35996
|
walk(path, inside);
|
|
35860
35997
|
} else {
|
|
35861
35998
|
hash.update(`${inside}
|
|
35862
35999
|
`);
|
|
35863
|
-
hash.update(
|
|
36000
|
+
hash.update(readFileSync21(path));
|
|
35864
36001
|
hash.update(`
|
|
35865
36002
|
`);
|
|
35866
36003
|
}
|
|
35867
36004
|
}
|
|
35868
36005
|
};
|
|
35869
36006
|
for (const name of names) {
|
|
35870
|
-
walk(
|
|
36007
|
+
walk(join23(from, name), name);
|
|
35871
36008
|
}
|
|
35872
36009
|
return hash.digest("hex");
|
|
35873
36010
|
}
|
|
@@ -35900,22 +36037,22 @@ function installedRuntimeDigest(projectPath) {
|
|
|
35900
36037
|
return digestMarker(projectPath, RUNTIME_ADDON);
|
|
35901
36038
|
}
|
|
35902
36039
|
function digestMarker(projectPath, addon) {
|
|
35903
|
-
const marker =
|
|
35904
|
-
if (!
|
|
36040
|
+
const marker = join23(projectPath, "addons", addon, DIGEST_MARKER);
|
|
36041
|
+
if (!existsSync17(marker)) {
|
|
35905
36042
|
return null;
|
|
35906
36043
|
}
|
|
35907
|
-
const said =
|
|
36044
|
+
const said = readFileSync21(marker, "utf8").trim();
|
|
35908
36045
|
return said === "" ? null : said;
|
|
35909
36046
|
}
|
|
35910
36047
|
function shippedAddonsDirectory() {
|
|
35911
|
-
return
|
|
36048
|
+
return join23(dirname16(fileURLToPath3(import.meta.url)), "godot", "addons");
|
|
35912
36049
|
}
|
|
35913
36050
|
function installedAddonVersion(projectPath) {
|
|
35914
|
-
const marker =
|
|
35915
|
-
if (!
|
|
36051
|
+
const marker = join23(projectPath, "addons", ADDONS[0], VERSION_MARKER);
|
|
36052
|
+
if (!existsSync17(marker)) {
|
|
35916
36053
|
return null;
|
|
35917
36054
|
}
|
|
35918
|
-
const said =
|
|
36055
|
+
const said = readFileSync21(marker, "utf8").trim();
|
|
35919
36056
|
return said === "" ? null : said;
|
|
35920
36057
|
}
|
|
35921
36058
|
function loaderAmong(projectPath, named) {
|
|
@@ -35926,7 +36063,7 @@ function loaderAmong(projectPath, named) {
|
|
|
35926
36063
|
}
|
|
35927
36064
|
let source = null;
|
|
35928
36065
|
try {
|
|
35929
|
-
source =
|
|
36066
|
+
source = readFileSync21(join23(projectPath, path), "utf8");
|
|
35930
36067
|
} catch {}
|
|
35931
36068
|
if (source?.includes(RUNTIME_AUTOLOAD.path)) {
|
|
35932
36069
|
return { name: entry, path, exists: true };
|
|
@@ -35968,10 +36105,10 @@ function enabledPlugins(settings) {
|
|
|
35968
36105
|
function inspectProject(projectPath) {
|
|
35969
36106
|
const problems = [];
|
|
35970
36107
|
const addons = ADDONS.map((name) => {
|
|
35971
|
-
const directory =
|
|
35972
|
-
const marker =
|
|
35973
|
-
const installed =
|
|
35974
|
-
const version =
|
|
36108
|
+
const directory = join23(projectPath, "addons", name);
|
|
36109
|
+
const marker = join23(directory, VERSION_MARKER);
|
|
36110
|
+
const installed = existsSync17(directory);
|
|
36111
|
+
const version = existsSync17(marker) ? readFileSync21(marker, "utf8").trim() : null;
|
|
35975
36112
|
const current = version === SERVER_VERSION;
|
|
35976
36113
|
if (!installed) {
|
|
35977
36114
|
problems.push(`addons/${name} is not installed; run gdharness setup`);
|
|
@@ -35980,7 +36117,7 @@ function inspectProject(projectPath) {
|
|
|
35980
36117
|
}
|
|
35981
36118
|
return { name, installed, version, current };
|
|
35982
36119
|
});
|
|
35983
|
-
const settings = parseProjectGodot(
|
|
36120
|
+
const settings = parseProjectGodot(readFileSync21(join23(projectPath, "project.godot"), "utf8"));
|
|
35984
36121
|
const pluginsEnabled = enabledPlugins(settings);
|
|
35985
36122
|
for (const name of EDITOR_PLUGINS) {
|
|
35986
36123
|
if (!pluginsEnabled.includes(name)) {
|
|
@@ -35996,7 +36133,7 @@ function inspectProject(projectPath) {
|
|
|
35996
36133
|
const tracked = trackedByGit(projectPath, [...named.values()]);
|
|
35997
36134
|
const addonMissing = (path) => addons.some((addon) => !addon.installed && path.startsWith(`addons/${addon.name}/`));
|
|
35998
36135
|
for (const [name, path] of named) {
|
|
35999
|
-
if (!
|
|
36136
|
+
if (!existsSync17(join23(projectPath, path))) {
|
|
36000
36137
|
if (!addonMissing(path)) {
|
|
36001
36138
|
problems.push(`the ${name} autoload names res://${path}, which is not in this project, so it boots with a missing script; restore that file, or point the autoload at one that is here`);
|
|
36002
36139
|
}
|
|
@@ -36047,7 +36184,7 @@ function argumentShape(schema) {
|
|
|
36047
36184
|
var UPDATE_NOTICE_EVERY = 500;
|
|
36048
36185
|
var FEEDBACK_NOTICE_EVERY = 250;
|
|
36049
36186
|
var run4 = promisify5(execFile5);
|
|
36050
|
-
var __dirname2 =
|
|
36187
|
+
var __dirname2 = dirname17(fileURLToPath4(import.meta.url));
|
|
36051
36188
|
var PARENT_ASKED_AFTER_MS = 5000;
|
|
36052
36189
|
function editorRestartTimeoutMs() {
|
|
36053
36190
|
return waitFromEnv("GDHARNESS_EDITOR_RESTART_TIMEOUT_MS", 90000);
|
|
@@ -36079,22 +36216,22 @@ function writtenFileRefusal(path, rule) {
|
|
|
36079
36216
|
if (/^(res|user):\/\//.test(path)) {
|
|
36080
36217
|
return `${named} ${path} is a Godot path; ${what} is saved outside the project, at an absolute path on this machine.`;
|
|
36081
36218
|
}
|
|
36082
|
-
if (!
|
|
36219
|
+
if (!isAbsolute4(path)) {
|
|
36083
36220
|
return `${named} ${path} is not absolute; name the whole path, so where it is written does not depend on this server's directory.`;
|
|
36084
36221
|
}
|
|
36085
36222
|
if (!rule.extensions.includes(extname3(path).toLowerCase())) {
|
|
36086
36223
|
return `${named} ${path} does not end in ${rule.extensions.join(" or ")}, which is what ${what} is.`;
|
|
36087
36224
|
}
|
|
36088
|
-
const directory = realPathOr(
|
|
36089
|
-
if (!
|
|
36090
|
-
return `${named}'s directory ${
|
|
36225
|
+
const directory = realPathOr(dirname17(path));
|
|
36226
|
+
if (!existsSync18(directory)) {
|
|
36227
|
+
return `${named}'s directory ${dirname17(path)} does not exist; ${what} creates no directories.`;
|
|
36091
36228
|
}
|
|
36092
|
-
const where =
|
|
36229
|
+
const where = join24(directory, basename6(path));
|
|
36093
36230
|
const inside = rule.projects.find((project) => project !== "" && isWithinRoot(realPathOr(project), where));
|
|
36094
36231
|
if (inside !== undefined) {
|
|
36095
36232
|
return `${named} ${path} is inside the project at ${inside}; ${what} is saved outside it, so it never becomes part of the game.`;
|
|
36096
36233
|
}
|
|
36097
|
-
const taken = rule.writes(where).find((file) =>
|
|
36234
|
+
const taken = rule.writes(where).find((file) => existsSync18(file));
|
|
36098
36235
|
if (taken !== undefined) {
|
|
36099
36236
|
return taken === where ? `${named} ${path} already exists; ${what} is not written over a file. Name a new one.` : `${named} ${path} would write ${taken}, which already exists; ${what} is not written over a file. Name a new one.`;
|
|
36100
36237
|
}
|
|
@@ -36144,6 +36281,7 @@ function captureDetail(payload) {
|
|
|
36144
36281
|
function patienceForFrames(frames, atLeast) {
|
|
36145
36282
|
return Math.max(atLeast, frames / SLOWEST_FRAME_RATE * 1000 + atLeast);
|
|
36146
36283
|
}
|
|
36284
|
+
var ALONGSIDE_NOTE = "Another call on this game was running while this one was: calls sent together run together and are answered as each finishes, so this answer is not from after the other. Send a call meant to come after another once that one has answered.";
|
|
36147
36285
|
function longerWaitNote(tool, op, waitedMs) {
|
|
36148
36286
|
const spec = TOOL_SPECS.find((one) => one.name === tool);
|
|
36149
36287
|
if (spec !== undefined && argumentsOf(spec, op).includes("timeoutMs")) {
|
|
@@ -36152,6 +36290,31 @@ function longerWaitNote(tool, op, waitedMs) {
|
|
|
36152
36290
|
const how = tool === "runtime_wait" && op === "frames" ? `the wait is counted from the number of frames, at ${SLOWEST_FRAME_RATE} a second on top of GDHARNESS_RUNTIME_TIMEOUT_MS: a game drawing frames more slowly than that is waited on in fewer frames at a time, and a game inside one long frame, such as a long _ready, answers no call however few frames are asked for` : "the wait is GDHARNESS_RUNTIME_TIMEOUT_MS, which is set in the environment the server is started with";
|
|
36153
36291
|
return `${tool} ${op} takes no timeoutMs: it was given ${waitedMs}ms, and ${how}.`;
|
|
36154
36292
|
}
|
|
36293
|
+
function keptGameRequest(tool, op, args) {
|
|
36294
|
+
if (tool === "debug_state") {
|
|
36295
|
+
if (op === "output") {
|
|
36296
|
+
return { op: "output" };
|
|
36297
|
+
}
|
|
36298
|
+
if (op === "variables") {
|
|
36299
|
+
const frame = args["frameId"];
|
|
36300
|
+
return { op: "variables", frame: typeof frame === "number" ? frame : 0 };
|
|
36301
|
+
}
|
|
36302
|
+
return { op: "stack" };
|
|
36303
|
+
}
|
|
36304
|
+
switch (op) {
|
|
36305
|
+
case "pause":
|
|
36306
|
+
return { op: "pause" };
|
|
36307
|
+
case "step_over":
|
|
36308
|
+
return { op: "resume", how: "next" };
|
|
36309
|
+
case "step_into":
|
|
36310
|
+
return { op: "resume", how: "step" };
|
|
36311
|
+
case "step_out":
|
|
36312
|
+
return { op: "resume", how: "out" };
|
|
36313
|
+
default:
|
|
36314
|
+
return { op: "resume", how: "continue" };
|
|
36315
|
+
}
|
|
36316
|
+
}
|
|
36317
|
+
var OWN_DEBUGGER = " Its debugger is the keeper holding it rather than the editor, so the debug_* tools answer for it all the same: breakpoints stop it, and debug_control pause breaks into it wherever it is, which the editor offers no way to do.";
|
|
36155
36318
|
function projectSpelling(projectPath, absolutePath) {
|
|
36156
36319
|
return `res://${relative4(realPathOr(projectPath), realPathOr(absolutePath)).replaceAll("\\", "/")}`;
|
|
36157
36320
|
}
|
|
@@ -36239,6 +36402,7 @@ function stopWaitMs() {
|
|
|
36239
36402
|
return waitFromEnv("GDHARNESS_STOP_WAIT_MS", 1e4);
|
|
36240
36403
|
}
|
|
36241
36404
|
var KEEPER_ANSWER_MS = 15000;
|
|
36405
|
+
var KEEPER_DEBUG_MS = PAUSE_MS + STEP_MS + 2 * READ_MS;
|
|
36242
36406
|
var EXIT_REPORTED_WITHIN_MS = 2000;
|
|
36243
36407
|
async function untilGone(pids, withinMs = stopWaitMs()) {
|
|
36244
36408
|
const gone = () => pids.every((pid) => !alive(pid));
|
|
@@ -36588,6 +36752,7 @@ function notRunNote(notRun, failFast) {
|
|
|
36588
36752
|
function exportAnswer(ending, asked, before, after) {
|
|
36589
36753
|
const { log } = ending;
|
|
36590
36754
|
const written = after !== null && after !== before;
|
|
36755
|
+
const notCopied = librariesNotCopied(log.all);
|
|
36591
36756
|
const verdict = {
|
|
36592
36757
|
exported: ending.exitCode === 0 && ending.failure === null && log.count("error") === 0 && written,
|
|
36593
36758
|
...asked,
|
|
@@ -36596,6 +36761,10 @@ function exportAnswer(ending, asked, before, after) {
|
|
|
36596
36761
|
failure: ending.failure ?? undefined,
|
|
36597
36762
|
errors: log.count("error"),
|
|
36598
36763
|
warnings: log.count("warning"),
|
|
36764
|
+
...notCopied.length === 0 ? {} : {
|
|
36765
|
+
librariesNotLoaded: notCopied,
|
|
36766
|
+
extensionNote: `The export could not load ${notCopied.join(" and ")}: on Windows an engine running as the editor, which an export is, loads a GDExtension from a ~ copy beside the library, and another engine on this project held that copy: it could not be moved aside before the export, or another engine made a new one in between. Export again once that engine has loaded or exited.`
|
|
36767
|
+
},
|
|
36599
36768
|
...foldedEntriesAnswer(log)
|
|
36600
36769
|
};
|
|
36601
36770
|
if (verdict.exported) {
|
|
@@ -36640,7 +36809,7 @@ var EDITOR_FREE_INSTEAD = {
|
|
|
36640
36809
|
scene_tree: "runtime_inspect tree reads the nodes of a game that is running, and needs no editor; scene_tree reads a scene file through the editor"
|
|
36641
36810
|
};
|
|
36642
36811
|
function editorAddonMissing(projectPath) {
|
|
36643
|
-
if (!
|
|
36812
|
+
if (!existsSync18(join24(projectPath, "project.godot"))) {
|
|
36644
36813
|
return null;
|
|
36645
36814
|
}
|
|
36646
36815
|
const report = inspectProject(projectPath);
|
|
@@ -36922,7 +37091,7 @@ function profileFrameFunctions(projectPath) {
|
|
|
36922
37091
|
let projectGodot = null;
|
|
36923
37092
|
if (projectPath !== null) {
|
|
36924
37093
|
try {
|
|
36925
|
-
projectGodot =
|
|
37094
|
+
projectGodot = readFileSync22(join24(projectPath, "project.godot"), "utf8");
|
|
36926
37095
|
} catch {}
|
|
36927
37096
|
}
|
|
36928
37097
|
return frameFunctionsFor(queuedMessagesOf(projectGodot));
|
|
@@ -36948,7 +37117,7 @@ function projectSourceOf(projectPath) {
|
|
|
36948
37117
|
let source = null;
|
|
36949
37118
|
if (inside.ok) {
|
|
36950
37119
|
try {
|
|
36951
|
-
source =
|
|
37120
|
+
source = readFileSync22(inside.absolutePath, "utf8");
|
|
36952
37121
|
} catch {}
|
|
36953
37122
|
}
|
|
36954
37123
|
read.set(script, source);
|
|
@@ -37100,7 +37269,7 @@ function foldedEntriesAnswer(log, floor = "warning") {
|
|
|
37100
37269
|
var ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd";
|
|
37101
37270
|
function gdUnitFailsOnScriptErrors(projectPath) {
|
|
37102
37271
|
try {
|
|
37103
|
-
return settingKeys(
|
|
37272
|
+
return settingKeys(readFileSync22(join24(projectPath, "project.godot"), "utf8")).get("gdunit4/report/godot/script_error") !== false;
|
|
37104
37273
|
} catch {
|
|
37105
37274
|
return true;
|
|
37106
37275
|
}
|
|
@@ -37115,13 +37284,13 @@ function uncaughtNote(uncaught) {
|
|
|
37115
37284
|
}
|
|
37116
37285
|
function elsewhereIn(projectPath, asked) {
|
|
37117
37286
|
const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
|
|
37118
|
-
if (wanted === "" ||
|
|
37287
|
+
if (wanted === "" || existsSync18(join24(projectPath, wanted))) {
|
|
37119
37288
|
return "";
|
|
37120
37289
|
}
|
|
37121
37290
|
const root = (wanted.split("/")[0] ?? "").toLowerCase();
|
|
37122
37291
|
let near = [];
|
|
37123
37292
|
try {
|
|
37124
|
-
near =
|
|
37293
|
+
near = readdirSync10(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()));
|
|
37125
37294
|
} catch {
|
|
37126
37295
|
return "";
|
|
37127
37296
|
}
|
|
@@ -37212,17 +37381,17 @@ function isDirectory(path) {
|
|
|
37212
37381
|
}
|
|
37213
37382
|
}
|
|
37214
37383
|
function scriptsUnder(directory, prefix) {
|
|
37215
|
-
if (
|
|
37384
|
+
if (existsSync18(join24(directory, ".gdignore"))) {
|
|
37216
37385
|
return [];
|
|
37217
37386
|
}
|
|
37218
37387
|
const found = [];
|
|
37219
|
-
for (const entry of
|
|
37388
|
+
for (const entry of readdirSync10(directory, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : 1)) {
|
|
37220
37389
|
if (entry.name.startsWith(".")) {
|
|
37221
37390
|
continue;
|
|
37222
37391
|
}
|
|
37223
37392
|
const path = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
37224
37393
|
if (entry.isDirectory()) {
|
|
37225
|
-
found.push(...scriptsUnder(
|
|
37394
|
+
found.push(...scriptsUnder(join24(directory, entry.name), path));
|
|
37226
37395
|
} else if (entry.isFile() && entry.name.endsWith(".gd")) {
|
|
37227
37396
|
found.push(path);
|
|
37228
37397
|
}
|
|
@@ -37230,7 +37399,7 @@ function scriptsUnder(directory, prefix) {
|
|
|
37230
37399
|
return found;
|
|
37231
37400
|
}
|
|
37232
37401
|
function hasMainScene(projectFile) {
|
|
37233
|
-
const scene = parseProjectGodot(
|
|
37402
|
+
const scene = parseProjectGodot(readFileSync22(projectFile, "utf8"))["application"]?.["run/main_scene"];
|
|
37234
37403
|
return typeof scene === "string" && scene !== "";
|
|
37235
37404
|
}
|
|
37236
37405
|
function runtimeAddonAnnouncesEditors(projectPath) {
|
|
@@ -37238,13 +37407,13 @@ function runtimeAddonAnnouncesEditors(projectPath) {
|
|
|
37238
37407
|
return false;
|
|
37239
37408
|
}
|
|
37240
37409
|
try {
|
|
37241
|
-
return
|
|
37410
|
+
return readFileSync22(join24(projectPath, RUNTIME_AUTOLOAD.path), "utf8").includes('"editor_pid"');
|
|
37242
37411
|
} catch {
|
|
37243
37412
|
return false;
|
|
37244
37413
|
}
|
|
37245
37414
|
}
|
|
37246
37415
|
function editorPlaysHeadless(projectFile) {
|
|
37247
|
-
const runArgs = parseProjectGodot(
|
|
37416
|
+
const runArgs = parseProjectGodot(readFileSync22(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
|
|
37248
37417
|
return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
|
|
37249
37418
|
}
|
|
37250
37419
|
function camelCased(params) {
|
|
@@ -37264,7 +37433,7 @@ var STATIC_LIST_HINT = { ttlMs: 5 * 60000, cacheScope: "private" };
|
|
|
37264
37433
|
class GodotServer {
|
|
37265
37434
|
stdio = null;
|
|
37266
37435
|
locator = new GodotLocator;
|
|
37267
|
-
operationsScript =
|
|
37436
|
+
operationsScript = join24(__dirname2, "godot", "operations", "godot_operations.gd");
|
|
37268
37437
|
godotBridge;
|
|
37269
37438
|
tools = buildToolDefinitions();
|
|
37270
37439
|
activeProcess = null;
|
|
@@ -37289,6 +37458,7 @@ class GodotServer {
|
|
|
37289
37458
|
launchedDropped = null;
|
|
37290
37459
|
closedHere = null;
|
|
37291
37460
|
inFlight = new Map;
|
|
37461
|
+
callsOnGames = new Set;
|
|
37292
37462
|
callsTaken = 0;
|
|
37293
37463
|
clientNoted = null;
|
|
37294
37464
|
parentCommand = null;
|
|
@@ -37520,14 +37690,14 @@ class GodotServer {
|
|
|
37520
37690
|
}
|
|
37521
37691
|
noteTheStart() {
|
|
37522
37692
|
const cwd = process.cwd();
|
|
37523
|
-
const mcpJson =
|
|
37693
|
+
const mcpJson = join24(cwd, ".mcp.json");
|
|
37524
37694
|
const announced = this.ownProject === null ? null : readAnnouncement(announcementPath(this.ownProject));
|
|
37525
37695
|
const predecessor = announced !== null && announced.pid !== process.pid && alive(announced.pid) ? announced.pid : null;
|
|
37526
37696
|
writeLifecycle("started", {
|
|
37527
37697
|
argv: process.argv.slice(1),
|
|
37528
37698
|
cwd,
|
|
37529
37699
|
project: this.ownProject,
|
|
37530
|
-
mcpJson:
|
|
37700
|
+
mcpJson: existsSync18(mcpJson) ? mcpJson : null
|
|
37531
37701
|
});
|
|
37532
37702
|
if (predecessor !== null) {
|
|
37533
37703
|
writeLifecycle("replaced", { project: this.ownProject, predecessor });
|
|
@@ -37668,7 +37838,7 @@ class GodotServer {
|
|
|
37668
37838
|
return checked.response;
|
|
37669
37839
|
}
|
|
37670
37840
|
const named = args["projectPath"];
|
|
37671
|
-
if (typeof named === "string" &&
|
|
37841
|
+
if (typeof named === "string" && existsSync18(join24(named, "project.godot"))) {
|
|
37672
37842
|
this.lastProjectPath = named;
|
|
37673
37843
|
}
|
|
37674
37844
|
this.updates.refresh();
|
|
@@ -38139,28 +38309,70 @@ class GodotServer {
|
|
|
38139
38309
|
return this.createErrorResponse(located.reason, PATH_SOLUTIONS);
|
|
38140
38310
|
}
|
|
38141
38311
|
const session = this.dapHoldingBreakpointsOf(project.value.path);
|
|
38142
|
-
const
|
|
38143
|
-
|
|
38144
|
-
|
|
38145
|
-
|
|
38146
|
-
|
|
38147
|
-
|
|
38312
|
+
const scriptPath = realPathOr(located.absolutePath);
|
|
38313
|
+
const line = readPositiveNumber(args, "line");
|
|
38314
|
+
if (line === undefined) {
|
|
38315
|
+
return this.createErrorResponse("line is required: the one-based line to stop on.");
|
|
38316
|
+
}
|
|
38317
|
+
const editor = this.godotBridge.isConnected();
|
|
38318
|
+
let answer;
|
|
38319
|
+
if (editor) {
|
|
38320
|
+
const answered = await this.handleDAP(op === "set" ? "dap_set_breakpoint" : "dap_remove_breakpoint", {
|
|
38321
|
+
...args,
|
|
38322
|
+
scriptPath
|
|
38323
|
+
});
|
|
38324
|
+
if (answered.isError === true) {
|
|
38325
|
+
return answered;
|
|
38326
|
+
}
|
|
38327
|
+
answer = asParams(JSON.parse(answered.content[0]?.text ?? "{}"));
|
|
38328
|
+
} else {
|
|
38329
|
+
if (op === "set") {
|
|
38330
|
+
session.holdBreakpoints([{ scriptPath, lines: [line] }]);
|
|
38331
|
+
} else {
|
|
38332
|
+
session.forgetBreakpoint(scriptPath, line);
|
|
38333
|
+
}
|
|
38334
|
+
answer = { [op === "set" ? "set" : "removed"]: true };
|
|
38148
38335
|
}
|
|
38149
38336
|
const held = session.breakpointsHeld();
|
|
38150
38337
|
writeBreakpointNote(project.value.path, held);
|
|
38338
|
+
const kept = this.keptRun();
|
|
38339
|
+
const told = kept === null ? null : await askTheKeepersDebugger(kept.keeper, {
|
|
38340
|
+
op: "breakpoint",
|
|
38341
|
+
script: projectSpelling(project.value.path, scriptPath),
|
|
38342
|
+
line,
|
|
38343
|
+
set: op === "set"
|
|
38344
|
+
}, KEEPER_DEBUG_MS);
|
|
38151
38345
|
return this.jsonTextResponse({
|
|
38152
|
-
...
|
|
38346
|
+
...answer,
|
|
38153
38347
|
held: this.breakpointsAsProjectSpells(project.value.path, held),
|
|
38154
|
-
|
|
38155
|
-
|
|
38156
|
-
|
|
38348
|
+
...editor ? {
|
|
38349
|
+
setInEditor: this.breakpointsAsProjectSpells(project.value.path, session.breakpointsInEditor())
|
|
38350
|
+
} : {},
|
|
38351
|
+
...told === null ? {} : ("unreached" in told) || typeof told["refused"] === "string" ? {
|
|
38352
|
+
runningGame: `The game this server started is running, and its debugger was not told: ${String("unreached" in told ? told.unreached : told["refused"])}. It takes the line when it is started again.`
|
|
38353
|
+
} : {
|
|
38354
|
+
runningGame: `Told to the running game (pid ${kept?.pid ?? "?"}), which this server started.`
|
|
38355
|
+
},
|
|
38356
|
+
note: editor ? "The editor keeps a breakpoint set this way for one play. Every breakpoint held here is sent again before each play editor_run starts, handed to every game this server starts itself, and kept in the project for the server after a reconnect." : "No editor is connected, so the set under held is kept here: every game this server starts is handed it as it starts, an editor that connects is sent it before its first play, and it is kept in the project for the server after a reconnect.",
|
|
38357
|
+
...editor ? this.breakpointsAtRisk() : {}
|
|
38157
38358
|
});
|
|
38158
38359
|
}
|
|
38159
38360
|
case "debug_control":
|
|
38160
38361
|
case "debug_state": {
|
|
38362
|
+
const kept = this.keptRun();
|
|
38363
|
+
if (kept !== null) {
|
|
38364
|
+
return await this.askTheKeptGame(kept.keeper, keptGameRequest(tool, op, args), kept.projectPath ?? "");
|
|
38365
|
+
}
|
|
38161
38366
|
if (tool === "debug_state" && op === "output") {
|
|
38162
38367
|
return await this.handleDAP("dap_get_output", args);
|
|
38163
38368
|
}
|
|
38369
|
+
const played = this.activeProcess;
|
|
38370
|
+
if ((op === "pause" || op === "step_out") && played?.throughEditor === true && !exited(played)) {
|
|
38371
|
+
return this.createErrorResponse(op === "pause" ? "A game the editor plays cannot be paused from here: Godot's debug adapter answers a pause by reporting the game stopped and leaving it running." : "A game the editor plays cannot be stepped out of a function from here: Godot's debug adapter implements no stepOut.", [
|
|
38372
|
+
"Set a breakpoint where it should stop, then step over or into from there",
|
|
38373
|
+
"A game this server starts, as editor_run start does with args, savesIn or env, has a debugger here that pauses and steps out"
|
|
38374
|
+
]);
|
|
38375
|
+
}
|
|
38164
38376
|
const held = await this.debuggedGame(tool === "debug_control" ? "move" : "read");
|
|
38165
38377
|
if (!held.ok) {
|
|
38166
38378
|
return held.response;
|
|
@@ -38184,8 +38396,8 @@ class GodotServer {
|
|
|
38184
38396
|
if (path === undefined) {
|
|
38185
38397
|
return { ok: false, response: this.createErrorResponse("projectPath is required.") };
|
|
38186
38398
|
}
|
|
38187
|
-
const file =
|
|
38188
|
-
if (!
|
|
38399
|
+
const file = join24(path, "project.godot");
|
|
38400
|
+
if (!existsSync18(file)) {
|
|
38189
38401
|
return {
|
|
38190
38402
|
ok: false,
|
|
38191
38403
|
response: this.createErrorResponse(`Not a Godot project: ${path}`, [
|
|
@@ -38231,7 +38443,7 @@ class GodotServer {
|
|
|
38231
38443
|
}
|
|
38232
38444
|
runEnvironment(args) {
|
|
38233
38445
|
const savesIn = readNonEmptyString(args, "savesIn");
|
|
38234
|
-
if (savesIn !== undefined && !
|
|
38446
|
+
if (savesIn !== undefined && !isAbsolute4(savesIn)) {
|
|
38235
38447
|
return {
|
|
38236
38448
|
ok: false,
|
|
38237
38449
|
response: this.createErrorResponse(`savesIn must be an absolute path, not "${savesIn}".`, [
|
|
@@ -38304,8 +38516,8 @@ class GodotServer {
|
|
|
38304
38516
|
if (!game.throughEditor) {
|
|
38305
38517
|
return {
|
|
38306
38518
|
ok: false,
|
|
38307
|
-
response: this.createErrorResponse("The running game
|
|
38308
|
-
"editor_run start
|
|
38519
|
+
response: this.createErrorResponse("The running game has no debugger: it was started with none, or by a keeper that could not take questions, so breakpoints never hit and there is no stack to read.", [
|
|
38520
|
+
"editor_run start starts it again, and the keeper holding a game this server starts is its debugger",
|
|
38309
38521
|
"editor_status says whether an editor has reached this server"
|
|
38310
38522
|
])
|
|
38311
38523
|
};
|
|
@@ -38815,11 +39027,11 @@ class GodotServer {
|
|
|
38815
39027
|
if (!engine.ok) {
|
|
38816
39028
|
return engine.response;
|
|
38817
39029
|
}
|
|
38818
|
-
const application = parseProjectGodot(
|
|
39030
|
+
const application = parseProjectGodot(readFileSync22(project.value.file, "utf8"))["application"] ?? {};
|
|
38819
39031
|
const name = application["config/name"];
|
|
38820
39032
|
const mainScene = application["run/main_scene"];
|
|
38821
39033
|
const info = {
|
|
38822
|
-
name: typeof name === "string" && name !== "" ? name :
|
|
39034
|
+
name: typeof name === "string" && name !== "" ? name : basename6(project.value.path),
|
|
38823
39035
|
path: project.value.path,
|
|
38824
39036
|
mainScene: typeof mainScene === "string" && mainScene !== "" ? mainScene : null,
|
|
38825
39037
|
godotVersion: await this.godotVersion(engine.value),
|
|
@@ -38871,18 +39083,18 @@ class GodotServer {
|
|
|
38871
39083
|
if (!engine.ok) {
|
|
38872
39084
|
return engine.response;
|
|
38873
39085
|
}
|
|
38874
|
-
const folder =
|
|
39086
|
+
const folder = dirname17(output.absolutePath);
|
|
38875
39087
|
try {
|
|
38876
39088
|
mkdirSync14(folder, { recursive: true });
|
|
38877
39089
|
} catch (error) {
|
|
38878
|
-
return this.createErrorResponse(`Cannot create ${
|
|
39090
|
+
return this.createErrorResponse(`Cannot create ${dirname17(output.relativePath)} to export into: ${errorMessage(error)}`);
|
|
38879
39091
|
}
|
|
38880
39092
|
const debug = readBoolean(args, "debug") ?? false;
|
|
38881
39093
|
const exportLogs = scratchDirectory("export");
|
|
38882
39094
|
const exportArgs = [
|
|
38883
39095
|
"--headless",
|
|
38884
39096
|
"--log-file",
|
|
38885
|
-
|
|
39097
|
+
join24(exportLogs, "engine.log"),
|
|
38886
39098
|
"--path",
|
|
38887
39099
|
project.value.path,
|
|
38888
39100
|
debug ? "--export-debug" : "--export-release",
|
|
@@ -38890,8 +39102,12 @@ class GodotServer {
|
|
|
38890
39102
|
output.absolutePath
|
|
38891
39103
|
];
|
|
38892
39104
|
this.logDebug(`Exporting: ${engine.value} ${exportArgs.join(" ")}`);
|
|
38893
|
-
const writtenAt = () =>
|
|
39105
|
+
const writtenAt = () => existsSync18(output.absolutePath) ? statSync9(output.absolutePath).mtimeMs : null;
|
|
38894
39106
|
const before = writtenAt();
|
|
39107
|
+
const freed = freeLibraryCopies(project.value.path);
|
|
39108
|
+
if (freed.setAside.length > 0 || freed.swept.length > 0) {
|
|
39109
|
+
this.logDebug(`Before the export: set aside ${freed.setAside.join(", ")}; swept ${freed.swept.join(", ")}`);
|
|
39110
|
+
}
|
|
38895
39111
|
let ending;
|
|
38896
39112
|
try {
|
|
38897
39113
|
ending = await runEngine(engine.value, exportArgs, { timeout: { ms: 300000, said: "five minutes" } });
|
|
@@ -38954,10 +39170,10 @@ class GodotServer {
|
|
|
38954
39170
|
if (movie !== null) {
|
|
38955
39171
|
return this.createErrorResponse(movie);
|
|
38956
39172
|
}
|
|
38957
|
-
const missing = paths.value.filter((path) => !
|
|
39173
|
+
const missing = paths.value.filter((path) => !existsSync18(join24(project.value.path, path.slice("res://".length))));
|
|
38958
39174
|
const asked = paths.value.filter((path) => !missing.includes(path));
|
|
38959
39175
|
const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
|
|
38960
|
-
const noRunner = !
|
|
39176
|
+
const noRunner = !existsSync18(join24(project.value.path, runner));
|
|
38961
39177
|
if (asked.length === 0) {
|
|
38962
39178
|
const verdict = `nothing at ${missing.join(" and ")}`;
|
|
38963
39179
|
const andNoRunner = noRunner ? `, and gdUnit4 is not installed in this project: no ${runner}` : "";
|
|
@@ -39048,21 +39264,21 @@ class GodotServer {
|
|
|
39048
39264
|
await untilGone([run.process.pid]);
|
|
39049
39265
|
}
|
|
39050
39266
|
await run.settled;
|
|
39051
|
-
const reportsDir =
|
|
39267
|
+
const reportsDir = join24(project.value.path, ".godot", ...ours.split("/"));
|
|
39052
39268
|
let report = null;
|
|
39053
39269
|
let reportProblem = null;
|
|
39054
39270
|
try {
|
|
39055
|
-
const written =
|
|
39271
|
+
const written = existsSync18(reportsDir) ? readdirSync10(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
|
|
39056
39272
|
const newest = written.at(-1);
|
|
39057
39273
|
if (newest !== undefined) {
|
|
39058
|
-
report = parseJUnit(
|
|
39274
|
+
report = parseJUnit(readFileSync22(join24(reportsDir, newest, "results.xml"), "utf8"));
|
|
39059
39275
|
}
|
|
39060
39276
|
} catch (error) {
|
|
39061
39277
|
reportProblem = errorMessage(error);
|
|
39062
39278
|
} finally {
|
|
39063
39279
|
discard(reportsDir, userData);
|
|
39064
39280
|
try {
|
|
39065
|
-
rmdirSync2(
|
|
39281
|
+
rmdirSync2(dirname17(reportsDir));
|
|
39066
39282
|
} catch {}
|
|
39067
39283
|
}
|
|
39068
39284
|
const engineEntries = foldedRepeats(run.log.select({
|
|
@@ -39322,11 +39538,11 @@ class GodotServer {
|
|
|
39322
39538
|
const cached = cachedClasses(projectPath);
|
|
39323
39539
|
const sourceOf = (resourcePath) => {
|
|
39324
39540
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
39325
|
-
if (!contained.ok || !
|
|
39541
|
+
if (!contained.ok || !existsSync18(contained.absolutePath)) {
|
|
39326
39542
|
return null;
|
|
39327
39543
|
}
|
|
39328
39544
|
try {
|
|
39329
|
-
return
|
|
39545
|
+
return readFileSync22(contained.absolutePath, "utf8");
|
|
39330
39546
|
} catch {
|
|
39331
39547
|
return null;
|
|
39332
39548
|
}
|
|
@@ -39391,11 +39607,11 @@ class GodotServer {
|
|
|
39391
39607
|
}
|
|
39392
39608
|
sourceOfClass(projectPath, resourcePath) {
|
|
39393
39609
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
39394
|
-
if (!contained.ok || !
|
|
39610
|
+
if (!contained.ok || !existsSync18(contained.absolutePath)) {
|
|
39395
39611
|
return null;
|
|
39396
39612
|
}
|
|
39397
39613
|
try {
|
|
39398
|
-
return
|
|
39614
|
+
return readFileSync22(contained.absolutePath, "utf8");
|
|
39399
39615
|
} catch {
|
|
39400
39616
|
return null;
|
|
39401
39617
|
}
|
|
@@ -39516,6 +39732,38 @@ class GodotServer {
|
|
|
39516
39732
|
breakpointsAtRisk: status.syncsBreakpoints === false ? "This editor clears every breakpoint in its script editor whenever a debug session opens on its adapter, which this server does for its first breakpoint or stack read: network/debug_adapter/sync_breakpoints is off. The installed addon turns it on as it loads, so the editor is running an older one; editor_launch restart puts the installed addon in, or turn the setting on in Editor Settings > Network > Debug Adapter." : "Whether this editor keeps its breakpoints when a debug session opens cannot be told: the addon it is running predates the report. Godot clears them unless network/debug_adapter/sync_breakpoints is on, which the installed addon turns on as it loads; editor_launch restart puts the installed addon in."
|
|
39517
39733
|
};
|
|
39518
39734
|
}
|
|
39735
|
+
breakpointsForAGame(projectPath) {
|
|
39736
|
+
return this.dapHoldingBreakpointsOf(projectPath).breakpointsHeld().flatMap((one) => one.lines.map((line) => `${projectSpelling(projectPath, one.scriptPath)}:${line}`));
|
|
39737
|
+
}
|
|
39738
|
+
keptRun() {
|
|
39739
|
+
const game = this.activeProcess;
|
|
39740
|
+
if (game === null || game.throughEditor || exited(game) || game.keeper === undefined) {
|
|
39741
|
+
return null;
|
|
39742
|
+
}
|
|
39743
|
+
return { ...game, keeper: game.keeper };
|
|
39744
|
+
}
|
|
39745
|
+
async askTheKeptGame(keeper, request, projectPath) {
|
|
39746
|
+
const answered = await askTheKeepersDebugger(keeper, request, KEEPER_DEBUG_MS);
|
|
39747
|
+
if ("unreached" in answered) {
|
|
39748
|
+
return this.createErrorResponse(`The debugger of the running game did not answer: ${String(answered.unreached)}.`, [
|
|
39749
|
+
"editor_run start starts the game again, with a debugger",
|
|
39750
|
+
"editor_status says whether it is still running"
|
|
39751
|
+
]);
|
|
39752
|
+
}
|
|
39753
|
+
if (typeof answered["refused"] === "string") {
|
|
39754
|
+
return this.createErrorResponse(answered["refused"]);
|
|
39755
|
+
}
|
|
39756
|
+
const stack = answered["stack"];
|
|
39757
|
+
return this.jsonTextResponse({
|
|
39758
|
+
...answered,
|
|
39759
|
+
...Array.isArray(stack) ? {
|
|
39760
|
+
stack: stack.map((frame) => ({
|
|
39761
|
+
...frame,
|
|
39762
|
+
...projectPath !== "" && typeof frame["script"] === "string" && frame["script"].startsWith("res://") ? { path: join24(projectPath, frame["script"].slice("res://".length)) } : {}
|
|
39763
|
+
}))
|
|
39764
|
+
} : {}
|
|
39765
|
+
});
|
|
39766
|
+
}
|
|
39519
39767
|
breakpointsAsProjectSpells(projectPath, held) {
|
|
39520
39768
|
return held.map((one) => ({
|
|
39521
39769
|
scriptPath: projectSpelling(projectPath, one.scriptPath),
|
|
@@ -39781,7 +40029,7 @@ class GodotServer {
|
|
|
39781
40029
|
return null;
|
|
39782
40030
|
}
|
|
39783
40031
|
try {
|
|
39784
|
-
return settingKeys(
|
|
40032
|
+
return settingKeys(readFileSync22(join24(projectPath, "project.godot"), "utf8"));
|
|
39785
40033
|
} catch {
|
|
39786
40034
|
return null;
|
|
39787
40035
|
}
|
|
@@ -40046,6 +40294,7 @@ class GodotServer {
|
|
|
40046
40294
|
}
|
|
40047
40295
|
async openAnEditor(engine, projectPath, ports, hidden) {
|
|
40048
40296
|
clearEditorLog(projectPath);
|
|
40297
|
+
freeLibraryCopies(projectPath);
|
|
40049
40298
|
const launched = await launchOutsideTheTree({
|
|
40050
40299
|
command: engine,
|
|
40051
40300
|
args: editorArguments(projectPath, ports, editorLogPath(projectPath), hidden),
|
|
@@ -40177,9 +40426,10 @@ class GodotServer {
|
|
|
40177
40426
|
started.announcedBefore = alreadyPlaying;
|
|
40178
40427
|
started.bootScene = runtimeWaitMs.scene;
|
|
40179
40428
|
this.activeProcess = started;
|
|
40180
|
-
const spawnedInstead = this.godotBridge.isConnected() && editorWouldPlay && (given.value.length > 0 || flags.value.length > 0) ?
|
|
40429
|
+
const spawnedInstead = this.godotBridge.isConnected() && editorWouldPlay && (given.value.length > 0 || flags.value.length > 0) ? ` The editor cannot be handed arguments for a game it plays, so this one was started here.${OWN_DEBUGGER}` : this.godotBridge.isConnected() && editorWouldPlay && needsItsOwnProcess(asked.value) ? ` The editor plays a game with its own environment, so a run given one was started here.${OWN_DEBUGGER}` : this.godotBridge.isConnected() && editorWouldPlay && silent ? " The editor cannot play a game silent, which takes an engine flag, so this one was " + `started here${args["silent"] === true ? "" : ", silent because GDHARNESS_SILENT says so"}, and silent: false plays it through the editor.${OWN_DEBUGGER}` : "";
|
|
40181
40430
|
const endedForThis = endedToStartThis(ended);
|
|
40182
40431
|
const stranded = wereHereFirst.filter((one) => one.pid !== ended?.pid && alive(one.pid));
|
|
40432
|
+
const handed = this.dapHoldingBreakpointsOf(project.value.path).breakpointsHeld();
|
|
40183
40433
|
const alsoRunning = leftRunningNote(stranded);
|
|
40184
40434
|
return this.jsonTextResponse({
|
|
40185
40435
|
started: true,
|
|
@@ -40189,6 +40439,7 @@ class GodotServer {
|
|
|
40189
40439
|
arguments: cmdArgs,
|
|
40190
40440
|
argsNote: engineFlagsNote(given.value),
|
|
40191
40441
|
transcript: started.transcript,
|
|
40442
|
+
breakpoints: started.keeper === undefined || handed.length === 0 ? undefined : this.breakpointsAsProjectSpells(project.value.path, handed),
|
|
40192
40443
|
profile: profileOnStart(profiled, "gdharness", runtimeAddonRegistered(project.value.path)),
|
|
40193
40444
|
refreshedClasses: refreshed.value,
|
|
40194
40445
|
...scanWaitAnswer(scanned),
|
|
@@ -40197,12 +40448,12 @@ class GodotServer {
|
|
|
40197
40448
|
previousRunLeft: previousRunLeft(ended),
|
|
40198
40449
|
...previousRunHow(ended),
|
|
40199
40450
|
runtime: await this.runtimeUp(project.value.path, alreadyPlaying, runtimeWaitMs, given.value.length > 0),
|
|
40200
|
-
message: `Use editor_output for what it prints and editor_run stop to end it.${spawnedInstead}${endedForThis}${alsoRunning}`
|
|
40451
|
+
message: `Use editor_output for what it prints and editor_run stop to end it.${started.keeper !== undefined && handed.length > 0 ? " It stops at the lines under breakpoints, and debug_state stack reads where it is held." : ""}${spawnedInstead}${endedForThis}${alsoRunning}`
|
|
40201
40452
|
});
|
|
40202
40453
|
}
|
|
40203
40454
|
async runtimeUp(projectPath, before, budget, withArgs) {
|
|
40204
40455
|
const budgetMs = budget.ms;
|
|
40205
|
-
if (!
|
|
40456
|
+
if (!existsSync18(join24(projectPath, RUNTIME_AUTOLOAD.path))) {
|
|
40206
40457
|
return runtimeVerdict(null, {
|
|
40207
40458
|
addon: false,
|
|
40208
40459
|
budgetMs,
|
|
@@ -40481,7 +40732,7 @@ class GodotServer {
|
|
|
40481
40732
|
return 0;
|
|
40482
40733
|
}
|
|
40483
40734
|
try {
|
|
40484
|
-
return reportAlreadyInTranscript(
|
|
40735
|
+
return reportAlreadyInTranscript(readFileSync22(game.transcript), readFileSync22(report));
|
|
40485
40736
|
} catch (error) {
|
|
40486
40737
|
this.logDebug(`Could not compare a picked-up run's report with its transcript: ${errorMessage(error)}`);
|
|
40487
40738
|
return 0;
|
|
@@ -40724,7 +40975,8 @@ class GodotServer {
|
|
|
40724
40975
|
startedAt,
|
|
40725
40976
|
projectPath,
|
|
40726
40977
|
...this.servedBy(),
|
|
40727
|
-
...profile === undefined ? {} : { profile }
|
|
40978
|
+
...profile === undefined ? {} : { profile },
|
|
40979
|
+
breakpoints: this.breakpointsForAGame(projectPath)
|
|
40728
40980
|
},
|
|
40729
40981
|
...desktop === undefined ? {} : { desktop }
|
|
40730
40982
|
});
|
|
@@ -41160,7 +41412,7 @@ class GodotServer {
|
|
|
41160
41412
|
}
|
|
41161
41413
|
const going = await this.runStillGoing(run);
|
|
41162
41414
|
const projectPath = run.projectPath;
|
|
41163
|
-
const projectGone = projectPath !== null && !
|
|
41415
|
+
const projectGone = projectPath !== null && !existsSync18(join24(projectPath, "project.godot"));
|
|
41164
41416
|
if (projectGone && callersName !== undefined) {
|
|
41165
41417
|
return this.createErrorResponse(`callersOf reads the project's scripts, and the run's project at ${projectPath} has no project.godot any more.`, ["editor_run profile without callersOf answers with the profile itself"]);
|
|
41166
41418
|
}
|
|
@@ -41448,7 +41700,7 @@ class GodotServer {
|
|
|
41448
41700
|
notes.push(endedWithoutACode({
|
|
41449
41701
|
throughEditor: run.throughEditor,
|
|
41450
41702
|
announced: announced !== undefined || run.announcedPid !== undefined,
|
|
41451
|
-
couldAnnounce: run.projectPath !== null &&
|
|
41703
|
+
couldAnnounce: run.projectPath !== null && existsSync18(join24(run.projectPath, RUNTIME_AUTOLOAD.path)),
|
|
41452
41704
|
endedHere: typeof run.endedHere === "string",
|
|
41453
41705
|
pickedUp: run.pickedUp === true
|
|
41454
41706
|
}));
|
|
@@ -42013,7 +42265,7 @@ class GodotServer {
|
|
|
42013
42265
|
if (!scriptPath.ok) {
|
|
42014
42266
|
return this.createErrorResponse(scriptPath.reason);
|
|
42015
42267
|
}
|
|
42016
|
-
if (!
|
|
42268
|
+
if (!existsSync18(scriptPath.absolutePath)) {
|
|
42017
42269
|
return this.createErrorResponse(`Script file does not exist: ${scriptPath.absolutePath}`);
|
|
42018
42270
|
}
|
|
42019
42271
|
const asked = readNonEmptyString(args, "newScriptPath");
|
|
@@ -42341,7 +42593,7 @@ class GodotServer {
|
|
|
42341
42593
|
async ownRunOnItsWay(projectPath, budgetMs) {
|
|
42342
42594
|
const run = await this.currentRun();
|
|
42343
42595
|
const project = projectPath ?? run?.projectPath ?? null;
|
|
42344
|
-
if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !
|
|
42596
|
+
if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync18(join24(project, RUNTIME_AUTOLOAD.path)) || !await this.runStillGoing(run)) {
|
|
42345
42597
|
return null;
|
|
42346
42598
|
}
|
|
42347
42599
|
const already = runtimesAnnounced().running;
|
|
@@ -42426,12 +42678,25 @@ class GodotServer {
|
|
|
42426
42678
|
}
|
|
42427
42679
|
}
|
|
42428
42680
|
const screenshotDir = expectsScreenshot ? scratchDirectory("runtime-screenshot") : null;
|
|
42429
|
-
const screenshotPath = screenshotDir ?
|
|
42681
|
+
const screenshotPath = screenshotDir ? join24(screenshotDir, "capture.png") : null;
|
|
42430
42682
|
const staleNote = this.runtimeStaleNote(choice.endpoint);
|
|
42431
42683
|
const stale = staleNote === undefined ? {} : { addonIsStale: true, staleNote };
|
|
42432
42684
|
const refused = (message) => this.createErrorResponse(staleNote === undefined ? message : `${message}
|
|
42433
42685
|
|
|
42434
42686
|
${staleNote}`);
|
|
42687
|
+
const onGame = {
|
|
42688
|
+
pid: choice.endpoint.pid,
|
|
42689
|
+
what: `${asked.tool} ${asked.op}`,
|
|
42690
|
+
alongside: new Set
|
|
42691
|
+
};
|
|
42692
|
+
for (const other of this.callsOnGames) {
|
|
42693
|
+
if (other.pid === onGame.pid) {
|
|
42694
|
+
other.alongside.add(onGame.what);
|
|
42695
|
+
onGame.alongside.add(other.what);
|
|
42696
|
+
}
|
|
42697
|
+
}
|
|
42698
|
+
this.callsOnGames.add(onGame);
|
|
42699
|
+
const alongside = () => onGame.alongside.size === 0 ? {} : { ranAlongside: [...onGame.alongside], alongsideNote: ALONGSIDE_NOTE };
|
|
42435
42700
|
try {
|
|
42436
42701
|
const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs, !expectsScreenshot);
|
|
42437
42702
|
if (!reply.ok && reply.requestId !== undefined) {
|
|
@@ -42442,7 +42707,8 @@ ${staleNote}`);
|
|
|
42442
42707
|
waitedMs: timeoutMs,
|
|
42443
42708
|
note: `${reply.message} ${longerWaitNote(asked.tool, asked.op, timeoutMs)}`,
|
|
42444
42709
|
...answeredBy,
|
|
42445
|
-
...stale
|
|
42710
|
+
...stale,
|
|
42711
|
+
...alongside()
|
|
42446
42712
|
});
|
|
42447
42713
|
}
|
|
42448
42714
|
if (!reply.ok) {
|
|
@@ -42450,7 +42716,7 @@ ${staleNote}`);
|
|
|
42450
42716
|
}
|
|
42451
42717
|
const { id: _id, ...payload } = reply.payload;
|
|
42452
42718
|
if (!expectsScreenshot) {
|
|
42453
|
-
return this.jsonTextResponse({ ...payload, ...answeredBy, ...stale });
|
|
42719
|
+
return this.jsonTextResponse({ ...payload, ...answeredBy, ...stale, ...alongside() });
|
|
42454
42720
|
}
|
|
42455
42721
|
const returnedPath = readString(payload, "path");
|
|
42456
42722
|
if (readString(payload, "type") !== "screenshot_file" || !returnedPath || !screenshotPath) {
|
|
@@ -42467,18 +42733,22 @@ ${staleNote}`);
|
|
|
42467
42733
|
return this.createErrorResponse(`The capture was taken and could not be saved at ${saveTo}: ${errorMessage(error)}`);
|
|
42468
42734
|
}
|
|
42469
42735
|
}
|
|
42736
|
+
const ranWith = [...onGame.alongside];
|
|
42470
42737
|
return {
|
|
42471
42738
|
content: [
|
|
42472
42739
|
{
|
|
42473
42740
|
type: "text",
|
|
42474
|
-
text: `Screenshot captured: ${dimensions}${chosenAmongSeveral ? ` from pid ${own}, the game this server holds` : ""}${captureDetail(payload)}${saveTo === null ? "" : `. Saved to ${saveTo}`}${
|
|
42741
|
+
text: `Screenshot captured: ${dimensions}${chosenAmongSeveral ? ` from pid ${own}, the game this server holds` : ""}${captureDetail(payload)}${saveTo === null ? "" : `. Saved to ${saveTo}`}${ranWith.length === 0 ? "" : `
|
|
42742
|
+
|
|
42743
|
+
Ran alongside ${ranWith.join(", ")}. ${ALONGSIDE_NOTE}`}${staleNote === undefined ? "" : `
|
|
42475
42744
|
|
|
42476
42745
|
${staleNote}`}`
|
|
42477
42746
|
},
|
|
42478
|
-
{ type: "image", data:
|
|
42747
|
+
{ type: "image", data: readFileSync22(screenshotPath).toString("base64"), mimeType: "image/png" }
|
|
42479
42748
|
]
|
|
42480
42749
|
};
|
|
42481
42750
|
} finally {
|
|
42751
|
+
this.callsOnGames.delete(onGame);
|
|
42482
42752
|
discard(screenshotDir);
|
|
42483
42753
|
}
|
|
42484
42754
|
}
|