gdharness 1.2.27 → 1.2.29
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 +804 -464
- package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +378 -8
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +29 -7
- 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 +25 -3
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +120 -0
- package/build/index.js +669 -342
- 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,26 +8251,109 @@ 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
|
|
|
8257
|
+
// src/library-copies.ts
|
|
8258
|
+
import { existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync3, renameSync, unlinkSync } from "node:fs";
|
|
8259
|
+
import { basename as basename3, dirname as dirname4, isAbsolute as isAbsolute2, join as join5 } from "node:path";
|
|
8260
|
+
function freeLibraryCopies(projectPath, platform = process.platform) {
|
|
8261
|
+
const setAside = [];
|
|
8262
|
+
const swept = [];
|
|
8263
|
+
if (platform !== "win32") {
|
|
8264
|
+
return { setAside, swept };
|
|
8265
|
+
}
|
|
8266
|
+
for (const library of windowsLibraries(projectPath)) {
|
|
8267
|
+
const folder = dirname4(library);
|
|
8268
|
+
const copy = `~${basename3(library)}`;
|
|
8269
|
+
if (existsSync5(join5(folder, copy))) {
|
|
8270
|
+
const aside = join5(folder, `${copy}.aside-${process.pid}-${Date.now()}`);
|
|
8271
|
+
try {
|
|
8272
|
+
renameSync(join5(folder, copy), aside);
|
|
8273
|
+
setAside.push(aside);
|
|
8274
|
+
} catch {}
|
|
8275
|
+
}
|
|
8276
|
+
let names;
|
|
8277
|
+
try {
|
|
8278
|
+
names = readdirSync3(folder);
|
|
8279
|
+
} catch {
|
|
8280
|
+
continue;
|
|
8281
|
+
}
|
|
8282
|
+
for (const name of names) {
|
|
8283
|
+
if (name.length > copy.length && name.toLowerCase().startsWith(copy.toLowerCase())) {
|
|
8284
|
+
try {
|
|
8285
|
+
unlinkSync(join5(folder, name));
|
|
8286
|
+
swept.push(join5(folder, name));
|
|
8287
|
+
} catch {}
|
|
8288
|
+
}
|
|
8289
|
+
}
|
|
8290
|
+
}
|
|
8291
|
+
return { setAside, swept };
|
|
8292
|
+
}
|
|
8293
|
+
function windowsLibraries(projectPath) {
|
|
8294
|
+
const listed = readOrNull(join5(projectPath, ".godot", "extension_list.cfg"));
|
|
8295
|
+
if (listed === null) {
|
|
8296
|
+
return [];
|
|
8297
|
+
}
|
|
8298
|
+
const libraries = new Set;
|
|
8299
|
+
for (const line of listed.split(/\r?\n/)) {
|
|
8300
|
+
const manifest = resolveResource(projectPath, projectPath, line.trim());
|
|
8301
|
+
const text = manifest === null ? null : readOrNull(manifest);
|
|
8302
|
+
if (manifest === null || text === null) {
|
|
8303
|
+
continue;
|
|
8304
|
+
}
|
|
8305
|
+
let inLibraries = false;
|
|
8306
|
+
for (const row of text.split(/\r?\n/)) {
|
|
8307
|
+
const section = /^\s*\[([^\]]+)\]/.exec(row)?.[1];
|
|
8308
|
+
if (section !== undefined) {
|
|
8309
|
+
inLibraries = section.trim() === "libraries";
|
|
8310
|
+
continue;
|
|
8311
|
+
}
|
|
8312
|
+
const named = inLibraries ? /^\s*windows[\w.]*\s*=\s*"([^"]+)"/.exec(row)?.[1] : undefined;
|
|
8313
|
+
const library = named === undefined ? null : resolveResource(projectPath, dirname4(manifest), named);
|
|
8314
|
+
if (library !== null) {
|
|
8315
|
+
libraries.add(library);
|
|
8316
|
+
}
|
|
8317
|
+
}
|
|
8318
|
+
}
|
|
8319
|
+
return [...libraries];
|
|
8320
|
+
}
|
|
8321
|
+
function resolveResource(projectPath, relativeTo, path) {
|
|
8322
|
+
if (path === "") {
|
|
8323
|
+
return null;
|
|
8324
|
+
}
|
|
8325
|
+
if (path.startsWith("res://")) {
|
|
8326
|
+
return join5(projectPath, path.slice("res://".length));
|
|
8327
|
+
}
|
|
8328
|
+
return isAbsolute2(path) ? path : join5(relativeTo, path);
|
|
8329
|
+
}
|
|
8330
|
+
function readOrNull(path) {
|
|
8331
|
+
try {
|
|
8332
|
+
return readFileSync3(path, "utf8");
|
|
8333
|
+
} catch {
|
|
8334
|
+
return null;
|
|
8335
|
+
}
|
|
8336
|
+
}
|
|
8337
|
+
var init_library_copies = () => {};
|
|
8338
|
+
|
|
8201
8339
|
// src/reimport.ts
|
|
8202
8340
|
import { createHash } from "node:crypto";
|
|
8203
|
-
import { existsSync as
|
|
8204
|
-
import { basename as
|
|
8341
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, rmSync as rmSync3, statSync as statSync4, utimesSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
8342
|
+
import { basename as basename4, join as join6 } from "node:path";
|
|
8205
8343
|
function forceNextImport(projectPath, resourcePath) {
|
|
8206
8344
|
const relative = resourcePath.replace(/^res:\/\//, "");
|
|
8207
|
-
const sidecar =
|
|
8208
|
-
if (!
|
|
8345
|
+
const sidecar = join6(projectPath, `${relative}.import`);
|
|
8346
|
+
if (!existsSync6(sidecar)) {
|
|
8209
8347
|
return;
|
|
8210
8348
|
}
|
|
8211
|
-
const { atime, mtimeMs } =
|
|
8212
|
-
writeFileSync3(sidecar,
|
|
8349
|
+
const { atime, mtimeMs } = statSync4(sidecar);
|
|
8350
|
+
writeFileSync3(sidecar, readFileSync4(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
|
|
8213
8351
|
utimesSync(sidecar, atime, new Date(mtimeMs + 2000));
|
|
8214
8352
|
rmSync3(importRecord(projectPath, resourcePath), { force: true });
|
|
8215
8353
|
}
|
|
8216
8354
|
function importRecord(projectPath, resourcePath) {
|
|
8217
8355
|
const digest = createHash("md5").update(resourcePath).digest("hex");
|
|
8218
|
-
return
|
|
8356
|
+
return join6(projectPath, ".godot", "imported", `${basename4(resourcePath)}-${digest}.md5`);
|
|
8219
8357
|
}
|
|
8220
8358
|
var init_reimport = () => {};
|
|
8221
8359
|
|
|
@@ -8235,17 +8373,17 @@ function alive(pid) {
|
|
|
8235
8373
|
var init_alive = () => {};
|
|
8236
8374
|
|
|
8237
8375
|
// src/scratch.ts
|
|
8238
|
-
import { existsSync as
|
|
8376
|
+
import { existsSync as existsSync7, mkdtempSync, readdirSync as readdirSync4, rmSync as rmSync4 } from "node:fs";
|
|
8239
8377
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
8240
|
-
import { join as
|
|
8378
|
+
import { join as join7 } from "node:path";
|
|
8241
8379
|
import process6 from "node:process";
|
|
8242
8380
|
function scratchDirectory(kind) {
|
|
8243
|
-
return mkdtempSync(
|
|
8381
|
+
return mkdtempSync(join7(tmpdir2(), `gdharness-${kind}-${process6.pid}-`));
|
|
8244
8382
|
}
|
|
8245
8383
|
function sweepAbandonedScratch(directory = tmpdir2()) {
|
|
8246
8384
|
let entries;
|
|
8247
8385
|
try {
|
|
8248
|
-
entries =
|
|
8386
|
+
entries = readdirSync4(directory);
|
|
8249
8387
|
} catch {
|
|
8250
8388
|
return;
|
|
8251
8389
|
}
|
|
@@ -8256,7 +8394,7 @@ function sweepAbandonedScratch(directory = tmpdir2()) {
|
|
|
8256
8394
|
continue;
|
|
8257
8395
|
}
|
|
8258
8396
|
try {
|
|
8259
|
-
remove(
|
|
8397
|
+
remove(join7(directory, entry));
|
|
8260
8398
|
} catch {}
|
|
8261
8399
|
}
|
|
8262
8400
|
}
|
|
@@ -8294,7 +8432,7 @@ function tryAgain(path, attempt) {
|
|
|
8294
8432
|
lastTryOnExit();
|
|
8295
8433
|
}
|
|
8296
8434
|
const after = AGAIN_AFTER_MS[attempt];
|
|
8297
|
-
if (after === undefined || !
|
|
8435
|
+
if (after === undefined || !existsSync7(path)) {
|
|
8298
8436
|
waiting.delete(path);
|
|
8299
8437
|
if (after === undefined) {
|
|
8300
8438
|
atExit.add(path);
|
|
@@ -8341,8 +8479,8 @@ var init_scratch = __esm(() => {
|
|
|
8341
8479
|
});
|
|
8342
8480
|
|
|
8343
8481
|
// src/headless.ts
|
|
8344
|
-
import { existsSync as
|
|
8345
|
-
import { dirname as
|
|
8482
|
+
import { existsSync as existsSync8, readFileSync as readFileSync5, realpathSync as realpathSync2, writeFileSync as writeFileSync4 } from "node:fs";
|
|
8483
|
+
import { dirname as dirname5, join as join8 } from "node:path";
|
|
8346
8484
|
function snakeCased(params) {
|
|
8347
8485
|
const result = emptyRecord();
|
|
8348
8486
|
for (const [key, value] of Object.entries(params)) {
|
|
@@ -8397,7 +8535,7 @@ function refusalsIn(log) {
|
|
|
8397
8535
|
}
|
|
8398
8536
|
function scriptFault(problems, script) {
|
|
8399
8537
|
const spelled = (path) => `(${path.replaceAll("\\", "/").toLowerCase()}/`;
|
|
8400
|
-
const directories = [...new Set([spelled(
|
|
8538
|
+
const directories = [...new Set([spelled(dirname5(script)), spelled(resolvedOr(dirname5(script)))])];
|
|
8401
8539
|
return problems.find((entry) => {
|
|
8402
8540
|
const at = entry.detail.find((line) => line.startsWith("at: "))?.replaceAll("\\", "/").toLowerCase();
|
|
8403
8541
|
return entry.severity === "error" && at !== undefined && directories.some((directory) => at.includes(directory));
|
|
@@ -8422,7 +8560,7 @@ function librariesNotCopied(messages) {
|
|
|
8422
8560
|
}
|
|
8423
8561
|
function extensionRetryNote(libraries, stillNotLoaded = []) {
|
|
8424
8562
|
const second = stillNotLoaded.length === 0 ? "so the import was run again and loaded the extension, and the messages are from that second run." : `so the import was run again, and it still could not load ${stillNotLoaded.join(" and ")}: this answer is from an import without ${stillNotLoaded.length === 1 ? "it" : "them"}, and its messages say what the engine hit.`;
|
|
8425
|
-
return `The first import could not load ${libraries.join(" and ")}: on Windows an engine running as the editor ` + "loads a GDExtension from a ~ copy beside the library, and the editor open on this project holds its own " + "copy there, so the first engine started beside it cannot make one. That failed attempt moves the " + `editor's copy aside, ${second} The editor's copy is left beside the library as a ~RF….TMP file, which ` + "
|
|
8563
|
+
return `The first import could not load ${libraries.join(" and ")}: on Windows an engine running as the editor ` + "loads a GDExtension from a ~ copy beside the library, and the editor open on this project holds its own " + "copy there, so the first engine started beside it cannot make one. That failed attempt moves the " + `editor's copy aside, ${second} The editor's copy is left beside the library as a ~RF….TMP file, which ` + "the next import, export or editor this server starts on the project deletes once the editor has exited.";
|
|
8426
8564
|
}
|
|
8427
8565
|
async function runImport(godotPath, projectPath, options = {}) {
|
|
8428
8566
|
const once = options.once ?? importOnce;
|
|
@@ -8430,6 +8568,7 @@ async function runImport(godotPath, projectPath, options = {}) {
|
|
|
8430
8568
|
for (const target of options.forcing ?? []) {
|
|
8431
8569
|
forceNextImport(projectPath, target);
|
|
8432
8570
|
}
|
|
8571
|
+
freeLibraryCopies(projectPath);
|
|
8433
8572
|
return once(godotPath, projectPath);
|
|
8434
8573
|
};
|
|
8435
8574
|
const first = await pass();
|
|
@@ -8453,7 +8592,7 @@ async function importOnce(godotPath, projectPath) {
|
|
|
8453
8592
|
ran = await runEngine(godotPath, [
|
|
8454
8593
|
"--headless",
|
|
8455
8594
|
"--log-file",
|
|
8456
|
-
|
|
8595
|
+
join8(logDir, "engine.log"),
|
|
8457
8596
|
"--path",
|
|
8458
8597
|
projectPath,
|
|
8459
8598
|
"--import"
|
|
@@ -8473,13 +8612,13 @@ async function importOnce(godotPath, projectPath) {
|
|
|
8473
8612
|
}
|
|
8474
8613
|
async function runOperation(engine, operation, params, projectPath) {
|
|
8475
8614
|
const paramsDir = scratchDirectory("params");
|
|
8476
|
-
const paramsFile =
|
|
8477
|
-
const answerFile =
|
|
8615
|
+
const paramsFile = join8(paramsDir, `${operation}.json`);
|
|
8616
|
+
const answerFile = join8(paramsDir, "answer.json");
|
|
8478
8617
|
writeFileSync4(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
|
|
8479
8618
|
const args = [
|
|
8480
8619
|
"--headless",
|
|
8481
8620
|
"--log-file",
|
|
8482
|
-
|
|
8621
|
+
join8(paramsDir, "engine.log"),
|
|
8483
8622
|
"--path",
|
|
8484
8623
|
projectPath,
|
|
8485
8624
|
"--script",
|
|
@@ -8549,10 +8688,10 @@ async function runOperation(engine, operation, params, projectPath) {
|
|
|
8549
8688
|
};
|
|
8550
8689
|
}
|
|
8551
8690
|
function answerIn(file) {
|
|
8552
|
-
if (!
|
|
8691
|
+
if (!existsSync8(file)) {
|
|
8553
8692
|
return { kind: "none" };
|
|
8554
8693
|
}
|
|
8555
|
-
const text =
|
|
8694
|
+
const text = readFileSync5(file, "utf8");
|
|
8556
8695
|
try {
|
|
8557
8696
|
const parsed = JSON.parse(text);
|
|
8558
8697
|
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? { kind: "answer", payload: parsed } : { kind: "unreadable", why: `it is JSON but not an object: ${text.slice(0, 200)}` };
|
|
@@ -8566,14 +8705,15 @@ function answerIn(file) {
|
|
|
8566
8705
|
var AFTER_ANSWER_NOTE = "Printed after this operation had written its answer, while the engine ran the project's autoloads and shut down: Godot starts a project's autoloads in every script run and cannot be told not to. An error raised in a project script here is that autoload's own, and none of these changes the answer.", OWN_ERROR = "[ERROR] ", ANSWERED = "[gdharness] answered", MESSAGES_KEPT = 200, ERRORS_NAMED = 3;
|
|
8567
8706
|
var init_headless = __esm(() => {
|
|
8568
8707
|
init_engine_run();
|
|
8708
|
+
init_library_copies();
|
|
8569
8709
|
init_reimport();
|
|
8570
8710
|
init_scratch();
|
|
8571
8711
|
});
|
|
8572
8712
|
|
|
8573
8713
|
// src/update-check.ts
|
|
8574
|
-
import { existsSync as
|
|
8714
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
|
|
8575
8715
|
import { homedir as homedir3, tmpdir as tmpdir3 } from "node:os";
|
|
8576
|
-
import { join as
|
|
8716
|
+
import { join as join9 } from "node:path";
|
|
8577
8717
|
function packageUrl(root) {
|
|
8578
8718
|
return `${root.replace(/\/+$/, "")}/gdharness/latest`;
|
|
8579
8719
|
}
|
|
@@ -8595,28 +8735,28 @@ function cacheDirectory(environment) {
|
|
|
8595
8735
|
};
|
|
8596
8736
|
const home = set("HOME") ?? homedir3();
|
|
8597
8737
|
if (process.platform === "win32") {
|
|
8598
|
-
return
|
|
8738
|
+
return join9(set("LOCALAPPDATA") ?? home, "gdharness");
|
|
8599
8739
|
}
|
|
8600
8740
|
if (process.platform === "darwin") {
|
|
8601
|
-
return
|
|
8741
|
+
return join9(home, "Library", "Caches", "gdharness");
|
|
8602
8742
|
}
|
|
8603
|
-
return
|
|
8743
|
+
return join9(set("XDG_CACHE_HOME") ?? join9(home, ".cache"), "gdharness");
|
|
8604
8744
|
}
|
|
8605
8745
|
function cacheFile(environment = process.env) {
|
|
8606
8746
|
try {
|
|
8607
8747
|
const directory = cacheDirectory(environment);
|
|
8608
8748
|
mkdirSync3(directory, { recursive: true, mode: 448 });
|
|
8609
|
-
return
|
|
8749
|
+
return join9(directory, "update-check.json");
|
|
8610
8750
|
} catch {
|
|
8611
|
-
return
|
|
8751
|
+
return join9(tmpdir3(), "gdharness-update-check.json");
|
|
8612
8752
|
}
|
|
8613
8753
|
}
|
|
8614
8754
|
function readCache(path) {
|
|
8615
8755
|
try {
|
|
8616
|
-
if (!
|
|
8756
|
+
if (!existsSync9(path)) {
|
|
8617
8757
|
return null;
|
|
8618
8758
|
}
|
|
8619
|
-
const parsed = JSON.parse(
|
|
8759
|
+
const parsed = JSON.parse(readFileSync6(path, "utf8"));
|
|
8620
8760
|
if (typeof parsed !== "object" || parsed === null) {
|
|
8621
8761
|
return null;
|
|
8622
8762
|
}
|
|
@@ -8764,7 +8904,7 @@ var init_update_check = __esm(() => {
|
|
|
8764
8904
|
});
|
|
8765
8905
|
|
|
8766
8906
|
// src/server-version.ts
|
|
8767
|
-
import { readFileSync as
|
|
8907
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
8768
8908
|
function editorIsStale(addonVersion, serverVersion, addonDigest, shippedDigest) {
|
|
8769
8909
|
if (addonDigest !== undefined && addonDigest !== "" && shippedDigest !== undefined) {
|
|
8770
8910
|
return addonDigest !== shippedDigest;
|
|
@@ -8835,7 +8975,7 @@ var init_server_version = __esm(() => {
|
|
|
8835
8975
|
GODOT_DEBUG_MODE_DEFAULT = process.env["GODOT_DEBUG"] === "true" || DEBUG_MODE;
|
|
8836
8976
|
SERVER_VERSION = (() => {
|
|
8837
8977
|
try {
|
|
8838
|
-
const pkg = JSON.parse(
|
|
8978
|
+
const pkg = JSON.parse(readFileSync7(new URL("../package.json", import.meta.url), "utf8"));
|
|
8839
8979
|
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
8840
8980
|
} catch {
|
|
8841
8981
|
return "0.0.0";
|
|
@@ -9620,8 +9760,8 @@ var init_gdscript_source = __esm(() => {
|
|
|
9620
9760
|
});
|
|
9621
9761
|
|
|
9622
9762
|
// src/class-cache.ts
|
|
9623
|
-
import { existsSync as
|
|
9624
|
-
import { join as
|
|
9763
|
+
import { existsSync as existsSync10, readdirSync as readdirSync5, readFileSync as readFileSync8, statSync as statSync5 } from "node:fs";
|
|
9764
|
+
import { join as join10, posix } from "node:path";
|
|
9625
9765
|
function isToolScript(source) {
|
|
9626
9766
|
return TOOL.test(codeOf(source));
|
|
9627
9767
|
}
|
|
@@ -9637,18 +9777,18 @@ function declaredClasses(projectPath) {
|
|
|
9637
9777
|
}
|
|
9638
9778
|
function eachScript(projectPath, each) {
|
|
9639
9779
|
const visit = (directory, prefix) => {
|
|
9640
|
-
if (
|
|
9780
|
+
if (existsSync10(join10(directory, ".gdignore"))) {
|
|
9641
9781
|
return;
|
|
9642
9782
|
}
|
|
9643
|
-
for (const entry of
|
|
9783
|
+
for (const entry of readdirSync5(directory, { withFileTypes: true })) {
|
|
9644
9784
|
if (entry.name.startsWith(".")) {
|
|
9645
9785
|
continue;
|
|
9646
9786
|
}
|
|
9647
|
-
const path =
|
|
9787
|
+
const path = join10(directory, entry.name);
|
|
9648
9788
|
if (entry.isDirectory()) {
|
|
9649
9789
|
visit(path, `${prefix}${entry.name}/`);
|
|
9650
9790
|
} else if (entry.isFile() && entry.name.endsWith(".gd")) {
|
|
9651
|
-
each(`res://${prefix}${entry.name}`,
|
|
9791
|
+
each(`res://${prefix}${entry.name}`, readFileSync8(path, "utf8"));
|
|
9652
9792
|
}
|
|
9653
9793
|
}
|
|
9654
9794
|
};
|
|
@@ -9670,12 +9810,12 @@ function scriptsNaming(projectPath, classes) {
|
|
|
9670
9810
|
return naming.sort((a, b) => a.script.localeCompare(b.script));
|
|
9671
9811
|
}
|
|
9672
9812
|
function cachedClasses(projectPath) {
|
|
9673
|
-
const cache =
|
|
9674
|
-
if (!
|
|
9813
|
+
const cache = join10(projectPath, ".godot", "global_script_class_cache.cfg");
|
|
9814
|
+
if (!existsSync10(cache)) {
|
|
9675
9815
|
return null;
|
|
9676
9816
|
}
|
|
9677
9817
|
const listed = new Map;
|
|
9678
|
-
const text =
|
|
9818
|
+
const text = readFileSync8(cache, "utf8");
|
|
9679
9819
|
for (const entry of text.matchAll(/"class":\s*&"([^"]+)"[\s\S]*?"path":\s*"([^"]+)"/g)) {
|
|
9680
9820
|
listed.set(entry[1] ?? "", entry[2] ?? "");
|
|
9681
9821
|
}
|
|
@@ -9941,9 +10081,9 @@ function uncachedClassNote(types) {
|
|
|
9941
10081
|
return `${types.join(", ")} ${one ? "is declared" : "are declared"} in this project and missing from ` + `.godot/global_script_class_cache.cfg, so a game launched now would not resolve ${one ? "it" : "them"} ` + "either. Try editor_rescan first: it asks the editor already running and starts nothing, and it " + "cleared this in 314ms in a second project with a 31-worker fan-out still importing. It is not " + "always enough, because a file another engine has already imported reads as settled and the " + "walk does not look inside it. project_import refresh_classes rewrites the cache from the " + "declarations on disk and does reach that case, at the cost of a short headless engine, which " + "is worth knowing when something else is already running against this project.";
|
|
9942
10082
|
}
|
|
9943
10083
|
function cacheWrittenAt(projectPath) {
|
|
9944
|
-
const cache =
|
|
10084
|
+
const cache = join10(projectPath, ".godot", "global_script_class_cache.cfg");
|
|
9945
10085
|
try {
|
|
9946
|
-
return
|
|
10086
|
+
return statSync5(cache).mtimeMs;
|
|
9947
10087
|
} catch {
|
|
9948
10088
|
return null;
|
|
9949
10089
|
}
|
|
@@ -9955,14 +10095,14 @@ function declaredSince(projectPath, paths, names, since) {
|
|
|
9955
10095
|
return false;
|
|
9956
10096
|
}
|
|
9957
10097
|
try {
|
|
9958
|
-
return
|
|
10098
|
+
return statSync5(join10(projectPath, path.slice("res://".length))).mtimeMs > since;
|
|
9959
10099
|
} catch {
|
|
9960
10100
|
return false;
|
|
9961
10101
|
}
|
|
9962
10102
|
});
|
|
9963
10103
|
}
|
|
9964
10104
|
function cachedAtMissingPaths(cached, projectPath) {
|
|
9965
|
-
return [...cached].filter(([, path]) => path.startsWith("res://") && !
|
|
10105
|
+
return [...cached].filter(([, path]) => path.startsWith("res://") && !existsSync10(join10(projectPath, path.slice("res://".length)))).map(([name]) => name).sort();
|
|
9966
10106
|
}
|
|
9967
10107
|
function staleAgainst(cached, projectPath) {
|
|
9968
10108
|
return [...declaredClasses(projectPath)].filter(([name, path]) => cached.get(name) !== path).map(([name]) => name);
|
|
@@ -9987,8 +10127,8 @@ var init_class_cache = __esm(() => {
|
|
|
9987
10127
|
});
|
|
9988
10128
|
|
|
9989
10129
|
// src/project-scan.ts
|
|
9990
|
-
import { existsSync as
|
|
9991
|
-
import { extname, join as
|
|
10130
|
+
import { existsSync as existsSync11, readdirSync as readdirSync6, readFileSync as readFileSync9 } from "node:fs";
|
|
10131
|
+
import { extname, join as join11 } from "node:path";
|
|
9992
10132
|
function skipped(name) {
|
|
9993
10133
|
return name.startsWith(".") || SKIPPED.has(name);
|
|
9994
10134
|
}
|
|
@@ -9996,10 +10136,10 @@ function steppedOver(directory) {
|
|
|
9996
10136
|
return gdignored(directory) || installedByUs(directory);
|
|
9997
10137
|
}
|
|
9998
10138
|
function gdignored(directory) {
|
|
9999
|
-
return
|
|
10139
|
+
return existsSync11(join11(directory, ".gdignore"));
|
|
10000
10140
|
}
|
|
10001
10141
|
function installedByUs(directory) {
|
|
10002
|
-
return
|
|
10142
|
+
return existsSync11(join11(directory, VERSION_MARKER));
|
|
10003
10143
|
}
|
|
10004
10144
|
function projectStructure(projectPath) {
|
|
10005
10145
|
const structure = { scenes: 0, scripts: 0, assets: 0, other: 0 };
|
|
@@ -10007,12 +10147,12 @@ function projectStructure(projectPath) {
|
|
|
10007
10147
|
if (steppedOver(directory)) {
|
|
10008
10148
|
return;
|
|
10009
10149
|
}
|
|
10010
|
-
for (const entry of
|
|
10150
|
+
for (const entry of readdirSync6(directory, { withFileTypes: true })) {
|
|
10011
10151
|
if (skipped(entry.name)) {
|
|
10012
10152
|
continue;
|
|
10013
10153
|
}
|
|
10014
10154
|
if (entry.isDirectory()) {
|
|
10015
|
-
visit(
|
|
10155
|
+
visit(join11(directory, entry.name));
|
|
10016
10156
|
} else if (entry.isFile()) {
|
|
10017
10157
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
10018
10158
|
if (SCENE_EXTENSIONS.has(extension)) {
|
|
@@ -10036,13 +10176,13 @@ function projectFiles(projectPath, extensions) {
|
|
|
10036
10176
|
if (steppedOver(directory)) {
|
|
10037
10177
|
return;
|
|
10038
10178
|
}
|
|
10039
|
-
for (const entry of
|
|
10179
|
+
for (const entry of readdirSync6(directory, { withFileTypes: true })) {
|
|
10040
10180
|
if (skipped(entry.name)) {
|
|
10041
10181
|
continue;
|
|
10042
10182
|
}
|
|
10043
10183
|
const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
10044
10184
|
if (entry.isDirectory()) {
|
|
10045
|
-
visit(
|
|
10185
|
+
visit(join11(directory, entry.name), spelled);
|
|
10046
10186
|
} else if (entry.isFile() && extensions.includes(extname(entry.name).toLowerCase())) {
|
|
10047
10187
|
found.push(spelled);
|
|
10048
10188
|
}
|
|
@@ -10057,20 +10197,20 @@ function scriptsWithoutUid(projectPath) {
|
|
|
10057
10197
|
if (steppedOver(directory)) {
|
|
10058
10198
|
return;
|
|
10059
10199
|
}
|
|
10060
|
-
for (const entry of
|
|
10200
|
+
for (const entry of readdirSync6(directory, { withFileTypes: true })) {
|
|
10061
10201
|
if (skipped(entry.name)) {
|
|
10062
10202
|
continue;
|
|
10063
10203
|
}
|
|
10064
10204
|
const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
10065
10205
|
if (entry.isDirectory()) {
|
|
10066
|
-
visit(
|
|
10206
|
+
visit(join11(directory, entry.name), spelled);
|
|
10067
10207
|
continue;
|
|
10068
10208
|
}
|
|
10069
10209
|
if (!entry.isFile()) {
|
|
10070
10210
|
continue;
|
|
10071
10211
|
}
|
|
10072
10212
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
10073
|
-
if (CARRIES_A_UID.has(extension) && !
|
|
10213
|
+
if (CARRIES_A_UID.has(extension) && !existsSync11(join11(directory, `${entry.name}.uid`))) {
|
|
10074
10214
|
found.push(spelled);
|
|
10075
10215
|
}
|
|
10076
10216
|
}
|
|
@@ -10087,11 +10227,11 @@ function projectTexts(projectPath) {
|
|
|
10087
10227
|
if (steppedOver(directory)) {
|
|
10088
10228
|
return;
|
|
10089
10229
|
}
|
|
10090
|
-
for (const entry of
|
|
10230
|
+
for (const entry of readdirSync6(directory, { withFileTypes: true })) {
|
|
10091
10231
|
if (skipped(entry.name)) {
|
|
10092
10232
|
continue;
|
|
10093
10233
|
}
|
|
10094
|
-
const absolute =
|
|
10234
|
+
const absolute = join11(directory, entry.name);
|
|
10095
10235
|
const spelled = `${prefix}${entry.name}`;
|
|
10096
10236
|
if (entry.isDirectory()) {
|
|
10097
10237
|
visit(absolute, `${spelled}/`);
|
|
@@ -10101,7 +10241,7 @@ function projectTexts(projectPath) {
|
|
|
10101
10241
|
if (!entry.isFile() || BINARY_EXTENSIONS.has(extension)) {
|
|
10102
10242
|
continue;
|
|
10103
10243
|
}
|
|
10104
|
-
const bytes =
|
|
10244
|
+
const bytes = readFileSync9(absolute);
|
|
10105
10245
|
if (!isBinary(bytes)) {
|
|
10106
10246
|
found.push({ path: `res://${spelled}`, absolute, text: bytes.toString("utf8") });
|
|
10107
10247
|
}
|
|
@@ -10117,11 +10257,11 @@ function ignoredTexts(projectPath) {
|
|
|
10117
10257
|
return;
|
|
10118
10258
|
}
|
|
10119
10259
|
const under = ignored || gdignored(directory);
|
|
10120
|
-
for (const entry of
|
|
10260
|
+
for (const entry of readdirSync6(directory, { withFileTypes: true })) {
|
|
10121
10261
|
if (skipped(entry.name)) {
|
|
10122
10262
|
continue;
|
|
10123
10263
|
}
|
|
10124
|
-
const absolute =
|
|
10264
|
+
const absolute = join11(directory, entry.name);
|
|
10125
10265
|
const spelled = `${prefix}${entry.name}`;
|
|
10126
10266
|
if (entry.isDirectory()) {
|
|
10127
10267
|
visit(absolute, `${spelled}/`, under);
|
|
@@ -10131,7 +10271,7 @@ function ignoredTexts(projectPath) {
|
|
|
10131
10271
|
if (!under || !entry.isFile() || BINARY_EXTENSIONS.has(extension) || entry.name === ".gdignore") {
|
|
10132
10272
|
continue;
|
|
10133
10273
|
}
|
|
10134
|
-
const bytes =
|
|
10274
|
+
const bytes = readFileSync9(absolute);
|
|
10135
10275
|
if (!isBinary(bytes)) {
|
|
10136
10276
|
found.push({ path: `res://${spelled}`, absolute, text: bytes.toString("utf8") });
|
|
10137
10277
|
}
|
|
@@ -10154,14 +10294,14 @@ function searchProject(projectPath, options) {
|
|
|
10154
10294
|
if (steppedOver(directory)) {
|
|
10155
10295
|
return;
|
|
10156
10296
|
}
|
|
10157
|
-
for (const entry of
|
|
10297
|
+
for (const entry of readdirSync6(directory, { withFileTypes: true })) {
|
|
10158
10298
|
if (full()) {
|
|
10159
10299
|
return;
|
|
10160
10300
|
}
|
|
10161
10301
|
if (skipped(entry.name)) {
|
|
10162
10302
|
continue;
|
|
10163
10303
|
}
|
|
10164
|
-
const entryPath =
|
|
10304
|
+
const entryPath = join11(directory, entry.name);
|
|
10165
10305
|
if (entry.isDirectory()) {
|
|
10166
10306
|
visit(entryPath);
|
|
10167
10307
|
continue;
|
|
@@ -10170,7 +10310,7 @@ function searchProject(projectPath, options) {
|
|
|
10170
10310
|
if (!entry.isFile() || extensions !== null && !extensions.has(extension)) {
|
|
10171
10311
|
continue;
|
|
10172
10312
|
}
|
|
10173
|
-
const bytes =
|
|
10313
|
+
const bytes = readFileSync9(entryPath);
|
|
10174
10314
|
if (extensions === null && isBinary(bytes)) {
|
|
10175
10315
|
continue;
|
|
10176
10316
|
}
|
|
@@ -10257,7 +10397,7 @@ var init_answer_json = __esm(() => {
|
|
|
10257
10397
|
});
|
|
10258
10398
|
|
|
10259
10399
|
// src/resources.ts
|
|
10260
|
-
import { readFileSync as
|
|
10400
|
+
import { readFileSync as readFileSync10, realpathSync as realpathSync3 } from "node:fs";
|
|
10261
10401
|
import { extname as extname2, resolve as resolve3 } from "node:path";
|
|
10262
10402
|
function ensureProjectPath(getProjectPath) {
|
|
10263
10403
|
const projectPath = getProjectPath();
|
|
@@ -10453,7 +10593,7 @@ function readResourceText(uri, getProjectPath) {
|
|
|
10453
10593
|
const parsedUri = parseGodotUri(uri);
|
|
10454
10594
|
if (parsedUri.kind === "project-info") {
|
|
10455
10595
|
const projectFilePath = resolveProjectFile(projectPath, "project.godot");
|
|
10456
|
-
const rawProject =
|
|
10596
|
+
const rawProject = readFileSync10(projectFilePath, "utf-8");
|
|
10457
10597
|
const parsedProject = parseProjectGodot(rawProject);
|
|
10458
10598
|
return {
|
|
10459
10599
|
mimeType: "application/json",
|
|
@@ -10462,7 +10602,7 @@ function readResourceText(uri, getProjectPath) {
|
|
|
10462
10602
|
}
|
|
10463
10603
|
const filePath = resolveProjectFile(projectPath, parsedUri.resourcePath);
|
|
10464
10604
|
ensureAllowedExtension(parsedUri.kind, filePath);
|
|
10465
|
-
const text =
|
|
10605
|
+
const text = readFileSync10(filePath, "utf-8");
|
|
10466
10606
|
return {
|
|
10467
10607
|
mimeType: parsedUri.kind === "script" ? "text/x-gdscript" : "text/plain",
|
|
10468
10608
|
text
|
|
@@ -10603,20 +10743,19 @@ function readParams(params, key) {
|
|
|
10603
10743
|
}
|
|
10604
10744
|
|
|
10605
10745
|
// src/setup.ts
|
|
10606
|
-
import { spawnSync } from "node:child_process";
|
|
10607
10746
|
import { createHash as createHash3 } from "node:crypto";
|
|
10608
10747
|
import {
|
|
10609
10748
|
cpSync,
|
|
10610
|
-
existsSync as
|
|
10749
|
+
existsSync as existsSync12,
|
|
10611
10750
|
mkdirSync as mkdirSync4,
|
|
10612
|
-
readdirSync as
|
|
10613
|
-
readFileSync as
|
|
10751
|
+
readdirSync as readdirSync7,
|
|
10752
|
+
readFileSync as readFileSync11,
|
|
10614
10753
|
rmdirSync,
|
|
10615
10754
|
rmSync as rmSync5,
|
|
10616
|
-
statSync as
|
|
10755
|
+
statSync as statSync6,
|
|
10617
10756
|
writeFileSync as writeFileSync6
|
|
10618
10757
|
} from "node:fs";
|
|
10619
|
-
import { dirname as
|
|
10758
|
+
import { dirname as dirname6, join as join12 } from "node:path";
|
|
10620
10759
|
import { fileURLToPath } from "node:url";
|
|
10621
10760
|
function editorAddonDigest(from = shippedAddonsDirectory()) {
|
|
10622
10761
|
return addonsDigest(EDITOR_PLUGINS, from);
|
|
@@ -10627,25 +10766,25 @@ function runtimeAddonDigest(from = shippedAddonsDirectory()) {
|
|
|
10627
10766
|
function addonsDigest(names, from) {
|
|
10628
10767
|
const hash = createHash3("sha256");
|
|
10629
10768
|
const walk = (directory, relative) => {
|
|
10630
|
-
for (const name of
|
|
10769
|
+
for (const name of readdirSync7(directory).sort()) {
|
|
10631
10770
|
if (name === VERSION_MARKER || name === DIGEST_MARKER) {
|
|
10632
10771
|
continue;
|
|
10633
10772
|
}
|
|
10634
|
-
const path =
|
|
10773
|
+
const path = join12(directory, name);
|
|
10635
10774
|
const inside = `${relative}/${name}`;
|
|
10636
|
-
if (
|
|
10775
|
+
if (statSync6(path).isDirectory()) {
|
|
10637
10776
|
walk(path, inside);
|
|
10638
10777
|
} else {
|
|
10639
10778
|
hash.update(`${inside}
|
|
10640
10779
|
`);
|
|
10641
|
-
hash.update(
|
|
10780
|
+
hash.update(readFileSync11(path));
|
|
10642
10781
|
hash.update(`
|
|
10643
10782
|
`);
|
|
10644
10783
|
}
|
|
10645
10784
|
}
|
|
10646
10785
|
};
|
|
10647
10786
|
for (const name of names) {
|
|
10648
|
-
walk(
|
|
10787
|
+
walk(join12(from, name), name);
|
|
10649
10788
|
}
|
|
10650
10789
|
return hash.digest("hex");
|
|
10651
10790
|
}
|
|
@@ -10676,60 +10815,60 @@ function installedRuntimeDigest(projectPath) {
|
|
|
10676
10815
|
return digestMarker(projectPath, RUNTIME_ADDON);
|
|
10677
10816
|
}
|
|
10678
10817
|
function digestMarker(projectPath, addon) {
|
|
10679
|
-
const marker =
|
|
10680
|
-
if (!
|
|
10818
|
+
const marker = join12(projectPath, "addons", addon, DIGEST_MARKER);
|
|
10819
|
+
if (!existsSync12(marker)) {
|
|
10681
10820
|
return null;
|
|
10682
10821
|
}
|
|
10683
|
-
const said =
|
|
10822
|
+
const said = readFileSync11(marker, "utf8").trim();
|
|
10684
10823
|
return said === "" ? null : said;
|
|
10685
10824
|
}
|
|
10686
10825
|
function shippedAddonsDirectory() {
|
|
10687
|
-
return
|
|
10826
|
+
return join12(dirname6(fileURLToPath(import.meta.url)), "godot", "addons");
|
|
10688
10827
|
}
|
|
10689
10828
|
function shippedOperationsScript() {
|
|
10690
|
-
return
|
|
10829
|
+
return join12(dirname6(fileURLToPath(import.meta.url)), "godot", "operations", "godot_operations.gd");
|
|
10691
10830
|
}
|
|
10692
10831
|
function installAddons(projectPath, from = shippedAddonsDirectory()) {
|
|
10693
10832
|
const installed = [];
|
|
10694
10833
|
for (const name of ADDONS) {
|
|
10695
|
-
const source =
|
|
10696
|
-
if (!
|
|
10834
|
+
const source = join12(from, name);
|
|
10835
|
+
if (!existsSync12(join12(source, name === RUNTIME_ADDON ? "runtime_autoload.gd" : "plugin.cfg"))) {
|
|
10697
10836
|
throw new Error(`The package holds no ${name} addon at ${source}.`);
|
|
10698
10837
|
}
|
|
10699
|
-
const target =
|
|
10700
|
-
const replaced =
|
|
10838
|
+
const target = join12(projectPath, "addons", name);
|
|
10839
|
+
const replaced = existsSync12(target);
|
|
10701
10840
|
rmSync5(target, { recursive: true, force: true });
|
|
10702
|
-
mkdirSync4(
|
|
10841
|
+
mkdirSync4(dirname6(target), { recursive: true });
|
|
10703
10842
|
cpSync(source, target, { recursive: true });
|
|
10704
|
-
writeFileSync6(
|
|
10843
|
+
writeFileSync6(join12(target, VERSION_MARKER), `${SERVER_VERSION}
|
|
10705
10844
|
`);
|
|
10706
10845
|
installed.push({ name, path: target, replaced });
|
|
10707
10846
|
}
|
|
10708
|
-
writeFileSync6(
|
|
10847
|
+
writeFileSync6(join12(projectPath, "addons", ADDONS[0], DIGEST_MARKER), `${editorAddonDigest(from)}
|
|
10709
10848
|
`);
|
|
10710
|
-
writeFileSync6(
|
|
10849
|
+
writeFileSync6(join12(projectPath, "addons", RUNTIME_ADDON, DIGEST_MARKER), `${runtimeAddonDigest(from)}
|
|
10711
10850
|
`);
|
|
10712
10851
|
return installed;
|
|
10713
10852
|
}
|
|
10714
10853
|
function installedAddonVersion(projectPath) {
|
|
10715
|
-
const marker =
|
|
10716
|
-
if (!
|
|
10854
|
+
const marker = join12(projectPath, "addons", ADDONS[0], VERSION_MARKER);
|
|
10855
|
+
if (!existsSync12(marker)) {
|
|
10717
10856
|
return null;
|
|
10718
10857
|
}
|
|
10719
|
-
const said =
|
|
10858
|
+
const said = readFileSync11(marker, "utf8").trim();
|
|
10720
10859
|
return said === "" ? null : said;
|
|
10721
10860
|
}
|
|
10722
10861
|
function removeAddons(projectPath) {
|
|
10723
10862
|
const removed = [];
|
|
10724
10863
|
for (const name of ADDONS) {
|
|
10725
|
-
const target =
|
|
10726
|
-
if (
|
|
10864
|
+
const target = join12(projectPath, "addons", name);
|
|
10865
|
+
if (existsSync12(target)) {
|
|
10727
10866
|
rmSync5(target, { recursive: true, force: true });
|
|
10728
10867
|
removed.push(target);
|
|
10729
10868
|
}
|
|
10730
10869
|
}
|
|
10731
|
-
const addons =
|
|
10732
|
-
if (removed.length > 0 &&
|
|
10870
|
+
const addons = join12(projectPath, "addons");
|
|
10871
|
+
if (removed.length > 0 && existsSync12(addons) && readdirSync7(addons).length === 0) {
|
|
10733
10872
|
rmdirSync(addons);
|
|
10734
10873
|
removed.push(addons);
|
|
10735
10874
|
}
|
|
@@ -10763,7 +10902,7 @@ function loaderAmong(projectPath, named) {
|
|
|
10763
10902
|
}
|
|
10764
10903
|
let source = null;
|
|
10765
10904
|
try {
|
|
10766
|
-
source =
|
|
10905
|
+
source = readFileSync11(join12(projectPath, path), "utf8");
|
|
10767
10906
|
} catch {}
|
|
10768
10907
|
if (source?.includes(RUNTIME_AUTOLOAD.path)) {
|
|
10769
10908
|
return { name: entry, path, exists: true };
|
|
@@ -10786,7 +10925,7 @@ function trackedByGit(projectPath, wanted) {
|
|
|
10786
10925
|
if (wanted.length === 0) {
|
|
10787
10926
|
return new Set;
|
|
10788
10927
|
}
|
|
10789
|
-
const asked =
|
|
10928
|
+
const asked = spawnSyncHidden("git", ["ls-files", "-z", "--", ...wanted], {
|
|
10790
10929
|
cwd: projectPath,
|
|
10791
10930
|
encoding: "utf8"
|
|
10792
10931
|
});
|
|
@@ -10805,10 +10944,10 @@ function enabledPlugins(settings) {
|
|
|
10805
10944
|
function inspectProject(projectPath) {
|
|
10806
10945
|
const problems = [];
|
|
10807
10946
|
const addons = ADDONS.map((name) => {
|
|
10808
|
-
const directory =
|
|
10809
|
-
const marker =
|
|
10810
|
-
const installed =
|
|
10811
|
-
const version =
|
|
10947
|
+
const directory = join12(projectPath, "addons", name);
|
|
10948
|
+
const marker = join12(directory, VERSION_MARKER);
|
|
10949
|
+
const installed = existsSync12(directory);
|
|
10950
|
+
const version = existsSync12(marker) ? readFileSync11(marker, "utf8").trim() : null;
|
|
10812
10951
|
const current = version === SERVER_VERSION;
|
|
10813
10952
|
if (!installed) {
|
|
10814
10953
|
problems.push(`addons/${name} is not installed; run gdharness setup`);
|
|
@@ -10817,7 +10956,7 @@ function inspectProject(projectPath) {
|
|
|
10817
10956
|
}
|
|
10818
10957
|
return { name, installed, version, current };
|
|
10819
10958
|
});
|
|
10820
|
-
const settings = parseProjectGodot(
|
|
10959
|
+
const settings = parseProjectGodot(readFileSync11(join12(projectPath, "project.godot"), "utf8"));
|
|
10821
10960
|
const pluginsEnabled = enabledPlugins(settings);
|
|
10822
10961
|
for (const name of EDITOR_PLUGINS) {
|
|
10823
10962
|
if (!pluginsEnabled.includes(name)) {
|
|
@@ -10833,7 +10972,7 @@ function inspectProject(projectPath) {
|
|
|
10833
10972
|
const tracked = trackedByGit(projectPath, [...named.values()]);
|
|
10834
10973
|
const addonMissing = (path) => addons.some((addon) => !addon.installed && path.startsWith(`addons/${addon.name}/`));
|
|
10835
10974
|
for (const [name, path] of named) {
|
|
10836
|
-
if (!
|
|
10975
|
+
if (!existsSync12(join12(projectPath, path))) {
|
|
10837
10976
|
if (!addonMissing(path)) {
|
|
10838
10977
|
problems.push(`the ${name} autoload names res://${path}, which is not in this project, so it boots with a missing script; restore that file, or point the autoload at one that is here`);
|
|
10839
10978
|
}
|
|
@@ -10869,6 +11008,7 @@ var init_setup = __esm(() => {
|
|
|
10869
11008
|
init_project_scan();
|
|
10870
11009
|
init_resources();
|
|
10871
11010
|
init_server_version();
|
|
11011
|
+
init_spawn();
|
|
10872
11012
|
ADDONS = ["gdharness_editor", "gdharness_runtime", "auto_reload"];
|
|
10873
11013
|
EDITOR_PLUGINS = ["gdharness_editor", "auto_reload"];
|
|
10874
11014
|
RUNTIME_AUTOLOAD = {
|
|
@@ -11917,6 +12057,11 @@ var init_tool_definitions = __esm(() => {
|
|
|
11917
12057
|
ops: ["start"],
|
|
11918
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."
|
|
11919
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
|
+
},
|
|
11920
12065
|
savesIn: {
|
|
11921
12066
|
type: "string",
|
|
11922
12067
|
ops: ["start", "check"],
|
|
@@ -12072,7 +12217,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12072
12217
|
name: "editor_rescan",
|
|
12073
12218
|
title: "Rescan the project",
|
|
12074
12219
|
effect: "additive",
|
|
12075
|
-
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.",
|
|
12076
12221
|
parameters: {
|
|
12077
12222
|
projectPath: PROJECT_PATH,
|
|
12078
12223
|
timeoutMs: {
|
|
@@ -12150,13 +12295,13 @@ var init_tool_definitions = __esm(() => {
|
|
|
12150
12295
|
pid: RUNNING_PID,
|
|
12151
12296
|
nodePath: {
|
|
12152
12297
|
type: "string",
|
|
12153
|
-
ops: ["tree", "text", "find", "rect", "property"],
|
|
12154
|
-
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.'
|
|
12155
12300
|
},
|
|
12156
12301
|
property: {
|
|
12157
12302
|
type: "string",
|
|
12158
12303
|
ops: ["property", "find"],
|
|
12159
|
-
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.'
|
|
12160
12305
|
},
|
|
12161
12306
|
depth: {
|
|
12162
12307
|
type: "integer",
|
|
@@ -12172,8 +12317,8 @@ var init_tool_definitions = __esm(() => {
|
|
|
12172
12317
|
properties: {
|
|
12173
12318
|
type: "array",
|
|
12174
12319
|
items: { type: "string" },
|
|
12175
|
-
ops: ["tree"],
|
|
12176
|
-
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.'
|
|
12177
12322
|
},
|
|
12178
12323
|
className: {
|
|
12179
12324
|
type: "string",
|
|
@@ -12189,15 +12334,15 @@ var init_tool_definitions = __esm(() => {
|
|
|
12189
12334
|
group: { type: "string", ops: ["find"], description: "find: a group the node is in." },
|
|
12190
12335
|
says: {
|
|
12191
12336
|
type: "string",
|
|
12192
|
-
ops: ["find"],
|
|
12193
|
-
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.'
|
|
12194
12339
|
},
|
|
12195
12340
|
limit: {
|
|
12196
12341
|
type: "integer",
|
|
12197
12342
|
minimum: 1,
|
|
12198
12343
|
maximum: 5000,
|
|
12199
|
-
ops: ["find", "text"],
|
|
12200
|
-
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."
|
|
12201
12346
|
},
|
|
12202
12347
|
includeHidden: {
|
|
12203
12348
|
type: "boolean",
|
|
@@ -12241,12 +12386,12 @@ var init_tool_definitions = __esm(() => {
|
|
|
12241
12386
|
},
|
|
12242
12387
|
metrics: {
|
|
12243
12388
|
effect: "read",
|
|
12244
|
-
summary: "frame time, memory, draw calls and the rest. frame_time is the mean wall time of a frame over the last frames_timed frames (up to 120, timed by the game itself, physics and the wait for the display included), frame_time_max the slowest of them, both in seconds and null before a frame has passed; vsync says whether a frame waits for the display, and max_fps is the game's frame-rate cap, 0 for none. fps, process_time_max and physics_time are the engine's monitors, which it sets once a second: the frames counted in the last second, and the slowest process and physics step in it, so they trail a change by up to a second and the last two are a worst case rather than the frame. The memory, object and render counts are read as asked. memory_static is what the engine allocated on the CPU side, in bytes; render_video_mem_used, render_texture_mem_used and render_buffer_mem_used are what the renderer has on the GPU, in bytes, which memory_static does not count",
|
|
12389
|
+
summary: "frame time, memory, draw calls and the rest. frame_time is the mean wall time of a frame over the last frames_timed frames (up to 120, timed by the game itself, physics and the wait for the display included), frame_time_max the slowest of them, both in seconds and null before a frame has passed; vsync says whether a frame waits for the display, and max_fps is the game's frame-rate cap, 0 for none. physics_steps_per_second is the physics steps taken a second over those frames, fewer than physics_ticks_per_second when a frame needs more than max_physics_steps_per_frame or the editor's Game view slows it. fps, process_time_max and physics_time are the engine's monitors, which it sets once a second: the frames counted in the last second, and the slowest process and physics step in it, so they trail a change by up to a second and the last two are a worst case rather than the frame. The memory, object and render counts are read as asked. memory_static is what the engine allocated on the CPU side, in bytes; render_video_mem_used, render_texture_mem_used and render_buffer_mem_used are what the renderer has on the GPU, in bytes, which memory_static does not count",
|
|
12245
12390
|
requires: []
|
|
12246
12391
|
},
|
|
12247
12392
|
accessibility: {
|
|
12248
12393
|
effect: "read",
|
|
12249
|
-
summary:
|
|
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",
|
|
12250
12395
|
requires: []
|
|
12251
12396
|
}
|
|
12252
12397
|
},
|
|
@@ -12255,19 +12400,19 @@ var init_tool_definitions = __esm(() => {
|
|
|
12255
12400
|
{
|
|
12256
12401
|
name: "runtime_invoke",
|
|
12257
12402
|
title: "Set or call in the running game",
|
|
12258
|
-
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.",
|
|
12259
12404
|
parameters: {
|
|
12260
12405
|
projectPath: RUNNING_PROJECT_PATH,
|
|
12261
12406
|
pid: RUNNING_PID,
|
|
12262
12407
|
nodePath: {
|
|
12263
12408
|
type: "string",
|
|
12264
12409
|
ops: ["set", "call"],
|
|
12265
|
-
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.'
|
|
12266
12411
|
},
|
|
12267
12412
|
property: {
|
|
12268
12413
|
type: "string",
|
|
12269
12414
|
ops: ["set"],
|
|
12270
|
-
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.'
|
|
12271
12416
|
},
|
|
12272
12417
|
value: {
|
|
12273
12418
|
ops: ["set"],
|
|
@@ -12400,6 +12545,13 @@ var init_tool_definitions = __esm(() => {
|
|
|
12400
12545
|
ops: ["action", "key", "mouse_click", "joypad_button"],
|
|
12401
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."
|
|
12402
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
|
+
},
|
|
12403
12555
|
strength: {
|
|
12404
12556
|
type: "number",
|
|
12405
12557
|
minimum: 0,
|
|
@@ -12548,7 +12700,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12548
12700
|
property: {
|
|
12549
12701
|
type: "string",
|
|
12550
12702
|
ops: ["until"],
|
|
12551
|
-
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.'
|
|
12552
12704
|
},
|
|
12553
12705
|
value: {
|
|
12554
12706
|
ops: ["until"],
|
|
@@ -34422,10 +34574,10 @@ var init_stdio = __esm(() => {
|
|
|
34422
34574
|
});
|
|
34423
34575
|
|
|
34424
34576
|
// src/runtime-client.ts
|
|
34425
|
-
import { existsSync as
|
|
34577
|
+
import { existsSync as existsSync14, readdirSync as readdirSync9, readFileSync as readFileSync13, statSync as statSync7, unlinkSync as unlinkSync2 } from "node:fs";
|
|
34426
34578
|
import { createConnection } from "node:net";
|
|
34427
34579
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
34428
|
-
import { dirname as
|
|
34580
|
+
import { dirname as dirname9, join as join15, resolve as resolve4 } from "node:path";
|
|
34429
34581
|
import { setTimeout as delay } from "node:timers/promises";
|
|
34430
34582
|
function runtimeDirectory(variables = process.env) {
|
|
34431
34583
|
const explicit = envValue("GDHARNESS_RUNTIME_DIR", variables);
|
|
@@ -34433,20 +34585,20 @@ function runtimeDirectory(variables = process.env) {
|
|
|
34433
34585
|
return explicit;
|
|
34434
34586
|
}
|
|
34435
34587
|
const perUser = envValue("XDG_RUNTIME_DIR", variables);
|
|
34436
|
-
return
|
|
34588
|
+
return join15(perUser ?? tmpdir4(), "gdharness");
|
|
34437
34589
|
}
|
|
34438
34590
|
function runtimeDirectories(variables = process.env) {
|
|
34439
34591
|
const candidates = [runtimeDirectory(variables)];
|
|
34440
34592
|
const fallbacks = process.platform === "win32" ? [envValue("SystemRoot", variables) ?? envValue("windir", variables) ?? "C:\\Windows"] : ["/tmp", "/var/tmp"];
|
|
34441
34593
|
for (const base of fallbacks) {
|
|
34442
|
-
candidates.push(
|
|
34594
|
+
candidates.push(join15(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
|
|
34443
34595
|
}
|
|
34444
34596
|
return [...new Set(candidates.map((path) => resolve4(path)))];
|
|
34445
34597
|
}
|
|
34446
34598
|
function errorReportOf(pid, directories = runtimeDirectories()) {
|
|
34447
34599
|
for (const directory of directories) {
|
|
34448
|
-
const path =
|
|
34449
|
-
if (
|
|
34600
|
+
const path = join15(directory, `runtime-${pid}.log`);
|
|
34601
|
+
if (existsSync14(path)) {
|
|
34450
34602
|
return path;
|
|
34451
34603
|
}
|
|
34452
34604
|
}
|
|
@@ -34477,7 +34629,7 @@ function reportAlreadyInTranscript(transcript, report) {
|
|
|
34477
34629
|
function parseAnnouncement(file, pid) {
|
|
34478
34630
|
let fields;
|
|
34479
34631
|
try {
|
|
34480
|
-
fields = asParams(JSON.parse(
|
|
34632
|
+
fields = asParams(JSON.parse(readFileSync13(file, "utf8")));
|
|
34481
34633
|
} catch {
|
|
34482
34634
|
return { kind: "rubbish" };
|
|
34483
34635
|
}
|
|
@@ -34494,7 +34646,7 @@ function parseAnnouncement(file, pid) {
|
|
|
34494
34646
|
};
|
|
34495
34647
|
let announcedAt;
|
|
34496
34648
|
try {
|
|
34497
|
-
announcedAt =
|
|
34649
|
+
announcedAt = statSync7(file).mtimeMs;
|
|
34498
34650
|
} catch {}
|
|
34499
34651
|
const when = announcedAt === undefined ? {} : { announcedAt };
|
|
34500
34652
|
if (protocol !== RUNTIME_PROTOCOL) {
|
|
@@ -34543,7 +34695,7 @@ function discoverRuntimes(directories = runtimeDirectories()) {
|
|
|
34543
34695
|
function wentAway(file, pid) {
|
|
34544
34696
|
let project = "";
|
|
34545
34697
|
try {
|
|
34546
|
-
const fields = asParams(JSON.parse(
|
|
34698
|
+
const fields = asParams(JSON.parse(readFileSync13(file, "utf8")));
|
|
34547
34699
|
project = readString(readParams(fields, "project") ?? {}, "path") ?? "";
|
|
34548
34700
|
} catch {
|
|
34549
34701
|
return;
|
|
@@ -34586,29 +34738,29 @@ function strangersAmong(candidates, now, startTimes = startTimesOf) {
|
|
|
34586
34738
|
}).map((one) => one.file));
|
|
34587
34739
|
}
|
|
34588
34740
|
function announcedIn(directory) {
|
|
34589
|
-
if (!
|
|
34741
|
+
if (!existsSync14(directory)) {
|
|
34590
34742
|
return [];
|
|
34591
34743
|
}
|
|
34592
34744
|
const found = [];
|
|
34593
34745
|
const candidates = [];
|
|
34594
34746
|
const entries = [];
|
|
34595
|
-
for (const entry of
|
|
34747
|
+
for (const entry of readdirSync9(directory)) {
|
|
34596
34748
|
const report = ERROR_REPORT_PATTERN.exec(entry);
|
|
34597
34749
|
if (report) {
|
|
34598
|
-
sweepErrorReport(
|
|
34750
|
+
sweepErrorReport(join15(directory, entry), Number.parseInt(report[1] ?? "", 10));
|
|
34599
34751
|
continue;
|
|
34600
34752
|
}
|
|
34601
34753
|
const match = ANNOUNCEMENT_PATTERN.exec(entry);
|
|
34602
34754
|
if (!match) {
|
|
34603
34755
|
continue;
|
|
34604
34756
|
}
|
|
34605
|
-
const file =
|
|
34757
|
+
const file = join15(directory, entry);
|
|
34606
34758
|
const pid = Number.parseInt(match[1] ?? "", 10);
|
|
34607
34759
|
const running = alive(pid);
|
|
34608
34760
|
entries.push({ file, pid, running });
|
|
34609
34761
|
if (running) {
|
|
34610
34762
|
try {
|
|
34611
|
-
candidates.push({ file, pid, writtenAt:
|
|
34763
|
+
candidates.push({ file, pid, writtenAt: statSync7(file).mtimeMs });
|
|
34612
34764
|
} catch {}
|
|
34613
34765
|
}
|
|
34614
34766
|
}
|
|
@@ -34622,7 +34774,7 @@ function announcedIn(directory) {
|
|
|
34622
34774
|
}
|
|
34623
34775
|
wentAway(file, pid);
|
|
34624
34776
|
try {
|
|
34625
|
-
|
|
34777
|
+
unlinkSync2(file);
|
|
34626
34778
|
} catch {}
|
|
34627
34779
|
verdicts.delete(file);
|
|
34628
34780
|
continue;
|
|
@@ -34630,16 +34782,16 @@ function announcedIn(directory) {
|
|
|
34630
34782
|
found.push(announced);
|
|
34631
34783
|
}
|
|
34632
34784
|
const held = new Set(entries.map((one) => one.file));
|
|
34633
|
-
const here =
|
|
34785
|
+
const here = join15(directory, ".");
|
|
34634
34786
|
for (const file of verdicts.keys()) {
|
|
34635
|
-
if (
|
|
34787
|
+
if (dirname9(file) === here && !held.has(file)) {
|
|
34636
34788
|
verdicts.delete(file);
|
|
34637
34789
|
}
|
|
34638
34790
|
}
|
|
34639
34791
|
return found;
|
|
34640
34792
|
}
|
|
34641
34793
|
async function announcementEnded(pid, directories = runtimeDirectories()) {
|
|
34642
|
-
const files = directories.map((directory) =>
|
|
34794
|
+
const files = directories.map((directory) => join15(directory, `runtime-${pid}.json`)).filter((file) => existsSync14(file));
|
|
34643
34795
|
const first = files[0];
|
|
34644
34796
|
if (first === undefined) {
|
|
34645
34797
|
return;
|
|
@@ -34655,7 +34807,7 @@ async function announcementEnded(pid, directories = runtimeDirectories()) {
|
|
|
34655
34807
|
wentAway(first, pid);
|
|
34656
34808
|
for (const file of files) {
|
|
34657
34809
|
try {
|
|
34658
|
-
|
|
34810
|
+
unlinkSync2(file);
|
|
34659
34811
|
} catch {}
|
|
34660
34812
|
verdicts.delete(file);
|
|
34661
34813
|
}
|
|
@@ -34665,8 +34817,8 @@ function sweepErrorReport(path, pid, now = Date.now()) {
|
|
|
34665
34817
|
return;
|
|
34666
34818
|
}
|
|
34667
34819
|
try {
|
|
34668
|
-
if (now -
|
|
34669
|
-
|
|
34820
|
+
if (now - statSync7(path).mtimeMs >= ERROR_REPORT_KEEP_MS) {
|
|
34821
|
+
unlinkSync2(path);
|
|
34670
34822
|
}
|
|
34671
34823
|
} catch {}
|
|
34672
34824
|
}
|
|
@@ -34940,14 +35092,14 @@ var init_runtime_client = __esm(() => {
|
|
|
34940
35092
|
});
|
|
34941
35093
|
|
|
34942
35094
|
// src/boot-note.ts
|
|
34943
|
-
import { mkdirSync as mkdirSync6, readFileSync as
|
|
34944
|
-
import { dirname as
|
|
35095
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync14, writeFileSync as writeFileSync8 } from "node:fs";
|
|
35096
|
+
import { dirname as dirname10, join as join16 } from "node:path";
|
|
34945
35097
|
function bootNotePath(projectPath) {
|
|
34946
|
-
return
|
|
35098
|
+
return join16(projectPath, ".godot", "gdharness-boot.json");
|
|
34947
35099
|
}
|
|
34948
35100
|
function readScenes(projectPath) {
|
|
34949
35101
|
try {
|
|
34950
|
-
return asParams(asParams(JSON.parse(
|
|
35102
|
+
return asParams(asParams(JSON.parse(readFileSync14(bootNotePath(projectPath), "utf8")))["scenes"]);
|
|
34951
35103
|
} catch {
|
|
34952
35104
|
return {};
|
|
34953
35105
|
}
|
|
@@ -34963,7 +35115,7 @@ function readBootNote(projectPath, scene) {
|
|
|
34963
35115
|
function writeBootNote(projectPath, scene, announcedAfterMs) {
|
|
34964
35116
|
try {
|
|
34965
35117
|
const path = bootNotePath(projectPath);
|
|
34966
|
-
mkdirSync6(
|
|
35118
|
+
mkdirSync6(dirname10(path), { recursive: true });
|
|
34967
35119
|
const scenes = {
|
|
34968
35120
|
...readScenes(projectPath),
|
|
34969
35121
|
[scene]: {
|
|
@@ -34989,10 +35141,10 @@ var init_boot_note = __esm(() => {
|
|
|
34989
35141
|
});
|
|
34990
35142
|
|
|
34991
35143
|
// src/breakpoint-note.ts
|
|
34992
|
-
import { mkdirSync as mkdirSync7, readFileSync as
|
|
34993
|
-
import { dirname as
|
|
35144
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync15, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "node:fs";
|
|
35145
|
+
import { dirname as dirname11, isAbsolute as isAbsolute3, join as join17, relative as relative3 } from "node:path";
|
|
34994
35146
|
function breakpointNotePath(projectPath) {
|
|
34995
|
-
return
|
|
35147
|
+
return join17(projectPath, ".godot", "gdharness-breakpoints.json");
|
|
34996
35148
|
}
|
|
34997
35149
|
function writeBreakpointNote(projectPath, held) {
|
|
34998
35150
|
const path = breakpointNotePath(projectPath);
|
|
@@ -35009,7 +35161,7 @@ function writeBreakpointNote(projectPath, held) {
|
|
|
35009
35161
|
lines: [...one.lines]
|
|
35010
35162
|
}))
|
|
35011
35163
|
};
|
|
35012
|
-
mkdirSync7(
|
|
35164
|
+
mkdirSync7(dirname11(path), { recursive: true });
|
|
35013
35165
|
writeFileSync9(path, `${JSON.stringify(note, null, 2)}
|
|
35014
35166
|
`, "utf8");
|
|
35015
35167
|
} catch (error) {
|
|
@@ -35018,7 +35170,7 @@ function writeBreakpointNote(projectPath, held) {
|
|
|
35018
35170
|
}
|
|
35019
35171
|
function readBreakpointNote(projectPath) {
|
|
35020
35172
|
try {
|
|
35021
|
-
const parsed = JSON.parse(
|
|
35173
|
+
const parsed = JSON.parse(readFileSync15(breakpointNotePath(projectPath), "utf8"));
|
|
35022
35174
|
if (typeof parsed !== "object" || parsed === null || !Array.isArray(parsed.breakpoints)) {
|
|
35023
35175
|
return [];
|
|
35024
35176
|
}
|
|
@@ -35028,12 +35180,12 @@ function readBreakpointNote(projectPath) {
|
|
|
35028
35180
|
continue;
|
|
35029
35181
|
}
|
|
35030
35182
|
const { script, lines: listed } = entry;
|
|
35031
|
-
if (typeof script !== "string" ||
|
|
35183
|
+
if (typeof script !== "string" || isAbsolute3(script) || script.split("/").includes("..")) {
|
|
35032
35184
|
continue;
|
|
35033
35185
|
}
|
|
35034
35186
|
const lines = Array.isArray(listed) ? listed.filter((line) => Number.isInteger(line) && line > 0) : [];
|
|
35035
35187
|
if (lines.length > 0) {
|
|
35036
|
-
held.push({ scriptPath: realPathOr(
|
|
35188
|
+
held.push({ scriptPath: realPathOr(join17(projectPath, script)), lines });
|
|
35037
35189
|
}
|
|
35038
35190
|
}
|
|
35039
35191
|
return held;
|
|
@@ -35043,56 +35195,104 @@ function readBreakpointNote(projectPath) {
|
|
|
35043
35195
|
}
|
|
35044
35196
|
function withinProject(projectPath, scriptPath) {
|
|
35045
35197
|
const rel = relative3(projectPath, scriptPath);
|
|
35046
|
-
return rel !== "" && !rel.startsWith("..") && !
|
|
35198
|
+
return rel !== "" && !rel.startsWith("..") && !isAbsolute3(rel);
|
|
35047
35199
|
}
|
|
35048
35200
|
var init_breakpoint_note = __esm(() => {
|
|
35049
35201
|
init_errors();
|
|
35050
35202
|
init_paths();
|
|
35051
35203
|
});
|
|
35052
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
|
+
|
|
35053
35236
|
// src/class-note.ts
|
|
35054
|
-
import { mkdirSync as mkdirSync8, readFileSync as
|
|
35055
|
-
import { dirname as
|
|
35237
|
+
import { mkdirSync as mkdirSync8, readFileSync as readFileSync16 } from "node:fs";
|
|
35238
|
+
import { dirname as dirname12, join as join18 } from "node:path";
|
|
35056
35239
|
function classNotePath(projectPath) {
|
|
35057
|
-
return
|
|
35240
|
+
return join18(projectPath, ".godot", "gdharness-classes.json");
|
|
35058
35241
|
}
|
|
35059
35242
|
function readClassNote(projectPath) {
|
|
35060
35243
|
try {
|
|
35061
|
-
const fields = asParams(JSON.parse(
|
|
35244
|
+
const fields = asParams(JSON.parse(readFileSync16(classNotePath(projectPath), "utf8")));
|
|
35062
35245
|
const rebuiltWith = readArray(fields, "rebuiltWith");
|
|
35063
35246
|
if (rebuiltWith === undefined || !rebuiltWith.every((name) => typeof name === "string")) {
|
|
35064
35247
|
return null;
|
|
35065
35248
|
}
|
|
35066
35249
|
const pid = readNumber(fields, "shortListEditorPid");
|
|
35250
|
+
const unfinished = unfinishedScanIn(fields["unseenBeforeAnUnfinishedScan"]);
|
|
35067
35251
|
return {
|
|
35068
35252
|
rebuiltWith: rebuiltWith.map(String),
|
|
35069
|
-
...Object.hasOwn(fields, "shortListEditorPid") ? { shortListEditorPid: pid ?? null } : {}
|
|
35253
|
+
...Object.hasOwn(fields, "shortListEditorPid") ? { shortListEditorPid: pid ?? null } : {},
|
|
35254
|
+
...unfinished === null ? {} : { unseenBeforeAnUnfinishedScan: unfinished }
|
|
35070
35255
|
};
|
|
35071
35256
|
} catch {
|
|
35072
35257
|
return null;
|
|
35073
35258
|
}
|
|
35074
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
|
+
}
|
|
35075
35271
|
function writeClassNote(projectPath, note) {
|
|
35272
|
+
const path = classNotePath(projectPath);
|
|
35076
35273
|
try {
|
|
35077
|
-
|
|
35078
|
-
|
|
35079
|
-
|
|
35080
|
-
|
|
35081
|
-
|
|
35274
|
+
mkdirSync8(dirname12(path), { recursive: true });
|
|
35275
|
+
} catch {
|
|
35276
|
+
return;
|
|
35277
|
+
}
|
|
35278
|
+
writeWhole(path, `${JSON.stringify(note, null, 2)}
|
|
35279
|
+
`);
|
|
35082
35280
|
}
|
|
35083
|
-
var init_class_note = () => {
|
|
35281
|
+
var init_class_note = __esm(() => {
|
|
35282
|
+
init_whole_file();
|
|
35283
|
+
});
|
|
35084
35284
|
|
|
35085
35285
|
// src/config-pin.ts
|
|
35086
|
-
import { existsSync as
|
|
35087
|
-
import { join as
|
|
35286
|
+
import { existsSync as existsSync15, readFileSync as readFileSync17 } from "node:fs";
|
|
35287
|
+
import { join as join19 } from "node:path";
|
|
35088
35288
|
function versionsNamedInConfig(projectPath) {
|
|
35089
|
-
const config =
|
|
35090
|
-
if (!
|
|
35289
|
+
const config = join19(projectPath, ".mcp.json");
|
|
35290
|
+
if (!existsSync15(config)) {
|
|
35091
35291
|
return [];
|
|
35092
35292
|
}
|
|
35093
35293
|
let text;
|
|
35094
35294
|
try {
|
|
35095
|
-
text =
|
|
35295
|
+
text = readFileSync17(config, "utf8");
|
|
35096
35296
|
JSON.parse(text);
|
|
35097
35297
|
} catch {
|
|
35098
35298
|
return [];
|
|
@@ -35113,30 +35313,30 @@ var init_config_pin = __esm(() => {
|
|
|
35113
35313
|
});
|
|
35114
35314
|
|
|
35115
35315
|
// src/cut-test-run.ts
|
|
35116
|
-
import { existsSync as
|
|
35117
|
-
import { dirname as
|
|
35316
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync9, readFileSync as readFileSync18, rmSync as rmSync9, writeFileSync as writeFileSync11 } from "node:fs";
|
|
35317
|
+
import { dirname as dirname13, join as join20 } from "node:path";
|
|
35118
35318
|
function keptAt(projectPath) {
|
|
35119
|
-
return
|
|
35319
|
+
return join20(projectPath, ".godot", "gdharness-reports", "cut-run.json");
|
|
35120
35320
|
}
|
|
35121
35321
|
function keepCutTestRun(projectPath, run) {
|
|
35122
35322
|
const path = keptAt(projectPath);
|
|
35123
35323
|
try {
|
|
35124
|
-
mkdirSync9(
|
|
35324
|
+
mkdirSync9(dirname13(path), { recursive: true });
|
|
35125
35325
|
writeFileSync11(path, `${JSON.stringify(run, null, 2)}
|
|
35126
35326
|
`, "utf8");
|
|
35127
35327
|
} catch {}
|
|
35128
35328
|
}
|
|
35129
35329
|
function takeCutTestRun(projectPath) {
|
|
35130
35330
|
const path = keptAt(projectPath);
|
|
35131
|
-
if (!
|
|
35331
|
+
if (!existsSync16(path)) {
|
|
35132
35332
|
return null;
|
|
35133
35333
|
}
|
|
35134
35334
|
try {
|
|
35135
|
-
const run = JSON.parse(
|
|
35136
|
-
|
|
35335
|
+
const run = JSON.parse(readFileSync18(path, "utf8"));
|
|
35336
|
+
rmSync9(path, { force: true });
|
|
35137
35337
|
return typeof run.cutAt === "string" && Array.isArray(run.paths) && run.paths.every((path) => typeof path === "string") ? run : null;
|
|
35138
35338
|
} catch {
|
|
35139
|
-
|
|
35339
|
+
rmSync9(path, { force: true });
|
|
35140
35340
|
return null;
|
|
35141
35341
|
}
|
|
35142
35342
|
}
|
|
@@ -35948,25 +36148,25 @@ var init_desktop = () => {};
|
|
|
35948
36148
|
// src/editor-log.ts
|
|
35949
36149
|
import {
|
|
35950
36150
|
closeSync,
|
|
35951
|
-
existsSync as
|
|
36151
|
+
existsSync as existsSync17,
|
|
35952
36152
|
mkdirSync as mkdirSync10,
|
|
35953
36153
|
openSync,
|
|
35954
|
-
readFileSync as
|
|
36154
|
+
readFileSync as readFileSync19,
|
|
35955
36155
|
readSync,
|
|
35956
|
-
rmSync as
|
|
35957
|
-
statSync as
|
|
36156
|
+
rmSync as rmSync10,
|
|
36157
|
+
statSync as statSync8,
|
|
35958
36158
|
writeFileSync as writeFileSync12
|
|
35959
36159
|
} from "node:fs";
|
|
35960
|
-
import { dirname as
|
|
36160
|
+
import { dirname as dirname14, join as join21 } from "node:path";
|
|
35961
36161
|
function editorLogPath(projectPath) {
|
|
35962
|
-
return
|
|
36162
|
+
return join21(projectPath, ".godot", "gdharness-editor.log");
|
|
35963
36163
|
}
|
|
35964
36164
|
function editorLogNotePath(projectPath) {
|
|
35965
|
-
return
|
|
36165
|
+
return join21(projectPath, ".godot", "gdharness-editor.json");
|
|
35966
36166
|
}
|
|
35967
36167
|
function readEditorLogNote(projectPath) {
|
|
35968
36168
|
try {
|
|
35969
|
-
const fields = asParams(JSON.parse(
|
|
36169
|
+
const fields = asParams(JSON.parse(readFileSync19(editorLogNotePath(projectPath), "utf8")));
|
|
35970
36170
|
const pid = readNumber(fields, "pid");
|
|
35971
36171
|
const startedAt = readString(fields, "startedAt");
|
|
35972
36172
|
return pid !== undefined && Number.isInteger(pid) && pid > 0 && startedAt !== undefined ? { pid, startedAt } : null;
|
|
@@ -35977,15 +36177,15 @@ function readEditorLogNote(projectPath) {
|
|
|
35977
36177
|
function clearEditorLog(projectPath) {
|
|
35978
36178
|
try {
|
|
35979
36179
|
const path = editorLogPath(projectPath);
|
|
35980
|
-
mkdirSync10(
|
|
35981
|
-
|
|
35982
|
-
|
|
36180
|
+
mkdirSync10(dirname14(path), { recursive: true });
|
|
36181
|
+
rmSync10(path, { force: true });
|
|
36182
|
+
rmSync10(editorLogNotePath(projectPath), { force: true });
|
|
35983
36183
|
} catch {}
|
|
35984
36184
|
}
|
|
35985
36185
|
function writeEditorLogNote(projectPath, pid) {
|
|
35986
36186
|
try {
|
|
35987
36187
|
const path = editorLogNotePath(projectPath);
|
|
35988
|
-
mkdirSync10(
|
|
36188
|
+
mkdirSync10(dirname14(path), { recursive: true });
|
|
35989
36189
|
writeFileSync12(path, JSON.stringify({ pid, startedAt: new Date().toISOString() }, null, 2), "utf8");
|
|
35990
36190
|
} catch {}
|
|
35991
36191
|
}
|
|
@@ -36007,7 +36207,7 @@ function editorConsole(projectPath, editorPid, openedByAServer) {
|
|
|
36007
36207
|
return { kind: "older server", notedPid: noted?.pid ?? null };
|
|
36008
36208
|
}
|
|
36009
36209
|
const path = editorLogPath(projectPath);
|
|
36010
|
-
if (!
|
|
36210
|
+
if (!existsSync17(path)) {
|
|
36011
36211
|
return { kind: "not written" };
|
|
36012
36212
|
}
|
|
36013
36213
|
const log = new GameLog;
|
|
@@ -36063,9 +36263,9 @@ function foldProgress(text) {
|
|
|
36063
36263
|
`);
|
|
36064
36264
|
}
|
|
36065
36265
|
function theFirstOf(path) {
|
|
36066
|
-
const whole =
|
|
36266
|
+
const whole = statSync8(path).size;
|
|
36067
36267
|
if (whole <= MOST_CONSOLE_BYTES) {
|
|
36068
|
-
return { text:
|
|
36268
|
+
return { text: readFileSync19(path, "utf8") };
|
|
36069
36269
|
}
|
|
36070
36270
|
const handle = openSync(path, "r");
|
|
36071
36271
|
try {
|
|
@@ -39202,12 +39402,21 @@ function getDefaultBridge(ownProject = null) {
|
|
|
39202
39402
|
defaultBridge ??= new GodotBridge(resolveDefaultBridgePort(), resolveDefaultBridgeHost(), DEFAULT_TIMEOUT_MS, ownProject);
|
|
39203
39403
|
return defaultBridge;
|
|
39204
39404
|
}
|
|
39205
|
-
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;
|
|
39206
39406
|
var init_godot_bridge = __esm(() => {
|
|
39207
39407
|
init_wrapper();
|
|
39208
39408
|
init_errors();
|
|
39209
39409
|
init_paths();
|
|
39210
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
|
+
};
|
|
39211
39420
|
GodotBridge = class GodotBridge extends EventEmitter {
|
|
39212
39421
|
listeningSince = null;
|
|
39213
39422
|
httpServer = null;
|
|
@@ -39545,7 +39754,7 @@ var init_godot_bridge = __esm(() => {
|
|
|
39545
39754
|
return new Promise((resolve, reject) => {
|
|
39546
39755
|
const timeout = setTimeout(() => {
|
|
39547
39756
|
this.pendingRequests.delete(requestId);
|
|
39548
|
-
reject(new
|
|
39757
|
+
reject(new BridgeTimeout(toolName, this.timeoutMs));
|
|
39549
39758
|
}, this.timeoutMs);
|
|
39550
39759
|
this.pendingRequests.set(requestId, {
|
|
39551
39760
|
toolName,
|
|
@@ -40273,19 +40482,19 @@ var DEBUG_PREFIX = "debug ";
|
|
|
40273
40482
|
var init_keeper_channel = () => {};
|
|
40274
40483
|
|
|
40275
40484
|
// src/lifecycle-log.ts
|
|
40276
|
-
import { appendFileSync, mkdirSync as mkdirSync11, renameSync, statSync as
|
|
40277
|
-
import { dirname as
|
|
40485
|
+
import { appendFileSync, mkdirSync as mkdirSync11, renameSync as renameSync3, statSync as statSync9 } from "node:fs";
|
|
40486
|
+
import { dirname as dirname15, join as join22 } from "node:path";
|
|
40278
40487
|
import process10 from "node:process";
|
|
40279
40488
|
function lifecycleLogPath() {
|
|
40280
|
-
return
|
|
40489
|
+
return join22(runtimeDirectory(), "servers.log");
|
|
40281
40490
|
}
|
|
40282
40491
|
function writeLifecycle(event, fields = {}) {
|
|
40283
40492
|
const path = lifecycleLogPath();
|
|
40284
40493
|
try {
|
|
40285
|
-
mkdirSync11(
|
|
40286
|
-
if ((
|
|
40494
|
+
mkdirSync11(dirname15(path), { recursive: true });
|
|
40495
|
+
if ((statSync9(path, { throwIfNoEntry: false })?.size ?? 0) > LIFECYCLE_LOG_LIMIT) {
|
|
40287
40496
|
try {
|
|
40288
|
-
|
|
40497
|
+
renameSync3(path, `${path}.1`);
|
|
40289
40498
|
} catch {}
|
|
40290
40499
|
}
|
|
40291
40500
|
const line = {
|
|
@@ -40320,10 +40529,10 @@ var init_lifecycle_log = __esm(() => {
|
|
|
40320
40529
|
});
|
|
40321
40530
|
|
|
40322
40531
|
// src/lsp_client.ts
|
|
40323
|
-
import { existsSync as
|
|
40532
|
+
import { existsSync as existsSync18, realpathSync as realpathSync4 } from "node:fs";
|
|
40324
40533
|
import { readFile, realpath } from "node:fs/promises";
|
|
40325
40534
|
import { createConnection as createConnection3 } from "node:net";
|
|
40326
|
-
import { dirname as
|
|
40535
|
+
import { dirname as dirname16, join as join23, resolve as resolve5 } from "node:path";
|
|
40327
40536
|
import { setTimeout as delay3 } from "node:timers/promises";
|
|
40328
40537
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
40329
40538
|
function initializeTimeoutMs() {
|
|
@@ -40485,7 +40694,7 @@ class GodotLSPClient {
|
|
|
40485
40694
|
if (this.initialized) {
|
|
40486
40695
|
return;
|
|
40487
40696
|
}
|
|
40488
|
-
const rootPath = this.rootPath ??
|
|
40697
|
+
const rootPath = this.rootPath ?? dirname16(resolve5(filePath));
|
|
40489
40698
|
await this.initialize(rootPath);
|
|
40490
40699
|
}
|
|
40491
40700
|
async sendRequest(method, params, timeoutMs = this.timeouts.requestMs) {
|
|
@@ -40986,7 +41195,7 @@ async function resolveLSPPaths(projectPathValue, scriptPathValue) {
|
|
|
40986
41195
|
} catch {
|
|
40987
41196
|
throw new ArgumentRefusal(`Project path does not exist: ${requestedProjectPath}`);
|
|
40988
41197
|
}
|
|
40989
|
-
if (!
|
|
41198
|
+
if (!existsSync18(join23(projectPath, "project.godot"))) {
|
|
40990
41199
|
throw new ArgumentRefusal(`Not a Godot project: ${projectPath}. Point projectPath at the directory holding project.godot.`);
|
|
40991
41200
|
}
|
|
40992
41201
|
const contained = resolveWithinProject(projectPath, scriptPathValue);
|
|
@@ -41146,7 +41355,6 @@ var init_lsp_client = __esm(() => {
|
|
|
41146
41355
|
});
|
|
41147
41356
|
|
|
41148
41357
|
// src/outside.ts
|
|
41149
|
-
import { spawn as spawn3 } from "node:child_process";
|
|
41150
41358
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
41151
41359
|
function changesFrom(env, base) {
|
|
41152
41360
|
const changes = {};
|
|
@@ -41179,9 +41387,8 @@ function readLaunched(printed) {
|
|
|
41179
41387
|
}
|
|
41180
41388
|
async function launchOutsideTheTree(spec, runtime = process.execPath, keeper = KEEPER_SCRIPT) {
|
|
41181
41389
|
return await new Promise((resolve) => {
|
|
41182
|
-
const launcher =
|
|
41183
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
41184
|
-
windowsHide: true
|
|
41390
|
+
const launcher = spawnHidden(runtime, [keeper, "launch"], {
|
|
41391
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
41185
41392
|
});
|
|
41186
41393
|
sendSpec(launcher, {
|
|
41187
41394
|
command: spec.command,
|
|
@@ -41210,6 +41417,7 @@ async function launchOutsideTheTree(spec, runtime = process.execPath, keeper = K
|
|
|
41210
41417
|
}
|
|
41211
41418
|
var KEEPER_SCRIPT;
|
|
41212
41419
|
var init_outside = __esm(() => {
|
|
41420
|
+
init_spawn();
|
|
41213
41421
|
KEEPER_SCRIPT = fileURLToPath4(new URL("./keeper.js", import.meta.url));
|
|
41214
41422
|
});
|
|
41215
41423
|
|
|
@@ -41250,8 +41458,6 @@ var STARTING_MS = 20000;
|
|
|
41250
41458
|
var init_port_probe = () => {};
|
|
41251
41459
|
|
|
41252
41460
|
// src/process-time.ts
|
|
41253
|
-
import { execFile as execFile3 } from "node:child_process";
|
|
41254
|
-
import { promisify as promisify3 } from "node:util";
|
|
41255
41461
|
async function cpuSecondsOf(pid) {
|
|
41256
41462
|
try {
|
|
41257
41463
|
return process.platform === "win32" ? await secondsOnWindows(pid) : await askPosix2(pid);
|
|
@@ -41265,7 +41471,7 @@ async function secondsOnWindows(pid) {
|
|
|
41265
41471
|
return Number.isFinite(seconds) ? seconds : undefined;
|
|
41266
41472
|
}
|
|
41267
41473
|
async function askPosix2(pid) {
|
|
41268
|
-
const { stdout } = await
|
|
41474
|
+
const { stdout } = await runHidden("ps", ["-o", "time=", "-p", String(pid)], { timeout: ASK_TIMEOUT_MS2 });
|
|
41269
41475
|
return secondsFromClock(stdout.trim());
|
|
41270
41476
|
}
|
|
41271
41477
|
function secondsFromClock(printed) {
|
|
@@ -41283,10 +41489,10 @@ function secondsFromClock(printed) {
|
|
|
41283
41489
|
const extra = days === null ? 0 : Number(days) * 86400;
|
|
41284
41490
|
return Number.isFinite(extra) ? seconds + extra : undefined;
|
|
41285
41491
|
}
|
|
41286
|
-
var
|
|
41492
|
+
var ASK_TIMEOUT_MS2 = 6000;
|
|
41287
41493
|
var init_process_time = __esm(() => {
|
|
41494
|
+
init_spawn();
|
|
41288
41495
|
init_windows_ask();
|
|
41289
|
-
run3 = promisify3(execFile3);
|
|
41290
41496
|
});
|
|
41291
41497
|
|
|
41292
41498
|
// src/prompts.ts
|
|
@@ -41426,8 +41632,8 @@ var init_prompts = __esm(() => {
|
|
|
41426
41632
|
});
|
|
41427
41633
|
|
|
41428
41634
|
// src/rename.ts
|
|
41429
|
-
import { existsSync as
|
|
41430
|
-
import { dirname as
|
|
41635
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync12, readFileSync as readFileSync20, renameSync as renameSync4, unlinkSync as unlinkSync3, writeFileSync as writeFileSync13 } from "node:fs";
|
|
41636
|
+
import { dirname as dirname17, posix as posix2 } from "node:path";
|
|
41431
41637
|
|
|
41432
41638
|
class Script {
|
|
41433
41639
|
path;
|
|
@@ -42128,26 +42334,26 @@ function rewritesOf(plan, texts, move) {
|
|
|
42128
42334
|
}
|
|
42129
42335
|
function applyRewrites(rewrites) {
|
|
42130
42336
|
for (const rewrite of rewrites) {
|
|
42131
|
-
const now =
|
|
42337
|
+
const now = readFileSync20(rewrite.absolute, "utf8");
|
|
42132
42338
|
if (now !== rewrite.before) {
|
|
42133
42339
|
throw new Error(`${rewrite.path} changed on disk while the rename was being worked out; nothing was written`);
|
|
42134
42340
|
}
|
|
42135
|
-
if (rewrite.writeTo !== rewrite.absolute &&
|
|
42341
|
+
if (rewrite.writeTo !== rewrite.absolute && existsSync19(rewrite.writeTo)) {
|
|
42136
42342
|
throw new Error(`${rewrite.writeTo} already exists; nothing was written`);
|
|
42137
42343
|
}
|
|
42138
42344
|
}
|
|
42139
42345
|
const done = [];
|
|
42140
42346
|
try {
|
|
42141
42347
|
for (const rewrite of rewrites) {
|
|
42142
|
-
mkdirSync12(
|
|
42348
|
+
mkdirSync12(dirname17(rewrite.writeTo), { recursive: true });
|
|
42143
42349
|
const staging = `${rewrite.writeTo}.gdharness-rename`;
|
|
42144
42350
|
writeFileSync13(staging, rewrite.after, "utf8");
|
|
42145
|
-
|
|
42351
|
+
renameSync4(staging, rewrite.writeTo);
|
|
42146
42352
|
let movedUid = false;
|
|
42147
42353
|
if (rewrite.writeTo !== rewrite.absolute) {
|
|
42148
|
-
|
|
42149
|
-
if (
|
|
42150
|
-
|
|
42354
|
+
unlinkSync3(rewrite.absolute);
|
|
42355
|
+
if (existsSync19(`${rewrite.absolute}.uid`)) {
|
|
42356
|
+
renameSync4(`${rewrite.absolute}.uid`, `${rewrite.writeTo}.uid`);
|
|
42151
42357
|
movedUid = true;
|
|
42152
42358
|
}
|
|
42153
42359
|
}
|
|
@@ -42158,9 +42364,9 @@ function applyRewrites(rewrites) {
|
|
|
42158
42364
|
try {
|
|
42159
42365
|
writeFileSync13(rewrite.absolute, rewrite.before, "utf8");
|
|
42160
42366
|
if (rewrite.writeTo !== rewrite.absolute) {
|
|
42161
|
-
|
|
42367
|
+
unlinkSync3(rewrite.writeTo);
|
|
42162
42368
|
if (movedUid) {
|
|
42163
|
-
|
|
42369
|
+
renameSync4(`${rewrite.writeTo}.uid`, `${rewrite.absolute}.uid`);
|
|
42164
42370
|
}
|
|
42165
42371
|
}
|
|
42166
42372
|
} catch {}
|
|
@@ -42192,8 +42398,8 @@ var init_rename = __esm(() => {
|
|
|
42192
42398
|
});
|
|
42193
42399
|
|
|
42194
42400
|
// src/rename-symbol.ts
|
|
42195
|
-
import { existsSync as
|
|
42196
|
-
import { basename as
|
|
42401
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync13, readdirSync as readdirSync10, realpathSync as realpathSync5, rmSync as rmSync11, writeFileSync as writeFileSync14 } from "node:fs";
|
|
42402
|
+
import { basename as basename6, dirname as dirname18, join as join24, relative as relative4, resolve as resolve6 } from "node:path";
|
|
42197
42403
|
function resourcePathOf(projectPath, absolute) {
|
|
42198
42404
|
const inside = relative4(onDiskSpelling(projectPath), onDiskSpelling(absolute));
|
|
42199
42405
|
if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
|
|
@@ -42206,13 +42412,13 @@ function onDiskSpelling(path) {
|
|
|
42206
42412
|
let at = resolve6(path);
|
|
42207
42413
|
for (;; ) {
|
|
42208
42414
|
try {
|
|
42209
|
-
return
|
|
42415
|
+
return join24(realpathSync5.native(at), ...missing.reverse());
|
|
42210
42416
|
} catch {
|
|
42211
|
-
const parent =
|
|
42417
|
+
const parent = dirname18(at);
|
|
42212
42418
|
if (parent === at) {
|
|
42213
42419
|
return resolve6(path);
|
|
42214
42420
|
}
|
|
42215
|
-
missing.push(
|
|
42421
|
+
missing.push(basename6(at));
|
|
42216
42422
|
at = parent;
|
|
42217
42423
|
}
|
|
42218
42424
|
}
|
|
@@ -42259,7 +42465,7 @@ async function renameSymbol(request, services) {
|
|
|
42259
42465
|
reason: `newScriptPath must be a .gd path inside the project, and ${request.newScriptPath} is not.`
|
|
42260
42466
|
};
|
|
42261
42467
|
}
|
|
42262
|
-
if (
|
|
42468
|
+
if (existsSync20(request.newScriptPath)) {
|
|
42263
42469
|
return { ok: false, reason: `${moveTo} already exists, so the script cannot be moved there.` };
|
|
42264
42470
|
}
|
|
42265
42471
|
}
|
|
@@ -42435,7 +42641,7 @@ async function renameSymbol(request, services) {
|
|
|
42435
42641
|
scripts,
|
|
42436
42642
|
from: declaringPath,
|
|
42437
42643
|
to: moveTo,
|
|
42438
|
-
exists: (path) =>
|
|
42644
|
+
exists: (path) => existsSync20(`${projectPath}/${path.slice("res://".length)}`)
|
|
42439
42645
|
}, plan);
|
|
42440
42646
|
}
|
|
42441
42647
|
plan = mentionIgnored(plan, ignoredTexts(projectPath), symbol, moveTo === null ? null : declaringPath);
|
|
@@ -42634,15 +42840,15 @@ ${mention.kind}`;
|
|
|
42634
42840
|
};
|
|
42635
42841
|
}
|
|
42636
42842
|
function writeLeftAloneDetail(projectPath, symbol, detail) {
|
|
42637
|
-
const directory =
|
|
42843
|
+
const directory = join24(projectPath, ".godot", "gdharness-renames");
|
|
42638
42844
|
mkdirSync13(directory, { recursive: true });
|
|
42639
42845
|
detailWrites += 1;
|
|
42640
|
-
const path =
|
|
42846
|
+
const path = join24(directory, `${String(Date.now())}-${String(detailWrites).padStart(6, "0")}-${String(process.pid)}-${symbol}.json`);
|
|
42641
42847
|
writeFileSync14(path, `${JSON.stringify(detail, null, 2)}
|
|
42642
42848
|
`, "utf8");
|
|
42643
|
-
const kept =
|
|
42849
|
+
const kept = readdirSync10(directory).filter((name) => name.endsWith(".json")).sort().reverse();
|
|
42644
42850
|
for (const old of kept.slice(10)) {
|
|
42645
|
-
|
|
42851
|
+
rmSync11(join24(directory, old), { force: true });
|
|
42646
42852
|
}
|
|
42647
42853
|
return path;
|
|
42648
42854
|
}
|
|
@@ -42682,15 +42888,15 @@ var init_rename_symbol = __esm(() => {
|
|
|
42682
42888
|
});
|
|
42683
42889
|
|
|
42684
42890
|
// src/restart-note.ts
|
|
42685
|
-
import { mkdirSync as mkdirSync14, readFileSync as
|
|
42686
|
-
import { dirname as
|
|
42891
|
+
import { mkdirSync as mkdirSync14, readFileSync as readFileSync21, rmSync as rmSync12, writeFileSync as writeFileSync15 } from "node:fs";
|
|
42892
|
+
import { dirname as dirname19, join as join25 } from "node:path";
|
|
42687
42893
|
function restartNotePath(projectPath) {
|
|
42688
|
-
return
|
|
42894
|
+
return join25(projectPath, ".godot", "gdharness-restart.json");
|
|
42689
42895
|
}
|
|
42690
42896
|
function noteRestartBegun(note) {
|
|
42691
42897
|
const path = restartNotePath(note.projectPath);
|
|
42692
42898
|
try {
|
|
42693
|
-
mkdirSync14(
|
|
42899
|
+
mkdirSync14(dirname19(path), { recursive: true });
|
|
42694
42900
|
writeFileSync15(path, `${JSON.stringify(note, null, 2)}
|
|
42695
42901
|
`, "utf8");
|
|
42696
42902
|
} catch (error) {
|
|
@@ -42699,7 +42905,7 @@ function noteRestartBegun(note) {
|
|
|
42699
42905
|
}
|
|
42700
42906
|
function restartOwed(projectPath) {
|
|
42701
42907
|
try {
|
|
42702
|
-
const parsed = JSON.parse(
|
|
42908
|
+
const parsed = JSON.parse(readFileSync21(restartNotePath(projectPath), "utf8"));
|
|
42703
42909
|
if (typeof parsed !== "object" || parsed === null) {
|
|
42704
42910
|
return null;
|
|
42705
42911
|
}
|
|
@@ -42714,7 +42920,7 @@ function restartOwed(projectPath) {
|
|
|
42714
42920
|
}
|
|
42715
42921
|
function restartSettled(projectPath) {
|
|
42716
42922
|
try {
|
|
42717
|
-
|
|
42923
|
+
rmSync12(restartNotePath(projectPath), { force: true });
|
|
42718
42924
|
} catch (error) {
|
|
42719
42925
|
console.error(`[SERVER] Could not clear the restart note: ${errorMessage(error)}`);
|
|
42720
42926
|
}
|
|
@@ -42723,58 +42929,25 @@ var init_restart_note = __esm(() => {
|
|
|
42723
42929
|
init_errors();
|
|
42724
42930
|
});
|
|
42725
42931
|
|
|
42726
|
-
// src/whole-file.ts
|
|
42727
|
-
import { renameSync as renameSync3, rmSync as rmSync12, writeFileSync as writeFileSync16 } from "node:fs";
|
|
42728
|
-
function writeWhole(path, text) {
|
|
42729
|
-
const partial = `${path}.${process.pid}.partial`;
|
|
42730
|
-
try {
|
|
42731
|
-
writeFileSync16(partial, text, "utf8");
|
|
42732
|
-
} catch {
|
|
42733
|
-
return false;
|
|
42734
|
-
}
|
|
42735
|
-
const until = Date.now() + HELD_FILE_MS;
|
|
42736
|
-
for (;; ) {
|
|
42737
|
-
try {
|
|
42738
|
-
renameSync3(partial, path);
|
|
42739
|
-
return true;
|
|
42740
|
-
} catch (error) {
|
|
42741
|
-
const code = error.code ?? "";
|
|
42742
|
-
if (!HELD_BY_A_READER.has(code) || Date.now() >= until) {
|
|
42743
|
-
try {
|
|
42744
|
-
rmSync12(partial, { force: true });
|
|
42745
|
-
} catch {}
|
|
42746
|
-
return false;
|
|
42747
|
-
}
|
|
42748
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, HELD_FILE_RETRY_MS);
|
|
42749
|
-
}
|
|
42750
|
-
}
|
|
42751
|
-
}
|
|
42752
|
-
var HELD_FILE_MS = 1000, HELD_FILE_RETRY_MS = 10, HELD_BY_A_READER;
|
|
42753
|
-
var init_whole_file = __esm(() => {
|
|
42754
|
-
HELD_BY_A_READER = new Set(["EPERM", "EACCES", "EBUSY"]);
|
|
42755
|
-
});
|
|
42756
|
-
|
|
42757
42932
|
// src/run-record.ts
|
|
42758
|
-
import { execFile as execFile4 } from "node:child_process";
|
|
42759
42933
|
import { createHash as createHash4 } from "node:crypto";
|
|
42760
|
-
import { mkdirSync as mkdirSync15, openSync as openSync2, readdirSync as
|
|
42761
|
-
import { basename as
|
|
42762
|
-
import { promisify as promisify4 } from "node:util";
|
|
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";
|
|
42935
|
+
import { basename as basename7, dirname as dirname20, join as join26, resolve as resolve7 } from "node:path";
|
|
42763
42936
|
function runsDirectory() {
|
|
42764
|
-
return
|
|
42937
|
+
return join26(runtimeDirectory(), "runs");
|
|
42765
42938
|
}
|
|
42766
42939
|
function realAsFarAsItExists(path) {
|
|
42767
42940
|
const missing = [];
|
|
42768
42941
|
let at = path;
|
|
42769
42942
|
for (;; ) {
|
|
42770
42943
|
try {
|
|
42771
|
-
return
|
|
42944
|
+
return join26(realpathSync6.native(at), ...missing);
|
|
42772
42945
|
} catch {
|
|
42773
|
-
const parent =
|
|
42946
|
+
const parent = dirname20(at);
|
|
42774
42947
|
if (parent === at) {
|
|
42775
42948
|
return path;
|
|
42776
42949
|
}
|
|
42777
|
-
missing.unshift(
|
|
42950
|
+
missing.unshift(basename7(at));
|
|
42778
42951
|
at = parent;
|
|
42779
42952
|
}
|
|
42780
42953
|
}
|
|
@@ -42785,10 +42958,10 @@ function projectKey(projectPath) {
|
|
|
42785
42958
|
return createHash4("sha256").update(folded.replaceAll("\\", "/")).digest("hex").slice(0, 16);
|
|
42786
42959
|
}
|
|
42787
42960
|
function runRecordPath(projectPath, directory = runtimeDirectory()) {
|
|
42788
|
-
return
|
|
42961
|
+
return join26(directory, "runs", `note-${projectKey(projectPath)}.json`);
|
|
42789
42962
|
}
|
|
42790
42963
|
function latestRunPath(servedBy, directory = runtimeDirectory()) {
|
|
42791
|
-
return
|
|
42964
|
+
return join26(directory, "runs", `latest-${projectKey(servedBy)}.json`);
|
|
42792
42965
|
}
|
|
42793
42966
|
function writeLatestRun(servedBy, latest) {
|
|
42794
42967
|
try {
|
|
@@ -42798,7 +42971,7 @@ function writeLatestRun(servedBy, latest) {
|
|
|
42798
42971
|
}
|
|
42799
42972
|
function readLatestRun(servedBy, directory = runtimeDirectory()) {
|
|
42800
42973
|
try {
|
|
42801
|
-
const fields = JSON.parse(
|
|
42974
|
+
const fields = JSON.parse(readFileSync22(latestRunPath(servedBy, directory), "utf8"));
|
|
42802
42975
|
if (typeof fields !== "object" || fields === null) {
|
|
42803
42976
|
return null;
|
|
42804
42977
|
}
|
|
@@ -42813,17 +42986,17 @@ function readLatestRun(servedBy, directory = runtimeDirectory()) {
|
|
|
42813
42986
|
}
|
|
42814
42987
|
function openTranscript(startedAt) {
|
|
42815
42988
|
mkdirSync15(runsDirectory(), { recursive: true });
|
|
42816
|
-
const path =
|
|
42989
|
+
const path = join26(runsDirectory(), `run-${startedAt}.log`);
|
|
42817
42990
|
return { path, fd: openSync2(path, "a") };
|
|
42818
42991
|
}
|
|
42819
42992
|
function profilePath(startedAt) {
|
|
42820
|
-
return
|
|
42993
|
+
return join26(runsDirectory(), `run-${startedAt}.profile.json`);
|
|
42821
42994
|
}
|
|
42822
42995
|
function noteIsAbout(note, playing) {
|
|
42823
42996
|
return note.play !== undefined && playing !== null && note.play.play === playing.play && note.play.editorPid === playing.editorPid;
|
|
42824
42997
|
}
|
|
42825
42998
|
function editorNotePath(projectPath, directory = runtimeDirectory()) {
|
|
42826
|
-
return
|
|
42999
|
+
return join26(directory, "runs", `editor-${projectKey(projectPath)}.json`);
|
|
42827
43000
|
}
|
|
42828
43001
|
function writeEditorRunNote(note) {
|
|
42829
43002
|
mkdirSync15(runsDirectory(), { recursive: true });
|
|
@@ -42834,7 +43007,7 @@ function writeEditorRunNote(note) {
|
|
|
42834
43007
|
}
|
|
42835
43008
|
function readEditorRunNote(projectPath) {
|
|
42836
43009
|
for (const directory of runtimeDirectories()) {
|
|
42837
|
-
const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(
|
|
43010
|
+
const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(join26(directory, "runs", "editor-run.json"));
|
|
42838
43011
|
if (note !== null && isSameDirectory(note.projectPath, projectPath)) {
|
|
42839
43012
|
return note;
|
|
42840
43013
|
}
|
|
@@ -42844,7 +43017,7 @@ function readEditorRunNote(projectPath) {
|
|
|
42844
43017
|
function editorNoteAt(path) {
|
|
42845
43018
|
let parsed;
|
|
42846
43019
|
try {
|
|
42847
|
-
parsed = JSON.parse(
|
|
43020
|
+
parsed = JSON.parse(readFileSync22(path, "utf8"));
|
|
42848
43021
|
} catch {
|
|
42849
43022
|
return null;
|
|
42850
43023
|
}
|
|
@@ -42868,7 +43041,7 @@ function readRunRecord(projectPath) {
|
|
|
42868
43041
|
if (own !== null) {
|
|
42869
43042
|
return own;
|
|
42870
43043
|
}
|
|
42871
|
-
const shared = recordAt(
|
|
43044
|
+
const shared = recordAt(join26(directory, "runs", SHARED_NOTE));
|
|
42872
43045
|
if (shared !== null && shared.projectPath !== "" && isSameDirectory(shared.projectPath, projectPath)) {
|
|
42873
43046
|
return shared;
|
|
42874
43047
|
}
|
|
@@ -42880,13 +43053,13 @@ function everyRunRecord() {
|
|
|
42880
43053
|
for (const directory of runtimeDirectories()) {
|
|
42881
43054
|
let entries;
|
|
42882
43055
|
try {
|
|
42883
|
-
entries =
|
|
43056
|
+
entries = readdirSync11(join26(directory, "runs"));
|
|
42884
43057
|
} catch {
|
|
42885
43058
|
continue;
|
|
42886
43059
|
}
|
|
42887
43060
|
for (const entry of entries) {
|
|
42888
43061
|
if (entry === SHARED_NOTE || entry.startsWith("note-") && entry.endsWith(".json")) {
|
|
42889
|
-
const record = recordAt(
|
|
43062
|
+
const record = recordAt(join26(directory, "runs", entry));
|
|
42890
43063
|
if (record !== null) {
|
|
42891
43064
|
found.push(record);
|
|
42892
43065
|
}
|
|
@@ -42898,7 +43071,7 @@ function everyRunRecord() {
|
|
|
42898
43071
|
function recordAt(path) {
|
|
42899
43072
|
let parsed;
|
|
42900
43073
|
try {
|
|
42901
|
-
parsed = JSON.parse(
|
|
43074
|
+
parsed = JSON.parse(readFileSync22(path, "utf8"));
|
|
42902
43075
|
} catch {
|
|
42903
43076
|
return null;
|
|
42904
43077
|
}
|
|
@@ -42951,10 +43124,10 @@ async function runningAs(pid) {
|
|
|
42951
43124
|
const began = await startedAt(pid, null);
|
|
42952
43125
|
const when = began === null ? {} : { startedAt: began };
|
|
42953
43126
|
if (process.platform === "linux") {
|
|
42954
|
-
const raw =
|
|
43127
|
+
const raw = readFileSync22(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
|
|
42955
43128
|
return raw === "" ? null : { kind: "commandLine", text: raw, ...when };
|
|
42956
43129
|
}
|
|
42957
|
-
const args = (await
|
|
43130
|
+
const args = (await runHidden("ps", ["-p", String(pid), "-o", "args="], { timeout: 15000 })).stdout.trim();
|
|
42958
43131
|
return args === "" ? null : { kind: "commandLine", text: args, ...when };
|
|
42959
43132
|
} catch {
|
|
42960
43133
|
return null;
|
|
@@ -42963,7 +43136,7 @@ async function runningAs(pid) {
|
|
|
42963
43136
|
async function listeningPid(port) {
|
|
42964
43137
|
try {
|
|
42965
43138
|
if (process.platform === "win32") {
|
|
42966
|
-
const { stdout } = await
|
|
43139
|
+
const { stdout } = await runHidden("netstat", ["-ano"], { timeout: 15000 });
|
|
42967
43140
|
return listeningPidInNetstat(stdout, port);
|
|
42968
43141
|
}
|
|
42969
43142
|
for (const [command, args, pattern] of [
|
|
@@ -42971,7 +43144,7 @@ async function listeningPid(port) {
|
|
|
42971
43144
|
["ss", ["-ltnpH", `sport = :${port}`], /pid=(\d+)/]
|
|
42972
43145
|
]) {
|
|
42973
43146
|
try {
|
|
42974
|
-
const said = (await
|
|
43147
|
+
const said = (await runHidden(command, [...args], { timeout: 15000 })).stdout.trim();
|
|
42975
43148
|
const found = pattern.exec(said);
|
|
42976
43149
|
if (found?.[1] !== undefined) {
|
|
42977
43150
|
return Number(found[1]);
|
|
@@ -43016,7 +43189,7 @@ async function startedAt(pid, windowsAnswer) {
|
|
|
43016
43189
|
return Number.isNaN(when) ? null : when;
|
|
43017
43190
|
}
|
|
43018
43191
|
try {
|
|
43019
|
-
const said = (await
|
|
43192
|
+
const said = (await runHidden("ps", ["-p", String(pid), "-o", "lstart="], { timeout: 15000 })).stdout.trim();
|
|
43020
43193
|
const when = Date.parse(said);
|
|
43021
43194
|
return said === "" || Number.isNaN(when) ? null : when;
|
|
43022
43195
|
} catch {
|
|
@@ -43025,10 +43198,8 @@ async function startedAt(pid, windowsAnswer) {
|
|
|
43025
43198
|
}
|
|
43026
43199
|
async function windowsImage(pid) {
|
|
43027
43200
|
try {
|
|
43028
|
-
const { stdout: csv } = await
|
|
43029
|
-
|
|
43030
|
-
timeout: 15000,
|
|
43031
|
-
windowsHide: true
|
|
43201
|
+
const { stdout: csv } = await runHidden("tasklist", ["/FI", `PID eq ${pid}`, "/FO", "CSV", "/NH"], {
|
|
43202
|
+
timeout: 15000
|
|
43032
43203
|
});
|
|
43033
43204
|
const image = /^"([^"]+)"/.exec(csv.trim())?.[1];
|
|
43034
43205
|
return image === undefined ? { why: "named no process under that number" } : { image };
|
|
@@ -43057,7 +43228,7 @@ function whyNotTheRun(record, running, needed) {
|
|
|
43057
43228
|
if (after > SAME_RUN_WINDOW_MS) {
|
|
43058
43229
|
return `that process started ${Math.round(after / 1000)} seconds after the run had`;
|
|
43059
43230
|
}
|
|
43060
|
-
const engine = record.command === undefined ? null :
|
|
43231
|
+
const engine = record.command === undefined ? null : basename7(record.command);
|
|
43061
43232
|
const said = process.platform === "win32" ? running.text.toLowerCase() : running.text;
|
|
43062
43233
|
const wanted = engine === null || process.platform !== "win32" ? engine : engine.toLowerCase();
|
|
43063
43234
|
if (wanted !== null && !said.includes(wanted)) {
|
|
@@ -43096,7 +43267,7 @@ function judgeAnnouncedGame(running, projectPath, announcedAt) {
|
|
|
43096
43267
|
}
|
|
43097
43268
|
function clearRunRecord(projectPath) {
|
|
43098
43269
|
for (const directory of runtimeDirectories()) {
|
|
43099
|
-
const shared =
|
|
43270
|
+
const shared = join26(directory, "runs", SHARED_NOTE);
|
|
43100
43271
|
const sharedRecord = recordAt(shared);
|
|
43101
43272
|
const ours = sharedRecord !== null && sharedRecord.projectPath !== "" && isSameDirectory(sharedRecord.projectPath, projectPath);
|
|
43102
43273
|
for (const path of [runRecordPath(projectPath, directory), ...ours ? [shared] : []]) {
|
|
@@ -43108,7 +43279,7 @@ function clearRunRecord(projectPath) {
|
|
|
43108
43279
|
}
|
|
43109
43280
|
function ageMs(path, now) {
|
|
43110
43281
|
try {
|
|
43111
|
-
return now -
|
|
43282
|
+
return now - statSync10(path).mtimeMs;
|
|
43112
43283
|
} catch {
|
|
43113
43284
|
return 0;
|
|
43114
43285
|
}
|
|
@@ -43121,7 +43292,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
43121
43292
|
]));
|
|
43122
43293
|
let entries;
|
|
43123
43294
|
try {
|
|
43124
|
-
entries =
|
|
43295
|
+
entries = readdirSync11(directory);
|
|
43125
43296
|
} catch {
|
|
43126
43297
|
return;
|
|
43127
43298
|
}
|
|
@@ -43130,7 +43301,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
43130
43301
|
if (!transcript && !entry.endsWith(".partial")) {
|
|
43131
43302
|
continue;
|
|
43132
43303
|
}
|
|
43133
|
-
const path =
|
|
43304
|
+
const path = join26(directory, entry);
|
|
43134
43305
|
if (kept.has(path) || ageMs(path, now) < keepMs) {
|
|
43135
43306
|
continue;
|
|
43136
43307
|
}
|
|
@@ -43139,19 +43310,19 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
43139
43310
|
} catch {}
|
|
43140
43311
|
}
|
|
43141
43312
|
}
|
|
43142
|
-
var
|
|
43313
|
+
var SHARED_NOTE = "run.json", SAME_RUN_WINDOW_MS = 1e4;
|
|
43143
43314
|
var init_run_record = __esm(() => {
|
|
43144
43315
|
init_errors();
|
|
43145
43316
|
init_paths();
|
|
43146
43317
|
init_process_children();
|
|
43147
43318
|
init_runtime_client();
|
|
43319
|
+
init_spawn();
|
|
43148
43320
|
init_whole_file();
|
|
43149
43321
|
init_windows_ask();
|
|
43150
|
-
execFileAsync = promisify4(execFile4);
|
|
43151
43322
|
});
|
|
43152
43323
|
|
|
43153
43324
|
// src/script-origins.ts
|
|
43154
|
-
import { join as
|
|
43325
|
+
import { join as join27 } from "node:path";
|
|
43155
43326
|
function unresolvedClassIn(message) {
|
|
43156
43327
|
return UNRESOLVED.exec(message)?.[1] ?? null;
|
|
43157
43328
|
}
|
|
@@ -43204,7 +43375,7 @@ function checkWithEngine(godotPath, projectPath) {
|
|
|
43204
43375
|
const ran = await runEngine(godotPath, [
|
|
43205
43376
|
"--headless",
|
|
43206
43377
|
"--log-file",
|
|
43207
|
-
|
|
43378
|
+
join27(logDir, "engine.log"),
|
|
43208
43379
|
"--path",
|
|
43209
43380
|
projectPath,
|
|
43210
43381
|
"--check-only",
|
|
@@ -43249,10 +43420,10 @@ var init_script_origins = __esm(() => {
|
|
|
43249
43420
|
});
|
|
43250
43421
|
|
|
43251
43422
|
// src/script-profile.ts
|
|
43252
|
-
import { readFileSync as
|
|
43423
|
+
import { readFileSync as readFileSync23 } from "node:fs";
|
|
43253
43424
|
function readProfileFile(path) {
|
|
43254
43425
|
try {
|
|
43255
|
-
const parsed = JSON.parse(
|
|
43426
|
+
const parsed = JSON.parse(readFileSync23(path, "utf8"));
|
|
43256
43427
|
return typeof parsed === "object" && typeof parsed.connected === "boolean" ? parsed : null;
|
|
43257
43428
|
} catch {
|
|
43258
43429
|
return null;
|
|
@@ -43423,27 +43594,25 @@ var init_server_instructions = __esm(() => {
|
|
|
43423
43594
|
});
|
|
43424
43595
|
|
|
43425
43596
|
// src/server.ts
|
|
43426
|
-
import { execFile as execFile5, spawn as spawn4 } from "node:child_process";
|
|
43427
43597
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
43428
43598
|
import {
|
|
43429
43599
|
appendFileSync as appendFileSync2,
|
|
43430
43600
|
closeSync as closeSync2,
|
|
43431
43601
|
constants,
|
|
43432
43602
|
copyFileSync,
|
|
43433
|
-
existsSync as
|
|
43603
|
+
existsSync as existsSync21,
|
|
43434
43604
|
fstatSync,
|
|
43435
43605
|
mkdirSync as mkdirSync16,
|
|
43436
43606
|
openSync as openSync3,
|
|
43437
|
-
readdirSync as
|
|
43438
|
-
readFileSync as
|
|
43607
|
+
readdirSync as readdirSync12,
|
|
43608
|
+
readFileSync as readFileSync24,
|
|
43439
43609
|
readSync as readSync2,
|
|
43440
43610
|
rmdirSync as rmdirSync2,
|
|
43441
|
-
statSync as
|
|
43611
|
+
statSync as statSync11
|
|
43442
43612
|
} from "node:fs";
|
|
43443
|
-
import { basename as
|
|
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";
|
|
43444
43614
|
import { setTimeout as delay4 } from "node:timers/promises";
|
|
43445
43615
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
43446
|
-
import { promisify as promisify5 } from "node:util";
|
|
43447
43616
|
function editorRestartTimeoutMs() {
|
|
43448
43617
|
return waitFromEnv("GDHARNESS_EDITOR_RESTART_TIMEOUT_MS", 90000);
|
|
43449
43618
|
}
|
|
@@ -43471,22 +43640,22 @@ function writtenFileRefusal(path, rule) {
|
|
|
43471
43640
|
if (/^(res|user):\/\//.test(path)) {
|
|
43472
43641
|
return `${named} ${path} is a Godot path; ${what} is saved outside the project, at an absolute path on this machine.`;
|
|
43473
43642
|
}
|
|
43474
|
-
if (!
|
|
43643
|
+
if (!isAbsolute4(path)) {
|
|
43475
43644
|
return `${named} ${path} is not absolute; name the whole path, so where it is written does not depend on this server's directory.`;
|
|
43476
43645
|
}
|
|
43477
43646
|
if (!rule.extensions.includes(extname3(path).toLowerCase())) {
|
|
43478
43647
|
return `${named} ${path} does not end in ${rule.extensions.join(" or ")}, which is what ${what} is.`;
|
|
43479
43648
|
}
|
|
43480
|
-
const directory = realPathOr(
|
|
43481
|
-
if (!
|
|
43482
|
-
return `${named}'s directory ${
|
|
43649
|
+
const directory = realPathOr(dirname21(path));
|
|
43650
|
+
if (!existsSync21(directory)) {
|
|
43651
|
+
return `${named}'s directory ${dirname21(path)} does not exist; ${what} creates no directories.`;
|
|
43483
43652
|
}
|
|
43484
|
-
const where =
|
|
43653
|
+
const where = join28(directory, basename8(path));
|
|
43485
43654
|
const inside = rule.projects.find((project) => project !== "" && isWithinRoot(realPathOr(project), where));
|
|
43486
43655
|
if (inside !== undefined) {
|
|
43487
43656
|
return `${named} ${path} is inside the project at ${inside}; ${what} is saved outside it, so it never becomes part of the game.`;
|
|
43488
43657
|
}
|
|
43489
|
-
const taken = rule.writes(where).find((file) =>
|
|
43658
|
+
const taken = rule.writes(where).find((file) => existsSync21(file));
|
|
43490
43659
|
if (taken !== undefined) {
|
|
43491
43660
|
return taken === where ? `${named} ${path} already exists; ${what} is not written over a file. Name a new one.` : `${named} ${path} would write ${taken}, which already exists; ${what} is not written over a file. Name a new one.`;
|
|
43492
43661
|
}
|
|
@@ -43536,6 +43705,9 @@ function captureDetail(payload) {
|
|
|
43536
43705
|
function patienceForFrames(frames, atLeast) {
|
|
43537
43706
|
return Math.max(atLeast, frames / SLOWEST_FRAME_RATE * 1000 + atLeast);
|
|
43538
43707
|
}
|
|
43708
|
+
function withDefined(fields) {
|
|
43709
|
+
return Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined));
|
|
43710
|
+
}
|
|
43539
43711
|
function longerWaitNote(tool, op, waitedMs) {
|
|
43540
43712
|
const spec = TOOL_SPECS.find((one) => one.name === tool);
|
|
43541
43713
|
if (spec !== undefined && argumentsOf(spec, op).includes("timeoutMs")) {
|
|
@@ -43979,9 +44151,35 @@ function notRunNote(notRun, failFast) {
|
|
|
43979
44151
|
const cases = `${notRun} case${notRun === 1 ? "" : "s"}`;
|
|
43980
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.`;
|
|
43981
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
|
+
}
|
|
43982
44179
|
function exportAnswer(ending, asked, before, after) {
|
|
43983
44180
|
const { log } = ending;
|
|
43984
44181
|
const written = after !== null && after !== before;
|
|
44182
|
+
const notCopied = librariesNotCopied(log.all);
|
|
43985
44183
|
const verdict = {
|
|
43986
44184
|
exported: ending.exitCode === 0 && ending.failure === null && log.count("error") === 0 && written,
|
|
43987
44185
|
...asked,
|
|
@@ -43990,6 +44188,10 @@ function exportAnswer(ending, asked, before, after) {
|
|
|
43990
44188
|
failure: ending.failure ?? undefined,
|
|
43991
44189
|
errors: log.count("error"),
|
|
43992
44190
|
warnings: log.count("warning"),
|
|
44191
|
+
...notCopied.length === 0 ? {} : {
|
|
44192
|
+
librariesNotLoaded: notCopied,
|
|
44193
|
+
extensionNote: `The export could not load ${notCopied.join(" and ")}: on Windows an engine running as the editor, which an export is, loads a GDExtension from a ~ copy beside the library, and another engine on this project held that copy: it could not be moved aside before the export, or another engine made a new one in between. Export again once that engine has loaded or exited.`
|
|
44194
|
+
},
|
|
43993
44195
|
...foldedEntriesAnswer(log)
|
|
43994
44196
|
};
|
|
43995
44197
|
if (verdict.exported) {
|
|
@@ -44027,7 +44229,7 @@ function engineFlagsNote(args) {
|
|
|
44027
44229
|
return `${named} ${among.length === 1 ? "is a flag" : "are flags"} the engine reads, and args goes to the game after --, where the engine does not read ${among.length === 1 ? "it" : "them"}. engineArgs hands the engine its own flags; ${among.length === 1 ? "leave it" : "leave them"} in args only for a game that reads ${among.length === 1 ? "that flag" : "those flags"} itself.`;
|
|
44028
44230
|
}
|
|
44029
44231
|
function editorAddonMissing(projectPath) {
|
|
44030
|
-
if (!
|
|
44232
|
+
if (!existsSync21(join28(projectPath, "project.godot"))) {
|
|
44031
44233
|
return null;
|
|
44032
44234
|
}
|
|
44033
44235
|
const report = inspectProject(projectPath);
|
|
@@ -44263,7 +44465,7 @@ function profileFrameFunctions(projectPath) {
|
|
|
44263
44465
|
let projectGodot = null;
|
|
44264
44466
|
if (projectPath !== null) {
|
|
44265
44467
|
try {
|
|
44266
|
-
projectGodot =
|
|
44468
|
+
projectGodot = readFileSync24(join28(projectPath, "project.godot"), "utf8");
|
|
44267
44469
|
} catch {}
|
|
44268
44470
|
}
|
|
44269
44471
|
return frameFunctionsFor(queuedMessagesOf(projectGodot));
|
|
@@ -44287,7 +44489,7 @@ function projectSourceOf(projectPath) {
|
|
|
44287
44489
|
let source = null;
|
|
44288
44490
|
if (inside.ok) {
|
|
44289
44491
|
try {
|
|
44290
|
-
source =
|
|
44492
|
+
source = readFileSync24(inside.absolutePath, "utf8");
|
|
44291
44493
|
} catch {}
|
|
44292
44494
|
}
|
|
44293
44495
|
read.set(script, source);
|
|
@@ -44437,7 +44639,7 @@ function foldedEntriesAnswer(log, floor = "warning") {
|
|
|
44437
44639
|
}
|
|
44438
44640
|
function gdUnitFailsOnScriptErrors(projectPath) {
|
|
44439
44641
|
try {
|
|
44440
|
-
return settingKeys(
|
|
44642
|
+
return settingKeys(readFileSync24(join28(projectPath, "project.godot"), "utf8")).get("gdunit4/report/godot/script_error") !== false;
|
|
44441
44643
|
} catch {
|
|
44442
44644
|
return true;
|
|
44443
44645
|
}
|
|
@@ -44452,13 +44654,13 @@ function uncaughtNote(uncaught) {
|
|
|
44452
44654
|
}
|
|
44453
44655
|
function elsewhereIn(projectPath, asked) {
|
|
44454
44656
|
const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
|
|
44455
|
-
if (wanted === "" ||
|
|
44657
|
+
if (wanted === "" || existsSync21(join28(projectPath, wanted))) {
|
|
44456
44658
|
return "";
|
|
44457
44659
|
}
|
|
44458
44660
|
const root = (wanted.split("/")[0] ?? "").toLowerCase();
|
|
44459
44661
|
let near = [];
|
|
44460
44662
|
try {
|
|
44461
|
-
near =
|
|
44663
|
+
near = readdirSync12(projectPath, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.toLowerCase() !== root).map((entry) => entry.name).filter((name) => name.toLowerCase().startsWith("test") || root.startsWith(name.toLowerCase()));
|
|
44462
44664
|
} catch {
|
|
44463
44665
|
return "";
|
|
44464
44666
|
}
|
|
@@ -44542,23 +44744,23 @@ function scriptsToDiagnose(projectPath, given) {
|
|
|
44542
44744
|
}
|
|
44543
44745
|
function isDirectory(path) {
|
|
44544
44746
|
try {
|
|
44545
|
-
return
|
|
44747
|
+
return statSync11(path).isDirectory();
|
|
44546
44748
|
} catch {
|
|
44547
44749
|
return false;
|
|
44548
44750
|
}
|
|
44549
44751
|
}
|
|
44550
44752
|
function scriptsUnder(directory, prefix) {
|
|
44551
|
-
if (
|
|
44753
|
+
if (existsSync21(join28(directory, ".gdignore"))) {
|
|
44552
44754
|
return [];
|
|
44553
44755
|
}
|
|
44554
44756
|
const found = [];
|
|
44555
|
-
for (const entry of
|
|
44757
|
+
for (const entry of readdirSync12(directory, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : 1)) {
|
|
44556
44758
|
if (entry.name.startsWith(".")) {
|
|
44557
44759
|
continue;
|
|
44558
44760
|
}
|
|
44559
44761
|
const path = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
44560
44762
|
if (entry.isDirectory()) {
|
|
44561
|
-
found.push(...scriptsUnder(
|
|
44763
|
+
found.push(...scriptsUnder(join28(directory, entry.name), path));
|
|
44562
44764
|
} else if (entry.isFile() && entry.name.endsWith(".gd")) {
|
|
44563
44765
|
found.push(path);
|
|
44564
44766
|
}
|
|
@@ -44566,7 +44768,7 @@ function scriptsUnder(directory, prefix) {
|
|
|
44566
44768
|
return found;
|
|
44567
44769
|
}
|
|
44568
44770
|
function hasMainScene(projectFile) {
|
|
44569
|
-
const scene = parseProjectGodot(
|
|
44771
|
+
const scene = parseProjectGodot(readFileSync24(projectFile, "utf8"))["application"]?.["run/main_scene"];
|
|
44570
44772
|
return typeof scene === "string" && scene !== "";
|
|
44571
44773
|
}
|
|
44572
44774
|
function runtimeAddonAnnouncesEditors(projectPath) {
|
|
@@ -44574,13 +44776,13 @@ function runtimeAddonAnnouncesEditors(projectPath) {
|
|
|
44574
44776
|
return false;
|
|
44575
44777
|
}
|
|
44576
44778
|
try {
|
|
44577
|
-
return
|
|
44779
|
+
return readFileSync24(join28(projectPath, RUNTIME_AUTOLOAD.path), "utf8").includes('"editor_pid"');
|
|
44578
44780
|
} catch {
|
|
44579
44781
|
return false;
|
|
44580
44782
|
}
|
|
44581
44783
|
}
|
|
44582
44784
|
function editorPlaysHeadless(projectFile) {
|
|
44583
|
-
const runArgs = parseProjectGodot(
|
|
44785
|
+
const runArgs = parseProjectGodot(readFileSync24(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
|
|
44584
44786
|
return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
|
|
44585
44787
|
}
|
|
44586
44788
|
function camelCased(params) {
|
|
@@ -44599,7 +44801,7 @@ function needsItsOwnProcess(asked) {
|
|
|
44599
44801
|
class GodotServer {
|
|
44600
44802
|
stdio = null;
|
|
44601
44803
|
locator = new GodotLocator;
|
|
44602
|
-
operationsScript =
|
|
44804
|
+
operationsScript = join28(__dirname2, "godot", "operations", "godot_operations.gd");
|
|
44603
44805
|
godotBridge;
|
|
44604
44806
|
tools = buildToolDefinitions();
|
|
44605
44807
|
activeProcess = null;
|
|
@@ -44624,6 +44826,7 @@ class GodotServer {
|
|
|
44624
44826
|
launchedDropped = null;
|
|
44625
44827
|
closedHere = null;
|
|
44626
44828
|
inFlight = new Map;
|
|
44829
|
+
callsOnGames = new Set;
|
|
44627
44830
|
callsTaken = 0;
|
|
44628
44831
|
clientNoted = null;
|
|
44629
44832
|
parentCommand = null;
|
|
@@ -44855,14 +45058,14 @@ class GodotServer {
|
|
|
44855
45058
|
}
|
|
44856
45059
|
noteTheStart() {
|
|
44857
45060
|
const cwd = process.cwd();
|
|
44858
|
-
const mcpJson =
|
|
45061
|
+
const mcpJson = join28(cwd, ".mcp.json");
|
|
44859
45062
|
const announced = this.ownProject === null ? null : readAnnouncement(announcementPath(this.ownProject));
|
|
44860
45063
|
const predecessor = announced !== null && announced.pid !== process.pid && alive(announced.pid) ? announced.pid : null;
|
|
44861
45064
|
writeLifecycle("started", {
|
|
44862
45065
|
argv: process.argv.slice(1),
|
|
44863
45066
|
cwd,
|
|
44864
45067
|
project: this.ownProject,
|
|
44865
|
-
mcpJson:
|
|
45068
|
+
mcpJson: existsSync21(mcpJson) ? mcpJson : null
|
|
44866
45069
|
});
|
|
44867
45070
|
if (predecessor !== null) {
|
|
44868
45071
|
writeLifecycle("replaced", { project: this.ownProject, predecessor });
|
|
@@ -45003,7 +45206,7 @@ class GodotServer {
|
|
|
45003
45206
|
return checked.response;
|
|
45004
45207
|
}
|
|
45005
45208
|
const named = args["projectPath"];
|
|
45006
|
-
if (typeof named === "string" &&
|
|
45209
|
+
if (typeof named === "string" && existsSync21(join28(named, "project.godot"))) {
|
|
45007
45210
|
this.lastProjectPath = named;
|
|
45008
45211
|
}
|
|
45009
45212
|
this.updates.refresh();
|
|
@@ -45410,7 +45613,15 @@ class GodotServer {
|
|
|
45410
45613
|
property: readNonEmptyString(args, "property") ?? ""
|
|
45411
45614
|
}, { tool: "runtime_inspect", op, patience });
|
|
45412
45615
|
case "accessibility":
|
|
45413
|
-
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
|
+
}, {
|
|
45414
45625
|
tool: "runtime_inspect",
|
|
45415
45626
|
op,
|
|
45416
45627
|
patience: readPositiveNumber(args, "timeoutMs") ?? Math.max(30000, this.runtimeTimeoutMs())
|
|
@@ -45461,7 +45672,14 @@ class GodotServer {
|
|
|
45461
45672
|
...args["index"] === undefined ? {} : { index: args["index"] }
|
|
45462
45673
|
}, { tool: "runtime_input", op });
|
|
45463
45674
|
}
|
|
45464
|
-
|
|
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
|
+
}
|
|
45465
45683
|
case "runtime_wait":
|
|
45466
45684
|
return await this.handleRuntimeWait(op, args);
|
|
45467
45685
|
case "debug_breakpoint": {
|
|
@@ -45561,8 +45779,8 @@ class GodotServer {
|
|
|
45561
45779
|
if (path === undefined) {
|
|
45562
45780
|
return { ok: false, response: this.createErrorResponse("projectPath is required.") };
|
|
45563
45781
|
}
|
|
45564
|
-
const file =
|
|
45565
|
-
if (!
|
|
45782
|
+
const file = join28(path, "project.godot");
|
|
45783
|
+
if (!existsSync21(file)) {
|
|
45566
45784
|
return {
|
|
45567
45785
|
ok: false,
|
|
45568
45786
|
response: this.createErrorResponse(`Not a Godot project: ${path}`, [
|
|
@@ -45608,7 +45826,7 @@ class GodotServer {
|
|
|
45608
45826
|
}
|
|
45609
45827
|
runEnvironment(args) {
|
|
45610
45828
|
const savesIn = readNonEmptyString(args, "savesIn");
|
|
45611
|
-
if (savesIn !== undefined && !
|
|
45829
|
+
if (savesIn !== undefined && !isAbsolute4(savesIn)) {
|
|
45612
45830
|
return {
|
|
45613
45831
|
ok: false,
|
|
45614
45832
|
response: this.createErrorResponse(`savesIn must be an absolute path, not "${savesIn}".`, [
|
|
@@ -46192,11 +46410,11 @@ class GodotServer {
|
|
|
46192
46410
|
if (!engine.ok) {
|
|
46193
46411
|
return engine.response;
|
|
46194
46412
|
}
|
|
46195
|
-
const application = parseProjectGodot(
|
|
46413
|
+
const application = parseProjectGodot(readFileSync24(project.value.file, "utf8"))["application"] ?? {};
|
|
46196
46414
|
const name = application["config/name"];
|
|
46197
46415
|
const mainScene = application["run/main_scene"];
|
|
46198
46416
|
const info = {
|
|
46199
|
-
name: typeof name === "string" && name !== "" ? name :
|
|
46417
|
+
name: typeof name === "string" && name !== "" ? name : basename8(project.value.path),
|
|
46200
46418
|
path: project.value.path,
|
|
46201
46419
|
mainScene: typeof mainScene === "string" && mainScene !== "" ? mainScene : null,
|
|
46202
46420
|
godotVersion: await this.godotVersion(engine.value),
|
|
@@ -46244,22 +46462,26 @@ class GodotServer {
|
|
|
46244
46462
|
if (!output.ok) {
|
|
46245
46463
|
return this.createErrorResponse(output.reason, PATH_SOLUTIONS);
|
|
46246
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
|
+
}
|
|
46247
46469
|
const engine = await this.engine();
|
|
46248
46470
|
if (!engine.ok) {
|
|
46249
46471
|
return engine.response;
|
|
46250
46472
|
}
|
|
46251
|
-
const folder =
|
|
46473
|
+
const folder = dirname21(output.absolutePath);
|
|
46252
46474
|
try {
|
|
46253
46475
|
mkdirSync16(folder, { recursive: true });
|
|
46254
46476
|
} catch (error) {
|
|
46255
|
-
return this.createErrorResponse(`Cannot create ${
|
|
46477
|
+
return this.createErrorResponse(`Cannot create ${dirname21(output.relativePath)} to export into: ${errorMessage(error)}`);
|
|
46256
46478
|
}
|
|
46257
46479
|
const debug = readBoolean(args, "debug") ?? false;
|
|
46258
46480
|
const exportLogs = scratchDirectory("export");
|
|
46259
46481
|
const exportArgs = [
|
|
46260
46482
|
"--headless",
|
|
46261
46483
|
"--log-file",
|
|
46262
|
-
|
|
46484
|
+
join28(exportLogs, "engine.log"),
|
|
46263
46485
|
"--path",
|
|
46264
46486
|
project.value.path,
|
|
46265
46487
|
debug ? "--export-debug" : "--export-release",
|
|
@@ -46267,8 +46489,12 @@ class GodotServer {
|
|
|
46267
46489
|
output.absolutePath
|
|
46268
46490
|
];
|
|
46269
46491
|
this.logDebug(`Exporting: ${engine.value} ${exportArgs.join(" ")}`);
|
|
46270
|
-
const writtenAt = () =>
|
|
46492
|
+
const writtenAt = () => existsSync21(output.absolutePath) ? statSync11(output.absolutePath).mtimeMs : null;
|
|
46271
46493
|
const before = writtenAt();
|
|
46494
|
+
const freed = freeLibraryCopies(project.value.path);
|
|
46495
|
+
if (freed.setAside.length > 0 || freed.swept.length > 0) {
|
|
46496
|
+
this.logDebug(`Before the export: set aside ${freed.setAside.join(", ")}; swept ${freed.swept.join(", ")}`);
|
|
46497
|
+
}
|
|
46272
46498
|
let ending;
|
|
46273
46499
|
try {
|
|
46274
46500
|
ending = await runEngine(engine.value, exportArgs, { timeout: { ms: 300000, said: "five minutes" } });
|
|
@@ -46331,10 +46557,10 @@ class GodotServer {
|
|
|
46331
46557
|
if (movie !== null) {
|
|
46332
46558
|
return this.createErrorResponse(movie);
|
|
46333
46559
|
}
|
|
46334
|
-
const missing = paths.value.filter((path) => !
|
|
46560
|
+
const missing = paths.value.filter((path) => !existsSync21(join28(project.value.path, path.slice("res://".length))));
|
|
46335
46561
|
const asked = paths.value.filter((path) => !missing.includes(path));
|
|
46336
46562
|
const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
|
|
46337
|
-
const noRunner = !
|
|
46563
|
+
const noRunner = !existsSync21(join28(project.value.path, runner));
|
|
46338
46564
|
if (asked.length === 0) {
|
|
46339
46565
|
const verdict = `nothing at ${missing.join(" and ")}`;
|
|
46340
46566
|
const andNoRunner = noRunner ? `, and gdUnit4 is not installed in this project: no ${runner}` : "";
|
|
@@ -46425,21 +46651,21 @@ class GodotServer {
|
|
|
46425
46651
|
await untilGone([run.process.pid]);
|
|
46426
46652
|
}
|
|
46427
46653
|
await run.settled;
|
|
46428
|
-
const reportsDir =
|
|
46654
|
+
const reportsDir = join28(project.value.path, ".godot", ...ours.split("/"));
|
|
46429
46655
|
let report = null;
|
|
46430
46656
|
let reportProblem = null;
|
|
46431
46657
|
try {
|
|
46432
|
-
const written =
|
|
46658
|
+
const written = existsSync21(reportsDir) ? readdirSync12(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
|
|
46433
46659
|
const newest = written.at(-1);
|
|
46434
46660
|
if (newest !== undefined) {
|
|
46435
|
-
report = parseJUnit(
|
|
46661
|
+
report = parseJUnit(readFileSync24(join28(reportsDir, newest, "results.xml"), "utf8"));
|
|
46436
46662
|
}
|
|
46437
46663
|
} catch (error) {
|
|
46438
46664
|
reportProblem = errorMessage(error);
|
|
46439
46665
|
} finally {
|
|
46440
46666
|
discard(reportsDir, userData);
|
|
46441
46667
|
try {
|
|
46442
|
-
rmdirSync2(
|
|
46668
|
+
rmdirSync2(dirname21(reportsDir));
|
|
46443
46669
|
} catch {}
|
|
46444
46670
|
}
|
|
46445
46671
|
const engineEntries = foldedRepeats(run.log.select({
|
|
@@ -46608,7 +46834,7 @@ class GodotServer {
|
|
|
46608
46834
|
}
|
|
46609
46835
|
async godotVersion(godotPath) {
|
|
46610
46836
|
try {
|
|
46611
|
-
const { stdout } = await
|
|
46837
|
+
const { stdout } = await runHidden(godotPath, ["--version"], { timeout: 1e4 });
|
|
46612
46838
|
this.lastGodotVersion = stdout.trim();
|
|
46613
46839
|
return this.lastGodotVersion;
|
|
46614
46840
|
} catch (error) {
|
|
@@ -46669,6 +46895,10 @@ class GodotServer {
|
|
|
46669
46895
|
return markIfStale(answer, this.godotBridge.getStatus().addonVersion, SERVER_VERSION, this.godotBridge.getStatus().addonDigest, shippedEditorDigest(), this.editorCodeOnDisk());
|
|
46670
46896
|
}
|
|
46671
46897
|
async diagnosticsOf(args) {
|
|
46898
|
+
const waited = await this.waitForTheEditorOpenedHere(args, []);
|
|
46899
|
+
if (waited !== null) {
|
|
46900
|
+
return { refusal: waited, perScript: false };
|
|
46901
|
+
}
|
|
46672
46902
|
const answer = await this.handleLSP("lsp_get_diagnostics", args);
|
|
46673
46903
|
const payload = asParams(JSON.parse(answer.content[0]?.text ?? "{}"));
|
|
46674
46904
|
const refused = this.languageServerRefusal(payload, "Diagnostics");
|
|
@@ -46699,11 +46929,11 @@ class GodotServer {
|
|
|
46699
46929
|
const cached = cachedClasses(projectPath);
|
|
46700
46930
|
const sourceOf = (resourcePath) => {
|
|
46701
46931
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
46702
|
-
if (!contained.ok || !
|
|
46932
|
+
if (!contained.ok || !existsSync21(contained.absolutePath)) {
|
|
46703
46933
|
return null;
|
|
46704
46934
|
}
|
|
46705
46935
|
try {
|
|
46706
|
-
return
|
|
46936
|
+
return readFileSync24(contained.absolutePath, "utf8");
|
|
46707
46937
|
} catch {
|
|
46708
46938
|
return null;
|
|
46709
46939
|
}
|
|
@@ -46753,9 +46983,34 @@ class GodotServer {
|
|
|
46753
46983
|
const recorded = readClassNote(projectPath)?.shortListEditorPid;
|
|
46754
46984
|
return recorded !== undefined && recorded === (this.godotBridge.getStatus().editorPid ?? null);
|
|
46755
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
|
+
}
|
|
46756
47010
|
noteAShortListEditor(projectPath) {
|
|
46757
47011
|
const note = readClassNote(projectPath);
|
|
46758
47012
|
writeClassNote(projectPath, {
|
|
47013
|
+
...note,
|
|
46759
47014
|
rebuiltWith: note?.rebuiltWith ?? [],
|
|
46760
47015
|
shortListEditorPid: this.godotBridge.getStatus().editorPid ?? null
|
|
46761
47016
|
});
|
|
@@ -46768,11 +47023,11 @@ class GodotServer {
|
|
|
46768
47023
|
}
|
|
46769
47024
|
sourceOfClass(projectPath, resourcePath) {
|
|
46770
47025
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
46771
|
-
if (!contained.ok || !
|
|
47026
|
+
if (!contained.ok || !existsSync21(contained.absolutePath)) {
|
|
46772
47027
|
return null;
|
|
46773
47028
|
}
|
|
46774
47029
|
try {
|
|
46775
|
-
return
|
|
47030
|
+
return readFileSync24(contained.absolutePath, "utf8");
|
|
46776
47031
|
} catch {
|
|
46777
47032
|
return null;
|
|
46778
47033
|
}
|
|
@@ -46780,7 +47035,17 @@ class GodotServer {
|
|
|
46780
47035
|
async handleLSP(toolName, args) {
|
|
46781
47036
|
return handleLSPTool(await this.lsp(), toolName, args);
|
|
46782
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
|
+
}
|
|
46783
47044
|
async askScriptInfo(toolName, asked, args) {
|
|
47045
|
+
const waited = await this.waitForTheEditorOpenedHere(args, []);
|
|
47046
|
+
if (waited !== null) {
|
|
47047
|
+
return waited;
|
|
47048
|
+
}
|
|
46784
47049
|
const answer = await this.handleLSP(toolName, args);
|
|
46785
47050
|
const refused = this.languageServerRefusal(asParams(JSON.parse(answer.content[0]?.text ?? "{}")), asked);
|
|
46786
47051
|
return refused === null ? answer : refused.refusal;
|
|
@@ -46920,7 +47185,7 @@ class GodotServer {
|
|
|
46920
47185
|
...Array.isArray(stack) ? {
|
|
46921
47186
|
stack: stack.map((frame) => ({
|
|
46922
47187
|
...frame,
|
|
46923
|
-
...projectPath !== "" && typeof frame["script"] === "string" && frame["script"].startsWith("res://") ? { path:
|
|
47188
|
+
...projectPath !== "" && typeof frame["script"] === "string" && frame["script"].startsWith("res://") ? { path: join28(projectPath, frame["script"].slice("res://".length)) } : {}
|
|
46924
47189
|
}))
|
|
46925
47190
|
} : {}
|
|
46926
47191
|
});
|
|
@@ -47190,7 +47455,7 @@ class GodotServer {
|
|
|
47190
47455
|
return null;
|
|
47191
47456
|
}
|
|
47192
47457
|
try {
|
|
47193
|
-
return settingKeys(
|
|
47458
|
+
return settingKeys(readFileSync24(join28(projectPath, "project.godot"), "utf8"));
|
|
47194
47459
|
} catch {
|
|
47195
47460
|
return null;
|
|
47196
47461
|
}
|
|
@@ -47455,6 +47720,7 @@ class GodotServer {
|
|
|
47455
47720
|
}
|
|
47456
47721
|
async openAnEditor(engine, projectPath, ports, hidden) {
|
|
47457
47722
|
clearEditorLog(projectPath);
|
|
47723
|
+
freeLibraryCopies(projectPath);
|
|
47458
47724
|
const launched = await launchOutsideTheTree({
|
|
47459
47725
|
command: engine,
|
|
47460
47726
|
args: editorArguments(projectPath, ports, editorLogPath(projectPath), hidden),
|
|
@@ -47505,6 +47771,17 @@ class GodotServer {
|
|
|
47505
47771
|
if (sceneToRun && !sceneToRun.ok) {
|
|
47506
47772
|
return this.createErrorResponse(sceneToRun.reason, PATH_SOLUTIONS);
|
|
47507
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
|
+
}
|
|
47508
47785
|
const given = this.gameArguments(args);
|
|
47509
47786
|
if (!given.ok) {
|
|
47510
47787
|
return given.response;
|
|
@@ -47521,13 +47798,13 @@ class GodotServer {
|
|
|
47521
47798
|
if (!asked.ok) {
|
|
47522
47799
|
return asked.response;
|
|
47523
47800
|
}
|
|
47524
|
-
if (!sceneToRun && !hasMainScene(project.value.file)) {
|
|
47801
|
+
if (exported === null && !sceneToRun && !hasMainScene(project.value.file)) {
|
|
47525
47802
|
return this.createErrorResponse("The project sets no main scene, so there is nothing to run.", [
|
|
47526
47803
|
"Pass scene to run one scene",
|
|
47527
47804
|
"Choose the main scene with project_settings set_main_scene"
|
|
47528
47805
|
]);
|
|
47529
47806
|
}
|
|
47530
|
-
const engine = await this.engine();
|
|
47807
|
+
const engine = exported?.ok === true ? { ok: true, value: exported.absolutePath } : await this.engine();
|
|
47531
47808
|
if (!engine.ok) {
|
|
47532
47809
|
return engine.response;
|
|
47533
47810
|
}
|
|
@@ -47565,11 +47842,11 @@ class GodotServer {
|
|
|
47565
47842
|
const editorWouldPlay = !headless || editorPlaysHeadless(project.value.file);
|
|
47566
47843
|
const silent = resolveSilent(args["silent"], process.env) && !headless;
|
|
47567
47844
|
const profiled = readBoolean(args, "profile") === true;
|
|
47568
|
-
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) {
|
|
47569
47846
|
return await this.playThroughEditor(sceneArgument, refreshed.value, scanned, project.value.path, alreadyPlaying, runtimeWaitMs, ended, profiled);
|
|
47570
47847
|
}
|
|
47571
47848
|
const cmdArgs = runArguments({
|
|
47572
|
-
projectPath: project.value.path,
|
|
47849
|
+
projectPath: exported === null ? project.value.path : null,
|
|
47573
47850
|
headless,
|
|
47574
47851
|
scene: sceneArgument,
|
|
47575
47852
|
userArgs: given.value,
|
|
@@ -47577,7 +47854,7 @@ class GodotServer {
|
|
|
47577
47854
|
silent
|
|
47578
47855
|
});
|
|
47579
47856
|
this.logDebug(`Running Godot project: ${engine.value} ${cmdArgs.join(" ")}`);
|
|
47580
|
-
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);
|
|
47581
47858
|
if ("error" in started) {
|
|
47582
47859
|
return this.createErrorResponse(`The game could not be started: ${started.error}`, [
|
|
47583
47860
|
"editor_status says which Godot this server runs and whether it answers"
|
|
@@ -47586,7 +47863,7 @@ class GodotServer {
|
|
|
47586
47863
|
started.announcedBefore = alreadyPlaying;
|
|
47587
47864
|
started.bootScene = runtimeWaitMs.scene;
|
|
47588
47865
|
this.activeProcess = started;
|
|
47589
|
-
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}` : "";
|
|
47590
47867
|
const endedForThis = endedToStartThis(ended);
|
|
47591
47868
|
const stranded = wereHereFirst.filter((one) => one.pid !== ended?.pid && alive(one.pid));
|
|
47592
47869
|
const handed = this.dapHoldingBreakpointsOf(project.value.path).breakpointsHeld();
|
|
@@ -47596,6 +47873,7 @@ class GodotServer {
|
|
|
47596
47873
|
through: "gdharness",
|
|
47597
47874
|
alsoRunning: stranded.length === 0 ? undefined : stranded.map((one) => one.pid),
|
|
47598
47875
|
pid: started.pid,
|
|
47876
|
+
executable: exported?.ok === true ? exported.relativePath : undefined,
|
|
47599
47877
|
arguments: cmdArgs,
|
|
47600
47878
|
argsNote: engineFlagsNote(given.value),
|
|
47601
47879
|
transcript: started.transcript,
|
|
@@ -47607,13 +47885,13 @@ class GodotServer {
|
|
|
47607
47885
|
endedPreviousRun: endedPreviousRun(ended),
|
|
47608
47886
|
previousRunLeft: previousRunLeft(ended),
|
|
47609
47887
|
...previousRunHow(ended),
|
|
47610
|
-
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),
|
|
47611
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}`
|
|
47612
47890
|
});
|
|
47613
47891
|
}
|
|
47614
47892
|
async runtimeUp(projectPath, before, budget, withArgs) {
|
|
47615
47893
|
const budgetMs = budget.ms;
|
|
47616
|
-
if (!
|
|
47894
|
+
if (!existsSync21(join28(projectPath, RUNTIME_AUTOLOAD.path))) {
|
|
47617
47895
|
return runtimeVerdict(null, {
|
|
47618
47896
|
addon: false,
|
|
47619
47897
|
budgetMs,
|
|
@@ -47698,7 +47976,7 @@ class GodotServer {
|
|
|
47698
47976
|
}
|
|
47699
47977
|
let announcedAt;
|
|
47700
47978
|
try {
|
|
47701
|
-
announcedAt =
|
|
47979
|
+
announcedAt = statSync11(endpoint.file).mtimeMs;
|
|
47702
47980
|
} catch {
|
|
47703
47981
|
return;
|
|
47704
47982
|
}
|
|
@@ -47892,7 +48170,7 @@ class GodotServer {
|
|
|
47892
48170
|
return 0;
|
|
47893
48171
|
}
|
|
47894
48172
|
try {
|
|
47895
|
-
return reportAlreadyInTranscript(
|
|
48173
|
+
return reportAlreadyInTranscript(readFileSync24(game.transcript), readFileSync24(report));
|
|
47896
48174
|
} catch (error) {
|
|
47897
48175
|
this.logDebug(`Could not compare a picked-up run's report with its transcript: ${errorMessage(error)}`);
|
|
47898
48176
|
return 0;
|
|
@@ -48047,7 +48325,10 @@ class GodotServer {
|
|
|
48047
48325
|
return gone ? "gone" : "lingering";
|
|
48048
48326
|
}
|
|
48049
48327
|
spawnGame(godotPath, cmdArgs, env) {
|
|
48050
|
-
const child =
|
|
48328
|
+
const child = spawnHidden(godotPath, cmdArgs, {
|
|
48329
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
48330
|
+
...env ? { env } : {}
|
|
48331
|
+
});
|
|
48051
48332
|
const cancelled = callSignal();
|
|
48052
48333
|
if (cancelled !== undefined) {
|
|
48053
48334
|
const end = () => {
|
|
@@ -48120,7 +48401,7 @@ class GodotServer {
|
|
|
48120
48401
|
});
|
|
48121
48402
|
return Object.assign(started, { settled });
|
|
48122
48403
|
}
|
|
48123
|
-
async launchKeptGame(godotPath, cmdArgs, projectPath, env, desktop, profiled = false) {
|
|
48404
|
+
async launchKeptGame(godotPath, cmdArgs, projectPath, env, desktop, profiled = false, debugged = true) {
|
|
48124
48405
|
const startedAt = Date.now();
|
|
48125
48406
|
const transcript = openTranscript(startedAt);
|
|
48126
48407
|
closeSync2(transcript.fd);
|
|
@@ -48136,7 +48417,7 @@ class GodotServer {
|
|
|
48136
48417
|
projectPath,
|
|
48137
48418
|
...this.servedBy(),
|
|
48138
48419
|
...profile === undefined ? {} : { profile },
|
|
48139
|
-
breakpoints: this.breakpointsForAGame(projectPath)
|
|
48420
|
+
...debugged ? { breakpoints: this.breakpointsForAGame(projectPath) } : { debugged: false }
|
|
48140
48421
|
},
|
|
48141
48422
|
...desktop === undefined ? {} : { desktop }
|
|
48142
48423
|
});
|
|
@@ -48572,7 +48853,7 @@ class GodotServer {
|
|
|
48572
48853
|
}
|
|
48573
48854
|
const going = await this.runStillGoing(run);
|
|
48574
48855
|
const projectPath = run.projectPath;
|
|
48575
|
-
const projectGone = projectPath !== null && !
|
|
48856
|
+
const projectGone = projectPath !== null && !existsSync21(join28(projectPath, "project.godot"));
|
|
48576
48857
|
if (projectGone && callersName !== undefined) {
|
|
48577
48858
|
return this.createErrorResponse(`callersOf reads the project's scripts, and the run's project at ${projectPath} has no project.godot any more.`, ["editor_run profile without callersOf answers with the profile itself"]);
|
|
48578
48859
|
}
|
|
@@ -48860,7 +49141,7 @@ class GodotServer {
|
|
|
48860
49141
|
notes.push(endedWithoutACode({
|
|
48861
49142
|
throughEditor: run.throughEditor,
|
|
48862
49143
|
announced: announced !== undefined || run.announcedPid !== undefined,
|
|
48863
|
-
couldAnnounce: run.projectPath !== null &&
|
|
49144
|
+
couldAnnounce: run.projectPath !== null && existsSync21(join28(run.projectPath, RUNTIME_AUTOLOAD.path)),
|
|
48864
49145
|
endedHere: typeof run.endedHere === "string",
|
|
48865
49146
|
pickedUp: run.pickedUp === true
|
|
48866
49147
|
}));
|
|
@@ -49100,8 +49381,8 @@ class GodotServer {
|
|
|
49100
49381
|
try {
|
|
49101
49382
|
const status = asParams(await this.godotBridge.invokeTool("scan_status", {}));
|
|
49102
49383
|
return status["scanning"] === true || status["importing"] === true;
|
|
49103
|
-
} catch {
|
|
49104
|
-
return
|
|
49384
|
+
} catch (error) {
|
|
49385
|
+
return error instanceof BridgeTimeout;
|
|
49105
49386
|
}
|
|
49106
49387
|
}
|
|
49107
49388
|
async handleRescanFilesystem(args) {
|
|
@@ -49112,7 +49393,7 @@ class GodotServer {
|
|
|
49112
49393
|
let writtenBefore = null;
|
|
49113
49394
|
let blind = { unseen: [] };
|
|
49114
49395
|
let waitedForEditorMs = 0;
|
|
49115
|
-
let
|
|
49396
|
+
let startUnanswered = false;
|
|
49116
49397
|
let completedBefore;
|
|
49117
49398
|
let uidsReread = [];
|
|
49118
49399
|
let uidsDuplicatedOnDisk = [];
|
|
@@ -49129,7 +49410,11 @@ class GodotServer {
|
|
|
49129
49410
|
before = projectPath === "" ? null : cachedClasses(projectPath);
|
|
49130
49411
|
writtenBefore = projectPath === "" ? null : cacheWrittenAt(projectPath);
|
|
49131
49412
|
blind = await this.classesTheEditorCannotSee(args);
|
|
49132
|
-
first = await this.
|
|
49413
|
+
const first = await this.askViaBridge("rescan_filesystem", args);
|
|
49414
|
+
if (first instanceof BridgeTimeout) {
|
|
49415
|
+
startUnanswered = true;
|
|
49416
|
+
break;
|
|
49417
|
+
}
|
|
49133
49418
|
if (first.isError) {
|
|
49134
49419
|
return first;
|
|
49135
49420
|
}
|
|
@@ -49152,7 +49437,17 @@ class GodotServer {
|
|
|
49152
49437
|
let idleSince = null;
|
|
49153
49438
|
while (Date.now() - started < timeoutMs) {
|
|
49154
49439
|
await new Promise((settle) => setTimeout(settle, 100));
|
|
49155
|
-
|
|
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
|
+
}
|
|
49156
49451
|
const pending = Boolean(status["pending"]) && Date.now() - started < SCAN_START_MS;
|
|
49157
49452
|
const completed = readNumber(status, "scansCompleted");
|
|
49158
49453
|
const notYetOver = completedBefore !== undefined && completed !== undefined && completed <= completedBefore;
|
|
@@ -49235,12 +49530,20 @@ class GodotServer {
|
|
|
49235
49530
|
}
|
|
49236
49531
|
const checked = busy ? { unseen: [] } : await this.classesTheEditorCannotSee(args);
|
|
49237
49532
|
const unseen = checked.unseen;
|
|
49238
|
-
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
|
+
}
|
|
49239
49539
|
const dependents = busy || projectPath === "" || broughtIn.length === 0 ? null : await this.reloadDependents(projectPath, broughtIn);
|
|
49240
49540
|
const stillGone = lost.filter((name) => !restored.includes(name));
|
|
49241
49541
|
const notes = [];
|
|
49242
49542
|
if (busy) {
|
|
49243
|
-
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
|
+
}
|
|
49244
49547
|
} else if (stillGone.length > 0) {
|
|
49245
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.");
|
|
49246
49549
|
} else if (lost.length > 0) {
|
|
@@ -49358,8 +49661,9 @@ class GodotServer {
|
|
|
49358
49661
|
const droppedByTheEditor = lost.filter((name) => !cameBack.includes(name));
|
|
49359
49662
|
const seenLosing = droppedByTheEditor.length > 0 && this.godotBridge.isConnected();
|
|
49360
49663
|
writeClassNote(projectPath, {
|
|
49664
|
+
...noted,
|
|
49361
49665
|
rebuiltWith: [...after.keys()].sort(),
|
|
49362
|
-
...seenLosing ? { shortListEditorPid: this.godotBridge.getStatus().editorPid ?? null } :
|
|
49666
|
+
...seenLosing ? { shortListEditorPid: this.godotBridge.getStatus().editorPid ?? null } : {}
|
|
49363
49667
|
});
|
|
49364
49668
|
const notes = [];
|
|
49365
49669
|
if (seenLosing) {
|
|
@@ -49425,7 +49729,7 @@ class GodotServer {
|
|
|
49425
49729
|
if (!scriptPath.ok) {
|
|
49426
49730
|
return this.createErrorResponse(scriptPath.reason);
|
|
49427
49731
|
}
|
|
49428
|
-
if (!
|
|
49732
|
+
if (!existsSync21(scriptPath.absolutePath)) {
|
|
49429
49733
|
return this.createErrorResponse(`Script file does not exist: ${scriptPath.absolutePath}`);
|
|
49430
49734
|
}
|
|
49431
49735
|
const asked = readNonEmptyString(args, "newScriptPath");
|
|
@@ -49433,6 +49737,10 @@ class GodotServer {
|
|
|
49433
49737
|
if (moveTo !== null && !moveTo.ok) {
|
|
49434
49738
|
return this.createErrorResponse(moveTo.reason);
|
|
49435
49739
|
}
|
|
49740
|
+
const waited = await this.waitForTheEditorOpenedHere(args, []);
|
|
49741
|
+
if (waited !== null) {
|
|
49742
|
+
return waited;
|
|
49743
|
+
}
|
|
49436
49744
|
const languageServer = {
|
|
49437
49745
|
problem: null,
|
|
49438
49746
|
initialized: false
|
|
@@ -49675,6 +49983,16 @@ class GodotServer {
|
|
|
49675
49983
|
]);
|
|
49676
49984
|
}
|
|
49677
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) {
|
|
49678
49996
|
if (!this.godotBridge.isConnected()) {
|
|
49679
49997
|
const alternative = askedAs !== undefined && Object.hasOwn(EDITOR_FREE_INSTEAD, askedAs) ? EDITOR_FREE_INSTEAD[askedAs] : undefined;
|
|
49680
49998
|
const refused = await this.noEditorYet(args, alternative === undefined ? [] : [alternative]);
|
|
@@ -49693,6 +50011,9 @@ class GodotServer {
|
|
|
49693
50011
|
try {
|
|
49694
50012
|
return this.jsonTextResponse(markIfStale(await this.godotBridge.invokeTool(toolName, args), this.godotBridge.getStatus().addonVersion, SERVER_VERSION, this.godotBridge.getStatus().addonDigest, shippedEditorDigest(), this.editorCodeOnDisk()));
|
|
49695
50013
|
} catch (error) {
|
|
50014
|
+
if (error instanceof BridgeTimeout) {
|
|
50015
|
+
return error;
|
|
50016
|
+
}
|
|
49696
50017
|
const message = errorMessage(error);
|
|
49697
50018
|
if (message.includes(`Unknown tool: ${toolName}`)) {
|
|
49698
50019
|
const status = this.godotBridge.getStatus();
|
|
@@ -49753,7 +50074,7 @@ class GodotServer {
|
|
|
49753
50074
|
async ownRunOnItsWay(projectPath, budgetMs) {
|
|
49754
50075
|
const run = await this.currentRun();
|
|
49755
50076
|
const project = projectPath ?? run?.projectPath ?? null;
|
|
49756
|
-
if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !
|
|
50077
|
+
if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync21(join28(project, RUNTIME_AUTOLOAD.path)) || !await this.runStillGoing(run)) {
|
|
49757
50078
|
return null;
|
|
49758
50079
|
}
|
|
49759
50080
|
const already = runtimesAnnounced().running;
|
|
@@ -49762,7 +50083,7 @@ class GodotServer {
|
|
|
49762
50083
|
return !run.announcedBefore.has(one.pid);
|
|
49763
50084
|
}
|
|
49764
50085
|
try {
|
|
49765
|
-
return
|
|
50086
|
+
return statSync11(one.file).mtimeMs >= run.startedAt;
|
|
49766
50087
|
} catch {
|
|
49767
50088
|
return false;
|
|
49768
50089
|
}
|
|
@@ -49838,12 +50159,25 @@ class GodotServer {
|
|
|
49838
50159
|
}
|
|
49839
50160
|
}
|
|
49840
50161
|
const screenshotDir = expectsScreenshot ? scratchDirectory("runtime-screenshot") : null;
|
|
49841
|
-
const screenshotPath = screenshotDir ?
|
|
50162
|
+
const screenshotPath = screenshotDir ? join28(screenshotDir, "capture.png") : null;
|
|
49842
50163
|
const staleNote = this.runtimeStaleNote(choice.endpoint);
|
|
49843
50164
|
const stale = staleNote === undefined ? {} : { addonIsStale: true, staleNote };
|
|
49844
50165
|
const refused = (message) => this.createErrorResponse(staleNote === undefined ? message : `${message}
|
|
49845
50166
|
|
|
49846
50167
|
${staleNote}`);
|
|
50168
|
+
const onGame = {
|
|
50169
|
+
pid: choice.endpoint.pid,
|
|
50170
|
+
what: `${asked.tool} ${asked.op}`,
|
|
50171
|
+
alongside: new Set
|
|
50172
|
+
};
|
|
50173
|
+
for (const other of this.callsOnGames) {
|
|
50174
|
+
if (other.pid === onGame.pid) {
|
|
50175
|
+
other.alongside.add(onGame.what);
|
|
50176
|
+
onGame.alongside.add(other.what);
|
|
50177
|
+
}
|
|
50178
|
+
}
|
|
50179
|
+
this.callsOnGames.add(onGame);
|
|
50180
|
+
const alongside = () => onGame.alongside.size === 0 ? {} : { ranAlongside: [...onGame.alongside], alongsideNote: ALONGSIDE_NOTE };
|
|
49847
50181
|
try {
|
|
49848
50182
|
const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs, !expectsScreenshot);
|
|
49849
50183
|
if (!reply.ok && reply.requestId !== undefined) {
|
|
@@ -49854,7 +50188,8 @@ ${staleNote}`);
|
|
|
49854
50188
|
waitedMs: timeoutMs,
|
|
49855
50189
|
note: `${reply.message} ${longerWaitNote(asked.tool, asked.op, timeoutMs)}`,
|
|
49856
50190
|
...answeredBy,
|
|
49857
|
-
...stale
|
|
50191
|
+
...stale,
|
|
50192
|
+
...alongside()
|
|
49858
50193
|
});
|
|
49859
50194
|
}
|
|
49860
50195
|
if (!reply.ok) {
|
|
@@ -49862,7 +50197,7 @@ ${staleNote}`);
|
|
|
49862
50197
|
}
|
|
49863
50198
|
const { id: _id, ...payload } = reply.payload;
|
|
49864
50199
|
if (!expectsScreenshot) {
|
|
49865
|
-
return this.jsonTextResponse({ ...payload, ...answeredBy, ...stale });
|
|
50200
|
+
return this.jsonTextResponse({ ...payload, ...answeredBy, ...stale, ...alongside() });
|
|
49866
50201
|
}
|
|
49867
50202
|
const returnedPath = readString(payload, "path");
|
|
49868
50203
|
if (readString(payload, "type") !== "screenshot_file" || !returnedPath || !screenshotPath) {
|
|
@@ -49879,18 +50214,22 @@ ${staleNote}`);
|
|
|
49879
50214
|
return this.createErrorResponse(`The capture was taken and could not be saved at ${saveTo}: ${errorMessage(error)}`);
|
|
49880
50215
|
}
|
|
49881
50216
|
}
|
|
50217
|
+
const ranWith = [...onGame.alongside];
|
|
49882
50218
|
return {
|
|
49883
50219
|
content: [
|
|
49884
50220
|
{
|
|
49885
50221
|
type: "text",
|
|
49886
|
-
text: `Screenshot captured: ${dimensions}${chosenAmongSeveral ? ` from pid ${own}, the game this server holds` : ""}${captureDetail(payload)}${saveTo === null ? "" : `. Saved to ${saveTo}`}${
|
|
50222
|
+
text: `Screenshot captured: ${dimensions}${chosenAmongSeveral ? ` from pid ${own}, the game this server holds` : ""}${captureDetail(payload)}${saveTo === null ? "" : `. Saved to ${saveTo}`}${ranWith.length === 0 ? "" : `
|
|
50223
|
+
|
|
50224
|
+
Ran alongside ${ranWith.join(", ")}. ${ALONGSIDE_NOTE}`}${staleNote === undefined ? "" : `
|
|
49887
50225
|
|
|
49888
50226
|
${staleNote}`}`
|
|
49889
50227
|
},
|
|
49890
|
-
{ type: "image", data:
|
|
50228
|
+
{ type: "image", data: readFileSync24(screenshotPath).toString("base64"), mimeType: "image/png" }
|
|
49891
50229
|
]
|
|
49892
50230
|
};
|
|
49893
50231
|
} finally {
|
|
50232
|
+
this.callsOnGames.delete(onGame);
|
|
49894
50233
|
discard(screenshotDir);
|
|
49895
50234
|
}
|
|
49896
50235
|
}
|
|
@@ -49989,7 +50328,7 @@ async function runGodotServer() {
|
|
|
49989
50328
|
const server = new GodotServer;
|
|
49990
50329
|
await server.run();
|
|
49991
50330
|
}
|
|
49992
|
-
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;
|
|
49993
50332
|
var init_server = __esm(() => {
|
|
49994
50333
|
init_dist();
|
|
49995
50334
|
init_stdio();
|
|
@@ -50019,6 +50358,7 @@ var init_server = __esm(() => {
|
|
|
50019
50358
|
init_junit();
|
|
50020
50359
|
init_keeper_channel();
|
|
50021
50360
|
init_launch();
|
|
50361
|
+
init_library_copies();
|
|
50022
50362
|
init_lifecycle_log();
|
|
50023
50363
|
init_lsp_client();
|
|
50024
50364
|
init_outside();
|
|
@@ -50041,11 +50381,11 @@ var init_server = __esm(() => {
|
|
|
50041
50381
|
init_server_instructions();
|
|
50042
50382
|
init_server_version();
|
|
50043
50383
|
init_setup();
|
|
50384
|
+
init_spawn();
|
|
50044
50385
|
init_tool_definitions();
|
|
50045
50386
|
init_tool_reference();
|
|
50046
50387
|
init_update_check();
|
|
50047
|
-
|
|
50048
|
-
__dirname2 = dirname20(fileURLToPath5(import.meta.url));
|
|
50388
|
+
__dirname2 = dirname21(fileURLToPath5(import.meta.url));
|
|
50049
50389
|
KEEPER_DEBUG_MS = PAUSE_MS + STEP_MS + 2 * READ_MS;
|
|
50050
50390
|
CONSOLE_WRAPPER = /_console(\.exe)?$/i;
|
|
50051
50391
|
LONGEST_BOOT_NOTED_MS = 5 * 60000;
|
|
@@ -50104,11 +50444,11 @@ var init_server = __esm(() => {
|
|
|
50104
50444
|
init_bridge_announce();
|
|
50105
50445
|
init_errors();
|
|
50106
50446
|
init_godot_path();
|
|
50107
|
-
import { existsSync as
|
|
50108
|
-
import { join as
|
|
50447
|
+
import { existsSync as existsSync22 } from "node:fs";
|
|
50448
|
+
import { join as join29, resolve as resolve8 } from "node:path";
|
|
50109
50449
|
|
|
50110
50450
|
// src/harnesses.ts
|
|
50111
|
-
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";
|
|
50112
50452
|
import { homedir as homedir2 } from "node:os";
|
|
50113
50453
|
import { basename as basename2, dirname as dirname3, join as join4, relative as relative2, resolve as resolve2 } from "node:path";
|
|
50114
50454
|
import process4 from "node:process";
|
|
@@ -50836,7 +51176,7 @@ function mainCheckoutOf(projectPath) {
|
|
|
50836
51176
|
top = up;
|
|
50837
51177
|
}
|
|
50838
51178
|
const marker = join4(top, ".git");
|
|
50839
|
-
if (!
|
|
51179
|
+
if (!statSync3(marker).isFile()) {
|
|
50840
51180
|
return null;
|
|
50841
51181
|
}
|
|
50842
51182
|
const pointer = /^gitdir:\s*(.+)$/m.exec(readFileSync2(marker, "utf8"))?.[1]?.trim();
|
|
@@ -50865,7 +51205,7 @@ function worktreeCheckoutsOf(projectPath) {
|
|
|
50865
51205
|
top = up;
|
|
50866
51206
|
}
|
|
50867
51207
|
const listed = join4(top, ".git", "worktrees");
|
|
50868
|
-
if (!
|
|
51208
|
+
if (!statSync3(join4(top, ".git")).isDirectory() || !existsSync4(listed)) {
|
|
50869
51209
|
return [];
|
|
50870
51210
|
}
|
|
50871
51211
|
const below = relative2(top, resolve2(projectPath));
|
|
@@ -51070,10 +51410,10 @@ init_setup();
|
|
|
51070
51410
|
init_setup();
|
|
51071
51411
|
init_tool_definitions();
|
|
51072
51412
|
init_tool_reference();
|
|
51073
|
-
import { existsSync as
|
|
51074
|
-
import { basename as
|
|
51413
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync5, readdirSync as readdirSync8, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "node:fs";
|
|
51414
|
+
import { basename as basename5, dirname as dirname7, join as join13 } from "node:path";
|
|
51075
51415
|
var SKILL_NAME = "gdharness";
|
|
51076
|
-
var SHARED_SKILLS =
|
|
51416
|
+
var SHARED_SKILLS = join13(".agents", "skills");
|
|
51077
51417
|
function skillMarkdown(version) {
|
|
51078
51418
|
return `---
|
|
51079
51419
|
name: ${SKILL_NAME}
|
|
@@ -51279,27 +51619,27 @@ comes back separately, under \`engine_messages_after_answer\`: none of it change
|
|
|
51279
51619
|
function skillFiles(version) {
|
|
51280
51620
|
return new Map([
|
|
51281
51621
|
["SKILL.md", skillMarkdown(version)],
|
|
51282
|
-
[
|
|
51622
|
+
[join13("references", "tools.md"), renderToolsMarkdown()]
|
|
51283
51623
|
]);
|
|
51284
51624
|
}
|
|
51285
51625
|
function skillDirectories(harnesses, projectPath, exists) {
|
|
51286
|
-
const directories = [
|
|
51626
|
+
const directories = [join13(projectPath, SHARED_SKILLS, SKILL_NAME)];
|
|
51287
51627
|
for (const harness of harnesses) {
|
|
51288
51628
|
if (harness.skills === undefined) {
|
|
51289
51629
|
continue;
|
|
51290
51630
|
}
|
|
51291
|
-
const own =
|
|
51292
|
-
if ((!harness.skills.shared || exists(own)) && !directories.includes(
|
|
51293
|
-
directories.push(
|
|
51631
|
+
const own = join13(projectPath, harness.skills.dir);
|
|
51632
|
+
if ((!harness.skills.shared || exists(own)) && !directories.includes(join13(own, SKILL_NAME))) {
|
|
51633
|
+
directories.push(join13(own, SKILL_NAME));
|
|
51294
51634
|
}
|
|
51295
51635
|
}
|
|
51296
51636
|
return directories;
|
|
51297
51637
|
}
|
|
51298
51638
|
function everySkillDirectory(projectPath, harnesses) {
|
|
51299
|
-
const directories = new Set([
|
|
51639
|
+
const directories = new Set([join13(projectPath, SHARED_SKILLS, SKILL_NAME)]);
|
|
51300
51640
|
for (const harness of harnesses) {
|
|
51301
51641
|
if (harness.skills !== undefined) {
|
|
51302
|
-
directories.add(
|
|
51642
|
+
directories.add(join13(projectPath, harness.skills.dir, SKILL_NAME));
|
|
51303
51643
|
}
|
|
51304
51644
|
}
|
|
51305
51645
|
return [...directories];
|
|
@@ -51307,33 +51647,33 @@ function everySkillDirectory(projectPath, harnesses) {
|
|
|
51307
51647
|
function removeSkill(directories, projectPath) {
|
|
51308
51648
|
const removed = [];
|
|
51309
51649
|
for (const directory of directories) {
|
|
51310
|
-
if (
|
|
51650
|
+
if (existsSync13(directory)) {
|
|
51311
51651
|
rmSync6(directory, { recursive: true, force: true });
|
|
51312
|
-
pruneEmpty(
|
|
51652
|
+
pruneEmpty(dirname7(directory), projectPath);
|
|
51313
51653
|
removed.push(directory);
|
|
51314
51654
|
}
|
|
51315
51655
|
}
|
|
51316
51656
|
return removed;
|
|
51317
51657
|
}
|
|
51318
51658
|
function pruneEmpty(directory, projectPath) {
|
|
51319
|
-
const shared =
|
|
51659
|
+
const shared = join13(projectPath, ".agents");
|
|
51320
51660
|
let path = directory;
|
|
51321
|
-
while (path !== projectPath && (
|
|
51322
|
-
if (
|
|
51661
|
+
while (path !== projectPath && (basename5(path) === "skills" || path === shared)) {
|
|
51662
|
+
if (readdirSync8(path).length > 0) {
|
|
51323
51663
|
return;
|
|
51324
51664
|
}
|
|
51325
51665
|
rmSync6(path, { recursive: true, force: true });
|
|
51326
|
-
path =
|
|
51666
|
+
path = dirname7(path);
|
|
51327
51667
|
}
|
|
51328
51668
|
}
|
|
51329
51669
|
function writeSkill(directories, version) {
|
|
51330
51670
|
const files = skillFiles(version);
|
|
51331
51671
|
return directories.map((directory) => {
|
|
51332
|
-
const replaced =
|
|
51672
|
+
const replaced = existsSync13(directory);
|
|
51333
51673
|
rmSync6(directory, { recursive: true, force: true });
|
|
51334
51674
|
for (const [name, contents] of files) {
|
|
51335
|
-
const path =
|
|
51336
|
-
mkdirSync5(
|
|
51675
|
+
const path = join13(directory, name);
|
|
51676
|
+
mkdirSync5(dirname7(path), { recursive: true });
|
|
51337
51677
|
writeFileSync7(path, contents, "utf8");
|
|
51338
51678
|
}
|
|
51339
51679
|
return { path: directory, replaced };
|
|
@@ -51341,18 +51681,18 @@ function writeSkill(directories, version) {
|
|
|
51341
51681
|
}
|
|
51342
51682
|
|
|
51343
51683
|
// src/version.ts
|
|
51344
|
-
import { readFileSync as
|
|
51345
|
-
import { dirname as
|
|
51684
|
+
import { readFileSync as readFileSync12 } from "node:fs";
|
|
51685
|
+
import { dirname as dirname8, join as join14 } from "node:path";
|
|
51346
51686
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
51347
51687
|
function getLocalVersion() {
|
|
51348
|
-
const currentDirectory =
|
|
51688
|
+
const currentDirectory = dirname8(fileURLToPath2(import.meta.url));
|
|
51349
51689
|
const packageCandidates = [
|
|
51350
|
-
|
|
51351
|
-
|
|
51690
|
+
join14(currentDirectory, "..", "package.json"),
|
|
51691
|
+
join14(currentDirectory, "..", "..", "package.json")
|
|
51352
51692
|
];
|
|
51353
51693
|
for (const packagePath of packageCandidates) {
|
|
51354
51694
|
try {
|
|
51355
|
-
const value = JSON.parse(
|
|
51695
|
+
const value = JSON.parse(readFileSync12(packagePath, "utf-8"));
|
|
51356
51696
|
if (typeof value === "object" && value !== null && "name" in value && value.name === "gdharness" && "version" in value && typeof value.version === "string") {
|
|
51357
51697
|
return value.version;
|
|
51358
51698
|
}
|
|
@@ -51370,7 +51710,7 @@ class UsageError extends Refusal {
|
|
|
51370
51710
|
function projectArgument(at) {
|
|
51371
51711
|
const named = args.slice(at).find((value) => !value.startsWith("--"));
|
|
51372
51712
|
const projectPath = resolve8(named ?? ".");
|
|
51373
|
-
if (!
|
|
51713
|
+
if (!existsSync22(join29(projectPath, "project.godot"))) {
|
|
51374
51714
|
throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
|
|
51375
51715
|
}
|
|
51376
51716
|
return projectPath;
|
|
@@ -51415,7 +51755,7 @@ async function registerRuntime(godot, projectPath) {
|
|
|
51415
51755
|
return;
|
|
51416
51756
|
}
|
|
51417
51757
|
console.log(`${RUNTIME_AUTOLOAD.name} autoload left at ${named}, which this project registers itself`);
|
|
51418
|
-
if (!
|
|
51758
|
+
if (!existsSync22(join29(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
|
|
51419
51759
|
console.log(` that file is not in this project, so it boots with a missing script: restore it, or point the entry at res://${RUNTIME_AUTOLOAD.path}`);
|
|
51420
51760
|
}
|
|
51421
51761
|
}
|
|
@@ -51547,7 +51887,7 @@ async function setup() {
|
|
|
51547
51887
|
console.log("no harness configured; name one yourself, and gdharness harnesses lists them all");
|
|
51548
51888
|
}
|
|
51549
51889
|
if (!args.includes("--no-skill")) {
|
|
51550
|
-
const directories = skillDirectories(chosen.harnesses, projectPath,
|
|
51890
|
+
const directories = skillDirectories(chosen.harnesses, projectPath, existsSync22);
|
|
51551
51891
|
for (const written of writeSkill(directories, getLocalVersion())) {
|
|
51552
51892
|
console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
|
|
51553
51893
|
}
|
|
@@ -51661,7 +52001,7 @@ async function upgrade() {
|
|
|
51661
52001
|
moved.push(took.from);
|
|
51662
52002
|
}
|
|
51663
52003
|
}
|
|
51664
|
-
for (const written of writeSkill(skillDirectories(already, projectPath,
|
|
52004
|
+
for (const written of writeSkill(skillDirectories(already, projectPath, existsSync22), version)) {
|
|
51665
52005
|
console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
|
|
51666
52006
|
}
|
|
51667
52007
|
console.log(`
|