gdharness 1.1.13 → 1.1.15
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 +742 -361
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +19 -1
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +8 -51
- package/build/godot/addons/gdharness_runtime/runtime_says.gd +97 -0
- package/build/godot/addons/gdharness_runtime/runtime_says.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/runtime_targets.gd +54 -12
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +4 -4
- package/build/index.js +623 -245
- package/build/keeper.js +111 -10
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -68,11 +68,156 @@ var init_errors = __esm(() => {
|
|
|
68
68
|
};
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
+
// src/windows-ask.ts
|
|
72
|
+
import { spawn } from "node:child_process";
|
|
73
|
+
import process2 from "node:process";
|
|
74
|
+
function holding(child, wanted) {
|
|
75
|
+
if (held === wanted) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
held = wanted;
|
|
79
|
+
const stdout = child.stdout;
|
|
80
|
+
if (wanted) {
|
|
81
|
+
stdout.ref?.();
|
|
82
|
+
} else {
|
|
83
|
+
stdout.unref?.();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function running() {
|
|
87
|
+
if (helper?.exitCode === null && helper.signalCode === null) {
|
|
88
|
+
return helper;
|
|
89
|
+
}
|
|
90
|
+
const child = spawn("powershell.exe", [
|
|
91
|
+
"-NoProfile",
|
|
92
|
+
"-NonInteractive",
|
|
93
|
+
"-ExecutionPolicy",
|
|
94
|
+
"Bypass",
|
|
95
|
+
"-EncodedCommand",
|
|
96
|
+
Buffer.from(SCRIPT, "utf16le").toString("base64")
|
|
97
|
+
], { stdio: ["pipe", "pipe", "pipe"], windowsHide: true });
|
|
98
|
+
buffered = "";
|
|
99
|
+
child.stdout.setEncoding("utf8");
|
|
100
|
+
child.stdout.on("data", (chunk) => {
|
|
101
|
+
buffered += chunk;
|
|
102
|
+
for (let end = buffered.indexOf(`
|
|
103
|
+
`);end !== -1; end = buffered.indexOf(`
|
|
104
|
+
`)) {
|
|
105
|
+
answered(buffered.slice(0, end).trim());
|
|
106
|
+
buffered = buffered.slice(end + 1);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
child.stderr.resume();
|
|
110
|
+
child.stdin.on("error", () => {});
|
|
111
|
+
const gone = () => {
|
|
112
|
+
if (helper !== child) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
helper = null;
|
|
116
|
+
if (out !== null) {
|
|
117
|
+
clearTimeout(out.timer);
|
|
118
|
+
out.question.reject(new WindowsAskFailure("exited before it answered"));
|
|
119
|
+
out = null;
|
|
120
|
+
}
|
|
121
|
+
sendNext();
|
|
122
|
+
};
|
|
123
|
+
child.once("exit", gone);
|
|
124
|
+
child.once("error", gone);
|
|
125
|
+
child.unref();
|
|
126
|
+
child.stdin.unref?.();
|
|
127
|
+
child.stderr.unref?.();
|
|
128
|
+
held = true;
|
|
129
|
+
holding(child, false);
|
|
130
|
+
helper = child;
|
|
131
|
+
return child;
|
|
132
|
+
}
|
|
133
|
+
function answered(line) {
|
|
134
|
+
const [id, tag, body] = line.split(" ");
|
|
135
|
+
if (out === null || out.id !== Number(id)) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const { question, timer } = out;
|
|
139
|
+
clearTimeout(timer);
|
|
140
|
+
out = null;
|
|
141
|
+
const said = Buffer.from(body ?? "", "base64").toString("utf8");
|
|
142
|
+
if (tag === "OK") {
|
|
143
|
+
question.resolve(said);
|
|
144
|
+
} else {
|
|
145
|
+
question.reject(new WindowsAskFailure(`refused the question: ${said.trim() || "no reason given"}`));
|
|
146
|
+
}
|
|
147
|
+
sendNext();
|
|
148
|
+
}
|
|
149
|
+
function sendNext() {
|
|
150
|
+
const question = queued.shift();
|
|
151
|
+
if (question === undefined) {
|
|
152
|
+
if (helper !== null) {
|
|
153
|
+
holding(helper, false);
|
|
154
|
+
}
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const child = running();
|
|
158
|
+
holding(child, true);
|
|
159
|
+
const id = next;
|
|
160
|
+
next += 1;
|
|
161
|
+
const timer = setTimeout(() => {
|
|
162
|
+
if (out?.id !== id) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
out = null;
|
|
166
|
+
question.reject(new WindowsAskFailure(`did not answer within ${question.withinMs}ms`));
|
|
167
|
+
if (helper === child) {
|
|
168
|
+
helper = null;
|
|
169
|
+
}
|
|
170
|
+
child.kill();
|
|
171
|
+
sendNext();
|
|
172
|
+
}, question.withinMs);
|
|
173
|
+
out = { id, question, timer };
|
|
174
|
+
child.stdin.write(`${id} ${Buffer.from(question.script, "utf8").toString("base64")}
|
|
175
|
+
`);
|
|
176
|
+
}
|
|
177
|
+
function askWindows(script, withinMs) {
|
|
178
|
+
if (process2.platform !== "win32") {
|
|
179
|
+
return Promise.reject(new WindowsAskFailure("is asked only on Windows"));
|
|
180
|
+
}
|
|
181
|
+
return new Promise((resolve, reject) => {
|
|
182
|
+
queued.push({ script, withinMs, resolve, reject });
|
|
183
|
+
if (out === null) {
|
|
184
|
+
sendNext();
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
var SCRIPT, WindowsAskFailure, helper = null, buffered = "", next = 1, out = null, queued, held = false;
|
|
189
|
+
var init_windows_ask = __esm(() => {
|
|
190
|
+
SCRIPT = [
|
|
191
|
+
"[Console]::OutputEncoding = [Text.Encoding]::UTF8",
|
|
192
|
+
"$in = [Console]::In",
|
|
193
|
+
"while ($null -ne ($line = $in.ReadLine())) {",
|
|
194
|
+
' $parts = $line.Split(" ", 2)',
|
|
195
|
+
" try {",
|
|
196
|
+
" $asked = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($parts[1]))",
|
|
197
|
+
" $said = (& ([ScriptBlock]::Create($asked)) | Out-String -Width 1000000)",
|
|
198
|
+
' $tag = "OK"',
|
|
199
|
+
" } catch {",
|
|
200
|
+
" $said = $_.Exception.Message",
|
|
201
|
+
' $tag = "ERR"',
|
|
202
|
+
" }",
|
|
203
|
+
' [Console]::Out.Write($parts[0] + " " + $tag + " " + [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($said)) + "`n")',
|
|
204
|
+
" [Console]::Out.Flush()",
|
|
205
|
+
"}"
|
|
206
|
+
].join(`
|
|
207
|
+
`);
|
|
208
|
+
WindowsAskFailure = class WindowsAskFailure extends Error {
|
|
209
|
+
constructor(how) {
|
|
210
|
+
super(`PowerShell ${how}`);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
queued = [];
|
|
214
|
+
});
|
|
215
|
+
|
|
71
216
|
// src/process-children.ts
|
|
72
|
-
import { execFile
|
|
217
|
+
import { execFile } from "node:child_process";
|
|
73
218
|
import { basename } from "node:path";
|
|
74
219
|
import { promisify } from "node:util";
|
|
75
|
-
function startTimesOf(pids) {
|
|
220
|
+
async function startTimesOf(pids) {
|
|
76
221
|
const began = new Map;
|
|
77
222
|
if (pids.length === 0) {
|
|
78
223
|
return began;
|
|
@@ -80,12 +225,7 @@ function startTimesOf(pids) {
|
|
|
80
225
|
try {
|
|
81
226
|
if (process.platform === "win32") {
|
|
82
227
|
const filter = pids.map((pid) => `ProcessId=${pid}`).join(" OR ");
|
|
83
|
-
const said =
|
|
84
|
-
"-NoProfile",
|
|
85
|
-
"-NonInteractive",
|
|
86
|
-
"-Command",
|
|
87
|
-
`Get-CimInstance Win32_Process -Filter "${filter}" | ForEach-Object { "$($_.ProcessId) $(([DateTimeOffset]$_.CreationDate).ToUnixTimeMilliseconds())" }`
|
|
88
|
-
], { encoding: "utf8", timeout: ASK_TIMEOUT_MS, windowsHide: true });
|
|
228
|
+
const said = await askWindows(`Get-CimInstance Win32_Process -Filter "${filter}" | ForEach-Object { "$($_.ProcessId) $(([DateTimeOffset]$_.CreationDate).ToUnixTimeMilliseconds())" }`, ASK_TIMEOUT_MS);
|
|
89
229
|
for (const line of said.split(`
|
|
90
230
|
`)) {
|
|
91
231
|
const found = /^\s*(\d+)\s+(\d+)\s*$/.exec(line);
|
|
@@ -95,11 +235,10 @@ function startTimesOf(pids) {
|
|
|
95
235
|
}
|
|
96
236
|
return began;
|
|
97
237
|
}
|
|
98
|
-
const said =
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}).stdout;
|
|
238
|
+
const said = await printedBy("ps", ["-p", pids.join(","), "-o", "pid=,lstart="], {
|
|
239
|
+
...process.env,
|
|
240
|
+
LC_ALL: "C"
|
|
241
|
+
});
|
|
103
242
|
for (const line of said.split(`
|
|
104
243
|
`)) {
|
|
105
244
|
const found = /^\s*(\d+)\s+(.+)$/.exec(line);
|
|
@@ -111,16 +250,39 @@ function startTimesOf(pids) {
|
|
|
111
250
|
} catch {}
|
|
112
251
|
return began;
|
|
113
252
|
}
|
|
253
|
+
function printedBy(command, args, env) {
|
|
254
|
+
return new Promise((resolve) => {
|
|
255
|
+
execFile(command, [...args], { encoding: "utf8", timeout: ASK_TIMEOUT_MS, env }, (_error, stdout) => {
|
|
256
|
+
resolve(stdout);
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
}
|
|
114
260
|
async function processTree() {
|
|
115
261
|
try {
|
|
116
|
-
const tree = process.platform === "win32" ? parseProcessTable(await
|
|
262
|
+
const tree = process.platform === "win32" ? parseProcessTable(await listWindows(), true) : parseProcessTable(await askPosix());
|
|
117
263
|
lastListingFailure = undefined;
|
|
118
264
|
return tree;
|
|
119
265
|
} catch (error) {
|
|
120
|
-
lastListingFailure =
|
|
266
|
+
lastListingFailure = listingFailure(error);
|
|
121
267
|
return;
|
|
122
268
|
}
|
|
123
269
|
}
|
|
270
|
+
function listingFailure(error) {
|
|
271
|
+
if (error instanceof WindowsAskFailure) {
|
|
272
|
+
return error.message;
|
|
273
|
+
}
|
|
274
|
+
if (!(error instanceof Error)) {
|
|
275
|
+
return String(error);
|
|
276
|
+
}
|
|
277
|
+
const failed = error;
|
|
278
|
+
const how = failed.killed === true ? `was killed after the ${ASK_TIMEOUT_MS}ms budget${failed.signal ? ` (${failed.signal})` : ""}` : failed.signal ? `was ended by ${failed.signal}` : failed.code !== undefined && failed.code !== null ? `exited with ${failed.code}` : "failed";
|
|
279
|
+
const said = typeof failed.stderr === "string" ? failed.stderr.trim() : "";
|
|
280
|
+
const asked = basename((failed.cmd ?? "").split(" ")[0] ?? "") || "the listing";
|
|
281
|
+
return `${asked} ${how}${said === "" ? ", with nothing on stderr" : `; stderr: ${said.slice(0, 400)}`}`;
|
|
282
|
+
}
|
|
283
|
+
function whyTheProcessTreeFailed() {
|
|
284
|
+
return lastListingFailure;
|
|
285
|
+
}
|
|
124
286
|
function parseProcessTable(printed, started = false) {
|
|
125
287
|
const tree = new Map;
|
|
126
288
|
for (const line of printed.split(/\r?\n/)) {
|
|
@@ -250,14 +412,8 @@ function commandWords(command) {
|
|
|
250
412
|
}
|
|
251
413
|
return words;
|
|
252
414
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
"-NoProfile",
|
|
256
|
-
"-NonInteractive",
|
|
257
|
-
"-Command",
|
|
258
|
-
`${POWERSHELL_UTF8}Get-CimInstance Win32_Process -Property ProcessId,ParentProcessId,CreationDate,CommandLine | ForEach-Object { $began = 0; if ($_.CreationDate) { $began = ([DateTimeOffset]$_.CreationDate).ToUnixTimeMilliseconds() }; "$($_.ProcessId) $($_.ParentProcessId) $began $($_.CommandLine)" }`
|
|
259
|
-
], { timeout: ASK_TIMEOUT_MS, windowsHide: true, maxBuffer: 16 * 1024 * 1024 });
|
|
260
|
-
return stdout;
|
|
415
|
+
function listWindows() {
|
|
416
|
+
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);
|
|
261
417
|
}
|
|
262
418
|
async function askPosix() {
|
|
263
419
|
const { stdout } = await run("ps", ["-e", "-o", "pid=,ppid=,args="], {
|
|
@@ -266,8 +422,9 @@ async function askPosix() {
|
|
|
266
422
|
});
|
|
267
423
|
return stdout;
|
|
268
424
|
}
|
|
269
|
-
var run,
|
|
425
|
+
var run, ASK_TIMEOUT_MS = 15000, lastListingFailure;
|
|
270
426
|
var init_process_children = __esm(() => {
|
|
427
|
+
init_windows_ask();
|
|
271
428
|
run = promisify(execFile);
|
|
272
429
|
});
|
|
273
430
|
|
|
@@ -302,12 +459,12 @@ function serverBehind(announcement, startedAt) {
|
|
|
302
459
|
}
|
|
303
460
|
return { version: announcement.version, pid: announcement.pid };
|
|
304
461
|
}
|
|
305
|
-
function serverServing(projectPath) {
|
|
462
|
+
async function serverServing(projectPath) {
|
|
306
463
|
const announcement = readAnnouncement(announcementPath(projectPath));
|
|
307
464
|
if (announcement === null) {
|
|
308
465
|
return null;
|
|
309
466
|
}
|
|
310
|
-
return serverBehind(announcement, startTimesOf([announcement.pid]).get(announcement.pid));
|
|
467
|
+
return serverBehind(announcement, (await startTimesOf([announcement.pid])).get(announcement.pid));
|
|
311
468
|
}
|
|
312
469
|
function announcementIsOurs(path) {
|
|
313
470
|
return readAnnouncement(path)?.pid === process.pid;
|
|
@@ -448,8 +605,18 @@ function resolveHeadless(requested, host) {
|
|
|
448
605
|
}
|
|
449
606
|
return envValue("DISPLAY", host.variables) === undefined && envValue("WAYLAND_DISPLAY", host.variables) === undefined;
|
|
450
607
|
}
|
|
608
|
+
function resolveSilent(requested, variables) {
|
|
609
|
+
if (typeof requested === "boolean") {
|
|
610
|
+
return requested;
|
|
611
|
+
}
|
|
612
|
+
const said = envValue("GDHARNESS_SILENT", variables)?.toLowerCase();
|
|
613
|
+
return said !== undefined && said !== "0" && said !== "false";
|
|
614
|
+
}
|
|
451
615
|
function runArguments(options) {
|
|
452
616
|
const args = options.headless ? ["--headless", "--path", options.projectPath] : ["--path", options.projectPath];
|
|
617
|
+
if (options.silent === true && !options.headless) {
|
|
618
|
+
args.push("--audio-driver", "Dummy");
|
|
619
|
+
}
|
|
453
620
|
if (options.quitAfter !== undefined && options.quitAfter !== null) {
|
|
454
621
|
args.push("--quit-after", String(options.quitAfter));
|
|
455
622
|
}
|
|
@@ -7520,16 +7687,16 @@ var require_public_api = __commonJS(function(exports) {
|
|
|
7520
7687
|
// src/runner.ts
|
|
7521
7688
|
import { existsSync as existsSync3 } from "node:fs";
|
|
7522
7689
|
import { dirname as dirname2, join as join3 } from "node:path";
|
|
7523
|
-
import
|
|
7690
|
+
import process3 from "node:process";
|
|
7524
7691
|
function currentRunner() {
|
|
7525
|
-
const versions =
|
|
7692
|
+
const versions = process3.versions;
|
|
7526
7693
|
return versions["bun"] === undefined ? "npx" : "bunx";
|
|
7527
7694
|
}
|
|
7528
7695
|
function runLine(runner, version, rest = "") {
|
|
7529
7696
|
const flag = runner === "npx" ? "-y " : "";
|
|
7530
7697
|
return `${runner} ${flag}gdharness@${version}${rest === "" ? "" : ` ${rest}`}`;
|
|
7531
7698
|
}
|
|
7532
|
-
function spawnFor(runner, version, execPath =
|
|
7699
|
+
function spawnFor(runner, version, execPath = process3.execPath) {
|
|
7533
7700
|
const spec = `gdharness@${version}`;
|
|
7534
7701
|
if (runner !== currentRunner()) {
|
|
7535
7702
|
return { command: runner, args: runner === "npx" ? ["-y", spec] : ["--bun", spec] };
|
|
@@ -7540,7 +7707,7 @@ function spawnFor(runner, version, execPath = process2.execPath) {
|
|
|
7540
7707
|
return { command: alongside(execPath, "npx") ?? "npx", args: ["-y", spec] };
|
|
7541
7708
|
}
|
|
7542
7709
|
function alongside(execPath, name) {
|
|
7543
|
-
const runner = join3(dirname2(execPath),
|
|
7710
|
+
const runner = join3(dirname2(execPath), process3.platform === "win32" ? `${name}.cmd` : name);
|
|
7544
7711
|
return existsSync3(runner) ? runner : undefined;
|
|
7545
7712
|
}
|
|
7546
7713
|
var init_runner = () => {};
|
|
@@ -7683,8 +7850,47 @@ var init_game_log = __esm(() => {
|
|
|
7683
7850
|
};
|
|
7684
7851
|
});
|
|
7685
7852
|
|
|
7853
|
+
// src/alive.ts
|
|
7854
|
+
import process5 from "node:process";
|
|
7855
|
+
function alive(pid) {
|
|
7856
|
+
if (pid === undefined || pid === null) {
|
|
7857
|
+
return false;
|
|
7858
|
+
}
|
|
7859
|
+
try {
|
|
7860
|
+
process5.kill(pid, 0);
|
|
7861
|
+
return true;
|
|
7862
|
+
} catch (error) {
|
|
7863
|
+
return error.code === "EPERM";
|
|
7864
|
+
}
|
|
7865
|
+
}
|
|
7866
|
+
var init_alive = () => {};
|
|
7867
|
+
|
|
7686
7868
|
// src/scratch.ts
|
|
7687
|
-
import { rmSync as rmSync3 } from "node:fs";
|
|
7869
|
+
import { existsSync as existsSync5, mkdtempSync, readdirSync as readdirSync2, rmSync as rmSync3 } from "node:fs";
|
|
7870
|
+
import { tmpdir } from "node:os";
|
|
7871
|
+
import { join as join5 } from "node:path";
|
|
7872
|
+
import process6 from "node:process";
|
|
7873
|
+
function scratchDirectory(kind) {
|
|
7874
|
+
return mkdtempSync(join5(tmpdir(), `gdharness-${kind}-${process6.pid}-`));
|
|
7875
|
+
}
|
|
7876
|
+
function sweepAbandonedScratch(directory = tmpdir()) {
|
|
7877
|
+
let entries;
|
|
7878
|
+
try {
|
|
7879
|
+
entries = readdirSync2(directory);
|
|
7880
|
+
} catch {
|
|
7881
|
+
return;
|
|
7882
|
+
}
|
|
7883
|
+
const named = new RegExp(`^gdharness-(${KINDS.join("|")})-(\\d+)-[A-Za-z0-9]{6}$`);
|
|
7884
|
+
for (const entry of entries) {
|
|
7885
|
+
const owner = named.exec(entry)?.[2];
|
|
7886
|
+
if (owner === undefined || alive(Number(owner))) {
|
|
7887
|
+
continue;
|
|
7888
|
+
}
|
|
7889
|
+
try {
|
|
7890
|
+
remove(join5(directory, entry));
|
|
7891
|
+
} catch {}
|
|
7892
|
+
}
|
|
7893
|
+
}
|
|
7688
7894
|
function discardWith(remove, paths) {
|
|
7689
7895
|
const left = [];
|
|
7690
7896
|
for (const path of paths) {
|
|
@@ -7700,20 +7906,75 @@ function discardWith(remove, paths) {
|
|
|
7700
7906
|
return left;
|
|
7701
7907
|
}
|
|
7702
7908
|
function discard(...paths) {
|
|
7703
|
-
|
|
7704
|
-
|
|
7705
|
-
|
|
7909
|
+
const left = discardWith(remove, paths);
|
|
7910
|
+
for (const { path } of left) {
|
|
7911
|
+
tryAgain(path, 0);
|
|
7912
|
+
}
|
|
7913
|
+
return left;
|
|
7914
|
+
}
|
|
7915
|
+
function remove(path) {
|
|
7916
|
+
rmSync3(path, SWEPT);
|
|
7917
|
+
}
|
|
7918
|
+
function tryAgain(path, attempt) {
|
|
7919
|
+
if (attempt === 0) {
|
|
7920
|
+
if (waiting.has(path)) {
|
|
7921
|
+
return;
|
|
7922
|
+
}
|
|
7923
|
+
waiting.add(path);
|
|
7924
|
+
atExit.delete(path);
|
|
7925
|
+
lastTryOnExit();
|
|
7926
|
+
}
|
|
7927
|
+
const after = AGAIN_AFTER_MS[attempt];
|
|
7928
|
+
if (after === undefined || !existsSync5(path)) {
|
|
7929
|
+
waiting.delete(path);
|
|
7930
|
+
if (after === undefined) {
|
|
7931
|
+
atExit.add(path);
|
|
7932
|
+
}
|
|
7933
|
+
return;
|
|
7934
|
+
}
|
|
7935
|
+
setTimeout(() => {
|
|
7936
|
+
try {
|
|
7937
|
+
remove(path);
|
|
7938
|
+
waiting.delete(path);
|
|
7939
|
+
} catch {
|
|
7940
|
+
tryAgain(path, attempt + 1);
|
|
7941
|
+
}
|
|
7942
|
+
}, after).unref();
|
|
7706
7943
|
}
|
|
7707
|
-
|
|
7944
|
+
function lastTryOnExit() {
|
|
7945
|
+
if (lastTryWatched) {
|
|
7946
|
+
return;
|
|
7947
|
+
}
|
|
7948
|
+
lastTryWatched = true;
|
|
7949
|
+
process6.once("exit", () => {
|
|
7950
|
+
for (const path of [...waiting, ...atExit]) {
|
|
7951
|
+
try {
|
|
7952
|
+
remove(path);
|
|
7953
|
+
} catch {}
|
|
7954
|
+
}
|
|
7955
|
+
});
|
|
7956
|
+
}
|
|
7957
|
+
var SWEPT, AGAIN_AFTER_MS, waiting, atExit, KINDS, lastTryWatched = false;
|
|
7708
7958
|
var init_scratch = __esm(() => {
|
|
7709
|
-
|
|
7959
|
+
init_alive();
|
|
7960
|
+
SWEPT = { recursive: true, force: true };
|
|
7961
|
+
AGAIN_AFTER_MS = [250, 500, 1000, 2000, 4000, 8000, 16000];
|
|
7962
|
+
waiting = new Set;
|
|
7963
|
+
atExit = new Set;
|
|
7964
|
+
KINDS = [
|
|
7965
|
+
"desktop",
|
|
7966
|
+
"export",
|
|
7967
|
+
"import",
|
|
7968
|
+
"params",
|
|
7969
|
+
"runtime-screenshot",
|
|
7970
|
+
"tests"
|
|
7971
|
+
];
|
|
7710
7972
|
});
|
|
7711
7973
|
|
|
7712
7974
|
// src/headless.ts
|
|
7713
7975
|
import { execFile as execFile3 } from "node:child_process";
|
|
7714
|
-
import {
|
|
7715
|
-
import {
|
|
7716
|
-
import { join as join5 } from "node:path";
|
|
7976
|
+
import { writeFileSync as writeFileSync3 } from "node:fs";
|
|
7977
|
+
import { join as join6 } from "node:path";
|
|
7717
7978
|
import { promisify as promisify3 } from "node:util";
|
|
7718
7979
|
function snakeCased(params) {
|
|
7719
7980
|
const result = emptyRecord();
|
|
@@ -7753,9 +8014,9 @@ function reason(stdout, stderr) {
|
|
|
7753
8014
|
return stdout.trim().split(/\r?\n/).at(-1) ?? "no output at all";
|
|
7754
8015
|
}
|
|
7755
8016
|
async function runImport(godotPath, projectPath) {
|
|
7756
|
-
const logDir =
|
|
8017
|
+
const logDir = scratchDirectory("import");
|
|
7757
8018
|
try {
|
|
7758
|
-
const { stderr } = await run3(godotPath, ["--headless", "--log-file",
|
|
8019
|
+
const { stderr } = await run3(godotPath, ["--headless", "--log-file", join6(logDir, "engine.log"), "--path", projectPath, "--import"], { signal: callSignal() });
|
|
7759
8020
|
return { ok: true, messages: problems(stderr) };
|
|
7760
8021
|
} catch (error) {
|
|
7761
8022
|
if (error instanceof Error && "stdout" in error && "stderr" in error) {
|
|
@@ -7776,13 +8037,13 @@ async function runImport(godotPath, projectPath) {
|
|
|
7776
8037
|
}
|
|
7777
8038
|
}
|
|
7778
8039
|
async function runOperation(engine, operation, params, projectPath) {
|
|
7779
|
-
const paramsDir =
|
|
7780
|
-
const paramsFile =
|
|
8040
|
+
const paramsDir = scratchDirectory("params");
|
|
8041
|
+
const paramsFile = join6(paramsDir, `${operation}.json`);
|
|
7781
8042
|
writeFileSync3(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
|
|
7782
8043
|
const args = [
|
|
7783
8044
|
"--headless",
|
|
7784
8045
|
"--log-file",
|
|
7785
|
-
|
|
8046
|
+
join6(paramsDir, "engine.log"),
|
|
7786
8047
|
"--path",
|
|
7787
8048
|
projectPath,
|
|
7788
8049
|
"--script",
|
|
@@ -7831,9 +8092,9 @@ var init_headless = __esm(() => {
|
|
|
7831
8092
|
});
|
|
7832
8093
|
|
|
7833
8094
|
// src/update-check.ts
|
|
7834
|
-
import { existsSync as
|
|
8095
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "node:fs";
|
|
7835
8096
|
import { homedir as homedir3, tmpdir as tmpdir2 } from "node:os";
|
|
7836
|
-
import { join as
|
|
8097
|
+
import { join as join7 } from "node:path";
|
|
7837
8098
|
function packageUrl(root) {
|
|
7838
8099
|
return `${root.replace(/\/+$/, "")}/gdharness/latest`;
|
|
7839
8100
|
}
|
|
@@ -7855,25 +8116,25 @@ function cacheDirectory(environment) {
|
|
|
7855
8116
|
};
|
|
7856
8117
|
const home = set("HOME") ?? homedir3();
|
|
7857
8118
|
if (process.platform === "win32") {
|
|
7858
|
-
return
|
|
8119
|
+
return join7(set("LOCALAPPDATA") ?? home, "gdharness");
|
|
7859
8120
|
}
|
|
7860
8121
|
if (process.platform === "darwin") {
|
|
7861
|
-
return
|
|
8122
|
+
return join7(home, "Library", "Caches", "gdharness");
|
|
7862
8123
|
}
|
|
7863
|
-
return
|
|
8124
|
+
return join7(set("XDG_CACHE_HOME") ?? join7(home, ".cache"), "gdharness");
|
|
7864
8125
|
}
|
|
7865
8126
|
function cacheFile(environment = process.env) {
|
|
7866
8127
|
try {
|
|
7867
8128
|
const directory = cacheDirectory(environment);
|
|
7868
8129
|
mkdirSync3(directory, { recursive: true, mode: 448 });
|
|
7869
|
-
return
|
|
8130
|
+
return join7(directory, "update-check.json");
|
|
7870
8131
|
} catch {
|
|
7871
|
-
return
|
|
8132
|
+
return join7(tmpdir2(), "gdharness-update-check.json");
|
|
7872
8133
|
}
|
|
7873
8134
|
}
|
|
7874
8135
|
function readCache(path) {
|
|
7875
8136
|
try {
|
|
7876
|
-
if (!
|
|
8137
|
+
if (!existsSync6(path)) {
|
|
7877
8138
|
return null;
|
|
7878
8139
|
}
|
|
7879
8140
|
const parsed = JSON.parse(readFileSync3(path, "utf8"));
|
|
@@ -8076,19 +8337,20 @@ var init_server_version = __esm(() => {
|
|
|
8076
8337
|
|
|
8077
8338
|
// src/issues.ts
|
|
8078
8339
|
import { createHash } from "node:crypto";
|
|
8079
|
-
import
|
|
8080
|
-
function feedbackNotice() {
|
|
8340
|
+
import process7 from "node:process";
|
|
8341
|
+
function feedbackNotice(every) {
|
|
8081
8342
|
return JSON.stringify({
|
|
8082
8343
|
gdharness_feedback: {
|
|
8344
|
+
why_here: `Asked once every ${every} answers, whatever the call was. It is not about the answer it comes with, which is complete as it stands.`,
|
|
8083
8345
|
what_to_do: "If this session has run into something gdharness cannot do, or a tool that should " + "exist and does not, tell the user what is missing and offer to open an enhancement " + "issue for them. Only if they say yes, and only for a real gap: a call you got wrong " + "is not one. If you cannot open an issue yourself, give them the link and what to " + "write in it.",
|
|
8084
8346
|
enhancement_url: ENHANCEMENT_URL
|
|
8085
8347
|
}
|
|
8086
8348
|
}, null, 2);
|
|
8087
8349
|
}
|
|
8088
8350
|
function runtime() {
|
|
8089
|
-
const versions =
|
|
8351
|
+
const versions = process7.versions;
|
|
8090
8352
|
const bun = versions["bun"];
|
|
8091
|
-
return bun === undefined ? `node ${
|
|
8353
|
+
return bun === undefined ? `node ${process7.versions.node}` : `bun ${bun}`;
|
|
8092
8354
|
}
|
|
8093
8355
|
function defectSignature(where, message) {
|
|
8094
8356
|
const normalised = `${where}
|
|
@@ -8100,7 +8362,7 @@ function facts(where, message, godotVersion) {
|
|
|
8100
8362
|
["Where", where],
|
|
8101
8363
|
["Error", message],
|
|
8102
8364
|
["Version", `gdharness ${SERVER_VERSION}`],
|
|
8103
|
-
["Runtime", `${runtime()}, ${
|
|
8365
|
+
["Runtime", `${runtime()}, ${process7.platform} ${process7.arch}`],
|
|
8104
8366
|
["Godot", godotVersion ?? "not known at the point this failed"],
|
|
8105
8367
|
["Signature", defectSignature(where, message)]
|
|
8106
8368
|
];
|
|
@@ -8110,7 +8372,7 @@ function filledTemplate(where, message, godotVersion) {
|
|
|
8110
8372
|
`**gdharness version**: ${SERVER_VERSION}`,
|
|
8111
8373
|
`**Godot version**: ${godotVersion ?? ""}`,
|
|
8112
8374
|
`**Bun version**: ${runtime()}`,
|
|
8113
|
-
`**OS**: ${
|
|
8375
|
+
`**OS**: ${process7.platform} ${process7.arch}`,
|
|
8114
8376
|
"**MCP client**:",
|
|
8115
8377
|
"",
|
|
8116
8378
|
"## What you did",
|
|
@@ -8199,35 +8461,82 @@ var init_issues = __esm(() => {
|
|
|
8199
8461
|
});
|
|
8200
8462
|
|
|
8201
8463
|
// src/class-cache.ts
|
|
8202
|
-
import { existsSync as
|
|
8203
|
-
import { join as
|
|
8464
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync5, statSync as statSync2 } from "node:fs";
|
|
8465
|
+
import { join as join8 } from "node:path";
|
|
8204
8466
|
function declaredClasses(projectPath) {
|
|
8205
8467
|
const declared = new Map;
|
|
8468
|
+
eachScript(projectPath, (script, source) => {
|
|
8469
|
+
const found = DECLARATION.exec(source);
|
|
8470
|
+
if (found?.[1]) {
|
|
8471
|
+
declared.set(found[1], script);
|
|
8472
|
+
}
|
|
8473
|
+
});
|
|
8474
|
+
return declared;
|
|
8475
|
+
}
|
|
8476
|
+
function eachScript(projectPath, each) {
|
|
8206
8477
|
const visit = (directory, prefix) => {
|
|
8207
|
-
if (
|
|
8478
|
+
if (existsSync7(join8(directory, ".gdignore"))) {
|
|
8208
8479
|
return;
|
|
8209
8480
|
}
|
|
8210
|
-
for (const entry of
|
|
8481
|
+
for (const entry of readdirSync3(directory, { withFileTypes: true })) {
|
|
8211
8482
|
if (entry.name.startsWith(".")) {
|
|
8212
8483
|
continue;
|
|
8213
8484
|
}
|
|
8214
|
-
const path =
|
|
8485
|
+
const path = join8(directory, entry.name);
|
|
8215
8486
|
if (entry.isDirectory()) {
|
|
8216
8487
|
visit(path, `${prefix}${entry.name}/`);
|
|
8217
8488
|
} else if (entry.isFile() && entry.name.endsWith(".gd")) {
|
|
8218
|
-
|
|
8219
|
-
if (found?.[1]) {
|
|
8220
|
-
declared.set(found[1], `res://${prefix}${entry.name}`);
|
|
8221
|
-
}
|
|
8489
|
+
each(`res://${prefix}${entry.name}`, readFileSync5(path, "utf8"));
|
|
8222
8490
|
}
|
|
8223
8491
|
}
|
|
8224
8492
|
};
|
|
8225
8493
|
visit(projectPath, "");
|
|
8226
|
-
|
|
8494
|
+
}
|
|
8495
|
+
function scriptsNaming(projectPath, classes) {
|
|
8496
|
+
const named = new Map(classes.map((name) => [name, ""]));
|
|
8497
|
+
const naming = [];
|
|
8498
|
+
eachScript(projectPath, (script, source) => {
|
|
8499
|
+
const declares = DECLARATION.exec(source)?.[1];
|
|
8500
|
+
if (declares !== undefined && named.has(declares)) {
|
|
8501
|
+
return;
|
|
8502
|
+
}
|
|
8503
|
+
const code = codeOf(source);
|
|
8504
|
+
if (classesNamedIn(code, named).length > 0) {
|
|
8505
|
+
naming.push({ script, tool: /^\s*@tool\b/m.test(code) });
|
|
8506
|
+
}
|
|
8507
|
+
});
|
|
8508
|
+
return naming.sort((a, b) => a.script.localeCompare(b.script));
|
|
8509
|
+
}
|
|
8510
|
+
function codeOf(source) {
|
|
8511
|
+
let code = "";
|
|
8512
|
+
let at = 0;
|
|
8513
|
+
while (at < source.length) {
|
|
8514
|
+
const here = source[at] ?? "";
|
|
8515
|
+
if (here === "#") {
|
|
8516
|
+
const end = source.indexOf(`
|
|
8517
|
+
`, at);
|
|
8518
|
+
at = end === -1 ? source.length : end;
|
|
8519
|
+
continue;
|
|
8520
|
+
}
|
|
8521
|
+
if (here === '"' || here === "'") {
|
|
8522
|
+
const fence = source.startsWith(here.repeat(3), at) ? here.repeat(3) : here;
|
|
8523
|
+
let end = at + fence.length;
|
|
8524
|
+
while (end < source.length && !source.startsWith(fence, end)) {
|
|
8525
|
+
end += source[end] === "\\" ? 2 : 1;
|
|
8526
|
+
}
|
|
8527
|
+
const literal = source.slice(at, Math.min(end + fence.length, source.length));
|
|
8528
|
+
code += literal.replace(/[^\n]/g, " ");
|
|
8529
|
+
at = end + fence.length;
|
|
8530
|
+
continue;
|
|
8531
|
+
}
|
|
8532
|
+
code += here;
|
|
8533
|
+
at += 1;
|
|
8534
|
+
}
|
|
8535
|
+
return code;
|
|
8227
8536
|
}
|
|
8228
8537
|
function cachedClasses(projectPath) {
|
|
8229
|
-
const cache =
|
|
8230
|
-
if (!
|
|
8538
|
+
const cache = join8(projectPath, ".godot", "global_script_class_cache.cfg");
|
|
8539
|
+
if (!existsSync7(cache)) {
|
|
8231
8540
|
return null;
|
|
8232
8541
|
}
|
|
8233
8542
|
const listed = new Map;
|
|
@@ -8340,7 +8649,7 @@ function uncachedClassNote(types) {
|
|
|
8340
8649
|
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.";
|
|
8341
8650
|
}
|
|
8342
8651
|
function cacheWrittenAt(projectPath) {
|
|
8343
|
-
const cache =
|
|
8652
|
+
const cache = join8(projectPath, ".godot", "global_script_class_cache.cfg");
|
|
8344
8653
|
try {
|
|
8345
8654
|
return statSync2(cache).mtimeMs;
|
|
8346
8655
|
} catch {
|
|
@@ -8354,14 +8663,14 @@ function declaredSince(projectPath, paths, names, since) {
|
|
|
8354
8663
|
return false;
|
|
8355
8664
|
}
|
|
8356
8665
|
try {
|
|
8357
|
-
return statSync2(
|
|
8666
|
+
return statSync2(join8(projectPath, path.slice("res://".length))).mtimeMs > since;
|
|
8358
8667
|
} catch {
|
|
8359
8668
|
return false;
|
|
8360
8669
|
}
|
|
8361
8670
|
});
|
|
8362
8671
|
}
|
|
8363
8672
|
function cachedAtMissingPaths(cached, projectPath) {
|
|
8364
|
-
return [...cached].filter(([, path]) => path.startsWith("res://") && !
|
|
8673
|
+
return [...cached].filter(([, path]) => path.startsWith("res://") && !existsSync7(join8(projectPath, path.slice("res://".length)))).map(([name]) => name).sort();
|
|
8365
8674
|
}
|
|
8366
8675
|
function staleAgainst(cached, projectPath) {
|
|
8367
8676
|
return [...declaredClasses(projectPath)].filter(([name, path]) => cached.get(name) !== path).map(([name]) => name);
|
|
@@ -15851,29 +16160,29 @@ function readParams(params, key) {
|
|
|
15851
16160
|
}
|
|
15852
16161
|
|
|
15853
16162
|
// src/setup.ts
|
|
15854
|
-
import { spawnSync
|
|
16163
|
+
import { spawnSync } from "node:child_process";
|
|
15855
16164
|
import { createHash as createHash2 } from "node:crypto";
|
|
15856
16165
|
import {
|
|
15857
16166
|
cpSync,
|
|
15858
|
-
existsSync as
|
|
16167
|
+
existsSync as existsSync8,
|
|
15859
16168
|
mkdirSync as mkdirSync4,
|
|
15860
|
-
readdirSync as
|
|
16169
|
+
readdirSync as readdirSync4,
|
|
15861
16170
|
readFileSync as readFileSync7,
|
|
15862
16171
|
rmdirSync,
|
|
15863
16172
|
rmSync as rmSync4,
|
|
15864
16173
|
statSync as statSync3,
|
|
15865
16174
|
writeFileSync as writeFileSync5
|
|
15866
16175
|
} from "node:fs";
|
|
15867
|
-
import { dirname as dirname4, join as
|
|
16176
|
+
import { dirname as dirname4, join as join9 } from "node:path";
|
|
15868
16177
|
import { fileURLToPath } from "node:url";
|
|
15869
16178
|
function editorAddonDigest(from = shippedAddonsDirectory()) {
|
|
15870
16179
|
const hash = createHash2("sha256");
|
|
15871
16180
|
const walk = (directory, relative) => {
|
|
15872
|
-
for (const name of
|
|
16181
|
+
for (const name of readdirSync4(directory).sort()) {
|
|
15873
16182
|
if (name === VERSION_MARKER || name === DIGEST_MARKER) {
|
|
15874
16183
|
continue;
|
|
15875
16184
|
}
|
|
15876
|
-
const path =
|
|
16185
|
+
const path = join9(directory, name);
|
|
15877
16186
|
const inside = `${relative}/${name}`;
|
|
15878
16187
|
if (statSync3(path).isDirectory()) {
|
|
15879
16188
|
walk(path, inside);
|
|
@@ -15887,7 +16196,7 @@ function editorAddonDigest(from = shippedAddonsDirectory()) {
|
|
|
15887
16196
|
}
|
|
15888
16197
|
};
|
|
15889
16198
|
for (const name of EDITOR_PLUGINS) {
|
|
15890
|
-
walk(
|
|
16199
|
+
walk(join9(from, name), name);
|
|
15891
16200
|
}
|
|
15892
16201
|
return hash.digest("hex");
|
|
15893
16202
|
}
|
|
@@ -15902,42 +16211,42 @@ function shippedEditorDigest() {
|
|
|
15902
16211
|
return shippedDigest ?? undefined;
|
|
15903
16212
|
}
|
|
15904
16213
|
function installedEditorDigest(projectPath) {
|
|
15905
|
-
const marker =
|
|
15906
|
-
if (!
|
|
16214
|
+
const marker = join9(projectPath, "addons", ADDONS[0], DIGEST_MARKER);
|
|
16215
|
+
if (!existsSync8(marker)) {
|
|
15907
16216
|
return null;
|
|
15908
16217
|
}
|
|
15909
16218
|
const said = readFileSync7(marker, "utf8").trim();
|
|
15910
16219
|
return said === "" ? null : said;
|
|
15911
16220
|
}
|
|
15912
16221
|
function shippedAddonsDirectory() {
|
|
15913
|
-
return
|
|
16222
|
+
return join9(dirname4(fileURLToPath(import.meta.url)), "godot", "addons");
|
|
15914
16223
|
}
|
|
15915
16224
|
function shippedOperationsScript() {
|
|
15916
|
-
return
|
|
16225
|
+
return join9(dirname4(fileURLToPath(import.meta.url)), "godot", "operations", "godot_operations.gd");
|
|
15917
16226
|
}
|
|
15918
16227
|
function installAddons(projectPath, from = shippedAddonsDirectory()) {
|
|
15919
16228
|
const installed = [];
|
|
15920
16229
|
for (const name of ADDONS) {
|
|
15921
|
-
const source =
|
|
15922
|
-
if (!
|
|
16230
|
+
const source = join9(from, name);
|
|
16231
|
+
if (!existsSync8(join9(source, name === "gdharness_runtime" ? "runtime_autoload.gd" : "plugin.cfg"))) {
|
|
15923
16232
|
throw new Error(`The package holds no ${name} addon at ${source}.`);
|
|
15924
16233
|
}
|
|
15925
|
-
const target =
|
|
15926
|
-
const replaced =
|
|
16234
|
+
const target = join9(projectPath, "addons", name);
|
|
16235
|
+
const replaced = existsSync8(target);
|
|
15927
16236
|
rmSync4(target, { recursive: true, force: true });
|
|
15928
16237
|
mkdirSync4(dirname4(target), { recursive: true });
|
|
15929
16238
|
cpSync(source, target, { recursive: true });
|
|
15930
|
-
writeFileSync5(
|
|
16239
|
+
writeFileSync5(join9(target, VERSION_MARKER), `${SERVER_VERSION}
|
|
15931
16240
|
`);
|
|
15932
16241
|
installed.push({ name, path: target, replaced });
|
|
15933
16242
|
}
|
|
15934
|
-
writeFileSync5(
|
|
16243
|
+
writeFileSync5(join9(projectPath, "addons", ADDONS[0], DIGEST_MARKER), `${editorAddonDigest(from)}
|
|
15935
16244
|
`);
|
|
15936
16245
|
return installed;
|
|
15937
16246
|
}
|
|
15938
16247
|
function installedAddonVersion(projectPath) {
|
|
15939
|
-
const marker =
|
|
15940
|
-
if (!
|
|
16248
|
+
const marker = join9(projectPath, "addons", ADDONS[0], VERSION_MARKER);
|
|
16249
|
+
if (!existsSync8(marker)) {
|
|
15941
16250
|
return null;
|
|
15942
16251
|
}
|
|
15943
16252
|
const said = readFileSync7(marker, "utf8").trim();
|
|
@@ -15946,14 +16255,14 @@ function installedAddonVersion(projectPath) {
|
|
|
15946
16255
|
function removeAddons(projectPath) {
|
|
15947
16256
|
const removed = [];
|
|
15948
16257
|
for (const name of ADDONS) {
|
|
15949
|
-
const target =
|
|
15950
|
-
if (
|
|
16258
|
+
const target = join9(projectPath, "addons", name);
|
|
16259
|
+
if (existsSync8(target)) {
|
|
15951
16260
|
rmSync4(target, { recursive: true, force: true });
|
|
15952
16261
|
removed.push(target);
|
|
15953
16262
|
}
|
|
15954
16263
|
}
|
|
15955
|
-
const addons =
|
|
15956
|
-
if (removed.length > 0 &&
|
|
16264
|
+
const addons = join9(projectPath, "addons");
|
|
16265
|
+
if (removed.length > 0 && existsSync8(addons) && readdirSync4(addons).length === 0) {
|
|
15957
16266
|
rmdirSync(addons);
|
|
15958
16267
|
removed.push(addons);
|
|
15959
16268
|
}
|
|
@@ -15987,7 +16296,7 @@ function loaderAmong(projectPath, named) {
|
|
|
15987
16296
|
}
|
|
15988
16297
|
let source = null;
|
|
15989
16298
|
try {
|
|
15990
|
-
source = readFileSync7(
|
|
16299
|
+
source = readFileSync7(join9(projectPath, path), "utf8");
|
|
15991
16300
|
} catch {}
|
|
15992
16301
|
if (source?.includes(RUNTIME_AUTOLOAD.path)) {
|
|
15993
16302
|
return { name: entry, path, exists: true };
|
|
@@ -16010,7 +16319,7 @@ function trackedByGit(projectPath, wanted) {
|
|
|
16010
16319
|
if (wanted.length === 0) {
|
|
16011
16320
|
return new Set;
|
|
16012
16321
|
}
|
|
16013
|
-
const asked =
|
|
16322
|
+
const asked = spawnSync("git", ["ls-files", "-z", "--", ...wanted], {
|
|
16014
16323
|
cwd: projectPath,
|
|
16015
16324
|
encoding: "utf8"
|
|
16016
16325
|
});
|
|
@@ -16029,10 +16338,10 @@ function enabledPlugins(settings) {
|
|
|
16029
16338
|
function inspectProject(projectPath) {
|
|
16030
16339
|
const problems = [];
|
|
16031
16340
|
const addons = ADDONS.map((name) => {
|
|
16032
|
-
const directory =
|
|
16033
|
-
const marker =
|
|
16034
|
-
const installed =
|
|
16035
|
-
const version =
|
|
16341
|
+
const directory = join9(projectPath, "addons", name);
|
|
16342
|
+
const marker = join9(directory, VERSION_MARKER);
|
|
16343
|
+
const installed = existsSync8(directory);
|
|
16344
|
+
const version = existsSync8(marker) ? readFileSync7(marker, "utf8").trim() : null;
|
|
16036
16345
|
const current = version === SERVER_VERSION;
|
|
16037
16346
|
if (!installed) {
|
|
16038
16347
|
problems.push(`addons/${name} is not installed; run gdharness setup`);
|
|
@@ -16041,7 +16350,7 @@ function inspectProject(projectPath) {
|
|
|
16041
16350
|
}
|
|
16042
16351
|
return { name, installed, version, current };
|
|
16043
16352
|
});
|
|
16044
|
-
const settings = parseProjectGodot(readFileSync7(
|
|
16353
|
+
const settings = parseProjectGodot(readFileSync7(join9(projectPath, "project.godot"), "utf8"));
|
|
16045
16354
|
const pluginsEnabled = enabledPlugins(settings);
|
|
16046
16355
|
for (const name of EDITOR_PLUGINS) {
|
|
16047
16356
|
if (!pluginsEnabled.includes(name)) {
|
|
@@ -16057,7 +16366,7 @@ function inspectProject(projectPath) {
|
|
|
16057
16366
|
const tracked = trackedByGit(projectPath, [...named.values()]);
|
|
16058
16367
|
const addonMissing = (path) => addons.some((addon) => !addon.installed && path.startsWith(`addons/${addon.name}/`));
|
|
16059
16368
|
for (const [name, path] of named) {
|
|
16060
|
-
if (!
|
|
16369
|
+
if (!existsSync8(join9(projectPath, path))) {
|
|
16061
16370
|
if (!addonMissing(path)) {
|
|
16062
16371
|
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`);
|
|
16063
16372
|
}
|
|
@@ -16816,7 +17125,11 @@ var init_tool_definitions = __esm(() => {
|
|
|
16816
17125
|
name: "editor_run",
|
|
16817
17126
|
description: "The run: starting the project, stopping it, or booting it once to see whether it comes up clean. start keeps it running and collecting output until it quits or is stopped, windowed where there is a display and headless where there is not, unless headless says otherwise; only runtime_capture needs the window. A run that quits on its own is kept, so a scene that prints an answer and quits is start, then editor_output until running is false. What runs is a scene: a SceneTree script is not an entry point here, so put the script on the root of a scene of its own and name that in scene. args hands the game its own flags, the ones it reads with OS.get_cmdline_user_args(), and a run carrying any is started by this server rather than by the editor. check boots it headless for a few frames, waits for it to quit, and answers with the verdict: whether it came up, and every error and warning it printed on the way. start and check both wait first for a scan the open editor is running and for the class cache it writes just after, since a game booted in between resolves no global class, as does every other engine this server starts on the project; waitedForEditorScanMs says how long, and scanNote says when a scan ran past thirty seconds and the game started during it. A start waits for the game to become something the runtime_* tools can talk to and says which it is under runtime: listening with the port it took, or why not, so the first call after a start does not have to be made twice. When it is not listening, mayYetAnnounce says whether that is final: false is a runtime that is not coming, true is a game still on its way up, which editor_status will see and runtimeWaitMs waits longer for. A start this server made also answers with transcript, the file both the run's streams are written to, so a watch on it can be armed off the start rather than off a second call.",
|
|
16818
17127
|
parameters: {
|
|
16819
|
-
projectPath: {
|
|
17128
|
+
projectPath: {
|
|
17129
|
+
...PROJECT_PATH,
|
|
17130
|
+
ops: ["start", "check", "stop", "wait"],
|
|
17131
|
+
description: "Absolute path to the project directory, the one holding project.godot. stop, wait: optional, the project whose run is meant; a run of another project is refused rather than touched."
|
|
17132
|
+
},
|
|
16820
17133
|
scene: {
|
|
16821
17134
|
type: "string",
|
|
16822
17135
|
ops: ["start", "check"],
|
|
@@ -16834,6 +17147,11 @@ var init_tool_definitions = __esm(() => {
|
|
|
16834
17147
|
ops: ["start"],
|
|
16835
17148
|
description: "start: on Windows, show the window on the desktop in use. By default a windowed run this server starts goes on a desktop of its own, where it renders and runtime_capture works but the window never shows and never takes the keyboard. A game a visible editor plays opens on the desktop in use, and one a hidden editor (editor_launch hidden: true) plays goes on its desktop. Elsewhere than Windows the window shows either way. Default false."
|
|
16836
17149
|
},
|
|
17150
|
+
silent: {
|
|
17151
|
+
type: "boolean",
|
|
17152
|
+
ops: ["start"],
|
|
17153
|
+
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, and the debug_* tools do not answer for it. 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."
|
|
17154
|
+
},
|
|
16837
17155
|
savesIn: {
|
|
16838
17156
|
type: "string",
|
|
16839
17157
|
ops: ["start", "check"],
|
|
@@ -16948,7 +17266,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16948
17266
|
},
|
|
16949
17267
|
{
|
|
16950
17268
|
name: "editor_rescan",
|
|
16951
|
-
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. 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.",
|
|
17269
|
+
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. 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. 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.",
|
|
16952
17270
|
parameters: {
|
|
16953
17271
|
projectPath: PROJECT_PATH,
|
|
16954
17272
|
timeoutMs: {
|
|
@@ -17054,7 +17372,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17054
17372
|
says: {
|
|
17055
17373
|
type: "string",
|
|
17056
17374
|
ops: ["find"],
|
|
17057
|
-
description:
|
|
17375
|
+
description: 'find: part of what the node has written on it as drawn, the way the text op reads it, case-insensitively, 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.'
|
|
17058
17376
|
},
|
|
17059
17377
|
limit: {
|
|
17060
17378
|
type: "integer",
|
|
@@ -17209,7 +17527,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17209
17527
|
says: {
|
|
17210
17528
|
type: "string",
|
|
17211
17529
|
ops: ["click"],
|
|
17212
|
-
description: "click: the words on the control to click, matched the way runtime_inspect find matches says (a plain word is contained in the text, a pattern with * or ? is a glob over it), among the controls on screen, and pressed in the same frame it was found in, so a panel rebuilding between a find and a click cannot free it first. Words on a label inside a button press the button. A control with something drawn over its centre that would take the pointer, such as a screen shown over it, is not on screen. The matches are ranked the way a person picks what to press: a button before text that cannot be pressed, then a control whose whole text is the words before one saying them as part of more, and the best one is pressed; found.picked says why. Several matches at the best rank are refused with each one listed, and index picks one."
|
|
17530
|
+
description: "click: the words on the control to click, matched the way runtime_inspect find matches says (a plain word is contained in the text, a pattern with * or ? is a glob over it, and | separates alternatives), among the controls on screen, and pressed in the same frame it was found in, so a panel rebuilding between a find and a click cannot free it first. Words on a label inside a button press the button. A control with something drawn over its centre that would take the pointer, such as a screen shown over it, is not on screen. The matches are ranked the way a person picks what to press: a button before text that cannot be pressed, then a control whose whole text, or one whole line of it such as the title on a card, is the words before one saying them as part of more, and the best one is pressed; found.picked says why. Several matches at the best rank are refused with each one listed, and index picks one."
|
|
17213
17531
|
},
|
|
17214
17532
|
action: {
|
|
17215
17533
|
type: "string",
|
|
@@ -17340,7 +17658,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17340
17658
|
says: {
|
|
17341
17659
|
type: "string",
|
|
17342
17660
|
ops: ["until"],
|
|
17343
|
-
description:
|
|
17661
|
+
description: 'until: wait for these words to appear anywhere under nodePath, or anywhere in the game when no nodePath is given, instead of for a property, which is how a panel that rebuilds its labels is waited on at all: the labels are named afresh each redraw and the panel is what stays put. Matched the way runtime_inspect find matches says: case-insensitive, a plain word anywhere in the text, a pattern with * or ? a glob over the whole of it, and alternatives separated by | met by whichever appears first ("*won*|*lost*"); on a node the player can see unless includeHidden says otherwise; a label with a line break is matched with the break in the words, and a backslash followed by n counts as one. Instead of, not as well as: a call carrying this and a property is refused, because they ask about different things and answering one of them silently is how a caller watches a screen believing they are watching a property. It looks every frame while a look is cheap and less often on a screen large enough for a look to cost a real share of a frame, so the game keeps its speed; the answer counts the frames the wait spanned and the looks it took.'
|
|
17344
17662
|
},
|
|
17345
17663
|
includeHidden: {
|
|
17346
17664
|
type: "boolean",
|
|
@@ -32099,10 +32417,10 @@ var init_stdio = __esm(() => {
|
|
|
32099
32417
|
});
|
|
32100
32418
|
|
|
32101
32419
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
|
|
32102
|
-
import
|
|
32420
|
+
import process9 from "node:process";
|
|
32103
32421
|
|
|
32104
32422
|
class StdioServerTransport {
|
|
32105
|
-
constructor(_stdin =
|
|
32423
|
+
constructor(_stdin = process9.stdin, _stdout = process9.stdout, options) {
|
|
32106
32424
|
this._stdin = _stdin;
|
|
32107
32425
|
this._stdout = _stdout;
|
|
32108
32426
|
this._started = false;
|
|
@@ -32167,10 +32485,10 @@ var init_stdio2 = __esm(() => {
|
|
|
32167
32485
|
});
|
|
32168
32486
|
|
|
32169
32487
|
// src/runtime-client.ts
|
|
32170
|
-
import { existsSync as
|
|
32488
|
+
import { existsSync as existsSync10, readdirSync as readdirSync6, readFileSync as readFileSync9, statSync as statSync4, unlinkSync } from "node:fs";
|
|
32171
32489
|
import { createConnection } from "node:net";
|
|
32172
32490
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
32173
|
-
import { dirname as dirname7, join as
|
|
32491
|
+
import { dirname as dirname7, join as join12, resolve as resolve3 } from "node:path";
|
|
32174
32492
|
import { setTimeout as delay } from "node:timers/promises";
|
|
32175
32493
|
function runtimeDirectory(variables = process.env) {
|
|
32176
32494
|
const explicit = envValue("GDHARNESS_RUNTIME_DIR", variables);
|
|
@@ -32178,20 +32496,20 @@ function runtimeDirectory(variables = process.env) {
|
|
|
32178
32496
|
return explicit;
|
|
32179
32497
|
}
|
|
32180
32498
|
const perUser = envValue("XDG_RUNTIME_DIR", variables);
|
|
32181
|
-
return
|
|
32499
|
+
return join12(perUser ?? tmpdir3(), "gdharness");
|
|
32182
32500
|
}
|
|
32183
32501
|
function runtimeDirectories(variables = process.env) {
|
|
32184
32502
|
const candidates = [runtimeDirectory(variables)];
|
|
32185
32503
|
const fallbacks = process.platform === "win32" ? [envValue("SystemRoot", variables) ?? envValue("windir", variables) ?? "C:\\Windows"] : ["/tmp", "/var/tmp"];
|
|
32186
32504
|
for (const base of fallbacks) {
|
|
32187
|
-
candidates.push(
|
|
32505
|
+
candidates.push(join12(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
|
|
32188
32506
|
}
|
|
32189
32507
|
return [...new Set(candidates.map((path) => resolve3(path)))];
|
|
32190
32508
|
}
|
|
32191
32509
|
function errorReportOf(pid, directories = runtimeDirectories()) {
|
|
32192
32510
|
for (const directory of directories) {
|
|
32193
|
-
const path =
|
|
32194
|
-
if (
|
|
32511
|
+
const path = join12(directory, `runtime-${pid}.log`);
|
|
32512
|
+
if (existsSync10(path)) {
|
|
32195
32513
|
return path;
|
|
32196
32514
|
}
|
|
32197
32515
|
}
|
|
@@ -32219,14 +32537,6 @@ function reportAlreadyInTranscript(transcript, report) {
|
|
|
32219
32537
|
const end = boundary + copied.length;
|
|
32220
32538
|
return end <= report.length && report.subarray(boundary, end).equals(copied) ? end : report.length;
|
|
32221
32539
|
}
|
|
32222
|
-
function processAlive(pid) {
|
|
32223
|
-
try {
|
|
32224
|
-
process.kill(pid, 0);
|
|
32225
|
-
return true;
|
|
32226
|
-
} catch (error) {
|
|
32227
|
-
return error.code === "EPERM";
|
|
32228
|
-
}
|
|
32229
|
-
}
|
|
32230
32540
|
function parseAnnouncement(file, pid) {
|
|
32231
32541
|
let fields;
|
|
32232
32542
|
try {
|
|
@@ -32314,46 +32624,52 @@ function strangersAmong(candidates, now, startTimes = startTimesOf) {
|
|
|
32314
32624
|
const known = verdicts.get(one.file);
|
|
32315
32625
|
return known === undefined || known.writtenAt !== one.writtenAt || !known.stranger && now - known.judgedAt > CONFIRMED_FOR_MS;
|
|
32316
32626
|
});
|
|
32317
|
-
if (toJudge.length > 0) {
|
|
32318
|
-
|
|
32319
|
-
|
|
32320
|
-
|
|
32321
|
-
|
|
32322
|
-
|
|
32323
|
-
|
|
32627
|
+
if (toJudge.length > 0 && judging === null) {
|
|
32628
|
+
judging = startTimes(toJudge.map((one) => one.pid)).then((began) => {
|
|
32629
|
+
for (const one of toJudge) {
|
|
32630
|
+
const at = began.get(one.pid);
|
|
32631
|
+
const stranger = at !== undefined && at - one.writtenAt > STARTED_AFTER_ANNOUNCING_MS;
|
|
32632
|
+
verdicts.set(one.file, { writtenAt: one.writtenAt, judgedAt: now, stranger });
|
|
32633
|
+
}
|
|
32634
|
+
}).catch(() => {}).finally(() => {
|
|
32635
|
+
judging = null;
|
|
32636
|
+
});
|
|
32324
32637
|
}
|
|
32325
|
-
return new Set(candidates.filter((one) =>
|
|
32638
|
+
return new Set(candidates.filter((one) => {
|
|
32639
|
+
const known = verdicts.get(one.file);
|
|
32640
|
+
return known?.stranger === true && known.writtenAt === one.writtenAt;
|
|
32641
|
+
}).map((one) => one.file));
|
|
32326
32642
|
}
|
|
32327
32643
|
function announcedIn(directory) {
|
|
32328
|
-
if (!
|
|
32644
|
+
if (!existsSync10(directory)) {
|
|
32329
32645
|
return [];
|
|
32330
32646
|
}
|
|
32331
32647
|
const found = [];
|
|
32332
32648
|
const candidates = [];
|
|
32333
32649
|
const entries = [];
|
|
32334
|
-
for (const entry of
|
|
32650
|
+
for (const entry of readdirSync6(directory)) {
|
|
32335
32651
|
const report = ERROR_REPORT_PATTERN.exec(entry);
|
|
32336
32652
|
if (report) {
|
|
32337
|
-
sweepErrorReport(
|
|
32653
|
+
sweepErrorReport(join12(directory, entry), Number.parseInt(report[1] ?? "", 10));
|
|
32338
32654
|
continue;
|
|
32339
32655
|
}
|
|
32340
32656
|
const match = ANNOUNCEMENT_PATTERN.exec(entry);
|
|
32341
32657
|
if (!match) {
|
|
32342
32658
|
continue;
|
|
32343
32659
|
}
|
|
32344
|
-
const file =
|
|
32660
|
+
const file = join12(directory, entry);
|
|
32345
32661
|
const pid = Number.parseInt(match[1] ?? "", 10);
|
|
32346
|
-
const
|
|
32347
|
-
entries.push({ file, pid,
|
|
32348
|
-
if (
|
|
32662
|
+
const running = alive(pid);
|
|
32663
|
+
entries.push({ file, pid, running });
|
|
32664
|
+
if (running) {
|
|
32349
32665
|
try {
|
|
32350
32666
|
candidates.push({ file, pid, writtenAt: statSync4(file).mtimeMs });
|
|
32351
32667
|
} catch {}
|
|
32352
32668
|
}
|
|
32353
32669
|
}
|
|
32354
32670
|
const strangers = strangersAmong(candidates, Date.now());
|
|
32355
|
-
for (const { file, pid,
|
|
32356
|
-
const gone = !
|
|
32671
|
+
for (const { file, pid, running } of entries) {
|
|
32672
|
+
const gone = !running || strangers.has(file);
|
|
32357
32673
|
const announced = gone ? { kind: "rubbish" } : parseAnnouncement(file, pid);
|
|
32358
32674
|
if (announced.kind === "rubbish") {
|
|
32359
32675
|
if (!gone) {
|
|
@@ -32369,7 +32685,7 @@ function announcedIn(directory) {
|
|
|
32369
32685
|
found.push(announced);
|
|
32370
32686
|
}
|
|
32371
32687
|
const held = new Set(entries.map((one) => one.file));
|
|
32372
|
-
const here =
|
|
32688
|
+
const here = join12(directory, ".");
|
|
32373
32689
|
for (const file of verdicts.keys()) {
|
|
32374
32690
|
if (dirname7(file) === here && !held.has(file)) {
|
|
32375
32691
|
verdicts.delete(file);
|
|
@@ -32378,13 +32694,13 @@ function announcedIn(directory) {
|
|
|
32378
32694
|
return found;
|
|
32379
32695
|
}
|
|
32380
32696
|
async function announcementEnded(pid, directories = runtimeDirectories()) {
|
|
32381
|
-
const files = directories.map((directory) =>
|
|
32697
|
+
const files = directories.map((directory) => join12(directory, `runtime-${pid}.json`)).filter((file) => existsSync10(file));
|
|
32382
32698
|
const first = files[0];
|
|
32383
32699
|
if (first === undefined) {
|
|
32384
32700
|
return;
|
|
32385
32701
|
}
|
|
32386
32702
|
const until = Date.now() + ENDED_WITHIN_MS;
|
|
32387
|
-
while (
|
|
32703
|
+
while (alive(pid)) {
|
|
32388
32704
|
const left = until - Date.now();
|
|
32389
32705
|
if (left <= 0) {
|
|
32390
32706
|
return;
|
|
@@ -32400,7 +32716,7 @@ async function announcementEnded(pid, directories = runtimeDirectories()) {
|
|
|
32400
32716
|
}
|
|
32401
32717
|
}
|
|
32402
32718
|
function sweepErrorReport(path, pid, now = Date.now()) {
|
|
32403
|
-
if (
|
|
32719
|
+
if (alive(pid)) {
|
|
32404
32720
|
return;
|
|
32405
32721
|
}
|
|
32406
32722
|
try {
|
|
@@ -32663,8 +32979,9 @@ function runtimeRequest(endpoint, command, params, timeoutMs, keepListening = fa
|
|
|
32663
32979
|
});
|
|
32664
32980
|
});
|
|
32665
32981
|
}
|
|
32666
|
-
var RUNTIME_PROTOCOL = 2, ANNOUNCEMENT_PATTERN, ERROR_REPORT_PATTERN, CONSOLE_BOUNDARY = "[gdharness] runtime listening on ", ERROR_REPORT_KEEP_MS, GONE_WINDOW_MS = 120000, GONE_KEPT = 32, gone, STARTED_AFTER_ANNOUNCING_MS = 2000, CONFIRMED_FOR_MS = 60000, JUDGED_AFTER_MS = 60000, verdicts, ENDED_WITHIN_MS = 3000, LOOK_EVERY_MS = 50, ANNOUNCE_BUDGET_MS = 5000, nextRequestId = 1, LATE_ANSWER_MS, LATE_KEPT = 50, lateAnswers;
|
|
32982
|
+
var RUNTIME_PROTOCOL = 2, ANNOUNCEMENT_PATTERN, ERROR_REPORT_PATTERN, CONSOLE_BOUNDARY = "[gdharness] runtime listening on ", ERROR_REPORT_KEEP_MS, GONE_WINDOW_MS = 120000, GONE_KEPT = 32, gone, STARTED_AFTER_ANNOUNCING_MS = 2000, CONFIRMED_FOR_MS = 60000, JUDGED_AFTER_MS = 60000, verdicts, judging = null, ENDED_WITHIN_MS = 3000, LOOK_EVERY_MS = 50, ANNOUNCE_BUDGET_MS = 5000, nextRequestId = 1, LATE_ANSWER_MS, LATE_KEPT = 50, lateAnswers;
|
|
32667
32983
|
var init_runtime_client = __esm(() => {
|
|
32984
|
+
init_alive();
|
|
32668
32985
|
init_paths();
|
|
32669
32986
|
init_process_children();
|
|
32670
32987
|
ANNOUNCEMENT_PATTERN = /^runtime-(\d+)\.json$/;
|
|
@@ -32678,9 +32995,9 @@ var init_runtime_client = __esm(() => {
|
|
|
32678
32995
|
|
|
32679
32996
|
// src/boot-note.ts
|
|
32680
32997
|
import { mkdirSync as mkdirSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "node:fs";
|
|
32681
|
-
import { dirname as dirname8, join as
|
|
32998
|
+
import { dirname as dirname8, join as join13 } from "node:path";
|
|
32682
32999
|
function bootNotePath(projectPath) {
|
|
32683
|
-
return
|
|
33000
|
+
return join13(projectPath, ".godot", "gdharness-boot.json");
|
|
32684
33001
|
}
|
|
32685
33002
|
function readBootNote(projectPath) {
|
|
32686
33003
|
try {
|
|
@@ -32713,9 +33030,9 @@ var init_boot_note = __esm(() => {
|
|
|
32713
33030
|
|
|
32714
33031
|
// src/breakpoint-note.ts
|
|
32715
33032
|
import { mkdirSync as mkdirSync7, readFileSync as readFileSync11, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "node:fs";
|
|
32716
|
-
import { dirname as dirname9, isAbsolute as isAbsolute2, join as
|
|
33033
|
+
import { dirname as dirname9, isAbsolute as isAbsolute2, join as join14, relative as relative2 } from "node:path";
|
|
32717
33034
|
function breakpointNotePath(projectPath) {
|
|
32718
|
-
return
|
|
33035
|
+
return join14(projectPath, ".godot", "gdharness-breakpoints.json");
|
|
32719
33036
|
}
|
|
32720
33037
|
function writeBreakpointNote(projectPath, held) {
|
|
32721
33038
|
const path = breakpointNotePath(projectPath);
|
|
@@ -32756,7 +33073,7 @@ function readBreakpointNote(projectPath) {
|
|
|
32756
33073
|
}
|
|
32757
33074
|
const lines = Array.isArray(listed) ? listed.filter((line) => Number.isInteger(line) && line > 0) : [];
|
|
32758
33075
|
if (lines.length > 0) {
|
|
32759
|
-
held.push({ scriptPath: realPathOr(
|
|
33076
|
+
held.push({ scriptPath: realPathOr(join14(projectPath, script)), lines });
|
|
32760
33077
|
}
|
|
32761
33078
|
}
|
|
32762
33079
|
return held;
|
|
@@ -32775,9 +33092,9 @@ var init_breakpoint_note = __esm(() => {
|
|
|
32775
33092
|
|
|
32776
33093
|
// src/class-note.ts
|
|
32777
33094
|
import { mkdirSync as mkdirSync8, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "node:fs";
|
|
32778
|
-
import { dirname as dirname10, join as
|
|
33095
|
+
import { dirname as dirname10, join as join15 } from "node:path";
|
|
32779
33096
|
function classNotePath(projectPath) {
|
|
32780
|
-
return
|
|
33097
|
+
return join15(projectPath, ".godot", "gdharness-classes.json");
|
|
32781
33098
|
}
|
|
32782
33099
|
function readClassNote(projectPath) {
|
|
32783
33100
|
try {
|
|
@@ -32806,11 +33123,11 @@ function writeClassNote(projectPath, note) {
|
|
|
32806
33123
|
var init_class_note = () => {};
|
|
32807
33124
|
|
|
32808
33125
|
// src/config-pin.ts
|
|
32809
|
-
import { existsSync as
|
|
32810
|
-
import { join as
|
|
33126
|
+
import { existsSync as existsSync11, readFileSync as readFileSync13 } from "node:fs";
|
|
33127
|
+
import { join as join16 } from "node:path";
|
|
32811
33128
|
function versionsNamedInConfig(projectPath) {
|
|
32812
|
-
const config =
|
|
32813
|
-
if (!
|
|
33129
|
+
const config = join16(projectPath, ".mcp.json");
|
|
33130
|
+
if (!existsSync11(config)) {
|
|
32814
33131
|
return [];
|
|
32815
33132
|
}
|
|
32816
33133
|
let text;
|
|
@@ -33443,6 +33760,9 @@ class GodotDAPClient {
|
|
|
33443
33760
|
isConnected() {
|
|
33444
33761
|
return this.connected;
|
|
33445
33762
|
}
|
|
33763
|
+
connection() {
|
|
33764
|
+
return this.connected ? this.socket : null;
|
|
33765
|
+
}
|
|
33446
33766
|
isStopped() {
|
|
33447
33767
|
return this.halt !== null;
|
|
33448
33768
|
}
|
|
@@ -33572,7 +33892,7 @@ var init_desktop = () => {};
|
|
|
33572
33892
|
// src/editor-log.ts
|
|
33573
33893
|
import {
|
|
33574
33894
|
closeSync,
|
|
33575
|
-
existsSync as
|
|
33895
|
+
existsSync as existsSync12,
|
|
33576
33896
|
mkdirSync as mkdirSync9,
|
|
33577
33897
|
openSync,
|
|
33578
33898
|
readFileSync as readFileSync14,
|
|
@@ -33581,12 +33901,12 @@ import {
|
|
|
33581
33901
|
statSync as statSync5,
|
|
33582
33902
|
writeFileSync as writeFileSync10
|
|
33583
33903
|
} from "node:fs";
|
|
33584
|
-
import { dirname as dirname11, join as
|
|
33904
|
+
import { dirname as dirname11, join as join17 } from "node:path";
|
|
33585
33905
|
function editorLogPath(projectPath) {
|
|
33586
|
-
return
|
|
33906
|
+
return join17(projectPath, ".godot", "gdharness-editor.log");
|
|
33587
33907
|
}
|
|
33588
33908
|
function editorLogNotePath(projectPath) {
|
|
33589
|
-
return
|
|
33909
|
+
return join17(projectPath, ".godot", "gdharness-editor.json");
|
|
33590
33910
|
}
|
|
33591
33911
|
function readEditorLogNote(projectPath) {
|
|
33592
33912
|
try {
|
|
@@ -33631,7 +33951,7 @@ function editorConsole(projectPath, editorPid, openedByAServer) {
|
|
|
33631
33951
|
return { kind: "older server", notedPid: noted?.pid ?? null };
|
|
33632
33952
|
}
|
|
33633
33953
|
const path = editorLogPath(projectPath);
|
|
33634
|
-
if (!
|
|
33954
|
+
if (!existsSync12(path)) {
|
|
33635
33955
|
return { kind: "not written" };
|
|
33636
33956
|
}
|
|
33637
33957
|
const log = new GameLog;
|
|
@@ -38060,7 +38380,7 @@ var init_lsp_client = __esm(() => {
|
|
|
38060
38380
|
});
|
|
38061
38381
|
|
|
38062
38382
|
// src/outside.ts
|
|
38063
|
-
import { spawn } from "node:child_process";
|
|
38383
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
38064
38384
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
38065
38385
|
function changesFrom(env, base) {
|
|
38066
38386
|
const changes = {};
|
|
@@ -38093,7 +38413,7 @@ function readLaunched(printed) {
|
|
|
38093
38413
|
}
|
|
38094
38414
|
async function launchOutsideTheTree(spec, runtime = process.execPath, keeper = KEEPER_SCRIPT) {
|
|
38095
38415
|
return await new Promise((resolve) => {
|
|
38096
|
-
const launcher =
|
|
38416
|
+
const launcher = spawn2(runtime, [keeper, "launch"], {
|
|
38097
38417
|
stdio: ["pipe", "pipe", "pipe"],
|
|
38098
38418
|
windowsHide: true
|
|
38099
38419
|
});
|
|
@@ -38132,14 +38452,14 @@ import { execFile as execFile4 } from "node:child_process";
|
|
|
38132
38452
|
import { promisify as promisify4 } from "node:util";
|
|
38133
38453
|
async function cpuSecondsOf(pid) {
|
|
38134
38454
|
try {
|
|
38135
|
-
return process.platform === "win32" ? await
|
|
38455
|
+
return process.platform === "win32" ? await secondsOnWindows(pid) : await askPosix2(pid);
|
|
38136
38456
|
} catch {
|
|
38137
38457
|
return;
|
|
38138
38458
|
}
|
|
38139
38459
|
}
|
|
38140
|
-
async function
|
|
38141
|
-
const
|
|
38142
|
-
const seconds = Number(
|
|
38460
|
+
async function secondsOnWindows(pid) {
|
|
38461
|
+
const said = await askWindows(`(Get-Process -Id ${pid}).CPU`, ASK_TIMEOUT_MS2);
|
|
38462
|
+
const seconds = Number(said.trim().replace(",", "."));
|
|
38143
38463
|
return Number.isFinite(seconds) ? seconds : undefined;
|
|
38144
38464
|
}
|
|
38145
38465
|
async function askPosix2(pid) {
|
|
@@ -38163,17 +38483,18 @@ function secondsFromClock(printed) {
|
|
|
38163
38483
|
}
|
|
38164
38484
|
var run4, ASK_TIMEOUT_MS2 = 6000;
|
|
38165
38485
|
var init_process_time = __esm(() => {
|
|
38486
|
+
init_windows_ask();
|
|
38166
38487
|
run4 = promisify4(execFile4);
|
|
38167
38488
|
});
|
|
38168
38489
|
|
|
38169
38490
|
// src/project-scan.ts
|
|
38170
|
-
import { existsSync as
|
|
38171
|
-
import { join as
|
|
38491
|
+
import { existsSync as existsSync13, readdirSync as readdirSync7, readFileSync as readFileSync15 } from "node:fs";
|
|
38492
|
+
import { join as join18 } from "node:path";
|
|
38172
38493
|
function skipped(name) {
|
|
38173
38494
|
return name.startsWith(".") || SKIPPED.has(name);
|
|
38174
38495
|
}
|
|
38175
38496
|
function steppedOver(directory) {
|
|
38176
|
-
return
|
|
38497
|
+
return existsSync13(join18(directory, ".gdignore"));
|
|
38177
38498
|
}
|
|
38178
38499
|
function projectStructure(projectPath) {
|
|
38179
38500
|
const structure = { scenes: 0, scripts: 0, assets: 0, other: 0 };
|
|
@@ -38181,12 +38502,12 @@ function projectStructure(projectPath) {
|
|
|
38181
38502
|
if (steppedOver(directory)) {
|
|
38182
38503
|
return;
|
|
38183
38504
|
}
|
|
38184
|
-
for (const entry of
|
|
38505
|
+
for (const entry of readdirSync7(directory, { withFileTypes: true })) {
|
|
38185
38506
|
if (skipped(entry.name)) {
|
|
38186
38507
|
continue;
|
|
38187
38508
|
}
|
|
38188
38509
|
if (entry.isDirectory()) {
|
|
38189
|
-
visit(
|
|
38510
|
+
visit(join18(directory, entry.name));
|
|
38190
38511
|
} else if (entry.isFile()) {
|
|
38191
38512
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
38192
38513
|
if (extension === "tscn") {
|
|
@@ -38210,20 +38531,20 @@ function scriptsWithoutUid(projectPath) {
|
|
|
38210
38531
|
if (steppedOver(directory)) {
|
|
38211
38532
|
return;
|
|
38212
38533
|
}
|
|
38213
|
-
for (const entry of
|
|
38534
|
+
for (const entry of readdirSync7(directory, { withFileTypes: true })) {
|
|
38214
38535
|
if (skipped(entry.name)) {
|
|
38215
38536
|
continue;
|
|
38216
38537
|
}
|
|
38217
38538
|
const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
38218
38539
|
if (entry.isDirectory()) {
|
|
38219
|
-
visit(
|
|
38540
|
+
visit(join18(directory, entry.name), spelled);
|
|
38220
38541
|
continue;
|
|
38221
38542
|
}
|
|
38222
38543
|
if (!entry.isFile()) {
|
|
38223
38544
|
continue;
|
|
38224
38545
|
}
|
|
38225
38546
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
38226
|
-
if (CARRIES_A_UID.has(extension) && !
|
|
38547
|
+
if (CARRIES_A_UID.has(extension) && !existsSync13(join18(directory, `${entry.name}.uid`))) {
|
|
38227
38548
|
found.push(spelled);
|
|
38228
38549
|
}
|
|
38229
38550
|
}
|
|
@@ -38251,14 +38572,14 @@ function searchProject(projectPath, options) {
|
|
|
38251
38572
|
if (steppedOver(directory)) {
|
|
38252
38573
|
return;
|
|
38253
38574
|
}
|
|
38254
|
-
for (const entry of
|
|
38575
|
+
for (const entry of readdirSync7(directory, { withFileTypes: true })) {
|
|
38255
38576
|
if (full()) {
|
|
38256
38577
|
return;
|
|
38257
38578
|
}
|
|
38258
38579
|
if (skipped(entry.name)) {
|
|
38259
38580
|
continue;
|
|
38260
38581
|
}
|
|
38261
|
-
const entryPath =
|
|
38582
|
+
const entryPath = join18(directory, entry.name);
|
|
38262
38583
|
if (entry.isDirectory()) {
|
|
38263
38584
|
visit(entryPath);
|
|
38264
38585
|
continue;
|
|
@@ -38299,12 +38620,12 @@ var init_project_scan = __esm(() => {
|
|
|
38299
38620
|
|
|
38300
38621
|
// src/reimport.ts
|
|
38301
38622
|
import { createHash as createHash3 } from "node:crypto";
|
|
38302
|
-
import { existsSync as
|
|
38303
|
-
import { basename as basename3, join as
|
|
38623
|
+
import { existsSync as existsSync14, readFileSync as readFileSync16, rmSync as rmSync8, statSync as statSync6, utimesSync, writeFileSync as writeFileSync11 } from "node:fs";
|
|
38624
|
+
import { basename as basename3, join as join19 } from "node:path";
|
|
38304
38625
|
function forceNextImport(projectPath, resourcePath) {
|
|
38305
38626
|
const relative = resourcePath.replace(/^res:\/\//, "");
|
|
38306
|
-
const sidecar =
|
|
38307
|
-
if (!
|
|
38627
|
+
const sidecar = join19(projectPath, `${relative}.import`);
|
|
38628
|
+
if (!existsSync14(sidecar)) {
|
|
38308
38629
|
return;
|
|
38309
38630
|
}
|
|
38310
38631
|
const { atime, mtimeMs } = statSync6(sidecar);
|
|
@@ -38314,15 +38635,15 @@ function forceNextImport(projectPath, resourcePath) {
|
|
|
38314
38635
|
}
|
|
38315
38636
|
function importRecord(projectPath, resourcePath) {
|
|
38316
38637
|
const digest = createHash3("md5").update(resourcePath).digest("hex");
|
|
38317
|
-
return
|
|
38638
|
+
return join19(projectPath, ".godot", "imported", `${basename3(resourcePath)}-${digest}.md5`);
|
|
38318
38639
|
}
|
|
38319
38640
|
var init_reimport = () => {};
|
|
38320
38641
|
|
|
38321
38642
|
// src/restart-note.ts
|
|
38322
38643
|
import { mkdirSync as mkdirSync10, readFileSync as readFileSync17, rmSync as rmSync9, writeFileSync as writeFileSync12 } from "node:fs";
|
|
38323
|
-
import { dirname as dirname13, join as
|
|
38644
|
+
import { dirname as dirname13, join as join20 } from "node:path";
|
|
38324
38645
|
function restartNotePath(projectPath) {
|
|
38325
|
-
return
|
|
38646
|
+
return join20(projectPath, ".godot", "gdharness-restart.json");
|
|
38326
38647
|
}
|
|
38327
38648
|
function noteRestartBegun(note) {
|
|
38328
38649
|
const path = restartNotePath(note.projectPath);
|
|
@@ -38361,28 +38682,29 @@ var init_restart_note = __esm(() => {
|
|
|
38361
38682
|
});
|
|
38362
38683
|
|
|
38363
38684
|
// src/run-record.ts
|
|
38364
|
-
import {
|
|
38685
|
+
import { execFile as execFile5 } from "node:child_process";
|
|
38365
38686
|
import { createHash as createHash4 } from "node:crypto";
|
|
38366
38687
|
import {
|
|
38367
38688
|
mkdirSync as mkdirSync11,
|
|
38368
38689
|
openSync as openSync2,
|
|
38369
|
-
readdirSync as
|
|
38690
|
+
readdirSync as readdirSync8,
|
|
38370
38691
|
readFileSync as readFileSync18,
|
|
38371
38692
|
realpathSync as realpathSync3,
|
|
38372
38693
|
rmSync as rmSync10,
|
|
38373
38694
|
statSync as statSync7,
|
|
38374
38695
|
writeFileSync as writeFileSync13
|
|
38375
38696
|
} from "node:fs";
|
|
38376
|
-
import { basename as basename4, dirname as dirname14, join as
|
|
38697
|
+
import { basename as basename4, dirname as dirname14, join as join21, resolve as resolve5 } from "node:path";
|
|
38698
|
+
import { promisify as promisify5 } from "node:util";
|
|
38377
38699
|
function runsDirectory() {
|
|
38378
|
-
return
|
|
38700
|
+
return join21(runtimeDirectory(), "runs");
|
|
38379
38701
|
}
|
|
38380
38702
|
function realAsFarAsItExists(path) {
|
|
38381
38703
|
const missing = [];
|
|
38382
38704
|
let at = path;
|
|
38383
38705
|
for (;; ) {
|
|
38384
38706
|
try {
|
|
38385
|
-
return
|
|
38707
|
+
return join21(realpathSync3.native(at), ...missing);
|
|
38386
38708
|
} catch {
|
|
38387
38709
|
const parent = dirname14(at);
|
|
38388
38710
|
if (parent === at) {
|
|
@@ -38399,15 +38721,15 @@ function projectKey(projectPath) {
|
|
|
38399
38721
|
return createHash4("sha256").update(folded.replaceAll("\\", "/")).digest("hex").slice(0, 16);
|
|
38400
38722
|
}
|
|
38401
38723
|
function runRecordPath(projectPath, directory = runtimeDirectory()) {
|
|
38402
|
-
return
|
|
38724
|
+
return join21(directory, "runs", `note-${projectKey(projectPath)}.json`);
|
|
38403
38725
|
}
|
|
38404
38726
|
function openTranscript(startedAt) {
|
|
38405
38727
|
mkdirSync11(runsDirectory(), { recursive: true });
|
|
38406
|
-
const path =
|
|
38728
|
+
const path = join21(runsDirectory(), `run-${startedAt}.log`);
|
|
38407
38729
|
return { path, fd: openSync2(path, "a") };
|
|
38408
38730
|
}
|
|
38409
38731
|
function editorNotePath(projectPath, directory = runtimeDirectory()) {
|
|
38410
|
-
return
|
|
38732
|
+
return join21(directory, "runs", `editor-${projectKey(projectPath)}.json`);
|
|
38411
38733
|
}
|
|
38412
38734
|
function writeEditorRunNote(note) {
|
|
38413
38735
|
mkdirSync11(runsDirectory(), { recursive: true });
|
|
@@ -38415,7 +38737,7 @@ function writeEditorRunNote(note) {
|
|
|
38415
38737
|
}
|
|
38416
38738
|
function readEditorRunNote(projectPath) {
|
|
38417
38739
|
for (const directory of runtimeDirectories()) {
|
|
38418
|
-
const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(
|
|
38740
|
+
const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(join21(directory, "runs", "editor-run.json"));
|
|
38419
38741
|
if (note !== null && isSameDirectory(note.projectPath, projectPath)) {
|
|
38420
38742
|
return note;
|
|
38421
38743
|
}
|
|
@@ -38447,7 +38769,7 @@ function readRunRecord(projectPath) {
|
|
|
38447
38769
|
if (own !== null) {
|
|
38448
38770
|
return own;
|
|
38449
38771
|
}
|
|
38450
|
-
const shared = recordAt(
|
|
38772
|
+
const shared = recordAt(join21(directory, "runs", SHARED_NOTE));
|
|
38451
38773
|
if (shared !== null && shared.projectPath !== "" && isSameDirectory(shared.projectPath, projectPath)) {
|
|
38452
38774
|
return shared;
|
|
38453
38775
|
}
|
|
@@ -38459,13 +38781,13 @@ function everyRunRecord() {
|
|
|
38459
38781
|
for (const directory of runtimeDirectories()) {
|
|
38460
38782
|
let entries;
|
|
38461
38783
|
try {
|
|
38462
|
-
entries =
|
|
38784
|
+
entries = readdirSync8(join21(directory, "runs"));
|
|
38463
38785
|
} catch {
|
|
38464
38786
|
continue;
|
|
38465
38787
|
}
|
|
38466
38788
|
for (const entry of entries) {
|
|
38467
38789
|
if (entry === SHARED_NOTE || entry.startsWith("note-") && entry.endsWith(".json")) {
|
|
38468
|
-
const record = recordAt(
|
|
38790
|
+
const record = recordAt(join21(directory, "runs", entry));
|
|
38469
38791
|
if (record !== null) {
|
|
38470
38792
|
found.push(record);
|
|
38471
38793
|
}
|
|
@@ -38503,49 +38825,47 @@ function recordAt(path) {
|
|
|
38503
38825
|
...typeof fields["exitSignal"] === "string" ? { exitSignal: fields["exitSignal"] } : {}
|
|
38504
38826
|
};
|
|
38505
38827
|
}
|
|
38506
|
-
function runningAs(pid) {
|
|
38828
|
+
async function runningAs(pid) {
|
|
38507
38829
|
if (process.platform === "win32") {
|
|
38508
|
-
const answer = windowsCommandLine(pid);
|
|
38830
|
+
const answer = await windowsCommandLine(pid);
|
|
38509
38831
|
if (answer !== null) {
|
|
38510
38832
|
const [, ...rest] = answer.split(`
|
|
38511
38833
|
`);
|
|
38512
38834
|
const line = rest.join(`
|
|
38513
38835
|
`).trim();
|
|
38514
|
-
const began = startedAt(pid, answer);
|
|
38836
|
+
const began = await startedAt(pid, answer);
|
|
38515
38837
|
if (line !== "") {
|
|
38516
38838
|
return { kind: "commandLine", text: line, ...began === null ? {} : { startedAt: began } };
|
|
38517
38839
|
}
|
|
38518
38840
|
}
|
|
38519
|
-
const image = windowsImage(pid);
|
|
38841
|
+
const image = await windowsImage(pid);
|
|
38520
38842
|
return image === null ? null : { kind: "image", text: image };
|
|
38521
38843
|
}
|
|
38522
38844
|
try {
|
|
38523
|
-
const began = startedAt(pid, null);
|
|
38845
|
+
const began = await startedAt(pid, null);
|
|
38524
38846
|
const when = began === null ? {} : { startedAt: began };
|
|
38525
38847
|
if (process.platform === "linux") {
|
|
38526
38848
|
const raw = readFileSync18(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
|
|
38527
38849
|
return raw === "" ? null : { kind: "commandLine", text: raw, ...when };
|
|
38528
38850
|
}
|
|
38529
|
-
const args =
|
|
38530
|
-
encoding: "utf8",
|
|
38531
|
-
timeout: 15000
|
|
38532
|
-
}).trim();
|
|
38851
|
+
const args = (await execFileAsync("ps", ["-p", String(pid), "-o", "args="], { encoding: "utf8", timeout: 15000 })).stdout.trim();
|
|
38533
38852
|
return args === "" ? null : { kind: "commandLine", text: args, ...when };
|
|
38534
38853
|
} catch {
|
|
38535
38854
|
return null;
|
|
38536
38855
|
}
|
|
38537
38856
|
}
|
|
38538
|
-
function listeningPid(port) {
|
|
38857
|
+
async function listeningPid(port) {
|
|
38539
38858
|
try {
|
|
38540
38859
|
if (process.platform === "win32") {
|
|
38541
|
-
|
|
38860
|
+
const { stdout } = await execFileAsync("netstat", ["-ano"], { encoding: "utf8", timeout: 15000 });
|
|
38861
|
+
return listeningPidInNetstat(stdout, port);
|
|
38542
38862
|
}
|
|
38543
38863
|
for (const [command, args, pattern] of [
|
|
38544
38864
|
["lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-t"], /^(\d+)/],
|
|
38545
38865
|
["ss", ["-ltnpH", `sport = :${port}`], /pid=(\d+)/]
|
|
38546
38866
|
]) {
|
|
38547
38867
|
try {
|
|
38548
|
-
const said =
|
|
38868
|
+
const said = (await execFileAsync(command, [...args], { encoding: "utf8", timeout: 15000 })).stdout.trim();
|
|
38549
38869
|
const found = pattern.exec(said);
|
|
38550
38870
|
if (found?.[1] !== undefined) {
|
|
38551
38871
|
return Number(found[1]);
|
|
@@ -38570,39 +38890,31 @@ function listeningPidInNetstat(printed, port) {
|
|
|
38570
38890
|
}
|
|
38571
38891
|
return null;
|
|
38572
38892
|
}
|
|
38573
|
-
function windowsCommandLine(pid) {
|
|
38893
|
+
async function windowsCommandLine(pid) {
|
|
38574
38894
|
try {
|
|
38575
|
-
const answer =
|
|
38576
|
-
"-NoProfile",
|
|
38577
|
-
"-NonInteractive",
|
|
38578
|
-
"-Command",
|
|
38579
|
-
`${POWERSHELL_UTF8}$p = Get-CimInstance Win32_Process -Filter "ProcessId=${pid}"; if ($p) { $p.CreationDate.ToUniversalTime().ToString("o"); $p.CommandLine }`
|
|
38580
|
-
], { encoding: "utf8", timeout: 15000, windowsHide: true }).trim();
|
|
38895
|
+
const answer = (await askWindows(`$p = Get-CimInstance Win32_Process -Filter "ProcessId=${pid}"; if ($p) { $p.CreationDate.ToUniversalTime().ToString("o"); $p.CommandLine }`, 15000)).trim();
|
|
38581
38896
|
return answer === "" ? null : answer;
|
|
38582
38897
|
} catch {
|
|
38583
38898
|
return null;
|
|
38584
38899
|
}
|
|
38585
38900
|
}
|
|
38586
|
-
function startedAt(pid, windowsAnswer) {
|
|
38901
|
+
async function startedAt(pid, windowsAnswer) {
|
|
38587
38902
|
if (process.platform === "win32") {
|
|
38588
38903
|
const when = Date.parse(windowsAnswer?.split(`
|
|
38589
38904
|
`)[0]?.trim() ?? "");
|
|
38590
38905
|
return Number.isNaN(when) ? null : when;
|
|
38591
38906
|
}
|
|
38592
38907
|
try {
|
|
38593
|
-
const said =
|
|
38594
|
-
encoding: "utf8",
|
|
38595
|
-
timeout: 15000
|
|
38596
|
-
}).trim();
|
|
38908
|
+
const said = (await execFileAsync("ps", ["-p", String(pid), "-o", "lstart="], { encoding: "utf8", timeout: 15000 })).stdout.trim();
|
|
38597
38909
|
const when = Date.parse(said);
|
|
38598
38910
|
return said === "" || Number.isNaN(when) ? null : when;
|
|
38599
38911
|
} catch {
|
|
38600
38912
|
return null;
|
|
38601
38913
|
}
|
|
38602
38914
|
}
|
|
38603
|
-
function windowsImage(pid) {
|
|
38915
|
+
async function windowsImage(pid) {
|
|
38604
38916
|
try {
|
|
38605
|
-
const csv =
|
|
38917
|
+
const { stdout: csv } = await execFileAsync("tasklist", ["/FI", `PID eq ${pid}`, "/FO", "CSV", "/NH"], {
|
|
38606
38918
|
encoding: "utf8",
|
|
38607
38919
|
timeout: 15000,
|
|
38608
38920
|
windowsHide: true
|
|
@@ -38612,11 +38924,11 @@ function windowsImage(pid) {
|
|
|
38612
38924
|
return null;
|
|
38613
38925
|
}
|
|
38614
38926
|
}
|
|
38615
|
-
function stillTheRecordedRun(record) {
|
|
38616
|
-
return judgeRun(record, runningAs(record.pid), "confirmed");
|
|
38927
|
+
async function stillTheRecordedRun(record) {
|
|
38928
|
+
return judgeRun(record, await runningAs(record.pid), "confirmed");
|
|
38617
38929
|
}
|
|
38618
|
-
function couldStillBeTheRecordedRun(record) {
|
|
38619
|
-
return judgeRun(record, runningAs(record.pid), "possible");
|
|
38930
|
+
async function couldStillBeTheRecordedRun(record) {
|
|
38931
|
+
return judgeRun(record, await runningAs(record.pid), "possible");
|
|
38620
38932
|
}
|
|
38621
38933
|
function judgeRun(record, running, needed) {
|
|
38622
38934
|
if (running === null) {
|
|
@@ -38643,8 +38955,8 @@ function judgeRun(record, running, needed) {
|
|
|
38643
38955
|
const project = process.platform === "win32" ? record.projectPath.toLowerCase() : record.projectPath;
|
|
38644
38956
|
return said.includes(project);
|
|
38645
38957
|
}
|
|
38646
|
-
function stillTheAnnouncedGame(pid, projectPath, announcedAt) {
|
|
38647
|
-
return judgeAnnouncedGame(runningAs(pid), projectPath, announcedAt);
|
|
38958
|
+
async function stillTheAnnouncedGame(pid, projectPath, announcedAt) {
|
|
38959
|
+
return judgeAnnouncedGame(await runningAs(pid), projectPath, announcedAt);
|
|
38648
38960
|
}
|
|
38649
38961
|
function judgeAnnouncedGame(running, projectPath, announcedAt) {
|
|
38650
38962
|
if (running?.kind !== "commandLine" || running.startedAt === undefined) {
|
|
@@ -38661,7 +38973,7 @@ function judgeAnnouncedGame(running, projectPath, announcedAt) {
|
|
|
38661
38973
|
}
|
|
38662
38974
|
function clearRunRecord(projectPath) {
|
|
38663
38975
|
for (const directory of runtimeDirectories()) {
|
|
38664
|
-
const shared =
|
|
38976
|
+
const shared = join21(directory, "runs", SHARED_NOTE);
|
|
38665
38977
|
const sharedRecord = recordAt(shared);
|
|
38666
38978
|
const ours = sharedRecord !== null && sharedRecord.projectPath !== "" && isSameDirectory(sharedRecord.projectPath, projectPath);
|
|
38667
38979
|
for (const path of [runRecordPath(projectPath, directory), ...ours ? [shared] : []]) {
|
|
@@ -38683,7 +38995,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
38683
38995
|
const kept = new Set(everyRunRecord().map((record) => record.transcript));
|
|
38684
38996
|
let entries;
|
|
38685
38997
|
try {
|
|
38686
|
-
entries =
|
|
38998
|
+
entries = readdirSync8(directory);
|
|
38687
38999
|
} catch {
|
|
38688
39000
|
return;
|
|
38689
39001
|
}
|
|
@@ -38691,7 +39003,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
38691
39003
|
if (!entry.startsWith("run-") || !entry.endsWith(".log")) {
|
|
38692
39004
|
continue;
|
|
38693
39005
|
}
|
|
38694
|
-
const path =
|
|
39006
|
+
const path = join21(directory, entry);
|
|
38695
39007
|
if (kept.has(path) || ageMs(path, now) < keepMs) {
|
|
38696
39008
|
continue;
|
|
38697
39009
|
}
|
|
@@ -38700,37 +39012,37 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
38700
39012
|
} catch {}
|
|
38701
39013
|
}
|
|
38702
39014
|
}
|
|
38703
|
-
var SHARED_NOTE = "run.json", SAME_RUN_WINDOW_MS = 1e4;
|
|
39015
|
+
var execFileAsync, SHARED_NOTE = "run.json", SAME_RUN_WINDOW_MS = 1e4;
|
|
38704
39016
|
var init_run_record = __esm(() => {
|
|
38705
39017
|
init_paths();
|
|
38706
39018
|
init_process_children();
|
|
38707
39019
|
init_runtime_client();
|
|
39020
|
+
init_windows_ask();
|
|
39021
|
+
execFileAsync = promisify5(execFile5);
|
|
38708
39022
|
});
|
|
38709
39023
|
|
|
38710
39024
|
// src/server.ts
|
|
38711
|
-
import { execFile as
|
|
39025
|
+
import { execFile as execFile6, spawn as spawn3 } from "node:child_process";
|
|
38712
39026
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
38713
39027
|
import {
|
|
38714
39028
|
appendFileSync,
|
|
38715
39029
|
closeSync as closeSync2,
|
|
38716
39030
|
constants,
|
|
38717
39031
|
copyFileSync,
|
|
38718
|
-
existsSync as
|
|
39032
|
+
existsSync as existsSync15,
|
|
38719
39033
|
fstatSync,
|
|
38720
39034
|
mkdirSync as mkdirSync12,
|
|
38721
|
-
mkdtempSync as mkdtempSync2,
|
|
38722
39035
|
openSync as openSync3,
|
|
38723
|
-
readdirSync as
|
|
39036
|
+
readdirSync as readdirSync9,
|
|
38724
39037
|
readFileSync as readFileSync19,
|
|
38725
39038
|
readSync as readSync2,
|
|
38726
39039
|
rmdirSync as rmdirSync2,
|
|
38727
39040
|
statSync as statSync8
|
|
38728
39041
|
} from "node:fs";
|
|
38729
|
-
import {
|
|
38730
|
-
import { basename as basename5, dirname as dirname15, extname as extname2, isAbsolute as isAbsolute3, join as join21, normalize as normalize2, relative as relative3 } from "node:path";
|
|
39042
|
+
import { basename as basename5, dirname as dirname15, extname as extname2, isAbsolute as isAbsolute3, join as join22, normalize as normalize2, relative as relative3 } from "node:path";
|
|
38731
39043
|
import { setTimeout as delay3 } from "node:timers/promises";
|
|
38732
39044
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
38733
|
-
import { promisify as
|
|
39045
|
+
import { promisify as promisify6 } from "node:util";
|
|
38734
39046
|
function captureDestinationRefusal(outputPath, projects) {
|
|
38735
39047
|
if (/^(res|user):\/\//.test(outputPath)) {
|
|
38736
39048
|
return `outputPath ${outputPath} is a Godot path; a capture is saved outside the project, at an absolute path on this machine.`;
|
|
@@ -38742,15 +39054,15 @@ function captureDestinationRefusal(outputPath, projects) {
|
|
|
38742
39054
|
return `outputPath ${outputPath} does not end in .png, which is what a capture is.`;
|
|
38743
39055
|
}
|
|
38744
39056
|
const directory = realPathOr(dirname15(outputPath));
|
|
38745
|
-
if (!
|
|
39057
|
+
if (!existsSync15(directory)) {
|
|
38746
39058
|
return `outputPath's directory ${dirname15(outputPath)} does not exist; a capture creates no directories.`;
|
|
38747
39059
|
}
|
|
38748
|
-
const where =
|
|
39060
|
+
const where = join22(directory, basename5(outputPath));
|
|
38749
39061
|
const inside = projects.find((project) => project !== "" && isWithinRoot(realPathOr(project), where));
|
|
38750
39062
|
if (inside !== undefined) {
|
|
38751
39063
|
return `outputPath ${outputPath} is inside the project at ${inside}; a capture is saved outside it, so it never becomes part of the game.`;
|
|
38752
39064
|
}
|
|
38753
|
-
if (
|
|
39065
|
+
if (existsSync15(where)) {
|
|
38754
39066
|
return `outputPath ${outputPath} already exists; a capture is not written over a file. Name a new one.`;
|
|
38755
39067
|
}
|
|
38756
39068
|
return null;
|
|
@@ -38906,7 +39218,8 @@ function aboutTheChildren(ended, throughEditor) {
|
|
|
38906
39218
|
return throughEditor ? `Anything that game started for itself, with OS.create_process or otherwise, is not the editor's to stop and was not signalled; ${reaches}` : `Anything that game started for itself, with OS.create_process or otherwise, is a separate process this stop did not signal; ${reaches}`;
|
|
38907
39219
|
}
|
|
38908
39220
|
if (ended.unknown === "platform") {
|
|
38909
|
-
|
|
39221
|
+
const why = whyTheProcessTreeFailed();
|
|
39222
|
+
return `andChildren was asked for and this platform would not list what the game had started, so nothing else was ended.${why === undefined ? "" : ` The listing failed: ${why}.`}`;
|
|
38910
39223
|
}
|
|
38911
39224
|
if (ended.unknown === "no process") {
|
|
38912
39225
|
return "andChildren was asked for and this run has no process id to list under: the editor plays it and its game announced none, so nothing else was ended.";
|
|
@@ -38987,17 +39300,6 @@ function runtimeVerdict(endpoint, after) {
|
|
|
38987
39300
|
heldAt: null
|
|
38988
39301
|
};
|
|
38989
39302
|
}
|
|
38990
|
-
function alive(pid) {
|
|
38991
|
-
if (pid === undefined || pid === null) {
|
|
38992
|
-
return false;
|
|
38993
|
-
}
|
|
38994
|
-
try {
|
|
38995
|
-
process.kill(pid, 0);
|
|
38996
|
-
return true;
|
|
38997
|
-
} catch (error) {
|
|
38998
|
-
return error.code === "EPERM";
|
|
38999
|
-
}
|
|
39000
|
-
}
|
|
39001
39303
|
function isOfType(value, kind) {
|
|
39002
39304
|
switch (kind) {
|
|
39003
39305
|
case "string":
|
|
@@ -39189,13 +39491,13 @@ function aboveTheRunner(entry) {
|
|
|
39189
39491
|
}
|
|
39190
39492
|
function elsewhereIn(projectPath, asked) {
|
|
39191
39493
|
const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
|
|
39192
|
-
if (wanted === "" ||
|
|
39494
|
+
if (wanted === "" || existsSync15(join22(projectPath, wanted))) {
|
|
39193
39495
|
return "";
|
|
39194
39496
|
}
|
|
39195
39497
|
const root = (wanted.split("/")[0] ?? "").toLowerCase();
|
|
39196
39498
|
let near = [];
|
|
39197
39499
|
try {
|
|
39198
|
-
near =
|
|
39500
|
+
near = readdirSync9(projectPath, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.toLowerCase() !== root).map((entry) => entry.name).filter((name) => name.toLowerCase().startsWith("test") || root.startsWith(name.toLowerCase()));
|
|
39199
39501
|
} catch {
|
|
39200
39502
|
return "";
|
|
39201
39503
|
}
|
|
@@ -39211,7 +39513,7 @@ function runtimeAddonAnnouncesEditors(projectPath) {
|
|
|
39211
39513
|
return false;
|
|
39212
39514
|
}
|
|
39213
39515
|
try {
|
|
39214
|
-
return readFileSync19(
|
|
39516
|
+
return readFileSync19(join22(projectPath, RUNTIME_AUTOLOAD.path), "utf8").includes('"editor_pid"');
|
|
39215
39517
|
} catch {
|
|
39216
39518
|
return false;
|
|
39217
39519
|
}
|
|
@@ -39236,7 +39538,7 @@ function needsItsOwnProcess(asked) {
|
|
|
39236
39538
|
class GodotServer {
|
|
39237
39539
|
mcp;
|
|
39238
39540
|
locator = new GodotLocator;
|
|
39239
|
-
operationsScript =
|
|
39541
|
+
operationsScript = join22(__dirname2, "godot", "operations", "godot_operations.gd");
|
|
39240
39542
|
godotBridge;
|
|
39241
39543
|
tools = buildToolDefinitions();
|
|
39242
39544
|
activeProcess = null;
|
|
@@ -39248,6 +39550,7 @@ class GodotServer {
|
|
|
39248
39550
|
lastGodotVersion = null;
|
|
39249
39551
|
lspClient = null;
|
|
39250
39552
|
dapClient = null;
|
|
39553
|
+
adapterPassed = null;
|
|
39251
39554
|
breakpointsSeededOn = null;
|
|
39252
39555
|
bridgeStartupError = null;
|
|
39253
39556
|
bridgeRetry = null;
|
|
@@ -39306,6 +39609,7 @@ class GodotServer {
|
|
|
39306
39609
|
this.handleShutdown("stdin:end", 0);
|
|
39307
39610
|
});
|
|
39308
39611
|
console.error("gdharness running on stdio");
|
|
39612
|
+
sweepAbandonedScratch();
|
|
39309
39613
|
try {
|
|
39310
39614
|
await this.takeOverFromAPredecessor();
|
|
39311
39615
|
await this.godotBridge.start();
|
|
@@ -39632,7 +39936,7 @@ class GodotServer {
|
|
|
39632
39936
|
this.callsSinceFeedback = 0;
|
|
39633
39937
|
return {
|
|
39634
39938
|
...answer,
|
|
39635
|
-
content: [...answer.content, { type: "text", text: feedbackNotice() }]
|
|
39939
|
+
content: [...answer.content, { type: "text", text: feedbackNotice(FEEDBACK_NOTICE_EVERY) }]
|
|
39636
39940
|
};
|
|
39637
39941
|
}
|
|
39638
39942
|
validateArguments(spec, args) {
|
|
@@ -39976,8 +40280,8 @@ class GodotServer {
|
|
|
39976
40280
|
if (path === undefined) {
|
|
39977
40281
|
return { ok: false, response: this.createErrorResponse("projectPath is required.") };
|
|
39978
40282
|
}
|
|
39979
|
-
const file =
|
|
39980
|
-
if (!
|
|
40283
|
+
const file = join22(path, "project.godot");
|
|
40284
|
+
if (!existsSync15(file)) {
|
|
39981
40285
|
return {
|
|
39982
40286
|
ok: false,
|
|
39983
40287
|
response: this.createErrorResponse(`Not a Godot project: ${path}`, [
|
|
@@ -40138,13 +40442,13 @@ class GodotServer {
|
|
|
40138
40442
|
let sawAScan = false;
|
|
40139
40443
|
let scanEndedAt = null;
|
|
40140
40444
|
for (;; ) {
|
|
40141
|
-
const asked = Date.now();
|
|
40142
40445
|
let status;
|
|
40143
40446
|
try {
|
|
40144
40447
|
status = asParams(await this.godotBridge.invokeTool("scan_status", {}));
|
|
40145
40448
|
} catch {
|
|
40146
40449
|
return { waitedMs: Date.now() - started, stillScanning: false };
|
|
40147
40450
|
}
|
|
40451
|
+
const answered = Date.now();
|
|
40148
40452
|
if (status["scanning"] === true || status["importing"] === true || status["pending"] === true) {
|
|
40149
40453
|
sawAScan = true;
|
|
40150
40454
|
if (Date.now() - started >= SCAN_WAIT_MS) {
|
|
@@ -40154,7 +40458,7 @@ class GodotServer {
|
|
|
40154
40458
|
continue;
|
|
40155
40459
|
}
|
|
40156
40460
|
const since = readNumber(status, "sinceScanFinishedMs");
|
|
40157
|
-
scanEndedAt = since !== undefined && since >= 0 ?
|
|
40461
|
+
scanEndedAt = since !== undefined && since >= 0 ? answered - since : sawAScan ? answered : null;
|
|
40158
40462
|
break;
|
|
40159
40463
|
}
|
|
40160
40464
|
let waitedForTheWrite = false;
|
|
@@ -40573,11 +40877,11 @@ class GodotServer {
|
|
|
40573
40877
|
return this.createErrorResponse(`Cannot create ${dirname15(output.relativePath)} to export into: ${errorMessage(error)}`);
|
|
40574
40878
|
}
|
|
40575
40879
|
const debug = readBoolean(args, "debug") ?? false;
|
|
40576
|
-
const exportLogs =
|
|
40880
|
+
const exportLogs = scratchDirectory("export");
|
|
40577
40881
|
const exportArgs = [
|
|
40578
40882
|
"--headless",
|
|
40579
40883
|
"--log-file",
|
|
40580
|
-
|
|
40884
|
+
join22(exportLogs, "engine.log"),
|
|
40581
40885
|
"--path",
|
|
40582
40886
|
project.value.path,
|
|
40583
40887
|
debug ? "--export-debug" : "--export-release",
|
|
@@ -40609,7 +40913,7 @@ class GodotServer {
|
|
|
40609
40913
|
log.finish();
|
|
40610
40914
|
const problems = log.select({ severity: "warning", sinceLastCall: false, limit: 200 });
|
|
40611
40915
|
const verdict = {
|
|
40612
|
-
exported: ending.exitCode === 0 && log.count("error") === 0 &&
|
|
40916
|
+
exported: ending.exitCode === 0 && log.count("error") === 0 && existsSync15(output.absolutePath),
|
|
40613
40917
|
preset,
|
|
40614
40918
|
outputPath: output.relativePath,
|
|
40615
40919
|
debug,
|
|
@@ -40645,7 +40949,7 @@ class GodotServer {
|
|
|
40645
40949
|
return contained.response;
|
|
40646
40950
|
}
|
|
40647
40951
|
const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
|
|
40648
|
-
if (!
|
|
40952
|
+
if (!existsSync15(join22(project.value.path, runner))) {
|
|
40649
40953
|
return this.createErrorResponse(`gdUnit4 is not installed in this project: no ${runner}.`, [
|
|
40650
40954
|
"Install gdUnit4 under addons/gdUnit4, from https://github.com/godot-gdunit-labs/gdUnit4"
|
|
40651
40955
|
]);
|
|
@@ -40681,7 +40985,7 @@ class GodotServer {
|
|
|
40681
40985
|
];
|
|
40682
40986
|
const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 600000;
|
|
40683
40987
|
this.logDebug(`Running tests: ${engine.value} ${cmdArgs.join(" ")}`);
|
|
40684
|
-
const userData =
|
|
40988
|
+
const userData = scratchDirectory("tests");
|
|
40685
40989
|
const run = this.spawnGame(engine.value, cmdArgs, userDataIn(userData));
|
|
40686
40990
|
const console2 = [];
|
|
40687
40991
|
let lastSaid = Date.now();
|
|
@@ -40708,14 +41012,17 @@ class GodotServer {
|
|
|
40708
41012
|
resolve(false);
|
|
40709
41013
|
});
|
|
40710
41014
|
});
|
|
40711
|
-
|
|
41015
|
+
if (timedOut && run.process.pid !== undefined) {
|
|
41016
|
+
await untilGone([run.process.pid]);
|
|
41017
|
+
}
|
|
41018
|
+
const reportsDir = join22(project.value.path, ".godot", ...ours.split("/"));
|
|
40712
41019
|
let report = null;
|
|
40713
41020
|
let reportProblem = null;
|
|
40714
41021
|
try {
|
|
40715
|
-
const written =
|
|
41022
|
+
const written = existsSync15(reportsDir) ? readdirSync9(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
|
|
40716
41023
|
const newest = written.at(-1);
|
|
40717
41024
|
if (newest !== undefined) {
|
|
40718
|
-
report = parseJUnit(readFileSync19(
|
|
41025
|
+
report = parseJUnit(readFileSync19(join22(reportsDir, newest, "results.xml"), "utf8"));
|
|
40719
41026
|
}
|
|
40720
41027
|
} catch (error) {
|
|
40721
41028
|
reportProblem = errorMessage(error);
|
|
@@ -40873,7 +41180,7 @@ class GodotServer {
|
|
|
40873
41180
|
const cached = cachedClasses(projectPath);
|
|
40874
41181
|
const contradicted = cached === null ? [] : contradictedDiagnostics(messages, cached, (resourcePath) => {
|
|
40875
41182
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
40876
|
-
if (!contained.ok || !
|
|
41183
|
+
if (!contained.ok || !existsSync15(contained.absolutePath)) {
|
|
40877
41184
|
return null;
|
|
40878
41185
|
}
|
|
40879
41186
|
try {
|
|
@@ -40927,7 +41234,7 @@ class GodotServer {
|
|
|
40927
41234
|
}
|
|
40928
41235
|
sourceOfClass(projectPath, resourcePath) {
|
|
40929
41236
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
40930
|
-
if (!contained.ok || !
|
|
41237
|
+
if (!contained.ok || !existsSync15(contained.absolutePath)) {
|
|
40931
41238
|
return null;
|
|
40932
41239
|
}
|
|
40933
41240
|
try {
|
|
@@ -40948,28 +41255,32 @@ class GodotServer {
|
|
|
40948
41255
|
async handleDAP(toolName, args) {
|
|
40949
41256
|
return handleDAPTool(this.dap(), toolName, args);
|
|
40950
41257
|
}
|
|
40951
|
-
adapterBelongsElsewhere() {
|
|
41258
|
+
async adapterBelongsElsewhere() {
|
|
40952
41259
|
const status = this.godotBridge.getStatus();
|
|
40953
41260
|
const ours = status.editorPid;
|
|
40954
41261
|
if (ours === undefined) {
|
|
40955
41262
|
return null;
|
|
40956
41263
|
}
|
|
41264
|
+
const connection = this.dapClient?.connection() ?? null;
|
|
41265
|
+
if (connection !== null && this.adapterPassed?.connection === connection && this.adapterPassed.editorPid === ours) {
|
|
41266
|
+
return null;
|
|
41267
|
+
}
|
|
40957
41268
|
const port = this.editorServes("dapPort", "GDHARNESS_DAP_PORT", DEFAULT_DAP_PORT);
|
|
40958
|
-
const
|
|
41269
|
+
const askedAt = Date.now();
|
|
41270
|
+
const holder = await listeningPid(port);
|
|
41271
|
+
this.logDebug(`Asked who holds debug adapter port ${port} in ${Date.now() - askedAt}ms: ${holder ?? "not said"}`);
|
|
40959
41272
|
if (holder === null || holder === ours) {
|
|
41273
|
+
this.adapterPassed = connection === null ? null : { connection, editorPid: ours };
|
|
40960
41274
|
return null;
|
|
40961
41275
|
}
|
|
40962
41276
|
return `The debug adapter on port ${port} belongs to process ${holder}, and the editor on this server's bridge is process ${ours}. Godot gives every editor the same debug adapter port by default, so the one this editor names in its settings is not necessarily the one it holds. Playing the game now would put another editor's console and another project's debugger under this project's name.`;
|
|
40963
41277
|
}
|
|
40964
|
-
|
|
40965
|
-
return anEditorIsStillComing(listeningSince, this.launchedEditorIsUp());
|
|
40966
|
-
}
|
|
40967
|
-
launchedEditorIsUp() {
|
|
41278
|
+
async launchedEditorIsUp() {
|
|
40968
41279
|
const launched = this.launchedEditor;
|
|
40969
41280
|
if (launched === null || !alive(launched.pid)) {
|
|
40970
41281
|
return false;
|
|
40971
41282
|
}
|
|
40972
|
-
const now = startTimesOf([launched.pid]).get(launched.pid);
|
|
41283
|
+
const now = (await startTimesOf([launched.pid])).get(launched.pid);
|
|
40973
41284
|
return launched.startedAt === null || now === undefined || now === launched.startedAt;
|
|
40974
41285
|
}
|
|
40975
41286
|
dap() {
|
|
@@ -41058,8 +41369,9 @@ class GodotServer {
|
|
|
41058
41369
|
}
|
|
41059
41370
|
return owed;
|
|
41060
41371
|
}
|
|
41061
|
-
getEditorStatusPayload() {
|
|
41372
|
+
async getEditorStatusPayload() {
|
|
41062
41373
|
const status = this.godotBridge.getStatus();
|
|
41374
|
+
const launchedIsUp = !status.connected && await this.launchedEditorIsUp();
|
|
41063
41375
|
const isPortConflict = this.bridgeStartupError?.includes("EADDRINUSE") ?? false;
|
|
41064
41376
|
const stale = status.connected && editorIsStale(status.addonVersion, SERVER_VERSION, status.addonDigest, shippedEditorDigest());
|
|
41065
41377
|
const unfinished = this.restartLeftUnfinished(status.connected);
|
|
@@ -41068,14 +41380,14 @@ class GodotServer {
|
|
|
41068
41380
|
serverVersion: SERVER_VERSION,
|
|
41069
41381
|
addonIsStale: status.connected ? stale : undefined,
|
|
41070
41382
|
bridgeAvailable: this.bridgeStartupError === null,
|
|
41071
|
-
mayYetConnect: status.connected ? undefined : unfinished !== null ? false :
|
|
41383
|
+
mayYetConnect: status.connected ? undefined : unfinished !== null ? false : anEditorIsStillComing(status.listeningSince, launchedIsUp),
|
|
41072
41384
|
restartInterrupted: unfinished === null ? undefined : {
|
|
41073
41385
|
quitEditorPid: unfinished.editorPid,
|
|
41074
41386
|
quitAt: unfinished.quitAt,
|
|
41075
41387
|
byServerPid: unfinished.byPid,
|
|
41076
41388
|
note: "A restart begun by a previous gdharness server asked this project's editor to quit, and that server was ended before it could start the editor again, so nothing is coming. editor_launch open starts one, and clears this."
|
|
41077
41389
|
},
|
|
41078
|
-
awaitingLaunchedEditor: status.connected || !
|
|
41390
|
+
awaitingLaunchedEditor: status.connected || !launchedIsUp ? undefined : this.launchedEditor?.pid ?? undefined,
|
|
41079
41391
|
...status.connected ? this.whatTheLaunchedEditorDropped() : {},
|
|
41080
41392
|
startupError: this.bridgeStartupError,
|
|
41081
41393
|
portNote: status.portWanted === undefined ? undefined : `Port ${status.portWanted}, the one this server was configured with, was held ${this.notHandedOverBy === null ? "by another process" : `by pid ${this.notHandedOverBy}, the gdharness server this one replaced, which had not let go of it ${HANDOVER_MS / 1000} seconds after being told it was replaced`} when the bridge started, so it took ${status.port} and announced that where the editor looks for it${this.announcedAt === null ? "" : `, ${this.announcedAt}`}. The editor finds it there; the next server started takes ${status.portWanted} again if it is free.`,
|
|
@@ -41164,7 +41476,7 @@ class GodotServer {
|
|
|
41164
41476
|
const asked = (async () => {
|
|
41165
41477
|
const playing = await this.editorPlayingState();
|
|
41166
41478
|
await this.pickUpWhatTheEditorIsPlaying();
|
|
41167
|
-
const current = this.currentRun();
|
|
41479
|
+
const current = await this.currentRun();
|
|
41168
41480
|
const active = current === null ? false : await this.runStillGoing(current, playing?.playing ?? null);
|
|
41169
41481
|
return {
|
|
41170
41482
|
playing,
|
|
@@ -41172,14 +41484,15 @@ class GodotServer {
|
|
|
41172
41484
|
hold: current === null ? {} : await this.holdOf(current, active)
|
|
41173
41485
|
};
|
|
41174
41486
|
})();
|
|
41175
|
-
const [games, { playing, active, hold }, scan] = await Promise.all([
|
|
41487
|
+
const [games, { playing, active, hold }, scan, editorStatus] = await Promise.all([
|
|
41176
41488
|
pinged,
|
|
41177
41489
|
asked,
|
|
41178
|
-
this.editorScanState()
|
|
41490
|
+
this.editorScanState(),
|
|
41491
|
+
this.getEditorStatusPayload()
|
|
41179
41492
|
]);
|
|
41180
41493
|
return this.jsonTextResponse({
|
|
41181
41494
|
editor: {
|
|
41182
|
-
...
|
|
41495
|
+
...editorStatus,
|
|
41183
41496
|
debugPort: playing?.debugPort ?? this.godotBridge.getStatus().debugPort,
|
|
41184
41497
|
scan
|
|
41185
41498
|
},
|
|
@@ -41209,7 +41522,7 @@ class GodotServer {
|
|
|
41209
41522
|
return null;
|
|
41210
41523
|
}
|
|
41211
41524
|
try {
|
|
41212
|
-
return settingKeys(readFileSync19(
|
|
41525
|
+
return settingKeys(readFileSync19(join22(projectPath, "project.godot"), "utf8"));
|
|
41213
41526
|
} catch {
|
|
41214
41527
|
return null;
|
|
41215
41528
|
}
|
|
@@ -41358,7 +41671,7 @@ class GodotServer {
|
|
|
41358
41671
|
writeEditorLogNote(projectPath, launched.pid);
|
|
41359
41672
|
this.launchedEditor = {
|
|
41360
41673
|
pid: launched.pid,
|
|
41361
|
-
startedAt: startTimesOf([launched.pid]).get(launched.pid) ?? null
|
|
41674
|
+
startedAt: (await startTimesOf([launched.pid])).get(launched.pid) ?? null
|
|
41362
41675
|
};
|
|
41363
41676
|
return { pid: launched.pid, error: null };
|
|
41364
41677
|
}
|
|
@@ -41406,13 +41719,15 @@ class GodotServer {
|
|
|
41406
41719
|
}
|
|
41407
41720
|
await this.pickUpWhatTheEditorIsPlaying();
|
|
41408
41721
|
let ended = null;
|
|
41409
|
-
const before = this.currentRun();
|
|
41722
|
+
const before = await this.currentRun();
|
|
41410
41723
|
if (before !== null && await this.runStillGoing(before)) {
|
|
41411
41724
|
this.logDebug("Ending the running game before starting another");
|
|
41725
|
+
const endingFrom = Date.now();
|
|
41412
41726
|
ended = {
|
|
41413
41727
|
pid: before.pid ?? this.announcedPidOf(before) ?? null,
|
|
41414
41728
|
ending: await this.endActiveGame("editor_run start, which ends the run that was going before it starts another", true)
|
|
41415
41729
|
};
|
|
41730
|
+
this.logDebug(`The running game was ${ended.ending} after ${Date.now() - endingFrom}ms`);
|
|
41416
41731
|
}
|
|
41417
41732
|
const alreadyPlaying = new Set(discoverRuntimes().map((endpoint) => endpoint.pid));
|
|
41418
41733
|
const wereHereFirst = this.everyRuntimeOfOurs();
|
|
@@ -41422,14 +41737,16 @@ class GodotServer {
|
|
|
41422
41737
|
});
|
|
41423
41738
|
const runtimeWaitMs = this.announceBudget(args, project.value.path);
|
|
41424
41739
|
const editorWouldPlay = !headless || editorPlaysHeadless(project.value.file);
|
|
41425
|
-
|
|
41740
|
+
const silent = resolveSilent(args["silent"], process.env) && !headless;
|
|
41741
|
+
if (this.godotBridge.isConnected() && editorWouldPlay && given.value.length === 0 && !needsItsOwnProcess(asked.value) && !silent) {
|
|
41426
41742
|
return await this.playThroughEditor(sceneArgument, refreshed.value, scanned, project.value.path, alreadyPlaying, runtimeWaitMs, ended);
|
|
41427
41743
|
}
|
|
41428
41744
|
const cmdArgs = runArguments({
|
|
41429
41745
|
projectPath: project.value.path,
|
|
41430
41746
|
headless,
|
|
41431
41747
|
scene: sceneArgument,
|
|
41432
|
-
userArgs: given.value
|
|
41748
|
+
userArgs: given.value,
|
|
41749
|
+
silent
|
|
41433
41750
|
});
|
|
41434
41751
|
this.logDebug(`Running Godot project: ${engine.value} ${cmdArgs.join(" ")}`);
|
|
41435
41752
|
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);
|
|
@@ -41440,7 +41757,7 @@ class GodotServer {
|
|
|
41440
41757
|
}
|
|
41441
41758
|
started.announcedBefore = alreadyPlaying;
|
|
41442
41759
|
this.activeProcess = started;
|
|
41443
|
-
const spawnedInstead = this.godotBridge.isConnected() && editorWouldPlay && given.value.length > 0 ? " The editor cannot be handed arguments for a game it plays, so this one was started " + "here: the debug_* tools answer only for a game the editor is playing." : this.godotBridge.isConnected() && editorWouldPlay && needsItsOwnProcess(asked.value) ? " The editor plays a game with its own environment, so a run given one was started " + "here: the debug_* tools answer only for a game the editor is playing." : "";
|
|
41760
|
+
const spawnedInstead = this.godotBridge.isConnected() && editorWouldPlay && given.value.length > 0 ? " The editor cannot be handed arguments for a game it plays, so this one was started " + "here: the debug_* tools answer only for a game the editor is playing." : this.godotBridge.isConnected() && editorWouldPlay && needsItsOwnProcess(asked.value) ? " The editor plays a game with its own environment, so a run given one was started " + "here: the debug_* tools answer only for a game the editor is playing." : 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"}: the debug_* tools answer only for a game the editor is playing, and silent: false plays it through the editor.` : "";
|
|
41444
41761
|
const endedForThis = endedToStartThis(ended);
|
|
41445
41762
|
const stranded = wereHereFirst.filter((one) => one.pid !== ended?.pid && alive(one.pid));
|
|
41446
41763
|
const alsoRunning = leftRunningNote(stranded);
|
|
@@ -41462,7 +41779,7 @@ class GodotServer {
|
|
|
41462
41779
|
}
|
|
41463
41780
|
async runtimeUp(projectPath, before, budget, withArgs) {
|
|
41464
41781
|
const budgetMs = budget.ms;
|
|
41465
|
-
if (!
|
|
41782
|
+
if (!existsSync15(join22(projectPath, RUNTIME_AUTOLOAD.path))) {
|
|
41466
41783
|
return runtimeVerdict(null, { addon: false, budgetMs, heldAt: null, running: false, withArgs });
|
|
41467
41784
|
}
|
|
41468
41785
|
let asking = null;
|
|
@@ -41473,12 +41790,12 @@ class GodotServer {
|
|
|
41473
41790
|
const lastSaid = () => editorSaysGoing ? "playing" : "not playing";
|
|
41474
41791
|
const endpoint = await announcedSince(projectPath, before, {
|
|
41475
41792
|
budgetMs,
|
|
41476
|
-
accept: (one) => this.isTheGameOf(this.
|
|
41793
|
+
accept: (one) => this.isTheGameOf(this.activeProcess, one, projectPath),
|
|
41477
41794
|
giveUp: () => {
|
|
41478
41795
|
if (this.dapClient?.isStopped() === true) {
|
|
41479
41796
|
return true;
|
|
41480
41797
|
}
|
|
41481
|
-
const run = this.
|
|
41798
|
+
const run = this.activeProcess;
|
|
41482
41799
|
if (run === null || !run.throughEditor) {
|
|
41483
41800
|
return !stillRunning(run);
|
|
41484
41801
|
}
|
|
@@ -41496,7 +41813,7 @@ class GodotServer {
|
|
|
41496
41813
|
});
|
|
41497
41814
|
const asked = editorAsks === 0 ? "editor not asked" : `editor asked ${editorAsks === 1 ? "once" : `${editorAsks} times`}, last said ${lastSaid()}`;
|
|
41498
41815
|
this.logDebug(`announce wait ended after ${Date.now() - waitBegan}ms: ${endpoint === null ? "nothing announced" : `pid ${endpoint.pid} announced`}, ${asked}`);
|
|
41499
|
-
const going = this.currentRun();
|
|
41816
|
+
const going = await this.currentRun();
|
|
41500
41817
|
if (endpoint !== null && going !== null && going.announcedPid === undefined) {
|
|
41501
41818
|
this.tie(going, endpoint);
|
|
41502
41819
|
}
|
|
@@ -41537,8 +41854,10 @@ class GodotServer {
|
|
|
41537
41854
|
}
|
|
41538
41855
|
async playThroughEditor(scene, refreshedClasses, scanned, projectPath, alreadyPlaying, runtimeWaitMs, ended) {
|
|
41539
41856
|
const log = new GameLog;
|
|
41857
|
+
const connectingFrom = Date.now();
|
|
41540
41858
|
try {
|
|
41541
41859
|
await this.dap().connect();
|
|
41860
|
+
this.logDebug(`The debug adapter was connected after ${Date.now() - connectingFrom}ms`);
|
|
41542
41861
|
this.dapClient?.getOutput(true);
|
|
41543
41862
|
} catch (error) {
|
|
41544
41863
|
return this.createErrorResponse(`The editor is connected but its debug adapter is not: ${errorMessage(error)}`, [
|
|
@@ -41546,7 +41865,7 @@ class GodotServer {
|
|
|
41546
41865
|
"GDHARNESS_DAP_PORT points this server at another one"
|
|
41547
41866
|
]);
|
|
41548
41867
|
}
|
|
41549
|
-
const stranger = this.adapterBelongsElsewhere();
|
|
41868
|
+
const stranger = await this.adapterBelongsElsewhere();
|
|
41550
41869
|
if (stranger !== null) {
|
|
41551
41870
|
return this.createErrorResponse(stranger, [
|
|
41552
41871
|
"editor_status names the editor on the bridge and the port it says it serves",
|
|
@@ -41554,7 +41873,9 @@ class GodotServer {
|
|
|
41554
41873
|
"Closing the other editor, or moving its debug adapter port in its editor settings, frees this one"
|
|
41555
41874
|
]);
|
|
41556
41875
|
}
|
|
41876
|
+
const sendingFrom = Date.now();
|
|
41557
41877
|
const breakpoints = await this.dapHoldingBreakpointsOf(projectPath).reapplyBreakpoints();
|
|
41878
|
+
this.logDebug(`Breakpoints were sent again after ${Date.now() - sendingFrom}ms`);
|
|
41558
41879
|
const answer = await this.handleViaBridge("play_scene", scene === null ? {} : { scenePath: `res://${scene}` });
|
|
41559
41880
|
if (answer.isError === true) {
|
|
41560
41881
|
return answer;
|
|
@@ -41740,13 +42061,13 @@ class GodotServer {
|
|
|
41740
42061
|
arguments: [],
|
|
41741
42062
|
...running.command === undefined ? {} : { command: running.command }
|
|
41742
42063
|
};
|
|
41743
|
-
if (!stillTheRecordedRun(startedAs)) {
|
|
42064
|
+
if (!await stillTheRecordedRun(startedAs)) {
|
|
41744
42065
|
clearTheRecord();
|
|
41745
42066
|
running.endedHere = null;
|
|
41746
42067
|
running.log.record("warning", `This run was not ended here: pid ${running.pid} no longer answers as the process the run was started as, so nothing was signalled. If that process is still the game, end it yourself; if it is not, it belongs to something else.`);
|
|
41747
42068
|
return "refused";
|
|
41748
42069
|
}
|
|
41749
|
-
const signalled = runningAs(running.pid);
|
|
42070
|
+
const signalled = await runningAs(running.pid);
|
|
41750
42071
|
const described = signalled === null ? "nothing it would name" : `${signalled.kind}: ${signalled.text}`;
|
|
41751
42072
|
let landed = true;
|
|
41752
42073
|
try {
|
|
@@ -41767,7 +42088,7 @@ class GodotServer {
|
|
|
41767
42088
|
return gone ? "gone" : "lingering";
|
|
41768
42089
|
}
|
|
41769
42090
|
spawnGame(godotPath, cmdArgs, env) {
|
|
41770
|
-
const child =
|
|
42091
|
+
const child = spawn3(godotPath, cmdArgs, { stdio: ["ignore", "pipe", "pipe"], ...env ? { env } : {} });
|
|
41771
42092
|
const cancelled = callSignal();
|
|
41772
42093
|
if (cancelled !== undefined) {
|
|
41773
42094
|
const end = () => {
|
|
@@ -41934,8 +42255,8 @@ class GodotServer {
|
|
|
41934
42255
|
}
|
|
41935
42256
|
return run.announcedPid;
|
|
41936
42257
|
}
|
|
41937
|
-
ownGameAmong(running, projectPath) {
|
|
41938
|
-
const run = this.currentRun();
|
|
42258
|
+
async ownGameAmong(running, projectPath) {
|
|
42259
|
+
const run = await this.currentRun();
|
|
41939
42260
|
if (run === null || !stillRunning(run)) {
|
|
41940
42261
|
return;
|
|
41941
42262
|
}
|
|
@@ -41946,7 +42267,7 @@ class GodotServer {
|
|
|
41946
42267
|
return its !== undefined && running.some((one) => one.pid === its) ? its : undefined;
|
|
41947
42268
|
}
|
|
41948
42269
|
async ownGameThroughTheTree(running, projectPath) {
|
|
41949
|
-
const run = this.currentRun();
|
|
42270
|
+
const run = await this.currentRun();
|
|
41950
42271
|
if (run === null || !stillRunning(run) || run.announcedPid !== undefined) {
|
|
41951
42272
|
return;
|
|
41952
42273
|
}
|
|
@@ -42032,7 +42353,7 @@ class GodotServer {
|
|
|
42032
42353
|
const editor = this.godotBridge.getStatus().editorPid;
|
|
42033
42354
|
return endpoint.editorPid === undefined || endpoint.editorPid !== editor;
|
|
42034
42355
|
}
|
|
42035
|
-
adoptRecordedRun() {
|
|
42356
|
+
async adoptRecordedRun() {
|
|
42036
42357
|
const project = this.ourProject();
|
|
42037
42358
|
const record = project === null ? null : readRunRecord(project);
|
|
42038
42359
|
if (record === null || !this.couldBeOurs(record.projectPath)) {
|
|
@@ -42053,7 +42374,7 @@ class GodotServer {
|
|
|
42053
42374
|
pickedUp: true
|
|
42054
42375
|
};
|
|
42055
42376
|
this.drainTranscript(adopted);
|
|
42056
|
-
if (!alive(record.pid) || !couldStillBeTheRecordedRun(record)) {
|
|
42377
|
+
if (!alive(record.pid) || !await couldStillBeTheRecordedRun(record)) {
|
|
42057
42378
|
adopted.log.finish();
|
|
42058
42379
|
adopted.exitCode = record.exitCode ?? null;
|
|
42059
42380
|
adopted.exitSignal = record.exitSignal ?? null;
|
|
@@ -42134,8 +42455,8 @@ class GodotServer {
|
|
|
42134
42455
|
const status = this.godotBridge.getStatus();
|
|
42135
42456
|
return this.ownProject ?? (status.connected ? status.projectPath ?? null : null);
|
|
42136
42457
|
}
|
|
42137
|
-
currentRun() {
|
|
42138
|
-
return this.activeProcess ?? this.adoptRecordedRun();
|
|
42458
|
+
async currentRun() {
|
|
42459
|
+
return this.activeProcess ?? await this.adoptRecordedRun();
|
|
42139
42460
|
}
|
|
42140
42461
|
async checkBoot(godotPath, projectPath, scene, args, userArgs, scanned, asked = {}) {
|
|
42141
42462
|
const frames = readPositiveNumber(args, "frames") ?? 3;
|
|
@@ -42174,10 +42495,14 @@ class GodotServer {
|
|
|
42174
42495
|
}
|
|
42175
42496
|
async handleWaitForRun(args) {
|
|
42176
42497
|
await this.pickUpWhatTheEditorIsPlaying();
|
|
42177
|
-
const run = this.currentRun();
|
|
42498
|
+
const run = await this.currentRun();
|
|
42178
42499
|
if (!run) {
|
|
42179
42500
|
return this.createErrorResponse(this.nothingOfOursIsRunning());
|
|
42180
42501
|
}
|
|
42502
|
+
const elsewhere = this.anotherProjectsRun(run.projectPath, args, "waited on");
|
|
42503
|
+
if (elsewhere !== null) {
|
|
42504
|
+
return elsewhere;
|
|
42505
|
+
}
|
|
42181
42506
|
const budgetMs = readPositiveNumber(args, "timeoutMs") ?? WAIT_FOR_RUN_MS;
|
|
42182
42507
|
const until = Date.now() + budgetMs;
|
|
42183
42508
|
const cancelled = callSignal();
|
|
@@ -42224,7 +42549,7 @@ class GodotServer {
|
|
|
42224
42549
|
await delay3(50);
|
|
42225
42550
|
}
|
|
42226
42551
|
}
|
|
42227
|
-
handleEditorConsole(args) {
|
|
42552
|
+
async handleEditorConsole(args) {
|
|
42228
42553
|
const status = this.godotBridge.getStatus();
|
|
42229
42554
|
const projectPath = status.projectPath ?? this.ownProject ?? undefined;
|
|
42230
42555
|
if (projectPath === undefined) {
|
|
@@ -42261,10 +42586,11 @@ class GodotServer {
|
|
|
42261
42586
|
const filtered = severity !== "info" || contains !== undefined;
|
|
42262
42587
|
const grouped = bursts(everything, readNonNegativeNumber(args, "before") ?? 3, filtered ? (entry) => answersTo(entry, severity, contains) : undefined);
|
|
42263
42588
|
const shown = grouped.slice(0, MOST_GROUPS);
|
|
42589
|
+
const stillComing = !status.connected && await this.launchedEditorIsUp();
|
|
42264
42590
|
return this.jsonTextResponse({
|
|
42265
42591
|
editorPid: editorPid ?? undefined,
|
|
42266
42592
|
projectPath,
|
|
42267
|
-
...status.connected ? {} :
|
|
42593
|
+
...status.connected ? {} : stillComing ? { stillConnecting: true } : { exitedBeforeConnecting: true },
|
|
42268
42594
|
capturedIn: console2.path,
|
|
42269
42595
|
...console2.of === undefined ? {} : {
|
|
42270
42596
|
readFromTheStart: console2.readBytes,
|
|
@@ -42288,7 +42614,7 @@ class GodotServer {
|
|
|
42288
42614
|
return this.handleEditorConsole(args);
|
|
42289
42615
|
}
|
|
42290
42616
|
await this.pickUpWhatTheEditorIsPlaying();
|
|
42291
|
-
const run = this.currentRun();
|
|
42617
|
+
const run = await this.currentRun();
|
|
42292
42618
|
if (!run) {
|
|
42293
42619
|
return this.createErrorResponse(this.nothingOfOursIsRunning());
|
|
42294
42620
|
}
|
|
@@ -42327,7 +42653,7 @@ class GodotServer {
|
|
|
42327
42653
|
notes.push(endedWithoutACode({
|
|
42328
42654
|
throughEditor: run.throughEditor,
|
|
42329
42655
|
announced: announced !== undefined || run.announcedPid !== undefined,
|
|
42330
|
-
couldAnnounce: run.projectPath !== null &&
|
|
42656
|
+
couldAnnounce: run.projectPath !== null && existsSync15(join22(run.projectPath, RUNTIME_AUTOLOAD.path)),
|
|
42331
42657
|
endedHere: typeof run.endedHere === "string",
|
|
42332
42658
|
pickedUp: run.pickedUp === true
|
|
42333
42659
|
}));
|
|
@@ -42365,16 +42691,27 @@ class GodotServer {
|
|
|
42365
42691
|
entries: forAnswer(selected.entries)
|
|
42366
42692
|
});
|
|
42367
42693
|
}
|
|
42694
|
+
anotherProjectsRun(runsProject, args, act) {
|
|
42695
|
+
const named = readNonEmptyString(args, "projectPath");
|
|
42696
|
+
if (named === undefined || runsProject === null || isSameDirectory(runsProject, named)) {
|
|
42697
|
+
return null;
|
|
42698
|
+
}
|
|
42699
|
+
return this.createErrorResponse(`The run this server holds is of ${runsProject}, not ${named}, so nothing was ${act}. A run of ${named} is held by the server that started it, in that project's own session.`);
|
|
42700
|
+
}
|
|
42368
42701
|
async handleStopProject(args) {
|
|
42369
42702
|
await this.pickUpWhatTheEditorIsPlaying();
|
|
42370
|
-
const stopped = this.currentRun();
|
|
42703
|
+
const stopped = await this.currentRun();
|
|
42371
42704
|
const named = readNumber(args, "pid");
|
|
42372
42705
|
if (named !== undefined && (stopped === null || named !== stopped.pid && named !== this.announcedPidOf(stopped))) {
|
|
42373
|
-
return await this.stopAnnouncedGame(named, readBoolean(args, "andChildren") === true);
|
|
42706
|
+
return await this.stopAnnouncedGame(named, readBoolean(args, "andChildren") === true, args);
|
|
42374
42707
|
}
|
|
42375
42708
|
if (!stopped) {
|
|
42376
42709
|
return this.createErrorResponse(this.nothingOfOursIsRunning());
|
|
42377
42710
|
}
|
|
42711
|
+
const elsewhere = this.anotherProjectsRun(stopped.projectPath, args, "stopped");
|
|
42712
|
+
if (elsewhere !== null) {
|
|
42713
|
+
return elsewhere;
|
|
42714
|
+
}
|
|
42378
42715
|
this.drainEditorOutput(stopped);
|
|
42379
42716
|
this.drainTranscript(stopped);
|
|
42380
42717
|
const wasRunning = await this.runStillGoing(stopped);
|
|
@@ -42416,16 +42753,22 @@ class GodotServer {
|
|
|
42416
42753
|
entries: forAnswer(stopped.log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries)
|
|
42417
42754
|
});
|
|
42418
42755
|
}
|
|
42419
|
-
async stopAnnouncedGame(pid, andChildren) {
|
|
42756
|
+
async stopAnnouncedGame(pid, andChildren, args) {
|
|
42420
42757
|
const announced = runtimesAnnounced();
|
|
42421
42758
|
const game = [
|
|
42422
42759
|
...this.allAnnouncedForOurProject(announced.running),
|
|
42423
42760
|
...this.allAnnouncedForOurProject(announced.unspoken)
|
|
42424
42761
|
].find((one) => one.pid === pid);
|
|
42762
|
+
if (game !== undefined) {
|
|
42763
|
+
const elsewhere = this.anotherProjectsRun(game.project.path, args, "signalled");
|
|
42764
|
+
if (elsewhere !== null) {
|
|
42765
|
+
return elsewhere;
|
|
42766
|
+
}
|
|
42767
|
+
}
|
|
42425
42768
|
if (game === undefined) {
|
|
42426
42769
|
return this.createErrorResponse(`pid ${pid} is not a game announced for this project, so nothing was signalled. editor_status lists this project's games under runtimes. A game that announces no runtime is ended by the server holding its run, or with andChildren by a stop of the run that started it.`);
|
|
42427
42770
|
}
|
|
42428
|
-
const read = game.announcedAt === undefined ? null : stillTheAnnouncedGame(pid, game.project.path, game.announcedAt);
|
|
42771
|
+
const read = game.announcedAt === undefined ? null : await stillTheAnnouncedGame(pid, game.project.path, game.announcedAt);
|
|
42429
42772
|
if (read === null) {
|
|
42430
42773
|
return this.createErrorResponse(`pid ${pid} announced itself as a game of this project, but the operating system does not describe it as this project's engine run started before that announcement (the engine with --path on the project and no editor flag), so nothing was signalled. If it is still the game, runtime_invoke can ask it to quit.`);
|
|
42431
42774
|
}
|
|
@@ -42623,6 +42966,8 @@ class GodotServer {
|
|
|
42623
42966
|
}
|
|
42624
42967
|
const checked = busy ? { unseen: [] } : await this.classesTheEditorCannotSee(args);
|
|
42625
42968
|
const unseen = checked.unseen;
|
|
42969
|
+
const broughtIn = blind.unseen.map((one) => one.className).filter((name) => !unseen.some((still) => still.className === name));
|
|
42970
|
+
const dependents = busy || projectPath === "" || broughtIn.length === 0 ? null : await this.reloadDependents(projectPath, broughtIn);
|
|
42626
42971
|
const stillGone = lost.filter((name) => !restored.includes(name));
|
|
42627
42972
|
const notes = [];
|
|
42628
42973
|
if (busy) {
|
|
@@ -42634,13 +42979,16 @@ class GodotServer {
|
|
|
42634
42979
|
} else if (unseen.length > 0) {
|
|
42635
42980
|
notes.push("The scan finished and these classes are still not in the list the editor resolves against, so every use of them reads as an unknown identifier. Its walk skips a file another engine has already imported. Rescan again on its own, which is the measured cure and needs no change to the declaring script, or restart the editor with editor_launch restart.");
|
|
42636
42981
|
}
|
|
42982
|
+
if (dependents !== null && dependents.notReloaded.length > 0) {
|
|
42983
|
+
notes.push(`The scan brought in ${broughtIn.join(", ")}, and ${dependents.notReloaded.map((one) => one.scriptPath).join(", ")} ${dependents.notReloaded.length === 1 ? "names one of them and" : "name them and"} could not be reloaded, so the editor still holds what it compiled while it could not see the class and reports that to the language server. editor_rescan with reloadScript on each, or editor_launch restart.`);
|
|
42984
|
+
}
|
|
42637
42985
|
if (stillAtMissingPaths.length > 0) {
|
|
42638
42986
|
notes.push(`The scan wrote the class cache with ${stillAtMissingPaths.join(", ")} at a path that is not on disk, and rebuilding the cache from the files did not take it out. The next engine to read the cache fails on "Could not parse global class" in whichever correct script shares the bare name. Restart the editor with editor_launch restart, then run project_import refresh_classes.`);
|
|
42639
42987
|
} else if (dropped.length > 0) {
|
|
42640
42988
|
notes.push(`The scan wrote the class cache from the list this editor is holding, and that list still has ${dropped.join(", ")} for a script that is gone, so the cache was rebuilt from the files without it. The editor goes on holding it and writes it back on every scan until it is restarted with editor_launch restart; until then a scan is followed by this same rebuild.`);
|
|
42641
42989
|
}
|
|
42642
42990
|
return this.jsonTextResponse({
|
|
42643
|
-
ok: !busy && unseen.length === 0 && stillGone.length === 0 && stillAtMissingPaths.length === 0 && checked.unchecked === undefined,
|
|
42991
|
+
ok: !busy && unseen.length === 0 && stillGone.length === 0 && stillAtMissingPaths.length === 0 && (dependents === null || dependents.notReloaded.length === 0) && checked.unchecked === undefined,
|
|
42644
42992
|
stillWorking: busy,
|
|
42645
42993
|
waitedMs: Date.now() - started,
|
|
42646
42994
|
waitedForEditorScanMs: waitedForEditorMs > 0 ? waitedForEditorMs : undefined,
|
|
@@ -42648,6 +42996,9 @@ class GodotServer {
|
|
|
42648
42996
|
reloadedMethods: reloaded.methods,
|
|
42649
42997
|
reloadProblem: reloaded.problem,
|
|
42650
42998
|
unseenByEditor: unseen.length > 0 ? unseen : undefined,
|
|
42999
|
+
broughtIn: broughtIn.length > 0 ? broughtIn : undefined,
|
|
43000
|
+
dependentsReloaded: dependents !== null && dependents.reloaded.length > 0 ? dependents.reloaded : undefined,
|
|
43001
|
+
dependentsNotReloaded: dependents !== null && dependents.notReloaded.length > 0 ? dependents.notReloaded : undefined,
|
|
42651
43002
|
cacheLost: lost.length > 0 ? lost : undefined,
|
|
42652
43003
|
cacheRestored: restored.length > 0 ? restored : undefined,
|
|
42653
43004
|
cacheDropped: dropped.length > 0 ? dropped : undefined,
|
|
@@ -42656,6 +43007,33 @@ class GodotServer {
|
|
|
42656
43007
|
note: notes.length > 0 ? notes.join(" ") : undefined
|
|
42657
43008
|
});
|
|
42658
43009
|
}
|
|
43010
|
+
async reloadDependents(projectPath, classes) {
|
|
43011
|
+
const reloaded = [];
|
|
43012
|
+
const notReloaded = [];
|
|
43013
|
+
for (const { script: scriptPath, tool } of scriptsNaming(projectPath, classes)) {
|
|
43014
|
+
if (tool) {
|
|
43015
|
+
notReloaded.push({
|
|
43016
|
+
scriptPath,
|
|
43017
|
+
problem: "a @tool script, which runs inside the editor, so it is not reloaded from here; editor_launch restart recompiles it"
|
|
43018
|
+
});
|
|
43019
|
+
continue;
|
|
43020
|
+
}
|
|
43021
|
+
try {
|
|
43022
|
+
const answer = asParams(await this.godotBridge.invokeTool("reload_script", { scriptPath }));
|
|
43023
|
+
if (readArray(answer, "methods") === undefined) {
|
|
43024
|
+
notReloaded.push({
|
|
43025
|
+
scriptPath,
|
|
43026
|
+
problem: "the editor would not say what it has after reloading it"
|
|
43027
|
+
});
|
|
43028
|
+
} else {
|
|
43029
|
+
reloaded.push(scriptPath);
|
|
43030
|
+
}
|
|
43031
|
+
} catch (error) {
|
|
43032
|
+
notReloaded.push({ scriptPath, problem: errorMessage(error) });
|
|
43033
|
+
}
|
|
43034
|
+
}
|
|
43035
|
+
return { reloaded, notReloaded };
|
|
43036
|
+
}
|
|
42659
43037
|
async rebuildCacheAfterLoss(projectPath, lost) {
|
|
42660
43038
|
if (projectPath === "") {
|
|
42661
43039
|
return [];
|
|
@@ -42838,9 +43216,9 @@ class GodotServer {
|
|
|
42838
43216
|
return { heldAt: undefined, heldUnknown: true, heldNote: reply.message };
|
|
42839
43217
|
}
|
|
42840
43218
|
async ownRunOnItsWay(projectPath, budgetMs) {
|
|
42841
|
-
const run = this.currentRun();
|
|
43219
|
+
const run = await this.currentRun();
|
|
42842
43220
|
const project = projectPath ?? run?.projectPath ?? null;
|
|
42843
|
-
if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !
|
|
43221
|
+
if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync15(join22(project, RUNTIME_AUTOLOAD.path)) || !await this.runStillGoing(run)) {
|
|
42844
43222
|
return null;
|
|
42845
43223
|
}
|
|
42846
43224
|
const already = runtimesAnnounced().running;
|
|
@@ -42866,8 +43244,8 @@ class GodotServer {
|
|
|
42866
43244
|
}
|
|
42867
43245
|
const endpoint = await announcedSince(project, new Set(already.map((one) => one.pid)), {
|
|
42868
43246
|
budgetMs: Math.min(budgetMs, remaining),
|
|
42869
|
-
accept: (one) => this.isTheGameOf(this.
|
|
42870
|
-
giveUp: () => this.
|
|
43247
|
+
accept: (one) => this.isTheGameOf(this.activeProcess, one, project),
|
|
43248
|
+
giveUp: () => this.activeProcess !== run || this.dapClient?.isStopped() === true || !stillRunning(run)
|
|
42871
43249
|
});
|
|
42872
43250
|
if (endpoint !== null) {
|
|
42873
43251
|
const tiedMeanwhile = () => run.announcedPid !== undefined;
|
|
@@ -42876,7 +43254,7 @@ class GodotServer {
|
|
|
42876
43254
|
}
|
|
42877
43255
|
return null;
|
|
42878
43256
|
}
|
|
42879
|
-
if (this.currentRun() !== run || !await this.runStillGoing(run)) {
|
|
43257
|
+
if (await this.currentRun() !== run || !await this.runStillGoing(run)) {
|
|
42880
43258
|
return null;
|
|
42881
43259
|
}
|
|
42882
43260
|
return `The game this server started (${which}) is still coming up and has not announced its runtime after a wait of ${Math.min(budgetMs, remaining)}ms, so there is nothing to talk to yet. It is still running: ask again in a moment rather than starting another.`;
|
|
@@ -42892,7 +43270,7 @@ class GodotServer {
|
|
|
42892
43270
|
}
|
|
42893
43271
|
const announced = runtimesAnnounced();
|
|
42894
43272
|
const wanted = typeof projectPath === "string" ? projectPath : this.ownProject ?? undefined;
|
|
42895
|
-
const own = typeof pid === "number" ? undefined : this.ownGameAmong(announced.running, wanted) ?? await this.ownGameThroughTheTree(announced.running, wanted);
|
|
43273
|
+
const own = typeof pid === "number" ? undefined : await this.ownGameAmong(announced.running, wanted) ?? await this.ownGameThroughTheTree(announced.running, wanted);
|
|
42896
43274
|
const choice = chooseRuntime(announced.running, wanted, announced.unspoken, typeof pid === "number" ? pid : own);
|
|
42897
43275
|
if ("problem" in choice) {
|
|
42898
43276
|
return this.createErrorResponse(choice.problem);
|
|
@@ -42919,8 +43297,8 @@ class GodotServer {
|
|
|
42919
43297
|
return this.createErrorResponse(refusal);
|
|
42920
43298
|
}
|
|
42921
43299
|
}
|
|
42922
|
-
const screenshotDir = expectsScreenshot ?
|
|
42923
|
-
const screenshotPath = screenshotDir ?
|
|
43300
|
+
const screenshotDir = expectsScreenshot ? scratchDirectory("runtime-screenshot") : null;
|
|
43301
|
+
const screenshotPath = screenshotDir ? join22(screenshotDir, "capture.png") : null;
|
|
42924
43302
|
try {
|
|
42925
43303
|
const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs, !expectsScreenshot);
|
|
42926
43304
|
if (!reply.ok && reply.requestId !== undefined) {
|
|
@@ -43066,6 +43444,7 @@ var init_server3 = __esm(() => {
|
|
|
43066
43444
|
init_mcp();
|
|
43067
43445
|
init_stdio2();
|
|
43068
43446
|
init_types();
|
|
43447
|
+
init_alive();
|
|
43069
43448
|
init_boot_note();
|
|
43070
43449
|
init_breakpoint_note();
|
|
43071
43450
|
init_bridge_announce();
|
|
@@ -43101,7 +43480,7 @@ var init_server3 = __esm(() => {
|
|
|
43101
43480
|
init_setup();
|
|
43102
43481
|
init_tool_definitions();
|
|
43103
43482
|
init_update_check();
|
|
43104
|
-
run5 =
|
|
43483
|
+
run5 = promisify6(execFile6);
|
|
43105
43484
|
__dirname2 = dirname15(fileURLToPath5(import.meta.url));
|
|
43106
43485
|
CONSOLE_WRAPPER = /_console(\.exe)?$/i;
|
|
43107
43486
|
LONGEST_BOOT_NOTED_MS = 5 * 60000;
|
|
@@ -43151,14 +43530,14 @@ var init_server3 = __esm(() => {
|
|
|
43151
43530
|
init_bridge_announce();
|
|
43152
43531
|
init_errors();
|
|
43153
43532
|
init_godot_path();
|
|
43154
|
-
import { existsSync as
|
|
43155
|
-
import { join as
|
|
43533
|
+
import { existsSync as existsSync16 } from "node:fs";
|
|
43534
|
+
import { join as join23, resolve as resolve6 } from "node:path";
|
|
43156
43535
|
|
|
43157
43536
|
// src/harnesses.ts
|
|
43158
43537
|
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
43159
43538
|
import { homedir as homedir2 } from "node:os";
|
|
43160
43539
|
import { dirname as dirname3, join as join4 } from "node:path";
|
|
43161
|
-
import
|
|
43540
|
+
import process4 from "node:process";
|
|
43162
43541
|
|
|
43163
43542
|
// node_modules/yaml/dist/index.js
|
|
43164
43543
|
var composer = require_composer();
|
|
@@ -43726,12 +44105,12 @@ var HARNESSES = [
|
|
|
43726
44105
|
}
|
|
43727
44106
|
];
|
|
43728
44107
|
function appDataRoot() {
|
|
43729
|
-
return
|
|
44108
|
+
return process4.env["APPDATA"] ?? join4(homedir2(), "AppData", "Roaming");
|
|
43730
44109
|
}
|
|
43731
44110
|
function homePath(path) {
|
|
43732
44111
|
return typeof path === "string" ? join4(homedir2(), path) : join4(appDataRoot(), path.appData);
|
|
43733
44112
|
}
|
|
43734
|
-
function fileFor(harness, platform =
|
|
44113
|
+
function fileFor(harness, platform = process4.platform) {
|
|
43735
44114
|
if (typeof harness.file === "string") {
|
|
43736
44115
|
return harness.file;
|
|
43737
44116
|
}
|
|
@@ -43744,7 +44123,7 @@ function configPath(harness, projectPath) {
|
|
|
43744
44123
|
}
|
|
43745
44124
|
return homePath(file);
|
|
43746
44125
|
}
|
|
43747
|
-
function displayPath(harness, platform =
|
|
44126
|
+
function displayPath(harness, platform = process4.platform) {
|
|
43748
44127
|
const file = fileFor(harness, platform);
|
|
43749
44128
|
if (typeof file !== "string") {
|
|
43750
44129
|
return `%APPDATA%/${file.appData.replaceAll("\\", "/")}`;
|
|
@@ -44022,10 +44401,10 @@ init_headless();
|
|
|
44022
44401
|
init_issues();
|
|
44023
44402
|
|
|
44024
44403
|
// src/prompt.ts
|
|
44025
|
-
import
|
|
44404
|
+
import process8 from "node:process";
|
|
44026
44405
|
import { createInterface } from "node:readline/promises";
|
|
44027
44406
|
function interactive() {
|
|
44028
|
-
return
|
|
44407
|
+
return process8.stdin.isTTY && process8.stdout.isTTY;
|
|
44029
44408
|
}
|
|
44030
44409
|
|
|
44031
44410
|
class Ask {
|
|
@@ -44034,7 +44413,7 @@ class Ask {
|
|
|
44034
44413
|
if (!interactive()) {
|
|
44035
44414
|
return fallback;
|
|
44036
44415
|
}
|
|
44037
|
-
this.line ??= createInterface({ input:
|
|
44416
|
+
this.line ??= createInterface({ input: process8.stdin, output: process8.stdout });
|
|
44038
44417
|
const answer = (await this.line.question(`${question} ${fallback ? "[Y/n]" : "[y/N]"} `)).trim();
|
|
44039
44418
|
return answer === "" ? fallback : answer.toLowerCase().startsWith("y");
|
|
44040
44419
|
}
|
|
@@ -44051,8 +44430,8 @@ init_setup();
|
|
|
44051
44430
|
// src/skill.ts
|
|
44052
44431
|
init_setup();
|
|
44053
44432
|
init_tool_definitions();
|
|
44054
|
-
import { existsSync as
|
|
44055
|
-
import { basename as basename2, dirname as dirname5, join as
|
|
44433
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync5, readdirSync as readdirSync5, rmSync as rmSync5, writeFileSync as writeFileSync6 } from "node:fs";
|
|
44434
|
+
import { basename as basename2, dirname as dirname5, join as join10 } from "node:path";
|
|
44056
44435
|
|
|
44057
44436
|
// src/tool-reference.ts
|
|
44058
44437
|
init_headless_operations();
|
|
@@ -44148,7 +44527,7 @@ function renderToolsMarkdown() {
|
|
|
44148
44527
|
|
|
44149
44528
|
// src/skill.ts
|
|
44150
44529
|
var SKILL_NAME = "gdharness";
|
|
44151
|
-
var SHARED_SKILLS =
|
|
44530
|
+
var SHARED_SKILLS = join10(".agents", "skills");
|
|
44152
44531
|
function skillMarkdown(version) {
|
|
44153
44532
|
return `---
|
|
44154
44533
|
name: ${SKILL_NAME}
|
|
@@ -44226,6 +44605,8 @@ that is running, and the project on disk. ${TOOL_SPECS.length} tools, named \`do
|
|
|
44226
44605
|
| Answer a dialog | \`runtime_input click\` on its button, or \`runtime_input key\` with \`keycode\` \`Escape\` to dismiss it: an [AcceptDialog] reads that key itself and never asks the InputMap, so \`ui_cancel\` leaves it standing |
|
|
44227
44606
|
| Press a bound key | \`runtime_input action\` with the action's name, or \`runtime_input key\` with \`keycode\`, a whole press unless you hold it |
|
|
44228
44607
|
| Where a 3D node is on screen | \`runtime_inspect\` \`rect\`, rather than unprojecting by hand |
|
|
44608
|
+
| Wait for whichever of several screens comes next | \`runtime_wait until\` with \`says\` naming each, separated by \`|\`: \`"*won*|*lost*|*turn 3*"\` is met by the first of them to appear. Read as one glob, the bars were characters no screen says and the wait ran out its whole timeout |
|
|
44609
|
+
| Ask the game a question its own code answers | \`runtime_invoke call\` on the method that answers it, reached through what a node holds (\`_ladder:encounter:opponent:most_answered\`), with object arguments given as paths to what the game holds and a list of paths for a list: the game's own rules answer, in the running game, before anything is committed |
|
|
44229
44610
|
| Wait for something in a running game | \`runtime_wait\`, never a sleep; \`until\` with \`says\` for a panel that rebuilds its own labels. The words have to be shown: a button that exists hidden through an animation does not satisfy it until it appears, unless \`includeHidden\` says otherwise |
|
|
44230
44611
|
| What every label on a screen says | \`runtime_inspect tree\` with \`properties\` naming \`text\`, which reads it off every node that has one; \`includeProperties\` is every stored property of every node and ran to seventy-six thousand characters for one row |
|
|
44231
44612
|
| Whether this engine has a method or property, and what it takes | \`editor_classes info\` with \`member\`, which answers that one member and which class declares it rather than the whole class |
|
|
@@ -44337,27 +44718,27 @@ whatever the engine said on stderr comes back under \`engine_messages\`.
|
|
|
44337
44718
|
function skillFiles(version) {
|
|
44338
44719
|
return new Map([
|
|
44339
44720
|
["SKILL.md", skillMarkdown(version)],
|
|
44340
|
-
[
|
|
44721
|
+
[join10("references", "tools.md"), renderToolsMarkdown()]
|
|
44341
44722
|
]);
|
|
44342
44723
|
}
|
|
44343
44724
|
function skillDirectories(harnesses, projectPath, exists) {
|
|
44344
|
-
const directories = [
|
|
44725
|
+
const directories = [join10(projectPath, SHARED_SKILLS, SKILL_NAME)];
|
|
44345
44726
|
for (const harness of harnesses) {
|
|
44346
44727
|
if (harness.skills === undefined) {
|
|
44347
44728
|
continue;
|
|
44348
44729
|
}
|
|
44349
|
-
const own =
|
|
44350
|
-
if ((!harness.skills.shared || exists(own)) && !directories.includes(
|
|
44351
|
-
directories.push(
|
|
44730
|
+
const own = join10(projectPath, harness.skills.dir);
|
|
44731
|
+
if ((!harness.skills.shared || exists(own)) && !directories.includes(join10(own, SKILL_NAME))) {
|
|
44732
|
+
directories.push(join10(own, SKILL_NAME));
|
|
44352
44733
|
}
|
|
44353
44734
|
}
|
|
44354
44735
|
return directories;
|
|
44355
44736
|
}
|
|
44356
44737
|
function everySkillDirectory(projectPath, harnesses) {
|
|
44357
|
-
const directories = new Set([
|
|
44738
|
+
const directories = new Set([join10(projectPath, SHARED_SKILLS, SKILL_NAME)]);
|
|
44358
44739
|
for (const harness of harnesses) {
|
|
44359
44740
|
if (harness.skills !== undefined) {
|
|
44360
|
-
directories.add(
|
|
44741
|
+
directories.add(join10(projectPath, harness.skills.dir, SKILL_NAME));
|
|
44361
44742
|
}
|
|
44362
44743
|
}
|
|
44363
44744
|
return [...directories];
|
|
@@ -44365,7 +44746,7 @@ function everySkillDirectory(projectPath, harnesses) {
|
|
|
44365
44746
|
function removeSkill(directories, projectPath) {
|
|
44366
44747
|
const removed = [];
|
|
44367
44748
|
for (const directory of directories) {
|
|
44368
|
-
if (
|
|
44749
|
+
if (existsSync9(directory)) {
|
|
44369
44750
|
rmSync5(directory, { recursive: true, force: true });
|
|
44370
44751
|
pruneEmpty(dirname5(directory), projectPath);
|
|
44371
44752
|
removed.push(directory);
|
|
@@ -44374,10 +44755,10 @@ function removeSkill(directories, projectPath) {
|
|
|
44374
44755
|
return removed;
|
|
44375
44756
|
}
|
|
44376
44757
|
function pruneEmpty(directory, projectPath) {
|
|
44377
|
-
const shared =
|
|
44758
|
+
const shared = join10(projectPath, ".agents");
|
|
44378
44759
|
let path = directory;
|
|
44379
44760
|
while (path !== projectPath && (basename2(path) === "skills" || path === shared)) {
|
|
44380
|
-
if (
|
|
44761
|
+
if (readdirSync5(path).length > 0) {
|
|
44381
44762
|
return;
|
|
44382
44763
|
}
|
|
44383
44764
|
rmSync5(path, { recursive: true, force: true });
|
|
@@ -44387,10 +44768,10 @@ function pruneEmpty(directory, projectPath) {
|
|
|
44387
44768
|
function writeSkill(directories, version) {
|
|
44388
44769
|
const files = skillFiles(version);
|
|
44389
44770
|
return directories.map((directory) => {
|
|
44390
|
-
const replaced =
|
|
44771
|
+
const replaced = existsSync9(directory);
|
|
44391
44772
|
rmSync5(directory, { recursive: true, force: true });
|
|
44392
44773
|
for (const [name, contents] of files) {
|
|
44393
|
-
const path =
|
|
44774
|
+
const path = join10(directory, name);
|
|
44394
44775
|
mkdirSync5(dirname5(path), { recursive: true });
|
|
44395
44776
|
writeFileSync6(path, contents, "utf8");
|
|
44396
44777
|
}
|
|
@@ -44400,13 +44781,13 @@ function writeSkill(directories, version) {
|
|
|
44400
44781
|
|
|
44401
44782
|
// src/version.ts
|
|
44402
44783
|
import { readFileSync as readFileSync8 } from "node:fs";
|
|
44403
|
-
import { dirname as dirname6, join as
|
|
44784
|
+
import { dirname as dirname6, join as join11 } from "node:path";
|
|
44404
44785
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
44405
44786
|
function getLocalVersion() {
|
|
44406
44787
|
const currentDirectory = dirname6(fileURLToPath2(import.meta.url));
|
|
44407
44788
|
const packageCandidates = [
|
|
44408
|
-
|
|
44409
|
-
|
|
44789
|
+
join11(currentDirectory, "..", "package.json"),
|
|
44790
|
+
join11(currentDirectory, "..", "..", "package.json")
|
|
44410
44791
|
];
|
|
44411
44792
|
for (const packagePath of packageCandidates) {
|
|
44412
44793
|
try {
|
|
@@ -44428,7 +44809,7 @@ class UsageError extends Refusal {
|
|
|
44428
44809
|
function projectArgument(at) {
|
|
44429
44810
|
const named = args.slice(at).find((value) => !value.startsWith("--"));
|
|
44430
44811
|
const projectPath = resolve6(named ?? ".");
|
|
44431
|
-
if (!
|
|
44812
|
+
if (!existsSync16(join23(projectPath, "project.godot"))) {
|
|
44432
44813
|
throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
|
|
44433
44814
|
}
|
|
44434
44815
|
return projectPath;
|
|
@@ -44467,7 +44848,7 @@ async function registerRuntime(godot, projectPath) {
|
|
|
44467
44848
|
return;
|
|
44468
44849
|
}
|
|
44469
44850
|
console.log(`${RUNTIME_AUTOLOAD.name} autoload left at ${named}, which this project registers itself`);
|
|
44470
|
-
if (!
|
|
44851
|
+
if (!existsSync16(join23(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
|
|
44471
44852
|
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}`);
|
|
44472
44853
|
}
|
|
44473
44854
|
}
|
|
@@ -44599,7 +44980,7 @@ async function setup() {
|
|
|
44599
44980
|
console.log("no harness configured; name one yourself, and gdharness harnesses lists them all");
|
|
44600
44981
|
}
|
|
44601
44982
|
if (!args.includes("--no-skill")) {
|
|
44602
|
-
const directories = skillDirectories(chosen.harnesses, projectPath,
|
|
44983
|
+
const directories = skillDirectories(chosen.harnesses, projectPath, existsSync16);
|
|
44603
44984
|
for (const written of writeSkill(directories, getLocalVersion())) {
|
|
44604
44985
|
console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
|
|
44605
44986
|
}
|
|
@@ -44693,7 +45074,7 @@ async function upgrade() {
|
|
|
44693
45074
|
moved.push(took.from);
|
|
44694
45075
|
}
|
|
44695
45076
|
}
|
|
44696
|
-
for (const written of writeSkill(skillDirectories(already, projectPath,
|
|
45077
|
+
for (const written of writeSkill(skillDirectories(already, projectPath, existsSync16), version)) {
|
|
44697
45078
|
console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
|
|
44698
45079
|
}
|
|
44699
45080
|
console.log(`
|
|
@@ -44702,7 +45083,7 @@ On ${version}. What is left for you:`);
|
|
|
44702
45083
|
` + ` so it gives an open editor nothing to restart for. editor_status says addonIsStale
|
|
44703
45084
|
` + " if the editor is older than that." : ` 1. The open editor is still running the addons it loaded at startup. Restart it with the
|
|
44704
45085
|
` + " editor_launch restart tool, which closes and reopens the window.");
|
|
44705
|
-
const serving = serverServing(projectPath);
|
|
45086
|
+
const serving = await serverServing(projectPath);
|
|
44706
45087
|
console.log(harnessNote({ moved, written, byHand }, [launch.command, ...launch.args].join(" "), serving === null ? null : { server: serving, isThisVersion: serving.version === version }));
|
|
44707
45088
|
}
|
|
44708
45089
|
function doctorReport(projectPath) {
|