gdharness 1.2.28 → 1.2.30
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 +395 -169
- package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +259 -31
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +168 -98
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +33 -7
- package/build/godot/addons/gdharness_runtime/runtime_pad.gd +10 -3
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +89 -15
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +3 -3
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +142 -0
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +1 -13
- package/build/index.js +384 -169
- package/build/keeper.js +20 -17
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -68,8 +68,34 @@ var init_errors = __esm(() => {
|
|
|
68
68
|
};
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
+
// src/spawn.ts
|
|
72
|
+
import {
|
|
73
|
+
execFile,
|
|
74
|
+
execFileSync,
|
|
75
|
+
spawn,
|
|
76
|
+
spawnSync
|
|
77
|
+
} from "node:child_process";
|
|
78
|
+
function spawnHidden(command, args, options = {}) {
|
|
79
|
+
return spawn(command, [...args], { ...options, windowsHide: true });
|
|
80
|
+
}
|
|
81
|
+
function runHidden(command, args, options = {}) {
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
execFile(command, [...args], { ...options, encoding: "utf8", windowsHide: true }, (error, stdout, stderr) => {
|
|
84
|
+
if (error !== null) {
|
|
85
|
+
Object.assign(error, { stdout, stderr });
|
|
86
|
+
reject(error);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
resolve({ stdout, stderr });
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function spawnSyncHidden(command, args, options = {}) {
|
|
94
|
+
return spawnSync(command, [...args], { ...options, windowsHide: true });
|
|
95
|
+
}
|
|
96
|
+
var init_spawn = () => {};
|
|
97
|
+
|
|
71
98
|
// src/windows-ask.ts
|
|
72
|
-
import { spawn } from "node:child_process";
|
|
73
99
|
import { tmpdir } from "node:os";
|
|
74
100
|
import process2 from "node:process";
|
|
75
101
|
function holding(child, wanted) {
|
|
@@ -88,14 +114,14 @@ function running() {
|
|
|
88
114
|
if (helper?.exitCode === null && helper.signalCode === null) {
|
|
89
115
|
return helper;
|
|
90
116
|
}
|
|
91
|
-
const child =
|
|
117
|
+
const child = spawnHidden("powershell.exe", [
|
|
92
118
|
"-NoProfile",
|
|
93
119
|
"-NonInteractive",
|
|
94
120
|
"-ExecutionPolicy",
|
|
95
121
|
"Bypass",
|
|
96
122
|
"-EncodedCommand",
|
|
97
123
|
Buffer.from(SCRIPT, "utf16le").toString("base64")
|
|
98
|
-
], {
|
|
124
|
+
], { cwd: tmpdir() });
|
|
99
125
|
buffered = "";
|
|
100
126
|
child.stdout.setEncoding("utf8");
|
|
101
127
|
child.stdout.on("data", (chunk) => {
|
|
@@ -188,6 +214,7 @@ function askWindows(script, withinMs) {
|
|
|
188
214
|
}
|
|
189
215
|
var SCRIPT, WindowsAskFailure, helper = null, buffered = "", next = 1, out = null, queued, held = false;
|
|
190
216
|
var init_windows_ask = __esm(() => {
|
|
217
|
+
init_spawn();
|
|
191
218
|
SCRIPT = [
|
|
192
219
|
"[Console]::OutputEncoding = [Text.Encoding]::UTF8",
|
|
193
220
|
"$in = [Console]::In",
|
|
@@ -215,9 +242,7 @@ var init_windows_ask = __esm(() => {
|
|
|
215
242
|
});
|
|
216
243
|
|
|
217
244
|
// src/process-children.ts
|
|
218
|
-
import { execFile } from "node:child_process";
|
|
219
245
|
import { basename } from "node:path";
|
|
220
|
-
import { promisify } from "node:util";
|
|
221
246
|
async function startTimesOf(pids) {
|
|
222
247
|
const began = new Map;
|
|
223
248
|
if (pids.length === 0) {
|
|
@@ -252,10 +277,9 @@ async function startTimesOf(pids) {
|
|
|
252
277
|
return began;
|
|
253
278
|
}
|
|
254
279
|
function printedBy(command, args, env) {
|
|
255
|
-
return
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
});
|
|
280
|
+
return runHidden(command, args, { timeout: ASK_TIMEOUT_MS, env }).then(({ stdout }) => stdout, (error) => {
|
|
281
|
+
const printed = error.stdout;
|
|
282
|
+
return typeof printed === "string" ? printed : "";
|
|
259
283
|
});
|
|
260
284
|
}
|
|
261
285
|
async function processTree() {
|
|
@@ -321,10 +345,18 @@ function linked(tree, child, parent) {
|
|
|
321
345
|
}
|
|
322
346
|
return from.startedAt >= to.startedAt;
|
|
323
347
|
}
|
|
348
|
+
function isConsoleHost(listed) {
|
|
349
|
+
const executable = commandWords(listed.command)[0];
|
|
350
|
+
if (executable === undefined) {
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
const host = `${process.env["SystemRoot"] ?? "C:\\Windows"}\\System32\\conhost.exe`;
|
|
354
|
+
return executable.replace(/^\\\?\?\\/, "").replaceAll("/", "\\").toLowerCase() === host.toLowerCase();
|
|
355
|
+
}
|
|
324
356
|
function childrenIn(tree, pid) {
|
|
325
357
|
const children = [];
|
|
326
|
-
for (const child of tree
|
|
327
|
-
if (linked(tree, child, pid)) {
|
|
358
|
+
for (const [child, listed] of tree) {
|
|
359
|
+
if (linked(tree, child, pid) && !isConsoleHost(listed)) {
|
|
328
360
|
children.push(child);
|
|
329
361
|
}
|
|
330
362
|
}
|
|
@@ -426,16 +458,16 @@ function listWindows() {
|
|
|
426
458
|
return askWindows('Get-CimInstance Win32_Process -Property ProcessId,ParentProcessId,CreationDate,CommandLine | ForEach-Object { $began = 0; if ($_.CreationDate) { $began = ([DateTimeOffset]$_.CreationDate).ToUnixTimeMilliseconds() }; "$($_.ProcessId) $($_.ParentProcessId) $began $($_.CommandLine)" }', ASK_TIMEOUT_MS);
|
|
427
459
|
}
|
|
428
460
|
async function askPosix() {
|
|
429
|
-
const { stdout } = await
|
|
461
|
+
const { stdout } = await runHidden("ps", ["-e", "-o", "pid=,ppid=,args="], {
|
|
430
462
|
timeout: ASK_TIMEOUT_MS,
|
|
431
463
|
maxBuffer: 16 * 1024 * 1024
|
|
432
464
|
});
|
|
433
465
|
return stdout;
|
|
434
466
|
}
|
|
435
|
-
var
|
|
467
|
+
var ASK_TIMEOUT_MS = 15000, lastListingFailure, ONE_SHOT_OPTIONS;
|
|
436
468
|
var init_process_children = __esm(() => {
|
|
469
|
+
init_spawn();
|
|
437
470
|
init_windows_ask();
|
|
438
|
-
run = promisify(execFile);
|
|
439
471
|
ONE_SHOT_OPTIONS = [
|
|
440
472
|
"--import",
|
|
441
473
|
"--quit",
|
|
@@ -674,7 +706,10 @@ function engineFlagsAmongGameArgs(args) {
|
|
|
674
706
|
return args.filter((one) => Object.hasOwn(ENGINE_FLAGS, one) || Object.hasOwn(SET_BY_ARGUMENT, one));
|
|
675
707
|
}
|
|
676
708
|
function runArguments(options) {
|
|
677
|
-
const args =
|
|
709
|
+
const args = [
|
|
710
|
+
...options.headless ? ["--headless"] : [],
|
|
711
|
+
...options.projectPath === null ? [] : ["--path", options.projectPath]
|
|
712
|
+
];
|
|
678
713
|
if (options.silent === true && !options.headless) {
|
|
679
714
|
args.push("--audio-driver", "Dummy");
|
|
680
715
|
}
|
|
@@ -778,10 +813,8 @@ var init_launch = __esm(() => {
|
|
|
778
813
|
});
|
|
779
814
|
|
|
780
815
|
// src/godot-path.ts
|
|
781
|
-
import { execFile as execFile2 } from "node:child_process";
|
|
782
816
|
import { existsSync as existsSync2 } from "node:fs";
|
|
783
817
|
import { normalize } from "node:path";
|
|
784
|
-
import { promisify as promisify2 } from "node:util";
|
|
785
818
|
|
|
786
819
|
class GodotLocator {
|
|
787
820
|
answered = new Map;
|
|
@@ -838,7 +871,7 @@ class GodotLocator {
|
|
|
838
871
|
reason = "does not exist";
|
|
839
872
|
} else {
|
|
840
873
|
try {
|
|
841
|
-
await
|
|
874
|
+
await runHidden(path, ["--version"]);
|
|
842
875
|
reason = null;
|
|
843
876
|
} catch (error) {
|
|
844
877
|
reason = `does not answer --version: ${errorMessage(error)}`;
|
|
@@ -848,12 +881,11 @@ class GodotLocator {
|
|
|
848
881
|
return reason;
|
|
849
882
|
}
|
|
850
883
|
}
|
|
851
|
-
var run2;
|
|
852
884
|
var init_godot_path = __esm(() => {
|
|
853
885
|
init_detection();
|
|
854
886
|
init_errors();
|
|
855
887
|
init_launch();
|
|
856
|
-
|
|
888
|
+
init_spawn();
|
|
857
889
|
});
|
|
858
890
|
|
|
859
891
|
// node_modules/yaml/dist/nodes/identity.js
|
|
@@ -7799,7 +7831,7 @@ var require_public_api = __commonJS(function(exports) {
|
|
|
7799
7831
|
});
|
|
7800
7832
|
|
|
7801
7833
|
// src/paths.ts
|
|
7802
|
-
import { realpathSync } from "node:fs";
|
|
7834
|
+
import { realpathSync, statSync as statSync2 } from "node:fs";
|
|
7803
7835
|
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
7804
7836
|
function realPathOr(path) {
|
|
7805
7837
|
try {
|
|
@@ -7860,6 +7892,31 @@ function resolveWithinProject(projectPath, candidatePath) {
|
|
|
7860
7892
|
}
|
|
7861
7893
|
return { ok: true, absolutePath, relativePath };
|
|
7862
7894
|
}
|
|
7895
|
+
function exportedBuild(projectPath, candidatePath) {
|
|
7896
|
+
const found = resolveWithinProject(projectPath, candidatePath);
|
|
7897
|
+
if (!found.ok) {
|
|
7898
|
+
return found;
|
|
7899
|
+
}
|
|
7900
|
+
let real;
|
|
7901
|
+
try {
|
|
7902
|
+
real = realpathSync.native(found.absolutePath);
|
|
7903
|
+
} catch {
|
|
7904
|
+
return {
|
|
7905
|
+
ok: false,
|
|
7906
|
+
reason: `There is no ${found.relativePath} in the project to start: export the build first, with project_export run.`
|
|
7907
|
+
};
|
|
7908
|
+
}
|
|
7909
|
+
if (!isWithinRoot(realPathOr(projectPath), real)) {
|
|
7910
|
+
return {
|
|
7911
|
+
ok: false,
|
|
7912
|
+
reason: `${found.relativePath} leads outside the project, to ${real}, so it is not started.`
|
|
7913
|
+
};
|
|
7914
|
+
}
|
|
7915
|
+
if (!statSync2(real).isFile()) {
|
|
7916
|
+
return { ok: false, reason: `${found.relativePath} is not a file, so there is nothing to start.` };
|
|
7917
|
+
}
|
|
7918
|
+
return { ok: true, absolutePath: real, relativePath: found.relativePath };
|
|
7919
|
+
}
|
|
7863
7920
|
var RESOURCE_SCHEME = "res://", OTHER_SCHEME;
|
|
7864
7921
|
var init_paths = __esm(() => {
|
|
7865
7922
|
OTHER_SCHEME = /^[a-zA-Z][a-zA-Z\d+.-]+:\/\//;
|
|
@@ -8154,15 +8211,13 @@ var init_game_log = __esm(() => {
|
|
|
8154
8211
|
});
|
|
8155
8212
|
|
|
8156
8213
|
// src/engine-run.ts
|
|
8157
|
-
import { spawn as spawn2 } from "node:child_process";
|
|
8158
8214
|
function runEngine(executable, args, options = {}) {
|
|
8159
8215
|
return new Promise((resolve) => {
|
|
8160
8216
|
const log = new GameLog;
|
|
8161
8217
|
let failure = null;
|
|
8162
8218
|
const signal = callSignal();
|
|
8163
|
-
const child =
|
|
8219
|
+
const child = spawnHidden(executable, args, {
|
|
8164
8220
|
stdio: ["ignore", "pipe", "pipe"],
|
|
8165
|
-
windowsHide: true,
|
|
8166
8221
|
...signal === undefined ? {} : { signal }
|
|
8167
8222
|
});
|
|
8168
8223
|
child.stdout.on("data", (chunk) => {
|
|
@@ -8196,6 +8251,7 @@ function howItEnded(run) {
|
|
|
8196
8251
|
var init_engine_run = __esm(() => {
|
|
8197
8252
|
init_call_signal();
|
|
8198
8253
|
init_game_log();
|
|
8254
|
+
init_spawn();
|
|
8199
8255
|
});
|
|
8200
8256
|
|
|
8201
8257
|
// src/library-copies.ts
|
|
@@ -8282,7 +8338,7 @@ var init_library_copies = () => {};
|
|
|
8282
8338
|
|
|
8283
8339
|
// src/reimport.ts
|
|
8284
8340
|
import { createHash } from "node:crypto";
|
|
8285
|
-
import { existsSync as existsSync6, readFileSync as readFileSync4, rmSync as rmSync3, statSync as
|
|
8341
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, rmSync as rmSync3, statSync as statSync4, utimesSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
8286
8342
|
import { basename as basename4, join as join6 } from "node:path";
|
|
8287
8343
|
function forceNextImport(projectPath, resourcePath) {
|
|
8288
8344
|
const relative = resourcePath.replace(/^res:\/\//, "");
|
|
@@ -8290,7 +8346,7 @@ function forceNextImport(projectPath, resourcePath) {
|
|
|
8290
8346
|
if (!existsSync6(sidecar)) {
|
|
8291
8347
|
return;
|
|
8292
8348
|
}
|
|
8293
|
-
const { atime, mtimeMs } =
|
|
8349
|
+
const { atime, mtimeMs } = statSync4(sidecar);
|
|
8294
8350
|
writeFileSync3(sidecar, readFileSync4(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
|
|
8295
8351
|
utimesSync(sidecar, atime, new Date(mtimeMs + 2000));
|
|
8296
8352
|
rmSync3(importRecord(projectPath, resourcePath), { force: true });
|
|
@@ -9704,7 +9760,7 @@ var init_gdscript_source = __esm(() => {
|
|
|
9704
9760
|
});
|
|
9705
9761
|
|
|
9706
9762
|
// src/class-cache.ts
|
|
9707
|
-
import { existsSync as existsSync10, readdirSync as readdirSync5, readFileSync as readFileSync8, statSync as
|
|
9763
|
+
import { existsSync as existsSync10, readdirSync as readdirSync5, readFileSync as readFileSync8, statSync as statSync5 } from "node:fs";
|
|
9708
9764
|
import { join as join10, posix } from "node:path";
|
|
9709
9765
|
function isToolScript(source) {
|
|
9710
9766
|
return TOOL.test(codeOf(source));
|
|
@@ -10027,7 +10083,7 @@ function uncachedClassNote(types) {
|
|
|
10027
10083
|
function cacheWrittenAt(projectPath) {
|
|
10028
10084
|
const cache = join10(projectPath, ".godot", "global_script_class_cache.cfg");
|
|
10029
10085
|
try {
|
|
10030
|
-
return
|
|
10086
|
+
return statSync5(cache).mtimeMs;
|
|
10031
10087
|
} catch {
|
|
10032
10088
|
return null;
|
|
10033
10089
|
}
|
|
@@ -10039,7 +10095,7 @@ function declaredSince(projectPath, paths, names, since) {
|
|
|
10039
10095
|
return false;
|
|
10040
10096
|
}
|
|
10041
10097
|
try {
|
|
10042
|
-
return
|
|
10098
|
+
return statSync5(join10(projectPath, path.slice("res://".length))).mtimeMs > since;
|
|
10043
10099
|
} catch {
|
|
10044
10100
|
return false;
|
|
10045
10101
|
}
|
|
@@ -10687,7 +10743,6 @@ function readParams(params, key) {
|
|
|
10687
10743
|
}
|
|
10688
10744
|
|
|
10689
10745
|
// src/setup.ts
|
|
10690
|
-
import { spawnSync } from "node:child_process";
|
|
10691
10746
|
import { createHash as createHash3 } from "node:crypto";
|
|
10692
10747
|
import {
|
|
10693
10748
|
cpSync,
|
|
@@ -10697,7 +10752,7 @@ import {
|
|
|
10697
10752
|
readFileSync as readFileSync11,
|
|
10698
10753
|
rmdirSync,
|
|
10699
10754
|
rmSync as rmSync5,
|
|
10700
|
-
statSync as
|
|
10755
|
+
statSync as statSync6,
|
|
10701
10756
|
writeFileSync as writeFileSync6
|
|
10702
10757
|
} from "node:fs";
|
|
10703
10758
|
import { dirname as dirname6, join as join12 } from "node:path";
|
|
@@ -10717,7 +10772,7 @@ function addonsDigest(names, from) {
|
|
|
10717
10772
|
}
|
|
10718
10773
|
const path = join12(directory, name);
|
|
10719
10774
|
const inside = `${relative}/${name}`;
|
|
10720
|
-
if (
|
|
10775
|
+
if (statSync6(path).isDirectory()) {
|
|
10721
10776
|
walk(path, inside);
|
|
10722
10777
|
} else {
|
|
10723
10778
|
hash.update(`${inside}
|
|
@@ -10870,7 +10925,7 @@ function trackedByGit(projectPath, wanted) {
|
|
|
10870
10925
|
if (wanted.length === 0) {
|
|
10871
10926
|
return new Set;
|
|
10872
10927
|
}
|
|
10873
|
-
const asked =
|
|
10928
|
+
const asked = spawnSyncHidden("git", ["ls-files", "-z", "--", ...wanted], {
|
|
10874
10929
|
cwd: projectPath,
|
|
10875
10930
|
encoding: "utf8"
|
|
10876
10931
|
});
|
|
@@ -10953,6 +11008,7 @@ var init_setup = __esm(() => {
|
|
|
10953
11008
|
init_project_scan();
|
|
10954
11009
|
init_resources();
|
|
10955
11010
|
init_server_version();
|
|
11011
|
+
init_spawn();
|
|
10956
11012
|
ADDONS = ["gdharness_editor", "gdharness_runtime", "auto_reload"];
|
|
10957
11013
|
EDITOR_PLUGINS = ["gdharness_editor", "auto_reload"];
|
|
10958
11014
|
RUNTIME_AUTOLOAD = {
|
|
@@ -12001,6 +12057,11 @@ var init_tool_definitions = __esm(() => {
|
|
|
12001
12057
|
ops: ["start"],
|
|
12002
12058
|
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."
|
|
12003
12059
|
},
|
|
12060
|
+
executable: {
|
|
12061
|
+
type: "string",
|
|
12062
|
+
ops: ["start"],
|
|
12063
|
+
description: "start: an exported build to run instead of the project, as a path inside the project such as export/windows/game.exe, so what a player runs can be measured. It takes args, engineArgs, savesIn, env, headless and visible as the project does, goes on the hidden desktop the same way, and is ended by stop, but takes no scene (its pack holds the main scene) and no profile, and has no debugger: a release build quits when given one. Its runtime is not waited for."
|
|
12064
|
+
},
|
|
12004
12065
|
savesIn: {
|
|
12005
12066
|
type: "string",
|
|
12006
12067
|
ops: ["start", "check"],
|
|
@@ -12156,7 +12217,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12156
12217
|
name: "editor_rescan",
|
|
12157
12218
|
title: "Rescan the project",
|
|
12158
12219
|
effect: "additive",
|
|
12159
|
-
description: "Makes the running editor scan the project filesystem, so files written outside it are picked up. A scan or an import the editor is already running is waited out first rather than scanned over, which would start a second import over it, and the answer says how long under waitedForEditorScanMs. The scan is a change-detecting walk rather than an unconditional reparse, so a file another engine has already imported reads as settled and the walk does not look inside it: its class_name then stays out of the list the editor resolves against, however many times you scan. Any class in that state is named under unseenByEditor, which is what no check on disk can see, since the declaration and the cache are both correct there and only the editor disagrees. The cure is this call on its own, with no change to the declaring script: measured against a real editor on three platforms, with idle waits of the same length ruled out so the scan is credited rather than the time it takes, and reproduced in a second project against its own reproduction in 203ms. editor_launch restart also does it and costs a window, and project_import refresh_classes does not: it rewrites the cache and does not touch what the editor is holding. The walk also passes over a file whose .uid alone changed, so the editor keeps the old UID and every engine reading its cache warns of a duplicate; before scanning, each file whose .uid names a UID the editor does not hold for it is read again and named under uidsReread, and files on disk naming one UID between them are named under uidsDuplicatedOnDisk, which reading again cannot settle. A class the scan brings in is named under broughtIn, and every script naming it is reloaded in the editor and named under dependentsReloaded: what the editor compiled from them while it could not see the class stays compiled that way after the scan, and a script extending a new base class went on reporting \"Could not find base class\" until reloaded. One that would not reload is named under dependentsNotReloaded and the call is not ok, as it is not when the script named in reloadScript was not reloaded. A scan writes .godot/global_script_class_cache.cfg from the list the editor is holding, so a class the editor cannot resolve goes out of that file with it. That is not a reason to refuse the scan, because on a class the editor has merely not walked yet the scan is the cure; what the answer does instead is name the loss under cacheLost and rebuild the cache from the files, naming what came back under cacheRestored, so no fresh engine, CI run or clone inherits the short file. An editor that lost classes is still holding the short list, so the note says to restart it before scanning again. The other direction is guarded the same way: a class the editor still holds for a script that was renamed or deleted comes back into the cache at a path that is not there, and the next engine to read it fails on \"Could not parse global class\" in whichever correct script shares the bare name, so the cache is rebuilt without it and the answer names it under cacheDropped; the editor goes on holding it until it is restarted, and every scan until then ends in that rebuild. A script or shader written outside the editor has no .uid sidecar until something imports it, and the scan writes one beside it, which a project that commits sidecars needs before the commit. Needs the editor connected.",
|
|
12220
|
+
description: "Makes the running editor scan the project filesystem, so files written outside it are picked up. A scan or an import the editor is already running is waited out first rather than scanned over, which would start a second import over it, and the answer says how long under waitedForEditorScanMs. The scan is a change-detecting walk rather than an unconditional reparse, so a file another engine has already imported reads as settled and the walk does not look inside it: its class_name then stays out of the list the editor resolves against, however many times you scan. Any class in that state is named under unseenByEditor, which is what no check on disk can see, since the declaration and the cache are both correct there and only the editor disagrees. The cure is this call on its own, with no change to the declaring script: measured against a real editor on three platforms, with idle waits of the same length ruled out so the scan is credited rather than the time it takes, and reproduced in a second project against its own reproduction in 203ms. editor_launch restart also does it and costs a window, and project_import refresh_classes does not: it rewrites the cache and does not touch what the editor is holding. The walk also passes over a file whose .uid alone changed, so the editor keeps the old UID and every engine reading its cache warns of a duplicate; before scanning, each file whose .uid names a UID the editor does not hold for it is read again and named under uidsReread, and files on disk naming one UID between them are named under uidsDuplicatedOnDisk, which reading again cannot settle. A class the scan brings in is named under broughtIn, and every script naming it is reloaded in the editor and named under dependentsReloaded: what the editor compiled from them while it could not see the class stays compiled that way after the scan, and a script extending a new base class went on reporting \"Could not find base class\" until reloaded. One that would not reload is named under dependentsNotReloaded and the call is not ok, as it is not when the script named in reloadScript was not reloaded. An editor importing answers nothing until the import is over, and a hidden one imports on the thread that answers, so a large addon install can leave the rescan itself unanswered: that is waited for as any busy editor is, within timeoutMs, and when the wait runs out first the answer is stillWorking. The classes the editor could not see before that scan are then noted in the project, and the next editor_rescan of the same editor, from any server, names them under broughtIn and reloads what names them, which it could not otherwise know to do, since by then the editor sees them. A scan writes .godot/global_script_class_cache.cfg from the list the editor is holding, so a class the editor cannot resolve goes out of that file with it. That is not a reason to refuse the scan, because on a class the editor has merely not walked yet the scan is the cure; what the answer does instead is name the loss under cacheLost and rebuild the cache from the files, naming what came back under cacheRestored, so no fresh engine, CI run or clone inherits the short file. An editor that lost classes is still holding the short list, so the note says to restart it before scanning again. The other direction is guarded the same way: a class the editor still holds for a script that was renamed or deleted comes back into the cache at a path that is not there, and the next engine to read it fails on \"Could not parse global class\" in whichever correct script shares the bare name, so the cache is rebuilt without it and the answer names it under cacheDropped; the editor goes on holding it until it is restarted, and every scan until then ends in that rebuild. A script or shader written outside the editor has no .uid sidecar until something imports it, and the scan writes one beside it, which a project that commits sidecars needs before the commit. Needs the editor connected.",
|
|
12160
12221
|
parameters: {
|
|
12161
12222
|
projectPath: PROJECT_PATH,
|
|
12162
12223
|
timeoutMs: {
|
|
@@ -12234,13 +12295,13 @@ var init_tool_definitions = __esm(() => {
|
|
|
12234
12295
|
pid: RUNNING_PID,
|
|
12235
12296
|
nodePath: {
|
|
12236
12297
|
type: "string",
|
|
12237
|
-
ops: ["tree", "text", "find", "rect", "property"],
|
|
12238
|
-
description:
|
|
12298
|
+
ops: ["tree", "text", "find", "rect", "property", "accessibility"],
|
|
12299
|
+
description: 'tree, find, text: where to start, default /root. rect: the node to place. property: the node to read, or an engine singleton by name as runtime_invoke takes them, "Engine" with property "get_frames_drawn()". accessibility: only the elements that node and those below it made, whether as node or part_of, each window keeping the outermost of them with what they hold, and matched counting them. What a ScrollContainer holds out of its sight is not in the tree until it is scrolled to, so an element missing under one may be scrolled away rather than hidden.'
|
|
12239
12300
|
},
|
|
12240
12301
|
property: {
|
|
12241
12302
|
type: "string",
|
|
12242
12303
|
ops: ["property", "find"],
|
|
12243
|
-
description: 'property: which one to read. find: read this one off every node matched, so a panel of labels is one call rather than one per label. Colons read through what a node holds, "_game:clock:speed", which is where a game keeps what is worth asking about. A number or a key steps into a list or a map, "_game:run:roster:0:traits", which is how the lists a game keeps its state in are walked: a roster, a board, an in-tray. A packed list, such as a PackedStringArray, steps the same way. A negative number counts from the end. A step written as a call, "get_viewport():gui_get_focus_owner()", calls a method that only reads (an engine method the engine marks as reading, or a game method not declared -> void, so queue_free() is refused) and walks into what it returned, which is how a question only a method answers is read in one call: which control holds the focus in the viewport this one is in. An engine method that reads takes literal arguments in its brackets, words in double quotes, numbers, true, false or null, so a control\'s theme is read where it is drawn: \'get_theme_stylebox("panel"):content_margin_top\' or \'get_theme_constant("separation")\'. A game method is given none in a path, since nothing marks one as only reading; runtime_invoke call takes those. A list or a map answers the calls that read it and take no arguments, "_game:run:board:size()" or "_game:inbox:keys()", and not those that change it, such as clear() or sort(). A step that is not there is named, and says what was there instead.'
|
|
12304
|
+
description: 'property: which one to read. find: read this one off every node matched, so a panel of labels is one call rather than one per label. Colons read through what a node holds, "_game:clock:speed", which is where a game keeps what is worth asking about. A number or a key steps into a list or a map, "_game:run:roster:0:traits", which is how the lists a game keeps its state in are walked: a roster, a board, an in-tray. A packed list, such as a PackedStringArray, steps the same way. A negative number counts from the end. A vector, a colour, a rectangle or a transform is stepped into by its components as GDScript names them, so one axis is one number: "size:x", "global_position:y", "modulate:a", "global_transform:origin:z"; a name the value does not have is refused, naming the ones it has. A step written as a call, "get_viewport():gui_get_focus_owner()", calls a method that only reads (an engine method the engine marks as reading, or a game method not declared -> void, so queue_free() is refused) and walks into what it returned, which is how a question only a method answers is read in one call: which control holds the focus in the viewport this one is in. An engine method that reads takes literal arguments in its brackets, words in double quotes, numbers, true, false or null, so a control\'s theme is read where it is drawn: \'get_theme_stylebox("panel"):content_margin_top\' or \'get_theme_constant("separation")\'. A game method is given none in a path, since nothing marks one as only reading; runtime_invoke call takes those. A list or a map answers the calls that read it and take no arguments, "_game:run:board:size()" or "_game:inbox:keys()", and not those that change it, such as clear() or sort(). A step that is not there is named, and says what was there instead.'
|
|
12244
12305
|
},
|
|
12245
12306
|
depth: {
|
|
12246
12307
|
type: "integer",
|
|
@@ -12256,8 +12317,8 @@ var init_tool_definitions = __esm(() => {
|
|
|
12256
12317
|
properties: {
|
|
12257
12318
|
type: "array",
|
|
12258
12319
|
items: { type: "string" },
|
|
12259
|
-
ops: ["tree"],
|
|
12260
|
-
description:
|
|
12320
|
+
ops: ["tree", "accessibility"],
|
|
12321
|
+
description: 'tree: the properties to read off each node, by name, so the text of every label and button under a panel is one call: a node that has not got one leaves it out. Colons read through what a node holds, as property does for find. accessibility: the fields to keep on each element, such as ["role", "name", "node"], leaving out the rest (bounds, actions, tooltip); children are always kept.'
|
|
12261
12322
|
},
|
|
12262
12323
|
className: {
|
|
12263
12324
|
type: "string",
|
|
@@ -12273,15 +12334,15 @@ var init_tool_definitions = __esm(() => {
|
|
|
12273
12334
|
group: { type: "string", ops: ["find"], description: "find: a group the node is in." },
|
|
12274
12335
|
says: {
|
|
12275
12336
|
type: "string",
|
|
12276
|
-
ops: ["find"],
|
|
12277
|
-
description: 'find: part of what the node has written on it as drawn, the way the text op reads it, case-insensitively and with a typed space matching any space the game draws (a non-breaking one included), which is how a button is reached by the word on it rather than by a generated path. Its own text, so a row is found by the label in it, and hidden nodes match. A bare word is a contains; write a glob and it is one, matched against the whole of what the node says, the same as namePattern, and a glob that finds nothing says under note how many nodes it would find open at both ends. A label with a line break is matched with the break in the words, and a backslash followed by n counts as one. Alternatives separated by | match when any one of them does, each by these rules ("*won*|*lost*"); write \\| for a bar that is part of the words.'
|
|
12337
|
+
ops: ["find", "accessibility"],
|
|
12338
|
+
description: 'accessibility: only the elements whose name or value says these words, matched as find matches them, with what they hold. find: part of what the node has written on it as drawn, the way the text op reads it, case-insensitively and with a typed space matching any space the game draws (a non-breaking one included), which is how a button is reached by the word on it rather than by a generated path. Its own text, so a row is found by the label in it, and hidden nodes match. A bare word is a contains; write a glob and it is one, matched against the whole of what the node says, the same as namePattern, and a glob that finds nothing says under note how many nodes it would find open at both ends. A label with a line break is matched with the break in the words, and a backslash followed by n counts as one. Alternatives separated by | match when any one of them does, each by these rules ("*won*|*lost*"); write \\| for a bar that is part of the words.'
|
|
12278
12339
|
},
|
|
12279
12340
|
limit: {
|
|
12280
12341
|
type: "integer",
|
|
12281
12342
|
minimum: 1,
|
|
12282
12343
|
maximum: 5000,
|
|
12283
|
-
ops: ["find", "text"],
|
|
12284
|
-
description: "find: the most nodes to answer with, default 100. text: the most lines, default 500, with omitted saying how many lines that left behind. At most 5000 for either. A screen whose dialog sits under a long list is a screen read with omitted greater than zero, so raise this or point root at the dialog rather than reading the answer as what is on screen."
|
|
12344
|
+
ops: ["find", "text", "accessibility"],
|
|
12345
|
+
description: "find: the most nodes to answer with, default 100. text: the most lines, default 500, with omitted saying how many lines that left behind. accessibility: the most elements below the windows, depth first, with omitted counting the rest; default all. At most 5000 for either. A screen whose dialog sits under a long list is a screen read with omitted greater than zero, so raise this or point root at the dialog rather than reading the answer as what is on screen."
|
|
12285
12346
|
},
|
|
12286
12347
|
includeHidden: {
|
|
12287
12348
|
type: "boolean",
|
|
@@ -12330,7 +12391,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12330
12391
|
},
|
|
12331
12392
|
accessibility: {
|
|
12332
12393
|
effect: "read",
|
|
12333
|
-
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
|
|
12394
|
+
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 node that made the element; an element a node made as a part of itself has part_of, the node drawn around it that holds its words, or part_of_candidates where several are, and part: announcement marks the text a window announces 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 nothing focused, the engine calls GdharnessRuntime 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",
|
|
12334
12395
|
requires: []
|
|
12335
12396
|
}
|
|
12336
12397
|
},
|
|
@@ -12339,24 +12400,24 @@ var init_tool_definitions = __esm(() => {
|
|
|
12339
12400
|
{
|
|
12340
12401
|
name: "runtime_invoke",
|
|
12341
12402
|
title: "Set or call in the running game",
|
|
12342
|
-
description: "Sets a property or calls a method on a node in the running game. Needs the game running with the runtime addon. The answer carries elapsed_usec, how long the call or the write took inside the game, measured around it there: the round trip to the game is a second or more, so timing from outside measures that instead. Errors and warnings the engine reported while a called method ran come back under errors, the first 20, with errors_not_shown counting the rest. A call that takes longer than timeoutMs is not cancelled: the answer is pending: true with a requestId, the call goes on and does everything it was asked, and op result with that requestId collects its reply once it comes.",
|
|
12403
|
+
description: "Sets a property or calls a method on a node, or on one of the engine singletons nodePath names, in the running game. Needs the game running with the runtime addon. The answer carries elapsed_usec, how long the call or the write took inside the game, measured around it there: the round trip to the game is a second or more, so timing from outside measures that instead. Errors and warnings the engine reported while a called method ran come back under errors, the first 20, with errors_not_shown counting the rest. A call that takes longer than timeoutMs is not cancelled: the answer is pending: true with a requestId, the call goes on and does everything it was asked, and op result with that requestId collects its reply once it comes.",
|
|
12343
12404
|
parameters: {
|
|
12344
12405
|
projectPath: RUNNING_PROJECT_PATH,
|
|
12345
12406
|
pid: RUNNING_PID,
|
|
12346
12407
|
nodePath: {
|
|
12347
12408
|
type: "string",
|
|
12348
12409
|
ops: ["set", "call"],
|
|
12349
|
-
description: 'Absolute node path, such as "/root/Main/Player".'
|
|
12410
|
+
description: 'Absolute node path, such as "/root/Main/Player", or the name of an engine singleton: Engine, Performance, RenderingServer, AudioServer, PhysicsServer2D, PhysicsServer3D, NavigationServer2D, NavigationServer3D, Time or TranslationServer, so "RenderingServer" with method viewport_get_measured_render_time_cpu reads what the engine measures. The other singletons are refused: OS runs programs, DisplayServer switches the keyboard layout and Input moves the real pointer.'
|
|
12350
12411
|
},
|
|
12351
12412
|
property: {
|
|
12352
12413
|
type: "string",
|
|
12353
12414
|
ops: ["set"],
|
|
12354
|
-
description: 'set: which one to write. Colons write through what a node holds, "_game:run:day", and a number or a key steps into a list or a map on the way, "_game:run:roster:0:name", a negative number counting from the end, and a step written as a call, "get_viewport():gui_embed_subwindows", walks through what a reading method returned, as runtime_inspect property describes. A map keyed by numbers is stepped into by the number, "_game:units:3". The answer reads back off the same holder, and a write that leaves the property as it was is refused and says what it still holds: the engine not taking it and a setter keeping the value look the same, so the refusal names neither. A write of the value the property already holds, at its own precision, as a float given in 64 bits to a property keeping 32, is not refused: it answers unchanged: true.'
|
|
12415
|
+
description: 'set: which one to write. Colons write through what a node holds, "_game:run:day", and a number or a key steps into a list or a map on the way, "_game:run:roster:0:name", a negative number counting from the end, and a step written as a call, "get_viewport():gui_embed_subwindows", walks through what a reading method returned, as runtime_inspect property describes. A map keyed by numbers is stepped into by the number, "_game:units:3". A component of a vector, a colour or a transform, "position:x", writes that one alone: the value holding it is a copy, so it is put back where it was read from, setter and all, and a component of what a call returned is refused, since the game keeps that copy nowhere. The answer reads back off the same holder, and a write that leaves the property as it was is refused and says what it still holds: the engine not taking it and a setter keeping the value look the same, so the refusal names neither. A write of the value the property already holds, at its own precision, as a float given in 64 bits to a property keeping 32, is not refused: it answers unchanged: true.'
|
|
12355
12416
|
},
|
|
12356
12417
|
value: {
|
|
12357
12418
|
ops: ["set"],
|
|
12358
12419
|
blank: true,
|
|
12359
|
-
description: `set: the value, fitted to the property's type. A vector, colour or the like may be written as its fields, {"x": 140, "y": 300}, or tagged, {"_type": "Vector2", "x": 140, "y": 300}, which is the form a read answers with. "" writes an empty string. A property typed as or holding an object takes a path naming one the game holds, the way call's args do, and holds that instance, or null, which the text null also means, to empty it. One declared as Variant or untyped takes anything, as the engine does: a value of the kind it holds is fitted to it, an int staying an int, and any other is stored as sent. A typed list or map, such as Array[int] or Dictionary[String, int], takes a JSON list or object with each element fitted to its element type, and an element that cannot become one is refused by its index or key, as is an object of another script than the one the list holds, whether or not that script has a class_name.`
|
|
12420
|
+
description: `set: the value, fitted to the property's type. A number the property would have to cut is refused rather than truncated: a fraction into an int, a number other than 0 or 1 into a bool. A vector, colour or the like may be written as its fields, {"x": 140, "y": 300}, or tagged, {"_type": "Vector2", "x": 140, "y": 300}, which is the form a read answers with. "" writes an empty string. A property typed as or holding an object takes a path naming one the game holds, the way call's args do, and holds that instance, or null, which the text null also means, to empty it. One declared as Variant or untyped takes anything, as the engine does: a value of the kind it holds is fitted to it, an int staying an int, and any other is stored as sent, a fraction into one holding an int included. A typed list or map, such as Array[int] or Dictionary[String, int], takes a JSON list or object with each element fitted to its element type, and an element that cannot become one is refused by its index or key, as is an object of another script than the one the list holds, whether or not that script has a class_name.`
|
|
12360
12421
|
},
|
|
12361
12422
|
method: {
|
|
12362
12423
|
type: "string",
|
|
@@ -12366,7 +12427,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12366
12427
|
args: {
|
|
12367
12428
|
type: "array",
|
|
12368
12429
|
ops: ["call"],
|
|
12369
|
-
description: `call: the arguments, fitted to the method's parameter types. A vector, colour or the like may be written as its fields, {"x": 140, "y": 300}, or tagged, {"_type": "Vector2", "x": 140, "y": 300}. A parameter typed as an object takes a path naming one the game holds and is handed that instance: a colon path read from nodePath, "_game:run:wares:3", or one starting at a node, "/root/Main/Hud" or "/root/Main:_game:run". A path that reaches no object, or an object of another class than the parameter declares, is refused, and so are too few or too many arguments. The engine names a parameter typed as a script class without class_name by the class the script extends, so an object of that class with another script passes that check and the engine refuses the call: the answer says the method did not run, with the engine's reason. A parameter typed as a list or map, such as Array[int] or Array[Gear], is built from a JSON list or object element by element, objects named by their paths. The method itself takes its arguments here only: brackets on it holding arguments are refused.`
|
|
12430
|
+
description: `call: the arguments, fitted to the method's parameter types. A number a parameter would have to cut is refused rather than truncated: a fraction for an int, a number other than 0 or 1 for a bool. A vector, colour or the like may be written as its fields, {"x": 140, "y": 300}, or tagged, {"_type": "Vector2", "x": 140, "y": 300}. A parameter typed as an object takes a path naming one the game holds and is handed that instance: a colon path read from nodePath, "_game:run:wares:3", or one starting at a node, "/root/Main/Hud" or "/root/Main:_game:run". A path that reaches no object, or an object of another class than the parameter declares, is refused, and so are too few or too many arguments. The engine names a parameter typed as a script class without class_name by the class the script extends, so an object of that class with another script passes that check and the engine refuses the call: the answer says the method did not run, with the engine's reason. A parameter typed as a list or map, such as Array[int] or Array[Gear], is built from a JSON list or object element by element, objects named by their paths. The method itself takes its arguments here only: brackets on it holding arguments are refused.`
|
|
12370
12431
|
},
|
|
12371
12432
|
properties: {
|
|
12372
12433
|
type: ["boolean", "array"],
|
|
@@ -12484,6 +12545,13 @@ var init_tool_definitions = __esm(() => {
|
|
|
12484
12545
|
ops: ["action", "key", "mouse_click", "joypad_button"],
|
|
12485
12546
|
description: "action, key, mouse_click, joypad_button: leave it out and the press is a whole one, down and up a frame apart. true holds it down, false lets go of one being held. A wheel button held down is still a whole step, since a wheel is never held."
|
|
12486
12547
|
},
|
|
12548
|
+
holdFrames: {
|
|
12549
|
+
type: "integer",
|
|
12550
|
+
minimum: 1,
|
|
12551
|
+
maximum: 600,
|
|
12552
|
+
ops: ["action", "key", "mouse_click", "joypad_button"],
|
|
12553
|
+
description: "action, key, mouse_click, joypad_button: hold a whole press down for this many process frames and then let go, both inside the game, so a pour, a charge or a long press runs to a set length. A press, a frames wait and a release sent as three calls hold it for the time between the calls as well. The answer says held_frames. Not with pressed, and not on a wheel step. Default 1."
|
|
12554
|
+
},
|
|
12487
12555
|
strength: {
|
|
12488
12556
|
type: "number",
|
|
12489
12557
|
minimum: 0,
|
|
@@ -12632,7 +12700,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12632
12700
|
property: {
|
|
12633
12701
|
type: "string",
|
|
12634
12702
|
ops: ["until"],
|
|
12635
|
-
description: 'until: the property name, or a colon path through what the node holds, "_game:run:day", read the way runtime_inspect property reads one, a step written as a call included. Walked again on every frame, so a holder the game replaces while the wait is on is followed.'
|
|
12703
|
+
description: 'until: the property name, or a colon path through what the node holds, "_game:run:day", read the way runtime_inspect property reads one, a step written as a call included, and one axis of a vector as well: "global_position:x" with compare ">=" stops once a thing has travelled far enough. Walked again on every frame, so a holder the game replaces while the wait is on is followed.'
|
|
12636
12704
|
},
|
|
12637
12705
|
value: {
|
|
12638
12706
|
ops: ["until"],
|
|
@@ -34506,7 +34574,7 @@ var init_stdio = __esm(() => {
|
|
|
34506
34574
|
});
|
|
34507
34575
|
|
|
34508
34576
|
// src/runtime-client.ts
|
|
34509
|
-
import { existsSync as existsSync14, readdirSync as readdirSync9, readFileSync as readFileSync13, statSync as
|
|
34577
|
+
import { existsSync as existsSync14, readdirSync as readdirSync9, readFileSync as readFileSync13, statSync as statSync7, unlinkSync as unlinkSync2 } from "node:fs";
|
|
34510
34578
|
import { createConnection } from "node:net";
|
|
34511
34579
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
34512
34580
|
import { dirname as dirname9, join as join15, resolve as resolve4 } from "node:path";
|
|
@@ -34578,7 +34646,7 @@ function parseAnnouncement(file, pid) {
|
|
|
34578
34646
|
};
|
|
34579
34647
|
let announcedAt;
|
|
34580
34648
|
try {
|
|
34581
|
-
announcedAt =
|
|
34649
|
+
announcedAt = statSync7(file).mtimeMs;
|
|
34582
34650
|
} catch {}
|
|
34583
34651
|
const when = announcedAt === undefined ? {} : { announcedAt };
|
|
34584
34652
|
if (protocol !== RUNTIME_PROTOCOL) {
|
|
@@ -34692,7 +34760,7 @@ function announcedIn(directory) {
|
|
|
34692
34760
|
entries.push({ file, pid, running });
|
|
34693
34761
|
if (running) {
|
|
34694
34762
|
try {
|
|
34695
|
-
candidates.push({ file, pid, writtenAt:
|
|
34763
|
+
candidates.push({ file, pid, writtenAt: statSync7(file).mtimeMs });
|
|
34696
34764
|
} catch {}
|
|
34697
34765
|
}
|
|
34698
34766
|
}
|
|
@@ -34749,7 +34817,7 @@ function sweepErrorReport(path, pid, now = Date.now()) {
|
|
|
34749
34817
|
return;
|
|
34750
34818
|
}
|
|
34751
34819
|
try {
|
|
34752
|
-
if (now -
|
|
34820
|
+
if (now - statSync7(path).mtimeMs >= ERROR_REPORT_KEEP_MS) {
|
|
34753
34821
|
unlinkSync2(path);
|
|
34754
34822
|
}
|
|
34755
34823
|
} catch {}
|
|
@@ -35134,8 +35202,39 @@ var init_breakpoint_note = __esm(() => {
|
|
|
35134
35202
|
init_paths();
|
|
35135
35203
|
});
|
|
35136
35204
|
|
|
35205
|
+
// src/whole-file.ts
|
|
35206
|
+
import { renameSync as renameSync2, rmSync as rmSync8, writeFileSync as writeFileSync10 } from "node:fs";
|
|
35207
|
+
function writeWhole(path, text) {
|
|
35208
|
+
const partial = `${path}.${process.pid}.partial`;
|
|
35209
|
+
try {
|
|
35210
|
+
writeFileSync10(partial, text, "utf8");
|
|
35211
|
+
} catch {
|
|
35212
|
+
return false;
|
|
35213
|
+
}
|
|
35214
|
+
const until = Date.now() + HELD_FILE_MS;
|
|
35215
|
+
for (;; ) {
|
|
35216
|
+
try {
|
|
35217
|
+
renameSync2(partial, path);
|
|
35218
|
+
return true;
|
|
35219
|
+
} catch (error) {
|
|
35220
|
+
const code = error.code ?? "";
|
|
35221
|
+
if (!HELD_BY_A_READER.has(code) || Date.now() >= until) {
|
|
35222
|
+
try {
|
|
35223
|
+
rmSync8(partial, { force: true });
|
|
35224
|
+
} catch {}
|
|
35225
|
+
return false;
|
|
35226
|
+
}
|
|
35227
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, HELD_FILE_RETRY_MS);
|
|
35228
|
+
}
|
|
35229
|
+
}
|
|
35230
|
+
}
|
|
35231
|
+
var HELD_FILE_MS = 1000, HELD_FILE_RETRY_MS = 10, HELD_BY_A_READER;
|
|
35232
|
+
var init_whole_file = __esm(() => {
|
|
35233
|
+
HELD_BY_A_READER = new Set(["EPERM", "EACCES", "EBUSY"]);
|
|
35234
|
+
});
|
|
35235
|
+
|
|
35137
35236
|
// src/class-note.ts
|
|
35138
|
-
import { mkdirSync as mkdirSync8, readFileSync as readFileSync16
|
|
35237
|
+
import { mkdirSync as mkdirSync8, readFileSync as readFileSync16 } from "node:fs";
|
|
35139
35238
|
import { dirname as dirname12, join as join18 } from "node:path";
|
|
35140
35239
|
function classNotePath(projectPath) {
|
|
35141
35240
|
return join18(projectPath, ".godot", "gdharness-classes.json");
|
|
@@ -35148,23 +35247,40 @@ function readClassNote(projectPath) {
|
|
|
35148
35247
|
return null;
|
|
35149
35248
|
}
|
|
35150
35249
|
const pid = readNumber(fields, "shortListEditorPid");
|
|
35250
|
+
const unfinished = unfinishedScanIn(fields["unseenBeforeAnUnfinishedScan"]);
|
|
35151
35251
|
return {
|
|
35152
35252
|
rebuiltWith: rebuiltWith.map(String),
|
|
35153
|
-
...Object.hasOwn(fields, "shortListEditorPid") ? { shortListEditorPid: pid ?? null } : {}
|
|
35253
|
+
...Object.hasOwn(fields, "shortListEditorPid") ? { shortListEditorPid: pid ?? null } : {},
|
|
35254
|
+
...unfinished === null ? {} : { unseenBeforeAnUnfinishedScan: unfinished }
|
|
35154
35255
|
};
|
|
35155
35256
|
} catch {
|
|
35156
35257
|
return null;
|
|
35157
35258
|
}
|
|
35158
35259
|
}
|
|
35260
|
+
function unfinishedScanIn(value) {
|
|
35261
|
+
if (typeof value !== "object" || value === null) {
|
|
35262
|
+
return null;
|
|
35263
|
+
}
|
|
35264
|
+
const fields = asParams(value);
|
|
35265
|
+
const classes = readArray(fields, "classes");
|
|
35266
|
+
if (classes === undefined || !classes.every((name) => typeof name === "string")) {
|
|
35267
|
+
return null;
|
|
35268
|
+
}
|
|
35269
|
+
return { editorPid: readNumber(fields, "editorPid") ?? null, classes: classes.map(String) };
|
|
35270
|
+
}
|
|
35159
35271
|
function writeClassNote(projectPath, note) {
|
|
35272
|
+
const path = classNotePath(projectPath);
|
|
35160
35273
|
try {
|
|
35161
|
-
const path = classNotePath(projectPath);
|
|
35162
35274
|
mkdirSync8(dirname12(path), { recursive: true });
|
|
35163
|
-
|
|
35164
|
-
|
|
35165
|
-
}
|
|
35275
|
+
} catch {
|
|
35276
|
+
return;
|
|
35277
|
+
}
|
|
35278
|
+
writeWhole(path, `${JSON.stringify(note, null, 2)}
|
|
35279
|
+
`);
|
|
35166
35280
|
}
|
|
35167
|
-
var init_class_note = () => {
|
|
35281
|
+
var init_class_note = __esm(() => {
|
|
35282
|
+
init_whole_file();
|
|
35283
|
+
});
|
|
35168
35284
|
|
|
35169
35285
|
// src/config-pin.ts
|
|
35170
35286
|
import { existsSync as existsSync15, readFileSync as readFileSync17 } from "node:fs";
|
|
@@ -35197,7 +35313,7 @@ var init_config_pin = __esm(() => {
|
|
|
35197
35313
|
});
|
|
35198
35314
|
|
|
35199
35315
|
// src/cut-test-run.ts
|
|
35200
|
-
import { existsSync as existsSync16, mkdirSync as mkdirSync9, readFileSync as readFileSync18, rmSync as
|
|
35316
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync9, readFileSync as readFileSync18, rmSync as rmSync9, writeFileSync as writeFileSync11 } from "node:fs";
|
|
35201
35317
|
import { dirname as dirname13, join as join20 } from "node:path";
|
|
35202
35318
|
function keptAt(projectPath) {
|
|
35203
35319
|
return join20(projectPath, ".godot", "gdharness-reports", "cut-run.json");
|
|
@@ -35217,10 +35333,10 @@ function takeCutTestRun(projectPath) {
|
|
|
35217
35333
|
}
|
|
35218
35334
|
try {
|
|
35219
35335
|
const run = JSON.parse(readFileSync18(path, "utf8"));
|
|
35220
|
-
|
|
35336
|
+
rmSync9(path, { force: true });
|
|
35221
35337
|
return typeof run.cutAt === "string" && Array.isArray(run.paths) && run.paths.every((path) => typeof path === "string") ? run : null;
|
|
35222
35338
|
} catch {
|
|
35223
|
-
|
|
35339
|
+
rmSync9(path, { force: true });
|
|
35224
35340
|
return null;
|
|
35225
35341
|
}
|
|
35226
35342
|
}
|
|
@@ -36037,8 +36153,8 @@ import {
|
|
|
36037
36153
|
openSync,
|
|
36038
36154
|
readFileSync as readFileSync19,
|
|
36039
36155
|
readSync,
|
|
36040
|
-
rmSync as
|
|
36041
|
-
statSync as
|
|
36156
|
+
rmSync as rmSync10,
|
|
36157
|
+
statSync as statSync8,
|
|
36042
36158
|
writeFileSync as writeFileSync12
|
|
36043
36159
|
} from "node:fs";
|
|
36044
36160
|
import { dirname as dirname14, join as join21 } from "node:path";
|
|
@@ -36062,8 +36178,8 @@ function clearEditorLog(projectPath) {
|
|
|
36062
36178
|
try {
|
|
36063
36179
|
const path = editorLogPath(projectPath);
|
|
36064
36180
|
mkdirSync10(dirname14(path), { recursive: true });
|
|
36065
|
-
|
|
36066
|
-
|
|
36181
|
+
rmSync10(path, { force: true });
|
|
36182
|
+
rmSync10(editorLogNotePath(projectPath), { force: true });
|
|
36067
36183
|
} catch {}
|
|
36068
36184
|
}
|
|
36069
36185
|
function writeEditorLogNote(projectPath, pid) {
|
|
@@ -36147,7 +36263,7 @@ function foldProgress(text) {
|
|
|
36147
36263
|
`);
|
|
36148
36264
|
}
|
|
36149
36265
|
function theFirstOf(path) {
|
|
36150
|
-
const whole =
|
|
36266
|
+
const whole = statSync8(path).size;
|
|
36151
36267
|
if (whole <= MOST_CONSOLE_BYTES) {
|
|
36152
36268
|
return { text: readFileSync19(path, "utf8") };
|
|
36153
36269
|
}
|
|
@@ -39286,12 +39402,21 @@ function getDefaultBridge(ownProject = null) {
|
|
|
39286
39402
|
defaultBridge ??= new GodotBridge(resolveDefaultBridgePort(), resolveDefaultBridgeHost(), DEFAULT_TIMEOUT_MS, ownProject);
|
|
39287
39403
|
return defaultBridge;
|
|
39288
39404
|
}
|
|
39289
|
-
var DEFAULT_PORT = 6505, DEFAULT_HOST = "127.0.0.1", DEFAULT_TIMEOUT_MS = 30000, KEEPALIVE_INTERVAL_MS = 1e4, SECOND_CONNECTION_CLOSE_CODE = 4000, MOST_CLOSE_REASON_BYTES = 123, OTHER_PROJECT_CLOSE_CODE = 4001, ONE_SHOT_CLOSE_CODE = 4002, CONNECT_WINDOW_MS = 35000, GodotBridge, defaultBridge = null;
|
|
39405
|
+
var DEFAULT_PORT = 6505, DEFAULT_HOST = "127.0.0.1", DEFAULT_TIMEOUT_MS = 30000, KEEPALIVE_INTERVAL_MS = 1e4, SECOND_CONNECTION_CLOSE_CODE = 4000, MOST_CLOSE_REASON_BYTES = 123, OTHER_PROJECT_CLOSE_CODE = 4001, ONE_SHOT_CLOSE_CODE = 4002, CONNECT_WINDOW_MS = 35000, BridgeTimeout, GodotBridge, defaultBridge = null;
|
|
39290
39406
|
var init_godot_bridge = __esm(() => {
|
|
39291
39407
|
init_wrapper();
|
|
39292
39408
|
init_errors();
|
|
39293
39409
|
init_paths();
|
|
39294
39410
|
init_ports();
|
|
39411
|
+
BridgeTimeout = class BridgeTimeout extends Error {
|
|
39412
|
+
toolName;
|
|
39413
|
+
waitedMs;
|
|
39414
|
+
constructor(toolName, waitedMs) {
|
|
39415
|
+
super(`Tool ${toolName} timed out after ${waitedMs}ms`);
|
|
39416
|
+
this.toolName = toolName;
|
|
39417
|
+
this.waitedMs = waitedMs;
|
|
39418
|
+
}
|
|
39419
|
+
};
|
|
39295
39420
|
GodotBridge = class GodotBridge extends EventEmitter {
|
|
39296
39421
|
listeningSince = null;
|
|
39297
39422
|
httpServer = null;
|
|
@@ -39629,7 +39754,7 @@ var init_godot_bridge = __esm(() => {
|
|
|
39629
39754
|
return new Promise((resolve, reject) => {
|
|
39630
39755
|
const timeout = setTimeout(() => {
|
|
39631
39756
|
this.pendingRequests.delete(requestId);
|
|
39632
|
-
reject(new
|
|
39757
|
+
reject(new BridgeTimeout(toolName, this.timeoutMs));
|
|
39633
39758
|
}, this.timeoutMs);
|
|
39634
39759
|
this.pendingRequests.set(requestId, {
|
|
39635
39760
|
toolName,
|
|
@@ -40357,7 +40482,7 @@ var DEBUG_PREFIX = "debug ";
|
|
|
40357
40482
|
var init_keeper_channel = () => {};
|
|
40358
40483
|
|
|
40359
40484
|
// src/lifecycle-log.ts
|
|
40360
|
-
import { appendFileSync, mkdirSync as mkdirSync11, renameSync as
|
|
40485
|
+
import { appendFileSync, mkdirSync as mkdirSync11, renameSync as renameSync3, statSync as statSync9 } from "node:fs";
|
|
40361
40486
|
import { dirname as dirname15, join as join22 } from "node:path";
|
|
40362
40487
|
import process10 from "node:process";
|
|
40363
40488
|
function lifecycleLogPath() {
|
|
@@ -40367,9 +40492,9 @@ function writeLifecycle(event, fields = {}) {
|
|
|
40367
40492
|
const path = lifecycleLogPath();
|
|
40368
40493
|
try {
|
|
40369
40494
|
mkdirSync11(dirname15(path), { recursive: true });
|
|
40370
|
-
if ((
|
|
40495
|
+
if ((statSync9(path, { throwIfNoEntry: false })?.size ?? 0) > LIFECYCLE_LOG_LIMIT) {
|
|
40371
40496
|
try {
|
|
40372
|
-
|
|
40497
|
+
renameSync3(path, `${path}.1`);
|
|
40373
40498
|
} catch {}
|
|
40374
40499
|
}
|
|
40375
40500
|
const line = {
|
|
@@ -41230,7 +41355,6 @@ var init_lsp_client = __esm(() => {
|
|
|
41230
41355
|
});
|
|
41231
41356
|
|
|
41232
41357
|
// src/outside.ts
|
|
41233
|
-
import { spawn as spawn3 } from "node:child_process";
|
|
41234
41358
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
41235
41359
|
function changesFrom(env, base) {
|
|
41236
41360
|
const changes = {};
|
|
@@ -41263,9 +41387,8 @@ function readLaunched(printed) {
|
|
|
41263
41387
|
}
|
|
41264
41388
|
async function launchOutsideTheTree(spec, runtime = process.execPath, keeper = KEEPER_SCRIPT) {
|
|
41265
41389
|
return await new Promise((resolve) => {
|
|
41266
|
-
const launcher =
|
|
41267
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
41268
|
-
windowsHide: true
|
|
41390
|
+
const launcher = spawnHidden(runtime, [keeper, "launch"], {
|
|
41391
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
41269
41392
|
});
|
|
41270
41393
|
sendSpec(launcher, {
|
|
41271
41394
|
command: spec.command,
|
|
@@ -41294,6 +41417,7 @@ async function launchOutsideTheTree(spec, runtime = process.execPath, keeper = K
|
|
|
41294
41417
|
}
|
|
41295
41418
|
var KEEPER_SCRIPT;
|
|
41296
41419
|
var init_outside = __esm(() => {
|
|
41420
|
+
init_spawn();
|
|
41297
41421
|
KEEPER_SCRIPT = fileURLToPath4(new URL("./keeper.js", import.meta.url));
|
|
41298
41422
|
});
|
|
41299
41423
|
|
|
@@ -41334,8 +41458,6 @@ var STARTING_MS = 20000;
|
|
|
41334
41458
|
var init_port_probe = () => {};
|
|
41335
41459
|
|
|
41336
41460
|
// src/process-time.ts
|
|
41337
|
-
import { execFile as execFile3 } from "node:child_process";
|
|
41338
|
-
import { promisify as promisify3 } from "node:util";
|
|
41339
41461
|
async function cpuSecondsOf(pid) {
|
|
41340
41462
|
try {
|
|
41341
41463
|
return process.platform === "win32" ? await secondsOnWindows(pid) : await askPosix2(pid);
|
|
@@ -41349,7 +41471,7 @@ async function secondsOnWindows(pid) {
|
|
|
41349
41471
|
return Number.isFinite(seconds) ? seconds : undefined;
|
|
41350
41472
|
}
|
|
41351
41473
|
async function askPosix2(pid) {
|
|
41352
|
-
const { stdout } = await
|
|
41474
|
+
const { stdout } = await runHidden("ps", ["-o", "time=", "-p", String(pid)], { timeout: ASK_TIMEOUT_MS2 });
|
|
41353
41475
|
return secondsFromClock(stdout.trim());
|
|
41354
41476
|
}
|
|
41355
41477
|
function secondsFromClock(printed) {
|
|
@@ -41367,10 +41489,10 @@ function secondsFromClock(printed) {
|
|
|
41367
41489
|
const extra = days === null ? 0 : Number(days) * 86400;
|
|
41368
41490
|
return Number.isFinite(extra) ? seconds + extra : undefined;
|
|
41369
41491
|
}
|
|
41370
|
-
var
|
|
41492
|
+
var ASK_TIMEOUT_MS2 = 6000;
|
|
41371
41493
|
var init_process_time = __esm(() => {
|
|
41494
|
+
init_spawn();
|
|
41372
41495
|
init_windows_ask();
|
|
41373
|
-
run3 = promisify3(execFile3);
|
|
41374
41496
|
});
|
|
41375
41497
|
|
|
41376
41498
|
// src/prompts.ts
|
|
@@ -41510,7 +41632,7 @@ var init_prompts = __esm(() => {
|
|
|
41510
41632
|
});
|
|
41511
41633
|
|
|
41512
41634
|
// src/rename.ts
|
|
41513
|
-
import { existsSync as existsSync19, mkdirSync as mkdirSync12, readFileSync as readFileSync20, renameSync as
|
|
41635
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync12, readFileSync as readFileSync20, renameSync as renameSync4, unlinkSync as unlinkSync3, writeFileSync as writeFileSync13 } from "node:fs";
|
|
41514
41636
|
import { dirname as dirname17, posix as posix2 } from "node:path";
|
|
41515
41637
|
|
|
41516
41638
|
class Script {
|
|
@@ -42226,12 +42348,12 @@ function applyRewrites(rewrites) {
|
|
|
42226
42348
|
mkdirSync12(dirname17(rewrite.writeTo), { recursive: true });
|
|
42227
42349
|
const staging = `${rewrite.writeTo}.gdharness-rename`;
|
|
42228
42350
|
writeFileSync13(staging, rewrite.after, "utf8");
|
|
42229
|
-
|
|
42351
|
+
renameSync4(staging, rewrite.writeTo);
|
|
42230
42352
|
let movedUid = false;
|
|
42231
42353
|
if (rewrite.writeTo !== rewrite.absolute) {
|
|
42232
42354
|
unlinkSync3(rewrite.absolute);
|
|
42233
42355
|
if (existsSync19(`${rewrite.absolute}.uid`)) {
|
|
42234
|
-
|
|
42356
|
+
renameSync4(`${rewrite.absolute}.uid`, `${rewrite.writeTo}.uid`);
|
|
42235
42357
|
movedUid = true;
|
|
42236
42358
|
}
|
|
42237
42359
|
}
|
|
@@ -42244,7 +42366,7 @@ function applyRewrites(rewrites) {
|
|
|
42244
42366
|
if (rewrite.writeTo !== rewrite.absolute) {
|
|
42245
42367
|
unlinkSync3(rewrite.writeTo);
|
|
42246
42368
|
if (movedUid) {
|
|
42247
|
-
|
|
42369
|
+
renameSync4(`${rewrite.writeTo}.uid`, `${rewrite.absolute}.uid`);
|
|
42248
42370
|
}
|
|
42249
42371
|
}
|
|
42250
42372
|
} catch {}
|
|
@@ -42276,7 +42398,7 @@ var init_rename = __esm(() => {
|
|
|
42276
42398
|
});
|
|
42277
42399
|
|
|
42278
42400
|
// src/rename-symbol.ts
|
|
42279
|
-
import { existsSync as existsSync20, mkdirSync as mkdirSync13, readdirSync as readdirSync10, realpathSync as realpathSync5, rmSync as
|
|
42401
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync13, readdirSync as readdirSync10, realpathSync as realpathSync5, rmSync as rmSync11, writeFileSync as writeFileSync14 } from "node:fs";
|
|
42280
42402
|
import { basename as basename6, dirname as dirname18, join as join24, relative as relative4, resolve as resolve6 } from "node:path";
|
|
42281
42403
|
function resourcePathOf(projectPath, absolute) {
|
|
42282
42404
|
const inside = relative4(onDiskSpelling(projectPath), onDiskSpelling(absolute));
|
|
@@ -42726,7 +42848,7 @@ function writeLeftAloneDetail(projectPath, symbol, detail) {
|
|
|
42726
42848
|
`, "utf8");
|
|
42727
42849
|
const kept = readdirSync10(directory).filter((name) => name.endsWith(".json")).sort().reverse();
|
|
42728
42850
|
for (const old of kept.slice(10)) {
|
|
42729
|
-
|
|
42851
|
+
rmSync11(join24(directory, old), { force: true });
|
|
42730
42852
|
}
|
|
42731
42853
|
return path;
|
|
42732
42854
|
}
|
|
@@ -42766,7 +42888,7 @@ var init_rename_symbol = __esm(() => {
|
|
|
42766
42888
|
});
|
|
42767
42889
|
|
|
42768
42890
|
// src/restart-note.ts
|
|
42769
|
-
import { mkdirSync as mkdirSync14, readFileSync as readFileSync21, rmSync as
|
|
42891
|
+
import { mkdirSync as mkdirSync14, readFileSync as readFileSync21, rmSync as rmSync12, writeFileSync as writeFileSync15 } from "node:fs";
|
|
42770
42892
|
import { dirname as dirname19, join as join25 } from "node:path";
|
|
42771
42893
|
function restartNotePath(projectPath) {
|
|
42772
42894
|
return join25(projectPath, ".godot", "gdharness-restart.json");
|
|
@@ -42798,7 +42920,7 @@ function restartOwed(projectPath) {
|
|
|
42798
42920
|
}
|
|
42799
42921
|
function restartSettled(projectPath) {
|
|
42800
42922
|
try {
|
|
42801
|
-
|
|
42923
|
+
rmSync12(restartNotePath(projectPath), { force: true });
|
|
42802
42924
|
} catch (error) {
|
|
42803
42925
|
console.error(`[SERVER] Could not clear the restart note: ${errorMessage(error)}`);
|
|
42804
42926
|
}
|
|
@@ -42807,43 +42929,10 @@ var init_restart_note = __esm(() => {
|
|
|
42807
42929
|
init_errors();
|
|
42808
42930
|
});
|
|
42809
42931
|
|
|
42810
|
-
// src/whole-file.ts
|
|
42811
|
-
import { renameSync as renameSync4, rmSync as rmSync12, writeFileSync as writeFileSync16 } from "node:fs";
|
|
42812
|
-
function writeWhole(path, text) {
|
|
42813
|
-
const partial = `${path}.${process.pid}.partial`;
|
|
42814
|
-
try {
|
|
42815
|
-
writeFileSync16(partial, text, "utf8");
|
|
42816
|
-
} catch {
|
|
42817
|
-
return false;
|
|
42818
|
-
}
|
|
42819
|
-
const until = Date.now() + HELD_FILE_MS;
|
|
42820
|
-
for (;; ) {
|
|
42821
|
-
try {
|
|
42822
|
-
renameSync4(partial, path);
|
|
42823
|
-
return true;
|
|
42824
|
-
} catch (error) {
|
|
42825
|
-
const code = error.code ?? "";
|
|
42826
|
-
if (!HELD_BY_A_READER.has(code) || Date.now() >= until) {
|
|
42827
|
-
try {
|
|
42828
|
-
rmSync12(partial, { force: true });
|
|
42829
|
-
} catch {}
|
|
42830
|
-
return false;
|
|
42831
|
-
}
|
|
42832
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, HELD_FILE_RETRY_MS);
|
|
42833
|
-
}
|
|
42834
|
-
}
|
|
42835
|
-
}
|
|
42836
|
-
var HELD_FILE_MS = 1000, HELD_FILE_RETRY_MS = 10, HELD_BY_A_READER;
|
|
42837
|
-
var init_whole_file = __esm(() => {
|
|
42838
|
-
HELD_BY_A_READER = new Set(["EPERM", "EACCES", "EBUSY"]);
|
|
42839
|
-
});
|
|
42840
|
-
|
|
42841
42932
|
// src/run-record.ts
|
|
42842
|
-
import { execFile as execFile4 } from "node:child_process";
|
|
42843
42933
|
import { createHash as createHash4 } from "node:crypto";
|
|
42844
|
-
import { mkdirSync as mkdirSync15, openSync as openSync2, readdirSync as readdirSync11, readFileSync as readFileSync22, realpathSync as realpathSync6, rmSync as rmSync13, statSync as
|
|
42934
|
+
import { mkdirSync as mkdirSync15, openSync as openSync2, readdirSync as readdirSync11, readFileSync as readFileSync22, realpathSync as realpathSync6, rmSync as rmSync13, statSync as statSync10 } from "node:fs";
|
|
42845
42935
|
import { basename as basename7, dirname as dirname20, join as join26, resolve as resolve7 } from "node:path";
|
|
42846
|
-
import { promisify as promisify4 } from "node:util";
|
|
42847
42936
|
function runsDirectory() {
|
|
42848
42937
|
return join26(runtimeDirectory(), "runs");
|
|
42849
42938
|
}
|
|
@@ -43038,7 +43127,7 @@ async function runningAs(pid) {
|
|
|
43038
43127
|
const raw = readFileSync22(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
|
|
43039
43128
|
return raw === "" ? null : { kind: "commandLine", text: raw, ...when };
|
|
43040
43129
|
}
|
|
43041
|
-
const args = (await
|
|
43130
|
+
const args = (await runHidden("ps", ["-p", String(pid), "-o", "args="], { timeout: 15000 })).stdout.trim();
|
|
43042
43131
|
return args === "" ? null : { kind: "commandLine", text: args, ...when };
|
|
43043
43132
|
} catch {
|
|
43044
43133
|
return null;
|
|
@@ -43047,7 +43136,7 @@ async function runningAs(pid) {
|
|
|
43047
43136
|
async function listeningPid(port) {
|
|
43048
43137
|
try {
|
|
43049
43138
|
if (process.platform === "win32") {
|
|
43050
|
-
const { stdout } = await
|
|
43139
|
+
const { stdout } = await runHidden("netstat", ["-ano"], { timeout: 15000 });
|
|
43051
43140
|
return listeningPidInNetstat(stdout, port);
|
|
43052
43141
|
}
|
|
43053
43142
|
for (const [command, args, pattern] of [
|
|
@@ -43055,7 +43144,7 @@ async function listeningPid(port) {
|
|
|
43055
43144
|
["ss", ["-ltnpH", `sport = :${port}`], /pid=(\d+)/]
|
|
43056
43145
|
]) {
|
|
43057
43146
|
try {
|
|
43058
|
-
const said = (await
|
|
43147
|
+
const said = (await runHidden(command, [...args], { timeout: 15000 })).stdout.trim();
|
|
43059
43148
|
const found = pattern.exec(said);
|
|
43060
43149
|
if (found?.[1] !== undefined) {
|
|
43061
43150
|
return Number(found[1]);
|
|
@@ -43100,7 +43189,7 @@ async function startedAt(pid, windowsAnswer) {
|
|
|
43100
43189
|
return Number.isNaN(when) ? null : when;
|
|
43101
43190
|
}
|
|
43102
43191
|
try {
|
|
43103
|
-
const said = (await
|
|
43192
|
+
const said = (await runHidden("ps", ["-p", String(pid), "-o", "lstart="], { timeout: 15000 })).stdout.trim();
|
|
43104
43193
|
const when = Date.parse(said);
|
|
43105
43194
|
return said === "" || Number.isNaN(when) ? null : when;
|
|
43106
43195
|
} catch {
|
|
@@ -43109,10 +43198,8 @@ async function startedAt(pid, windowsAnswer) {
|
|
|
43109
43198
|
}
|
|
43110
43199
|
async function windowsImage(pid) {
|
|
43111
43200
|
try {
|
|
43112
|
-
const { stdout: csv } = await
|
|
43113
|
-
|
|
43114
|
-
timeout: 15000,
|
|
43115
|
-
windowsHide: true
|
|
43201
|
+
const { stdout: csv } = await runHidden("tasklist", ["/FI", `PID eq ${pid}`, "/FO", "CSV", "/NH"], {
|
|
43202
|
+
timeout: 15000
|
|
43116
43203
|
});
|
|
43117
43204
|
const image = /^"([^"]+)"/.exec(csv.trim())?.[1];
|
|
43118
43205
|
return image === undefined ? { why: "named no process under that number" } : { image };
|
|
@@ -43192,7 +43279,7 @@ function clearRunRecord(projectPath) {
|
|
|
43192
43279
|
}
|
|
43193
43280
|
function ageMs(path, now) {
|
|
43194
43281
|
try {
|
|
43195
|
-
return now -
|
|
43282
|
+
return now - statSync10(path).mtimeMs;
|
|
43196
43283
|
} catch {
|
|
43197
43284
|
return 0;
|
|
43198
43285
|
}
|
|
@@ -43223,15 +43310,15 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
43223
43310
|
} catch {}
|
|
43224
43311
|
}
|
|
43225
43312
|
}
|
|
43226
|
-
var
|
|
43313
|
+
var SHARED_NOTE = "run.json", SAME_RUN_WINDOW_MS = 1e4;
|
|
43227
43314
|
var init_run_record = __esm(() => {
|
|
43228
43315
|
init_errors();
|
|
43229
43316
|
init_paths();
|
|
43230
43317
|
init_process_children();
|
|
43231
43318
|
init_runtime_client();
|
|
43319
|
+
init_spawn();
|
|
43232
43320
|
init_whole_file();
|
|
43233
43321
|
init_windows_ask();
|
|
43234
|
-
execFileAsync = promisify4(execFile4);
|
|
43235
43322
|
});
|
|
43236
43323
|
|
|
43237
43324
|
// src/script-origins.ts
|
|
@@ -43507,7 +43594,6 @@ var init_server_instructions = __esm(() => {
|
|
|
43507
43594
|
});
|
|
43508
43595
|
|
|
43509
43596
|
// src/server.ts
|
|
43510
|
-
import { execFile as execFile5, spawn as spawn4 } from "node:child_process";
|
|
43511
43597
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
43512
43598
|
import {
|
|
43513
43599
|
appendFileSync as appendFileSync2,
|
|
@@ -43522,12 +43608,11 @@ import {
|
|
|
43522
43608
|
readFileSync as readFileSync24,
|
|
43523
43609
|
readSync as readSync2,
|
|
43524
43610
|
rmdirSync as rmdirSync2,
|
|
43525
|
-
statSync as
|
|
43611
|
+
statSync as statSync11
|
|
43526
43612
|
} from "node:fs";
|
|
43527
43613
|
import { basename as basename8, dirname as dirname21, extname as extname3, isAbsolute as isAbsolute4, join as join28, normalize as normalize2, relative as relative5 } from "node:path";
|
|
43528
43614
|
import { setTimeout as delay4 } from "node:timers/promises";
|
|
43529
43615
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
43530
|
-
import { promisify as promisify5 } from "node:util";
|
|
43531
43616
|
function editorRestartTimeoutMs() {
|
|
43532
43617
|
return waitFromEnv("GDHARNESS_EDITOR_RESTART_TIMEOUT_MS", 90000);
|
|
43533
43618
|
}
|
|
@@ -43620,6 +43705,9 @@ function captureDetail(payload) {
|
|
|
43620
43705
|
function patienceForFrames(frames, atLeast) {
|
|
43621
43706
|
return Math.max(atLeast, frames / SLOWEST_FRAME_RATE * 1000 + atLeast);
|
|
43622
43707
|
}
|
|
43708
|
+
function withDefined(fields) {
|
|
43709
|
+
return Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined));
|
|
43710
|
+
}
|
|
43623
43711
|
function longerWaitNote(tool, op, waitedMs) {
|
|
43624
43712
|
const spec = TOOL_SPECS.find((one) => one.name === tool);
|
|
43625
43713
|
if (spec !== undefined && argumentsOf(spec, op).includes("timeoutMs")) {
|
|
@@ -44063,6 +44151,31 @@ function notRunNote(notRun, failFast) {
|
|
|
44063
44151
|
const cases = `${notRun} case${notRun === 1 ? "" : "s"}`;
|
|
44064
44152
|
return failFast ? `This run stopped at the first failure in each suite it failed in, so ${cases} never ran and count as neither passed nor failed. Leave failFast out to run every case.` : `${cases} counted in ${notRun === 1 ? "its suite" : "their suites"} never ran and count as neither passed nor failed. failFast was not set, so it was not that: the suites below with notRun are the ones to read.`;
|
|
44065
44153
|
}
|
|
44154
|
+
function exportPresetRefusal(projectPath, preset) {
|
|
44155
|
+
let text;
|
|
44156
|
+
try {
|
|
44157
|
+
text = readFileSync24(join28(projectPath, "export_presets.cfg"), "utf8");
|
|
44158
|
+
} catch {
|
|
44159
|
+
return `This project has no export_presets.cfg, so it has no preset '${preset}' to export with. The editor writes the file when a preset is added in Project > Export.`;
|
|
44160
|
+
}
|
|
44161
|
+
const names = [];
|
|
44162
|
+
let inPreset = false;
|
|
44163
|
+
for (const line of text.split(/\r?\n/)) {
|
|
44164
|
+
const section = /^\s*\[([^\]]+)\]/.exec(line)?.[1];
|
|
44165
|
+
if (section !== undefined) {
|
|
44166
|
+
inPreset = /^preset\.\d+$/.test(section.trim());
|
|
44167
|
+
continue;
|
|
44168
|
+
}
|
|
44169
|
+
const named = inPreset ? /^\s*name\s*=\s*"((?:[^"\\]|\\.)*)"/.exec(line)?.[1] : undefined;
|
|
44170
|
+
if (named !== undefined) {
|
|
44171
|
+
names.push(named.replace(/\\(.)/g, "$1"));
|
|
44172
|
+
}
|
|
44173
|
+
}
|
|
44174
|
+
if (names.includes(preset)) {
|
|
44175
|
+
return null;
|
|
44176
|
+
}
|
|
44177
|
+
return names.length === 0 ? `export_presets.cfg names no preset, so there is no '${preset}' to export with. The editor adds one in Project > Export.` : `This project has no export preset named '${preset}'. Its presets are ${names.map((name) => `'${name}'`).join(", ")}.`;
|
|
44178
|
+
}
|
|
44066
44179
|
function exportAnswer(ending, asked, before, after) {
|
|
44067
44180
|
const { log } = ending;
|
|
44068
44181
|
const written = after !== null && after !== before;
|
|
@@ -44631,7 +44744,7 @@ function scriptsToDiagnose(projectPath, given) {
|
|
|
44631
44744
|
}
|
|
44632
44745
|
function isDirectory(path) {
|
|
44633
44746
|
try {
|
|
44634
|
-
return
|
|
44747
|
+
return statSync11(path).isDirectory();
|
|
44635
44748
|
} catch {
|
|
44636
44749
|
return false;
|
|
44637
44750
|
}
|
|
@@ -45500,7 +45613,15 @@ class GodotServer {
|
|
|
45500
45613
|
property: readNonEmptyString(args, "property") ?? ""
|
|
45501
45614
|
}, { tool: "runtime_inspect", op, patience });
|
|
45502
45615
|
case "accessibility":
|
|
45503
|
-
return await this.handleRuntimeCommand("get_accessibility",
|
|
45616
|
+
return await this.handleRuntimeCommand("get_accessibility", {
|
|
45617
|
+
...whichGame(args),
|
|
45618
|
+
...withDefined({
|
|
45619
|
+
node_path: readNonEmptyString(args, "nodePath"),
|
|
45620
|
+
says: readNonEmptyString(args, "says"),
|
|
45621
|
+
properties: readStringArray(args, "properties"),
|
|
45622
|
+
limit: readPositiveNumber(args, "limit")
|
|
45623
|
+
})
|
|
45624
|
+
}, {
|
|
45504
45625
|
tool: "runtime_inspect",
|
|
45505
45626
|
op,
|
|
45506
45627
|
patience: readPositiveNumber(args, "timeoutMs") ?? Math.max(30000, this.runtimeTimeoutMs())
|
|
@@ -45551,7 +45672,14 @@ class GodotServer {
|
|
|
45551
45672
|
...args["index"] === undefined ? {} : { index: args["index"] }
|
|
45552
45673
|
}, { tool: "runtime_input", op });
|
|
45553
45674
|
}
|
|
45554
|
-
|
|
45675
|
+
{
|
|
45676
|
+
const holding = readPositiveNumber(args, "holdFrames");
|
|
45677
|
+
return await this.handleRuntimeCommand(`inject_${op}`, args, {
|
|
45678
|
+
tool: "runtime_input",
|
|
45679
|
+
op,
|
|
45680
|
+
...holding === undefined ? {} : { patience: patienceForFrames(holding, this.runtimeTimeoutMs()) }
|
|
45681
|
+
});
|
|
45682
|
+
}
|
|
45555
45683
|
case "runtime_wait":
|
|
45556
45684
|
return await this.handleRuntimeWait(op, args);
|
|
45557
45685
|
case "debug_breakpoint": {
|
|
@@ -46334,6 +46462,10 @@ class GodotServer {
|
|
|
46334
46462
|
if (!output.ok) {
|
|
46335
46463
|
return this.createErrorResponse(output.reason, PATH_SOLUTIONS);
|
|
46336
46464
|
}
|
|
46465
|
+
const noPreset = exportPresetRefusal(project.value.path, preset);
|
|
46466
|
+
if (noPreset !== null) {
|
|
46467
|
+
return this.createErrorResponse(noPreset, ["project_export list reads the presets the project has"]);
|
|
46468
|
+
}
|
|
46337
46469
|
const engine = await this.engine();
|
|
46338
46470
|
if (!engine.ok) {
|
|
46339
46471
|
return engine.response;
|
|
@@ -46357,7 +46489,7 @@ class GodotServer {
|
|
|
46357
46489
|
output.absolutePath
|
|
46358
46490
|
];
|
|
46359
46491
|
this.logDebug(`Exporting: ${engine.value} ${exportArgs.join(" ")}`);
|
|
46360
|
-
const writtenAt = () => existsSync21(output.absolutePath) ?
|
|
46492
|
+
const writtenAt = () => existsSync21(output.absolutePath) ? statSync11(output.absolutePath).mtimeMs : null;
|
|
46361
46493
|
const before = writtenAt();
|
|
46362
46494
|
const freed = freeLibraryCopies(project.value.path);
|
|
46363
46495
|
if (freed.setAside.length > 0 || freed.swept.length > 0) {
|
|
@@ -46702,7 +46834,7 @@ class GodotServer {
|
|
|
46702
46834
|
}
|
|
46703
46835
|
async godotVersion(godotPath) {
|
|
46704
46836
|
try {
|
|
46705
|
-
const { stdout } = await
|
|
46837
|
+
const { stdout } = await runHidden(godotPath, ["--version"], { timeout: 1e4 });
|
|
46706
46838
|
this.lastGodotVersion = stdout.trim();
|
|
46707
46839
|
return this.lastGodotVersion;
|
|
46708
46840
|
} catch (error) {
|
|
@@ -46763,6 +46895,10 @@ class GodotServer {
|
|
|
46763
46895
|
return markIfStale(answer, this.godotBridge.getStatus().addonVersion, SERVER_VERSION, this.godotBridge.getStatus().addonDigest, shippedEditorDigest(), this.editorCodeOnDisk());
|
|
46764
46896
|
}
|
|
46765
46897
|
async diagnosticsOf(args) {
|
|
46898
|
+
const waited = await this.waitForTheEditorOpenedHere(args, []);
|
|
46899
|
+
if (waited !== null) {
|
|
46900
|
+
return { refusal: waited, perScript: false };
|
|
46901
|
+
}
|
|
46766
46902
|
const answer = await this.handleLSP("lsp_get_diagnostics", args);
|
|
46767
46903
|
const payload = asParams(JSON.parse(answer.content[0]?.text ?? "{}"));
|
|
46768
46904
|
const refused = this.languageServerRefusal(payload, "Diagnostics");
|
|
@@ -46847,9 +46983,34 @@ class GodotServer {
|
|
|
46847
46983
|
const recorded = readClassNote(projectPath)?.shortListEditorPid;
|
|
46848
46984
|
return recorded !== undefined && recorded === (this.godotBridge.getStatus().editorPid ?? null);
|
|
46849
46985
|
}
|
|
46986
|
+
unseenBeforeAnUnfinishedScan(projectPath) {
|
|
46987
|
+
const noted = readClassNote(projectPath)?.unseenBeforeAnUnfinishedScan;
|
|
46988
|
+
if (noted === undefined || noted.editorPid !== (this.godotBridge.getStatus().editorPid ?? null)) {
|
|
46989
|
+
return [];
|
|
46990
|
+
}
|
|
46991
|
+
return [...noted.classes];
|
|
46992
|
+
}
|
|
46993
|
+
noteUnseenBeforeAnUnfinishedScan(projectPath, classes) {
|
|
46994
|
+
const note = readClassNote(projectPath);
|
|
46995
|
+
const held = note?.unseenBeforeAnUnfinishedScan;
|
|
46996
|
+
if (classes.length === 0 && held === undefined) {
|
|
46997
|
+
return;
|
|
46998
|
+
}
|
|
46999
|
+
const { unseenBeforeAnUnfinishedScan: _replaced, ...rest } = note ?? { rebuiltWith: [] };
|
|
47000
|
+
writeClassNote(projectPath, {
|
|
47001
|
+
...rest,
|
|
47002
|
+
...classes.length === 0 ? {} : {
|
|
47003
|
+
unseenBeforeAnUnfinishedScan: {
|
|
47004
|
+
editorPid: this.godotBridge.getStatus().editorPid ?? null,
|
|
47005
|
+
classes: [...classes].sort()
|
|
47006
|
+
}
|
|
47007
|
+
}
|
|
47008
|
+
});
|
|
47009
|
+
}
|
|
46850
47010
|
noteAShortListEditor(projectPath) {
|
|
46851
47011
|
const note = readClassNote(projectPath);
|
|
46852
47012
|
writeClassNote(projectPath, {
|
|
47013
|
+
...note,
|
|
46853
47014
|
rebuiltWith: note?.rebuiltWith ?? [],
|
|
46854
47015
|
shortListEditorPid: this.godotBridge.getStatus().editorPid ?? null
|
|
46855
47016
|
});
|
|
@@ -46874,7 +47035,17 @@ class GodotServer {
|
|
|
46874
47035
|
async handleLSP(toolName, args) {
|
|
46875
47036
|
return handleLSPTool(await this.lsp(), toolName, args);
|
|
46876
47037
|
}
|
|
47038
|
+
async waitForTheEditorOpenedHere(args, instead) {
|
|
47039
|
+
if (this.godotBridge.isConnected() || this.launchedEditor === null || !await this.launchedEditorIsUp()) {
|
|
47040
|
+
return null;
|
|
47041
|
+
}
|
|
47042
|
+
return await this.noEditorYet(args, instead);
|
|
47043
|
+
}
|
|
46877
47044
|
async askScriptInfo(toolName, asked, args) {
|
|
47045
|
+
const waited = await this.waitForTheEditorOpenedHere(args, []);
|
|
47046
|
+
if (waited !== null) {
|
|
47047
|
+
return waited;
|
|
47048
|
+
}
|
|
46878
47049
|
const answer = await this.handleLSP(toolName, args);
|
|
46879
47050
|
const refused = this.languageServerRefusal(asParams(JSON.parse(answer.content[0]?.text ?? "{}")), asked);
|
|
46880
47051
|
return refused === null ? answer : refused.refusal;
|
|
@@ -47600,6 +47771,17 @@ class GodotServer {
|
|
|
47600
47771
|
if (sceneToRun && !sceneToRun.ok) {
|
|
47601
47772
|
return this.createErrorResponse(sceneToRun.reason, PATH_SOLUTIONS);
|
|
47602
47773
|
}
|
|
47774
|
+
const build = readNonEmptyString(args, "executable");
|
|
47775
|
+
const exported = build === undefined ? null : exportedBuild(project.value.path, build);
|
|
47776
|
+
if (exported !== null && !exported.ok) {
|
|
47777
|
+
return this.createErrorResponse(exported.reason, PATH_SOLUTIONS);
|
|
47778
|
+
}
|
|
47779
|
+
if (exported !== null && scene !== undefined) {
|
|
47780
|
+
return this.createErrorResponse("An exported build runs the main scene its pack holds, and takes no scene: leave scene out, or run the project without executable.");
|
|
47781
|
+
}
|
|
47782
|
+
if (exported !== null && readBoolean(args, "profile") === true) {
|
|
47783
|
+
return this.createErrorResponse("A profile is read through the debugger, and an exported build is started without one, since a release build quits when given --remote-debug. Profile the project without executable.");
|
|
47784
|
+
}
|
|
47603
47785
|
const given = this.gameArguments(args);
|
|
47604
47786
|
if (!given.ok) {
|
|
47605
47787
|
return given.response;
|
|
@@ -47616,13 +47798,13 @@ class GodotServer {
|
|
|
47616
47798
|
if (!asked.ok) {
|
|
47617
47799
|
return asked.response;
|
|
47618
47800
|
}
|
|
47619
|
-
if (!sceneToRun && !hasMainScene(project.value.file)) {
|
|
47801
|
+
if (exported === null && !sceneToRun && !hasMainScene(project.value.file)) {
|
|
47620
47802
|
return this.createErrorResponse("The project sets no main scene, so there is nothing to run.", [
|
|
47621
47803
|
"Pass scene to run one scene",
|
|
47622
47804
|
"Choose the main scene with project_settings set_main_scene"
|
|
47623
47805
|
]);
|
|
47624
47806
|
}
|
|
47625
|
-
const engine = await this.engine();
|
|
47807
|
+
const engine = exported?.ok === true ? { ok: true, value: exported.absolutePath } : await this.engine();
|
|
47626
47808
|
if (!engine.ok) {
|
|
47627
47809
|
return engine.response;
|
|
47628
47810
|
}
|
|
@@ -47660,11 +47842,11 @@ class GodotServer {
|
|
|
47660
47842
|
const editorWouldPlay = !headless || editorPlaysHeadless(project.value.file);
|
|
47661
47843
|
const silent = resolveSilent(args["silent"], process.env) && !headless;
|
|
47662
47844
|
const profiled = readBoolean(args, "profile") === true;
|
|
47663
|
-
if (this.godotBridge.isConnected() && editorWouldPlay && given.value.length === 0 && flags.value.length === 0 && !needsItsOwnProcess(asked.value) && !silent) {
|
|
47845
|
+
if (exported === null && this.godotBridge.isConnected() && editorWouldPlay && given.value.length === 0 && flags.value.length === 0 && !needsItsOwnProcess(asked.value) && !silent) {
|
|
47664
47846
|
return await this.playThroughEditor(sceneArgument, refreshed.value, scanned, project.value.path, alreadyPlaying, runtimeWaitMs, ended, profiled);
|
|
47665
47847
|
}
|
|
47666
47848
|
const cmdArgs = runArguments({
|
|
47667
|
-
projectPath: project.value.path,
|
|
47849
|
+
projectPath: exported === null ? project.value.path : null,
|
|
47668
47850
|
headless,
|
|
47669
47851
|
scene: sceneArgument,
|
|
47670
47852
|
userArgs: given.value,
|
|
@@ -47672,7 +47854,7 @@ class GodotServer {
|
|
|
47672
47854
|
silent
|
|
47673
47855
|
});
|
|
47674
47856
|
this.logDebug(`Running Godot project: ${engine.value} ${cmdArgs.join(" ")}`);
|
|
47675
|
-
const started = await this.launchKeptGame(engine.value, cmdArgs, project.value.path, needsItsOwnProcess(asked.value) ? environmentFor({ ...asked.value, runtimeDirectory: runtimeDirectory() }) : undefined, headless || args["visible"] === true ? undefined : HIDDEN_DESKTOP, profiled);
|
|
47857
|
+
const started = await this.launchKeptGame(engine.value, cmdArgs, project.value.path, needsItsOwnProcess(asked.value) ? environmentFor({ ...asked.value, runtimeDirectory: runtimeDirectory() }) : undefined, headless || args["visible"] === true ? undefined : HIDDEN_DESKTOP, profiled, exported === null);
|
|
47676
47858
|
if ("error" in started) {
|
|
47677
47859
|
return this.createErrorResponse(`The game could not be started: ${started.error}`, [
|
|
47678
47860
|
"editor_status says which Godot this server runs and whether it answers"
|
|
@@ -47681,7 +47863,7 @@ class GodotServer {
|
|
|
47681
47863
|
started.announcedBefore = alreadyPlaying;
|
|
47682
47864
|
started.bootScene = runtimeWaitMs.scene;
|
|
47683
47865
|
this.activeProcess = started;
|
|
47684
|
-
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}` : "";
|
|
47866
|
+
const spawnedInstead = exported !== null ? " An exported build is started without a debugger, since a release build quits when given one, so breakpoints and debug_state do not reach it." : 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}` : "";
|
|
47685
47867
|
const endedForThis = endedToStartThis(ended);
|
|
47686
47868
|
const stranded = wereHereFirst.filter((one) => one.pid !== ended?.pid && alive(one.pid));
|
|
47687
47869
|
const handed = this.dapHoldingBreakpointsOf(project.value.path).breakpointsHeld();
|
|
@@ -47691,6 +47873,7 @@ class GodotServer {
|
|
|
47691
47873
|
through: "gdharness",
|
|
47692
47874
|
alsoRunning: stranded.length === 0 ? undefined : stranded.map((one) => one.pid),
|
|
47693
47875
|
pid: started.pid,
|
|
47876
|
+
executable: exported?.ok === true ? exported.relativePath : undefined,
|
|
47694
47877
|
arguments: cmdArgs,
|
|
47695
47878
|
argsNote: engineFlagsNote(given.value),
|
|
47696
47879
|
transcript: started.transcript,
|
|
@@ -47702,7 +47885,7 @@ class GodotServer {
|
|
|
47702
47885
|
endedPreviousRun: endedPreviousRun(ended),
|
|
47703
47886
|
previousRunLeft: previousRunLeft(ended),
|
|
47704
47887
|
...previousRunHow(ended),
|
|
47705
|
-
runtime: await this.runtimeUp(project.value.path, alreadyPlaying, runtimeWaitMs, given.value.length > 0),
|
|
47888
|
+
runtime: exported !== null ? undefined : await this.runtimeUp(project.value.path, alreadyPlaying, runtimeWaitMs, given.value.length > 0),
|
|
47706
47889
|
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}`
|
|
47707
47890
|
});
|
|
47708
47891
|
}
|
|
@@ -47793,7 +47976,7 @@ class GodotServer {
|
|
|
47793
47976
|
}
|
|
47794
47977
|
let announcedAt;
|
|
47795
47978
|
try {
|
|
47796
|
-
announcedAt =
|
|
47979
|
+
announcedAt = statSync11(endpoint.file).mtimeMs;
|
|
47797
47980
|
} catch {
|
|
47798
47981
|
return;
|
|
47799
47982
|
}
|
|
@@ -48142,7 +48325,10 @@ class GodotServer {
|
|
|
48142
48325
|
return gone ? "gone" : "lingering";
|
|
48143
48326
|
}
|
|
48144
48327
|
spawnGame(godotPath, cmdArgs, env) {
|
|
48145
|
-
const child =
|
|
48328
|
+
const child = spawnHidden(godotPath, cmdArgs, {
|
|
48329
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
48330
|
+
...env ? { env } : {}
|
|
48331
|
+
});
|
|
48146
48332
|
const cancelled = callSignal();
|
|
48147
48333
|
if (cancelled !== undefined) {
|
|
48148
48334
|
const end = () => {
|
|
@@ -48215,7 +48401,7 @@ class GodotServer {
|
|
|
48215
48401
|
});
|
|
48216
48402
|
return Object.assign(started, { settled });
|
|
48217
48403
|
}
|
|
48218
|
-
async launchKeptGame(godotPath, cmdArgs, projectPath, env, desktop, profiled = false) {
|
|
48404
|
+
async launchKeptGame(godotPath, cmdArgs, projectPath, env, desktop, profiled = false, debugged = true) {
|
|
48219
48405
|
const startedAt = Date.now();
|
|
48220
48406
|
const transcript = openTranscript(startedAt);
|
|
48221
48407
|
closeSync2(transcript.fd);
|
|
@@ -48231,7 +48417,7 @@ class GodotServer {
|
|
|
48231
48417
|
projectPath,
|
|
48232
48418
|
...this.servedBy(),
|
|
48233
48419
|
...profile === undefined ? {} : { profile },
|
|
48234
|
-
breakpoints: this.breakpointsForAGame(projectPath)
|
|
48420
|
+
...debugged ? { breakpoints: this.breakpointsForAGame(projectPath) } : { debugged: false }
|
|
48235
48421
|
},
|
|
48236
48422
|
...desktop === undefined ? {} : { desktop }
|
|
48237
48423
|
});
|
|
@@ -49195,8 +49381,8 @@ class GodotServer {
|
|
|
49195
49381
|
try {
|
|
49196
49382
|
const status = asParams(await this.godotBridge.invokeTool("scan_status", {}));
|
|
49197
49383
|
return status["scanning"] === true || status["importing"] === true;
|
|
49198
|
-
} catch {
|
|
49199
|
-
return
|
|
49384
|
+
} catch (error) {
|
|
49385
|
+
return error instanceof BridgeTimeout;
|
|
49200
49386
|
}
|
|
49201
49387
|
}
|
|
49202
49388
|
async handleRescanFilesystem(args) {
|
|
@@ -49207,7 +49393,7 @@ class GodotServer {
|
|
|
49207
49393
|
let writtenBefore = null;
|
|
49208
49394
|
let blind = { unseen: [] };
|
|
49209
49395
|
let waitedForEditorMs = 0;
|
|
49210
|
-
let
|
|
49396
|
+
let startUnanswered = false;
|
|
49211
49397
|
let completedBefore;
|
|
49212
49398
|
let uidsReread = [];
|
|
49213
49399
|
let uidsDuplicatedOnDisk = [];
|
|
@@ -49224,7 +49410,11 @@ class GodotServer {
|
|
|
49224
49410
|
before = projectPath === "" ? null : cachedClasses(projectPath);
|
|
49225
49411
|
writtenBefore = projectPath === "" ? null : cacheWrittenAt(projectPath);
|
|
49226
49412
|
blind = await this.classesTheEditorCannotSee(args);
|
|
49227
|
-
first = await this.
|
|
49413
|
+
const first = await this.askViaBridge("rescan_filesystem", args);
|
|
49414
|
+
if (first instanceof BridgeTimeout) {
|
|
49415
|
+
startUnanswered = true;
|
|
49416
|
+
break;
|
|
49417
|
+
}
|
|
49228
49418
|
if (first.isError) {
|
|
49229
49419
|
return first;
|
|
49230
49420
|
}
|
|
@@ -49247,7 +49437,17 @@ class GodotServer {
|
|
|
49247
49437
|
let idleSince = null;
|
|
49248
49438
|
while (Date.now() - started < timeoutMs) {
|
|
49249
49439
|
await new Promise((settle) => setTimeout(settle, 100));
|
|
49250
|
-
|
|
49440
|
+
let status;
|
|
49441
|
+
try {
|
|
49442
|
+
status = asParams(await this.godotBridge.invokeTool("rescan_filesystem", { ...args, statusOnly: true }));
|
|
49443
|
+
} catch (error) {
|
|
49444
|
+
if (!(error instanceof BridgeTimeout)) {
|
|
49445
|
+
throw error;
|
|
49446
|
+
}
|
|
49447
|
+
busy = true;
|
|
49448
|
+
idleSince = null;
|
|
49449
|
+
continue;
|
|
49450
|
+
}
|
|
49251
49451
|
const pending = Boolean(status["pending"]) && Date.now() - started < SCAN_START_MS;
|
|
49252
49452
|
const completed = readNumber(status, "scansCompleted");
|
|
49253
49453
|
const notYetOver = completedBefore !== undefined && completed !== undefined && completed <= completedBefore;
|
|
@@ -49330,12 +49530,20 @@ class GodotServer {
|
|
|
49330
49530
|
}
|
|
49331
49531
|
const checked = busy ? { unseen: [] } : await this.classesTheEditorCannotSee(args);
|
|
49332
49532
|
const unseen = checked.unseen;
|
|
49333
|
-
const
|
|
49533
|
+
const carried = projectPath === "" ? [] : this.unseenBeforeAnUnfinishedScan(projectPath);
|
|
49534
|
+
const unseenBefore = [...new Set([...blind.unseen.map((one) => one.className), ...carried])];
|
|
49535
|
+
const broughtIn = busy ? [] : unseenBefore.filter((name) => !unseen.some((still) => still.className === name));
|
|
49536
|
+
if (projectPath !== "") {
|
|
49537
|
+
this.noteUnseenBeforeAnUnfinishedScan(projectPath, busy ? unseenBefore : []);
|
|
49538
|
+
}
|
|
49334
49539
|
const dependents = busy || projectPath === "" || broughtIn.length === 0 ? null : await this.reloadDependents(projectPath, broughtIn);
|
|
49335
49540
|
const stillGone = lost.filter((name) => !restored.includes(name));
|
|
49336
49541
|
const notes = [];
|
|
49337
49542
|
if (busy) {
|
|
49338
|
-
notes.push("The editor was still scanning or importing when the wait ran out, so new files may not be visible yet.");
|
|
49543
|
+
notes.push(startUnanswered ? "The editor took the rescan in and did not answer it before the wait ran out, which is an editor importing: a hidden editor imports on the thread that answers, and answers nothing until it is done. It carries the scan out once it is free, so new files may not be visible yet." : "The editor was still scanning or importing when the wait ran out, so new files may not be visible yet.");
|
|
49544
|
+
if (unseenBefore.length > 0) {
|
|
49545
|
+
notes.push(`Scripts naming ${unseenBefore.join(", ")}, which the editor could not see before this scan, were compiled without them and stay that way until reloaded. editor_rescan again once the editor is idle reloads them, for any server on this project, until this editor is restarted.`);
|
|
49546
|
+
}
|
|
49339
49547
|
} else if (stillGone.length > 0) {
|
|
49340
49548
|
notes.push("The scan wrote the class cache from the list this editor is holding, that list is shorter than the file was, and rebuilding the cache from the files did not bring these back. Every engine that reads the cache next, including a test run, will report them as unknown identifiers. Restart the editor with editor_launch restart before scanning again.");
|
|
49341
49549
|
} else if (lost.length > 0) {
|
|
@@ -49453,8 +49661,9 @@ class GodotServer {
|
|
|
49453
49661
|
const droppedByTheEditor = lost.filter((name) => !cameBack.includes(name));
|
|
49454
49662
|
const seenLosing = droppedByTheEditor.length > 0 && this.godotBridge.isConnected();
|
|
49455
49663
|
writeClassNote(projectPath, {
|
|
49664
|
+
...noted,
|
|
49456
49665
|
rebuiltWith: [...after.keys()].sort(),
|
|
49457
|
-
...seenLosing ? { shortListEditorPid: this.godotBridge.getStatus().editorPid ?? null } :
|
|
49666
|
+
...seenLosing ? { shortListEditorPid: this.godotBridge.getStatus().editorPid ?? null } : {}
|
|
49458
49667
|
});
|
|
49459
49668
|
const notes = [];
|
|
49460
49669
|
if (seenLosing) {
|
|
@@ -49528,6 +49737,10 @@ class GodotServer {
|
|
|
49528
49737
|
if (moveTo !== null && !moveTo.ok) {
|
|
49529
49738
|
return this.createErrorResponse(moveTo.reason);
|
|
49530
49739
|
}
|
|
49740
|
+
const waited = await this.waitForTheEditorOpenedHere(args, []);
|
|
49741
|
+
if (waited !== null) {
|
|
49742
|
+
return waited;
|
|
49743
|
+
}
|
|
49531
49744
|
const languageServer = {
|
|
49532
49745
|
problem: null,
|
|
49533
49746
|
initialized: false
|
|
@@ -49770,6 +49983,16 @@ class GodotServer {
|
|
|
49770
49983
|
]);
|
|
49771
49984
|
}
|
|
49772
49985
|
async handleViaBridge(toolName, args, askedAs) {
|
|
49986
|
+
const answered = await this.askViaBridge(toolName, args, askedAs);
|
|
49987
|
+
if (!(answered instanceof BridgeTimeout)) {
|
|
49988
|
+
return answered;
|
|
49989
|
+
}
|
|
49990
|
+
return this.createErrorResponse(`The editor took ${toolName} in and did not answer within ${String(answered.waitedMs / 1000)}s. An editor importing files answers nothing until the import is over, and a hidden one imports on the thread that answers.`, [
|
|
49991
|
+
"editor_status says whether it is scanning or importing",
|
|
49992
|
+
"The editor still carries the request out once it is free, so read what it changed before asking again"
|
|
49993
|
+
]);
|
|
49994
|
+
}
|
|
49995
|
+
async askViaBridge(toolName, args, askedAs) {
|
|
49773
49996
|
if (!this.godotBridge.isConnected()) {
|
|
49774
49997
|
const alternative = askedAs !== undefined && Object.hasOwn(EDITOR_FREE_INSTEAD, askedAs) ? EDITOR_FREE_INSTEAD[askedAs] : undefined;
|
|
49775
49998
|
const refused = await this.noEditorYet(args, alternative === undefined ? [] : [alternative]);
|
|
@@ -49788,6 +50011,9 @@ class GodotServer {
|
|
|
49788
50011
|
try {
|
|
49789
50012
|
return this.jsonTextResponse(markIfStale(await this.godotBridge.invokeTool(toolName, args), this.godotBridge.getStatus().addonVersion, SERVER_VERSION, this.godotBridge.getStatus().addonDigest, shippedEditorDigest(), this.editorCodeOnDisk()));
|
|
49790
50013
|
} catch (error) {
|
|
50014
|
+
if (error instanceof BridgeTimeout) {
|
|
50015
|
+
return error;
|
|
50016
|
+
}
|
|
49791
50017
|
const message = errorMessage(error);
|
|
49792
50018
|
if (message.includes(`Unknown tool: ${toolName}`)) {
|
|
49793
50019
|
const status = this.godotBridge.getStatus();
|
|
@@ -49857,7 +50083,7 @@ class GodotServer {
|
|
|
49857
50083
|
return !run.announcedBefore.has(one.pid);
|
|
49858
50084
|
}
|
|
49859
50085
|
try {
|
|
49860
|
-
return
|
|
50086
|
+
return statSync11(one.file).mtimeMs >= run.startedAt;
|
|
49861
50087
|
} catch {
|
|
49862
50088
|
return false;
|
|
49863
50089
|
}
|
|
@@ -50102,7 +50328,7 @@ async function runGodotServer() {
|
|
|
50102
50328
|
const server = new GodotServer;
|
|
50103
50329
|
await server.run();
|
|
50104
50330
|
}
|
|
50105
|
-
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250,
|
|
50331
|
+
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, __dirname2, PARENT_ASKED_AFTER_MS = 5000, EDITOR_SERVERS_START_MS = 20000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, 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.", 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.", PLAY_STARTS_WITHIN_MS = 30000, KEEPER_ANSWER_MS = 15000, KEEPER_DEBUG_MS, EXIT_REPORTED_WITHIN_MS = 2000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, PROFILE_SEND_MS = 5000, MOST_GROUPS = 20, RUNTIME_LISTENING_LINE = "[gdharness] runtime listening on ", VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CONSOLE_WATCH_MS = 250, BOUNDARY_WAIT_MS = 2000, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, STREAMS_GRACE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, SCAN_SETTLED_MS = 500, REIMPORT_TIMEOUT_MS = 300000, EDITOR_FREE_INSTEAD, OPS_ELSEWHERE, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, TEST_HEARTBEAT_MS = 20000, DEPENDED_FAILED = "Failed to compile depended scripts", WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/", PROFILE_FUNCTIONS = 30, PROFILER_ON_MS = 3000, PROPERTY_PLACES = "It is a property, so each place says whether it writes it (access write), which runs its set block, or reads it (access read), which runs its get block: withinCalls on a read says nothing of the set block, nor on a write of the get block unless the write is a compound one such as +=. Inside the property's own blocks its name is the value stored, which the engine reads and writes without running them, so those lines are not listed.", CALLERS_FOUND_BY_NAME = "Found by name in the project's scripts, since the engine's profiler counts calls and time per function and records nothing about who made them: a method of the same name on another class is listed too, unless one class was asked about and it is called through another class by name or through a variable declared as one. Each place carries the profile's numbers for the function it is written in.", MOST_ENGINE_ENTRIES = 200, ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd", EXTERNAL_MEMBER = 'Could not resolve external class member "', STATIC_LIST_HINT;
|
|
50106
50332
|
var init_server = __esm(() => {
|
|
50107
50333
|
init_dist();
|
|
50108
50334
|
init_stdio();
|
|
@@ -50155,10 +50381,10 @@ var init_server = __esm(() => {
|
|
|
50155
50381
|
init_server_instructions();
|
|
50156
50382
|
init_server_version();
|
|
50157
50383
|
init_setup();
|
|
50384
|
+
init_spawn();
|
|
50158
50385
|
init_tool_definitions();
|
|
50159
50386
|
init_tool_reference();
|
|
50160
50387
|
init_update_check();
|
|
50161
|
-
run4 = promisify5(execFile5);
|
|
50162
50388
|
__dirname2 = dirname21(fileURLToPath5(import.meta.url));
|
|
50163
50389
|
KEEPER_DEBUG_MS = PAUSE_MS + STEP_MS + 2 * READ_MS;
|
|
50164
50390
|
CONSOLE_WRAPPER = /_console(\.exe)?$/i;
|
|
@@ -50222,7 +50448,7 @@ import { existsSync as existsSync22 } from "node:fs";
|
|
|
50222
50448
|
import { join as join29, resolve as resolve8 } from "node:path";
|
|
50223
50449
|
|
|
50224
50450
|
// src/harnesses.ts
|
|
50225
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, statSync as
|
|
50451
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, statSync as statSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
50226
50452
|
import { homedir as homedir2 } from "node:os";
|
|
50227
50453
|
import { basename as basename2, dirname as dirname3, join as join4, relative as relative2, resolve as resolve2 } from "node:path";
|
|
50228
50454
|
import process4 from "node:process";
|
|
@@ -50950,7 +51176,7 @@ function mainCheckoutOf(projectPath) {
|
|
|
50950
51176
|
top = up;
|
|
50951
51177
|
}
|
|
50952
51178
|
const marker = join4(top, ".git");
|
|
50953
|
-
if (!
|
|
51179
|
+
if (!statSync3(marker).isFile()) {
|
|
50954
51180
|
return null;
|
|
50955
51181
|
}
|
|
50956
51182
|
const pointer = /^gitdir:\s*(.+)$/m.exec(readFileSync2(marker, "utf8"))?.[1]?.trim();
|
|
@@ -50979,7 +51205,7 @@ function worktreeCheckoutsOf(projectPath) {
|
|
|
50979
51205
|
top = up;
|
|
50980
51206
|
}
|
|
50981
51207
|
const listed = join4(top, ".git", "worktrees");
|
|
50982
|
-
if (!
|
|
51208
|
+
if (!statSync3(join4(top, ".git")).isDirectory() || !existsSync4(listed)) {
|
|
50983
51209
|
return [];
|
|
50984
51210
|
}
|
|
50985
51211
|
const below = relative2(top, resolve2(projectPath));
|